Why the browser plans the route and the object decides
On a Saturday the bakery plans 63 deliveries on up to four vans twice, once on the server and once in your browser, and publishes neither until one object has checked the plan against its own numbers. This is why the work is split that way, how to read the gap between the two plans, and what the plan does not promise.
The problem
The seeded Saturday has 63 stops and four vans. Dealing the stops out in the order the orders arrived sends each van across the city and back more often than it needs to. A good plan groups stops that are near each other and orders them so the van does not backtrack, and finding one is real computation: the number of ways to split 63 stops among four vans and order each van’s stops is far past what any program tries one by one.
What the lab shows, and what it cannot
The Route Lab, as read on 2026-11-06, is a made-up bakery in Seattle with 20 to 63 orders. It plans the same orders two ways, as they came in and as VROOM plans them in a Web Worker, and reports the difference: on its busy Saturday, 3 h 12 min of driving against 5 h 34 min. Its page says VROOM “promises a good plan fast, and not always the best one”, and that its travel times assume no traffic.
It compares a plan with no thought against one with a lot. It has no second planner of comparable quality, so it can show no gap between two good plans. It has no record of deliveries, so it cannot show what happens when a plan is wrong, or a stop is missing, or a van has already left. Its data are Seattle’s, from OSRM; the bakery’s are Salt Lake City’s, from openrouteservice. The bakery adds the server’s planner and the object that judges.
Two planners, neither exact
The server plans with the savings heuristic of Clarke and Wright (1964). Every stop starts as its own round trip from the bakery. Joining the end of one trip to the start of another saves d(i,0) + d(0,j) − d(i,j) minutes, where d is the drive time and 0 is the bakery. The heuristic scores every ordered pair, 63 × 62 = 3,906 of them for the Saturday, sorts by saving, and merges trips from the largest saving down while a van has room. It uses no clock and no random draw, so the same input gives the same plan. It is in crates/domain/src/dispatch/savings.rs.
VROOM, run in the browser as WebAssembly, builds a plan and then moves and swaps stops between routes until no change shortens the driving. It looks harder, so it should find a shorter plan more often than not. The page asks it to look at level 5 up to 40 stops and level 3 above that, and to stop after 5 seconds (apps/web/src/lib/dispatch.ts).
Neither is exact, and the gap on the dispatch screen has to be read with that in mind. The screen reports the server’s total less the browser’s, as a percentage of the browser’s. That is a gap between two heuristics. A gap of zero says they agree, not that either is best. A positive gap is a floor on what the server’s plan leaves unused, since the browser’s plan is a real plan and the best one can only be shorter still. A negative gap, where the server’s plan is shorter, is possible, and the screen says so in words. The gap does not say how far VROOM is from the best plan, which neither planner computes. ADR 0012 sets the bakery’s own line at 25 percent of total drive time on the seeded days, a revisit trigger, and the roadmap records no measured gap.
Neither is trusted: the object decides
The browser can send anything, so the browser’s plan is a request. The Dispatch object, one per sandbox and business day, checks it before it is stored and again before it is published, since stops come and go in between. Its rules have names a visitor can see in a refusal: vans, stop-unknown, stop-twice, over-capacity, route-ends, stop-missing and, since 2026-11-08, window. Every stop appears once, no van carries more than its capacity, every route starts and ends at the bakery, no stop is left out, and no van reaches a stop after its delivery window closes. A plan that sends its own total is refused with 400, since the object does not read it. The minutes are always worked out from the object’s own travel-time matrix, the same arithmetic for the server’s plan and the browser’s.
That is what makes the comparison fair. The page builds VROOM’s input from the same matrix, with every leg rounded up to whole minutes, so VROOM minimizes the figure the object will count. Since 2026-11-08 it also gives VROOM the times the object checks: the vans leave at 9:00 am, a stop may be served until two hours after its window opens, and each door takes 3 minutes. Until then the browser’s plan ignored time. A published plan is a RoutePublished event, and it is the only one that reaches the kitchen board and the orders. Once a van has left, the object asks the orders about it and refuses a new publish with 409 published.
Where the computation is free and where it is paid
- The browser’s search is free to the server. VROOM’s files are 919,927 B of WebAssembly, 343,650 B gzipped (ADR 0012, as of 2026-11-04), fetched at build, checked against a lock file and loaded on the first solve. The visitor pays the bytes once and the time to solve: 548 ms for the 63-stop day in one headless Chrome run on 2026-11-05 (the roadmap’s PR 9 note, not a committed test).
- The server’s plan is cheap by design. ADR 0012 estimated a few milliseconds of CPU for tens of stops. That has not been timed on its own. The Worker’s configured limit,
limits.cpu_msinworkers/api/wrangler.jsonc, is 300 ms, where Cloudflare’s paid default on 2026-11-06 was 30 seconds. The bakery set it low on purpose, as a bill guard. A VROOM search that took 548 ms in a browser on another machine is a figure to compare against 300 ms, not a test that it would not fit. - The travel times are paid once, offline. No routing service is called while a visitor plans. A build ran openrouteservice 9.10.0 on the bakery’s own machine over an OpenStreetMap extract with a replication time of 2026-11-05T00:58:30Z, and wrote a 64-by-64 matrix of 64 public places into
tools/route-data/out/. The committed files are the record. The roadmap says the build reran byte for byte on one machine and does not promise equal times on another machine or version.
A plan, not a promise
The times are free-flow drive times with no traffic. The totals count driving only; the time the last van is back adds 3 minutes at every door and any wait for a window to open. The map draws straight lines between stops, not roads. Delivery addresses are made up and map onto the 64 places by index, so two customers can share a drop point. A published plan says what the bakery would do on a road with nobody else on it.
What to try
On the dispatch screen, plan Saturday’s stops both ways, read the gap as above, and publish one. Then try the Route Lab and add or remove an order on its map and watch both of its plans re-solve. The summary on How it’s built says the same in short.
Sources
- Scheduling of Vehicles from a Central Depot to a Number of Delivery Points, G. Clarke and J. W. Wright, Operations Research, 1964: the savings heuristic. Cited from the bakery’s reading list in
docs/roadmap.md; the page itself was not read on 2026-11-06. - VROOM, the Vehicle Routing Open-source Optimization Machine, the VROOM project, read on 2026-11-06: a C++ solver under the BSD-2-Clause licence, tagged “Good solutions, fast”, which does not claim optimal ones.
- openrouteservice, the GIScience project on GitHub, GPL-3.0 and LGPL-3.0, read on 2026-11-06: a routing service over OpenStreetMap with directions, matrix and isochrone services, which the bakery runs on its own machine at build time.
- Workers limits, Cloudflare’s documentation, read on 2026-11-06: on the paid plan, 30 seconds of CPU a request by default, configurable up to 5 minutes with limits.cpu_ms.
- Durable Objects limits, Cloudflare’s documentation, read on 2026-11-06: a Durable Object follows the Workers limit of 30 seconds of CPU a request by default.
- ADR 0012, delivery dispatch, the bakery’s decision record, read on 2026-11-06: VROOM in the browser to plan, a savings heuristic in Rust to propose, the object to decide.
- Plan a bakery’s deliveries, the Route Lab on ihaveasegway.com, read on 2026-11-06: VROOM in your browser against orders taken as they came, on made-up Seattle data.
Back to the routing principle on How it’s built, or to the lab.