feedback4: merge master — PR #73 (operator battle voices); golden regenerated: every moved roster / bonds / fields scenario differs only in events.engage (the sim's new 'engage' event for the voice slot; no gameplay change), matches unchanged
This commit is contained in:
commit
beb0296a52
25 files changed
+940
-166
No files matched your search
@@ -19,8 +19,9 @@
|
||||
// feedback after 0.1.0 (DESIGN §21, v0.1.1) including batch 6 (§21.21–§21.25: 坚固维式重锤 once per deployment, 起飞,
|
||||
// fenced tiles, knocked-out bodies, the dispatcher snapshot and the manifest shrink guard credited to PR #2 / PR #7) and
|
||||
// the 突变细胞 bench rule (§21.1: the carrier destroyed, its new operator gained into the 整备区 — official footage, PR #2),
|
||||
// the closing additions §21.26–§21.28 (GitHub issues #1 / #5 / #8) and the owner's deliberate trigger deviation for six
|
||||
// 重装 skills (§21.29, GitHub issue #4 / PR #12).
|
||||
// the closing additions §21.26–§21.28 (GitHub issues #1 / #5 / #8), the owner's deliberate trigger deviation for six
|
||||
// 重装 skills (§21.29, GitHub issue #4 / PR #12) and the operator battle voice the user asked for the same day (§21.30,
|
||||
// battle only — the 休整期 is silent).
|
||||
import { test } from 'node:test';
|
||||
import assert from 'node:assert/strict';
|
||||
import { readFileSync } from 'node:fs';
|
||||
@@ -845,3 +846,57 @@ test('the deliberate trigger deviation (DESIGN §21.29): six 重装 skills DEFAU
|
||||
assert.match(PLAYING, /深巡、雷蛇的二技能,号角的二、三技能,灰毫的一、二技能按玩家反馈改为攻击范围内有敌人时就释放/);
|
||||
assert.match(doc('CHANGELOG.md'), /深巡、雷蛇的二技能,号角的二、三技能,灰毫的一、二技能改为攻击范围内有敌人时就释放/);
|
||||
});
|
||||
|
||||
test('干员战斗语音 (DESIGN §21.30): the manifest data, the official priorities, and the 休整期 stays silent', async () => {
|
||||
const { VOICE_PRIORITY, VOICE_COOLDOWN_MS, resultVoiceSlot } = await import('../public/js/audio.js');
|
||||
const manifest = JSON.parse(readFileSync(join(ROOT, 'data/assets.json'), 'utf8'));
|
||||
const voice = manifest.audio?.voice ?? {};
|
||||
const charIds = Object.keys(voice);
|
||||
assert.ok(charIds.length >= 100, `${charIds.length} operators carry official battle voice`);
|
||||
assert.equal(manifest.stats.voiceChars, charIds.length, 'stats.voiceChars counts them');
|
||||
// one operator carries every slot a battle can play — and none of the prep-only ones (plan.mjs VOICE_BATTLE_SLOTS):
|
||||
// 干员报到 / 编入队伍 / 任命队长 are never requested by the client, so planning them only made every `npm run assets`
|
||||
// download 360 files (19.3 MB, one per operator and slot) nobody hears. `--voice-all` brings the complete set back.
|
||||
const slots = ['start', 'faceEnemy', 'select', 'place', 'skill1', 'skill2', 'skill3', 'skill4', 'resultFour', 'resultThree', 'resultTwo', 'resultLose'];
|
||||
const prepOnly = ['gacha', 'squad', 'squadFirst'];
|
||||
const lines = [];
|
||||
const walk = (x) => {
|
||||
if (typeof x === 'string') lines.push(x);
|
||||
else if (Array.isArray(x)) x.forEach(walk);
|
||||
else if (x && typeof x === 'object') Object.values(x).forEach(walk);
|
||||
};
|
||||
for (const id of charIds) {
|
||||
for (const s of slots) assert.ok(voice[id][s], `${id}.${s}`);
|
||||
for (const s of prepOnly) assert.equal(voice[id][s], undefined, `${id}.${s} is not planned by default`);
|
||||
walk(voice[id]);
|
||||
}
|
||||
// a battle slot usually carries several lines (选中干员 / 部署 have two), so the battle set alone stays well above 10 each
|
||||
assert.ok(lines.length >= charIds.length * 10, `${lines.length} voice lines for ${charIds.length} operators`);
|
||||
for (const u of lines) assert.match(u, /^\/assets\/audio\/voice\/cn\/char_[^/]+\/cn_\d+\.mp3$/);
|
||||
// the official scheduling numbers (audio_data.json battleVoice.voiceTypeOptions)
|
||||
assert.equal(VOICE_PRIORITY.start, 100);
|
||||
assert.equal(VOICE_PRIORITY.faceEnemy, 90);
|
||||
assert.equal(VOICE_PRIORITY.skill1, 70);
|
||||
assert.equal(VOICE_PRIORITY.place, 20);
|
||||
assert.equal(VOICE_PRIORITY.select, 10);
|
||||
assert.equal(VOICE_COOLDOWN_MS.skill1, 10000);
|
||||
assert.equal(VOICE_COOLDOWN_MS.faceEnemy, 3000);
|
||||
assert.equal(VOICE_COOLDOWN_MS.select, 1500);
|
||||
assert.equal(resultVoiceSlot({ perfect: true }), 'resultThree');
|
||||
// the docs
|
||||
assert.match(DESIGN, /### 21\.30 /);
|
||||
assert.match(DESIGN, /\*\*Where each line plays — battle only\.\*\*/);
|
||||
assert.match(doc('docs/ASSETS.md'), /\| 干员战斗语音 \|/);
|
||||
assert.match(doc('docs/ASSETS.md'), /voiceChars/);
|
||||
assert.match(SIM, /\['engage', id\]/);
|
||||
// the code: every slot the client asks for comes from a running battle's own stream — the three prep-only lines
|
||||
// (干员报到 / 编入队伍 / 任命队长) are never requested, and 选中干员 sits behind the panel's combat flag
|
||||
const panel = doc('public/js/ui/detailPanel.js');
|
||||
const game = doc('public/js/screens/game.js');
|
||||
for (const [name, src] of [['audio.js', doc('public/js/audio.js')], ['game.js', game], ['detailPanel.js', panel]]) {
|
||||
assert.ok(!/voice\([^)]*'(gacha|squad|squadFirst)'/.test(src), `${name}: no prep slot is played`);
|
||||
}
|
||||
assert.match(panel, /const selectKey = voice && detail\?\.type === 'chess'/);
|
||||
assert.match(game, /voice=\$\{combat\}/);
|
||||
assert.match(game, /audio\.voice\(charId, resultVoiceSlot\(/);
|
||||
});
|
||||
Reference in new issue
Block a user