Compose the Sneak Attack arrival with the Bene Gesserit reactions

A positive Moritani Sneak Attack now resolves its committed Terror entry first —
placing the reserve forces and accruing the Heighliners token — and then offers
the Bene Gesserit reactions under the same adopted shipment classification used
for ordinary off-planet arrivals: an eligible Advanced Intrusion choice when
Bene Gesserit fighters stand in the destination, carrying the free
spiritual-advisor follow-up, and the advisor shipment itself when the intrusion
is declined, with its existing territory, storm and occupancy validation. A
zero-force resolution still opens none of them, and the last "Sneak Attack
combined with Bene Gesserit arrival reactions" guard is removed rather than left
as a duplicate check.

This selected order is an implementation interpretation, not a located ruling;
the source note, the pending-interpretation registry, the component inventory,
the player reference and the status log record it. Evidence:
tests/moritani-force-leader.test.ts asserts the post-entry Intrusion offer and
the full decline -> advisor decision -> placement chain with nothing left
pending; the former gated-interaction pin was replaced by the composed
behaviour. Offline suite 7,798/7,798 with typecheck and lint clean, plus the
reference guide tests 10/10. No game, seat, saved state or deployment changes.
This commit is contained in:
Quintine committed 2026-10-07 18:53:19 +11:00
1 parent 972b781d0a
commit 3bebc68cd5
8 files changed
+96 -26

No files matched your search

+36 -5
View File
@@ -340,17 +340,48 @@ void test('Sneak Attack rejects an altered client sector and accrues Heighliners
assert.equal(passed.pendingTerrorEntry, null);
});
void test('Sneak Attack leaves unresolved Bene Gesserit arrival interactions unchanged and permits zero', () => {
void test('Sneak Attack resolves the committed entry and then offers the Bene Gesserit arrival reactions', () => {
const initial = fixture('sneakAttack', 'beneGesserit');
initial.advanced = true;
player(initial, 'e').forces = { 'arrakeen:10': 1 };
player(initial, 'e').reserves = 19;
const revealed = decide(enter(initial), { reveal: true });
rejected(revealed, { type: 'decision', amount: 1 });
const done = decide(revealed, { amount: 0 });
assert.deepEqual(done.players, revealed.players);
const done = decide(revealed, { amount: 1 });
assert.equal(player(done, 'm').forces['arrakeen:10'], 1);
assert.equal(player(done, 'm').reserves, 19);
assert.equal(done.pendingTerrorEntry, null);
assert.equal(done.decision, null);
assert.equal(done.decision?.kind, 'intrusion');
assert.equal(done.decision?.player, 'e');
const zero = decide(revealed, { amount: 0 });
assert.deepEqual(zero.players, revealed.players);
assert.equal(zero.pendingTerrorEntry, null);
assert.equal(zero.decision, null);
});
void test('declining the Bene Gesserit intrusion still offers the free advisor shipment and completes the entry', () => {
const initial = fixture('sneakAttack', 'beneGesserit');
initial.advanced = true;
player(initial, 'e').forces = { 'arrakeen:10': 1 };
player(initial, 'e').reserves = 19;
const revealed = decide(enter(initial), { reveal: true });
const sneaked = decide(revealed, { amount: 2 });
assert.equal(sneaked.decision?.kind, 'intrusion');
const declined = applyAction(sneaked, 'e', { type: 'decision', accept: false });
assert.equal(declined.decision?.kind, 'advisor');
assert.equal(declined.decision?.player, 'e');
const offered = applyAction(declined, 'e', {
type: 'decision',
accept: true,
amount: 1,
});
assert.equal(offered.response ?? null, null);
assert.equal(offered.pendingTerrorEntry, null);
assert.equal(player(offered, 'm').forces['arrakeen:10'], 2);
assert.equal(
player(offered, 'e').forces['polar_sink:0'] ?? 0,
1,
'The declined intrusion still lets the Bene Gesserit send one free reserve force.',
);
});
void test('all four AI profiles complete blocked effects by declining instead of retrying illegal choices', () => {