Raise the variety bar to 0.1
Deliberately a target rather than a description of where the library sits. Of the scenes measured since the region block went in, only Droste Feedback at 0.324 is comfortably over; Plasma Bloom is 0.082, Voronoi Shatter and Apollonian Gasket 0.062, Moiré Grid 0.047. Most of the library fails this, which is the point — a bar set where the work already is measures nothing. Third value in two days, and both previous moves were instrument corrections rather than changes of mind: the motion block was reading zero for the entire library, and adding the region block made the total a mean over six blocks instead of five. Both raised every score, so 0.04 had quietly drifted down relative to the thing it was judging. Documented as something to re-read off a fresh gallery whenever the descriptor changes, rather than a number to carry forward. The marker now sits at the middle of each meter and the grades land where they should: 0.047, 0.062 and 0.082 all red, 0.140 amber, 0.324 green. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
parent
b879abe230
commit
c099501ead
@ -28,12 +28,15 @@ checks.html?scene=X the per-scene gate, including `consumes:` lines
|
||||
```
|
||||
|
||||
The gallery score is the mean structural distance between a scene's own six
|
||||
frames, on the same descriptor the variety harness uses. **Minimum bar: 0.04**
|
||||
frames, on the same descriptor the variety harness uses. **Minimum bar: 0.1**
|
||||
(`MIN_VARIETY` in `checks/gallery.js`), drawn as a red line. Below it a scene is
|
||||
the same picture wherever it appears, and because it is cast into many songs
|
||||
that sameness leaks between them — the viewer recognises the shot rather than
|
||||
the song.
|
||||
|
||||
Most of the library does not clear it. That is deliberate: a bar set where the
|
||||
work already is measures nothing.
|
||||
|
||||
Some reference points, measured:
|
||||
|
||||
```
|
||||
@ -247,7 +250,7 @@ Two more traps, both of which have bitten:
|
||||
2. No glow, bloom, grain, haze, chroma or trail parameters.
|
||||
3. Element size goes through `stageScale()`.
|
||||
4. Placement goes through `stageNode()` unless the composition IS the scene.
|
||||
5. Gallery score above 0.04, checked before and after.
|
||||
5. Gallery score above 0.1, checked before and after.
|
||||
6. The rest of the per-scene battery still passes — especially `distinct`. The
|
||||
more scenes share a cast, the easier it is to become one of your neighbours.
|
||||
|
||||
@ -261,5 +264,8 @@ Two more traps, both of which have bitten:
|
||||
- **Fixed-geometry scenes** have no known route to variety.
|
||||
- **The `layout` blindness** for full-frame fields would need a new descriptor
|
||||
block measuring cell statistics rather than where structure sits.
|
||||
- **The 0.04 bar** is a starting point, set where the flat cluster sat — and set
|
||||
against scores that were depressed by the motion bug. It deserves re-deriving.
|
||||
- **The bar is a target, not a description.** Most of the library does not clear
|
||||
0.1 — that is the point of it. It has moved from 0.04 to 0.05 to 0.1
|
||||
as the descriptor gained a block and lost a bug, both of which raised every
|
||||
score. Expect to move it again whenever the descriptor changes, and re-read it
|
||||
off a fresh gallery rather than carrying the old number forward.
|
||||
|
||||
@ -38,10 +38,19 @@ const THUMB = { width: 256, height: 144 };
|
||||
* watches two videos recognises the shot rather than the song. It is a property
|
||||
* of the scene, not of the generator, so it is the scene that has to be lifted.
|
||||
*
|
||||
* 0.04 is a starting bar rather than a derived one. Set where the flat cluster
|
||||
* measurably sits, to be raised as scenes are brought up to it.
|
||||
* 0.1, and deliberately a target rather than a description of where the library
|
||||
* currently sits. Most of it does not clear this — of the scenes measured after
|
||||
* the region block went in, only Droste Feedback at 0.324 is comfortably over,
|
||||
* with Plasma Bloom at 0.082 and Voronoi Shatter at 0.062 still short. A bar set
|
||||
* where the work already is measures nothing.
|
||||
*
|
||||
* It has moved twice for instrument reasons rather than taste — the motion block
|
||||
* was reading zero for the whole library, and adding the region block made the
|
||||
* total a mean over six rather than five — and both raised every score. Expect
|
||||
* to re-read it off a fresh gallery whenever the descriptor changes, rather than
|
||||
* carrying the old number forward.
|
||||
*/
|
||||
export const MIN_VARIETY = 0.04;
|
||||
export const MIN_VARIETY = 0.1;
|
||||
|
||||
/**
|
||||
* Six contexts, one per song: everything a scene is handed when it is cast.
|
||||
|
||||
@ -69,6 +69,7 @@ import { isometricBlocks } from './shader/isometric-blocks.js';
|
||||
import { halftoneMisprint } from './shader/halftone-misprint.js';
|
||||
import { drosteFeedback } from './shader/droste-feedback.js';
|
||||
import { analogWow } from './shader/analog-wow.js';
|
||||
import { mountainFlight } from './shader/mountain-flight.js';
|
||||
|
||||
/**
|
||||
* The scene library. Families exist so the arc driver can choose by section
|
||||
@ -163,6 +164,7 @@ const MODULES = [
|
||||
constellation,
|
||||
soloist,
|
||||
swarm,
|
||||
mountainFlight,
|
||||
];
|
||||
|
||||
const errors = [];
|
||||
|
||||
142
flow-state/src/scenes/shader/mountain-flight.js
Normal file
142
flow-state/src/scenes/shader/mountain-flight.js
Normal file
@ -0,0 +1,142 @@
|
||||
// Structural family: a flight through mountains. A real perspective camera
|
||||
// travelling forward over a raymarched heightfield — peaks rise, pass to either
|
||||
// side and occlude what is behind them, with a snowline near the top and the
|
||||
// ridge silhouettes drawn in the song's hand.
|
||||
//
|
||||
// Ridge Terrain is the neighbour, and it is the flat version of this: parallax
|
||||
// bands of 1D noise that slide sideways and can never hide each other, because
|
||||
// there is no depth for them to hide in. Here the image is a 3D field sampled
|
||||
// along the ray, so the motion is INTO the frame rather than across it, valleys
|
||||
// open and close as the camera passes through them, and a near peak eats a far
|
||||
// one. The massifs themselves are the song's protagonist, extruded: a hollow
|
||||
// cast makes calderas, a notched one makes star-shaped ridges.
|
||||
|
||||
export const mountainFlight = {
|
||||
name: 'Mountain Flight',
|
||||
family: 'structural',
|
||||
kind: 'fragment',
|
||||
// Personality: see look/Personality.js. Identity: see look/Identity.js.
|
||||
consumes: ['cast', 'ink', 'staging'],
|
||||
traits: ['camera', 'space', 'style'],
|
||||
|
||||
params: {
|
||||
relief: { type: 'float', range: [0.15, 1.4], default: 0.55, uniform: 'u_relief', bias: 'energy' },
|
||||
rugged: { type: 'float', range: [0.15, 1.1], default: 0.45, uniform: 'u_rug', bias: 'density' },
|
||||
ridges: { type: 'int', range: [2, 5], default: 4, uniform: 'u_oct', bias: 'density' },
|
||||
massif: { type: 'float', range: [0.1, 0.9], default: 0.5, uniform: 'u_massif' },
|
||||
span: { type: 'float', range: [4, 16], default: 8.0, uniform: 'u_span', bias: 'density' },
|
||||
altitude: { type: 'float', range: [0.15, 2.2], default: 0.7, uniform: 'u_alt', slowAxis: true },
|
||||
snowline: { type: 'float', range: [0.15, 1.1], default: 0.55, uniform: 'u_snow', bias: 'energy' },
|
||||
speed: { type: 'float', range: [0.1, 2.5], default: 0.8, uniform: 'u_speed', bias: 'motion', rate: true },
|
||||
palette: { type: 'palette', count: 6 },
|
||||
},
|
||||
|
||||
reactive: {
|
||||
relief: { feature: 'bandLow', amount: 0.22, response: 'smooth' },
|
||||
snowline: { feature: 'bandHigh', amount: 0.2, response: 'smooth' },
|
||||
altitude: { feature: 'bandSub', amount: 0.15, response: 'smooth' },
|
||||
},
|
||||
|
||||
shader: `
|
||||
// The massif standing in the world cell around w: the song's protagonist, used
|
||||
// as a PLAN rather than a picture — its footprint is extruded into a mountain.
|
||||
// Kept strictly inside its own cell so one sample of one cell is the whole
|
||||
// answer, which is what makes it affordable inside the march.
|
||||
float massifH(vec2 w) {
|
||||
vec2 cell = floor(w / u_span);
|
||||
vec2 local = w - (cell + 0.5) * u_span;
|
||||
|
||||
// Which node of the song's lattice this cell got, and how big it stands.
|
||||
vec3 node = stageNode(floor(hash12(cell) * 7.0), 8.0);
|
||||
vec2 at = clamp(node.xy, -1.0, 1.0) * u_span * 0.18;
|
||||
float s = u_span * 0.2 * clamp(u_massif * node.z, 0.08, 1.1);
|
||||
|
||||
float d = castMain((local - at) / s) * s;
|
||||
// Flanks fall away from the footprint's edge; the summit is its middle.
|
||||
float m = sat(-d / max(s * 0.9, 1e-3));
|
||||
return pow(m, 0.7) * u_relief * 1.7;
|
||||
}
|
||||
|
||||
float terrainH(vec2 w) {
|
||||
// The song's element size is the size of the whole landscape's vocabulary:
|
||||
// a few enormous massifs, or a crowd of small ones.
|
||||
vec2 q = w * u_rug / max(stageScale(), 0.25);
|
||||
|
||||
float sum = 0.0, norm = 0.0, amp = 1.0;
|
||||
for (int i = 0; i < 5; i++) {
|
||||
if (i >= u_oct) break;
|
||||
// Ridged, not billowy: mountains have crests.
|
||||
float n = 1.0 - abs(vnoise(q) * 2.0 - 1.0);
|
||||
sum += n * n * amp;
|
||||
norm += amp;
|
||||
q = rot(0.63) * q * 2.03;
|
||||
amp *= 0.45;
|
||||
}
|
||||
return (sum / max(norm, 1e-3)) * u_relief + massifH(w);
|
||||
}
|
||||
|
||||
vec4 scene(vec2 uv, vec2 p) {
|
||||
float t = u_time * u_speed + u_seed;
|
||||
p = sigCamera(p);
|
||||
|
||||
// The camera rides a fixed height over whatever it is flying across, so a
|
||||
// tall massif lifts the flight path instead of swallowing it.
|
||||
vec3 ro = vec3(u_seed * 3.7, 0.0, t * 4.0);
|
||||
ro.y = terrainH(ro.xz) + u_alt * (1.0 + stageScale() * 0.3);
|
||||
|
||||
// The track's horizon decides how far down the camera is looking.
|
||||
vec3 rd = normalize(vec3(p.x, p.y - sigHorizonY() * 0.5, 1.6));
|
||||
|
||||
float dist = 0.4;
|
||||
float hit = 0.0;
|
||||
float gap = 0.0;
|
||||
float grazed = 1e3; // closest the ray came, per unit distance
|
||||
|
||||
// A march ends on its hit or its far plane, not on a param.
|
||||
// lint: fixed-cost
|
||||
for (int i = 0; i < 72; i++) {
|
||||
vec3 at = ro + rd * dist;
|
||||
gap = at.y - terrainH(at.xz);
|
||||
grazed = min(grazed, gap / max(dist, 1.0));
|
||||
if (gap < 0.002 * dist) { hit = 1.0; break; }
|
||||
// Cone stepping: safe near the surface, cheap out at the far end.
|
||||
dist += max(gap * 0.5, 0.03 + dist * 0.02);
|
||||
if (dist > 46.0) break;
|
||||
}
|
||||
|
||||
float fog = sat(dist / 46.0);
|
||||
vec3 col;
|
||||
|
||||
if (hit > 0.5) {
|
||||
vec3 at = ro + rd * dist;
|
||||
float e = 0.02 + dist * 0.006;
|
||||
vec3 nrm = normalize(vec3(
|
||||
terrainH(at.xz - vec2(e, 0.0)) - terrainH(at.xz + vec2(e, 0.0)),
|
||||
2.0 * e,
|
||||
terrainH(at.xz - vec2(0.0, e)) - terrainH(at.xz + vec2(0.0, e))));
|
||||
|
||||
vec3 sun = normalize(vec3(0.55, 0.42, -0.5));
|
||||
float diff = sat(dot(nrm, sun));
|
||||
|
||||
float elev = sat(at.y / max(u_relief * 2.2, 0.2));
|
||||
// Snow lies high, and only where the slope will hold it.
|
||||
float snow = smoothstep(u_snow, u_snow + 0.16, elev * (0.45 + 0.75 * nrm.y));
|
||||
|
||||
vec3 rock = mix(pal(2), pal(3), sat(elev * 1.3));
|
||||
col = mix(rock, pal(5), snow);
|
||||
col *= 0.22 + 0.9 * diff;
|
||||
col += pal(1) * 0.12 * sat(nrm.y); // sky bounce into the flats
|
||||
} else {
|
||||
col = mix(pal(1) * 0.45, pal(0) * 0.22, sat(rd.y * 2.2 + 0.15));
|
||||
fog = 0.85;
|
||||
// The ridgeline the sky is cut against, drawn at the song's weight.
|
||||
col += pal(4) * inkStroke(grazed * 0.6) * 0.5;
|
||||
}
|
||||
|
||||
col = sigAir(col, p, fog);
|
||||
return vec4(inkValue(col), 1.0);
|
||||
}
|
||||
`,
|
||||
};
|
||||
|
||||
export default mountainFlight;
|
||||
Loading…
Reference in New Issue
Block a user