fuzzyworld

tower / commit

feat(interior): the collector gets a lit rim

49b0d64d13b029d5fe601634fdaa6856e3d52165 · Pavel Safronov · 2026-09-08 09:24:24 -0700 · parents 2bb3e0de9318

feat(interior): the collector gets a lit rim

Drawing the collector as pure black was right about its face and wrong about
the object. From inside the sun cap window at night you could not see it at
all — only that a piece of the sky was missing, which reads as a rendering
hole rather than as a 244 km structure.

The physics gives the rim for free. The collector is backlit by definition:
its mirror face points at the sun and away from Tower. Anything holding
46,938 km² flat has structure standing proud of the mirror plane at the
edge, and there the sun strikes it side-on. Add the scatter every real
mirror throws at grazing incidence and the boundary is a thin sun-coloured
ring — which is how anything backlit looks, and the one part of the
collector that can be lit at all.

What it is not is sunlight leaking past the edge, and the distinction
matters because the first is a property of the object while the second would
break canon: Tower sits wholly inside the umbra (§1.3.0), so the sun's disc
is occulted from every point in the world. Nobody sees a sliver of star.
They see lit metal.

Two exponentials: a tight core at the declared 4 km of edge structure, and a
wide dim skirt for the scatter around it. One alone reads as a drawn outline
rather than as light. The first attempt was three times too bright with a
skirt seven times too wide, which washed the whole disc gold instead of
tracing it.

It does not scale with the beam — the sun does not dim when Tower banks its
own lamp — so the rim sits outside uExposure, like the stars. It fades as
the eye adapts, but from a much higher floor than a star's, and stays
faintly visible at noon where a star cannot.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
 README.md                                      |   4 +++-
 decisions.md                                   |  29 ++++++++++++++++++++++++
 design.md                                      |   9 ++++++++
 docs/screenshots/desktop-exterior-true.png     | Bin 506858 -> 506739 bytes
 docs/screenshots/desktop-exterior.png          | Bin 332036 -> 332293 bytes
 docs/screenshots/desktop-interior-dawn.png     | Bin 485508 -> 488654 bytes
 docs/screenshots/desktop-interior-night.png    | Bin 276084 -> 275988 bytes
 docs/screenshots/desktop-interior-overhead.png | Bin 544165 -> 544184 bytes
 docs/screenshots/desktop-interior-suncap.png   | Bin 383930 -> 397844 bytes
 docs/screenshots/desktop-interior.png          | Bin 545739 -> 545739 bytes
 docs/screenshots/mobile-exterior.png           | Bin 81564 -> 81707 bytes
 docs/screenshots/mobile-interior.png           | Bin 172350 -> 172345 bytes
 docs/screenshots/overview.png                  | Bin 1152660 -> 1155305 bytes
 src/canon/constants.ts                         |  16 +++++++++++++
 src/views/interior.ts                          |  30 ++++++++++++++++++++-----
 15 files changed, 82 insertions(+), 6 deletions(-)

diff --git a/README.md b/README.md
index 4ff33ea..8f4d9a2 100644
--- a/README.md
+++ b/README.md
@@ -9,7 +9,9 @@ is a kilometre of sealed hull: no windows along all 540 km, and no view of the E
 it orbits. Both end caps are windows, though — 100 km of glass each, and the only
 places in the world with a horizon that is not Tower. Nobody aboard can see the sun
 either: the 244 km collector sits between Tower and it, 79 times its diameter, so
-the sun cap looks out at a black disc and the stars around it.
+the sun cap looks out at a black disc and the stars around it — black except at its
+edge, where the structure holding it flat catches the sun side-on and traces the
+whole 244 km in a thin sun-coloured ring.
 
 Explore Tower from outside, pick a spot on the inner surface, and stand there — with
 the ground curving up on both sides and the far side of the world hanging 100 km
diff --git a/decisions.md b/decisions.md
index e19a1d5..29c1d02 100644
--- a/decisions.md
+++ b/decisions.md
@@ -1615,3 +1615,32 @@ scrubbing and ticking are the same operation.
 
 The readout names the distance to the line rather than just the hour, because the
 distance is the part you can act on: 27 km sunward is about two minutes' wait.
