# Iteration 004 — No-API NPC dialog (offline-first hardening)

## Why
Player reported seeing raw provider 401 dumps in NPC chat ("Error code: 401 ... token_invalidated"). Codex Plus and Anthropic OAuth both hit usage caps mid-session, so any free-form path was leaking provider errors directly into the conversation log. Goal: every NPC interaction must work for any visitor with NO API and NO auth.

## What shipped
- **Free-form chat is now opt-in** via `VITE_PLAYGROUND_LLM_CHAT=1` env flag. Default Playground experience runs entirely on the canned response picker (already complete from earlier iterations).
- **Provider error detection** in `/api/playground-npc`: server now scans assistant content for leaking 401 / token_invalidated / usage_limit / `{"error":...}` patterns and swaps in a clean in-character fallback before the response leaves the route.
- **Client never surfaces raw errors**: `playground-dialog.tsx` catches all failures and rotates through 3 in-character fallback lines instead of dumping JSON or HTTP codes.
- **"Scripted mode" pill** added to the dialog header when LLM is disabled or any fallback has fired, so players know the world is intentionally offline-first.
- **Conversation log preserves persona**: fallback lines are NPC-specific (`*Athena pauses*`, `*Iris cocks their head*`, `*Apollo sighs*`) so the offline state still feels intentional.

## Files touched
- `src/screens/playground/components/playground-dialog.tsx`
- `src/routes/api/playground-npc.ts`

## Build status
- `pnpm build` clean ✅

## Why this is the right baseline for the hackathon
- A first-time visitor on `/playground` no longer needs Hermes Agent running, no API key, no Codex auth.
- Tutorial Q1–Q5 already advance via canned dialog choices (verified in earlier iterations).
- If a visitor *does* have a healthy local Hermes gateway and sets `VITE_PLAYGROUND_LLM_CHAT=1`, free-form chat lights up automatically and gracefully degrades to scripted mode on any provider failure.

## Commit
- Iteration committed locally as **`feat(playground): no-API offline-first NPC dialog (iter 004)`**
- Branch: `feat/agent-view-port-from-controlsuite` (local-only, not pushed)

## Provider context (root cause of the original bug)
- Codex Plus tier hit usage cap mid-session ("You've hit your usage limit").
- Anthropic OAuth route was already throwing `token_invalidated` from the gateway before that.
- The gateway was returning **HTTP 200** with the upstream error embedded *inside* the assistant message content, which is why `openaiChat()` happily returned the error text as the NPC reply.
- Iteration 004's `looksLikeProviderError()` filter on the server route is what stops that from leaking again, even if the gateway's auth flips.

## Next-up
- (Optional) Surface a small "Live agent dialog: off / on" toggle in side panel debug area when `VITE_PLAYGROUND_LLM_CHAT=1`.
- (Optional) Cache last 5 conversations per NPC in `playerProfile`.
- (Optional) Reaction emojis on choice fire.
- Decide on free-tier hosted model fallback (Kimi free / Nous Portal / OpenRouter free) for users who want live LLM but don't run Hermes locally.
