Problem
A Papers-Please-like — but built for a one-week game jam, in the browser, good enough to feel like “real” work on a desk, fast enough that the rules engine doesn’t block drag physics.
What I built
Three-tier hybrid architecture.
- Presentation — JavaScript, HTML5 Canvas, Web Audio. 11 modules, ~6,400 lines. Document physics (drag, bounds, collision with the inspection zone, random pickup rotation for feel), CSS 3D perspective on the desk, parallax snow at three depths, dynamic shadows that track document position.
- Data — JSON configs for cases, rules, documents, dialogue, verdicts. Rule complexity escalates by day. The game is fully re-balanceable without touching code.
- Logic — C compiled to WebAssembly via Emscripten. Game state, discrepancy detection, rule processing, verdict application, ending calculation. JS and C talk over JSON strings through
cwrap— no binary protocol, no marshalling edge cases, easy to debug.
Five in-game days of decreasing shift length (10 → 6 min) as rules get stricter. Performance breakpoints trigger supervisor memos. Multiple endings based on how the player balances quota, accuracy, and mercy.
Interesting bits
- Why split the game at this boundary. Anything that touches rendering or input stays in JS where it’s cheap to iterate. Anything that has to be provably correct (rule evaluation, verdict, ending logic) lives in C where it’s easier to reason about as a pure function of state and input. The JSON boundary makes the split visible.
- Game feel from small details. Random ±3° pickup rotation on drag, paper-rustle synthesized from filtered noise on mouse-down, shadow offset and blur that scale with document position — each one is a three-line change, and all of them together are what separate “functional prototype” from “feels good.”
- Verdict tones are the same oscillator at different frequencies —
600 Hzfor NICE,200 Hzfor NAUGHTY, exponential gain decay over 300ms. The first version used stock click sounds and felt wrong immediately. - Day-length and rule escalation are data, not code. Balancing happens by editing JSON between test runs. After the jam, that’s also what makes the game extendable into a longer version without a rewrite.