Skip to content
AtomicGaryBuseyPublic

About

DOOM running entirely inside a ServiceNow PDI — Dwasm/PrBoomX engine, leaderboard, one-command installer

Resources

Stars

0 stars

Watchers

0 watching

Forks

Latest commit

 

History

10 Commits

Folders and files

NameName
Last commit message
Last commit date
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

SNDoom

DOOM running entirely on the ServiceNow platform.

Every game asset, every byte of engine code, and every line of backend logic is stored in and served from native ServiceNow objects on a Personal Developer Instance. No external servers. No CDNs. Just ServiceNow doing things it was never designed to do.

Browser                            ServiceNow PDI
+-----------------------+          +--------------------------------+
|  Processor (sndoom.do)|<-------->|  sys_processor (raw HTML)      |
|  +------------------+ |          |                                |
|  | HTML5 Canvas     | |          |  Attachments on sys_ui_page:   |
|  | DOOM @ 60+ FPS   | |  XHR/   |   - dwasm_index.wasm  (1.9MB)  |
|  +------------------+ | fetch()  |   - dwasm_index.data  (4.5MB)  |
|  +------------------+ |<-------->|   - dwasm_index.js    (198KB)  |
|  | OPL2 Audio       | |          |   - bootstrap.js      (15KB)   |
|  | WebAudio API     | |          |                                |
|  +------------------+ |          |  Scripted REST API:            |
|  +------------------+ |  fetch() |   GET  /health                 |
|  | Leaderboard (F1) | |<-------->|   GET  /leaderboard            |
|  +------------------+ |          |   POST /leaderboard            |
|  Session Keepalive    |          |                                |
|  (15 min interval)    |          |  Script Include:               |
+-----------------------+          |   SNDoom_Leaderboard           |
                                   |                                |
                                   |  Table: u_x_sndoom_score       |
                                   +--------------------------------+

Features

  • Full DOOM gameplay -- shareware episode (Knee-Deep in the Dead), all nine levels
  • OPL2 synthesized music and sound effects via Dwasm/PrBoomX and SDL2_mixer
  • Leaderboard with server-side persistence (Scripted REST API + custom table)
  • One-command deployment to any ServiceNow PDI via tools/install.sh
  • Processor-based serving -- works for all users, not just admins
  • Session keepalive -- 15-minute heartbeat prevents the 60-90 minute session timeout
  • Save/load via Emscripten IDBFS (browser IndexedDB persistence)
  • Mouse capture with pointer lock, widescreen support, 60+ FPS capable
  • ServiceNow-themed Easter eggs in the browser console

Quick Start

Prerequisites

Tool Purpose Install
ServiceNow PDI Target platform Free at developer.servicenow.com
Emscripten WASM compilation brew install emscripten
CMake Build system brew install cmake
curl HTTP requests Pre-installed on macOS/Linux
python3 JSON processing in scripts Pre-installed on macOS/Linux

Build

The build is a two-phase process: first generate the engine resource WAD natively, then compile the full engine to WebAssembly.

1. Download the shareware WAD (if not already present):

curl -o src/engine/dwasm-src/wasm/fs/doom1.wad \
  https://distro.ibiblio.org/slitaz/sources/packages/d/doom1.wad

2. Native pre-build (generates prboomx.wad, the engine resource file):

cd src/engine/dwasm-src
mkdir -p build-native && cd build-native
cmake .. -DCMAKE_BUILD_TYPE=Release -DCMAKE_POLICY_VERSION_MINIMUM=3.5
make prboomwad -j$(sysctl -n hw.ncpu)
cp prboomx.wad ../wasm/fs/

3. Emscripten build (compiles PrBoomX to WebAssembly):

cd src/engine/dwasm-src
mkdir -p build && cd build
emcmake cmake .. -DCMAKE_BUILD_TYPE=Release -DCMAKE_POLICY_VERSION_MINIMUM=3.5
emmake make -j$(sysctl -n hw.ncpu)

This produces three artifacts in build/: index.js (198KB), index.wasm (1.9MB), and index.data (4.5MB).

Deploy

./tools/install.sh https://<instance>.service-now.com admin 'your_password'

Then open https://<instance>.service-now.com/sndoom.do in your browser (logged in).

The installer is idempotent -- run it again to update an existing deployment.

Controls

Key Action
Arrow keys Move / turn
Ctrl Fire
Space Use / open doors
Shift Run
1-7 Select weapon
Tab Automap
Escape Menu
F1 Leaderboard (SNDoom addition)

How It Works

