@vosjs/studio-core 0.42.0 → 0.42.2

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/dist/index.js CHANGED
@@ -623,7 +623,7 @@ function projectFromArtifact(artifact, videoUrl, opts = {}) {
623
623
  const coverage = surface === "monitor" || artifact.meta.windowMovedDuringTake || unfocused ? 0 : norm.coverage;
624
624
  if (artifact.cursor.length > 0 && coverage < CAPTURE_COVERAGE_MIN) {
625
625
  console.warn(
626
- "[voila/studio] dropping cursor track \u2014",
626
+ "[studio] dropping cursor track \u2014",
627
627
  surface === "monitor" ? "entire-screen takes have no cursor effects (untrackable outside browser pages)" : artifact.meta.windowMovedDuringTake ? "window moved/resized during the take" : unfocused ? `browser window focused only ${Math.round((artifact.meta.windowFocusedFrac ?? 0) * 100)}% of the take \u2014 a different window was likely shared` : `coverage ${norm.coverage.toFixed(2)} (wrong surface shared, or unusable capture geometry)`
628
628
  );
629
629
  }
@@ -4843,7 +4843,7 @@ var STUDIO_SETUP = `async (ctx) => {
4843
4843
  // The MediaError rides along: code 4 is an unreadable/unsupported source
4844
4844
  // (a dead blob URL, a 404), code 3 a decode failure, code 2 a network
4845
4845
  // stall. A bare "failed to load" gave the fleet log nothing to act on.
4846
- v.onerror = () => rej(new Error('[voila] video failed to load' + (v.error ? ' (' + v.error.code + (v.error.message ? ': ' + v.error.message : '') + ')' : '')))
4846
+ v.onerror = () => rej(new Error('[studio] video failed to load' + (v.error ? ' (' + v.error.code + (v.error.message ? ': ' + v.error.message : '') + ')' : '')))
4847
4847
  v.load()
4848
4848
  })
4849
4849
  cache.set(src, v)
@@ -4856,7 +4856,7 @@ var STUDIO_SETUP = `async (ctx) => {
4856
4856
  const img = new Image()
4857
4857
  img.crossOrigin = 'anonymous'
4858
4858
  img.onload = () => { cache.set(src, img); res(img) }
4859
- img.onerror = () => rej(new Error('[voila] image failed to load'))
4859
+ img.onerror = () => rej(new Error('[studio] image failed to load'))
4860
4860
  img.src = src
4861
4861
  })
4862
4862
  }
@@ -4870,7 +4870,7 @@ var STUDIO_SETUP = `async (ctx) => {
4870
4870
  const faces = ctx.data.overlayFonts || ${JSON.stringify(OVERLAY_FONT_FACES)}
4871
4871
  // Mark what SETUP registers, so ON_FRAME's live path (a text overlay
4872
4872
  // arriving through SET_DATA) never adds the same face twice.
4873
- const ofSet = window.__voilaFontSet || (window.__voilaFontSet = {})
4873
+ const ofSet = window.__vosFontSet || (window.__vosFontSet = {})
4874
4874
  const loads = faces.map((f) => {
4875
4875
  ofSet[f.family + '|' + f.weight] = 1
4876
4876
  const ff = new FontFace(f.family, 'url(' + f.url + ')', { weight: String(f.weight) })
@@ -4881,7 +4881,7 @@ var STUDIO_SETUP = `async (ctx) => {
4881
4881
  Promise.all(loads).catch(() => {}),
4882
4882
  new Promise((res) => setTimeout(res, 4000)),
4883
4883
  ])
4884
- } catch (e) { console.warn('[voila] overlay fonts failed to load', e) }
4884
+ } catch (e) { console.warn('[studio] overlay fonts failed to load', e) }
4885
4885
  }
4886
4886
  // An HTML layer arrives as SOURCE, and the picture is built HERE.
4887
4887
  //
@@ -4924,7 +4924,7 @@ var STUDIO_SETUP = `async (ctx) => {
4924
4924
  // Fail open per URL, and NOT sticky: the layer paints without it now
4925
4925
  // and the next build asks again.
4926
4926
  faceData.delete(url)
4927
- console.warn('[voila] html layer ' + what + ' failed to load', url, e)
4927
+ console.warn('[studio] html layer ' + what + ' failed to load', url, e)
4928
4928
  return ''
4929
4929
  })
4930
4930
  faceData.set(url, p)
@@ -4976,7 +4976,7 @@ var STUDIO_SETUP = `async (ctx) => {
4976
4976
  ns.htmlSafe = true
4977
4977
  } catch (e) {
4978
4978
  ns.htmlSafe = false
4979
- console.error('[voila] html layers are off: this browser taints a canvas that draws a foreignObject SVG', e)
4979
+ console.error('[studio] html layers are off: this browser taints a canvas that draws a foreignObject SVG', e)
4980
4980
  }
