Preserve Fremen revival grant opportunity for bot allies
Verify and publish checkpoint container / container (push) Successful in 10m54s
Verify and publish checkpoint container / container (push) Successful in 10m54s
This commit is contained in:
1 parent
825f903cce
commit
ac7db3cb35
7 files changed
+201
-2
No files matched your search
@@ -40,6 +40,7 @@ export function FremenRevivalGrant({
|
|||||||
if (!grant || game.status !== 'playing' || game.phase !== 4 ||
|
if (!grant || game.status !== 'playing' || game.phase !== 4 ||
|
||||||
me?.faction !== 'fremen' || me.ally !== grant.recipient) return null;
|
me?.faction !== 'fremen' || me.ally !== grant.recipient) return null;
|
||||||
const recipient = game.players.find((player) => player.id === grant.recipient)?.name ?? 'your ally';
|
const recipient = game.players.find((player) => player.id === grant.recipient)?.name ?? 'your ally';
|
||||||
|
const automatedAlly = game.players.find(player => player.id === grant.recipient);
|
||||||
const disabled = busy || !!game.truthtrance || !!game.nexusTraitors?.pending ||
|
const disabled = busy || !!game.truthtrance || !!game.nexusTraitors?.pending ||
|
||||||
!!grant.blocked || grant.granted;
|
!!grant.blocked || grant.granted;
|
||||||
return (
|
return (
|
||||||
@@ -50,6 +51,10 @@ export function FremenRevivalGrant({
|
|||||||
replaces their native ordinary rate rather than adding three. Ordinary total
|
replaces their native ordinary rate rather than adding three. Ordinary total
|
||||||
revival and special-force limits still apply.
|
revival and special-force limits still apply.
|
||||||
</p>
|
</p>
|
||||||
|
{!game.advanced && !game.revivalOrder && !group &&
|
||||||
|
(automatedAlly?.bot || automatedAlly?.autopilot) && !grant.granted && !grant.blocked && (
|
||||||
|
<p className="fine">When this grant would help your automated ally, it waits to revive forces until you grant it or select Ready for next phase.</p>
|
||||||
|
)}
|
||||||
<p className="fine">
|
<p className="fine">
|
||||||
Provisional current-rate policy: Recruits doubles the current granted rate, including applicable bonuses,
|
Provisional current-rate policy: Recruits doubles the current granted rate, including applicable bonuses,
|
||||||
before caps. Without other bonuses, three becomes six.
|
before caps. Without other bonuses, three becomes six.
|
||||||
|
|||||||
@@ -17,6 +17,17 @@ open mode gates.
|
|||||||
|
|
||||||
## Current checkpoint and work
|
## Current checkpoint and work
|
||||||
|
|
||||||
|
**Basic Fremen ally revival scheduling — 29 October. Prototyped / Partial.**
|
||||||
|
An automated mutual ally now waits for a useful human Fremen free-revival grant
|
||||||
|
in concurrent Basic Revival, until the grant resolves or the Fremen selects the
|
||||||
|
existing Ready action. Karama responses remain active; other bot actions do not
|
||||||
|
wait. The Fremen panel explains the choice. Genuine setup, battle casualties,
|
||||||
|
Nexus alliance, grant/prevention and all four automated profiles are covered;
|
||||||
|
the four-file related batch passes 40 cases, and type checking, lint and the
|
||||||
|
production build pass. Ordered Revival, larger alliances,
|
||||||
|
other opportunity races and deployed acceptance remain open.
|
||||||
|
[Scope](FREMEN_REVIVAL_GRANT.md#human-fremen-and-an-automated-ally--29-october-2026).
|
||||||
|
|
||||||
**Opt-in CHOAM market continuation — 29 October. Prototyped / Partial.**
|
**Opt-in CHOAM market continuation — 29 October. Prototyped / Partial.**
|
||||||
In supported Basic CHOAM/classic games, a human can finish a market and choose
|
In supported Basic CHOAM/classic games, a human can finish a market and choose
|
||||||
to skip later markets for that turn; an owner-only persistent control revokes
|
to skip later markets for that turn; an owner-only persistent control revokes
|
||||||
|
|||||||
@@ -1,5 +1,18 @@
|
|||||||
# Fremen allied free revival
|
# Fremen allied free revival
|
||||||
|
|
||||||
|
## Human Fremen and an automated ally — 29 October 2026
|
||||||
|
|
||||||
|
**Prototyped / Partial.** During concurrent Basic Revival, an automated mutual
|
||||||
|
ally waits to make its ordinary force return or finish Revival while a human
|
||||||
|
Fremen has an eligible grant that would increase that ally's remaining free
|
||||||
|
allowance and the ally has forces in the Tanks. The Fremen may use the existing
|
||||||
|
grant action; its ordinary Karama response still resolves before the ally acts.
|
||||||
|
The Fremen's existing Ready for next phase also releases the wait, as do an
|
||||||
|
accepted or prevented grant. Other bot actions and responses remain available.
|
||||||
|
The Fremen panel explains this scheduling choice. This is a provisional online
|
||||||
|
priority policy, not a printed rule or a new mandatory confirmation. Ordered
|
||||||
|
Revival, larger alliances and other races are not covered by this policy.
|
||||||
|
|
||||||
## Staged three-member alliance — 28 October 2026
|
## Staged three-member alliance — 28 October 2026
|
||||||
|
|
||||||
In the unexposed Basic classic group prototype, Fremen may select each of its
|
In the unexposed Basic classic group prototype, Fremen may select each of its
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ import { liveMovementExpressionPromises } from './movement-expression-promises';
|
|||||||
import { isAlliedShipmentVisit } from './allied-separation';
|
import { isAlliedShipmentVisit } from './allied-separation';
|
||||||
import { discoveryBotActions } from './discovery-options';
|
import { discoveryBotActions } from './discovery-options';
|
||||||
import { normalAuctionHandBlock } from './normal-auction';
|
import { normalAuctionHandBlock } from './normal-auction';
|
||||||
|
import { fremenRevivalGrantBlock } from './fremen-revival-grant';
|
||||||
import { atreidesFullPlanAction } from './atreides-full-plan';
|
import { atreidesFullPlanAction } from './atreides-full-plan';
|
||||||
import { liveMovementPromises } from './movement-promises';
|
import { liveMovementPromises } from './movement-promises';
|
||||||
import { ecazOccupyDialChoices, ecazOccupyOwnProfile, ecazOccupyPolicyActions } from './ecaz-occupy-options';
|
import { ecazOccupyDialChoices, ecazOccupyOwnProfile, ecazOccupyPolicyActions } from './ecaz-occupy-options';
|
||||||
@@ -60,6 +61,8 @@ import { strongholdBattleEffects } from './stronghold-battle';
|
|||||||
import {
|
import {
|
||||||
eliteRevivalRemaining,
|
eliteRevivalRemaining,
|
||||||
forceRevivalPrice,
|
forceRevivalPrice,
|
||||||
|
freeRevivalRemaining,
|
||||||
|
revivalPrevented,
|
||||||
paidForceRevivalCost,
|
paidForceRevivalCost,
|
||||||
} from './revival';
|
} from './revival';
|
||||||
import { controlsLeader, nativeAvailable } from './leader-control';
|
import { controlsLeader, nativeAvailable } from './leader-control';
|
||||||
@@ -4661,6 +4664,21 @@ export function botActions(g: GameView): Action[] {
|
|||||||
: [{ type: 'endMovement' }];
|
: [{ type: 'endMovement' }];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** In concurrent Basic Revival, let a human Fremen decide its existing grant
|
||||||
|
* before its automated ally spends or finishes. Ready is the normal way to
|
||||||
|
* finish that decision; responses and unrelated actions remain available. */
|
||||||
|
function waitsForHumanFremenGrant(g: Game, player: Game['players'][number]): boolean {
|
||||||
|
if (g.status !== 'playing' || g.advanced || g.phase !== 4 || g.revivalOrder ||
|
||||||
|
g.coalitionState || player.tanks <= 0 || revivalPrevented(g, player.id)) return false;
|
||||||
|
const fremen = g.players.find(seat => seat.faction === 'fremen');
|
||||||
|
if (!fremen || fremen.bot || fremen.autopilot || fremen.ally !== player.id ||
|
||||||
|
player.ally !== fremen.id || g.ready.includes(fremen.id) ||
|
||||||
|
fremenRevivalGrantBlock(g, fremen.id) !== null) return false;
|
||||||
|
const current = freeRevivalRemaining(g, player);
|
||||||
|
const granted = freeRevivalRemaining({ ...g, freeRevival: [...g.freeRevival, player.id] }, player);
|
||||||
|
return Math.min(player.tanks, granted) > Math.min(player.tanks, current);
|
||||||
|
}
|
||||||
|
|
||||||
export function runBots(state: Game, limit = 96): Game {
|
export function runBots(state: Game, limit = 96): Game {
|
||||||
const supplier = state.players.find((p) => p.faction === 'richese');
|
const supplier = state.players.find((p) => p.faction === 'richese');
|
||||||
const unopenedAllyWindow =
|
const unopenedAllyWindow =
|
||||||
@@ -4700,6 +4718,8 @@ export function runBots(state: Game, limit = 96): Game {
|
|||||||
actors.sort((a, b) => Number(b.faction === 'guild') - Number(a.faction === 'guild'));
|
actors.sort((a, b) => Number(b.faction === 'guild') - Number(a.faction === 'guild'));
|
||||||
for (const p of actors) {
|
for (const p of actors) {
|
||||||
for (const action of botActions(viewGame(g, p.id))) {
|
for (const action of botActions(viewGame(g, p.id))) {
|
||||||
|
if ((action.type === 'revive' || action.type === 'ready') && waitsForHumanFremenGrant(g, p))
|
||||||
|
continue;
|
||||||
try {
|
try {
|
||||||
next = applyAction(g, p.id, action);
|
next = applyAction(g, p.id, action);
|
||||||
break;
|
break;
|
||||||
|
|||||||
+4
-2
@@ -6553,7 +6553,7 @@ export const RULE_TOPICS: RuleTopic[] = [
|
|||||||
"During Revival, Fremen may offer the current ally its ordinary free-force rate of three. The grant replaces the native ordinary rate; current bonuses, total limits and separate special-force limits still apply. It never moves counters by itself.",
|
"During Revival, Fremen may offer the current ally its ordinary free-force rate of three. The grant replaces the native ordinary rate; current bonuses, total limits and separate special-force limits still apply. It never moves counters by itself.",
|
||||||
"The original owner, ally and turn remain bound while another player may prevent the grant with Karama. All-pass commits once. Under the provisional prevention policy, cancellation denies this grant for the current Revival, preserving the ally’s intrinsic free rate, Recruits and completed returns/payments. The next native Revival clears the denial.",
|
"The original owner, ally and turn remain bound while another player may prevent the grant with Karama. All-pass commits once. Under the provisional prevention policy, cancellation denies this grant for the current Revival, preserving the ally’s intrinsic free rate, Recruits and completed returns/payments. The next native Revival clears the denial.",
|
||||||
"Recruits doubles the current granted rate including applicable bonuses before caps. With no other bonus, three becomes six. Provisionally accept grants before or after the card with the same calculation: deduct recorded free usage, preserve all earlier total returns and paid classifications, and give no refund. Finish a pending transaction before declaring a grant.",
|
"Recruits doubles the current granted rate including applicable bonuses before caps. With no other bonus, three becomes six. Provisionally accept grants before or after the card with the same calculation: deduct recorded free usage, preserve all earlier total returns and paid classifications, and give no refund. Finish a pending transaction before declaring a grant.",
|
||||||
"The Fremen panel keeps status and unavailable reasons visible. Your ally’s normal revival panel supplies the actual remaining amount. Complete modes, broader combinations and final acceptance remain unfinished.",
|
"The Fremen panel keeps status and unavailable reasons visible. In concurrent Basic Revival an automated ally can wait for a useful grant until the Fremen grants it or finishes the phase; the existing Karama response still applies. Your ally’s normal revival panel supplies the actual remaining amount. Complete modes, broader combinations and final acceptance remain unfinished.",
|
||||||
"In the Basic group preview, Fremen can provisionally grant each ally the rate separately; each named declaration has its own Karama response. Group interactions remain incomplete."
|
"In the Basic group preview, Fremen can provisionally grant each ally the rate separately; each named declaration has its own Karama response. Group interactions remain incomplete."
|
||||||
],
|
],
|
||||||
"related": [
|
"related": [
|
||||||
@@ -6588,9 +6588,10 @@ export const RULE_TOPICS: RuleTopic[] = [
|
|||||||
{
|
{
|
||||||
"area": "AI",
|
"area": "AI",
|
||||||
"status": "Partial",
|
"status": "Partial",
|
||||||
"detail": "Existing-profile native/Recruits paths and staged group grants use the same projected legal permissions. Strategy and strength calibration remain deferred.",
|
"detail": "Existing-profile native/Recruits paths and staged group grants use the same projected legal permissions. In concurrent Basic Revival a mutual automated ally waits for a useful human Fremen grant until it is resolved or Fremen is Ready. Strategy and strength calibration remain deferred.",
|
||||||
"evidence": [
|
"evidence": [
|
||||||
"game/bots.ts",
|
"game/bots.ts",
|
||||||
|
"tests/fremen-revival-grant-bot-priority.test.ts",
|
||||||
"tests/recruits-controls.test.tsx",
|
"tests/recruits-controls.test.tsx",
|
||||||
"docs/FREMEN_REVIVAL_GRANT.md"
|
"docs/FREMEN_REVIVAL_GRANT.md"
|
||||||
]
|
]
|
||||||
@@ -6615,6 +6616,7 @@ export const RULE_TOPICS: RuleTopic[] = [
|
|||||||
"tests/fremen-revival-grant-engine.test.ts",
|
"tests/fremen-revival-grant-engine.test.ts",
|
||||||
"tests/fremen-revival-grant-quote.test.ts",
|
"tests/fremen-revival-grant-quote.test.ts",
|
||||||
"tests/fremen-revival-grant-controls.test.tsx",
|
"tests/fremen-revival-grant-controls.test.tsx",
|
||||||
|
"tests/fremen-revival-grant-bot-priority.test.ts",
|
||||||
"tests/recruits-engine.test.ts",
|
"tests/recruits-engine.test.ts",
|
||||||
"tests/recruits-controls.test.tsx"
|
"tests/recruits-controls.test.tsx"
|
||||||
]
|
]
|
||||||
|
|||||||
@@ -0,0 +1,140 @@
|
|||||||
|
import assert from 'node:assert/strict';
|
||||||
|
import test from 'node:test';
|
||||||
|
import { viewGame, type Game } from '../game/engine';
|
||||||
|
import { botActions, runBots } from '../game/bots';
|
||||||
|
import { DIFFICULTIES, type Difficulty } from '../game/bot-profiles';
|
||||||
|
import { actFremenRevivalGrant as act, advanceFremenRevivalGrant as advance,
|
||||||
|
createFremenRevivalGrantFixture, fremenRevivalGrantClean as clean,
|
||||||
|
fremenRevivalGrantPlayer as player, passFremenRevivalGrantResponses as pass } from './fixture-fremen-revival-grant';
|
||||||
|
|
||||||
|
let fixture: ReturnType<typeof createFremenRevivalGrantFixture> | undefined;
|
||||||
|
const source = () => fixture ??= createFremenRevivalGrantFixture();
|
||||||
|
|
||||||
|
/** The source uses real public Basic setup, an actual battle with six allied
|
||||||
|
* casualties and reciprocal Nexus consent. Handover uses the real seat action. */
|
||||||
|
function botAlly(difficulty: Difficulty) {
|
||||||
|
const f = source();
|
||||||
|
const game = act(structuredClone(f.game), f.ally, {type:'setAutopilot', difficulty});
|
||||||
|
assert.equal(game.advanced, false);
|
||||||
|
assert.equal(game.phase, 4);
|
||||||
|
assert.equal(player(game, f.fremen).ally, f.ally);
|
||||||
|
assert.equal(player(game, f.ally).ally, f.fremen);
|
||||||
|
assert.ok(player(game, f.ally).tanks >= 3);
|
||||||
|
assert.equal(viewGame(game, f.ally).revival.freeRemaining, 2);
|
||||||
|
assert.equal(viewGame(game, f.fremen).fremenRevivalGrant?.blocked, null);
|
||||||
|
return {f, game};
|
||||||
|
}
|
||||||
|
const ownChoices = (game: Game, id: string) => botActions(viewGame(game, id));
|
||||||
|
|
||||||
|
for (const difficulty of DIFFICULTIES) void test(
|
||||||
|
`Basic ${difficulty} ally waits for human Fremen, then uses the granted free revival`, () => {
|
||||||
|
const {f, game} = botAlly(difficulty);
|
||||||
|
const spice = player(game, f.ally).spice, tanks = player(game, f.ally).tanks;
|
||||||
|
const afterBot = runBots(game, 1);
|
||||||
|
assert.equal(player(afterBot, f.ally).spice, spice);
|
||||||
|
assert.equal(player(afterBot, f.ally).tanks, tanks);
|
||||||
|
assert.equal(player(afterBot, f.ally).revived, 0);
|
||||||
|
assert.ok(!afterBot.ready.includes(f.ally));
|
||||||
|
let next = act(afterBot, f.fremen, f.grant);
|
||||||
|
assert.equal(next.response?.kind, 'fremenRevivalGrant');
|
||||||
|
assert.equal(next.freeRevival.includes(f.ally), false);
|
||||||
|
next = pass(next);
|
||||||
|
assert.equal(viewGame(next, f.ally).revival.freeRemaining, 3);
|
||||||
|
const revival = ownChoices(next, f.ally).find(action => action.type === 'revive' && action.amount === 3);
|
||||||
|
assert.ok(revival, `${difficulty} should offer its legal three-force revival`);
|
||||||
|
next = advance(runBots(next, 1), clean);
|
||||||
|
assert.equal(player(next, f.ally).spice, spice);
|
||||||
|
assert.equal(player(next, f.ally).tanks, tanks - 3);
|
||||||
|
assert.equal(player(next, f.ally).freeForcesRevived, 3);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
void test('Fremen’s normal Ready releases the waiting bot without granting the benefit', () => {
|
||||||
|
const {f, game} = botAlly('Easy');
|
||||||
|
const waiting = runBots(game, 1);
|
||||||
|
assert.equal(player(waiting, f.ally).revived, 0);
|
||||||
|
assert.ok(!waiting.ready.includes(f.ally));
|
||||||
|
const next = act(waiting, f.fremen, {type:'ready'});
|
||||||
|
assert.ok(next.ready.includes(f.fremen));
|
||||||
|
assert.equal(next.freeRevival.includes(f.ally), false);
|
||||||
|
const resumed = runBots(next, 1);
|
||||||
|
assert.ok(player(resumed, f.ally).revived > 0 || resumed.ready.includes(f.ally));
|
||||||
|
});
|
||||||
|
|
||||||
|
void test('a real Karama prevention releases the waiting bot with its intrinsic free rate', () => {
|
||||||
|
const {f, game} = botAlly('Medium');
|
||||||
|
let next = act(game, f.fremen, f.grant);
|
||||||
|
assert.equal(next.response?.kind, 'fremenRevivalGrant');
|
||||||
|
next = act(next, f.observer, {type:'card', card:f.karama, mode:'cancel'});
|
||||||
|
assert.equal(next.response, null);
|
||||||
|
assert.ok(next.revivalRules?.fremenGrantBlocked?.includes(f.ally));
|
||||||
|
assert.equal(viewGame(next, f.ally).revival.freeRemaining, 2);
|
||||||
|
const resumed = runBots(next, 1);
|
||||||
|
assert.ok(player(resumed, f.ally).revived > 0 || resumed.ready.includes(f.ally));
|
||||||
|
});
|
||||||
|
|
||||||
|
void test('the waiting ally can still pass its actual Karama response to the Fremen grant', () => {
|
||||||
|
const {f, game} = botAlly('Easy');
|
||||||
|
// Stage one conserved physical card exchange solely to make the allied
|
||||||
|
// bot, rather than the original Emperor, own the response card.
|
||||||
|
const ally = player(game, f.ally), observer = player(game, f.observer);
|
||||||
|
const karama = observer.hand.findIndex(card => card.id === f.karama);
|
||||||
|
assert.ok(karama >= 0 && ally.hand.length > 0);
|
||||||
|
[ally.hand[0], observer.hand[karama]] = [observer.hand[karama], ally.hand[0]];
|
||||||
|
let next = act(game, f.fremen, f.grant);
|
||||||
|
assert.equal(next.response?.kind, 'fremenRevivalGrant');
|
||||||
|
next = runBots(next, 1);
|
||||||
|
assert.equal(next.response, null);
|
||||||
|
assert.equal(next.freeRevival.includes(f.ally), true);
|
||||||
|
});
|
||||||
|
|
||||||
|
void test('a bot Fremen does not make its bot ally wait for a human grant', () => {
|
||||||
|
const {f, game} = botAlly('Easy');
|
||||||
|
const next = act(game, f.fremen, {type:'setAutopilot', difficulty:'Easy'});
|
||||||
|
// Staged actor iteration order puts the allied bot first; public storm order
|
||||||
|
// and the genuine prior game state are otherwise untouched.
|
||||||
|
next.players = [player(next, f.ally), player(next, f.fremen), player(next, f.observer)];
|
||||||
|
const resumed = runBots(next, 1);
|
||||||
|
assert.ok(player(resumed, f.ally).revived > 0 || resumed.ready.includes(f.ally));
|
||||||
|
});
|
||||||
|
|
||||||
|
void test('a completed normal revival does not hold the bot’s Ready for an unusable grant', () => {
|
||||||
|
// This fixture actually completes its paid/free three-force return before
|
||||||
|
// handover; the remaining ordinary revival allowance is genuinely zero.
|
||||||
|
const f = createFremenRevivalGrantFixture({priorRevival:true});
|
||||||
|
const game = act(f.game, f.ally, {type:'setAutopilot', difficulty:'Easy'});
|
||||||
|
assert.equal(viewGame(game, f.ally).revival.forcesRemaining, 0);
|
||||||
|
const resumed = runBots(game, 1);
|
||||||
|
assert.ok(resumed.ready.includes(f.ally));
|
||||||
|
});
|
||||||
|
|
||||||
|
void test('two remaining Tanks forces do not hold a bot that already has two free revivals', () => {
|
||||||
|
const {f, game} = botAlly('Easy');
|
||||||
|
const ally = player(game, f.ally);
|
||||||
|
// Labelled physical-custody staging: return the surplus ordinary Tanks
|
||||||
|
// counters to reserves, preserving the player's total force inventory.
|
||||||
|
const surplus = ally.tanks - 2;
|
||||||
|
assert.ok(surplus > 0);
|
||||||
|
ally.tanks -= surplus;
|
||||||
|
ally.reserves += surplus;
|
||||||
|
const reserves = ally.reserves, spice = ally.spice;
|
||||||
|
assert.equal(viewGame(game, f.ally).revival.freeRemaining, 2);
|
||||||
|
assert.equal(viewGame(game, f.fremen).fremenRevivalGrant?.blocked, null);
|
||||||
|
const resumed = runBots(game, 1);
|
||||||
|
assert.equal(player(resumed, f.ally).revived, 2);
|
||||||
|
assert.equal(player(resumed, f.ally).tanks, 0);
|
||||||
|
assert.equal(player(resumed, f.ally).reserves, reserves + 2);
|
||||||
|
assert.equal(player(resumed, f.ally).spice, spice);
|
||||||
|
});
|
||||||
|
|
||||||
|
void test('a bot barred from normal revival can Ready without waiting for a futile grant', () => {
|
||||||
|
const {f, game} = botAlly('Easy');
|
||||||
|
// Stage the existing current-turn native prevention receipt, without
|
||||||
|
// changing the genuine alliance, Tanks, wallet or Fremen grant state.
|
||||||
|
game.revivalPrevention = {player:f.ally, turn:game.turn};
|
||||||
|
assert.equal(viewGame(game, f.ally).revival.prevented, true);
|
||||||
|
const resumed = runBots(game, 1);
|
||||||
|
assert.ok(resumed.ready.includes(f.ally));
|
||||||
|
assert.equal(player(resumed, f.ally).revived, 0);
|
||||||
|
assert.equal(player(resumed, f.ally).tanks, player(game, f.ally).tanks);
|
||||||
|
});
|
||||||
@@ -64,6 +64,14 @@ void test('native Fremen grant renders its ally and submits the original grant a
|
|||||||
assert.deepEqual(grant(game), [{ type: 'grantRevival' }]);
|
assert.deepEqual(grant(game), [{ type: 'grantRevival' }]);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
void test('the Fremen sees how an automated ally resumes without an extra confirmation', () => {
|
||||||
|
const game = projected();
|
||||||
|
game.players.find(player => player.id === 'a')!.autopilot = 'Easy';
|
||||||
|
assert.match(markup(game), /waits to revive forces until you grant it or select Ready for next phase/);
|
||||||
|
game.fremenRevivalGrant!.granted = true;
|
||||||
|
assert.doesNotMatch(markup(game), /waits to revive forces/);
|
||||||
|
});
|
||||||
|
|
||||||
void test('the persistent grant guide names native three, provisional Recruits current rates, prior free use and no refunds', () => {
|
void test('the persistent grant guide names native three, provisional Recruits current rates, prior free use and no refunds', () => {
|
||||||
const html = markup(projected());
|
const html = markup(projected());
|
||||||
assert.match(html, /replaces their native ordinary rate rather than adding three/);
|
assert.match(html, /replaces their native ordinary rate rather than adding three/);
|
||||||
|
|||||||
Reference in new issue
Block a user