Skip to content

The loader hands the panel's EDID to the compositor, which chooses the display's scale - #895

Merged
Japabu merged 12 commits into
mainfrom
wt/toyos-hidpi
Oct 14, 2026
Merged

Japabu merged 12 commits into
mainfrom
wt/toyos-hidpi

Conversation

@Japabu

@Japabu Japabu commented Oct 12, 2026 •

Copy link
Copy Markdown
Collaborator

On a GOP machine the compositor now knows the panel's physical size and the scale it calls for. The loader hands the panel's EDID to the kernel; the compositor decodes it, chooses a scale, logs it and answers it in inspect display. Nothing draws at the scale yet: that, the display port and its override, the window protocol, the winit fork and virtio-gpu leaving the kernel are issues/the-desktop-draws-at-the-panels-scale.md.

The accepted design and its review rounds are on this PR: the design, reviews 1, 2 and 3, and the acceptance. The orchestrator ruled that this part lands on its own.

What changes

  • The loader reads EFI_EDID_ACTIVE_PROTOCOL before ExitBootServices. It reads it only from a GOP handle that shows the scanout it hands over, matched by the frame buffer's base and the mode's size, pixel format and stride, and only where exactly one such handle carries the protocol. It logs which handle it took, or why it took none. The rule comes from a metal reading (M1, below): the protocol is on the first of two GOP handles on the T14 and on the second on the AMD bench, while the loader's display is the first on both. Two matching handles that both carry one are refused, because an EDID from the wrong output would set the wrong scale silently.
  • The handoff. KernelArgs gains gop_edid and gop_edid_len, and FramebufferInfo gains edid and edid_len: the 128-byte base block, unparsed, and a length of 0 or 128. The kernel refuses any other length the loader writes and reads none of the bytes.
  • lib/edid (toyos-edid), a crate of its own because it decodes bytes from outside the reader's trust. It refuses a short block, a missing header and a bad checksum by name. The size is the first detailed timing's mm; a timing of 0 mm is "undefined" and falls to bytes 0x15/0x16 in cm; one zero byte there is an aspect ratio and both zero are unknown, neither a size (VESA E-EDID 1.4, §3.3, §3.10, §3.11, Tables 3.12 and 3.21).
  • toyos_desktop::Scale::auto, ToyOS's own rule. The desktop's pixel is CSS Values 3 §5.2's reference pixel, 1/96 inch at 28 inches; every panel is assumed viewed from 24 inches, ToyOS's choice and not a published figure, so the target is 112 dpi. The scale is the quarter step from 1 to 4 nearest the panel's density over that, a tie going down, stepped down until the logical screen holds 642x446: an 80x24 terminal of 8x16 cells in Chrome::DEFAULT's frame above the taskbar, computed from those values. Integer arithmetic throughout.
  • The compositor logs compositor: display WxH, panel WxH mm, scale S on standard output, or that the panel gave no size; only a block refused as malformed goes to standard error. inspect display gains scale.
  • The metal row gop_edid_scale rides metalcase's existing boot. It tells the bench by its CPU signature and asserts the loader's line for the handle M1 found and the compositor's size and scale from that bench's fixture.

Size. git diff --shortstat origin/main...HEAD: 32 files changed, 745 insertions(+), 10 deletions(-). Of those, 230 are tests and their fixtures' provenance (lib/edid/src/tests.rs, scale.rs's tests, the row in tests/toyos.rs, fixtures/SOURCE), 44 the fixtures' declarations in build/licence.rs, and 50 the issue files; the rest is production, the largest pieces the loader's handle matching (90) and Scale (92).

Checks and evidence

