[orchestrator] round 7 phase 0: 27.3 MB -> 7.0 MB, and the build can no longer forget
All 27 shipped GLBs run through gltf-transform (meshopt + webp + 1024 textures). Measured per-asset, no failures: 27,304 KB -> 7,132 KB (74% off) with PBR intact. The biggest wins are the wave-1/wave-2 assets that shipped raw (artifact-bottler 1453->329, chroma-keyer 1928->301, mv-extractor 1376->190); the six baked through the new farm script were already lean and barely moved, which is the check that the farm-side step is doing its job. tools/optimize-assets.mjs: idempotent gate wired into deploy.sh BEFORE vite build. Skips anything already carrying EXT_meshopt_compression (so it costs ~0s when nothing is new) and FAILS THE BUILD on any asset over 900 KB — a farm regression that emits trellis2-dense meshes now stops the deploy instead of shipping 36 MB. deploy.sh verify gap closed: it tested exactly one .js file, so a deploy that shipped ZERO models would have reported success. Now checks the JS bundle, a real GLB endpoint, and asserts live model count == built model count. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
parent
84d378a80e
commit
20d2bcbbfe
19
deploy.sh
19
deploy.sh
@ -12,6 +12,9 @@ VPS=humanjing@100.71.119.27
|
||||
CONTAINER=forum-nginx
|
||||
WEBROOT=/usr/share/nginx/html
|
||||
|
||||
echo "— optimize assets (idempotent; skips anything already meshopt-compressed) —"
|
||||
node fktry/tools/optimize-assets.mjs
|
||||
|
||||
echo "— build —"
|
||||
(cd fktry && npx vite build --base=./)
|
||||
cp index.html fktry/dist/match.html
|
||||
@ -27,7 +30,19 @@ ssh "$VPS" "docker exec $CONTAINER rm -rf $WEBROOT/glytch \
|
||||
echo "— verify (cache-busted) —"
|
||||
curl -fsS -o /dev/null -w '%{http_code} https://partly.party/glytch/\n' "https://partly.party/glytch/?v=$(date +%s)"
|
||||
curl -fsS -o /dev/null -w '%{http_code} https://partly.party/glytch/match.html\n' "https://partly.party/glytch/match.html?v=$(date +%s)"
|
||||
# data-endpoint check, not just index (classic nginx-routing failure mode):
|
||||
# Data-endpoint checks, not just the index (classic nginx-routing failure mode).
|
||||
# Round-7 fix: this used to test ONE js file, so a deploy that shipped zero models
|
||||
# still reported success. Now the JS bundle, a real GLB, and the model COUNT.
|
||||
ASSET=$(ls fktry/dist/assets | grep -m1 '\.js$')
|
||||
curl -fsS -o /dev/null -w "%{http_code} https://partly.party/glytch/assets/$ASSET\n" "https://partly.party/glytch/assets/$ASSET?v=$(date +%s)"
|
||||
curl -fsS -o /dev/null -w "%{http_code} assets/$ASSET\n" "https://partly.party/glytch/assets/$ASSET?v=$(date +%s)"
|
||||
|
||||
MODELS_LOCAL=$(ls fktry/dist/assets/models/*.glb 2>/dev/null | wc -l | tr -d ' ')
|
||||
MODEL=$(basename "$(ls fktry/dist/assets/models/*.glb 2>/dev/null | head -1)")
|
||||
if [ -z "$MODEL" ] || [ "$MODELS_LOCAL" -eq 0 ]; then
|
||||
echo "FAIL: build produced no GLB models" >&2; exit 1
|
||||
fi
|
||||
curl -fsS -o /dev/null -w "%{http_code} assets/models/$MODEL\n" "https://partly.party/glytch/assets/models/$MODEL?v=$(date +%s)"
|
||||
MODELS_LIVE=$(ssh "$VPS" "docker exec $CONTAINER sh -c 'ls $WEBROOT/glytch/assets/models/*.glb 2>/dev/null | wc -l'" | tr -d ' ')
|
||||
echo "models: $MODELS_LIVE live / $MODELS_LOCAL built"
|
||||
[ "$MODELS_LIVE" = "$MODELS_LOCAL" ] || { echo "FAIL: model count mismatch" >&2; exit 1; }
|
||||
echo "done. If this was a major change: purge Cloudflare cache for partly.party."
|
||||
|
||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
69
fktry/tools/optimize-assets.mjs
Normal file
69
fktry/tools/optimize-assets.mjs
Normal file
@ -0,0 +1,69 @@
|
||||
#!/usr/bin/env node
|
||||
/**
|
||||
* Idempotent GLB optimization gate. Runs from deploy.sh BEFORE vite build.
|
||||
*
|
||||
* Why this exists: the farm emits 1–8 MB PBR GLBs. Shipped raw, 27 of them were
|
||||
* 27.3 MB; through `gltf-transform optimize` they are 7.0 MB with the same PBR.
|
||||
* That step used to live in my head, which is not a build system. An asset added
|
||||
* by hand — or by a farm run that skipped the post-step — would silently ship raw.
|
||||
*
|
||||
* Skips anything already carrying EXT_meshopt_compression, so it is safe to run
|
||||
* every deploy and costs ~0s when there is nothing new.
|
||||
*/
|
||||
import { execFileSync } from 'node:child_process';
|
||||
import { readdirSync, readFileSync, statSync, renameSync, rmSync } from 'node:fs';
|
||||
import { join } from 'node:path';
|
||||
|
||||
const DIR = new URL('../public/assets/models/', import.meta.url).pathname;
|
||||
const MAX_KB = 900; // per-asset ceiling; a farm regression should fail the build
|
||||
|
||||
/** Read the GLB JSON chunk without a glTF library. */
|
||||
function meta(path) {
|
||||
const b = readFileSync(path);
|
||||
if (b.length < 20 || b.subarray(0, 4).toString() !== 'glTF') return null;
|
||||
const jsonLen = b.readUInt32LE(12);
|
||||
try {
|
||||
return JSON.parse(b.subarray(20, 20 + jsonLen).toString());
|
||||
} catch {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
const files = readdirSync(DIR).filter((f) => f.endsWith('.glb'));
|
||||
let optimized = 0, skipped = 0, before = 0, after = 0;
|
||||
const oversize = [];
|
||||
|
||||
for (const f of files) {
|
||||
const p = join(DIR, f);
|
||||
const sizeBefore = statSync(p).size;
|
||||
before += sizeBefore;
|
||||
const m = meta(p);
|
||||
if (m?.extensionsRequired?.includes('EXT_meshopt_compression')) {
|
||||
skipped++;
|
||||
after += sizeBefore;
|
||||
} else {
|
||||
const tmp = `${p}.opt`;
|
||||
execFileSync('npx', ['--yes', '@gltf-transform/cli@latest', 'optimize', p, tmp,
|
||||
'--texture-compress', 'webp', '--texture-size', '1024',
|
||||
'--simplify-error', '0.002', '--compress', 'meshopt'],
|
||||
{ stdio: 'pipe', timeout: 900_000 });
|
||||
rmSync(p);
|
||||
renameSync(tmp, p);
|
||||
optimized++;
|
||||
after += statSync(p).size;
|
||||
console.log(` optimized ${f}: ${(sizeBefore / 1024) | 0} KB -> ${(statSync(p).size / 1024) | 0} KB`);
|
||||
}
|
||||
const kb = (statSync(p).size / 1024) | 0;
|
||||
if (kb > MAX_KB) oversize.push(`${f} (${kb} KB)`);
|
||||
}
|
||||
|
||||
console.log(`assets: ${files.length} files, ${optimized} optimized, ${skipped} already lean — ` +
|
||||
`${(before / 1048576).toFixed(1)} MB -> ${(after / 1048576).toFixed(1)} MB`);
|
||||
|
||||
if (oversize.length) {
|
||||
console.error(`\nFAIL: ${oversize.length} asset(s) over ${MAX_KB} KB after optimization:`);
|
||||
for (const o of oversize) console.error(` ${o}`);
|
||||
console.error('A mesh this heavy usually means the bake used too dense a pipeline ' +
|
||||
'(trellis2 1024_cascade produces ~487k tris; hunyuan3d_mlx is the machine default).');
|
||||
process.exit(1);
|
||||
}
|
||||
Loading…
Reference in New Issue
Block a user