ZXdesk puts overlapping windows, pull-down menus, a file manager, a clock, a calendar, settings, and a small application model on a 1982 ZX Spectrum with 48K of RAM.
The delightful part is that it runs on the real machine. The useful part is how it got there. Its author, mindbox77, did not treat the Spectrum as a miniature modern PC or rely on emulator speed to hide expensive decisions. The project measures the hardware's actual clock, display contention, interrupt behavior, and drawing costs, then builds the desktop around those limits.
That makes ZXdesk more than a visual trick. It is a compact case study in systems work: when the machine is small enough, every abstraction eventually has to explain itself in bytes and clock cycles.
The desktop fits because the measurements changed the design.
A real desktop-shaped system
Hackaday describes ZXdesk as perhaps the closest thing to an operating system seen on the classic rubber-key Spectrum. The qualifier matters. A stock 48K Spectrum has no disk, so this is not a conventional general-purpose operating system that discovers and launches arbitrary programs from storage. Its applications ship with the desktop.
Inside that boundary, however, the structure is surprisingly complete. Windows overlap, keep a z-order, receive focus, move, and resize. A permanent menu bar opens pull-down menus. Input from the keyboard, Kempston joystick, and Kempston mouse becomes events in a 16-slot queue. Applications have descriptors with initialization, event, and paint entry points, plus per-instance state. Storage sits behind a registry of interchangeable backends for RAM, tape, spare memory banks on a 128K machine, and a reserved path for esxDOS.
There is also a real heap: 8,112 bytes, with allocations owned by the windows that use them. That sounds tiny until you remember the entire machine has 49,152 bytes of RAM and part of that space must hold the display, system variables, program, stacks, buffers, application state, and the desktop itself.
3.5 MHz Z80
48K total RAM
69,888 T states per 48K frame
8,112-byte desktop heap
16 queued input events
50 Hz window draggingThe frame is the scheduler
A 48K Spectrum frame lasts exactly 69,888 T states. ZXdesk uses that fixed interval as its most trustworthy clock. The main loop synchronizes on the interrupt, performs pointer work in the top border, waits for the video beam when a window has moved, redraws, then reads and dispatches input at the end of the frame.
This is not decorative timing trivia. The Spectrum's ULA shares memory with the processor and steals cycles while drawing the display. Code below address $8000 can therefore slow down depending on where the beam is. ZXdesk deliberately puts work that must not run during a frame into the contended region, while the drag path, pointer path, interrupt handler, and other timing-sensitive code live above it.
The project also measured an important piece of Spectrum folklore. It started with a pessimistic assumption that display contention imposed a 50% penalty. Repeated fills placed at different parts of the frame showed a worst-case penalty of 14.7% instead. That did not make the machine fast, but it made the budget honest. Designs based on the old estimate were spending scarce complexity to solve the wrong problem.
Drawing less beats drawing faster
The first complete window move cost 96,010 T states, or 137% of a frame. That limited dragging to about 25 Hz. ZXdesk brought the path under budget through several measured changes.
- Compose once, then copy. An off-screen window buffer reduced redraw cost from 71,274 to 26,048 T states.
- Fix the expensive text path. Reworking the pixel text renderer cut the cost per character from 1,307 to 622 T states.
- Erase only the exposed strip. Damage rectangles reduced an erase from 19,664 to 1,456 T states.
- Use a narrow fill for the desktop. A specialized two-byte column path cut that operation from 14,256 to 5,712 T states.
- Follow the beam. Scheduling around the display position saved a further 16,128 T states.
The resulting drag frame ends at 59,858 T states and runs at 50 Hz. The sequence is a useful corrective to optimization by instinct. The author expected fills to be the main problem. Measurement showed that drawing three short strings consumed half the cost of a window, while fills accounted for about a fifth. A week spent polishing the presumed hot path would have bought little.
An interrupt can simply disappear
The most interesting discovery sits below the desktop itself. Some fast fill routines temporarily used the stack pointer to write screen memory and disabled interrupts while doing so. Received wisdom held that the Spectrum would delay an interrupt until interrupts were enabled again.
It does not. The machine asserts its interrupt for only 32 T states. If a disabled interval covers that pulse, the interrupt is lost rather than deferred. Briefly enabling interrupts between rows still leaves most instruction boundaries uncovered, so it only makes the failure less predictable.
ZXdesk's fix was to run the fills with interrupts enabled and make an interrupt's automatic stack write harmless. The routine leaves the final two bytes of each row unwritten until the stack pointer has moved into the next row. If an interrupt arrives, its return address lands in pixels the next step was going to replace anyway. Tests across every instruction boundary took the fill paths from hundreds of lost-interrupt positions to zero.
That repair costs roughly 48 to 50 T states per row. It is slower and correct, which is often the right outcome in low-level work. The project had to understand not merely that interrupts were involved, but the electrical duration of the signal and the exact memory side effect of accepting it.
Tests shaped for the machine
ZXdesk includes a scripted-input mode that can open a menu, choose an item, drag a window, type into a field, and save the result the same way on every run. Screen checksums catch rendering drift. A mouse diagnostic separated an emulator input problem from desktop behavior. Calendar results are checked against Python's datetime, while timing runs compare routines against the Spectrum's own frame clock.
Even the clock refuses a convenient approximation. The 48K model interrupts at about 50.0801 Hz, not exactly 50 Hz. Counting every 50 interrupts as one second would gain more than two minutes per day, so ZXdesk occasionally waits for a 51st interrupt to keep time. The 128K model gets a different correction because its frame is different.
These tests are not a modern harness pasted onto old hardware. They use stable properties of the machine as oracles: its frame period, memory map, pixel output, and reproducible input stream. The project is small enough for those properties to stay visible.
Constraints can improve the architecture
It is tempting to frame projects like this as stunts: look what somebody forced an old computer to do. ZXdesk is more interesting as design work. The device layer isolates the Spectrum's unusual screen layout. Events keep the main loop free of window-specific logic. Storage backends sit behind six vectors. Application instances separate behavior from state. The memory map documents which work can tolerate contention and which cannot.
None of those ideas are new. The sharpness comes from having nowhere to hide their cost. A registry entry is counted in bytes. A dispatch is counted in T states. A redraw must finish before a beam returns. A lock around the wrong routine can erase time itself.
Modern systems give us more room, but not freedom from budgets. Latency still accumulates, caches still contend, interrupts still have semantics, and benchmarks still measure the wrong thing when their setup omits real overhead. ZXdesk makes those truths visible on a machine where one frame is 69,888 ticks and every one of them has a job.

// Discussion
Comments
No comments yet. Start the thread.