feedback5 WY: a newly merged elite 拉普兰德 is a new 拉普兰德 — its first manual refresh this round adds +8 even when its copies already fired (GitHub #169; the owner's decision of 2026-10-06)

garrison_123 「<刷新时>若为本回合首次主动刷新…此干员在整备区时也有效」 counts per piece (§21.1). No source says whether a
merge resets the count; since 0.1.1 _mergeChess carried the copies' highest per-piece round count onto the elite
([ASSUMED], conservative), so two white copies that had fired this round made an elite that did not fire again. The
owner decided on 2026-10-06 that a newly merged elite counts as a new 拉普兰德.

server/match/player/acquire.js _mergeChess no longer carries the per-piece round counters (拉普兰德's refresh count,
garrison:SERVER_GAIN_BOND_LAYER_BY_REFRESH_CNT:refreshes, is the only one): the elite starts at 0 like any new piece;
an in-place promotion keeps its count. test/match/feedback1-meta.test.js: the test that locked the old inherit is
updated on purpose (the elite adds +8 on its next refresh, once), and the case "two white copies refreshed this round,
merge, refresh → +8" is new; both fail without the change. Docs: META (counter API, edge cases), PLAYING, the comments
(PlayerState, pieces, effectsMeta, garrisons/meta), DESIGN §25.13.3 and the §25.10 table.
Golden unchanged (no corpus match merges 拉普兰德 after a refresh in the same round).
This commit is contained in:
sganggs committed 2026-10-06 21:41:49 +08:00
1 parent 269d7b0bcf
commit 661f1c074e
9 files changed
+79 -36

No files matched your search

+1 -1
View File
@@ -35,7 +35,7 @@
// outside PREP (a SETTLE merge's elite waits in temp for the next prep).
// * Per-piece round counters (pieceRoundCount, piece.meta.round): an operator's own counts of the current round
// (拉普兰德: the manual refreshes she witnessed — player feedback after 0.1.0); a new piece starts at 0, an elite
// merged this round keeps the highest of its copies' [ASSUMED].
// merged this round too (a new 拉普兰德 — GitHub #169, the owner's decision of 2026-10-06).
// * Transformations (transformChess, 突变细胞 — PRTS 备注 "生效时,原干员销毁,获得一名高一阶的随机初始干员"): a destroy
// followed by a gain. The carrier leaves wherever it stands (a board tile is freed, the deploy count drops), its
// equipment — the cell included — returns to the hand first (overflow temp), then the new chess is gained like any
+2 -2
View File
@@ -493,8 +493,8 @@ export function makeCtx(m, ps, source, hook, ev = null) {
counter: (k) => (Number.isFinite(ps.counters[k]) ? ps.counters[k] : 0),
setCounter: (k, v) => { if (typeof k === 'string' && Number.isFinite(v)) ps.counters[k] = v; return ps.counters[k] ?? 0; },
incCounter: (k, n = 1) => { if (typeof k !== 'string' || !Number.isFinite(n)) return 0; ps.counters[k] = (Number.isFinite(ps.counters[k]) ? ps.counters[k] : 0) + n; return ps.counters[k]; },
/** Per-piece counter of the current round (0 in a new round / for a new piece; an elite merged this round keeps the
* highest of its copies' — PlayerState.pieceRoundCount). */
/** Per-piece counter of the current round (0 in a new round / for a new piece, an elite merged this round included —
* PlayerState.pieceRoundCount). */
pieceCounter: (uid, k) => { const l = ps.find(uid); return l ? ps.pieceRoundCount(l.piece, k) : 0; },
incPieceCounter: (uid, k, n = 1) => { const l = ps.find(uid); return l ? ps.bumpPieceRoundCount(l.piece, k, n) : 0; },
+3 -6
View File
@@ -98,13 +98,10 @@ export class PlayerAcquire {
for (const it of l.piece.items || []) items.push(it);
l.piece.items = [];
}
// a new piece: this round's per-piece counters start at 0, none is carried over from the copies — a newly merged
// elite 拉普兰德 is a new 拉普兰德 and fires +8 on its own first manual refresh this round (GitHub #169; the owner's
// decision of 2026-10-06; pieceRoundCount)
const elite = this.newPiece('chess', goldenId, { poolCopies: copies });
// this round's per-piece counters: the highest of the copies' (an elite made from 拉普兰德 that already saw their
// first refresh this round does not fire again this round — [ASSUMED] conservative, pieceRoundCount)
for (const l of consumed) {
const rc = l.piece.meta && l.piece.meta.round;
if (rc && rc.r === this.m.round) for (const [k, v] of Object.entries(rc.n)) this.bumpPieceRoundCount(elite, k, Math.max(0, v - this.pieceRoundCount(elite, k)));
}
const deployed = consumed.filter((l) => l.key && !this.board.has(l.key)).map((l) => ({ key: l.key, dir: pieceDir(l.piece) }));
const toTile = (t) => { elite.dir = parseDir(t.dir) || 'RIGHT'; this.board.set(t.key, elite); return 'board'; };
const tile = mergeTile(deployed, (r, c) => this._legal(elite, r, c));
+4 -3
View File
@@ -114,9 +114,10 @@ export class PlayerPieces {
/**
* Per-piece counter of the current round (`piece.meta.round` = { r, n: { key: count } }): 0 for a key not counted yet
* this round. The counters belong to the operator: a move keeps them, a new piece (bought, granted, transformed)
* starts at 0, and an elite merged this round keeps the highest count of its copies (_mergeChess) — 拉普兰德's
* "本回合首次主动刷新" is the first manual refresh she witnesses (player feedback after 0.1.0, garrisons/meta.js).
* this round. The counters belong to the operator: a move keeps them, a new piece (bought, granted, transformed, or
* an elite merged this round — _mergeChess carries none over: GitHub #169, the owner's decision of 2026-10-06)
* starts at 0 — 拉普兰德's "本回合首次主动刷新" is the first manual refresh she witnesses (player feedback after 0.1.0,
* garrisons/meta.js).
*/
pieceRoundCount(piece, key) {
const rc = piece && piece.meta && piece.meta.round;
+9 -7
View File
@@ -22,8 +22,9 @@
// of any tier (the text gives no tier cap); a bond without an available chess falls through to the next tied one.
// * [ASSUMED] 松果: the "免费特殊招募" is a free pick-one offer of `rewardOffer.count` (3) chess of the pool's tier.
// * 拉普兰德 SERVER_GAIN_BOND_LAYER_BY_REFRESH_CNT "若为本回合首次主动刷新": per copy — the first manual refresh this
// operator witnesses in the round (players' report after 0.1.0); [ASSUMED] an elite merged this round keeps its
// copies' count, and a copy bought after selling one this round is a new copy (fires on its own first refresh).
// operator witnesses in the round (players' report after 0.1.0); an elite merged this round is a new 拉普兰德 and
// fires +8 on its own first one (GitHub #169, the owner's decision of 2026-10-06); [ASSUMED] a copy bought after
// selling one this round is a new copy (fires on its own first refresh).
// "本回合每刷新过1次" (SERVER_ADD_REFRESH_CNT_MULTIPLIER_BOND_LAYER, 阿罗玛 / 安洁莉娜 / 售出时)
// fires on another event and reads the player's refreshes of the round (roundStats), like 本回合每获得过 / 每花费.
@@ -198,11 +199,12 @@ H.SERVER_ADD_REFRESH_CNT_MULTIPLIER_BOND_LAYER = {
// refreshes this copy witnessed this round (board or hand), so a 拉普兰德 bought after the round's first refresh still
// fires on the next one (players' report after 0.1.0: "获得该干员后该回合的首次刷新" also stacks — the official behaviour;
// read as each trait instance counting its own SERVER_REFRESH_SHOP triggers against bb.refresh_cnt). A re-triggered trait
// (ev.trigger) is no manual refresh: it neither fires nor counts. A new copy (bought, granted) starts at 0. [ASSUMED]:
// the copies of an elite merged this round pass on their highest count (PlayerState.pieceRoundCount — no second trigger
// that round, conservative); a copy bought after selling one this round is a new copy — "获得该干员后" — and fires on its
// own first refresh (the server cannot tell it from any other copy; each such +4 costs her price + a refresh − the
// 1-fund refund, and needs her in the shop again).
// (ev.trigger) is no manual refresh: it neither fires nor counts. A new copy (bought, granted) starts at 0, and so does
// an elite merged this round: a new 拉普兰德, its own first manual refresh this round adds +8 even when its copies
// already fired (GitHub #169; the owner's decision of 2026-10-06 — PlayerState._mergeChess carries no counter over).
// [ASSUMED]: a copy bought after selling one this round is a new copy — "获得该干员后" — and fires on its own first
// refresh (the server cannot tell it from any other copy; each such +4 costs her price + a refresh − the 1-fund refund,
// and needs her in the shop again).
const REFRESH_CNT_KEY = 'garrison:SERVER_GAIN_BOND_LAYER_BY_REFRESH_CNT:refreshes'; // per-piece counter (module-prefixed)
H.SERVER_GAIN_BOND_LAYER_BY_REFRESH_CNT = {
onRefresh(ctx, ev) {