- With HTTP(S)_PROXY set, Node 22.0–22.20 / 23 cannot route the built-in fetch through it; the port of #289 made
fetch-assets (and so setup) fail there. Before 0.2.2 those downloads went direct, and a proxy variable left over
from another tool must not stop a player's first start: restartForEnvProxy now logs the reason and returns false
(go on in this process), guardDefaultFetch warns once and fetches. A Node that can proxy (≥ 22.21 / ≥ 24) still
restarts once with NODE_USE_ENV_PROXY=1 and never fetches around the proxy.
- restartForEnvProxy takes an injectable `version` for the test. Tests: assets-env-proxy 9 / 9 (a new case for the
old-Node path).
Did not apply the patch. It set a global dispatcher on the npm undici package and rewrote the lockfile. Node's built-in fetch does not use that dispatcher, and node:undici is not importable. fetch() reads HTTP_PROXY, HTTPS_PROXY and NO_PROXY only at startup, and only when that startup had NODE_USE_ENV_PROXY=1 (or --use-env-proxy). That is Node >=22.21.0 and >=24.0.0.
tools/fetch-assets.mjs restarts itself once with NODE_USE_ENV_PROXY=1 before downloading. The default fetch in the downloader and the index cache rejects when a proxy is set but this process cannot use it, including Node 22.0–22.20 and 23, instead of fetching directly. No new dependency. Engines stay >=22.
Tests: node --test test/assets-env-proxy.test.js — 8 pass, 0 fail. node --test test/asset-network.test.js test/assets-diy.test.js test/fetch-assets-shrink.test.js test/assets.test.js — 84 pass, 0 fail. Before this commit: npm run -s lint — 0 errors (135 existing warnings); npm run -s typecheck — ok; npm run -s check:imports — exit 0 (3 pre-existing sim-node notes). docs, version and package tests: 46 pass and 1 fail while env-proxy.mjs was still untracked; after git add, node --test test/package.test.js — 10 pass, 0 fail.
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.
Downloaded 1,295 files, 127.2 MB (133,426,089 bytes) into the shared public/assets; no existing file changed or removed. data/assets.json only gains entries: 72 chars (all with avatar, portrait, Front and Back Spine), 38 tokens (avatars; no Spine — upstream has skin variants only), 208 skill icons, 10 sub-profession icons, 102 SFX units, 131 module type icons.