WORKFLOW TEST — autonomous opt-in #7

Closed
opened 2026-07-30 08:03:44 +00:00 by jeroen · 2 comments
Owner

Problem

"Make the water look better somehow" has no defect behind it, and the local water shader is not a rough draft — it is a deliberate, documented design. Before this can be implemented, someone has to say which of several unrelated changes is wanted, because they differ by two orders of magnitude in cost.

What exists today (local tier only):

  • assets/shaders/water.wgsl is an ExtendedMaterial over StandardMaterial: 5 sharpened directional octaves with per-octave distance fade (water.wgsl:107189), crest wobble to break the corduroy pattern (water.wgsl:153), deep-water dispersion (water.wgsl:137), fresnel depth tint (water.wgsl:237), whitecaps masked on chop-riding-a-crest (water.wgsl:257), and a shore lap line (water.wgsl:268).
  • A per-cell rasterised distance-to-shore field (vector_tiles.rs:1259) drives shelter, a shallow tint and the foam line (water.wgsl:215, :245, :269).
  • Sea state is wind-driven from live weather (water.rs:214238, water.rs:244).

So the obvious wins are already spent. The genuine gaps I can cite are these, and they are not equivalent:

A. The waves always travel the same compass direction, whatever the weather is doing. sea_state takes its direction from CLOUD_WINDwater.rs:217 — which is Vec2::new(0.04, 0.015) at main.rs:2380, a cloud drift velocity in texture units, reused as a bearing. Normalised it is a constant ≈ENE, forever. Meanwhile Observation (weather.rs:4352) carries only wind_ms; parse_weather reads wind_speed at weather.rs:176 and drops the rest. I checked the live endpoint the client already calls — MET's locationforecast/2.0/complete returns wind_from_direction in degrees in the same instant.details object the parser is already inside. The data is fetched and thrown away. Wind speed changes the sea; wind direction does not.

B. Rain does not touch the water. weather.rs:46 carries precipitation, wired into clouds and fog. water.rs and water.wgsl contain no reference to it (grep: zero hits). A downpour leaves the canals glassy.

C. The water is opaque, and cannot cheaply stop being so. WaterSeaState::material builds a StandardMaterial with no alpha_mode (water.rs:147161), so it is AlphaMode::Opaque. The "shallows" are a pure albedo tint (water.wgsl:245250), not transmission. Making it transparent does not buy depth: the water mesh sits at y = -0.004 (vector_tiles.rs:499) directly over the map tile, whose texture has already painted the same polygon flat palette::WATER 0xA8C4DA (vector_tiles.rs:170, :91). You would see flat blue paint through the water, not a bed.

D. The water reflects the sky and nothing else. Reflection comes entirely from AtmosphereEnvironmentMapLight via the standard PBR path (water.rs:1115). A canal in a city therefore does not reflect the houses lining it. Real reflections would need screen-space: the camera spawns with no DepthPrepass and no DeferredPrepass (free_camera.rs:335390), and Bevy's ScreenSpaceReflections is deferred-only — a custom forward fragment shader is exactly the case it does not cover. This is a rendering-architecture change, not a shader tweak.

E. Nobody can see a water change in the documentation image set. None of the six presets in docs/shots.toml frames water: city, city-dusk, avatar, route, horizon, globe. ShotMetrics (shot_harness.rs:243281) has fields for globe tiles and route meshes but nothing for water, so --dump-state cannot answer "did water cells even build here?".

Also worth stating plainly: none of this reaches the space tier. globe_ocean.rs is a separate BRDF-mask system that shares nothing with water.wgsl (globe_ocean.rs:311), and surface geometry only streams from vector sources (map_geometry.rs:243 returns early on a raster template), so on the default raster basemap there is no water shader at all.

Approach

