oakcity
A tilt-shift city builder on a custom wgpu engine. Draw roads as splines, zone land, watch procedurally generated buildings grow along the frontage, and follow individual citizens as they commute, shop and go home.

cargo run --release -- --demo
What's in it
Custom engine. No game framework β just wgpu, winit and egui. One
forward HDR pass with a single-cascade shadow map, a blended water surface, and
a tilt-shift post stack. Everything is vertex-coloured; there is not a single
texture in the project.
Tilt-shift. Two effects stacked, which is what actually sells the toybox look. A depth-based circle of confusion blurs anything far from the focus distance, and on top of it a photographic tilt band keeps one horizontal strip of the frame sharp and softens everything above and below it regardless of depth. Both feed a two-pass separable blur at half resolution, composited back with a saturation and contrast push and a vignette. The camera runs a 25Β° lens, because the telephoto compression is the other half of the illusion. Every parameter is a slider and a hot-reloadable config value.
Splines everywhere. Roads, avenues, tram lines and railways are all Catmull-Rom splines. Laying one flattens a corridor of terrain under it, generates a ribbon mesh with kerbs, pavements and lane markings, and folds the result into a node/edge graph β splitting existing ways at crossings so junctions are real junctions. Pavements and markings are suppressed where another carriageway runs over them.
Generated terrain. Five map presets (assets/config/maps.ron), each a
different generator: a river valley with a meandering carved channel, a coastal
bay, plains with a lake, a mountain pass with ridged noise either side of a
buildable valley, and an archipelago. The water line is chosen as a quantile of
the finished heightfield, so water_fraction: 0.34 really does give you a third
of a map of water.
Water. A single blended surface whose vertices carry the depth of the bed beneath them, so shallows go turquoise, deep water goes dark and the shoreline gets foam β with no second depth pass. Three summed directional waves animate the surface and flatten out as they reach the shore.
Neighbourhoods, not scatter. Growth walks fixed-width plots down both sides of every street and fills them, so buildings line up shoulder to shoulder on a common building line with driveways joining them to the pavement, instead of being sprinkled at random offsets. Each archetype carries a palette of wall and roof colours that the generator picks from per building, which is the difference between a street and a row of identical boxes. Junction approaches get zebra crossings and street trees alternate with lamps down the verge.
Generative buildings. A building spec is a recipe, not a model: a massing (block, L, H, setback tower, shed, basilica, slab, courtyard), a roof (flat, parapet, gable, hip, shed, sawtooth, dome, mansard), a window style, and a list of recognisable features. Combined with a per-building seed, no two fire stations are identical and every one of them still reads as a fire station:
| Building | What makes it legible |
|---|---|
| Fire station | Red brick, three roller doors, hose-drying tower, white trim |
| Police HQ | Blue-grey stone, colonnaded entrance, radio mast, roof dish, shield |
| Hospital | White H-plan, red cross on the facade, rooftop helipad |
| School | Brick wings around a courtyard, flagpole, playground |
| Church | Nave and aisles, steeple with a cross |
| City hall | Dome, portico, flag |
| Power plant | Two hyperboloid cooling towers, banded stack |
| Water tower | Tank on four legs |
| Station | Clock tower, arched train shed, platforms |
| Factory | Sawtooth north-light roof, silos, banded chimneys |
Windows are dark glass by day and warm light after dark β the same emissive vertex flag lights up street lamps.
Citizens. Everyone has a home, possibly a job, four needs (rest, food, fun, money) and a daily routine. They path with A* over the transport graph, walk on the pavement or drive in the right-hand lane, and are cached per origin/ destination pair so thousands of them cost almost nothing. Trams and trains shuttle along their lines. The clock drives the sun, the sky, the window glow and everyone's schedule.
Traffic. Cars are not on rails. Each one watches for the vehicle ahead in its own lane through a spatial hash and eases off rather than driving through it, with real acceleration and braking. Junctions β any graph node where three or more edges meet β are mutual-exclusion locks: the first car to arrive claims it, everyone else decelerates to a stop line and waits their turn, and claims time out so a stalled vehicle can never deadlock the city. The result is queues on the approach and cars filtering through one at a time.
The developer window
Render tuning, lighting, the map picker, hot-reload status and counters live in
a separate OS window (F1, the π Dev button, or --debug) rather than on
top of the city. It is a second winit window with its own wgpu surface and egui
instance on the same device. The game window carries only the HUD a player
should see, so shipping is simply a matter of never opening the dev window.
The dev loop
Anything that does not need rustc lives in assets/ and reloads while the
game is running:
| Edit | Effect |
|---|---|
assets/shaders/*.wgsl | Pipelines rebuild. A syntax error prints the message, keeps the last good pipelines, and shows it in the HUD β the game never dies. |
assets/config/theme.ron | Terrain, road and water colours; the whole tilt-shift rig (including the tilt band) and lighting. Meshes rebuild in place. |
assets/config/buildings.ron | Building archetypes. Add an entry and it appears in the build menu; change a colour or feature and every existing building of that type regenerates. |
assets/config/maps.ron | Map presets. The current map regenerates. |
assets/config/sim.ron | Need drain rates, wages, speeds, tax. Running citizens pick the new numbers up immediately. |
The workspace is split into five crates (engine, world, gen, sim,
game) so a change to the simulation does not recompile the renderer, and
[profile.dev] optimises dependencies once while keeping our own code cheap to
build. A touch-and-rebuild of the game crate is a couple of seconds.
cargo run -- --demo # starter town, windowed
cargo run -- --map 3 --hour 19 # Sable Pass at dusk
cargo run -- --demo --screenshot shot.png # render a PNG and exit
cargo test # configs parse, every archetype
# generates, every map is habitable
--screenshot renders entirely offscreen with a fixed timestep, so it works
with the window hidden and the same --frames always simulates the same amount
of city life. It is the fastest way to check a visual change.
| Flag | Meaning |
|---|---|
--demo | Seed a starter town: avenues, a street grid, a tram line, a railway, zoning and the core civic buildings |
--debug | Open the developer window at startup |
--map <n> | Map preset to load, 0-based |
--hour <0..24> | Time of day |
--dist <metres> | Camera distance |
--screenshot <path> | Render a PNG and exit |
--frames <n> | Frames to simulate before capturing |
Controls
| Input | Action |
|---|---|
WASD / arrows | Pan (hold shift to move faster) |
Middle-drag, Q / E | Orbit |
| Scroll | Zoom |
| Left click | Use the current tool |
| Right-drag | Pan |
| Enter / right click | Finish the way you are drawing |
| Esc | Cancel |
| Space | Pause |
1β4 | Street, avenue, tram, railway |
B / I | Bulldoze / inspect |
F1 | Open/close the developer window |
Draw a road by clicking control points and pressing Enter. Paint a zone next to it and buildings will start appearing along the frontage; the mix and density depend on the zone and how big the city has grown. Place a fire station and watch the coverage bar fill.
Layout
crates/engine wgpu device, camera, meshes, shadow + HDR + tilt-shift passes,
asset paths, the file watcher
crates/world heightfield, map generators, splines, the transport graph,
ribbon meshing, zoning
crates/gen building specs and the procedural mesh generator
crates/sim citizens, needs, A* and the route cache, road traffic and
junction locking, transit, the clock
crates/game tools, growth, the egui HUD, the frame loop
assets/shaders WGSL, stitched together with a tiny `#include` resolver
assets/config RON: theme, maps, buildings, simulation tuning
Known limits
- Roads cannot cross deep water; there are no bridges or tunnels yet.
- Junctions are first-come-first-served locks rather than modelled priority rules or signals, so there is no concept of a main road having right of way.
- One shadow cascade, sized to the camera's focus, so distant shadows fade out.
- Growth picks lots along road frontage at random rather than filling blocks in a considered order.
Version control
This repository is tracked with Oak, not git.
oak status
oak diff --print
oak log --oneline