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>
80 lines
3.4 KiB
JavaScript
80 lines
3.4 KiB
JavaScript
// Minimal family: a single ribbon of light draping across an almost empty
|
|
// frame. The curve is a closed-form travelling wave, so it is seek-exact; it is
|
|
// drawn by sampling the polyline and taking the minimum distance, which reads as
|
|
// one fluid strand rather than a repeated motif. Most of the frame is negative
|
|
// space — what an intro or a breakdown needs.
|
|
|
|
export const silkRibbon = {
|
|
name: 'Silk Ribbon',
|
|
family: 'minimal',
|
|
kind: 'fragment',
|
|
// Personality: see look/Personality.js.
|
|
// A soft drape of light. Grain furs the one thing it is made of.
|
|
texture: 0,
|
|
traits: ['camera', 'style'],
|
|
|
|
params: {
|
|
thickness: { type: 'float', range: [0.008, 0.08], default: 0.03, uniform: 'u_thickness' },
|
|
wave: { type: 'float', range: [0.1, 2.5], default: 1.1, uniform: 'u_wave' },
|
|
sway: { type: 'float', range: [0.05, 0.6], default: 0.25, uniform: 'u_sway', bias: 'motion' },
|
|
glow: { type: 'float', range: [0, 1.5], default: 0.5, uniform: 'u_glow', bias: 'energy' },
|
|
speed: { type: 'float', range: [0.02, 0.4], default: 0.12, uniform: 'u_speed', bias: 'motion', rate: true },
|
|
spread: { type: 'float', range: [0.5, 1.2], default: 0.9, uniform: 'u_spread' },
|
|
palette: { type: 'palette', count: 4 },
|
|
},
|
|
|
|
reactive: {
|
|
glow: { feature: 'beat', amount: 0.3, response: 'smooth' },
|
|
wave: { feature: 'bandLow', amount: 0.25, response: 'smooth' },
|
|
},
|
|
|
|
shader: `
|
|
// Nearest distance from p to a fixed travelling strand.
|
|
vec4 scene(vec2 uv, vec2 p) {
|
|
float t = u_time * u_speed + u_seed;
|
|
p = sigCamera(p);
|
|
|
|
// Bare wash so the frame is never black.
|
|
vec3 col = pal(0) * 0.04;
|
|
|
|
// Two nested strands, offset in phase so the ribbon reads as a drape with a
|
|
// folded edge rather than a single hairline.
|
|
for (int k = 0; k < 2; k++) {
|
|
float fk = float(k);
|
|
float ph = fk * 2.2 + t * 0.3;
|
|
float best = 1e9;
|
|
|
|
// lint: fixed-cost — this samples the curve at a fixed resolution, so
|
|
// there is no param to break on. Cost is governed by the 4K budget gate.
|
|
for (int n = 0; n < 48; n++) {
|
|
float uu = (float(n) + 0.5) / 48.0;
|
|
float x = (uu - 0.5) * 2.0 * u_spread;
|
|
float y = sin(uu * 6.28318530718 * u_wave + ph) * 0.28
|
|
+ sin(uu * 13.1 - t * 0.8 + fk) * 0.06
|
|
+ u_sway * 0.4 * sin(t * 0.6 + fk);
|
|
float d = length(p - vec2(x, y));
|
|
best = min(best, d);
|
|
}
|
|
|
|
// TRAIT style: the ribbon is drawn in the track's hand. u_sigLine sets
|
|
// how wide the strand is, u_sigSoft how fast it falls off — a sharp
|
|
// track gets a filament with a defined edge, a soft one gets a haze.
|
|
// This is the scene's whole art direction, so it is not scaled by any
|
|
// of its own params: there is no setting at which the track stops
|
|
// showing.
|
|
float w = u_thickness * mix(0.55, 1.7, sat(u_sigLine));
|
|
float falloff = mix(2.6, 0.9, sat(u_sigSoft));
|
|
vec3 hc = pal(int(fk) % 3);
|
|
col += hc * exp(-pow(best / w, falloff * 2.0));
|
|
col += pal(3) * exp(-best * best * 60.0) * u_glow * 0.6;
|
|
}
|
|
|
|
// Knot the ribbon just below frame centre so it has somewhere to pull from.
|
|
float knot = length(p - vec2(0.0, 0.1));
|
|
col = mix(col, pal(2), exp(-knot * knot * 30.0) * u_glow * 0.5);
|
|
|
|
col += sigGrain(uv);
|
|
return vec4(col, 1.0);
|
|
}
|
|
`,
|
|
}; |