Separate Spiritual Advisor shipment matching and alone conversion

Apply the Advanced retained-fighter target cutover through shared quotes and controls. Preserve committed foreign shipments while handling free-packet shipping, Polar matching, actual placement and original arrival once.
This commit is contained in:
Quintine committed 2026-10-24 22:28:42 +11:00
1 parent 438a5692a6
commit 767bace774
24 files changed
+1536 -69

No files matched your search

+24 -2
View File
@@ -92,12 +92,34 @@ function advance(game: Game, until: (state: Game) => boolean, actions: BgAmbassa
throw Error('Original native BG arrival did not reach the requested boundary');
}
/** Finish only original response/acknowledgement windows. In particular this
* never silently chooses Intrusion, accompaniment or the Ambassador offer. */
/** Allow response/acknowledgement windows, including a committed Spiritual
* arrival's automatic conversion. Never choose Intrusion, accompaniment or an
* Ambassador offer; tests stop at those original owned decisions. */
export function settleBgAmbassadorArrivalControls(game: Game, actions: BgAmbassadorArrivalStep[] = []): Game {
return advance(game, state => !state.response && !state.phaseOpening && !state.pendingTreacheryDiscard, actions);
}
/** Controlled already-declared legacy control, not fresh accompaniment permission.
* Keep the earned shipment, parent, forces, cards and wallets; replace only its
* current advisor chooser with the old recorded response shape, without the new
* Spiritual source. This isolates the retained callback protocol. */
export function recordControlledLegacyBgAdvisorResponse(state: Game): Game {
const decision = state.decision;
assert.ok(decision?.kind === 'advisor' && decision.destination);
assert.ok(!state.response && !state.pendingSpiritualAdvisor);
const game = structuredClone(state);
game.decision = null;
game.response = {
kind: 'advisor', owner: decision.player, location: decision.destination,
amount: 1, passed: [],
...(decision.ambassadorEvent === undefined ? {} : {
advisorResume: 'ambassador' as const, advisorAmbassadorEvent: decision.ambassadorEvent,
}),
};
assert.equal(game.response.spiritualAdvisorSource, undefined);
return game;
}
function orderLandBlows(game: Game, staging: string[]) {
for (let at = 0; at < 2; at++) {
const index = game.spiceDeck.findIndex((card, index) => index >= at && 'territory' in card &&