Skip to content

Make tongue-out stage 3 actually reach users: ship its model, stop latching failed init - #36

Merged
guyiome merged 2 commits into
mainfrom
fix-tongue-embedder-shipping
Sep 16, 2026
Merged

guyiome merged 2 commits into
mainfrom
fix-tongue-embedder-shipping

Conversation

@guyiome

@guyiome guyiome commented Sep 16, 2026

Copy link
Copy Markdown
Owner

Summary

Two separate defects, both found by plugging in a new test device (nubia NX809J, Android 16) running the published v0.3.1.

1. The model was in no published APK

Both models are gitignored, and all three workflows only downloaded face_landmarker.task. image_embedder.tflite was therefore absent from every release, so stage 3 of tongue-out detection (points 15/56) could never initialise for anyone installing from GitHub Releases. It only ever worked on locally built APKs, where the model is present from the manual download the README documents.

Confirmed on device against v0.3.1: "Impossible d'initialiser le modèle de détection de langue tirée (CPU et GPU en échec)" — both delegates failing is exactly what a missing asset looks like.

Why it went unnoticed: the feature is opt-in and its failure is caught cleanly (logged, no crash). Point 56 recorded it as shipped and device-confirmed, which was true of local debug builds but never of the artifact users install.

release.yml and ci.yml now download both models — CI too, so the CI build stays identical to the published one and a future packaging problem surfaces on a PR rather than at release time. codeql.yml deliberately keeps fetching only the first: it compiles for analysis, where a runtime asset changes nothing. That asymmetry is commented in both files so it isn't "fixed" later by mistake.

The URL was checked rather than copied on faith: HTTP 200, 4 117 670 bytes — byte-for-byte the size of the local model already known to work. Expect the published APK to grow by ~4 MB.

2. A failed init was latched and reported as a success

The device logs showed a failure warning and TongueEmbeddingHelper initialisé at the same millisecond. ensureTongueEmbeddingHelper() assigned the instance whatever happened and logged success unconditionally — and since its own entry guard is if (tongueEmbeddingHelper != null) return, a failed setup was latched for the whole process lifetime: toggling the experimental setting off and on never retried.

setup() now returns a Boolean (single caller, no test depended on its signature; signalling failure only through onError left the caller unable to tell). The field is set only on success, the failed instance is closed, and the success log moved after the check.

Test plan

  • testDebugUnitTest, assembleDebug, lintDebug green. The 3 extra lint warnings versus two weeks ago are all "a newer version is available" (CameraX 1.6.2, compose-bom 2026.09.00, ARCore 1.56.0, Kotlin plugins 2.4.20), none from this change — the only warning on the touched files, EmptySuperCall, predates it.
  • Not yet verified on device, and deliberately so: a local build bundles the model anyway, so it would only exercise fix 2. Fix 1 can only be proven by a rebuilt release — hence the v0.3.2 to follow.

🤖 Generated with Claude Code

guyiome and others added 2 commits September 16, 2026 17:53
Found on a new test device (nubia NX809J, Android 16) running the
released v0.3.1: the logs showed a failure warning and a success line
at the same millisecond.

    W/MainViewModel: Échec d'initialisation de TongueEmbeddingHelper : …
    I/MainViewModel: TongueEmbeddingHelper initialisé (délégué GPU=false)

Two defects behind that, not just a cosmetic log problem:

- setup() only signalled failure through the onError callback, so the
  caller had no way to know. It now returns a Boolean, which is
  unambiguous. It has a single caller and no test depends on its
  signature.
- ensureTongueEmbeddingHelper() assigned the instance to the field
  whatever happened, then logged "initialisé" unconditionally. Since
  the function's own entry guard is `if (tongueEmbeddingHelper != null)
  return`, a failed setup was latched for the whole process lifetime:
  toggling the experimental setting off and on again never retried. The
  field is now only set on success, and the failed instance is closed,
  so a later attempt is possible again -- worthwhile for a
  circumstantial failure (memory, GPU delegate), even though the most
  common one, a missing model asset, will fail identically.

The success log stays unconditional on TONGUE_DIAGNOSTIC_LOGGING as
before, but now sits after the success check, so it can no longer
contradict the warning printed a moment earlier.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Stage 3 of tongue-out detection (revue technique points 15/56) has
never worked in any published build. Both model files are gitignored,
and all three workflows only ever downloaded face_landmarker.task, so
image_embedder.tflite was simply absent from every release APK. The
feature only ever ran on locally built APKs, where the model sits in
app/src/main/assets from the manual download the README documents.

Confirmed on device (nubia NX809J, Android 16) against the published
v0.3.1: "Impossible d'initialiser le modèle de détection de langue
tirée (CPU et GPU en échec)" -- both delegates failing is exactly what
a missing asset looks like. The feature is opt-in and degrades without
crashing, which is why this went unnoticed: point 56 recorded it as
shipped and device-confirmed, which was true of local debug builds but
never of the artifact users install.

release.yml now downloads both models, since that is the APK that gets
published. ci.yml does too, so the CI build stays identical to the
published one and a future packaging problem surfaces on a PR rather
than at release time. codeql.yml deliberately keeps downloading only
the first: it just compiles for analysis, where a runtime asset
changes nothing. That asymmetry is commented in both files so it is
not "fixed" later by mistake.

The URL was checked rather than copied on faith: HTTP 200 and
4 117 670 bytes, byte-for-byte the size of the local model already
known to work. Expect the published APK to grow by about 4 MB.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
@guyiome
guyiome merged commit 39c7265 into main Sep 16, 2026
3 checks passed
@guyiome
guyiome deleted the fix-tongue-embedder-shipping branch September 16, 2026 16:01
guyiome added a commit that referenced this pull request Sep 16, 2026
Patch release whose point is to get the tongue-out stage 3 packaging
fix (#36) into an artifact users actually install: image_embedder.tflite
was downloaded by no workflow, so the feature could never initialise in
any published APK. Also carries the failed-init latch fix from the same
PR.

As for v0.3.1, bumping versionName is a prerequisite rather than
bookkeeping: the in-app update checker compares BuildConfig.VERSION_NAME
against the latest release tag, so tagging v0.3.2 on a build still
reporting 0.3.1 would leave a permanent "update available" badge on
devices already running the newest build.

Verified the built APK reports the new version rather than assuming the
edit took (aapt2 dump badging: versionCode='5' versionName='0.3.2').
Unit tests, debug build and lint pass.

Co-authored-by: Claude Opus 5 <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant