feedback5: merge feedback4 (3af5b84) — PRs #186 / #134 / #185 / #24 / #183 ported to the split layout (terrainInfo / TERRAIN_TIPS into a new ui/gameLogic/terrain.js re-exported by the gameLogic.js barrel, closesOnFieldPress into ui/gameLogic/shortcuts.js, the rest in place; 42 strings through t(), #183's 设置 too); golden unchanged
Ported by hand: - public/js/ui/gameLogic.js is a re-export shim on feedback5: #185's TERRAIN_TIPS / terrainInfo went into the new public/js/ui/gameLogic/terrain.js (the barrel gains one re-export line); closesOnFieldPress 'terrain' into ui/gameLogic/shortcuts.js. - i18n (docs/I18N.md): every new user-visible string goes through t() with an English entry in public/i18n/en.json (42, after the 补位 strings): the terrain names / tags / fact table are N_() and translated where terrainInfo hands them out, the mechanism lines are msgids with their numbers as params (a line with an optional half is two whole msgids, the list joins use an array param), the card's 地形机制 / 这一格 titles in ui/detailPanel.js. Official EN terms where the EN client has them (stage_table tileInfo: Active Originium, Mire, Exhaust Grille, Deep Water Zone, Incursion Point, Protection Objective, Tunnel Entry / Exit, High Ground; Camouflage). The Chinese output is byte-identical to feedback4's terrainInfo on all 5187 tiles of the 13 stages and on synthetic stages. - public/js/screens/game.js (MatchScreen still lives there): the import keeps feedback5's names plus terrainInfo / fieldTile; the terrain stage is the one view.setStage gets, so it includes feedback5's fieldStage — a 联防 field is played on its own map (GitHub #41), and a tap there explains that map's tile, not the round stage's. The tileClick handler and render/app.js emitTileClick / ui/fallbackField.js auto-merged in place. terrainInfo reads the stage only, so a 补位 / 自选 board explains its tiles the same way (their pieces only change a piece's art and name). - public/js/ui/detailPanel.js: the terrain branch of resolveDetail before feedback5's stand-in / 自选 helpers. - public/js/screens/title.js (#183): feedback5's LangToggle / t() imports plus GIcon / SettingsModal; the gear button's aria-label / title are t('设置') (en.json already has "Settings"). - tools/fetch-assets.mjs / tools/assets/downloader.mjs: --add-only (keepExisting, the font skip) beside #24's --asset-source / SP_ASSET_SOURCE and the shared MirrorPolicy; feedback5's extraOperators / moduleTypes and the voice plan unchanged. - data/assets.json: feedback5's manifest plus #134's audio.sfx.battle.leak (after heal, the plan order); stats files 6993 → 6994 and bytes +23 240 as measured on feedback4; the hash recomputed with the generator's contentHash (c01aa739a2d3 → 6a547fc5c937). A field-wise JSON diff against feedback5 shows only those four fields. - docs: DESIGN §24 intro and §24.5 / §24.6 as on feedback4 (the rest of DESIGN.md is feedback5's); ASSETS.md, DEPLOY.md, PLAYING.md auto-merged. - Auto-merged and checked: audio.js (the sim still emits ['leak', id], server/sim/battle/deploy.js), render/style.js, textures.js, tiles.js, board3d/materials.js, boardArt.js, tools/assets/*, tools/setup.mjs, extract.py, the tests. Checks: the PRs' unit tests (audio 22, gameLogic 44, terrain-detail 1, originium-art 4, boardart 15, board3d-extract 9, asset-network 21, fetch-assets-shrink 4), i18n 9 / i18n-data 8 / i18n-server 4, client-static 313, docs-consistency 24, kits_layout 6 pass; lint 0 errors (no new warnings), check:imports, typecheck; golden: all 283 scenarios match, nothing regenerated.
This commit is contained in:
commit
7059528c47
40 files changed
+1944
-97
No files matched your search
+27
-9
@@ -13,9 +13,10 @@
|
||||
// that file from the extracted models (after a game update).
|
||||
//
|
||||
// Idempotent: existing files with the right size are skipped, so re-running is
|
||||
// cheap. Downloads use ~16 parallel connections, 3 retries per source and a
|
||||
// jsDelivr mirror fallback. Spine atlases get `size:` (and `pma: true` for
|
||||
// enemies); every skeleton is parsed to resolve animation roles.
|
||||
// cheap. Downloads use ~16 parallel connections, 3 retries per direct source,
|
||||
// a jsDelivr fallback and an opt-in GitHub proxy (one short attempt per URL).
|
||||
// Spine atlases get `size:` (and `pma: true` for enemies); every skeleton is
|
||||
// parsed to resolve animation roles.
|
||||
//
|
||||
// The committed data/assets.json never shrinks by accident: an entry whose files
|
||||
// are missing here is left out of a rebuilt manifest, so a run on a machine where
|
||||
@@ -45,6 +46,8 @@ import { existsSync, realpathSync } from 'node:fs';
|
||||
import { dirname, join, relative, sep } from 'node:path';
|
||||
import { fileURLToPath, pathToFileURL } from 'node:url';
|
||||
import { Downloader } from './assets/downloader.mjs';
|
||||
import { MirrorPolicy, selectDownloadSource, validateSource } from './assets/network.mjs';
|
||||
import { normalizeProxyPrefix } from './assets/sources.mjs';
|
||||
import { loadIndexes } from './assets/cache.mjs';
|
||||
import { indexAudio, VOICE_DIRS } from './assets/audio.mjs';
|
||||
import { buildPlan } from './assets/plan.mjs';
|
||||
@@ -63,6 +66,7 @@ const LOCAL_SPINES = join(ROOT, LOCAL_ENEMY_SPINES_FILE);
|
||||
|
||||
const HELP = `Usage: node tools/fetch-assets.mjs [options]
|
||||
--concurrency=N parallel downloads (default 16)
|
||||
--asset-source=M direct (default) or mirror (opt-in; no public-IP lookup)
|
||||
--force re-download files even when present
|
||||
--offline no network: post-process what is on disk and rebuild data/assets.json
|
||||
--dry-run print the plan and exit
|
||||
@@ -78,18 +82,23 @@ const HELP = `Usage: node tools/fetch-assets.mjs [options]
|
||||
file, no font rebuild (a worktree sharing public/assets and public/fonts)
|
||||
--local-spines rewrite ${LOCAL_ENEMY_SPINES_FILE} from the enemy models extracted
|
||||
by tools/local-extract/extract.py (public/assets/local/spine/enemy/)
|
||||
--help this text`;
|
||||
--help this text
|
||||
Environment: SP_ASSET_SOURCE sets the default source; SP_GITHUB_PROXY sets the
|
||||
HTTPS mirror prefix (default https://gh-proxy.com/; empty disables the proxy).
|
||||
Mirror attempts have an 8 s response header timeout; response body has a separate idle timeout. Stops for this run after 3 consecutive
|
||||
failures. Only explicitly enabled GitHub downloads use the third-party proxy.`;
|
||||
|
||||
/**
|
||||
* Parse CLI flags.
|
||||
* @param {string[]} argv
|
||||
* @returns {{concurrency:number, force:boolean, offline:boolean, dryRun:boolean, refreshIndex:boolean, prune:boolean, allowShrink:boolean, addOnly:boolean, localSpines:boolean, voiceLang:string, voiceAll:boolean, help:boolean}}
|
||||
* @returns {{concurrency:number, force:boolean, offline:boolean, dryRun:boolean, refreshIndex:boolean, prune:boolean, allowShrink:boolean, addOnly:boolean, localSpines:boolean, voiceLang:string, voiceAll:boolean, help:boolean, source:string}}
|
||||
*/
|
||||
export function parseArgs(argv) {
|
||||
const o = { concurrency: 16, force: false, offline: false, dryRun: false, refreshIndex: false, prune: false, allowShrink: false, addOnly: false, localSpines: false, voiceLang: 'cn', voiceAll: false, help: false };
|
||||
const o = { concurrency: 16, force: false, offline: false, dryRun: false, refreshIndex: false, prune: false, allowShrink: false, addOnly: false, localSpines: false, voiceLang: 'cn', voiceAll: false, help: false, source: process.env.SP_ASSET_SOURCE || 'direct' };
|
||||
for (const a of argv) {
|
||||
const [k, v] = a.split('=');
|
||||
if (k === '--concurrency') o.concurrency = Math.max(1, Math.min(64, parseInt(v, 10) || 16));
|
||||
else if (k === '--asset-source') o.source = v;
|
||||
else if (k === '--force') o.force = true;
|
||||
else if (k === '--offline') o.offline = true;
|
||||
else if (k === '--dry-run') o.dryRun = true;
|
||||
@@ -104,9 +113,14 @@ export function parseArgs(argv) {
|
||||
else throw new Error(`unknown option ${a}\n${HELP}`);
|
||||
}
|
||||
if (o.addOnly && (o.prune || o.force)) throw new Error(`--add-only never deletes or rewrites files: not with --prune / --force\n${HELP}`);
|
||||
if (!o.help) validateSource(o.source);
|
||||
return o;
|
||||
}
|
||||
|
||||
export function resolveProxyPrefix(source, offline = false, value = process.env.SP_GITHUB_PROXY) {
|
||||
return offline || source !== 'mirror' ? '' : normalizeProxyPrefix(value);
|
||||
}
|
||||
|
||||
/**
|
||||
* What data/backups.json and data/chess.json add to the asset plan: `extraOperators` — every unit of backups.json
|
||||
* (`{ name, subProfessionId, nationId, skills: [{ index, skillId, iconId }] }`; buildPlan plans those research 07 lacks,
|
||||
@@ -264,7 +278,11 @@ async function main() {
|
||||
readJson('docs/research/05-enemies.json'),
|
||||
readJson('docs/research/05-maps.json'),
|
||||
]);
|
||||
const { audioData, modelsData, charword } = await loadIndexes(ROOT, { refresh: opts.refreshIndex && !opts.offline, offline: opts.offline, log });
|
||||
const proxyPrefix = resolveProxyPrefix(opts.source, opts.offline);
|
||||
const source = await selectDownloadSource({ mode: opts.source, offline: opts.offline, proxyPrefix, log });
|
||||
const mirrorPolicy = new MirrorPolicy({ source, proxyPrefix, log });
|
||||
const network = { source, proxyPrefix, mirrorPolicy };
|
||||
const { audioData, modelsData, charword } = await loadIndexes(ROOT, { refresh: opts.refreshIndex && !opts.offline, offline: opts.offline, log, ...network });
|
||||
const audio = indexAudio(audioData);
|
||||
// The game data built by tools/build-data.mjs (when present) may reference more
|
||||
// spawnable enemies/tokens than research lists (e.g. 机变 enemy swaps): cover them too.
|
||||
@@ -298,7 +316,7 @@ async function main() {
|
||||
|
||||
const dl = new Downloader({
|
||||
root: ASSETS, ledgerPath: join(CACHE, 'assets-ledger.json'),
|
||||
concurrency: opts.concurrency, force: opts.force, keepExisting: opts.addOnly, log,
|
||||
concurrency: opts.concurrency, force: opts.force, keepExisting: opts.addOnly, log, ...network,
|
||||
});
|
||||
await dl.loadLedger();
|
||||
const downloadErrors = opts.offline ? [] : await downloadLeaves(leaves, dl, ASSETS, 'files');
|
||||
@@ -311,7 +329,7 @@ async function main() {
|
||||
// Fonts (--add-only: not rebuilt — public/fonts may be another checkout's; the manifest keeps its current entry)
|
||||
let fontErrors = [];
|
||||
if (!opts.offline && !opts.addOnly) {
|
||||
const fdl = new Downloader({ root: FONTS, ledgerPath: join(CACHE, 'fonts-ledger.json'), concurrency: 4, force: opts.force, log });
|
||||
const fdl = new Downloader({ root: FONTS, ledgerPath: join(CACHE, 'fonts-ledger.json'), concurrency: 4, force: opts.force, log, ...network });
|
||||
await fdl.loadLedger();
|
||||
await fdl.run(fontJobs(), 'fonts');
|
||||
dl.totals.bytesDownloaded += fdl.totals.bytesDownloaded;
|
||||
|
||||
Reference in new issue
Block a user