Answer the review: say which search gave up, and let the bisection earn its offer
The panel printed "No route at this range." beside the range it was offering — which is the sentence this branch exists to stop it printing. It was gated on there being no offer, and a search that gives up usually has one: Sol to HD 120147 at 4.5 pc spends the budget, offers 4.83 pc, and says there is no route where a 71-jump route exists. The wording now follows the search at the range that was asked for, and nothing else. That needs the two give-ups kept apart, so `least` travels beside `gaveUp` to the panel: one says the asked range was not searched out, the other that the search for a range that would work was. HIP 69445 at 3 pc — asked-range search exhaustive in 44 ms, ceiling probe out of budget — used to read "Too many stars to search at this range." and now reads "No route at this range.", with nothing claimed after it. Two more from the same review. The budget flag was read off the settled count, so a search that proved a dead end with the last star it was allowed reported a give-up; it now records why the loop stopped. And the bisection's cap could fire before a single probe had narrowed anything, leaving the ceiling route's own longest hop as the answer: star 1000115173 at 3 pc was told to go to 8.00 pc, the control's maximum, for a crossing that works at 6. It now offers 6.93. Measured in the app, all three: "Too many stars to search at this range. 4.90 pc would reach.", "No route at this range." alone, and 7.00 pc in place of 8.00. The duplicated dead-end test now asks the question it was named for — exactly the budget's worth of stars reaching each other and none of them the destination — and each fix kills its own mutant. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_016jxMkwA2rbicdGxHosecYi
This commit is contained in:
@@ -94,14 +94,13 @@ describe('routeBetween', () => {
|
||||
expect(search.gaveUp).toBe(true);
|
||||
});
|
||||
|
||||
it('reports a genuine dead end as one, having looked everywhere the range reaches', () => {
|
||||
const split = index([
|
||||
{ id: 0, x: 0, y: 0, z: 0 },
|
||||
{ id: 1, x: 1, y: 0, z: 0 },
|
||||
{ id: 2, x: 20, y: 0, z: 0 }
|
||||
]);
|
||||
it('reports a dead end proved with the last star of the budget as a dead end, not a give-up', { timeout: 30_000 }, () => {
|
||||
// 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.
|
||||
const search = routeBetween(budgetExactly(), 0, BUDGET_ISLAND, 1.5);
|
||||
|
||||
expect(routeBetween(split, 0, 2, 5)).toEqual({ route: null, gaveUp: false });
|
||||
expect(search).toEqual({ route: null, gaveUp: false });
|
||||
});
|
||||
|
||||
it('heads for the destination rather than exhausting a dense knot around the departure', () => {
|
||||
@@ -131,6 +130,19 @@ function knotAndChain(cellSizePc?: number): StarNeighbourhood {
|
||||
return new StarNeighbourhood([{ id: 0, x: 0, y: 0, z: 0 }, ...knot, ...chainOut, { id: ISLAND, x: 500, y: 0, z: 0 }], cellSizePc);
|
||||
}
|
||||
|
||||
/**
|
||||
* 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.
|
||||
*/
|
||||
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);
|
||||
}
|
||||
|
||||
/**
|
||||
* 45 000 stars in a 10 pc cube — dense enough to stay one connected piece at half a parsec, where
|
||||
* walking it costs more than a search's budget — with a chain a parsec a hop leaving its edge for
|
||||
@@ -166,6 +178,9 @@ describe('minimumRangeBetween', () => {
|
||||
expect(needed.least).toBe(false);
|
||||
expect(needed.rangePc).not.toBeNull();
|
||||
expect(routeBetween(knot, 0, CROWD_CHAIN_END, needed.rangePc!).route).not.toBeNull();
|
||||
// Narrower than the ceiling's own route, too: stopping before the bisection has found a range
|
||||
// of its own hands back the ceiling, which is the control's maximum — the question, not an answer.
|
||||
expect(needed.rangePc!).toBeLessThan(routeBetween(knot, 0, CROWD_CHAIN_END, 1.2).route!.longestHopPc);
|
||||
});
|
||||
|
||||
it('names the shortest range that opens a way through', () => {
|
||||
|
||||
Reference in New Issue
Block a user