Watch
1
0
Fork
You've already forked SouveraineOS
0

compositor pushes the strip; float, gamma

This commit is contained in:
Fimeg 2026-08-05 19:16:41 -04:00
commit 1aa61030dd
5 changed files with 479 additions and 3 deletions

View file

@ -0,0 +1,122 @@
# Handoff — 2026-08-05, navigation & the window sheet
**Your job first: audit what this session did against the docs, then judge
whether it actually delivered Casey's ask.** Do not start new work until you
have. Several things were built fast and one was built wrong and rewritten.
## Read these FULLY. Do not grep them.
Grepping these produces confident, wrong architecture — it is the single most
reliable failure in this project, and it is written down in three places.
1. `SouveraineOS/START-HERE.md`
2. `SouveraineOS/docs/tasks/55-hand-verbs-on-the-glass.md` — the sheet; most of
this session is its "what landed" section
3. `SouveraineOS/docs/tasks/60-the-multitasking-view.md` — written after the
multitasking view was built wrong and Casey said so. **Uncommitted.**
4. `SouveraineOS/docs/SESSION-AUTHORITY-DOCTRINE.md` — §13 first, then §4, §9-11
5. `SouveraineOS/docs/DEVICE-STATE-MACHINE.md` — §1, §4, §12
6. `SouveraineOS/docs/LOCK-DPMS-LESSONS.md` — §1 is the lock-then-blank invariant
7. `souveraine-viewtop/docs/SHELL-BOUNDARY.md` — the engine runs in-process
8. `souveraine-viewtop/crates/compositor/src/workspace.rs` — header only; the
zone model, and why a zone exists because something is on it
9. `souveraine-viewtop/crates/compositor/src/gesture.rs` — header only; the
compositor recognises, the shell draws
10. `SouveraineOS/docs/tasks/59-her-face-on-the-glass.md` — TASK-59 phase 1
passed on hardware; phase 2 is unstarted
## State of the tree — READ THIS BEFORE TOUCHING ANYTHING
- **`souveraine` has UNCOMMITTED work.** `ZoneOverview.qml` (new), the rail's
gesture rewrite, `Overview.qml` rewired to zone cards, `deploy.sh` manifest,
`ViewtopControl.qml`. It is **deployed to the phone** and **not committed**.
Commit or discard deliberately; do not deploy over it without looking.
- **`souveraine-viewtop` is pushed** at `8e6700b` and **not yet on the phone**.
The device runs `r84.g2d0ffdc`. So the compositor there does not yet report
`focused`, `app_id` or `title` — which `ZoneOverview` needs to group and
label cards. **Until `r85+` installs, the new overview will draw zones but
its cards cannot match pictures to windows.** That is expected, not a bug.
- CI publishes automatically now: green `main` on viewtop dispatches
Pixel3Arch's `packages.yml`. Verified end-to-end (run 1176 → 1177).
## What was asked, and what happened
Casey's asks, in order given:
1. **Remove three-finger tap from everything binding it** — done. It opened the
overview; `Action::Overview` is gone.
2. **Three-finger tap opens an overlay on the app: resize, split, close**
built (`WindowSheet.qml`), deployed. Now four buttons: Move, Resize, Split,
Close. Close is polite on tap, `kill` on a 1 s hold.
3. **Zones/states, not workspaces; drop Hyprland verbiage** — done in new code
and in the shell. **The wire op is still spelled `workspace`** and the
compositor's `workspace.rs` still uses the old noun throughout. A rename
sweep is owed and was deliberately not started.
4. **Long-press power → context overlay** — built (`PowerMenu.qml`), shows real
USB-C data role + charger state. Not lock-gated.
5. **Short swipe → multitasking, long swipe → home; dock always on zone one**
built. Home is 18% of panel height.
6. **Multitasking must show all backgrounded zones including splits**
**this is the one that was built wrong twice.** First as a live scale of the
active zone (a transition mistaken for a destination), then rewritten as
`ZoneOverview.qml`. **The rewrite is deployed but UNVERIFIED on glass.**
TASK-60 holds the design questions that were answered by fiat, not by Casey:
card = zone, `ScreencopyView` per window inside it, focus reported by the
compositor, refresh on open, scale-on-drag deleted. **Check those against
what he actually wanted.**
## Bugs found and fixed, worth verifying rather than trusting
- **`pose` never worked.** It was applied by passing a smaller scale to
`render_elements_from_surface_tree`, which positions but does not resize; the
size comes from `Element::geometry(scale)`, called later with the *output*
scale. Windows moved and never shrank. Fixed with `RescaleRenderElement`.
Casey diagnosed it precisely: *"the content slid to meet the upper left
corner instead of shrinking."* **Verify on glass — it was confirmed broken by
eye, and the fix has not been confirmed the same way.**
- **Firefox drew off-centre** because the main render path never subtracted
`window.geometry().loc` (CSD shadow inset). The popup and capture paths both
did. Fixed. Not folded into `window_origin` on purpose — that feeds the hit
test, and correcting it there would move touch targets instead.
- **Every window got two zone-membership entries**`new_toplevel` had a
pre-existing `on_workspace.push` plus the new explicit claim. Duplicates keep
empty zones alive forever, which is what `prune_workspaces` exists to prevent.
- **Three-finger drag deleted outright**, not retuned: it beat the tap to nearly
every gesture. The window carry came back as `grab`/`drop`, entered from the
sheet. While grabbed, `Route::Client` becomes `Withheld` so the app underneath
stops receiving the finger.
- **CI never published viewtop.** Its own workflow only ran fmt/clippy/test/
cross-build. Fixed by dispatching Pixel3Arch's job on green `main`; the 403
was `LOCAL_GITEA_TOKEN` existing only as a Pixel3Arch repo secret.
## Known-not-done — say so rather than discovering it later
- **The dock when an app opens.** Casey asked twice; never resolved. The dock is
a *global* layer surface whose visibility is now *per-zone* (pinned on home).
Its exclusive zone may still reserve space on every zone. Needs eyes on glass.
- **A real resize handle.** Resize cycles half → two-thirds → full. No corner
drag.
- **Widgets on zone one.** It is kept clear and never pruned; nothing fills it.
- **The runtime-lock paint gap.** Power → black → lockscreen paints → blank.
Decisions are correctly ordered (`lock-before-blank``lock-directive`
`panel-off`); the black is `frame_elements` compositing lock surfaces *and
nothing else* while the shell's lock surface has not painted. Casey's rule:
lock, confirm the lockscreen is up, *then* blank — which is viewtop's to
attest, since only it knows a lock surface is presented on every output.
- **Casey's OSK bug from the morning handoff** (search bar "jerked", keyboard
did not open) — never investigated. Untouched by this session.
- **`app_id`+`title` as a join key is approximate.** Two terminals with the same
title collide. The sound fix is a shared id in foreign-toplevel.
## The trap that cost this session the most time
Reaching for the phone's control socket earlier would have saved an hour:
`$XDG_RUNTIME_DIR/souveraine/viewtop.sock`, one JSON object per line.
`{"op":"describe"}` is the authoritative verb table, `{"op":"workspaces"}` lists
zones and windows, and `{"op":"scene","intent":"close","id":N}` closed two apps
Casey could not close — no rebuild, no deploy. The phone has no socat/nc but has
python3.
Also: `qs -c souveraine ipc …` needs `WAYLAND_DISPLAY` set or it reports "no
running instances" while the shell is plainly running. sessiond has it; an ssh
session does not.

