@vincentt-xr/sdk 2.0.0-alpha.7 → 2.0.0-alpha.8

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 (275) hide show
  1. package/GROUNDING.md +1109 -13
  2. package/dist/{DebuggerUI-BjOMr8q_.js → DebuggerUI-C-ldiozL.js} +1 -1
  3. package/dist/{DebuggerUI-B6bP6P9r.cjs → DebuggerUI-CWX5EtQv.cjs} +1 -1
  4. package/dist/FaceMeshRenderer-BEEXZwS6.cjs +158 -0
  5. package/dist/FaceMeshRenderer-UuH5sTXG.js +1322 -0
  6. package/dist/FaceRetouch-C7h7x9kd.js +683 -0
  7. package/dist/FaceRetouch-DqftT26Y.cjs +134 -0
  8. package/dist/GLTFLoader-C5q63sKO.js +1633 -0
  9. package/dist/GLTFLoader-Cb_CRwmG.cjs +1 -0
  10. package/dist/SegmentationOverlay-DQuW3dsc.js +133 -0
  11. package/dist/SegmentationOverlay-aWVod8Kl.cjs +1 -0
  12. package/dist/assets/cache.d.ts +99 -0
  13. package/dist/assets/estimate.d.ts +11 -0
  14. package/dist/assets/index.d.ts +7 -0
  15. package/dist/assets/loaders.d.ts +28 -0
  16. package/dist/assets/react.d.ts +33 -0
  17. package/dist/calibration/calibration.d.ts +70 -0
  18. package/dist/calibration/index.d.ts +6 -0
  19. package/dist/calibration/storage.d.ts +13 -0
  20. package/dist/calibration/types.d.ts +53 -0
  21. package/dist/calibration/useXRCalibration.d.ts +46 -0
  22. package/dist/camera/CameraPermissionGate.d.ts +26 -0
  23. package/dist/camera/devices.d.ts +26 -0
  24. package/dist/camera/index.d.ts +8 -0
  25. package/dist/camera/permission.d.ts +86 -0
  26. package/dist/camera/useCameraPermission.d.ts +35 -0
  27. package/dist/capabilities/detect.d.ts +45 -0
  28. package/dist/capabilities/index.d.ts +5 -0
  29. package/dist/capabilities/types.d.ts +120 -0
  30. package/dist/capabilities/useXRCapabilities.d.ts +18 -0
  31. package/dist/compat/environment.d.ts +5 -0
  32. package/dist/compat/index.d.ts +4 -0
  33. package/dist/compat/run.d.ts +18 -0
  34. package/dist/compat/scenarios.d.ts +10 -0
  35. package/dist/compat/types.d.ts +101 -0
  36. package/dist/components/BodyTracker/index.d.ts +9 -0
  37. package/dist/components/FaceMesh/canonical/CanonicalFaceMeshRenderer.d.ts +33 -3
  38. package/dist/components/FaceMesh/canonical/parseCanonicalFaceModel.d.ts +12 -0
  39. package/dist/components/FaceMesh/canonical/shaders.d.ts +1 -1
  40. package/dist/components/FaceMesh/index.d.ts +1 -1
  41. package/dist/components/GestureListener/index.d.ts +11 -0
  42. package/dist/components/HandTracker/index.d.ts +10 -0
  43. package/dist/components/HolisticTracker/index.d.ts +69 -0
  44. package/dist/components/HolisticTracker/settings.d.ts +58 -0
  45. package/dist/components/HolisticTracker/signals.d.ts +49 -0
  46. package/dist/components/IrisEffect/IrisMaterial.d.ts +73 -0
  47. package/dist/components/IrisEffect/index.d.ts +51 -0
  48. package/dist/components/IrisEffect/settings.d.ts +117 -0
  49. package/dist/components/LipEffect/LipEffect.d.ts +7 -1
  50. package/dist/components/LipEffect/LipEffectMaterial.d.ts +43 -7
  51. package/dist/components/LipEffect/LipEffects.d.ts +9 -0
  52. package/dist/components/LipEffect/defaults.d.ts +27 -4
  53. package/dist/components/LipEffect/index.d.ts +11 -3
  54. package/dist/components/LipEffect/settings.d.ts +271 -0
  55. package/dist/components/LipEffect/types.d.ts +56 -9
  56. package/dist/components/LipEffect/useLipDesignTextures.d.ts +11 -0
  57. package/dist/components/LipEffect/uvGuide.d.ts +59 -0
  58. package/dist/components/PersonCutout/index.d.ts +50 -0
  59. package/dist/components/PersonCutout/personCutout.d.ts +16 -0
  60. package/dist/components/ScreenImage/components/ScreenImageBody.d.ts +1 -1
  61. package/dist/components/ScreenImage/index.d.ts +3 -1
  62. package/dist/components/ScreenImage/materials/ScreenImageBlendMaterial.d.ts +1 -1
  63. package/dist/components/ScreenImage/materials/ScreenImageMaskMaterial.d.ts +11 -0
  64. package/dist/components/ScreenImage/source.d.ts +6 -0
  65. package/dist/components/ScreenImage/types.d.ts +18 -0
  66. package/dist/components/SegmentationOverlay/SegmentationOverlay.d.ts +37 -0
  67. package/dist/components/SegmentationOverlay/SegmentationOverlayMaterial.d.ts +31 -0
  68. package/dist/components/SegmentationOverlay/index.d.ts +9 -0
  69. package/dist/components/SegmentationOverlay/maskTexture.d.ts +23 -0
  70. package/dist/components/SegmentationOverlay/providers.d.ts +72 -0
  71. package/dist/components/SegmentationOverlay/settings.d.ts +65 -0
  72. package/dist/components/XRProvider/index.d.ts +16 -2
  73. package/dist/components/XRViewport/hooks/useViewportExternalFaces.d.ts +1 -1
  74. package/dist/components/XRViewport/hooks/useViewportMediaSource.d.ts +1 -1
  75. package/dist/components/XRViewport/hooks/useViewportPipeline.d.ts +1 -1
  76. package/dist/components/index.d.ts +3 -0
  77. package/dist/constants/index.d.ts +4 -1
  78. package/dist/contexts-CO0sd4JQ.cjs +1 -0
  79. package/dist/{contexts-ByhzppYl.js → contexts-FzlnnMYD.js} +252 -246
  80. package/dist/coordinates/feed.d.ts +52 -0
  81. package/dist/coordinates/index.d.ts +5 -0
  82. package/dist/coordinates/spaces.d.ts +49 -0
  83. package/dist/coordinates/viewport.d.ts +22 -0
  84. package/dist/coordinates/world.d.ts +15 -0
  85. package/dist/debug-ui/gesture-settings/index.d.ts +14 -0
  86. package/dist/debug-ui/holistic-tracker-settings/index.d.ts +16 -0
  87. package/dist/debug-ui/index.d.ts +14 -0
  88. package/dist/debug-ui/iris-effect-settings/index.d.ts +12 -0
  89. package/dist/debug-ui/lip-effect-settings/index.d.ts +15 -0
  90. package/dist/debug-ui/media-source.cjs +1 -1
  91. package/dist/debug-ui/media-source.js +1 -1
  92. package/dist/debug-ui/product-hotspots/index.d.ts +24 -0
  93. package/dist/debug-ui/segmentation-overlay-settings/index.d.ts +12 -0
  94. package/dist/debug-ui/xr-diagnostics/index.d.ts +25 -0
  95. package/dist/debug-ui.cjs +3 -3
  96. package/dist/debug-ui.js +2057 -1687
  97. package/dist/detector-K8aej4MB.js +59 -0
  98. package/dist/detector-ijypGtse.cjs +1 -0
  99. package/dist/entries/core.d.ts +18 -1
  100. package/dist/entries/face-effects.d.ts +11 -3
  101. package/dist/entries/low-level.d.ts +22 -3
  102. package/dist/entries/product.d.ts +11 -0
  103. package/dist/entries/tracking.d.ts +24 -1
  104. package/dist/face-effects.cjs +155 -1
  105. package/dist/face-effects.js +1134 -2197
  106. package/dist/gestures/definitions.d.ts +85 -0
  107. package/dist/gestures/detector.d.ts +87 -0
  108. package/dist/gestures/index.d.ts +8 -0
  109. package/dist/gestures/measures.d.ts +101 -0
  110. package/dist/gestures/settings.d.ts +49 -0
  111. package/dist/{getFaceInfo-C4QpCQIY.js → getFaceInfo-BkZfaQs-.js} +5 -5
  112. package/dist/{getFaceInfo-fA1bFBub.cjs → getFaceInfo-ahPxQFGu.cjs} +1 -1
  113. package/dist/hooks/selectors.d.ts +68 -1
  114. package/dist/hooks/useCalibrationFaceTracker.d.ts +1 -1
  115. package/dist/hooks/useFaceInfo.d.ts +13 -1
  116. package/dist/hooks/useFaceInfos.d.ts +1 -1
  117. package/dist/hooks/useGesture.d.ts +32 -0
  118. package/dist/hooks/useIrisTracking.d.ts +45 -0
  119. package/dist/hooks/useXRDiagnostics.d.ts +81 -0
  120. package/dist/hooks/useXRModel.d.ts +7 -0
  121. package/dist/hooks/useXRSession/facade.d.ts +42 -2
  122. package/dist/hooks/useXRSession/index.d.ts +2 -2
  123. package/dist/hooks/useXRSession/reason.d.ts +3 -1
  124. package/dist/hooks/useXRSession/registry.d.ts +14 -0
  125. package/dist/hooks/useXRSession/store.d.ts +52 -3
  126. package/dist/index--RgGzZ6X.cjs +47 -0
  127. package/dist/{index-VJ0SJwmG.js → index-CHVbH0hz.js} +4 -4
  128. package/dist/index-CeVkgsag.cjs +1 -0
  129. package/dist/index-CnGox6Xr.js +3054 -0
  130. package/dist/{index-ByoNj0QP.js → index-CsUdQ-pn.js} +113 -111
  131. package/dist/{index-BjmTe9eM.cjs → index-DKBAh6hV.cjs} +1 -1
  132. package/dist/index-DsZwTfG0.cjs +1 -0
  133. package/dist/index-Dw6tj2Mi.js +144 -0
  134. package/dist/index-OCPjOaeP.cjs +1 -0
  135. package/dist/{index-DXT3WccE.js → index-s9pF4zkG.js} +1065 -1101
  136. package/dist/internal-BYza1h8_.cjs +1 -0
  137. package/dist/{internal-Dmk1cTXq.js → internal-vsfNQycj.js} +15 -16
  138. package/dist/internal.cjs +1 -1
  139. package/dist/internal.js +2 -2
  140. package/dist/iris/compute.d.ts +25 -0
  141. package/dist/iris/index.d.ts +4 -0
  142. package/dist/iris/lead.d.ts +19 -0
  143. package/dist/iris/types.d.ts +87 -0
  144. package/dist/landmarkMask-CbTFzBYG.js +224 -0
  145. package/dist/landmarkMask-u7es2k4X.cjs +1 -0
  146. package/dist/low-level.cjs +1 -1
  147. package/dist/low-level.js +1085 -634
  148. package/dist/main.cjs +1 -1
  149. package/dist/main.js +1519 -465
  150. package/dist/measures-9dQPdw96.js +154 -0
  151. package/dist/measures-KfJzpvod.cjs +1 -0
  152. package/dist/models/Base/index.d.ts +49 -0
  153. package/dist/models/Body/index.d.ts +1 -1
  154. package/dist/models/Face/index.d.ts +1 -1
  155. package/dist/models/Hand/GestureTracking.d.ts +1 -1
  156. package/dist/models/Hand/HandTracking.d.ts +1 -1
  157. package/dist/models/Holistic/index.d.ts +49 -0
  158. package/dist/models/Holistic/result.d.ts +68 -0
  159. package/dist/models/SemanticSegmentation/index.d.ts +31 -0
  160. package/dist/models/SemanticSegmentation/presets.d.ts +28 -0
  161. package/dist/models/index.d.ts +2 -0
  162. package/dist/product/ProductDimensions.d.ts +16 -0
  163. package/dist/product/ProductHotspot.d.ts +16 -0
  164. package/dist/product/ProductViewer.d.ts +9 -0
  165. package/dist/product/details.d.ts +53 -0
  166. package/dist/product/index.d.ts +14 -0
  167. package/dist/product/loadModelViewer.d.ts +4 -0
  168. package/dist/product/types.d.ts +71 -0
  169. package/dist/product/useProductAr.d.ts +37 -0
  170. package/dist/product/useProductDetails.d.ts +60 -0
  171. package/dist/product.cjs +1 -0
  172. package/dist/product.js +414 -0
  173. package/dist/providers-C0Dlhh9Z.js +365 -0
  174. package/dist/providers-YNwkmNeK.cjs +62 -0
  175. package/dist/quality/XRQualityController.d.ts +25 -0
  176. package/dist/quality/controller.d.ts +33 -0
  177. package/dist/quality/index.d.ts +6 -0
  178. package/dist/quality/types.d.ts +100 -0
  179. package/dist/quality/useXRQuality.d.ts +28 -0
  180. package/dist/{renderGroups-KZk6eRiY.js → renderGroups-CN1cGFUo.js} +1 -1
  181. package/dist/{renderGroups-80H3DpTl.cjs → renderGroups-O4KmaMl9.cjs} +1 -1
  182. package/dist/renderPasses-Cg-zqmEz.cjs +21 -0
  183. package/dist/renderPasses-NnKZVfBy.js +1460 -0
  184. package/dist/scene-object.cjs +1 -1
  185. package/dist/scene-object.js +2 -2
  186. package/dist/screenTransform-BenfCtZt.js +44 -0
  187. package/dist/screenTransform-BiXdpVf0.cjs +1 -0
  188. package/dist/segmentation-9PhSvc-R.cjs +1 -0
  189. package/dist/segmentation-CeOTKemr.js +5 -0
  190. package/dist/segmentation2d/faceMemory.d.ts +28 -0
  191. package/dist/segmentation2d/faceRegions.d.ts +24 -0
  192. package/dist/segmentation2d/index.d.ts +9 -0
  193. package/dist/segmentation2d/maskUv.d.ts +23 -0
  194. package/dist/segmentation2d/rasterize.d.ts +14 -0
  195. package/dist/segmentation2d/types.d.ts +46 -0
  196. package/dist/selectors-C5G1PMYL.js +195 -0
  197. package/dist/selectors-Du_PsVnW.cjs +1 -0
  198. package/dist/{session-CpkiK5YA.cjs → session-BBr0yN8h.cjs} +7 -7
  199. package/dist/{session-DLFN_VII.js → session-Cy0m7l7-.js} +76 -76
  200. package/dist/settings-DRR7-snV.cjs +1 -0
  201. package/dist/settings-aQk845NJ.js +73 -0
  202. package/dist/subjects/index.d.ts +2 -0
  203. package/dist/subjects/select.d.ts +62 -0
  204. package/dist/subjects/types.d.ts +55 -0
  205. package/dist/tracking-config/apply.d.ts +57 -0
  206. package/dist/tracking-config/index.d.ts +7 -0
  207. package/dist/tracking-config/resolve.d.ts +25 -0
  208. package/dist/tracking-config/storage.d.ts +8 -0
  209. package/dist/tracking-config/types.d.ts +106 -0
  210. package/dist/tracking-config/useXRTrackingConfig.d.ts +38 -0
  211. package/dist/tracking.cjs +1 -1
  212. package/dist/tracking.js +1056 -383
  213. package/dist/types/compositor.d.ts +21 -0
  214. package/dist/types/holistic.d.ts +121 -0
  215. package/dist/types/index.d.ts +1 -0
  216. package/dist/types/model.d.ts +22 -2
  217. package/dist/types/nodes.d.ts +27 -2
  218. package/dist/types/segmentation.d.ts +19 -0
  219. package/dist/types/session.d.ts +41 -3
  220. package/dist/useFaceInfo-Blf7FCu3.js +303 -0
  221. package/dist/useFaceInfo-C5mO8NQY.cjs +1 -0
  222. package/dist/useXRCapabilities-B1R2ghy-.cjs +1 -0
  223. package/dist/useXRCapabilities-C68v__wB.js +30 -0
  224. package/dist/utils/fns.d.ts +6 -3
  225. package/dist/utils/integrity.d.ts +4 -1
  226. package/dist/utils/updateThrottle.d.ts +2 -0
  227. package/dist/utils/useStableUniforms.d.ts +18 -0
  228. package/docs/api/adaptive-quality.mdx +104 -0
  229. package/docs/api/asset-lifecycle.mdx +78 -0
  230. package/docs/api/camera-permission.mdx +142 -0
  231. package/docs/api/coordinate-spaces.mdx +165 -0
  232. package/docs/api/cross-camera.mdx +88 -0
  233. package/docs/api/diagnostics-calibration.mdx +97 -0
  234. package/docs/api/face-effects.mdx +6 -0
  235. package/docs/api/face-mesh.mdx +4 -2
  236. package/docs/api/gestures.mdx +173 -0
  237. package/docs/api/holistic-tracker.mdx +159 -0
  238. package/docs/api/iris-tracking.mdx +242 -0
  239. package/docs/api/multi-subject.mdx +95 -0
  240. package/docs/api/person-cutout.mdx +136 -0
  241. package/docs/api/product-ar.mdx +111 -0
  242. package/docs/api/product-details.mdx +78 -0
  243. package/docs/api/screen-space-coordinates.mdx +4 -1
  244. package/docs/api/segmentation-overlay.mdx +301 -0
  245. package/docs/api/session-lifecycle.mdx +136 -0
  246. package/docs/api/trackers.mdx +5 -0
  247. package/docs/api/tracking-config.mdx +108 -0
  248. package/docs/docs.json +23 -1
  249. package/docs/examples/lip-shaders.mdx +224 -0
  250. package/docs/guides/lip-effect-textures.mdx +292 -0
  251. package/docs/guides/lip-uv-template.png +0 -0
  252. package/docs/guides/lip-uv-template.svg +1 -0
  253. package/package.json +13 -1
  254. package/dist/FaceMeshRenderer-C5l6VLdO.cjs +0 -155
  255. package/dist/FaceMeshRenderer-wjAHrJzP.js +0 -1270
  256. package/dist/FaceRetouch-BkJzoA79.cjs +0 -134
  257. package/dist/FaceRetouch-DwbKQJAN.js +0 -551
  258. package/dist/contexts-Dr62It_K.cjs +0 -1
  259. package/dist/index-9dlAQAhy.js +0 -1462
  260. package/dist/index-BpEf2BBb.cjs +0 -1
  261. package/dist/index-CyUUurQT.cjs +0 -1
  262. package/dist/index-DCiMbxC3.cjs +0 -1
  263. package/dist/index-DpI-qo5X.cjs +0 -17
  264. package/dist/index-DyVF4vEW.js +0 -111
  265. package/dist/internal-Cq-TEQ5Y.cjs +0 -1
  266. package/dist/renderLayers-lDtL0_43.cjs +0 -1
  267. package/dist/renderLayers-zrK2pv5O.js +0 -13
  268. package/dist/renderPasses-BOoBSr8z.cjs +0 -21
  269. package/dist/renderPasses-BcquyVtu.js +0 -1500
  270. package/dist/selectors-B8H97CKA.js +0 -59
  271. package/dist/selectors-CZLKT2Iv.cjs +0 -1
  272. package/dist/useFaceInfo-BJ8-_MOs.cjs +0 -1
  273. package/dist/useFaceInfo-Dk426LTu.js +0 -62
  274. package/dist/useFaceResults-CtetsO9M.js +0 -19
  275. package/dist/useFaceResults-wBJF3zyp.cjs +0 -1
