Switch monthly phase close-ups to NASA SVS Dial-a-Moon at fullscreen

The previous east-flavored composite (tight-crop east region + lunar
texture overlay) was honest but visually limited: 38-px source moon, no
real terminator shadows on quarters, and a faint upscaled-halo
background that was less compelling than just a clean lunar render.

New flow:
- East stays the witness (verifies the moon was visible during the
  collection window) and supplies the timestamp.
- moon_dialamoon.py fetches the NASA SVS Dial-a-Moon render for that
  exact UTC hour. Free, public-domain, real-physics: correct phase,
  libration, and crater shadows for any timestamp.
- moon_composite.render_phase_closeup() places that render at ~92% of
  frame height on a 1920x1080 black background -- the long-telephoto
  look the user asked for.
- One API call per phase event (~36/year), cached forever in
  moon-ref/dialamoon/.

Quarter moons now show real 3D crater shadows along the terminator,
which the prior algorithmic phase-shadow couldn't simulate from a
full-moon reference.

Adds MOON_REQUIRE_EAST_VERIFY=true|false toggle so the user can choose
"only post when east saw it" (default, current behavior) vs "post every
cycle regardless of weather over east."

Drops the install-time lunar reference download (no longer needed) and
the now-unused per-phase phase-shadow path stays in moon_composite.py
for reference / future reuse.

