Download packages/coding-agent/examples/extensions/doom-overlay/doom-engine.ts from SaylorTwift/pi: direct link, hf CLI and curl.
- Browser
- Download file 4.91 kB
-
https://huggingface.co/SaylorTwift/pi/resolve/main/packages/coding-agent/examples/extensions/doom-overlay/doom-engine.ts
- Command line
-
hf download hf://SaylorTwift/pi/packages/coding-agent/examples/extensions/doom-overlay/doom-engine.ts
-
curl -L -o doom-engine.ts https://huggingface.co/SaylorTwift/pi/resolve/main/packages/coding-agent/examples/extensions/doom-overlay/doom-engine.ts
4.91 kB
| /** | |
| * DOOM Engine - WebAssembly wrapper for doomgeneric | |
| */ | |
| import { existsSync, readFileSync } from "node:fs"; | |
| import { createRequire } from "node:module"; | |
| import { dirname, join } from "node:path"; | |
| import { fileURLToPath } from "node:url"; | |
| export interface DoomModule { | |
| _doomgeneric_Create: (argc: number, argv: number) => void; | |
| _doomgeneric_Tick: () => void; | |
| _DG_GetFrameBuffer: () => number; | |
| _DG_GetScreenWidth: () => number; | |
| _DG_GetScreenHeight: () => number; | |
| _DG_PushKeyEvent: (pressed: number, key: number) => void; | |
| _malloc: (size: number) => number; | |
| _free: (ptr: number) => void; | |
| HEAPU8: Uint8Array; | |
| HEAPU32: Uint32Array; | |
| FS_createDataFile: (parent: string, name: string, data: number[], canRead: boolean, canWrite: boolean) => void; | |
| FS_createPath: (parent: string, path: string, canRead: boolean, canWrite: boolean) => string; | |
| setValue: (ptr: number, value: number, type: string) => void; | |
| getValue: (ptr: number, type: string) => number; | |
| } | |
| export class DoomEngine { | |
| private module: DoomModule | null = null; | |
| private frameBufferPtr: number = 0; | |
| private initialized = false; | |
| private wadPath: string; | |
| private _width = 640; | |
| private _height = 400; | |
| constructor(wadPath: string) { | |
| this.wadPath = wadPath; | |
| } | |
| get width(): number { | |
| return this._width; | |
| } | |
| get height(): number { | |
| return this._height; | |
| } | |
| async init(): Promise<void> { | |
| // Locate WASM build | |
| const __dirname = dirname(fileURLToPath(import.meta.url)); | |
| const buildDir = join(__dirname, "doom", "build"); | |
| const doomJsPath = join(buildDir, "doom.js"); | |
| if (!existsSync(doomJsPath)) { | |
| throw new Error(`WASM not found at ${doomJsPath}. Run ./doom/build.sh first`); | |
| } | |
| // Read WAD file | |
| const wadData = readFileSync(this.wadPath); | |
| const wadArray = Array.from(new Uint8Array(wadData)); | |
| // Load WASM module - eval to bypass jiti completely | |
| const doomJsCode = readFileSync(doomJsPath, "utf-8"); | |
| const moduleExports: { exports: unknown } = { exports: {} }; | |
| const nativeRequire = createRequire(doomJsPath); | |
| const moduleFunc = new Function("module", "exports", "__dirname", "__filename", "require", doomJsCode); | |
| moduleFunc(moduleExports, moduleExports.exports, buildDir, doomJsPath, nativeRequire); | |
| const createDoomModule = moduleExports.exports as (config: unknown) => Promise<DoomModule>; | |
| const moduleConfig = { | |
| locateFile: (path: string) => { | |
| if (path.endsWith(".wasm")) { | |
| return join(buildDir, path); | |
| } | |
| return path; | |
| }, | |
| print: () => {}, | |
| printErr: () => {}, | |
| preRun: [ | |
| (module: DoomModule) => { | |
| // Create /doom directory and add WAD | |
| module.FS_createPath("/", "doom", true, true); | |
| module.FS_createDataFile("/doom", "doom1.wad", wadArray, true, false); | |
| }, | |
| ], | |
| }; | |
| this.module = await createDoomModule(moduleConfig); | |
| if (!this.module) { | |
| throw new Error("Failed to initialize DOOM module"); | |
| } | |
| // Initialize DOOM | |
| this.initDoom(); | |
| // Get framebuffer info | |
| this.frameBufferPtr = this.module._DG_GetFrameBuffer(); | |
| this._width = this.module._DG_GetScreenWidth(); | |
| this._height = this.module._DG_GetScreenHeight(); | |
| this.initialized = true; | |
| } | |
| private initDoom(): void { | |
| if (!this.module) return; | |
| const args = ["doom", "-iwad", "/doom/doom1.wad"]; | |
| const argPtrs: number[] = []; | |
| for (const arg of args) { | |
| const ptr = this.module._malloc(arg.length + 1); | |
| for (let i = 0; i < arg.length; i++) { | |
| this.module.setValue(ptr + i, arg.charCodeAt(i), "i8"); | |
| } | |
| this.module.setValue(ptr + arg.length, 0, "i8"); | |
| argPtrs.push(ptr); | |
| } | |
| const argvPtr = this.module._malloc(argPtrs.length * 4); | |
| for (let i = 0; i < argPtrs.length; i++) { | |
| this.module.setValue(argvPtr + i * 4, argPtrs[i]!, "i32"); | |
| } | |
| this.module._doomgeneric_Create(args.length, argvPtr); | |
| for (const ptr of argPtrs) { | |
| this.module._free(ptr); | |
| } | |
| this.module._free(argvPtr); | |
| } | |
| /** | |
| * Run one game tick | |
| */ | |
| tick(): void { | |
| if (!this.module || !this.initialized) return; | |
| this.module._doomgeneric_Tick(); | |
| } | |
| /** | |
| * Get current frame as RGBA pixel data | |
| * DOOM outputs ARGB, we convert to RGBA | |
| */ | |
| getFrameRGBA(): Uint8Array { | |
| if (!this.module || !this.initialized) { | |
| return new Uint8Array(this._width * this._height * 4); | |
| } | |
| const pixels = this._width * this._height; | |
| const buffer = new Uint8Array(pixels * 4); | |
| for (let i = 0; i < pixels; i++) { | |
| const argb = this.module.getValue(this.frameBufferPtr + i * 4, "i32"); | |
| const offset = i * 4; | |
| buffer[offset + 0] = (argb >> 16) & 0xff; // R | |
| buffer[offset + 1] = (argb >> 8) & 0xff; // G | |
| buffer[offset + 2] = argb & 0xff; // B | |
| buffer[offset + 3] = 255; // A | |
| } | |
| return buffer; | |
| } | |
| /** | |
| * Push a key event | |
| */ | |
| pushKey(pressed: boolean, key: number): void { | |
| if (!this.module || !this.initialized) return; | |
| this.module._DG_PushKeyEvent(pressed ? 1 : 0, key); | |
| } | |
| isInitialized(): boolean { | |
| return this.initialized; | |
| } | |
| } | |