Files
ark-stronghold-protocol/test/match/prep-bench.test.js
T
sganggs 2df1cf15bf feedback7 R22-LATE: a watched teammate's bond popup counts their bench like their own popup — hand operators owned (and counted for 投资人 / 远见 / 奇迹), temp ones never, neither 在场; golden: unchanged
GitHub #385 / PR #387 by @zasx77771. The prep scout sends the hand and the 临时整备区 as units, and ownerBoard put every
op unit on the popup's synthetic board, so the popup marked bench operators 在场 and its 成员 header disagreed with the
server's ring (炎: ring 1, header 3; 远见: ring 2, header 3 with the temp copy).

The scout's units now carry `area` 'board' | 'hand' | 'temp' (the server tags from PR #387; the tag survives the
boss-half remap, the row does not). ownerBoard sorts them into board / hand / temp instead of dropping the bench as
the PR did: dropping the hand would break the bonds that count the 整备区 (投资人 / 远见 / 奇迹 — a hand-only 远见
member: ring 1, header 0). bondMembers / memberHeadCount then count exactly as the player's own popup: hand = owned
and in hand, temp = owned only. A unit without an area (battles, older servers) stays on the board.

Docs: PLAYING.md's 观看谁 paragraph (the bench is listed in prep); comments in bondStrip.js / game.js.
Tests: test/ui/watch-bonds.test.js (36; 2 fail on the parent), test/match/prep-bench.test.js +
feedback5-boss-pair-prep.test.js (area tags; 4 fail on the parent). Golden unchanged (views only).
2026-10-08 21:11:25 +08:00

150 lines
7.9 KiB
JavaScript

