feedback5 R19E: the 鸭爵 strategy's swapped-in 流泪小子 hides again 1 s after a block ends instead of the official 0 s — a deliberate deviation, the owner's decision of 2026-10-07 (「要不等待个1秒这样再重新隐匿」); a separate commit the integrator can drop; golden: 1 scenario moves (matches solo-HARD-2)

content/enemies/archetypes.js stealth: an enemy spawned with the band's swap tag 'duck' (content/bands/meta.js
duckReplace) gets its 隐匿 back no sooner than DUCK_STEALTH_RESTORE (1 s, content/enemies/helpers.js) after a block ends
— in practice 流泪小子 (enemy_2034_sythef_2), the only one of the four swaps with 隐匿, officially 0 s (PRTS 流泪小子
「隐匿(解除阻挡0秒后恢复)」). The same enemy anywhere else keeps 0 s: the bounty card's 流泪小子 (enemy_2034_sythef), an
untagged spawn; a 3 s 隐匿 stays 3 s; a copy leaked into 联防 keeps its tag and the 1 s. After each strike (its stunned
blocker lets go) it is now revealed through its 0.8 s post-strike stand and 0.2 s of walking.

Measured, 8 seeds each — leaked / HP left / revealed s per battle; W = 3 elite melee in a lane + an elite sniper vs the
R6 wave's ground enemies with one swapped 流泪小子, B = 8 elites on 战场#01 vs the whole R6 wave with one swap: before
the clip fix W 5/8, 42 %, 2.3 / B 8/8, 47 %, 3.2; after it W 5/8, 31 %, 2.6 / B 8/8, 58 %, 2.5; with the 1 s W 4/8,
31 %, 4.0 / B 8/8, 56 %, 5.5. Options for the owner (measured on the fixed engine, not built): 2 s W 4/8, 31 %, 5.4 /
B 8/8, 46 %, 8.5; 3 s (the general 隐匿 rule) W 4/8, 31 %, 6.4 / B 8/8, 39 %, 11.5; a 1.5 s stun on the copy (below its
2 s interval, so its blocker takes it over again) W 4/8, 2 %, 4.0 / B 4/8, 6 %, 5.5; a 1 s stun: knocked out in all 16;
the 1 s delay + a 2 s stun W 4/8, 21 %, 5.1 / B 5/8, 26 %, 8.1. Worse: half speed while 隐匿; a stunned operator that
keeps blocking it (pins that operator for good).

Golden: matches solo-HARD-2 — its bot holds 鸭爵; the swapped 流泪小子 of round 13 is free and revealed for 49 ticks in
its 1 s windows (the round-12 one is never released by a block), so the match parts at round 13 (a victory instead of
a defeat); the other 282 scenarios unchanged.

Tests: test/content/feedback5-duck-restealth.test.js (3; the swap's 1 s and the band path fail on the parent, the 0 s
guard passes on both); enemies_bosses, feedback2-surtr-stealth, feedback1-ducklord, feedback5-attack-clip-rest pass.
Docs: CHANGELOG 0.2.0 有意与官方不同的调整 (and the 核实后不是问题 line points to it), docs/history/0.2.0.md §25.22.6,
docs/PLAYING.md, docs/SIM.md, design/engine.md.
This commit is contained in:
sganggs committed 2026-10-07 12:58:06 +08:00
1 parent ef25c03525
commit dd66adfbd1
10 files changed
+132 -19

No files matched your search

