A 3D client for playing NetHack on web, Windows, macOS and Android. It uses a fork of @neth4ck/neth4ck to run NetHack compiled into WebAssembly, with a React hooks/Zustand frontend handling input, and game UI, with a Three.JS renderer. Supports NetHack 5.0, NetHack 3.6.7, and Slash'Em.
See the codeNetHack 3D lets you play classic NetHack in a fully interactive 3D dungeon.
Play in browser: https://jamesiv4.github.io/nethack-3d/
Note: The macOS desktop build is packaged as an unsigned DMG from GitHub Actions.
On first launch, macOS may ask you to right-click the app and choose Open.
On iPhone/iPad, open the play link in Safari, tap the Share button, then tap Add to Home Screen.
Launch it from your Home Screen for a web app fullscreen experience.
NetHack 3D <version>.AppImage from release/ to your Linux machine.--windowed: launch in a normal framed window instead of fullscreen.--borderless: launch in a frameless non-fullscreen window that covers the display without using native fullscreen mode.# so advanced playstyles are easy to manage, plus all commands available via buttons on mobile.npm inpm run devhttp://localhost:5173/npm run dev - Start Vite dev server.npm run check:tsc - Check TypeScript and subsystem contracts without building.npm test - Run regression tests, including engine input, world state, and rendering resources.npm run build - Build production bundles.npm run build:electron - Build bundles with Electron-safe relative asset paths.npm run preview - Preview production build locally.npm run electron:dev - Run Electron against the Vite dev server.npm run electron:pack:mac - Build an unpacked macOS app bundle to release/ for local testing.npm run electron:dist:mac - Build a macOS DMG to release/ (pass -- --universal to produce a universal macOS build).npm run electron:dist:win - Build and package a Windows NSIS .exe installer (x64) to release/.npm run electron:dist:win:portable - Build and package portable Windows .exe files for x64 and legacy x86 to release/; the 32-bit artifact ends in -legacy-x86.exe.npm run electron:dist:linux:appimage - Build and package a Linux AppImage (x64) to release/ (uses WSL automatically on Windows, stages Linux runtime deps, and includes Linux icon assets).npm run electron:dist:all - Build Electron assets once, then package the Windows setup, x64 and legacy x86 portable executables, and Linux AppImage back to back.npm run electron:dist:all:parallel - Same as above, but packages Windows and Linux at the same time after the shared Electron build.npm run android:add - Create the native Android project with Capacitor (run once).npm run android:sync - Build web assets and sync them into the Android project.npm run android:open - Open the Android project in Android Studio.npm run android:run - Build web assets and run on a connected Android device/emulator.npm run updates:package - Create build/client-updates/manifest.json + build payload from dist/ for in-app client updates.npm run update - Build + package the latest client-update payload in one command.npm run glyphs:generate - Regenerate glyph catalog from runtime artifacts.npm run glyphs:check - Verify checked-in glyph catalog is up to date.build/client-updates/manifest.json (or VITE_NH3D_UPDATE_MANIFEST_URL when set), prompts users when updates are available, and can download/apply the latest packaged web build.scripts/updates/prepare-client-update.mjs, which copies dist/ into build/client-updates/latest/ (rolling state) and writes SHA-verified file metadata.scripts/updates/channel-config.json:
requireClientUpgrade to true to force a full native client upgrade warning while still allowing web build download.clientUpgradeMessage for custom upgrade guidance text shown in the startup update dialog.npm run update when you want to prepare and publish a new online update from current source.src/main.tsx mounts App.tsx. The React UI feature modules own startup, dialogs, client options and interaction state; the app composition wires them to the engine and UI adapter. Nethack3DEngine.ts coordinates startup, runtime events, frame updates, options, and disposal, and preserves the public controller API used by the UI.
Rendering, input, camera, world presentation, menus, audio, and diagnostics live in focused classes under src/game/engine/. Each subsystem owns its state and declares the specific members it uses from neighboring systems. NetHack's authoritative game state and rules continue to run in WASM inside the worker.
LocalNetHackRuntime.ts coordinates the worker-facing API, callback dispatch, startup and shutdown. Its runtime subsystems own input waits, menus, map and status caches, callback decoding, and persistence, with explicit dependencies assembled before WASM starts.
flowchart LR
UI[React UI] -->|controller calls| Engine[Engine coordinator]
Engine --> Systems[Engine subsystems]
Systems -->|commands| Bridge[WorkerRuntimeBridge]
Bridge --> Runtime[LocalNetHackRuntime coordinator]
Runtime --> RuntimeSystems[Runtime subsystems]
RuntimeSystems <--> WASM[NetHack WASM]
Runtime -->|runtime events| Bridge
Bridge -->|runtime events| Engine
Engine -->|UI adapter and store| UI
Systems -->|UI adapter and store| UI
| Area | Entry point |
|---|---|
| Engine composition and dependencies | create-engine-systems.ts |
| Rendering and visual effects | rendering/, effects/ |
| Commands, devices, and camera | input/, camera/ |
| Terrain caches, tile updates, and entity movement | world/ |
| Menus, minimap, and status presentation | ui/ |
| Sound, haptics, and developer panels | audio/, diagnostics/ |
| React state bridge | engineUiAdapter.ts, gameStore.ts |
| Worker transport and host | WorkerRuntimeBridge.ts, runtime-worker.ts |
| Runtime API and subsystem assembly | LocalNetHackRuntime.ts, create-runtime-systems.ts |
| NetHack callbacks, input waits, state and persistence | Runtime ownership guide, RuntimeInputBroker.ts |
| Glyph classification and fallback catalogs | glyphs/ |
| Browser debug helpers | app.ts |
For task-to-file maps and ownership rules, start with the engine guide, runtime guide, and React UI guide. The world/runtime flow guide covers map updates, under-player items, level transitions, and movement. Contributor and agent references include the project structure, code hotspots, movement and input flow, and WASM pointer troubleshooting.
horlogeislux/tileto370 (MIT License).Settings > Pages.Source to GitHub Actions..github/workflows/deploy-gh-pages.yml (main by default).main (or run the workflow manually).The workflow builds with Vite and deploys the dist/ folder.
.github/workflows/build-macos-electron.yml builds the macOS Electron app on macos-latest.1.2.2 also upload the macOS DMG to the matching GitHub Release.TypeScript
92.1%
C
3.0%
SCSS
2.5%
JavaScript
1.9%
A 3D client for playing NetHack on web, Windows, macOS and Android. It uses a fork of @neth4ck/neth4ck to run NetHack compiled into WebAssembly, with a React hooks/Zustand frontend handling input, and game UI, with a Three.JS renderer. Supports NetHack 5.0, NetHack 3.6.7, and Slash'Em.
See the codeNetHack 3D lets you play classic NetHack in a fully interactive 3D dungeon.
Play in browser: https://jamesiv4.github.io/nethack-3d/
Note: The macOS desktop build is packaged as an unsigned DMG from GitHub Actions.
On first launch, macOS may ask you to right-click the app and choose Open.
On iPhone/iPad, open the play link in Safari, tap the Share button, then tap Add to Home Screen.
Launch it from your Home Screen for a web app fullscreen experience.
NetHack 3D <version>.AppImage from release/ to your Linux machine.--windowed: launch in a normal framed window instead of fullscreen.--borderless: launch in a frameless non-fullscreen window that covers the display without using native fullscreen mode.# so advanced playstyles are easy to manage, plus all commands available via buttons on mobile.npm inpm run devhttp://localhost:5173/npm run dev - Start Vite dev server.npm run check:tsc - Check TypeScript and subsystem contracts without building.npm test - Run regression tests, including engine input, world state, and rendering resources.npm run build - Build production bundles.npm run build:electron - Build bundles with Electron-safe relative asset paths.npm run preview - Preview production build locally.npm run electron:dev - Run Electron against the Vite dev server.npm run electron:pack:mac - Build an unpacked macOS app bundle to release/ for local testing.npm run electron:dist:mac - Build a macOS DMG to release/ (pass -- --universal to produce a universal macOS build).npm run electron:dist:win - Build and package a Windows NSIS .exe installer (x64) to release/.npm run electron:dist:win:portable - Build and package portable Windows .exe files for x64 and legacy x86 to release/; the 32-bit artifact ends in -legacy-x86.exe.npm run electron:dist:linux:appimage - Build and package a Linux AppImage (x64) to release/ (uses WSL automatically on Windows, stages Linux runtime deps, and includes Linux icon assets).npm run electron:dist:all - Build Electron assets once, then package the Windows setup, x64 and legacy x86 portable executables, and Linux AppImage back to back.npm run electron:dist:all:parallel - Same as above, but packages Windows and Linux at the same time after the shared Electron build.npm run android:add - Create the native Android project with Capacitor (run once).npm run android:sync - Build web assets and sync them into the Android project.npm run android:open - Open the Android project in Android Studio.npm run android:run - Build web assets and run on a connected Android device/emulator.npm run updates:package - Create build/client-updates/manifest.json + build payload from dist/ for in-app client updates.npm run update - Build + package the latest client-update payload in one command.npm run glyphs:generate - Regenerate glyph catalog from runtime artifacts.npm run glyphs:check - Verify checked-in glyph catalog is up to date.build/client-updates/manifest.json (or VITE_NH3D_UPDATE_MANIFEST_URL when set), prompts users when updates are available, and can download/apply the latest packaged web build.scripts/updates/prepare-client-update.mjs, which copies dist/ into build/client-updates/latest/ (rolling state) and writes SHA-verified file metadata.scripts/updates/channel-config.json:
requireClientUpgrade to true to force a full native client upgrade warning while still allowing web build download.clientUpgradeMessage for custom upgrade guidance text shown in the startup update dialog.npm run update when you want to prepare and publish a new online update from current source.src/main.tsx mounts App.tsx. The React UI feature modules own startup, dialogs, client options and interaction state; the app composition wires them to the engine and UI adapter. Nethack3DEngine.ts coordinates startup, runtime events, frame updates, options, and disposal, and preserves the public controller API used by the UI.
Rendering, input, camera, world presentation, menus, audio, and diagnostics live in focused classes under src/game/engine/. Each subsystem owns its state and declares the specific members it uses from neighboring systems. NetHack's authoritative game state and rules continue to run in WASM inside the worker.
LocalNetHackRuntime.ts coordinates the worker-facing API, callback dispatch, startup and shutdown. Its runtime subsystems own input waits, menus, map and status caches, callback decoding, and persistence, with explicit dependencies assembled before WASM starts.
flowchart LR
UI[React UI] -->|controller calls| Engine[Engine coordinator]
Engine --> Systems[Engine subsystems]
Systems -->|commands| Bridge[WorkerRuntimeBridge]
Bridge --> Runtime[LocalNetHackRuntime coordinator]
Runtime --> RuntimeSystems[Runtime subsystems]
RuntimeSystems <--> WASM[NetHack WASM]
Runtime -->|runtime events| Bridge
Bridge -->|runtime events| Engine
Engine -->|UI adapter and store| UI
Systems -->|UI adapter and store| UI
| Area | Entry point |
|---|---|
| Engine composition and dependencies | create-engine-systems.ts |
| Rendering and visual effects | rendering/, effects/ |
| Commands, devices, and camera | input/, camera/ |
| Terrain caches, tile updates, and entity movement | world/ |
| Menus, minimap, and status presentation | ui/ |
| Sound, haptics, and developer panels | audio/, diagnostics/ |
| React state bridge | engineUiAdapter.ts, gameStore.ts |
| Worker transport and host | WorkerRuntimeBridge.ts, runtime-worker.ts |
| Runtime API and subsystem assembly | LocalNetHackRuntime.ts, create-runtime-systems.ts |
| NetHack callbacks, input waits, state and persistence | Runtime ownership guide, RuntimeInputBroker.ts |
| Glyph classification and fallback catalogs | glyphs/ |
| Browser debug helpers | app.ts |
For task-to-file maps and ownership rules, start with the engine guide, runtime guide, and React UI guide. The world/runtime flow guide covers map updates, under-player items, level transitions, and movement. Contributor and agent references include the project structure, code hotspots, movement and input flow, and WASM pointer troubleshooting.
horlogeislux/tileto370 (MIT License).Settings > Pages.Source to GitHub Actions..github/workflows/deploy-gh-pages.yml (main by default).main (or run the workflow manually).The workflow builds with Vite and deploys the dist/ folder.
.github/workflows/build-macos-electron.yml builds the macOS Electron app on macos-latest.1.2.2 also upload the macOS DMG to the matching GitHub Release.TypeScript
92.1%
C
3.0%
SCSS
2.5%
JavaScript
1.9%