@bitmagic/cli 0.1.64-dev.1 → 0.1.64-dev.3

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.
@@ -1374,6 +1374,7 @@ ${ASSET_DETAIL_HTML}
1374
1374
  image: '🖼', skybox: '🖼',
1375
1375
  glb: '🧊', gltf: '🧊', 'polygon-mesh': '🧊', vxl: '🧊', voxels: '🧊',
1376
1376
  animation: '🏃', 'animation-video': '🎥', audio: '🔊', sound: '🔊', 'sound-effect': '🔊', music: '🎵',
1377
+ video: '🎬',
1377
1378
  vwld: '🗺', spz: '✨'
1378
1379
  };
1379
1380
 
@@ -1614,6 +1615,21 @@ ${ASSET_DETAIL_HTML}
1614
1615
 
1615
1616
  var MAX_VXL_PREVIEW_BYTES = 8 * 1024 * 1024;
1616
1617
 
1618
+ /**
1619
+ * Which of a clip's two renditions to play.
1620
+ *
1621
+ * The forger stores every clip twice, and the VP9 sibling is the one worth fetching where the
1622
+ * browser takes it: same picture, ~6x smaller (757 KB against 4.5 MB on a measured 5 s 720p
1623
+ * clip), and bitmagic dev drives Chrome, which decodes VP9 natively. canPlayType only
1624
+ * promises the codec, never the file, so every caller keeps the mp4 as the fallback.
1625
+ */
1626
+ function videoSourceFor(asset) {
1627
+ var mp4 = typeof asset.url === 'string' ? asset.url : '';
1628
+ var webm = typeof asset.webmUrl === 'string' ? asset.webmUrl : '';
1629
+ if (webm === '') return mp4;
1630
+ return document.createElement('video').canPlayType('video/webm; codecs="vp9"') ? webm : mp4;
1631
+ }
1632
+
1617
1633
  /** The mesh route: the asset itself, or the GLB a voxelized one was baked from. */
1618
1634
  function glbJobFor(asset) {
1619
1635
  var glb = asset.sourceGlbUrl || asset.url;
@@ -1634,6 +1650,18 @@ ${ASSET_DETAIL_HTML}
1634
1650
  return { kind: 'vxl', url: url };
1635
1651
  }
1636
1652
 
1653
+ /**
1654
+ * The clip route. Unlike its two neighbours this one is NOT drawn by the engine — see
1655
+ * grabVideoPoster. It is listed as a job anyway so a video row goes through the same queue,
1656
+ * the same disk cache and the same hasPreview path every other tile already uses.
1657
+ */
1658
+ function videoJobFor(asset) {
1659
+ if (String(asset.type || '').toLowerCase() !== 'video') return null;
1660
+ var url = videoSourceFor(asset);
1661
+ if (typeof url !== 'string' || !/\\.(mp4|webm)(\\?|$)/i.test(url)) return null;
1662
+ return { kind: 'video', url: url };
1663
+ }
1664
+
1637
1665
  /**
1638
1666
  * How the engine should draw this asset's thumbnail, or null when there is nothing to draw.
1639
1667
  *
@@ -1652,7 +1680,7 @@ ${ASSET_DETAIL_HTML}
1652
1680
  * answer for something with no appearance.
1653
1681
  */
1654
1682
  function previewJobFor(asset) {
1655
- return glbJobFor(asset) || vxlJobFor(asset);
1683
+ return glbJobFor(asset) || vxlJobFor(asset) || videoJobFor(asset);
1656
1684
  }
1657
1685
 
1658
1686
  function assetRow(asset, isFresh, hasPreview) {
@@ -1812,6 +1840,61 @@ ${ASSET_DETAIL_HTML}
1812
1840
  return await_('VOXEL_PREVIEW_RESPONSE', 15000);
1813
1841
  }
1814
1842
 
