Skip to content
v0.17.0 npm

Dressing a roadside

The racetrack demo is handed a road and works out what should stand beside it, and where. This is the ruler it measures in, the rules it has to satisfy, and the loop it runs until they all come out true at once.

generated live · plan view
centreline road dressing

The idea

A roadside is not scenery. It is instructions.

On a fast road, the trees, signs, barriers and grandstands beside the track are not decoration. They tell you a corner is coming, how hard it is, and where to brake. Take them away and the same road becomes unreadable at speed.

So the demo is given two things. A spec — rules with numbers in them: how much stuff, how far off the road, what must stay visible from the cockpit, what has to stand before a corner. And a vocabulary — the objects it may use, described as sizes and habits.

Then it dresses a road it has never seen, and every rule has to come out true.

Step one

Measure everything in road-widths.

A rule that says “a barrier 4 metres from the edge” is a rule about one road. Said in half-widths — call it W, half the distance across the tarmac — it is a rule about every road. That single choice is what makes the whole spec portable: point it at a wider track, or a longer one, and nothing has to be restated.

keep this empty |t| < 1W  ·  h < 1.2W 1 W half the road verge near mid far 8% 29% 35% 13% 1.0 1.5 2.5 5 13 W −1.0 −1.5 −2.5 eye, h = 0.3W tacross, + to the right hup, from the tarmac salong, from the start line all three in W
Cross-section. Every object is filed under three numbers: how far along the lap, how far across, how high. The bands are the shares Z-3 asks for, and the finished lap has to land inside every one of them.

How the graph is built

The graph makes the road and the ruler. The rules are code that reads them.

pcg-ts is a node graph: little boxes that each do one thing to a pile of points, wired nose to tail. This demo's graph is about twenty nodes, and it has exactly two jobs — turn the given centreline into a road surface, and write the s / t / h / W coordinate system onto every point of it so that anything downstream can speak in road-widths.

THE SEAM THE RULER THE OUTPUTS dataInput your centreline pathResample 900 evenly spaced writeCurveFrame tangent, curvature setAttribute halfWidth  ·  W setAttribute stationW  ·  s setAttribute across  ·  right of travel setAttribute up  ·  surface normal sweepProfile ribbon, 2W wide pathResample one row per side mergePoints stations to dress ↑ the one thing pcg-ts does not make the road arrives as data, so you can point it at your own track ↓ from here on it is plain TypeScript reading the cooked lap back
The graph, simplified. Every downstream rule reads stationW, across and up off the points rather than recomputing them — one definition of “right of travel”, so the road and the things beside it cannot disagree.

Worth saying plainly: the graph does not place the scenery. The placement rules are ordinary TypeScript that cooks the graph, reads the lap back, and works out a list of things to stand beside it. The graph's own roadside row — two evenly spaced lines of points — is a deliberate placeholder, kept only so the seam between “where the road is” and “what stands beside it” is already cut.

What the demo reads

A vocabulary of boxes, and the habits of each box.

The demo reads one data file, demos/racetrack/vocabulary.json. Think of it as a parts list: each object described as a handful of axis-aligned boxes, plus how that kind of object behaves — how far off the road it stands, how high, how often, which side, whether it prefers corners or straights.

229objects
2,200boxes
362poses
5shape classes
286 Wlap length
ANY OBJECT, AS THE VOCABULARY HOLDS IT Four boxes: a size and a rough shape, enough to build something that fits them. ONE ENTRY, AND ITS HABITS size 0.92 × 0.86 × 1.81 W stands at t = 1.66 W  (1.45 … 3.51) height h = 0.91 W how often 18 times a lap side bias 72% right of travel clumping gap CV 3.17 likes cornersstraight 0.53   easy 1.69 medium 0.99   tight 1.58 “likes corners” is a multiplier: this one turns up three times as often on a bend as on a straight.
One entry. The boxes are never drawn as art. They are read for size — will this fit? — and the habits are read to decide where this kind of thing belongs.

Size

Will it fit?

  • Across, along and tall, in W
  • Enough to test it against the driving corridor, the overhead ceiling and whatever stands next to it

Habits

Where does it belong?

  • Typical lateral and height, with a spread
  • Which side it favours, how clumped it is, how much it prefers a bend to a straight

Each entry also carries a set of poses, and they are the reason the dressing does not look stamped. The vocabulary carries no rotation, so the demo chooses one — but every pose keeps its own boxes, and 362 poses give 361 distinct sets. The variety is in the shapes.

The important distinction

Some rules you can roll dice for. Some you have to go back and fix.

A rule like “35% of things sit in the mid band” is a distribution. You can satisfy it while placing, by picking each object with the right odds. A rule like “the driver must be able to see the next stretch of road” is a threshold. No amount of careful dice-rolling guarantees it, because it is a promise about the result, not a shape of the results.

Thresholds get a repair pass: place first, then measure, then move or drop the offenders. Getting this backwards is what makes a rule set look self-contradictory when it is only being applied in the wrong order.

