tower / files / world-generation.md
world-generation.md
376f2ce6 2026-09-16 Pavel Safronov | 1 | # Tower — Live World Generation |
| 2 | ||
00ceeedb 2026-09-27 Pavel Safronov | 3 | **Status: IN PROGRESS.** Accepted by the author (D-062). **Phases 1–6 are built** |
| 4 | (D-063 to D-067, D-069, D-071), with a scenic tour between 4 and 5 (§19, D-068) and the | |
| 5 | Capital given an extent (D-070); phases 7–8 are not. This document replaces the terrain pipeline of D-019 and reverses | |
376f2ce6 2026-09-16 Pavel Safronov | 6 | D-006; see §11 for every existing decision it touches, and §15 for where Phase 1 |
| 7 | departed from the plan and why. | |
| 8 | ||
| 9 | --- | |
| 10 | ||
| 11 | ## 0. Summary | |
| 12 | ||
| 13 | Today the world is two images. `npm run terrain` reads `content/geography.json`, | |
| 14 | bakes a 2048 × 1192 height raster and a colour raster (264 m per pixel), and the | |
| 15 | interior view displaces one cylinder mesh by the first and paints it with the | |
| 16 | second. Every lake, forest and desert is a colour in a PNG. Nothing is smaller than | |
| 17 | a pixel, and nothing moves. | |
| 18 | ||
| 19 | This proposal replaces that with **features that generate themselves, live, in the | |
| 20 | browser**: | |
| 21 | ||
| 22 | | Feature | What gets generated | | |
| 23 | |---|---| | |
| 24 | | **Mountains** | named peaks at their authored heights, ridges and saddles between them, valleys cut by simulated fluvial erosion, snow and bare rock by altitude and slope | | |
| 25 | | **Lakes** | a basin, a real water surface at a constant level, wind waves sized by fetch | | |
| 26 | | **Rivers** | a valley and channel descending monotonically from source to outlet, a water surface that follows the descent, and flow animated downstream | | |
| 27 | | **Cap seas** | the two ring oceans, with ocean waves and sailing ships on deterministic routes, both driven by Tower's published weather plan | | |
| 28 | | **Desert** | dune fields, rocky hills, terraced plateaus and mesas, and the marks of water that was never there: dry washes, salt pans, abandoned shorelines | | |
| 29 | | **Forest** | individual trees at real sizes, placed by a density field with planned stands and open meadows, and paths through it; a **tall forest** of 110 m giants that stands out as a landmark | | |
| 30 | | **Settlements** | the capital, the stations, and generated hamlets, villages and towns filling out the world: a road skeleton, blocks, lots and blocky extruded buildings | | |
| 31 | | **Weather plan** | Tower's published schedule of wind and storms per body of water, which it can revise at any moment | | |
| 32 | | **Roads & paths** | authored or auto-routed along the terrain, grading the ground they cross, around the capital and through the forest | | |
| 33 | ||
| 34 | There is **no build step**. The world file is the only input. The browser compiles it | |
| 35 | in web workers when the page loads and again whenever the author edits a feature, | |
| 36 | behind a status dialog that says what it is making (§10.1), and streams detail in | |
| 37 | around the observer as they fly through the interior view. | |
| 38 | ||
| 39 | ### 0.1 The premise every generator follows | |
| 40 | ||
| 41 | **Nothing inside Tower is a remnant.** Tower is in full control of the inside, and every | |
| 42 | landform, forest, shoreline and wind is there because Tower chose to put it there — | |
| 43 | often because it saw the form on Earth, or on other planets, and judged it appropriate. | |
| 44 | So a dry wash in the Dry Reach is not evidence that water once ran there; it is a | |
| 45 | quotation. The generators still produce physically *plausible* forms — a wash still | |
| 46 | branches the way drainage branches — because Tower copies well. But nothing has to be | |
| 47 | consistent with a history, because there is none. (Author, 2026-09-16.) | |
| 48 | ||
| 49 | --- | |
| 50 | ||
| 51 | ## 1. Goals, non-goals, constraints | |
| 52 | ||
| 53 | ### 1.1 Goals | |
| 54 | ||
| 55 | 1. **Each concept is a first-class, authorable thing** — placed on the map, given | |
| 56 | parameters, and regenerated when either changes. | |
| 57 | 2. **Scale runs from the world to your feet.** 540 × 314 km is readable from 25 km | |
| 58 | up; a tree, a house and a path are readable standing at 1.7 m. | |
| 59 | 3. **Live.** Changing a feature shows up in the map and the interior within seconds, | |
| 60 | without a terminal. | |
| 61 | 4. **Deterministic.** The same world file produces the same world on every machine, | |
| 62 | every time. A reader's link to "stand here" shows the author's forest, not a | |
| 63 | reroll of it. | |
| 64 | 5. **Canon holds.** Everything in `design.md` §1 still applies: rivers descend | |
| 65 | because the floor was shaped (§1.9.3), level water is a surface of constant radius | |
| 66 | (§1.9.3), falling things lean antispinward (§1.9.2), haze is integrated (§1.8). | |
| 67 | ||
| 68 | ### 1.2 Non-goals (for this proposal) | |
| 69 | ||
| 70 | - Simulating the world over time (erosion as a process you watch, growth, traffic). | |
| 71 | Erosion is a *generation* technique here, run once per edit. | |
| 72 | - Clouds and rain are still not drawn (D-049 stands). **Wind and waves are** in | |
| 73 | scope, as Tower's published weather plan (§4.9). | |
| 74 | - Interiors of buildings, people, vehicles other than ships. | |
| 75 | - Photorealism. The target is "reads correctly and is honest about scale". | |
| 76 | ||
| 77 | ### 1.3 Constraints carried over | |
| 78 | ||
| 79 | - **Static hosting.** GitLab Pages, no backend (§3.1). All generation is client-side. | |
| 80 | - **Phones.** The app works below 720 px (D-043). Generation and rendering need a | |
| 81 | reduced tier, not a separate app (§8.4). | |
| 82 | - **Vanilla TypeScript, Three.js** (D-005). No framework. | |
| 83 | - **Scene units are kilometres** in both 3D views, so shaders read in canon numbers. | |
| 84 | ||
| 85 | --- | |
| 86 | ||
| 87 | ## 2. Terms | |
| 88 | ||
| 89 | | Term | Meaning | | |
| 90 | |---|---| | |
| 91 | | **(θ, z)** | canon surface coordinate: θ radians around, z metres from the sun cap (§1.7) | | |
| 92 | | **arc** | θ × R, metres around the circumference. (z, arc) is the unrolled plane, which is exact | | |
| 93 | | **elevation** | metres above the datum cylinder, i.e. *toward the axis* | | |
| 94 | | **feature** | one authored thing in the world file: a mountain range, a lake, a forest… | | |
| 95 | | **world compile** | the whole-world pass that runs on load and on each edit (§5.2) | | |
| 96 | | **tile** | one square-ish patch of the unrolled surface at one level of detail (§6.1) | | |
| 97 | | **field** | a function of (θ, z): elevation, surface material, tree density… | | |
| 98 | | **scatter** | discrete objects placed by a field: trees, buildings, rocks, ships | | |
| 99 | ||
| 100 | --- | |
| 101 | ||
| 102 | ## 3. The world model (schema v2) | |
| 103 | ||
| 104 | `content/geography.json` and `static/world.json` merge into **one file**, since a | |
| 105 | place and the terrain that makes it are no longer separate things. No backwards | |
| 106 | compatibility: a one-off script ports today's geography into v2 so the authored | |
| 107 | world is kept, and then both old formats are deleted. | |
| 108 | ||
| 109 | ### 3.1 Shape | |
| 110 | ||
| 111 | > **As built in Phase 1**, the schema differs from the sketch below in one structural | |
| 112 | > way: every kind has the **same `geometry` field** (point, polyline, polygon or band), | |
| 113 | > and the kind-specific parameters sit beside it — not `area`, `shore`, `course` and | |
| 114 | > `centre` fields per kind. One field meant the map, the list, the editor and the | |
| 115 | > permalinks handle every kind one way. And each phase adds a kind's parameters only | |
| 116 | > when a generator reads them, so Phase 1's `Desert` has `upliftM` and `edgeM`, not | |
| 117 | > dunes; `src/world/schema.ts` is the current truth. The sketch stays as the target. | |
| 118 | ||
| 119 | ```ts | |
| 120 | interface World { | |
| 121 | version: 2 | |
| 122 | /** Changes every feature's randomness at once. Rarely touched. */ | |
| 123 | seed: number | |
| 124 | ground: GroundSettings // the gentle base relief everywhere (today's "base" op) | |
| 125 | features: Feature[] | |
| 126 | /** Generated hamlets and villages between the authored places (§4.7). */ | |
| 127 | settlementDensity: number // per 1,000 km² of habitable land; 0 = only authored ones | |
| 128 | weather: WeatherPlan // Tower's published schedule (§4.9) | |
| 129 | } | |
| 130 | ||
| 131 | interface FeatureBase { | |
| 132 | id: string // permalink target; follows the name until pinned (D-012) | |
| 133 | name: string | |
| 134 | blurb: string // ships to readers | |
| 135 | notes: string // never ships (D-011) | |
| 136 | public: boolean | |
| 137 | /** Written once at creation and mixed into the world seed (§5.5). Never derived from the id. */ | |
| 138 | seed: number | |
| 139 | } | |
| 140 | ||
| 141 | type Area = | |
| 142 | | { type: 'polygon'; points: [theta: number, z: number][] } | |
| 143 | | { type: 'band'; zFrom: number; zTo: number } // rings the world (D-027) | |
| 144 | ||
| 145 | type Feature = FeatureBase & ( | |
| 146 | | Mountains | Lake | River | Sea | Desert | Forest | Settlement | Route | Marker | |
| 147 | ) | |
| 148 | ``` | |
| 149 | ||
| 150 | ### 3.2 The concepts | |
| 151 | ||
| 152 | Every parameter below has a default, so a feature can be dropped on the map with | |
| 153 | nothing but its geometry and look reasonable immediately. | |
| 154 | ||
| 155 | ```ts | |
| 156 | interface Mountains { | |
| 157 | kind: 'mountains' | |
| 158 | area: Area // footprint; relief fades to the ground at its edge | |
| 159 | spine?: Polyline // optional main ridge line; peaks are generated along it | |
| 160 | peaks: Peak[] // authored summits, held at exactly their elevation | |
| 161 | generatedPeaks: number // extra unnamed summits per 100 km² (default 0.6) | |
| 162 | ruggedness: number // 0..1, how sharp ridges and deep valleys are | |
| 163 | erosion: number // 0..1, how far the fluvial pass cuts in | |
| 164 | snowlineM: number // default 3000 | |
| 165 | treelineM: number // default 2200 | |
| 166 | } | |
| 167 | interface Peak { name?: string; theta: number; z: number; elevationM: number } | |
| 168 | ||
| 169 | interface Lake { | |
| 170 | kind: 'lake' | |
| 171 | shore: Polygon | |
| 172 | levelM: number | 'auto' // 'auto' = fill the basin to its lowest rim | |
| 173 | depthM: number | |
| 174 | wind: Wind // drives the waves (§4.2) | |
| 175 | } | |
| 176 | ||
| 177 | interface River { | |
| 178 | kind: 'river' | |
| 179 | course: Polyline // source first, mouth last | |
| 180 | flowM3S: number // sets channel width and valley size | |
| 181 | source: { elevationM: number | 'terrain' } | |
| 182 | outlet: string | 'auto' // id of the lake / sea / river it ends in | |
| 183 | valley: { widthM: number; depthM: number } | 'auto' | |
| 184 | } | |
| 185 | ||
| 186 | interface Sea { | |
| 187 | kind: 'sea' | |
| 188 | area: Area // the cap seas are bands; a sea can also be a polygon | |
| 189 | levelM: number | |
| 190 | depthM: number | |
| 191 | wind: Wind | |
| 192 | ships: { count: number; routes: 'ring' | 'coastal' | 'crossing' } | |
e18dad04 2026-09-30 Pavel Safronov | 193 | ice?: { edgeM: number; fastM: number; // D-090: open water, pack from edgeM, fast from fastM |
| 194 | range?: { fromM: number; heightM: number } } // D-093: ice mountains at the cap | |
376f2ce6 2026-09-16 Pavel Safronov | 195 | } |
| 196 | ||
| 197 | interface Desert { | |
| 198 | kind: 'desert' | |
| 199 | area: Area | |
| 200 | dunes: { coverage: number; heightM: number; wind: Wind } | |
| 201 | hills: { coverage: number; heightM: number } | |
| 202 | plateaus: { coverage: number; heightM: number; terraces: number } | |
| 203 | pastWater: { washes: number; saltPans: number; shorelines: boolean } | |
| 204 | } | |
| 205 | ||
| 206 | interface Forest { | |
| 207 | kind: 'forest' | |
| 208 | area: Area | |
| 209 | composition: { conifer: number } // 0 = all broadleaf, 1 = all conifer | |
| 210 | giants: boolean // a tall forest: sequoia-class trees (§4.6) | |
| 211 | densityPerHa: number // mature stems per hectare, default 450 | |
| 212 | stands: Stand[] // planned concentrations (§4.6) | |
| 213 | meadows: { coverage: number; clearings: Polygon[] } | |
| 214 | paths: { density: number } // auto-generated trail network, 0 = none | |
| 215 | } | |
| 216 | interface Stand { area: Polygon; densityPerHa: number; conifer: number; ageYears: number; layout: 'natural' | 'rows' } | |
| 217 | ||
| 218 | interface Settlement { | |
| 219 | kind: 'settlement' | |
| 220 | size: 'hamlet' | 'village' | 'town' | 'station' | 'city' // the capital is 'city' | |
| 221 | centre: { theta: number; z: number } | |
| 222 | boundary: Polygon | { radiusM: number } | 'auto' // 'auto' sizes from `size` | |
| 223 | roads: { arterials: number; pattern: 'grid' | 'radial' | 'organic'; blockM: number } | |
| 224 | buildings: { coverage: number; storeys: [min: number, max: number]; peakAtCentre: boolean } | |
| 225 | } | |
| 226 | ||
| 227 | interface Route { | |
| 228 | kind: 'road' | 'path' | |
| 229 | /** Either drawn, or two endpoints routed automatically across the terrain. */ | |
| 230 | way: Polyline | { from: string; to: string } | |
| 231 | widthM: number | |
| 232 | } | |
| 233 | ||
| 234 | /** Everything that is a label and not a landform: towns, embassies, landmarks, notes. */ | |
| 235 | interface Marker { | |
| 236 | kind: 'embassy' | 'tower' | 'landmark' | 'note' | 'region' | |
| 237 | geometry: Point | Polyline | Area | |
| 238 | } | |
| 239 | ||
| 240 | interface Wind { directionDeg: number; speedMS: number } // 0° = deepward, 90° = spinward | |
| 241 | ||
| 242 | interface WeatherPlan { | |
| 243 | /** The wind each body of water gets when nothing is scheduled. */ | |
| 244 | baseline: { target: string; wind: Wind }[] | |
| 245 | events: WeatherEvent[] | |
| 246 | } | |
| 247 | interface WeatherEvent { | |
| 248 | id: string | |
| 249 | target: string // a sea, lake or desert id | |
| 250 | kind: 'calm' | 'breeze' | 'gale' | 'storm' | |
| 251 | start: { day: number; hour: number } | |
| 252 | durationH: number | |
| 253 | wind: Wind | |
| 254 | /** When Tower announced it. A plan is only a plan once people can read it. */ | |
| 255 | publishedAt: { day: number; hour: number } | |
| 256 | /** Tower changing its mind. The published event is kept; this is what happens instead. */ | |
| 257 | revisions: { at: { day: number; hour: number }; kind: WeatherEvent['kind']; wind: Wind; reason: string }[] | |
| 258 | } | |
| 259 | ``` | |
| 260 | ||
| 261 | ### 3.3 What changes from v1, and why | |
| 262 | ||
| 263 | - **Relief parameters move onto the feature.** v1 had `kind` on the place and an | |
| 264 | `op` with its own parameter bag in a separate file. v2 has one discriminated union: | |
| 265 | the kind decides what gets generated, and the TypeScript type decides which | |
| 266 | parameters exist. | |
| 267 | - **Canyons and ridges stop being kinds.** A canyon is what a river valley is where | |
| 268 | it passes through high ground; The Narrows becomes a named `Marker` on the | |
| 269 | river's course. A ring ridge is a `Mountains` feature with a `band` area. | |
| 270 | - **Sub-elements live inside their feature.** Peaks inside mountains; stands and | |
| 271 | clearings inside a forest. That is what makes them move, hide and delete together. | |
| 272 | - **Private features vanish from readers' terrain too.** A `public: false` lake is | |
| 273 | stripped from the reader's file (D-011), so readers do not get its basin either. | |
| 274 | That is the correct behaviour — a hole in the ground shaped like an unpublished | |
9d5a061c 2026-09-27 Pavel Safronov | 275 | lake would leak it. Since D-072 the author uses the same app readers do, so an |
| 276 | unpublished feature exists only in `content/world.json` until it is published. | |
376f2ce6 2026-09-16 Pavel Safronov | 277 | |
| 278 | --- | |
| 279 | ||
| 280 | ## 4. The concepts, one by one | |
| 281 | ||
| 282 | Each section says: what is authored, how it is generated, how it is drawn, and which | |
| 283 | libraries (if any) it uses. Library choices are justified in §7. | |
| 284 | ||
| 285 | ### 4.1 Mountains — "reasonable peaks" | |
| 286 | ||
| 287 | **The problem with today's approach.** A dome with ridged noise laid over it, | |
| 288 | normalised to the declared height. It gives one summit at an arbitrary spot in a | |
| 289 | blister of noise, and no real ridges, saddles or valleys. | |
| 290 | ||
e7a9f9dd 2026-09-17 Pavel Safronov | 291 | > **As built (D-065)**, stage 3 is not an iterative erosion but the stream-power |
| 292 | > **steady state**, computed directly over the drainage network and scaled back to the | |
| 293 | > authored massif; overlapping ranges erode as one group; and authored tarns keep a | |
| 294 | > shelf. The description below is kept as the plan; §15 says why each changed. | |
| 295 | ||
376f2ce6 2026-09-16 Pavel Safronov | 296 | **Generation, in three stages.** |
| 297 | ||
| 298 | 1. **A ridge graph.** The authored peaks plus `generatedPeaks` extra summits | |
| 299 | (Poisson-disk placed, heights falling off from the spine and from the authored | |
| 300 | peaks) are triangulated with **delaunator**. The minimum spanning tree of that | |
| 301 | triangulation, weighted by the height drop along each edge, gives the main | |
| 302 | ridges; a fraction of the remaining edges become spurs. Each ridge edge gets a | |
| 303 | saddle height below the lower of its two peaks. | |
| 304 | 2. **An uplift field.** Elevation is a function of distance to the ridge graph: | |
| 305 | a sharp crest at the ridge (the arête), steepening flanks, and a foot that blends | |
| 306 | to the ground at the edge of `area`. Authored peaks are pinned — the field | |
| 307 | is exactly `elevationM` at each one. | |
| 308 | 3. **Fluvial erosion.** The uplift field is sampled on a feature-local grid | |
| 309 | (120 m; 200 m for a ring range, since a band is 314 km long) and run through the | |
| 310 | **stream-power law with the implicit solver of Braun & Willett (2013)**, for a | |
| 311 | fixed number of iterations proportional to `erosion`. This is what cuts | |
| 312 | dendritic valleys and makes the ridges between them read as ridges. Authored peak | |
| 313 | cells are held fixed during it. The result is cached (§5.4). | |
| 314 | ||
| 315 | **Below the grid**, detail comes from *derivative-damped fBm* (noise whose higher | |
| 316 | octaves are suppressed on steep slopes — the standard "eroded noise" trick), | |
| 317 | evaluated per tile. | |
| 318 | ||
| 319 | **Drawn as:** terrain tiles (§6). Material by elevation and slope: vegetation below | |
| 320 | `treelineM`, scree and rock above it, snow above `snowlineM` only where the slope is | |
| 321 | shallow enough to hold it (today's rule, kept). | |
| 322 | ||
| 323 | **Test:** every authored peak's generated elevation is within 1 m of `elevationM`, | |
| 324 | and is a local maximum within 500 m. | |
| 325 | ||
| 326 | ### 4.2 Lakes — "waves" | |
| 327 | ||
| 328 | **Generation.** The shore polygon is carved into a basin whose depth profile follows | |
| 329 | distance from the shore. `levelM: 'auto'` fills to the lowest point of the rim, the | |
| 330 | way today's tarn op fills from the floor up. | |
| 331 | ||
| 332 | **The water surface is real geometry.** Level water in Tower is a surface of | |
| 333 | **constant radius** (§1.9.3), so a lake surface is a patch of a cylinder at | |
| 334 | `R − levelM`. That makes "the far shore rides 2.3 km above your tangent plane" true | |
| 335 | in the render for free. | |
| 336 | ||
| 337 | **Waves are sized from the lake, not chosen.** Wind waves are limited by *fetch*, | |
| 338 | the distance the wind blows over water. The JONSWAP fetch-limited relations give: | |
| 339 | ||
| 340 | - significant height `Hs ≈ 0.0016 · U · √(F / g)` | |
| 341 | - peak period `Tp ≈ 0.286 · (U / g) · (g F / U²)^⅓` | |
| 342 | ||
| 343 | A 10 m/s wind across a 30 km lake gives **0.9 m waves at a 4.2 s period**. A | |
| 344 | mountain tarn 2 km across gives 0.2 m. The shader is a sum of 4–6 Gerstner waves | |
| 345 | fitted to that height and period near the camera, fading to scrolling normal | |
| 346 | detail at distance. Shore foam where the water is shallow, from the depth between | |
| 347 | the water surface and the terrain under it. | |
| 348 | ||
| 349 | ### 4.3 Rivers — "flow" | |
| 350 | ||
| 351 | **Canon first.** A Tower river exists only because the floor was shaped to make it | |
| 352 | descend (§1.9.3), and it runs in a closed pumped or weather-driven loop (§1.9.5). | |
| 353 | So the generator's one hard rule is: **the water surface descends monotonically from | |
| 354 | source to outlet.** If the terrain along the drawn course would make the river flow | |
9d5a061c 2026-09-27 Pavel Safronov | 355 | uphill, the valley is cut deeper until it does not — and the compile reports how |
376f2ce6 2026-09-16 Pavel Safronov | 356 | much it had to cut, because that is information the author wants. |
| 357 | ||
| 358 | **Generation.** | |
| 359 | ||
| 360 | 1. The course is smoothed (Catmull–Rom, as today) and resampled every 200 m. | |
| 361 | 2. **Long profile.** *Changed in Phase 1 (§15).* Not a concave curve between the ends, | |
| 362 | and not clamped to the ground: the **least-earthwork** descending profile — the | |
| 363 | weighted isotonic regression of the ground along the course, held to the outlet's | |
| 364 | level at the mouth and a lake's level at a source in one, falling at least 2 m/km | |
| 365 | so no reach is a canal. It cuts where the ground is high and **builds the ground up | |
| 366 | where it is low**, in whatever balance moves the least earth. | |
| 367 | 3. **Hydraulic geometry.** Channel width ≈ `a · Q^0.5` and depth ≈ `c · Q^0.4` | |
| 368 | (Leopold's relations), so flow in m³/s sets the size. A tributary adds its | |
| 369 | flow to the river it joins, downstream of the confluence. The Narrows gets its | |
| 370 | 392 m³/s from the four rivers that feed it, not from a number typed in. | |
| 371 | 4. **Valley carving, and embankments.** Distance from the centreline carves a | |
| 372 | channel, banks and a valley whose width depends on the ground: narrow where the | |
| 373 | river crosses high ground (canyon), wide where it is low (floodplain). Where the | |
| 374 | ground is below the profile, the same distance builds an embankment with 1:6.7 | |
| 375 | flanks. This replaces v1's hand-authored `reaches`. | |
| 376 | ||
| 377 | **Drawn as:** a ribbon mesh along the centreline, at the profile's water level, | |
| 378 | with UVs running along the flow. Flow is a normal map scrolled along the ribbon at a | |
| 379 | speed from Manning's equation, the technique of Three's `Water2` flow-map example. | |
| 380 | Where the profile is steep enough, rapids (white, faster); where a drop exceeds ~20 m, | |
| 381 | a waterfall sheet that **leans antispinward** by the impact angle | |
| 382 | `canon/motion.ts` already computes — 11° for a 1 km fall. The waterfall is the | |
| 383 | compass (§1.9.2), and this would be the first time the render shows it. | |
| 384 | ||
| 385 | ### 4.4 Cap seas — "waves and sailing ships" | |
| 386 | ||
| 387 | **Generation.** A band carved into a basin, as today's flood op, with a water surface | |
| 388 | at constant radius across the whole ring. | |
| 389 | ||
| 390 | **A ring sea has unlimited fetch.** Wind blowing around the circumference crosses the | |
| 391 | same water forever, so the fetch limit of §4.2 never applies and the sea reaches a | |
| 392 | **fully developed** state: `Hs ≈ 0.21 · U² / g`, which is **2.1 m at 10 m/s and | |
| 393 | 8.6 m at 20 m/s**. No lake on Earth does that; only open ocean does — and in Tower | |
| 394 | it happens only when Tower schedules it (§4.9). Wind speed at any moment comes from the | |
| 395 | weather plan, and the sea state follows it with a lag of a few hours, so a storm | |
| 396 | builds and a sea goes on running after the wind drops. Waves are Gerstner as in §4.2, | |
| 397 | with longer wavelengths and a swell component. | |
| 398 | ||
| 399 | **Ships.** `count` ships per sea, each with a route: `ring` circles the world | |
| 400 | along a constant z; `coastal` follows the shore; `crossing` shuttles between named | |
| 401 | harbours. Position is a **pure function of route and clock time**, so every reader | |
| 402 | at the same moment sees the same fleet in the same place. Ships **read the | |
| 403 | published plan**: a ship does not sail into a published storm, and waits in harbour | |
| 404 | until it has passed. Each ship samples the same wave function on the CPU at its hull to | |
| 405 | heave, pitch and roll. | |
| 406 | ||
| 407 | Hulls and sails are built procedurally (a lofted hull, a mast, one to three sails | |
| 408 | that are planes with a billow), a few hundred triangles each, instanced. Sails set | |
| 409 | to the sea's wind, so a ship on a ring route sailing against the wind tacks. | |
| 410 | ||
a088e5d5 2026-09-17 Pavel Safronov | 411 | *As built (§18): ring routes only, no harbours drawn, flat sails, and no tacking yet.* |
| 412 | ||
376f2ce6 2026-09-16 Pavel Safronov | 413 | **Scale check.** A 40 m ship subtends 0.9 arc-minutes at 150 km, the edge of the haze |
| 414 | horizon. At a 60° field on a 1,500 px-wide view one pixel is 2.4 arc-minutes, so it is | |
| 415 | under half a pixel: invisible to the naked eye there, a speck through the binoculars | |
| 416 | (the field-of-view control). At 20 km it is 7 arc-minutes, three pixels — a readable | |
| 417 | sail. | |
| 418 | ||
| 419 | ### 4.5 Desert — "hills, plateaus, visible past water" | |
| 420 | ||
| 421 | **Generation, as layers inside the desert area:** | |
| 422 | ||
| 423 | - **Dunes.** Anisotropic ridged noise stretched perpendicular to the wind, with an | |
| 424 | asymmetric profile (gentle windward slope, steep slip face). Tower makes its own | |
| 425 | wind (§1.9.4), so the dune direction is a statement about what Tower does there. | |
| 426 | - **Rocky hills.** Low domes of ridged noise, `hills.coverage` of the area. | |
| 427 | - **Plateaus, mesas and buttes.** A noise mask thresholded into plateau shapes, then | |
| 428 | raised by `heightM` through a **terrace function** with `terraces` steps: flat | |
| 429 | tops, near-vertical cliff bands, talus slopes below. Small isolated parts of the | |
| 430 | mask become buttes. The rock shader paints strata by elevation, so the layers line | |
| 431 | up across separate mesas, which is what makes them read as one eroded formation. | |
| 432 | - **Past water — quoted, not remembered** (§0.1). Three marks, each switchable: | |
| 433 | - **Dry washes** — a drainage network generated by the same flow routing as the | |
| 434 | mountains (§4.1 step 3), cut shallow into the plateaus and the plain, with a | |
| 435 | pale gravel floor and no water. Tower routes them as if water had carved them, | |
| 436 | because that is what the originals it copied look like. | |
| 437 | - **Salt pans** — the lowest closed basins, flattened and painted white. | |
| 438 | - **Abandoned shorelines** — horizontal benches at fixed levels around a salt pan, | |
| 439 | the bathtub rings of a lake that never existed. | |
| 440 | ||
| 441 | Because none of it is history, the generator is free to place these as **set | |
| 442 | pieces** — a salt pan ringed by shorelines where the author wants the view, not where | |
| 443 | a drainage model would demand one. | |
| 444 | ||
51c39d8b 2026-09-17 Pavel Safronov | 445 | *As built (§20): all of the above, with the pans placed in the lowest ground rather than |
| 446 | by hand, and the terraces cut by the tiles rather than compiled.* | |
| 447 | ||
376f2ce6 2026-09-16 Pavel Safronov | 448 | ### 4.6 Forest — "trees, planned concentrations, meadows, paths" |
| 449 | ||
| 450 | **Trees are real size.** Conifers 20–45 m tall with narrow crowns; broadleaf 15–30 m | |
| 451 | with crowns ~0.3 of their height. Size varies with `ageYears` per stand and with | |
| 452 | elevation (smaller toward the treeline). | |
| 453 | ||
| 454 | **Where trees go is a density field**, in stems per hectare, built from: | |
| 455 | ||
| 456 | 1. the forest's `densityPerHa`; | |
| 457 | 2. **stands** — authored polygons with their own density, species mix and age. | |
| 458 | These are the planned concentrations: Tower plants its forests, and a stand can | |
| 459 | be `natural` (irregular) or `rows` (a plantation grid); | |
| 460 | 3. **meadows** — authored clearings plus a generated fraction from a thresholded noise | |
| 461 | field, with soft ragged edges; | |
| 462 | 4. **exclusions** — water, roads, paths, buildings, slopes too steep, above treeline. | |
| 463 | ||
| 464 | **Placement is stratified, not a whole-domain Poisson solve.** The surface is divided | |
| 465 | into a fixed global grid of cells sized to the tree spacing; each cell hashes | |
| 466 | `(seed, cell)` to decide whether it holds a tree and where inside it, accepting with | |
| 467 | probability density / max density. This is *blue-noise-like*, and — the property | |
| 468 | that matters — **any tile can compute its own trees without knowing any other | |
| 469 | tile.** A sequential Poisson-disk algorithm cannot promise that (§7.3). | |
| 470 | ||
| 471 | **Paths.** A trail network connecting meadows, stands and the forest edge, routed by | |
| 472 | A* over a slope-cost grid and smoothed (§4.8). Paths clear trees and flatten a 2 m | |
| 473 | corridor. | |
| 474 | ||
| 475 | **Drawn as** three distances: | |
| 476 | ||
| 477 | | Distance | Representation | | |
| 478 | |---|---| | |
| 479 | | < 400 m | instanced meshes: trunk plus layered cones (conifer) or clustered lumps (broadleaf), 30–80 triangles | | |
| 480 | | 400 m – 4 km | instanced camera-facing impostors, one quad per tree | | |
| 481 | | > 4 km | no trees. The terrain shader paints canopy, and tile vertices in the forest are lifted by the local mean canopy height with a rough top, so a forest has a silhouette and an edge from far away | | |
| 482 | ||
| 483 | **The tall forest.** A forest with `giants: true` grows sequoia-class trees at | |
| 484 | **90–115 m** — coast redwoods reach 116 m on Earth, and hydraulics cap any tree near | |
| 485 | 122–130 m (Koch et al., 2004), so Tower is growing them close to the limit of what a | |
| 486 | tree can be at 1 g. Stems are far sparser (≈ 40–80 per ha) and trunks 5–8 m across. | |
| 487 | ||
| 488 | Honest about what that looks like: the ordinary canopy is ~30 m, so the giants stand | |
| 489 | **~80 m proud of it** as a dark wall with a ragged top. That is 0.46° at 10 km — | |
| 490 | about a thumb's width at arm's length, clearly a landmark — and 0.09° at 50 km, a | |
| 491 | line on the horizon. Devils Tower is 265 m, so a giant forest on level ground is a | |
| 492 | third of that. If the author ever wants it to read at Devils Tower scale, the forest | |
| 493 | can stand on a raised plinth of ground (a `Mountains` feature of low ruggedness), and | |
| 494 | the two heights add. | |
| 495 | ||
| 496 | **Where it goes: near the capital, placed at random — once** (author, 2026-09-16). The | |
| 497 | port script creates it as its own `Forest` feature, **The Tall Wood** (provisional | |
| 498 | name), about 50 km² with a ragged outline. Its position is drawn from the world seed: | |
| 499 | on land, inside Greatwood, 15–50 km from the capital, and clear of The Deep, the | |
| 500 | capital's own boundary and its roads. The result is **written into the world file**, | |
| 501 | so it is placed once and then stays put for everyone; the author can move it like any | |
| 502 | other feature. Where it overlaps Greatwood, the Tall Wood's trees replace Greatwood's | |
| 503 | (the smaller forest wins inside its own area). | |
| 504 | ||
| 505 | **Why not render every tree.** Greatwood is 216 × 314 km. At 450 stems/ha that is | |
| 506 | **3 billion trees.** Only the ones within 4 km of the eye exist at any moment. | |
| 507 | ||
00ceeedb 2026-09-27 Pavel Safronov | 508 | *As built (§21): the density field, meadows, the three distances and the tall wood, with |
| 509 | the ranges closer in than planned (meshes to 320 m, cards to 760 m, canopy skin beyond) and | |
| 510 | without authored stands or paths — stands and the trail network wait for phase 7's router.* | |
| 511 | ||
376f2ce6 2026-09-16 Pavel Safronov | 512 | ### 4.7 Settlements — "blocky buildings and a few simple roads" |
| 513 | ||
51c39d8b 2026-09-17 Pavel Safronov | 514 | *As built so far (D-070): a settlement is a **point with an extent** — a size and a radius, |
| 515 | drawn on the map as a ring — and the Capital is 20 km across, centred on the axial midpoint, | |
| 516 | which reaches The Deep's shore. The layout inside it is this phase's work.* | |
| 517 | ||
376f2ce6 2026-09-16 Pavel Safronov | 518 | No usable city generator exists on npm (§7.3), so this is written in-house and kept |
| 519 | deliberately simple, as asked. One generator handles every size; `size` sets the | |
| 520 | defaults: | |
| 521 | ||
| 522 | | Size | Radius | Buildings | Streets | | |
| 523 | |---|---|---|---| | |
| 524 | | hamlet | 150 m | 5–20 | one lane | | |
| 525 | | village | 400 m | 50–300 | a main street and a few lanes | | |
| 526 | | town | 1.2 km | 500–3,000 | arterials plus a grid or organic core | | |
| 527 | | station | 300 m | 20–80 | mountain settlements at the tarns (Sisters Station, Hollow Station); lifts and runs are a later addition | | |
| 528 | | city | 6 km | 20,000+ | the capital | | |
| 529 | ||
| 530 | **Filling out the world.** Beyond the authored places, the compile places generated | |
| 531 | settlements at `settlementDensity`. Candidate sites are scored by suitability — flat | |
| 532 | ground, fresh water within 2 km, not in the desert core, not above the treeline — and | |
| 533 | accepted largest first with a minimum spacing that grows with size. Generated | |
| 534 | settlements are **named for their size** — "Hamlet", "Village", "Town" — and marked | |
| 535 | as generated on the map (author, 2026-09-16). Several share a name, which is correct: | |
9d5a061c 2026-09-27 Pavel Safronov | 536 | nobody has named them yet. **Adopting** one — writing it into `content/world.json` as an |
| 537 | authored feature — pins it: after that, changing the world elsewhere cannot move or | |
| 538 | remove it. | |
376f2ce6 2026-09-16 Pavel Safronov | 539 | |
| 540 | Layout, for every size: | |
| 541 | ||
| 542 | 1. **Arterials.** `arterials` roads from the centre to the boundary, bent to avoid | |
| 543 | water and steep ground, and joined to any external routes that reach the city. | |
| 544 | 2. **Local streets.** A `grid` (rotated to the main arterial), `radial` (rings plus | |
| 545 | spokes) or `organic` (streets grown with branching and snapping) at `blockM` | |
| 546 | spacing, clipped to the boundary and to the shoreline. | |
| 547 | 3. **Blocks** are the faces of the planar road graph, inset by half the road width | |
| 548 | (**clipper2-js** offsetting). | |
| 549 | 4. **Lots** by recursive split of each block along its longer axis until lots are | |
| 550 | 15–40 m wide, keeping only lots with street frontage. | |
| 551 | 5. **Buildings** are the lot inset by a setback, extruded to `storeys × 3.2 m`. | |
| 552 | Height rises toward the centre if `peakAtCentre`. Flat roofs. Per-building colour | |
| 553 | jitter. A city pad flattens the ground under the built area. | |
| 554 | ||
| 555 | The Spindle (3.2 km) stays a `tower` marker with its own mesh, not a generated | |
| 556 | building. | |
| 557 | ||
| 558 | ### 4.8 Roads and paths — "around the capitol and through the forest" | |
| 559 | ||
| 560 | - **Drawn routes** are smoothed and draped on the terrain. | |
| 561 | - **Routed routes** (`{ from, to }`) run **A* over a cost grid**: cost rises steeply | |
| 562 | with slope, water is impassable except at a bridge cost, existing roads are cheap | |
| 563 | (so routes merge into networks rather than running in parallel). The result is | |
| 564 | simplified and smoothed. | |
| 565 | - **Default network:** every settlement is joined to its nearest larger neighbour by | |
| 566 | a road, and to the next nearest as well where that saves a long detour, so the | |
| 567 | network is a connected web rather than a star around the capital. Hamlets get | |
| 568 | tracks. Forests add paths (§4.6). | |
| 569 | - **Grading.** A route cuts and fills a corridor to a smoothed longitudinal profile | |
| 570 | with a maximum grade (8% road, 20% path), blended into the ground over a shoulder. | |
| 571 | Surface material is paved or packed earth. | |
| 572 | ||
| 573 | ### 4.9 Weather plan — Tower's schedule | |
| 574 | ||
| 575 | Every wind in Tower is Tower's doing (§1.9.4). Tower **publishes its plans** so that | |
| 576 | people can live around them — sail, harvest, travel — and **changes them whenever it | |
| 577 | chooses**: for example, cancelling a storm because someone foolish has gone sailing | |
| 578 | into it. Both halves are data here, because both are story. | |
| 579 | ||
| 580 | - **Baseline:** each sea, lake and desert has a default wind when nothing is scheduled. | |
| 581 | - **Events:** calm, breeze, gale or storm on a target, for a start and a duration, with | |
| 582 | the moment Tower published it. | |
| 583 | - **Revisions:** Tower's changes of mind, each with the moment it happened and a | |
| 584 | reason. The published event is **kept**, so the app can show both what people were | |
| 585 | told and what Tower actually did. | |
| 586 | ||
| 587 | **The clock gains a day.** Today the interior has a time of day only (D-053). Scheduling | |
| 588 | needs a day number, so the clock becomes `day + phase`. **Days count from 0, with no | |
| 589 | week or month names** (author, 2026-09-16). The panel shows the plan | |
| 590 | as Tower would publish it: a board of upcoming events, with revisions struck through | |
| 591 | and replaced. | |
| 592 | ||
| 593 | **What the weather drives:** wave height and direction on seas and lakes (§4.2, §4.4), | |
| 594 | sail trim and harbour decisions for ships, and the dune direction in a desert, which | |
| 595 | uses the target's baseline wind. What it does **not** drive: clouds and rain, which are | |
| 596 | still not drawn (D-049). | |
| 597 | ||
| 598 | **Default plan.** The port script writes a baseline breeze everywhere and a repeating | |
| 599 | pattern of a few scheduled storms per month on each cap sea, so the feature is visible | |
| 600 | before anyone authors an event. | |
| 601 | ||
a088e5d5 2026-09-17 Pavel Safronov | 602 | *As built (§18): the plan is written into `content/world.json` directly rather than by the |
| 603 | port script, and deserts do not read it yet (Phase 5).* | |
| 604 | ||
376f2ce6 2026-09-16 Pavel Safronov | 605 | --- |
| 606 | ||
| 607 | ## 5. Generation pipeline | |
| 608 | ||
| 609 | ### 5.1 Overview | |
| 610 | ||
| 611 | ```mermaid | |
| 612 | flowchart LR | |
| 613 | W[world file] --> C[world compile<br/>workers, on load + on edit] | |
| 614 | C --> K[(feature caches<br/>ridges, erosion grids,<br/>river profiles, city layouts,<br/>route polylines)] | |
| 615 | C --> P[coarse world field<br/>~500 m] | |
| 616 | P --> M[map view preview] | |
| 617 | P --> X[exterior x-ray + far tiles] | |
| 618 | K --> T[tile generator<br/>worker pool, on demand] | |
| 619 | T --> R[interior renderer<br/>terrain · water · trees · buildings · ships] | |
| 620 | T --> G[ground query<br/>standing, collision] | |
| 621 | ``` | |
| 622 | ||
| 623 | ### 5.2 The world compile | |
| 624 | ||
| 625 | Runs in a worker on load and after every edit. It never produces pixels for the | |
| 626 | 3D view; it produces the **expensive, whole-feature** results that tiles then sample. | |
| 627 | ||
| 628 | In dependency order: | |
| 629 | ||
| 630 | 1. **Validate and index.** Parse, fill defaults, give every feature an influence box. | |
| 631 | *Phase 1 checks the boxes directly: with 28 features that is cheaper than an index. | |
| 632 | **flatbush** comes in when settlements and routes make the count large.* | |
| 633 | 2. **Landforms.** Mountain ridge graphs and erosion grids; desert plateau masks and | |
| 634 | wash networks. | |
| 635 | 3. **Water.** Lake levels (`auto` needs the landforms), river long profiles | |
| 636 | (needs outlet levels), river flow accumulation down tributaries. | |
| 637 | 4. **Settlement.** Generated settlement sites, then layouts for every settlement; | |
| 638 | route networks (need the final terrain cost grid). | |
| 639 | 5. **Weather.** Resolve the plan's events and revisions into a wind timeline per target. | |
| 640 | 6. **Coarse world field.** Elevation and surface material for the whole world at | |
| 641 | ~500 m (1080 × 628), by evaluating the full elevation function once per cell. | |
| 642 | This feeds the map, the exterior's x-ray and the most distant tiles. | |
| 643 | ||
| 644 | **Incremental.** Each result is keyed by a hash of the inputs that produced it. | |
| 645 | Editing a forest's meadows reruns nothing in steps 2–3; moving a peak reruns that | |
| 646 | mountain range, the rivers whose course crosses it, and the routes whose cost grid | |
| 647 | changed. Tiles are invalidated only where the changed features' influence boxes | |
| 648 | overlap them. | |
| 649 | ||
| 650 | ### 5.3 The elevation function | |
| 651 | ||
| 652 | One pure function, used by the tiles, the coarse field, the ground query and the | |
| 653 | tests: | |
| 654 | ||
| 655 | ``` | |
| 656 | elevation(θ, z) = | |
| 657 | ground(θ, z) gentle base relief | |
| 658 | ⊕ landforms: mountains (max), desert (add) from their caches + detail noise | |
| 659 | ⊖ basins: seas, lakes carved | |
| 660 | ⊖ valleys: rivers, washes carved to their profiles | |
| 661 | ⊜ grading: city pads, roads, paths cut and fill toward a target | |
| 662 | ``` | |
| 663 | ||
| 664 | Features are fetched from the spatial index, so a point in open plain evaluates | |
| 665 | the ground and nothing else. | |
| 666 | ||
| 667 | **Noise is evaluated on the cylinder itself.** 3D simplex noise at | |
| 668 | `(R cos θ, R sin θ, z) / scale` is seamless around the circumference by construction. | |
| 669 | That retires D-019's snapped-lattice workaround, which was a fix for sampling 2D noise | |
| 670 | on a surface that wraps. | |
| 671 | ||
| 672 | A sibling function, `surface(θ, z)`, returns material weights (rock, scree, snow, | |
| 673 | grass, meadow, forest floor, sand, gravel, salt, soil, paving) and the scatter | |
| 674 | densities (trees per ha, whether a building lot is here). | |
| 675 | ||
| 676 | ### 5.4 Caching | |
| 677 | ||
| 678 | - **Memory:** an LRU of generated tiles (~400 tiles on desktop, ~120 on phones). | |
| 679 | - **IndexedDB:** feature caches keyed by input hash — erosion grids above all, since | |
| 680 | they are the slow part (§8.3). A returning reader skips the compile's heavy steps. | |
| 681 | - **No cache is authoritative.** Clearing site data costs time, never correctness. | |
| 682 | ||
| 683 | ### 5.5 Determinism, and the seed | |
| 684 | ||
| 685 | **The seed is part of the world file, so it is the same for everyone.** Nothing is | |
| 686 | rolled per visitor, per device or per page load. Two readers standing on the same spot | |
| 687 | see the same trees, the same village, and the same ship on the same wave; a reader's | |
| 688 | link lands on what the author saw. | |
| 689 | ||
| 690 | Where the numbers come from: | |
| 691 | ||
| 692 | | Seed | Set when | Changes when | | |
| 693 | |---|---|---| | |
9d5a061c 2026-09-27 Pavel Safronov | 694 | | `world.seed` | once, by the port script, to a fixed value | only if the author deliberately rerolls the whole world | |
| 695 | | `feature.seed` | once, when the feature is written into the file — the one moment a random number is drawn — and saved with it | only if the author asks for that feature to be rerolled | | |
376f2ce6 2026-09-16 Pavel Safronov | 696 | | everything generated | never stored; recomputed from the two seeds above and the feature's parameters | whenever those change | |
| 697 | ||
9d5a061c 2026-09-27 Pavel Safronov | 698 | `content/world.json` carries the seeds, and the `world.json` the app loads is made from |
| 699 | it with the same seeds, so what the author sees is exactly what readers get. | |
376f2ce6 2026-09-16 Pavel Safronov | 700 | Placements made "at random" — generated peaks, generated settlements, the Tall Wood's |
| 701 | position — are random in the sense of *chosen by the seed*, not *different each time*. | |
| 702 | ||
| 703 | - One integer hash (the existing `Math.imul` hash from `tools/noise.mjs`, kept) and a | |
| 704 | seeded PRNG built on it. No `Math.random` anywhere in generation. | |
| 705 | - Every feature's randomness comes from `hash(world.seed, feature.seed)`, where | |
| 706 | `feature.seed` is a number written once when the feature is created. It is **not** | |
| 707 | derived from the id: ids follow names until pinned (D-012), so an id-based seed | |
| 708 | would reshuffle a forest every time it was renamed. | |
| 709 | - A test generates a fixed set of tiles twice and compares them byte for byte. | |
| 710 | ||
| 711 | --- | |
| 712 | ||
| 713 | ## 6. Rendering in the interior view | |
| 714 | ||
| 715 | ### 6.1 Terrain level of detail | |
| 716 | ||
| 717 | The single 512 × 768 cylinder is replaced by a **quadtree of tiles in the unrolled | |
| 718 | (z, arc) plane**, bent onto the cylinder. | |
| 719 | ||
| 720 | - **Level 0:** 16 tiles along × 10 around — 33.75 × 31.4 km each. | |
| 721 | - Each level halves the tile. **Level 10**, the finest, is 33 × 31 m. | |
| 722 | - Every tile is a **65 × 65 vertex grid** (33 × 33 on phones), so level 0 has 527 m | |
| 723 | vertex spacing and level 10 has 0.5 m. | |
| 724 | - A tile splits when the eye is closer than **1.8** tile-widths (1.6 on phones), | |
| 725 | measured to a sphere around the tile's *actual* relief, and is drawn from its parent | |
| 726 | until all four children are ready — nothing pops *out*, detail pops *in*. Measured: | |
| 727 | about 60 tiles a level and 740 chosen, of which frustum culling leaves about 200 draw | |
| 728 | calls and 1.9 M triangles on a laptop. | |
| 729 | - Neighbouring tiles at different levels are sealed with **skirts** (a short curtain | |
| 730 | hung from each edge). Geomorphing is a later refinement if popping is noticeable. | |
| 731 | - Around θ the tree wraps: tile index 9 neighbours tile index 0. | |
| 732 | ||
| 733 | **Precision.** Vertices are bent onto the cylinder **in the worker**, in coordinates | |
| 734 | relative to the tile's own centre, and the tile mesh is positioned at that centre. | |
| 735 | Float32 at 50 km in kilometre units is ~6 mm; relative to a tile centre it is | |
| 736 | sub-millimetre. Doing the bend in the vertex shader from absolute coordinates would | |
| 737 | put 6 mm of jitter on every tree and roof edge. The logarithmic depth buffer stays. | |
| 738 | ||
| 739 | **Standing on the ground** calls the same `elevation(θ, z)` function, so D-035's | |
| 740 | problem — the CPU and GPU disagreeing about where the ground is — disappears rather | |
| 741 | than being worked around. | |
| 742 | ||
| 743 | ### 6.2 Surface shading without a bitmap | |
| 744 | ||
| 745 | The colour raster is gone. Each tile vertex carries material weights from | |
| 746 | `surface(θ, z)`; the fragment shader blends a small palette of procedural materials | |
| 747 | (colour, noise-driven variation, slope darkening) — splatting, with the "texture" | |
| 748 | computed rather than loaded. Distant tiles average to the same colours, so there is | |
| 749 | no seam between near and far. | |
| 750 | ||
| 751 | Every new material — terrain, water, trees, buildings, ships — includes | |
59c12827 2026-09-28 Pavel Safronov | 752 | `HAZE_GLSL`, `SUN_GLSL` and the shared uniforms (`uExposure`, `uSun` …), so haze, the |
| 753 | sun and night apply to all of them uniformly (D-074). This is a hard rule: a material | |
376f2ce6 2026-09-16 Pavel Safronov | 754 | without it will glow through 200 km of air. |
| 755 | ||
| 756 | ### 6.3 Water, scatter, ships | |
| 757 | ||
| 758 | - **Water bodies:** each lake and sea has its own tile set at constant radius, drawn | |
| 759 | only where the terrain is below the level (the depth test does the shoreline). | |
| 760 | - **Rivers:** ribbon meshes per tile. | |
| 761 | - **Trees, buildings:** one `InstancedMesh` per tile per model; tile visibility is the | |
| 762 | culling. Upgrade path: `@three.ez/instanced-mesh` if per-instance culling is needed. | |
| 763 | - **Ships:** one instanced mesh per sea, updated per frame from the clock. | |
| 764 | ||
| 765 | ### 6.4 Flying | |
| 766 | ||
| 767 | The interior already has eye height to 25 km and the drag-the-ground gesture | |
| 768 | (D-059). Added: | |
| 769 | ||
| 770 | - **Keyboard flight:** W/S forward/back, A/D strafe (arrows too), Q/E down/up, shift | |
| 771 | to boost; speed is 1.2 × height above ground per second, at least 2 m/s, and shift | |
| 772 | multiplies it by five. Climbing is a fixed factor per second — ×2 a second, ×7.4 with | |
| 773 | shift — since the same ×5 there would take 100 m to 40 km in a single second. | |
| 774 | - Terrain clearance: the eye never goes below ground + 1.7 m. | |
a088e5d5 2026-09-17 Pavel Safronov | 775 | - **Scenic tour** (§19): a scene every thirty seconds, flown by the view itself. |
3e898b51 2026-09-28 Pavel Safronov | 776 | - Eye height ceiling raised toward the axis (~45 km; since D-084, the beam's edge at 48.4 km) so the whole world can be seen |
376f2ce6 2026-09-16 Pavel Safronov | 777 | from the middle of the air, if wanted. |
| 778 | ||
| 779 | --- | |
| 780 | ||
| 781 | ## 7. Libraries | |
| 782 | ||
| 783 | ### 7.1 Adopt | |
| 784 | ||
| 785 | | Package | Version | License | Use | Why this one | | |
| 786 | |---|---|---|---|---| | |
| 787 | | **simplex-noise** ✅ Phase 1 | 4.0.3 | MIT | 2D/3D simplex for every field | TypeScript, tree-shakable, takes a seeded random function; 3D is what makes noise on the cylinder seamless | | |
e7a9f9dd 2026-09-17 Pavel Safronov | 788 | | **delaunator** ✅ Phase 2 | 5.1.0 | ISC | mountain ridge graph; city road graph faces | the standard, fast, tiny; updated 2026 | |
376f2ce6 2026-09-16 Pavel Safronov | 789 | | **flatbush** | 4.6.2 | ISC | spatial index of feature influence boxes | static packed R-tree, ideal for "which features touch this tile"; transferable to workers as one buffer | |
| 790 | | **clipper2-js** | 1.2.4 | Boost | polygon offset (road insets, setbacks), clipping (blocks to boundary and shore) | offsetting is the operation needed, and `polygon-clipping` does not offset. **Check its bundle cost before adopting**; the package ships 1.8 MB unpacked, mostly sources | | |
| 791 | | **comlink** ✅ Phase 1 | 4.4.2 | Apache-2.0 | worker RPC | removes the message-passing boilerplate for the worker pool; 4 KB | | |
| 792 | ||
| 793 | Three.js itself supplies `ShapeUtils` triangulation (earcut) and the `Water2` flow-map | |
| 794 | technique, so neither needs a separate package. | |
| 795 | ||
| 796 | ### 7.2 Considered, useful later | |
| 797 | ||
| 798 | | Package | Why not now | | |
| 799 | |---|---| | |
| 800 | | `@three.ez/instanced-mesh` 0.3.16 (MIT) | per-instance culling and LOD; tile-level culling is enough to start | | |
| 801 | | `three-mesh-bvh` 0.9.15 (MIT) | fast raycasting; only needed if picking against generated meshes | | |
| 802 | | `@thi.ng/poisson` (Apache-2.0) | Poisson sampling with density functions — but sequential over a whole domain, which breaks per-tile independence (§4.6). Fine for placing generated peaks in the compile | | |
| 803 | ||
| 804 | ### 7.3 Looked for and not found, or not suitable | |
| 805 | ||
| 806 | - **City / town layout.** Nothing on npm. Watabou's Medieval Fantasy City Generator is | |
| 807 | Haxe and GPL-3.0. `garden-city` on GitHub is MIT and structurally close (road graph | |
| 808 | → blocks → buildings) but unpublished, 0 stars. Write our own (§4.7), borrowing | |
| 809 | ideas only. | |
| 810 | - **Erosion.** No npm package. The GitHub projects found are demos (WebGPU, particle | |
| 811 | erosion) rather than libraries. Braun–Willett stream power is ~200 lines and | |
| 812 | deterministic, which a droplet simulation is not. Write our own. | |
| 813 | - **Terrain LOD.** `@interverse/three-terrain-lod` (MIT, 2026) is a quadtree for a | |
| 814 | *flat* plane with a single heightmap; `three.terrain.js` generates small | |
| 815 | heightmap terrains. Neither handles a cylinder, per-tile worker generation, or | |
| 816 | a surface that wraps. `@mapbox/martini` builds adaptive meshes but assumes a | |
| 817 | square 2ᵏ+1 grid per tile with no crack handling between LODs. Write our own | |
| 818 | quadtree (~500 lines). | |
| 819 | - **Road routing.** `ngraph.path` and `pathfinding` need an explicit graph or grid | |
| 820 | object; for a million-cell cost grid a typed-array A* with a binary heap in a | |
| 821 | worker is smaller and faster. Write our own. | |
| 822 | - **Ocean.** Tidewater and Three.js Water Pro exist, but target WebGPU/TSL or are | |
| 823 | commercial, and none knows a water surface is a cylinder. Gerstner waves in our own | |
| 824 | shader, including the haze. | |
| 825 | ||
| 826 | --- | |
| 827 | ||
| 828 | ## 8. Performance budgets | |
| 829 | ||
| 830 | ### 8.1 Frame (desktop, 60 fps target) | |
| 831 | ||
| 832 | | | Budget | | |
| 833 | |---|---| | |
| 834 | | terrain tiles drawn | ≤ 250 (≈ 1.05 M vertices at 65²) | | |
| 835 | | trees, meshes | ≤ 60 k instances within 400 m | | |
| 836 | | trees, impostors | ≤ 400 k within 4 km | | |
| 837 | | buildings | ≤ 50 k in view | | |
| 838 | | draw calls | ≤ 600 | | |
| 839 | ||
| 840 | ### 8.2 Generation | |
| 841 | ||
| 842 | | | Budget | | |
| 843 | |---|---| | |
| 844 | | one tile, all layers | ≤ 60 ms in a worker | | |
| 845 | | workers | `hardwareConcurrency − 1`, capped at 6 | | |
| 846 | | first usable view after load | ≤ 3 s (coarse tiles), full detail near the eye ≤ 8 s | | |
| 847 | | edit → visible change | ≤ 2 s for anything except a mountain range's erosion | | |
| 848 | ||
| 849 | ### 8.3 The expensive one | |
| 850 | ||
| 851 | Erosion on the Girdle's ring at 200 m is 425 × 1571 ≈ 670 k cells; Braun–Willett at | |
| 852 | ~100 iterations is estimated at 3–8 s in a worker. **When it is paid, exactly:** | |
| 853 | ||
| 854 | | Situation | Wait | | |
| 855 | |---|---| | |
| 856 | | first ever visit (nothing cached) | the full compile, **3–8 s** on a desktop, behind the status dialog (§10.1) | | |
| 857 | | returning visit, world unchanged | well under 1 s — the caches in IndexedDB are reused | | |
| 858 | | editing a mountain range (peaks, area, erosion) | that range's erosion again, 3–8 s for the Girdle, less for smaller ranges | | |
| 859 | | any other edit | ≤ 2 s; no erosion reruns | | |
| 860 | ||
| 861 | The author accepted this (2026-09-16), so precomputed erosion grids are **not** shipped. | |
| 862 | ||
| 863 | ### 8.4 Phones | |
| 864 | ||
| 865 | **Rule: a phone sees the same world, not a thinner one. It sees less of it at once.** | |
| 866 | ||
| 867 | The forest's density, its stands and meadows, where every tree stands, every | |
| 868 | building and every ship are the same on a phone as on a desktop — they are authored | |
| 869 | canon, and a reader holding a phone in a meadow should be standing in the same meadow | |
| 870 | as the author. What shrinks is **reach and cost**: | |
| 871 | ||
| 872 | | | Desktop | Phone | | |
| 873 | |---|---|---| | |
| 874 | | tile grid | 65² | 33² | | |
| 875 | | trees as meshes | 400 m | 150 m | | |
| 876 | | trees as impostors | 4 km | 1.5 km | | |
| 877 | | canopy beyond that | yes | yes, same shading, so the forest's outline is unchanged | | |
| 878 | | workers | up to 6 | 2 | | |
| 879 | | ship wave sampling | all visible ships | within 5 km | | |
| 880 | ||
| 881 | So yes, a phone flies through a full-density forest; the individual trees simply start | |
| 882 | closer in. The tier is chosen automatically from the GPU and memory, with a | |
| 883 | **Detail: auto / low / high** override in the panel. A first compile on a phone is | |
| 884 | estimated at 2–3× the desktop time, 10–20 s, which the status dialog carries. | |
| 885 | ||
| 886 | --- | |
| 887 | ||
| 888 | ## 9. Views other than the interior | |
| 889 | ||
| 890 | - **Map.** Draws a hillshaded, material-coloured image of the **coarse world field**, | |
| 891 | produced live by the compile, with feature geometry drawn over it as vectors. It is | |
| 892 | still an image on screen, but it is a view generated from the features, not a | |
9d5a061c 2026-09-27 Pavel Safronov | 893 | painted source of truth. The vectors are changed in the world file. |
376f2ce6 2026-09-16 Pavel Safronov | 894 | - **Exterior.** The x-ray reading uses the same coarse field as its texture. Looking |
| 895 | in through the windows (D-060) uses only the coarsest tiles, since nothing finer is | |
| 896 | resolvable through 100 km of glass and haze. | |
| 897 | ||
| 898 | --- | |
| 899 | ||
| 900 | ## 10. Editing | |
| 901 | ||
9d5a061c 2026-09-27 Pavel Safronov | 902 | *Rewritten for D-072.* The world is edited in **`content/world.json`**, not in the |
| 903 | browser: the author describes a change, Claude makes it in the file, and the app | |
| 904 | regenerates from it. There is no writer mode, no local draft and no export. With | |
| 905 | `npm run dev` running, saving the file reloads the open page; the published site | |
| 906 | changes when the commit is pushed. The app loads `world.json`, which `vite.config.ts` | |
| 907 | makes from the file with every `notes` emptied and unpublished features left out | |
| 908 | (design.md §3.1). | |
376f2ce6 2026-09-16 Pavel Safronov | 909 | |
9d5a061c 2026-09-27 Pavel Safronov | 910 | What each concept is authored as — the parameters a change touches: |
| 911 | ||
| 912 | | Concept | Shape | Parameters | | |
376f2ce6 2026-09-16 Pavel Safronov | 913 | |---|---|---| |
9d5a061c 2026-09-27 Pavel Safronov | 914 | | Mountains | polygon or band | peaks (name, position, elevation), generated-summit height and count, ruggedness, erosion, snowline | |
| 915 | | Lake | polygon (the shore) | level (auto or a number), depth, basin, shore, rim, wind | | |
| 916 | | River | polyline, source to mouth | flow, source, outlet (by id, or auto) | | |
e18dad04 2026-09-30 Pavel Safronov | 917 | | Sea | band or polygon | level, depth, shore, wind, ships, ice (the ice edge and where the fast ice begins) | |
9d5a061c 2026-09-27 Pavel Safronov | 918 | | Desert | polygon or band | uplift, edge, dunes, hills, plateaus, past water | |
| 919 | | Forest | polygon or band | uplift, edge, density, conifer share, meadows, treeline, giants | | |
abc0aad0 2026-10-01 Pavel Safronov | 920 | | Farmland | polygon or band | edge (D-095); the fields are laid out from where they are | |
9d5a061c 2026-09-27 Pavel Safronov | 921 | | Settlement | point | size, radius; a generated one is **adopted** by writing it in | |
| 922 | | Weather | the plan's events | target, kind, start, duration, wind, when published, revisions with reasons; the cycle | | |
| 923 | | Road / path | polyline | width | | |
| 924 | ||
| 925 | Each change goes: file → compile (only what the change touches, §5.2) → map and | |
| 926 | interior regenerate. A change that does not touch the ground — a name, a blurb, a | |
| 927 | note, a wind — regenerates nothing. | |
376f2ce6 2026-09-16 Pavel Safronov | 928 | |
| 929 | ### 10.1 The status dialog | |
| 930 | ||
| 931 | Whenever a compile runs longer than 400 ms — always on a first visit, and after | |
| 932 | editing a mountain range — a dialog covers the view with a progress bar and one line | |
| 933 | saying what is happening, in the manner of Terraria's world creation. Shorter edits | |
| 934 | show only the small indicator. | |
| 935 | ||
| 936 | **Every message is a real step.** A line appears when that step of §5.2 starts, and it | |
| 937 | names the actual feature, so the dialog is honest progress rather than decoration. | |
| 938 | The voice is **Tower's**, since Tower is the one making all of it (§0.1). The author | |
| 939 | approved these lines (2026-09-16): | |
| 940 | ||
| 941 | | Step | Message | | |
| 942 | |---|---| | |
| 943 | | validate | Reading the plans | | |
| 944 | | mountain uplift | Raising {Snowcrown} | | |
| 945 | | erosion | Letting rain find its way down {the Girdle} | | |
| 946 | | desert | Quoting the deserts of Earth in {the Dry Reach} | | |
| 947 | | desert washes | Cutting riverbeds for rivers that never ran | | |
| 948 | | lakes, seas | Filling {The Deep} | | |
| 949 | | river profiles | Teaching {The Long Water} which way is down | | |
| 950 | | forest | Planting {Greatwood} | | |
| 951 | | giants | Letting {the tall wood} grow as tall as a tree can | | |
| 952 | | settlements | Choosing where people will live | | |
| 953 | | city | Laying out the streets of {the Capital} | | |
| 954 | | routes | Walking the roads in | | |
| 955 | | weather | Publishing the weather | | |
| 956 | | coarse field | Drawing the map | | |
| 957 | ||
| 958 | Steps that take less than a few frames still show their line briefly, in order, so the | |
| 959 | sequence reads as the world being built up rather than as a flicker. | |
| 960 | ||
| 961 | --- | |
| 962 | ||
| 963 | ## 11. Existing decisions this affects | |
| 964 | ||
| 965 | These need an explicit yes from the author before building, since some of them were | |
| 966 | deliberate choices: | |
| 967 | ||
| 968 | | Decision | Effect | | |
| 969 | |---|---| | |
| 970 | | **D-006** Interior fidelity: simple ground, "no scenery meshes, no settlements, no vegetation" | **Reversed.** Trees, buildings and ships are exactly that. `design.md` §4 also lists "procedural scenery, settlements" as explicitly out of scope | | |
| 971 | | **D-019** Geography authored as data, generated by `npm run terrain`, with no dependencies | **Superseded.** Still data-first, but generated live, and five runtime dependencies are added | | |
| 972 | | D-020, D-051 (the world's belts, the Girdle) | **Kept as content**, ported into v2 features | | |
| 973 | | D-021 (terrain tool progress) | Retired with the tool; progress moves to the UI | | |
| 974 | | D-024, D-047 (x-ray raster on the hull) | Kept, fed by the live coarse field | | |
| 975 | | D-035 (CPU height readback) | Retired: the CPU calls the elevation function directly | | |
| 976 | | D-039, D-057 (raster height range, no mipmaps) | Retired with the rasters | | |
| 977 | | `design.md` §2.4 "water has no surface" | Reversed: water is geometry | | |
| 978 | | D-049 (rain not drawn) | Kept. Wind and waves become scheduled weather (§4.9); clouds and rain stay undrawn | | |
| 979 | | D-053 (the day is a front) | Kept, and the clock gains a day number for the weather plan | | |
| 980 | | `design.md` §1.11 "The cap seas are not designed" | **Superseded by §0.1.** The seas pool where gravity puts them, but every landform around them is now a choice Tower made | | |
| 981 | | `test/raster.test.ts` | Deleted; replaced by generation tests (§13) | | |
| 982 | ||
| 983 | --- | |
| 984 | ||
| 985 | ## 12. Author's answers, and what is still open | |
| 986 | ||
| 987 | ### 12.1 Answered (2026-09-16) | |
| 988 | ||
| 989 | | # | Question | Answer | Where it landed | | |
| 990 | |---|---|---|---| | |
| 991 | | 1 | "Visible past water" | Tower added these forms because it saw them on Earth and other planets and thought them appropriate — not because water was ever there | §0.1, §4.5 | | |
| 992 | | 2 | Ships and wind | Tower is in charge of the weather, publishes its plans so people can plan, and changes them when it wants — e.g. for a foolish sailor in a storm | §4.4, §4.9 | | |
| 993 | | 3 | Ring-sea storms | Up to Tower; it schedules them | §4.4, §4.9 | | |
| 994 | | 4 | Settlements | Generate them to fill out the world | §4.7 | | |
| 995 | | 5 | Tree scale | Real-world sizes, plus a tall forest of giants that reads as a landmark | §4.6 | | |
| 996 | | 6 | Compile wait | 3–8 s is acceptable behind a working dialog; Terraria-style status messages welcome | §8.3, §10.1 | | |
| 997 | | 7 | Phones | Left to the design: same world at full density, shorter draw distances | §8.4 | | |
| 998 | ||
| 999 | ### 12.2 Answered (2026-09-16, second round) | |
| 1000 | ||
| 1001 | | # | Question | Answer | Where it landed | | |
| 1002 | |---|---|---|---| | |
| 1003 | | 1 | Tower's calendar | Days count from 0, no names | §4.9 | | |
| 1004 | | 1a | Is the seed the same for everyone? | Yes — it lives in the world file (the author's question; this was already the design, now stated explicitly) | §5.5 | | |
| 1005 | | 2 | Generated settlement names | Named for their size: "Hamlet", "Village" | §4.7 | | |
| 1006 | | 3 | The tall forest | Placed at random near the capital, once, then fixed | §4.6 | | |
| 1007 | | 4 | Status dialog lines | Approved as drafted | §10.1 | | |
| 1008 | ||
| 1009 | Nothing is open. The design is ready for Phase 1. | |
| 1010 | ||
| 1011 | --- | |
| 1012 | ||
| 1013 | ## 13. Testing | |
| 1014 | ||
| 1015 | Generation is pure TypeScript and runs under vitest without a browser: | |
| 1016 | ||
| 1017 | - **Determinism:** fixed tiles generated twice are byte-identical, and a world | |
| 1018 | compiled in two separate workers from the same file produces identical caches. | |
| 1019 | - **Seams:** a tile's edge heights equal its neighbour's, at the same and at | |
| 1020 | different levels, including across θ = 0. | |
| 1021 | - **Mountains:** authored peaks within 1 m of their elevation and locally highest. | |
| 1022 | - **Rivers:** water surface monotone non-increasing from source to outlet; ends at | |
| 1023 | its outlet's level. | |
| 1024 | - **Water:** lake surfaces at constant radius; no lake level above its rim. | |
| 1025 | - **Forest:** no tree in water, on a path, in a clearing, or above the treeline. | |
| 1026 | - **Settlements:** no building overlaps a road or another building, or leaves the boundary. | |
| 1027 | - **Settlements:** generated sites respect minimum spacing; every settlement is | |
| 1028 | reachable from the capital by road. | |
| 1029 | - **Weather:** a revision overrides its event from its moment onward and not before; | |
| 1030 | no ship is at sea inside a published storm. | |
| 1031 | - **Routes:** grade never exceeds the maximum after grading. | |
| 1032 | - **Budgets:** one tile of each kind generates under its time budget on CI (loose). | |
| 1033 | - **Screenshots** (D-042): the tour gains views of each concept from the ground and | |
| 1034 | from altitude. | |
| 1035 | ||
| 1036 | --- | |
| 1037 | ||
| 1038 | ## 14. Phases | |
| 1039 | ||
| 1040 | Each phase ends with something visible and runnable, documentation current, and a | |
| 1041 | screenshot check before any commit. | |
| 1042 | ||
| 1043 | | # | Phase | Delivers | | |
| 1044 | |---|---|---| | |
| 1045 | | 1 ✅ | **Foundation** | schema v2 + port script; status dialog; elevation function with ground + today's landforms reimplemented; worker pool; quadtree tile renderer on the cylinder; surface splatting; ground query; keyboard flight; map preview from the coarse field. Delete `tools/terrain.mjs`, the PNGs, `raster.ts`. **Parity checkpoint:** today's world, without the bitmap | | |
e7a9f9dd 2026-09-17 Pavel Safronov | 1046 | | 2 ✅ | **Mountains** | ridge graph, erosion, peak editing | |
80bcc97a 2026-09-17 Pavel Safronov | 1047 | | 3 ✅ | **Water** | seas and lakes with surfaces and waves; rivers with profiles, flow and waterfalls | |
a088e5d5 2026-09-17 Pavel Safronov | 1048 | | 4 ✅ | **Weather and ships** | weather plan with revisions and the day clock; plan board in the panel; procedural hulls, routes, harbour waits, heave on the waves | |
51c39d8b 2026-09-17 Pavel Safronov | 1049 | | 5 ✅ | **Desert** | dunes, hills, terraced plateaus, washes, salt pans, shorelines | |
00ceeedb 2026-09-27 Pavel Safronov | 1050 | | 6 ✅ | **Forest** | density field, stands, meadows, the tall forest, three-distance tree rendering | |
376f2ce6 2026-09-16 Pavel Safronov | 1051 | | 7 | **Settlements, roads, paths** | one layout generator for every size, generated settlements and Adopt, routing, grading, buildings | |
| 1052 | | 8 | **Editing polish, performance, phones** | incremental compile everywhere, IndexedDB caches, phone tier, screenshot tour | | |
| 1053 | ||
| 1054 | Phase 1 is the largest and riskiest: it changes how every pixel of the ground is made. | |
| 1055 | Nothing after it is started until its parity checkpoint looks right. | |
| 1056 | ||
| 1057 | --- | |
| 1058 | ||
| 1059 | ## 15. Phase 1, as built (D-063) | |
| 1060 | ||
| 1061 | What the plan did not know until the world was generated honestly: | |
| 1062 | ||
| 1063 | - **The rivers cross a lowland they cannot climb out of.** Between the Girdle's foot | |
| 1064 | and the Dry Reach (z 115–135 km) v1's ground is about 60 m high, and the plateau | |
| 1065 | beyond it 800–1,100 m. v1 painted its rivers across at ~1,000 m. The first profile — | |
| 1066 | clamped to the ground — dropped every river to The Deep's 120 m and cut a canyon | |
| 1067 | 900 m deep through the whole desert; the second — a graded curve — needed a | |
| 1068 | 1,300 m embankment. The least-earthwork profile of §4.3 cuts The Narrows 300–500 m | |
| 1069 | deep, which is what design.md §1.11 always said, and builds the lowland up by up to | |
14d35b7d 2026-09-16 Pavel Safronov | 1070 | 880 m under each river. That is the world as authored, generated faithfully. |
| 1071 | **Resolved by the author (D-064):** raise the lowland. The Uplands, a region lifting | |
| 1072 | the ground to ~1,000 m, bring the worst embankment under 200 m; and the profile now | |
| 1073 | prefers cutting to filling (a 0.2-quantile isotonic fit rather than least squares), | |
| 1074 | since a cut reads as a valley and an embankment as a dam. | |
| 1075 | - **The hull was at the floor's radius.** Seen from outside, generated sea floors and | |
| 1076 | tile skirts came through it as a lattice. The skin now sits at the wall's outer face, | |
| 1077 | a kilometre out (D-044, D-064), and nothing generated may reach it. | |
376f2ce6 2026-09-16 Pavel Safronov | 1078 | - **Tiles were measured against the tallest mountain.** A fixed 4.5 km relief allowance |
| 1079 | made every tile within 5 km of the eye split to the finest level: 11,000 tiles on | |
| 1080 | screen. Each tile is now bounded by its own measured relief. | |
| 1081 | - **Coarse tiles cannot show a river.** A vertex of a 500 m tile landing in an 80 m | |
| 1082 | channel painted a 500 m blue diamond, in a dotted line down every river on the far | |
| 1083 | side of the world. Water now weighs by how much of a vertex's spacing it covers. | |
| 1084 | - **The x-ray hull was mirrored end to end, before any of this.** v1's hull UVs flipped | |
| 1085 | the axis, so the Girdle's snow sat at the far cap. It shows in the committed v1 | |
| 1086 | screenshot; fixed here. | |
| 1087 | - **Per-tile memory.** Normals travel as signed bytes, and every CPU copy of a tile is | |
| 1088 | dropped once it is on the GPU. | |
| 1089 | ||
e7a9f9dd 2026-09-17 Pavel Safronov | 1090 | ## 16. Phase 2, as built (D-065) |
| 1091 | ||
| 1092 | Mountains are `src/terrain/mountains.ts`. What changed from §4.1 on contact with the | |
| 1093 | real ranges: | |
| 1094 | ||
| 1095 | - **Overlapping ranges are one group.** The Girdle and the four massifs standing on it | |
| 1096 | are summits on one ring, triangulated, joined and eroded together; the three desert | |
| 1097 | ridges, which overlap each other, are a second group. Separately eroded, the massifs' | |
| 1098 | valleys would have met the ring's at a seam. | |
| 1099 | - **The ridges are blended, not maxed.** A hard maximum of cone-like flanks meets its | |
| 1100 | neighbour along a straight crease and the range reads as folded paper. The flanks are | |
| 1101 | combined by a soft maximum (the fourth root of the sum of fourth powers) and their | |
| 1102 | distances nudged by noise. | |
| 1103 | - **Flanks are broad.** A 4,300 m summit's roots reach 27 km and it still stands 2,000 m | |
| 1104 | high 12 km out — v1's massif size, and where its tarns and ski stations were. | |
| 1105 | - **Erosion is the steady state, not iterations.** Braun–Willett iterations over a | |
| 1106 | smooth uplift field cut almost nothing in a sensible number of passes, and what they | |
| 1107 | did cut ran in parallel grooves along the grid. The stream-power steady state can be | |
| 1108 | written down directly over a priority-flood drainage network — each cell above its | |
| 1109 | receiver by the slope that keeps pace with uplift — and eight passes, each re-finding | |
| 1110 | the network, give branching valleys and sharp divides (Cordonnier et al., 2016). Three | |
| 1111 | things were needed to stop the grid showing through: a surface roughened at valley | |
| 1112 | scales to start from, a fresh jitter on the flood each pass, and a **channel | |
| 1113 | threshold** of 1 km² with a little hillslope diffusion, below which ground is slope | |
| 1114 | rather than a one-cell gully. | |
| 1115 | - **The steady state is scaled back to the author's shape.** Its heights have their own | |
| 1116 | scale, so each cell is multiplied by the authored field over the steady state, both | |
| 1117 | blurred over 8 km. The Girdle stays 2,900 m and Snowcrown's massif 4,300 m; the ridges | |
| 1118 | and valleys inside them are the water's. `erosion` blends that with the uncut field. | |
| 1119 | - **Summits are the top, exactly.** Where anything within a few kilometres stands above | |
| 1120 | a named summit the ground around it is scaled down smoothly; the summit is pinned to its | |
| 1121 | elevation; a gentle cone trims the few cells pinning lifts past it; and it is pinned | |
| 1122 | again. Fine detail is kept off the summit. Authored summits are exact to under a metre | |
| 1123 | **above the datum** — the land under a summit is subtracted before it is pinned, where | |
| 1124 | v1 added its 4,300 m on top of whatever ground was there — and higher than everything | |
| 1125 | within half a kilometre (tested). | |
| 1126 | - **Tarns keep a shelf.** The steady state cut valleys through three of v1's four tarns. | |
| 1127 | A cirque is ground the rivers did not cut down, so each authored bowl lake keeps its | |
| 1128 | range at the uncut height around it. Sisters Tarn resolves to 2,173 m, beside the ski | |
| 1129 | station design.md puts at 2,100 m. | |
9d5a061c 2026-09-27 Pavel Safronov | 1130 | - **Editing.** *(The editor was removed by D-072; summits are now edited in the file.)* |
| 1131 | Summits are listed in the editor with name and elevation, added by clicking | |
e7a9f9dd 2026-09-17 Pavel Safronov | 1132 | the map and dragged to move; the range's generated-summit height, count, ruggedness, |
| 1133 | erosion and snowline are controls. Every change regenerates the range, and the service | |
| 1134 | now runs **one compile at a time**, collapsing edits made during it into the next: a | |
| 1135 | ten-step drag regenerated twice, not ten times. | |
| 1136 | - **Cost.** A compile is about 4 s in a browser worker for the whole world, nearly all of | |
| 1137 | it the Girdle's erosion; tiles sample the 200 m relief grid and add detail at a few | |
| 1138 | percent of the relief below it. | |
| 1139 | ||
376f2ce6 2026-09-16 Pavel Safronov | 1140 | Parity, against the committed v1 screenshots: the map matches in layout, colour and the |
| 1141 | desert's ragged edge; standing at The Narrows and looking up from Greatwood match | |
| 1142 | within a few metres of elevation. Cold load of the production build to a fully detailed | |
| 1143 | interior is 4.3 s on the author's laptop; a compile runs in about 120 ms and the map | |
| 1144 | draws in about 1.2 s. | |
80bcc97a 2026-09-17 Pavel Safronov | 1145 | |
| 1146 | ## 17. Phase 3, as built (D-066) | |
| 1147 | ||
| 1148 | - **Water is a mesh per tile**, on the tile's own grid: every wet vertex carries its | |
| 1149 | water's level, a dry vertex beside one borrows it, and the depth test draws the shore. | |
| 1150 | The ground under drawn water is bed. A river narrower than a tile's vertex spacing is | |
| 1151 | not drawn as a surface there, only as a tint on the ground in proportion to how much of | |
| 1152 | each cell it covers — a coarse tile's vertex landing in a channel drew a 500 m blue | |
| 1153 | diamond, and a line of them down every river. | |
| 1154 | - **Waves**: `water.ts` gives JONSWAP fetch-limited seas capped at Pierson–Moskowitz, and | |
| 1155 | the tests pin §4.2 and §4.4's printed figures. Lakes and seas have a `wind`; the editor | |
a088e5d5 2026-09-17 Pavel Safronov | 1156 | shows the sea it raises. Phase 4 made that wind the baseline under Tower's weather plan |
| 1157 | (§18). | |
80bcc97a 2026-09-17 Pavel Safronov | 1158 | - **Rivers**: speed by Manning, foam by grade. Ripples are advected downstream in the |
| 1159 | shader by a two-phase flow map. | |
| 1160 | - **Waterfalls come from the profile.** The least-earthwork fit pools reaches and steps | |
| 1161 | between pools, so a step of 12 m or more is a fall. A river coming off a range stepped | |
| 1162 | every 200 m for kilometres; consecutive steps are merged into one drop, with the reach | |
| 1163 | below taking the foot's level — which cuts a **gorge**. The valley's walls are shaped | |
| 1164 | from a separate *wall level* that ramps smoothly through the drop, so the gorge is a slot | |
| 1165 | inside an unbroken valley; embankments use the same ramp, or they ended in a cliff the | |
| 1166 | width of the embankment. Gorge walls stand at about 50°, wander with noise, and share the | |
| 1167 | valley's curvature, or they cut a second cliff kilometres out. | |
| 1168 | - **Sheets lean antispinward** by `canon/motion.ts`'s free-fall drift, tested. | |
| 1169 | - **Skirts reach past a tile's steepest step.** On a gorge wall a coarser neighbour's edge | |
| 1170 | can be off by far more than 4% of the tile, and the cracks showed as dark panels. | |
| 1171 | - **Rivers start and end at the water's edge.** v1 drew the Long Water from the centre of | |
| 1172 | Hollow Tarn, and kept whole its channel was cut across the lake floor, its water a stripe | |
| 1173 | across the tarn (reported by the author). A river is now trimmed where it meets a lake — | |
| 1174 | found by sampling the water, since a tarn's water fills only about the inner 70% of its | |
| 1175 | outline, and a trim to the outline left the river stopping short of it. | |
| 1176 | - **Narrow rivers stay visible at a distance.** Where a tile is too coarse to draw a river | |
| 1177 | as a surface it tints the ground by the square root of the share of each cell the river | |
| 1178 | covers: by the plain share, a 40 m river in a 200 m cell was a 20% tint, and vanished | |
| 1179 | where tiles coarsened. | |
| 1180 | - **The beam is drawn after water**, or the far side's seas blended over it and it went | |
| 1181 | fuzzy where it crossed them. Reported by the author. | |
| 1182 | - **The compiled world is cached** in IndexedDB (§5.4), keyed by the world's terrain | |
| 1183 | signature and the worker bundle's URL — hashed in a build, so a new build never reads an | |
| 1184 | old world. The screenshot tour compiles once and gives every shot the warm cache, and | |
| 1185 | caps each page at two workers: it had been compiling the world fifteen times, four at a | |
| 1186 | time, with a full pool each, and the author's laptop ran hot for it. | |
| 1187 | - **Cost**: 99–112 fps with water in view, 2.1–2.6 M triangles, on the author's laptop. | |
a088e5d5 2026-09-17 Pavel Safronov | 1188 | |
| 1189 | --- | |
| 1190 | ||
| 1191 | ## 18. Phase 4, as built (D-067) | |
| 1192 | ||
| 1193 | - **The clock has a day.** The interior's clock is `day + time of day`, from day 0; the | |
| 1194 | panel has a Day field, running the clock turns the calendar over at midnight, and the | |
| 1195 | day and time are remembered across reloads. World time in hours is the one input the | |
| 1196 | weather and the ships need. | |
| 1197 | - **The weather plan is data in the world file** (`weather: { cycleDays, events }`), and | |
| 1198 | `world/weather.ts` reads it as pure functions of the plan and the hour: | |
| 1199 | - **What Tower does** (`actualAt`): the event in force, as last revised *so far* — a | |
| 1200 | revision counts from the moment it was made, and only within the occurrence it was | |
| 1201 | made in. The wind rises over the event's first hour and falls over its last. | |
| 1202 | - **What people were told** (`inHarbour`): the event as published, revisions ignored. | |
| 1203 | Ships stay in harbour from three hours before a published gale or storm until it ends | |
| 1204 | — including a storm Tower has quietly called off. That is the point of keeping both. | |
| 1205 | A blow published with less than three hours' warning catches the fleet at sea, and it | |
| 1206 | rides the weather out (changed in D-068; the default plan has one such squall). | |
| 1207 | - **A repeating plan**: `cycleDays` repeats every event (28 in the default plan), so the | |
| 1208 | seas stay eventful without authoring a year of storms. | |
| 1209 | - **The sea lags the wind** (`seaWindAt`): waves answer an exponentially weighted memory | |
| 1210 | of the last six hours of wind (2 h e-folding), so a storm builds and a sea runs on | |
| 1211 | after it. | |
| 1212 | - **Waves are no longer compiled.** Phase 3 baked each body's sea state into the compiled | |
| 1213 | world, which would have meant recompiling for every gust. Now a water vertex carries only | |
| 1214 | its body's index, `terrain/sea-state.ts` computes every body's waves from the weather a | |
| 1215 | few times a second, and the shader reads them from a small table. Wind and ship count are | |
| 1216 | out of the terrain signature, so editing them no longer regenerates anything. | |
| 1217 | - **Whitecaps** cover the share of the sea Monahan and O'Muircheartaigh's fit gives for | |
| 1218 | the wind, `3.84e-6 · U^3.41` — about 2% at 9 m/s, 9% at 20, 29% at 28. They break on | |
| 1219 | two new short components (the wind's own sea on top of the swell); keyed to the whole | |
| 1220 | sum they lined up along a storm's 600 m swell crests and left the troughs bare. | |
| 1221 | - **A precision fix, found through the storm.** The water shader measured position around | |
| 1222 | the ring as `atan(y, x) × 50 km`, which in float32 is good to about 6 m: anything finer | |
| 1223 | — whitecap noise, short waves, river ripples — smeared into streaks radiating from the | |
| 1224 | eye. Surface coordinates are now measured from a reference point near the eye that moves | |
| 1225 | in whole 2,457.6 m steps (256 cells of every noise scale used), and each wave adds back | |
| 1226 | its phase at the reference, computed in float64. Distant long waves also fade out | |
| 1227 | earlier (from 16 pixels of wavelength rather than 8), which removes a moiré of stripes a | |
| 1228 | storm swell drew near the horizon. | |
| 1229 | - **Ships** (`terrain/ships.ts`): each ring sea has `ships` lanes (default 90), seeded from | |
| 1230 | the world and the sea. A ship's position is a pure function of the hour: a steady 3–6 m/s | |
| 1231 | round the ring, weaving gently in z, kept 4 km off either shore. Lengths run 8–45 m, | |
| 1232 | mostly small. The hull, mainsail, jib and mast are procedural and instanced — three draw | |
| 1233 | calls for every ship in the world; ships are drawn within 40 km. Each ship rides the same | |
| 1234 | wave sum the shader draws (a TypeScript port, tested against its own derivative), heaving | |
| 1235 | and pitching less the longer it is against the waves. The mainsail swings out running | |
e18dad04 2026-09-30 Pavel Safronov | 1236 | before the wind and in toward the centreline heading into it. On a sea with ice the |
| 1237 | lanes keep to the open water, 2 km short of the ice edge (D-091). | |
| 1238 | - **Sea ice** (`terrain/ice.ts`, D-090, D-091): a sea's `ice` gives its ice edge and where | |
| 1239 | its fast ice begins, as z; the fast ice runs on from there to the sea's end. Each water | |
| 1240 | sample carries its ice cover, 0..1 — the pack from 0.3 at its edge to 0.95 at the fast | |
| 1241 | ice, 1 on the fast ice — with the edge wandering 1.5 km and the fast ice's 400 m, by | |
| 1242 | noise. The water shader breaks the pack into 110 m Voronoi floes, each ice or open by | |
| 1243 | its own hash against the cover, with leads between and cracks where two floes meet; the | |
| 1244 | fast ice is one sheet cracked in 660 m cells. Ice stills the waves under it. The map | |
| 1245 | whitens the water by the cover. | |
abc0aad0 2026-10-01 Pavel Safronov | 1246 | - **Farmland** (`terrain/farms.ts`, D-095, D-097). A farm given as a point grows at compile |
| 1247 | time over a 200 m lattice: from its seed, always taking the frontier patch with the lowest | |
| 1248 | key — half its distance from the seed over the farm's radius, half a hash — and never a patch | |
| 1249 | that is wet, sand, rock, a range (relief over 150 m), ice, or steeper than 12%, until it has | |
| 1250 | its `areaKm2`. The fields are a Voronoi of jittered **blocks** ~1.5 km across (a whole | |
| 1251 | number round the ring) with 6 m tracks on their edges; in each block, a second Voronoi in | |
| 1252 | the block's own frame — its own angle, field size 220–440 m and stretch 1.3–2.2 — measured | |
| 1253 | from the block's centre so float32 holds; a third of those fields are cut into 2–4 strips. | |
| 1254 | Each field takes a crop from a weighted table by hash; its rows run along its length. A | |
| 1255 | field belongs to a grown farm if the patch under the point it was laid out from does. The | |
| 1256 | same layout is written as GLSL, so the map and the views agree field by field. A sample | |
| 1257 | carries only how much farmland it is, which clears forest; the ground shader draws the crop | |
| 1258 | — rows over soil, bloom among the leaves, averaged once a row is under a pixel — and lights | |
| 1259 | each field on its crop's own day from a uniform carrying the hours since sunrise. | |
e18dad04 2026-09-30 Pavel Safronov | 1260 | - **Ice mountains** (`field.ts glacier`, D-093): a sea's `ice.range` raises rock from the sea |
| 1261 | bed at `fromM` to `heightM` at the sea's end, steepening toward it (height ∝ t^1.6), ±30% | |
| 1262 | round the ring and ridged by up to 0.7 of its height. Ice mantles it from where it leaves | |
| 1263 | the water, as an ice sheet's plastic profile (τ = 150 kPa) to 350 m, draping 60% of the | |
| 1264 | ridges; where a ridge stands out of the ice it is bare rock. Grounded ice is white however | |
| 1265 | steep. The far cap's shader buries the glass's foot in ice to the height the ground stands | |
| 1266 | at the cap, read from the terrain round the ring into a 2,048-sample strip. | |
a088e5d5 2026-09-17 Pavel Safronov | 1267 | - **Not built yet:** tacking (a ship on a lane into the wind sails straight into it, sail |
| 1268 | sheeted in), harbours (a ship in harbour is simply not on the water), ships on polygon | |
| 1269 | seas and lakes, and weather over deserts (Phase 5). | |
| 1270 | - **The weather board** is a panel section in every view: upcoming occurrences for the | |
| 1271 | next fortnight, the one in force highlighted, revisions listed under their event with the | |
| 1272 | original struck through. Readers see only what has been published. Writers can schedule | |
| 1273 | an event (published the moment it is added), revise one from now with a reason, delete | |
9d5a061c 2026-09-27 Pavel Safronov | 1274 | one, and set the cycle *(removed by D-072: the plan is edited in the file, and the board |
| 1275 | shows only what has been published, to everyone)*. A selected lake or sea's readout shows its waves *now*. | |
a088e5d5 2026-09-17 Pavel Safronov | 1276 | |
| 1277 | --- | |
| 1278 | ||
99a2821b 2026-09-29 Pavel Safronov | 1279 | ## 19. The scenic tour, as built (D-068, rebuilt by D-085) |
| 1280 | ||
| 1281 | D-068 added a tour of nine thirty-second scenes, once the world had particular things in it | |
| 1282 | worth finding. D-085 replaced it, at the author's request, with the world's own flyers: | |
| 1283 | automatic flight, inside and out, a new craft every twelve to eighteen seconds, in an order | |
ec1a6075 2026-09-30 Pavel Safronov | 1284 | dealt at random, with the camera pointed at whatever is worth seeing. The caption's border |
| 1285 | fills in clockwise as a leg runs and closes as it fades to the next (D-094). | |
99a2821b 2026-09-29 Pavel Safronov | 1286 | |
| 1287 | - **A leg is a craft on a path, and a camera on the craft** (`tour/interior-legs.ts`, | |
| 1288 | `tour/exterior-legs.ts`). Twenty-six legs inside and nine outside, each finding its | |
| 1289 | subject in the compiled world — the tallest fall, the highest summit, the biggest ship at | |
| 1290 | sea, a squall Tower gave no warning of, the broadest stand of mesas, the tallest giant, the | |
| 1291 | sun where the clock has it — so they follow the world as it is edited, and a leg whose | |
| 1292 | subject is gone drops out. The craft exists for its leg (D-085); only the ships are the | |
| 1293 | world's own, followed where they are. | |
| 1294 | - **The craft**: paragliders, gliders, light aircraft and seaplanes, helicopters, airliners, | |
| 1295 | airships, giant airships near the axis, trains, and outside, spacecraft (`views/craft.ts`). | |
| 1296 | A winged craft banks into its turns by the lift the turn needs against the local gravity, | |
| 1297 | so the same turn banks hard near the axis and barely at the floor; airships, ships and | |
| 1298 | trains keep level. | |
| 1299 | - **Two rigs.** In the craft, looking where the director says, the horizon tilting with the | |
| 1300 | bank when looking ahead; or chasing it, on the far side of the craft from what the | |
| 1301 | director is looking at, so the two share the frame, walked slowly round it over the leg. | |
| 1302 | A chase camera is never under 15 m over the ground. | |
| 1303 | - **The director** (`tour/director.ts`) scores every point of interest from the craft, | |
| 1304 | twice a second: weight × transmission² × apparent size × how far off the nose, with the | |
| 1305 | transmission integrated through the density profile as the renderer's haze is, and the | |
| 1306 | sightline sampled against the ground for anything a hill could hide. The leg's subject is | |
| 1307 | boosted fourfold. A subject holds the camera at least 4.5 s, bores it after 7, and a turn | |
| 1308 | to the next is eased over 3 s and never swings faster than 0.7 rad/s. It is all worked out | |
| 1309 | when a leg is first played, so a pose is a pure function of time. | |
| 1310 | - **Flights hold a steady clearance** over the highest ground within 700 m, smoothed, as | |
| 1311 | D-068's did. Aim is taken in the tower's frame, where a straight line is straight: 6 km | |
| 1312 | around, the floor has risen 360 m in the eye's frame. | |
| 1313 | - **Each leg sets the clock** and runs it at its own rate — real time, or a labelled | |
| 1314 | time-lapse for the slow giants. The observer's own clock comes back when the tour stops; | |
| 1315 | where they are does not, so stopping leaves them at the view they liked. Outside, the hull | |
| 1316 | turns at the leg's rate whatever the Speed slider says. | |
| 1317 | - **Between legs the view fades to black and waits for the ground** — up to four seconds — | |
| 1318 | so a leg opens on its terrain rather than on the coarse tiles of the moment | |
| 1319 | (`views/tour-player.ts`). | |
| 1320 | - **The deal** (`tour/plan.ts`): every leg once before any twice, no two of a kind of craft | |
| 1321 | in a row, two legs about the same subject kept apart where they can be, and a new deck never | |
| 1322 | opening on the craft the last one closed on. | |
| 1323 | - **Controls**: *Scenic tour* in the interior's panel, and in the exterior's, starts and stops | |
| 1324 | it; **N** skips to the next leg; **Esc**, a drag, the wheel or a flight key stops it where | |
42b4554e 2026-09-29 Pavel Safronov | 1325 | it is, and so does switching to another view — a tour belongs to its view, and one left |
| 1326 | running behind another kept its caption on screen under the new one's. | |
99a2821b 2026-09-29 Pavel Safronov | 1327 | - **The screenshot tour photographs the legs**, each held at one moment |
| 1328 | (`tower.showScene(id, t)` inside, `towerOutside.showScene(id, t)` outside) and planned | |
| 1329 | from a fixed seed, so a leg is the same flight every run. A test checks that every leg the | |
| 1330 | screenshot tool names exists, and flies every leg over the real world, across twenty | |
| 1331 | seeds while it was built: cameras stay in the world, above the ground and out of the beam, | |
| 1332 | move and turn smoothly, keep a chased craft in the frame and bring each leg's subject into | |
| 1333 | the middle of it; outside, nothing touches the hull or the beam's cone, and the grand shot | |
| 1334 | keeps the sun clear of the collector. | |
51c39d8b 2026-09-17 Pavel Safronov | 1335 | |
| 1336 | --- | |
| 1337 | ||
| 1338 | ## 20. Phase 5, as built (D-069) | |
| 1339 | ||
| 1340 | - **A desert is a grid and a pile of noise.** `terrain/desert.ts` compiles what a tile | |
| 1341 | cannot work out alone — the plateau field, the wash network, the pans and their | |
| 1342 | shorelines — on a 250 m grid over the desert (352,000 cells for the Dry Reach, about a | |
| 1343 | second). Dunes and rocky hills are noise, and the tiles make them (field.ts). | |
| 1344 | - **The grid machinery is now shared.** `terrain/grid.ts` holds the grid, the priority | |
| 1345 | flood, the box blur, the neighbourhood walk and bilinear sampling, which the ranges and | |
| 1346 | the deserts both use; the ranges' copies moved there unchanged. | |
| 1347 | - **Terraces are cut as the tile samples, not in the compile.** The compile stores how far | |
| 1348 | up the plateau each cell stands, smooth; `terrace()` turns that into flat tops and steep | |
| 1349 | risers at whatever detail the tile has. Terraced in the compile, every cliff would be as | |
| 1350 | wide as a grid cell — a 250 m ramp instead of a cliff band. | |
| 1351 | - **The plateau threshold is measured, not guessed.** The authored coverage is turned into | |
| 1352 | a threshold by taking that quantile of the noise as sampled over this desert, and the | |
| 1353 | same for the dune fields. Thresholds picked against an assumed spread put mesas over | |
| 1354 | everything or nowhere — which is exactly what the first attempt did. | |
| 1355 | - **The washes are routed, not drawn.** The same priority flood the ranges erode with runs | |
| 1356 | over the desert's own surface, and where a cell would drain more than 15 km² it is cut | |
| 1357 | by 4 m and more, to tens of metres on the trunks. Two things this taught: | |
| 1358 | - A flat desert has no drainage at all. The first version fanned out from every edge and | |
| 1359 | gathered nothing, so **Tower crowns its deserts**: the middle of the Reach stands 150 m | |
| 1360 | above its rim, and everything runs off it. | |
| 1361 | - Widening a one-cell channel by blurring it averages the channel away. The wash is | |
| 1362 | **dilated** to its full depth over a couple of cells first, and blurred afterwards for | |
| 1363 | the banks. | |
| 1364 | - **Salt pans are set pieces in the low ground** (§4.5 allows exactly this): the lowest | |
| 1365 | cells, spaced apart, each a disc flattened to one level with a crust that frays at the | |
| 1366 | rim, and benches stepped every 20 m around it — the shorelines of a lake that never | |
| 1367 | stood. Both are **levels in the world**, not in the sand: the shelf under the desert | |
| 1368 | rolls by a hundred metres, and a floor that followed it would not be a floor. | |
| 1369 | - **Tower keeps its rock off its rivers.** A mesa field dropped across The Narrows left | |
| 1370 | the whole world's drainage cutting a kilometre-deep slot through it. Rivers now carry a | |
| 1371 | 6.5 km corridor the plateaus keep out of, which is also what a river crossing a mesa | |
| 1372 | field looks like. Beside the lower Long Water the desert's rock still stands over the | |
| 1373 | river, and that section of canyon now runs about 780 m rather than 480. | |
| 1374 | - **Rock is a material, and so is salt.** Materials gained a ninth weight, `salt`, and | |
| 1375 | bare rock is now claimed by the desert's plateaus, its gravel wash floors, and **any | |
| 1376 | steep desert face** — sand does not stand at 60°. The ground shader lays **strata** by | |
| 1377 | elevation, so the bands line up across separate mesas however far apart they stand, | |
| 1378 | which is what makes a field of them read as one eroded formation. | |
9d5a061c 2026-09-27 Pavel Safronov | 1379 | - **The panel edits all of it** *(until D-072; now the file does, and the detail card |
| 1380 | keeps the readout)*: dune height, spacing, direction and coverage; hills; | |
51c39d8b 2026-09-17 Pavel Safronov | 1381 | plateau height, coverage and terraces; washes, pans and shorelines — with a readout of |
| 1382 | what was actually made ("24% standing rock, 37% dune field, 4 salt pans"). | |
| 1383 | - **The tour gained two scenes** (§19): past the mesas from the sand, and over a salt pan | |
| 1384 | ringed by its shorelines. | |
| 1385 | - **Cost**: the whole world compiles in about 5 s, of which the Dry Reach is about 1 s. | |
00ceeedb 2026-09-27 Pavel Safronov | 1386 | |
| 1387 | --- | |
| 1388 | ||
| 1389 | ## 21. Phase 6, as built (D-071) | |
| 1390 | ||
| 1391 | - **A forest is a density field and a placement rule** (`terrain/forest.ts`). Stems per | |
| 1392 | hectare come from what Tower planted, thinned toward the forest's edge, opened where it | |
| 1393 | left a meadow, and stopped by water, rock, sand and the treeline. The meadow threshold | |
| 1394 | *and the width of a meadow's edge* are quantiles measured over the forest itself: a band | |
| 1395 | of ±0.02 in noise units left Greatwood as scattered islands, because its noise only | |
| 1396 | varies by about that much in total. | |
| 1397 | - **Placement is stratified, as planned.** A fixed global grid about one tree apart; each | |
| 1398 | cell hashes its own coordinates for whether it holds a tree, where in the cell, and what | |
| 1399 | it is, accepted in proportion to the density there. So **any tile, any view and any | |
| 1400 | visitor works out the same trees with no knowledge of the others**, and walking away and | |
| 1401 | back puts every tree back where it was. Tested. | |
| 1402 | - **Four ranges, not the three the plan drew** — because at three the wood visibly ended a | |
| 1403 | few hundred metres off, and that edge walked along with you (reported by the author): | |
| 1404 | - **to ~210 m — meshes.** A conifer is a tapered trunk and three cones; a broadleaf is a | |
| 1405 | trunk and three lumps; a giant is a column with its crown in the top half. 40–70 | |
| 1406 | triangles each, instanced, 6,000 of them. | |
| 1407 | - **to ~560 m — cards.** One quad each, turned to face the eye about the tree's own | |
| 1408 | upright, with the silhouette cut out in the fragment shader: a cone, a round crown or a | |
| 1409 | column. No texture — at that distance a tree is a few pixels and its outline is all of | |
| 1410 | it that survives. About 39,000. | |
| 1411 | - **to 2.8 km — clumps.** One card for everything in a 30 m cell, drawn as three crowns | |
| 1412 | over a band: a card per tree would be a quarter of a million at a kilometre and a | |
| 1413 | million at two, and a clump every thirty metres reaches nearly three kilometres for | |
| 1414 | 26,000. At that distance a clump is a thumbnail held at arm's length, which is what a | |
| 1415 | group of crowns looks like from there. | |
| 1416 | - **under all of it, from 620 m out — the canopy skin.** Each tile carries a second | |
| 1417 | surface: its own ground vertices, lifted by the canopy's height with a rough top, | |
| 1418 | sinking back onto the ground where the trees themselves take over. It is what closes the | |
| 1419 | wood behind the clumps — a field of cards seen from above is all gaps. | |
| 1420 | - **The skin is opened up from below.** Seen from inside the wood it was a black ceiling | |
| 1421 | over everything past the last drawn tree. A canopy is not solid — the gaps between crowns | |
| 1422 | are what let you see any distance through a forest — so from underneath it drops about | |
| 1423 | half its fragments on a pattern fixed to the ground, and what is left is lit as light that | |
| 1424 | has come *through* the canopy rather than off it. | |
| 1425 | - **Meadows are grass.** The forest-floor colour now follows the stems, so a clearing | |
| 1426 | inside a wood is the plain ramp and reads as open grass, which is what makes the meadows | |
| 1427 | visible from the air at all. | |
| 1428 | - **The Tall Wood exists** (§4.6): 52 km² of giants, 90–115 m, drawn once from the world's | |
| 1429 | seed — 17 km from the Capital, well inside Greatwood, clear of the water and the rivers — | |
| 1430 | and written into the world file, where the author can move it like anything else. Its | |
| 1431 | crowns are narrow for their height, as a redwood's are: at the planned 0.34 of height the | |
| 1432 | grove closed over into unbroken dark, so a giant carries 0.18 and stands 18 m from its | |
| 1433 | neighbours. **Greatwood gives up its ground where a smaller forest stands**, so the | |
| 1434 | giants stand *instead of* the ordinary wood rather than mixed through it. | |
| 1435 | - **Cost**: 130–160 fps standing in Greatwood, and a rebuild is about 19 ms for 71,000 | |
| 1436 | trees. Three things bought that: the placement reads the density off the grid the view | |
| 1437 | already samples rather than working the noise out per candidate tree; the clump layer, | |
| 1438 | two kilometres out, is left alone until the eye has moved 170 m; and a box is cut to its | |
| 1439 | circle before the ground is looked at. In flight the near budget drops to a quarter, and | |
| 1440 | is restored in full the moment the eye settles — arriving somewhere used to leave the wood | |
| 1441 | thin until you walked thirty metres. | |
| 1442 | - **The panel** sets stems per hectare, the conifer share, the meadows, the treeline and | |
| 1443 | whether a wood is giants, and says what that comes to: "3.1 billion trees over 67,858 km², | |
| 1444 | about 4.7 m apart, canopy 28 m." | |
| 1445 | - **Two more tour scenes**: under one of the giants, and low over Greatwood's canopy. | |
| 1446 | - **A settlement clears its ground.** The Capital stands at z 270 km, in the middle of | |
| 1447 | Greatwood's belt, and was standing in a wood; a forest now stops at any settlement's | |
| 1448 | footprint and thins over its last fifth. Until phase 7 fills that footprint the Capital is | |
| 1449 | 20 km of grass, which is at least the right kind of nothing. | |
| 1450 | - **Not built:** authored stands (a polygon with its own density and species), paths | |
| 1451 | through the woods, and trees on the map. Stands and paths belong with phase 7's router. |