+
+## D-054 — The collector gets a lit rim
+
+Drawing the collector as pure black was right about its face and wrong about the
+object. From inside the sun cap window at night you could not see it at all — only
+that a piece of the sky was missing, which reads as a rendering hole rather than as
+a 244 km structure.
+
+**The physics gives the rim for free.** The collector is backlit by definition: its
+mirror face is pointed at the sun and away from Tower. Anything holding 46,938 km²
+flat has structure standing proud of the mirror plane at the edge, and at the edge
+the sun strikes that structure side-on. Add the scatter every real mirror throws at
+grazing incidence and the boundary is a thin sun-coloured ring. That is simply how
+backlit things look, and it is the one part of the collector that can be lit.
+
+**What it is not is sunlight past the edge**, and the distinction matters because the
+first is a property of the object and the second would break canon. Tower sits wholly
+inside the umbra — §1.3.0 — so the sun's disc is occulted from every point in the
+world. Nobody sees a sliver of star; they see lit metal.
+
+Rendered as two exponentials: a tight core at the declared 4 km of edge structure,
+and a wide dim skirt for the scatter around it. One alone reads as a drawn outline
+rather than as light. The first attempt was three times too bright and its skirt
+seven times too wide, which washed the whole disc gold instead of tracing it.
+
+**It does not scale with the beam.** The sun does not dim when Tower banks its own
+lamp, so the rim is outside `uExposure`, like the stars. It does fade as the eye
+adapts, but from a much higher floor than a star's — this is sunlit structure, and
+it stays faintly visible at noon where a star cannot.
diff --git a/design.md b/design.md
index e4bae17..bdbbe05 100644
--- a/design.md
+++ b/design.md
@@ -105,6 +105,15 @@ infrared, and therefore black. It is the only object in Tower's sky that is neit
 Tower nor a star, and it never moves, because the collector flies in formation
 rather than spinning. D-050.
 
+**Its edge is not black.** The face is a mirror pointed away, but the collector is
+*backlit*, and the structure holding 46,938 km² flat stands proud of the mirror
+plane at the rim, where the sun strikes it side-on. With the scatter any real mirror
+throws at grazing incidence, the edge reads as a thin sun-coloured ring — so the
+collector is a hole in the stars with a lit rim rather than an absence, which is how
+anything backlit looks. **It is not sunlight leaking past the edge**: Tower is wholly
+inside the umbra, so the sun's disc is hidden from every point in the world. The ring
+is lit structure. D-054.
+
 #### 1.3.1 The diffuser swarm
 
 The diffusers must be individually invisible, or the sky becomes a row of separate
diff --git a/docs/screenshots/desktop-exterior-true.png b/docs/screenshots/desktop-exterior-true.png
index 2615bcf..ef7e6af 100644
Binary files a/docs/screenshots/desktop-exterior-true.png and b/docs/screenshots/desktop-exterior-true.png differ
diff --git a/docs/screenshots/desktop-exterior.png b/docs/screenshots/desktop-exterior.png
index 3a19f4a..7810fb6 100644
Binary files a/docs/screenshots/desktop-exterior.png and b/docs/screenshots/desktop-exterior.png differ
diff --git a/docs/screenshots/desktop-interior-dawn.png b/docs/screenshots/desktop-interior-dawn.png
index 4dfd0e6..cee0a2a 100644
Binary files a/docs/screenshots/desktop-interior-dawn.png and b/docs/screenshots/desktop-interior-dawn.png differ
diff --git a/docs/screenshots/desktop-interior-night.png b/docs/screenshots/desktop-interior-night.png
index 24251b7..7e05589 100644
Binary files a/docs/screenshots/desktop-interior-night.png and b/docs/screenshots/desktop-interior-night.png differ
diff --git a/docs/screenshots/desktop-interior-overhead.png b/docs/screenshots/desktop-interior-overhead.png
index 7f169b7..e911380 100644
Binary files a/docs/screenshots/desktop-interior-overhead.png and b/docs/screenshots/desktop-interior-overhead.png differ
diff --git a/docs/screenshots/desktop-interior-suncap.png b/docs/screenshots/desktop-interior-suncap.png
index 2fce396..b17d8da 100644
Binary files a/docs/screenshots/desktop-interior-suncap.png and b/docs/screenshots/desktop-interior-suncap.png differ
diff --git a/docs/screenshots/desktop-interior.png b/docs/screenshots/desktop-interior.png
index 53b8fe1..9ab8ec9 100644
Binary files a/docs/screenshots/desktop-interior.png and b/docs/screenshots/desktop-interior.png differ
diff --git a/docs/screenshots/mobile-exterior.png b/docs/screenshots/mobile-exterior.png
index 0149785..2fa380c 100644
Binary files a/docs/screenshots/mobile-exterior.png and b/docs/screenshots/mobile-exterior.png differ
diff --git a/docs/screenshots/mobile-interior.png b/docs/screenshots/mobile-interior.png
index f1d92d7..ebdac10 100644
Binary files a/docs/screenshots/mobile-interior.png and b/docs/screenshots/mobile-interior.png differ
diff --git a/docs/screenshots/overview.png b/docs/screenshots/overview.png
index 3063fc3..bdeb741 100644
Binary files a/docs/screenshots/overview.png and b/docs/screenshots/overview.png differ
diff --git a/src/canon/constants.ts b/src/canon/constants.ts
index 1e1831a..e9e3ec9 100644
--- a/src/canon/constants.ts
+++ b/src/canon/constants.ts
@@ -81,6 +81,22 @@ export const COLLECTOR_STANDOFF_M = 320_000
  */
 export const COLLECTOR_ANGULAR_DIAMETER_RAD =
   2 * Math.atan(COLLECTOR_DIAMETER_M / 2 / COLLECTOR_STANDOFF_M)
