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).
This commit is contained in:
sganggs committed 2026-10-08 21:11:25 +08:00
1 parent 19c7b26850
commit 2df1cf15bf
8 files changed
+80 -30

No files matched your search

+3 -1
View File
@@ -17,7 +17,9 @@
// already did.
// Watching a teammate (DESIGN §20.15, ui/watchBonds.js) the strip and the popup show THAT player's bonds and layers:
// the strip carries an amber "👁 name" tag (`owner`, the observing pill's spelling, research 09 §3.1) and amber rings,
// the popup a "👁 name 的盟约" line; its member list reads the teammate's operators on the field (no hand: never sent).
// the popup a "👁 name 的盟约" line; its member list reads the teammate's operators on the field — in prep their bench
// too, counted like their own popup counts it (ui/watchBonds.js ownerBoard: hand / temp by the scout's `area`, GitHub
// #385); a battle sends no bench.
// 0.2.0 补位: a member the player fields as its stand-in (the own m.private.standIns; a teammate's unit that says so —
// UnitInfo `standInFor`) is drawn as the stand-in — avatar, name, a small 「替补」 mark on the thumbnail — and its row
// opens the stand-in's card (gameLogic memberStandIn; the owner's recall of the official mode, 2026-10-06).
+19 -16
View File
@@ -151,27 +151,30 @@ export function withLiveLayers(bonds, live) {
* A teammate's board as far as the screen shows it, in the m.private shape the bond popup's member list reads
* (gameLogic bondMembers): their operators on the field meta on screen (the prep scouting board, a server-run battle
* field's units) and, under client-side combat, the battle on screen's (`extra` = battle/runner.js ownerOps: the meta
* the runner publishes is taken before the operators deploy). Their hand is never sent — members count as owned only
* when they are on the field. Their operators keep the items the unit info carries (UnitInfo `items`), so a 变形同构体
* wearer is listed as a member of the bond it grants (gameLogic grantedBonds), and a 补位 unit its `standInFor` (the
* popup draws the stand-in). null without a field or battle.
* @param {any} field @param {string} ownerId @param {any[]|null} [extra] UnitInfo-like { kind, ownerId, defId, items?, standInFor? }
* the runner publishes is taken before the operators deploy). A prep scout also carries their bench, each unit tagged
* with its `area` (server prepFieldMeta): a 'hand' operator goes to the hand list, a 'temp' one to the temp list, so the
* popup counts them as the player's own popup does (GitHub #385, PR #387) — owned, not 在场; a hand operator counts for
* the bonds that count the 整备区 (投资人 远见 奇迹: memberHeadCount), the 5 temporary slots for none. A unit without
* an area (a battle's, an older server's) is on the board; a battle sends no bench. Their operators keep the items the
* unit info carries (UnitInfo `items`), so a 变形同构体 wearer is listed as a member of the bond it grants (gameLogic
* grantedBonds), and a 补位 unit its `standInFor` (the popup draws the stand-in). null without a field or battle.
* @param {any} field @param {string} ownerId @param {any[]|null} [extra] UnitInfo-like { kind, ownerId, defId, area?, items?, standInFor? }
*/
export function ownerBoard(field, ownerId, extra = null) {
const fromField = isObj(field) && Array.isArray(field.units) ? field.units : null;
const fromBattle = Array.isArray(extra) ? extra : null;
if (!fromField && !fromBattle) return null;
const board = [...(fromField || []), ...(fromBattle || [])]
.filter((u) => isObj(u) && u.ownerId === ownerId && u.kind === 'op' && typeof u.defId === 'string')
.map((u) => {
const p = Array.isArray(u.items) && u.items.length ? { kind: 'chess', id: u.defId, items: u.items.filter((x) => typeof x === 'string') } : { kind: 'chess', id: u.defId };
// 0.2.0 自选编队: a DIY slot's unit names its operator (UnitInfo diy) — the popup counts it for that operator's bonds
if (isObj(u.diy) && typeof u.diy.charId === 'string') p.diy = u.diy;
// 0.2.0 补位: a unit fighting as its stand-in says so (UnitInfo standInFor) — the popup draws that member as the stand-in
if (typeof u.standInFor === 'string' && u.standInFor) p.standInFor = u.standInFor;
return p;
});
return { board, hand: [], temp: [] };
const out = { board: /** @type {any[]} */ ([]), hand: /** @type {any[]} */ ([]), temp: /** @type {any[]} */ ([]) };
for (const u of [...(fromField || []), ...(fromBattle || [])]) {
if (!isObj(u) || u.ownerId !== ownerId || u.kind !== 'op' || typeof u.defId !== 'string') continue;
const p = Array.isArray(u.items) && u.items.length ? { kind: 'chess', id: u.defId, items: u.items.filter((x) => typeof x === 'string') } : { kind: 'chess', id: u.defId };
// 0.2.0 自选编队: a DIY slot's unit names its operator (UnitInfo diy) — the popup counts it for that operator's bonds
if (isObj(u.diy) && typeof u.diy.charId === 'string') p.diy = u.diy;
// 0.2.0 补位: a unit fighting as its stand-in says so (UnitInfo standInFor) — the popup draws that member as the stand-in
if (typeof u.standInFor === 'string' && u.standInFor) p.standInFor = u.standInFor;
out[u.area === 'hand' ? 'hand' : u.area === 'temp' ? 'temp' : 'board'].push(p);
}
return out;
}
/**