feedback5 R19F: community report 「小拉和赫默有概率在备战区叠不上叙拉古和远见」 checked, not a bug — 拉普兰德 stacks 叙拉古 from the 整备区 whenever 叙拉古 is active (it counts deployed 叙拉古 only, her bench copy is not one of the three) at her first manual refresh of the round; 赫默's trait has no 「此干员在整备区时也有效」 and the official data marks it deployed-only (conditionkey character_target_inboard); the 临时整备区 is not the 整备区. No code change: pinned by test/match/feedback5-bench-traits.test.js (the official data rule and the report's cases through the match paths), PLAYING.md §6 says where a 特质 works, CHANGELOG 核实后不是问题, DESIGN §25.22.7; golden unchanged
This commit is contained in:
1 parent
0255afd12b
commit
522f6287bb
4 files changed
+247
No files matched your search
@@ -127,6 +127,7 @@
|
|||||||
- 本局禁用的盟约在每个难度都一样,只移除全部盟约都被禁用的干员;炎佑也会受到源石溶剂「每秒受到 60 真实伤害」的效果(PRTS 备注);两名变形同构体变成的阿戈尔照常吞噬(社区反馈)。
|
- 本局禁用的盟约在每个难度都一样,只移除全部盟约都被禁用的干员;炎佑也会受到源石溶剂「每秒受到 60 真实伤害」的效果(PRTS 备注);两名变形同构体变成的阿戈尔照常吞噬(社区反馈)。
|
||||||
- 伺夜的狼群最多 3 只狼影,吟游者的特性可以回复战术家召唤物和不屈者的生命,荒芜拉普兰德三技能期间由无人机攻击、本人不普攻,叙拉古的恐惧概率,都和官方一致;突袭的玛恩纳攻击范围内有敌人时不突袭,是盟约原文的规则(社区反馈)。
|
- 伺夜的狼群最多 3 只狼影,吟游者的特性可以回复战术家召唤物和不屈者的生命,荒芜拉普兰德三技能期间由无人机攻击、本人不普攻,叙拉古的恐惧概率,都和官方一致;突袭的玛恩纳攻击范围内有敌人时不突袭,是盟约原文的规则(社区反馈)。
|
||||||
- 以下没有复现,需要具体的对局(干员、地图、回合或录像):野鬃把敌人顶出地图、锏拉动领袖、昆图斯占地变小、炼金单元没有伤害、归溟幽灵鲨被吞噬时进入再部署、炎佑被空中单位阻挡、选 Pith 策略时联防里阿戈尔的加成被清空、满血时源石溶剂不生效(社区反馈)。
|
- 以下没有复现,需要具体的对局(干员、地图、回合或录像):野鬃把敌人顶出地图、锏拉动领袖、昆图斯占地变小、炼金单元没有伤害、归溟幽灵鲨被吞噬时进入再部署、炎佑被空中单位阻挡、选 Pith 策略时联防里阿戈尔的加成被清空、满血时源石溶剂不生效(社区反馈)。
|
||||||
|
- 整备区里的拉普兰德照样叠【叙拉古】层数,但叙拉古要已经激活(只数作战区里不同的叙拉古干员,整备区里的她不算在内),而且每回合只看她经历的第一次主动刷新,那次没激活就要等下一回合;临时整备区不算整备区。赫默的特质没有「此干员在整备区时也有效」,和官方数据一样只在作战区生效(社区反馈;文档补充了说明)。
|
||||||
|
|
||||||
## 0.1.4 — 2026-10-06
|
## 0.1.4 — 2026-10-06
|
||||||
|
|
||||||
|
|||||||
@@ -112,6 +112,7 @@
|
|||||||
- **调和**:激活时,场上已有成员的核心盟约人数各 +1(只加 1 人,不论有几名调和干员)。盟约详情会写「含调和 +1」,成员列表上方列出带来这 1 人的调和干员(如缪尔赛思)。
|
- **调和**:激活时,场上已有成员的核心盟约人数各 +1(只加 1 人,不论有几名调和干员)。盟约详情会写「含调和 +1」,成员列表上方列出带来这 1 人的调和干员(如缪尔赛思)。
|
||||||
- **炎佑**:场上 6 名以上「炎」干员时召唤炎佑(9 名时两只)。它追踪全场仇恨最高的敌人并悬停在其上方,技能「祛恶之焰」锁定 1 个目标**持续喷火**(最长 20 秒,每秒对目标周围的敌人造成法术伤害并附带灼燃,喷火时不普攻,目标死亡或被沉默时结束);场上暂时没有敌人时停在原地等待。它持有官方的「孤立」:医疗干员不会治疗它,友方的增益、光环和天赋也不会选中它(敌人照常可以攻击它)。
|
- **炎佑**:场上 6 名以上「炎」干员时召唤炎佑(9 名时两只)。它追踪全场仇恨最高的敌人并悬停在其上方,技能「祛恶之焰」锁定 1 个目标**持续喷火**(最长 20 秒,每秒对目标周围的敌人造成法术伤害并附带灼燃,喷火时不普攻,目标死亡或被沉默时结束);场上暂时没有敌人时停在原地等待。它持有官方的「孤立」:医疗干员不会治疗它,友方的增益、光环和天赋也不会选中它(敌人照常可以攻击它)。
|
||||||
- **层数**:盟约通过各种效果叠加层数,层数越高效果越强(例如「炎」攻击力随层数提升、「远见」每 10 层给资金)。**层数整局保留,但只有盟约激活时才生效**,多数情况下也只有激活的盟约能叠层。每个盟约最多 **999 层**(与官方相同,取自官方客户端的常量):到 999 后再获得的层数直接作废,按层数发放的奖励(如「远见」的资金)也随之停止。作战中获得的层数在盟约栏里**实时**显示,作战结束后一直保留到结算(联防期间也不会退回去)。「调和」「协防干员」「独行」「绝技」与官方一样**不显示层数**(盟约栏、详情和结算卡片都不显示),但叠层的特质 / 策略 / 装备照样给它们加层(PRTS 盟约记录:「部分盟约不会显示叠加层数,但是叠加层数的特质/策略/装备等效果仍然对其生效」),这些层数也计入激活层数总和;它们的效果本身与层数无关。
|
- **层数**:盟约通过各种效果叠加层数,层数越高效果越强(例如「炎」攻击力随层数提升、「远见」每 10 层给资金)。**层数整局保留,但只有盟约激活时才生效**,多数情况下也只有激活的盟约能叠层。每个盟约最多 **999 层**(与官方相同,取自官方客户端的常量):到 999 后再获得的层数直接作废,按层数发放的奖励(如「远见」的资金)也随之停止。作战中获得的层数在盟约栏里**实时**显示,作战结束后一直保留到结算(联防期间也不会退回去)。「调和」「协防干员」「独行」「绝技」与官方一样**不显示层数**(盟约栏、详情和结算卡片都不显示),但叠层的特质 / 策略 / 装备照样给它们加层(PRTS 盟约记录:「部分盟约不会显示叠加层数,但是叠加层数的特质/策略/装备等效果仍然对其生效」),这些层数也计入激活层数总和;它们的效果本身与层数无关。
|
||||||
|
- **特质在哪里生效**:「进入休整期时」「休整期结束时」「刷新时」的特质(详情卡的第一项)只在干员部署在作战区时生效;文字里写着「此干员在整备区时也有效」的拉普兰德、调香师、波登可,以及激活【炎】/【投资人】时的琳琅诗怀雅,在整备区里也生效(临时整备区不算),与官方数据一致;「获得时」「售出时」的特质在获得、售出的那一刻生效,不论干员在哪里。「远见」「奇迹」「投资人」的「整备区的…干员也可用于激活盟约」只是把整备区的成员算进人数,不会让赫默等干员的特质在整备区生效。写着「使已激活的…」的特质要求盟约当时已经激活:例如「叙拉古」只数作战区里不同的叙拉古干员,整备区里的拉普兰德不算在这 3 名里;拉普兰德每回合只在她经历的第一次主动刷新时叠层(免费刷新也算),那次刷新时叙拉古没有激活,这一回合就不会再叠。
|
||||||
- **加成怎么叠**:盟约、策略、装备、机变卡和特质给的「攻击力 / 防御力 / 生命值 +X%」与技能的「攻击力 +X%」**相加**后再乘到属性上(官方:直接乘算),例如精准 +130%、独行 +60%、一件 +40% 的装备合计 +230%,而不是 ×2.3×1.6×1.4。「伤害提升至 X%」「受到的伤害 +X%」(维多利亚、谢拉格、奥术、脆弱等)这类伤害倍率之间仍然相乘;但同名效果只算一个——同一战场两名玩家都开了「奥术」时,敌人身上只挂一层奥术易伤,取两人中较高的那一个(不再相乘);两名「灵知」的坚冰、两名「莱恩哈特」/「缄默德克萨斯」的法抗削减也一样只算最强的一个。「坚守」的反伤计入被打的那名成员(个人对领袖伤害统计里能看到)。
|
- **加成怎么叠**:盟约、策略、装备、机变卡和特质给的「攻击力 / 防御力 / 生命值 +X%」与技能的「攻击力 +X%」**相加**后再乘到属性上(官方:直接乘算),例如精准 +130%、独行 +60%、一件 +40% 的装备合计 +230%,而不是 ×2.3×1.6×1.4。「伤害提升至 X%」「受到的伤害 +X%」(维多利亚、谢拉格、奥术、脆弱等)这类伤害倍率之间仍然相乘;但同名效果只算一个——同一战场两名玩家都开了「奥术」时,敌人身上只挂一层奥术易伤,取两人中较高的那一个(不再相乘);两名「灵知」的坚冰、两名「莱恩哈特」/「缄默德克萨斯」的法抗削减也一样只算最强的一个。「坚守」的反伤计入被打的那名成员(个人对领袖伤害统计里能看到)。
|
||||||
- 每局会随机**禁用**若干盟约(标准模拟较少),它们的干员不会出现在商店;开局的「确认本局信息」会列出,选择策略时(「查看禁用盟约与干员」)和对局中(左上角 🔍)也能再查看。
|
- 每局会随机**禁用**若干盟约(标准模拟较少),它们的干员不会出现在商店;开局的「确认本局信息」会列出,选择策略时(「查看禁用盟约与干员」)和对局中(左上角 🔍)也能再查看。
|
||||||
- 两件同名装备会合成一件更强的装备。休整期结束时才到手的那一件(维多利亚每 25 层发的维式重锤是这种情况)先留在整备区,已经戴在干员身上的不会为了这场战斗被卸下,合成出的进阶装备在**下一休整期开始**时出现;其他时候拿到的同名装备仍立刻合成。整备区和临时整备区都满时,这件新装备照旧销毁(「整备区已满,获得的装备已销毁」),不会换进装备栏。特定的装备组合可以为干员赋予额外的盟约效果:「变形同构体」与另一件盟约装备(如维式重锤)一同携带时,携带者视为该装备对应盟约的成员。对应关系见变形同构体详情卡的「天赋」一栏(本局禁用的盟约会标出;装在干员身上时,正在生效的那一组会高亮)。
|
- 两件同名装备会合成一件更强的装备。休整期结束时才到手的那一件(维多利亚每 25 层发的维式重锤是这种情况)先留在整备区,已经戴在干员身上的不会为了这场战斗被卸下,合成出的进阶装备在**下一休整期开始**时出现;其他时候拿到的同名装备仍立刻合成。整备区和临时整备区都满时,这件新装备照旧销毁(「整备区已满,获得的装备已销毁」),不会换进装备栏。特定的装备组合可以为干员赋予额外的盟约效果:「变形同构体」与另一件盟约装备(如维式重锤)一同携带时,携带者视为该装备对应盟约的成员。对应关系见变形同构体详情卡的「天赋」一栏(本局禁用的盟约会标出;装在干员身上时,正在生效的那一组会高亮)。
|
||||||
|
|||||||
@@ -1980,3 +1980,37 @@ here, each reproduced first; the rest were answered (not a bug, already fixed in
|
|||||||
the parent), `test/match/playtest6-matchflow.test.js` #235 (2; the 联防 one fails on the parent: losses = the LP
|
the parent), `test/match/playtest6-matchflow.test.js` #235 (2; the 联防 one fails on the parent: losses = the LP
|
||||||
settled, no result ticker, gone in the next phase), SP_E2E `test/ui/resultbox.e2e.test.js` (3, mock variants
|
settled, no result ticker, gone in the next phase), SP_E2E `test/ui/resultbox.e2e.test.js` (3, mock variants
|
||||||
`unite[,through][,helper|,dead]`). Docs: PLAYING.md §1 / §5, design/network.md, design/client.md, design/match.md.
|
`unite[,through][,helper|,dead]`). Docs: PLAYING.md §1 / §5, design/network.md, design/client.md, design/match.md.
|
||||||
|
|
||||||
|
#### 25.22.7 「小拉和赫默有概率在备战区叠不上叙拉古和远见」 (community report, relayed by the owner on 2026-10-07): not a bug — 拉普兰德 stacks from the 整备区 when 【叙拉古】 is active, 赫默's trait works only deployed (official data) — no code change; `docs/PLAYING.md` §6
|
||||||
|
|
||||||
|
- **Seen**: 「有的时候在备战区的干员不能100%叠层…小拉和赫默有概率在备战区叠不上叙拉古和远见」. 备战区 is the players' word
|
||||||
|
for the 整备区 (community guides: 「远见奇迹干员不需要了就放备战区」). Reproduced through the match paths: a matrix of 26
|
||||||
|
cases (the 整备区, the board, the 临时整备区; bought before / after the refresh, two copies, an elite merged this round, a
|
||||||
|
full hand, a 机变 round, a pending reward, the shop frozen, a free refresh, a reconnect, a bot), and 115 whole matches
|
||||||
|
(60 solo, 40 co-op, 15 co-op with client-run combat; a random human and the bots) checked against an oracle written
|
||||||
|
from the official text: 11 951 refreshes and 3 640 round starts, no difference. Every case without layers is one of
|
||||||
|
four rules, nothing random: (1) 【叙拉古】 is inactive — it counts different 叙拉古 deployed, and 拉普兰德 on the bench is
|
||||||
|
not one of the three (普罗旺斯 + 德克萨斯 deployed, her in the 整备区: 2/3); (2) her trait fires on the first manual
|
||||||
|
refresh she sees each round, so a refresh made while 叙拉古 is inactive spends it — deploying the third 叙拉古 and
|
||||||
|
refreshing again adds nothing until the next round (in the random matches 440 of the 675 first refreshes a bench
|
||||||
|
拉普兰德 saw were of this kind, the other 235 stacked); (3) in the 临时整备区 she neither fires nor counts, and fires on
|
||||||
|
her first refresh once a free slot pulls her into the 整备区; (4) 赫默 in the 整备区 never fires, 远见 active or not
|
||||||
|
(204 such round starts).
|
||||||
|
- **Official**: garrison_123 「<刷新时>若为本回合首次主动刷新,使已激活的【叙拉古】层数+4,此干员在整备区时也有效」 (elite +8, no
|
||||||
|
condition key); 叙拉古 activeCondition BOARD (「在场」). garrison_80 「<进入休整期时>使自身已激活的盟约层数+2」 (elite +4) has
|
||||||
|
no such clause, and its blackboard carries conditionkey `character_target_inboard`: deployed only. In the act2autochess
|
||||||
|
garrison table every 进入休整期时 / 休整期结束时 / 刷新时 trait that works in the 整备区 says so and has no condition key —
|
||||||
|
拉普兰德 123, 调香师 81, 波登可 102, 琳琅诗怀雅 98 (「若已激活【炎】/【投资人】此特质在整备区也有效」); every other one is
|
||||||
|
`character_target_inboard`, or a row / tile-in-front condition only a deployed operator meets. 远见's
|
||||||
|
「整备区的【远见】干员也可用于激活盟约」 counts bench members for the bond; it moves no trait to the bench. The in-game tips
|
||||||
|
name the overflow 「临时整备区」 apart from the 整备区 (「处于临时整备区的调度资源,在进入下一回合后会自动销毁」).
|
||||||
|
- **Now**: unchanged. Prep traits run for deployed operators and, unless `character_target_inboard`, for the 10 整备区
|
||||||
|
slots (`effectsMeta.js _garrisons`), never for the 临时整备区 (§21.1, like the 远见 / 奇迹 / 投资人 count). PLAYING.md §6
|
||||||
|
now says where a 特质 works, that 「使已激活的…」 needs the bond active, and what 拉普兰德's refresh needs.
|
||||||
|
- [ASSUMED] (unchanged since §21.1): a refresh made while 叙拉古 is inactive spends her trait for the round (the literal
|
||||||
|
「若为本回合首次主动刷新」, counted per copy); the 临时整备区 is not the 整备区; a free refresh is a manual refresh. Open for
|
||||||
|
the owner: fire on her first refresh with 叙拉古 active instead — no source either way.
|
||||||
|
- Golden: unchanged (no code change). Tests: `test/match/feedback5-bench-traits.test.js` (6 pins: the official data rule;
|
||||||
|
+4 / +8 in the 整备区 with the hand full, the shop frozen, a free refresh, a reward pending, a reconnect, in a 机变 round,
|
||||||
|
for a bot; the inactive bond and the spent refresh; the 临时整备区; a copy bought after the first refresh and 普罗旺斯's
|
||||||
|
free refresh; 赫默 deployed vs in the 整备区).
|
||||||
@@ -0,0 +1,211 @@
|
|||||||
|
// Community report relayed by the owner (2026-10-07): 「有的时候在备战区的干员不能100%叠层…小拉和赫默有概率在备战区叠不上叙拉古
|
||||||
|
// 和远见」 — 拉普兰德 (小拉, Ⅱ) in the 整备区 sometimes adds no 【叙拉古】 layers, 赫默 (Ⅱ) no 【远见】 layers. Reproduced over
|
||||||
|
// whole matches (scratchpad notes, R19F): every non-firing case is a rule of the official data, nothing random —
|
||||||
|
// * 拉普兰德 garrison_123 「<刷新时>若为本回合首次主动刷新,使已激活的【叙拉古】层数+4,此干员在整备区时也有效」: she works in
|
||||||
|
// the 整备区, but 【叙拉古】 must be active (activeCondition BOARD: 3 different 叙拉古 deployed — her bench copy is not one
|
||||||
|
// of them), and her trait fires on the first manual refresh she sees this round: a refresh made while 叙拉古 is
|
||||||
|
// inactive spends it. The 临时整备区 is not the 整备区 (as for every 整备区 rule): she fires once she is in it.
|
||||||
|
// * 赫默 garrison_80 「<进入休整期时>使自身已激活的盟约层数+2」 has no 「此干员在整备区时也有效」 and carries the official
|
||||||
|
// blackboard conditionkey character_target_inboard: deployed only. 【远见】's 「整备区的【远见】干员也可用于激活盟约」 counts
|
||||||
|
// bench members for the bond, it does not move traits to the bench.
|
||||||
|
// Through the real match paths (g.refresh / g.buy / g.move, the round loop's ROUND_START dispatch). Pins; no change.
|
||||||
|
import { test } from 'node:test';
|
||||||
|
import assert from 'node:assert/strict';
|
||||||
|
import { makeMatch, give, legalTileFor, checkInvariants, chessOfTier, DATA } from './harness.js';
|
||||||
|
import { createRegistry } from '../../server/match/effectsMeta.js';
|
||||||
|
|
||||||
|
const QUIET = { warn() {}, error() {}, info() {} };
|
||||||
|
const REG = createRegistry({ log: QUIET });
|
||||||
|
const OK = { ok: true };
|
||||||
|
const LAP = 'chess_char_2_16_a'; // 拉普兰德 (Ⅱ, 叙拉古) — garrison_123_a
|
||||||
|
const LAP_B = 'chess_char_2_16_b';
|
||||||
|
const PROVENCE = 'chess_char_1_07_a'; // 普罗旺斯 (Ⅰ, 叙拉古; <获得时>获得1次免费刷新)
|
||||||
|
const TEXAS = 'chess_char_1_08_a'; // 德克萨斯 (Ⅰ, 叙拉古)
|
||||||
|
const SIYE = 'chess_char_3_18_a'; // 伺夜 (Ⅲ, 叙拉古)
|
||||||
|
const SILENCE = 'chess_char_2_02_a'; // 赫默 (Ⅱ, 远见) — garrison_80_a
|
||||||
|
const SILENCE_B = 'chess_char_2_02_b';
|
||||||
|
const SWIRE = 'chess_char_3_03_a'; // 诗怀雅 (Ⅲ, 远见)
|
||||||
|
const FILLER = 'chess_char_1_01_a';
|
||||||
|
/** distinct fillers for a full 整备区: no 叙拉古 / 远见, no prep-side 特质 (one copy each, so nothing merges) */
|
||||||
|
const FILLERS = [1, 2].flatMap((t) => chessOfTier(t, (c) => !c.bonds.includes('siracusaShip') && !c.bonds.includes('visiShip')
|
||||||
|
&& (c.garrisonIds || []).every((g) => DATA.garrisons[g].eventType === 'IN_BATTLE')));
|
||||||
|
const LAP_KEY = 'garrison:SERVER_GAIN_BOND_LAYER_BY_REFRESH_CNT';
|
||||||
|
const SIL_KEY = 'garrison:SERVER_ADD_BOND_CHESS_ALL';
|
||||||
|
|
||||||
|
function setup({ mode = 'solo', bots = 0, round = 1, pid = 'p_0', seed = 70 } = {}) {
|
||||||
|
const h = makeMatch({ mode, bots, seed, registry: REG, fake: true }).start();
|
||||||
|
h.toPrep(round);
|
||||||
|
const m = h.m;
|
||||||
|
const ps = h.ps(pid);
|
||||||
|
for (const p of [...ps.board.values(), ...ps.hand.filter(Boolean), ...ps.temp.filter(Boolean)]) if (p.kind === 'chess') ps.returnCopies(p);
|
||||||
|
ps.board.clear();
|
||||||
|
ps.hand.fill(null);
|
||||||
|
ps.temp.fill(null);
|
||||||
|
ps.offers.length = 0;
|
||||||
|
ps.funds = 50;
|
||||||
|
ps.layers = {};
|
||||||
|
ps.pendingFunds = 0;
|
||||||
|
ps.shop.freeRefreshes = 0;
|
||||||
|
ps.bandId = null; // isolate from the band's own prep effects
|
||||||
|
ps.ready = false;
|
||||||
|
ps.recompute();
|
||||||
|
/** layers each trait added (the dispatcher's reason = the registry key) */
|
||||||
|
const got = { lap: 0, sil: 0 };
|
||||||
|
const addLayers = ps.addLayers.bind(ps);
|
||||||
|
ps.addLayers = (b, n, o = {}) => {
|
||||||
|
const a = addLayers(b, n, o);
|
||||||
|
if (o.reason === LAP_KEY && b === 'siracusaShip') got.lap += a;
|
||||||
|
if (o.reason === SIL_KEY && b === 'visiShip') got.sil += a;
|
||||||
|
return a;
|
||||||
|
};
|
||||||
|
const refresh = () => assert.deepEqual(m.handle(pid, { t: 'g.refresh' }), OK);
|
||||||
|
const deploy = (id) => give(m, ps, id, 'board', legalTileFor(m, ps, id));
|
||||||
|
const place = (piece) => {
|
||||||
|
const [row, col] = legalTileFor(m, ps, piece.id);
|
||||||
|
assert.deepEqual(m.handle(pid, { t: 'g.move', uid: piece.uid, to: { area: 'board', row, col }, dir: 'RIGHT' }), OK);
|
||||||
|
};
|
||||||
|
const buy = (id) => {
|
||||||
|
ps.shop.slots[0] = { kind: 'chess', id, basePrice: m.gd.chessPrice(id), frozen: false, sold: false };
|
||||||
|
const before = new Set(ps.allChess().map((p) => p.uid));
|
||||||
|
assert.deepEqual(m.handle(pid, { t: 'g.buy', slot: 0 }), OK, `buy ${id}`);
|
||||||
|
return ps.allChess().find((p) => !before.has(p.uid));
|
||||||
|
};
|
||||||
|
const fillHand = () => { let i = 0; while (ps.hand.some((x) => x == null)) give(m, ps, FILLERS[i++], 'hand'); };
|
||||||
|
const nextRound = () => { ps.ready = false; h.toPrep(m.round + 1); ps.funds = 50; };
|
||||||
|
const siracusa = () => ({ active: !!ps.bonds.siracusaShip?.active, count: ps.bonds.siracusaShip?.count ?? 0 });
|
||||||
|
return { h, m, ps, got, refresh, deploy, place, buy, fillHand, nextRound, siracusa };
|
||||||
|
}
|
||||||
|
|
||||||
|
test('official data: a 进入休整期时 / 休整期结束时 / 刷新时 特质 works in the 整备区 exactly when its text says so', () => {
|
||||||
|
const PREP = new Set(['SERVER_PREP_START', 'SERVER_PREP_FIN', 'SERVER_REFRESH_SHOP']);
|
||||||
|
const bench = [];
|
||||||
|
for (const g of Object.values(DATA.garrisons)) {
|
||||||
|
if (!PREP.has(g.eventType)) continue;
|
||||||
|
const key = g.bbStr && g.bbStr.conditionkey;
|
||||||
|
if (/整备区时也有效|整备区也有效/.test(g.desc)) {
|
||||||
|
assert.equal(key, undefined, `${g.garrisonId}: a bench trait carries no condition key`);
|
||||||
|
bench.push(g.garrisonId);
|
||||||
|
} else if (!/^身前一格干员若为/.test(g.desc)) {
|
||||||
|
// the rest: deployed only (character_target_inboard), or a row condition that only a deployed operator can meet;
|
||||||
|
// the two copy traits (白面鸮 / 塞雷娅 "身前一格…") read the tile in front and are positional by nature
|
||||||
|
assert.ok(key === 'character_target_inboard' || key === 'character_same_row', `${g.garrisonId}: ${key} — ${g.desc}`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// 拉普兰德 123, 调香师 81, 波登可 102, 琳琅诗怀雅 98 (若已激活【炎】/【投资人】) — normal and elite records
|
||||||
|
assert.deepEqual(bench.sort(), ['garrison_102_a', 'garrison_102_b', 'garrison_123_a', 'garrison_123_b', 'garrison_81_a', 'garrison_81_b', 'garrison_98_a', 'garrison_98_b']);
|
||||||
|
for (const id of ['garrison_80_a', 'garrison_80_b']) {
|
||||||
|
const g = DATA.garrisons[id];
|
||||||
|
assert.equal(g.bbStr.conditionkey, 'character_target_inboard', `${id} (赫默): deployed only`);
|
||||||
|
assert.doesNotMatch(g.desc, /整备区/);
|
||||||
|
}
|
||||||
|
assert.equal(DATA.bonds.siracusaShip.countMode, 'BOARD', '叙拉古 counts deployed operators only');
|
||||||
|
assert.equal(DATA.bonds.visiShip.countMode, 'BOARD_AND_DECK', '远见 also counts the 整备区');
|
||||||
|
});
|
||||||
|
|
||||||
|
test('拉普兰德 in the 整备区 with 3 different 叙拉古 deployed: +4 on her first manual refresh (+8 elite), whatever else holds', () => {
|
||||||
|
const cases = {
|
||||||
|
plain: () => {},
|
||||||
|
'hand full (10/10)': (s) => s.fillHand(),
|
||||||
|
'shop frozen': (s) => assert.deepEqual(s.m.handle('p_0', { t: 'g.freeze' }), OK),
|
||||||
|
'a free refresh': (s) => { s.ps.shop.freeRefreshes = 1; },
|
||||||
|
'a reward offer pending': (s) => { s.ps.offers.push({ source: 'merge', kind: 'chess', ids: [FILLER], tier: 2 }); },
|
||||||
|
'a reconnect': (s) => { s.m.onDisconnect('p_0'); s.m.onReconnect('p_0'); },
|
||||||
|
};
|
||||||
|
for (const [name, before] of Object.entries(cases)) {
|
||||||
|
for (const [id, n] of [[LAP, 4], [LAP_B, 8]]) {
|
||||||
|
const s = setup();
|
||||||
|
for (const x of [PROVENCE, TEXAS, SIYE]) s.deploy(x);
|
||||||
|
give(s.m, s.ps, id, 'hand');
|
||||||
|
before(s);
|
||||||
|
assert.deepEqual(s.siracusa(), { active: true, count: 3 }, name);
|
||||||
|
s.refresh();
|
||||||
|
assert.equal(s.got.lap, n, `${name} ${id}`);
|
||||||
|
s.refresh();
|
||||||
|
assert.equal(s.got.lap, n, `${name}: once per round`);
|
||||||
|
s.m.dispose();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// a 机变 round (solo R6) and a bot seat (its own ps.refresh, bot.js) work the same
|
||||||
|
const r6 = setup({ round: 6 });
|
||||||
|
for (const x of [PROVENCE, TEXAS, SIYE]) r6.deploy(x);
|
||||||
|
give(r6.m, r6.ps, LAP, 'hand');
|
||||||
|
r6.refresh();
|
||||||
|
assert.equal(r6.got.lap, 4, 'a 机变 round');
|
||||||
|
r6.m.dispose();
|
||||||
|
const bot = setup({ mode: 'coop', bots: 1, pid: 'ai_0' });
|
||||||
|
for (const x of [PROVENCE, TEXAS, SIYE]) bot.deploy(x);
|
||||||
|
give(bot.m, bot.ps, LAP, 'hand');
|
||||||
|
assert.deepEqual(bot.ps.refresh(), OK);
|
||||||
|
assert.equal(bot.got.lap, 4, 'a bot');
|
||||||
|
bot.m.dispose();
|
||||||
|
});
|
||||||
|
|
||||||
|
test('拉普兰德 in the 整备区 is not one of the 3 叙拉古: inactive bond, nothing — and that refresh was her first of the round', () => {
|
||||||
|
const s = setup();
|
||||||
|
s.deploy(PROVENCE);
|
||||||
|
s.deploy(TEXAS);
|
||||||
|
const lap = give(s.m, s.ps, LAP, 'hand');
|
||||||
|
assert.deepEqual(s.siracusa(), { active: false, count: 2 }, 'two 叙拉古 deployed: 2/3 (she sits in the 整备区)');
|
||||||
|
s.refresh();
|
||||||
|
assert.equal(s.got.lap, 0, '已激活的【叙拉古】: not active, no layers');
|
||||||
|
s.place(lap);
|
||||||
|
assert.deepEqual(s.siracusa(), { active: true, count: 3 }, 'deployed she is the third');
|
||||||
|
s.refresh();
|
||||||
|
assert.equal(s.got.lap, 0, '若为本回合首次主动刷新: her first refresh of the round was the inactive one');
|
||||||
|
s.nextRound();
|
||||||
|
s.refresh();
|
||||||
|
assert.equal(s.got.lap, 4, 'the next round: her first refresh with 叙拉古 active');
|
||||||
|
checkInvariants(s.m);
|
||||||
|
s.m.dispose();
|
||||||
|
});
|
||||||
|
|
||||||
|
test('拉普兰德 in the 临时整备区: no layers there; once she is in the 整备区 her next refresh adds +4', () => {
|
||||||
|
const s = setup();
|
||||||
|
for (const x of [PROVENCE, TEXAS, SIYE]) s.deploy(x);
|
||||||
|
s.fillHand();
|
||||||
|
const lap = give(s.m, s.ps, LAP, 'temp');
|
||||||
|
assert.equal(s.ps.find(lap.uid).area, 'temp');
|
||||||
|
s.refresh();
|
||||||
|
assert.equal(s.got.lap, 0, 'the 临时整备区 is not the 整备区');
|
||||||
|
assert.deepEqual(s.m.handle('p_0', { t: 'g.sell', uid: s.ps.hand[0].uid }), OK); // a free slot pulls her in
|
||||||
|
assert.equal(s.ps.find(lap.uid).area, 'hand');
|
||||||
|
s.refresh();
|
||||||
|
assert.equal(s.got.lap, 4, 'her first refresh in the 整备区');
|
||||||
|
s.refresh();
|
||||||
|
assert.equal(s.got.lap, 4, 'once');
|
||||||
|
checkInvariants(s.m);
|
||||||
|
s.m.dispose();
|
||||||
|
});
|
||||||
|
|
||||||
|
test('拉普兰德 bought after the round\'s first refresh, and a free refresh from 普罗旺斯: both stack (each copy counts its own refreshes)', () => {
|
||||||
|
const s = setup();
|
||||||
|
s.deploy(TEXAS);
|
||||||
|
s.deploy(SIYE);
|
||||||
|
give(s.m, s.ps, LAP, 'hand');
|
||||||
|
const prov = s.buy(PROVENCE); // <获得时>获得1次免费刷新
|
||||||
|
assert.equal(s.ps.shop.freeRefreshes, 1);
|
||||||
|
s.place(prov);
|
||||||
|
s.refresh(); // the free one is a manual refresh too
|
||||||
|
assert.equal(s.ps.shop.freeRefreshes, 0);
|
||||||
|
assert.equal(s.got.lap, 4);
|
||||||
|
s.buy(LAP); // a second copy, bought after the round's first refresh, stays in the 整备区
|
||||||
|
s.refresh();
|
||||||
|
assert.equal(s.got.lap, 8, 'her own first refresh');
|
||||||
|
s.m.dispose();
|
||||||
|
});
|
||||||
|
|
||||||
|
test('赫默: +2 / +4 at the round start only when deployed; in the 整备区 nothing even with 远见 active (character_target_inboard)', () => {
|
||||||
|
for (const [id, where, partner, n] of [
|
||||||
|
[SILENCE, 'board', 'hand', 2], [SILENCE, 'board', 'board', 2], [SILENCE_B, 'board', 'hand', 4],
|
||||||
|
[SILENCE, 'hand', 'board', 0], [SILENCE, 'hand', 'hand', 0], [SILENCE_B, 'hand', 'board', 0],
|
||||||
|
]) {
|
||||||
|
const s = setup();
|
||||||
|
if (where === 'board') s.deploy(id); else give(s.m, s.ps, id, 'hand');
|
||||||
|
if (partner === 'board') s.deploy(SWIRE); else give(s.m, s.ps, SWIRE, 'hand');
|
||||||
|
assert.ok(s.ps.bonds.visiShip.active, '远见 counts the 整备区: active');
|
||||||
|
s.nextRound();
|
||||||
|
assert.equal(s.got.sil, n, `${id} ${where}, partner ${partner}`);
|
||||||
|
assert.equal(s.ps.layers.visiShip || 0, n);
|
||||||
|
s.m.dispose();
|
||||||
|
}
|
||||||
|
});
|
||||||
Reference in new issue
Block a user