Files
thejayman77 ce69b8cd18 zen: UB is now the Queen angelfish (real model) + fix admin lockout
- 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>
2026-07-01 18:10:38 -04:00

40 lines
1.9 KiB
JavaScript
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
// Trim a GLB's baked animation down to a [0, tEnd] window (seconds) — for the angelfish
// pack that's the Idle clip (GLB frames 0109 ≈ 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);