{"slug": "building-a-keyless-ai-text-adventure-in-pure-pipe-die-ruine-von-aldenmoor", "title": "🏰 Building a Keyless AI Text Adventure in Pure Pipe — \"Die Ruine von Aldenmoor\"", "summary": "A new open-source project demonstrates a keyless AI text adventure game, \"Die Ruine von Aldenmoor,\" built in ~600 lines of Pipe and run entirely on the OpenCode Zen free tier with zero API keys and zero cost. The game uses Pipe's tool-calling, RAG, SQLite state, summarize, and sandbox_profile features to create a deterministic, persistent world with an LLM narrator, achieving instant responses despite latency-heavy AI calls. The project includes 30 unit tests and is designed to showcase Pipe's capabilities for agentic language applications.", "body_md": "[← All posts← Alle Beiträge](../blog.html)\n\n# 🏰 Building a Keyless AI Text Adventure in Pure Pipe — \"Die Ruine von Aldenmoor\"\n\n**A complete, playable fantasy text adventure — tool-calling, RAG, SQLite state, summarize, and a locked-down sandbox — written in ~600 lines of Pipe and run entirely on the OpenCode Zen free tier. Zero API keys, zero cost. This post walks through every Pipe feature the game leans on, plus the two tricks we used to make a latency-heavy AI game feel instant.**\n\nText adventures are the perfect stress test for an agentic language: the game *needs* the LLM to narrate, but the *world* must be deterministic, persistent, and consistent. That tension is exactly what Pipe's toolkit is built for — `ai_with_tools`\n\nfor agency, `embed`\n\n/`nearest`\n\nfor memory, `db_exec`\n\nfor state, `summarize`\n\nfor compression, and `sandbox_profile`\n\nfor safety. Aldenmoor is a demo that exercises all of them at once.\n\n## What the game is\n\nYou wake at the entrance of a cursed ruin. A torch, seven rooms, a locked treasure chamber guarded by a monster, and a silent watchman named Ser Aldric who is bound by a curse. Your goal: return a piece of the unspoiled heritage of Aldenmoor to the watcher — freely, not by force — to break the curse.\n\nIt is a real RPG: movement, item pickup, combat, quests, and a small faction-reputation system. And every turn, an LLM acts as the *narrator*, choosing which tools to call.\n\n```\ncd adventure\n/home/droid/pipe/bin/pipe adventure.pipe\n# > nimm die fackel\n# > geh nach norden\n# > geh nach osten\n# > greife den kammerling an\n# > nimm die muenze\n# > geh nach westen\n# > gib dem waechter die muenze\n```\n\nThe win condition is a pure-Pipe state check: when the `muenze`\n\nreaches the `waechter`\n\n, the curse breaks and the run ends in victory.\n\n## Architecture: three layers\n\n```\nadventure.pipe   game loop, provider setup, feedback (spinner + fast-path)\ntools.pipe       the ai_tool functions + registrations\nstate.pipe       sqlite schema, seed, save/load\nlore.pipe        RAG over world lore\nrooms.json       the 7-room world\nworld/          lore documents (history, factions, the watcher)\n```\n\nThe split is deliberate. `state.pipe`\n\nand `tools.pipe`\n\nare imported by the test suite **without starting the game loop**, so every world rule is unit-testable in pure Pipe (30 tests, no AI calls).\n\n## Feature 1 — Tool-calling without an SDK\n\nThe narrator is one `ai_with_tools`\n\ncall per turn. You register ordinary Pipe functions as tools and let the model decide when to call them:\n\n```\nai_tool \"move_to\" \"Bewege den Spieler durch einen Ausgang\"\n    {exit_name: \"Himmelsrichtung, z.B. norden\"} move_to\nai_tool \"take_item\" \"Hebe einen Gegenstand auf\"\n    {item_name: \"z.B. fackel\"} take_item\nai_tool \"give_item\" \"Biete einem NPC einen Gegenstand an\"\n    {item_name: \"Name\", npc: \"z.B. waechter\"} give_item\nai_tool \"attack_enemy\" \"Greife ein Untier an\"\n    {target: \"z.B. kammerling\"} attack_enemy\n\n-- one turn:\nantwort: ai_with_tools NARRATOR_SYSTEM player_input\n```\n\nThe model sees the tool *schemas* (name, description, typed parameters) and returns structured calls. Pipe executes them and feeds the results back. The functions themselves are plain deterministic Pipe — `move_to`\n\njust mutates the player's room in SQLite and returns the new description. **The LLM never holds the world state; it only narrates the consequences.**\n\n## Feature 2 — RAG so the world stays consistent\n\nWithout memory, the narrator invents rooms, items, and lore on the fly. Aldenmoor instead builds a vector index over its own lore files once at startup:\n\n```\nLORE_FILES: [\"world/lore_history.txt\", \"world/lore_factions.txt\", \"world/npcs/waechter.txt\"]\n\nfn build_lore_index _\n    docs: []\n    for f in LORE_FILES\n        push docs (read_file f)\n    vecs: embed_batch docs\n    set LORE_INDEX 0 {docs: docs, vectors: vecs}\n\nfn lore_context query\n    idx: at LORE_INDEX 0\n    qv: embed query\n    top: nearest qv (get idx \"vectors\") 1\n    -- concatenate the matched lore snippets\n```\n\nWhen the player talks to Ser Aldric, the watcher's `talk_to`\n\npulls the most relevant lore and feeds it as grounding context to `ask`\n\n. The result: the knight mentions the *real* history of Aldenmoor, not whatever the model dreamed up that turn.\n\n**Keyless detail:** the OpenCode Zen provider has no embeddings endpoint, so `embed`\n\n/`embed_batch`\n\ntransparently fall back to Pipe's local hash embedder. RAG still works — just keyword-ish in quality — with no API cost and no setup.\n\n## Feature 3 — SQLite as the single source of truth\n\nEverything that matters lives in SQLite: `player`\n\n, `rooms`\n\n, `monsters`\n\n, `quests`\n\n, `reputation`\n\n, `npc_memory`\n\n, and `flavor`\n\n(more on that later). A small helper escapes strings so lore with apostrophes can't break a query:\n\n```\nfn sql_escape s\n    replace (replace s \"'\" \"''\") \"\\\"\" \"\\\\\\\"\"\n\nfn db_query h sql\n    unwrap (db_exec h sql)   -- pure-Pipe SQL engine, no driver\n```\n\n`save_game`\n\nserialises the whole world into a JSON blob and writes it to `.pipe_sandbox/saves/`\n\n; `load_game`\n\nreplays it row by row. Because the engine is pure Pipe, saves work with zero dependencies.\n\n## Feature 4 — summarize compresses NPC memory\n\nDialogues would otherwise grow the `npc_memory`\n\nfield without bound. Every third exchange we compress it:\n\n```\nturns: npc_turns npc\nif (turns % 3) == 2\n    mem: summarize (alt ++ \"\\nSpieler: \" ++ last_message)\nelse\n    mem: alt           -- keep as-is, but the latest line is still injected live\n```\n\nThe trick: even on the two turns where we *skip* compression, the most recent player line is injected directly into the prompt — so the watcher never \"forgets\" what you just said, while the long-term summary stays cheap.\n\n## Feature 5 — A sandbox that locks everything but the AI\n\nThe world files are read *before* the sandbox is raised, then the filesystem, shell, and free network are all removed:\n\n```\nsandbox_profile \"game\" {fs: \"temp-only\", network: true,\n    network_whitelist: [\"opcode.ai\"], exec: false, ai: true}\nset_sandbox \"game\"\n```\n\nNow the LLM can only reach the OpenCode Zen endpoint (`ai: true`\n\nplus the whitelist), cannot spawn a shell, and can only write to temp. The agent is genuinely constrained — which matters the moment you let a model drive game logic.\n\n## Feature 6 — Keyless free tier via OpenCode Zen\n\nNo API key, no credit card. The provider is selected in one line:\n\n```\nai_provider \"opencode\"\nFREE_MODELS: [\"x-preview-f-free\", \"laguna-s-2.1-free\"]\n```\n\nA small probe picks the first model that answers; the turn loop rotates through the list on failure, so a 503 from one endpoint never blocks the player. The whole playthrough costs **$0**.\n\n## Feature 7 — Making an AI game feel instant\n\nThis was the real engineering. A naive turn is three sequential AI calls (narrator + dialogue `ask`\n\n+ `summarize`\n\n), and free endpoints can be slow. Two layers of work fixed the feel:\n\n**a) Fast-path for common commands.** Direction, pickup, inventory, quests, and combat are parsed by pure Pipe — no LLM at all:\n\n```\nfn fast_command cmd\n    t: lower (trim cmd)\n    w: split t \" \"\n    erstes: at w 0\n    if erstes == \"norden\" || erstes == \"n\" || (erstes == \"geh\" && contains t \"norden\")\n        {handled: true, out: move_to \"norden\"}\n    else if erstes == \"nimm\" && len w > 1\n        {handled: true, out: take_item (rest_words t)}\n    -- ... inventar, quests, greife, etc.\n    else\n        {handled: false, out: \"\"}   -- fall through to the LLM narrator\n```\n\nTyped `> nimm die fackel`\n\nand the torch is in your hand in under 5 ms. The LLM only runs for free text and dialogue.\n\n**b) Pre-generated room atmosphere, in parallel.** At startup we generate an atmospheric description for every room at once with `ai_batch`\n\n(all 7 in one parallel round-trip), and store the result in the `flavor`\n\ntable. During play, `look`\n\nreturns the cached text instantly — no waiting for the narrator to describe a room you just walked into.\n\n**c) A spinner that proves the game is alive.** Pipe has real concurrency (`spawn`\n\n+ channels). While the narrator thinks, a background task animates dots:\n\n```\nfn think_spinner ch\n    v: try_recv ch\n    while v == nil\n        print_raw \".\"\n        sleep 400\n        v: try_recv ch\n    print_raw \"\\n\"\n\nch: chan 1\nspawn think_spinner ch\nantwort: ai_with_tools NARRATOR_SYSTEM text\nsend ch 1\n```\n\nThe player sees `Der Wind trägt einen Hauch Glockenklang herüber. ................`\n\ninstead of a frozen prompt.\n\n## How it all fits together\n\nOne turn, in order:\n\n`fast_command`\n\nchecks for an instant local action. Hit → result printed in <5 ms, done.- Miss → a random ambient line prints, the spinner spawns,\n`ai_with_tools`\n\nruns the narrator. - The narrator calls tools (\n`move_to`\n\n,`take_item`\n\n,`attack_enemy`\n\n,`give_item`\n\n…); each mutates SQLite and returns factual text. - The narration wraps the tool results; the win flag is re-checked; if the curse broke, the epilogue prints.\n\n## Why Pipe, not a Python notebook\n\n**One binary.**`bin/pipe`\n\nis ~8 MB; no venv, no`pip install`\n\n, no model server.**AI is a language primitive.**`ai_with_tools`\n\n,`embed_batch`\n\n,`summarize`\n\n,`ai_tool`\n\nare builtins — no LangChain, no SDK wiring.**Safety is structural.**`sandbox_profile`\n\nconstrains filesystem, network, and shell *as a language construct*, so the agent can't escape into your machine.**It tests without the cloud.** The entire world logic runs under`pipe -test`\n\nwith zero API calls — 30 green tests cover movement, combat, quests, reputation, and that the win condition fires.\n\n## Try it\n\n```\ngit clone https://github.com/MachuraHarry/pipe\ncd pipe/adventure\n../bin/pipe adventure.pipe\n```\n\nThe full source — `adventure.pipe`\n\n, `state.pipe`\n\n, `tools.pipe`\n\n, `lore.pipe`\n\n, `rooms.json`\n\n, the `world/`\n\nlore, and the 30-test suite — is in the repository. It is a small, readable example of how far you can get with agentic AI, persistence, and a real sandbox, all expressed in one language and run for free.\n\n# 🏰 Ein schlüsselloses KI-Textadventure in reinem Pipe — \"Die Ruine von Aldenmoor\"\n\n**Ein komplettes, spielbares Fantasy-Textadventure — Tool-Calling, RAG, SQLite-State, summarize und eine verriegelte Sandbox — geschrieben in ~600 Zeilen Pipe und komplett auf dem kostenlosen OpenCode-Zen-Tier laufend. Null API-Keys, null Kosten. Dieser Post geht durch jede Pipe-Funktion, auf die das Spiel baut, plus die zwei Tricks, mit denen wir ein latenzlastiges KI-Spiel augenblicklich wirken ließen.**\n\nTextadventures sind der perfekte Stresstest für eine agentische Sprache: Das Spiel *braucht* die LLM zum Erzählen, aber die *Welt* muss deterministisch, persistent und konsistent bleiben. Genau diesen Spannungsbogen bedient Pipes Werkzeugkasten — `ai_with_tools`\n\nfür Agentizität, `embed`\n\n/`nearest`\n\nfür Gedächtnis, `db_exec`\n\nfür State, `summarize`\n\nfür Kompression und `sandbox_profile`\n\nfür Sicherheit. Aldenmoor ist eine Demo, die all das auf einmal trainieren.\n\n## Was das Spiel ist\n\nDu erwachst am Eingang einer verfluchten Ruine. Eine Fackel, sieben Räume, eine verschlossene Schatzkammer, bewacht von einem Untier, und ein schweigsamer Wächter namens Ser Aldric, der an einen Fluch gebunden ist. Dein Ziel: ein Stück des unberührten Erbes von Aldenmoor dem Wächter *freiwillig* zurückzugeben — nicht mit Gewalt — um den Fluch zu brechen.\n\nEs ist ein echtes RPG: Bewegung, Gegenstandsaufnahme, Kampf, Quests und ein kleines Fraktions-Reputationssystem. Und jede Runde agiert eine LLM als *Erzähler*, die selbst entscheidet, welche Werkzeuge sie aufruft.\n\n```\ncd adventure\n/home/droid/pipe/bin/pipe adventure.pipe\n# > nimm die fackel\n# > geh nach norden\n# > geh nach osten\n# > greife den kammerling an\n# > nimm die muenze\n# > geh nach westen\n# > gib dem waechter die muenze\n```\n\nDie Siegbedingung ist ein reiner Pipe-State-Check: erreicht die `muenze`\n\nden `waechter`\n\n, bricht der Fluch und der Lauf endet im Sieg.\n\n## Architektur: drei Schichten\n\n```\nadventure.pipe   Spielschleife, Provider-Setup, Feedback (Spinner + Fast-Path)\ntools.pipe       die ai_tool-Funktionen + Registrierungen\nstate.pipe       SQLite-Schema, Seed, Save/Load\nlore.pipe        RAG über Welt-Lore\nrooms.json       die 7-Räume-Welt\nworld/          Lore-Dokumente (Geschichte, Fraktionen, der Wächter)\n```\n\nDie Trennung ist bewusst. `state.pipe`\n\nund `tools.pipe`\n\nwerden von der Testsuite importiert, **ohne die Spielschleife zu starten** — jede Weltregel ist damit in reinem Pipe unit-testbar (30 Tests, keine KI-Calls).\n\n## Feature 1 — Tool-Calling ohne SDK\n\nDer Erzähler ist pro Runde ein einziger `ai_with_tools`\n\n-Call. Du registrierst gewöhnliche Pipe-Funktionen als Tools und überlässt dem Modell die Entscheidung, wann es sie aufruft:\n\n```\nai_tool \"move_to\" \"Bewege den Spieler durch einen Ausgang\"\n    {exit_name: \"Himmelsrichtung, z.B. norden\"} move_to\nai_tool \"take_item\" \"Hebe einen Gegenstand auf\"\n    {item_name: \"z.B. fackel\"} take_item\nai_tool \"give_item\" \"Biete einem NPC einen Gegenstand an\"\n    {item_name: \"Name\", npc: \"z.B. waechter\"} give_item\nai_tool \"attack_enemy\" \"Greife ein Untier an\"\n    {target: \"z.B. kammerling\"} attack_enemy\n\n-- eine Runde:\nantwort: ai_with_tools NARRATOR_SYSTEM spieler_eingabe\n```\n\nDas Modell sieht die Tool-*Schemas* (Name, Beschreibung, typisierte Parameter) und liefert strukturierte Aufrufe. Pipe führt sie aus und füttert die Ergebnisse zurück. Die Funktionen selbst sind schlichtes, deterministisches Pipe — `move_to`\n\nmutiert nur den Raum des Spielers in SQLite und liefert die neue Beschreibung. **Die LLM hält niemals den Welt-State; sie erzählt nur die Konsequenzen.**\n\n## Feature 2 — RAG, damit die Welt konsistent bleibt\n\nOhne Gedächtnis erfindet der Erzähler Räume, Gegenstände und Lore aus dem Stegreif. Aldenmoor baut stattdessen einmalig beim Start einen Vektor-Index über seine eigenen Loredateien:\n\n```\nLORE_FILES: [\"world/lore_history.txt\", \"world/lore_factions.txt\", \"world/npcs/waechter.txt\"]\n\nfn build_lore_index _\n    docs: []\n    for f in LORE_FILES\n        push docs (read_file f)\n    vecs: embed_batch docs\n    set LORE_INDEX 0 {docs: docs, vectors: vecs}\n\nfn lore_context query\n    idx: at LORE_INDEX 0\n    qv: embed query\n    top: nearest qv (get idx \"vectors\") 1\n    -- die passenden Lore-Schnipsel verketten\n```\n\nSpricht der Spieler mit Ser Aldric, zieht dessen `talk_to`\n\ndie relevanteste Lore und füttert sie als Grounding-Kontext an `ask`\n\n. Ergebnis: der Ritter erwähnt die *echte* Geschichte Aldenmoors, nicht das, was das Modell in der Runde gerade träumt.\n\n**Schlüsselloser Detail:** Der OpenCode-Zen-Provider hat keinen Embeddings-Endpoint, also fällt `embed`\n\n/`embed_batch`\n\ntransparent auf Pipe's lokalen Hash-Embedder zurück. RAG funktioniert trotzdem — nur qualitativ eher keyword-artig — ohne API-Kosten und ohne Setup.\n\n## Feature 3 — SQLite als alleinige Wahrheitsquelle\n\nAlles, was zählt, lebt in SQLite: `player`\n\n, `rooms`\n\n, `monsters`\n\n, `quests`\n\n, `reputation`\n\n, `npc_memory`\n\nund `flavor`\n\n(dazu später). Ein kleiner Helper escaped Strings, damit Lore mit Apostrophen keine Query bricht:\n\n```\nfn sql_escape s\n    replace (replace s \"'\" \"''\") \"\\\"\" \"\\\\\\\"\"\n\nfn db_query h sql\n    unwrap (db_exec h sql)   -- reine-Pipe-SQL-Engine, kein Treiber\n```\n\n`save_game`\n\nserialisiert die ganze Welt in einen JSON-Blob und schreibt ihn nach `.pipe_sandbox/saves/`\n\n; `load_game`\n\nspielt ihn zeilenweise zurück. Weil die Engine reines Pipe ist, funktionieren Saves ohne Abhängigkeiten.\n\n## Feature 4 — summarize komprimiert NPC-Gedächtnis\n\nDialoge würden das `npc_memory`\n\n-Feld sonst ungebremst wachsen lassen. Jeden dritten Austausch komprimieren wir es:\n\n```\nturns: npc_turns npc\nif (turns % 3) == 2\n    mem: summarize (alt ++ \"\\nSpieler: \" ++ last_message)\nelse\n    mem: alt           -- unverändert lassen, aber die letzte Zeile wird live injiziert\n```\n\nDer Trick: selbst in den zwei Runden, in denen wir die Kompression *überspringen*, wird die aktuellste Spielerzeile direkt in den Prompt injiziert — der Wächter „vergisst\" also nie, was du gerade gesagt hast, während die Langzeit-Zusammenfassung günstig bleibt.\n\n## Feature 5 — Eine Sandbox, die alles außer der KI sperrt\n\nDie Weltdateien werden gelesen, *bevor* die Sandbox gehoben wird; danach werden Dateisystem, Shell und freies Netz entfernt:\n\n```\nsandbox_profile \"game\" {fs: \"temp-only\", network: true,\n    network_whitelist: [\"opencode.ai\"], exec: false, ai: true}\nset_sandbox \"game\"\n```\n\nJetzt kann die LLM nur noch den OpenCode-Zen-Endpoint erreichen (`ai: true`\n\nplus Whitelist), keine Shell spawnen und nur nach Temp schreiben. Der Agent ist echt constrainiert — was genau dann zählt, wenn du ein Modell Spiellogik treiben lässt.\n\n## Feature 6 — Schlüsselloses Free-Tier via OpenCode Zen\n\nKein API-Key, keine Kreditkarte. Der Provider steht in einer Zeile:\n\n```\nai_provider \"opencode\"\nFREE_MODELS: [\"x-preview-f-free\", \"laguna-s-2.1-free\"]\n```\n\nEine kleine Sonde wählt das erste Modell, das antwortet; die Rundenschleife rotiert bei Fehlschlag durch die Liste, sodass ein 503 eines Endpunkts den Spieler nie blockiert. Der komplette Durchlauf kostet **$0**.\n\n## Feature 7 — Ein KI-Spiel augenblicklich wirken lassen\n\nDas war die eigentliche Ingenieursarbeit. Eine naive Runde sind drei sequenzielle KI-Calls (Erzähler + Dialog-`ask`\n\n+ `summarize`\n\n), und Free-Endpunkte können langsam sein. Zwei Arbeitsschichten fixten das Gefühl:\n\n**a) Fast-Path für Standardbefehle.** Richtung, Aufnehmen, Inventar, Quests und Kampf werden von reinem Pipe geparst — gar keine LLM:\n\n```\nfn fast_command cmd\n    t: lower (trim cmd)\n    w: split t \" \"\n    erstes: at w 0\n    if erstes == \"norden\" || erstes == \"n\" || (erstes == \"geh\" && contains t \"norden\")\n        {handled: true, out: move_to \"norden\"}\n    else if erstes == \"nimm\" && len w > 1\n        {handled: true, out: take_item (rest_words t)}\n    -- ... inventar, quests, greife, etc.\n    else\n        {handled: false, out: \"\"}   -- an den LLM-Erzähler durchreichen\n```\n\nTippt man `> nimm die fackel`\n\n, ist die Fackel in unter 5 ms im Beutel. Die LLM läuft nur für Freitext und Dialog.\n\n**b) Vorgenerierte Raumatmosphäre, parallel.** Beim Start erzeugen wir mit `ai_batch`\n\nauf einmal eine atmosphärische Beschreibung für jeden Raum (alle 7 in einem parallelen Round-Trip) und speichern das Ergebnis in der `flavor`\n\n-Tabelle. Während des Spiels liefert `look`\n\nden gecachten Text sofort — kein Warten auf den Erzähler, der einen Raum beschreibt, den du gerade betreten hast.\n\n**c) Ein Spinner, der beweist, dass das Spiel lebt.** Pipe hat echte Concurrency (`spawn`\n\n+ Channels). Während der Erzähler denkt, animiert ein Hintergrundtask Punkte:\n\n```\nfn think_spinner ch\n    v: try_recv ch\n    while v == nil\n        print_raw \".\"\n        sleep 400\n        v: try_recv ch\n    print_raw \"\\n\"\n\nch: chan 1\nspawn think_spinner ch\nantwort: ai_with_tools NARRATOR_SYSTEM text\nsend ch 1\n```\n\nDer Spieler sieht `Der Wind trägt einen Hauch Glockenklang herüber. ................`\n\nstatt eines eingefrorenen Prompts.\n\n## Wie alles zusammenspielt\n\nEine Runde, in Reihenfolge:\n\n`fast_command`\n\nprüft auf eine instant lokale Aktion. Treffer → Ergebnis in <5 ms ausgegeben, fertig.- Kein Treffer → eine zufällige Ambiente-Zeile wird ausgegeben, der Spinner startet,\n`ai_with_tools`\n\nlässt den Erzähler laufen. - Der Erzähler ruft Tools auf (\n`move_to`\n\n,`take_item`\n\n,`attack_enemy`\n\n,`give_item`\n\n…); jedes mutiert SQLite und liefert Faktentext. - Die Erzählung rahmt die Tool-Ergebnisse; die Sieg-Flagge wird neu geprüft; brach der Fluch, druckt die Epilog.\n\n## Warum Pipe, nicht ein Python-Notebook\n\n**Eine Binary.**`bin/pipe`\n\nist ~8 MB; kein venv, kein`pip install`\n\n, kein Modellserver.**KI ist ein Sprach-Primitive.**`ai_with_tools`\n\n,`embed_batch`\n\n,`summarize`\n\n,`ai_tool`\n\nsind Builtins — kein LangChain, kein SDK-Verkabeln.**Sicherheit ist strukturell.**`sandbox_profile`\n\nconstrainiert Dateisystem, Netz und Shell *als Sprachkonstrukt*, sodass der Agent nicht in deine Maschine ausbrechen kann.**Es testet ohne Cloud.** Die gesamte Weltlogik läuft unter`pipe -test`\n\nmit null API-Calls — 30 grüne Tests decken Bewegung, Kampf, Quests, Reputation und dass die Siegbedingung feuert.\n\n## Ausprobieren\n\n```\ngit clone https://github.com/MachuraHarry/pipe\ncd pipe/adventure\n../bin/pipe adventure.pipe\n```\n\nDer komplette Source — `adventure.pipe`\n\n, `state.pipe`\n\n, `tools.pipe`\n\n, `lore.pipe`\n\n, `rooms.json`\n\n, die `world/`\n\n-Lore und die 30-Test-Suite — liegt im Repository. Es ist ein kleines, lesbares Beispiel dafür, wie weit man mit agentischer KI, Persistenz und echter Sandbox kommt, alles in einer Sprache ausgedrückt und kostenlos laufend.", "url": "https://wpnews.pro/news/building-a-keyless-ai-text-adventure-in-pure-pipe-die-ruine-von-aldenmoor", "canonical_source": "https://pipe-lang.com/blog/adventure-aldenmoor.html", "published_at": "2026-08-24 00:00:00+00:00", "updated_at": "2026-08-24 02:13:17.009823+00:00", "lang": "en", "topics": ["artificial-intelligence", "ai-tools", "ai-agents", "large-language-models", "generative-ai"], "entities": ["Pipe", "OpenCode Zen", "Aldenmoor", "Ser Aldric"], "alternates": {"html": "https://wpnews.pro/news/building-a-keyless-ai-text-adventure-in-pure-pipe-die-ruine-von-aldenmoor", "markdown": "https://wpnews.pro/news/building-a-keyless-ai-text-adventure-in-pure-pipe-die-ruine-von-aldenmoor.md", "text": "https://wpnews.pro/news/building-a-keyless-ai-text-adventure-in-pure-pipe-die-ruine-von-aldenmoor.txt", "jsonld": "https://wpnews.pro/news/building-a-keyless-ai-text-adventure-in-pure-pipe-die-ruine-von-aldenmoor.jsonld"}}