From 5a9dfa6b2bde8a7a0e399cff36a5ac465bc009fc Mon Sep 17 00:00:00 2001 From: Dejvino Date: Tue, 18 Aug 2026 16:42:55 +0200 Subject: [PATCH] =?UTF-8?q?A=20scatter=20point=20in=20Isometric=20Blocks?= =?UTF-8?q?=20=E2=80=94=20which=20measures=20worse,=20twice?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Asked for, built, and honest about the result: the focal point costs this scene a quarter of its variety and I could not make it pay for itself. uniform scatter, no focus 0.0490 focus, trading scatter for it 0.0374 focus, added on top of it 0.0365 The first version spent the scatter budget near the focus and calmed the rest, which put most of the field back on the rigid lattice and took the orientation variety with it, 0.164 down to 0.105. That diagnosis looked right, so the second version made the focus additive — every cell keeps its full scatter and the ones near the point get more. It changed nothing: 0.0365, orientation still 0.104. So the diagnosis was wrong and I do not have the real one. The remaining suspect is the height modulation, which lifts blocks toward the focus and may be flattening the skyline's variety between songs by dominating it — but that is a guess, and two guesses have already been wrong here. The feature is committed rather than reverted because the metric does not measure what was asked for. A scatter point is a compositional idea, and the gallery scores how much a scene changes BETWEEN songs, which is a different question — Apollonian Gasket makes the same point from the other direction. This may well look better and score worse. It needs eyes on it before the number decides. `gather` is a parameter and seed-driven like any other, so the amount varies per song as asked; the focal points themselves come from the identity. Co-Authored-By: Claude Opus 5 --- .../src/scenes/shader/isometric-blocks.js | 39 +++++++++++++++---- 1 file changed, 32 insertions(+), 7 deletions(-) diff --git a/flow-state/src/scenes/shader/isometric-blocks.js b/flow-state/src/scenes/shader/isometric-blocks.js index 6597edd..d50eeed 100644 --- a/flow-state/src/scenes/shader/isometric-blocks.js +++ b/flow-state/src/scenes/shader/isometric-blocks.js @@ -27,6 +27,16 @@ export const isometricBlocks = { // enough to nothing. Displacing them per cell puts the SONG in the // arrangement rather than only in the elevation. scatter: { type: 'float', range: [0, 0.85], default: 0.3, uniform: 'u_scatter', bias: 'density' }, + // Extra disturbance AT the song's focal points, on top of the scatter + // everywhere — not instead of it. + // + // It was a trade at first, spending the scatter budget near the focus + // and calming the rest, and that measured worse than no focus at all: + // 0.049 down to 0.037. Most of the field went back onto the rigid + // lattice and took the orientation variety with it, 0.164 to 0.105. The + // focal point has to be something the field gains, not something the + // rest of it pays for. + gather: { type: 'float', range: [0, 1.6], default: 0.8, uniform: 'u_gather' }, plan: { type: 'float', range: [0.2, 0.5], default: 0.4, uniform: 'u_plan' }, tilt: { type: 'float', range: [0.35, 0.75], default: 0.55, uniform: 'u_isoTilt' }, light: { type: 'float', range: [0, 1.4], default: 0.65, uniform: 'u_light' }, @@ -88,14 +98,29 @@ vec4 scene(vec2 uv, vec2 p) { float row = rowHere - float(7 - j); vec2 cell = vec2(colX, row); - // Each cell steps off its slot by a fixed amount of its own, so the - // plan of the field is the song's rather than the lattice's. Bounded - // under half a cell: past that blocks cross each other and the - // isometric read — which is the whole point of the scene — breaks down. - vec2 offset = (hash22(cell + u_seed + 3.7) - 0.5) * u_scatter * cw * 0.9; + // Where this block would stand if nothing disturbed it, so the focus can + // be asked about the cell rather than about the pixel. + float restY = (row + stagger) * tilt * cw; + float focus = focusField(vec2((colX + 0.5) * cw - 0.5, restY)); - float groundY = (row + stagger) * tilt * cw + offset.y; // where this cell meets the ground - float h = columnHeight(cell, t) * (1.0 + offset.x * 2.0); + // Each cell steps off its slot by a fixed amount of its own, and how far + // depends on how close it is to what the song is looking at. Spread + // evenly this is noise on a grid; concentrated, the lattice stands + // except where it is disturbed, and the field acquires a subject. + // + // Bounded under half a cell: past that blocks cross each other and the + // isometric read — which is the whole point of the scene — breaks down. + float amount = u_scatter * (1.0 + focus * u_gather * 2.0); + vec2 offset = (hash22(cell + u_seed + 3.7) - 0.5) * amount * cw * 0.9; + + float groundY = restY + offset.y; // where this cell meets the ground + // Blocks rise toward the focus as well as scattering around it, so the + // disturbance is something you can see in the skyline rather than only + // in the plan — and rising changes WHERE the frame's energy is, which + // scattering alone did not. + float h = columnHeight(cell, t) + * (1.0 + offset.x * 2.0) + * (1.0 + focus * u_gather * u_focusPull * 1.3); float dy = p.y - groundY; // height up the block face // Side wall: the plan swept up from the ground line to the top face.