Two devices that were built, wired up, and doing nothing. OVERLAYS. buildStack gated layering on `surfaceOf(m) === 'composable'`, and no scene in the library declared `surface` — so the only thing that could ever sit on top was the one scene declaring `role: 'accent'`, and the overlay roster excluded accents by construction. Empty intersection, every time: 0/144 stacks carried an overlay. Every layered frame in every song was the same particle field. Labelled the library from the phase 12 coverage gate rather than by eye: the 37 scenes painting under 30% of the frame are composable. Particle Field loses its privileged slot and becomes one of them, keeping only `background: false`, which is the honest part — points in empty space cannot carry a section alone. The reserved accent slot is gone; one roster, two passes at it. 1 distinct overlay scene becomes 27, and the rate lands at 34% of stacks after trimming a base chance that had been tuned while the branch was dead. THE CAMERA. framing.shift moved the frame by a median of 0.029 of a half-frame at a fresh random angle every shot, so successive offsets cancelled and the median jump at a cut was 0.014. Present in every frame, visible in none. look/Camera.js is the director's camera department: the story says tension, order and which act a section is in, and this turns that into where the frame looks and how it travels there. Each director names a camera. Jump distance follows tension and act, speed follows energy, and the curve is one of four. Cuts choose between reframing and matching, so two scenes can still read as one place. Median offset 0.190, median reframe 0.135, and 94% of shots now move during the shot rather than only at the cut. Four new gates, each with a FLOOR — the device this replaces passed every existing check while doing nothing, because the only bound was a ceiling. Also lands the in-progress Epic 4 story layer it builds on: Story.js, phase 13, and the direction statistic. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
105 lines
4.7 KiB
JavaScript
105 lines
4.7 KiB
JavaScript
// Structural family: a suspension bridge passing overhead — two towers in
|
|
// frame at a time, the main cable slung between them, hangers dropping to a
|
|
// deck that runs off both sides.
|
|
//
|
|
// Girder Lattice is a truss: hundreds of short members in tension and
|
|
// compression, and the image is a texture of triangles. This is the opposite
|
|
// structure and reads as one — two long curves and a rhythm of verticals under
|
|
// them, with almost nothing else in the frame. The curve is the whole subject,
|
|
// and the only thing that repeats is the spacing.
|
|
|
|
export const suspensionSpan = {
|
|
name: 'Suspension Span',
|
|
family: 'structural',
|
|
kind: 'fragment',
|
|
// Paints 21% of the frame — see checks/phase12 coverage.
|
|
surface: 'composable',
|
|
texture: 0.6,
|
|
consumes: ['cast', 'ink'],
|
|
traits: ['shape', 'camera', 'space', 'style'],
|
|
|
|
params: {
|
|
span: { type: 'float', range: [0.5, 2.2], default: 1.1, uniform: 'u_span', bias: 'density' },
|
|
sag: { type: 'float', range: [0.15, 0.9], default: 0.5, uniform: 'u_sag' },
|
|
tower: { type: 'float', range: [0.3, 1.1], default: 0.7, uniform: 'u_tower' },
|
|
hangers: { type: 'float', range: [2, 22], default: 9, uniform: 'u_hangers', bias: 'density' },
|
|
deck: { type: 'float', range: [-0.7, 0.25], default: -0.2, uniform: 'u_deck', slowAxis: true },
|
|
lamp: { type: 'float', range: [0, 0.12], default: 0.045, uniform: 'u_lamp' },
|
|
travel: { type: 'float', range: [0.01, 0.3], default: 0.06, uniform: 'u_travel', bias: 'motion', rate: true },
|
|
palette: { type: 'palette', count: 5 },
|
|
},
|
|
|
|
reactive: {
|
|
lamp: { feature: 'beat', amount: 0.2, response: 'spike' },
|
|
},
|
|
|
|
shader: `
|
|
// Distance to a segment, for the one-off members that are not on a grid.
|
|
float seg(vec2 q, vec2 a, vec2 b) {
|
|
vec2 ab = b - a, ap = q - a;
|
|
float h = sat(dot(ap, ab) / max(dot(ab, ab), 1e-6));
|
|
return length(ap - ab * h);
|
|
}
|
|
|
|
vec4 scene(vec2 uv, vec2 p) {
|
|
float t = u_time * u_travel + u_seed;
|
|
p = sigCamera(p);
|
|
|
|
float horizon = sigHorizonY() * 0.3;
|
|
float deckY = horizon + u_deck;
|
|
|
|
// Sky, and the water or ground under the deck.
|
|
vec3 col = mix(pal(1) * 0.2, pal(0) * 0.06, sat((p.y - horizon) * 0.6 + 0.25));
|
|
col += pal(3) * exp(-abs(p.y - horizon) * 7.0) * 0.2;
|
|
col = mix(col, pal(0) * 0.09, smoothstep(deckY + 0.01, deckY - 0.03, p.y));
|
|
|
|
// Bays: the bridge repeats every span, and travelling means the whole
|
|
// structure slides through that repeat.
|
|
float g = (p.x + t) / u_span;
|
|
float bay = fract(g) - 0.5;
|
|
float bayId = floor(g);
|
|
float x = bay * u_span; // metres from this bay's centre
|
|
|
|
float towerTop = deckY + u_tower;
|
|
float w = 0.006 + u_sigLine * 0.02;
|
|
|
|
// Main cable: a parabola from tower top to tower top, lowest mid-bay.
|
|
float cableY = towerTop - u_sag * u_tower * (1.0 - 4.0 * bay * bay);
|
|
float dCable = abs(p.y - cableY);
|
|
col += palRamp(0.15) * smoothstep(w * 1.6, w * 0.3, dCable) * (0.6 + 0.55 * 0.6);
|
|
|
|
// Hangers: verticals from the cable down to the deck, on their own spacing.
|
|
float hg = (p.x + t) * u_hangers / u_span;
|
|
float hx = (fract(hg) - 0.5) * u_span / u_hangers;
|
|
float hangerCableY = towerTop - u_sag * u_tower *
|
|
(1.0 - 4.0 * pow(fract(g + (floor(hg) - hg + 0.5) / u_hangers) - 0.5, 2.0));
|
|
float inSpan = step(p.y, hangerCableY) * step(deckY, p.y);
|
|
col += palRamp(0.3) * smoothstep(w, w * 0.25, abs(hx)) * inSpan * (0.3 + 0.55 * 0.35);
|
|
|
|
// 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 + 0.55 * 0.5);
|
|
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.
|
|
float dTower = min(seg(vec2(x, p.y), vec2(0.0, deckY - 0.1), vec2(0.0, towerTop)),
|
|
seg(vec2(x, p.y), vec2(-0.03, towerTop - 0.12), vec2(0.03, towerTop - 0.12)));
|
|
col += palRamp(0.05) * smoothstep(w * 3.0, w * 1.2, dTower) * 0.7;
|
|
|
|
if (u_lamp > 0.004) {
|
|
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 + 0.55);
|
|
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));
|
|
return vec4(inkValue(col), 1.0);
|
|
}
|
|
`,
|
|
};
|
|
|
|
export default suspensionSpan;
|