Write up the three side quests Epic 2 left behind
All three were debts taken on knowingly, with the reason recorded at the time. Writing them down so that picking one up does not mean reconstructing why it exists — each gets the measurement that justifies it, a concrete approach naming the actual files and lines, the existing gate that says when it is done, and the wrong fix it must not reach for. 1. Four scenes (Classic Wave, Silk Ribbon, Kaleido Tunnel, Slow Orb) declare the style trait and honour it with `col += sigGrain(uv)` and nothing else, so they cannot decline the track's grain the way other scenes now can. Each already has an edge-weight or softness knob to route u_sigLine and u_sigSoft into. Parked at texture: 0.35, which keeps grain on the library's cleanest scenes purely to keep a gate green. 2. Curl Flow, Signal Decay and Circuit Bloom have no parameter that changes their structure — 1.31x, 1.21x and 1.10x against a 1.25x bar. They are statistically identical everywhere and at all times, which is what "it looks the same for five minutes" means technically. Needs structure, not motion; they already move too much. 3. Horizon Lines reports a 2/255 determinism delta when phase 7 runs alone and passes in a full run. Invisible as an image, but a gate whose verdict depends on preceding GPU load will eventually stay green through a real regression on the one property this whole project is built on. Prime suspects are named: 40 accumulated line terms, and a smoothstep edge as thin as 0.002 evaluated on a cancelling subtraction. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
parent
b7c6fd1c5a
commit
8dfd8392f3
199
flow-state/SIDE-QUESTS.md
Normal file
199
flow-state/SIDE-QUESTS.md
Normal file
@ -0,0 +1,199 @@
|
||||
# Side quests
|
||||
|
||||
Three follow-ups left behind by Epic 2. None of them blocks anything; all three are debts that
|
||||
were taken on knowingly, with the reason recorded at the time. They are written up here so that
|
||||
picking one up does not require reconstructing why it exists.
|
||||
|
||||
Each is independent. Each has an existing gate that will tell you when it is done.
|
||||
|
||||
| # | quest | size | what it unblocks |
|
||||
|---|---|---|---|
|
||||
| 1 | Four scenes express `style` only through grain | small | lets them opt out of grain entirely |
|
||||
| 2 | Three scenes have no structure a parameter can move | medium, per-scene | the open half of EPIC-2 §3.4 |
|
||||
| 3 | Horizon Lines determinism flake | small, investigative | a gate that currently depends on GPU load |
|
||||
|
||||
---
|
||||
|
||||
## 1. Four scenes whose only art direction is grain
|
||||
|
||||
### What is wrong
|
||||
|
||||
`Classic Wave`, `Silk Ribbon`, `Kaleido Tunnel` and `Slow Orb` all declare the `style`
|
||||
personality trait. A scene that declares a trait is promising it visibly honours it — that is a
|
||||
contract, not a hint, because the disqualification rule in `Personality.js` is the only thing
|
||||
keeping off-design scenes out of a track.
|
||||
|
||||
These four honour it with exactly one line:
|
||||
|
||||
```glsl
|
||||
col += sigGrain(uv);
|
||||
```
|
||||
|
||||
That is the whole of their art direction. So when the grain work (commit `14d1204`) let a scene
|
||||
decline the track's surface grain with `texture: 0`, these four could not take it: Phase 9's
|
||||
"every declared trait visibly changes the scene that declares it" measured their style response
|
||||
at exactly **0**. They were parked at `texture: 0.35` as a stopgap, which is the wrong shape of
|
||||
fix — it keeps grain on scenes that would look better clean, purely so a gate stays green.
|
||||
|
||||
### Why it matters
|
||||
|
||||
The user's original complaint was that grain was everywhere. Three of these four are the
|
||||
library's cleanest, softest scenes — a drifting orb, a silk ribbon, a ring wave. They are
|
||||
exactly the scenes that should be able to say "not on me".
|
||||
|
||||
### The approach
|
||||
|
||||
Each already has a parameter that *is* an edge-weight or a softness knob. The work is routing
|
||||
`u_sigLine` and `u_sigSoft` into it, so the track's hand shows in the line quality rather than
|
||||
in the dirt. Concretely, per scene:
|
||||
|
||||
- **Classic Wave** — `src/scenes/shader/classic-wave.js:50` already has
|
||||
`v = mix(v, smoothstep(0.2, 0.8, v), u_softness)`. Widen or narrow that smoothstep by
|
||||
`u_sigSoft`, and let `u_sigLine` drive how hard the ring crests read.
|
||||
- **Silk Ribbon** — `:61` is `exp(-best * best / (u_thickness * u_thickness))`. The strand width
|
||||
is `u_thickness` (range `0.008..0.08`); scale it by `u_sigLine`, and put `u_sigSoft` on the
|
||||
falloff exponent so a soft-handed track gets a haze and a sharp one gets a filament.
|
||||
- **Kaleido Tunnel** — draws its grid with `smoothstep(0.42, 0.0, ringLines)`. That constant is
|
||||
a line weight with no name. Drive the threshold from `u_sigLine` and the smoothstep width from
|
||||
`u_sigSoft`.
|
||||
- **Slow Orb** — `:47` is `smoothstep(u_softness * 0.5, -u_softness * 0.5, d)`, the body edge.
|
||||
Fold `u_sigSoft` into it, and consider a `sigEdge(d)` rim so the shape reads in the track's
|
||||
hand rather than only in its own.
|
||||
|
||||
Then set `texture: 0` on the ones that genuinely want to be clean — probably all four, but that
|
||||
is a judgement call to make while looking at them, not now.
|
||||
|
||||
### Done when
|
||||
|
||||
- Phase 9's trait check reports a non-zero style delta for all four with `texture: 0`.
|
||||
- `npm run lint:scenes` still finds style evidence in each shader (it greps for `sigEdge` /
|
||||
`sigGrain` / `sigFolded` / `u_sigLine` / `u_sigSoft` / `u_sigTexture` / `u_sigFold`).
|
||||
- The Phase 11 grain checks are unaffected — this quest must not put grain back.
|
||||
|
||||
### Do not
|
||||
|
||||
Do not solve this by dropping `style` from their `traits` arrays. That would pass every gate and
|
||||
would shrink the library for every style-signature track, which is the opposite of the point.
|
||||
|
||||
---
|
||||
|
||||
## 2. Three scenes with no structure to move
|
||||
|
||||
### What is wrong
|
||||
|
||||
EPIC-2 §3.4 gave scenes a **slow axis**: a declared parameter walked one way across the whole
|
||||
track, so the frame at four minutes is not the frame at thirty seconds. It works — Moiré Grid
|
||||
3.93×, Gate Corridor 2.88×, Truchet Fold 1.40×, measured as axis-driven structural change
|
||||
against what the scene does on its own.
|
||||
|
||||
For three scenes it cannot work, because **no parameter changes their structure**:
|
||||
|
||||
| scene | best parameter | ratio |
|
||||
|---|---|---|
|
||||
| `curl-flow.js` | `veins` | 1.31× |
|
||||
| `signal-decay.js` | `traces` | 1.21× |
|
||||
| `circuit-bloom.js` | `cells` | 1.10× |
|
||||
|
||||
Sweeping any parameter through most of its range barely moves the ten-second time-averaged
|
||||
image. That is the technical statement of "it looks the same for five minutes": these scenes are
|
||||
statistically identical everywhere and at all times. They churn — every pixel moving, nothing
|
||||
developing — and the eye adapts in about two seconds.
|
||||
|
||||
### Why it matters
|
||||
|
||||
Churn is the failure that survives every other fix. Cut rhythm, colour movement and framing all
|
||||
change *what surrounds* the image; none of them change the fact that the image itself has
|
||||
nothing to find after two seconds.
|
||||
|
||||
### The approach
|
||||
|
||||
This is genuinely shader work and it is not mechanical — that assessment was wrong when EPIC-2
|
||||
was first written and is corrected in §3.4. Each scene needs a persistent large-scale structure
|
||||
that a parameter can then move. Sketches, one per scene, to be argued with rather than followed:
|
||||
|
||||
- **Curl Flow** is a curl-noise field at one scale, so it looks the same everywhere by
|
||||
construction. Give it a low-frequency term that varies across the frame — a density gradient,
|
||||
a region where the flow stalls, a dominant current — and put the parameter that positions or
|
||||
scales *that* on the axis.
|
||||
- **Signal Decay** already quantises to an era grid (`floor(u_barPhase * 8.0)`), which is good
|
||||
rhythm and no structure: every trace is the same kind of trace. Consider making traces
|
||||
differ from one another — a hierarchy, one dominant channel, a slow reordering — so the
|
||||
arrangement can change over minutes.
|
||||
- **Circuit Bloom** is a uniform grid of cells with per-cell hashes. The `reach` term already
|
||||
gates growth by distance from the centre; that is the seed of a structure. Let the board grow,
|
||||
reroute or densify along a slow axis instead of being fully populated from frame one.
|
||||
|
||||
Then declare the parameter with `slowAxis: true` in the params block.
|
||||
|
||||
### Done when
|
||||
|
||||
Phase 11's `a declared slow axis actually changes the scene` holds the scene at **1.25×**, and
|
||||
its companion check still reads ~1.00× with the axis disabled. Run:
|
||||
|
||||
```bash
|
||||
http://localhost:5180/checks.html?phase=11&slow=1
|
||||
```
|
||||
|
||||
### Do not
|
||||
|
||||
Do not add `slowAxis: true` before the shader can back it up — the gate will fail, correctly.
|
||||
And do not reach for more motion: these scenes already move too much. The missing thing is
|
||||
structure, which is the opposite of motion.
|
||||
|
||||
---
|
||||
|
||||
## 3. The Horizon Lines determinism flake
|
||||
|
||||
### What is wrong
|
||||
|
||||
Phase 7's `every scene is deterministic` renders each scene twice and requires a max channel
|
||||
delta of ≤ 1/255. `Horizon Lines` reports **2**, but only sometimes:
|
||||
|
||||
```bash
|
||||
http://localhost:5180/checks.html?phase=7&slow=1 # FAILS — delta 2
|
||||
http://localhost:5180/checks.html?slow=1 # PASSES — delta ≤ 1
|
||||
```
|
||||
|
||||
Verified present on `f050eaa`, so it predates Epic 2. The difference is how much GPU work ran
|
||||
before it: in a full suite, phases 0-6 warm the device first.
|
||||
|
||||
### Why it matters
|
||||
|
||||
Not for the image — 2/255 is invisible, and PLAN.md §1 already accepts 1/255 as the floor the
|
||||
hardware offers. It matters because **a gate whose verdict depends on preceding load will
|
||||
eventually stay green through a real determinism regression**, and determinism is the single
|
||||
architectural constraint this whole project is built on. A flaky guard on the load-bearing
|
||||
property is worse than no guard, because it is trusted.
|
||||
|
||||
### The approach
|
||||
|
||||
Two steps, in order.
|
||||
|
||||
**First, find out why this scene and not the other 41.** The suspects are in
|
||||
`src/scenes/shader/horizon-lines.js`:
|
||||
|
||||
- The loop accumulates up to **40** line contributions into `col`. Summing many small terms is
|
||||
where float ordering shows up, and no other scene in the library sums this many.
|
||||
- `float line = smoothstep(u_thickness * (0.5 + u_sigLine), 0.0, d)` with `u_thickness` as low
|
||||
as **0.002**. That is a razor-thin edge: a pixel sitting on it is decided by the last bit of
|
||||
`d`, and `d = abs(p.y - y)` is a subtraction of two nearby numbers — cancellation, right where
|
||||
the result is most sensitive.
|
||||
- `exp(-d * 26.0)` on the same `d`.
|
||||
|
||||
If reformulating the line term stabilises it — accumulating in a saturated or normalised form,
|
||||
or widening the minimum edge so no pixel sits exactly on a discontinuity — that is the real fix
|
||||
and it costs nothing visually.
|
||||
|
||||
**Only if it cannot be made stable**, fix the check instead: either warm the GPU to a known
|
||||
state before measuring, or raise the tolerance with the reason written into the comment that
|
||||
already explains the 1-LSB rationale. Either is defensible; silently raising the number is not.
|
||||
|
||||
### Done when
|
||||
|
||||
`?phase=7&slow=1` passes in isolation, repeatedly, on a cold device.
|
||||
|
||||
### Do not
|
||||
|
||||
Do not raise the tolerance as the first move. The whole value of this check is that a real bug
|
||||
scores in the tens or hundreds while the hardware floor is 1 — that separation is what makes it
|
||||
worth having, and widening it without understanding the cause spends it for nothing.
|
||||
Loading…
Reference in New Issue
Block a user