diff --git a/flow-state/src/look/shots.js b/flow-state/src/look/shots.js index 53b3df2..6ca2304 100644 --- a/flow-state/src/look/shots.js +++ b/flow-state/src/look/shots.js @@ -137,7 +137,18 @@ export function planShots(section, track, bias, variantCount, rng, story = null) const lengths = fitPattern(rhythmFor(bias.energy, rng, story), barSeconds); const shortest = Math.min(...lengths); - // A section with only one visual to show has nothing to cut to. + // A section with only one visual to show has nothing to cut TO. It still + // gets shot boundaries: a boundary changes the FRAMING as well as the + // image, so the same visual filmed again at another size is a shot, and the + // ceiling is about how long one image is held rather than about how many + // images a section has. + // + // This used to skip cut planning entirely, and the exemption swallowed the + // ceiling with it — measured, a 150 BPM track held one visual for 151.98 + // seconds, seven times the limit, which is exactly the complaint this whole + // level of hierarchy exists to answer. What it does still suppress is the + // hard cut: cutting straight between two framings of one image is a jump + // cut, so those boundaries always dissolve. const single = variantCount < 2; // Walk the pattern, laying shots end to end from the section start. Each cut @@ -149,7 +160,7 @@ export function planShots(section, track, bias, variantCount, rng, story = null) const downbeats = track.tempo.downbeats || []; const cuts = []; - if (!single) { + { let at = section.start; for (let k = 0; k < 512; k++) { const raw = at + lengths[k % lengths.length]; @@ -193,7 +204,10 @@ export function planShots(section, track, bias, variantCount, rng, story = null) // edited, but on anything calmer it reads as a glitch, so cuts are // gated on real energy rather than sprinkled everywhere: nothing below // the threshold ever cuts, and only the loudest material cuts often. - hardCut: bias.energy > HARD_CUT_ENERGY + // A single-visual section changes framing, not image. Cutting hard + // between two framings of the same thing is a jump cut, so those + // boundaries always dissolve. + hardCut: !single && bias.energy > HARD_CUT_ENERGY && rng.bool(Math.min(0.85, (bias.energy - HARD_CUT_ENERGY) * 2.5)), }); }