Phase 47
Doom
m3OS can run DOOM as a real full-screen graphical application. The game loads its WAD data from disk, renders through the framebuffer path, accepts keyboard input for gameplay, and returns the system to a usable administration path when it exits.
Learning goal
Understand what a real graphical program needs from the OS beyond text output.
Category
showcase
Components touched
Builds on
Unlocked by this phase
Why this phase matters
m3OS can run DOOM as a real full-screen graphical application. The game loads its WAD data from disk, renders through the framebuffer path, accepts keyboard input for gameplay, and returns the system to a usable administration path when it exits.
How it works in m3OS
Phase 47 is where m3OS stops proving isolated subsystems and starts carrying a real graphical application end to end. It adds framebuffer mapping, raw scancode input, doomgeneric build integration, shareware WAD delivery on the ext2 image, and the debugging work needed to keep a real game responsive under QEMU. The end result is a working DOOM port with known remaining performance headroom.
How it builds on earlier phases
This phase builds on System Services and turns that foundation into Doom.
Implementation outline
- Define the documented userspace framebuffer and keyboard path used by the port.
- Implement or finish the platform layer for DOOM.
- Integrate the WAD and binary into the supported image/build flow.
- Validate launch, rendering, gameplay input, and exit/recovery behavior.
- Document what this phase proves and what it does not prove about the later GUI stack.
Task progress snapshot
0 of 81 tracked tasks are complete.
Documentation targets
- Documentation deliverables have not been listed yet.
Deferred follow-on work
- Multi-application composition and windowing
- Pointer-driven GUI policy
- Audio output for the graphical session
- Hardware-accelerated rendering
Source materials
Important code
Code spotlight cards
Code spotlight
`sys_framebuffer_info` syscall
and pixel format before it can render anything — this is the discovery mechanism. Why it matters: Userspace needs the framebuffer dimensions, pitch, bytes-per-pixel,. Focus on `sys_framebuffer_info` and `0x1002` in `kernel/src/arch/x86_64/syscall.rs` while you trace the snippet.
fn sys_framebuffer_info(buf_addr: u64, buf_len: u64) -> u64 {
const USER_LIMIT: u64 = 0x0000_8000_0000_0000;
const FB_INFO_SIZE: u64 = core::mem::size_of::<FbInfo>() as u64;
if buf_addr == 0 || buf_addr >= USER_LIMIT || buf_len < FB_INFO_SIZE {
return NEG_EINVAL;
}
let (width, height, stride, bpp, pixel_format) = match crate::fb::framebuffer_raw_info() {
Some(info) => info,
None => return NEG_EINVAL,
};
let pixel_format_val: u32 = match pixel_format {
bootloader_api::info::PixelFormat::Rgb => 0,
bootloader_api::info::PixelFormat::Bgr => 1,
_ => return NEG_EINVAL,
};
let info = FbInfo {
width: width as u32,
height: height as u32,
stride: stride as u32,
bpp: bpp as u32, Code spotlight
Syscall constants in `syscall-lib`
assembly or direct syscall invocation) need named constants for the new syscall. Why it matters: Rust userspace programs (and the C platform layer via inline. Focus on `syscall-lib` and `SYS_FRAMEBUFFER_INFO` in `userspace/syscall-lib/src/lib.rs` while you trace the snippet.
/// Phase 47: framebuffer info syscall.
pub const SYS_FRAMEBUFFER_INFO: u64 = 0x1005;
/// Phase 47: framebuffer mmap syscall.
pub const SYS_FRAMEBUFFER_MMAP: u64 = 0x1006; Code spotlight
Console yield function
same framebuffer memory, causing garbled output. Why it matters: Without this, the kernel text console and DOOM would race on the. Focus on `yield_console` and `write_str` in `kernel/src/fb/mod.rs` while you trace the snippet.
/// Suppresses all framebuffer console output.
///
/// For internal use only. External callers that need to claim the
/// framebuffer should use [`try_yield_console`] which performs an
/// atomic compare-and-swap ownership check.
fn yield_console(owner_pid: u32) {
FB_OWNER_PID.store(owner_pid, Ordering::Release);
crate::arch::x86_64::interrupts::reset_raw_input_state();
let _guard = CONSOLE.lock();
CONSOLE_YIELDED.store(true, Ordering::Release);
} Key files
- kernel/src/arch/x86_64/syscall.rs — Framebuffer syscalls, raw scancode syscall, TSC-backed gettimeofday() and short nanosleep() behavior
- kernel/src/arch/x86_64/interrupts.rs — Keyboard IRQ handling, raw scancode buffers, bounded i8042 drain loop
- kernel/src/fs/ext2.rs — WAD read hot path, enlarged block cache, zero-copy read_block_into_slice() helper
- kernel/src/fb/mod.rs — Framebuffer ownership, console yield/restore path
- userspace/doom/dg_m3os.c — m3OS doomgeneric platform layer: framebuffer init, blit path, key translation, timing wrappers
- userspace/doom/patches/i_input.c — Persistent local patch removing the upstream key-up drain asymmetry
- xtask/src/main.rs — doomgeneric clone/build flow, patch overlay, WAD population on the ext2 image
How real OSes differ
- Mature systems usually expose standardized graphics and input interfaces rather than one milestone app driving the first visible proof.
- Real desktops rely on compositors, richer input stacks, audio, toolkit layers, and often hardware acceleration.
- m3OS should use this phase to validate the substrate with a real workload, not to freeze the wrong long-term abstraction.
Success criteria
- DOOM or the chosen equivalent graphical workload launches from m3OS and renders through the documented framebuffer path.
- The application loads its assets from the supported storage path without undocumented host-side tricks.
- Interactive keyboard input works well enough for real use of the application.
- Exiting or crashing the graphical program returns the system to a usable administration path.
- The docs explicitly describe this milestone as a graphics proof point, not a complete GUI stack.
Related phases
Keep exploring the roadmap
Phase 46
CompleteSystem Services
The OS has a service manager, system logging, and scheduled task execution. Services (sshd, telnetd, etc.) are managed by a unified init/service system that handles startup ordering, restart on failure, and clean shutdown. The OS behaves like a real server that can be administered remotely.
Phase 48
CompleteSecurity Foundation
m3OS closes the trust-floor gaps that currently block any honest claim of "safe enough for serious headless use." When this phase lands, identity transitions, password storage, entropy, and default remote-exposure behavior all match the project's intended multi-user and network-facing story.