Erosion in three regions, and a three-column shell - #13
Merged
Lincoln504 merged 4 commits intoJul 29, 2026
Conversation
A mountain does not weather the same way at every height, and the eye knows it. Geomorphology names the three regimes and they are exactly the three the palette already paints: below the treeline transport is diffusive — creep under soil and roots, convex slopes, broad hollows and no gullies; between the lines it is advective, water running on bare rock and concentrating into channels, which is the branching-gully country the filter was built for; above the snowline there is little running water at all and the shaping is frost and ice, so the cuts are finer, sharper and confined to faces steep enough to shed. One parameter set from sea level to the summit gets at most one of the three right. So `ErosionFilter` is now three `ErosionZone`s over one `ErosionShape`, plus the two lines they meet at and the controls that stay shared — concentration, which is a global tilt and per band would collapse into a second strength slider, and sediment, which is a material opacity and gets per-band behaviour for free because a band that is off deposits no ridge to paint. The weights are differences of one monotone ramp, so they are an exact partition of unity for any lines and any blend width, with no clamp and no divide. That is load-bearing rather than tidy: the sum *is* the filter's strength, and a partition that dipped to 0.9 in a crossfade would leave a terrace following the treeline — the one artefact this feature must not introduce. Height is an offset, so the bands add; a band switched off contributes nothing and its ground keeps the shape it came in with, faded by the same ramp instead of ending on a contour. The lines are measured on the filter's *input* surface, not its own result — the alternative is a mask that depends on its output, which is an iteration or a lie. On the GPU it is still one dispatch and still four storage buffers. The band loop skips against exactly zero (smoothstep saturates exactly), so outside a crossfade it runs one band and never three; each band carries its own derived octave count and gradient stencil, since both follow that band's gully scale. `Zone` is padded to 48 bytes so the array stride is 16-byte aligned under either side's layout rules — not the place to find out V3D disagrees. `range` became two scalars rather than a vec2f, which retires the alignment hazard the old struct had a paragraph about. Pre-band documents load their flat parameter set into all three bands via a `#[serde(from)]` shim — dropping those fields would have silently reset every saved scene's erosion. Reachable from the console: `filter zone=alpine strength=1.3`, and `off` means the filter or the band depending on whether a zone is named. Retuning a band that is off does not switch it back on. Echo prints all three bands and both lines, so a transcript still replays. Presets that had one reason to band, band: the canyon and monument caprock finally hold still (the top of a canyon is not a summit, it is the surface the canyon was cut into), the fjord runs three processes down its walls, and alpine is the one it was designed on. Ashfields does not — ash falls on everything equally. Tests: CPU/GPU parity gains four banded cases including bands that disagree about scale (so about octave count and stencil too), one band off, degenerate lines, and bands under bedding. Plus the partition summing to one outside [0,1] and for hostile lines, a band off sparing the other bands' altitudes, identical bands being bit-equal to the unbanded filter, and a rendered frame proving two different band edits are two different pictures.
Neither is implemented. Both are written to be argued with. UI-PROPOSAL: the current shell's controls inventoried and re-homed into three columns — assistant, viewport, settings — with a header for state and file operations and the bottom bar reduced to scene info. The layout is cheap because the engine has no swapchain resize path at all, and the proposal is explicit that it must stay that way. Right panel is multi-open collapsible sections rather than tabs, because the settings are consecutive stages of one filter chain and tabs sever the adjacencies. The erosion bands get an elevation strip instead of twenty-one labelled sliders. LAYERS-PROPOSAL: whether the fixed terrain pass chain should become an ordered stack of operators, and whether the assistant should address them. Recommends not yet, in that order: the chain has no defined height space (units in the working buffer depend on which stages ran), masks cannot reference another operator's output without a fifth storage binding the Pi will not give us, and the assistant has an IR already — what it lacks is a printer. Says plainly which parts are worth doing regardless of the rest.
The bands shipped blending *results* — both evaluated in the crossfade, weighted and summed. That works, but it means two gully systems dissolving into each other rather than one whose character changes. Blending the settings instead and evaluating once is the better picture, and it is cheaper. The measurement decided which settings can do it. The gully scale cannot. Two integers are derived from it — the octave count and the gradient stencil in texels — and interpolating it steps them. Swept over the range the presets' own bands spanned (0.075 to 0.20 at 256²) that is one octave-count step worth up to 0.026 of the height range, nearly seven texels of relief, plus forty-eight stencil steps worth up to 0.013 each, every one falling on an altitude isoline. A terrace following the treeline is the artefact the exact partition of unity exists to prevent, and it would be self-defeating to let it back in through the parameters. So the scale is shared, and everything else interpolates. Deposition became a band setting, because it does not follow the carving: debris leaves the summits and arrives in the valleys, so a mountain with heavy alluvium on its peaks and none in its bottoms is the picture drawn backwards. The opacity folds into the ridge map inside the kernel, so the material needs no idea that bands exist. Its *tone* is regional too and comes from the palette — frost debris in a couloir, washed grit mid-slope, fine mixed silt on a valley floor — which costs no control at all: the palette owns colour, the Lab owns amounts. That moved the three tones into the material lookup, which the terrain's fragment stage already binds, rather than spending env texels on them. Then the audit, by the method this filter's own controls were pruned by: sweep one control over its range in one band and measure a rendered frame, area and depth separately so a control that repaints a small area strongly is not scored as weak. Nothing was dead. But detail spread (3.2/1.7 mean levels end to end) and rounding (1.9/1.6) came in five to ten times under strength (16.4/14.3) and gully weight (11.3/2.3) in *both* bands, and all three describe the same thing — what the gullies look like — so they joined the scale as shared. The band panel is five sliders instead of seven, and the band blend width lost its slider entirely: it has one right value, the same 0.08 the material uses for its own windows. Also fixed on the way through: the WGSL params struct used `vec2f`/`vec3f` padding, which gives the struct alignment `#[repr(C)]` does not, and the dispatch refused a 172-byte buffer where the shader wanted 176. Plain scalars on both sides now, with the count chosen so the array lands on a 16-byte boundary with no padding member at all.
…he right The shell was a mode bar over a preset rail over a viewport, with three full-screen Labs that slid across it. Both halves of that were takeovers. The mode you were in decided what the left rail was for, so finding the material presets meant leaving Create; and a Lab covered the controls you were not in, so you could not watch the erosion sliders and the material at once. The assistant, meanwhile, floated over the picture as a bar, which is the shape of a thing that interrupts rather than a place you work. Now: header, three columns, info bar. The header is document state and document verbs — undo/redo, new/save/open, the .muse recorder, heightmap io, the adapter, the shortcut sheet, Render. Nothing in it shapes the landscape. The bottom bar is information and nothing pressable; it prints the engine's own status_line rather than restating the same facts beside it. The settings column is every scene control in the order the engine applies them. Collapsible sections, not tabs, and the reason is structural rather than aesthetic: these are consecutive stages of one filter chain — bedding runs before the erosion filter, plants read the material, the water level is a percentile of the terrain's own heights — and tabs would either sever those adjacencies or put the same control in two places. Several open at once; three at boot. The assistant is docked and full height, with the conversation growing upward from the input. Two things are genuinely lost and both are deliberate. The Labs' Apply/ Cancel pair cannot exist on a panel that is always open — undo is what it was, and every control here dispatches one ordinary undoable command. And the mode bar's context-sensitive preset rail is gone; the material and sky palettes now sit in their own sections, beside the controls they interact with, which is where you would look for them anyway. The gesture bracket the Lab frame owned moved to the settings column, so a slider drag is still one undo step rather than one per debounce tick — the one thing in this change that would have broken silently. Nothing here costs GPU work. The canvas keeps its fixed backing store and CSS letterboxes it, so a narrower viewport is one layout pass and no swapchain reconfigure; that property is load-bearing on the Pi and is now written down where the grid is defined.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
35 files changed, 4612 insertions(+), 844 deletions(-)· gate green at98daac2(fmt, clippy, workspace tests against a real adapter, wasm build, headless-Chromium smoke) · payload 464695 gz (113373 from the previous PR, against the 6 MB NFR-2 budget)Part of a 12-PR stack over
pr/00-ci. Based onpr/06-rock-sky-surf-shore; retargets tomainonce that lands.