A browser-based WebGL voxel sandbox prototype with procedural terrain, chunk streaming, block editing, creative flight, and screenshot readiness. Built as a compact but convincing creative-mode slice with its own original visual identity (no copyrighted assets).
VOXL is an original indie prototype. It is not Minecraft, is not affiliated with Mojang/Microsoft, and uses no Minecraft assets, branding, or code.
A focused, playable voxel sandbox you can drop into in seconds: a main menu, a generated world with biomes (plains, forest, desert, mountains), water, beaches, trees and caves, gravity + jumping, creative flight, block breaking/placement, a hotbar, pause/settings, and clouds. The priority is a stable, playable, good-looking core rather than a huge superficial feature list. The world is deterministic from a seed and designed to compose well in screenshots.
Stack: Three.js + Vite + TypeScript, run with Bun.
| Choice | Why |
|---|---|
| Three.js | The right weight for a voxel game: direct BufferGeometry/index control for chunk meshing + face culling, a solid WebGL2 renderer, and lighting/sky primitives. Far lighter than a full engine like Babylon, far more capable than raw WebGL (which would force re-implementing matrices/meshing/shaders). |
| Vite | Instant dev server with HMR, optimized production build, and vite preview — zero config for a TS+Three app. |
| TypeScript | Voxel/world/meshing code has lots of data structures that benefit from typing; strict mode catches real bugs. |
| Bun | Required runtime/package manager; also runs the optional screenshot script. |
Why not something else?
- Raw WebGL — too much boilerplate (custom shader/matrix/meshing pipelines); would slow delivery and reduce quality. Too limited.
- Babylon.js — heavier full-game-engine abstraction (scene graph, inspectors, GUI) we don't need for a hand-rolled chunk mesher. Overkill.
- React / React-Three-Fiber — DOM/component abstraction adds overhead to a tight per-frame game loop and buys little for a fullscreen canvas game with custom UI overlays. Overkill here.
- A big engine (PlayCanvas, Phaser) — voxel meshing is custom anyway; an engine's 2D/scene conveniences don't help. Overkill.
The result is not overkill (Three is the minimum that gives us real meshing +
lighting) and not too limited (we get performant chunk meshes, sky shaders,
and clean TS). Dependencies are intentionally tiny: only three, plus
vite/typescript/@types/three in dev. Playwright is an optional
dev dependency for automated screenshots.
index.html DOM for all screens + HUD (single source of UI structure)
src/
main.ts Bootstraps the Game; exposes window.__voxl automation hook
constants.ts Tunables (chunk size, physics, reach, budgets)
types.ts Shared types (Settings, FaceDef, RaycastHit, GameState)
engine/
Renderer.ts WebGLRenderer wrapper (canvas, DPI, tone mapping, preserveDrawingBuffer)
Noise.ts Seeded Perlin noise + fBm (fully deterministic from seed)
Textures.ts Procedural 16px texture atlas drawn to canvas (no external assets)
Sky.ts Gradient sky dome shader + sun + ambient/hemi lights + scrolling clouds
Input.ts Keyboard/mouse state, mouse-look deltas, pointer lock, double-tap-space
Screenshot.ts Canvas → PNG (File System Access API, else download)
game/
Blocks.ts Block registry (tiles per face, solid/opaque/liquid flags) + hotbar
Chunk.ts 16×16×64 Uint8Array block store + dirty/version flags
ChunkMesher.ts Face-culled chunk geometry → opaque + transparent buffers (baked shading)
TerrainGenerator.ts Heightmap + biomes + caves + trees + water, deterministic from seed
World.ts Chunk map, streaming (gen+mesh budgets), get/setBlock, mesh lifecycle
Player.ts Camera, mouse-look, walk/fly, gravity, swept-AABB voxel collision, raycast
BlockRaycaster.ts Amanatides–Woo voxel DDA for precise block picking
Game.ts Orchestrator: loop, state machine, editing, hotbar, settings, screenshots
ui/
ui.css Cohesive indie-voxel visual identity
ScreenManager.ts DOM screen visibility + transitions
HUD.ts Crosshair, hotbar, fps/coords/mode badges, toast
Menus.ts Main menu / pause / controls / settings wiring
state/
Settings.ts Default settings + localStorage persistence
scripts/
screenshot.ts Optional Playwright capture for main-menu.png + in-game.png
Key engineering points
- Chunk meshing builds one indexed
BufferGeometryper chunk (opaque + a separate transparent pass for water). Only visible faces are emitted; faces are culled against opaque neighbors, and same-type transparent neighbors (water–water). Directional brightness is baked into vertex colors so the world reads clearly. - Streaming generates and meshes a budgeted number of chunks per frame (closest-first spiral) and unloads far chunks, disposing their geometry. Block edits mark the chunk (and border neighbors) dirty and rebuild meshes immediately.
- Collision is per-axis swept AABB against voxel solidity; fly/walk/swim states are all handled. Bedrock is unbreakable; placement inside the player is blocked.
- Determinism: terrain derives entirely from the seed string via seeded Perlin.
Requirements: Bun and a modern desktop browser (Chrome/Firefox/Edge/Safari).
bun install # install dependencies
bun run dev # start BOTH dev servers (game + website)bun run dev runs a small orchestrator (scripts/dev.ts) that launches two
processes with prefixed logs and one Ctrl-C to stop both:
| Tag | Server | URL |
|---|---|---|
[game] |
Vite HMR for the voxel game itself | http://localhost:5173 |
[site] |
Astro dev for the marketing site (embeds the game at /play) |
http://localhost:4321 |
Open the site URL and click Play to run the game in-browser, or open the game URL directly for the bare game with HMR. Click the canvas if pointer lock didn't engage. Press Ctrl-C once to stop both servers.
The website lives in
website/(seewebsite/README.md). Granular commands:bun run game:dev(game only),bun run site:dev(site only),bun run site:build,bun run site:preview.
bun run build # production build → dist/
bun run preview # serve the production build (http://localhost:4173)
bun run typecheck # tsc --noEmit (type checking)
bun run check # alias for typecheckAll four are verified working in this environment (see §10).
| Action | Input |
|---|---|
| Move | W A S D |
| Look | Mouse (pointer lock) |
| Jump / Swim up / Fly up | Space |
| Fly down / descend | Shift |
| Toggle flight | Space ×2 (double-tap) |
| Sprint | Ctrl |
| Break block | Left mouse |
| Place block | Right mouse |
| Select hotbar slot | 1–8 / scroll wheel / F to cycle |
| Capture screenshot | P |
| Pause / release mouse | Esc |
Flight, gravity, and swimming are all reflected in the on-screen mode badge (Walking / Flying / Swimming). Controls are also listed under Controls in the main menu.
Available from the main menu and the pause screen; changes apply live:
- View distance (chunk radius 2–10) — affects generation, meshing, and fog.
- Mouse sensitivity
- Field of view (60–110)
- Show FPS
- Clouds on/off
- World seed + Regenerate (rebuilds the world from a new seed)
Settings persist to localStorage.
There are two ways to capture the required screenshots
(screenshots/main-menu.png and screenshots/in-game.png). Both are real
captures from the running game — no fakes.
Uses Playwright to capture full-viewport screenshots (so DOM overlays like the menu and HUD are included alongside the canvas). Run a server first, then the script:
bun add -D playwright
bunx playwright install chromium
bun run dev # or: bun run build && bun run preview
VOXL_URL=http://localhost:5173 bun run screenshot(If you used preview, point VOXL_URL at http://localhost:4173.)
The script waits for the menu, captures screenshots/main-menu.png, starts a
game, waits for chunks to stream in, then captures screenshots/in-game.png.
If Playwright fails to launch under Bun on your machine, run the same script with Node instead:
VOXL_URL=http://localhost:5173 node scripts/screenshot.ts.
bun run dev, open the browser.- Main menu: on the title screen, press
P. The canvas PNG downloads — save it asscreenshots/main-menu.png.- Note: the
Pshortcut captures the WebGL canvas. The menu is a DOM overlay, so for a menu screenshot that includes the title/buttons use your OS screenshot (or method A). The canvas behind the menu is the sky, so thePcapture is mainly useful in-game.
- Note: the
- In-game: click Play, look at a nice view (sky + terrain + trees +
water), press
P. Save the download asscreenshots/in-game.png.
Browser security prevents web pages from writing directly to a chosen folder, so captures are delivered as a download (or via the File System Access API "Save" dialog where supported). Save them with the exact filenames above.
Environment note (honest): automated capture could not be executed in the
build environment because the headless server is missing Chromium's system
libraries (libglib-2.0.so.0, etc.) and there is no package manager/sudo to
install them. The script is implemented and reaches the browser-launch stage
correctly; run it on a normal desktop machine. The in-game P shortcut and OS
screenshot also work fully locally.
- One indexed mesh per chunk (opaque) + one for water; no per-block meshes. Face culling keeps triangle counts low.
- Budgeted generation (2 chunks/frame) and meshing (2 chunks/frame) prevent frame hitches while streaming; spawn area is pre-generated + pre-meshed so the first frame already looks good.
- Far chunks are unloaded and their geometries disposed (no GPU/memory leak). Shared materials are never disposed per chunk.
pixelRatiocapped at 2; fog hides the view-distance edge and blends to sky.- Production bundle ≈ 132 kB gzipped (mostly Three.js).
Typical playable range: view distance 4–8 on a normal laptop is smooth.
| Check | Status |
|---|---|
bun install works |
✅ |
bun run dev works |
✅ |
bun run build works |
✅ |
bun run preview works |
✅ |
bun run typecheck / bun run check works |
✅ |
| Framework/tooling choice justified (Three.js + Vite + TS + Bun) | ✅ |
| Bun is the package manager and script runner | ✅ |
| Main menu | ✅ |
| In-game HUD (crosshair, hotbar, fps/coords/mode) | ✅ |
| Procedural voxel terrain | ✅ |
| Chunk-based loading / chunk organization | ✅ |
| Block data supports edits (setBlock updates world data + remeshes) | ✅ |
| Efficient visible-face / chunk-mesh rendering | ✅ |
| Gravity-based movement + jumping | ✅ |
| Creative flight (double-tap Space) | ✅ |
| Mouse-look controls (pointer lock) | ✅ |
| Block breaking (left click; bedrock protected) | ✅ |
| Block placement (right click; blocked inside player) | ✅ |
| Hotbar block selection (1–8 / scroll / F) | ✅ |
| Pause / menu / settings UI | ✅ |
| View distance control (live) | ✅ |
| Mouse sensitivity control (live) | ✅ |
| Clouds / sky treatment | ✅ |
| Terrain variation (biomes, hills, mountains, water, beaches, trees, caves) | ✅ |
| Controls documented in UI + README | ✅ |
| Screenshot workflow documented | ✅ |
screenshots/main-menu.png can be captured from the actual game |
✅* |
screenshots/in-game.png can be captured from the actual game |
✅* |
| No fake / placeholder screenshots claimed as real | ✅ |
| No fake compliance or placeholder-only gameplay systems | ✅ |
* Capturable via the implemented P shortcut / Playwright script / OS
screenshot. Could not be executed in the build sandbox due to missing Chromium
system libraries (no GUI stack); run locally.
bun install → ok (20 packages)
bun run typecheck → ok (tsc --noEmit, 0 errors)
bun run build → ok (dist/ built, ~132 kB gzip)
bun run preview → ok (HTTP 200 on http://localhost:4173/)
node scripts/screenshot.ts → reached Chromium launch; failed ONLY because the
headless host lacks libglib/etc. (no apt, no sudo)
- No per-vertex ambient occlusion. Shading is baked directional face tint + Lambert lights; AO would add depth to screenshots. (Meshing is structured to add it later.)
- Canvas-only
Pcapture excludes DOM overlays (HUD/menu). Full-viewport capture needs the Playwright script or an OS screenshot. (Could render an orthographic HUD pass into the canvas to makePcomplete.) - Caves are simple 3D-noise pockets/tunnels rather than full cave systems.
- No day/night cycle, mobs, inventory crafting, or sounds — out of scope for this slice.
- Water is a flat transparent surface with light swim physics; no waves/flow.
- Automation not executable in the build sandbox (missing GUI libs).
- Mobile: UI doesn't break, but touch controls are not implemented (documented as optional).
- Trees are kept within-chunk to avoid cross-chunk writes; chunk borders are slightly sparser as a result.
- What's strong: clean separation (engine/game/ui/state); deterministic world; budgeted streaming with proper disposal; swept-AABB collision with fly/walk/swim; precise voxel DDA picking; cohesive original UI; live settings; a real (if environment-blocked) automated screenshot path plus a guaranteed manual path.
- What's weakest: no AO (biggest visual-quality miss for screenshots); the
P-key canvas capture can't include the DOM HUD; caves are basic; and I could not produce the actual PNGs in-sandbox because the host has no GUI stack — so the screenshot deliverables are implemented and documented but must be generated on a normal machine. - Trade-off I'd revisit: I disabled
noUncheckedIndexedAccessbecause voxel array indexing is pervasive and construction-bounds-safe; this trades a bit of type rigor for readable code. With more time I'd add focused bounds helpers and re-enable it.