fuzzyworld

tower / commit

feat(world): the range rings the world

0360727016a9a9949ac9ffadadee30bdd89778bc · Pavel Safronov · 2026-09-07 23:44:08 -0700 · parents 9c891d2e3b1e

feat(world): the range rings the world

The mountains were "a 140 km arc, not a ring" — four named domes over about
a third of the circumference, with the other two thirds simply unauthored.
Closing it is better than the arc was: a ring of peaks is a watershed that
divides the whole world, rather than a wall you can walk around the end of.

The ridge op grows optional ridged noise, which is what turns a ridge into a
range. Without it a ring band is a smooth swell all the way round — right
for the three low walls across the waist, wrong for peaks.

It blends with `max` rather than adding, and that is not a detail. Added,
The Girdle's 2,900 m would stack onto Snowcrown's 4,300 and push the massif
to 6,900 — past the raster's 4,400 m ceiling, where it would clip flat and
silently. Taking the greater of the two leaves the named summits as the high
points of their own range, which is what they now are: Snowcrown is not a
separate mountain from The Girdle, it is the tall part of it.

Peaks reach about 3,900 m, so they cross the 3,000 m snowline and the ring
reads as snow the whole way round. The rivers still cut it, because carve
runs after relief. Height range comes out -508..4,392 m, still inside the
declared -520..4,400 encoding window.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
 decisions.md      | 21 +++++++++++++++++++++
 design.md         |  6 ++++--
 tools/terrain.mjs | 26 +++++++++++++++++++++++++-
 3 files changed, 50 insertions(+), 3 deletions(-)

diff --git a/decisions.md b/decisions.md
index 7089902..026e61a 100644
--- a/decisions.md
+++ b/decisions.md
@@ -1526,3 +1526,24 @@ have to carry a 244 km mesh 320 km outside the world to draw one black circle.
 The interior's checkered cap material is gone with this. Both caps are windows now,
 and nothing inside was still using it — the checker survives as the exterior's sun
 cap and as both windows' mullions.
+
+## D-051 — The range rings the world
+
+The mountains were "a 140 km arc, not a ring" — four named domes covering about a
+third of the circumference, with the other two thirds simply unauthored. Asked to
+close it, and closing it is better than the arc was: a ring of peaks is a *watershed*
+that divides the whole world rather than a wall you can walk around the end of.
+
+**The ridge op grows optional ridged noise**, which is what turns a ridge into a
+range. Without it a ring band is a smooth swell all the way round — right for the
+three low walls across the waist, wrong for peaks.
+
+**It blends with `max` rather than adding**, and that is not a detail. Added, The
+Girdle's 2,900 m would stack onto Snowcrown's 4,300 and push the massif to 6,900 —
+past the raster's 4,400 m ceiling, where it would clip flat. Taking the greater of
+the two leaves the named summits as the high points of their own range, which is
+what they are: Snowcrown is not a separate mountain from The Girdle, it is the tall
+part of it.
+
+Peaks reach about 3,900 m, so they cross the 3,000 m snowline and the ring reads as
+snow the whole way round. The rivers still cut it, because carve runs after relief.
diff --git a/design.md b/design.md
index 576cff0..b003eb6 100644
--- a/design.md
+++ b/design.md
@@ -513,7 +513,7 @@ along the axis**, which is the natural grain of an engineered climate (§1.9.4):
 | z | | |
 |---|---|---|
 | 0–42 km | **The Sunward Sea** | ring ocean at the foot of the sun cap |
-| 55–140 km | **the range** | Westwall, The Sisters, **Snowcrown** (4,300 m), Eastwall — a 140 km arc, not a ring |
+| 55–140 km | **the range** | Westwall, The Sisters, **Snowcrown** (4,300 m), Eastwall — the named high points of **The Girdle**, which rings the world at z 90 km (D-051) |
 | 148–218 km | **The Dry Reach** | desert **ringing the world**, 22,000 km², 13% of the surface |
 | 176/190/203 km | **three ridges** | ringing, concentric — and **The Narrows** is the only breach in all three |
 | 214–430 km | **Greatwood** | forest belt |
@@ -579,7 +579,9 @@ Hollow Station — around 2,100 m, with the runs above them.
 
 - **The Spindle's height.** 3.2 km is a placeholder; an object dropped from its top
   lands ~760 m antispinward of its base.
-- **z 430–496 km** is unauthored, as is most of the circumference outside the range.
+- **z 430–496 km** is unauthored. The circumference outside the named peaks is no
+  longer empty — The Girdle carries the range round the whole 314 km — but it is
+  unwalked: peaks with no names, and no rivers drawn off them yet.
 
 ## 2. Application architecture
 
diff --git a/tools/terrain.mjs b/tools/terrain.mjs
index 079207a..f135fac 100644
--- a/tools/terrain.mjs
+++ b/tools/terrain.mjs
@@ -166,7 +166,31 @@ function opRidge(f) {
       const bump = Math.exp(-(d * d) / (2 * (r.widthZM / 2) ** 2))
       const sr = nc(zw, px2y(y), 5000)
       const n = fbm(sr.x, sr.y, { octaves: 3, seed: 41, wrapY: sr.wrapY })
-      height[y * W + x] += inTheta * bump * r.heightM * (0.75 + 0.5 * n)
+      let scale = 0.75 + 0.5 * n
+      /**
+       * Optional ridged noise, which is what turns a ridge into a *range*. Without
+       * it a ring band is a smooth swell all the way round — right for the three
+       * low walls across the waist, wrong for anything meant to read as peaks.
+       */
+      if (r.ridgedAmp) {
+        const sd = nc(zw, px2y(y), r.ridgedScaleM ?? 15000)
+        const rg = ridged(sd.x, sd.y, { octaves: 6, seed: 91, wrapY: sd.wrapY })
+        scale *= 1 - r.ridgedAmp + r.ridgedAmp * rg * 1.6
+      }
+      const h = inTheta * bump * r.heightM * scale
+      const i = y * W + x
+      /**
+       * `max` rather than `+=` matters wherever a ring runs under named summits:
+       * added, the ring's 2,900 m would stack onto Snowcrown's 4,300 and push the
+       * massif past the raster's 4,400 m ceiling. Taking the greater of the two
+       * lets the named peaks stay the high points of their own range, which is
+       * what they are.
+       */
+      height[i] = r.blend === 'max' ? Math.max(height[i], h) : height[i] + h
+      if (f.biome === 'alpine' && h > 1600) {
+        biome[i] = BIOME.alpine
+        biomeStrength[i] = 2
+      }
     }
   }
 }