feat(view): the moon in its phase over the "Follow the Sun" background (#661)

At dawn, dusk and night the environment shows the moon in the top-left
corner of the scene, behind the plan: computed in the card from the home
coordinates and the browser clock (short Meeus series + topocentric
parallax, within 1.4° / 1.8 pp of JPL Horizons), one designer image under
a continuous phase mask with the lit side always on the left (owner
2026-09-29), a feathered terminator, 3°/3 % thresholds and the 2 s fade of
the window rays. Everything but a small gate lives in the lazy
moon-runtime chunk, with its own 30 s ticker. General settings: "Sun"
becomes "Sun and Moon" with one switch, on for new installations
(DEFAULT_CONFIG), off for existing ones.

The initial View graph sat 728 B under its budget: the gate is paid for by
moving fifteen dialog-only strings of General settings into the lazy
settings dictionary (#459) and by one build fingerprint literal instead of
three, so the graph ends 4 B above dev. Golden: two new moon scenes, and
the two General settings help frames show «Sun and Moon»; the WSL artifact
test fixture now models scenes whose first capture awaits acceptance.

Issue: #661
User-Visible: yes
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_018qZfe7YS4rqEMKoVeS3GKd
This commit is contained in:
Sergey Matyunin
2026-09-30 12:25:46 +03:00
co-authored by Claude Opus 5.5
parent e515bdaab8
commit c8c470ed57
58 changed files with 1682 additions and 126 deletions
+9
View File
@@ -734,6 +734,13 @@ export function assertBundleBudget(
if (!manifest.lazyPdfFiles?.length) {
throw new Error('bundle has no lazy PDF graph');
}
// #661 C7/AC8: the moon's math, art and template load only at night.
if (!manifest.lazyMoonFiles?.length) {
throw new Error('bundle has no lazy moon graph');
}
if (manifest.initialViewFiles.some((path) => manifest.lazyMoonFiles.includes(path))) {
throw new Error('initial View graph overlaps lazy moon graph');
}
// #474: designer furniture artwork is lazy; a static import anywhere in the
// View graph would pull ~10 KB gzip back into the initial graph silently.
if (!manifest.lazyFurnitureArtFiles?.length) {
@@ -806,6 +813,7 @@ export function assertBundleBudget(
lazyIsometricGzipBytes: manifest.lazyIsometricGzipBytes,
lazyFurnitureArtGzipBytes: manifest.lazyFurnitureArtGzipBytes,
lazyPdfGzipBytes: manifest.lazyPdfGzipBytes,
lazyMoonGzipBytes: manifest.lazyMoonGzipBytes,
};
}
@@ -840,6 +848,7 @@ if (import.meta.url === pathToFileURL(process.argv[1] || '').href) {
`lazy locale: ${result.lazyLocaleGzipBytes} B gzip`,
`lazy isometric: ${result.lazyIsometricGzipBytes} B gzip`,
`lazy PDF: ${result.lazyPdfGzipBytes} B gzip`,
`lazy moon: ${result.lazyMoonGzipBytes} B gzip`,
];
for (const line of lines) console.log(line);
const warning = lowHeadroomWarning(headroom);