@displayxr/inline3d 1.42.1 → 1.43.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -5,6 +5,40 @@ entry points (`.`, `./three`) are frozen for 1.x, while the **scene subpaths** (
5
5
  `./splat`, `./model`) are a preview tier whose options may change in any release. Entries below say
6
6
  which tier they touch, because that is what tells you whether an upgrade can move your pixels.
7
7
 
8
+ ## 1.43.1 — 2026-10-13
9
+
10
+ ### Fixed — `warmEntity` on a display-rig layer
11
+
12
+ - `warmEntity` warmed only through the eye camera(s). A mesh on a layer moved to the display rig
13
+ (`setLayerRig`, e.g. a round stage) is drawn **only** by the layer rig's cameras, with their own
14
+ `shaderParams`, so its real program still linked on first draw while the warm issued a variant
15
+ nobody draws. It now warms through the layer rig's display/post cameras too.
16
+
17
+ ## 1.43.0 — 2026-10-12
18
+
19
+ ### `./splat` (PlayCanvas) — `warmEffect`: no frozen frame when a reveal first plays
20
+
21
+ - **`handle.warmEffect(name, opts)`** compiles the program `playEffect(name, opts)` will need and
22
+ links it in idle time (`KHR_parallel_shader_compile`), so the effect's first frame no longer
23
+ blocks on the link. On a Windows panel `'assemble'` froze for 250–300 ms on every cold session.
24
+ Resolves `true` once warm, `false` when nothing could be warmed (entity scope, custom fragment
25
+ stage). It is also on the deferred handle, so it can be called right after `addSplat`.
26
+ - **`prepareSource(src, { warmEffects: [...] })`** warms effects you will play once that asset is
27
+ in (names or `{ name, opts }`), after its load and its transition pre-warm.
28
+ - docs/splat-effects.md §Warming an effect.
29
+
30
+ ### `./splat` + `./model` (PlayCanvas) — `warmEntity`: a glTF's first draw without the freeze
31
+
32
+ - **`handle.warmEntity(entity)`** (splat handle: content under `handle.engine.root`; model
33
+ handle: defaults to the model) compiles the entity's lit programs in idle time through the
34
+ renderer's own `MeshInstance.getShaderInstance`, with its exact pass, light hash, lights, view
35
+ format and the eye cameras' live shader params. It then links them off the frame
36
+ (`KHR_parallel_shader_compile`, time-sliced issuing), so the first draw no longer blocks.
37
+ PlayCanvas links every new lit program synchronously on first draw: a 13-material car froze
38
+ 0.8–0.95 s on a panel. On Khronos CarConcept (13 programs), headless: 0 links on show (from 13),
39
+ worst frame 20 ms (from 102 ms with the shader cache warm, 2.69 s cold), pixel-identical. Spark
40
+ and three resolve 0. docs/playcanvas-adapter.md §warmEntity.
41
+
8
42
  ## 1.42.1 — 2026-10-12
9
43
 
10
44
  Touches the **preview tier** (`./call`, `./camera`) only: auto-convergence and the stereo-tile depth
