Files
sganggs 01a44cd288 卫戍协议:盟约 web remake — initial import
Online co-op browser remake of Arknights' Stronghold Protocol: Alliance
(client-simulated combat, official waves/bonds/items/strategies, 3D board,
facing wheel, operator loadout, official emotes, deployment scripts).
2026-09-29 16:21:31 +08:00

115 lines
5.7 KiB
JavaScript

// Shared wave-B helpers (server/sim/content/support): bond membership (own bonds, 变形同构体 grants, 调和 enjoying
// active core bonds), live bond state, IN_BATTLE layer gains (requireActive, per-battle caps, disabled fields) and the
// prep→battle `contentInfo` written by the global meta handler.
import { test } from 'node:test';
import assert from 'node:assert/strict';
import { makeBattle, chessRec, checkInvariants } from '../helpers/battleHarness.js';
import {
unitBonds, isMember, bondMembers, bondTier, bondLayers, gainLayers, layersUsed, contentInfo, topActiveBond,
itemKeyOf, buffParams, bondRecord, isCoreBond, frontTile, alliesAround, N4,
} from '../../server/sim/content/support/index.js';
import { createRegistry } from '../../server/match/effectsMeta.js';
import { makeMatch, give } from '../match/harness.js';
import { FakeBattle } from '../match/fakeBattle.js';
const guard = (id, bonds, extra = {}) => chessRec({ id, bonds, profession: 'WARRIOR', skill: null, ...extra });
test('support: data helpers', () => {
assert.equal(itemKeyOf('chess_item_1_01_e_b'), 'chess_item_1_01_e');
assert.equal(itemKeyOf('chess_item_6_02_m'), 'chess_item_6_02_m');
assert.ok(isCoreBond('yanShip') && !isCoreBond('preciShip'));
const p = buffParams(bondRecord('yanShip'), 'act1autochess_bond_eff_yan');
assert.equal(p.base_atk, 0.23);
assert.equal(p.atk_per_stack, 0.009);
});
test('support: membership — own bonds, 变形同构体 grants, 调和 enjoys active core bonds only', () => {
const h = makeBattle({
defs: { chess: {
t_yan_a: guard('t_yan_a', ['yanShip']),
t_mani_a: guard('t_mani_a', ['maniShip']),
t_none_a: guard('t_none_a', ['preciShip']),
} },
units: [
{ chessId: 't_yan_a', row: 10, col: 3 },
{ chessId: 't_mani_a', row: 10, col: 4 },
// 变形同构体 + 维式重锤 → also 维多利亚
{ chessId: 't_none_a', row: 10, col: 5, items: ['chess_item_6_09_e_a', 'chess_item_1_01_e_a'] },
],
bonds: {
yanShip: { count: 3, active: true, tier: 1, layers: 10 },
maniShip: { count: 1, active: true, tier: 1, layers: 0 },
victoriaShip: { count: 1, active: false, tier: 0, layers: 4 },
preciShip: { count: 1, active: false, tier: 0, layers: 0 },
},
});
h.step();
const yan = h.unit('t_yan_a'), mani = h.unit('t_mani_a'), none = h.unit('t_none_a');
assert.deepEqual([...unitBonds(none)].sort(), ['preciShip', 'victoriaShip']);
assert.ok(isMember(h.b, yan, 'yanShip'));
assert.ok(isMember(h.b, mani, 'yanShip'), '调和 enjoys a core bond');
assert.ok(!isMember(h.b, mani, 'preciShip'), '调和 does not enjoy add-on bonds');
assert.ok(isMember(h.b, none, 'victoriaShip'), '变形同构体 grant');
assert.deepEqual(bondMembers(h.b, 'p1', 'yanShip').map((u) => u.defId).sort(), ['t_mani_a', 't_yan_a']);
assert.equal(bondTier(h.b, 'p1', 'yanShip'), 1);
assert.equal(bondTier(h.b, 'p1', 'victoriaShip'), 0);
assert.equal(topActiveBond(h.b, 'p1'), 'yanShip');
assert.deepEqual(frontTile(yan), [10, 4]);
assert.deepEqual(alliesAround(h.b, mani, N4).map((u) => u.defId).sort(), ['t_none_a', 't_yan_a']);
checkInvariants(h.b);
});
test('support: gainLayers — requireActive, per-battle caps, live layers, recorded in the result', () => {
const h = makeBattle({
defs: { chess: { t_a: guard('t_a', ['yanShip']) } },
units: [{ chessId: 't_a', row: 10, col: 3 }],
bonds: { yanShip: { count: 3, active: true, tier: 1, layers: 0 }, preciShip: { count: 0, active: false, tier: 0, layers: 0 } },
});
h.step();
const u = h.unit('t_a');
assert.equal(gainLayers(h.b, { playerId: 'p1', bonds: ['yanShip', 'preciShip'], n: 4, source: u, reason: 'garrison' }), 4, 'inactive 精准 skipped');
assert.equal(gainLayers(h.b, { playerId: 'p1', bonds: 'preciShip', n: 4, requireActive: false }), 4, '无需激活');
for (let i = 0; i < 5; i++) gainLayers(h.b, { playerId: 'p1', bonds: 'yanShip', n: 4, cap: 12, capKey: 'g1' });
assert.equal(layersUsed(h.b, 'g1', 'yanShip'), 12);
assert.equal(bondLayers(h.b, 'p1', 'yanShip'), 16, 'live layers');
h.b.forceEnd('forced');
assert.deepEqual(h.result().perPlayer.p1.layerGains, { yanShip: 16, preciShip: 4 });
});
test('support: gainLayers is a no-op when layer gains are disabled (联防 / boss)', () => {
const h = makeBattle({
defs: { chess: { t_a: guard('t_a', ['yanShip']) } },
units: [{ chessId: 't_a', row: 10, col: 3 }],
bonds: { yanShip: { count: 3, active: true, tier: 1, layers: 0 } },
flags: { layerGainsEnabled: false },
});
h.step();
assert.equal(gainLayers(h.b, { playerId: 'p1', bonds: 'yanShip', n: 5 }), 0);
assert.equal(bondLayers(h.b, 'p1', 'yanShip'), 0);
});
test('support meta: global:contentb_info writes contentInfo into every battle input', () => {
const reg = createRegistry();
assert.ok(reg.has('global:contentb_info'));
const h = makeMatch({ mode: 'coop', humans: 1, bots: 1, seed: 11, registry: reg, fake: true }).start();
h.toPrep(1);
const ps = h.ps('p_0');
give(h.m, ps, 'chess_char_1_01_a', 'hand');
h.drive(() => FakeBattle.instances.length > 0, { ready: true });
const fb = FakeBattle.instances.find((b) => b.players.includes('p_0'));
assert.ok(fb, 'battle built');
const inp = fb.opts.players.find((p) => p.playerId === 'p_0');
const ci = inp.contentInfo;
assert.ok(ci && ci.round === 1 && ci.kind === 'normal');
assert.ok(ci.handChess >= 1 && ci.handUnits >= ci.handChess);
assert.equal(ci.matchBands.length, 2);
assert.equal(ci.teammates.length, 1);
assert.equal(typeof ci.roundStats.gainedChess, 'number');
h.m.dispose();
});
test('support: contentInfo() is {} without meta input (plain harness battle)', () => {
const h = makeBattle({ units: [] });
assert.deepEqual(contentInfo(h.b, 'p1'), {});
});