SNDoom exploits a specific combination of ServiceNow platform features to create a browser environment clean enough to run a WASM game engine:

The Processor Pattern

Non-admin users can't navigate to sn_doom.do (a direct=true UI Page) without ServiceNow's Polaris/UI16 navigator intercepting and redirecting them. The solution is a Processor at sndoom.do that serves raw text/html via g_processor.writeOutput(), bypassing the framework entirely.

The processor also injects the user's CSRF token (gs.getSessionToken()) into the page as window.g_ck, so the bootstrap JS can authenticate all REST API calls (asset downloads, leaderboard) via the X-UserToken header. This eliminates the double-login problem.

The XML Parsing Trap

Even with direct=true, ServiceNow XML-parses the HTML field of UI Pages before serving them. Any inline JavaScript containing <, &&, or template literals causes a silent blank white page with zero console output. This is the single most disorienting failure mode on the platform.

The fix: never inline JavaScript. All JS is loaded from attachments via <script src>. Styles use CDATA blocks.

Asset Loading Chain

Browser hits sndoom.do
  |
  +-- Processor serves raw HTML with embedded g_ck token
  |
  +-- Inline XHR loads bootstrap JS from attachment (with X-UserToken)
  |     |
  |     +-- Bootstrap defines window.Module with locateFile()
  |     +-- Sets up leaderboard UI, keepalive timer, Easter eggs
  |     +-- Patches XMLHttpRequest to inject X-UserToken on all /api/ calls
  |     +-- Creates <script> to load Emscripten runtime from attachment
  |           |
  |           +-- Emscripten runtime (index.js) executes:
  |                 +-- Calls Module.locateFile('index.wasm')
  |                 |     -> fetch('/api/now/attachment/<id>/file')
  |                 |     -> WebAssembly.instantiate()
  |                 +-- Calls Module.locateFile('index.data')
  |                 |     -> fetch('/api/now/attachment/<id>/file')
  |                 |     -> Unpack to virtual filesystem
  |                 +-- Mount IDBFS at /dwasm/ for save persistence
  |                 +-- Start DOOM main loop
  |
  +-- Game running, all assets served from SN Attachment API

Leaderboard

Scores are stored in a custom ServiceNow table (u_x_sndoom_score) with seven columns: player name, level, kills, secrets, difficulty, completion time, and computed score. The score formula is kills*100 + secrets*500 - time*2.

The leaderboard UI is built in the bootstrap JS and toggled with F1. It fetches scores from and submits scores to a Scripted REST API at /api/sndoom/sndoom_api/leaderboard. The backend is a Script Include (SNDoom_Leaderboard) that performs GlideRecord CRUD.

ServiceNow Objects

The installer creates these platform objects:

Object SN Table Purpose
Processor (sndoom.do) sys_processor Serves game HTML with injected CSRF token
UI Page (sn_doom, direct=true) sys_ui_page HTML shell + attachment host (legacy entry point)
Bootstrap JS sys_attachment Module setup, leaderboard UI, XHR patch, Easter eggs
Emscripten Runtime sys_attachment WASM loader + preload manifest
DOOM Engine sys_attachment PrBoomX compiled to WebAssembly
Game Data sys_attachment doom1.wad + prboomx.wad + config
SNDoom API sys_ws_definition Scripted REST API at /api/sndoom/sndoom_api
GET /health sys_ws_operation Session keepalive endpoint
GET /leaderboard sys_ws_operation Fetch top scores
POST /leaderboard sys_ws_operation Submit a score
SNDoom_Leaderboard sys_script_include Server-side score CRUD
SNDoom_Ajax sys_script_include CSRF token provider
Score Table sys_db_object u_x_sndoom_score -- 7 custom columns

API Reference

Base URL: https://<instance>/api/sndoom/sndoom_api

GET /health

Session keepalive and health check. Returns:

{"result": {"status": "ok", "game": "SNDoom", "engine": "Dwasm/PrBoomX"}}

GET /leaderboard

Query parameters: ?level=E1M1&limit=10 (both optional)

Returns top scores sorted by score descending:

{
  "result": {
    "status": "ok",
    "count": 3,
    "scores": [
      {
        "player_name": "Doomguy",
        "level": "E1M1",
        "kills": 42,
        "secrets": 3,
        "difficulty": "Hurt Me Plenty",
        "completion_time": 180,
        "score": 5700,
        "created": "2026-03-23 20:15:00"
      }
    ]
  }
}

POST /leaderboard

Submit a score. Request body:

