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>
129 lines
5.0 KiB
JavaScript
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;
|