4981
4981
  }
4982
4982
  // One picture per clip in the cache: the previous key goes when a new
@@ -4993,7 +4993,7 @@ var STUDIO_SETUP = `async (ctx) => {
4993
4993
  // recorded on the clip and said at the level a render page forwards.
4994
4994
  ns.buildHtmlLayer = (oc, live) => buildHtmlLayer(oc, live).catch((e) => {
4995
4995
  ns.htmlErrors[oc.id] = String((e && e.message) || e)
4996
- console.error('[voila] html layer ' + oc.id + ' ' + ns.htmlErrors[oc.id])
4996
+ console.error('[studio] html layer ' + oc.id + ' ' + ns.htmlErrors[oc.id])
4997
4997
  })
4998
4998
  // The capture contract for a LIVE layer, on ANY anchor. A recording's own
4999
4999
  // program installs the settle machinery (pendingDecodes, the wait a
@@ -5036,7 +5036,7 @@ var STUDIO_SETUP = `async (ctx) => {
5036
5036
  try {
5037
5037
  if (oc.kind === 'image') await loadImage(oc.key)
5038
5038
  else await load(oc.key, true)
5039
- } catch (e) { console.warn('[voila] overlay media failed to load', oc.key, e) }
5039
+ } catch (e) { console.warn('[studio] overlay media failed to load', oc.key, e) }
5040
5040
  }
5041
5041
  // GLB object assets (V3b): load via the engine's GLTFLoader addon into a
5042
5042
  // shared cache, bbox-NORMALIZED (norm = 1/maxDim) so transform3d.scale means
@@ -5048,7 +5048,7 @@ var STUDIO_SETUP = `async (ctx) => {
5048
5048
  if (!oc.asset || oc.asset.kind !== 'gltf' || !oc.asset.key || objCache.has(oc.asset.key)) continue
5049
5049
  try {
5050
5050
  const GL = ctx.loaders && ctx.loaders.GLTFLoader
5051
- if (!GL) { console.warn('[voila] GLTFLoader unavailable'); continue }
5051
+ if (!GL) { console.warn('[studio] GLTFLoader unavailable'); continue }
5052
5052
  const gltf = await new Promise((res, rej) => new GL().load(oc.asset.key, res, undefined, rej))
5053
5053
  const box = new ctx.THREE.Box3().setFromObject(gltf.scene)
5054
5054
  const size = new ctx.THREE.Vector3()
@@ -5057,7 +5057,7 @@ var STUDIO_SETUP = `async (ctx) => {
5057
5057
  box.getCenter(center)
5058
5058
  const maxDim = Math.max(size.x, size.y, size.z) || 1
5059
5059
  objCache.set(oc.asset.key, { scene: gltf.scene, norm: 1 / maxDim, center: center })
5060
- } catch (e) { console.warn('[voila] glb failed to load', oc.asset.key, e) }
5060
+ } catch (e) { console.warn('[studio] glb failed to load', oc.asset.key, e) }
5061
5061
  }
5062
5062
  // 3D-text typefaces: FontLoader JSONs into a shared cache, keyed by
5063
5063
  // URL. Awaited here so frame 0 is complete on cold loads (export/chunk
@@ -5068,10 +5068,10 @@ var STUDIO_SETUP = `async (ctx) => {
5068
5068
  if (!oc.asset || oc.asset.kind !== 'text3d' || !oc.asset.url || fontCache.has(oc.asset.url)) continue
5069
5069
  try {
5070
5070
  const FL = ctx.loaders && ctx.loaders.FontLoader
5071
- if (!FL) { console.warn('[voila] FontLoader unavailable'); continue }
5071
+ if (!FL) { console.warn('[studio] FontLoader unavailable'); continue }
5072
5072
  const font = await new Promise((res, rej) => new FL().load(oc.asset.url, res, undefined, rej))
5073
5073
  fontCache.set(oc.asset.url, font)
5074
- } catch (e) { console.warn('[voila] typeface failed to load', oc.asset.url, e) }
5074
+ } catch (e) { console.warn('[studio] typeface failed to load', oc.asset.url, e) }
5075
5075
  }
5076
5076
  // Audio clips (music/SFX): start decoding into the shared cache now, but
5077
5077
  // never hold the first frame for it. Playback starts on a press, so the
@@ -5096,7 +5096,7 @@ var STUDIO_SETUP = `async (ctx) => {
5096
5096
  bufs.set(c.key, b)
5097
5097
  if (ns.audioSched) ns.audioSched.sig = ''
5098
5098
  })
