DrivEngine

10 SEP 20269 MIN READ

The car brakes too hard, and it was not the brakes

Hard braking looked like a pedal problem. It was mostly caused by late junction decisions, one real controller bug and a test that blamed bends for waits they did not cause.

drivenginedriver-aidebugging

The car brakes too hard, and it was not the brakes

Part seven of a series. The earlier parts: what DrivEngine is, the two layers, junctions, the three tiers of a driver, and the two parts on right of way, one and two.

One of the rules the test suite checks is that braking stays inside a comfortable rate. It is written as a plain sentence: braking stays inside the comfortable deceleration. The thinking behind it is simple. A car that has to brake hard made a mistake earlier. It planned something it could not do gently, and the emergency brake is what it paid to get out.

Across a sweep of sixteen seeds, that rule was breaking about a hundred and ten thousand times.

This article is what happened next, and it is really three stories stacked on top of each other. The symptom. The fix that hid it. And the metric that had been misreporting a different thing entirely for weeks.

Story one: the fix that made everything worse

The obvious response to "cars brake too hard" is to stop them braking that hard. So: cap the brake pedal at the comfortable rate and see what happens.

What happened is that the count went to zero. Perfect score. And the collision numbers went up by two thirds, deadlock rings appeared where there had been none, and the count of cars chasing a target speed they could no longer reach tripled.

That last number is the one that explains it. There is another rule in the suite: a target speed is one the car can still reach. When a driver decides to stop at a line and the line is already too close to stop at comfortably, that plan is unreachable, and the driver has exactly one way out, which is to brake hard.

So the hard braking was not a defect in the brakes. It was the price of plans that arrived too late, and capping the pedal did not remove the late plans. It removed the payment. The cars then simply drove through the things they had decided to stop for.

The cap did not create the defect. It stopped hiding it. Which is useful information, just not in the direction I was hoping for.

Then I ran two more variants to find out where the late plans were coming from, and this is where it got useful. Cap everything, and the count is zero. Lift the cap only when the reason for slowing is a car in front, and about a fifth of the braking comes back. Lift it for the junction reasons as well, yielding and blocked junctions, and nearly all of it comes back.

So roughly four fifths of all hard braking in the town came from junction decisions, and about a fifth from following other cars.

That is a measurement I could not have obtained by reading the code, and it was only possible because of the design choice in part four: every speed decision carries the reason that produced it, so you can split a count by cause. The reason field turned a single useless number into a diagnosis.

For completeness I also halved the speed controller's gains and its authority, since surely a gentler controller brakes more gently. It moved the count by one percent, and it put twenty six deadlock rings back. The controller was not the cause. It did produce an interesting side result, though: it cut overlaps by nine percent and negative gaps by forty, for two percent of throughput, so if a slower, safer traffic profile is ever wanted, that dial exists. It is a trade, not a fix.

Story two: the real control defect, which was a different thing

Everything above is about the planner. But there was a real bug in the controller, and it is a good one, because it reads as correct in a code review.

The speed pedal was the sum of two terms. A controller reading the speed error, and a feedforward term meant to supply the planned rate of change directly.

The intent is standard and sound: the feedforward carries the part of the job you can predict, the controller cleans up what is left. Plan open loop, correct the residual.

The implementation did not do that. The feedforward divided the same speed error by the timestep, while the controller read that same error. Two terms, one input. Not a plan plus a correction, the same correction applied twice under two names.

The arithmetic is brutal. Half a pedal of feedforward plus half a pedal of controller output is a full pedal, and a full pedal is maximum braking. The town printed that number over and over, and every one of those was the invariant firing.

The fix is to make the two terms different quantities. The decision tier already knows what acceleration it intends to use, because that is how it decided its target speed in the first place. That intended acceleration maps onto pedal travel directly, because the vehicle knows what its own pedals are worth. The controller is then given a much smaller job: correct the difference between the speed the plan predicted for this moment and the speed the car actually has.

A plan the controller does not have to rediscover. A correction that is only ever a correction.

What it cost, said plainly

Fixing the controller made the traffic measurably worse.

Trips fell by around ten percent. Throughput fell by eight. Unreachable targets roughly doubled. Overlaps and shared crossings grew across every busy sweep. The dense town lost nearly a quarter of its trips. Steering weave got worse, although drift did not.

This was accepted deliberately, with a note attached to the decision that I still think is the most valuable sentence in the notes: do not recover this throughput by restoring duplicate feedback, and do not recover it by tuning the gains.

