fuzzyworld

tower / commit

docs: establish Tower's canon and its decision record

866c7f965aadc90793b462cfded44584019e7adc · Pavel Safronov · 2026-09-06 20:13:43 -0700

docs: establish Tower's canon and its decision record

Tower is the setting of a novel and this repo is its companion piece, so
these two files are series-bible material: coherence outranks spectacle and
the prose is downstream of them.

design.md holds the physics of a 50 x 540 km cylinder spun for 1 g, with
Wisconsin's surface area, lit by 54.3 TW piped down its axis and scattered
by 78 billion individually flown mirrorlets.

decisions.md records how each number was reached, including the ones that
were wrong first. The beam was widened from 1 km once the original figure
turned out to demand 313,000x concentration and break the etendue limit; the
star slats were removed entirely, which cost the wheeling sky and bought a
world where nothing overhead moves but all falling water leans.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
 decisions.md | 702 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
 design.md    | 684 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++
 2 files changed, 1386 insertions(+)

diff --git a/decisions.md b/decisions.md
new file mode 100644
index 0000000..24a0105
--- /dev/null
+++ b/decisions.md
@@ -0,0 +1,702 @@
+# Tower — Decisions
+
+Standing record of choices made and why, so they can be revisited deliberately
+rather than rediscovered. Newest sections append at the end.
+
+---
+
+## D-001 — Cylinder geometry: r = 50 km, L = 540 km
+
+**Decided.** Radius 50 km, length 540 km, giving 169,646 km² of inner surface.
+
+Considered three shapes, all matching Wisconsin's area at 1 g:
+
+| | radius | length | spin period | character |
+|---|---|---|---|---|
+| Long & narrow | 8 km | 3,375 km | 3.0 min | ceiling, not sky; absurd aspect ratio |
+| **Balanced** | **50 km** | **540 km** | **7.5 min** | far side visible overhead through haze |
+| Wide & short | 150 km | 180 km | 13.0 min | a drum; far cap looms as a wall |
+
+**Why balanced.** It is the only one where the sky reads as *sky*. At 8 km the
+roof is 16 km up and oppressive; at 150 km the far side is 300 km away and lost
+entirely. At 50 km the opposite land is 100 km overhead — far enough to be pale
+and dreamlike, near enough to be unmistakably *land above you*. That image is the
+point of the whole project, and this geometry is what produces it.
+
+The 7.5-minute spin also sits in a good window: fast enough that star motion is
+obvious in a few seconds of watching, slow enough not to look like a fairground
+ride.
+
+---
+
+## D-002 — Slats sized so land area equals Wisconsin's land area
+
+**Decided.** 8 slats, 6.80 km wide, leaving 140,268 km² of land.
+
+The brief specified a Wisconsin-sized inner surface. Wisconsin has two areas —
+total 169,635 km² and land 140,268 km². Sizing the slats to consume exactly the
+difference makes both true at once: **Tower's total inner surface is Wisconsin's
+total area, and Tower's habitable land is Wisconsin's land area. The slats are its
+lakes.**
+
+This is a coincidence discovered while computing, not a constraint imposed. It
+costs nothing and it is too good to pass up.
+
+*Superseded by D-017: there are no slats, so Tower's land is its entire inner
+surface, 169,646 km² — Wisconsin's total area. The second coincidence recorded
+below no longer applies.*
+
+**Slat count is left as a tunable constant.** The width follows from the count:
+
+| slats | each | land strips between |
+|---|---|---|
+| 6 | 9.07 km | 43.3 km |
+| **8** | **6.80 km** | **32.5 km** |
+| 12 | 4.53 km | 21.7 km |
+| 24 | 2.27 km | 10.8 km |
+
+8 chosen because 32.5 km strips read as *regions* — a day's travel across, room
+for real geography. At 24 the world becomes narrow ribbons where every point is
+within 5 km of a wall of stars, which is striking but claustrophobic. Revisit
+after seeing the map view; this is cheap to change.
+
+*"Revisit after seeing the map view" is now literal — D-014 makes the count a
+runtime control from 0 to 16, and 8 is the default rather than a fixed value.*
+
+---
+
+## D-003 — Widen the beam to 10 km; the physics now works unbroken
+
+**Decided.** The beam is 10 km in circumference, carrying 54.3 TW at 5,000×
+concentration. No physical law is bent. *This supersedes an earlier version of
+D-003 that accepted an étendue violation in order to keep a 1 km beam.*
+
+**The mistake in the first pass.** I treated the 1 km beam as fixed and concluded
+that Earth-like brightness was impossible without an active, physics-breaking
+waveguide. That was wrong, because it conflated two independent parameters:
+
+- **Collector aperture** sets *how much power* is gathered. This was never limited
+  by anything but engineering scale.
+- **Beam cross-section** sets *how hard the light must be concentrated* to fit
+  through. This is where étendue binds — passive optics cannot exceed the sun's own
+  surface radiance, ~46,223× at 1 AU, or 62.9 MW/m².
+
+Holding the beam at 1 km forced 313,000× and broke the second law. Widening the
+beam relaxes the concentration requirement quadratically and costs nothing else.
+
+**Where the number came from.** Targeting 54.3 TW — Earth clear-sky noon at local
+midday, see design §1.3 — against candidate concentrations:
+
+| concentration | beam circumference needed | status |
+|---|---|---|
+| 46,223× (étendue limit) | 3.29 km | thermodynamically impossible to exceed |
+| 10,000× | 7.08 km | beyond current practice |
+| **5,000×** | **10.01 km** | **Odeillo solar furnace reaches ~10 MW/m² today** |
+| 1,000× | 22.39 km | routine, but a conspicuously fat beam |
+
+**5,000× chosen.** It lands the beam on a round 10 km, it is *demonstrated*
+terrestrial technology rather than an extrapolation, and it sits at 11 % of the
+étendue limit — 9.2× of headroom in reserve. Diffuser mirrors at 99.9 %
+reflectivity reach thermal equilibrium at 316 °C in that beam, an ordinary
+radiator problem rather than an exotic one.
+
+**Cost to the imagery: none.** At 1,592 m radius seen from 50 km below, the beam is
+3.65° wide — about seven sun-widths. It still reads as a luminous rod running the
+length of the sky, which is the image the 1 km figure was protecting.
+
+**The unlocked consequence.** Because Tower now has *surplus* capability rather
+than a deficit, brightness becomes a **setting** rather than a fixed value. The
+diffusers are software-controlled; illumination can run anywhere from 30 W/m²
+permanent dusk to 320 W/m² clear-sky noon. The "embrace the dimness" option
+weighed and rejected in the first pass is no longer an alternative to this
+decision — it is a position on its slider. The app should expose that control.
+
+## D-004 — Terminator orbit, actively precessed
+
+**Decided.** Orbital plane held normal to the Earth–Sun line, precessing 360°/year.
+
+The brief requires that one cap face the sun permanently. Any orbit whose plane
+contains the sun line puts Tower through Earth's shadow once per revolution, which
+would interrupt the beam. A terminator orbit never enters shadow.
+
+The cost is that the plane must be reoriented ~1°/day against the gyroscopic
+stiffness of a spinning 540 km cylinder. This is enormous and continuous. Canon
+adopts it as a feature: Tower's existence is a permanent act of maintenance, not a
+stable configuration. That is more interesting than a free lunch.
+
+---
+
+## D-005 — Vanilla TypeScript, no UI framework
+
+**Decided.** Vite + TypeScript + Three.js, no React/Vue/Svelte.
+
+The app is three canvases, a picker and a control panel, sharing one small state
+object. A framework would add build weight and a rendering model that fights
+Three.js's own loop, in exchange for conveniences this UI does not need. Revisit
+only if the control panel grows genuinely complex.
+
+Canvas 2D rather than Three.js for the map: it is a flat unrolled rectangle with no
+projection distortion, and 2D canvas draws that natively.
+
+---
+
+## D-006 — Interior fidelity: real geometry, simple ground
+
+**Decided.** The shell, curvature, haze, beam, diffusers, slats and stars are all
+genuinely modelled and correct. The ground is procedural terrain and texture only —
+no scenery meshes, no settlements, no vegetation.
+
+The illusion of standing inside Tower comes almost entirely from the sky: land
+curving up into blue, the lit shaft overhead, stars wheeling past every 7.5
+minutes. Detailed ground scenery adds cost proportional to realism and contributes
+little to that illusion. Deferred, not abandoned.
+
+---
+
+## D-007 — All canon derived from four constants
+
+**Decided.** `canon/constants.ts` exposes radius, length, slat count and target
+land area as free parameters. Everything else — spin period, rim speed, Coriolis
+parameter, slat width, angular spacing, axis pressure, beam power — is computed.
+
+The alternative, transcribing the figures in `design.md` as literals, would drift
+the moment any parameter is revisited (and D-002 and D-003 both flag likely
+revisits). Derivation makes the whole app answer coherently to a changed radius.
+
+---
+
+## D-008 — Diffusers are a 1.2 cm swarm; the beam glows from air, not from them
+
+**Decided.** 78 billion 1.2 cm mirrorlets, and the beam's visible glow is
+atmospheric scattering that does not depend on them.
+
+**The question.** Would a modest number of large diffusers read as a string of
+separate suns rather than a continuous glowing rod?
+
+**Yes.** At 50 km, naked-eye resolution is 14.5 m; anything larger shows a visible
+disc. But shrinking below 14.5 m is not sufficient, which was the non-obvious part.
+A flat mirror preserves radiance, so an aimed mirrorlet shows the *sun's own
+surface brightness* over a small angle — unresolved but blinding, exactly as stars
+are unresolved and plainly visible. Magnitudes: 1 m → −13.6 (brighter than a full
+moon), 10 cm → −8.6, **1.2 cm → −4.0**, which finally sits under the daylight
+visibility threshold. The binding constraint is flux, not angular size, and it is
+two orders of magnitude tighter than the resolution limit implies.
+
+**The better answer, found while checking.** The beam glows regardless. Rayleigh
+scattering of a 6.8 MW/m² beam through 3.18 km of 5.2 kPa axis air gives 779
+W/m²/sr — 26× blue sky, 25,700× below the solar disc. The continuous-glow imagery
+is therefore a robust property of beam plus atmosphere, not a consequence of
+getting the diffuser design right. Canon leans on this; the swarm sizing is then
+only about keeping individual glints out of the sky.
+
+**Consequences taken.** 11.3 km² of mirror at 1.2 cm is 78 billion units massing
+158 tonnes, spaced 3.8 m apart. Optical depth 1.41 leaves 24 % of the light
+reaching the far cap, which satisfies the brief's "shines to the far end" without
+stipulating it. *The passive drift-and-recirculate mechanism first recorded here is
+superseded by D-009 — it does not work.*
+
+**Story consequence.** 78 billion independently aimable mirrors make illumination a
+render target rather than a schedule. Sunsets are compositing, not optics.
+
+---
+
+## D-009 — Tower flies every foil itself, because nothing else works
+
+**Decided.** All 78 billion foils are under continuous individual control by Tower.
+There is no recirculation machinery at either cap. *This supersedes the passive
+drift mechanism in D-008.*
+
+**Origin.** Proposed by the author as characterisation — Tower is an intelligent
+being, so it controls each foil directly and moves the swarm partly for its own
+enjoyment. Checking the physics turned this from a flourish into a requirement.
+
+**A passive swarm is impossible.** D-008 assumed the axis was effectively
+weightless. It is not. At the beam's edge, 1,592 m out, g = ω²s = **0.312 m/s²**. A
+2 mg foil sinks outward at 0.26 m/s and **falls clear of the beam in 1.7 hours**. A
+drifting swarm dissolves in an afternoon. Continuous active flight is the only
+configuration that holds together at all.
+
+**They can easily afford it.** Each foil intercepts 979 W; harvesting 0.1 % yields
+~1 W on a 2 mg body — 489,600 W/kg, ~50× a jet engine. Flying up-beam at 1.5 m/s
+costs 15 µW against 14,400 µW harvestable from ambient world-light: **a 1000×
+margin, without touching the beam.** So the foils fly home unaided, no structure is
+required at the far cap, and the exterior render is simpler than D-008 implied.
+
+**The interesting part is the slack.** With 1000× of margin, efficiency is not a
+constraint on Tower's behaviour. It can move the swarm in spirals, shoals and
+patterns that cost energy for no reason except preference, and nothing about the
+world degrades. Play is not a luxury Tower affords despite the physics — it is
+budget the physics hands over for free. The weather is therefore not a system
+Tower operates but something closer to body language.
+
+**Falling out of this: aiming is human-scale.** A 1.2 cm mirror is
+diffraction-limited to 4.6×10⁻⁵ rad, giving a **2.3 m spot** at 50 km. Tower can
+place a glint on one person that a bystander five metres away cannot see. Offered
+to the author as a story mechanic; canon records the optics either way.
+
+**And: mirrorfall.** A failed foil sinks out of the beam, takes ~2 days to descend,
+and lands at 0.48 m/s — snowflake speed. A permanent slow glitter falls out of the
+sky.
+
+---
+
+## D-010 — The app is a companion to a novel, not a tech demo
+
+**Decided.** Tower is the setting of a novel the author is writing. The app serves
+that purpose first.
+
+Two consequences, recorded because they change priorities rather than mechanics:
+
+- **Coherence outranks spectacle.** These documents are series-bible material and
+  the prose is downstream of them. A number that looks better but contradicts
+  something is worse than a duller number that holds. This is already how the
+  physics has been handled; it is now the stated rule.
+- **Shareable permalinks to a point move into scope** (phase 4, previously
+  deferred). "Stand here and look at this" is the entire value of a companion
+  piece, and a link is how a reader receives it. Named locations from the novel
+  should be markable on the map for the same reason.
+
+Not yet decided, and deliberately left open: whether the app should ever surface
+narrative content — place names, events, characters — or remain a pure physical
+model that the prose refers to from outside. That is the author's call and does not
+need answering to proceed.
+
+---
+
+## D-011 — Writer mode: the app is an instrument, and the world file is git
+
+**Decided.** Two modes over one committed `world.json`. Readers get a finished map;
+the author gets editing plus a set of measuring instruments. No backend.
+
+**Origin.** The author intends to use the app to *write* — placing things and asking
+questions like "can the far face be seen from the midpoint?", "where does a central
+embassy go?", "can a lake overhang?". That makes measurement, not rendering, the
+core feature. D-010 said the app serves the novel; this is what that costs.
+
+**Why those questions justify the build.** Two of the three had non-obvious answers
+that only fell out of computation, and both became canon (§1.8): the far cap is
+invisible from the midpoint at 4 % transmission, and — unexpectedly — the far side
+of the world overhead is *clearer* than the ground at the same distance (78 % vs
+31 % at 100 km), because a sightline across the interior rises through thin axis
+air. An author cannot guess these. The instrument earns its place immediately.
+
+**Static, file-based, no backend.** GitLab Pages serves static files, so: edit in
+browser → export → commit → publish. The world lives in git beside the prose,
+diffable and versioned, with no service to run or pay for. Drafts sit in
+`localStorage` between exports.
+
+**Coordinates in the file are canon (θ, z), never map pixels,** so the world
+survives any change to how the map is drawn. Consistent with D-007.
+
+**Writer mode is a UI mode, not a security boundary.** `?writer=1` is guessable and
+that is fine — an editor with no persistence and no backend can only alter the
+visitor's own copy. Recorded explicitly so it is never mistaken for access control
+and never load-bearing for privacy.
+
+**The real privacy risk is the repo, not the flag.** Working notes must not ship to
+readers, but anything committed to a public repository is public whatever a
+`"public": false` field says. Hence two export files: `world.json` (public entries,
+committed) and `world.private.json` (everything, gitignored). Whether private notes
+should be version-controlled anyway — private repo, or a second one — is left to
+the author and does not block anything.
+
+**Sequenced early.** The sightline probe lands in phase 6 but should be built first
+within it: it is useful the day it exists, and §1.8 is evidence the author is
+already asking questions only it can answer.
+
+---
+
+## D-012 — Ids follow the name until pinned by hand
+
+**Decided.** A place's id is re-derived from its name while it still matches it,
+and freezes permanently once the author edits the id field.
+
+**The tension.** design.md §3.2 makes ids permalink targets, which argues for
+never changing them. But places are created from a placeholder name ("New city"),
+so a never-changing id produces a world file of `new-city`, `new-city-2`,
+`new-city-7` — unreadable in git diffs and unusable for hand-editing, both of which
+D-011 counts as reasons for the file-based design in the first place.
+
+**Resolution.** During world-building, readability wins; after publication,
+stability wins; and the author is the only one who knows which phase a place is in.
+So the id tracks the name automatically until they say otherwise, and the id field
+is shown in the editor so saying otherwise is one edit. `isAutoId()` in
+`src/world/schema.ts` is the test, and is unit-tested directly.
+
+**Found by running it.** The first implementation derived the id once at creation
+and never updated it, which the browser check caught immediately — every exported
+place was called `new-city`. Two further bugs surfaced the same way: renaming
+blanked the editor (the store re-rendered before the selection moved to the new id),
+and clicking an existing marker while a creation tool was active dragged the old
+place instead of creating a new one. None were visible in the unit tests.
+
+---
+
+## D-013 — The map is bounded, and cut at a slat centre
+
+**Decided.** The map shows the inner surface once, as a bordered rectangle, rather
+than tiling vertically. The cut falls at the centre of the last slat.
+
+**Why not tile.** The circumference wraps, so tiling is the honest rendering and it
+makes places near the seam continuous. But it also makes the world look boundless:
+with no edge, the author cannot see the extent of what they are building, and
+repeated strip labels read as more strips rather than as repeats. For a map — a
+thing whose job is to show where things are relative to the whole — legible extent
+beats a hidden seam.
+
+**Why cut there.** Any bounded map needs an edge, and the edge destroys whatever it
+falls on. Land strip 0 is centred on θ = 0, so the obvious cut would slice a whole
+strip in half across the top and bottom margins. Cutting at a slat centre instead
+leaves every land strip whole and splits only a band of stars, which carries no
+geography. This is exactly why Earth's maps are cut at the antimeridian. With zero
+slats there is nothing to protect, so the cut returns to θ = 0.
+
+**Consequences taken.** Panning is clamped so the surface cannot be lost off-screen
+— without tiling there is nothing to pan back into. A sightline whose short route
+crosses the edge is drawn as two segments, entering and leaving opposite edges, so
+the drawn path always matches the measured distance rather than contradicting it.
+
+---
+
+## D-014 — Slat count is a runtime control, 0 to 16
+
+**Decided.** `SLAT_COUNT` stops being a compile-time constant. The count lives in
+`src/canon/slats.ts`, adjustable from 0 to 16, persisted per browser, with every
+slat-derived figure computed from it.
+
+**Why.** D-002 already called this "the most tunable number in canon" and said to
+revisit it after seeing the map. Making it a slider makes that revisiting real
+rather than a code edit, and the author is choosing the shape of a world rather
+than configuring software.
+
+**The invariant that makes it safe.** D-002 fixes the *total* slat width from the
+land-area target, and that total does not depend on how many slats it is divided
+into. So **land area stays exactly Wisconsin's 140,268 km² at every count from 1 to
+16** — the coincidence survives the whole range, and only the per-slat width moves
+(18.1 km at 3 slats, 3.40 km at 16).
+
+**Zero is a genuinely different world, and the app says so.** With no slats the
+land becomes the entire inner surface, 169,646 km², and nobody in Tower can ever
+see a star. That breaks the D-002 coincidence, so the control states it plainly
+instead of silently reporting a changed number. Rounding that away would have been
+the easy option and the wrong one — it is exactly the kind of quiet inconsistency
+the documentation gate exists to catch.
+
+**Cost.** Slat geometry moved from constants to functions, which rippled through
+`geometry.ts`, the map, the instruments and the tests. Worth it: D-007's principle
+was always that canon is derived rather than transcribed, and this is that
+principle extended one parameter further.
+
+---
+
+## D-015 — Hydrology: shaped terrain, no sideways Coriolis, climate from the slats
+
+**Decided.** Recorded as canon in design.md §1.9, and it amends §1.4.
+
+**The provable part.** The floor's tangent plane is spanned by θ̂ and ẑ and the spin
+axis is ẑ, so surface flow gets `ẑ × ẑ = 0` axially and a purely radial `ẑ × θ̂ = −ŝ`
+circumferentially. The Coriolis force has **no component inside the surface** at any
+heading. Tower's rivers do not curve, and Earth's entire rotational repertoire —
+hemispheres, Baer's law, gyres, cyclones — is absent. This is not a simplification;
+it falls out of the geometry.
+
+**Where the force goes instead.** Into vertical motion. Waterfalls lean retrograde
+as h^1.5 (133 m over a 1 km drop, 377 m over 2 km), and rain hangs at a permanent
+1.47° slant that never varies. Rising things lean the opposite way. A world where
+every falling thing drifts one way and every rising thing the other is a strong,
+cheap, always-visible signature.
+
+**The absence that matters more.** Potential depends only on radius, so the floor is
+one equipotential and a perfect Tower has *no rivers at all*. Every river exists
+because the floor was shaped, there is no tectonic uplift, and erosion is therefore
+one-way — ~30 Myr to erase a 3 km range. Tower's hydrology is maintenance, which is
+D-004's theme arriving from a new direction.
+
+**An inconsistency found and fixed, not smoothed over.** §1.4 specified a
+glass/**diamond** composite slat fill. Diamond conducts ~2,000 W/m·K; such a slat
+would radiate 385 W/m² to space, exceeding Tower's entire 320 W/m² mean insolation
+and freezing the world. Canon now specifies low-conductivity glass, which loses
+~28 W/m² through 5 m. Raised explicitly per the documentation gate rather than
+quietly retconned.
+
+**The gift in that loss.** Tower has no latitude — every point is equidistant from
+the light, so there is no natural climate gradient whatsoever, which is a real
+worldbuilding hole. The leaking slats fill it: eight cool belts running the full
+540 km, parallel to the axis. **Tower's climate bands run lengthwise, not around**,
+and the slat count sets the climate as well as the geography — further support for
+D-014 making it a control.
+
+---
+
+## D-016 — Slat orientation: axial or rings — OPEN
+
+**Not decided.** Canon keeps axial strips for now. Rings are a live alternative and
+this records what the choice costs, so it can be made deliberately.
+
+**Why it matters more than it looks.** Slat layout turns out to set three things at
+once — the shape of Tower's countries, its entire climate map, and what its people
+can see of the universe. It is not a detail of the hull.
+
+**Geometry.** At equal total area: rings are 11.69 km wide with 55.8 km land bands.
+Each land region is then 314 × 55.8 km rather than 32.5 × 540 km — the same area at
+aspect 5.6:1 instead of 16.6:1, a country instead of a corridor. Ring bands are also
+**closed loops**: you can walk 314 km around your whole region and come home without
+crossing a window. Axial strips are ribbons with two dead ends at the caps.
+
+**Climate.** Axial strips put the cool belts lengthwise, so a 540 km river runs its
+whole length through identical country. Rings put them crosswise, giving Tower a
+genuine latitude analogue — a long river crosses all eight bands like the Nile.
+
+**The sky, which is the deep one.** The spin axis is the celestial pole, so stars
+move along the celestial equator. Orientation decides whether a window lies across
+that motion or along it:
+
+- *Axial*: 3.90° across the motion — each star crosses in **4.9 s**, but the strip
+  spans pole to pole so essentially the whole sky passes through, strobing.
+- *Rings*: 6.70° along the motion — a star rides the band horizon to horizon over
+  **3.7 minutes**, but only the 5.8 % of sky within 3.35° of the celestial equator
+  is *ever* visible. The rest is never seen at all.
+
+Earth follows the same rule: a five-second flicker under axial slats, a slow bright
+traveller under rings. The sun is at the celestial pole and invisible through both.
+
+**The cost of rings.** Hoop stress dominates the structure — ρv² is 785 MPa in a
+composite shell plus ~500 MPa of air pressure — and it runs along θ. Axial slats sit
+across that path, so ribs bridge the gap and the glass carries only local pressure.
+A ring runs along the path and is continuous in θ, so **the window itself must carry
+~1.2 GPa**. Glass cannot; it would need hoop cables through every pane, putting bars
+across every view forever. Characterful, but a real price axial slats do not pay.
+
+**Consequence for the app if rings are chosen.** D-013 cuts the map at a slat centre
+because there is an empty θ to cut at. With rings there is not — every θ is land —
+so the map seam would have to slice through inhabited country, or the map would need
+reorienting. Worth knowing before the choice is made rather than after.
+
+---
+
+## D-017 — The hull is sealed; the only views out are at the caps
+
+**Decided by the author.** No slats, in any orientation. The cylinder wall is
+opaque and unbroken for its full 540 km. Supersedes D-002's slat sizing, D-014's
+default, and closes D-016.
+
+**What it costs.** The wheeling star field was canon's answer to "what is most alien
+about standing inside Tower" (§1.2) and the interior view's headline feature. It is
+gone. Tower also loses its only passive climate driver and its only natural source
+of geographic variation.
+
+**What it buys, which is more.**
+
+- **One unbroken surface.** 169,646 km² of continuous land — Wisconsin's *total*
+  area, the coincidence in §1.1 now standing alone and cleaner. Nothing divides the
+  world into strips; you can walk anywhere, and a river can circle the whole 314 km
+  circumference and close on itself.
+- **The spin inverts.** With no windows and a co-rotating far side, *nothing in the
+  sky moves at all*. But rain still hangs at a permanent 1.47° slant and a 1 km
+  waterfall still lands 133 m off its cliff. **The sky is still and the water is
+  not** — a stranger signature than wheeling stars, and one that shows up in every
+  puddle rather than only when you look up.
+- **The caps become the only view, and it is a good one.** A cap window looks along
+  the spin axis — the celestial pole — so stars do not sweep but *rotate about a
+  fixed centre*, one turn per 7.5 minutes, one motionless star at the middle. The
+  caps are also walls rather than floors: gravity there is tangent to their surface,
+  so each is a 50 km cliff at the end of the world with its windows at the base.
+- **Nobody can ever see Earth.** The terminator orbit (D-004) puts Earth ~90° off
+  the axis, out the side, which is now solid hull. A habitat in Earth orbit whose
+  people have never seen the planet they orbit.
+
+**The consequence the author accepted explicitly.** With no latitude and no windows,
+nothing breaks the thermal uniformity, so there is no passive weather whatsoever.
+Every cloud, every rainfall and every jet stream is Tower deliberately aiming light.
+This is D-009 reaching its end point: the swarm was already Tower's body language,
+and is now its lungs. There is no autonomic layer beneath. **If Tower's attention
+lapses, the rain stops.**
+
+---
+
+## D-018 — Rivers are a closed, pumped circulation, and Tower can easily afford it
+
+**Decided.** Tower's rivers follow the architecture the author proposed: reservoir →
+shaped descent → lake → lift back to the reservoir. Source and sink are one system.
+
+**Why it is the only option.** The floor is an equipotential (D-015) so there is no
+natural slope, and the sealed hull (D-017) leaves no natural water cycle. Water in
+Tower moves only because something moves it.
+
+**Why it is not a compromise.** Costed against the 54.3 TW budget:
+
+| | | |
+|---|---|---|
+| Earth-like rainfall, 1,000 mm/yr | 12.2 TW | **22.4 %** — Earth spends the same share |
+| Recirculating all runoff (1,792 m³/s, a Rhine) against 500 m | 8.8 GW | **0.016 %** |
+
+Pumping the entire hydrology of the world is a rounding error — 6,000× headroom.
+And full Earth-intensity weather is affordable at the same fraction of budget Earth
+itself spends. Tower does not have to choose.
+
+**Canon uses both, because they give different rivers.** A pumped river *begins at a
+machine* — walk to its source and find a wall of water leaving a structure. A
+rain-fed river begins at a cloud Tower made over the highlands, and looks entirely
+natural while being entirely deliberate. Made weather runs the world's general
+hydrology; pumps run the watercourses Tower wants under precise control.
+
+**Two consequences worth keeping.** A closed loop has no net descent, so a river
+circling the world must be pumped — which is the same architecture, closed the whole
+way round. And since nothing evaporates to space, **Tower's water inventory is
+conserved and exactly known, forever.**
+
+---
+
+## D-019 — Geography is authored as data; terrain is generated from it
+
+**Decided.** `content/geography.json` holds the authored geography. `npm run terrain`
+(tools/terrain.mjs) reads it and emits a height map, a colour map, a corridor detail
+render, and a seed `world.json` of places.
+
+**Why data first.** The author asked to work the geography out before returning to
+app code. A JSON source plus a generator means the map is re-derivable from one
+readable file: move the river by editing eighteen waypoints, not by repainting.
+Coordinates are canon (θ, z) throughout, consistent with D-007 and D-011.
+
+**No dependencies.** Node's `zlib` is enough to write PNGs directly (tools/png.mjs,
+~60 lines for 16-bit greyscale and 8-bit RGB), so the tool adds nothing to
+`package.json`. Terrain noise is a small value-noise/fBm module.
+
+**Three bugs the renders caught that no test would have.**
+
+- *A float hash.* The noise hash multiplied large integers in floating point,
+  silently losing precision and streaking the whole map with axis-aligned lines.
+  Fixed with `Math.imul` throughout.
+- *A fractional wrap period.* The circumference is not a round multiple of any
+  useful feature size, so the noise lattice did not close on itself and tore at the
+  seam. Sample coordinates on the θ axis are now snapped to a whole number of
+  cells; the resulting few-percent anisotropy is invisible.
+- *A discontinuous river.* Valley geometry switched between reaches abruptly,
+  stepping the valley from 5.4 km wide to 18.2 km in one pixel column and drawing a
+  visible wall across the map. Reach parameters now blend along the river. A
+  `--probe` flag prints elevation jumps along a row, which is how it was found.
+
+**Known gap, deliberately not fixed here.** The seed `world.json` contains
+`polyline` geometry (the river, the canyon) and kinds the app does not yet know.
+`parseWorld` accepts only points and polygons, so those two places would be dropped
+silently on load. App work is paused by request; this is the first thing to fix when
+it resumes. *Closed by D-022.*
+
+---
+
+## D-020 — The world is belts, and the desert rings it
+
+**Decided by the author.** The desert and its three ridges ring the world; the
+capital's water becomes a sea running along the axis; both end caps get ring oceans;
+the range feeds four parallel rivers.
+
+**The ring desert is a barrier no Earth desert can be.** 22,000 km², 13% of the
+surface — modest against Earth's ~33% — but on a cylinder there is no way *around* a
+band. Combined with three ringing ridges it is three concentric walls across Tower's
+waist.
+
+**The drainage was the hard part, and Earth answered it.** Many rivers each cutting
+their own canyon would destroy the chokepoint. But on Earth most drainages do not
+cut through a rising ridge — they are defeated and diverted, and only an antecedent
+river keeps its course. So three of the four rivers are turned by First Ridge, run
+along its front gathering each other, and leave with the fourth through the one gap.
+**The Narrows carries the entire world's runoff, ~392 m³/s.** The chokepoint is
+strengthened by the extra rivers rather than diluted, and the hydrology is more
+realistic than a single river would have been.
+
+**The Deep's orientation is the decision, not its size.** Long down the axis and
+narrow around, so it is crossed the *short* way — around θ, where the floor curves
+up and the far shore stands 2.6 km above eye level. Lengthwise it would be dead
+flat. It also gives Tower back a thermal flywheel: 19× the diurnal heat storage of
+bare ground, 5.9 days per kelvin at full power. D-017's "if Tower blinks the rain
+stops" becomes "the world has about a week of momentum" — a lapse now has time to
+become visible before it becomes fatal.
+
+**The cap seas were not chosen.** Gravity on an end cap is tangent to its surface,
+so water cannot rest there and runs to the rim. Both ring oceans are consequences,
+not designs.
+
+**Skiing came out of §1.9.2 unprompted.** A descending skier has radial velocity and
+therefore real Coriolis: a steady antispinward push on axial fall lines, and a
+weight change of up to 11% on circumferential ones. A retrograde run gets lighter as
+it accelerates; a prograde run grips harder. Every slope in Tower has a handedness,
+which is a sport that cannot exist on Earth.
+
+**Tool work this required:** `ring` footprints, a `flood` op for troughs and bands,
+and a `tarn` op that fills from the bowl floor rather than the rim — the first
+version took its level from the rim mean, so water spilled downhill and every
+mountain lake rendered as a half-moon.
+
+---
+
+## D-021 — The terrain tool reports progress, and got 13× faster doing it
+
+**Prompted by the author:** `npm run terrain` ran for a long time saying nothing.
+Adding progress output meant first working out where the time went, which turned a
+logging task into a performance one.
+
+**86 s → 6.4 s.** Three causes, all found by measuring rather than guessing:
+
+- **No spatial index on the river carve.** `opCarve` tested all 592 smoothed river
+  segments against all 2.4 million pixels — ~2.9 billion distance tests across both
+  renders. Segments are now binned into a 4 km grid and each pixel tests only its
+  own cell. *86 s → 17.8 s.*
+- **Deflate level 9 on the colour maps.** Hillshade noise compresses poorly, so
+  level 9 spent 4.2 s on one map for a few percent of size. RGB now encodes at
+  level 6: 8× faster for 10% larger files, which is the right trade for a tool run
+  on every edit. The 16-bit height map stays at level 9 — smooth terrain under the
+  Up filter is nearly all zero deltas, and it costs 0.1 s. *17.8 s → 8 s.*
+- **Domain warps computed before range checks.** Several ops evaluated two noise
+  functions per pixel *then* discovered the pixel was nowhere near the footprint.
+  A conservative unwarped-z rejection now runs first. *8 s → 6.4 s.*
+
+**The log is a table, not a stream.** One row per relief op with its timing, then
+relief/colourise totals per view, then each PNG with its size, then the seed. The
+point is to see which feature is expensive when the file grows, not just that
+something is happening. `--quiet` collapses per-feature rows.
+
+**Worth recording as a habit:** the request was for logging, and the logging is what
+exposed that the colour PNG cost more than the entire mountain range. Instrumenting
+first and optimising second found three real problems in one pass.
+
+---
+
+## D-022 — The app catches up with the decisions
+
+**Implemented.** Five outstanding decisions, plus two bugs found while doing it.
+
+**The schema now matches the world** (closes D-019's gap). `polyline` geometry
+joins point and polygon — a polygon needs three vertices for an interior, a
+polyline only needs two to go somewhere — and the kind list grows from six to
+thirteen (`mountain`, `river`, `canyon`, `forest`, `desert`, `tower`, `town`), with
+`AREA_KINDS` / `LINE_KINDS` deciding whether a traced shape closes. Before this the
+seed silently dropped The Long Water and The Narrows. A test now loads
+`public/world.json` and asserts nothing is lost, so the two files cannot drift apart
+again unnoticed.
+
+**The rendered terrain is drawn under the map**, which is what the whole tool was
+for. Area regions become outlines rather than fills when it is showing: the raster
+already says where the desert is, and a translucent rectangle over it only fights.
+
+**A desync that would have been baffling.** The app cut the map at θ = 0 with no
+slats; `content/geography.json` renders against θ = π. The raster would have landed
+**half a world out of register** with its own place markers. Both now use
+`MAP_CUT_WITHOUT_SLATS = π` — opposite the capital, which is D-013's antimeridian
+principle applied to habitation rather than geology — and a test asserts the two
+files agree.
+
+**Reader and writer modes** (D-011). `?writer=1` reveals the tools, editor, canon
+controls and import/export; readers get the map, the place list, a read-only detail
+card and the sightline probe. **A bug worth recording:** `renderEditor` un-hid the
+editor whenever a place was selected, overriding the mode — so readers were shown
+the edit fields *including private notes*. Mode is now decided inside
+`renderEditor`, not applied around it. Hiding a panel elsewhere is not the same as
+never rendering it.
+
+**Permalinks** (D-010). `#place=<id>` selects and centres a place; `#at=θ,z` restores
+a sightline endpoint. This is why D-012 made ids follow names only until pinned —
+a permalink is worthless if the id churns.
+
+**Labels are placed by priority and skipped on collision.** Settlements outrank
+regions; overlapping labels are dropped rather than drawn on top of each other. With
+22 seeded places the mountain stations were an unreadable smear.
diff --git a/design.md b/design.md
new file mode 100644
index 0000000..aee40ed
--- /dev/null
+++ b/design.md
@@ -0,0 +1,684 @@
+# Tower — Design
+
+**Tower is the setting of a novel.** This app exists to serve that: first as an
+instrument for the author to see the place and keep it consistent, and second as a
+companion piece readers can explore. Where a choice trades spectacle against
+coherence, coherence wins — canon here is series-bible material, and the book is
+downstream of it.
+
+Tower is a smart cylinder in orbit around Earth, spun for 1 g, with an inner
+surface the size of Wisconsin, lit by sunlight piped down its axis and scattered by
+a swarm of mirrors it flies itself. It is also a person.
+
+The app has three linked views:
+
+1. **Exterior** — a 3D render of Tower from outside, with its collector, beam and
+   orbit. You pick a point on the hull.
+2. **Map** — a 2D unrolled map of the inner surface. You pick a point here too; the
+   two pickers stay in sync.
+3. **Interior** — a first-person "streetview" from the picked point, with live
+   controls for time of day and spin phase.
+
+---
+
+## 1. Canon
+
+### 1.1 Dimensions
+
+All numbers derive from two free parameters — radius and length — chosen so the
+inner surface matches Wisconsin.
+
+| Quantity | Value | Notes |
+|---|---|---|
+| Radius | **50 km** | floor to axis |
+| Length | **540 km** | sun cap to far cap |
+| Circumference | **314.16 km** | the world's "width" |
+| Inner surface area | **169,646 km²** | Wisconsin total area, 169,635 km² |
+| Habitable land area | **169,646 km²** | all of it — the hull is sealed (§1.4) |
+
+Every square metre of the inner surface is land. There are no windows in the wall,
+so nothing is subtracted. *An earlier version sized star slats to leave exactly
+Wisconsin's land area of 140,268 km²; D-017 removes them and that second
+coincidence with it.*
+
+### 1.2 Spin and gravity
+
+| Quantity | Value |
+|---|---|
+| Angular velocity ω | **0.014005 rad/s** |
+| Spin period | **448.6 s** = 7 min 29 s |
+| Rotation rate | **0.134 rpm** |
+| Rim speed | **700.2 m/s** |
+| Floor gravity | **1.000 g** by construction |
+
+Consequences that the app should show or at least respect:
+
+- **Gravity falls off linearly with height.** g(h) = ω²(R−h). A 1 km peak sits at
+  0.98 g; the axis is weightless. Mirrors at the axis float.
+- **Coriolis is mild but real.** 2ω = 0.0280 s⁻¹. Walking is unaffected (0.4 % g).
+  A ball thrown at 30 m/s deflects at 8.6 % g — enough that Tower's sports differ
+  from Earth's, and enough that thrown objects visibly curve.
+- **Nothing in the sky moves.** The hull is sealed (§1.4), so there are no stars to
+  wheel past — and the far side of the world overhead co-rotates with you, hanging
+  perfectly still. The spin is invisible *upward*.
+- **But everything that falls leans.** Rain hangs at a permanent 1.47° slant, smoke
+  drifts the other way, and a 1 km waterfall lands 133 m off its own cliff (§1.9.2).
+  This inversion is the alien fact the interior view must sell: **the sky is still
+  and the water is not.**
+
+### 1.3 The sun system
+
+Sunlight enters through the sun-facing end cap and runs the full 540 km axis.
+
+- **Collector.** A mirror array flies ahead of Tower, gathering sunlight and
+  concentrating it into the beam. **46,900 km² of aperture — a disc ~244 km
+  across**, two and a half times Tower's own diameter. It is the dominant visual
+  element of the exterior view.
+- **Beam.** **10 km in circumference** — 1,592 m radius, 7.96 km² cross-section —
+  carrying **54.3 TW** at a concentration of **5,000×**, or 6.8 MW/m². It runs the
+  axis from sun cap to far cap.
+- **Diffusers.** Not lamps — a **swarm**. See §1.3.1.
+- **The beam is visible, and it glows by itself.** There is real air at the axis
+  (§1.5), and a 6.8 MW/m² beam Rayleigh-scattering through 3.18 km of it radiates
+  **779 W/m²/sr** — **26× brighter than clear blue sky, 25,700× dimmer than the
+  sun's disc.** It is a brilliantly sunlit cloud extruded into a rod, safe to look
+  at directly. From the floor 50 km below it is **3.65° wide, about seven
+  sun-widths**, running the whole length of the sky. This glow is a property of the
+  beam and the air, not of the diffusers — it would be there if the beam were empty.
+
+#### 1.3.1 The diffuser swarm
+
+The diffusers must be individually invisible, or the sky becomes a row of separate
+suns. Two thresholds govern this, and only the second one binds:
+
+- **Resolvability.** At 50 km, one arcminute is 14.5 m. Anything larger is a
+  resolved disc with a visible edge.
+- **Visibility.** Being unresolved is not enough — stars are unresolved and we see
+  them. A mirror *preserves radiance*, so a mirrorlet aimed at an observer shows
+  the sun's own surface brightness over a tiny angle. A 1 m mirrorlet reads at
+  magnitude −13.6, brighter than the full moon. Blending into daylight requires
+  **~1.2 cm**, at magnitude −4.0, just under Venus.
+
+Diverting 54.3 TW needs 11.3 km² of mirror, so:
+
+| | |
+|---|---|
+| Mirrorlet size | **1.2 cm**, ~1.4 cm², 2 mg |
+| Count | **78 billion** |
+| Total mass | **158 tonnes** — the entire sun system |
+| Mean spacing in beam | **3.8 m** — sparse enough to fly through |
+| Optical depth over 540 km | 1.41, so **24 % of light reaches the far cap** |
+
+**The swarm cannot be passive — it must fly, always.** The axis is not weightless.
+At the beam's edge gravity is **0.312 m/s²**, enough that a foil sinks outward at
+0.26 m/s and **leaves the beam in 1.7 hours**. A drifting swarm dissolves the same
+afternoon. Every foil is therefore under continuous individual control. This is not
+a flourish on a working system; it is the only configuration that works.
+
+**They have absurd power to do it with.** Each foil intercepts 979 W of beam.
+Harvesting 0.1 % gives it ~1 W on a 2 mg body — **489,600 W/kg**, some fifty times
+a jet engine's specific power.
+
+| | |
+|---|---|
+| Down-beam, riding radiation pressure | 0.85 m/s, 7.4 days |
+| Back up-beam under its own power at 1.5 m/s | 15 µW, 4.2 days |
+| Harvestable from ambient world-light | **14,400 µW** — a 1000× margin |
+
+**So there is no recirculation machinery, at either cap.** The foils fly home on
+scavenged daylight. Nothing needs to be built for them, and the 1000× power margin
+means Tower can afford to move them *wastefully* — in spirals, shoals and patterns
+that cost energy for no reason but preference.
+
+**Lighting is a render target, not a schedule.** 78 billion independently aimed
+mirrors put any intensity on any patch of ground at any time. Dawn, dusk, moving
+cloud shadows and seasons are compositing problems, not optical ones.
+
+**Aiming resolution is human-sized.** A 1.2 cm mirror is diffraction-limited to
+4.6×10⁻⁵ rad, so its reflection lands as a **2.3 m spot** on the floor 50 km below.
+Tower can put a glint on one person that someone five metres away cannot see. The
+privacy is set by the wavelength of light, not by any protocol.
+
+**Mirrorfall.** A failed foil sinks out of the beam and falls. Terminal velocity at
+the floor is 0.48 m/s; the descent takes about two days and it lands like a
+snowflake. There is a permanent slow glitter coming down out of Tower's sky.
+
+#### Illumination is a dial, not a constant
+
+54.3 TW is Tower's *capability*, not its setting. Spread over the inner surface it
+is 320 W/m² mean, which — with roughly half the surface lit at a time and a
+sinusoidal day profile — puts **local noon at ~1,000 W/m², Earth clear-sky
+daylight**. But the diffusers are software-controlled, so brightness is genuinely
+adjustable in-world across the full range:
+
+| Setting | Mean W/m² | Feels like |
+|---|---|---|
+| Twilight | 30 | permanent overcast dusk |
+| Temperate | 200 | Earth's global mean |
+| **Full** | **320** | **Earth clear-sky noon** |
+
+This should be a control in the app, not a fixed number. It is the most
+characterful thing about a world whose sun is a machine.
+
+#### Headroom
+
+5,000× is **11 % of the étendue limit** (46,223× at 1 AU). Tower could in principle
+push the beam 9.2× further and cook itself; the diffuser network is as much a safety
+system as a lighting system. 5,000× was chosen because it is *demonstrated*
+technology — the Odeillo solar furnace reaches ~10 MW/m² on Earth today. Nothing in
+Tower's sun system requires physics that does not exist. See `decisions.md` D-003.
+
+### 1.4 Sealed hull, and the cap windows
+
+**The cylinder wall is opaque and unbroken.** No slats, no windows, no view out
+along any of the 540 km. From anywhere on the inner surface, the sky is land, haze
+and the beam — nothing else. See D-017.
+
+The only views out are at the **end caps**, and they are peculiar:
+
+- **A cap is a wall, not a floor.** Gravity there points radially outward, which is
+  *tangent* to the cap's surface, so nothing rests on it — you would slide to the
+  rim. Each cap is a **50 km vertical cliff** standing at the end of the world, and
+  its windows sit at its base, at eye level for anyone standing on the floor.
+- **The sky through them does not sweep — it turns.** A cap window looks along the
+  spin axis, which is the celestial pole. Stars rotate about a fixed centre, one
+  full turn every 7.5 minutes, with one motionless star at the middle. **A pinwheel
+  sky.** It is the only moving sky anyone in Tower ever sees.
+- The **far cap** looks into deep anti-sun space. The **sun cap** faces the sun and
+  the collector, and needs heavy glare management.
+
+**Nobody aboard can ever see Earth.** Tower holds a terminator orbit (D-004), which
+puts Earth roughly 90° off the axis — out the *side* of the cylinder, which is now
+solid hull. A habitat in Earth orbit whose inhabitants have never seen the planet
+they orbit.
+
+The caps are therefore the only places in the world with a horizon that is not
+Tower. Whatever grows up around them, it will not be ordinary.
+
+### 1.5 Atmosphere and the look of the sky
+
+Air co-rotates with the shell. Because g falls linearly to zero at the axis, the
+atmosphere is much deeper than Earth's:
+
+- Pressure at the axis, 50 km up: **5.2 kPa** — thin, but genuinely air.
+- A vertical column floor-to-axis is ~10,600 kg/m², about **one Earth atmosphere**.
+
+So the interior sky behaves like this, and the shader must reproduce it:
+
+- **Straight up** you look through ~1 atmosphere and see the far side of the world
+  100 km away — visible, but washed to pale blue like a very distant mountain range.
+- **Along the axis** you look through many atmospheres. The far end cap is not
+  visible as a wall; it dissolves into blue haze around 100–200 km out. The world
+  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. With the
+  hull sealed, it is the only thing in the sky that is not land.
+
+### 1.6 Orbit and attitude
+
+- Tower holds a high circular orbit with its **orbital plane kept normal to the
+  Earth–Sun line** — a terminator orbit. This is why one cap can face the sun
+  permanently and Tower is never eclipsed.
+- The plane precesses 360° per year to track the sun. Tower's angular momentum is
+  enormous, so this 1°/day reorientation is actively driven, continuously, forever.
+  Canon treats this as Tower's defining maintenance burden.
+- Consequence for the interior view: the star field slowly drifts across the year
+  while spinning fast every 7.5 minutes. Two nested motions.
+
+### 1.7 Coordinates
+
+The canonical surface coordinate is **(θ, z)**:
+
+- `θ` — angle around the cylinder, 0 to 2π. θ = 0 is the centre of land strip 0.
+- `z` — distance from the sun cap, 0 to 540 km.
+
+The 2D map is the direct unroll: `x = z`, `y = (θ − θ₀) · R`. No projection
+distortion — a cylinder unrolls exactly. This is a genuine advantage over mapping a
+globe and the map view should lean on it.
+
+**The map shows the surface once, bounded**, so its extent is readable. That means
+it needs an edge, and the edge is placed at **θ₀ = the centre of the last slat**.
+Land strip 0 is centred on θ = 0, so cutting there would slice a whole strip in
+half across the top and bottom margins; cutting at a slat centre leaves every land
+strip whole and splits only a band of stars, which carries no geography. Same
+reasoning as putting Earth's map edge at the antimeridian. A sightline whose short
+route crosses the edge is drawn as two segments, so the drawn path always matches
+the measured distance.
+
+---
+
+### 1.8 Sightlines: Tower has two horizons
+
+Along the axis the floor is perfectly flat and never falls away, so nothing is ever
+hidden by curvature. **Tower's horizon is haze.** Circumferentially the floor curves
+*up*, so distant land rises into the sky rather than sinking below a line. The two
+directions behave completely differently, and this is one of the most alien facts
+about living there.
+
+Rayleigh transmission from a floor-level observer:
+
+| Distance | Along the axis | Across the interior (overhead) |
+|---|---|---|
+| 26 km | 78 % | 77 % |
+| 50 km | 56 % | 70 % |
+| 100 km | **31 %** | **78 %** |
+| 270 km (far cap from midpoint) | **4 %** — a ghost | — |
+
+Two consequences:
+
+- **The axial horizon closes at 100–150 km.** From the midpoint you cannot see
+  either end cap. Everyone can see 100 km, knows there is more beyond, and cannot
+  resolve it — a different psychology of distance than Earth's 5 km cliff-edge.
+- **The far side of the world is clearer than the ground at the same distance,**
+  because a sightline across the interior rises through thin axis air while one
+  along the floor grinds through full density. It gets *better* with distance: the
+  land directly overhead, 100 km away, is the crispest far object in Tower at 78 %.
+  **Distant geography is best seen by looking up.** People would orient by what is
+  overhead; the ground is the vague part. This inverts the human default.
+
+### 1.9 Water
+
+Tower's hydrology is stranger than its geometry, and most of it follows from one
+fact and one absence.
+
+#### 1.9.1 Coriolis never pushes surface water sideways
+
+The floor's tangent plane is spanned by θ̂ and ẑ, and the spin axis is ẑ. For any
+flow along the surface:
+
+- **along the axis** — `ẑ × ẑ = 0`, no Coriolis force at all;
+- **around the circumference** — `ẑ × θ̂ = −ŝ`, the force is purely radial.
+
+The Coriolis term never has a component *inside* the surface. So **Tower's rivers
+do not curve.** No hemispheres, no Baer's law, no preferred bank erosion, no gyres,
+no cyclones. Every large-scale rotational habit of Earth's water and weather is
+absent, and a meander in Tower is pure flow dynamics with no thumb on the scale.
+
+#### 1.9.2 What it does instead: everything vertical leans
+
+The force goes into the floor rather than across it, so it appears whenever water
+moves up or down. Deflection of a free fall goes as h^1.5:
+
+| Fall | Lands (retrograde) | Lean |
+|---|---|---|
+| 100 m | 4.2 m | 2.4° |
+| 500 m | 47 m | 5.4° |
+| **1,000 m** | **133 m** | **7.6°** |
+| 2,000 m | 377 m | 10.7° |
+
+**Rain falls permanently slanted** — a raindrop at terminal velocity sits at a fixed
+**1.47° against the spin**, ~51 m of drift over a 2 km fall, never varying, never
+reversing. Rising things lean the opposite way: smoke drifts spinward, rain drifts
+anti-spinward. Every plume and every shower in Tower tilts, and they tilt against
+each other.
+
+The same applies to anything that descends, not just water — see §1.11 on what it
+does to skiing.
+
+Horizontal flow changes *weight* rather than direction (Eötvös): +0.57 % for a
+2 m/s prograde river, negligible hydrologically. It only matters at speed — an
+express train at 100 m/s gains 29 %, and anything moving retrograde at the 700 m/s
+rim speed is stationary in space and therefore weightless.
+
+#### 1.9.3 A perfect cylinder has no slope anywhere
+
+Potential depends only on radius, so **the whole floor is a single equipotential
+surface.** Water on it has no preferred direction: a perfect Tower has no rivers at
+all, forever.
+
+**Every river in Tower exists because the floor was shaped.** There are no
+tectonics and no uplift, so erosion is strictly one-way — a 3 km range wears flat in
+~30 Myr under a wet engineered climate, ~150 Myr under a dry one. Tower's rivers
+are steadily destroying its landscape and the highlands must be rebuilt to keep them
+running. This is D-004's theme again: Tower's rivers are plumbing it chose to
+install and has to keep re-installing.
+
+Two corollaries:
+
+- **No river can circumnavigate.** A closed loop has no net descent.
+- **Lakes curve upward.** Level means constant radius, so across a 30 km lake the
+  far shore rides **2.3 km above your tangent plane** — Earth would hide it 18 m
+  below the horizon. You look *up* at the opposite beach, and water appears to
+  climb away from you.
+
+#### 1.9.4 There is no passive climate at all
+
+**Tower has no latitude** — every point is equidistant from the light — and with the
+hull sealed it has no windows either. Nothing breaks the thermal uniformity. A
+uniform hull radiates at 274 K everywhere, so left alone the world settles into a
+flat calm: no gradient, no wind, no evaporation, no rain, no rivers.
+
+**So every weather system in Tower is deliberate.** Tower makes climate the only way
+left to it — by aiming light. It warms where it wants convection and shades where it
+wants condensation; the hull's uniform loss is the sink. Every cloud, every
+rainfall, every jet stream is something Tower is doing, continuously, on purpose.
+
+This is D-009 arriving at its conclusion. The mirrorlet swarm was already Tower's
+body language; now it is also its lungs and its weather. There is no autonomic
+layer underneath. **If Tower's attention ever lapses, the rain stops.**
+
+Costs, for scale against a 54.3 TW budget:
+
+| | | |
+|---|---|---|
+| Earth-like rainfall, 1,000 mm/yr | **12.2 TW** | **22.4 %** — Earth spends the same fraction |
+| Recirculating all runoff (1,792 m³/s) against 500 m | 8.8 GW | 0.016 % |
+
+#### 1.9.5 Rivers are a closed, pumped circulation
+
+With no natural cycle and no natural slope, a Tower river is a **loop**: a headwater
+reservoir, a shaped descent through the landscape, a lake at the bottom, and a lift
+back to the top. Source and sink are the same system.
+
+Two ways to close the loop, and they give completely different rivers:
+
+- **The pump outfall.** A river begins at a machine. Walk to the source of any
+  river and find a wall of water coming out of a structure — not a spring, an
+  outlet. Costs a rounding error (above).
+- **The made cloud.** Tower evaporates the lake with aimed light and condenses it
+  over the highlands, so rain falls exactly where the river should start. No
+  machinery: the swarm is the pump. Costs the 22.4 % above, and buys real weather
+  with it.
+
+Canon uses **both** — made weather for the world's general hydrology, pumps for
+watercourses Tower wants under precise control.
+
+Two consequences of the sealed hull:
+
+- **A river can circle the world.** The surface is one unbroken sheet, so a channel
+  can run the full 314 km circumference and close on itself — a lazy river returning
+  to its own source. Gravity cannot drive a closed loop, so it is pumped.
+- **The water inventory is conserved and exactly known.** Nothing evaporates to
+  space. Tower knows how much water it has, to the litre, forever.
+
+Lifting is also slightly cheaper than on Earth, since gravity falls off with height:
+1 % cheaper at 1 km, 5 % at 5 km, 25 % at 25 km.
+
+### 1.10 Slat orientation — RESOLVED: none
+
+Axial strips versus rings was weighed at length (D-016) and answered by removing
+slats entirely (D-017). The comparison is kept in D-016 because it established what
+the choice *cost*, and because the reasoning — that slat layout sets the shape of
+Tower's countries, its climate map and its astronomy all at once — is what made
+"none" a real option rather than an absence.
+
+### 1.11 The world as built
+
+Authored geography lives in **`content/geography.json`** — the source of truth — and
+`npm run terrain` renders it. Names are provisional. Tower is laid out in **belts
+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 |
+| 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 |
+| 248–402 km | **The Deep** | 154 × 32 km sea, long down the axis and narrow around |
+| 270 km | **The Capital / The Spindle** | axial midpoint, on The Deep's antispinward shore |
+| 496–540 km | **The Deepward Sea** | ring ocean at the foot of the far cap |
+
+#### Why the desert rings
+
+**On a cylinder you cannot go around a band.** Every desert on Earth can be
+circumnavigated; a ringed one cannot. It is an absolute barrier across the world's
+waist, and with the three ridges ringing too it becomes three concentric walls.
+
+#### The drainage, and why it keeps the chokepoint
+
+Four rivers come off the range — Westwall Water, Sisters Water, Eastwall Water and
+The Long Water. On Earth most drainages **do not** cut through a rising ridge; they
+are defeated and diverted, and only an *antecedent* river with enough power keeps
+its course. So three of the four are turned by First Ridge, run **along its front**
+gathering each other, and leave with the fourth through the single gap.
+
+- **The Narrows carries the entire world's runoff** — ~392 m³/s off a 9,800 km²
+  range, lifted 1,780 m for 6.9 GW (0.013% of budget).
+- The ridge-front reaches are **the only circumferential water in Tower**, and
+  weigh fractionally more or less for it (§1.9.2). Everything else runs axially,
+  where Coriolis is exactly zero.
+- Whoever holds The Narrows holds the axis of the world. The Capital sits behind it.
+
+#### The Deep is oriented for the view
+
+Long down the axis and narrow around it, so you cross it the **short** way — around
+θ, where the floor curves up. From the Capital the far shore is 32 km away and
+stands **2.6 km above eye level**. Crossing it lengthwise instead would be dead
+flat. The orientation is the whole point.
+
+It is also the world's flywheel: 19× the diurnal heat storage of bare ground, and
+5.9 days at full power to move its temperature one kelvin. Tower still supplies
+every joule (§1.9.4), but the world now coasts for about a week rather than
+stopping the moment attention lapses.
+
+#### The cap seas are not designed
+
+Gravity on an end cap points radially outward — *tangent* to the cap. Nothing rests
+there; water runs down to the rim and pools. **Both ring oceans exist whether anyone
+planned them or not**, each with a 314 km shoreline and a 50 km cliff standing
+behind it. The only windows in the world are at their feet (§1.4).
+
+#### Skiing, because it is a different sport here
+
+Level motion feels no Coriolis (§1.9.1) but a descending skier has radial velocity,
+and that does deflect:
+
+- **Down the axis:** a steady sideways push, always antispinward — 1.5% of g at
+  15 m/s on a 20° slope, 6.4% at 35 m/s on 40°.
+- **Around the cylinder:** you get heavier or lighter. 8.6% at 30 m/s, 11.4% at 40.
+
+So a **retrograde run gets lighter the faster you go**, which is self-reinforcing,
+while a prograde run grips harder as it accelerates. Every slope in Tower has a
+handedness. No Earth mountain does. Settlements sit at the tarns — Sisters Station,
+Hollow Station — around 2,100 m, with the runs above them.
+
+#### Open questions
+
+- **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.
+
+## 2. Application architecture
+
+### 2.1 Stack
+
+- **Vite** + **TypeScript** (strict), no framework — the app is three canvases and
+  a control panel, and a framework would not earn its weight.
+- **Three.js** for the exterior and interior 3D views.
+- **Canvas 2D** for the map view.
+- **GitLab CI → GitLab Pages**, publishing `dist/` as `public/`.
+
+### 2.2 Module layout
+
+```
+src/
+  canon/
+    constants.ts    single source of truth for every number in §1
+    geometry.ts     (θ,z) <-> 3D, slat layout, map <-> surface
+    lighting.ts     swarm aiming solution -> sun direction + intensity at
+                    (θ,z,t), scaled by the global brightness setting (§1.3)
+  views/
+    palette.ts      one colour per place kind, shared by map, list and editor
+  world/
+    schema.ts       world.json types + validation
+    store.ts        places CRUD, localStorage drafts, JSON import/export
+    instruments.ts  sightline, viewshed, lighting probe, travel (§3.3)
+  state/
+    store.ts        selected point, time-of-day, spin phase, view mode, writer flag
+  views/
+    exterior.ts     Three.js: hull, slats, collector, beam, orbit, point picker
+    map.ts          Canvas 2D: unrolled surface, slats, terminator, point picker
+    interior.ts     Three.js: shell geometry, beam, diffusers, ground
+    sky.ts          the atmospheric shader of §1.5
+  ui/
+    controls.ts     time-of-day slider, brightness slider, spin toggle, view switcher
+    editor.ts       writer-mode place editing panel
+    readout.ts      live figures for the picked point
+  main.ts
+```
+
+`canon/constants.ts` is the spine. Every derived figure — period, rim speed, land
+area, slat width — is *computed* from radius, length, slat count and target land
+area, never hard-coded. Changing the radius changes the whole app coherently.
+
+### 2.3 State
+
+One small observable store. Views subscribe; nothing owns anything else.
+
+```ts
+type TowerState = {
+  point: { theta: number; z: number } | null  // the picked location
+  tod: number        // 0..1, position in the diffuser day cycle
+  brightness: number // W/m^2 mean, 30..320 -- the diffuser output setting
+  spinPhase: number  // 0..2pi, rotation for star motion
+  spinning: boolean
+  view: 'exterior' | 'map' | 'interior'
+  writer: boolean    // ?writer=1 -- a UI mode, not a security boundary
+  probe: { from: SurfacePoint; to: SurfacePoint } | null
+}
+```
+
+The exterior and map pickers both write `point`; the interior view reads it. That
+is the entire interaction loop.
+
+### 2.4 The interior view
+
+Per the chosen scope: **real geometry, simple ground.**
+
+Real: the shell curving up on both sides, the far land visible overhead through
+haze, the axis beam and its diffusers, correct gravity-gradient and atmospheric
+cues. **No stars** — the hull is sealed (§1.4). The sky is land and light.
+
+Simple: the ground is procedural terrain and texture that varies plausibly with
+(θ, z), not modelled scenery. No buildings, no vegetation meshes. Detailed
+procedural scenery is a later phase, deliberately deferred.
+
+The sky is a shader over an inward-facing shell, fed by: view direction, the
+atmospheric column integral along that ray, and the diffuser lighting solution. It
+is the piece that carries the whole illusion and deserves the most care.
+
+With the stars gone, the interior view has to sell the spin some other way, and
+§1.2 says how: **the sky is still and the water is not.** Slanted rain and a leaning
+waterfall now do the work the wheeling stars used to.
+
+---
+
+## 3. Writer mode and the world file
+
+The app is an instrument for writing the novel as much as a thing to look at. It
+must answer questions like *can the far face be seen from the midpoint?*, *where
+does a central embassy go?*, *can a lake overhang?* — and let their answers be
+placed, named and kept.
+
+### 3.1 Two modes over one data file
+
+| | Reader (default) | Writer (`?writer=1`) |
+|---|---|---|
+| World content | committed `world.json`, read-only | editable, drafts in `localStorage` |
+| Instruments (§3.3) | sightline probe only | all |
+| Output | permalinks to a point or place | **Export JSON** for the author to commit |
+| Panel | read-only detail card | full editor, canon controls, import/export |
+
+There is no backend — GitLab Pages is static. The loop is: the author edits in the
+browser, exports `world.json`, commits it, and the published site serves it. Simple,
+versioned in git alongside the prose, and diffable.
+
+**Writer mode is a UI mode, not a security boundary.** Anyone may set `?writer=1`;
+they can only edit their own local copy, which persists nowhere and reaches nobody.
+This is fine, and arguably good — a reader who finds it gets a sandbox. It must not
+be mistaken for access control.
+
+### 3.2 The world file
+
+```jsonc
+{
+  "version": 1,
+  "places": [{
+    "id": "hallow-crossing",          // permalink target; see below
+    "name": "Hallow Crossing",
+    "kind": "city|embassy|lake|region|landmark|note",
+    "geometry": { "type": "point",   "theta": 1.9635, "z": 270000 },
+    //        or { "type": "polygon", "points": [[theta,z], ...] }   for lakes, regions
+    "blurb": "public description, shown to readers",
+    "notes": "the author's working notes — NEVER shipped",
+    "public": true                    // false = not ready to exist publicly
+  }]
+}
+```
+
+**Two text fields, deliberately separate.** `blurb` is written for readers and
+ships; `notes` is the author's working thinking and never leaves the machine except
+in a backup. One field with a flag would have made every note a publication
+accident waiting to happen.
+
+**Ids follow the name until pinned.** An id is a permalink target and must be
+stable once published, but during world-building names churn constantly and a file
+full of `new-city-7` is unreadable. So the id tracks the name while it still
+matches it, and editing the id field freezes it for good. The editor shows the id
+for exactly this reason.
+
+Coordinates are canon (θ radians, z metres, §1.7) — never map pixels, so the file
+survives any change to how the map is drawn.
+
+**The `public` flag is a privacy trap worth naming.** Working notes ("embassy here?
+check sightline to the cap") must not ship to readers. But a `world.json` committed
+to a public repository is public regardless of any flag in it. So export writes two
+files: **`world.json`** (public entries only, committed) and
+**`world.private.json`** (everything, gitignored by default). Whether private notes
+should nonetheless be version-controlled — in a private repo, or a second one — is
+the author's call and is deliberately unresolved.
+
+### 3.3 Instruments
+
+The measuring tools are the real substance of writer mode. Each answers a question
+the novel actually raised:
+
+- **Sightline probe.** Pick two points; get surface distance, straight-line
+  distance, optical depth, transmission, and a plain verdict (*crisp / hazy / ghost
+  / invisible*). This is the tool that answered §1.8. Available to readers too — it
+  is the single most illustrative thing in the app.
+- **Viewshed overlay.** From a selected point, shade the whole map by transmission.
+  It renders the axis-versus-overhead asymmetry as one picture, which is worth more
+  than the table in §1.8.
+- **Lighting probe.** At a point, illumination across the day cycle at the current
+  brightness setting (§1.3) — for writing a scene at a specific hour.
+- **Travel.** Surface distance and journey time between points at chosen speeds,
+  including whether the route crosses a slat.
+- **Sky-at-a-time.** Star field, beam position and Earth transits for a given point
+  and moment, so a described sky is a correct sky.
+
+### 3.4 What readers get
+
+The public link is the finished map: embassies, towns, lakes, named regions — the
+permanent things, behaving like any map application. Plus the exterior view, the
+interior view from any point, and permalinks so a reader can be handed a place and
+a moment. Reading the world, not editing it.
+
+## 4. Phases
+
+1. **Design** — this document and `decisions.md`. ← current
+2. **Scaffold** — Vite/TS/Three, CI to Pages, `canon/` implemented and unit-tested.
+3. **Exterior** — hull, collector, beam, slats, orbit; hull point picking.
+4. **Map** — unrolled surface, slat rendering, picking, sync with exterior.
+5. **Interior** — shell geometry, sky shader, beam, stars, time-of-day.
+6. **Writer mode** — ✅ *built ahead of 3–5, at the author's request, because the
+   world needs developing before there is anything to render.* Map view, place
+   editing for all thirteen kinds, points/polylines/polygons, localStorage
+   autosave, backup and static exports, import, and the sightline probe.
+7. **Content pipeline and reader mode** — ✅ `content/geography.json` +
+   `npm run terrain` (D-019); the rendered terrain drawn under the map; reader and
+   writer modes (D-011); permalinks (D-010).
+8. **Polish** — transitions, mobile, performance.
+
+Phases 3 (exterior) and 5 (interior) are still ahead; phase 2's `canon/` landed
+with phase 6 since the map could not be drawn without it.
+
+**Shareable permalinks are implemented** (phase 7) — the app has
+readers, and "stand here and look at this" is the whole reason it exists as a
+companion piece. Named locations from the novel should be markable on the map for
+the same reason.
+
+Later, explicitly out of scope for now: procedural scenery, settlements, weather
+systems.