Title: Yapoleon's Court — MidnightDev
URL: https://midnightdev.dev/work/yapoleonscourt
Description: An AI game the LLM can't game.
Generated from the live page on 2026-10-22. Do not edit by hand; run `pnpm twins`.

---

case study

# Yapoleon's Court

An AI game the LLM can't game.

[visit site](https://court.yapoleon.com/) · court.yapoleon.com

The authority boundary behind all of this work is stated once, in [the model cannot own the money](https://midnightdev.dev/writing/the-model-cannot-own-the-money.md). This page covers what is specific to Yapoleon's Court.

![Yapoleon's Court gameplay screen showing the AI opponent's in-voice reply beside a favor meter the model cannot write to.](https://midnightdev.dev/screenshots/yapoleonscourt.png)

01 the problem

A competitive game backed by an LLM the player is actively trying to game — running on a paid API, in front of minors — is a stack of production hazards. The model can be prompt-injected. It can be flattered into a high score. A bad actor can burn through your API bill. And the model can say things a family audience shouldn't hear.

02 the solution

Built the game so the model *can't* be gamed — structurally, not with hope. The Gemini call returns taste only (five bounded axes plus one in-voice line). The favor delta is derived server-side by a pure function the model can't see or reach. Prompt injection, sycophancy-gaming, and denial-of-wallet are handled at the architecture layer. A deterministic red-line pre-filter runs before any model call, so flagged input costs zero.

03 decisions & tradeoffs

### Model returns taste, server derives the score

Making the favor delta a field the LLM can output makes it a field the LLM can be talked into inflating. Removing the field entirely is stronger than any prompt hardening.

### Structured, low-temperature output only

Predictable schema across every turn, cheaper to reason about, easier to write invariant tests against. Voice comes from prompt, not temperature.

### Deterministic pre-filter before model call

Red-line content should never spend a token. Rules-based short-circuit is faster, cheaper, and auditable in a way an LLM classifier isn't.

### Cached degrade mode over hard failure under load

Concurrency damper serves an in-voice cached reaction with zero model calls when spend or load spikes. Still routes a neutral score through the same derive-favor function. Meter never breaks, character never breaks.

04 tech stack

### Google Gemini

Server-side proxy, low-temperature structured taste-only output

### React + TypeScript

Web app, share-card generator, favor meter UI

### Vercel serverless

API routes with atomic-slot concurrency damper (TOCTOU-safe)

### Supabase

Auth, standing and rivalry persistence across days

05 results

- Judge calibrated against 30 demands × weak/mid/strong: 96.7% strong, 72.2% mid, 0% weak; a fixed rhetorical mold wins 0% of off-axis days; 1,095 calls, 0 errors
- Denial-of-wallet bounded five ways: 3-turn daily cap, per-IP + per-user rate limits, atomic concurrency slot, cached degrade mode, Vercel spend hard-stop
- Content pre-filter short-circuits red-line input before any model call — zero cost on flagged requests
- Prompt injection handled structurally (player reply rides as data being judged, never in the system instruction)

Next step

## Want something like this?

Book a 30-minute call. I will tell you plainly whether I can help and what I would test first.

[Book a call](https://calendly.com/alex-midnightdev/30min?utm_source=midnightdev&utm_medium=site&utm_campaign=case-yapoleonscourt) [or email alex@midnightdev.dev](mailto:alex@midnightdev.dev)