@@ -0,0 +1,88 @@
1
+ ---
2
+ title: "Cross-Camera & Orientation"
3
+ description: "Front / rear camera selection and switching, device enumeration, explicit mirror policy for preview and tracking, and orientation-safe alignment."
4
+ sidebarTitle: "Cross-Camera"
5
+ icon: "camera-rotate"
6
+ ---
7
+
8
+ ## Choosing and switching cameras
9
+
10
+ ```tsx
11
+ import { XRMediaSource, listCameras, findCameraByFacing } from "@vincentt-xr/sdk";
12
+
13
+ // Bind: front (default) or rear, or a specific device.
14
+ await session.setMediaSource({ source: XRMediaSource.WEBCAM, facingMode: "environment" });
15
+ await session.setMediaSource({ source: XRMediaSource.WEBCAM, deviceId });
16
+
17
+ // Switch on a live session (models, scene and lifecycle stay; a bound or
18
+ // stopped session is started). Resolves to the facing the track reports.
19
+ const facing = await session.switchCamera({ facingMode: "environment" });
20
+
21
+ // Enumerate (labels appear once permission is granted).
22
+ const cameras = await listCameras(); // [{ deviceId, label, facing: "user" | "environment" | null }]
23
+ const rear = findCameraByFacing(cameras, "environment");
24
+ ```
25
+
26
+ `facingMode` is a soft hint: a laptop with one camera answers `environment`
27
+ with that camera, and `cameraFacing` then reports what the track says.
28
+ `deviceId` is exact and beats the hint. The same request again is a no-op;
29
+ a different facing, device or mirror policy releases the old stream and
30
+ tracks once and binds the new camera under the same source canvas, so every
31
+ model keeps its input binding. `useXRCameraFacing()` and
32
+ `useXRCameraDeviceId()` (low-level) read the result.
33
+
34
+ ## Mirror policy
35
+
36
+ There is exactly one mirror decision per session and it is keyed on the
37
+ source kind:
38
+
39
+ | Source | `sourceKind` | Mirrored |
40
+ | ---------------------------- | ----------------- | -------- |
41
+ | front camera (`user`) | `webcam` | yes |
42
+ | rear camera (`environment`) | `rear-camera` | no |
43
+ | image / video stream, canvas | `image` / `video` | no |
44
+
45
+ The kind follows the _effective_ facing (what the track reports), so a
46
+ one-camera laptop that answers an `environment` request with its front
47
+ camera is still mirrored; `mirrored: true | false` on the bind pins it
48
+ either way. Preview and tracking read the same feed, so a landmark
49
+ unprojected to the scene lands where the preview shows it on every camera.
50
+ Consumers never flip positions; `useXRMirrored()` (low-level, the same
51
+ answer as `isMirroredSourceKind(sourceKind)`) is for _direction_ semantics
52
+ only, such as which hand is the person's left. Every tracker and the
53
+ coordinate toolkit already key off the kind.
54
+
55
+ ## Orientation
56
+
57
+ `useXROrientation()` (low-level) reports `{ type: "portrait" | "landscape",
58
+ angle }` from `screen.orientation` (viewport aspect as the fallback). On a
59
+ change the session re-reads the live video size, so `useXRFeedCrop()` and
60
+ everything built on the coordinate toolkit re-align without waiting for the
61
+ next frame; the blit loop also resyncs the source size every frame. Content
62
+ anchored to landmarks (`TrackingAnchor`, `feedToWorld`, …) therefore stays
63
+ on the face through a rotation and a camera switch. Use
64
+ `calibration.orientation` only for sources the browser does not rotate
65
+ (a raw sideways capture).
66
+
67
+ ## Lifecycle guarantees
68
+
69
+ - Switching releases the previous stream exactly once (`ended` from the
70
+ replaced tracks is ignored), keeps models initialized, and keeps
71
+ `running` / `paused` as it was; `switchCamera` additionally starts a
72
+ `media-bound` / `stopped` session.
73
+ - A switch that fails (device unplugged, in use) leaves the session where it
74
+ was with the error published; call again or pick another device.
75
+ - `session.dispose()` (provider unmount) removes the orientation listeners.
76
+
77
+ ## Manual validation
78
+
79
+ Run `/camera-switching` on a phone:
80
+
81
+ | Case | Steps | Expect |
82
+ | ------------------- | ----------------------------------- | ---------------------------------------------------------------------- |
83
+ | Front camera | load | `front · mirrored`; the cyan dot sits on the nose |
84
+ | Rear camera | "front → rear" | `rear · straight`; text in view reads correctly; dot still on the nose |
85
+ | Device list | after permission | labelled cameras; tapping one opens it (device id shown) |
86
+ | Mirror override | mirror = straight, "apply mirror" | front feed un-mirrored; dot still aligned |
87
+ | Rotate | turn the phone | orientation line flips, source size updates, dot stays aligned |
88
+ | Switch while paused | pause (session-health), switch here | stays paused; resume runs on the new camera |
@@ -0,0 +1,97 @@
1
+ ---
2
+ title: "Diagnostics & Calibration"
3
+ description: "Opt-in tracking diagnostics (readiness, inference time and rate, dropped frames, confidence, errors) and a device calibration workflow for orientation, mirror, scale, face distance and body proportions."
4
+ sidebarTitle: "Diagnostics & Calibration"
5
+ icon: "gauge-high"
6
+ ---
7
+
8
+ ## Tracking diagnostics
9
+
10
+ ```tsx
11
+ import { useXRDiagnostics } from "@vincentt-xr/sdk/low-level";
12
+ import { XRDiagnosticsPanel } from "@vincentt-xr/sdk/debug-ui";
13
+
14
+ // Data (any component under <XRProvider>, outside the R3F canvas or inside):
15
+ const diagnostics = useXRDiagnostics({ intervalMs: 250, models: [XRModel.FACE_TRACKER] });
16
+ diagnostics.session.feedFps; // frames the session blits per second
17
+ diagnostics.models[0].state; // "unavailable" | "loading" | "ready" | "paused" | "error"
18
+ diagnostics.models[0].inferenceMs; // last inference, ms
19
+ diagnostics.models[0].inferenceFps; // inferences per second (~1 s window)
20
+ diagnostics.models[0].skippedFrames; // RAF ticks not analysed (throttle / busy)
21
+ diagnostics.models[0].detections; // faces / hands / bodies in the last inference
22
+ diagnostics.models[0].confidence; // primary-subject score, when the model has one
23
+
24
+ // Panel (debug UI, outside the canvas):
25
+ <XRDiagnosticsPanel models={[XRModel.FACE_TRACKER, XRModel.HAND_TRACKER]} />;
26
+ ```
27
+
28
+ The hook samples on an interval, never per frame, and only while a consumer
29
+ is mounted. Nothing is collected in production unless you render it, so it is
30
+ safe to ship behind a debug flag. The `models` list defaults to every
31
+ `XRModel`, so models nobody acquired show as `unavailable`.
32
+
33
+ Per-model fields come from the model instance (`getDiagnostics()` on every
34
+ `BaseXRModel`): inference timing is recorded around each MediaPipe call;
35
+ `detections` and `confidence` are what each model can report (face: count,
36
+ no score; hand / gesture: count and the handedness or gesture score; body /
37
+ holistic: count and mean landmark visibility; segmenters: 1 when a mask was
38
+ produced, no score). `targetFps` is the per-tracker cap when one was
39
+ requested. `owners` lists who holds the model and who wants it running.
40
+
41
+ Session fields mirror the store: lifecycle, ready, source and kind, camera
42
+ and feed sizes, feed fps and the session error. `inferenceMsTotal` sums the
43
+ last inference of every running model, a quick "how much ML per frame".
44
+
45
+ ## Calibration
46
+
47
+ Cameras differ in orientation, mirroring and field of view, and people stand
48
+ at different distances. The calibration workflow measures a device once,
49
+ persists it, and applies it only where you ask. **Raw tracker results are
50
+ never altered.**
51
+
52
+ ```tsx
53
+ import { useXRCalibration } from "@vincentt-xr/sdk/tracking";
54
+
55
+ const cal = useXRCalibration({ storageKey: "myapp.calibration" });
56
+
57
+ // Capture references while the person stands at their normal distance:
58
+ cal.calibrateFace(faceInfo.landmarks); // feed-space face landmarks
59
+ cal.calibrateBody(poseLandmarksInFeedSpace); // 33 pose landmarks, feed space
60
+
61
+ // Device transform:
62
+ cal.update({ orientation: 90, mirrored: "auto", scale: 1.1, offset: { x: 0, y: 0.02 } });
63
+
64
+ // Apply where it matters:
65
+ const nose = cal.applyToFeedPoint(faceInfo.landmarks[4]); // new point, input untouched
66
+ const distance = cal.faceDistance(faceInfo.landmarks); // 1 at reference, >1 closer
67
+ const bodyScale = cal.bodyScale(poseLandmarksInFeedSpace); // 1 at reference
68
+
69
+ cal.reset(); // defaults; clears the persisted value
70
+ ```
71
+
72
+ | Field | Meaning |
73
+ | ----------------- | ------------------------------------------------------------------------------------------------------------------------- |
74
+ | `orientation` | `0 / 90 / 180 / 270`: clockwise rotation applied to feed points. |
75
+ | `mirrored` | `"auto"` follows the session (only the webcam is mirrored); `true`/`false` flip only when they disagree with the session. |
76
+ | `scale`, `offset` | Uniform scale about the feed centre, then a feed-unit nudge. |
77
+ | `face` | `faceWidth` (temples 234↔454) and `eyeDistance` (33↔263) in feed-width units, plus `capturedAt`. |
78
+ | `body` | `shoulderWidth` (11↔12), `hipWidth` (23↔24), `torsoLength` (mid-shoulder to mid-hip). |
79
+
80
+ The pure helpers (`applyCalibrationToFeedPoint`, `measureFaceCalibration`,
81
+ `measureBodyCalibration`, `estimateFaceDistance`, `estimateBodyScale`,
82
+ `updateCalibration`, `resetCalibration`, `loadCalibration`,
83
+ `saveCalibration`, `clearCalibration`) work without React. Persistence uses
84
+ `localStorage` under `vincentt.xr.calibration` by default, validates every
85
+ field on load, and drops blobs from another `version`.
86
+
87
+ ## Manual validation
88
+
89
+ Run `/session-health`:
90
+
91
+ | Case | Steps | Expect |
92
+ | ----------------- | ----------------------------- | ------------------------------------------------------------------------- |
93
+ | Diagnostics panel | load, pause / resume | face + body `ready`, inference ms / fps live; `paused` and 0 fps on pause |
94
+ | Unavailable model | untick "mount hand tracker" | hand row reads `unavailable`; tick again → `loading` → `ready` |
95
+ | Face calibration | "calibrate face", move closer | distance rises above 1; reload keeps the reference |
96
+ | Orientation | set 90° | calibrated nose point rotates; raw point unchanged |
97
+ | Reset | "reset" | defaults, distance `–`, storage cleared |
@@ -13,6 +13,12 @@ This section is split into focused pages:
13
13
  paint onto the live face mesh.
