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.
A shot filters surviving chains
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.json…z.json, _ for digits), rows
[normalized, title, year, votes] sorted for binary-search prefix lookup. One
shard per query.
Prefix lookup over static shards
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: firstChar → manifest.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.