View file

@ -22,6 +22,24 @@ accepted**. The **CS40L20 is absent from this kernel entirely** — zero dmesg
hits, no i2c node. So this is no longer "which of two drivers"; it is one live
driver and one unbuilt one.
**Measured again 2026-08-05, at the device, and the chain is hot end-to-end.**
A live check from the laptop repeated the 2026-08-02 result and added the two
things the O2 did not know:
- `feedbackd` is **D-Bus activatable** (`/usr/share/dbus-1/services/
org.sigxcpu.Feedback.service`), not a systemd unit — the unit will never
"exist", it activates on first D-Bus caller. Profile `quiet` is live
(`Profile` property reads `"quiet"`), and the udev rule types `spmi_haptics`
as `FEEDBACKD_TYPE=vibra` with `TAG+="uaccess"``casey` holds `rw` ACL on
`/dev/input/event5`.
- The shell's exact call (`TriggerFeedback` `button-pressed`/`button-released`/
`bell-terminal` as shown in `Haptics.qml`) **all succeeded** (feedback ids
5965 in a row), and feedbackd holds fd 5 open on `/dev/input/event5`.
- **Correction to the old acceptance:** `/sys/class/leds/vibrator/brightness`
**does not exist on this kernel**. The only output path is the input FF
interface (`EVIOCSFF`/play), and that path is confirmed working. The
acceptance line forgiving `/sys/class/leds/vibrator` must change.
**Question 1, now down to one thing a hand can answer: does it buzz?** If yes,
the PMIC reaches the motor and the Cirrus part is an upgrade. If no, the LRA is
wired to the CS40L20, nothing has ever vibrated, and this task is the *enabler*
@ -73,9 +91,12 @@ CS40L20's, not the PMIC's.
## Acceptance
- Question 1 answered in writing, at the device, before the port starts.
- If the port proceeds: probe + firmware load visible in `dmesg`, a vibration
from `/sys/class/leds/vibrator/brightness`, and f0 tuned against this unit's
factory values rather than the defaults.
- If the port proceeds: probe + firmware load visible in `dmesg`, and f0 tuned
against this unit's factory values rather than the defaults.
- A trigger through feedbackd (the `TriggerFeedback` path the shell already
uses) results in the LRA firing — the physical half of Question 1. The old
`/sys/class/leds/vibrator/brightness` check is **retired**: that node does not
exist on this kernel; the input FF interface is the only output path.
- feedbackd drives it through the same profile the dial already uses — the
shell must not learn a second haptics API. One owner per input
(`SESSION-AUTHORITY-DOCTRINE` §10) applies to outputs too.

