Watch
1
0
Fork
You've already forked SouveraineOS
0

59/60 and the bug dump: what landed the day her face went on the glass

TASK-59 becomes a record of five bugs that each presented as something else —
the missing #live_talk element, --app-id doing nothing, the GL context claimed
before the canvas was sized, posture wired to a function that does not exist,
and a feed that dropped its own reader for being slow. Plus the furniture role,
input regions, gaze, and what the rig costs next.

TASK-60: the cards were zero pixels wide, and the capture-hang theory is
retracted — the session had been left locked by a test.

DUMP: 3/4/5 closed, 7 retracted with the trap that produced it, 8/9/10 new.
This commit is contained in:
Fimeg 2026-08-07 02:29:37 -04:00
commit 528d290ba6
3 changed files with 269 additions and 5 deletions

View file

@ -48,7 +48,53 @@ model can carry it out without re-deriving context.
the global or ship a recorder that speaks ext-capture. Severity: medium,
this gates the radial-dial record entry.
## Recording — narrowed 2026-08-07
## Resolved 2026-08-07
Items 3, 4 and 5 are closed, and item 7 below turned out to be a false lead.
- **3. The avatar renders nothing — FIXED.** Not the loopback origin. The page
omitted `#live_talk`, which the bundled character layer reads every frame;
it threw on the first update after the model and all four textures had
loaded. See TASK-59's 2026-08-07 section for the five bugs in that chain.
- **4. A second app splits instead of minting a zone — FIXED and verified.**
Three apps → zones 1/2/3. The fix (`60ff764`) had been committed and never
pushed, so the phone had never had it. **The null `app_id`/`title` in the
`workspaces` report was real and is also fixed** — the report calls them
"the join key" and nothing had ever written them — but it was not what broke
the cards (see 7).
- **5. Does viewtop serve wlr-screencopy — YES.** `grim` works. It hangs only
behind a lock, which is the capture gate doing its job.
- **7. Screencopy hangs / the cards are blocked on it — WRONG, and here is the
trap.** The compositor was refusing captures with *"no client captures the
screen behind a lock screen"* because the session had been left locked by a
test, while `logind` reported `LockedHint=no`. **Check `locked` in `state`
before concluding anything from a capture failure.** The cards were zero
pixels wide; see TASK-60.
## New, 2026-08-07
8. **The pill strands a pose.** A partial slide up leaves the app scaled small
and it takes another pull on the pill to get it back. `onCanceled` already
exists on the rail and already calls `endPull(false)` — **that is not the
hole**, and a duplicate handler added there fails the whole QML module
("Property value set multiple times") and takes the shell down. Cause
unfound. Casey also wants the partial slide gated on speed *and* length and
then committed, iOS-style, with no backing-off state at all.
9. **Side panels glitch on the way out.** The keyboard half is fixed — the
drawer cleared `oskOpen` on close and `SidebarLeft` never did, so the OSK
outlived the only text field it was serving. The visual glitch on leaving is
still uncharacterised.
10. **The terminal cannot keep the keyboard.** It disappears while the cursor
is in a field and the right panel closes with it. Note a terminal never
requests text-input so the OSK must be summoned, and `oskOpen` **flaps by
design** because the OSK self-hides whenever input-method focus drops —
never gate a persistent surface on it. Being the mirror image of 9 suggests
one focus-handoff bug, not two.
## Recording — narrowed 2026-08-07 (superseded by "Resolved" above)
7. **Screencopy hangs while mission control is open, and that is why the
cards never appear.** Measured on blueline: `grim` returns a file in under

View file

