Live gate-by-gate status is tracked in
docs/status.md. This roadmap is the milestone plan; it does not track day-to-day gate progress.
A tiny AArch64 UEFI application, written in Zig, is built, placed at
EFI/BOOT/BOOTAA64.EFI on a FAT32 ESP inside a GPT image, and booted under
UEFI by the Swift Virtualization.framework launcher (Apple silicon running
macOS 27 or newer only; there is no QEMU path in this project). The application prints
DIPSHITOS BOOTLOADER
firmware has agreed to cooperate
and returns control to the firmware.
Deliverables: boot/, host/vm-runner/, image/, tools/, docs/,
build.zig, build.zig.zon, AGENTS.md, README.md.
No kernel, loader, allocator, scheduler, filesystem, graphics, networking, SMP, or userspace existed at the end of milestone zero (historical statement of the M0 end state).
Load a separate AArch64 kernel image and transfer control to its entry point.
Implemented (2026-08-05; branch m1-kernel-handoff, merged to main;
see docs/decisions/0002-kernel-handoff.md): the boot UEFI app loads
\KERNEL.BIN (flat format v1, magic "DSK1") from the ESP via the Simple
File System protocol, allocates EfiLoaderCode pages with Boot Services,
copies the image, performs D/I-cache maintenance, and jumps to the kernel
entry (handoff ABI: x0 = base, x1 = size, x2 = System Table, x3 = open
root directory; the kernel returns a u64 status). The kernel was then a
few hundred bytes of freestanding Zig that returned 0 (historical
description of the M1 stub — superseded by the milestone-two kernel
proper).
Observed evidence on Apple M4 / macOS 27: BOOTED.TXT (loader ran),
LOADER.TXT (loader-observed placement, byte-perfect copy), RC.TXT
(kernel_rc=0x0 — the kernel ran and returned), MEMMAP.TXT.
Known issue (observed, RESOLVED 2026-08-05): the kernel's own
\KERNEL.TXT write previously landed corrupted on Apple VZ firmware
(shifted slices of the kernel image's .rodata) while the loader's
identical writes were byte-perfect. Root cause: the loader loaded the
file verbatim, putting the 24-byte DSK1 header at base+0 and the
content at base+24; LLVM's adrp+add references to .rodata
silently dropped the +24 header offset and read every literal 24 bytes
early. Fix: the loader now parses the header but places the content at
base+0 (ELF VMA V at RAM base+V) and jumps to
base + (entry_offset - 24). KERNEL.TXT is now byte-perfect and
byte-identical across runs, and zig build run gates on its content
(DIPSHITOS KERNEL, entry reached via handoff) in addition to
BOOTED.TXT and RC.TXT. Full investigation: ADR 0002 and
artifacts/m1-run*.txt / m1-fix-*.txt.
The kernel seizes the machine: it ends UEFI Boot Services, takes over the MMU with its own identity-map page tables, and drives a minimal MMIO serial console. No firmware services remain in use.
Design: docs/decisions/0004-kernel-proper.md (ADR 0004), with the
implementation design and review in docs/archive/m2-kernel-proper-design.md.
Apple Virtualization.framework is the only supported host (Apple silicon,
macOS 27+); there is no QEMU path. The guest stays freestanding Zig — no
libc, no POSIX.
Implemented; all milestone-two gates pass (2026-08-08, claim 1517):
build gates, the bad-handoff failure gate, the ADR 0004 D4 marker-fallback
gate, and — since claim 1517 — the VZ serial gate (zig build run:
post-MMU virtio TX puts the exact banner, memory-map print, and terminal
state in vm-serial.log). The boot stub allocates the v2 stack/handoff
contract; the kernel captures the map, retries ExitBootServices up to eight
times, builds/installs identity TTBR0_EL1 tables, probes declared MMIO
windows, and enters a terminal WFE loop after serial evidence. The MMU-
takeover death the marker ladder first exposed (claim 0009, M2_MAPD!) was
root-caused and fixed (claim 0010, 2026-08-07): the identity-map switch
now completes on VZ and the ladder reaches M2_SERIA. Claim 0013 decoded
the declared windows (Apple's efivars store + an internal debug UART) and
found the real console — a virtio-pci device outside them; claims 6460/7896
root-caused the post-MMU transport hang (translation start-level mismatch +
stale-TLB crutch) and claim 1517 fixed it in production (T0SZ=16 + TLBI at
the switch). The canonical, always-current gate table lives in
docs/status.md.
Turn the milestone-one kernel stub (runs on UEFI services, prints, returns) into a kernel that keeps the machine:
- Call
ExitBootServicesitself (stub never does), with a documented retry-and-abort behavior, after capturing the EFI memory map. - Replace the firmware's page tables with the kernel's own identity-map tables (TTBR0_EL1, 4K granule, Device/WB attributes from the memory map, MMU never disabled).
- Drive a minimal MMIO serial console (polled TX, no interrupts/DMA/RX)
and print the banner
DipshitOS kernel has seized control.— the first real content in the long-emptyvm-serial.log. - Hand off to the kernel through the versioned contract v2 (handoff struct: image handle, kernel stack, bounds; x3 repurposed from the ESP root directory).
kernel/— the kernel proper:ExitBootServicescall + retry loop, memory-map capture, static BSS-resident identity-map tables and the TTBR0_EL1 switch, theuartconsole module, banner + memory-map hex print.boot/— stub updates only: allocate the kernel stack and handoff struct (EfiLoaderData), pass the struct in x3, keep everything else (evidence writes, failure return) exactly as milestone one.host/— only if the fixed-memory-marker fallback is needed (see evidence contingency in ADR 0004 D4): a dump of the marker address. The serial path needs no runner change —vm-serial.logis already captured.docs/— this roadmap section, ADR 0004, architecture update, and the new[inferred]hardware-contract assumptions.
zig fmt --check,zig build,zig build image,zig build inspect, andswift build --package-path host/vm-runnercomplete; outputs are saved asartifacts/m2-*.txt.- Primary: a successful VZ run must put the exact banner
DipshitOS kernel has seized control.and the memory-map hex print invm-serial.log. The log must also containkernel terminal state. - The kernel does not return: the runner requires the terminal marker emitted immediately before the WFE loop.
- Failure path still works: the bad-handoff fixture must yield non-zero
RC.TXTbefore exit. Passing since 2026-08-06 (kernel_rc=0x2). - Every
[inferred]hardware assumption (UART base/layout, MMU behavior, GIC presence) is flipped to[observed]only with matching probe/serial evidence. A blocked host run leaves the entries inferred and is reported. - Marker fallback (ADR 0004 D4):
bash tools/verify-marker.sh— passing since 2026-08-07; the ladder discriminated the death site (M2_MAPD!, claim 0009), which claim 0010 then root-caused and fixed (ladder now reachesM2_MMUP! → M2_SERIA; seedocs/status.md).
- Allocator beyond fixed carve-outs, scheduler, processes, filesystems, graphics, networking, SMP, syscalls, ELF loading.
- Interrupts/GIC and timers — the contract records the GIC as an assumption now, but nothing programs it until milestone three.
- UART RX, FIFO/DMA/interrupt-driven I/O, any QEMU path.
The milestone-one KERNEL.TXT corruption (ADR 0002 known issue) is
closed: the loader's content-at-base+0 fix made the kernel's write
byte-perfect, and zig build run now gates on it. The issue sat on the
UEFI storage path; ExitBootServices removes that path entirely, so it
would not have gated milestone-two evidence (which moves to the serial
console) either way.
Historical: every VZ run observed an empty vm-serial.log until claim 1517;
the NVRAM marker ladder (ADR 0004 D4, claims 0009/0010,
artifacts/m2-mmu-takeover-gate.txt) was the working evidence channel, and
claim 0013 observed the actual console — a modern virtio-pci device
(VID=0x1af4 DID=0x1043) outside the declared windows (Apple's efivars
store + an internal debug UART). Post-exit access to the virtio transport
hung on VZ (claims 0013/0020) until claims 6460/7896 root-caused it (the
start-level mismatch + stale-TLB crutch) and claim 1517 fixed it in
production: the VZ serial gate now passes (banner + memory-map + terminal
state in vm-serial.log). The NVRAM console channel (claim 0015) remains
for nvram-console builds. The declared-window and virtio-pci findings are
[observed] in docs/hardware-contract.md; the device register layout
stays [inferred] where RX is concerned until the RX path is driven.
Scope frozen 2026-08-06. The next deliverable is an interactive command monitor served by the kernel's own polled serial console — not further kernel-proper plumbing first.
Named Milestone 1.5, the "Dipshit Monitor": boot into a terminal,
display a banner, accept commands at dipshit>, and execute at least ten
useful commands (identity, memory-map inspection, shell utilities, and
machine controls). Milestone two's kernel already owns the console and
never returns; the monitor is its terminal-loop payload. It promises no new
allocator, MMU work, interrupts, scheduler, or userspace. (Post-tag, the
allocator, exception vectors, and — claim 6420 — a guest-side FAT32
storage driver on the ESP landed anyway; the milestone's promise list is
historical.)
Progress as of 2026-08-08: the host plumbing (duplex serial attachment,
terminal handling, zig build console), console & shell core (RX abstraction,
line editor, tokenizer, dipshit> prompt loop), command registry (20 commands,
mock-tested), and transcript test gate (zig build test-console) are all
✅ done and gate-passing in CI. The MMU-takeover death is fixed (claim 0010),
the console is identified (virtio-pci, claim 0013), the NVRAM console
channel carries post-exit bytes (claim 0015), and — since claim 1517 — the
VZ serial gate passes (zig build run: post-MMU virtio TX lands the
banner + dipshit> prompt in vm-serial.log), and live RX is wired
(claim 6684: the polled virtio receive queue delivers host keystrokes end
to end — verify-live-transcript.sh asserts the live dipshit>
transcript in vm-serial.log), and the live reboot/shutdown
observation is done (claim 0527: reboot resets the machine, shutdown
powers it off — 4/4 boots via verify-live-reboot.sh), and the
filesystem gate is closed (claim 3475, 2026-08-09: ls/cat/write
persist through reboot via the pre-exit ESP snapshot + NVRAM-persisted
writes, verify-live-fs.sh — the ESP file window, registry now 20
commands) and upgraded the same day to a real FAT32 storage driver
(claim 6420: ls/cat/write read and write the live ESP's FAT volume
through a virtio-blk transport; files persist on the disk itself;
NVRAM variables are no longer the persistence medium). Closed
2026-08-09: all 7 hard gates pass; the milestone is tagged
m1.5-interactive-monitor. Current gate
state: docs/status.md.
The M1.5 hard gates, target screen, and milestone status live in
docs/status.md (the living status document); the twenty-step plan,
agent split, and per-step progress tracker lived in
docs/archive/march-m15.md (M1.5 closed 2026-08-09, tagged
m1.5-interactive-monitor; the active per-milestone tracker is
docs/march-m3.md). The monitor itself is implemented and
host-tested (console abstraction, line editor, tokenizer, 20 commands,
banner, mock-level transcript gate), and the host-side --console
plumbing landed (steps 4–7); the VZ serial gate passes (claim 1517 —
post-MMU virtio TX fixed with T0SZ=16 + TLBI at the switch), the RX
path is live (claim 6684 — the polled virtio receive queue delivers
host keystrokes; the live dipshit> transcript is asserted in
vm-serial.log by verify-live-transcript.sh), a live
reboot/shutdown is observed end to end (claim 0527:
verify-live-reboot.sh — reboot resets the machine, shutdown powers
it off), and the filesystem gate is closed (claim 3475, 2026-08-09:
ls/cat/write persist through reboot via the pre-exit ESP snapshot +
NVRAM-persisted writes, verify-live-fs.sh, 1/1 pair) and upgraded to
a real FAT32 storage driver (claim 6420: the ESP's FAT volume is
mounted and written through a virtio-blk transport, files persist on the
disk itself). The milestone is closed 2026-08-09: all 7 M1.5 hard
gates pass; tagged m1.5-interactive-monitor (see
docs/status.md).
Milestone three — allocator, interrupts, tasks, EL0/SVC, syscalls, uaccess, and userspace (CLOSED 2026-08-10, tagged m3-userspace)
The milestone-three plan, in canonical order (mirroring
docs/status.md's "What comes immediately afterward" and the per-card trackerdocs/march-m3.md): physical allocator → exception vectors → GIC + timer → kernel tasks → EL0/SVC boundary → syscall ABI → uaccess → per-task address spaces → user lifecycle → ESP exec → blocking syscalls. All cards are done (claims 3972/5162/9746/ 9187/5275/8215/3594/6120/5804/6729/6783/3200; see the tracker for per-card evidence), the full class A + class B gate set re-ran green at the candidate (claim 0707), and the milestone is taggedm3-userspace(2026-08-10).
A physical page allocator over the captured EFI map.First step DONE 2026-08-08 (claim 3972): first-fit bitmap allocator over the captured map's ConventionalMemory (fixed 128 KiB BSS bitmap over the 4 GiB identity-map span), wired post-exit;pages/pages selftestmonitor commands; 18 unit tests; live-observed on VZ. Loader/boot- services pooling DONE 2026-08-09 (claim 5162): the pool now covers conventional + loader + boot-services RAM, with explicit exclusion ranges protecting the live kernel image, stack, handoff page, and captured-map buffer (25 unit tests, class-A green,pagesreportsexcluded=). The boot-time map walk is already served bymemmap.MapView+mem/pages.Exception vectors (VBAR_EL1 + basic synchronous/IRQ handlers).DONE 2026-08-08 (claim 9746) — a real vector table + sync/IRQ handlers installed post-MMU;dipshit> faulttriggers a synchronous exception that is reported and resumed live on VZ (class B gatetools/verify-live-exceptions.sh).Interrupt setup (GIC) and a timer.DONE 2026-08-09 (claim 9187, superseding claim 7948's blocker conclusion): corrected ACPI MADT GIC type IDs, GICv3 redistributor SGI-frame offsets, and ICFGR trigger-bit programming. A real periodic CNTP PPI 30 now enters the claim-9746 EL1 IRQ vector on VZ, is acknowledged/EOI’d and re-armed; the strict live gate requiresticks=5 irq=5 poll=0and passes 3/3 boots.Tasks (kernel tasks first).DONE 2026-08-09 (claim 5275) — a tick-driven round-robin scheduler between two kernel tasks: the shell/main task and a demo worker on its own static BSS stack preempt at every timer PPI, with a minimal save/restore (the claim-9746 stubs already keep the register file on the stack, so the scheduler only saves the vector-frame pointer + ELR/SPSR per task).dipshit> tasksreports per-task saves/resumes/advances; host tests cover the switch logic; the class-B live gatetools/verify-live-tasks.shproves both tasks advance across ticks on VZ (worker report line after ≥ 2 real context switches + a responsive shell), and the strict live-timer gate still passes under preemption. No userspace, no MMU changes — a later card adds userspace.First EL0t task + SVC kernel boundary.DONE 2026-08-09 (claim 8215, PR #60) — a statically linked EL0 task with page-local user text/stack apertures, x8-selectedsvc #0, SP_EL0-preserving scheduling, and the strict live gatetools/verify-live-userspace.sh(two sequenced pings prove return to EL0 under timer preemption).Frozen syscall ABI + runtime dispatch table.DONE 2026-08-10 (claim 3594, PR #64) — ADR 0007 (docs/decisions/0007-syscall-abi.md) freezes x8 number, x0–x5 arguments, x0 result; the runtime-built 64-slot table implements slots 0–4 (ping/write/yield/exit/sleep) and returnsENOSYSfor reserved 5–63;sys_writeis bounded to the kernel-known EL0 apertures and the low-4-GiB identity blanket; scheduler yield/exit/sleep hooks and deterministicsyscallscounters land with the live gatetools/verify-live-svc.shpassing 1/1 (evidence:artifacts/syscall-abi-3594/verification-summary.txt).uaccess: fault-safe copy-in/copy-out.DONE 2026-08-10 (claim 6120) —kernel/src/uaccess.zigadds boundedcopy_in/copy_outover the EL0 text (read) + stack (read/write) apertures with the ADR-0007EFAULT(-3) contract (out-of-region, overflow, unmapped, permission), and a masked fault-recovery window: a real EL1 data abort during a copy is latched and ELR advanced past the faulting instruction, so the copy returns EFAULT instead of crashing EL1 (an optimizer-reordering hazard that parked on the first live run was root-caused and fixed with volatile window state).sys_writemigrates onto uaccess; theuaccessmonitor command and the EL0 payload prove the contract and the recovery live on VZ (gatetools/verify-live-uaccess.sh, 1/1:valid=1 fault=1 recovered=1,uaccess: efault ok n=8, no[EXC] parking).
M1.5 close-out: milestone tag (all hard gates now pass).DONE 2026-08-09 (tagm1.5-interactive-monitor) — the transport layer is done: post-MMU TX (claim 1517: T0SZ=16 + TLBI at the switch), live RX (claim 6684: the polled virtio receive queue delivers host keystrokes end to end —verify-live-transcript.shasserts the livedipshit>transcript invm-serial.log), the live reboot/shutdown observation (claim 0527:verify-live-reboot.sh—rebootresets the machine,shutdownpowers it off, 4/4 boots;ResetSystemunit-proven in claim 0011), and the filesystem gate (claim 3475:ls/cat/writepersist through reboot via the pre-exit ESP snapshot- NVRAM-persisted writes,
verify-live-fs.sh, 1/1 pair — the last previously-deferred hard gate) upgraded to a real FAT32 storage driver (claim 6420: the ESP's FAT volume is mounted + written through a virtio-blk transport; files persist on the disk; NVRAM is no longer the persistence medium). All 7 hard gates pass; milestone closed. (The milestone-three cards continue in the Milestone three section above; canonical ordering:docs/status.md.)
- NVRAM-persisted writes,
- A guest-side filesystem — the ESP FAT32 driver landed 2026-08-09
(claim 6420):
kernel/src/fat.zig(GPT + FAT32 mount/list/read/write with injected sector I/O) overkernel/src/virtio_blk.zig(the runner's disk as a modern virtio-blk transport, DID 0x1042 on VZ, re-armed post-exit after VZ resets the device at ExitBootServices).ls/cat/writeserve the live ESP volume; files persist on the disk itself. A general (non-ESP) filesystem is DONE 2026-08-10 (claim 3678, milestone four card 2) — the driver now mounts ANY FAT32 volume at ANY disk offset (fat.mount_partition), walks arbitrary directory cluster chains with/-path resolution (the image's EFI/BOOT tree is reachable:ls [<dir>],cat <file|path>), and the disk image carries a second 36 MiB DATA FAT32 partition (Linux-FS type GUID) mounted by the newmount <esp|data>command with a re- snapshotted, honestly-labeled window. Live gatetools/verify-live-gfs.shPASS 1/1 — the DATA volume is mounted by GUID, listed, read, written, and the file persists across a reboot on the disk itself. Entropy/CSPRNG (sketch).DONE 2026-08-10 (claim 2665, milestone four card 1) — the kernel has a REAL randomness source: a modern virtio-pci entropy driver (kernel/src/virtio_entropy.zig, DID 0x1044) with the claim-6420 post-MMU re-arm lesson (observed: VZ resets the device at ExitBootServices —entropy: pre-rearm st=00), a freestanding ChaCha20 CSPRNG (kernel/src/csprng.zig, RFC 7539, KAT-pinned inzig test) seeded from a 64-byte boot-time device read (entropy: seeded n=64), therandom [n]monitor command (registry 27→28), and a real ASLR consumer (the exec path randomizes the loaded EL0 program's user stack VA per boot). Live gatetools/verify-live-entropy.shPASS 2/2 — two boots produce differentrandomsequences and stack placements; seedocs/hardware-contract.md(entropy bullet now[observed]).- A process abstraction is DONE 2026-08-10 (claim 3848, milestone four
card 3) — a bounded process registry (
kernel/src/process.zig) where each Process owns the loaded image, the address space, the lifecycle state, and the exit status (which now survives the executor task's reap); exec and the boot-time static EL0 payload register as real processes,exit_currentfeeds the registry, theprocsmonitor command (registry 29→30) prints the table, and the new class-B gatetools/verify-live-procs.shPASS 1/1 shows the exec'd program running as a process with the boot payload's exited status kept past the reap. - Concurrent processes is DONE 2026-08-10 (claim 0826, milestone four
follow-on) — the exec gate (
scheduler.user_root_in_use, one user program at a time) is gone: every process owns its own TTBR0 user root and allocator-backed text/user-stack/EL1-exception-stack pages, the syscall/uaccess regions arm per task at SVC entry, and exec gates on capacity (pool slot, table carve-out, registry) instead. Two programs now load and run CONCURRENTLY — the class-B gatetools/verify-live-concurrent.shPASS 1/1 on VZ shows aprocstable with TWOstate=runningUSER.BIN rows (distinct task ids + stack VAs) and both programs' markers interleaving; the full shared-seam live sweep (exec/procs/addrspaces/tasks/userspace/svc/uaccess/lifecycle/ sleep/entropy) is green against the relaxed gate. - A long-lived process among live peers is DONE 2026-08-10 (claim 4613,
milestone-four follow-on 2) — claim 0826's two processes were copies
of the SAME program that both exited; this card adds a SECOND DSK1
image (COUNTER.BIN from
user/src/counter.zig, embedded by the same build/image pipeline) that NEVER exits (sys_write + sys_yield only, no sys_exit) with DISTINCTcounter: alivemarkers, and returns an exited program's allocator pages at the same reap that frees its executor slot (the exited descriptor stays inprocs). The class-B gatetools/verify-live-long-lived.shPASS 1/1 on VZ — the counter staysstate=runningacross the whole session while USER.BIN exits, is reaped, and is re-exec'd into the freed slot (the runner's new--script2/--script2-aftersecond phase forwards the re-exec after the first reap), thepagesfree count recovers, and a further exec with both live reportspool_full— the capacity gate with one spare slot; the full shared-seam live sweep is green against the second program. - Kill — the kernel owns process lifetime — is DONE 2026-08-10 (claim
7786, milestone-four follow-on 3, card 3c) — claim 4613 proved a
process can REFUSE to exit (COUNTER.BIN loops forever) but nothing
could END it; the new
kill <pid|name>monitor command (registry 30→31) arms the target's TCB (scheduler.request_kill) and the ring converts its NEXT selection into the existing exit path with the reserved status 137 (no syscall, ADR 0007 frozen; the switching core is untouched). The killed process flows through the REAL lifecycle — exit → zombie → idle-reap → page return (procsshowsstate=exited task=reaped exit=137) — and the class-B gatetools/verify-live-kill.shPASS 1/1 on VZ: NOcounter: alivemarker lands after thekill:line (only one task runs at a time, so the killed task never executes again), thepagesfree count recovers by EXACTLY 5 at the reap, and a phase-3 re-exec lands in the freed slot (the runner gains the--script3/--script3-afterthird phase); the full 12-gate shared-seam live sweep is green. - Per-process exit reports — exact counts — is DONE 2026-08-10 (claim
1014, milestone-four follow-on 3, card 3d) — the exit/reap report
machinery was a single first-wins-while-undrained flag (documented
debt from claims 0826/4613), so N exits in one idle-loop window
collapsed to ONE report line and the concurrent/long-lived gates had to
assert ≥1. The three single-slot report flags become bounded 4-slot
name+status FIFOs (pushed from exception context — pure BSS writes —
and drained IN ORDER by the shell idle loop and the monitor, no
double-print; drop-oldest overflow, documented + host-tested; ADR 0007,
the switching core, and the lifecycle states untouched — reporting
machinery only). The concurrent + long-lived gates tighten to EXACT
counts and both PASS 1/1 on VZ: two exits in one window print exactly
two
tasks user-exec exited status=43/ twoprocs USER.BIN exited status=43/ twotasks user-exec reapedlines, in order, with the boot payload'stasks user-el0 exited status=7staying its own distinct line; the full 12-gate shared-seam live sweep is green. - Exec context block — arguments to EL0 — is DONE 2026-08-10 (claim
4636, milestone-four follow-on 3, card 3e) — the tokenizer already
split
exec <file> [arg...]butmonitor.execignored the extras, so a program's identity was its image only and the SAME binary could not distinguish itself per exec. Card 3e packs a bounded argv block (8 args × 32 B, NUL-terminated, per-arg 31-byte truncation; >8 args is an honest refusal) into the process's OWN text page right after the loaded content — the text leaf is already EL0 read-only (W^X, AP= read-only), so the block is a READ-ONLY leaf with ZERO extra pages (the per-program 5-page budget and every exact-count page gate stay untouched) — and extends the ENTRY contract (NOT a syscall; ADR 0007 frozen):_startreceivesargcin x0 and the block VA in x1 via the claim-9746 frame slots. The text aperture extends over the block, so uaccess copy_in reads it and copy_out faults (host-tested both directions). The class-B gatetools/verify-live-args.shPASS 1/1 on VZ —exec USER.BIN alpha+exec USER.BIN beta: the SAME binary loads twice, the procs snapshot shows twostate=runningrows with distinct task ids + stack VAs, the DISTINCT markers (user: arg=alpha/user: arg=beta) prove which invocation is which, both programs complete (status 43 — EXACT FIFO counts), and a third exec ispool_full(5/5, no spare); the full 12-gate shared-seam live sweep is green. - IPC — distinct processes exchange data (claim 5965, milestone-four
follow-on 3, card 3f) — coexistence is proven (claims 0826/4613) but
two live processes cannot COMMUNICATE; the strongest proof of "real
processes" is end-to-end data flow between them. The card lands the
FIRST inter-process data path: a bounded per-process kernel mailbox
(4 × 64 B BSS ring per process id, no allocation —
kernel/src/mailbox.zig) behind TWO new syscalls (the card's ONE ABI change, following thesys_sleepslot-4 precedent):sys_ipc_send(slot 5) copies the caller's bytes through uaccess into the TARGET's ring (full →ENOSPC-5),sys_ipc_recv(slot 6) copies the caller's OWN ring out (empty → 0; peek → copy_out → drop, so an EFAULT never loses a message) — cross-process isolation (a process reaches only its own mailbox via recv and a live target's via send), the ring reset on process create/recycle.mbox [<pid>]monitor command (registry 31→32) dumps per-process pending/sent/recv + the queued bytes. COUNTER.BIN gains a periodic send (ipc: ping <d>every 3 iterations, target pid parsed from its argv — card 3e's entry contract) and a THIRD image PEER.BIN (user/src/peer.zigthrough the parameterized build pipeline) recv-loops forever and echoespeer: got ping <d>— TWO never-exiting programs exchanging bytes, byte-exact in the serial log. Pool math at 5 slots: counter + peer + shell + worker + idle = 5/5, NO spare (a third exec ispool_full; the 3g capstone raises the budget). The class-B gatetools/verify-live-ipc.shPASS 1/1 on VZ:exec PEER.BIN+exec COUNTER.BIN 1back to back, the counter'sipc: ping Nsends and the peer'speer: got ping Nechoes interleaved across the whole log (every send echoed byte-for-byte),mboxshows the peer's ring drained (pending ≤ 1, sent − recv == pending) and the counter's ring empty, both processes stillstate=runningat the finalprocsand neither ever exits, a third exec isexec: no free scheduler pool slot, and the shell stays responsive; the full 12-gate shared-seam live sweep + the args + kill gates are green. - [Claim 5795, milestone-four follow-on 3, card 3g — the pool-scale
capstone] — every prior card documented the 5-slot budget (3b/3c/3f:
"5/5, NO spare"; 3a/3e: one spare). This card DELIBERATELY raises the
scheduler pool
max_tasks5 → 7 (idle_idstaysmax_tasks - 1) and re-derives the gates: shell + worker + FOUR EL0t user slots + idle (the 4th user slot is the "spare" while only three are live). A BUDGET change only — ADR 0007, the switching core, the lifecycle states, and the ring mechanics untouched; the pool is a BSS array (no allocation). The page-table carve-out survey (kernel root + 4 user roots,addrspaces: tables=NN/256) keeps the roots well inside the 256-page budget (observed 150/256). Every capacity assertion re-derives: the exec/scheduler host tests (pool_fullat the new budget, exact free-counts on the refused path), the transcript fixture (tasks: pool=4/5→pool=4/7), and the live gates (args/ipc'spool_fullmoves to the FIFTH exec at 7/7; long-lived's ending becomes the one-spare scenario — counter + two users + spare — with the page counts differing by exactly the second program's 5 pages). The new class-B gatetools/verify-live-scale.shPASS 1/1 on VZ:exec COUNTER.BIN+exec USER.BIN×3 back to back — FOURstate=runninguser rows with distinct task ids + stack VAs, the programs' markers interleaving with the worker's advances across the whole log, a FIFTH execpool_full(7/7 — shell + worker + 4 users + idle),addrspaces: tables=150/256, the counter still running at the final procs; the full shared-seam sweep re-derived against the 7-slot pool: 12-gate sweep + args + kill + ipc all PASS 1/1. - [Claim 5799, milestone-four follow-on 4, card 4a — process
observability] — the process registry exists (claim 3848) but only
the EL1h monitor can read it; this card gives EL0 a READ-ONLY view:
sys_procs(buf, max)= slot 7 (ADR 0007 amendment — the card's ONE ABI change,implemented_count7 → 8,syscallsrows 0–7) copies a bounded snapshot of the process table (one fixed 40-byte row per non-free descriptor: u64 pid, u64 state code, u64 exit status, name[16] NUL-padded) out into the caller's region through uaccess,maxtruncating to whole rows (a documented truncation result like the ipc recv path), marshaled into a fixed BSS scratch — no allocation. PEER.BIN (reused — the pool stays 7/7) pollssys_procsonce per quantum until it sees a running peer, then printspeer: sees <pid> <name> <state>per row (including the exited boot payload's row) and falls into its existing recv loop. The new class-B gatetools/verify-live-procs-syscall.shPASS 1/1 on VZ: the peer'speer: sees 2 COUNTER.BIN runningrow proves the counter is visible FROM EL0 — distinct from the monitor'sprocsread — the IPC flow still echoes, both processes never exit; the full 12-gate shared-seam sweep + the args/kill/ipc/scale gates all PASS 1/1. - [Claim 3179, milestone-four follow-on 4, card 4b — IPC depth] —
the card-3f mailbox is 4 × 64 B per process, so a bursty flow (more
than 4 sends before the peer drains) would refuse with ENOSPC. This
card raises
mailbox.max_messages4 → 8 (the per-process ring grows 256 → 512 B of fixed BSS) as a DATA-PATH CONSTANT — NOT a syscall number (ADR 0007 documents the choice; the follow-on-4 set's ABI amendments are ONLY slots 7/8, on cards 4a/4c). The truncation contract is unchanged: a message > 64 B still truncates at the slot bound, a full ring still refuses with the sameENOSPC-5 (now at the 9th send), the same empty → 0 recv, the same drain invariantsent − recv == pending ≤ capacity, the same cross-process isolation. COUNTER.BIN's send cadence becomes a BURST: every 6th iteration it sends 6 messages back-to-back in ONE quantum, then 5 quiet iterations (the peer drains 1 per round — the ring peaks at 6 of the 8 slots and drains to 0 before the next burst: NO ENOSPC, deterministically); each send checks its return and prints a distinctipc: enospcmarker on failure. The re-derived class-B gatetools/verify-live-ipc.shPASS 1/1 on VZ: the counter's 6-message bursts (ipc: ping N…ipc: ping N+5back-to-back) interleave with the peer's byte-exact echoes, ZEROipc: enospclines, the log's peak (sends − echoes) = 6 (> 4 messages queued at once, never over the re-derived 8-slot bound), themboxsnapshot shows the peer's ring drained at the new capacity (pending ≤ 8, sent − recv == pending) and the counter's empty, both never exit, a fifth exec ispool_fullat 7/7; the full 12-gate shared-seam sweep + the args/kill/scale/procs-syscall gates all PASS 1/1. - [Claim 9946, milestone-four follow-on 4, card 4c — exit-status
propagation] — the process table is observable FROM EL0 (4a) and
the mailbox flows deeper (4b), but a process still cannot WAIT on a
peer: the exit status of another process is visible only through the
EL1h monitor. This card lands
sys_wait(target)= slot 8 (ADR 0007 amendment — the follow-on-4 set's explicit slots 7/8 change,implemented_count8 → 9,syscallsrows 0–8): the caller blocks until the target process exits and returns its status — bounded, kernel-owned, NOT POSIX wait (no zombies, no fds). A running target parks the caller through the claim-0635 sleep seam (scheduler.wait_current— the SVC frame stays on the caller's kernel stack), and the exit path'swake_waitersflips the task back toreadywhile patching the observed status into the saved frame's x0, so the syscall return lands when the ring resumes it; an already-exited target returns its stored status immediately; EINVAL for a non-process caller, a free/out-of-range target, acreated(loaded, not yet running) target, or a self-wait (the refused deadlock). The block is event-driven — the tick clock never wakes a waiter. A THIRD program STATUS43.BIN (user/src/status43.zig, a fourth ESP image through the same build pipeline) prints its alive marker, sleeps 6 scheduler ticks (a deterministic window), then exits 43; COUNTER.BIN exec'd with the wait target in its argv (slot 8) printsipc: waiting pid=<n>, blocks, and printsipc: saw pid=<n> status=<s>on wake — the EL0-side proof of the propagation. The new class-B gatetools/verify-live-wait.shPASS 1/1 on VZ: the phase-2taskssnapshot shows TWOstate=blockeduser-exec rows (the sleeping STATUS43 + the waiting counter) whileprocsstill shows STATUS43state=running— the target ALIVE while the waiter is blocked — thenipc: saw pid=1 status=43agreeing with the kernel'stasks user-exec exited status=43/procs STATUS43.BIN exited status=43records; the full 12-gate shared-seam sweep + the args/kill/ipc/scale/procs-syscall gates all PASS 1/1. - Network stack (in progress — N1 + N2 + N3 + N4 + N5 + N6 landed 2026-08-12, claims 1373/6076/7293/0148/8552/1384). The
last "Eventually" item, and the one the virtio surface table below maps
to. The transport it needs was already proven (virtio
console/blk/entropy/custom drivers: discovery, queues, IRQ delivery,
post-exit re-arm), the runner change was one config line
(
config.networkDevices, now flag-gated behind--net <capture>), and N1 — the virtio-net TRANSPORT + TX — is DONE (claim 1373, branchagent/buffy/m5-net-tx): the guest'skernel/src/virtio_net.zigdiscovers the device (DID 0x1041 — the modern spec DID confirmed on VZ), negotiates features (claim-time finding: the device NEEDSVIRTIO_NET_F_MTUaccepted — VER1-only and VER1|MAC masks are rejected with FEATURES_OK cleared), reads the host-set MAC via the feature path, arms queues 0 (RX) + 1 (TX), re-arms post-exit (claim-time finding: the net device does NOT reset at ExitBootServices, unlike blk/entropy), prepends the 12-byte virtio_net_hdr the device consumes on every TX buffer (observed contract), andnetsenddrives TX end to end with bounded BSS staging and polled used-ring drain.net/netsendmonitor commands; live gatetools/verify-live-net-tx.shPASS 2/2 (byte-exact host captures: 46-byte known frame, ring reuse, honest truncation). Card order:N1 — virtio-net transport + TX.DONE 2026-08-11 (claim 1373). Runner attachesVZVirtioNetworkDeviceConfigurationunder the flag-gated--net <capture-file>mode;VZFileHandleNetworkDeviceAttachmentgives the deterministic gates (host reads exact Ethernet frames, like the custom-virtio spike), with a FIXED host MAC (02:00:00:00:00:01) the guest reads via the feature path;VZNATNetworkDeviceAttachmentfor real outbound connectivity stays a later card's option. Guestkernel/src/virtio_net.zig: discover the device (modern virtio-net DID 0x1041 — confirmed at claim time), post-exit re-arm (the claim-6420/2665 lesson; the net device does not reset — observed st=0f), MAC via VIRTIO_NET_F_MAC negotiation (worked — the fallback is the fixed BSS MAC), TX + RX queues, bounded frame staging (no heap), 12-byte virtio_net_hdr prepended (observed contract).netmonitor command (device/DID/MAC/queues/features);netsendproving the host receives known frames byte-exact. Live gatetools/verify-live-net-tx.shPASS 2/2.N2 — raw Ethernet RX.DONE 2026-08-11 (claim 6076). Queue 0 supplied with one fixed BSS buffer, used-ring drain into a bounded 4-slot frame FIFO (card-3d push/drain pattern), MAC filtering (own MAC + broadcast, drop the rest with a counter),net recvprints the frame byte-exact. The host injects a known frame via the runner's--net-inject <file>(a serial trigger, not a sleep) and the guest receives it and re-sends the SAME bytes — raw Ethernet frames back and forth. Live gatetools/verify-live-net-rx.shPASS 3/3 (broadcast round trip + byte-exact capture, own-MAC receive, foreign-MAC drop); claim-time observations pinned in the hardware contract: the device writes a 12-byte virtio_net_hdr into RX buffers (num_buffers=1, the RX-header question answered) and REFUSES an RX buffer under 1530 bytes; the used-buffer IRQ line is not yet observed — drain is polled.- N3 — ARP. Resolve peers (send requests, parse replies) and answer
requests for our protocol address. Static IP first (
net ip <a.b.c.d>, bounded, no config heap); DHCP is a later card. Live gate: the host ARPs for the guest IP and the reply carries the right MAC; the guest resolves the host's MAC from a crafted reply. LIVE 2026-08-11 (claim 7293) —kernel/src/arp.zig(pure RFC 826 logic: static IP, build request/reply, bounded 4-slot table, counters) wired into the RX drain (answer requests for our IP, learn replies, drop the rest) +net ip/net arpsubcommands + the runner's--net-arp-respond <host-ip>(deterministic host-side answer, OFF by default). Live gatetools/verify-live-net-arp.shPASS 3/3 (answer a request for our IP — 42-byte reply byte-exact in the capture; resolve a peer — request byte-exact in the capture + the host answer lands in the table; a foreign-address request is dropped with a counter while still observable vianet recv). Observed: the device delivers/transmits the 42-byte ARP frames unpadded (below the Ethernet 60-byte minimum). - N4 — IPv4. Minimal IPv4 TX/RX with ones-complement checksums
(host-testable); ICMP echo as the proof — the guest answers echo
requests and can send its own; honest bounds: no fragmentation, no
reassembly (drop fragments, documented). Live gate: the file-handle
host sends an ICMP echo request to the guest IP and the reply is
byte-exact; with NAT attached, the guest pings an outbound address.
LIVE 2026-08-11 (claim 0148) —
kernel/src/ipv4.zig(pure RFC 791/792 logic: RFC 1071 checksums, parse/build, ICMP echo request/reply byte-exact, fragments dropped counted — no reassembly) wired into the RX drain BESIDE the ARP dispatch (answer an echo for our static IP, observe echo replies — pong + seq, drop the rest counted) +net ping <a.b.c.d>subcommand + the runner's--net-icmp-respond <host-ip>(deterministic host-side echo answer, OFF by default). Live gatetools/verify-live-net-icmp.shPASS 3/3 (answer an injected echo request — the 46-byte reply is byte-exact in the capture with the identification + id/seq/payload echoed; ping a peer — resolve + echo request byte-exact in the capture and the host's answer lands as pong=1 with seq=1; a foreign-address echo request is dropped with a counter while still observable vianet recv). - N5 — UDP. Minimal UDP TX/RX over the N4 IPv4 seam (RFC 768):
datagrams in/out with the IPv4 pseudo-header checksum, a bounded
4-slot LISTEN table + bounded per-listener datagram buffers (
net udp listen/close/send/recv), and a real LOOPBACK path (a send to our OWN IP delivers directly into the local receive path — no device round trip; the bounded host/loopback test surface). LIVE 2026-08-11 (claim 8552) —kernel/src/udp.zig(pure RFC 768 logic: header parse/build, the pseudo-header checksum computed ALWAYS, bad-checksum / closed-port / short-length drops counted) wired into ipv4.zig's protocol dispatch (protocol 17 → udp on already-validated frames) +net udpsubcommands + the runner's--net-udp-respond <host-ip>:<host-port>(deterministic host-side echo answer, OFF by default). Live gatetools/verify-live-net-udp.shPASS 4/4 (loopback — send to our own IP delivers locally byte-exact with an EMPTY capture; host→ guest — the injected datagram is delivered to the listener byte-exact; guest→host — the datagram is byte-exact in the capture and the host answer lands in the listener buffer; a datagram to a closed port is dropped with a counter while still observable vianet recv). - N6 — UDP behind a bounded syscall seam. LIVE 2026-08-12
(claim 1384) — the ADR 0007 amendment slots 9/10/11
(
sys_udp_listen/sys_udp_send/sys_udp_recv): the N5 UDP layer exposed to EL0 user programs through the claim-6120 uaccess window (implemented 9 → 12), driven end to end by UDP.BIN (a new EL0 program: listen on 7000, LOOPBACK send+recv, peer round trip with the host's--net-udp-respondanswer, theEINVALerror mapping from EL0 for an unbound-port recv + an unresolved-peer send,sys_exit(17)). The recv drains the device FIRST (the claim-6076 polled-drain contract) so an EL0 polling loop is self-sufficient. Live gatetools/verify-live-net-udp-syscall.shPASS 4/4; the 34-gateverify-vzaggregate re-ran green 34/34. - Later, sketched only: DHCP, loopback-as-a-device, then TCP — far future, and the "only when the ones below it are demonstrably working" rule applies at every rung. The driver starts single-CPU (boot CPU, the claim-9187/0828 IRQ pattern); SMP is a separate future card.
Milestone six — graphics: Driving Award + Road Pops (IMPLEMENTED 2026-08-13 — G1–G6 all live, milestone closed)
Scope sketch (2026-08-12) — implemented below. The last open virtio surface row (Graphics) became a real milestone: the machine boots to a graphical interface. The boot terminal that had been the M1.5 "Dipshit Monitor" over the virtio serial console became Road Pops, a graphical terminal window, running under Driving Award, the window manager. Every card below kept the project's honest bounds (fixed BSS, no heap, one-request-at-a-time device access, gates with real observed evidence) and recorded new hardware assumptions in
docs/hardware-contract.md. The runner's--screenshotchannel already attaches the device (VZVirtioGraphicsDeviceConfiguration, 1280×720 scanout); the milestone's evidence path was that channel's raw pixels, byte-asserted like the net captures. All cards landed — see the ladder below anddocs/status.md.
Card ladder (canonical order; per-card tracker
docs/march-m6.md):
- G1 — virtio-gpu transport + framebuffer (
kernel/src/virtio_gpu.zig).Discover the virtio-gpu device (spec DID 0x1050 — [inferred] until observed on VZ, like every DID before it), negotiate features, set up the scanout + resources, and give the kernel a writable framebuffer (virtio-gpu 2D command path / resource mapping — the exact exposure mode is a claim-time observation). Post-exit re-arm per the claim-6420 lesson (whether VZ resets the gpu device at ExitBootServices is observed, not assumed).✅ DONE 2026-08-12 (claim 6053) — DID 0x1050 observed (class 0x038000), VER1-only accepted, the spec 2D command path to a writable BSS framebuffer (B8G8R8X8, opaque alpha), the post-exit re-arm (VZ resets the gpu at ExitBootServices —screenmonitor command (scanout/framebuffer report) + a solid-fill test. Gate: the host--screenshotcapture shows real guest pixels (a known color marker) — the first non-blank framebuffer.st=00),screen/screen fill/screen peek(registry 34→35), andtools/verify-live-screen.shPASS 1/1: the decoded capture is the fill green (first non-blank framebuffer; evidenceartifacts/live-screen-*,artifacts/gpu-screen-*s). - G2 — framebuffer text rendering ✅ done 2026-08-12 (claim 3194, branch
agent/buffy/m6-text) —kernel/src/text.zig(a built-in 8x8 bitmap font, fixed BSS glyph data, putc/puts, cursor, line wrap, a bounded 128-line scrollback ring,clear; pure logic host-tested against an injectable mock canvas — 21 tests including golden glyphs); the kernel paints its banner +dipshit>prompt on G1's framebuffer (fg 0x00ff00 / bg 0x101418) and pushes it through G1's transfer/flush unchanged;text/text put/text clearmonitor commands (registry 35→36). Live gatetools/verify-live-text.shPASS 1/1: the decoded capture shows real glyphs (green fg over the dark bg in the banner region — fg=0.255/bg=0.745 sampled; the G1-gate precedent: live pixels are color-managed, byte-exact glyphs live in the class A mock); the 36-gateverify-vzaggregate re-ran 36/36 PASS (artifacts/m6-text-vz-sweep.log). - G3 — Road Pops: the boot terminal goes graphical ✅ done
2026-08-12 (claim 1574, branch
agent/buffy/m6-roadpops) —kernel/src/road_pops.zig: a TEE console (serial shared seam FIRST + G2's text layer), drained one full-frame present per output batch by the shell idle loop; the G2 one-shot boot paint is replaced by the tee rendering the shell's OWN banner. Claim-time fix: theTargetstruct literal was folded into.rodatawith link-time&fnaddresses (claim-0015 redux, faulted live) — built in RAM now.roadpopscommand (registry 36→37). Live gatetools/verify-live-roadpops.shPASS 1/1: the decoded capture shows the banner AND the live session glyphs below it (echoed commands + replies rendered — a working terminal on screen; serial shared seam intact). G1/G2 gates updated honestly (the terminal renders over the raw fill; thetextreport's cur/lines are session-dynamic); the 37-gateverify-vzaggregate re-ran 37/37 PASS (artifacts/m6-roadpops-vz-sweep.log). Post-G3 hardening (the SCK switch): the pixel gates enforce the composited- window evidence, and introduced thetools/verify-live-glyphs.shmirror-tripwire gate. Issue #125 / claim 8742 corrected that first gate's false oracle on 2026-08-14: the font source is LSB-left, while both renderers and the decoder had repeated an MSB-left interpretation. The terminal and Driving Award clock now share the corrected source-bit helper; an independent asymmetricCgolden pins the decoder's normalization. The targeted VZ rerun passed: terminal forward 0/604 unknown glyphs versus 549/595 mirrored; clock titleclockand bodyDRIVING AWARDexact versus 4/5 and 10/13 unknowns mirrored. The historical 38/38 aggregate predates this correction; seedocs/status.mdandartifacts/live-glyphs-gate.txtfor the superseding evidence. - G4 — input: keyboard + pointer — MOVED to milestone seven.
[observed] 2026-08-13 (claim 3868): VZ exposes keyboard/pointer
(
VZUSBKeyboardConfiguration+VZUSBScreenCoordinatePointingDeviceConfiguration) as an Apple XHCI USB host controller (VID=0x106b DID=0x1a06CLS=0x0c0330) with USB HID devices behind it — there is no virtio-input device (DID 0x1052) in the framework. Screen-side input is therefore a full USB XHCI + HID stack, split into its own milestone — see Milestone seven anddocs/march-m7.md. - G5 — Driving Award: the window manager. ✅ DONE 2026-08-13
(claim 1543) —
kernel/src/driving_award.zig: a bounded fixed-BSS window registry (max 8) with z-order = array order, focus tracked by id, topmost-window hit-testing, and a dirty-rect compositor that repaints from the LOWEST dirty window up (the overlay-preserving order) and pushes one transfer + flush per dirty batch. Road Pops is window 0 (the full-screen terminal); a 1 Hz clock overlay (960,16 304×192, amber title bar / navy body, own BSS back-buffer) is window 1, redrawn fromtimer.ticksby the shell idle loop. The G3 tee's present now routes through the compositor,text put/clearcomposite too, and the I3 keyboard read source is gated onterminal_focused()— screen-side input lands in the focused window.win/win focus <n>/win raise <n>/win hit <x> <y>(registry 39→40); 9 host tests pin the hit-test/raise/focus/repaint/blit contracts. Live gatetools/verify-live-win.shPASS 1/1: the serial session (win: windows=2 focused=0,win[]rows,win hit 1000,100 -> 1focusing the clock,win hit 100,400 -> 0re-focusing the terminal) + a KEYBOARD-typedunamelanding in the focused terminal, and the decoded capture shows two overlapping windows with the right z-order (the clock's amber title bar + navy body over the terminal, the terminal's green glyphs beside it). Full class A green; the default VM stayed byte-identical. - G6 — a draw/window syscall seam for user programs. ✅ DONE
2026-08-13 (claim 0487) — the ADR 0007 amendment slots 12/13/14
(
sys_win_open/sys_win_fill/sys_win_present, implemented 12 → 15; a teardown follow-on adds slot 15sys_win_close+ thewin closecommand → 16) expose the G5 user-window surface to EL0: open a bounded kernel-owned window (id 2..3, fixed BSS back-buffer ≤ 256×192 B8G8R8X8), fill rects in its back-buffer, and present it (mark dirty for the compositor) — no uaccess, no per-process ownership (the window persists after exit, the honest bound). WIN.BIN (a newuser/src/win.zigprogram) proves EL0 graphics end to end: open → fill (dark-blue background + red/cyan/white blocks) → present → exit 87. Live gatetools/verify-live-win-syscall.shPASS 1/1: the program's markers +win: windows=3 focused=2/win[2]: user user rect=64,64,256,192+syscallsimplemented=16 (open=1/fill=4/ present=1, slot 15sys_win_closeregistered), and the decoded capture shows the window's own content over the terminal. Teardown follow-on:win close <n>+sys_win_close(slot 15) release a user window so the id can be re-opened instead of leaking until reboot (open → close → re-open host-tested indriving_award+syscall, and proven LIVE by the seventh image WINCLOSE.BIN — the class-B gatetools/verify-live-win-close.shshows the window gone (windows=2) and a re-exec re-opening id 2). Ownership follow-on: windows are OWNED by the opening process and AUTO-CLOSE when it exits (close_ownerfrom the scheduler's exit path — the real teardown semantic); fill/present/close are owner-restricted (host-tested cross-process refusals), WIN.BIN's window now vanishes on exit (windows=2,sys_win_close calls=0), and an eighth image WINLOOP.BIN keeps its window alive for the live gate's decoded-capture pixel proof. Move/raise follow-on: slots 16/17 (sys_win_move/sys_win_raise, implemented 16 → 18) reposition and restack the caller's window from EL0 — move clamps on-scanout, raise reorders the z-order, both owner-restricted; the monitor'swin move <n> <x> <y>is the EL1h half. A ninth image WINMOVE.BIN drives it live (open → fill → present → move → move-clamp → raise → yield-forever), and the class-B gatetools/verify-live-win-move.shPASS 1/1 shows the clamped rect (win[2]: user user rect=1024,528,256,192) + the counters (move=2/ raise=1) + the decoded capture with the window's colors at the NEW position. Read-back follow-on: slot 18 (sys_win_get, implemented 18 → 19) copies the caller's window rect (four u32 LE words) OUT through uaccess — the ONE pointer-taking win slot — so an EL0 program reads its clamped position back aftersys_win_move(the move is silent); WINMOVE.BIN now printswinmove: get 1024,528,256,192(the gate's get=1 assertion). Full-state query follow-on: slot 19 (sys_win_query, implemented 19 → 20) copies the caller's window FULL state (eight u32 LE words: x, y, w, h, z, focused, visible, dirty) OUT through uaccess — so an EL0 program introspects z-order rank + focus + visible/dirty, not just the rect; WINMOVE.BIN now printswinmove: query 1024,528,256,192 z=2 focused=1 visible=1 dirty=1(the gate's query=1 assertion). Visibility follow-on: slot 20 (sys_win_set_visible, implemented 20 → 21) HIDES (visible0) or SHOWS (visible1) the caller's window from EL0 (driving_award.user_set_visible, owner-restricted; the fixed terminal + clock are refused, a non-0/1 flag is EINVAL) — hiding marks the terminal dirty so the next composite repaints over the hidden window, showing marks the window dirty so it reappears; the back-buffer + z-order rank are untouched. WINMOVE.BIN now hides its window, sleeps 2 ticks, shows it again, and printswinmove: hide ok/winmove: show ok; the gate asserts hide=1/show=1/set_visible=2 + implemented=21 and gained a marker-driven capture (--screenshot-after "winmove: hide ok", a new VMRunner flag) proving the PIXEL DISAPPEARS (no red/cyan/white blocks at the clamped spot while hidden) and RETURNS (the LATEST capture shows them back) — the hide/show round trip, not just a registry flip. Milestone six closed — G1–G6 all live; the default VM stayed byte-identical.
Non-goals (for now): the balloon device stays unattached; no accelerated / 3D paths (virtio-gpu 2D blits only); no SMP; the window manager is single-display (one 1280×720 scanout).
Milestone seven — input: USB XHCI + HID (keyboard + pointer) (IMPLEMENTED 2026-08-13 — I1–I3 all live, milestone closed)
Scope sketch (2026-08-13) — implemented below. Milestone six's G4 input card was split into its own milestone after the claim-3868 observation: VZ's only keyboard/pointer surface is a USB HID stack behind an Apple XHCI USB host controller (
VID=0x106b DID=0x1a06 CLS=0x0c0330, two MMIO BARs0x50001000+0x50000000), not the hypothesized virtio-input device (DID 0x1052 — which does not exist in the framework). The three rungs, in order: I1 XHCI host-controller transport (MMIO + command/event rings + port status, NO HID), I2 USB enumeration + HID (port reset, address assignment, config descriptors, interrupt-IN endpoints, HID boot-protocol parsing), I3 event FIFO + keycode decode (bounded BSS FIFO feeding Road Pops' line editor + pointer). Per-card trackerdocs/march-m7.md; the runner's flag-gated--inputmode (claim 3868) already attaches the configs (OFF by default).
Card ladder (canonical order; per-card tracker
docs/march-m7.md):
- I1 — XHCI host-controller transport (NO HID yet). ✅ DONE
2026-08-13 (claim 4272).
kernel/src/xhci.zig: discover the observed XHCI device, map the two MMIO BARs (capability/operational/doorbell/RT), parse HCSPARAMS/HCSPARAMS1/HCSPARAMS2, set up the command + event rings + the primary interrupter, drive a NO-OP command TRB to completion, and read port status (USBSTS/PORTSC — how many ports, which connected).usbmonitor command (device/DID/BARs, HCSPARAMS, rings, ports). Honest bounds: no enumeration, no transfer rings, polled event-ring drain (the claim-6076 net lesson — the XHCI event IRQ is observed, not assumed); whether VZ resets the device at ExitBootServices is observed at claim time. LIVE:verify-live-xhci.shPASS 1/1 (14/14) — bus 0 dev 8, DID 0x1a06 CLS 0x0c0330, BAR0=0x50001000/BAR1=0x50000000, CAPLENGTH 0x20/VER 0x110/DBOFF 0x940/RTSOFF 0x520, HCSPARAMS1=0x10002010 (16 slots/32 intrs/16 ports), NO-OP completed CC=1 (USBSTS=0x0), VZ does NOT reset at EBS (pre-reset USBSTS=0x9/USBCMD=0x0), ports 9+10 connected (CCS=1 — the two HID devices, the I2 handoff). - I2 — USB enumeration + HID. ✅ DONE 2026-08-13 (claim 4116).
Port reset, Enable Slot + Address Device, read device/config
descriptors over the control endpoint (Setup/Data/Status TRBs), select
configuration 1, arm the interrupt-IN endpoints for the keyboard +
pointing devices, and parse the HID boot-protocol reports (8-byte
keyboard report + absolute pointer report — observed byte-exact at
claim time).
usb devices+usb reportsubcommands. Honest bounds: boot protocol only, the two known devices, no hubs/mass-storage/isochronous. LIVE:verify-live-usb.shPASS 1/1 (11/11) — BOTH devices enumerated: the keyboard (port 9, slot 1, VID 0x05ac PID 0x8105, boot protocol, EP1-IN maxpkt 8, boot=1) and the absolute pointer (port 10, slot 2, VID 0x05ac PID 0x8106, protocol 0 — NOT a boot mouse — EP1-IN maxpkt 10, Set_Protocol(boot) honestly REFUSED boot=0); a synthesized host keyDown (macOS keyCode 0) produced the observed 8-byte report00 00 04 00 00 00 00 00(mod 0, HID usage 0x04 = 'a'), read back byusb report. The runner gained the minimal synthesized-key seam (--input-key/--input-key-after); the full scripted key-sequence surface stays I3. - I3 — event FIFO + keycode decode → Road Pops. ✅ DONE
2026-08-13 (claim 6050).
kernel/src/input.zig(a bounded pure-BSS event FIFO + HID-usage → ASCII keymap + the shell-idle drain, the card-3d pattern) decodes the XHCI interrupt-IN reports into bytes that feed the Road Pops tee's read path; theinputmonitor command reports armed/FIFO occupancy/drop count/last keyboard + pointer events. The runner's--input-stringsynthesizes one NSEvent per keyDown/keyUp into the VZVirtualMachineView (VZ has NO keyboard API) at 2 s per keystroke (VZ delivers ~one report per Road Pops present cadence; single-TRB arming re-armed per completion is the correct shape — a multi-TRB depth experiment wrapped the transfer ring at the 8th report). Gate:tools/verify-live-input.shPASS 1/1 — the keyboard typedinput\nand the guest's owninputreport showedevents=6(i,n,p,u,t,Enter) withdropped=0andkb-usage=0x28 kb-byte=0xa(Enter) — the typed command ran end to end. This is what makes the graphical terminal a real machine, and G5 (Driving Award) depends on it.
Non-goals (for now): no hubs, no mass storage over USB, no isochronous endpoints, no full HID report-descriptor parser (boot protocol only), no USB 3.0 SuperSpeed transfer scheduling (the two HID devices are the only consumers).
The virtio device surface — where the OS meets the host. Every virtio
device VZ can attach maps to a host configuration class in
host/vm-runner/Sources/VMRunner/main.swift and, where driven, a guest
driver under kernel/src/. Five of the seven are done and live-gated; the
remaining two (graphics, balloon) are the open milestones.
| Device (guest-observed DID) | VZ host surface | Guest driver | Status | Claims / evidence |
|---|---|---|---|---|
| Console (0x1043) | VZVirtioConsoleDeviceSerialPortConfiguration (serial attachment) |
virtio_console.zig — queue 1 TX + queue 0 RX |
✅ done | 0013 (device identity), 1517 (post-MMU TX), 6684 (live RX transcript) |
| Block (0x1042) | VZVirtioBlockDeviceConfiguration (disk image attachment) |
virtio_blk.zig — modern virtio-blk, post-exit re-arm |
✅ done | 6420 (FAT32 storage driver), 3678 (general non-ESP FS on top) |
| Entropy (0x1044) | VZVirtioEntropyDeviceConfiguration |
virtio_entropy.zig (boot-time 64-byte seed) + csprng.zig (ChaCha20, RFC 7539) |
✅ done | 2665 (driver + CSPRNG + random), 3693 (EL0 stack ASLR consumer) |
| Custom virtio (0x1082, macOS 27) | VZCustomVirtioDeviceConfiguration (--custom-virtio / zig build spike-virtio) |
virtio_custom.zig — queue transport + used-ring IRQ |
✅ done | 5844 (host spike + pci command), 0828 (bidirectional queue + SPI IRQ), 4374 (ring allocator / multi-queue), 9492 (multi-descriptor payloads), 9737 (feature negotiation), 4837 (log transport) |
| Network (0x1041) | VZVirtioNetworkDeviceConfiguration + VZFileHandleNetworkDeviceAttachment (--net <capture-file>, --net-inject <file>, --net-arp-respond <host-ip>, --net-icmp-respond <host-ip>, --net-udp-respond <host-ip>:<host-port>, flag-gated; fixed host MAC) |
virtio_net.zig — TX + RX + ARP + ICMP + UDP (N1 + N2 + N3 + N4 + N5); arp.zig; ipv4.zig; udp.zig; syscall.zig slots 9/10/11 (N6 — the UDP syscall seam) |
✅ TX + RX + ARP + ICMP + UDP + the UDP syscall seam (claims 1373/6076/7293/0148/8552/1384) | 1373 (transport + TX live — DID 0x1041, VER1|MTU|MAC, feature-path MAC, queues 0/1, 12-byte TX-hdr contract, byte-exact host capture; verify-live-net-tx.sh PASS 2/2); 6076 (RX live — queue-0 buffer supply, polled used-ring drain, bounded FIFO, MAC filter, net recv; host→guest injection + the round trip; 12-byte RX-hdr observed, min RX buffer 1530 observed; verify-live-net-rx.sh PASS 3/3 + 29-gate aggregate green); 7293 (ARP live — static IP, answer/resolve over the RX seam, bounded table, --net-arp-respond; 42-byte frames unpadded observed; verify-live-net-arp.sh PASS 3/3 + 31-gate aggregate green); 0148 (IPv4/ICMP live — RFC 1071 checksums, echo answer/observe over the RX seam, net ping, --net-icmp-respond; the 46-byte frames travel unpadded, consistent with the N3 observation; verify-live-net-icmp.sh PASS 3/3 + 32-gate aggregate green); 8552 (UDP live — RFC 768 + the pseudo-header checksum over the N4 seam, bounded listen table + datagram buffers, the LOOPBACK path, net udp, --net-udp-respond; the 46-byte datagrams travel unpadded, consistent with the N3/N4 observation; verify-live-net-udp.sh PASS 4/4 + 33-gate aggregate green); 1384 (UDP syscall seam live — ADR 0007 slots 9/10/11 from EL0 via UDP.BIN: listen, loopback, the peer round trip, the EINVAL error mapping, exit 17; the polled-drain recv contract; verify-live-net-udp-syscall.sh PASS 4/4 + 34-gate aggregate green) |
| Graphics | VZVirtioGraphicsDeviceConfiguration — attached only for screenshots (--screenshot); milestone six's --display mode always attaches it |
kernel/src/virtio_gpu.zig (G1 — claim 6053); kernel/src/text.zig (G2 — claim 3194); kernel/src/road_pops.zig (G3 — claim 1574); driving_award.zig planned (G5) |
🚧 G1 live 2026-08-12 (claim 6053) — first non-blank framebuffer; G2 live 2026-08-12 (claim 3194) — the machine boots to words on the screen; G3 live 2026-08-12 (claim 1574) — Road Pops, the boot terminal is ON the screen (verify-live-screen.sh + verify-live-text.sh + verify-live-roadpops.sh PASS 1/1 each; 37-gate aggregate green; the SCK switch then enforced the composited-window evidence in the pixel gates and added the mirror-tripwire gate verify-live-glyphs.sh PASS 1/1 — the 38-gate aggregate re-ran 38/38 PASS); G5 next (docs/march-m6.md); the G4 input card was re-scoped into milestone seven (docs/march-m7.md) |
— |
| Balloon | VZMemoryBalloonDeviceConfiguration — nothing attached |
none | ⬜ not started — low priority (fixed 256 MiB guest, no demand paging) | — |
Each milestone must state what was observed versus inferred and must
record new hardware assumptions in docs/hardware-contract.md.
Scope (2026-08-14). Milestones zero through seven shipped a complete machine; milestone eight turns it into a usable one. The normative contract is ADR 0008 (
docs/decisions/0008-human-interface-guidelines.md): one command grammar + groupedhelp, a real line editor (history, cursor movement, Ctrl chords, tab completion), oneerror:/usage:shape, a visible-focus window model with click-to-focus, and anabout/welcome/sysinfosupport surface — all enforced by gates, not prose. Per-card tracker + collision-free agent split:docs/march-m8.md. The cards, in order:
- U0 — Human interface guidelines (ADR 0008). ✅ DONE 2026-08-14 (claim 8938). The normative shell/window/support contract (D1 command grammar + grouped help, D2 prompt/editing, D3 error/usage shapes, D4 window interface, D5 support surface, D6 gate-enforceability). Docs only — no code, no ADR 0007 change, no POSIX/readline promise.
- U1 — Help & catalog. ✅ DONE 2026-08-14 (claim 3275). grouped
help+help <cmd>+help <topic>pages, usage strings wired to handlers (they already were — U1 adds the category field + the grouped catalog +topic_body). Topics: networking, windows, storage, graphics; command-named topics (syscalls,input) resolve to their command detail. Gate: the byte-identical transcript (regenerated) + the livehelpwalktools/verify-live-help.shPASS 1/1 on VZ. - U2 — Shell editing & history. ✅ DONE 2026-08-14 (claim 1809).
bounded history ring, cursor left/right + Home/End, Ctrl-A/E/K/U/L/C,
Delete, and tab completion over the I3 input path; the arrow/Home/End/
Delete usages + Ctrl-chord modifier decoding landed in
input.zig; the runner gained--input-chords(one NSEvent per keyDown/keyUp). Gate:tools/verify-live-editing.shPASS 1/1 on VZ (mid-line insert + Up recall typed by a real keyboard); the unchanged paths stay byte-identical. Also fixed a latent I3 interrupt-ring wrap OOB (xhci.zig) the longer sequence exposed (phantom-key re-read). - U3 — Error/usage contract. ✅ DONE 2026-08-14 (claim 1511). one
usage: <cmd> <args>(registry single usage string, reused for sub-verb misuse viaprint_usage), oneerror: <actionable>(err_prefix) across storage/machine/win/kill/netsend/screen/text/exec + the whole net family, oneunknown command '<x>' -- try 'help'; byte-exact misuse transcript (usage: pages [selftest],error: …, the long-line unknown verb) + three deterministic host fuzz tests (tokenizer / handlers / full input path) that never panic. The fuzz found and the card fixed a latent U2 width bug: the history ring's@min(hist_count, hist_capacity - 1)inferred u4 and overflowed at the 16th distinct history entry (explicitusizeanchor + regression test). Live help + live transcript gates re-run green. - U4 — Pointer focus + cursor. ⬜ consume the absolute-pointer reports into Driving Award hit-test focus + cursor rendering (needs a runner pointer-synthesis seam, the I3 keyboard seam's analogue).
- U5 — Window HIG. ⬜ title bars, focus ring, click = focus + raise, keyboard focus cycling.
- U6 — First-boot experience. ⬜
welcome/aboutrefresh + boot motd. - U7 —
sysinfo. ⬜ the one-command support snapshot. - U8 — Persistent settings. ⬜
settings get/setbacked by a DATA-partition config file, read at boot.
Non-goals (for now): POSIX/readline/coreutils compatibility, shell scripting/pipes/job control, any syscall-number addition (a focus syscall, if ever, lands as its own ADR 0007 amendment), and a visual-design pixel spec (colors/spacing stay implementation detail).
Maintainer's wishlist (2026-08-14). These are destinations, not a milestone ladder — a hope chest, roughly in the order the maintainer would like to reach them, but nothing here is a promise and the roads between them stay open. If dependency discoveries reshuffle these (e.g.
M9 interactive apps → M10 user files → M11 application platform), that is the point. No agent should treat an item here as a green-light to climb a mountain just because it is visible on the map.
The bridges the maintainer would be most disappointed to see omitted — from "very capable kernel" to "weird little computer" — are EL0 application events, userland filesystem access, consumer apps, and a lightweight app/launcher model (items 2, 5, 4, and 10 below).
- Finish M8 as the usability consolidation milestone. Help,
editing/history, coherent errors, pointer focus/cursor, window chrome,
first-boot experience,
sysinfo, persistent settings — no giant new subsystem hiding inside it. (This is the current, real milestone — see above.) - Interactive EL0 application events (the biggest near-term want). Per-process event queues: keyboard, pointer/button, focus/blur, close requests, and probably a blocking/polling event syscall. This is what turns graphical syscall demos into actual applications.
- A tiny userland application support layer. Not a GUI framework — just enough shared Zig for app startup, event loops, windows, drawing, basic strings, and syscall wrappers. Build a couple apps first and extract only what they genuinely duplicate.
- Several deliberately small GUI apps. Calculator, a notepad-ish thing, process viewer, network monitor, a dumb paint thing. Architectural probes disguised as toys: if every one needs a kernel modification, the app boundary is wrong.
- Userland filesystem access. Handles (or a Dipshit-native equivalent), read/write/create, directory enumeration, metadata, paths — deliberately before any graphical file manager.
- A graphical file browser. Read-mostly at first: browse DATA, open text files; create/rename/delete later. The integration proof for events + windows + filesystem APIs.
- Userland TCP/network API. UDP already crossed the boundary; eventually let EL0 own connections without cloning POSIX sockets by reflex. Then make a real user network client consume it.
- DNS. Once an EL0 network app exists, numeric IPs get stupid fast. Keep it bounded and boring initially.
- Application identity/metadata. A tiny manifest or image-metadata concept: name, executable, maybe an icon later, the file types it understands — so the launcher/file manager stop hardcoding knowledge.
- A launcher. Could start hilariously simple: list installed apps, run one. Alongside the file browser, this is when DipshitOS gets a recognizable "desktop" shape.
- Clipboard / shared user-interaction services. Probably later; text apps will make the absence obvious. Discover the right IPC/service model through this rather than inventing "system services" abstractly.
- Timers/events available to applications. Apps shouldn't spin or invent sleep loops to animate/update; a clean timer-event mechanism fits after the event queue exists.
- Resource-model cleanup when the fixed pools hurt. Windows, processes, mailboxes, and network state are wonderfully bounded now — keep them that way until real apps expose actual pain, then introduce dynamic kernel objects/allocators because the workload demands them.
- Richer virtual memory only when applications force it. More flexible mappings, guard pages, larger programs, maybe mmap-ish primitives, COW much later. No demand paging merely because Serious-OS bingo says so.
- Executable-format evolution. The DSK1 flat-image model has done heroic work; larger programs/libraries may eventually justify ELF loading or another structured native format. Consumer first.
- Reusable UI toolkit, but late. Buttons, labels, text fields, lists, scrolling, layout — after two or three hand-built apps have shown what the common pieces actually are. Otherwise someone lovingly architects GTKdipshit before anyone has clicked a button.
- Better filesystem semantics eventually. Atomic-ish updates, truncation, deletion, rename, free-space management, corruption handling, maybe beyond FAT32 someday — but let applications create the pressure first.
- Audio eventually. Fun, and another real device/service pipeline. Not remotely urgent; on the "DipshitOS becomes a computer" list.
- Security/isolation hardening. More permissions around resources, stronger validation of userspace-controlled arguments, process ownership everywhere, maybe capabilities — grown alongside userland power, not as one giant Security Milestone.
- Distant mountains (keep visible, do not climb yet). SMP, 3D acceleration, dynamic linking, sophisticated VM, POSIX compatibility, browser-grade networking, USB-everything. Keep them on the map so nobody thinks we forgot them — but no agent should see "mountain" and immediately buy climbing gear.
Meta-requirement for the roadmap:
Every major infrastructure card should name the small program or experience that will consume it next. Every milestone should end with a composition test that a human can see or use.