feedback5 CB4: three community reports already answered, pinned — the briefing types fill 7-9 of the 13 normal waves, at most 3 each (item 6, the official schedule; now stated in docs/PLAYING.md); 新硎's 4 断刃 since GitHub #107 (item 53); a freed prisoner re-enters 联防 confined (item 57)

No product change. Item 6: 400 seeds per mode, 险境/绝境/终极 7 in 141, 8 in 192, 9 in 67, per type 1-3 (R14/R15 take
slots of the 15). Item 53: v0.1.4 stacked a layer per attack (2430 -> 5670), this tree spends one blade per attack and
ends the bonus with the 4th (client template enemy_dead_spawn[sfji]). Item 57: a leak re-enters as a new spawn (key,
mods, LP, bounty only), confined.
Golden unchanged. Tests: test/sim/feedback5-cb4-pins.test.js (2). Docs: DESIGN §25.18.3-§25.18.5, docs/PLAYING.md.
This commit is contained in:
sganggs committed 2026-10-07 01:51:18 +08:00
1 parent 7fb6a1c8b4
commit 686f7ecf37
3 files changed
+114 -2

No files matched your search

+1 -1
View File
@@ -16,7 +16,7 @@
| 阶段 | 说明 |
|---|---|
| 确认本局信息 | 本局地图(标准模拟固定为「战场#01」;险境从 8 张地图中随机,绝境 / 终极从不含战场#01 的 7 张中随机)、敌方派系、**被禁用的盟约及其干员**、领袖预告。所有人确认后继续;同盟模拟最多 25 秒;只有你一名玩家时(独立模拟,或同盟模拟只带 AI 队友)不限时。 |
| 确认本局信息 | 本局地图(标准模拟固定为「战场#01」;险境从 8 张地图中随机,绝境 / 终极从不含战场#01 的 7 张中随机)、敌方派系(三种特训敌人类型,如折射、飞行:和官方一样,每种占全局 15 个回合位中的 3 个,第 14 回合领袖战和第 15 回合隐秘核心也各占一个位,所以险境 / 绝境 / 终极第 1–13 回合中有 7–9 波是这三种类型、每种最多 3 波,其余各波是「特异」类型;每波只有一种类型)、**被禁用的盟约及其干员**、领袖预告。所有人确认后继续;同盟模拟最多 25 秒;只有你一名玩家时(独立模拟,或同盟模拟只带 AI 队友)不限时。 |
| 选择策略 | 40 名策略(分队长)决定**初始目标生命值(20–45)**和一条永久规则。同盟:随机顺序轮流选,**每人 30 秒**,右上角和「决策中」显示同一个倒计时;AI 队友立即选完;队友已选的不能重复,每人可**跳过一次**(排到最后);超时自动选你**当前选中**的策略(已被队友选走则选华法琳)。独立:自由选择、不限时;同盟模拟只有你一名玩家时也不限时。选的时候可以点左侧决策顺序下方的「查看禁用盟约与干员」,再看一遍「确认本局信息」里的盟约(置灰、禁用人数)和本局禁用干员:只能查看,倒计时照常走,底部也显示轮到谁和剩余秒数;点「关闭」、点窗口外面或按 Esc 关闭,轮到下一位时会自动关闭。 |
| 机变 | 部分回合开始前出现:同盟共 6 张卡轮流各取 1 张(首位 30 秒,其余 16 秒;只有你一名玩家时不限时),独立 3 选 1。卡片包括装备、资金、干员、盟约层数、悬赏等;**悬赏决策**为你的作战添加悬赏敌人,卡上用蓝字标出持续多久:「下场作战」/「接下来两场作战」的只出现一场或两场,之后不再出现;官方的「多轮悬赏」(原文写「之后的每场作战」)在这里也按「接下来两场作战」处理,只出现两场(如教鞭的法术大师A2·多轮战术特训)。各回合的悬赏决策按官方对局截图来出:第 3 回合是 6 张「接下来两场作战」的悬赏(3 张 1 资金、2 张 2 资金、1 张 3 资金);第 9 回合是各首领的「下场作战」悬赏和「源石虫·特训」;绝境 / 终极的第 11 回合多半是悬赏决策(也可能是机密商店或战术决策),其中的悬赏通常有 1 只特异巨兽,其余是各阵营的「下场战斗」悬赏。击倒悬赏敌人的人(联防中的队友也算)获得资金;右侧效果栏会写「还剩 N 场作战」。**机密商店**的 6 件装备都免费,和官方一样可能出现两件相同的装备(各算一张卡,可以分别被选走);第 11 回合(绝境 / 终极)的机密商店按官方:2 件 VI 阶、至少 1 件 V 阶和 1 枚盟约之币,标准 / 险境较早回合的机密商店各阶装备都可能出现。**战术决策**也和官方一样可能出现两张相同的卡(官方截图里出现过两张「补给」;各算一张卡,可以分别被选走,每张只生效一次,两人各选一张全队卡则效果叠加);第 11 回合(绝境 / 终极)的战术决策按官方截图只出盟誓、驰援、列装 / 财富 / 补给 / 整备 / 升华等增益卡,不出削弱敌人的卡和战场演变卡。每张卡左上是图标和名称,下面写着完整效果(与详情卡相同的官方描述,手机上也显示);已被选走的卡变暗,右上角显示选走它的玩家头像(悬停可看名字)。选卡要**点两次**:第一次只选中(金框 +「确认选择 · 再次点击」),再点同一张或点右上的「确认选择」才生效;点别的卡换选,按 Esc 或点空白处取消。 |
| 休整期 | 购买、摆阵、配装备、冻结 / 刷新、升级调度中心;同盟有倒计时(65–215 秒,随回合增加);独立模拟、以及只有你一名玩家(只带 AI 队友)的同盟模拟不限时。所有人「准备就绪」即提前开战,全员就绪前可取消。 |
+37 -1
View File
@@ -27,7 +27,8 @@ a merged elite 拉普兰德 as a new one (GitHub #169), the leader pool per play
model quirks (PR #211) → §25.13; the community reports of 2026-10-06 (the 孤岛风云 prisoners drawn freed, 忍冬's trigger, a
bounty burned to death by the 活性源石, 枯朽之种 paying a bounty) → §25.14; the second batch of community reports of
2026-10-06, stream CB4 (enemies, bosses, waves, movement: the 突袭 landing on the 临时整备区 row and the other half of the
联防 map, a silenced 萨卡兹枯朽战士's death pollution, …) → §25.18.
联防 map, a silenced 萨卡兹枯朽战士's death pollution; the briefing types' 7–9 waves, 新硎's 断刃 and a freed prisoner in 联防
already answered, …) → §25.18.
Decisions of the owner: 2026-10-04 — the server stays on plain `node:http` + `ws`, no web framework (§25.1);
2026-10-05 — the 补位 plan (§25.3); the 自选编队 rules, the collab operators out of the pool, ISW-A refused (§25.4);
@@ -1115,3 +1116,38 @@ The real ones are fixed, one commit each with its golden scenarios named; the re
`test/content/enemies_bosses.test.js` (the silence rule, the two keys silenced and after a silence ran out; 3 fail on
the parent).
#### 25.18.3 「每局游戏开始展示的敌人类型(折射、飞行等等),实际出现波次在7到9波,而且每个最多三波,其余波次用特异类型填充」 (item 6): already the official schedule — no change
- **Checked**: `setupMatchWaves` (research 08 §2.1, decoded from the client's `RandomEnemyGenerater`): each of the three
briefing types owns 3 of the 15 round slots, SPECIAL (特异) the other 6, shuffled, one type per round. R14 (the leader)
and R15 (the hidden core) take slots too, so the 13 normal rounds of 险境 / 绝境 / 终极 hold 7–9 type waves — 400 seeds
per mode: 7 in 141 matches, 8 in 192, 9 in 67; per type 1–3 — and every other wave is SPECIAL (solo 标准: 8 normal
rounds, 3–7). Exactly what the player describes.
- **Official**: act2autochess `specialEnemyRandomTypeDict` (count 3 per random type, SPECIAL −1), constData
`maxLevelCnt` 15, `specialEnemyNum` 3, `enemyTypeIdentifierToFillRandom` 1 (SPECIAL).
- **Now**: docs/PLAYING.md states it at 确认本局信息 (the report: 「更新日志和机制描述也没写」).
- Golden: unchanged. Pinned: `test/sim/feedback5-cb4-pins.test.js` (the 7–9 and the 1–3), `test/match/waves.test.js` (3 of
the 15 slots each).
#### 25.18.4 「敌人新硎提升攻击力的断刃好像无限消耗,实际应当攻击一次消耗一个,具体有几个复核」 (item 53): already fixed in 0.2.0 (§25.6.2, GitHub #107) — no change
- **Checked**: on v0.1.4 新硎's ATK went 2430 → 3510 → 4590 → 5670 and stayed (a layer per attack, none removed) — the
report. On this tree: 4 attacks at 2430 (+80 %), the 4th spends the last 断刃 and ends the bonus, the next swing comes at
once at 1350; killed, max(1, 4 − spent) 铁矛头 (沉沙: +70 %, 铜矛头).
- **Official**: PRTS 天赋 (4 【断刃】, one spent per successful attack); the client's template `enemy_dead_spawn[sfji]`
(battle prefab enemy_1207_sfji_2): ON_OUTPUT_ATK_OR_HEAL — while `cnt` ≥ 1, `cnt` += `cnt_add` (−1) and one blade marker
goes; ON_ABILITY_FINISH — `cnt` ≤ 0 ends the ATK buff and switches to mode 1 (无断刃模式); ON_OWNER_KILLED (not 坠落) —
summons `cnt` (at least 1).
- [ASSUMED]: the client spends a blade per damage output; here every attack spends one, so an attack an operator dodges
spends one too.
- Golden: unchanged. Pinned: `test/content/enemies_bosses.test.js` (the #107 test).
#### 25.18.5 「囚犯敌人的解放状态联防时不应继承」 (item 57): already answered (§25.14.1) — no change
- **Checked**: a leak re-enters 联防 as a new spawn — `planUnite` passes on its key, spawn mods, LP and bounty only, and
`buildUniteWave` spawns it — so `prisoner()` starts it confined (the 禁锢 buff, no form) and only its own attacks there
free it; the client drops every view when a battle starts. The freed look was 普通 / 老练囚犯's red clip set, drawn from
the gate until §25.14.1.
- Golden: unchanged. Pinned: `test/sim/feedback5-cb4-pins.test.js` (freed in its own combat, leaked, confined in 联防,
freed at its 4th attack there).
+76
View File
@@ -0,0 +1,76 @@
// test/sim/feedback5-cb4-pins.test.js — community reports of 2026-10-06 that this tree already answers, pinned (DESIGN
// §25.18; they pass on the parent commit — nothing changed for them):
// item 6 「每局游戏开始展示的敌人类型(折射、飞行等等),实际出现波次在7到9波,而且每个最多三波,其余波次用特异类型填充」 — the
// official type schedule (client RandomEnemyGenerater, research 08 §2.1; act2autochess specialEnemyRandomTypeDict count 3,
// constData maxLevelCnt 15 / specialEnemyNum 3 / enemyTypeIdentifierToFillRandom 1): each briefing type owns 3 of the
// 15 round slots, SPECIAL (特异) the other 6, shuffled — R14 (leader) and R15 (hidden core) take slots too, so the 13
// normal rounds hold 7–9 type waves, at most 3 per type;
// item 57 「囚犯敌人的解放状态联防时不应继承」 — a leaked prisoner re-enters 联防 as a new spawn (unite.js planUnite →
// waves.js buildUniteWave: its key and spawn mods only), confined again (archetypes.js prisoner spawn); the freed look
// the report saw was 普通 / 老练囚犯's red clip set drawn from the gate, fixed with the prisoners' forms (§25.14.1).
// Run: node --test test/sim/feedback5-cb4-pins.test.js
import { test } from 'node:test';
import assert from 'node:assert/strict';
import { makeBattle, chessRec, checkInvariants } from '../helpers/battleHarness.js';
import { GameData } from '../../server/match/gamedata.js';
import { setupMatchWaves, buildUniteWave } from '../../server/match/waves.js';
import { planUnite } from '../../server/match/unite.js';
import { createRng } from '../../server/sim/rng.js';
import { DATA } from '../match/harness.js';
test('item 6: the three briefing types fill 7–9 of the 13 normal waves (险境 / 绝境 / 终极), at most 3 each; the rest are SPECIAL', () => {
for (const modeId of ['mode_multi_normal', 'mode_single_hard', 'mode_multi_abyss']) {
const gd = new GameData(DATA, modeId);
assert.equal(gd.bossRound, 14);
const totals = new Set();
for (let seed = 1; seed <= 200; seed++) {
const s = setupMatchWaves(gd, createRng(seed));
const normal = s.typeSlots.slice(0, gd.bossRound - 1);
const per = s.factions.map((f) => normal.filter((t) => t === f).length);
for (const n of per) assert.ok(n >= 1 && n <= 3, `${modeId} seed ${seed}: ${per}`);
const total = per.reduce((a, b) => a + b, 0);
assert.ok(total >= 7 && total <= 9, `${modeId} seed ${seed}: ${total}`);
assert.equal(normal.filter((t) => t === 'SPECIAL').length, normal.length - total, 'every other wave is SPECIAL');
totals.add(total);
}
assert.deepEqual([...totals].sort(), [7, 8, 9], `${modeId}: all three totals occur`);
}
});
test('item 57: a prisoner freed in its own combat leaks and re-enters 联防 confined (no freed form, the confinement buff, freed only by its own attacks there)', () => {
const KEY = 'enemy_1116_liprr'; // 普通囚犯
const wall = chessRec({ id: 't_wall', profession: 'TANK', stats: { atk: 0, maxHp: 1e9, def: 0, blockCnt: 3 }, rangeGrid: [[0, 0]], skill: null });
const kits = { t_wall: () => ({ trait: { noAttack: true } }) };
const h = makeBattle({ autoFinish: false, timeLimit: 120, defs: { chess: { t_wall: wall } }, kits, units: [{ chessId: 't_wall', row: 9, col: 6 }], enemies: [{ key: KEY, route: 0 }] });
assert.ok(h.runUntil(() => h.enemy(KEY) && h.enemy(KEY).form === 'liberty', 60), 'freed by its own attacks');
const p = h.enemy(KEY);
assert.ok(p.findBuff('ab:liberty') && !p.findBuff('ab:confined'));
h.b.retreat(h.unit('t_wall'));
assert.ok(h.runUntil(() => !p.alive, 60), 'it walks on and leaks');
const r = h.result().perPlayer.p1;
assert.deepEqual(r.leaked.filter((l) => l.counted !== false).map((l) => l.enemyKey), [KEY]);
checkInvariants(h.b);
// 联防: the leaker's counted leak re-enters on the escaped template of one helper
const gd = new GameData(DATA, 'mode_multi_normal');
const seat = (id, s) => ({ playerId: id, seat: s, deployCount: 1, bonds: {}, layers: {}, board: new Map(), bounties: [] });
const m = { isSolo: false, alivePlayers: () => [seat('p1', 1), seat('H', 0)], gd: { unite: { maxHelpers: 2 }, enemy: (k) => gd.enemy(k) } };
const plan = planUnite(m, new Map([['p1', r], ['H', { perfect: true, leaked: [], unitsEnd: [] }]]));
assert.deepEqual(plan.leaked.map((l) => l.enemyKey), [KEY]);
assert.deepEqual(Object.keys(plan.leaked[0]).sort(), ['bounty', 'enemyKey', 'lpr', 'mods', 'sourcePlayerId', 'tag'], 'nothing of its state travels');
const wave = buildUniteWave(gd, plan.leaked, 1, 60);
const u = makeBattle({
kind: 'unite', stageId: 'act1autochess_escaped_single', autoFinish: false, timeLimit: 60, routes: wave.routes,
defs: { chess: { t_wall: wall } }, kits, units: [{ chessId: 't_wall', row: 9, col: 8 }],
enemies: wave.spawns.map((s) => ({ key: s.enemyKey, time: s.time, route: s.routeIndex, mods: s.mods, sourcePlayerId: s.sourcePlayerId })),
});
assert.ok(u.runUntil(() => u.enemy(KEY), 30), 're-entered');
const q = u.enemy(KEY);
assert.equal(q.form ?? null, null, 'drawn confined');
assert.ok(q.findBuff('ab:confined') && !q.findBuff('ab:liberty'), 'confined again');
assert.ok(u.runUntil(() => q.stats.attacks >= 1, 40), 'blocked, it attacks');
assert.equal(q.form ?? null, null, 'still confined after its first 联防 attack');
assert.ok(u.runUntil(() => q.form === 'liberty', 60), 'freed again only by its own attacks there');
assert.equal(q.stats.attacks, 4, 'at its 4th attack in 联防 (confinement.times), as in a fresh spawn');
checkInvariants(u.b);
});