feedback5 WU: module type icons get one lower-case file per type (dec-X and dec-x were two names for one file)

The official data spells one DEC X module 'dec-X' (uniequip_003_aglina) and the others 'dec-x' (WAH-Y is the other
mixed-case type id), and the asset plan stored each under its own case: module/dec-X.png and module/dec-x.png. On
Windows / macOS those are one file, so a release zip built there could hold only one of them and a case-sensitive
server (a Linux VPS) would miss the other — the release packager found it ("planned but not staged"). The local file
is now module/{typeIcon in lower case}.png; the upstream names keep their order (the id as spelled, then lower case).

data/assets.json regenerated offline (node tools/fetch-assets.mjs --offline --add-only). JSON diff: modules.dec-X
/assets/module/dec-X.png -> /assets/module/dec-x.png, modules.WAH-Y /assets/module/WAH-Y.png -> /assets/module/wah-y.png,
hash, stats.files 6979 -> 6978, stats.bytes 458334505 -> 458333444; nothing else.

fetch-assets compares its orphans (what --prune deletes) without case: on Windows / macOS module/WAH-Y.png on disk is
the listed module/wah-y.png and must not be deleted (orphanFiles, exported).

Tests: test/assets-diy.test.js (one lower-case file per type, no two manifest URLs differing only in case, orphans).
docs/ASSETS.md: the module icon file name.
This commit is contained in:
sganggs committed 2026-10-06 17:59:13 +08:00
1 parent 9bfac54c8f
commit b8a86b88f1
5 files changed
+42 -8

No files matched your search

+5 -2
View File
@@ -595,8 +595,11 @@ export function buildPlan({ assets07, ops03, enemies05, maps05, audio, modelsDat
// --- module type icons ----------------------------------------------------
const modules = {};
for (const t of [...new Set(moduleTypes || [])].filter((x) => typeof x === 'string' && /^[a-z0-9-]+$/i.test(x)).sort()) {
// the client's file name, else its lower-case form (the type id of a few modules is mixed case: WAH-Y → wah-y.png)
modules[t] = leaf(alt(`module/${safeName(t)}.png`, [...new Set([t, t.toLowerCase()])].map((n) => joinUrl(RAW.aa2, `arts/ui/uniequiptype/${n}.png`))));
// the client's file name, else its lower-case form (the type id of a few modules is mixed case: WAH-Y → wah-y.png).
// One lower-case file per type: the official data spells one DEC X module 'dec-X' (uniequip_003_aglina) and the
// others 'dec-x'; two paths that differ only in case are one file on Windows / macOS (and in a release zip built or
// extracted there), so a case-sensitive server would miss one of them.
modules[t] = leaf(alt(`module/${safeName(t).toLowerCase()}.png`, [...new Set([t, t.toLowerCase()])].map((n) => joinUrl(RAW.aa2, `arts/ui/uniequiptype/${n}.png`))));
}
// --- 干员战斗语音 (excel/charword_table.json → audio.voice) ---------------------------------------------
+15 -3
View File
@@ -158,6 +158,19 @@ export function shrinkGuard(prev, next, { allowShrink = false, prune = false } =
return { dropped, write: dropped.length === 0 || !!allowShrink || !!prune };
}
/**
* Files under public/assets the manifest does not reference (`--prune` deletes them). public/assets/local/** belongs to
* tools/local-extract (data/local-assets.json) and is never an orphan: --prune used to delete all of it. Compared without
* case: on Windows / macOS a listed path and a file whose name differs only in case are one file (module/WAH-Y.png on
* disk serves the listed module/wah-y.png), which --prune must not delete.
* @param {string[]} onDisk forward-slash paths relative to public/assets
* @param {Iterable<string>} referenced the manifest's files, same form
*/
export function orphanFiles(onDisk, referenced) {
const listed = new Set([...referenced].map((r) => r.toLowerCase()));
return onDisk.filter((r) => !listed.has(r.toLowerCase()) && !r.startsWith('local/'));
}
const mb = (n) => `${(n / 1048576).toFixed(1)} MB`;
const log = (m) => console.log(m);
@@ -363,9 +376,8 @@ async function main() {
const guard = shrinkGuard(current, manifest, opts);
if (guard.write) await writeJsonAtomic(MANIFEST, manifest);
// Orphans: files on disk that the manifest does not reference (e.g. after a mapping change). public/assets/local/**
// belongs to tools/local-extract (data/local-assets.json) and is never an orphan: --prune used to delete all of it.
const orphans = (await listFiles(ASSETS)).filter((r) => !resolved.files.has(r) && !r.startsWith('local/'));
// Orphans: files on disk that the manifest does not reference (e.g. after a mapping change).
const orphans = orphanFiles(await listFiles(ASSETS), resolved.files);
if (opts.prune) for (const r of orphans) { try { await unlink(join(ASSETS, r)); } catch { /* ignore */ } }
const charIds = Object.keys(assets07.operators || {});