← Back to devlog

Replay verification, the storefront, and player profiles

by Keith Elliott

Ten days in one entry, because the whole stretch was really one piece of work: making a score on a leaderboard mean something.

The game template got a deterministic simulation layer — sim::SimSet and sim::RunClock — so a run can be replayed from its input log and verified on the server rather than taken on trust from the browser that sent it. Getting there turned up three template bugs, which means all the games had them at once. The sim was reading the app's lifetime clock rather than its own, so a run's outcome could depend on how long the app had been open before you pressed start. A second run could inherit state from the first. And the clock formula had drifted from what the ports were actually doing. Each fix shipped as a "second-run probe" test that every game now carries, so "play it twice, get a different answer" can't come back quietly.

Then the port: every game onto the new sim, across two template revisions, 17ea13b and then 4123759. Voidrunner, Gulper, Grand Theft Otto, Beat Bender, Ladder Legend, Hunted, Pizza Pinball, BeerPong and Tire Stack got their own port commits; the rest came through on the template upgrades. Hunted picked up a same-tick double despawn fix on the way. Every game's docs now carry a replay-verification.md and the determinism conventions.

The template also gained store-assets.sh, which builds screenshots, a trailer and v2 metadata out of a recorded capture. Fifteen of the sixteen games committed that set for their store pages. The exception is Hunted, which still has no version, developer, release date or screenshots on its page — I had that on my list before this fortnight and it is still on it.

The website took about seventy-five commits. The storefront went up first: a checked-in catalog behind fetchCatalog(), v2 metadata parsed and validated at publish time, a home page with a featured game, new releases and the week's top scores, and game pages with a gallery, controls and related games under the demo.

Then names. There are Profile and ProfileWallet models now, a public profile at /u/[handle], a claim-and-edit settings page, preset avatars, and handles resolved onto every leaderboard, with ranking order and score format declared per game. Scores stopped being anonymous strings. The rules that govern hunt names now govern handles, and there is an admin ban. Most of the real time went into a long tail of Privy session-timing and stale-response fixes. Runs are shareable too: a share token on the run, an owner-only share API, and a /c/[token] page with the live standing on it.

The last two days were display. Games declare an aspect ratio in metadata — 4:3, 1:1 or 3:4 — and the player sizes the game box from that. The gallery stopped cropping. gamebient-input v0.4.0 added named pinned canvas sizes, a pad-aware fit so the touch pad never covers the canvas, and a highlight event for cabinet marquees. The template pinned to v0.4.0 and grew a display module: pinned window, native letterboxing, UI scaled by the short side, borderless fullscreen on Linux cabinets, with the cabinet layout tables written as tests.

Gravestone Gauntlet got a visual pass, one graveyard art direction carried from the cover into the game. HUD entities now survive band swaps. Text scale animates instead of font size, which was freezing the web build with an out-of-memory. And a copy-pasted "Voidrunner" was sitting in the build banners of thirteen other games, which is exactly as embarrassing as it sounds.

Play something: colecovisiongx.com/library — or go straight at Dough.io.