import InfoBox from '../components/InfoBox.astro';
import PullQuote from '../components/PullQuote.astro';

A native version of the game changes who controls an update. The web app can replace itself on reload, but an installed client may remain unchanged for months. Before the Godot client has its first release, the existing backend now accepts a completed offline game summary, exports the [English dictionary](/posts/the-repo-grows-up#a-borrowed-word-list-becomes-a-liability) as a compressed asset, and has a rule for how the mobile-facing contract may evolve.

## An install can stay old

An architecture grilling session tested three options: rely on everyone remembering which fields the client uses, add a version prefix immediately, or split the mobile surface into its own service. I chose a smaller rule. Existing request and response fields may only change additively, and a versioned route appears when there is a real breaking change to carry, not before. The API surface is still small enough that a second service or a permanent version label would add structure without moving any complexity out of the current application.

<PullQuote>The first old client exists the moment the second client ships.</PullQuote>

## One summary after the game

The native game is meant to keep playing without a network connection, so sending every move to the server would make the wrong part authoritative. It plays locally and submits one complete summary when the daily game ends. The server validates the shape and internal consistency of that summary, applies an address-based rate limit, and stores the accepted result. I wrote the boundary as a pure validation contract first, which let the agent cover malformed and contradictory submissions without needing a database or a running route in the test.

## The dictionary becomes a release artifact

Offline play also means word validation cannot depend on a request for every submission. An export script walks the English dictionary with keyset pagination, builds a compressed JSON asset, and leaves it ready to bundle into the Godot release. The first end-to-end run exposed two assumptions hidden by the tests: a database address that only resolves inside the hosting network, and a second database read for lexical data already available in a local source file. Surfacing the original connection error and reading that second source locally made the exporter usable from a development machine without changing what it produces.

<InfoBox title="Why keyset pagination">
  The export is roughly 370,000 entries. Asking for each next page after the last key keeps the
  work proportional to the rows returned. Repeatedly asking the database to skip an ever-growing
  offset makes the later pages more expensive than the early ones for no benefit.
</InfoBox>

## Versioning when it earns its place

The next step is to build the client against this contract and treat the exported dictionary as part of each native release. If a genuinely incompatible change arrives, the old and new routes can live side by side until the older installs disappear. For now, compatibility is a written constraint, not a directory name added in anticipation.