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>
This commit is contained in:
jay
2026-07-01 18:10:38 -04:00
parent e64c5ced3c
commit ce69b8cd18
11 changed files with 1075 additions and 56 deletions
+18 -20
View File
@@ -2,7 +2,7 @@
import { onMount } from 'svelte';
import { goto } from '$app/navigation';
import { page } from '$app/stores';
import { auth } from '$lib/auth.svelte.js';
import { auth, refresh as refreshAuth } from '$lib/auth.svelte.js';
import { isDevGated, blockedForViewer } from '$lib/devgate.js';
import Footer from '$lib/components/Footer.svelte';
@@ -25,12 +25,17 @@
}
onMount(() => {
// Dev-gated while UB is being ironed out: non-admins (no preview token) bounce.
if (blockedForViewer('zen', auth.user, $page.url)) { goto('/play'); return; }
debug = $page.url.searchParams.get('debug') === '1';
let h;
let cancelled = false; // guard the async load against an early unmount
(async () => {
// Don't decide the gate until we actually KNOW who's here. A cold load / hard
// refresh / direct link mounts before auth has revalidated, so auth.user is still
// null and an admin would be wrongly bounced. Revalidate first, THEN gate.
if (!auth.ready) { try { await refreshAuth(); } catch { /* offline → treat as gated */ } }
if (cancelled) return;
// Dev-gated while UB is being ironed out: non-admins (no preview token) bounce.
if (blockedForViewer('zen', auth.user, $page.url)) { goto('/play'); return; }
debug = $page.url.searchParams.get('debug') === '1';
try {
// WebGL guard — fall back to a warm card rather than a blank canvas.
const t = document.createElement('canvas');
@@ -88,27 +93,20 @@
<input type="range" min="-3.15" max="3.15" step="0.01" bind:value={dbg.yaw} oninput={apply} /></label>
<label>pitch <span>{dbg.pitch.toFixed(2)}</span>
<input type="range" min="-0.6" max="0.6" step="0.01" bind:value={dbg.pitch} oninput={apply} /></label>
<label>scale <span>{dbg.scale.toFixed(2)}</span>
<input type="range" min="0.3" max="2.5" step="0.05" bind:value={dbg.scale} oninput={apply} /></label>
<hr />
<div class="ph">Tail</div>
<label class="chk"><input type="checkbox" bind:checked={dbg.tailTranslucent} onchange={apply} /> translucent (off = opaque, coherent)</label>
<label>side
<select bind:value={dbg.tailSide} onchange={apply}><option>front</option><option>back</option><option>double</option></select></label>
<label>alphaTest <span>{dbg.tailAlphaTest.toFixed(3)}</span>
<input type="range" min="0" max="0.2" step="0.005" bind:value={dbg.tailAlphaTest} oninput={apply} /></label>
{#if dbg.tailTranslucent}
<label>opacity <span>{dbg.tailOpacity.toFixed(2)}</span>
<input type="range" min="0" max="1" step="0.05" bind:value={dbg.tailOpacity} oninput={apply} /></label>
{/if}
<hr />
<div class="ph">Fins</div>
<div class="ph">Fins &amp; tail</div>
<label class="chk"><input type="checkbox" bind:checked={dbg.finTranslucent} onchange={apply} /> translucent (off = opaque, coherent)</label>
<label>side
<select bind:value={dbg.finSide} onchange={apply}><option>front</option><option>back</option><option>double</option></select></label>
<label>opacity <span>{dbg.finOpacity.toFixed(2)}</span>
<input type="range" min="0" max="1" step="0.05" bind:value={dbg.finOpacity} oninput={apply} /></label>
<label>alphaTest <span>{dbg.finAlphaTest.toFixed(3)}</span>
<input type="range" min="0" max="0.2" step="0.005" bind:value={dbg.finAlphaTest} oninput={apply} /></label>
<input type="range" min="0" max="0.9" step="0.01" bind:value={dbg.finAlphaTest} oninput={apply} /></label>
{#if dbg.finTranslucent}
<label>opacity <span>{dbg.finOpacity.toFixed(2)}</span>
<input type="range" min="0" max="1" step="0.05" bind:value={dbg.finOpacity} oninput={apply} /></label>
{/if}
<hr />
<label class="chk"><input type="checkbox" bind:checked={dbg.paused} onchange={apply} /> freeze frame</label>