feedback1 cell: a 突变细胞 transformation gains the new operator into the 整备区 and frees the carrier's tile (pointed out in PR #2)

Official gameplay footage (bilibili BV1vzyVBuEN9 at about 8:24, BV1Qkw1zMEoR at about 7:25) shows the
next prep with the carrier's tile empty, one more deployment left, and the returned equipment plus the
new tier+1 operator on the bench, deployed by hand afterwards. That is the PRTS 备注 read as written
("生效时,原干员销毁,获得一名高一阶的随机初始干员"): a destroy followed by a gain. The closed PR #2 made
the same claim from a playtest.

- PlayerState.transformChess: the carrier is destroyed wherever it stands (board tile freed, summons
  removed, copies back to the pool); its equipment, the cell included, returns to the hand first
  (overflow temp, auto-merge); then the new operator is gained through acquireChess like any gained
  operator (hand, overflow temp, the no-room rule of every gain; no summon card until it is deployed).
  The returnItems option is gone (the cell's built-in was its only caller).
- A merge the gain completes merges like any gained copy: the elite takes a consumed deployed copy's
  tile, else the hand. _mergeChess loses fromKey / fromDir, so the destroyed carrier's tile never
  counts (PR #2 had kept that path; not adopted); audit.js drops its exception for it.
- builtinMeta, ctx.transform, items/meta.js and bot.js comments follow. Bots and AI 托管 deploy the
  gained operator in their normal lineup step (no bot change needed; a real co-op round pins it).
- ITEM_RULES note and implFormula for the item card; data/items.json rebuilt with build-data --offline.
- Tests: facing, merge, playtest6-elite-to-board and items no longer pin the kept tile; feedback1-meta
  covers a deployed and a bench carrier, hand full -> temp, hand and temp full, a merge with and
  without a deployed copy (never the carrier's tile), 商业包装方案's count on the returned item, and bots /
  AI 托管 redeploying; feedback1-cell checks the new note.
This commit is contained in:
sganggs committed 2026-10-03 15:18:42 +08:00
1 parent 842e1e13c4
commit 91c9b21d7f
14 files changed
+400 -178

No files matched your search

+9 -5
View File
@@ -296,7 +296,7 @@ test('item merge with a full hand AND a full temp: the golden item takes the equ
m.dispose();
});
test('a merge completed during SETTLE (突变细胞) keeps its reward offer for the next prep; the elite takes a deployed copy\'s tile (the carrier\'s counts) or goes to the hand', () => {
test('a merge completed during SETTLE (突变细胞) keeps its reward offer for the next prep; the elite takes a deployed copy\'s tile (never the carrier\'s) or goes to the hand', () => {
const X = 'chess_char_2_04_a';
const fillers = Object.values(DATA.items).filter((i) => i.itemType === 'EQUIP' && !i.isGolden && !String(i.kind || '').startsWith('consume')).map((i) => i.itemId ?? i.id).filter(Boolean);
// where the carrier of 突变细胞 and the two copies of X are: hand/hand, board/hand, board/board
@@ -317,7 +317,9 @@ test('a merge completed during SETTLE (突变细胞) keeps its reward offer for
const holder = carrierAt === 'board' ? give(m, ps, T, 'board', legalTileFor(m, ps, T)) : give(m, ps, T);
holder.items.push(ps.newPiece('item', 'chess_item_5_08_e_a'));
const copies = copiesAt === 'board' ? [0, 1].map(() => give(m, ps, X, 'board', legalTileFor(m, ps, X))) : [give(m, ps, X), give(m, ps, X)];
const deployedTiles = [holder, ...copies].map((p) => ps.find(p.uid)).filter((l) => l.area === 'board').map((l) => l.key);
// the carrier is destroyed before its gain (PRTS 备注 "原干员销毁,获得一名…"): only the copies' tiles count
const holderTile = ps.find(holder.uid).key || null;
const deployedTiles = copies.map((p) => ps.find(p.uid)).filter((l) => l.area === 'board').map((l) => l.key);
const deployed0 = ps.deployCount;
for (let i = 0; ps.hand.some((x) => x == null); i++) giveItem(m, ps, fillers[i]);
const fillers0 = ps.hand.filter((p) => p && p.kind === 'item').length;
@@ -332,10 +334,10 @@ test('a merge completed during SETTLE (突变细胞) keeps its reward offer for
assert.deepEqual(ps.offers.map((o) => o.source), ['merge'], 'the promotion reward waits for this prep');
assert.ok(ps.privateView().shop.rewardOffer, 'and is shown');
const loc = ps.find(elite.uid);
if (holderTile) assert.ok(!ps.board.has(holderTile), `${label}: the carrier's tile is empty`);
if (deployedTiles.length) {
// PRTS 卫戍协议/帮助: a merge consuming a deployed copy sends the elite to that copy's tile — outside PREP too; the
// transformed carrier stood on the board, so its tile counts (when legal for the elite); of several, the first
// in deploy order (row desc, col asc)
// PRTS 卫戍协议/帮助: a merge consuming a deployed copy sends the elite to that copy's tile — outside PREP too; of
// several, the first in deploy order (row desc, col asc)
const map = ps.deployMap();
const pos = positionClass(m.gd.chess(elite.id));
const legal = deployedTiles.map((k) => k.split(',').map(Number)).filter(([r, c]) => canPlace(map, pos, r, c)).sort((a, b) => b[0] - a[0] || a[1] - b[1]);
@@ -346,6 +348,8 @@ test('a merge completed during SETTLE (突变细胞) keeps its reward offer for
} else {
assert.equal(loc.area, 'hand', `${label}: the consumed hand pieces freed the slots`);
}
// the carrier's deployment is gone; deployed copies were replaced by at most the one elite
assert.equal(ps.deployCount, deployed0 - (holderTile ? 1 : 0) - deployedTiles.length + (loc.area === 'board' ? 1 : 0), `${label}: deploy count`);
const CELL = 'chess_item_5_08_e_a';
assert.equal(ps.hand.filter((p) => p && p.kind === 'item' && p.id !== CELL).length, fillers0, `${label}: the hand's equipment stays`);
// the cell is not consumed (player feedback after 0.1.0): it came back to the hand — or temp, the hand being full