The result first: one HTML file, 238.7 KB, double-click and play. No install, no network connection, and not a single external image or audio file inside. From sending the prompt to running in a browser: 20 minutes.
How it started: let the kid write the spec
My kid had been asking for Minecraft. I didn't want him starting with the online version, so I suggested we build one instead. What I actually wanted him to see was something else: what AI can do and how you use it. Explaining that doesn't land — he had to set the requirements himself and then watch the thing grow.
I asked what kind of world he wanted. He thought for a while and gave a fairly specific list:
- Grass blocks, trees, and water
- No zombies, no monsters
- Villagers, but they must not attack
- Ores, the diamond kind
- Lots of chests right at the start — the more rewarding the better
I copied those almost verbatim, no polishing. The only thing I added was: make the colors vivid, don't make it cartoonish.
My kid wants to make a Minecraft game. It can be simple but should look polished, and it should replicate the game. What tech or approach do we need? Is plain HTML enough, or is there something better? I need to play it right away — get it working fast enough for a kid: single player, no monsters or zombies, villagers that don't attack, various block types such as grass blocks, trees, water, various ores, items. Lots of chests and rewards at the start, the more satisfying the better, with weapons in the chests. Analyze the best approach first.
I left the line 「is plain HTML enough, or is there something better」 in the prompt. Its answer was a single HTML file with hand-written WebGL2 — no framework, no engine. The reasoning was practical: this machine can't reach the internet, so Three.js can't be pulled from a CDN. Writing it all by hand removed dependencies entirely.
Twenty minutes to a playable body
This was DeepSeek Desktop, same model throughout, no plugins. I honestly didn't expect much — I've seen plenty of these demos, and usually getting one to actually run takes half a day of back-and-forth. This time terrain, trees, water, ores, mining and placing, chests that open, villagers that walk around — all present. At this scale:
| Item | Count / spec |
|---|
| Block types | 69 |
| Items | 111 |
| Crafting recipes | 36 |
| Chests | 44, in wood / gold / diamond tiers |
| Villagers | 12, plus 4 practice targets |
| World size | 192 × 96 × 192, 16×16 chunks |
| External dependencies | 0 |
| Output | 6,221 lines / 238.7 KB |
He stared at it for a few seconds, then started digging next to the spawn point and didn't stop. I sat there for a good ten minutes while he turned a patch of grass into a trench. That night he was still awake at ten, saying he wanted to mine a couple more ores, and asked whether I could add a rideable horse tomorrow.
Looks: the one rule was 'not cartoonish'
That single sentence changed the art direction. Textures aren't 16×16 mosaics but 128×128 procedural maps: multi-octave noise, gradients, stains, ore glints, with mipmaps and anisotropic filtering on top. Canvas2D draws all of them at load time; there is no image asset in the file.
Water has its own pass: Fresnel reflection, animated normals, depth absorption. When the sun is low it draws a bright band across the surface. Night isn't pitch black either — moonlight ambient compensation keeps the road visible without glare.
The work after the first 20 minutes
Those 20 minutes bought 'playable', not 'finished'. I spent the next few days fixing things that were plainly broken:
- Held item invisible: the WebGL sprite approach failed, switched to a DOM overlay
- Villagers had one leg: offset both legs by ±0.115 and swing them front-to-back
- Health hearts rendered as the letter 'b': SVG needs createElementNS + setAttribute, not innerHTML
- 'this.game.sfx is not a function': moved to a global sfx singleton
- The starting axe_diamond didn't exist: swapped for axe_iron
- The loot table referenced a non-existent torchless: swapped for lantern
- Pickaxe icon was torn apart: redrawn symmetrically with quadratic Béziers
- Night was pitch black: added moonlight ambient compensation
- Chunk deletion threw 'm.bufs is not iterable': both the opaque and water buffers need handling
The stack is plain
| Layer | What | Notes |
|---|
| Rendering | WebGL2 | Chunk meshing, face culling, vertex AO, 2048² shadow map, HDR + Bloom + ACES |
| Textures | Canvas2D | 128×128 procedural generation, assembled into an atlas at runtime |
| Item icons | Canvas2D | All 111 icons drawn by code |
| Audio | WebAudio | Real-time oscillator synthesis, no audio files |
| Save | localStorage | Stores the seed and world edits |
The source is split into 12 modules and concatenated into one HTML file by build.mjs. Module breakdown and rendering pipeline details are in a companion piece: A Zero-Dependency WebGL2 Voxel Engine in One File.
What's actually worth noting
- Specifying well beats coding. Two of his five items were 'no X', which pins down direction far better than 'yes X' and saves the back-and-forth of removing features later.
- Verifiable beats impressive. Screenshots, console errors, a count of missing item icons — a machine can check those, no gut feeling needed.
- Split the timeline. Twenty minutes got the first playable build; the next few days made it not awkward to use. Both numbers deserve saying.
NotesNumbers come from one local run (DeepSeek Desktop, verified with headless Chrome). Chest counts and tier distribution vary with the world seed. Headless SwiftShader runs at 1–2 FPS and says nothing about real performance.