Symptom. On clean, completed single turns, the leading-emoji face animation only fires
for sad (and smile, per UC2). Surprise and thinking never change the face.
Expected. Each reply's leading emoji drives a distinct face (๐๐๐ข๐ฎ๐ค๐ ๐๐๐ด).
Code verification (2026-07-11) โ kid-mode-filter hypothesis REFUTED. The kid_mode
full-turn filter (custom-providers/textUtils.py:121-147, post-4233bb2) is a pure regex
gate, not a rewriter: on a clean turn it re-yields the buffered chunks byte-for-byte
(leading emoji intact โ asserted by tests/test_voice_content_filter.py), and on a hit it
replaces the whole turn with CONTENT_FILTER_REPLACEMENT, which itself starts with ๐.
The dashboard's "Content filter โ recent hits" card showed no filter activity all
session, and the video frames (19:52:40, 19:53:02) show a neutral face during a normally
spoken reply โ no replacement text was played.
Actual root cause the code supports. On the live PiVoiceLLM path the emoji contract
is enforced only by prompt steering (textUtils.py:34-35, _BASE_SUFFIX rule 2).
There is no fallback/re-prefix code anywhere in pi_voice.py/pi_client.py โ unlike
the OpenAICompat provider, which buffers the reply head and prepends ๐ when the model
doesn't lead with an allowed emoji (openai_compat.py:229-246). So whenever qwen3.5:4b
omits the prefix or only ever picks ๐/๐ข, the firmware never sees ๐ฎ/๐ค. (Server-side
get_emotion()/EMOJI_MAP do map ๐ฎโsurprised and ๐คโthinking correctly, so it's not a
mapping gap.)
Suggested fix. Port the OpenAICompat leading-emoji enforcement into the pi_voice
stream path (buffer to first non-whitespace, prepend fallback emoji if absent), and/or
strengthen persona-prompt steering. Optionally log the raw first chunk per turn to measure
how often the 4b model actually emits each emoji.
Repro. kid_mode ON or OFF (irrelevant per above); ask a surprising / thinking-provoking
question; face stays neutral while a completed spoken reply plays. First UAT attempt was
also confounded by ASR fragmentation (see the ASR mishear issue) + a device crash
(~19:50:55); the clean retry still failed.
From the filmed UAT session 2026-07-11 (uat-sessions/2026-07-11/: results.csv, container logs, two video recordings). Investigated and drafted with AI assistance (Claude Fable 5 via Claude Code); human-reviewed before filing.
Symptom. On clean, completed single turns, the leading-emoji face animation only fires
for sad (and smile, per UC2). Surprise and thinking never change the face.
Expected. Each reply's leading emoji drives a distinct face (๐๐๐ข๐ฎ๐ค๐ ๐๐๐ด).
Code verification (2026-07-11) โ kid-mode-filter hypothesis REFUTED. The kid_mode
full-turn filter (
custom-providers/textUtils.py:121-147, post-4233bb2) is a pure regexgate, not a rewriter: on a clean turn it re-yields the buffered chunks byte-for-byte
(leading emoji intact โ asserted by
tests/test_voice_content_filter.py), and on a hit itreplaces the whole turn with
CONTENT_FILTER_REPLACEMENT, which itself starts with ๐.The dashboard's "Content filter โ recent hits" card showed no filter activity all
session, and the video frames (19:52:40, 19:53:02) show a neutral face during a normally
spoken reply โ no replacement text was played.
Actual root cause the code supports. On the live
PiVoiceLLMpath the emoji contractis enforced only by prompt steering (
textUtils.py:34-35,_BASE_SUFFIXrule 2).There is no fallback/re-prefix code anywhere in
pi_voice.py/pi_client.pyโ unlikethe
OpenAICompatprovider, which buffers the reply head and prepends๐when the modeldoesn't lead with an allowed emoji (
openai_compat.py:229-246). So whenever qwen3.5:4bomits the prefix or only ever picks ๐/๐ข, the firmware never sees ๐ฎ/๐ค. (Server-side
get_emotion()/EMOJI_MAPdo map ๐ฎโsurprised and ๐คโthinking correctly, so it's not amapping gap.)
Suggested fix. Port the OpenAICompat leading-emoji enforcement into the
pi_voicestream path (buffer to first non-whitespace, prepend fallback emoji if absent), and/or
strengthen persona-prompt steering. Optionally log the raw first chunk per turn to measure
how often the 4b model actually emits each emoji.
Repro. kid_mode ON or OFF (irrelevant per above); ask a surprising / thinking-provoking
question; face stays neutral while a completed spoken reply plays. First UAT attempt was
also confounded by ASR fragmentation (see the ASR mishear issue) + a device crash
(~19:50:55); the clean retry still failed.
From the filmed UAT session 2026-07-11 (
uat-sessions/2026-07-11/: results.csv, container logs, two video recordings). Investigated and drafted with AI assistance (Claude Fable 5 via Claude Code); human-reviewed before filing.