Make the budget test spend the budget, and bound the probes that earn nothing

The fixture built for "exactly MAX_VISITED stars reachable" was 338 short: its
random cloud leaves clumps the departure never reaches (the LCG gives 12 212
distinct positions for 39 999 stars), so the search settled 39 662 and the
pre-fix code answered `gaveUp: false` too. The test could not fail on the code
it was written to pin — and the mutant that seemed to prove otherwise was
failing to compile, not failing the test. It is now a line of 40 000 a parsec
apart with the island off the line: settled 40 000 exactly, 115 ms, and the
pre-fix code does report a give-up. Both mutants now compile and are caught.

The give-up cap also has to hold while the bisection has earned nothing: the
exception added for that case had no bound at all, so a search could spend the
resolution's own eight full-budget probes — about 17 s of "Plotting…" — where
two used to cost 4 s. Bounded at five. On the repo's crowded-knot fixture:
1.9 s for the unearned ceiling figure with the old cap, 7.2 s for a range the
bisection earned, and five probes is where that lands.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016jxMkwA2rbicdGxHosecYi
This commit is contained in:
2026-09-18 15:51:38 +02:00
co-authored by Claude Opus 5
parent 1a53f26474
commit 337602f606
2 changed files with 26 additions and 16 deletions
+10 -8
View File
@@ -94,7 +94,7 @@ describe('routeBetween', () => {
expect(search.gaveUp).toBe(true);
});
it('reports a dead end proved with the last star of the budget as a dead end, not a give-up', { timeout: 30_000 }, () => {
it('reports a dead end proved with the last star of the budget as a dead end, not a give-up', () => {
// Exactly the budget's worth of stars reach each other, and the destination is not among them.
// The search does look everywhere the range reaches, so "no chain" is what it found — but the
// set is full at the end of it, and a budget read off the settled count says it gave up.
@@ -131,16 +131,18 @@ function knotAndChain(cellSizePc?: number): StarNeighbourhood {
}
/**
* Exactly a search's budget of stars that reach one another — 39 999 through the 30 pc cube around
* the origin, plus the departure — and one at 500 pc that nothing reaches. The dead end is real and
* the search proves it, with the last star it is allowed.
* Exactly a search's budget of stars that reach one another — 40 000 a parsec apart along x, which
* a 1.5 pc range walks end to end — and one 500 pc off that line, which nothing reaches. The dead
* end is real and the search proves it, with the last star it is allowed.
*
* A line rather than a crowd because the count has to be exact: a random cloud dense enough to
* connect leaves clumps the departure never reaches, and 39 662 of 40 000 settled is a budget that
* was never spent.
*/
const BUDGET_ISLAND = 99_999;
function budgetExactly(): StarNeighbourhood {
let seed = 13;
const random = () => ((seed = (seed * 1103515245 + 12345) % 2147483648) / 2147483648) * 30 - 15;
const crowd: StarPoint[] = Array.from({ length: 39_999 }, (_, i) => ({ id: 1000 + i, x: random(), y: random(), z: random() }));
return new StarNeighbourhood([{ id: 0, x: 0, y: 0, z: 0 }, ...crowd, { id: BUDGET_ISLAND, x: 500, y: 0, z: 0 }], 1.5);
const line: StarPoint[] = Array.from({ length: 40_000 }, (_, i) => ({ id: i, x: i, y: 0, z: 0 }));
return new StarNeighbourhood([...line, { id: BUDGET_ISLAND, x: 0, y: 500, z: 0 }], 1.5);
}
/**