Research: a city that sounds as loud as it is, and occlusion culling at street level #309

Closed
opened 2026-09-29 23:55:36 +00:00 by jeroen · 0 comments
Owner

Research night 2026-09-30 (unattended, read-only). Two areas, each checked against a live service or the code tonight. Neither is in docs/direction.md, an open issue, or #308.

1. A city that sounds as loud as it is: the national noise map under the soundscape

What. The soundscape is the avatar's own sounds and nothing else. systems/player/soundscape.rs:1-2: "footsteps timed off the gait, a landing thud, and wind that rises with speed." The Grote Markt and a quiet courtyard sound exactly the same. RIVM publishes the Netherlands' modelled environmental-noise map (Lden, the EU noise-directive measure, all sources combined) as a 10 m national raster. A city ambience bed whose volume follows it would make busy streets loud and parks and courtyards quiet, from a register rather than a guess.

Why it fits. "Twin is the load-bearing word: the content comes from registries, never from invention." And value 2: "Official sources first. PDOK, the Kadaster… public registers with a licence and an interface." This one is RIVM (Atlas Leefomgeving). It is a modelled register rather than a measured one: it is computed from traffic counts and infrastructure. That should be said plainly, but it is the official figure, and it beats having no noise at all.

Evidence, checked 2026-09-30 ~00:00 UTC:

  • WMS https://data.rivm.nl/geo/alo/wms, layers rivm_20191112_g_geluidkaart_lden_alle_bronnen and …_lden_wegverkeer, plus an lnight_wegverkeer layer for road traffic at night. Capabilities: Fees NONE, AccessConstraints NONE.

  • GetFeatureInfo, all sources, dB Lden:

    • Grote Markt: 62
    • Noorderplantsoen (park): 47
    • by the western ring road: 53

    A 15 dB spread across 2 km of one city.

  • WCS DescribeCoverage for the same coverage: EPSG:28992 (RD New), 10 m cells, 26,400 × 31,200 over the whole country. GetCoverage with subset=X(233000,234500)&subset=Y(581500,583000) returned 200 image/tiff, 150×150, 16-bit, 166 KB (the axis labels are X/Y; E/N return 404). As one byte per cell, a z14 cell is about 22 KB, or about 45 KB at 16 bits.

  • soundscape.rs already synthesises its sounds and has a looping-ambience path (:623, the wind loop), so a city bed is a sibling sound, not new machinery.

What players get. Walking out of a quiet side street onto the Vismarkt, you hear the city get louder. A park is quiet. At night, with the lnight layer, the city calms down. It is also the first thing in the app that makes being in a place audible rather than only visible.

Size. Medium, in two pieces, following atlas's existing shape:

  • an atlas noise dataset writing a per-cell u8 dB grid;
  • the client sampling it at the avatar (bilinear, like TerrainField::at) and mapping dB to the gain of one synthesised city bed.

Risks.

  • The sound itself is judged by ear. The container has no audio hardware (docs/notes/container-audio.md), so the timbre is Jeroen's call on a device. The machine-checkable part is the gain as a function of dB.
  • The 2019 map is the latest layer name on the service.
  • Outside the Netherlands there is no map; it must fall back to silence, i.e. today's behaviour (value 3).

First ticket. Atlas: publish the RIVM Lden map as a per-cell grid. Acceptance:

  • A fixture test over a recorded 1.5 km TIFF returns 62 ± 1 at the Grote Markt and 47 ± 1 in the Noorderplantsoen.
  • A cell outside the Netherlands produces no file.
  • The client's sampler, and the ambience, are the ticket after it.

2. Draws are the lever: Bevy's GPU occlusion culling at street level

What. Bevy 0.19 ships two-phase GPU occlusion culling (bevy_render::occlusion_culling::OcclusionCulling; its doc: "fully dynamic and requires no baking step. The CPU overhead is minimal"). It is opt-in and unused here. At street level almost the whole city is behind the first row of façades.

Why it fits. Value 5, "The frame budget is not negotiable", and the render survey's own finding (docs/notes/render-performance-survey.md:171): "draw count is the lever". That note measured 191 fewer draws taking city from 69 to 89 fps (:214).

