#!/usr/bin/env python3
"""Reproduce Scale Lab signal-delay teaching cases offline; Python3.9+ standard library.
Rounded reference inputs and generated arithmetic are not live ranges or mission predictions.
"""
import argparse,csv,hashlib,json,math,sys
from pathlib import Path
REFS=[
 ('c',299792.458,'km/s','exact_definition','https://www.bipm.org/en/si-base-units/metre'),
 ('au',149597870.7,'km','exact_definition','https://syrte.obspm.fr/IAU_resolutions/Sumredef_au.html'),
 ('mile',1.609344,'km','exact_conversion','https://www.nist.gov/pml/us-surveyfoot/revised-unit-conversion-factors'),
 ('julian_year',365.25,'days','time_convention','https://ssd.jpl.nasa.gov/astro_par.html'),
 ('moon_mean',384400,'km','rounded_mean','https://science.nasa.gov/moon/facts/'),
 ('l2_reference',1500000,'km','rounded_reference','https://ntrs.nasa.gov/citations/20210018417'),
 ('mars_j2000_a',1.52371034,'au','published_element','https://ssd.jpl.nasa.gov/planets/approx_pos.html'),
 ('proxima',4.24,'ly','rounded_distance','https://science.nasa.gov/sun/facts/')]
CASES=[('1','moon','near'),('2','l2','near'),('3','mars','near'),('4','mars','far'),('5','proxima-centauri','near')]
def require(test,message):
    if not test:raise ValueError(message)
def near(a,b):return math.isfinite(a) and math.isclose(a,b,rel_tol=2e-14,abs_tol=2e-9)
def analyze(folder):
    ref_file=folder/'reference-inputs.csv';data_file=folder/'generated-signal-cases.csv'
    refs=list(csv.DictReader(ref_file.open(newline='',encoding='utf-8')));rows=list(csv.DictReader(data_file.open(newline='',encoding='utf-8')))
    require(len(refs)==len(REFS),'Expected eight pinned reference rows');require(len(rows)==5,'Expected five generated cases')
    values={}
    for row,(quantity,value,unit,kind,source) in zip(refs,REFS):
        require(row['quantity']==quantity and float(row['reference_value'])==value and row['unit']==unit and row['reference_kind']==kind and row['source_url']==source,'Reference transcription mismatch')
        values[quantity]=value
    c=values['c'];au=values['au'];mile=values['mile'];year_seconds=values['julian_year']*86400;ly=c*year_seconds;mars=round(values['mars_j2000_a'],3)
    expected_km=[values['moon_mean'],values['l2_reference'],abs(mars-1)*au,(mars+1)*au,values['proxima']*ly];results=[]
    for row,(case,destination,alignment),distance in zip(rows,CASES,expected_km):
        require(row['case_id']==case and row['destination']==destination and row['alignment']==alignment,'Case identity mismatch')
        km=float(row['distance_km']);one=float(row['one_way_s']);reply=float(row['immediate_reply_s'])
        require(near(km,distance),'Generated separation mismatch');require(near(one,distance/c) and near(reply,2*distance/c),'Generated travel-time mismatch')
        units=[]
        for unit,factor in [('km',1),('mi',mile),('au',au),('ly',ly)]:
            value=distance/factor;repeated=value*factor/c;require(near(repeated,one),'Unit-invariance check failed');units.append(dict(unit=unit,inputDistance=value,repeatedOneWaySeconds=repeated))
        results.append(dict(case=case,destination=destination,alignment=alignment,separationKm=distance,separationMiles=distance/mile,separationAu=distance/au,oneWaySeconds=distance/c,immediateReplySeconds=2*distance/c,oneWayJulianYears=distance/c/year_seconds,immediateReplyJulianYears=2*distance/c/year_seconds,geometryOnlyBlocked=(case=='4'),unitChecks=units))
    return dict(methodVersion='1.0',resourceRelease='268',activity='https://darksciencedocs.com/scale-lab/',sourceReviewDate='2026-10-25',provenance='Pinned unit definitions and rounded reference distances; generated static propagation comparisons from the unchanged Scale Lab. Not live ephemerides, telemetry or observed communication timing.',constants=dict(speedOfLightKmS=c,auKm=au,kmPerInternationalMile=mile,julianYearSeconds=year_seconds,lightYearKm=ly,earthTeachingRadiusAu=1,marsTeachingRadiusAu=mars),cases=results,workedMarsNear=dict(case='3',nearSeparationAu=abs(mars-1),roundTripWith30SecondRemoteWait=2*expected_km[2]/c+30,remoteWaitSeconds=30),checks=dict(referenceSha256=hashlib.sha256(ref_file.read_bytes()).hexdigest(),generatedCasesSha256=hashlib.sha256(data_file.read_bytes()).hexdigest(),allCasesAndFourUnitRepresentationsAgree=True),limits='Constant sender-receiver separation, straight vacuum path, instantaneous hypothetical reply. No live positions, orbital motion during exchange, mission scheduling, processing, transmission duration, plasma or relativistic corrections. Mars uses chosen circular radii1/1.524AU, not actual extrema; the Sun blocks a direct path at exact opposite-side alignment. Rounded mean Moon and L2 references are not actual surface/spacecraft ranges. Proxima extension assumes hypothetical receiver and static separation; no detection or inhabited-system claim. Additional digits describe arithmetic, not physical precision.')
def main():
    p=argparse.ArgumentParser(description=__doc__);p.add_argument('--data-dir',type=Path,default=Path('.'));args=p.parse_args()
    try:print(json.dumps(analyze(args.data_dir),indent=2,allow_nan=False))
    except (OSError,ValueError,KeyError,ZeroDivisionError) as error:print('Verification failed: '+str(error),file=sys.stderr);return 1
    return 0
if __name__=='__main__':sys.exit(main())
