Classic Wave, Silk Ribbon, Kaleido Tunnel and Slow Orb all declared the `style` trait and honoured it with `col += sigGrain(uv)` and nothing else. A declared trait is a contract — the disqualification rule in Personality.js is the only thing keeping off-design scenes out of a track — so honouring it with dirt meant these four could not take the `texture: 0` opt-out the grain work introduced. Phase 9 measured their style response at exactly 0. They sat at texture: 0.35 as a stopgap, which kept speckle on the library's cleanest scenes purely to keep a gate green. Each already had the knob; it just was not wired to the track: - Classic Wave contrasts its wave through a bare smoothstep(0.2, 0.8). The transition width now comes from u_sigSoft and u_sigLine, centred on 0.5 so changing the hand does not change the exposure. Crest concentration is driven separately by u_sigLine, because a track is free to sample the scene's own u_softness at zero and the art direction must still show. - Silk Ribbon's strand width is u_sigLine and its falloff exponent u_sigSoft: a sharp track gets a filament with a defined edge, a soft one a haze. - Kaleido Tunnel drew its grid against a bare 0.42 — a line weight with no name. It is now a weight and a feather, both from the track. - Slow Orb's body edge multiplies the scene's softness by the video's, and gains a sigEdge rim so a sharp-handed track gets a defined limb. All four are now texture: 0. Style response measured against a maximally soft-handed versus maximally sharp-handed personality: Classic Wave 111, Silk Ribbon 216, Kaleido Tunnel 206, Slow Orb 102, out of 255 — previously 0. 104/104 checks pass including the slow set. See SIDE-QUESTS.md §1. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
61 lines
2.5 KiB
HTML
61 lines
2.5 KiB
HTML
<!DOCTYPE html>
|
|
<html>
|
|
<head><title>play test</title></head>
|
|
<body>
|
|
<video id="v" controls playsinline></video>
|
|
<div id="out">running…</div>
|
|
<script type="module">
|
|
const out = document.getElementById('out');
|
|
const log = (m) => { out.textContent += m + '\n'; };
|
|
const v = document.getElementById('v');
|
|
v.src = '/export.mp4';
|
|
|
|
const times = [];
|
|
const stamp = (mediaTime, now) => times.push({ mt: mediaTime, t: now });
|
|
function track() {
|
|
v.requestVideoFrameCallback((now, md) => {
|
|
stamp(md.mediaTime, now);
|
|
track();
|
|
});
|
|
}
|
|
track();
|
|
let audioTimes = [];
|
|
const ap = (e) => { if (audioTimes.length < 200) audioTimes.push({ ct: v.currentTime, t: performance.now() }); };
|
|
setInterval(ap, 30);
|
|
|
|
v.onplaying = () => {
|
|
log('playing…');
|
|
setTimeout(async () => {
|
|
const dur = v.duration;
|
|
log(`video.duration = ${dur.toFixed(3)}s`);
|
|
await new Promise((r) => setTimeout(r, 2500));
|
|
const end = performance.now();
|
|
const pts = times.filter((x) => x.t <= end);
|
|
const deltas = pts.slice(1).map((x, i) => x.mt - pts[i].mt);
|
|
log(`presented frames in window: ${pts.length}`);
|
|
const mean = deltas.reduce((a, b) => a + b, 0) / deltas.length;
|
|
const variance = deltas.reduce((a, b) => a + (b - mean) ** 2, 0) / deltas.length;
|
|
log(`mean inter-frame mediaTime delta: ${mean.toFixed(5)}s (60fps would be ${(1/60).toFixed(5)})`);
|
|
log(`variance: ${variance.toFixed(8)}`);
|
|
log(`min/max: ${Math.min(...deltas).toFixed(5)} / ${Math.max(...deltas).toFixed(5)}`);
|
|
// A/V sync: compare media time progression to wall time
|
|
if (pts.length > 10) {
|
|
const wallSpan = (pts[pts.length-1].t - pts[0].t) / 1000;
|
|
const mediaSpan = pts[pts.length-1].mt - pts[0].mt;
|
|
log(`wall ${wallSpan.toFixed(2)}s vs media ${mediaSpan.toFixed(2)}s → play rate ${(mediaSpan/wallSpan).toFixed(3)}`);
|
|
}
|
|
const aEnd = audioTimes[audioTimes.length-1];
|
|
const aStart = audioTimes[0];
|
|
if (aStart && aEnd) {
|
|
log(`audio: currentTime ${aStart.ct.toFixed(2)}→${aEnd.ct.toFixed(2)} over ${((aEnd.t-aStart.t)/1000).toFixed(2)}s → rate ${((aEnd.ct-aStart.ct)/((aEnd.t-aStart.t)/1000)).toFixed(3)}`);
|
|
}
|
|
log('DONE');
|
|
window.__PLAY_DONE__ = true;
|
|
}, 500);
|
|
};
|
|
await new Promise((resolve, reject) => { v.onerror = () => reject(new Error('video error')); setTimeout(resolve, 30000); });
|
|
try { await v.play(); } catch (e) { log('play() threw: ' + e.message); }
|
|
</script>
|
|
</body>
|
|
</html>
|