Blocked on the pick in Open questions. My recommendation is A + E, and here is that spec in full so a "yes, do A+E" is enough to start:

  • crates/viberfox/src/systems/weather.rs — add wind_from_deg: f32 to Observation (weather.rs:43), parsed from inst["wind_from_direction"] alongside wind_speed at weather.rs:176. Missing key → keep the current constant bearing rather than defaulting to 0° (north), which would be a silent wrong answer.
  • crates/viberfox/src/systems/water.rssea_state takes the bearing as an argument instead of reading CLOUD_WIND (water.rs:217); convert meteorological from-degrees to a travel direction in the map's XZ frame. update_water_waves (water.rs:244) passes weather.latest's bearing and gains a direction term in its change gate next to WIND_EPSILON (water.rs:64) so a slow veer still rewrites the materials.
  • crates/viberfox/src/main.rsCLOUD_WIND stays as it is; only the water import at water.rs:40 goes.
  • docs/shots.toml + crates/viberfox/src/systems/shot_harness.rs — a water preset, and water_cells / water_rendered counters on ShotMetrics following the route_meshes / route_rendered precedent at shot_harness.rs:279, added to CSV_HEADER (shot_harness.rs:285) and csv_row together.

No shader edit is needed for A: water.wgsl:122 already consumes wind.xy as an arbitrary unit direction.

Acceptance criteria

Conditional on A + E being the chosen option.

  • parse_weather extracts wind_from_direction; a unit test on a fixture body asserts the parsed bearing, and a second asserts a body without the key falls back to the current constant bearing rather than to 0°.
  • A unit test asserts two different bearings produce two different wind.xy in sea_state, and that the existing the_wind_direction_is_a_unit_vector invariant (water.rs:291) still holds for every bearing in 0..360.
  • A unit test pins the meteorological convention: wind_from_direction = 0 (wind from the north) makes the waves travel southward in the map frame. Getting this backwards is invisible in a still image, so it has to be a test.
  • update_water_waves rewrites materials on a direction change alone, with the wind speed unchanged — covered by the epsilon-gate logic, testable without a GPU.
  • CLOUD_WIND is unchanged and still drives the cloud shader at main.rs:2531.
  • docs/shots.toml gains a water preset, and cargo shots --only water writes docs/img/water.png (workstation only).
  • ShotMetrics carries water counts, present in all three sinks (log line, --csv, --dump-state) per the one-field-three-sinks rule in CLAUDE.md.
  • The README image set is regenerated only if the maintainer wants the new preset linked — see Open questions.

Verification

In this container:

cargo check -p viberfox
cargo test -p viberfox --bin viberfox water
cargo test -p viberfox --bin viberfox weather

cargo check does not compile cfg(test) code, so the test run is the real gate.

Workstation only (no GPU here, per the container rules):

cargo shots --only water --csv /tmp/water.csv
cargo run -p viberfox --profile dev-bevy -- --shot /tmp/w.png \
    --at <lat>,<lng>,120 --look=-15,<bearing> --dump-state /tmp/w.json

Anything about whether the water actually looks better — wave direction reading correctly against the visible wind, the shore line, the whitecaps — is unverifiable in this container and needs a human at a workstation. The shader itself cannot even be compiled here.

Out of scope

  • The space tier. globe_ocean.rs is a per-texel BRDF mask, shares no code with water.wgsl, and is not touched.
  • The raster basemap. No vector source means no surface geometry at all (map_geometry.rs:243); water there stays flat paint (vector_tiles.rs:170) and no shader change can reach it.
  • Transparency / refraction / a riverbed — see problem C; it needs the tile texture to stop painting water first.
  • Screen-space reflections — see problem D; it needs a prepass and a deferred path.
  • Rain-on-water (problem B) unless it is explicitly chosen.
  • Any change to CLOUD_WIND or the cloud shader.
  • Perf work on update_water_waves iterating every live material (water.rs:274) — it is gated to a handful of runs an hour and is not a looks problem.

