feedback5 WE2: AI 托管 buys the player's own 自选 pieces more readily and levels toward them a round earlier (#13)

bot.js: the player's own slotted piece scores DIY_PIECE_BONUS (12) more; levelUp takes the
step that opens a slotted tier with copies left one round earlier (diyOpensAt). Bots field
no 自选 piece. Exports buyScoreOf / levelUp / diyOpensAt for the tests. Docs: DESIGN §6.6,
PLAYING. Golden: coop2-NORMAL-70-diy moved — the 自选 seat under AI 托管 reaches level 6 one
round earlier and fields its tier-6 Sharp at round 12.
This commit is contained in:
sganggs committed 2026-10-06 15:54:07 +08:00
1 parent d31de31e31
commit 2eaef154d5
5 files changed
+98 -16

No files matched your search

+13 -13
View File
@@ -1623,7 +1623,7 @@
"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},
"rng": {"setup":25,"shop":503,"waves":0,"draft":34,"bots":16504,"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]},
@@ -1666,18 +1666,18 @@
},
{
"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],
"h_0": [1,27,14,0,2,6,8,"a5cfb056",363,"arcaneShip 0/2, indomShip 1/16, preciShip 2/116, sargonShip 1/139, steadShip 1/51, suntShip 1/0, swiftShip 1/5, victoriaShip 0/19, visiShip 1/36",19,19,0,106],
"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",16,33,17,112]
"h_0": [1,27,16,0,2,6,8,"750c100e",381,"arcaneShip 0/2, emptyShip 1/0, indomShip 0/16, preciShip 2/148, sargonShip 1/181, steadShip 0/51, suntShip 1/0, swiftShip 0/5, victoriaShip 0/19, visiShip 1/52",33,33,0,125],
"ai_0": [1,28,12,0,0,6,8,"d3c53bfd",63,"sargonShip 0/8, siracusaShip 1/12, skillfulShip 1/18, soloShip 1/0, suntShip 2/0, visiShip 1/0, yanShip 1/33",18,33,15,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",32,50,18,125]
"h_0": [1,27,16,4,0,6,8,"d90306b7",486,"arcaneShip 0/2, indomShip 0/16, maniShip 1/0, preciShip 2/166, sargonShip 1/209, steadShip 0/51, suntShip 1/0, swiftShip 0/5, victoriaShip 1/31, visiShip 1/80",50,50,0,140],
"ai_0": [1,28,12,0,0,6,8,"5700d2b4",81,"miraShip 1/0, sargonShip 0/8, siracusaShip 1/20, skillfulShip 1/24, soloShip 1/0, suntShip 2/0, visiShip 1/0, yanShip 1/37",14,50,36,124]
}
],
"players": {
@@ -1691,9 +1691,9 @@
"title": {"id":"comment_1","name":"卫戍之星","picId":"comment_icon_1","text":"对敌方领袖造成伤害最高(仅胜利时)"},
"trophies": 4,
"reward": 400,
"stats": {"activatedLayers":534,"bossDamage":572474,"dmgDealt":4818483,"gold":154,"itemsEquipped":23,"kills":349,"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"]
"stats": {"activatedLayers":502,"bossDamage":596123,"dmgDealt":5126250,"gold":156,"itemsEquipped":23,"kills":364,"leaks":0,"lpLost":0,"merges":4,"perfectRounds":13,"refreshes":29},
"lineup": ["6_05_a@12,8[2_01_e_a+4_08_e_a]","5_02_a@11,6[5_01_e_a+6_01_e_a]","6_08_a@11,7[3_09_e_a]","4_20_b@11,8[6_07_e_a+1_05_e_a]","6_11_a@10,6","5_21_b@10,8[3_04_e_a+6_09_e_a]","4_07_b@9,8[3_07_e_a+5_07_e_a]","4_23_b@9,9[4_04_e_b+4_11_e_a]"],
"bonds": ["arcaneShip 0/2","indomShip 0/16","maniShip 1/0","preciShip 2/166","sargonShip 1/209","steadShip 0/51","suntShip 1/0","swiftShip 0/5","victoriaShip 1/31","visiShip 1/96"]
},
"ai_0": {
"alive": true,
@@ -1705,9 +1705,9 @@
"title": {"id":"comment_3","name":"坚若磐石","picId":"comment_icon_3","text":"目标生命值损失最少"},
"trophies": 4,
"reward": 400,
"stats": {"activatedLayers":113,"bossDamage":215026,"dmgDealt":2640057,"gold":137,"itemsEquipped":20,"kills":247,"leaks":46,"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"]
"stats": {"activatedLayers":105,"bossDamage":191377,"dmgDealt":2449031,"gold":135,"itemsEquipped":20,"kills":231,"leaks":62,"lpLost":0,"merges":6,"perfectRounds":8,"refreshes":12},
"lineup": ["2_04_b@12,8[6_02_e_a+6_09_e_a]","5_02_a@11,7","6_05_a@11,8","5_20_a@10,5[1_05_e_a+1_02_e_b]","6_11_a@10,6","3_03_b@10,8[3_11_e_b+2_04_e_a]","1_08_b@9,8[4_06_e_a+4_11_e_a]","3_02_b@9,9[4_04_e_a]"],
"bonds": ["maniShip 1/0","miraShip 1/0","preciShip 1/0","sargonShip 1/8","siracusaShip 1/28","skillfulShip 1/30","soloShip 1/0","suntShip 1/0","visiShip 1/0","yanShip 1/39"]
}
},
"diy": {
@@ -1730,7 +1730,7 @@
[9,"",""],
[10,"",""],
[11,"",""],
[12,"",""],
[12,"","6_diy1_a→char_609_acguad"],
[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}
+58
View File
@@ -0,0 +1,58 @@
// test/match/feedback5-we2-bot-diy.test.js — 0.2.0 WE2 #13: AI 托管 of a human with 自选 picks buys the player's own slotted
// pieces more readily (bot.js buyScore +DIY_PIECE_BONUS) and levels the 调度中心 one round earlier for the step that opens a
// slotted tier (levelUp, diyOpensAt). Bots field no 自选 piece: nothing changes for them.
// Run: node --test test/match/feedback5-we2-bot-diy.test.js
import { test } from 'node:test';
import assert from 'node:assert/strict';
import { buyScoreOf, levelUp, diyOpensAt, DIY_PIECE_BONUS } from '../../server/match/bot.js';
import { diyGameData } from '../../server/match/player/diy.js';
import { makeMatch, give, chessOfTier, checkInvariants } from './harness.js';
const T5A = 'chess_char_5_diy1_a';
const PICK = { charId: 'char_112_siege', skillIndex: 2, uniEquipId: 'uniequip_002_siege' }; // 推进之王
function prep(seed = 911) {
const seats = [
{ seat: 0, playerId: 'p_0', name: 'P0', isBot: false, connected: true, diy: { [T5A]: PICK } },
{ seat: 1, playerId: 'p_1', name: 'P1', isBot: false, connected: true },
];
const h = makeMatch({ mode: 'coop', difficulty: 'NORMAL', seats, seed }).start();
h.toPrep(1);
return { h, m: h.m, p0: h.ps('p_0'), p1: h.ps('p_1') };
}
test('#13 buyScore: the player\'s own 自选 piece scores DIY_PIECE_BONUS more than the same record without the 自选 mark', () => {
const { m, p0 } = prep();
const rec = p0.gd.chess(T5A);
assert.ok(rec.diyFor, 'a composed 自选 record');
const withMark = buyScoreOf(m, p0, T5A);
// the same player and record, only the 自选 mark taken off (a view of the match's data with that record)
const plain = { ...rec };
delete plain.diyFor;
const saved = p0.gd;
p0.gd = diyGameData(m.gd, new Map([[T5A, plain]]));
let without;
try { without = buyScoreOf(m, p0, T5A); } finally { p0.gd = saved; }
assert.equal(withMark - without, DIY_PIECE_BONUS);
checkInvariants(m);
m.dispose();
});
test('#13 levelUp: one round earlier for the step that opens a slotted tier (round 9: level 4 → 5 at a price above 2); a player without picks waits', () => {
const { m, p0, p1 } = prep(912);
m.round = 9;
for (const ps of [p0, p1]) {
ps.shop.level = 4;
ps.shop.upgradePrice = 5;
ps.funds = 6;
const ids = chessOfTier(1).filter((id) => m.pool.has(id) && m.pool.left(id) > 0).slice(0, 6);
for (const id of ids) give(m, ps, id, 'hand');
}
assert.ok(diyOpensAt(p0, 5) && !diyOpensAt(p1, 5) && !diyOpensAt(p0, 6));
levelUp(m, p0);
levelUp(m, p1);
assert.equal(p0.shop.level, 5, 'p_0 (a tier-5 自选 piece) levels now');
assert.equal(p1.shop.level, 4, 'p_1 waits for round 10 (LEVEL_TARGET)');
m.dispose();
});