music-video-gen/flow-state/src/scenes/shader/girder-lattice.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

129 lines
5.0 KiB
JavaScript

// Structural family: an overhead truss, seen from underneath.
//
// Distinct from Pylon Grid (columns standing on the ground), Gate Corridor
// (frames receding head-on) and Neon City (a skyline at eye level): this is the
// only scene in the library whose subject is ABOVE the camera. Two parallel
// chords run away toward the horizon with diagonal bracing zigzagging between
// them, and the whole thing is looked up at, so the perspective converges
// downward rather than inward.
//
// Joint plates are stamped in the track's signature form.
//
// Scaffolded by tools/new-scene.js. See HOWTO-visualizers.md.
export const girderLattice = {
name: 'Girder Lattice',
family: 'structural',
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: {
bays: { type: 'int', range: [3, 16], default: 8, uniform: 'u_bays', bias: 'density' },
beam: { type: 'float', range: [0.01, 0.12],default: 0.04, uniform: 'u_beam' },
depth: { type: 'float', range: [0.3, 2.2], default: 1.0, uniform: 'u_depth' },
brace: { type: 'float', range: [0, 1], default: 0.7, uniform: 'u_brace', bias: 'density' },
travel: { type: 'float', range: [0.0, 0.6], default: 0.12, uniform: 'u_travel', bias: 'motion', rate: true },
plate: { type: 'float', range: [0, 0.12], default: 0.05, uniform: 'u_plate' },
lamp: { type: 'float', range: [0, 1.4], default: 0.45, uniform: 'u_lamp', bias: 'energy' },
palette: { type: 'palette', count: 5 },
},
reactive: {
lamp: { feature: 'beat', amount: 0.3, response: 'spike' },
brace: { feature: 'bandLow', amount: 0.15, response: 'smooth' },
},
shader: `
// Distance to a line segment. The truss is nothing but segments.
float segment(vec2 p, vec2 a, vec2 b) {
vec2 pa = p - a, ba = b - a;
float h = clamp(dot(pa, ba) / max(dot(ba, ba), 1e-6), 0.0, 1.0);
return length(pa - ba * h);
}
vec4 scene(vec2 uv, vec2 p) {
float t = u_time * u_travel + u_seed;
p = sigCamera(p);
// Looking up: the vanishing point sits BELOW the frame's horizon, and the
// structure spans above it. Everything converges toward that point.
float vanishY = sigHorizonY() * 0.5 - 0.35;
float above = p.y - vanishY;
// Sky between the members.
vec3 col = mix(pal(0) * 0.1, pal(1) * 0.06, sat(above * 0.6));
if (above <= 0.001) {
col = sigAir(col, p, 1.0);
col += sigGrain(uv);
return vec4(inkValue(col), 1.0);
}
float best = 1e3;
float nearest = 0.0;
float plateHit = 1e3;
// Bays march toward the camera on a wrapping saw, so the truss travels
// overhead rather than sitting still.
for (int i = 0; i < 16; i++) {
if (i >= u_bays) break;
float fi = float(i);
float phase = fract(fi / float(u_bays) + t);
// Perspective: nearer bays are further up the frame and further apart.
float y0 = vanishY + exp(phase * 2.4) * 0.12 * u_depth;
float y1 = vanishY + exp((phase + 1.0 / float(u_bays)) * 2.4) * 0.12 * u_depth;
float halfW0 = (y0 - vanishY) * 1.5;
float halfW1 = (y1 - vanishY) * 1.5;
// The two chords, running away from the camera.
best = min(best, segment(p, vec2(-halfW0, y0), vec2(-halfW1, y1)));
best = min(best, segment(p, vec2(halfW0, y0), vec2(halfW1, y1)));
// Cross member at this joint.
best = min(best, segment(p, vec2(-halfW0, y0), vec2(halfW0, y0)));
// Diagonal bracing, alternating direction bay to bay: the zigzag is
// what makes it a truss rather than a ladder.
if (u_brace > 0.05) {
float flip = mod(fi, 2.0) < 0.5 ? 1.0 : -1.0;
float d = segment(p, vec2(-halfW0 * flip, y0), vec2(halfW1 * flip, y1));
best = min(best, d + (1.0 - u_brace) * 0.05);
}
if (u_plate > 0.004) {
float pd = castMain((p - vec2(halfW0, y0)) / u_plate) * u_plate;
plateHit = min(plateHit, pd);
pd = castMain((p - vec2(-halfW0, y0)) / u_plate) * u_plate;
plateHit = min(plateHit, pd);
}
if (phase > 0.75) nearest = max(nearest, phase);
}
// Members are drawn thicker nearer the camera, which is the only depth cue
// a wireframe has.
float weight = u_beam * (0.4 + above * 0.9) * (0.5 + u_sigLine);
float steel = smoothstep(weight, weight * 0.3, best);
col = mix(col, pal(2) * (0.3 + above * 0.5), steel);
col += pal(3) * inkStroke(best - weight) * 0.4;
// Joint plates.
col = mix(col, pal(4) * 0.7, smoothstep(0.006, -0.006, plateHit));
// A lamp slung under the nearest bay, pulsing on the beat. Local.
col += pal(4) * exp(-length(p - vec2(0.0, vanishY + nearest * 1.4)) * 14.0) * u_lamp;
col = sigAir(col, p, 1.0 - smoothstep(0.0, 1.4, above));
col += sigGrain(uv);
return vec4(col, 1.0);
}
`,
};
export default girderLattice;