feedback5 WK: 自选 summon pieces — a test that a DIY 鸿雪 (her operator kit) gets her 打字机 stack in the hand when deployed in prep (the variant's deployLimit), places it, fields it in battle owned by her with the kit her install gives it (server and browser data sources), and loses it when she is sold; kits README documents how a kit finds its pieces (PlayerBattleInput token units with ownerUid, as op-bgsnow / op-cgbird do)

No new hook needed: the match already hands placed summon pieces to the battle with their owner. Golden unchanged.
This commit is contained in:
sganggs committed 2026-10-06 00:27:57 +08:00
1 parent 335f3f6045
commit c4e04609ea
2 files changed
+58 -2

No files matched your search

+10 -2
View File
@@ -182,8 +182,16 @@ pick chooses any of the three, so write every skill under `skills: { [skillId]:
`talents`, `trait` and `install` apply under every skill. Read every number from `chess` / `bb` — the same file serves
both tiers, both forms and both module stages. A 自选 piece has no 特质 and its bonds come from its factions: neither is
the kit's business. Summons: `battle.tokenDef(tokenId, unit)` / `battle.spawnToken(unit, …)` resolve the variant of the
pick; a placeable summon comes to its player's hand like any operator's (server/match/player/diy.js `placeableTokens`
reads the owner form's variant). Potential is 0 [ASSUMED: no account]. A new kit makes the operator a legal pick at once:
pick. A **placeable** summon (data/backups.json `tokens[id].placeable`, made by the pick's skill or a talent — the
variant's `sources`) is a hand piece like any operator's: when the 自选 piece is deployed in prep its player gets one
stack of the variant's `deployLimit` (server/match/player/diy.js `placeableTokens`, PlayerState `grantTokensFor`), places
it on a legal tile (the token's position / `ownerRange`), loses it with its owner (sold, merged, moved back), and the
match hands every placed piece to the battle as a PlayerBattleInput token unit `{ kind: 'token', tokenId, ownerUid }` —
so a kit finds its pieces before the battle starts as `battle.allyUnits` with `kind === 'token'`, its `defId` and
`ownerUnit === unit`, still undeployed (e.g. `op-bgsnow.js` gives its 打字机 pieces their kit in a talent's `install`
with `battle._setupUnit(t, kit)`; `op-cgbird.js` counts its 幻影). A kit test fields a piece the same way:
`units: [op, { uid, kind: 'token', tokenId, ownerUid, row, col }]` (`test/content/op_bgsnow.test.js`); the match side is
covered by `test/match/diy-shop.test.js` (a DIY 鸿雪's 打字机 from prep to battle). Potential is 0 [ASSUMED: no account]. A new kit makes the operator a legal pick at once:
the server's `welcome.diyKitted` lists `KITTED_CHARS`, so the 自选编队 picker offers it and `room.diy` keeps it.
**The fidelity rule and the checklist** above apply item by item: every skill at rank 4 and 7, every talent, every
+48
View File
@@ -357,3 +357,51 @@ test('a placeable 自选 summon comes to the hand like any operator\'s, places,
h.invariants();
m.dispose();
});
test('a DIY 鸿雪 (her operator kit): her 打字机 piece comes to the hand in prep, places, fields with its kit; it leaves with her', REAL, () => {
const SNOW = 'char_4055_bgsnow';
const TYPEWRITER = 'token_10026_bgsnow_subbow';
if (!KITTED_CHARS.includes(SNOW)) return; // (her kit is on feedback5 since O6)
const h = diyMatch().start();
const m = h.m;
h.toPrep(1);
const p0 = h.ps('p_0');
assert.equal(p0.setDiy({ [T5B]: { charId: SNOW, skillIndex: 0, uniEquipId: null } }), true, 'a kitted pick: no widened list');
p0.initDiyStock(new Set());
clear(p0);
const snow = gain(p0, T5B);
assert.ok(!p0.hand.some((p) => p && p.kind === 'token'), 'in the hand she brings no summon yet');
const tile = legalTileFor(m, p0, T5B);
assert.deepEqual(m.handle('p_0', { t: 'g.move', uid: snow.uid, to: { area: 'board', row: tile[0], col: tile[1] } }), { ok: true });
const stack = p0.hand.find((p) => p && p.kind === 'token');
assert.ok(stack, 'deployed, her 打字机 comes to the hand (PRTS 卫戍协议/帮助 §战斗部署)');
assert.deepEqual([stack.id, stack.ownerUid, stack.count], [TYPEWRITER, snow.uid, DATA.backups.tokens[TYPEWRITER].variants[`${SNOW}@2/1/4/0`].stats.deployLimit]);
const t2 = legalTileFor(m, p0, T5B, new Set([`${tile[0]},${tile[1]}`]));
assert.deepEqual(m.handle('p_0', { t: 'g.move', uid: stack.uid, to: { area: 'board', row: t2[0], col: t2[1] } }), { ok: true });
h.invariants();
const input = p0.battleInput();
const tok = input.units.find((u) => u.kind === 'token');
assert.deepEqual([tok.tokenId, tok.ownerUid], [TYPEWRITER, snow.uid], 'the battle input carries the piece with its owner');
const spec = wire(buildBattleSpec({
battleId: 't.3', fieldId: 'n:p_0', kind: 'normal', seed: 9, modeId: m.modeId, round: 1, stageId: m.stageId,
rect: { ...GEO.NORMAL_RECT }, timeLimit: 20, players: [input], spawns: [], routes: m.wave.routes,
flags: { layerGainsEnabled: true, ...m.gd.dp }, enemyOverrides: {}, content: 'full',
}));
for (const ds of [m.ds, browserSource()]) {
const bt = createBattleFromSpec(wire(spec), ds, { quiet: true, recordEvents: false });
bt.step();
const u = bt.allyUnits.find((x) => x.uid === snow.uid);
const t = bt.allyUnits.find((x) => x.kind === 'token' && x.defId === TYPEWRITER);
assert.ok(u && !u.kit.generic, 'her operator kit');
assert.ok(t, 'the 打字机 is fielded');
assert.equal(t.ownerUnit, u, 'owned by her (the kit finds its piece by owner)');
assert.equal(t.def.stats.maxHp, DATA.backups.tokens[TYPEWRITER].variants[`${SNOW}@2/1/4/0`].stats.maxHp, 'the token record of her form');
assert.ok(t.kit && !t.kit.generic, 'the typewriter runs the kit her install gave it');
assert.equal(bt.errors.length, 0);
}
// sold, the piece leaves with her
assert.deepEqual(m.handle('p_0', { t: 'g.sell', uid: snow.uid }), { ok: true });
assert.ok(![...p0.board.values(), ...p0.hand, ...p0.temp].some((p) => p && p.kind === 'token'), 'her summon leaves with her');
h.invariants();
m.dispose();
});