- moon_composite.py: remove _make_atmosphere_layer() and all atmosphere
parameters from render_phase_closeup(); add when_utc parameter and apply
parallactic angle rotation so the NASA disk is oriented to match east's sky
- moon_phase_monthly.py: change obs time default to 22:00 (aligns with NASA
hourly renders); scan east frames in 22:00-23:00 window; binary go/no-go
(quality >= MIN_QUALITY = post, no clear shot = skip entirely); remove
_atmosphere_opacity_from_quality(), ATMOSPHERE_BLUR, CLOUDY_POST_ENABLED
- sky-cam.conf: update MOON_OBS_TIME_LOCAL to 22:00, remove
MOON_OBS_WINDOW_MIN, remove the atmospheric overlay and cloudy fallback
sections and their config variables
- test_moon_composite.py: single clean test — fetch NASA for 22:00 UTC on
2026-04-29, verify east frame quality, render with parallactic angle; no
procedural fallback
https://claude.ai/code/session_01HkTxpNSTWtViZzxbrbKytR
Previous approach: scan a 3-day window, find best moon detection,
use that timestamp. Problems: fuzzy composite (multi-day scan could
pick a frame far from actual full moon), no clear tie between the
NASA render and a specific observable moment.
New approach — MOON_OBS_TIME_LOCAL (default 22:30 local):
On the night of the exact phase event, look at east frames in a
±MOON_OBS_WINDOW_MIN (default 30 min) window around the configured
time. The frame closest to that time determines atmosphere opacity;
the same time (rounded to hour) drives the NASA Dial-a-Moon fetch.
This gives one definitive moment per phase per month.
moon_phase_monthly.py:
- PHASE_SPEC stripped to just post_delay + enabled_key (all window/
illumination filtering removed — obs time is the only selector)
- run_phase() replaced with obs-time logic; opacity derived from
detect_moon quality on that single frame
- MOON_FULL_POST_DELAY_DAYS / MOON_QUARTER_POST_DELAY_DAYS default
changed to 1 (post morning after the phase, frames already on disk)
moon_composite.py:
- atmosphere blur default: output_width//10 (192px) → output_width//20
(96px) so cloud shapes survive the blur
sky-cam.conf:
- MOON_OBS_TIME_LOCAL=22:30, MOON_OBS_WINDOW_MIN=30
- Post delay defaults updated to 1
test_moon_composite.py:
- Tries real NASA SVS Dial-a-Moon API first; procedural disc only as
fallback if API unreachable
- Runs detect_moon on the east frame to compute real opacity
- Single output (test_composite_out.jpg)
https://claude.ai/code/session_01HkTxpNSTWtViZzxbrbKytR
The core mental model was wrong: clouds are between the observer and
the moon, so they occlude the disk — they don't sit behind it.
moon_composite.py:
_make_east_sky_backdrop() → _make_atmosphere_layer()
render_phase_closeup() pipeline is now:
1. Black background
2. NASA moon disk centred (correct phase / libration / shadows)
3. East frame scaled + blurred → composited OVER the disk at
atmosphere_opacity (0.0 = clear, 0.68 = heavy overcast)
Parameters: east_sky_enabled/blur → atmosphere_opacity/blur
moon_phase_monthly.py:
_atmosphere_opacity_from_quality() maps detection quality to opacity:
quality >= 0.85 → 0.00 (clear)
quality 0.70 → 0.20 (light haze)
quality 0.55 → 0.40 (notable cloud, still detected)
quality None → 0.68 (no detection, heavy overcast)
Frame scan now tracks two lists:
qualifying — frames passing quality + illumination (existing)
above_horizon — frames where moon is up but detection failed
When qualifying is empty and MOON_CLOUDY_POST_ENABLED=true, the
above-horizon frame closest to the exact phase moment is used with
opacity 0.45–0.68. The month is always represented — full moon,
first quarter, and third quarter each get a post even in cloudy
months, showing the moon as a faint glow behind cloud.
sky-cam.conf:
MOON_EAST_SKY_ENABLED/BLUR → MOON_ATMOSPHERE_BLUR (opacity is
computed automatically from quality, not configured directly)
New: MOON_CLOUDY_POST_ENABLED=true
test_moon_composite.py:
Generates three outputs at opacity 0.00 / 0.20 / 0.65 so the
full opacity range is visible in one test run.
https://claude.ai/code/session_01HkTxpNSTWtViZzxbrbKytR
The annular-ring veil approach sampled too small a region (38 px moon
in 3840×2160 gives a tiny annulus) and was invisible in practice.
More fundamentally, using the whole east sky is what "relayed to where
it was taken" actually means.
New approach — _make_east_sky_backdrop():
Scale the full east camera frame to output size, apply GaussianBlur
r = output_width // 6 (≈ 320 px at 1920 wide). The blur erases RTSP
artefacts, OSD overlays, and the wide-angle look while preserving real
sky colour, cloud / haze gradients, and the dark ground silhouette.
The NASA moon disk is pasted sharp on top. The result reads as east
photographed the moon through a telephoto with its actual sky that night.
Clear dark sky → nearly black backdrop (same feel as before).
Thin cloud cover → soft grey-blue haze behind the sharp moon.
Heavy overcast → the moon detection would not qualify, so this case
never reaches rendering.
Config rename: MOON_CLOUD_OVERLAY_* → MOON_EAST_SKY_ENABLED / _BLUR.
moon_phase_monthly.py: CLOUD_OVERLAY_* → EAST_SKY_*.
render_phase_closeup(): cloud_overlay_* params → east_sky_*.
test_moon_composite.py: replace full_moon_closeup.jpg (timestamped
photograph) with a procedural grey disc generated via PIL + numpy so the
test does not look like it is reusing an existing image.
https://claude.ai/code/session_01HkTxpNSTWtViZzxbrbKytR
test_moon_composite.py uses the existing repo sample frames
(21-07-00.jpg east frame with thin cloud cover + full_moon_closeup.jpg
as NASA render stand-in) to produce test_composite_out.jpg without
needing a live NASA API call or moon detection run.
Run: python3 test_moon_composite.py
https://claude.ai/code/session_01HkTxpNSTWtViZzxbrbKytR