5099
- .catch((e) => console.warn('[voila] audio decode failed', c.key, e))
5099
+ .catch((e) => console.warn('[studio] audio decode failed', c.key, e))
5100
5100
  .then(() => pend.delete(c.key))
5101
5101
  }
5102
5102
  }
@@ -5218,7 +5218,7 @@ var STUDIO_FRAME = `(ctx, content, dt) => {
5218
5218
  .then(function (res) { return res.arrayBuffer() })
5219
5219
  .then(function (ab) { return actx.decodeAudioData(ab) })
5220
5220
  .then(function (b) { bufs.set(cc.key, b); AS.sig = '' })
5221
- .catch(function (e) { console.warn('[voila] audio decode failed', cc.key, e) })
5221
+ .catch(function (e) { console.warn('[studio] audio decode failed', cc.key, e) })
5222
5222
  .then(function () { pend.delete(cc.key) })
5223
5223
  })(clips[di])
5224
5224
  }
@@ -5604,7 +5604,7 @@ var STUDIO_FRAME = `(ctx, content, dt) => {
5604
5604
  // Keyed per window, so SETUP's cold-load faces and repeat frames never
5605
5605
  // re-register; fail-open, frames repaint as each face lands.
5606
5606
  if (typeof FontFace !== 'undefined') {
5607
- var ofSet = window.__voilaFontSet || (window.__voilaFontSet = {})
5607
+ var ofSet = window.__vosFontSet || (window.__vosFontSet = {})
5608
5608
  var ofBase = d.overlayFonts || ${JSON.stringify(OVERLAY_FONT_FACES)}
5609
5609
  for (var ofi = 0; ofi < ofBase.length; ofi++) {
5610
5610
  var ofB = ofBase[ofi]
@@ -6768,7 +6768,7 @@ var FRAME_STEP_SRC = `
6768
6768
  // A WebCodecs provider (capture pages) services the frame by PTS at
6769
6769
  // decode speed \u2014 no element seek, no 250ms settle cap. The element stays
6770
6770
  // paused as the dimension source and fallback.
6771
- var wcp = vid.__voilaWc
6771
+ var wcp = vid.__vosWc
6772
6772
  if (wcp) {
6773
6773
  var wcT = Math.min(srcT, wcp.duration || srcT)
6774
6774
  if (wcp.req !== wcT) {
@@ -6799,7 +6799,7 @@ var FRAME_STEP_SRC = `
6799
6799
  // Capture pages: the loop's frame by PTS from its own provider, like the
6800
6800
  // footage. The loop clock only moves forward between wraps, so the walk
6801
6801
  // stays sequential and a wrap is one re-seek to the keyframe at 0.
6802
- var bwc = bgEl.__voilaWc
6802
+ var bwc = bgEl.__vosWc
6803
6803
  if (bwc) {
6804
6804
  var bwT = Math.min(bgT, bwc.duration || bgT)
6805
6805
  if (bwc.req !== bwT) {
@@ -6846,9 +6846,9 @@ var FRAME_STEP_SRC = `
6846
6846
  if (fired) return
6847
6847
  fired = true
6848
6848
  g.removeEventListener('loadeddata', done)
6849
- if (g.__voilaWant != null && g.readyState >= 1) {
6850
- var want = g.__voilaWant
6851
- g.__voilaWant = null
6849
+ if (g.__vosWant != null && g.readyState >= 1) {
6850
+ var want = g.__vosWant
6851
+ g.__vosWant = null
6852
6852
  var seeked = function () { g.removeEventListener('seeked', seeked); resolve() }
6853
6853
  g.addEventListener('seeked', seeked)
6854
6854
  setTimeout(seeked, 250)
@@ -6868,8 +6868,8 @@ var FRAME_STEP_SRC = `
6868
6868
  return g
6869
6869
  }
6870
6870
  function stepGhost(g, at) {
6871
- if (g.readyState >= 1) { g.__voilaWant = null; stepPaused(g, at); return }
6872
- g.__voilaWant = at
6871
+ if (g.readyState >= 1) { g.__vosWant = null; stepPaused(g, at); return }
6872
+ g.__vosWant = at
6873
6873
  }`;
6874
6874
  var SETUP = `async (ctx) => {
6875
6875
  ;${timelineRuntimeCode2}
@@ -6897,11 +6897,11 @@ var SETUP = `async (ctx) => {
6897
6897
  const BG_BLOB_MAX = 64 * 1024 * 1024
6898
6898
  const toBlobUrl = async (src) => {
6899
6899
  const resp = await fetch(src)
6900
- if (!resp.ok) throw new Error('[voila] blob fetch HTTP ' + resp.status)
6900
+ if (!resp.ok) throw new Error('[studio] blob fetch HTTP ' + resp.status)
6901
6901
  const len = Number(resp.headers.get('content-length') || 0)
6902
6902
  if (len > BLOB_FETCH_MAX) {
6903
6903
  try { if (resp.body) await resp.body.cancel() } catch (e) { void e }
6904
- throw new Error('[voila] blob fetch over size cap')
6904
+ throw new Error('[studio] blob fetch over size cap')
6905
6905
  }
6906
6906
  const fetched = await resp.blob()
6907
6907
  // Keep the Blob itself: the WebCodecs provider demuxes the SAME
@@ -6915,7 +6915,7 @@ var SETUP = `async (ctx) => {
6915
6915
  let url = src
6916
6916
  if (ctx.data.videoFetchMode === 'blob') {
6917
6917
  try { url = await toBlobUrl(src) }
6918
- catch (e) { console.warn('[voila] blob fetch failed, using network src', e) }
6918
+ catch (e) { console.warn('[studio] blob fetch failed, using network src', e) }
6919
6919
  }
6920
6920
  v = document.createElement('video')
6921
6921
  v.src = url
@@ -6928,7 +6928,7 @@ var SETUP = `async (ctx) => {
6928
6928
  // The MediaError rides along: code 4 is an unreadable/unsupported source
6929
6929
  // (a dead blob URL, a 404), code 3 a decode failure, code 2 a network
6930
6930
  // stall. A bare "failed to load" gave the fleet log nothing to act on.
6931
- v.onerror = () => rej(new Error('[voila] video failed to load' + (v.error ? ' (' + v.error.code + (v.error.message ? ': ' + v.error.message : '') + ')' : '')))
6931
+ v.onerror = () => rej(new Error('[studio] video failed to load' + (v.error ? ' (' + v.error.code + (v.error.message ? ': ' + v.error.message : '') + ')' : '')))
6932
6932
  v.load()
6933
6933
  })
6934
6934
  cache.set(src, v)
@@ -6943,7 +6943,7 @@ var SETUP = `async (ctx) => {
6943
6943
  const img = new Image()
6944
6944
  img.crossOrigin = 'anonymous'
6945
6945
  img.onload = () => { cache.set(src, img); res(img) }
6946
- img.onerror = () => rej(new Error('[voila] image failed to load'))
6946
+ img.onerror = () => rej(new Error('[studio] image failed to load'))
6947
6947
  img.src = src
6948
6948
  })
6949
6949
  }
@@ -7019,7 +7019,7 @@ var SETUP = `async (ctx) => {
7019
7019
  provider.req = t
7020
7020
  chain = chain
7021
7021
  .then(() => advanceTo(t))
7022
- .catch((e) => { console.warn('[voila] webcodecs frame failed', e) })
7022
+ .catch((e) => { console.warn('[studio] webcodecs frame failed', e) })
7023
7023
  return chain
7024
7024
  },
7025
7025
  // Draw the CURRENT frame into the card. Returns false (element path
@@ -7054,12 +7054,12 @@ var SETUP = `async (ctx) => {
7054
7054
  (wcProvider.width === video.videoWidth &&
7055
7055
  wcProvider.height === video.videoHeight))
7056
7056
  ) {
7057
- video.__voilaWc = wcProvider
7057
+ video.__vosWc = wcProvider
7058
7058
  } else if (wcProvider) {
7059
- console.warn('[voila] webcodecs dims mismatch \u2014 element path keeps the frame')
7059
+ console.warn('[studio] webcodecs dims mismatch \u2014 element path keeps the frame')
7060
7060
  }
7061
7061
  } catch (e) {
7062
- console.warn('[voila] webcodecs provider unavailable, seeks stay html5', e)
7062
+ console.warn('[studio] webcodecs provider unavailable, seeks stay html5', e)
7063
7063
  }
7064
7064
  }
7065
7065
  // The webcam is a separate recording (video-only) drawn as an editable bubble overlay.
@@ -7078,14 +7078,14 @@ var SETUP = `async (ctx) => {
7078
7078
  if (!m.isImage && ctx.data.videoDecodeMode === 'webcodecs') {
7079
7079
  try {
7080
7080
  const wp = await makeWcProvider(m.src)
7081
- if (wp && (!el.videoWidth || (wp.width === el.videoWidth && wp.height === el.videoHeight))) el.__voilaWc = wp
7081
+ if (wp && (!el.videoWidth || (wp.width === el.videoWidth && wp.height === el.videoHeight))) el.__vosWc = wp
7082
7082
  } catch (e) {
7083
- console.warn('[voila] webcodecs provider unavailable for a media, seeks stay html5', e)
7083
+ console.warn('[studio] webcodecs provider unavailable for a media, seeks stay html5', e)
7084
7084
  }
7085
7085
  }
7086
7086
  mediaEls[m.id] = el
7087
7087
  } catch (e) {
7088
- console.warn('[voila] a media failed to load', m.id, e)
7088
+ console.warn('[studio] a media failed to load', m.id, e)
7089
7089
  }
7090
7090
  }
7091
7091
  // Background media (frame.backgroundMedia): warm-load so the first paint is
@@ -7105,7 +7105,7 @@ var SETUP = `async (ctx) => {
7105
7105
  // into a Blob first when it is small enough (one request instead
7106
7106
  // of ranged reads against a CDN). FAIL-OPEN to the element at
7107
7107
  // every step, like the footage.
7108
- if (ctx.data.videoDecodeMode === 'webcodecs' && !bgV.__voilaWc) {
7108
+ if (ctx.data.videoDecodeMode === 'webcodecs' && !bgV.__vosWc) {
7109
7109
  try {
7110
7110
  if (!(ns.videoBlobs && ns.videoBlobs.get(bgm.key)) && bgm.key.indexOf('blob:') !== 0 && bgm.key.indexOf('data:') !== 0) {
7111
7111
  try {
@@ -7119,22 +7119,22 @@ var SETUP = `async (ctx) => {
7119
7119
  } catch (e) { void e }
7120
7120
  }
7121
7121
  const bgWc = await makeWcProvider(bgm.key)
7122
- if (bgWc && (!bgV.videoWidth || (bgWc.width === bgV.videoWidth && bgWc.height === bgV.videoHeight))) bgV.__voilaWc = bgWc
7122
+ if (bgWc && (!bgV.videoWidth || (bgWc.width === bgV.videoWidth && bgWc.height === bgV.videoHeight))) bgV.__vosWc = bgWc
7123
7123
  } catch (e) {
7124
- console.warn('[voila] webcodecs provider unavailable for the background, seeks stay html5', e)
7124
+ console.warn('[studio] webcodecs provider unavailable for the background, seeks stay html5', e)
7125
7125
  }
7126
7126
  }
7127
7127
  }
7128
- } catch (e) { console.warn('[voila] background media failed to load', e) }
7128
+ } catch (e) { console.warn('[studio] background media failed to load', e) }
7129
7129
  }
7130
7130
  // Which path decodes what, for a host's telemetry: 'provider' is the
7131
7131
  // sequential WebCodecs walk, 'element' an HTMLVideoElement seek per frame.
7132
- ns.voilaDecode = {
7133
- recording: ctx.data.isImage ? 'none' : video.__voilaWc ? 'provider' : 'element',
7132
+ ns.studioDecode = {
7133
+ recording: ctx.data.isImage ? 'none' : video.__vosWc ? 'provider' : 'element',
7134
7134
  backdrop: (function () {
7135
7135
  if (!bgm || !bgm.key || bgm.kind === 'image') return 'none'
7136
7136
  const bgHit = cache.get(bgm.key)
7137
- return bgHit && bgHit.__voilaWc ? 'provider' : 'element'
7137
+ return bgHit && bgHit.__vosWc ? 'provider' : 'element'
7138
7138
  })(),
7139
7139
  }
7140
7140
  // Frame prep: a capture harness calls every ns.framePrep hook right after
@@ -7150,7 +7150,7 @@ var SETUP = `async (ctx) => {
7150
7150
  // chaining; playback never calls it; a harness without it keeps the
7151
7151
  // two-phase loop and stays correct.
7152
7152
  ns.framePrep = ns.framePrep || new Map()
7153
- ns.framePrep.set('voila.card', (t) => {
7153
+ ns.framePrep.set('studio.card', (t) => {
7154
7154
  if (ns.isPaused === false) return
7155
7155
  const d = ctx.data
7156
7156
  const TL = globalThis.__vosTimeline
@@ -7438,17 +7438,17 @@ var ON_FRAME = `(ctx, content, dt) => {
7438
7438
  // allow="autoplay" so the top frame's activation counts here).
7439
7439
  if (!vid.muted && err && err.name === 'NotAllowedError') {
7440
7440
  vid.muted = true
7441
- vid.__voilaAutoMuted = true
7441
+ vid.__vosAutoMuted = true
7442
7442
  var p2 = vid.play()
7443
7443
  if (p2 && p2.catch) p2.catch(function () {})
7444
7444
  }
7445
7445
  })
7446
- } else if (vid.__voilaAutoMuted && d.audioUnlocked) {
7446
+ } else if (vid.__vosAutoMuted && d.audioUnlocked) {
7447
7447
  // First user gesture happened \u2014 lift the policy fallback. If the
7448
7448
  // browser still objects it pauses the element, and the paused branch
7449
7449
  // above self-heals (re-mute + resume) on the next frame.
7450
7450
  vid.muted = false
7451
- vid.__voilaAutoMuted = false
7451
+ vid.__vosAutoMuted = false
7452
7452
  }
7453
7453
  } else {
7454
7454
  // The paused step (FRAME_STEP_SRC): the provider by PTS, else the
@@ -7768,7 +7768,7 @@ var ON_FRAME = `(ctx, content, dt) => {
7768
7768
  // The loop's provider draws its current sample (paused capture
7769
7769
  // pages); false \u21D2 the element (playback, or before the first ask).
7770
7770
  try {
7771
- var bgWcp = !bgIsImg && !playing ? bgEl.__voilaWc : null
7771
+ var bgWcp = !bgIsImg && !playing ? bgEl.__vosWc : null
7772
7772
  if (!(bgWcp && bgWcp.draw(bgC, null, (W - bgDw) / 2 + bgOx, (H - bgDh) / 2 + bgOy, bgDw, bgDh))) {
7773
7773
  bgC.drawImage(bgEl, (W - bgDw) / 2 + bgOx, (H - bgDh) / 2 + bgOy, bgDw, bgDh)
7774
7774
  }
@@ -8030,7 +8030,7 @@ var ON_FRAME = `(ctx, content, dt) => {
8030
8030
  // The provider draws the current sample directly (crop-capable,
8031
8031
  // dimension-checked at attach); false \u21D2 element path (pre-first-seek,
8032
8032
  // or the provider never attached).
8033
- var wcp3 = video.__voilaWc
8033
+ var wcp3 = video.__vosWc
8034
8034
  if (!(wcp3 && wcp3.draw(c, crp, ceX, ceY, ceW, ceH))) {
8035
8035
  if (crp) c.drawImage(video, crp.x, crp.y, crp.w, crp.h, ceX, ceY, ceW, ceH)
8036
8036
  else c.drawImage(video, ceX, ceY, ceW, ceH)
@@ -8589,10 +8589,10 @@ var ON_FRAME = `(ctx, content, dt) => {
8589
8589
  // gated inside their blocks (dirty-tracking above).
8590
8590
  if (card.texture) card.texture.needsUpdate = true
8591
8591
 
8592
- // verification hook (no-op unless the harness sets window.__VOILA_DEBUG__).
8592
+ // verification hook (no-op unless the harness sets window.__STUDIO_DEBUG__).
8593
8593
  // cv = the CARD 2D canvas; bgCv/ovCv are the background/overlay layers (v2).
8594
- if (typeof window !== 'undefined' && window.__VOILA_DEBUG__) {
8595
- window.__voilaDebug = { cv: cv, bgCv: bg.canvas, ovCv: ov.canvas, W: W, H: H, glW: gl && gl.width, glH: gl && gl.height, t: t, canvases: document.querySelectorAll('canvas').length }
8594
+ if (typeof window !== 'undefined' && window.__STUDIO_DEBUG__) {
8595
+ window.__studioDebug = { cv: cv, bgCv: bg.canvas, ovCv: ov.canvas, W: W, H: H, glW: gl && gl.width, glH: gl && gl.height, t: t, canvases: document.querySelectorAll('canvas').length }
8596
8596
  }
8597
8597
  }`;
8598
8598
  function studioLayerData(input, duration, pins) {