ce69b8cd18
- Admin lockout: /zen checked blockedForViewer() before auth loaded, so a hard-refresh/
direct-link bounced admins to /play. Now revalidate auth (await refresh if !ready)
BEFORE the gate check.
- UB swap: retired the two-tail koi (ub.glb/ub-split.glb) for the vetted Queen angelfish.
Trimmed the 75.67s baked Take down to just the Idle loop (tools/glb-split/trim-idle.mjs
→ 16MB → 6.9MB) → static/models/ub-angelfish.glb. aquarium.js reworked for the pack's
ONE-mesh/TWO-material layout (…_body opaque single-sided; …_fins opaque alpha-tested,
tunable); animation is the trimmed Idle. Debug tuner (/zen?debug=1) updated: yaw/pitch/
scale + one fins&tail section. Still devgate IN_DEV={'zen'} — admin-only.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
40 lines
1.9 KiB
JavaScript
40 lines
1.9 KiB
JavaScript
// Trim a GLB's baked animation down to a [0, tEnd] window (seconds) — for the angelfish
|
||
// pack that's the Idle clip (GLB frames 0–109 ≈ 3.633s), which drops ~95% of the animation
|
||
// data (the full Take is 75.67s). Geometry/skin/textures are untouched. Shared input
|
||
// accessors are sliced once; outputs per-sampler. Usage: node trim-idle.mjs in.glb out.glb 3.633
|
||
import { NodeIO } from '@gltf-transform/core';
|
||
import { ALL_EXTENSIONS } from '@gltf-transform/extensions';
|
||
import { prune } from '@gltf-transform/functions';
|
||
|
||
const [, , inPath, outPath, tEndStr] = process.argv;
|
||
const tEnd = parseFloat(tEndStr);
|
||
const io = new NodeIO().registerExtensions(ALL_EXTENSIONS);
|
||
const doc = await io.read(inPath);
|
||
const root = doc.getRoot();
|
||
|
||
// Pass 1: per UNIQUE input time-track, compute how many keyframes fall in [0, tEnd]
|
||
// (BEFORE mutating — all channels share one input), then slice the input once.
|
||
const keepByInput = new Map();
|
||
for (const anim of root.listAnimations())
|
||
for (const s of anim.listSamplers()) {
|
||
const input = s.getInput();
|
||
if (keepByInput.has(input)) continue;
|
||
const times = input.getArray();
|
||
let last = 0;
|
||
for (let i = 0; i < times.length; i++) { if (times[i] <= tEnd + 1e-6) last = i; else break; }
|
||
const keep = last + 1;
|
||
keepByInput.set(input, keep);
|
||
if (keep >= 2 && keep < times.length) input.setArray(times.slice(0, keep));
|
||
}
|
||
// Pass 2: slice every sampler's OUTPUT to its input's keep count.
|
||
for (const anim of root.listAnimations())
|
||
for (const s of anim.listSamplers()) {
|
||
const keep = keepByInput.get(s.getInput());
|
||
const output = s.getOutput();
|
||
const comps = output.getElementSize();
|
||
if (keep * comps < output.getArray().length) output.setArray(output.getArray().slice(0, keep * comps));
|
||
}
|
||
await doc.transform(prune()); // repack buffers + drop the orphaned (trimmed-away) data
|
||
await io.write(outPath, doc);
|
||
console.log('wrote', outPath);
|