Support Basic allied No-Field transit
Verify and publish checkpoint container / container (push) Canceled after 0s
Verify and publish checkpoint container / container (push) Canceled after 0s
This commit is contained in:
1 parent
40d567a053
commit
9c6b2d884b
8 files changed
+171
-7
No files matched your search
@@ -93,6 +93,11 @@ export function RicheseAlliedNoFieldControls({
|
|||||||
token reveals when the shipment resolves. No payment or existing
|
token reveals when the shipment resolves. No payment or existing
|
||||||
marker changes occur on decline.
|
marker changes occur on decline.
|
||||||
</p>
|
</p>
|
||||||
|
{!game.advanced && <p className="notice">
|
||||||
|
Basic allied transit: if the destination is occupied by you,
|
||||||
|
the recipient must move the arriving forces out before finishing.
|
||||||
|
Under the provisional rule, new visitors left behind go to Tanks.
|
||||||
|
</p>}
|
||||||
{pricing.policy && <p className="notice">{pricing.policy}</p>}
|
{pricing.policy && <p className="notice">{pricing.policy}</p>}
|
||||||
{info.private.deployed && (
|
{info.private.deployed && (
|
||||||
<p className="fine">
|
<p className="fine">
|
||||||
@@ -233,6 +238,11 @@ export function RicheseAlliedNoFieldDecision({
|
|||||||
declining costs nothing. Any earlier concealed No-Field reveals only if
|
declining costs nothing. Any earlier concealed No-Field reveals only if
|
||||||
the accepted shipment proceeds.
|
the accepted shipment proceeds.
|
||||||
</p>
|
</p>
|
||||||
|
{!game.advanced && <p className="notice">
|
||||||
|
If this destination is occupied by your ally, move the arriving forces
|
||||||
|
out before finishing. Under the provisional Basic rule, new visitors
|
||||||
|
left behind go to Tanks.
|
||||||
|
</p>}
|
||||||
{offer.blocked && (
|
{offer.blocked && (
|
||||||
<p className="notice" id={`${id}-blocked`}>
|
<p className="notice" id={`${id}-blocked`}>
|
||||||
{offer.blocked} You can decline without cost.
|
{offer.blocked} You can decline without cost.
|
||||||
|
|||||||
@@ -2,6 +2,26 @@
|
|||||||
|
|
||||||
Audit: 7 September 2026. Read-only source and code audit, retained by the coordinating agent. Scope is classic GF9 base rules and the applicable November 2020 FAQ, not tournament or movie-game rules.
|
Audit: 7 September 2026. Read-only source and code audit, retained by the coordinating agent. Scope is classic GF9 base rules and the applicable November 2020 FAQ, not tournament or movie-game rules.
|
||||||
|
|
||||||
|
## Basic allied No-Field transit — 28 October 2026
|
||||||
|
|
||||||
|
**Prototyped / Partial.** The November 2020 FAQ p. 2 permits shipment into
|
||||||
|
an ally's territory followed by departure, subject to stronghold limits. The
|
||||||
|
printed Richese alliance ability ships its ally's off-planet forces and
|
||||||
|
immediately reveals the No-Field. Provisionally compose these rules for a
|
||||||
|
Basic offer to the Richese-occupied territory. The recipient consents, the
|
||||||
|
native cancellation and payment sequence applies, and the arriving counters
|
||||||
|
are ordinary physical forces. The pre-arrival physical baseline records only
|
||||||
|
new visitors; finishing without moving them out sends those visitors to Tanks
|
||||||
|
under the existing provisional Basic fallback. A zero-force arrival creates no
|
||||||
|
visitor receipt. The supplier's older marker reveals before this baseline is
|
||||||
|
recorded. Supplier and recipient see the fallback before offering or accepting;
|
||||||
|
minimal AI retains its existing legal empty-destination offer path.
|
||||||
|
|
||||||
|
This composition and the no-departure fallback are software interpretations,
|
||||||
|
not publisher rulings. Advanced allied No-Field transit and other transport
|
||||||
|
combinations remain separate. [FAQ](https://www.gf9games.com/dune/wp-content/uploads/2020/11/Dune-FAQ-Nov-2020.pdf),
|
||||||
|
[CHOAM & Richese rulebook](https://www.gf9games.com/dune/wp-content/uploads/2021/11/CHOAM-Rulebook-low-res.pdf).
|
||||||
|
|
||||||
## Concealed No-Field at the separation deadline — 27 October 2026
|
## Concealed No-Field at the separation deadline — 27 October 2026
|
||||||
|
|
||||||
A deployed marker is one force for all effects, including0, under publisher E2
|
A deployed marker is one force for all effects, including0, under publisher E2
|
||||||
@@ -118,7 +138,7 @@ Tanks. A zero marker reveals without casualties. Physical visitors use the
|
|||||||
same pre-arrival baseline; an earlier voluntary reveal is not repeated.
|
same pre-arrival baseline; an earlier voluntary reveal is not repeated.
|
||||||
The source FAQ permits allied shipment transit and the Richese rules count a
|
The source FAQ permits allied shipment transit and the Richese rules count a
|
||||||
concealed marker as one force; their combination and no-exit consequence are
|
concealed marker as one force; their combination and no-exit consequence are
|
||||||
provisional. Allied No-Field use, paired markers and other
|
provisional. Paired markers and other
|
||||||
transport compositions remain separate.
|
transport compositions remain separate.
|
||||||
|
|
||||||
## Basic ordinary allied shipment prototype — 8 October 2026
|
## Basic ordinary allied shipment prototype — 8 October 2026
|
||||||
@@ -148,8 +168,9 @@ co-occupation deadlines still resolve separately. AI prioritizes available
|
|||||||
native departures and does not retain a stronghold sentinel from a
|
native departures and does not retain a stronghold sentinel from a
|
||||||
visiting group.
|
visiting group.
|
||||||
|
|
||||||
Allied No-Field arrivals, mobile/homeworld transports and
|
Mobile/homeworld transports and optional arrival compositions are not expanded
|
||||||
optional arrival compositions are not expanded by the Basic prototype.
|
by the ordinary Basic prototype. Allied No-Field arrivals have their own
|
||||||
|
later scoped prototype above.
|
||||||
Advanced ordinary physical entry is connected in the follow-up above.
|
Advanced ordinary physical entry is connected in the follow-up above.
|
||||||
Refinement of interrupted and mixed-cohort interactions remains later work.
|
Refinement of interrupted and mixed-cohort interactions remains later work.
|
||||||
|
|
||||||
|
|||||||
@@ -16,6 +16,15 @@ open mode gates.
|
|||||||
|
|
||||||
|
|
||||||
## Current checkpoint and work
|
## Current checkpoint and work
|
||||||
|
|
||||||
|
**Basic allied No-Field transit — 28 October. Prototyped / Partial.** Richese
|
||||||
|
may offer its ally an immediately revealed shipment into a Richese-occupied
|
||||||
|
territory. The recipient may depart or accept the existing provisional loss
|
||||||
|
of new visitors at turn end; consent, price and cancellation retain their
|
||||||
|
native sequence. Advanced and other transport compositions remain separate.
|
||||||
|
The six-file related union passes 51 cases; the new offer, departure, loss,
|
||||||
|
zero and decline cases use a staged, force-conserving Basic fixture.
|
||||||
|
[Scope](ALLIED_TRANSIT_RULES.md#basic-allied-no-field-transit--28-october-2026).
|
||||||
**Basic allied No-Field transit with paid companions — 28 October.
|
**Basic allied No-Field transit with paid companions — 28 October.
|
||||||
Prototyped / Partial.** High Richese Homeworld can include paid physical
|
Prototyped / Partial.** High Richese Homeworld can include paid physical
|
||||||
reserves beside its own visiting marker. The existing temporary-visit baseline
|
reserves beside its own visiting marker. The existing temporary-visit baseline
|
||||||
|
|||||||
@@ -1,5 +1,12 @@
|
|||||||
# Rule decision index
|
# Rule decision index
|
||||||
|
|
||||||
|
## Basic allied No-Field transit — 28 October 2026
|
||||||
|
|
||||||
|
The FAQ's allied shipment and the Richese alliance ability are provisionally
|
||||||
|
combined for an immediately revealed Basic arrival into Richese's territory.
|
||||||
|
The existing new-visitor fallback applies; the exact interaction is not a
|
||||||
|
publisher ruling. [Scope](ALLIED_TRANSIT_RULES.md#basic-allied-no-field-transit--28-october-2026).
|
||||||
|
|
||||||
## Fresh No-Field with native Homeworld reserves — 28 October 2026
|
## Fresh No-Field with native Homeworld reserves — 28 October 2026
|
||||||
|
|
||||||
The supplied Advanced revision permits World-to-World shipment and counts a
|
The supplied Advanced revision permits World-to-World shipment and counts a
|
||||||
|
|||||||
@@ -4905,6 +4905,7 @@ function alliedNoFieldQuote(
|
|||||||
offer.sector,
|
offer.sector,
|
||||||
false,
|
false,
|
||||||
advisors,
|
advisors,
|
||||||
|
!trial.advanced && isAlliedShipmentVisit(trial, trialRecipient, offer.territory),
|
||||||
);
|
);
|
||||||
const pricing = alliedNoFieldPricing(g, recipient);
|
const pricing = alliedNoFieldPricing(g, recipient);
|
||||||
const cost = territory(offer.territory).type === 'stronghold' ? pricing.stronghold : pricing.other;
|
const cost = territory(offer.territory).type === 'stronghold' ? pricing.stronghold : pricing.other;
|
||||||
@@ -38482,6 +38483,12 @@ function commitShipment(g: Game, shipment: PendingShipment, settlement?: {recipi
|
|||||||
}
|
}
|
||||||
const owner = getPlayer(g, offer.owner);
|
const owner = getPlayer(g, offer.owner);
|
||||||
if (owner.noField!.deployed) revealPlayerNoField(g, owner, 'beforeAlly');
|
if (owner.noField!.deployed) revealPlayerNoField(g, owner, 'beforeAlly');
|
||||||
|
// The marker reveals immediately for its ally. Record only the recipient's
|
||||||
|
// pre-arrival physical baseline, after the supplier's old marker reveals.
|
||||||
|
if (!g.advanced && n > 0) {
|
||||||
|
const visit = occupancyRule(() => makeBasicAlliedShipment(g, p, to));
|
||||||
|
if (visit) g.basicAlliedShipment = visit;
|
||||||
|
}
|
||||||
const result = noFieldRule(() =>
|
const result = noFieldRule(() =>
|
||||||
shipAlliedRicheseNoField(
|
shipAlliedRicheseNoField(
|
||||||
owner.noField!,
|
owner.noField!,
|
||||||
|
|||||||
+3
-3
@@ -4459,14 +4459,14 @@ export const RULE_TOPICS: RuleTopic[] = [
|
|||||||
'A concealed No-Field counts as one presence, including0. If its actual separation deadline is due, provisional destructive timing reveals it, places only available reserves, then sends your actual shared forces to Tanks. Your ally stays. A marker elsewhere, a not-yet-due Basic turn and existing exemptions retain concealment. The warning states that a marker is affected without exposing its value or quantity.',
|
'A concealed No-Field counts as one presence, including0. If its actual separation deadline is due, provisional destructive timing reveals it, places only available reserves, then sends your actual shared forces to Tanks. Your ally stays. A marker elsewhere, a not-yet-due Basic turn and existing exemptions retain concealment. The warning states that a marker is affected without exposing its value or quantity.',
|
||||||
'Complete any pending shipment, movement, arrival or response before ordinary finish controls become available. Extra-move and deferred-turn endings use the same separation rule; they do not replay the arrival or charge shipment again.',
|
'Complete any pending shipment, movement, arrival or response before ordinary finish controls become available. Extra-move and deferred-turn endings use the same separation rule; they do not replay the arrival or charge shipment again.',
|
||||||
'An ordinary physical reserve shipment may enter an allied territory before your movement on your own unused turn. Ship, then move out; stronghold capacity and storm still apply. The ending consequence covers your entire shared group, not only new visitors. No-Fields and optional Homeworld/mobile transport keep their separate legality.',
|
'An ordinary physical reserve shipment may enter an allied territory before your movement on your own unused turn. Ship, then move out; stronghold capacity and storm still apply. The ending consequence covers your entire shared group, not only new visitors. No-Fields and optional Homeworld/mobile transport keep their separate legality.',
|
||||||
'Basic still uses its older later-player deadline and formation-turn exemption, plus its visibly provisional new-visitor consequence. Homeworlds keep their native allied-entry restrictions. The rule grants no extra shipment, movement or alliance formation window; completed turns are not reevaluated.',
|
'Basic still uses its older later-player deadline and formation-turn exemption, plus its visibly provisional new-visitor consequence. An accepted Richese allied No-Field offer may also arrive at Richese’s territory; its immediately revealed visitors use the same physical baseline. Homeworlds keep their native allied-entry restrictions. The rule grants no extra shipment, movement or alliance formation window; completed turns are not reevaluated.',
|
||||||
],
|
],
|
||||||
checklist: [
|
checklist: [
|
||||||
{ area: 'Implementation', status: 'Partial', detail: 'A shared ordinary reserve-entry quote connects native preflight and commit. Canonical separation distinguishes Basic from Advanced timing and exceptions; native ending-group custody removes all Advanced shared forces. Due concealed markers use provisional reserve-capped revelation instead of blocking Finish.', evidence: ['game/allied-separation.ts', 'game/engine.ts'] },
|
{ area: 'Implementation', status: 'Partial', detail: 'A shared ordinary reserve-entry quote connects native preflight and commit. Canonical separation distinguishes Basic from Advanced timing and exceptions; native ending-group custody removes all Advanced shared forces. Due concealed markers use provisional reserve-capped revelation instead of blocking Finish.', evidence: ['game/allied-separation.ts', 'game/engine.ts'] },
|
||||||
{ area: 'Player controls', status: 'Partial', detail: 'Pre-shipment notice explains the mode-specific consequence; own clean-turn warnings in both modes contain public loss territories and optional marker presence only. Mandatory loss adds no confirmation and pending-control locks retain priority.', evidence: ['components/game-table.tsx', 'components/advanced-ally-separation.tsx'] },
|
{ area: 'Player controls', status: 'Partial', detail: 'Pre-shipment and allied No-Field offer notices explain the mode-specific consequence; own clean-turn warnings in both modes contain public loss territories and optional marker presence only. Mandatory loss adds no confirmation and pending-control locks retain priority.', evidence: ['components/game-table.tsx', 'components/advanced-ally-separation.tsx', 'components/richese-allied-no-field.tsx'] },
|
||||||
{ area: 'AI', status: 'Partial', detail: 'All four legal policies use projected movement/ending availability. Native departure takes priority, without retaining a doomed city sentinel; a Medium typed-arrival/departure smoke passes. No separate separation calculator or strategic calibration was added.', evidence: ['game/bots.ts', 'game/bot-mobility.ts', 'tests/bot-advanced-source.test.ts'] },
|
{ area: 'AI', status: 'Partial', detail: 'All four legal policies use projected movement/ending availability. Native departure takes priority, without retaining a doomed city sentinel; a Medium typed-arrival/departure smoke passes. No separate separation calculator or strategic calibration was added.', evidence: ['game/bots.ts', 'game/bot-mobility.ts', 'tests/bot-advanced-source.test.ts'] },
|
||||||
{ area: 'Documentation', status: 'Implemented', detail: 'The unofficial per-player/advisor departures are distinguished from the earlier publisher/FAQ and unchanged Basic policy.', evidence: ['docs/ALLIED_TRANSIT_RULES.md', 'docs/RULE_DECISIONS.md', 'docs/BASE_ADVANCED_READINESS_20260907.md'] },
|
{ area: 'Documentation', status: 'Implemented', detail: 'The unofficial per-player/advisor departures are distinguished from the earlier publisher/FAQ and unchanged Basic policy.', evidence: ['docs/ALLIED_TRANSIT_RULES.md', 'docs/RULE_DECISIONS.md', 'docs/BASE_ADVANCED_READINESS_20260907.md'] },
|
||||||
{ area: 'Verification', status: 'Partial', detail: 'Genuine native overlaps cover first/later newly allied ends, exact normal/elite custody, Hajr, advisor/Ecaz/Polar exceptions and unchanged Basic policy. Authenticated SQLite covers restart, races, privacy and immutable rejects. Actual phone ending moved only the ending group to Tanks and refresh retained it; wider combinations, full modes and deployment remain open.', evidence: ['tests/fixture-no-field-allied-separation.ts', 'tests/no-field-allied-separation-engine.test.ts', 'tests/no-field-allied-separation-controls.test.tsx', 'tests/allied-separation.test.ts', 'tests/advanced-source-engine.test.ts', 'tests/advanced-source-recovery.test.ts', 'tests/bot-advanced-source.test.ts', 'docs/ALLIED_TRANSIT_RULES.md'] },
|
{ area: 'Verification', status: 'Partial', detail: 'Genuine native overlaps cover first/later newly allied ends, exact normal/elite custody, Hajr, advisor/Ecaz/Polar exceptions and unchanged Basic policy. Staged allied No-Field offers exercise consent, departure, loss and zero arrivals. Authenticated SQLite covers restart, races, privacy and immutable rejects. Actual phone ending moved only the ending group to Tanks and refresh retained it; wider combinations, full modes and deployment remain open.', evidence: ['tests/allied-no-field-transit.test.ts', 'tests/fixture-no-field-allied-separation.ts', 'tests/no-field-allied-separation-engine.test.ts', 'tests/no-field-allied-separation-controls.test.tsx', 'tests/allied-separation.test.ts', 'tests/advanced-source-engine.test.ts', 'tests/advanced-source-recovery.test.ts', 'tests/bot-advanced-source.test.ts', 'docs/ALLIED_TRANSIT_RULES.md'] },
|
||||||
],
|
],
|
||||||
related: ['movement', 'spice-blow', 'advanced-advisors', 'ecaz-ambassadors', 'homeworlds'],
|
related: ['movement', 'spice-blow', 'advanced-advisors', 'ecaz-ambassadors', 'homeworlds'],
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -0,0 +1,109 @@
|
|||||||
|
import assert from 'node:assert/strict';
|
||||||
|
import test from 'node:test';
|
||||||
|
import { baseDeck } from '../game/cards';
|
||||||
|
import { applyAction, createGame, newPlayer, viewGame, type Action, type Game } from '../game/engine';
|
||||||
|
import { createRicheseNoField } from '../game/richese-no-field';
|
||||||
|
|
||||||
|
// Controlled Basic turn: both allies already have conserved forces in the
|
||||||
|
// destination, as can happen when they formed their alliance this turn.
|
||||||
|
function fixture() {
|
||||||
|
const game = createGame('ALLYTRANSIT', newPlayer('r', 'Richese', 'richese'), false, ['choam']);
|
||||||
|
game.players.push(newPlayer('a', 'Emperor', 'emperor'), newPlayer('e', 'Atreides', 'atreides'));
|
||||||
|
game.status = 'playing'; game.phase = 5; game.turn = 2; game.active = 'a';
|
||||||
|
game.order = ['r', 'a', 'e']; game.movementRemaining = ['a', 'r', 'e']; game.storm = 18;
|
||||||
|
game.deck = baseDeck();
|
||||||
|
for (const p of game.players) {
|
||||||
|
p.forces = {}; p.reserves = 20; p.tanks = 0; p.spice = 10; p.hand = [];
|
||||||
|
p.traitorChoices = []; p.traitors = [];
|
||||||
|
}
|
||||||
|
const [richese, recipient] = game.players;
|
||||||
|
richese.ally = 'a'; recipient.ally = 'r';
|
||||||
|
richese.allySinceTurn = 2; recipient.allySinceTurn = 2;
|
||||||
|
richese.forces['imperial_basin:11'] = 2; richese.reserves = 18;
|
||||||
|
recipient.forces['imperial_basin:11'] = 2; recipient.reserves = 18;
|
||||||
|
recipient.elites = {reserves: 4, tanks: 0, forces: {'imperial_basin:11': 1}, revived: 0};
|
||||||
|
richese.noField = createRicheseNoField(['zero', 'three', 'five']);
|
||||||
|
richese.noFieldEvent = 'original-marker-event';
|
||||||
|
return game;
|
||||||
|
}
|
||||||
|
const send = (game: Game, id: string, action: Action) => applyAction(game, id, action);
|
||||||
|
function offer(game: Game, token = 'three') {
|
||||||
|
return send(game, 'r', {type: 'offerRicheseNoField', token,
|
||||||
|
event: game.players[0].noFieldEvent, payer: 'r', territory: 'imperial_basin', sector: 9});
|
||||||
|
}
|
||||||
|
function accept(game: Game, elite = 2) {
|
||||||
|
return send(game, 'a', {type: 'decision', event: game.richeseAllyOffer!.event, accept: true, elite});
|
||||||
|
}
|
||||||
|
function finishResponse(game: Game) {
|
||||||
|
for (let count = 0; game.response && count < 10; count++) {
|
||||||
|
const pending = game.response;
|
||||||
|
const next = game.players.find(p => !pending.passed.includes(p.id));
|
||||||
|
assert.ok(next, 'Every response must resolve after all eligible players pass.');
|
||||||
|
game = send(game, next.id, {type: 'passResponse'});
|
||||||
|
}
|
||||||
|
assert.equal(game.pendingShipment, null);
|
||||||
|
return game;
|
||||||
|
}
|
||||||
|
function physicalAt(game: Game, id: string, territory: string) {
|
||||||
|
const p = game.players.find(seat => seat.id === id)!;
|
||||||
|
return Object.entries(p.forces).reduce((sum, [key, n]) => sum + (key.startsWith(`${territory}:`) ? n : 0), 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
void test('Basic allied No-Field physical visitors may leave after the real offer and acceptance', () => {
|
||||||
|
const initial = fixture();
|
||||||
|
let game = offer(initial);
|
||||||
|
assert.equal(game.decision?.kind, 'richeseAllyShipment');
|
||||||
|
assert.equal(game.players[0].spice, 10);
|
||||||
|
game = finishResponse(accept(game));
|
||||||
|
assert.equal(game.players[0].spice, 8);
|
||||||
|
assert.equal(game.players[0].noField!.lastShipped, 'three');
|
||||||
|
assert.equal(game.players[0].noField!.deployed, null);
|
||||||
|
assert.equal(game.players[1].shipped, true);
|
||||||
|
assert.equal(game.players[1].reserves, 15);
|
||||||
|
assert.equal(game.players[1].forces['imperial_basin:9'], 3);
|
||||||
|
assert.deepEqual(viewGame(game, 'a').basicAlliedShipment, {territory: 'Imperial Basin', normal: 1, elite: 2});
|
||||||
|
game = send(game, 'a', {type: 'move', forces: {'imperial_basin:9': 3},
|
||||||
|
eliteForces: {'imperial_basin:9': 2}, territory: 'polar_sink', sector: 0});
|
||||||
|
assert.equal(viewGame(game, 'a').basicAlliedShipment, null);
|
||||||
|
game = send(game, 'a', {type: 'endMovement'});
|
||||||
|
assert.equal(physicalAt(game, 'a', 'imperial_basin'), 2);
|
||||||
|
assert.equal(game.players[1].forces['polar_sink:0'], 3);
|
||||||
|
assert.equal(game.players[1].tanks, 0);
|
||||||
|
assert.equal(game.players[0].forces['imperial_basin:11'], 2);
|
||||||
|
});
|
||||||
|
|
||||||
|
void test('Basic allied No-Field visitors left in the territory lose only their physical and elite excess', () => {
|
||||||
|
let game = finishResponse(accept(offer(fixture())));
|
||||||
|
game = send(game, 'a', {type: 'endMovement'});
|
||||||
|
const recipient = game.players[1];
|
||||||
|
assert.equal(physicalAt(game, 'a', 'imperial_basin'), 2);
|
||||||
|
assert.equal(Object.values(recipient.elites!.forces).reduce((sum, n) => sum + n, 0), 1);
|
||||||
|
assert.equal(recipient.tanks, 3);
|
||||||
|
assert.equal(recipient.elites!.tanks, 2);
|
||||||
|
assert.equal(recipient.reserves, 15);
|
||||||
|
assert.equal(game.players[0].forces['imperial_basin:11'], 2);
|
||||||
|
assert.equal(viewGame(game, 'a').basicAlliedShipment, null);
|
||||||
|
});
|
||||||
|
|
||||||
|
void test('Basic allied No-Field zero does not create visitor casualties or disturb the elite baseline', () => {
|
||||||
|
let game = finishResponse(accept(offer(fixture(), 'zero'), 0));
|
||||||
|
assert.equal(game.players[0].noField!.lastShipped, 'zero');
|
||||||
|
assert.equal(game.players[1].shipped, true);
|
||||||
|
assert.equal(game.players[1].reserves, 18);
|
||||||
|
assert.equal(viewGame(game, 'a').basicAlliedShipment, null);
|
||||||
|
game = send(game, 'a', {type: 'endMovement'});
|
||||||
|
assert.equal(physicalAt(game, 'a', 'imperial_basin'), 2);
|
||||||
|
assert.equal(game.players[1].elites!.forces['imperial_basin:11'], 1);
|
||||||
|
assert.equal(game.players[1].tanks, 0);
|
||||||
|
assert.equal(game.players[0].forces['imperial_basin:11'], 2);
|
||||||
|
});
|
||||||
|
|
||||||
|
void test('declining the allied No-Field visit keeps payment, reserves and visitor receipt untouched', () => {
|
||||||
|
let game = offer(fixture());
|
||||||
|
game = send(game, 'a', {type: 'decision', event: game.richeseAllyOffer!.event, decline: true});
|
||||||
|
assert.equal(game.players[0].spice, 10);
|
||||||
|
assert.equal(game.players[0].noField!.lastShipped, null);
|
||||||
|
assert.equal(game.players[1].shipped, false);
|
||||||
|
assert.equal(game.players[1].reserves, 18);
|
||||||
|
assert.ok(game.basicAlliedShipment == null);
|
||||||
|
});
|
||||||
@@ -272,7 +272,7 @@ void test('stale acceptance and changed pending reserve allocation cannot cause
|
|||||||
assert.ok(k);
|
assert.ok(k);
|
||||||
conserve(g);
|
conserve(g);
|
||||||
});
|
});
|
||||||
void test('invalid payer, occupancy, repeated token, wrong turn and on-planet Fremen reserves reject atomically', () => {
|
void test('invalid payer, Advanced allied occupancy, wrong turn and on-planet Fremen reserves reject atomically', () => {
|
||||||
const g = fixture(),
|
const g = fixture(),
|
||||||
before = structuredClone(g);
|
before = structuredClone(g);
|
||||||
assert.throws(() => offer(g, 'five', 'e'), /ally payer/);
|
assert.throws(() => offer(g, 'five', 'e'), /ally payer/);
|
||||||
@@ -285,6 +285,7 @@ void test('invalid payer, occupancy, repeated token, wrong turn and on-planet Fr
|
|||||||
g.active = 'a';
|
g.active = 'a';
|
||||||
g.players[0].forces = { 'arrakeen:10': 1 };
|
g.players[0].forces = { 'arrakeen:10': 1 };
|
||||||
g.players[0].reserves = 19;
|
g.players[0].reserves = 19;
|
||||||
|
g.advanced = true;
|
||||||
assert.throws(() => offer(g), /occupied by your ally/);
|
assert.throws(() => offer(g), /occupied by your ally/);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
Reference in new issue
Block a user