diff --git a/.codex-temp/php-server.err.log b/.codex-temp/php-server.err.log index e35c176..8b592c0 100644 --- a/.codex-temp/php-server.err.log +++ b/.codex-temp/php-server.err.log @@ -902,3 +902,90 @@ Stack trace: [Fri Apr 3 16:13:22 2026] 127.0.0.1:62960 Accepted [Fri Apr 3 16:13:31 2026] 127.0.0.1:62960 [200]: GET /monatsvorhersage_old.php?year=2026&month=12 [Fri Apr 3 16:13:31 2026] 127.0.0.1:62960 Closing +[Fri Apr 3 16:19:37 2026] 127.0.0.1:55410 Accepted +[Fri Apr 3 16:19:53 2026] 127.0.0.1:55410 [200]: GET /monatsvorhersage_old.php?year=2026&month=3 +[Fri Apr 3 16:19:53 2026] 127.0.0.1:55410 Closing +[Fri Apr 3 16:19:53 2026] 127.0.0.1:52807 Accepted +[Fri Apr 3 16:20:02 2026] 127.0.0.1:52807 [200]: GET /monatsvorhersage_old.php?year=2026&month=10 +[Fri Apr 3 16:20:02 2026] 127.0.0.1:52807 Closing +[Fri Apr 3 16:20:24 2026] 127.0.0.1:51409 Accepted +[Fri Apr 3 16:20:33 2026] 127.0.0.1:51409 [200]: GET /monatsvorhersage_old.php?year=2026&month=10 +[Fri Apr 3 16:20:33 2026] 127.0.0.1:51409 Closing +[Fri Apr 3 16:20:50 2026] 127.0.0.1:60154 Accepted +[Fri Apr 3 16:21:06 2026] 127.0.0.1:60154 [200]: GET /monatsvorhersage_old.php?year=2026&month=3 +[Fri Apr 3 16:21:06 2026] 127.0.0.1:60154 Closing +[Fri Apr 3 16:21:06 2026] 127.0.0.1:51578 Accepted +[Fri Apr 3 16:21:06 2026] 127.0.0.1:64104 Accepted +[Fri Apr 3 16:21:06 2026] 127.0.0.1:51578 [200]: GET /css/style.css +[Fri Apr 3 16:21:06 2026] 127.0.0.1:64104 [200]: GET /js/astronomy.browser.min.js +[Fri Apr 3 16:21:06 2026] 127.0.0.1:51578 Closing +[Fri Apr 3 16:21:06 2026] 127.0.0.1:64104 Closing +[Fri Apr 3 16:21:06 2026] 127.0.0.1:64109 Accepted +[Fri Apr 3 16:21:06 2026] 127.0.0.1:53635 Accepted +[Fri Apr 3 16:21:06 2026] 127.0.0.1:64109 [200]: GET /images/star16x16.png +[Fri Apr 3 16:21:06 2026] 127.0.0.1:53635 [200]: GET /images/star16x16_rays.png +[Fri Apr 3 16:21:06 2026] 127.0.0.1:64109 Closing +[Fri Apr 3 16:21:06 2026] 127.0.0.1:53635 Closing +[Fri Apr 3 16:21:06 2026] 127.0.0.1:52555 Accepted +[Fri Apr 3 16:21:08 2026] 127.0.0.1:52555 [200]: GET /favicon.ico +[Fri Apr 3 16:21:08 2026] 127.0.0.1:52555 Closing +[Fri Apr 3 16:22:29 2026] 127.0.0.1:53891 Accepted +[Fri Apr 3 16:22:38 2026] 127.0.0.1:53891 [200]: GET /monatsvorhersage_old.php?year=2026&month=9 +[Fri Apr 3 16:22:38 2026] 127.0.0.1:53891 Closing +[Fri Apr 3 16:22:38 2026] 127.0.0.1:52655 Accepted +[Fri Apr 3 16:22:38 2026] 127.0.0.1:52655 [200]: GET /css/style.css +[Fri Apr 3 16:22:38 2026] 127.0.0.1:52655 Closing +[Fri Apr 3 16:22:38 2026] 127.0.0.1:52780 Accepted +[Fri Apr 3 16:22:38 2026] 127.0.0.1:52780 [200]: GET /js/astronomy.browser.min.js +[Fri Apr 3 16:22:38 2026] 127.0.0.1:52780 Closing +[Fri Apr 3 16:22:38 2026] 127.0.0.1:55211 Accepted +[Fri Apr 3 16:22:38 2026] 127.0.0.1:55211 [200]: GET /images/star16x16.png +[Fri Apr 3 16:22:38 2026] 127.0.0.1:59860 Accepted +[Fri Apr 3 16:22:38 2026] 127.0.0.1:59860 [200]: GET /images/star16x16_rays.png +[Fri Apr 3 16:22:38 2026] 127.0.0.1:55211 Closing +[Fri Apr 3 16:22:38 2026] 127.0.0.1:59860 Closing +[Fri Apr 3 16:22:38 2026] 127.0.0.1:55583 Accepted +[Fri Apr 3 16:22:40 2026] 127.0.0.1:55583 [200]: GET /favicon.ico +[Fri Apr 3 16:22:40 2026] 127.0.0.1:55583 Closing +[Fri Apr 3 16:22:58 2026] 127.0.0.1:52476 Accepted +[Fri Apr 3 16:23:08 2026] 127.0.0.1:52476 [200]: GET /monatsvorhersage_old.php?year=2026&month=10 +[Fri Apr 3 16:23:08 2026] 127.0.0.1:52476 Closing +[Fri Apr 3 16:23:08 2026] 127.0.0.1:60654 Accepted +[Fri Apr 3 16:23:08 2026] 127.0.0.1:64290 Accepted +[Fri Apr 3 16:23:08 2026] 127.0.0.1:60654 [200]: GET /css/style.css +[Fri Apr 3 16:23:08 2026] 127.0.0.1:64290 [200]: GET /js/astronomy.browser.min.js +[Fri Apr 3 16:23:08 2026] 127.0.0.1:60654 Closing +[Fri Apr 3 16:23:08 2026] 127.0.0.1:64290 Closing +[Fri Apr 3 16:23:08 2026] 127.0.0.1:59075 Accepted +[Fri Apr 3 16:23:08 2026] 127.0.0.1:49458 Accepted +[Fri Apr 3 16:23:08 2026] 127.0.0.1:59075 [200]: GET /images/star16x16.png +[Fri Apr 3 16:23:08 2026] 127.0.0.1:49458 [200]: GET /images/star16x16_rays.png +[Fri Apr 3 16:23:08 2026] 127.0.0.1:59075 Closing +[Fri Apr 3 16:23:08 2026] 127.0.0.1:49458 Closing +[Fri Apr 3 16:23:08 2026] 127.0.0.1:61695 Accepted +[Fri Apr 3 16:23:09 2026] 127.0.0.1:61695 [200]: GET /favicon.ico +[Fri Apr 3 16:23:09 2026] 127.0.0.1:61695 Closing +[Fri Apr 3 16:24:34 2026] 127.0.0.1:57467 Accepted +[Fri Apr 3 16:24:50 2026] 127.0.0.1:57467 [200]: GET /monatsvorhersage_old.php?year=2026&month=3 +[Fri Apr 3 16:24:50 2026] 127.0.0.1:57467 Closing +[Fri Apr 3 16:24:50 2026] 127.0.0.1:58591 Accepted +[Fri Apr 3 16:25:00 2026] 127.0.0.1:58591 [200]: GET /monatsvorhersage_old.php?year=2026&month=10 +[Fri Apr 3 16:25:00 2026] 127.0.0.1:58591 Closing +[Fri Apr 3 16:25:00 2026] 127.0.0.1:54076 Accepted +[Fri Apr 3 16:25:00 2026] 127.0.0.1:61845 Accepted +[Fri Apr 3 16:25:10 2026] 127.0.0.1:54076 [200]: GET /monatsvorhersage_old.php?year=2026&month=10 +[Fri Apr 3 16:25:10 2026] 127.0.0.1:54076 Closing +[Fri Apr 3 16:25:10 2026] 127.0.0.1:60099 Accepted +[Fri Apr 3 16:25:10 2026] 127.0.0.1:61845 [200]: GET /css/style.css +[Fri Apr 3 16:25:10 2026] 127.0.0.1:60099 [200]: GET /js/astronomy.browser.min.js +[Fri Apr 3 16:25:10 2026] 127.0.0.1:61845 Closing +[Fri Apr 3 16:25:10 2026] 127.0.0.1:60099 Closing +[Fri Apr 3 16:25:10 2026] 127.0.0.1:57577 Accepted +[Fri Apr 3 16:25:10 2026] 127.0.0.1:57577 [200]: GET /images/star16x16.png +[Fri Apr 3 16:25:10 2026] 127.0.0.1:58971 Accepted +[Fri Apr 3 16:25:10 2026] 127.0.0.1:58971 [200]: GET /images/star16x16_rays.png +[Fri Apr 3 16:25:10 2026] 127.0.0.1:57577 Closing +[Fri Apr 3 16:25:10 2026] 127.0.0.1:58971 Closing +[Fri Apr 3 16:25:10 2026] 127.0.0.1:65351 Accepted +[Fri Apr 3 16:25:12 2026] 127.0.0.1:65351 [200]: GET /favicon.ico +[Fri Apr 3 16:25:12 2026] 127.0.0.1:65351 Closing diff --git a/public/py/__pycache__/api.cpython-314.pyc b/public/py/__pycache__/api.cpython-314.pyc index 9a9d1fc..4ef38dc 100644 Binary files a/public/py/__pycache__/api.cpython-314.pyc and b/public/py/__pycache__/api.cpython-314.pyc differ diff --git a/public/py/api.py b/public/py/api.py index c0584d4..4208e1f 100644 --- a/public/py/api.py +++ b/public/py/api.py @@ -494,19 +494,47 @@ def action_golden_handle_for_month(args: list[str]) -> dict: else: local_end = datetime(year, month + 1, 1, 0, 0, 0, tzinfo=tz) - # Approximate the Golden Handle geometrically by the effective selenographic - # colongitude over Montes Jura/Sinus Iridum (roughly 44 N, 36 W), - # corrected by libration. Empirically, the best-fit target shifts with - # libration in latitude by several tenths of a degree, i.e. by hours. - def target_colongitude(elat_deg: float) -> float: - return 33.385316705346064 + 0.11176238230626076 * elat_deg + 0.0734267792065295 * elat_deg * elat_deg + # Minimal physical model for the Golden Handle: + # solve the time when the floor of Sinus Iridum is still below sunrise + # by half of the topographic advance angle of the Jura rim. + moon_radius_km = 1737.4 + floor_latitude_deg = 42.0 + floor_longitude_west_deg = 35.5 + relief_height_difference_km = 1.18 - def golden_handle_metric(dt_utc: datetime) -> float: + def relief_advance_angle_deg() -> float: + return math.degrees( + math.acos(moon_radius_km / (moon_radius_km + relief_height_difference_km)) + ) + + def solar_selenographic_coordinates(dt_utc: datetime) -> tuple[float, float]: time_value = dt_to_time(dt_utc) phase_deg = normalize_degrees(astronomy.MoonPhase(time_value)) libration = astronomy.Libration(time_value) - effective_colongitude = normalize_degrees(phase_deg - 90.0 - float(libration.elon)) - return normalize_signed_degrees(effective_colongitude - target_colongitude(float(libration.elat))) + axis_latitudes = calculate_moon_axis_latitudes(time_value) + + # Approximate selenographic solar longitude from colongitude using + # west-positive longitude for the lunar surface model. + colongitude_deg = normalize_degrees(phase_deg - 90.0 - float(libration.elon)) + solar_longitude_west_deg = normalize_signed_degrees(colongitude_deg - 90.0) + solar_latitude_deg = float(axis_latitudes["subsolar_latitude"]) + return solar_longitude_west_deg, solar_latitude_deg + + def floor_solar_altitude_deg(dt_utc: datetime) -> float: + solar_longitude_west_deg, solar_latitude_deg = solar_selenographic_coordinates(dt_utc) + phi = math.radians(floor_latitude_deg) + lam_f = math.radians(floor_longitude_west_deg) + lam_s = math.radians(solar_longitude_west_deg) + b_s = math.radians(solar_latitude_deg) + x = ( + math.sin(phi) * math.sin(b_s) + + math.cos(phi) * math.cos(b_s) * math.cos(lam_f - lam_s) + ) + x = max(-1.0, min(1.0, x)) + return math.degrees(math.asin(x)) + + def golden_handle_metric(dt_utc: datetime) -> float: + return floor_solar_altitude_deg(dt_utc) + relief_advance_angle_deg() / 2.0 def refine_peak_time(left_utc: datetime, right_utc: datetime) -> datetime: left = left_utc