Instant Scenes: Write the Next Turn Early, Throw Most of It Away
A write-up for developers of AI games. Last updated 31 October 2026
We build Fable Forge, a one-player tabletop-style RPG in the browser where an AI narrates and the game's own rules decide what happens. This is one technique from building it, written for other developers of AI games: what worked, and what failed first.
The problem
A full narration call takes several seconds. Players forgive that after a bold move they typed themselves. They don't forgive it every time they walk through a door or say hello to someone.
Those moments have one thing in common: the player picks from a short list. A room has a handful of exits and a few people in it. So you can guess every option and write all of them before the click.
The idea
When the player arrives somewhere, a background job writes the full scene for every exit, and the opening greeting for each character present. When the player clicks one, the scene is already written.
Most of what you write is thrown away. That is the price, and we come back to it below.
The one rule that makes it safe: exact prompt match
Key each pre-written scene by a hash of the exact prompt that produced it. Build that prompt through the same code the live turn uses, not a copy of it. At click time the live path builds its own prompt as usual, hashes it, and uses the stored scene only if the hashes match exactly.
What this gives you:
- No invalidation rules. The player equips an item, a quest updates, something happens in the room: the prompt bytes change, the hash no longer matches, and the turn runs live. You never have to list what counts as a change.
- A hit is the real thing. It is exactly the scene the click would have generated, not a cheaper or shorter version.
- Mistakes cost time, never correctness. A bug in the background job means a miss and a normal wait. It never puts wrong text on screen.
Two details that matter:
- Build from the state being saved, not the state the request started with. The next click loads the saved state, so that is the prompt it will build.
- Expire entries after a few minutes, on the server and in the client.
Making it instant on screen
A server-side hit still costs a round trip. To get to zero:
- Push the pre-written scenes to the client as they are written.
- On click, show the matching scene straight away, while the server checks the hash behind it.
- Keep the Continue button locked until the server confirms.
- On a hit the confirmed text is identical, so the swap can't be seen. On a miss the live text replaces it.
- Any player action clears everything the client is holding. The state just changed, so nothing written before it may be shown after it.
What failed first
- Building inputs in two places. Our background job gathered some prompt inputs with its own copy of the code. The copies drifted, and hits quietly dropped with no error. Every input has to come from one function that both paths call.
- Visible swaps. Showing pre-written text that turns out to be a miss means replacing it while the player reads, which is worse than waiting. So don't pre-write scenes you know will miss. In our case that is a move that completes a story step, because that turn carries content the background job can't build. Skipping those costs nothing: they were always going to miss.
- Refreshing late. Ending a conversation changes the state the next move's prompt is built from. Our first refresh was queued behind other background work, and every move straight after a conversation missed. Refresh straight from the request that changed things.
- Voice played too early. We never generate voice speculatively; it costs too much to throw away. Instead, on click we start generating voice for the pre-written text at once. Our first version also played it at once. On a miss, the replacement audio spoke different text, and players heard the line restart mid-sentence. Now we fetch straight away but hold playback until the turn is confirmed.
- Voice fetched too widely. Fetching every sentence of the pre-written scene at once filled the voice provider's limit on requests in flight. The real turn's first clip then queued behind them and hung. Only the first sentence needs a head start; the rest can follow as each one plays.
- A second audio element. If your play route generates audio on request, creating a new audio element for the same clip pays for it twice. Reuse the element.
- The window that asked never heard the answer. A pre-written result can arrive before the HTTP response does. A window that closes on response, such as a map, is gone before the result lands, so the turn hangs on a locked button "sometimes". The part of the interface that sends a turn must stay mounted until its result arrives.
Measure what it costs
- Hit rate is not use. Hit rate is hits per click. Use is scenes shown per scene written. With one scene per exit, most are written for nothing. The money you spend on those buys speed, not content. Decide with that number in front of you.
- Make the counter durable. Our hit and miss log was debug-only and invisible in production for a while. Count both, keep the counts, and cross-check them: misses should equal the live calls you made for those clicks.
- A small bonus. If your provider caches prompt prefixes, the background calls warm that cache, so even a miss runs on a warm prefix.
Checklist
- Only for choices from a short list (exits, people), not free-typed actions.
- Keyed by a hash of the exact prompt, built through the live code path.
- Every input comes from one function both paths share.
- Built from the state being saved.
- Entries expire, and any player action clears the client's copies.
- Scenes that will certainly miss are not written at all.
- Continue stays locked until the server confirms.
- Voice is fetched on click, first sentence only, and held until confirmed.
- The part of the interface that sends a turn outlives it.
- Hits, misses and scenes used are counted and kept.
Limits
- It costs real money for text nobody reads. It suits cheap, fast models and choice-heavy moments. Measure use before you widen it.
- It only covers choices you can list. A free-typed action still waits.
- The pre-written scene is one sample of what the live call would produce. If the prompt needs the exact moment of the click (a clock, a timer), it will never match.
Licence
This write-up is shared under CC BY 4.0. Copy it and adapt it freely, with credit to Fable Forge and a link to this page. To see these scenes in play, try Fable Forge.
Browse in Fable Forge