Underground 2 Revived
Play Need for Speed: Underground 2 (2004) in Chrome and Firefox with WebGPU graphics, Web Audio, up to 120 fps widescreen, controller support, free roam and Online races with friends. Fan project, not
A two-player Slavic dark-fantasy tactical battler. Send a link, pick a castle, buy an army, fight.
Translation pending
GamesTypeScript · HTMLGitHub @ifyoufeelOnlineOwner unconfirmed
Have you tried it? One tap is enough. No sign-in needed.
No updates detected yet. GitHub releases will appear here.
Owners of public repositories under a personal GitHub account can sign in to verify ownership and manage the introduction themselves. No administrator approval is needed.
Sign in, generate a verification file, and commit it to this repository’s default branch within 30 minutes. Files on other branches or forks do not count. Then check the file below to manage the project without administrator approval. Keep the file to retain your access.
If needed, you will go to sign-in. After signing in, return here and press your chosen verification button again.
Run /nomorevibe verify in your project folder to verify ownership and manage the introduction yourself.
Verify in one minuteAsk about other verification options
Loading the full README. The saved excerpt is shown below.
A two-player, turn-based tactical battler in the spirit of Heroes of Might & Magic 3,
set in a Slavic dark-fantasy world.
Send a friend a link → you both pick a castle → you each spend a gold budget recruiting
an army → the two armies fight on a hex battlefield until one side is wiped.
There is no adventure map, no hero and no spellbook. The battlefield phase is the
whole game.Read the full README ↗A two-player, turn-based tactical battler in the spirit of Heroes of Might & Magic 3, set in a Slavic dark-fantasy world.
Send a friend a link → you both pick a castle → you each spend a gold budget recruiting an army → the two armies fight on a hex battlefield until one side is wiped.
There is no adventure map, no hero and no spellbook. The battlefield phase is the whole game.
| Side | Castle | Flavour |
|---|---|---|
| Явь | Kitezh | The sunken city's druzhina. Human steel, shield walls, discipline. |
| Явь | Borovina | Leshy's old forest. Wolves, wood-wights, things wearing bark. |
| Явь | Gromoboy | Perun's thunder-host. Fast, fragile, hits like a storm. |
| Навь | Kostyanoy Dvor | Koshchei's bone court. Slow, relentless, hard to finish. |
| Навь | Topyla | Vodyanoy's drowned mire. Rusalki, bolotniks, drag-you-under tricks. |
| Навь | Yagaya Pushcha | Baba Yaga's wildwood. Witches, hexes, the hut on chicken legs. |
Full stat tables live in docs/factions.md.
npm install
npm run dev
| Script | What it does |
|---|---|
npm run dev | Vite dev server |
npm run build | Typecheck and build to dist/ |
npm run typecheck | TypeScript only |
npm run lint | oxlint |
npm run test | Vitest unit tests |
npm run e2e | Playwright end-to-end tests |
Cross-device multiplayer works out of the box, no account needed: it connects the two
browsers directly over WebRTC, using a free public broker only to make the initial
introduction. For a connection that does not depend on either player's network (some
restrictive corporate/campus networks block direct peer-to-peer), copy .env.example to
.env, fill in Supabase credentials, and that real server relay is used instead whenever
it's configured.
src/
hex/ axial coordinates, neighbours, pixel<->hex, A* pathfinding
engine/ rule-agnostic core: seeded RNG, Result<T>, pure action reducer
rules/ HoMM3 combat: initiative queue, retaliation, damage, victory
content/ factions, units, battlefield dimensions, balance numbers
net/ rooms, the sync protocol, and its transports (WebRTC, Supabase, BroadcastChannel)
ui/ screens, battle components, theme tokens
Two invariants hold the whole thing together:
Action handlers are pure and total. Same state plus same action always produces the
same result, with no clocks, no Math.random, and no I/O. Every die roll goes through
the seeded RNG in src/engine/rng.ts, whose cursor is part of serialized state.
Nothing outside src/ui/theme/tokens.css hardcodes a colour. Every colour, spacing
step and animation duration is a custom property, so a change to the visual direction
stays in one file.
The match is host-authoritative with verification. The guest sends action
requests; the host resolves them — including every random draw — and broadcasts
{action, rngDraws, stateHash}. The guest replays the action using the host's draws,
then compares its own hash against the host's.
Pure lockstep determinism would also work, and would be more elegant, but it fails silently: one difference in iteration order and the two players are playing subtly different games with no indication anything is wrong. Resolving randomness once and checksumming after every action means a desync is caught immediately and can be reported honestly.
Known limitation: the host's browser is the authority, so if the host closes their tab, the match ends. A guest who drops can reconnect and the host will re-send state, but a host who drops cannot be recovered — there is no database behind this, by design.
There is also no anti-cheat, and there cannot be one without a real server. Play with people you like.
Pushes to main deploy to Vercel automatically; other branches get preview URLs.
CI (typecheck, lint, test, build) runs on every push and pull request.