+2 -1
View File
@@ -23,6 +23,7 @@
- 效果作用在召唤物范围上的技能,敌人进入场上召唤物对应的范围时也会释放:麦哲伦一技能(龙腾的范围,含空中)、令三技能(每个召唤物周围的十字范围,仅地面)、电弧二技能(赛柯的范围,仅地面)和三技能(桑特拉的范围,含空中),不再只等敌人走进干员自己的攻击范围(维护者 2026-10-06 的决定)。
- 望:本模式里放在棋盘上的棋子原本不占她的持有库存,技能叠到上限 7 枚后一直阻回,整场只能放一次。现在场上的棋子占用库存,棋子退场会回到库存、原地再部署时再占一枚,库存满时退场一枚就会降到 6 枚,技能整场都能循环释放(维护者 2026-10-06 的决定,与本模式「不消耗持有数量」的说明不同)。
- 乌尔比安三技能【移动】后被击倒时,回到自己的部署位倒地,并在那里再部署,不再留在【移动】后的位置(社区反馈;维护者 2026-10-07 的决定。PRTS 的规则是在倒下的位置再部署,这里只对他的三技能例外)。
- 策略「鸭爵」换来的流泪小子,解除阻挡后要过 1 秒才重新隐匿(官方是 0 秒):它晕眩干员、被放开以后,周围的干员有一秒可以攻击它;别处出现的流泪小子(悬赏敌人)仍按官方 0 秒(社区反馈、GitHub #214;维护者 2026-10-07 的决定)。
### 规则(按官方)
@@ -128,7 +129,7 @@
- 本局禁用的盟约在每个难度都一样,只移除全部盟约都被禁用的干员;炎佑也会受到源石溶剂「每秒受到 60 真实伤害」的效果(PRTS 备注);两名变形同构体变成的阿戈尔照常吞噬(社区反馈)。
- 伺夜的狼群最多 3 只狼影,吟游者的特性可以回复战术家召唤物和不屈者的生命,荒芜拉普兰德三技能期间由无人机攻击、本人不普攻,叙拉古的恐惧概率,都和官方一致;突袭的玛恩纳攻击范围内有敌人时不突袭,是盟约原文的规则(社区反馈)。
- 以下没有复现,需要具体的对局(干员、地图、回合或录像):野鬃把敌人顶出地图、锏拉动领袖、昆图斯占地变小、炼金单元没有伤害、归溟幽灵鲨被吞噬时进入再部署、炎佑被空中单位阻挡、选 Pith 策略时联防里阿戈尔的加成被清空、满血时源石溶剂不生效(社区反馈)。
- 策略「鸭爵」换来的流泪小子(「泪眼汪汪」「哭狗」):攻击使干员晕眩 3.5 秒,而被晕眩的干员不能阻挡(官方术语「晕眩:无法移动、阻挡、攻击及使用技能」),所以它会接着走向下一名干员;解除阻挡后 0 秒就重新隐匿,移动速度 0.8,攻击间隔 2 秒,走到下一名干员面前马上抬手、0.767 秒后命中,这些都与官方数据一致。AI 队友选了鸭爵,你的作战同样会换怪(策略原文「你和队友遭遇的部分敌人」),最终攻势和隐秘核心也会换,换上的敌人走被替换敌人的路线(社区反馈、GitHub #214,感谢 @FrogThai)。
- 策略「鸭爵」换来的流泪小子(「泪眼汪汪」「哭狗」):攻击使干员晕眩 3.5 秒,而被晕眩的干员不能阻挡(官方术语「晕眩:无法移动、阻挡、攻击及使用技能」),所以它会接着走向下一名干员;官方解除阻挡后 0 秒就重新隐匿(本模式对鸭爵换来的这一只改为 1 秒,见「有意与官方不同的调整」),移动速度 0.8,攻击间隔 2 秒,走到下一名干员面前马上抬手、0.767 秒后命中,这些都与官方数据一致。AI 队友选了鸭爵,你的作战同样会换怪(策略原文「你和队友遭遇的部分敌人」),最终攻势和隐秘核心也会换,换上的敌人走被替换敌人的路线(社区反馈、GitHub #214,感谢 @FrogThai)。
## 0.1.4 — 2026-10-06
+1 -1
View File
@@ -82,7 +82,7 @@
- **元素损伤条**在单位自己的血条正下方:左边是元素图标,白条是剩余的元素值,只有真正爆发时才会耗尽;爆发后条变成元素的颜色、图标闪烁,在冷却期间慢慢回满(这段时间不会再积累元素损伤)。
- **敌人**:远程敌人的射程按官方从干员的碰撞体积(半径 0.25 格)算起,比数据上的攻击范围半径远 0.25 格——例如萨卡兹枯朽战车(射程 2.2)能打到中心 2.45 格以内、站在低地的干员。没被阻挡的远程敌人每次攻击都会站定、播完攻击动作,两次攻击之间才继续前进。它的第一下攻击就是「秽蚀轰击」(在目标处留下半径 1.7、持续 10 秒的污染区:低地每秒 50、高台 25 点真实伤害,同名不叠加),被阻挡时普通攻击伤害翻倍。“萨科塔之翼”等血量第一次低于一半时会在原地小范围乱飞 5 秒(官方叫「恐惧」);叙拉古、妮芙等干员造成的恐惧会让敌人朝远离干员的方向逃跑。隐匿的敌人只有被阻挡(或被揭示)时才能被选为攻击目标,溅射、技能的圆形范围伤害也打不到没被挡住的隐匿敌人(和官方一样);阻挡解除后 3 秒内它仍然可以被选中(官方页面注明 0 秒或 1 秒的敌人按页面),之后才重新隐匿:深池逐火战士每次被击倒后先重生 1 秒,然后变成继续往前走的隐匿「余烬」(需要 5 次伤害击倒,护卫的「火灰」10 次),一般要用干员挡住它才能选中(挡住时它不再半透明;被阻挡时被击倒的,重生结束后约 2 秒内也能用远程或范围伤害补刀),10 秒内没打掉就满血站起来;敌人重生时身上的减速、恐惧等状态会被清空、技能冷却从头开始,被恐惧的逐火战士变成余烬后也能被挡住;转译基底·α 原始形态受到的伤害全部无效,受到第 4 次物理伤害、第 4 次法术伤害或被阻挡时花 2 秒变成寻仇者、特战术师或幽灵,之后才能被击倒。
- **复活**(策略「埃芒加德」的前 3 次、M3茧甲)与官方一致:算作一次被击倒,紧接着在倒下的位置免费立即再部署——干员「被击倒时」和「部署时」的效果都会触发(例如砾的不屈 / 卡西米尔层数、瑕光的层数、卡西米尔盟约的部署攻击力),技力和身上的增益像普通再部署一样重置。
- **策略「鸭爵」(“神秘顾客”)**:从第 5 回合起,你和队友的每场作战(**最终攻势和隐秘核心也算**,联防不另外替换)有 0–2 名地面敌人(不含领袖及其部件、悬赏敌人)被替换为特殊版本的鸭爵、高普尼克、流泪小子或圆仔,出现在被替换敌人的时间和路线上(两人同场的领袖战里,各自只替换走向自己保护目标点的敌人);击倒者获得 1 资金(联防中的队友也算),漏进保护目标点照常扣 1 点目标生命值。鸭爵和圆仔**不攻击、无法被阻挡**:鸭爵受到伤害后移动速度 +300%;圆仔始终面向我方干员较多的一侧,来自正面的物理 / 法术伤害 −80%。流泪小子隐匿(被阻挡时才能被选中),攻击使目标晕眩 3.5 秒;高普尼克免疫晕眩、冻结和浮空。
- **策略「鸭爵」(“神秘顾客”)**:从第 5 回合起,你和队友的每场作战(**最终攻势和隐秘核心也算**,联防不另外替换)有 0–2 名地面敌人(不含领袖及其部件、悬赏敌人)被替换为特殊版本的鸭爵、高普尼克、流泪小子或圆仔,出现在被替换敌人的时间和路线上(两人同场的领袖战里,各自只替换走向自己保护目标点的敌人);击倒者获得 1 资金(联防中的队友也算),漏进保护目标点照常扣 1 点目标生命值。鸭爵和圆仔**不攻击、无法被阻挡**:鸭爵受到伤害后移动速度 +300%;圆仔始终面向我方干员较多的一侧,来自正面的物理 / 法术伤害 −80%。流泪小子隐匿(被阻挡时才能被选中),攻击使目标晕眩 3.5 秒,被晕眩的干员挡不住它(官方规则);它被放开后要过 1 秒才重新隐匿(官方是 0 秒,本模式有意放宽,维护者 2026-10-07 的决定);高普尼克免疫晕眩、冻结和浮空。
- 右上角 🔍▶▶ 可以查看本回合即将来袭的敌人(预览区),左上角 🔍 查看本局信息(禁用盟约、禁用干员、策略)。
## 5. 联防(同盟)
+1 -1
View File
@@ -624,7 +624,7 @@ of coverage per 3 s), kept because the current wording no longer says so (feedba
| `tremble` (战栗) | 被阻挡后无法进行普通攻击: no normal attack **while blocked** (abilities still fire) | – |
| `palsy` (麻痹) | each stack cancels one enemy normal attack (max 3, lasts until consumed); refused by 麻痹免疫 (data `palsyImmune`); each cancel fires the hook `palsyTrigger` (§5) | stacks, default 1 |
| `disarm` | no normal attacks | – |
| `stealth` / `reveal` | 隐匿: untargetable unless blocked (an ally: only the enemy it blocks attacks it, and an enemy's area effects and buff auras skip it even when it blocks that enemy — `targeting.js areaSelectable` / `auraSelectable`, GitHub #97, owner 2026-10-04; 0.1.2 made an exception for that blocker) / cancels stealth. An enemy's 隐匿 also stays off after a block: each block's end (`Battle._stealthSwitch`, every release path) switches each of its 隐匿 sources off for `STEALTH_RESTORE` (3) s — PRTS 作战机制 §隐匿 "不被阻挡的3秒后重新进入隐匿" — or the source's own "(解除阻挡N秒后恢复)" (buff `data.stealthRestore`: 0 s for 业余竞演者, 节日爵士乐手, 假想敌:骨刺, 流泪小子, 访问团强攻冠军 and 清明's veil, 1 s for the 家族灭迹人); a new block inside it lifts it again and its end restarts the window; our operators' 隐匿 / 迷彩 never lift by blocking (DESIGN §22.8). `targeting.js enemyStealthed` is the one test: the b.snap stealth bit is set only while its 隐匿 is on (drawn solid otherwise); an operator's radius area damage (`foesInRadius`) skips it too (PRTS 作战机制 §AOE伤害判定 "对攻击范围内的每个可以被选中的敌人进行判定"; until 0.1.1 the splash still hit it) | – |
| `stealth` / `reveal` | 隐匿: untargetable unless blocked (an ally: only the enemy it blocks attacks it, and an enemy's area effects and buff auras skip it even when it blocks that enemy — `targeting.js areaSelectable` / `auraSelectable`, GitHub #97, owner 2026-10-04; 0.1.2 made an exception for that blocker) / cancels stealth. An enemy's 隐匿 also stays off after a block: each block's end (`Battle._stealthSwitch`, every release path) switches each of its 隐匿 sources off for `STEALTH_RESTORE` (3) s — PRTS 作战机制 §隐匿 "不被阻挡的3秒后重新进入隐匿" — or the source's own "(解除阻挡N秒后恢复)" (buff `data.stealthRestore`: 0 s for 业余竞演者, 节日爵士乐手, 假想敌:骨刺, 流泪小子, 访问团强攻冠军 and 清明's veil, 1 s for the 家族灭迹人 — and no less than 1 s for a 鸭爵 swap, spawn tag `'duck'`: `content/enemies/helpers.js DUCK_STEALTH_RESTORE`, the owner's decision of 2026-10-07, a deliberate deviation for its 流泪小子); a new block inside it lifts it again and its end restarts the window; our operators' 隐匿 / 迷彩 never lift by blocking (DESIGN §22.8). `targeting.js enemyStealthed` is the one test: the b.snap stealth bit is set only while its 隐匿 is on (drawn solid otherwise); an operator's radius area damage (`foesInRadius`) skips it too (PRTS 作战机制 §AOE伤害判定 "对攻击范围内的每个可以被选中的敌人进行判定"; until 0.1.1 the splash still hit it) | – |
| `camou` (迷彩) | an ally's camouflage (ba.camou "不阻挡时不成为敌方普通攻击的目标(无法躲避溅射类攻击)"): like `stealth` for enemy targeting (only the enemy it blocks attacks it) — but an enemy's splash and other area effects still hit it (`areaSelectable` does not check it) — and on screen (b.snap stealth bit, `snapshot.js flagsOf`), but not 隐匿 for 隐匿-conditions (叙拉古, 家族徽章) and under its own buff keys. 忍冬 S3 (key `vulpis:camou`, until her next cast), 寒芒克洛丝 S1 | – |
| `invulnerable` | ignores damage | – |
| `levitate` (浮空) | stun + unblockable (unblocks enemies) + 失衡免疫 (`noDisplace`); an air unit meanwhile (`isFlying`: melee cannot hit it); **half duration on units with (current) massLevel > 3**; refused on data flyers (`motion` FLY) that hold no 缚地 and on units already levitated (PRTS 异常效果 "若单位数据上为飞行单位且不持有缚地异常或是持有浮空异常则Buff取消") — a 近地悬浮 enemy is WALK in its data, so it can be levitated; 浮空 is not one of the 近地悬浮 enemies' drop triggers | – |
+1 -1
View File
@@ -175,7 +175,7 @@ Each domain file `server/sim/content/{tokens,bonds,garrisons,items,bands,enemies
- bonds: 23 (battle + meta), exact per-layer formulas from research 02.
- garrisons: 43 effect keys (IN_BATTLE via hooks; SERVER_* via meta registry).
- items: 56 equipment (+golden) + 3 Arts; 变形同构体 bond grant; VI-tier set/combos.
- bands: 40 strategies (meta + battle). 鸭爵 “神秘顾客” swaps 0–2 ground enemies (not the leader, its parts or bounties; on a pair boss field only the player's half — the boss / hidden `onBattleStart` event carries `routes` and `side`) in every teammate's normal / Final Assault / Hidden Core battle from R5 into the act2 鸭爵 / 高普尼克 / 流泪小子 / 圆仔 (lpr 1, 1 fund to the killer, 联防 included) (§21.5).
- bands: 40 strategies (meta + battle). 鸭爵 “神秘顾客” swaps 0–2 ground enemies (not the leader, its parts or bounties; on a pair boss field only the player's half — the boss / hidden `onBattleStart` event carries `routes` and `side`) in every teammate's normal / Final Assault / Hidden Core battle from R5 into the act2 鸭爵 / 高普尼克 / 流泪小子 / 圆仔 (lpr 1, 1 fund to the killer, 联防 included) (§21.5); the swaps carry the spawn tag `'duck'`, and the swapped 流泪小子 hides again 1 s after a block ends, not the official 0 s (the owner's decision of 2026-10-07, §25.22.6).
- enemies: special-type behaviours (FLY, TIMES, ELEMENT, DOT, INVISIBLE, REFLECTION, SPECIAL) and notable individual abilities from `data/enemies.json`; bosses 1–10 scripted — the huge leaders (假想敌:胄 ×2, 假想敌:管 ×2, 盐风主教昆图斯, 阿利斯泰尔, “萨米的意志”) are 自缚 + 无法被阻挡 (PRTS 天赋; `content/bosses.js SELF_BOUND`: a persistent noMove + unblockable buff from spawn) with their hit area (§19.4); 圆仔 and 鸭爵 are 无法被阻挡 (PRTS 天赋, §21.5); the knock-out forms follow PRTS — 深池逐火 余烬 (5 / 10 hits, 隐匿, blockable, back after 10 s), 转译基底·α (damage cancelled until its 2 s change into 寻仇者 / 特战术师 / 幽灵), the leaders' 重生, 守墓石像's statue and flight — and every 重生 clears what operators put on the enemy and restarts its skills from their initial cooldown (§21.4, §21.19); the mode's air units are 静态刚体, never moved (§21.9); leader parts (斩胄之剑 / 破胄之锤 / 碎铳之簧) pass every damage they take to their leader 1:1 as 无来源 HP loss (`PART_TRANSFER` 1, PRTS "等量"), a 假想敌:胄 drone that dies costs it 2 % of the pool (`DRONE_LINK_BASE 'pool'`) (§20.10, §20.13); devices/terrain.
- choices: 机变 families (悬赏决策 bounties, 道具补给, 机密商店, 战术决策 team/personal buffs). 悬赏决策 = the official structure of its round (choices.json `bountyDrafts`, 86 drafted cards: R3 a fixed set of six 两场 cards, R9 6 of a boss group's list, R11 6 of a seen 7-card list; §21.2); 机密商店 (R11 the official composition) and 战术决策 can offer the same card twice (`shopDraft` / `tacticDraft`); bounty cards carry `descRaw`; no draft offers a multi-round card, and 教鞭's lasts two battles (`choices.js MULTI_ROUND_BOUNTY_BATTLES`, §20.6). 教鞭 = a random 战术特训 card (3 drawn, 1 taken).
+20
View File
@@ -2027,3 +2027,23 @@ here, each reproduced first; the rest were answered (not a bug, already fixed in
the same round. The displacement rule alone moves one roster scenario.
- Tests: `test/sim/feedback5-attack-clip-rest.test.js` (5; the four behaviour tests fail on 0255afd, the data pin passes
on both). Docs: docs/SIM.md (enemy attacks), DESIGN §5 (design/engine.md), docs/DATA.md (`attackAnim`).
- **Deliberate deviation — the owner's decision of 2026-10-07** (「鸭爵策略产生的泪眼汪汪我想了一下,要不等待个1秒这样再重新隐匿,
或者提供点别的降低难度的方案」): the 鸭爵 swap's copies (spawn tag `'duck'`, `content/bands/meta.js duckReplace` — in
practice 流泪小子, the only one of the four with 隐匿) get their 隐匿 back no sooner than 1 s after a block ends
(`content/enemies/helpers.js DUCK_STEALTH_RESTORE`, `archetypes.js stealth`; officially 0 s). The same enemy anywhere
else — the bounty card's 流泪小子 (`enemy_2034_sythef`), any other spawn — keeps 0 s; a copy leaked into 联防 keeps its
tag and the 1 s. After each strike it is now revealed through its 0.8 s stand and 0.2 s of walking.
- Measured, 8 seeds each (W: 3 elite melee operators in a lane + an elite sniper against the R6 wave's ground enemies with
one swapped 流泪小子; B: 8 elite operators on 战场#01 against the whole R6 wave with one swap) — leaked, HP left, revealed
s per battle: before 0.2.0's fixes W 5/8, 42 %, 2.3 / B 8/8, 47 %, 3.2; after the clip fix W 5/8, 31 %, 2.6 / B 8/8,
58 %, 2.5; with the 1 s W 4/8, 31 %, 4.0 / B 8/8, 56 %, 5.5. Other options, measured on the fixed engine and not built:
2 s — W 4/8, 31 %, 5.4 / B 8/8, 46 %, 8.5; 3 s (the general 隐匿 rule of PRTS 作战机制) — W 4/8, 31 %, 6.4 / B 8/8,
39 %, 11.5; a 1.5 s stun on the copy (below its 2 s interval: its blocker recovers while it still stands in its block
radius and takes it over again) — W 4/8, 2 %, 4.0 / B 4/8, 6 %, 5.5; a 1 s stun — knocked out in all 16; the 1 s delay
with a 2 s stun — W 4/8, 21 %, 5.1 / B 5/8, 26 %, 8.1. Worse, not offered: half speed while 隐匿 (B 8/8, 76 % — it
stays hidden longer), a stunned operator that keeps blocking it (pins that operator for the whole battle).
- Golden (this rule alone): matches solo-HARD-2 — its bot holds 鸭爵; the swapped 流泪小子 of round 13 is free and revealed
for 49 ticks in its 1 s windows (the round-12 one is never released by a block), so the match parts at round 13 and
ends in a victory instead of a defeat; the other 282 scenarios are unchanged. Tests:
`test/content/feedback5-duck-restealth.test.js` (3; the swap's 1 s and the band path fail before it, the 0 s guard
for the same enemy elsewhere passes on both).
+3 -1
View File
@@ -373,7 +373,9 @@ K.preparation_start_add_special_goods_every_n_round = (ps) => ({
// share taken over that half [ASSUMED: "你和队友遭遇的敌人" — the enemies heading for the player's own protection point;
// each official client simulates its own battle, docs/research/08 appendix A]. The originals never exist, so none of
// their death / kill / leak effects happen. Reaching the protection point costs 1 LP ("但进入保护目标点将减少1点目标生命值":
// the enemies' data lpr, tools/build-data.mjs).
// the enemies' data lpr, tools/build-data.mjs). The swaps carry the spawn tag 'duck': their 隐匿 (流泪小子) comes back no
// sooner than 1 s after a block ends, not the official 0 s (content/enemies/helpers.js DUCK_STEALTH_RESTORE — the owner's
// decision of 2026-10-07, a deliberate deviation).
export const DUCK_BAND = 'band_ducklord';
/** "击倒这些敌人者获得1资金奖励" (the blackboard's `count` is 1 too). */
+6 -4
View File
@@ -3,9 +3,9 @@
// unbalanced, artsBarrier, husk, statue, bleed, pollution, dmgZone, skill(), blinkForward … and the archetype kits
// reused by several keys (kitEp, kitTimes …).
import { MOVE_SCALE, COLS } from '../../constants.js';
import { MOVE_SCALE, COLS, STEALTH_RESTORE } from '../../constants.js';
import {
HUSK_REBIRTH, BOOM_RADIUS, POLLUTION_INTERVAL, STEALTH_RESTORE_BY_KEY, nthOf, stOf, safe, num, T, elem, hurt,
HUSK_REBIRTH, BOOM_RADIUS, POLLUTION_INTERVAL, STEALTH_RESTORE_BY_KEY, DUCK_STEALTH_RESTORE, nthOf, stOf, safe, num, T, elem, hurt,
areaAllies, areaAlliesInTiles, zone, spawnChildren, stepToward, setHits, hitCount, setForm, absorbArts, auraBuff,
} from './helpers.js';
@@ -14,11 +14,13 @@ import {
/**
* 隐匿: permanent stealth. Blocked it is lifted; it hides again STEALTH_RESTORE s after the block ends, or after its own
* "(解除阻挡N秒后恢复)" (STEALTH_RESTORE_BY_KEY → buff `data.stealthRestore`; Battle._stealthSwitch).
* "(解除阻挡N秒后恢复)" (STEALTH_RESTORE_BY_KEY → buff `data.stealthRestore`; Battle._stealthSwitch) — a 鸭爵 swap (tag
* 'duck') no sooner than DUCK_STEALTH_RESTORE s (the owner's decision of 2026-10-07, a deliberate deviation).
*/
const stealth = () => ({
spawn(b, e, a, ab) {
const n = STEALTH_RESTORE_BY_KEY[ab.key];
let n = STEALTH_RESTORE_BY_KEY[ab.key];
if (e.tag === 'duck' && (n ?? STEALTH_RESTORE) < DUCK_STEALTH_RESTORE) n = DUCK_STEALTH_RESTORE;
b.addBuff(e, { key: 'ab:stealth', flags: { stealth: true }, persist: true, data: n != null ? { stealthRestore: n } : {} });
},
});
+10 -1
View File
@@ -44,6 +44,15 @@ const STEALTH_RESTORE_BY_KEY = Object.freeze({
enemy_1283_sgkill_2: 1, // 家族暗影灭迹人
});
/**
* 隐匿 of the 鸭爵 strategy's swapped-in enemies (spawn tag 'duck': content/bands/meta.js duckReplace) comes back no sooner
* than this many seconds after a block ends — in practice 流泪小子 (enemy_2034_sythef_2, the only one of the four with 隐匿;
* officially 0 s, STEALTH_RESTORE_BY_KEY). A deliberate deviation from the official game: the owner's decision of
* 2026-10-07 「鸭爵策略产生的泪眼汪汪…要不等待个1秒这样再重新隐匿」 (community report of 2026-10-07, GitHub #214). The same
* enemy anywhere else — the 流泪小子 of a bounty card (enemy_2034_sythef), a test spawn — keeps the official time.
*/
const DUCK_STEALTH_RESTORE = 1;
/** Splash radius (中点判定 around the target) of 烹泉 / 沏虹's attack (PRTS 天赋 "普通攻击对目标对及目标周围半径1.0范围内的所有
* 我方单位造成法术普通伤害(无视迷彩,不可对空)") and of 集团军重型火炮's shell (PRTS "对目标半径1.0范围内的所有我方单位造成攻击力
* 100%的物理伤害(此弹道会强制击中主目标,碰撞无视迷彩,不可对空)"). */
@@ -387,6 +396,6 @@ const onTerrain = (b, u, terrain) => { const [r, c] = tileOf(u); return b.grid.t
// used by the other enemy modules (content/enemies.js and content/enemies/*.js)
export {
BOOM_RADIUS, POLLUTION_INTERVAL, STEALTH_RESTORE_BY_KEY, TEA_SPLASH_RADIUS, SHELL_SPLASH_RADIUS, stOf, safe, dispatch,
BOOM_RADIUS, POLLUTION_INTERVAL, STEALTH_RESTORE_BY_KEY, DUCK_STEALTH_RESTORE, TEA_SPLASH_RADIUS, SHELL_SPLASH_RADIUS, stOf, safe, dispatch,
num, auraBuff, watchDeaths, onTerrain,
};
@@ -0,0 +1,79 @@
// test/content/feedback5-duck-restealth.test.js — the owner's decision of 2026-10-07 (「鸭爵策略产生的泪眼汪汪我想了一下,要不
// 等待个1秒这样再重新隐匿」, after the community report of 2026-10-07 and GitHub #214): the 鸭爵 strategy's swapped-in
// 流泪小子 (spawn tag 'duck', content/bands/meta.js duckReplace) hides again 1 s after a block ends instead of the official
// 0 s (content/enemies/helpers.js DUCK_STEALTH_RESTORE) — a deliberate deviation, so the operators beside the one it
// stuns get a second to hit it. The same enemy anywhere else keeps the official 0 s.
import { test } from 'node:test';
import assert from 'node:assert/strict';
import { readFileSync } from 'node:fs';
import { makeBattle, chessRec, checkInvariants } from '../helpers/battleHarness.js';
import * as enemiesMod from '../../server/sim/content/enemies.js';
import { duckReplace } from '../../server/sim/content/bands/meta.js';
import { TICK } from '../../server/sim/constants.js';
import { enemyStealthed } from '../../server/sim/targeting.js';
const E = JSON.parse(readFileSync(new URL('../../data/enemies.json', import.meta.url), 'utf8'));
const BANDS = JSON.parse(readFileSync(new URL('../../data/bands.json', import.meta.url), 'utf8'));
const DOG = 'enemy_2034_sythef_2';
const BIG = [];
for (let dr = -4; dr <= 4; dr++) for (let dc = -12; dc <= 12; dc++) BIG.push([dr, dc]);
const WALL = chessRec({ id: 'w1', profession: 'TANK', stats: { atk: 0, maxHp: 1e7, def: 0, blockCnt: 1 }, rangeGrid: [[0, 0]], skill: null });
const GUN = chessRec({ id: 'gun', profession: 'SNIPER', projectile: 'none', stats: { atk: 400, maxHp: 1e7, bat: 1, blockCnt: 0 }, rangeGrid: BIG, skill: null });
function arena(o = {}) {
return makeBattle({
content: 'generic', extraContent: [enemiesMod], seed: 7, autoFinish: false, timeLimit: 600,
defs: { chess: { w1: WALL, gun: GUN } }, kits: { w1: () => ({ trait: { noAttack: true } }) }, ...o,
});
}
test('the 鸭爵 swap\'s 流泪小子 hides again 1 s after its strike frees it (its stunned blocker lets go), and a ranged operator hits it in that second', () => {
const h = arena({ units: [{ chessId: 'w1', row: 9, col: 6 }, { chessId: 'gun', row: 11, col: 3 }] });
h.step();
const e = h.spawn(DOG, { routeIndex: 0, tag: 'duck', bounty: { coins: 1, ownerPlayerId: 'p1' } });
assert.equal(e.findBuff('ab:stealth').data.stealthRestore, 1);
assert.ok(enemyStealthed(e), '隐匿 on its way');
assert.ok(h.runUntil(() => e.stats.attacks >= 1, 60), 'it strikes its blocker');
assert.equal(e.blockedBy, null, 'the stunned wall let it go');
const ts = h.b.time, hp0 = e.hp;
h.run(1 - 2 * TICK);
assert.equal(enemyStealthed(e), false, 'still revealed 1 s after (officially 隐匿 at once)');
assert.ok(e.hp < hp0, 'the sniper hit it in that second');
h.run(4 * TICK);
assert.equal(enemyStealthed(e), true, `隐匿 again ${(h.b.time - ts).toFixed(3)} s after the strike`);
checkInvariants(h.b);
});
test('the same enemy outside the swap keeps the official 0 s: the act2 copy spawned without the tag, and the original 流泪小子 of the bounty card', () => {
const h = arena({ units: [{ chessId: 'w1', row: 9, col: 6 }] });
h.step();
for (const key of [DOG, 'enemy_2034_sythef']) {
const e = h.spawn(key, { pos: [10, 8], routeIndex: 0, mods: { speedMul: 0 } });
assert.equal(e.findBuff('ab:stealth').data.stealthRestore, 0, key);
}
const e = h.spawn(DOG, { routeIndex: 0 });
assert.ok(h.runUntil(() => e.stats.attacks >= 1, 60));
assert.equal(enemyStealthed(e), true, 'untagged: 隐匿 the moment its strike frees it');
// a 3 s 隐匿 stays 3 s even when tagged (the rule only lengthens a shorter restore)
const lurker = h.spawn('enemy_1009_lurker', { pos: [10, 7], routeIndex: 0, tag: 'duck', mods: { speedMul: 0 } });
assert.equal(lurker.findBuff('ab:stealth').data.stealthRestore, undefined);
});
test('the band\'s own swap (duckReplace) tags the copy, and the battle it builds gives that copy the 1 s', () => {
const p = BANDS.band_ducklord.params;
assert.ok(p.enemylist.split(',').includes(DOG));
const spawns = [{ time: 2, enemyKey: 'enemy_1007_slime', routeIndex: 0, count: 4, interval: 3 }];
const ctx = { gd: { enemy: (k) => E[k] }, rng: { int: () => 1, shuffle: (a) => a.slice(), pick: (a) => (a.includes(DOG) ? DOG : a[0]) } };
const out = duckReplace(ctx, spawns, p, 'p1');
assert.equal(out.length, 1);
assert.equal(out[0].enemyKey, DOG);
assert.equal(out[0].tag, 'duck');
const h = arena({
enemies: spawns.map((s) => ({ key: s.enemyKey, time: s.time, route: s.routeIndex, count: s.count ?? 1, interval: s.interval ?? 0, tag: s.tag ?? null, bounty: s.bounty ?? null })),
});
assert.ok(h.runUntil(() => h.b.enemies.some((x) => x.defId === DOG), 30), 'the copy spawns');
const dog = h.b.enemies.find((x) => x.defId === DOG);
assert.equal(dog.tag, 'duck');
assert.equal(dog.findBuff('ab:stealth').data.stealthRestore, 1);
});
+9 -9
View File
@@ -318,7 +318,7 @@
"combat": "client",
"boost": null,
"setup": {"stageId":"act1autochess_m03","bossId":"boss_5","hiddenBossId":"boss_10","factions":["ELEMENT","DOT","REFLECTION"]},
"end": {"victory":false,"roundsPassed":13,"hiddenReached":false,"hiddenCleared":false,"reason":"defeat","teamLp":0,"bossPoolLeft":34046,"virtualMs":107800},
"end": {"victory":true,"roundsPassed":14,"hiddenReached":false,"hiddenCleared":false,"reason":"victory","teamLp":17,"bossPoolLeft":0,"virtualMs":107800},
"rng": {"setup":25,"shop":329,"waves":0,"draft":42,"bots":8690,"meta":113},
"errors": {"engine":0,"logged":0,"sim":0,"dispatcher":0},
"rounds": [
@@ -363,23 +363,23 @@
},
{
"round": 13,
"ai_0": [1,18,13,0,2,6,8,"d04adca2",172,"arcaneShip 0/24, kazimierzShip 0/2, kjeragShip 0/8, lateranoShip 0/3, miraShip 1/3, preciShip 1/79, sargonShip 0/85, siracusaShip 2/90, steadShip 0/8, swiftShip 0/5",28,29,1,135]
"ai_0": [1,17,13,0,2,6,8,"d04adca2",182,"arcaneShip 0/24, kazimierzShip 0/2, kjeragShip 0/8, lateranoShip 0/3, miraShip 1/1, preciShip 1/95, sargonShip 0/85, siracusaShip 2/86, steadShip 0/8, swiftShip 0/5",27,29,2,135]
}
],
"players": {
"ai_0": {
"alive": true,
"victory": false,
"roundsPassed": 13,
"victory": true,
"roundsPassed": 14,
"eliminatedRound": null,
"lp": 0,
"lp": 17,
"bandId": "band_ducklord",
"title": {"id":"comment_2","name":"不朽盟约","picId":"comment_icon_2","text":"激活的盟约层数总和最高"},
"title": {"id":"comment_1","name":"卫戍之星","picId":"comment_icon_1","text":"对敌方领袖造成伤害最高(仅胜利时)"},
"trophies": 0,
"reward": 340,
"stats": {"activatedLayers":233,"bossDamage":745954,"dmgDealt":3104391,"gold":149,"itemsEquipped":21,"kills":298,"leaks":11,"lpLost":11,"merges":4,"perfectRounds":10,"refreshes":33},
"reward": 425,
"stats": {"activatedLayers":245,"bossDamage":780000,"dmgDealt":3137836,"gold":149,"itemsEquipped":21,"kills":297,"leaks":12,"lpLost":12,"merges":4,"perfectRounds":10,"refreshes":33},
"lineup": ["5_20_a@12,5[1_05_e_b+3_08_e_b]","5_02_a@12,6[2_04_e_a]","6_01_a@12,7","6_12_a@11,8","4_10_b@10,3[3_07_e_a+3_11_e_a]","5_07_a@10,7[4_05_e_a+4_10_e_b]","4_16_a@10,8[4_02_e_a]","5_10_a@9,8"],
"bonds": ["arcaneShip 1/36","emptyShip 1/0","kazimierzShip 0/2","kjeragShip 0/8","lateranoShip 0/3","miraShip 0/3","preciShip 2/79","raidShip 1/0","sargonShip 0/85","siracusaShip 1/118","steadShip 0/8","swiftShip 0/5"]
"bonds": ["arcaneShip 1/36","emptyShip 1/0","kazimierzShip 0/2","kjeragShip 0/8","lateranoShip 0/3","miraShip 0/1","preciShip 2/95","raidShip 1/0","sargonShip 0/85","siracusaShip 1/114","steadShip 0/8","swiftShip 0/5"]
}
}
},