fuzzyworld

tower / interior-flight / files / README.md

Branch interior-flight · default branch · all branches

README.md

Blame: the commit that last changed each line · file · history

cf4f8772 2026-09-07 Pavel Safronov1# Tower
2
3**Live map — <https://pavelsafronov.gitlab.io/tower/>** · [Source](https://gitlab.com/PavelSafronov/tower)
4
5Companion piece to a novel set inside **Tower** — a fictional smart cylinder orbiting Earth,
6spun for 1 g, with an inner surface the size of Wisconsin, lit by 54 TW of sunlight
769726ea 2026-09-08 Pavel Safronov7piped down its axis and scattered by mirrors that make its days and nights. Its wall
8is a kilometre of sealed hull: no windows along all 540 km, and no view of the Earth
9c891d2e 2026-09-08 Pavel Safronov9it orbits. Both end caps are windows, though — 100 km of glass each, and the only
10places in the world with a horizon that is not Tower. Nobody aboard can see the sun
11either: the 244 km collector sits between Tower and it, 79 times its diameter, so
49b0d64d 2026-09-08 Pavel Safronov12the sun cap looks out at a black disc and the stars around it — black except at its
13edge, where the structure holding it flat catches the sun side-on and traces the
14whole 244 km in a thin sun-coloured ring.
cf4f8772 2026-09-07 Pavel Safronov15
17ccee99 2026-09-08 Pavel Safronov16**Tower's clock is painted on it**, because it is the only thing in the sky that
17does not turn. Two lit lines from the hub: one fixed normal to the plane of the
18solar system, one sweeping a full turn over a day. You read the *angle between
19them* — from inside, the whole face wheels once every 7 min 29 s along with the
20stars, so a hand against a dial would be useless, but an angle survives it.
21
28f9a6bc 2026-09-07 Pavel Safronov22Explore Tower from outside, pick a spot on the inner surface, and stand there — with
23the ground curving up on both sides and the far side of the world hanging 100 km
24overhead, clearer than the ground at your feet. Turn its sun up to Earth clear-sky
769726ea 2026-09-08 Pavel Safronov25noon, down to permanent dusk, or shut the beam off entirely and watch the sky come
26out: through the far cap the stars wheel once every 7 min 29 s about a single
27motionless star on the axis. Nothing inside Tower rotates, because you and the
28ground and the air all turn together — so that window is the only place the spin is
29visible at all.
cf4f8772 2026-09-07 Pavel Safronov30
31Tower flies all 78 billion mirrors of its own sunlight, individually, because
32nothing passive would stay in the beam past mid-afternoon. It is a person.
33
34## Status
35
28f9a6bc 2026-09-07 Pavel Safronov36**All three views are built** — map, exterior and interior — over an authored
37geography and a working writer mode.
cf4f8772 2026-09-07 Pavel Safronov38
28f9a6bc 2026-09-07 Pavel Safronov39Built: the unrolled surface map, Tower from outside, standing inside it, place
40editing for thirteen kinds (points, lines, polygons and bands), localStorage
41autosave, backup and static exports, import, permalinks, and the sightline probe.
cf4f8772 2026-09-07 Pavel Safronov42
43- [design.md](design.md) — canon, physics and application architecture
44- [decisions.md](decisions.md) — choices made and why
45
46Both are series-bible material: coherence outranks spectacle, and the prose is
47downstream of them.
48
fc11b064 2026-09-08 Pavel Safronov49On a phone the sidebar is a **drawer** rather than a column: below 720 px the world
50gets the whole screen and the panel slides in over it, one tap away and one tap
51gone. Picking a place closes it, because in every view selecting a place is how you
52travel there — see [decisions.md](decisions.md) D-043.
53
6364748a 2026-09-08 Pavel Safronov54## Screenshots
55
56[![All ten views on one sheet](docs/screenshots/overview.png)](docs/screenshots/overview.png)
57
58`npm run screenshots` drives the real application in a headless browser — clicking
59the view buttons, picking places out of the list, dragging to orbit and to look up —
60and writes [`docs/screenshots/`](docs/screenshots/). Taking them *through the UI* is
61the point: a shot taken this way fails when the UI fails, so a black canvas ends up
62in the picture instead of hiding behind a passing unit test.
63
18958d3d 2026-09-08 Pavel Safronov64The whole tour takes about **35 seconds**. It used to take six and a half minutes,
65nearly all of it spent rendering Tower's haze integral on the CPU — the default
66headless browser has no GPU. It now asks for the full Chrome build, which reaches
67the real one, and photographs four shots at a time, since a shot is mostly spent
68waiting. The cost is that the committed images are tied to the GPU that drew them:
69another machine will show a large one-off divergence rather than a regression.
70
6364748a 2026-09-08 Pavel Safronov71| Desktop — 1440×900 | |
72|---|---|
73| [map](docs/screenshots/desktop-map.png) | the unrolled inner surface, with a place selected |
74| [map-sightline](docs/screenshots/desktop-map-sightline.png) | shift-click two points to measure a sightline through the air |
4699e6cb 2026-09-08 Pavel Safronov75| [exterior](docs/screenshots/desktop-exterior.png) | x-ray hull — the inner surface projected out, with the caps and the collector |
76| [exterior-true](docs/screenshots/desktop-exterior-true.png) | true hull — bare skin, and the land visible through the far cap window |
6364748a 2026-09-08 Pavel Safronov77| [interior](docs/screenshots/desktop-interior.png) | standing at The Narrows, the ground curving up on both sides |
78| [interior-overhead](docs/screenshots/desktop-interior-overhead.png) | looking up: the far side 100 km overhead is clearer than ground 100 km away |
17ccee99 2026-09-08 Pavel Safronov79| [interior-night](docs/screenshots/desktop-interior-night.png) | the far cap window at night: stars, and the beam banked to a thousandth |
80| [interior-suncap](docs/screenshots/desktop-interior-suncap.png) | the sun cap window: the collector, its sunlit rim, and Tower's clock on its face |
1fcef8e6 2026-09-08 Pavel Safronov81| [interior-flying](docs/screenshots/desktop-interior-flying.png) | flying — 4 km up, off the ground, with the world closing overhead |
2bb3e0de 2026-09-08 Pavel Safronov82| [interior-dawn](docs/screenshots/desktop-interior-dawn.png) | dawn — a lit sky over dark ground, the light still 27 km away |
6364748a 2026-09-08 Pavel Safronov83| [reader](docs/screenshots/desktop-reader.png) | `?reader=1` — terrain, places, a detail card and the probe, no editing |
84
85| Mobile — 390×844 | |
86|---|---|
87| [map](docs/screenshots/mobile-map.png) | the world with the whole screen, the panel one tap away |
88| [exterior](docs/screenshots/mobile-exterior.png) | the spin readout dropped clear of the panel button |
89| [interior](docs/screenshots/mobile-interior.png) | the standing readout at a size that fits 390 px |
90| [panel](docs/screenshots/mobile-panel.png) | the sidebar as a drawer, opened over the world |
91
92### Looking before committing
93
94[`overview.png`](docs/screenshots/overview.png) is a contact sheet of all ten. It
95exists to be **looked at, once, before committing** — one image that answers "is the
96application still there?". Regenerate, glance at it, then commit.
97
98`--check` puts a number on the same question. Every shot is compared against the
99committed one and the run fails if any of them moved too far:
100
101```
102npm run screenshots -- --check # fail past 3% divergence on any shot
103npm run screenshots -- --threshold 8 # report only, looser bar
104npm run screenshots -- --url http://localhost:5173 # use a dev server already up
105```
106
18958d3d 2026-09-08 Pavel Safronov107The threshold is measured at both ends, not guessed. The 2D shots come out
108**byte-identical** between runs — nothing in Tower is stochastic, the exterior spin
109is switched off, and every shot gets a fresh browser context so no camera leaks into
110the next. **The WebGL shots do not**, because the tour draws on the real GPU and
111Metal does not schedule floating-point work identically twice; they drift by 0.1% to
112**0.9%**. Against that, hiding both 3D canvases scored **7% to 31%**, and a view
113going entirely black scores 12% to 39%.
6364748a 2026-09-08 Pavel Safronov114
115**3% is three times the worst noise and less than half the mildest real failure.**
116It is a smoke alarm, not a verdict: when it fires, go and look at the sheet. Note
117that a view which never worked scores 0% when it breaks further — this catches
118regressions, not things that were already broken.
119
cf4f8772 2026-09-07 Pavel Safronov120## Development
121
122```
123npm install
124npm run dev # http://localhost:5173
28f9a6bc 2026-09-07 Pavel Safronov125npm test # 94 tests; canon figures are pinned to design.md
cf4f8772 2026-09-07 Pavel Safronov126npm run build
127npm run terrain # regenerate the maps from content/geography.json
6364748a 2026-09-08 Pavel Safronov128npm run screenshots # drive the app and rephotograph docs/screenshots/
cf4f8772 2026-09-07 Pavel Safronov129```
130
131Vite + TypeScript, deployed to GitLab Pages by `.gitlab-ci.yml` on every push to the
28f9a6bc 2026-09-07 Pavel Safronov132default branch. Three.js is loaded only when a 3D view is opened, so a reader who
133stays on the map never downloads it.
cf4f8772 2026-09-07 Pavel Safronov134
4a8aed84 2026-09-07 Pavel Safronov135Assets are referenced **relatively**, so the same build works whether Pages serves
136it from `https://pavelsafronov.gitlab.io/tower/` or from a generated unique domain
137at a root. Set a `BASE` CI variable to force an absolute base if that is ever
138needed.
cf4f8772 2026-09-07 Pavel Safronov139
140## Geography
141
142[`content/geography.json`](content/geography.json) is the authored source of truth
28f9a6bc 2026-09-07 Pavel Safronov143for terrain — coordinates in canon (θ, z), elevations in metres relative to the base
144shell, negative for anything cut below it. `render.minHeightM` / `render.maxHeightM`
145declare the range the height raster is stretched across; the generator prints the
146range the terrain actually occupies and warns if it has outgrown the declaration. `npm run terrain` renders it, with no dependencies beyond Node:
cf4f8772 2026-09-07 Pavel Safronov147
148| | |
149|---|---|
28f9a6bc 2026-09-07 Pavel Safronov150| `static/terrain-height.png` | 16-bit greyscale, across the declared `minHeightM`–`maxHeightM` |
62458672 2026-09-07 Pavel Safronov151| `static/terrain-color.png` | the whole world, 264 m/px |
152| `static/terrain-corridor.png` | the developed corridor, 122 m/px |
153| `static/world.json` | seed places for the app |
cf4f8772 2026-09-07 Pavel Safronov154
155It reports each stage as it goes — every relief op, colourisation, and PNG encode —
156and takes about 6 seconds. Flags:
157
158| | |
159|---|---|
160| `--probe <theta>` | print elevation jumps along a row, for checking river gradients and finding discontinuities |
161| `--quiet` | one line per stage instead of one per feature |
162| `--force` | let the seed overwrite places added in the app |
163
164## Reading and writing
165
166The map opens in **writer mode**: tools, editor, canon controls and import/export.
167Add **`?reader=1`** to preview what a reader gets — the terrain, the places, a
168detail card and the sightline probe, with no editing. Writer is the default while
169the world is still being built; that is a one-line change when it is ready to
170publish. Either way it is a UI mode, not a security boundary — with no backend an
171editor can only change their own copy (decisions.md D-011).
172
173Any selection updates the URL, so a link is always shareable:
174
175| | |
176|---|---|
177| `#place=the-narrows` | select and centre a place |
178| `#at=0.03,170000` | drop a sightline endpoint at (θ, z) |
179
5f38efd0 2026-09-07 Pavel Safronov180## Views
181
a3f01bd7 2026-09-08 Pavel Safronov182**Map** is the unrolled inner surface. **Exterior** is Tower from outside — a
183kilometre of hull, the checkered sun cap, the glazed far cap, the 244 km collector
184flying ahead and the beam converging into the sun cap. Each piece is captioned.
185
186The hull reads two ways and the **Hull** switch picks one. **True** is the outer
187skin: bare structure, and the far cap a real window — look in through it and the
188land is actually there, lit and receding 540 km down the tube. It is the only line
189of sight into Tower that is not a diagram. **X-ray** wraps the terrain onto the
190outside instead, which is a diagram of what is inside rather than a view of it, and
191useful precisely because the wall is opaque. The active view is remembered and travels in the permalink, so
8b6287ba 2026-09-07 Pavel Safronov192`#view=exterior&place=the-narrows` opens where you left off. Drag to orbit, scroll to zoom, click the hull to drop a point; the spin runs at
5f38efd0 2026-09-07 Pavel Safronov1931x to 1200x real time, and a point picked off the turning hull still comes back in
194canon coordinates.
195
28f9a6bc 2026-09-07 Pavel Safronov196**Interior** is standing inside. The shell is the real cylinder, displaced by the
197height raster and painted with the colour one, so the ground curves up on both sides
198and closes overhead. Haze is integrated through Tower's rotational air column for
199every pixel, which reproduces the strangest fact about the place: the land 100 km
200straight up is **78%** clear while the ground 100 km away is **31%** — distant
201geography here is best seen by looking up.
202
203Drag to look, scroll to zoom in like a pair of binoculars, and select a place — in
204the list or by its caption — to **walk there**. Most of the world is beyond the haze
205horizon from any given spot, so travelling is more use than turning.
206
769726ea 2026-09-08 Pavel Safronov207**The far cap is a window** — 100 km of it, looking into deep anti-sun space. By day
208it is simply a disc of blue, because 540 km of air in front of it scatters more
209light than anything behind it sends; the stars are there at noon and unseeable,
210which is a fact about eyes rather than about glass. Turn the light down and they
211come out. The sun cap stays a checkered plate: it faces the sun, and needs glare
212management rather than a view.
213
da6bcb0e 2026-09-08 Pavel Safronov214The one part of the far cap that is not glass is a **3.2 km mirror** on the axis.
215A quarter of the beam survives all 540 km and arrives collimated, so it lands as
2161.7 MW/m² — 1,200 suns — on a spot a thousandth of the cap's area; the mirror sends
217it back up the column for a second pass, which lifts the beam's utilisation from 76%
218to 94%. You can see it: the rod of light visibly terminates on a bright disc, and
219the disc banks and goes out with the beam.
220
769726ea 2026-09-08 Pavel Safronov221The checker survives on the far cap as the window's **mullions** — the same polar
222tessellation at the same pitch, so it is still the only object in the world with a
223known cell size and still the only rule anyone has for judging distance. It is just
224read against sky now instead of against a plate.
225
2bb3e0de 2026-09-08 Pavel Safronov226**Dawn is a line, not a fade.** The beam glows by itself, so when its power comes up
227the sky brightens everywhere at once — but the ground is lit by the diffusers, and
228they hand the light down the axis. So a Tower morning is a **fully lit sky over dark
229ground**, with daylight arriving as a front running deepward from the sun cap at
230225 m/s, crossing all 540 km in 40 minutes. Tower has no latitude; the cycle gives it
231longitude. Scrub the **Time** slider to 06:17 and stand at The Capital to watch it
232come, or tick **Run the clock** and let it.
233
234The light has five settings when the clock is not driving it. **Dark** shuts the 54 TW beam down entirely and is the
769726ea 2026-09-08 Pavel Safronov235only one that is genuinely black — there is no airlight, no lit ground and no rod
236overhead, and the only thing left in the world is the sky. **Night** banks the beam
237to a thousandth instead of killing it, which is what night here has to be: the beam
238glows by itself, so a lit beam lights the world whatever the mirrors are doing, and
239aiming them away would leave the floor at about 100 W/m² — half of noon.
240
567fe8ad 2026-09-08 Pavel Safronov241The eye-height slider runs from 1.7 m to 25 km with the local gravity beside it, and
41516065 2026-09-08 Pavel Safronov242**Fly** lets go of the ground altogether — an autopilot on a permanent helix, 18°
243off the axis, crossing the world lengthwise while it rolls around it and reflecting
244off the caps rather than stopping. **Your input turns your head, not the aircraft**,
245so you can watch whatever you like while it flies. The slider becomes the altitude
246it holds, terrain-following underneath: climbing at once, sinking slowly. Gravity
247falls linearly to zero at the axis, so height makes flight *easier* — 0.91 g at
2484.3 km up, and the beam widens overhead as you close on it.
1fcef8e6 2026-09-08 Pavel Safronov249
250Nothing *inside* rotates, because you, the ground and the air all turn together — so
251for 539 of the 540 km there is no evidence Tower turns at all. The exception is the
252window: the sky out there does not co-rotate, and wheels once every 7 min 29 s about
253the one motionless star on the axis. Rain used to stand in for that, leaning a
254permanent 1.5°, and is no longer drawn — the lean is real and still computed, but
2551.5° was never a picture of anything. It is by a wide margin the plainest sign of the spin anywhere in the view, and
769726ea 2026-09-08 Pavel Safronov256it is only available at night, at the deepward end, looking the one direction that
257has ever had anything in it but Tower.
28f9a6bc 2026-09-07 Pavel Safronov258
cf4f8772 2026-09-07 Pavel Safronov259## Working in the map
260
261Pick a tool, click to place; **Lake**, **Forest**, **Desert** and **Region** trace a
262polygon and **River** and **Canyon** trace an open line (Enter finishes, Esc
263cancels). Drag markers to move them, **shift-click two points** to
264measure a sightline, **f** to fit the view.
265
cf37225c 2026-09-07 Pavel Safronov266Canon is **0 star slats** — the hull is sealed, the inner surface is one unbroken
267169,646 km², and the only views out are at the end caps. The runtime control for
268this is disabled and hidden (decisions.md D-033): the terrain raster is baked at 0
269slats, so any other count draws bands over land that has none.
cf4f8772 2026-09-07 Pavel Safronov270
271Everything autosaves to this browser's localStorage. Two exports:
272
273| | |
274|---|---|
275| **Export backup** | everything, private notes included. Keep out of the public repo — `.gitignore` already excludes `tower-backup-*.json`. |
62458672 2026-09-07 Pavel Safronov276| **Export static map content** | public places only, `notes` stripped. This is `static/world.json`, the world readers see, and the seed for a browser with no local draft. |