Added
Per-song
GENMIDIbank inlibsxmidi.sx_midi_song_create_with_banktakes the lump with the MIDI,sx_midi_song_uses_genmidireports the fallback, the port passes it always. Host checks plusdoom-mus-teston the realD_E1M1cover both paths (seedocs/media/MIDI.md).libsxmidi.so.0.4, a General MIDI synthesizer the tree builds and installs in/disk/lib. It parses a Standard MIDI File, plays it through a built-in 16-family instrument bank and a drum kit, and renders mono PCM for the mixer. No soundfont and nolibm; the public surface issavanxp/midi.hand the design is indocs/media/MIDI.md.The DoomGeneric port has music. Each MUS lump is converted to MIDI with the upstream
mus2mid.c,libsxmidisynthesises it into the mixer's buffer as the game runs and a dedicated voice loops that buffer. The port is now PIE and declares aDT_NEEDEDfor the library, so building it requires./build.sh buildto have produced/disk/lib/libsxmidi.so.0.4first.The port's music is checked on the guest audio, and on what follows it.
./build.sh smoke doom-music-smokeopens the port from the desktop, starts the level, fails if the WAV QEMU captured comes out silent, then closes the window and fails if any audio outlives the last music.Three SpicyGame pixel fonts join the baked set.
RetroSans.ttf,PixelSans.ttfandImpactfulBits.ttf(CC0) are baked bytools/font/genfont.pyinto committed tables; only the RetroSans caption is linked, the other two are spares.genfont.pynow accepts anySX_*prefix.Start menu (trimmed): Start button plus a flat Programs list.
/bin/startmenuopens anchored above the button with the progman catalog grouped by category, launches on click and closes on toggle or outside click. Mouse only, no cascade.The Start menu gains a footer, a banner strip, a logo button and a self-listed launcher. Shut Down (with its icon) and Restart with two-step confirm over a margin, a 24px gradient strip in title blue, the brand logo on Start, Program Manager under System.
libffmpeg.so.0.4, FFmpeg's five components as one shared library. The FFmpeg port installs it at/disk/lib, andldso_loadmaps it: 6.8 MB on disk, 2334 exported symbols and 9228 relocations, which is the first workload the loader has seen that it was not built for.The Media Player is a program that depends on a library. It went from 7 MB with FFmpeg linked in to 344 KB with one
DT_NEEDED, and it decodes through/disk/lib/libffmpeg.so.0.4.The Media Player is a program of the system tree, and the FFmpeg port builds no program at all. Its source moved to
subsystems/posix/userland/mediaplayer/and the port's only artifact is/disk/lib/libffmpeg.so.0.4. Opening.mp3,.aviand.flacfrom Files goes through/bin/mediaplayer, which says in its own window which library it could not load. The program is built when the port has been built — FFmpeg's headers are generated, not versioned — and CMake says so instead of skipping quietly; without the port the launcher drops the entry, as it does for Doom.A program whose library is missing now says so and keeps running. It used to die with a page fault at the address of a PLT stub.
ldso_missing()is part of the SDK, and the player checks it before its first call.A missing dependency no longer leaves the program unrelocated, and no longer takes the rest of them with it. Relocations were skipped when a
DT_NEEDEDfailed to load, which left every global pointer at its link-time value —stdoutamong them, so the program could not even print the reason it was broken. And the walk stopped at the first missing library, so a missinglibffmpeg.so.0.4also leftlibsxgfx,libgfx2dandlibsxguiunloaded: the Media Player started and died on its first toolkit call, jumping throughsx_rect_make@pltwith no link between that address and its cause.The loader applies
R_X86_64_64. A data slot holding the address of an imported symbol needs a runtime resolution, and the loader aborted the load on it.libffmpeg.so.0.4has 253 of them.libgfx2d.so.0.4, the 2D painter and window chrome, as a shared library. No library asks the program for a single symbol now — every reference any of them makes is to the C runtime or a system call. A program no longer has to be PIE so that a toolkit can find its drawing code.Up to 32 shared libraries per process, instead of 8. The longest chain the tree can build is seven — a program, its two interface libraries and FFmpeg's codec, utility, scale and resample libraries — so eight was one slot from a wall that FFmpeg integration would have discovered instead of us. Raising it alone would have cost 134 KiB of resident memory in every process, including programs that load no library at all, so the per-library slot was halved first.
The library loader no longer leaks a descriptor per library. It kept the file descriptor and a whole-file section open after the segments were mapped, two per library, forever. A session with the task bar and the keyboard popup ran out of descriptor headroom once a third library was added.
A rebuilt program now reaches the bootable image. The rootfs staging step depended on an aggregate build target that produces no file, so it was considered up to date while the programs were already rebuilt. A change to any program left the image booting the previous binary, with every check still passing.
The visual scenarios now assert the glyphs on screen, not just that something moved. A toolkit that painted text in the background colour passed: the scroll was checked with the scrollbar thumb, which moves regardless. The expected pixels are rendered from the font tables inside the built graphics library, so the check is about the characters rather than about ink.
A program can now ask the loader which dependency failed to load. The loader was never fatal — a program with an unresolvable library still starts — but there was no way to find that out, so the ability existed by accident rather than by design. Both properties are now covered by a self-test that removes a declared library from the volume.
The Media Player is built position-independent. Its objects were compiled
-fno-pic, so they could not go into a shared library at all.libffmpeg.so.0.4now links from them: 6.7 MB, 2334 exported symbols, no relocation errors.Two Media Player smoke scenarios are removed. They tested a
--availabilitymode that stopped existing when SxMedia was withdrawn, and they had been asking for output nothing produced since. The absence-detection they covered is going away for a better reason: the player becomes a system application and reports a missing library itself.A missing shared library is now recorded as reported by the application itself. The loader does not abort when a dependency fails, so a program can still open a window and say what is missing; the two pieces the loader would need for that are written down, along with the fact that an installed library cannot be uninstalled and why that is correct.
Dead code in the binaries is now measured against what would recover it: 5.5 MB across the tree, held back by
--export-dynamic, which has to stay while the libraries resolve the C runtime against the program.A relocation type the loader does not implement now says so, instead of failing with a step number that leads nowhere. It cannot arise from this linker, which only emits the three supported types; it is there for a hand-linked object.
Three more loader failures now say what they are, and each has a self-test. A missing library and a full slot table were both just numbers, and they are different problems with different fixes. A dependency shared by two branches of a dependency graph was loaded once per branch; nothing exercised that case, because a two-library chain cannot produce it.
The library loader says which symbol it could not resolve. A load failure reported a step number, which says where the failure was and nothing about what it was. A dependency built with an incomplete
DT_NEEDEDnow names the missing symbol and the library that wanted it, and a self-test asserts the name rather than just the failure.The runtime for external applications is derived from the one for the system's own programs, instead of being listed again by hand. The two lists have to agree and nothing complained when they did not — the symptom is an undefined symbol much later than the change that caused it.
The build now fails if a program carries its own copy of a library it loads. Such a program links, runs and passes every test — the library is mapped and never called — so the sharing silently stops happening and nothing notices.
The kernel now tells a program where its own image starts. The library loader was guessing it by walking backwards a page at a time looking for the ELF header, which assumed every page on the way was mapped. A linker can leave a gap between two loadable segments, and reading one of those is a page fault — a program died before
main. A self-test with text starting at0xc000and its first segment ending at0xaff3is enough to trigger it.libsxgfx.so.0.4, the graphics layer, as a shared library. The toolkit now links against it instead of asking the program to export the graphics entry points, so a program no longer has to be PIE just to satisfy it. The task bar, the keyboard layout popup, the shell wallpaper, the window server and the desktop shell draw through it, and no executable carries its own copy of it.sxgui-smoke, which drives the visual scenario as a smoke test. The SxGUI gate used to run only if someone remembered to ask for it.libsxgui.so.0.4, and ten programs now draw through it. Every user of the toolkit — the calculator, the text editor, the control gallery, the file manager, the application wizard, the system properties window, Minesweeper, the self-test, Program Manager and Task Manager — declares it and carries a copy of neither. The toolkit resolves the graphics layer against the program that started it, so the layer underneath stays where it is for now. Verified at the pixel level: the editor's scroll screenshots are byte-identical with and without the library.ldso_symbol_is_shared()reports whether a symbol came from a shared library or from the program itself, so a program can check that it really is using the library and not a copy of its own.sx_start_dynamicreports a failing interpreter. A program that could not be relocated said nothing and failed later somewhere unrelated.calcnow links as a PIE, the first desktop program to do so. It joinslibtestin proving the profile beyond the test programs.crt0now runs the library loader beforemain. A program that links the loader is ready to call into its libraries with no setup of its own; programs that do not link it are unaffected.libtest, the first program that links against a shared library. It callssqrtlike any other function instead of resolving it by hand, afterldso_start()loads what the program declares and fills itsGOT. It is built withoutmath.c, sosqrtgenuinely comes fromlibmath.so.0.4.The loader relocates the executable, not just libraries. The kernel maps the main image but leaves its
GOTempty, so a program that links a library has to callldso_start()before using it.R_X86_64_RELATIVEis now applied: without it a relocated image keeps pointers to address zero.The executable is now in the library loader's symbol scope. A library can resolve a symbol against the program that started it, so
-fstack-protector- strongis back on for shared libraries.ldtestchecks the stack canary resolves and holds a live value.A library now loads the libraries it declares.
DT_NEEDEDis walked from/lib, each dependency is mapped once however many ask for it, and a symbol resolves through the whole chain.ldtestcalls a function in a dependency and checks the result.pietest, the first PIE executable in the tree.ET_DYN, relocated by the kernel onto a base, with.dynsymand.dynamic. It proves a relocated image loads and runs — and it proves the premise the whole feature rests on: a PIE executable exports symbols a library can resolve against, which lld denies to a non-PIEET_EXEC.The kernel loads
ET_DYNimages with a load bias. AnET_EXECis unchanged — bias zero, exactly where the linker script put it. AnET_DYNis relocated onto a base, which starts atkUserBase. Per-process entropy for that base, and ASLR for the executable image, is the next step.The kernel reads
PT_INTERPand hands the path tocrt0. A binary linked with--dynamic-linkernow has its interpreter path delivered inrcxand readable throughsavanxp_interpreter_path().interptestis linked with one and checks it arrives. The interpreter is not run yet:crt0still goes straight tomain.libmath.so.0.4, a real shared library, and a minimal loader that maps it./lib/libmath.so.0.4is built as PIC and lands on the volume, not in/bin, so Program Manager never lists it.ldsoreads its program headers, places eachPT_LOADat the address the ELF asks for, copies the writable segments into private anonymous sections, appliesR_X86_64_JUMP_SLOTandR_X86_64_GLOB_DAT, and resolves symbols by name./disk/bin/ldtestcallssqrt,fabsandfloorthrough the loaded pointers.section_open_rangebacks a section with part of a file. APT_LOADis a slice of the file, not the whole file, and mapping the whole thing puts the bytes at addresses that are not theirs.section_openmaps a file into a read-only section. Given an open file descriptor it returns a section handle holding the file's bytes, so user mode can map a library image. Write access cannot be requested: the backing is read-only and writable segments are separate anonymous sections. Two processes asking for the same file share one set of pages.map_view_atmaps a section view at an address the caller chooses. The base must be page-aligned and free.map_viewis the same call with the base left to the kernel.savanxp_system_inforeports live section counts.sections_liveandfile_sections_live, so a shared mapping is observable instead of assumed.SAVANXP_SECTION_EXECmaps a section view as executable. Execution is a permission of the view, granted by the section, instead of a hardcodedNXon every mapping. A view that asks for write and execute is still refused, withEINVAL.mmapkeeps refusingPROT_EXEC: executable mappings go throughsection_create/map_viewonly.fscheckreports SxFS consistency from inside the system. It reconciles the block bitmap against the inodes in both directions and walks the tree for unreachable inodes, aliased entries and duplicate names. Read-only: it repairs nothing and runs against the mounted/disk. Exits non-zero on findings.Celeste Classic is now an official port.
./ports/ccleste/build.shinstalls/disk/bin/cclesteplus the game assets under/disk/games/celeste, building the upstream engine unpatched because the overlay replaces its SDL frontend../build.sh smoke ccleste-selftestand./tools/shoot.sh --scenario cclestecover it. Music is deferred until the OS has a dynamic linker, so that a program can reach a codec it was not built with.The SDK now owns Doom-style pixel presentation and timed PCM mixing.
sx_scaled_presenterhandles scaling/centering/row damage; opt-insavanxp/audio.hprovidessx_audio_mixer, and Doom uses both.Linux builds can now be driven by Bash and CMake.
build.shdrives the freestanding CMake graph, stages the rootfs, validates sibling SxFS candidates, and produces EFI/ISO artifacts through one native workflow.Official ports now have a versioned layout. Each port keeps its upstream pin, focused patches, SavanXP overlay, standalone build script, and persistence instructions; local experiments remain outside the official tree.
DoomGeneric now has a versioned official port.
ports/doomgenericpins upstream, separates reviewed patches from the SavanXP overlay, builds with Bash, and installs through the persistent SxFS candidate flow.The optional Haxe subsystem now has a standalone Linux build.
subsystems/native/build.shpins Haxe/reflaxe, keeps the base CMake target independent, and installs native demos through the SxFS candidate flow.Linux now has a headless smoke runner.
./build.sh smoke <scenario>injects the scenario into the initramfs, runs QEMU on a validated disposable image copy, records serial output, and supports QMP keyboard/taskbar input.Linux smoke orchestration now covers audio, TCP, and external native apps. Audio backends/WAV files, the TCP echo server, and
tools/build-user.shkeep these scenarios outside the base CMake target.Linux build commands now match the one-word smoke aliases and SDK install destinations. Direct
windowd-smoke/gpu-soaktargets,build-user.sh --destination, andtools/run-user.shcover the matching Bash workflows.The host
sxfs-cli checkcommand validates SxFS metadata. Linux image synchronization checks before and after candidate applies and fails closed on corruption, dirty/pending journals, or no-space conditions.SxFS images now compact safely when fragmentation blocks an apply. The Linux sync path preserves reachable external data, validates a same-size staging image, and atomically installs it without deleting the original first.
Stack canaries are randomized at kernel boot and for every new process.
forkinherits the parent guard, whileexecinstalls a fresh one beforemain; the runtime no longer embeds a reusable cookie in each image.Kernel security boundaries are documented and enforced. NX/W^X, stack canaries, partial mapping ASLR and checked user/device/storage boundaries land together with the kernel audit.
Alt+F4 closes the active window, the same as its X button: a dialog is cancelled, a main window closes with its process, and it also works for an app in fullscreen.
System name and version at the bottom right of the desktop, above the taskbar, as classic desktop systems show them. Drawn with the wallpaper, so every wallpaper mode has it and no window is covered.
Dialogs are windows of their own. Under
sxgui_app_run,sxgui_dialog_beginopens a WM window owned by the app — framed, kept above its owner, which takes no input meanwhile — so Minesweeper's About no longer gets cut off at Beginner. Newgfx_window_open/gfx_window_closeandSAVANXP_SYS_PIPE_SEND_HANDLE/_RECEIVE_HANDLE(handle passing over a pipe), tested byhandletest. How it works.Media Player is now a system application in Accessories.
/bin/mediaplayeris always built, delegates to/disk/bin/mediaplayer-ffmpegwhen installed, and otherwise opens an in-OS warning explaining thatports/ffmpegmust be built; the installed player keeps the audio/video controls and associations. How it plays.The FFmpeg port decodes the common formats. H.264, HEVC, VP8, VP9, MPEG-4, Theora and MPEG-1/2 video; AAC, MP3, Vorbis, Opus, FLAC and AC-3 audio; MP4, Matroska/WebM, AVI, Ogg and MPEG-PS/TS containers; plus swscale and swresample.
Gears (
/bin/gears), in the Diagnostics group. A hand-written software rasterizer — matrices, viewport, z-buffer, backface culling and flat shading with one directional light — drawing the three meshed gears. It is the consumer that defines the subset of the future SxGL, not a port. Newtools/shoot.sh --scenario gears. The plan it serves.Fixed-size windows:
window_flags=fixed_sizein a.sxres. The edges stop grabbing and maximize is drawn disabled, so a layout that cannot stretch no longer gets an empty margin around it; Minesweeper and Calculator declare it. The program still sizes its own window — in a fixed one every size hint is honoured, so changing Minesweeper's level resizes the window. How it works.Floating point in every app of the image. The in-tree userland compiles with
-msse2and links the libm ofruntime/math.c, sodouble,%f,strtodandsqrtwork with no switch to remember;--ssestays for external apps. The rule it replaces.docs/desktop/SXGL_ROADMAP.md. OpenGL does not grow out of SxGFX: SxGL would be a sibling layer binding to the window surface andgfx_present_region, as a runtime module of the SDK. Nothing is implemented.Calculator (
/bin/calc), in the Accessories group. Four operations,%,sqrt,1/x, memory keys, copy/paste and the numeric keypad, over a decimal engine of 16 significant digits written in integers, so0.1 + 0.2is0.3. New./build.sh smoke calc-smokeandtools/shoot.sh --scenario calc. Why its engine is decimal and integer anyway.Minesweeper (
/bin/mines), in the Games group. The first game that comes in the image instead of being installed like Doom: three levels, LED counters, the face button,?marks, chord with both buttons, keyboard play and best times in/disk/mines.ini. New./build.sh smoke mines-smoke, and apps that paint their own content now get the system 3D edges fromsxgui_draw_raised_edge(). Why it is in C and not Haxe.System master volume and mute.
AUDIO_IOC_SET/GET_VOLUME(0..100, 100 is identity) andSET/GET_MUTEDon/dev/audio0scale every writer's PCM in the kernel;/bin/volumereads, sets and persists it to/disk/audio.cfg.Taskbar volume button and popup. The speaker left of the layout indicator opens a frameless slider with mute, applied live through the master ioctls; a second click or a click outside closes it, like the Start menu.
sx_audio_mixer_set_voice_loop. A mixer voice can repeat its buffer instead of stopping at the end, for short music loops; starting a voice leaves it off, so looping stays opt-in per start.Celeste Classic now plays music. The port links PIE against
libffmpeg.so.0.4and decodes the five OGG loops on first request into a dedicated looping mixer voice; without the FFmpeg port it runs silent.Two programs can now sound at once.
/bin/audiodowns/dev/audio0and sums what clients send over UDP loopback; every writer falls back to direct turn-taking without it, so nothing changes where it does not run.Per-application volume. Streams carry names now, the daemon levels each one, and
volume list/volume set <app> <0-100>drive the census; the mixer, the player, Doom, Celeste and the test tone all introduce themselves.Per-application sliders in the volume popup. Every application that is sounding gets a row with its own slider, refreshed from the daemon census once per second; levels are per session and are not persisted.
Changed
Doom music now plays the WAD's OPL2 instruments. With
GENMIDIthe song renders through a two-operator FM voice instead of the families, without it bit-identically; the smoke log names the path ((FM)vs(familias)).The MIDI bank stopped being one waveform per family. A voice now has a second partial (interval and weight), a blend toward sine, an optional vibrato LFO and an attack pitch sweep, and the drum kit uses all of it.
The calculator's arithmetic now comes from
libmath.so.0.4. Its own 16-digit decimal engine existed because the in-tree userland was built-mno-sseanddoubledid not compile.sqrtis now resolved from the shared library, socalccarries no copy of it.The calculator shows 15 significant digits instead of 16, and accepts 15 digits of input instead of 16. A
doublecarries about 15.95 decimal digits, so the sixteenth was not always true.1/3 × 3now reads1instead of0.9999999999999999, and2/3reads0.666666666666667.Section budgets raised: 64 section views per process, 256 sections system-wide. The old limits left no room for shared libraries, and 32 views per process was already tight for heap arenas alone. Exhaustion is still a clean
ENOMEM.Two
PT_LOADsegments may now share a page. Required by any binary linked with an interpreter, where the text segment does not end on a page boundary. The page takes the union of both segments' permissions, and a union that is both writable and executable is still refused.The ELF loader reports which program header it rejected.
bad elf segmentnow carries the segment index, or 65535 when the rejection is not about any one segment. Without it, aPT_LOADtable with four entries fails as one opaque message.The section cache keys on the file range, not just the inode. Asking for the whole file and asking for a slice of it are different sections, and confusing them mapped a segment with the whole image behind it.
savanxp_system_inforeports live section counts and why the last view mapping failed, so a shared mapping is observable instead of assumed.Window caption bars are two pixels taller. The title area now has a 20px caption, giving the compact title text and caption buttons more breathing room.
The test machine now boots with 1 GiB of RAM, and the persistent volume is 1 GiB while the LiveCD carries 256 MiB. The image stays sparse, so it costs ~30 MiB of real disk.
SxFS format v2: 4096 inodes and a 1 GiB ceiling. The inode table went from 64 to 1024 sectors and the block bitmap from 32 to 512, so a volume is no longer capped at 64 MiB.
build/disk.imgis reformatted once; a v1 image does not mount, andsxfs-clisays so instead of failing obscurely later.ATA now splits large requests instead of rejecting them. A metadata commit asks for 1537 sectors and the PIO count register holds 8 bits, so writes over 255 sectors were being refused — harmless while the metadata fit in 97 sectors, fatal on the default IDE machine once it did not.
fscheckreports metadata commit cost.commits,bytes_writtenandbytes_per_commitper volume, so the journal's cost is a measurement instead of an inference from the wall clock.The development path no longer carries the persistent volume as a boot module.
build/disk.imgreaches QEMU as an attached disk instead, which frees the 64 MiB it was occupying in RAM and drops the staged EFI tree from 542 MiB to 30 MiB. The ISO keeps its own tree and itslivecdvolume, since there is no disk to attach, staged at its exact filesystem size so a grown image does not pad the ISO.build/disk.imgmay now be larger than the volume inside it.sxfs-cliaccepts an oversized image and still rejects a truncated one, and a build preserves both the size and the sparseness of the file, sotruncate -sis enough to make room for a later volume grow.Window titles use RetroSans instead of Noto Sans. The caption face is
RetroSans.ttfat 18px with no faux-bold; the desktop body stays on Noto Sans 13px.Active captions follow the declared accent instead of tinting the blue base. A declared accent renders as its own shade-to-tint ramp; windows without one keep the Windows Standard gradient. Fixes the two-tone split on accents like Files.
init relaunches the audio daemon only when it dies serving. A deliberate startup exit (no network, no audio, another daemon) leaves it stopped and the system on direct turn-taking instead of respawning forever.
The test machine now boots with 512 MiB of RAM, up from 256 MiB, for
run,debug, the smoke scenarios,gpu-soakand the ISO boot test.Window title text is now one pixel smaller and bold. Caption text uses a dedicated 12px Noto Sans face while the rest of the UI keeps its regular font.
Normal builds no longer regenerate desktop source icons. Checked-in PNGs are treated as editable artwork;
tools/gen_desktop_source_art.pyremains available through the optionalsavanxp_desktop_source_arttarget.Gears (
/bin/gears) has a redesigned app icon. The launcher and taskbar now use fuller 16×16 and 32×32 gear artwork.The About app now identifies the system as the Alpha edition.
The boot screen fades in from black, like a classic desktop splash. Logo, name, progress bar and status line brighten together over about a second, driven by the monotonic clock; without a calibrated clock the splash appears at once.
Visual desktop verification uses the native Bash launcher.
tools/shoot.shreuses the shared scenario driver over Unix QMP and a disposable SxFS copy.Linux Program Manager and Add/Remove Programs smokes restore Doom when its external ELF exists. The optional image remains unchanged when no ELF is installed.
Doom persistence now has an isolated Linux regression command.
tools/verify_doom_persistence.shrebuilds a disposable image and compares the Doom binary, WAD, configuration, and savegame bytes.ISO verification now covers El Torito plus BIOS and UEFI boot.
tools/iso_boot_test.pyboots the generated image through both firmware paths and waits for the guest handoff token.WM↔client protocol v4: the session opens all 12 windows. Each costs
windowdtwo descriptors instead of nine: one event pipe (SAVANXP_WM_FD_EVENTS), a wake event, one submit event per session, and hints and launches in the surface header (gfx_should_closeis new). Apps no longer inheritwindowd's descriptors. Breaks the client ABI: rebuild external apps such asdoomgeneric. Descriptor budget.The automated smokes honour
--accel../build.sh smoke smoke --accel kvmruns the suite with KVM instead of TCG; the default remainstcg.The system 3D edges moved to
savanxp/sxchrome.h, out of the toolkit.sxchrome_draw_edge/_raised/_sunken/_inset/_etched,sxchrome_fill_raisedand the disabled relief (_draw_text_disabled,_draw_glyph_disabled) are in every binary's base runtime, sowindowd,calcandtaskmgrdropped their private copies. Why not in SXGUI-C.The userland links with
--gc-sections. Each binary keeps only what it reaches from_startinstead of whole runtime objects: the initramfs goes from ~14 MB to ~5.5 MB, libm included.The managed app layer (Haxe on a VM) is deferred until after v1.0. Until then everything ships in C against the POSIX SDK;
subsystems/nativestays as a frozen, validated experiment that nothing in the image depends on. If it is ever needed, it arrives as a selective port, UWP-style, beside the native apps. The decision and the shape it would have to fit.About is now System Properties. General shows the edition, the version, where it is installed and the processor (brand, speed and features from
CPUID); Hardware lists the devices found. The live counters it used to show moved to the Task Manager.The Shell window opens and repaints far faster.
/bin/shellappdropped an unused 8 MiB static backbuffer, repaints only the text rows that actually changed instead of the whole surface, blinks the cursor without a repaint, and keeps scrollback in a ring. Monospace text blits ~16x faster system-wide. Newshellapp-stats:line over/dev/serialandtools/shoot.sh --scenario shell.The compositor accumulates damage as an exact region.
windowdstopped merging dirty rectangles by bounding box: dragging a window now repaints and presents the ring that changed, not the box around the old and new frames.tools/shoot.shdrives other hardware and loads the display path.--virtio/--accel(tcg/kvm) pick devices and accelerator; thebench,saturateandspinscenarios feedwindowd-stats.Launcher icon captions wrap to two lines. A name that does not fit on one line breaks at a space and is centred over two, and only what still does not fit is cut with an ellipsis — before, a long caption was clipped at both ends and read as broken rather than as truncated. The cell now derives its height from the active font instead of a baked 76 pixels.
The boot screen is now a splash with the project logo. Black background,
assets/brand/logo.pngbaked in bytools/gen_boot_logo.py, the system name below it, a sliding block bar, and the step names now in English.The splash bar keeps moving through the long boot steps. It advances on
monotonic_ns(), now calibrated at the start of boot bytimer::calibrate_monotonic()instead of during the ACPI bring-up.All repository documentation is in English, and the README leads with a quickstart. The Linux requirements move to
docs/build/BUILD_LINUX.md,docs/README.mdindexes the design docs, andCLAUDE.mdpoints atAGENTS.md.The filesystem is now called
SxFS, and the2leaves the name. The constants and the namespace never carried the number (SVFS_*,svfs::), so the name said one version and the code another. The on-disk magic goes fromSVFS2toSXFS(the journal's fromSVJNL2toSXJNL) andSXFS_VERSIONreturns to1. Every earlier image stops mounting:build/disk.imgregenerates itself, but a persistent VirtualBox/QEMU disk must be reformatted.assert()reports when it fails. It was((void)(expression)), so a failing assert went unnoticed. It now prints and terminates the process; withNDEBUGit still disappears.struct stathas the fields third-party code names.st_uid,st_gid,st_nlink,st_blksize,st_blocks,st_atime,st_mtime,st_ctime. The kernel only reports type and size, so the rest stay zero.execno longer copies the whole image into physically contiguous pages.elf::load_user_imagetakes anelf::ImageReaderand reads the file directly onto the process's pages: half the memory during exec, and no contiguous reservation that fails on a fragmented heap.The user stack goes from a fixed 128 KiB to 1 MiB on demand, with a guard page. Only 8 pages are mapped at startup and the rest appear when touched (
process::grow_user_stack), so a shallow process uses LESS memory than before. An overflow now dies with a readable fault.spawn/execaccept 128 arguments and 8 KiB total, instead of 15 arguments of 63 characters. The buffer now comes from the kernel heap instead of 4 pages of kernel stack, and overflow fails withE2BIG(new) instead of truncating silently.Streams read in batches and
mallocaligns to 16.fgetswas issuing oneread()per character; file streams now fill a 512-byte buffer. 16 is x86-64'smax_align_t: with 8, amovapsin an--sseapp could fault.The libc stops renaming with
#define: the standard names are now real symbols. The SDK headers defined#define read sx_readand ~140 more, so the preprocessor rewrote any identifier with those names — a.closestruct field included — andmallocorstatwere not symbols a third-party object could link. The raw syscall layer is prefixedsavanxp_*;math.handsetjmp.hkeep their macros on purpose.One userland runtime and one
printf.runtime/posix.cis linked into every program andruntime/libc.cis reduced to the raw syscall layer plus gfx. Which formatter ran used to depend on the headers each.cincluded, and libc.c's did not even understand%ld/%lu. Rawputsbecomesputs_outandputchar(fd, c)becomesputchar_fd.icon=inprogman.inipoints at a program, not at an art catalog. It stores a PATH and the icon is read from the binary it points at. The old names still work as aliases, and an expliciticon=beats the binary.The sxgui chrome moves to the Win95 two-pixel bevel. A period 3D border takes four tones —
SXGUI_COLOR_BEVEL(223,223,223) was missing — and sunken is now the exact reverse of raised. Also dotted focus rectangle, 50% scrollbar trough, redrawn checkbox tick, concentric radio button, engraved disabled text.The sxgui apps share a single layout grid. The metrics live in
savanxp/sxgui.h(SXGUI_MARGIN,SXGUI_GAP,SXGUI_BUTTON_WIDTH, ...) and are used by notepad, files, progman, aboutapp and widgetsdemo.tools/shoot.shgains thefilesscenario. The explorer is the window with the most distinct controls at once, so it is where a stray toolkit margin shows up.The keyboard event carries the modifiers.
savanxp_input_eventgainsmodifiers(SAVANXP_KEY_MOD_*); the kernel already computed it and threw it away, so the WM tracked Ctrl by hand and got stuck if a KEY_UP was lost. The event grows from 12 to 16 bytes: external apps built against the previous SDK need a rebuild — Doom reads this struct.
Removed
The SxMedia multimedia layer is withdrawn, and the Media Player is the optional FFmpeg port again rather than a system program. A program can only use the codec libraries it was built with, so "one player that plays whatever is installed" cannot be arranged without a dynamic linker or a codec service. The design, the findings and the prerequisites to try again are in docs/media/SXMEDIA.md.
The alternate host compatibility surface is removed. Its entry points, bootstrap/toolchain cache, non-native accelerator selection, and TCP QMP fallback are gone; Bash/CMake is the canonical build and test path.
The legacy SDK trees for Doom and FFmpeg are removed. Doom and FFmpeg now live exclusively under
ports/doomgenericandports/ffmpeg.The
wavinfoandplayerFFmpeg demos. Every binary that links libavcodec carries all its decoders, so they became modes of the player:mediaplayer --probe,--selftestand--gpu-hold.The icon set baked into
desktop_icons.his down to one. OnlyDESKTOP_ICON_DESKTOPsurvives, as the safety net for a binary that cannot be read at all. The PNGs inassets/desktop/icons/stay as the catalog each.sxresreferences withicon=<name>.The start menu strip art is deleted. The menu was retired with the rest of the Win95 chrome, but the chain that drew it was still whole, down to a symbol nobody used.
The eight hand-written coreutils in
subsystems/posix/userlandare deleted.cat.c,echo.c,ls.c,mv.c,rm.c,sleep.c,true.candfalse.cwere built by nothing:/bingets them from the busybox multicall.
Fixed
Celeste's effects no longer saturate at any volume. Its 16-bit WAVs were decoded taking the high byte unsigned, shifting everything half scale (even silence played full-negative); the mixer also compresses past 0.75 FS now.
A jittery audio stream is concealed instead of zeroed. While a stream has played a frame, the daemon repeats its last frame — scaled by its own volume — for up to 50 ms of jitter, so a brief packet gap does not click but a client that is gone does not keep the device busy either.
Changing the music no longer stalls the game for a second. The Doom port synthesised a whole track before playing it, which costs ~120 ms natively and about a second under TCG; it now keeps a one-second lead and fills a quarter-second chunk per frame while the looped voice plays the buffer.
A missing library is now named deterministically, and counted. It used to be whichever failed last, so the same image could name three different libraries for the same three absent files. It is now the first, plus a count of how many libraries could not be loaded — which is not the same as how many files are missing: two absent files can stop four libraries from loading.
The emulated disk transfers 32 bits per port access instead of 16, which makes a 390 KB write ~26% faster (measured: 365930 and 401218 ms down to 280703 and 278922). Most of that cost is not the port writes at all: dropping every cache flush changes nothing, and the per-sector cost on the device side is what remains. Bus-master DMA is the fix for that, and the kernel does not use it.
Four loader diagnostics can now fail.
brokentest,missingtest,slottestanddiamondtestasserted only througheprintf, so they had nothing a smoke scenario could look for and none of them had one: they were built, shipped in the image and never run. Each now prints a success token, each has a scenario, and each was checked against a build with the loader logic it tests disabled — where it stops passing.The launcher's baked table and the manifests agree. The table said
Main/Games/Diagnosticsand every.sxressaidAccessories/System/Games/Diagnostics, and had done for months without anything failing, because the real catalog is rebuilt by scanning the manifests. A plan B that disagrees with the plan A is a second answer to the same question;progman-smokenow fails if a group exists in one and not the other.The visual smoke scenarios work on a virtio machine. They drove an absolute tablet with relative moves, so their clicks landed on the wrong window, and they compared screenshots pixel by pixel including the cursor glyph, so an editor that had not moved read as one that had.
sxgui-smoke --virtiowas failing on "the wheel did not scroll the editor".Moving the mouse over a virtio machine no longer freezes the VM. Letting the cursor into the QEMU window was enough for the guest to take
#14 page faultand halt. What the fix was about.smoke_runner_layout_test.pyruns again. It asserted two Media Player scenarios withdrawn with SxMedia, and aremove_pathsentry from beforelibffmpeg.so.0.4moved to/disk/lib, so it had been red and was guarding nothing.progman-smokepasses again. It was failing on its 180 s timeout, not on anything it asserts. Copying the 390 KB stamped fixture into the volume costs 365930 ms on its own — five metadata commits, each copying the whole 1537-sector metadata region twice — while the rest of the smoke adds up to 3 s. The timeout is now 900 s and the smoke prints what the copy cost, because a test that goes red without saying why is what made this read as a broken launcher.Relocated global pointers in a PIE no longer become a pointer to the image base.
R_X86_64_RELATIVEtook its value from memory, butlldleaves the slot at zero and stores the address in the relocation's addend. Everystdin/stdout/stderrand any other relocated global in a PIE program was affected.crt0no longer losesargcandargv. Running the library loader beforemainused the registers the kernel had put them in, so a program with arguments lost them. Every program that ran the loader with real work to do was affected.The interpreter path no longer overwrites an argument string. It was written above the
argvpointer array, where the argument strings live; a program could read a path instead of its own argument.PIE executables no longer declare a glibc interpreter path.
lldinserts/lib64/ld-linux-x86-64.so.2by default for-pie, a path from the host that builds and not one SavanXP has.A shared library no longer resolves its calls against the wrong image. All its
PT_LOADsegments now share one load bias, and each segment's file bytes land at theirp_vaddrinstead of at the start of the mapping. APLTin the text segment jumping through aGOTin the data segment was landing in unrelated code with no visible error.A library that calls nothing from outside now loads. A missing
DT_JMPREL/DT_PLTRELSZis skipped instead of treated as a failure.The mouse no longer drifts away from the host's. A
virtio-tabletevent now carries the true pointer position to userland, not only a delta, so an event dropped by a full/dev/mouse0queue re-places the cursor instead of leaving it offset for good.savanxp_mouse_eventgrowsabsolute_x,absolute_yandflags(SAVANXP_MOUSE_FLAG_ABSOLUTE);windowdassigns that position and still sums deltas for a relative mouse (PS/2). The rule the event used to break.The AC'97 driver measures its own feed. An
ac97-stats:line every ~11 s of audio over/dev/serial: wall time against audio actually delivered, periods discarded because the ring was full, underruns, and the longest gap between two writes. How to read it../build.sh smoke clock-smoke. Measuresuptime_msandmonotonic_nsagainst the RTC while spinning and while idle, and fails only if a clock changes rate with idleness. What it measured.Sockets that ported code can use.
recv()/send()work on streams,SO_RCVTIMEO/SO_SNDTIMEOgovern the wait through the newsetsockopt()(0 = wait forever, replacing a hardcoded 5 s cap on reads), and awrite()larger than one segment writes what fits instead of failing. A blocking socketread()now parks the process instead of spinning. The contract.A network adapter with no driver is reported as such.
system_infogainednet_hardwareand the PCI id of the adapter it found, so System Properties and the Task Manager stop saying "no adapter" on a machine that has one SavanXP cannot drive — VirtualBox's default Intel PRO/1000, for one. What to do about it.Task Manager (
/bin/taskmgr), in the System group. Live CPU, memory and handles per process with an End Process that asks first; meters, history graphs and totals in Performance; adapter throughput in Networking;File > New TaskandShut Down.proc_info/system_infogained the counters it reads (cpu_ticks,memory_bytes,cpu_ticks_total,memory_free_bytes). New./build.sh smoke taskmgr-smoke. How it measures.TCP survives a network that loses and reorders. Segments are kept and resent with backoff (
SYNincluded), out-of-order data is reassembled, and a dead connection now reportsECONNRESET/ETIMEDOUTinstead of a clean end of file. New./build.sh smoke tcp-smoke,NET_IOC_GET_TCP_STATSandNET_IOC_SET_TCP_FAULT. What it guarantees.The mouse wheel works. PS/2 negotiates IntelliMouse 4-byte packets (
ps2: mouse wheel enabled), virtio readsREL_WHEEL, and the newwheelfield ofsavanxp_gui_pointer_eventscrolls lists, text views and scrollbars under the cursor. Why it accumulates.The compositor measures itself.
windowdreports compose, sync and present times — present split intoipc/svc/gpu/tl, andblk_usfor the total time blocked oncompositord— plus damage area, over the new write-only/dev/serial. Newmonotonic_ns(); compositor protocol v2. How to read it.Gfx Demo has an auto-motion mode.
Smoves the box every frame without input, so the demo presents as fast as the compositor allows — the load that shows the display path's real ceiling.Every core runs user processes, under one big kernel lock. Each core the bootloader reports gets its own TSS, idle process and LAPIC timer (
smp: planificando en N de M cores);/bin/smptestin./build.sh smokeproves the overlap. Task Manager usage is now of all cores together. Unmapped kernel pages are flushed from every core's TLB (smp: TLB perezosa ok).--smp <n>still defaults to 1. Phases 0-3 of the SMP roadmap.Add or Remove Programs (
/bin/appwiz), in the System group. It lists what was installed outside the system image — in/disk/binand not in/bin— and deletes the binary, optionally with the data directory the program declares asdata_dir=in its.sxres. New./build.sh smoke appwiz-smoke. Why that is the rule.Program Manager lists every installed program on its own. A binary whose
.sxresdeclarescategory=shows up under that group with nothing to register: installing is copying it to/disk/bin, uninstalling is deleting it. Without the key it stays launchable, just unlisted.F5(File > Actualizar) rebuilds the catalog, and launching an entry that is gone says so instead of nothing. How it works.virtio-netdriver for the NIC. With--virtio,./build.shbuildsvirtio-net-pciinstead ofrtl8139, andnic::prefersvirtio_net::driver(). Polling-only, no IRQ of its own.--accel kvm.run/debugaccelerate against/dev/kvmwith-cpu hoston Linux. The smokes stay on TCG on purpose, to remain deterministic.Audio capture (
virtio-soundRX) and duplex/dev/audio0.read()has its own session owner, independent ofwrite(), so recording and playback can come from different processes. Newaudiotest --recordandvirtio-record.Keyboard over
virtio-input.--virtioaddsvirtio-keyboard-pci; itsEV_KEYevents become scancode set 1 (ps2::inject_scancode) and reuse the existing layout logic.ps2::disables its own keyboard while it is active.virtio-blkdriver for the persistent disk. With--virtiothe machine getsvirtio-blk-pciinstead of IDE, appearing inblock:asvblk0. It flushes after every write, or a "successful" write could stay in QEMU's cache.The file list shows an icon per type. The catalog is standalone
.sxiconblobs in/disk/icons, mapped by extension indiskfs/mimeicon.ini. This is extension to icon, not type detection:MIME_OPENis still unresolved.Video player on FFmpeg: MJPEG decoding, YUV->RGB through swscale, on screen.
./build.sh smoke ffmpeg-smokerunswavinfo, the player in--selftestand the player in--hold, which presents over/dev/gpu0.FFmpeg runs inside SavanXP. libavutil, libavcodec, libavformat and libswresample compile against this libc and really decode. Minimal codec set (WAV + PCM s16le), no asm and no threads; built separately with GNU make, see
ports/ffmpeg/README.md.static_assertin<assert.h>, thePRI*/SCN*in<inttypes.h>, andfpclassify. Plus the missing errnos (EDOM,EILSEQ,EPERM,ESPIPE),mkdir,F_SETFD/FD_CLOEXECandimaxabs/strtoimax/strtoumax../build.sh smokerunsimagetest. It inspects itself to verify what a streaming loader can break:.rodatabyte by byte,.bsszeroed and.datawith its initial values, from/binand from/disk/bin../build.sh smokerunsstacktest. Deep recursion in the process and in a child, an overflow that must die with a#PFon the guard page, and a 40-argumentargv.The libc gains what a port takes for granted. Strings, integers, aligned memory, stdio (
fgetc,ungetc,fdopen,setvbuf,fseeko,perror),sscanfwith scansets, and a full calendar. Everything is UTC.Real
%f,%eand%g, andstrtod.%fwas a stub that consumed the argument and wrote0. Now with precision, width, rounding carry and the odd cases (nan,inf, signed zero). All behind__SSE2__, like libm.libm: the missing ones.
asin,acos,hypot,cbrt,rint,lrint,lround,frexp,modf,scalbn,nextafter,fdim,fma,log1p,expm1, the hyperbolics,remainderand theirfloatvariants../build.sh smokerunslibctest. Exercises the libc surface a port consumes: standard names as struct fields,qsort/bsearch, streams, directories and the POSIX error convention (-1pluserrno).ES/EN keyboard layout selector, in the taskbar. US QWERTY joins the baked ES map, chosen at runtime through
/dev/input0(INPUT_IOC_SET_LAYOUT/GET_LAYOUT), saved in/disk/keyboard.cfgand applied at boot, before windowd. UI inkbdlayoutpopup.c../build.sh smoke kbd-smoke: the keyboard driver is tested on its own. It closes the real PS/2 -> IRQ ->kernel/ps2.cpp->/dev/input0path, which the harnesses that inject already-formed events do not cover../build.sh smoke pointer-smoke --virtio: the pointer is tested on its own. The pointer twin ofkbd-smoke:mousetest --selftestreads the real/dev/mouse0while the host moves the emulated tablet over QMP, and asserts the centre, a click and the opposite corner as screen positions. Nothing covered thevirtio-inputqueue:cursor-reproinjects events by hand.SXE stamping is no longer opt-in: everything built for the system comes out in that format. Coverage over the complete image is 68/68 installed binaries, in-tree and external (Doom, busybox) alike. Every program gets a
.sxmetawith or without a.sxres— name, version, subsystem and the short commit asBUILD_ID; icon, accent, description and mimes are never invented and stay.sxresenrichment.icon_file=in the manifest: the icon travels in the binary, not inassets/. It takes a PNG relative to the.sxresand derives the two sizes the runtime needs. Mutually exclusive withicon=.Floating point in userland, with
--sseand a libm of our own. Code usingfloat/doublecompiled but did NOT link: under-mno-sseclang resolves each operation through compiler-rt soft-float helpers this system does not have. The switch is opt-in, andmath.hdeclares the library under#if defined(__SSE2__)so the error appears at COMPILE time naming the function. New./build.sh smoke float-smoke.Tab control in sxgui (
sxgui_tabs). The active tab is drawn taller and covers the page's top border, so both parts read as one sheet. Program Manager's group selector now uses it.Taskbar at the bottom of the screen, listing open windows. It is a WM CLIENT (
/bin/taskbar), not windowd chrome — the explorer.exe model. No start menu and no notification area. Buttons keep a STABLE order by slot, not by z-order, or the next click would land on something else. What a client cannot know arrives over the new channels insavanxp/wm_shell_protocol.h.tools/shoot.sh: visual verification of the session, headless. It boots without a window, sends keys over QMP (holding modifiers, which the monitor'ssendkeycannot) and captures PNGs. Thetaskbarscenario verifies pixels and is wired as./build.sh smoke taskbar-smoke. Not part of the build.Alt+Tab to switch windows. While Alt is held each Tab moves the selection and releasing it confirms; Alt+Shift+Tab goes backwards. The switcher shown is the Task List: no new UI.
Text selection in sxgui, with Cut, Copy and Paste. Shift with the arrows, Home, End and the page keys extends the selection; click anchors, drag stretches. Ctrl+C, Ctrl+X, Ctrl+V and Ctrl+A; other Ctrl+letter combinations are consumed instead of typed, except with AltGr. Also exposed as
sxgui_textedit_copy,_cut,_paste,_select_alland_has_selection; Notepad gains its Edit menu.System clipboard, in
/dev/clipboard. The content is a value, not a stream: awritereplaces everything and areadreturns from the start. OverSAVANXP_CLIPBOARD_CAPACITY(8 KiB) it fails withENOSPC. SDK wrappersclipboard_set_text,_get_text,_get_infoand_clear.Closing a window no longer freezes the desktop while the program is still loading.
windowdreaped the client with a blockingwaitpidinside its event loop, so killing a program stuck in its loader stalled input, compose and present until it died.The native SXGUI smoke label is now distinct from the native GUI host.
sxguihostno longer reportsNATIVEGUI HOSTwhen its runner exits.CMake-generated UEFI images now include
startup.nsh. The file selectsfs0:\EFI\BOOT\BOOTX64.EFIfor firmware and removable-media boot paths.Linux
gpu-soaknow uses the legacy 96-iteration default. Explicit iteration counts derive a proportional timeout on both build frontends.DoomGeneric verifies its vendored upstream tree before building. The deterministic digest in
UPSTREAMrejects accidental source drift.build.sh --jobsnow applies to every CMake build target. Kernel, userland, ISO, smoke, run, debug, test and clean paths share the same limit.Linux QEMU launches honor
SAVANXP_QEMUand custom OVMF pairs. Normal runs and smoke scenarios use the same explicit toolchain overrides.Native Haxe
--forcenow clears stale generated artifacts. The normal path preserves reusable output directories instead of silently rebuilding from an identical clean state.BusyBox is built with stack canaries enabled. The port boots and runs its applets with the same runtime protection as other external applications.
SxFS directory reconstruction now uses a bounded 32-level depth. The visited-cycle check remains, while recursive frames stay within the kernel stack budget used by the current 32 KiB stacks.
SxFS now rejects data extents claimed by more than one inode. Metadata validation tracks a per-volume claim bitmap across the complete inode table.
SMEP and kernel write protection are enabled on supported processors. Every application processor establishes the same execution boundary; SMAP remains a separate follow-up.
Initramfs archives that exceed the VFS node limit now fail closed. The parser no longer silently discards entries and mounts a partial root filesystem.
RTL8139 accepts valid maximum-size frames and recovers from bad records. RX validation now accounts for the CRC header and wrap tail, and resynchronizes with the hardware pointer instead of repeatedly parsing one malformed frame.
NX is now enabled explicitly on every application processor. APs no longer depend on the per-core
EFER.NXEstate inherited from the bootloader.The keyboard-layout popup can change the active layout again. It opens
/dev/input0for the write access required by the setter and no longer persists a selection that the kernel rejected.Kernel stack-canary failures report the actual return address. The panic diagnostic no longer assumes a compiler-specific stack-frame offset.
User memory is no longer uniformly executable. NX is now enforced for data, stacks and shared sections; ELF segments must be valid, non-RWX and remain in the user half. Stack canaries cover kernel, in-tree, BusyBox and supported external SDK builds.
Malformed storage and boot images are rejected before use. SxFS extents, inode metadata and directory graphs, CPIO names/hex/trailers, GPU geometry and device-reported packet lengths are now bounded before they reach kernel memory.
Mutating device ioctls require a writable descriptor. Init and idle tasks cannot be killed, and reparented descendants no longer leave permanent zombies.
Restoring a maximized window no longer leaves residue on the wallpaper. Only the restored frame was repainted, so the rest of the maximized area kept stale pixels until something passed over it.
windowd-smokenow checks it.forkhands the child the floating-point registers too. It used to start with the clean FPU/SSE state the kernel seeds, so adoublelive across the call read as 0 in the child.forktestnow checks it.A command typed in the desktop Shell keeps its arguments. The parser tokenizes the line in place, and the window then handed that same line to
/bin/sh -c: only the first word survived, soecho holaprinted nothing.netinfoovervirtio-netno longer kills the machine on VirtualBox. A 64-bit virtio MMIO field now goes in two 32-bit halves: a single 64-bit access is what VirtualBox answers with a guru meditation. The rule.QEMU starts from a path with a space in it.
./build.sh run, smoke targets, andtools/shoot.shnow pass OVMF, disk, and log paths as quoted arguments, so QEMU cannot interpret half a path as a second drive.The kernel/userland build handles host paths with spaces. CMake and Ninja receive quoted include and output paths, so a directory containing whitespace is passed as one argument instead of being split.
Closing a window that was playing sound no longer mutes the machine.
windowdkills the client, so the close handler of/dev/audio0ran in the killer's context and the device stayed owned by a dead pid: every later program gotEBUSY. It is released by pid on exit, like the GPU session.The wall clock follows the ACPI PM timer on machines that expose a usable one. The TSC measures the host's time, not the virtual machine's: with VirtualBox under load it counted 561% of real time, and everything paced on it ran ahead — Doom offered three times the audio the device could take and the driver dropped the rest, which is what a chopped-up sound is. Needs a 32-bit PM timer; QEMU's is 24-bit and stays on the TSC. Why.
The kernel's wall clock is a free-running counter, not a count of timer interrupts.
uptime_msand every deadline —sleep_ms,polltimeouts, the TCP RTO — used to advance only when an interrupt was delivered, and on VirtualBox that delivery turns bursty once the machine idles: measured between 151 Hz and 4000 Hz with the timer set to 1000. Anything paced on it stalled and then fast-forwarded. How it was found.A process waiting in
poll()wakes on the event, not only on the next tick. Blockingpoll()left the compositor noticing a client's frame up to a tick late, and that latency landed on every frame drawn../build.sh buildno longer deletesbuild/disk.imgwhen it cannot read it. A locked image — a VM running on it — raised the same error as a corrupt one and fell into the branch that recreates it. It now reports and stops.poll()blocks instead of spinning, and an idle machine halts. The syscall parks the caller (WaitReason::poll) and the tick wakes it, so a process that only waits no longer books every tick of the wait: the idle desktop went fromwindowd 99%toidle 99%,CPU Usage: 0%, with average compose time ~4x lower.yieldnow halts for the idle process. What it found and how.Notepad's editor ignored the mouse wheel and its own scrollbar. The wheel dispatcher and the embedded-scrollbar hit test only knew about
SXGUI_LISTBOX/SXGUI_TEXTVIEW, not the editableSXGUI_TEXTEDIT; a click on the bar moved the caret instead of scrolling.tools/shoot.sh --scenario notepadwheelcovers it.tools/shoot.shguarded the wrong path for a planted automation spec. It checkedbuild/image/SMOKEwhile./build.shplantsbuild/rootfs/SMOKE, so after any smoke target the guest silently ran that harness, not the desktop.A blocking pipe read did not get the CPU when its data arrived. Only event waits asked for the preemptive wakeup, so a synchronous RPC over pipes waited for the round-robin. Present drops ~470x, and 1.4 to 5.8 fps.
The taskbar drew the generic icon for every window. The shell window list only carried a baked-in icon id, and that set is now just the generic one, so each button gets the window's own 16x16
.sxiconinstead.The ELF loader failed on two
PT_LOADs sharing a page.map_segment_pagestreated an already-mapped page as an error and the load died reporting "out of memory". The shared page now keeps the union of the permissions.argccould exceed the arguments that existed. The kernel copied at most 15 arguments but handed the process the originalargc, so a longerargvmade the program read pointers that were never written.time()returned the uptime, not the Unix epoch. It computeduptime_ms() / 1000with the RTC right there, so any derived date came out asstrtouland friends wrapped silently on overflow. A number larger thanULONG_MAXreturned garbage; it now saturates and setsERANGE.waitpid()was infinite recursion. The variadic macro in<sys/wait.h>expanded back into itself. Nothing in the tree reached it, but any port calling POSIXwaitpidate the 128 KiB stack with no net.A read-only SxFS volume became writable again once published.
sxfs::attach()set the status tomountedwithout looking at how the mount had ended, so a volume whose journal could not be recovered accepted writes. New./build.sh smoke sxfs-smokemounts a broken volume from a host test.Two windowd bugs uncovered by the keyboard selector popup. The click-down landed on the overlay window path, and the popup never entered the composed/retire signalling lists, so its second
gfx_present()hung forever.The SXE resource generator blurred an icon when enlarging it.
collect_icons_from_file()only detected the integer multiple when shrinking, so a 16x16 original derived its 32x32 with LANCZOS instead of NEAREST.The Type column in files was clipped when the scrollbar appeared. The width split subtracted a fixed 6 pixels instead of the two bevels plus the bar's 16. The bar is now always reserved, so columns no longer jump width.
Row text did not line up with its column label. The header cell's bevel eats two pixels the row was not subtracting.
In widgetsdemo the free-standing scrollbar overlapped the second column. The column origin was computed from the list width without counting the bar between the two.
Pressing Ctrl dropped the selection, so Ctrl+C copied nothing. A modifier key produces its own event with
asciiat zero, and the text widgets treat "any other key" as a reason to drop the selection. Modifiers no longer reach the widgets.