Files
Dune/tests/e3-nexus-moritani-runtime.test.ts

334 lines
23 KiB
TypeScript

import assert from 'node:assert/strict';
import test from 'node:test';
import { applyAction, viewGame, type Action, type Game } from '../game/engine';
import { DIFFICULTIES } from '../game/bot-profiles';
import { MOBILE_STRONGHOLD, territory } from '../game/board';
import { ownedTech } from '../game/tech-tokens';
import { quoteSpiceCollection } from '../game/board-resolution-quote';
import { nativeShipmentSources } from '../game/homeworld-options';
import { homeworldShipmentChoice } from '../game/homeworld-shipment-options';
import { nativeE3HomeworldPool as homePool } from './fixture-homeworld-native-e3-modules';
import { nexusMoritaniAction } from '../game/nexus-moritani-options';
import { nexusTraitorDrawAction, nexusTraitorReturnAction } from '../game/nexus-traitor-options';
import { discoveryEntryMoveAction } from '../game/discovery-entry-options';
import {
advanceMoritaniNexus as advance, assertMoritaniNexusPhysical as physical,
closeMoritaniNexus as close, createMoritaniNexusClosing as closing,
createMoritaniNexusDiscovery as discovery,
moritaniNexusAct as act, moritaniNexusClean as clean, moritaniNexusPlayer as player,
moritaniNexusPolicy as policy, moritaniNexusReload as reload,
openMoritaniNexusBattle as openBattle, openMoritaniNexusLoss as loss,
orderMoritaniNexusSpice as orderSpice, revealMoritaniNexus as reveal,
settleMoritaniNexus as settle, shipMoritaniNexus as ship,
type MoritaniNexusStep,
} from './fixture-e3-nexus-moritani';
function ordinaryCollectionSpice(g: Game, actor: string): number {
return g.phase === 7 ? quoteSpiceCollection(g).receipts.find(r => r.player === actor)?.strongholds ?? 0 : 0;
}
function reject(g: Game, actor: string, action: Action): void {
const before = structuredClone(g); assert.throws(() => applyAction(g, actor, action)); assert.deepEqual(g, before);
}
/** Every policy consumes a real private owned window from a JSON continuation. */
function policies(g: Game, actor: string, consume: (after: Game, action: Action) => void): void {
for (const difficulty of DIFFICULTIES) {
const saved = reload(g); player(saved, actor).bot = difficulty;
const selected = policy(saved, actor, difficulty)[0]; assert.ok(selected, `${difficulty} needs an actual owned choice.`);
const after = applyAction(saved, actor, selected); consume(after, selected); physical(after);
}
}
function finish(g: Game, actions?: MoritaniNexusStep[]): Game {
for (let n = 0; n < 12; n++) {
g = settle(g, actions);
const d = g.decision;
if (!d) return g;
if (d.kind === 'moritaniAssassinate') {
g = act(g, { actor: d.player, action: { type: 'decision', event: d.event, decline: true } }, actions);
continue;
}
assert.equal(d.kind, 'techToken');
if (d.kind !== 'techToken') throw Error('Expected actual winner Tech choice.');
assert.equal(g.battle, null, 'The battle and physical card cleanup precede its reward.');
for (const entry of g.lastBattleContext!.mandatoryDiscard?.entries ?? [])
assert.ok(g.discard.some(c => c.id === entry.card));
reject(g, d.loser, { type: 'decision', token: d.choices[0] });
reject(g, d.player, { type: 'decision', decline: true });
policies(g, d.player, (after, action) => {
const token = action.token as keyof NonNullable<Game['techTokens']>;
assert.ok(d.choices.includes(token)); assert.equal(after.techTokens![token].owner, d.player);
});
g = act(g, { actor: d.player, action: { type: 'decision', token: d.choices[0] } }, actions);
}
throw Error('Original native aftermath reopened.');
}
void test('no-Skills native closing draw, paid Guild-home loss, printed bounty and private Mentat replacement obey the original physical order', () => {
const f = closing({ homeworlds: true, tech: true, strongholds: true, discovery: true });
assert.equal(f.firstMentat.after.strongholdCards!.owners.sietch_tabr, f.owner);
assert.equal(f.firstMentat.after.strongholdCards!.owners.tueks_sietch, f.opponent);
assert.equal(f.alliance.players.find(p => p.id === f.owner)!.ally, null);
policies(f.draw.before, f.owner, after => {
assert.equal(after.nexusCards!.cards!.hands[f.owner], 'moritani');
assert.ok(!after.nexusCards!.cards!.deck.includes('moritani'));
});
reject(f.draw.before, f.opponent, f.draw.step.action);
reject(f.draw.after, f.owner, f.draw.step.action);
const b = loss(f, f.game);
const sources = nativeShipmentSources(viewGame(b.arrival.before, f.owner), 1, 0); assert.ok(sources);
const quote = homeworldShipmentChoice(viewGame(b.arrival.before, f.owner), b.location,
Object.fromEntries(Object.entries(sources).filter(([, group]) => group.normal + group.elite > 0)));
assert.equal(player(b.settledArrival, f.owner).spice, player(b.arrival.before, f.owner).spice - quote.cost);
assert.equal(player(b.settledArrival, f.owner).reserves, player(b.arrival.before, f.owner).reserves - 1);
assert.equal(homePool(b.settledArrival, f.owner, b.location).normal, 1);
assert.ok(!b.settledArrival.pendingTerrorEntry && !b.settledArrival.pendingAmbassador);
const revealed = reveal(b.game, b.plans, f.actions);
assert.equal(player(revealed, f.opponent).spice, player(b.game, f.opponent).spice, 'Sealed support is not prepaid.');
let g = settle(revealed, f.actions);
const d = g.decision; assert.ok(d?.kind === 'moritaniAssassinate');
assert.equal(player(g, f.owner).leaders.find(l => l.id === b.ownLeader)!.dead, true);
assert.equal(player(g, f.owner).tanks, player(revealed, f.owner).tanks + 1);
assert.equal(homePool(g, f.owner, b.location).normal, 0);
assert.equal(player(g, f.opponent).tanks, player(revealed, f.opponent).tanks);
assert.equal(homePool(g, f.opponent, b.location).normal, homePool(revealed, f.opponent, b.location).normal);
const killed = player(revealed, f.owner).leaders.find(l => l.id === b.ownLeader)!;
assert.equal(player(g, f.opponent).spice, player(revealed, f.opponent).spice,
'Native assassination pauses the original winner casualties, support, bounty and cleanup continuation.');
assert.equal(player(g, f.owner).spice, player(revealed, f.owner).spice);
const token = ownedTech(g.techTokens, f.owner)[0]; assert.ok(token);
const target = player(g, f.opponent).leaders.find(l => l.id === f.target)!;
assert.equal(target.dead, false); assert.notEqual(b.enemyLeader, f.target);
const before = reload(g), action: Action = { type: 'decision', event: d.event, card: f.target };
reject(g, f.opponent, action); reject(g, f.owner, { ...action, event: 'stale' });
reject(g, f.owner, { ...action, card: b.enemyLeader });
policies(g, f.owner, after => {
assert.equal(player(after, f.opponent).leaders.find(l => l.id === f.target)!.dead, true);
assert.equal(player(after, f.owner).spice, player(before, f.owner).spice + target.strength);
assert.equal(after.techTokens![token].owner, f.owner, 'Winner Tech cannot bypass private assassination and cleanup.');
});
g = act(g, { actor: f.owner, action }, f.actions);
assert.equal(player(g, f.opponent).leaders.find(l => l.id === f.target)!.deaths, target.deaths + 1);
assert.ok(player(g, f.owner).traitors.includes(f.target), 'The revealed physical card remains held until Mentat.');
reject(g, f.owner, action);
g = finish(g, f.actions);
assert.equal(g.techTokens![token].owner, f.opponent);
assert.equal(player(g, f.opponent).tanks, player(revealed, f.opponent).tanks + 1);
assert.equal(homePool(g, f.opponent, b.location).normal, homePool(revealed, f.opponent, b.location).normal - 1);
assert.equal(player(g, f.opponent).spice, player(revealed, f.opponent).spice - 1 + killed.strength +
ordinaryCollectionSpice(g, f.opponent), 'The original winner support/bounty settles once after assassination.');
const reserve = [...g.traitorReserve!], held = [...player(g, f.owner).traitors];
g = advance(g, s => s.phase === 8 && s.decision?.kind === 'moritaniPlacement', f.actions);
const receipt = g.moritaniAssassinate!.opportunities[0];
assert.equal(receipt.stage, 'replaced'); assert.equal(receipt.replacement, reserve[0]);
assert.deepEqual(g.traitorReserve, reserve.slice(1));
assert.deepEqual(player(g, f.owner).traitors, [...held.filter(id => id !== f.target), reserve[0]]);
const balance = player(g, f.owner).spice;
g = act(g, { actor: f.owner, action: { type: 'decision', decline: true } }, f.actions);
assert.equal(player(g, f.owner).spice, balance, 'Replacement never pays the bounty a second time.');
physical(g);
});
void test('native Moritani Cunning spends one physical Nexus for an original supply placement and real assassination Terror entry, never relocation or home placement', () => {
const f = closing({ roster: ['moritani', 'guild', 'atreides'], homeworlds: true, tech: true, strongholds: true });
let g = advance(f.game, s => s.phase === 8 && s.decision?.kind === 'moritaniPlacement', f.actions);
const view = viewGame(g, f.owner), token = g.moritaniTerror!.tokens.find(t => t.kind === 'assassination' && t.status === 'available')!;
const action = nexusMoritaniAction(view, view.nexusMoritani!.event, token.id, 'polar_sink'); assert.ok(action);
reject(g, f.opponent, action); reject(g, f.owner, { ...action, nexus: 'stale' });
reject(g, f.owner, { ...action, territory: 'homeworld:guild' });
reject(g, f.owner, { ...action, territory: 'homeworld:moritani' });
reject(g, f.owner, { ...action, territory: MOBILE_STRONGHOLD });
const placed = g.moritaniTerror!.tokens.find(t => t.status === 'placed')!;
reject(g, f.owner, { ...action, token: placed.id });
policies(g, f.owner, (after, chosen) => {
assert.equal(after.nexusCards!.cards!.hands[f.owner], null);
assert.equal(after.nexusCards!.cards!.discard.filter(c => c === 'moritani').length, 1);
const committed = advance(after, s => clean(s) && !s.pendingMoritaniPlacement);
assert.equal(committed.moritaniTerror!.tokens.find(t => t.id === chosen.token)!.location, chosen.territory);
assert.equal(committed.moritaniTerror!.tokens.filter(t => t.status === 'available').length,
g.moritaniTerror!.tokens.filter(t => t.status === 'available').length - 1);
});
g = act(g, { actor: f.owner, action }, f.actions);
g = advance(g, s => clean(s) && !s.pendingMoritaniPlacement, f.actions);
assert.equal(g.moritaniTerror!.tokens.find(t => t.id === token.id)!.location, 'polar_sink');
assert.equal(g.nexusMoritaniHistory!.at(-1)!.stage, 'complete');
reject(g, f.owner, action);
g = advance(g, s => s.turn === 3 && s.phase === 5 && s.active === f.opponent && clean(s), f.actions);
const beforeShipment = reload(g), sources = nativeShipmentSources(viewGame(g, f.opponent), 1, 0); assert.ok(sources);
g = act(g, { actor: f.opponent, action: { type: 'ship', territory: 'polar_sink', sector: 0,
amount: 1, elite: 0, allyPayment: 0, homeworldSources: sources } }, f.actions);
g = advance(g, s => !s.response && !s.phaseOpening && s.decision?.kind === 'moritaniTerror', f.actions);
if (g.pendingTerrorEntry!.stage === 'select') {
g = act(g, { actor: f.owner, action: { type: 'decision', token: token.id } }, f.actions);
}
const before = reload(g), revealAction: Action = { type: 'decision', reveal: true };
reject(g, f.opponent, revealAction);
g = act(g, { actor: f.owner, action: revealAction }, f.actions);
g = advance(g, s => !s.pendingShipment && !s.pendingTerrorEntry && clean(s), f.actions);
const dead = player(g, f.opponent).leaders.filter(l => l.dead && !player(before, f.opponent).leaders.find(old => old.id === l.id)!.dead);
assert.equal(dead.length, 1);
assert.equal(dead[0].deaths, player(before, f.opponent).leaders.find(l => l.id === dead[0].id)!.deaths + 1);
assert.equal(player(g, f.owner).spice, player(before, f.owner).spice + dead[0].strength);
assert.equal(player(g, f.opponent).reserves, player(beforeShipment, f.opponent).reserves - 1);
assert.equal(player(g, f.opponent).forces['polar_sink:0'], 1);
assert.ok(player(g, f.opponent).spice < player(beforeShipment, f.opponent).spice);
assert.equal(g.moritaniTerror!.tokens.find(t => t.id === token.id)!.status, 'removed');
assert.equal(g.moritaniAssassinate!.opportunities.length, 0, 'Terror death is not a fabricated post-loss Traitor assassination.');
reject(g, f.owner, revealAction); physical(g);
});
void test('actual Harkonnen Nexus Secret Ally draws and returns physical extra Traitors before a no-Skills native loss can assassinate the retained identity', () => {
const f = closing({ nexus: 'harkonnen', homeworlds: true, tech: true });
let g = advance(f.game, s => s.phase === 8 && clean(s), f.actions);
assert.equal(g.players.some(p => p.faction === 'harkonnen'), false);
assert.equal(player(g, f.owner).traitors.includes(f.target), false);
// Select only the still-undealt original reserve. Both other draw identities
// stay physical, and the old held identity is returned by the owner below.
const i = g.traitorReserve!.indexOf(f.target); assert.ok(i >= 0);
g.traitorReserve!.unshift(g.traitorReserve!.splice(i, 1)[0]);
const before = reload(g), view = viewGame(g, f.owner), offer = view.nexusTraitors!.offer!;
const action = nexusTraitorDrawAction(view, offer.event, 'secretAlly'); assert.ok(action);
reject(g, f.opponent, action); reject(g, f.owner, { ...action, event: 'stale' });
policies(g, f.owner, after => {
assert.equal(after.nexusCards!.cards!.hands[f.owner], null);
assert.deepEqual(after.traitorReserve, before.traitorReserve!.slice(2));
});
g = act(g, { actor: f.owner, action }, f.actions);
const pending = viewGame(g, f.owner).nexusTraitors!.pending!;
const choices = pending.choices.map(c => c.id), returned = choices.filter(id => id !== f.target); assert.equal(returned.length, 2);
const returnAction = nexusTraitorReturnAction(viewGame(g, f.owner), pending.event, returned); assert.ok(returnAction);
reject(g, f.opponent, returnAction); reject(g, f.owner, { ...returnAction, cards: [returned[0], returned[0]] });
policies(g, f.owner, after => {
assert.equal(player(after, f.owner).traitors.length, player(before, f.owner).traitors.length);
assert.equal(after.traitorReserve!.length, before.traitorReserve!.length);
});
g = act(g, { actor: f.owner, action: returnAction }, f.actions);
assert.deepEqual(player(g, f.owner).traitors, [f.target]);
reject(g, f.owner, returnAction);
const b = loss(f, g), revealed = reveal(b.game, b.plans, f.actions);
g = settle(revealed, f.actions);
const d = g.decision; assert.ok(d?.kind === 'moritaniAssassinate');
const bounty = player(g, f.opponent).leaders.find(l => l.id === f.target)!.strength, balance = player(g, f.owner).spice;
g = act(g, { actor: f.owner, action: { type: 'decision', event: d.event, card: f.target } }, f.actions);
assert.equal(player(g, f.owner).spice, balance + bounty);
assert.equal(player(g, f.opponent).leaders.find(l => l.id === f.target)!.dead, true);
g = finish(g, f.actions); physical(g);
});
void test('Advanced normal Traitor call forfeits assassination, while Basic keeps the printed ordinary call and battle outcome', () => {
for (const advanced of [true, false]) {
const f = closing({ advanced, normalCall: true, homeworlds: true, tech: true });
const b = loss(f, f.game, true); assert.equal(b.enemyLeader, f.target);
let g = settle(reveal(b.game, b.plans, f.actions), f.actions, true);
assert.notEqual(g.decision?.kind, 'moritaniAssassinate');
g = finish(g, f.actions);
assert.equal(g.lastBattleContext!.winner, f.owner);
assert.equal(player(g, f.opponent).leaders.find(l => l.id === f.target)!.dead, true);
assert.equal(player(g, f.opponent).forces[b.location] ?? 0, 0);
assert.equal(player(g, f.owner).forces[b.location], 1);
assert.equal(player(g, f.owner).leaders.find(l => l.id === b.ownLeader)!.dead, false);
if (advanced) {
assert.equal(g.moritaniAssassinate!.normalTraitorCall, true);
assert.equal(g.moritaniAssassinate!.opportunities.length, 0);
const blocked = viewGame(g, f.owner).moritaniAssassinate; assert.ok(blocked?.blocked);
const emperor = g.players.find(p => p.faction === 'emperor')!.id;
const arrival = ship(g, f.owner, 'homeworld:emperor', 1, f.actions);
const battle = openBattle(arrival.settled, 'homeworld:emperor', f.owner, emperor, f.actions);
const own = player(battle, f.owner).leaders.filter(l => !l.dead && !l.usedAt).sort((a, b) => a.strength - b.strength)[0];
const enemy = player(battle, emperor).leaders.filter(l => !l.dead && !l.usedAt).sort((a, b) => b.strength - a.strength)[0];
g = finish(reveal(battle, [
{ actor: f.owner, action: { type: 'battlePlan', leader: own.id, dial: 0, support: 0 } },
{ actor: emperor, action: { type: 'battlePlan', leader: enemy.id, dial: 1, support: 1 } },
], f.actions), f.actions);
assert.equal(g.lastBattleContext!.winner, emperor);
assert.equal(g.moritaniAssassinate!.opportunities.length, 0, 'A later real native-home loss cannot recover the forfeited power.');
assert.equal(player(g, emperor).leaders.find(l => l.id === enemy.id)!.dead, false);
} else assert.ok(!g.moritaniAssassinate, 'Basic must not receive the Advanced private assassination power.');
reject(g, f.owner, { type: 'decision', event: b.game.battle!.event, card: f.target });
physical(g);
}
});
void test('Basic no-Skills ordinary Homeworld loss uses printed battle casualties without an Advanced assassination suffix', () => {
const f = closing({ advanced: false, homeworlds: true, tech: true });
const b = loss(f, f.game), revealed = reveal(b.game, b.plans, f.actions);
const done = finish(revealed, f.actions);
assert.equal(done.lastBattleContext!.winner, f.opponent);
assert.ok(!done.moritaniAssassinate);
assert.equal(player(done, f.owner).leaders.find(l => l.id === b.ownLeader)!.dead, true);
assert.equal(player(done, f.opponent).leaders.find(l => l.id === f.target)!.dead, false);
assert.equal(player(done, f.owner).tanks, player(revealed, f.owner).tanks + 1);
assert.equal(homePool(done, f.owner, b.location).normal, 0);
assert.equal(player(done, f.opponent).spice, player(revealed, f.opponent).spice +
player(revealed, f.owner).leaders.find(l => l.id === b.ownLeader)!.strength);
physical(done);
});
void test('original held Tabr rewards only its local normal win after real paid support, before loser Tech is transferred', () => {
const f = closing({ tech: true, strongholds: true });
const location = `sietch_tabr:${territory('sietch_tabr').sectors[0]}`;
const arrival = ship(f.game, f.opponent, location, 1, f.actions);
assert.equal(player(arrival.settled, f.opponent).reserves, player(arrival.before, f.opponent).reserves - 1);
assert.ok(player(arrival.settled, f.opponent).spice < player(arrival.before, f.opponent).spice);
const g = openBattle(arrival.settled, location, f.owner, f.opponent, f.actions);
const own = player(g, f.owner).leaders.filter(l => !l.dead && !l.usedAt).sort((a, b) => b.strength - a.strength)[0];
const enemy = player(g, f.opponent).leaders.filter(l => !l.dead && !l.usedAt).sort((a, b) => a.strength - b.strength)[0];
const revealed = reveal(g, [
{ actor: f.owner, action: { type: 'battlePlan', leader: own.id, dial: 3, support: 3 } },
{ actor: f.opponent, action: { type: 'battlePlan', leader: enemy.id, dial: 1, support: 1 } },
], f.actions);
const pending = settle(revealed, f.actions);
assert.equal(pending.lastBattleContext!.winner, f.owner);
assert.equal(player(pending, f.owner).spice, player(revealed, f.owner).spice - 3 + 1 + ordinaryCollectionSpice(pending, f.owner));
assert.equal(player(pending, f.owner).forces[location], player(revealed, f.owner).forces[location] - 3);
assert.equal(player(pending, f.owner).tanks, player(revealed, f.owner).tanks + 3);
assert.equal(player(pending, f.opponent).forces[location] ?? 0, 0);
const token = ownedTech(revealed.techTokens, f.opponent)[0]; assert.ok(token);
assert.equal(pending.techTokens![token].owner, f.owner, 'A sole loser token transfers automatically after physical cleanup.');
const done = finish(pending, f.actions);
assert.equal(done.techTokens![token].owner, f.owner);
assert.equal(player(done, f.owner).spice, player(pending, f.owner).spice, 'Tech choice cannot replay held-card income.');
physical(done);
});
void test('original Discovery supply, reveal and free nested entry survive the late closing draw and source-local physical battle loss without Skills', () => {
const f = discovery({ homeworlds: true, tech: true, strongholds: true });
assert.equal(player(f.shipment.settled, f.owner).reserves, player(f.shipment.before, f.owner).reserves - 2);
assert.ok(player(f.shipment.settled, f.owner).spice < player(f.shipment.before, f.owner).spice);
const entryView = viewGame(f.entry.before, f.owner), entry = discoveryEntryMoveAction(entryView, entryView.discoveryEntry!.sources); assert.ok(entry);
reject(f.entry.before, f.opponent, entry); reject(f.entry.before, f.owner, { ...entry, event: 'stale' });
policies(f.entry.before, f.owner, (after, action) => {
const moved = Array.isArray(action.groups)
? action.groups.reduce((sum, group) => sum + Number(group.normal) + Number(group.elite), 0) : 0;
assert.equal(player(after, f.owner).forces['cistern:0'] ?? 0, moved);
assert.equal(player(after, f.owner).forces['gara_kulon:8'] ?? 0, 2 - moved);
assert.equal(player(after, f.owner).spice, player(f.entry.before, f.owner).spice);
});
assert.equal(player(f.entry.after, f.owner).forces['cistern:0'], 2);
assert.equal(player(f.entry.after, f.owner).reserves, player(f.entry.before, f.owner).reserves);
assert.equal(player(f.entry.after, f.owner).spice, player(f.entry.before, f.owner).spice);
assert.equal(player(f.entry.after, f.owner).forces[f.shipment.step.action.territory + ':' + f.shipment.step.action.sector] ?? 0, 0);
let g = advance(f.game, s => s.phase === 1 && clean(s), f.actions); orderSpice(g, true);
const closed = close(f, g); g = closed.game;
assert.equal(closed.draw.after.nexusCards!.cards!.hands[f.owner], 'moritani');
const arrival = ship(g, f.opponent, 'cistern:0', 1, f.actions);
assert.equal(player(arrival.settled, f.opponent).forces['cistern:0'], 1);
assert.ok(player(arrival.settled, f.opponent).spice < player(arrival.before, f.opponent).spice);
g = openBattle(arrival.settled, 'cistern:0', f.owner, f.opponent, f.actions);
const own = player(g, f.owner).leaders.filter(l => !l.dead && !l.usedAt).sort((a, b) => a.strength - b.strength)[0];
const enemy = player(g, f.opponent).leaders.filter(l => !l.dead && !l.usedAt && l.id !== f.target).sort((a, b) => b.strength - a.strength)[0];
const revealed = reveal(g, [
{ actor: f.owner, action: { type: 'battlePlan', leader: own.id, dial: 0, support: 0 } },
{ actor: f.opponent, action: { type: 'battlePlan', leader: enemy.id, dial: 1, support: 1 } },
], f.actions);
const done = finish(revealed, f.actions);
assert.equal(done.lastBattleContext!.winner, f.opponent);
assert.equal(player(done, f.owner).forces['cistern:0'] ?? 0, 0);
assert.equal(player(done, f.owner).tanks, player(revealed, f.owner).tanks + 2);
assert.equal(player(done, f.opponent).tanks, player(revealed, f.opponent).tanks + 1);
assert.equal(player(done, f.opponent).spice, player(revealed, f.opponent).spice - 1 + ordinaryCollectionSpice(done, f.opponent),
'Ordinary Collection is separate; held Tuek income and native-home strength do not migrate into a nested battle.');
assert.equal(homePool(done, f.owner, 'homeworld:moritani').normal, homePool(revealed, f.owner, 'homeworld:moritani').normal);
physical(done);
});