← Writing · Celebrity Golf

Offline validation

Three tiers of static JSON — legality, plausibility, existence — and a course audit that caught a wrong par.

Free-shot needs “was that film a legal stroke?” instantly, in the client. Answer ships in the bundle.

Hole generation emits validChains: ace check, shortest co-star chains (enumerated, capped, reduced to ≤10 per route by signature), plus one crew-typed route when ≤ co-star length. Hole needs ≥2 routes or it’s rejected. Shortest-path closure: nine holes, 71 KB in puzzle.ts, typical hole under 8 KB, chain data ~4.6 KB. Client flattens chains into a candidate set; named film either advances surviving chains or doesn’t.

FIG. 03

A shot filters surviving chains

Diagram of named-film shots advancing matching validChains and pruning the rest, with the shortest-path closure as the correctness set
A named film advances matching chains and prunes the rest. Correctness is the shortest-path closure — orders of magnitude smaller than the typeahead index.

Three tiers of static JSON, each precomputed offline, each loaded at the narrowest scope its question needs:

Legality — validChains, ~4.6 KB/hole, in the bundle. Shortest-path closure. Only tier consulted to score a shot.

Plausibility — per-hole move bundles, 48–273 KB, lazy-loaded. Tee’s filmography plus from[filmId] (cast-sharing neighbors of every reachable film) → graph-aware suggestions. Failed load fails visibly — an earlier fallback to the global dictionary hid broken loads as bad suggestions.

Existence — letter shards, ~348 KB for ~6,000 titles. Global dictionary, split by first normalized letter (a.jsonz.json, _ for digits), rows [normalized, title, year, votes] sorted for binary-search prefix lookup. One shard per query.

FIG. 04

Prefix lookup over static shards

Diagram of prefix lookup over letter-sharded static JSON title dictionaries, with a growth path to budget-split B-plus-tree cuts
One shard fetch, then binary search. Rows are sorted; the cut points are the index. Bottom band: same structure with byte-budget cuts when letters get too big.

Upgrade path: one globally sorted row list, cut wherever a chunk exceeds a byte budget, cut points as strings in the manifest (m1 covering ["m", "mid"), …) — B+-tree of depth one. Client delta: firstCharmanifest.floor. Current size: 348 KB total, mean shard ~13 KB (~4 KB gzipped); fetch cost is RTT. Full-graph dictionary (~45.9k titles, ~2.6 MB) is when letter shards blow open.

Today move bundles filter suggestions; validChains alone decides legality. → Address space on What Year Is It?

Same closure audits the course. Recomputing unrestricted shortest paths against the shipped board verified eight of nine pars — and caught hole 8 claiming par 3 where the board says 2. graph_us ⊂ full graph, so distances can only lengthen; a 2 here means the stated par was never the true shortest. Left uncaught: a player naming the real two-stroke path would have a correct solution scored as a whiff (not in validChains). Cause: build_hole took the first typed crew route (composer, three strokes) and never checked the cinematographer route — The Boss → Julio Macat → Miracle on 34th Street, two strokes. Generator now selects the shortest typed route; hole 8 ships as par 2; course total par 20 → 19.