https://claude.ai/code/session_015PBVDESC3KLMbq1LpA6qLn
This commit is contained in:
Claude
2026-05-01 11:54:46 +00:00
parent 5fb7ff09a3
commit 28c3c1dc63
6 changed files with 394 additions and 79 deletions
+45 -27
View File
@@ -52,12 +52,13 @@
# a tracked sequence, stitches into an mp4
#
# moon-phase-monthly.sh — daily at SCHEDULE_MOON_PHASE (SUNRISE_CAM only)
# no-ops except on phase post-days, when it picks
# the best east frame from the collection window,
# composites a high-res lunar texture into it,
# and posts to Mattermost. Handles full moon,
# first quarter (waxing half), and third quarter
# (waning half).
# no-ops except on phase post-days, when it
# confirms east saw the moon during the
# collection window, fetches NASA SVS Dial-a-Moon
# for that exact UTC hour, renders the moon
# full-screen on a black background, and posts to
# Mattermost. Handles full moon, first quarter
# (waxing half), and third quarter (waning half).
#
#
# install.sh — run once at setup, and again if this file changes
@@ -448,19 +449,23 @@ AMBIENT_RETENTION_DAYS=30 # global default; 0 = keep forever
# 🌕 Full Moon posts D + MOON_FULL_POST_DELAY_DAYS
# 🌓 First Quarter posts D + MOON_QUARTER_POST_DELAY_DAYS
# 🌗 Third Quarter posts D + MOON_QUARTER_POST_DELAY_DAYS
# Each picks the frame closest in time to the
# exact phase moment that meets quality / altitude
# / illumination thresholds, composites the
# cached lunar texture into it (sky/halo/angle
# real from east, surface detail borrowed),
# uploads to Mattermost. Output:
# East acts as the WITNESS — it confirms the moon
# was actually visible in your sky during the
# collection window and supplies the timestamp.
# The image itself is a NASA SVS Dial-a-Moon
# render for that exact UTC hour, sized to fill
# the frame on a black background (like the look
# of a long-telephoto shot). Output:
# $MOVIES_DIR/<cam>/full-moons/YYYY-MM-full.jpg
# $MOVIES_DIR/<cam>/first-quarter/YYYY-MM-first-quarter.jpg
# $MOVIES_DIR/<cam>/third-quarter/YYYY-MM-third-quarter.jpg
#
# Honest-by-design: the surface texture is not from your camera (no software
# can recover detail your camera didn't capture). The framing — sky color,
# halo, parallactic angle, exact moment of capture — is all real-from-east.
# Honest-by-design: a 38-px white blob from a wide-field IP camera cannot be
# enhanced into crater detail. The image you see in Mattermost is a NASA
# render for the exact moment east captured the moon — the strongest possible
# match for what your sky actually looked like, with full real-physics
# crater shadows, libration, and phase. East's role is verifying that you
# had a clear view of the moon that night.
#
# Geometry caveat: east only sees the eastern sky. At first quarter the moon
# is up only from noon to midnight, transiting south at sunset, so east only
@@ -475,6 +480,11 @@ MOON_FULL_ENABLED=true
MOON_FIRST_QUARTER_ENABLED=true # set false if daytime-detection misses are noisy
MOON_THIRD_QUARTER_ENABLED=true
# Set false to skip east verification entirely and post the NASA render for
# the exact phase moment regardless of clouds / camera outage. Default true
# means "only post when east actually saw the moon that month."
MOON_REQUIRE_EAST_VERIFY=true
# Nightly tracker tuning ──────────────────────────────────────────────────────
MOON_TRACK_CROP_PX=480 # pixels — box size around the moon (source coords)
MOON_TRACK_FPS=12 # output mp4 framerate
@@ -486,25 +496,33 @@ MOON_TRACK_RETENTION_DAYS=90 # delete tracker mp4s older than this; 0 = fo
# nights to be on disk, then runs the morning of D+3.
MOON_FULL_POST_DELAY_DAYS=3
# Quarter (half-moon) tuning ──────────────────────────────────────────────────
# 2 = waits for D-1, D, D+1 nights, runs the morning of D+2.
MOON_QUARTER_POST_DELAY_DAYS=2
MOON_QUARTER_MIN_ILLUMINATION=0.40 # ~50% ± 10% covers the day around exact quarter
MOON_QUARTER_MAX_ILLUMINATION=0.65
# Frame-acceptance thresholds — a candidate must beat all three to qualify.
# Lower = more permissive (accept hazier nights / lower moon). If you find
# the script never finds a clear shot, loosen these.
MOON_FULL_MIN_QUALITY=0.55 # 0..1 from moon_detect (roundness × halo × isolation)
MOON_FULL_MIN_ALTITUDE_DEG=15 # below this the moon is in trees / on the horizon
MOON_MIN_QUALITY=0.55 # 0..1 from moon_detect (roundness × halo × isolation)
MOON_MIN_ALTITUDE_DEG=15 # below this the moon is in trees / on the horizon
MOON_FULL_MIN_ILLUMINATION=0.95 # 0..1 — ~95% lit covers ±2 days from exact full
# Output frame. Default 1920×1080 to match the sunrise videos.
MOON_FULL_OUTPUT_W=1920
MOON_FULL_OUTPUT_H=1080
MOON_FULL_HEIGHT_PCT=0.70 # moon disk fills this fraction of frame height
MOON_OUTPUT_W=1920
MOON_OUTPUT_H=1080
MOON_HEIGHT_PCT=0.92 # moon disk fills this fraction of frame height (full-screen feel)
# Lunar reference texture — downloaded once by install.sh, reused forever.
# Override MOON_REFERENCE_URL in .env to use a different image. Any high-res
# photo of a full moon on a black background works. Default is the Wikipedia
# "FullMoon2010" by Gregory H. Revera (CC BY-SA 3.0), 3500×3500.
#MOON_REFERENCE_URL=https://upload.wikimedia.org/wikipedia/commons/e/e1/FullMoon2010.jpg
#MOON_REFERENCE_DIR="$SCRIPT_DIR/moon-ref"
#MOON_REFERENCE_PATH="$MOON_REFERENCE_DIR/full-moon.jpg"
# NASA SVS Dial-a-Moon ────────────────────────────────────────────────────────
# Hourly pre-rendered moon images, free, public domain, real physics
# (correct phase, libration, crater shadows for any UTC hour).
# https://svs.gsfc.nasa.gov/api/dialamoon/<ISO-DATE>
# We hit it once per phase event (~36 calls/year), cache the result forever.
#MOON_DIALAMOON_API=https://svs.gsfc.nasa.gov/api/dialamoon
#MOON_DIALAMOON_CACHE_DIR="$SCRIPT_DIR/moon-ref/dialamoon"
MOON_DIALAMOON_TARGET_PX=2048 # cached PNG longest side; downsampled on save
MOON_DIALAMOON_TIMEOUT_SEC=30
# ── Mattermost — daily sunrise upload ─────────────────────────────────────────
# mattermost_url, access_token, channel_id go in .env (see bottom of this file).