feedback5 WU: 焰狐龙梓兰 (MH05) leaves data/assets.json; a voice slot with no line on disk is dropped, not []

She left the 自选 pool in 0.2.0 (data/backups.json diy.excluded, a collab operator) but her art stayed in the
manifest because fetch-assets refuses to shrink it. Regenerated offline in this worktree, whose public/assets and
public/fonts are the shared ones: node tools/fetch-assets.mjs --offline --add-only --allow-shrink (--add-only so no
file on disk is rewritten and the fonts entry is kept). The run dropped exactly her 163 entries.

JSON diff against the previous manifest: removed chars.char_1048_orchd2, skills.skchr_orchd2_1..3,
skillsById.skchr_orchd2_1..3, audio.sfx.units.char_1048_orchd2; changed hash and stats (files 6994 -> 6979,
bytes 462620448 -> 458334505, chars 210 -> 209, charsWithBack 207 -> 206, spineModels 673 -> 671,
skills 525 -> 522, sfxUnits 484 -> 483); nothing added, nothing else changed.

The first regeneration also added 71 entries like audio.voice.char_134_ifrit = {select: [], place: []}: the 自选
operators' voice lines are planned but not downloaded on this machine, and resolveTemplate kept an array whose lines
were all missing as []. tools/assets/manifest.mjs now drops such an array like a missing leaf (and the entry it
empties); an array that is empty in the template stays.

Tests: test/assets.test.js (the array rule), test/assets-diy.test.js (no diy.excluded operator keeps art in the
manifest). docs/ASSETS.md: 71 自选 picks, 209 operators (206 with Back), 522 skill icons, the empty-slot rule.
This commit is contained in:
sganggs committed 2026-10-06 17:51:41 +08:00
1 parent e2bb090f44
commit 9bfac54c8f
5 files changed
+42 -5

No files matched your search

+16
View File
@@ -91,3 +91,19 @@ test('the committed data/assets.json lists every 自选 operator\'s avatar, port
for (const t of dataExtras(BACKUPS, CHESS).moduleTypes) assert.ok(m.modules[t] && onDisk(m.modules[t]), `module type icon ${t}`);
assert.equal(m.stats.modules, Object.keys(m.modules).length);
});
test('an operator left out of 自选 (data/backups.json diy.excluded: the collab picks, 焰狐龙梓兰 MH05 among them) keeps no art in data/assets.json, so the full release zip (tools/package.mjs) never ships it', () => {
const m = load('assets');
const text = JSON.stringify(m);
assert.ok(BACKUPS.diy.excluded.includes('char_1048_orchd2'), '焰狐龙梓兰 is excluded');
for (const id of BACKUPS.diy.excluded) {
const code = id.split('_').slice(2).join('_');
assert.equal(m.chars[id], undefined, `${id}: chars`);
assert.equal(m.audio.sfx.units[id], undefined, `${id}: unit sfx`);
assert.equal(m.audio.voice?.[id], undefined, `${id}: voice`);
assert.ok(!Object.keys(m.skills).some((k) => k.startsWith(`skchr_${code}_`)), `${id}: skill icons`);
assert.ok(!text.includes(id), `${id}: no file of theirs`);
}
assert.equal(m.stats.chars, Object.keys(m.chars).length);
assert.equal(m.stats.skills, Object.keys(m.skills).length);
});
+15
View File
@@ -362,6 +362,21 @@ describe('audio banks and plan id sets', () => {
assert.equal(ok.value.p, '/assets/package.json');
assert.equal(ok.fallbacks.length, 1);
});
test('an array whose lines are all missing is dropped, with the entry it empties (a voice slot never downloaded)', () => {
const line = (rel) => ({ alts: [{ rel, urls: ['u'] }] });
const tpl = {
voice: {
a: { select: [line('nope/1.mp3'), line('nope/2.mp3')], place: [line('nope/3.mp3')] },
b: { select: [line('nope/4.mp3'), line('package.json')] },
},
empty: [],
};
const r = resolveTemplate(tpl, { root: ROOT, spine: new Map() });
assert.deepEqual(r.value.voice, { b: { select: ['/assets/package.json'] } }, 'operator a has no line on disk: no entry at all');
assert.deepEqual(r.value.empty, [], 'an array that was empty in the template stays');
assert.deepEqual(r.misses.sort(), ['voice.a.place[0]', 'voice.a.select[0]', 'voice.a.select[1]', 'voice.b.select[0]']);
});
});
// ---------------------------------------------------------------------------