A junction is a decision, not a crossroad
Four roads meeting at one point do not say which turns are allowed. The junction bake builds those movements, rejects turns a car cannot follow and reports what disappeared.
A junction is a decision, not a crossroad
Part three of a series. Part one is what DrivEngine actually is, part two is the road you edit and the lanes cars drive.
Draw four roads meeting at a point. Now answer a simple question: what can a car do there?
You cannot answer it from the picture. You do not know whether the left turn is allowed. You do not know whether a car in the right hand lane may go straight or must turn. You do not know whether the northbound approach can reach all three of the other arms or only two. The shape does not contain the answer, and no amount of looking at the shape harder will produce it.
In DrivEngine, the movement list is the junction. Its geometry is built afterwards.
From arms to movements
A junction in the editor layer is a point with arms. Each arm is a road end: a position, a direction, and a cross section of lanes. Building a junction means producing the list of movements, where a movement is "from this lane of this arm, to that lane of that arm", and then producing one connector curve for each.
The starting point is the dumbest possible rule: connect every incoming lane to every outgoing lane. For a four arm junction with two lanes each way that is a large number of connectors, and most of them are wrong. A U turn from an arm back into itself is excluded from the start, but nearly everything else survives the naive rule, including movements that cross three other movements and movements that bend back on themselves.
So the real work is subtraction. Start with everything, then remove what cannot be true.
Remove what a car cannot steer
A car cannot turn tighter than a circle set by its wheelbase and its maximum steering angle. For an ordinary car that limit is a little under four metres of radius. The bake measures the curvature of each candidate connector along its whole length, takes the tightest point, and throws the connector away if that is tighter than the smallest turn the junction is willing to offer.
The number used is deliberately above the physical limit rather than equal to it. A car sitting exactly at its steering lock has nothing left to correct with. If the path itself demands full lock, then any drift, any overshoot, any disturbance is unrecoverable, and the car will simply leave its lane. So the junction refuses turns that a car could only just barely make.
This single filter removes a surprising amount of nonsense, and it removes it once, at bake time, instead of leaving runtime failures that look like AI bugs.
Remove what bends backwards
A connector whose exit direction points back the way it came is not a turn, it is a mistake in the geometry. Comparing the start tangent with the end tangent catches those cheaply, before anyone has to think about curvature.
Remove what crosses too much
Two connectors inside one junction can cross each other. Sometimes that is correct and normal: a left turn crosses the oncoming through movement at every unsignalled intersection in the world. Sometimes it is a sign that the movement should not exist at all.
For junctions where crossing connectors are not acceptable, the bake finds every crossing pair, then repeatedly removes the connector involved in the most crossings until none are left. It is a greedy rule and it is not optimal, but it is stable and it is easy to reason about, which for a tool that an author has to predict matters more than optimality.
Then add and remove by hand
After the automatic rules, the author gets the final word. A movement can be banned, and a movement that the filters removed can be forced back in. This is the escape hatch, and it is important that it exists, because the rules above are heuristics and every real road network has a junction that breaks them.
Why generation needs three attempts, not one
Procedurally generated towns produce junctions that no human would draw. Two roads meeting at a fifteen degree angle. Three roads where two of them are nearly parallel. A hairpin where a road doubles back.
When the strict rule set runs on one of those, it can legitimately remove every single movement, and a junction with no movements is a hole in the road network. Cars route into it and stop, or cannot route through the area at all.
So generation does not run one rule set. It runs a sequence, from strict to permissive, and takes the first one that produces anything at all:
- The normal rules.
- The same rules with the "bends backwards" limit lifted, because a genuine hairpin does bend backwards and still has to carry traffic.
- The same again with the turn radius limit dropped to the true physical minimum, as a last resort for a junction that would otherwise be a dead end.
The list stops there, and the bottom of it is still a real limit rather than a surrender. If even the last attempt produces no movement, generation does not fall back to connecting everything to everything. It fails, and the layout is rejected. A network that cannot be generated is an annoyance. A network that bakes connectors no car can steer through is a defect that will surface somewhere else, hours later, looking like something else entirely.
Earlier versions ended with a fallback that connected everything. Hairpins in generated towns reached that fallback and kept turns with a radius of about one metre. Cars could not follow them and drifted out of their lanes. It looked like a driver bug, but the bad path had been created three layers earlier. The generator now rejects the layout instead.
That gap between where a defect shows and where it lives is the reason so much of this system is built to check itself at bake time.
Junction size decides which turns survive
Every junction has a size, which is how far from the junction point its arms stop. The roads are trimmed back by that distance, and the space in between is where the connectors live.
It is the number that ruins the most towns, and both directions of getting it wrong are bad.
Too small, and the connectors have no room. A left turn that has to be completed in four metres is tighter than the turn radius filter allows, so the filter removes it. The author sees a junction that silently lost its left turns, and nothing about the picture explains why.
Too large, and the junction eats the roads on either side. Two junctions closer together than the sum of their sizes leave no road between them at all.
There is a guard against the second case: a road refuses to be trimmed past a fraction of its own length, so a badly sized junction produces a short road instead of a vanished one. The guard stops the catastrophe. It does not make the result good.
Junction size is computed from the width and angles of the roads that meet there. Generated layouts do not use one global value, and each junction preset stores its own.
Priority, signs and signals attach to movements
Once movements exist as objects, the rest of junction control has somewhere to live.
An arm can carry a rank, which is how "this is the main road, that is the side road" is expressed. The rank travels onto the connectors that leave that arm, so a driver deciding whether to yield is reading a property of the path it is on, not looking up a table about junctions.
Signs and signals work the same way. A signal group is a set of movements that go green together. A give way sign belongs to the movements it governs. None of these are properties of the junction as a whole, because none of them behave that way in reality: one arm of a real intersection can be a priority road while another has a stop sign.
Because they attach to movements, and movements already know which other movements they conflict with, generating plausible control for a junction is mostly a graph problem rather than a geometry problem. That is a later article.
You cannot see it, so ask it
The habit this whole design pushes you toward is checking rather than looking.
A junction can tell you which movements it actually has after a bake, and more usefully, which pairs of arms have no connector joining them at all. That second list is the important one. A missing movement is invisible in a picture. You do not notice the left turn that is not there. You notice it two hours later when cars are queueing strangely at a junction three streets away, because the route they wanted no longer exists.
The same instinct runs through the whole authoring layer. Validation reports things like a road that crosses a junction without meeting it, a junction with only one road attached, a road that got entirely swallowed by the junctions at its ends, and a signal plan that leaves one arm permanently red. Each of these is a specific mistake with a specific name, rather than a general "something is wrong".
A road network may be generated from a seed, stamped from a preset or assembled by a script. None of those callers can notice that a junction looks odd in the viewport. They depend on the same reports that the editor shows to a person.
Making movements explicit also gave signs, signals and priority rules a clear place to attach. The expensive geometry checks happen once during the bake. Most importantly, the bake reports what it removed. A silent missing turn otherwise appears much later as a strange route or queue.
The next article moves from the road to the cars on it: how a driver is split into three tiers, and the one decision that made the whole thing debuggable.
Four roads meeting at a point do not tell a driver which turns are allowed. That information has to be stored separately.
A junction therefore stores permitted movements. The connector geometry is built from that list.
Building that list starts with the dumbest possible rule, connect every incoming lane to every outgoing lane, and then subtracts:
- Remove what a car cannot steer. The curvature limit sits above the physical minimum on purpose, because a car at full steering lock has nothing left to correct with.
- Remove connectors that bend backwards.
- Remove movements that cross too many others, where crossing is not acceptable.
- Then let the author ban a movement, or force back one the filters took.
Generated towns produce junctions no human would draw, so generation runs the rules three times, strict to permissive, and takes the first that yields anything. If even the last attempt yields nothing, the layout is rejected. It does not fall back to connecting everything, because that once kept turns of about a metre radius. Cars could not follow them and drifted out of their lanes. It looked like a driver bug. It was a junction bug three layers away.
Junction size, the distance where its arms stop, ruins more towns than anything else. Too small and the turn filter silently deletes the left turns. Too large and the junction eats the roads beside it. Size is computed from the roads that meet, not chosen once for the whole town.
A missing turn is hard to spot in a picture. The bake reports every movement it removed, with the reason.
This is the short version. The full article has the reasoning, the numbers, and the parts that went wrong.