chore: drop non-code junk (pi-mono-reference, runtime state, root duplicates)
Remove files that should never have been tracked: - pi-mono-reference/: 688-file copy of an upstream monorepo, zero imports, used only as offline human reference - build/electron-builder.runtime.json: environment-specific build artifact with hardcoded local paths - state/cancel-signal-state.json: runtime state written by the app - prompt.txt, logo.svg (root): exact duplicates of the canonical copies under .planning/archive/ and assets/ Add state/ to .gitignore so future runtime state stays out.
This commit is contained in:
1 parent
34ca5b77f1
commit
1f7230fe87
691 files changed
+3
-190434
No files matched your search
@@ -1,2 +0,0 @@
|
||||
# Auto-downloaded on first run
|
||||
doom1.wad
|
||||
-132
@@ -1,132 +0,0 @@
|
||||
/**
|
||||
* DOOM Component for overlay mode
|
||||
*
|
||||
* Renders DOOM frames using half-block characters (▀) with 24-bit color.
|
||||
* Height is calculated from width to maintain DOOM's aspect ratio.
|
||||
*/
|
||||
|
||||
import type { Component } from "@mariozechner/pi-tui";
|
||||
import { isKeyRelease, type TUI } from "@mariozechner/pi-tui";
|
||||
import type { DoomEngine } from "./doom-engine.js";
|
||||
import { DoomKeys, mapKeyToDoom } from "./doom-keys.js";
|
||||
|
||||
function renderHalfBlock(
|
||||
rgba: Uint8Array,
|
||||
width: number,
|
||||
height: number,
|
||||
targetCols: number,
|
||||
targetRows: number,
|
||||
): string[] {
|
||||
const lines: string[] = [];
|
||||
const scaleX = width / targetCols;
|
||||
const scaleY = height / (targetRows * 2);
|
||||
|
||||
for (let row = 0; row < targetRows; row++) {
|
||||
let line = "";
|
||||
const srcY1 = Math.floor(row * 2 * scaleY);
|
||||
const srcY2 = Math.floor((row * 2 + 1) * scaleY);
|
||||
|
||||
for (let col = 0; col < targetCols; col++) {
|
||||
const srcX = Math.floor(col * scaleX);
|
||||
const idx1 = (srcY1 * width + srcX) * 4;
|
||||
const idx2 = (srcY2 * width + srcX) * 4;
|
||||
const r1 = rgba[idx1] ?? 0,
|
||||
g1 = rgba[idx1 + 1] ?? 0,
|
||||
b1 = rgba[idx1 + 2] ?? 0;
|
||||
const r2 = rgba[idx2] ?? 0,
|
||||
g2 = rgba[idx2 + 1] ?? 0,
|
||||
b2 = rgba[idx2 + 2] ?? 0;
|
||||
line += `\x1b[38;2;${r1};${g1};${b1}m\x1b[48;2;${r2};${g2};${b2}m▀`;
|
||||
}
|
||||
line += "\x1b[0m";
|
||||
lines.push(line);
|
||||
}
|
||||
return lines;
|
||||
}
|
||||
|
||||
export class DoomOverlayComponent implements Component {
|
||||
private engine: DoomEngine;
|
||||
private tui: TUI;
|
||||
private interval: ReturnType<typeof setInterval> | null = null;
|
||||
private onExit: () => void;
|
||||
|
||||
// Opt-in to key release events for smooth movement
|
||||
wantsKeyRelease = true;
|
||||
|
||||
constructor(tui: TUI, engine: DoomEngine, onExit: () => void, resume = false) {
|
||||
this.tui = tui;
|
||||
this.engine = engine;
|
||||
this.onExit = onExit;
|
||||
|
||||
// Unpause if resuming
|
||||
if (resume) {
|
||||
this.engine.pushKey(true, DoomKeys.KEY_PAUSE);
|
||||
this.engine.pushKey(false, DoomKeys.KEY_PAUSE);
|
||||
}
|
||||
|
||||
this.startGameLoop();
|
||||
}
|
||||
|
||||
private startGameLoop(): void {
|
||||
this.interval = setInterval(() => {
|
||||
try {
|
||||
this.engine.tick();
|
||||
this.tui.requestRender();
|
||||
} catch {
|
||||
// WASM error (e.g., exit via DOOM menu) - treat as quit
|
||||
this.dispose();
|
||||
this.onExit();
|
||||
}
|
||||
}, 1000 / 35);
|
||||
}
|
||||
|
||||
handleInput(data: string): void {
|
||||
// Q to pause and exit (but not on release)
|
||||
if (!isKeyRelease(data) && (data === "q" || data === "Q")) {
|
||||
// Send DOOM's pause key before exiting
|
||||
this.engine.pushKey(true, DoomKeys.KEY_PAUSE);
|
||||
this.engine.pushKey(false, DoomKeys.KEY_PAUSE);
|
||||
this.dispose();
|
||||
this.onExit();
|
||||
return;
|
||||
}
|
||||
|
||||
const doomKeys = mapKeyToDoom(data);
|
||||
if (doomKeys.length === 0) return;
|
||||
|
||||
const released = isKeyRelease(data);
|
||||
|
||||
for (const key of doomKeys) {
|
||||
this.engine.pushKey(!released, key);
|
||||
}
|
||||
}
|
||||
|
||||
render(width: number): string[] {
|
||||
// DOOM renders at 640x400 (1.6:1 ratio)
|
||||
// With half-block characters, each terminal row = 2 pixels
|
||||
// So effective ratio is 640:200 = 3.2:1 (width:height in terminal cells)
|
||||
// Add 1 row for footer
|
||||
const ASPECT_RATIO = 3.2;
|
||||
const MIN_HEIGHT = 10;
|
||||
const height = Math.max(MIN_HEIGHT, Math.floor(width / ASPECT_RATIO));
|
||||
|
||||
const rgba = this.engine.getFrameRGBA();
|
||||
const lines = renderHalfBlock(rgba, this.engine.width, this.engine.height, width, height);
|
||||
|
||||
// Footer
|
||||
const footer = " DOOM | Q=Pause | WASD=Move | Shift+WASD=Run | Space=Use | F=Fire | 1-7=Weapons";
|
||||
const truncatedFooter = footer.length > width ? footer.slice(0, width) : footer;
|
||||
lines.push(`\x1b[2m${truncatedFooter}\x1b[0m`);
|
||||
|
||||
return lines;
|
||||
}
|
||||
|
||||
invalidate(): void {}
|
||||
|
||||
dispose(): void {
|
||||
if (this.interval) {
|
||||
clearInterval(this.interval);
|
||||
this.interval = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
-173
@@ -1,173 +0,0 @@
|
||||
/**
|
||||
* 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;
|
||||
}
|
||||
}
|
||||
-104
@@ -1,104 +0,0 @@
|
||||
/**
|
||||
* DOOM key codes (from doomkeys.h)
|
||||
*/
|
||||
export const DoomKeys = {
|
||||
KEY_RIGHTARROW: 0xae,
|
||||
KEY_LEFTARROW: 0xac,
|
||||
KEY_UPARROW: 0xad,
|
||||
KEY_DOWNARROW: 0xaf,
|
||||
KEY_STRAFE_L: 0xa0,
|
||||
KEY_STRAFE_R: 0xa1,
|
||||
KEY_USE: 0xa2,
|
||||
KEY_FIRE: 0xa3,
|
||||
KEY_ESCAPE: 27,
|
||||
KEY_ENTER: 13,
|
||||
KEY_TAB: 9,
|
||||
KEY_F1: 0x80 + 0x3b,
|
||||
KEY_F2: 0x80 + 0x3c,
|
||||
KEY_F3: 0x80 + 0x3d,
|
||||
KEY_F4: 0x80 + 0x3e,
|
||||
KEY_F5: 0x80 + 0x3f,
|
||||
KEY_F6: 0x80 + 0x40,
|
||||
KEY_F7: 0x80 + 0x41,
|
||||
KEY_F8: 0x80 + 0x42,
|
||||
KEY_F9: 0x80 + 0x43,
|
||||
KEY_F10: 0x80 + 0x44,
|
||||
KEY_F11: 0x80 + 0x57,
|
||||
KEY_F12: 0x80 + 0x58,
|
||||
KEY_BACKSPACE: 127,
|
||||
KEY_PAUSE: 0xff,
|
||||
KEY_EQUALS: 0x3d,
|
||||
KEY_MINUS: 0x2d,
|
||||
KEY_RSHIFT: 0x80 + 0x36,
|
||||
KEY_RCTRL: 0x80 + 0x1d,
|
||||
KEY_RALT: 0x80 + 0x38,
|
||||
} as const;
|
||||
|
||||
import { Key, matchesKey, parseKey } from "@mariozechner/pi-tui";
|
||||
|
||||
/**
|
||||
* Map terminal key input to DOOM key codes
|
||||
* Supports both raw terminal input and Kitty protocol sequences
|
||||
*/
|
||||
export function mapKeyToDoom(data: string): number[] {
|
||||
// Arrow keys
|
||||
if (matchesKey(data, Key.up)) return [DoomKeys.KEY_UPARROW];
|
||||
if (matchesKey(data, Key.down)) return [DoomKeys.KEY_DOWNARROW];
|
||||
if (matchesKey(data, Key.right)) return [DoomKeys.KEY_RIGHTARROW];
|
||||
if (matchesKey(data, Key.left)) return [DoomKeys.KEY_LEFTARROW];
|
||||
|
||||
// WASD - check both raw char and Kitty sequences
|
||||
if (data === "w" || matchesKey(data, "w")) return [DoomKeys.KEY_UPARROW];
|
||||
if (data === "W" || matchesKey(data, Key.shift("w"))) return [DoomKeys.KEY_UPARROW, DoomKeys.KEY_RSHIFT];
|
||||
if (data === "s" || matchesKey(data, "s")) return [DoomKeys.KEY_DOWNARROW];
|
||||
if (data === "S" || matchesKey(data, Key.shift("s"))) return [DoomKeys.KEY_DOWNARROW, DoomKeys.KEY_RSHIFT];
|
||||
if (data === "a" || matchesKey(data, "a")) return [DoomKeys.KEY_STRAFE_L];
|
||||
if (data === "A" || matchesKey(data, Key.shift("a"))) return [DoomKeys.KEY_STRAFE_L, DoomKeys.KEY_RSHIFT];
|
||||
if (data === "d" || matchesKey(data, "d")) return [DoomKeys.KEY_STRAFE_R];
|
||||
if (data === "D" || matchesKey(data, Key.shift("d"))) return [DoomKeys.KEY_STRAFE_R, DoomKeys.KEY_RSHIFT];
|
||||
|
||||
// Fire - F key
|
||||
if (data === "f" || data === "F" || matchesKey(data, "f") || matchesKey(data, Key.shift("f"))) {
|
||||
return [DoomKeys.KEY_FIRE];
|
||||
}
|
||||
|
||||
// Use/Open
|
||||
if (data === " " || matchesKey(data, Key.space)) return [DoomKeys.KEY_USE];
|
||||
|
||||
// Menu/UI keys
|
||||
if (matchesKey(data, Key.enter)) return [DoomKeys.KEY_ENTER];
|
||||
if (matchesKey(data, Key.escape)) return [DoomKeys.KEY_ESCAPE];
|
||||
if (matchesKey(data, Key.tab)) return [DoomKeys.KEY_TAB];
|
||||
if (matchesKey(data, Key.backspace)) return [DoomKeys.KEY_BACKSPACE];
|
||||
|
||||
// Ctrl keys (except Ctrl+C) = fire (legacy support)
|
||||
const parsed = parseKey(data);
|
||||
if (parsed?.startsWith("ctrl+") && parsed !== "ctrl+c") {
|
||||
return [DoomKeys.KEY_FIRE];
|
||||
}
|
||||
if (data.length === 1 && data.charCodeAt(0) < 32 && data !== "\x03") {
|
||||
return [DoomKeys.KEY_FIRE];
|
||||
}
|
||||
|
||||
// Weapon selection (0-9)
|
||||
if (data >= "0" && data <= "9") return [data.charCodeAt(0)];
|
||||
|
||||
// Plus/minus for screen size
|
||||
if (data === "+" || data === "=") return [DoomKeys.KEY_EQUALS];
|
||||
if (data === "-") return [DoomKeys.KEY_MINUS];
|
||||
|
||||
// Y/N for prompts
|
||||
if (data === "y" || data === "Y" || matchesKey(data, "y") || matchesKey(data, Key.shift("y"))) {
|
||||
return ["y".charCodeAt(0)];
|
||||
}
|
||||
if (data === "n" || data === "N" || matchesKey(data, "n") || matchesKey(data, Key.shift("n"))) {
|
||||
return ["n".charCodeAt(0)];
|
||||
}
|
||||
|
||||
// Other printable characters (for cheats)
|
||||
if (data.length === 1 && data.charCodeAt(0) >= 32) {
|
||||
return [data.toLowerCase().charCodeAt(0)];
|
||||
}
|
||||
|
||||
return [];
|
||||
}
|
||||
-152
@@ -1,152 +0,0 @@
|
||||
#!/usr/bin/env bash
|
||||
# Build DOOM for pi-doom using doomgeneric and Emscripten
|
||||
|
||||
set -e
|
||||
|
||||
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||
PROJECT_ROOT="$(dirname "$SCRIPT_DIR")"
|
||||
DOOM_DIR="$PROJECT_ROOT/doom"
|
||||
BUILD_DIR="$PROJECT_ROOT/doom/build"
|
||||
|
||||
echo "=== pi-doom Build Script ==="
|
||||
|
||||
# Check for emcc
|
||||
if ! command -v emcc &> /dev/null; then
|
||||
echo "Error: Emscripten (emcc) not found!"
|
||||
echo ""
|
||||
echo "Install via Homebrew:"
|
||||
echo " brew install emscripten"
|
||||
echo ""
|
||||
echo "Or manually:"
|
||||
echo " git clone https://github.com/emscripten-core/emsdk.git ~/emsdk"
|
||||
echo " cd ~/emsdk && ./emsdk install latest && ./emsdk activate latest"
|
||||
echo " source ~/emsdk/emsdk_env.sh"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Clone doomgeneric if not present
|
||||
if [ ! -d "$DOOM_DIR/doomgeneric" ]; then
|
||||
echo "Cloning doomgeneric..."
|
||||
cd "$DOOM_DIR"
|
||||
git clone https://github.com/ozkl/doomgeneric.git
|
||||
fi
|
||||
|
||||
# Create build directory
|
||||
mkdir -p "$BUILD_DIR"
|
||||
|
||||
# Copy our platform file
|
||||
cp "$DOOM_DIR/doomgeneric_pi.c" "$DOOM_DIR/doomgeneric/doomgeneric/"
|
||||
|
||||
echo "Compiling DOOM to WebAssembly..."
|
||||
cd "$DOOM_DIR/doomgeneric/doomgeneric"
|
||||
|
||||
# Resolution - 640x400 is doomgeneric default, good balance of speed/quality
|
||||
RESX=${DOOM_RESX:-640}
|
||||
RESY=${DOOM_RESY:-400}
|
||||
|
||||
echo "Resolution: ${RESX}x${RESY}"
|
||||
|
||||
# Compile with Emscripten (no sound)
|
||||
emcc -O2 \
|
||||
-s WASM=1 \
|
||||
-s EXPORTED_FUNCTIONS="['_doomgeneric_Create','_doomgeneric_Tick','_DG_GetFrameBuffer','_DG_GetScreenWidth','_DG_GetScreenHeight','_DG_PushKeyEvent','_malloc','_free']" \
|
||||
-s EXPORTED_RUNTIME_METHODS="['ccall','cwrap','getValue','setValue','FS']" \
|
||||
-s ALLOW_MEMORY_GROWTH=1 \
|
||||
-s INITIAL_MEMORY=33554432 \
|
||||
-s MODULARIZE=1 \
|
||||
-s EXPORT_NAME="createDoomModule" \
|
||||
-s ENVIRONMENT='node' \
|
||||
-s FILESYSTEM=1 \
|
||||
-s FORCE_FILESYSTEM=1 \
|
||||
-s EXIT_RUNTIME=0 \
|
||||
-s NO_EXIT_RUNTIME=1 \
|
||||
-DDOOMGENERIC_RESX="$RESX" \
|
||||
-DDOOMGENERIC_RESY="$RESY" \
|
||||
-I. \
|
||||
am_map.c \
|
||||
d_event.c \
|
||||
d_items.c \
|
||||
d_iwad.c \
|
||||
d_loop.c \
|
||||
d_main.c \
|
||||
d_mode.c \
|
||||
d_net.c \
|
||||
doomdef.c \
|
||||
doomgeneric.c \
|
||||
doomgeneric_pi.c \
|
||||
doomstat.c \
|
||||
dstrings.c \
|
||||
f_finale.c \
|
||||
f_wipe.c \
|
||||
g_game.c \
|
||||
hu_lib.c \
|
||||
hu_stuff.c \
|
||||
i_cdmus.c \
|
||||
i_input.c \
|
||||
i_endoom.c \
|
||||
i_joystick.c \
|
||||
i_scale.c \
|
||||
i_sound.c \
|
||||
i_system.c \
|
||||
i_timer.c \
|
||||
i_video.c \
|
||||
icon.c \
|
||||
info.c \
|
||||
m_argv.c \
|
||||
m_bbox.c \
|
||||
m_cheat.c \
|
||||
m_config.c \
|
||||
m_controls.c \
|
||||
m_fixed.c \
|
||||
m_menu.c \
|
||||
m_misc.c \
|
||||
m_random.c \
|
||||
memio.c \
|
||||
p_ceilng.c \
|
||||
p_doors.c \
|
||||
p_enemy.c \
|
||||
p_floor.c \
|
||||
p_inter.c \
|
||||
p_lights.c \
|
||||
p_map.c \
|
||||
p_maputl.c \
|
||||
p_mobj.c \
|
||||
p_plats.c \
|
||||
p_pspr.c \
|
||||
p_saveg.c \
|
||||
p_setup.c \
|
||||
p_sight.c \
|
||||
p_spec.c \
|
||||
p_switch.c \
|
||||
p_telept.c \
|
||||
p_tick.c \
|
||||
p_user.c \
|
||||
r_bsp.c \
|
||||
r_data.c \
|
||||
r_draw.c \
|
||||
r_main.c \
|
||||
r_plane.c \
|
||||
r_segs.c \
|
||||
r_sky.c \
|
||||
r_things.c \
|
||||
s_sound.c \
|
||||
sha1.c \
|
||||
sounds.c \
|
||||
st_lib.c \
|
||||
st_stuff.c \
|
||||
statdump.c \
|
||||
tables.c \
|
||||
v_video.c \
|
||||
w_checksum.c \
|
||||
w_file.c \
|
||||
w_file_stdc.c \
|
||||
w_main.c \
|
||||
w_wad.c \
|
||||
wi_stuff.c \
|
||||
z_zone.c \
|
||||
dummy.c \
|
||||
-o "$BUILD_DIR/doom.js"
|
||||
|
||||
echo ""
|
||||
echo "Build complete!"
|
||||
echo "Output: $BUILD_DIR/doom.js and $BUILD_DIR/doom.wasm"
|
||||
-21
@@ -1,21 +0,0 @@
|
||||
var createDoomModule = (() => {
|
||||
var _scriptName = typeof document != 'undefined' ? document.currentScript?.src : undefined;
|
||||
if (typeof __filename != 'undefined') _scriptName = _scriptName || __filename;
|
||||
return (
|
||||
async function(moduleArg = {}) {
|
||||
var moduleRtn;
|
||||
|
||||
var Module=moduleArg;var readyPromiseResolve,readyPromiseReject;var readyPromise=new Promise((resolve,reject)=>{readyPromiseResolve=resolve;readyPromiseReject=reject});var ENVIRONMENT_IS_WORKER=false;var ENVIRONMENT_IS_NODE=true;if(ENVIRONMENT_IS_NODE){}var moduleOverrides={...Module};var arguments_=[];var thisProgram="./this.program";var quit_=(status,toThrow)=>{throw toThrow};var scriptDirectory="";function locateFile(path){if(Module["locateFile"]){return Module["locateFile"](path,scriptDirectory)}return scriptDirectory+path}var readAsync,readBinary;if(ENVIRONMENT_IS_NODE){var fs=require("fs");var nodePath=require("path");scriptDirectory=__dirname+"/";readBinary=filename=>{filename=isFileURI(filename)?new URL(filename):filename;var ret=fs.readFileSync(filename);return ret};readAsync=async(filename,binary=true)=>{filename=isFileURI(filename)?new URL(filename):filename;var ret=fs.readFileSync(filename,binary?undefined:"utf8");return ret};if(!Module["thisProgram"]&&process.argv.length>1){thisProgram=process.argv[1].replace(/\\/g,"/")}arguments_=process.argv.slice(2);quit_=(status,toThrow)=>{process.exitCode=status;throw toThrow}}else{}var out=Module["print"]||console.log.bind(console);var err=Module["printErr"]||console.error.bind(console);Object.assign(Module,moduleOverrides);moduleOverrides=null;if(Module["arguments"])arguments_=Module["arguments"];if(Module["thisProgram"])thisProgram=Module["thisProgram"];var wasmBinary=Module["wasmBinary"];var wasmMemory;var ABORT=false;var EXITSTATUS;var HEAP8,HEAPU8,HEAP16,HEAPU16,HEAP32,HEAPU32,HEAPF32,HEAP64,HEAPU64,HEAPF64;var runtimeInitialized=false;var isFileURI=filename=>filename.startsWith("file://");function updateMemoryViews(){var b=wasmMemory.buffer;Module["HEAP8"]=HEAP8=new Int8Array(b);Module["HEAP16"]=HEAP16=new Int16Array(b);Module["HEAPU8"]=HEAPU8=new Uint8Array(b);Module["HEAPU16"]=HEAPU16=new Uint16Array(b);Module["HEAP32"]=HEAP32=new Int32Array(b);Module["HEAPU32"]=HEAPU32=new Uint32Array(b);Module["HEAPF32"]=HEAPF32=new Float32Array(b);Module["HEAPF64"]=HEAPF64=new Float64Array(b);Module["HEAP64"]=HEAP64=new BigInt64Array(b);Module["HEAPU64"]=HEAPU64=new BigUint64Array(b)}function preRun(){if(Module["preRun"]){if(typeof Module["preRun"]=="function")Module["preRun"]=[Module["preRun"]];while(Module["preRun"].length){addOnPreRun(Module["preRun"].shift())}}callRuntimeCallbacks(onPreRuns)}function initRuntime(){runtimeInitialized=true;if(!Module["noFSInit"]&&!FS.initialized)FS.init();TTY.init();wasmExports["__wasm_call_ctors"]();FS.ignorePermissions=false}function postRun(){if(Module["postRun"]){if(typeof Module["postRun"]=="function")Module["postRun"]=[Module["postRun"]];while(Module["postRun"].length){addOnPostRun(Module["postRun"].shift())}}callRuntimeCallbacks(onPostRuns)}var runDependencies=0;var dependenciesFulfilled=null;function getUniqueRunDependency(id){return id}function addRunDependency(id){runDependencies++;Module["monitorRunDependencies"]?.(runDependencies)}function removeRunDependency(id){runDependencies--;Module["monitorRunDependencies"]?.(runDependencies);if(runDependencies==0){if(dependenciesFulfilled){var callback=dependenciesFulfilled;dependenciesFulfilled=null;callback()}}}function abort(what){Module["onAbort"]?.(what);what="Aborted("+what+")";err(what);ABORT=true;what+=". Build with -sASSERTIONS for more info.";var e=new WebAssembly.RuntimeError(what);readyPromiseReject(e);throw e}var wasmBinaryFile;function findWasmBinary(){return locateFile("doom.wasm")}function getBinarySync(file){if(file==wasmBinaryFile&&wasmBinary){return new Uint8Array(wasmBinary)}if(readBinary){return readBinary(file)}throw"both async and sync fetching of the wasm failed"}async function getWasmBinary(binaryFile){if(!wasmBinary){try{var response=await readAsync(binaryFile);return new Uint8Array(response)}catch{}}return getBinarySync(binaryFile)}async function instantiateArrayBuffer(binaryFile,imports){try{var binary=await getWasmBinary(binaryFile);var instance=await WebAssembly.instantiate(binary,imports);return instance}catch(reason){err(`failed to asynchronously prepare wasm: ${reason}`);abort(reason)}}async function instantiateAsync(binary,binaryFile,imports){if(!binary&&typeof WebAssembly.instantiateStreaming=="function"&&!ENVIRONMENT_IS_NODE){try{var response=fetch(binaryFile,{credentials:"same-origin"});var instantiationResult=await WebAssembly.instantiateStreaming(response,imports);return instantiationResult}catch(reason){err(`wasm streaming compile failed: ${reason}`);err("falling back to ArrayBuffer instantiation")}}return instantiateArrayBuffer(binaryFile,imports)}function getWasmImports(){return{env:wasmImports,wasi_snapshot_preview1:wasmImports}}async function createWasm(){function receiveInstance(instance,module){wasmExports=instance.exports;wasmMemory=wasmExports["memory"];updateMemoryViews();removeRunDependency("wasm-instantiate");return wasmExports}addRunDependency("wasm-instantiate");function receiveInstantiationResult(result){return receiveILine truncated
|
||||
|
||||
|
||||
return moduleRtn;
|
||||
}
|
||||
);
|
||||
})();
|
||||
if (typeof exports === 'object' && typeof module === 'object') {
|
||||
module.exports = createDoomModule;
|
||||
// This default export looks redundant, but it allows TS to import this
|
||||
// commonjs style module.
|
||||
module.exports.default = createDoomModule;
|
||||
} else if (typeof define === 'function' && define['amd'])
|
||||
define([], () => createDoomModule);
|
||||
BIN
Binary file not shown.
-72
@@ -1,72 +0,0 @@
|
||||
/**
|
||||
* pi-doom platform implementation for doomgeneric
|
||||
*
|
||||
* Minimal implementation - no sound, just framebuffer and input.
|
||||
*/
|
||||
|
||||
#include "doomgeneric.h"
|
||||
#include "doomkeys.h"
|
||||
#include <emscripten.h>
|
||||
#include <stdint.h>
|
||||
|
||||
// Key event queue
|
||||
#define KEY_QUEUE_SIZE 256
|
||||
static struct {
|
||||
int pressed;
|
||||
unsigned char key;
|
||||
} key_queue[KEY_QUEUE_SIZE];
|
||||
static int key_queue_read = 0;
|
||||
static int key_queue_write = 0;
|
||||
|
||||
// Get the framebuffer pointer for JS to read
|
||||
EMSCRIPTEN_KEEPALIVE
|
||||
uint32_t *DG_GetFrameBuffer(void) { return DG_ScreenBuffer; }
|
||||
|
||||
// Get framebuffer dimensions
|
||||
EMSCRIPTEN_KEEPALIVE
|
||||
int DG_GetScreenWidth(void) { return DOOMGENERIC_RESX; }
|
||||
|
||||
EMSCRIPTEN_KEEPALIVE
|
||||
int DG_GetScreenHeight(void) { return DOOMGENERIC_RESY; }
|
||||
|
||||
// Push a key event from JavaScript
|
||||
EMSCRIPTEN_KEEPALIVE
|
||||
void DG_PushKeyEvent(int pressed, unsigned char key) {
|
||||
int next_write = (key_queue_write + 1) % KEY_QUEUE_SIZE;
|
||||
if (next_write != key_queue_read) {
|
||||
key_queue[key_queue_write].pressed = pressed;
|
||||
key_queue[key_queue_write].key = key;
|
||||
key_queue_write = next_write;
|
||||
}
|
||||
}
|
||||
|
||||
void DG_Init(void) {
|
||||
// Nothing to initialize
|
||||
}
|
||||
|
||||
void DG_DrawFrame(void) {
|
||||
// Frame is in DG_ScreenBuffer, JS reads via DG_GetFrameBuffer
|
||||
}
|
||||
|
||||
void DG_SleepMs(uint32_t ms) {
|
||||
// No-op - JS handles timing
|
||||
(void)ms;
|
||||
}
|
||||
|
||||
uint32_t DG_GetTicksMs(void) {
|
||||
return (uint32_t)emscripten_get_now();
|
||||
}
|
||||
|
||||
int DG_GetKey(int *pressed, unsigned char *key) {
|
||||
if (key_queue_read != key_queue_write) {
|
||||
*pressed = key_queue[key_queue_read].pressed;
|
||||
*key = key_queue[key_queue_read].key;
|
||||
key_queue_read = (key_queue_read + 1) % KEY_QUEUE_SIZE;
|
||||
return 1;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
void DG_SetWindowTitle(const char *title) {
|
||||
(void)title;
|
||||
}
|
||||
@@ -1,74 +0,0 @@
|
||||
/**
|
||||
* DOOM Overlay Demo - Play DOOM as an overlay
|
||||
*
|
||||
* Usage: pi --extension ./examples/extensions/doom-overlay
|
||||
*
|
||||
* Commands:
|
||||
* /doom-overlay - Play DOOM in an overlay (Q to pause/exit)
|
||||
*
|
||||
* This demonstrates that overlays can handle real-time game rendering at 35 FPS.
|
||||
*/
|
||||
|
||||
import type { ExtensionAPI } from "@mariozechner/pi-coding-agent";
|
||||
import { DoomOverlayComponent } from "./doom-component.js";
|
||||
import { DoomEngine } from "./doom-engine.js";
|
||||
import { ensureWadFile } from "./wad-finder.js";
|
||||
|
||||
// Persistent engine instance - survives between invocations
|
||||
let activeEngine: DoomEngine | null = null;
|
||||
let activeWadPath: string | null = null;
|
||||
|
||||
export default function (pi: ExtensionAPI) {
|
||||
pi.registerCommand("doom-overlay", {
|
||||
description: "Play DOOM as an overlay. Q to pause and exit.",
|
||||
|
||||
handler: async (args, ctx) => {
|
||||
if (!ctx.hasUI) {
|
||||
ctx.ui.notify("DOOM requires interactive mode", "error");
|
||||
return;
|
||||
}
|
||||
|
||||
// Auto-download WAD if not present
|
||||
ctx.ui.notify("Loading DOOM...", "info");
|
||||
const wad = args?.trim() ? args.trim() : await ensureWadFile();
|
||||
|
||||
if (!wad) {
|
||||
ctx.ui.notify("Failed to download DOOM WAD file. Check your internet connection.", "error");
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
// Reuse existing engine if same WAD, otherwise create new
|
||||
let isResume = false;
|
||||
if (activeEngine && activeWadPath === wad) {
|
||||
ctx.ui.notify("Resuming DOOM...", "info");
|
||||
isResume = true;
|
||||
} else {
|
||||
ctx.ui.notify(`Loading DOOM from ${wad}...`, "info");
|
||||
activeEngine = new DoomEngine(wad);
|
||||
await activeEngine.init();
|
||||
activeWadPath = wad;
|
||||
}
|
||||
|
||||
await ctx.ui.custom(
|
||||
(tui, _theme, _keybindings, done) => {
|
||||
return new DoomOverlayComponent(tui, activeEngine!, () => done(undefined), isResume);
|
||||
},
|
||||
{
|
||||
overlay: true,
|
||||
overlayOptions: {
|
||||
width: "75%",
|
||||
maxHeight: "95%",
|
||||
anchor: "center",
|
||||
margin: { top: 1 },
|
||||
},
|
||||
},
|
||||
);
|
||||
} catch (error) {
|
||||
ctx.ui.notify(`Failed to load DOOM: ${error}`, "error");
|
||||
activeEngine = null;
|
||||
activeWadPath = null;
|
||||
}
|
||||
},
|
||||
});
|
||||
}
|
||||
-51
@@ -1,51 +0,0 @@
|
||||
import { existsSync, writeFileSync } from "node:fs";
|
||||
import { dirname, join, resolve } from "node:path";
|
||||
import { fileURLToPath } from "node:url";
|
||||
|
||||
// Get the bundled WAD path (relative to this module)
|
||||
const __dirname = dirname(fileURLToPath(import.meta.url));
|
||||
const BUNDLED_WAD = join(__dirname, "doom1.wad");
|
||||
const WAD_URL = "https://distro.ibiblio.org/slitaz/sources/packages/d/doom1.wad";
|
||||
|
||||
const DEFAULT_WAD_PATHS = ["./doom1.wad", "./DOOM1.WAD", "~/doom1.wad", "~/.doom/doom1.wad"];
|
||||
|
||||
export function findWadFile(customPath?: string): string | null {
|
||||
if (customPath) {
|
||||
const resolved = resolve(customPath.replace(/^~/, process.env.HOME || ""));
|
||||
if (existsSync(resolved)) return resolved;
|
||||
return null;
|
||||
}
|
||||
|
||||
// Check bundled WAD first
|
||||
if (existsSync(BUNDLED_WAD)) {
|
||||
return BUNDLED_WAD;
|
||||
}
|
||||
|
||||
// Fall back to default paths
|
||||
for (const p of DEFAULT_WAD_PATHS) {
|
||||
const resolved = resolve(p.replace(/^~/, process.env.HOME || ""));
|
||||
if (existsSync(resolved)) return resolved;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/** Download the shareware WAD if not present. Returns path or null on failure. */
|
||||
export async function ensureWadFile(): Promise<string | null> {
|
||||
// Check if already exists
|
||||
const existing = findWadFile();
|
||||
if (existing) return existing;
|
||||
|
||||
// Download to bundled location
|
||||
try {
|
||||
const response = await fetch(WAD_URL);
|
||||
if (!response.ok) {
|
||||
throw new Error(`HTTP ${response.status}`);
|
||||
}
|
||||
const buffer = await response.arrayBuffer();
|
||||
writeFileSync(BUNDLED_WAD, Buffer.from(buffer));
|
||||
return BUNDLED_WAD;
|
||||
} catch {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user