# markrussinovich/doompaint architecture

> Architecture diagram of the GitHub repository markrussinovich/doompaint, generated by GitDiagram from its file tree, README and a sample of source files (last updated 2026-08-02). It is an AI-made overview: check details against the source.

- Interactive diagram: https://gitdiagram.com/markrussinovich/doompaint
- Repository: https://github.com/markrussinovich/doompaint

## Overview

#### Purpose and entry points
MS Paint Doom runs ViZDoom headlessly, then displays real Doom frames by pasting them into Microsoft Paint as clipboard images. `run.bat` is the primary Windows launcher; it creates the environment, installs dependencies, and updates ViZDoom’s OpenAL DLL. Python orchestration starts in `mspaintdoom/main.py`; `run_debug.bat` enables live input diagnostics.

#### Game orchestration and timing
`mspaintdoom/main.py` owns startup, CLI options, lifecycle, pause/focus behavior, and the self-clocking render/paste loop. The engine advances independently at Doom’s 35 Hz; paste publication waits for both a new engine frame and evidence that Paint consumed the previous clipboard data. This keeps simulation real-time while limiting visible output to Paint’s throughput.
- `mspaintdoom/main.py`
- `mspaintdoom/engine_vzd.py`

#### ViZDoom engine boundary
`engine_vzd.py` configures and drives ViZDoom/ ZDoom: WAD selection, map, skill, resolution, action inputs, frame extraction, and engine sound reset behavior. It uses the bundled shareware IWAD by default; ViZDoom-provided Freedoom supports unavailable episodes/maps. The WAD is game content, not application logic.
- `mspaintdoom/engine_vzd.py`
- `wad/doom1.wad`
- `requirements.txt`

#### Paint output and window control
`paint_out.py` launches/manages Paint, prepares its canvas, chooses zoom, and executes the validated paste route. Preferred output is synthetic Ctrl+V; UI Automation’s Edit > Paste is the fallback after repeated failed consumptions. Frames remain floating selections; each subsequent paste implicitly commits the prior frame, preserving Paint undo history as frame history.
- `mspaintdoom/paint_out.py`
- `mspaintdoom/sendinput.py`
- `mspaintdoom/capture.py`

#### OLE clipboard serving
Instead of replacing clipboard contents per frame, `clipserve.py` exposes a persistent Windows OLE `IDataObject` whose image bytes are updated in place. Paint retrieves DIB data through `GetData`; this avoids freeing data during Paint’s asynchronous paste read. Clipboard consumption is also the pacing acknowledgment. Clipboard-loss detection permits another application’s copy operation while paused and reclaims ownership only when publishing resumes.
- `mspaintdoom/clipserve.py`
- `exp_ole_clip.py`

#### Input isolation and control translation
A low-level Windows keyboard hook captures gameplay keys only while Paint is foreground, preventing arrows, shortcuts, and selection movement from affecting Paint. Input is sampled at engine-tic cadence with tap latching. Ctrl+Z is specially reinjected so Paint undo remains available; failure to install the hook falls back to asynchronous key polling.
- `mspaintdoom/keys.py`
- `mspaintdoom/sendinput.py`
- `mspaintdoom/main.py`

#### Audio and platform dependencies
Engine effects use ViZDoom/OpenAL; startup and runtime recovery reset ZDoom sound when metering indicates silent output. Separate music handling reads WAD music lumps, converts MUS where required, and plays MIDI through Windows MCI, synchronized with focus pause state. This is Windows-specific software relying on Win32 input, clipboard/OLE, UI Automation, Paint, MCI, ViZDoom, and OpenAL. The launcher replaces ViZDoom’s older OpenAL with the included version for default-device tracking.
- `mspaintdoom/music.py`
- `third_party/OpenAL-COPYING.txt`
- `run.bat`