Open questions

  1. Which change do you want? They are not comparable:
    • A — real wind direction (~40 lines, fully testable headlessly, my recommendation). Fixes a factual wrongness: the sea ignores the wind bearing the app already downloads.
    • B — rain on the water (small shader change: precipitation → roughness lift + a dimple term). Needs you to say what rain-struck water should look like here, which is a taste call I cannot make for you.
    • C — transparency / bed (large; requires stopping the tile rasteriser from painting water, then something to see).
    • D — screen-space reflections (largest; prepass + deferred + likely incompatible with the current forward ExtendedMaterial. I would not start here).
  2. For E: which viewpoint? I can add the ShotMetrics counters without input, but a water preset needs a lat/lng/look, and picking one would be guessing. The manifest's own instruction is to fly there and use Dev panel ▸ 📋 Copy as preset — which needs a workstation. Either paste a [[shot]] block here, or name a water body (Noorderhaven? the Verbindingskanaal? the open Eemskanaal?) and accept that the exact framing gets tuned by whoever runs cargo shots.
  3. Should the new preset be linked from the README, or is it a regression-checking image only? The README currently links the six existing presets; adding a seventh is an editorial decision, not a technical one.

Branch: issue-7

Original request

Make the water look better somehow.

🤖 Refined by the viberfox issue agent. Reply with @agent refine and what is wrong to have this rewritten.

