
Overview
Frosthold is a private World of Warcraft 3.3.5a realm running on AzerothCore, with
a public site under /wow and a desktop launcher. The game server is the least
interesting part — it is a well-documented emulator. The project is really about
one question asked over and over in different shapes: what can a site honestly
publish when there is almost nothing to publish yet?
The realm has a handful of accounts and a couple of characters, none of them past the first levels. Every design decision on the site comes from refusing to hide that.
"No data" is not "down"
The rule that shaped everything: a reading older than 90 seconds becomes "no data", never "down". They are different claims and conflating them is a lie. That forced a third visual state, in grey, distinct from green and red — and status is never communicated by colour alone, always colour plus a text label.
The same doctrine then had to survive contact with every new section:
- The player counter shows zero when it read a zero, next to the line saying when someone was last seen — but it shows a dash, not a zero, when the reading aged out.
- The population curve draws gaps where a day has no samples. A gap is not a zero, and a curve that cannot be rebuilt backwards must never be padded.
- The armory stops serving the roster entirely past two hours instead of serving it with a footnote. A three-day-old list shows people who may be gone and nobody who arrived since.
Thresholds that live in the data, not in the UI
The census, the ladders and the faction split all have a minimum below which they are not drawn — a percentage over two characters describes a person, not a realm. The interesting part is where that minimum lives: the worker never publishes a block that has not reached its threshold, and the API checks again before serving. The page only knows how to draw what arrives.
That makes the honest behaviour the only possible behaviour: no rushed deploy, no forgotten flag and no well-meaning shortcut can put "100% Paladin over two characters" on screen. And where a block is closed, the page says what opens it and how far away it is — a gold progress bar towards a goal, visually distinct from the ice-coloured bars that carry actual game data.
An armory that is not a radar
Every character has a permanent, shareable URL from day one, with level, class, race, guild, time played, professions and any realm firsts they hold. What it deliberately never publishes:
- The account a character belongs to. The most requested armory feature and the most harmful: it destroys alt anonymity in a realm where everybody knows everybody.
- Zone, map or coordinates. A live player map in a small realm is an ambush radar.
- Last login and last logout. Publishing someone's schedule publishes when they are not home.
- Gold, and who is online right now.
Even the freshness stamp is defensive: it is the age of the whole roster, rounded to hours, because "updated 4 minutes ago" on one character page is a presence signal reconstructed through the back door.
Registration and recovery without ever holding a password
WoW's login uses SRP6: the server stores a salt and a verifier, never the password.
The site computes both inside the serverless function — an implementation validated
against the official janedoe test vector, operating on byte arrays rather than
hex strings, which is where roughly a tenth of the npm libraries get it wrong — and
discards the password immediately. What reaches the queue is already cryptographic
material.
Password recovery had to be designed around that pair, because salt and verifier are equivalent to the password itself: whoever holds them logs in. So there is no site login at all. Recovery is an emailed single-use link, 30 minutes long, where:
- The database stores the digest of the ticket, never the ticket.
- The email index the realm publishes stores digests too, never an address in clear.
- The screen shows the same message whether or not the address has an account.
- A stale email index answers "I don't know", never "that email has no account" — the same doctrine as the realm status, with its own threshold.
The realm publishes outward rather than being polled: a worker reads MySQL and writes state to a cloud database, and the site only reads from there. No inbound connections.
The launcher
An Electron app that reads a manifest of 141 files with a SHA-256 for each one, compares against disk and downloads only what is missing or wrong. It resumes interrupted downloads with HTTP range requests, retries with exponential backoff and verifies while writing. It also leaves the realmlist configured, which removes the single most common reason a new player cannot connect.
The detail that matters at 16.5 GB: after verifying a file it records its size and modification time, so later launches only re-hash what changed. Hashing the whole install on every start would take minutes.
The site's download endpoint resolves the latest published release at request time instead of linking a fixed file — the installer name carries the version inside it, so a hardcoded link would silently rot — and the app updates itself from there via electron-updater.
The contract, in writing
Two sections exist purely because a new realm has to answer "will this still be here in three months?" before anyone invests a hundred hours in a character:
- Progression publishes the whole phase plan up front, with a hard editorial rule enforced in code: a planned date that has passed is not rendered. The page shows "no date announced" rather than an expired promise, which is the loudest possible "abandoned server" signal.
- The realm log ships as typed data compiled into the bundle rather than MDX, so an entry can never 404 in production. English is optional per entry: when it is missing the Spanish is served with an honest note instead of machine translation, and the index warns out loud when the log itself has gone quiet.
Every rate on the site is read from a single constant that mirrors the core's configuration, so the day the realm went from x1 to x8 experience, the rate table, the rules page and the home page all moved together — and the log entry announcing it keeps its literal figures forever, because a log entry is a record of that day, not a live view.
Stack
Next.js 15 App Router, React 19 and strict TypeScript, with a design system isolated from the rest of the portfolio in CSS Modules — two accent colours only, gold for hierarchy and ice for live data, with green, red and grey reserved for realm state so they never appear as decoration. Fully bilingual (Spanish/English) page by page. MongoDB Atlas as the message bus. MySQL for the game core. Electron with electron-updater for the launcher, built on Windows by GitHub Actions and published on tag push. Everything runs on the realm's own dedicated infrastructure.
World of Warcraft® and Blizzard Entertainment® are registered trademarks of Blizzard Entertainment, Inc. Frosthold is not affiliated with Blizzard Entertainment. The server core is licensed under the GPL.



