fuzzyworld

tower / commit

feat(sky): the beam runs from cap to cap, the sun along it (D-079)

f16f084e3d3c784d4f0a30a4ee898307efda649b · Pavel Safronov · 2026-09-28 07:45:36 -0700 · parents c954a775b37a

Claude Code usage: this commit ~68.5K out / ~21.0M cached · cost unknown (no per-model trailer) · session 3 tally 700 in / 450.7K out / 75.6M cached · usage by commit and session

feat(sky): the beam runs from cap to cap, the sun along it (D-079)

The sun's shoal takes part of the beam instead of all of it, so the
rod glows the whole length at every hour, 70% of itself past the sun,
and always ends on the far cap's lit mirror. The ground's light is
unchanged; the split is on the backlog.

Usage: 0.7K in / 450.7K out / 75.6M cached (session tally)
rtk:   963.1K saved, 15.9% over 2869 cmds (global/lifetime)

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
 README.md                   |  5 +++--
 decisions.md                | 25 +++++++++++++++++++++++++
 design.md                   | 20 ++++++++++++--------
 src/views/interior-model.ts | 19 ++++++++++++-------
 4 files changed, 52 insertions(+), 17 deletions(-)

diff --git a/README.md b/README.md
index 58f3cd2..91d254f 100644
--- a/README.md
+++ b/README.md
@@ -374,8 +374,9 @@ The one part of the far cap that is not glass is a **3.2 km mirror** on the axis
 A quarter of the beam survives all 540 km and arrives collimated, so it lands as
 1.7 MW/m² — 1,200 suns — on a spot a thousandth of the cap's area; the mirror sends
 it back up the column for a second pass, which lifts the beam's utilisation from 76%
-to 94%. By day the sun takes the whole beam before it gets there (below), so the mirror
-is lit only at night, by the banked beam: a faint disc where the rod ends.
+to 94%. The rod reaches it at every hour: by day the sun takes part of the beam and the
+rest runs on past it, so the beam is one line of light from cap to cap with the sun
+moving along it, and the mirror is a lit disc where the rod ends.
 
 Both caps' windows are framed by **mullions** in a lattice of identical equilateral
 triangles, 8.3 km on a side, six meeting at the axis. That is the pitch of the checker the
diff --git a/decisions.md b/decisions.md
index 290b1b5..8097748 100644
--- a/decisions.md
+++ b/decisions.md
@@ -2447,6 +2447,8 @@ and the build — but it means a remembered view or camera follows you between b
 
 ## D-074 — Tower has a sun, and keeps Sydney's days
 
+*Amended by D-079: the rod no longer stops at the sun; it runs on to the far cap.*
+
 **Decided** by the author, 2026-09-27. Built. Supersedes D-053; amends D-008.
 
 **A sun at one point on the beam, crossing the world once a day.** The swarm gathers into
@@ -2620,3 +2622,26 @@ rim mullion closes them. The far cap's mirror sits on the centre vertex, and the
 aperture on the same point of its own lattice. `test/cap-pattern.test.ts` pins the lattice:
 three directions 60° apart, every line a whole triangle height from the axis. The polar
 checker drawing, which nothing had used since both caps became windows, was removed with it.
+
+## D-079 — The beam runs from cap to cap, and the sun moves along it
+
+**Asked for** (author, 2026-09-28): "can we keep a permanent light beam in the sky (from cap
+to cap) and the large sun still moving from cap to cap? … i'd like the beam extend all the
+way, not just from sun cap to the sun."
+
+Under D-074 the sun's shoal took the whole beam where it gathered, so by day the rod glowed
+from the sun cap to the sun and was dark past it, and the far cap's mirror was lit only at
+night. Now the shoal takes **part** of the beam and the rest runs on: the rod is one line of
+light from cap to cap at every hour, the sun still crosses the world along it, and the rod
+always ends on the lit mirror at the centre of the far cap (D-048).
+
+**How much gets past.** `BEAM_PAST_SUN = 0.7` in `SUN_GLSL`: past the sun the rod is 70% of
+itself, so the sun still reads as where the beam is being taken, and the mirror by day is
+lit by that same share. One constant for both, so the rod and the mirror it ends on agree.
+Night is unchanged: the banked beam runs the whole length evenly.
+
+**What did not change, and should be settled.** The light on the ground is still worked out
+as if the sun took the whole beam (`direct`), so the world is exactly as bright as before.
+Strictly, a sun taking 30% of the beam would light it less. That is on the backlog
+(design.md §5): pick the split, then the light budget follows from it. The map, which does
+not draw the rod, is untouched.
diff --git a/design.md b/design.md
index 3615b63..85ede38 100644
--- a/design.md
+++ b/design.md
@@ -168,9 +168,10 @@ there — one blazing point 3.2 km across, which is Tower's sun. The shoal moves
 forms at the sun cap at sunrise and flies the length of the world by sunset, where it
 disperses (§1.3.4). Each mirrorlet in it is aimed, so Tower sends as much of the sun's
 light toward ground far away as toward ground beneath it, and the sun looks equally
-bright from everywhere, as Earth's does; the ground is lit by how high it stands. A
-shoal dense enough to take the whole beam leaves none past it, so **the rod glows from
-the sun cap to the sun and is dark beyond it.** This is the one exception to the rule
+bright from everywhere, as Earth's does; the ground is lit by how high it stands. The
+shoal takes only part of the beam and lets the rest run on (D-079), so **the rod glows
+from cap to cap at every hour**, a little dimmer past the sun than before it, and ends
+on the far cap's mirror. This is the one exception to the rule
 above that the diffusers must never read as a sun: Tower wants exactly one.
 
 **Aiming resolution is human-sized.** A 1.2 cm mirror is diffraction-limited to