## Problem "Make the water look better somehow" has no defect behind it, and the local water shader is not a rough draft — it is a deliberate, documented design. Before this can be implemented, someone has to say *which* of several unrelated changes is wanted, because they differ by two orders of magnitude in cost. What exists today (local tier only): - `assets/shaders/water.wgsl` is an `ExtendedMaterial` over `StandardMaterial`: 5 sharpened directional octaves with per-octave distance fade (`water.wgsl:107`–`189`), crest wobble to break the corduroy pattern (`water.wgsl:153`), deep-water dispersion (`water.wgsl:137`), fresnel depth tint (`water.wgsl:237`), whitecaps masked on chop-riding-a-crest (`water.wgsl:257`), and a shore lap line (`water.wgsl:268`). - A per-cell rasterised distance-to-shore field (`vector_tiles.rs:1259`) drives shelter, a shallow tint and the foam line (`water.wgsl:215`, `:245`, `:269`). - Sea state is wind-driven from live weather (`water.rs:214`–`238`, `water.rs:244`). So the obvious wins are already spent. The genuine gaps I can cite are these, and they are not equivalent: **A. The waves always travel the same compass direction, whatever the weather is doing.** `sea_state` takes its direction from `CLOUD_WIND` — `water.rs:217` — which is `Vec2::new(0.04, 0.015)` at `main.rs:2380`, a *cloud drift velocity in texture units*, reused as a bearing. Normalised it is a constant ≈ENE, forever. Meanwhile `Observation` (`weather.rs:43`–`52`) carries only `wind_ms`; `parse_weather` reads `wind_speed` at `weather.rs:176` and drops the rest. I checked the live endpoint the client already calls — MET's `locationforecast/2.0/complete` returns `wind_from_direction` in degrees in the same `instant.details` object the parser is already inside. The data is fetched and thrown away. Wind *speed* changes the sea; wind *direction* does not. **B. Rain does not touch the water.** `weather.rs:46` carries `precipitation`, wired into clouds and fog. `water.rs` and `water.wgsl` contain no reference to it (grep: zero hits). A downpour leaves the canals glassy. **C. The water is opaque, and cannot cheaply stop being so.** `WaterSeaState::material` builds a `StandardMaterial` with no `alpha_mode` (`water.rs:147`–`161`), so it is `AlphaMode::Opaque`. The "shallows" are a pure albedo tint (`water.wgsl:245`–`250`), not transmission. Making it transparent does **not** buy depth: the water mesh sits at `y = -0.004` (`vector_tiles.rs:499`) directly over the map tile, whose texture has already painted the same polygon flat `palette::WATER` `0xA8C4DA` (`vector_tiles.rs:170`, `:91`). You would see flat blue paint through the water, not a bed. **D. The water reflects the sky and nothing else.** Reflection comes entirely from `AtmosphereEnvironmentMapLight` via the standard PBR path (`water.rs:11`–`15`). A canal in a city therefore does not reflect the houses lining it. Real reflections would need screen-space: the camera spawns with **no** `DepthPrepass` and no `DeferredPrepass` (`free_camera.rs:335`–`390`), and Bevy's `ScreenSpaceReflections` is deferred-only — a custom forward fragment shader is exactly the case it does not cover. This is a rendering-architecture change, not a shader tweak. **E. Nobody can see a water change in the documentation image set.** None of the six presets in `docs/shots.toml` frames water: `city`, `city-dusk`, `avatar`, `route`, `horizon`, `globe`. `ShotMetrics` (`shot_harness.rs:243`–`281`) has fields for globe tiles and route meshes but nothing for water, so `--dump-state` cannot answer "did water cells even build here?". Also worth stating plainly: none of this reaches the space tier. `globe_ocean.rs` is a separate BRDF-mask system that shares nothing with `water.wgsl` (`globe_ocean.rs:3`–`11`), and surface geometry only streams from **vector** sources (`map_geometry.rs:243` returns early on a raster template), so on the default raster basemap there is no water shader at all. ## Approach Blocked on the pick in Open questions. My recommendation is **A + E**, and here is that spec in full so a "yes, do A+E" is enough to start: - `crates/viberfox/src/systems/weather.rs` — add `wind_from_deg: f32` to `Observation` (`weather.rs:43`), parsed from `inst["wind_from_direction"]` alongside `wind_speed` at `weather.rs:176`. Missing key → keep the current constant bearing rather than defaulting to 0° (north), which would be a silent wrong answer. - `crates/viberfox/src/systems/water.rs` — `sea_state` takes the bearing as an argument instead of reading `CLOUD_WIND` (`water.rs:217`); convert meteorological *from*-degrees to a *travel* direction in the map's XZ frame. `update_water_waves` (`water.rs:244`) passes `weather.latest`'s bearing and gains a direction term in its change gate next to `WIND_EPSILON` (`water.rs:64`) so a slow veer still rewrites the materials. - `crates/viberfox/src/main.rs` — `CLOUD_WIND` stays as it is; only the water import at `water.rs:40` goes. - `docs/shots.toml` + `crates/viberfox/src/systems/shot_harness.rs` — a `water` preset, and `water_cells` / `water_rendered` counters on `ShotMetrics` following the `route_meshes` / `route_rendered` precedent at `shot_harness.rs:279`, added to `CSV_HEADER` (`shot_harness.rs:285`) and `csv_row` together. No shader edit is needed for A: `water.wgsl:122` already consumes `wind.xy` as an arbitrary unit direction. ## Acceptance criteria Conditional on **A + E** being the chosen option. - [ ] `parse_weather` extracts `wind_from_direction`; a unit test on a fixture body asserts the parsed bearing, and a second asserts a body *without* the key falls back to the current constant bearing rather than to 0°. - [ ] A unit test asserts two different bearings produce two different `wind.xy` in `sea_state`, and that the existing `the_wind_direction_is_a_unit_vector` invariant (`water.rs:291`) still holds for every bearing in 0..360. - [ ] A unit test pins the meteorological convention: `wind_from_direction = 0` (wind *from* the north) makes the waves travel *southward* in the map frame. Getting this backwards is invisible in a still image, so it has to be a test. - [ ] `update_water_waves` rewrites materials on a direction change alone, with the wind speed unchanged — covered by the epsilon-gate logic, testable without a GPU. - [ ] `CLOUD_WIND` is unchanged and still drives the cloud shader at `main.rs:2531`. - [ ] `docs/shots.toml` gains a `water` preset, and `cargo shots --only water` writes `docs/img/water.png` (workstation only). - [ ] `ShotMetrics` carries water counts, present in all three sinks (log line, `--csv`, `--dump-state`) per the one-field-three-sinks rule in CLAUDE.md. - [ ] The README image set is regenerated only if the maintainer wants the new preset linked — see Open questions. ## Verification In this container: ```bash cargo check -p viberfox cargo test -p viberfox --bin viberfox water cargo test -p viberfox --bin viberfox weather ``` `cargo check` does not compile `cfg(test)` code, so the test run is the real gate. Workstation only (no GPU here, per the container rules): ```bash cargo shots --only water --csv /tmp/water.csv cargo run -p viberfox --profile dev-bevy -- --shot /tmp/w.png \ --at <lat>,<lng>,120 --look=-15,<bearing> --dump-state /tmp/w.json ``` Anything about whether the water actually *looks* better — wave direction reading correctly against the visible wind, the shore line, the whitecaps — is unverifiable in this container and needs a human at a workstation. The shader itself cannot even be compiled here. ## Out of scope - The space tier. `globe_ocean.rs` is a per-texel BRDF mask, shares no code with `water.wgsl`, and is not touched. - The raster basemap. No vector source means no surface geometry at all (`map_geometry.rs:243`); water there stays flat paint (`vector_tiles.rs:170`) and no shader change can reach it. - Transparency / refraction / a riverbed — see problem C; it needs the tile texture to stop painting water first. - Screen-space reflections — see problem D; it needs a prepass and a deferred path. - Rain-on-water (problem B) unless it is explicitly chosen. - Any change to `CLOUD_WIND` or the cloud shader. - Perf work on `update_water_waves` iterating every live material (`water.rs:274`) — it is gated to a handful of runs an hour and is not a looks problem. ## Open questions 1. **Which change do you want?** They are not comparable: - **A — real wind direction** (~40 lines, fully testable headlessly, my recommendation). Fixes a factual wrongness: the sea ignores the wind bearing the app already downloads. - **B — rain on the water** (small shader change: precipitation → roughness lift + a dimple term). Needs you to say what rain-struck water should look like here, which is a taste call I cannot make for you. - **C — transparency / bed** (large; requires stopping the tile rasteriser from painting water, then something to see). - **D — screen-space reflections** (largest; prepass + deferred + likely incompatible with the current forward `ExtendedMaterial`. I would not start here). 2. **For E: which viewpoint?** I can add the `ShotMetrics` counters without input, but a `water` preset needs a lat/lng/look, and picking one would be guessing. The manifest's own instruction is to fly there and use Dev panel ▸ **📋 Copy as preset** — which needs a workstation. Either paste a `[[shot]]` block here, or name a water body (Noorderhaven? the Verbindingskanaal? the open Eemskanaal?) and accept that the exact framing gets tuned by whoever runs `cargo shots`. 3. **Should the new preset be linked from the README**, or is it a regression-checking image only? The README currently links the six existing presets; adding a seventh is an editorial decision, not a technical one. --- Branch: `issue-7` <details><summary>Original request</summary> Make the water look better somehow. </details> <sub>🤖 Refined by the viberfox issue agent. Reply with **@agent refine** and what is wrong to have this rewritten.</sub>
Collaborator