Evidence.

  • git grep OcclusionCulling on main: nothing.
  • It needs a DepthPrepass, per its own doc comment. The prepass was removed on 2026-09-21 together with ambient occlusion (634be61, road_depth.rs:176-181), and the survey recorded the old prepass at "3.0× overdraw" at orbit (render-performance-survey.md:~255). So the experiment is to add back a depth-only prepass and see whether culling pays for it. That is a real trade, not a free win.
  • quality::Capabilities::compute_shaders already exists, and is exactly the gate a GPU-culling path needs: it runs on compute.
  • Granularity is the unknown that decides it. A LoD2.2 cell is merged into a few byte-bounded meshes (lod22::MAX_CELL_MESH_BYTES). Occlusion is tested per mesh bounding box, so big merged meshes occlude well but are occluded poorly. The number of meshes per cell at street level is what to measure first.

What players get. Frame time at street level, which is where people walk, and on handhelds, where the survey says the render thread is the wall.

Size. Small to measure, medium to ship.

Risks.

  • On lavapipe only the draw counts mean anything. The verdict needs the Deck (GPU slot) and cargo perf --repeat (#306) to separate a real change from run-to-run spread.
  • Occlusion culling is incompatible with deferred shading (not used here).
  • The prepass pipeline trap at road_depth.rs:160-190 must stay handled.
  • The web build (WebGL2) lacks the compute path.

First ticket. CARTO_OCCLUSION=1 adds DepthPrepass + OcclusionCulling to the camera, gated on Capabilities::compute_shaders. It is judged by a cargo perf --repeat 3 A/B on deck-1 at a street-level scenario and at city, with GPU frame time as median and range, on and off. Whether it becomes the default is decided by those numbers, and "not worth it" goes into the survey note.

Recommendation

Area 1, if a player-visible change is wanted next. Its data side is fully verified tonight, keyless and with no stated constraints, and its first ticket is testable without anyone's ears. Area 2 is the better performance bet, but it should wait until #306 is merged, because its verdict is only as good as the spread measurement it depends on.

Research night 2026-09-30 (unattended, read-only). Two areas, each checked against a live service or the code tonight. Neither is in `docs/direction.md`, an open issue, or #308. ## 1. A city that sounds as loud as it is: the national noise map under the soundscape **What.** The soundscape is the avatar's own sounds and nothing else. `systems/player/soundscape.rs:1-2`: *"footsteps timed off the gait, a landing thud, and wind that rises with speed."* The Grote Markt and a quiet courtyard sound exactly the same. RIVM publishes the Netherlands' modelled environmental-noise map (Lden, the EU noise-directive measure, all sources combined) as a 10 m national raster. A city ambience bed whose volume follows it would make busy streets loud and parks and courtyards quiet, from a register rather than a guess. **Why it fits.** *"Twin is the load-bearing word: the content comes from registries, never from invention."* And value 2: *"Official sources first. PDOK, the Kadaster… public registers with a licence and an interface."* This one is RIVM (Atlas Leefomgeving). It is a *modelled* register rather than a measured one: it is computed from traffic counts and infrastructure. That should be said plainly, but it is the official figure, and it beats having no noise at all. **Evidence, checked 2026-09-30 ~00:00 UTC:** - WMS `https://data.rivm.nl/geo/alo/wms`, layers `rivm_20191112_g_geluidkaart_lden_alle_bronnen` and `…_lden_wegverkeer`, plus an `lnight_wegverkeer` layer for road traffic at night. Capabilities: `Fees NONE`, `AccessConstraints NONE`. - GetFeatureInfo, all sources, dB Lden: - **Grote Markt: 62** - **Noorderplantsoen (park): 47** - by the western ring road: 53 A 15 dB spread across 2 km of one city. - WCS `DescribeCoverage` for the same coverage: EPSG:28992 (RD New), **10 m cells**, 26,400 × 31,200 over the whole country. `GetCoverage` with `subset=X(233000,234500)&subset=Y(581500,583000)` returned **200 image/tiff, 150×150, 16-bit, 166 KB** (the axis labels are `X`/`Y`; `E`/`N` return 404). As one byte per cell, a z14 cell is about 22 KB, or about 45 KB at 16 bits. - `soundscape.rs` already synthesises its sounds and has a looping-ambience path (`:623`, the wind loop), so a city bed is a sibling sound, not new machinery. **What players get.** Walking out of a quiet side street onto the Vismarkt, you hear the city get louder. A park is quiet. At night, with the `lnight` layer, the city calms down. It is also the first thing in the app that makes being *in* a place audible rather than only visible. **Size.** Medium, in two pieces, following atlas's existing shape: - an atlas `noise` dataset writing a per-cell `u8` dB grid; - the client sampling it at the avatar (bilinear, like `TerrainField::at`) and mapping dB to the gain of one synthesised city bed. **Risks.** - **The sound itself is judged by ear.** The container has no audio hardware (`docs/notes/container-audio.md`), so the timbre is Jeroen's call on a device. The machine-checkable part is the *gain* as a function of dB. - The 2019 map is the latest layer name on the service. - Outside the Netherlands there is no map; it must fall back to silence, i.e. today's behaviour (value 3). **First ticket.** *Atlas: publish the RIVM Lden map as a per-cell grid.* Acceptance: - A fixture test over a recorded 1.5 km TIFF returns 62 ± 1 at the Grote Markt and 47 ± 1 in the Noorderplantsoen. - A cell outside the Netherlands produces no file. - The client's sampler, and the ambience, are the ticket after it. ## 2. Draws are the lever: Bevy's GPU occlusion culling at street level **What.** Bevy 0.19 ships two-phase GPU occlusion culling (`bevy_render::occlusion_culling::OcclusionCulling`; its doc: *"fully dynamic and requires no baking step. The CPU overhead is minimal"*). It is opt-in and unused here. At street level almost the whole city is behind the first row of façades. **Why it fits.** Value 5, *"The frame budget is not negotiable"*, and the render survey's own finding (`docs/notes/render-performance-survey.md:171`): *"draw count is the lever"*. That note measured 191 fewer draws taking `city` from 69 to 89 fps (`:214`). **Evidence.** - `git grep OcclusionCulling` on main: nothing. - It **needs a `DepthPrepass`**, per its own doc comment. The prepass was **removed on 2026-09-21** together with ambient occlusion (`634be61`, `road_depth.rs:176-181`), and the survey recorded the old prepass at *"3.0× overdraw"* at `orbit` (`render-performance-survey.md:~255`). So the experiment is to add back a depth-only prepass *and* see whether culling pays for it. That is a real trade, not a free win. - `quality::Capabilities::compute_shaders` already exists, and is exactly the gate a GPU-culling path needs: it runs on compute. - **Granularity is the unknown that decides it.** A LoD2.2 cell is merged into a few byte-bounded meshes (`lod22::MAX_CELL_MESH_BYTES`). Occlusion is tested per mesh bounding box, so big merged meshes occlude well but are *occluded* poorly. The number of meshes per cell at street level is what to measure first. **What players get.** Frame time at street level, which is where people walk, and on handhelds, where the survey says the render thread is the wall. **Size.** Small to measure, medium to ship. **Risks.** - On lavapipe only the draw counts mean anything. **The verdict needs the Deck (GPU slot)** and `cargo perf --repeat` (#306) to separate a real change from run-to-run spread. - Occlusion culling is incompatible with deferred shading (not used here). - The prepass pipeline trap at `road_depth.rs:160-190` must stay handled. - The web build (WebGL2) lacks the compute path. **First ticket.** `CARTO_OCCLUSION=1` adds `DepthPrepass` + `OcclusionCulling` to the camera, gated on `Capabilities::compute_shaders`. It is judged by a `cargo perf --repeat 3` A/B on `deck-1` at a street-level scenario and at `city`, with GPU frame time as median and range, on and off. Whether it becomes the default is decided by those numbers, and "not worth it" goes into the survey note. ## Recommendation **Area 1**, if a player-visible change is wanted next. Its data side is fully verified tonight, keyless and with no stated constraints, and its first ticket is testable without anyone's ears. Area 2 is the better *performance* bet, but it should wait until #306 is merged, because its verdict is only as good as the spread measurement it depends on.
Sign in to join this conversation.
No milestone
No project
No assignees
1 participant
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#309
No description provided.