+/**
+ * The sunlit rim.
+ *
+ * The collector's face is a mirror pointed away from Tower, so from inside it is a
+ * black disc — but it is a **backlit** black disc, and its edge is not. The array
+ * has to hold 46,938 km² flat, and the structure that does it stands proud of the
+ * mirror plane at the rim, where the sun strikes it side-on. Add the scatter any
+ * real mirror throws at grazing incidence and the edge reads as a thin sun-coloured
+ * ring: a crescent of lit structure with nothing behind it. 4 km on a 244 km disc.
+ *
+ * Note what it is *not*: sunlight leaking past the edge. Tower sits wholly inside
+ * the umbra (below), so the sun's disc is hidden from every point in the world. The
+ * ring is lit structure, not a sliver of star. D-054.
+ */
+export const COLLECTOR_RIM_M = 4000
+
 /**
  * Umbra radius at the sun cap. Tower's 51 km sits well inside it, which is what
  * makes the eclipse total rather than partial.
diff --git a/src/views/interior.ts b/src/views/interior.ts
index ad757dd..6650c5e 100644
--- a/src/views/interior.ts
+++ b/src/views/interior.ts
@@ -25,7 +25,7 @@ import {
 } from 'three'
 import {
   BEAM_RADIUS_M, BRIGHTNESS_FULL_W_M2, COLLECTOR_DIAMETER_M, COLLECTOR_STANDOFF_M,
-  DAY_LENGTH_S, DAY_SWEEP_FRACTION, BRIGHTNESS_NIGHT_W_M2, BRIGHTNESS_TEMPERATE_W_M2,
+  COLLECTOR_RIM_M, DAY_LENGTH_S, DAY_SWEEP_FRACTION, BRIGHTNESS_NIGHT_W_M2, BRIGHTNESS_TEMPERATE_W_M2,
   BRIGHTNESS_TWILIGHT_W_M2, G0, HULL_DEPTH_M, LENGTH_M, OMEGA_RAD_S, RADIUS_M,
   RAYLEIGH_FLOOR_PER_M,
   SPECIFIC_GAS_CONSTANT, TEMPERATURE_K, gravityAtHeight,
@@ -50,6 +50,8 @@ const HULL_D = HULL_DEPTH_M / KM
 /** The collector, in km: 122 km of radius flying 320 km ahead of the sun cap. */
 const COLLECTOR_R = COLLECTOR_DIAMETER_M / KM / 2
 const COLLECTOR_Z = -COLLECTOR_STANDOFF_M / KM
+/** Width of the sunlit edge structure, in km (D-054). */
+const COLLECTOR_RIM = COLLECTOR_RIM_M / KM
 
 /**
  * Rayleigh extinction per kilometre at floor density, per channel.
@@ -389,11 +391,29 @@ void main() {
 
   vec3 dir = normalize(vWorld - uCam);
   vec3 sky = starField(dir);
-  // Cast on through the window to the collector's plane. Anything that lands on
-  // the disc is looking at its unlit back, which is as black as the sky is not.
+  /**
+   * Cast on through the window to the collector's plane. Anything landing on the
+   * disc sees its unlit back, which is as black as the sky is not — and the edge,
+   * which is the opposite: structure standing proud of the mirror plane with the
+   * sun striking it side-on (D-054). So the collector reads the way any backlit
+   * thing does, as a hole in the stars with a lit rim, rather than as an absence.
+   */
   float t = (${COLLECTOR_Z.toFixed(1)} - vWorld.z) / min(dir.z, -1e-4);
-  vec2 hit = vWorld.xy + dir.xy * t;
-  if (t > 0.0 && dot(hit, hit) < ${(COLLECTOR_R * COLLECTOR_R).toFixed(1)}) sky = vec3(0.0);
+  if (t > 0.0) {
+    float r = length(vWorld.xy + dir.xy * t);
+    sky = mix(sky, vec3(0.0), step(r, ${COLLECTOR_R.toFixed(2)}));
+    // Tight core for the structure itself, and a wide dim skirt for the scatter
+    // around it. One exponential alone reads as a drawn outline rather than light.
+    float d = abs(r - ${COLLECTOR_R.toFixed(2)});
+    float rim = exp(-d / ${COLLECTOR_RIM.toFixed(3)})
+              + 0.10 * exp(-d / ${(COLLECTOR_RIM * 3).toFixed(3)});
+    /**
+     * Not scaled by uExposure — the sun does not dim when Tower banks its beam.
+     * It does fade as the eye adapts, like the stars, but from a much higher floor:
+     * this is sunlit structure, and it stays visible at noon where a star does not.
+     */
+    sky += vec3(1.0, 0.93, 0.78) * rim * (0.3 + 0.7 * uStarGain) * 0.42;
+  }
 
   vec3 face = mix(sky, mullion, frame.a);
   vec3 T = towerTransmittance(uCam, vWorld);