From 8fb9f591060ba501ff9def8e32f8aa6e1786fc7d Mon Sep 17 00:00:00 2001 From: Quintine <15965533+Quintine@users.noreply.github.com> Date: Wed, 28 Oct 2026 21:30:49 +1100 Subject: [PATCH] Make mixed Homeworld HTTP fixture deterministic --- docs/CURRENT_STATUS.md | 8 +++++++- tests/richese-homeworld-marker-mixed-http.test.ts | 15 ++++++++++----- 2 files changed, 17 insertions(+), 6 deletions(-) diff --git a/docs/CURRENT_STATUS.md b/docs/CURRENT_STATUS.md index 20bead02..1c45bcf4 100644 --- a/docs/CURRENT_STATUS.md +++ b/docs/CURRENT_STATUS.md @@ -64,7 +64,13 @@ job and test-container loopback addresses are in separate nested Docker network namespaces. With user approval the dedicated runner now shares its own network namespace with its jobs, while retaining its separate Docker daemon; the runner reconnected. A fresh end-to-end build and registry -publication remain unverified. Existing NAS deployment and its persistent +publication remain unverified. Build `02695ae` passed container startup and +25 administrator checks, then failed one of 69 HTTP cases: the mixed Richese +Homeworld fixture allowed an unrelated earlier automated shipment before its +scripted two-force move. The fixture now suppresses those unrelated turns and +asserts the zero-to-two transition; its focused HTTP case passes locally. The +CI log did not preserve the precise earlier action, so the exact cause of its +extra visitor remains unproven. Existing NAS deployment and its persistent storage remain untouched. **Semuta before Basic Moritani ally retention — 28 October. Prototyped / diff --git a/tests/richese-homeworld-marker-mixed-http.test.ts b/tests/richese-homeworld-marker-mixed-http.test.ts index 1b6dd91c..82cb1491 100644 --- a/tests/richese-homeworld-marker-mixed-http.test.ts +++ b/tests/richese-homeworld-marker-mixed-http.test.ts @@ -46,7 +46,6 @@ void test('authenticated mixed transfer moves a concealed marker and physical vi for (const seat of seats) await act(seat.cookie, { type: 'ready' }); await act(hostSeat.cookie, { type: 'start' }); - let firstMarkerPlaced = false; async function advance(until: (ownerView: GameView) => boolean) { for (let count = 0; count < 800; count++) { const currentOwner = await read(ownerSeat.cookie); @@ -90,14 +89,16 @@ void test('authenticated mixed transfer moves a concealed marker and physical vi } assert.ok(proposed, `No native continuation at ${currentOwner.status}/${currentOwner.phase}/${currentOwner.decision?.kind ?? 'no decision'}.`); if (view.response && !view.response.passed.includes(view.me)) proposed.action = { type: 'passResponse' }; - // Keep the actual Atreides shipment affordable and avoid extra foreign - // arrivals to the temporary marker World; all passes/skips are legal. + // Keep the actual Atreides shipment affordable and reserve every + // Richese movement turn for the explicit shipments below. Richese may + // act before Atreides empties Caladan; letting its bot ship on that + // earlier turn would add unrelated visitors to the test's source pool. else if ((proposed.actor === hostSeat.id || proposed.actor === ownerSeat.id) && proposed.action.type === 'bid') proposed.action = { type: 'passBid' }; else if ((proposed.actor === hostSeat.id || proposed.actor === ownerSeat.id) && proposed.action.type === 'richeseBid') proposed.action = { type: 'richeseBid', event: proposed.action.event, amount: 0, allyPayment: 0 }; else if (view.phase === 5 && view.active === proposed.actor && !view.response && !view.decision && !view.truthtrance && !view.phaseOpening && - (proposed.actor === guildSeat.id || proposed.actor === ownerSeat.id && firstMarkerPlaced)) + (proposed.actor === guildSeat.id || proposed.actor === ownerSeat.id)) proposed.action = { type: 'endMovement' }; await act(proposed.cookie, proposed.action); } @@ -108,12 +109,16 @@ void test('authenticated mixed transfer moves a concealed marker and physical vi !own.players.find(player => player.id === own.me)!.shipped && own.players.find(player => player.id === hostSeat.id)!.reserves === 0 && !!own.homeworldShipment && !own.homeworldShipment.blocked && !!own.richeseNoField?.canShip); + const beforePhysical = first.homeworlds?.worlds?.find(world => world.id === 'homeworld:atreides')?.forces[first.me]; + assert.equal(beforePhysical?.normal ?? 0, 0, 'No unrelated Richese visit precedes the scripted shipment.'); + assert.equal(beforePhysical?.elite ?? 0, 0, 'No unrelated Richese elite visit precedes the scripted shipment.'); const physical = homeworldShipmentChoice(first, 'homeworld:atreides', { 'homeworld:richese': { normal: 2, elite: 0 } }); assert.ok(physical.action, physical.blocked ?? 'The physical shipment is available.'); await act(ownerSeat.cookie, physical.action); - firstMarkerPlaced = true; // Keep later owner turns for the test's explicit shipments. const firstLanded = await advance(own => own.phase === 5 && own.players.find(player => player.id === own.me)?.shipped === true); + assert.deepEqual(firstLanded.homeworlds?.worlds?.find(world => world.id === 'homeworld:atreides')?.forces[firstLanded.me], + { normal: 2, elite: 0 }, 'The scripted shipment adds exactly two visitors.'); const physicalTurn = firstLanded.turn; const markerTurn = await advance(own => own.status === 'playing' && own.turn > physicalTurn && own.phase === 5 && own.active === own.me && !own.players.find(player => player.id === own.me)!.shipped &&