music-video-gen/flow-state/src/scenes/shader/salt-flat.js
Dejvino 2d3ed8eb02 Finish the migration: all 65 scenes on the identity artifacts
Two helpers made the bulk of it mechanical. `inkStroke` is a drop-in for
sigEdge — the same line at the identity's weight rather than the track's — and
`castForm` is a drop-in for sigForm, same signature so call sites do not change
shape. With those in place the substitution table is one-to-one:

    sigForm(  -> castForm(     sigShape( -> castMain(     sigEdge( -> inkStroke(

Forty-seven scenes went through that pass in one run: twenty-six take the cast
and the ink, twenty-one take the ink alone. Then the gate ran over all sixty-five
and found three the pass had broken, which is the entire reason it exists.

Spectrum Sculpture rendered pure black. It had been using sigShape as a RADIAL
METRIC rather than drawing it, and the cast carries notches and a hollow — an
annulus used as a radius turns a sculpture inside out. Reverted to sigShape and
dropped to ink only. The lesson generalises: a scene that reads a form as
geometry is not a scene that draws it, and the classifier cannot tell those
apart from the source.

Eclipse Field stopped honouring its `style` trait. It opts out of surface grain,
so sigEdge was its only style evidence, and the ink replaced it. The trait claim
is now dropped — and so is the lint change that had let inkMask count as style
evidence, which was wrong and was hiding exactly this. A trait is a property of
the track a scene may honour; an artifact is content it draws. Taking the ink
says nothing about whether a scene responds to u_sigLine.

Circuit Bloom went empty at the bottom of its `grown` range, where the pads were
carried by a hairline and the ink's stroke is thinner than the edge it replaced.
Now filled as well as stroked.

Also: the backtick check now covers every shader literal rather than only the
preamble, because a mechanical pass over sixty files reintroduced one
immediately. Three rounds lost to that typo is enough.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-18 06:39:19 +02:00

85 lines
3.4 KiB
JavaScript

// Minimal family: an empty plain under a very large sky, with one distant form
// standing on the horizon.
//
// The emptiest scene in the library and the most deliberate about it — most of
// the frame is a gradient. Distinct from Horizon Lines (a bundle of lines) and
// Ridge Terrain (layered silhouettes): there is exactly one object, it is small,
// and it is far away. What moves is the light and the heat shimmer.
//
// The object is the track's signature form, so the thing on the horizon of a
// hexagonal video is a hexagon.
export const saltFlat = {
name: 'Salt Flat',
family: 'minimal',
kind: 'fragment',
consumes: ['cast', 'ink'],
traits: ['shape', 'camera', 'space', 'style'],
params: {
monolith: { type: 'float', range: [0.0, 0.35], default: 0.12, uniform: 'u_monolith' },
standing: { type: 'float', range: [-0.7, 0.7], default: 0.0, uniform: 'u_standing' },
shimmer: { type: 'float', range: [0, 0.09], default: 0.025,uniform: 'u_shimmer' },
glowBand: { type: 'float', range: [0, 1.2], default: 0.45, uniform: 'u_glowBand', bias: 'energy' },
ground: { type: 'float', range: [0, 1], default: 0.4, uniform: 'u_ground' },
salt: { type: 'float', range: [0, 1], default: 0.35, uniform: 'u_salt', bias: 'density' },
pace: { type: 'float', range: [0.02, 0.5], default: 0.12, uniform: 'u_pace', bias: 'motion', rate: true },
palette: { type: 'palette', count: 5 },
},
reactive: {
glowBand: { feature: 'loudness', amount: 0.3, response: 'smooth' },
shimmer: { feature: 'bandAir', amount: 0.25 },
},
shader: `
vec4 scene(vec2 uv, vec2 p) {
float t = u_time * u_pace + u_seed;
p = sigCamera(p);
float horizon = sigHorizonY() * 0.6 - 0.15;
// Heat shimmer: everything near the horizon wobbles, nothing else does.
float nearHorizon = exp(-abs(p.y - horizon) * 5.0);
p.x += sin(p.y * 60.0 + t * 6.0) * u_shimmer * nearHorizon;
float above = p.y - horizon;
// Sky: a tall gradient, darkest at the top.
vec3 col = mix(pal(1) * 0.5, pal(0) * 0.25, sat(above * 0.8 + 0.15));
// The glow band sitting on the horizon — the light source of the whole scene.
col += pal(3) * exp(-abs(above) * 9.0) * u_glowBand * 0.8;
if (above < 0.0) {
float depth = sat(-above * 2.2); // 0 far .. 1 near
vec3 plain = mix(pal(2) * 0.5, pal(0) * 0.3, depth);
// Salt crust: cracked cells, only legible in the near field.
vec2 cellUv = vec2(p.x / max(-above * 0.9 + 0.06, 0.02), 1.0 / max(-above + 0.05, 0.02));
float crack = abs(fract(cellUv.x * 0.5) - 0.5) + abs(fract(cellUv.y * 0.5) - 0.5);
plain += pal(4) * smoothstep(0.42, 0.5, crack) * u_salt * depth * 0.25;
// Reflection of the glow band, compressed toward the horizon.
plain += pal(3) * exp(above * 7.0) * u_glowBand * 0.3;
col = mix(col, plain, u_ground);
}
// 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 = castMain((p - at) / u_monolith) * u_monolith;
col = mix(col, pal(0) * 0.12, smoothstep(0.006, -0.006, d));
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(inkValue(col), 1.0);
}
`,
};
export default saltFlat;