tower / commit
feat(canon): the far cap's bullseye is a mirror
README.md | 7 +++++ decisions.md | 62 +++++++++++++++++++++++++++++++++++++++--- design.md | 41 +++++++++++++++++++++++++--- src/canon/constants.ts | 40 +++++++++++++++++++++++++++ src/views/cap-pattern.ts | 34 +++++++++++++++++++++++ src/views/interior.ts | 70 +++++++++++++++++++++++++++++------------------- test/canon.test.ts | 35 +++++++++++++++++++++--- 7 files changed, 251 insertions(+), 38 deletions(-) diff --git a/README.md b/README.md index 5700a7c..18c7d81 100644 --- a/README.md +++ b/README.md @@ -189,6 +189,13 @@ which is a fact about eyes rather than about glass. Turn the light down and they come out. The sun cap stays a checkered plate: it faces the sun, and needs glare management rather than a view. +The one part of the far cap that is not glass is a **3.2 km mirror** on the axis. +A quarter of the beam survives all 540 km and arrives collimated, so it lands as +1.7 MW/m² — 1,200 suns — on a spot a thousandth of the cap's area; the mirror sends +it back up the column for a second pass, which lifts the beam's utilisation from 76% +to 94%. You can see it: the rod of light visibly terminates on a bright disc, and +the disc banks and goes out with the beam. + The checker survives on the far cap as the window's **mullions** — the same polar tessellation at the same pitch, so it is still the only object in the world with a known cell size and still the only rule anyone has for judging distance. It is just diff --git a/decisions.md b/decisions.md index 00c25e2..324a866 100644 --- a/decisions.md +++ b/decisions.md @@ -487,6 +487,13 @@ about standing inside Tower" (§1.2) and the interior view's headline feature. I gone. Tower also loses its only passive climate driver and its only natural source of geographic variation. +*Partly recovered by D-045, and worth reading as a correction to the paragraph +above rather than a reversal of the decision. The wall is still opaque and unbroken +for its full 540 km — that is what D-017 decided and it stands. What came back is +the pinwheel, through the far cap window, where §1.4 had always said it would be: +one sky, at one end of the world, at night. It is no longer everywhere, which is +the whole difference between the slats and a window.* + **What it buys, which is more.** - **One unbroken surface.** 169,646 km² of continuous land — Wisconsin's *total* @@ -1164,6 +1171,11 @@ whichever axial direction has the most room and prints the distance it used. ## D-041 — The caps get a pattern, and it is polar +*Half superseded by D-045: the far cap is now a window, and this pattern survives +on it as the window's mullions rather than as a filled checker. Everything below +still holds — the tessellation, the pitch, the mipmapping and the reason for all +three are unchanged, and the sun cap is still exactly this.* + The end caps were flat tinted discs. They are the only surfaces in Tower that are not landscape — 100 km of structure closing each end — and blank they read as fog with an edge. A checker fixes that, and does something more useful besides: it is @@ -1366,10 +1378,9 @@ The pole star is placed rather than left to the hash. It is the fixed point the whole phenomenon is read against, and a sky that happened not to roll one there would be missing the only part of it anybody in Tower would have named. -**Left open:** §1.3.1 puts 24% of the beam — 13 TW — on the far cap. Glazing it -means that power leaves through the window instead of being absorbed by it. Whether -that is a heat-rejection system Tower depends on, or a load the glass has to -survive, is not settled. +**Settled by D-048:** the surviving quarter of the beam does not go through the +window at all. It arrives collimated in a 3.2 km spot — a thousandth of the cap — +and that spot is a mirror, not glass. ## D-046 — Night is the beam banked, not the swarm aimed away @@ -1423,3 +1434,46 @@ window and the hull got two faces in the same week. The dark backing disc the first version of D-045 put behind the glazing is gone. It was there to stop the panes showing the inside of the tube — which turns out to be exactly the thing worth showing. + +## D-048 — The far cap's bullseye is a mirror + +**Decided by the author**, from four options: mirror it, let it out, absorb it +behind fins, or grade the swarm so it never arrives. + +**The shape of the load is the argument.** §1.3.1 leaves 24% of the beam +unscattered after 540 km, and it survives *collimated* — so this was never 13 TW +spread over a 7,854 km² cap. It is **1.7 MW/m² in a 7.96 km² spot**, about 1,200 +suns, and an absorber there sits at **2,330 K**: survivable for tungsten, and for +no structure. But the spot is **a thousandth of the cap's area**, which means the +whole problem lives on a 3.2 km bullseye and 99.9% of the far cap needs nothing +done to it at all. That is what makes D-045's window and this coexist. + +**A mirror is the cheap thermal answer, not the expensive one.** At 99.9% it +absorbs 1,665 W/m² — about 1.2 Earth solar constants, ordinary sunlight. The thing +that would have glowed white is merely warm. The absorber-with-fins option needed +the entire cap radiating at 400 K *and* 13 TW moved 50 km sideways from the spot to +get there; it was strictly worse than every alternative. + +**It also recovers the energy, and fixes a number that was already wrong.** The +return trip makes the optical column 1,080 km rather than 540, so utilisation is +1 - e^(-2τ) = **94%** against 76%. That matters more than it looks, because canon +had a contradiction sitting in it: §1.3 sets full brightness at 54.3 TW ÷ 169,646 +km² = **320 W/m²**, which assumes the whole beam reaches the inner surface, while +§1.3.1's optical depth says only 76% does — implying **243 W/m²**. Both were +printed, and a test pinned 320. With the cavity closed at both ends (the mirror at +one, the collector at the other, returning what still escapes) essentially all +54.3 TW is eventually absorbed, and **320 is right as it stands**. No test moved. + +**What it does not do is make weather.** Aiming light at a region to warm it stays a +separate lever, and §1.9.4 already says that is how every climate in Tower is made. +The mirror is about not wasting the beam; the Dry Reach is about where the heat is +wanted. Keeping them separate means neither constrains the other. + +**A geometric trap, recorded because I fell into it.** The mirror sits on the axis, +so it looks like it must occult the pinwheel's motionless centre — I wrote that into +canon before checking. It does not. The pinwheel turns about the direction +*parallel* to the axis, and that direction lands on the window at the observer's +**own radius**, not at the cap's middle. From the floor the spin axis is horizontal, +so the centre of Tower's only moving sky is a point on the **horizon**, dead ahead +at eye level — while the mirror is ~10° above it from 270 km. The two coincide only +for an observer standing on the axis, and nobody can stand there. diff --git a/design.md b/design.md index 5ec2b9d..d1ffdcc 100644 --- a/design.md +++ b/design.md @@ -195,6 +195,30 @@ this is a state Tower can survive and not one it can sit in. | Temperate | 200 W/m² | | | Full | 320 W/m² | Earth clear-sky noon | +#### 1.3.3 The far cap mirror, and where the last quarter of the beam goes + +The swarm has optical depth 1.41, so **24% of the beam survives all 540 km** — and +it survives *collimated*, in the beam's own 7.96 km² cross-section. That is the +whole shape of the problem: not 13 TW spread over a 7,854 km² cap, but **1.7 MW/m² +in a 3.2 km spot**, about 1,200 suns. An absorber there sits at **2,330 K**. + +**So the spot is a mirror** (D-048), and only the spot: it is a thousandth of the +cap's area, which is what leaves the other 99.9% free to be window (§1.4). A mirror +is the cheap answer rather than the expensive one — at 99.9% it absorbs **1,665 +W/m²**, about 1.2 Earth solar constants, so the thing that would have glowed white +is merely warm. + +**It also recovers the energy.** The residual goes back up the column, which makes +the optical path 1,080 km instead of 540 and utilisation **1 - e^(-2τ) = 94%** +rather than 76%. What survives even the return trip leaves through the beam +aperture toward the collector, which sends it back again; in the limit the whole +54.3 TW lands on the inner surface. **This is why full brightness is 320 W/m² and +not 243** — the figure in §1.3 assumes the entire beam is eventually used, and with +the cavity closed at both ends it is. + +Aiming light at a region to warm it is a separate lever and stays one (§1.9.4). The +mirror is about not wasting the beam; the desert is about where the weather goes. + ### 1.4 Sealed hull, and the cap windows **The cylinder wall is opaque and unbroken.** No slats, no windows, no view out @@ -224,10 +248,19 @@ The only views out are at the **end caps**, and they are peculiar: haze, so from anywhere but the deepward end the airlight in front of it swamps whatever is behind, and the cap reads as a disc of sky. The stars are there at noon and unseeable, which is a fact about eyes and not about the glass. -- **13 TW lands on it.** §1.3.1 puts 24% of the beam on the far cap, and glazing it - means that power now leaves through the window rather than being absorbed by it. - Whether that is a heat-rejection system Tower depends on or a load the glass has - to survive is not yet settled canon. +- **The beam does not go through it.** §1.3.3 puts the surviving quarter of the + beam on a 3.2 km bullseye at the centre of the cap, and that bullseye is a mirror + rather than glass: it returns the residual up the column for a second pass. The + window is everything else, which is 99.9% of the disc. +- **The mirror is not the pinwheel's centre, and it is worth being clear why.** The + sky turns about the direction *parallel* to the spin axis, and from anywhere on + the floor that direction is horizontal — so **the pinwheel's centre is at eye + level, dead ahead**, down where the window meets the ground, not overhead and not + at the middle of the cap. The mirror is a physical object *on* the axis, which + from 270 km sits about 10° above that. They coincide only for an observer + standing on the axis itself, and nobody can. By day the mirror blazes — it is + returning 1.7 MW/m² — and is the brightest thing in Tower's sky; at night it + banks with the beam and goes dark. **Nobody aboard can ever see Earth.** Tower holds a terminator orbit (D-004), which puts Earth roughly 90° off the axis — out the *side* of the cylinder, which is now diff --git a/src/canon/constants.ts b/src/canon/constants.ts index 4aa34b5..98e75ab 100644 --- a/src/canon/constants.ts +++ b/src/canon/constants.ts @@ -83,6 +83,46 @@ 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 +// ------------------------------------------- the far cap mirror (design.md §1.3.2) +/** + * What survives the swarm arrives collimated, in the beam's own cross-section — + * 7.96 km², a thousandth of the far cap's 7,854. So the load is not 13 TW spread + * over a disc; it is **1.7 MW/m² in a 3.2 km spot**, some 1,200 suns, which an + * absorber would sit at 2,330 K to re-radiate. D-048 puts a mirror there instead. + */ +export const BEAM_FAR_CAP_FLUX_W_M2 = + (BEAM_POWER_W * Math.exp(-SWARM_OPTICAL_DEPTH)) / BEAM_AREA_M2 +/** Sized to the beam's 1,592 m with a margin, since the spot is not held perfectly. */ +export const FAR_CAP_MIRROR_RADIUS_M = 2000 +/** + * A mirror is the cheap thermal answer, not the expensive one: at 99.9% it absorbs + * about 1.2 Earth solar constants, which is a warm plate rather than a glowing one. + */ +export const FAR_CAP_MIRROR_REFLECTIVITY = 0.999 +export const FAR_CAP_MIRROR_ABSORBED_W_M2 = + BEAM_FAR_CAP_FLUX_W_M2 * (1 - FAR_CAP_MIRROR_REFLECTIVITY) +/** + * Returning the residual for a second pass makes the optical column 1,080 km + * rather than 540, so utilisation is 1 - e^(-2τ) instead of 1 - e^(-τ): **94%** + * against 76%. What survives even that exits the beam aperture toward the + * collector, which returns it again — so in the limit the whole 54.3 TW lands on + * the inner surface, and BRIGHTNESS_FULL_W_M2 is right as it stands (D-048). + */ +export const BEAM_UTILISATION_ONE_PASS = 1 - Math.exp(-SWARM_OPTICAL_DEPTH) +export const BEAM_UTILISATION_TWO_PASS = 1 - Math.exp(-2 * SWARM_OPTICAL_DEPTH) + +/** + * The beam's own glow, seen from the floor (design.md §1.3). + * + * Radiance is the figure §1.3 states — 26x a clear blue sky — and is taken from + * the doc rather than re-derived here; the derivation needs the Rayleigh phase + * function and the air density at the axis, and a second version of it that + * disagreed by 30% would be worse than none. The angular width IS derived, and + * comes out at the 3.65° §1.3 prints. + */ +export const BEAM_RADIANCE_W_M2_SR = 779 +export const BEAM_ANGULAR_WIDTH_RAD = 2 * Math.atan(BEAM_RADIUS_M / RADIUS_M) + /** * 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 diff --git a/src/views/cap-pattern.ts b/src/views/cap-pattern.ts index b0bdb89..5c39cd0 100644 --- a/src/views/cap-pattern.ts +++ b/src/views/cap-pattern.ts @@ -16,6 +16,7 @@ * hub or slabs at the rim. */ import { CanvasTexture, LinearFilter, SRGBColorSpace, type Texture } from 'three' +import { FAR_CAP_MIRROR_RADIUS_M } from '../canon/constants.js' const RADIUS_KM = 50 /** @@ -34,6 +35,8 @@ const DARK = '#8d96a5' const RIB = '#dae1eb' /** The far cap's window frame. Lighter than the checker: it is seen against sky. */ const MULLION = '#aeb7c6' +/** The bullseye mirror. Near-white: it is returning 1.7 MW/m² back up the axis. */ +const MIRROR = '#f2f6ff' /** Sectors at a given radius, chosen to keep cells about CELL_KM wide. */ const sectorsAt = (rKm: number): number => { @@ -152,9 +155,40 @@ export function drawCapGlazing(size = 2048): HTMLCanvasElement { ctx.stroke() } + /** + * The bullseye: not glass, and the only part of the far cap that is not (D-048). + * The beam arrives collimated in its own cross-section, so what has to survive + * 1.7 MW/m² is a 4 km disc on the axis and nothing else — the mirror that sends + * it back up the column for a second pass. + * + * Alpha 1, so the star field does not show through it. It does not occult the + * pole star, though the geometry invites that guess: the pinwheel turns about the + * direction parallel to the axis, which from the floor is horizontal, so its + * centre lands on the window at the observer's own radius — down at eye level — + * while the mirror is an object on the axis, about 10° above it from 270 km. + */ + const mirrorKm = FAR_CAP_MIRROR_RADIUS_M / 1000 + ctx.fillStyle = MIRROR + ctx.beginPath() + ctx.arc(mid, mid, mirrorKm * px, 0, Math.PI * 2) + ctx.fill() + // A collar around it, so at a distance the mirror reads as set into structure + // rather than as a bright fleck of nothing. + ctx.strokeStyle = MULLION + ctx.lineWidth = Math.max(1, mullionKm * 1.5 * px) + ctx.beginPath() + ctx.arc(mid, mid, mirrorKm * px, 0, Math.PI * 2) + ctx.stroke() return c } +/** + * How far out the mirror reaches, as a fraction of the cap's radius. The shaders + * need it: the bullseye is the one part of the window that is lit by the beam, so + * it brightens and banks with the beam instead of with the ambient light. + */ +export const MIRROR_UV_RADIUS = FAR_CAP_MIRROR_RADIUS_M / 1000 / RADIUS_KM + /** The pattern as a texture. Built once; both views share the one instance. */ let cached: Texture | null = null export function capPatternTexture(): Texture { diff --git a/src/views/interior.ts b/src/views/interior.ts index 60a7b40..b86426f 100644 --- a/src/views/interior.ts +++ b/src/views/interior.ts @@ -25,8 +25,8 @@ import { Scene, ShaderMaterial, TextureLoader, Vector3, WebGLRenderer, } from 'three' import { - BEAM_RADIUS_M, BRIGHTNESS_FULL_W_M2, BRIGHTNESS_TEMPERATE_W_M2, - BRIGHTNESS_NIGHT_W_M2, BRIGHTNESS_TWILIGHT_W_M2, G0, HULL_DEPTH_M, LENGTH_M, OMEGA_RAD_S, RADIUS_M, + BEAM_RADIUS_M, BRIGHTNESS_FULL_W_M2, BRIGHTNESS_NIGHT_W_M2, BRIGHTNESS_TEMPERATE_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' @@ -34,7 +34,7 @@ import { RAINDROP_TERMINAL_M_S, steadyFallLeanRad } from '../canon/motion.js' import { type SurfacePoint, mapThetaOrigin, normalizeTheta } from '../canon/geometry.js' import type { Place } from '../world/schema.js' import { EYE_MAX_M, EYE_MIN_M } from '../ui/scales.js' -import { capGlazingTexture, capPatternTexture } from './cap-pattern.js' +import { MIRROR_UV_RADIUS, capGlazingTexture, capPatternTexture } from './cap-pattern.js' import { COLOR_RASTER, HEIGHT_RASTER, RASTER_HEIGHT, RASTER_HEIGHT_SPAN_M, RASTER_MIN_HEIGHT_M, RASTER_WIDTH, @@ -44,7 +44,7 @@ const KM = 1000 const R = RADIUS_M / KM const L = LENGTH_M / KM const BEAM_R = BEAM_RADIUS_M / KM -/** Wall depth in km. The far cap sits at the outer face of it. */ +/** Wall depth in km. The far cap's window is set this far into its own frame. */ const HULL_D = HULL_DEPTH_M / KM /** @@ -144,21 +144,6 @@ vec3 towerTransmittance(vec3 from, vec3 to) { } ` -/** The bore between the world's end and the cap. Plain structure, hazed. */ -const REVEAL_FRAG = /* glsl */` -#include <common> -#include <logdepthbuf_pars_fragment> -uniform vec3 uTint; -varying vec3 vWorld; -${HAZE_GLSL} -void main() { - #include <logdepthbuf_fragment> - vec3 T = towerTransmittance(uCam, vWorld); - gl_FragColor = vec4(uTint * uExposure * T + uHaze * uExposure * (1.0 - T), 1.0); - #include <colorspace_fragment> -} -` - const GROUND_VERT = /* glsl */` #include <common> #include <logdepthbuf_pars_vertex> @@ -355,6 +340,21 @@ void main() { } ` +/** The bore between the world's end and the glazing. Plain structure, hazed. */ +const REVEAL_FRAG = /* glsl */` +#include <common> +#include <logdepthbuf_pars_fragment> +uniform vec3 uTint; +varying vec3 vWorld; +${HAZE_GLSL} +void main() { + #include <logdepthbuf_fragment> + vec3 T = towerTransmittance(uCam, vWorld); + gl_FragColor = vec4(uTint * uExposure * T + uHaze * uExposure * (1.0 - T), 1.0); + #include <colorspace_fragment> +} +` + /** * The far cap: a window, and the frame holding it. * @@ -386,6 +386,21 @@ void main() { */ vec3 sky = starField(vWorld - uCam); vec3 face = mix(sky, mullion, frame.a); + /** + * The bullseye mirror (D-048) is returning 1.7 MW/m² back up the column, so it + * is lit by the beam rather than by the room: it blazes at noon, banks at night + * and goes out entirely when the beam does. The stars behind it do none of + * those, which is the whole difference between the two things in this shader. + * + * It is on the axis, but that is NOT the centre of the pinwheel: starField turns + * about the direction parallel to the axis, which lands on the window at the + * camera's own radius rather than at the cap's middle. The two are ~10° apart + * from the floor and coincide only for an observer standing on the axis. + */ + float rc = length(vWorld.xy) / ${R.toFixed(1)}; + float mirror = 1.0 - smoothstep(${(MIRROR_UV_RADIUS * 0.86).toFixed(5)}, + ${MIRROR_UV_RADIUS.toFixed(5)}, rc); + face += mirror * vec3(1.0, 0.96, 0.88) * uExposure * 2.6; vec3 T = towerTransmittance(uCam, vWorld); gl_FragColor = vec4(face * T + uHaze * uExposure * (1.0 - T), 1.0); #include <colorspace_fragment> @@ -694,22 +709,23 @@ export class InteriorView { * The caps close the world at both ends. From the midpoint they are 270 km away * and 4% transmitted (§1.8), so they are all but pure haze — which is the correct * and slightly unnerving result, and worth drawing precisely because it is. + * + * They are not the same object any more. The sun cap faces the sun and the + * collector and keeps its checker; the far cap looks into deep anti-sun space and + * is glazed (D-045), set a kilometre into its own frame so the wall depth of + * D-044 has somewhere to be visible from. */ private buildCaps(): void { - // The tints are now multipliers over the pattern, not the colour of the cap. - // The sun cap sits closer to the beam's brightest end; the far cap is dimmer. + // The tint is a multiplier over the pattern, not the colour of the cap. const sun = new Mesh(new RingGeometry(BEAM_R, R, 128), this.capMaterial(0xffffff)) this.scene.add(sun) - /** - * The far cap sits at the outer face of the wall, so from inside there is a - * kilometre of reveal standing in front of it — which is what the wall depth of - * D-044 actually looks like from the only place it can be seen. - */ + const far = new Mesh(new CircleGeometry(R, 128), this.glazedCapMaterial()) far.position.z = L + HULL_D this.scene.add(far) - // The bore. BackSide, because it is only ever seen from inside. + // The reveal: a kilometre of bore standing between the end of the world and + // the glass. BackSide, because it is only ever seen from inside. const bore = new CylinderGeometry(R, R, HULL_D, 128, 1, true) bore.rotateX(Math.PI / 2) bore.translate(0, 0, L + HULL_D / 2) diff --git a/test/canon.test.ts b/test/canon.test.ts index a6b3ec3..34b79da 100644 --- a/test/canon.test.ts +++ b/test/canon.test.ts @@ -118,13 +118,42 @@ 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('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)) + /** + * The far cap mirror (D-048). The shape of the load is the whole argument: the + * survivors arrive collimated, so this is a spot problem and not a disc problem. + */ + it('the beam reaches the far cap as 1.7 MW/m² in a 3.2 km spot', () => { + near(C.BEAM_FAR_CAP_FLUX_W_M2 / 1e6, 1.67, 0.05) + // A thousandth of the cap's area — which is why 99.9% of it can be window. + near(C.BEAM_AREA_M2 / (Math.PI * C.RADIUS_M ** 2), 0.001, 0.0002) + }) + it('an absorber there would sit at 2330 K; a 99.9% mirror at 1.2 solar constants', () => { + near((C.BEAM_FAR_CAP_FLUX_W_M2 / 5.67e-8) ** 0.25, 2330, 20) + near(C.FAR_CAP_MIRROR_ABSORBED_W_M2, 1665, 30) + }) + it('the mirror lifts utilisation from 76% to 94% by doubling the column', () => { + near(C.BEAM_UTILISATION_ONE_PASS, 0.756, 0.005) + near(C.BEAM_UTILISATION_TWO_PASS, 0.940, 0.005) + }) 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)) + /** + * The reason night has to be made this way rather than by aiming the swarm off. + * §1.3 gives the glow to the beam and the air, so a fully lit beam is still a + * light source however the diffusers are pointed: 779 W/m²/sr over a strip 3.65° + * wide running horizon to horizon is ~0.2 sr, and with the mean cosine of a + * strip through the zenith that lands near 100 W/m² on the floor — half of + * temperate, which is not a night by anyone's reckoning (D-046). + */ + it('a lit beam alone would light the floor to about 100 W/m²', () => { + const strip = (C.BEAM_ANGULAR_WIDTH_RAD * Math.PI) * (2 / Math.PI) + near(C.BEAM_RADIANCE_W_M2_SR * strip, 100, 25) + }) it('stays inside the étendue limit — D-003 is no longer a physics break', () => { expect(C.BEAM_CONCENTRATION).toBeLessThan(C.ETENDUE_LIMIT) near(C.BEAM_CONCENTRATION / C.ETENDUE_LIMIT, 0.11, 0.005)