Lockstep
Docker app from smiei's Repository
Overview
Readme
View on GitHubLockstep
SoulLink & Nuzlocke tracker
A Nuzlocke tracker built around SoulLink: 2–4 players, linked deaths, the rules of your generation.
Free, self-hosted, and live on every device at the table.
https://github.com/user-attachments/assets/f7191534-8d36-472e-bc7f-660101b234cd
New to the format?
A Nuzlocke lets you catch only the first Pokémon in each area, and a fainted Pokémon is dead. In a SoulLink, several players run at the same time and each area's catches are linked: one dies, its partners die too.
Contents
- What sets it apart
- Screenshots
- Pokémon Infinite Fusion
- Quick start: Windows app, Docker, Pinokio, or Node.js
- Supported games
- Documentation
- Version history
- Acknowledgements
- License
What sets it apart
You know the rules. This is what the tracker handles so you don't have to.
SoulLink, the way you actually play it
- 2–4 players or solo, each with their own catches per route. Every device at the table sees each change live.
- A link forms only once every player has caught on that route. If one player's encounter fled or was knocked out, the pair never formed: the survivor stays in the box and never shows up as a link.
- Marking a link dead takes all of its Pokémon, and records who lost theirs and how ("lost by Red · Lt. Surge's Raichu"). The run's Memorial keeps the list.
- Your team is linked too. A team slot holds the whole link, and new catches fill free slots on their own.
Clauses that know the difference
- Species Clause in three variants:
- across all players;
- per player;
- per player, but with a formed link locking its families for everyone.
- Evolution families, statics and fusions are handled.
- Shiny Clause: a shiny never locks its family.
- The clauses only warn, they never block. Your group's house rules win.
- Rule presets carry a ruleset, including your written rules, from one run to the next.
Accurate to the generation you're playing
- The type chart, base stats, catch rates and move names your game actually had: Gen 1's single Special, no Fairy type before Gen 6, pre-buff stats, old move names.
- Catch rates use that generation's formula and ball list, Apricorn Balls included, up to Sword/Shield's and Scarlet/Violet's level and badge rules.
- Evolution overrides for randomizers and ROM hacks: impossible, easier or time-based evolutions.
Planning the next fight
- Level caps for every gym leader, rival and Elite Four member, with the next cap always in view.
- Battle scout: the opponent's moves by level and its matchup against each player's team.
- Team coverage (weaknesses and offensive gaps), judged at your last level cap rather than at level 100.
- Base-stat totals now and fully evolved, with a note when an evolution is possible within the cap.
And also
- Pokémon Infinite Fusion (Classic and Remix): fusions are one battle unit, and a fused catch locks both families.
- Custom routes for areas a game pack doesn't list.
- Free-team mode for runs that aren't Nuzlockes.
- Blind flight, which hides every calculated hint, optionally locked on for the whole run.
- An installable phone app in 5 languages: English, German, French, Spanish and Italian. German and English are reviewed by hand; French, Spanish and Italian are machine-translated. The names of Pokémon and of every official game's places are the official ones in each language. ROM hacks keep their English names, as in the game.
- Backups of one run or all of them.
- No account, no cloud: it runs on your PC or home server.
Screenshots
Pokémon Infinite Fusion
Lockstep tracks Pokémon Infinite Fusion runs, Classic and Remix, fusions included.
- Fuse on the Team tab: two of a player's catches become one battle unit. Swap head and body, or split them again.
- Real fusion values: name, types and base stats are worked out from the pair the way the game does it. The sprite is the community's hand-drawn fusion art, or the generated one where nobody has drawn it yet.
- Wild fusions: a catch can already be a fusion ("Caught as a fusion" on the Encounter tab), or every catch is, with the randomizer rule "Fuse everything".
- SoulLink with fusions: a pairing stays tied to its routes, so a death takes everything linked to it, across fusions. A fused catch locks both families for the Species Clause; you can switch that off.
- The game's own data: stats, types, catch rates, moves, TM compatibility, the trainers' level caps, and the capture formula with all 40 of the game's balls.
Details: features.
Quick start
Pick whatever fits you best. All four give you the same app at http://localhost:3000, or at your server's address.
Windows app (one download)
- Download
Lockstep-Setup-<version>.exefrom the latest release and run it. No admin rights needed; there is also a portable zip. - Your browser opens with a short waiting page while the first start downloads the Pokémon sprites (a few minutes); it switches to Lockstep by itself. Lockstep then keeps running in the system tray.
- Friends on the same Wi-Fi open the address shown under Address for other devices in the tray menu. Allow access when the Windows firewall asks.
The installer is not code-signed yet, so your browser or Windows may warn about it even though it is safe:
- Browser ("may contain a virus", "not commonly downloaded"): keep or allow the download.
- Windows SmartScreen ("Windows protected your PC"): click More info → Run anyway.
Docker (home servers, NAS, Unraid)
services:
lockstep:
image: ghcr.io/smiei/lockstep:latest
ports:
- "3000:3000"
environment:
- DATABASE_URL=file:/app/db/nuzlocke.db
# - DEFAULT_SHELL_COLOR=blue # optional: green (default), red, blue or yellow
# - DEFAULT_LANGUAGE=en # optional: de, en, fr, es or it
volumes:
- db:/app/db
- sprites:/app/public/pokemon-sprites
- trainers:/app/public/trainers
- covers:/app/public/covers
restart: unless-stopped
volumes:
db:
sprites:
trainers:
covers:
Save that as compose.yaml and run docker compose up -d. The first start downloads the Pokémon sprites, trainer pictures and game covers, which takes a few minutes.
Pinokio (one click, Windows / macOS / Linux)
- Install Pinokio.
- On the Explore page, paste
https://github.com/smiei/lockstep-pinokiointo the GitHub or Hugging Face URL field and click Go. - Click Install, then Start.
Without Docker (Node.js 20.19+)
git clone https://github.com/smiei/lockstep.git
cd lockstep
npm install
npm run setup
npm start
→ Self-hosting guide: volumes, updating, backups, HTTPS for installing the app on a phone, trainer pictures, and a custom app icon.
Supported games
| Generation | Games |
|---|---|
| 1 | Red / Blue / Yellow |
| 2 | Gold / Silver / Crystal |
| 3 | Ruby, Sapphire, FireRed / LeafGreen, Emerald |
| 4 | Diamond / Pearl, Platinum, HeartGold, SoulSilver |
| 5 | Black, White, Black 2 / White 2 |
| 6 | X / Y, Omega Ruby, Alpha Sapphire |
| 7 | Sun, Moon, Ultra Sun, Ultra Moon |
| 8 | Sword, Shield, Brilliant Diamond / Shining Pearl (no TM list: the data source has none for it) |
| 9 | Scarlet / Violet |
| Fan game | Pokémon Infinite Fusion (Classic and Remix), with fusions and the game's own data |
| ROM hacks | Radical Red, Unbound, Run & Bun, Renegade Platinum, Sacred Gold, Storm Silver, Blaze Black, Volt White, Blaze Black 2 / Volt White 2 Redux, Emerald Kaizo, Inclement Emerald, Rising Ruby, Sinking Sapphire, Blazing Emerald, Glazed, New Generations, with each hack's own stats, types and moves, Pokémon up to Gen 9 and regional forms; named in English, as in the game |
Found a wrong route or name? Corrections are welcome as an issue.
Playing a ROM hack or another game? A pack is just the route list and the boss level caps, with no encounter tables. You can import your own in the app or contribute it for everyone; see game packs.
Documentation
- Features in detail — every tab and rule explained
- Self-hosting — Windows app, Docker, Pinokio, Node.js, updates, backups, HTTPS
- Development — local setup, scripts, data, migrations, releasing
- Game packs — add a game or ROM hack, in the app or as a contribution (CONTRIBUTING)
Version history
Docker tags follow MAJOR.MINOR: latest is always the newest release, and every version stays available under its own tag for rollbacks.
| Version | What it is |
|---|---|
1.0 |
Pre-PWA state (was latest until then). |
1.1 |
Adds the installable PWA (was pwa). |
2.0 |
Full UI overhaul: design tokens, shared component primitives, loading/error boundaries, toasts, pinned tab strip, keyboard-navigable dropdowns, one modal implementation, 40/44px touch targets. |
3.0 |
Pokémon Infinite Fusion (Classic and Remix: fusions, catches that already are fusions, the game's own species/move/capture data), SoulLink for 3 or 4 players, and per-player Species Clause variants. Backup format v4. |
3.1 |
Runs are addressed by an unguessable access key instead of their numeric id (old ?run=<number> links still open and are rewritten). Accessibility pass from a UI audit: readable type chips, text greys and focus ring, labelled Encounter and Battle & Catch fields, keyboard access to the Pokédex, a title per tab, drawn icons instead of emoji. Runs without Docker via npm run setup and with a one-click Pinokio launcher. Now MIT-licensed. |
4.0 |
Redesign as a natural-history collection (the app is now called Lockstep in its interface): sage ground, specimen trays and labels, a green side rail on desktop, a new logo and fonts. The Team tab becomes a drawer of six trays plus a Box and Memorial column; Overview opens with one run sheet; the Pokédex card is a field-guide entry. Per-device shell colour (green, Poké Ball red, blue, yellow). Docker image for amd64 and arm64, also on GHCR. |
4.1 |
The logo is back in the shell's bottle green (4.0 shipped it on a red tile); the app icon, splash screen and status bar follow. |
4.2 |
The run list opens right under the run box and reads clearly in dark mode (ruled rows, the open run marked). New rule: permanent blind flight, which locks blind flight on so the header button cannot lift it. Clearer Docker volume guide and a by-game reference for trainer pictures. |
4.3 |
Server defaults for the colour (DEFAULT_SHELL_COLOR, icons included) and the browser language (DEFAULT_LANGUAGE); the installed app follows the phone's language. The side-rail logo takes the shell colour, the run list reaches past the rail, the dark-mode button can return to following the device, and "Lock blind flight" got its name. |
5.0 |
The app is now called Lockstep, and so are the repository (smiei/lockstep) and the image, which is published on GitHub's registry only (ghcr.io/smiei/lockstep). A Windows app: one installer, runs in the system tray. 16 ROM hacks (Radical Red, Unbound, Run & Bun, Renegade Platinum, and more) with their own stats, types and moves; Pokémon data up to Gen 9 with regional forms; a hack's own Pokémon as placeholders. Import your own game pack in the app, or contribute one. A start page with every run as a card. Boss-trainer pictures download by themselves. Notes on machine-translated languages and unverified game data. Backup format v5. |
5.1 |
The Encounter tab tells open and finished routes apart at a glance, in dark mode too (a coloured or dashed frame and an outcome badge per route). 66 more boss-trainer pictures for the ROM hacks, and a complete list of the pictures each game expects. A ROM hack's own forms (Sevii, Hoenn) show their species' sprite, marked as a placeholder. Updated dependencies. |
5.2 |
15 more official games: Ruby and Sapphire, Diamond and Pearl, Black 2 and White 2, and every main game from X and Y to Scarlet and Violet, with place, boss and badge names in all five languages. The catch calculator knows Gen 6 to 9: new balls (Beast Ball), the changed ball bonuses, and Sword/Shield's and Scarlet/Violet's level and badge rules; the Gen 5 formula is corrected. Fixed: 18 Pokémon re-typed to Fairy in Gen 6 (Clefairy, Gardevoir, Mawile, ...) lacked the Fairy type in every game and ROM hack with Gen 6+ data. |
5.3 |
Official place names in all five languages for every official game, taken from PokéWiki per game and region (Spanish, Italian and French place names were often English before). ROM hacks keep their English names for places, trainers and badges, as in the game, and say so. Game covers in the game picker, on the start page and on the Overview, downloaded on the first start; your own covers win. The "data unverified" notes are gone. |
5.4 |
The Team tab's Box can list the team too ("Show team"), so one sort ranks every living link by base stats. The desktop side rail folds away with an arrow at its foot. On the start page, runs can be marked as won (a gold Hall of Fame card) or lost (greyed out) and deleted in two clicks. The next level cap shows the team level beside it ("24 / 22") in the rail and the phone's top bar. "Open only" on the Encounter tab keeps the route saved last after a reload. Fixed: the Pokémon picker of an empty entry was cut off, and Box entries overlapped their buttons on narrow screens. HeartGold/SoulSilver route order corrected up to the second badge. Devices in other languages get English. Prisma 7; installs without Docker need Node.js 20.19 or newer. |
Acknowledgements
This project stands on the work of others. Thank you!
- PokéAPI — Pokémon, move, evolution and catch-rate data, and the sprite collection the app downloads at runtime.
- infinite-fusion-nuzlocke by Frederik Bosch — the Infinite Fusion locations, encounters and species list, and the fusion typing rules.
- Pokémon Infinite Fusion — the game's own stats, moves, trainer levels and capture code. Fusion sprites come from its sprite community.
- Pokémon League Badges by Stephen Griffiths — the badge icons (CC BY 3.0, cropped from the sheet).
- PokéWiki — the official place and badge names in all five languages, and historical move names.
- Bulbapedia, pokemon-infinite-fusion.com and the Infinite Fusion wiki — reference for historical values, mechanics, level caps and trainer names.
- nuzlocke.app (fork Bassel-T/nuzlocke.app) — the ROM-hack route lists, boss teams and the hacks' stat, type and move changes (BSD 3-Clause).
- Pokémon Showdown — the boss trainer pictures, downloaded on first start.
- libretro-thumbnails and Wikipedia — the game covers, downloaded on first start.
- Built with Next.js, React, Prisma, Tailwind CSS and the Familjen Grotesk and Red Hat Mono fonts.
Licenses and attributions in full: THIRD_PARTY_NOTICES.md.
License
The source code is released under the MIT License. Data and artwork from third parties keep their own terms; see THIRD_PARTY_NOTICES.md.
Disclaimer: This is an unofficial, non-commercial fan project. It is not affiliated with, endorsed by, or connected to Nintendo, Game Freak, Creatures Inc. or The Pokémon Company. Pokémon names, game data and artwork are the property of their respective owners.
This repository and the published Docker image deliberately contain no copyrighted artwork:
- Pokémon sprites are downloaded from PokéAPI when the app is set up.
- Infinite Fusion's ball icons come from the game's public repository at container start.
- Fusion sprites are loaded by the browser straight from the Infinite Fusion community's sprite server and never stored.
- Trainer pictures are downloaded from Pokémon Showdown when the app is set up, unless you bring your own.
- Game covers are downloaded from libretro-thumbnails and Wikipedia when the app is set up, unless you bring your own.
Requirements
Categories
Related apps
Explore more like this
Explore allDetails
ghcr.io/smiei/lockstep:latestRuntime arguments
- Web UI
http://[IP]:[PORT:3000]- Network
bridge- Shell
sh- Privileged
- false
Template configuration
Port of the web interface.
- Target
- 3000
- Default
- 3000
- Value
- 3000
Your runs (SQLite database) and imported game packs. Back this folder up.
- Target
- /app/db
- Default
- /mnt/user/appdata/lockstep
- Value
- /mnt/user/appdata/lockstep
Downloaded once and kept across updates. Without it, every image update downloads them again (a few minutes).
- Target
- /app/public/pokemon-sprites
- Default
- /mnt/user/appdata/lockstep/pokemon-sprites
- Value
- /mnt/user/appdata/lockstep/pokemon-sprites
Boss-trainer pictures, downloaded once and kept across updates. Your own pictures go here too and are never overwritten.
- Target
- /app/public/trainers
- Default
- /mnt/user/appdata/lockstep/trainers
- Value
- /mnt/user/appdata/lockstep/trainers
Game covers, downloaded once and kept across updates. Your own covers (game id .png or .webp) go here too and are never overwritten.
- Target
- /app/public/covers
- Default
- /mnt/user/appdata/lockstep/covers
- Value
- /mnt/user/appdata/lockstep/covers
The colour every device starts with, icons included. Each device can still pick its own in the gear menu.
- Target
- DEFAULT_SHELL_COLOR
- Default
- green|red|blue|yellow
- Value
- green
The language a browser tab starts in: de, en, fr, es or it. Empty: each browser's own language. A language picked in the gear menu always wins.
- Target
- DEFAULT_LANGUAGE
off skips downloading the boss-trainer pictures from Pokémon Showdown.
- Target
- TRAINER_SPRITES
- Default
- on|off
- Value
- on
off skips downloading the game covers.
- Target
- COVERS
- Default
- on|off
- Value
- on
Where the database lives inside the container. Leave as is.
- Default
- file:/app/db/nuzlocke.db
- Value
- file:/app/db/nuzlocke.db