Releases

Every tagged build of os8088, newest first, with the same notes that go on the GitHub releases page. Each one is four raw floppy images built from a single commit, booted in an emulator before it was published.

Builds before the first tag are not listed: the project was published as images on the download page without a release behind them. The download page always serves the newest release, with checksums.

v1.0.20260808 latest

v1.0.20260808

Released 2026-08-08 from ca8211243 · on GitHub

The disk stops being a floppy. os8088 installs onto a hard disk and boots from a partition, mounts every FAT partition it finds, and a document on any volume opens its own program when you double-click it -- so the system disk, the software disk and a 40MB drive are all just places files live. Three packages join: Missile Command from Atari's own 1979 sources, TameGram from Jason Page, and ModPlug Player, whose face is drawn from an invalidation mask and costs a tenth of the glyphs it used to. The File Manager gets Cut, Copy, Paste and drag and drop, with copies that stream rather than fit in memory. Mono text drawing is ~30% faster. And the kernel grew a debugging story it never had: a serial monitor that ships as a loadable driver, and an emulator-side debugger the guest cannot feel.

  • Hard disks: install it, and boot what you installed #61
    SPEC.md 52 lands in full. The kernel mounts every FAT partition it finds, an installer writes os8088 onto a drive, and the machine boots from that partition rather than from A: -- then mounts what it booted and asks for one disk instead of two. Rung 0 is the controller ROM, which is what an XT actually has, and it is the rung QEMU cannot test at all: the verification is 86Box emulating st506_xt_st11_m, the calibration machine's own controller. A pre-merge safety pass closed six hard-disk defects and the FAT window's write half before any of it shipped.
  • A document opens its program #61
    ASSOC.DAT maps an extension to the package that handles it, and says where that package lives -- so one mount of a volume's root seeds associations for the whole disk, on any volume rather than just the two floppies. Double-click a .MOD and the player opens it. Coming back from a document open is a file operation like any other (SPEC.md 54.9), so it reports itself on the menu bar and leaves the current directory where the instance had it.
  • Missile Command, the twelfth package #53
    Ported from Atari's original W3MAIN / W3DSUP / W3COMN 6502 sources -- "WWIII", project 23603, July 1979. The numbers are the arcade's and not re-invented: six cities and three bases at their original coordinates, ten ABMs a base, satellites and MIRVs on the arcade's own wave counters, and SETICS scoring with the 10,000-point bonus city. No kernel change and no heap claim -- every array is sized by the arcade's object counts and fits the package bss. Windowed, or on the fullscreen surface with F and Esc. Two bugs it cost first are worth the telling: a trail drawn in per-frame segments and erased as one line does not erase (the two Bresenham rasterisations differ by a pixel, leaving half of every dead missile on screen), and a permanent refusal coded like a transient one hung the wave forever on a perfectly live machine.
  • ModPlug Player, the fourteenth package -- and a face drawn from dirty bits #58
    ModPlug Player V2's look and feel: the skinned window with its green LCD, LED transport row, scrubber, option-button grid and spectrum pane, plus a Setup window and a PlayList editor. The interface is the port; the replayer is this tree's own, because ModPlugPlayer's real playback is libopenmpt and no 8086 runs that. The reason it keeps time at all is SPEC.md 52.12: every wake used to redraw 158 glyph cells, which at a millisecond a cell is 158ms of work inside a 55ms frame -- the player could not keep its own clock. Drawing is now gated on a word of dirty bits, the period's update-region idea applied at the widget. Measured with counters, over the same ten seconds of playback: 28,365 glyph cells down to 2,468 (11.5x), gfx_fill calls 12,856 down to 4,130, and 158 cells a frame down to 13.7.
  • TameGram, the thirteenth package #55
    A four-direction dual-faction containment matrix, designed and originally implemented by Jason Page and credited in the app's own About panel. Units fall from the centre of a 32x32 matrix toward one of four edges; eight contiguous same-faction cells purge, and what survives settles along the vector until nothing more clears. The port is a catalogue of the three mistakes this ABI invites: six callbacks ending in retf (which assembles perfectly and hangs at the first paint), a reachable out-of-bounds grid write where a worker updated lock-free while W_PAINT ran inside the same scratch words, and drawing outside the window, because the gfx primitives clip to the screen -- on CGA the matrix hung 19 rows through the bottom of its own window.
  • The File Manager gets Cut, Copy, Paste and drag and drop #54
    With a recursive paste engine, so a folder pastes its whole tree. A copy is a stream now (SPEC.md 22.5), so the size of a file is no longer bounded by what fits in memory, and a move inside one volume is three sector writes rather than a copy and a delete (22.6). Underneath, one read and one write with no 64KB ceiling on either (18.4.1). The listing gained a .. row in both views and in the file dialog, and the mount snapshot sorts by name.
  • Note Pad stops walking the note, and ~30% off mono text #54
    A keystroke used to cost a walk of the whole document and a repaint of the band; it now draws the cells that changed, records where each row starts so a caret key costs a row, scrolls by moving the pixels it already has, and keeps the note in a heap claim that grows instead of 512 bytes of bss. The general win underneath it is font_run (SPEC.md 6.1): the erase-and-letter pair as one operation, one decision per cell, so a line is never momentarily blank. Measured on mono: 1.26x fewer instructions and 2.85x less framebuffer traffic. WF_SNAP is the other half -- an opt-in, mono-only flag that keeps a window's content on a byte boundary so the run never straddles.
  • Two debuggers, because 86Box has no automation socket and real iron has none at all #61
    DEBUG.DRV is a serial monitor that reads and writes memory and I/O ports on a running machine -- and it is a driver, not a SERDBG= kernel build, which is the whole point: a knob kernel is a different binary, so the thing you debugged is not the thing that ships. It costs a machine that never loads it one table row and a file it can delete. It takes COM4, the port the kernel has never heard of, and tests that it may have IRQ3 by reading the mouse driver's own state rather than assuming. Alongside it, a MartyPC-side debugger that costs the guest not one cycle and does three things a guest-side stub structurally cannot: breakpoints, single-step and real cycle counts. On an emulator it supersedes the driver; the driver stays the only half that works on a 5150.
  • 86Box targets for the fast end of the range #59
    make 486 (486DX2/66) and make pentium (P54C/133) join the register. 8086 real-mode code runs verbatim on both, so what they are for is the other end of the timing range -- every constant in this tree was sized while looking at a 4.77MHz 8088, and QEMU models no clock speed at all. Recording the trap they cost: 86Box silently replaces an unrecognised cpu_family rather than rejecting it, so a config saying pentium boots a 75MHz P54C while still claiming 133. The name that takes the multiplier is pentium_p54c.
  • The repository stops shipping its own build output #56
    35 artifacts under build/ had been force-added past the gitignore -- the kernel, both boot sectors, all four floppies and every package. The staleness check that guarded them worked; the reason to stop is that the cache carried no information. The toolchain is deterministic on purpose (tools/os88disk.py pins the volume serial and every FAT timestamp), so make reproduces any of those bytes exactly, and a committed copy bought a correctness obligation for nothing. The images live where a version can be attached to them: this page, and the GitHub release. tools/setup-macos.sh (#57) installs the toolchain on a Mac, including the 86Box ROM set nobody remembers.

The Disk window and the file dialog now list .. as the first row, and entries sort by name rather than the order they were written to the disk. Both disks also carry SYSTEM/ and MEDIA/ folders now, and README.TXT ships on the system disk. If you have scripted anything against row positions, they moved.

os8088 can be installed onto a hard disk and booted from a partition. It mounts every FAT partition it finds -- which means a partition it can see is a partition it can write to, so point the installer at a drive you are willing to lose.

The kernel image figure below jumped from 52,718 to 78,950 bytes, and that is a change of shape rather than 26KB of new resident code. KERNEL.SYS now carries three things: the resident image (54,272), the cold-start code that runs once and is then thrown away (22,016), and the boot overlay (2,662). Image + .bss -- the kernel's own 64KB segment -- actually fell, from 59,031 to 53,842.

The whole-kernel footprint is at its ceiling: KERN_SIZE is 90,112 bytes of a KERN_BUDGET of 90,112, with zero spare, and the three rungs have 430, 175 and 444 bytes left between them. The next feature pays for itself by shrinking something else, or by a deliberate decision to raise the budget.

Kernel image
78,950 bytes
Image + .bss
53,842 of 65,536 (11,694 free)
Source
112,792 lines of assembly across 35 kernel modules
Files in os8088 v1.0.20260808
FileWhat it isSize
os8088.imgBoot disk, 1.44MB geometry: 80 cylinders, 2 heads, 18 sectors per track. For QEMU.1,474,560 bytes
os8088-360.imgBoot disk, 360KB geometry: 40 cylinders, 2 heads, 9 sectors per track. For 86Box and real XT hardware.368,640 bytes
apps.imgSoftware disk, 1.44MB: a FAT12 volume holding APPS (ARTFUL.O88, FRACTAL.O88, HELLO.O88, MODPLUG.O88, NOTEPAD.O88, PAINT.O88, PIANO.O88, RECORDER.O88, TRACKER.O88), GAMES (ARKANOID.O88, MINES.O88, MISSILE.O88, SOLITAIR.O88, TAMEGRAM.O88), MEDIA (BEVERLY.MOD), SYSTEM (TASKMGR.O88) and ASSOC.DAT. Not bootable.1,474,560 bytes
apps360.imgSoftware disk, 360KB. The same 15 packages, same filesystem, different geometry.368,640 bytes

The download page serves these same four images with their SHA-256 checksums: os8088.com/download.

v1.0.20260804.1

v1.0.20260804.1

Released 2026-08-04 from aad482530 · on GitHub

An experimental branch comes home, and it changes what the system is made of. Memory stops being a floor plan and becomes a heap: everything above the kernel is handed out on demand, which retires the fixed package pool and drops the RAM floor from 256KB to 128KB. A driver becomes a file you can put on a floppy -- the sound card's is the first, loaded by name from a settings file that also holds the whole Control Panel, because the system disk is a real FAT12 volume now. Repainting stops meaning the screen: closing a window costs the rectangle it vacated, raising one costs a title bar, and renaming one costs a strip. And the eleventh package is ArtfulType, a distraction-free Markdown writer that takes the whole screen and draws its own Macintosh menu bar on it.

  • ArtfulType, the eleventh package #49
    A port of ActionRetro's ArtfulType -- the distraction-free Markdown writer for classic 68k Macintoshes -- onto the fullscreen surface. Windowed it is a splash card; in Writer mode it takes the whole screen and draws its own Macintosh menu bar on it, black with white titles, tracking press-drag-release pull-downs with drop shadows, right-aligned shortcuts and the triple selection flash. The document is canonical Markdown in a 20KB gap buffer and the styling is a rendering property, not a second buffer: styled lines hide their delimiters and draw from the package's own glyph engine -- bold by overstrike, italic by shear, headings bit-doubled to two and three times size, underlined links with the URL hidden, code cells dithered -- while the paragraph the caret is in renders raw and collapses again when the caret leaves. The performance contract is what makes it usable at 4.77MHz: a keystroke is one gap store, a one-paragraph relayout and one blit per repainted line. Nothing on the typing path walks the document. It needed no kernel changes at all -- the whole port sits on the API surface that was already there.
  • Loadable drivers: a driver is a package that is not an application #51
    Same 32-byte header, same flat binary, same paragraph-aligned heap claim, same three-byte dispatcher -- so a driver author writes ordinary near procedures and the kernel calls them exactly as it calls a window's. Four things make it a driver instead: it is a .DRV file (the mount types only .O88 as an application, so it can never be double-clicked into the loader), its header version is 4 (a second, independent gate), it has no instance record, and its bss ships inside its image -- which is what lets the kernel make exactly one claim, at the size the directory entry already reported, before a byte is read. It publishes a service table the kernel copies into its own memory at attach, so the timer interrupt can ask whether there is sound work to do without touching a segment register. SOUND.DRV is the first one, and publishing its tone cell moves the tone tier off the PC speaker -- an FM note becomes two register writes and then no CPU at all. Nothing here can stop the boot: no disk, no file, no card and no memory are all recorded and reported afterwards, on the Control Panel's new Drivers page.
  • The claim heap, and a 128KB floor #51
    Everything above the kernel is now handed out on demand: ask for so many kilobytes, get a segment back, and give it back when the instance dies. That retires the fixed 60KB package pool -- a package's region is an ordinary claim taken from the top down, because a region's base is its code segment and can never move, while a data claim can -- and returns those 60KB to every machine. The RAM floor drops from 256KB to 128KB. The kernel is a client of its own heap too: the menu save-under is claimed for exactly as long as a menu is on screen, and the 150KB double-buffer is claimed when you switch it on and freed when you switch it off -- which is why the Control Panel's Display row now greys out and comes back as you open and close Paint. Refusal is a normal path everywhere, not a panic: every claim in the tree has a fallback.
  • Repainting costs a rectangle, not the screen #51
    Three things that used to repaint the whole desktop no longer do. Coming to the front reveals nothing -- the window moves up, so every other window's covered area can only grow -- so raising a window draws that window and the outgoing front window's title bar, and a click on a background window's title bar now costs two title bars. Going away costs the rectangle you vacated: hiding, closing and dragging repaint the desktop, the drive icons and the windows clipped to the damaged rect, with a marking pass that also catches every window overlapping a window already marked. Retitling costs a strip -- a caption changes on an event, after the frame carrying it has already been drawn, so there is now a call that redraws the title row and nothing else. A window closing on the left of the screen no longer redraws a window on the right.
  • The system disk is a FAT12 volume, and it remembers #51
    Drive A: mounts, browses and writes like any other volume -- the boot sector's raw read of the kernel's sectors is untouched, because those sectors are simply the reserved region the BPB already describes. SYSTEM.CFG in its root is 32 bytes carrying the whole Control Panel: the driver list, the sound route, the clock options, the scheduler mode and the back-buffer setting, rewritten on every click and restored at boot. A missing or malformed file means the defaults, never an error.
  • A clock ladder, because the BIOS is the last rung #51
    An XT BIOS implements two of the six clock calls, so on a 5150 with a clock card sitting right there the BIOS knows nothing about it -- and a BIOS that can read the clock may still quietly ignore the two calls that set it. The kernel now probes four rungs in an order chosen so that no rung writes to a chip a later rung would have identified differently, obeys each chip's own BCD and 12/24-hour settings rather than rewriting them behind the machine's BIOS, and bounds every wait loop -- the one way to hang here is to wait forever for a bit that never changes on a machine where every read is 0FFh. The Control Panel names the rung that answered, because on a machine whose clock will not hold a setting that is the whole diagnosis.
  • CPU tiers and memory above 1MB #51
    The kernel identifies the processor it is running on and, above the 8086, can find the A20 line and the store above 1MB. On the target machine all of it is zero kilobytes and every entry point returns having touched no port. The Task Manager reports it as one line below the memory map rather than a map of its own -- real mode has no address for it, so it cannot honestly appear in either map above.

A machine with no sound card now opens the Control Panel on its Drivers page at boot, saying what could not be attached and why. That is the driver system reporting itself, not an error -- nothing a driver does can stop the boot -- and turning the driver off on that page makes it stop.

Settings persist now, in SYSTEM.CFG on the boot floppy. If you run os8088 from a write-protected disk, every Control Panel change still applies immediately and none of them survive a reboot.

The kernel has grown a lot this release: 59,031 bytes of the 65,536 a single segment holds, against 47,681 last time. 6,505 bytes of headroom left.

Kernel image
52,718 bytes
Image + .bss
59,031 of 65,536 (6,505 free)
Source
66,183 lines of assembly across 29 kernel modules
Files in os8088 v1.0.20260804.1
FileWhat it isSize
os8088.imgBoot disk, 1.44MB geometry: 80 cylinders, 2 heads, 18 sectors per track. For QEMU.1,474,560 bytes
os8088-360.imgBoot disk, 360KB geometry: 40 cylinders, 2 heads, 9 sectors per track. For 86Box and real XT hardware.368,640 bytes
apps.imgSoftware disk, 1.44MB: a FAT12 volume holding APPS (HELLO.O88, NOTEPAD.O88, PIANO.O88, FRACTAL.O88, PAINT.O88, RECORDER.O88, TRACKER.O88, ARTFUL.O88, BEVERLY.MOD) and GAMES (MINES.O88, SOLITAIR.O88, ARKANOID.O88). Not bootable.1,474,560 bytes
apps360.imgSoftware disk, 360KB. The same eleven packages, same filesystem, different geometry.368,640 bytes
v1.0.20260804

v1.0.20260804

Released 2026-08-04 from b401eda0c · on GitHub

Three programs arrive at once -- Solitaire, Arkanoid and an FT2-style MOD player -- taking the software disk from seven packages to ten, which is also why the disk now has folders. Each of the three left something behind in the kernel, because a card game that drags a fan of cards, a game whose ball has to keep moving between keystrokes, and a player that has to keep a sound card fed while it redraws a full screen all needed something the API did not yet offer. The application menu bar gained the one cell it was missing: an About item under the program's own name, where a Macintosh has always kept it.

  • Solitaire #44
    Klondike, ported from the same fork that contributed Paint: three-card draw, a stock that recycles, and the full set of tableau and foundation moves. It is the one package that drags the way the window manager does -- a held card is an XOR outline that erases before the drawing lock drops, so a hand of cards costs a few inverted strips per timer tick and nothing repaints until you let go. Card faces are drawn from primitives, but the backs are blitted: the lattice is rendered once into a packed 4bpp image and every later back is a single gfx_blit4 call instead of hundreds of far calls across the package boundary. On a Hercules or CGA machine the red pips go hollow rather than red, because the red index reduces to white and a filled pip would vanish into the card face -- the shape carries the color when the color cannot.
  • About moved under the application's name #44
    The menu bar has carried the active application's name since the menus became per-app, but the name was only a label. It becomes a real one-item menu -- About <Name> -- the moment a window registers a handler through the new OSAPI_ABOUT_SET call, which is where a Macintosh application has always kept its About box. The cell is appended to the bar rather than inserted, so every application menu keeps the index it had and nothing downstream of the dispatch had to change. Locator never grows one: its About is already in the chip menu, where the system's own About belongs.
  • Arkanoid #45
    A brick-breaker, and the first package whose game loop is its worker task -- one frame per tick of sleep, running whether or not you touch the keyboard, because a ball has to keep moving between keystrokes. Everything the UI task does is set a word the worker reads. Two things it discovered are worth knowing before writing another real-time program here. The BIOS keyboard has no key-up event, so a held arrow key has to be inferred from typematic repeat: each press refills a deadline that must outlast the roughly half-second typematic delay, or a held key stalls and reads as a dropped keyboard. And OSAPI_SND_TONE turns out to be safe to call from a worker -- the kernel stamps the grant with the running task's own instance when no callback is being dispatched -- which the SDK had never said.
  • The software disk has folders #45
    Ten packages is more than a flat 32-entry root wants to show at once, so the apps disk now keeps them in two folders: APPS for the tools and GAMES for the games, and nothing else in the root. The Disk window already knew how to walk a subdirectory -- it grew that with the FAT write path -- so this is a change to what is on the disk rather than to the file manager. The visible consequence is that a program is now two double-clicks away instead of one, and the Folder menu's Up One Folder and Root Folder are how you get back.
  • Tracker, an FT2-style MOD player #46
    A four-channel ProTracker MOD player with a FastTracker II screen: pattern view, live VU bars, and a song that keeps playing while you scroll. The window it opens is only a splash card -- any key or click takes it fullscreen, F toggles and Esc comes back. The audio is the interesting half: the UI task opens and closes the stream, and the worker mixes and feeds it, handing the sound card a 16KB ring buffer that is refilled a half at a time rather than a clip that has to be complete before it starts. Playback runs at 11, 22 or 44 kHz, and there is an XT mode sized for what a 4.77MHz 8088 can actually mix in real time. It ships with BEVERLY.MOD, a 116,085-byte module -- which is what forced the next item.
  • What the Tracker needed from the kernel #46
    Three amendments, all of them things the player could not fake. Ring-mode streams: the sound stream verbs learned an open flag that makes a grant a circular buffer with free-running counters, so a stream can be fed forever instead of being staged whole up front -- and the feed verb is safe to call from a worker task. Wide rates: a DSP 4.00 or better now opens up to 44,100 Hz instead of stopping at the 22,222 Hz the timing-constant range allows. OSAPI_FILE_READBIG: the ordinary read call takes a 16-bit byte count, so any file of 65,536 bytes or more failed outright; BEVERLY.MOD is 116,085. The API table went from 57 far slots to 62, all appended.

Packages built for the previous release still run. The API table grew from 57 slots to 62 and every new one is appended, so nothing was renumbered and the format is still v3.

Programs are two double-clicks away now. The software disk's root holds only the APPS and GAMES folders. Minesweeper is GAMES then MINES.O88; the Note Pad is APPS then NOTEPAD.O88. Folder > Up One Folder and Folder > Root Folder navigate back out.

The Disk window's size column is 16-bit and clamps at 65535, so BEVERLY.MOD lists as 65535 rather than its real 116,085 bytes. Only the display is clamped -- the file loads in full.

Sound hardware is what decides how the Tracker sounds. 44 kHz needs a Sound Blaster 16 or another DSP 4.00 part; older cards top out at 22 kHz, and the XT mode exists for machines that cannot mix faster than they can play.

Recorded as unexercised: 86Box -- the XT targets and the three AT-class machines -- and the 256KB floor, where the arena is empty and no package can load at all. QEMU is the only environment run routinely. Verified there: all ten packages load from their folders, Solitaire deals and drags, Arkanoid plays with sound from its worker, and the Tracker loads BEVERLY.MOD through readbig and plays it fullscreen. The XT mode and the 44 kHz path were not verified on period hardware.

Kernel image
47,617 bytes
Image + .bss
47,681 of 65,536 (17,855 free)
Source
58,243 lines of assembly across 31 kernel modules
Files in os8088 v1.0.20260804
FileWhat it isSize
os8088.imgBoot disk, 1.44MB geometry: 80 cylinders, 2 heads, 18 sectors per track. For QEMU.1,474,560 bytes
os8088-360.imgBoot disk, 360KB geometry: 40 cylinders, 2 heads, 9 sectors per track. For 86Box and real XT hardware.368,640 bytes
apps.imgSoftware disk, 1.44MB: a FAT12 volume holding APPS (HELLO.O88, NOTEPAD.O88, RECORDER.O88, PIANO.O88, FRACTAL.O88, PAINT.O88, TRACKER.O88, BEVERLY.MOD) and GAMES (MINES.O88, SOLITAIR.O88, ARKANOID.O88). Not bootable.1,474,560 bytes
apps360.imgSoftware disk, 360KB. The same ten packages, same filesystem, different geometry.368,640 bytes
v1.0.20260803.1

v1.0.20260803.1

Released 2026-08-03 from cef5bf3bf · on GitHub

Paint arrives -- a bitmap editor with eight tools, an undo that doubles as redo, and BMP and GIF files it reads and writes off the floppy -- and the kernel grows the thing Paint needed in order to exist. Under the old rules a program that wanted a quarter megabyte to draw on took it by picking an address and hoping nothing else wanted it; now it asks, and the arena that hands out program segments hands out plain memory from the same single first-fit walk. A window can also resize itself for the first time, which is what lets a canvas refuse a shrink that would crop the picture. Along the way the floppy driver learned that the DMA controller cannot cross a 64KB boundary -- a rule it had been getting away with ignoring only because every buffer before this one happened to be aligned.

  • Paint #43
    A bitmap editor contributed as a fork of os8088 by github.com/Elendilon: eight tools over a 4bpp offscreen canvas, one level of undo that doubles as redo, an internal clipboard, and BMP and GIF load and save through the Standard File dialog. At 14,112 bytes it is by some distance the largest package that ships -- the six before it ran from 323 bytes to 2,774. Its canvas, undo image, clipboard and scratch space are one allocation, sized from the largest free run the kernel reports, and it deliberately leaves 64KB behind when doing so still funds the top size tier: a canvas plus an equal-sized undo image otherwise eats a 233KB arena whole and nothing else can load while Paint is open. Two copies of Paint get two canvases, the second tiered down to fit.
  • A package can ask the kernel for memory #43
    Three new calls -- OSAPI_MEM_ALLOC, OSAPI_MEM_FREE and OSAPI_MEM_AVAIL -- hand a package a plain segment out of the same conventional-memory arena that loaded programs come from. A package's own region caps at one segment and holds its image and its static data, so an application whose working set is hundreds of kilobytes had nowhere to put it. What matters about the implementation is that there is still only one allocator: the loader's region search is now a jump into the grant allocator's first-fit walk, which rejects a candidate overlapping any package region or any grant. Two allocators over one arena that searched separately would eventually hand out the same paragraph. Grants are stamped with the instance that asked and force-freed at all three teardown paths, which is what makes the call safe for a package that owns no task and is therefore never told its window is closing.
  • The segment a package was already executing in #43
    The loader reserves a package's region early but does not publish the instance record until after the entry procedure returns -- and the record is the only evidence the allocator has that a region is taken. So for the whole of that call, the loading package's own memory read as free. Paint was the first package to allocate from its entry, and it was handed the segment it was running in: it filled its new canvas with white and the machine wedged mid-repaint on the first 0xFF opcode, with the drawing lock still held. There is now an explicit reservation covering that window, written with MOV only so the entry procedure's carry flag still reaches the loader.
  • A window can resize itself #43
    OSAPI_WM_RESIZE retires the last place where a package wrote a kernel record behind the kernel's back. The two things that could resize a window -- the grow box and full-screen -- were both kernel-internal, so an app whose content has a size of its own, a picture just opened or a canvas whose shrink must be refused because it would crop artwork, had no legitimate way to say so. The call clamps to the minimum window size and to the screen, re-clamps the position, and draws nothing: the caller is inside its own callback and knows what it is about to repaint.
  • One sector per call is not enough: the 64KB DMA page #43
    Opening a saved BMP answered Disk error, while the save that produced it had worked and the file was byte-exact to a host reader. The floppy driver transfers one sector per BIOS call and had concluded from that -- in writing, in the spec -- that DMA alignment could never matter. The first half of that is true and the second is not: the DMA controller's page register does not increment, so a transfer of any length may not cross a 64KB physical boundary, and a BIOS asked to do it anyway refuses rather than wrapping the buffer. It hid for years because every disk buffer in the tree before this one was 512-aligned by accident. The first that was not was the first to come from an arena grant, where a base can be any paragraph. The driver now detects a sector that would straddle and stages it through a buffer of its own -- at most one extra 512-byte copy per 64KB transferred. This is the kernel's obligation and not the caller's: an application cannot be asked to know about the 8237.
  • The Task Manager counts what was granted #43
    The RAM line, the arena caption, the per-row size column and the memory map's coloured bands all include grants now. A grant is routinely an order of magnitude larger than the package region beside it, and leaving it out drew a one-pixel sliver where a quarter of the machine had gone.
  • About lives under the app's own name #43
    Paint puts a pull-down exactly where the menu bar draws an application's name, which is where a Macintosh keeps About. It needs no kernel change -- a menu set whose name is the empty string and whose first menu is titled after the app does it -- and the dock tile, the Task Manager row and the loader all keep reading the name out of the package header. The About window is a second instance-less window of the file dialog's species, and its command is dispatched ahead of the check for whether there was enough memory to start, so a machine too small to fund a canvas can still be told what the program is and who wrote it.

Packages built for the previous release still run. The API table grew from 53 slots to 57 and the four new ones are appended, so nothing was renumbered and the format is still v3.

Paint sizes itself to the memory it can get. On a 640KB machine it opens full size; with less free arena it tiers down, and on a machine with no arena at all it puts up a notice instead of a canvas. Two instances is normal and the second is smaller by design.

The disk fix matters beyond Paint. Any file read or written into a buffer that a package allocated could previously fail with Disk error depending on where in memory that buffer happened to land. If you have a package of your own that stages file I/O anywhere other than its own static data, this release is the one that makes it reliable.

Recorded as unexercised: 86Box -- the XT targets and the three AT-class machines -- and the 256KB floor, where the arena is empty and Paint should show its notice. QEMU is the only environment run routinely, and everything above was verified there: launch, draw, resize, a Save As to a 320x280 4bpp BMP that a host reader accepts, close with the grant released, two instances, and Minesweeper loading beside a full-size Paint. The file-system gate package passes 21 of 21 on plain FAT12, a deliberately fragmented volume and FAT16.

Kernel image
45,595 bytes
Image + .bss
45,586 of 65,536 (19,950 free)
Source
49,298 lines of assembly across 31 kernel modules
Files in os8088 v1.0.20260803.1
FileWhat it isSize
os8088.imgBoot disk, 1.44MB geometry: 80 cylinders, 2 heads, 18 sectors per track. For QEMU.1,474,560 bytes
os8088-360.imgBoot disk, 360KB geometry: 40 cylinders, 2 heads, 9 sectors per track. For 86Box and real XT hardware.368,640 bytes
apps.imgSoftware disk, 1.44MB: a FAT12 volume holding MINES.O88, HELLO.O88, NOTEPAD.O88, RECORDER.O88, PIANO.O88, FRACTAL.O88 and PAINT.O88. Not bootable.1,474,560 bytes
apps360.imgSoftware disk, 360KB. The same seven packages, same filesystem, different geometry.368,640 bytes
v1.0.20260803

v1.0.20260803

Released 2026-08-03 from a999aef41 · on GitHub

Packages move out of the kernel's 64KB segment and into segments of their own. A loaded package used to live in a 19,968-byte pool carved out of the kernel, which meant three copies of the fractal renderer would not fit -- and never could have. Format v3 gives each one a paragraph-aligned region from a conventional-memory arena instead, so five run at once where two was the ceiling; the pool's bytes go back to the kernel, which now has 20,756 free where it had 1,579. Alongside it the kernel learns what CPU it is running on, opens the A20 gate, and can reach memory above 1MB as a data store -- and three new emulated AT-class machines run the same 8086 binary on a 286 and two 386s.

  • Five fractals where two was the ceiling #41
    One instance of FRACTAL.O88 costs a 7,168-byte region -- 2,774 bytes of image and 4,394 of working storage -- and the package pool held 19,968 bytes at kernel offset 0xB000. The third launch answered Out of memory, and no amount of care inside the fractal was going to change that: the pool was a fixed slice of a 64KB segment the kernel also had to fit in. Under format v3 each loaded package gets a whole-paragraph region from an arena in conventional memory, first-fit, with occupancy derived from the instance table that was already tracking it. The arena is about 233KB on a 640KB machine and about 107KB at 512KB. Five concurrent fractal instances now run and were confirmed from the dock.
  • Where the arena starts, and why it is not convenient #41
    The arena begins at linear 0x65800, one paragraph past the end of the optional back buffer's pinned 0x40000..0x657FF. Starting lower would have given packages more room, and would also have let a package load and a Display toggle in the Control Panel fail each other at run time depending on which happened first. A smaller arena with a fixed boundary is the better failure: on the 256KB floor there is no arena at all, and a package load refuses with a message that says so while every built-in runs as it always has.
  • The API goes far, and callbacks resolve their own segment #41
    A package in its own segment can no longer near-call the kernel, so all 53 jump table slots become far: the stride goes from 4 bytes to 8 and the sequence is push ds / push cs / pop ds / call / pop ds / retf. Neither pop ds nor retf touches the flags, so the slots that answer in the carry flag keep their contracts unchanged, and slot order is preserved. Window callbacks took the opposite route -- they stay near offsets, and a new per-window stamp records which segment created the window, so the dispatcher resolves far for a package window and near for a kernel one. That keeps the window record the size it was, at the price of one rule: teardown has to sweep every window stamped with a dying package, because a callback resolved this way must not outlive its segment.
  • Marshalling at the boundary, not segment overrides everywhere #41
    The kernel can no longer follow a package's pointer through DS, and the tempting fix -- thread a "which segment" answer through font_str, the menu tracker and the filename parsers -- would pile onto an ES already carrying save-unders, directory caches, the FAT snapshot, sound and disk buffers. So the kernel copies at the boundary instead, the way it has always staged directory entries. Strings land in bounded buffers with a stated capacity and a defined truncation; the entire menu set is copied whole at relayout, which retires the nested-pointer traversal in one move. Package data is treated as hostile input, because it is.
  • Relocation is gone #41
    At org 0 on a paragraph boundary the segment base does the work the old class 0 relocations did, and far calls with absolute immediates do class 1's. So the relocation table goes away, and with it os88pkg.py's trick of assembling each package twice at two different origins and diffing the results to find the words that needed patching. The build is one assembly per package now, and the tool is validate-and-stamp. The v2 rule that a package author could only relocate whole 16-bit words is retired along with it.
  • The kernel knows what CPU it is on, and can see above 1MB #41
    Two new modules. cpudet.inc distinguishes 8086, 286 and 386-or-later, opens the A20 gate through port 0x92 with a keyboard-controller fallback, and never believes it succeeded without a wraparound test. xmem.inc claims the high memory area, sizes extended memory with int 15h AH=88h, and copies through it -- AH=87h block move on a 286, unreal mode on a 386. Real mode addresses only up to 0x10FFEF, so extended memory is a data store and nothing else; package code stays below 1MB, which is why the package arena is in conventional memory. Unreal mode uses FS and GS precisely because no 8086 code in this tree touches them, so the widened limits survive every segment reload, and every 386 instruction sits in a scoped island behind a run-time tier check -- an %cpu directive is assembly-time permission, never proof that the chip is there.
  • Twenty thousand bytes back in the kernel #41
    The evicted pool's 19,968 bytes return to the kernel segment, and they pay for the far jump table, the marshalling buffers, the copied menu set and an icon cache with a great deal left over. The guard that binds -- image plus far-code blob against the 64KB segment -- goes from 1,579 bytes of slack to 20,756. The kernel image itself grew, from 41,739 bytes to 44,780, and for the first time in a while that is not the interesting number.
  • The same binary on a 286 and two 386s #40
    os8088 is 8086 code running in real mode, so a 286 or a 386 executes it verbatim -- and nothing in the tree said so, because every emulated target was an XT. Three 86Box configurations now do: an AMI 286 clone at 12.5MHz with 1MB, a Shuttle 386SX at 16MHz and a Micronics 386DX at 25MHz with 2MB each, all with ISA VGA and a serial mouse, all booting the 1.44MB images. The OS changes not at all, which is the point -- int 12h still answers 640K and the kernel never leaves real mode. What they buy is the other end of the speed range from the 4.77MHz XT, whose repaints you can watch happen.

Packages must be rebuilt. The package format is v3, and the loader rejects v1 and v2 files outright rather than trying to interpret them -- the API call sequence and the stride between jump table slots both changed. Rebuild against the SDK header shipped with this release; the source of a well-behaved package needs no edits, only one reassembly at org 0.

On a 256KB machine there is no package arena, so loading a package from the software disk now fails with a message saying so. This is not new behavior arriving -- a 256KB machine could not run much anyway -- but the refusal is explicit where it used to be an out-of-memory surprise. Every built-in still runs.

The extended-memory allocator and both of its transports are written, verified to assemble and to detect correctly, and have no caller yet. Nothing in this release stores anything above 1MB. Treat it as groundwork.

Recorded as unexercised: the 8086 and 286 code paths have no automated coverage, because QEMU emulates neither. The A20-less XT path and the 286's block-move copy need a run on 86Box, which is interactive. The three new AT-class machines also want one manual pass through their BIOS setup screen the first time -- they have a CMOS an XT does not, and it waits for EXIT FOR BOOT to be picked once per VM directory.

Kernel image
44,780 bytes
Image + .bss
44,719 of 65,536 (20,817 free)
Source
40,373 lines of assembly across 30 kernel modules
Files in os8088 v1.0.20260803
FileWhat it isSize
os8088.imgBoot disk, 1.44MB geometry: 80 cylinders, 2 heads, 18 sectors per track. For QEMU.1,474,560 bytes
os8088-360.imgBoot disk, 360KB geometry: 40 cylinders, 2 heads, 9 sectors per track. For 86Box and real XT hardware.368,640 bytes
apps.imgSoftware disk, 1.44MB: a FAT12 volume holding MINES.O88, HELLO.O88, NOTEPAD.O88, RECORDER.O88, PIANO.O88 and FRACTAL.O88. Not bootable.1,474,560 bytes
apps360.imgSoftware disk, 360KB. The same six packages, same filesystem, different geometry.368,640 bytes
v1.0.20260802

v1.0.20260802

Released 2026-08-02 from 7afa4de88 · on GitHub

A package can run in the background, and a covered window can keep drawing. Two API calls let a loaded package claim one worker task of its own, so two packages can be pre-empted against each other for the first time; a clip region replaces the boolean "am I covered?" veto that made every background painter freeze under a single overlapping pixel; and FRACTAL.O88 -- five escape-time fractals computed scanline by scanline while the desktop stays live -- is the package that needed both.

  • A package can claim a background task #36
    Until now a loaded package only ever ran inside its own window callbacks, on the UI task: it could be scheduled against nothing. Two slots in the kernel's jump table change that. OSAPI_TASK_SPAWN gives an instance exactly one worker task, and OSAPI_TASK_ALIVE is where that worker finds out it is being closed and dies. The teardown machinery already existed -- Clock and Bounce have been task-owned instances since they were built -- so a package simply becomes eligible for a path it had been excluded from. Two kernel-side rules hold it up. inst_pkg_spawn fences the caller's window pointer with an ownership test, because attaching a worker to a stranger's record puts both instances on the wrong teardown path: the stranger hides forever waiting on a worker it does not own, while the caller's own close frees the region its worker is still executing in. And task_spawn now runs its slot scan and its state publish under one cli, because this is the first time two different tasks can spawn at once.
  • A covered window can draw again #37
    wm_obscured answers a boolean, and every background painter used it as a veto -- one covered pixel and the whole frame was skipped. It had to be that blunt, because the drawing primitives take absolute screen coordinates and clip only to the edge of the screen, so a covered window that drew would paint straight over the window on top of it. The visible cost was a Bounce that froze under a corner, a Clock that stopped, and a fractal that gave up two minutes of work. wm_clip_set replaces the veto with a region: the window's content rect less every visible window above it, drop shadows included, as a list of up to sixteen disjoint rectangles, and while it is armed the six clipped primitives draw only inside it. Overflow past sixteen degrades to "skip this frame" -- exactly what the old boolean said -- so it cannot regress anything.
  • Fills clip per pixel; glyphs clip per whole cell #37
    That asymmetry is deliberate -- a character cell cannot be drawn half-wide, so a glyph that would straddle the region is dropped whole, the same way it is already dropped at a screen edge -- and it is the sharp edge of the whole feature. Anything that erases a rectangle and then writes text into it has to reconcile the two or it goes blank rather than stale: a Clock cut horizontally by another window's edge would get its visible rows white-filled and then no digits back in them, twice a second. There is a third call, wm_clip_test, so a caller can ask the glyphs' question before it erases anything -- the Clock now erases per 8x8 cell behind it, the fractal's status strip gates the erase and the text together. Solid drawing is unaffected, which is why Bounce needed no change at either end.
  • A repaint no longer destroys the picture #37
    A window's content has never been stored anywhere -- exposing one meant asking the application to compute it from scratch, which for a fractal is minutes. There is still no frame buffer, and there cannot be: the canvas is 27,200 bytes against a 19,968-byte pool shared by every resident package. What fits is the progressive first pass alone, one word per run of equal color -- color in the top four bits, last column in the low twelve, which cannot collide -- so 4,000 bytes covers the whole canvas at quarter vertical resolution. A repaint replays the cache and tells the worker to resume rather than restart, so an uncovered window comes back with its picture already on it and the render carries on from where it was.
  • FRACTAL.O88, the package that needed both #36
    Five escape-time fractals -- Mandelbrot, two Julia sets, Burning Ship and Tricorn -- through one fixed-point iteration core, four palettes, click to recentre and zoom, all chosen from the menu bar. It is the reference client for worker tasks: the worker computes each scanline with the drawing lock free and takes it only for a short blit, so the desktop stays fully live through a render, and two Fractals will render side by side while a Clock ticks and a window is dragged over both. It also ships on the software disk, which is now six packages -- and two Fractals plus Minesweeper plus Note Pad still fit the pool, at 19,456 bytes of 19,968.

A worker task must never return or exit on its own. If it does, its instance record and its slice of the package pool leak for the rest of the session, because the close path then sets a die flag that nobody is left to read. The loop calls OSAPI_TASK_ALIVE every pass and that call is where it dies -- this is written into the SDK's rules rather than left to be rediscovered.

Bounce now always steps, and only its erase and redraw are conditional, so a covered or minimized one turns up where it now is rather than where it was buried. The old rule that a hidden Bounce skips without stepping is retired.

Not landed, and recorded as such: the fractal's symmetry mirror, and non-destructive window moves in the window manager. A drag still repaints what it uncovers rather than blitting it.

Kernel image
41,739 bytes
Image + .bss
39,922 of 45,056 (5,134 free)
Source
37,184 lines of assembly across 28 kernel modules
Files in os8088 v1.0.20260802
FileWhat it isSize
os8088.imgBoot disk, 1.44MB geometry: 80 cylinders, 2 heads, 18 sectors per track. For QEMU.1,474,560 bytes
os8088-360.imgBoot disk, 360KB geometry: 40 cylinders, 2 heads, 9 sectors per track. For 86Box and real XT hardware.368,640 bytes
apps.imgSoftware disk, 1.44MB: a FAT12 volume holding MINES.O88, HELLO.O88, NOTEPAD.O88, RECORDER.O88, PIANO.O88 and FRACTAL.O88. Not bootable.1,474,560 bytes
apps360.imgSoftware disk, 360KB. The same six packages, same filesystem, different geometry.368,640 bytes
v1.0.20260801.1

v1.0.20260801.1

Released 2026-08-01 from 1518792cf · on GitHub

One binary, three display adapters. The kernel probes the video card at boot and drives whichever it finds -- VGA at 640x480 in 16 colors, a Hercules card at 720x348, or a CGA at 640x200 -- so os8088 now runs on the monochrome hardware an actual 1983 PC was likely to have, and it cost 1,171 bytes to do it.

  • The video card is decided at boot, not at build time #34
    os8088 has always required a VGA. It no longer does. kernel/viddet.inc probes in the order VGA, then Hercules, then CGA, and sets the mode itself: mode 12h through the BIOS for a VGA, mode 6 for a CGA, and the 6845 registers directly for a Hercules, because no BIOS will do it for you. The equipment word is consulted last -- an EGA or a VGA driving a monochrome monitor also reports a mono card, and those machines belong on the mode 12h path. One documented scope cut: a Hercules is not distinguished from a plain MDA. An MDA is text-only so there is no better action available, and driving it as a Hercules is strictly less wrong than driving it as a CGA at the wrong address.
  • There is no second graphics driver #34
    A monochrome card is a different enough machine that the obvious answer is a second driver, and the obvious answer was wrong. vgabb.inc, the software renderer written for the optional back buffer, was already latch-free and port-free -- it drew into RAM, and nothing in it cared that the target was RAM. Point it at the framebuffer, tell it there is one bit plane instead of four, route its row advances through a banked next-row helper, and it is the 1bpp driver. Hercules and CGA then differ from each other in four numbers in a table. That is what made this affordable: 1,171 bytes of code, against roughly 3KB for a driver written twice. The planar routines are simply unreachable on a mono machine and keep their assembly-time constants.
  • The screen size is a variable now #34
    Chrome that had been pinned to 640x480 since the first window appeared -- the dock strip, the clock cell at the right end of the menu bar, the pull-down item cap, the column the drive icons stand in -- is derived from the live geometry at boot, and reproduces its old constants exactly at 640 wide. Windows are clamped onto the real screen when created, so all five shipped packages place correctly on a 200-line CGA with no rebuild: the same .o88 files from the same disk. A package that wants to know what it actually got can ask, through a new call in the kernel's table.
  • Two flags where there was one #34
    The back-buffer flag used to mean two things at once: route drawing through the software renderer and a buffer is armed and must be flushed. On a mono machine the first is permanently true and the second is permanently false, so they had to come apart. Without the split, a 256KB Hercules machine would have reported double buffering in the Control Panel and billed 150KB of RAM it never allocated. The nine places that dispatch on it needed no new code.
  • Also fixed: the Task Manager painted over the dock #34
    Nothing in this kernel clips a draw to a window -- the clip is to the screen -- and the Task Manager laid its rows out from fixed constants with no check against its own height. On a 200-line screen it repainted straight over the dock once a second. Its two row lists now stop at a computed limit. Separately, the mouse test driver was silently dropping moves on a fast host, because the emulated serial mouse runs at 1200 baud and loses a packet whose predecessor is still in flight; it paces itself now. The symptom was a cursor that never moved while every screenshot still looked plausible.

Nothing changes on a VGA machine: the mode 12h output of this build was compared byte for byte against the previous one, with double buffering armed and windows re-dragged, on both floppy geometries. It is pixel-identical, which is why the screenshots on this site were not recaptured.

On a monochrome adapter the sixteen colors reduce to black, white and a 50 percent dither, and double buffering is unavailable by design -- the renderer already writes the framebuffer directly, so there is nothing to double. The Control Panel's Display page says so rather than offering it.

The boot splash draws only its progress bar on a mono adapter. Its dialog does not fit in 200 rows, and it runs before any other module is in memory, so the bar is deliberately kept on all three cards: a wrong stride, wrong bank arithmetic or wrong height each fail it visibly, and differently, on hardware with no debugger.

Kernel image
40,636 bytes
Image + .bss
38,657 of 45,056 (6,399 free)
Source
34,465 lines of assembly across 28 kernel modules
Files in os8088 v1.0.20260801.1
FileWhat it isSize
os8088.imgBoot disk, 1.44MB geometry: 80 cylinders, 2 heads, 18 sectors per track. For QEMU.1,474,560 bytes
os8088-360.imgBoot disk, 360KB geometry: 40 cylinders, 2 heads, 9 sectors per track. For 86Box and real XT hardware.368,640 bytes
apps.imgSoftware disk, 1.44MB: a FAT12 volume holding MINES.O88, HELLO.O88, NOTEPAD.O88, RECORDER.O88 and PIANO.O88. Not bootable.1,474,560 bytes
apps360.imgSoftware disk, 360KB. The same five packages, same filesystem, different geometry.368,640 bytes
v1.0.20260801

v1.0.20260801

Released 2026-08-01 from 739889a34 · on GitHub

The disk becomes real. The software floppy is now a standard FAT12 volume that os8088 reads and writes, folders and a Standard File dialog give files names and places, and the menu bar stops belonging to nobody -- it belongs to whichever application is active, with the kernel itself appearing as one, called Locator.

  • The software floppy is a standard DOS floppy #30
    The disk used to be os88fs, a private read-only format shaped around exactly what the kernel had to do. It is now canonical FAT12 -- the thing IBM PC DOS wrote in 1983 -- so DOS, Windows, macOS and Linux all mount it, and you can drop a .o88 onto the floppy with the file manager of whatever computer you happen to be sitting at. The type is decided by cluster count per the Microsoft specification, so FAT16 comes along for free; only the entry decode differs. Because a foreign machine can now write this disk, every byte on it is treated as hostile: the BIOS parameter block goes through seventeen rules before a single derived number is trusted, and the directory is re-shaped on the way in, with volume labels, long-filename fragments, hidden, system and deleted entries all filtered out. Files load through a size-driven cluster-chain walk with run coalescing, so a file a host wrote back fragmented loads fine and a corrupt chain fails bounded as "Bad package" rather than wandering.
  • ...and os8088 writes it too #31
    Reading a real filesystem is half of it. kernel/diskw.inc is the other half: create-or-replace, read, delete, rename and free space, reached by the system directly and by packages through the API table. Three rules carry the weight. Commit order -- allocate and write the data, flush the allocation table, then write the one directory sector that commits the file, then free whatever chain was replaced -- so the worst-case power failure leaks lost clusters, which any host fsck reclaims, and can never cross-link two files or lose bytes you already had. Rollback: anything failing before that commit re-reads the allocation table off the disk, so a half-built chain cannot survive in memory to be flushed later by an unrelated write. Coherence by remount: a successful metadata change re-runs the mount, so the directory listing stays exactly a mount snapshot and no new staleness rule enters the kernel. Writing is gated on a fully successful mount, which is why the boot floppy -- no valid BPB -- can never be written.
  • Every application owns the menu bar, and the kernel becomes Locator #32
    The bar was three fixed menus belonging to nobody. It is now three zones: the chip menu, identical in every application and always the first cell; the active application's name; and that application's own menus. Ownership is a window, and it moves through three one-line hooks, so nothing else in the kernel has to know the bar exists -- raising a window activates it, clicking a window that is already frontmost activates it too, and one validation at the top of every bar redraw covers close, minimize and hide at once. Locator is the kernel acting as an application, the os8088 answer to the Finder: the desktop, the drive icons, the Disk browser and the menus that launch everything else. It is not an instance and has no task -- it is the menu set the bar falls back to, and clicking the bare desktop is how you get back to it. For a program the whole interface is one API slot and three macros; all five shipped packages take the bar, and every item calls the routine its key or button already called, so the menus are a second door rather than a replacement.
  • Folders, and four file-manager windows #33
    A FAT volume has two directory shapes -- the root is a flat run of sectors, a subdirectory is an ordinary cluster chain -- and folders rest on spelling that out exactly once, in a single iterator used by both the mount listing and the write path's slot search, so the two shapes cannot drift apart. Navigation is a remount, the same choice writing already made, so no third staleness rule enters the kernel; going up reads the parent out of the .. link and needs no path stack. You get up to four Disk windows, each on its own drive and folder, each with its own cached copy of the listing: paints read the cache and actions re-sync first, which is what makes a repaint, a drag or a full-desktop repaint cost zero floppy I/O. New Folder, Rename and Delete are on the File menu -- Delete asks first, because this system has no undo and no trash -- and the right mouse button drops a context menu anywhere.
  • Files get names: the Standard File dialog #33
    The file API shipped five whole-file operations and no way to name a file, which is why the Note Pad wrote a hard-coded NOTES.TXT. The Open/Save chooser fixes that, and it is modal on purpose. Because nothing else is clickable while it is up, no other window can navigate the volume, so the dialog reads the global mount snapshot directly and needs no listing cache of its own. It is not an instance either -- no dock tile, no Task Manager row, closer to a pull-down menu than to an application -- so its close and minimize boxes both reduce to "cancelled" and the module contains no close-path code at all. Window callbacks cannot block, so it shows its window inline and answers later through a completion callback, run after teardown so the program repaints onto clean screen. The Note Pad is its first caller: a document name per instance, Open, Save and Save As.
  • Resizable windows, a scrolling browser, and fullscreen #29
    A resizable window draws a Mac-style grow box in its bottom-right corner and resizes through the same rubber-band outline discipline as a title-bar drag. There is no resize callback and there does not need to be: a resizable window lays out from its live record every paint, which is also why the Note Pad re-wraps its text as you drag the corner for free. The Disk browser stopped baking in its old fixed size -- painter and hit-tester derive every coordinate from one shared layout routine, so they cannot disagree -- and gained a scroll bar, keyboard scrolling, and a list/icon view toggle. Fullscreen is the other half: a fullscreen surface is a real window covering the whole screen with its chrome suppressed, so the existing obscured test silences the background drawers behind it without knowing anything new.

The Disk window lists 8.3 names now -- MINES.O88 rather than MINES -- because that is what is really on a FAT12 disk. A running program still shows the name from its own header.

The menu bar changes as you switch applications, and the menus you launch things from belong to Locator. If the bar is showing some other program's menus, click the bare desktop to hand it back to Locator.

The software floppy is now ordinary FAT12, so you can mount it on any modern machine and copy programs on and off it. tools/os88disk.py still builds one, but it is no longer the only way.

The Note Pad saves and loads real files: F2 saves, F3 opens, and Save As names the file through the new dialog. Line endings are translated both ways, so a document written here opens in Windows Notepad and one written there opens here.

Kernel headroom is down to 5,652 bytes on the binding image-plus-far-code guard, from 17,474 at the last release. The room the memory work bought has largely been spent; moving packages into their own segments is the next tranche.

Only QEMU is exercised routinely. This release is not verified on 86Box or on real XT hardware, though both floppy geometries build and all five size guards pass.

Kernel image
39,404 bytes
Image + .bss
37,479 of 45,056 (7,577 free)
Source
33,553 lines of assembly across 27 kernel modules
Files in os8088 v1.0.20260801
FileWhat it isSize
os8088.imgBoot disk, 1.44MB geometry: 80 cylinders, 2 heads, 18 sectors per track. For QEMU.1,474,560 bytes
os8088-360.imgBoot disk, 360KB geometry: 40 cylinders, 2 heads, 9 sectors per track. For 86Box and real XT hardware.368,640 bytes
apps.imgSoftware disk, 1.44MB: a FAT12 volume holding MINES.O88, HELLO.O88, NOTEPAD.O88, RECORDER.O88 and PIANO.O88. Not bootable.1,474,560 bytes
apps360.imgSoftware disk, 360KB. The same five packages, same filesystem, different geometry.368,640 bytes
v1.0.20260730.1

v1.0.20260730.1

Released 2026-07-30 from 84387fae8 · on GitHub

The menu bar tells the time. A new system clock reads the hardware RTC at boot and keeps itself on the PIT from then on, the right end of the bar carries the date and time of day, and clicking it opens a Control Panel page that sets them.

  • The date and the time, at the right end of the menu bar #28
    The bar has carried nothing but the chip glyph, File and Special since the GUI existed; it now ends with the date and the time of day, and clicking it opens the Control Panel straight onto the page that sets them. The text is right-aligned inside a cell sized for the longest form it can take -- the string runs 18 to 24 glyphs depending on whether seconds and a 12-hour AM/PM are showing -- so switching format redraws one cell rather than relaying the bar out. An edit or a toggle asks for that redraw through a queue flag rather than painting from where it stands, because the alternative was a whole-desktop repaint to fix a flicker, which is a worse flash than the one it fixes.
  • A system clock that assumes its machine may not have one #28
    kernel/clock.inc probes the hardware RTC through int 1Ah at boot, and the probe is deliberately paranoid, because the machines this OS targets are exactly the ones that may have no CMOS clock at all and whose BIOS may simply iret out of a function it does not implement. CX and DX are poisoned and the carry flag set before every call, every returned byte must be valid packed BCD and in range, and the values commit out of scratch only once all of it has passed. Nothing found means 4 July 2026, 00:00:00, and the system runs on regardless. From then on the PIT is the clock -- the same accumulator idiom the rest of the kernel uses -- carrying through month lengths and leap Februaries, which is how DOS behaves on the same hardware.
  • Reading a clock that ticks underneath you #28
    Only the UI task ever writes the fields, but the readers are not so confined: the bar's painter reaches the formatter from wm_paint_all, which a dying background task runs inside wm_destroy. So every write that can carry is one interrupts-off section, and every formatter works from a snapshot copy taken under that same guard. A reader pre-empted mid-format can never pair a pre-carry field with a post-carry one and print 31 December of next year, or 13:00 on a clock that has just rolled to midnight.
  • A Date/Time page in the Control Panel #28
    The panel's fourth page is two rows of fields with one selected and a +/- pair that steps it. It knows nothing about what a month or an hour is -- the field index is the number the clock's stringify and adjust routines take -- and it touches no BIOS itself: an edit posts a dirty flag and the UI task writes the RTC back outside the drawing lock, because a page proc may not take one. 12-hour mode adds a seventh, clickable AM/PM field, while the hour underneath is always stored and stepped 0..23, so the format is a rendering and nothing can drift out of it.
  • Seconds are off by default, and that is not taste #28
    The tick returns a two-bit mask saying what actually changed. With seconds hidden, the bar's text is unchanged 59 seconds out of 60, so the UI task does not take the drawing lock at all -- and the cursor blink that comes with taking the lock is precisely the flicker the setting removes. Turn seconds on and you get them, once a second, blink included. An open Date/Time page still wants its seconds either way, which a cheap check answers before the lock is reached for.
  • What it cost #28
    The kernel image grew from 25,136 bytes to 27,582 and its segment footprint from 23,358 to 25,079, leaving 19,977 bytes of headroom below the 0xB000 line where loaded programs begin. The Control Panel window grew with its two new option rows, 320x120 to 320x140; the other three pages simply have more white space below them now. Every screenshot on this site was recaptured, because the menu bar is in all of them.

Clicking the clock is a new thing the menu bar does: it opens the Control Panel on the Date/Time page. The bar's left-hand menus are unchanged.

Seconds in the menu bar are off by default. The Date/Time page turns them on, along with 12-hour time.

A machine with no readable RTC boots at 4 July 2026, 00:00:00 rather than refusing to keep time. Set it from the Control Panel; the write-back survives a Restart on hardware that has a clock to write to.

Kernel image
27,582 bytes
Image + .bss
25,079 of 45,056 (19,977 free)
Source
23,601 lines of assembly across 25 kernel modules
Files in os8088 v1.0.20260730.1
FileWhat it isSize
os8088.imgBoot disk, 1.44MB geometry: 80 cylinders, 2 heads, 18 sectors per track. For QEMU.1,474,560 bytes
os8088-360.imgBoot disk, 360KB geometry: 40 cylinders, 2 heads, 9 sectors per track. For 86Box and real XT hardware.368,640 bytes
apps.imgSoftware disk, 1.44MB: an os88fs volume holding MINES, HELLO, NOTEPAD, RECORDER and PIANO. Not bootable.1,474,560 bytes
apps360.imgSoftware disk, 360KB. The same five packages, same filesystem, different geometry.368,640 bytes
v1.0.20260730

v1.0.20260730

Released 2026-07-30 from 3405e0bf0 · on GitHub

os8088 gets sound. One API covers three generations of PC audio -- the PC speaker, the AdLib, and the Sound Blaster playing and recording in the background -- and two new programs on the software floppy use it.

  • One sound API, three cards, five capability tiers #26
    A program does not ask for a Sound Blaster. It asks for a tone, an FM note, or a stream, and a router picks the best device present. The layer is a table of drivers, each declaring what it can do -- tone, FM, background PCM, exclusive PCM, PCM in -- and five API slots at 0x0078 that dispatch through it. The PC speaker is always row zero, so every machine can make a sound; an AdLib adds nine-voice FM; a Sound Blaster adds streaming playback and recording that run off their own interrupt while the GUI keeps going. Ownership is a single owner with priority steal, and every grant is made inside one interrupts-off window, so a timer tick can never observe a half-made one. A program that dies with a note held is silenced by its own teardown.
  • The PC speaker plays sampled sound #26
    The speaker is a one-bit output, so a clip is played as pulse-width modulation: one PIT write per sample, and the cone integrates the pulse train. Timing comes from reading the scheduler's own channel 0 -- latch-and-read, never a write, so the clock the whole system runs on is untouched -- and a sample that arrives late is dropped by deadline resync rather than played slow. It is exclusive by nature: the clip owns the CPU while it runs, so a mouse click aborts it, and the aborting click is drained rather than falling through to whatever is underneath. An unplayable rate is refused with an error rather than quietly clamped.
  • Piano and Recorder, on the software floppy #26
    PIANO is an octave and a half of clickable keys with a scrolling mini-staff, a 300-note sequence recorder, a REPLAY button that reproduces your original timing, and three built-in songs. RECORDER is a wave recorder and player: it captures to a 5-second buffer through a Sound Blaster, draws the waveform, and plays back through whichever device the router picks -- and on a machine with no capture hardware the REC button is grayed and says why when clicked, so it stays a usable player. Both are ordinary packages that reach all of this through the public API table, which is the point: nothing about the sound layer is reserved for the kernel.
  • The Task Manager draws memory #25
    Clicking in the Task Manager flips it between the load graph and a new memory view: the RAM line, a map of the 640K conventional space, and a map of the kernel's own segment where every loaded package's region is drawn in its slot's dither pattern, matched by a legend square on that package's row in the process list. The sampler keeps running while you are in the other view, so flipping back shows an unbroken history. The process list is re-columned as NAME / ADDR / SIZE to go with it.
  • A Sound page in the Control Panel #26
    It reports which devices were actually found, lets you pin the tone route to the speaker or the AdLib -- refusing the AdLib when no probe found one, the same way the Display page refuses double buffering on a small machine -- carries the exclusive-clips checkbox with its trade-off stated in prose, and has a Test button that plays a synthesised 1 kHz clip through the public API. Underneath it are the emitted and resync counters, which is how the speaker's timing was measured in the first place.
  • What it cost, and what is left
    The kernel grew from 16,611 bytes to 25,136 and its segment footprint from 16,927 to 23,358, leaving 21,698 bytes of headroom below the 0xB000 line where loaded programs begin -- the sound layer spent about a quarter of what the memory release before it bought. Nothing left conventional memory that a 256KB machine needs, and a machine with no sound hardware behaves exactly as it did: the drivers publish themselves only after a probe succeeds, and the speaker row is the one that is always there.

The software floppy now holds five packages, not three. Drive B: lists MINES, HELLO, NOTEPAD, RECORDER and PIANO.

To hear anything past the speaker in QEMU you have to give it the hardware: add -device adlib for FM and -device sb16 for streaming and recording, each with an -audiodev. Without them the probes correctly report the cards as absent and the Control Panel's Sound page refuses to route to them.

Recording is Sound Blaster only, and QEMU never starts its input DMA against a file audiodev -- a capture there always ends on the watchdog, which RECORDER reports honestly as REC STOPPED (WATCHDOG) rather than pretending it worked. The live capture path is owed to real hardware, as is the whole DSP-below-2.00 branch.

Kernel image
25,136 bytes
Image + .bss
23,358 of 45,056 (21,698 free)
Source
21,997 lines of assembly across 24 kernel modules
Files in os8088 v1.0.20260730
FileWhat it isSize
os8088.imgBoot disk, 1.44MB geometry: 80 cylinders, 2 heads, 18 sectors per track. For QEMU.1,474,560 bytes
os8088-360.imgBoot disk, 360KB geometry: 40 cylinders, 2 heads, 9 sectors per track. For 86Box and real XT hardware.368,640 bytes
apps.imgSoftware disk, 1.44MB: an os88fs volume holding MINES, HELLO, NOTEPAD, RECORDER and PIANO. Not bootable.1,474,560 bytes
apps360.imgSoftware disk, 360KB. The same five packages, same filesystem, different geometry.368,640 bytes
v1.0.20260729

v1.0.20260729

Released 2026-07-29 from f7a3b2973 · on GitHub

A memory release. The task stacks, the disk buffers and two of the coldest modules move out of the kernel's 64KB segment, taking the headroom left for future features from 1,089 bytes to 28,129.

  • Room to grow -- 1,089 bytes of headroom becomes 28,129 #24
    The kernel was not short of memory, it was short of segment: everything it touched through DS had to fit in one 64KB window, and 59% of that budget was uninitialised scratch. The task stacks alone -- eleven of them at 1,536 bytes -- were 41% of the window. Three moves fixed it. The stacks and the floppy buffers went to their own segment at linear 0x08000, in low memory that sits free on every machine once the boot sector hands off, which is 20KB back. The package pool slid up from 0xA000 to 0xB000 into the 4KB that task 0's stack used to occupy. And two cold modules, the Control Panel and the Task Manager, now assemble into a far segment that is copied out of the kernel image at boot and lands on top of .bss -- 2,922 bytes of code that costs the window nothing. Nothing left conventional memory and nothing on screen changed, so a 256KB machine behaves exactly as before.
  • The RAM figure means something different now
    This page said 39,871 bytes last release and says 16,927 this one. No code was deleted -- the number counts what the kernel keeps inside its own segment, and the stacks and disk buffers that made up most of it still exist, just at another address. The ceiling moved too, from 0xA000 to 0xB000, because the package pool moved with it. The Task Manager's RAM total and its System row both add back what now lives outside the segment, so its rows still sum to the total.
  • The release process feeds this page #23
    This entry exists because the release checklist now requires it. The publishing script writes what it can read off the build -- version, date, commit, kernel size, file list -- and stops there, so the words around the numbers are written by hand, once, and used both here and on the GitHub release.

Packages load at a new address. The pool moved from offsets 0xA000-0xEFFF to 0xB000-0xFDFF and is 19,968 bytes rather than 20,480. A .o88 built before this release carries the old link base in its header and will not load -- reassemble it against the current SDK. The three packages on the software floppy here are already rebuilt.

Kernel image
16,611 bytes
Image + .bss
16,927 of 45,056 (28,129 free)
Source
13,243 lines of assembly across 21 kernel modules
Files in os8088 v1.0.20260729
FileWhat it isSize
os8088.imgBoot disk, 1.44MB geometry: 80 cylinders, 2 heads, 18 sectors per track. For QEMU.1,474,560 bytes
os8088-360.imgBoot disk, 360KB geometry: 40 cylinders, 2 heads, 9 sectors per track. For 86Box and real XT hardware.368,640 bytes
apps.imgSoftware disk, 1.44MB: an os88fs volume holding MINES, HELLO and NOTEPAD. Not bootable.1,474,560 bytes
apps360.imgSoftware disk, 360KB. The same three packages, same filesystem, different geometry.368,640 bytes
v1.0.20260728

v1.0.20260728

Released 2026-07-28 from 698b5878c · on GitHub

The first tagged release. Optional double buffering, a graphical boot splash, 640KB boot targets, and the Note Pad moved out of the kernel onto the software floppy.

  • Optional double buffering #22
    Switchable at runtime from the Control Panel's new Display page, and off by default on every machine -- a 256K machine draws straight to video memory as before and can never leave that path. At 500K or more of conventional memory the kernel allows a four-plane back buffer at segment 0x4000, flushed one dirty rectangle at a time when the drawing lock drops. While everything on screen is black or white all four planes hold identical bytes, so the flush writes one plane and lets the Map Mask register fan it out; the transient XOR overlays, drag outlines and menu highlights, bypass the buffer entirely.
  • A graphical boot splash #21
    A spinning 8088 over a progress bar, drawn while the kernel loads.
  • 640KB boot targets #20
    make run-640 for QEMU and make xt-640 for 86Box, so the system can be exercised on a maxed-out period machine as well as a 256K one.
  • Repository housekeeping
    A CONTRIBUTING guide (#19), SECURITY.md (#18), a CODEOWNERS file (#17) and a tracked gitleaks pre-commit hook (#16).

Note Pad is no longer on the File menu. It left the kernel and became the NOTEPAD package on the software floppy: open File > Disk and double-click it. The File menu is now Clock, Bounce, Disk and Close Window.

Kernel image
16,281 bytes
Image + .bss
39,871 of 40,960 (1,089 free)
Source
12,848 lines of assembly across 20 kernel modules
Files in os8088 v1.0.20260728
FileWhat it isSize
os8088.imgBoot disk, 1.44MB geometry: 80 cylinders, 2 heads, 18 sectors per track. For QEMU.1,474,560 bytes
os8088-360.imgBoot disk, 360KB geometry: 40 cylinders, 2 heads, 9 sectors per track. For 86Box and real XT hardware.368,640 bytes
apps.imgSoftware disk, 1.44MB: an os88fs volume holding MINES, HELLO and NOTEPAD. Not bootable.1,474,560 bytes
apps360.imgSoftware disk, 360KB. The same three packages, same filesystem, different geometry.368,640 bytes

What a release is here

A release is a tag on the source repository plus the four images that tag builds: a boot disk in 1.44MB and 360KB geometries, and a software disk carrying the loadable packages in the same two geometries. Nothing is compiled on the way in: the images published are byte-for-byte the ones the build produced, which is what the checksums on the download page are for.

The kernel size quoted with each release is the assembled kernel.bin. The second figure is that image plus every buffer the kernel addresses through its data segment, which is the number that has to fit the 64K segment. A release that ran out of that headroom would not build at all. Scratch the kernel keeps outside that segment -- chiefly the task stacks -- is not counted in it, and neither are loaded programs, which since v1.0.20260803 live in segments of their own.