View file

@ -0,0 +1,141 @@
# 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:
> *"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
> opened even if focused is not scalling down. Second, all we have is scaling;
> it's supposed to be showing me all sorta backgrounded zones; including split
> zones. You've seen macOS. That whole multitasking view is a function of it's
> own."*
**Repo:** `souveraine` (shell surfaces), `souveraine-viewtop` (capture + zone
facts).
## What was built, and why it is not the thing
The rail's short swipe scales the live windows on the active zone from 1.0 to
0.6 as the thumb climbs (`ViewtopControl.poseActiveZone`), then opens
`GlobalStates.missionControlOpen`. That is a **transition** — the animation of
leaving an app — and it was mistaken for the **destination**.
Two failures follow from that mistake, and both were seen on hardware:
1. **It scales the wrong windows.** `poseActiveZone` poses everything whose
`workspace` matches `ViewtopControl.activeZone`, and that list comes from a
**2-second poll** of `{"op":"workspaces"}`. Open a second app and the poll
has not caught up, so the newly focused window is not in the set being
scaled. There is no "focused window" fact in the shell at all — the
compositor knows it (`Surface::focused`) and does not report it in the
`workspaces` reply.
2. **Scaling one zone is not a view of all of them.** Even done perfectly, it
shows the zone you are already on, smaller. It cannot show what is
*backgrounded*, which is the entire question multitasking answers.
## What exists
| Piece | Where | State |
|---|---|---|
| `WindowOverview.qml` | `modules/souveraine/navigation/` | One column of equal cards, one per **window**, live `ScreencopyView` texture, flick-up to dismiss |
| Zone facts | `{"op":"workspaces"}` | `count`, `active`, `offset` (the float the strip is scrolled to), `settled`, and per window `{id, workspace, at, size}` |
| Per-window capture | viewtop, since 2026-08-03 | A card can show a window rather than the screen covering it |
| `pose` / `unpose` | viewtop | Now genuinely scales content (fixed 2026-08-05 — it had only ever moved it) |
**`WindowOverview.qml`'s own header states the gap**: *"viewtop has them
[workspaces], and they are not Hyprland's: a continuous strip that grows when a
window needs a room and shrinks when the last one leaves… That is a thing this
overview could draw and does not yet."* This task is that sentence.
## What macOS actually does, since it is the named reference
Mission Control is **two levels in one surface**: a row of *spaces* across the
top, and the windows of the current space spread below. Neither alone is the
feature — the row answers "where else am I?" and the spread answers "what is
here?". Dragging a window onto a space in the row moves it there, which is the
same gesture doing placement.
Ours differs in one load-bearing way and it must not be papered over: **macOS
spaces are a fixed set the user creates; viewtop's zones are minted and
destroyed by need** (`workspace.rs`: "a workspace exists because something is on
it"). A row of zones is therefore a row that grows and shrinks under you, and
home is the one that is always there and always empty.
## The questions
### Q1. What is a card — a window, or a zone?
Today it is a window. macOS says both, at two levels. A phone has room for one
level at a time. Options: zones-as-cards with their windows composited inside
(split zones then read correctly, which is Casey's explicit ask); windows-as-
cards grouped by zone; or a two-level surface where the zone row is a strip and
tapping one spreads its windows. **Q's real content:** a split zone has two
windows and must look like one thing you can switch to, not two.
### Q2. Live textures, or posed real windows?
Two mechanisms now exist and they are not the same:
- `ScreencopyView` — a live *picture*. Cheap to lay out anywhere, but it is not
the window; input goes to the card.
- `pose` — the *real* window, scaled, still touchable through `unpose`'s inverse
mapping.
The reference compositor composes client windows as external textures directly
into the shell's scene, which is the third answer and the one the in-process
Flutter shell would make natural (`SHELL-BOUNDARY.md`). Choosing here decides
whether the overview is a gallery of pictures or the actual desktop, zoomed
out — and whether the transition can be continuous into it.
### Q3. Where does the focused window come from?
The shell has no such fact. `Surface::focused` exists in the compositor's
registry and is not in the `workspaces` reply. Any answer to Q1 needs it, and it
should be reported rather than inferred — inferring "the last window I saw
open" is the kind of shadow copy §4 spends its whole argument against.
### Q4. Is the poll acceptable?
`ViewtopControl` polls every 2 s. That is already the proximate cause of failure
1. A view built on it will be wrong every time something changes faster than
two seconds — which is every gesture. Either the compositor pushes zone/window
changes, or this surface asks synchronously when it opens and stops guessing in
between. **Design the push channel; it is owed anyway.**
### Q5. What does the transition become?
If the destination is a real view, the scale-on-drag is either (a) the first
frames of it, continuous into the cards, or (b) noise that should be deleted.
macOS and Phosh both do (a) — Phosh's `home.c` is a drag surface travelling
between two states, not a button. (a) is better and harder: it means the
transition and the view share one progress value, which is the reference
overview's whole trick (`WindowOverview.qml` already cites it: "one `progress`
driving everything").
### Q6. What can you do to a card?
Switch to it, certainly. Close it — the `close`/`kill` verbs exist. Move a
window between zones — `{"op":"workspace","to":N,"surface":id}` exists too, so
drag-a-card-onto-a-zone is reachable today. Decide before building, because it
changes whether cards need to be drag targets.
## Acceptance (measured, on the phone)
- Every zone with something on it is represented, and a **split zone reads as
one destination containing two windows** — not as two unrelated cards.
- The view reflects state at the moment it opened, not up to two seconds stale.
Open an app, immediately open multitasking: the new app is there.
- Tapping a card goes to that zone. Home is reachable from it.
- Whatever drives the transition and whatever draws the view agree — no frame
where a window is scaled by one and laid out by the other.
- The scale-on-drag either continues into the view or is gone. Not both.
## Connects to
TASK-14 (the overview pane and its app grid; "a grid of workspace thumbnails is
dead weight on a phone" is the constraint that made cards windows in the first
place), TASK-55 (the sheet, and `ViewtopControl` as the one path to scene
verbs), TASK-43 (the compositor's surface model and per-window capture), TASK-52
(atmosphere — `pose` is a gravity well and this is its first real user),
`VIEWTOP-AND-DENIAL.md` (external textures into the shell's scene; Q2's third
answer), `SHELL-BOUNDARY.md` (the in-process shell that would make it natural).

View file

@ -0,0 +1,191 @@
# TASK 61 — the colour path, whole: gamma, gamut, sRGB, precision
**Status: 2026-08-05 — scoped, not started.** The full design of record for
restoring colour fidelity on viewtop. This is one task, not a roadmap with
"build this first": every piece below is in scope and nothing is waved off as
deferred. It closes three separate, code-confirmed defects:
1. **Gamma control is dead.** The shell's brightness→gamma slider shells out to
`hyprctl hyprsunset …` (`surfaces/quickshell/ii-base/services/Hyprsunset.qml:110,116,125`).
That is Hyprland IPC. viewtop exposes no `hyprctl`, no gamma global, no LUT
path — so the call silently no-ops since the viewtop move.
2. **The panel is wide-gamut and unmapped.** blueline's panel is the LG SW43408 —
1080×2160, 4-lane DSI, ~DCI-P3 OLED, 24-bit RGB, DSC, no EDID. viewtop emits
raw sRGB bytes onto it with zero gamut mapping (`kms.rs` scans out
`[Xrgb8888, Argb8888]`, and the forked GLES has no sRGB path at all), so
everything renders against Hyprland saturated.
3. **Nothing asks for more than 8-bit sRGB.** `kms.rs:340` hardcaps the scanout
formats; the fork renders `GL_RGBA8` and never enables the pieces it already
knows about — `egl/display.rs:606` carries `srgb: false` and a `// TODO:
EGL_KHR_gl_colorspace to know that`.
**Repos:** `souveraine-viewtop` (compositor, wire verbs, and the smithay GLES
pin that builds the renderer) and `souveraine` (shell surfaces). The kernel/DPU
is deliberately not changed by this task; the door is left open and a gate
written (Part 6).
## Part 1 — gamma is a verb, not a hack
Gamma is an authority claim on the glass, so it goes in the compositor's own
verb table — `crates/wire/src/verbs.rs`, the `INTENTS` array whose round-trip
test at the bottom of that file makes a lying table fail the build.
Add one entry:
```json
{"intent":"gamma","value":45}
```
- `mutates: true`; refuses `InvalidArgument` | `RefusedByState` | `Unavailable`
(the last for "panel is off").
- `value` in 0..=100, mapped to the panel's ramp; the `example` must round-trip
through `ToCompositor` like every other line.
It does **not** route through sessiond. The compositor owns pixels and LUTs;
the state machine owns device states. Gamma is a pixel claim, not
proprioception — the same boundary TASK-30/31 drew for the other pixel verbs.
**Apply side.** The fork's `DrmDevice` implements `ControlDevice`
(`backend/drm/device/mod.rs:128`), and the pinned `drm` crate exposes
`Device::get_gamma` / `set_gamma(crtc, red, green, blue)` plus
`crtc.gamma_length()` (`drm-0.14 control/mod.rs:599,627`, `control/crtc.rs:93`).
So the handlers:
- build a `value`→curve ramp of the panel's own length (identity at 100),
- `self.kms.drm.set_gamma(crtc, &lut…)` on the verb,
- reset to identity on boot and on `session_paused`/`session_resumed`, so a
ramp never outlives its session or survives a resume wrong-headed.
A colour-temperature curve (Part 5) is the same slot with a different curve
generator.
**Shell side.** Replace `Hyprsunset`'s channel, not its API. Wire the QML
service to the compositor socket — either directly or through the `shell-host`
crate, which already speaks the wire — and keep the public surface byte-identical:
the Brightness↔Gamma toggle, `gamma`, `temperature`, automatic timing. Then
every caller (`QuickSliders.qml`, `GammaIndicator.qml`, the night-light auto
clock) keeps working without touching it.
## Part 2 — the sRGB/linear pipeline, and the gamut matrix
This is the heart of the "colour" and lives in the GLES fork that viewtop is
already pinned to (the fork is ours; this is one more set of patches on top of
that pin).
1. **Mark the encode.** Import buffers the client claims sRGB as
`GL_SRGB8_ALPHA8` so texture sampling *decodes* the gamma the GPU knows, and
enable `GL_FRAMEBUFFER_SRGB` around the render so the write re-encodes.
Work happens in linear. When colour management is off this must degrade to
exactly today's byte-through path — identity is load-bearing.
2. **Gamut in linear.** A peroutput 3×3 uniform carries the panel transform.
Two presets, decided by config:
- **natural** — assume DCIP3 primaries (the SW43408's), map linear sRGB into
the panel primaries: content renders *correct*, the way stock Android's
"Natural" did.
- **vivid** — the same matrix composed with a fixed saturation/depth boost,
the stock "Vivid"-style look, not a raw profile.
No ICC files. The panel has no EDID, so the primaries are a constant, not a
probe; a wrong constant is a *fixed* factor, never a lookup that degrades.
3. **Clamp.** Out-of-range values are clamped in the shader to [0,1] before the
(already clamped) framebuffer write; nothing exotic.
Default is **off** (identity, byte-through). Every stage is an explicit config
diagonal, not a rewrite of the render loop.
## Part 3 — the framebuffer & blend invariant
Alpha currently goes through one multiply with no pre-multiplied discipline
(the fork's `texture.frag` does `color * alpha`). The renderer's contract with
the sources decides: adoption of **premultiplied-alpha** at the surface-import
boundary so the linear blending scene doesn't double-count. Invert: the
`NO_ALPHA` and the translucent case must both load-test equal to today's
behaviour on the identity path. Measured, not assumed (see Acceptance).
## Part 4 — precision: 10-bit in the chain
Add the 10-bit variants to `kms.rs`'s scanout formats:
`[Xrgb8888, Xrgb2101010, Argb2101010]`. The renderer already maps the read/write
side — `renderer/gles/format.rs` carries `Abgr2101010 → GL_RGB10_A2` — so this
is a formats decision (score the swapchain at 10) and a couple of allocation
plugs, not a new sampler.
Honest expectation: the SW43408 consumes 24-bit, so the visible win is
banding/legroom through the DPU+panel dither, not new gamut. It is a *chain*
property: it must hold the identity invariant exactly as 8-bit did, including
through the DSC compression path, or the 10-bit path is off (config, not
action).
## Part 5 — the sunset that the slider lost
The gamma verb is the mechanism; the *product* use is the night-light:
- **Automatic sunset** keeps the same LUT slot: a colour-temperature curve for
the chosen mired value, computed once from the temperature and reuploaded on
change — the `Hyprsunset` automatic mode already schedules that part. Time-of-day belongs
to the shell (it owns `Config.options.light.night`); the compositor owns only
the applied LUT.
- Manual gamma override must disarm the automatic state exactly as before —
that rule already lives in the QML service and must survive the rewire.
## Part 6 — the door that stays open: DPU hardware
The DPU (Snapdragon Display Engine) has hardware colour processors (CTM,
gamma, 3D LUT) that Android's "SDE" stack drove. If the DPU exposes crtc
CTM/GAMMA-LUT-properties on this hardware, Part 2's matrices could live in SDE
hardware instead of the shader. **This task does not require it** — the
software path of record is the shader. The gate is a spike, recorded with the
results: attempt to `set_blob`/program the DPU, read back, and decide if the
SDE path is usable vs the shader. Not a pre-condition for anything in
Parts 15.
## Acceptance (measured, on the phone, using the existing capture path)
- **The toggle returns.** The side-panel Brightness↔Gamma switch controls a real
LUT: `gamma` as a verb in `INTENTS`, every `Hyprsunset` contract callable with
no `hyprctl`.
- **LUT authority.** `set_gamma` + `get_gamma` round-trip byte-identical on the
panel's dynamics; value→ramp matches the table; on blank/resume the identity
is restored, never yesterday's ramp.
- **Gamut proxies.** Under `natural` vs `vivid`, a known sRGB test swatch
(e.g. `#ff0000` on the neutral background) moved in the expected two
directions, observed through the compositor's window capture — same sample
in the same spot, delta recorded in the stack.
- **Identity invariant.** Colour=off is byte-through: capture of the same scene
with management off == today's output, no surprise tint.
- **Precision.** A 16-step ramp displays without visible step boundaries at the
current bit depth; with 10-bit on, the same ramp holds smoother (banding
measured by count of unique LSBs across the same capture).
- **Night / adaptive.** Sunset automatic mode runs the same calendar
temperature; manual override disarms it; the LUT after a resume matches the
one before it.
## Open questions (each recorded; none blocks)
- LUT size on the panel by `gamma_length()` (assume 256; keep, or scale if 512) —
decided on first measurement.
- DPU pathway (Part 6) — a spike, its results recorded here, not assumed.
- The 3×3 constants for `natural`/`vivid` — fixed from the panel's generation
(DCI-P3), with the two columns in the same table that any future measured
profile can replace.
## Repos touched
- `souveraine-viewtop``wire/src/verbs.rs` (+ the round-trip test already
enforcing), the verb handler in the compositor, the kms LUT utilities and the
scanout format list, and the fork-GLES patches (sRGB textures,
`FRAMEBUFFER_SRGB`, the linear 3×3 uniform path).
- `souveraine``Hyprsunset` service rewired to compositor verbs, API intact.
## Ordering note
There is an ordering that falls out of the code (the verb first — it is what a
caller can already compose). That ordering is *not* the scope; the scope is all
six parts.
## Connects to
TASK-30/31 (verb tables: this is one more row speaking in the one vocabulary),
TASK-51/53 (the daily-driver move is the event where gamma was lost), TASK-52
(atmosphere — the same warm glass at night), the smithy fork pin (colour lives
in the one GLES surface), and `Pixel3Arch`'s DT for the panel (the DCI-P3
constant, and the DPU door).

View file

@ -59,6 +59,7 @@ Four that are cheap relative to what they unblock:
| # | Task | What's left |
|---|------|-------------|
| 60 | [Multitasking is a place, not a transition](60-the-multitasking-view.md) | Written after building the wrong thing. The rail scales the active zone's windows on swipe — a *transition* mistaken for the *destination*. It scales the wrong windows (2 s poll, and the shell has no "focused window" fact at all), and scaling one zone can never show what is backgrounded. `WindowOverview` draws one column of window cards and has no concept of zones; its own header admits the gap. Needs deciding before code: card = window or zone, `ScreencopyView` pictures vs posed real windows, where focus comes from, and whether the poll gets replaced by a push. |
| 59 | [Her face on the glass: a Live2D presence](59-her-face-on-the-glass.md) | **Phase 1 passed on hardware 2026-08-05: ~58 fps, rig verified drawing (`painted_px` 23.6k/24k), 283 MB WebKit RSS.** Scoped from a Live2D board-girl found in a cloned blog. The reference streams its chat over **SSE terminated by `[DONE]`** — the contract souveraine's server already speaks, so the integration is accidentally done. Base layer is **wry**, not the Chromium already on the phone; the first deliverable is a measured frame rate for the Cubism runtime in WebKit on freedreno, and if that fails the task stops there. Replaces ani-avatar's 2.5 GB of sprite states with one rig. Reference rig ships as the working placeholder and gets edited toward Annie — it just stays out of any public repo. Summoned by app launch and/or the radial dial, not always-on. |
| 58 | [Host power: the SMB2 OTG boost](58-host-power-smb2-boost.md) | Host data works; the boost isn't. The SMB2 5 V boost exists in silicon and mainline's `qcom-smbx-charger` never registers it. Investigation first (which tree builds the running kernel, does smb2 touch the boost, does `qcom_pmic_typec` bind pmi8998), then vbus-supply + regulator + typec. Unlocks host-mode power, power-bank mode, and the Pi without a wall wart. |
| 57 | [The Pi dock head](57-pi-dock-head.md) | Phone stays a gadget; the Pi 4 drives the monitor + dock. NCM link is default, Venus encoder is present, compositor capture landed — the unproven seams are `zwlr_screencopy` vs our own capture IPC, and the Pi's own bring-up ("major malfunctions TBD"). Four phases: Pi bring-up, static chain, real capture + udev trigger, input injection. |