Evidence is on ToyOSOrg/evidence branch pr-895.

  • M1, the reading that set the handle rule (T14, AMD; a scratch loader report and row at 9d8a3811a, run by the orchestrator, replaced by this change): T14 (LENOVO 20W0003AMZ), panel CMN N140HCA-EAE, 309x173 mm, on GOP handle 0 of 2; AMD bench (LENOVO 82A2), panel AUO B140HAN06.8, 309x174 mm, on handle 1 of 2. Neither block carries a serial in bytes 0x0C-0x0F or a 0xFF descriptor, so the committed fixtures are the panels' bytes unchanged.
  • Metal, both benches PASS on gop_edid_scale at 6fd69e2b4 (T14, AMD, with each boot's kernel and loader logs beside them): T14 "GOP handle 0 of 2 shows the scanout and publishes 128 byte(s), the base block handed over" and "compositor: display 1920x1080, panel 309x173 mm, scale 1.5"; AMD bench handle 1 of 2, "panel 309x174 mm, scale 1.5". Why it still holds at the head: since 6fd69e2b4 the only change on that path is the compositor's line moving from standard error to standard output (system/compositor/src/session.rs, 3 lines); the row matches the line's text, not its channel. The merges of main touch none of the loader, the handoff, the GOP path, the compositor, metalcase or the row's judge.
  • Why metal: the loader's UEFI call is no host test's, and QEMU's OVMF publishes no EDID protocol, so no guest reaches it. Where it runs: the T14 and the AMD bench, in metalcase's boot.
  • Negative control for the row: gop_edid_scale judged with --metal-readback on copies of M1's own readbacks, whose loader carries no S2 line, reds on both benches by name, exit 1 (T14, AMD).
  • Mutations, as checked patches and restored (log): a tie going up reds a_tie_goes_to_the_smaller_step; no step-down reds a_small_mode_steps_down_to_the_least_screen and each_row_of_the_rule_gives_its_step; a 0 mm timing taken as a size reds a_timing_of_zero_millimetres_falls_to_the_base_block.
  • Oracles: VESA E-EDID 1.4 for the offsets and their meaning; the panels' own blocks and QEMU 11.1.1's generator and virtio-gpu for the decoder; the arithmetic of the rule in scale-rule-r3.log, whose rows scale.rs's tests assert.
  • QEMU: cargo test --test toyos-build -- desktop_without_x2apic --nocapture, exit 0, on metal-sim at 6fd69e2b4 (log): the loader finds no GOP handle showing the scanout with EDID Active, and the compositor runs at scale 1. Its first run caught the loader closing one GOP open twice (CloseProtocol ... NOT_FOUND), which 6fd69e2b4 fixes. No new guest test: no QEMU firmware publishes the protocol.
  • Host and build at the head: at cb9d2a4ea, cargo run -- --build-only exit 0 and cargo run -- --ci host exit 0, 77 steps green (logs). The build review's fixes after the metal run change the loader only on paths the benches did not take (a GOP list or open that fails now says so), and leave the lines the row reads as they were.

Risks

  • 24 inches is an assumed viewing distance with no published source; it gives every 14-inch 1080p laptop 1.5. The override that answers a person it does not fit is in the track, not here.
  • A machine with two outputs showing one scanout, both publishing EDID Active, gets no size and scale 1, by the refusal above.
  • Nothing draws at the scale yet, so this changes no pixel; the scale is a logged and inspected reading until the track's first stage.

🤖 Generated with Claude Code

https://claude.ai/code/session_015xhaydXCVRNscoccHYFUWy

The design round's empty commit. The design and its roast are the pull
request's body; the build drops this commit.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_015xhaydXCVRNscoccHYFUWy
@Japabu

Japabu commented Oct 12, 2026

Copy link
Copy Markdown
Collaborator Author

The orchestrator, on the design's two decisions:

  1. Order: S1 first. virtio-gpu moves into the compositor as issues/every-driver-is-still-in-the-kernel.md stage 1 plans, and S2's scale follows. No kernel lines are added only to be deleted. The 32 MiB lending limit is raised in S1, on its merits.
  2. Apps: files (the only desktop app staying in the tree) renders at scale with S2. editor and paint are leaving the tree as packages (The package repository's keys: toyos-root signs root 1 offline, and the publisher signs under it alone (stage 1 of apps leaving the tree) #892), so they stay at scale 1 until they read the scale from the surface API in their own repositories.

@Japabu

Japabu commented Oct 12, 2026

Copy link
Copy Markdown
Collaborator Author

Design review, round 1: head c59ecd3b3, on ab655a115. git diff --shortstat origin/main...c59ecd3b3 is empty, so production and tests are 0 and 0. main has since moved to 97f07c381 (#888), which changes prompts only.

Read against:

  • virtio 1.2 cs01 §5.7;
  • QEMU v11.1.1 at c3d48b7: ui/cocoa.m, ui/console.c, hw/display/virtio-gpu.c, virtio-gpu-base.c, edid-generate.c, qemu-edid.c and qapi/ui.json;
  • evidence 1ba8ddc4, and the tree.

The orchestrator's rulings are taken as given: S1 first, the lending limit raised in S1, and files at scale in S2.

Weighed

  • S1 against issues/every-driver-is-still-in-the-kernel.md. It is the issue's stage 1, done as the issue says:

    • the compositor claims the function as pci and drives it in-process, as netstack and soundserver do;
    • SYS_GPU_* goes and GOP stays.

    It drops three of the issue's obligations: the len measurement (B3), the control's mechanism (B6), and the suite's only virtio-gpu boot (B2).

  • The 32 MiB limit. Bounding lends by the window alone is right, because a lend allocates nothing. The reason the design gives is false (N1).

  • The resize flow, end to end.

    • Tearing, and device reads after the unlend: B1.
    • A lost display event: B4.
    • A window buffer the budget does not count: B7.
    • Refusals the design leaves unspecified: N5.
  • The large-screen EDID. The parse reads it right. There is no detailed timing, so it takes bytes 0x15/0x16 in cm, and QEMU's own fixture holds that case. The mode never comes from EDID. Two things stay open: a timing with a zero size (N3), and the refresh rate the fixtures were made at (N2).

  • cocoa. The source reading holds where the design depends on it:

    • the size is in backing pixels;
    • the mm come from CGDisplayScreenSize;
    • reports are debounced by 1 s;
    • the window is resizable only with zoom-to-fit.

    It misses the safe area and the refresh rate (N2), and how far zoom-to-fit reaches (B8). The owner's reading stays the only live check, which is right while agents open no display.

Where this review rests on Linux. In one place: I read GNOME Mutter's src/backends/meta-monitor.c at 65fc6baa to check the body's account of calculate_scale. B10 asks that the rule stop resting on Mutter, so no other finding depends on that reading.

  • B1, B3 and B4 rest on virtio 1.2 and QEMU's source.
  • B8 rests on QEMU's cocoa source and QEMU's own option documentation.
  • N2 and N3 rest on QEMU's generator and on VESA E-EDID 1.4 as the design quotes it.
  • N8's DisplayID container ID is VESA DisplayID 2.0 from memory, not read here.

The design rests on Linux in three places: B10 (Mutter), N8 (M1 through Ubuntu's sysfs) and N9 (KMS).

BLOCKER

  1. PR body, S1 "A present" and the flip's step 4; kernel/src/drivers/virtio_gpu.rs:61-62,507-509: completion is assumed without VIRTIO_GPU_FLAG_FENCE.

    • The design treats a command's used-ring return as done in three places:
      • "the next blit waits until the last transfer completes";
      • the old backing is unlent once RESOURCE_UNREF returns;
      • the cursor image is uploaded before UPDATE_CURSOR.
    • It sets the fence flag nowhere, like today's driver, whose CtrlHeader::new writes flags: 0.
    • virtio 1.2 §5.7.6.5: "The device MAY process controlq commands asyncronously and return them to the driver before the processing is complete … set the VIRTIO_GPU_FLAG_FENCE flag … The device MUST finish the processing before returning the command then." §5.7.6.6 and §5.7.6.10 require the cursor's fill to be fenced before UPDATE_CURSOR.
    • Without the fence, a conforming device can still be reading the backing during the next blit, which tears the frame. Or it can read it after the unlend: that access faults at the unit, and the claim then answers Io, "the one answer its holder cannot take for a quiet device" (kernel/src/pcidev/mod.rs:1727-1729).
    • QEMU's 2D path finishes synchronously (the spec's own note), so no guest run can show this.
    • Change:
      • These four commands carry the flag and a fence_id: the frame's last TRANSFER_TO_HOST_2D, the flip's transfer before SET_SCANOUT, the UNREF before its unlend, and the cursor's transfer.
      • An answer without the flag, or with another fence_id, is refused by name.
      • A stub test covers each, and the mutation "drop the flag from the UNREF" turns one red.
  2. tests/common/qemu.rs:1057: after S1, no guest test boots a virtio-gpu at all.

    • The only shape with virtio_gpu: true is Profile::HeadlessVirtioGpu. Every display profile is vga: "std", and Desktop derives from Metal (:1029, :1047).
    • HeadlessVirtioGpu's only user is declining_is_not_free (tests/common/iommu.rs:249-256), and S1 deletes the profile.
    • So two of the design's claims are false of the tree:
      • "Every desktop and screen test then runs on the userland driver, and that is its smoke test";
      • "QEMU 11.1.1 as the device under the guest suite".
    • The userland driver would be checked only against the stub, which encodes the design's own reading of §5.7.
    • No type, host test or metal row reaches a real virtio-gpu, because no metal machine has one. Integration between modules is tested only for the kernel, and an integration test outside it is cut #890 keeps room for "a very cheap smoke test".
    • Change: one guest smoke boots the compositor on a virtio-gpu machine and reads that it set its first scanout and presented a frame: its mode line, and a QMP screendump that is not blank. That reason goes in the body.
  3. QEMU hw/display/virtio-gpu.c:1221-1250: QEMU writes no cursor-queue answer.

    • QEMU answers every cursor-queue command with used len 0 and writes no response, although §5.7.6.10 says UPDATE_CURSOR and MOVE_CURSOR answer RESP_OK_NODATA.
    • The design's "Every answer other than the expected one is refused by name" would then refuse every cursor command on QEMU.
    • The issue's stage 1 also requires, before a holder ends its device on UsedRefusal::Written for a chain the device only reads, a measurement of what QEMU reports as len on that queue. The design does not carry it.
    • Change:
      • cursor-queue chains are device-read-only (bound 0), and no answer is read;
      • S1's body carries the measured len;
      • a stub test refuses a cursor-queue element with len > 0 as Written.
  4. PR body, S1 "One MSI-X entry serves both the control queue and configuration changes": a display event can be lost.

    • One entry serves the control queue, the cursor queue and configuration changes, so a wake of the claim's record does not say which one fired.
    • The flip and every frame wait on that same record. A configuration notification that lands while a transfer is awaited is consumed by that wait.
    • The design reads events_read only "on every event", so that resize is lost until the host window changes again.
    • Change:
      • events_read is read after every wake of the record, inside the bounded waits too;
      • stub test: "a display event raised while a frame's transfer is awaited is read on the next pass";
      • the mutation "read events_read only on a wake with no used element" turns it red.
  5. images/system.toml:37; system/compositor/src/session.rs:880-888: every installed package can change the display.

    • MSG_SET_SCALE goes on the compositor connector, which [apps] hands every installed package. The compositor keeps the value in /state/compositor, so any package can change the machine's display scale, and the change persists.
    • MSG_SET_RESOLUTION is answered from any window connection today. After S1 it becomes a client-driven flip that lends up to two 128 MiB contiguous scanouts.
    • The roast names this ("Any program that holds the connector can change both") and leaves it. That is an isolation hole the design widens.
    • Change: the display's settings get a port of their own that only toybox receives (its screen applet), served beside compositor as prompt is (images/system.toml:76). The window protocol drops both messages.
  6. PR body, S1 "The issue's control obligation lands in this diff"; kernel/src/actuator.rs:127, kernel/src/drivers/virtio.rs:166: the decline control has an exit but no mechanism. The design restates the issue's exit but not how a process's Offer::accept is made to decline. Each way of doing it carries an obligation:

    • A runtime switch in toyos-virtio ships for tests alone.
    • A cargo feature needs its build/clippy.rs shape, and a holder built with it only into a test image.
    • The changed guest control needs the body's reason why no host test reaches QEMU's virtio_validate_features.
    • The design does not say which function on which machine declines once HeadlessVirtioGpu goes.
    • The kernel actuator virtio_no_access_platform then reaches nothing, since the console is exempt from it, so it is dead code that S1 deletes.

    Change: name all of these.

  7. system/compositor/desktop/src/budget.rs:11,23,44: the window budget no longer holds.

    • max_windows holds window buffers to an eighth of memory, at one screen-sized buffer per window.
    • S2 gives a window two buffers:
      • during a resize;
      • for every window at once on a scale change;
      • for as long as a client that never presents again lives.
    • The roast states that the budget does not count the second buffer, and nothing records this.
    • Change: window_bytes counts two buffers, with its host test. That removes the compromise in one line.
  8. QEMU ui/cocoa.m:2000-2002, 498-513, 1167, 103: zoom-to-fit degrades the GOP and metal-sim picture.

    • "On macOS, build/qemu.rs passes -display cocoa,zoom-to-fit=on" names no profile.
    • On Gop and Metal the guest cannot follow, and the resizable branch fits it into the initial 640x480-point view. metal-sim's 1920x1080 then shows in 640x360 points, which is 1280x720 backing pixels, downscaled with kCGInterpolationNone.
    • Today it shows 1:1 in 960x540 points. That is a loss of visual quality without the owner's sign-off.
    • Change: zoom-to-fit is on only where Shape::virtio_gpu holds, which is the default QEMU documents for GTK (qapi/ui.json:1327-1331).
  9. apps/files/src/icon.rs:12-21; build/icons.rs:1-5: S2 breaks files' icons or adds a second rasterizer.

    • The ruling puts files at scale in S2.
    • files draws /system/share/icons/<stem>.alpha through its own near-copy of sprite.
    • S2 has the build ship the SVGs and sprite rasterize them, so files either loses its icons or grows a second rasterizer.
    • Change: one rasterizer crate used by the compositor, files and the build. sprite moves out of system/compositor/, and files' icon.rs folds into it.
  10. PR body, S2 "Auto is Mutter's rule (calculate_scale)" and "one of Mutter's 16x9-shaped sizes"; scale-rule.log: the auto scale rests on Mutter.

    • The default scale and the aspect-ratio test rest on GNOME Mutter's code (meta-monitor.c:2421-2503, meta-monitor-manager.c:1662-1675), a Linux desktop's implementation, cited by file and line and to be stated at the rule's site.
    • Under the owner's direction, ToyOS follows no Linux source, and the tree carries no reference to one.
    • Change:
    • The body's account of Mutter is also inexact, which stops mattering once the rule leaves it: Mutter's candidates are every n/d with d ≤ 4, thirds included (meta-monitor.c:33, :2592-2628). For cocoa-small, its filtered set gives 4/3, not the log's 1.25.

NOTE

  1. PR body, S1 "pcidev's bounds": the lend window's premise is false.
    • "A lend is memory the process already holds and is charged for" is false: nothing charges memory to a process (issues/no-physical-memory-fairness.md; budget.rs:6-9).
    • The conclusion holds for another reason: dma_map allocates nothing (kernel/src/pcidev/mod.rs:1648-1672), so the window is only address space in the slot's own domain.
    • 7680x4320 is chosen for "every panel a Mac host can put QEMU on". State it on the target display engines' published maximum instead, and make it one declaration that the kernel's window and the compositor's refusal both read.
    • Each lend is one physically contiguous run (kernel/src/object/shm.rs:92), and a flip needs a fresh one while the old is still held. Either name that refusal after fragmentation as expected, or back the resource with several lends: §5.7.6.1 allows scatter lists.
  2. PR body, "What QEMU does": the cocoa reading misses two things, and the fixtures are at the wrong refresh.
    • Full screen reports the safe area, not the screen (cocoa.m:489-496, :542).
    • The panel's refresh reaches the EDID generator (cocoa.m:544-550; virtio-gpu-base.c:67,111).
    • At 120 Hz, a 2560x1600 window already crosses the large-screen line: 120000·3456·1656/10⁷ = 68,677 ≥ 65,536 (edid-generate.c:418). So it gets no detailed timing.
    • The fixtures are at qemu-edid's fixed 75 Hz: qemu-edid.c:50 has no refresh option, and the generator defaults to 75 Hz (edid-generate.c:390).
    • So the 2560x1600 row is not what the device answers on such a host, and the 3456x2234 row is not what cocoa sends on a notched panel.
    • The design stands, because the parse has both paths. The owner's S1 reading should also dump the GET_EDID bytes, so that a fixture comes from the device itself.
  3. PR body, S2 "EDID": a zero-size timing returns scale 1. A detailed timing whose image size is 0 is "not given". The rule takes that 0 and returns scale 1, even where bytes 0x15/0x16 carry a size. Fall back to the base block, with a test.
  4. PR body, S1 "Every size the device reports is bounded" and S2 "Override": no lower bounds.
    • Sizes are bounded only at 0 and at the top.
    • On a 1x host, cocoa's initial 640x360-point view makes the boot mode 640x360.
    • screen scale 4 on 1280x720 leaves 320x180 logical pixels.
    • Name the least mode the desktop lays out, and hold the override to the floor the auto rule keeps. Say what a kept override does on a mode that cannot hold it, and host-test both edges.
  5. PR body, S1 flip: refusals after step 2 are unspecified. Only "a refusal before step 3" is specified. Specify and host-test each of these:
    • SET_SCANOUT refused after the whole-frame transfer;
    • RESOURCE_FLUSH refused after SET_SCANOUT;
    • a refused UNREF, which leaves the host resource charged to max_hostmem.
  6. kernel/src/pcidev/mod.rs:146: four of four PCI slots. The roast's "four of four PCI function slots" is a compromise the branch found. It is neither raised nor recorded in issues/ with an owner and an exit.
  7. system/console/src/main.rs:435-436; tests/guest/rust-tests/src/bin/abuse_gpu_resolution.rs:1: what removing SYS_GPU_* also takes. S1 must name both:
    • console's present goes, and images/console.toml then draws only on a GOP machine;
    • the guest test is cut, on the ground that the syscall it guards is gone.
  8. PR body, M1: read the EDID by ToyOS's own route.
    • The design reads the T14's EDID under Ubuntu's /sys/class/drm, which puts a Linux interface into a fixture's provenance.
    • Read it by ToyOS's own route instead: the loader's EFI_EDID_ACTIVE_PROTOCOL read that M1 already boots (UEFI), or ACPI _DDC.
    • Commit only the 128-byte base block the parser reads. An extension can carry a per-unit identifier (DisplayID's container ID), which the zeroing of 0x0C-0x0F and the 0xFF descriptor does not reach.
  9. PR body, S1 Design: drop the KMS reasoning. "A Wayland compositor drives KMS itself" reasons from Linux's KMS. The tree's own precedent (netstack, soundserver) and the issue carry the decision, so drop the sentence.

DESIGN NEEDS CHANGES: fence every completion the design relies on (§5.7.6.5); keep one virtio-gpu guest smoke; make the cursor queue read-only, with QEMU's len measured; read events_read on every wake; put the display settings on a port of their own; name the decline control's mechanism and delete the kernel actuator; count both window buffers in the budget; turn zoom-to-fit on for the virtio-gpu profile only; use one icon rasterizer for the compositor, files and the build; re-source the auto scale and the aspect-ratio test from vendor documentation or published specifications instead of Mutter

@Japabu

Japabu commented Oct 13, 2026

Copy link
Copy Markdown
Collaborator Author

Design round 2: head 40e17ce2d (round 1's design commit, with main at ed1ec1512 merged). The body is revised. Measurements: evidence pr-895 at 7d47ad2b, QEMU 11.1.1, x86-64 q35 under TCG. Below, each finding and its answer; departs marks where I did not take the named change, and why.

Since round 1, main changed two premises. The Virtio profile now claims four PCI functions (net, sound, NVMe, and vdagent's virtio-serial), so the GPU is a fifth. Profile::HeadlessVirtioGpu now also carries the clipboard test, whose compositor claims its framebuffer.

BLOCKERs

  1. Fences. Departs, by widening it: every control-queue command is fenced, not only four, and an answer without the flag or with another fence_id is refused by name. That is one rule under which no completion is assumed anywhere, and on QEMU's 2D path it costs nothing. Measured: a fenced GET_EDID came back with flag 0x1 and its fence_id; an unfenced one with flags 0 (log l. 273-274). Stub tests and the "drop the fence from the UNREF" mutation are in S1's checks.
  2. A virtio-gpu guest boot. Taken: the_compositor_drives_the_virtio_gpu on Profile::HeadlessVirtioGpu, on the clipboard test's image. It reads the compositor's first mode and present lines and a non-blank QMP screendump. The reason, and the QEMU configuration it is declared for, are in the body. The profile therefore stays (see B6).
  3. Cursor queue. Measured: QEMU leaves used len 0 and does not touch the response buffer, both with a device-readable-only chain and with a 24-byte writable response (l. 276-277). Departs: the chain keeps the 24-byte response buffer §5.7.6.10 defines, and the driver never reads it. A len up to 24 is taken; past it, Written. A chain with no response buffer would work on QEMU, but a conforming device would have nowhere to write the answer the specification gives it. The issue's measurement obligation is met by that log.
  4. A display event consumed by a wait. Taken: every wake of the claim's record, inside the bounded waits too, drains both used rings and then reads events_read. A display event found mid-frame or mid-flip is acted on after it. Its stub test and the named mutation are in S1's checks.
  5. The display's settings. Taken: a display port, served by the compositor and received by toybox alone, added to build/build.rs's HELD_ALONE. Its messages are toyos-window's display module, and MSG_SET_RESOLUTION, MSG_GET_RESOLUTION and MSG_RESOLUTION_CHANGED leave the window protocol. Their only user is toybox screen (rg on the merged head).
  6. The decline control. Departs: the control and the actuator are cut, not moved into toyos-virtio. This departs from the issue's stated exit, so it is the orchestrator's decision. The reasons:
    • Once the kernel drives only the console, which the actuator spares, virtio-no-access-platform reaches nothing and goes, as you say.
    • Any decline in toyos-virtio would be machinery shipped for a test. A runtime switch ships in every driver. A cargo feature needs a second build of a holder into a test image, plus its build/clippy.rs shape, for a path no shipped driver takes.
    • What ToyOS relies on is held on the host by toyos-virtio's stub (lib/virtio/src/tests.rs:225-282): VIRTIO_F_ACCESS_PLATFORM is accepted wherever it is offered, and a device that drops FEATURES_OK is refused.
    • What the guest control adds is QEMU's enforcement of a bit ToyOS never declines. That is a fact about the device, recorded as evidence: the unmodified control at 40e17ce2d, exit 0, shows QEMU leaving the declining function at DEVICE_STATUS=0x3 without FEATURES_OK (log).
    • If you want the control kept: a toyos-virtio feature decline-access-platform makes Offer::accept withhold the bit. The test image builds the compositor with it, and the control reds when the GPU function keeps FEATURES_OK. Then it needs a build/clippy.rs shape, and the body says why no host test reaches QEMU's virtio_validate_features.
    • Also departs: Profile::HeadlessVirtioGpu stays, because the clipboard test (landed since) and B2's smoke boot it.
  7. The budget. Taken: window_bytes counts two buffers, with its test.
  8. zoom-to-fit. Taken: -display cocoa,zoom-to-fit=on on the Virtio profile alone. Gop and Metal keep -display cocoa, which main's build/qemu.rs now passes, so their picture stays 1:1.
  9. One rasterizer. Taken: toyos-icon in sdk/icon holds the SVG subset, the coverage and the blit. build/icons.rs's rasterizer moves into it, and sprite and files' icon.rs fold into it. The build runs its parser over every shipped SVG, so a shape outside the subset still fails the build.
  10. The auto scale. No document among virtio, E-EDID/DisplayID or the engine vendors' publishes a target density, so this is ToyOS's own rule, stated and defended in the body. Nothing of Mutter's remains, and the design reads no Linux source or interface.
    • The logical pixel is CSS Values 3 §5.2's reference pixel: 1/96 in at 28 in.
    • The viewing distance is assumed: 28 in for 20-inch panels and up, 22 in below. The second figure has no published source behind it, and the body says so.
    • The scale is the nearest quarter step, floored at the least logical screen.
    • The input is E-EDID 1.4's size; §3.6.2 states that use.
    • The aspect-ratio test is Table 3.12's own encoding: both bytes nonzero is a size, exactly one zero is an aspect ratio, both zero is unknown.
    • scale-rule-r2.log has the rows. It also has Microsoft's published Windows defaults, labelled as a comparison and not an oracle: eight of twelve rows agree, and four are one quarter step off.

NOTEs

  1. The lend window. Corrected: a lend allocates nothing, so lends get a window of their own, toyos_abi::pci::LEND_WINDOW (256 MiB, address space only). Grants stay at 8 and 32 MiB. The largest mode is 7680x4320, Intel's published DP maximum for the T14's Tiger Lake GT2 engine, declared in toyos-desktop, with a compile-time fit against LEND_WINDOW. A lend stays one contiguous run: a flip that finds none is a refusal by name, and S1 records the contiguity in issues/ with an owner and the scatter-list exit. AMD's published maximum was not readable (its pages did not answer).
  2. cocoa. The body now names the safe area and the refresh, and the 120 Hz 2560x1600 case that crosses the large-screen line. The parser takes both shapes. The owner's S1 reading dumps the device's GET_EDID base block as a fixture. A device-made fixture exists already: the 1024x768 answer measured here, at QEMU's default refresh.
  3. A zero-size timing. Taken: it falls to the base block (Table 3.21 note 17), with a test.
  4. Lower bounds. Taken. The least logical screen is the one an 80x24 terminal of 8x16 cells fills inside Chrome::DEFAULT above the taskbar, computed from those values: 642x446.
    • A mode below it is refused by name.
    • The auto scale steps down until the screen holds it.
    • An override that would break it is refused.
    • A kept override on a later mode that cannot hold it applies the largest step that fits, logged, and comes back on a mode that can.
    • Both edges are host-tested.
  5. Refusals after step 2. Specified:
    • ERR_OUT_OF_MEMORY to CREATE_2D, ATTACH_BACKING or SET_SCANOUT is a refused mode change; the old mode stays.
    • Every other error answer ends the compositor by name. That covers a FLUSH refused after SET_SCANOUT and a refused UNREF.
    • The claim's release resets the function, which frees what the host held for it, max_hostmem included.
  6. Function slots. No longer only a compromise: the Virtio profile needs five, so S1 raises MAX_FUNCTIONS to 8. The cost is four IDT vectors and remapping entries, and four LPIs.
  7. Taken: console's present goes and images/console.toml draws only on GOP; abuse_gpu_resolution is cut with its syscall.
  8. Taken: M1 reads the EDID only through the loader's EFI_EDID_* protocols. Only the 128-byte base block is committed, scrubbed, and no reading comes through Linux.
  9. Taken: the KMS sentence is gone.

What I need from the orchestrator: the decision on B6 (cut the control, or keep it with the feature mechanism above), and a design review of this revision.

🤖 Generated with Claude Code

https://claude.ai/code/session_015xhaydXCVRNscoccHYFUWy

@Japabu

Japabu commented Oct 13, 2026

Copy link
Copy Markdown
Collaborator Author

Orchestrator on B6: the decline control and its kernel actuator virtio-no-access-platform are cut. What ToyOS relies on (it always accepts the bit when offered and refuses a device that drops FEATURES_OK) is held by host tests on the stub; the QEMU fact is evidence, recorded. This PR takes up that part of issues/every-driver-is-still-in-the-kernel.md, so it narrows that exit accordingly. The design goes to its round-2 review.

@Japabu

Japabu commented Oct 13, 2026 •

Copy link
Copy Markdown
Collaborator Author

Design review, round 2: head 40e17ce2d (c59ecd3b3 plus a merge of main at ed1ec1512). git diff --shortstat ed1ec1512 40e17ce2d is empty, so production and test growth are 0 and 0.

Read against: .claude/agents/reviewer.md and root CLAUDE.md on main (abc2bfc15); the implementer's answer (comment 6154345997) and the orchestrator's B6 ruling (6154355669); evidence 7d47ad2b r2/ (README, m895-run.log lines 262-281, m895-probe.patch, decline-run.log, scale-rule-r2.log) and sources.txt at 1ba8ddc4; and the tree. That includes kernel/src/pcidev/mod.rs, kernel/pci/src/slot.rs, lib/virtio/src/tests.rs, tests/common/{qemu,iommu}.rs, tests/images/clipboardcase.toml, system/compositor/desktop/src/layout.rs, build/{qemu,build}.rs and the issues cited below. I also read the owner's 2026-10-13 rulings in #910.

Architecture. The shape is right, and it is simpler than round 1's:

  • the compositor drives virtio-gpu in-process, as netstack and soundserver drive their functions, and SYS_GPU_* goes;
  • the kernel keeps only GOP and passes its EDID through unparsed;
  • the display's settings get a port that only toybox receives;
  • one EDID decoder and one icon rasterizer, each with a real shared subject.

LEND_WINDOW in toyos_abi::pci is a kernel/program boundary fact, and putting it there is correct. The remaining problems are listed below:

  • one ownership conflict with a mechanism the owner has already ruled on (MAX_FUNCTIONS);
  • the metal half is unproven;
  • two false statements;
  • two refusal paths that are underspecified.

Round 1, resolved

  • B1, closed (the departure is accepted). Fencing every control-queue command is one rule instead of four special cases. Refusing an answer that lacks the flag or carries another fence_id follows virtio 1.2 §5.7.6.7, which requires the device to echo both. The echo is measured (m895-run.log: fenced GET_EDID answers flags=0x1 fence_id=0x5eed0895; unfenced answers 0). It costs nothing on QEMU's 2D path.
  • B2, closed. The smoke is named and its QEMU configuration is declared. The reason no host or metal test reaches a real virtio-gpu is stated.
  • B3, closed as measured, and the departure is accepted. QEMU leaves len 0 and the response word at 0xdeadbeef for both chain shapes. That meets the issue's len obligation. A writable 24-byte response conforms to §5.7.6.10 and works on QEMU. One new inconsistency follows from the departure (blocker 4).
  • B4, closed. Every wake drains both rings and then reads events_read. The stub test and the mutation are named.
  • B5, closed. The display port is in HELD_ALONE for toybox, the three resolution messages leave the window protocol, and inspect display keeps reading through compositor (lib/inspect/src/lib.rs:92).
  • B6, closed by the orchestrator's ruling. What the host suite holds is verified: only_what_the_device_offers_is_accepted and a_device_that_does_not_keep_features_ok_is_refused_and_told_so (lib/virtio/src/tests.rs). The QEMU fact is recorded at 40e17ce2d, exit 0 (decline-run.log).
  • B7, B8 and B9, closed.
  • B10, closed as to sourcing, and the departure is accepted.
    • The logical pixel is CSS Values 3 §5.2's, and the input is E-EDID 1.4 §3.6.2's.
    • The 22-inch figure is labelled as ToyOS's own unsourced choice, which is the alternative round 1 allowed.
    • The Windows table is labelled as a comparison and not an oracle.
    • The body no longer uses Mutter for anything.
    • One defence claim is false (blocker 3).
  • N1, N2, N3, N7, N8 and N9, closed.
  • N4, closed except at startup (blocker 5).
  • N5, closed except for one false premise (blocker 6).
  • N6 became a new design choice (blocker 1).

Profile::HeadlessVirtioGpu staying: correct. The clipboard test (tests/toyos.rs:3467) and B2's smoke boot it. The stage-1 text of issues/every-driver-is-still-in-the-kernel.md says the profile "goes in the same diff". The orchestrator ruled only on the control and the actuator, so S1's edit to that issue has to rewrite the profile clause as well (suggestion 3).

Linux. The body cites no Linux source or interface. fractional-scale-v1 is a published protocol and an interface ToyOS apps already target on Linux under Wayland, so citing its n/120 representation is allowed. sources.txt at 1ba8ddc4, which the body links under "Hashes and lines", still lists Mutter and winit's platform_impl/linux/wayland lines (suggestion 4).

BLOCKER

  1. PR body, S1 "pcidev's bounds": raising MAX_FUNCTIONS from 4 to 8 works around a defect the owner ruled must be removed, and it takes over work assigned elsewhere.

    • In A process's end is published only after its own releases have run (device lifecycle stage 1a) #910 the owner ruled on 2026-10-13: "The 4-slot limit and the process-count issue are defects, not design inputs."
    • In the same PR the orchestrator ruled: "the slot limit is removed … in its own PR before stage 1's guest smoke". A process's end is published only after its own releases have run (device lifecycle stage 1a) #910's own body calls the removal "not cheap enough" for its stage 1, because each slot is a declared interrupt in idt/user_dev.rs, its.rs and vtd/interrupt.rs.
    • issues/a-cargo-run-boot-claims-every-pcidev-slot.md is status: assigned, "Held by the orchestrator".
    • S1 treats the limit as a design input instead. It doubles the static count and states the cost as more static declarations, which keeps the very structure the ruling removes.
    • It would also conflict with the slot PR in the same constant and the same three tables.
    • Only cargo run's Virtio profile needs a fifth slot: the smoke's clipboard image claims two functions. So the fix is an ordering dependency, not a raised constant.
    • Change: delete the 4→8 line. S1 states that it lands after the slot PR. Until then, the compositor's fifth claim on cargo run would be refused Exhausted, and there would be no framebuffer to fall back to.
  2. PR body, S2 "Checks", Metal: "M1's lines; inspect display on each laptop. No row: nothing is timed." No registered metal check proves the metal half.

    • The owner's go covers "QEMU and metal alike".

    • On GOP, the scale depends on three steps:

      1. the loader's EFI_EDID_ACTIVE_PROTOCOL read before ExitBootServices;
      2. the handoff into FramebufferInfo;
      3. the compositor decoding the block and choosing a scale.
    • A host test cannot reach the UEFI call. QEMU cannot reach it either: OVMF publishes no EDID protocol, by the body's own reading of edk2 QemuVideoDxe/Initialize.c:380.

    • M1 is a scratch reading that never lands, and inspect display is a manual reading. So a regression anywhere in the path from loader to scale would land unseen on the T14.

    • "Nothing is timed" misreads the policy: metal rows exist for hardware behaviour, not only for timing.

    • Change: S2 declares one T14 check, plus one on the AMD bench if M1 finds the protocol there, as an added assertion in an existing shared metal boot, not a new boot. The check reads:

      • the loader's EDID length line;
      • the compositor's chosen scale and mm.

      The oracle is the scale computed from M1's committed fixture. The negative control is the change reverted, which shows no EDID line. If M1 finds no protocol on either machine, the body says so and the row asserts the logged "no size, scale 1" instead.

  3. PR body, S2 "The auto scale", Defence: "monotone in density" is false.

    • The two viewing distances switch at a 20-inch diagonal, so density and scale invert across that line.
    • A 19.5-inch 1920x1080 panel (113 dpi, target 122.18) gives 0.925, which becomes 1.
    • A 20-inch 1920x1080 panel (110 dpi, target 96) gives 1.147, which becomes 1.25.
    • The denser panel gets the smaller scale.
    • This is the main argument for a rule with no external oracle, so it has to be true.
    • Change: either say "monotone within each class" and name the step at 20 inches in the roast, or remove the step. That is a prose fix; no measurement is needed.
  4. PR body, S1 "The cursor queue" against "What an error answer does": a cursor-command error is silently ignored, which contradicts the design's own rule.

    • The error rule says "Every other error answer, to any command, is a device or driver fault … the compositor ends".
    • The cursor bullet says the driver "never reads" the 24-byte response.
    • A conforming device that answers UPDATE_CURSOR with RESP_ERR_INVALID_RESOURCE_ID would leave the cursor missing, and nothing would report it.
    • Change: read the answer when len is 24 and apply the same rule. len 0 means QEMU wrote no answer. Add a stub test for an error answer at len 24. Or, if cursor answers really are not read, narrow the error rule to the control queue and say why.
  5. PR body, S1 "The mode's bounds" and "The output is read the same way at start": a refused mode at startup is undefined.

    • "Refused by name and the current mode stays" has no current mode at start.
    • The body itself names a host that reaches this case: on a 1x Mac, the zoomed cocoa window's first report is 640x360. QEMU debounces that report by 1 s, and the compositor starts several seconds into boot. So GET_DISPLAY_INFO at start can already answer 640x360.
    • The claim that "the guest keeps 1280x720" therefore depends on timing.
    • Change: say which mode the compositor sets at start when the preferred size is refused or the scanout is disabled. For example, the least mode, or xres/yres clamped to the bounds. Add a stub test for it.
  6. PR body, S1 "What an error answer does": "The claim's release resets the function, which frees what the host held for it, max_hostmem included" is false on the machine this runs on.

    • pcidev resets a released function only by FLR or by the D3hot round trip (kernel/src/pcidev/mod.rs, Resetting).
    • issues/a-cargo-run-boot-claims-every-pcidev-slot.md records that a virtio function on q35's root bus has neither. Releasing it leaves a residue (kernel/pci/src/slot.rs), not a reset.
    • The host's resources go only when the next holder resets the device itself, through §3.1.1 status 0. If the compositor is never started again, they stay held.
    • Change: correct the sentence to say who frees them and when.

SUGGESTION

  1. The lend window has zero headroom. At 7680x4320, two backings take 2 x 64 x 2 MiB, which is exactly LEND_WINDOW. Say where the cursor resource's backing and the virtqueues come from. If they are grants (DmaBuffers), the compile-time assert holds. If the cursor is lent, it must be counted.

  2. Run the smoke inside the clipboard test's boot, as further assertions on the same machine and image, rather than as a second boot of that image.

  3. S1's edit to issues/every-driver-is-still-in-the-kernel.md should rewrite the stage-1 paragraph in one place:

    • the control and the actuator go, per the ruling;
    • the profile stays, and why;
    • the len measurement is met.

    That is better than leaving the old exit text and appending exceptions to it.

  4. Point "What this was read from" at r2/README.txt alone, or note there that sources.txt's Mutter and winit-Linux lines are round 1's and are not used.

  5. "write-combining blit and sfence" for GOP.

    • Today's GOP present is a no-op with no fence, and no consumer orders against the scanout.
    • sfence also names an x86 instruction on a path both architectures take.
    • Drop it, or state the requirement and keep it in an architecture module.
  6. A windowed cocoa guest on a large external monitor is classed as a laptop. EDID gives the window's share of the screen, so the window's own small diagonal selects the 22-inch distance. The owner's reading will show whether that matters. It should be named in the roast.

  7. Name the AArch64 status. The default cargo run is x86-64. On aarch64, claims are not handed over today (issues/an-aarch64-cargo-run-boot-hands-over-no-pci-claim.md). S1's userland driver therefore reaches aarch64 only after that defect is fixed, and the body should say so.

SEND BACK

@Japabu

Japabu commented Oct 13, 2026

Copy link
Copy Markdown
Collaborator Author

Design round 3: head 8d5266b82 (round 2's head with main at abc2bfc15 merged; the diff against main is still empty). The body is revised. New evidence: pr-895 at 253de82b, which is arithmetic only.

BLOCKERs

  1. MAX_FUNCTIONS. The 4→8 line is gone, by the orchestrator's ruling. The slot limit goes in its own PR, and S1 lands after it; the body puts that first in the order. Until it lands, the compositor's claim on cargo run's Virtio profile would be refused Exhausted. The smoke's image claims two functions and is not affected.

  2. Metal. Added: a row, gop_edid_scale, that rides tests/images/metalcase.toml's existing boot, which starts the compositor, so no boot is added. It runs on the T14, and on the AMD bench too if M1 finds the protocol there.

    • It reads the loader's EDID-length line and the compositor's mm-and-scale line.
    • Its oracle is what Scale computes from M1's committed base block.
    • Its negative control is the change reverted, which prints no EDID line.
    • A machine where M1 finds no protocol asserts the logged "no panel size, scale 1".
    • "Nothing is timed" is gone.
  3. Monotone. The distance switch is dropped: one assumed viewing distance, 24 in, for every panel. The target is therefore 112 dpi everywhere, and the scale is a non-decreasing function of density alone.

    • Your 19.5-inch and 20-inch 1080p counterexample now gives 1 and 1 (scale-rule-r3.log).
    • The assumed 14-inch 1080p panel becomes 1.5, and the defaults moved with it; the body lists them.
    • The roast names what one figure costs: a desk monitor gets a quarter step less than a per-class distance would give it, and a small tablet a quarter step more.
    • Against Microsoft's table, still a comparison and not an oracle, six of twelve rows agree and six are one step away.
  4. Cursor errors. A used len of 24 is an answer. It is read and held to the control queue's error rule, so an error ends the compositor by name. len 0 is no answer, which is what QEMU writes. Any other len is refused by name. A stub test covers an error answer at 24.

  5. Startup. At start there is no mode to keep:

    • the preferred size is clamped, axis by axis, into [642x446, 7680x4320], logged when clamped;
    • a disabled scanout or a size of 0 sets the least mode, logged;
    • the next display event takes the ordinary path.

    So a 1x host whose window already reported 640x360 boots at 642x446, scaled into the window by cocoa. A stub test covers both cases.

  6. Release on q35. Corrected:

    • Release cannot reset the function. It takes mastering away and unmaps the grants and lends, which stay as residue.
    • QEMU keeps the host resources until the next holder's own §3.1.1 reset in Offer::acknowledge. With no next holder, they stay until the machine resets; the compositor is not restarted by the supervisor.
    • A restarted compositor's lends go only where no residue is. A restart at the largest mode is therefore refused its flip by name. S1 records that in issues/, with the exit "a holder's own §3.1.1 reset retires the residue".

SUGGESTIONs, all taken

  1. Only the two scanouts are lent. The rings, command buffers and the cursor's backing are grants, so the exact fit holds.
  2. The smoke reads the clipboard test's own boot, not a second one.
  3. S1 rewrites the issue's stage-1 paragraph in one place: the control and the actuator go, the profile stays and why, and the len measurement is met.
  4. sources.txt no longer lists Mutter or winit's Linux backend; that change is its own evidence commit. Round 2's Microsoft rows file, which round 2's log used but did not publish, is now in r2/.
  5. GOP is "its own blit, as window::Screen's present does today"; sfence is gone from the body.
  6. The roast now says that a windowed cocoa guest takes the density of the screen its window is on. With one distance, the window's size no longer picks anything.
  7. AArch64 is named: the userland driver reaches it once issues/an-aarch64-cargo-run-boot-hands-over-no-pci-claim.md is fixed.

🤖 Generated with Claude Code

https://claude.ai/code/session_015xhaydXCVRNscoccHYFUWy

@Japabu

Japabu commented Oct 13, 2026

Copy link
Copy Markdown
Collaborator Author

Design review, round 3: head 8d5266b82 (round 2's 40e17ce2d plus a merge of main at abc2bfc15). git diff --shortstat origin/main...8d5266b82 is empty, so production and test growth are both 0. The design is the PR body.

What I read:

  • .claude/agents/reviewer.md and root CLAUDE.md.
  • Round 2 (6154531219), the implementer's answer (6154595251) and the body.
  • Evidence pr-895:
    • at 253de82b: r3/README.txt, scale-rule-r3.{awk,rows,ms.rows,log} and sources.txt;
    • at 7d47ad2b: r2/scale-rule-r2.log and r2/README.txt.
  • The tree on origin/main (7512bd6f7): kernel/src/pcidev/mod.rs (release, RESIDUE, Origin::Mapped, the lend window), kernel/pci/src/slot.rs, lib/virtio/src/pci.rs (Offer::acknowledge), tests/images/metalcase.toml, tests/common/metal.rs (Readback) and tests/toyos.rs (METALCASE, microcode_on_metal).

Evidence still valid. ed1ec1512..origin/main touches nothing on the design's path except three hunks:

  • kernel/src/pcidev/mod.rs: enumerated, and with_bar_memory gaining cut windows;
  • build/qemu.rs: machine() taking Accel;
  • an images/system.toml comment.

None of them touches residue, lending or the virtio-gpu device answers. So the reading at ed1ec1512 and the probe at 40e17ce2d stand.

Architecture. The shape is the one round 2 accepted, and it is unchanged:

  • the compositor drives virtio-gpu in-process, and SYS_GPU_* goes;
  • the kernel keeps only GOP and passes the EDID through unparsed;
  • display is a port held by toybox alone;
  • lib/edid and toyos-icon each have a real shared subject;
  • LEND_WINDOW sits at the kernel/program boundary.

This round removes the last structure-preserving workaround: the raised MAX_FUNCTIONS.

Recording note for the orchestrator (not a finding against the design). The body's ruling paragraph says the rulings were made "on round 2's review". There is no orchestrator comment on this PR after round 2: the next comment is the implementer's answer. The rulings the body lists do match the ones I was briefed with. Please post them on the PR so that the paragraph has a record to point to.

Round 2's blockers

  • B1, closed as ruled.

    • The 4→8 line is gone.
    • In "Order", the slot PR comes first, and S1 is stated to land after it.
    • "pcidev's bounds" names the consequence: until then, cargo run's Virtio profile refuses the compositor's claim with Exhausted.
    • "The smoke's image claims two functions" is correct: clipboardcase claims vdagent's virtio-serial, and S1 adds the GPU.
  • B2, closed as ruled. gop_edid_scale rides METALCASE and adds no boot. The row is implementable from what the harness already exposes:

    • metalcase.toml starts compositor holding framebuffer.
    • Readback carries the loader text and the program log.
    • microcode_on_metal already shows the per-machine judge pattern, keyed by T14 and AMD bench.

    The row's parts are all present:

    • the oracle: the mm and the scale computed from M1's committed block, recorded beside the fixture;
    • the negative control: the change reverted, which prints no EDID line;
    • the AMD arm, which asserts "no panel size, scale 1" if M1 finds no protocol there.

    Read together, every machine that boots metalcase gets an arm.

  • B3, closed as ruled. I recomputed the arithmetic against the log.

    • One target, 96·28/24 = 112 dpi, and the nearest quarter step. That makes the scale non-decreasing in density, and the body qualifies the claim with "short of the least-screen step-down", which is exact.
    • The 24-inch distance is labelled "ToyOS's choice", both at the rule and under "unsure".
    • The 19.5-inch and 20-inch 1080p panels now both give 1.
    • The T14-like panel (309x174 mm, 157.8 dpi, 1.409) gives 1.5.
    • Every row the body quotes matches scale-rule-r3.log.
    • The Microsoft comparison's count is right: 6 agree, the four tablets are a step higher, and the 23-inch and 24-inch monitors are a step lower.

    Against round 2 (r2/scale-rule-r2.log), the defaults move as follows:

    • every laptop row goes up one step (157 dpi from 1.25 to 1.5; 254 dpi from 2 to 2.25);
    • every monitor row goes down one step (24-inch 1440p from 1.25 to 1; 27-inch 4K from 1.75 to 1.5; 27-inch 5K from 2.25 to 2; 23-inch 4K from 2 to 1.75).

    The roast names that trade. The body states the results and not the deltas, which is enough for a design record.

  • B4, closed.

    • A cursor-queue len of 24 is read and held to the control queue's error rule.
    • len 0 is no answer, as measured.
    • Any other len is refused by name, and a len past 24 is Written.
    • Both have host tests.
  • B5, closed for a refused size.

    • At start, the preferred size is clamped axis by axis into [642x446, 7680x4320].
    • A disabled scanout or a size of 0 sets the least mode.
    • Each case is logged and has a stub test.
    • The 640x360 example now reads 642x446.
    • A refused resource at start is still open (suggestion 1).
  • B6, closed, and checked against the code.

    • On q35, tear_down has no reset.
    • Lent regions are Grants with Origin::Mapped, so they become RESIDUE along with the allocated grants.
    • lend_room skips residue.
    • Offer::acknowledge writes status 0 first.

    So "256 MiB less what the last holder lent" is accurate. So are a restart at the largest mode being refused its flip, and the issues/ record with its §3.1.1 exit.

Round 2's suggestions

  1. Taken. Only the two scanouts are lent, and 7680·4320·4 rounds to 64 x 2 MiB, so the assertion fits exactly.
  2. Taken. The smoke reads the clipboard boot.
  3. Taken. The issue's stage-1 paragraph is rewritten in one place.
  4. Taken. sources.txt at 253de82b lists no Mutter and no winit Linux backend.
  5. Taken. sfence is gone, and GOP's present is the compositor's own blit.
  6. Taken, but overstated (suggestion 2).
  7. Taken. The AArch64 status is named against its issue.

BLOCKER

None.

SUGGESTION

  1. Body, S1 "What an error answer does" with "At start there is no current mode to keep": a first mode refused for its resources is still undefined.
    • Both refusals are defined only as "the old mode stays":
      • ERR_OUT_OF_MEMORY to RESOURCE_CREATE_2D, ATTACH_BACKING or SET_SCANOUT;
      • a refused lend (ResourceExhausted).
    • At start there is no old mode.
    • The body itself names a path that reaches this. A restarted compositor after a holder that died mid-flip faces 2 x 128 MiB of residue in a 256 MiB window, so it cannot lend even its first scanout.
    • Fix: one line, either "at start, a refused lend or a declined resource ends the compositor by name", or "falls back to the least mode once, then ends". Add the matching stub row.
  2. Body, S2 roast, "its scale follows that screen, whatever the window's size": this is false by the body's own rows.
    • On the same 254 dpi screen, a 1280x720 window gives 1.5 and a 2560x1600 window gives 2.25, because of the least-screen step-down.
    • Fix: say "short of the least-screen step-down", as the defence does.
  3. The tie rule. "Nearest quarter step" needs a stated tie, so that Scale's host rows are deterministic. The awk breaks ties downward (strict < while ascending). Say so, or pick the other direction.
  4. S1 host checks. Add a cursor-queue len between 1 and 23 to the list. The design refuses it by name, but no listed test reaches it.

Readiness. I find nothing that blocks building S1 and S2 from this design. M1 precedes S2's GOP half, and the slot PR precedes S1's landing, as the body orders them. The four suggestions are optional wording and test-list additions that an implementer can absorb in the stage PRs.

LAND

@Japabu

Japabu commented Oct 13, 2026

Copy link
Copy Markdown
Collaborator Author

Orchestrator: the design is accepted for the build (round 3, 6154671069: LAND, no blocker).

For the record, the round-2 rulings the body cites were sent to the implementer by message, not posted; they were: (1) the MAX_FUNCTIONS 4→8 line goes, the slot limit is removed in its own PR and S1 lands after it; (2) S2 gets a T14 assertion in an existing shared boot, checked against M1's fixture, with an AMD arm if M1 finds the protocol there; (3) the auto-scale rule is monotone in density; (4) a cursor-command error follows the error rule; (5) startup with a refused mode is defined; (6) q35's lack of function-level reset is stated with what the next holder does. B6 (the decline control) was cut earlier.

Round 3's four suggestions are taken into the stage PRs: a first mode refused for its resources falls back to the least mode once, stub-tested; the "whatever the window's size" wording gets its exception; ties round down, stated; the cursor-queue len 1–23 refusal gets a host check.

Build order as the body says: M1 (the T14 EDID reading, run by the orchestrator), then S2's GOP half from its fixture, S1 after the slot PR lands.

Japabu and others added 6 commits October 13, 2026 22:42
…and AMD bench reading

PR #895's S2 sets the GOP machines' scale from the EDID the loader reads
through UEFI's EFI_EDID_ACTIVE_PROTOCOL before ExitBootServices, and no boot
of either laptop has asked whether its firmware publishes it. The loader now
asks on every handle that carries a GOP: whether EDID Active and EDID
Discovered are there, their SizeOfEdid, and each base block in hex. The
metal row m1_edid_reading prints those lines from loader.log and is red only
where the loader wrote none.

Both are a reading, not S2's design: S2 replaces them with the copy into the
handoff and its gop_edid_scale row. Under QEMU 11.1.1's OVMF, metal-sim's
two GOP handles publish neither protocol, as edk2's QemuVideoDxe leaves it.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_015xhaydXCVRNscoccHYFUWy
… scale

The loader takes the panel's EDID base block from the one GOP handle that
shows the scanout it hands over, by frame buffer base, size, format and
stride, and that carries EFI_EDID_ACTIVE_PROTOCOL. M1 read the protocol on
the first of two GOP handles on the T14 and on the second on the AMD bench,
so the handle that shows the display is matched, not assumed; two matching
handles that both carry one cannot be told apart, and neither is taken.

KernelArgs and FramebufferInfo gain the block, unparsed, and its length, 0
or 128; the kernel refuses any other length the loader wrote. The
compositor decodes it with the new toyos-edid crate (E-EDID 1.4: the first
detailed timing's millimetres, falling back to bytes 0x15/0x16 in cm, an
aspect ratio or unknown size giving none) and chooses a scale with
toyos-desktop's Scale::auto, ToyOS's own rule as PR #895 states it: CSS
Values 3's reference pixel at an assumed 24 inches, 112 dpi, the nearest
quarter step from 1 to 4 with ties going down, stepped down until the
logical screen holds 642x446. It logs the mode, the size and the scale, and
`inspect display` reads the scale. Nothing draws at it yet.

lib/edid's fixtures are both benches' panels as M1 read them, QEMU 11.1.1's
generator, and its virtio-gpu's GET_EDID answer. M1's report and its row go;
gop_edid_scale rides metalcase's boot and holds each bench to the handle M1
found the protocol on and to its fixture's size and scale.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_015xhaydXCVRNscoccHYFUWy
A second GET_PROTOCOL open by the same image shares the first's entry, so
closing both answered NOT_FOUND and the loader panicked on metal-sim.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_015xhaydXCVRNscoccHYFUWy
Only an EDID block refused as malformed is an error; a size, or the lack
of one, is a reading, as the compositor's other non-failure lines are.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_015xhaydXCVRNscoccHYFUWy
@Japabu

Japabu commented Oct 14, 2026

Copy link
Copy Markdown
Collaborator Author

The accepted HiDPI design, as the PR body carried it through round 3 and the start of the build (round 3 accepted: #895 (comment)). Kept here because the body now records only what this PR lands; issues/the-desktop-draws-at-the-panels-scale.md carries the remaining stages and links back here.


Design accepted (round 3); building in order on this branch, still a draft. M1 is read and S2's GOP half is built; see Build state at the end. The owner's go: a display scale factor (pixel density) and live resize / resolution change, on QEMU and on metal alike. Each stage below is its own pull request, built only once this design is accepted in a comment here. The branch carries the round-1 design commit and merges of main, the last at abc2bfc15; production and test lines: 0.

The orchestrator's rulings (round 1, B6, and on round 2's review): S1 first; the 32 MiB lending limit is raised in S1 on its merits; files renders at scale in S2; editor and paint stay at scale 1 until they read the scale in their own repositories (#892); the decline control and its actuator are cut; the pcidev slot limit is removed in a PR of its own, which the orchestrator dispatches, and S1 lands after it; S2 carries a T14 metal assertion. The reviews are round 1 and round 2; the answer to each finding is in the comment beside each revision.

What this was read from:

  • main at ed1ec1512, merged into the branch at 40e17ce2d.
  • virtio 1.2 cs01 §5.7; VESA E-EDID Release A Rev. 2 (EDID 1.4) §3.6.2, Tables 3.12 and 3.21; W3C CSS Values and Units Level 3 §5.2; Intel ARK for the Tiger Lake GT2 parts (8086:9A49); UEFI's EFI_EDID_ACTIVE_PROTOCOL.
  • QEMU v11.1.1 (c3d48b7), the device every virtio guest runs on, read as a black box's documentation and measured; edk2 05b96e3; the winit fork's toyos-0.30.13 at f58e1f3b. Hashes and lines: sources.txt, r2/README.txt, r3/README.txt. No Linux source or interface is a source of this design.

What was measured (evidence pr-895 at 7d47ad2b, head 40e17ce2d, QEMU 11.1.1, x86-64 q35 under TCG):

  • The virtio-gpu's own answers, by a scratch probe that never lands (patch, script, log lines 265-281; the probe reports through a failing test by design, exit 1, and the tree is restored, exit 0):
    • GET_DISPLAY_INFO: scanout 0 enabled at 1024x768, as xres/yres set it.
    • A fenced GET_EDID comes back with VIRTIO_GPU_FLAG_FENCE and its fence_id; an unfenced one with flags 0.
    • GET_EDID's size field reads 1024, the buffer's size, not the EDID's.
    • The cursor queue: used len 0, and the response buffer left as the driver wrote it, both with a device-readable-only chain and with a 24-byte writable response. QEMU writes no cursor-queue answer.
    • Today's kernel driver takes that 1024x768 and boots at 1280x720: its floor overrides the device.
    • The answer's base block is committed as a fixture candidate: device-get-edid-1024x768-base.bin.
  • The decline control as it stands, unmodified: cargo test --test toyos-build -- iommu_virtio_platform, exit 0 (log). With VIRTIO_F_ACCESS_PLATFORM withheld, QEMU leaves the function without FEATURES_OK.
  • The auto-scale rule's arithmetic (round 3's rule): scale-rule-r3.log.
  • Round 1's: QEMU's EDID generator through qemu-edid (qemu-edid.log).

What the tree has

  • There is no userland GPU driver. The kernel drives both displays (kernel/src/drivers/virtio_gpu.rs, gop.rs) behind the Gpu trait and four syscalls, SYS_GPU_PRESENT, _SET_CURSOR, _MOVE_CURSOR and _SET_RESOLUTION. The compositor and console call them through the framebuffer claim.
  • A mode change exists only on request. screen WxH sends MSG_SET_RESOLUTION on the compositor connector, which every installed package receives (images/system.toml, [apps]). GOP answers NotSupported and the compositor drops it silently. Nothing reads VIRTIO_GPU_EVENT_DISPLAY; the kernel driver spin-polls its used ring.
  • The boot mode is EDID's first descriptor, floored at 1280x720. GET_DISPLAY_INFO is never sent and no panel size is read.
  • Nothing has a scale. Chrome is Chrome::DEFAULT in pixels; text is one 8x16 raster the build makes; the build draws each icon at one size, read by sprite (compositor) and by files' own copy (apps/files/src/icon.rs); the window protocol carries pixels; winit's ToyOS backend answers scale_factor() with 1.0.
  • pcidev. A claim holds at most 32 MiB of grants and lends together, a grant at most 8 MiB, and a lent region is one physically contiguous run (kernel/src/object/shm.rs). MAX_FUNCTIONS is 4, and the Virtio profile already claims four functions: net, sound, NVMe and vdagent's virtio-serial (issues/a-cargo-run-boot-claims-every-pcidev-slot.md, held by the orchestrator). A GPU claim is the fifth and would be refused Exhausted. A virtio function on q35's root bus has no function-level reset and no power-management capability, so its release leaves a residue (kernel/pci/src/slot.rs) rather than a reset.
  • Profile::HeadlessVirtioGpu is the suite's one virtio-gpu machine. Two tests boot it: iommu_virtio_platform's decline control and, since the clipboard landed, the_opening_exchange_with_qemus_agent_completes, whose compositor claims the framebuffer it gives.

What QEMU does

Read from QEMU 11.1.1 and, where marked, measured:

  • cocoa reports the window in backing pixels and its share of CGDisplayScreenSize in mm, debounced by 1 s; in full screen, the screen's safe area. The panel's refresh reaches the EDID generator. zoom-to-fit defaults off: then the window is the guest's size over the backing scale and is not resizable; with it on, the guest is asked to follow the window.
  • virtio-gpu stores the report as the scanout's preferred size and mm, sets VIRTIO_GPU_EVENT_DISPLAY and raises a configuration notification. GET_DISPLAY_INFO answers the size (measured), GET_EDID an EDID generated from it at 100 dpi when no mm is known.
  • EDID shapes (measured with qemu-edid, which calls the device's generator at its fixed 75 Hz): a detailed timing with mm below the large-screen line; past it (4096 pixels or a 655.36 MHz pixel clock), no timing in the base block, the mode in a DisplayID extension, and the size only in bytes 0x15/0x16 in cm. A 2560x1600 window on a 120 Hz panel already crosses the line, so both shapes are what a Mac host produces.
  • OVMF publishes no EDID protocol on its -vga std GOP, so the GOP profiles and metal-sim boot with no panel size.
  • GTK reports logical pixels and no mm; that is QEMU's to fix.

Order

  1. The slot PR (the orchestrator's): the 4-slot limit goes. S1 lands after it.
  2. M1, a metal reading, before S2's GOP half.
  3. S1: virtio-gpu leaves the kernel; the compositor drives it. Live mode changes then work.
  4. S2: the scale.

Who owns what

part owner
the virtio-gpu function the compositor, through a pci:1af4:1050 claim; the driver is system/compositor/virtio-gpu on toyos-virtio (S1)
the GOP scanout the kernel, which maps the firmware's memory and mints the framebuffer claim; the claim carries the EDID base block the loader read, unparsed (S2)
a claim's lend window the kernel; its length is toyos_abi::pci::LEND_WINDOW, the boundary's (S1)
the display's settings the compositor's display port, which toybox alone receives (S1)
decoding an EDID lib/edid, its own crate because it decodes a device's bytes (S2)
the mode's bounds, the scale, the layout at that scale toyos-desktop (S1, S2)
a window's size and scale on the wire toyos-window (S2)
winit apps the winit fork's ToyOS backend (S2)
glyphs at a size toyos-font, from the TTF (S2)
icons and the cursor at a size, for the compositor, files and the build toyos-icon in sdk/icon, replacing sprite and files' copy (S2)

M1. Each laptop's panel size, read the way ToyOS reads it

  • One T14 boot and one AMD bench boot of a scratch loader that never lands. It opens EFI_EDID_ACTIVE_PROTOCOL and EFI_EDID_DISCOVERED_PROTOCOL on the GOP's handle and prints whether each is there, SizeOfEdid, and the 128-byte base block in hex.
  • Only the base block is committed, as a lib/edid fixture, with the ID serial (0x0C-0x0F) and any 0xFF serial descriptor zeroed and the checksum recomputed, the provenance saying so. An extension can carry a per-unit identifier and is not committed.
  • Why: S2's GOP half rests on a protocol QEMU's firmware does not publish, and no boot of either laptop has asked for it. If neither firmware publishes it, S2's GOP half is the override alone until a display driver reads the panel over its own link; the next source is ACPI _DDC, which acpiserver can evaluate.

S1. virtio-gpu leaves the kernel; the compositor drives it and follows the host's window

Issue: stage 1 of issues/every-driver-is-still-in-the-kernel.md, its virtio-gpu half; meets the exits of the-gpu-driver-asserts-on-what-the-device-answers.md and the-second-scanout-buffer-is-read-by-nobody.md.

Design.

  • The holder is the compositor. Its row gains pci:1af4:1050 beside framebuffer, taking virtio first, as soundserver does. A separate display server would put an IPC hop in every present for one client; netstack and soundserver drive their functions in-process, and so does this.
  • The driver is system/compositor/virtio-gpu, written against toyos-virtio's Registers and DmaBuffers and host-tested on its stub. The commands and answers are types; every size the device reports is bounded before it is a size.
  • Every control-queue command is fenced (§5.7.6.5, §5.7.6.7): it carries VIRTIO_GPU_FLAG_FENCE and a fresh fence_id, and an answer without the flag or with another id is refused by name. So no completion the driver relies on is assumed: a frame's transfers, the flip's transfer before SET_SCANOUT, the UNREF before its backing is unlent, and the cursor's fill before UPDATE_CURSOR (§5.7.6.6). QEMU's 2D path echoes the fence (measured).
  • The cursor queue carries UPDATE_CURSOR and MOVE_CURSOR with the 24-byte response buffer §5.7.6.10 defines. A used len of 24 is an answer, read and held to the same error rule as the control queue's; len 0 is no answer, which is what QEMU writes (measured, both chain shapes); any other len is refused by name, and past 24 is toyos-virtio's UsedRefusal::Written.
  • One MSI-X entry serves both queues and configuration changes (Setup::config_vector). Every wake of the claim's record, inside a frame's or a flip's bounded wait too, drains both used rings and then reads events_read. An EVENT_DISPLAY found there is noted and acted on when the current flip or frame is done, so a notification consumed by a wait is never lost.
  • The output is read the same way at start and on every event (§5.7.4.2, §5.7.5): write events_clear with what events_read held, then GET_DISPLAY_INFO and GET_EDID(0). Clearing first means an event that lands during the reads raises another.
    • The mode is the display info's preferred size (§5.7.6.8); the 1280x720 floor goes.
    • At start there is no current mode to keep. The compositor sets the preferred size clamped, axis by axis, into the bounds below, logging by name when it clamped; a disabled scanout or a size of 0 sets the least mode, logged by name. The next display event then takes the ordinary path. So a 1x Mac host whose zoomed window already reported 640x360 boots at 642x446, which cocoa scales into the window, and a later report wins.
    • The EDID gives only the size in mm (S2). Its length is its own extension count (byte 0x7E), held to the answer's 1024 bytes: the size field is the buffer's (measured).
  • The mode's bounds are one declaration each, in toyos-desktop.
    • Least: the logical screen an 80x24 terminal of 8x16 cells fills inside Chrome::DEFAULT's frame above the taskbar, computed from those values: 642x446. After start, a mode smaller than that at scale 1 is refused by name and the current mode stays.
    • Largest: 7680x4320, the largest mode Intel publishes for the T14's display engine (ARK, Tiger Lake GT2: DP 7680x4320, eDP and HDMI 4096x2304). A report past it is refused by name.
  • A mode change is a page flip (§5.7.6.3); the old mode stays on screen until SET_SCANOUT:
    1. Lend a new backing; RESOURCE_CREATE_2D (the next id), RESOURCE_ATTACH_BACKING.
    2. Make the new back buffer, desk and layout (S2's relayout) and compose a whole frame into the backing.
    3. TRANSFER_TO_HOST_2D (whole, fenced), SET_SCANOUT, RESOURCE_FLUSH (whole).
    4. RESOURCE_UNREF the old resource (fenced), then unlend its backing and drop it.
  • What an error answer does (§5.7.6.7's RESP_ERR_*):
    • ERR_OUT_OF_MEMORY to RESOURCE_CREATE_2D, RESOURCE_ATTACH_BACKING or SET_SCANOUT is the device declining the mode: the new resource is unref'd, its backing unlent, the old mode stays, and it is logged by name. QEMU answers it when max_hostmem cannot hold the resource or the host surface.
    • Every other error answer, to any command, is a device or driver fault on a command ToyOS built: the compositor ends, saying which command and which answer. That covers RESOURCE_FLUSH refused after SET_SCANOUT, a refused UNREF, and an error answer on the cursor queue.
    • What release frees on q35. A virtio function there has no FLR and no D3hot round trip, so pcidev cannot reset it: release takes mastering away, unmaps every grant and lend, and keeps their addresses as the slot's residue. QEMU keeps the host resources, max_hostmem included, until the next holder resets the device itself, which toyos-virtio's Offer::acknowledge does first (§3.1.1, status 0; QEMU's reset destroys every resource). The compositor is not restarted by the supervisor, so with no next holder they stay held until the machine resets.
    • The next holder is a compositor started again. It takes over the slot and its residue: its grants of the same sizes land where the last holder's were, and its lends go only where no residue is (pcidev's lend rule). On a function no mechanism resets, residue stays for the rest of the boot, so the lend window beside it is what a restarted compositor can lend: 256 MiB less what the last holder lent. A restart at the largest mode is then refused its flip by name. S1 records that in issues/, beside the slot issue the orchestrator holds; its exit is a residue that a holder's own §3.1.1 reset retires.
    • A lend the kernel refuses (ResourceExhausted: no contiguous run, or the window full) is a refused mode change, logged by name.
  • A present is TRANSFER_TO_HOST_2D and RESOURCE_FLUSH per damaged rectangle, the last transfer fenced; the frame waits on it, bounded by a timeout that fails loudly. The next blit starts after it, so the host never copies a half-written frame.
  • The display's settings get a port of their own, display, served by the compositor beside prompt and clipboard and received by toybox alone (its screen applet), added to build/build.rs's HELD_ALONE. Its messages are toyos-window's display module: the mode, the scale (S2), a set and the answer. MSG_SET_RESOLUTION, MSG_GET_RESOLUTION and MSG_RESOLUTION_CHANGED leave the window protocol. screen WxH takes the same flip on virtio; on GOP it is refused by name. The next display event's size wins over it.
  • pcidev's bounds.
    • Grants stay at 8 MiB each and 32 MiB per claim, counted apart from lends.
    • A lend allocates nothing (dma_map places pages the holder already has), so its only cost is address space in the slot's own domain: lends get a window of their own, toyos_abi::pci::LEND_WINDOW = 256 MiB. toyos-desktop asserts at compile time that two scanouts at the largest mode fit it: 2 x 64 x 2 MiB.
    • A lent region stays one physically contiguous run; a flip that finds none is the refusal above. S1 records that in issues/ (owner: the memory track; exit: a region backed by more than one run, which §5.7.6.1's scatter lists already accept).
    • Only the two scanouts are lent. The virtqueues, the command buffers and the cursor resource's 64x64 backing are grants (DmaBuffers), under the grant bound, so two scanouts at the largest mode fill the lend window exactly and the compile-time assertion holds.
    • The slot count is not S1's: the 4-slot limit is removed in its own PR, and S1 lands after it. Until then the compositor's claim on cargo run's Virtio profile would be refused Exhausted, with no framebuffer to fall back to. The smoke's image claims two functions and is not affected.
  • The decline control and its actuator go (see the comment for the reasoning): declining_is_not_free, the kernel actuator virtio-no-access-platform and platform_addressing. Once the kernel drives only the console, the actuator reaches nothing. What ToyOS relies on is toyos-virtio's, host-tested on the stub: VIRTIO_F_ACCESS_PLATFORM is accepted wherever offered, and a device that drops FEATURES_OK is refused. What the control showed is QEMU's enforcement of a bit ToyOS never declines, measured above at 40e17ce2d. This departs from the issue's stated exit, by the orchestrator's ruling.
  • Profile::HeadlessVirtioGpu stays as the suite's virtio display machine, its doc rewritten: the clipboard test boots it, and the smoke reads that boot.
  • issues/every-driver-is-still-in-the-kernel.md's stage-1 paragraph is rewritten in one place, not appended to: the decline control and the actuator go, by the orchestrator's ruling; the profile stays, and why; the len measurement is met (round 2's evidence).
  • AArch64: a cargo run on virt hands no PCI claim over today (issues/an-aarch64-cargo-run-boot-hands-over-no-pci-claim.md), so the userland driver reaches AArch64 once that defect is fixed. Nothing in S1 is x86-64's alone.
  • What else goes: drivers/virtio_gpu.rs, the Gpu trait, the four SYS_GPU_* numbers and toyos::gpu; the claim's cursor region and second scanout; the panic console's detach on a mode change; console's present, so images/console.toml draws only on a GOP machine; and tests/guest/rust-tests/src/bin/abuse_gpu_resolution.rs, which guards a syscall that no longer exists.
  • GOP stays the claim's one scanout. The compositor presents it with its own blit, as window::Screen's present does today.
  • The development host. On macOS, build/qemu.rs passes -display cocoa,zoom-to-fit=on on the Virtio profile alone and -display cocoa on Gop and Metal, whose guests cannot follow the window, so their picture stays 1:1. GTK already defaults zoom-to-fit on for virtio-gpu.

Roast.

  • This is the tracked stage, and bigger than the feature. Keeping the driver in the kernel would add a config interrupt, a pollable claim and new SYS_GPU_* ABI for work netstack and soundserver show userland owns, against the issue's ruling that no syscall carries a GPU operation. Rejected.
  • A GPU fault now ends the compositor, not the kernel. The desktop cannot outlive its display.
  • The compositor blocks on its own device during a flip and each frame's last transfer, bounded. The server doctrine forbids blocking on a client, not on the server's own device.
  • Fencing every command costs nothing on QEMU's 2D path and is the only reading of §5.7.6.5 under which no completion is assumed.
  • A 256 MiB lend window per slot is address space only; the grant bound, which protects memory, is unchanged.
  • The panic console shows nothing on the Virtio profile, as today: OVMF's virtio-gpu GOP publishes no framebuffer.
  • screen WxH and the host window can disagree; the later wins.

Checks.

  • Host: the driver on toyos-virtio's stub:
    • start and every event clear before they read; an event raised between the clear and the reads is seen on the next pass;
    • an event raised while a frame's transfer is awaited is read on the next pass;
    • every control command is fenced; an answer without the flag, or with another fence_id, is refused by name;
    • a cursor-queue error answer at len 24 ends the driver by name; len 0 is taken as no answer;
    • at start, a preferred size outside the bounds is clamped and a disabled scanout sets the least mode, each logged;
    • a cursor-queue element with len past 24 is refused as Written;
    • the flip's order, with the old backing unlent only after SET_SCANOUT and the fenced UNREF;
    • ERR_OUT_OF_MEMORY at each of the three steps keeps the old mode and unlends the new backing; any other error answer ends the driver by name;
    • a mode below 642x446 or past 7680x4320, or of size 0, is refused by name; a disabled scanout keeps the mode;
    • toyos-desktop: both bounds, and the compile-time fit against LEND_WINDOW.
  • Guest: one smoke, the_compositor_drives_the_virtio_gpu, read from the clipboard test's own boot on Profile::HeadlessVirtioGpu (x86-64 q35, virtio-gpu-pci behind VT-d), not a second boot: the compositor logs its first mode and its first present, and a QMP screendump of the scanout is not blank. Why a guest test: no metal machine has a virtio-gpu, and the stub encodes this design's own reading of §5.7, so only QEMU's device tells the driver it was read right. Plus the guest tests whose behaviour S1 changes: the clipboard boot, iommu_virtio_platform (its kernel-driven arm is then the console alone) and the screen tests on GOP.
  • Negative control: named mutations turn the stub tests red: clearing after the reads, reading events_read only on a wake with no used element, dropping the fence from the UNREF, unlending before SET_SCANOUT, and accepting an error answer. The whole change reverted onto the measured base for the smoke.
  • Oracle: virtio 1.2 §5.7 for the order, the fence and the fields; QEMU 11.1.1 as the device under the smoke.
  • The owner's reading: cargo run on the Mac; drag the window and go full screen. The compositor logs one line per event naming the mode, the mm and the scale, and dumps the GET_EDID base block once, which becomes a fixture made by the device at the host's refresh. The picture is sharp.

S2. The scale

Issue: stage 5 of issues/toyos-has-a-desktop.md.

Design.

  • The model is physical-first. The compositor composes in the panel's pixels and never scales a client's buffer. A client is told its buffer's physical size and the output's scale as n/120, the representation the Wayland fractional-scale-v1 protocol publishes, and renders at that scale into a buffer of exactly that size. Integer scales take the same path.

  • macOS's model was considered and rejected. Rendering at an integer backing scale and resampling the whole screen to a non-integer "looks like" mode would, at 1.25 on a 1080p panel, render 3072x1728 and resample 2.56 times the panel's pixels every frame on a CPU, and resampled text is softer than text drawn at size.

  • No compositor upscaling of a client that ignores the scale. Every ToyOS client renders at scale; a third-party app that ignores scale_factor draws small.

  • The auto scale is ToyOS's own rule, in toyos-desktop:

    • The logical pixel is CSS Values 3 §5.2's reference pixel: the visual angle of 1/96 inch at a nominal 28 inches, about 0.0213°. A panel's density is held to it at the distance it is viewed from: target = 96 x 28 / distance dpi.
    • One viewing distance is assumed for every panel: 24 inches, so the target is 96 x 28 / 24 = 112 dpi. No published standard was read for it; it is ToyOS's choice, stated at the site, and the override exists for a person who sits elsewhere.
    • The scale is the quarter step from 1 to 4 nearest density / 112 dpi, stepped down until the logical screen holds the least mode (642x446).
    • The input is the panel's size as E-EDID 1.4 defines it, whose §3.6.2 says the host "is expected to use this data to get a rough idea of the image size to generate properly scaled text and icons". With no size, the scale is 1, logged by name.
    • What it gives (scale-rule-r3.log): QEMU's default EDID, -display none and the device's 1024x768 answer, 1; a 14-inch 1080p panel (an assumed 309x174 mm; the T14's own waits for M1), 1.5; a 254-dpi cocoa window, 1.5 at 1280x720 (held there by the least logical screen) and 2.25 at 2560x1600; a full-screen 3456x2234, 2.25; a 24-inch 1440p monitor, 1; a 27-inch 4K, 1.5; a 27-inch 5K, 2; a 23-inch 4K, 1.75; 19.5- and 20-inch 1080p panels, 1 each.
    • Defence: one published definition of a logical pixel, one stated distance and quarter steps, so the scale is a non-decreasing function of density alone, short of the least-screen step-down. As a comparison and not an oracle, the log runs Microsoft's published table of Windows defaults: six of twelve rows agree and the other six are one quarter step away, ToyOS larger on the 10- and 11-inch tablets and smaller on the 24-inch monitor, and one where Windows has no 1.75.
  • The override: screen scale 1.25 or screen scale auto, on the display port.

    • The compositor applies it and keeps it in its own HOME (/state/compositor).
    • A value that is not a quarter step from 1 to 4, or that leaves the current mode below the least logical screen, is refused by name.
    • A kept override on a later mode that cannot hold it is applied as the largest quarter step that fits, logged by name, and kept: the next mode that can hold it gets it back.
  • EDID is decoded by lib/edid, no_std, no unsafe:

    1. It checks the base block's header and checksum, and takes the extension count as the length, within what the device answered.
    2. The image size, by E-EDID 1.4: a first 18-byte descriptor with a nonzero pixel clock is a detailed timing, and its mm are the size (Table 3.21 notes 11-12). A timing whose mm are 0 is "undefined" (note 17), and the size falls to the base block. Bytes 0x15/0x16 are a size in cm when both are nonzero, an aspect ratio when exactly one is zero, and unknown when both are (Table 3.12); only the first is a size.

    Nothing else is read: the mode comes from the device or the firmware.

  • GOP's EDID travels unparsed: the loader copies EFI_EDID_ACTIVE_PROTOCOL's base block before ExitBootServices, and the kernel passes it through the boot handoff into FramebufferInfo as 128 bytes and a length, 0 or 128, refusing any other length the loader wrote.

  • Which GOP handle (ruled after M1). M1 found the protocol on the first of two GOP handles on the T14 and on the second on the AMD bench, while the loader's display is the first on both. So the loader reads it only from a GOP handle that shows the scanout it hands over, judged by the frame buffer's base and the mode's size, pixel format and stride, and only where exactly one such handle carries EDID Active. None is "no EDID, scale 1", logged; two are refused by name, since an EDID from the wrong output would set the wrong scale silently. If S2's first metal run finds no match on either bench, reading the one GOP handle that carries the protocol goes to the orchestrator as a design change.

  • Rounding is half away from zero, in integers: px(l) = sign(l)·((|l|·n + 60) / 120). A rectangle scales by its corners, so two rectangles that touch still touch.

  • The wire (toyos-window):

    • WindowInfo and ResizeInfo gain scale. A window whose size or scale changed gets a new buffer and MSG_WINDOW_RESIZED, as a size change does today.
    • CreateWindowRequest's size says which it is, logical or physical, as winit's Size does; the compositor converts a logical one at the output's scale.
    • Input coordinates and damage stay in the window's physical pixels.
    • Window gains scale_factor() -> f64; Event::Resized means the size or the scale changed.
  • The winit fork gets one commit appended to toyos-0.30.13: scale_factor() answers the window's scale; a resize whose scale changed sends ScaleFactorChanged and then Resized; a requested Size goes on the wire as the kind it is. The InnerSizeWriter's answer is not honoured: the compositor decides window sizes.

  • The compositor draws at scale:

    • Chrome: every Chrome metric is px(DEFAULT); the desk, back buffer, wallpaper and window budget follow.
    • Text: toyos-font rasterizes JetBrains Mono from its TTF into a cell of px(8) x px(16), with the algorithm the build runs now (build/assets.rs's rasterize_cells) moved into the crate, so build and guest share one producer. fontdue becomes its runtime dependency; it is already in Cargo.lock for the build, calc and snake.
    • Icons and the cursor: one crate, toyos-icon in sdk/icon, holds the SVG subset, the coverage at a size and the alpha blit. build/icons.rs's rasterizer moves into it; sprite and files' icon.rs fold into it. The build ships the SVGs and runs the crate's parser over each, so a shape outside the subset still fails the build. The compositor and files draw each at px(size). files is an app and toyos-icon is pure Rust, so its hosts are unchanged.
    • The hardware cursor is used while px(20) fits virtio's 64x64, up to 3.2x; past that it is drawn in software. The hotspot scales.
  • The window budget counts two buffers per window (budget::window_bytes), because a window holds two during a resize, for every window at once on a scale change, and while a client that never presents again lives.

  • Relayout is pure, in toyos-desktop: on a scale change a normal window's rectangle scales by n'/n and is reflowed onto the work area; a maximized or snapped window is retargeted; a mode change alone reflows as today.

  • Nothing flashes. A window keeps the buffer it last presented until its first present into the new one, drawn clipped into the new rectangle meanwhile. A second resize before a present drops the buffer never presented.

  • ToyOS's own clients render at scale: terminal, filepicker (its consent prompt) and files. editor and paint stay at 1, per the ruling. console stays at 1: it draws on a raw scanout with no compositor.

  • inspect display gains scale.

Roast.

  • The viewing distance is an assumption, and one figure for every panel. A desk monitor is viewed from further than a laptop, so a monitor gets one quarter step less than a distance per class would give it, and a small tablet one more. A switch by size made the scale fall as density rose across it (round 2's review); the override is the answer for a person the one figure does not fit.
  • A windowed cocoa guest has the density of the screen the window is on, so its scale follows that screen, whatever the window's size.
  • Quarter steps only. The wire carries n/120, so finer steps would be a policy change, not a wire change.
  • Physical-first puts a non-integer logical size in every client's hands. winit already deals in LogicalSize<f64>.
  • A normal window can drift by one pixel when the scale goes down and back up. Keeping each window's size in logical units would remove it at the cost of converting the desktop crate's geometry. Not taken.
  • Two rasterizers move into the guest rather than the build drawing every font and icon at 13 scales: about 5 MB of font tables (Σs² = 92.6 times snake's 54,920-byte table) and a size list that tracks the policy. At run time, rasterizing costs milliseconds per scale change.
  • calc and snake draw build-rasterized fonts at a requested PhysicalSize. At 1.5 they open larger with text the same size. Fixing them is their job.
  • The T14's 1.5 rests on M1. If its firmware gives no EDID, it boots at 1 until the override.
  • QEMU's GOP profiles have no EDID, so metal-sim boots at 1 and a screen test there reads text at 1.

Checks.

  • Host:
    • lib/edid: bad header, bad checksum, short block, an extension count past the answer; the timing's mm; a timing with mm 0 falling to the base block; the cm when the first descriptor is not a timing; an aspect ratio and an unknown size refused. Fixtures: QEMU 11.1.1's four qemu-edid blocks, the device's own GET_EDID base block, the owner's S1 reading, and M1's panels, each with the command or boot that made it.
    • Scale: the rule's rows above, plus M1's panels; rounding at the halves and at negative coordinates; corner scaling of touching rectangles; the override's parsing, its floor refusal, and a kept override on a mode that cannot hold it.
    • relayout: a normal window keeps its logical size to within a pixel and stays on the work area; a maximized window fills the new work area; the cursor is clamped.
    • budget: two buffers per window.
    • the window's buffer swap: at most two, the old dropped at the first present after a resize.
    • toyos-icon: build/icons.rs's tests, moved, including the comparison with resvg's pixels at the shipped sizes.
  • Guest: the screen tests S2 changes, at scale 1, which every QEMU EDID gives. No new guest test.
  • Metal: one row, gop_edid_scale, riding tests/images/metalcase.toml's existing boot (no new boot), on the T14, and on the AMD bench too if M1 finds the protocol there. Why metal: the loader's EFI_EDID_ACTIVE_PROTOCOL read is reached by no host test, and by no QEMU guest, since OVMF publishes no EDID protocol.
    • It reads the loader's line giving the EDID length it handed over, and the compositor's line giving the decoded mm and the chosen scale.
    • Oracle: the mm and the scale Scale computes from M1's committed base block for that machine, recorded beside the fixture.
    • Negative control: the change reverted onto the measured base, which prints no EDID line, and the row reds.
    • If M1 finds no protocol on a machine, the body says so and that machine's row asserts the logged "no panel size, scale 1" instead.
    • M1's own lines and inspect display on each laptop are readings beside it.
  • Negative control: each rule's test against the change reverted onto the measured base; mutations named per test in the build: rounding half to even, skipping the zero-mm fall-through, dropping the second buffer from the budget.
  • Oracle: VESA E-EDID 1.4 for the offsets and their meaning; QEMU's generator, the device's own answer and the real panels' EDIDs for the parser; CSS Values 3 §5.2 for the reference pixel.

Metal, now and later

  • T14: Iris Xe, 8086:9A49, on the firmware's GOP at the panel's 1920x1080.
  • AMD bench: an AMD APU whose display audio is 1002:1637. Its GPU's id, its panel, its GOP mode and its engine's published maximum are not in the tree; AMD's product pages did not answer from this host.
  • Now, on both: a scale, from the firmware's EDID if M1 finds one and from the override otherwise, changeable live. No mode change: the mode is the firmware's and the kernel makes no UEFI call after the loader. No hotplug and no vblank, so the blit can still tear (issues/tearing-is-what-gop-cannot-give-back.md).
  • Later: one display driver per display engine; the T14's Xe-LP is the desktop track's stage 12, and an AMD one is tracked nowhere. Each joins the compositor's Display as another variant with EDID over the panel's own link, a mode list, flips on vblank and hotplug. Multiple outputs are the desktop track's stage 8.

What I am unsure of

  • 24 inches as the one viewing distance has no published source behind it here.
  • The override's home: the compositor's HOME, or /config beside the keyboard layout, which is there because every translator reads it; the scale has one reader.
  • UEFI 2.10's and ACPI 6.5's section numbers for the EDID protocols and _DDC are from memory and are checked against the standards before the build. E-EDID 1.4's are now read.
  • QEMU's 1 s debounce: whether a drag feels live enough is the owner's reading.
  • An AMD display engine driver is not filed anywhere; whether to file it is the orchestrator's call.

Build state

M1, read (staged with --metal-readback, run by the orchestrator; temporary row m1_edid_reading, since removed):

  • T14 (LENOVO 20W0003AMZ): GOP handle 0 of 2, the display's, carries EDID Active and Discovered, 128 bytes each and identical; handle 1 carries neither. Panel CMN N140HCA-EAE, first timing 1920x1080 at 138.78 MHz, 309x173 mm.
  • AMD bench (LENOVO 82A2): handle 0 carries neither; handle 1 carries both, 128 bytes, identical. Panel AUO B140HAN06.8, 1920x1080 at 141.00 MHz, 309x174 mm.
  • Neither block has a serial in bytes 0x0C-0x0F or a 0xFF descriptor, so the committed fixtures are the panels' bytes unchanged. The rule gives both 1.5.

S2's GOP half, built and read on both benches (head 56a8bfbb6, with main at 3ba4a4a2f merged; evidence s2/README.txt):

  • lib/edid (toyos-edid): the base block's header and checksum refused by name; the first timing's mm, a 0 mm timing falling to bytes 0x15/0x16, an aspect ratio and an unknown size. Fixtures: both panels, QEMU 11.1.1's four qemu-edid blocks, its virtio-gpu's GET_EDID answer; declared in build/licence.rs.
  • toyos-desktop::Scale: the rule as above, integer arithmetic, ties down, the least screen computed from Chrome::DEFAULT (642x446).
  • KernelArgs::gop_edid/gop_edid_len and FramebufferInfo::edid/edid_len; the loader's handle matching; the compositor's line compositor: display WxH, panel WxH mm, scale S on standard output (or why there is no size; only a malformed block goes to standard error); inspect display reads scale. Nothing draws at the scale yet: that is the rest of S2.
  • Metal row gop_edid_scale on metalcase's boot: the bench by its CPU signature, the loader's line for the handle M1 found, and the compositor's size and scale from that bench's fixture.

Checks:

  • Metal, both benches PASS at 6fd69e2b4 (T14, AMD): T14 "GOP handle 0 of 2 shows the scanout ... the base block handed over" and "panel 309x173 mm, scale 1.5"; AMD bench handle 1 of 2, "panel 309x174 mm, scale 1.5". The handle matching holds on both, so its fallback question is closed. The one later commit moves that line from standard error to standard output and changes nothing the row reads.
  • Host: cargo run -- --ci host at 56a8bfbb6, exit 0, 77 steps green (log).
  • QEMU: desktop_without_x2apic, exit 0, on metal-sim: "no GOP handle showing the scanout publishes EDID Active" and "no panel size, scale 1" (log). Its first run caught the loader closing one GOP open twice, fixed in 6fd69e2b4.
  • Negative controls: the row judged on M1's own readbacks reds on both benches by name (T14, AMD); three mutations as checked patches each red their named test (log).

Next: the rest of S2 (drawing at the scale, the override on the display port, the wire and winit, files, terminal and filepicker), then S1 once the slot PR lands.

🤖 Generated with Claude Code

https://claude.ai/code/session_015xhaydXCVRNscoccHYFUWy

issues/the-desktop-draws-at-the-panels-scale.md carries what PR #895 does
not land: the display port and its override, drawing at the scale, the
window protocol and the winit fork, and virtio-gpu leaving the kernel after
the slot issue. It links the accepted design and its reviews on #895, and
the desktop track's stage 5 points at it.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_015xhaydXCVRNscoccHYFUWy
@Japabu Japabu changed the title Design: a display scale factor and live mode changes, on QEMU and metal The loader hands the panel's EDID to the compositor, which chooses the display's scale Oct 14, 2026
@Japabu

Japabu commented Oct 14, 2026

Copy link
Copy Markdown
Collaborator Author

Build review: PR #895 at 229d36892 (GOP half of S2), main c4b0e05c0 merged

Architecture

Ownership matches the accepted design (6158220132, sections S2 and "Which GOP handle"). The loader makes the only UEFI call. The kernel passes 128 opaque bytes from KernelArgs into the framebuffer claim's FramebufferInfo and never parses them. toyos-edid is the decoder at the trust boundary; it has no unsafe, is no_std and has no architecture code. The scale policy lives in toyos-desktop, and the compositor owns the log line and inspect display. The kernel adds only what userland can't do: carrying bytes that exist only before ExitBootServices. Putting toyos-edid in its own crate is justified as an input boundary, and the virtio-gpu half of the track will reuse it.

Net size: +746/−10. About 230 lines are tests and fixture provenance, 44 are licence rows and 59 are issue text, which leaves roughly 410 lines of production code. The largest pieces are the loader's handle matching (80) and Scale (97). Neither carries machinery beyond the rule it implements.

What I checked

  • Loader against UEFI 2.10. The EFI_EDID_ACTIVE_PROTOCOL GUID bd8c1056-9f36-44ec-92a8-a6337f817986 is correct. The {UINT32 SizeOfEdid; UINT8 *Edid} layout is 16 bytes with the pointer at 8, which the code asserts. The protocol lives on the GOP child handle. SizeOfEdid 0 with Edid NULL is handled, and anything under 128 bytes is refused by name. The handle rule is implemented as the design states it: a handle matches only on equal framebuffer base and mode key, and exactly one matching carrier is required. "None" and "more than one" each produce a named line.

  • The double-open fix. CloseProtocol releases the (agent, controller) open-list entry, and that entry has an OpenCount in the spec's EFI_OPEN_PROTOCOL_INFORMATION_ENTRY. A second GET_PROTOCOL open from the same image therefore shares that entry, and closing twice returns NOT_FOUND. The drop(gop) comment is accurate. mode is not used after the drop.

  • Handoff. gop_edid is at 1336 and gop_edid_len at 1464, size 1472, and LAYOUT follows the size. FramebufferInfo gets its no-padding check from user_safe!. The kernel accepts a length of 0 or 128 and panics on anything else. That is right: the loader is ToyOS's own, so a wrong length is an internal invariant violation. set_resolution can't make the scale stale with an EDID present, because GOP's set_resolution returns NotSupported and virtio-gpu always reports length 0.

  • lib/edid against E-EDID 1.4. It checks the header, the mod-256 checksum and a short block. It reads the detailed timing's 12-bit image size (bytes 12–14). A timing of 0 mm falls through to bytes 0x15/0x16. Exactly one zero byte means an aspect ratio, and both zero means unknown.

  • Fixtures. I hex-dumped all seven files and their SHA-256 hashes match build/licence.rs. Both panels have zero in 0x0C–0x0F and no 0xFF descriptor; their tags are 0xFE and 0x0F. The T14 and AMD bytes are identical to the base blocks printed in M1's readbacks, which appear in the ctl-*.log files. The ours:/MIT OR Apache-2.0 entries follow the existing ACPI-fixture precedent. The SOURCE file states how each fixture was made.

  • Scale::auto arithmetic. I re-derived it. ideal² = px²·(254·24)² / (mm²·(10·2688)²). "Nearest quarter, tie down" is 64A ≤ (2q+1)²B. The step-down is mode-only, so the result is monotone in density for a fixed mode. LEAST (642×446) is computed from Chrome::DEFAULT and asserted. The tie test (1890×2520 on 381×508 mm is exactly 126 dpi, or 1.125 × 112) is a real tie.

  • Evidence. Both benches' judge-s2.log show PASS with the expected lines at 6fd69e2b4. The control logs exit 1 by name on M1's readbacks. The mutation log reds each named test. land/ shows --build-only and --ci host at 229d36892 with EXIT=0 and 77 steps green.

  • "Still holds at the head". git diff --stat 6fd69e2b4 229d36892 over the loader, kernel handoff, GOP, abi, compositor, lib/edid, metal harness and row touches only:

    The supervisor hands every boot service a single ring as both fd 1 and fd 2 (system/supervisor/src/main.rs:2632-2633), and Readback::log() reads those rings. Changing the channel therefore can't move the line out of what the row reads. The reuse is sound.

  • Row and declaration. The row rides METALCASE's existing boot. It says why it needs metal. It declares its machines through the CPU signatures and reds by name on any other machine. That matches the design ("T14, and the AMD bench too if M1 finds it").

Findings

BLOCKER

  1. system/compositor/desktop/src/scale.rs:14,44-47 — /// A scale as 120ths, the representation the wire carries. and pub const fn n(self) -> u16 /// The 120ths, as the wire carries them.
    • No wire carries a scale at this head. The display port and WindowInfo's n/120 belong to the track (issues/the-desktop-draws-at-the-panels-scale.md item 1), so both comments are false.
    • n() has no caller anywhere in the tree (git grep '\.n()'). It is code for an imagined caller and ships in a published-to-workspace library.
    • Fix: delete n() and reduce the struct comment to what is true now (e.g. "A scale in 120ths: a quarter step from 1 to 4"). The stage that adds the wire can add the accessor along with its first caller.

SUGGESTION

  1. issues/the-desktop-draws-at-the-panels-scale.md:24-38 — Item 1 restates design decisions: the logical/physical create flag, two buffers per window counted by budget::window_bytes, /state/compositor, toyos-font from the TTF, and the sdk/icon replacement. issues/README.md says a track "does not carry a design". The accepted design already has a home in the linked comment. Existing tracks (e.g. every-driver-is-still-in-the-kernel.md) are about as detailed, so I am not blocking on this. Still, a shorter list of deliverables pointing at 6158220132 would avoid keeping a second copy of the specification that can drift.
  2. loader/src/main.rs:417,419 — bs.handles::<Gop>().ok()? returns silently, which contradicts the PR's "logs which handle it took, or why it took none". A per-handle bs.get::<Gop> failure also drops that handle silently, so on a failed open the line would read "no GOP handle showing the scanout publishes EDID Active", which is misleading. Both cases are practically unreachable right after handle_for succeeded, but a named line for each would keep the "always says why" contract true.
  3. system/compositor/src/session.rs:120 — .min(info.edid.len()) silently clamps a length the kernel guarantees is 0 or 128. info.edid.get(..info.edid_len as usize) with an expect naming the kernel contract would fail loudly on a kernel bug rather than decoding a block whose length was misreported.
  4. loader/src/loaderlog.rs:26-27 — EDID_AT is used once and nothing reads it (the row spells "EDID: GOP handle" itself). Unlike GOP_AT, it disambiguates nothing. Consider inlining it.
  5. system/compositor/desktop/src/scale.rs:34,77 — LEAST and holds_least are pub with no caller outside the module. Private until the override needs them.

Nothing else is outstanding:

  • The handle rule, handoff, decoder, rule and tests match the accepted design and its rulings: ties down, monotone, 642×446, refusal by name.
  • The evidence is what the PR says it is.
  • The new track and the desktop track's stage-5 pointer otherwise follow issues/README.md. The frontmatter is valid (track/assigned), the files are named, and every referenced issue exists.

SEND BACK

… every turn

Scale::n() goes with its false comment: no wire carries a scale until the
track's first stage, which adds it with its first caller. LEAST and
holds_least are private. The loader names a GOP list or open it could not
make, where it dropped them silently, and spells its prefix in place. The
compositor takes the EDID length the kernel guarantees with an expect
naming that contract, where it clamped. The track's first item lists
deliverables and leaves the design to the comment it links.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_015xhaydXCVRNscoccHYFUWy
@Japabu
Japabu marked this pull request as ready for review October 14, 2026 01:15
@Japabu
Japabu enabled auto-merge October 14, 2026 01:15
@Japabu
Japabu added this pull request to the merge queue Oct 14, 2026
Merged via the queue into main with commit 4e11ebc Oct 14, 2026
6 checks passed
@Japabu
Japabu deleted the wt/toyos-hidpi branch October 14, 2026 01:40
Japabu added a commit that referenced this pull request Oct 14, 2026
No conflict.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_015xhaydXCVRNscoccHYFUWy
Japabu added a commit that referenced this pull request Oct 14, 2026
No conflict. Both sides changed `Cargo.lock`, `Cargo.toml` and
`tests/toyos.rs`, and each merged cleanly: main adds `lib/edid` and the
`gop_edid_scale` row, which tells the benches apart by the `microcode:` line's
signature, a line this branch leaves as it was on both benches. Main touches
none of `kernel/pure/cpu.rs`, `kernel/pure/xsave.rs`, the HWP path, the
counters path, `toyos-microcode`'s source or the rows judged at 49f786c.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_015xhaydXCVRNscoccHYFUWy
Japabu added a commit that referenced this pull request Oct 14, 2026
…gain

#918 moved since 9b39b7c: review round 1 and two merges of main (#895,
#917 and more). No conflict.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_015xhaydXCVRNscoccHYFUWy
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