@@ -237,9 +238,9 @@ this is a state Tower can survive and not one it can sit in.
 
 #### 1.3.3 The far cap mirror, and where the last quarter of the beam goes
 
-*Since D-074 this is the night's account.* By day the sun's shoal takes the whole beam
-wherever it is, so nothing reaches the far cap until sunset; the mirror works on the
-banked beam of the night, and on whatever passes the shoal at the day's ends.
+*Since D-079 the mirror is lit at every hour.* By day the sun's shoal takes part of the
+beam where it is and lets the rest run on to the far cap; by night the mirror works on the
+banked beam. The rod always ends on it.
 
 The swarm has optical depth 1.41, so **24% of the beam survives all 540 km** — and
 it survives *collimated*, in the beam's own 7.96 km² cross-section. That is the
@@ -368,8 +369,8 @@ So the interior sky behaves like this, and the shader must reproduce it:
   reads as endless in the axial direction and closed in the circumferential one.
 - **The ground curves up on both sides** into that same blue, meeting overhead.
 - **The beam** is a self-luminous scattering shaft 3.65° wide at 779 W/m²/sr —
-  render it as a bright sunlit cloud, not as a light source with a halo. By day it
-  runs from the sun cap to the sun and stops there (§1.3.1).
+  render it as a bright sunlit cloud, not as a light source with a halo. It runs
+  from cap to cap at every hour; by day it is dimmer past the sun (§1.3.1, D-079).
 - **The sun** is the other thing in the sky that is not land: a blazing disc on the
   beam, 3.65° across from directly beneath and smaller from afar, with glare round it
   (§1.3.4). With the hull sealed, the rod and the sun are all there is.
@@ -1178,6 +1179,9 @@ Things decided to be worth coming back to, not scheduled.
   antisolar point walks a degree a day. The placed star in `STAR_GLSL` is **not canon**
   and stays only until this is revisited: a motionless point, a beacon Tower hangs there,
   or nothing.
+- **The sun's share of the beam** (D-079). The rod now runs on past the sun at 70% of
+  itself, which says the shoal takes only 30% — but the ground is still lit as if the sun
+  took the whole beam. Canon has to settle the split, and the light budget with it.
 - **The exterior's dress** (D-076, D-077). Rock and Piping are experiments, labelled as
   such in the Hull switch. Pick one, combine them, or keep the bare skin — and if a
   heavy one stays, answer for its mass.
diff --git a/src/views/interior-model.ts b/src/views/interior-model.ts
index 5df6131..79048dd 100644
--- a/src/views/interior-model.ts
+++ b/src/views/interior-model.ts
@@ -182,6 +182,11 @@ vec4 windowColour(vec3 face, vec3 solid, float cover) {
  */
 const SUN_GLSL = /* glsl */`
 uniform vec3 uSun;
+/**
+ * How much of the beam runs on past the sun by day, to the far cap (D-079). The shoal takes
+ * the rest; this is what keeps the rod whole from cap to cap, and the far cap's mirror lit.
+ */
+const float BEAM_PAST_SUN = 0.7;
 vec3 sunPos() { return vec3(0.0, 0.0, uSun.x); }
 vec3 sunDir(vec3 p) { return normalize(sunPos() - p); }
 vec3 upAt(vec3 p) { return -normalize(vec3(p.xy, 0.0)); }
@@ -1210,9 +1215,9 @@ void main() {
   float rc = length(vWorld.xy) / ${R.toFixed(1)};
   float mirror = 1.0 - smoothstep(${(MIRROR_UV_RADIUS * 0.86).toFixed(5)},
                                   ${MIRROR_UV_RADIUS.toFixed(5)}, rc);
-  // By day the sun's shoal takes the whole beam before it gets here (D-074): only the
-  // banked beam of the night reaches the mirror.
-  vec3 glow = mirror * vec3(1.0, 0.96, 0.88) * uExposure * 2.6 * uSun.z;
+  // Lit by whatever beam arrives: by night the banked beam, by day what gets past the
+  // sun (D-079), so the rod ends on a lit mirror at every hour.
+  vec3 glow = mirror * vec3(1.0, 0.96, 0.88) * uExposure * 2.6 * (uSun.z + uSun.y * BEAM_PAST_SUN);
   gl_FragColor = windowColour(face + glow, mullion + glow, frame.a);
   #include <colorspace_fragment>
 }
@@ -1252,11 +1257,11 @@ void main() {
   // or the shaft renders as a hard-edged slab instead of a glowing rod.
   float through = pow(clamp(abs(dot(vNormal2, view)), 0.0, 1.0), 1.1);
   /**
-   * By day the sun's shoal throws the whole beam out sideways where it gathers (D-074), so
-   * the rod glows from the sun cap to the sun and is dark past it. By night the banked
-   * beam runs the whole length again.
+   * The rod runs from cap to cap, day and night (D-079). By day the sun's shoal takes part
+   * of the beam where it gathers, so past the sun the rod is dimmer — BEAM_PAST_SUN of
+   * itself — but never gone. By night the banked beam runs the whole length evenly.
    */
-  float spent = smoothstep(uSun.x - 1.5, uSun.x + 1.5, vWorld.z) * uSun.y;
+  float spent = smoothstep(uSun.x - 1.5, uSun.x + 1.5, vWorld.z) * uSun.y * (1.0 - BEAM_PAST_SUN);
   vec3 T = towerTransmittance(uCam, vWorld);
   gl_FragColor = vec4(uBeam * uExposure * through * (1.0 - spent) * T, 1.0);
   #include <colorspace_fragment>