Pocket Miner is a browser-based mining game built as a static HTML/CSS/JavaScript project. It does not require a build step, backend, or package install to run.
Each shift drops you into a fresh mine with a time limit. Mine valuable ore, collect what drops, survive until the timer ends, then bank your earnings and spend them on upgrades before the next shift.
Your long-term goal is to dig deeper, carry more loot, and improve your permanent stat bonuses so each shift becomes more productive than the last.
AorLeft Arrow: move leftDorRight Arrow: move rightWorUp Arrow: jumpSorDown Arrow: drop down through a platformSpaceorLeft Mouse Button: mine the block under your cursorQorRight Mouse Button: use the current primary tool at the cursorE: use the current secondary tool at the cursorTab: swap which tool is primary and secondary
Gamepad:
Left StickorD-pad Left/Right: moveA: jump, confirm prompts, start or continue screensX: mine- Hold
Xand press a direction on theLeft StickorD-pad: directional mining without the mouse D-pad DownorLeft Stick Down: drop through platformsRight Stick: aim the left or right tool around the player in a radial range based on stick direction and distance- Hold
Left BumperorRight Bumper: preview tool aim around the player, then release the bumper to place that side's tool Y: swap which tool is primary and secondaryMenu/Start: pause, resume, or advance overlay screensView/Back: toggle the performance readout
At the start of a run, Platform is the primary tool and Bombs are secondary once unlocked. After swapping with Tab, Q and Right Mouse Button follow the tool shown in the left hotbar dial, while E follows the tool shown in the right hotbar dial.
A/Left Arrow/W/Up Arrow: move reward selection left or upD/Right Arrow/S/Down Arrow: move reward selection right or down1,2,3: choose a reward directlyEnter,Space, orE: confirm the selected reward
R: toggle the performance readoutTon the title screen: start the attract mode demo immediately (any other input returns to the title)- Intro and pause screens show a 12-character password you can enter later to restore your progress profile
- Start a shift after the
3-2-1-GO!countdown. - Mine blocks within cursor range and collect the ore drops before the shift ends.
- Open treasure caches when they appear and choose one permanent bonus.
- Reach the end-of-shift summary and bank your earnings.
- Visit the store to buy upgrades, then start the next shift.
- Mining is cursor-targeted. You must point at a mineable block within range of the player.
- When mining with a gamepad and no mouse button held, the game also supports directional mining from the movement input.
- Ore is not banked just by breaking blocks. You need to actually collect the dropped items.
- Temporary platforms can only be placed within range, in line of sight, and not inside the player.
- Touching magma ends the shift immediately.
- Every new shift generates a fresh world.
- The countdown before each shift means movement does not begin instantly when you leave the intro or summary screens.
Store upgrades are split into a few main branches:
Tools: stronger pickaxes for faster mining and one-swing breakpointsBombs: dynamite sticks, dynamite bundles, and heavier mining bombs with stronger payloadsStorage: more inventory slots and larger stack sizesMisc: longer shift duration
Treasure caches grant permanent stat bonuses such as:
- move speed
- jump power
- swing rate
- platform cooldown reduction
- luck
- mastery
- tool damage
The intro screen and pause screen both display a 12-character password in an NES-style grouped format.
Entering a valid password restores your progression profile:
- unlocked upgrade tiers are restored exactly
- round, bank, and permanent bonus values are restored from the password's compressed form
Because the code is limited to 12 human-entered characters, bank and permanent bonuses are rounded to password tiers when restored.
- Early on, focus on ore you can collect consistently rather than reaching too deep too fast.
- Storage upgrades matter quickly because uncollected or uncarried value is lost potential income.
- Platform placement is useful for recovering awkward drops and climbing back through mined shafts.
- Permanent cache bonuses compound over time, so treasure rewards are a major part of progression.
A few in-game screenshots showing gameplay, reward caches and the store.
Cheat codes are enabled in the current project build.
ROSEBUD: grants10000€MOTHERLODE: grants50000€IDFA: grants+50%to all player bonus statsIDKFA: grants theIDFAstat boost plus a stronger loadout upgrade package
The attract mode player is driven by a planning AI (src/ai/attractAiController.js).
- Press the AI debug key (see
toggleAiDebuginsrc/input/inputMethod.js) during attract mode to show the live overlay: the planned route color-coded by edge kind, candidate goals with €/s rates, the danger field, and executor status. - While the overlay is visible, a "Save AI trace" button appears (also
window.__aiTrace.download()in the console). The JSON trace records the initial terrain, every planning decision with alternatives, replan reasons, and 10 Hz movement samples. - Open
tools/ai-trace-viewer.htmlin any browser and drop a trace on it to scrub through the shift on a reconstructed minimap. node tools/ai-sim.js --shifts 5runs full headless shifts and fails on invariant violations (magma touch, stuck episodes, excessive hard resets). Add--trace out.jsonto inspect a run in the viewer.window.__probePickups()in the console lists every live drop with its current cell, spawn cell, and whether either is solid — for diagnosing stray-drop issues in any game mode.
This repository is now set up to deploy directly to GitHub Pages.
- Push this repository to GitHub.
- In the repository settings, open
Pages. - Set the source to
GitHub Actions. - Push to
mainand the workflow in.github/workflows/deploy-pages.ymlwill publish the game.
Because the project uses only relative asset paths like ./assets/..., it also works on other static hosts such as:
- GitHub Pages
- Netlify
- Cloudflare Pages
- Vercel static hosting
- any simple web server
Open the repository from a local web server rather than double-clicking index.html, because modern browsers restrict module loading and audio/file fetches from the file protocol.
Examples:
python -m http.server 8000Then open:
http://localhost:8000/
- Keep asset filenames and letter casing exactly as they are in the repository. Linux-based hosts are case-sensitive.
- The intro title art is expected at
./assets/title.pngif you want a custom title image. If that file is missing, the game falls back to the text title automatically. - The project is a static site, so no bundler or transpiler is required for deployment.
The runtime terrain atlas used by the renderer is checked in under assets/tiles/terrain-atlas.png.
The bomb spritesheet and sound effects are also generated procedurally and checked in under assets/sprites/bomb-spritesheet.png and assets/sfx/.
Regenerate it after changing tile visuals with:
.\generate_tile_assets.ps1Regenerate bomb visuals and bomb audio with:
.\generate_bomb_assets.ps1That script writes:
assets/tiles/terrain-atlas.pngassets/tiles/terrain-atlas-lookup.pngassets/tiles/terrain-atlas-manifest.jsonassets/tiles/terrain-atlas-manifest.js
The bomb generator writes:
assets/sprites/bomb-spritesheet.pngassets/sfx/bomb-fuse.wavassets/sfx/bomb-explode.wav
The generator is self-contained and recreates all terrain visuals procedurally, so no separate source tilesheet file is required.
The password system is implemented in src/systems/passwordSystem.js as a compact fixed-width binary payload that is then encoded into a human-friendly alphabet.
- Total password length:
12characters - Data characters:
11 - Checksum characters:
1 - Display grouping:
XXXX-XXXX-XXXX - Alphabet:
ABCDEFGHJKLMNPQRSTUVWXYZ23456789
That alphabet is a custom Base32-style set. It intentionally removes ambiguous characters such as I, O, 0, and 1 so the code is easier to read aloud and type from memory.
Because each data character stores 5 bits, the first 11 characters carry exactly 55 bits of payload:
The twelfth character is not payload. It is a checksum derived from the first 11 characters.
The encoder builds a single integer by packing fields from left to right, then emits that integer as 11 Base32 characters. The payload currently uses all 55 available bits.
Field layout, in encode order:
- Version:
2bits - Pickaxe tier:
4bits - Bag tier:
2bits - Capacity tier:
3bits - Time tier:
2bits - Platform tier:
2bits - Bomb unlocked flag:
1bit - Bomb capacity tier:
2bits - Bomb type tier:
2bits - Round:
5bits - Bank bucket:
7bits - Ten permanent bonus tiers:
20bits total,2bits per bonus
Total:
This allows up to 4 password format versions. The current implementation uses version 1.
This field can represent values from 0 to 15. The current game only uses the first nine values:
0: bare-hands1: wood-pick2: copper-pick3: tin-pick4: iron-pick5: silver-pick6: gold-pick7: ruby-pick8: sapphire-pick
This field can represent values from 0 to 3. The game currently uses:
0: trouser-pockets1: bag-12: bag-2
This field can represent values from 0 to 7. The game currently uses:
0: small-size1: capacity-12: capacity-23: capacity-34: capacity-4
This field stores the active shift-duration upgrade:
0: time-root1: time-12: time-23: time-3
This field stores the platform rig upgrade:
0: platform-rig1: platform-12: platform-2
0: bombs are locked1: bombs are unlocked
If bombs are locked, the bomb capacity and bomb type fields are still present in the payload, but they are ignored during restore.
This field maps to the compact bomb rack tier list:
0: bomb-capacity-root1: bomb-capacity-22: bomb-capacity-33: bomb-capacity-5
This field maps to the compact bomb type list:
0: bomb-type-root1: bomb-type-22: bomb-type-33: bomb-type-4
Five bits allow values from 0 to 31, which are stored as rounds 1 through 32.
The encoder stores:
The decoder restores:
That means the password system cannot represent rounds above 32. Higher rounds are clamped to 32 when encoded.
Seven bits allow values from 0 to 127.
The live bank is not stored exactly. It is quantized in 500€ steps:
On restore, the bank becomes:
So the maximum encoded bank is:
Anything above that is clamped when the password is generated.
There are 10 permanent bonus stats:
- moveSpeed
- jumpPower
- swingRate
- platformCooldown
- bombDamage
- bombRestock
- pickupMagnetism
- luck
- mastery
- toolDamage
Each stat gets 2 bits, so each one stores a value from 0 to 3.
The encoder does not store raw floating-point bonus values. It quantizes them using these thresholds:
- tier
0: value< 0.05 - tier
1:0.05 <= value < 0.175 - tier
2:0.175 <= value < 0.375 - tier
3: value>= 0.375
When a password is restored, each tier becomes one of these exact values:
- tier
0->0 - tier
1->0.1 - tier
2->0.25 - tier
3->0.5
This is why passwords preserve the general strength of a build, but not the exact original chest-reward floating-point values.
The last character is a checksum over the first 11 characters, not over the raw bitfields directly.
For each data character, the encoder looks up its alphabet index and computes:
The resulting number selects the final character from the same Base32 alphabet.
This is not a cryptographic checksum. It is only there to catch mistyped passwords and reject obviously invalid codes.
At a high level, password generation works like this:
- Extract a compact progression snapshot from the live game state.
- Convert upgrade ids to compact tier indices.
- Clamp and quantize round, bank, and permanent bonuses.
- Pack all fields into one
55-bit integer. - Encode that integer into
11Base32 characters. - Compute the checksum character.
- Format the result as
XXXX-XXXX-XXXXfor display.
Password restore does the reverse:
- Normalize input to uppercase and strip separators.
- Reject unsupported characters.
- Require exactly
12characters. - Verify the checksum.
- Decode the first
11characters back into the55-bit payload. - Unpack fields from right to left.
- Rebuild tier ids and normalized bonus values.
- Restore the game into a fresh round with a new world seed.
The game intentionally uses a lossy password system because the design target was a short, human-entered password instead of a long exact save code.
With only 55 payload bits available, the password cannot store all of the following exactly at once:
- every upgrade and unlock state
- arbitrary round values
- arbitrary bank values
- ten floating-point permanent bonus values
So the implementation keeps upgrade and unlock tiers exact, while round is capped and bank and bonus values are quantized. That tradeoff is what makes a short memorisable password possible.




