feedback7: merge fb7-dobermann — 教鞭 is the official three-choice (PR #353 by @Sukvii, the owner's decision), the Final Assault / Hidden Core pay a 战术特训 card's perfect coins (guarded by b.progress leaksBy), the bots put zero-range heavy guards on the road first (PR #339 by @IceCodeNew, placement only)

- Conflicts (the branch started at ae22335c): spec.js / protocol.js b.progress (resolved from fb7-ui-backlog + leaksBy),
  gameLogic.js / game.js exports (device tips + normalizePersonalChoice), docs/PLAYING.md / design/network.md lines
  merged against the base, META.md (画卷 note + 教鞭), test/docs-consistency.test.js pin (both fields).
- Grok review: MERGE AFTER FIXES — the HIGH (a forged boss result paid the cards) fixed on the branch (66a228c6), the
  META wording (1ab12763).
- Golden: matches 10 (the 杜宾 bot picks and the guard placement); union: coop3-ABYSS-5 differs from HEAD only in ai_2's
  dmgDealt (1238011 → 1186269), no outcome change — HEAD's side had moved it, the new placement applies on that path.
- Broad node --test 5623 (5621 pass, 0 fail, 2 skipped); lint 0 errors; typecheck; i18n strict clean.
This commit is contained in:
sganggs committed 2026-10-08 22:11:49 +08:00
commit 2de2feee3c
57 files changed
+1339 -276

No files matched your search

+68 -1
View File
@@ -1140,7 +1140,23 @@ test('教鞭 (Art): a 战术特训 bounty (PRTS "于3个战术特训的悬赏任
const { m, ps } = setup({ seed: 60 + s });
for (let k = 0; k < 2; k++) {
const art = giveItem(m, ps, 'chess_item_6_03_m');
const deadline = m.deadline;
assert.deepEqual(m.handle('p_0', { t: 'g.art', itemUid: art.uid, row: 10, col: 5 }), OK);
const pending = ps.personalChoice;
assert.equal(ps.bounties.length, k, 'offering cards does not add a bounty');
assert.equal(pending.cards.length, 3);
assert.equal(new Set(pending.cards.map((c) => c.effectId)).size, 3);
assert.equal(pending.round, m.round);
assert.equal(pending.sourceItemId, art.id);
assert.equal(m.deadline, deadline, 'uses the existing PREP clock');
assert.equal(ps.find(art.uid), null);
assert.equal(ps.round.arts, k + 1, 'consumed when offered');
for (const c of pending.cards) assert.ok(training.has(c.effectId) && !m.gd.inactiveEnemies.has(c.enemyKey));
assert.deepEqual(m.handle('p_0', { t: 'g.choice', idx: 1, choiceId: pending.id }), OK);
assert.equal(ps.personalChoice, null);
assert.equal(ps.bounties[k].card.effectId, pending.cards[1].effectId, 'the submitted card is applied');
assert.equal(ps.round.arts, k + 1, 'picking does not consume another Art');
assert.equal(m.handle('p_0', { t: 'g.choice', idx: 1, choiceId: pending.id }).error, 'BAD_TARGET');
}
assert.equal(ps.bounties.length, 2);
for (const b of ps.bounties) {
@@ -1151,7 +1167,7 @@ test('教鞭 (Art): a 战术特训 bounty (PRTS "于3个战术特训的悬赏任
seen.add(b.card.effectId);
}
}
assert.ok(seen.size >= 5, 'random pick');
assert.ok(seen.size >= 5, 'different offered cards across seeds');
const { h, m } = setup({ mode: 'coop', humans: 2, seed: 7 });
const p0 = h.ps('p_0'), p1 = h.ps('p_1');
const art = giveItem(m, p0, 'chess_item_6_01_m');
@@ -1162,9 +1178,60 @@ test('教鞭 (Art): a 战术特训 bounty (PRTS "于3个战术特训的悬赏任
const passed = p1.hand.find((p) => p && p.id === 'chess_item_6_01_m');
assert.deepEqual(m.handle('p_1', { t: 'g.art', itemUid: passed.uid, row: 10, col: 5 }), OK);
assert.match(p1.bounties[0].card.effectId, /^enemyeffect_b_/);
assert.equal(p1.personalChoice, null, '神秘顾客 still applies its random bounty immediately');
cover('chess_item_6_03_m', 'chess_item_6_01_m');
});
test('教鞭: pending and empty-pool failures keep the Art, Arts count and RNG; short offers use actual cards', () => {
const { m, ps } = setup();
const first = giveItem(m, ps, 'chess_item_6_03_m');
const second = giveItem(m, ps, 'chess_item_6_03_m', 'temp');
assert.deepEqual(ps.useArt(first.uid, 10, 5), OK);
const pending = ps.personalChoice;
const rng = m.rngMeta.state(), uid = m.uidSeq;
assert.deepEqual(ps.useArt(second.uid, 10, 5), { error: 'BAD_TARGET', detail: '请先完成当前教鞭选择' });
assert.equal(ps.personalChoice, pending);
assert.ok(ps.find(second.uid));
assert.equal(ps.round.arts, 1);
assert.equal(m.rngMeta.state(), rng);
assert.equal(m.uidSeq, uid);
assert.equal(m.pickPersonalChoice(ps, -1, pending.id).error, 'BAD_TARGET');
assert.equal(m.pickPersonalChoice(ps, 0.5, pending.id).error, 'BAD_TARGET');
assert.equal(m.pickPersonalChoice(ps, 3, pending.id).error, 'BAD_TARGET');
assert.equal(ps.personalChoice, pending);
assert.deepEqual(m.pickPersonalChoice(ps, 0, pending.id), OK);
const training = DATA.choices.cards.bounty.filter((c) => c.payout === 'perfect');
m.gd.inactiveEnemies = new Set(training.map((c) => c.enemyKey));
assert.deepEqual(ps.useArt(second.uid, 10, 5), { error: 'BAD_TARGET', detail: '当前没有可用的战术特训' });
assert.ok(ps.find(second.uid));
assert.equal(ps.round.arts, 1);
assert.equal(ps.personalChoice, null);
assert.equal(m.rngMeta.state(), rng);
const allowed = training.find((c) => c.multiRound && m.gd.enemy(c.enemyKey));
m.gd.inactiveEnemies.delete(allowed.enemyKey);
assert.deepEqual(ps.useArt(second.uid, 10, 5), OK);
assert.ok(ps.personalChoice.cards.length > 0 && ps.personalChoice.cards.length < 3);
assert.equal(new Set(ps.personalChoice.cards.map((c) => c.effectId)).size, ps.personalChoice.cards.length);
assert.equal(ps.find(second.uid), null, 'Arts in temp are consumed too');
const view = ps.privateView().personalChoice;
assert.ok(view.cards.every((c) => c.kind === 'bounty' && c.rounds === 2));
assert.ok(view.cards.every((c) => !/每场/.test(c.descRaw || c.desc)), 'multi-round candidates use the same two-battle text as applied bounties');
m.dispose();
});
test('教鞭: an open choice goes with a player who is eliminated, and nothing of it is left in its views', () => {
const { h, m } = setup({ mode: 'coop', humans: 2, seed: 8 });
const a = h.ps('p_0');
const art = giveItem(m, a, 'chess_item_6_03_m');
assert.deepEqual(a.useArt(art.uid, 10, 5), { ok: true });
assert.equal(a.privateView().personalChoice.cards.length, 3);
a.eliminate(m.round);
assert.equal(a.personalChoice, null);
assert.equal(a.privateView().personalChoice, null);
assert.equal(a.privateView().canReady, false);
m.dispose();
});
test('merging: two identical normal items become the golden one (upgradeNum 2); upgradeNum-100 items never merge', () => {
const never = Object.values(DATA.items).filter((r) => r.itemType === 'EQUIP' && !r.isGolden && r.upgradeNum === 100).map((r) => r.name).sort();
assert.deepEqual(never, ['人事部文档', '信标', '变形同构体', '寻呼模块', '拟态物质', '突变细胞'].sort());
+2 -1
View File
@@ -490,7 +490,8 @@ test('user playtest #6 (DESIGN §20): summons, skill triggers, blocking, push fo
assert.match(sec(17), /\*\*revised by §20\.6\*\*/);
assert.ok(!/the own battle's count stays on show as an upper bound tagged 联防中/.test(DESIGN), '§17.5: the frozen 联防 count is gone');
assert.match(sec(8), /uniteLeft\? \/\* 联防: the leaker's enemies still standing/);
assert.match(sec(14), /`b\.progress \{ battleId, gt, killed, total, resolved\?, leaks\?, left\?, bossDmg\?, by\?, done\? \}`/);
assert.match(sec(14), /`b\.progress \{ battleId, gt, killed, total, resolved\?, leaks\?, left\?, bossDmg\?, by\?, leaksBy\?, done\? \}`/);
assert.match(doc('shared/protocol.js'), /leaksBy: \(v\) => isMap\(v, RESULT_LIMITS\.players, isId, \(x\) => isNum\(x, 0, 1e6\)\)/);
assert.match(sec(6), /3 \*\*different\*\* free chess of tier `min\(level\+1, 6\)`/);
assert.match(S20, /105 cards: 56 next-battle incl\. 源石虫·特训, 42 two-battle, 7 multi-round/);
// #11 / #9 / #13: the gauge look (§18.3 / §19.5 superseded, §8.2 fill), model scale and fear (§9, §5.3, §2)
+153 -157
View File
@@ -23,7 +23,7 @@
{"round":1,"ai_0":[1,27,4,0,0,1,2,"20c00c02",0,"preciShip 0/2, sargonShip 0/2",4,4,0,4]},
{"round":2,"ai_0":[1,27,5,0,0,2,4,"8cdbb4d3",0,"preciShip 0/2, sargonShip 0/4",8,8,0,9]},
{"round":3,"ai_0":[1,27,6,0,0,3,6,"24c9b010",1,"indomShip 1/1, preciShip 0/2, sargonShip 0/4",7,7,0,15]},
{"round":4,"ai_0":[1,27,7,0,0,3,8,"2f6d1395",16,"deputShip 1/2, indomShip 1/4, preciShip 0/2, sargonShip 1/10",10,10,0,22]},
{"round":4,"ai_0":[1,27,7,0,0,3,8,"e0ec421c",16,"deputShip 1/2, indomShip 1/4, preciShip 0/2, sargonShip 1/10",10,10,0,22]},
{"round":5,"ai_0":[1,27,8,0,0,3,8,"beffa9be",29,"deputShip 1/4, indomShip 1/7, preciShip 0/2, sargonShip 1/12, steadShip 1/6",53,16,0,29]},
{"round":6,"ai_0":[1,27,9,0,0,4,8,"522b51a4",40,"deputShip 2/8, indomShip 0/7, preciShip 0/2, sargonShip 1/18, steadShip 2/14",28,28,0,40]},
{"round":7,"ai_0":[1,27,10,0,0,4,8,"84e59f1d",58,"deputShip 2/12, indomShip 0/7, preciShip 0/2, sargonShip 1/24, steadShip 2/22",27,27,0,51]},
@@ -40,7 +40,7 @@
"title": {"id":"comment_1","name":"卫戍之星","picId":"comment_icon_1","text":"对敌方领袖造成伤害最高(仅胜利时)"},
"trophies": 0,
"reward": 113,
"stats": {"activatedLayers":78,"bossDamage":200000,"dmgDealt":776675,"gold":72,"itemsEquipped":10,"kills":173,"leaks":0,"lpLost":0,"merges":2,"perfectRounds":8,"refreshes":14},
"stats": {"activatedLayers":78,"bossDamage":200000,"dmgDealt":775946,"gold":72,"itemsEquipped":10,"kills":173,"leaks":0,"lpLost":0,"merges":2,"perfectRounds":8,"refreshes":14},
"lineup": ["4_14_a@12,7","2_08_a@12,8[4_05_e_a+2_04_e_b]","2_14_a@11,8","3_10_a@10,3","2_17_a@10,4[1_01_e_b+4_02_e_a]","4_01_b@10,7","1_12_b@10,8[3_05_e_a+5_02_e_a]","5_02_a@9,8[4_08_e_a]"],
"bonds": ["deputShip 0/16","emptyShip 1/0","indomShip 0/7","preciShip 1/7","sargonShip 1/28","steadShip 2/38","swiftShip 1/5"]
}
@@ -106,8 +106,8 @@
{"round":1,"ai_0":[1,27,4,0,0,1,2,"cc1bac8f",0,"sargonShip 0/2, soloShip 1/0",5,5,0,4]},
{"round":2,"ai_0":[1,27,5,0,0,1,4,"eee78fe4",0,"kazimierzShip 0/2, sargonShip 0/4, soloShip 1/0, swiftShip 0/2",10,10,0,9]},
{"round":3,"ai_0":[1,27,6,0,0,1,6,"a5dafe29",1,"kazimierzShip 0/2, sargonShip 0/8, soloShip 1/1, swiftShip 0/2",8,8,0,15]},
{"round":4,"ai_0":[1,27,7,0,0,2,8,"3fbb6d23",2,"kazimierzShip 0/4, kjeragShip 0/2, sargonShip 0/8, soloShip 1/2, steadShip 0/2, swiftShip 0/4",8,8,0,22]},
{"round":5,"ai_0":[1,27,8,0,0,3,8,"e6160244",3,"kazimierzShip 0/4, kjeragShip 0/2, sargonShip 0/8, soloShip 1/3, steadShip 0/2, swiftShip 0/4",21,21,0,30]},
{"round":4,"ai_0":[1,27,7,0,0,2,8,"71c18729",2,"kazimierzShip 0/4, kjeragShip 0/2, sargonShip 0/8, soloShip 1/2, steadShip 0/2, swiftShip 0/4",8,8,0,22]},
{"round":5,"ai_0":[1,27,8,0,0,3,8,"b2a814f4",3,"kazimierzShip 0/4, kjeragShip 0/2, sargonShip 0/8, soloShip 1/3, steadShip 0/2, swiftShip 0/4",21,21,0,30]},
{
"round": 6,
"ai_0": [1,27,9,0,0,3,8,"83353941",23,"deputShip 1/2, kazimierzShip 0/4, kjeragShip 0/2, sargonShip 0/8, siracusaShip 1/8, soloShip 1/9, steadShip 1/4, swiftShip 0/4",64,19,0,39]
@@ -250,7 +250,7 @@
{"round":1,"ai_0":[1,27,4,0,0,1,2,"cc1bac8f",0,"sargonShip 0/2, soloShip 1/0",5,5,0,4]},
{"round":2,"ai_0":[1,27,5,0,0,1,4,"eee78fe4",0,"kazimierzShip 0/2, sargonShip 0/4, soloShip 1/0, swiftShip 0/2",10,10,0,9]},
{"round":3,"ai_0":[1,27,6,0,1,1,6,"d5f9d956",1,"kazimierzShip 0/2, sargonShip 0/8, soloShip 1/1, swiftShip 0/2",11,9,0,15]},
{"round":4,"ai_0":[1,27,8,0,1,2,8,"247bff16",4,"kazimierzShip 0/4, kjeragShip 0/2, sargonShip 0/8, soloShip 1/1, steadShip 1/3, swiftShip 0/4",11,9,0,23]},
{"round":4,"ai_0":[1,27,8,0,1,2,8,"9e01b865",4,"kazimierzShip 0/4, kjeragShip 0/2, sargonShip 0/8, soloShip 1/1, steadShip 1/3, swiftShip 0/4",11,9,0,23]},
{"round":5,"ai_0":[1,27,9,0,0,3,8,"8f1605f3",5,"kazimierzShip 0/4, kjeragShip 0/2, sargonShip 0/8, soloShip 1/1, steadShip 1/4, swiftShip 0/4",21,21,0,31]},
{
"round": 6,
@@ -387,58 +387,54 @@
"combat": "client",
"boost": null,
"setup": {"stageId":"act1autochess_m03","bossId":"boss_5","hiddenBossId":"boss_8","factions":["TIMES","DOT","INVISIBLE"]},
"end": {"victory":false,"roundsPassed":11,"hiddenReached":false,"hiddenCleared":false,"reason":"eliminated","teamLp":null,"bossPoolLeft":null,"virtualMs":94500},
"rng": {"setup":25,"shop":121,"waves":0,"draft":42,"bots":7368,"meta":2},
"end": {"victory":false,"roundsPassed":10,"hiddenReached":false,"hiddenCleared":false,"reason":"eliminated","teamLp":null,"bossPoolLeft":null,"virtualMs":87100},
"rng": {"setup":25,"shop":114,"waves":0,"draft":42,"bots":6638,"meta":1},
"errors": {"engine":0,"logged":0,"sim":0,"dispatcher":0},
"rounds": [
{"round":1,"ai_0":[1,27,4,0,0,1,2,"cc1bac8f",0,"sargonShip 0/2, soloShip 1/0",5,5,0,4]},
{"round":2,"ai_0":[1,27,5,0,0,1,4,"eee78fe4",0,"kazimierzShip 0/2, sargonShip 0/4, soloShip 1/0, swiftShip 0/2",10,10,0,9]},
{"round":3,"ai_0":[1,27,6,0,1,1,6,"d5f9d956",1,"kazimierzShip 0/2, sargonShip 0/8, soloShip 1/1, swiftShip 0/2",11,9,0,15]},
{"round":4,"ai_0":[1,27,8,0,1,2,8,"247bff16",4,"kazimierzShip 0/4, kjeragShip 0/2, sargonShip 0/8, soloShip 1/1, steadShip 1/3, swiftShip 0/4",11,9,0,23]},
{"round":5,"ai_0":[1,27,9,0,0,3,8,"5fdaa5e6",5,"kazimierzShip 0/4, kjeragShip 0/2, sargonShip 0/8, soloShip 1/1, steadShip 1/4, swiftShip 0/4",21,21,0,31]},
{"round":4,"ai_0":[1,24,8,0,1,2,8,"dd7803fd",4,"kazimierzShip 0/4, kjeragShip 0/2, sargonShip 0/8, soloShip 1/1, steadShip 1/3, swiftShip 0/4",8,9,3,23]},
{"round":5,"ai_0":[1,24,9,0,0,3,8,"5fdaa5e6",5,"kazimierzShip 0/4, kjeragShip 0/2, sargonShip 0/8, soloShip 1/1, steadShip 1/4, swiftShip 0/4",21,21,0,31]},
{
"round": 6,
"ai_0": [1,27,9,0,0,3,8,"3bdfd652",23,"deputShip 1/2, kazimierzShip 0/4, kjeragShip 0/2, sargonShip 0/8, siracusaShip 1/8, soloShip 1/3, steadShip 1/10, swiftShip 0/4",56,17,0,40]
"ai_0": [1,24,9,0,0,3,8,"3bdfd652",23,"deputShip 1/2, kazimierzShip 0/4, kjeragShip 0/2, sargonShip 0/8, siracusaShip 1/8, soloShip 1/3, steadShip 1/10, swiftShip 0/4",56,17,0,40]
},
{
"round": 7,
"ai_0": [1,27,10,0,0,4,8,"1e743cfe",38,"deputShip 1/4, kazimierzShip 0/4, kjeragShip 0/2, sargonShip 0/8, siracusaShip 1/16, soloShip 1/5, steadShip 1/13, swiftShip 0/4",28,28,0,50]
"ai_0": [1,24,10,0,0,4,8,"1e743cfe",38,"deputShip 1/4, kazimierzShip 0/4, kjeragShip 0/2, sargonShip 0/8, siracusaShip 1/16, soloShip 1/5, steadShip 1/13, swiftShip 0/4",28,28,0,50]
},
{
"round": 8,
"ai_0": [1,22,11,0,0,4,8,"0ccee9fb",70,"deputShip 1/6, emptyShip 1/2, kazimierzShip 0/8, kjeragShip 1/9, preciShip 0/5, sargonShip 0/8, siracusaShip 1/31, soloShip 1/7, steadShip 1/15, swiftShip 0/8",14,19,5,61]
"ai_0": [1,19,11,0,0,4,8,"0ccee9fb",70,"deputShip 1/6, emptyShip 1/2, kazimierzShip 0/8, kjeragShip 1/9, preciShip 0/5, sargonShip 0/8, siracusaShip 1/31, soloShip 1/7, steadShip 1/15, swiftShip 0/8",14,19,5,61]
},
{
"round": 9,
"ai_0": [1,22,12,0,0,4,8,"8f39e18f",106,"arcaneShip 1/10, deputShip 1/8, emptyShip 1/4, kazimierzShip 0/8, kjeragShip 1/16, preciShip 1/14, sargonShip 0/8, siracusaShip 1/45, soloShip 1/9, steadShip 0/15, swiftShip 0/8",34,34,0,75]
"ai_0": [1,19,12,0,0,4,8,"8f39e18f",106,"arcaneShip 1/10, deputShip 1/8, emptyShip 1/4, kazimierzShip 0/8, kjeragShip 1/16, preciShip 1/14, sargonShip 0/8, siracusaShip 1/45, soloShip 1/9, steadShip 0/15, swiftShip 0/8",34,34,0,75]
},
{
"round": 10,
"ai_0": [1,12,12,0,0,5,8,"2c008a00",122,"arcaneShip 1/12, deputShip 1/10, emptyShip 1/6, kazimierzShip 0/8, kjeragShip 1/18, preciShip 1/16, sargonShip 0/8, siracusaShip 1/49, soloShip 1/11, steadShip 0/15, swiftShip 0/8",14,24,10,86]
"ai_0": [1,9,12,0,0,5,8,"2c008a00",122,"arcaneShip 1/12, deputShip 1/10, emptyShip 1/6, kazimierzShip 0/8, kjeragShip 1/18, preciShip 1/16, sargonShip 0/8, siracusaShip 1/49, soloShip 1/11, steadShip 0/15, swiftShip 0/8",14,24,10,86]
},
{
"round": 11,
"ai_0": [1,2,12,0,1,5,8,"662804e8",152,"arcaneShip 1/18, deputShip 1/12, emptyShip 1/8, kazimierzShip 0/8, kjeragShip 1/22, preciShip 1/18, sargonShip 0/8, siracusaShip 1/61, soloShip 1/13, steadShip 0/17, swiftShip 0/8",22,34,12,98]
},
{
"round": 12,
"ai_0": [0,0,13,0,0,6,0,"811c9dc5",0,"arcaneShip 0/18, deputShip 0/14, emptyShip 0/10, kazimierzShip 0/8, kjeragShip 0/27, preciShip 0/23, sargonShip 0/8, siracusaShip 0/69, soloShip 0/15, steadShip 0/17, swiftShip 0/8",9,31,22,111]
"ai_0": [0,0,12,0,0,5,0,"811c9dc5",0,"arcaneShip 0/18, deputShip 0/12, emptyShip 0/8, kazimierzShip 0/8, kjeragShip 0/22, preciShip 0/18, sargonShip 0/8, siracusaShip 0/61, soloShip 0/13, steadShip 0/17, swiftShip 0/8",22,34,12,98]
}
],
"players": {
"ai_0": {
"alive": false,
"victory": false,
"roundsPassed": 11,
"eliminatedRound": 12,
"roundsPassed": 10,
"eliminatedRound": 11,
"lp": 0,
"bandId": "band_ermengard",
"title": {"id":"comment_4","name":"精英云集","picId":"comment_icon_4","text":"晋升精锐次数最多"},
"trophies": 0,
"reward": 255,
"stats": {"activatedLayers":0,"bossDamage":0,"dmgDealt":1683862,"gold":111,"itemsEquipped":13,"kills":235,"leaks":49,"lpLost":35,"merges":2,"perfectRounds":8,"refreshes":6},
"reward": 213,
"stats": {"activatedLayers":0,"bossDamage":0,"dmgDealt":1352800,"gold":98,"itemsEquipped":12,"kills":223,"leaks":30,"lpLost":28,"merges":2,"perfectRounds":7,"refreshes":6},
"lineup": [],
"bonds": ["arcaneShip 0/18","deputShip 0/14","emptyShip 0/10","kazimierzShip 0/8","kjeragShip 0/27","preciShip 0/23","sargonShip 0/8","siracusaShip 0/69","soloShip 0/15","steadShip 0/17","swiftShip 0/8"]
"bonds": ["arcaneShip 0/18","deputShip 0/12","emptyShip 0/8","kazimierzShip 0/8","kjeragShip 0/22","preciShip 0/18","sargonShip 0/8","siracusaShip 0/61","soloShip 0/13","steadShip 0/17","swiftShip 0/8"]
}
}
},
@@ -651,48 +647,48 @@
},
{
"round": 4,
"ai_0": [1,25,8,0,1,2,8,"0c0d931c",5,"kazimierzShip 0/2, kjeragShip 0/2, soloShip 1/3, steadShip 2/2, swiftShip 0/2",12,12,0,23],
"ai_1": [1,26,8,0,1,2,8,"81a34b39",0,"arcaneShip 0/2, kazimierzShip 0/2, preciShip 0/2, sargonShip 0/4, soloShip 1/0, swiftShip 0/2",11,12,1,23]
"ai_0": [1,25,8,0,1,2,8,"30629777",5,"kazimierzShip 0/2, kjeragShip 0/2, soloShip 1/3, steadShip 2/2, swiftShip 0/2",12,12,0,23],
"ai_1": [1,25,8,0,1,2,8,"81a34b39",0,"arcaneShip 0/2, kazimierzShip 0/2, preciShip 0/2, sargonShip 0/4, soloShip 1/0, swiftShip 0/2",11,12,1,23]
},
{
"round": 5,
"ai_0": [1,25,9,0,0,3,8,"9d0d6b64",7,"kazimierzShip 0/2, kjeragShip 0/2, soloShip 1/3, steadShip 2/2, swiftShip 1/2",18,18,0,32],
"ai_1": [1,26,9,0,0,3,8,"7a9c7584",0,"arcaneShip 0/2, kazimierzShip 0/2, preciShip 0/2, sargonShip 0/4, skillfulShip 1/0, soloShip 1/0, swiftShip 0/2",18,18,0,32]
"ai_1": [1,25,9,0,0,3,8,"7a9c7584",0,"arcaneShip 0/2, kazimierzShip 0/2, preciShip 0/2, sargonShip 0/4, skillfulShip 1/0, soloShip 1/0, swiftShip 0/2",18,18,0,32]
},
{
"round": 6,
"ai_0": [1,25,9,0,0,3,8,"a0e6e026",9,"kazimierzShip 1/2, kjeragShip 0/2, skillfulShip 1/0, soloShip 1/3, steadShip 2/2, swiftShip 1/2",24,24,0,41],
"ai_1": [1,26,9,0,0,3,8,"e1a93e5c",2,"arcaneShip 0/2, kazimierzShip 0/2, preciShip 0/2, sargonShip 0/4, skillfulShip 1/0, soloShip 1/0, swiftShip 1/2",24,24,0,43]
"ai_1": [1,25,9,0,0,3,8,"e1a93e5c",2,"arcaneShip 0/2, kazimierzShip 0/2, preciShip 0/2, sargonShip 0/4, skillfulShip 1/0, soloShip 1/0, swiftShip 1/2",24,24,0,43]
},
{
"round": 7,
"ai_0": [1,25,10,0,0,4,8,"7dbd16ef",20,"kazimierzShip 1/2, kjeragShip 0/2, raidShip 1/4, skillfulShip 1/3, soloShip 1/3, steadShip 1/6, swiftShip 1/2",28,28,0,51],
"ai_1": [1,26,10,0,2,4,8,"6a9b4c34",2,"arcaneShip 0/2, kazimierzShip 0/2, preciShip 0/2, sargonShip 0/4, skillfulShip 1/0, soloShip 1/0, swiftShip 1/2",28,28,0,53]
"ai_1": [1,25,10,0,2,4,8,"6a9b4c34",2,"arcaneShip 0/2, kazimierzShip 0/2, preciShip 0/2, sargonShip 0/4, skillfulShip 1/0, soloShip 1/0, swiftShip 1/2",28,28,0,53]
},
{
"round": 8,
"ai_0": [1,25,11,0,0,4,8,"50b7d162",57,"kazimierzShip 1/16, kjeragShip 0/2, raidShip 1/21, skillfulShip 1/6, soloShip 1/3, steadShip 2/11, swiftShip 0/4",9,9,0,61],
"ai_1": [1,26,13,0,0,4,8,"bdd566ef",15,"arcaneShip 0/2, kazimierzShip 0/2, preciShip 2/15, sargonShip 0/4, siracusaShip 1/0, skillfulShip 1/0, soloShip 1/0, swiftShip 0/2",6,9,3,66]
"ai_1": [1,25,13,0,0,4,8,"bdd566ef",15,"arcaneShip 0/2, kazimierzShip 0/2, preciShip 2/15, sargonShip 0/4, siracusaShip 1/0, skillfulShip 1/0, soloShip 1/0, swiftShip 0/2",6,9,3,66]
},
{
"round": 9,
"ai_0": [1,25,12,0,2,4,8,"d4f6e277",74,"kazimierzShip 1/19, kjeragShip 0/2, raidShip 1/32, skillfulShip 0/9, soloShip 1/3, steadShip 2/12, suntShip 1/0, swiftShip 1/8",23,23,0,73],
"ai_1": [1,25,12,0,1,4,8,"f01fb873",39,"arcaneShip 0/2, kazimierzShip 0/2, preciShip 1/28, sargonShip 0/4, siracusaShip 1/4, skillfulShip 1/0, soloShip 1/0, swiftShip 1/7",17,23,6,78]
"ai_1": [1,24,12,0,1,4,8,"f01fb873",39,"arcaneShip 0/2, kazimierzShip 0/2, preciShip 1/28, sargonShip 0/4, siracusaShip 1/4, skillfulShip 1/0, soloShip 1/0, swiftShip 1/7",17,23,6,78]
},
{
"round": 10,
"ai_0": [1,23,14,0,1,5,8,"eb530a8d",86,"kazimierzShip 1/25, kjeragShip 0/2, raidShip 1/32, skillfulShip 0/9, soloShip 1/3, steadShip 2/12, suntShip 1/0, swiftShip 1/14",6,8,2,88],
"ai_1": [1,22,13,0,0,5,8,"b403dc5e",61,"arcaneShip 0/2, kazimierzShip 0/2, preciShip 1/41, sargonShip 0/4, siracusaShip 1/4, skillfulShip 1/2, soloShip 1/0, suntShip 1/0, swiftShip 1/14",5,8,3,91]
"ai_1": [1,21,13,0,0,5,8,"b403dc5e",61,"arcaneShip 0/2, kazimierzShip 0/2, preciShip 1/41, sargonShip 0/4, siracusaShip 1/4, skillfulShip 1/2, soloShip 1/0, suntShip 1/0, swiftShip 1/14",5,8,3,91]
},
{
"round": 11,
"ai_0": [1,13,13,0,1,5,8,"5c79cc63",105,"kazimierzShip 1/29, kjeragShip 0/2, raidShip 1/38, skillfulShip 0/9, soloShip 1/3, steadShip 2/13, suntShip 1/0, swiftShip 1/22",13,27,14,100],
"ai_1": [1,12,12,0,1,5,8,"33d685b9",87,"arcaneShip 0/2, kazimierzShip 0/2, preciShip 1/54, sargonShip 0/4, siracusaShip 1/8, skillfulShip 1/4, soloShip 1/0, suntShip 1/0, swiftShip 1/21",8,27,19,102]
"ai_1": [1,11,12,0,1,5,8,"33d685b9",87,"arcaneShip 0/2, kazimierzShip 0/2, preciShip 1/54, sargonShip 0/4, siracusaShip 1/8, skillfulShip 1/4, soloShip 1/0, suntShip 1/0, swiftShip 1/21",8,27,19,102]
},
{
"round": 12,
"ai_0": [1,3,13,0,0,6,8,"a701cd86",125,"kazimierzShip 1/29, kjeragShip 0/2, raidShip 1/49, skillfulShip 0/9, soloShip 1/3, steadShip 2/18, suntShip 1/0, swiftShip 1/26, visiShip 1/0",31,50,19,114],
"ai_1": [1,2,13,0,0,6,8,"62b855c3",71,"arcaneShip 0/2, kazimierzShip 0/2, preciShip 1/63, sargonShip 0/4, siracusaShip 2/8, skillfulShip 0/6, soloShip 1/0, suntShip 1/0, swiftShip 0/23",23,50,27,114]
"ai_1": [1,1,13,0,0,6,8,"62b855c3",71,"arcaneShip 0/2, kazimierzShip 0/2, preciShip 1/63, sargonShip 0/4, siracusaShip 2/8, skillfulShip 0/6, soloShip 1/0, suntShip 1/0, swiftShip 0/23",23,50,27,114]
},
{
"round": 13,
@@ -711,7 +707,7 @@
"title": {"id":"comment_4","name":"精英云集","picId":"comment_icon_4","text":"晋升精锐次数最多"},
"trophies": 3,
"reward": 298,
"stats": {"activatedLayers":0,"bossDamage":0,"dmgDealt":2920409,"gold":127,"itemsEquipped":9,"kills":226,"leaks":65,"lpLost":32,"merges":4,"perfectRounds":9,"refreshes":12},
"stats": {"activatedLayers":0,"bossDamage":0,"dmgDealt":2915613,"gold":127,"itemsEquipped":9,"kills":225,"leaks":65,"lpLost":32,"merges":4,"perfectRounds":9,"refreshes":12},
"lineup": [],
"bonds": ["kazimierzShip 0/45","kjeragShip 0/10","miraShip 0/7","raidShip 0/49","skillfulShip 0/9","soloShip 0/3","steadShip 0/18","swiftShip 0/46","yanShip 0/6"]
},
@@ -725,7 +721,7 @@
"title": {"id":"comment_5","name":"万事俱备","picId":"comment_icon_5","text":"装备配发数量最多"},
"trophies": 3,
"reward": 298,
"stats": {"activatedLayers":0,"bossDamage":0,"dmgDealt":2271756,"gold":126,"itemsEquipped":11,"kills":194,"leaks":88,"lpLost":34,"merges":3,"perfectRounds":6,"refreshes":23},
"stats": {"activatedLayers":0,"bossDamage":0,"dmgDealt":2271756,"gold":126,"itemsEquipped":11,"kills":194,"leaks":88,"lpLost":35,"merges":3,"perfectRounds":6,"refreshes":23},
"lineup": [],
"bonds": ["arcaneShip 0/2","kazimierzShip 0/2","preciShip 0/63","sargonShip 0/4","siracusaShip 0/24","skillfulShip 0/14","swiftShip 0/28"]
}
@@ -857,7 +853,7 @@
"title": {"id":"comment_5","name":"万事俱备","picId":"comment_icon_5","text":"装备配发数量最多"},
"trophies": 2,
"reward": 191,
"stats": {"activatedLayers":0,"bossDamage":0,"dmgDealt":1238011,"gold":83,"itemsEquipped":7,"kills":160,"leaks":67,"lpLost":30,"merges":2,"perfectRounds":2,"refreshes":14},
"stats": {"activatedLayers":0,"bossDamage":0,"dmgDealt":1186269,"gold":83,"itemsEquipped":7,"kills":160,"leaks":67,"lpLost":30,"merges":2,"perfectRounds":2,"refreshes":14},
"lineup": [],
"bonds": ["indomShip 0/2","siracusaShip 0/80","soloShip 0/4","swiftShip 0/6","yanShip 0/1"]
}
@@ -874,7 +870,7 @@
"boost": null,
"setup": {"stageId":"act1autochess_m01","bossId":"boss_3","hiddenBossId":null,"factions":["TIMES","ELEMENT","REFLECTION"]},
"end": {"victory":true,"roundsPassed":14,"hiddenReached":false,"hiddenCleared":false,"reason":"victory","teamLp":130,"bossPoolLeft":0,"virtualMs":130000},
"rng": {"setup":16,"shop":910,"waves":0,"draft":25,"bots":35892,"meta":30},
"rng": {"setup":16,"shop":832,"waves":0,"draft":25,"bots":35892,"meta":31},
"errors": {"engine":0,"logged":0,"sim":0,"dispatcher":0},
"rounds": [
{
@@ -893,7 +889,7 @@
},
{
"round": 3,
"ai_0": [1,26,6,0,0,1,6,"762d2060",4,"deputShip 1/4, kjeragShip 0/2, sargonShip 0/13, steadShip 0/2",6,6,0,15],
"ai_0": [1,26,6,0,0,1,6,"21f68a64",4,"deputShip 1/4, kjeragShip 0/2, sargonShip 0/13, steadShip 0/2",6,6,0,15],
"ai_1": [1,45,6,0,0,1,7,"24a9876d",0,"preciShip 0/2",6,6,0,15],
"ai_2": [1,28,6,0,0,1,6,"683d4946",2,"sargonShip 0/8, siracusaShip 1/2",6,6,0,15],
"ai_3": [1,31,6,0,0,1,6,"3694ec95",4,"preciShip 1/4, siracusaShip 0/3, yanShip 0/1",6,6,0,15]
@@ -921,52 +917,52 @@
},
{
"round": 7,
"ai_0": [1,26,10,0,0,4,8,"caacae15",53,"deputShip 2/34, kjeragShip 0/2, sargonShip 0/15, steadShip 2/19",29,29,0,52],
"ai_0": [1,26,10,0,0,4,8,"58c5f5d4",52,"deputShip 2/34, kjeragShip 0/2, sargonShip 0/15, steadShip 2/18",29,29,0,52],
"ai_1": [1,45,10,0,0,4,8,"ec27f6eb",45,"deputShip 2/30, preciShip 0/4, sargonShip 0/2, siracusaShip 1/15",29,29,0,49],
"ai_2": [1,28,10,0,0,4,8,"15280eab",16,"sargonShip 0/8, siracusaShip 1/16, swiftShip 0/6, visiShip 1/0, yanShip 0/1",29,29,0,50],
"ai_3": [1,31,10,0,1,4,8,"53fd121b",82,"deputShip 2/12, emptyShip 1/8, kjeragShip 0/20, preciShip 1/53, siracusaShip 0/3, swiftShip 1/9, yanShip 0/1",29,29,0,50]
},
{
"round": 8,
"ai_0": [1,26,11,0,0,4,8,"eba0dfca",119,"deputShip 1/41, kjeragShip 0/2, preciShip 2/20, sargonShip 1/29, steadShip 1/29",15,15,0,61],
"ai_0": [1,26,11,0,0,4,8,"eba0dfca",118,"deputShip 1/41, kjeragShip 0/2, preciShip 2/20, sargonShip 1/29, steadShip 1/28",15,15,0,61],
"ai_1": [1,45,11,0,0,4,8,"528da1bc",65,"deputShip 2/38, preciShip 0/4, sargonShip 0/2, siracusaShip 1/23, steadShip 1/4",15,15,0,60],
"ai_2": [1,28,11,0,0,4,8,"dfed3d0a",48,"sargonShip 0/8, siracusaShip 1/36, swiftShip 1/12, visiShip 1/0, yanShip 0/1",15,15,0,59],
"ai_3": [1,31,12,0,1,4,7,"363e51cd",130,"deputShip 1/14, emptyShip 1/10, kjeragShip 1/27, preciShip 1/63, siracusaShip 0/3, swiftShip 1/16, yanShip 0/1",15,15,0,63]
},
{
"round": 9,
"ai_0": [1,26,12,0,0,4,8,"fc454676",165,"deputShip 1/47, kjeragShip 0/2, preciShip 2/38, sargonShip 1/41, steadShip 1/39",30,30,0,74],
"ai_0": [1,26,12,0,0,4,8,"fc454676",164,"deputShip 1/47, kjeragShip 0/2, preciShip 2/38, sargonShip 1/41, steadShip 1/38",30,30,0,74],
"ai_1": [1,45,12,0,0,4,8,"33bd98dc",83,"deputShip 2/48, emptyShip 1/4, preciShip 0/4, sargonShip 0/2, siracusaShip 1/31, steadShip 0/4",30,30,0,70],
"ai_2": [1,28,12,0,0,4,8,"6965081a",70,"sargonShip 0/8, siracusaShip 1/58, swiftShip 1/12, visiShip 1/0, yanShip 0/1",30,30,0,72],
"ai_3": [1,31,13,0,0,4,8,"9a60eaff",155,"deputShip 1/16, emptyShip 1/12, kjeragShip 1/32, preciShip 1/72, siracusaShip 0/3, swiftShip 1/23, yanShip 0/1",30,30,0,76]
},
{
"round": 10,
"ai_0": [1,26,12,0,0,5,8,"2b6a123a",239,"deputShip 1/49, kjeragShip 0/2, preciShip 2/70, sargonShip 1/67, steadShip 1/53",29,29,0,86],
"ai_0": [1,26,12,0,0,5,8,"1f68e81b",230,"deputShip 1/53, kjeragShip 0/2, preciShip 2/66, sargonShip 1/63, steadShip 1/48",29,29,0,86],
"ai_1": [1,45,12,0,0,5,8,"d8c7d895",101,"deputShip 2/58, emptyShip 1/8, preciShip 0/4, sargonShip 0/2, siracusaShip 1/35, steadShip 0/4",29,29,0,82],
"ai_2": [1,28,12,0,0,5,8,"fe139ef2",72,"sargonShip 0/8, siracusaShip 2/60, swiftShip 1/12, visiShip 1/0, yanShip 0/1",29,29,0,83],
"ai_3": [1,31,12,0,0,5,7,"957d85d7",169,"deputShip 1/18, emptyShip 0/14, kjeragShip 1/40, preciShip 1/81, siracusaShip 0/3, swiftShip 1/30, yanShip 0/1",29,29,0,88]
},
{
"round": 11,
"ai_0": [1,26,12,0,0,5,8,"a6455ca9",219,"deputShip 0/49, kjeragShip 0/2, preciShip 2/85, sargonShip 1/73, steadShip 2/61",28,28,0,98],
"ai_0": [1,26,12,0,0,5,8,"a6455ca9",206,"deputShip 0/53, kjeragShip 0/2, preciShip 2/81, sargonShip 1/69, steadShip 2/56",28,28,0,98],
"ai_1": [1,45,12,0,0,5,8,"8d2d3b28",139,"deputShip 2/68, emptyShip 1/15, indomShip 1/6, preciShip 0/4, sargonShip 0/2, siracusaShip 1/50, steadShip 0/4",13,28,15,95],
"ai_2": [1,28,12,0,0,5,8,"11fc8c7b",106,"emptyShip 1/0, sargonShip 0/8, siracusaShip 2/94, swiftShip 1/12, yanShip 0/1",27,28,1,97],
"ai_3": [1,31,12,0,0,5,7,"b2ef4516",206,"deputShip 1/20, emptyShip 0/14, kjeragShip 1/55, preciShip 1/94, siracusaShip 0/3, swiftShip 1/37, yanShip 0/1",28,28,0,100]
"ai_2": [1,28,12,0,0,5,8,"98a908c4",138,"emptyShip 1/0, sargonShip 0/8, siracusaShip 2/114, swiftShip 1/24, yanShip 0/1",28,28,0,95],
"ai_3": [1,31,12,0,0,5,7,"e35238bd",203,"deputShip 1/20, emptyShip 0/14, kjeragShip 1/52, preciShip 1/94, siracusaShip 0/3, swiftShip 1/37, yanShip 0/1",28,28,0,100]
},
{
"round": 12,
"ai_0": [1,26,12,0,0,6,8,"3fc98b94",258,"deputShip 0/49, kjeragShip 0/2, preciShip 2/106, sargonShip 1/83, steadShip 2/69",46,46,0,112],
"ai_1": [1,45,12,0,0,6,8,"c13985aa",151,"deputShip 2/78, emptyShip 1/19, indomShip 0/6, preciShip 0/4, sargonShip 0/2, siracusaShip 1/54, steadShip 0/4",24,46,22,107],
"ai_2": [1,28,12,0,0,6,8,"cbd1cd4c",124,"emptyShip 1/0, sargonShip 0/8, siracusaShip 2/112, swiftShip 1/12, yanShip 0/1",46,46,0,107],
"ai_3": [1,31,12,0,0,6,7,"960125ca",269,"deputShip 1/24, emptyShip 0/14, kjeragShip 1/90, preciShip 1/111, siracusaShip 0/3, swiftShip 1/44, yanShip 0/1",46,46,0,112]
"ai_0": [1,26,12,0,0,6,8,"6982f097",241,"deputShip 0/53, kjeragShip 0/2, preciShip 2/100, sargonShip 1/77, steadShip 2/64",46,46,0,113],
"ai_1": [1,45,12,0,0,6,8,"9409d711",157,"deputShip 2/78, emptyShip 1/19, indomShip 0/6, preciShip 0/4, sargonShip 0/2, siracusaShip 1/60, steadShip 0/4",26,46,20,107],
"ai_2": [1,28,12,0,0,6,8,"a00d83bb",204,"emptyShip 1/0, sargonShip 0/8, siracusaShip 2/180, swiftShip 1/24, yanShip 0/1",46,46,0,107],
"ai_3": [1,31,12,0,0,6,7,"0adf3859",250,"deputShip 1/22, emptyShip 0/14, kjeragShip 1/77, preciShip 1/107, siracusaShip 0/3, swiftShip 1/44, yanShip 0/1",46,46,0,111]
},
{
"round": 13,
"ai_0": [1,26,12,0,0,6,8,"c7cc20e6",270,"deputShip 0/49, kjeragShip 0/2, preciShip 2/110, sargonShip 1/83, steadShip 2/77, victoriaShip 0/5, yanShip 0/5",32,32,0,124],
"ai_1": [1,45,12,0,0,6,8,"b8ae2ad4",181,"deputShip 1/86, emptyShip 1/21, indomShip 2/10, preciShip 0/4, sargonShip 0/2, siracusaShip 1/64, steadShip 0/4",32,32,0,118],
"ai_2": [1,28,12,0,0,6,8,"690ad961",210,"emptyShip 1/0, sargonShip 0/8, siracusaShip 2/186, swiftShip 1/24, yanShip 0/1",32,32,0,119],
"ai_3": [1,31,12,0,0,6,8,"7229fc68",300,"deputShip 1/26, emptyShip 0/14, kjeragShip 1/99, preciShip 2/124, siracusaShip 0/3, swiftShip 1/51, yanShip 0/1",32,32,0,125]
"ai_0": [1,26,12,0,0,6,8,"7e6a7c23",277,"deputShip 0/53, kjeragShip 0/2, preciShip 2/120, sargonShip 1/85, steadShip 2/72, victoriaShip 0/5, yanShip 0/5",32,32,0,125],
"ai_1": [1,45,12,0,0,6,8,"8ffb520b",201,"deputShip 2/90, emptyShip 1/23, indomShip 0/6, preciShip 1/10, sargonShip 0/2, siracusaShip 1/78, steadShip 0/4",32,32,0,122],
"ai_2": [1,28,12,0,0,6,8,"417dbf48",256,"emptyShip 1/0, sargonShip 0/8, siracusaShip 2/232, swiftShip 1/24, yanShip 0/1",32,32,0,119],
"ai_3": [1,31,12,0,2,6,7,"c443f903",203,"deputShip 0/22, emptyShip 0/14, kjeragShip 1/91, preciShip 2/112, siracusaShip 0/3, swiftShip 0/49, yanShip 0/1",32,32,0,122]
}
],
"players": {
@@ -980,9 +976,9 @@
"title": {"id":"comment_2","name":"不朽盟约","picId":"comment_icon_2","text":"激活的盟约层数总和最高"},
"trophies": 3,
"reward": 250,
"stats": {"activatedLayers":354,"bossDamage":141377,"dmgDealt":1741970,"gold":138,"itemsEquipped":19,"kills":302,"leaks":0,"lpLost":0,"merges":6,"perfectRounds":13,"refreshes":20},
"lineup": ["2_08_a@12,8[4_04_e_a+4_02_e_b]","2_14_b@11,6","6_12_a@11,7[4_07_e_a+4_03_e_b]","6_05_a@11,8[1_05_e_a+5_02_e_b]","6_14_a@10,3","6_03_a@9,4","3_20_b@9,8[3_02_e_a]","5_02_b@9,9[3_06_e_a+5_02_e_b]"],
"bonds": ["deputShip 2/53","emptyShip 1/4","kjeragShip 0/7","preciShip 2/125","sargonShip 1/91","steadShip 1/81","victoriaShip 0/5","yanShip 0/5"]
"stats": {"activatedLayers":281,"bossDamage":147152,"dmgDealt":1866747,"gold":139,"itemsEquipped":20,"kills":323,"leaks":0,"lpLost":0,"merges":6,"perfectRounds":13,"refreshes":18},
"lineup": ["6_03_a@12,4","5_02_b@11,6[3_06_e_a+5_02_e_b]","4_20_b@11,7","2_08_b@10,4[4_04_e_a+4_02_e_a]","1_12_b@10,8[3_02_e_a+4_05_e_a]","4_22_b@9,4","6_08_a@9,8[5_01_e_a+4_07_e_a]","6_05_a@9,9[5_09_e_a+3_04_e_a]"],
"bonds": ["deputShip 0/53","kjeragShip 0/2","preciShip 2/124","sargonShip 1/85","steadShip 1/72","victoriaShip 0/5","yanShip 0/5"]
},
"ai_1": {
"alive": true,
@@ -994,9 +990,9 @@
"title": {"id":"comment_3","name":"坚若磐石","picId":"comment_icon_3","text":"目标生命值损失最少"},
"trophies": 3,
"reward": 250,
"stats": {"activatedLayers":213,"bossDamage":49702,"dmgDealt":1353781,"gold":130,"itemsEquipped":17,"kills":246,"leaks":37,"lpLost":0,"merges":5,"perfectRounds":11,"refreshes":20},
"lineup": ["2_14_b@12,7","5_10_a@11,6[4_08_e_a+3_01_e_b]","5_20_a@11,7[3_07_e_a+5_02_e_b]","4_16_a@11,8[4_04_e_a+4_11_e_a]","1_20_b@10,7","6_06_a@10,8[4_05_e_b+2_04_e_a]","1_13_b@9,8","6_06_a@9,9"],
"bonds": ["deputShip 1/94","emptyShip 1/23","indomShip 1/14","preciShip 0/4","sargonShip 0/2","siracusaShip 1/82","steadShip 0/4"]
"stats": {"activatedLayers":237,"bossDamage":75279,"dmgDealt":1384510,"gold":135,"itemsEquipped":22,"kills":248,"leaks":35,"lpLost":0,"merges":5,"perfectRounds":11,"refreshes":17},
"lineup": ["2_14_b@12,3","5_10_a@11,6[4_08_e_a+3_01_e_a]","6_12_a@11,7[1_01_e_a+4_05_e_b]","5_20_a@11,8[2_01_e_a+1_05_e_a]","6_14_a@10,3","2_16_b@10,8[4_03_e_a+4_06_e_a]","1_13_b@9,8[5_02_e_b+5_02_e_b]","4_16_a@9,9[4_04_e_a+4_11_e_a]"],
"bonds": ["deputShip 2/102","emptyShip 1/27","indomShip 0/6","preciShip 0/12","sargonShip 0/2","siracusaShip 1/108","steadShip 0/4"]
},
"ai_2": {
"alive": true,
@@ -1005,12 +1001,12 @@
"eliminatedRound": null,
"lp": 28,
"bandId": "band_mlyss",
"title": {"id":"comment_5","name":"万事俱备","picId":"comment_icon_5","text":"装备配发数量最多"},
"title": {"id":"comment_4","name":"精英云集","picId":"comment_icon_4","text":"晋升精锐次数最多"},
"trophies": 3,
"reward": 250,
"stats": {"activatedLayers":240,"bossDamage":315493,"dmgDealt":1782088,"gold":131,"itemsEquipped":20,"kills":287,"leaks":1,"lpLost":0,"merges":4,"perfectRounds":12,"refreshes":21},
"lineup": ["6_14_a@12,3","6_18_a@11,3[3_01_e_a+1_02_e_a]","1_07_b@11,7[1_01_e_a+1_05_e_a]","5_10_a@11,8[4_03_e_b+5_01_e_b]","3_10_a@10,3","3_18_b@9,4[2_01_e_b+4_02_e_a]","5_20_a@9,8[3_02_e_a+2_01_e_b]","2_16_b@9,9[3_07_e_a]"],
"bonds": ["emptyShip 1/0","sargonShip 0/8","siracusaShip 2/216","swiftShip 1/24","yanShip 0/1"]
"stats": {"activatedLayers":280,"bossDamage":320516,"dmgDealt":1866796,"gold":129,"itemsEquipped":20,"kills":288,"leaks":0,"lpLost":0,"merges":5,"perfectRounds":13,"refreshes":16},
"lineup": ["6_14_a@11,6","3_10_b@11,7","5_10_a@11,8[4_03_e_b+4_12_e_a]","3_18_b@10,4[3_07_e_a+4_02_e_a]","2_16_b@10,8[5_01_e_a+2_01_e_a]","4_16_b@9,4","6_18_a@9,8[4_03_e_a+5_02_e_a]","5_20_a@9,9[3_02_e_a+2_01_e_b]"],
"bonds": ["emptyShip 1/2","sargonShip 0/8","siracusaShip 2/254","swiftShip 1/24","yanShip 0/1"]
},
"ai_3": {
"alive": true,
@@ -1022,9 +1018,9 @@
"title": {"id":"comment_1","name":"卫戍之星","picId":"comment_icon_1","text":"对敌方领袖造成伤害最高(仅胜利时)"},
"trophies": 3,
"reward": 250,
"stats": {"activatedLayers":320,"bossDamage":633429,"dmgDealt":2257252,"gold":136,"itemsEquipped":37,"kills":305,"leaks":0,"lpLost":0,"merges":4,"perfectRounds":13,"refreshes":23},
"lineup": ["3_20_b@12,8[5_02_e_b+6_03_e_a]","6_14_a@11,3[4_03_e_a+4_05_e_a]","4_13_b@11,6[3_11_e_a+5_02_e_b]","6_01_a@11,7[4_04_e_b+1_01_e_b]","4_14_a@11,8[2_01_e_b+5_01_e_a]","4_22_a@10,8[4_02_e_a+3_08_e_a]","3_11_b@9,8[4_09_e_a+3_07_e_a]","5_14_a@9,9[4_06_e_b+4_10_e_a]"],
"bonds": ["deputShip 1/28","emptyShip 0/14","kjeragShip 1/101","preciShip 2/133","siracusaShip 0/3","swiftShip 1/58","yanShip 0/1"]
"stats": {"activatedLayers":245,"bossDamage":597053,"dmgDealt":2088676,"gold":134,"itemsEquipped":38,"kills":283,"leaks":0,"lpLost":0,"merges":3,"perfectRounds":13,"refreshes":20},
"lineup": ["4_21_a@12,3","6_08_a@11,6[4_11_e_a]","6_01_a@11,7","4_13_b@11,8[2_01_e_b+5_02_e_b]","3_20_b@10,3[5_02_e_b+6_03_e_a]","4_22_a@10,8[4_04_e_a+4_02_e_a]","3_11_b@9,8[3_07_e_b+3_06_e_a]","5_14_a@9,9[4_06_e_b+4_04_e_b]"],
"bonds": ["deputShip 1/24","emptyShip 0/14","kjeragShip 1/104","preciShip 2/117","siracusaShip 0/3","swiftShip 0/57","yanShip 0/1"]
}
}
},
@@ -1203,8 +1199,8 @@
"combat": "server",
"boost": null,
"setup": {"stageId":"act1autochess_m01","bossId":"boss_1","hiddenBossId":"boss_9","factions":["FLY","DOT","REFLECTION"]},
"end": {"victory":true,"roundsPassed":14,"hiddenReached":false,"hiddenCleared":false,"reason":"victory","teamLp":42,"bossPoolLeft":0,"virtualMs":122900},
"rng": {"setup":25,"shop":550,"waves":0,"draft":34,"bots":18090,"meta":100},
"end": {"victory":true,"roundsPassed":14,"hiddenReached":false,"hiddenCleared":false,"reason":"victory","teamLp":46,"bossPoolLeft":0,"virtualMs":116900},
"rng": {"setup":25,"shop":485,"waves":0,"draft":34,"bots":18112,"meta":106},
"errors": {"engine":0,"logged":0,"sim":0,"dispatcher":0},
"rounds": [
{
@@ -1214,63 +1210,63 @@
},
{
"round": 2,
"ai_0": [1,29,5,0,0,1,4,"76d92108",2,"arcaneShip 0/2, preciShip 1/2, soloShip 1/0",10,11,1,9],
"ai_1": [1,28,5,0,0,1,4,"fca300a0",2,"arcaneShip 0/2, kjeragShip 0/2, preciShip 1/2, soloShip 1/0, steadShip 0/2",10,10,0,9]
"ai_0": [1,30,5,0,2,1,4,"4678f135",2,"arcaneShip 0/2, preciShip 1/2, soloShip 1/0",11,11,0,9],
"ai_1": [1,28,5,0,0,1,4,"a2f52619",2,"arcaneShip 0/2, kjeragShip 0/2, preciShip 1/2, soloShip 1/0, steadShip 0/2",10,10,0,9]
},
{
"round": 3,
"ai_0": [1,29,6,0,0,1,7,"b7a24935",5,"arcaneShip 0/2, preciShip 1/2, siracusaShip 0/3, soloShip 1/3",9,9,0,15],
"ai_1": [1,28,6,0,0,1,7,"bf48e672",6,"arcaneShip 0/2, kjeragShip 0/4, preciShip 1/2, soloShip 1/0, steadShip 1/4",9,9,0,15]
"ai_0": [1,30,8,0,0,1,7,"388a275b",5,"arcaneShip 0/2, preciShip 1/2, siracusaShip 0/3, soloShip 1/3",9,9,0,16],
"ai_1": [1,28,6,0,0,1,7,"d5a926f3",6,"arcaneShip 0/2, kjeragShip 0/4, preciShip 1/2, soloShip 1/0, steadShip 1/4",9,9,0,15]
},
{
"round": 4,
"ai_0": [1,29,7,0,1,1,8,"e64a4d06",13,"arcaneShip 0/2, kjeragShip 0/2, preciShip 1/2, siracusaShip 0/6, soloShip 1/9, steadShip 1/2",13,13,0,22],
"ai_1": [1,28,7,0,0,2,8,"ef6d85b4",6,"arcaneShip 0/2, kjeragShip 0/4, preciShip 1/2, soloShip 1/0, steadShip 1/4",12,12,0,22]
"ai_0": [1,30,7,0,2,1,8,"3158c50f",13,"arcaneShip 0/2, kjeragShip 0/2, preciShip 1/2, siracusaShip 0/6, soloShip 1/9, steadShip 1/2",13,13,0,23],
"ai_1": [1,28,7,0,0,2,8,"1db5c96f",6,"arcaneShip 0/2, kjeragShip 0/4, preciShip 1/2, soloShip 1/0, steadShip 1/4",12,12,0,22]
},
{
"round": 5,
"ai_0": [1,29,9,0,0,3,8,"c1f02bc4",13,"arcaneShip 0/2, kjeragShip 0/2, preciShip 1/2, siracusaShip 0/6, soloShip 1/9, steadShip 1/2",15,15,0,31],
"ai_0": [1,30,10,0,0,3,8,"e8ba0f59",13,"arcaneShip 0/2, kjeragShip 0/2, preciShip 1/2, siracusaShip 0/6, soloShip 1/9, steadShip 1/2",15,15,0,33],
"ai_1": [1,28,8,0,2,3,8,"a3c40192",6,"arcaneShip 0/2, kjeragShip 0/4, preciShip 1/2, soloShip 1/0, steadShip 1/4",15,15,0,30]
},
{
"round": 6,
"ai_0": [1,29,9,0,3,3,8,"5840167c",27,"arcaneShip 0/2, kjeragShip 0/2, preciShip 1/2, siracusaShip 1/12, soloShip 1/11, steadShip 2/2",25,23,0,39],
"ai_1": [1,28,11,0,1,3,8,"c883cfec",11,"arcaneShip 0/2, kjeragShip 0/9, preciShip 2/7, siracusaShip 1/0, soloShip 1/0, steadShip 1/4",22,22,0,41]
"ai_0": [1,30,9,0,3,3,8,"64e268da",27,"arcaneShip 0/2, kjeragShip 0/2, preciShip 1/2, siracusaShip 1/12, soloShip 1/11, steadShip 2/2",23,23,0,41],
"ai_1": [1,28,11,0,1,3,8,"9fbb73ca",11,"arcaneShip 0/2, kjeragShip 0/9, preciShip 2/7, siracusaShip 1/0, soloShip 1/0, steadShip 1/4",22,22,0,41]
},
{
"round": 7,
"ai_0": [1,19,13,0,2,4,8,"2261a840",41,"arcaneShip 0/2, deputShip 1/2, kjeragShip 0/2, preciShip 1/4, siracusaShip 1/22, soloShip 1/13, steadShip 0/2",17,27,10,51],
"ai_1": [1,23,11,0,1,4,8,"a0f53595",17,"arcaneShip 0/2, kjeragShip 0/9, preciShip 2/7, siracusaShip 1/6, soloShip 1/0, steadShip 1/4, swiftShip 0/6",22,27,5,53]
"ai_0": [1,20,13,0,2,4,8,"e485391c",41,"arcaneShip 0/2, deputShip 1/2, kjeragShip 0/2, preciShip 1/4, siracusaShip 1/22, soloShip 1/13, steadShip 0/2",16,27,11,53],
"ai_1": [1,26,11,0,1,4,8,"ba8ea888",17,"arcaneShip 0/2, kjeragShip 0/9, preciShip 2/7, siracusaShip 1/6, soloShip 1/0, steadShip 1/4, swiftShip 0/6",25,27,2,53]
},
{
"round": 8,
"ai_0": [1,19,13,0,0,4,8,"876b6843",81,"arcaneShip 1/12, deputShip 2/6, kjeragShip 0/2, preciShip 1/12, siracusaShip 1/34, soloShip 1/17, steadShip 0/2",23,23,0,66],
"ai_1": [1,23,12,0,0,4,8,"170e7347",43,"arcaneShip 1/8, kjeragShip 0/13, preciShip 1/7, siracusaShip 2/28, soloShip 1/0, steadShip 0/8, swiftShip 0/6",21,23,2,63]
"ai_0": [1,20,13,0,0,4,8,"07dcbf6d",81,"arcaneShip 1/12, deputShip 2/6, kjeragShip 0/2, preciShip 1/12, siracusaShip 1/34, soloShip 1/17, steadShip 0/2",23,23,0,68],
"ai_1": [1,26,12,0,0,4,8,"c6ebef9d",43,"arcaneShip 1/8, kjeragShip 0/13, preciShip 1/7, siracusaShip 2/28, soloShip 1/0, steadShip 0/8, swiftShip 0/6",23,23,0,63]
},
{
"round": 9,
"ai_0": [1,19,12,0,1,4,8,"1adfa3ff",119,"arcaneShip 1/23, deputShip 1/8, emptyShip 1/2, indomShip 0/8, kjeragShip 0/2, preciShip 1/18, siracusaShip 1/47, soloShip 1/19, steadShip 0/2, suntShip 1/2, victoriaShip 0/8, visiShip 0/8",16,16,0,76],
"ai_1": [1,23,12,0,0,4,8,"2376d356",71,"arcaneShip 1/14, indomShip 0/8, kjeragShip 0/13, miraShip 1/0, preciShip 1/7, siracusaShip 2/50, soloShip 1/0, steadShip 0/8, swiftShip 0/6, victoriaShip 0/8, visiShip 0/8",15,15,0,74]
"ai_0": [1,20,12,0,2,4,8,"4bdb7882",113,"arcaneShip 1/20, deputShip 1/8, emptyShip 1/2, indomShip 0/8, kjeragShip 0/2, preciShip 1/18, siracusaShip 1/44, soloShip 1/19, steadShip 0/2, suntShip 1/2, victoriaShip 0/8, visiShip 0/8",16,16,0,80],
"ai_1": [1,26,12,0,0,4,8,"f7fe62df",84,"arcaneShip 1/16, indomShip 0/8, kjeragShip 0/13, miraShip 1/3, preciShip 1/9, siracusaShip 2/56, soloShip 1/0, steadShip 0/8, swiftShip 0/6, victoriaShip 0/8, visiShip 0/8",15,15,0,74]
},
{
"round": 10,
"ai_0": [1,19,13,0,4,5,8,"d7f57e17",144,"arcaneShip 1/27, deputShip 1/10, emptyShip 1/4, indomShip 0/8, kjeragShip 0/2, preciShip 1/25, siracusaShip 1/53, soloShip 1/21, steadShip 0/2, suntShip 1/4, victoriaShip 0/8, visiShip 0/8",24,24,0,89],
"ai_1": [1,23,12,0,0,5,8,"c9414faf",92,"arcaneShip 1/16, indomShip 0/8, kjeragShip 0/13, miraShip 1/3, preciShip 1/7, siracusaShip 2/66, soloShip 1/0, steadShip 0/8, swiftShip 0/6, victoriaShip 0/8, visiShip 0/8",23,23,0,86]
"ai_0": [1,20,14,0,4,5,8,"1feec931",138,"arcaneShip 1/24, deputShip 1/10, emptyShip 1/4, indomShip 0/8, kjeragShip 0/2, preciShip 1/25, siracusaShip 1/50, soloShip 1/21, steadShip 0/2, suntShip 1/4, victoriaShip 0/8, visiShip 0/8",24,24,0,93],
"ai_1": [1,26,12,0,0,5,8,"7b71e64a",105,"arcaneShip 1/18, indomShip 0/8, kjeragShip 0/13, miraShip 1/6, preciShip 1/9, siracusaShip 2/72, soloShip 1/0, steadShip 0/8, swiftShip 0/6, victoriaShip 0/8, visiShip 0/8",23,23,0,86]
},
{
"round": 11,
"ai_0": [1,19,16,0,0,5,8,"d9d7baf5",196,"arcaneShip 1/35, deputShip 1/12, emptyShip 1/6, indomShip 0/8, kjeragShip 0/2, preciShip 1/32, siracusaShip 1/69, soloShip 1/23, steadShip 0/2, suntShip 1/6, swiftShip 1/13, victoriaShip 0/8, visiShip 0/8",21,23,2,106],
"ai_1": [1,23,12,0,0,5,8,"b94df622",144,"arcaneShip 1/22, indomShip 0/8, kjeragShip 0/13, miraShip 1/5, preciShip 1/7, siracusaShip 2/110, soloShip 1/0, steadShip 0/8, swiftShip 0/6, victoriaShip 0/8, visiShip 0/8",23,23,0,98]
"ai_0": [1,20,16,0,0,5,8,"65d57ecf",151,"arcaneShip 1/32, deputShip 0/10, emptyShip 1/4, indomShip 0/8, kjeragShip 0/2, preciShip 2/32, raidShip 1/0, siracusaShip 1/58, soloShip 1/21, steadShip 0/2, suntShip 1/4, victoriaShip 0/8, visiShip 0/8",23,23,0,110],
"ai_1": [1,26,12,0,0,5,8,"6412e274",145,"arcaneShip 2/30, indomShip 0/8, kjeragShip 0/13, miraShip 1/9, preciShip 0/9, siracusaShip 2/106, soloShip 1/0, steadShip 0/8, swiftShip 0/6, victoriaShip 0/8, visiShip 0/8",23,23,0,98]
},
{
"round": 12,
"ai_0": [1,19,12,0,0,6,8,"9673c983",228,"arcaneShip 1/39, deputShip 1/14, emptyShip 1/8, indomShip 0/8, kjeragShip 0/2, preciShip 1/41, siracusaShip 1/73, soloShip 1/25, steadShip 0/2, suntShip 1/8, swiftShip 1/20, victoriaShip 0/8, visiShip 0/8",36,46,10,118],
"ai_1": [1,23,12,0,2,5,8,"7af40c48",221,"arcaneShip 1/30, indomShip 0/8, kjeragShip 0/13, miraShip 1/8, preciShip 1/9, siracusaShip 2/168, soloShip 1/0, steadShip 0/8, swiftShip 1/6, victoriaShip 0/8, visiShip 0/8",46,46,0,112]
"ai_0": [1,20,12,0,1,6,8,"d8b6db14",156,"arcaneShip 1/32, deputShip 0/10, emptyShip 1/4, indomShip 0/8, kjeragShip 0/2, preciShip 2/37, raidShip 1/0, siracusaShip 1/58, soloShip 1/21, steadShip 0/2, suntShip 1/4, victoriaShip 0/8, visiShip 0/8",47,47,0,123],
"ai_1": [1,26,12,0,0,6,8,"6e6d725d",194,"arcaneShip 2/30, indomShip 0/8, kjeragShip 0/13, miraShip 1/12, preciShip 0/9, siracusaShip 2/152, soloShip 1/0, steadShip 0/8, swiftShip 0/6, victoriaShip 0/8, visiShip 0/8",46,46,0,111]
},
{
"round": 13,
"ai_0": [1,19,12,0,0,6,8,"6b48239d",268,"arcaneShip 1/47, deputShip 1/16, emptyShip 1/10, indomShip 0/8, kjeragShip 0/2, preciShip 1/48, siracusaShip 1/83, soloShip 1/27, steadShip 0/2, suntShip 1/10, swiftShip 1/27, victoriaShip 0/8, visiShip 0/8",28,39,11,128],
"ai_1": [1,23,14,0,0,6,8,"03a221b4",270,"arcaneShip 1/32, indomShip 0/8, kjeragShip 0/13, miraShip 1/11, preciShip 1/9, siracusaShip 2/212, soloShip 1/0, steadShip 0/8, swiftShip 1/6, victoriaShip 0/8, visiShip 0/8",39,39,0,126]
"ai_0": [1,20,13,0,0,6,8,"dfdf661e",164,"arcaneShip 1/36, deputShip 0/10, emptyShip 1/4, indomShip 0/8, kjeragShip 0/2, preciShip 2/37, raidShip 1/0, siracusaShip 1/62, soloShip 1/21, steadShip 0/2, suntShip 1/4, victoriaShip 0/8, visiShip 0/8",38,39,1,135],
"ai_1": [1,26,12,0,0,6,8,"61848044",279,"arcaneShip 2/40, indomShip 0/8, kjeragShip 0/13, miraShip 1/15, preciShip 0/9, siracusaShip 2/224, soloShip 1/0, steadShip 0/8, suntShip 1/0, swiftShip 0/6, victoriaShip 0/8, visiShip 0/8",39,39,0,121]
}
],
"players": {
@@ -1279,28 +1275,28 @@
"victory": true,
"roundsPassed": 14,
"eliminatedRound": null,
"lp": 19,
"lp": 20,
"bandId": "band_doberm",
"title": {"id":"comment_2","name":"不朽盟约","picId":"comment_icon_2","text":"激活的盟约层数总和最高"},
"title": {"id":"comment_5","name":"万事俱备","picId":"comment_icon_5","text":"装备配发数量最多"},
"trophies": 4,
"reward": 400,
"stats": {"activatedLayers":312,"bossDamage":190349,"dmgDealt":3265362,"gold":143,"itemsEquipped":17,"kills":253,"leaks":34,"lpLost":11,"merges":3,"perfectRounds":8,"refreshes":26},
"lineup": ["2_14_a@12,3","5_10_a@11,3[1_01_e_a+4_08_e_a]","1_17_a@11,6[1_02_e_a+1_05_e_a]","1_06_b@11,7","4_14_a@10,3[4_06_e_a+4_02_e_a]","4_10_a@10,7[3_11_e_a+6_01_e_a]","1_08_b@10,8[5_02_e_a+4_03_e_b]","3_18_a@9,9[3_04_e_a+4_05_e_a]"],
"bonds": ["arcaneShip 1/57","deputShip 1/18","emptyShip 1/12","indomShip 0/8","kjeragShip 0/2","preciShip 1/57","siracusaShip 1/93","soloShip 1/29","steadShip 0/2","suntShip 1/12","swiftShip 1/34","victoriaShip 0/8","visiShip 0/8"]
"stats": {"activatedLayers":176,"bossDamage":279994,"dmgDealt":3560951,"gold":149,"itemsEquipped":22,"kills":273,"leaks":12,"lpLost":10,"merges":4,"perfectRounds":11,"refreshes":20},
"lineup": ["5_10_a@11,3[6_10_e_a+3_03_e_b]","6_12_a@11,6[2_01_e_a+6_09_e_a]","6_11_a@11,7","1_17_a@10,3[1_05_e_a+1_02_e_b]","4_10_a@10,7[3_11_e_a+4_03_e_a]","4_16_b@10,8[3_05_e_b+5_01_e_a]","6_08_a@9,8[6_01_e_a+4_02_e_a]","1_08_b@9,9[5_02_e_a+1_01_e_b]"],
"bonds": ["arcaneShip 1/42","deputShip 0/10","emptyShip 1/4","indomShip 0/8","kjeragShip 0/2","maniShip 1/0","preciShip 2/37","siracusaShip 1/68","soloShip 1/21","steadShip 0/2","suntShip 1/4","victoriaShip 0/8","visiShip 0/8"]
},
"ai_1": {
"alive": true,
"victory": true,
"roundsPassed": 14,
"eliminatedRound": null,
"lp": 23,
"lp": 26,
"bandId": "band_malkie",
"title": {"id":"comment_1","name":"卫戍之星","picId":"comment_icon_1","text":"对敌方领袖造成伤害最高(仅胜利时)"},
"trophies": 4,
"reward": 400,
"stats": {"activatedLayers":294,"bossDamage":1159651,"dmgDealt":5116628,"gold":136,"itemsEquipped":19,"kills":284,"leaks":7,"lpLost":5,"merges":4,"perfectRounds":11,"refreshes":26},
"lineup": ["5_20_a@11,3[3_11_e_a+5_02_e_a]","1_17_a@11,6[5_07_e_a+1_02_e_b]","3_19_a@11,7","6_18_a@10,3[3_06_e_b]","4_10_a@10,7[2_01_e_b+3_04_e_a]","1_08_b@10,8","6_05_a@9,8[1_01_e_b+4_07_e_a]","3_18_a@9,9[4_05_e_a+4_06_e_b]"],
"bonds": ["arcaneShip 1/38","indomShip 0/8","kjeragShip 0/13","miraShip 1/11","preciShip 1/9","siracusaShip 2/230","soloShip 1/0","steadShip 0/8","swiftShip 1/6","victoriaShip 0/8","visiShip 0/8"]
"stats": {"activatedLayers":321,"bossDamage":1070006,"dmgDealt":4448912,"gold":137,"itemsEquipped":21,"kills":267,"leaks":2,"lpLost":2,"merges":5,"perfectRounds":12,"refreshes":21},
"lineup": ["6_18_a@11,3[3_11_e_a+5_07_e_a]","3_19_a@11,6[4_04_e_a+3_04_e_a]","1_17_b@11,7[1_02_e_a]","5_20_a@10,3[4_07_e_a+4_03_e_a]","4_10_a@10,7[2_01_e_b+1_01_e_a]","2_16_b@10,8[4_06_e_a+1_02_e_b]","5_12_a@9,8","1_08_b@9,9[4_10_e_a+4_05_e_a]"],
"bonds": ["arcaneShip 2/56","indomShip 0/8","kjeragShip 0/13","miraShip 1/15","preciShip 0/13","siracusaShip 2/250","soloShip 1/0","steadShip 0/8","suntShip 1/0","swiftShip 0/6","victoriaShip 0/8","visiShip 0/8"]
}
}
},
@@ -1398,8 +1394,8 @@
"combat": "client",
"boost": {"lp":400,"layers":300},
"setup": {"stageId":"act2autochess_m04","bossId":"boss_5","hiddenBossId":"boss_9","factions":["FLY","ELEMENT","INVISIBLE"]},
"end": {"victory":true,"roundsPassed":15,"hiddenReached":true,"hiddenCleared":true,"reason":"victory","teamLp":795,"bossPoolLeft":0,"virtualMs":124650},
"rng": {"setup":25,"shop":750,"waves":0,"draft":61,"bots":22165,"meta":198},
"end": {"victory":true,"roundsPassed":15,"hiddenReached":true,"hiddenCleared":true,"reason":"victory","teamLp":543,"bossPoolLeft":0,"virtualMs":123150},
"rng": {"setup":25,"shop":591,"waves":0,"draft":61,"bots":22093,"meta":138},
"errors": {"engine":0,"logged":0,"sim":0,"dispatcher":0},
"rounds": [
{
@@ -1410,62 +1406,62 @@
{
"round": 2,
"ai_0": [1,400,5,0,0,1,4,"f5818d14",302,"arcaneShip 0/300, deputShip 0/300, egirShip 0/300, emptyShip 0/300, indomShip 0/300, investShip 0/300, kazimierzShip 0/300, kjeragShip 0/302, lateranoShip 0/300, maniShip 0/300, miraShip 0/300, preciShip 0/300, raidShip 0/300, sargonShip 0/300, siracusaShip 0/300, skillfulShip 0/300, soloShip 0/300, steadShip 1/302, suntShip 0/300, swiftShip 0/300, victoriaShip 0/300, visiShip 0/300, yanShip 0/300",9,9,0,9],
"ai_1": [1,400,5,0,1,1,4,"e14f7c28",0,"arcaneShip 0/300, deputShip 0/300, egirShip 0/300, emptyShip 0/300, indomShip 0/300, investShip 0/300, kazimierzShip 0/300, kjeragShip 0/300, lateranoShip 0/300, maniShip 0/300, miraShip 0/300, preciShip 0/300, raidShip 0/300, sargonShip 0/300, siracusaShip 0/300, skillfulShip 0/300, soloShip 0/300, steadShip 0/300, suntShip 0/300, swiftShip 0/300, victoriaShip 0/300, visiShip 0/300, yanShip 0/300",10,10,0,9]
"ai_1": [1,400,5,0,1,1,4,"1bb10c7b",0,"arcaneShip 0/300, deputShip 0/300, egirShip 0/300, emptyShip 0/300, indomShip 0/300, investShip 0/300, kazimierzShip 0/300, kjeragShip 0/300, lateranoShip 0/300, maniShip 0/300, miraShip 0/300, preciShip 0/300, raidShip 0/300, sargonShip 0/300, siracusaShip 0/300, skillfulShip 0/300, soloShip 0/300, steadShip 0/300, suntShip 0/300, swiftShip 0/300, victoriaShip 0/300, visiShip 0/300, yanShip 0/300",10,10,0,9]
},
{
"round": 3,
"ai_0": [1,400,6,0,1,1,7,"6ff64423",603,"arcaneShip 0/300, deputShip 0/300, egirShip 0/300, emptyShip 0/300, indomShip 0/300, investShip 0/300, kazimierzShip 0/302, kjeragShip 0/302, lateranoShip 0/300, maniShip 0/300, miraShip 0/300, preciShip 0/300, raidShip 0/300, sargonShip 0/300, siracusaShip 0/300, skillfulShip 0/300, soloShip 1/300, steadShip 1/303, suntShip 0/300, swiftShip 0/302, victoriaShip 0/300, visiShip 0/300, yanShip 0/300",11,11,0,15],
"ai_1": [1,400,7,0,2,1,7,"97624e1b",602,"arcaneShip 0/302, deputShip 0/300, egirShip 0/300, emptyShip 0/300, indomShip 0/300, investShip 0/300, kazimierzShip 0/300, kjeragShip 0/300, lateranoShip 0/300, maniShip 0/300, miraShip 0/300, preciShip 1/302, raidShip 0/300, sargonShip 0/300, siracusaShip 0/300, skillfulShip 0/300, soloShip 1/300, steadShip 0/300, suntShip 0/300, swiftShip 0/300, victoriaShip 0/300, visiShip 0/300, yanShip 0/300",11,11,0,16]
"ai_0": [1,400,6,0,1,1,7,"0d06d571",603,"arcaneShip 0/300, deputShip 0/300, egirShip 0/300, emptyShip 0/300, indomShip 0/300, investShip 0/300, kazimierzShip 0/302, kjeragShip 0/302, lateranoShip 0/300, maniShip 0/300, miraShip 0/300, preciShip 0/300, raidShip 0/300, sargonShip 0/300, siracusaShip 0/300, skillfulShip 0/300, soloShip 1/300, steadShip 1/303, suntShip 0/300, swiftShip 0/302, victoriaShip 0/300, visiShip 0/300, yanShip 0/300",11,11,0,15],
"ai_1": [1,400,7,0,2,1,7,"f4115666",602,"arcaneShip 0/302, deputShip 0/300, egirShip 0/300, emptyShip 0/300, indomShip 0/300, investShip 0/300, kazimierzShip 0/300, kjeragShip 0/300, lateranoShip 0/300, maniShip 0/300, miraShip 0/300, preciShip 1/302, raidShip 0/300, sargonShip 0/300, siracusaShip 0/300, skillfulShip 0/300, soloShip 1/300, steadShip 0/300, suntShip 0/300, swiftShip 0/300, victoriaShip 0/300, visiShip 0/300, yanShip 0/300",11,11,0,16]
},
{
"round": 4,
"ai_0": [1,400,8,0,1,2,8,"f2591697",1206,"arcaneShip 0/302, deputShip 0/300, egirShip 0/300, emptyShip 0/300, indomShip 0/300, investShip 0/300, kazimierzShip 0/302, kjeragShip 0/302, lateranoShip 0/300, maniShip 0/300, miraShip 0/300, preciShip 1/302, raidShip 0/300, sargonShip 0/300, siracusaShip 0/300, skillfulShip 1/300, soloShip 1/300, steadShip 1/304, suntShip 0/300, swiftShip 0/302, victoriaShip 0/300, visiShip 0/300, yanShip 0/300",21,21,0,23],
"ai_1": [1,400,9,0,4,2,8,"a5d7e73a",909,"arcaneShip 0/302, deputShip 1/302, egirShip 0/300, emptyShip 0/300, indomShip 0/300, investShip 0/300, kazimierzShip 0/300, kjeragShip 0/300, lateranoShip 0/300, maniShip 0/300, miraShip 0/300, preciShip 1/305, raidShip 0/300, sargonShip 0/300, siracusaShip 0/300, skillfulShip 0/300, soloShip 1/302, steadShip 0/300, suntShip 0/300, swiftShip 0/300, victoriaShip 0/300, visiShip 0/300, yanShip 0/300",24,24,0,24]
"ai_1": [1,400,9,0,4,2,8,"bb20c536",909,"arcaneShip 0/302, deputShip 1/302, egirShip 0/300, emptyShip 0/300, indomShip 0/300, investShip 0/300, kazimierzShip 0/300, kjeragShip 0/300, lateranoShip 0/300, maniShip 0/300, miraShip 0/300, preciShip 1/305, raidShip 0/300, sargonShip 0/300, siracusaShip 0/300, skillfulShip 0/300, soloShip 1/302, steadShip 0/300, suntShip 0/300, swiftShip 0/300, victoriaShip 0/300, visiShip 0/300, yanShip 0/300",22,22,0,24]
},
{
"round": 5,
"ai_0": [1,400,9,0,0,3,8,"e81795ce",1207,"arcaneShip 0/302, deputShip 0/300, egirShip 0/300, emptyShip 0/300, indomShip 0/300, investShip 0/300, kazimierzShip 0/302, kjeragShip 0/302, lateranoShip 0/300, maniShip 0/300, miraShip 0/300, preciShip 1/302, raidShip 0/300, sargonShip 0/300, siracusaShip 0/300, skillfulShip 1/300, soloShip 1/300, steadShip 1/305, suntShip 0/300, swiftShip 0/302, victoriaShip 0/300, visiShip 0/300, yanShip 0/300",24,24,0,31],
"ai_1": [1,400,12,0,0,3,8,"9203fcff",1219,"arcaneShip 0/302, deputShip 1/304, egirShip 0/300, emptyShip 0/300, indomShip 0/300, investShip 0/300, kazimierzShip 0/300, kjeragShip 0/300, lateranoShip 0/300, maniShip 0/300, miraShip 0/300, preciShip 1/309, raidShip 0/300, sargonShip 0/300, siracusaShip 0/300, skillfulShip 0/300, soloShip 1/304, steadShip 1/302, suntShip 0/300, swiftShip 0/300, victoriaShip 0/300, visiShip 0/300, yanShip 0/300",24,24,0,36]
"ai_0": [1,400,9,0,0,3,8,"1ba198e4",1207,"arcaneShip 0/302, deputShip 0/300, egirShip 0/300, emptyShip 0/300, indomShip 0/300, investShip 0/300, kazimierzShip 0/302, kjeragShip 0/302, lateranoShip 0/300, maniShip 0/300, miraShip 0/300, preciShip 1/302, raidShip 0/300, sargonShip 0/300, siracusaShip 0/300, skillfulShip 1/300, soloShip 1/300, steadShip 1/305, suntShip 0/300, swiftShip 0/302, victoriaShip 0/300, visiShip 0/300, yanShip 0/300",24,24,0,31],
"ai_1": [1,400,12,0,0,3,8,"db4f0eb6",1219,"arcaneShip 0/302, deputShip 1/304, egirShip 0/300, emptyShip 0/300, indomShip 0/300, investShip 0/300, kazimierzShip 0/300, kjeragShip 0/300, lateranoShip 0/300, maniShip 0/300, miraShip 0/300, preciShip 1/309, raidShip 0/300, sargonShip 0/300, siracusaShip 0/300, skillfulShip 0/300, soloShip 1/304, steadShip 1/302, suntShip 0/300, swiftShip 0/300, victoriaShip 0/300, visiShip 0/300, yanShip 0/300",24,24,0,36]
},
{
"round": 6,
"ai_0": [1,400,9,0,2,3,8,"765371f3",1528,"arcaneShip 1/307, deputShip 1/302, egirShip 0/300, emptyShip 0/300, indomShip 0/300, investShip 0/300, kazimierzShip 0/302, kjeragShip 0/302, lateranoShip 0/300, maniShip 0/300, miraShip 0/300, preciShip 1/304, raidShip 0/300, sargonShip 0/300, siracusaShip 0/300, skillfulShip 0/300, soloShip 1/302, steadShip 1/313, suntShip 0/300, swiftShip 0/302, victoriaShip 0/300, visiShip 0/300, yanShip 0/300",27,27,0,41],
"ai_1": [1,400,9,0,3,3,8,"2eaa7d4c",919,"arcaneShip 0/302, deputShip 1/308, egirShip 0/300, emptyShip 0/300, indomShip 0/300, investShip 0/300, kazimierzShip 0/300, kjeragShip 0/300, lateranoShip 0/300, maniShip 0/300, miraShip 0/300, preciShip 0/309, raidShip 0/300, sargonShip 0/300, siracusaShip 0/300, skillfulShip 0/300, soloShip 1/306, steadShip 2/305, suntShip 0/300, swiftShip 0/300, victoriaShip 0/300, visiShip 0/300, yanShip 0/300",28,28,0,44]
"ai_1": [1,400,9,0,2,3,8,"12bd0f90",921,"arcaneShip 0/302, deputShip 1/307, egirShip 0/300, emptyShip 0/300, indomShip 0/300, investShip 0/300, kazimierzShip 0/300, kjeragShip 0/300, lateranoShip 0/300, maniShip 0/300, miraShip 0/300, preciShip 0/309, raidShip 0/300, sargonShip 0/300, siracusaShip 0/300, skillfulShip 0/300, soloShip 1/306, steadShip 2/308, suntShip 0/300, swiftShip 0/300, victoriaShip 0/300, visiShip 0/300, yanShip 0/300",28,28,0,44]
},
{
"round": 7,
"ai_0": [1,400,12,0,1,4,8,"d26ea2d9",1544,"arcaneShip 1/312, deputShip 1/304, egirShip 0/300, emptyShip 0/300, indomShip 0/300, investShip 0/300, kazimierzShip 0/302, kjeragShip 0/302, lateranoShip 0/300, maniShip 0/300, miraShip 0/300, preciShip 1/306, raidShip 0/300, sargonShip 0/300, siracusaShip 0/300, skillfulShip 0/300, soloShip 1/304, steadShip 1/318, suntShip 0/300, swiftShip 0/302, victoriaShip 0/300, visiShip 0/300, yanShip 0/300",25,25,0,54],
"ai_1": [1,400,13,0,0,4,8,"b18267fd",1242,"arcaneShip 0/302, deputShip 2/316, egirShip 0/300, emptyShip 1/304, indomShip 0/300, investShip 0/300, kazimierzShip 0/300, kjeragShip 0/300, lateranoShip 0/300, maniShip 0/300, miraShip 0/300, preciShip 0/309, raidShip 0/300, sargonShip 0/300, siracusaShip 0/300, skillfulShip 0/300, soloShip 1/310, steadShip 2/312, suntShip 0/300, swiftShip 0/300, victoriaShip 0/300, visiShip 0/300, yanShip 0/300",24,25,1,59]
"ai_0": [1,400,12,0,1,4,8,"22644261",1545,"arcaneShip 1/312, deputShip 1/304, egirShip 0/300, emptyShip 0/300, indomShip 0/300, investShip 0/300, kazimierzShip 0/302, kjeragShip 0/302, lateranoShip 0/300, maniShip 0/300, miraShip 0/300, preciShip 1/306, raidShip 0/300, sargonShip 0/300, siracusaShip 0/300, skillfulShip 0/300, soloShip 1/304, steadShip 1/319, suntShip 0/300, swiftShip 0/302, victoriaShip 0/300, visiShip 0/300, yanShip 0/300",25,25,0,54],
"ai_1": [1,400,12,0,0,4,8,"2dd93a93",938,"arcaneShip 0/302, deputShip 2/311, egirShip 0/300, emptyShip 0/300, indomShip 0/300, investShip 0/300, kazimierzShip 0/300, kjeragShip 0/300, lateranoShip 0/300, maniShip 0/300, miraShip 0/300, preciShip 0/309, raidShip 0/300, sargonShip 0/300, siracusaShip 0/300, skillfulShip 0/300, soloShip 1/310, steadShip 2/317, suntShip 0/300, swiftShip 0/300, victoriaShip 0/300, visiShip 0/300, yanShip 0/300",25,25,0,57]
},
{
"round": 8,
"ai_0": [1,400,12,0,0,4,8,"d692537c",1558,"arcaneShip 1/315, deputShip 0/304, egirShip 1/307, emptyShip 0/300, indomShip 0/300, investShip 0/300, kazimierzShip 0/302, kjeragShip 0/302, lateranoShip 0/300, maniShip 0/300, miraShip 0/300, preciShip 2/310, raidShip 0/300, sargonShip 0/300, siracusaShip 0/300, skillfulShip 0/300, soloShip 1/304, steadShip 2/322, suntShip 0/300, swiftShip 0/302, victoriaShip 0/300, visiShip 0/300, yanShip 0/300",18,18,0,64],
"ai_1": [1,400,11,0,0,4,8,"b2496d66",1574,"arcaneShip 0/302, deputShip 2/324, egirShip 0/300, emptyShip 1/308, indomShip 0/300, investShip 0/300, kazimierzShip 0/300, kjeragShip 0/300, lateranoShip 0/300, maniShip 0/300, miraShip 0/300, preciShip 0/309, raidShip 0/300, sargonShip 0/300, siracusaShip 1/318, skillfulShip 0/300, soloShip 1/314, steadShip 0/312, suntShip 0/300, swiftShip 1/310, victoriaShip 0/300, visiShip 0/300, yanShip 0/300",18,18,0,70]
"ai_0": [1,400,12,0,0,4,8,"61230d1b",1552,"arcaneShip 1/312, deputShip 0/304, egirShip 1/303, emptyShip 0/300, indomShip 0/300, investShip 0/300, kazimierzShip 0/302, kjeragShip 0/302, lateranoShip 0/300, maniShip 0/300, miraShip 0/300, preciShip 2/310, raidShip 0/300, sargonShip 0/300, siracusaShip 0/300, skillfulShip 0/300, soloShip 1/304, steadShip 2/323, suntShip 0/300, swiftShip 0/302, victoriaShip 0/300, visiShip 0/300, yanShip 0/300",18,18,0,64],
"ai_1": [1,400,11,0,1,4,8,"770dde40",1261,"arcaneShip 0/302, deputShip 2/315, egirShip 0/300, emptyShip 0/300, indomShip 1/304, investShip 0/300, kazimierzShip 0/300, kjeragShip 0/300, lateranoShip 0/300, maniShip 0/300, miraShip 0/300, preciShip 0/309, raidShip 0/300, sargonShip 0/300, siracusaShip 0/300, skillfulShip 0/300, soloShip 1/314, steadShip 1/328, suntShip 0/300, swiftShip 0/300, victoriaShip 0/305, visiShip 0/300, yanShip 0/305",19,19,0,68]
},
{
"round": 9,
"ai_0": [1,400,12,0,0,4,8,"cdec5dfa",2231,"arcaneShip 2/318, deputShip 0/304, egirShip 1/319, emptyShip 0/300, indomShip 0/300, investShip 0/300, kazimierzShip 0/302, kjeragShip 0/302, lateranoShip 0/300, maniShip 0/300, miraShip 0/300, preciShip 1/310, raidShip 1/353, sargonShip 0/300, siracusaShip 0/300, skillfulShip 0/300, soloShip 1/304, steadShip 1/327, suntShip 1/300, swiftShip 0/302, victoriaShip 0/300, visiShip 0/300, yanShip 0/300",24,24,0,77],
"ai_1": [1,400,12,0,3,6,8,"36a202d9",2210,"arcaneShip 0/302, deputShip 1/326, egirShip 0/300, emptyShip 0/308, indomShip 0/300, investShip 0/300, kazimierzShip 0/300, kjeragShip 0/300, lateranoShip 0/300, maniShip 0/300, miraShip 1/306, preciShip 0/309, raidShip 0/300, sargonShip 0/300, siracusaShip 1/342, skillfulShip 1/302, soloShip 1/316, steadShip 0/312, suntShip 1/302, swiftShip 1/316, victoriaShip 0/300, visiShip 0/300, yanShip 0/300",29,25,0,114]
"ai_0": [1,400,12,0,0,4,8,"b75b1b8c",2237,"arcaneShip 2/312, deputShip 0/304, egirShip 1/314, emptyShip 0/300, indomShip 0/300, investShip 0/300, kazimierzShip 1/310, kjeragShip 0/302, lateranoShip 0/300, maniShip 0/300, miraShip 0/300, preciShip 1/314, raidShip 1/353, sargonShip 0/300, siracusaShip 0/300, skillfulShip 0/300, soloShip 1/304, steadShip 1/330, suntShip 0/300, swiftShip 0/306, victoriaShip 0/300, visiShip 0/300, yanShip 0/300",24,24,0,77],
"ai_1": [1,400,13,0,3,4,8,"d94eb18f",1569,"arcaneShip 0/302, deputShip 1/317, egirShip 0/300, emptyShip 0/300, indomShip 1/306, investShip 0/300, kazimierzShip 0/300, kjeragShip 0/300, lateranoShip 0/300, maniShip 0/300, miraShip 0/300, preciShip 0/309, raidShip 0/300, sargonShip 0/300, siracusaShip 0/300, skillfulShip 1/302, soloShip 0/314, steadShip 1/336, suntShip 0/300, swiftShip 0/300, victoriaShip 1/308, visiShip 0/300, yanShip 0/305",24,24,0,80]
},
{
"round": 10,
"ai_0": [1,400,12,0,0,5,8,"582856d0",2251,"arcaneShip 2/318, deputShip 0/304, egirShip 1/327, emptyShip 0/300, indomShip 0/300, investShip 0/300, kazimierzShip 0/302, kjeragShip 0/304, lateranoShip 0/300, maniShip 0/300, miraShip 0/300, preciShip 1/310, raidShip 1/363, sargonShip 0/300, siracusaShip 0/300, skillfulShip 0/300, soloShip 1/304, steadShip 1/329, suntShip 1/300, swiftShip 0/302, victoriaShip 0/300, visiShip 0/300, yanShip 0/300",12,12,0,89],
"ai_1": [1,400,15,0,2,6,8,"3a43d0ba",2260,"arcaneShip 0/302, deputShip 1/328, egirShip 0/300, emptyShip 0/308, indomShip 0/300, investShip 0/300, kazimierzShip 0/300, kjeragShip 0/300, lateranoShip 0/300, maniShip 0/300, miraShip 1/314, preciShip 0/309, raidShip 0/300, sargonShip 0/300, siracusaShip 1/370, skillfulShip 1/306, soloShip 1/318, steadShip 0/312, suntShip 1/304, swiftShip 1/320, victoriaShip 0/300, visiShip 0/300, yanShip 0/300",13,13,0,130]
"ai_0": [1,400,12,0,0,5,8,"6e225676",2268,"arcaneShip 2/312, deputShip 0/304, egirShip 1/319, emptyShip 0/300, indomShip 0/300, investShip 0/300, kazimierzShip 1/318, kjeragShip 0/302, lateranoShip 0/300, maniShip 0/300, miraShip 0/300, preciShip 1/322, raidShip 1/363, sargonShip 0/300, siracusaShip 0/300, skillfulShip 0/300, soloShip 1/304, steadShip 1/330, suntShip 0/300, swiftShip 0/306, victoriaShip 0/300, visiShip 0/300, yanShip 0/300",12,12,0,92],
"ai_1": [1,400,15,0,0,5,8,"a3501bfb",1588,"arcaneShip 0/302, deputShip 1/319, egirShip 0/300, emptyShip 0/300, indomShip 0/306, investShip 0/300, kazimierzShip 0/300, kjeragShip 0/300, lateranoShip 0/300, maniShip 0/300, miraShip 0/300, preciShip 0/309, raidShip 0/300, sargonShip 0/300, siracusaShip 0/300, skillfulShip 1/314, soloShip 0/314, steadShip 1/342, suntShip 1/302, swiftShip 0/300, victoriaShip 1/311, visiShip 0/300, yanShip 0/305",12,12,0,97]
},
{
"round": 11,
"ai_0": [1,400,12,0,1,5,8,"9ac6e043",2281,"arcaneShip 2/332, deputShip 0/304, egirShip 1/340, emptyShip 0/300, indomShip 0/300, investShip 0/300, kazimierzShip 0/302, kjeragShip 0/308, lateranoShip 0/300, maniShip 0/300, miraShip 0/300, preciShip 1/315, raidShip 1/382, sargonShip 0/300, siracusaShip 0/300, skillfulShip 1/308, soloShip 1/304, steadShip 0/333, suntShip 1/300, swiftShip 0/302, victoriaShip 0/300, visiShip 0/300, yanShip 0/300",38,34,0,102],
"ai_1": [1,400,14,0,1,6,8,"120b46d6",2628,"arcaneShip 1/318, deputShip 1/330, egirShip 0/300, emptyShip 0/308, indomShip 0/300, investShip 0/300, kazimierzShip 0/300, kjeragShip 0/300, lateranoShip 0/300, maniShip 0/300, miraShip 1/322, preciShip 0/311, raidShip 0/300, sargonShip 0/300, siracusaShip 1/390, skillfulShip 1/318, soloShip 1/320, steadShip 0/312, suntShip 1/306, swiftShip 1/324, victoriaShip 0/300, visiShip 0/300, yanShip 0/300",34,34,0,144]
"ai_0": [1,400,12,0,2,5,8,"3fe9e60e",2646,"arcaneShip 1/315, deputShip 0/304, egirShip 1/338, emptyShip 0/300, indomShip 0/300, investShip 0/300, kazimierzShip 1/326, kjeragShip 0/302, lateranoShip 0/300, maniShip 0/300, miraShip 0/300, preciShip 1/335, raidShip 1/384, sargonShip 0/300, siracusaShip 0/300, skillfulShip 0/300, soloShip 1/304, steadShip 1/333, suntShip 0/300, swiftShip 1/311, victoriaShip 0/300, visiShip 0/300, yanShip 0/300",34,34,0,102],
"ai_1": [1,400,12,0,1,5,8,"44ef6174",1936,"arcaneShip 0/302, deputShip 1/321, egirShip 0/300, emptyShip 0/300, indomShip 0/306, investShip 0/300, kazimierzShip 0/300, kjeragShip 0/300, lateranoShip 0/300, maniShip 0/300, miraShip 0/300, preciShip 0/309, raidShip 0/300, sargonShip 0/300, siracusaShip 0/300, skillfulShip 1/327, soloShip 1/316, steadShip 1/351, suntShip 1/304, swiftShip 0/300, victoriaShip 1/317, visiShip 0/300, yanShip 0/305",34,34,0,110]
},
{
"round": 12,
"ai_0": [1,400,13,0,0,6,8,"afebef50",2325,"arcaneShip 2/340, deputShip 0/304, egirShip 1/345, emptyShip 0/300, indomShip 0/300, investShip 0/300, kazimierzShip 0/302, kjeragShip 0/308, lateranoShip 0/300, maniShip 0/300, miraShip 0/300, preciShip 1/320, raidShip 1/400, sargonShip 0/300, siracusaShip 0/300, skillfulShip 1/316, soloShip 1/304, steadShip 0/333, suntShip 1/300, swiftShip 0/302, victoriaShip 0/300, visiShip 0/300, yanShip 0/300",6,14,8,115],
"ai_1": [1,400,13,0,5,6,8,"9e462d87",2688,"arcaneShip 1/333, deputShip 1/332, egirShip 0/300, emptyShip 0/308, indomShip 0/300, investShip 0/300, kazimierzShip 0/300, kjeragShip 0/300, lateranoShip 0/300, maniShip 0/300, miraShip 1/328, preciShip 0/311, raidShip 0/300, sargonShip 0/300, siracusaShip 1/411, skillfulShip 1/326, soloShip 1/322, steadShip 0/312, suntShip 1/308, swiftShip 1/328, victoriaShip 0/300, visiShip 0/300, yanShip 0/300",15,15,0,152]
"ai_0": [1,400,14,0,0,6,8,"2a0b76e3",2717,"arcaneShip 1/318, deputShip 0/304, egirShip 1/351, emptyShip 0/300, indomShip 0/300, investShip 0/300, kazimierzShip 1/342, kjeragShip 0/302, lateranoShip 0/300, maniShip 0/300, miraShip 0/300, preciShip 1/356, raidShip 1/397, sargonShip 0/300, siracusaShip 0/300, skillfulShip 0/300, soloShip 1/304, steadShip 1/333, suntShip 0/300, swiftShip 1/316, victoriaShip 0/300, visiShip 0/300, yanShip 0/300",6,14,8,116],
"ai_1": [1,400,13,0,3,6,8,"224928d1",1961,"arcaneShip 0/302, deputShip 1/323, egirShip 0/300, emptyShip 0/300, indomShip 0/306, investShip 0/300, kazimierzShip 0/300, kjeragShip 0/300, lateranoShip 0/300, maniShip 0/300, miraShip 0/300, preciShip 0/309, raidShip 0/300, sargonShip 0/300, siracusaShip 0/300, skillfulShip 1/335, soloShip 1/318, steadShip 1/359, suntShip 1/306, swiftShip 0/300, victoriaShip 1/320, visiShip 0/300, yanShip 0/305",15,15,0,121]
},
{
"round": 13,
"ai_0": [1,400,12,0,2,6,8,"97689296",2380,"arcaneShip 2/349, deputShip 0/304, egirShip 1/359, emptyShip 0/300, indomShip 0/300, investShip 0/300, kazimierzShip 0/302, kjeragShip 0/308, lateranoShip 0/300, maniShip 0/300, miraShip 0/300, preciShip 1/325, raidShip 1/421, sargonShip 0/300, siracusaShip 0/300, skillfulShip 1/322, soloShip 1/304, steadShip 0/333, suntShip 1/300, swiftShip 0/302, victoriaShip 0/300, visiShip 0/300, yanShip 0/300",41,41,0,126],
"ai_1": [1,400,17,0,0,6,8,"36a5d772",2717,"arcaneShip 1/345, deputShip 0/332, egirShip 0/300, emptyShip 0/308, indomShip 0/300, investShip 0/300, kazimierzShip 0/300, kjeragShip 0/300, lateranoShip 0/300, maniShip 0/300, miraShip 1/334, preciShip 1/311, raidShip 0/300, sargonShip 0/300, siracusaShip 1/429, skillfulShip 1/338, soloShip 1/322, steadShip 0/312, suntShip 1/308, swiftShip 1/330, victoriaShip 0/300, visiShip 0/300, yanShip 0/300",41,41,0,168]
"ai_0": [1,400,12,0,2,6,8,"06928963",3079,"arcaneShip 1/321, deputShip 0/304, egirShip 1/358, emptyShip 0/300, indomShip 0/300, investShip 0/300, kazimierzShip 1/354, kjeragShip 0/302, lateranoShip 0/300, maniShip 0/300, miraShip 0/300, preciShip 1/369, raidShip 1/415, sargonShip 0/300, siracusaShip 0/300, skillfulShip 0/300, soloShip 1/304, steadShip 1/333, suntShip 1/300, swiftShip 1/325, victoriaShip 0/300, visiShip 0/300, yanShip 0/300",41,41,0,129],
"ai_1": [1,400,15,0,0,6,8,"b067967e",1988,"arcaneShip 0/302, deputShip 1/325, egirShip 0/300, emptyShip 0/300, indomShip 0/306, investShip 0/300, kazimierzShip 0/300, kjeragShip 0/300, lateranoShip 0/300, maniShip 0/300, miraShip 0/300, preciShip 0/309, raidShip 0/300, sargonShip 0/300, siracusaShip 0/300, skillfulShip 1/345, soloShip 1/320, steadShip 1/367, suntShip 1/308, swiftShip 0/300, victoriaShip 1/323, visiShip 0/300, yanShip 0/305",41,41,0,134]
}
],
"players": {
@@ -1474,28 +1470,28 @@
"victory": true,
"roundsPassed": 15,
"eliminatedRound": null,
"lp": 398,
"lp": 272,
"bandId": "band_sarkazb",
"title": {"id":"comment_1","name":"卫戍之星","picId":"comment_icon_1","text":"对敌方领袖造成伤害最高(仅胜利时)"},
"trophies": 8,
"reward": 425,
"stats": {"activatedLayers":3362,"bossDamage":7735924,"dmgDealt":20519044,"gold":154,"itemsEquipped":21,"kills":287,"leaks":8,"lpLost":0,"merges":7,"perfectRounds":12,"refreshes":21},
"lineup": ["4_09_b@11,5[3_11_e_a+3_11_e_b]","6_11_a@11,8","3_13_b@10,3","3_05_b@10,8[1_01_e_a+6_09_e_a]","6_16_a@9,6","6_05_a@9,7[3_02_e_a]","4_14_b@9,8[2_01_e_a+1_02_e_b]","5_07_a@9,9[4_10_e_b]"],
"bonds": ["arcaneShip 1/365","deputShip 0/304","egirShip 1/362","emptyShip 0/300","indomShip 0/300","investShip 0/300","kazimierzShip 0/302","kjeragShip 0/308","lateranoShip 0/300","maniShip 1/300","miraShip 0/300","preciShip 1/345","raidShip 1/423","sargonShip 1/300","siracusaShip 0/300","skillfulShip 1/338","soloShip 1/307","steadShip 0/333","suntShip 1/300","swiftShip 1/322","victoriaShip 0/300","visiShip 0/300","yanShip 0/300"]
"stats": {"activatedLayers":2790,"bossDamage":7592235,"dmgDealt":21017016,"gold":156,"itemsEquipped":19,"kills":331,"leaks":8,"lpLost":0,"merges":8,"perfectRounds":12,"refreshes":31},
"lineup": ["2_18_b@11,5[1_05_e_b+1_01_e_a]","4_14_a@11,8[4_03_e_a]","4_09_a@10,3","1_19_b@10,4","6_17_b@10,8[2_01_e_a+1_02_e_a]","3_09_a@9,7","4_20_b@9,8[1_01_e_b+3_11_e_a]","3_05_b@9,9"],
"bonds": ["arcaneShip 0/321","deputShip 0/304","egirShip 1/358","emptyShip 0/300","indomShip 0/300","investShip 0/300","kazimierzShip 1/357","kjeragShip 0/302","lateranoShip 0/300","maniShip 0/300","miraShip 0/300","preciShip 1/379","raidShip 1/424","sargonShip 0/300","siracusaShip 0/300","skillfulShip 0/300","soloShip 1/304","steadShip 1/333","suntShip 2/300","swiftShip 1/335","victoriaShip 0/300","visiShip 0/300","yanShip 0/300"]
},
"ai_1": {
"alive": true,
"victory": true,
"roundsPassed": 15,
"eliminatedRound": null,
"lp": 397,
"lp": 271,
"bandId": "band_doberm",
"title": {"id":"comment_3","name":"坚若磐石","picId":"comment_icon_3","text":"目标生命值损失最少"},
"trophies": 8,
"reward": 425,
"stats": {"activatedLayers":2801,"bossDamage":6156699,"dmgDealt":23996575,"gold":191,"itemsEquipped":32,"kills":287,"leaks":1,"lpLost":0,"merges":5,"perfectRounds":12,"refreshes":55},
"lineup": ["4_10_a@11,5[3_05_e_a+4_03_e_a]","6_05_a@11,8[4_08_e_a+6_09_e_a]","6_16_a@10,3[3_11_e_a+1_05_e_b]","2_13_a@10,7[4_04_e_a+4_05_e_a]","1_08_b@10,8[4_07_e_b+5_07_e_a]","6_08_a@9,6[2_01_e_b+3_01_e_b]","5_20_a@9,7[1_05_e_a+3_04_e_a]","6_08_a@9,8[3_03_e_a+1_01_e_a]","3_19_b@9,9[2_04_e_a+3_07_e_b]"],
"bonds": ["arcaneShip 1/371","deputShip 0/332","egirShip 0/300","emptyShip 0/308","indomShip 0/300","investShip 0/300","kazimierzShip 0/300","kjeragShip 0/300","lateranoShip 0/300","maniShip 0/300","miraShip 1/334","preciShip 1/311","raidShip 0/300","sargonShip 0/300","siracusaShip 1/465","skillfulShip 1/356","soloShip 1/322","steadShip 0/312","suntShip 1/308","swiftShip 1/334","victoriaShip 0/300","visiShip 0/300","yanShip 0/300"]
"stats": {"activatedLayers":2377,"bossDamage":6286125,"dmgDealt":26191649,"gold":159,"itemsEquipped":34,"kills":281,"leaks":0,"lpLost":0,"merges":3,"perfectRounds":13,"refreshes":23},
"lineup": ["2_17_a@12,8[4_02_e_a+3_03_e_a]","1_13_b@11,5[4_07_e_a+3_11_e_a]","5_03_a@11,8[3_02_e_a+2_03_e_b]","3_16_a@10,4[1_05_e_b+2_01_e_b]","4_11_a@10,8[4_11_e_a+4_09_e_a]","6_16_a@9,7[2_03_e_b+4_09_e_b]","1_06_b@9,8[3_06_e_a+3_08_e_b]","5_12_a@9,9[5_03_e_a+1_01_e_b]"],
"bonds": ["arcaneShip 1/332","deputShip 1/329","egirShip 0/300","emptyShip 0/300","indomShip 0/306","investShip 0/300","kazimierzShip 0/300","kjeragShip 0/300","lateranoShip 0/300","maniShip 0/300","miraShip 0/300","preciShip 0/309","raidShip 0/300","sargonShip 0/300","siracusaShip 0/300","skillfulShip 1/370","soloShip 1/324","steadShip 1/377","suntShip 1/312","swiftShip 0/300","victoriaShip 1/333","visiShip 0/300","yanShip 0/305"]
}
}
},
@@ -1638,8 +1634,8 @@
"combat": "client",
"boost": null,
"setup": {"stageId":"act1autochess_m03","bossId":"boss_7","hiddenBossId":"boss_10","factions":["ELEMENT","INVISIBLE","REFLECTION"]},
"end": {"victory":true,"roundsPassed":14,"hiddenReached":false,"hiddenCleared":false,"reason":"victory","teamLp":55,"bossPoolLeft":0,"virtualMs":119900},
"rng": {"setup":25,"shop":477,"waves":0,"draft":34,"bots":16274,"meta":23},
"end": {"victory":true,"roundsPassed":14,"hiddenReached":false,"hiddenCleared":false,"reason":"victory","teamLp":55,"bossPoolLeft":0,"virtualMs":122900},
"rng": {"setup":25,"shop":512,"waves":0,"draft":34,"bots":16483,"meta":18},
"errors": {"engine":0,"logged":0,"sim":0,"dispatcher":0},
"rounds": [
{"round":1,"h_0":[1,27,4,0,0,5,1,"3e4697a0",0,"",5,5,0,3],"ai_0":[1,28,4,0,0,1,2,"a1734bc6",0,"sargonShip 0/2, soloShip 1/0",5,5,0,4]},
@@ -1652,48 +1648,48 @@
},
{
"round": 5,
"h_0": [1,27,8,0,0,5,8,"1a62176b",30,"indomShip 1/0, preciShip 0/2, sargonShip 1/28, steadShip 1/1, victoriaShip 1/1",27,27,0,28],
"h_0": [1,27,8,0,0,5,8,"89fff9d8",29,"indomShip 1/0, preciShip 0/2, sargonShip 1/26, steadShip 1/2, victoriaShip 1/1",27,27,0,28],
"ai_0": [1,28,8,0,1,2,8,"e0bfc937",0,"sargonShip 0/2, skillfulShip 1/0, soloShip 1/0, suntShip 1/0, yanShip 0/6",27,27,0,29]
},
{
"round": 6,
"h_0": [1,27,9,0,1,5,8,"7f0036b7",93,"arcaneShip 0/2, indomShip 1/6, preciShip 1/17, sargonShip 1/50, steadShip 1/6, victoriaShip 1/8, visiShip 1/6",24,22,0,38],
"h_0": [1,27,9,0,1,5,8,"7f0036b7",92,"arcaneShip 0/2, indomShip 1/6, preciShip 1/17, sargonShip 1/48, steadShip 1/7, victoriaShip 1/8, visiShip 1/6",24,22,0,38],
"ai_0": [1,28,10,0,2,3,8,"ea720e1b",0,"sargonShip 0/4, skillfulShip 1/0, soloShip 1/0, suntShip 1/0, yanShip 0/6",22,22,0,39]
},
{
"round": 7,
"h_0": [1,27,11,0,1,5,8,"f502b39b",129,"arcaneShip 0/2, indomShip 1/10, preciShip 1/24, sargonShip 1/71, steadShip 1/11, victoriaShip 1/13, visiShip 0/6",28,26,0,50],
"h_0": [1,27,11,0,1,5,8,"5702ef65",134,"arcaneShip 0/2, indomShip 1/6, preciShip 1/40, sargonShip 1/67, steadShip 1/12, victoriaShip 1/9, visiShip 0/6",27,26,1,50],
"ai_0": [1,28,12,0,2,4,8,"6aec478c",0,"sargonShip 0/4, skillfulShip 1/0, soloShip 1/0, suntShip 1/0, yanShip 0/6",26,26,0,51]
},
{
"round": 8,
"h_0": [1,27,12,0,0,5,8,"97941a60",215,"arcaneShip 0/2, indomShip 1/22, preciShip 2/57, sargonShip 1/104, soloShip 1/0, steadShip 2/32, victoriaShip 0/25, visiShip 0/6",18,18,0,65],
"h_0": [1,27,12,0,0,5,8,"b6d1cc82",224,"arcaneShip 0/2, indomShip 1/18, preciShip 2/73, sargonShip 1/100, soloShip 1/0, steadShip 2/33, victoriaShip 0/21, visiShip 0/6",18,18,0,63],
"ai_0": [1,28,13,0,1,4,8,"8d499ef9",16,"miraShip 1/0, sargonShip 0/4, skillfulShip 1/3, soloShip 1/0, steadShip 1/0, suntShip 1/0, yanShip 1/13",17,18,1,63]
},
{
"round": 9,
"h_0": [1,27,12,0,4,5,8,"96baa6b8",260,"arcaneShip 0/2, indomShip 1/22, preciShip 1/66, sargonShip 1/117, soloShip 1/0, steadShip 2/40, swiftShip 1/15, victoriaShip 0/25, visiShip 0/6",23,23,0,76],
"ai_0": [1,28,13,0,0,4,8,"a5033a5f",77,"miraShip 1/0, preciShip 1/29, sargonShip 1/28, skillfulShip 1/3, soloShip 1/0, suntShip 1/0, yanShip 1/17",20,23,3,76]
"h_0": [1,27,12,0,4,5,8,"e8c67194",264,"arcaneShip 0/2, indomShip 1/18, preciShip 2/94, sargonShip 1/108, steadShip 1/34, victoriaShip 0/21, visiShip 1/10",22,23,1,77],
"ai_0": [1,28,13,0,2,4,8,"f213e56e",30,"sargonShip 0/4, siracusaShip 1/8, skillfulShip 1/3, soloShip 1/0, suntShip 1/0, yanShip 1/19",23,23,0,76]
},
{
"round": 10,
"h_0": [1,27,16,0,2,5,8,"831f33bf",270,"arcaneShip 0/2, indomShip 1/22, kazimierzShip 0/8, kjeragShip 0/8, preciShip 1/80, sargonShip 1/123, soloShip 1/0, steadShip 2/45, suntShip 1/0, swiftShip 0/23, victoriaShip 0/25, visiShip 0/6",31,31,0,90],
"ai_0": [1,28,12,0,0,5,8,"276b0c45",128,"miraShip 1/0, preciShip 1/56, sargonShip 1/50, skillfulShip 1/3, soloShip 1/0, suntShip 1/0, visiShip 1/0, yanShip 1/19",31,31,0,88]
"h_0": [1,27,16,0,2,5,8,"9e1684a3",309,"arcaneShip 0/2, indomShip 1/18, preciShip 2/110, sargonShip 1/116, soloShip 1/0, steadShip 2/43, suntShip 1/0, victoriaShip 0/21, visiShip 1/22",31,31,0,91],
"ai_0": [1,28,14,0,0,5,8,"dcc3d5bd",35,"sargonShip 0/4, siracusaShip 1/8, skillfulShip 1/6, soloShip 1/0, suntShip 2/0, yanShip 1/21",31,31,0,89]
},
{
"round": 11,
"h_0": [1,27,14,0,0,6,8,"e9f0f2fe",363,"arcaneShip 0/2, indomShip 1/22, kazimierzShip 0/8, kjeragShip 0/8, preciShip 2/99, sargonShip 1/147, steadShip 1/49, suntShip 1/0, swiftShip 1/40, victoriaShip 0/25, visiShip 1/6",19,19,0,102],
"ai_0": [1,28,12,0,0,5,8,"bd0a32a3",183,"preciShip 1/76, sargonShip 1/74, skillfulShip 1/3, soloShip 1/0, suntShip 1/0, victoriaShip 0/5, visiShip 1/0, yanShip 1/30",19,19,0,102]
"h_0": [1,27,16,0,0,6,8,"92c1a6c0",354,"arcaneShip 0/2, indomShip 1/18, preciShip 2/127, sargonShip 1/120, steadShip 1/46, suntShip 1/0, swiftShip 1/5, victoriaShip 0/21, visiShip 1/38",19,19,0,106],
"ai_0": [1,28,12,0,0,5,8,"57021233",53,"sargonShip 0/4, siracusaShip 1/12, skillfulShip 1/12, soloShip 1/0, suntShip 2/0, visiShip 1/0, yanShip 1/29",19,19,0,101]
},
{
"round": 12,
"h_0": [1,27,12,0,0,6,8,"efd0ac13",413,"arcaneShip 0/2, indomShip 1/26, kazimierzShip 0/8, kjeragShip 0/8, maniShip 1/0, preciShip 1/129, sargonShip 1/169, steadShip 2/54, suntShip 1/0, swiftShip 0/40, victoriaShip 1/35, visiShip 0/6",33,33,0,117],
"ai_0": [1,28,12,0,0,6,8,"931d261f",245,"preciShip 1/105, sargonShip 1/107, skillfulShip 1/3, soloShip 1/0, suntShip 1/0, victoriaShip 0/5, visiShip 1/0, yanShip 1/30",27,33,6,113]
"h_0": [1,27,14,0,2,6,8,"7d4a4b17",428,"arcaneShip 0/2, indomShip 1/18, preciShip 2/159, sargonShip 1/139, steadShip 1/48, suntShip 1/0, swiftShip 1/10, victoriaShip 0/21, visiShip 1/54",33,33,0,124],
"ai_0": [1,28,12,0,0,6,8,"672ecfc2",63,"sargonShip 0/8, siracusaShip 1/12, skillfulShip 1/18, soloShip 1/0, suntShip 2/0, yanShip 1/33",22,33,11,114]
},
{
"round": 13,
"h_0": [1,27,12,0,0,6,8,"e1220022",484,"arcaneShip 0/2, indomShip 1/28, kazimierzShip 0/8, kjeragShip 0/8, maniShip 1/0, preciShip 1/161, sargonShip 1/193, steadShip 2/59, suntShip 1/0, swiftShip 0/40, victoriaShip 1/43, visiShip 0/6",50,50,0,130],
"ai_0": [1,28,12,0,0,6,8,"a2f4a969",302,"preciShip 1/134, sargonShip 1/131, skillfulShip 1/3, soloShip 1/0, suntShip 1/0, victoriaShip 0/5, visiShip 1/0, yanShip 1/34",50,50,0,127]
"h_0": [1,27,16,0,2,6,8,"dc6467d8",466,"arcaneShip 0/2, indomShip 0/18, preciShip 2/186, sargonShip 1/189, siracusaShip 0/6, steadShip 0/48, suntShip 1/0, swiftShip 1/21, victoriaShip 0/21, visiShip 1/70",50,50,0,140],
"ai_0": [1,28,12,0,0,6,8,"a47a6f63",87,"kazimierzShip 0/2, preciShip 2/24, sargonShip 0/8, siracusaShip 0/12, skillfulShip 1/24, soloShip 1/0, suntShip 2/0, swiftShip 0/2, yanShip 1/39",44,50,6,126]
}
],
"players": {
@@ -1704,12 +1700,12 @@
"eliminatedRound": null,
"lp": 27,
"bandId": "band_yu",
"title": {"id":"comment_2","name":"不朽盟约","picId":"comment_icon_2","text":"激活的盟约层数总和最高"},
"title": {"id":"comment_1","name":"卫戍之星","picId":"comment_icon_1","text":"对敌方领袖造成伤害最高(仅胜利时)"},
"trophies": 4,
"reward": 400,
"stats": {"activatedLayers":496,"bossDamage":678256,"dmgDealt":4102418,"gold":141,"itemsEquipped":24,"kills":319,"leaks":0,"lpLost":0,"merges":2,"perfectRounds":13,"refreshes":19},
"lineup": ["4_20_b@11,8[3_05_e_a+5_03_e_a]","6_11_a@10,4[6_05_e_a+4_06_e_a]","5_02_a@10,6[5_01_e_a+6_01_e_a]","6_07_a@10,8[2_04_e_a+6_09_e_a]","3_16_a@9,4[4_05_e_a+3_09_e_a]","2_08_b@9,5[3_11_e_a+3_07_e_a]","6_03_a@9,6[4_04_e_b+2_01_e_a]","4_07_a@9,9[4_11_e_a+4_02_e_a]"],
"bonds": ["arcaneShip 0/2","indomShip 1/28","kazimierzShip 0/8","kjeragShip 0/8","maniShip 1/0","preciShip 1/161","sargonShip 1/195","steadShip 2/63","suntShip 1/0","swiftShip 0/40","victoriaShip 1/49","visiShip 0/6"]
"stats": {"activatedLayers":492,"bossDamage":1089764,"dmgDealt":4817588,"gold":156,"itemsEquipped":24,"kills":325,"leaks":2,"lpLost":0,"merges":4,"perfectRounds":11,"refreshes":32},
"lineup": ["6_05_a@12,8[3_04_e_a+6_03_e_a]","4_14_a@11,6","5_21_b@11,7[4_03_e_a+5_01_e_b]","4_20_b@11,8[1_01_e_b]","6_05_a@10,6[4_08_e_a+2_01_e_b]","5_02_a@9,7[6_01_e_a+3_11_e_a]","4_07_b@9,8[4_04_e_b+4_11_e_b]","4_23_b@9,9[4_06_e_a+4_04_e_a]"],
"bonds": ["arcaneShip 0/2","indomShip 0/18","preciShip 2/191","sargonShip 1/189","siracusaShip 0/6","steadShip 0/48","suntShip 1/0","swiftShip 1/26","victoriaShip 0/21","visiShip 1/86"]
},
"ai_0": {
"alive": true,
@@ -1718,12 +1714,12 @@
"eliminatedRound": null,
"lp": 28,
"bandId": "band_mlyss",
"title": {"id":"comment_1","name":"卫戍之星","picId":"comment_icon_1","text":"对敌方领袖造成伤害最高(仅胜利时)"},
"title": {"id":"comment_3","name":"坚若磐石","picId":"comment_icon_3","text":"目标生命值损失最少"},
"trophies": 4,
"reward": 400,
"stats": {"activatedLayers":316,"bossDamage":896744,"dmgDealt":3970823,"gold":140,"itemsEquipped":19,"kills":279,"leaks":10,"lpLost":0,"merges":4,"perfectRounds":10,"refreshes":21},
"lineup": ["5_02_a@12,8[3_01_e_a+5_01_e_a]","2_04_b@11,7[2_04_e_a+3_08_e_a]","5_03_a@11,8","3_13_a@10,5","6_08_a@10,6","4_04_b@10,8[4_02_e_a+4_08_e_a]","3_03_a@9,8[4_06_e_b]","6_06_a@9,9[4_10_e_a+4_07_e_a]"],
"bonds": ["preciShip 1/134","sargonShip 1/131","skillfulShip 1/3","suntShip 1/0","victoriaShip 0/5","visiShip 1/10","yanShip 1/38"]
"stats": {"activatedLayers":115,"bossDamage":485236,"dmgDealt":3388651,"gold":140,"itemsEquipped":20,"kills":273,"leaks":18,"lpLost":0,"merges":6,"perfectRounds":10,"refreshes":11},
"lineup": ["4_14_a@12,8","2_04_b@11,7[5_03_e_a]","1_03_b@11,8","5_02_a@10,5","6_05_a@10,6[3_03_e_a]","3_03_b@10,8[3_11_e_b+2_04_e_b]","1_08_b@9,8[1_02_e_a+4_06_e_a]","3_02_b@9,9"],
"bonds": ["kazimierzShip 0/2","preciShip 2/29","sargonShip 0/8","siracusaShip 0/12","skillfulShip 1/33","soloShip 1/0","suntShip 2/0","swiftShip 1/7","yanShip 1/46"]
}
},
"diy": {
@@ -1749,7 +1745,7 @@
[12,"",""],
[13,"",""]
],
"stock": {"chess_char_5_diy1_a":7,"chess_char_5_diy2_a":8,"chess_char_6_diy1_a":5,"chess_char_6_diy2_a":5}
"stock": {"chess_char_5_diy1_a":8,"chess_char_5_diy2_a":8,"chess_char_6_diy1_a":5,"chess_char_6_diy2_a":5}
}
}
}
+40 -1
View File
@@ -38,9 +38,45 @@ test('bounty pick: the card the own board can beat, never one it cannot (even wh
assert.equal(botPickCard(m, ps, [hard, easy, huge].map((c, idx) => ({ ...c, idx })), [0, 1, 2]), 1);
ps.lp = 2;
assert.equal(botPickCard(m, ps, [hard, huge, easy].map((c, idx) => ({ ...c, idx })), [0, 1, 2]), 2, 'low LP: still the beatable one');
const raw = [hard, easy].map((c) => DATA.choices.cards.bounty.find((x) => x.effectId === c.id));
assert.deepEqual(m.offerBountyChoice(ps, raw, 'chess_item_6_03_m'), { ok: true });
assert.deepEqual(m.autoPickPersonalChoice(ps, 'bot'), { ok: true });
assert.equal(ps.personalChoice, null);
assert.equal(ps.bounties.at(-1).card.enemyKey, easy.enemyKey, 'personal cards use bounty scoring too');
const metaState = m.rngMeta.state(), botState = m.rngBots.state();
m.autoPickPersonalChoice(ps, 'bot');
m.autoPickPersonalChoice(ps, 'random');
assert.equal(m.rngMeta.state(), metaState);
assert.equal(m.rngBots.state(), botState, 'nothing pending: no RNG draws');
m.dispose();
});
test('a bot\'s 教鞭 pick is deterministic: one seed offers the same three cards and picks the same one, drawing only on the bots\' rng', () => {
const runs = [];
for (let k = 0; k < 2; k++) {
const h = soloBot({ seed: 5, difficulty: 'HARD' }).start();
const m = h.m;
const ps = m.order[0];
h.run(() => m.phase === PHASE.PREP && m.round === 1);
ps.lp = 999;
h.run(() => m.phase === PHASE.PREP && m.round === 5);
const art = giveItem(m, ps, 'chess_item_6_03_m');
assert.deepEqual(ps.useArt(art.uid, 10, 5), { ok: true });
const offered = ps.personalChoice.cards.map((c) => c.effectId);
assert.equal(offered.length, 3);
assert.equal(new Set(offered).size, 3, 'three different cards');
const meta = m.rngMeta.state(), bots = m.rngBots.state();
assert.deepEqual(m.autoPickPersonalChoice(ps, 'bot'), { ok: true });
assert.equal(m.rngMeta.state(), meta, 'the pick leaves the meta rng alone');
assert.notEqual(m.rngBots.state(), bots, 'one scoring draw per card from the bots\' rng');
assert.equal(ps.personalChoice, null);
runs.push({ offered, picked: ps.bounties.at(-1).card.effectId, bots: m.rngBots.state() });
assert.ok(offered.includes(runs[k].picked));
m.dispose();
}
assert.deepEqual(runs[0], runs[1]);
});
test('bounty pick in real drafts (绝境 R3 悬赏决策): a card the board likely beats (p ≥ 0.5) whenever a near-sure one (p ≥ 0.9) is offered', () => {
let drafts = 0;
for (const seed of [1, 2, 3, 4]) {
@@ -138,9 +174,12 @@ test('bounty Arts (教鞭): kept after a battle with leaks, used after a perfect
assert.ok(owns(ps, WHIP), 'kept after a battle with leaks (not destroyed)');
h.run(() => m.phase === PHASE.PREP && m.round === 4);
m.lastResults.set(ps.playerId, clean);
giveItem(m, ps, WHIP);
const bounties = ps.bounties.length;
h.run(() => m.phase === PHASE.COMBAT && m.round === 4);
assert.ok(!owns(ps, WHIP) && ps.bounties.length > bounties, 'used after a perfect battle');
assert.ok(!owns(ps, WHIP) && ps.bounties.length === bounties + 2, 'both Arts used and picked after a perfect battle');
assert.equal(ps.personalChoice, null);
assert.equal(ps.round.arts, 2);
assert.equal(m.errorCount, 0);
m.dispose();
}
+79 -2
View File
@@ -3,11 +3,62 @@ import { test } from 'node:test';
import assert from 'node:assert/strict';
import { PHASE } from '../../shared/constants.js';
import { fieldModel, planLayout, rehearse, rangeTiles, REHEARSAL_VARIANTS, LAYOUT_PARAMS, botPickCard, botPickBand } from '../../server/match/bot.js';
import { FIELD, canPlace, placeClass, parseKey } from '../../server/match/board.js';
import { makeMatch, checkInvariants, give, DATA } from './harness.js';
import { FIELD, canPlace, placeClass, parseKey, legalTiles, tileKey } from '../../server/match/board.js';
import { makeMatch, checkInvariants, give, giveItem, DATA } from './harness.js';
import { makeBattle } from '../helpers/battleHarness.js';
const soloBot = (o = {}) => makeMatch({ mode: 'solo', difficulty: 'NORMAL', seats: [{ seat: 0, playerId: 'ai_0', name: 'AI', isBot: true, connected: true }], ...o });
test('user: Given a saturated layout, When placing a self-range defender, Then prefer a free enemy path tile', () => {
const h = makeMatch({ mode: 'solo', seed: 11 }).start().toPrep();
const m = h.m;
const ps = m.order[0];
try {
const pieces = ['chess_char_6_03_a', 'chess_char_5_11_a', 'chess_char_1_02_a',
'chess_char_1_10_a', 'chess_char_2_08_a', 'chess_char_3_16_a'].map((id) => give(m, ps, id, 'hand'));
const plan = planLayout(m, ps, pieces);
// act2autochess_m04, first wave: the ground route crosses these deployable tiles.
const road = new Set(['9,9', '9,8', '9,7', '9,6', '11,5', '11,4', '10,4', '9,4', '9,3']);
assert.equal(m.stageId, 'act2autochess_m04');
assert.ok(road.has(plan.get(pieces.at(-1).uid)), `Cuora must block the route, got ${plan.get(pieces.at(-1).uid)}`);
for (const [key, expectedToBlock] of [[plan.get(pieces.at(-1).uid), true], ['12,5', false]]) {
const [row, col] = parseKey(key);
const battle = makeBattle({ stageId: m.stageId, waveTemplate: 'act1autochess_01',
units: [{ chessId: 'chess_char_3_16_a', row, col }] });
battle.runToEnd();
assert.equal(battle.hooksOf('blocked').length > 0, expectedToBlock, `blocking at ${key}`);
assert.equal(battle.result().perPlayer.p1.damageDealt > 0, expectedToBlock, `damage at ${key}`);
}
// When every path position is occupied, a legal fallback remains available.
const fallback = planLayout(m, ps, [pieces.at(-1)], undefined, { occupied: road });
const key = fallback.get(pieces.at(-1).uid);
assert.ok(key && !road.has(key));
assert.equal(ps.deployMap().get(key), 'melee');
} finally { m.dispose(); }
});
test('self-range blockers (range 0-1, only their own tile) fill the free enemy-road tiles before any other tile, on every stage and seed', () => {
// official range_table "0-1": the one grid (0,0) — 角峰 古米 泡泡 折桠 菲莱 蛇屠箱 塞雷娅 余
const guards = Object.values(DATA.chess).filter((c) => c && !c.isGolden && c.rangeGrid && c.rangeGrid.length === 1 && c.rangeGrid[0].every((v) => v === 0));
assert.equal(guards.length, 8);
const stages = new Set();
for (let seed = 1; seed <= 40; seed++) {
const h = makeMatch({ mode: 'solo', seed }).start().toPrep();
const m = h.m;
const ps = m.order[0];
try {
stages.add(m.stageId);
const pieces = guards.map((c) => give(m, ps, c.chessId, 'hand'));
const plan = planLayout(m, ps, pieces);
const model = fieldModel(m, ps);
const road = legalTiles(ps.deployMap(), 'melee').filter(([r, c]) => model.ground.has(tileKey(r, c)));
const onRoad = pieces.filter((p) => model.ground.has(plan.get(p.uid))).length;
assert.equal(onRoad, Math.min(pieces.length, road.length), `seed ${seed}, ${m.stageId}: ${onRoad} of ${pieces.length} guards on the ${road.length} road tiles`);
} finally { m.dispose(); }
}
assert.equal(stages.size, 8, 'every stage was tried');
});
test('field model: the round\'s routes traced over the own board, weighted by the enemies that use them', () => {
const h = soloBot({ seed: 2 }).start();
const m = h.m;
@@ -222,6 +273,32 @@ test('a sliced rehearsal is dropped when the prep ends first; the default layout
m.dispose();
});
test('AI takeover resolves a personal choice in untimed PREP, including generator-error Ready cleanup', () => {
for (const broken of [false, true]) {
const h = makeMatch({ mode: 'coop', humans: 1, seed: 6, fake: true, botSliceMs: 0 }).start();
h.toPrep(1);
const m = h.m, ps = h.ps('p_0');
assert.equal(m.deadline, 0, 'single-human co-op is untimed');
const art = giveItem(m, ps, 'chess_item_6_03_m');
assert.deepEqual(ps.useArt(art.uid, 10, 5), { ok: true });
const pick = m.autoPickPersonalChoice.bind(m), modes = [];
m.autoPickPersonalChoice = (p, mode) => {
if (p.personalChoice) modes.push(mode);
if (broken && mode === 'bot') throw new Error('scripted choice scoring failure');
return pick(p, mode);
};
assert.deepEqual(m.handle(ps.playerId, { t: 'g.autoplay', on: true }), { ok: true });
assert.ok(h.run(() => m.phase !== PHASE.PREP));
assert.equal(ps.personalChoice, null);
assert.equal(ps.bounties.length, 1);
assert.equal(ps.round.arts, 1);
assert.ok(ps.ready);
assert.ok(modes.includes(broken ? 'random' : 'bot'));
assert.equal(m.errorCount > 0, broken);
m.dispose();
}
});
test('economy: the bot fills the board first (8 units by round 4), levels on its curve and keeps a hand slot free', () => {
for (const seed of [5, 8]) {
const h = soloBot({ seed }).start();
+136 -1
View File
@@ -9,7 +9,7 @@ import { PHASE } from '../../shared/constants.js';
import { validateC2S, isBattleResult } from '../../shared/protocol.js';
import { buildBattleSpec, createBattleFromSpec, resultDigest, compactResult, LocalBossPool, jsonClone } from '../../server/sim/spec.js';
import { DataSource } from '../../server/sim/simdata.js';
import { validateClientResult, specBounds } from '../../server/match/fields.js';
import { validateClientResult, specBounds, syntheticResult } from '../../server/match/fields.js';
import { CreditPool, SharedBossPool } from '../../server/match/finalAssault.js';
import { FakeBattle } from './fakeBattle.js';
import { DATA, makeMatch, checkInvariants, give, chessOfTier } from './harness.js';
@@ -516,6 +516,10 @@ test('Final Assault: an early boss b.result (forced at t≈0) does not end the p
test('Final Assault: a 999-layer kill faster than the budget — the \'cleared\' b.result whose report covers the pool waits for the budget, no takeover (QA 6b)', () => {
const { h, m, f } = faWithForger(9122);
const ps = h.ps('p_0');
const training = DATA.choices.cards.bounty.find((c) => c.payout === 'perfect' && m.gd.enemy(c.enemyKey));
m.addBounty(ps, { ...training, rounds: 2, multiRound: false });
const pending = ps.pendingFunds;
const max = m.bossPool.maxHp;
h.sched.advance(500); // 1 game s on the field clock: the budget credits ≤ 20 % of the pool
const gt = m._fieldElapsed(f);
@@ -544,10 +548,141 @@ test('Final Assault: a 999-layer kill faster than the budget — the \'cleared\'
assert.equal(f.resultSource, 'client', 'the held client result completed the field');
assert.equal(m.verifyStats.takeovers, takeovers0);
assert.equal(m.verifyStats.rejected, rejected0);
assert.equal(ps.pendingFunds, pending + training.coin, 'validated held client result earns the server card amount');
assert.equal(ps.bounties[0].roundsLeft, 1);
assert.deepEqual(m.handle('p_0', { t: 'b.result', battleId: f.battleId, result: res }), { ok: true });
assert.equal(ps.pendingFunds, pending + training.coin, 'duplicate accepted result does not pay twice');
assert.equal(ps.bounties[0].roundsLeft, 1);
for (const pid of f.players) assert.ok(Math.abs((f.bossBy[pid] || 0) - Math.min(by[pid], max)) <= max * 1e-9 + 5, `${pid} credited as reported (${f.bossBy[pid]} vs ${by[pid]})`);
m.dispose();
});
test('boss bounty eligibility: forced real results on a team victory qualify; synthetic, leaked and team defeat do not', () => {
for (const [ending, synthetic, perfect, leaked, eligible] of [
['cleared', false, true, [], true],
['cleared', true, true, [], false],
['cleared', false, false, [], false],
['cleared', false, true, [{ counted: true }], false],
['cleared', false, true, [{ counted: false }], true],
['forced', false, true, [], false],
]) {
const { m, f } = faWithForger(9122);
const ps = m.players.get(f.players[0]);
const card = DATA.choices.cards.bounty.find((c) => c.payout === 'perfect' && m.gd.enemy(c.enemyKey));
m.addBounty(ps, { ...card, rounds: 2, multiRound: false });
m.addBounty(ps, { ...card, rounds: 2, multiRound: false });
const res = syntheticResult(f.players);
if (!synthetic) delete res.synthetic;
res.reason = 'forced';
Object.assign(res.perPlayer[ps.playerId], { perfect, leaked, coins: 3 });
const pending = ps.pendingFunds, gained = ps.stats.fundsGained;
m._finalEnding = ending;
m.bossPool.hp = 0; // even a late clearing report cannot override an already-registered team defeat
m._finishFinal(false, () => res);
assert.equal(ps.pendingFunds, pending + 3 + (eligible ? 2 * card.coin : 0));
assert.equal(ps.stats.fundsGained, gained + 3 + (eligible ? 2 * card.coin : 0));
assert.deepEqual(ps.bounties.map((b) => b.roundsLeft), [1, 1]);
m.dispose();
}
});
// ---- a perfect-payout bounty and what the authority reported (a modified client could tell the two halves apart) -------
/**
* A Final Assault pair field played by hand (the SimClient of p_0, the authority, is muted): both players hold the same
* perfect-payout card; the authority sends one b.progress — `leaks` (the field's LP cost) and `leaksBy` (its per-player
* split, when given) — then a 'cleared' b.result whose leaked lists hold `leaked[pid]` counted leaks (lpr 1); the fight
* is driven to its end. Returns the funds each player was paid (kill coins: none) and what the server charged.
*/
function bossPaid(seed, { leaks = 0, leaksBy = null, leaked = {} }) {
const { h, m, f } = faWithForger(seed);
const card = DATA.choices.cards.bounty.find((c) => c.payout === 'perfect' && m.gd.enemy(c.enemyKey));
const ps = f.players.map((pid) => h.ps(pid));
for (const p of ps) m.addBounty(p, { ...card, rounds: 2, multiRound: false });
const before = ps.map((p) => p.pendingFunds);
const lp0 = m.teamLp;
const max = m.bossPool.maxHp;
h.sched.advance(500);
const gt = m._fieldElapsed(f);
const by = { [f.players[0]]: max * 0.6, [f.players[1]]: max * 0.4 + 5 };
const progress = { t: 'b.progress', battleId: f.battleId, gt, killed: 0, total: 0, bossDmg: max + 5, by, leaks, ...(leaksBy ? { leaksBy } : {}) };
assert.equal(validateC2S(progress), null);
assert.deepEqual(m.handle('p_0', progress), { ok: true });
const entry = (pid) => ({ enemyKey: card.enemyKey, mods: null, lpr: 1, sourcePlayerId: pid, tag: null, counted: true, spawned: true });
const res = { reason: 'cleared', time: gt, killed: 0, total: 0, errors: 0,
perPlayer: Object.fromEntries(f.players.map((pid) => {
const list = Array.from({ length: leaked[pid] || 0 }, () => entry(pid));
return [pid, { killed: 0, total: 0, leaked: list, perfect: list.length === 0, layerGains: {}, coins: 0, damageDealt: by[pid], bossDamage: by[pid], healingDone: 0, deaths: 0, unitsEnd: [], unitStats: [] }];
})) };
assert.equal(isBattleResult(res), true);
assert.deepEqual(m.handle('p_0', { t: 'b.result', battleId: f.battleId, result: res }), { ok: true });
h.drive(() => m._finalEnding != null || m.phase !== PHASE.FINAL_ASSAULT, { maxSteps: 2e5 });
const out = { ending: m._finalEnding, source: f.resultSource, coin: card.coin, charged: lp0 - m.teamLp, paid: ps.map((p, i) => p.pendingFunds - before[i]), left: ps.map((p) => p.bounties.map((b) => b.roundsLeft)) };
m.dispose();
return out;
}
test('Final Assault, a modified authority: LP charged by b.progress while both leaked lists are empty pays no perfect-payout card', () => {
const r = bossPaid(9124, { leaks: 1, leaked: {} });
assert.equal(r.ending, 'cleared');
assert.equal(r.source, 'client');
assert.equal(r.charged, 1, 'the server charged the team leak');
assert.deepEqual(r.paid, [0, 0], 'a result that shows no leak after a charged one is not believed');
assert.deepEqual(r.left, [[1], [1]], 'the boss battle still used one of the cards\' battles');
});
test('Final Assault, a modified authority: one seat\'s reported leak cannot be moved to the other — nobody is paid', () => {
// progress: the leak is p_0's; the result pins it on p_1 so that p_0 reads perfect (and p_1 cannot be paid anyway)
const r = bossPaid(9125, { leaks: 1, leaksBy: { p_0: 1, p_1: 0 }, leaked: { p_1: 1 } });
assert.equal(r.ending, 'cleared');
assert.equal(r.charged, 1);
assert.deepEqual(r.paid, [0, 0]);
// …and with no split at all the result decides nothing either: the same leak pinned on p_1
assert.deepEqual(bossPaid(9126, { leaks: 1, leaked: { p_1: 1 } }).paid, [0, 0]);
});
test('Final Assault, honest authority: the seat whose half let nothing through is paid, the leaker is not; a leader\'s own LP effect costs nobody the card', () => {
const leak = bossPaid(9127, { leaks: 1, leaksBy: { p_0: 0, p_1: 1 }, leaked: { p_1: 1 } });
assert.equal(leak.charged, 1);
assert.deepEqual(leak.paid, [leak.coin, 0]);
// 2 LP of leader "扣除目标生命" effects (no leaked enemy): the report says so, the result agrees — both halves are perfect
const fx = bossPaid(9128, { leaks: 2, leaksBy: { p_0: 0, p_1: 0 }, leaked: {} });
assert.equal(fx.charged, 2);
assert.deepEqual(fx.paid, [fx.coin, fx.coin]);
// nothing charged, nothing leaked: as before
assert.deepEqual(bossPaid(9129, { leaks: 0, leaked: {} }).paid.map((n, i, a) => n === a[0]), [true, true]);
assert.ok(bossPaid(9130, { leaks: 0, leaked: {} }).paid.every((n) => n > 0));
});
test('Final Assault, honest clients (client-side combat): a leak on one half costs that seat its perfect-payout card, not its partner; the leader\'s own LP effect costs nobody', () => {
const h = makeMatch({ mode: 'coop', difficulty: 'FUNNY', humans: 2, seed: 9132, fake: true, clientCombat: true, instant: false,
script: (b) => (b.kind === 'boss' ? { bossDps: 20000, leaks: { p_1: 1 }, leakEvents: [{ at: 1, lpr: 1 }], lpLossEvents: [{ at: 1.5, amount: 2 }] } : { duration: 2 }) }).start();
const m = h.m;
h.autoHumans();
h.drive(() => m.phase === PHASE.FINAL_ASSAULT);
const card = DATA.choices.cards.bounty.find((c) => c.payout === 'perfect' && m.gd.enemy(c.enemyKey));
const [p0, p1] = [h.ps('p_0'), h.ps('p_1')];
for (const p of [p0, p1]) m.addBounty(p, { ...card, rounds: 2, multiRound: false });
const [a, b] = [p0.pendingFunds, p1.pendingFunds];
const end = h.runToEnd();
assert.equal(end.victory, true);
const f = fields(h)[0];
assert.equal(f.resultSource, 'client');
assert.equal(f.lpReported, 3, 'leak lpr 1 + leader effect 2');
assert.deepEqual(f.leaksBy, { p_1: 1 }, 'the authority split it: p_1\'s one leak (the other 2 LP are the leader\'s effect), nothing on p_0');
assert.equal(p0.pendingFunds - a, card.coin, 'the seat that let nothing through is paid');
assert.equal(p1.pendingFunds - b, 0, 'the seat that leaked is not');
m.dispose();
});
test('b.progress carries `leaksBy`, the per-player split of a boss field\'s leak LP (shared/protocol.js)', () => {
const base = { t: 'b.progress', battleId: 'a.1.1.b1', gt: 3, killed: 0, total: 0, leaks: 2, bossDmg: 10, by: { p_0: 10 } };
assert.equal(validateC2S({ ...base, leaksBy: { p_0: 1, p_1: 0.5 } }), null);
assert.equal(validateC2S({ ...base, leaksBy: {} }), null);
assert.equal(validateC2S(base), null, 'optional: a client that never sends it still validates');
for (const bad of [{ p_0: -1 }, { p_0: 'x' }, { p_0: 1e7 }, [1, 2], 'p_0', null, { 'a b': 1 }]) assert.notEqual(validateC2S({ ...base, leaksBy: bad }), null, JSON.stringify(bad));
});
test('Final Assault: a \'cleared\' b.result whose report does NOT cover the pool is still handed over (the partner takes the field)', () => {
const { h, m, f } = faWithForger(9123);
const max = m.bossPool.maxHp;
+41 -1
View File
@@ -4,7 +4,7 @@ import assert from 'node:assert/strict';
import { PHASE, ERR } from '../../shared/constants.js';
import { Match } from '../../server/match/Match.js';
import { StubMatch } from '../../server/match/StubMatch.js';
import { DATA, makeMatch, checkInvariants, give, chessOfTier } from './harness.js';
import { DATA, makeMatch, checkInvariants, give, giveItem, chessOfTier } from './harness.js';
import { bossPoolHp } from '../../server/match/finalAssault.js';
const STATUSES = ['acting', 'ready', 'deciding', 'combat', 'done', 'helping', 'left', 'dead'];
@@ -51,6 +51,7 @@ test('m.private shape in PREP matches DESIGN §8.3 (pieces, slots, bonds, effect
assert.ok(s.kind === 'chess' || s.kind === 'item');
}
assert.equal(priv.shop.rewardOffer, null);
assert.equal(priv.personalChoice, null);
assert.equal(priv.deployCap, 8);
assert.ok(priv.effects.some((e) => e.iconKind === 'band' && e.name));
assert.ok(priv.nextEnemies.length > 0);
@@ -109,6 +110,45 @@ test('disconnect: the seat keeps playing; draft turns / prep auto-resolve at dea
m.dispose();
});
test('教鞭: simultaneous private choices survive reconnect; foreign, duplicate and old IDs cannot add bounties', () => {
const h = makeMatch({ mode: 'coop', humans: 2, spectators: ['watcher'], seed: 63, fake: true }).start();
h.toPrep(1);
const m = h.m, a = h.ps('p_0'), b = h.ps('p_1');
for (const ps of [a, b]) {
const art = giveItem(m, ps, 'chess_item_6_03_m');
assert.deepEqual(ps.useArt(art.uid, 10, 5), { ok: true });
}
const aChoice = a.privateView().personalChoice, bChoice = b.privateView().personalChoice;
assert.notEqual(aChoice.id, bChoice.id);
assert.equal(m.handle('p_0', { t: 'g.choice', idx: 0, choiceId: bChoice.id }).error, ERR.BAD_TARGET);
assert.equal(a.bounties.length + b.bounties.length, 0);
m.onDisconnect('p_0');
m.onReconnect('p_0');
assert.deepEqual(h.lastTo('p_0', 'm.private').personalChoice, aChoice);
m.flush(true);
assert.deepEqual(h.lastTo('p_1', 'm.private').personalChoice, bChoice);
assert.ok(!h.allTo('watcher', 'm.private').length);
assert.ok(!JSON.stringify(m.publicView()).includes(aChoice.id));
assert.ok(!JSON.stringify(m.prepFieldMeta(a)).includes(aChoice.id));
assert.deepEqual(m.handle('p_0', { t: 'g.choice', idx: 1, choiceId: aChoice.id }), { ok: true });
m.flush(true);
assert.equal(h.lastTo('p_0', 'm.private').personalChoice, null);
assert.equal(m.handle('p_0', { t: 'g.choice', idx: 1, choiceId: aChoice.id }).error, ERR.BAD_TARGET);
const next = giveItem(m, a, 'chess_item_6_03_m');
assert.deepEqual(a.useArt(next.uid, 10, 5), { ok: true });
const nextId = a.personalChoice.id;
assert.notEqual(nextId, aChoice.id);
assert.equal(m.handle('p_0', { t: 'g.choice', idx: 0, choiceId: aChoice.id }).error, ERR.BAD_TARGET);
assert.equal(a.personalChoice.id, nextId);
assert.equal(a.bounties.length, 1);
m.onDisconnect('p_1');
h.sched.advance(m.deadline - h.sched.now() + 1);
m.onReconnect('p_1');
assert.equal(h.lastTo('p_1', 'm.private').personalChoice, null);
assert.equal(b.bounties.length, 1, 'offline choice picked at the existing PREP deadline');
m.dispose();
});
test('g.autoplay lets the bot play a human seat (buys, places, readies); turning it off returns control', () => {
const h = makeMatch({ mode: 'solo', seed: 64, fake: true }).start();
const m = h.m;
+2
View File
@@ -150,6 +150,8 @@ test('机变 (co-op): 6 shared cards, random order, 30 s first / 16 s others, ti
assert.deepEqual(sp.order.slice().sort(), ['p_0', 'p_1', 'p_2']);
assert.equal(Math.round((m2.deadline - h2.sched.now()) / 1000), 30, 'first picker 30 s');
const [a, b, c] = sp.order;
assert.equal(m2.handle(a, { t: 'g.choice', idx: 2, choiceId: 'old.choice.1' }).error, ERR.WRONG_PHASE);
assert.deepEqual(m2.sp.picks, {}, 'personal IDs never fall through to the global draft');
assert.equal(m2.handle(b, { t: 'g.choice', idx: 0 }).error, ERR.NOT_YOUR_TURN);
assert.deepEqual(m2.handle(a, { t: 'g.choice', idx: 2 }), { ok: true });
assert.equal(m2.handle(a, { t: 'g.choice', idx: 3 }).error, ERR.ALREADY);
+27
View File
@@ -214,6 +214,15 @@ test('ready gating: temp must be empty (a freed hand slot pulls the temp piece i
assert.equal(a.hand[3], t);
assert.ok(a.tempEmpty);
assert.equal(a.privateView().canReady, true);
const art = giveItem(m, a, 'chess_item_6_03_m', 'temp');
assert.deepEqual(a.useArt(art.uid, 10, 5), { ok: true });
assert.ok(a.tempEmpty);
assert.equal(a.privateView().canReady, false);
assert.deepEqual(a.setReady(true), { error: ERR.BAD_TARGET, detail: '请先完成教鞭选择' });
assert.deepEqual(a.setReady(false), { ok: true });
assert.deepEqual(m.handle('p_0', { t: 'g.choice', idx: 0, choiceId: a.personalChoice.id }), { ok: true });
assert.equal(a.privateView().canReady, true);
assert.equal(a.round.arts, 1);
assert.deepEqual(m.handle('p_0', { t: 'g.ready', ready: true }), { ok: true });
assert.deepEqual(m.handle('p_0', { t: 'g.refresh' }), { error: ERR.WRONG_PHASE, detail: 'ready' });
assert.deepEqual(m.handle('p_0', { t: 'g.ready', ready: false }), { ok: true });
@@ -235,13 +244,26 @@ test('prep deadline (co-op): unready players are auto-readied and their temp is
const a = h.ps('p_0');
const id = chessOfTier(1).find((x) => m.pool.has(x));
const left = m.pool.left(id);
const art = giveItem(m, a, 'chess_item_6_03_m');
assert.deepEqual(a.useArt(art.uid, 10, 5), { ok: true });
fillHand(a);
give(m, a, id, 'temp');
assert.ok(!a.tempEmpty, 'a full hand: it waits in temp');
const endPrep = m.endPrep.bind(m);
let checked = false;
m.endPrep = () => {
assert.equal(a.personalChoice, null, 'deadline picks before endPrep builds the battle');
assert.equal(a.bounties.length, 1);
assert.equal(a.round.arts, 1);
assert.ok(a.ready && a.tempEmpty);
checked = true;
endPrep();
};
assert.ok(m.deadline > h.sched.now(), 'co-op prep is timed');
assert.equal(Math.round((m.deadline - h.sched.now()) / 1000), m.gd.prepTime(1));
h.sched.advance(m.deadline - h.sched.now() + 1);
assert.notEqual(m.phase, PHASE.PREP);
assert.ok(checked);
assert.ok(a.tempEmpty);
assert.equal(m.pool.left(id), left, 'temp chess sold back to the pool');
checkInvariants(m);
@@ -252,7 +274,12 @@ test('solo prep and 机变 are untimed', () => {
const h = makeMatch({ mode: 'solo', difficulty: 'NORMAL', seed: 13 }).start();
h.toPrep(1);
assert.equal(h.m.deadline, 0);
const ps = h.ps('p_0');
const art = giveItem(h.m, ps, 'chess_item_6_03_m');
assert.deepEqual(ps.useArt(art.uid, 10, 5), { ok: true });
const id = ps.personalChoice.id;
h.sched.advance(10 * 60 * 1000);
assert.equal(h.m.phase, PHASE.PREP);
assert.equal(ps.personalChoice.id, id, 'no separate personal-choice timer');
h.m.dispose();
});
+85 -1
View File
@@ -11,7 +11,7 @@ import { fieldModel } from '../../server/match/bot.js';
import { FIELD, parseKey } from '../../server/match/board.js';
import { createRng } from '../../server/sim/rng.js';
import { FakeBattle } from './fakeBattle.js';
import { DATA, makeMatch, checkInvariants } from './harness.js';
import { DATA, makeMatch, checkInvariants, giveItem } from './harness.js';
const bossFields = () => FakeBattle.instances.filter((b) => b.kind === 'boss' || b.kind === 'hidden');
@@ -56,6 +56,90 @@ test('multi-round bounties spawn in the Final Assault on the owner\'s half, show
m.dispose();
});
// A 战术特训 card pays its coins when the player's own battle is perfect (PRTS "若各自行动阶段就达成完美作战,获得N资金");
// the Final Assault and the Hidden Core are such battles — they pay it with the kill coins, at the next income.
test('perfect-payout bounties in the boss rounds: a 战术特训 card\'s coins join the kill coins at the next income (R14 → R15, then the Hidden Core)', () => {
const h = makeMatch({ mode: 'coop', difficulty: 'NORMAL', humans: 2, seed: 61, fake: true,
script: (b) => (b.kind === 'boss' || b.kind === 'hidden' ? { bossDps: 1e9, coins: { p_0: 3 } } : {}) }).start();
const m = h.m;
h.toPrep(14);
const ps = h.ps('p_0');
const [card, second] = DATA.choices.cards.bounty.filter((c) => c.payout === 'perfect' && m.gd.enemy(c.enemyKey) && !m.gd.inactiveEnemies.has(c.enemyKey));
m.addBounty(ps, card);
const pending = ps.pendingFunds, gained = ps.stats.fundsGained;
h.drive(() => m.phase === PHASE.FINAL_ASSAULT);
m.hiddenLayerSum = 1e6; // the Hidden Core opens
assert.ok(bossFields()[0].opts.spawns.some((s) => s.tag === 'bounty' && s.enemyKey === card.enemyKey && s.ownerPlayerId === ps.playerId), 'the card\'s enemies are in the boss battle');
h.run(() => m.runner === null);
assert.equal(ps.pendingFunds, pending + 3 + card.coin, 'kill coins + one card amount, regardless of its enemy count');
assert.equal(ps.stats.fundsGained, gained + 3 + card.coin);
const left = bountyBattles(card);
assert.equal(ps.bounties.length, left > 1 ? 1 : 0, 'the boss battle used one of its battles');
if (left > 1) assert.equal(ps.bounties[0].roundsLeft, left - 1);
h.toPrep(15);
assert.equal(ps.pendingFunds, 0);
assert.equal(ps.funds, m.gd.income(15) + pending + 3 + card.coin, 'the base income is separate from the bounty funds');
m.addBounty(ps, second);
const owed = ps.bounties.filter((b) => b.card.payout === 'perfect').reduce((n, b) => n + b.card.coin, 0);
h.drive(() => m.phase === PHASE.HIDDEN_CORE);
assert.ok(bossFields().at(-1).opts.spawns.some((s) => s.tag === 'bounty' && s.enemyKey === second.enemyKey), 'and in the Hidden Core');
h.runToEnd();
assert.equal(ps.pendingFunds, 3 + owed, 'the Hidden Core pays by the same rule');
assert.equal(ps.bounties.length, 0, 'every bounty used its battles');
checkInvariants(m);
m.dispose();
});
test('教鞭 in the boss rounds (R14, R15): the card the owner chose spawns in the boss battle and pays at the next income, the Hidden Core takes another choice', () => {
const h = makeMatch({ mode: 'coop', difficulty: 'NORMAL', humans: 2, seed: 61, fake: true,
script: (b) => (b.kind === 'boss' || b.kind === 'hidden' ? { bossDps: 1e9, coins: { p_0: 3 } } : {}) }).start();
const m = h.m;
h.toPrep(14);
const ps = h.ps('p_0');
const art = giveItem(m, ps, 'chess_item_6_03_m');
assert.deepEqual(ps.useArt(art.uid, 10, 5), { ok: true });
assert.equal(ps.bounties.length, 0, 'nothing is applied before the owner confirms');
assert.equal(ps.setReady(true).error, 'BAD_TARGET', 'Ready waits for the choice');
const card = ps.personalChoice.cards[2];
assert.deepEqual(m.handle(ps.playerId, { t: 'g.choice', idx: 2, choiceId: ps.personalChoice.id }), { ok: true });
assert.equal(ps.bounties[0].card.effectId, card.effectId, 'the card of the tapped slot');
const pending = ps.pendingFunds;
h.drive(() => m.phase === PHASE.FINAL_ASSAULT);
m.hiddenLayerSum = 1e6;
assert.ok(bossFields()[0].opts.spawns.some((s) => s.tag === 'bounty' && s.enemyKey === card.enemyKey && s.ownerPlayerId === ps.playerId), 'the chosen card\'s enemies come in the boss battle');
h.run(() => m.runner === null);
assert.equal(ps.pendingFunds, pending + 3 + card.coin, 'kill coins + the chosen card\'s coins');
h.toPrep(15);
assert.equal(ps.funds, m.gd.income(15) + pending + 3 + card.coin, 'paid at the R15 income, apart from the base income');
const hiddenArt = giveItem(m, ps, 'chess_item_6_03_m');
assert.deepEqual(ps.useArt(hiddenArt.uid, 10, 5), { ok: true });
const hiddenCard = ps.personalChoice.cards[0];
assert.deepEqual(m.handle(ps.playerId, { t: 'g.choice', idx: 0, choiceId: ps.personalChoice.id }), { ok: true });
const owed = ps.bounties.filter((b) => b.card.payout === 'perfect').reduce((n, b) => n + b.card.coin, 0);
h.drive(() => m.phase === PHASE.HIDDEN_CORE);
assert.ok(bossFields().at(-1).opts.spawns.some((s) => s.tag === 'bounty' && s.enemyKey === hiddenCard.enemyKey));
h.runToEnd();
assert.equal(ps.pendingFunds, 3 + owed, 'the Hidden Core pays by the same rule');
checkInvariants(m);
m.dispose();
});
test('boss victory without a perfect own result keeps the kill coins but gives no perfect-card funds', () => {
const h = makeMatch({ mode: 'coop', humans: 2, seed: 61, fake: true,
script: (b) => (b.kind === 'boss' ? { bossDps: 1e9, leaks: { p_0: 1 }, coins: { p_0: 3 } } : {}) }).start();
h.toPrep(14);
const m = h.m, ps = h.ps('p_0');
const card = DATA.choices.cards.bounty.find((c) => c.payout === 'perfect' && !m.gd.inactiveEnemies.has(c.enemyKey));
m.addBounty(ps, { ...card, rounds: 2, multiRound: false });
const gained = ps.stats.fundsGained;
const result = h.drive(() => h.ended != null);
assert.ok(result && h.ended.victory);
assert.equal(ps.pendingFunds, 3);
assert.equal(ps.stats.fundsGained, gained + 3);
assert.equal(ps.bounties[0].roundsLeft, 1);
m.dispose();
});
test('solo Final Assault: the bounty spawns on the `_s` template; bountySpawns route choice by side', () => {
const h = makeMatch({ mode: 'solo', difficulty: 'HARD', seed: 62, fake: true, script: (b) => (b.kind === 'boss' ? { bossDps: 1e9 } : {}) }).start();
const m = h.m;
+16
View File
@@ -397,6 +397,22 @@ test('collectViolations reports corrupted state (funds, pool accounting, duplica
m.dispose();
});
test('collectViolations: a 教鞭 choice belongs to its owner\'s open prep only', () => {
const { m, ps } = prepSolo({ seed: 922 });
const art = giveItem(m, ps, 'chess_item_6_03_m');
assert.deepEqual(ps.useArt(art.uid, 10, 5), { ok: true });
assert.deepEqual(collectViolations(m), [], 'an open choice in its own prep is fine');
const pc = ps.personalChoice;
pc.round = m.round - 1;
assert.match(collectViolations(m).join('\n'), /personal choice outside its own prep/, 'a choice left from an earlier round');
pc.round = m.round;
pc.cards = [pc.cards[0], pc.cards[0]];
assert.match(collectViolations(m).join('\n'), /personal choice of 2 cards/, 'the same card twice');
ps.personalChoice = null;
assert.deepEqual(collectViolations(m), []);
m.dispose();
});
test('the rule auditor flags an LP loss that does not match the leaks (engine regression guard)', () => {
const h = makeMatch({ mode: 'coop', humans: 2, seed: 921, fake: true, script: (b) => (b.kind === 'normal' ? { leaks: Object.fromEntries(b.players.map((p) => [p, 2])) } : {}) }).start();
const m = h.m;
+1
View File
@@ -124,6 +124,7 @@ export function makeMatch(o = {}) {
if (idx != null) m.handle(ps.playerId, { t: 'g.choice', idx });
}
if (ready && m.phase === 'PREP' && ps.alive && !ps.ready) {
if (ps.personalChoice) m.handle(ps.playerId, { t: 'g.choice', idx: 0, choiceId: ps.personalChoice.id });
if (!ps.tempEmpty) ps.resolveTemp();
m.handle(ps.playerId, { t: 'g.ready', ready: true });
}
+6
View File
@@ -217,6 +217,12 @@ test('boss field: the local pool follows b.pool (server hp − unacknowledged lo
assert.ok(last.bossDmg <= pool.cum);
assert.ok(last.by && typeof last.by === 'object');
assert.equal(typeof last.leaks, 'number');
// the per-player split of the leak LP (the server holds the result's leaked lists to it, Match._bossLeaksAgree): the
// leaks of every player of the field, never more than the field's LP cost (leader effects make up the rest)
assert.ok(last.leaksBy && typeof last.leaksBy === 'object', 'boss progress carries leaksBy');
assert.deepEqual(Object.keys(last.leaksBy).sort(), start.spec.players.map((p) => p.playerId).sort());
assert.ok(Object.values(last.leaksBy).every((n) => Number.isFinite(n) && n >= 0));
assert.ok(Object.values(last.leaksBy).reduce((a, b) => a + b, 0) <= last.leaks + 1e-9);
r.net.emit('b.pool', { hp: pool.maxHp * 0.5, max: pool.maxHp, teamLp: 20, acked: { [start.fieldId]: pool.cum } });
assert.ok(Math.abs(pool.hp - pool.maxHp * 0.5) < 1e-6, 'server hp when everything is acknowledged');
r.runner.dispose();
+1
View File
@@ -135,6 +135,7 @@ export class SimClient {
if (bossLike) {
const pool = e.battle.sharedBoss;
msg.leaks = e.meter.lp;
if (p.leaksBy) msg.leaksBy = p.leaksBy; // like public/js/battle/runner.js
msg.bossDmg = pool && Number.isFinite(pool.cum) ? pool.cum : 0;
if (pool && pool.byPlayer) msg.by = { ...pool.byPlayer };
} else {
+5
View File
@@ -42,6 +42,11 @@ test('protocol: g.unitStats { seq? } and g.bandFocus { bandId? } validate; m.uni
assert.equal(validateC2S({ t: 'g.bandFocus', bandId: 'band_bldsk' }), null);
assert.notEqual(validateC2S({ t: 'g.bandFocus', bandId: 'a b' }), null);
assert.ok(S2C.includes('m.unitStats'));
assert.equal(validateC2S({ t: 'g.choice', idx: 0 }), null);
assert.equal(validateC2S({ t: 'g.choice', idx: 2, choiceId: 'seed-2.choice.12' }), null);
for (const choiceId of [null, '', 'a b', 12, 'a'.repeat(65)]) {
assert.notEqual(validateC2S({ t: 'g.choice', idx: 0, choiceId }), null);
}
});
test('unitStatsEntry: effective stats next to the base, rounded for display; the interval from bat / aspd when absent', () => {
+27 -1
View File
@@ -11,7 +11,7 @@ import {
phaseMode, phaseBanner, isCombatPhase, isBossPhase, countdownState, phaseTotalSeconds, sortBonds, bondTier, nextThreshold,
battleOverSfx, ownRoundLoss, uniteResultBox, battleResultBox, roundResultBox, RESULT_BOX_MS,
bondMembers, memberHeadCount, bannedPerBond, priceTone, mergeProgress, shopBlockReason, deploySets, indexPieces, placementContext, canPlace,
boardTargets, dropIntent, normalizeDraft, normalizeSp, groupEnemies, factionTypes, snapHud, bossFrac, attackInterval, fmtNum,
boardTargets, dropIntent, normalizeDraft, normalizeSp, normalizePersonalChoice, groupEnemies, factionTypes, snapHud, bossFrac, attackInterval, fmtNum,
rangeGridBox, shortcutFor, sanitizeSettings, DEFAULT_SETTINGS, normalizeResult, cycleField, fieldLabel, homeFieldId,
activeBubbles, sortedPlayers, tileKey, prepCapsuleLabel, prepCamera, dropFailureReason, terrainInfo,
} from '../../public/js/ui/gameLogic.js';
@@ -584,6 +584,32 @@ describe('drafts', () => {
assert.equal(normalizeSp(null), null);
assert.equal(normalizeSp({ cards: new Array(9).fill({}) }).cards.length, 6, 'at most 6 cards');
});
test('personal choice: only the living recipient in the current PREP, independent of the public draft', () => {
const pub = { phase: PHASE.PREP, round: 14, sp: { family: 'supply', cards: ['global'] } };
const priv = { playerId: 'a', alive: true, canReady: false, temp: [], personalChoice: {
id: 'seed.choice.3', round: 14, sourceItemId: 'chess_item_6_03_m',
cards: [{ id: 'e1', kind: 'bounty', name: '战术特训', coin: 2 }, { id: 'e2', kind: 'bounty' }],
} };
const before = JSON.stringify({ pub, priv });
const sp = normalizePersonalChoice(pub, priv, 'a');
assert.equal(sp.id, priv.personalChoice.id);
assert.equal(sp.family, 'bounty');
assert.equal(sp.name, '教鞭 · 战术特训');
assert.equal(sp.turnPid, 'a');
assert.equal(sp.pickOf.size, 0);
assert.deepEqual(sp.cards.map((c) => c.idx), [0, 1]);
assert.ok(sp.cards.every((c) => c.takenBy === null));
assert.equal(JSON.stringify({ pub, priv }), before);
assert.equal(shopBlockReason('ready', { priv, editable: true }), '请先完成教鞭选择');
assert.equal(shopBlockReason('ready', { priv: { ...priv, temp: [{}] }, editable: true }), '请先完成教鞭选择');
assert.match(shopBlockReason('ready', { priv: { ...priv, personalChoice: null }, editable: true }), /临时整备区/);
for (const phase of [PHASE.SP_DRAFT, PHASE.COMBAT, PHASE.ROUND_START]) assert.equal(normalizePersonalChoice({ ...pub, phase }, priv, 'a'), null);
assert.equal(normalizePersonalChoice(pub, null, 'a'), null, 'spectator has no private state');
assert.equal(normalizePersonalChoice(pub, priv, 'b'), null);
assert.equal(normalizePersonalChoice(pub, { ...priv, alive: false }, 'a'), null);
assert.equal(normalizePersonalChoice({ ...pub, round: 15 }, priv, 'a'), null);
assert.equal(normalizePersonalChoice(pub, { ...priv, personalChoice: null }, 'a'), null);
});
});
describe('fields, players, emotes', () => {
+140 -1
View File
@@ -15,7 +15,7 @@
import { test, describe, before, after } from 'node:test';
import assert from 'node:assert/strict';
import { existsSync, mkdirSync } from 'node:fs';
import { existsSync, mkdirSync, readFileSync } from 'node:fs';
import path from 'node:path';
import { fileURLToPath } from 'node:url';
@@ -71,6 +71,16 @@ describe('in-match UI (mock harness, headless Chrome)', { skip: !ENABLED && 'set
}
const mockState = (page) => page.evaluate(() => JSON.parse(JSON.stringify(globalThis.__MOCK__.S().priv)));
const sleep = (ms) => new Promise((r) => setTimeout(r, ms));
const useWhip = (page) => page.evaluate(async () => {
const { net } = await import('/js/net.js');
const uid = 900000 + globalThis.__MOCK__.S().requests.length;
globalThis.__MOCK__.mutate((S) => {
const idx = S.priv.hand.findIndex((p) => !p);
S.priv.hand[idx] = { uid, kind: 'item', id: 'chess_item_6_03_m', tier: 6 };
});
await net.request('g.art', { itemUid: uid, row: 10, col: 5 });
return globalThis.__MOCK__.S().priv.personalChoice;
});
/** Swipe the open direction wheel from its centre towards `dir` and release (research 09 §1.2). */
const swipeWheel = async (page, dir = 'RIGHT') => {
await page.waitForSelector('.fwheel__dia', { timeout: 4000 });
@@ -480,6 +490,135 @@ describe('in-match UI (mock harness, headless Chrome)', { skip: !ENABLED && 'set
await page.close();
});
test('教鞭: PREP cards confirm twice, use its timer, reset on a new ID; old replies cannot clear a new busy pick', async () => {
const { page, problems } = await open('phase=PREP', { render: 'fallback' });
await page.click('.gm__gear');
await page.waitForSelector('.modal .set-keys');
await page.click('.set-key[data-action="ready"]');
await page.waitForSelector('.set-key.is-waiting[data-action="ready"]');
await page.keyboard.press('KeyW');
await page.waitForFunction(() => document.querySelector('.set-key[data-action="ready"]')?.textContent.trim() === 'W');
await page.click('.modal__actions .btn--primary');
await page.waitForSelector('.modal', { hidden: true });
const deadline = await page.evaluate(() => globalThis.__MOCK__.S().pub.deadline);
const first = await useWhip(page);
await page.waitForSelector('.spov[aria-label="教鞭选择"]');
assert.equal(await page.evaluate(() => globalThis.__MOCK__.S().pub.deadline), deadline);
assert.equal(await page.$('.spov__order'), null);
assert.ok(await page.$('.spov .countdown'));
assert.doesNotMatch(await page.$eval('.spov', (el) => el.textContent), /机变阶段|当前轮到|正在决策/);
assert.equal(await page.$eval('.readybtn', (el) => el.disabled), true);
assert.match(await page.$eval('[data-testid="ready-why"]', (el) => el.textContent), /请先完成教鞭选择/);
await page.evaluate(async () => { await (await import('/js/ui/lang.js')).loadLangIndex(); });
for (const lang of ['en', 'ja', 'ko', 'zh-TW']) {
const messages = JSON.parse(readFileSync(path.join(ROOT, `public/i18n/${lang}.json`), 'utf8'));
await page.evaluate(async (code) => {
await (await import('/js/ui/lang.js')).switchLang(code);
globalThis.__MOCK__.pushPublic(); // this harness has no App/useLang root; render again with the active language
}, lang);
await page.waitForFunction((label) => document.querySelector('.spov')?.getAttribute('aria-label') === label, {}, messages['教鞭选择']);
assert.equal(await page.$eval('.spov__title', (el) => el.textContent), `${messages['教鞭 · 战术特训']}|${messages['请选择一项战术特训']}`);
assert.match(await page.$eval('.spov__sub', (el) => el.textContent), new RegExp(messages['休整期结束时未选择将自动选定']));
assert.equal(await page.$eval('[data-testid="ready-why"]', (el) => el.textContent), messages['请先完成教鞭选择']);
assert.equal((await mockState(page)).personalChoice.id, first.id, 'language switch keeps the same pending offer');
}
await page.evaluate(async () => { await (await import('/js/ui/lang.js')).switchLang('zh'); globalThis.__MOCK__.pushPublic(); });
await page.waitForSelector('.spov[aria-label="教鞭选择"]');
const requests = await page.evaluate(() => globalThis.__MOCK__.S().requests.length);
for (const key of ['KeyW', 'KeyR', 'KeyF', 'KeyD', 'KeyQ', 'KeyX']) await page.keyboard.press(key);
await sleep(150);
assert.equal(await page.evaluate(() => globalThis.__MOCK__.S().requests.length), requests, 'background shortcuts send no intents');
assert.match(await page.$$eval('.toast', (els) => els.map((el) => el.textContent).join('\n')), /请先完成教鞭选择/);
assert.equal(await page.$eval('.readybtn__key', (el) => el.textContent), 'W');
await page.click('.spcard.is-pickable');
await page.waitForSelector('.spcard.is-armed');
await sleep(200); // the overlay attaches its Escape listener in an effect, a frame after the highlight shows
await page.keyboard.press('Escape');
await page.waitForFunction(() => !document.querySelector('.spcard.is-armed'));
assert.equal((await mockState(page)).personalChoice.id, first.id, 'Escape only removes the highlight');
await page.screenshot({ path: path.join(OUT, 'ui-personal-choice-1920.png') });
await page.evaluate(async (id) => {
const { net } = await import('/js/net.js');
const request = net.request;
globalThis.__choiceReplies = {};
net.request = async (t, fields) => {
if (t !== 'g.choice' || fields.choiceId === undefined) return request(t, fields);
if (fields.choiceId === id) {
const result = await request(t, fields);
await new Promise((resolve) => { globalThis.__choiceReplies[fields.choiceId] = resolve; });
return result;
}
await new Promise((resolve) => { globalThis.__choiceReplies[fields.choiceId] = resolve; });
return request(t, fields);
};
}, first.id);
await page.click('.spcard.is-pickable');
assert.equal((await mockState(page)).personalChoice.id, first.id, 'one tap does not apply it');
await page.click('.spcard.is-armed');
await page.waitForFunction(() => !document.querySelector('.spov'));
const firstRequest = await page.evaluate(() => globalThis.__MOCK__.S().requests.filter(([t]) => t === 'g.choice').at(-1));
assert.deepEqual(firstRequest, ['g.choice', { idx: 0, choiceId: first.id }]);
const second = await useWhip(page);
await page.waitForSelector('.spov');
await page.click('.spcard.is-pickable');
await page.waitForSelector('.spcard.is-armed');
const thirdId = second.id + '.next';
await page.evaluate((id) => globalThis.__MOCK__.mutate((S) => { S.pub.deadline = 0; S.priv.personalChoice.id = id; }), thirdId);
await page.waitForFunction(() => !document.querySelector('.spcard.is-armed'));
assert.equal(await page.$('.spov .countdown'), null, 'co-op PREP without a deadline is untimed');
await page.click('.spcard.is-pickable');
await page.click('.spov__confirm');
await page.waitForSelector('.spcard.is-busy');
await page.waitForFunction((id) => !!globalThis.__choiceReplies[id], {}, first.id);
await page.evaluate((id) => globalThis.__choiceReplies[id](), first.id);
await sleep(150);
assert.ok(await page.$('.spcard.is-busy'), 'old reply leaves the newer choice busy');
await page.evaluate((id) => globalThis.__choiceReplies[id](), thirdId);
await page.waitForFunction(() => !document.querySelector('.spov'));
assert.equal((await mockState(page)).personalChoice, null);
assert.deepEqual(problems, []);
await page.close();
});
test('教鞭 appearing cancels the uncommitted direction wheel and active drag, releasing its held piece', async () => {
const { page, problems } = await open('phase=PREP', { render: 'fallback' });
const s = await mockState(page);
const hand = s.hand.find((p) => p && p.kind === 'chess');
const target = s.board.find((p) => p.kind === 'chess' && p.row === 9);
const selector = `.ff-piece[data-uid="${hand.uid}"]`;
const center = (sel) => page.$eval(sel, (el) => { const r = el.getBoundingClientRect(); return { x: r.left + r.width / 2, y: r.top + r.height / 2 }; });
const homeStyle = await page.$eval(selector, (el) => el.style.transform);
const from = await center(selector), to = await center(`.ff-piece[data-uid="${target.uid}"]`);
await page.mouse.move(from.x, from.y);
await page.mouse.down();
await page.mouse.move(to.x, to.y, { steps: 12 });
await sleep(80);
await page.mouse.up();
await page.waitForSelector('.fwheel');
await useWhip(page);
await page.waitForSelector('.spov');
await page.waitForFunction(() => !document.querySelector('.fwheel'));
await page.waitForFunction((sel, transform) => document.querySelector(sel)?.style.transform === transform, {}, selector, homeStyle);
assert.equal(await page.$eval(selector, (el) => el.style.transform), homeStyle, 'cancelFacing released the held bench piece');
assert.equal(await page.$('.ff-piece.is-draggable'), null);
await page.click('.spcard.is-pickable');
await page.click('.spov__confirm');
await page.waitForFunction(() => !document.querySelector('.spov'));
const current = await center(selector);
await page.mouse.move(current.x, current.y);
await page.mouse.down();
await page.mouse.move(current.x + 90, current.y - 40, { steps: 8 });
await page.waitForSelector('.gm.is-dragging');
await useWhip(page);
await page.waitForSelector('.spov');
await page.waitForFunction(() => !document.querySelector('.gm.is-dragging, .ff-piece.is-lifted'));
await page.mouse.up();
assert.deepEqual((await mockState(page)).board, s.board);
assert.ok(!(await page.evaluate(() => globalThis.__MOCK__.S().requests.some(([t]) => t === 'g.move'))));
assert.deepEqual(problems, []);
await page.close();
});
// ---- round-1 UI fixes ----------------------------------------------------------------------------------------
const rectOf = (page, sel) => page.$eval(sel, (el) => { const r = el.getBoundingClientRect(); return { left: r.left, top: r.top, right: r.right, bottom: r.bottom }; });
const overlaps = (a, b) => !!a && !!b && a.left < b.right && b.left < a.right && a.top < b.bottom && b.top < a.bottom;
+42 -1
View File
@@ -345,7 +345,7 @@ describe('user playtest #2 item 10 — boss-round prep on the boss field (real s
});
test('co-op 险境 (R14): host on the left half, guest on the mirrored right half; scouting and 返回 keep the pen hidden', { skip: only('coop'), timeout: 10 * 60 * 1000 }, async () => {
const srv = await startRealServer({ fast: { timerScale: 0.5, combatSpeed: 8, startRound: 'boss' } });
const srv = await startRealServer({ fast: { timerScale: 1, combatSpeed: 8, startRound: 'boss', items: ['chess_item_6_03_m'] } });
const P = await pptr();
const host = new Client(P, srv.base, 'host', { prefix: 'fix' });
const guest = new Client(P, srv.base, 'guest', { prefix: 'fix', w: 1280, h: 720 });
@@ -412,6 +412,47 @@ describe('user playtest #2 item 10 — boss-round prep on the boss field (real s
assert.deepEqual(back.prepField, { kind: 'bossPrep', side: 'L', mirror: false }, '返回: the own boss half again');
}
await penRoundTrip(guest, 'guest');
// Real clients open their own 教鞭 candidates together; refreshing one tab restores that same private choice.
const choiceOf = (c) => c.page.evaluate(() => JSON.parse(JSON.stringify(globalThis.__SP__.store.get().match.private.personalChoice)));
const deadline = await host.page.evaluate(() => globalThis.__SP__.store.get().match.public.deadline);
await Promise.all([[host, hp], [guest, gp]].map(async ([c, placed]) => {
const art = (await c.handPieces('item')).find((p) => p.id === 'chess_item_6_03_m');
assert.ok(art, `${c.label}: starter 教鞭`);
await c.drag(await c.piecePoint(art.uid), await c.tilePoint(placed.row, placed.col));
await c.page.waitForSelector('.spov[aria-label="教鞭选择"]', { timeout: 6000 });
}));
const hostChoice = await choiceOf(host), guestChoice = await choiceOf(guest);
assert.notEqual(hostChoice.id, guestChoice.id);
for (const c of [host, guest]) {
assert.equal((await choiceOf(c)).cards.length, 3);
assert.equal((await c.st()).canReady, false);
assert.equal(await c.page.$('.spov__order'), null);
assert.equal(await c.page.evaluate(() => globalThis.__SP__.store.get().match.public.deadline), deadline);
}
await host.page.reload({ waitUntil: 'networkidle0' });
await host.page.waitForSelector('.spov[aria-label="教鞭选择"]', { timeout: 20000 });
assert.deepEqual(await choiceOf(host), hostChoice, 'refresh reuses the ID and cards');
assert.deepEqual(await choiceOf(guest), guestChoice, 'the teammate keeps its own candidates');
await host.page.evaluate(async () => { await (await import('/js/ui/lang.js')).switchLang('en'); });
await host.page.waitForSelector('.spov[aria-label="Pointing Stick Choice"]');
assert.match(await host.page.$eval('.spov__title', (el) => el.textContent), /Pointing Stick · Tactical Training/);
assert.equal(await host.page.$eval('[data-testid="ready-why"]', (el) => el.textContent), 'Complete the Pointing Stick choice first');
assert.equal(await guest.page.$eval('.spov', (el) => el.getAttribute('aria-label')), '教鞭选择', 'language stays per client');
assert.deepEqual(await choiceOf(host), hostChoice, 'language switch does not replace the offer');
await host.page.evaluate(async () => { await (await import('/js/ui/lang.js')).switchLang('zh'); });
await host.page.waitForSelector('.spov[aria-label="教鞭选择"]');
const refused = await host.page.evaluate(async (id) => {
try { await globalThis.__SP__.net.request('g.choice', { idx: 0, choiceId: id }); return null; }
catch (e) { return e.code; }
}, guestChoice.id);
assert.equal(refused, 'BAD_TARGET', 'a live client cannot submit the teammate\'s ID');
await host.shot('dobermann-private-reconnect');
for (const c of [host, guest]) {
await c.click('.spcard.is-pickable');
assert.ok(await choiceOf(c), 'first tap only highlights');
await c.click('.spov__confirm');
await c.page.waitForFunction(() => globalThis.__SP__.store.get().match.private.personalChoice === null, { timeout: 6000 });
}
for (const c of [host, guest]) await c.click('.readybtn');
await checkBossBattle(host, hp, 'L', 'host');
await checkBossBattle(guest, gp, 'R', 'guest');
+25 -1
View File
@@ -222,6 +222,18 @@ describe('3: the temp overflow row (临时整备区)', () => {
assert.deepEqual(tempInfo(null), { count: 0, items: 0 });
});
test('Ready shows the personal-choice reason and describes it even with no temp pieces', () => {
for (const temp of [[], [{ uid: 1, kind: 'item' }]]) {
const v = ReadyToggle({ priv: { ...priv(temp), canReady: false, personalChoice: { id: 'choice.1' } }, onToggle() {} });
const nodes = [...walk(v)];
const btn = nodes.find((n) => n.type === 'button');
assert.equal(btn.props.disabled, true);
assert.equal(btn.props['aria-describedby'], 'readywrap-why');
assert.equal(textOf(nodes.find((n) => hasClass(n, 'readywrap__why'))), '请先完成教鞭选择');
assert.equal(nodes.find((n) => n.type?.name === 'Tooltip').props.text, '请先完成教鞭选择');
}
});
test('the row frame: the outer corners of both end tiles, the label on the side with room (mirrored Final Assault prep too)', () => {
// tileScreen polys: [back-left, back-right, front-right, front-left] (render/app.js tileScreen corner order)
const tile = (cx, y, w = 100, d = 60) => ({ x: cx, y, s: w, poly: [[cx - w / 2 + 6, y - d / 2], [cx + w / 2 - 6, y - d / 2], [cx + w / 2, y + d / 2], [cx - w / 2, y + d / 2]] });
@@ -258,7 +270,7 @@ describe('3: the temp overflow row (临时整备区)', () => {
test('while the ready button shows its reason the effects column moves down a line (on phones the ready count ran into it)', () => {
const game = read('public/js/screens/game.js');
// the same condition as ReadyToggle's reason: PREP, alive, not ready, pieces in the row
assert.match(game, /const readyWhy = phase === PHASE\.PREP && alive && !priv\?\.ready && temp\.count > 0;/);
assert.match(game, /const readyWhy = phase === PHASE\.PREP && alive && !priv\?\.ready && \(hasPersonalChoice \|\| temp\.count > 0\);/);
assert.match(game, /readyWhy && 'has-readywhy'/);
const css = read('public/css/screens/game.css');
assert.match(css, /\.gm__effects \{ position: absolute; right: \.3rem; top: 2\.4rem; \}/);
@@ -389,6 +401,18 @@ describe('9: busy indicators and data loading', () => {
assert.ok(![...walk(landed)].some((n) => hasClass(n, 'spcard__busy')));
});
test('choice requests carry the personal ID while global requests keep their existing shape', async () => {
const { net } = await import('../../public/js/net.js');
const { actions } = await import('../../public/js/ui/gameActions.js');
const request = net.request, sent = [];
net.request = async (t, fields) => { sent.push([t, fields]); };
try {
assert.equal(await actions.choice(0), true);
assert.equal(await actions.choice(2, 'seed.choice.3'), true);
assert.deepEqual(sent, [['g.choice', { idx: 0 }], ['g.choice', { idx: 2, choiceId: 'seed.choice.3' }]]);
} finally { net.request = request; }
});
test('the "作战结束,等待队友完成作战" hourglass stands still', () => {
const css = read('public/css/screens/game.css');
assert.doesNotMatch(css, /hg-flip/);
+22
View File
@@ -130,6 +130,28 @@ describe('2: 机变 cards take two taps (select → confirm), like buying in the
assert.match(textOf(lone.find((n) => hasClass(n, 'spov__sub'))), /无时间限制/);
});
test('教鞭 ChoiceView: personal heading, no global order or turn; timing follows PREP deadline', () => {
const s = sp({ name: '教鞭 · 战术特训', desc: '请选择一项战术特训', cards: sp().cards.slice(0, 2) });
for (const [solo, deadline] of [[false, 0], [true, 123456]]) {
let confirmed = false;
const v = ChoiceView({ pub: { players: [{ playerId: 'me', name: 'Me' }, { playerId: 'p2', name: 'P2' }], deadline }, sp: s,
...me, solo, personal: true, armed: 0, total: 74, onConfirm: () => { confirmed = true; } });
const nodes = [...walk(v)];
assert.equal(v.props['aria-label'], '教鞭选择');
assert.ok(!nodes.some((n) => hasClass(n, 'spov__order')));
assert.doesNotMatch(textOf(v), /机变阶段|当前轮到|正在决策/);
const clock = nodes.find((n) => n.type?.name === 'Countdown');
assert.equal(!!clock, deadline > 0);
if (clock) { assert.equal(clock.props.deadline, deadline); assert.equal(clock.props.total, 74); }
assert.match(textOf(nodes.find((n) => hasClass(n, 'spov__sub'))), deadline ? /休整期结束时未选择将自动选定/ : /无时间限制/);
const btn = nodes.find((n) => n.type === Button);
btn.props.onClick();
assert.ok(confirmed);
assert.match(btn.props.title, /确认选择「A」/);
assert.match(nodes.find((n) => hasClass(n, 'spcard')).props.title, /^A\na$/);
}
});
test('the overlay keeps the selection itself and drops it on Esc / a tap elsewhere (source contract)', () => {
const src = read('public/js/ui/choiceOverlay.js');
assert.match(src, /export function ChoiceOverlay\(props\) \{[\s\S]*useState\(null\)[\s\S]*armedCard\(sel, sp/);