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>
96 lines
3.8 KiB
HTML
96 lines
3.8 KiB
HTML
<!DOCTYPE html>
|
|
<html>
|
|
<head><title>mux probe</title></head>
|
|
<body>
|
|
<canvas id="c" width="1" height="1"></canvas>
|
|
<pre id="out">running…</pre>
|
|
<script type="module">
|
|
import { Show } from '/src/Show.js';
|
|
import { FeatureTrack } from '/src/audio/FeatureTrack.js';
|
|
import { synthesizeSectioned } from '/src/audio/synth.js';
|
|
import { generateLook } from '/src/look/LookGenerator.js';
|
|
import { Exporter } from '/src/export/Exporter.js';
|
|
|
|
const out = document.getElementById('out');
|
|
const log = (m) => { out.textContent += m + '\n'; console.log(m); };
|
|
|
|
// ---- minimal mp4 box walker: report per-track sample counts and stts ----
|
|
function probe(ab) {
|
|
const buf = new DataView(ab);
|
|
const u8 = new Uint8Array(ab);
|
|
const str = (o, n) => String.fromCharCode(...u8.subarray(o, o + n));
|
|
const report = [];
|
|
let cur = null;
|
|
function walk(start, end) {
|
|
let off = start;
|
|
while (off + 8 <= end) {
|
|
let size = buf.getUint32(off);
|
|
const type = str(off + 4, 4);
|
|
let hdr = 8;
|
|
if (size === 1) { size = Number(buf.getBigUint64(off + 8)); hdr = 16; }
|
|
if (size === 0) size = end - off;
|
|
if (['moov', 'trak', 'mdia', 'minf', 'stbl'].includes(type)) {
|
|
if (type === 'trak') { cur = {}; report.push(cur); }
|
|
walk(off + hdr, off + size);
|
|
} else {
|
|
const s = off + hdr;
|
|
if (type === 'mdhd' && cur) {
|
|
cur.timescale = buf.getUint32(s + 12);
|
|
cur.duration = buf.getUint32(s + 16);
|
|
}
|
|
if (type === 'hdlr' && cur) cur.kind = str(s + 8, 4);
|
|
if (type === 'stsd' && cur) cur.format = str(s + 12, 4);
|
|
if (type === 'stts' && cur) {
|
|
const n = buf.getUint32(s + 4);
|
|
let samples = 0, total = 0;
|
|
const rows = [];
|
|
for (let i = 0; i < n; i++) {
|
|
const c = buf.getUint32(s + 8 + i * 8);
|
|
const d = buf.getUint32(s + 12 + i * 8);
|
|
rows.push([c, d]); samples += c; total += c * d;
|
|
}
|
|
cur.stts = { entries: n, samples, total, rows: rows.slice(0, 12) };
|
|
}
|
|
if (type === 'stsz' && cur) cur.sampleCount = buf.getUint32(s + 8);
|
|
}
|
|
off += size;
|
|
}
|
|
}
|
|
walk(0, ab.byteLength);
|
|
return report;
|
|
}
|
|
|
|
const q = new URLSearchParams(location.search);
|
|
const PRESET = q.get('preset') || '720p';
|
|
const SECONDS = Number(q.get('seconds') || 8);
|
|
|
|
const fps = 60;
|
|
const show = new Show({ canvas: document.getElementById('c'), width: 640, height: 360, fps });
|
|
const buffer = synthesizeSectioned({ bpm: 120, duration: SECONDS, changeAt: SECONDS / 2 });
|
|
const track = FeatureTrack.fromAudioBuffer(buffer, { fps });
|
|
show.track = track;
|
|
show.engine.timeline.setDuration(track.duration);
|
|
show.look = generateLook(track, { seed: 7 });
|
|
show.audioBuffer = buffer;
|
|
show.arc = new (await import('/src/look/ArcDriver.js')).ArcDriver(show.look, track);
|
|
|
|
log(`frameCount ${show.frameCount} · fps ${fps} · audio ${buffer.sampleRate}Hz ${buffer.numberOfChannels}ch ${buffer.duration.toFixed(2)}s`);
|
|
|
|
const ex = new Exporter(show);
|
|
try {
|
|
const t0 = performance.now();
|
|
const blob = await ex.export({ preset: PRESET, onProgress: () => {} });
|
|
log(`export ${(performance.now() - t0) / 1000 | 0}s · ${(blob.size / 1e6).toFixed(2)} MB · warnings: ${JSON.stringify(ex.warnings)}`);
|
|
log(`frameStats ${JSON.stringify(ex.frameStats)}`);
|
|
const rows = probe(await blob.arrayBuffer());
|
|
for (const r of rows) log(JSON.stringify(r));
|
|
log(`EXPECTED video samples: ${show.frameCount}`);
|
|
} catch (e) {
|
|
log('THREW: ' + e.stack);
|
|
}
|
|
log('DONE');
|
|
window.__DONE__ = true;
|
|
</script>
|
|
</body>
|
|
</html>
|