package/dist/call.js CHANGED
@@ -1,4 +1,4 @@
1
- /* @displayxr/inline3d 1.42.1 — dist/call.js — Apache-2.0 — https://github.com/DisplayXR/displayxr-web */
1
+ /* @displayxr/inline3d 1.43.1 — dist/call.js — Apache-2.0 — https://github.com/DisplayXR/displayxr-web */
2
2
  var _n=Object.defineProperty;var _e=(r,e)=>()=>(r&&(e=r(r=0)),e);var yn=(r,e)=>{for(var t in e)_n(r,t,{get:e[t],enumerable:!0})};function Ti(r,e,t=1){let i=Pe,n=!!r&&r.displayWidthMeters>0&&r.displayHeightMeters>0&&r.displayPixelWidth>0&&r.displayPixelHeight>0,s=n?[r.displayWidthMeters,r.displayHeightMeters]:[...i.displaySizeM],o=n?[r.displayPixelWidth,r.displayPixelHeight]:[...i.displayPixels],a=(s[0]/o[0]+s[1]/o[1])/2,l=t>0?t:1,c=n?a*l:a*(o[1]/Math.max(1,globalThis.innerHeight||o[1])),f=r?.nominalViewerPosition?.z,m=Number.isFinite(f)&&f>.1;return{canvasSizeM:[(e?.width||0)*c,(e?.height||0)*c],metersPerCssPx:c,displaySizeM:s,nominalViewerM:m?f:i.nominalViewerM,eyeSeparationM:i.eyeSeparationM,source:{size:n?"display":"default",viewer:m?"display":"default",eyeSeparation:"default"}}}var Pe,gt=_e(()=>{"use strict";Pe=Object.freeze({displaySizeM:Object.freeze([.3456,.1944]),displayPixels:Object.freeze([1920,1080]),nominalViewerM:.65,eyeSeparationM:.063})});function Ge(r,e,t,i,n,s,o,a,l,c,f){if(s<0||s+a>t||o<0)return-1;let m=0,h=0,d=0,u=a*l;for(let w=0;w<l;w++){let b=(n+w)*e+i,T=(o+w)*e+t+s;for(let x=0;x<a;x++){let P=r[T+x];m+=P,h+=P*P,d+=r[b+x]*P}}let p=m/u,y=h/u-p*p;return y<1?-1:(d/u-c*p)/(f*Math.sqrt(y))}function us(r,e,t,i,n,s){let o=0,a=0;for(let f=0;f<s;f++){let m=(i+f)*e+t;for(let h=0;h<n;h++){let d=r[m+h];o+=d,a+=d*d}}let l=n*s,c=o/l;return{mean:c,std:Math.sqrt(Math.max(0,a/l-c*c))}}function he(r,e,t,i,n,s,o,{dMin:a,dMax:l,dyMax:c=2,uniq:f}={}){let m=e/2,{mean:h,std:d}=us(r,e,i,n,s,o);if(d<6)return null;let u=-2,p=0,y=0,w=new Map,b=(M,g,v,_,S)=>{for(let D=_;D<=S;D++){let E=n+D;if(!(E<0||E+o>t))for(let F=M;F<=g;F+=v){let H=Ge(r,e,m,i,n,i-F,E,s,o,h,d);H>(w.get(F)??-2)&&w.set(F,H),H>u&&(u=H,p=F,y=D)}}},T=l-a>8;if(b(a,l,T?2:1,-c,c),T&&u>-1){let M=p,g=y;b(Math.max(a,M-2),Math.min(l,M+2),1,Math.max(-c,g-1),Math.min(c,g+1))}if(u<-1)return null;let x=-2,P=M=>{let g=-2;for(let v=-c;v<=c;v++){let _=n+v;if(!(_<0||_+o>t))for(let S=M-1;S<=M+1;S++)g=Math.max(g,Ge(r,e,m,i,n,i-S,_,s,o,h,d))}return g};for(let[M,g]of w){if(Math.abs(M-p)<3||g<=x)continue;let _=g>=(w.get(M-2)??-2)&&g>=(w.get(M+2)??-2)&&g>0?P(M):g;_>x&&(x=_)}if(l-a>12&&x>-2&&u-x<(f??.08))return null;let C=Ge(r,e,m,i,n,i-(p-1),n+y,s,o,h,d),O=Ge(r,e,m,i,n,i-(p+1),n+y,s,o,h,d),I=0,L=C-2*u+O;return C>-1&&O>-1&&L<0&&(I=Math.max(-.5,Math.min(.5,.5*(C-O)/L))),{d:p+I,dy:y,c:u}}function fs(r,e,t,i={}){let n=e/2,s=i.block||Math.max(8,Math.round(n/20)),o=i.step||s,a=i.dMax??Math.round(n*.3),l=i.dMin??-Math.round(n*.05),c=Math.round(n*(i.roiX0??.15)),f=Math.round(n*(i.roiX1??.85))-s,m=Math.round(t*(i.roiY0??.05)),h=Math.round(t*(i.roiY1??.85))-s,d=[];for(let u=m;u<=h;u+=o)for(let p=c;p<=f;p+=o){let y=he(r,e,t,p,u,s,s,{dMin:l,dMax:a,dyMax:i.dyMax??2,uniq:i.uniq});y&&y.c>=(i.minNcc??.8)&&d.push({x:p,y:u,...y})}return d}function ps(r,{tol:e=2,relTol:t=.1,minFrac:i=.12,minBlocks:n=3}={}){if(!r.length)return null;let s=Math.max(n,Math.ceil(r.length*i)),o=r.map(a=>a.d).sort((a,l)=>l-a);for(let a of o){let l=Math.max(e,t*Math.abs(a)),c=r.filter(f=>Math.abs(f.d-a)<=l);if(c.length>=s){let f=c.map(m=>m.d).sort((m,h)=>m-h);return{d:f[f.length>>1],members:c}}}return null}function ms(r,e){let t=Math.min(...r.map(l=>l.y)),n=r.filter(l=>l.y<=t+2*e).map(l=>l.x),s=Math.min(...n),o=Math.max(...n)+e,a=o-s;return{x:(s+o)/2,y:t+.4*1.3*a}}function Xt(r,e,t,i={}){let n=e/2,s=i.block||Math.max(8,Math.round(n/20)),o=i.dMax??Math.round(n*.3),a=i.dMin??-Math.round(n*.05),l=i.focus||null,c="focus",f=0,m=null;if(!l){let T=fs(r,e,t,{...i,block:s,dMax:o,dMin:a});f=T.length;let x=ps(T,i);if(!x)return null;l=ms(x.members,s),m=x.d,c="mode"}let h=Math.round(s*(i.refineW??3)),d=Math.round(s*(i.refineH??1.5)),u=Math.round(Math.max(0,Math.min(n-h,l.x-h/2))),p=Math.round(Math.max(0,Math.min(t-d,l.y-d/2))),y=m===null?a:Math.max(a,Math.floor(m)-4),w=m===null?o:Math.min(o,Math.ceil(m)+4),b=he(r,e,t,u,p,h,d,{dMin:y,dMax:w,dyMax:i.dyMax??2});return b&&b.c>=(i.minNcc??.8)?{d:b.d,x:l.x,y:l.y,c:b.c,method:c,blocks:f}:m!==null?{d:m,x:l.x,y:l.y,c:0,method:c,blocks:f}:null}function Je(r,e){let t=new Uint8Array(e);for(let i=0,n=0;i<e;i++,n+=4)t[i]=r[n]*77+r[n+1]*150+r[n+2]*29>>8;return t}function Ye({n:r=3}={}){let e=[],t={value:null,push(i){if(i===null||!Number.isFinite(i))return t.value;e.push(i),e.length>r&&e.shift();let n=[...e].sort((s,o)=>s-o);return t.value=n[n.length>>1],t.value},reset(){e.length=0,t.value=null}};return t}function ur(r,e,t,i,n,s){let o=new Float32Array(n*s);for(let a=0;a<s;a++)for(let l=0;l<n;l++)o[a*n+l]=r[(i+a)*e+t+l];return o}function gs(r,e,t,i,n,s,o,a,l,c){let f=e/2,m=n*s,h=0,d=0;for(let b=0;b<m;b++)h+=i[b],d+=i[b]*i[b];h/=m;let u=Math.sqrt(Math.max(1e-6,d/m-h*h)),p=-2,y=o,w=a;for(let b=Math.max(0,a-c);b<=Math.min(t-s,a+c);b++)for(let T=Math.max(0,o-l);T<=Math.min(f-n,o+l);T++){let x=0,P=0,C=0;for(let M=0;M<s;M++){let g=(b+M)*e+T;for(let v=0;v<n;v++){let _=r[g+v];x+=_,P+=_*_,C+=_*i[M*n+v]}}let O=x/m,I=P/m-O*O;if(I<1)continue;let L=(C/m-O*h)/(Math.sqrt(I)*u);L>p&&(p=L,y=T,w=b)}return{x:y,y:w,c:p}}function De({fullEveryMs:r=2e3,dWin:e=4,rx:t=12,ry:i=8,minTrackNcc:n=.75,jump:s=8}={}){let o=null,a=null;return{measure(c,f,m,h,d={}){let u=f/2,p=d.block||Math.max(8,Math.round(u/20)),y=Math.round(p*3),w=Math.round(p*1.5),b=null;if(o&&o.w===y&&o.h===w){let O=gs(c,f,m,o.tpl,y,w,o.x,o.y,t,i);if(O.c>=n){let I=he(c,f,m,O.x,O.y,y,w,{dMin:Math.floor(o.d)-e,dMax:Math.ceil(o.d)+e,dyMax:2});I&&I.c>=.8&&(b={m:I,f:O})}}let T=({m:O,f:I})=>(o={...o,x:I.x,y:I.y,d:O.d,tpl:ur(c,f,I.x,I.y,y,w)},{d:O.d,x:I.x+y/2,y:I.y+w/2,c:O.c,method:"track",blocks:0});if(b&&h-o.at<r)return T(b);let x=Xt(c,f,m,d);if(b&&(!x||Math.abs(x.d-b.m.d)>s)){let O=x&&a&&Math.abs(a.d-x.d)<=3;if(a=x&&!O?{d:x.d}:null,!O)return o.at=h,T(b)}else a=null;if(!x)return o=null,null;let P=Math.round(Math.max(0,Math.min(u-y,x.x-y/2))),C=Math.round(Math.max(0,Math.min(m-w,x.y-w/2)));return o={x:P,y:C,d:x.d,tpl:ur(c,f,P,C,y,w),w:y,h:w,at:h},x},reset(){o=null,a=null}}}function fr(r,e,t,i,n,s){let o=Math.max(1,s|0),a=Math.floor(i/o);a-=a&1;let l=Math.floor(n/o),c=new Uint8Array(a*l);if(o<=2){let h=o*o;for(let d=0;d<l;d++)for(let u=0;u<a;u++){let p=0;for(let y=0;y<o;y++){let w=e+(d*o+y)*t+u*o;for(let b=0;b<o;b++)p+=r[w+b]}c[d*a+u]=p/h|0}return{img:c,w:a,h:l}}let f=o>>2,m=3*o>>2;for(let h=0;h<l;h++){let d=e+(h*o+f)*t,u=e+(h*o+m)*t;for(let p=0;p<a;p++){let y=p*o+f,w=p*o+m;c[h*a+p]=r[d+y]+r[d+w]+r[u+y]+r[u+w]>>2}}return{img:c,w:a,h:l}}var Gt=_e(()=>{"use strict"});function _s(r,e="@displayxr/inline3d"){if(r==null||r===!1)return null;if(r==="depth"||r===!0)return{};if(typeof r!="object")throw new Error(`${e}: cursor ${JSON.stringify(r)} \u2014 expected 'depth' or { height, margin, anchor, pointerScope }.`);let t=Object.keys(r).filter(i=>!mr.includes(i));if(t.length)throw new Error(`${e}: cursor \u2014 unknown option(s) ${t.join(", ")}; expected ${mr.join(", ")}.`);if(r.anchor!==void 0&&!pr.includes(r.anchor))throw new Error(`${e}: cursor.anchor "${r.anchor}" \u2014 expected ${pr.join(", ")}.`);if(r.pointerScope!==void 0&&r.pointerScope!=="canvas"&&r.pointerScope!=="window")throw new Error(`${e}: cursor.pointerScope "${r.pointerScope}" \u2014 expected 'canvas' or 'window'.`);for(let i of["height","margin"])if(r[i]!==void 0&&!(typeof r[i]=="number"&&r[i]>0&&Number.isFinite(r[i])))throw new Error(`${e}: cursor.${i} must be a positive number.`);return{...r}}function Ke(r,e="@displayxr/inline3d"){let t=_s(r,e);if(!t)return null;let i=Object.keys(t).filter(n=>!gr.includes(n));if(i.length)throw new Error(`${e}: cursor \u2014 ${i.join(", ")} not supported on a stereo pair; expected ${gr.join(", ")}.`);return t}var pr,mr,gr,Jt=_e(()=>{"use strict";pr=Object.freeze(["hybrid","screen","world"]),mr=Object.freeze(["height","margin","anchor","pointerScope"]);gr=Object.freeze(["height","pointerScope"])});function Lr(r){return r>0&&Number.isFinite(r)?Math.min(.5,ti/r):Ms}function Ar(r,e){return r<=Es?!(e>0&&Number.isFinite(e))||-r*e>=Rs:!1}function ii(r,e,t){if(!e||!Number.isFinite(t)||!(t<0))return 0;let i=Math.min(0,Number.isFinite(r.maxDisparity)?r.maxDisparity:0);return Math.min(i,Math.max(r.minDisparity,t-r.margin))}function Or(r,e,t,i){if(!r.primed||i<r.last||i-r.last>e.stale)return r.primed=!0,r.disparity=t,r.last=i,t;if(i===r.last)return r.disparity;let n=i-r.last,s=t<r.disparity?e.riseTau:e.sinkTau,o=s>0?1-Math.exp(-n/s):1;return r.disparity+=o*(t-r.disparity),r.last=i,r.disparity}function Cs(r){let e=globalThis.document;if(!(!e||!e.documentElement)){if(r&&!e.getElementById("inline3d-cursor-style")){let t=e.createElement("style");t.id="inline3d-cursor-style",t.textContent="html.inline3d-cursor-hidden, html.inline3d-cursor-hidden * { cursor: none !important; }",(e.head||e.documentElement).appendChild(t)}ei=Math.max(0,ei+(r?1:-1)),e.documentElement.classList.toggle("inline3d-cursor-hidden",ei>0)}}function ks(r,e,t,i){let n=.75*t,s=[[r,e]];for(let o=0;o<8;o++){let a=o*Math.PI/4;s.push([r+n*Math.cos(a)/i,e+n*Math.sin(a)])}return s}function Pr(r,e,t,i){return[[r,e],...ks(r+.3*t/i,e+.5*t,t,i)]}function ri(r,e=rt){let t=0;for(let s=0;s<e.length;s++){let[o,a]=e[s],[l,c]=e[(s+1)%e.length];t+=o*c-l*a}let i=t>0?1:-1,n=e.map((s,o)=>{let a=e[(o+1)%e.length],l=a[0]-s[0],c=a[1]-s[1],f=Math.hypot(l,c)||1;return[-c/f*i,l/f*i]});return e.map((s,o)=>{let a=n[(o+e.length-1)%e.length],l=n[o],c=r/(1+a[0]*l[0]+a[1]*l[1]);return[s[0]+(a[0]+l[0])*c,s[1]+(a[1]+l[1])*c]})}var Ms,ti,Es,Rs,it,tt,ei,rt,Ho,$o,Uo,Vo,Ir=_e(()=>{"use strict";Ms=.03,ti=19;Es=-.005,Rs=.5;it=Object.freeze({margin:.015,minDisparity:-.6,maxDisparity:0,riseTau:.03,sinkTau:.25,stale:.5});tt=class{constructor(e,{scope:t="canvas"}={}){if(t!=="canvas"&&t!=="window")throw new Error(`@displayxr/inline3d: cursor pointerScope "${t}" \u2014 expected 'canvas' or 'window'.`);this.canvas=e,this.scope=t,this.uv=null,this._hidden=!1,this._prev="",this._onMove=i=>{let n=e.getBoundingClientRect();if(!(n.width>0&&n.height>0))return void(this.uv=null);let s=(i.clientX-n.left)/n.width,o=(i.clientY-n.top)/n.height;this.uv=s>=0&&s<=1&&o>=0&&o<=1?[s,o]:null},this._onLeave=()=>{this.uv=null},this._target=t==="window"?globalThis:e,this._target.addEventListener("pointermove",this._onMove),t==="window"?(this._onOut=i=>{i.relatedTarget||(this.uv=null)},globalThis.addEventListener("pointerout",this._onOut),globalThis.addEventListener("blur",this._onLeave)):e.addEventListener("pointerleave",this._onLeave)}set(e,t){this.uv=e==null?null:[e,t]}aspect(){let e=this.canvas.getBoundingClientRect();return e.height>0?e.width/e.height:1}cssHeight(){let e=this.canvas.getBoundingClientRect();return e.height>0?e.height:0}panelPx(){return this.cssHeight()*(globalThis.devicePixelRatio>0?globalThis.devicePixelRatio:1)}hideCss(e){e!==this._hidden&&(this.scope==="window"?Cs(e):e?(this._prev=this.canvas.style.cursor||"",this.canvas.style.cursor="none"):this.canvas.style.cursor==="none"&&(this.canvas.style.cursor=this._prev),this._hidden=e)}dispose(){this._target.removeEventListener("pointermove",this._onMove),this.scope==="window"?(globalThis.removeEventListener("pointerout",this._onOut),globalThis.removeEventListener("blur",this._onLeave)):this.canvas.removeEventListener("pointerleave",this._onLeave),this.hideCss(!1)}},ei=0;rt=Object.freeze([[0,0],[0,16],[4.2,12.4],[6.9,18.6],[9.5,17.5],[6.8,11.5],[11.7,11.5]].map(([r,e])=>Object.freeze([r/19,e/19]))),Ho=Object.freeze([[0,1,2],[0,2,5],[0,5,6],[2,3,4],[2,4,5]]);$o=Object.freeze(ri(1/19).map(r=>Object.freeze(r))),Uo=Object.freeze([1,1,1,1]),Vo=Object.freeze([.02,.02,.03,1])});var oi={};yn(oi,{PAIR_CURSOR_ARROW:()=>Ds,PAIR_CURSOR_CSS_PX:()=>Ts,PAIR_CURSOR_CSS_PX_M:()=>Nr,PAIR_CURSOR_HEIGHT:()=>jr,PAIR_SUPPORT_COPY_PX:()=>Hr,PairCursor:()=>si,consistentMatch:()=>ni,drawPairArrow:()=>Br,pairCursorScale:()=>Ur,pairCursorTarget:()=>As,pairFootprintDisparity:()=>zr,pairNearestSupported:()=>$r,pairPxPerBaseline:()=>Vr});function zr(r,e,t,i,n,s){if(!r||!r.img||!e||!e.rL||!e.rR||!(e.outW>0))return null;let{img:o,w:a,h:l,scale:c}=r,f=a/2,m=Math.max(8,Math.round(f/20)),h=Math.round(f*.3),d=-Math.round(f*.05),{rL:u,rR:p}=e,y=e.outW/u.sw,w=Ls(r),b=[],T=new Set,x=(M,g)=>{let v=Math.round((u.sy+g*u.sh)*c-m/2);if(v<0||v+m>l)return;let _=Math.round((u.sx+M*u.sw)*c-m/2),S=Math.round((p.sx+M*p.sw)*c-m/2);if(!T.has(`L${_},${v}`)){T.add(`L${_},${v}`);let D=ni(o,w,a,l,_,v,m,d,h);D!==null&&b.push({d:D,x:_,y:v,eye:0})}if(!T.has(`R${S},${v}`)){T.add(`R${S},${v}`);let D=ni(w,o,a,l,S,v,m,-h,-d);D!==null&&b.push({d:-D,x:S,y:v,eye:1})}},P=0,C=0,O=0;for(let[M,g]of Pr(t,i,n,s))M>=0&&M<=1&&g>=0&&g<=1&&(x(e.mirror?1-M:M,g),P+=M,C+=g,O++);if(!O)return null;let I=m/2/c/u.sw,L=m/2/c/u.sh;for(let[M,g]of[[-I,0],[I,0],[0,-L],[0,L]]){let v=P/O+M,_=C/O+g;v>=0&&v<=1&&_>=0&&_<=1&&x(e.mirror?1-v:v,_)}for(let M of b)M.d=(M.d/c-(u.sx-p.sx))*y;return $r(b,Hr/c*y,m/2)}function Ls(r){let e=Wr.get(r);if(e)return e;let{img:t,w:i,h:n}=r,s=i/2;e=new Uint8Array(i*n);for(let o=0;o<n;o++){let a=o*i;for(let l=0;l<s;l++)e[a+l]=t[a+s+l],e[a+s+l]=t[a+l]}return Wr.set(r,e),e}function ni(r,e,t,i,n,s,o,a,l){let c=t/2;if(n<0||n+o>c)return null;let f=he(r,t,i,n,s,o,o,{dMin:a,dMax:l,dyMax:2});if(!f||f.c<.8)return null;let m=Math.round(n-f.d),h=s+f.dy;if(m<0||m+o>c||h<0||h+o>i)return null;let d=he(e,t,i,m,h,o,o,{dMin:-Math.ceil(f.d)-2,dMax:-Math.floor(f.d)+2,dyMax:1});return!d||d.c<.8||Math.abs(-d.d-f.d)>1?null:f.d}function $r(r,e,t=0){let i=r.map(o=>typeof o=="number"?{d:o,x:0,y:0,eye:-1}:o);if(i.length<2)return null;let n=[...i].sort((o,a)=>a.d-o.d),s=(o,a)=>o.eye===-1||o.eye!==a.eye||Math.abs(o.x-a.x)+Math.abs(o.y-a.y)>=t;for(let o of n)for(let a of n)if(a!==o&&Math.abs(o.d-a.d)<=e&&s(o,a))return o.d;return null}function Ur(r,e,t){let i=t>0&&Number.isFinite(t)?t:Nr;return e>0?r/e/i:r/(1e3*i)}function Vr(r,e,t,i=Pe.eyeSeparationM){return Ur(r,e,t)*i}function As(r,e,t=it){return r===null||!Number.isFinite(r)||!(e>0)?0:-ii(t,!0,-r/e)*e||0}function Os(r){let e=r>0?Math.max(.05263157894736842,1/r):.05263157894736842,t=Math.round(e*1e4),i=nt.get(t);return i||(nt.size>32&&nt.clear(),nt.set(t,i=ri(e))),i}function Br(r,e,t,i,n,s,o){let a=n*t,l=Os(o);for(let c of[0,1]){let f=c*e+i*e+(c===0?s/2:-s/2);r.save(),r.beginPath(),r.rect(c*e,0,e,t),r.clip();for(let[m,h]of[[rt,"#000"],[l,"#fff"]])r.beginPath(),m.forEach(([d,u],p)=>(p?r.lineTo:r.moveTo).call(r,f+d*o,a+u*o)),r.closePath(),r.fillStyle=h,r.fill();r.restore()}}var Ts,jr,Ds,Nr,Hr,Wr,nt,si,ai=_e(()=>{"use strict";Ir();gt();Gt();Ts=ti,jr=.06,Ds=rt,Nr=.0254/96;Hr=1.5,Wr=new WeakMap;nt=new Map;si=class{constructor(e,{height:t,pointerScope:i="window"}={}){this.pointer=new tt(e,{scope:i}),this.height=t>0?t:null,this.filter={},this._luma=null,this._uv=null,this._content=null,this._metricsFor=null,this.metersPerCssPx=null,this.eyeSeparationM=Pe.eyeSeparationM,this.last=null}get hovering(){return!!this.pointer.uv}draw(e,t,i,n){let s=this.pointer.uv;if(!s)return this.idle();let[o,a]=s,l=this.height??this._osArrowFraction();(t!==this._luma||!this._uv||this._uv[0]!==o||this._uv[1]!==a)&&(this._content=zr(t,i,o,a,l,i.outW/i.outH),this._luma=t,this._uv=s),this._askMetrics(i.handle);let c=this.pointer.canvas.getBoundingClientRect?.(),f=Vr(i.outW,c&&c.width,this.metersPerCssPx,this.eyeSeparationM),m=this._content===null?null:-this._content/f,h=ii(it,m!==null,m),d=Or(this.filter,it,h,n),u=Ar(d,f),p=-d*f;return u&&Br(e,i.outW,i.outH,o,a,p,l*i.outH),this.pointer.hideCss(u),this.last={contentPx:this._content,targetPx:-h*f||0,disparityPx:u?p:0,filteredPx:p||0,drawn:u,pxPerBaseline:f},this.last}_askMetrics(e){!e||e===this._metricsFor||typeof e.displayMetrics!="function"||(this._metricsFor=e,Promise.resolve().then(()=>e.displayMetrics()).then(t=>{this._metricsFor!==e||!t||(t.metersPerCssPx>0&&(this.metersPerCssPx=t.metersPerCssPx),t.eyeSeparationM>0&&(this.eyeSeparationM=t.eyeSeparationM))}).catch(()=>{}))}_osArrowFraction(){let e=this.pointer.canvas.getBoundingClientRect?.();return e&&e.height>0?Lr(e.height):jr}idle(){return this.pointer.hideCss(!1),this.filter={},this._uv=null,this.last=null,null}dispose(){this.pointer.dispose()}}});var ve=Object.freeze({speaker:"high",other:"normal",hidden:"paused"}),vn=["high","normal","low","paused"],wn=.02,bn=1e3/20,xn=3e3,Sn=1e3/28;function be(r){return typeof r=="function"?r:r===!1||r==="off"?"off":"auto"}function dt(r=import.meta.url){return ye?typeof ye=="function"?ye():ye:new URL("../lift/index.js",r).href}var ye=null;function Ri(r){ye=r||null}function Mn(){return import(dt())}async function ut(r,{importer:e=Mn,nav:t=globalThis.navigator,log:i=null,capsOpts:n={webFallback:!0}}={}){let s=(h,d)=>i&&i(h,d),o=(h,d={})=>(s("lift-unavailable",{reason:h,...d}),{lift:null,capabilities:null,caps:null,source:"none",reason:h}),a=be(r);if(a==="off")return{lift:null,capabilities:null,caps:null,source:"none",reason:"off"};if(typeof a=="function"){let h=typeof a.liftCapabilities=="function"?a.liftCapabilities:null;return{lift:a,capabilities:h,caps:null,source:"injected",reason:null}}let l;try{l=await e()}catch(h){return o("import-failed",{message:String(h&&h.message||h).slice(0,200)})}let c=l&&typeof l.lift=="function"?l.lift:null;if(!c)return o("no-lift-export");let f=typeof l.liftCapabilities=="function"?l.liftCapabilities:null,m=null;if(f){try{m=await f(n)}catch{m=null}let h=!!(m&&m.webFallback&&m.webFallback.webgpu);if(m&&!m.native&&!h)return o("no-provider",{native:!1,webgpu:!1})}else if(!(t&&t.gpu))return o("no-webgpu");return s("lift-ready",{native:!!(m&&m.native),provider:m&&m.provider||null}),{lift:c,capabilities:f,caps:m,source:"module",reason:null}}function we(r){let e=Number.isFinite(+r)&&r!==null?Math.max(-1,Math.min(1,+r)):0;return Math.abs(e)<wn?"auto":Math.round((.5+.5*e)*1e3)/1e3}function Oe({id:r,speakerId:e,visible:t=!0}){return t?r!=null&&r===e?ve.speaker:ve.other:ve.hidden}function ft(r,e){if(!r||!vn.includes(e)||typeof r.setPriority!="function")return!1;try{return r.setPriority(e)!==!1}catch{return!1}}function pt({degradedMs:r=bn,recoveredMs:e=Sn,holdMs:t=xn,alpha:i=.1}={}){let n=null,s=0,o=!1;return{feed(a){if(!(a>0)||a>1e3)return null;if(n=n===null?a:n+i*(a-n),o){if(n<e)return o=!1,s=0,"recovered"}else if(s=n>r?s+a:0,s>=t)return o=!0,"degraded";return null},reset(){n=null,s=0,o=!1},get frameMs(){return n},get degraded(){return o}}}function mt({lift:r,max:e=4,log:t=null,options:i=null}={}){let n=i&&typeof i=="object"?i:{},s=new Map,o=null,a=0,l=0,c=(h,d)=>t&&t(h,d),f=(h,d)=>{if(!d.handle)return;let u=Oe({id:h,speakerId:o,visible:d.visible});if(u===d.priority)return;d.priority=u;let p=ft(d.handle,u);c("lift-priority",{peer:h,priority:u,applied:p})},m={get size(){return s.size},get max(){return e},set max(h){e=Math.max(0,h|0)},has:h=>s.has(h),handle:h=>s.get(h)&&s.get(h).handle||null,priority:h=>s.get(h)&&s.get(h).priority||null,canAcquire:h=>s.has(h)||s.size<e,acquire(h,d,{wall:u,visible:p=!0,onError:y}={}){let w=s.get(h);if(w)return w.pending||Promise.resolve(w.handle);if(s.size>=e)return Promise.resolve(null);let b=++l,T=Oe({id:h,speakerId:o,visible:p}),x={gen:b,handle:null,visible:p,priority:T,pending:null};s.set(h,x);let P={quality:"auto",...n,mode:"live",wall:u,ui:"none",convergence:we(a),priority:T};return c("lift-acquire",{peer:h,priority:T,convergence:P.convergence}),x.pending=Promise.resolve().then(()=>r(d,P)).then(C=>{if(s.get(h)!==x||x.gen!==b){try{C&&C.remove&&C.remove()}catch{}return null}return x.pending=null,x.handle=C||null,x.priority=null,f(h,x),C&&typeof C.setConvergence=="function"&&C.setConvergence(we(a)),x.handle},C=>(s.get(h)===x&&s.delete(h),c("lift-failed",{peer:h,message:String(C&&C.message||C).slice(0,200)}),y&&y(C),null)),x.pending},release(h){let d=s.get(h);if(d){if(s.delete(h),d.handle)try{d.handle.remove()}catch{}c("lift-release",{peer:h})}},releaseAll(){for(let h of[...s.keys()])m.release(h)},setSpeaker(h){o=h===void 0?null:h;for(let[d,u]of s)f(d,u)},setVisible(h,d){let u=s.get(h);!u||u.visible===!!d||(u.visible=!!d,f(h,u))},setDepth(h){a=h;let d=we(h);for(let u of s.values())u.handle&&typeof u.handle.setConvergence=="function"&&u.handle.setConvergence(d);return d},get anyWeb(){for(let h of s.values())if(h.handle&&!h.handle.native)return!0;return!1},list(){return[...s].map(([h,d])=>({id:h,priority:d.priority,pending:!!d.pending,native:!!(d.handle&&d.handle.native)}))}};return m}var Rn=null;function Ci(r){Rn=typeof r=="function"?r:null}var Cn=new WeakMap;function ki(r,e){r&&(typeof r=="object"||typeof r=="function")&&Cn.set(r,e)}gt();var Di=new WeakMap,J;function Li(r){let e=null;for(let[t,i]of r)i>0&&(!e||i<e[1])&&(e=[t,i]);return e}function kn(r){if(!r||typeof r.getParameter!="function")return null;let e=Di.get(r);if(e)return e;let t,i,n;try{t=r.getParameter(r.MAX_TEXTURE_SIZE??3379),i=r.getParameter(r.MAX_RENDERBUFFER_SIZE??34024),n=r.getParameter(r.MAX_VIEWPORT_DIMS??3386)}catch{return null}let s=n&&n.length>=2?n[0]:0,o=n&&n.length>=2?n[1]:0,a=Li([["MAX_TEXTURE_SIZE",t],["MAX_RENDERBUFFER_SIZE",i],["MAX_VIEWPORT_DIMS",s]]),l=Li([["MAX_TEXTURE_SIZE",t],["MAX_RENDERBUFFER_SIZE",i],["MAX_VIEWPORT_DIMS",o]]);if(!a||!l)return null;let c={maxW:a[1],maxH:l[1],nameW:a[0],nameH:l[0],valueW:a[1],valueH:l[1]};return Di.set(r,c),c}function Ai(){if(J!==void 0)return J;J=null;try{if(typeof document>"u"||!document||typeof document.createElement!="function")return J;let r=document.createElement("canvas"),e=r.getContext("webgl2")||r.getContext("webgl");if(!e)return J;J=kn(e),e.getExtension?.("WEBGL_lose_context")?.loseContext?.()}catch{J=null}return J}function Oi(r,e,t,{cols:i=2,rows:n=1}={}){let s=r*i,o=e*n,a={eyeW:r,eyeH:e,cols:i,scale:1,clamped:!1,reqW:s,reqH:o,bufW:s,bufH:o,limitName:"",limitValue:0};if(!t||!(r>0)||!(e>0))return a;let l=t.maxW/s,c=t.maxH/o,f=Math.min(l,c);if(!(f<1))return a;let m=l<=c,h=Math.max(1,Math.min(Math.floor(r*f+1e-9),Math.floor(t.maxW/i))),d=Math.max(1,Math.min(Math.floor(e*f+1e-9),Math.floor(t.maxH/n)));return{eyeW:h,eyeH:d,cols:i,scale:f,clamped:!0,reqW:s,reqH:o,bufW:h*i,bufH:d*n,limitName:m?t.nameW:t.nameH,limitValue:m?t.valueW:t.valueH}}function _t(r){return Math.round(r*1e3)/1e3}function Pi(r,e,t){let i=typeof t=="number"?` (renderScale ${_t(t)} \u2192 ${_t(t*e.scale)})`:` (scale ${_t(e.scale)})`;return`${r} ${e.cols===1?"buffer":"SBS buffer"} ${e.reqW}\xD7${e.reqH} exceeds ${e.limitName} ${e.limitValue} on this device; rendering at ${e.bufW}\xD7${e.bufH}${i}.`}function Ii(r,e){let t=r?.width||0,i=r?.height||0,n=e&&e.drawingBufferWidth>0?e.drawingBufferWidth:t,s=e&&e.drawingBufferHeight>0?e.drawingBufferHeight:i,o=t>0?n/t:1,a=i>0?s/i:1;return{sx:o,sy:a,w:n,h:s,mismatch:o!==1||a!==1}}function Fi(r,e,t,i="laying the eyes out from the drawing buffer. Lower renderScale to stay inside the device limit."){return`${r} the browser clamped this canvas's drawing buffer to ${t.w}\xD7${t.h} (canvas.width/height say ${e.width}\xD7${e.height}); ${i}`}var re="smoothstep",vt=["linear","smoothstep","easeoutcubic"];function wt(r){if(typeof r!="string")return null;let e=r.toLowerCase().replace(/[-_\s]/g,"");return vt.includes(e)?e:null}function Tn(r,e){if(e<=0)return 0;if(e>=1)return 1;if(r==="linear")return e;if(r==="easeoutcubic"){let t=1-e;return 1-t*t*t}return e*e*(3-2*e)}var ie="idle",Wi="rampDownThenFire",yt="fireThenRampUp",Ie=class{constructor(e,t){this._phase=ie,this._targetMode=null,this._firePending=!1,this._fireAtEnd=!1,this._from=0,this._to=0,this._cur=0,this._t=1,this._dur=180/1e3,this._easing=re,(e!==void 0||t!==void 0)&&this.configure(e,t)}configure(e,t){if(e!==void 0){let i=Number(e);this._dur=Number.isFinite(i)&&i>0?i:0}return t!==void 0&&(this._easing=wt(t)||re),this}get durationS(){return this._dur}get easing(){return this._easing}request({targetMode:e=null,targetViewCount:t,currentMode:i=null,currentViewCount:n,current:s,steady:o=1}={}){let a=!(t>1),l=!(n>1),c=Number.isFinite(o)?o:1,f=Number.isFinite(s)?s:c;return this._targetMode=e,this._from=f,a&&!l?(this._phase=Wi,this._to=0,this._fireAtEnd=!0,this._firePending=!1):!a&&l?(this._phase=yt,this._from=0,this._to=c,this._firePending=!0,this._fireAtEnd=!1):(this._phase=yt,this._to=a?f:c,this._firePending=!(e!==null&&e===i),this._fireAtEnd=!1),this._t=this._dur>0?0:1,this._cur=this._from,this}update(e){let t=!1;if(this._phase!==ie){let i=Number.isFinite(e)&&e>0?e:0;this._t<1&&this._dur>0?(this._t+=i/this._dur,this._t>1&&(this._t=1)):this._t=1,this._cur=this._from+(this._to-this._from)*Tn(this._easing,this._t),this._phase===yt?(this._firePending&&(this._firePending=!1,t=!0),this._t>=1&&(this._phase=ie)):this._t>=1&&(this._fireAtEnd&&(this._fireAtEnd=!1,t=!0),this._phase=ie)}return{factor:this._cur,fire:t,mode:this._targetMode}}active(){return this._phase!==ie}value(){return this._cur}firePending(){return this._phase===Wi?this._fireAtEnd:this._firePending}cancel(){return this._phase=ie,this._firePending=!1,this._fireAtEnd=!1,this._t=1,this}};var B=null;Ci(r=>{let e=B;if(!e||!e._running||!r)return null;let t=i=>({layer:i.layer,session:e.session});for(let i of e._windows.values())if(i.canvas===r&&i.layer)return t(i);for(let i of e._windows.values())if(i.layer&&typeof r.contains=="function"&&r.contains(i.canvas))return t(i);for(let i of e._windows.values()){let n=i.canvas.parentElement;if(i.layer&&n&&typeof n.contains=="function"&&n.contains(r))return t(i)}return null});var Dn=()=>typeof navigator<"u"&&!!navigator.xr,Q=()=>typeof window<"u"&&typeof window.XRDisplayLayer=="function",Fe=()=>Q()&&"excludeElement"in window.XRDisplayLayer.prototype,bt=()=>{if(!Q())return!1;try{return"setViewRig"in window.XRDisplayLayer.prototype}catch{return!1}},Ln=["getDisplayInfo","getRenderingModes","requestRenderingMode"],Gi=["renderingmodechange","hardwaredisplaystatechange"],Dt="trackingstatechange",Ji=["unknown","tracking","searching"],Ni=[...Gi,Dt];function We(r){try{let e=r?r.trackingState:void 0;return Ji.includes(e)?e:null}catch{return null}}var xt=()=>{if(!Q())return!1;try{let r=window.XRDisplayLayer.prototype;return Ln.every(e=>typeof r[e]=="function")}catch{return!1}},St=()=>{if(!Q())return!1;try{return typeof window.XRDisplayLayer.prototype.undock=="function"}catch{return!1}},se=null;function ne(){if(se!==null)return se;if(!Q())return!1;try{let r=window.XRDisplayLayer.occlusionByDrawOrder;if(typeof r=="boolean")return se=r}catch{}return!1}function An(r){if(se!==null)return se;if(!r||!Q()||!("occlusionByDrawOrder"in window.XRDisplayLayer.prototype))return null;try{return se=!!r.occlusionByDrawOrder}catch{return null}}var zi=!1;function Mt(){zi||(zi=!0,console.info("[inline3d] This browser composites 2D over woven 3D automatically, per-pixel by draw order \u2014 overlay exclusion is obsolete here, so exclude()/addGlobalOverlay()/data-inline3d-overlay/autoChrome are accepted and ignored. Your 2D chrome already occludes the tiles correctly. The calls are harmless (keep them if you also ship to older DisplayXR Browsers); gate on inline3dOcclusionByDrawOrder() to drop them."))}function Yi(){return Dn()&&Q()}function Et(r){return{type:"display",position:{x:0,y:0,z:0},orientation:{x:0,y:0,z:0,w:1},virtualDisplayHeight:r.virtualDisplayHeight>0?r.virtualDisplayHeight:.24,ipdFactor:1,parallaxFactor:1,perspectiveFactor:1}}function On(r,e){let t=Number.isFinite(r.ipdFactor)?r.ipdFactor:1,i=Number.isFinite(r.parallaxFactor)?r.parallaxFactor:1;return{...r,ipdFactor:t*e,parallaxFactor:i*e}}var Pn=16;function N(){return typeof performance<"u"&&performance&&typeof performance.now=="function"?performance.now():Date.now()}var Lt=1200,In=new Set(["pending","woven","withheld"]),Hi=3,Fn=new Set(["getDisplayInfo","getRenderingModes","requestRenderingMode"]),Wn=new Set(["getDisplayInfo","getRenderingModes"]);function jn(r){return r==null||Array.isArray(r)&&r.length===0}function Rt(){try{let r=typeof XRDisplayLayer=="function"?XRDisplayLayer:null;return!!(r&&r.prototype&&"wovenState"in r.prototype)}catch{return!1}}function je(r){try{let e=r?r.wovenState:null;return In.has(e)?e:null}catch{return null}}function Ct(r){try{let e=r?r.withheldReason:null;return typeof e=="string"&&e?e:null}catch{return null}}var $i=!1;function Nn(r){if(r==null)return re;let e=wt(r);return e||($i||($i=!0,console.warn(`[inline3d] createInline3D({ modeSwitch: { easing: ${JSON.stringify(r)} } }) is not a curve this SDK knows (${vt.join(" / ")}); using '${re}'.`)),re)}var Ui=!1;function zn(){Ui||(Ui=!0,console.info("[inline3d] The active rendering mode is 1-view, so this SDK has zeroed every window rig (ipdFactor/parallaxFactor -> 0) \u2014 both eyes now render from one place. The runtime keeps weaving the same two-view atlas whatever the page submits, so leaving stereo in it would put two slightly different images on a flat panel, i.e. a blurry double image instead of 2D. Your rendering is unchanged; the flattening is a copy applied on the way to the layer and it is undone the moment a 2-view mode goes active again."))}function At(r){return r&&r.detail!==void 0&&r.detail!==null?r.detail:void 0}function Ki(r){let e=At(r);return e!==void 0?[e,r]:[r]}function Hn(r){for(let e of Ki(r)){if(typeof e=="number"&&Number.isFinite(e))return e;if(e&&typeof e=="object"){let t=e.modeIndex!==void 0?e.modeIndex:e.mode;if(typeof t=="number"&&Number.isFinite(t))return t}}return-1}function $n(r){for(let e of Ki(r)){if(e==="2d"||e==="3d")return e;if(e&&typeof e=="object"){let t=e.state!==void 0?e.state:e.hardwareDisplayState;if(t==="2d"||t==="3d")return t}}return null}var Vi=!1;function Un(){Vi||(Vi=!0,console.warn("[inline3d] This browser has no XRDisplayLayer.setViewRig, so the view rig was ignored (the window still weaves \u2014 the runtime keeps the default display rig, scaled by virtualDisplayHeight). Gate on inline3dViewRigSupported() if your page's framing depends on the rig; further calls are silent."))}var Bi=!1;function Vn(){Bi||(Bi=!0,console.warn("[inline3d] addScene got BOTH viewRig and virtualDisplayHeight; on a browser with rig support the rig wins and the height is dropped. They describe the same slot \u2014 virtualDisplayHeight is shorthand for exactly one rig (display, identity pose, all factors 1) \u2014 so there is no merge. This is only worth passing as a PAIR deliberately: the height is what a browser without setViewRig will use, so a camera-rig scene can name its own fallback framing. Otherwise say it once, inside the rig."))}async function Zi(r={}){let{referenceSpace:e="viewer",lazy:t=!0,rootMargin:i="50% 0px",autoChrome:n=!0,modeSwitch:s=null,untrackedFallback:o="none",viewerEase:a}=r;if(!Yi())return{supported:!1,trackingState:"unknown"};let l;try{l=await navigator.xr.requestSession("inline-3d")}catch(f){return{supported:!1,trackingState:"unknown",error:f}}a!==void 0&&ki(l,a);let c=null;try{c=await l.requestReferenceSpace(e)}catch{}return new Ot(l,c,{lazy:t,rootMargin:i,autoChrome:n,modeSwitch:s,untrackedFallback:o})}var xe=null;function ze(r={}){return B&&B._running?Promise.resolve(B):xe||(xe=Zi(r).finally(()=>{xe=null}),xe)}var qi=.7,Bn=new Set(["IMG","SVG","VIDEO","CANVAS","BUTTON","INPUT","SELECT","TEXTAREA"]);function qn(r){let e=[],t=document.createTreeWalker(r,NodeFilter.SHOW_ELEMENT);for(let i=t.nextNode();i;i=t.nextNode())if(!i.closest("[data-inline3d-no-overlay]")){if(Bn.has(i.tagName.toUpperCase())){e.push(i);continue}for(let n of i.childNodes)if(n.nodeType===Node.TEXT_NODE&&n.nodeValue.trim()){e.push(i);break}}return e}var kt=4;function Tt(r){let e=typeof window<"u"&&window.devicePixelRatio||1,t="";if(typeof r.getBoundingClientRect=="function"){let i=r.getBoundingClientRect()||{},n=typeof window<"u"&&window.scrollX||0,s=typeof window<"u"&&window.scrollY||0;t=`+${Math.round((i.left??i.x??0)+n)},${Math.round((i.top??i.y??0)+s)}`}return`${r.clientWidth}x${r.clientHeight}@${e}${t}`}function Xi(r){let e=typeof window<"u"&&window.devicePixelRatio||1;return{width:r.clientWidth||0,height:r.clientHeight||0,dpr:e}}function Xn(r){if(r==null||r===!1||r==="off")return null;if(r==="auto"||r===!0)return{color:"#000",snapshot:!0};if(typeof r=="object"){if(r.color!==void 0&&typeof r.color!="string")throw new TypeError("[inline3d] rectCover.color must be a CSS color string.");return{color:r.color||"#000",snapshot:r.snapshot!==!1}}throw new TypeError(`[inline3d] rectCover: expected 'auto', 'off' or { color?, snapshot? }; got ${JSON.stringify(r)}.`)}var Ot=class{constructor(e,t,{lazy:i,rootMargin:n,autoChrome:s=!0,modeSwitch:o=null,untrackedFallback:a="none"}){this.supported=!0,this.session=e,this.refSpace=t,this._windows=new Map,this._claims=new Map,this._doubleWarned=new WeakSet,this._globalOverlays=new Set,this._isolatedBy=new WeakMap,this._autoChrome=s,this._autoChromeEls=new Set,this._lastChromeScan=0,this._fullTileWarned=new WeakSet,this._stoodDown=!1,this._activeModeIndex=-1,this._activeViewCount=0,this._hardwareDisplayState=null,this._modes=null,this._stereoCollapsed=!1,this._displayListeners=new Map,this._primedDisplayState=!1;let l=o&&typeof o=="object"?o:{};this._msEnabled=l.enabled!==!1,this._modeSwitch=new Ie((Number.isFinite(l.durationMs)?Math.max(0,l.durationMs):180)/1e3,Nn(l.easing)),this._stereoFactor=1,this._msFire=null,this._msArmedUp=!1,this._msLastMs=null,this._msTick=null,this._trackingSupported=We(e)!==null,this._trackingState=this._trackingSupported?We(e):"unknown",this._untrackedFallback=a==="mono"?"mono":"none",this._bakedStereo=1,this._bakedTarget=1,this._bakedAt=null,this._bakedEaseS=(Number.isFinite(l.durationMs)?Math.max(0,l.durationMs):180)/1e3,this._applyUntrackedTarget(),this.undock=St()?{model:!1,splat:!1}:null,this._undockRead=!1,this._running=!0,this._lazy=i,this._observer=i&&typeof IntersectionObserver=="function"?new IntersectionObserver(c=>this._onIntersect(c),{rootMargin:n}):null,this._loopId=0,this._framePending=!1,this._frameCount=0,this._frameWatchdog=null,this._suspended=null,B&&B._running&&B!==this&&console.warn("[inline3d] A second inline-3D session is live in this document. The browser's element-rect channel is a whole-widget setter, so both managers clobber each other's rect list every frame \u2014 tiles may flicker, ghost, or weave at a stale rect. Use ONE createInline3D() per document and add every window to it; if you are switching views, close() the previous manager first."),B=this,e.addEventListener("end",()=>this._teardown()),this._bindDisplayEvents(),this._bindTrackingEvents(),this._scanChrome(),this._bindLifecycle(),this._armDprWatch(),this._requestFrame()}get liveCount(){let e=0;for(let t of this._windows.values())t.layer&&e++;return e}addGlobalOverlay(e){if(!(!e||this._globalOverlays.has(e))){ne()&&Mt(),this._globalOverlays.add(e);for(let t of this._windows.values())t.layer&&this._applyExclusion(t,e)}}removeGlobalOverlay(e){if(!(!e||!this._globalOverlays.delete(e)))for(let t of this._windows.values())t.layer&&this._dropExclusion(t,e)}_scanChrome(){if(ne()||!this._autoChrome||!Fe()||typeof document>"u")return;let e=Date.now();if(e-this._lastChromeScan<1e3)return;this._lastChromeScan=e;let t=document.body;if(!t)return;let i=new Set,n=t.querySelectorAll(":scope > *, :scope > * > *, :scope > * > * > *");for(let s of n){if(s.closest("[data-inline3d-no-overlay]"))continue;let o=getComputedStyle(s).position;if(o!=="fixed"&&o!=="sticky")continue;let a=!1;for(let l of this._windows.keys())if(s===l||s.contains(l)){a=!0;break}if(!a){i.add(s);for(let l of qn(s))i.add(l)}}for(let s of this._autoChromeEls){let o=!1;if(s.isConnected){for(let a of this._windows.keys())if(s===a||s.contains(a)){o=!0;break}}(!s.isConnected||o)&&(this._autoChromeEls.delete(s),this.removeGlobalOverlay(s))}for(let s of i)this._globalOverlays.has(s)||(this._autoChromeEls.add(s),this.addGlobalOverlay(s))}addImage(e,t,i={}){this._warnDoubleAttach(e,"addImage",i._dxrClaim);let n=this._register(e,"image",i);return n.method="addImage",n.ready=Jn(t).then(s=>{n.img=s,n.repaint()}),this._handle(e,n)}addVideo(e,t,i={}){this._warnDoubleAttach(e,"addVideo",i._dxrClaim);let n=this._register(e,"video",i);return n.method="addVideo",n.video=t,this._handle(e,n)}addScene(e,t,i={}){this._warnDoubleAttach(e,"addScene",i._dxrClaim),i.viewRig&&i.virtualDisplayHeight!==void 0&&Vn();let n=this._register(e,"scene",{virtualDisplayHeight:.24,...i});return n.method="addScene",n.onFrame=t,n.ownsBuffer=!1,i.bufferClamped===!0&&(n.warnedBufMismatch=!0),this._handle(e,n)}_liveRegistration(e,t){let i=this._claims.get(e);if(i&&i.token!==t)return i.method;let n=this._windows.get(e);return n?n.method||"add*":null}_warnDoubleAttach(e,t,i){let n=this._liveRegistration(e,i);!n||this._doubleWarned.has(e)||(this._doubleWarned.add(e),console.warn(`[inline3d] ${t}() on a canvas that is already registered on this inline-3D session (by ${n}(), not yet removed). This call is rebuilding the canvas's layer (a fresh 0.4-1.2 s identity gap) or putting a second renderer on its context. Change what is IN the canvas instead (setSource, your onFrame, a redrawn source canvas), or call handle.remove() on the first registration first. (Warned once per canvas.)`,e))}_attachGuard(e,t,i={}){let n=this._claims.get(e);if(i&&i._dxrClaim&&n&&n.token===i._dxrClaim)return{opts:i,own:()=>{}};this._warnDoubleAttach(e,t,null);let s={};return{opts:{...i,_dxrClaim:s},own:o=>this._claim(e,t,o,s)}}_claim(e,t,i,n){if(!this._running||!i||typeof i!="object")return;let s={token:n,method:t};this._claims.set(e,s);let o=i.remove,a=(...l)=>(this._claims.get(e)===s&&this._claims.delete(e),typeof o=="function"?o.apply(i,l):void 0);Object.defineProperty(i,"remove",{configurable:!0,enumerable:!0,get:()=>a,set:l=>{o=l}})}_handle(e,t){let i=this;return{remove:()=>this._remove(e),exclude:n=>{n&&(ne()&&Mt(),t.excluded.add(n),this._applyExclusion(t,n))},unexclude:n=>{!n||!t.excluded.delete(n)||this._dropExclusion(t,n)},get trackingState(){return i._trackingState},setViewRig:n=>(t.viewRig=n||null,this._pushViewRig(t)),getDisplayInfo:()=>this._layerCall(t,"getDisplayInfo","getDisplayInfo()"),displayMetrics:async()=>{let n=null;try{n=await this._layerCall(t,"getDisplayInfo","getDisplayInfo()")}catch{}return Ti(n,e.getBoundingClientRect(),globalThis.devicePixelRatio||1)},getRenderingModes:()=>this._getRenderingModes(t),requestRenderingMode:n=>this._requestRenderingModeEased(n,t),setStereoEnabled:n=>this._setStereoEnabled(n,t),on:(n,s)=>this.on(n,s),off:(n,s)=>this.off(n,s),onDisplayModeChange:n=>this._onDisplayModeChange(n),stats:()=>({frames:t.frames,monoFrames:t.monoFrames}),get wovenState(){return Rt()?t.layer?je(t.layer):"pending":null},get withheldReason(){return!Rt()||!t.layer?null:je(t.layer)==="withheld"?Ct(t.layer):null},get firstWoven(){return t.fwPromise},onFirstWoven:n=>{if(typeof n!="function")throw new TypeError("[inline3d] onFirstWoven() takes a function.");let s=!0;return t.fwPromise.then(o=>{if(s)try{n(o)}catch(a){console.error("[inline3d] onFirstWoven callback threw",a)}}),()=>{s=!1}},rewoven:()=>this._rewoven(t)}}getDisplayInfo(){return this._layerCall(this._liveWindow(null),"getDisplayInfo","getDisplayInfo()")}getRenderingModes(){return this._getRenderingModes(null)}requestRenderingMode(e){return this._requestRenderingModeEased(e,null)}setStereoEnabled(e){return this._setStereoEnabled(e,null)}get trackingState(){return this._trackingState}get hardwareDisplayState(){return this._hardwareDisplayState}get activeMode(){return{modeIndex:this._activeModeIndex,viewCount:this._activeViewCount}}get stereoCollapsed(){return this._stereoCollapsed}get modeSwitch(){return{active:this._msTransitionActive(),factor:this._stereoFactor}}close(){try{this.session.end()}catch{}this._teardown()}_effectiveViewRig(e){return this._stereoFactor>=1?e.viewRig||(e.stereoSynthRig?Et(e):null):(e.viewRig||(e.stereoSynthRig=!0),On(e.viewRig||Et(e),this._stereoFactor))}_setStereoCollapsed(e){let t=!!e;if(this._stereoCollapsed!==t){this._stereoCollapsed=t,t&&zn(),!t&&this._msArmedUp?(this._msArmedUp=!1,this._startUpRamp()):this._modeSwitch.active()||(this._stereoFactor=t?0:1);for(let i of this._windows.values()){if(t){let n=i.viewRig||Et(i);i.stereoSaved={ipdFactor:n.ipdFactor,parallaxFactor:n.parallaxFactor}}else i.stereoSaved=null;this._pushViewRig(i)}}}_pushViewRig(e){if(!bt())return Un(),!1;if(!e.layer)return!1;let t=this._effectiveViewRig(e);try{return e.layer.setViewRig(t),!0}catch{return!1}}_layerCall(e,t,i,n=[]){if(!xt())return Promise.reject(new Error(`[inline3d] ${i} needs a DisplayXR Browser with the display-mode API (XRDisplayLayer.getDisplayInfo/getRenderingModes/requestRenderingMode). Gate on inline3dDisplayModesSupported().`));if(!e||!e.layer)return Promise.reject(new Error(`[inline3d] ${i} needs a live weave layer, and this window has none right now (lazy mode closes the layer while the tile is off screen). Call it once the tile is visible, or create the manager with { lazy: false }.`));if(Fn.has(t)&&!e.layerFramed&&this._layerReadPending(e))return this._whenLayerFramed(e).then(()=>this._layerCall(e,t,i,n));let o;try{o=Promise.resolve(e.layer[t](...n))}catch(l){return Promise.reject(l)}if(!Wn.has(t)||!this._layerReadPending(e))return o;let a=e.layer;return o.then(l=>e.layer!==a?l:jn(l)?this._layerReadPending(e)?this._whenLayerFramed(e).then(()=>this._layerCall(e,t,i,n)):l:(e.lfAnswered=!0,l))}_layerFramedCap(e){return kt*Math.max(e.fwHoldMs,Lt)}_layerReadPending(e){return!e.layer||e.lfAnswered||e.lfCapped?!1:N()-e.lfAt>=this._layerFramedCap(e)?(e.lfCapped=!0,!1):!0}_whenLayerFramed(e){return new Promise(t=>{let i=e.layer,n={resolve:t,timer:null},s=Math.max(0,this._layerFramedCap(e)-(N()-e.lfAt));n.timer=setTimeout(()=>{let o=e.framedWaiters.indexOf(n);o>=0&&e.framedWaiters.splice(o,1),e.layer===i&&(e.lfCapped=!0),t()},s),n.timer&&typeof n.timer.unref=="function"&&n.timer.unref(),e.framedWaiters.push(n)})}_tickLayerFramed(e){let t=e.wsLive?je(e.layer):null;(t?t==="woven":e.lfStereo)?this._noteLayerFramed(e,!0):e.framedWaiters.length&&!this._layerReadPending(e)&&this._noteLayerFramed(e,!1)}_noteLayerFramed(e,t=!0){t&&(e.layerFramed=!0);let i=e.framedWaiters.splice(0);for(let n of i)clearTimeout(n.timer),n.resolve()}_liveWindow(e){if(e&&e.layer)return e;for(let t of this._windows.values())if(t.layer)return t;return e||null}async _getRenderingModes(e){let t=await this._layerCall(this._liveWindow(e),"getRenderingModes","getRenderingModes()"),i=Array.isArray(t)?t:[];this._modes=i;let n=i.find(s=>s.isActive);return n&&(this._activeModeIndex=n.modeIndex,this._activeViewCount=n.viewCount,(n.viewCount===1||n.viewCount===2)&&this._setStereoCollapsed(n.viewCount===1)),this._primedDisplayState=!0,t}_requestRenderingMode(e,t){return this._layerCall(this._liveWindow(t),"requestRenderingMode","requestRenderingMode()",[e])}_msTransitionActive(){return this._modeSwitch.active()||this._msArmedUp||this._msFire!==null}async _requestRenderingModeEased(e,t){if(!this._msEnabled||!bt())return this._requestRenderingMode(e,t);let i=this._modes;if(!Array.isArray(i)||i.length===0)try{i=await this._getRenderingModes(t)}catch{i=null}let n=(Array.isArray(i)?i:[]).find(a=>a&&a.modeIndex===e),s=n&&Number.isFinite(n.viewCount)?n.viewCount:0,o=this._activeViewCount;return s<1||s>2||o<1?this._requestRenderingMode(e,t):s===1?o===1?this._requestRenderingMode(e,t):this._rampDownThenRequest(e,t):this._requestThenRampUp(e,t)}_rampDownThenRequest(e,t){if(this._msFire&&this._msFire.modeIndex===e)return this._msFire.promise;this._settlePendingDown("superseded",`a request for mode ${e} replaced it`);let i={modeIndex:e,win:t,resolve:null,reject:null,promise:null};return i.promise=new Promise((n,s)=>{i.resolve=n,i.reject=s}),this._msFire=i,this._modeSwitch.request({targetMode:e,targetViewCount:1,currentMode:this._activeModeIndex,currentViewCount:this._activeViewCount,current:this._stereoFactor,steady:1}),this._msLastMs=N(),this._armModeSwitchTick(),i.promise}_requestThenRampUp(e,t){let n=this._msFire!==null&&e===this._activeModeIndex;this._settlePendingDown("superseded",`a request for mode ${e} reversed it`);let s=n?Promise.resolve(void 0):this._requestRenderingMode(e,t);return this._stereoFactor<1&&(this._stereoCollapsed?(this._msArmedUp=!0,this._modeSwitch.cancel(),this._stereoFactor=0):this._startUpRamp()),s.catch(o=>{throw this._msArmedUp&&(this._msArmedUp=!1,this._modeSwitch.active()||(this._stereoFactor=this._stereoCollapsed?0:1)),o})}_startUpRamp(){this._settlePendingDown("superseded","the display returned to 3D"),this._modeSwitch.request({targetMode:this._activeModeIndex,targetViewCount:2,currentMode:this._activeModeIndex,currentViewCount:2,current:this._stereoFactor,steady:1}),this._msLastMs=N(),this._armModeSwitchTick()}_settlePendingDown(e,t){let i=this._msFire;if(!i)return;this._msFire=null;let n=new Error(`[inline3d] the request for rendering mode ${i.modeIndex} was never forwarded: ${t}. A ->2D switch is held until the disparity has ramped out, so a request that is reversed or replaced in that window is dropped rather than fired late.`);n.name=e,i.reject(n)}_advanceModeSwitch(){if(!this._modeSwitch.active()){this._msLastMs=null,this._disarmModeSwitchTick();return}let e=N(),t=typeof this._msLastMs=="number"?Math.max(0,(e-this._msLastMs)/1e3):0;this._msLastMs=e;let i=this._modeSwitch.update(t);if(i.factor!==this._stereoFactor){this._stereoFactor=i.factor;for(let n of this._windows.values())this._pushViewRig(n)}if(i.fire&&this._msFire){let n=this._msFire;this._msFire=null,this._requestRenderingMode(n.modeIndex,n.win).then(s=>n.resolve(s),s=>{this._startUpRamp(),n.reject(s)})}this._modeSwitch.active()||(this._msLastMs=null,this._disarmModeSwitchTick())}_armModeSwitchTick(){this._msTick!==null||typeof setTimeout!="function"||(this._msTick=setTimeout(()=>{this._msTick=null,this._running&&(this._advanceModeSwitch(),this._modeSwitch.active()&&this._armModeSwitchTick())},Pn))}_disarmModeSwitchTick(){this._msTick!==null&&(typeof clearTimeout=="function"&&clearTimeout(this._msTick),this._msTick=null)}async _setStereoEnabled(e,t){let i=e?2:1,n=await this._getRenderingModes(t),s=Array.isArray(n)?n:[],o=s.find(a=>a.viewCount===i&&a.isRequestable);if(!o)throw new Error(`[inline3d] setStereoEnabled(${!!e}) found no requestable ${i}-view mode on this display (${s.length} mode(s) listed). It is sugar over requestRenderingMode() and cannot invent one \u2014 read getRenderingModes() and drive the list yourself.`);return o.isActive&&!this._msTransitionActive()||await this._requestRenderingModeEased(o.modeIndex,t),!!e}_bindDisplayEvents(){let e=this.session;!e||typeof e.addEventListener!="function"||xt()&&(e.addEventListener("renderingmodechange",t=>{this._onRenderingModeChange(t).catch(()=>{})}),e.addEventListener("hardwaredisplaystatechange",t=>{this._onHardwareDisplayStateChange(t).catch(()=>{})}))}_onHardwareDisplayStateChange(e){let t=$n(e),i=At(e),n=a=>{a&&(this._hardwareDisplayState=a),this._emitDisplay({type:"hardwaredisplaystatechange",state:a||this._hardwareDisplayState,detail:i})};t&&n(t);let s=this._activeModeIndex,o=this._activeViewCount;return this._getRenderingModes(null).then(a=>{let l=(Array.isArray(a)?a:[]).find(c=>c.isActive)||null;if(l&&(l.modeIndex!==s||l.viewCount!==o)&&this._emitDisplay({type:"renderingmodechange",modeIndex:this._activeModeIndex,viewCount:this._activeViewCount||null,mode:l,detail:null}),!t){let c=l&&typeof l.hardwareDisplay3D=="boolean"?l.hardwareDisplay3D?"3d":"2d":null;n(c)}}).catch(()=>{t||n(null)})}async _onRenderingModeChange(e){let t=At(e),i=Hn(e),n=null;try{let s=await this._getRenderingModes(null),o=Array.isArray(s)?s:[];n=(i>=0?o.find(a=>a.modeIndex===i):null)||o.find(a=>a.isActive)||null}catch{}n?(this._activeModeIndex=n.modeIndex,this._activeViewCount=n.viewCount):i>=0&&(this._activeModeIndex=i),(this._activeViewCount===1||this._activeViewCount===2)&&this._setStereoCollapsed(this._activeViewCount===1),this._emitDisplay({type:"renderingmodechange",modeIndex:this._activeModeIndex,viewCount:this._activeViewCount||null,mode:n,detail:t})}_emitDisplay(e,t){let i=this._displayListeners.get(e.type);if(!i)return;let n=t||[e];for(let s of[...i])try{s(...n)}catch(o){console.error(`[inline3d] ${e.type} listener threw`,o)}}on(e,t){if(typeof t!="function")throw new TypeError("[inline3d] on() takes (type, function).");if(!Ni.includes(e))throw new TypeError(`[inline3d] on() knows ${Ni.join(" / ")}, got ${JSON.stringify(e)}.`);let i=this._displayListeners.get(e);return i||this._displayListeners.set(e,i=new Set),i.add(t),()=>this.off(e,t)}off(e,t){let i=this._displayListeners.get(e);i&&i.delete(t)}_onDisplayModeChange(e){if(typeof e!="function")throw new TypeError("[inline3d] onDisplayModeChange() takes a function.");let t=Gi.map(i=>this.on(i,e));return()=>{for(let i of t)i()}}async refreshUndock(){if(!St())return this.undock=null;let e=this._liveWindow(null);if(!e||!e.layer||typeof e.layer.getUndockCapabilities!="function")return this.undock;try{let t=await e.layer.getUndockCapabilities();this.undock={model:!!(t&&t.model),splat:!!(t&&t.splat)}}catch{}return this.undock}_register(e,t,i){this._windows.has(e)&&this._remove(e),e.style.willChange="transform",e.style.transform="translateZ(0)";let n={canvas:e,kind:t,layer:null,img:null,video:null,onFrame:null,onLayerLost:typeof i.onLayerLost=="function"?i.onLayerLost:null,layerLostSent:!1,onResize:t==="scene"&&typeof i.onResize=="function"?i.onResize:null,resizeBox:null,inResize:!1,ready:null,ownsBuffer:t!=="scene",cornerRadius:i.cornerRadius||0,feather:i.feather||0,reqW:i.width||0,reqH:i.height||0,virtualDisplayHeight:i.virtualDisplayHeight||0,viewRig:i.viewRig||null,stereoSaved:null,stereoSynthRig:!1,observeEl:i.observe||e,ctx:t==="scene"?null:e.getContext("2d"),repaint:()=>this._paint(n,null),excluded:new Set,autoExcluded:new Set,overlayObserver:null,sizeObserver:null,resizePending:!1,frames:0,monoFrames:0,fwResult:null,fwResolve:null,fwPromise:null,fwHoldMs:Gn(i.firstWovenHoldMs),fwRegAt:N(),fwLayerAt:null,fwStereo:!1,rw:null,rwGone:null,wsLive:!1,rc:null,layerFramed:!1,lfAnswered:!1,lfStereo:!1,lfAt:0,lfCapped:!1,framedWaiters:[]},s=Xn(i.rectCover);return s&&(n.rc={cfg:s,box:null,el:null,up:!1,snapped:!1,snapWanted:!1,waiting:null,w:0,h:0}),n.fwPromise=new Promise(o=>{n.fwResolve=o}),this._windows.set(e,n),this._lazy&&this._observer?this._observer.observe(n.observeEl):this._activate(n),n}_remove(e){let t=this._windows.get(e);t&&(this._observer&&this._observer.unobserve(t.observeEl),this._deactivate(t),this._windows.delete(e),this._settleFirstWoven(t,!1,"removed"),this._dropCover(t))}_onIntersect(e){for(let t of e){let i=null;for(let n of this._windows.values())if(n.observeEl===t.target){i=n;break}i&&(t.isIntersecting?this._activate(i):this._deactivate(i))}}_activate(e){if(!e.layer){this._scanChrome();try{let t=bt()?this._effectiveViewRig(e):null,i=t?{viewRig:t}:e.virtualDisplayHeight>0?{virtualDisplayHeight:e.virtualDisplayHeight}:{};e.layer=new XRDisplayLayer(this.session,e.canvas,i)}catch(t){e.layer=null,e.layerFailWarned||(e.layerFailWarned=!0,console.warn("[inline3d] new XRDisplayLayer() failed for this window \u2014 it will show FLAT 2D instead of woven 3D, and the SDK will not retry. The canvas has been repainted mono so it cannot be left holding a raw side-by-side pair.",t)),this._paintMono(e),this._notifyLayerLost(e),this._settleFirstWoven(e,!1,"layer-failed");return}e.fwResult||(e.fwLayerAt=N(),e.fwStereo=!1),e.wsLive=Rt(),e.layerFramed=!1,e.lfAnswered=!1,e.lfStereo=!1,e.lfCapped=!1,e.lfAt=N(),this._restartRewoven(e),e.layerLostSent=!1,!this._primedDisplayState&&xt()&&(this._primedDisplayState=!0,this._getRenderingModes(e).catch(()=>{this._primedDisplayState=!1})),St()&&this.undock&&!this._undockRead&&(this._undockRead=!0,this.refreshUndock().catch(()=>{})),An(e.layer)===!0&&this._standDownLegacyOcclusion();for(let t of e.excluded)this._applyExclusion(e,t);for(let t of this._globalOverlays)this._applyExclusion(e,t);this._startOverlayScan(e),e.ownsBuffer&&(this._sizeBuffer(e,!0),this._paint(e,null)),this._startSizeWatch(e)}}_deactivate(e){if(this._noteLayerFramed(e,!1),this._stopOverlayScan(e),this._stopSizeWatch(e),e.layer){try{e.layer.close()}catch{}e.layer=null}this._paintMono(e)}_paintMono(e){!e.ownsBuffer||e.kind==="scene"||(this._sizeBuffer(e,!1),this._paint(e,null))}_notifyLayerLost(e){if(!(e.kind!=="scene"||typeof e.onLayerLost!="function"||e.layerLostSent)){e.layerLostSent=!0;try{e.onLayerLost()}catch(t){console.warn("[inline3d] a scene window's onLayerLost callback threw",t)}}}_isFullTileOverlay(e,t){if(!t||typeof t.getBoundingClientRect!="function")return!1;let i=t.getBoundingClientRect(),n=e.canvas.getBoundingClientRect(),s=i.width*i.height,o=n.width*n.height;if(s<=0||o<=0)return!1;let a=Math.min(i.right,n.right)-Math.max(i.left,n.left),l=Math.min(i.bottom,n.bottom)-Math.max(i.top,n.top);if(a<=0||l<=0)return!1;let c=a*l;return c/s<qi||c/o<qi?!1:(this._fullTileWarned.has(t)||(this._fullTileWarned.add(t),console.warn("[inline3d] Refusing a full-tile overlay: this element covers its own woven canvas, and the browser's geometric matcher cannot tell the two apart \u2014 it would stage the CANVAS as the overlay and the tile would show its raw side-by-side buffer instead of 3D. Make the overlay a PARTIAL region of the tile (a caption band, a badge, a corner plate), or move it outside the tile and register it with addGlobalOverlay().",t)),!0)}_applyExclusion(e,t){if(ne()||!e.layer||!Fe()||this._isFullTileOverlay(e,t))return;let i=this._isolatedBy.get(t);if(i||(i=new Set,this._isolatedBy.set(t,i)),i.add(e),!t.dataset.inline3dIsolated){t.dataset.inline3dPriorWillChange=t.style.willChange||"";let n=t.style.willChange&&t.style.willChange!=="auto"?t.style.willChange+", transform":"transform";t.style.willChange=n,t.dataset.inline3dIsolated="1"}try{e.layer.excludeElement(t)}catch{}}_dropExclusion(e,t){if(ne())return;let i=this._isolatedBy.get(t);if(i&&i.delete(e),(!i||i.size===0)&&this._unpromote(t),!(!e.layer||!Fe()))try{e.layer.unexcludeElement(t)}catch{}}_unpromote(e){return!e||!e.dataset||!e.dataset.inline3dIsolated?!1:(e.style.willChange=e.dataset.inline3dPriorWillChange||"",delete e.dataset.inline3dPriorWillChange,delete e.dataset.inline3dIsolated,!0)}_standDownLegacyOcclusion(){if(this._stoodDown)return;this._stoodDown=!0;let e=!1;for(let t of this._windows.values()){t.overlayObserver&&(this._stopOverlayScan(t),e=!0);for(let i of t.excluded)this._unpromote(i)&&(e=!0)}for(let t of this._autoChromeEls)this._globalOverlays.delete(t),this._unpromote(t),e=!0;this._autoChromeEls.clear();for(let t of this._globalOverlays)this._unpromote(t)&&(e=!0);this._isolatedBy=new WeakMap,e&&Mt()}_startOverlayScan(e){if(ne()||!Fe()||typeof MutationObserver!="function")return;let t=e.canvas.parentElement;if(!t)return;let i=()=>{let n=new Set(t.querySelectorAll("[data-inline3d-overlay]"));for(let s of e.autoExcluded)n.has(s)||(e.autoExcluded.delete(s),this._dropExclusion(e,s));for(let s of n)e.autoExcluded.has(s)||(e.autoExcluded.add(s),this._applyExclusion(e,s))};i(),e.overlayObserver=new MutationObserver(i),e.overlayObserver.observe(t,{childList:!0,subtree:!0,attributes:!0,attributeFilter:["data-inline3d-overlay"]})}_stopOverlayScan(e){e.overlayObserver&&(e.overlayObserver.disconnect(),e.overlayObserver=null),e.autoExcluded.clear()}_eyeSize(e,t=!0){let i=Math.min(window.devicePixelRatio||1,2),n=e.reqW||Math.round((e.canvas.clientWidth||256)*i),s=e.reqH||Math.round((e.canvas.clientHeight||256)*i),o=1,a=!e.reqW&&!e.reqH&&this._sourceEyeSize(e);if(a){let c=Math.max(a.w/n,a.h/s);c<1&&(o=c,n=Math.max(1,Math.round(n*c)),s=Math.max(1,Math.round(s*c)))}let l=Oi(n,s,Ai(),{cols:t?2:1});return l.clamped&&(o*=l.scale,n=l.eyeW,s=l.eyeH,e.warnedBufClamp||(e.warnedBufClamp=!0,console.warn(Pi("[inline3d]",l)))),e.bufScale=o,{w:n,h:s}}_sourceEyeSize(e){if(e.kind!=="video"&&e.kind!=="image")return null;let t=e.kind==="video"?e.video:e.img;if(!t)return null;let i=t.videoWidth||t.naturalWidth||t.width,n=t.videoHeight||t.naturalHeight||t.height;return!(i>1)||!(n>0)?null:{w:i/2,h:n}}_checkSceneBuffer(e){let t=e.canvas;if(!t||typeof t.getContext!="function")return;if(e.sceneGl===void 0){let n=null;try{n=t.getContext("webgl2")||t.getContext("webgl")}catch{n=null}e.sceneGl=n&&typeof n.drawingBufferWidth=="number"?n:null}if(!e.sceneGl)return;let i=Ii(t,e.sceneGl);i.mismatch&&(e.warnedBufMismatch=!0,console.warn(Fi("[inline3d]",t,i,"getViewport() splits canvas.width, so the eyes are off-centre in the woven buffer. This scene canvas is sized by the page: keep 2 \xD7 eye width and the height within MAX_TEXTURE_SIZE / MAX_RENDERBUFFER_SIZE / MAX_VIEWPORT_DIMS, or map getViewport() onto gl.drawingBufferWidth/Height.")))}_sizeBuffer(e,t){let{w:i,h:n}=this._eyeSize(e,t);e.eyeW=i,e.eyeH=n,e.canvas.width=t?i*2:i,e.canvas.height=n,e.sbs=t}_startSizeWatch(e){if(typeof ResizeObserver=="function"&&!e.sizeObserver){if(e.kind==="scene"){if(!e.onResize)return;e.resizeBox=Xi(e.canvas),e.sizeObserver=new ResizeObserver(()=>this._onSceneResize(e)),e.sizeObserver.observe(e.canvas);return}!e.ownsBuffer||e.reqW&&e.reqH||(e.sizeObserver=new ResizeObserver(()=>this._onBoxChange(e)),e.sizeObserver.observe(e.canvas))}}_onSceneResize(e){if(!e.layer||!e.onResize||e.inResize)return;let t=Xi(e.canvas),i=e.resizeBox;if(!(i&&i.width===t.width&&i.height===t.height&&i.dpr===t.dpr)){e.resizeBox=t,e.inResize=!0;try{e.onResize(t)}catch(n){e.resizeThrewWarned||(e.resizeThrewWarned=!0,console.warn("[inline3d] a scene window's onResize threw; further throws from it are silent.",n))}finally{e.inResize=!1}}}_stopSizeWatch(e){e.sizeObserver&&(e.sizeObserver.disconnect(),e.sizeObserver=null),e.resizePending=!1}_onBoxChange(e){if(!e.layer||!e.ownsBuffer||e.resizePending)return;e.resizePending=!0;let t=()=>{if(!e.resizePending||(e.resizePending=!1,!e.layer||!e.ownsBuffer))return;this._noteRewovenBox(e);let{w:i,h:n}=this._eyeSize(e);i===e.eyeW&&n===e.eyeH||(this._sizeBuffer(e,!0),this._paint(e,null))};typeof requestAnimationFrame=="function"?requestAnimationFrame(t):t()}_armDprWatch(){if(typeof window>"u"||typeof window.matchMedia!="function")return;this._disarmDprWatch();let e;try{e=window.matchMedia(`(resolution: ${window.devicePixelRatio||1}dppx)`)}catch{return}let t=()=>{if(this._running){this._armDprWatch();for(let i of this._windows.values())i.layer&&this._onBoxChange(i)}};try{e.addEventListener("change",t)}catch{return}this._dprWatch={q:e,onChange:t}}_disarmDprWatch(){if(this._dprWatch){try{this._dprWatch.q.removeEventListener("change",this._dprWatch.onChange)}catch{}this._dprWatch=null}}_paint(e,t){if(e.kind==="scene"||!e.ctx)return;let i=this._running&&!!e.layer;!i&&e.sbs&&this._sizeBuffer(e,!1);let n=e.kind==="video"?e.video:e.img;if(!n)return;if(e.kind==="video"&&(n.readyState||0)<2){i&&this._recommitLastFrame(e);return}let s=e.canvas,o=e.ctx,a=n.videoWidth||n.naturalWidth||n.width,l=n.videoHeight||n.naturalHeight||n.height;if(!a||!l)return;let c=a+"x"+l;if(e.srcKey!==c&&(e.srcKey=c,e.ownsBuffer&&!(e.reqW&&e.reqH))){let{w:p,h:y}=this._eyeSize(e,e.sbs);(p!==e.eyeW||y!==e.eyeH)&&this._sizeBuffer(e,e.sbs)}let f=e.bufScale||1,m=e.cornerRadius*f,h=e.feather*f;if(o.clearRect(0,0,s.width,s.height),!e.sbs){Ne(o,n,0,0,a/2,l,0,0,s.width,s.height,m,h);return}let d=s.width/2,u=Math.min(this._stereoFactor,this._bakedStereo);if(u<1){Ne(o,n,0,0,a/2,l,0,0,d,s.height,m,h),Yn(o,n,a/2,l,d,s.height,u,m,h);return}m>0||h>0?(Ne(o,n,0,0,a/2,l,0,0,d,s.height,m,h),Ne(o,n,a/2,0,a/2,l,d,0,d,s.height,m,h)):o.drawImage(n,0,0,a,l,0,0,s.width,s.height)}_bindTrackingEvents(){let e=this.session;!e||typeof e.addEventListener!="function"||this._trackingSupported&&e.addEventListener(Dt,()=>{this._setTrackingState(We(e)||"unknown")})}_setTrackingState(e){let t=Ji.includes(e)?e:"unknown";if(t===this._trackingState)return;this._trackingState=t,this._applyUntrackedTarget();let i={type:Dt,state:t};this._emitDisplay(i,[t,i])}_applyUntrackedTarget(){if(this._untrackedFallback!=="mono"){this._bakedTarget=1;return}this._trackingState==="searching"?this._bakedTarget=0:this._trackingState==="tracking"&&(this._bakedTarget=1)}_trackBakedStereo(e){this._trackingSupported&&this._setTrackingState(We(this.session)||"unknown");let t=this._bakedAt===null||!Number.isFinite(e)?0:Math.max(0,(e-this._bakedAt)/1e3);this._bakedAt=Number.isFinite(e)?e:this._bakedAt;let i=this._bakedStereo,n=this._bakedTarget;if(i===n)return;let s=this._bakedEaseS>0?t/this._bakedEaseS:1;this._bakedStereo=n>i?Math.min(n,i+s):Math.max(n,i-s)}_recommitLastFrame(e){let t=e.canvas;if(!t.width||!t.height)return;let i=e.ctx;try{i.save(),i.globalCompositeOperation="copy",i.drawImage(t,0,0),i.restore()}catch{}}_requestFrame(e){if(!this._running||this._framePending&&!e)return;let t=e?++this._loopId:this._loopId;this._framePending=!0;try{this.session.requestAnimationFrame((i,n)=>{t===this._loopId&&(this._framePending=!1,this._frameCount++,this._frame(i,n))})}catch{this._framePending=!1}}_frame(e,t){if(!this._running)return;this._requestFrame(),this._advanceModeSwitch();let i=this.refSpace?t.getViewerPose(this.refSpace):null,n=i?i.views:null;this._trackBakedStereo(e);for(let s of this._windows.values())if(s.layer){if(s.rc&&this._rectCoverCheck(s),s.kind==="scene"){if(n&&s.onFrame){if(s.frames++,n.length<2&&(s.monoFrames++,s.monoFrames%300===1&&typeof console<"u"&&console.debug)){let o=(100*s.monoFrames/Math.max(1,s.frames)).toFixed(1);console.debug(`[inline3d] scene window: ${s.monoFrames} non-stereo view lists in ${s.frames} frames (${o}%). The viewer replays its last good stereo frame for these; a rising rate means the session is falling back under load.`)}try{s.onFrame(n,s.layer,t),n.length>=2&&(s.fwStereo=!0,s.lfStereo=!0,s.warnedBufMismatch||this._checkSceneBuffer(s),s.rw&&(s.rw.stereo=!0))}catch(o){s.frameThrewWarned||(s.frameThrewWarned=!0,console.warn("[inline3d] a scene window's onFrame threw; this window will keep whatever it last drew, and the other windows carry on. Further throws from it are silent.",o))}}}else this._paint(s,n),s.sbs&&(s.kind==="video"?(s.video&&s.video.readyState||0)>=2:s.img)&&(s.fwStereo=!0,s.lfStereo=!0,s.rw&&(s.rw.stereo=!0));s.rc&&s.rc.snapWanted&&this._rectCoverSnap(s),(!s.layerFramed||s.framedWaiters.length)&&this._tickLayerFramed(s),this._tickFirstWoven(s)}}_tickFirstWoven(e){let t=e.wsLive&&e.layer?je(e.layer):null;if(t){this._tickWovenState(e,t);return}if(e.rw){this._noteRewovenBox(e);let i=e.rw,n=N();i.stereo&&n-i.at>=e.fwHoldMs?this._settleRewoven(e,!0,"hold-elapsed"):i.at>i.calledAt&&n-i.calledAt>=kt*e.fwHoldMs&&this._settleRewoven(e,!0,"hold-capped")}e.fwResult||!e.fwStereo||e.fwLayerAt===null||N()-e.fwLayerAt<e.fwHoldMs||this._settleFirstWoven(e,!0,"hold-elapsed")}_tickWovenState(e,t){let i=N(),n=kt*Math.max(e.fwHoldMs,Lt);if(e.rw){this._noteRewovenBox(e);let s=e.rw;t!=="woven"?s.gap=!0:s.stereo&&(s.gap||s.boxFrame!==null&&this._frameCount-s.boxFrame>=Hi||s.boxFrame===null&&this._frameCount-s.callFrame>=Hi)&&this._settleRewoven(e,!0,"woven",!0),e.rw&&i-s.calledAt>=n&&(t==="woven"?this._settleRewoven(e,!0,"hold-capped",!0):this._settleRewoven(e,!1,Ct(e.layer)||t,!0))}e.fwResult||e.fwLayerAt===null||(e.fwStereo&&t==="woven"?this._settleFirstWoven(e,!0,"woven",!0):i-e.fwLayerAt>=n&&(t==="woven"?this._settleFirstWoven(e,!0,"hold-capped",!0):this._settleFirstWoven(e,!1,Ct(e.layer)||t,!0)))}_settleFirstWoven(e,t,i,n=!1){!t&&!n&&(e.rwGone||(e.rwGone=i),this._settleRewoven(e,!1,i)),!e.fwResult&&(e.fwResult=Object.freeze({woven:t,confirmed:n,reason:i,ms:Math.round(N()-e.fwRegAt)}),e.fwResolve(e.fwResult),e.fwResolve=null)}_rewoven(e){if(!e.fwResult)return e.fwPromise;if(!e.fwResult.woven&&!e.fwResult.confirmed)return Promise.resolve(e.fwResult);if(e.rwGone)return Promise.resolve(Object.freeze({woven:!1,confirmed:!1,reason:e.rwGone,ms:0}));if(e.rw)return this._restartRewoven(e),e.rw.promise;let t,i=new Promise(s=>{t=s}),n=N();return e.rw={promise:i,resolve:t,calledAt:n,at:n,stereo:!1,box:Tt(e.canvas),gap:!1,boxFrame:null,callFrame:this._frameCount},i}_noteRewovenBox(e){let t=e.rw;if(!t)return;let i=Tt(e.canvas);i!==t.box&&(t.box=i,this._restartRewoven(e))}_restartRewoven(e){e.rw&&(e.rw.at=N(),e.rw.stereo=!1,e.rw.gap=!1,e.rw.boxFrame=this._frameCount)}_settleRewoven(e,t,i,n=!1){let s=e.rw;s&&(e.rw=null,s.resolve(Object.freeze({woven:t,confirmed:n,reason:i,ms:Math.round(N()-s.calledAt)})))}_rectCoverCheck(e){let t=e.rc,i=Tt(e.canvas);if(i===t.box)return;let n=t.box===null,s=t.w,o=t.h;if(t.box=i,t.w=e.canvas.clientWidth||0,t.h=e.canvas.clientHeight||0,n)return;t.up&&this._placeCover(e);let a=e.fwResult;if(!a||e.rwGone||!(a.woven||a.confirmed))return;t.up||(t.snapped||(t.aspect=o>0?s/o:0),this._raiseCover(e)),t.cfg.snapshot&&!t.snapped&&t.w>0&&t.h>0&&(t.snapWanted=!0);let l;e.rw?(this._noteRewovenBox(e),l=e.rw.promise):(l=this._rewoven(e),e.rw&&(e.rw.boxFrame=this._frameCount)),t.waiting!==l&&(t.waiting=l,l.then(()=>{t.waiting===l&&(t.waiting=null,this._lowerCover(e))}))}_raiseCover(e){let t=e.rc;if(!t.el){if(typeof document>"u"||!document||typeof document.createElement!="function"||!e.canvas.parentElement)return;let i=document.createElement("canvas");i.setAttribute?.("aria-hidden","true"),i.dataset&&(i.dataset.inline3dCover="");let n=i.style;n.position="absolute",n.pointerEvents="none",n.margin="0",n.border="0",n.padding="0",n.background=t.cfg.color,n.display="none";try{let s=typeof getComputedStyle=="function"?getComputedStyle(e.canvas).zIndex:"auto";s&&s!=="auto"&&(n.zIndex=s)}catch{}t.el=i}this._placeCover(e),t.el.style.display="block",t.up=!0,this._fullTileWarned.add(t.el),this._applyExclusion(e,t.el)}_placeCover(e){let i=e.rc.el,n=e.canvas;if(!i)return;let s=n.parentElement;s&&(i.parentElement!==s||i.previousSibling!==n)&&s.insertBefore(i,n.nextSibling);let o=i.style;if(n.offsetParent)o.position="absolute",o.left=`${n.offsetLeft}px`,o.top=`${n.offsetTop}px`,o.width=`${n.offsetWidth}px`,o.height=`${n.offsetHeight}px`;else{let a=n.getBoundingClientRect();o.position="fixed",o.left=`${a.left??a.x??0}px`,o.top=`${a.top??a.y??0}px`,o.width=`${a.width}px`,o.height=`${a.height}px`}}_rectCoverSnap(e){let t=e.rc;t.snapWanted=!1;let i=t.el,n=e.canvas;if(!i||!t.up||!(n.width>1)||!(n.height>0))return;let s=Math.max(1,Math.round(t.w)),o=Math.max(1,Math.round(t.h)),a=s/o,l=t.aspect>0?t.aspect:n.width/2/n.height,c=1,f=1;a>l?f=l/a:c=a/l;let m=n.width/2*c,h=n.height*f,d=(n.width/2-m)/2,u=(n.height-h)/2;try{i.width=s,i.height=o;let p=i.getContext("2d");if(!p)return;p.drawImage(n,d,u,m,h,0,0,s,o),t.snapped=!0}catch{}}_lowerCover(e){let t=e.rc;t&&(t.up=!1,t.snapped=!1,t.snapWanted=!1,t.el&&(t.el.style.display="none",this._dropExclusion(e,t.el)))}_dropCover(e){let t=e.rc;t&&(t.waiting=null,this._lowerCover(e),t.el&&t.el.parentElement&&t.el.parentElement.removeChild(t.el),t.el=null)}_bindLifecycle(){typeof window>"u"||(this._onPageHide=()=>this._suspend(),this._onPageShow=e=>this._resume(!!(e&&e.persisted)),window.addEventListener("pagehide",this._onPageHide),window.addEventListener("pageshow",this._onPageShow),typeof document<"u"&&"onfreeze"in document&&(this._onFreeze=()=>this._suspend(),this._onResume=()=>this._resume(!0),document.addEventListener("freeze",this._onFreeze),document.addEventListener("resume",this._onResume)))}_unbindLifecycle(){typeof window>"u"||(this._onPageHide&&window.removeEventListener("pagehide",this._onPageHide),this._onPageShow&&window.removeEventListener("pageshow",this._onPageShow),this._onFreeze&&typeof document<"u"&&(document.removeEventListener("freeze",this._onFreeze),document.removeEventListener("resume",this._onResume)),this._onPageHide=this._onPageShow=this._onFreeze=this._onResume=null,this._frameWatchdog&&(clearTimeout(this._frameWatchdog),this._frameWatchdog=null))}_suspend(){if(!this._running||this._suspended)return;let e=[];for(let t of this._windows.values())t.layer&&(e.push(t),this._deactivate(t));this._suspended=e}_resume(e){if(!this._running)return;let t=this._suspended;if(this._suspended=null,t)for(let i of t)this._windows.has(i.canvas)&&(this._lazy&&this._observer?(this._observer.unobserve(i.observeEl),this._observer.observe(i.observeEl)):this._activate(i));this._lastChromeScan=0,this._scanChrome(),this._armDprWatch(),e&&this._watchForStalledFrames()}_watchForStalledFrames(){if(this._frameWatchdog||typeof setTimeout!="function")return;let e=this._frameCount;this._frameWatchdog=setTimeout(()=>{this._frameWatchdog=null,!(!this._running||this._frameCount!==e)&&this._requestFrame(!0)},1e3)}_teardown(){if(this._running){this._running=!1,B===this&&(B=null),this._unbindLifecycle(),this._disarmDprWatch(),this._disarmModeSwitchTick(),this._modeSwitch.cancel(),this._msArmedUp=!1,this._settlePendingDown("closed","the inline-3D session closed first"),this._observer&&this._observer.disconnect();for(let e of this._windows.values()){if(this._stopOverlayScan(e),this._stopSizeWatch(e),e.layer){try{e.layer.close()}catch{}e.layer=null}this._paintMono(e),this._notifyLayerLost(e),this._settleFirstWoven(e,!1,"session-ended"),this._dropCover(e),this._noteLayerFramed(e,!1)}this._windows.clear(),this._claims.clear(),this._setTrackingState("unknown"),this._displayListeners.clear()}}};function Gn(r){return typeof r=="number"&&Number.isFinite(r)&&r>=0?r:Lt}function Jn(r){return typeof r!="string"?Promise.resolve(r):new Promise((e,t)=>{let i=new Image;i.decoding="async",i.onload=()=>e(i),i.onerror=t,i.src=r})}function Ne(r,e,t,i,n,s,o,a,l,c,f,m){f>0&&r.roundRect?(r.save(),r.beginPath(),r.roundRect(o,a,l,c,f),r.clip(),r.drawImage(e,t,i,n,s,o,a,l,c),r.restore()):r.drawImage(e,t,i,n,s,o,a,l,c),m>0&&Qi(r,o,a,l,c,m)}function Yn(r,e,t,i,n,s,o,a,l){r.save(),a>0&&r.roundRect&&(r.beginPath(),r.roundRect(n,0,n,s,a),r.clip()),r.drawImage(e,0,0,t,i,n,0,n,s),o>0&&(r.globalAlpha=o,r.drawImage(e,t,0,t,i,n,0,n,s)),r.restore(),l>0&&Qi(r,n,0,n,s,l)}function Qi(r,e,t,i,n,s){let o=Math.min(s,Math.floor(Math.min(i,n)/2));if(o<=0)return;r.save(),r.globalCompositeOperation="destination-out";let a=[[e,t,i,o,[e,t],[e,t+o]],[e,t+n-o,i,o,[e,t+n],[e,t+n-o]],[e,t,o,n,[e,t],[e+o,t]],[e+i-o,t,o,n,[e+i,t],[e+i-o,t]]];for(let[l,c,f,m,h,d]of a){let u=r.createLinearGradient(h[0],h[1],d[0],d[1]);u.addColorStop(0,"rgba(0,0,0,1)"),u.addColorStop(1,"rgba(0,0,0,0)"),r.fillStyle=u,r.fillRect(l,c,f,m)}r.restore()}function oe(r,e){return r>0&&e>0&&r/e>2.5}function U(r,e,t,i,n){let s=Math.abs(i||0),o=Math.min(r-2*s,e*t);o=Math.max(1,o);let a=Math.min(e,o/t);o=a*t;let l=(r-o)/2+(n===0?i:-i);return{sx:Math.max(0,Math.min(r-o,l)),sy:(e-a)/2,sw:o,sh:a}}function Se(r,e,t){let i=Math.min(r,e*t),n=i/t;return{w:Math.max(2,Math.round(i/2)*2),h:Math.max(2,Math.round(n/2)*2)}}function ae(r,e){let t=r/2;return[{src:"R",sx:t,sw:t,sy:0,sh:e,dx:0,dw:t,mirror:!0},{src:"L",sx:0,sw:t,sy:0,sh:e,dx:t,dw:t,mirror:!0}]}function Pt(r,e,t){let i=new r.constructor(r.length);for(let n of ae(e,t))for(let s=0;s<t;s++)for(let o=0;o<n.dw;o++){let a=n.sx+(n.sw-1-o);i[s*e+n.dx+o]=r[s*e+a]}return i}function er(r,e){return!(r>0)||!(e>0)||e>=180?null:r/2/Math.tan(e/2*Math.PI/180)}function He({eyeWidthPx:r,hfovDeg:e,baselineMm:t,subjectZmm:i}){let n=er(r,e);return n===null||!(t>0)||!(i>0)?0:n*t/(2*i)}function $e(r,e,t=.2){let i=Math.max(0,e*t);return Math.max(-i,Math.min(i,r||0))}function Ue(r,e,t=.2){let i=r+t*(e-r);return Math.abs(e-i)<.05?e:i}function Me(){let r={hello:null,subjectZmm:null,measuredPx:null,depth:0,current:0,target(e){let t=r.measuredPx!==null?r.measuredPx/2:r.hello&&r.subjectZmm?He({eyeWidthPx:e,hfovDeg:r.hello.hfovDeg,baselineMm:r.hello.baselineMm,subjectZmm:r.subjectZmm}):0;return $e(t+r.depth*.05*e,e)},step(e){return r.current=Ue(r.current,r.target(e)),r.current}};return r}var Ve=1,It="inline3d-call/1",tr=Object.freeze(["sbs","mono"]),le=4,ir=4,Ft=5,Kn=150,Zn=5e3,Qn=/^[A-Za-z0-9_-]{16,64}$/,es=16;function Wt(r){let e=new Uint8Array(es);return(r||(i=>globalThis.crypto.getRandomValues(i)))(e),nr(e)}function rr(r){let e=new Uint8Array(8);return(r||(i=>globalThis.crypto.getRandomValues(i)))(e),nr(e)}function nr(r){let e="ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_",t="",i=0;for(;i+2<r.length;i+=3){let s=r[i]<<16|r[i+1]<<8|r[i+2];t+=e[s>>18&63]+e[s>>12&63]+e[s>>6&63]+e[s&63]}let n=r.length-i;if(n===1){let s=r[i]<<16;t+=e[s>>18&63]+e[s>>12&63]}else if(n===2){let s=r[i]<<16|r[i+1]<<8;t+=e[s>>18&63]+e[s>>12&63]+e[s>>6&63]}return t}function ce(r){return typeof r=="string"&&Qn.test(r)}function Ee(r){if(!r)return null;let e="";if(typeof r=="object")e=r.hash||r.href&&String(r.href).split("#")[1]||"";else{let t=String(r),i=t.indexOf("#");e=i>=0?t.slice(i+1):t.includes("=")&&!t.includes("://")?t:""}e=e.replace(/^#/,"");for(let t of e.split("&")){let i=t.indexOf("=");if(i<0||decodeURIComponent(t.slice(0,i))!=="room")continue;let n=decodeURIComponent(t.slice(i+1));return ce(n)?n:null}return null}function Be(r,e){if(!ce(e))throw new Error(`@displayxr/inline3d/call: invalid room id "${e}"`);return`${String(r||"").split("#")[0]}#room=${e}`}function jt(r){if(!r||typeof r!="object"||r.type!=="hello")return null;let e=(t,i,n)=>typeof t=="number"&&Number.isFinite(t)&&t>=i&&t<=n?t:null;return{type:"hello",v:typeof r.v=="number"?r.v:Ve,format:tr.includes(r.format)?r.format:"mono",width:e(r.width,1,16384),height:e(r.height,1,16384),baselineMm:e(r.baselineMm,1,1e3),hfovDeg:e(r.hfovDeg,5,170),rectified:r.rectified===!0,sdk:typeof r.sdk=="string"?r.sdk.slice(0,64):null}}function Nt({format:r,width:e,height:t,baselineMm:i,hfovDeg:n,rectified:s}={}){let o={type:"hello",v:Ve,format:tr.includes(r)?r:"mono",width:e||0,height:t||0,rectified:!!s,sdk:It};return Number.isFinite(i)&&(o.baselineMm=i),Number.isFinite(n)&&(o.hfovDeg=n),o}function zt(r){if(!r||r.type!=="hint")return null;let e=r.subjectZmm;return typeof e=="number"&&Number.isFinite(e)&&e>=Kn&&e<=Zn?{type:"hint",subjectZmm:e}:null}function sr(r){return!r||r.type!=="state"?null:{type:"state",muted:r.muted===!0,cameraOff:r.cameraOff===!0,speaking:r.speaking===!0}}function Ht(r){let e=1e3/r,t=-1/0;return i=>i-t<e?!1:(t=i,!0)}function $t({format:r,woven:e,mono3D:t="auto",lift:i=!1,overBudget:n=!1,failed:s=!1}){return(r==="sbs"?"sbs":"mono")==="sbs"?{route:e?"woven-sbs":"flat-left"}:e?t==="off"||t===!1?{route:"flat",mono3d:"off"}:i?s?{route:"flat",mono3d:"failed"}:n?{route:"flat",mono3d:"budget"}:{route:"lifted",mono3d:"lifted"}:{route:"flat",mono3d:"unavailable"}:{route:"flat"}}function Ut(r){return r==="woven-sbs"?"3D":r==="lifted"?"2D\u21923D":"2D"}function Vt({need:r=10,needNoPose:e=30}={}){let t=0,i=0,n=!1;return{feed(s){return n?!0:(i++,s===null?n=i>=e:(t=s>=1?t+1:0,n=t>=r),n)},get live(){return n}}}function qe(r,e){let t=Math.max(1,e|0),n=r==="sbs"?[6e3,4e3,3e3]:[2500,1800,1400];return n[Math.min(t,n.length)-1]}function q(r,{baseMs:e=1e3,maxMs:t=3e4,rand:i=Math.random}={}){let n=Math.min(t,e*Math.pow(2,Math.max(0,r)));return Math.round(n*(.8+.4*i()))}var Re=Object.freeze(["VP9","VP8","AV1"]);function is(r,e=Re){let t=e.indexOf(String(r||"").toUpperCase());return t<0?e.length:t}function Xe(r,e=Re){let t=i=>is(String(i.mimeType||"").split("/")[1],e);return r.map((i,n)=>({c:i,i:n,r:t(i)})).sort((i,n)=>i.r-n.r||i.i-n.i).map(i=>i.c)}function Ce(r,e=Re){if(!r)return r;let t=r.includes(`\r
3
3
  `)?`\r
4
4
  `:`
@@ -141,7 +141,10 @@ function addModelUnguarded(wall, canvas, src, opts = {}) {
141
141
  remove: queue('remove'),
142
142
  exclude: queue('exclude'),
143
143
  unexclude: queue('unexclude'),
144
+ // Before the backend lands: once it has, the backend's own (PlayCanvas); three has none (0).
145
+ warmEntity: (entity, o) => out.ready.then(() => (out.warmEntity !== stubWarm && typeof out.warmEntity === 'function' ? out.warmEntity(entity, o) : 0)),
144
146
  };
147
+ const stubWarm = out.warmEntity;
145
148
  out.firstWoven = new Promise((resolve) => {
146
149
  out._resolveFirstWoven = resolve;
147
150
  });
@@ -515,6 +515,14 @@ export function attachPlayCanvasModel(out, wall, canvas, src, opts, pending = []
515
515
  },
516
516
  exclude: (el) => handle?.exclude(el),
517
517
  unexclude: (el) => handle?.unexclude(el),
518
+ /**
519
+ * Compile `entity`'s mesh programs off the frame before it is shown (default: the model) —
520
+ * PlayCanvasSplatViewer.warmEntity. Resolves the number of programs warmed; 0 before `ready`.
521
+ */
522
+ async warmEntity(entity, o) {
523
+ await out.ready?.catch?.(() => null);
524
+ return removed ? 0 : viewer.warmEntity(entity ?? out.model, o);
525
+ },
518
526
  });
