Tell a search that gave up from a route that is not there

The route search stops after MAX_VISITED stars and returned null, which everything downstream read
as "the catalogue holds no chain". On the real catalogue that was wrong for real questions: Sol to
HD 120147 (136 pc) at 5 pc is 50 jumps, and the panel said there was no route. The budget also sat
under what the shipped catalogue needs, so it is now 40 000 rather than 20 000: both that route and
a star at 170 pc are found, and Sol to HD 2626 at 6 pc, which used to be refused after 4.7 s, plots
56 jumps in about 2 s.

A search now reports whether it gave up. The range search no longer counts a give-up as proof that
nothing routes below it — that is what reported ranges up to 29% too wide — and it stops after two
of them, since those are the probes that cost the most and settle the least: for HD 2626 at 3 pc it
offers 5.92 pc in about 4 s, against 6.13 pc in 4.7 s. At the panel's widest range the refused route
and the range search are the same question, so it is asked once. Where nothing can be said, the
panel says "Too many stars to search at this range." rather than claiming there is no route.

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 12:49:18 +02:00
co-authored by Claude Opus 5
parent b4017fcafc
commit 862fb65ea4
10 changed files with 278 additions and 81 deletions
@@ -73,7 +73,7 @@ describe('RoutingClient without a worker', () => {
it('answers a route from the index it was given', async () => {
const client = new RoutingClient(STARS, POSITIONS, index);
await expect(client.route(100, 104, 1.5, 8)).resolves.toEqual({ route: routeBetween(index, 100, 104, 1.5), neededRangePc: null });
await expect(client.route(100, 104, 1.5, 8)).resolves.toEqual({ route: routeBetween(index, 100, 104, 1.5).route, neededRangePc: null, gaveUp: false });
client.dispose();
});
@@ -143,8 +143,8 @@ describe('RoutingClient with a worker', () => {
await expect(first).resolves.toHaveLength(6);
const routeRequest = worker.requests[1];
worker.answer({ kind: 'route', requestId: routeRequest.requestId, route: null, neededRangePc: 4 });
await expect(route).resolves.toEqual({ route: null, neededRangePc: 4 });
worker.answer({ kind: 'route', requestId: routeRequest.requestId, route: null, neededRangePc: 4, gaveUp: false });
await expect(route).resolves.toEqual({ route: null, neededRangePc: 4, gaveUp: false });
await flush();
expect(worker.requests.map((request) => (request.kind === 'links' ? request.rangePc : request.kind))).toEqual([5, 'route', 8]);
@@ -161,14 +161,14 @@ describe('RoutingClient with a worker', () => {
const widerRange = client.route(100, 104, 2.5, 8);
expect(worker.requests).toHaveLength(1);
worker.answer({ kind: 'route', requestId: worker.requests[0].requestId, route: null, neededRangePc: 4 });
worker.answer({ kind: 'route', requestId: worker.requests[0].requestId, route: null, neededRangePc: 4, gaveUp: false });
expect(await again).toEqual(await once);
await flush();
// The same two stars at another range is another question.
expect(worker.requests.map((request) => request.rangePc)).toEqual([1.5, 2.5]);
worker.answer({ kind: 'route', requestId: worker.requests[1].requestId, route: null, neededRangePc: null });
await expect(widerRange).resolves.toEqual({ route: null, neededRangePc: null });
worker.answer({ kind: 'route', requestId: worker.requests[1].requestId, route: null, neededRangePc: null, gaveUp: false });
await expect(widerRange).resolves.toEqual({ route: null, neededRangePc: null, gaveUp: false });
client.dispose();
});
@@ -218,6 +218,16 @@ describe('RoutingClient with a worker', () => {
client.dispose();
});
it('hands on that the search gave up, along with the answer it did give', async () => {
const { client, worker } = clientWithFake();
const answer = client.route(100, 105, 1.5, 8);
worker.answer({ kind: 'route', requestId: worker.requests[0].requestId, route: null, neededRangePc: null, gaveUp: true });
await expect(answer).resolves.toEqual({ route: null, neededRangePc: null, gaveUp: true });
client.dispose();
});
it('rejects a request the worker failed on, and goes on to the next', async () => {
const { client, worker } = clientWithFake();
const failing = client.route(100, 104, 1.5, 8).catch((error: unknown) => error);
@@ -240,7 +250,7 @@ describe('RoutingClient with a worker', () => {
worker.fail();
await expect(route).resolves.toEqual({ route: routeBetween(index, 100, 104, 1.5), neededRangePc: null });
await expect(route).resolves.toEqual({ route: routeBetween(index, 100, 104, 1.5).route, neededRangePc: null, gaveUp: false });
expect(Array.from(await graph)).toEqual([0, 0, 0, 1, 0, 0]);
await expect(client.route(100, 105, 1.5, 8)).resolves.toMatchObject({ route: null });
expect(worker.terminated).toBe(true);