Connect the Basic Ecaz new-ally Duke loan
This commit is contained in:
1 parent
c28e24b5fa
commit
2d0f20b69c
11 files changed
+106
-25
No files matched your search
@@ -23,7 +23,7 @@ import { TERRITORIES } from '@/game/board';
|
||||
import { faction } from '@/game/catalog';
|
||||
|
||||
export const AMBASSADOR_COVERAGE =
|
||||
'Development coverage: Ambassador placement, Ecaz’s direct Duke acquisition and consensual alliance, Emperor, Atreides, Harkonnen, CHOAM, Ixian, Richese, Fremen, Guild and Tleilaxu entry effects are supported, plus Bene Gesserit copies. The Tleilaxu effect offers physical force returns or a first-death own-leader revival. Ordinary allowance and income accounting, repeat-death and shared-leader cases remain for refinement. Duke loan choices and some competing arrival reactions remain unfinished; full Ecaz starts remain disabled.';
|
||||
'Development coverage: Ambassador placement, Ecaz’s direct Duke acquisition and consensual alliance, Emperor, Atreides, Harkonnen, CHOAM, Ixian, Richese, Fremen, Guild and Tleilaxu entry effects are supported, plus Bene Gesserit copies. The Tleilaxu effect offers physical force returns or a first-death own-leader revival. Basic Ecaz can optionally lend Duke to a newly accepted ally, with provisional one-battle/turn-end tenure. Ordinary revival accounting, repeat-death/shared-leader cases, Advanced loans and competing arrival reactions remain for refinement; full Ecaz starts remain disabled.';
|
||||
function tokenLocation(token: AmbassadorToken) {
|
||||
if (token.zone === 'placed')
|
||||
return `Placed in ${TERRITORIES.find((t) => t.id === token.location)?.name ?? 'a stronghold'}`;
|
||||
|
||||
@@ -32,7 +32,7 @@ export function EcazEntry({
|
||||
!entry ||
|
||||
game.decision?.kind !== 'ecazAmbassador' ||
|
||||
game.decision.player !== game.me ||
|
||||
!['offer', 'allianceReply', 'copy', 'cards', 'move', 'ship', 'revival'].includes(
|
||||
!['offer', 'allianceReply', 'loan', 'copy', 'cards', 'move', 'ship', 'revival'].includes(
|
||||
entry.stage,
|
||||
)
|
||||
)
|
||||
@@ -141,7 +141,8 @@ export function EcazEntry({
|
||||
<p className="m-0 text-sm leading-6">
|
||||
Both factions must be unallied. The entrant chooses whether to
|
||||
accept. The triggered Ambassador returns to supply even if the
|
||||
offer is refused. Duke loans remain unfinished.
|
||||
offer is refused. In Basic, an accepted alliance can be followed
|
||||
by an optional Duke loan when the disc is available.
|
||||
</p>
|
||||
</>
|
||||
) : (
|
||||
@@ -194,8 +195,8 @@ export function EcazEntry({
|
||||
<p className="m-0 text-sm leading-6">
|
||||
{game.players.find((p) => p.id === entry.owner)?.name} offers you an
|
||||
alliance. Accepting activates both factions’ alliance abilities
|
||||
immediately. Duke Vidal is not included in this offer. Your
|
||||
remaining actions resume after your reply.
|
||||
immediately. In Basic, Ecaz can then optionally lend an available
|
||||
Duke Vidal. Your remaining actions resume after those choices.
|
||||
</p>
|
||||
<Button
|
||||
className={buttonClass}
|
||||
@@ -213,6 +214,19 @@ export function EcazEntry({
|
||||
Refuse alliance
|
||||
</Button>
|
||||
</>
|
||||
) : entry.stage === 'loan' ? (
|
||||
<>
|
||||
<p className="m-0 text-sm leading-6">
|
||||
Lend Duke Vidal to your new ally for this turn, or continue without
|
||||
lending him. Basic prototype: one battle; an unused living Duke is
|
||||
set aside at turn end. Return and capture interactions remain provisional.
|
||||
</p>
|
||||
<Button className={buttonClass} disabled={busy || !!entry.dukeLoan?.blocked}
|
||||
onClick={() => send({ loan: true })}>Lend Duke Vidal to the new ally</Button>
|
||||
{entry.dukeLoan?.blocked && <p>{entry.dukeLoan.blocked}</p>}
|
||||
<Button variant="outline" className={buttonClass} disabled={busy}
|
||||
onClick={() => send({ loan: false })}>Continue without lending Duke</Button>
|
||||
</>
|
||||
) : entry.stage === 'copy' ? (
|
||||
<>
|
||||
<p className="m-0 text-sm leading-6">
|
||||
|
||||
@@ -38,6 +38,12 @@ first-death own leader for free instead of returning forces. Private choices,
|
||||
zero price and legal AI are connected. Independent allowance treatment is
|
||||
provisional; repeat-death/shared-disc and income interactions remain for refinement.
|
||||
One Basic arrival-and-bot smoke passes.
|
||||
**Basic Ecaz new-ally Duke loan.** After an Ambassador alliance is accepted,
|
||||
Ecaz can lend the available existing disc to that new ally or continue without
|
||||
lending it. Owned controls and legal AI are connected; the entrant resumes once.
|
||||
One-battle use and turn-end set-aside expiry are provisional first-version policies.
|
||||
One direct Basic alliance-and-loan smoke passes; no broad suite was run.
|
||||
|
||||
|
||||
|
||||
**7 October 2026 — five pending rulings resolved by the user.** The user
|
||||
|
||||
@@ -35,7 +35,7 @@ The [base Harkonnen capture rules, p. 17](https://www.gf9games.com/dunegame/wp-c
|
||||
| Moritani acquisition from captured or ghola custody | Ecaz’s Ambassador explicitly excludes both; Moritani’s acquisition excludes only Tanks and says to take Duke from any controller. Do not silently copy the Ecaz exclusions as a confirmed Moritani rule. The interaction with concealed capture and the apparent ghola restriction needs an explicit supported policy. |
|
||||
| Duke used in a lost battle against Harkonnen | One-battle tenure and postbattle capture both apply. No retrieved Duke-specific text orders release versus selection into the capture pool. Removing him first changes the random pool; keeping him first can change custody. Gate this combination until resolved rather than choosing by handler order. |
|
||||
| Returning a captured Duke | Native identity is Ecaz, but temporary prior custody may be Moritani or a loaned ally. Base capture return plus Duke’s special acquisition restrictions do not explicitly settle active-control versus set-aside destination in every combination. Preserve prior custody rather than discarding that information. |
|
||||
| Loan to the new Ecaz ally | The printed loan lasts this turn. No retrieved passage resolves unused-loan return destination, an alliance breaking mid-loan, or one-battle tenure for a non-Moritani/non-Ecaz recipient. Preserve owner/controller/loan expiry separately and gate unresolved combinations. |
|
||||
| Loan to the new Ecaz ally | The printed loan lasts this turn. The 8 October Basic-first prototype now offers the loan after an accepted Ambassador alliance, with the same physical disc. One-battle tenure and unused living turn-end set-aside are explicitly provisional implementation policies. Advanced, capture and alliance-break interactions remain for refinement; no publisher clarification is claimed. |
|
||||
| Ecaz co-occupied battles | Ecaz’s Occupy rules distinguish participating forces from the faction chosen to fight. Duke eligibility excludes battles involving Ecaz, but no direct combined example decides every Ecaz-ally designation case. Ordinary faction-pair battle lists alone do not certify the full Occupy interaction. |
|
||||
| Revival alternatives | Ecaz’s Tleilaxu Ambassador effect and sharing an Ambassador benefit may interact with the absolute Ecaz-only restriction. No retrieved specific exception permits another faction to revive Duke. Keep that restriction unless a primary clarification establishes otherwise. |
|
||||
|
||||
|
||||
@@ -4,6 +4,20 @@ Integrated 7 September 2026. Ecaz can now choose **Acquire Duke Vidal for Ecaz**
|
||||
|
||||
The direct acquisition and tenure follow the publisher’s [Ecaz & Moritani rules, pp.7–9](https://www.gf9games.com/dune/wp-content/uploads/EcazMoritani-Rulebook-LOWRES.pdf). Fresh indexed publisher retrieval rechecked eligibility outside Tanks, captured or ghola custody. The prior [Duke source audit](DUKE_VIDAL_RULES.md) and [remaining effects readiness review](ECAZ_REMAINING_EFFECTS_READINESS.md) record the separate unresolved combinations. This implements the direct self-acquisition choice; it does not certify the token’s alliance and loan alternatives, full Ecaz or a complete expansion game.
|
||||
|
||||
## Basic new-ally loan first version — 8 October 2026
|
||||
|
||||
After the reusable Ambassador alliance is accepted in Basic, Ecaz receives a
|
||||
separate optional loan choice when Duke is available. The new ally controls
|
||||
the same disc; no native leader or Traitor card is created. Ecaz may decline,
|
||||
leaving the disc unchanged. The original entrant resumes after this choice.
|
||||
Private decision ownership and a minimal legal bot choice are connected.
|
||||
|
||||
The first version uses one-battle tenure and sets an unused living loan aside
|
||||
at turn end. These are labelled implementation policies under the Basic-first
|
||||
instruction, not a publisher clarification of return destination or mid-loan
|
||||
alliance changes. Advanced loans and exceptional custody remain for later work.
|
||||
A direct Basic arrival → consent → legal bot loan → resumed entrant smoke passes.
|
||||
|
||||
## Rules and authoritative execution
|
||||
|
||||
`game/ecaz-duke-acquisition.ts` quotes a detached next `DukeState` using the existing custody helper. It requires a current seated Ecaz owner and the existing canonical shared disc. It preserves identity, death history and `usedAt`; it never appends a leader to a native roster, adds a Traitor card, manufactures a replacement disc, samples randomness or grants new battle-use permissions. Starting-force initialization remains the normal disc-creation boundary.
|
||||
|
||||
@@ -1911,6 +1911,8 @@ function policyActions(g: GameView): Action[] {
|
||||
me.noField.deployed.location.sector !== g.storm)
|
||||
);
|
||||
if (entry.stage === 'allianceReply') return [{ ...action, accept: true }];
|
||||
if (entry.stage === 'loan')
|
||||
return [{ ...action, loan: !!entry.dukeLoan && !entry.dukeLoan.blocked }];
|
||||
if (entry.stage === 'revival') {
|
||||
const revival = entry.revival;
|
||||
if (!revival || revival.blocked) return [{ ...action, decline: true }];
|
||||
|
||||
+5
-4
@@ -76,13 +76,13 @@ export function consumeDuke(state: DukeState): DukeState {
|
||||
|
||||
/**
|
||||
* End-turn custody only. Ecaz's ordinary Ambassador acquisition lasts until
|
||||
* battle use; the distinct Ecaz Nexus Cunning acquisition lasts this turn.
|
||||
* Allied loans and captured/ghola release destinations need a verified return contract.
|
||||
* battle use; Ecaz Nexus Cunning and the Basic Ambassador ally loan last this
|
||||
* turn. The prototype sets an unused loan aside, pending return-policy refinement.
|
||||
*/
|
||||
export function expireDuke(state: DukeState, turn: number): DukeState {
|
||||
validTurn(turn);
|
||||
if (
|
||||
(state.source === 'moritani' || state.source === 'ecazNexus') &&
|
||||
(state.source === 'moritani' || state.source === 'ecazNexus' || state.source === 'ally') &&
|
||||
state.acquiredTurn !== null &&
|
||||
state.acquiredTurn <= turn &&
|
||||
!state.leader.dead &&
|
||||
@@ -116,8 +116,9 @@ export const DUKE_VIDAL_RULES = Object.freeze({
|
||||
revival: 'ecaz-only-set-aside',
|
||||
ambassadorAcquisition: 'ecaz-self-implemented',
|
||||
ambassadorAlliance: 'consent-implemented',
|
||||
ambassadorNewAllyLoan: 'basic-prototype',
|
||||
ambassadorAllyAcquisition: 'unresolved',
|
||||
allyLoanReturn: 'unresolved',
|
||||
allyLoanReturn: 'basic-set-aside-provisional',
|
||||
sourceAudit: 'docs/DUKE_VIDAL_RULES.md',
|
||||
}),
|
||||
});
|
||||
+42
-4
@@ -1598,6 +1598,7 @@ export type Game = {
|
||||
stage:
|
||||
| 'offer'
|
||||
| 'allianceReply'
|
||||
| 'loan'
|
||||
| 'copy'
|
||||
| 'cards'
|
||||
| 'income'
|
||||
@@ -16395,6 +16396,18 @@ function ecazAllianceQuote(g: Game, owner: string, entrant: string) {
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
function ecazAmbassadorLoanBlock(g: Game, owner: Player, entrant: Player) {
|
||||
if (g.advanced) return 'This first-version Duke loan is available in Basic.';
|
||||
if (owner.ally !== entrant.id || entrant.ally !== owner.id)
|
||||
return 'The new Ecaz alliance is no longer current.';
|
||||
const duke = g.dukeVidal;
|
||||
if (!duke || duke.leader.id !== DUKE_VIDAL_ID ||
|
||||
duke.leader.faction !== 'ecaz' || duke.leader.strength !== 6 ||
|
||||
duke.leader.dead || duke.leader.concealed ||
|
||||
duke.leader.capturedBy || duke.leader.gholaBy)
|
||||
return 'Duke Vidal is unavailable for this loan.';
|
||||
return duke.controller === owner.id ? null : ecazDukeAcquisitionBlock(g, owner.id);
|
||||
}
|
||||
function ecazAllianceIntegrity(g: Game) {
|
||||
const entry = g.pendingAmbassador;
|
||||
const continuation = g.pendingTreacheryDiscard?.continuation;
|
||||
@@ -16414,7 +16427,7 @@ function ecazAllianceIntegrity(g: Game) {
|
||||
);
|
||||
return;
|
||||
}
|
||||
if (entry.stage !== 'allianceReply') return;
|
||||
if (entry.stage !== 'allianceReply' && entry.stage !== 'loan') return;
|
||||
const token = g.ecazAmbassadors?.tokens.find((t) => t.id === entry.token);
|
||||
requireRule(
|
||||
g.status === 'playing' &&
|
||||
@@ -16424,18 +16437,21 @@ function ecazAllianceIntegrity(g: Game) {
|
||||
typeof entry.event === 'string' &&
|
||||
entry.event.length > 0 &&
|
||||
entry.effect === 'ecaz' &&
|
||||
entry.beneficiary === entry.entrant &&
|
||||
entry.beneficiary === (entry.stage === 'loan' ? entry.owner : entry.entrant) &&
|
||||
token?.effect === 'ecaz' &&
|
||||
token.zone === 'supply' &&
|
||||
token.location === null &&
|
||||
controls.some(
|
||||
(control) =>
|
||||
control?.decision?.kind === 'ecazAmbassador' &&
|
||||
control.decision.player === entry.entrant,
|
||||
control.decision.player === (entry.stage === 'loan' ? entry.owner : entry.entrant),
|
||||
),
|
||||
'The Ecaz alliance reply has lost its original Ambassador or decision owner.',
|
||||
);
|
||||
ecazAllianceQuote(g, entry.owner, entry.entrant);
|
||||
if (entry.stage === 'allianceReply') ecazAllianceQuote(g, entry.owner, entry.entrant);
|
||||
else requireRule(getPlayer(g, entry.owner).ally === entry.entrant &&
|
||||
getPlayer(g, entry.entrant).ally === entry.owner,
|
||||
'The Duke loan lost its accepted Ecaz alliance.');
|
||||
}
|
||||
function decideAmbassador(g: Game, p: Player, action: Action) {
|
||||
const entry = g.pendingAmbassador;
|
||||
@@ -16452,6 +16468,19 @@ function decideAmbassador(g: Game, p: Player, action: Action) {
|
||||
p.id === (entry.stage === 'offer' ? owner.id : entry.beneficiary),
|
||||
'The Ambassador choice belongs to another player.',
|
||||
);
|
||||
if (entry.stage === 'loan') {
|
||||
requireRule(typeof action.loan === 'boolean',
|
||||
'Choose whether to lend Duke Vidal to the new ally.');
|
||||
if (action.loan) {
|
||||
const blocked = ecazAmbassadorLoanBlock(g, owner, entrant);
|
||||
requireRule(!blocked, blocked ?? 'This Duke loan is unavailable.');
|
||||
g.dukeVidal = acquireDuke(g.dukeVidal!, entrant.id, g.turn, 'ally');
|
||||
log(g, `${owner.name} lent Duke Vidal to ${entrant.name} for this turn. The Basic prototype allows one battle; an unused living Duke is set aside at turn end.`,
|
||||
{ faction: 'ecaz', name: 'Ambassador Duke loan' });
|
||||
}
|
||||
finishAmbassador(g);
|
||||
return;
|
||||
}
|
||||
if (entry.stage === 'allianceReply') {
|
||||
requireRule(
|
||||
typeof action.accept === 'boolean',
|
||||
@@ -16473,6 +16502,12 @@ function decideAmbassador(g: Game, p: Player, action: Action) {
|
||||
`${owner.name} and ${entrant.name} formed an alliance through the Ecaz Ambassador. Both were unallied and ${entrant.name} accepted. Their alliance abilities apply immediately; the entrant’s remaining actions resume.`,
|
||||
{ faction: 'ecaz', name: 'Ambassador alliance' },
|
||||
);
|
||||
if (!ecazAmbassadorLoanBlock(g, owner, entrant)) {
|
||||
entry.stage = 'loan';
|
||||
entry.beneficiary = owner.id;
|
||||
g.decision = { kind: 'ecazAmbassador', player: owner.id };
|
||||
return;
|
||||
}
|
||||
} else
|
||||
log(
|
||||
g,
|
||||
@@ -32060,6 +32095,9 @@ export function viewGame(state: Game, id: string) {
|
||||
id === owner.id
|
||||
? { blocked: ambassadorEffectBlock(g, 'ecaz', owner, entrant) }
|
||||
: null,
|
||||
dukeLoan:
|
||||
entry.stage === 'loan' && id === owner.id
|
||||
? { blocked: ecazAmbassadorLoanBlock(g, owner, entrant) } : null,
|
||||
beneficiaries:
|
||||
entry.stage === 'offer' && id === owner.id
|
||||
? g.players
|
||||
|
||||
+5
-5
@@ -1316,9 +1316,9 @@ export const RULE_TOPICS: RuleTopic[] = [
|
||||
'The current closing order is CHOAM market, deferred technology income, then Ecaz placement. Income received at that point can fund placement. This combined order is an implementation choice, not a priority established by a combined official FAQ.',
|
||||
'The first placement costs one spice, the second two, and so on that turn. A declaration opens a Karama response before spice is paid or the token moves. Canceling stops the remaining placement opportunity for that turn; previous placements and payments remain.',
|
||||
'Finish Ambassador placement advances the phase. On a later turn the price begins at one again and the earlier placement block expires. Pending declarations survive reload without charging twice.',
|
||||
'The custody model returns destroyed tokens to supply and returns Ecaz after its trigger. Supported entry effects set regular tokens aside, permanently remove Bene Gesserit after its trigger, and replenish only after all five random tokens have triggered. Storm crossings return tokens from exposed Arrakeen and Carthag after the Shield Wall is destroyed. A Lasgun–shield explosion returns a token only if the explosion actually resolves; an overriding traitor victory leaves it placed. Destruction does not count toward the five triggers. The reusable Ecaz Ambassador can acquire Duke directly for Ecaz; it also offers a consensual alliance, while Duke loans remain unfinished.',
|
||||
'When an eligible entrant reaches the reusable Ecaz token, Ecaz may choose Acquire Duke Vidal for Ecaz if the shared disc is available. The token immediately returns to supply, its five random companions are unchanged, and the entrant resumes. Ecaz keeps an unused Duke across turns until battle use or Moritani acquisition. Already controlling him, assigning this effect to an ally, loans and Advanced Harkonnen custody remain visibly unavailable while their interactions are unfinished.',
|
||||
'Instead of acquiring Duke, unallied Ecaz can offer an alliance to an unallied entrant. The entrant accepts or refuses, then resumes its remaining actions. The triggered token returns to supply in either case. Acceptance activates both factions’ alliance abilities immediately and leaves Duke unchanged. An unavailable Duke does not prevent this alliance choice. Duke loans remain unfinished pending their custody ruling.',
|
||||
'Destroyed tokens return to supply; triggered regular tokens are set aside, Bene Gesserit is permanently removed, and replenishment waits for all five random tokens. Storm crossings and resolved explosions use destruction rather than trigger accounting. The reusable Ecaz token returns to supply after Duke acquisition or an alliance proposal. Basic now also offers a provisional new-ally Duke loan after consent.',
|
||||
'When an eligible entrant reaches the reusable Ecaz token, Ecaz may acquire the available shared Duke. The token returns to supply and its five companions are unchanged. Unused Ecaz control persists until battle use or Moritani acquisition. Basic also connects the separate optional new-ally loan after an accepted Ambassador alliance; standing ally assignment and exceptional capture remain unfinished.',
|
||||
'Unallied Ecaz can instead propose an alliance to an unallied entrant. The entrant accepts or refuses; the token returns to supply either way. Acceptance activates alliance abilities immediately. In Basic, Ecaz may then lend an available Duke to this newly allied faction or continue without a loan. The first-version loan allows one battle and sets an unused living Duke aside at turn end; those return/tenure details are provisional, not a resolved publisher ruling. An unavailable Duke never prevents alliance consent.',
|
||||
'When another eligible faction enters a marked stronghold, Ecaz may trigger its Ambassador for itself or its current ally, or leave the token in place. Ecaz itself, its ally, advisors and the faction matching the physical Ambassador do not trigger it. Entries with competing arrival reactions remain blocked until their ordering is implemented; a rejected entry does not spend or move its pieces.',
|
||||
'Emperor grants five bank spice. Atreides records the entrant’s current Treachery hand privately for the beneficiary; Harkonnen records one randomly selected held Traitor identity, or Face Dancer identity for a Tleilaxu entrant. These snapshots remain in the recipient’s private inspection history, identify when they were taken, and do not become a live view of later changes. Opening, enlarging or closing them needs no game acknowledgement.',
|
||||
'CHOAM lets the beneficiary choose any available hand cards, including none, for three spice each. Ixians require one available card to discard before drawing its replacement. Card controls respect projected reservations. After committing an Ixian exchange, a voluntary interruption must leave a card available for its required discard; a queued Truthtrance counts as already committed. A Bene Gesserit trigger permanently removes its token, then the beneficiary chooses an available effect outside the original supply for this group; unsupported copies remain visibly blocked.',
|
||||
@@ -1354,7 +1354,7 @@ export const RULE_TOPICS: RuleTopic[] = [
|
||||
area: 'Documentation',
|
||||
status: 'Partial',
|
||||
detail:
|
||||
'Placement, supported entry decisions, private historical inspections, inventory lifecycle and all eleven effect descriptions are available. The Ecaz guide distinguishes consent, token commitment and Duke loans awaiting a custody ruling. Complete timing and interaction coverage remains open.',
|
||||
'Placement, entry choices, private inspections, token inventory and all eleven effect guides are available. The Basic new-ally Duke loan and its provisional tenure policy are distinguished from Advanced and exceptional-custody work. Complete timing and interaction refinement remains open.',
|
||||
},
|
||||
{
|
||||
area: 'Verification',
|
||||
@@ -2840,7 +2840,7 @@ export const RULE_TOPICS: RuleTopic[] = [
|
||||
'Karama can prevent Moritani from acquiring Vidal. That cancellation does not remove him once Moritani already controls him.',
|
||||
'Ecaz gains Vidal through its Ecaz Ambassador if he is neither in the Tanks, captured nor a ghola. Ecaz keeps him until battle use or Moritani takes him. The Ambassador can instead form a consensual alliance when both factions are unallied; Ecaz may then lend Vidal to that new ally for the turn.',
|
||||
'Only Ecaz may revive Vidal, including with Ghola Treachery. Ecaz may revive him for five spice regardless of how many of its leaders are in the Tanks. Five spice is his special revival cost; his printed battle strength is six. Ecaz may begin ordinary leader revival when five leaders are in the Tanks, counting Vidal, even if it still holds another leader.',
|
||||
'The current implementation supports Moritani acquisition, direct Ecaz Ambassador self-acquisition, consensual Ambassador alliances, one-battle use and Ecaz-only paid or physical Ghola revival to living set-aside custody. Native ordinary-disc skills can coexist with a separately controlled Duke, without assigning him a skill. Duke loans, reacquisition while already controlled, shared skill assignment and captured-leader interactions remain unfinished. This shared component does not enable complete Ecaz or Moritani games. Inspection does not commit a battle plan.',
|
||||
'Current development support includes Moritani acquisition, Ecaz Ambassador self-acquisition, consensual alliances, a Basic optional new-ally loan, one-battle use, and Ecaz-only revival to living set-aside custody. The new loan uses provisional one-battle and unused-living turn-end set-aside policies. Native ordinary-disc skills coexist with Duke without assigning him a skill. Advanced loans, shared skill assignment and captured-leader interactions remain for later work; this does not certify complete modes.',
|
||||
],
|
||||
related: [
|
||||
'faction-ecaz',
|
||||
|
||||
@@ -118,16 +118,16 @@ void test('single-battle consumption releases custody while retaining alive or d
|
||||
}
|
||||
});
|
||||
|
||||
void test('turn end releases only Moritani tenure and preserves Ecaz holdings and unresolved allied loans', () => {
|
||||
void test('turn end releases Moritani tenure and allied loans while preserving unused Ecaz holdings', () => {
|
||||
for (const source of ['moritani', 'ally', 'ecaz'] as const) {
|
||||
const state = acquireDuke(createDukeVidal(), 'holder', 4, source);
|
||||
const earlier = expireDuke(state, 3);
|
||||
assert.deepEqual(earlier, state);
|
||||
for (const turn of [4, 5]) {
|
||||
const expired = expireDuke(state, turn);
|
||||
assert.equal(expired.controller, source === 'moritani' ? null : 'holder');
|
||||
assert.equal(expired.acquiredTurn, source === 'moritani' ? null : 4);
|
||||
assert.equal(expired.source, source === 'moritani' ? null : source);
|
||||
assert.equal(expired.controller, source === 'ecaz' ? 'holder' : null);
|
||||
assert.equal(expired.acquiredTurn, source === 'ecaz' ? 4 : null);
|
||||
assert.equal(expired.source, source === 'ecaz' ? source : null);
|
||||
assert.deepEqual(expired.leader, state.leader);
|
||||
}
|
||||
assert.equal(state.controller, 'holder');
|
||||
|
||||
@@ -89,11 +89,17 @@ function propose(g: Game) {
|
||||
return applyAction(g, 'ec', proposal(g));
|
||||
}
|
||||
function answer(g: Game, accept: boolean) {
|
||||
return applyAction(json(g), 'in', {
|
||||
let done = applyAction(json(g), 'in', {
|
||||
type: 'decision',
|
||||
event: g.pendingAmbassador!.event,
|
||||
accept,
|
||||
});
|
||||
// These scenarios exercise alliance-only continuation, declining the optional loan.
|
||||
if (done.pendingAmbassador?.stage === 'loan')
|
||||
done = applyAction(done, 'ec', {
|
||||
type: 'decision', event: done.pendingAmbassador.event, loan: false,
|
||||
});
|
||||
return done;
|
||||
}
|
||||
function reject(g: Game, id: string, action: Action) {
|
||||
const before = structuredClone(g);
|
||||
@@ -372,7 +378,7 @@ for (const level of DIFFICULTIES)
|
||||
assert.equal(reply.accept, true);
|
||||
const done = applyAction(offered, 'in', reply);
|
||||
assert.equal(seat(done, 'ec').ally, 'in');
|
||||
assert.equal(done.pendingAmbassador, null);
|
||||
assert.equal(done.pendingAmbassador?.stage, 'loan');
|
||||
});
|
||||
|
||||
void test('saved alliance replies reject missing events, corrupted token custody and changed entrant bindings before any answer', () => {
|
||||
|
||||
Reference in new issue
Block a user