// Pair already-recognized synthetic identities in a COMMON measured clock. // Refuse multiple same-code candidates instead of choosing the smallest skew. // Repeated palettes without an unambiguous time bound are not unique IDs. const valid=e=>e&&Number.isInteger(e.code)&&e.code>=1&&e.code<=16&& Number.isFinite(e.startMs)&&Number.isFinite(e.endMs)&&e.startMs>=0&&e.endMs>=e.startMs&& e.endMs-e.startMs<=250; const quantile=(values,p)=>values.length?[...values].sort((a,b)=>a-b)[Math.ceil(values.length*p)-1]:null; export function pairedSummary(audio,video,{clockUncertaintyMs=null,maxSkewMs=2000}={}) { const result={pairs:0,ambiguous:0,unpairedAudio:audio.length,unpairedVideo:video.length, skewAbsP95UpperMs:null,skewAbsMaxUpperMs:null,medianSignedMs:null,estimatedMedianSignedMs:null, clockUncertaintyMs:null,releaseEligible:false}; if(audio.length>256||video.length>256||!audio.every(valid)||!video.every(valid)|| !Number.isFinite(maxSkewMs)||maxSkewMs<=0||maxSkewMs>10000)return result; for(const events of [audio,video]) { if(events.some((e,i)=>i&&e.startMs<=events[i-1].startMs))return result; } const candidates=audio.map(a=>video.map((v,i)=>a.code===v.code&& Math.max(a.startMs-v.endMs,v.startMs-a.endMs)<=maxSkewMs?i:-1).filter(i=>i>=0)); const reverse=video.map((_,i)=>candidates.reduce((n,c)=>n+Number(c.includes(i)),0)); const pairs=[];let lastVideo=-1; for(let i=0;i(a.startMs+a.endMs-v.startMs-v.endMs)/2),.5); // Identity matches without independently bounded clock alignment must not // be converted into a calibrated skew or accepted qualification result. if(typeof clockUncertaintyMs!=='number'||!Number.isFinite(clockUncertaintyMs)||clockUncertaintyMs<0||clockUncertaintyMs>1000)return result; result.clockUncertaintyMs=clockUncertaintyMs; const signed=[],bounds=[]; for(const [a,v] of pairs) { // Positive = audio later. Interval arithmetic includes detection error. signed.push((a.startMs+a.endMs-v.startMs-v.endMs)/2); bounds.push(Math.max(Math.abs(a.startMs-v.endMs),Math.abs(a.endMs-v.startMs))+clockUncertaintyMs); } result.medianSignedMs=quantile(signed,.5); result.skewAbsP95UpperMs=quantile(bounds,.95); result.skewAbsMaxUpperMs=bounds.length?Math.max(...bounds):null; return result; }