{
  "player_name": "Doomguy",
  "level": "E1M1",
  "kills": 42,
  "secrets": 3,
  "difficulty": "Hurt Me Plenty",
  "completion_time": 180,
  "score": 5700
}

Returns 201 on success with the new record's sys_id.

Project Structure

SNDoom/
+-- README.md                          # This file
+-- CLAUDE.md                          # AI agent context
+-- .env                               # PDI credentials (gitignored)
+-- .gitignore
+-- RFI/                               # Cross-agent research requests
|   +-- 001-platform-constraints.md    # Platform research questions
|   +-- 001-response.md                # Answers from Principal Researcher
+-- docs/
|   +-- architecture.md                # Detailed technical design
|   +-- development.md                 # Build guide and dev workflow
|   +-- deployment.md                  # Deployment reference
|   +-- api.md                         # REST API documentation
+-- src/
|   +-- servicenow/
|   |   +-- ui_pages/
|   |       +-- sn_doom.html                  # UI Page HTML shell
|   |       +-- sndoom_dwasm_bootstrap.js     # Bootstrap + leaderboard + Easter eggs
|   |       +-- sndoom_engine.js              # v1 engine (retired, no audio)
|   +-- engine/
|       +-- doom.wasm                         # v1 WASM binary (retired)
|       +-- doom-wasm-src/                    # v1 engine source (jacobenget/doom.wasm)
|       +-- dwasm-src/                        # v2 engine source (GMH-Code/Dwasm)
|           +-- build/                        # Emscripten output (index.js/wasm/data)
|           +-- build-native/                 # Native build (rdatawad -> prboomx.wad)
|           +-- wasm/fs/                      # Preloaded filesystem (WADs + config)
|           +-- src/                          # PrBoomX C source code
+-- tools/
    +-- install.sh                     # One-command PDI deployment
    +-- deploy.sh                      # Granular deployment helpers (legacy)

Engine History

SNDoom went through two engine iterations:

v1: doom.wasm (jacobenget/doom.wasm)

A minimal WASM port of DOOM with a clean 14-function interface (10 imports, 4 exports). Provided the initial proof of concept but had no audio support -- DOOM without the E1M1 music felt wrong. The v1 engine and its HTML shell (sn_doom.html + sndoom_engine.js) are preserved in the repo for reference.

v2: Dwasm/PrBoomX (GMH-Code/Dwasm)

The current engine. A full-featured PrBoomX port compiled to WebAssembly via Emscripten. Adds OPL2 synthesized music, sound effects via SDL2_mixer, mouse capture with pointer lock, widescreen support, and 60+ FPS capability. Emscripten handles the virtual filesystem, audio subsystem, and browser integration.

The tradeoff: the Emscripten build produces three artifacts that must be uploaded together (the JS contains a preload manifest with byte offsets into the data file), vs. v1's single self-contained WASM binary. The install script handles this automatically.

Background

SNDoom was built as a creative engineering challenge: can a 1993 first-person shooter run entirely within an enterprise IT service management platform? The answer is yes, with caveats.

The project was developed collaboratively by a human engineer and Claude Code AI agents. Platform constraints were researched via a formal Request for Information (RFI) process informed by prior hands-on ServiceNow platform experience.

Key platform discoveries during development:

  • direct=true UI Pages bypass Jelly but NOT XML parsing -- the most counterintuitive gotcha
  • Processors bypass the framework entirely but can't be created via REST API (ACL-locked) -- solved with a temporary server-side REST endpoint
  • ServiceNow injects no JavaScript libraries on direct=true pages -- a clean browser environment
  • PDI session timeout is 60-90 minutes of inactivity, but "inactivity" means no server calls -- a client-side game needs its own keepalive
  • CSP is instance-global, not per-page, but wasn't enforced on our Zurich Patch 6 PDI
  • The Attachment API serves binary files with correct Content-Types -- no workarounds needed

Platform Compatibility

Tested on ServiceNow Zurich Patch 6. The architecture should work on any recent release (Xanadu, Yokohama, Zurich) since it relies on stable platform features (UI Pages, Processors, Attachments, Scripted REST APIs, Script Includes, custom tables). CSP enforcement varies by release and instance configuration -- if WASM is blocked, the Scripted REST API response-header override is the fallback.

License

DOOM source code is GPL-2.0 (id Software). PrBoomX/Dwasm is GPL-2.0 (GMH-Code). doom.wasm is GPL-2.0 (jacobenget). The shareware DOOM1.WAD is freely distributable per id Software's original terms.

About

DOOM running entirely inside a ServiceNow PDI — Dwasm/PrBoomX engine, leaderboard, one-command installer

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages