0.2.5-dev: official battlefield scenes on the 3D board — a local fetch / extract step for the season scene bundles, per-field scene rendering with the metal board as fallback, the full bundle can include the extracted scenes and setup can fetch them (--scenes); nothing official is stored in the repository (PR #533 by @andrew05060414, ported; the owner's decision of 2026-10-12); golden: unchanged

This commit is contained in:
sganggs committed 2026-10-12 14:59:20 +08:00
1 parent 7fd0478dc0
commit d482fbb8ba
19 files changed
+1659 -22

No files matched your search

+20 -3
View File
@@ -20,7 +20,9 @@
// THIRD-PARTY-NOTICES.md, README.md, CHANGELOG.md, docs/PLAYING.md and docs/DEPLOY.md. `npm ci --omit=dev` in the
// stage adds the production node_modules and (postinstall) public/vendor. The full zip adds the git-ignored art: the
// files data/assets.json lists, public/fonts, and the local-client extraction (public/assets/local/,
// data/local-assets.json) when present — nothing else on disk, so art the data no longer lists (焰狐龙梓兰, left out of
// data/local-assets.json) when present, including the 0.2.5 battlefield scenes under public/assets/local/scene/
// when this machine extracted them. Missing scenes are not an error: the board keeps the metal tiles, and the
// summary says so. Nothing else on disk ships, so art the data no longer lists (焰狐龙梓兰, left out of
// 自选 in 0.2.0, or files of an old mapping) never ships even when this machine still has it. FULL_ZIP_JP_VOICE (below)
// decides whether the full zip carries the Japanese voice dub too (default: yes).
// Left out: test/, the maintainer tools (build-data, golden, botbench, i18n, check-imports, this file …),
@@ -105,6 +107,8 @@ export const PLAYER_NPM_SCRIPTS = ['start', 'setup', 'doctor', 'launch', 'postin
export const FULL_ZIP_JP_VOICE = true;
/** Where the JP dub lies (plan.mjs voiceAlt: audio/voice/<lang>/<charId>/<file>). */
export const JP_VOICE_DIR = 'public/assets/audio/voice/jp/';
/** Extracted official battlefield scenes (0.2.5 asset pack). Already ignored with public/assets/. */
const SCENE_ART_PREFIX = 'public/assets/local/scene/';
/**
* Refused in a plan and in a stage: 0.1.x's list (owner-only notes, the promo project, caches, per-machine config) and
@@ -220,9 +224,11 @@ export const manifestFiles = listedArtFiles;
* data/local-assets.json (when present). `missing`: listed files not on disk; `orphans`: files under public/assets
* that nothing lists (left out); `held`: listed files the zip holds back on purpose — the JP voice dub when `jpVoice`
* (FULL_ZIP_JP_VOICE) is off: neither shipped nor missing, and never deleted by an update.
* `scenes`: the files under public/assets/local/scene/ (the 0.2.5 battlefield scenes). They are also in `files`.
* A lite plan and a machine that has not extracted them return `scenes: []`; packaging still succeeds.
*/
export function artPlan(root, { lite = false, jpVoice = FULL_ZIP_JP_VOICE } = {}) {
if (lite) return { files: [], missing: [], orphans: [], held: [], local: false };
if (lite) return { files: [], missing: [], orphans: [], held: [], local: false, scenes: [] };
const listed = manifestFiles(readJson(path.join(root, 'data', 'assets.json')) ?? {});
const local = isFile(path.join(root, 'data', 'local-assets.json'));
if (local) for (const f of manifestFiles(readJson(path.join(root, 'data', 'local-assets.json')) ?? {})) listed.add(f);
@@ -242,7 +248,11 @@ export function artPlan(root, { lite = false, jpVoice = FULL_ZIP_JP_VOICE } = {}
// without case: on Windows / macOS a file whose name differs only in case is the listed one (fetch-assets orphanFiles)
const lower = new Set([...files, ...held].map((f) => f.toLowerCase()));
const orphans = listTree(root, 'public/assets').filter((f) => !lower.has(f.toLowerCase()));
return { files: [...files].sort(), missing: missing.sort(), orphans, held: held.sort(), local };
const sorted = [...files].sort();
return {
files: sorted, missing: missing.sort(), orphans, held: held.sort(), local,
scenes: sorted.filter((f) => f.startsWith(SCENE_ART_PREFIX)),
};
}
const URL_MOUNTS = [['/data/', 'data/'], ['/shared/', 'shared/'], ['/sim/', 'server/sim/']];
@@ -401,6 +411,7 @@ export function plan(root, opts = {}) {
return {
version: pkg.version || '0.0.0', lite, tracked: keep, art: art.files, generated, files, dropped, orphans: art.orphans,
held: art.held, localArt: art.local, problems, names: names.length, scanned,
scenes: { files: art.scenes || [], bytes: sumBytes(root, art.scenes || []) },
bytes: {
tracked: trackedBytes, art: artBytes, modules: modulesBytes, vendor: vendorBytes,
total: trackedBytes + artBytes + modulesBytes + vendorBytes,
@@ -419,6 +430,12 @@ export function formatSummary(p) {
];
if (!p.lite) {
lines.push(`art: ${p.art.length} files, ${MB(p.bytes.art)} (data/assets.json, public/fonts${p.localArt ? ', public/assets/local + data/local-assets.json' : '; no local-client art here'})`);
const sceneFiles = p.scenes?.files;
if (sceneFiles?.length) {
lines.push(`battlefield scenes: ${sceneFiles.length} files, ${MB(p.scenes.bytes || 0)} (0.2.5 asset pack, public/assets/local/scene; the metal board is the fallback when a stage has none)`);
} else if (p.scenes) {
lines.push('battlefield scenes: none on this machine (the full bundle ships the metal board; fetch_scenes.py then extract.py --only scene before packaging to include the 11 official scenes)');
}
}
if (p.generated?.length) lines.push(`generated: ${p.generated.join(', ')} (the pack index of the shipped packs, for a static host)`);
lines.push(`production node_modules: ${MB(p.bytes.modules)} · public/vendor: ${MB(p.bytes.vendor)} (npm ci --omit=dev and its postinstall)`);