feedback5 CB3: 银灰's normal attack no longer plays his S3 swing (and S2 / S3 impact); 雷蛇, 初雪 and 圣约送葬人 lose a skill mode's impact the same way (community report of 2026-10-06, item 54)
Official (excel/audio_data.json, local client charpack char_172_svrash): his normal attack is the Default mode's Combat — ON_ABILITY_HIT.char_172_svrash.combat p_imp_spear_n, no swing bank; .attack.2 (p_atk_silver_n swing, p_imp_sword_n impact) is his S3 mode, .attack.1 his S2 mode. Cause: tools/assets/audio.mjs pickUnitSfx took the numbered variants because their files end in _n (its skill-mode test is by file name). Now an operator whose default mode is the unsuffixed ability (a plain attack / combat bank of any event) never takes a numbered variant; operators that number their default mode (attack.0 …) keep the earlier order. Over the 209 operators of the manifest it changes 4: 银灰 (charpack modes Default / S2 / S3), 雷蛇 (attack.1 = S2, lightning impact), 初雪 (attack.1 = her S1-2 mode) and 圣约送葬人 (attack.4 = S3_End). data/assets.json regenerated offline (fetch-assets --offline --add-only --allow-shrink; JSON compare: exactly the 4 operators' entries, hash and stats): 雷蛇 / 初雪 / 圣约送葬人 drop their wrong `hit`; 银灰's entry goes too, because his official impact p_imp_spear_n.mp3 was never downloaded on this machine — one online `node tools/fetch-assets.mjs --add-only` adds it (needs the owner's OK for that download). Golden: unchanged (the manifest is client-side). Tests: test/feedback5-operator-sfx.test.js (3; 2 fail on the parent). Docs: docs/ASSETS.md.
This commit is contained in:
1 parent
3d472e2304
commit
fe2a0cbc29
4 files changed
+64
-2
No files matched your search
+1
-1
@@ -1 +1 @@
|
||||
{"version":1,"hash":"65d6d7dc722e","generator":"tools/fetch-assets.mjs","stats":{"files":7972,"bytes":485093753,"chars":209,"charsWithBack":206,"enemies":252,"enemiesWithSpine":251,"tokens":58,"tokensWithSpine":13,"spineModels":671,"bonds":23,"items":59,"bands":40,"skills":522,"modules":131,"ui":602,"sfxUnits":483,"voiceChars":191},"chars":{"char_003_kalts":{"avatar":"/assets/char/avatar/char_003_kalts.png","avatarE2":"/assets/char/avatar/char_003_kalts_2.png","portrait":"/assets/char/portrait/char_003_kalts_1.png","portraitE2":"/assets/char/portrait/char_003_kalts_2.png","spine":{"front":{"skel":"/assets/spine/op/char_003_kalts/front/char_003_kalts.skel","atlas":"/assets/spine/op/char_003_kalts/front/char_003_kalts.atlas","textures":["/assets/spine/op/char_003_kalts/front/char_003_kalts.png"],"pma":false,"anims":{"idle":"Idle","deploy":"Start","attack":{"begin":null,"loop":"Attack","end":null},"attackDown":null,"skill":{"begin":null,"loop":"Attack","end":null,"via":"attack","index":0,"idle":null},"die":"Die","move":null,"stun":null,"skills":{"0":{"begin":null,"loop":"Attack","end":null,"via":"attack","index":0,"idle":null},"1":{"begin":null,"loop":"Attack","end":null,"via":"attack","index":1,"idle":null},"2":{"begin":null,"loop":"Attack","end":null,"via":"attack","index":2,"idle":null}}},"animations":{"Attack":1,"Default":0,"Die":1,"Idle":9,"Start":1},"events":["OnAttack","OnStart"],"hits":{"Attack":[0.433]},"bounds":{"x":-227.579,"y":-138.244,"width":532.04,"height":551.479}},"back":{"skel":"/assets/spine/op/char_003_kalts/back/char_003_kalts.skel","atlas":"/assets/spine/op/char_003_kalts/back/char_003_kalts.atlas","textures":["/assets/spine/op/char_003_kalts/back/char_003_kalts.png"],"pma":false,"anims":{"idle":"Idle","deploy":"Start","attack":{"begin":null,"loop":"Attack","end":null},"attackDown":null,"skill":{"begin":null,"loop":"Attack","end":null,"via":"attack","index":0,"idle":null},"die":null,"move":null,"stun":null,"skills":{"0":{"begin":null,"loop":"Attack","end":null,"via":"attack","index":0,"idle":null},"1":{"begin":null,"loop":"Attack","end":null,"via":"attack","index":1,"idle":null},"2":{"begin":null,"loop":"Attack","end":null,"via":"attack","index":2,"idle":null}}},"animations":{"Attack":1,"Default":0,"Idle":9,"Start":1},"events":["OnAttack","OnStart"],"hits":{"Attack":[0.433]},"bounds":{"x":410.785,"y":-2.26,"width":531.785,"height":416.492}}}},"char_010_chen":{"avatar":"/assets/char/avatar/char_010_chen.png","avatarE2":"/assets/char/avatar/char_010_chen_2.png","portrait":"/assets/char/portrait/char_010_chen_1.png","portraitE2":"/assets/char/portrait/char_010_chen_2.png","spine":{"front":{"skel":"/assets/spine/op/char_010_chen/front/char_010_chen.skel","atlas":"/assets/spine/op/char_010_chen/front/char_010_chen.atlas","textures":["/assets/spine/op/char_010_chen/front/char_010_chen.png"],"pma":false,"anims":{"idle":"Idle","deploy":"Start","attack":{"begin":null,"loop":"Attack","end":null},"attackDown":null,"skill":{"begin":null,"loop":"Skill","end":"Skill_End","index":0,"idle":null},"die":"Die","move":null,"stun":null,"skills":{"0":{"begin":null,"loop":"Skill","end":"Skill_End","index":0,"idle":null},"1":{"begin":null,"loop":"Skill_2","end":null,"index":1,"idle":null},"2":{"begin":null,"loop":"Skill_3","end":null,"index":2,"idle":null}}},"animations":{"Attack":1.5,"Attack_End":0.6,"Attack_Pre":0.6,"Default":0,"Die":1,"Idle":2,"Skill":1,"Skill_2":1.167,"Skill_3":2.933,"Skill_End":0.6,"Skill_End_2":0.6,"Skill_End_3":0.3,"Start":0.833},"events":["OnAttack","OnStart"],"hits":{"Attack":[0.433,1],"Skill":[0.467],"Skill_2":[0.467],"Skill_3":[0.6,0.8,1,1.2,1.4,1.6,1.8,2,2.2,2.833,2.933]},"bounds":{"x":-322.335,"y":-109.274,"width":663.549,"height":524.806}},"back":{"skel":"/assets/spine/op/char_010_chen/back/char_010_chen.skel","atlas":"/assets/spine/op/char_010_chen/back/char_010_chen.atlas","textures":["/assets/spine/op/char_010_chen/back/char_010_chen.png"],"pma":false,"anims":{"idle":"Idle","deploy":"Start","attack":{"begin":null,"loop":"Attack","end":null},"attackDown":null,"skill":{"begin":null,"loop":"Skill","end":"Skill_End","index":0,"idle":null},"die":null,"move":null,"stun":null,"skills":{"0":{"begin":null,"loop":"Skill","end":"Skill_End","index":0,"idle":null},"1":{"begin":null,"loop":"Skill","end":"Skill_End","index":1,"idle":null},"2":{"begin":null,"loop":"Skill","end":"Skill_End","index":2,"idle":null}}},"animations":{"Attack":1.5,"Attack_End":0.6,"Attack_Pre":0.6,"Default":0,"Idle":2,"Skill":1,"Skill_End":0.6,"Start":0.833},"events":["OnAttack","OnStart"],"hits":{"Attack":[0.433,1],"Skill":[0.467]},"bounds":{"x":-173.082,"y":-13.829,"width":315.887,"height":404.623}}}},"char_017_huang":{"avatar":"/assets/char/avatar/char_017_huang.png","avatarE2":"/assets/char/avatar/char_017_huang_2.png","portrait":"/assets/char/portrait/char_017_huang_1.png","portraitE2":"/assets/char/portrait/char_017_huang_2.png","spine":{"front":{"skel":"/assets/spine/op/char_017_huang/front/char_017_huang.skelLine truncated
|
||||
{"version":1,"hash":"0fcb28896f6e","generator":"tools/fetch-assets.mjs","stats":{"files":7968,"bytes":485053064,"chars":209,"charsWithBack":206,"enemies":252,"enemiesWithSpine":251,"tokens":58,"tokensWithSpine":13,"spineModels":671,"bonds":23,"items":59,"bands":40,"skills":522,"modules":131,"ui":602,"sfxUnits":482,"voiceChars":191},"chars":{"char_003_kalts":{"avatar":"/assets/char/avatar/char_003_kalts.png","avatarE2":"/assets/char/avatar/char_003_kalts_2.png","portrait":"/assets/char/portrait/char_003_kalts_1.png","portraitE2":"/assets/char/portrait/char_003_kalts_2.png","spine":{"front":{"skel":"/assets/spine/op/char_003_kalts/front/char_003_kalts.skel","atlas":"/assets/spine/op/char_003_kalts/front/char_003_kalts.atlas","textures":["/assets/spine/op/char_003_kalts/front/char_003_kalts.png"],"pma":false,"anims":{"idle":"Idle","deploy":"Start","attack":{"begin":null,"loop":"Attack","end":null},"attackDown":null,"skill":{"begin":null,"loop":"Attack","end":null,"via":"attack","index":0,"idle":null},"die":"Die","move":null,"stun":null,"skills":{"0":{"begin":null,"loop":"Attack","end":null,"via":"attack","index":0,"idle":null},"1":{"begin":null,"loop":"Attack","end":null,"via":"attack","index":1,"idle":null},"2":{"begin":null,"loop":"Attack","end":null,"via":"attack","index":2,"idle":null}}},"animations":{"Attack":1,"Default":0,"Die":1,"Idle":9,"Start":1},"events":["OnAttack","OnStart"],"hits":{"Attack":[0.433]},"bounds":{"x":-227.579,"y":-138.244,"width":532.04,"height":551.479}},"back":{"skel":"/assets/spine/op/char_003_kalts/back/char_003_kalts.skel","atlas":"/assets/spine/op/char_003_kalts/back/char_003_kalts.atlas","textures":["/assets/spine/op/char_003_kalts/back/char_003_kalts.png"],"pma":false,"anims":{"idle":"Idle","deploy":"Start","attack":{"begin":null,"loop":"Attack","end":null},"attackDown":null,"skill":{"begin":null,"loop":"Attack","end":null,"via":"attack","index":0,"idle":null},"die":null,"move":null,"stun":null,"skills":{"0":{"begin":null,"loop":"Attack","end":null,"via":"attack","index":0,"idle":null},"1":{"begin":null,"loop":"Attack","end":null,"via":"attack","index":1,"idle":null},"2":{"begin":null,"loop":"Attack","end":null,"via":"attack","index":2,"idle":null}}},"animations":{"Attack":1,"Default":0,"Idle":9,"Start":1},"events":["OnAttack","OnStart"],"hits":{"Attack":[0.433]},"bounds":{"x":410.785,"y":-2.26,"width":531.785,"height":416.492}}}},"char_010_chen":{"avatar":"/assets/char/avatar/char_010_chen.png","avatarE2":"/assets/char/avatar/char_010_chen_2.png","portrait":"/assets/char/portrait/char_010_chen_1.png","portraitE2":"/assets/char/portrait/char_010_chen_2.png","spine":{"front":{"skel":"/assets/spine/op/char_010_chen/front/char_010_chen.skel","atlas":"/assets/spine/op/char_010_chen/front/char_010_chen.atlas","textures":["/assets/spine/op/char_010_chen/front/char_010_chen.png"],"pma":false,"anims":{"idle":"Idle","deploy":"Start","attack":{"begin":null,"loop":"Attack","end":null},"attackDown":null,"skill":{"begin":null,"loop":"Skill","end":"Skill_End","index":0,"idle":null},"die":"Die","move":null,"stun":null,"skills":{"0":{"begin":null,"loop":"Skill","end":"Skill_End","index":0,"idle":null},"1":{"begin":null,"loop":"Skill_2","end":null,"index":1,"idle":null},"2":{"begin":null,"loop":"Skill_3","end":null,"index":2,"idle":null}}},"animations":{"Attack":1.5,"Attack_End":0.6,"Attack_Pre":0.6,"Default":0,"Die":1,"Idle":2,"Skill":1,"Skill_2":1.167,"Skill_3":2.933,"Skill_End":0.6,"Skill_End_2":0.6,"Skill_End_3":0.3,"Start":0.833},"events":["OnAttack","OnStart"],"hits":{"Attack":[0.433,1],"Skill":[0.467],"Skill_2":[0.467],"Skill_3":[0.6,0.8,1,1.2,1.4,1.6,1.8,2,2.2,2.833,2.933]},"bounds":{"x":-322.335,"y":-109.274,"width":663.549,"height":524.806}},"back":{"skel":"/assets/spine/op/char_010_chen/back/char_010_chen.skel","atlas":"/assets/spine/op/char_010_chen/back/char_010_chen.atlas","textures":["/assets/spine/op/char_010_chen/back/char_010_chen.png"],"pma":false,"anims":{"idle":"Idle","deploy":"Start","attack":{"begin":null,"loop":"Attack","end":null},"attackDown":null,"skill":{"begin":null,"loop":"Skill","end":"Skill_End","index":0,"idle":null},"die":null,"move":null,"stun":null,"skills":{"0":{"begin":null,"loop":"Skill","end":"Skill_End","index":0,"idle":null},"1":{"begin":null,"loop":"Skill","end":"Skill_End","index":1,"idle":null},"2":{"begin":null,"loop":"Skill","end":"Skill_End","index":2,"idle":null}}},"animations":{"Attack":1.5,"Attack_End":0.6,"Attack_Pre":0.6,"Default":0,"Idle":2,"Skill":1,"Skill_End":0.6,"Start":0.833},"events":["OnAttack","OnStart"],"hits":{"Attack":[0.433,1],"Skill":[0.467]},"bounds":{"x":-173.082,"y":-13.829,"width":315.887,"height":404.623}}}},"char_017_huang":{"avatar":"/assets/char/avatar/char_017_huang.png","avatarE2":"/assets/char/avatar/char_017_huang_2.png","portrait":"/assets/char/portrait/char_017_huang_1.png","portraitE2":"/assets/char/portrait/char_017_huang_2.png","spine":{"front":{"skel":"/assets/spine/op/char_017_huang/front/char_017_huang.skelLine truncated
|
||||
+4
-1
@@ -206,7 +206,10 @@ All paths are URL paths relative to the site root, for example `/assets/char/ava
|
||||
// units' attack / hit (tools/assets/audio.mjs pickUnitSfx): operators get normal-mode banks only — the plain
|
||||
// `attack` / `combat` ability first, never a bank holding a skill-mode file (`_d` / `_h` / `_s`; the normal attack's end
|
||||
// in `_n`) — with their own projectile banks (ON_PROJECTILE_BORN / _HIT.projectile_chr_<name>) as fallbacks
|
||||
// (DESIGN §18.4: 纯烬艾雅法拉's S3 impact used to be her `hit`); enemies and tokens take the first attack-like bank
|
||||
// (DESIGN §18.4: 纯烬艾雅法拉's S3 impact used to be her `hit`); an operator with such a plain bank (its default mode is
|
||||
// the unsuffixed ability) never takes a numbered variant `attack.N` — a skill mode's, whatever its file name (银灰's S3
|
||||
// swing p_atk_silver_n, community report of 2026-10-06); one that numbers its default mode (`attack.0` …) keeps the
|
||||
// numbered order; enemies and tokens take the first attack-like bank
|
||||
fonts: { css: '/fonts/fonts.css', faces: { [name]: { family, weight, woff2?, original } } }
|
||||
}
|
||||
```
|
||||
|
||||
@@ -0,0 +1,44 @@
|
||||
// test/feedback5-operator-sfx.test.js — community report of 2026-10-06 (item 54) 「干员银灰的普通攻击的音效错误的使用了3技能期间
|
||||
// 的攻击音效」. The official banks of 银灰 (excel/audio_data.json): ON_ABILITY_HIT.char_172_svrash.combat (p_imp_spear_n — his
|
||||
// Default mode's Combat, the normal attack, which has no swing bank), ON_ABILITY_START / HIT .attack.2 (p_atk_silver_n /
|
||||
// p_imp_sword_n — his S3 mode; the charpack's modes are Default, S2, S3) and HIT .attack.1 (p_imp_sword_n — S2). The picker
|
||||
// took the numbered variants because their files end in _n. Now an operator whose default mode is the unsuffixed ability (a
|
||||
// plain attack / combat bank of any event) never takes a numbered variant (tools/assets/audio.mjs pickUnitSfx). The same
|
||||
// rule drops 雷蛇's S2 lightning impact, 初雪's S1-2 mode impact and 圣约送葬人's S3_End impact from their normal attacks.
|
||||
// Run: node --test test/feedback5-operator-sfx.test.js
|
||||
|
||||
import { test } from 'node:test';
|
||||
import assert from 'node:assert/strict';
|
||||
import { pickUnitSfx } from '../tools/assets/audio.mjs';
|
||||
|
||||
const P = (f) => [`player/${f.startsWith('p_imp') ? 'p_imp' : 'p_atk'}/${f}.mp3`];
|
||||
const op = (entries, projectile = {}) => pickUnitSfx(new Map(entries.map(([k, f]) => [k, P(f)])), { operator: true, projectile });
|
||||
|
||||
test('银灰: the normal attack is the Default mode\'s Combat — spear impact, no swing; the S3 swing and the S2 / S3 impact never', () => {
|
||||
const sfx = op([
|
||||
['ON_ABILITY_HIT.combat', 'p_imp_spear_n'],
|
||||
['ON_ABILITY_START.attack.2', 'p_atk_silver_n'],
|
||||
['ON_ABILITY_HIT.attack.2', 'p_imp_sword_n'],
|
||||
['ON_ABILITY_HIT.attack.1', 'p_imp_sword_n'],
|
||||
]);
|
||||
assert.deepEqual(sfx, { hit: P('p_imp_spear_n') });
|
||||
});
|
||||
|
||||
test('雷蛇 / 初雪 / 圣约送葬人: a plain swing, and no skill mode\'s impact on the normal attack', () => {
|
||||
assert.deepEqual(op([['ON_ABILITY_ON.attack', 'p_atk_pistol_n'], ['ON_ABILITY_HIT.attack.1', 'p_imp_lightning_n'], ['ON_ABILITY_START.attack.1', 'p_atk_lightning_n']]),
|
||||
{ attack: P('p_atk_pistol_n') }, '雷蛇 (attack.1 = S2)');
|
||||
assert.deepEqual(op([['ON_ABILITY_ON.attack', 'p_atk_shakebell_n'], ['ON_ABILITY_HIT.attack.1', 'p_imp_shakebell_n']]),
|
||||
{ attack: P('p_atk_shakebell_n') }, '初雪 (attack.1 = the S1-2 mode)');
|
||||
assert.deepEqual(op([['ON_ABILITY_ON.attack', 'p_atk_shotgunx_n'], ['ON_ABILITY_ON.attack.1', 'p_atk_shotgunx_d'], ['ON_ABILITY_START.attack.4', 'p_atk_bngel'], ['ON_ABILITY_HIT.attack.4', 'p_imp_bngel']]),
|
||||
{ attack: P('p_atk_shotgunx_n') }, '圣约送葬人 (attack.4 = S3_End)');
|
||||
// the operator's own projectile bank still serves when the plain ability has no impact bank
|
||||
assert.deepEqual(op([['ON_ABILITY_ON.attack', 'p_atk_x_n'], ['ON_ABILITY_HIT.attack.1', 'p_imp_x_s']], { hit: P('p_imp_x_n') }), { attack: P('p_atk_x_n'), hit: P('p_imp_x_n') });
|
||||
});
|
||||
|
||||
test('operators that number their default mode (attack.0 …) keep the earlier rule; a plain bank that is a skill-mode file does not count', () => {
|
||||
assert.deepEqual(op([['ON_ABILITY_START.attack.0', 'p_atk_y_n'], ['ON_ABILITY_HIT.attack.0', 'p_imp_y_n'], ['ON_ABILITY_START.attack.1', 'p_atk_y_n2']]),
|
||||
{ attack: P('p_atk_y_n'), hit: P('p_imp_y_n') });
|
||||
// 荒芜拉普兰德: the unsuffixed START bank is her _d file (a skill mode), her default is attack.0
|
||||
assert.deepEqual(op([['ON_ABILITY_START.attack.0', 'p_atk_whtolfdrksl_n'], ['ON_ABILITY_START.attack', 'p_atk_whtolfdrksl_d'], ['ON_ABILITY_ON.attack.2', 'p_atk_whtolfdrksl_h']]),
|
||||
{ attack: P('p_atk_whtolfdrksl_n') });
|
||||
});
|
||||
@@ -167,6 +167,21 @@ export function pickUnitSfx(banks, opts = {}) {
|
||||
};
|
||||
const exact = (event) => ['attack', 'combat'].map((ab) => banks.get(`${event}.${ab}`)).find((p) => p?.length && ok(p)) ?? null;
|
||||
const own = (p) => (p?.length && ok(p) ? p : null);
|
||||
// operators whose default mode is the unsuffixed ability (a plain attack / combat bank of any event): every numbered
|
||||
// variant (attack.1, attack.2 …) is then a skill mode's ability, whatever its file name — 银灰's attack.2 swing
|
||||
// p_atk_silver_n is his S3 mode's (charpack modes Default / S2 / S3), his normal attack is the Default mode's Combat (impact
|
||||
// ON_ABILITY_HIT.combat p_imp_spear_n, no swing bank): community report of 2026-10-06 (item 54) 「银灰的普通攻击的音效错误
|
||||
// 的使用了3技能期间的攻击音效」. Only the plain banks then, or the operator's own projectile banks; newer operators number
|
||||
// their default mode too (attack.0 …) and keep the rule below
|
||||
if (opts.operator && ['ON_ABILITY_START', 'ON_ABILITY_ON', 'ON_ABILITY_HIT'].some((e) => exact(e))) {
|
||||
const swing = exact('ON_ABILITY_START') ?? exact('ON_ABILITY_ON') ?? own(proj.born);
|
||||
const impact = exact('ON_ABILITY_HIT') ?? own(proj.hit);
|
||||
if (swing) out.attack = swing;
|
||||
if (impact) out.hit = impact;
|
||||
if (banks.get('ON_UNIT_DEAD')?.length) out.die = banks.get('ON_UNIT_DEAD');
|
||||
if (banks.get('ON_UNIT_BORN')?.length) out.born = banks.get('ON_UNIT_BORN');
|
||||
return out;
|
||||
}
|
||||
// operators: the plain ability of either event before any numbered variant
|
||||
const plain = opts.operator ? exact('ON_ABILITY_START') ?? exact('ON_ABILITY_ON') : null;
|
||||
const attack = plain
|
||||
|
||||
Reference in new issue
Block a user