@ -1,7 +1,167 @@
# TASK 59 — her face on the glass: a Live2D presence for Annie
**Status: 2026-08-05 — phase 1 PASSED on hardware. Cubism renders on blueline at
~58 fps in the engine wry wraps.** The gate this task set for itself is cleared;
**Status: 2026-08-07 — SHE IS ON THE GLASS.** She draws, she is furniture on
home rather than an app, she takes touch only on her silhouette, she follows a
finger anywhere on the panel, holding her records and sends through the
existing STT, double-tapping her dismisses her, and locking the phone sends her
away. Q0/Q1/Q1a/Q3/Q4 are answered in code. What remains is her *own* face
(TASK-17-shaped, see "the rig is next") and idle behaviour (Q2).
## 2026-08-07 — what landed, and the five bugs that were in the way
Every one of these presented as something else, which is the reason they are
written down rather than summarised.
1. **The rig never drew, and it was one missing element.** `live2d.js` is not
only the Cubism runtime — it is the reference's webpack bundle with the
character layer inside, and `Live2DModel.update()` reads
`#live_talk`'s value **every frame** as its motion-state slot. Our page
omitted it, so it threw `TypeError: null is not an object` on the first
update — *after* the runtime had logged its banner and loaded the model and
all four textures. Assets 200, WebGL live, rAF at 60, no error reaching
Rust, blank canvas. Exactly the failure this task predicted would cost an
afternoon, and it did.
**`#live_talk` starts at `"0"`, not the reference's `"1"`** — `"1"` means
*start a talk motion*, so she came up miming speech with no voice behind it.
2. **`--app-id` was a no-op.** `set_prgname` ran *after* GTK had already created
the surface, so the compositor saw `souveraine-web` and window rules could
only ever match the title. It has to run before the event loop is built.
Without this the compositor cannot recognise her at all, so the whole role
below is unreachable.
3. **Claiming the GL context for `preserveDrawingBuffer` must happen after the
canvas is sized.** Before it, the context is born at the default 300x150 and
`gl.viewport` stays there while `fit()` grows the buffer to the panel — the
rig then draws into a 300x150 corner of a 1080x1994 buffer, and because GL's
origin is bottom-left she appears as a thumbnail in the bottom-left of the
screen. Same cause as the silhouette sampling reading 9 lit cells of 864.
4. **`posture()` called `window.live2dMotion`, which does not exist.** Nine
states advertised to her in the prompt and wired to nothing. The bundle
exposes two levers: `#live_talk`, and its own head/body hit test. The nine
collapse onto those — speaking talks, idle rests, and the rest are a look:
head for attention, body for effort. The bundle rate-limits its own hit
reaction to ~8 s, which is why a burst of tags reads as one shift.
5. **The gaze feed died of its own speed.** `Feeds::publish` dropped any
subscriber whose channel was `Full`, so ~25 pushes/s filled the queue faster
than a QML socket drained it and the shell was unsubscribed inside the first
burst of a drag — never told. An external probe that read faster kept
working, which is what made it look like the shell's bug. A full channel now
skips the message; only `Disconnected` drops a subscriber.
(Also: Quickshell's `Socket` emits `onConnectionStateChanged`, not
`onConnectedChanged`, so the subscribe was never sent.)
### The role — Q1 answered in the compositor
`Role::App | Role::Furniture`, decided at the **first commit** and not at map,
because `xdg_toplevel` is created before the client sends `set_app_id`. The old
path minted a zone, moved the strip, took the keyboard and told the dock about
a window it had not yet identified. Furniture (`org.souveraine.face`) takes no
zone, is announced to nobody, wears no frame, keeps its own size, and hangs
from the **top** of the zone with all the leftover room reserved below her —
Casey wants that space for what she shares (data, a page, something of hers).
`state` reports `furniture` and its `input_rects`, because neither is visible
on the glass and a face that swallows the screen looks identical to one that
does not.
### Her input region — and the compositor bug under it
She is a transparent surface the width of the panel, so without an input region
she takes every contact on home. The page samples her own alpha on a 24x36 grid
and reports row-runs; the host installs them with `input_shape_combine_region`.
Measured: 36-47 rectangles, ~24% of her bounding box.
**`windows_under` never consulted `wl_surface.set_input_region`** — so the
client could declare whatever it liked and viewtop still handed it every
contact. Fixing that also stopped GTK client-side-decoration shadows from
eating taps meant for what is behind them.
### Gaze — Q3 answered, and it is better than tracking-on-touch
A `gaze` verb on the control socket: its own feed (the strip changes when the
world does; a contact changes tens of times a second), throttled to 40 ms,
physical contacts only, and the release is never dropped or she keeps staring
at where the finger last was. The shell forwards it as a synthetic
`mousemove` on the canvas, so `live2d.js`'s own damping does the work.
**Synthetic is the point.** Her input region means the compositor deliberately
does *not* hand her those contacts — this lets her watch a finger she is not
being given, anywhere on the panel, rather than only when a drag starts on her.
### Q1a — holding her to talk works, and is the wrong affordance
Press and hold past 350 ms → `pw-record` at 16k mono s16 → release →
`souveraine-stt --file``Souveraine.send()`, so the sidebar logs it and the
reply streams back to her bubble. Verified end to end.
**Whisper wraps its output with newlines** and the transcript arrives through a
`SplitParser` on `"\n"`, so it must be collapsed or one utterance becomes four
turns.
Casey, 2026-08-07: *"holding onto her has a different connotation."* The
gesture works and should be replaced by explicit controls. Recorded here rather
than fixed.
### Q4 answered: whose she is
Double tap her to dismiss — the same gesture in the same place that summoned
her from the clock, because the clock has faded by then and a double tap on an
invisible object is not a route. **Locking sends her away and she stays away**;
you come back to the clock and summon her if you want her.
Gated on `screenLockSecure`, the compositor's ack, **not** `screenLocked`: the
request drifts. Measured — `session lock` answered `already-locked` while
logind reported `LockedHint=no` and the phone was in use. A face gated on that
would have been permanently dismissed with nothing on screen to explain it.
### The clock yields
`CookieClock` and `CookieQuote` fade and shrink out when she is up (180 ms out,
260 ms back, 0.86 scale — the overview cards' own number, so the device's
theatre moves alike), and the faded clock stops being a tap target.
`CookieQuote` needed a souveraine override: it is the clock's *sibling* in
`ClockWidget`'s Column, and that parent lives in `ii-base`, which never reaches
the phone.
`CookieClock` had been reading `Face.rigPresent` — **a property that does not
exist**, left behind when the rig pre-flight check was deleted. It bound to
`undefined` for as long as it had been there.
### Her size is her scale
The Cubism view fits the rig to the canvas, so shrinking the canvas shrinks
*her*; it does not trim the empty margin around her. 640 was tried to cut the
~100 px of dead space under her feet and came back *"a tiny version that's
scaled odd."* The margin is the rig's own `layout` (`center_y`, `width`) in
`model.json` — moving it is a rig change, not a window one. **540x760.**
### The rig is next, and what it costs
Casey wants her to look like Annie: blonde twin-tails, teal circuit markings, a
six-state sheet (IDLE / ALERT / ENGAGED / PROCESSING / YAWNING / STRAINING)
that maps almost 1:1 onto the posture vocabulary already in `Face.qml`.
**V1 is the face**, not the outfit: Histoire reads as a little girl and not a
woman, so shrink the eyes and slim the face with transparency. Hair already
reads close. Wings become holographic — "witchy wizard tech woman".
The real goal is a **pipeline, not a repaint**: strip the rig's parts out by
name with their exact UV footprints, rebuild by compositing them back at those
footprints, and let agents swap components the way they swap outfits. The
rebuild half is what makes swapping real. See TASK-18's notes in the session
task list and the ceiling below.
**Ceiling, so it is not discovered late:** parts are paint; silhouette is mesh.
Twin-tails, proportions and the sitting-on-a-book pose need a new rig — and
that is a Cubism 2 → 4/5 change, a **different loader**, which also replaces
the character layer that reads `#live_talk`.
**Watch:** the blink/eye-open deformers are authored against the *original* eye
geometry, so a smaller eye may not line up with the eyelid sweep. Test by
rendering a blink, not by looking at the atlas.
## 2026-08-05 — phase 1 PASSED on hardware
**Cubism renders on blueline at ~58 fps in the engine wry wraps.**
The gate this task set for itself is cleared;
what remains is building the host and making her Annie. From Casey, after
finding a Live2D board-girl on a blog he had cloned: *"I want to take a peek at how it
functions and see if there's a crossover event to where it could exist on the

