tower / commit
feat(interior): night, and the difference between banked and off
decisions.md | 23 +++++++++++++++++++++++ design.md | 23 +++++++++++++++++++++++ index.html | 2 ++ src/canon/constants.ts | 8 ++++++++ src/views/interior.ts | 33 ++++++++++++++++++++++++++------- test/canon.test.ts | 4 ++++ 6 files changed, 86 insertions(+), 7 deletions(-) diff --git a/decisions.md b/decisions.md index 24cc875..7eaba1e 100644 --- a/decisions.md +++ b/decisions.md @@ -1318,3 +1318,26 @@ diameter, so at the old 120 km minimum distance the collar was two pixels and th near plane sat at 8 km — the depth was being drawn correctly and could not be got close enough to to see. 60 km and a 2 km near plane make the choice legible; without that change the constant would have been a number in a file and nothing else. + +## D-046 — Night is the beam banked, not the swarm aimed away + +The obvious way to make night in a habitat lit by a steerable mirror swarm is to +aim the swarm off. It does not work here, and §1.3's own numbers say so. + +The beam glows **by itself**: §1.3 gives the glow to the beam and the air rather +than to the diffusers, and says outright it "would be there if the beam were empty". +779 W/m²/sr over a strip 3.65° wide running horizon to horizon is about 0.2 sr, and +with the mean cosine of a strip through the zenith that is **~100 W/m² on the +floor** — half of temperate. A lit beam is a light source no matter where the +mirrorlets are pointed, so a dark world requires a dark beam. There is a test for +this figure in `test/canon.test.ts`, because the conclusion rests entirely on it. + +So **night is the beam banked to a thousandth of full, 0.32 W/m²**: a rod you can +still find overhead and a world you can just make out under it. And **dark is the +beam shut down** — no airlight, no lit ground, no rod, and the only thing left in +the world is the sky through the far cap (D-045). The two settings are genuinely +different states rather than two points on a dimmer, which is what made both worth +having. + +The beam's brightness scales with the exposure already, so it banks and goes out on +its own; neither setting needed the beam shader touched. diff --git a/design.md b/design.md index 7523ded..2f2d035 100644 --- a/design.md +++ b/design.md @@ -169,6 +169,29 @@ system as a lighting system. 5,000× was chosen because it is *demonstrated* technology — the Odeillo solar furnace reaches ~10 MW/m² on Earth today. Nothing in Tower's sun system requires physics that does not exist. See `decisions.md` D-003. +#### 1.3.2 Night, and the difference between banked and off + +**Night cannot be made by aiming the swarm away.** The glow above belongs to the +beam and the air, not to the diffusers, so a fully lit beam lights the world +whatever the mirrorlets are doing: 779 W/m²/sr over a strip 3.65° wide running +horizon to horizon is about 0.2 sr, which lands **~100 W/m² on the floor** — half +of temperate, and no kind of night. Tower's night is therefore made by **banking +the beam itself**, to a thousandth of full: **0.32 W/m²**, a rod you can still find +overhead and a world you can just make out under it (D-046). + +**Shutting the beam down entirely is a different state and a much worse one.** No +airlight, no lit ground, no rod — the only thing left in the world is the sky +through the far cap. §1.9.4 gives the thermal mass about a week of coasting, so +this is a state Tower can survive and not one it can sit in. + +| Setting | Floor | What it is | +|---|---|---| +| Dark | **0** | the beam shut down. Stars, and nothing else. | +| Night | **0.32 W/m²** | the beam banked to a thousandth | +| Twilight | 30 W/m² | | +| Temperate | 200 W/m² | | +| Full | 320 W/m² | Earth clear-sky noon | + ### 1.4 Sealed hull, and the cap windows **The cylinder wall is opaque and unbroken.** No slats, no windows, no view out diff --git a/index.html b/index.html index 97677f9..a7db194 100644 --- a/index.html +++ b/index.html @@ -54,6 +54,8 @@ <input id="f-eye" type="range" min="0" max="1000" step="1" value="0" /></label> <label>Light <select id="f-light"> + <option value="dark">Dark — beam shut down</option> + <option value="night">Night — 0.3 W/m²</option> <option value="twilight">Twilight — 30 W/m²</option> <option value="temperate" selected>Temperate — 200 W/m²</option> <option value="full">Full — 319 W/m²</option> diff --git a/src/canon/constants.ts b/src/canon/constants.ts index 0cca227..4aa34b5 100644 --- a/src/canon/constants.ts +++ b/src/canon/constants.ts @@ -83,6 +83,14 @@ export const AIM_SPOT_AT_FLOOR_M = MIRRORLET_DIFFRACTION_RAD * RADIUS_M export const BRIGHTNESS_TWILIGHT_W_M2 = 30 export const BRIGHTNESS_TEMPERATE_W_M2 = 200 export const BRIGHTNESS_FULL_W_M2 = BEAM_POWER_W / INNER_AREA_M2 +/** + * Night is made by banking the beam, not by aiming the swarm away — §1.3 says the + * glow belongs to the beam and the air, so a lit beam lights the world whatever + * the diffusers do. A thousandth of full leaves a rod you can still find overhead + * and a world you can just make out under it. D-046. + */ +export const NIGHT_BEAM_FRACTION = 0.001 +export const BRIGHTNESS_NIGHT_W_M2 = BRIGHTNESS_FULL_W_M2 * NIGHT_BEAM_FRACTION // --------------------------------------------------------- structure (design.md §1.1) /** diff --git a/src/views/interior.ts b/src/views/interior.ts index fba9095..0fbafc4 100644 --- a/src/views/interior.ts +++ b/src/views/interior.ts @@ -26,7 +26,7 @@ import { } from 'three' import { BEAM_RADIUS_M, BRIGHTNESS_FULL_W_M2, BRIGHTNESS_TEMPERATE_W_M2, - BRIGHTNESS_TWILIGHT_W_M2, G0, HULL_DEPTH_M, LENGTH_M, OMEGA_RAD_S, RADIUS_M, + BRIGHTNESS_NIGHT_W_M2, BRIGHTNESS_TWILIGHT_W_M2, G0, HULL_DEPTH_M, LENGTH_M, OMEGA_RAD_S, RADIUS_M, RAYLEIGH_FLOOR_PER_M, SPECIFIC_GAS_CONSTANT, TEMPERATURE_K, gravityAtHeight, } from '../canon/constants.js' @@ -82,12 +82,26 @@ const RAIN_STREAK_KM = 1.1 / KM const SEG_AROUND = 512 const SEG_ALONG = 768 +/** + * The five settings, darkest first. + * + * `dark` is the beam shut down entirely and is the only one that is genuinely + * black — with 54 TW off there is no airlight, no lit ground and no rod overhead, + * and the only thing left in the world is the sky through the far cap. `night` is + * the beam banked rather than killed, which is what night has to be here: §1.3 + * gives the glow to the beam and the air rather than to the diffusers, so aiming + * the swarm away would not darken anything (D-046). + */ export const LIGHTING = { + dark: 0, + night: BRIGHTNESS_NIGHT_W_M2, twilight: BRIGHTNESS_TWILIGHT_W_M2, temperate: BRIGHTNESS_TEMPERATE_W_M2, full: BRIGHTNESS_FULL_W_M2, } as const export type Lighting = keyof typeof LIGHTING +export const isLighting = (v: unknown): v is Lighting => + typeof v === 'string' && v in LIGHTING const STATE_KEY = 'tower.interior' const STATE_SAVE_MS = 400 @@ -695,11 +709,16 @@ export class InteriorView { * exponent compresses the canon watts into something a screen can show while * keeping the ordering and the ratios honest. */ - const e = (LIGHTING[this.lighting] / BRIGHTNESS_TEMPERATE_W_M2) ** 0.35 - for (const m of this.shared) m.uniforms['uExposure']!.value = e - const haze = new Color(this.lighting === 'twilight' ? HAZE_DUSK : HAZE_DAY) - for (const m of this.shared) (m.uniforms['uHaze']!.value as Color).copy(haze) - this.renderer.setClearColor(haze, 1) + const watts = LIGHTING[this.lighting] + const e = (watts / BRIGHTNESS_TEMPERATE_W_M2) ** 0.35 + const haze = new Color(watts <= BRIGHTNESS_TWILIGHT_W_M2 ? HAZE_DUSK : HAZE_DAY) + for (const m of this.shared) { + m.uniforms['uExposure']!.value = e + ;(m.uniforms['uHaze']!.value as Color).copy(haze) + } + // Scaled, so `dark` clears to black rather than to a dusk-coloured sky that + // nothing in the scene is bright enough to justify. + this.renderer.setClearColor(haze.clone().multiplyScalar(e), 1) } setLighting(l: Lighting): void { @@ -853,7 +872,7 @@ export class InteriorView { this.yaw = num(s['yaw'], -100, 100) ?? this.yaw this.pitch = num(s['pitch'], -1.55, 1.55) ?? this.pitch this.fovDeg = num(s['fov'], 3, 90) ?? this.fovDeg - if (s['lighting'] === 'twilight' || s['lighting'] === 'temperate' || s['lighting'] === 'full') { + if (isLighting(s['lighting'])) { this.lighting = s['lighting'] } this.showRain = s['rain'] === true diff --git a/test/canon.test.ts b/test/canon.test.ts index 78ec12a..a6b3ec3 100644 --- a/test/canon.test.ts +++ b/test/canon.test.ts @@ -118,6 +118,10 @@ describe('§1.4 slat count is adjustable from 0 to 16', () => { describe('§1.3 sun system', () => { it('beam radius is 1592 m', () => near(C.BEAM_RADIUS_M, 1592, 1)) it('beam carries 54.3 TW', () => near(C.BEAM_POWER_W / 1e12, 54.3, 0.2)) + it('night is the beam banked to a thousandth, 0.32 W/m²', () => { + near(C.BRIGHTNESS_NIGHT_W_M2, 0.32, 0.01) + near(C.NIGHT_BEAM_FRACTION, 0.001, 1e-9) + }) it('full brightness is Earth clear-sky noon, 320 W/m² mean', () => near(C.BRIGHTNESS_FULL_W_M2, 320, 2)) it('collector is 244 km across', () => near(C.COLLECTOR_DIAMETER_M / 1000, 244, 1))