14
14
  - [Face Retouching](/api/face-retouching) for the legacy skin-retouch shader
15
15
  and its controls.
16
+ - [Segmentation Overlay](/api/segmentation-overlay) for 2D semantic region
17
+ layers (lips, eyes, brows, face skin, person, background) without 3D
18
+ geometry, and when to prefer them over the canonical mesh.
19
+ - [Lip Effect Textures](/guides/lip-effect-textures) for `LipEffect`: the
20
+ design-texture canvas contract, the generated lip UV template, mask modes,
21
+ and the custom material context.
16
22
 
17
23
  Use `@vincentt-xr/sdk/tracking` for trackers, `FaceInfo`, Face Actions, and the
18
24
  bare deforming `FaceMesh`. Use `@vincentt-xr/sdk/face-effects` for the modular
@@ -68,8 +68,10 @@ texture, blend, or mask state changes.
68
68
 
69
69
  Material types and shader presets are quick SDK defaults. Use
70
70
  `materialOverride` when the face needs a custom Three.js material or shader.
71
- It accepts a material node or a factory that receives the current camera texture
72
- and geometry key. This prop wins over preset settings and is intentionally not
71
+ It accepts a material node or a factory that receives the current camera texture,
72
+ the geometry key, and `faceUv`: the shared live camera-UV `BufferAttribute`
73
+ (`null` until the canonical mesh loads), also bound to every group's geometry as
74
+ `attribute vec2 faceUv`. This prop wins over preset settings and is intentionally not
73
75
  part of `FaceMeshRendererSettings`, because materials, uniforms, and factories