1843
+ /** A poster is a thumbnail, not a frame grab for keeps: 256px on the long edge. */
1844
+ var POSTER_MAX_EDGE = 256;
1845
+
1846
+ /**
1847
+ * A clip's poster, drawn HERE rather than by the engine — a video is not a mesh, and this page
1848
+ * can already decode one. Answers in the same shape the two engine renderers do, so the queue
1849
+ * below does not care which drew it.
1850
+ *
1851
+ * The canvas does not taint: the asset host serves 'access-control-allow-origin: *' (checked
1852
+ * against a stored clip), which is what 'crossOrigin' here asks it to honour. Without that
1853
+ * attribute the draw would succeed and toDataURL would throw.
1854
+ *
1855
+ * Seeks a tenth of the way in rather than taking frame zero — a generated clip often opens on a
1856
+ * fade from black, and a black tile is the thing this whole preview cache exists to avoid.
1857
+ */
1858
+ function grabVideoPoster(url, requestId) {
1859
+ return new Promise(function (resolve) {
1860
+ var probe = document.createElement('video');
1861
+ var settled = false;
1862
+ var timer = null;
1863
+ function finish(previewBase64) {
1864
+ if (settled) return;
1865
+ settled = true;
1866
+ if (timer) clearTimeout(timer);
1867
+ probe.removeAttribute('src');
1868
+ resolve({ requestId: requestId, success: previewBase64 !== '', previewBase64: previewBase64 });
1869
+ }
1870
+ timer = setTimeout(function () { finish(''); }, 15000);
1871
+ probe.crossOrigin = 'anonymous';
1872
+ probe.muted = true;
1873
+ probe.preload = 'auto';
1874
+ probe.addEventListener('error', function () { finish(''); });
1875
+ probe.addEventListener('loadeddata', function () {
1876
+ probe.currentTime = Math.min(1, (probe.duration || 1) * 0.1);
1877
+ });
1878
+ probe.addEventListener('seeked', function () {
1879
+ try {
1880
+ var width = probe.videoWidth;
1881
+ var height = probe.videoHeight;
1882
+ if (!width || !height) { finish(''); return; }
1883
+ var scale = Math.min(1, POSTER_MAX_EDGE / Math.max(width, height));
1884
+ var canvas = document.createElement('canvas');
1885
+ canvas.width = Math.max(1, Math.round(width * scale));
1886
+ canvas.height = Math.max(1, Math.round(height * scale));
1887
+ canvas.getContext('2d').drawImage(probe, 0, 0, canvas.width, canvas.height);
1888
+ finish(canvas.toDataURL('image/png'));
1889
+ } catch (error) {
1890
+ // A tainted canvas or a codec the browser will not decode. Neither is worth a retry.
1891
+ finish('');
1892
+ }
1893
+ });
1894
+ probe.src = url;
1895
+ });
1896
+ }
1897
+
1815
1898
  async function drainPreviewQueue() {
1816
1899
  if (previewRunning) return;
1817
1900
  previewRunning = true;
@@ -1819,17 +1902,21 @@ ${ASSET_DETAIL_HTML}
1819
1902
  try {
1820
1903
  while (previewQueue.length > 0) {
1821
1904
  // The engine can only draw once it exists. Anything queued before then waits for the next
1822
- // pass rather than being dropped — refreshAssets re-queues on every render.
1823
- if (!loaded) return;
1905
+ // pass rather than being dropped — refreshAssets re-queues on every render. A clip's
1906
+ // poster is drawn in this page, so it is the one kind that does not wait.
1907
+ var next = previewJobFor(previewQueue[0]);
1908
+ if (!loaded && next !== null && next.kind !== 'video') return;
1824
1909
  var asset = previewQueue.shift();
1825
- var job = previewJobFor(asset);
1910
+ var job = next;
1826
1911
  if (job === null) continue;
1827
1912
 
1828
1913
  previewRequestId += 1;
1829
1914
  var requestId = 'preview-' + previewRequestId;
1830
- var response = job.kind === 'vxl'
1831
- ? await requestVoxelPreview(job.url, requestId)
1832
- : await requestGlbPreview(job.url, requestId);
1915
+ var response = job.kind === 'video'
1916
+ ? await grabVideoPoster(job.url, requestId)
1917
+ : job.kind === 'vxl'
1918
+ ? await requestVoxelPreview(job.url, requestId)
1919
+ : await requestGlbPreview(job.url, requestId);
1833
1920
 
1834
1921
  // The requestId is verified rather than assumed: a late answer from a request that already
1835
1922
  // timed out would otherwise be cached under the NEXT asset's id, which is a wrong picture
@@ -1896,6 +1983,7 @@ ${ASSET_DETAIL_HTML}
1896
1983
  var assetFrame = document.getElementById('asset-frame');
1897
1984
  var assetImage = document.getElementById('asset-image');
1898
1985
  var assetAudio = document.getElementById('asset-audio');
1986
+ var assetVideo = document.getElementById('asset-video');
1899
1987
  var assetNote = document.getElementById('asset-note');
1900
1988
  var assetFacts = document.getElementById('asset-facts');
1901
1989
  var assetResetButton = document.getElementById('asset-reset');
@@ -1907,6 +1995,9 @@ ${ASSET_DETAIL_HTML}
1907
1995
 
1908
1996
  var IMAGE_TYPES = { image: true, skybox: true };
1909
1997
  var AUDIO_TYPES = { audio: true, sound: true, 'sound-effect': true };
1998
+ // Only one member today, but the same shape as its neighbours — and the same '=== true'
1999
+ // read, so a type of 'constructor' cannot find Object.prototype's own property and pass.
2000
+ var VIDEO_TYPES = { video: true };
1910
2001
 
1911
2002
  /**
1912
2003
  * What the VIEWER should load — which is not always what the thumbnail drew.
@@ -1952,6 +2043,10 @@ ${ASSET_DETAIL_HTML}
1952
2043
 
1953
2044
  assetFacts.textContent = '';
1954
2045
  fact('Size', formatSize(asset.size));
2046
+ fact('Length', typeof asset.durationSeconds === 'number' && asset.durationSeconds > 0
2047
+ ? asset.durationSeconds.toFixed(1) + 's' : '');
2048
+ fact('Frame', typeof asset.width === 'number' && typeof asset.height === 'number'
2049
+ ? asset.width + ' × ' + asset.height : '');
1955
2050
  fact('Extent', dimensionsOf(asset));
1956
2051
  fact('Voxels', typeof asset.voxelCount === 'number' ? String(asset.voxelCount) : '');
1957
2052
  fact('Id', asset.id || '');
@@ -1975,6 +2070,16 @@ ${ASSET_DETAIL_HTML}
1975
2070
  } else if (AUDIO_TYPES[type] === true && direct) {
1976
2071
  assetAudio.src = direct;
1977
2072
  assetAudio.classList.remove('hidden');
2073
+ } else if (VIDEO_TYPES[type] === true && direct) {
2074
+ // A clip is already the thing, like an image — no renderer, no iframe. The mp4 is wired to
2075
+ // the error event rather than left to chance: canPlayType answers for the codec, and a
2076
+ // WebM that fails for any other reason would otherwise be a black rectangle with no sound.
2077
+ assetVideo.onerror = function () {
2078
+ if (assetVideo.getAttribute('src') === direct) return;
2079
+ assetVideo.src = direct;
2080
+ };
2081
+ assetVideo.src = videoSourceFor(asset);
2082
+ assetVideo.classList.remove('hidden');
1978
2083
  } else if (type === 'animation' && direct) {
1979
2084
  // The clip plays on the engine's own block character — /animation-preview imports the
1980
2085
  // project's debug/AnimationPreview.js, the module behind the Creator's clip preview.
@@ -2041,6 +2146,11 @@ ${ASSET_DETAIL_HTML}
2041
2146
  assetImage.removeAttribute('src');
2042
2147
  assetAudio.pause();
2043
2148
  assetAudio.removeAttribute('src');
2149
+ // Same reason the iframe goes to about:blank: a dialog nobody is looking at must not keep
2150
+ // decoding video next to the game that wants the GPU.
2151
+ assetVideo.pause();
2152
+ assetVideo.onerror = null;
2153
+ assetVideo.removeAttribute('src');
2044
2154
  if (assetPausedByOverlay) {
2045
2155
  post('SET_PAUSE', { paused: false });
2046
2156
  assetPausedByOverlay = false;
@@ -1 +1 @@
1
- {"version":3,"file":"shell-page.js","sourceRoot":"","sources":["../../src/editor/shell-page.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAiDG;AAEH,OAAO,EACL,qBAAqB,EAAE,kBAAkB,GAC1C,MAAM,qDAAqD,CAAC;AAC7D,OAAO,EAAE,iBAAiB,EAAE,MAAM,+CAA+C,CAAC;AAClF,OAAO,EAAE,gBAAgB,EAAE,iBAAiB,EAAE,MAAM,yBAAyB,CAAC;AAC9E,OAAO,EAAE,iBAAiB,EAAE,kBAAkB,EAAE,MAAM,oBAAoB,CAAC;AAC3E,OAAO,EAAE,mBAAmB,EAAE,oBAAoB,EAAE,qBAAqB,EAAE,MAAM,sBAAsB,CAAC;AACxG,OAAO,EAAE,cAAc,EAAE,MAAM,qBAAqB,CAAC;AACrD,OAAO,EAAE,gBAAgB,EAAE,iBAAiB,EAAE,eAAe,EAAE,MAAM,mBAAmB,CAAC;AACzF,OAAO,EAAE,eAAe,EAAE,gBAAgB,EAAE,MAAM,kBAAkB,CAAC;AACrE,OAAO,EAAE,qBAAqB,EAAE,kBAAkB,EAAE,mBAAmB,EAAE,MAAM,aAAa,CAAC;AAE7F,oGAAoG;AACpG,MAAM,gBAAgB,GAAG,GAAG,CAAC;AAE7B;;;;;;GAMG;AACH,MAAM,gBAAgB,GAAG,GAAG,CAAC;AAE7B;;;;;;;;;;;;GAYG;AACH,SAAS,sBAAsB;IAC7B,OAAO;QACL,6BAA6B,IAAI,CAAC,SAAS,CAAC,iBAAiB,CAAC,GAAG;QACjE,iCAAiC,qBAAqB,CAAC,QAAQ,EAAE,GAAG;QACpE,8BAA8B,kBAAkB,CAAC,QAAQ,EAAE,GAAG;KAC/D,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC;AACjB,CAAC;AAED;;;;;;GAMG;AACH,MAAM,qBAAqB,GAAG,IAAI,CAAC;AAEnC,kGAAkG;AAClG,SAAS,UAAU,CAAC,KAAa;IAC/B,OAAO,KAAK;SACT,OAAO,CAAC,IAAI,EAAE,OAAO,CAAC;SACtB,OAAO,CAAC,IAAI,EAAE,QAAQ,CAAC;SACvB,OAAO,CAAC,IAAI,EAAE,MAAM,CAAC;SACrB,OAAO,CAAC,IAAI,EAAE,MAAM,CAAC,CAAC;AAC3B,CAAC;AAmBD,oGAAoG;AACpG,MAAM,oBAAoB,GAAG,cAAc,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;AAElE,MAAM,UAAU,iBAAiB,CAAC,OAA2B;IAC3D,MAAM,OAAO,GAAG,oBAAoB,OAAO,CAAC,QAAQ,2BAA2B,kBAAkB,CAAC,OAAO,CAAC,MAAM,CAAC,EAAE,CAAC;IACpH,MAAM,OAAO,GAAG,OAAO,CAAC,OAAO,KAAK,KAAK,CAAC;IAC1C,MAAM,SAAS,GAAG,OAAO;QACvB,CAAC,CAAC,YAAY,OAAO,CAAC,UAAU,uDAAuD;QACvF,CAAC,CAAC,YAAY,OAAO,CAAC,UAAU,qCAAqC,CAAC;IACxE,MAAM,KAAK,GAAG,GAAG,OAAO,CAAC,QAAQ,kBAAkB,OAAO,CAAC,CAAC,CAAC,cAAc,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC;IACnF,OAAO;;;;SAIA,UAAU,CAAC,KAAK,CAAC;EACxB,kBAAkB;;EAElB,mBAAmB;EACnB,qBAAqB;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;EAkIrB,gBAAgB;EAChB,iBAAiB;EACjB,mBAAmB;EACnB,gBAAgB;EAChB,eAAe;;;;;;yCAMwB,OAAO,CAAC,CAAC,CAAC,oBAAoB,CAAC,CAAC,CAAC,EAAE,YAAY,UAAU,CAAC,SAAS,CAAC,KAAK,OAAO,CAAC,CAAC,CAAC,WAAW,CAAC,CAAC,CAAC,SAAS;oFAC/D,UAAU,CAAC,OAAO,CAAC,QAAQ,CAAC;2DACrD,UAAU,CAAC,OAAO,CAAC,MAAM,CAAC;kFACH,UAAU,CAAC,OAAO,CAAC,WAAW,CAAC;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;EA4C/G,gBAAgB;;;;;;;;;;;;;;;;;;;;;;;;;EAyBhB,kBAAkB;EAClB,oBAAoB;EACpB,iBAAiB;EACjB,iBAAiB;;;;mBAIA,IAAI,CAAC,SAAS,CAAC,OAAO,CAAC;kBACxB,gBAAgB;yBACT,qBAAqB;2BACnB,gBAAgB;;IAEvC,sBAAsB,EAAE;;;;;;;;0BAQF,IAAI,CAAC,SAAS,CAAC,qBAAqB,CAAC;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;wBA42BvC,oBAAoB;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;EAiuC1C,eAAe;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CAmChB,CAAC;AACF,CAAC"}
1
+ {"version":3,"file":"shell-page.js","sourceRoot":"","sources":["../../src/editor/shell-page.ts"],"names":[],"mappings":"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;;;;;;0BAQF,IAAI,CAAC,SAAS,CAAC,qBAAqB,CAAC;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;wBA42BvC,oBAAoB;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;EA+0C1C,eAAe;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CAmChB,CAAC;AACF,CAAC"}
@@ -0,0 +1,26 @@
1
+ /**
2
+ * The `building-levels-in-blender` skill: a hand-designed low-poly world written as a Python script
3
+ * against the shipped `bmlevel` library, built by Blender into one GLB plus the collider / door /
4
+ * light / landmark JSON, audited with ray casts, reviewed from rendered stills, and loaded by the
5
+ * engine's `MeshLevel`. Distilled from a creator's space-station game whose agent installed Blender
6
+ * and hand-rolled every part of this — the build vocabulary, the audits, the review renders, the
7
+ * level loader — and hit the two failures the skill now names up front (a GLB fetched from a local
8
+ * path that 404s after publish; NPC spawns routed into an absent voxel grid).
9
+ *
10
+ * Same contract as `unity-skill-files.ts`: pure functions returning file contents, registered in
11
+ * `SHIPPED_SKILLS` (project.ts). The Python and Blender scripts ship in the package's
12
+ * `assets/blender-level/` (see `files` in package.json) rather than being rendered into every
13
+ * project; the skill's step 1 copies them into `tools/blender-level/`.
14
+ *
15
+ * The skill's first section is the gate: the path is for frontier models with Blender available,
16
+ * and everything else goes to `bitmagic forge`. The CLI cannot tell which model drives it, so the
17
+ * gate is the agent's own call — stated once, plainly, where the planning skill sends it.
18
+ *
19
+ * Backticks and `${` are escaped for the template literal; `project-files.test.ts` checks the
20
+ * rendered frontmatter and that no escape leaks into the output.
21
+ */
22
+ export declare function renderBlenderLevelsSkill(): string;
23
+ export declare function renderBlenderConventionsReference(): string;
24
+ export declare function renderBlenderAuditsReference(): string;
25
+ export declare function renderBlenderPassesReference(): string;
26
+ export declare function renderBlenderEngineHandoffReference(): string;