export const MODEL_VERSION='1.0.0',EXPERIMENT_ID='course-04/optical-magnification',DEFAULTS=Object.freeze({focal_mm:10,object_ratio:1.2,eyepiece_pitch_mm:.1,wire_width_mm:.05,telescope_objective_mm:200,telescope_eyepiece_mm:40}); export function validateParameters(p){for(const[k,l,h]of[['focal_mm',10,30],['object_ratio',1.1,1.5],['eyepiece_pitch_mm',.05,.2],['wire_width_mm',.02,.08],['telescope_objective_mm',100,300],['telescope_eyepiece_mm',20,50]])if(!Number.isFinite(p[k])||p[k]h)throw new RangeError(`${k}须在${l}–${h}`);return{...p};} export function optics(p){validateParameters(p);const u=p.focal_mm*p.object_ratio,v=p.focal_mm*u/(u-p.focal_mm),magnification=v/u,scale=p.eyepiece_pitch_mm/magnification;return{object_distance_mm:u,image_distance_mm:v,magnification,scale_mm_div:scale,calibration_length_mm:.1,calibration_divisions:.1/scale,wire_divisions:p.wire_width_mm/scale,telescope_magnification:-p.telescope_objective_mm/p.telescope_eyepiece_mm};} export function sameOptics(a,b){return['focal_mm','object_ratio','eyepiece_pitch_mm'].every(k=>a?.[k]===b?.[k]);} export function calibrationScale(records,p,source){validateParameters(p);const selected=records.filter(r=>r.kind==='calibration'&&r.source===source&&sameOptics(r.parameters,p));if(!selected.length)throw new RangeError('先用同一来源、同一光学参数完成标尺标定');return selected.reduce((s,r)=>s+r.scale_mm_div,0)/selected.length;} export function measurement(kind,p,source='ideal-model',left=null,right=null,scale=null){validateParameters(p);if(!['calibration','wire'].includes(kind)||!['ideal-model','manual-reticle'].includes(source))throw new RangeError('记录身份或类型无效');const o=optics(p),a=source==='ideal-model'?1:left,b=source==='ideal-model'?1+(kind==='calibration'?o.calibration_divisions:o.wire_divisions):right;if(!Number.isFinite(a)||!Number.isFinite(b)||b<=a)throw new RangeError('右读数须大于左读数');const divisions=b-a;const base={experiment_id:EXPERIMENT_ID,model_version:MODEL_VERSION,parameters:{...p},source,kind,left_div:a,right_div:b,span_div:divisions};if(kind==='calibration')return{...base,known_length_mm:o.calibration_length_mm,scale_mm_div:o.calibration_length_mm/divisions};if(!Number.isFinite(scale)||scale<=0)throw new RangeError('需要有效且独立读取的标定值');return{...base,calibration_scale_mm_div:scale,diameter_mm:scale*divisions};}