The game is a pure function
In this writeup
I built a game in my spare time. Evenings, mostly. It’s called The Lantern and the Lair, and it’s a cozy-horror descendant of Hunt the Wumpus, the 1973 text game, with a nod to The Oregon Trail. It goes up free this Friday.
I don’t make games for a living. I run technical programs and build evaluations, and you can tell. Most people start a game with a sketch of a monster. I started with three assumptions and a plan to check each one. The monster came later.
The assumptions
One: the game never lies to you. Every room tells you, honestly, what’s next door. A cold draft means a pit. A skittering means a creature. A pallid stench means the thing that hunts you. Old iron means the Heart. What the room won’t tell you is which doorway. For that you Focus, and Focusing costs oil and time.
So if you die, it’s not because the game cheated. It’s because you were told something and weighed it wrong. That’s the whole design bet. It’s also the kind of claim that’s worth nothing unless you can check it.
Two: checking it means every run has to replay. If a death is a chain of decisions, I need to pull the chain apart afterwards, exactly as it happened, not approximately. That one sentence decided the architecture before a single room existed.
Three: if a bot can play it, so can a language model. The bots were for tuning. But a bot that plays through the same interface a person uses is most of a harness for anything that can read text and pick from a list. Once that harness existed, the question I actually cared about was sitting right there: how do models do against each other, and against a person, in a game that never lies?
The game, briefly
You go down into a labyrinth of rooms on a ten-by-ten grid, holding an oil lamp that every action burns. Find the Heart, carry it back out, or retreat alive without it. Something hunts you by the scent you leave behind. It can’t be killed. You smell it when it’s within two rooms, which is about the amount of warning you’d want and slightly less than the amount you’d like.
Nearly every action is a twenty-sided die plus labelled modifiers against a number, and the margin decides how well it went, not just whether. My favourite outcome band is “you got it, and it cost you.” A roll that went badly can be bet on a coin: heads turns it into a triumph, tails into a disaster. Creatures can be fought, which is quick and loud, or tamed, which is slow and quiet. A tamed one can be sent ahead as bait, and it doesn’t come back. Browser-first, text-first, free. Four difficulties: Drowsing, Stirring, Hunting and Ravening.
One engine, and nothing else touches the dice
The rules live in one pure module. It imports nothing. It touches no ambient state: no system clock, no screen, no randomness it wasn’t handed. There’s a test that fails the build the moment any of that stops being true, which is the software equivalent of a tripwire across the kitchen door.
The payoff is that a run is two things, a seed and a list of moves. Replay the moves against the seed and you get the same run, exactly. Three things drive the engine, all through the same interface: a text renderer, a visual renderer and a bot harness. None of them can change a rule. A bot’s run goes through exactly the same rules as a person’s. That sentence is doing a lot of work in Friday’s post.
The first thing the replays caught was me. Two runs from my friends’ build on 24 September ended on turn 2 and turn 3. Nobody died. Each player took one step in and one step back (one of them stopped to Focus in between), and the game logged a retreat, because I’d made the entrance not-a-room, and stepping onto it without the Heart ended the run. The replays couldn’t tell me why they turned around. They told me exactly what happened, and that was plenty. The entrance is a room now. Leaving is a choice, with a confirm.
What the bots measure
The harness hands a player the same view a person gets in text mode: the room, what’s coming through its doorways, the map so far, and a numbered list of actions. That’s it. A second test fails if any hidden state leaks into that view, and it matters more for bots than for people, because a bot will use whatever you give it. Every time.
Three bots set the scale. The floor bot plays from that view alone, by fixed rules I wrote. The random bot picks any legal action, like a toddler at a vending machine. The ceiling bot is the deliberate cheat: it reads the true map and walks straight to the Heart. It’s good at finding, and even it gets out of fewer than one run in five at Ravening. Floor and random tell me what the honest view is worth to a careful player and to no player at all. Ceiling tells me how much of the game is the map, and how much is the thing behind you.
Escape rate is the obvious metric and the least useful one. A hundred yes-or-no outcomes can’t separate two players that are close. So the harness also records, per run, how many turns you survived, how deep you went and when you turned back, how often you walked into a room whose hazard had been announced the turn before, whether you tamed or fought, whether you took the coin, and what finally ended you. Those numbers say what a player was doing, not just whether it got out.
The Heart that didn’t go deeper
The bots found the result I didn’t see coming. It’s from an 18 September build. Four days later I moved the Heart closer at every difficulty, so these numbers describe a placement the game no longer uses, and I haven’t re-measured on the shipped rules. Caveat filed.
Nearly all of the grid was reachable within half the turn budget: 94.9 to 100 percent, depending on difficulty. A round trip to the Heart left 21 to 39 turns spare. That looks like slack. And the obvious thing to do with slack is put the Heart deeper so people see more of the labyrinth.
So I tried it in simulation. Two rooms deeper bought about 1.8 more rooms explored per run at Drowsing, the only difficulty where I counted. It cost 6 points of escape rate at Drowsing, 8 at Stirring, 11 at Hunting and 14 at Ravening. Four rooms deeper, the two hardest difficulties were close to unwinnable: escapes in 7.8% of runs at Hunting and 2.2% at Ravening. Those are the most depth can cost, because the sweep’s bots walk the shortest path and never run from the thing. The Heart didn’t go deeper.
Here’s why. A deeper Heart means more rooms walked in, and more rooms walked back out carrying it, with something tracking your scent the whole way. The turn budget could afford a deeper Heart. The monster couldn’t. The spare turns weren’t slack. They were the price of the danger. I would have gotten that wrong by feel. It showed up in a table first.
This Friday
With the harness done, the third assumption was cheap to test. Seven language models have now played the same labyrinths, through the same harness, against the same bots, every run replayable. I played the first twenty labyrinths myself before any model did, once each, so there’s a human in the lineup too. Me, specifically. No pressure.
On 13 November the game goes up on itch, and the other post goes up with it: how the models did against each other, how they did against me, and whether, in a game that never lies, losing really did come down to decisions.