What this is

A third-person open-world city you can walk around, drive through, and run deliveries in — as a single self-contained HTML file. No npm, no Vite, no bundler, no build step. You double-click it and it runs.

There is a sunny coastal city on an 8×7 road grid: sidewalks, lane markings, working traffic lights, storefronts with signage and awnings, alleys, palms, streetlights, parked cars, a beach, a boardwalk and an ocean with an animated shader. Four drivable vehicles. Twenty-six ambient cars and forty-six pedestrians that route the grid and get out of your way. A wall-clock day/night cycle, a courier delivery loop, a minimap, Web Audio, and local-first saves with JSON export and import.

Every texture in it was drawn in code at runtime. There is not a single image file.

The constraint is the exercise

The familiar version of this prompt targets a Vite project: npm, a multi-file source tree, an asset pipeline. This one had to survive as one file you can email to someone.

That is not a simplification. It is a different engineering problem. With no asset pipeline there is nowhere to put a texture, so every surface — asphalt, lane markings, storefront glass, sand, palm bark, car paint — has to be generated into a canvas at runtime and uploaded. The art direction is constrained by what you can draw procedurally in a few milliseconds, which is a real design pressure and not a cosmetic one.

The whole world builds in 51.5 ms: textures 39.5, city geometry 6.7, actors 1.9, the rest materials and sky.

The gate failed, and the gate was wrong

The builder reported its own numbers, which is exactly the report you should not trust. So a separate acceptance gate re-measured all seven checks independently.

It came back 10 out of 11. The failure was on the most important check: is the simulation paced by the wall clock, or by the frame rate?

The app was fine. It exposes its clock as __gameDebug.tod. The gate had gone looking for __gameState.timeOfDay, found nothing, and correctly failed rather than skipping — but it was failing a correct implementation because the assertion was wrong.

This was the seventh time in a single session that a reported failure turned out to be the test rather than the code. It is the reason the first rule is: check the assertion before you fix the implementation.

Corrected, the check passes cleanly. Day-fraction advance measured at 1.851×10-3 per second unthrottled versus 1.856×10-3 under 8× CPU throttle — a ratio of 1.002. The world keeps the same time regardless of how fast it is drawing.

The best thing in the builder's report was a caveat

When it ran its own pacing test, CPU throttling barely moved the frame rate. A weaker report would have quietly published the clean-looking number.

Instead it proved the throttle was actually applied — a JavaScript benchmark went from 2.7 ms to 56 ms, a 20.7× slowdown — and then explained why the frame rate hadn't moved: under software rasterisation the raster threads dominate, and they are not covered by that throttle. So it re-ran the test with a 320 ms main-thread hog instead, collapsed the frame rate 8.95×, and got 1.0000 simulated milliseconds per real millisecond, drift 0.000%.

That is the difference between reporting a number and taking a measurement.

Seven bugs, all found by running it

None of these are visible by reading the source:

  • Top-level THREE.* access that ran before the dynamic import resolved.
  • A GLSL shader reading .z off a vec2.
  • A sky dome placed outside the camera's far plane, so it simply wasn't there.
  • Toggling light.visible forcing a shader recompile every frame.
  • Stale player speed carried over after exiting a vehicle.
  • Twenty metres of position drift across a save and restore.
  • Vehicles wedging permanently against geometry — fixed with displacement-based stall detection that folds the forward vector back to the distance actually covered, plus a yaw-escape that points the nose away after two seconds. Velocity-based detection does not catch this, because a wedged car is still trying to move.