3.9 KiB
Sunlight that reads on a light plan — the rejected model
Status: REJECTED by the owner on 2026-08-04. The model below was
approved in principle a few hours earlier and never became code. The owner
cancelled it in favour of one line out of it — the rim — which IS
implemented and specified in docs/SUN.md, section «The rim».
This file is kept for one reason: the analysis of why a wedge of light cannot read on white paper is correct, it is what the rim answers, and it is the first thing anyone will have to re-derive the next time somebody proposes «просто сделай лучи поярче».
The problem (this part still holds)
Today a lit window casts a warm translucent wedge (docs/SUN.md). It reads
beautifully on a dark scene — glow fill, night, a dark theme — and almost
disappears on a light one, which is now the common case: bg_mode: daynight
makes a clear day white, and a hand-drawn plan's paper is white too.
The cause is not opacity, it is physics. Painting light means adding luminance, and white has none left to give. Raising the alpha does not add contrast, it only tints the paper towards beige until the whole room looks dirty. Any fix has to take its contrast from something other than brightness.
What was proposed: paint the shade, not the light
On a light scene the wedges would invert. The lit areas stay untouched paper; the rest of the room gets a light, cool veil, so the contrast comes from the unlit part, which has plenty of room to go darker, and the wedge becomes a window of clean paper inside a softly shaded room. This is how architects draw insolation on white sheets, and it is physically honest: a sunlit room really is brighter where the shaft lands.
Geometrically it is the same wedge polygon used as a hole — a veil over the
room's polygon, minus the wedges (even-odd, or a mask). computeSunRays()
would not change at all; only what we do with the result. Around it the
spec needed: a luminance threshold to choose the model automatically, a
cross-fade whenever that threshold is crossed (theme switch, day turning to
night), a rule keeping the veil under the room fills so temperature and
LQI colours keep their identity, and an open question about offering
«свет / тень / авто» explicitly.
Why the owner dropped it. It is a second rendering model for one feature: two code paths, an automatic chooser that must be a pure function of a background nobody controls, a cross-fade between them, and a veil that has to coexist with the meaning-carrying room fills without being mistaken for one — all to solve a problem that a single hairline solves.
The spec's other amplifier — a mark on the lit window itself (a thicker warm stroke plus a short arrow along the sun direction, answering «в какие окна сейчас светит солнце» without reading the floor) — was not rejected on its merits. It is simply out of scope; this paragraph is where the idea stays written down.
What was implemented instead (2026-08-04)
A rim: a 1 px black hairline along the two SIDE edges of the wedge, fading to zero on exactly the same gradient axis, the same curve and the same 85 % threshold as the fill, clipped by the room like the wedge itself, and living inside the same layer — so the 3° threshold, the two-second fade, cloud cover and the editors govern it for free.
Light is invisible on white paper; its boundary is not. A boundary costs one stroke, works on any background, and needs no second model, no luminance threshold and no cross-fade.
Full contract and the tuned peak opacity: docs/SUN.md, «The rim».
Implementation: rayRimEdges(), rimStops(), rimPeakAlpha() and
RIM_MAX_ALPHA in src/sun.ts, plus the hp-sunrim-N gradient in
src/houseplan-card.ts. Browser proof, including the pixel probe showing
the wedge's side is measurably darker than the paper beside it:
demo/smoke_sun_rim.mjs.