https://github.com/danielbodart/talebrary
An interactive fiction platform for discovering and playing 2,000+ text adventure games. Browse a curated catalogue, play classics like Zork in the browser, and enjoy AI-generated cover art and illustrations throughout.
https://github.com/danielbodart/talebrary
Last synced: 2 days ago
JSON representation
An interactive fiction platform for discovering and playing 2,000+ text adventure games. Browse a curated catalogue, play classics like Zork in the browser, and enjoy AI-generated cover art and illustrations throughout.
- Host: GitHub
- URL: https://github.com/danielbodart/talebrary
- Owner: danielbodart
- Created: 2024-05-05T14:43:18.000Z (about 2 years ago)
- Default Branch: master
- Last Pushed: 2026-07-03T14:42:13.000Z (21 days ago)
- Last Synced: 2026-07-03T16:19:29.516Z (21 days ago)
- Language: TypeScript
- Homepage: https://talebrary.com/
- Size: 35.9 MB
- Stars: 0
- Watchers: 1
- Forks: 0
- Open Issues: 0
-
Metadata Files:
- Readme: README.md
- Agents: AGENTS.md
Awesome Lists containing this project
README
# Talebrary
[**talebrary.com**](https://talebrary.com/) — an interactive fiction platform for discovering and playing over 2,000 tales in the browser, with AI-generated cover art, scene illustrations, and intelligent command suggestions.
## AI Pipelines
Every game in the catalogue gets unique AI-generated artwork, and players get real-time AI assistance during gameplay. This is powered by three distinct pipelines, each orchestrating multiple Cloudflare Workers AI models.
### Cover Art Generation
Each game's cover art is produced by a durable workflow that chains text and image models with a multi-strategy fallback:
**When the game has existing cover art** (from IFDB):
1. **Store original** — Fetch and persist the source image in R2
2. **Style transfer** — Pass the original through [FLUX.2 Klein](https://developers.cloudflare.com/workers-ai/models/flux-2-klein-9b/) (img2img) with a graphic-novel style prompt
3. **LLM-enhanced style transfer** (fallback) — If plain style transfer fails, use [Llama 3.3 70B](https://developers.cloudflare.com/workers-ai/models/llama-3.3-70b-instruct-fp8-fast/) to extract a visual scene description from the game's metadata, combine it with the style prompt, and retry FLUX.2 with the richer context
4. **Pure generation** (final fallback) — Generate a fresh image with [Leonardo Phoenix](https://developers.cloudflare.com/workers-ai/models/leonardo-phoenix-1.0/) using just the title
**When there's no existing art:**
1. **Scene extraction** — Llama 3.3 reads the game's title and description to produce an image prompt
2. **Image generation** — Leonardo Phoenix generates the cover art from that prompt
The pipeline always produces something — the fallback chain ensures no game goes without cover art.
### Scene Illustrations
During gameplay, the illustration workflow generates contextual artwork for the current scene:
1. **Prompt generation** — Llama 3.3 receives the full scene context: the story's title and description, the current scene, and optionally the previous scene. It generates an image prompt that's consistent with the narrative — if the previous scene was underground, the new illustration won't suddenly be outdoors unless the text says so.
2. **Image generation** — Leonardo Phoenix renders the scene. The model can be overridden per-request for experimentation.
The LLM returns a 404 status (in JSON) when a scene has no visual content worth illustrating — the workflow gracefully falls back to a stylised book cover rather than generating a meaningless image.
### Command Suggestions
The suggestions pipeline powers an interactive command UI during gameplay. Llama 3.3 analyses the current scene and returns a structured `SuggestionTree` — a mapping of verbs to applicable nouns extracted from the scene description:
```json
{
"people": true,
"tree": {
"examine": ["atrium", "library", "bookcases", "desk", "sandwich"],
"ask": ["librarian"],
"take": ["sandwich"],
"open": ["drawer"],
"east": [],
"inventory": []
}
}
```
The LLM works from a fixed command vocabulary (directions, object interactions, people commands, game controls) and only includes commands and nouns that are relevant to the current scene, sorted by usefulness.
### Models
| Model | Type | Used For |
|-------|------|----------|
| [Meta Llama 3.3 70B](https://developers.cloudflare.com/workers-ai/models/llama-3.3-70b-instruct-fp8-fast/) | Text (instruct) | Scene extraction, illustration prompts, command suggestions |
| [FLUX.2 Klein 9B](https://developers.cloudflare.com/workers-ai/models/flux-2-klein-9b/) | Image (img2img) | Style transfer on existing cover art — always uses multipart/form-data |
| [Leonardo Phoenix 1.0](https://developers.cloudflare.com/workers-ai/models/leonardo-phoenix-1.0/) | Image (text-to-image) | Cover art generation, scene illustrations |
## Caching Architecture
AI generation is expensive, so every generated asset is cached through multiple layers to ensure models are only ever called once per resource.
```
Browser cache (ETag / If-None-Match → 304)
└─ Cloudflare CDN (public, max-age=60, stale-while-revalidate=600)
└─ ETag middleware (automatic MD5-based ETags on all responses)
└─ Cache-Control middleware (default public caching policy)
└─ R2 write-through cache (BucketCachingHandler)
└─ Origin handler (AI workflow / IF Archive / etc.)
```
**R2 as a write-through cache** — `BucketCachingHandler` wraps the AI handlers for cover art, illustrations, suggestions, and story files. On first request, it calls the origin, stores the result in R2 (keyed by URL path + MD5 of query params), and returns it. Subsequent requests are served directly from R2 with full ETag support for conditional requests. A `?reload` parameter forces cache bypass and regeneration.
**HTTP caching middleware** — Every response flows through two decorators: `CacheControlHandler` stamps a default `public, max-age=60, stale-while-revalidate=600` policy, and `EtagHandler` computes strong ETags and handles `If-None-Match` → 304 short-circuiting. These work together with Cloudflare's CDN edge caching.
**Original cover art** gets `max-age=31536000` (1 year) — it's fetched once from the source and never changes. Generated assets get shorter TTLs but are effectively permanent in R2.
## Cloudflare Workers Platform
The production deployment runs entirely on Cloudflare's edge platform:
| Service | Purpose |
|---------|---------|
| **Workers** | Application runtime — server-rendered HTML, API endpoints, routing |
| **Workers AI** | All model inference (Llama 3.3, FLUX.2, Leonardo Phoenix) |
| **D1** | Game catalogue database (synced from IFDB) |
| **R2** | Object storage for runtime-written content — generated images and cached story files |
| **Workers Static Assets** | Build-time client assets (CSS/JS/HTML/images/wasm), bundled and versioned with each deploy |
| **Durable Objects** | IF Archive proxy — routes all ifarchive.org requests through a US-East data centre to avoid UK geo-blocking (HTTP 451) |
| **Workflows** | Durable orchestration of multi-step AI pipelines with step-level persistence |
### Portable Workflow Abstraction
The AI pipelines use a workflow abstraction that's decoupled from Cloudflare:
```typescript
type Workflow = (params: Params, step: Step) => Promise;
```
`Step.do(name, fn)` maps directly to Cloudflare's `WorkflowStep.do()` in production, giving durable execution with step-level persistence. In local dev and tests, `DirectRunner` executes workflows synchronously with `InMemoryStep`. The workflow code itself has no Cloudflare imports — it's just async functions with dependency injection.
## WebAssembly — Playing Text Adventures in the Browser
The in-browser player is powered by [**wasiglk**](https://github.com/bodar/wasiglk) ([JSR](https://jsr.io/@bodar/wasiglk)) — a companion library that compiles classic interactive fiction interpreters to WebAssembly. It supports 17 interpreters covering virtually every IF format ever created:
| Interpreter | Formats |
|-------------|---------|
| Fizmo | Z-machine v1-8 (.z1-.z8, .zblorb) |
| Glulxe, Git | Glulx (.ulx, .gblorb) |
| Hugo | Hugo (.hex) |
| TADS 2, TADS 3 | TADS (.gam, .t3) |
| Alan 2, Alan 3 | Alan (.acd, .a3c) |
| Scare | ADRIFT (.taf) |
| Agility | AGT (.agx) |
| AdvSys, Level 9, Magnetic, Scott, Plus, Taylor, JACL | Various legacy formats |
The interpreters are compiled from their original C sources using **Zig as a cross-compiler** targeting `wasm32-wasi`. They run in a **Web Worker** with a WASI shim providing the system interface. The key challenge is that these interpreters are synchronous C programs that block on `read()` for player input — this is solved using **JSPI** (JavaScript Promise Integration), which allows the WebAssembly execution to suspend at the `fd_read` WASI call and resume when the player's input arrives as a resolved Promise.
Communication between the interpreter and the browser uses the **RemGlk JSON protocol** — the WASM module reads JSON input events and writes JSON update objects describing window layout, text content, and input requests. Custom elements (``, ``, ``, ``) render the game UI on the main thread.
## Architecture
Two runtimes, one codebase:
- **Bun** for local development (`src/bun/app.ts`)
- **Cloudflare Workers** for production (`src/cloudflare/app.ts`)
Both share the same application logic via `Application.ts`, which wires everything together using dependency injection (`@bodar/yadic`). The only differences are the concrete implementations: SQLite vs D1, folder vs R2, direct workflow execution vs Cloudflare Workflows.
`Http` is the universal type: `(request: Request) => Promise`. Handlers are composed by decorating this function — no frameworks, no middleware stacks.
### The Catalogue
The catalogue uses a library metaphor:
- **Atrium** (`/`) — the entrance hall with search and links to each wing
- **Wings** (`/catalogue/{wing}`) — genre collections (Fantasy, Sci-Fi, Horror, etc.) and special collections (Top-Rated, Classics, Recent)
- **Aisles** (`/catalogue/{wing}/{aisle}`) — individual category pages listing games
Each level is a server-rendered HTML page with AI-generated scene illustrations.
## Project Structure
```
src/
ai/ AI abstraction (TalebraryAi interface + adapters)
workflows/ Portable workflow definitions (cover art, illustration)
prompts/ LLM and image prompt templates
storage/ Bucket abstraction (R2 / folder) + BucketCachingHandler
http/ Http type, URI parsing, ETag + Cache-Control middleware
catalogue/ Atrium, Wing, Aisle handlers
content/ Game detail, cover art, story, suggestions, illustrations
player/ Client-side custom elements for the IF player
templates/ Base template, slot processing, renderers
games/ Game finder queries
database/ Database abstraction (D1 / SQLite)
bun/ Local dev composition root
cloudflare/ Production composition root + Durable Object/Workflow entrypoints
events/ Event tracking
system/ Clock, timers, digest utilities
www/ Compiled client assets
db/ Database schema and sync tooling
test/ Tests (mirrors src/ structure)
```
## Development
```bash
./run start # Bun dev server with file watching
./run startw # Wrangler dev with remote bindings (real D1, R2, AI)
./run test # Run all tests
./run check # TypeScript type checking
./run build # Full build (check + compile + test)
```