Wildermarch
A 2 to 4 player fantasy hex-tile strategy game: face-down tile exploration, a four-resource economy, dungeon raids, and secret win conditions.
01
Context
Wildermarch is a hex-tile strategy game for two to four players. You reveal the board as you walk onto it, run an economy of gold, wood, food and ore, raid dungeons, sabotage each other, and win through a secret condition dealt to you from a deck of eight that only you can see.
There is one line drawn through the middle of it, and everything in this study sits downstream of that line. On one side are the rules, as a pure package with no dependencies and no I/O. On the other side is everything else: the board, the server, the balance simulator, the replays. All of them are callers.
I own both sides. The tile draw, the resource economy, the network protocol, the rendering, the deploy. There is no rules person and no engine person, which means every decision below is one I made and get to answer for.
02
The constraint
Three properties of the game make it a harder software problem than a hex grid suggests.
Hidden information. Tiles are face down until somebody steps on them, and your win condition is yours alone. A client that receives the full state cannot be trusted not to reveal it, no matter how careful the interface is, so the server can never send a client everything it knows.
Determinism. The game has a random tile draw and still has to replay identically. Without that, a bug report is a story rather than a reproduction, and a balance finding is an anecdote.
Balance. An economy, raids, sabotage and eight possible win conditions generate more interactions than one person can hold in their head, let alone reason about honestly. You do not argue a game into balance. You play it a few thousand times.
03
What was decided
Four decisions, each one falling out of a constraint above.
The rules engine is a pure package. TypeScript, no dependencies, no I/O. It takes a state, an action and a seed, and returns the next state plus the events that got it there. It cannot read the clock, reach the network or invent its own randomness, because everything it needs is handed to it. That is the difference between deterministic and usually deterministic.
The database is the server. The game runs as a module on SpacetimeDB. Reducers mutate state transactionally, clients subscribe over a socket and get pushed what changed, and hidden information is handled by giving each caller its own view of the world rather than by asking the client to be polite about what it was sent.
The board is drawn on a canvas. Dozens of hex tiles lifting, flipping and animating at once is not a layout problem, so it renders through PixiJS instead of through the DOM.
The simulator is the same engine with nothing attached. Bots play each other headlessly, thousands of games at a time, and balance questions get answered by running them rather than by discussing them. The engine's purity is exactly what makes this cheap: nothing to stub, because there is no I/O to stub, and a seed on every run means a surprising result is one somebody can go and look at.
All four are the same decision seen from different sides. The rules are pure, deterministic and testable, and the live game, the simulator and the replays are three callers of one implementation, so a rule fixed once is fixed in all three.
04
What it cost
Zero dependencies means writing things that already exist. Seeded shuffling and serialization are solved problems and I solved them again. I still think it is the right call for a rules engine, and the bill is real.
The boundary taxes every feature. Anything that wants the time, the network or a random number has to have it passed in, which turns a six-line change into a six-line change plus plumbing. That cost lands on small features hardest, and small features are most of them.
SpacetimeDB is young. Choosing it means fewer people have hit your problem before you did, and the answer to a strange evening is more often reading source than reading an answer.
Solo means no review. There is nobody to tell me an idea is bad before it is in the engine with three features standing on top of it. I have paid that one more than once, and it is the cost I would most like to buy my way out of.
And the simulator only answers the questions I thought to ask. Ten thousand bot games prove the bots cannot break it. Bots are not people, and people find the thing you did not think of on their second evening.
05
What it returned
There is a build you can open and play: wildermarch.vercel.app. It is still in development, which is the normal state of a board game rather than an apology for one.
What I would carry into other work is not the stack. It is that the boundary is worth more than anything sitting on either side of it. Every hard question in this project turned out to be answerable on one side or the other, and the questions that stayed hard were the ones where I had put something on the wrong side.
Determinism is the clearest case. It is not a testing strategy bolted onto the engine, it is a consequence of where the line sits, and an engine allowed to read a clock or reach the network could not have it at any price.
The other half is quieter and I think it is the more useful half. A hobby project is where you get to hold a line that a deadline would have talked you out of. This one is where I found out which lines are worth holding.