Files
ark-stronghold-protocol/test/match/feedback3-prep-end-item.test.js

157 lines
6.8 KiB
JavaScript

// An item that arrives at 休整期结束 waits in the hand and merges at the next prep start (owner's decision 2026-10-04).
// 维多利亚 pays 战栗维式重锤 on onPrepEnd; merging in that call used to detach the equipped copy before the fight.
// [ASSUMED] every grant while that dispatch is on the stack, not only this hammer.
import { test } from 'node:test';
import assert from 'node:assert/strict';
import { createRegistry } from '../../server/match/effectsMeta.js';
import { registerMeta as coreRegisterMeta } from '../../server/sim/content/bonds/core.js';
import { makeMatch, give, giveItem, legalTileFor } from './harness.js';
import { FakeBattle } from './fakeBattle.js';
import { tileKey } from '../../server/match/board.js';
const HAMMER = 'chess_item_2_03_e_a'; // 战栗维式重锤
const GOLDEN = 'chess_item_2_03_e_b';
const FILLER = 'chess_item_1_01_e_a';
const OPS = ['chess_char_1_06_a', 'chess_char_2_05_a', 'chess_char_3_08_a'];
function copies(ps, id) {
let n = 0;
const see = (p) => { if (p && p.kind === 'item' && p.id === id) n++; };
for (const p of ps.hand) see(p);
for (const p of ps.temp) see(p);
for (const c of ps.board.values()) if (c && c.kind === 'chess') for (const it of c.items || []) see(it);
return n;
}
function fresh() {
const reg = createRegistry({ content: false });
coreRegisterMeta(reg);
const h = makeMatch({ mode: 'solo', seed: 5, registry: reg, fake: true }).start();
h.toPrep(1);
const orig = h.m.rollItemId.bind(h.m);
h.m.rollItemId = (opts = {}) => (opts.pool === 'pool_equip_vict' ? HAMMER : orig(opts));
return h;
}
/** Bond inactive while the layers land, then three 维多利亚 operators. One wears the hammer when `equip`. */
function arm(h, { layers, equip = true, fill = false } = {}) {
const ps = h.ps('p_0');
assert.equal(ps.setReady(false).ok, true);
for (const p of [...ps.board.values(), ...ps.hand.filter(Boolean), ...ps.temp.filter(Boolean)]) {
if (p && p.kind === 'chess') ps.returnCopies(p);
}
ps.board.clear();
ps.hand.fill(null);
ps.temp.fill(null);
ps._tempDue.clear();
ps.recompute();
assert.equal(ps.bonds.victoriaShip?.active ?? false, false);
if (layers > 0) assert.equal(ps.addLayers('victoriaShip', layers), layers);
assert.equal(ps.counters['bond:victoria:hammers'] || 0, 0, 'inactive: the milestones are owed, not paid');
const used = new Set();
const ops = OPS.map((id) => {
const t = legalTileFor(h.m, ps, id, used);
used.add(tileKey(t[0], t[1]));
return give(h.m, ps, id, 'board', t);
});
assert.equal(ps.bonds.victoriaShip.active, true);
let equipped = null;
if (equip) {
equipped = giveItem(h.m, ps, HAMMER);
const eq = ps.equip(equipped.uid, ops[0].uid);
assert.equal(eq.ok, true, eq.error || eq.detail);
assert.equal(ops[0].items[0].uid, equipped.uid);
}
if (fill) {
for (let i = 0; i < ps.hand.length; i++) if (!ps.hand[i]) giveItem(h.m, ps, FILLER, 'hand', i);
for (let i = 0; i < ps.temp.length; i++) if (!ps.temp[i]) giveItem(h.m, ps, FILLER, 'temp', i);
assert.ok(ps.hand.every(Boolean) && ps.temp.every(Boolean));
}
return { ps, carrier: ops[0], equipped };
}
test('维多利亚 pays a 战栗维式重锤 at prep end: the equipped copy stays for the fight, and they merge at the next prep', () => {
const h = fresh();
const { ps, carrier, equipped } = arm(h, { layers: 25 });
assert.equal(ps.setReady(true).ok, true);
h.m.endPrep();
assert.equal(h.m.phase, 'COMBAT');
assert.equal(carrier.items.length, 1);
assert.equal(carrier.items[0].uid, equipped.uid, 'the equipped hammer was not taken off');
assert.equal(carrier.items[0].id, HAMMER);
assert.equal(copies(ps, HAMMER), 2, 'the new copy is in hand or temp');
assert.equal(copies(ps, GOLDEN), 0, 'no golden yet');
const input = FakeBattle.instances.at(-1).opts.players[0];
const unit = input.units.find((u) => u.uid === carrier.uid);
assert.ok(unit && unit.items.includes(HAMMER), 'the fight is built with the hammer still equipped');
h.toPrep(2);
assert.equal(h.m.phase, 'PREP');
assert.equal(copies(ps, GOLDEN), 1, 'the pair merged when the next prep started');
assert.equal(copies(ps, HAMMER), 0);
h.invariants();
h.m.dispose();
});
test('two milestones owed at prep end both wait: no golden before the fight; the next prep merges the hand copies and leaves the equipped one', () => {
const h = fresh();
const { ps, carrier, equipped } = arm(h, { layers: 50 });
assert.equal(ps.setReady(true).ok, true);
h.m.endPrep();
assert.equal(carrier.items[0].uid, equipped.uid);
assert.equal(copies(ps, HAMMER), 3, 'equipped + two stowed, the nested onGain grant included');
assert.equal(copies(ps, GOLDEN), 0);
h.toPrep(2);
assert.equal(copies(ps, GOLDEN), 1);
assert.equal(copies(ps, HAMMER), 1);
assert.equal(carrier.items[0] && carrier.items[0].uid, equipped.uid, 'the hand copies merged with each other; the equipped one stayed');
h.m.dispose();
});
test('hand and temp full at prep end: the new hammer is destroyed, the equipped one stays, and the next prep does not put a golden in the slot', () => {
const h = fresh();
const { ps, carrier, equipped } = arm(h, { layers: 50, fill: true });
h.m.endPrep();
assert.equal(carrier.items.length, 1);
assert.equal(carrier.items[0].uid, equipped.uid);
assert.equal(carrier.items[0].id, HAMMER);
assert.equal(copies(ps, GOLDEN), 0);
assert.equal(copies(ps, HAMMER), 1);
const toasts = h.sent.filter(([, msg]) => msg.t === 'm.toast').map(([, msg]) => msg.text);
assert.equal(toasts.filter((t) => t === '整备区已满,获得的装备已销毁').length, 2);
const input = FakeBattle.instances.at(-1).opts.players[0];
assert.ok(input.units.find((u) => u.uid === carrier.uid).items.includes(HAMMER));
h.toPrep(2);
assert.equal(carrier.items[0].uid, equipped.uid, 'nothing was merged into the slot');
assert.equal(copies(ps, GOLDEN), 0);
assert.equal(copies(ps, HAMMER), 1);
h.m.dispose();
});
test('a buy during prep still merges at once', () => {
const h = fresh();
const ps = h.ps('p_0');
assert.equal(ps.setReady(false).ok, true);
giveItem(h.m, ps, HAMMER);
ps.funds = 50;
ps.shop.slots[0] = { kind: 'item', id: HAMMER, price: 1, basePrice: 1, sold: false };
assert.equal(ps.buy(0).ok, true);
assert.equal(h.m.phase, 'PREP');
assert.equal(copies(ps, HAMMER), 0);
assert.equal(copies(ps, GOLDEN), 1);
h.m.dispose();
});
test('a grant after the prep has ended (onLayers during combat) still merges at once', () => {
const h = fresh();
const { ps, carrier, equipped } = arm(h, { layers: 0 });
assert.equal(ps.setReady(true).ok, true);
h.m.endPrep();
assert.equal(h.m.phase, 'COMBAT');
assert.equal(carrier.items[0].uid, equipped.uid);
assert.equal(ps.addLayers('victoriaShip', 25), 25);
assert.equal(copies(ps, HAMMER), 0, 'the equipped copy was merged immediately');
assert.equal(copies(ps, GOLDEN), 1);
assert.equal(h.m.phase, 'COMBAT');
h.m.dispose();
});