🤖 Refining this before anything gets built — reading the code, no branch, no commits. If it turns out I'd have to guess at the requirements I'll stop and ask instead.

🤖 Refining this before anything gets built — reading the code, no branch, no commits. If it turns out I'd have to guess at the requirements I'll stop and ask instead.
Collaborator

🤖 This needs a decision before I build anything. The open questions are in the description above.

Answer them and reply with @agent refine to have the spec rewritten, or @agent to make me implement it as it stands.


🤖 viberfox issue agent on the VPS — 18 turns · $1.26 · session 906450e3-b5f5-4c25-8c17-2743a4b6ab25. Reply with @agent to continue this session.

🤖 **This needs a decision before I build anything.** The open questions are in the description above. Answer them and reply with **@agent refine** to have the spec rewritten, or **@agent** to make me implement it as it stands. --- <sub>🤖 viberfox issue agent on the VPS — 18 turns · $1.26 · session `906450e3-b5f5-4c25-8c17-2743a4b6ab25`. Reply with **@agent** to continue this session.</sub>
viberfox-agent removed this from the Viberfox project 2026-07-30 08:08:00 +00:00
viberfox-agent removed this from the Viberfox project 2026-07-30 09:20:14 +00:00
Sign in to join this conversation.
No milestone
No project
No assignees
2 participants
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set

Reference
jeroen/cartopolis#7
No description provided.