View file

@ -1,7 +1,65 @@
# TASK 60 — multitasking is a place, not a transition
**Status: open — design, 2026-08-05.** Written after building the wrong thing
and being told so. Casey, on device:
**Status: 2026-08-07 — the cards draw.** Zone cards with live previews, a close
control and a zone label, verified on hardware. What remains is the swipe
*into* it, which is still awkward.
## 2026-08-07 — the cards were zero pixels wide
Reported as *"it goes to a blur screen but nothing else"*, and every signal read
healthy: `zones 2, windows 1, subscribed true, loader active, item present,
opacity 1, progress 1`. The blur was the backdrop; the cards were there and
**540 px shorter than nothing** — `item.w: 0, h: 842`.
`ZoneOverview` took its width from the `Column` it sits in, and a Column is as
wide as its widest child. In the drawer the search widget supplies that width;
**mission control is the cards alone and has no search**, so the only child left
was the loader — whose width came from the column, whose width came from the
loader. The cycle resolves to zero. Bound to `panelWindow.width` instead, which
is what this surface covers anyway (its mask is `missionBackdrop`, not the
drawer's sheet).
Two things that made it findable, and are worth keeping:
- **`overview missionControl` and `overview state` verbs.** The one surface
reported broken was the only one nothing but a pill swipe could raise, so it
could not be inspected or tested. `state` reports what the cards are drawn
from, and reading `item.w: 0` beside `column.w: 0` is what located this in
one step after a long time spent on wrong theories.
- **A false lead worth recording:** the compositor was refusing every capture
with `"no client captures the screen behind a lock screen"`, and `grim` hung
the same way the cards did. That is *correct* behaviour behind a lock — the
session had been left locked by a test. Capture was never the problem. Check
`locked` in `state` before concluding anything from a capture failure.
**Cards are inset** (26 px sides, 12 top) so neighbouring zones show through —
full-bleed reads as "you are looking at that app" rather than "here are the
places you can go", and leaves nothing on screen saying there ARE neighbours.
### Still open: the swipe in
Casey, 2026-08-07: *"the visual style from swiping up into this mode... we lost
the consistency... the swipe to this swap is awkward."*
The machinery exists and is where to look. `ZoneOverview.progress` follows
`GlobalStates.zonePullProgress` while the rail is pulling and the state flag
takes over on commit — this task's own acceptance is *"no frame where a window
is scaled by one and laid out by the other."* Two concrete suspects:
1. Cards start at `scale 0.6 + 0.4 * share`, chosen to match the scale the
rail's `pose` has reached at the multitasking detent. **0.6 is a
written-down constant**; if the rail's actual pose there is not 0.6, the
card pops to a different size at handoff.
2. The inset above makes the card **smaller than the full-bleed window it
replaces** at the handoff moment, which may have made that seam worse.
Either the rail's pose should end at the inset card's rect, or the inset
should animate in after the handoff rather than being there at `share = 0`.
Needs watching in motion. Screenshots cannot see it.
## Original — design, 2026-08-05
Written after building the wrong thing and being told so. Casey, on device:
> *"Still major issues. We might need to take these concepts we've built back to
> the workshop. The scaling doesn't follow the selected app. meaning app#2