The old numbers were produced by a controller that was doing the wrong arithmetic. They were not better traffic, they were a better score on a rigged test. Once the controller became correct, every number it produced became an honest description of how good the planning actually was, and most of the work that followed, including the whole junction admission story in part six, was measured against those honest baselines.

Keeping the broken controller would have been keeping a debt that every future measurement would have paid interest on.

Three traps that each cost a full measurement pass

Nothing above is exotic. These three are, and each one cost a sweep to find.

Feeding the throttle forward at the vehicle's maximum. If the planner asks for acceleration and the pedal maps that at the kinematic limit, cars leave junctions far harder than the rule that planned the corner expected. Cornering violations went from thirty four to over two thousand. The fix is that a comfortable acceleration is a separate number from a possible one. A car can accelerate at its limit. It should not, unless it has a reason.

Planning a bend with all of the available grip. The rule that picks a speed for a curve used to assume the full lateral limit. So a car that actually held its target cornered at exactly the limit, with nothing left for the steering controller to correct with. This only ever passed because the old speed loop could never reach its targets. Fixing the loop exposed it immediately. Now the curve rule plans on a fraction of the grip and keeps the rest as margin, and that violation count is zero on every seed.

That one changed how I read every later result. A latent design flaw was being masked by a bug, and fixing the bug revealed it. If you fix a real defect and something unrelated gets worse, consider that the unrelated thing was always broken and was being protected by the defect.

A following margin that was solving a problem it did not have. An extra safety margin on the braking half of the car following rule cost between six and twelve percent of throughput at one metre, and gridlocked a seed at half a metre. The correct feedforward did the same job with none of the cost, because the defect was never in the following law to begin with.

Story three: the metric that was lying

The last layer is the one I would most like to have skipped, and the one I learned most from.

The suite reported thousands of violations of a rule about cars standing still without a reason, blamed on curves. Cars, apparently, stopping dead on bends. On the busy sweeps it was between two and three thousand per run.

There were no cars stopped on bends.

What actually happened: a car stops for a real reason, yielding to another car or waiting for a junction. That is legitimate waiting, and the rule allows it. Then, on the first tick where the car is finally free to go, the winning speed reason flips to the curve ahead of it, because that is now the thing limiting its speed. The check looked at the reason in that tick and attributed the whole preceding wait to it.

The metric was measuring the last label attached to a queue of legitimate waiting.

Fixing the check, so that it only counts a continuous unexplained standstill, took the count to zero in every run in the suite. Not one car moved differently. Every other measurement stayed exactly where it was.

For months, a few thousand phantom violations per run sat in the reports, and every real signal in that metric was buried under them. Worse, there had been an attempt to fix the fictional defect: a minimum speed for curves, on the theory that cars were coming to a halt on tight bends. That floor was actively harmful, because a speed floor can demand more grip than the curve allows, and it has since been removed. Invalid curvature is now rejected when the connector is built, which is part three's argument again: catch it in the bake, not at runtime.

There was a second one of these. A rule about whether a driver sees the car in front was filed as a scan distance problem for a long time. It was not. The path scan always carries every stretch to the end of a lane, so a car directly ahead is never missed. Almost every violation was a car travelling faster than its own target speed, which is the speed loop defect from story two again. Fixing the loop took that count from two hundred and sixty four to fifty six.

A bad metric is dangerous because it points to a specific wrong fix. In this case, the number improved when the driving became worse.

Three causes behind one symptom

The hard-braking count combined three different problems:

  1. A real defect elsewhere. Junction decisions arriving too late to act on gently, paid for with the emergency brake.
  2. A local defect that reads as correct. The same error applied twice under two names.
  3. A metric misnaming a legitimate behaviour, plus a second one blaming the wrong subsystem entirely.

Each direct fix made something else worse. Capping the pedal scored perfectly on the metric and tripled the crashes. Tuning the gains moved nothing and broke deadlock recovery. Adding a following margin cost throughput even though the following rule was not at fault.

I had to split the count by cause, fix the controller even though the headline numbers got worse, and then check whether each metric matched its name. Four fifths of the original count came from junction decisions that arrived too late for gentle braking.

The effects show up thousands of ticks later and vary between seeds, so watching one replay was not enough.


The next article is about that last idea on its own: how you measure traffic at all, why a single run proves nothing, and what a town looks like at the density where it stops flowing.