feedback5 WK: golden — one 自选 match (coop2-NORMAL-70-diy): a human seat on AI 托管 slots 推进之王 (S3, SOL-X) and the prototypes Sharp (tiers 5 and 6) and 领主·Sharp, its 调度中心 at level 5 from the first prep; its own shop draws 推进之王 in round 3 (and Sharp in round 7), its AI fields 推进之王 — the operator kit — in rounds 3–7; the digest's diy lists per round the 自选 shop draws and the fielded pieces, and the stock left
Added to the fast subset (7 matches, 62 of 151 scenarios). No existing scenario moved (17 matches and every other family byte-identical). Test: test/golden-diy.test.js.
This commit is contained in:
1 parent
0def7babfd
commit
01d5fa3e94
4 files changed
+229
-12
No files matched your search
@@ -0,0 +1,32 @@
|
||||
// The 自选 golden match covers what it claims (test/golden/README.md, tools/golden.mjs — 0.2.0 自选编队): reads the stored
|
||||
// digest only (test/golden.test.js recomputes it). Its human seat (AI 托管) slots 推进之王 and prototypes; its shop draws
|
||||
// its 自选 pieces (and only from the slots' 调度中心 levels), its AI fields 推进之王 — the operator kit — in battle, the
|
||||
// stock ends where the held copies leave it, and the match ends without errors.
|
||||
import { test } from 'node:test';
|
||||
import assert from 'node:assert/strict';
|
||||
import { readFileSync } from 'node:fs';
|
||||
|
||||
const read = (p) => JSON.parse(readFileSync(new URL(p, import.meta.url), 'utf8'));
|
||||
const MATCHES = read('./golden/matches.json');
|
||||
const BACKUPS = read('../data/backups.json');
|
||||
|
||||
test('matches: the 自选 match\'s human seat draws its 自选 pieces in its shop and fields 推进之王 in battle', () => {
|
||||
const [id, dg] = Object.entries(MATCHES.scenarios).find(([k]) => /-diy$/.test(k)) || [];
|
||||
assert.ok(dg, 'scenario present');
|
||||
assert.deepEqual(dg.errors, { engine: 0, logged: 0, sim: 0, dispatcher: 0 }, id);
|
||||
assert.deepEqual(Object.keys(dg.diy.picks).sort(), Object.keys(BACKUPS.diy.slots).sort(), 'all four slots filled');
|
||||
assert.equal(dg.diy.picks.chess_char_5_diy1_a.charId, 'char_112_siege');
|
||||
const draws = dg.diy.rounds.flatMap(([, d]) => (d ? d.split(' ') : []));
|
||||
assert.ok(draws.length >= 2, `shop draws: ${draws.join(', ')}`);
|
||||
for (const d of draws) assert.ok(Object.hasOwn(BACKUPS.diy.slots, `chess_char_${d}`), d);
|
||||
const fielded = dg.diy.rounds.filter(([, , f]) => f).flatMap(([r, , f]) => f.split(' ').map((x) => `${r}:${x}`));
|
||||
assert.ok(fielded.some((x) => x.endsWith('5_diy1_a→char_112_siege')), `fielded: ${fielded.join(', ')}`);
|
||||
for (const [r, , f] of dg.diy.rounds) {
|
||||
for (const x of f ? f.split(' ') : []) {
|
||||
const [slot, charId] = x.split('→');
|
||||
assert.equal(dg.diy.picks[`chess_char_${slot}`].charId, charId, `round ${r}: ${x} fights as its pick`);
|
||||
}
|
||||
}
|
||||
// the stock: never above its cap (8 at tier 5, 5 at tier 6)
|
||||
for (const [slot, left] of Object.entries(dg.diy.stock)) assert.ok(left >= 0 && left <= (BACKUPS.diy.slots[slot].tier === 5 ? 8 : 5), slot);
|
||||
});
|
||||
@@ -17,7 +17,7 @@ through.
|
||||
| `roster` | 49 battles: every visible chess record (normal and elite) with every selectable skill and module (DESIGN §16), 12 operators per battle on a real stage (all 11 in turn) against the round's real wave three times over, every non-leader enemy kind of `data/enemies.json` as extra spawns (half of them bounties), placeable summons on the board, every equipment item, band, battle-side 机变 card and stage map card in turn, bonds from the board |
|
||||
| `bonds` | 46 battles: every bond at its activation threshold (1 layer) and at its top tier (999 layers) |
|
||||
| `fields` | 22 battles: every Final Assault / Hidden Core leader on a pair and a solo template (shared pool, 200 s cap) and the 联防 field with 1 and 2 helpers on its own map (carried HP / SP, a knocked-out operator, two leakers' enemies) |
|
||||
| `matches` | 17 matches to the end in virtual time: 16 bot-only (solo 标准 / 险境 / 绝境 / 终极 × 2 seeds, co-op 2 / 3 / 4, one server-run combat match, two runs boosted to the Hidden Core) and one co-op match whose human seat (AI 托管, offline) does not own 7 NORMAL chess — they fight as their 补位 stand-ins (its digest lists them per round, `standIns`) |
|
||||
| `matches` | 18 matches to the end in virtual time: 16 bot-only (solo 标准 / 险境 / 绝境 / 终极 × 2 seeds, co-op 2 / 3 / 4, one server-run combat match, two runs boosted to the Hidden Core), one co-op match whose human seat (AI 托管, offline) does not own 7 NORMAL chess — they fight as their 补位 stand-ins (its digest lists them per round, `standIns`) — and one whose human seat slots 自选 picks (推进之王 and prototypes) with its 调度中心 at level 5 from the first prep: its own shop draws them and its AI fields 推进之王 (its digest lists per round the 自选 shop draws and the fielded pieces, `diy`) |
|
||||
| `standins` | 10 battles: every NORMAL chess record (normal and elite, 110) fielded as its 补位 stand-in (`standIn: true`: the stand-in's body, backup skill / module and kit — all 17 stand-ins, every skill a chess names for them), 12 per battle by strength band, laid out by the stand-in's position on a real stage, against the round's real wave three times over plus 8 ground enemy kinds (melee stand-ins always meet an enemy), an item each, bonds from the board |
|
||||
| `diy` | 6 battles of 自选 pieces (a DIY slot with its `diy` pick, `shared/diy.js`): every owned 6★ with an operator kit (`kits/index.js OPERATOR_KITS` — 推进之王 so far) in each form of tiers 5 and 6 (normal; elite with no module and with each module) under each skill, then every prototype pick with a kit at its locked selection, normal and elite, 12 per battle against the round's real wave; a new operator kit adds its scenarios |
|
||||
|
||||
@@ -31,8 +31,8 @@ within a tick (they are counted, not hashed) and wall-clock time. Damage / heali
|
||||
to 2 decimals.
|
||||
|
||||
`npm test` runs the fast subset (the scenarios marked `"fast": true`: every chess record with its default loadout,
|
||||
every stage and every non-leader enemy kind, every bond at its top tier, six fields, six matches, the five stand-in
|
||||
battles of the normal records, the first 自选 battle of the operator kits and of the prototypes — 61 of the 150). `GOLDEN_FULL=1` checks everything.
|
||||
every stage and every non-leader enemy kind, every bond at its top tier, six fields, seven matches, the five stand-in
|
||||
battles of the normal records, the first 自选 battle of the operator kits and of the prototypes — 62 of the 151). `GOLDEN_FULL=1` checks everything.
|
||||
|
||||
## Workflow
|
||||
|
||||
|
||||
@@ -1611,6 +1611,130 @@
|
||||
[13,"3_12_a→char_608_acpion 6_05_a→char_611_acnipe 6_17_a→char_608_acpion"]
|
||||
]
|
||||
}
|
||||
},
|
||||
"coop2-NORMAL-70-diy": {
|
||||
"about": "coop NORMAL, 1 bot seat(s) + 1 human seat (AI 托管) with 4 自选 picks and its 调度中心 at level 5 from the first prep, seed 70",
|
||||
"fast": true,
|
||||
"mode": "coop",
|
||||
"difficulty": "NORMAL",
|
||||
"seats": 1,
|
||||
"seed": 70,
|
||||
"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":122900},
|
||||
"rng": {"setup":25,"shop":461,"waves":0,"draft":34,"bots":16433,"meta":20},
|
||||
"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]},
|
||||
{"round":2,"h_0":[1,27,5,0,0,5,3,"ba528c80",0,"",8,8,0,8],"ai_0":[1,28,5,0,0,1,4,"f66f48b3",0,"sargonShip 0/2, soloShip 1/0",8,8,0,9]},
|
||||
{"round":3,"h_0":[1,27,6,0,0,5,4,"43d0e2bf",0,"",7,7,0,13],"ai_0":[1,28,6,0,0,1,7,"0d31196a",0,"sargonShip 0/2, soloShip 1/0, yanShip 0/1",7,7,0,15]},
|
||||
{
|
||||
"round": 4,
|
||||
"h_0": [1,27,7,0,0,5,6,"2d3d3501",10,"preciShip 1/2, sargonShip 1/8",20,20,0,20],
|
||||
"ai_0": [1,28,7,0,0,2,8,"bf509782",0,"sargonShip 0/2, soloShip 1/0, yanShip 0/2",20,20,0,22]
|
||||
},
|
||||
{
|
||||
"round": 5,
|
||||
"h_0": [1,27,8,0,0,5,8,"1a62176b",31,"indomShip 1/0, preciShip 0/2, sargonShip 1/29, steadShip 1/1, 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,"7e9969de",84,"arcaneShip 0/2, indomShip 1/4, preciShip 1/15, sargonShip 1/49, steadShip 1/6, victoriaShip 1/6, visiShip 1/4",22,22,0,38],
|
||||
"ai_0": [1,28,10,0,1,3,8,"ea720e1b",0,"sargonShip 0/4, skillfulShip 1/0, soloShip 1/0, suntShip 1/0, yanShip 0/6",23,24,1,39]
|
||||
},
|
||||
{
|
||||
"round": 7,
|
||||
"h_0": [1,27,11,0,1,5,8,"f502b39b",103,"arcaneShip 0/2, indomShip 1/4, preciShip 1/21, sargonShip 1/60, steadShip 1/11, victoriaShip 1/7, visiShip 0/4",26,26,0,50],
|
||||
"ai_0": [1,28,11,0,1,4,8,"7f20e163",0,"sargonShip 0/4, skillfulShip 1/0, soloShip 1/0, suntShip 1/0, yanShip 0/6",28,28,0,49]
|
||||
},
|
||||
{
|
||||
"round": 8,
|
||||
"h_0": [1,27,12,0,0,5,8,"b6d1cc82",197,"arcaneShip 0/2, indomShip 1/16, preciShip 2/55, sargonShip 1/94, soloShip 1/0, steadShip 2/32, victoriaShip 0/19, visiShip 0/4",18,18,0,63],
|
||||
"ai_0": [1,28,12,0,1,4,8,"854ef8a8",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,61]
|
||||
},
|
||||
{
|
||||
"round": 9,
|
||||
"h_0": [1,27,12,0,2,5,8,"6405e223",267,"arcaneShip 0/2, indomShip 1/16, preciShip 2/81, sargonShip 1/125, steadShip 1/37, victoriaShip 0/19, visiShip 1/8",23,23,0,77],
|
||||
"ai_0": [1,28,13,0,2,4,8,"1875f830",30,"sargonShip 0/4, siracusaShip 1/8, skillfulShip 1/3, soloShip 1/0, suntShip 1/0, yanShip 1/19",14,23,9,74]
|
||||
},
|
||||
{
|
||||
"round": 10,
|
||||
"h_0": [1,27,16,0,2,5,8,"c899fa07",312,"arcaneShip 0/2, indomShip 1/16, preciShip 2/97, sargonShip 1/133, soloShip 1/0, steadShip 2/46, suntShip 1/0, victoriaShip 0/19, visiShip 1/20",31,31,0,91],
|
||||
"ai_0": [1,28,14,0,0,5,8,"0d283e5f",35,"sargonShip 0/4, siracusaShip 1/8, skillfulShip 1/6, soloShip 1/0, suntShip 2/0, yanShip 1/21",31,31,0,87]
|
||||
},
|
||||
{
|
||||
"round": 11,
|
||||
"h_0": [1,27,14,0,2,5,8,"7c42dc27",357,"arcaneShip 0/2, indomShip 1/16, preciShip 2/109, sargonShip 1/137, skillfulShip 1/8, steadShip 1/51, suntShip 1/0, victoriaShip 0/19, visiShip 1/36",19,19,0,105],
|
||||
"ai_0": [1,28,12,0,0,5,8,"fe5ac1a4",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,99]
|
||||
},
|
||||
{
|
||||
"round": 12,
|
||||
"h_0": [1,27,16,0,2,6,8,"3cdb76eb",388,"arcaneShip 0/2, indomShip 2/16, preciShip 2/141, sargonShip 1/179, skillfulShip 0/8, steadShip 0/51, suntShip 1/0, victoriaShip 0/19, visiShip 1/52",33,33,0,122],
|
||||
"ai_0": [1,28,12,0,0,6,8,"547fcf92",67,"sargonShip 0/8, siracusaShip 1/16, skillfulShip 1/18, soloShip 1/0, suntShip 2/0, yanShip 1/33",15,33,18,112]
|
||||
},
|
||||
{
|
||||
"round": 13,
|
||||
"h_0": [1,27,16,0,0,6,8,"d0f5bd71",508,"arcaneShip 0/2, indomShip 1/16, preciShip 2/173, sargonShip 1/239, skillfulShip 0/8, steadShip 0/51, suntShip 1/0, swiftShip 1/12, victoriaShip 0/19, visiShip 1/68",50,50,0,138],
|
||||
"ai_0": [1,28,12,0,0,6,8,"d2ff3200",90,"sargonShip 0/8, siracusaShip 1/24, skillfulShip 1/24, soloShip 1/0, suntShip 2/0, victoriaShip 0/5, yanShip 1/42",31,50,19,125]
|
||||
}
|
||||
],
|
||||
"players": {
|
||||
"h_0": {
|
||||
"alive": true,
|
||||
"victory": true,
|
||||
"roundsPassed": 14,
|
||||
"eliminatedRound": null,
|
||||
"lp": 27,
|
||||
"bandId": "band_yu",
|
||||
"title": {"id":"comment_1","name":"卫戍之星","picId":"comment_icon_1","text":"对敌方领袖造成伤害最高(仅胜利时)"},
|
||||
"trophies": 4,
|
||||
"reward": 400,
|
||||
"stats": {"activatedLayers":534,"bossDamage":572474,"dmgDealt":4854054,"gold":154,"itemsEquipped":23,"kills":351,"leaks":0,"lpLost":0,"merges":4,"perfectRounds":13,"refreshes":23},
|
||||
"lineup": ["5_21_b@12,8[3_07_e_b+1_05_e_a]","5_22_a@11,7","4_20_b@11,8","5_02_a@10,5[5_01_e_a+6_01_e_a]","6_05_a@10,6[2_01_e_a+4_08_e_a]","6_06_a@10,8[1_01_e_a+3_04_e_a]","4_07_b@9,8[5_03_e_a+4_11_e_b]","4_23_b@9,9[4_04_e_a]"],
|
||||
"bonds": ["arcaneShip 0/2","indomShip 1/16","preciShip 2/173","sargonShip 1/239","skillfulShip 0/8","steadShip 0/51","suntShip 1/0","swiftShip 1/22","victoriaShip 0/19","visiShip 1/84"]
|
||||
},
|
||||
"ai_0": {
|
||||
"alive": true,
|
||||
"victory": true,
|
||||
"roundsPassed": 14,
|
||||
"eliminatedRound": null,
|
||||
"lp": 28,
|
||||
"bandId": "band_mlyss",
|
||||
"title": {"id":"comment_3","name":"坚若磐石","picId":"comment_icon_3","text":"目标生命值损失最少"},
|
||||
"trophies": 4,
|
||||
"reward": 400,
|
||||
"stats": {"activatedLayers":113,"bossDamage":215026,"dmgDealt":2608447,"gold":137,"itemsEquipped":20,"kills":245,"leaks":48,"lpLost":0,"merges":6,"perfectRounds":8,"refreshes":13},
|
||||
"lineup": ["6_05_a@12,8[3_07_e_a+6_09_e_a]","2_04_b@11,7","1_07_b@11,8","6_11_a@10,6","3_03_b@10,8[3_11_e_b+2_04_e_a]","5_02_a@9,7[1_02_e_b]","1_08_b@9,8[4_06_e_a+1_05_e_a]","3_02_b@9,9[4_10_e_a+4_11_e_a]"],
|
||||
"bonds": ["maniShip 1/0","preciShip 1/0","sargonShip 1/8","siracusaShip 1/24","skillfulShip 1/30","soloShip 1/0","suntShip 2/0","victoriaShip 0/10","yanShip 1/51"]
|
||||
}
|
||||
},
|
||||
"diy": {
|
||||
"picks": {
|
||||
"chess_char_5_diy1_a": {"charId":"char_112_siege","skillIndex":2,"uniEquipId":"uniequip_002_siege"},
|
||||
"chess_char_5_diy2_a": {"charId":"char_609_acguad","skillIndex":2,"uniEquipId":"uniequip_002_acguad"},
|
||||
"chess_char_6_diy1_a": {"charId":"char_609_acguad","skillIndex":2,"uniEquipId":"uniequip_002_acguad"},
|
||||
"chess_char_6_diy2_a": {"charId":"char_617_sharp2","skillIndex":0,"uniEquipId":"uniequip_002_sharp2"}
|
||||
},
|
||||
"banned": [],
|
||||
"rounds": [
|
||||
[1,"",""],
|
||||
[2,"",""],
|
||||
[3,"5_diy1_a","5_diy1_a→char_112_siege"],
|
||||
[4,"","5_diy1_a→char_112_siege"],
|
||||
[5,"","5_diy1_a→char_112_siege"],
|
||||
[6,"","5_diy1_a→char_112_siege"],
|
||||
[7,"5_diy2_a","5_diy1_a→char_112_siege"],
|
||||
[8,"",""],
|
||||
[9,"",""],
|
||||
[10,"",""],
|
||||
[11,"",""],
|
||||
[12,"",""],
|
||||
[13,"",""]
|
||||
],
|
||||
"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}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
+70
-9
@@ -25,10 +25,12 @@
|
||||
// ended at 200 game s) and 联防 fields (1 and 2 helpers on the 联防 map of their count — boards laid out on a
|
||||
// battle stage —, carried HP / SP, a knocked-out operator, two leakers' enemies with a summoned-only kind and
|
||||
// a bounty)
|
||||
// matches 17 matches run to the end in virtual time with the match's default bot rehearsal: 16 bot-only (solo 标准 /
|
||||
// matches 18 matches run to the end in virtual time with the match's default bot rehearsal: 16 bot-only (solo 标准 /
|
||||
// 险境 / 绝境 / 终极 ×2 seeds, co-op 2 / 3 / 4, one server-run combat match, two with LP and layers raised at the
|
||||
// first prep so they reach the Hidden Core) and one co-op match whose human seat (AI 托管, offline: its
|
||||
// battles run on the server) does not own a few NORMAL chess — they fight as their 补位 stand-ins (0.2.0)
|
||||
// first prep so they reach the Hidden Core), one co-op match whose human seat (AI 托管, offline: its
|
||||
// battles run on the server) does not own a few NORMAL chess — they fight as their 补位 stand-ins (0.2.0) — and
|
||||
// one whose human seat slots 自选 picks (推进之王 and prototypes; its 调度中心 at level 5 from the first prep):
|
||||
// its shop sells them and its AI fields them (0.2.0 自选编队, the digest's `diy`)
|
||||
// standins 10 battles: every NORMAL chess record (normal + elite, 110) fielded as its 补位 stand-in (PlayerBattleInput
|
||||
// standIn: true — DATA.md §18: the stand-in's body, its backup skill / module, its kit by charId; all 17
|
||||
// stand-ins and every skill a chess names for them), 12 per battle by strength band, laid out by the stand-in's
|
||||
@@ -713,9 +715,29 @@ export function matchScenarios() {
|
||||
out.push({ id: 'coop2-ABYSS-10-boosted', mode: 'coop', difficulty: 'ABYSS', bots: 2, seed: 10, boost: { lp: 400, layers: 300 } });
|
||||
// 0.2.0 补位: a human seat (AI 托管, offline: its battles run on the server) that does not own a few NORMAL chess
|
||||
out.push({ id: 'coop2-NORMAL-14-standins', mode: 'coop', difficulty: 'NORMAL', bots: 1, seed: 14, human: { notOwned: MATCH_NOT_OWNED } });
|
||||
return out.map((m) => ({ ...m, family: 'matches', kind: 'match', about: `${m.mode} ${m.difficulty}, ${m.bots} bot seat(s)${m.human ? ` + 1 human seat (AI 托管) without ${m.human.notOwned.length} operators (补位 stand-ins)` : ''}, seed ${m.seed}${m.clientCombat === false ? ', server-run combat' : ''}${m.boost ? `, LP ${m.boost.lp} and ${m.boost.layers} layers per bond from the first prep` : ''}` }));
|
||||
// 0.2.0 自选编队: a human seat (AI 托管, offline) that slots 推进之王 and prototypes into the four DIY slots
|
||||
// (its 调度中心 starts at level 5, like the boosted runs' raised LP: the 自选 pieces are sold from level 5 / 6, which the
|
||||
// AI otherwise reaches only with a full board, where a single new operator rarely improves its lineup)
|
||||
out.push({ id: `coop2-NORMAL-${MATCH_DIY_SEED}-diy`, mode: 'coop', difficulty: 'NORMAL', bots: 1, seed: MATCH_DIY_SEED, human: { diy: MATCH_DIY, shopLevel: 5 } });
|
||||
const humanAbout = (h) => (h.notOwned ? ` + 1 human seat (AI 托管) without ${h.notOwned.length} operators (补位 stand-ins)`
|
||||
: ` + 1 human seat (AI 托管) with ${Object.keys(h.diy || {}).length} 自选 picks${h.shopLevel ? ` and its 调度中心 at level ${h.shopLevel} from the first prep` : ''}`);
|
||||
return out.map((m) => ({ ...m, family: 'matches', kind: 'match', about: `${m.mode} ${m.difficulty}, ${m.bots} bot seat(s)${m.human ? humanAbout(m.human) : ''}, seed ${m.seed}${m.clientCombat === false ? ', server-run combat' : ''}${m.boost ? `, LP ${m.boost.lp} and ${m.boost.layers} layers per bond from the first prep` : ''}` }));
|
||||
}
|
||||
|
||||
/**
|
||||
* The 自选 match's human seat (0.2.0 自选编队, shared/diy.js): 推进之王 (an owned 6★ with its operator kit; S3 and SOL-X) in a
|
||||
* tier-5 slot, Sharp (a prototype: its locked S3 + SOL-X) in the other tier-5 slot and a tier-6 one, 领主·Sharp in the last
|
||||
* — its shop sells them from 调度中心 level 5 / 6 and its AI fields them (the digest's `diy` lists the shop draws and the
|
||||
* fielded pieces per round).
|
||||
*/
|
||||
const MATCH_DIY = Object.freeze({
|
||||
chess_char_5_diy1_a: { charId: 'char_112_siege', skillIndex: 2, uniEquipId: 'uniequip_002_siege' },
|
||||
chess_char_5_diy2_a: { charId: 'char_609_acguad' },
|
||||
chess_char_6_diy1_a: { charId: 'char_609_acguad' },
|
||||
chess_char_6_diy2_a: { charId: 'char_617_sharp2' },
|
||||
});
|
||||
const MATCH_DIY_SEED = 70;
|
||||
|
||||
/**
|
||||
* The NORMAL chess the 补位 match's human seat does not own: operators its AI fields with seed 14 (瑕光 → 郁金香 from
|
||||
* round 4, 异客 → Stormeye, 耀骑士临光 → 郁金香 later; the digest's `standIns` lists them per round).
|
||||
@@ -737,8 +759,14 @@ export function runMatch(cfg) {
|
||||
const sched = new VirtualScheduler();
|
||||
const seats = [];
|
||||
// a human seat (cfg.human): offline (its battles run on the server) and on AI 托管 from the start; its not-owned chess
|
||||
// fight as their 补位 stand-ins
|
||||
if (cfg.human) seats.push({ seat: 0, playerId: 'h_0', name: 'H-1', isBot: false, connected: false, notOwned: [...cfg.human.notOwned] });
|
||||
// fight as their 补位 stand-ins, its 自选 picks join its shop
|
||||
if (cfg.human) {
|
||||
seats.push({
|
||||
seat: 0, playerId: 'h_0', name: 'H-1', isBot: false, connected: false,
|
||||
...(cfg.human.notOwned ? { notOwned: [...cfg.human.notOwned] } : null),
|
||||
...(cfg.human.diy ? { diy: JSON.parse(JSON.stringify(cfg.human.diy)) } : null),
|
||||
});
|
||||
}
|
||||
for (let i = 0; i < cfg.bots; i++) seats.push({ seat: seats.length, playerId: `ai_${i}`, name: `AI-${i + 1}`, isBot: true, connected: true });
|
||||
let summary = null;
|
||||
const logged = [];
|
||||
@@ -752,10 +780,24 @@ export function runMatch(cfg) {
|
||||
const rounds = [];
|
||||
// 补位: per round at SETTLE, the human's board pieces that fought as stand-ins (chess id → stand-in charId)
|
||||
const standIns = [];
|
||||
// 自选编队: per round, the human's shop draws of its 自选 pieces and, at SETTLE, its board pieces that fought as their
|
||||
// operators (slot id → charId) — an instance wrapper that only observes (as server/match/audit.js does)
|
||||
const diyRounds = [];
|
||||
const diyRolls = new Map();
|
||||
if (cfg.human?.diy) {
|
||||
const h = m.players.get('h_0');
|
||||
const roll = h._rollChessSlot.bind(h);
|
||||
h._rollChessSlot = () => {
|
||||
const slot = roll();
|
||||
if (slot && h.diyPickOf(slot.id)) diyRolls.set(m.round, [...(diyRolls.get(m.round) || []), slot.id.replace(/^chess_char_/, '')]);
|
||||
return slot;
|
||||
};
|
||||
}
|
||||
let last = '';
|
||||
m.start();
|
||||
const setup = { stageId: m.stageId, bossId: m.bossId, hiddenBossId: m.hiddenBossId, factions: [...(m.factions || [])] };
|
||||
let boosted = !cfg.boost;
|
||||
let levelled = !cfg.human?.shopLevel;
|
||||
const prepFunds = {};
|
||||
sched.runUntil(() => {
|
||||
const key = `${m.phase}:${m.round}`;
|
||||
@@ -769,6 +811,14 @@ export function runMatch(cfg) {
|
||||
ps.recompute();
|
||||
}
|
||||
}
|
||||
// the human's 调度中心 raised at the first prep (its shop rerolled there; the next level's price follows)
|
||||
if (!levelled && m.phase === 'PREP') {
|
||||
levelled = true;
|
||||
const h = m.players.get('h_0');
|
||||
h.shop.level = cfg.human.shopLevel;
|
||||
h.shop.upgradePrice = m.gd.upgradeBase(h.shop.level) ?? 0;
|
||||
h.rollShop({ keepFrozen: false });
|
||||
}
|
||||
// funds when the prep opens (income in, nothing bought yet: the bots play their prep in later callbacks)
|
||||
if (m.phase === 'PREP') for (const ps of m.order) prepFunds[ps.playerId] = ps.funds;
|
||||
if (m.phase === 'SETTLE') {
|
||||
@@ -782,11 +832,16 @@ export function runMatch(cfg) {
|
||||
];
|
||||
}
|
||||
rounds.push(row);
|
||||
if (cfg.human) {
|
||||
if (cfg.human?.notOwned) {
|
||||
const h = m.players.get('h_0');
|
||||
const fielded = [...h.board.values()].filter((p) => p.kind === 'chess' && h.fieldsStandIn(p.id)).map((p) => `${p.id.replace(/^chess_char_/, '')}→${m.gd.standIn(p.id)?.charId}`).sort(byId);
|
||||
standIns.push([m.round, fielded.join(' ')]);
|
||||
}
|
||||
if (cfg.human?.diy) {
|
||||
const h = m.players.get('h_0');
|
||||
const fielded = [...h.board.values()].filter((p) => p.kind === 'chess' && h.diyPickOf(p.id)).map((p) => `${p.id.replace(/^chess_char_/, '')}→${h.diyPickOf(p.id).charId}`).sort(byId);
|
||||
diyRounds.push([m.round, (diyRolls.get(m.round) || []).sort(byId).join(' '), fielded.join(' ')]);
|
||||
}
|
||||
}
|
||||
}
|
||||
return summary != null;
|
||||
@@ -817,7 +872,13 @@ export function runMatch(cfg) {
|
||||
rounds,
|
||||
players,
|
||||
};
|
||||
if (cfg.human) digest.standIns = { notOwned: [...m.players.get('h_0').standIns], rounds: standIns };
|
||||
if (cfg.human?.notOwned) digest.standIns = { notOwned: [...m.players.get('h_0').standIns], rounds: standIns };
|
||||
if (cfg.human?.diy) {
|
||||
const h = m.players.get('h_0');
|
||||
// [round, this round's shop draws of its 自选 pieces (slot ids), the 自选 pieces fielded at SETTLE (slot → operator)];
|
||||
// the stock left at the end
|
||||
digest.diy = { picks: JSON.parse(JSON.stringify(h.diy)), banned: [...h.diyBanned], rounds: diyRounds, stock: h.diyStock.snapshot() };
|
||||
}
|
||||
m.dispose();
|
||||
return digest;
|
||||
}
|
||||
@@ -961,7 +1022,7 @@ const ABOUT = {
|
||||
* fields (one of them a Hidden Core), six matches (solo 标准 / 绝境, co-op 2 / 4, the boosted Hidden Core run, the 补位
|
||||
* match) and the normal-record stand-in battles.
|
||||
*/
|
||||
const FAST_MATCHES = new Set(['solo-FUNNY-1', 'solo-HARD-1', 'coop2-NORMAL-3', 'coop4-ABYSS-7', 'solo-HARD-9-boosted', 'coop2-NORMAL-14-standins']);
|
||||
const FAST_MATCHES = new Set(['solo-FUNNY-1', 'solo-HARD-1', 'coop2-NORMAL-3', 'coop4-ABYSS-7', 'solo-HARD-9-boosted', 'coop2-NORMAL-14-standins', `coop2-NORMAL-${MATCH_DIY_SEED}-diy`]);
|
||||
const FAST_FIELDS = new Set(['boss-boss_1-pair', 'boss-boss_4-solo', 'boss-boss_7-pair', 'hidden-boss_9-pair', 'unite-1', 'unite-2']);
|
||||
export function isFast(sc) {
|
||||
if (sc.family === 'matches') return FAST_MATCHES.has(sc.id);
|
||||
|
||||
Reference in new issue
Block a user