// 观战队友的整备区(游玩记录 #2 item 1「观战队友时看不到队友的未上场的干员」,建议见 GitHub #44
// 「看不见队友的待战栏位」):prep 侦察的 m.field(`Match.prepFieldMeta`)把被侦察玩家的手牌当作
// 单位放进手牌行(row 7,col = 手牌槽位,道具是 kind 'item')——客户端按自己整备区的同一渲染路径
// 画出这一行(app.js battleView / ItemView)。手牌变化(部署 / 购入 / 装备)跟棋盘变化一样触发
// 推送(`_prepScoutSig`);观战席副本与玩家完全一致(test/match/spectator.test.js)。客户端:
// test/ui/prep-bench.test.js。
import { test } from 'node:test';
import assert from 'node:assert/strict';
import { GEO, PHASE } from '../../shared/constants.js';
import { DATA, makeMatch, give, giveItem, legalTileFor, chessOfTier } from './harness.js';
const MELEE = (c) => c.position === 'MELEE' && c.profession === 'TANK';
test('#44 prep scout: held pieces are units on the hand row; deploying moves them onto the field', () => {
const S = 's_spec';
const h = makeMatch({ mode: 'coop', humans: 2, seed: 44, fake: true, spectators: [S] }).start();
h.toPrep(1);
const m = h.m;
assert.equal(m.phase, PHASE.PREP);
const b = h.ps('p_1');
assert.deepEqual(m.handle('p_1', { t: 'g.ready', ready: false }), { ok: true });
for (const p of [...b.board.values(), ...b.hand.filter(Boolean), ...b.temp.filter(Boolean)]) {
if (p.kind === 'chess') b.returnCopies(p);
}
b.board.clear();
b.hand.fill(null);
b.temp.fill(null);
b.recompute();
const id = chessOfTier(1, MELEE).find((x) => m.pool.has(x));
const piece = give(m, b, id); // into the hand
assert.deepEqual(m.handle('p_0', { t: 'g.watch', fieldId: 'n:p_1' }), { ok: true });
assert.deepEqual(m.handle(S, { t: 'g.watch', fieldId: 'n:p_1' }), { ok: true });
for (const pid of ['p_0', S]) {
const first = h.lastTo(pid, 'm.field');
assert.equal(first.prep, true);
const u = first.units.find((x) => x.uid === piece.uid);
assert.ok(u, `${pid}: the held operator is a unit of the scout`);
assert.deepEqual([u.x, u.y], [0, GEO.HAND_ROW], `${pid}: standing on the first hand slot`);
assert.equal(u.area, 'hand', 'tagged with its area: the bond popup counts it as held, not in play (GitHub #385)');
assert.equal(u.kind, 'op');
assert.equal(u.items, undefined, 'no items equipped yet');
}
assert.deepEqual(h.lastTo('p_0', 'm.field').effects, h.ps('p_1').effectsView(), 'the scouted effects column rides the meta');
// deploying it is a hand AND a board change: one new push with the piece on the field and off the hand row
const [r, c] = legalTileFor(m, b, id);
assert.deepEqual(m.handle('p_1', { t: 'g.move', uid: piece.uid, to: { area: 'board', row: r, col: c }, dir: 'LEFT' }), { ok: true });
const last = h.lastTo('p_0', 'm.field');
const u = last.units.find((x) => x.uid === piece.uid);
assert.deepEqual([u.x, u.y], [c, r], 'deployed onto the watched board');
assert.equal(u.area, 'board');
assert.equal(last.units.some((x) => x.uid === piece.uid && x.y === GEO.HAND_ROW), false, 'and gone from the hand row');
m.dispose();
});
test('#44 prep scout: a held item is a kind-item unit on the hand row; equipped items ride their operator', () => {
const h = makeMatch({ mode: 'coop', humans: 2, seed: 46, fake: true }).start();
h.toPrep(1);
const m = h.m;
const b = h.ps('p_1');
assert.deepEqual(m.handle('p_1', { t: 'g.ready', ready: false }), { ok: true });
for (const p of [...b.board.values(), ...b.hand.filter(Boolean), ...b.temp.filter(Boolean)]) {
if (p.kind === 'chess') b.returnCopies(p);
}
b.board.clear();
b.hand.fill(null);
b.temp.fill(null);
b.recompute();
const id = chessOfTier(1, MELEE).find((x) => m.pool.has(x));
const op = give(m, b, id);
const item = giveItem(m, b, 'chess_item_1_01_e_a'); // 维式重锤, into the hand
assert.deepEqual(m.handle('p_0', { t: 'g.watch', fieldId: 'n:p_1' }), { ok: true });
const first = h.lastTo('p_0', 'm.field');
const iu = first.units.find((x) => x.uid === item.uid);
assert.ok(iu, 'the held item is a unit of the scout');
assert.equal(iu.kind, 'item');
assert.equal(iu.y, GEO.HAND_ROW);
assert.equal(iu.area, 'hand');
// equip the item onto the operator (still in the hand): the operator's unit carries the item id
assert.deepEqual(m.handle('p_1', { t: 'g.equip', itemUid: item.uid, targetUid: op.uid }), { ok: true });
const after = h.lastTo('p_0', 'm.field');
assert.equal(after.units.some((x) => x.uid === item.uid), false, 'the equipped item left the hand row');
const ou = after.units.find((x) => x.uid === op.uid);
assert.deepEqual(ou.items, ['chess_item_1_01_e_a'], 'the operator scouts with its equipment');
m.dispose();
});
test("PR #129 review: moving a hand piece to another slot is a scout change (x follows the slot), and the temp row rides along", () => {
const h = makeMatch({ mode: 'coop', humans: 2, seed: 47, fake: true }).start();
h.toPrep(1);
const m = h.m;
const b = h.ps('p_1');
assert.deepEqual(m.handle('p_1', { t: 'g.ready', ready: false }), { ok: true });
for (const p of [...b.board.values(), ...b.hand.filter(Boolean), ...b.temp.filter(Boolean)]) {
if (p.kind === 'chess') b.returnCopies(p);
}
b.board.clear();
b.hand.fill(null);
b.temp.fill(null);
b.recompute();
const id = chessOfTier(1, MELEE).find((x) => m.pool.has(x));
const a = give(m, b, id, 'hand', 0);
// a full hand (distinct plain equipment: nothing merges), so the temp piece stays in temp — a free hand slot would pull
// it in (PRTS 卫戍协议/帮助 §手牌区 "常规手牌区出现空位时自动移入")
const plain = Object.values(DATA.items).filter((i) => i.itemType === 'EQUIP' && !i.isGolden && i.kind === 'passive').map((i) => i.itemId ?? i.id);
for (let i = 1; i < b.hand.length; i++) b.hand[i] = b.newPiece('item', plain[i]);
const c = give(m, b, id, 'temp', 0); // 临时整备区: PRTS 帮助把它算进手牌区,一起侦察
assert.ok(b.temp.includes(c));
assert.deepEqual(m.handle('p_0', { t: 'g.watch', fieldId: 'n:p_1' }), { ok: true });
const first = h.lastTo('p_0', 'm.field');
const tu = first.units.find((x) => x.uid === c.uid);
assert.deepEqual([tu.x, tu.y], [GEO.TEMP_C0, GEO.TEMP_ROW], 'the temp piece scouts on the temp row (first slot, col 4)');
assert.equal(tu.area, 'temp');
const hu = first.units.find((x) => x.uid === a.uid);
assert.equal(hu.x, 0);
assert.equal(hu.area, 'hand');
// a hand piece moved to another slot keeps uid and id — the slot must be in the signature for the push to fire
assert.deepEqual(m.handle('p_1', { t: 'g.move', uid: a.uid, to: { area: 'hand', idx: 3 } }), { ok: true });
const last = h.lastTo('p_0', 'm.field');
assert.ok(last, 'a new scout push arrived');
assert.equal(last.units.find((x) => x.uid === a.uid).x, 3, 'the piece stands on its new hand slot');
m.dispose();
});
test('#44 prep scout: an empty hand adds no units and never an m.private to a spectator seat', () => {
const S = 's_spec';
const h = makeMatch({ mode: 'coop', humans: 2, seed: 45, fake: true, spectators: [S] }).start();
h.toPrep(1);
const m = h.m;
const b = h.ps('p_1');
assert.deepEqual(m.handle('p_1', { t: 'g.ready', ready: false }), { ok: true });
for (const p of [...b.board.values(), ...b.hand.filter(Boolean), ...b.temp.filter(Boolean)]) {
if (p.kind === 'chess') b.returnCopies(p);
}
b.board.clear();
b.hand.fill(null);
b.temp.fill(null);
b.recompute();
assert.deepEqual(m.handle('p_0', { t: 'g.watch', fieldId: 'n:p_1' }), { ok: true });
assert.deepEqual(m.handle(S, { t: 'g.watch', fieldId: 'n:p_1' }), { ok: true });
const first = h.lastTo('p_0', 'm.field');
assert.equal(first.units.length, 0, 'nothing held, nothing deployed: no units');
assert.equal(first.bench, undefined, 'no separate bench field (the hand rides units)');
assert.equal(h.allTo(S, 'm.private').length, 0, 'still no private state to a scout');
m.dispose();
});