The game is a static web app. There’s no server and no backend, and it has to work on a desktop browser and on iPad Safari with a finger or a mouse. Claude Code wrote nearly all of it in cloud build sessions, working from a build brief and a set of rules in the repo. How that was run is on how the project runs. This page is about what came out.

The stack

TypeScript and Vite, with scenes drawn in SVG and no UI framework. The scenes are turn-based, so a game framework would add weight and nothing else. SVG scales cleanly and gives me simple tap targets. TypeScript lets the engine, the content schema and the tests share one vocabulary of facts, so a mismatch fails the build and doesn’t turn up at the playtest.

Lessons are written in YAML and compiled to JSON at build time, with schema validation. Vitest runs the checks and Playwright drives a browser at iPad and desktop sizes. The site is hosted on Cloudflare Pages on the free tier, built from a private repo on every push. The allowed dependencies are Vite, TypeScript, Vitest, Playwright, yaml and zod, plus Node’s type definitions, and anything else needs a logged decision first.

Content is data

The engine reports facts. It knows how to draw a street, move Pip, run the traffic and work out a fixed list of facts about what the child did: where they stood, which way they looked, whether anything was approaching, whether the path was straight. It never decides what any of that means.

The content files decide the meaning: which tier of feedback, which rule, which source code and which words. They hold a list of outcome rules, tried in order at the moment the child commits to crossing, and the first match wins. A combination of facts that matches no rule is a build error. So a new module is mostly new data, and a rule that rewards the wrong thing is something I can read in a file.

The checks

Everything in this list fails the build.

  • Every outcome is covered. For every scene, the test enumerates every combination of spot, stop position, look, listen, decision, traffic, path and stage, and checks that each matches exactly one outcome. It counted 15,693 combinations when it first ran and found no problems. A combination the module lists as permitted can never land on a correction.
  • Every rule has a source, and every correction names a hazard or a source text containing a prohibition.
  • Tolerance can’t go below its floor, and only appears in the scenes where its conditions hold.
  • No banned words. Copy is scanned for “ready”, “passed”, “licence” and similar.
  • No timers. A scan of the code for timeouts and clock reads, with each allowed exception written down with its reason.
  • Left-hand traffic. On every two-way street the near lane comes from the child’s right.
  • The picture matches the fact. The kerb view draws a vehicle only if the engine’s own ray test says it’s visible.
  • No harm in the simulation. For every crossing the game accepts, a test plays the walk and checks no vehicle enters the space Pip occupies. A correction produces no movement at all.

A few more tests keep the code honest about its own structure. The screens can’t import the evaluator, so they can’t decide a tier. The engine can’t import the copy or the rules. Only the storage module touches the browser’s storage. Nothing reads the clock to decide anything a child does.

The browser tests use a scripted child and adult that work out the correct play from the scene content, with animations sped up 30 times. The cloud container has no WebKit, so the iPad-sized runs use Chromium at the same sizes. They aren’t a Safari result. My iPad is the Safari check.

How the view is worked out

Each street scene lists what stands in it: a van, a hedge, a skip. The engine treats each as a rectangle with a height and tests the line of sight from the child’s eye, 1.1 metres up. The kerb view then draws only what that test says is visible, so the picture and the fact can’t disagree. Moving traffic steers out around a van or skip standing in the road, and one function does that for the animation, the kerb view and the harm test, so a vehicle is never drawn driving through a van.

There is one scene engine per strand, pedestrian now and cycling later. The pedestrian one is designed on paper for its hardest modules, P1 to P7 and the scooter module, and built in stages. The first build only included what P1 uses, and a later module’s features get added as general code when its content arrives.

Audio

Every line a child hears is made ahead of time by Piper, an offline text-to-speech tool, from the copy file. A recorded clip dropped into an overrides folder replaces a generated one. The sound effects are synthesised from code and not downloaded: a car idling, a car and a bike passing, a bike freewheeling and birdsong. They’re own work, so there’s no licence to track, and they’re a cue to listen. Recognising real traffic is something the walk teaches. The review rule that stops a changed line playing unheard is covered on the hazard log page, under H27.

Storage and offline use

Progress is one JSON document in the browser’s local storage, behind a single storage module with a version number. It holds a picture avatar, a stage, the weekly plan days, and which scenes were played and how. It holds no names, ages, free text or device identifiers.

Safari deletes a site’s stored data after seven days of Safari use with no visit to that site. A web app added to the iPad’s Home Screen has its own counter, and WebKit treats losing that data as a bug. So the first run on an iPad guides the parent through adding the app to the Home Screen, with pictures, and asks the browser to keep the storage. A backup file can be exported and imported in the grown-up area. A service worker caches the app, the content and the audio for offline use, and a new version only takes over at the next parent start, never in the middle of a session. Whether the Home Screen app really keeps its own storage is something I still need to check on the iPad.

Art

All the artwork is flat SVG drawn from one colour palette: Pip, the avatars, the vehicles, the street frontages and the zebra crossing as Know Your Traffic Signs shows it. Every asset is recorded with its source and licence. The picture at the top of this project was composed from those same files.