# A Prompt Adventure

A procedural, family-friendly prompt-jailbreaking arcade built around externally served `openai/gpt-oss-120b`.

This reference release contains the FastAPI application, browser UI, game data, tests, PostgreSQL leaderboard, and container configuration. It contains no language-model or image-model weights, checkpoints, tokenizers, caches, generated artwork, database state, or deployment secrets.

## The game

- Six player-controlled difficulties: Easy, Medium, Hard, Expert, Max, and Insane.
- Each round has an Answer challenge and a Tool Call challenge. Players may repeat a difficulty for unlimited points, advance after completing both objective types, or submit their score and reset.
- Procedural old-school fantasy frames bind the setting, guardian, guarded thing, and objective before the author model writes the short scene and character voice.
- Easy has a discoverable engineered character weakness. Medium removes that weakness. Hard removes Easy/Medium's deliberately credulous anti-security framing. Expert, Max, and Insane progressively raise reasoning and refusal/secrecy strictness.
- Active session state is encrypted and authenticated in the browser's IndexedDB. The server stores only completed arcade leaderboard entries in PostgreSQL; there are no user profiles.
- The in-browser Spellbook applies eight composable text transformations without sending its text anywhere.
- Optional scene art can be supplied by a separate image service. The game works normally with artwork disabled.

The all-ages policy is enforced in the system prompts by design. This release does not add input or output filtering.

## Requirements

- Docker Engine with the Compose plugin
- A separately running `openai/gpt-oss-120b` service with the specific API behavior documented in [docs/MODELS.md](docs/MODELS.md)
- Enough inference capacity for the model's 131,072-token context and concurrent players
- Optional: a separately managed Bonsai Image service implementing [docs/ART_SERVICE.md](docs/ART_SERVICE.md)

The application does not download, start, or manage model weights.

## Quick start

1. Copy the environment template:

   ```sh
   cp .env.example .env
   ```

2. Set `MODEL_BASE_URL` to the root of your externally served GPT-OSS endpoint. Keep `MODEL_ID=openai/gpt-oss-120b` unless the server advertises the same model under another identifier.

3. Generate independent secrets and paste them into `.env`:

   ```sh
   python3 -c "import secrets; print(secrets.token_urlsafe(32))"
   python3 -c "import base64, os; print(base64.urlsafe_b64encode(os.urandom(32)).decode())"
   ```

   Use the first value for `POSTGRES_PASSWORD` and the second for `RUN_STATE_KEY`.

4. Leave `ART_ENABLED=false` for the first run. Then build and start:

   ```sh
   docker compose up --build -d
   docker compose ps
   curl -fsS http://localhost:8080/api/health
   ```

5. Open `http://localhost:8080`. Change `APP_PORT` and `PUBLIC_BASE_URL` in `.env` if another address is required.

To stop the application without deleting its leaderboard:

```sh
docker compose down
```

`docker compose down --volumes` deletes the leaderboard, logs, diagnostics, and generated-art volume. It is intentionally not part of the normal shutdown procedure.

## Configuration

The required variables are:

- `MODEL_BASE_URL`: external model server root; the app appends `/v1/models`, `/v1/chat/completions`, and `/v1/responses`.
- `POSTGRES_PASSWORD`: local database password used by Compose.
- `RUN_STATE_KEY`: URL-safe base64 encoding of exactly 32 random bytes (a Fernet key).

TLS verification is enabled by default. To connect to an HTTPS endpoint whose certificate cannot be verified, both `MODEL_VERIFY_TLS=false` and `ALLOW_INSECURE_MODEL_TLS=true` are required. This weakens transport security and should be an explicit operator decision.

`CAMPAIGN_ID` partitions leaderboard results. Change it when starting a fresh public competition. Changing `RUN_STATE_KEY` invalidates saved browser runs.

Artwork is disabled by default. Enabling it requires `ART_ENABLED=true`, `ART_SERVICE_URL`, and an external image service that mounts the Docker volume named by `ART_VOLUME_NAME`.

## Diagnostics and retention

The application is instrumented as a long-running service. JSON logs rotate at 10 MiB by default, retain a 14-day window, and use a 100 MiB ceiling. Browser actions and browser-side failures are posted to the backend logging endpoint with correlation identifiers.

The latest completed scenario generation is atomically stored at `/app/runtime/diagnostics/latest-scenario.json` in the app's diagnostics volume. It includes private game material and full prompts for debugging, is excluded from Git, and is never served by an application route. The in-game prompt inspector similarly exposes secrets and permanently disqualifies that run from the leaderboard.

## Development and tests

```sh
docker build -t prompt-adventure:test .
docker run --rm --user 0 -e PYTHONDONTWRITEBYTECODE=1 \
  -v "$PWD:/src:ro" -w /src prompt-adventure:test \
  sh -lc "pip install --no-cache-dir -r requirements-dev.txt && python -m pytest -q -p no:cacheprovider"
```

JavaScript Spellbook tests require a recent Node.js runtime:

```sh
node --test tests/spellbook.test.js
```

Prompt construction lives in `app/prompts.py`, `app/scenario.py`, and `app/difficulty.py`. Procedural content lives in `app/fantasy_content.py`. See [AGENTS.md](AGENTS.md) before asking a coding agent to modify or deploy the project.

## License

The original code, text, browser UI, documentation, tests, and other materials in this reference snapshot are dedicated to the public domain under CC0 1.0 Universal. The dedication was applied on 2026-08-14. See [LICENSE](LICENSE) for the complete terms. Third-party components, model names, and trademarks remain subject to their own terms.