D-1distribution

How much stuff there is, overall.

0.95 per W of lap  ·  0.71–1.54
Z-3distribution

How it splits across the bands, from verge to horizon.

15 / 8 / 29 / 35 / 13 / 0.5 %
D-4threshold

No long empty stretches. Most of the lap has something within reach.

85% within 2W  ·  gaps ≤ 25W
Z-1threshold

Nothing may stand in the volume the car drives through.

|t| < 1W, h < 1.2W kept clear
L-1threshold

From the cockpit, the road ahead must stay visible. Blockers move out or go.

next 12W, eye at h = 0.3W
L-2threshold

Every corner gets something tall before its entry, so you can see it coming.

one marker per corner
L-3threshold

Evenly spaced marks through the braking zone — a ruler you read at speed.

on one line, outside the corner
L-4threshold

Every tenth of the lap gets a landmark you recognise, so you know where you are.

≥ 1 repeated asset per tenth
L-5threshold

No false edges: a line of objects must never read as a road edge that isn't there.

checked on |t|, then broken up
L-6threshold

Some of the lap runs under cover — tunnels and bridges, placed as runs, not as single objects.

10–25% of the lap enclosed

The pipeline

The order is the design.

Nine stages, and they only work in this sequence. The cull sits sixth rather than fourth — everything the legibility rules add has to face the visibility test too, and a corner marker is exactly the tall thing near the racing line that the test exists to catch.

0ReserveSet aside the objects tall enough to be corner markers, before anything else can spend them.
1StationsDecide how many things there are and roughly where along the lap they go.
2AssetsGive each station an object, drawn from the habits stored against it.
3CorridorPush anything overhanging the driving volume outwards — by its near face, not its centre.
4LanguageAdd the corner markers and the braking rulers. This is the part that makes the road readable.
5LandmarksMake sure every tenth of the lap has something recognisable in it.
6SightlineThe cull. Anything blocking the view ahead is moved out or removed — including things stages 4 and 5 just placed.
7CoverageClose the gaps the cull just opened, by moving the most redundant object into them.
8Band mixRe-balance the bands, which the cull also disturbed. Objects move between bands; the count never changes.

Every repair reports how many times it had to fire. A repair that never runs and a generator that never needed one produce identical green tests — so the count is part of the output, not just the pass.

Why it runs more than once

Fixing one rule breaks another. So it goes round until nothing moves.

Z-1 corridor L-1 cull L-6 cover D-4 gaps L-4 landmarks L-5 edges Z-3 mix round again until a whole round moves nothing at most 12  ·  typically 3
The repair tail. Pushing an object out of the driving corridor opens a gap; closing the gap disturbs the band mix; re-balancing the mix can put something back in the corridor. Each pass is minimal — no single move is removable — and the loop stops when a full round changes nothing.

Two failures worth remembering, both found by looking at the screen rather than at the tests. A repair that keeps handing work to the next repair never settles — trace what each round actually moved before blaming the rules. And a repair that cannot recognise its own no-op will fire forever on a rounding error: a height of 1.2 that comes back as 1.1999999999999997 looks like a thing that still needs fixing.

What you see when you run it

Two layers, and the whole argument is between them.

the vocabulary's own poses what the rules generated the road the centreline you gave it the car
The key. The look the demo ships with, and it is monochrome on purpose. Both layers are one flat grey and the reference is the lighter one; each is drawn at half alpha with depth writing off, so it stacks — the strips are the same fill one, two and three boxes deep. From above it is not monochrome: the overhead pass draws the generated dressing in flat red and the reference in grey, because a plan has no accumulation to protect and can spend its one channel on telling the two apart.

Both layers are on screen at once, drawn by the same renderer at the same half alpha. The lighter grey drops the vocabulary's own poses straight onto the lap, placed by nothing but their three numbers. The darker one is what the rules built from nothing. One value against another is the whole difference between them.

Below full alpha a flat fill writes no depth, so a box in front does not hide the box behind it — they add. Brightness is how much structure stands along the line of sight. That is also why nothing here is coloured by what it is: accumulated alpha and hue are the same channel, and six colours summing through each other make a muddy one that means neither. The look spends the channel on density instead. The overhead pass, which is a line drawing rather than an accumulation, is free to spend its own on the thing the chase view cannot say — which of the two populations a box belongs to.

They stand side by side on purpose. Statistics can say the generated set matches the vocabulary on every single figure, and it can still look wrong at eye level. The only test that catches that is a person looking at it.

There is also a density slider, because “how much stuff” is taste as much as measurement — the rule gives a range, not a number.

Next

Where to go from here

The demo itself is at demos/racetrack, and demos/road dresses a road with none of these placement rules in it. They are two independent demos that share a spline and a naming scheme, not one page with the rules switched off, so read road as the other answer rather than as a diff. Architecture is how the library underneath is put together, and the corpus gallery is every graph in the repository, cooked and shot.

← back to the landing page