# Building Omarchy Racer: Engineering an In-Bar Retro Engine for Omarchy Quattro > **A deep dive into crafting a zero-latency, embedded gaming popup inside Omarchy's Wayland desktop environment.** --- ## πŸ“– Table of Contents 1. [Executive Summary](#-executive-summary) 2. [The Development Journey](#-the-development-journey) - [Stage 1: Architecture Dissection & Container Isolation](#stage-1-architecture-dissection--container-isolation) - [Stage 2: The Blank Quickshell Container](#stage-2-the-blank-quickshell-container) - [Stage 3: Porting & Rewriting the Game Engine](#stage-3-porting--rewriting-the-game-engine) - [Stage 4: Edge-to-Edge Proportions & Dynamic Square Aspect Ratio](#stage-4-edge-to-edge-proportions--dynamic-square-aspect-ratio) - [Stage 5: Status Bar Placement & Optical Glyph Centering](#stage-5-status-bar-placement--optical-glyph-centering) - [Stage 6: Public Release & Open Source Packaging](#stage-6-public-release--open-source-packaging) - [Stage 7: OutRun 3D Road Racer & Clutter-Free Hotkey Switching](#stage-7-outrun-3d-road-racer--clutter-free-hotkey-switching) - [Stage 8: The DOOM Integration Phase (Native vs WebAssembly Architecture)](#stage-8-the-doom-integration-phase-native-vs-webassembly-architecture) - [Stage 9: The In-Panel DOOM Raycaster (v2.0)](#stage-9-the-in-panel-doom-raycaster-v20) - [Stage 10: Zen Toy β€” Falling Sand](#stage-10-zen-toy--falling-sand) - [Stage 11: Authentic Classics β€” Porting Jake Gordon's Arcade Suite](#stage-11-authentic-classics--porting-jake-gordons-arcade-suite-v21) 3. [Technical Architecture](#-technical-architecture) --- ## 🌟 Executive Summary **Omarchy Racer** is a standalone, lightweight, zero-latency desktop widget for [Omarchy Quattro](https://omarchy.org). It embeds retro classicsβ€”**Jake Gordon's OutRun 3D Road Racer**, the iconic **Chrome Dinosaur Runner**, a fully in-panel **DOOM-style raycasting shooter**, and three relaxing **zen toys**β€”directly inside the Wayland status bar. Unlike traditional Linux game launchers that spawn heavy X11/Wayland windows or external processes, Omarchy Arcade renders **100% inside Quickshell's hardware-accelerated layer-shell popup** using pure QML + vanilla JavaScript. It uses 0% CPU and negligible RAM when closed, makes 0 network calls, and responds instantly upon clicking the status bar icon. | Key Metric | Value | | :--- | :--- | | **Target Shell** | Omarchy Quattro / Quickshell (Qt 6 QML) | | **Engine** | Pure V8 JavaScript Physics + Qt Quick GPU Scene Graph | | **FPS Target** | 60 FPS Ultra-Smooth | | **Included Games** | OutRun 3D Racer *(Default)*, Chrome Dino, DOOM Raycaster, Tetris, Snakes, Breakout *(v2.1)* + Falling Sand | | **Switching** | Instant hotkey (`Tab` or `G`) with 0 on-screen UI clutter | | **External Binaries / Network Calls** | **0 / 0** (Pure QML + JS) | | **Idle CPU / RAM** | 0.0% CPU / < 4 MB RAM | | **GitHub Repository** | [https://github.com/oppenheimer-rick/omarchy-racer](https://github.com/oppenheimer-rick/omarchy-racer) | --- ## πŸ› οΈ The Development Journey ### Stage 1: Architecture Dissection & Container Isolation We started by studying how first-party Omarchy widgets and community plugins interface with `Bar.qml`, `WidgetButton.qml`, and `KeyboardPanel.qml`. ![Studying the Screen Time reference widget](docs/images/stage1_study.png) #### Key Architectural Findings: 1. **`BarWidget` Presentation**: Status bar items must pass `implicitWidth: button.implicitWidth` and `implicitHeight: button.implicitHeight` to prevent zero-width parent layout collapses. 2. **Window Layer Shell**: `KeyboardPanel` from `qs.Ui` provides Wayland-native window anchoring under the bar icon with automatic outside-click dismissal. 3. **Keyboard Focus Prime**: `PanelKeyCatcher` captures keypresses (`SPACE`, `Arrows`, `WASD`, `ESC`) without leaking keystrokes to background desktop applications. --- ### Stage 2: The Blank Quickshell Container Next, we completely gutted all non-game code (database scripts, tracking logic, metrics rows, complex settings) to leave a pristine, blank container frame ready to host our game engine. ![Clean blank container popup](docs/images/stage2_container.png) ``` β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”‚ 1. Panel (qs.Ui Base) β”‚ β”‚ β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”‚ β”‚ β”‚ 2. KeyboardPanel (Wayland Layer Surface) β”‚ β”‚ β”‚ β”‚ β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”‚ β”‚ β”‚ β”‚ β”‚ 3. PanelKeyCatcher (Focus & Keystroke Trap) β”‚ β”‚ β”‚ β”‚ β”‚ β”‚ β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”‚ β”‚ β”‚ β”‚ β”‚ β”‚ β”‚ 4. Blank Viewport / Game Canvas β”‚ β”‚ β”‚ β”‚ β”‚ β”‚ β”‚ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β”‚ β”‚ β”‚ β”‚ β”‚ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β”‚ β”‚ β”‚ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β”‚ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ ``` --- ### Stage 3: Porting & Rewriting the Game Engine We started with the Chrome Dinosaur runner, porting the physics state machine into **pure V8 JavaScript (`Model.js`)** and native **Qt Quick Image sprites (`DinoGame.qml`)**: - Ported authentic jump trajectories, gravity arcs, and ducking hitboxes. - Tested using a Node.js automated test suite (`test/model.test.mjs`), achieving passing unit tests. ![Authentic Dino game running edge-to-edge](docs/images/stage3_dino_game.png) --- ### Stage 4: Edge-to-Edge Proportions & Dynamic Square Aspect Ratio We re-anchored coordinates and built a **dynamic square viewport (380Γ—320)** that automatically adapts to any container aspect ratio without empty voids. ![Dynamic square gameplay](docs/images/stage4_gameplay.png) --- ### Stage 5: Status Bar Placement & Optical Glyph Centering We observed that standard FontAwesome icons (`fa-dragon`) are horizontally elongated (1.26:1 ratio), creating uneven gaps next to adjacent status bar indicators. ![Bar icon alignment](docs/images/stage5_bar_icons.png) #### Solution: - Inspected the exact glyph metrics in `JetBrainsMono Nerd Font` using Python `fontTools`. - Configured **`BarIconButton`** with uniform 1:1 square bounding boxes: - **Retro Space Invader (`σ°―‰` / `\udb82\udfc9`)** - **Square Pixel Dragon (`σ°Ό’` / `\udb80\udf22`)** - **Handheld Game Boy (`σ±Ž“` / `\udb84\udf93`)** - Added configurable placement in `manifest.json` enabling 1-click toggling between **Right** and **Center** bar sections. --- ### Stage 6: Public Release & Open Source Packaging - Cleaned up all author references and initialized git version control. - Created and pushed the public repository: [https://github.com/oppenheimer-rick/omarchy-racer](https://github.com/oppenheimer-rick/omarchy-racer). - Fully validated with `omarchy plugin validate` (Exit code 0). --- ### Stage 7: OutRun 3D Road Racer & Clutter-Free Hotkey Switching To expand beyond a single title into an arcade vault without adding visual clutter: 1. **Ported Jake Gordon's OutRun 3D Engine (`RacerModel.js` & `RacerGame.qml`)**: - 3D perspective projection (`project(p, cameraX, cameraY, cameraZ, cameraDepth, roadWidth)`) - Curvature acceleration, centrifugal force simulation, and procedural hill elevation arcs. - Parallax sky, hills, and tree background layer scrolling with `background.png`. - Sliced roadside billboards (`Code inComplete`, `LiquidPlanner`), palm trees, and AI opponent traffic cars from `sprites.png`. 2. **Zero-UI Hotkey Switching**: - Stripped away on-screen buttons and menus. - Pressing **`Tab`** or **`G`** instantly toggles between **OutRun Racer** and **Dino Runner** seamlessly. --- ### Stage 8: The DOOM Integration Phase (Native vs. WebAssembly Architecture) For 3D titles like DOOM, we analyzed two leading open-source approaches: #### 1. Native Source-Port Launcher ([Deoxizn/omarchy-doom](https://github.com/Deoxizn/omarchy-doom)) - **How it works**: Uses `Quickshell.Io.Process` to launch a high-performance Wayland/OpenGL source port (`doomretro`, `chocolate-doom`, `gzdoom`). - **WAD Detection**: Automatically scans standard paths (`~/Games/doom`, `~/.local/share/doom`, `/usr/share/doom`) for `DOOM.WAD` or `DOOM1.WAD`. - **Pros**: 120+ FPS, native controller support, full audio fidelity, zero WASM compilation overhead. #### 2. Embedded WebAssembly Port ([UstymUkhman/webDOOM](https://github.com/UstymUkhman/webDOOM)) - **How it works**: Compiles PrBoom C codebase to Emscripten WebAssembly (`doom1.wasm` + `doom1.data`). - **Pros**: Embeds directly inside the browser / canvas without spawning external processes. - **Trade-offs**: Requires downloading ~96 MB shareware WAD data package. > ⚠️ **Superseded in v2.0**: Both approaches were ultimately abandoned. WebAssembly cannot run inside Quickshell's QJSEngine (no WASM runtime), and external source-port windows break the core in-panel philosophy β€” they detach from the bar card, leak focus, and require users to own a WAD file. Stage 9 describes what shipped instead. --- ### Stage 9: The In-Panel DOOM Raycaster (v2.0) The v2.0 answer was to stop *launching* DOOM and start *being* DOOM: a fully self-contained, pure-JavaScript raycasting engine living entirely inside the panel. #### Why a Hand-Rolled Raycaster? - **QJSEngine has no WebAssembly runtime**, so compiled C ports were off the table. - **External windows violate the decompression philosophy** β€” the arcade must live and die with the bar popup, with zero process management, zero binaries, and zero assets to hunt down (no WADs). - A classic **DDA raycasting engine** is only a few hundred lines of vanilla JS β€” perfect for QJSEngine. #### How It Works: 1. **DDA Grid Traversal**: For every screen column, a ray is marched through the tile grid using the exact Digital Differential Analysis algorithm popularized by Wolfenstein 3D β€” no per-pixel stepping, just clean integer grid hops. 2. **Fixed Internal Resolution**: The scene is rendered at a fixed low internal resolution (e.g. 320Γ—200-style buffer) and **pixel-perfect upscaled** to fill the panel via QML Canvas `drawImage` scaling with smoothing disabled β€” crisp chunky pixels at any window size, including F11 fullscreen. 3. **Textured Walls**: Per-column texture slices are sampled from procedurally generated wall textures (no image assets), with distance-based fog/shading for depth. 4. **Sprite Demons & Hitscan Combat**: Billboard sprites are depth-sorted against the z-buffer per column; firing performs hitscan checks against enemy positions with hit feedback and death states. 5. **Handcrafted E1M1-Inspired Map**: An embedded ASCII map layout inspired by Hangar (E1M1) β€” rooms, corridors, ambush pockets, and demon spawns β€” compiled into the engine grid at load. 6. **Controls**: `W/S` or `↑/↓` move, `A/D` strafe, `←/β†’` turn, `SPACE` fires, `P` pauses, `R` restarts β€” routed through the existing `PanelKeyCatcher`. The result: authentic retro-shooter feel, **0 external binaries, 0 network calls, 0 asset downloads**, fully in-panel. --- ### Stage 10: Zen Toy β€” Falling Sand Not every break needs adrenaline. v2.0 rounds out the arcade with a low-stakes toy built on the same pure-JS + Canvas foundation: **πŸ–οΈ Falling Sand**: A classic falling-sand cellular automaton. `1/2/3/4` select Sand, Water, Wall, or Acid; paint with the mouse; watch sand pile, water flow, and acid dissolve walls. `C` clears the canvas. The timer suspends when the panel closes, preserving the plugin's 0.0%-idle-CPU guarantee. --- ### Stage 11: Authentic Classics β€” Porting Jake Gordon's Arcade Suite (v2.1) To keep every game *authentic* rather than approximated, v2.1 ports three classics directly from the original MIT-licensed sources β€” the same author behind the OutRun racer: 1. **🧱 Tetris** ([jakesgordon/javascript-tetris](https://github.com/jakesgordon/javascript-tetris)): faithful piece masks, rotation system, scoring (100/200/400/800), next-piece preview, and the exact speed curve (`0.6s βˆ’ 0.005s/row, min 0.1s`). 2. **🐍 Snakes** ([jakesgordon/javascript-snakes](https://github.com/jakesgordon/javascript-snakes)): grid court, growth-per-food, speed acceleration with cap, wall/self death, high-score persistence. 3. **🧨 Breakout** ([jakesgordon/javascript-breakout](https://github.com/jakesgordon/javascript-breakout)): angle-based paddle deflection (`dx = speedΒ·offset/(w/2)`), all 10 level layouts/palettes verbatim, run-merged brick scoring, lives and ball speed-up curves. Each port splits cleanly into a pure-JS model (`TetrisModel.js`, `SnakeModel.js`, `BreakoutModel.js`) plus a QML Canvas renderer, with dedicated Node test suites (`test/tetris.test.mjs`, `test/snake.test.mjs`, `test/breakout.test.mjs`) β€” 45 tests green across the plugin. --- ## πŸ—οΈ Technical Architecture ```mermaid graph TD BarWidget["BarWidget.qml (Top Bar)"] -->|Loads / Injects| Panel["Panel.qml (Popup Window)"] BarWidget -->|Displays| Icon["BarIconButton (Space Invader / 1:1 Glyph)"] Panel -->|Wraps| KeyCatcher["PanelKeyCatcher (Keystroke Router)"] KeyCatcher -->|Routes Tab/G| Switcher{"Active Game Selector"} Switcher -->|Racer Mode| Racer["RacerGame.qml (3D Road Engine)"] Switcher -->|Dino Mode| Dino["DinoGame.qml (2D Sprite Runner)"] Switcher -->|DOOM Mode| Doom["DoomGame.qml + DoomModel.js (Pure-JS DDA Raycaster)"] Switcher -->|Sand Mode| Sand["FallingSand.qml (Particle Cellular Automaton)"] Racer -->|Reads Math| RacerModel["RacerModel.js (3D Perspective Math)"] Racer -->|Draws Assets| RacerAssets["Assets/Racer/ (background.png, sprites.png)"] Dino -->|Reads Math| DinoModel["Model.js (Physics Engine)"] Dino -->|Draws Assets| DinoAssets["Assets/Dino/ (Sprites)"] Doom -->|Ray Marches| DoomMap["Embedded E1M1-Inspired Map (In-Code Grid)"] Doom -->|Pixel-Perfect Upscale| DoomCanvas["QML Canvas (Fixed Internal Resolution)"] ``` ## Game Licensing Appendix | Content | License | Notes | |---|---|---| | Native games (Dino, Snake, Racer, Sand, Snow Run, Summit, Breakout, Asteroids) | MIT / MIT-based ports | Breakout inspired by jakesgordon/javascript-breakout (MIT); all artwork CC0 (Kenney.nl) or procedural | | Web ports (Pac-Man, Hextris, 2048, DOOM, …) | **REMOVED** | All bundled web games and EmulatorJS ROM support removed 2026-08-24 β€” plugin is now 100% native QML | | Angry Birds assets | **NONE INCLUDED** | A slingshot homage existed briefly and was removed 2026-08-24 by maintainer decision; no Rovio assets ever shipped in a release | Snow Run player/tile art: Kenney Pixel Platformer pack (CC0, kenney.nl).