Main entry points: [`mspaintdoom/main.py`](https://github.com/markrussinovich/doompaint/blob/main/mspaintdoom/main.py) (Runtime orchestrator), [`mspaintdoom/engine_vzd.py`](https://github.com/markrussinovich/doompaint/blob/main/mspaintdoom/engine_vzd.py) (ViZDoom engine adapter), [`mspaintdoom/clipserve.py`](https://github.com/markrussinovich/doompaint/blob/main/mspaintdoom/clipserve.py) (OLE clipboard server), [`run.bat`](https://github.com/markrussinovich/doompaint/blob/main/run.bat) (Windows launcher)

## Diagram (Mermaid)

```mermaid
flowchart TD

subgraph group_launch["Windows launch"]
  node_launcher["Windows launcher<br/>batch launcher<br/>[run.bat]"]
  node_debug_launcher["Debug launcher<br/>batch launcher<br/>[run_debug.bat]"]
  node_requirements["Python dependencies<br/>dependency manifest<br/>[requirements.txt]"]
end

subgraph group_runtime["Game runtime"]
  node_main["Runtime orchestrator<br/>Python entry point<br/>[main.py]"]
  node_engine["ViZDoom engine adapter<br/>engine boundary<br/>[engine_vzd.py]"]
  node_vizdoom{{"ViZDoom / ZDoom<br/>game runtime"}}
  node_wad["Doom IWAD<br/>game content<br/>[doom1.wad]"]
end

subgraph group_paint["Paint integration"]
  node_paint_output["Paint controller<br/>Win32/UI Automation output<br/>[paint_out.py]"]
  node_clipboard["OLE clipboard server<br/>IDataObject provider<br/>[clipserve.py]"]
  node_paint_app{{"Microsoft Paint<br/>Windows application"}}
  node_sendinput["Synthetic input<br/>Win32 input bridge<br/>[sendinput.py]"]
  node_capture["Paint capture checks<br/>window inspection<br/>[capture.py]"]
  node_ole_experiment["OLE prototype<br/>clipboard experiment<br/>[exp_ole_clip.py]"]
end

subgraph group_input["Input and audio"]
  node_keyboard["Gameplay key isolation<br/>low-level keyboard hook<br/>[keys.py]"]
  node_music["WAD music playback<br/>MIDI/MCI audio<br/>[music.py]"]
  node_openal["Bundled OpenAL<br/>audio runtime dependency<br/>[OpenAL-COPYING.txt]"]
end

node_launcher -->|"installs"| node_requirements
node_debug_launcher -.->|"debug variant"| node_launcher
node_launcher -->|"starts"| node_main
node_launcher -->|"updates ViZDoom with"| node_openal
node_main -->|"drives tics and frames"| node_engine
node_engine -->|"configures and controls"| node_vizdoom
node_engine -->|"loads"| node_wad
node_main -->|"publishes new frames"| node_clipboard
node_clipboard -->|"consumption acknowledgment"| node_paint_output
node_paint_output -->|"requests Ctrl+V"| node_sendinput
node_sendinput -->|"pastes frames"| node_paint_app
node_paint_output -->|"validates paste route"| node_capture
node_capture -->|"confirms consumption"| node_clipboard
node_clipboard -->|"serves DIB via GetData"| node_paint_app
node_main -->|"samples at tic cadence"| node_keyboard
node_keyboard -->|"translates gameplay actions"| node_engine
node_keyboard -->|"reinjects Ctrl+Z"| node_sendinput
node_main -->|"controls focus pause"| node_music
node_music -->|"reads music lumps"| node_wad
node_engine -->|"uses effects audio"| node_openal
node_ole_experiment -.->|"informs implementation"| node_clipboard

click node_launcher "https://github.com/markrussinovich/doompaint/blob/main/run.bat"
click node_debug_launcher "https://github.com/markrussinovich/doompaint/blob/main/run_debug.bat"
click node_requirements "https://github.com/markrussinovich/doompaint/blob/main/requirements.txt"
click node_main "https://github.com/markrussinovich/doompaint/blob/main/mspaintdoom/main.py"
click node_engine "https://github.com/markrussinovich/doompaint/blob/main/mspaintdoom/engine_vzd.py"
click node_wad "https://github.com/markrussinovich/doompaint/blob/main/wad/doom1.wad"
click node_paint_output "https://github.com/markrussinovich/doompaint/blob/main/mspaintdoom/paint_out.py"
click node_clipboard "https://github.com/markrussinovich/doompaint/blob/main/mspaintdoom/clipserve.py"
click node_sendinput "https://github.com/markrussinovich/doompaint/blob/main/mspaintdoom/sendinput.py"
click node_capture "https://github.com/markrussinovich/doompaint/blob/main/mspaintdoom/capture.py"
click node_keyboard "https://github.com/markrussinovich/doompaint/blob/main/mspaintdoom/keys.py"
click node_music "https://github.com/markrussinovich/doompaint/blob/main/mspaintdoom/music.py"
click node_openal "https://github.com/markrussinovich/doompaint/blob/main/third_party/OpenAL-COPYING.txt"
click node_ole_experiment "https://github.com/markrussinovich/doompaint/blob/main/exp_ole_clip.py"

classDef toneNeutral fill:#f8fafc,stroke:#334155,stroke-width:1.5px,color:#0f172a
classDef toneBlue fill:#dbeafe,stroke:#2563eb,stroke-width:1.5px,color:#172554
classDef toneAmber fill:#fef3c7,stroke:#d97706,stroke-width:1.5px,color:#78350f
classDef toneMint fill:#dcfce7,stroke:#16a34a,stroke-width:1.5px,color:#14532d
classDef toneRose fill:#ffe4e6,stroke:#e11d48,stroke-width:1.5px,color:#881337
classDef toneIndigo fill:#e0e7ff,stroke:#4f46e5,stroke-width:1.5px,color:#312e81
classDef toneTeal fill:#ccfbf1,stroke:#0f766e,stroke-width:1.5px,color:#134e4a
class node_launcher,node_debug_launcher,node_requirements toneBlue
class node_main,node_engine,node_vizdoom,node_wad toneAmber
class node_paint_output,node_clipboard,node_paint_app,node_sendinput,node_capture,node_ole_experiment toneMint
class node_keyboard,node_music,node_openal toneRose
```

## Components

### Windows launch

- [Windows launcher](https://github.com/markrussinovich/doompaint/blob/main/run.bat): batch launcher · Creates the environment, installs dependencies, and updates OpenAL. · `run.bat`
- [Debug launcher](https://github.com/markrussinovich/doompaint/blob/main/run_debug.bat): batch launcher · Enables live input diagnostics. · `run_debug.bat`
- [Python dependencies](https://github.com/markrussinovich/doompaint/blob/main/requirements.txt): dependency manifest · `requirements.txt`

### Game runtime

- [Runtime orchestrator](https://github.com/markrussinovich/doompaint/blob/main/mspaintdoom/main.py): Python entry point · Owns lifecycle, focus/pause behavior, and the self-clocking publication loop. · `mspaintdoom/main.py`
- [ViZDoom engine adapter](https://github.com/markrussinovich/doompaint/blob/main/mspaintdoom/engine_vzd.py): engine boundary · Configures ZDoom and advances gameplay at 35 Hz. · `mspaintdoom/engine_vzd.py`
- **ViZDoom / ZDoom**: game runtime
- [Doom IWAD](https://github.com/markrussinovich/doompaint/blob/main/wad/doom1.wad): game content · Default bundled shareware game data. · `wad/doom1.wad`

### Paint integration

- [Paint controller](https://github.com/markrussinovich/doompaint/blob/main/mspaintdoom/paint_out.py): Win32/UI Automation output · Prepares Paint and chooses synthetic or UI Automation paste. · `mspaintdoom/paint_out.py`
- [OLE clipboard server](https://github.com/markrussinovich/doompaint/blob/main/mspaintdoom/clipserve.py): IDataObject provider · Updates image bytes in a persistent clipboard object and detects consumption. · `mspaintdoom/clipserve.py`
- **Microsoft Paint**: Windows application · Displays floating pasted frame selections and retains undo history.
- [Synthetic input](https://github.com/markrussinovich/doompaint/blob/main/mspaintdoom/sendinput.py): Win32 input bridge · `mspaintdoom/sendinput.py`
- [Paint capture checks](https://github.com/markrussinovich/doompaint/blob/main/mspaintdoom/capture.py): window inspection · Validates that Paint consumed pasted clipboard data. · `mspaintdoom/capture.py`
- [OLE prototype](https://github.com/markrussinovich/doompaint/blob/main/exp_ole_clip.py): clipboard experiment · Reference implementation for OLE clipboard behavior. · `exp_ole_clip.py`

### Input and audio

- [Gameplay key isolation](https://github.com/markrussinovich/doompaint/blob/main/mspaintdoom/keys.py): low-level keyboard hook · Captures keys only while Paint is foreground and falls back to polling. · `mspaintdoom/keys.py`
- [WAD music playback](https://github.com/markrussinovich/doompaint/blob/main/mspaintdoom/music.py): MIDI/MCI audio · Extracts music lumps and synchronizes playback with focus pause state. · `mspaintdoom/music.py`
- [Bundled OpenAL](https://github.com/markrussinovich/doompaint/blob/main/third_party/OpenAL-COPYING.txt): audio runtime dependency · Replaces ViZDoom's older library for device tracking. · `third_party/OpenAL-COPYING.txt`

## Connections

- Windows launcher → Python dependencies: installs
- Debug launcher → Windows launcher: debug variant
- Windows launcher → Runtime orchestrator: starts
- Windows launcher → Bundled OpenAL: updates ViZDoom with
- Runtime orchestrator → ViZDoom engine adapter: drives tics and frames
- ViZDoom engine adapter → ViZDoom / ZDoom: configures and controls
- ViZDoom engine adapter → Doom IWAD: loads
- Runtime orchestrator → OLE clipboard server: publishes new frames
- OLE clipboard server → Paint controller: consumption acknowledgment
- Paint controller → Synthetic input: requests Ctrl+V
- Synthetic input → Microsoft Paint: pastes frames
- Paint controller → Paint capture checks: validates paste route
- Paint capture checks → OLE clipboard server: confirms consumption
- OLE clipboard server → Microsoft Paint: serves DIB via GetData
- Runtime orchestrator → Gameplay key isolation: samples at tic cadence
- Gameplay key isolation → ViZDoom engine adapter: translates gameplay actions
- Gameplay key isolation → Synthetic input: reinjects Ctrl+Z
- Runtime orchestrator → WAD music playback: controls focus pause
- WAD music playback → Doom IWAD: reads music lumps
- ViZDoom engine adapter → Bundled OpenAL: uses effects audio
- OLE prototype → OLE clipboard server: informs implementation
