# Ressort Top Down World

> Build a top-down (overhead) world on the machin-ressort engine — an irregular city or map generated offline into a tile grid, flood-field routing, lane-holding traffic, and the handling model that makes a top-down vehicle feel like a vehicle. Use when the camera looks straight down rather than from the side, when a map needs generating rather than authoring, or when anything in the world has to find its way somewhere.

- Skill: `javimosch/ressort-top-down-world` (Agent Skill)
- Install (CLI): `npx skillmds@latest add javimosch/ressort-top-down-world`
- Raw SKILL.md: https://api.skillmd.com/api/skills/javimosch/ressort-top-down-world/raw
- Safety review: pending
- Works with: Claude Code, Claude.ai, OpenAI Codex
- Category: AI & ML
- Author: javimosch (https://skillmd.com/u/javimosch)
- Updated: 2026-09-17
- Page: https://skillmd.com/skills/javimosch/ressort-top-down-world

---


# The world seen from above

A side-scroller's world is a heightfield and a parallax stack. A top-down
world is a **plan**: a grid of cells, most of them solid, with the playable
space cut through them. Almost everything below follows from that one
difference, and most of it was learned twice before it was written down.

The engine parts are `engine/25_grid.src` (grid, flood routing, sight lines,
file format) and `engine/65_bake.src` (the full-circle sprite bake).

## Rule 1 — generate the plan OFFLINE, into a file

The first version of our city was a *function*: which cell you stood on was
arithmetic on a fixed block grid. That is elegant, costs nothing to store, and
produces a town where every block is the same square and every junction is the
same junction — a street plan a player has seen in full after ten seconds.

Generate it once, write it out, load it back:

```
./bin/game gen --seed 7 --out city.jsonl     # a separate program
./bin/game --city city.jsonl                 # play it
```

The payoff is not file size, it is that the plan becomes **data you can look
at**: diffed between seeds, hand-edited, regenerated, replaced wholesale, and
re-skinned without touching the code that draws it. `grid_encode`/`grid_decode`
do it as one header line and one run-length row per line — 168² cells is 14 kB.
Keep a fallback that generates at startup when handed no file, or the game has
a bootstrapping problem the first time someone clones it.

## Rule 2 — irregular means SUBDIVISION, not jitter

Do not build a grid and then perturb it; cut the map up instead. Take the whole
rectangle, put a street across it somewhere that is *not* the middle, and do
the same to both halves until the pieces are block-sized.

Because a cut only ever spans the piece it divides, streets **do not line up
across the map**. That single property is what gives you T-junctions, streets
that run for three blocks and stop, and a block twice the size of its
neighbour — none of which you have to ask for.

Then dress each leaf: a pavement ring, buildings in lots divided by a one-cell
alley, a notch out of a corner, and every so often a park or a yard instead of
buildings. Two dozen lines, and it is the difference between a plan and a grid.

Assert the irregularity, or it will quietly regress to a grid: walk several
scanlines, collect the gaps between streets, and require more than a couple of
distinct values.

## Rule 3 — one integer per cell, and passability is a BITMASK

Store the cell's kind *and* its decoration in one integer — `TL_BLDG + tone`
rather than two parallel arrays. Then the engine's `Grid` is a view over the
same array rather than a copy, and the file format falls out for free.

Order the values so that **everything from the first solid kind upward is
solid**. Passability is then a bitmask over cell values, and one map serves
every kind of mover without a second copy:

```
MASK_DRIVE = 1        // roads only
MASK_WALK  = 7        // roads, pavements, parks
```

**The trap this creates, which you will hit:** once a cell carries a tone,
`cell == TL_BLDG()` matches only tone zero. One place in our generator still
compared a raw cell to a kind and five sixths of the kerbs silently stopped
being generated — the pavement count fell 4% and nothing else complained. Ask
for the *kind* through an accessor everywhere, and assert both halves: that
buildings do carry tones, and that a cell's kind never leaks one.

## Rule 4 — routing is a FLOOD, not a heuristic

Anything that has to get somewhere floods outward from the **destination**,
breadth-first, once; every reachable cell gets its distance, and everything
heading there reads the cell under it and walks downhill.

```
f := flow_build(city_grid(c), tx, ty, MASK_DRIVE())
ax, ay, ok := flow_aim(f, city_grid(c), x, y, TILE(), 5)   // 5 cells ahead
```

We wrote the other thing twice — *travel along the street you are on until you
are level with the target, then turn*. On a regular grid it is optimal and
free. On an irregular plan it is a guess, and it fails by pressing into a dead
end it cannot see round, forever. It also fails **invisibly**: a stopped
vehicle looks like a stopped vehicle, and two rounds of screenshots found
nothing. One `--trace` line a second found it immediately.

One flood is about a frame of drawing and is **shared by everything with the
same destination** — which is what makes it affordable for a crowd. Rebuild
only when the destination changes cell; police chasing a player need one
rebuild per cell the player crosses, not per tick.

Aim several cells downhill rather than at the next one, or the mover twitches
instead of steering. Off the network entirely (shunted onto a kerb),
`flow_aim` heads for the nearest cell that is on it — without that, anything
pushed off the road is stuck for good.

## Rule 5 — traffic holds a lane it has to MEASURE

With irregular streets there is no block size to take a modulo of, so a lane
centre cannot be computed — it has to be found. Scan out along one axis for the
nearest road cell, widen to the whole contiguous run, take its middle, and
offset to one side so oncoming traffic passes instead of colliding.

`city_road_x` originally returned the block's *own* carriageway. On the far
pavement the nearest road is the **next** block's, so everything self-driving
was steered through the building between them. Nearest, always — and assert it:
the middle of a street must itself be a street.

The same measurement tells you which way a street runs (compare the run length
in x against y), which is what a mover needs to decide whether it may travel
along the road it is standing on.

## Rule 6 — a top-down vehicle goes where it POINTS

Three numbers, and all three were wrong in our first cut.

**Grip.** Velocity leans toward the nose at a rate, rather than snapping to it:
`v += (fwd·(v·fwd) - v) · clamp(GRIP·dt)`. Snap it and the car is on rails —
no drift, no handbrake turn, every corner identical. Omit it and it is a
spaceship. This one blend is most of what "feels like driving" means.

**Steering must fade at BOTH ends.** It has to build from a standstill, or a
parked car pirouettes and is a tank; and it has to fall away with speed, or the
fast vehicles are unsteerable at exactly the speed you most want to steer them.

**Drag is for coasting only.** A linear drag term makes the real top speed
`power/drag`, approached exponentially — see `ressort-side-scroll-world`, where
the same trap cost a saloon two thirds of its rated speed.

Braking acts against **travel**, not against the nose, or braking mid-slide
accelerates you sideways. Move one axis at a time against the map so a glancing
hit slides along a wall instead of stopping dead.

## Rule 7 — the crowd needs separating, and the pedestrians need a cooldown

Once the streets are busy enough to look alive, two things start looking
broken.

**Vehicles interpenetrate.** A circle test is enough — a box test on a rotated
sprite wants the separating-axis theorem and nobody can tell at this size.
Push apart by mass ratio so the bus shoves and the sports car gets shoved, and
scale the damage of a shunt *well* below a wall impact: at full rate a truck
clipping you at 30 m/s removes half a car, and every mission vehicle dies in
traffic.

**Pedestrians spin.** Ours turned 90° on every blocked tick — 5400°/s, which
reads as a figure drilling into the pavement. Turn at most a few times a
second, toward a direction that is actually free (try left, then right, then
back), and stop re-aiming a fleeing one when the player is standing on top of
it: "away from you" flips every tick at zero distance, and a figure that
reverses at 60 Hz reads as two figures.

## Rule 8 — density is per YARD, not per magic number

Population as a fraction of the map: `cars = len(road_cells)/42`,
`people = len(pavement_cells)/13`. A city twice the size then needs no
retuning, and a screen shows the same number of things wherever you are. Ours
was a constant, and tripling the map turned a busy street into an empty one
with no code change to blame.

## What to assert without a window

- every open cell can reach every other (an unreachable street is an
  unfinishable objective)
- following the gradient from the far corner **arrives**
- an aim point is on the network, and far enough ahead to steer at
- the middle of a street is a street
- no two cells of the same block have the same size gap on every scanline
- spawns land on ground the thing spawned can actually occupy
- a written plan reads back cell for cell
- a blocked pedestrian turns a handful of times a second, not sixty

Then render it and look, because numbers can be right while the render is
wrong. The lane markings were absent for a week: the code asked whether a
cell's centre *was* the centre line, which is never true on a road of even
width.

