export const MODEL_VERSION='1.0.0';export const DEFAULT_PARAMETERS=Object.freeze({stiffness:5.2,mass:.1032,springMass:0,amplitude:.03,carrier:40000,soundSpeed:343,addedMass:.05,baselinePosition:.249,staticOffset:0,gravity:9.8});export const PARAMETER_BOUNDS=Object.freeze({stiffness:[3,8],mass:[.05,.2],springMass:[0,.05],amplitude:[.01,.05],carrier:[20000,60000],soundSpeed:[330,350],addedMass:[.01,.1],baselinePosition:[.249,.249],staticOffset:[-.005,.005],gravity:[9.8,9.8]}); export function validateParameters(p){for(const[k,[lo,hi]]of Object.entries(PARAMETER_BOUNDS))if(!Number.isFinite(p[k])||p[k]hi)throw new RangeError(`${k} outside [${lo},${hi}]`);return p;} export function properties(p){validateParameters(p);const effectiveMass=p.mass+p.springMass/3,omega=Math.sqrt(p.stiffness/effectiveMass),period=2*Math.PI/omega,staticExtension=p.addedMass*p.gravity/p.stiffness,staticPosition=p.baselinePosition+staticExtension+p.staticOffset;return{effectiveMass,omega,period,staticExtension,staticPosition,maxShift:p.carrier*p.amplitude*omega/p.soundSpeed};} export function motion(p,t){const q=properties(p);if(!Number.isFinite(t)||t<0)throw new RangeError('invalid time');const position=p.amplitude*Math.cos(q.omega*t),velocity=-p.amplitude*q.omega*Math.sin(q.omega*t),frequency=p.carrier*(1+velocity/p.soundSpeed),phaseDifference=2*Math.PI*p.carrier*position/p.soundSpeed;return{position,velocity,frequency,frequencyShift:frequency-p.carrier,phaseDifference,energy:.5*q.effectiveMass*velocity**2+.5*p.stiffness*position**2};} export function inferPeriod(p,elapsed){validateParameters(p);if(!Number.isFinite(elapsed)||elapsed<=0)throw new RangeError('周期须为正有限数');return{period:elapsed,stiffnessIgnoringSpring:4*Math.PI**2*p.mass/elapsed**2,stiffnessWithEffectiveMass:4*Math.PI**2*(p.mass+p.springMass/3)/elapsed**2};} export function periodRecord(p,elapsed,source,markers=null){if(!['ideal-model','manual-cursors'].includes(source))throw new RangeError('unknown source');return{experiment:'course-01/doppler-application',modelVersion:MODEL_VERSION,kind:'period',source,parameters:{...p},raw:{period:elapsed,markers},derived:inferPeriod(p,elapsed)};} export function staticRecord(p,position,source){validateParameters(p);if(!Number.isFinite(position)||position<=p.baselinePosition||!['ideal-model','manual-position'].includes(source))throw new RangeError('位置须大于未加砝码基线');return{experiment:'course-01/doppler-application',modelVersion:MODEL_VERSION,kind:'static',source,parameters:{...p},raw:{position,addedMass:p.addedMass,baseline:p.baselinePosition},derived:{extension:position-p.baselinePosition,stiffness:p.addedMass*p.gravity/(position-p.baselinePosition)}};}