Failure modes
Every fault this project has a named, deliberate answer for — not a promise nothing ever goes wrong, a record of what happens when it does.
| # | Fault | Contract |
|---|---|---|
| F1 | Camera permission denied | Non-camera flashcard mode: reference diagram + text description + manual "I made this shape" advance, keyboard-operable. No dead end. |
| F2 | No camera hardware | Same fallback as F1, distinct copy naming the actual cause. |
| F3 | GPU (WebGL2) delegate rejected, including no WebGL at all | MediaPipe's CPU delegate is pure WASM and needs no WebGL, so these are one branch, not two rungs. Catch the rejection, retry delegate:'CPU'. Label: "Running on CPU — same result, about 15 frames a second instead of 35." Never "reduced accuracy" (it isn't), never "identical" (frame rate visibly differs). |
| F4 | Model fetch fails mid-load | Loud on-page error + retry, never a stuck spinner. Flashcard mode needs no model and stays available. |
| F5 | Cache bug — .task re-downloads every visit (GCS caps freshness at max-age=3600) | Fixed at the fetch layer: manual fetch() + caches.open()/.put(), pass the ArrayBuffer via modelAssetBuffer, not modelAssetPath. Verified by a second-visit network capture showing ~0 model bytes. |
| F6 | Two hands in frame with numHands:1 | MediaPipe may flicker its choice. Absorbed by the stability window; copy asks for one hand. |
| F7 | Low-confidence prediction | A tick's effective label is null below threshold — never silently guesses. |
| F8 | Hand exits frame mid-hold | reset() — a gap always breaks a streak. No partial credit across a gap. |
| F9 | prefers-reduced-motion | Feedback becomes an instant static state change. No autoplaying motion. |
| F10 | localStorage unavailable | Practice works in-session; UI says progress will not persist. Never throws. |
| F11 | Classifier wrong on a genuinely confusable letter | Not a bug. Normal miss; hint references the published confusion pair ("commonly mixed up with M — check thumb position"), never phrased as the user being wrong. |
| F12 | J or Z requested | Structurally impossible — never offered. /docs/limitations states why. |