feedback1 gaps: 变形同构体 lists its pairings; the bond popup explains 调和's +1

The 变形同构体 item card gets its 天赋 list (gameLogic morphPairings, from
the same giveBondId the server counts with): one line per bond with the
items that pair with it, 本局禁用 marked, and on a wearer's card the pairing
it wears highlighted. A bond item's card says which bond it gives a
变形同构体 wearer.

computeBonds marks a core bond state whose count holds 调和's +1
(harmony: 1) and both bond views send it. The popup reads it: 在场 n
(含调和 +1) and a 调和 row naming the 调和 operators on that board, for
the own strip and a teammate's; the strip and chip titles say it too.
The mock harness gets the funny / morph / harmony variants.
This commit is contained in:
sganggs committed 2026-10-03 15:07:14 +08:00
1 parent 481b76027d
commit 016fdb3804
8 files changed
+248 -30

No files matched your search

+72
View File
@@ -404,6 +404,78 @@ export function pieceBondIds(chess, items, getItem = () => null) {
return out;
}
/**
* The 变形同构体 pairings — the list its 天赋栏 shows (the item text: "具体对应关系可在模拟中查看本装备天赋栏"; character_table
* trap_1073_acarm073 "“炎国短刀”→【炎】盟约 …", 14 bonds; GitHub issue #1: without it the item read as doing nothing).
* Built from the same `giveBondId` the server counts with (server/match/bondsMeta.js pieceBonds, grantedBonds here): every
* bond an item grants, in bonds.json order, with the items that grant it (one entry per item family — the normal and 进阶
* copies share a name —, by tier, then data order). `off`: the mode never activates the bond (modeOffBonds: 本局禁用 — 标准
* leaves 5 of the 14 off). `worn`: the wearer's items (`carried`, the card shows the 变形同构体 on an operator) pair it with
* one of the bond's items — the pairing in effect on that operator (that item `worn` too).
* @param {Iterable<any>} itemRecs items.json records (data.list('items'))
* @param {Iterable<any>} bondRecs bonds.json records in data order (data.list('bonds'))
* @param {{ off?: Set<string>|null, carried?: Array<string|{id:string}>|null }} [opts]
* @returns {Array<{ bondId: string, name: string, off: boolean, worn: boolean, items: Array<{ id: string, name: string, worn: boolean }> }>}
*/
export function morphPairings(itemRecs, bondRecs, { off = null, carried = null } = {}) {
const recs = [...(itemRecs || [])].filter(isObj);
const byId = new Map(recs.map((r) => [r.id, r]));
const getItem = (id) => byId.get(id) || null;
const worn = new Set(grantedBonds(carried, getItem));
const carriedIds = new Set((Array.isArray(carried) ? carried : []).map((it) => (typeof it === 'string' ? it : it?.id)).filter((x) => typeof x === 'string'));
const famOf = (r) => (typeof r.baseId === 'string' && r.baseId ? r.baseId : r.id);
/** bondId → family id → { id, name, tier, order, ids } */
const groups = new Map();
recs.forEach((r, order) => {
if (r.canGiveBond || typeof r.giveBondId !== 'string' || !r.giveBondId) return;
let g = groups.get(r.giveBondId);
if (!g) groups.set(r.giveBondId, (g = new Map()));
const fam = famOf(r);
const e = g.get(fam);
if (e) { e.ids.push(r.id); return; }
g.set(fam, { id: fam, name: typeof r.name === 'string' && r.name ? r.name : fam, tier: int(r.tier, 99), order, ids: [r.id] });
});
const out = [];
for (const b of bondRecs || []) {
const g = isObj(b) ? groups.get(b.bondId) : null;
if (!g) continue;
const bondWorn = worn.has(b.bondId);
const items = [...g.values()].sort((x, y) => (x.tier - y.tier) || (x.order - y.order))
.map((e) => ({ id: e.id, name: e.name, worn: bondWorn && e.ids.some((id) => carriedIds.has(id)) }));
out.push({ bondId: b.bondId, name: typeof b.name === 'string' && b.name ? b.name : b.bondId, off: !!(off && typeof off.has === 'function' && off.has(b.bondId)), worn: bondWorn, items });
}
return out;
}
/**
* Bond id of 调和 (bonds.json maniShip, "激活时使场上核心盟约的激活人数+1") — server/match/bondsMeta.js HARMONY_BOND; a test
* pins the two. Only to name the 调和 operators: whether a bond's count holds the +1 is the server's word (the view
* entry's `harmony`, DESIGN §21.26).
*/
export const HARMONY_BOND = 'maniShip';
/**
* The 调和 operators on a board, distinct (normal and elite copies of one operator once): who activates the +1 a bond
* entry's `harmony` reports (the bond popup's 调和 row names them — 缪尔赛思, the Pith strategy's 盟约·辅助干员).
* @param {any} priv m.private — or a teammate's field operators (ui/watchBonds.js ownerBoard)
* @param {(id:string)=>any} [getChess]
* @returns {Array<{ id: string, name: string }>} base chess ids
*/
export function harmonyMembers(priv, getChess = () => null) {
const out = [];
const seen = new Set();
for (const p of Array.isArray(priv?.board) ? priv.board : []) {
if (p?.kind !== 'chess') continue;
const c = getChess(p.id);
if (!Array.isArray(c?.bonds) || !c.bonds.includes(HARMONY_BOND)) continue;
const base = c.baseId || (typeof p.id === 'string' ? p.id.replace(/_b$/, '_a') : p.id);
if (seen.has(base)) continue;
seen.add(base);
out.push({ id: base, name: getChess(base)?.name || c.name || base });
}
return out;
}
/**
* Member rows of a bond popup: every visible member with owned / on-board / banned state, plus the player's operators
* that are members through 变形同构体 (grantedBonds; `granted: true` and `items`: the item ids of the copy that wears the