tower / commit
feat(exterior): the collector is dark on both sides, with a lit rim
decisions.md | 29 ++++++++++++++++++++++ design.md | 7 +++++- src/views/exterior.ts | 66 ++++++++++++++++++++++++++++++++++++++++++--------- 3 files changed, 90 insertions(+), 12 deletions(-) diff --git a/decisions.md b/decisions.md index 29c1d02..e866ee9 100644 --- a/decisions.md +++ b/decisions.md @@ -1644,3 +1644,32 @@ seven times too wide, which washed the whole disc gold instead of tracing it. lamp, so the rim is outside `uExposure`, like the stars. It does fade as the eye adapts, but from a much higher floor than a star's — this is sunlit structure, and it stays faintly visible at noon where a star cannot. + +## D-055 — The collector looks the same from outside as it does from inside + +The exterior view drew the collector as a near-white disc with a pale blue glow, and +the sun cap window drew it as black with a lit rim (D-054). Both cannot be right, and +the window is the one that follows from the physics: the collector's mirror face is +pointed at the sun and away from Tower, so what the world sees is the back of it, a +46,938 km² radiator running at 245 K. That is infrared. In visible light it is black. + +**So the exterior draws two faces rather than one.** The disc is two meshes sharing a +plane, one front-facing and one back-facing, and the camera's own position picks which +one it gets — culling does the choosing, so they never fight over the same depth. + +- The Tower side is very dark and unlit, with just enough emissive to keep it a + surface. Not zero: against black sky, a wholly unlit disc is not a dark object, it + is a missing one. +- The sun side is bare metal at almost no roughness, which under one directional light + is dark everywhere except a specular glint. That is what a flat mirror in vacuum + actually looks like: it shows the empty sky in front of it, not the sun behind you. + +**The rim is what makes it an object.** The 4 km of sunlit edge structure from D-054 is +drawn here as a ring of geometry rather than as two exponentials in a shader, because +out here it is being seen rather than looked through — and it is placed just outboard +of the mirror, not overlapping its edge, so the two surfaces never share a depth +either. It reads from both sides, which is right: it is structure, not a reflection. + +The cost is that the largest object in the scene is now a dark one. The label, the +beam converging on it and the lit rim carry it — and being hard to see is the point, +since Tower has never seen this thing lit. diff --git a/design.md b/design.md index bdbbe05..2958ae6 100644 --- a/design.md +++ b/design.md @@ -707,9 +707,14 @@ map's hillshade: - **A visible beam.** In vacuum there is nothing between collector and cap to scatter light, so the converging beam would not be visible at all. It is drawn as rays because it is the thing the view is about. +- **A dark collector.** Both of its sides are dark, and for different reasons: the + face pointed at Tower is a 245 K radiator, and the face pointed at the sun is a + mirror, which shows the empty sky in front of it and not the sun behind the eye. + What makes it legible is the sunlit rim of D-054 — the same 4 km ring the sun cap + window sees from inside, drawn here as geometry rather than in a shader (D-055). Everything in the scene is captioned — collector, beam, both caps — with screen-space -labels. A pale disc and a cone of light are not self-explanatory, and the collector +labels. A dark disc and a cone of light are not self-explanatory, and the collector is the least intuitive object in Tower: it is larger than the world it feeds. The default camera frames the whole system rather than Tower alone: the collector diff --git a/src/views/exterior.ts b/src/views/exterior.ts index 5681b74..84ff81c 100644 --- a/src/views/exterior.ts +++ b/src/views/exterior.ts @@ -18,7 +18,8 @@ import { } from 'three' import { OrbitControls } from 'three/examples/jsm/controls/OrbitControls.js' import { - BEAM_RADIUS_M, COLLECTOR_DIAMETER_M, COLLECTOR_STANDOFF_M, HULL_DEPTH_M, LENGTH_M, OMEGA_RAD_S, RADIUS_M, + BEAM_RADIUS_M, COLLECTOR_DIAMETER_M, COLLECTOR_RIM_M, COLLECTOR_STANDOFF_M, HULL_DEPTH_M, LENGTH_M, + OMEGA_RAD_S, RADIUS_M, } from '../canon/constants.js' import { type SurfacePoint, mapThetaOrigin, normalizeTheta } from '../canon/geometry.js' import type { Place } from '../world/schema.js' @@ -33,6 +34,7 @@ const BEAM_R = BEAM_RADIUS_M / KM const HULL_D = HULL_DEPTH_M / KM const COLLECTOR_R = COLLECTOR_DIAMETER_M / KM / 2 const COLLECTOR_STANDOFF = COLLECTOR_STANDOFF_M / KM +const COLLECTOR_RIM = COLLECTOR_RIM_M / KM /** How close to an end, in km, counts as being on a cap rather than on the wall. */ const CAP_TOLERANCE = 8 const isCapZ = (zKm: number): boolean => zKm < CAP_TOLERANCE || zKm > L - CAP_TOLERANCE @@ -297,15 +299,57 @@ export class ExteriorView { } private buildSunSystem(): void { - // The collector flies ahead of the sun cap: 244 km across, wider than Tower. - const collector = new Mesh( - new CircleGeometry(COLLECTOR_R, 128), - new MeshStandardMaterial({ - color: 0xe6edf8, roughness: 0.2, metalness: 0.5, side: DoubleSide, - emissive: new Color(0x93a2be), emissiveIntensity: 1.15, - })) - collector.position.z = -COLLECTOR_STANDOFF - this.scene.add(collector) + /** + * The collector flies ahead of the sun cap: 244 km across, wider than Tower. + * + * It has two sides and they do not look alike, so it is drawn as two meshes + * rather than one double-sided disc. The face pointed at the sun is a mirror; + * the side pointed at Tower — the one this camera almost always sees, since + * Tower is between it and the viewer — is the back of that mirror, radiating + * its waste heat at 245 K. That is infrared, so in visible light it is black + * (D-050), which is exactly how the interior view draws it: a hole with a lit + * rim. Drawing it pale from out here contradicted the window it is seen + * through. Culling does the choosing: only one of the two faces survives for + * any given camera, so the pair never fight over the same depth. + */ + const disc = new CircleGeometry(COLLECTOR_R, 128) + // CircleGeometry faces +z, and +z from the collector is toward Tower. + const back = new Mesh(disc, new MeshStandardMaterial({ + color: 0x0b0e14, roughness: 0.95, metalness: 0.0, side: FrontSide, + // Not black to the last digit: against black space a wholly unlit disc is + // not a dark object, it is a missing one. This is the least glow that still + // reads as a surface. + emissive: new Color(0x171d27), emissiveIntensity: 1.0, + })) + back.position.z = -COLLECTOR_STANDOFF + this.scene.add(back) + /** + * The mirror face. A flat mirror in vacuum shows what is in front of it, and + * what is in front of this one is empty sky — so it is dark too, with the + * sun's own reflection as a glint where the angle happens to line up. Metal + * with almost no roughness does exactly that on its own: no diffuse term, one + * tight specular lobe. The emissive is only enough to keep it a surface + * rather than a hole when the glint is elsewhere. + */ + const front = new Mesh(disc, new MeshStandardMaterial({ + color: 0xdfe8ff, roughness: 0.12, metalness: 1.0, side: BackSide, + emissive: new Color(0x1a2334), emissiveIntensity: 1.0, + })) + front.position.z = -COLLECTOR_STANDOFF + this.scene.add(front) + /** + * The sunlit rim (D-054): 4 km of structure standing proud of the mirror + * plane at the edge, struck side-on by a sun nothing else here can see. It is + * the only lit part of the collector from Tower's side, and it is what makes + * the black disc read as a disc rather than as a gap in the stars. Drawn + * outboard of the mirror instead of overlapping its edge, so the two surfaces + * never share a depth. + */ + const rim = new Mesh( + new RingGeometry(COLLECTOR_R, COLLECTOR_R + COLLECTOR_RIM, 128), + new MeshBasicMaterial({ color: 0xffe6bb, side: DoubleSide })) + rim.position.z = -COLLECTOR_STANDOFF + this.scene.add(rim) /** * The converging beam, drawn as rays from the mirror's rim to the aperture. @@ -362,7 +406,7 @@ export class ExteriorView { } /** - * A pale disc and a glowing cone are not self-explanatory, and the collector is + * A dark disc and a glowing cone are not self-explanatory, and the collector is * the least intuitive object in Tower: it is bigger than the world it feeds. */ private buildLabels(): void {