Adopt the Sneak Attack shipment classification for Heighliners

A positive Moritani Sneak Attack in Shipment and Movement now accrues the
Heighliners token once for its owner through the same techIncome helper as an
ordinary off-planet arrival. The token still enforces its working phase, its
printed Guild exception and one accrual per turn, and a zero-force resolution
accrues nothing; the previous phase-5 guard is removed rather than duplicated.

This is an explicitly adopted implementation interpretation, not a retrieved
publisher ruling: the printed verb is "Send", and classifying the expressly
off-planet reserve transfer as a shipment is the supported reading already
recorded in the source note. Bene Gesserit spiritual-advisor accompaniment and
Intrusion stay gated because their ordering inherits the unresolved
arrival-timing boundary; a Spice Blow worm-ride Sneak Attack stays outside the
token's working phase.

Evidence: tests/moritani-force-leader.test.ts asserts the accrual, the
once-per-turn guard, the unchanged zero-force resolution and the altered-sector
rejection; the former unsupported-classification pin was replaced by the
adopted behaviour rather than re-pinned. The component inventory, the source
note, the pending-interpretation registry, the player reference and the status
log record the reduced boundary. Offline suite 7,797/7,797 with typecheck and
lint clean. No game, seat, saved state or deployment changes.
This commit is contained in:
Quintine committed 2026-10-07 18:40:19 +11:00
1 parent 0e1a442dca
commit 972b781d0a
8 files changed
+79 -8

No files matched your search

+12 -3
View File
@@ -315,16 +315,25 @@ void test('advanced Harkonnen tables uniformly disable Assassination without exp
rejected(held, { type: 'decision', reveal: true });
});
void test('Sneak Attack rejects an altered client sector and unsupported technology classification while allowing zero', () => {
void test('Sneak Attack rejects an altered client sector and accrues Heighliners once for a positive entry while allowing zero', () => {
const revealed = decide(offered('sneakAttack'), { reveal: true });
rejected(revealed, { type: 'decision', amount: 1, sector: 9 });
const valid = decide(revealed, { amount: 1, sector: 10 });
assert.equal(player(valid, 'm').forces['arrakeen:10'], 1);
const technology = reload(revealed);
technology.techTokens = createTechTokens();
technology.techTokens.heighliners.owner = 'm';
technology.techTokens.heighliners.owner = 'a';
technology.techTokens.heighliners.spice = 2;
rejected(technology, { type: 'decision', amount: 1 });
const accrued = decide(technology, { amount: 1, sector: 10 });
assert.equal(player(accrued, 'm').forces['arrakeen:10'], 1);
assert.equal(accrued.techTokens!.heighliners.triggeredTurn, accrued.turn);
assert.equal(accrued.techTokens!.heighliners.spice, 1);
assert.equal(
accrued.log.some((line) =>
line.text.includes('Heighliners accrued 1 spice for Atreides'),
),
true,
);
const passed = decide(technology, { amount: 0 });
assert.deepEqual(passed.techTokens, technology.techTokens);
assert.deepEqual(passed.players, technology.players);