519
527
 
520
528
  // The per-eye buffer scale in force (the request times the device-limit clamp,
@@ -37,6 +37,7 @@ export {
37
37
  Vec4,
38
38
  Quat,
39
39
  LAYERID_SKYBOX,
40
+ LAYERID_WORLD,
40
41
  LAYERID_UI,
41
42
  LAYERID_IMMEDIATE,
42
43
  // cursor: 'depth' (./inline3d-cursor-depth-playcanvas.js, loaded only on opt-in)
@@ -57,6 +58,7 @@ export {
57
58
  SEMANTIC_COLOR,
58
59
  ShaderChunks,
59
60
  SHADERLANGUAGE_GLSL,
61
+ SHADER_FORWARD,
60
62
  CHUNKAPI_2_8,
61
63
  // setSource's frame snapshot
62
64
  RenderTarget,
@@ -117,6 +117,13 @@ export function addSplatDeferred(wall, canvas, src, opts) {
117
117
  validateEffectCall(name, o, 'play');
118
118
  return out.ready.then(() => out.playEffect(name, o));
119
119
  },
120
+ // Warm before the adapter has loaded: validated now, warmed once the first asset is on screen.
121
+ warmEffect: (name, o) => {
122
+ validateEffectCall(name, o, 'play');
123
+ return out.ready.then(() => out.warmEffect(name, o));
124
+ },
125
+ // Mesh-program warm before the adapter has loaded: runs once it has (it needs the engine).
126
+ warmEntity: (entity, o) => out.ready.then(() => out.warmEntity(entity, o)),
120
127
  setEffect: (name, params) => {
121
128
  validateEffectCall(name, params, 'set');
122
129
  pending.push(['setEffect', [name, params]]);
@@ -1788,6 +1788,22 @@ export class SplatEffects {
1788
1788
  };
1789
1789
  }
1790
1790
 
1791
+ /**
1792
+ * The tile chunk play(name, opts) WOULD install right now — the tile effects already on, with
1793
+ * this one added (or replacing its namesake, in place, as _replace does) — without installing
1794
+ * it: what handle.warmEffect compiles ahead of the play. Null for an entity-scope effect (its
1795
+ * program is the asset's work-buffer modifier, not the tile chunk) or a custom fragment stage.
1796
+ */
1797
+ playChunkCode(name, opts = {}) {
1798
+ const o = resolveEffectOptions(name, opts, 'play');
1799
+ if ((o.scope ?? 'tile') !== 'tile' || o.fragmentGlsl) return null;
1800
+ const inst = { name, def: defOf(name), opts: o };
1801
+ const m = this.scopes.get('tile');
1802
+ const list = m ? [...m.values()].map((i) => (i.name === name ? inst : i)) : [];
1803
+ if (!m?.has(name)) list.push(inst);
1804
+ return composeModifier(list).code;
1805
+ }
1806
+
1791
1807
  /**
1792
1808
  * The tile chunk driveShared(name, effect, opts) WOULD install, without installing it — what
1793
1809
  * setSource's shader pre-warm compiles ahead of the transition.
@@ -1190,6 +1190,90 @@ export class PlayCanvasSplatViewer {
1190
1190
  * engine's `renderSceneColorMap` setter counts one request per camera. Cameras made later (a
1191
1191
  * view-path switch) inherit it via `_makeCamera`.
1192
1192
  */
1193
+ /**
1194
+ * Compile the forward programs `entity`'s meshes will draw with — through the RENDERER's own
1195
+ * call (MeshInstance.getShaderInstance with the pass, layer light hash, sorted lights, view
1196
+ * uniform format and each eye camera's live shaderParams it will pass), so the variant is the
1197
+ * very one the first draw asks for, cached on the mesh instance — then finish each link in idle
1198
+ * periods (KHR_parallel_shader_compile) instead of on that draw. PlayCanvas's forward renderer
1199
+ * links synchronously on first use (asyncCompile = false): a glTF with ~13 lit materials froze
1200
+ * 0.8–0.95 s on a panel. Calling material.getShaderVariant with hand-built params instead
1201
+ * produces broken variants (`#include "litMainVS"` unresolved) — do not.
1202
+ *
1203
+ * Warm in the state you will draw in: lights, fog, the layer and the camera's render target
1204
+ * key the variant. Needs one frame rendered (the cameras' shaderParams). Resolves the number of
1205
+ * programs warmed (0: nothing to do, or an engine whose internals differ).
1206
+ */
1207
+ async warmEntity(entity, { timeoutMs = 4000 } = {}) {
1208
+ const pc = this.pc;
1209
+ const app = this.app;
1210
+ if (!pc || !app || this._disposed || !entity) return 0;
1211
+ const device = app.graphicsDevice;
1212
+ const scene = app.scene;
1213
+ const renderer = app.renderer;
1214
+ if (!renderer || typeof renderer.viewUniformFormat === 'undefined') return 0;
1215
+ // [meshInstance, the layer ids it draws in] — a render/model component's own `layers`
1216
+ const groups = [];
1217
+ for (const c of [...(entity.findComponents?.('render') || []), ...(entity.findComponents?.('model') || [])]) {
1218
+ if (c.meshInstances?.length) groups.push([c.meshInstances, c.layers?.length ? c.layers : [pc.LAYERID_WORLD]]);
1219
+ }
1220
+ if (!groups.length) return 0;
1221
+ // Every camera that may draw it: the eye(s), and the layer rig's display/post cameras (a layer on
1222
+ // the display rig — Home's round stage — is drawn ONLY by those, with their own shaderParams;
1223
+ // warming through the eye camera alone missed it and issued a variant nobody draws).
1224
+ const rig = this.layerRigs?.cams;
1225
+ const cams = [this.eye, ...(this._viewPath === 'cameras' ? this._views : []), ...(rig?.display || []), ...(rig?.post || [])]
1226
+ .map((c) => c?.camera)
1227
+ .filter((c) => c?.camera?.shaderParams);
1228
+ const forward = pc.SHADER_FORWARD ?? 0;
1229
+ const shaders = new Set();
1230
+ // Issuing a program compiles its two shaders on THIS thread: 13 lit programs at once were one
1231
+ // 65 ms frame. Time-slice — yield to idle whenever a slice passes SLICE_MS.
1232
+ const SLICE_MS = 8;
1233
+ let slice = now();
1234
+ try {
1235
+ for (const cc of cams) {
1236
+ const shaderParams = cc.camera.shaderParams;
1237
+ // the renderer's own pick (renderForwardLayer): the camera's shader pass, else forward
1238
+ const pass = cc.camera.shaderPassInfo?.index ?? forward;
1239
+ for (const [mis, layerIds] of groups) {
1240
+ for (const layerId of layerIds) {
1241
+ if (cc.layers && !cc.layers.includes(layerId)) continue; // this camera never draws it
1242
+ const layer = scene.layers.getLayerById(layerId);
1243
+ if (!layer) continue;
1244
+ const lightHash = layer.getLightHash?.(scene.clusteredLightingEnabled) ?? 0;
1245
+ for (const mi of mis) {
1246
+ if (!mi?.mesh || !mi.material) continue;
1247
+ if (((mi.shaderPassMask ?? ~0) & (1 << pass)) === 0) continue;
1248
+ mi.ensureMaterial?.(device);
1249
+ mi.material._scene = scene;
1250
+ mi.material.prepareForRender?.(device, scene);
1251
+ const si = mi.getShaderInstance(pass, lightHash, scene, shaderParams, renderer.viewUniformFormat, layer.splitLights);
1252
+ if (si?.shader) shaders.add(si.shader);
1253
+ if (now() - slice > SLICE_MS) {
1254
+ await yieldIdle(50);
1255
+ if (this._disposed) return 0;
1256
+ slice = now();
1257
+ }
1258
+ }
1259
+ }
1260
+ }
1261
+ }
1262
+ } catch (err) {
1263
+ console.info(`${this.logTag} warmEntity: could not issue the programs (engine internals differ?)`, err);
1264
+ return 0;
1265
+ }
1266
+ const pending = () => [...shaders].filter((x) => !x.ready && !x.failed && x.impl?.finalize);
1267
+ const t0 = now();
1268
+ while (pending().length && now() - t0 < timeoutMs && !this._disposed) {
1269
+ for (const x of pending()) if (x.impl.isLinked?.(device)) x.impl.finalize(device, x) || (x.failed = true);
1270
+ if (pending().length) await yieldIdle(50);
1271
+ }
1272
+ // still linking (or no completion query): finalize now — a block here, not on the first draw
1273
+ for (const x of pending()) x.impl.finalize(device, x) || (x.failed = true);
1274
+ return shaders.size;
1275
+ }
1276
+
1193
1277
  useSceneColor(on = true) {
1194
1278
  this.sceneColor = !!on;
1195
1279
  const cams = [this.eye, ...(this._viewPath === 'cameras' ? this._views : [])];
@@ -3024,6 +3108,20 @@ export function validateDepthEnvelope(o, prev) {
3024
3108
  return next;
3025
3109
  }
3026
3110
 
3111
+ /**
3112
+ * prepareSource's `warmEffects`: an array of effect names, or of { name, opts } (validated as a
3113
+ * playEffect call, at prepareSource's own line). Returns [{ name, opts }].
3114
+ */
3115
+ export function validateWarmEffects(list) {
3116
+ if (list === undefined) return [];
3117
+ if (!Array.isArray(list)) throw new TypeError('@displayxr/inline3d/splat: prepareSource warmEffects must be an array of effect names or { name, opts }.');
3118
+ return list.map((w) => {
3119
+ const e = typeof w === 'string' ? { name: w, opts: {} } : { name: w?.name, opts: w?.opts ?? {} };
3120
+ validateEffectCall(e.name, e.opts, 'play');
3121
+ return e;
3122
+ });
3123
+ }
3124
+
3027
3125
  /**
3028
3126
  * handle.setStereo's argument, validated and merged onto the current override (throws at the
3029
3127
  * call). Returns the new override, or null when nothing is overridden.
@@ -3477,6 +3575,15 @@ export function attachPlayCanvasSplat(out, wall, canvas, src, opts, pending = []
3477
3575
  },
3478
3576
  prepareSource,
3479
3577
  setRig,
3578
+ /**
3579
+ * Compile `entity`'s mesh programs (content under handle.engine.root) off the frame, before
3580
+ * it is shown — viewer.warmEntity. Resolves the number of programs warmed.
3581
+ */
3582
+ async warmEntity(entity, o) {
3583
+ await booted;
3584
+ await first.catch(() => null);
3585
+ return removed ? 0 : viewer.warmEntity(entity, o);
3586
+ },
3480
3587
  setStereo,
3481
3588
  setDepthEnvelope,
3482
3589
  /**
@@ -3577,6 +3684,7 @@ export function attachPlayCanvasSplat(out, wall, canvas, src, opts, pending = []
3577
3684
  validateEffectCall(name, o, 'play');
3578
3685
  return first.then(() => (fx && !removed ? fx.play(name, o) : { finished: false }));
3579
3686
  },
3687
+ warmEffect,
3580
3688
  /** Set a persistent effect (grade, clip, custom), hold a transition at `progress`, or null to remove. */
3581
3689
  setEffect(name, params) {
3582
3690
  validateEffectCall(name, params, 'set');
@@ -4966,7 +5074,9 @@ export function attachPlayCanvasSplat(out, wall, canvas, src, opts, pending = []
4966
5074
  */
4967
5075
  async function prepareSource(src, po = {}) {
4968
5076
  if (po === null || typeof po !== 'object') throw new TypeError('@displayxr/inline3d/splat: prepareSource options must be an object.');
4969
- const warm = po.transition !== undefined ? resolveSwap(po) : null; // the setSource options it will get; throws on a bad one
5077
+ const warmList = validateWarmEffects(po.warmEffects);
5078
+ const swapOpts = po.warmEffects === undefined ? po : Object.fromEntries(Object.entries(po).filter(([k]) => k !== 'warmEffects'));
5079
+ const warm = po.transition !== undefined ? resolveSwap(swapOpts) : null; // the setSource options it will get; throws on a bad one
4970
5080
  const app = await booted;
4971
5081
  await first.catch(() => null);
4972
5082
  if (!app || removed) throw new Error('@displayxr/inline3d/splat: prepareSource on a removed tile.');
@@ -4995,6 +5105,8 @@ export function attachPlayCanvasSplat(out, wall, canvas, src, opts, pending = []
4995
5105
  diag?.mark('prepared', { ms: Math.round(performance.now() - t0), fetchOnly });
4996
5106
  // The transition the page declared: compile its shader now, in the dwell, not on its first frame.
4997
5107
  await prewarmTransition(warm); // null (no transition declared): the overlay only
5108
+ // And the effects the page will play once this asset is in (a reveal after the swap).
5109
+ for (const w of warmList) await warmEffect(w.name, w.opts);
4998
5110
  const entry = { loaded, source, state: 'ready', dispose: null };
4999
5111
  const prepared = {
5000
5112
  [PREPARED_TAG]: true,
@@ -5062,32 +5174,9 @@ export function attachPlayCanvasSplat(out, wall, canvas, src, opts, pending = []
5062
5174
  const pc = pcModule;
5063
5175
  const made = [];
5064
5176
  for (const [chunkKey, codeOf] of fx ? wantChunks : []) {
5065
- const cams = [viewer.eye, viewer._live?.cam].filter(Boolean);
5066
- const code = codeOf();
5067
- let issued = 0;
5068
- for (const cam of cams) {
5069
- const mi = managerMi(cam, null);
5070
- const src = mi?.material;
5071
- const camera = cam.camera?.camera;
5072
- if (!src?.shaderDesc || !camera?.shaderParams || typeof src.getShaderVariant !== 'function') continue;
5073
- const m = new pc.ShaderMaterial(src.shaderDesc);
5074
- src.defines.forEach((v, k) => m.setDefine(k, v));
5075
- m.shaderChunks.copy(src.shaderChunks);
5076
- m.getShaderChunks(pc.SHADERLANGUAGE_GLSL).set('gsplatModifyVS', code);
5077
- m.blendState = src.blendState;
5078
- made.push(m.getShaderVariant({
5079
- device: viewer.app.graphicsDevice,
5080
- scene: viewer.app.scene,
5081
- objDefs: mi._shaderDefs,
5082
- cameraShaderParams: camera.shaderParams,
5083
- pass: 0, // SHADER_FORWARD
5084
- sortedLights: [],
5085
- viewUniformFormat: viewer.app.renderer?.viewUniformFormat ?? null,
5086
- vertexFormat: mi.mesh?.vertexBuffer?.format,
5087
- }));
5088
- issued++;
5089
- }
5090
- if (issued) prewarmed.add(chunkKey);
5177
+ const got = issueChunkVariants(pc, codeOf());
5178
+ made.push(...got);
5179
+ if (got.length) prewarmed.add(chunkKey);
5091
5180
  }
5092
5181
  const cam = viewer.eye?.camera?.camera;
5093
5182
  if (wantOverlay && cam?.shaderParams) {
@@ -5115,6 +5204,76 @@ export function attachPlayCanvasSplat(out, wall, canvas, src, opts, pending = []
5115
5204
  }
5116
5205
 
5117
5206
 
5207
+ /**
5208
+ * Issue (compile + link, not wait for) the gsplat render variant with `code` as its tile
5209
+ * `gsplatModifyVS` chunk, on every eye camera's splat material: a throwaway material with the
5210
+ * renderer's own description, defines and chunks asks the engine's program library for the
5211
+ * variant the renderer will ask for (the library keys on the generated source + processing
5212
+ * options, not on the material). Returns the issued shaders, for finalizeWhenLinked.
5213
+ */
5214
+ function issueChunkVariants(pc, code) {
5215
+ const made = [];
5216
+ for (const cam of [viewer.eye, viewer._live?.cam].filter(Boolean)) {
5217
+ const mi = managerMi(cam, null);
5218
+ const src = mi?.material;
5219
+ const camera = cam.camera?.camera;
5220
+ if (!src?.shaderDesc || !camera?.shaderParams || typeof src.getShaderVariant !== 'function') continue;
5221
+ const m = new pc.ShaderMaterial(src.shaderDesc);
5222
+ src.defines.forEach((v, k) => m.setDefine(k, v));
5223
+ m.shaderChunks.copy(src.shaderChunks);
5224
+ m.getShaderChunks(pc.SHADERLANGUAGE_GLSL).set('gsplatModifyVS', code);
5225
+ m.blendState = src.blendState;
5226
+ const v = m.getShaderVariant({
5227
+ device: viewer.app.graphicsDevice,
5228
+ scene: viewer.app.scene,
5229
+ objDefs: mi._shaderDefs,
5230
+ cameraShaderParams: camera.shaderParams,
5231
+ pass: 0, // SHADER_FORWARD
5232
+ sortedLights: [],
5233
+ viewUniformFormat: viewer.app.renderer?.viewUniformFormat ?? null,
5234
+ vertexFormat: mi.mesh?.vertexBuffer?.format,
5235
+ });
5236
+ if (v) made.push(v);
5237
+ }
5238
+ return made;
5239
+ }
5240
+
5241
+ /**
5242
+ * handle.warmEffect(name, opts) — compile the program playEffect(name, opts) will need, now,
5243
+ * finishing the link in idle periods (KHR_parallel_shader_compile), so the effect's first frame
5244
+ * does not block on it (a particle reveal's link: 250–300 ms on a panel). Keyed on the chunk
5245
+ * the play would install with the tile effects on NOW — warm in the state you will play from.
5246
+ * Resolves `true` once warm (or already warm), `false` where nothing could be warmed (an
5247
+ * entity-scope effect, a custom fragment stage, no engine yet, an engine whose internals
5248
+ * differ: it then compiles on its first frame, as before). Never throws after validation.
5249
+ */
5250
+ async function warmEffect(name, o = {}) {
5251
+ validateEffectCall(name, o, 'play');
5252
+ await first.catch(() => null);
5253
+ if (removed || !fx || !pcModule || diag?.has('nowarm')) return false;
5254
+ let code;
5255
+ try {
5256
+ code = fx.playChunkCode(name, o);
5257
+ } catch {
5258
+ return false;
5259
+ }
5260
+ if (!code) return false;
5261
+ const key = `play|${code}`;
5262
+ if (prewarmed.has(key)) return true;
5263
+ await yieldIdle();
5264
+ if (removed) return false;
5265
+ try {
5266
+ const made = issueChunkVariants(pcModule, code);
5267
+ if (!made.length) return false;
5268
+ await finalizeWhenLinked(made);
5269
+ prewarmed.add(key);
5270
+ return true;
5271
+ } catch (err) {
5272
+ console.info('[inline3d/splat] warmEffect skipped', err);
5273
+ return false;
5274
+ }
5275
+ }
5276
+
5118
5277
  /**
5119
5278
  * Creating a program only ISSUES its compile + link; the browser resolves the link when the
5120
5279
  * program is first queried — the engine's first draw, blocking (the 35–50 ms GetProgramiv the
@@ -434,6 +434,11 @@ function addSplatUnguarded(wall, canvas, src, opts = {}) {
434
434
  setEffect() {
435
435
  throw effectsNotOnSpark('setEffect()');
436
436
  },
437
+ warmEffect() {
438
+ throw effectsNotOnSpark('warmEffect()');
439
+ },
440
+ /** PlayCanvas-backend feature (its renderer links synchronously); nothing to warm here. */
441
+ warmEntity: () => Promise.resolve(0),
437
442
  stopEffect() {
438
443
  throw effectsNotOnSpark('stopEffect()');
439
444
  },
package/model.d.ts CHANGED
@@ -222,6 +222,14 @@ export interface ModelHandle {
222
222
 
223
223
  remove(): void;
224
224
  exclude(el: Element): void;
225
+ /**
226
+ * PlayCanvas only (resolves 0 on three). Compile the lit programs `entity` (default: the model)
227
+ * will draw with, in idle time, through the renderer's own shader lookup, and finish their links
228
+ * off the frame (KHR_parallel_shader_compile), so its first draw does not freeze. Warm while it
229
+ * is hidden, in the lighting it will be shown in. Resolves the number of programs warmed.
230
+ * docs/playcanvas-adapter.md §warmEntity.
231
+ */
232
+ warmEntity(entity?: unknown, opts?: { timeoutMs?: number }): Promise<number>;
225
233
  unexclude(el: Element): void;
226
234
  /**
227
235
  * The core window's `TileHandle.firstWoven`: when it is safe to reveal the canvas. Resolves
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@displayxr/inline3d",
3
- "version": "1.42.1",
3
+ "version": "1.43.1",
4
4
  "description": "Turn any HTML <canvas> into a 3D window on a spatial display, inside an ordinary web page, for the DisplayXR Browser. Dependency-free; progressive enhancement (falls back to plain 2D everywhere else).",
5
5
  "type": "module",
6
6
  "types": "./index.d.ts",
package/splat.d.ts CHANGED
@@ -870,6 +870,12 @@ export type SplatPrepareOptions = SplatSourceOptions & {
870
870
  * resident during the dwell.
871
871
  */
872
872
  resident?: boolean;
873
+ /**
874
+ * Effects to compile during this prepare (after the asset and the transition), for a
875
+ * `playEffect` you will run once this asset is in — e.g. a reveal after the swap. Names, or
876
+ * `{ name, opts }` with the opts you will play with. See {@link SplatHandle.warmEffect}.
877
+ */
878
+ warmEffects?: Array<Exclude<SplatEffectName, 'grade' | 'clip'> | { name: Exclude<SplatEffectName, 'grade' | 'clip'>; opts?: SplatEffectTiming & Record<string, unknown> }>;
873
879
  };
874
880
 
875
881
  /** setSource's particle transitions (docs/splat-effects.md §Particle transitions). */
@@ -1091,6 +1097,17 @@ export interface SplatHandle {
1091
1097
  * display rig; a value set meanwhile applies when the video exits. `handle.rig.ipdFactor` /
1092
1098
  * `parallaxFactor` read what is in force. The 2D↔3D mode-switch ramp still multiplies on top.
1093
1099
  */
1100
+ /**
1101
+ * `engine: 'playcanvas'` only (resolves 0 on Spark). Compile the lit programs of `entity` — your
1102
+ * content under `handle.engine.root`, e.g. a glTF — in idle time, through the renderer's own
1103
+ * shader lookup (MeshInstance.getShaderInstance with its exact pass, light hash, lights, view
1104
+ * format and the eye cameras' live shader params), and finish each link off the frame
1105
+ * (KHR_parallel_shader_compile). PlayCanvas's forward renderer otherwise links every new program
1106
+ * synchronously on its first draw: a 13-material car froze 0.8–0.95 s on a panel. Warm it while
1107
+ * it is hidden (`entity.enabled = false` is fine), with the lights it will be shown under; show
1108
+ * it after this resolves. Resolves the number of programs warmed. docs/playcanvas-adapter.md §warmEntity.
1109
+ */
1110
+ warmEntity(entity: unknown, opts?: { timeoutMs?: number }): Promise<number>;
1094
1111
  setStereo(opts: SplatStereoOptions | null): SplatHandle;
1095
1112
  /** This tile in physical units — the core `TileHandle.displayMetrics()`, on the splat handle. Never rejects. */
1096
1113
  displayMetrics(): Promise<DisplayMetrics>;
@@ -1202,6 +1219,17 @@ export interface SplatHandle {
1202
1219
  name: Exclude<SplatEffectName, 'grade' | 'clip'>,
1203
1220
  opts?: SplatEffectTiming & Record<string, unknown>,
1204
1221
  ): Promise<{ finished: boolean }>;
1222
+ /**
1223
+ * `engine: 'playcanvas'` only (throws on Spark). Compile the program `playEffect(name, opts)`
1224
+ * will need NOW, finishing the link in idle periods (KHR_parallel_shader_compile), so the
1225
+ * effect's first frame does not stall on it — a particle reveal (`'assemble'`) links for
1226
+ * 250–300 ms on a panel otherwise. Keyed on the chunk the play would install with the tile
1227
+ * effects on at the time: warm in the state you will play from. Resolves `true` once warm,
1228
+ * `false` when nothing could be warmed (an entity-scope effect, a custom fragment stage); the
1229
+ * effect then compiles on its first frame, as before. Validated at the call.
1230
+ * docs/splat-effects.md §Warming an effect.
1231
+ */
1232
+ warmEffect(name: Exclude<SplatEffectName, 'grade' | 'clip'>, opts?: SplatEffectTiming & Record<string, unknown>): Promise<boolean>;
1205
1233
  /**
1206
1234
  * `engine: 'playcanvas'` only. Set a persistent effect (`grade`, `clip`, `custom`), hold a
1207
1235
  * transition at `{ progress }`, or pass `null` to remove it (the exact baseline).