Watch
1
0
Fork
You've already forked souveraine
0

face: her postures reach the rig, and she stops miming on startup

`posture()` called window.live2dMotion, which does not exist — measured
undefined on the device. Nine states advertised to her in the prompt and
wired to nothing. The bundle exposes two levers: #live_talk, a motion slot it
reads every frame, and its own head/body hit test. The nine collapse onto
those rather than pretending: speaking talks, idle rests, and the rest are a
look — head for attention, body for effort.

#live_talk starts at "0". "1" means start a talk motion, so she came up
mouthing words with no voice behind them.

The gaze socket used onConnectedChanged; Quickshell's Socket emits
onConnectionStateChanged, so the subscribe was never sent — a handler for a
signal that does not exist.
This commit is contained in:
Fimeg 2026-08-07 02:21:06 -04:00
commit 47e8135cdc
2 changed files with 56 additions and 4 deletions

View file

@ -41,8 +41,14 @@
draws nothing at all. That is exactly the "canvas is there and nothing
draws" TASK-59 predicted would cost an afternoon, and it did (2026-08-06).
The runtime writes to this too, so it must be a real input, not a constant.
Starts at "0", not the reference's "1". "1" means *start a talk motion*, so
she came up mouthing words with no voice behind them — Casey, 2026-08-07:
"she is making mouth motions like she's saying something but nothing is
saying." "0" resets to idle and the runtime writes back "3" itself. Talking
is something the shell asks for when there is something to say.
-->
<input type="hidden" id="live_talk" value="1">
<input type="hidden" id="live_talk" value="0">
<script src="live2d/js/live2d.js"></script>
<script>
// The character layer, reduced to what the shell drives. The reference's
@ -103,8 +109,42 @@
clear: function () { bubble.classList.remove('up'); },
// Posture from the shell. The rig's own motion groups are named by the
// reference (idle, tap); anything richer waits on her own rig.
// Posture, driven through the levers the runtime actually has.
//
// This called `window.live2dMotion`, which **does not exist** — measured
// undefined on the device. So every posture tag she emitted was a no-op:
// nine states advertised to her in the prompt and wired to nothing.
//
// What the bundle really exposes is two things. `#live_talk` is a motion
// state slot it reads every frame ("1" talk, "0" reset to idle), and its
// own touch handler hit-tests head vs body and answers with an expression
// or a tap motion. Both are reachable from here; nothing else is, without
// a rig of her own.
//
// So the nine states collapse onto what exists rather than pretending:
// speaking talks, idle rests, and the rest are a look — head for the ones
// that are about attention, body for the ones that are about effort. The
// bundle rate-limits its own hit reaction to once per ~8s, so a burst of
// tags reads as one shift rather than a twitching model.
posture: function (name) {
if (typeof window.live2dMotion === 'function') window.live2dMotion(name);
var slot = document.getElementById('live_talk');
if (name === 'speaking' || name === 'talk') {
if (slot) slot.value = '1';
return;
}
if (name === 'idle' || name === 'rest') {
if (slot) slot.value = '0';
return;
}
var head = ['alert', 'affectionate', 'thinking', 'listening'].indexOf(name) >= 0;
var r = canvas.getBoundingClientRect();
var x = r.left + r.width / 2;
var y = r.top + r.height * (head ? 0.22 : 0.55);
['mousedown', 'mouseup', 'click'].forEach(function (t) {
try {
canvas.dispatchEvent(new MouseEvent(t, { clientX: x, clientY: y, bubbles: true }));
} catch (e) {}
});
},
// Look at a point on the panel, in her window's own CSS pixels.
//
@ -121,6 +161,8 @@
// her first. These do not go through the compositor, so she can follow a
// finger she is deliberately not being given.
lookAt: function (x, y) {
if (window.__gazeSeen === undefined) window.__gazeSeen = 0;
if (window.__gazeSeen++ < 3) console.log('gaze: lookAt ' + Math.round(x) + ',' + Math.round(y));
try {
canvas.dispatchEvent(new MouseEvent('mousemove', {
clientX: x, clientY: y, bubbles: true

View file

@ -329,7 +329,13 @@ Singleton {
path: (Quickshell.env("XDG_RUNTIME_DIR") || "/run/user/1000") + "/souveraine/viewtop.sock"
connected: root.joined
onConnectedChanged: {
// `onConnectionStateChanged`, not `onConnectedChanged` Quickshell's
// Socket emits the former, so the latter is a handler for a signal
// that does not exist and never runs. The subscribe was therefore
// never sent, the compositor never pushed, and she never followed a
// finger. `ViewtopControl`'s feed had the right idiom the whole time.
onConnectionStateChanged: {
console.log("[face] gaze socket connected=" + gaze.connected);
if (gaze.connected)
gaze.write('{"op":"gaze"}\n');
}
@ -343,6 +349,9 @@ Singleton {
} catch (e) {
return;
}
if (root._gazeSeen === undefined) root._gazeSeen = 0;
if (root._gazeSeen++ < 4)
console.log("[face] gaze push: " + line);
if (m.ok !== undefined && m.down === undefined)
return;
if (!m.down) {
@ -357,6 +366,7 @@ Singleton {
// Where her window sits on the panel. Read from the compositor's own
// furniture report rather than assumed, because the layout decides it and
// it moves with the zone.
property var _gazeSeen: undefined
property real originX: 0
property real originY: 0
@ -364,7 +374,7 @@ Singleton {
id: whereAmI
path: (Quickshell.env("XDG_RUNTIME_DIR") || "/run/user/1000") + "/souveraine/viewtop.sock"
onConnectedChanged: {
onConnectionStateChanged: {
if (whereAmI.connected)
whereAmI.write('{"op":"state"}\n');
}