Project 09
Gloaming
The board that plays back.
Version one worked perfectly and frightened nobody. So I threw it away and rebuilt every rule as something you can watch move.

I wanted to make a co-op horror board game where the board itself is the enemy — no human running the monster, the game running it. I got it working. Every rule fired correctly, the numbers went up and down exactly as designed, and it was completely unfrightening. It played, as I wrote at the time, like a spreadsheet in a horror skin.
So I threw it out and rebuilt it on one rule: if a mechanic is a hidden number doing arithmetic, replace it with a thing that visibly moves, grows, shrinks or gets eaten. The night meter became tiles actually turning to void from the outside in. The currency became a torch that burns down, and at zero you become a drifting wisp. Progress became three lanterns you physically carry — and drop where you fall.
Then I had to prove the game was worth playing well, which is a harder question than whether it works. So I wrote a smart bot that reads the monster's telegraphed route and protects whoever is carrying a lantern, and a greedy bot that just grabs, and played them against each other on identical seeded games so the only variable was decisions. The first run came back flat — skill was not paying. That result changed the design: the torch went from eight to seven, and stepping on frayed ground went from costing one to costing two. Now a skilled bot scores sixteen points higher than a greedy one over the same seeded games, and I can show you the number.
Why it was hard
A co-op game where the board is the antagonist normally needs a human to run the antagonist’s bookkeeping, which caps how clever it can be. But the deeper problem arrived in the middle of the build: everything worked mechanically and it still played like a spreadsheet in a horror skin. Numbers went up and down correctly and none of it was frightening. So I tore it down and rebuilt it on a single rule — if a mechanic is a hidden number doing arithmetic, replace it with a thing that visibly moves, grows, shrinks, or gets eaten.
How it actually works
The board is rings inside rings, and the dark eats them from the outside in. Where you are standing IS the clock — there is no timer anywhere on screen.
The board is a graph of concentric rings, and the dark consumes the outermost surviving ring inward. The geometry is the mechanic: which act you are in is read off how deep the dark has eaten, not from a counter.
1 receipt
- gloaming-gameRing-graph board with the dark advancing inward as the act structure.
Every hidden number became a thing you can watch move. The clock became tiles going dark. Money became a torch burning down. Hit zero and you are a drifting wisp.
I replaced every abstraction with an object. The night meter became tiles turning to void; a currency became a torch that burns down, and at zero you become a drifting wisp; progress became three lanterns you physically carry, and drop where you fall.
1 receipt
- READMEThe session-five teardown and the rule it was rebuilt around.
The monster shows you exactly where it is going before it goes, and visibly changes its mind when it picks a new target. The dread is in watching it decide.
The monster telegraphs its entire route before it moves, and visibly changes its mind when it re-targets. The fear comes from watching it decide, not from a dice roll you cannot see.
1 receipt
- READMETelegraphed movement, evolving by act; the Gate is sanctuary it cannot enter.
Two players or six, it squeezes just as hard. Otherwise a full table is an easy table.
I normalise pace by player count, so a two-player game and a six-player game feel the same rather than one being trivially easy.
1 receipt
- PROGRESS.mdPer-round accumulators divided by party size.
You cannot cheat by clicking fast. The rules live inside the game itself rather than in whether a button happened to look clickable — checked across 1,272 turns of play.
Out-of-order input is rejected by the engine, not hidden by the interface — asserted across 1,272 turns. A disabled button is a suggestion; a reducer that refuses is a guarantee.
1 receipt
- PROGRESS.md:17The UI-state contract, verified over 1,272 turns.
There is not one sound file in the whole project. Every noise you hear is built by the browser, live, out of nothing.
Every sound in the game is synthesised at runtime. There is not a single audio file in the repository.
1 receipt
- package.jsonHowler driving procedurally generated tones — zero audio assets.




The numbers
receipts
- PROGRESS.md:17Measured at two players and positive at every player count.
receipts
- PROGRESS.md:17Measured win-rate band with random heroes.
receipts
- PROGRESS.md:17Per-hero balance measurement.
receipts
- PROGRESS.md:17Referee assertions H1–H17 plus randomised full games.
receipts
- PROGRESS.md:17Verified locally and against the deployed site.
receipts
- README:142"Runtime AI: None. Fully self-contained — no LLM, no network, no secrets at runtime."
The part most people would have skipped
My skill gap came out flat on the first measurement — a good player was winning no more often than a greedy one, which meant the decisions did not matter. Rather than shipping and hoping, I tightened two numbers until they did — one fewer turn of torch, one more point of cost for careless footing. Squeezing the margin is what turned reading the monster’s telegraph from a nicety into the difference between finishing and not. I tuned it until skill provably paid, and left the proof in the repository.
- boardgame.io
- React 19
- Vite
- TypeScript
- Framer Motion
- Howler (synthesised audio)
- puppeteer-core

