The planner produced a route that was valid under its graph rules and unreasonable for a rider. The case connected a point in Buhangin to Sampaguita Street, but the recommended path began with a walk of almost five kilometres before the transit leg started.
The origin was longitude 125.62562, latitude 7.11925. The historical incident record described a 22.2-kilometre itinerary: about 4.7 kilometres of initial access walking, a 17-kilometre bus ride, and about 5.2 kilometres of walking across both ends. The current profile script preserves the query as buhangin-southbound-regression, and planner-image-regression.test.ts keeps the same coordinates. That current test checks access and transfer limits, but it does not assert the historical 22.2-kilometre output. The distances on this page are therefore preserved incident measurements, not a claim about today's exact result.
The long access leg was the failure, not a rendering detail. The graph had a path; the product had no rule that asked whether the first walk was reasonable.
The assumption behind the score
The working assumption was that a lowest-cost path through the graph would also be a good recommendation. Walking and riding were represented in the same search score, and a faster long-distance bus could beat a slower local option even when reaching that bus required a very long access walk.
The graph was not lying about connectivity. It had found a sequence of edges from the origin projection to the destination projection. The mistake was allowing graph feasibility to stand in for passenger acceptability. A route can be connected, directed, and internally consistent while still asking a person to walk farther than the ride is worth.
The previous trip surface made the same concern visible in a different form. Commit 3dfa11b compared already-generated Trip objects using walking bands, ride count, first-bus availability, and exact walking distances. Its comparison entered the result as a scalar burden before the other tie-breakers:
const burdenDifference = walkingBurden(first, preference) - walkingBurden(second, preference)if (burdenDifference !== 0) return burdenDifferenceThat historical code ranked a set of trips after candidate generation. It did not stop an upstream graph search from producing a very long access path, and its preference bands are not the current contract. The incident exposed the need for a hard feasibility boundary before ranking. A soft preference score was insufficient. The full historical function is in rank-trips.ts at 3dfa11b.
What changed
The current planner applies access limits to complete candidates. Each access end may be at most 1,500 metres, and the two endpoint walks together may be at most 2,000 metres. A candidate must also contain one or two ride groups, cannot reboard the same service after a transfer, and cannot use a transfer walk above 400 metres.
The limit is expressed as a small policy function:
export function accessWithinCaps( originAccessMm: number, destinationAccessMm: number): boolean { return ( originAccessMm <= ACCESS_END_CAP_MM && destinationAccessMm <= ACCESS_END_CAP_MM && originAccessMm + destinationAccessMm <= ACCESS_TOTAL_CAP_MM )}Source: planner-runtime-policy.ts. Candidate selection applies the same condition to the projected access and egress arcs. The search still generates candidates; selection removes those that do not satisfy the rider policy.
The regression suite checks the boundary directly:
void test("accepts 1499 and 1500 metres but rejects 1501 metres at one end", () => { assert.equal(accessWithinCaps(1_499_000, 0), true) assert.equal(accessWithinCaps(1_500_000, 0), true) assert.equal(accessWithinCaps(1_501_000, 0), false)})void test("accepts a 2000 metre total but rejects a larger total", () => { assert.equal(accessWithinCaps(1_000_000, 1_000_000), true) assert.equal(accessWithinCaps(1_000_001, 1_000_000), false)})Source: planner-select.test.ts. Another image regression keeps pins 50, 200, 800, and 1,499 metres from a known jeepney line and checks that returned trips stay within the endpoint and transfer limits. The tests encode the boundary without pretending that a straight-line distance is a pedestrian route.
What remains imperfect
The cap fixes the class of error exposed by the incident. It does not calculate a safe walking route. Access is measured from a query point to projected service geometry, so a 1,499-metre geometric distance can be longer on streets. The 400-metre intermodal rule has the same limitation: it prevents arbitrary long-distance connections, but it does not prove that every road between a jeepney node and an official bus stop can be crossed.
The current browser path also plans with static topology and an empty liveBusArrivals list. Live timing does not rescue a bad access projection in this path. If the compiled route geometry, stop identity, or explicit jeepney direction is wrong, the selection layer can limit the damage but cannot supply missing transit evidence.
The lasting change was not a new magic walking number. It was the separation between finding a path and deciding whether to show it. The Buhangin case is still useful because it makes that separation concrete: the graph can be right about edges and wrong about advice.