# Text Adventure > A text adventure driven by free-text input, where the world state is held by the page > rather than by the model's memory — so a long story cannot drift and a truncated > conversation cannot lose an early scene. Live at https://text-adventure.skillsafe.ai/ — a static bundle on the SkillSafe app platform, running one metered model lane (`gpt-terra`) through multi-turn sessions. ## What it does You pick a setting, type what you want to do in plain English, and the story plays out. Inventory, location, established facts, open goals and the routes you have closed persist across turns, and choices genuinely foreclose and open things. ## The design problem it exists to solve Multi-turn model sessions on this platform have two properties that are harmless for chat and fatal for interactive fiction: 1. **Conversation history is truncated oldest-pair-first** as it grows. The turns most likely to be dropped are the earliest ones — which in a story are the load-bearing ones. 2. **Sessions cap at 200 messages**, so a hundred-turn adventure runs out of conversation entirely. The answer this app implements: **the model is never the custodian of anything the story depends on.** - The **client owns a structured world**: location, a discovered map, inventory, a set of established flags, a list of foreclosed routes, a list of newly-opened ones, open threads, and a one-line-per-turn chronicle. - A model reply is parsed as **narration plus separately-labelled proposed mutations**. Each mutation is validated against the world before it is applied. Gaining an item is always allowed; losing one is only allowed if it was held; resolving a thread only if it was open; moving is checked against the foreclosed list. A mutation that cannot be true is refused and surfaced, not absorbed. - **Every outbound turn restates the whole world** inside a fixed character budget (5,200 characters). When the budget is tight the app walks a published ladder of degradations that gives up old prose and old flavour first. Location, inventory names, established flags, foreclosures, open threads and the player's own move appear on no rung of that ladder and are never sacrificed. - **The chronicle is compressed by keeping turns that changed something and thinning turns that only described something.** That distinction is decidable from the client's own mutation record — no second model call, no summarisation — and it is exactly the right one, because the turns a later scene can contradict are precisely the turns that changed the world. - Because nothing is kept in the conversation, **the conversation is disposable**. At turn forty the app deletes it and opens a fresh one, well clear of the message cap, losing nothing. ## The turn contract Replies are plain labelled lines, not JSON — lines complete individually while streaming, so a half-arrived reply still renders and one stray comma cannot destroy a billed turn. `NARRATION` (the only multi-line label) · `MOVE` · `PLACE_NOTE` · `GET` · `DROP` · `FLAG` · `CLOSED` · `OPENED` · `THREAD` · `RESOLVE` · `CHRONICLE` · `EXITS` · `MOOD` · `ENDING` · `ENDING_TEXT` · `NOTE` Four turn kinds, each with its own required labels: `open` (first scene of a custom world), `act` (the ordinary turn), `nudge` (looking around, changes nothing), `close` (ending it). The kind is a property of the envelope, not a field in a JSON body — `sessions.send` takes a string. ## What is free, with no account and no credits - **Opening any of the five bundled worlds.** Each ships a hand-written first scene, starting inventory, opening threads and exits, so the page is a working adventure before sign-in. - **Two recorded expeditions** — one ending in victory, one in death — which replay through the real parser and the real renderers. - **The continuity auditor.** Runs in the browser on every scene: catches narration that uses an item you no longer carry, a foreclosed route being walked again, two established facts that contradict each other, a narrator that has begun repeating an earlier scene word for word, and any mutation the world refused. - **The pressure meter** — how much has shut behind you against how much has opened. - **The recap**, rebuilt entirely from the client's record rather than from the conversation. - The map, the chronicle, the inventory panel, and all three exports. ## Exports The whole story as Markdown, the chronicle as CSV (turn, location, whether it changed anything, what happened, gained, lost, closed), and the full world as JSON. ## Reliability notes worth knowing - `sessions.send` accepts no idempotency key. A blindly resent turn would append a *second copy* of the move to server-side history — worse than a double charge. Instead every send is guarded by an in-flight lock, and a turn interrupted by a reload is settled by asking the session how many assistant messages it holds and adopting the reply if the move landed. - That reconciliation counts **assistant messages against this session**, not against the story's turn count, because the app rotates sessions and a fresh one starts counting at zero. - The run in progress is a **collection row**, not an `ss.data` key: `ss.data` reads are cached about ninety seconds per subject and key, which would hand a reloading player a stale world. - Records are trimmed to the 64 KB document cap by shortening old prose and dropping old scenes — never by dropping a fact, an item or a foreclosure. ## Content Generated fiction, general audience. Peril, dread, danger and death are in scope; graphic violence and sexual content are not, and that boundary is stated in the system prompt rather than left to chance. Nothing in the app describes real people, places or events. ## Pages - `/` — the app - `/api.html` — driving it programmatically over the app API, in eight languages - `/tokens.html` — token management for this app (noindex)