# VM6 — does the wind move the trees, and only the trees? **Date:** 2026-08-23 · **Campaign:** VM slice VM6 · **Status:** see the verdict at the end; the owner's visual gate (plan §VM6 Acceptance) is still owed regardless. **Question.** With the built-in `acdream.atmospheric` pack, does the foliage-wind displacement reach the **production** world geometry, and is it confined to procedural scenery foliage? This note records the apparatus that finally answered it, including the first method that did not — two wrong conclusions were drawn from it before the confound was found, and both are recorded here so nobody re-derives them. ## Apparatus Offline pixel gate (`tools/run-offline-pixel-gate.ps1`, Release, `-SkipBuild`, `-Uncapped`, 1280×720, day group 0 "Sunny" → `WeatherKind.Clear`, day fraction 0.5, MSAA 0), isolated settings, the fixed offline scene the VM0/VM3 captures use, drawn through `RetailPViewPassExecutor.DrawPackedProductionRoute` (the production entity route). `-RenderPackPreset high`; `ACDREAM_SKY_PHASE_SECONDS` pins the pack's per-graph clock so two captures at the same pin see the same wind phase. Pixel metric: max-channel |Δ| ≥ 8 (`tools/vm6/wind-pixel-proof.py`, numpy). ### Method 1 (confounded — do not reuse): "t0→t3 with wind on, minus t0→t3 with wind off" The idea was that the clock pin at 0 s vs 3 s moves only the wind, so the wind-off pair is a control for everything else the clock drives. It is not: the same binary, same settings, same pin differs run-to-run by **0 or ~280 px**, bimodally, and those pixels are isolated single-pixel flips along the treeline's leaf edges (`artifacts/vm6amp/churn-crop.png`) — rasterisation edge flips, not content. Whether the churn lands in the wind pair or the control pair is luck, so "wind-only" counts of 23 / 314 / 568 / 703 px from this method on `43e3abed` / `a82959f1` were all within that churn. The overlay images looked convincing precisely because the churn sits on tree silhouettes. Two conclusions drawn from it — "the F1 packed-classifier fix is confirmed by pixels" and "wind reaches the geometry" — were unsupported. (The F1 fix itself is correct; the round-3 Opus review traced it to the GPU word by reading, and the round-4 apparatus below confirms geometry motion.) ### Method 2: same pin, wind on vs off, signal amplified, repeats as the floor Settings `wind-strength = 2`, `wind-lean-metres = 1`, `wind-branch-metres = 1`, `wind-flutter-metres = 0.5` make the canopy displacement ~1 m — several pixels even at this scene's treeline distance, where the default Clear wind (≤ 11 cm lean) is sub-pixel. Two captures per arm; the repeat pairs measure the floor; the four cross pairs measure the effect. ## Results ### `fccba839` (round 3), `sun-shadow-strength = 0` | Pair | px ≥ 8 | |---|---| | wind-off A vs wind-off B (floor) | 22 | | amplified-wind A vs amplified-wind B (floor + churn) | 317 | | amplified A vs wind-off A / B | 65 / 49 | | amplified B vs wind-off A / B | 284 / 273 | | robust mask (all four cross pairs, neither repeat pair) | **1** | A 1 m wind produced nothing. A CPU probe in `ResolveFoliageWind` (removed after the run) showed the state was right — first advance at serial 2 snaps to Clear `(0.25, 0.15)`, strength 1, gate on, one graph instance — so the uniform was correct and the world pass never used it. **Cause (by reading, round-4 finding):** the wind was welded to "directional shadows rendered this frame". `DirectionalSunShadowRenderer.Render` left the frame binding `Disabled` whenever the environment gate said no; `WbDrawDispatcher.PipelinesFor` then fell back to the plain `mesh_modern` pipeline, which has no `foliage_wind.glsl` at all; and `BindDirectionalShadowReceiver` returned early on `!Enabled`. The gate is `dayGroupPolicy × sunElevationResponse × strength`, so the wind also stopped every night, at user strength 0 and under the portal cover. Fixed in round 4 by publishing a valid-but-disabled binding (zeroed shadow block, flags 0 — the receiver shader already returns visibility 1.0 on that bit — plus the wind frame slice) so the built-in pack's world pass always runs the receiver pipeline. ### `eec95535` (round 4), `sun-shadow-strength = 0` | Pair | px ≥ 8 | |---|---| | wind-off A vs wind-off B (floor) | 19 | | amplified-wind A vs amplified-wind B (repeat) | 51 | | amplified A/B vs wind-off A/B (four cross pairs) | 13,876 / 13,887 / 13,893 / 13,903 | | **robust mask (all four cross pairs, neither repeat pair)** | **13,854** | | **default Clear strength vs both wind-off, floor-excluded** | **1,402** (2,655 at |Δ| ≥ 3) | Per-60-row band of the robust mask: `[8580, 4101, 800, 61, 0, 110, 14, 188, 0, 0, 0, 0]` — the treeline (rows 0–180), the hillside trees (rows 300–360) and the shoreline bushes (rows 420–480). Overlays: `evidence/vm6/eec95535-amplified-wind-vs-off-shadows-off.png` (every tree and bush red; no house, fence, road, lifestone, ground, water or UI pixel), `evidence/vm6/eec95535-default-clear-wind-vs-off-shadows-off.png`, the amplified frame itself `evidence/vm6/eec95535-amplified-wind-frame.png`, and the Method-1 churn crop `evidence/vm6/method1-run-to-run-churn-crop.png`. ### `754d59d9` (round 5 — gated-off frames light from the authored sun again) The round-4 narrow review found that round 4's decoupling lit every shadow-gated-off frame from straight overhead: both receiver vertex shaders take the sun *direction* from the shadow block, and the disabled block's `(0,0,1)` guard became the sun. Round 5 makes the receiver verts fall back to the plain pipeline's `uLights` expression when the flag bit is clear. Same apparatus, wind-off arm compared across binaries (shadows off, same pin): | Pair | px ≥ 8 | mean |Δ| | |---|---|---| | `754d59d9` wind-off A vs B (floor) | 65 | 0.002 | | **`754d59d9` vs `fccba839` (plain pipeline, pre-round-4)** | **277** | **0.007** | | `eec95535` vs `fccba839` (the round-4 regression) | 32,538 | 1.77 | Wind on `754d59d9`: robust mask **14,993 px** (amplified), same bands as round 4. ## Verdict On `754d59d9` the foliage wind reaches the production world geometry with the shadow term removed (so it is geometry, not shadow), is 700× the run-to-run floor when amplified and 70× at the default Clear strength, and is confined to procedural scenery foliage in this scene. Earlier binaries: `43e3abed` had no geometry wind at all on the production route (round-2 review F1, confirmed by reading); `a82959f1`/`fccba839` had it only while the shadow gate was open (round-4 defect); `eec95535` had the wind but lit shadow-gated-off frames from overhead (round-5 defect). Commands: `tools/run-offline-pixel-gate.ps1 -Out