diff --git a/flow-state/src/engine/shader-contract.js b/flow-state/src/engine/shader-contract.js index e0a701c..2288978 100644 --- a/flow-state/src/engine/shader-contract.js +++ b/flow-state/src/engine/shader-contract.js @@ -452,6 +452,33 @@ float inkMask(float d, vec2 uv) { return clamp(max(fillA, line), 0.0, 1.0); } +/** + * Outline only, in the song's hand. The drop-in replacement for sigEdge. + * + * sigEdge draws a line at the track's line weight; this draws it at the + * identity's, which is the same idea one layer up. Kept separate from inkMask + * because a scene that only ever wanted an edge should not suddenly acquire a + * fill when it migrates. + */ +float inkStroke(float d) { + float soft = mix(0.03, 0.0015, clamp(u_inkEdge, 0.0, 1.0)); + float w = 0.004 + u_inkWeight * 0.055; + return smoothstep(w + soft, w - soft, abs(d)); +} + +/** + * The protagonist as a filled, inked mask at a point. Replaces sigForm. + * + * Same signature as the thing it supersedes so the substitution is mechanical + * across the library — see MIGRATION.md. uv is recomputed here rather than + * passed, so the call site does not have to change shape. + */ +float castForm(vec2 p, vec2 centre, float size) { + float s = max(size, 1e-3); + vec2 uv = vec2(p.x / u_aspect, p.y) * 0.5 + 0.5; + return inkMask(castMain((p - centre) / s) * s, uv); +} + /** The track's value structure. Off unless the identity asked for it. */ vec3 inkValue(vec3 col) { if (u_inkPosterize < 1.5) return col; diff --git a/flow-state/src/scenes/shader/analog-wow.js b/flow-state/src/scenes/shader/analog-wow.js index 4d87133..8cf2ce0 100644 --- a/flow-state/src/scenes/shader/analog-wow.js +++ b/flow-state/src/scenes/shader/analog-wow.js @@ -17,6 +17,7 @@ export const analogWow = { family: 'glitch', kind: 'fragment', texture: 0.9, + consumes: ['ink'], traits: ['camera', 'style'], params: { @@ -46,7 +47,7 @@ vec3 programme(vec2 q, float t) { col += pal(4) * smoothstep(0.85, 1.0, sat(0.5 + 0.5 * sin(q.x * 3.0 - t * 1.3))) * 0.25; // The boundary between bands is drawn in the track's weight, so even a // damaged signal is damaged in the video's own hand. - col += pal(4) * sigEdge(fract(band) - 0.5) * (0.15 + u_sigLine * 0.9); + col += pal(4) * inkStroke(fract(band) - 0.5) * (0.15 + u_sigLine * 0.9); return col * 0.7; } @@ -92,7 +93,7 @@ vec4 scene(vec2 uv, vec2 p) { col = mix(col, pal(2) * hash12(vec2(uv.x * 300.0, floor(t * 12.0))), sw * 0.6); col += sigGrain(uv); - return vec4(col, 1.0); + return vec4(inkValue(col), 1.0); } `, }; diff --git a/flow-state/src/scenes/shader/apollonian-gasket.js b/flow-state/src/scenes/shader/apollonian-gasket.js index d38ec64..1325816 100644 --- a/flow-state/src/scenes/shader/apollonian-gasket.js +++ b/flow-state/src/scenes/shader/apollonian-gasket.js @@ -14,6 +14,7 @@ export const apollonianGasket = { kind: 'fragment', // Drawn geometry; grain only muddies the small discs. texture: 0.3, + consumes: ['ink'], traits: ['camera', 'style'], params: { @@ -88,14 +89,14 @@ vec4 scene(vec2 uv, vec2 p) { vec3 col = pal(0) * 0.06; col += palRamp(gen) * edge * (0.55 + u_rim * 0.7); col += palRamp(gen + 0.25) * glow * u_rim * 0.4; - col += pal(4) * sigEdge(d) * 0.5; + col += pal(4) * inkStroke(d) * 0.5; // Fill the interiors faintly so the discs are objects and not just outlines. col += palRamp(gen + 0.5) * smoothstep(0.0, -0.06, d) * 0.12; col *= 0.7 + 0.3 * exp(-dot(p, p) * 0.2); col += sigGrain(uv); - return vec4(col, 1.0); + return vec4(inkValue(col), 1.0); } `, }; diff --git a/flow-state/src/scenes/shader/aqueduct-march.js b/flow-state/src/scenes/shader/aqueduct-march.js index 17dcdde..7cf9a48 100644 --- a/flow-state/src/scenes/shader/aqueduct-march.js +++ b/flow-state/src/scenes/shader/aqueduct-march.js @@ -21,6 +21,7 @@ export const aqueductMarch = { family: 'structural', kind: 'fragment', texture: 0.9, + consumes: ['cast', 'ink'], traits: ['shape', 'camera', 'space', 'style'], params: { @@ -61,7 +62,7 @@ float tier(vec2 q, float bays, float top, float bottom, float shift) { // The opening sits low in the tier, leaving a solid band above it to carry // the next tier — which is the whole structural point of an aqueduct. - float hole = sigShape(local) * min(cw, ch) * k; + float hole = castMain(local) * min(cw, ch) * k; return smoothstep(-0.004, 0.004, hole); } @@ -110,7 +111,7 @@ vec4 scene(vec2 uv, vec2 p) { vec3 masonry = mix(pal(2) * 0.35, pal(4) * 0.8, sat(q.x * 0.4 + 0.5)); masonry *= 0.7 + (0.15 + u_sigLine * 0.5) * course; masonry += pal(3) * u_sunlight * 0.22 * sat(q.x * 0.5 + 0.5); - masonry += pal(4) * sigEdge(0.5 - abs(fract(q.x * u_bays) - 0.5) - 0.03) * (0.1 + u_sigLine * 0.9); + masonry += pal(4) * inkStroke(0.5 - abs(fract(q.x * u_bays) - 0.5) - 0.03) * (0.1 + u_sigLine * 0.9); float hazed = mix(1.0, 0.55, fk * u_recede); col = mix(col, masonry * hazed, stone * base); @@ -118,7 +119,7 @@ vec4 scene(vec2 uv, vec2 p) { col = sigAir(col, p, smoothstep(-0.2, 0.9, p.y - horizon)); col += sigGrain(uv); - return vec4(col, 1.0); + return vec4(inkValue(col), 1.0); } `, }; diff --git a/flow-state/src/scenes/shader/aurora-veil.js b/flow-state/src/scenes/shader/aurora-veil.js index 2ec7c8b..5ade7ff 100644 --- a/flow-state/src/scenes/shader/aurora-veil.js +++ b/flow-state/src/scenes/shader/aurora-veil.js @@ -13,6 +13,7 @@ export const auroraVeil = { name: 'Aurora Veil', family: 'flow', kind: 'fragment', + consumes: ['ink'], traits: ['camera', 'space', 'style'], params: { @@ -73,7 +74,7 @@ vec4 scene(vec2 uv, vec2 p) { col = sigAir(col, p, smoothstep(0.0, 1.6, abs(p.x))); col += sigGrain(uv); - return vec4(col, 1.0); + return vec4(inkValue(col), 1.0); } `, }; diff --git a/flow-state/src/scenes/shader/balance-stack.js b/flow-state/src/scenes/shader/balance-stack.js index 538a1f7..235cdaf 100644 --- a/flow-state/src/scenes/shader/balance-stack.js +++ b/flow-state/src/scenes/shader/balance-stack.js @@ -17,6 +17,7 @@ export const balanceStack = { name: 'Balance Stack', family: 'minimal', kind: 'fragment', + consumes: ['cast', 'ink'], traits: ['shape', 'camera', 'space', 'style'], params: { @@ -102,7 +103,7 @@ vec4 scene(vec2 uv, vec2 p) { x += u_lean * size * 1.6 + sin(t * 1.3 + fi * 0.7) * 0.012 * fi; vec2 at = vec2(x, y); - float d = sigShape((p - at) / size) * size; + float d = castMain((p - at) / size) * size; // Body: shaded across the form, so it has a lit side and a dark side. float lit = sat(dot(normalize(p - at + 1e-4), lightDir) * 0.5 + 0.5); @@ -110,13 +111,13 @@ vec4 scene(vec2 uv, vec2 p) { col = mix(col, body, smoothstep(0.004, -0.004, d)); // Rim: the sun catching the edge, in the track's line weight. - col += pal(4) * sigEdge(d) * (0.3 + u_rim * 0.9) * (0.4 + lit * 0.8); + col += pal(4) * inkStroke(d) * (0.3 + u_rim * 0.9) * (0.4 + lit * 0.8); y += size; } col = sigAir(col, p, smoothstep(0.1, 1.5, abs(p.x) * 0.7 + sat(above * 0.5))); col += sigGrain(uv); - return vec4(col, 1.0); + return vec4(inkValue(col), 1.0); } `, }; diff --git a/flow-state/src/scenes/shader/block-mosh.js b/flow-state/src/scenes/shader/block-mosh.js index c4f2692..f62566d 100644 --- a/flow-state/src/scenes/shader/block-mosh.js +++ b/flow-state/src/scenes/shader/block-mosh.js @@ -11,6 +11,7 @@ export const blockMosh = { family: 'glitch', kind: 'fragment', // Personality: see look/Personality.js. + consumes: ['ink'], traits: ['camera', 'style'], params: { @@ -74,7 +75,7 @@ vec4 scene(vec2 uv, vec2 p) { col += pal(2) * u_burst * (0.5 + 0.5 * blk) * (0.5 + 0.5 * beat); col += sigGrain(uv); - return vec4(col, 1.0); + return vec4(inkValue(col), 1.0); } `, }; diff --git a/flow-state/src/scenes/shader/cargo-belt.js b/flow-state/src/scenes/shader/cargo-belt.js index a97c5f2..75f382e 100644 --- a/flow-state/src/scenes/shader/cargo-belt.js +++ b/flow-state/src/scenes/shader/cargo-belt.js @@ -15,6 +15,7 @@ export const cargoBelt = { kind: 'fragment', // Takes the track's surface grain, but lightly — this is drawn, not filmed. texture: 0.4, + consumes: ['cast', 'ink'], traits: ['shape', 'camera', 'style'], params: { @@ -65,11 +66,11 @@ vec4 scene(vec2 uv, vec2 p) { if (rnd > u_gap) { vec2 local = vec2((withinCell - 0.5) * 2.0, dy / max(span * 0.5, 1e-3)); float size = u_crateSize * (0.7 + rnd * 0.5); - float d = sigShape(local / max(size, 1e-3)) * size; + float d = castMain(local / max(size, 1e-3)) * size; vec3 crateColor = pal(int(mod(cell + fi, 4.0)) + 1); col = mix(col, crateColor * (0.35 + rnd * 0.5), smoothstep(0.02, -0.02, d)); - col += crateColor * sigEdge(d) * (0.4 + u_lamp * 0.5); + col += crateColor * inkStroke(d) * (0.4 + u_lamp * 0.5); // Lamp on a minority of crates, pulsing on the beat. Local, not // whole-frame: a per-crate blink is not a flash. @@ -82,7 +83,7 @@ vec4 scene(vec2 uv, vec2 p) { } col += sigGrain(uv); - return vec4(col, 1.0); + return vec4(inkValue(col), 1.0); } `, }; diff --git a/flow-state/src/scenes/shader/cell-divide.js b/flow-state/src/scenes/shader/cell-divide.js index d2fdfa5..2f78817 100644 --- a/flow-state/src/scenes/shader/cell-divide.js +++ b/flow-state/src/scenes/shader/cell-divide.js @@ -15,6 +15,7 @@ export const cellDivide = { name: 'Cell Divide', family: 'organic', kind: 'fragment', + consumes: ['cast', 'ink'], traits: ['shape', 'camera', 'style'], params: { @@ -95,13 +96,13 @@ vec4 scene(vec2 uv, vec2 p) { // Nucleus, in the track's signature form. if (u_nucleus > 0.01) { float size = u_nucleus * 0.35; - float d = sigShape((p - nearestAt) / max(size, 1e-3)) * size; + float d = castMain((p - nearestAt) / max(size, 1e-3)) * size; col = mix(col, pal(1), smoothstep(0.008, -0.008, d) * 0.85); - col += pal(2) * sigEdge(d) * (0.35 + u_glow * 0.4); + col += pal(2) * inkStroke(d) * (0.35 + u_glow * 0.4); } col += sigGrain(uv); - return vec4(col, 1.0); + return vec4(inkValue(col), 1.0); } `, }; diff --git a/flow-state/src/scenes/shader/circuit-bloom.js b/flow-state/src/scenes/shader/circuit-bloom.js index ba67a38..6202f0c 100644 --- a/flow-state/src/scenes/shader/circuit-bloom.js +++ b/flow-state/src/scenes/shader/circuit-bloom.js @@ -15,6 +15,7 @@ export const circuitBloom = { kind: 'fragment', // Takes the track's surface grain, but lightly — this is drawn, not filmed. texture: 0.4, + consumes: ['cast', 'ink'], traits: ['shape', 'camera', 'style'], params: { @@ -91,13 +92,17 @@ vec4 scene(vec2 uv, vec2 p) { // Pads sit where both traces meet, stamped in the signature form. if (horizontal > 0.5 && vertical > 0.5 && rnd2 > 1.0 - u_pads) { - float d = sigShape(f / max(u_padSize, 1e-3)) * u_padSize; + float d = castMain(f / max(u_padSize, 1e-3)) * u_padSize; col += pal(2) * smoothstep(0.01, -0.01, d) * reach * 0.7; - col += pal(3) * sigEdge(d) * reach * (0.4 + u_pulse * 0.4); + // Filled as well as stroked. At the bottom of the 'grown' range the pads + // were a hairline and nothing else, and the ink's stroke can be thinner + // than the edge it replaced — which left the frame empty at grown=0.25. + col = mix(col, pal(3), inkMask(d, uv) * reach * 0.6); + col += pal(3) * inkStroke(d) * reach * (0.4 + u_pulse * 0.4); } col += sigGrain(uv); - return vec4(col, 1.0); + return vec4(inkValue(col), 1.0); } `, }; diff --git a/flow-state/src/scenes/shader/classic-wave.js b/flow-state/src/scenes/shader/classic-wave.js index 34111c0..3706ebb 100644 --- a/flow-state/src/scenes/shader/classic-wave.js +++ b/flow-state/src/scenes/shader/classic-wave.js @@ -9,6 +9,7 @@ export const classicWave = { // Personality: see look/Personality.js. // Smooth concentric colour: grain only mutes the ramp it is built on. texture: 0, + consumes: ['cast', 'ink'], traits: ['shape', 'camera', 'style'], params: { @@ -38,7 +39,7 @@ vec4 scene(vec2 uv, vec2 p) { p = sigCamera(p); // The rings take the track's signature form: round tracks get circles, // hexagonal tracks get hexagonal rings, and it costs one call. - float d = sigShape(p) + 1.0; + float d = castMain(p) + 1.0; float angle = atan(p.y, p.x); float t = u_time * u_speed + u_seed; @@ -66,7 +67,7 @@ vec4 scene(vec2 uv, vec2 p) { col *= 0.6 + 0.4 * (1.0 - smoothstep(0.8, 1.8, d)); col += sigGrain(uv); - return vec4(col, 1.0); + return vec4(inkValue(col), 1.0); } `, }; diff --git a/flow-state/src/scenes/shader/curl-flow.js b/flow-state/src/scenes/shader/curl-flow.js index da3a808..d45b3ce 100644 --- a/flow-state/src/scenes/shader/curl-flow.js +++ b/flow-state/src/scenes/shader/curl-flow.js @@ -9,6 +9,7 @@ export const curlFlow = { family: 'flow', kind: 'fragment', // Personality: see look/Personality.js. + consumes: ['ink'], traits: ['camera', 'space', 'style'], params: { @@ -81,7 +82,7 @@ vec4 scene(vec2 uv, vec2 p) { col *= 0.6 + 0.4 * exp(-dot(p, p) * 0.35); col = sigAir(col, p, smoothstep(0.0, 1.7, length(p))); col += sigGrain(uv); - return vec4(col, 1.0); + return vec4(inkValue(col), 1.0); } `, }; diff --git a/flow-state/src/scenes/shader/data-aisle.js b/flow-state/src/scenes/shader/data-aisle.js index 0eb5cbc..a417169 100644 --- a/flow-state/src/scenes/shader/data-aisle.js +++ b/flow-state/src/scenes/shader/data-aisle.js @@ -12,6 +12,7 @@ export const dataAisle = { family: 'structural', kind: 'fragment', texture: 0.6, + consumes: ['ink'], traits: ['camera', 'space', 'style'], params: { @@ -62,7 +63,7 @@ vec3 wall(vec2 at, float side, float t, float lightAmount) { float bright = mix(0.25, 1.0, busy); col += palRamp(0.55 + id * 0.35) * led * bright * lightAmount * 1.8; - col += pal(4) * sigEdge(seam - seamW) * (0.15 + u_sigLine * 0.9); + col += pal(4) * inkStroke(seam - seamW) * (0.15 + u_sigLine * 0.9); return col; } @@ -106,7 +107,7 @@ vec4 scene(vec2 uv, vec2 p) { col = mix(col, pal(1) * 0.12, sat(far * u_haze)); col = sigAir(col, p, far); col += sigGrain(uv); - return vec4(col, 1.0); + return vec4(inkValue(col), 1.0); } `, }; diff --git a/flow-state/src/scenes/shader/droste-feedback.js b/flow-state/src/scenes/shader/droste-feedback.js index 729fc70..afa37dd 100644 --- a/flow-state/src/scenes/shader/droste-feedback.js +++ b/flow-state/src/scenes/shader/droste-feedback.js @@ -22,6 +22,7 @@ export const drosteFeedback = { family: 'glitch', kind: 'fragment', texture: 0.5, + consumes: ['ink'], traits: ['camera', 'style'], params: { @@ -54,7 +55,7 @@ vec4 scene(vec2 uv, vec2 p) { vec3 col = pal(0) * 0.05; col += palRamp(0.2 + fbm(p * 2.0 + t, 3) * 0.2) * smoothstep(0.02, -0.02, ring) * (0.5 + u_bloom * 0.8); - col += pal(4) * sigEdge(ring) * (0.3 + u_bloom * 0.9); + col += pal(4) * inkStroke(ring) * (0.3 + u_bloom * 0.9); col += pal(3) * exp(-abs(d) * 9.0) * u_bloom * 0.35; // The loop: read the previous frame from a slightly LARGER, slightly turned @@ -77,7 +78,7 @@ vec4 scene(vec2 uv, vec2 p) { // Vignette, which also stops the corner content being recycled forever. col *= 0.75 + 0.25 * exp(-dot(p, p) * 0.35); col += sigGrain(uv); - return vec4(col, 1.0); + return vec4(inkValue(col), 1.0); } `, }; diff --git a/flow-state/src/scenes/shader/dust-chamber.js b/flow-state/src/scenes/shader/dust-chamber.js index d6e09d3..34d2740 100644 --- a/flow-state/src/scenes/shader/dust-chamber.js +++ b/flow-state/src/scenes/shader/dust-chamber.js @@ -13,6 +13,7 @@ export const dustChamber = { name: 'Dust Chamber', family: 'minimal', kind: 'fragment', + consumes: ['cast', 'ink'], traits: ['shape', 'camera', 'space', 'style'], params: { @@ -77,13 +78,13 @@ vec4 scene(vec2 uv, vec2 p) { float lit = exp(-pow((at.x - axis) / max(halfWidth, 1e-3), 2.0) * 2.0); float pulse = 0.55 + 0.45 * sin(t * 2.0 + s * 3.0); - float m = sigForm(p, at, u_moteSize * (0.6 + hash11(s + 3.3))); + float m = castForm(p, at, u_moteSize * (0.6 + hash11(s + 3.3))); col += pal(3) * m * lit * pulse * (0.5 + beam); } col = sigAir(col, p, smoothstep(0.0, 1.5, length(p))); col += sigGrain(uv); - return vec4(col, 1.0); + return vec4(inkValue(col), 1.0); } `, }; diff --git a/flow-state/src/scenes/shader/eclipse-field.js b/flow-state/src/scenes/shader/eclipse-field.js index f093bff..d2d1f94 100644 --- a/flow-state/src/scenes/shader/eclipse-field.js +++ b/flow-state/src/scenes/shader/eclipse-field.js @@ -18,7 +18,12 @@ export const eclipseField = { kind: 'fragment', // Crisp line work: the track's surface grain would only fur the edges. texture: 0, - traits: ['shape', 'camera', 'space', 'style'], + consumes: ['cast', 'ink'], + // No `style`: this scene opts out of surface grain (texture: 0) and its only + // other style evidence was sigEdge, which the migration replaced with the + // ink. Traits and artifacts are different layers, so taking the ink does not + // earn the trait back. See MIGRATION.md. + traits: ['shape', 'camera', 'space'], params: { size: { type: 'float', range: [0.2, 0.85], default: 0.45, uniform: 'u_size' }, @@ -47,7 +52,7 @@ vec4 scene(vec2 uv, vec2 p) { vec2 discAt = vec2(0.0, sigHorizonY() * 0.25); // Distance to the occluder's edge, in the track's form. - float d = sigShape((p - discAt) / max(u_size, 1e-3)) * u_size; + float d = castMain((p - discAt) / max(u_size, 1e-3)) * u_size; // The source behind it. float toLight = length(p - lightAt); @@ -76,11 +81,11 @@ vec4 scene(vec2 uv, vec2 p) { // The occluder: not pure black, so it reads as an object rather than a hole. col = mix(col, pal(0) * 0.12, smoothstep(0.004, -0.004, d)); - col += pal(3) * sigEdge(d) * (0.4 + u_corona * 0.5); + col += pal(3) * inkStroke(d) * (0.4 + u_corona * 0.5); col = sigAir(col, p, smoothstep(0.0, 1.7, length(p))); col += sigGrain(uv); - return vec4(col, 1.0); + return vec4(inkValue(col), 1.0); } `, }; diff --git a/flow-state/src/scenes/shader/flora.js b/flow-state/src/scenes/shader/flora.js index c44c9aa..3221b06 100644 --- a/flow-state/src/scenes/shader/flora.js +++ b/flow-state/src/scenes/shader/flora.js @@ -12,6 +12,7 @@ export const flora = { family: 'organic', kind: 'fragment', // Personality: see look/Personality.js. + consumes: ['cast', 'ink'], traits: ['shape', 'camera', 'style'], params: { @@ -48,7 +49,7 @@ float petal(vec2 p, vec2 node, vec2 dir, float len, float width) { float across = dot(off, perp); float taper = 0.6 + 0.4 * sat(along / max(len, 1e-3)); vec2 q = vec2(along / max(len, 1e-3), across / max(width * taper, 1e-3)); - float form = sigForm(q * 0.5, vec2(0.0), 0.5); + float form = castForm(q * 0.5, vec2(0.0), 0.5); float clip = smoothstep(0.0, -0.1, along) * smoothstep(len + 0.12, len * 0.72, along); return form * clip; } @@ -105,13 +106,13 @@ vec4 scene(vec2 uv, vec2 p) { for (int k = 0; k < 6; k++) { float a = 6.2831853 * (float(k) + 0.5) / 6.0 + s; vec2 off = vec2(cos(a), sin(a)) * u_leaf * (0.30 + 0.2 * beat); - bloom += sigForm(p - vec2(tipX, tipY), off, u_leaf * 0.5); + bloom += castForm(p - vec2(tipX, tipY), off, u_leaf * 0.5); } col = mix(col, pal(2), sat(bloom) * u_bud * (0.6 + 0.4 * beat)); } col += sigGrain(uv); - return vec4(col, 1.0); + return vec4(inkValue(col), 1.0); } `, }; diff --git a/flow-state/src/scenes/shader/gate-corridor.js b/flow-state/src/scenes/shader/gate-corridor.js index 9d498f5..e470ffb 100644 --- a/flow-state/src/scenes/shader/gate-corridor.js +++ b/flow-state/src/scenes/shader/gate-corridor.js @@ -16,6 +16,7 @@ export const gateCorridor = { kind: 'fragment', // Takes the track's surface grain, but lightly — this is drawn, not filmed. texture: 0.4, + consumes: ['cast', 'ink'], traits: ['shape', 'camera', 'space', 'style'], params: { @@ -62,7 +63,7 @@ vec4 scene(vec2 uv, vec2 p) { float phase = fract((fi / float(u_gates)) + t); float scale = u_aperture * exp(phase * 3.2) * 0.35; - float d = abs(sigShape(q / max(scale, 1e-3)) * max(scale, 1e-3)); + float d = abs(castMain(q / max(scale, 1e-3)) * max(scale, 1e-3)); // Near gates are drawn thicker and brighter: the only depth cue that // matters once the geometry is right. @@ -83,7 +84,7 @@ vec4 scene(vec2 uv, vec2 p) { col = sigAir(col, p, 1.0 - smoothstep(0.0, 1.2, length(q))); col += sigGrain(uv); - return vec4(col, 1.0); + return vec4(inkValue(col), 1.0); } `, }; diff --git a/flow-state/src/scenes/shader/girder-lattice.js b/flow-state/src/scenes/shader/girder-lattice.js index adbebb3..93be173 100644 --- a/flow-state/src/scenes/shader/girder-lattice.js +++ b/flow-state/src/scenes/shader/girder-lattice.js @@ -17,6 +17,7 @@ export const girderLattice = { kind: 'fragment', // Crisp line work: the track's surface grain would only fur the edges. texture: 0, + consumes: ['cast', 'ink'], traits: ['shape', 'camera', 'space', 'style'], params: { @@ -58,7 +59,7 @@ vec4 scene(vec2 uv, vec2 p) { if (above <= 0.001) { col = sigAir(col, p, 1.0); col += sigGrain(uv); - return vec4(col, 1.0); + return vec4(inkValue(col), 1.0); } float best = 1e3; @@ -94,9 +95,9 @@ vec4 scene(vec2 uv, vec2 p) { } if (u_plate > 0.004) { - float pd = sigShape((p - vec2(halfW0, y0)) / u_plate) * u_plate; + float pd = castMain((p - vec2(halfW0, y0)) / u_plate) * u_plate; plateHit = min(plateHit, pd); - pd = sigShape((p - vec2(-halfW0, y0)) / u_plate) * u_plate; + pd = castMain((p - vec2(-halfW0, y0)) / u_plate) * u_plate; plateHit = min(plateHit, pd); } @@ -109,7 +110,7 @@ vec4 scene(vec2 uv, vec2 p) { float steel = smoothstep(weight, weight * 0.3, best); col = mix(col, pal(2) * (0.3 + above * 0.5), steel); - col += pal(3) * sigEdge(best - weight) * 0.4; + col += pal(3) * inkStroke(best - weight) * 0.4; // Joint plates. col = mix(col, pal(4) * 0.7, smoothstep(0.006, -0.006, plateHit)); diff --git a/flow-state/src/scenes/shader/horizon-lines.js b/flow-state/src/scenes/shader/horizon-lines.js index 08ea428..b4e1830 100644 --- a/flow-state/src/scenes/shader/horizon-lines.js +++ b/flow-state/src/scenes/shader/horizon-lines.js @@ -9,6 +9,7 @@ export const horizonLines = { // Personality: see look/Personality.js. // Crisp line work: the track's surface grain would only fur the edges. texture: 0, + consumes: ['ink'], traits: ['space', 'camera', 'style'], params: { @@ -80,7 +81,7 @@ vec4 scene(vec2 uv, vec2 p) { col *= 0.55 + 0.45 * exp(-dot(p, p) * 0.5); col = sigAir(col, p, smoothstep(0.0, 1.6, abs(p.y))); col += sigGrain(uv); - return vec4(col, 1.0); + return vec4(inkValue(col), 1.0); } `, }; diff --git a/flow-state/src/scenes/shader/ink-bleed.js b/flow-state/src/scenes/shader/ink-bleed.js index eefd0d8..a237e15 100644 --- a/flow-state/src/scenes/shader/ink-bleed.js +++ b/flow-state/src/scenes/shader/ink-bleed.js @@ -13,6 +13,7 @@ export const inkBleed = { name: 'Ink Bleed', family: 'organic', kind: 'fragment', + consumes: ['ink'], traits: ['camera', 'space', 'style'], params: { @@ -73,7 +74,7 @@ vec4 scene(vec2 uv, vec2 p) { col = sigAir(col, p, smoothstep(0.0, 1.7, length(p))); col += sigGrain(uv); - return vec4(col, 1.0); + return vec4(inkValue(col), 1.0); } `, }; diff --git a/flow-state/src/scenes/shader/isometric-blocks.js b/flow-state/src/scenes/shader/isometric-blocks.js index 47847d9..e0196d5 100644 --- a/flow-state/src/scenes/shader/isometric-blocks.js +++ b/flow-state/src/scenes/shader/isometric-blocks.js @@ -13,6 +13,7 @@ export const isometricBlocks = { family: 'geometric', kind: 'fragment', texture: 0.6, + consumes: ['cast', 'ink'], traits: ['shape', 'camera', 'style'], params: { @@ -102,7 +103,7 @@ vec4 scene(vec2 uv, vec2 p) { // squashed by the tilt — so every block in the field is the same // silhouette as every other subject in the video. vec2 q = vec2(fx, (p.y - groundY - h) / tilt); - float dTop = sigShape(q / (u_plan * cw)) * (u_plan * cw); + float dTop = castMain(q / (u_plan * cw)) * (u_plan * cw); float topMask = smoothstep(0.004, -0.004, dTop); vec3 top = mix(pal(2), pal(3), sat(h / max(u_height, 1e-3))) * 0.8; @@ -112,12 +113,12 @@ vec4 scene(vec2 uv, vec2 p) { // a flash. float picked = step(0.75, hash12(cell * 3.17 + 5.0)); col += pal(3) * topMask * picked * u_beat * 0.45; - col += pal(4) * sigEdge(dTop) * (0.2 + u_light * 1.2) * step(0.0, dy); + col += pal(4) * inkStroke(dTop) * (0.2 + u_light * 1.2) * step(0.0, dy); } col *= 0.75 + 0.25 * exp(-dot(p, p) * 0.3); col += sigGrain(uv); - return vec4(col, 1.0); + return vec4(inkValue(col), 1.0); } `, }; diff --git a/flow-state/src/scenes/shader/kaleido-tunnel.js b/flow-state/src/scenes/shader/kaleido-tunnel.js index e4313b7..570ee09 100644 --- a/flow-state/src/scenes/shader/kaleido-tunnel.js +++ b/flow-state/src/scenes/shader/kaleido-tunnel.js @@ -8,6 +8,7 @@ export const kaleidoTunnel = { // Personality: see look/Personality.js. // Hard grid lines on flat colour: grain reads as dirt on the lens. texture: 0, + consumes: ['cast', 'ink'], traits: ['shape', 'camera', 'style'], params: { @@ -37,7 +38,7 @@ vec4 scene(vec2 uv, vec2 p) { // Cross-section measured in the signature form: the tunnel mouth is the // track's shape rather than a circle. - float radius = max(sigShape(p) + 1.0, 1e-4); + float radius = max(castMain(p) + 1.0, 1e-4); vec2 folded = kaleido(p, sides); float angle = atan(folded.y, folded.x); @@ -65,7 +66,7 @@ vec4 scene(vec2 uv, vec2 p) { col += pal(3) * (1.0 - fade) * u_glow * 0.6; col += sigGrain(uv); - return vec4(col, 1.0); + return vec4(inkValue(col), 1.0); } `, }; diff --git a/flow-state/src/scenes/shader/karman-street.js b/flow-state/src/scenes/shader/karman-street.js index 81cd7ab..5f854de 100644 --- a/flow-state/src/scenes/shader/karman-street.js +++ b/flow-state/src/scenes/shader/karman-street.js @@ -13,6 +13,7 @@ export const karmanStreet = { family: 'flow', kind: 'fragment', texture: 0.7, + consumes: ['cast', 'ink'], traits: ['shape', 'camera', 'space', 'style'], params: { @@ -92,14 +93,14 @@ vec4 scene(vec2 uv, vec2 p) { col += pal(2) * sat(-vort) * u_glow * 0.5; // The obstacle: still, solid, with the stream piling up on its nose. - float d = sigShape((p - origin) / u_body) * u_body; + float d = castMain((p - origin) / u_body) * u_body; col = mix(col, pal(0) * 0.1, smoothstep(0.005, -0.005, d)); - col += pal(4) * sigEdge(d) * (0.5 + u_glow * 0.5); + col += pal(4) * inkStroke(d) * (0.5 + u_glow * 0.5); col += pal(4) * exp(-abs(d) * 14.0) * smoothstep(0.1, -0.3, p.x - origin.x) * u_glow * 0.35; col = sigAir(col, p, smoothstep(0.0, 1.6, length(p - origin) * 0.7)); col += sigGrain(uv); - return vec4(col, 1.0); + return vec4(inkValue(col), 1.0); } `, }; diff --git a/flow-state/src/scenes/shader/magnet-lines.js b/flow-state/src/scenes/shader/magnet-lines.js index 8f26076..898d516 100644 --- a/flow-state/src/scenes/shader/magnet-lines.js +++ b/flow-state/src/scenes/shader/magnet-lines.js @@ -14,6 +14,7 @@ export const magnetLines = { kind: 'fragment', // Line work. Grain furs it up, so take only a dusting. texture: 0.5, + consumes: ['cast', 'ink'], traits: ['shape', 'camera', 'style'], params: { @@ -77,16 +78,16 @@ vec4 scene(vec2 uv, vec2 p) { // lint: fixed-cost — the same three poles for (int i = 0; i < 3; i++) { vec2 c = poleAt(i, t); - float d = sigShape((p - c) / u_core) * u_core; + float d = castMain((p - c) / u_core) * u_core; vec3 tint = mod(float(i), 2.0) < 0.5 ? pal(3) : pal(4); col = mix(col, tint * 0.25, smoothstep(0.004, -0.004, d)); - col += tint * sigEdge(d) * (0.5 + u_glow * 0.6); + col += tint * inkStroke(d) * (0.5 + u_glow * 0.6); col += tint * exp(-abs(d) * 12.0) * u_glow * 0.25; } col *= 0.7 + 0.3 * exp(-dot(p, p) * 0.3); col += sigGrain(uv); - return vec4(col, 1.0); + return vec4(inkValue(col), 1.0); } `, }; diff --git a/flow-state/src/scenes/shader/neon-city.js b/flow-state/src/scenes/shader/neon-city.js index 2135cf4..c259480 100644 --- a/flow-state/src/scenes/shader/neon-city.js +++ b/flow-state/src/scenes/shader/neon-city.js @@ -12,6 +12,7 @@ export const neonCity = { family: 'structural', kind: 'fragment', // Personality: see look/Personality.js. + consumes: ['ink'], traits: ['space', 'camera', 'style'], params: { @@ -115,7 +116,7 @@ vec4 scene(vec2 uv, vec2 p) { col = sigAir(col, p, smoothstep(0.0, 1.4, length(p))); col += sigGrain(uv); - return vec4(col, 1.0); + return vec4(inkValue(col), 1.0); } `, }; diff --git a/flow-state/src/scenes/shader/pendulum-trace.js b/flow-state/src/scenes/shader/pendulum-trace.js index dd06ecd..5bf490a 100644 --- a/flow-state/src/scenes/shader/pendulum-trace.js +++ b/flow-state/src/scenes/shader/pendulum-trace.js @@ -15,6 +15,7 @@ export const pendulumTrace = { kind: 'fragment', // Fine line work; grain only furs it up. texture: 0.25, + consumes: ['ink'], traits: ['camera', 'style'], params: { @@ -86,7 +87,7 @@ vec4 scene(vec2 uv, vec2 p) { col = max(col, prev(uv) * u_persist); col += sigGrain(uv); - return vec4(col, 1.0); + return vec4(inkValue(col), 1.0); } `, }; diff --git a/flow-state/src/scenes/shader/pitch-shatter.js b/flow-state/src/scenes/shader/pitch-shatter.js index 8f91950..4103d39 100644 --- a/flow-state/src/scenes/shader/pitch-shatter.js +++ b/flow-state/src/scenes/shader/pitch-shatter.js @@ -10,6 +10,7 @@ export const pitchShatter = { family: 'glitch', kind: 'fragment', // Personality: see look/Personality.js. + consumes: ['ink'], traits: ['camera', 'style'], params: { @@ -68,7 +69,7 @@ vec4 scene(vec2 uv, vec2 p) { col += pal(int(mod(slice, 5.0))) * (1.0 - sliceRand) * u_glow * 0.3 * sliceRand; col += sigGrain(uv); - return vec4(col, 1.0); + return vec4(inkValue(col), 1.0); } `, }; \ No newline at end of file diff --git a/flow-state/src/scenes/shader/psychedelic-drift.js b/flow-state/src/scenes/shader/psychedelic-drift.js index 3870571..401accc 100644 --- a/flow-state/src/scenes/shader/psychedelic-drift.js +++ b/flow-state/src/scenes/shader/psychedelic-drift.js @@ -10,6 +10,7 @@ export const psychedelicDrift = { family: 'glitch', kind: 'fragment', // Personality: see look/Personality.js. + consumes: ['cast', 'ink'], traits: ['shape', 'camera', 'style'], params: { @@ -108,7 +109,7 @@ vec4 scene(vec2 uv, vec2 p) { // characters includes the one every other scene is built from. if (kind == 0.0) d = sdStar(sp, size, 5.0 + floor(hash11(s * 1.2) * 3.0)); else if (kind == 1.0) d = sdSmiley(sp, size, u_time, fi); - else d = sigShape(sp / max(size, 1e-3)) * size; + else d = castMain(sp / max(size, 1e-3)) * size; float hit = 0.0; for (int j = 0; j < 5; j++) { @@ -130,7 +131,7 @@ vec4 scene(vec2 uv, vec2 p) { } col += sigGrain(uv); - return vec4(col, 1.0); + return vec4(inkValue(col), 1.0); } `, }; diff --git a/flow-state/src/scenes/shader/pylon-grid.js b/flow-state/src/scenes/shader/pylon-grid.js index 4f10b3a..c3ae5a1 100644 --- a/flow-state/src/scenes/shader/pylon-grid.js +++ b/flow-state/src/scenes/shader/pylon-grid.js @@ -11,6 +11,7 @@ export const pylonGrid = { // Personality: see look/Personality.js. // Takes the track's surface grain, but lightly — this is drawn, not filmed. texture: 0.4, + consumes: ['cast', 'ink'], traits: ['shape', 'space'], params: { @@ -72,7 +73,7 @@ vec4 scene(vec2 uv, vec2 p) { // Crown stamped in the track's signature form. vec2 crown = vec2(cx, yTop); float size = (0.06 + 0.2 * scale) * (1.0 + u_beat * 0.4); - col += pal(3) * sigForm(p, crown, size) * inten * (0.6 + u_pulse); + col += pal(3) * castForm(p, crown, size) * inten * (0.6 + u_pulse); col += pal(int(mod(fr, 4.0))) * exp(-dot(p - crown, p - crown) * 60.0) * u_pulse * depthShade; } } @@ -80,7 +81,7 @@ vec4 scene(vec2 uv, vec2 p) { // Ground haze and the shared air between here and the horizon. col = sigAir(col, p, smoothstep(0.0, 1.6, abs(p.y - hy))); col += sigGrain(uv); - return vec4(col, 1.0); + return vec4(inkValue(col), 1.0); } `, }; \ No newline at end of file diff --git a/flow-state/src/scenes/shader/quasicrystal.js b/flow-state/src/scenes/shader/quasicrystal.js index 47db882..7205dfb 100644 --- a/flow-state/src/scenes/shader/quasicrystal.js +++ b/flow-state/src/scenes/shader/quasicrystal.js @@ -16,6 +16,7 @@ export const quasicrystal = { kind: 'fragment', // Crisp line work: the track's surface grain would only fur the edges. texture: 0, + consumes: ['ink'], traits: ['camera', 'style'], params: { @@ -67,11 +68,11 @@ vec4 scene(vec2 uv, vec2 p) { // Ridges: where the sum crosses zero, which is the aperiodic skeleton. float ridge = 1.0 - abs(shaped); col += pal(4) * pow(sat(ridge), 6.0) * u_glow; - col += pal(3) * sigEdge(shaped) * u_glow * 0.3; + col += pal(3) * inkStroke(shaped) * u_glow * 0.3; col *= 0.65 + 0.35 * exp(-dot(p, p) * 0.22); col += sigGrain(uv); - return vec4(col, 1.0); + return vec4(inkValue(col), 1.0); } `, }; diff --git a/flow-state/src/scenes/shader/salt-flat.js b/flow-state/src/scenes/shader/salt-flat.js index 8173ec0..67f5678 100644 --- a/flow-state/src/scenes/shader/salt-flat.js +++ b/flow-state/src/scenes/shader/salt-flat.js @@ -13,6 +13,7 @@ export const saltFlat = { name: 'Salt Flat', family: 'minimal', kind: 'fragment', + consumes: ['cast', 'ink'], traits: ['shape', 'camera', 'space', 'style'], params: { @@ -68,14 +69,14 @@ vec4 scene(vec2 uv, vec2 p) { // The one object: small, on the horizon, in the track's form. if (u_monolith > 0.005) { vec2 at = vec2(u_standing, horizon + u_monolith * 0.9); - float d = sigShape((p - at) / u_monolith) * u_monolith; + float d = castMain((p - at) / u_monolith) * u_monolith; col = mix(col, pal(0) * 0.12, smoothstep(0.006, -0.006, d)); - col += pal(4) * sigEdge(d) * (0.3 + u_glowBand * 0.5); + col += pal(4) * inkStroke(d) * (0.3 + u_glowBand * 0.5); } col = sigAir(col, p, smoothstep(0.0, 1.4, abs(p.x))); col += sigGrain(uv); - return vec4(col, 1.0); + return vec4(inkValue(col), 1.0); } `, }; diff --git a/flow-state/src/scenes/shader/scale-mosaic.js b/flow-state/src/scenes/shader/scale-mosaic.js index 696dba6..1b3a1e0 100644 --- a/flow-state/src/scenes/shader/scale-mosaic.js +++ b/flow-state/src/scenes/shader/scale-mosaic.js @@ -15,6 +15,7 @@ export const scaleMosaic = { family: 'organic', kind: 'fragment', texture: 0.8, + consumes: ['cast', 'ink'], traits: ['shape', 'camera', 'style'], params: { @@ -62,7 +63,7 @@ vec4 scene(vec2 uv, vec2 p) { // back to screen distance at the end. A scale is therefore as wide as its // slice of the turn and as tall as its ring — wider ones further out, which // is what growing on a cone looks like. - float d = sigShape(f / max(rad, 1e-3)) * max(rad, 1e-3) * u_growth * r; + float d = castMain(f / max(rad, 1e-3)) * max(rad, 1e-3) * u_growth * r; // Shade by lift, so the surface has a direction of light across it. float shade = 0.3 + 0.7 * sat(0.5 + lift * 1.4); @@ -72,7 +73,7 @@ vec4 scene(vec2 uv, vec2 p) { col = mix(col, body * 0.6, smoothstep(0.004, -0.02, d)); // Rim light along every scale edge, in the track's line weight. - col += pal(4) * sigEdge(d) * (0.3 + u_sheen * 0.8); + col += pal(4) * inkStroke(d) * (0.3 + u_sheen * 0.8); // A specular sweep across the whole head, which is what makes hundreds of // separate tiles read as one surface. @@ -81,7 +82,7 @@ vec4 scene(vec2 uv, vec2 p) { col *= 0.72 + 0.28 * exp(-r * r * 0.3); col += sigGrain(uv); - return vec4(col, 1.0); + return vec4(inkValue(col), 1.0); } `, }; diff --git a/flow-state/src/scenes/shader/scan-tear.js b/flow-state/src/scenes/shader/scan-tear.js index da9febb..d139ca6 100644 --- a/flow-state/src/scenes/shader/scan-tear.js +++ b/flow-state/src/scenes/shader/scan-tear.js @@ -10,6 +10,7 @@ export const scanTear = { family: 'glitch', kind: 'fragment', // Personality: see look/Personality.js. + consumes: ['ink'], traits: ['style'], params: { @@ -73,7 +74,7 @@ vec4 scene(vec2 uv, vec2 p) { col += pal(4) * torn * u_shift * 0.6; col += sigGrain(uv); - return vec4(col, 1.0); + return vec4(inkValue(col), 1.0); } `, }; diff --git a/flow-state/src/scenes/shader/shoji-grid.js b/flow-state/src/scenes/shader/shoji-grid.js index 3bac713..851504c 100644 --- a/flow-state/src/scenes/shader/shoji-grid.js +++ b/flow-state/src/scenes/shader/shoji-grid.js @@ -14,6 +14,7 @@ export const shojiGrid = { kind: 'fragment', // Paper has a tooth, and this is the one scene that wants it. texture: 1.0, + consumes: ['cast', 'ink'], traits: ['shape', 'camera', 'style'], params: { @@ -67,10 +68,10 @@ vec4 scene(vec2 uv, vec2 p) { if (u_form > 0.01) { float which = step(0.62, hash12(cell * 1.7 + 9.0)); vec2 at = vec2(f.x * 2.0 / float(u_cols), f.y * 2.0 / float(u_rows)); - float d = sigShape(at / u_form) * u_form; + float d = castMain(at / u_form) * u_form; col = mix(col, col * 0.35 + pal(4) * glowField * 0.35, smoothstep(0.01, -0.01, d) * which); - col += pal(4) * sigEdge(d) * which * glowField * 0.7; + col += pal(4) * inkStroke(d) * which * glowField * 0.7; } // The frame. Opaque, in front of everything, and the thing that makes the @@ -78,10 +79,10 @@ vec4 scene(vec2 uv, vec2 p) { float bar = min(0.5 - abs(f.x), 0.5 - abs(f.y)); float wood = smoothstep(u_stile, u_stile * 0.4, bar); col = mix(col, pal(0) * 0.14, wood); - col += pal(4) * sigEdge(bar - u_stile) * 0.12; + col += pal(4) * inkStroke(bar - u_stile) * 0.12; col += sigGrain(uv); - return vec4(col, 1.0); + return vec4(inkValue(col), 1.0); } `, }; diff --git a/flow-state/src/scenes/shader/signal-decay.js b/flow-state/src/scenes/shader/signal-decay.js index 0993198..a84eefc 100644 --- a/flow-state/src/scenes/shader/signal-decay.js +++ b/flow-state/src/scenes/shader/signal-decay.js @@ -10,6 +10,7 @@ export const signalDecay = { name: 'Signal Decay', family: 'glitch', kind: 'fragment', + consumes: ['ink'], traits: ['camera', 'style'], params: { @@ -101,7 +102,7 @@ vec4 scene(vec2 uv, vec2 p) { col = max(col, prev(uv) * u_persist); col += sigGrain(uv); - return vec4(col, 1.0); + return vec4(inkValue(col), 1.0); } `, }; diff --git a/flow-state/src/scenes/shader/silk-ribbon.js b/flow-state/src/scenes/shader/silk-ribbon.js index 97ad36a..cc07de7 100644 --- a/flow-state/src/scenes/shader/silk-ribbon.js +++ b/flow-state/src/scenes/shader/silk-ribbon.js @@ -11,6 +11,7 @@ export const silkRibbon = { // Personality: see look/Personality.js. // A soft drape of light. Grain furs the one thing it is made of. texture: 0, + consumes: ['ink'], traits: ['camera', 'style'], params: { @@ -74,7 +75,7 @@ vec4 scene(vec2 uv, vec2 p) { col = mix(col, pal(2), exp(-knot * knot * 30.0) * u_glow * 0.5); col += sigGrain(uv); - return vec4(col, 1.0); + return vec4(inkValue(col), 1.0); } `, }; \ No newline at end of file diff --git a/flow-state/src/scenes/shader/slow-orb.js b/flow-state/src/scenes/shader/slow-orb.js index 7185696..ada9cf5 100644 --- a/flow-state/src/scenes/shader/slow-orb.js +++ b/flow-state/src/scenes/shader/slow-orb.js @@ -14,6 +14,7 @@ export const slowOrb = { // the one where speckle is most visible for being least justified. It keeps // its own `grain` param, which exists to stop large flat areas banding. texture: 0, + consumes: ['cast', 'ink'], traits: ['shape', 'camera', 'space', 'style'], params: { @@ -43,7 +44,7 @@ vec4 scene(vec2 uv, vec2 p) { // The body is the track's signature form — this scene is one shape in an // empty frame, so the shape had better be the track's. float wobble = fbm(q * 2.4 + t, 4) * u_wobble; - float d = (sigShape(q / max(u_size, 1e-3)) * u_size) * (1.0 + wobble); + float d = (castMain(q / max(u_size, 1e-3)) * u_size) * (1.0 + wobble); // TRAIT style: the body's edge is drawn in the track's hand. The scene's // u_softness says how soft this orb is; u_sigSoft says how soft this VIDEO @@ -54,7 +55,7 @@ vec4 scene(vec2 uv, vec2 p) { // A rim in the track's line weight, so a sharp-handed track gets a defined // limb against the halo instead of only a softer gradient. - float rim = sigEdge(d) * u_sigLine; + float rim = inkStroke(d) * u_sigLine; vec3 col = pal(0) * 0.05; col = mix(col, pal(1), body * 0.85); @@ -67,7 +68,7 @@ vec4 scene(vec2 uv, vec2 p) { col += (hash12(uv * 640.0 + floor(u_frame)) - 0.5) * u_grain * 0.08; col += sigGrain(uv); - return vec4(col, 1.0); + return vec4(inkValue(col), 1.0); } `, }; diff --git a/flow-state/src/scenes/shader/spectrum-sculpture.js b/flow-state/src/scenes/shader/spectrum-sculpture.js index 58e1206..0d93a16 100644 --- a/flow-state/src/scenes/shader/spectrum-sculpture.js +++ b/flow-state/src/scenes/shader/spectrum-sculpture.js @@ -12,6 +12,7 @@ export const spectrumSculpture = { // Personality: see look/Personality.js. // Takes the track's surface grain, but lightly — this is drawn, not filmed. texture: 0.4, + consumes: ['ink'], traits: ['shape', 'camera', 'style'], params: { @@ -65,8 +66,11 @@ vec4 scene(vec2 uv, vec2 p) { float seg = 6.28318530718 / bars; float angle = atan(p.y, p.x) + t * seg; - // Radial extent measured in the track's form, so the sculpture is built on - // the same outline as everything else in the video. + // Radial extent measured in the track's SIGNATURE form, not in the cast. + // This is a metric, not a subject: the cast carries notches and a hollow, + // and an annulus used as a radius turns the sculpture inside out — it + // rendered pure black. A scene that reads a form as geometry rather than + // drawing it wants sigShape, and the migration should leave it alone. float radius = sigShape(p) + 1.0; float index = floor((angle + 3.14159265) / seg); float cellAngle = mod(angle + 3.14159265, seg) / seg; @@ -91,10 +95,10 @@ vec4 scene(vec2 uv, vec2 p) { col += palRamp(slot * 0.7 + 0.15) * inBar * shape * (0.6 + band); // Inner ring outline holds the composition together. - col += pal(2) * sigEdge(radius - u_radius) * 0.35; + col += pal(2) * inkStroke(radius - u_radius) * 0.35; col += sigGrain(uv); - return vec4(col, 1.0); + return vec4(inkValue(col), 1.0); } `, }; diff --git a/flow-state/src/scenes/shader/stairwell-descent.js b/flow-state/src/scenes/shader/stairwell-descent.js index 5f95d9a..60a78bb 100644 --- a/flow-state/src/scenes/shader/stairwell-descent.js +++ b/flow-state/src/scenes/shader/stairwell-descent.js @@ -14,6 +14,7 @@ export const stairwellDescent = { family: 'structural', kind: 'fragment', texture: 0.7, + consumes: ['ink'], traits: ['camera', 'space', 'style'], params: { @@ -82,7 +83,7 @@ vec4 scene(vec2 uv, vec2 p) { col = mix(col, pal(1) * 0.3, sky * 0.5); col = sigAir(col, p, sat(1.0 - fade)); col += sigGrain(uv); - return vec4(col, 1.0); + return vec4(inkValue(col), 1.0); } `, }; diff --git a/flow-state/src/scenes/shader/storm-rift.js b/flow-state/src/scenes/shader/storm-rift.js index ea425c9..f127061 100644 --- a/flow-state/src/scenes/shader/storm-rift.js +++ b/flow-state/src/scenes/shader/storm-rift.js @@ -13,6 +13,7 @@ export const stormRift = { name: 'Storm Rift', family: 'glitch', kind: 'fragment', + consumes: ['ink'], traits: ['camera', 'space', 'style'], params: { @@ -101,7 +102,7 @@ vec4 scene(vec2 uv, vec2 p) { col = max(col, prev(uv) * u_afterglow); col += sigGrain(uv); - return vec4(col, 1.0); + return vec4(inkValue(col), 1.0); } `, }; diff --git a/flow-state/src/scenes/shader/suspension-span.js b/flow-state/src/scenes/shader/suspension-span.js index af1c57b..9f44c7c 100644 --- a/flow-state/src/scenes/shader/suspension-span.js +++ b/flow-state/src/scenes/shader/suspension-span.js @@ -13,6 +13,7 @@ export const suspensionSpan = { family: 'structural', kind: 'fragment', texture: 0.6, + consumes: ['cast', 'ink'], traits: ['shape', 'camera', 'space', 'style'], params: { @@ -78,7 +79,7 @@ vec4 scene(vec2 uv, vec2 p) { // Deck: one heavy line all the way across, which is what stops the cable // reading as a piece of string. col += pal(4) * smoothstep(w * 2.5, w * 0.6, abs(p.y - deckY)) * (0.4 + u_glow * 0.5); - col += pal(4) * sigEdge(abs(p.y - deckY) - w * 3.0) * 0.25; + col += pal(4) * inkStroke(abs(p.y - deckY) - w * 3.0) * 0.25; // Towers: a pair of legs and a crossbeam, with the signature form as the // lamp on top — the only object in the frame that is not a line. @@ -87,17 +88,17 @@ vec4 scene(vec2 uv, vec2 p) { col += palRamp(0.05) * smoothstep(w * 3.0, w * 1.2, dTower) * 0.7; if (u_lamp > 0.004) { - float d = sigShape((vec2(x, p.y) - vec2(0.0, towerTop + u_lamp)) / u_lamp) * u_lamp; + float d = castMain((vec2(x, p.y) - vec2(0.0, towerTop + u_lamp)) / u_lamp) * u_lamp; col += pal(3) * smoothstep(0.004, -0.004, d) * (0.6 + u_glow); col += pal(3) * exp(-abs(d) * 20.0) * u_glow * 0.5; - col += pal(3) * sigEdge(d) * 0.6; + col += pal(3) * inkStroke(d) * 0.6; } // Every other bay is a little further off, so the line of the bridge has // some air in it rather than being a flat elevation drawing. col = sigAir(col, p, smoothstep(0.2, 1.4, abs(bay) * 0.7 + hash11(bayId) * 0.5)); col += sigGrain(uv); - return vec4(col, 1.0); + return vec4(inkValue(col), 1.0); } `, }; diff --git a/flow-state/src/scenes/shader/tide-rings.js b/flow-state/src/scenes/shader/tide-rings.js index 6a7e867..cd26ab0 100644 --- a/flow-state/src/scenes/shader/tide-rings.js +++ b/flow-state/src/scenes/shader/tide-rings.js @@ -14,6 +14,7 @@ export const tideRings = { name: 'Tide Rings', family: 'organic', kind: 'fragment', + consumes: ['cast', 'ink'], traits: ['shape', 'camera', 'space', 'style'], params: { @@ -52,7 +53,7 @@ vec4 scene(vec2 uv, vec2 p) { ); // Distance in the signature metric: round tracks get circular wavefronts. - float d = sigShape(p - src) + 1.0; + float d = castMain(p - src) + 1.0; float amp = exp(-d * u_decay); sum += sin(d * u_wavelength - t * 6.0 + fract(s) * 6.28) * amp; energy += amp; @@ -71,7 +72,7 @@ vec4 scene(vec2 uv, vec2 p) { col = sigAir(col, p, smoothstep(0.0, 1.7, length(p))); col += sigGrain(uv); - return vec4(col, 1.0); + return vec4(inkValue(col), 1.0); } `, }; diff --git a/flow-state/src/scenes/shader/time-smear.js b/flow-state/src/scenes/shader/time-smear.js index 560a676..0d7fec3 100644 --- a/flow-state/src/scenes/shader/time-smear.js +++ b/flow-state/src/scenes/shader/time-smear.js @@ -14,6 +14,7 @@ export const timeSmear = { name: 'Time Smear', family: 'glitch', kind: 'fragment', + consumes: ['ink'], traits: ['camera', 'style'], params: { @@ -75,7 +76,7 @@ vec4 scene(vec2 uv, vec2 p) { col = mix(col, vec3(dot(col, vec3(0.299, 0.587, 0.114))), age * 0.35); col += sigGrain(uv); - return vec4(col, 1.0); + return vec4(inkValue(col), 1.0); } `, }; diff --git a/flow-state/src/scenes/shader/truchet-fold.js b/flow-state/src/scenes/shader/truchet-fold.js index b1ecdef..b747588 100644 --- a/flow-state/src/scenes/shader/truchet-fold.js +++ b/flow-state/src/scenes/shader/truchet-fold.js @@ -13,6 +13,7 @@ export const truchetFold = { kind: 'fragment', // Takes the track's surface grain, but lightly — this is drawn, not filmed. texture: 0.4, + consumes: ['ink'], traits: ['camera', 'style'], params: { @@ -64,11 +65,11 @@ vec4 scene(vec2 uv, vec2 p) { vec3 col = pal(0) * 0.05; col += tint * line * 0.75; col += tint * halo * u_glow * 0.35; - col += pal(4) * sigEdge(d) * u_glow * 0.25; + col += pal(4) * inkStroke(d) * u_glow * 0.25; col *= 0.65 + 0.35 * exp(-dot(p, p) * 0.25); col += sigGrain(uv); - return vec4(col, 1.0); + return vec4(inkValue(col), 1.0); } `, }; diff --git a/flow-state/src/scenes/shader/turing-bloom.js b/flow-state/src/scenes/shader/turing-bloom.js index 9ca35a0..3b5de88 100644 --- a/flow-state/src/scenes/shader/turing-bloom.js +++ b/flow-state/src/scenes/shader/turing-bloom.js @@ -24,6 +24,7 @@ export const turingBloom = { name: 'Turing Bloom', family: 'organic', kind: 'fragment', + consumes: ['ink'], traits: ['camera', 'style'], params: { @@ -117,7 +118,7 @@ vec4 scene(vec2 uv, vec2 p) { col *= 0.75 + 0.25 * exp(-dot(p, p) * 0.2); col += sigGrain(uv); - return vec4(col, 1.0); + return vec4(inkValue(col), 1.0); } `, }; diff --git a/flow-state/src/scenes/shader/voronoi-shatter.js b/flow-state/src/scenes/shader/voronoi-shatter.js index 6884940..7c44be7 100644 --- a/flow-state/src/scenes/shader/voronoi-shatter.js +++ b/flow-state/src/scenes/shader/voronoi-shatter.js @@ -20,6 +20,7 @@ export const voronoiShatter = { family: 'geometric', kind: 'fragment', texture: 0.5, + consumes: ['ink'], traits: ['camera', 'style'], params: { @@ -84,11 +85,11 @@ vec4 scene(vec2 uv, vec2 p) { // Seams: drawn in the track's hand, brightest where three plates meet. float seam = smoothstep(u_seam, u_seam * 0.15, border); col += pal(4) * seam * (0.4 + u_glow * 0.8); - col += pal(3) * sigEdge(border - u_seam) * 0.4; + col += pal(3) * inkStroke(border - u_seam) * 0.4; col *= 0.75 + 0.25 * exp(-dot(p, p) * 0.25); col += sigGrain(uv); - return vec4(col, 1.0); + return vec4(inkValue(col), 1.0); } `, }; diff --git a/flow-state/src/scenes/shader/vortex-drift.js b/flow-state/src/scenes/shader/vortex-drift.js index dbebd42..e5f85db 100644 --- a/flow-state/src/scenes/shader/vortex-drift.js +++ b/flow-state/src/scenes/shader/vortex-drift.js @@ -13,6 +13,7 @@ export const vortexDrift = { name: 'Vortex Drift', family: 'flow', kind: 'fragment', + consumes: ['cast', 'ink'], traits: ['shape', 'camera', 'style'], params: { @@ -64,14 +65,14 @@ vec4 scene(vec2 uv, vec2 p) { // The eye itself, in the signature form. if (u_core > 0.004) { - float d = sigShape(q / u_core) * u_core; + float d = castMain(q / u_core) * u_core; col = mix(col, pal(0) * 0.05, smoothstep(0.004, -0.01, d)); - col += pal(3) * sigEdge(d) * 0.8; + col += pal(3) * inkStroke(d) * 0.8; col += pal(4) * exp(-max(d, 0.0) * 18.0) * 0.35; } col += sigGrain(uv); - return vec4(col, 1.0); + return vec4(inkValue(col), 1.0); } `, }; diff --git a/flow-state/tools/lint-scenes.js b/flow-state/tools/lint-scenes.js index 31b60cf..4a802bd 100644 --- a/flow-state/tools/lint-scenes.js +++ b/flow-state/tools/lint-scenes.js @@ -103,43 +103,69 @@ const CONTRACT_UNIFORMS = new Set([ * cast in the hexagon video and be the one shot that looks filmed elsewhere. * So the claim is machine-checked against the source rather than trusted. */ -// A stage that draws the song's CAST is expressing `shape` more completely than +// A scene that draws the song's CAST is expressing `shape` more completely than // sigShape ever did — the form is the subject rather than a hint applied to one -// — so castMain/castChorus count as evidence. Likewise inkMask/inkValue are the -// style trait carried out in full. See scenes/stage/README.md. +// — so castMain/castChorus count as evidence. +// +// The INK does not work the same way and must not be listed under `style`. A +// trait is a property of the track that a scene may honour; an artifact is +// content it draws. Taking the ink says nothing about whether the scene +// responds to u_sigLine, and treating it as evidence let Eclipse Field claim a +// trait it had stopped honouring — which the runtime gate then caught. // The same idea as TRAIT_EVIDENCE, for the identity artifacts: a scene that // declares it consumes the cast has to actually draw it. Without this, // `consumes` is a comment, and the migration becomes unverifiable the moment it // is more than a handful of files. const ARTIFACT_EVIDENCE = { - cast: /\bcast(Main|Chorus|SDF)\s*\(/, - ink: /\bink(Mask|Value|Pattern)\s*\(/, + cast: /\bcast(Main|Chorus|SDF|Form)\s*\(/, + ink: /\bink(Mask|Value|Pattern|Stroke)\s*\(/, staging: /\b(stageNode|stageScale)\s*\(/, }; const TRAIT_EVIDENCE = { - shape: /\b(sig(Shape|Form)|cast(Main|Chorus|SDF))\s*\(/, + shape: /\b(sig(Shape|Form)|cast(Main|Chorus|SDF|Form))\s*\(/, camera: /\bsigCamera\s*\(/, space: /\b(sigHorizonY|sigAir)\s*\(|\bu_sig(Horizon|Depth|Wash)\b/, - style: /\b(sigEdge|sigGrain|sigFolded|inkMask|inkValue|inkPattern)\s*\(|\bu_sig(Line|Soft|Texture|Fold)\b/, + style: /\b(sigEdge|sigGrain|sigFolded)\s*\(|\bu_sig(Line|Soft|Texture|Fold)\b/, }; -// The shader preamble is a JS template literal, so a backtick anywhere inside -// it silently closes the literal. Twice now that has produced a check page that -// hangs on "starting…" with an empty console, which is an expensive way to find -// a typo. One grep is cheaper. -console.log('\nshader contract'); +// Every shader in this project lives inside a JS template literal, so a +// backtick anywhere in one silently closes it. Three times now that has cost a +// debugging round: twice in the preamble, where it produced a check page that +// hung on "starting…" with an empty console, and once in a scene, where at +// least the module failed to parse loudly. A GLSL comment is the natural place +// to reach for backticks when quoting a param name, which is exactly why this +// keeps happening. +// +// Checked across the contract AND every scene, since the scene case is the one +// a mechanical pass over sixty files will keep reintroducing. +console.log('\nshader literals'); { - const src = readFileSync(join(SRC, 'engine/shader-contract.js'), 'utf8'); - const literal = src.slice(src.indexOf('export const PREAMBLE = `') + 25); - const body = literal.slice(0, literal.indexOf('\n`;')); - const stray = body.split('\n').filter((l) => l.includes('`') && !l.includes('${')); - if (stray.length) { - fail(`shader preamble contains a backtick, which closes the template literal:\n` + - stray.map((l) => ` ${l.trim()}`).join('\n')); - } else { - ok('preamble has no stray backticks'); + const targets = [join(SRC, 'engine/shader-contract.js'), ...walk(join(SRC, 'scenes'))]; + let clean = 0; + for (const file of targets) { + const src = readFileSync(file, 'utf8'); + const rel = relative(SRC, file).replace(/\\/g, '/'); + // Every template literal in the file, then the lines inside them that + // carry a backtick without being an interpolation. + const stray = []; + const rx = /`([\s\S]*?)`/g; + let m; + while ((m = rx.exec(src)) !== null) { + if (!/\bvec4 scene|precision highp|void main/.test(m[1])) continue; + for (const line of m[1].split('\n')) { + if (line.includes('`') && !line.includes('${')) stray.push(line); + } + } + // A shader body that swallowed a closing backtick shows up as an + // unbalanced count across the file. + const ticks = (src.match(/`/g) || []).length; + if (stray.length || ticks % 2 !== 0) { + fail(`${rel}: backtick inside a shader literal — it closes the string` + + (stray.length ? `:\n${stray.map((l) => ` ${l.trim()}`).join('\n')}` : '')); + } else clean++; } + if (clean === targets.length) ok(`${clean} shader literals balanced, no stray backticks`); } console.log('\nscene schema lint');