74
76
  are not serializable scene data.
75
77
 
@@ -0,0 +1,173 @@
1
+ ---
2
+ title: "Gestures"
3
+ description: "Reusable face, hand and body gesture detection with thresholds, hysteresis, confidence, cooldown and lifecycle events."
4
+ sidebarTitle: "Gestures"
5
+ icon: "hand-sparkles"
6
+ ---
7
+
8
+ `useGesture` turns raw tracking into stable gesture events: pinch, grab,
9
+ open palm, wave, hand raise, nod, head shake, mouth open, smile, blink,
10
+ brow raise, arm raise and T-pose, plus your own. One engine handles the
11
+ thresholds, hysteresis, hold, cooldown, confidence and tracking loss, so
12
+ scenes stop re-implementing them.
13
+
14
+ ```tsx
15
+ import { GestureListener, useGesture } from "@vincentt-xr/sdk/tracking";
16
+
17
+ // Hook: React state that changes only when the gesture's state changes.
18
+ const pinch = useGesture("pinch", { hand: "right", onBegin: () => grab(), onEnd: () => drop() });
19
+ if (pinch.active) {
20
+ /* … */
21
+ }
22
+
23
+ // Declarative: mount and act on events.
24
+ <GestureListener gesture="headNod" onBegin={() => confirm()} />;
25
+ ```
26
+
27
+ Each gesture acquires the shared face, hand or body model it needs; no
28
+ second detector starts. Mount gesture hooks once above scene switches, like
29
+ any tracker.
30
+
31
+ ## Built-in gestures
32
+
33
+ | Name | Kind | Measures | Default enter / exit / hold / cooldown |
34
+ | ---------------------------------- | ---- | ------------------------------------------------------------ | -------------------------------------- |
35
+ | `mouthOpen` | face | `jawOpen` blendshape | 0.5 / 0.35 / 0 / 0 |
36
+ | `smile` | face | mean of `mouthSmileLeft` / `mouthSmileRight` | 0.4 / 0.25 / 0 / 0 |
37
+ | `blink` | face | both eyes closed (min of the `eyeBlink` blendshapes) | 0.55 / 0.4 / 50 ms / 0 |
38
+ | `browRaise` | face | max of the brow-up blendshapes | 0.45 / 0.3 / 0 / 0 |
39
+ | `headNod` | face | pitch oscillation: 3 turning points ≥ 0.12 rad within 1.5 s | 0.99 / 0.3 / 0 / 600 ms |
40
+ | `headShake` | face | yaw oscillation: 3 turning points ≥ 0.15 rad within 1.5 s | 0.99 / 0.3 / 0 / 600 ms |
41
+ | `pinch` | hand | thumb–index tip gap relative to palm size (1 = touching) | 0.75 / 0.5 / 0 / 0 |
42
+ | `grab` | hand | mean curl of index..pinky (1 = fist) | 0.7 / 0.45 / 0 / 0 |
43
+ | `openPalm` | hand | mean extension of index..pinky | 0.7 / 0.45 / 0 / 0 |
44
+ | `handRaised` | hand | wrist height in the frame (0 bottom, 1 top) | 0.7 / 0.6 / 0 / 0 |
45
+ | `oneFinger` | hand | index extended, middle..pinky curled (pointing) | 0.65 / 0.45 / 0 / 0 |
46
+ | `peaceSign` | hand | index + middle extended, ring + pinky curled | 0.65 / 0.45 / 0 / 0 |
47
+ | `threeFingers` | hand | index + middle + ring extended, pinky curled | 0.65 / 0.45 / 0 / 0 |
48
+ | `fourFingers` | hand | index..pinky extended, thumb tucked | 0.65 / 0.45 / 0 / 0 |
49
+ | `fist` | hand | all four curled and the thumb tucked | 0.65 / 0.45 / 0 / 0 |
50
+ | `palm` | hand | all four extended and the thumb out | 0.65 / 0.45 / 0 / 0 |
51
+ | `thumbUp` / `thumbDown` | hand | thumb out and pointing up / down, fingers curled | 0.6 / 0.4 / 0 / 0 |
52
+ | `wave` | hand | wrist x oscillation: 3 turning points ≥ 0.08 within 1.2 s | 0.99 / 0.3 / 0 / 600 ms |
53
+ | `armRaisedLeft` / `armRaisedRight` | body | wrist above its shoulder, in shoulder widths (person's side) | 0.3 / 0.15 / 0 / 0 |
54
+ | `armRaised` / `bothArmsRaised` | body | either / both wrists above their shoulders | 0.3 / 0.15 / 0 / 0 |
55
+ | `tPose` | body | both arms level and outstretched by a shoulder width | 0.6 / 0.4 / 150 ms / 0 |
56
+
57
+ Hand measures are scale-free (divided by the palm size) and body measures
58
+ are in shoulder widths, so distance from the camera does not change the
59
+ thresholds. `GESTURES[name].description` carries each line of the table.
60
+
61
+ The finger-count signs score the _weakest_ required finger, so a pattern is
62
+ 1 only when every finger agrees: `peaceSign` needs index and middle extended
63
+ _and_ ring and pinky curled. `grab` and `openPalm` are the softer, mean-curl
64
+ versions of `fist` and `palm` that ignore the thumb; `measureFingerPattern`
65
+ builds any other pattern (`{ fingers: [true, null, null, false], thumb: true }`
66
+ uses `null` for "either").
67
+
68
+ ## The engine contract
69
+
70
+ Every frame the definition measures a `{ value, confidence }` sample (or
71
+ `null` when the tracker has no subject). The detector then applies, in order:
72
+
73
+ 1. **Confidence gate**: a sample below `minConfidence` (default 0.5), or no
74
+ sample, is tracking loss.
75
+ 2. **Loss grace**: an active gesture survives loss for `lostGraceMs`
76
+ (default 250, the SDK's hold window) with its value frozen, then ends
77
+ with `endReason: "lost"`. An inactive gesture just resets its hold timer.
78
+ 3. **Hysteresis**: inactive → `begin` when `value >= enterThreshold`;
79
+ active → `end` (`endReason: "released"`) when `value <= exitThreshold`.
80
+ The band between them is where the gesture is stable; the default exit is
81
+ 0.15 below enter.
82
+ 4. **Hold**: the value must stay at or above enter for `holdMs` before
83
+ `begin`; a dip resets the clock.
84
+ 5. **Cooldown**: after `end`, no `begin` for `cooldownMs`.
85
+
86
+ A `begin` is always followed by at least one `stay` frame, so a one-frame
87
+ gesture still surfaces both edges to an effect that watches `justBegan` and
88
+ `justEnded`.
89
+
90
+ `GestureState`: `active`, `value`, `confidence`, `tracked`, `phase`
91
+ (`begin` | `stay` | `end` | `none`), `justBegan`, `justEnded`, `endReason`
92
+ (`released` | `lost` | null), `startedAtMs`, `durationMs`, `count`. The hook
93
+ publishes a new state only when one of these changes, and `durationMs` is
94
+ excluded from that comparison so an active gesture does not re-render every
95
+ frame: it reads as of the last published change (an ending gesture carries
96
+ its full length). Derive a live duration from `startedAtMs`.
97
+
98
+ Confidence is presence-based for the face, hand and body trackers, which
99
+ report no per-landmark score: 1 while the subject is tracked. A custom
100
+ definition can supply a real score (the holistic result's `confidence`, for
101
+ example).
102
+
103
+ ## Options
104
+
105
+ `useGesture(gesture, options)` takes the detector options above plus
106
+ `active`, `faceIndex` / `numFaces` (face), `hand` / `handIndex` (hand),
107
+ `bodyIndex` (body), `targetFps`, and `onBegin` / `onEnd` / `onChange`
108
+ callbacks, which fire from the frame loop with that frame's state.
109
+
110
+ ```tsx
111
+ const nod = useGesture("headNod", { faceIndex: 1, numFaces: 2, cooldownMs: 1000 });
112
+ const wave = useGesture("wave", { hand: "left", lostGraceMs: 400 });
113
+ ```
114
+
115
+ Per-gesture defaults are the ones in the table; `resolveGestureDetectorOptions(overrides, defaults)`
116
+ is what the hook applies.
117
+
118
+ ## Custom gestures
119
+
120
+ ```ts
121
+ import { defineGesture, HAND_JOINTS } from "@vincentt-xr/sdk/tracking";
122
+
123
+ // A "thumbs up": thumb tip well above the wrist while the other fingers curl.
124
+ const thumbsUp = defineGesture({
125
+ name: "thumbsUp",
126
+ kind: "hand",
127
+ description: "Thumb up, fingers curled.",
128
+ defaults: { enterThreshold: 0.7, exitThreshold: 0.5 },
129
+ measure: (hand) => {
130
+ if (!hand || hand.length < 21) return null;
131
+ const lift = hand[HAND_JOINTS.thumbTip].y - hand[HAND_JOINTS.wrist].y;
132
+ return { value: Math.min(1, Math.max(0, lift / 0.3)), confidence: 1 };
133
+ },
134
+ });
135
+
136
+ const up = useGesture(thumbsUp);
137
+ ```
138
+
139
+ Temporal gestures add `createMemory` and read it in `measure`; the built-in
140
+ `OscillationTracker` (on `/low-level`) counts direction reversals for nods,
141
+ shakes and waves.
142
+
143
+ ## Relation to the other gesture APIs
144
+
145
+ - `<GestureTrigger>` fires once on a **named static hand pose** from the
146
+ gesture-recognizer model (`victory`, `thumb_up`, …). Use it for "show ✌️ to
147
+ start". `useGesture` measures **continuous and temporal** gestures from the
148
+ landmark trackers with thresholds you can tune.
149
+ - `useFaceAction` is the face-only predecessor with the same hysteresis idea;
150
+ the face gestures here reuse its measurements, and add cooldown,
151
+ confidence, loss grace and the temporal nod / shake.
152
+
153
+ ## Tracking loss and confidence
154
+
155
+ Losing the subject never leaves a gesture stuck: within `lostGraceMs` the
156
+ state holds (value frozen, `tracked: false`), after it the gesture ends with
157
+ `endReason: "lost"` so an effect can clean up differently from a deliberate
158
+ release. A subject that reappears starts cold: hold timers restart and
159
+ temporal memories reset, so a nod half-done before the dropout does not
160
+ complete on return.
161
+
162
+ ## Manual validation
163
+
164
+ Run `/gestures`:
165
+
166
+ | Case | Steps | Expect |
167
+ | ------------- | --------------------------------------- | --------------------------------------------------------------- |
168
+ | Hysteresis | hold a half pinch around the threshold | Row stays active until the value drops below the exit threshold |
169
+ | Hold | set hold 300 ms on `pinch`, tap quickly | No begin for taps shorter than 300 ms |
170
+ | Cooldown | `headNod`, nod twice quickly | One begin; the second counts after 600 ms |
171
+ | Tracking loss | cover the hand mid-pinch | Row shows "lost" then ends with `lost` after the grace window |
172
+ | Confidence | set min confidence above 1 | Gesture never begins |
173
+ | Body | "Person Fullbody" clip, raise an arm | `armRaised` / side rows activate |
@@ -0,0 +1,159 @@
1
+ ---
2
+ title: "Holistic Tracker"
3
+ description: "One MediaPipe inference for face, hands and pose, with unified results, dropout bridging and the same TrackingAnchor targets as the individual trackers."
4
+ sidebarTitle: "Holistic Tracker"
5
+ icon: "person-rays"
6
+ ---
7
+
8
+ `<HolisticTracker>` runs MediaPipe's Holistic Landmarker: a single graph that
9
+ returns face landmarks and blendshapes, left and right hand landmarks, and the
10
+ 33-point pose for **one person**. It is additive and opt-in; `FaceTracker`,
11
+ `HandTracker` and `BodyTracker` are unchanged.
12
+
13
+ ```tsx
14
+ import { HolisticTracker, TrackingAnchor } from "@vincentt-xr/sdk/tracking";
15
+
16
+ <HolisticTracker holdMs={250}>
17
+ <HolisticTracker.Face>
18
+ <TrackingAnchor target="face.noseTip">{/* … */}</TrackingAnchor>
19
+ </HolisticTracker.Face>
20
+ <HolisticTracker.Hand hand="right">
21
+ <TrackingAnchor target="hand.indexTip">{/* … */}</TrackingAnchor>
22
+ </HolisticTracker.Hand>
23
+ <HolisticTracker.Body>
24
+ <TrackingAnchor target="body.leftShoulder">{/* … */}</TrackingAnchor>
25
+ </HolisticTracker.Body>
26
+ </HolisticTracker>;
27
+ ```
28
+
29
+ ## Composition
30
+
31
+ - `<HolisticTracker>` acquires `XRModel.HOLISTIC_TRACKER` through the shared,
32
+ refcounted registry. Every descendant and every other `<HolisticTracker>`
33
+ in the session shares that one model and one inference per frame.
34
+ - `<HolisticTracker.Face>`, `<HolisticTracker.Hand hand="left" | "right">` and
35
+ `<HolisticTracker.Body>` are the stream groups. Each provides the same
36
+ tracker context as `FaceTracker` / `HandTracker` / `BodyTracker`, so
37
+ `TrackingAnchor` targets (`face.*`, `hand.*`, `body.*`, including
38
+ `body.neck` and `face.custom`) and `smoothing` work unchanged. The group is
39
+ hidden while its stream is absent, and its origin is the nose tip, the
40
+ wrist, or the head.
41
+ - `useHolisticTracking()` returns `{ node, ready, status, error }`. Read
42
+ `node.getResult()` inside `useFrame`; the result object is stable and
43
+ mutated in place.
44
+
45
+ ## The unified result
46
+
47
+ `HolisticResult` carries `version`, `timestampMs` and four streams that are
48
+ `null` when neither detected nor held:
49
+
50
+ | Stream | Coordinates | Extras |
51
+ | ----------------------- | ---------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------ |
52
+ | `face` | 478 `NormalizedLandmark`s in MediaPipe image space (0..1, top-left, Y down) — the same space as `FaceTracker`'s `detectionResults` | `blendshapes` name → score, `confidence`, `held` |
53
+ | `leftHand`, `rightHand` | 21 `TrackerPoint`s: center origin, X right, Y up, Z toward the viewer — the same as `HandTracker`'s `coordinates` | `handedness`, `worldLandmarks` (metres, MediaPipe world space), `confidence`, `held` |
54
+ | `pose` | 33 `TrackerPoint`s — the same as `BodyTracker`'s `getLandmarks2D()` | `visibility[]`, `worldLandmarks`, `confidence`, `held` |
55
+
56
+ Mirroring of coordinates follows the individual trackers: no source-dependent
57
+ flip is applied to any stream. Hand **labels** are the person's anatomical
58
+ side on every source: holistic assigns hands from the pose, whose left/right
59
+ follow the image, so the SDK swaps them on the mirrored webcam feed.
60
+ `leftHand` is the person's left hand (on the viewer's left in the selfie view),
61
+ the same hand `<HandTracker hand="left">` selects, and `hands="left"` keeps it.
62
+ The pose's left/right landmark indices are swapped the same way, so `pose`
63
+ wrist 15 and `leftHand` are always the same physical hand. This differs from
64
+ `BodyTracker`, whose indices follow the image.
65
+
66
+ **Pre-release note:** the default model is MediaPipe's published
67
+ `holistic_landmarker.task` on `storage.googleapis.com` (integrity pinned)
68
+ until the SDK CDN copy is published. Pass `modelUrl` to self-host it. The face has no transformation matrix in the holistic graph, so there is
69
+ no head rotation; use `<FaceTracker>` when children must roll with the head.
70
+
71
+ ### Confidence contract
72
+
73
+ - Face and hands: `1` while detected (the holistic graph does not report
74
+ per-landmark visibility for them).
75
+ - Pose: the mean landmark `visibility`.
76
+ - While a stream is **held** after a dropout, `held` is `true` and
77
+ `confidence` decays linearly from the last live value to `0` at `holdMs`.
78
+
79
+ ### Dropouts
80
+
81
+ Each stream runs through `FaceTrackMemory`, the SDK's one hold/predict bridge:
82
+ a missing stream is replayed for `holdMs` (default 250), extrapolated along its
83
+ last velocity for the first `predictMs` (default 0), then dropped. Set
84
+ `holdMs={0}` for immediate disappearance. Do not add app-side holds.
85
+
86
+ ## Options
87
+
88
+ | Prop | Applies | Notes |
89
+ | -------------------------------------------------------------------------------------------------------------------------------------------------- | -------------- | -------------------------------------------------------------------------------------- |
90
+ | `modelUrl` | model creation | Defaults to MediaPipe's published `holistic_landmarker.task` (13.7 MB), SHA-384 pinned |
91
+ | `minFaceDetectionConfidence`, `minFacePresenceConfidence`, `minPoseDetectionConfidence`, `minPosePresenceConfidence`, `minHandLandmarksConfidence` | model creation | MediaPipe thresholds, default 0.5 |
92
+ | `outputFaceBlendshapes` | model creation | Default `true` |
93
+ | `delegate` | model creation | `"auto"` (default), `"GPU"`, `"CPU"`; see below |
94
+ | `targetFps` | run time | Overrides `perf.targetFps` for this model |
95
+ | `hands` | live | `"both"` (default), `"left"`, `"right"`, `"none"`; holistic tracks at most one of each |
96
+ | `analysisMaxDimension` | live | Longest side of the analysed frame, default 768 |
97
+ | `holdMs`, `predictMs` | live | Dropout bridge |
98
+ | `active` | live | Pause inference without unmounting |
99
+ | `onStatusChange(status, error)` | — | `loading` → `ready` / `error`; a transient inference error recovers on the next frame |
100
+
101
+ Model-creation options come from the first mounted consumer (like `numFaces`
102
+ on `FaceTracker`); changing them re-acquires the model. Live options apply
103
+ through `node.setOptions` and the last writer wins.
104
+
105
+ ## Delegate: GPU with an automatic CPU fallback
106
+
107
+ MediaPipe's holistic face-blendshape stage has no WebGL implementation for
108
+ one of its ops, so with `outputFaceBlendshapes` the GPU graph fails on its
109
+ first frame (`UNIMPLEMENTED … No support of const`). With the default
110
+ `delegate="auto"` the tracker rebuilds the graph on the CPU once: one
111
+ `console.warn`, status dips to `loading` during the rebuild, no refetch, and
112
+ `node.getDelegate()` reports `"CPU"` afterwards. CPU inference is several
113
+ times slower (tens to hundreds of milliseconds per frame on a laptop). When
114
+ blendshapes are not needed, `outputFaceBlendshapes={false}` keeps the whole
115
+ graph on the GPU. `delegate="GPU"` reports the failure as `error` instead of
116
+ retrying; `delegate="CPU"` starts on the CPU.
117
+
118
+ ## Coexistence with the individual trackers
119
+
120
+ - Holistic is a **separate model**. Mounting `<FaceTracker>` next to
121
+ `<HolisticTracker>` runs two inferences; pick one per scene.
122
+ - Inside a holistic scene, use the sub-trackers instead of `FaceTracker` /
123
+ `HandTracker` / `BodyTracker`. `GestureTracker` (named gestures) and
124
+ `Segmentation` are unrelated models and combine freely.
125
+ - `FaceMesh`, face readers (`useFaceInfo`, `useFaceResults`), face effects and
126
+ head binding read `XRModel.FACE_TRACKER`; they do not consume holistic
127
+ results. For those, keep `<FaceTracker>`.
128
+
129
+ ## Combined signals
130
+
131
+ `getHolisticSignals(result)` derives presence, `smile` / `mouthOpen`
132
+ blendshape scores, raised hands (pose wrist above its shoulder), open palms
133
+ (three of four fingers extended) and `greeting`: a raised open hand while
134
+ smiling. `isHandRaised` and `isPalmOpen` are exported on their own.
135
+
136
+ ## Migration and performance
137
+
138
+ - Replace `<FaceTracker>` + `<HandTracker>` + `<BodyTracker>` with one
139
+ `<HolisticTracker>` and three sub-trackers when a scene needs all three; the
140
+ anchors move over unchanged. Keep the individual trackers when only one
141
+ stream is needed: the holistic model is heavier than any single one.
142
+ - Default inference cap follows `perf.targetFps` (the template ships 24).
143
+ Lower `analysisMaxDimension` (512) when only the face and hands matter and
144
+ the subject is close.
145
+ - First load downloads 13.7 MB plus the shared MediaPipe engine.
146
+
147
+ ## Manual validation
148
+
149
+ Run `/holistic-tracking` in the playground:
150
+
151
+ | Case | Source | Expect |
152
+ | ----------------- | ------------------------------------ | ----------------------------------------------------------------------------------------- |
153
+ | All streams | "Person Fullbody" | Face, both hands and pose dots follow; HUD shows every stream present |
154
+ | Hands option | panel `hands: left` | Right hand dots disappear immediately; left keeps tracking |
155
+ | Hold | panel `holdMs: 0` vs 250 | Dots vanish instantly on a dropout vs. hold for a quarter second with decaying confidence |
156
+ | Combined gesture | webcam, raise an open hand and smile | The raised hand turns yellow and the HUD reports the greeting |
157
+ | Status | reload | `loading` → `ready`; a bad `modelUrl` reports `error` with the message |
158
+ | Delegate fallback | blendshapes on, then off | HUD shows `delegate CPU` after one warning; with blendshapes off it stays on `GPU` |
159
+ | Coexistence | mount `<FaceTracker>` too | Two models in the debugger; no crash, no shared state |