export const MODEL_VERSION='1.0.0';export const DEFAULT_PARAMETERS=Object.freeze({mass1:.34175,mass2:.15449,incident:.7102,restitution:1,flagLength:.02});export const PARAMETER_BOUNDS=Object.freeze({mass1:[.05,1],mass2:[.05,1],incident:[.1,1.5],restitution:[0,1],flagLength:[.005,.05]}); 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 solve(p){validateParameters(p);const total=p.mass1+p.mass2,v1=(p.mass1-p.restitution*p.mass2)/total*p.incident,v2=(1+p.restitution)*p.mass1/total*p.incident;return diagnostics(p,p.incident,v1,v2);} export function diagnostics(p,incident,v1,v2){validateParameters(p);if(![incident,v1,v2].every(Number.isFinite)||incident<=0)throw new RangeError('invalid velocity');const momentumIn=p.mass1*incident,momentumOut=p.mass1*v1+p.mass2*v2,energyIn=.5*p.mass1*incident**2,energyOut=.5*p.mass1*v1**2+.5*p.mass2*v2**2;return{incident,v1,v2,momentumIn,momentumOut,momentumDifference:momentumOut-momentumIn,energyIn,energyOut,energyLoss:energyIn-energyOut,restitution:(v2-v1)/incident};} export function motion(p,t){const q=solve(p);if(!Number.isFinite(t)||t<0)throw new RangeError('invalid time');const collisionTime=.7/p.incident,x1c=1.,x2c=1.1;return t<=collisionTime?{x1:.3+p.incident*t,x2:1.1,v1:p.incident,v2:0,collided:false,collisionTime}:{x1:x1c+q.v1*(t-collisionTime),x2:x2c+q.v2*(t-collisionTime),v1:q.v1,v2:q.v2,collided:true,collisionTime};} export function gateReadings(p){const q=solve(p),time=v=>Math.abs(v)<1e-14?null:p.flagLength/Math.abs(v);return{incidentTime:time(q.incident),time1:time(q.v1),time2:time(q.v2),direction1:q.v1<0?-1:1,direction2:1};} export function fromGate(p,r){validateParameters(p);if(!Number.isFinite(r.incidentTime)||r.incidentTime<=0)throw new RangeError('入射过门时间须大于0');for(const k of['time1','time2'])if(r[k]!==null&&(!Number.isFinite(r[k])||r[k]<=0))throw new RangeError('运动滑块过门时间须大于0');if(![-1,1].includes(r.direction1)||![-1,1].includes(r.direction2))throw new RangeError('invalid direction');return diagnostics(p,p.flagLength/r.incidentTime,r.time1===null?0:r.direction1*p.flagLength/r.time1,r.time2===null?0:r.direction2*p.flagLength/r.time2);} export function energyLossFormula(p){validateParameters(p);return .5*p.mass1*p.mass2/(p.mass1+p.mass2)*(1-p.restitution**2)*p.incident**2;}