diff --git a/plugins.json b/plugins.json
index 971cae2e..af8478a8 100644
--- a/plugins.json
+++ b/plugins.json
@@ -1569,5 +1569,23 @@
"repository": "https://github.com/hrmcngs/sb-worn-display-blockbench",
"bug_tracker": "https://github.com/hrmcngs/sb-worn-display-blockbench/issues",
"creation_date": "2026-05-31"
+ },
+ "cosmiq_cosmetic_creator": {
+ "title": "Cosmiq Creator",
+ "author": "Cosmiq Team",
+ "description": "Create anchored accessories and editable numbered multi-actor emote scenes.",
+ "icon": "accessibility_new",
+ "version": "0.17.0",
+ "min_version": "5.0.0",
+ "variant": "both",
+ "creation_date": "2026-08-01",
+ "await_loading": true,
+ "tags": ["Minecraft: Java Edition"],
+ "website": "https://cosmiq.gg/",
+ "repository": "https://github.com/CosmiqGG/cosmiq-cosmetic-creator",
+ "bug_tracker": "https://github.com/CosmiqGG/cosmiq-cosmetic-creator/issues",
+ "contributes": {
+ "formats": ["cosmiq_cosmetic"]
+ }
}
}
diff --git a/plugins/cosmiq_cosmetic_creator/about.md b/plugins/cosmiq_cosmetic_creator/about.md
new file mode 100644
index 00000000..075a49f6
--- /dev/null
+++ b/plugins/cosmiq_cosmetic_creator/about.md
@@ -0,0 +1,10 @@
+# Cosmiq Creator
+
+Create anchored Minecraft: Java Edition accessories and editable multi-actor emote scenes directly in Blockbench.
+
+## Info
+
+- Need help? Check out the [Creator Guides](https://guides.cosmiq.gg/) and [YouTube Guides](https://www.youtube.com/@CosmiqGG) for tutorials.
+- Join the [Cosmiq Discord](https://discord.cosmiq.gg/) for giveaways and competitions.
+
+Visit the [Cosmiq website](https://cosmiq.gg/) for more information.
diff --git a/plugins/cosmiq_cosmetic_creator/cosmiq_cosmetic_creator.js b/plugins/cosmiq_cosmetic_creator/cosmiq_cosmetic_creator.js
new file mode 100644
index 00000000..34276c3d
--- /dev/null
+++ b/plugins/cosmiq_cosmetic_creator/cosmiq_cosmetic_creator.js
@@ -0,0 +1,14651 @@
+(function (root) {
+ 'use strict';
+
+ const PLUGIN_ID = 'cosmiq_cosmetic_creator';
+ const FORMAT_ID = 'cosmiq_cosmetic';
+ const PLUGIN_VERSION = '0.17.0';
+ const PLUGIN_AUTHOR = 'Cosmiq Team';
+ const PLUGIN_CREATION_DATE = '2026-08-01';
+ const PROJECT_WEBSITE_URL = 'https://cosmiq.gg/';
+ const CREATOR_GUIDES_URL = 'https://guides.cosmiq.gg/';
+ const COMMUNITY_DISCORD_URL = 'https://discord.cosmiq.gg/';
+ const YOUTUBE_GUIDES_URL = 'https://www.youtube.com/@CosmiqGG';
+ const PROJECT_REPOSITORY_URL = 'https://github.com/CosmiqGG/cosmiq-cosmetic-creator';
+ const FORMAT_BANNER_FILE = 'temp_title.png';
+ const BASE_TEMPLATE_FILE = 'CosmiqPlayerTemplate.bbmodel';
+ const BASE_TEMPLATE_SHA256 = '769bad284e181e699278b91aada6a74bd63a2a61e0c201c5772a5206644e1505';
+ const BASE_TEMPLATE_GZIP_BASE64 = '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';
+ const IMPORTED_GROUP_NAME = 'IMPORTED';
+ const LEGACY_IMPORT_EXTENSIONS = Object.freeze(['cosmiq', 'bbmodel']);
+ const PLUGIN_ABOUT = [
+ '
',
+ '
',
+ ' Website',
+ ' ',
+ '
',
+ ' Discord',
+ ' ',
+ '
',
+ ' Creator Guides',
+ ' ',
+ '
',
+ ' YouTube Guides',
+ ' ',
+ '
',
+ ''
+ ].join('');
+
+ function formatPageComponent() {
+ return {
+ data: function () {
+ return {
+ bannerSource: FORMAT_BANNER_DATA_URL,
+ fallbackBannerSource: FORMAT_BANNER_DATA_URL
+ };
+ },
+ methods: {
+ create: function () {
+ return creatorFormat.new();
+ },
+ useFallbackBanner: function (event) {
+ if (this.bannerSource !== this.fallbackBannerSource) {
+ this.bannerSource = this.fallbackBannerSource;
+ return;
+ }
+ if (event && event.target) {
+ event.target.style.display = 'none';
+ }
+ }
+ },
+ template: [
+ '',
+ '
Create Cosmiq accessories and emotes directly in Blockbench.
',
+ '
Target : Minecraft: Java Edition
',
+ '
',
+ ' ',
+ '
',
+ ' ',
+ ' Bring your Minecraft character ideas to life with Cosmiq Creator.
',
+ ' ',
+ ' ',
+ '
',
+ '
',
+ ' ',
+ '
',
+ '
'
+ ].join('')
+ };
+ }
+ const PACKAGE_SCHEMA = 'cosmiq.package.v3';
+ const SOURCE_SCHEMA = PACKAGE_SCHEMA;
+ const CAPE_SOURCE_SCHEMA = PACKAGE_SCHEMA;
+ const EMOTE_SOURCE_SCHEMA = PACKAGE_SCHEMA;
+ const EMOTE_LOGIC_SCHEMA = 'cosmiq.emote.logic.v0';
+ const COORDINATE_PROFILE = 'cosmiq.blockbench.v3';
+ const COSMIQ_STANDARD_MAGIC = 'COSMIQCP';
+ const COSMETIC_FORMAT_VERSION = 3;
+ const CAPE_FORMAT_VERSION = 3;
+ const PACKAGE_EXTENSION = 'bbmodel';
+ const CATEGORIES = Object.freeze({
+ ACCESSORY: 'accessory',
+ EMOTE: 'emote',
+ CAPE: 'cape'
+ });
+ const ANIMATION_MODES = Object.freeze({
+ ANIMATED: 'animated',
+ STATIC: 'static'
+ });
+ const EMOTE_LOGIC_LIMITS = Object.freeze({
+ sourceBytes: 16 * 1024,
+ sourceLines: 256,
+ graphBytes: 64 * 1024
+ });
+ const IDENTIFIER_PATTERN = /^[a-z0-9._-]+:[a-z0-9._/-]+$/;
+ const PROJECT_ID_PATTERN = /^[a-z0-9][a-z0-9._-]{0,63}:[a-z0-9][a-z0-9._/-]{0,126}$/;
+ const PATH_FIELD_NAMES = Object.freeze([
+ 'path',
+ 'relative_path',
+ 'source_path',
+ 'export_path',
+ 'save_path',
+ 'file'
+ ]);
+
+ const PACKAGE_LIMITS = Object.freeze({
+ packageBytes: 500_000,
+ manifestBytes: 64 * 1024,
+ bbmodelBytes: 500_000,
+ projectIdCharacters: 128,
+ bodySlots: 8,
+ jsonDepth: 64,
+ jsonValues: 100000,
+ editorNodes: 512,
+ animations: 32,
+ keyframes: 4096,
+ textures: 16,
+ meshFaceVertices: 32,
+ pngBytes: 4 * 1024 * 1024
+ });
+ const AUTHORING_LIMITS = Object.freeze({
+ resources: 64,
+ cues: 256,
+ effectLocators: 64,
+ markersPerAnimation: 256,
+ displayNameCharacters: 64,
+ markerNameCharacters: 96,
+ commentCharacters: 512
+ });
+
+ const RESOURCE_TYPES = Object.freeze({
+ PARTICLE_EFFECT: 'particle_effect',
+ SOUND: 'sound'
+ });
+ const SOUND_REFERENCE_TYPES = Object.freeze(['sound_effect', 'music']);
+ const CUE_TYPES = Object.freeze({
+ PARTICLE: 'particle',
+ SOUND: 'sound',
+ NODE_VISIBILITY: 'node_visibility',
+ COMMENT: 'comment'
+ });
+ const BODY_SLOTS = Object.freeze([
+ 'cosmiq:slot/head',
+ 'cosmiq:slot/body',
+ 'cosmiq:slot/arms',
+ 'cosmiq:slot/legs'
+ ]);
+
+ function inlineSvgData(svg) {
+ return 'data:image/svg+xml;charset=UTF-8,' + encodeURIComponent(svg);
+ }
+
+ // BEGIN GENERATED CREATOR CARD IMAGES
+ const FORMAT_BANNER_DATA_URL = 'data:image/png;base64,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';
+ const CREATOR_CARD_IMAGES = Object.freeze({
+ CREATE_ACCESSORY: 'data:image/webp;base64,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',
+ CREATE_EMOTE: 'data:image/webp;base64,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ja9PmTxptExptExptExWuBFf54+YD/qpQv5Um1G636xz4HdHCyI2g6BPochQCHN0sRrrDPya/5+BW9YXxf+g/D+Ue247UNmhZTaEM3h02TMhb8y70YYw6s1NKxzCL6Chp57OxtGPmTxptExptExptExO9vHIb+b01OwJFDi1n84Xoux/QB9TL4HCr3ZKmCEf26XWE+Zxuz4yw9ZacD2wTyChTELpSqI06Ry57MyYjOKdLryJBZnl33/L+9IKuRFCFnQ9H9UcZa9PmTxptExptExptEsPiEfUi3yDhPK09N4HsuAQEVFPJR6u4Ppzi75rZKf8XPOPaYmVpXiB8ds4madra7xO8INyCIXe4YOLWkfVqmmDayFNnCXT0s4tj5fnHyhWf5zh5a9PmTxptExptExpY7aWwHNCcKN08gCr89EQ8F21HqPC/EkVG/bvnpERKYgJiQdIOECIGNehlhTeRlNrq0C/yl43i+1KVTvr1bxNYYwhfokY8VF6zWH+/ic4eWvT5k8abRMabRMabRMYBjMly8nPVoW5nyp8kQN2zIDyzJyfQ0r5V1p+nHSh7zbxTN/55f5v61C/RhVwyZvQIDhAbLBpyy402iY02iY02iY02iY02iY0EjMBXlghthRSpD2C4asQiNl+Tr1J+y9kQcq6ByecMV9PlXmo11Ht6laBVyX6u3i1mQeJMabRMabRMabRMabRMabRMZx26HQ/SGMbE87vgs4D6TsnWDRY8sMLIDgbf6zImBWvaM30swS4QBmDpzM1idtox8yeNNomNNomNNomNNomNNomJPe63CdAnt0We/Cy8NQBdo+6u1QiWwRpUd7+wsD+FrNk8TJYLZAQAy5uY+ZPGm0TGm0TGm0TGm0TGm0TGm0LQLYnUF32bZisxmTeZlw/E5L6i/XtRARz2gJvtVIdwfS8LPZ19dM8M/JmxzsyeNNomNNomNNomNNomNNomNNomMRZMnCh8snObIBHnaop0A0LHENkb1THWLg4eMt35uXyZfvWwfiIlYdvP/VA05DvtExptExptExptExptExptExptExXV4vFESUTNZuiLQhSOxrb6OvpIXf7KuQUr9usgoDiVqsHEHIoAQPBf4LZ20Y+ZPGm0TGm0TGm0TGm0TGm0S2rVA/fHgsfh7H7SLizRR2iwe1InO8Mlrn8nVQuuQ0KkWOdCLijYkMi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',
+ WIDE_CHARACTER: 'data:image/webp;base64,UklGRu4/AABXRUJQVlA4WAoAAAAAAAAAvwMABwIAVlA4IHo/AACQHAGdASrAAwgCAAAAJZ278Ljnl6zDb85/wf5gd5HI/fX8D/mf77/gv7v/xf9b861a/vH32/Z3/W/dx/b5ZOvPM68x/Pf8D/bf8F/mf73/0f939wP8//uv9H+zPza/NP/K9wb+k/0b/B/579d/7T/zvu7/wP2Z95X7of7X2C/03+if4L/J/fb9wX+Y/2H+J90H9e/sX+F/tv+V+QL+j/zz5//uu/1vsl/1f/l+wP/E/6P95v7//Vp/1f8n+5/0w/17/V/8v/Qfuz9Ef89/q3+t/L79//sA9AD0AP3/9z/1v+/d8n9a/xf5d/3/sjvUftV8YNsfs2vt/w391/dT8zPu//T/7Xxv+a2od65/zn5ffmHy1O0+YX7YfWv9h/fv3S/xf7afSv8z/yf8V6p/Zb/afcV9gH8w/rH+f/t/7s/H3/X8SX7l/z/YJ/j39f/1/+N/IP5S/+P/R/5z9uPe19ef+r3Gf59/Yv9j/hv3X/zn//8WXoZ/sQmnzJ402iY02iY02iY02iY02iY02iY02iY02iY02iY02iY02iYpfT79T5eBk01qapngg2Pgzs6pN7TtD6EP7IyeNNomNNomNNomNNomNNomNNomNMeBKtYyByLeJ+hVxXEwFslEG9ExjNYf8Y8tenzJ402iY02iY02iY02iY02iYz8+z01wt8PrZXZm5rQkSAUe9w228I6NhaC/tFsG7mTxptExptExptExptExptExptExpiUh3HpAOYA6q1+wo/HtYX4+6JCH+C983rkXic1FtTtBk8abRMabRMabRMabRMabRMabRLnYrkId76ldClD2lsqgav2HbxiY13VPB/PEsGTxptExptExptExptExptExptExnkDuDIXb9JRhhK7m29OpRSLY51Ry/Fl9YQqvQeVyYJoZTg8tenzJ402iY02iY02iY02iY02iYujf5VkEj7CKO/FpbZIhSDoi7xHh4zl9o2VKxaLPVUc4eWvT5k8abRMabRMabRMabRMabRL5ye9sfmO+r4utGKnjcpQxOqxxzOIT87ud9NiY5W/4UhfE7ypEmNNomNNomNNomNNomNNomNNofbU1FRNCpy6y8zqXw4qE3unB6edGpYtGDwRlFkueOqvTfczis3DiKbhBEyowWI5a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',
+ SLIM_CHARACTER: 'data:image/webp;base64,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5M7ZOxptExptExptExptExptExptExptExpHwHhmwvq5h89diznbU48EaNPaHPsLn5G3t1YPG/Pd+R0Xa9PmTxptExptExptExptExptExps+G8gd7d+1cjvRzEpLquIyN36aY2iNGqHLMMK74yTIY9gmJAKWiY02iY02iY02iY02iY02iY02iYj8uI4Lh6nuvBxCUoAO8GFPert6aK7juS5QOu/+GGeDFRjnDy16fMnjTaJjTaJjTaJjTaJjTZ+EIP2D5l1oEDGEYxJ/XaRwqKJSC4BDO/igmGhNCe69cAUhBtGPmTxptExptExptExptExptExmpTJGcDNWnpZjLd2GHNgvpyBuF+xdfyztA7462I7FEKOGntFa47QZPGm0TGm0TGm0TGm0TGm0TGm0ADMuRO7o7k9cDgfRvjtH1mdmJ1DpCM507/uwSJ+fRjgAh+nzJ402iY02iY02iY02iY02iY02jUvroAcJSa0GieKTZh4qHbUBDefj9402iY02iY02iY02iY02iY02iY02iY0uX9cdlIdtVPcKwfUdomNNomNNomNNomNNomNNomNNomNNomNNomNNomNNomNNomMsAD+//3Io/T7F5bCnAejBOTH+EAAAAADa//ItNFkZ/NtEy+Ak7XLWOibdSQPR6PeDB13H26yKEWjVulNMXxVJ3dCQx/vmXitIo2az7ucRG3smzP175yvjZT+lMQES9ALI6aLBF/kAgjw9hKcWvfhUNwe/1kWQScnizPJ9mRgwh4mMcD/V0eF5TRfE7XcxNaCiUWFWBz0eKX0mBWYM+CsomXb7gdQsRioPQlSaB1LZKqQSLEjvg/y48LbgN+uTiQWOK70bj11lvJTTSZZuQxS4w39YkwEj5cjNb7X3yaUH+CruPxOija3AmT4r5ptNnCmALMh22n9sx8wlLUvADbNUYBA+EE4io2lQfJHPXu9uHrMwmA29j4xfA3EkgG+yqdJEiLFIy7twJtxbAORtSAVD+ta4dxCzpofESO7VYlwMbgxVFoM6lup/R5ypCUJP5GSD+jSvvhX455L7XcpJ6KH4iP1gJMkYfjWRGRk/Yn1FRsJQ4fSiPcZKKDtrrZ9jcl/tFUXhKAW+z2MqnhCvGBKNFkKZHrHYirgxA+nEAbwDDqpLc+xBdKzPFjsyWhSrpFs7BH+l7C7EL1o1svk+lrlVZjK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',
+ SIMPLE_EMOTE: 'data:image/webp;base64,UklGRopTAABXRUJQVlA4WAoAAAAAAAAAvwMABwIAVlA4IBZTAADQFQGdASrAAwgCAAAAJZ27hd1wovtP+x/jz+83j/yH3C+u/ip/bf9h/nPmkpn9N/Vb+f/2v/Pf4bdva8807yH8u/rf9v/x393/vP/j+rv+j/yv4pfLT9Uf6r3Av0i/pf9u/yf9u/un/z+ab/h/7D3Y/ux6gf5j/Nf77/dPvo/5343f4H/Cf3f3a/27/T/5n3Af5h/QPu2/e77x/+l7GP+Q/2n/n9wP+Vf2L5//t2/03+9/yn7kfv//7f05/Yr/ff4//Yf8T9///h+iP8n/oP3kfv9/1vwA9AD/g+xn/AP3K/P/6H/Rv6//fvyE+AXzr9J/uH5C/uT5m/kf6l+NP96/23+j+9O132i6mvxz7B/ZP73+x397/6n+Y/AP87/p/zA/pPp78rP7/8uf6r8gv43/GP7F+OH+L/6H+Z9bLuEtw8wX1u+bf3r/D/sb/iP/D/o/pr+n/2/5R+6PzeflV9AH8h/oX97/I3/Mf+L7D/5nhsfjf+P9M32B/yn+m/4j/Cfth/f//Z9uP9j/w/9L/q//D/kvcT+i/5D/i/579rv9d/9PwG/j384/u/9t/zX+Y/uf/1/133r+wT9w/+z7kX6f/eH+/6eoMnjTaJjTaJjTaJjTaJjTaJjTaJjTaJjTaJjTaJjTaJjTaJjTaJjTaJjTaJjTaJjTaJjTaJjTaJjTaJjTaIws5cE9qeVfjbhfmCIc1dwUFVCyxgMH17xgHjTaJjTaJjTaJjTaJjTaJjTaJjTaIaAazw6yIyMUfE8zC6Y/IMygjwPZJZ0OWbtExptExptExptExptExptExptExlqVbfIjZhoWJjNWRaVpvSWnqeOXr8dkv5HtenzJ402iY02iY02iY02iY02iY02FMGF9SXzglp03afItSPh8yGDC9Y+4v5bazExptExptExptExptExptExptExpfngc69qLwTBzQvHMYey7QqCa1dYqGm0TGm0TGm0TGm0TGm0TGm0TGm0QWaTJsBQbMvlFAtgjVuP4RH2rOVo5QOEw/ZSkt5qLSs2iY02iY02iY02iY02iY02iY02iYoj94ZxJA22F6dt+JQ5IYG0Y+ZPGm0TGm0TGm0TGm0TGm0TGeaGlErIcj1gtKSWIy1snDqd7u5VI3EcUlLlLEqHtox8yeNNomNNomNNomNNomNNomM5yGOu6mKsYvsv/ljeLDIkreuZpIFQLhgcwWXg6qMLFjlzisN5saSKnRDFOUUd069pvPp8yeNNomNNomNNomNNomNNomNNiCxSs9KAPaAZvgRu+whw7kTCD/5s6OFtXLAU5UwHAj+iByhPG/PZY7luljx7eZBuqv4ExptExptExptExptExptExps3hZ3uEKTsagyPdI0PskPb2TE+eV23GWaKOw07MiusoIIaNx9rSQYlI+HZMD1OtQfZwRBeAy16fMnjTaJjTaJjTaJjTaJjSqHMH+ljFqyidrpb5yT4038azSE+iJKqheN2w/k7SCGB4OSQPDHRABD5/FFXdF1Tc4+pp3mTxptExptExptExptExptEq2baGL/5M8+vbwIBjtwnkDHlbNbnERaHSehKlQuOevV+Tv7/FSv2KYb7u/W3xrC6gTFr5sVd9e171lAiY02iY02iY02iY02iY02iYn3v0D2uHZHfk3OBhqEGSPGaefzI3JlUqE7xOpfn8mMBfEZvTPNL0xy09lkEQueiS4Tkt/SESY02iY02iY02iY02iY02dkZjXm0qfstRMusXXPw3q5wQvLmVY6v8Ux2iplyV/DwL3jpCnzTNsfB6qwhDpgxCHp07vQuQ3ER2se5tfQo2LTtox8yeNNomNNomNNomNNc4tmg9gkXH/K6vLm0XasGeoOZkTnEMW8VZAha7wyc63+ib+9K5WY/mjUZR+l2d8G7URkrSrzeEqjl4Ab7xafUekR5REkeRoS6x2JjTaJjTaJjTaJjTaJjTaJeZlT0KDOo3lX6K0Vydzos3zCYvAz6bKDcM0sWYUYzBH3LDFY9oUWZ9Z3HGdxFbXokkRCPbrCphdnv0+ZPGm0TGm0TGm0TGm0TGm0TEH79exPDKvBw/Qo48JzkMubiq2WR+GANN9y7dpyvSIbXVAs+3PRd6FYNs44BUtExptExptExptExptExptExpZJNXWPZ74vT2Dm+PX8jJo/oXPgYSh4Rf9SR4tT0qbv7u6DMbzmlYm8eH+OPIdqgj/kHMnjTaJjTaJjTaJjTaJjTaJjSp7KO332s2zZVB8f690whdiiqTY783DFd3ESDT5eVflTjObVjqHWfkOgXpCCzCGgUqK8Dpzh5a9PmTxptExptExptExptEbXCDNOdfaaUL21ekiUpGs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',
+ PRECISE_EMOTE: 'data:image/webp;base64,UklGRpxMAABXRUJQVlA4WAoAAAAAAAAAvwMABwIAVlA4IChMAACQFAGdASrAAwgCAAAAJZ27hd2EbEx/cPx472SV/WP7/+sn+B/1/+g+ZWmP0r9d/6z/bP8z/lPlx/r/yA6I+rvM/8e/M/7X/af1x/uv/e/1f3H/y3+j/Ir5X/oT/E+4D/H/5l/YP7r/fP71/Z//R8x//G/y3ul/c3/QewH+b/0D/D/3j76P+Z+OP+G/5H+J9z/9f/vf+b/tf+o/P/8Af5Z/Ofu9/ej/0flp/tfY7/y3+Y9gL+R/1f/T/mB++v25/7D/rf6n96vo9/ZP/jf53/Yf9H99PwL/kv9O/0n7Qf8/9+P+x+AH7/+wB/yf/n7k/8A/en3F+u3+f7U/8R/hf2e/uX+97U/y362/tl/heV31P5sfyb7Q/fv7f+yP7gf5L71fz/+q/KP0L+XH+B+W/wF/lH8q/sf5Df4H/j/4fklt1/dL8gPgL9ffoX+D/v/7Cf4T9tvo5+t/4P5h+7v2h/035SfQF/KP6b/f/yd/eP6+/6viS+lftB8Af8m/rX+Z/yf7b/6X/8fR5/pf7b9qP7Z///ht9O/87/Q/uR/kP/t+BH8l/nn+E/vP+K/0f95/9n+7++H2ZfuL7HP61f8ZNPmTxptExptExptExptExptExptExptExptExptExptExptExljYKdfdtGPmTxptExptExptExptExptExptExlq4XwhpJ/mdaR229SFXHPbBlEOch7hFQ42jBKh/tm7nx/HaDJ402iY02iY02iY02iY02iY02iJszsVqNuOFxDya6D+sNSYzLsutS1w4GdBn0uK6AmZ6JOjRYc4eWvT5k8abRMabRMabRMabRMabRpww++KQJriBu+ZFy47srP5ag1L62+hRKQ/Qg0KfuUSCR4hNQb+zzb/taMMODJ402iY02iY02iY02iY02iY02cuqMvnGeCKXOFpzHVfYKh5NP+/r1tMjmyzYKsrYANqamuDy16fMnjTaJjTaJjTaJjTaJjTaJjQ2l9Ie1kAOL8Yt8eS+7yWmuZUapohqTk5RxwAqJOvm/UKMj20swBtGPmTxptExptExptExptExptExpsR0CQ9jPEp4oy2bCFdSpG856qJBrkqAkSQJAZSAy3N39057RMabRMabRMabRMabRMabRMabRMZkp5hSZdaqsLuDsPXpDd+ShmysMBUjV2CRMV402iY02iY02iY02iY02iY02iY016vt4GLvSAtRVYW+XuZRZ88EeL3X/lPxTm1G5MDbyYQyV/XiDovpj8SI68ZcGEU/hCwqgRzh5a9PmTxptExptExptExptEU8VIVYsLhKjJHtxB3InIsCmTtt5kwUbsUhW1ptGqZMRFzn25vnDYfz//oi5UHmR+F8dJKvg/YIMvBm6iXopA3fJJdFbq+8nNrHsiJPGm0TGm0TGm0TGm0TGm0TGlvQg52q6zMzeC2gf5cYB4rqP8J9UtJmfysD7p7E3GYy43Zz7B6VWmAKskeRNc3X97DU+zxadpYkl0WFH8vgJPdmem81Z+cPLXp8yeNNomNNomNNomNNhFfg9Lz2DHglSj/13UAw9Itb+nV6RbnwIgDyve4wouVukOsu9sWWPHg4W+qBpmh6J70X30TT5k8abRMabRMabRMabRMabRBwrwLFa/bRJOGIFtqYj9XWyMjQZUJNxPb2fMMaEPERXp2oIeWsOcInNSeWvT5k8abRMabRMabRMabRMZb6vxjM7Wq2SfklGxxvA4mPr08YPDm47S8Tv4lNxeresMD1f+HpH4kGp92zusVQbNm/73w4QZv57YG7JomNNomNNomNNomNNomNNomNMw3G/PNHyYy5szrbtWbSd+Xn7F5p9WuN4vK9EEu6T48BgusKsDNyx7FhEmNNomNNomNNomNNomNNomNNoCeiBURuloD2dzx0xLPjXsWlVJKI0ODT+a2UX3QYnLYPVPBOlEZBW9brknxqmibpGc2iY02iY02iY02iY02iY02iY02dv2yHwwMT8/bZ0aEFgGH8OOuTuK2MO3KQL3leo6ob2W0ChlAGVtyy6+gjtmiX4uSxfnDy16fMnjTaJjTaJjTaJjTaJjQSkk62Rbr6M9LLIZseeH0qNvx43VHRjCIew6I+QyXioEGTxptExptExptExptExptExptExmy4Ap60iszHYM8BF3CYi+Gd8ZOHJG6bZ5yLrgyeNNomNNomNNomNNomNNomNNomLBU2jtBupr0MjsHLVrd195DxirXUie6bhbM24EK3jaIZkBvFKCJExptExptExptExptExptExptExptADh/dpkT8hws59//YUczkBWMh7VT2ij793ULsyV1QY9a/tSESY02iY02iY02iY02iY02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'
+ });
+ // END GENERATED CREATOR CARD IMAGES
+
+
+ const CREATOR_CHOICES = Object.freeze([
+ {
+ id: 'cosmetic',
+ name: 'Accessories',
+ description: 'Create a wearable model. Its attachment point and animation mode are detected automatically.',
+ image: CREATOR_CARD_IMAGES.CREATE_ACCESSORY
+ },
+ {
+ id: 'emote',
+ name: 'Emotes',
+ description: 'Create a player animation workspace with a Wide or Slim visible body.',
+ image: CREATOR_CARD_IMAGES.CREATE_EMOTE
+ }
+ ]);
+
+ const BODY_TYPE_CHOICES = Object.freeze([
+ {
+ id: 'wide',
+ name: 'Wide',
+ description: 'Start with the Wide player body visible. Slim arm attachment folders are still included.',
+ image: CREATOR_CARD_IMAGES.WIDE_CHARACTER
+ },
+ {
+ id: 'slim',
+ name: 'Slim',
+ description: 'Start with the Slim player body visible. Wide arm attachment folders are still included.',
+ image: CREATOR_CARD_IMAGES.SLIM_CHARACTER
+ }
+ ]);
+
+ const EMOTE_RIG_CHOICES = Object.freeze([
+ {
+ id: 'simple',
+ name: 'Simple',
+ description: 'A seven-bone rigid player rig for straightforward Minecraft-style emotes.',
+ image: CREATOR_CARD_IMAGES.SIMPLE_EMOTE
+ },
+ {
+ id: 'precise',
+ name: 'Precise',
+ description: 'A weighted skinned mesh with bendable waist, elbows, and knees.',
+ image: CREATOR_CARD_IMAGES.PRECISE_EMOTE
+ }
+ ]);
+
+ const CAPE_RESOLUTIONS = Object.freeze([
+ Object.freeze({id: '64x32', label: '64 x 32', width: 64, height: 32}),
+ Object.freeze({id: '128x64', label: '128 x 64', width: 128, height: 64}),
+ Object.freeze({id: '256x128', label: '256 x 128', width: 256, height: 128}),
+ Object.freeze({id: '512x256', label: '512 x 256', width: 512, height: 256}),
+ Object.freeze({id: '1024x512', label: '1024 x 512', width: 1024, height: 512})
+ ]);
+
+ const COSMETIC_MODE_CHOICES = Object.freeze([
+ {
+ id: 'cosmetic_animated',
+ name: 'Animated',
+ description: 'Recommended. Adds empty player-state clips ready for keyframes.',
+ image: inlineSvgData([
+ ''
+ ].join(''))
+ },
+ {
+ id: 'cosmetic_static',
+ name: 'Static',
+ description: 'Creates the same Wide and Slim model folders without animation clips.',
+ image: inlineSvgData([
+ ''
+ ].join(''))
+ }
+ ]);
+
+ const YES_NO_CHOICES = Object.freeze([
+ {
+ id: 'no',
+ name: 'No',
+ description: 'Keep this option disabled.',
+ image: inlineSvgData('')
+ },
+ {
+ id: 'yes',
+ name: 'Yes',
+ description: 'Enable this option for the emote draft.',
+ image: inlineSvgData('')
+ }
+ ]);
+
+ const CREATION_FLOW_STEPS = Object.freeze([
+ {id: 'type', title: 'Type'},
+ {id: 'name', title: 'Name'},
+ {id: 'setup', title: 'Setup'},
+ {id: 'creator', title: 'Creator'}
+ ]);
+ const COSMETIC_FLOW_STEPS = Object.freeze([
+ {id: 'type', title: 'Accessory'},
+ {id: 'name', title: 'Name'},
+ {id: 'visible_body', title: 'Visible Body'},
+ {id: 'creator', title: 'Creator'}
+ ]);
+ const EMOTE_FLOW_STEPS = Object.freeze([
+ {id: 'type', title: 'Emote'},
+ {id: 'name', title: 'Name'},
+ {id: 'body_type', title: 'Body Type'},
+ {id: 'review', title: 'Review'},
+ {id: 'creator', title: 'Start Creating'}
+ ]);
+ const CAPE_FLOW_STEPS = Object.freeze([
+ {id: 'type', title: 'Cape'},
+ {id: 'name', title: 'Name'},
+ {id: 'resolution', title: 'Resolution'},
+ {id: 'creator', title: 'Creator'}
+ ]);
+
+ const MODEL_VARIANTS = Object.freeze(['universal', 'wide', 'slim']);
+ const ARM_WIDTH_MODES = Object.freeze({
+ UNCONFIGURED: 'unconfigured',
+ SHARED: 'shared',
+ DEPENDENT: 'dependent'
+ });
+ const ARM_WIDTH_MODE_VALUES = Object.freeze([
+ ARM_WIDTH_MODES.UNCONFIGURED,
+ ARM_WIDTH_MODES.SHARED,
+ ARM_WIDTH_MODES.DEPENDENT
+ ]);
+ const TEMPLATE_VALUES = Object.freeze(['cosmetic_static', 'cosmetic_animated']);
+ const EMOTE_TEMPLATE_VALUES = Object.freeze(['emote_player', 'emote_with_props']);
+ const PROJECT_KINDS = Object.freeze({
+ COSMETIC: 'cosmetic',
+ EMOTE: 'emote',
+ CAPE: 'cape'
+ });
+ const EMOTE_ACTOR_MIN = 1;
+ const EMOTE_ACTOR_MAX = 4;
+ const EMOTE_ACTOR_OFFSETS = Object.freeze([0, 24, -24, 48]);
+ const EMOTE_ANIMATION_SCOPES = Object.freeze({
+ NONE: 'none',
+ SCENE: 'scene',
+ ACTOR: 'actor'
+ });
+ const EMOTE_PLAYER_BONES = Object.freeze([
+ {id: 'root', label: 'Root', parent: null},
+ {id: 'body', label: 'Body', parent: 'root'},
+ {id: 'head', label: 'Head', parent: 'body'},
+ {id: 'left_arm', label: 'Left Arm', parent: 'body'},
+ {id: 'right_arm', label: 'Right Arm', parent: 'body'},
+ {id: 'left_leg', label: 'Left Leg', parent: 'root'},
+ {id: 'right_leg', label: 'Right Leg', parent: 'root'}
+ ]);
+ const EMOTE_PRECISE_PLAYER_BONES = Object.freeze([
+ {id: 'root', label: 'Root', parent: null},
+ {id: 'torso_lower', label: 'Lower Torso', parent: 'root'},
+ {id: 'torso_upper', label: 'Upper Torso', parent: 'torso_lower'},
+ {id: 'head', label: 'Head', parent: 'torso_upper'},
+ {id: 'left_arm_upper', label: 'Left Upper Arm', parent: 'torso_upper'},
+ {id: 'left_arm_lower', label: 'Left Lower Arm', parent: 'left_arm_upper'},
+ {id: 'right_arm_upper', label: 'Right Upper Arm', parent: 'torso_upper'},
+ {id: 'right_arm_lower', label: 'Right Lower Arm', parent: 'right_arm_upper'},
+ {id: 'left_leg_upper', label: 'Left Upper Leg', parent: 'torso_lower'},
+ {id: 'left_leg_lower', label: 'Left Lower Leg', parent: 'left_leg_upper'},
+ {id: 'right_leg_upper', label: 'Right Upper Leg', parent: 'torso_lower'},
+ {id: 'right_leg_lower', label: 'Right Lower Leg', parent: 'right_leg_upper'}
+ ]);
+ const EMOTE_RIG_PROFILES = Object.freeze({
+ SIMPLE: 'simple',
+ PRECISE: 'precise'
+ });
+
+ const LIMITS = Object.freeze({
+ nodes: 512,
+ animations: 32,
+ keyframes: 4096,
+ vertices: 8192,
+ triangles: 16384,
+ textures: 8,
+ textureDimension: 4096,
+ animationSeconds: 120,
+ emotePivotOffsetUnits: 128,
+ futureParticleMarkers: 16
+ });
+ const DEFERRED_SIZE_LIMIT_CODES = Object.freeze({
+ COSMIQ_RESOURCE_LIMIT: true,
+ COSMIQ_EFFECT_LOCATOR_LIMIT: true,
+ COSMIQ_TIMELINE_MARKER_LIMIT: true,
+ COSMIQ_TIMELINE_CUE_LIMIT: true,
+ COSMIQ_NODE_LIMIT: true,
+ COSMIQ_VERTEX_LIMIT: true,
+ COSMIQ_TRIANGLE_LIMIT: true,
+ COSMIQ_ANIMATION_LIMIT: true,
+ COSMIQ_KEYFRAME_LIMIT: true,
+ COSMIQ_PARTICLE_MARKER_LIMIT: true,
+ COSMIQ_TEXTURE_LIMIT: true,
+ COSMIQ_TEXTURE_DIMENSION: true,
+ COSMIQ_EMOTE_NODE_LIMIT: true,
+ COSMIQ_EMOTE_ANIMATION_LIMIT: true,
+ COSMIQ_EMOTE_KEYFRAME_LIMIT: true,
+ COSMIQ_PACKAGE_PNG_BYTES: true,
+ COSMIQ_PACKAGE_PNG_DIMENSIONS: true,
+ COSMIQ_PACKAGE_TEXTURE_COUNT: true,
+ COSMIQ_PACKAGE_NODE_COUNT: true,
+ COSMIQ_PACKAGE_VERTEX_LIMIT: true,
+ COSMIQ_PACKAGE_TRIANGLE_LIMIT: true,
+ COSMIQ_PACKAGE_ANIMATION_COUNT: true,
+ COSMIQ_PACKAGE_KEYFRAME_COUNT: true,
+ COSMIQ_PACKAGE_BBMODEL_LIMIT: true,
+ COSMIQ_PACKAGE_SIZE_LIMIT: true
+ });
+
+ const ROLES = Object.freeze({
+ NONE: 'none',
+ EXPORT_ROOT: 'export_root',
+ MODEL_VARIANT: 'model_variant_root',
+ STRUCTURE: 'authoring_structure',
+ ANCHOR: 'player_anchor',
+ AUTHORING: 'cosmetic_node',
+ REFERENCE_ROOT: 'reference_root',
+ REFERENCE: 'reference',
+ PARTICLE_MARKER: 'particle_emitter_marker',
+ PARTICLE_EFFECT_LOCATOR: 'particle_effect_locator',
+ SOUND_EFFECT_LOCATOR: 'sound_effect_locator',
+ EMOTE_SCENE: 'emote_scene',
+ EMOTE_ACTOR: 'emote_actor',
+ PLAYER_BONE: 'player_bone',
+ EMOTE_TRANSFORM: 'emote_transform',
+ PROPS_ROOT: 'props_root',
+ OBJECT_ACTOR: 'object_actor',
+ OBJECT_ACTOR_NODE: 'object_actor_node'
+ });
+ const BLOCKBENCH_NODE_ROLE_VALUES = Object.freeze([
+ ROLES.NONE,
+ ROLES.EXPORT_ROOT,
+ ROLES.MODEL_VARIANT,
+ ROLES.STRUCTURE,
+ ROLES.ANCHOR,
+ ROLES.AUTHORING,
+ ROLES.REFERENCE_ROOT,
+ ROLES.REFERENCE,
+ ROLES.PARTICLE_MARKER,
+ ROLES.PARTICLE_EFFECT_LOCATOR,
+ ROLES.SOUND_EFFECT_LOCATOR,
+ ROLES.EMOTE_SCENE,
+ ROLES.EMOTE_ACTOR,
+ ROLES.PLAYER_BONE,
+ ROLES.EMOTE_TRANSFORM,
+ ROLES.PROPS_ROOT,
+ ROLES.OBJECT_ACTOR,
+ ROLES.OBJECT_ACTOR_NODE
+ ]);
+ const BLOCKBENCH_PLAYER_BONE_VALUES = Object.freeze(Array.from(new Set(
+ ['none']
+ .concat(EMOTE_PLAYER_BONES.map(function (bone) { return bone.id; }))
+ .concat(EMOTE_PRECISE_PLAYER_BONES.map(function (bone) { return bone.id; }))
+ .concat(['upper_body'])
+ )));
+
+ const ANCHORS = Object.freeze([
+ {id: 'cosmiq:player/head', label: 'Head', origin: [0, 24, 0], slot: 'cosmiq:slot/head'},
+ {id: 'cosmiq:player/torso', label: 'Torso', origin: [0, 24, 0], slot: 'cosmiq:slot/body'},
+ {id: 'cosmiq:player/left_arm', label: 'Left Arm', origin: [5, 22, 0], slot: 'cosmiq:slot/arms'},
+ {id: 'cosmiq:player/right_arm', label: 'Right Arm', origin: [-5, 22, 0], slot: 'cosmiq:slot/arms'},
+ {id: 'cosmiq:player/left_leg', label: 'Left Leg', origin: [2, 12, 0], slot: 'cosmiq:slot/legs'},
+ {id: 'cosmiq:player/right_leg', label: 'Right Leg', origin: [-2, 12, 0], slot: 'cosmiq:slot/legs'}
+ ]);
+ const TEMPLATE_ATTACHMENT_POINTS = Object.freeze([
+ Object.freeze({
+ key: 'Bd', modelGroup: '[Model Bd]', bodyPartGroup: 'Body',
+ label: 'Body', anchorId: 'cosmiq:player/torso', modelVariant: 'universal'
+ }),
+ Object.freeze({
+ key: 'Hd', modelGroup: '[Model Hd]', bodyPartGroup: 'Head',
+ label: 'Head', anchorId: 'cosmiq:player/head', modelVariant: 'universal'
+ }),
+ Object.freeze({
+ key: 'WR', modelGroup: '[Model WR]', bodyPartGroup: 'Right Arm Wide',
+ label: 'Right Arm', anchorId: 'cosmiq:player/right_arm', modelVariant: 'wide'
+ }),
+ Object.freeze({
+ key: 'WL', modelGroup: '[Model WL]', bodyPartGroup: 'Left Arm Wide',
+ label: 'Left Arm', anchorId: 'cosmiq:player/left_arm', modelVariant: 'wide'
+ }),
+ Object.freeze({
+ key: 'SR', modelGroup: '[Model SR]', bodyPartGroup: 'Right Arm Slim',
+ label: 'Right Arm', anchorId: 'cosmiq:player/right_arm', modelVariant: 'slim'
+ }),
+ Object.freeze({
+ key: 'SL', modelGroup: '[Model SL]', bodyPartGroup: 'Left Arm Slim',
+ label: 'Left Arm', anchorId: 'cosmiq:player/left_arm', modelVariant: 'slim'
+ }),
+ Object.freeze({
+ key: 'RL', modelGroup: '[Model RL]', bodyPartGroup: 'Right Leg',
+ label: 'Right Leg', anchorId: 'cosmiq:player/right_leg', modelVariant: 'universal'
+ }),
+ Object.freeze({
+ key: 'LL', modelGroup: '[Model LL]', bodyPartGroup: 'Left Leg',
+ label: 'Left Leg', anchorId: 'cosmiq:player/left_leg', modelVariant: 'universal'
+ })
+ ]);
+ const BODY_PART_FIELDS = Object.freeze([
+ {formKey: 'part_head', anchorId: 'cosmiq:player/head'},
+ {formKey: 'part_body', anchorId: 'cosmiq:player/torso'},
+ {formKey: 'part_left_arm', anchorId: 'cosmiq:player/left_arm'},
+ {formKey: 'part_right_arm', anchorId: 'cosmiq:player/right_arm'},
+ {formKey: 'part_left_leg', anchorId: 'cosmiq:player/left_leg'},
+ {formKey: 'part_right_leg', anchorId: 'cosmiq:player/right_leg'}
+ ]);
+
+ const ALL_BODY_PART_ANCHOR_IDS = Object.freeze(BODY_PART_FIELDS.map(function (part) {
+ return part.anchorId;
+ }));
+
+ const TRIGGERS = Object.freeze([
+ {
+ id: 'idle',
+ kind: 'player_state',
+ family: 'movement',
+ label: 'Idle / Standing',
+ aliases: ['idle', 'stand', 'standing'],
+ crossVersion: true,
+ defaultLoop: true
+ },
+ {
+ id: 'walking',
+ kind: 'player_state',
+ family: 'movement',
+ label: 'Walking',
+ aliases: ['walk', 'walking'],
+ crossVersion: true,
+ defaultLoop: true
+ },
+ {
+ id: 'sprinting',
+ kind: 'player_state',
+ family: 'movement',
+ label: 'Running / Sprinting',
+ aliases: ['run', 'running', 'sprint', 'sprinting'],
+ crossVersion: true,
+ defaultLoop: true
+ },
+ {
+ id: 'crouching',
+ kind: 'player_state',
+ family: 'pose',
+ label: 'Crouching / Sneaking',
+ aliases: ['crouch', 'crouching', 'sneak', 'sneaking'],
+ crossVersion: true,
+ defaultLoop: true
+ },
+ {
+ id: 'crouch_walking',
+ kind: 'player_state',
+ family: 'combined',
+ label: 'Crouch Walking',
+ aliases: ['crouch_walk', 'crouch_walking', 'sneak_walk', 'sneak_walking'],
+ crossVersion: true,
+ defaultLoop: true
+ },
+ {
+ id: 'jumping',
+ kind: 'player_state',
+ family: 'contact',
+ label: 'Jumping / Rising',
+ aliases: ['jump', 'jumping', 'rising'],
+ crossVersion: true,
+ defaultLoop: false
+ },
+ {
+ id: 'falling',
+ kind: 'player_state',
+ family: 'contact',
+ label: 'Falling',
+ aliases: ['fall', 'falling', 'airborne'],
+ crossVersion: true,
+ defaultLoop: true
+ },
+ {
+ id: 'riding',
+ kind: 'player_state',
+ family: 'pose',
+ label: 'Sitting / Riding',
+ aliases: ['sit', 'sitting', 'ride', 'riding', 'mounted'],
+ crossVersion: true,
+ defaultLoop: true
+ },
+ {
+ id: 'sleeping',
+ kind: 'player_state',
+ family: 'pose',
+ label: 'Laying / Sleeping',
+ aliases: ['lay', 'laying', 'sleep', 'sleeping'],
+ crossVersion: true,
+ defaultLoop: true
+ },
+ {
+ id: 'in_water',
+ kind: 'player_state',
+ family: 'environment',
+ label: 'In Water / Treading Water',
+ aliases: ['water', 'in_water', 'treading_water'],
+ crossVersion: true,
+ defaultLoop: true
+ },
+ {
+ id: 'swimming',
+ kind: 'player_state',
+ family: 'pose',
+ label: 'Swimming',
+ aliases: ['swim', 'swimming'],
+ crossVersion: false,
+ defaultLoop: true
+ },
+ {
+ id: 'crawling',
+ kind: 'player_state',
+ family: 'pose',
+ label: 'Crawling',
+ aliases: ['crawl', 'crawling'],
+ crossVersion: false,
+ defaultLoop: true
+ },
+ {
+ id: 'fall_flying',
+ kind: 'player_state',
+ family: 'pose',
+ label: 'Elytra Flying',
+ aliases: ['fall_flying', 'elytra', 'elytra_gliding', 'glide', 'gliding'],
+ crossVersion: false,
+ defaultLoop: true
+ },
+ {
+ id: 'climbing',
+ kind: 'player_state',
+ family: 'movement',
+ label: 'Climbing',
+ aliases: ['climb', 'climbing', 'ladder'],
+ crossVersion: true,
+ defaultLoop: true
+ },
+ {
+ id: 'creative_flying',
+ kind: 'player_state',
+ family: 'pose',
+ label: 'Creative / Spectator Flying',
+ aliases: ['fly', 'flying', 'creative_flying', 'spectator_flying'],
+ crossVersion: true,
+ defaultLoop: true
+ },
+ {
+ id: 'using_item',
+ kind: 'player_state',
+ family: 'activity',
+ label: 'Using Item / Eating / Drinking',
+ aliases: ['use_item', 'using_item', 'eating', 'drinking', 'charging_bow'],
+ crossVersion: true,
+ defaultLoop: true
+ },
+ {
+ id: 'blocking',
+ kind: 'player_state',
+ family: 'activity',
+ label: 'Blocking',
+ aliases: ['block', 'blocking', 'shield'],
+ crossVersion: true,
+ defaultLoop: true
+ },
+ {
+ id: 'fishing',
+ kind: 'player_state',
+ family: 'activity',
+ label: 'Fishing',
+ aliases: ['fish', 'fishing'],
+ crossVersion: true,
+ defaultLoop: true
+ },
+ {
+ id: 'rowing',
+ kind: 'player_state',
+ family: 'activity',
+ label: 'Rowing A Boat',
+ aliases: ['row', 'rowing', 'boat_rowing'],
+ crossVersion: true,
+ defaultLoop: true
+ },
+ {
+ id: 'spin_attack',
+ kind: 'player_state',
+ family: 'activity',
+ label: 'Riptide Spin Attack',
+ aliases: ['spin', 'spin_attack', 'riptide'],
+ crossVersion: false,
+ defaultLoop: true
+ },
+ {
+ id: 'dead',
+ kind: 'player_state',
+ family: 'pose',
+ label: 'Dead',
+ aliases: ['dead', 'death_pose'],
+ crossVersion: true,
+ defaultLoop: false
+ },
+ {
+ id: 'land',
+ kind: 'player_event',
+ family: 'event',
+ label: 'Landing (one-shot event)',
+ aliases: ['land', 'landing'],
+ crossVersion: true,
+ defaultLoop: false
+ },
+ {
+ id: 'attack',
+ kind: 'player_event',
+ family: 'event',
+ label: 'Hit / Attack (one-shot event)',
+ aliases: ['attack', 'attacking', 'swing'],
+ crossVersion: true,
+ defaultLoop: false
+ },
+ {
+ id: 'hurt',
+ kind: 'player_event',
+ family: 'event',
+ label: 'Getting Hurt (one-shot event)',
+ aliases: ['hurt', 'damage', 'damaged'],
+ crossVersion: true,
+ defaultLoop: false
+ }
+ ]);
+
+ const anchorById = Object.create(null);
+ const triggerById = Object.create(null);
+ const triggerByAlias = Object.create(null);
+ ANCHORS.forEach(function (anchor) {
+ anchorById[anchor.id] = anchor;
+ });
+ TRIGGERS.forEach(function (trigger) {
+ triggerById[trigger.id] = trigger;
+ trigger.aliases.forEach(function (alias) {
+ triggerByAlias[alias] = trigger;
+ });
+ });
+
+ function normalizeToken(value) {
+ return String(value == null ? '' : value)
+ .trim()
+ .toLowerCase()
+ .replace(/[\s./-]+/g, '_')
+ .replace(/[^a-z0-9_]/g, '')
+ .replace(/^_+|_+$/g, '');
+ }
+
+ function normalizeProjectId(value) {
+ const raw = String(value == null ? '' : value).trim().toLowerCase();
+ const separator = raw.indexOf(':');
+ if (separator < 1) {
+ return '';
+ }
+ const namespace = raw.slice(0, separator)
+ .replace(/[^a-z0-9._-]+/g, '_')
+ .replace(/^[._-]+|[._-]+$/g, '');
+ const path = raw.slice(separator + 1)
+ .replace(/\\/g, '/')
+ .replace(/[^a-z0-9._/-]+/g, '_')
+ .replace(/\/+/g, '/')
+ .replace(/^[/_.-]+|[/_.-]+$/g, '');
+ if (!namespace || !path) {
+ return '';
+ }
+ const normalized = namespace + ':' + path;
+ return PROJECT_ID_PATTERN.test(normalized) ? normalized : '';
+ }
+
+ function boundedCreatorString(value, maximum) {
+ const text = String(value == null ? '' : value).trim();
+ return text.slice(0, Math.max(0, Number(maximum) || 0));
+ }
+
+ function isRawMediaReference(value) {
+ const raw = String(value == null ? '' : value).trim();
+ return /^(?:https?:|file:|data:|blob:)/i.test(raw) ||
+ /^[a-z]:[\\/]/i.test(raw) ||
+ /^[/\\]{1,2}/.test(raw) ||
+ /[\u0000-\u001f\u007f-\u009f]/.test(raw);
+ }
+
+ function normalizeAssetRef(value) {
+ const raw = String(value == null ? '' : value).trim();
+ if (!raw || isRawMediaReference(raw)) {
+ return '';
+ }
+ return normalizeProjectId(raw);
+ }
+
+ function isParticleResource(resource) {
+ return !!resource && resource.type === RESOURCE_TYPES.PARTICLE_EFFECT;
+ }
+
+ function normalizeResourceReference(value) {
+ const source = value && typeof value === 'object' ? value : {};
+ const type = String(source.type || '').trim().toLowerCase();
+ const authored = type === RESOURCE_TYPES.PARTICLE_EFFECT;
+ const assetRef = normalizeAssetRef(source.assetRef);
+ const displayName = boundedCreatorString(
+ source.displayName || (type === RESOURCE_TYPES.SOUND ? 'Sound Reference' : ''),
+ AUTHORING_LIMITS.displayNameCharacters
+ );
+ const normalized = {
+ id: normalizeAssetRef(source.id),
+ type: type,
+ displayName: displayName,
+ assetRef: assetRef,
+ soundKind: type === RESOURCE_TYPES.SOUND && SOUND_REFERENCE_TYPES.indexOf(source.soundKind) !== -1
+ ? source.soundKind
+ : (type === RESOURCE_TYPES.SOUND ? 'sound_effect' : ''),
+ embeddedOgg: type === RESOURCE_TYPES.SOUND && typeof source.embeddedOgg === 'string'
+ ? source.embeddedOgg
+ : null,
+ referenceOnly: false,
+ rawMediaIncluded: type === RESOURCE_TYPES.SOUND && typeof source.embeddedOgg === 'string'
+ };
+ if (authored) {
+ normalized.profileId = normalizeToken(source.profileId);
+ normalized.editorStatus = source.editorStatus === 'placeholder'
+ ? 'placeholder'
+ : 'configured';
+ normalized.appearance = source.appearance && typeof source.appearance === 'object'
+ ? cosmiqClone(source.appearance)
+ : null;
+ normalized.emitter = source.emitter && typeof source.emitter === 'object'
+ ? cosmiqClone(source.emitter)
+ : null;
+ normalized.runtimeExport = source.runtimeExport !== false;
+ }
+ return normalized;
+ }
+
+ function normalizeTimelineCue(value) {
+ const source = value && typeof value === 'object' ? value : {};
+ const type = String(source.type || '').trim().toLowerCase();
+ const isComment = type === CUE_TYPES.COMMENT;
+ const isVisibility = type === CUE_TYPES.NODE_VISIBILITY;
+ return {
+ id: normalizeAssetRef(source.id),
+ type: type,
+ animationUuid: boundedCreatorString(source.animationUuid, 128),
+ markerName: boundedCreatorString(source.markerName, AUTHORING_LIMITS.markerNameCharacters),
+ endMarkerName: boundedCreatorString(source.endMarkerName, AUTHORING_LIMITS.markerNameCharacters) || null,
+ resourceId: isComment || isVisibility ? null : (normalizeAssetRef(source.resourceId) || null),
+ locatorUuid: isComment || isVisibility ? null : (boundedCreatorString(source.locatorUuid, 128) || null),
+ nodeUuid: isVisibility ? (boundedCreatorString(source.nodeUuid, 128) || null) : null,
+ visible: isVisibility ? source.visible === true : null,
+ comment: isComment
+ ? boundedCreatorString(source.comment, AUTHORING_LIMITS.commentCharacters)
+ : null,
+ sourceOnly: isComment,
+ runtimeExport: !isComment && source.runtimeExport !== false
+ };
+ }
+
+ function normalizeCosmeticSettings(value) {
+ if (!Array.isArray(value) || value.length === 0) {
+ return [];
+ }
+ if (value.length !== 1) {
+ return [];
+ }
+ const setting = value[0] && typeof value[0] === 'object' ? value[0] : {};
+ const side = String(setting.data && setting.data.side || '').trim().toUpperCase();
+ if (setting.type !== 'SIDE' || setting.id != null || setting.enabled !== true ||
+ (side !== 'LEFT' && side !== 'RIGHT')) {
+ return [];
+ }
+ return [{
+ type: 'SIDE',
+ id: null,
+ enabled: true,
+ data: {side: side}
+ }];
+ }
+
+ function authoringStateFromMetadata(metadata) {
+ const source = metadata && typeof metadata === 'object' ? metadata : {};
+ return {
+ resources: Array.isArray(source.resourceReferences)
+ ? source.resourceReferences.map(normalizeResourceReference)
+ : [],
+ cues: Array.isArray(source.timelineCues)
+ ? source.timelineCues.map(normalizeTimelineCue)
+ : []
+ };
+ }
+
+ function listValues(value) {
+ if (Array.isArray(value)) {
+ return value.slice();
+ }
+ return String(value == null ? '' : value).split(',');
+ }
+
+ function uniqueValues(values) {
+ const seen = Object.create(null);
+ return values.filter(function (value) {
+ if (!value || seen[value]) {
+ return false;
+ }
+ seen[value] = true;
+ return true;
+ });
+ }
+
+ function wizardProgressMarkup(steps, stepIndex) {
+ const items = (steps || []).map(function (step, index) {
+ let state = ' locked';
+ let icon = 'lock';
+ if (index < stepIndex) {
+ state = ' complete';
+ icon = 'check';
+ } else if (index === stepIndex) {
+ state = ' current';
+ icon = '' + (index + 1) + '';
+ }
+ return '' + icon +
+ '' + step.title + '
';
+ }).join('');
+ return '' + items + '
';
+ }
+
+ function wizardPageHeading(steps, stepIndex, title, description) {
+ return '' + wizardProgressMarkup(steps, stepIndex) +
+ '
' + title + '
' + description +
+ '
';
+ }
+
+ function cardChoiceComponent(choices, title, description, options) {
+ const settings = options || {};
+ return {
+ data: function () {
+ return {
+ selected: settings.defaultSelected || null,
+ choices: choices.map(function (choice) { return Object.assign({}, choice); }),
+ heading: title,
+ description: description,
+ progressMarkup: settings.progressMarkup || ''
+ };
+ },
+ methods: {
+ select: function (id) {
+ this.selected = id;
+ }
+ },
+ template: [
+ '',
+ '
',
+ ' ',
+ '
',
+ '
',
+ '
check_circle',
+ '
{{ choice.badge }}{{ choice.name }}{{ choice.description }}',
+ '
',
+ '
',
+ '
touch_appSelect a card, then choose Continue.
',
+ '
'
+ ].join('')
+ };
+ }
+
+ function creationTypeComponent() {
+ return cardChoiceComponent(
+ CREATOR_CHOICES,
+ 'What Do You Want To Create?',
+ 'Choose a creator workspace to continue.',
+ {progressMarkup: wizardProgressMarkup(CREATION_FLOW_STEPS, 0)}
+ );
+ }
+
+ function bodyTypeComponent(selected) {
+ return cardChoiceComponent(
+ BODY_TYPE_CHOICES,
+ 'Choose The Visible Body',
+ 'Choose the player body you want visible while creating. Both Wide and Slim arm attachment folders are included.',
+ {
+ defaultSelected: selected || 'wide',
+ progressMarkup: wizardProgressMarkup(COSMETIC_FLOW_STEPS, 2)
+ }
+ );
+ }
+
+ function emoteRigTypeComponent(selected) {
+ return cardChoiceComponent(
+ EMOTE_RIG_CHOICES,
+ 'Choose The Body Type',
+ 'Simple uses seven rigid bones. Precise uses a weighted twelve-bone rig that bends at the waist, elbows, and knees.',
+ {
+ defaultSelected: selected || EMOTE_RIG_PROFILES.SIMPLE,
+ progressMarkup: wizardProgressMarkup(EMOTE_FLOW_STEPS, 2)
+ }
+ );
+ }
+
+ function emoteCreationReviewMarkup(value) {
+ const draft = mergeEmoteDraft(value);
+ const precise = draft.rig_profile === EMOTE_RIG_PROFILES.PRECISE;
+ const bodyType = precise ? 'Precise' : 'Simple';
+ const bodyDescription = precise
+ ? 'Weighted twelve-bone rig with bendable waist, elbows, and knees.'
+ : 'Six rigid body parts controlled by seven animator-friendly bones.';
+ return wizardPageHeading(
+ EMOTE_FLOW_STEPS,
+ 3,
+ 'Review Your Emote',
+ 'Confirm these choices, then choose Start Creating.'
+ ) +
+ '' +
+ '
Name' +
+ escapeHtml(creationProjectName(draft.creation_name, PROJECT_KINDS.EMOTE)) +
+ '
' +
+ '
Body Type' + bodyType + '' +
+ '' + bodyDescription + '
' +
+ '
';
+ }
+
+ function capeResolutionFromId(value) {
+ return CAPE_RESOLUTIONS.find(function (resolution) {
+ return resolution.id === String(value || '');
+ }) || CAPE_RESOLUTIONS[0];
+ }
+
+ function emoteBooleanComponent(title, description, selected, stepIndex) {
+ return cardChoiceComponent(
+ YES_NO_CHOICES,
+ title,
+ description,
+ {
+ defaultSelected: selected === true ? 'yes' : 'no',
+ progressMarkup: wizardProgressMarkup(EMOTE_FLOW_STEPS, stepIndex)
+ }
+ );
+ }
+
+ function normalizeEmoteDraftName(value) {
+ return String(value == null ? '' : value).trim().replace(/\s+/g, ' ');
+ }
+
+ function normalizeCreationName(value) {
+ return String(value == null ? '' : value).trim().replace(/\s+/g, ' ');
+ }
+
+ function creationCategoryLabel(kind) {
+ if (kind === PROJECT_KINDS.EMOTE) return 'Emote';
+ if (kind === PROJECT_KINDS.CAPE) return 'Cape';
+ return 'Accessory';
+ }
+
+ function creationProjectName(name, kind) {
+ return (normalizeCreationName(name) || 'New') + ' ' + creationCategoryLabel(kind);
+ }
+
+ function creationProjectId(name, kind) {
+ const token = normalizeToken(name) || normalizeToken(creationCategoryLabel(kind));
+ return 'creator:' + token;
+ }
+
+ function defaultSimpleCosmeticDraft() {
+ return {
+ creation_name: '',
+ project_id: 'creator:accessory',
+ template: 'cosmetic_animated',
+ initial_visible_variant: 'wide',
+ texture_width: 64,
+ texture_height: 64,
+ include_reference: true,
+ wide_skin: null,
+ slim_skin: null
+ };
+ }
+
+ function mergeSimpleCosmeticDraft(draft, values) {
+ return Object.assign(defaultSimpleCosmeticDraft(), draft || {}, values || {});
+ }
+
+ function defaultCapeTemplateDraft() {
+ return mergeSimpleCosmeticDraft(null, {
+ project_id: 'creator:cape',
+ template: 'cosmetic_animated',
+ resolution: '64x32',
+ texture_width: 64,
+ texture_height: 32,
+ anchor_ids: ['cosmiq:player/torso'],
+ cape_template: true
+ });
+ }
+
+ function defaultEmoteDraft() {
+ return {
+ creation_name: '',
+ actors: 1,
+ containsProps: false,
+ traversable: false,
+ rig_profile: EMOTE_RIG_PROFILES.SIMPLE
+ };
+ }
+
+ function mergeEmoteDraft(draft, values) {
+ return Object.assign(defaultEmoteDraft(), draft || {}, values || {});
+ }
+
+ function baseTemplateGroup(rawTemplate, name) {
+ const matches = (rawTemplate.groups || []).filter(function (group) {
+ return group && group.name === name;
+ });
+ if (matches.length !== 1) {
+ throw new Error('Base template must contain exactly one "' + name + '" group.');
+ }
+ return matches[0];
+ }
+
+ function verifyBaseTemplateStructure(rawTemplate) {
+ if (!rawTemplate || typeof rawTemplate !== 'object' || Array.isArray(rawTemplate)) {
+ throw new Error('The Cosmiq base template is not a Blockbench project object.');
+ }
+ [
+ 'Cosmiq Reference Model', 'Upper Body', 'Body', 'Head', 'Arms', 'Wide', 'Slim',
+ 'Right Arm Wide', 'Left Arm Wide', 'Right Arm Slim', 'Left Arm Slim',
+ 'Lower Body', 'Right Leg', 'Left Leg', 'Cape/Elytra', 'Cape', 'Elytra'
+ ].concat(TEMPLATE_ATTACHMENT_POINTS.map(function (point) {
+ return point.modelGroup;
+ })).forEach(function (name) {
+ baseTemplateGroup(rawTemplate, name);
+ });
+ ['wide.png', 'slim.png', 'cape.png'].forEach(function (name) {
+ if (!(rawTemplate.textures || []).some(function (texture) {
+ return texture && texture.name === name && /^data:image\/png;base64,/.test(texture.source || '');
+ })) {
+ throw new Error('Base template is missing its embedded ' + name + ' texture.');
+ }
+ });
+ if (!(rawTemplate.animations || []).some(function (animation) {
+ return animation && animation.name === 'state.idle';
+ })) {
+ throw new Error('Base template is missing its state.idle animation lane.');
+ }
+ return rawTemplate;
+ }
+
+ async function baseTemplateDigest(text) {
+ if (typeof require === 'function') {
+ try {
+ return require('node:crypto').createHash('sha256').update(text, 'utf8').digest('hex');
+ } catch (error) {
+ // Browser builds do not expose Node's crypto module.
+ }
+ }
+ if (typeof crypto !== 'undefined' && crypto.subtle && typeof TextEncoder !== 'undefined') {
+ const digest = await crypto.subtle.digest('SHA-256', new TextEncoder().encode(text));
+ return Array.from(new Uint8Array(digest)).map(function (value) {
+ return value.toString(16).padStart(2, '0');
+ }).join('');
+ }
+ throw new Error('This Blockbench build cannot verify the bundled base template.');
+ }
+
+ async function decodeEmbeddedBaseTemplate(value) {
+ if (typeof require === 'function') {
+ try {
+ const compressed = require('node:buffer').Buffer.from(value, 'base64');
+ return require('node:zlib').gunzipSync(compressed).toString('utf8');
+ } catch (error) {
+ // Browser builds use the standards-based decompression path below.
+ }
+ }
+ if (typeof atob !== 'function' || typeof DecompressionStream === 'undefined' ||
+ typeof Response === 'undefined') {
+ throw new Error('This Blockbench build cannot unpack the bundled base template.');
+ }
+ const binary = atob(value);
+ const compressed = new Uint8Array(binary.length);
+ for (let index = 0; index < binary.length; index++) compressed[index] = binary.charCodeAt(index);
+ const stream = new Blob([compressed]).stream().pipeThrough(new DecompressionStream('gzip'));
+ return new Response(stream).text();
+ }
+
+ async function loadBaseTemplateJson() {
+ const embeddedJson = await decodeEmbeddedBaseTemplate(BASE_TEMPLATE_GZIP_BASE64);
+ if (await baseTemplateDigest(embeddedJson) !== BASE_TEMPLATE_SHA256) {
+ throw new Error('The bundled Cosmiq base template failed its integrity check.');
+ }
+ return verifyBaseTemplateStructure(JSON.parse(embeddedJson));
+ }
+
+ function importedUuid(seed, used) {
+ let first = 2166136261;
+ let second = 2166136261;
+ const text = String(seed || 'imported');
+ for (let index = 0; index < text.length; index++) {
+ first = Math.imul(first ^ text.charCodeAt(index), 16777619) >>> 0;
+ second = Math.imul(second ^ text.charCodeAt(text.length - index - 1), 16777619) >>> 0;
+ }
+ let attempt = 0;
+ let value;
+ do {
+ const a = (first + attempt * 2654435761) >>> 0;
+ const b = (second + attempt * 2246822519) >>> 0;
+ value = a.toString(16).padStart(8, '0') + '-' +
+ ((b >>> 16) & 0xffff).toString(16).padStart(4, '0') + '-4' +
+ (b & 0x0fff).toString(16).padStart(3, '0') + '-8' +
+ ((a >>> 20) & 0x0fff).toString(16).padStart(3, '0') + '-' +
+ (b.toString(16).padStart(8, '0') + a.toString(16).padStart(8, '0')).slice(0, 12);
+ attempt++;
+ } while (used && used[value]);
+ if (used) used[value] = true;
+ return value;
+ }
+
+ function importedBytesBase64(bytes) {
+ if (typeof Buffer !== 'undefined') return Buffer.from(bytes).toString('base64');
+ let binary = '';
+ for (let offset = 0; offset < bytes.length; offset += 0x8000) {
+ binary += String.fromCharCode.apply(null, bytes.subarray(offset, offset + 0x8000));
+ }
+ return btoa(binary);
+ }
+
+ function parseLegacyV1Container(bytes) {
+ if (bytes.length < 17 || bytes[8] !== 1) {
+ throw new Error('This is not a supported V1 Cosmiq package.');
+ }
+ const view = new DataView(bytes.buffer, bytes.byteOffset, bytes.byteLength);
+ const manifestLength = view.getUint32(9, false);
+ const modelLength = view.getUint32(13, false);
+ if (!manifestLength || !modelLength || manifestLength > 64 * 1024 ||
+ modelLength > PACKAGE_LIMITS.bbmodelBytes ||
+ 17 + manifestLength + modelLength !== bytes.length) {
+ throw new Error('The V1 Cosmiq package header or payload length is invalid.');
+ }
+ let manifest;
+ let project;
+ try {
+ manifest = JSON.parse(decodeUtf8(bytes.subarray(17, 17 + manifestLength)));
+ project = JSON.parse(decodeUtf8(bytes.subarray(17 + manifestLength)));
+ } catch (error) {
+ throw new Error('The V1 Cosmiq package does not contain valid UTF-8 JSON.');
+ }
+ if (!cosmiqPlainObject(manifest) || !cosmiqPlainObject(project)) {
+ throw new Error('The V1 Cosmiq package must contain manifest and Blockbench objects.');
+ }
+ cosmiqValidateJsonBounds(manifest);
+ cosmiqValidateJsonBounds(project);
+ return {manifest: manifest, project: project};
+ }
+
+ function importedProjectKind(project, manifest) {
+ const metadata = project && cosmiqPlainObject(project.cosmiq) ? project.cosmiq : {};
+ const values = [
+ project && project.cosmiq_project_kind,
+ metadata.kind,
+ metadata.category,
+ metadata.assetKind,
+ manifest && manifest.kind,
+ manifest && manifest.category,
+ manifest && manifest.assetKind
+ ].map(function (value) { return String(value || '').toLowerCase(); });
+ if (values.some(function (value) { return value === 'emote'; })) return 'emote';
+ if (values.some(function (value) { return value === 'cape'; })) return 'cape';
+ return 'accessory';
+ }
+
+ function importedAnchor(project, manifest) {
+ const metadata = project && cosmiqPlainObject(project.cosmiq) ? project.cosmiq : {};
+ const cosmetic = cosmiqPlainObject(metadata.cosmetic) ? metadata.cosmetic : {};
+ const direct = [
+ cosmetic.attachmentAnchorId,
+ manifest && manifest.attachmentAnchorId,
+ manifest && Array.isArray(manifest.nodes) && manifest.nodes
+ .filter(function (node) { return node && node.parentId === null; })
+ .map(function (node) {
+ return node.attachment && node.attachment.kind === 'player_anchor'
+ ? node.attachment.anchor : null;
+ }).find(function (value) { return !!value; })
+ ].map(canonicalAnchorId).find(function (value) { return !!value; });
+ if (direct) return direct;
+ const bindings = cosmiqPlainObject(metadata.nodeBindings) ? metadata.nodeBindings : {};
+ const bound = Object.keys(bindings).map(function (uuid) {
+ return bindings[uuid] && canonicalAnchorId(bindings[uuid].anchorId);
+ }).find(function (value) { return !!value; });
+ if (bound) return bound;
+ const slots = []
+ .concat(cosmetic.bodySlots || [])
+ .concat(manifest && manifest.bodySlots || [])
+ .concat(project && project.cosmiq_body_slots || []);
+ const slotMap = {
+ 'cosmiq:slot/head': 'cosmiq:player/head',
+ 'cosmiq:slot/body': 'cosmiq:player/torso',
+ 'cosmiq:slot/arms': 'cosmiq:player/right_arm',
+ 'cosmiq:slot/legs': 'cosmiq:player/right_leg'
+ };
+ return slots.map(function (slot) { return slotMap[String(slot)]; })
+ .find(function (value) { return !!value; }) || 'cosmiq:player/torso';
+ }
+
+ function importedVariant(project, manifest) {
+ const metadata = project && cosmiqPlainObject(project.cosmiq) ? project.cosmiq : {};
+ const cosmetic = cosmiqPlainObject(metadata.cosmetic) ? metadata.cosmetic : {};
+ const candidate = cosmetic.modelVariant ||
+ project && project.cosmiq_model_variant ||
+ manifest && manifest.previewCamera && manifest.previewCamera.pose &&
+ manifest.previewCamera.pose.modelVariant ||
+ manifest && manifest.modelVariant;
+ return candidate === 'slim' ? 'slim' : candidate === 'wide' ? 'wide' : 'universal';
+ }
+
+ function quaternionToImportedEuler(value) {
+ const q = Array.isArray(value) && value.length === 4 ? value.map(Number) : [0, 0, 0, 1];
+ if (q.some(function (number) { return !Number.isFinite(number); })) return [0, 0, 0];
+ const x = q[0];
+ const y = q[1];
+ const z = q[2];
+ const w = q[3];
+ const pitch = Math.asin(Math.max(-1, Math.min(1, 2 * (w * y - z * x))));
+ const roll = Math.atan2(2 * (w * x + y * z), 1 - 2 * (x * x + y * y));
+ const yaw = Math.atan2(2 * (w * z + x * y), 1 - 2 * (y * y + z * z));
+ return [roll, -pitch, -yaw].map(function (radians) {
+ const degrees = radians * 180 / Math.PI;
+ return Math.abs(degrees) < 1e-8 ? 0 : degrees;
+ });
+ }
+
+ function importedRuntimeVector(value) {
+ return [-Number(value[0]) * 16, Number(value[1]) * 16, Number(value[2]) * 16];
+ }
+
+ function importedAnchorOrigin(anchorId) {
+ const origins = {
+ 'cosmiq:player/head': [0, 24, 0],
+ 'cosmiq:player/torso': [0, 12, 0],
+ 'cosmiq:player/right_arm': [5, 22, 0],
+ 'cosmiq:player/left_arm': [-5, 22, 0],
+ 'cosmiq:player/right_leg': [1.9, 12, 0],
+ 'cosmiq:player/left_leg': [-1.9, 12, 0]
+ };
+ return (origins[anchorId] || origins['cosmiq:player/torso']).slice();
+ }
+
+ function importedV2Textures(decoded, used) {
+ const manifest = decoded.manifest;
+ const textureIndexById = Object.create(null);
+ const textures = (manifest.textures || []).map(function (texture, index) {
+ const section = decoded.sections[texture.section];
+ if (!section || section.type !== 'texture_png') {
+ throw new Error('A V2 package texture section is missing.');
+ }
+ const uuid = importedUuid('v2-texture:' + texture.id, used);
+ textureIndexById[texture.id] = index;
+ return {
+ name: texture.id + '.png',
+ uuid: uuid,
+ id: texture.id,
+ internal: true,
+ saved: false,
+ visible: true,
+ width: texture.width,
+ height: texture.height,
+ uv_width: texture.width,
+ uv_height: texture.height,
+ source: 'data:image/png;base64,' + importedBytesBase64(section.bytes)
+ };
+ });
+ const textureByMaterial = Object.create(null);
+ (manifest.materials || []).forEach(function (material) {
+ textureByMaterial[material.id] = material.baseColorTextureId == null
+ ? null
+ : textureIndexById[material.baseColorTextureId];
+ });
+ return {textures: textures, textureByMaterial: textureByMaterial};
+ }
+
+ function importedV2Nodes(manifest) {
+ const geometryById = Object.create(null);
+ (manifest.geometry || []).forEach(function (geometry) { geometryById[geometry.id] = geometry; });
+ const nodeById = Object.create(null);
+ (manifest.nodes || []).forEach(function (node) { nodeById[node.id] = node; });
+ const preferredVariant = importedVariant({}, manifest);
+ const retainedNodes = (manifest.nodes || []).filter(function (node) {
+ if (preferredVariant === 'universal') return node.modelVariant === 'universal';
+ return node.modelVariant === 'universal' || node.modelVariant === preferredVariant;
+ });
+ const retainedIds = Object.create(null);
+ retainedNodes.forEach(function (node) { retainedIds[node.id] = true; });
+ let changed = true;
+ while (changed) {
+ changed = false;
+ retainedNodes.slice().forEach(function (node) {
+ if (node.parentId && !retainedIds[node.parentId] && nodeById[node.parentId]) {
+ retainedNodes.push(nodeById[node.parentId]);
+ retainedIds[node.parentId] = true;
+ changed = true;
+ }
+ });
+ }
+ const point = TEMPLATE_ATTACHMENT_POINTS.find(function (entry) {
+ return entry.anchorId === importedAnchor({}, manifest) &&
+ (entry.modelVariant === 'universal' || entry.modelVariant === preferredVariant);
+ }) || TEMPLATE_ATTACHMENT_POINTS[0];
+ return {
+ geometryById: geometryById,
+ preferredVariant: preferredVariant,
+ retainedNodes: retainedNodes,
+ point: point
+ };
+ }
+
+ function importedV2Groups(nodes, used) {
+ const nodeUuidById = Object.create(null);
+ const originById = Object.create(null);
+ const groups = [];
+ nodes.retainedNodes.forEach(function (node) {
+ const uuid = importedUuid('v2-node:' + node.id, used);
+ nodeUuidById[node.id] = uuid;
+ const parentOrigin = node.parentId && originById[node.parentId]
+ ? originById[node.parentId]
+ : importedAnchorOrigin(nodes.point.anchorId);
+ const delta = importedRuntimeVector(node.bind.translation);
+ const origin = parentOrigin.map(function (coordinate, axis) { return coordinate + delta[axis]; });
+ originById[node.id] = origin;
+ groups.push({
+ name: node.id.replace(/_/g, ' '),
+ uuid: uuid,
+ origin: origin,
+ rotation: quaternionToImportedEuler(node.bind.rotation),
+ export: true,
+ visibility: node.defaultVisible !== false,
+ cosmiq_role: ROLES.NONE,
+ cosmiq_actor_index: 0,
+ cosmiq_player_bone: 'none',
+ cosmiq_anchor: 'none',
+ cosmiq_model_branch: 'none',
+ children: []
+ });
+ });
+ return {groups: groups, nodeUuidById: nodeUuidById, originById: originById};
+ }
+
+ function importedV2Cuboid(geometryId, geometry, elementUuid, origin, textures, textureByMaterial) {
+ const directions = {north: 'north', south: 'south', east: 'west', west: 'east', up: 'up', down: 'down'};
+ const localA = importedRuntimeVector(geometry.min);
+ const localB = importedRuntimeVector(geometry.max);
+ const faces = {};
+ Object.keys(directions).forEach(function (runtimeFace) {
+ const face = geometry.faces[runtimeFace];
+ const targetFace = directions[runtimeFace];
+ faces[targetFace] = face ? {
+ uv: [
+ face.uv[0] * (textures[textureByMaterial[face.materialId]] || {width: 16}).width,
+ face.uv[1] * (textures[textureByMaterial[face.materialId]] || {height: 16}).height,
+ face.uv[2] * (textures[textureByMaterial[face.materialId]] || {width: 16}).width,
+ face.uv[3] * (textures[textureByMaterial[face.materialId]] || {height: 16}).height
+ ],
+ texture: textureByMaterial[face.materialId],
+ rotation: face.rotation || 0
+ } : {uv: [0, 0, 0, 0], texture: null};
+ });
+ return {
+ name: geometryId.replace(/_/g, ' '),
+ type: 'cube',
+ uuid: elementUuid,
+ from: origin.map(function (coordinate, axis) {
+ return coordinate + Math.min(localA[axis], localB[axis]);
+ }),
+ to: origin.map(function (coordinate, axis) {
+ return coordinate + Math.max(localA[axis], localB[axis]);
+ }),
+ origin: origin.slice(),
+ rotation: [0, 0, 0],
+ inflate: Number(geometry.inflate || 0) * 16,
+ visibility: true,
+ export: true,
+ faces: faces
+ };
+ }
+
+ function importedV2View(record, components, unsigned, view) {
+ const output = [];
+ for (let item = 0; item < record.count; item++) {
+ const values = [];
+ for (let component = 0; component < components; component++) {
+ const offset = record.byteOffset + (item * components + component) * 4;
+ values.push(unsigned ? view.getUint32(offset, true) : view.getFloat32(offset, true));
+ }
+ output.push(components === 1 ? values[0] : values);
+ }
+ return output;
+ }
+
+ function importedV2Mesh(decoded, geometryId, geometry, elementUuid, origin, textures, textureByMaterial) {
+ const section = decoded.sections[geometry.positions.section];
+ const view = new DataView(section.bytes.buffer, section.bytes.byteOffset, section.bytes.byteLength);
+ const positions = importedV2View(geometry.positions, 3, false, view);
+ const uvs = importedV2View(geometry.uvs, 2, false, view);
+ const indices = importedV2View(geometry.indices, 1, true, view);
+ const vertices = {};
+ const vertexIds = positions.map(function (position, index) {
+ const vertexId = 'v' + index;
+ vertices[vertexId] = importedRuntimeVector(position);
+ return vertexId;
+ });
+ const faces = {};
+ const textureIndex = textureByMaterial[geometry.materialId];
+ const texture = textures[textureIndex] || {width: 16, height: 16};
+ for (let triangle = 0; triangle < indices.length; triangle += 3) {
+ const ids = indices.slice(triangle, triangle + 3).map(function (index) {
+ return vertexIds[index];
+ });
+ const faceUvs = {};
+ indices.slice(triangle, triangle + 3).forEach(function (index) {
+ faceUvs[vertexIds[index]] = [uvs[index][0] * texture.width, uvs[index][1] * texture.height];
+ });
+ faces['f' + triangle / 3] = {vertices: ids, uv: faceUvs, texture: textureIndex};
+ }
+ return {
+ name: geometryId.replace(/_/g, ' '),
+ type: 'mesh',
+ uuid: elementUuid,
+ origin: origin.slice(),
+ rotation: [0, 0, 0],
+ visibility: true,
+ export: true,
+ vertices: vertices,
+ faces: faces
+ };
+ }
+
+ function importedV2Geometry(decoded, nodes, grouping, used, textures) {
+ const elements = [];
+ const roots = [];
+ nodes.retainedNodes.forEach(function (node) {
+ const groupUuid = grouping.nodeUuidById[node.id];
+ const origin = grouping.originById[node.id];
+ const childIds = [];
+ (node.geometryIds || []).forEach(function (geometryId) {
+ const geometry = nodes.geometryById[geometryId];
+ if (!geometry) return;
+ const elementUuid = importedUuid('v2-geometry:' + geometryId + ':' + node.id, used);
+ childIds.push(elementUuid);
+ const element = geometry.kind === 'cuboid'
+ ? importedV2Cuboid(
+ geometryId,
+ geometry,
+ elementUuid,
+ origin,
+ textures.textures,
+ textures.textureByMaterial
+ )
+ : geometry.kind === 'indexed_triangles'
+ ? importedV2Mesh(
+ decoded,
+ geometryId,
+ geometry,
+ elementUuid,
+ origin,
+ textures.textures,
+ textures.textureByMaterial
+ )
+ : null;
+ if (element) elements.push(element);
+ });
+ const group = grouping.groups.find(function (candidate) { return candidate.uuid === groupUuid; });
+ group.children = childIds;
+ if (node.parentId && grouping.nodeUuidById[node.parentId]) {
+ const parent = grouping.groups.find(function (candidate) {
+ return candidate.uuid === grouping.nodeUuidById[node.parentId];
+ });
+ parent.children.push({uuid: groupUuid, isOpen: true, children: group.children});
+ } else {
+ roots.push({uuid: groupUuid, isOpen: true, children: group.children});
+ }
+ });
+ return {elements: elements, roots: roots};
+ }
+
+ function v2RuntimeToBbmodel(decoded) {
+ const manifest = decoded.manifest;
+ if (manifest.assetKind !== 'accessory') {
+ throw new Error('Only V1/V2 accessory packages can be imported into the cosmetic editor.');
+ }
+ const used = Object.create(null);
+ const textures = importedV2Textures(decoded, used);
+ const nodes = importedV2Nodes(manifest);
+ const grouping = importedV2Groups(nodes, used);
+ const geometry = importedV2Geometry(decoded, nodes, grouping, used, textures);
+ return {
+ meta: {format_version: '5.0', model_format: FORMAT_ID, box_uv: false},
+ name: manifest.id || 'Imported V2 Accessory',
+ resolution: {
+ width: textures.textures[0] && textures.textures[0].width || 64,
+ height: textures.textures[0] && textures.textures[0].height || 64
+ },
+ elements: geometry.elements,
+ groups: grouping.groups,
+ outliner: geometry.roots,
+ textures: textures.textures,
+ animations: [],
+ cosmiq_project_kind: PROJECT_KINDS.COSMETIC,
+ cosmiq_model_variant: nodes.preferredVariant,
+ cosmiq_arm_width_mode: ARM_WIDTH_MODES.UNCONFIGURED,
+ cosmiq: {
+ schemaVersion: 'cosmiq.imported.runtime.v2',
+ formatVersion: 2,
+ kind: 'cosmetic',
+ cosmetic: {
+ attachmentAnchorId: importedAnchor({}, manifest),
+ modelVariant: nodes.preferredVariant,
+ bodySlots: []
+ }
+ }
+ };
+ }
+
+ function parseLegacyCosmeticImport(content, fileName) {
+ const bytes = cosmiqBytes(content);
+ if (!bytes.length || bytes.length > 32 * 1024 * 1024) {
+ throw new Error('Imported file is empty or exceeds the 32 MiB migration limit.');
+ }
+ let project;
+ let manifest = null;
+ let sourceVersion = 'bbmodel';
+ const hasMagic = bytes.length >= 9 && decodeUtf8(bytes.subarray(0, 8)) === COSMIQ_STANDARD_MAGIC;
+ if (hasMagic && bytes[8] === 1) {
+ const decodedV1 = parseLegacyV1Container(bytes);
+ project = decodedV1.project;
+ manifest = decodedV1.manifest;
+ sourceVersion = 'v1';
+ } else if (hasMagic) {
+ const decodedV2 = parseAndValidateCosmiqRuntime(bytes);
+ project = v2RuntimeToBbmodel(decodedV2);
+ manifest = decodedV2.manifest;
+ sourceVersion = 'v2';
+ } else {
+ try {
+ project = JSON.parse(decodeUtf8(bytes));
+ } catch (error) {
+ throw new Error('The selected .bbmodel is not valid UTF-8 JSON.');
+ }
+ if (!cosmiqPlainObject(project)) throw new Error('The selected .bbmodel root must be an object.');
+ cosmiqValidateJsonBounds(project);
+ }
+ const kind = importedProjectKind(project, manifest);
+ if (kind !== 'accessory') {
+ throw new Error('This importer accepts accessory cosmetics. Emote and cape projects use their dedicated creators.');
+ }
+ return {
+ project: project,
+ manifest: manifest,
+ sourceVersion: sourceVersion,
+ fileName: String(fileName || 'Imported Accessory'),
+ attachmentAnchorId: importedAnchor(project, manifest),
+ modelVariant: importedVariant(project, manifest)
+ };
+ }
+
+ function importedTreeParents(outliner) {
+ const parentByUuid = Object.create(null);
+ const entryByUuid = Object.create(null);
+ function visit(entries, parentUuid) {
+ (Array.isArray(entries) ? entries : []).forEach(function (entry) {
+ if (typeof entry === 'string') {
+ parentByUuid[entry] = parentUuid || null;
+ return;
+ }
+ if (!entry || typeof entry !== 'object' || !entry.uuid) return;
+ parentByUuid[entry.uuid] = parentUuid || null;
+ entryByUuid[entry.uuid] = entry;
+ visit(entry.children, entry.uuid);
+ });
+ }
+ visit(outliner, null);
+ return {parentByUuid: parentByUuid, entryByUuid: entryByUuid};
+ }
+
+ function importedReferenceTexture(texture) {
+ const name = String(texture && (texture.name || texture.id || texture.path) || '').toLowerCase();
+ return texture && (texture.cosmiq_role === ROLES.REFERENCE || texture.role === ROLES.REFERENCE) ||
+ /(^|[\\/_ -])(alex|steve|skin|pmcskin|reference|wide|slim)([\\/_ .-]|$)/.test(name);
+ }
+
+ function importedBodyElement(element, textures) {
+ if (!element) return true;
+ if (element.cosmiq_role === ROLES.REFERENCE || element.role === ROLES.REFERENCE) return true;
+ const name = String(element.name || '').toLowerCase().replace(/[_-]+/g, ' ').trim();
+ if (!/^(head|body|torso|left arm|right arm|left leg|right leg)( layer)?$/.test(name)) return false;
+ const faces = element.faces || {};
+ const referenced = Object.keys(faces).some(function (key) {
+ const value = faces[key] && faces[key].texture;
+ if (Number.isInteger(value)) return importedReferenceTexture(textures[value]);
+ const normalized = String(value || '').replace(/^#/, '');
+ return textures.some(function (texture) {
+ return texture && (texture.uuid === normalized || texture.id === normalized) &&
+ importedReferenceTexture(texture);
+ });
+ });
+ return referenced || !Object.keys(faces).length;
+ }
+
+ function importedAnchorFromNames(anchorId, keptUuids, groupByUuid, parentByUuid) {
+ if (anchorId && anchorId !== 'cosmiq:player/torso') return anchorId;
+ const names = [];
+ Object.keys(keptUuids).forEach(function (uuid) {
+ let cursor = uuid;
+ let guard = 0;
+ while (cursor && guard++ < 64) {
+ const group = groupByUuid[cursor];
+ if (group && group.name) names.push(String(group.name).toLowerCase());
+ cursor = parentByUuid[cursor];
+ }
+ });
+ const joined = names.join(' ');
+ if (/\bhead\b|\bhat\b|\bhorn\b|\bhair\b/.test(joined)) return 'cosmiq:player/head';
+ if (/\bleft arm\b|\bleft hand\b|\bbracelet\b|\bbracer\b/.test(joined)) return 'cosmiq:player/left_arm';
+ if (/\bright arm\b|\bright hand\b/.test(joined)) return 'cosmiq:player/right_arm';
+ if (/\bleft leg\b|\bleft foot\b/.test(joined)) return 'cosmiq:player/left_leg';
+ if (/\bright leg\b|\bright foot\b/.test(joined)) return 'cosmiq:player/right_leg';
+ return anchorId || 'cosmiq:player/torso';
+ }
+
+ function remapImportedValue(value, uuidMap) {
+ if (typeof value === 'string') return uuidMap[value] || value;
+ if (Array.isArray(value)) {
+ return value.map(function (item) { return remapImportedValue(item, uuidMap); });
+ }
+ if (!cosmiqPlainObject(value)) return value;
+ const output = {};
+ Object.keys(value).forEach(function (key) {
+ output[uuidMap[key] || key] = remapImportedValue(value[key], uuidMap);
+ });
+ return output;
+ }
+
+ function selectImportedNodes(decoded, groups, elements, textures, metadata) {
+ const groupByUuid = Object.create(null);
+ const elementByUuid = Object.create(null);
+ groups.forEach(function (group) { if (group && group.uuid) groupByUuid[group.uuid] = group; });
+ elements.forEach(function (element) { if (element && element.uuid) elementByUuid[element.uuid] = element; });
+ const tree = importedTreeParents(decoded.project.outliner);
+ const explicit = Object.create(null);
+ const nodeBindings = cosmiqPlainObject(metadata.nodeBindings) ? metadata.nodeBindings : {};
+ Object.keys(nodeBindings).forEach(function (uuid) {
+ if (nodeBindings[uuid] && nodeBindings[uuid].role === 'cosmetic_node') explicit[uuid] = true;
+ });
+ const locatorBindings = cosmiqPlainObject(metadata.effectLocatorBindings)
+ ? metadata.effectLocatorBindings : {};
+ Object.keys(locatorBindings).forEach(function (uuid) { explicit[uuid] = true; });
+ groups.forEach(function (group) {
+ if (group && [
+ ROLES.PARTICLE_MARKER,
+ ROLES.PARTICLE_EFFECT_LOCATOR,
+ ROLES.SOUND_EFFECT_LOCATOR
+ ].indexOf(group.cosmiq_role) !== -1) explicit[group.uuid] = true;
+ });
+ const hasExplicitBindings = Object.keys(explicit).length > 0;
+ const kept = Object.create(null);
+ elements.forEach(function (element) {
+ if (hasExplicitBindings ? explicit[element.uuid] : !importedBodyElement(element, textures)) {
+ kept[element.uuid] = true;
+ }
+ });
+ groups.forEach(function (group) {
+ if (explicit[group.uuid]) kept[group.uuid] = true;
+ });
+ Object.keys(kept).forEach(function (uuid) {
+ let parent = tree.parentByUuid[uuid];
+ let guard = 0;
+ while (parent && guard++ < 64) {
+ kept[parent] = true;
+ parent = tree.parentByUuid[parent];
+ }
+ });
+ if (!Object.keys(kept).some(function (uuid) { return !!elementByUuid[uuid]; })) {
+ throw new Error('No cosmetic geometry remained after the humanoid reference body was removed.');
+ }
+ const anchorId = importedAnchorFromNames(
+ decoded.attachmentAnchorId,
+ kept,
+ groupByUuid,
+ tree.parentByUuid
+ );
+ return {anchorId: anchorId, kept: kept, tree: tree};
+ }
+
+ function copyImportedTextures(elements, textures, kept, uuidMap, used) {
+ const textureUsage = Object.create(null);
+ elements.filter(function (element) { return kept[element.uuid]; }).forEach(function (element) {
+ Object.keys(element.faces || {}).forEach(function (faceName) {
+ const texture = element.faces[faceName] && element.faces[faceName].texture;
+ if (Number.isInteger(texture) && textures[texture]) textureUsage[texture] = true;
+ else {
+ const normalized = String(texture || '').replace(/^#/, '');
+ textures.forEach(function (candidate, index) {
+ if (candidate && (candidate.uuid === normalized || candidate.id === normalized)) {
+ textureUsage[index] = true;
+ }
+ });
+ }
+ });
+ });
+ const textureIndexMap = Object.create(null);
+ const importedTextures = [];
+ Object.keys(textureUsage).map(Number).sort(function (a, b) { return a - b; }).forEach(function (oldIndex) {
+ const texture = JSON.parse(JSON.stringify(textures[oldIndex]));
+ const oldUuid = texture.uuid;
+ texture.uuid = importedUuid('import-texture:' + (oldUuid || oldIndex), used);
+ if (oldUuid) uuidMap[oldUuid] = texture.uuid;
+ texture.cosmiq_role = ROLES.NONE;
+ texture.role = ROLES.NONE;
+ textureIndexMap[oldIndex] = importedTextures.length;
+ importedTextures.push(texture);
+ });
+ return {textures: importedTextures, indexMap: textureIndexMap};
+ }
+
+ function copyImportedNodes(groups, elements, kept, uuidMap, textureIndexMap) {
+ const importedGroups = groups.filter(function (group) { return kept[group.uuid]; }).map(function (group) {
+ const copy = JSON.parse(JSON.stringify(group));
+ copy.uuid = uuidMap[group.uuid];
+ copy.export = [
+ ROLES.PARTICLE_EFFECT_LOCATOR,
+ ROLES.SOUND_EFFECT_LOCATOR
+ ].indexOf(group.cosmiq_role) === -1;
+ copy.locked = false;
+ if ([
+ ROLES.PARTICLE_MARKER,
+ ROLES.PARTICLE_EFFECT_LOCATOR,
+ ROLES.SOUND_EFFECT_LOCATOR
+ ].indexOf(group.cosmiq_role) === -1) copy.cosmiq_role = ROLES.NONE;
+ copy.cosmiq_anchor = 'none';
+ copy.cosmiq_model_branch = 'none';
+ copy.children = remapImportedValue(copy.children || [], uuidMap);
+ return copy;
+ });
+ const importedElements = elements.filter(function (element) { return kept[element.uuid]; }).map(function (element) {
+ const copy = JSON.parse(JSON.stringify(element));
+ copy.uuid = uuidMap[element.uuid];
+ copy.export = [
+ ROLES.PARTICLE_EFFECT_LOCATOR,
+ ROLES.SOUND_EFFECT_LOCATOR
+ ].indexOf(element.cosmiq_role) === -1;
+ copy.locked = false;
+ if ([
+ ROLES.PARTICLE_MARKER,
+ ROLES.PARTICLE_EFFECT_LOCATOR,
+ ROLES.SOUND_EFFECT_LOCATOR
+ ].indexOf(element.cosmiq_role) === -1) copy.cosmiq_role = ROLES.NONE;
+ Object.keys(copy.faces || {}).forEach(function (faceName) {
+ const face = copy.faces[faceName];
+ if (!face) return;
+ if (Number.isInteger(face.texture)) {
+ face.texture = Object.hasOwn(textureIndexMap, face.texture)
+ ? textureIndexMap[face.texture] : null;
+ } else {
+ const normalized = String(face.texture || '').replace(/^#/, '');
+ face.texture = uuidMap[normalized] || face.texture;
+ }
+ });
+ return copy;
+ });
+ return {elements: importedElements, groups: importedGroups};
+ }
+
+ function copyImportedTree(entries, kept, uuidMap) {
+ const output = [];
+ (Array.isArray(entries) ? entries : []).forEach(function (entry) {
+ if (typeof entry === 'string') {
+ if (kept[entry]) output.push(uuidMap[entry]);
+ return;
+ }
+ if (!entry || typeof entry !== 'object' || !kept[entry.uuid]) return;
+ const children = copyImportedTree(entry.children, kept, uuidMap);
+ output.push(Object.assign({}, entry, {uuid: uuidMap[entry.uuid], children: children}));
+ });
+ return output;
+ }
+
+ function copyImportedAnimations(project, metadata, uuidMap, used) {
+ const animationUuidMap = Object.create(null);
+ const cueAnimationUuids = Object.create(null);
+ (Array.isArray(metadata.timelineCues) ? metadata.timelineCues : []).forEach(function (cue) {
+ if (cue && cue.animationUuid) cueAnimationUuids[cue.animationUuid] = true;
+ });
+ const importedAnimations = (Array.isArray(project.animations) ? project.animations : [])
+ .map(function (animation) {
+ const copy = JSON.parse(JSON.stringify(animation));
+ const animators = {};
+ Object.keys(copy.animators || {}).forEach(function (targetUuid) {
+ const animator = copy.animators[targetUuid];
+ const hasKeyframes = animator && Array.isArray(animator.keyframes) &&
+ animator.keyframes.length > 0;
+ if (uuidMap[targetUuid] && (hasKeyframes || cueAnimationUuids[copy.uuid])) {
+ animators[uuidMap[targetUuid]] = animator;
+ }
+ });
+ if (!Object.keys(animators).length && !cueAnimationUuids[copy.uuid]) return null;
+ const oldUuid = copy.uuid;
+ copy.uuid = importedUuid('import-animation:' + (oldUuid || copy.name), used);
+ if (oldUuid) animationUuidMap[oldUuid] = copy.uuid;
+ copy.animators = animators;
+ return copy;
+ }).filter(Boolean);
+ Object.assign(uuidMap, animationUuidMap);
+ return importedAnimations;
+ }
+
+ function animationImportIdentity(animation) {
+ if (!animation || typeof animation !== 'object') return null;
+ const explicitTrigger = resolveTrigger(
+ animation.cosmiq_trigger_kind,
+ animation.cosmiq_trigger
+ );
+ const parsedTrigger = explicitTrigger ? null : parseAnimationName(animation.name);
+ const trigger = explicitTrigger || (parsedTrigger
+ ? {kind: parsedTrigger.kind, id: parsedTrigger.triggerId}
+ : null);
+ if (trigger) return 'trigger:' + trigger.kind + ':' + trigger.id;
+ const normalizedName = normalizeToken(animation.name);
+ if (normalizedName) return 'name:' + normalizedName;
+ const uuid = String(animation.uuid || '').trim();
+ return uuid ? 'uuid:' + uuid : null;
+ }
+
+ function importedAnimationDecisionId(animation, identity, index) {
+ const uuid = String(animation && animation.uuid || '').trim();
+ return 'animation:' + (uuid || identity || 'unnamed') + ':' + index;
+ }
+
+ function canonicalImportedAnimation(animation, existingAnimation) {
+ const copy = JSON.parse(JSON.stringify(animation));
+ if (!existingAnimation) return copy;
+ copy.name = existingAnimation.name;
+ const trigger = resolveTrigger(
+ existingAnimation.cosmiq_trigger_kind,
+ existingAnimation.cosmiq_trigger
+ ) || (function () {
+ const parsed = parseAnimationName(existingAnimation.name);
+ return parsed ? {kind: parsed.kind, id: parsed.triggerId} : null;
+ }());
+ if (trigger) {
+ copy.cosmiq_trigger_kind = trigger.kind;
+ copy.cosmiq_trigger = trigger.id;
+ if (trigger.id === 'idle' && copy.cosmiq_fallback == null) {
+ copy.cosmiq_fallback = true;
+ }
+ }
+ return copy;
+ }
+
+ function planImportedAnimationMerge(existingAnimations, importedAnimations, decisions) {
+ const merged = (Array.isArray(existingAnimations) ? existingAnimations : []).slice();
+ const requestedDecisions = decisions && typeof decisions === 'object' ? decisions : {};
+ const conflicts = [];
+ const autoReplaced = [];
+ const overwritten = [];
+ const kept = [];
+ const skippedEmptyImports = [];
+
+ (Array.isArray(importedAnimations) ? importedAnimations : []).forEach(function (animation, index) {
+ if (!animation || typeof animation !== 'object') return;
+ const identity = animationImportIdentity(animation);
+ const existingIndex = identity ? merged.findIndex(function (candidate) {
+ return animationImportIdentity(candidate) === identity;
+ }) : -1;
+ if (existingIndex === -1) {
+ merged.push(canonicalImportedAnimation(animation, null));
+ return;
+ }
+
+ const existingAnimation = merged[existingIndex];
+ const existingKeyframes = animationKeyframeCount(existingAnimation);
+ const importedKeyframes = animationKeyframeCount(animation);
+ const decisionId = importedAnimationDecisionId(animation, identity, index);
+ const collision = {
+ decisionId: decisionId,
+ identity: identity,
+ existingName: String(existingAnimation.name || 'Unnamed Animation'),
+ importedName: String(animation.name || 'Unnamed Animation'),
+ existingKeyframes: existingKeyframes,
+ importedKeyframes: importedKeyframes
+ };
+
+ if (existingKeyframes === 0) {
+ merged[existingIndex] = canonicalImportedAnimation(animation, existingAnimation);
+ autoReplaced.push(Object.assign({action: 'replace_empty'}, collision));
+ return;
+ }
+ if (importedKeyframes === 0) {
+ skippedEmptyImports.push(Object.assign({action: 'skip_empty_import'}, collision));
+ return;
+ }
+ if (!Object.hasOwn(requestedDecisions, decisionId)) {
+ conflicts.push(Object.assign({action: 'ask'}, collision));
+ return;
+ }
+ if (requestedDecisions[decisionId] === true) {
+ merged[existingIndex] = canonicalImportedAnimation(animation, existingAnimation);
+ overwritten.push(Object.assign({action: 'overwrite'}, collision));
+ return;
+ }
+ kept.push(Object.assign({action: 'keep_existing'}, collision));
+ });
+
+ return {
+ animations: merged,
+ conflicts: conflicts,
+ autoReplaced: autoReplaced,
+ overwritten: overwritten,
+ kept: kept,
+ skippedEmptyImports: skippedEmptyImports
+ };
+ }
+
+ function filterImportedAnimationMetadata(metadata, animations) {
+ const filtered = Object.assign({}, metadata || {});
+ const includedAnimationUuids = Object.create(null);
+ (Array.isArray(animations) ? animations : []).forEach(function (animation) {
+ if (animation && animation.uuid) includedAnimationUuids[animation.uuid] = true;
+ });
+ if (filtered.animationBindings && typeof filtered.animationBindings === 'object') {
+ filtered.animationBindings = Object.keys(filtered.animationBindings)
+ .reduce(function (bindings, uuid) {
+ if (includedAnimationUuids[uuid]) bindings[uuid] = filtered.animationBindings[uuid];
+ return bindings;
+ }, {});
+ }
+ if (filtered.timelineMarkers && typeof filtered.timelineMarkers === 'object') {
+ filtered.timelineMarkers = Object.keys(filtered.timelineMarkers)
+ .reduce(function (markers, uuid) {
+ if (includedAnimationUuids[uuid]) markers[uuid] = filtered.timelineMarkers[uuid];
+ return markers;
+ }, {});
+ }
+ if (Array.isArray(filtered.timelineCues)) {
+ filtered.timelineCues = filtered.timelineCues.filter(function (cue) {
+ return !cue || !cue.animationUuid || includedAnimationUuids[cue.animationUuid];
+ });
+ }
+ return filtered;
+ }
+
+ function applyPreparedAnimationImportDecisions(project, decisions) {
+ const state = project && project.__cosmiqAnimationImport;
+ if (!state) {
+ throw new Error('This project does not contain a prepared animation import.');
+ }
+ const plan = planImportedAnimationMerge(
+ state.existingAnimations,
+ state.importedAnimations,
+ decisions
+ );
+ project.animations = plan.animations;
+ project.cosmiq = filterImportedAnimationMetadata(state.metadata, plan.animations);
+ state.decisions = Object.assign({}, decisions || {});
+ state.plan = plan;
+ return plan;
+ }
+
+ function copyImportedMetadata(metadata, uuidMap) {
+ const preservedMetadata = {};
+ [
+ 'resourceReferences',
+ 'timelineCues',
+ 'timelineMarkers',
+ 'effectLocatorBindings',
+ 'animationBindings',
+ 'playerAppearance'
+ ].forEach(function (key) {
+ if (metadata[key] != null) preservedMetadata[key] = remapImportedValue(metadata[key], uuidMap);
+ });
+ return preservedMetadata;
+ }
+
+ function extractImportedCosmetic(decoded) {
+ const project = decoded.project;
+ const groups = Array.isArray(project.groups) ? project.groups : [];
+ const elements = Array.isArray(project.elements) ? project.elements : [];
+ const textures = Array.isArray(project.textures) ? project.textures : [];
+ const metadata = cosmiqPlainObject(project.cosmiq) ? project.cosmiq : {};
+ const selected = selectImportedNodes(decoded, groups, elements, textures, metadata);
+ const used = Object.create(null);
+ const uuidMap = Object.create(null);
+ Object.keys(selected.kept).forEach(function (uuid) {
+ uuidMap[uuid] = importedUuid('import-node:' + uuid, used);
+ });
+ const importedTextures = copyImportedTextures(
+ elements,
+ textures,
+ selected.kept,
+ uuidMap,
+ used
+ );
+ const importedNodes = copyImportedNodes(
+ groups,
+ elements,
+ selected.kept,
+ uuidMap,
+ importedTextures.indexMap
+ );
+ const importedAnimations = copyImportedAnimations(project, metadata, uuidMap, used);
+ return {
+ anchorId: selected.anchorId,
+ modelVariant: decoded.modelVariant,
+ groups: importedNodes.groups,
+ elements: importedNodes.elements,
+ textures: importedTextures.textures,
+ animations: importedAnimations,
+ outliner: copyImportedTree(project.outliner, selected.kept, uuidMap),
+ metadata: copyImportedMetadata(metadata, uuidMap)
+ };
+ }
+
+ function findImportedOutlinerEntry(entries, uuid) {
+ let found = null;
+ (Array.isArray(entries) ? entries : []).some(function (entry) {
+ if (!entry || typeof entry !== 'object') return false;
+ if (entry.uuid === uuid) {
+ found = entry;
+ return true;
+ }
+ found = findImportedOutlinerEntry(entry.children, uuid);
+ return !!found;
+ });
+ return found;
+ }
+
+ async function prepareLegacyCosmeticImport(decoded) {
+ const embedded = await loadBaseTemplateJson();
+ const name = String(decoded.fileName || 'Imported Accessory')
+ .replace(/\.(?:cosmiq|bbmodel)$/i, '')
+ .replace(/[_-]+/g, ' ')
+ .trim();
+ const extracted = extractImportedCosmetic(decoded);
+ const visibleBody = extracted.modelVariant === 'slim' ? 'slim' : 'wide';
+ const project = annotateBaseTemplate(embedded, PROJECT_KINDS.COSMETIC, {
+ name: name || 'Imported',
+ visibleBody: visibleBody
+ });
+ project.elements = [];
+ project.textures = [];
+ function stripTemplateElements(entries) {
+ return (Array.isArray(entries) ? entries : []).reduce(function (output, entry) {
+ if (typeof entry === 'string') return output;
+ if (!entry || typeof entry !== 'object') return output;
+ output.push(Object.assign({}, entry, {children: stripTemplateElements(entry.children)}));
+ return output;
+ }, []);
+ }
+ project.outliner = stripTemplateElements(project.outliner);
+ const targetPoint = TEMPLATE_ATTACHMENT_POINTS.find(function (point) {
+ return point.anchorId === extracted.anchorId &&
+ (point.modelVariant === 'universal' || point.modelVariant === visibleBody);
+ }) || TEMPLATE_ATTACHMENT_POINTS[0];
+ const targetGroup = baseTemplateGroup(project, targetPoint.modelGroup);
+ const used = Object.create(null);
+ project.groups.concat(extracted.groups).forEach(function (group) {
+ if (group && group.uuid) used[group.uuid] = true;
+ });
+ const wrapperUuid = importedUuid('import-wrapper:' + decoded.fileName, used);
+ const wrapper = {
+ name: IMPORTED_GROUP_NAME,
+ uuid: wrapperUuid,
+ origin: importedAnchorOrigin(extracted.anchorId),
+ rotation: [0, 0, 0],
+ export: true,
+ locked: false,
+ visibility: true,
+ cosmiq_role: ROLES.NONE,
+ cosmiq_actor_index: 0,
+ cosmiq_player_bone: 'none',
+ cosmiq_anchor: 'none',
+ cosmiq_model_branch: 'none',
+ children: extracted.outliner
+ };
+ project.groups = project.groups.concat([wrapper], extracted.groups);
+ project.elements = extracted.elements;
+ project.textures = extracted.textures;
+ const animationImportState = {
+ existingAnimations: (project.animations || []).slice(),
+ importedAnimations: extracted.animations.slice(),
+ metadata: Object.assign({}, extracted.metadata),
+ decisions: {},
+ plan: null
+ };
+ Object.defineProperty(project, '__cosmiqAnimationImport', {
+ value: animationImportState,
+ enumerable: false,
+ configurable: true
+ });
+ applyPreparedAnimationImportDecisions(project, {});
+ const targetEntry = findImportedOutlinerEntry(project.outliner, targetGroup.uuid);
+ if (!targetEntry) throw new Error('The bundled base template is missing the selected import anchor.');
+ const wrapperEntry = {uuid: wrapperUuid, isOpen: true, children: extracted.outliner};
+ targetEntry.children.push(wrapperEntry);
+ targetGroup.children.push(wrapperEntry);
+ return project;
+ }
+
+ function baseAnimationTrigger(animationName) {
+ const aliases = {
+ sitting: 'riding',
+ gliding: 'fall_flying',
+ trident_spin: 'spin_attack'
+ };
+ const parsed = /^(state|event)\.([a-z0-9_]+)$/.exec(String(animationName || ''));
+ if (!parsed) return null;
+ const triggerId = aliases[parsed[2]] || parsed[2];
+ return triggerById[triggerId] || null;
+ }
+
+ function addEmoteTemplateWrappers(rawTemplate, rootGroup) {
+ const sceneUuid = 'c05a1e00-0000-4000-8000-000000000001';
+ const actorUuid = 'c05a1e00-0000-4000-8000-000000000002';
+ rawTemplate.groups.push({
+ name: 'Emote Scene',
+ uuid: sceneUuid,
+ origin: [0, 0, 0],
+ rotation: [0, 0, 0],
+ export: false,
+ visibility: true,
+ cosmiq_role: ROLES.EMOTE_SCENE,
+ cosmiq_actor_index: 0,
+ cosmiq_player_bone: 'none',
+ cosmiq_anchor: 'none',
+ cosmiq_model_branch: 'none',
+ children: []
+ });
+ rawTemplate.groups.push({
+ name: '1 Actor',
+ uuid: actorUuid,
+ origin: [0, 0, 0],
+ rotation: [0, 0, 0],
+ export: false,
+ visibility: true,
+ cosmiq_role: ROLES.EMOTE_ACTOR,
+ cosmiq_actor_index: 1,
+ cosmiq_player_bone: 'none',
+ cosmiq_anchor: 'none',
+ cosmiq_model_branch: 'none',
+ children: []
+ });
+ const originalRoot = (rawTemplate.outliner || []).find(function (entry) {
+ return entry && typeof entry === 'object' && entry.uuid === rootGroup.uuid;
+ });
+ if (!originalRoot) {
+ throw new Error('Base template Outliner is missing Cosmiq Reference Model.');
+ }
+ rawTemplate.outliner = [{
+ uuid: sceneUuid,
+ isOpen: true,
+ children: [{
+ uuid: actorUuid,
+ isOpen: true,
+ children: [originalRoot]
+ }]
+ }];
+ }
+
+ function initialEmoteTemplateAnimations() {
+ return [
+ {
+ uuid: 'c05a1e00-0000-4000-8000-000000000101',
+ name: 'scene.main',
+ loop: 'once',
+ override: false,
+ length: 2,
+ snapping: 20,
+ selected: false,
+ group_name: '',
+ scope: 0,
+ anim_time_update: '',
+ blend_weight: '',
+ start_delay: '',
+ loop_delay: '',
+ animators: {}
+ },
+ {
+ uuid: 'c05a1e00-0000-4000-8000-000000000102',
+ name: 'actor.1.main',
+ loop: 'once',
+ override: false,
+ length: 2,
+ snapping: 20,
+ selected: false,
+ group_name: '',
+ scope: 0,
+ anim_time_update: '',
+ blend_weight: '',
+ start_delay: '',
+ loop_delay: '',
+ animators: {}
+ }
+ ];
+ }
+
+ const SIMPLE_ARMATURE_UUID = 'c05a1e11-0000-4000-8000-000000000000';
+ const SIMPLE_BONE_UUIDS = Object.freeze({
+ root: 'c05a1e11-0000-4000-8000-000000000001',
+ body: 'c05a1e11-0000-4000-8000-000000000002',
+ head: 'c05a1e11-0000-4000-8000-000000000003',
+ left_arm: 'c05a1e11-0000-4000-8000-000000000004',
+ right_arm: 'c05a1e11-0000-4000-8000-000000000005',
+ left_leg: 'c05a1e11-0000-4000-8000-000000000006',
+ right_leg: 'c05a1e11-0000-4000-8000-000000000007'
+ });
+ const PRECISE_ARMATURE_UUID = 'c05a1e10-0000-4000-8000-000000000000';
+ const PRECISE_BONE_UUIDS = Object.freeze({
+ root: 'c05a1e10-0000-4000-8000-000000000001',
+ torso_lower: 'c05a1e10-0000-4000-8000-000000000002',
+ torso_upper: 'c05a1e10-0000-4000-8000-000000000003',
+ head: 'c05a1e10-0000-4000-8000-000000000004',
+ left_arm_upper: 'c05a1e10-0000-4000-8000-000000000005',
+ left_arm_lower: 'c05a1e10-0000-4000-8000-000000000006',
+ right_arm_upper: 'c05a1e10-0000-4000-8000-000000000007',
+ right_arm_lower: 'c05a1e10-0000-4000-8000-000000000008',
+ left_leg_upper: 'c05a1e10-0000-4000-8000-000000000009',
+ left_leg_lower: 'c05a1e10-0000-4000-8000-000000000010',
+ right_leg_upper: 'c05a1e10-0000-4000-8000-000000000011',
+ right_leg_lower: 'c05a1e10-0000-4000-8000-000000000012'
+ });
+
+ function templateOutlinerEntry(entries, uuid) {
+ let found = null;
+ (Array.isArray(entries) ? entries : []).some(function (entry) {
+ if (!entry || typeof entry !== 'object') return false;
+ if (entry.uuid === uuid) {
+ found = entry;
+ return true;
+ }
+ found = templateOutlinerEntry(entry.children, uuid);
+ return !!found;
+ });
+ return found;
+ }
+
+ function detachTemplateOutlinerEntry(entries, uuid) {
+ const source = Array.isArray(entries) ? entries : [];
+ for (let index = 0; index < source.length; index++) {
+ const entry = source[index];
+ if (!entry || typeof entry !== 'object') continue;
+ if (entry.uuid === uuid) return source.splice(index, 1)[0];
+ const nested = detachTemplateOutlinerEntry(entry.children, uuid);
+ if (nested) return nested;
+ }
+ return null;
+ }
+
+ function attachTemplateCapeToUpperTorso(rawTemplate, rigProfile) {
+ const capeRoot = baseTemplateGroup(rawTemplate, 'Cape/Elytra');
+ if (rigProfile === EMOTE_RIG_PROFILES.PRECISE ||
+ rigProfile === EMOTE_RIG_PROFILES.SIMPLE) {
+ const targetWorldY = rigProfile === EMOTE_RIG_PROFILES.PRECISE ? 18 : 12;
+ const groupByUuid = Object.create(null);
+ const elementByUuid = Object.create(null);
+ (rawTemplate.groups || []).forEach(function (group) {
+ groupByUuid[group.uuid] = group;
+ });
+ (rawTemplate.elements || []).forEach(function (element) {
+ elementByUuid[element.uuid] = element;
+ });
+ const capeEntry = templateOutlinerEntry(rawTemplate.outliner, capeRoot.uuid);
+ function localize(entries) {
+ (entries || []).forEach(function (entry) {
+ const uuid = typeof entry === 'string' ? entry : entry.uuid;
+ const record = groupByUuid[uuid] || elementByUuid[uuid];
+ if (record) {
+ ['origin', 'from', 'to', 'position'].forEach(function (field) {
+ if (Array.isArray(record[field])) {
+ record[field] = record[field].map(function (value, axis) {
+ return Number(value) - (axis === 1 ? targetWorldY : 0);
+ });
+ }
+ });
+ }
+ if (entry && typeof entry === 'object') localize(entry.children);
+ });
+ }
+ capeRoot.origin = [0, 24 - targetWorldY, 0];
+ localize(capeEntry && capeEntry.children);
+ }
+ const targetUuid = rigProfile === EMOTE_RIG_PROFILES.PRECISE
+ ? PRECISE_BONE_UUIDS.torso_upper
+ : SIMPLE_BONE_UUIDS.body;
+ const capeEntry = detachTemplateOutlinerEntry(rawTemplate.outliner, capeRoot.uuid);
+ const targetEntry = templateOutlinerEntry(rawTemplate.outliner, targetUuid);
+ if (!capeEntry || !targetEntry) {
+ throw new Error('Emote template could not attach Cape/Elytra to the upper torso.');
+ }
+ targetEntry.children = Array.isArray(targetEntry.children) ? targetEntry.children : [];
+ targetEntry.children.push(capeEntry);
+ return rawTemplate;
+ }
+
+ function directTemplateElementUuids(rawTemplate, groupName) {
+ const group = baseTemplateGroup(rawTemplate, groupName);
+ const entry = templateOutlinerEntry(rawTemplate.outliner, group.uuid);
+ if (!entry) throw new Error('Base template Outliner is missing "' + groupName + '".');
+ return (entry.children || []).filter(function (child) {
+ return typeof child === 'string';
+ });
+ }
+
+ function preciseFaceUvCorners(face) {
+ const uv = face && Array.isArray(face.uv) ? face.uv : [0, 0, 0, 0];
+ const points = [
+ [uv[0], uv[1]],
+ [uv[2], uv[1]],
+ [uv[2], uv[3]],
+ [uv[0], uv[3]]
+ ];
+ let rotation = Number(face && face.rotation) || 0;
+ while (rotation > 0) {
+ rotation -= 90;
+ points.unshift(points.pop());
+ }
+ return [points[1], points[0], points[2], points[3]];
+ }
+
+ function preciseLerpUv(from, to, amount) {
+ return [
+ from[0] + (to[0] - from[0]) * amount,
+ from[1] + (to[1] - from[1]) * amount
+ ];
+ }
+
+ function preciseMeshFromCube(cube, settings) {
+ const options = settings || {};
+ const inflate = Number(cube.inflate) || 0;
+ const origin = Array.isArray(cube.origin) ? cube.origin.slice(0, 3) : [0, 0, 0];
+ const from = cube.from.map(function (value) { return Number(value) - inflate; });
+ const to = cube.to.map(function (value) { return Number(value) + inflate; });
+ const joint = Number.isFinite(Number(options.jointY)) ? Number(options.jointY) : null;
+ const ringYs = joint === null
+ ? [to[1], from[1]]
+ : [to[1], joint + 1, joint, joint - 1, from[1]];
+ const vertices = {};
+ const vertexY = {};
+ const rings = ringYs.map(function (y, ringIndex) {
+ return [
+ [to[0], y, to[2]],
+ [to[0], y, from[2]],
+ [from[0], y, to[2]],
+ [from[0], y, from[2]]
+ ].map(function (position, cornerIndex) {
+ const key = 'r' + ringIndex + 'c' + cornerIndex;
+ vertices[key] = position.map(function (value, axis) {
+ return value - origin[axis];
+ });
+ vertexY[key] = y;
+ return key;
+ });
+ });
+ const faces = {};
+
+ function addFace(key, direction, faceVertices, uvValues) {
+ const cubeFace = cube.faces && cube.faces[direction];
+ if (!cubeFace || cubeFace.texture === null) return;
+ const uv = {};
+ faceVertices.forEach(function (vertexKey, index) {
+ uv[vertexKey] = uvValues[index];
+ });
+ faces[key] = {
+ uv: uv,
+ texture: cubeFace.texture,
+ vertices: faceVertices
+ };
+ }
+
+ const sideDefinitions = {
+ east: [1, 0],
+ west: [2, 3],
+ south: [0, 2],
+ north: [3, 1]
+ };
+ Object.keys(sideDefinitions).forEach(function (direction) {
+ const cubeFace = cube.faces && cube.faces[direction];
+ if (!cubeFace || cubeFace.texture === null) return;
+ const fullUvs = preciseFaceUvCorners(cubeFace);
+ const corners = sideDefinitions[direction];
+ for (let segment = 0; segment < rings.length - 1; segment++) {
+ const topAmount = (to[1] - ringYs[segment]) / (to[1] - from[1]);
+ const bottomAmount = (to[1] - ringYs[segment + 1]) / (to[1] - from[1]);
+ const faceVertices = [
+ rings[segment][corners[0]],
+ rings[segment][corners[1]],
+ rings[segment + 1][corners[0]],
+ rings[segment + 1][corners[1]]
+ ];
+ addFace(direction + '_' + segment, direction, faceVertices, [
+ preciseLerpUv(fullUvs[0], fullUvs[2], topAmount),
+ preciseLerpUv(fullUvs[1], fullUvs[3], topAmount),
+ preciseLerpUv(fullUvs[0], fullUvs[2], bottomAmount),
+ preciseLerpUv(fullUvs[1], fullUvs[3], bottomAmount)
+ ]);
+ }
+ });
+
+ [
+ ['up', rings[0], [1, 3, 0, 2]],
+ ['down', rings[rings.length - 1], [0, 2, 1, 3]]
+ ].forEach(function (definition) {
+ const direction = definition[0];
+ const cubeFace = cube.faces && cube.faces[direction];
+ if (!cubeFace || cubeFace.texture === null) return;
+ const faceVertices = definition[2].map(function (corner) {
+ return definition[1][corner];
+ });
+ addFace(direction, direction, faceVertices, preciseFaceUvCorners(cubeFace));
+ });
+
+ return {
+ mesh: {
+ name: cube.name,
+ type: 'mesh',
+ uuid: cube.uuid,
+ color: Number(cube.color) || 0,
+ origin: origin,
+ rotation: Array.isArray(cube.rotation) ? cube.rotation.slice(0, 3) : [0, 0, 0],
+ shading: 'flat',
+ export: false,
+ visibility: options.visibility !== false,
+ locked: false,
+ vertices: vertices,
+ faces: faces,
+ cosmiq_role: ROLES.REFERENCE,
+ cosmiq_actor_index: 1,
+ cosmiq_player_bone: 'none'
+ },
+ vertexY: vertexY
+ };
+ }
+
+ function simpleBoneLayout(boneId) {
+ const layouts = {
+ root: {
+ origin: [0, 0, 0],
+ length: 12,
+ width: 1,
+ connected: false
+ },
+ body: {
+ origin: [0, 12, 0],
+ length: 12,
+ width: 1.75,
+ connected: true
+ },
+ head: {
+ origin: [0, 12, 0],
+ length: 8,
+ width: 2,
+ connected: true
+ },
+ left_arm: {
+ origin: [-5, 10, 0],
+ length: -10,
+ width: 1.25,
+ connected: false
+ },
+ right_arm: {
+ origin: [5, 10, 0],
+ length: -10,
+ width: 1.25,
+ connected: false
+ },
+ left_leg: {
+ origin: [-1.9, 12, 0],
+ length: -12,
+ width: 1.25,
+ connected: false
+ },
+ right_leg: {
+ origin: [1.9, 12, 0],
+ length: -12,
+ width: 1.25,
+ connected: false
+ }
+ };
+ const layout = layouts[boneId];
+ return {
+ origin: layout.origin.slice(),
+ rotation: [0, 0, 0],
+ length: layout.length,
+ width: layout.width,
+ connected: layout.connected
+ };
+ }
+
+ function verifySimpleRigStructure(rawTemplate) {
+ const elements = Array.isArray(rawTemplate && rawTemplate.elements)
+ ? rawTemplate.elements
+ : [];
+ const hierarchy = importedTreeParents(rawTemplate && rawTemplate.outliner);
+ const armatures = elements.filter(function (element) { return element.type === 'armature'; });
+ if (armatures.length !== 1 || armatures[0].uuid !== SIMPLE_ARMATURE_UUID) {
+ throw new Error('Simple template requires exactly one Cosmiq player armature.');
+ }
+ const meshes = elements.filter(function (element) { return element.type === 'mesh'; });
+ const bones = elements.filter(function (element) { return element.type === 'armature_bone'; });
+ if (meshes.length !== 16 || bones.length !== EMOTE_PLAYER_BONES.length) {
+ throw new Error('Simple template requires 16 rigid player meshes and 7 armature bones.');
+ }
+ const boneBySemantic = Object.create(null);
+ bones.forEach(function (bone) {
+ boneBySemantic[bone.cosmiq_player_bone] = bone;
+ });
+ EMOTE_PLAYER_BONES.forEach(function (definition) {
+ const bone = boneBySemantic[definition.id];
+ if (!bone) {
+ throw new Error('Simple template is missing armature bone ' + definition.id + '.');
+ }
+ const expectedParentUuid = definition.parent
+ ? boneBySemantic[definition.parent].uuid
+ : SIMPLE_ARMATURE_UUID;
+ if (hierarchy.parentByUuid[bone.uuid] !== expectedParentUuid) {
+ throw new Error(
+ 'Simple template armature bone ' + definition.id +
+ ' must be parented to ' + (definition.parent || 'the player armature') + '.'
+ );
+ }
+ });
+ meshes.forEach(function (mesh) {
+ const prefix = mesh.uuid.slice(0, 6) + ':';
+ Object.keys(mesh.vertices || {}).forEach(function (vertexKey) {
+ const weightKey = prefix + vertexKey;
+ const influences = bones.map(function (bone) {
+ return Number(bone.vertex_weights && bone.vertex_weights[weightKey]) || 0;
+ }).filter(function (weight) { return weight > 0; });
+ if (influences.length !== 1 || Math.abs(influences[0] - 1) > 1e-9) {
+ throw new Error('Simple template has a non-rigid weight for ' + mesh.name + ':' + vertexKey + '.');
+ }
+ });
+ });
+ return rawTemplate;
+ }
+
+ function convertToSimpleRig(rawTemplate, visibleBody) {
+ const selectedVariant = visibleBody === 'slim' ? 'slim' : 'wide';
+ const elementByUuid = Object.create(null);
+ (rawTemplate.elements || []).forEach(function (element) {
+ elementByUuid[element.uuid] = element;
+ });
+ const partPlans = [
+ {group: 'Head', bone: 'head'},
+ {group: 'Body', bone: 'body'},
+ {group: 'Left Arm Wide', variant: 'wide', bone: 'left_arm'},
+ {group: 'Right Arm Wide', variant: 'wide', bone: 'right_arm'},
+ {group: 'Left Arm Slim', variant: 'slim', bone: 'left_arm'},
+ {group: 'Right Arm Slim', variant: 'slim', bone: 'right_arm'},
+ {group: 'Left Leg', bone: 'left_leg'},
+ {group: 'Right Leg', bone: 'right_leg'}
+ ];
+ const convertedUuids = Object.create(null);
+ const converted = [];
+ partPlans.forEach(function (plan) {
+ directTemplateElementUuids(rawTemplate, plan.group).forEach(function (uuid) {
+ const cube = elementByUuid[uuid];
+ if (!cube || (cube.type && cube.type !== 'cube')) {
+ throw new Error('Simple template expected a cube in "' + plan.group + '".');
+ }
+ const result = preciseMeshFromCube(cube, {
+ visibility: !plan.variant || plan.variant === selectedVariant
+ });
+ result.bone = plan.bone;
+ convertedUuids[uuid] = true;
+ converted.push(result);
+ });
+ });
+
+ const bones = EMOTE_PLAYER_BONES.map(function (definition, index) {
+ const layout = simpleBoneLayout(definition.id);
+ return {
+ isOpen: true,
+ uuid: SIMPLE_BONE_UUIDS[definition.id],
+ type: 'armature_bone',
+ name: definition.label,
+ origin: layout.origin,
+ rotation: layout.rotation,
+ length: layout.length,
+ width: layout.width,
+ connected: layout.connected,
+ color: index % 8,
+ vertex_weights: {},
+ export: false,
+ visibility: true,
+ locked: false,
+ cosmiq_role: ROLES.PLAYER_BONE,
+ cosmiq_actor_index: 1,
+ cosmiq_player_bone: definition.id,
+ children: []
+ };
+ });
+ const boneBySemantic = Object.create(null);
+ bones.forEach(function (bone) { boneBySemantic[bone.cosmiq_player_bone] = bone; });
+ EMOTE_PLAYER_BONES.forEach(function (definition) {
+ if (definition.parent) {
+ boneBySemantic[definition.parent].children.push(SIMPLE_BONE_UUIDS[definition.id]);
+ }
+ });
+ converted.forEach(function (entry) {
+ const prefix = entry.mesh.uuid.slice(0, 6) + ':';
+ Object.keys(entry.mesh.vertices).forEach(function (vertexKey) {
+ boneBySemantic[entry.bone].vertex_weights[prefix + vertexKey] = 1;
+ });
+ });
+
+ const armature = {
+ isOpen: true,
+ uuid: SIMPLE_ARMATURE_UUID,
+ type: 'armature',
+ name: 'Simple Player Rig',
+ export: false,
+ visibility: true,
+ locked: false,
+ cosmiq_role: ROLES.STRUCTURE,
+ cosmiq_actor_index: 1,
+ cosmiq_player_bone: 'none',
+ children: converted.map(function (entry) { return entry.mesh.uuid; })
+ .concat([SIMPLE_BONE_UUIDS.root])
+ };
+ rawTemplate.elements = (rawTemplate.elements || []).filter(function (element) {
+ return !convertedUuids[element.uuid];
+ }).concat(converted.map(function (entry) { return entry.mesh; }), [armature], bones);
+
+ function removeConvertedEntries(entries) {
+ return (entries || []).reduce(function (output, entry) {
+ if (typeof entry === 'string') {
+ if (!convertedUuids[entry]) output.push(entry);
+ return output;
+ }
+ if (!entry || typeof entry !== 'object') return output;
+ entry.children = removeConvertedEntries(entry.children);
+ output.push(entry);
+ return output;
+ }, []);
+ }
+ rawTemplate.outliner = removeConvertedEntries(rawTemplate.outliner);
+ const referenceRoot = baseTemplateGroup(rawTemplate, 'Cosmiq Reference Model');
+ const referenceEntry = templateOutlinerEntry(rawTemplate.outliner, referenceRoot.uuid);
+ function boneOutlinerEntry(boneId) {
+ const definition = EMOTE_PLAYER_BONES.find(function (candidate) {
+ return candidate.id === boneId;
+ });
+ return {
+ uuid: SIMPLE_BONE_UUIDS[boneId],
+ isOpen: true,
+ children: EMOTE_PLAYER_BONES.filter(function (candidate) {
+ return candidate.parent === definition.id;
+ }).map(function (candidate) {
+ return boneOutlinerEntry(candidate.id);
+ })
+ };
+ }
+ referenceEntry.children.unshift({
+ uuid: SIMPLE_ARMATURE_UUID,
+ isOpen: true,
+ children: converted.map(function (entry) { return entry.mesh.uuid; })
+ .concat([boneOutlinerEntry('root')])
+ });
+ rawTemplate.cosmiq_rig_profile = EMOTE_RIG_PROFILES.SIMPLE;
+ return verifySimpleRigStructure(rawTemplate);
+ }
+
+ function preciseBoneLayout(boneId) {
+ const layouts = {
+ root: {
+ origin: [0, 0, 0],
+ rotation: [0, 0, 0],
+ length: 12,
+ width: 1,
+ connected: false
+ },
+ torso_lower: {
+ origin: [0, 12, 0],
+ rotation: [0, 0, 0],
+ length: 6,
+ width: 1.75,
+ connected: true
+ },
+ torso_upper: {
+ origin: [0, 6, 0],
+ rotation: [0, 0, 0],
+ length: 6,
+ width: 1.75,
+ connected: true
+ },
+ head: {
+ origin: [0, 6, 0],
+ rotation: [0, 0, 0],
+ length: 8,
+ width: 2,
+ connected: true
+ },
+ left_arm_upper: {
+ origin: [-5, 4, 0],
+ rotation: [0, 0, 0],
+ length: 4,
+ width: 1.25,
+ connected: false
+ },
+ left_arm_lower: {
+ origin: [0, -4, 0],
+ rotation: [0, 0, 0],
+ length: -6,
+ width: 1.25,
+ connected: true
+ },
+ right_arm_upper: {
+ origin: [5, 4, 0],
+ rotation: [0, 0, 0],
+ length: 4,
+ width: 1.25,
+ connected: false
+ },
+ right_arm_lower: {
+ origin: [0, -4, 0],
+ rotation: [0, 0, 0],
+ length: -6,
+ width: 1.25,
+ connected: true
+ },
+ left_leg_upper: {
+ origin: [-1.9, 0, 0],
+ rotation: [0, 0, 0],
+ length: 6,
+ width: 1.25,
+ connected: false
+ },
+ left_leg_lower: {
+ origin: [0, -6, 0],
+ rotation: [0, 0, 0],
+ length: -6,
+ width: 1.25,
+ connected: true
+ },
+ right_leg_upper: {
+ origin: [1.9, 0, 0],
+ rotation: [0, 0, 0],
+ length: 6,
+ width: 1.25,
+ connected: false
+ },
+ right_leg_lower: {
+ origin: [0, -6, 0],
+ rotation: [0, 0, 0],
+ length: -6,
+ width: 1.25,
+ connected: true
+ }
+ };
+ const layout = layouts[boneId];
+ return {
+ origin: layout.origin.slice(),
+ rotation: layout.rotation.slice(),
+ length: layout.length,
+ width: layout.width,
+ connected: layout.connected
+ };
+ }
+
+ function verifyPreciseRigStructure(rawTemplate) {
+ const elements = Array.isArray(rawTemplate && rawTemplate.elements)
+ ? rawTemplate.elements
+ : [];
+ const hierarchy = importedTreeParents(rawTemplate && rawTemplate.outliner);
+ const armatures = elements.filter(function (element) { return element.type === 'armature'; });
+ if (armatures.length !== 1 || armatures[0].uuid !== PRECISE_ARMATURE_UUID) {
+ throw new Error('Precise template requires exactly one Cosmiq player armature.');
+ }
+ const meshes = elements.filter(function (element) { return element.type === 'mesh'; });
+ const bones = elements.filter(function (element) { return element.type === 'armature_bone'; });
+ if (meshes.length !== 16 || bones.length !== EMOTE_PRECISE_PLAYER_BONES.length) {
+ throw new Error('Precise template requires 16 player meshes and 12 armature bones.');
+ }
+ const boneBySemantic = Object.create(null);
+ bones.forEach(function (bone) {
+ boneBySemantic[bone.cosmiq_player_bone] = bone;
+ });
+ EMOTE_PRECISE_PLAYER_BONES.forEach(function (definition) {
+ const bone = boneBySemantic[definition.id];
+ if (!bone) {
+ throw new Error('Precise template is missing armature bone ' + definition.id + '.');
+ }
+ const expectedParentUuid = definition.parent
+ ? boneBySemantic[definition.parent].uuid
+ : PRECISE_ARMATURE_UUID;
+ if (hierarchy.parentByUuid[bone.uuid] !== expectedParentUuid) {
+ throw new Error(
+ 'Precise template armature bone ' + definition.id +
+ ' must be parented to ' + (definition.parent || 'the player armature') + '.'
+ );
+ }
+ });
+ meshes.forEach(function (mesh) {
+ const prefix = mesh.uuid.slice(0, 6) + ':';
+ Object.keys(mesh.vertices || {}).forEach(function (vertexKey) {
+ const weightKey = prefix + vertexKey;
+ const influences = bones.map(function (bone) {
+ return Number(bone.vertex_weights && bone.vertex_weights[weightKey]) || 0;
+ }).filter(function (weight) { return weight > 0; });
+ const sum = influences.reduce(function (total, weight) { return total + weight; }, 0);
+ if (influences.length < 1 || influences.length > 2 || Math.abs(sum - 1) > 1e-9) {
+ throw new Error('Precise template has invalid weights for ' + mesh.name + ':' + vertexKey + '.');
+ }
+ });
+ });
+ return rawTemplate;
+ }
+
+ function convertToPreciseRig(rawTemplate, visibleBody) {
+ const selectedVariant = visibleBody === 'slim' ? 'slim' : 'wide';
+ const elementByUuid = Object.create(null);
+ (rawTemplate.elements || []).forEach(function (element) {
+ elementByUuid[element.uuid] = element;
+ });
+ const partPlans = [
+ {group: 'Head', fixedBone: 'head'},
+ {group: 'Body', jointY: 18, upperBone: 'torso_upper', lowerBone: 'torso_lower'},
+ {group: 'Left Arm Wide', variant: 'wide', jointY: 18, upperBone: 'left_arm_upper', lowerBone: 'left_arm_lower'},
+ {group: 'Right Arm Wide', variant: 'wide', jointY: 18, upperBone: 'right_arm_upper', lowerBone: 'right_arm_lower'},
+ {group: 'Left Arm Slim', variant: 'slim', jointY: 18, upperBone: 'left_arm_upper', lowerBone: 'left_arm_lower'},
+ {group: 'Right Arm Slim', variant: 'slim', jointY: 18, upperBone: 'right_arm_upper', lowerBone: 'right_arm_lower'},
+ {group: 'Left Leg', jointY: 6, upperBone: 'left_leg_upper', lowerBone: 'left_leg_lower'},
+ {group: 'Right Leg', jointY: 6, upperBone: 'right_leg_upper', lowerBone: 'right_leg_lower'}
+ ];
+ const convertedUuids = Object.create(null);
+ const converted = [];
+ partPlans.forEach(function (plan) {
+ directTemplateElementUuids(rawTemplate, plan.group).forEach(function (uuid) {
+ const cube = elementByUuid[uuid];
+ if (!cube || (cube.type && cube.type !== 'cube')) {
+ throw new Error('Precise template expected a cube in "' + plan.group + '".');
+ }
+ const result = preciseMeshFromCube(cube, {
+ jointY: plan.jointY,
+ visibility: !plan.variant || plan.variant === selectedVariant
+ });
+ result.plan = plan;
+ convertedUuids[uuid] = true;
+ converted.push(result);
+ });
+ });
+
+ const bones = EMOTE_PRECISE_PLAYER_BONES.map(function (definition, index) {
+ const layout = preciseBoneLayout(definition.id);
+ return {
+ isOpen: true,
+ uuid: PRECISE_BONE_UUIDS[definition.id],
+ type: 'armature_bone',
+ name: definition.id,
+ origin: layout.origin,
+ rotation: layout.rotation,
+ length: layout.length,
+ width: layout.width,
+ connected: layout.connected,
+ color: index % 8,
+ vertex_weights: {},
+ export: false,
+ visibility: true,
+ locked: false,
+ cosmiq_role: ROLES.PLAYER_BONE,
+ cosmiq_actor_index: 1,
+ cosmiq_player_bone: definition.id,
+ children: []
+ };
+ });
+ const boneBySemantic = Object.create(null);
+ bones.forEach(function (bone) { boneBySemantic[bone.cosmiq_player_bone] = bone; });
+ EMOTE_PRECISE_PLAYER_BONES.forEach(function (definition) {
+ if (definition.parent) {
+ boneBySemantic[definition.parent].children.push(PRECISE_BONE_UUIDS[definition.id]);
+ }
+ });
+
+ converted.forEach(function (entry) {
+ const mesh = entry.mesh;
+ const plan = entry.plan;
+ const prefix = mesh.uuid.slice(0, 6) + ':';
+ Object.keys(mesh.vertices).forEach(function (vertexKey) {
+ const weightKey = prefix + vertexKey;
+ if (plan.fixedBone) {
+ boneBySemantic[plan.fixedBone].vertex_weights[weightKey] = 1;
+ return;
+ }
+ const y = entry.vertexY[vertexKey];
+ const upperWeight = Math.max(0, Math.min(1, (y - (plan.jointY - 1)) / 2));
+ const lowerWeight = 1 - upperWeight;
+ if (upperWeight > 0) {
+ boneBySemantic[plan.upperBone].vertex_weights[weightKey] = upperWeight;
+ }
+ if (lowerWeight > 0) {
+ boneBySemantic[plan.lowerBone].vertex_weights[weightKey] = lowerWeight;
+ }
+ });
+ });
+
+ const armature = {
+ isOpen: true,
+ uuid: PRECISE_ARMATURE_UUID,
+ type: 'armature',
+ name: 'cosmiq_precise_player',
+ export: false,
+ visibility: true,
+ locked: false,
+ cosmiq_role: ROLES.STRUCTURE,
+ cosmiq_actor_index: 1,
+ cosmiq_player_bone: 'none',
+ children: converted.map(function (entry) { return entry.mesh.uuid; })
+ .concat([PRECISE_BONE_UUIDS.root])
+ };
+ rawTemplate.elements = (rawTemplate.elements || []).filter(function (element) {
+ return !convertedUuids[element.uuid];
+ }).concat(converted.map(function (entry) { return entry.mesh; }), [armature], bones);
+ (rawTemplate.groups || []).forEach(function (group) {
+ if (group.cosmiq_role === ROLES.PLAYER_BONE) {
+ group.cosmiq_role = ROLES.STRUCTURE;
+ group.cosmiq_actor_index = 0;
+ group.cosmiq_player_bone = 'none';
+ }
+ });
+
+ function removeConvertedEntries(entries) {
+ return (entries || []).reduce(function (output, entry) {
+ if (typeof entry === 'string') {
+ if (!convertedUuids[entry]) output.push(entry);
+ return output;
+ }
+ if (!entry || typeof entry !== 'object') return output;
+ entry.children = removeConvertedEntries(entry.children);
+ output.push(entry);
+ return output;
+ }, []);
+ }
+ rawTemplate.outliner = removeConvertedEntries(rawTemplate.outliner);
+ const referenceRoot = baseTemplateGroup(rawTemplate, 'Cosmiq Reference Model');
+ const referenceEntry = templateOutlinerEntry(rawTemplate.outliner, referenceRoot.uuid);
+ function boneOutlinerEntry(boneId) {
+ const definition = EMOTE_PRECISE_PLAYER_BONES.find(function (candidate) {
+ return candidate.id === boneId;
+ });
+ return {
+ uuid: PRECISE_BONE_UUIDS[boneId],
+ isOpen: true,
+ children: EMOTE_PRECISE_PLAYER_BONES.filter(function (candidate) {
+ return candidate.parent === definition.id;
+ }).map(function (candidate) {
+ return boneOutlinerEntry(candidate.id);
+ })
+ };
+ }
+ referenceEntry.children.unshift({
+ uuid: PRECISE_ARMATURE_UUID,
+ isOpen: true,
+ children: converted.map(function (entry) { return entry.mesh.uuid; })
+ .concat([boneOutlinerEntry('root')])
+ });
+ rawTemplate.cosmiq_rig_profile = EMOTE_RIG_PROFILES.PRECISE;
+ return verifyPreciseRigStructure(rawTemplate);
+ }
+
+ function annotateBaseTemplate(rawInput, kind, settings) {
+ const rawTemplate = verifyBaseTemplateStructure(JSON.parse(JSON.stringify(rawInput)));
+ const options = settings || {};
+ const visibleBody = options.visibleBody === 'slim' ? 'slim' : 'wide';
+ const rigProfile = options.rigProfile === EMOTE_RIG_PROFILES.PRECISE
+ ? EMOTE_RIG_PROFILES.PRECISE
+ : EMOTE_RIG_PROFILES.SIMPLE;
+ const resolution = capeResolutionFromId(options.resolution);
+ const creationName = normalizeCreationName(options.name);
+ rawTemplate.meta = Object.assign({}, rawTemplate.meta, {model_format: FORMAT_ID});
+ rawTemplate.name = creationProjectName(creationName, kind);
+ rawTemplate.cosmiq_project_kind = kind;
+ rawTemplate.cosmiq_project_id = creationProjectId(creationName, kind);
+ rawTemplate.cosmiq_template = kind === PROJECT_KINDS.EMOTE
+ ? 'emote_player'
+ : kind === PROJECT_KINDS.CAPE ? 'cape' : 'cosmetic_animated';
+ rawTemplate.cosmiq_compatibility_profile = 'cross_version';
+ rawTemplate.cosmiq_slot = kind === PROJECT_KINDS.COSMETIC ? 'cosmiq:slot/body' : '';
+ rawTemplate.cosmiq_body_slots = '';
+ rawTemplate.cosmiq_model_variant = 'universal';
+ rawTemplate.cosmiq_arm_width_mode = ARM_WIDTH_MODES.UNCONFIGURED;
+ rawTemplate.cosmiq_source_revision = 1;
+ rawTemplate.cosmiq_emote_actor_count = 1;
+ rawTemplate.cosmiq_emote_contains_props = false;
+ rawTemplate.cosmiq_emote_traversable = false;
+ rawTemplate.cosmiq_rig_profile = kind === PROJECT_KINDS.EMOTE
+ ? rigProfile
+ : EMOTE_RIG_PROFILES.SIMPLE;
+ rawTemplate.cosmiq_camera_x = 0;
+ rawTemplate.cosmiq_camera_y = 1;
+ rawTemplate.cosmiq_camera_z = kind === PROJECT_KINDS.EMOTE ? -5 : -3;
+ rawTemplate.cosmiq_camera_pitch = 0;
+ rawTemplate.cosmiq_camera_yaw = 0;
+ rawTemplate.cosmiq_camera_roll = 0;
+ rawTemplate.cosmiq_camera_fov = 35;
+ rawTemplate.cosmiq_camera_clip_id = '';
+ rawTemplate.cosmiq_camera_time_ms = 0;
+ rawTemplate.cosmiq_camera_model_variant = kind === PROJECT_KINDS.COSMETIC
+ ? visibleBody
+ : 'universal';
+ rawTemplate.cosmiq_camera_visible_props = '';
+ rawTemplate.resolution = kind === PROJECT_KINDS.CAPE
+ ? {width: resolution.width, height: resolution.height}
+ : {width: 64, height: 64};
+
+ (rawTemplate.groups || []).forEach(function (group) {
+ group.cosmiq_role = ROLES.STRUCTURE;
+ group.cosmiq_anchor = 'none';
+ group.cosmiq_model_branch = 'none';
+ group.cosmiq_actor_index = 0;
+ group.cosmiq_player_bone = 'none';
+ group.export = false;
+ });
+ (rawTemplate.elements || []).forEach(function (element) {
+ element.cosmiq_role = ROLES.REFERENCE;
+ element.cosmiq_actor_index = 0;
+ element.cosmiq_player_bone = 'none';
+ element.export = false;
+ element.locked = true;
+ });
+ (rawTemplate.textures || []).forEach(function (texture) {
+ texture.cosmiq_role = ROLES.REFERENCE;
+ });
+
+ const rootGroup = baseTemplateGroup(rawTemplate, 'Cosmiq Reference Model');
+ const wideGroup = baseTemplateGroup(rawTemplate, 'Wide');
+ const slimGroup = baseTemplateGroup(rawTemplate, 'Slim');
+ const capeRoot = baseTemplateGroup(rawTemplate, 'Cape/Elytra');
+ wideGroup.visibility = visibleBody === 'wide';
+ slimGroup.visibility = visibleBody === 'slim';
+ wideGroup.cosmiq_model_branch = 'wide';
+ slimGroup.cosmiq_model_branch = 'slim';
+ capeRoot.visibility = kind === PROJECT_KINDS.CAPE;
+ capeRoot.cosmiq_role = ROLES.REFERENCE_ROOT;
+
+ if (kind === PROJECT_KINDS.COSMETIC) {
+ rootGroup.cosmiq_role = ROLES.EXPORT_ROOT;
+ rootGroup.export = true;
+ wideGroup.cosmiq_role = ROLES.MODEL_VARIANT;
+ slimGroup.cosmiq_role = ROLES.MODEL_VARIANT;
+ TEMPLATE_ATTACHMENT_POINTS.forEach(function (point) {
+ const bodyPart = baseTemplateGroup(rawTemplate, point.bodyPartGroup);
+ bodyPart.cosmiq_role = ROLES.ANCHOR;
+ bodyPart.cosmiq_anchor = point.anchorId;
+ const modelGroup = baseTemplateGroup(rawTemplate, point.modelGroup);
+ modelGroup.cosmiq_role = ROLES.AUTHORING;
+ modelGroup.export = true;
+ });
+ } else if (kind === PROJECT_KINDS.EMOTE) {
+ addEmoteTemplateWrappers(rawTemplate, rootGroup);
+ rawTemplate.animations = initialEmoteTemplateAnimations();
+ if (rigProfile === EMOTE_RIG_PROFILES.PRECISE) {
+ convertToPreciseRig(rawTemplate, visibleBody);
+ } else {
+ convertToSimpleRig(rawTemplate, visibleBody);
+ }
+ attachTemplateCapeToUpperTorso(rawTemplate, rigProfile);
+ } else {
+ rawTemplate.animations = [];
+ rootGroup.cosmiq_role = ROLES.EXPORT_ROOT;
+ rootGroup.export = true;
+ capeRoot.cosmiq_role = ROLES.AUTHORING;
+ baseTemplateGroup(rawTemplate, 'Cape').cosmiq_role = ROLES.AUTHORING;
+ const capeTexture = (rawTemplate.textures || []).find(function (texture) {
+ return texture.name === 'cape.png';
+ });
+ const capeTextureIndex = rawTemplate.textures.indexOf(capeTexture);
+ capeTexture.cosmiq_role = ROLES.NONE;
+ capeTexture.uv_width = resolution.width;
+ capeTexture.uv_height = resolution.height;
+ scaleTemplateTextureUvs(rawTemplate, capeTextureIndex, resolution.width / 64);
+ }
+
+ (rawTemplate.animations || []).forEach(function (animation) {
+ const trigger = baseAnimationTrigger(animation.name);
+ const emoteActorMatch = /^actor\.(\d+)\./.exec(String(animation.name || ''));
+ animation.cosmiq_clip_id = runtimeId(
+ rawTemplate.cosmiq_project_id,
+ 'animation',
+ animation.uuid
+ );
+ animation.cosmiq_trigger_kind = kind === PROJECT_KINDS.COSMETIC && trigger
+ ? trigger.kind
+ : 'none';
+ animation.cosmiq_trigger = kind === PROJECT_KINDS.COSMETIC && trigger ? trigger.id : '';
+ animation.cosmiq_transition_ms = 150;
+ animation.cosmiq_priority = 100;
+ animation.cosmiq_fallback = kind === PROJECT_KINDS.COSMETIC && trigger && trigger.id === 'idle';
+ animation.cosmiq_emote_scope = kind === PROJECT_KINDS.EMOTE
+ ? (emoteActorMatch ? EMOTE_ANIMATION_SCOPES.ACTOR : EMOTE_ANIMATION_SCOPES.SCENE)
+ : EMOTE_ANIMATION_SCOPES.NONE;
+ animation.cosmiq_emote_actor_index = kind === PROJECT_KINDS.EMOTE && emoteActorMatch
+ ? Math.floor(Number(emoteActorMatch[1]))
+ : 0;
+ });
+ return rawTemplate;
+ }
+
+ function scaleTemplateTextureUvs(rawTemplate, textureIndex, scale) {
+ if (!Number.isInteger(textureIndex) || textureIndex < 0 || !Number.isFinite(scale) || scale === 1) {
+ return rawTemplate;
+ }
+ (rawTemplate.elements || []).forEach(function (element) {
+ const faces = element && element.faces || {};
+ let usesTexture = false;
+ Object.keys(faces).forEach(function (faceKey) {
+ const face = faces[faceKey];
+ if (!face || String(face.texture) !== String(textureIndex)) return;
+ usesTexture = true;
+ if (Array.isArray(face.uv)) {
+ face.uv = face.uv.map(function (coordinate) {
+ return Number(coordinate) * scale;
+ });
+ }
+ });
+ if (usesTexture && Array.isArray(element.uv_offset)) {
+ element.uv_offset = element.uv_offset.map(function (coordinate) {
+ return Number(coordinate) * scale;
+ });
+ }
+ });
+ return rawTemplate;
+ }
+
+ function resizeTemplateTexture(source, width, height) {
+ if (width === 64 && height === 32) return Promise.resolve(source);
+ if (typeof Image === 'undefined' || typeof document === 'undefined') {
+ return Promise.reject(new Error('This Blockbench build cannot resize the cape template texture.'));
+ }
+ return new Promise(function (resolve, reject) {
+ const image = new Image();
+ image.onload = function () {
+ const canvas = document.createElement('canvas');
+ canvas.width = width;
+ canvas.height = height;
+ const context = canvas.getContext('2d');
+ context.imageSmoothingEnabled = false;
+ context.drawImage(image, 0, 0, width, height);
+ resolve(canvas.toDataURL('image/png'));
+ };
+ image.onerror = function () {
+ reject(new Error('The embedded cape texture in the base template could not be resized.'));
+ };
+ image.src = source;
+ });
+ }
+
+ async function startBaseTemplateProject(kind, settings) {
+ const options = settings || {};
+ const pristineTemplate = await loadBaseTemplateJson();
+ const rawTemplate = annotateBaseTemplate(pristineTemplate, kind, options);
+ if (kind === PROJECT_KINDS.CAPE) {
+ const resolution = capeResolutionFromId(options.resolution);
+ const capeTexture = rawTemplate.textures.find(function (texture) {
+ return texture.name === 'cape.png';
+ });
+ capeTexture.source = await resizeTemplateTexture(
+ capeTexture.source,
+ resolution.width,
+ resolution.height
+ );
+ capeTexture.width = resolution.width;
+ capeTexture.height = resolution.height;
+ }
+ if (typeof Codecs === 'undefined' || !Codecs.project || typeof Codecs.project.load !== 'function') {
+ throw new Error('This Blockbench build cannot open the bundled base template.');
+ }
+ Codecs.project.load(rawTemplate, {path: ''});
+ refreshMetadata(true);
+ if (typeof Validator !== 'undefined') Validator.validate();
+ const selectionName = kind === PROJECT_KINDS.COSMETIC
+ ? (options.visibleBody === 'slim' ? '[Model SR]' : '[Model WR]')
+ : kind === PROJECT_KINDS.CAPE ? 'Cape' : 'Body';
+ if (typeof Group !== 'undefined' && Array.isArray(Group.all)) {
+ const selection = Group.all.find(function (group) { return group.name === selectionName; });
+ if (selection && typeof selection.select === 'function') selection.select();
+ }
+ if (typeof Blockbench !== 'undefined') {
+ Blockbench.showQuickMessage(kind === PROJECT_KINDS.CAPE
+ ? '[COMING SOON] Cape workspace created. Save the .bbmodel locally; upload is not available yet.'
+ : kind === PROJECT_KINDS.EMOTE
+ ? 'Created an Emote workspace from CosmiqPlayerTemplate.bbmodel.'
+ : 'Created an Accessory workspace from CosmiqPlayerTemplate.bbmodel.');
+ }
+ return true;
+ }
+
+ function installCreatorStyles() {
+ if (typeof document === 'undefined' || !document.head) {
+ return;
+ }
+ const existing = document.getElementById('cosmiq-creator-styles');
+ if (existing) {
+ pluginStyle = existing;
+ return;
+ }
+ pluginStyle = document.createElement('style');
+ pluginStyle.id = 'cosmiq-creator-styles';
+ pluginStyle.textContent = [
+ '.cosmiq-creation-picker{padding:26px;background:var(--color-back,#15161b);color:var(--color-text,#eee)}',
+ '.cosmiq-creation-header{display:block!important;text-align:center;margin-bottom:22px}',
+ '.cosmiq-creation-header h1,.cosmiq-creation-header p{display:block;width:auto}',
+ '.cosmiq-creation-header h1{margin:0 0 6px;font-size:27px;font-weight:400;line-height:1.2}',
+ '.cosmiq-creation-header p{margin:0;color:var(--color-subtle_text,#aaa);line-height:1.4}',
+ '.cosmiq-creation-grid{display:grid;grid-template-columns:repeat(2,minmax(0,1fr));gap:16px}',
+ '.cosmiq-creation-card{box-sizing:border-box;display:flex;min-width:0;min-height:245px;height:auto!important;max-height:none!important;flex-direction:column;align-items:stretch;margin:0;padding:0 0 16px;overflow:hidden;border:2px solid var(--color-border,#373a45);border-radius:8px;outline:none;background:var(--color-ui,#23252d);color:var(--color-text,#eee);text-align:left;cursor:pointer;transition:transform .12s,border-color .12s,background-color .12s}',
+ '.cosmiq-creation-card:hover{transform:translateY(-2px);border-color:var(--color-subtle_text,#777);background:var(--color-button,#2d3039)}',
+ '.cosmiq-creation-card:focus-visible{border-color:var(--color-accent,#f36b91);box-shadow:0 0 0 2px color-mix(in srgb,var(--color-accent,#f36b91) 40%,transparent)}',
+ '.cosmiq-creation-card.selected{border-color:var(--color-accent,#f36b91);box-shadow:0 0 0 1px var(--color-accent,#f36b91)}',
+ '.cosmiq-creation-image{position:relative;display:block;flex:none;width:100%;height:165px;overflow:hidden;background:#20242d}',
+ '.cosmiq-creation-image img{display:block;width:100%;height:100%;object-fit:cover}',
+ '.cosmiq-creation-image>.material-icons{position:absolute;right:10px;top:10px;display:grid!important;box-sizing:border-box;width:33px!important;height:33px!important;min-width:33px;min-height:33px;padding:3px!important;place-items:center;border-radius:50%;background:#fff;color:var(--color-accent,#f36b91);font-size:27px;line-height:1!important}',
+ '.cosmiq-creation-copy{display:flex;flex:1;flex-direction:column;gap:5px;padding:14px 16px 0}',
+ '.cosmiq-coming-soon{align-self:flex-start;padding:4px 8px;border:2px solid #ffd873;border-radius:4px;color:#ffd873;font-size:17px;letter-spacing:.06em;line-height:1}',
+ '.cosmiq-creation-copy strong{font-size:20px;font-weight:500}',
+ '.cosmiq-creation-copy small{color:var(--color-subtle_text,#aaa);font-size:12px;line-height:1.4}',
+ '.cosmiq-creation-hint{display:flex;align-items:center;justify-content:center;gap:6px;margin:18px 0 0;color:var(--color-subtle_text,#999);font-size:12px}',
+ '.cosmiq-creation-hint i{font-size:17px}',
+ '#cosmiq_creation_type .button_bar,#cosmiq_creation_name .button_bar,#cosmiq_cosmetic_body_type .button_bar,#cosmiq_emote_rig_type .button_bar,#cosmiq_emote_review .button_bar,#cosmiq_cape_resolution .button_bar,#cosmiq_emote_actors .button_bar,#cosmiq_emote_props .button_bar,#cosmiq_emote_traversable .button_bar{display:flex!important}',
+ '#cosmiq_creation_type .button_bar .confirm_btn,#cosmiq_creation_name .button_bar .confirm_btn,#cosmiq_cosmetic_body_type .button_bar .confirm_btn,#cosmiq_emote_rig_type .button_bar .confirm_btn,#cosmiq_emote_review .button_bar .confirm_btn,#cosmiq_cape_resolution .button_bar .confirm_btn,#cosmiq_emote_actors .button_bar .confirm_btn,#cosmiq_emote_props .button_bar .confirm_btn,#cosmiq_emote_traversable .button_bar .confirm_btn{margin-left:auto!important}',
+ '.cosmiq-simple-wizard{margin:0 0 18px;padding:18px 20px;border-bottom:1px solid var(--color-border,#343640);background:var(--color-back,#15161b);color:var(--color-text,#eee)}',
+ '.cosmiq-wizard-progress{display:grid;grid-template-columns:repeat(var(--cosmiq-step-count,3),minmax(0,1fr));gap:8px;margin-bottom:18px}',
+ '.cosmiq-wizard-step{position:relative;display:flex;min-width:0;align-items:center;gap:7px;color:var(--color-subtle_text,#999)}',
+ '.cosmiq-wizard-step:not(:last-child):after{position:absolute;z-index:0;right:-6px;left:32px;top:13px;height:2px;background:var(--color-border,#424552);content:""}',
+ '.cosmiq-wizard-step span{z-index:1;display:flex;width:27px;height:27px;flex:none;align-items:center;justify-content:center;border:2px solid var(--color-border,#545866);border-radius:50%;background:var(--color-ui,#252830);font-size:12px;font-weight:700}',
+ '.cosmiq-wizard-step span i{font-size:14px}.cosmiq-wizard-step span b{font-size:12px}',
+ '.cosmiq-wizard-step small{z-index:1;overflow:hidden;padding-right:4px;background:var(--color-back,#15161b);text-overflow:ellipsis;white-space:nowrap;font-size:11px}',
+ '.cosmiq-wizard-step.current{color:var(--color-text,#fff)}',
+ '.cosmiq-wizard-step.current span{border-color:var(--color-accent,#f36b91);background:var(--color-accent,#f36b91);color:var(--color-accent_text,#fff)}',
+ '.cosmiq-wizard-step.complete span{border-color:var(--color-accent,#f36b91);color:var(--color-accent,#f36b91)}',
+ '.cosmiq-wizard-step.locked{opacity:.68}',
+ '.cosmiq-wizard-heading{display:block!important;text-align:left}',
+ '.cosmiq-wizard-heading h2{display:block;margin:0 0 4px;font-size:23px;font-weight:400;line-height:1.2}',
+ '.cosmiq-wizard-heading p{display:block;margin:0;color:var(--color-subtle_text,#aaa);line-height:1.4}',
+ '.cosmiq-cape-coming-soon{margin:16px 20px;padding:20px;border:3px solid #ffd873;border-radius:8px;background:#423714;text-align:center}.cosmiq-cape-coming-soon>strong{display:block;color:#ffd873;font-size:30px;letter-spacing:.08em}.cosmiq-cape-coming-soon h2{margin:8px 0 5px}.cosmiq-cape-coming-soon p{margin:0;color:#f6e8b0}',
+ '.cosmiq-emote-intro{display:flex;align-items:center;gap:16px;margin:4px 0 16px}',
+ '.cosmiq-emote-intro img{width:150px;max-width:36%;border:1px solid var(--color-border,#373a45);border-radius:6px}',
+ '.cosmiq-emote-intro h2{margin:0 0 5px;font-weight:400}.cosmiq-emote-intro p{margin:0;color:var(--color-subtle_text,#aaa)}',
+ '.cosmiq-emote-summary{margin:0 20px 16px;padding:12px;border:1px solid var(--color-border,#343640);border-radius:5px;background:var(--color-ui,#23252d);text-align:center}',
+ '.cosmiq-emote-review{display:grid;gap:10px;margin:0 20px 20px}.cosmiq-emote-review>div{display:flex;flex-direction:column;gap:4px;padding:14px 16px;border:1px solid var(--color-border,#3c3f49);border-radius:6px;background:var(--color-ui,#25272f)}.cosmiq-emote-review span{color:var(--color-subtle_text,#aaa);font-size:11px;text-transform:uppercase;letter-spacing:.06em}.cosmiq-emote-review strong{font-size:18px;font-weight:500}.cosmiq-emote-review small{color:var(--color-subtle_text,#aaa);line-height:1.4}',
+ '.cosmiq-card-progress{margin:-8px 0 24px;padding-bottom:4px;border-bottom:1px solid var(--color-border,#343640)}',
+ '.cosmiq-export-review{padding:8px 18px 12px}.cosmiq-export-review h2{margin:16px 0 9px;font-size:19px;font-weight:500}',
+ '.cosmiq-export-models{display:grid;grid-template-columns:1fr 1fr;gap:10px}.cosmiq-export-models>div{display:flex;flex-direction:column;gap:3px;padding:13px;border:1px solid var(--color-border,#3c3f49);border-radius:5px;background:var(--color-ui,#25272f)}',
+ '.cosmiq-export-models span,.cosmiq-export-review>p,.cosmiq-export-review li{color:var(--color-subtle_text,#aaa)}.cosmiq-export-review ul{margin:5px 0 8px;padding-left:23px}',
+ '.cosmiq-export-ready,.cosmiq-export-warning{display:flex;align-items:center;gap:7px;padding:9px 11px;border-radius:4px}.cosmiq-export-ready{background:#213d32;color:#70e3aa!important}.cosmiq-export-warning{background:#512a33;color:#ff9bb1!important}.cosmiq-export-ready i,.cosmiq-export-warning i{font-size:19px}',
+ '.cosmiq-export-confirm{padding:20px 22px;color:var(--color-text,#eee);text-align:center}.cosmiq-export-confirm h1{margin:0 0 5px;font-size:27px;font-weight:400}.cosmiq-export-confirm>p{margin:0 auto 14px;color:var(--color-subtle_text,#aaa)}',
+ '.cosmiq-attachment-diagram{display:block;width:210px;max-width:65%;margin:14px auto 12px}.cosmiq-attachment-diagram .part{fill:#434957;stroke:#858da1;stroke-width:4}.cosmiq-attachment-diagram .selected{fill:var(--color-accent,#f36b91);stroke:#fff;stroke-width:6;filter:drop-shadow(0 0 7px rgba(243,107,145,.72))}',
+ '.cosmiq-animation-detection{display:inline-block;margin:0 0 15px;padding:5px 10px;border-radius:999px;background:var(--color-ui,#292c35);color:var(--color-subtle_text,#bbb);font-size:12px}',
+ '.cosmiq-copy-diagnostic{display:inline-flex;align-items:center;gap:6px;margin:0 auto 17px;padding:8px 13px;border:1px solid var(--color-border,#4a4e5a);border-radius:5px;background:var(--color-button,#30333c);color:var(--color-text,#eee);cursor:pointer}.cosmiq-copy-diagnostic i{font-size:18px}.cosmiq-copy-status{min-height:18px;margin:-10px 0 12px!important;color:#70e3aa!important;font-size:12px}',
+ '.cosmiq-export-advanced{border:1px solid var(--color-border,#3b3e48);border-radius:6px;background:var(--color-back,#17181d);text-align:left}.cosmiq-export-advanced>summary{padding:12px 14px;cursor:pointer;font-weight:500}.cosmiq-export-advanced[open]>summary{border-bottom:1px solid var(--color-border,#3b3e48)}',
+ '.cosmiq-format-page{display:flex;height:100%;min-height:0;flex-direction:column;color:var(--color-text,#eee)}',
+ '.cosmiq-format-page .format_target{margin-bottom:6px}',
+ '.cosmiq-format-content{display:flex;min-height:0;flex:1;flex-direction:column;overflow-y:auto}',
+ '.cosmiq-format-banner-link{display:block;margin:14px 0 4px;border-radius:5px;outline:none}',
+ '.cosmiq-format-banner-link:focus-visible{box-shadow:0 0 0 2px var(--color-accent,#f36b91)}',
+ '.cosmiq-format-banner{display:block;width:100%;max-height:180px;object-fit:contain}',
+ '.cosmiq-format-tagline{margin:0 20px 15px;color:var(--color-subtle_text,#aaa);text-align:center;line-height:1.4}',
+ '.cosmiq-format-notes{margin:0 0 8px;padding:0 8px}',
+ '.cosmiq-format-notes h2{margin:5px 0 8px;font-size:21px;font-weight:400}',
+ '.cosmiq-format-notes ul{margin:0;padding-left:25px}',
+ '.cosmiq-format-notes li{margin:0 0 7px;line-height:1.45}',
+ '.cosmiq-format-links{display:flex;flex-wrap:wrap;justify-content:space-around;gap:6px;margin-top:auto;padding:10px 8px 8px}',
+ '.cosmiq-format-links>a{display:flex;width:120px;min-height:82px;flex-direction:column;align-items:center;justify-content:center;gap:7px;padding:6px;border-radius:5px;color:var(--color-subtle_text,#aaa);text-align:center;text-decoration:none}',
+ '.cosmiq-format-links>a:hover{background:var(--color-button,#2d3039);color:var(--color-text,#fff)}',
+ '.cosmiq-format-links>a>i,.cosmiq-material-icon-frame{display:inline-flex!important;box-sizing:content-box;width:44px!important;min-width:44px;max-width:none!important;height:40px!important;min-height:40px;flex:none;align-items:center;justify-content:center;overflow:visible!important;font-size:32px!important;line-height:1!important;text-align:center}',
+ '.cosmiq-material-icon-frame>i.material-icons.icon{display:block!important;box-sizing:content-box!important;width:44px!important;min-width:44px!important;max-width:none!important;height:40px!important;margin:0!important;padding:0!important;overflow:visible!important;font-size:32px!important;line-height:40px!important;text-align:center!important;white-space:nowrap}',
+ '.cosmiq-format-links>a>i.cosmiq-about-website{color:#20e08f!important}',
+ '.cosmiq-format-links>a>i.cosmiq-about-discord{color:#5865f2!important}',
+ '.cosmiq-format-links>a>i.cosmiq-about-guides{color:#8b5cf6!important}',
+ '.cosmiq-format-links>a>i.cosmiq-about-youtube{color:#ff3b3b!important}',
+ '.cosmiq-format-links>a>span:not(.cosmiq-material-icon-frame){line-height:1.25}',
+ '.cosmiq-format-page .button_bar{margin-top:12px;margin-bottom:24px}',
+ '.cosmiq-format-actions{display:grid;gap:6px}',
+ '.cosmiq-format-actions>button{display:flex;width:100%;min-height:40px;align-items:center;justify-content:center;gap:7px;margin:0;font-size:15px}',
+ '.cosmiq-format-actions>button>i{font-size:20px}',
+ '@media(max-width:620px){.cosmiq-creation-grid{grid-template-columns:1fr}.cosmiq-creation-picker{padding:18px}.cosmiq-creation-card{min-height:220px}.cosmiq-creation-image{height:140px}.cosmiq-wizard-step{justify-content:center}.cosmiq-wizard-step small{display:none}.cosmiq-wizard-step:not(:last-child):after{right:-12px;left:50%}}'
+ ].join('');
+ document.head.appendChild(pluginStyle);
+ }
+
+ function normalizeBodySlots(value) {
+ const normalizedSlots = [];
+ listValues(value).forEach(function (slot) {
+ const normalizedSlot = String(slot == null ? '' : slot).trim().toLowerCase();
+ if (normalizedSlot) normalizedSlots.push(normalizedSlot);
+ });
+ return uniqueValues(normalizedSlots);
+ }
+
+ function canonicalAnchorId(value) {
+ return String(value == null ? '' : value).trim();
+ }
+
+ function selectedAnchorIdsFromForm(form) {
+ const values = form || {};
+ return BODY_PART_FIELDS.filter(function (part) {
+ return values[part.formKey] === true;
+ }).map(function (part) {
+ return part.anchorId;
+ });
+ }
+
+ function newCosmeticAnchorIds() {
+ return ALL_BODY_PART_ANCHOR_IDS.slice();
+ }
+
+ function bodySlotsForAnchors(anchorIds) {
+ return uniqueValues(listValues(anchorIds).map(function (anchorId) {
+ return anchorById[canonicalAnchorId(anchorId)] || null;
+ }).filter(Boolean).map(function (anchor) {
+ return anchor.slot;
+ }));
+ }
+
+ function bodySlotsFromNodeBindings(nodeBindings) {
+ const usedAnchors = Object.create(null);
+ Object.keys(nodeBindings || {}).forEach(function (nodeUuid) {
+ const binding = nodeBindings[nodeUuid];
+ if (binding && isKnownAnchor(binding.anchorId)) {
+ usedAnchors[binding.anchorId] = true;
+ }
+ });
+ return bodySlotsForAnchors(BODY_PART_FIELDS.map(function (part) {
+ return part.anchorId;
+ }).filter(function (anchorId) {
+ return usedAnchors[anchorId] === true;
+ }));
+ }
+
+ function modelVariantFromNodeBindings(nodeBindings, fallback) {
+ let hasWide = false;
+ let hasSlim = false;
+ Object.keys(nodeBindings || {}).forEach(function (nodeUuid) {
+ const binding = nodeBindings[nodeUuid];
+ if (!binding || binding.hasGeometry !== true) {
+ return;
+ }
+ hasWide = hasWide || binding.modelVariant === 'wide';
+ hasSlim = hasSlim || binding.modelVariant === 'slim';
+ });
+ if (hasWide && hasSlim) {
+ return 'universal';
+ }
+ if (hasSlim) {
+ return 'slim';
+ }
+ if (hasWide) {
+ return 'wide';
+ }
+ return MODEL_VARIANTS.indexOf(fallback) === -1 ? 'universal' : fallback;
+ }
+
+ function numberOrDefault(value, fallback) {
+ const number = Number(value);
+ return Number.isFinite(number) ? number : fallback;
+ }
+
+ function bodySlotsForProject(project) {
+ const bodySlots = normalizeBodySlots(project.bodySlots);
+ if (bodySlots.length > 0) {
+ return bodySlots;
+ }
+ const primarySlot = String(project.slotId || '').trim().toLowerCase();
+ return [primarySlot || 'cosmiq:slot/body'];
+ }
+
+ function defaultTextureVariants() {
+ return [{id: 'default', replacements: {}}];
+ }
+
+ function inferredTextureVariantId(name, fallbackIndex) {
+ const stem = String(name || '')
+ .replace(/^.*[\\/]/, '')
+ .replace(/\.png$/i, '')
+ .replace(/^varient/i, 'variant')
+ .toLowerCase()
+ .replace(/[^a-z0-9._-]+/g, '_')
+ .replace(/^[._-]+|[._-]+$/g, '');
+ if (/^[a-z][a-z0-9._-]{0,63}$/.test(stem) && stem !== 'default') {
+ return stem;
+ }
+ return 'variant_' + fallbackIndex;
+ }
+
+ function textureVariantsFromTextures(textures, nodes) {
+ const sourceTextures = Array.isArray(textures) ? textures : [];
+ const variants = defaultTextureVariants();
+ const byId = Object.create(null);
+ const explicitlyConfiguredTextureUuids = Object.create(null);
+ sourceTextures.forEach(function (texture) {
+ const id = String(texture.variantId || '').trim().toLowerCase();
+ if (!id) return;
+ let variant = byId[id];
+ if (!variant) {
+ variant = {
+ id: id,
+ replacements: {}
+ };
+ byId[id] = variant;
+ variants.push(variant);
+ }
+ variant.replacements[String(texture.variantBaseUuid || '')] = String(texture.uuid || '');
+ explicitlyConfiguredTextureUuids[String(texture.uuid || '')] = true;
+ });
+
+ // Accessory artists usually import color PNGs beside the texture already
+ // used by their model. Treat unused, same-size PNGs as variants without
+ // requiring every texture to be configured in Blockbench. Template skins
+ // and particle atlases are intentionally excluded.
+ if (!Array.isArray(nodes) || variants.length >= 10) {
+ return variants;
+ }
+ const usageByTextureUuid = Object.create(null);
+ nodes.forEach(function (node) {
+ if (!node || node.export === false || Number(node.triangleCount) <= 0 ||
+ node.role === ROLES.REFERENCE || node.role === ROLES.PARTICLE_MARKER) {
+ return;
+ }
+ (Array.isArray(node.textureUuids) ? node.textureUuids : []).forEach(function (uuid) {
+ const key = String(uuid || '');
+ if (key) usageByTextureUuid[key] = (usageByTextureUuid[key] || 0) + 1;
+ });
+ });
+ const candidatesByUuid = Object.create(null);
+ sourceTextures.forEach(function (texture, index) {
+ const uuid = String(texture && texture.uuid || '');
+ candidatesByUuid[uuid] = {
+ texture: texture,
+ index: index,
+ usage: usageByTextureUuid[uuid] || 0
+ };
+ });
+ const baseCandidates = Object.keys(usageByTextureUuid).map(function (uuid) {
+ return candidatesByUuid[uuid];
+ }).filter(function (entry) {
+ return entry && entry.texture && entry.texture.role !== ROLES.REFERENCE &&
+ entry.texture.role !== 'particle_sprite' &&
+ !explicitlyConfiguredTextureUuids[String(entry.texture.uuid || '')];
+ }).sort(function (left, right) {
+ const leftArea = Number(left.texture.width) * Number(left.texture.height);
+ const rightArea = Number(right.texture.width) * Number(right.texture.height);
+ return right.usage - left.usage || rightArea - leftArea || left.index - right.index;
+ });
+ const base = baseCandidates[0] && baseCandidates[0].texture;
+ if (!base || Number(base.width) <= 0 || Number(base.height) <= 0) {
+ return variants;
+ }
+ const reservedNames = {wide: true, slim: true, cape: true};
+ const usedIds = Object.create(null);
+ variants.forEach(function (variant) { usedIds[variant.id] = true; });
+ sourceTextures.map(function (texture, index) {
+ return {texture: texture, index: index};
+ }).filter(function (entry) {
+ const texture = entry.texture || {};
+ const fileName = String(texture.name || '').replace(/^.*[\\/]/, '').replace(/\.png$/i, '').toLowerCase();
+ const uuid = String(texture.uuid || '');
+ return uuid && uuid !== String(base.uuid || '') &&
+ !reservedNames[fileName] &&
+ texture.role !== ROLES.REFERENCE &&
+ texture.role !== 'particle_sprite' &&
+ !texture.variantId &&
+ !usageByTextureUuid[uuid] &&
+ !explicitlyConfiguredTextureUuids[uuid] &&
+ Number(texture.width) === Number(base.width) &&
+ Number(texture.height) === Number(base.height);
+ }).sort(function (left, right) {
+ const leftNamed = /^(?:variant|varient)/i.test(String(left.texture.name || '')) ? 0 : 1;
+ const rightNamed = /^(?:variant|varient)/i.test(String(right.texture.name || '')) ? 0 : 1;
+ return leftNamed - rightNamed || left.index - right.index;
+ }).slice(0, 10 - variants.length).forEach(function (entry, index) {
+ let id = inferredTextureVariantId(entry.texture.name, variants.length + index);
+ let suffix = 2;
+ const baseId = id;
+ while (usedIds[id]) {
+ id = (baseId.slice(0, 61) + '_' + suffix).slice(0, 64);
+ suffix += 1;
+ }
+ usedIds[id] = true;
+ variants.push({
+ id: id,
+ replacements: {}
+ });
+ variants[variants.length - 1].replacements[String(base.uuid || '')] =
+ String(entry.texture.uuid || '');
+ });
+ return variants;
+ }
+
+ function containsForbiddenControl(value, allowLayoutWhitespace) {
+ for (let index = 0; index < value.length; index++) {
+ const code = value.charCodeAt(index);
+ const isControl = code <= 0x1f || (code >= 0x7f && code <= 0x9f);
+ const isAllowedLayout = allowLayoutWhitespace && (code === 0x09 || code === 0x0a || code === 0x0d);
+ if (isControl && !isAllowedLayout) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ function encodeUtf8(value) {
+ if (typeof TextEncoder === 'undefined') {
+ throw new Error('This Blockbench build does not provide UTF-8 TextEncoder support.');
+ }
+ return new TextEncoder().encode(String(value));
+ }
+
+ function isEmbeddedPngDataUri(value) {
+ return typeof value === 'string' &&
+ /^data:image\/png;base64,[A-Za-z0-9+/]+={0,2}$/.test(value);
+ }
+
+ function validateJsonBounds(root) {
+ const issues = [];
+ const pending = [{value: root, depth: 0}];
+ const seen = new Set();
+ let valueCount = 0;
+ let depthExceeded = false;
+ let valueLimitExceeded = false;
+ while (pending.length > 0 && !valueLimitExceeded) {
+ const current = pending.pop();
+ valueCount++;
+ if (valueCount > PACKAGE_LIMITS.jsonValues) {
+ valueLimitExceeded = true;
+ break;
+ }
+ if (current.depth > PACKAGE_LIMITS.jsonDepth) {
+ depthExceeded = true;
+ continue;
+ }
+ const value = current.value;
+ if (!value || typeof value !== 'object' || seen.has(value)) {
+ continue;
+ }
+ seen.add(value);
+ const keys = Object.keys(value);
+ if (valueCount + pending.length + keys.length > PACKAGE_LIMITS.jsonValues) {
+ valueLimitExceeded = true;
+ break;
+ }
+ keys.forEach(function (key) {
+ if (key === '__proto__' || key === 'prototype' || key === 'constructor') {
+ issues.push(makeIssue(
+ 'error',
+ 'COSMIQ_PACKAGE_JSON_KEY',
+ 'Embedded source contains an unsafe object key.'
+ ));
+ return;
+ }
+ pending.push({value: value[key], depth: current.depth + 1});
+ });
+ }
+ if (depthExceeded) {
+ issues.push(makeIssue(
+ 'error',
+ 'COSMIQ_PACKAGE_JSON_DEPTH',
+ 'Embedded source exceeds the maximum JSON depth of ' + PACKAGE_LIMITS.jsonDepth + '.'
+ ));
+ }
+ if (valueLimitExceeded) {
+ issues.push(makeIssue(
+ 'error',
+ 'COSMIQ_PACKAGE_JSON_VALUES',
+ 'Embedded source exceeds the maximum JSON value count of ' + PACKAGE_LIMITS.jsonValues + '.'
+ ));
+ }
+ return issues;
+ }
+
+ function animationKeyframeCount(animation) {
+ if (!animation || typeof animation !== 'object') {
+ return 0;
+ }
+ if (animation.animators && typeof animation.animators === 'object') {
+ return Object.keys(animation.animators).reduce(function (total, animatorKey) {
+ const animator = animation.animators[animatorKey];
+ return total + (animator && Array.isArray(animator.keyframes) ? animator.keyframes.length : 0);
+ }, 0);
+ }
+ if (typeof animation.keyframeCount === 'number' && Number.isFinite(animation.keyframeCount)) {
+ return Math.max(0, Math.floor(animation.keyframeCount));
+ }
+ return 0;
+ }
+
+ function animationsWithKeyframes(animations) {
+ return (Array.isArray(animations) ? animations : []).filter(function (animation) {
+ return animationKeyframeCount(animation) > 0;
+ });
+ }
+
+ function animationHasPackageContent(animation) {
+ return animationKeyframeCount(animation) > 0;
+ }
+
+ function animationsWithPackageContent(animations, timelineCues) {
+ return (Array.isArray(animations) ? animations : []).filter(function (animation) {
+ return animationHasPackageContent(animation, timelineCues);
+ });
+ }
+
+ function packageBytes(content) {
+ if (content instanceof ArrayBuffer) {
+ return new Uint8Array(content);
+ }
+ if (typeof ArrayBuffer !== 'undefined' && ArrayBuffer.isView(content)) {
+ return new Uint8Array(content.buffer, content.byteOffset, content.byteLength);
+ }
+ throw new Error('Cosmiq package content must be binary data.');
+ }
+
+ function decodeUtf8(bytes) {
+ if (typeof TextDecoder === 'undefined') {
+ throw new Error('This Blockbench build cannot decode UTF-8 package data.');
+ }
+ return new TextDecoder('utf-8', {fatal: true}).decode(bytes);
+ }
+
+ /*
+ * Private normalized validation projection used by Creator review. V3 package bytes are the
+ * Blockbench project itself and never expose this historical binary frame.
+ */
+ const COSMIQ_RUNTIME_SCHEMA = 'cosmiq.package.v2';
+ const COSMIQ_STANDARD_COORDINATES = 'cosmiq.coordinates.v1';
+ const COSMIQ_STANDARD_CONTAINER_VERSION = 2;
+ const COSMIQ_STANDARD_HEADER_BYTES = 64;
+ const COSMIQ_STANDARD_DIRECTORY_ENTRY_BYTES = 80;
+ const COSMIQ_STANDARD_MAX_SECTIONS = 128;
+ const COSMIQ_STANDARD_SECTION_TYPES = Object.freeze({
+ manifest_json: 1,
+ geometry_buffer: 2,
+ animation_buffer: 3,
+ texture_png: 4,
+ audio_ogg_vorbis: 5
+ });
+ const COSMIQ_STANDARD_SECTION_NAMES = Object.freeze({
+ 1: 'manifest_json',
+ 2: 'geometry_buffer',
+ 3: 'animation_buffer',
+ 4: 'texture_png',
+ 5: 'audio_ogg_vorbis'
+ });
+ const COSMIQ_STANDARD_TOP_LEVEL_FIELDS = Object.freeze([
+ 'schemaVersion', 'assetKind', 'coordinateProfile', 'bodyGroup', 'capeMesh',
+ 'movementPolicy', 'previewCamera', 'sections', 'actors', 'nodes', 'materials',
+ 'geometry', 'textures', 'textureVariants', 'clips', 'stateBindings', 'audio',
+ 'particles', 'locators', 'cues', 'outcomeSets', 'logic'
+ ]);
+ const COSMIQ_STANDARD_OPTIONAL_TOP_LEVEL_FIELDS = Object.freeze([
+ 'playerAppearance'
+ ]);
+ const COSMIQ_LOCAL_ID_PATTERN = /^[a-z][a-z0-9._-]{0,63}$/;
+
+ function CosmiqValidationError(code, message, location) {
+ const instance = new Error(code + ': ' + message);
+ instance.name = 'CosmiqValidationError';
+ instance.code = code;
+ instance.location = location || null;
+ if (typeof Object.setPrototypeOf === 'function') {
+ if (!(CosmiqValidationError.prototype instanceof Error)) {
+ Object.setPrototypeOf(CosmiqValidationError.prototype, Error.prototype);
+ }
+ Object.setPrototypeOf(instance, CosmiqValidationError.prototype);
+ }
+ return instance;
+ }
+
+ function cosmiqFail(code, message, location) {
+ throw new CosmiqValidationError(code, message, location);
+ }
+
+ function cosmiqAlign8(value) {
+ return (value + 7) & ~7;
+ }
+
+ function cosmiqCompareText(left, right) {
+ return left < right ? -1 : left > right ? 1 : 0;
+ }
+
+ function cosmiqBytes(value) {
+ if (value instanceof ArrayBuffer) {
+ return new Uint8Array(value);
+ }
+ if (typeof ArrayBuffer !== 'undefined' && ArrayBuffer.isView(value)) {
+ return new Uint8Array(value.buffer, value.byteOffset, value.byteLength);
+ }
+ cosmiqFail('header.invalid_length', 'The package must be supplied as exact binary bytes.');
+ }
+
+ function cosmiqReadU64(view, offset, code) {
+ const low = view.getUint32(offset, true);
+ const high = view.getUint32(offset + 4, true);
+ if (high > 0x1fffff) {
+ cosmiqFail(code || 'directory.invalid', 'A 64-bit value exceeds the exact integer range.');
+ }
+ return high * 0x100000000 + low;
+ }
+
+ function cosmiqWriteU64(view, offset, value) {
+ if (!Number.isSafeInteger(value) || value < 0) {
+ cosmiqFail('directory.invalid', 'A 64-bit field is outside the supported exact range.');
+ }
+ view.setUint32(offset, value >>> 0, true);
+ view.setUint32(offset + 4, Math.floor(value / 0x100000000), true);
+ }
+
+ function cosmiqValidUnicode(value) {
+ for (let index = 0; index < value.length; index++) {
+ const code = value.charCodeAt(index);
+ if (code >= 0xd800 && code <= 0xdbff) {
+ const next = value.charCodeAt(index + 1);
+ if (next < 0xdc00 || next > 0xdfff) return false;
+ index++;
+ } else if (code >= 0xdc00 && code <= 0xdfff) {
+ return false;
+ }
+ }
+ return true;
+ }
+
+ function canonicalCosmiqJson(value) {
+ const active = new Set();
+ function serialize(item, location) {
+ if (item === null) return 'null';
+ if (typeof item === 'string') {
+ if (!cosmiqValidUnicode(item)) {
+ cosmiqFail('manifest.invalid_json', 'JSON strings must contain valid Unicode.', location);
+ }
+ return JSON.stringify(item);
+ }
+ if (typeof item === 'number') {
+ if (!Number.isFinite(item)) {
+ cosmiqFail('manifest.invalid_json', 'JSON numbers must be finite.', location);
+ }
+ return JSON.stringify(item);
+ }
+ if (typeof item === 'boolean') return item ? 'true' : 'false';
+ if (!item || typeof item !== 'object') {
+ cosmiqFail('manifest.invalid_json', 'Unsupported JSON value.', location);
+ }
+ if (active.has(item)) {
+ cosmiqFail('manifest.invalid_json', 'Cyclic JSON values are invalid.', location);
+ }
+ active.add(item);
+ let result;
+ if (Array.isArray(item)) {
+ result = '[' + item.map(function (child, index) {
+ return serialize(child, location + '[' + index + ']');
+ }).join(',') + ']';
+ } else {
+ const prototype = Object.getPrototypeOf(item);
+ if (prototype !== Object.prototype && prototype !== null) {
+ cosmiqFail('manifest.invalid_json', 'Manifest objects must be ordinary JSON objects.', location);
+ }
+ const keys = Object.keys(item).sort();
+ result = '{' + keys.map(function (key) {
+ if (!cosmiqValidUnicode(key)) {
+ cosmiqFail('manifest.invalid_json', 'JSON object keys must contain valid Unicode.', location);
+ }
+ return JSON.stringify(key) + ':' + serialize(item[key], location + '.' + key);
+ }).join(',') + '}';
+ }
+ active.delete(item);
+ return result;
+ }
+ return serialize(value, '$');
+ }
+
+ function cosmiqValidateJsonBounds(value) {
+ let count = 0;
+ const pending = [{value: value, depth: 1}];
+ while (pending.length) {
+ const current = pending.pop();
+ count++;
+ if (count > 250000) cosmiqFail('budget.exceeded', 'Manifest JSON value budget exceeded.', '$');
+ if (current.depth > 64) cosmiqFail('budget.exceeded', 'Manifest JSON depth exceeds 64.', '$');
+ if (current.value && typeof current.value === 'object') {
+ Object.keys(current.value).forEach(function (key) {
+ pending.push({value: current.value[key], depth: current.depth + 1});
+ });
+ }
+ }
+ }
+
+ function cosmiqSha256(input) {
+ const bytes = cosmiqBytes(input);
+ const bitLength = bytes.length * 8;
+ const paddedLength = Math.ceil((bytes.length + 9) / 64) * 64;
+ const padded = new Uint8Array(paddedLength);
+ padded.set(bytes);
+ padded[bytes.length] = 0x80;
+ const padView = new DataView(padded.buffer);
+ const bitHigh = Math.floor(bitLength / 0x100000000);
+ const bitLow = bitLength >>> 0;
+ padView.setUint32(paddedLength - 8, bitHigh, false);
+ padView.setUint32(paddedLength - 4, bitLow, false);
+ const constants = [
+ 0x428a2f98, 0x71374491, 0xb5c0fbcf, 0xe9b5dba5, 0x3956c25b, 0x59f111f1,
+ 0x923f82a4, 0xab1c5ed5, 0xd807aa98, 0x12835b01, 0x243185be, 0x550c7dc3,
+ 0x72be5d74, 0x80deb1fe, 0x9bdc06a7, 0xc19bf174, 0xe49b69c1, 0xefbe4786,
+ 0x0fc19dc6, 0x240ca1cc, 0x2de92c6f, 0x4a7484aa, 0x5cb0a9dc, 0x76f988da,
+ 0x983e5152, 0xa831c66d, 0xb00327c8, 0xbf597fc7, 0xc6e00bf3, 0xd5a79147,
+ 0x06ca6351, 0x14292967, 0x27b70a85, 0x2e1b2138, 0x4d2c6dfc, 0x53380d13,
+ 0x650a7354, 0x766a0abb, 0x81c2c92e, 0x92722c85, 0xa2bfe8a1, 0xa81a664b,
+ 0xc24b8b70, 0xc76c51a3, 0xd192e819, 0xd6990624, 0xf40e3585, 0x106aa070,
+ 0x19a4c116, 0x1e376c08, 0x2748774c, 0x34b0bcb5, 0x391c0cb3, 0x4ed8aa4a,
+ 0x5b9cca4f, 0x682e6ff3, 0x748f82ee, 0x78a5636f, 0x84c87814, 0x8cc70208,
+ 0x90befffa, 0xa4506ceb, 0xbef9a3f7, 0xc67178f2
+ ];
+ const state = [
+ 0x6a09e667, 0xbb67ae85, 0x3c6ef372, 0xa54ff53a,
+ 0x510e527f, 0x9b05688c, 0x1f83d9ab, 0x5be0cd19
+ ];
+ const words = new Uint32Array(64);
+ function rotate(value, bits) {
+ return (value >>> bits) | (value << (32 - bits));
+ }
+ for (let block = 0; block < padded.length; block += 64) {
+ for (let index = 0; index < 16; index++) {
+ words[index] = padView.getUint32(block + index * 4, false);
+ }
+ for (let index = 16; index < 64; index++) {
+ const left = words[index - 15];
+ const right = words[index - 2];
+ const s0 = rotate(left, 7) ^ rotate(left, 18) ^ (left >>> 3);
+ const s1 = rotate(right, 17) ^ rotate(right, 19) ^ (right >>> 10);
+ words[index] = (words[index - 16] + s0 + words[index - 7] + s1) >>> 0;
+ }
+ let a = state[0];
+ let b = state[1];
+ let c = state[2];
+ let d = state[3];
+ let e = state[4];
+ let f = state[5];
+ let g = state[6];
+ let h = state[7];
+ for (let index = 0; index < 64; index++) {
+ const sum1 = rotate(e, 6) ^ rotate(e, 11) ^ rotate(e, 25);
+ const choice = (e & f) ^ (~e & g);
+ const temp1 = (h + sum1 + choice + constants[index] + words[index]) >>> 0;
+ const sum0 = rotate(a, 2) ^ rotate(a, 13) ^ rotate(a, 22);
+ const majority = (a & b) ^ (a & c) ^ (b & c);
+ const temp2 = (sum0 + majority) >>> 0;
+ h = g;
+ g = f;
+ f = e;
+ e = (d + temp1) >>> 0;
+ d = c;
+ c = b;
+ b = a;
+ a = (temp1 + temp2) >>> 0;
+ }
+ state[0] = (state[0] + a) >>> 0;
+ state[1] = (state[1] + b) >>> 0;
+ state[2] = (state[2] + c) >>> 0;
+ state[3] = (state[3] + d) >>> 0;
+ state[4] = (state[4] + e) >>> 0;
+ state[5] = (state[5] + f) >>> 0;
+ state[6] = (state[6] + g) >>> 0;
+ state[7] = (state[7] + h) >>> 0;
+ }
+ const digest = new Uint8Array(32);
+ const digestView = new DataView(digest.buffer);
+ state.forEach(function (value, index) {
+ digestView.setUint32(index * 4, value, false);
+ });
+ return digest;
+ }
+
+ function cosmiqEqualBytes(left, right) {
+ if (left.length !== right.length) return false;
+ let difference = 0;
+ for (let index = 0; index < left.length; index++) {
+ difference |= left[index] ^ right[index];
+ }
+ return difference === 0;
+ }
+
+ function cosmiqPlainObject(value) {
+ return !!value && typeof value === 'object' && !Array.isArray(value);
+ }
+
+ function cosmiqClosedObject(value, fields, location, code) {
+ if (!cosmiqPlainObject(value)) {
+ cosmiqFail(code || 'kind.invariant_violation', 'Expected a closed object.', location);
+ }
+ const keys = Object.keys(value).sort();
+ const expected = fields.slice().sort();
+ if (keys.length !== expected.length || keys.some(function (key, index) {
+ return key !== expected[index];
+ })) {
+ const unknown = keys.find(function (key) { return expected.indexOf(key) === -1; });
+ cosmiqFail(
+ unknown ? 'manifest.unknown_field' : (code || 'kind.invariant_violation'),
+ unknown ? 'Unknown field ' + unknown + '.' : 'Required fields are missing.',
+ location
+ );
+ }
+ }
+
+ function cosmiqClosedManifest(value) {
+ if (!cosmiqPlainObject(value)) {
+ cosmiqFail('kind.invariant_violation', 'Expected a closed object.', '$');
+ }
+ const allowed = COSMIQ_STANDARD_TOP_LEVEL_FIELDS.concat(
+ COSMIQ_STANDARD_OPTIONAL_TOP_LEVEL_FIELDS
+ );
+ const unknown = Object.keys(value).find(function (key) {
+ return allowed.indexOf(key) === -1;
+ });
+ const missing = COSMIQ_STANDARD_TOP_LEVEL_FIELDS.find(function (key) {
+ return !Object.hasOwn(value, key);
+ });
+ if (unknown || missing) {
+ cosmiqFail(
+ unknown ? 'manifest.unknown_field' : 'kind.invariant_violation',
+ unknown ? 'Unknown field ' + unknown + '.' : 'Required fields are missing.',
+ '$'
+ );
+ }
+ }
+
+ function cosmiqArray(value, location) {
+ if (!Array.isArray(value)) {
+ cosmiqFail('kind.invariant_violation', 'Expected an array.', location);
+ }
+ return value;
+ }
+
+ function cosmiqNumber(value, minimum, maximum, location, integer) {
+ if (typeof value !== 'number' || !Number.isFinite(value) || Object.is(value, -0) ||
+ (integer && !Number.isInteger(value)) || value < minimum || value > maximum) {
+ cosmiqFail('kind.invariant_violation', 'Numeric value is outside its allowed range.', location);
+ }
+ return value;
+ }
+
+ function cosmiqLocalId(value, location) {
+ if (typeof value !== 'string' || !COSMIQ_LOCAL_ID_PATTERN.test(value)) {
+ cosmiqFail('kind.invariant_violation', 'Expected a Local ID.', location);
+ }
+ return value;
+ }
+
+ function cosmiqRequireSorted(collection, keyFor, location, startIndex) {
+ const start = startIndex || 0;
+ let previous = '';
+ for (let index = start; index < collection.length; index++) {
+ const key = keyFor(collection[index]);
+ if (index > start && key <= previous) {
+ cosmiqFail('kind.invariant_violation', 'Collection is not in canonical order.', location);
+ }
+ previous = key;
+ }
+ }
+
+ function cosmiqVector(value, length, minimum, maximum, location) {
+ cosmiqArray(value, location);
+ if (value.length !== length) {
+ cosmiqFail('kind.invariant_violation', 'Vector has the wrong component count.', location);
+ }
+ value.forEach(function (component, index) {
+ cosmiqNumber(component, minimum, maximum, location + '[' + index + ']', false);
+ });
+ return value;
+ }
+
+ function cosmiqTransform(value, location, limits) {
+ cosmiqClosedObject(value, ['translation', 'rotation', 'scale', 'pivot'], location);
+ const bound = limits == null ? 8 : limits;
+ cosmiqVector(value.translation, 3, -bound, bound, location + '.translation');
+ cosmiqVector(value.pivot, 3, -bound, bound, location + '.pivot');
+ cosmiqVector(value.scale, 3, 0.0000001, 16, location + '.scale');
+ cosmiqVector(value.rotation, 4, -1, 1, location + '.rotation');
+ const length = Math.sqrt(value.rotation.reduce(function (sum, component) {
+ return sum + component * component;
+ }, 0));
+ if (Math.abs(length - 1) > 0.0001) {
+ cosmiqFail('kind.invariant_violation', 'Quaternion is not normalized.', location + '.rotation');
+ }
+ }
+
+ function cosmiqValidatePreviewCamera(manifest, clipIds, rootNodeIds) {
+ const camera = manifest.previewCamera;
+ cosmiqClosedObject(camera, ['id', 'transform', 'projection', 'pose'], '$.previewCamera', 'camera.invalid');
+ if (camera.id !== 'thumbnail_camera') {
+ cosmiqFail('camera.invalid', 'The preview Camera Node ID must be thumbnail_camera.', '$.previewCamera.id');
+ }
+ cosmiqTransform(camera.transform, '$.previewCamera.transform', 64);
+ if (JSON.stringify(camera.transform.scale) !== '[1,1,1]' ||
+ JSON.stringify(camera.transform.pivot) !== '[0,0,0]') {
+ cosmiqFail('camera.invalid', 'Camera scale and pivot must be identity.', '$.previewCamera.transform');
+ }
+ cosmiqClosedObject(
+ camera.projection,
+ ['kind', 'verticalFovDegrees', 'nearPlane', 'farPlane'],
+ '$.previewCamera.projection',
+ 'camera.invalid'
+ );
+ if (camera.projection.kind !== 'perspective') {
+ cosmiqFail('camera.invalid', 'Only perspective preview cameras are supported.', '$.previewCamera.projection.kind');
+ }
+ cosmiqNumber(camera.projection.verticalFovDegrees, 10, 90, '$.previewCamera.projection.verticalFovDegrees');
+ cosmiqNumber(camera.projection.nearPlane, 0.001, 1, '$.previewCamera.projection.nearPlane');
+ cosmiqNumber(camera.projection.farPlane, 0.001, 256, '$.previewCamera.projection.farPlane');
+ if (camera.projection.farPlane <= camera.projection.nearPlane) {
+ cosmiqFail('camera.invalid', 'The far plane must be greater than the near plane.', '$.previewCamera.projection');
+ }
+ cosmiqClosedObject(
+ camera.pose,
+ ['clipId', 'timeMs', 'modelVariant', 'visiblePropRootIds'],
+ '$.previewCamera.pose',
+ 'camera.invalid'
+ );
+ if (camera.pose.clipId === null) {
+ if (camera.pose.timeMs !== 0) {
+ cosmiqFail('camera.invalid', 'A bind-pose camera must use timeMs 0.', '$.previewCamera.pose.timeMs');
+ }
+ } else {
+ cosmiqLocalId(camera.pose.clipId, '$.previewCamera.pose.clipId');
+ if (!clipIds[camera.pose.clipId]) {
+ cosmiqFail('camera.invalid', 'The preview clip does not resolve.', '$.previewCamera.pose.clipId');
+ }
+ cosmiqNumber(camera.pose.timeMs, 0, clipIds[camera.pose.clipId], '$.previewCamera.pose.timeMs', true);
+ }
+ const visible = cosmiqArray(camera.pose.visiblePropRootIds, '$.previewCamera.pose.visiblePropRootIds');
+ let previous = '';
+ visible.forEach(function (id, index) {
+ cosmiqLocalId(id, '$.previewCamera.pose.visiblePropRootIds[' + index + ']');
+ if (!rootNodeIds[id] || id <= previous) {
+ cosmiqFail('camera.invalid', 'Visible Prop roots must be sorted, unique, and resolve.', '$.previewCamera.pose.visiblePropRootIds');
+ }
+ previous = id;
+ });
+ if (manifest.assetKind === 'accessory') {
+ if (['universal', 'wide', 'slim'].indexOf(camera.pose.modelVariant) === -1 || visible.length) {
+ cosmiqFail('camera.invalid', 'Accessory camera pose has invalid variant or visible Props.', '$.previewCamera.pose');
+ }
+ } else if (camera.pose.modelVariant !== null) {
+ cosmiqFail('camera.invalid', 'Cape and Emote cameras use a null model variant.', '$.previewCamera.pose.modelVariant');
+ }
+ if (manifest.assetKind === 'cape' && (camera.pose.clipId !== null || visible.length)) {
+ cosmiqFail('camera.invalid', 'Cape cameras use the bind pose with no Props.', '$.previewCamera.pose');
+ }
+ }
+
+ function cosmiqCrc32(bytes, start, end) {
+ let crc = 0xffffffff;
+ for (let index = start; index < end; index++) {
+ crc ^= bytes[index];
+ for (let bit = 0; bit < 8; bit++) {
+ crc = (crc >>> 1) ^ ((crc & 1) ? 0xedb88320 : 0);
+ }
+ }
+ return (crc ^ 0xffffffff) >>> 0;
+ }
+
+ function cosmiqInspectPng(input, location) {
+ const bytes = cosmiqBytes(input);
+ const signature = [137, 80, 78, 71, 13, 10, 26, 10];
+ if (bytes.length < 33 || signature.some(function (value, index) { return bytes[index] !== value; })) {
+ cosmiqFail('media.invalid_png', 'PNG signature is invalid.', location);
+ }
+ const view = new DataView(bytes.buffer, bytes.byteOffset, bytes.byteLength);
+ const allowed = {IHDR: true, PLTE: true, tRNS: true, sRGB: true, gAMA: true, IDAT: true, IEND: true};
+ let offset = 8;
+ let stage = 'start';
+ let width = 0;
+ let height = 0;
+ let bitDepth = 0;
+ let colorType = 0;
+ let paletteEntries = 0;
+ let idatCount = 0;
+ let gammaValue = null;
+ const seen = Object.create(null);
+ while (offset < bytes.length) {
+ if (offset + 12 > bytes.length) cosmiqFail('media.invalid_png', 'PNG chunk is truncated.', location);
+ const length = view.getUint32(offset, false);
+ const chunkEnd = offset + 12 + length;
+ if (chunkEnd > bytes.length) cosmiqFail('media.invalid_png', 'PNG chunk exceeds its section.', location);
+ const type = String.fromCharCode(
+ bytes[offset + 4], bytes[offset + 5], bytes[offset + 6], bytes[offset + 7]
+ );
+ if (!allowed[type]) cosmiqFail('media.invalid_png', 'PNG contains forbidden chunk ' + type + '.', location);
+ const expectedCrc = view.getUint32(offset + 8 + length, false);
+ const actualCrc = cosmiqCrc32(bytes, offset + 4, offset + 8 + length);
+ if (expectedCrc !== actualCrc) cosmiqFail('media.invalid_png', 'PNG chunk CRC is invalid.', location);
+ if (type === 'IHDR') {
+ if (stage !== 'start' || length !== 13) cosmiqFail('media.invalid_png', 'PNG IHDR is invalid.', location);
+ width = view.getUint32(offset + 8, false);
+ height = view.getUint32(offset + 12, false);
+ bitDepth = bytes[offset + 16];
+ colorType = bytes[offset + 17];
+ const legalDepths = {
+ 0: [1, 2, 4, 8, 16],
+ 2: [8, 16],
+ 3: [1, 2, 4, 8],
+ 4: [8, 16],
+ 6: [8, 16]
+ };
+ if (!width || !height || !legalDepths[colorType] ||
+ legalDepths[colorType].indexOf(bitDepth) === -1 ||
+ bytes[offset + 18] !== 0 || bytes[offset + 19] !== 0 || bytes[offset + 20] !== 0) {
+ cosmiqFail('media.invalid_png', 'PNG IHDR properties are unsupported.', location);
+ }
+ stage = 'before_idat';
+ } else if (type === 'IDAT') {
+ if (colorType === 3 && !seen.PLTE) {
+ cosmiqFail('media.invalid_png', 'Indexed PNG requires a palette before image data.', location);
+ }
+ if (stage !== 'before_idat' && stage !== 'idat') {
+ cosmiqFail('media.invalid_png', 'PNG IDAT chunks must be consecutive.', location);
+ }
+ stage = 'idat';
+ idatCount++;
+ } else if (type === 'IEND') {
+ if (stage !== 'idat' || length !== 0 || idatCount === 0 || chunkEnd !== bytes.length) {
+ cosmiqFail('media.invalid_png', 'PNG IEND or trailing bytes are invalid.', location);
+ }
+ stage = 'end';
+ } else {
+ if (stage !== 'before_idat' || seen[type]) {
+ cosmiqFail('media.invalid_png', 'PNG ancillary chunk ordering is invalid.', location);
+ }
+ if (type === 'PLTE') {
+ paletteEntries = length / 3;
+ if (length < 3 || length > 768 || length % 3 !== 0 ||
+ colorType === 0 || colorType === 4 || seen.tRNS ||
+ (colorType === 3 && paletteEntries > Math.pow(2, bitDepth))) {
+ cosmiqFail('media.invalid_png', 'PNG palette is invalid for its color type.', location);
+ }
+ } else if (type === 'tRNS') {
+ const maxSample = bitDepth === 16 ? 65535 : Math.pow(2, bitDepth) - 1;
+ if ((colorType === 0 && length !== 2) ||
+ (colorType === 2 && length !== 6) ||
+ (colorType === 3 && (!seen.PLTE || length < 1 || length > paletteEntries)) ||
+ colorType === 4 || colorType === 6) {
+ cosmiqFail('media.invalid_png', 'PNG transparency chunk is invalid.', location);
+ }
+ if ((colorType === 0 && view.getUint16(offset + 8, false) > maxSample) ||
+ (colorType === 2 && [0, 2, 4].some(function (channelOffset) {
+ return view.getUint16(offset + 8 + channelOffset, false) > maxSample;
+ }))) {
+ cosmiqFail('media.invalid_png', 'PNG transparency sample exceeds its bit depth.', location);
+ }
+ } else if (type === 'sRGB') {
+ if (length !== 1 || bytes[offset + 8] > 3 || seen.PLTE || seen.tRNS) {
+ cosmiqFail('media.invalid_png', 'PNG sRGB chunk is invalid.', location);
+ }
+ } else if (type === 'gAMA') {
+ gammaValue = length === 4 ? view.getUint32(offset + 8, false) : null;
+ if (length !== 4 || gammaValue === 0 || seen.PLTE || seen.tRNS) {
+ cosmiqFail('media.invalid_png', 'PNG gamma chunk is invalid.', location);
+ }
+ }
+ seen[type] = true;
+ }
+ offset = chunkEnd;
+ }
+ if (stage !== 'end') cosmiqFail('media.invalid_png', 'PNG is incomplete.', location);
+ if (seen.sRGB && gammaValue !== null && gammaValue !== 45455) {
+ cosmiqFail('media.invalid_png', 'PNG sRGB and gamma declarations disagree.', location);
+ }
+ return {width: width, height: height};
+ }
+
+ function cosmiqOggCrc(bytes) {
+ let crc = 0;
+ for (let index = 0; index < bytes.length; index++) {
+ crc ^= bytes[index] << 24;
+ for (let bit = 0; bit < 8; bit++) {
+ crc = (crc & 0x80000000) !== 0 ? ((crc << 1) ^ 0x04c11db7) : (crc << 1);
+ }
+ }
+ return crc >>> 0;
+ }
+
+ function cosmiqInspectOggVorbis(input, location) {
+ const bytes = cosmiqBytes(input);
+ const packets = [];
+ let packetParts = [];
+ let packetLength = 0;
+ let offset = 0;
+ let serial = null;
+ let sequence = 0;
+ let finalGranule = null;
+ let sawEos = false;
+ while (offset < bytes.length) {
+ if (offset + 27 > bytes.length ||
+ String.fromCharCode(bytes[offset], bytes[offset + 1], bytes[offset + 2], bytes[offset + 3]) !== 'OggS' ||
+ bytes[offset + 4] !== 0) {
+ cosmiqFail('media.invalid_ogg_vorbis', 'Ogg page header is invalid.', location);
+ }
+ const view = new DataView(bytes.buffer, bytes.byteOffset + offset, bytes.length - offset);
+ const headerType = bytes[offset + 5];
+ const granuleLow = view.getUint32(6, true);
+ const granuleHigh = view.getUint32(10, true);
+ const pageSerial = view.getUint32(14, true);
+ const pageSequence = view.getUint32(18, true);
+ const expectedCrc = view.getUint32(22, true);
+ const segmentCount = bytes[offset + 26];
+ if (offset + 27 + segmentCount > bytes.length || (headerType & ~7) !== 0 ||
+ (serial === null ? !(headerType & 2) || pageSequence !== 0 :
+ pageSerial !== serial || pageSequence !== sequence || Boolean(headerType & 2)) ||
+ (offset === 0 && (headerType & 1)) ||
+ (offset > 0 && Boolean(headerType & 1) !== (packetParts.length > 0))) {
+ cosmiqFail('media.invalid_ogg_vorbis', 'Ogg stream sequence or continuation is invalid.', location);
+ }
+ serial = pageSerial;
+ sequence++;
+ let bodyLength = 0;
+ for (let index = 0; index < segmentCount; index++) bodyLength += bytes[offset + 27 + index];
+ const pageLength = 27 + segmentCount + bodyLength;
+ if (offset + pageLength > bytes.length) {
+ cosmiqFail('media.invalid_ogg_vorbis', 'Ogg page is truncated.', location);
+ }
+ const pageForCrc = bytes.slice(offset, offset + pageLength);
+ pageForCrc.fill(0, 22, 26);
+ if (cosmiqOggCrc(pageForCrc) !== expectedCrc) {
+ cosmiqFail('media.invalid_ogg_vorbis', 'Ogg page checksum is invalid.', location);
+ }
+ let bodyOffset = offset + 27 + segmentCount;
+ for (let index = 0; index < segmentCount; index++) {
+ const length = bytes[offset + 27 + index];
+ const part = bytes.subarray(bodyOffset, bodyOffset + length);
+ packetParts.push(part);
+ packetLength += length;
+ bodyOffset += length;
+ if (length < 255) {
+ const packet = new Uint8Array(packetLength);
+ let packetOffset = 0;
+ packetParts.forEach(function (piece) {
+ packet.set(piece, packetOffset);
+ packetOffset += piece.length;
+ });
+ packets.push(packet);
+ packetParts = [];
+ packetLength = 0;
+ }
+ }
+ if (headerType & 4) {
+ if (sawEos || offset + pageLength !== bytes.length || packetParts.length) {
+ cosmiqFail('media.invalid_ogg_vorbis', 'Ogg EOS page is invalid.', location);
+ }
+ sawEos = true;
+ if (granuleHigh > 0x1fffff) {
+ cosmiqFail('media.invalid_ogg_vorbis', 'Vorbis final granule is too large.', location);
+ }
+ finalGranule = granuleHigh * 0x100000000 + granuleLow;
+ }
+ offset += pageLength;
+ }
+ if (!sawEos || packets.length < 4 || packetParts.length) {
+ cosmiqFail('media.invalid_ogg_vorbis', 'Ogg Vorbis stream is incomplete.', location);
+ }
+ function isVorbisPacket(packet, type) {
+ return packet.length >= 7 && packet[0] === type &&
+ String.fromCharCode(packet[1], packet[2], packet[3], packet[4], packet[5], packet[6]) === 'vorbis';
+ }
+ if (!isVorbisPacket(packets[0], 1) || !isVorbisPacket(packets[1], 3) ||
+ !isVorbisPacket(packets[2], 5) || packets[0].length !== 30 ||
+ packets[2].length < 8 || packets.slice(3).some(function (packet) {
+ return !packet.length || (packet[0] & 1) !== 0;
+ })) {
+ cosmiqFail('media.invalid_ogg_vorbis', 'Required Vorbis headers are missing.', location);
+ }
+ const identification = new DataView(
+ packets[0].buffer, packets[0].byteOffset, packets[0].byteLength
+ );
+ const channels = packets[0][11];
+ const sampleRateHz = identification.getUint32(12, true);
+ const smallBlockExponent = packets[0][28] & 0x0f;
+ const largeBlockExponent = packets[0][28] >>> 4;
+ if (identification.getUint32(7, true) !== 0 || (channels !== 1 && channels !== 2) ||
+ sampleRateHz < 8000 || sampleRateHz > 48000 ||
+ smallBlockExponent < 6 || smallBlockExponent > 13 ||
+ largeBlockExponent < smallBlockExponent || largeBlockExponent > 13 ||
+ packets[0][29] !== 1) {
+ cosmiqFail('media.invalid_ogg_vorbis', 'Vorbis identification header is invalid.', location);
+ }
+ const comment = packets[1];
+ if (comment.length < 16) cosmiqFail('media.invalid_ogg_vorbis', 'Vorbis comment header is truncated.', location);
+ const commentView = new DataView(comment.buffer, comment.byteOffset, comment.byteLength);
+ const vendorLength = commentView.getUint32(7, true);
+ const commentCountOffset = 11 + vendorLength;
+ if (vendorLength > 128 || commentCountOffset + 5 !== comment.length ||
+ commentView.getUint32(commentCountOffset, true) !== 0 ||
+ comment[comment.length - 1] !== 1) {
+ cosmiqFail('media.invalid_ogg_vorbis', 'Vorbis comments are not canonical.', location);
+ }
+ for (let index = 11; index < commentCountOffset; index++) {
+ if (comment[index] < 0x20 || comment[index] > 0x7e) {
+ cosmiqFail('media.invalid_ogg_vorbis', 'Vorbis vendor must be printable ASCII.', location);
+ }
+ }
+ if (!Number.isSafeInteger(finalGranule) || finalGranule <= 0) {
+ cosmiqFail('media.invalid_ogg_vorbis', 'Vorbis final granule is invalid.', location);
+ }
+ return {
+ sampleRateHz: sampleRateHz,
+ channels: channels,
+ durationMs: Math.ceil(finalGranule * 1000 / sampleRateHz)
+ };
+ }
+
+ const COSMIQ_BUFFER_FORMATS = Object.freeze({
+ position: {bytes: 12, alignment: 4, components: 3, kind: 'float32'},
+ normal: {bytes: 12, alignment: 4, components: 3, kind: 'float32'},
+ uv: {bytes: 8, alignment: 4, components: 2, kind: 'float32'},
+ index: {bytes: 4, alignment: 4, components: 1, kind: 'uint32'},
+ time: {bytes: 4, alignment: 4, components: 1, kind: 'uint32'},
+ translation: {bytes: 12, alignment: 4, components: 3, kind: 'float32'},
+ rotation: {bytes: 16, alignment: 4, components: 4, kind: 'float32'},
+ scale: {bytes: 12, alignment: 4, components: 3, kind: 'float32'},
+ bezier: {bytes: 72, alignment: 4, components: 18, kind: 'float32'},
+ interpolation: {bytes: 1, alignment: 1, components: 1, kind: 'uint8'}
+ });
+ const COSMIQ_BINARY32_MIN_NORMAL = 1.1754943508222875e-38;
+
+ function cosmiqCanonicalBinary32(value, location) {
+ const rounded = Math.fround(Number(value));
+ if (!Number.isFinite(rounded)) {
+ cosmiqFail(
+ 'buffer.invalid_view',
+ 'Buffer value cannot be represented as a finite binary32 value.',
+ location
+ );
+ }
+ const absolute = Math.abs(rounded);
+ return Object.is(rounded, -0) ||
+ (absolute > 0 && absolute < COSMIQ_BINARY32_MIN_NORMAL)
+ ? 0
+ : rounded;
+ }
+
+ function cosmiqWriteBinary32(view, offset, value, location) {
+ view.setFloat32(offset, cosmiqCanonicalBinary32(value, location), true);
+ }
+
+ function cosmiqValidateBufferView(value, formatName, expectedSectionType, sectionRecords, coverage, location) {
+ cosmiqClosedObject(value, ['section', 'byteOffset', 'count'], location, 'buffer.invalid_view');
+ cosmiqNumber(value.section, 1, sectionRecords.length - 1, location + '.section', true);
+ cosmiqNumber(value.byteOffset, 0, Number.MAX_SAFE_INTEGER, location + '.byteOffset', true);
+ cosmiqNumber(value.count, 0, Number.MAX_SAFE_INTEGER, location + '.count', true);
+ const format = COSMIQ_BUFFER_FORMATS[formatName];
+ const section = sectionRecords[value.section];
+ const byteLength = value.count * format.bytes;
+ if (!section || section.type !== expectedSectionType ||
+ value.byteOffset % format.alignment !== 0 ||
+ !Number.isSafeInteger(byteLength) ||
+ value.byteOffset + byteLength > (section ? section.bytes.length : 0)) {
+ cosmiqFail('buffer.invalid_view', 'Buffer View is out of bounds or has the wrong section type.', location);
+ }
+ if (value.count === 0 && formatName !== 'interpolation') {
+ cosmiqFail('buffer.invalid_view', 'Only a one-key interpolation view may be empty.', location);
+ }
+ const semanticKey = value.section + ':' + value.byteOffset + ':' + value.count + ':' + formatName;
+ const rangeKey = value.section + ':' + value.byteOffset + ':' + value.count + ':' + formatName;
+ if (!coverage.semantic[semanticKey]) {
+ const ranges = coverage.ranges[value.section] || (coverage.ranges[value.section] = []);
+ const conflicting = ranges.find(function (range) {
+ return value.byteOffset < range.end && value.byteOffset + byteLength > range.start &&
+ range.key !== rangeKey;
+ });
+ if (conflicting) cosmiqFail('buffer.invalid_view', 'Distinct Buffer Views overlap.', location);
+ if (!ranges.some(function (range) { return range.key === rangeKey; })) {
+ ranges.push({start: value.byteOffset, end: value.byteOffset + byteLength, key: rangeKey});
+ }
+ coverage.semantic[semanticKey] = true;
+ }
+ const data = [];
+ const view = new DataView(section.bytes.buffer, section.bytes.byteOffset, section.bytes.byteLength);
+ for (let element = 0; element < value.count; element++) {
+ const item = [];
+ for (let component = 0; component < format.components; component++) {
+ const offset = value.byteOffset + element * format.bytes +
+ component * (format.kind === 'uint8' ? 1 : 4);
+ let number;
+ if (format.kind === 'float32') number = view.getFloat32(offset, true);
+ else if (format.kind === 'uint32') number = view.getUint32(offset, true);
+ else number = view.getUint8(offset);
+ if (format.kind === 'float32') {
+ const absolute = Math.abs(number);
+ if (!Number.isFinite(number) || Object.is(number, -0) ||
+ (absolute > 0 && absolute < COSMIQ_BINARY32_MIN_NORMAL)) {
+ cosmiqFail('buffer.invalid_view', 'Buffer contains an invalid binary32 value.', location);
+ }
+ }
+ item.push(number);
+ }
+ data.push(format.components === 1 ? item[0] : item);
+ }
+ return data;
+ }
+
+ function cosmiqValidateGeometry(manifest, ids, sectionRecords, coverage, usage, counts) {
+ manifest.geometry.forEach(function (geometry, index) {
+ const location = '$.geometry[' + index + ']';
+ if (!cosmiqPlainObject(geometry)) cosmiqFail('geometry.invalid', 'Geometry must be an object.', location);
+ if (geometry.kind === 'cuboid') {
+ cosmiqClosedObject(geometry, ['id', 'kind', 'min', 'max', 'inflate', 'faces'], location, 'geometry.invalid');
+ cosmiqVector(geometry.min, 3, -64, 64, location + '.min');
+ cosmiqVector(geometry.max, 3, -64, 64, location + '.max');
+ cosmiqNumber(geometry.inflate, -0.25, 0.25, location + '.inflate');
+ for (let axis = 0; axis < 3; axis++) {
+ if (geometry.min[axis] >= geometry.max[axis] ||
+ geometry.min[axis] - geometry.inflate >= geometry.max[axis] + geometry.inflate ||
+ geometry.min[axis] - geometry.inflate < -64 ||
+ geometry.max[axis] + geometry.inflate > 64) {
+ cosmiqFail('geometry.invalid', 'Cuboid is degenerate or exceeds node-local bounds.', location);
+ }
+ }
+ const faceNames = ['north', 'south', 'east', 'west', 'up', 'down'];
+ cosmiqClosedObject(geometry.faces, faceNames, location + '.faces', 'geometry.invalid');
+ let faceCount = 0;
+ faceNames.forEach(function (faceName) {
+ const face = geometry.faces[faceName];
+ if (face === null) return;
+ faceCount++;
+ cosmiqClosedObject(face, ['materialId', 'uv', 'rotation'], location + '.faces.' + faceName, 'geometry.invalid');
+ if (!ids.material[face.materialId]) {
+ cosmiqFail('reference.missing', 'Cuboid face material does not resolve.', location + '.faces.' + faceName);
+ }
+ usage.material[face.materialId] = true;
+ cosmiqVector(face.uv, 4, 0, 1, location + '.faces.' + faceName + '.uv');
+ if ([0, 90, 180, 270].indexOf(face.rotation) === -1) {
+ cosmiqFail('geometry.invalid', 'Cuboid face rotation is invalid.', location + '.faces.' + faceName);
+ }
+ });
+ if (!faceCount) cosmiqFail('geometry.invalid', 'Cuboid must contain at least one face.', location);
+ counts.cuboids++;
+ } else if (geometry.kind === 'indexed_triangles') {
+ cosmiqClosedObject(
+ geometry,
+ ['id', 'kind', 'materialId', 'positions', 'normals', 'uvs', 'indices'],
+ location,
+ 'geometry.invalid'
+ );
+ if (!ids.material[geometry.materialId]) {
+ cosmiqFail('reference.missing', 'Mesh material does not resolve.', location + '.materialId');
+ }
+ usage.material[geometry.materialId] = true;
+ const positions = cosmiqValidateBufferView(
+ geometry.positions, 'position', 'geometry_buffer', sectionRecords, coverage, location + '.positions'
+ );
+ const normals = cosmiqValidateBufferView(
+ geometry.normals, 'normal', 'geometry_buffer', sectionRecords, coverage, location + '.normals'
+ );
+ const uvs = cosmiqValidateBufferView(
+ geometry.uvs, 'uv', 'geometry_buffer', sectionRecords, coverage, location + '.uvs'
+ );
+ const indices = cosmiqValidateBufferView(
+ geometry.indices, 'index', 'geometry_buffer', sectionRecords, coverage, location + '.indices'
+ );
+ if (!positions.length || positions.length !== normals.length || positions.length !== uvs.length ||
+ !indices.length || indices.length % 3 !== 0) {
+ cosmiqFail('geometry.invalid', 'Mesh view counts are inconsistent.', location);
+ }
+ positions.forEach(function (position) {
+ if (position.some(function (value) { return value < -64 || value > 64; })) {
+ cosmiqFail('geometry.invalid', 'Mesh position exceeds node-local bounds.', location + '.positions');
+ }
+ });
+ uvs.forEach(function (uv) {
+ if (uv.some(function (value) { return value < 0 || value > 1; })) {
+ cosmiqFail('geometry.invalid', 'Mesh UV is outside [0,1].', location + '.uvs');
+ }
+ });
+ normals.forEach(function (normal) {
+ const length = Math.sqrt(normal[0] * normal[0] + normal[1] * normal[1] + normal[2] * normal[2]);
+ if (Math.abs(length - 1) > 0.0001) {
+ cosmiqFail('geometry.invalid', 'Mesh normal is not unit length.', location + '.normals');
+ }
+ });
+ indices.forEach(function (vertexIndex) {
+ if (vertexIndex >= positions.length) {
+ cosmiqFail('geometry.invalid', 'Mesh index exceeds the vertex count.', location + '.indices');
+ }
+ });
+ for (let triangle = 0; triangle < indices.length; triangle += 3) {
+ const a = positions[indices[triangle]];
+ const b = positions[indices[triangle + 1]];
+ const c = positions[indices[triangle + 2]];
+ const ab = [b[0] - a[0], b[1] - a[1], b[2] - a[2]];
+ const ac = [c[0] - a[0], c[1] - a[1], c[2] - a[2]];
+ const cross = [
+ ab[1] * ac[2] - ab[2] * ac[1],
+ ab[2] * ac[0] - ab[0] * ac[2],
+ ab[0] * ac[1] - ab[1] * ac[0]
+ ];
+ if (cross[0] * cross[0] + cross[1] * cross[1] + cross[2] * cross[2] <= 1e-16) {
+ cosmiqFail('geometry.invalid', 'Mesh contains a degenerate triangle.', location + '.indices');
+ }
+ }
+ counts.indexedPrimitives++;
+ counts.vertices += positions.length;
+ counts.triangles += indices.length / 3;
+ } else {
+ cosmiqFail('geometry.invalid', 'Unknown geometry kind.', location + '.kind');
+ }
+ });
+ }
+
+ function cosmiqValidateActors(manifest, ids) {
+ const boneIds = [
+ 'root', 'body', 'head', 'left_arm', 'right_arm', 'left_leg', 'right_leg',
+ 'torso_lower', 'torso_upper',
+ 'left_arm_upper', 'left_arm_lower', 'right_arm_upper', 'right_arm_lower',
+ 'left_leg_upper', 'left_leg_lower', 'right_leg_upper', 'right_leg_lower'
+ ];
+ const supportedSkeletons = [
+ 'cosmiq:player/humanoid-v1',
+ 'cosmiq:player/humanoid-precise-v1'
+ ];
+ manifest.actors.forEach(function (actor, index) {
+ const location = '$.actors[' + index + ']';
+ cosmiqClosedObject(actor, ['id', 'skeleton', 'required', 'formation'], location);
+ if (supportedSkeletons.indexOf(actor.skeleton) === -1 ||
+ typeof actor.required !== 'boolean') {
+ cosmiqFail('kind.invariant_violation', 'Actor Role declaration is invalid.', location);
+ }
+ cosmiqTransform(actor.formation, location + '.formation', 8);
+ if (JSON.stringify(actor.formation.scale) !== '[1,1,1]') {
+ cosmiqFail('kind.invariant_violation', 'Actor formation scale must be identity.', location + '.formation.scale');
+ }
+ if (index === 0 ? actor.id !== 'host' : actor.id <= manifest.actors[index - 1].id) {
+ cosmiqFail('kind.invariant_violation', 'Actor Roles must be host-first and sorted.', location);
+ }
+ });
+ if (manifest.assetKind === 'emote') {
+ if (manifest.actors.length < 1 || manifest.actors.length > 4 ||
+ !ids.actor.host || ids.actor.host.required !== true) {
+ cosmiqFail('kind.invariant_violation', 'Emote requires one through four roles and a required host.', '$.actors');
+ }
+ } else if (manifest.actors.length) {
+ cosmiqFail('kind.invariant_violation', 'Only Emotes may declare Actor Roles.', '$.actors');
+ }
+ return boneIds;
+ }
+
+ function cosmiqValidateNodes(manifest, ids, usage, counts) {
+ if (manifest.nodes.length > 128) {
+ cosmiqFail('budget.exceeded', 'Node budget exceeded.', '$.nodes');
+ }
+ const anchorGroups = {
+ 'cosmiq:player/head': 'head',
+ 'cosmiq:player/torso': 'torso',
+ 'cosmiq:player/left_arm': 'arms',
+ 'cosmiq:player/right_arm': 'arms',
+ 'cosmiq:player/left_leg': 'legs',
+ 'cosmiq:player/right_leg': 'legs'
+ };
+ const actorAttachments = [
+ 'root', 'body', 'head', 'left_arm', 'right_arm', 'left_leg', 'right_leg',
+ 'left_hand', 'right_hand', 'left_foot', 'right_foot'
+ ];
+ const depthById = Object.create(null);
+ const bodyGroups = Object.create(null);
+ const variantPairs = Object.create(null);
+ const childVariants = Object.create(null);
+ const previousSibling = Object.create(null);
+ const graphState = Object.create(null);
+ function inspectGraph(nodeId) {
+ if (graphState[nodeId] === 1) {
+ cosmiqFail('graph.cycle', 'Node hierarchy contains a cycle.', '$.nodes');
+ }
+ if (graphState[nodeId] === 2) return;
+ graphState[nodeId] = 1;
+ const node = ids.node[nodeId];
+ if (node && typeof node.parentId === 'string' && ids.node[node.parentId]) {
+ inspectGraph(node.parentId);
+ }
+ graphState[nodeId] = 2;
+ }
+ manifest.nodes.forEach(function (node) {
+ if (node && typeof node.id === 'string') inspectGraph(node.id);
+ });
+ manifest.nodes.forEach(function (node, index) {
+ const location = '$.nodes[' + index + ']';
+ cosmiqClosedObject(node, [
+ 'id', 'parentId', 'attachment', 'bind', 'geometryIds', 'modelVariant',
+ 'variantKey', 'defaultVisible'
+ ], location);
+ if (typeof node.defaultVisible !== 'boolean') {
+ cosmiqFail('graph.invalid_owner', 'Node visibility must be boolean.', location + '.defaultVisible');
+ }
+ cosmiqTransform(node.bind, location + '.bind', 8);
+ if (node.parentId === null) {
+ if (!cosmiqPlainObject(node.attachment)) {
+ cosmiqFail('graph.invalid_owner', 'Root node requires one attachment.', location);
+ }
+ depthById[node.id] = 1;
+ if (node.attachment.kind === 'player_anchor') {
+ cosmiqClosedObject(node.attachment, ['kind', 'anchor'], location + '.attachment');
+ if (manifest.assetKind !== 'accessory' || !anchorGroups[node.attachment.anchor]) {
+ cosmiqFail('graph.invalid_owner', 'Player anchor is invalid for this node.', location + '.attachment');
+ }
+ bodyGroups[anchorGroups[node.attachment.anchor]] = true;
+ } else if (node.attachment.kind === 'actor_attachment') {
+ cosmiqClosedObject(node.attachment, ['kind', 'actorRole', 'attachment'], location + '.attachment');
+ if (manifest.assetKind !== 'emote' || !ids.actor[node.attachment.actorRole] ||
+ actorAttachments.indexOf(node.attachment.attachment) === -1) {
+ cosmiqFail('graph.invalid_owner', 'Actor attachment is invalid.', location + '.attachment');
+ }
+ } else if (node.attachment.kind === 'scene') {
+ cosmiqClosedObject(node.attachment, ['kind'], location + '.attachment');
+ if (manifest.assetKind !== 'emote') {
+ cosmiqFail('graph.invalid_owner', 'Scene attachment is Emote-only.', location + '.attachment');
+ }
+ } else {
+ cosmiqFail('graph.invalid_owner', 'Unknown node attachment form.', location + '.attachment');
+ }
+ } else {
+ cosmiqLocalId(node.parentId, location + '.parentId');
+ if (node.attachment !== null || !ids.node[node.parentId] || depthById[node.parentId] == null) {
+ cosmiqFail('graph.invalid_owner', 'Child parent must resolve earlier and attachment must be null.', location);
+ }
+ depthById[node.id] = depthById[node.parentId] + 1;
+ if (depthById[node.id] > 32) cosmiqFail('budget.exceeded', 'Node depth exceeds 32.', location);
+ }
+ const geometryIds = cosmiqArray(node.geometryIds, location + '.geometryIds');
+ let previousGeometry = '';
+ geometryIds.forEach(function (geometryId) {
+ if (!ids.geometry[geometryId] || geometryId <= previousGeometry) {
+ cosmiqFail(
+ ids.geometry[geometryId] ? 'kind.invariant_violation' : 'reference.missing',
+ 'Node geometry IDs must resolve, be sorted, and be unique.',
+ location + '.geometryIds'
+ );
+ }
+ usage.geometry[geometryId] = true;
+ previousGeometry = geometryId;
+ });
+ const siblingKey = node.parentId || 'root';
+ if (previousSibling[siblingKey] && node.id <= previousSibling[siblingKey]) {
+ cosmiqFail('kind.invariant_violation', 'Node siblings are not sorted by ID.', location);
+ }
+ previousSibling[siblingKey] = node.id;
+ if (manifest.assetKind === 'accessory') {
+ if (['universal', 'wide', 'slim'].indexOf(node.modelVariant) === -1 ||
+ (node.modelVariant === 'universal' ? node.variantKey !== null :
+ typeof node.variantKey !== 'string')) {
+ cosmiqFail('kind.invariant_violation', 'Accessory model variant declaration is invalid.', location);
+ }
+ if (node.variantKey !== null) {
+ cosmiqLocalId(node.variantKey, location + '.variantKey');
+ const pair = variantPairs[node.variantKey] || (variantPairs[node.variantKey] = {});
+ if (pair[node.modelVariant]) {
+ cosmiqFail('reference.duplicate_id', 'Duplicate model-variant branch.', location + '.variantKey');
+ }
+ pair[node.modelVariant] = node;
+ if (node.parentId) {
+ childVariants[node.parentId] = childVariants[node.parentId] || [];
+ childVariants[node.parentId].push(node.variantKey);
+ }
+ }
+ } else if (node.modelVariant !== 'universal' || node.variantKey !== null) {
+ cosmiqFail('kind.invariant_violation', 'Non-Accessory nodes use universal/null model variants.', location);
+ }
+ });
+ if (manifest.assetKind === 'accessory') {
+ const groups = Object.keys(bodyGroups);
+ if (!groups.length) cosmiqFail('accessory.no_body_group', 'Accessory has no player anchor.', '$.nodes');
+ if (groups.length > 1) {
+ cosmiqFail('accessory.multiple_body_groups', 'Accessory roots span more than one Body Group.', '$.nodes');
+ }
+ if (manifest.bodyGroup !== groups[0]) {
+ cosmiqFail('accessory.body_group_mismatch', 'Declared Body Group does not match anchors.', '$.bodyGroup');
+ }
+ Object.keys(variantPairs).forEach(function (key) {
+ const pair = variantPairs[key];
+ if (!pair.wide || !pair.slim) {
+ cosmiqFail('kind.invariant_violation', 'Every model variant key requires wide and slim nodes.', '$.nodes');
+ }
+ const attachmentLeft = canonicalCosmiqJson(pair.wide.attachment);
+ const attachmentRight = canonicalCosmiqJson(pair.slim.attachment);
+ const parentLeft = pair.wide.parentId ? ids.node[pair.wide.parentId].variantKey : null;
+ const parentRight = pair.slim.parentId ? ids.node[pair.slim.parentId].variantKey : null;
+ const childrenLeft = (childVariants[pair.wide.id] || []).slice().sort();
+ const childrenRight = (childVariants[pair.slim.id] || []).slice().sort();
+ if (attachmentLeft !== attachmentRight || parentLeft !== parentRight ||
+ canonicalCosmiqJson(childrenLeft) !== canonicalCosmiqJson(childrenRight)) {
+ cosmiqFail('kind.invariant_violation', 'Wide/slim variant topology differs.', '$.nodes');
+ }
+ manifest.clips.forEach(function (clip) {
+ const targetsWide = clip.tracks.some(function (track) {
+ return track.target.kind === 'node' && track.target.nodeId === pair.wide.id;
+ });
+ const targetsSlim = clip.tracks.some(function (track) {
+ return track.target.kind === 'node' && track.target.nodeId === pair.slim.id;
+ });
+ if (targetsWide !== targetsSlim) {
+ cosmiqFail(
+ 'kind.invariant_violation',
+ 'Wide/slim variant animation targets differ.',
+ '$.clips'
+ );
+ }
+ });
+ });
+ const variants = Object.keys(variantPairs).length
+ ? ['wide', 'slim']
+ : ['universal'];
+ if (variants.indexOf(manifest.previewCamera.pose.modelVariant) === -1) {
+ cosmiqFail('camera.invalid', 'Preview model variant is not populated.', '$.previewCamera.pose.modelVariant');
+ }
+ } else if (manifest.assetKind === 'emote') {
+ const propRoots = manifest.nodes.filter(function (node) { return node.parentId === null; });
+ if (propRoots.length > 16) {
+ cosmiqFail('budget.exceeded', 'Emote Prop root budget exceeded.', '$.nodes');
+ }
+ }
+ counts.nodes = manifest.nodes.length;
+ }
+
+ function cosmiqValidateChannel(channel, formatName, clip, sectionRecords, coverage, location, counts) {
+ if (channel === null) return null;
+ cosmiqClosedObject(
+ channel, ['times', 'values', 'interpolations', 'bezierControls'],
+ location, 'animation.invalid'
+ );
+ const times = cosmiqValidateBufferView(
+ channel.times, 'time', 'animation_buffer', sectionRecords, coverage, location + '.times'
+ );
+ const values = cosmiqValidateBufferView(
+ channel.values, formatName, 'animation_buffer', sectionRecords, coverage, location + '.values'
+ );
+ const interpolations = cosmiqValidateBufferView(
+ channel.interpolations, 'interpolation', 'animation_buffer', sectionRecords, coverage,
+ location + '.interpolations'
+ );
+ const bezierControls = channel.bezierControls === null ? null : cosmiqValidateBufferView(
+ channel.bezierControls, 'bezier', 'animation_buffer', sectionRecords, coverage,
+ location + '.bezierControls'
+ );
+ if (!times.length || times.length !== values.length ||
+ interpolations.length !== Math.max(0, times.length - 1) ||
+ (bezierControls !== null && bezierControls.length !== interpolations.length)) {
+ cosmiqFail('animation.invalid', 'Animation channel view counts are inconsistent.', location);
+ }
+ let previous = -1;
+ times.forEach(function (time) {
+ if (time <= previous || time > clip.durationMs) {
+ cosmiqFail('animation.invalid', 'Keyframe times must strictly increase within the clip.', location + '.times');
+ }
+ previous = time;
+ });
+ interpolations.forEach(function (value) {
+ if (value !== 0 && value !== 1 && value !== 2 && value !== 3) {
+ cosmiqFail('animation.invalid', 'Unknown interpolation value.', location + '.interpolations');
+ }
+ });
+ const hasBezier = interpolations.indexOf(3) !== -1;
+ if (hasBezier !== (bezierControls !== null)) {
+ cosmiqFail('animation.invalid', 'Bezier segments require one control record per segment.', location);
+ }
+ if (bezierControls) bezierControls.forEach(function (control, index) {
+ if (interpolations[index] !== 3 && control.some(function (value) { return value !== 0; })) {
+ cosmiqFail('animation.invalid', 'Unused Bezier control records must be zero.', location);
+ }
+ if (interpolations[index] === 3 && control.slice(0, 6).some(function (value) {
+ return value < 0 || value > 1;
+ })) {
+ cosmiqFail('animation.invalid', 'Bezier time controls must be normalized.', location);
+ }
+ });
+ if (formatName === 'rotation') {
+ values.forEach(function (rotation) {
+ const length = Math.sqrt(rotation.reduce(function (sum, value) { return sum + value * value; }, 0));
+ if (Math.abs(length - 1) > 0.0001) {
+ cosmiqFail('animation.invalid', 'Animation quaternion is not normalized.', location + '.values');
+ }
+ });
+ } else if (formatName === 'translation') {
+ values.forEach(function (translation) {
+ if (translation.some(function (value) { return value < -8 || value > 8; })) {
+ cosmiqFail('animation.invalid', 'Animation translation exceeds its bounds.', location + '.values');
+ }
+ });
+ }
+ counts.keyframes += times.length;
+ return values;
+ }
+
+ function cosmiqValidateClips(manifest, ids, sectionRecords, coverage, usage, counts) {
+ const bones = [
+ 'root', 'body', 'head', 'left_arm', 'right_arm', 'left_leg', 'right_leg',
+ 'torso_lower', 'torso_upper',
+ 'left_arm_upper', 'left_arm_lower', 'right_arm_upper', 'right_arm_lower',
+ 'left_leg_upper', 'left_leg_lower', 'right_leg_upper', 'right_leg_lower'
+ ];
+ manifest.clips.forEach(function (clip, index) {
+ const location = '$.clips[' + index + ']';
+ cosmiqClosedObject(clip, ['id', 'durationMs', 'loop', 'tracks'], location, 'animation.invalid');
+ cosmiqNumber(clip.durationMs, 1, 120000, location + '.durationMs', true);
+ cosmiqClosedObject(clip.loop, ['mode', 'startMs', 'endMs'], location + '.loop', 'animation.invalid');
+ cosmiqNumber(clip.loop.startMs, 0, clip.durationMs, location + '.loop.startMs', true);
+ cosmiqNumber(clip.loop.endMs, 0, clip.durationMs, location + '.loop.endMs', true);
+ if ((clip.loop.mode === 'none' && (clip.loop.startMs !== 0 || clip.loop.endMs !== clip.durationMs)) ||
+ (clip.loop.mode === 'range' && clip.loop.startMs >= clip.loop.endMs) ||
+ ['none', 'range'].indexOf(clip.loop.mode) === -1) {
+ cosmiqFail('animation.invalid', 'Clip loop range is invalid.', location + '.loop');
+ }
+ const targets = Object.create(null);
+ let previousTargetKey = '';
+ cosmiqArray(clip.tracks, location + '.tracks').forEach(function (track, trackIndex) {
+ const trackLocation = location + '.tracks[' + trackIndex + ']';
+ cosmiqClosedObject(
+ track, ['target', 'translation', 'rotation', 'scale'], trackLocation, 'animation.invalid'
+ );
+ let targetKey;
+ let actorBone = false;
+ if (track.target && track.target.kind === 'node') {
+ cosmiqClosedObject(track.target, ['kind', 'nodeId'], trackLocation + '.target');
+ if (!ids.node[track.target.nodeId]) {
+ cosmiqFail('reference.missing', 'Animation target node does not resolve.', trackLocation + '.target');
+ }
+ targetKey = 'node:' + track.target.nodeId;
+ } else if (track.target && track.target.kind === 'actor_bone') {
+ cosmiqClosedObject(
+ track.target, ['kind', 'actorRole', 'bone'], trackLocation + '.target'
+ );
+ if (!ids.actor[track.target.actorRole] || bones.indexOf(track.target.bone) === -1) {
+ cosmiqFail('reference.missing', 'Actor-bone target does not resolve.', trackLocation + '.target');
+ }
+ if (manifest.assetKind !== 'emote') {
+ cosmiqFail(
+ 'animation.forbidden_player_target',
+ 'Only Emotes may animate player bones.',
+ trackLocation + '.target'
+ );
+ }
+ actorBone = true;
+ targetKey = 'actor:' + track.target.actorRole + ':' + track.target.bone;
+ } else {
+ cosmiqFail('animation.invalid', 'Unknown animation target form.', trackLocation + '.target');
+ }
+ if (targets[targetKey]) cosmiqFail('animation.invalid', 'Clip target is duplicated.', trackLocation + '.target');
+ if (targetKey <= previousTargetKey) {
+ cosmiqFail('animation.invalid', 'Clip tracks are not in canonical target order.', trackLocation);
+ }
+ previousTargetKey = targetKey;
+ targets[targetKey] = true;
+ const translation = cosmiqValidateChannel(
+ track.translation, 'translation', clip, sectionRecords, coverage,
+ trackLocation + '.translation', counts
+ );
+ cosmiqValidateChannel(
+ track.rotation, 'rotation', clip, sectionRecords, coverage,
+ trackLocation + '.rotation', counts
+ );
+ const scales = cosmiqValidateChannel(
+ track.scale, 'scale', clip, sectionRecords, coverage,
+ trackLocation + '.scale', counts
+ );
+ if (track.translation === null && track.rotation === null && track.scale === null) {
+ cosmiqFail('animation.invalid', 'Animation track has no channels.', trackLocation);
+ }
+ if (translation && translation.some(function (value) {
+ return value.some(function (component) { return component < -8 || component > 8; });
+ })) {
+ cosmiqFail('animation.invalid', 'Translation keyframe is out of range.', trackLocation);
+ }
+ if (scales && scales.some(function (value) {
+ return value.some(function (component) {
+ return component < (actorBone ? 0.05 : 0.01) || component > (actorBone ? 4 : 16);
+ });
+ })) {
+ cosmiqFail('animation.invalid', 'Scale keyframe is out of range.', trackLocation);
+ }
+ });
+ if (!clip.tracks.length) cosmiqFail('animation.invalid', 'Clip must have at least one track.', location);
+ });
+ }
+
+ function cosmiqValidateMovement(manifest) {
+ if (manifest.assetKind !== 'emote') {
+ if (manifest.movementPolicy !== null) {
+ cosmiqFail('kind.invariant_violation', 'Only Emotes declare movement policy.', '$.movementPolicy');
+ }
+ return;
+ }
+ const movement = manifest.movementPolicy;
+ cosmiqClosedObject(
+ movement, ['mode', 'cancelOnAttack', 'cancelOnDamage', 'allowCameraControl'], '$.movementPolicy'
+ );
+ if (['stationary', 'upper_body', 'full_body_traversable', 'visual_root_motion'].indexOf(movement.mode) === -1 ||
+ typeof movement.cancelOnAttack !== 'boolean' || typeof movement.cancelOnDamage !== 'boolean' ||
+ typeof movement.allowCameraControl !== 'boolean') {
+ cosmiqFail('kind.invariant_violation', 'Emote movement policy is invalid.', '$.movementPolicy');
+ }
+ if (movement.mode === 'upper_body') {
+ manifest.clips.forEach(function (clip) {
+ clip.tracks.forEach(function (track) {
+ if (track.target.kind === 'actor_bone' &&
+ ['root', 'left_leg', 'right_leg'].indexOf(track.target.bone) !== -1) {
+ cosmiqFail('animation.invalid', 'Upper-body Emote targets a forbidden bone.', '$.clips');
+ }
+ });
+ });
+ }
+ }
+
+ function cosmiqValidateStateBindings(manifest, ids, usage) {
+ const states = [
+ 'idle', 'walking', 'sprinting', 'crouching', 'crouch_walking', 'jumping', 'falling',
+ 'riding', 'sleeping', 'in_water', 'swimming', 'crawling', 'fall_flying', 'climbing',
+ 'creative_flying', 'using_item', 'blocking', 'fishing', 'rowing', 'spin_attack', 'dead'
+ ];
+ const events = ['attack', 'hurt', 'land'];
+ const triggers = Object.create(null);
+ let fallbackCount = 0;
+ manifest.stateBindings.forEach(function (binding, index) {
+ const location = '$.stateBindings[' + index + ']';
+ cosmiqClosedObject(
+ binding, ['trigger', 'clipId', 'fallback', 'playback', 'blendMillis', 'priority'], location
+ );
+ cosmiqClosedObject(binding.trigger, ['kind', 'id'], location + '.trigger');
+ const allowed = binding.trigger.kind === 'player_state' ? states :
+ binding.trigger.kind === 'player_event' ? events : [];
+ if (allowed.indexOf(binding.trigger.id) === -1) {
+ cosmiqFail('kind.invariant_violation', 'Accessory trigger is invalid.', location + '.trigger');
+ }
+ const triggerKey = binding.trigger.kind + ':' + binding.trigger.id;
+ if (triggers[triggerKey]) cosmiqFail('reference.duplicate_id', 'Accessory trigger is duplicated.', location);
+ triggers[triggerKey] = true;
+ if (!ids.clip[binding.clipId]) cosmiqFail('reference.missing', 'State Binding clip does not resolve.', location);
+ usage.clip[binding.clipId] = true;
+ if (typeof binding.fallback !== 'boolean' ||
+ ['loop', 'once'].indexOf(binding.playback) === -1 ||
+ (binding.trigger.kind === 'player_event' && (binding.playback !== 'once' || binding.fallback))) {
+ cosmiqFail('kind.invariant_violation', 'State Binding playback is invalid.', location);
+ }
+ cosmiqNumber(binding.blendMillis, 0, 2000, location + '.blendMillis', true);
+ cosmiqNumber(binding.priority, 0, 1000, location + '.priority', true);
+ if (binding.fallback) {
+ fallbackCount++;
+ if (triggerKey !== 'player_state:idle') {
+ cosmiqFail('kind.invariant_violation', 'Fallback must bind player_state:idle.', location);
+ }
+ }
+ });
+ if (manifest.assetKind === 'accessory') {
+ if ((!manifest.clips.length && manifest.stateBindings.length) ||
+ (manifest.clips.length && (!manifest.stateBindings.length || fallbackCount !== 1))) {
+ cosmiqFail('kind.invariant_violation', 'Accessory clips and State Bindings are inconsistent.', '$.stateBindings');
+ }
+ if (manifest.previewCamera.pose.clipId !== null &&
+ !manifest.stateBindings.some(function (binding) {
+ return binding.clipId === manifest.previewCamera.pose.clipId;
+ })) {
+ cosmiqFail(
+ 'camera.invalid',
+ 'Accessory preview clip must be referenced by a State Binding.',
+ '$.previewCamera.pose.clipId'
+ );
+ }
+ } else if (manifest.stateBindings.length) {
+ cosmiqFail('kind.invariant_violation', 'State Bindings are Accessory-only.', '$.stateBindings');
+ }
+ }
+
+ function cosmiqValidateLogic(manifest, ids, usage) {
+ if (manifest.assetKind !== 'emote') {
+ if (manifest.logic !== null || manifest.outcomeSets.length) {
+ cosmiqFail('logic.invalid', 'Logic and Outcome Sets are Emote-only.', '$.logic');
+ }
+ return;
+ }
+ const logic = manifest.logic;
+ cosmiqClosedObject(logic, ['initialState', 'states'], '$.logic', 'logic.invalid');
+ cosmiqLocalId(logic.initialState, '$.logic.initialState');
+ const stateIds = Object.create(null);
+ if (!logic.states.length || logic.states.length > 16) {
+ cosmiqFail('logic.invalid', 'Logic state count is invalid.', '$.logic.states');
+ }
+ let totalRules = 0;
+ logic.states.forEach(function (state, index) {
+ const location = '$.logic.states[' + index + ']';
+ cosmiqClosedObject(state, ['id', 'rules'], location, 'logic.invalid');
+ cosmiqLocalId(state.id, location + '.id');
+ if (stateIds[state.id]) cosmiqFail('reference.duplicate_id', 'Duplicate logic state.', location);
+ stateIds[state.id] = state;
+ cosmiqArray(state.rules, location + '.rules');
+ if (state.rules.length > 16) cosmiqFail('logic.invalid', 'Too many rules in one state.', location);
+ totalRules += state.rules.length;
+ if (index > 1 && state.id <= logic.states[index - 1].id) {
+ cosmiqFail('logic.invalid', 'Logic states are not in canonical order.', location);
+ }
+ });
+ if (!stateIds[logic.initialState] || logic.states[0].id !== logic.initialState || totalRules > 64) {
+ cosmiqFail('logic.invalid', 'Initial logic state or rule budget is invalid.', '$.logic');
+ }
+ function nodeOwner(nodeId) {
+ let node = ids.node[nodeId];
+ while (node && node.parentId !== null) node = ids.node[node.parentId];
+ if (!node || !node.attachment) return null;
+ return node.attachment.kind === 'actor_attachment'
+ ? node.attachment.actorRole
+ : 'scene';
+ }
+ function actionSelectsClip(action, location) {
+ if (action.target === 'all') return;
+ const clip = ids.clip[action.clipId];
+ let requiresAll = false;
+ const requiredRoles = Object.create(null);
+ clip.tracks.forEach(function (track) {
+ if (track.target.kind === 'actor_bone') {
+ requiredRoles[track.target.actorRole] = true;
+ } else {
+ const owner = nodeOwner(track.target.nodeId);
+ if (owner === 'scene') requiresAll = true;
+ else if (owner) requiredRoles[owner] = true;
+ }
+ });
+ manifest.cues.forEach(function (cue) {
+ if (cue.clipId !== clip.id) return;
+ if (cue.kind === 'sound' || cue.kind === 'particle') {
+ requiresAll = true;
+ } else if (cue.kind === 'node_visibility') {
+ const owner = nodeOwner(cue.nodeId);
+ if (owner === 'scene') requiresAll = true;
+ else if (owner) requiredRoles[owner] = true;
+ }
+ });
+ if (requiresAll || Object.keys(requiredRoles).some(function (role) {
+ return role !== action.target;
+ })) {
+ cosmiqFail(
+ 'logic.invalid',
+ 'Play-animation target cannot select every track and Cue owned by the clip.',
+ location
+ );
+ }
+ }
+ logic.states.forEach(function (state, stateIndex) {
+ const actionsByEvent = Object.create(null);
+ state.rules.forEach(function (rule, ruleIndex) {
+ const location = '$.logic.states[' + stateIndex + '].rules[' + ruleIndex + ']';
+ cosmiqClosedObject(rule, ['event', 'action'], location, 'logic.invalid');
+ const event = rule.event;
+ const action = rule.action;
+ if (!cosmiqPlainObject(event) || !cosmiqPlainObject(action)) {
+ cosmiqFail('logic.invalid', 'Logic event and action must be objects.', location);
+ }
+ switch (event.type) {
+ case 'enter':
+ case 'cancel':
+ cosmiqClosedObject(event, ['type'], location + '.event', 'logic.invalid');
+ break;
+ case 'participant_joined':
+ cosmiqClosedObject(event, ['type', 'actorRole'], location + '.event', 'logic.invalid');
+ if (!ids.actor[event.actorRole]) cosmiqFail('reference.missing', 'Logic Actor Role missing.', location);
+ break;
+ case 'animation_finished':
+ cosmiqClosedObject(event, ['type', 'clipId'], location + '.event', 'logic.invalid');
+ if (!ids.clip[event.clipId]) cosmiqFail('reference.missing', 'Logic clip missing.', location);
+ usage.clip[event.clipId] = true;
+ break;
+ case 'input_pressed':
+ cosmiqClosedObject(event, ['type', 'input'], location + '.event', 'logic.invalid');
+ if (!/^action_[1-4]$/.test(event.input)) cosmiqFail('logic.invalid', 'Unknown logical input.', location);
+ break;
+ case 'timer_elapsed':
+ cosmiqClosedObject(event, ['type', 'afterMillis'], location + '.event', 'logic.invalid');
+ cosmiqNumber(event.afterMillis, 1, 120000, location + '.event.afterMillis', true);
+ break;
+ case 'outcome_resolved':
+ cosmiqClosedObject(event, ['type', 'outcomeSetId', 'value'], location + '.event', 'logic.invalid');
+ if (!ids.outcome[event.outcomeSetId] ||
+ ids.outcome[event.outcomeSetId].values.indexOf(event.value) === -1) {
+ cosmiqFail('reference.missing', 'Logic outcome does not resolve.', location);
+ }
+ usage.outcomeEvent[event.outcomeSetId] = true;
+ break;
+ default:
+ cosmiqFail('logic.invalid', 'Unknown logic event.', location + '.event');
+ }
+ switch (action.type) {
+ case 'play_animation':
+ cosmiqClosedObject(
+ action,
+ ['type', 'clipId', 'target', 'mode', 'loopDurationMillis', 'blendMillis'],
+ location + '.action',
+ 'logic.invalid'
+ );
+ if (!ids.clip[action.clipId] || (action.target !== 'all' && !ids.actor[action.target]) ||
+ ['once', 'loop_until_interrupted', 'loop_for'].indexOf(action.mode) === -1) {
+ cosmiqFail('logic.invalid', 'Play-animation action is invalid.', location + '.action');
+ }
+ if (event.type !== 'enter' && action.mode !== 'once') {
+ cosmiqFail('logic.invalid', 'Only enter rules may start loops.', location + '.action');
+ }
+ if (action.mode === 'loop_for') {
+ cosmiqNumber(action.loopDurationMillis, 50, 120000, location + '.action.loopDurationMillis', true);
+ } else if (action.loopDurationMillis !== null) {
+ cosmiqFail('logic.invalid', 'Only loop_for declares a loop duration.', location + '.action');
+ }
+ cosmiqNumber(action.blendMillis, 0, 2000, location + '.action.blendMillis', true);
+ usage.clip[action.clipId] = true;
+ break;
+ case 'transition':
+ cosmiqClosedObject(action, ['type', 'state'], location + '.action', 'logic.invalid');
+ if (!stateIds[action.state]) cosmiqFail('reference.missing', 'Transition state missing.', location);
+ break;
+ case 'complete':
+ cosmiqClosedObject(action, ['type'], location + '.action', 'logic.invalid');
+ break;
+ case 'request_outcome':
+ cosmiqClosedObject(action, ['type', 'outcomeSetId'], location + '.action', 'logic.invalid');
+ if (!ids.outcome[action.outcomeSetId]) cosmiqFail('reference.missing', 'Outcome Set missing.', location);
+ usage.outcomeRequest[action.outcomeSetId] = true;
+ break;
+ default:
+ cosmiqFail('logic.invalid', 'Unknown logic action.', location + '.action');
+ }
+ const eventKey = canonicalCosmiqJson(event);
+ const group = actionsByEvent[eventKey] || (actionsByEvent[eventKey] = []);
+ group.push({action: action, location: location + '.action'});
+ if (action.type === 'play_animation') actionSelectsClip(action, location + '.action');
+ });
+ Object.keys(actionsByEvent).forEach(function (eventKey) {
+ const group = actionsByEvent[eventKey];
+ if (group.length < 2) return;
+ if (group.some(function (entry) { return entry.action.type !== 'play_animation'; })) {
+ cosmiqFail(
+ 'logic.invalid',
+ 'An exact event cannot mix or repeat terminal, transition, or outcome actions.',
+ group[1].location
+ );
+ }
+ const targets = Object.create(null);
+ group.forEach(function (entry) {
+ const target = entry.action.target;
+ if (target === 'all' || targets.all || targets[target]) {
+ cosmiqFail(
+ 'logic.invalid',
+ 'An exact event may start only non-overlapping Actor Role targets.',
+ entry.location
+ );
+ }
+ targets[target] = true;
+ });
+ });
+ });
+ if (encodeUtf8(canonicalCosmiqJson(logic)).length > 64 * 1024) {
+ cosmiqFail('budget.exceeded', 'Canonical logic JSON exceeds 64 KiB.', '$.logic');
+ }
+ }
+
+ function cosmiqValidateEffects(manifest, ids, sectionRecords, usage) {
+ const audioSections = Object.create(null);
+ let totalAudioDuration = 0;
+ manifest.audio.forEach(function (audio, index) {
+ const location = '$.audio[' + index + ']';
+ cosmiqClosedObject(
+ audio, ['id', 'section', 'kind', 'sampleRateHz', 'channels', 'durationMs'], location
+ );
+ const section = sectionRecords[audio.section];
+ if (!section || section.type !== 'audio_ogg_vorbis' || audioSections[audio.section]) {
+ cosmiqFail('reference.missing', 'Audio section is missing or reused.', location + '.section');
+ }
+ audioSections[audio.section] = true;
+ if (['sound_effect', 'music'].indexOf(audio.kind) === -1) {
+ cosmiqFail('media.invalid_ogg_vorbis', 'Audio kind is invalid.', location + '.kind');
+ }
+ const inspected = cosmiqInspectOggVorbis(section.bytes, location);
+ if (audio.sampleRateHz !== inspected.sampleRateHz || audio.channels !== inspected.channels ||
+ audio.durationMs !== inspected.durationMs || audio.durationMs < 1 || audio.durationMs > 120000) {
+ cosmiqFail('media.invalid_ogg_vorbis', 'Declared audio metadata does not match the stream.', location);
+ }
+ totalAudioDuration += audio.durationMs;
+ });
+ sectionRecords.slice(1).forEach(function (section) {
+ if (section.type === 'audio_ogg_vorbis' && !audioSections[section.index]) {
+ cosmiqFail('reference.unreferenced', 'Ogg section has no Audio record.', '$.sections');
+ }
+ });
+
+ const particleLimits = {
+ sparkle: [16, 900, 24],
+ burst: [24, 600, 40],
+ trail: [32, 2000, 20],
+ ring: [32, 1200, 32],
+ smoke_puff: [12, 1800, 12]
+ };
+ manifest.particles.forEach(function (particle, index) {
+ const location = '$.particles[' + index + ']';
+ cosmiqClosedObject(particle, [
+ 'id', 'template', 'textureId', 'maxParticles', 'durationMs', 'ratePerSecond',
+ 'size', 'color', 'localVelocity'
+ ], location);
+ const limits = particleLimits[particle.template];
+ if (!limits) cosmiqFail('kind.invariant_violation', 'Particle template is invalid.', location + '.template');
+ cosmiqNumber(particle.maxParticles, 1, limits[0], location + '.maxParticles', true);
+ cosmiqNumber(particle.durationMs, 1, limits[1], location + '.durationMs', true);
+ cosmiqNumber(particle.ratePerSecond, Number.MIN_VALUE, limits[2], location + '.ratePerSecond');
+ cosmiqNumber(particle.size, Number.MIN_VALUE, 2, location + '.size');
+ cosmiqVector(particle.color, 4, 0, 1, location + '.color');
+ cosmiqVector(particle.localVelocity, 3, -8, 8, location + '.localVelocity');
+ if (particle.textureId !== null) {
+ if (!ids.texture[particle.textureId]) {
+ cosmiqFail('reference.missing', 'Particle texture does not resolve.', location + '.textureId');
+ }
+ usage.texture[particle.textureId] = true;
+ }
+ });
+
+ const actorAttachments = [
+ 'root', 'body', 'head', 'left_arm', 'right_arm', 'left_leg', 'right_leg',
+ 'left_hand', 'right_hand', 'left_foot', 'right_foot'
+ ];
+ manifest.locators.forEach(function (locator, index) {
+ const location = '$.locators[' + index + ']';
+ cosmiqClosedObject(locator, ['id', 'target', 'transform'], location);
+ if (locator.target && locator.target.kind === 'node') {
+ cosmiqClosedObject(locator.target, ['kind', 'nodeId'], location + '.target');
+ if (!ids.node[locator.target.nodeId]) {
+ cosmiqFail('reference.missing', 'Locator node does not resolve.', location + '.target');
+ }
+ } else if (locator.target && locator.target.kind === 'actor_attachment') {
+ cosmiqClosedObject(
+ locator.target, ['kind', 'actorRole', 'attachment'], location + '.target'
+ );
+ if (manifest.assetKind !== 'emote' || !ids.actor[locator.target.actorRole] ||
+ actorAttachments.indexOf(locator.target.attachment) === -1) {
+ cosmiqFail('reference.missing', 'Locator Actor attachment does not resolve.', location + '.target');
+ }
+ } else {
+ cosmiqFail('kind.invariant_violation', 'Unknown Locator target.', location + '.target');
+ }
+ cosmiqTransform(locator.transform, location + '.transform', 8);
+ if (JSON.stringify(locator.transform.scale) !== '[1,1,1]') {
+ cosmiqFail('kind.invariant_violation', 'Locator scale must be identity.', location + '.transform.scale');
+ }
+ });
+
+ let previousCueKey = '';
+ const cueIds = Object.create(null);
+ manifest.cues.forEach(function (cue, index) {
+ const location = '$.cues[' + index + ']';
+ cosmiqClosedObject(cue, [
+ 'id', 'clipId', 'atMs', 'endAtMs', 'kind', 'resourceId',
+ 'locatorId', 'nodeId', 'visible', 'gain'
+ ], location);
+ cosmiqLocalId(cue.id, location + '.id');
+ if (cueIds[cue.id]) cosmiqFail('reference.duplicate_id', 'Timeline Cue ID is duplicated.', location);
+ cueIds[cue.id] = true;
+ const clip = ids.clip[cue.clipId];
+ if (!clip) cosmiqFail('reference.missing', 'Cue clip does not resolve.', location + '.clipId');
+ cosmiqNumber(cue.atMs, 0, clip.durationMs, location + '.atMs', true);
+ if (cue.endAtMs !== null) {
+ cosmiqNumber(cue.endAtMs, cue.atMs + 1, clip.durationMs, location + '.endAtMs', true);
+ }
+ const cueKey = cue.clipId + '\u0000' + String(cue.atMs).padStart(9, '0') + '\u0000' + cue.id;
+ if (cueKey <= previousCueKey) cosmiqFail('kind.invariant_violation', 'Cues are not canonically sorted.', location);
+ previousCueKey = cueKey;
+ if (cue.kind === 'sound') {
+ if (!ids.audio[cue.resourceId] || cue.nodeId !== null || cue.visible !== null ||
+ (cue.locatorId !== null && !ids.locator[cue.locatorId])) {
+ cosmiqFail('reference.missing', 'Sound Cue references are invalid.', location);
+ }
+ cosmiqNumber(cue.gain, 0, 4, location + '.gain');
+ usage.audio[cue.resourceId] = true;
+ if (cue.locatorId !== null) usage.locator[cue.locatorId] = true;
+ } else if (cue.kind === 'particle') {
+ if (!ids.particle[cue.resourceId] || !ids.locator[cue.locatorId] ||
+ cue.nodeId !== null || cue.visible !== null || cue.gain !== null) {
+ cosmiqFail('reference.missing', 'Particle Cue references are invalid.', location);
+ }
+ usage.particle[cue.resourceId] = true;
+ usage.locator[cue.locatorId] = true;
+ } else if (cue.kind === 'node_visibility') {
+ if (manifest.assetKind !== 'emote' || !ids.node[cue.nodeId] ||
+ typeof cue.visible !== 'boolean' || cue.resourceId !== null ||
+ cue.locatorId !== null || cue.gain !== null) {
+ cosmiqFail('reference.missing', 'Node-visibility Cue references are invalid.', location);
+ }
+ } else {
+ cosmiqFail('kind.invariant_violation', 'Unknown Timeline Cue kind.', location + '.kind');
+ }
+ });
+ Object.keys(ids.audio).forEach(function (id) {
+ if (!usage.audio[id]) cosmiqFail('reference.unreferenced', 'Audio record is unreferenced.', '$.audio');
+ });
+ Object.keys(ids.particle).forEach(function (id) {
+ if (!usage.particle[id]) cosmiqFail('reference.unreferenced', 'Particle is unreferenced.', '$.particles');
+ });
+ Object.keys(ids.locator).forEach(function (id) {
+ if (!usage.locator[id]) cosmiqFail('reference.unreferenced', 'Locator is unreferenced.', '$.locators');
+ });
+ if (manifest.audio.length > 8 || totalAudioDuration > 480000 ||
+ manifest.particles.length > 16 || manifest.locators.length > 64 ||
+ manifest.cues.length > (manifest.assetKind === 'emote' ? 1024 : 512)) {
+ cosmiqFail('budget.exceeded', 'Effect budget exceeded.', '$');
+ }
+ }
+
+ function cosmiqValidatePlayerAppearance(manifest) {
+ const appearance = Object.hasOwn(manifest, 'playerAppearance')
+ ? manifest.playerAppearance
+ : null;
+ if (appearance === null) return;
+ if (manifest.assetKind !== 'accessory') {
+ cosmiqFail(
+ 'kind.invariant_violation',
+ 'Only Accessories can alter player-part appearance.',
+ '$.playerAppearance'
+ );
+ }
+ cosmiqClosedObject(
+ appearance,
+ ['occlusions'],
+ '$.playerAppearance',
+ 'appearance.invalid'
+ );
+ const occlusions = cosmiqArray(
+ appearance.occlusions,
+ '$.playerAppearance.occlusions'
+ );
+ if (!occlusions.length || occlusions.length > 6) {
+ cosmiqFail(
+ 'appearance.invalid',
+ 'Player appearance must occlude one through six body parts.',
+ '$.playerAppearance.occlusions'
+ );
+ }
+ const partGroup = {
+ head: 'head',
+ body: 'torso',
+ left_arm: 'arms',
+ right_arm: 'arms',
+ left_leg: 'legs',
+ right_leg: 'legs'
+ };
+ let previousPart = '';
+ occlusions.forEach(function (occlusion, index) {
+ const location = '$.playerAppearance.occlusions[' + index + ']';
+ cosmiqClosedObject(
+ occlusion,
+ ['part', 'layers'],
+ location,
+ 'appearance.invalid'
+ );
+ if (!partGroup[occlusion.part] || occlusion.part <= previousPart) {
+ cosmiqFail(
+ 'appearance.invalid',
+ 'Occluded parts must be supported, sorted, and unique.',
+ location + '.part'
+ );
+ }
+ if (partGroup[occlusion.part] !== manifest.bodyGroup) {
+ cosmiqFail(
+ 'appearance.invalid',
+ 'An Accessory can only occlude parts in its declared Body Group.',
+ location + '.part'
+ );
+ }
+ previousPart = occlusion.part;
+ const layers = cosmiqArray(occlusion.layers, location + '.layers');
+ if (!layers.length || layers.length > 2) {
+ cosmiqFail(
+ 'appearance.invalid',
+ 'Each occlusion must select at least one player layer.',
+ location + '.layers'
+ );
+ }
+ let previousLayer = '';
+ layers.forEach(function (layer, layerIndex) {
+ if (['base', 'outer'].indexOf(layer) === -1 || layer <= previousLayer) {
+ cosmiqFail(
+ 'appearance.invalid',
+ 'Player layers must be supported, sorted, and unique.',
+ location + '.layers[' + layerIndex + ']'
+ );
+ }
+ previousLayer = layer;
+ });
+ });
+ }
+
+ function cosmiqValidateManifest(manifest, sectionRecords) {
+ cosmiqClosedManifest(manifest);
+ if (manifest.schemaVersion !== COSMIQ_RUNTIME_SCHEMA) {
+ cosmiqFail('manifest.unsupported_schema', 'Unsupported Package Manifest schema.', '$.schemaVersion');
+ }
+ if (manifest.coordinateProfile !== COSMIQ_STANDARD_COORDINATES ||
+ ['accessory', 'cape', 'emote'].indexOf(manifest.assetKind) === -1) {
+ cosmiqFail('kind.invariant_violation', 'Asset Kind or coordinate profile is invalid.', '$');
+ }
+ [
+ 'sections', 'actors', 'nodes', 'materials', 'geometry', 'textures', 'textureVariants',
+ 'clips', 'stateBindings', 'audio', 'particles', 'locators', 'cues', 'outcomeSets'
+ ].forEach(function (field) { cosmiqArray(manifest[field], '$.' + field); });
+
+ const descriptors = Object.create(null);
+ manifest.sections.forEach(function (descriptor, index) {
+ const location = '$.sections[' + index + ']';
+ cosmiqClosedObject(descriptor, ['index', 'id', 'type'], location);
+ if (descriptor.index !== index + 1 || descriptors[descriptor.id]) {
+ cosmiqFail('directory.invalid', 'Section descriptors must be contiguous and unique.', location);
+ }
+ cosmiqLocalId(descriptor.id, location + '.id');
+ if (!COSMIQ_STANDARD_SECTION_TYPES[descriptor.type]) {
+ cosmiqFail('directory.unknown_section_type', 'Unknown section descriptor type.', location + '.type');
+ }
+ const record = sectionRecords[descriptor.index];
+ if (!record || record.type !== descriptor.type) {
+ cosmiqFail('directory.invalid', 'Section descriptor does not match the directory.', location);
+ }
+ descriptors[descriptor.id] = descriptor;
+ });
+ if (manifest.sections.length + 1 !== sectionRecords.length) {
+ cosmiqFail('directory.invalid', 'Every non-manifest directory entry needs one descriptor.', '$.sections');
+ }
+
+ const ids = {
+ actor: Object.create(null), node: Object.create(null), material: Object.create(null),
+ geometry: Object.create(null), texture: Object.create(null), clip: Object.create(null),
+ audio: Object.create(null), particle: Object.create(null), locator: Object.create(null),
+ outcome: Object.create(null)
+ };
+ function unique(collection, kind) {
+ collection.forEach(function (record, index) {
+ const location = '$.' + kind + 's[' + index + '].id';
+ cosmiqLocalId(record && record.id, location);
+ if (ids[kind][record.id]) cosmiqFail('reference.duplicate_id', 'Duplicate ID.', location);
+ ids[kind][record.id] = record;
+ });
+ }
+ unique(manifest.actors, 'actor');
+ unique(manifest.nodes, 'node');
+ unique(manifest.materials, 'material');
+ unique(manifest.geometry, 'geometry');
+ unique(manifest.textures, 'texture');
+ unique(manifest.clips, 'clip');
+ unique(manifest.audio, 'audio');
+ unique(manifest.particles, 'particle');
+ unique(manifest.locators, 'locator');
+ unique(manifest.outcomeSets, 'outcome');
+ [
+ ['materials', manifest.materials],
+ ['geometry', manifest.geometry],
+ ['textures', manifest.textures],
+ ['clips', manifest.clips],
+ ['audio', manifest.audio],
+ ['particles', manifest.particles],
+ ['locators', manifest.locators],
+ ['outcomeSets', manifest.outcomeSets]
+ ].forEach(function (entry) {
+ cosmiqRequireSorted(entry[1], function (record) { return record.id; }, '$.' + entry[0]);
+ });
+ if (manifest.textureVariants.length > 1) {
+ cosmiqRequireSorted(
+ manifest.textureVariants,
+ function (variant) { return variant.id; },
+ '$.textureVariants',
+ 1
+ );
+ }
+ cosmiqRequireSorted(manifest.stateBindings, function (binding) {
+ return binding.trigger.kind + '\u0000' + binding.trigger.id;
+ }, '$.stateBindings');
+
+ const clipDurations = Object.create(null);
+ manifest.clips.forEach(function (clip) {
+ clipDurations[clip.id] = clip.durationMs;
+ });
+ const rootNodeIds = Object.create(null);
+ manifest.nodes.forEach(function (node) {
+ if (node && node.parentId === null) rootNodeIds[node.id] = true;
+ });
+ cosmiqValidatePreviewCamera(manifest, clipDurations, rootNodeIds);
+
+ manifest.materials.forEach(function (material, index) {
+ const location = '$.materials[' + index + ']';
+ cosmiqClosedObject(material, [
+ 'id', 'baseColorTextureId', 'baseColorFactor', 'alphaMode', 'alphaCutoff',
+ 'doubleSided', 'shading', 'emissiveTextureId', 'emissiveFactor'
+ ], location);
+ cosmiqVector(material.baseColorFactor, 4, 0, 1, location + '.baseColorFactor');
+ cosmiqVector(material.emissiveFactor, 3, 0, 8, location + '.emissiveFactor');
+ if (['opaque', 'mask', 'blend'].indexOf(material.alphaMode) === -1 ||
+ ['lit', 'unlit'].indexOf(material.shading) === -1 ||
+ typeof material.doubleSided !== 'boolean') {
+ cosmiqFail('kind.invariant_violation', 'Material enum is invalid.', location);
+ }
+ cosmiqNumber(material.alphaCutoff, 0, 1, location + '.alphaCutoff');
+ ['baseColorTextureId', 'emissiveTextureId'].forEach(function (field) {
+ const id = material[field];
+ if (id !== null && !ids.texture[id]) {
+ cosmiqFail('reference.missing', 'Material texture does not resolve.', location + '.' + field);
+ }
+ });
+ });
+
+ const textureSections = Object.create(null);
+ manifest.textures.forEach(function (texture, index) {
+ const location = '$.textures[' + index + ']';
+ cosmiqClosedObject(
+ texture,
+ ['id', 'section', 'width', 'height', 'colorSpace', 'filter', 'wrap'],
+ location
+ );
+ const record = sectionRecords[texture.section];
+ if (!record || record.type !== 'texture_png' || textureSections[texture.section]) {
+ cosmiqFail('reference.missing', 'Texture section is missing or reused.', location + '.section');
+ }
+ textureSections[texture.section] = true;
+ cosmiqNumber(texture.width, 1, 2048, location + '.width', true);
+ cosmiqNumber(texture.height, 1, 2048, location + '.height', true);
+ if (texture.colorSpace !== 'srgb' || ['nearest', 'linear'].indexOf(texture.filter) === -1 ||
+ ['clamp', 'repeat'].indexOf(texture.wrap) === -1) {
+ cosmiqFail('media.invalid_png', 'Texture sampling declaration is invalid.', location);
+ }
+ const png = cosmiqInspectPng(record.bytes, location);
+ if (png.width !== texture.width || png.height !== texture.height) {
+ cosmiqFail('media.invalid_png', 'Declared PNG dimensions do not match IHDR.', location);
+ }
+ });
+ Object.keys(sectionRecords).forEach(function (key) {
+ const record = sectionRecords[key];
+ if (record.type === 'texture_png' && !textureSections[key]) {
+ cosmiqFail('reference.unreferenced', 'PNG section has no Texture record.', '$.sections');
+ }
+ });
+
+ const variantIds = Object.create(null);
+ manifest.textureVariants.forEach(function (variant, index) {
+ const location = '$.textureVariants[' + index + ']';
+ cosmiqClosedObject(variant, ['id', 'replacements'], location);
+ cosmiqLocalId(variant.id, location + '.id');
+ if (variantIds[variant.id]) cosmiqFail('reference.duplicate_id', 'Duplicate texture variant.', location);
+ variantIds[variant.id] = true;
+ if (!cosmiqPlainObject(variant.replacements)) {
+ cosmiqFail('kind.invariant_violation', 'Variant replacements must be a map.', location + '.replacements');
+ }
+ const replacementKeys = Object.keys(variant.replacements);
+ if (variant.id !== 'default' && replacementKeys.length === 0) {
+ cosmiqFail('kind.invariant_violation', 'A non-default texture variant must replace a texture.', location);
+ }
+ replacementKeys.forEach(function (base) {
+ const replacement = variant.replacements[base];
+ if (!ids.texture[base] || !ids.texture[replacement]) {
+ cosmiqFail('reference.missing', 'Texture variant replacement does not resolve.', location);
+ }
+ if (base === replacement) {
+ cosmiqFail('kind.invariant_violation', 'Texture variants must replace a base with a distinct texture.', location);
+ }
+ if (ids.texture[base].width !== ids.texture[replacement].width ||
+ ids.texture[base].height !== ids.texture[replacement].height) {
+ cosmiqFail('kind.invariant_violation', 'Texture variant dimensions differ.', location);
+ }
+ });
+ });
+ if (manifest.textures.length) {
+ if (!manifest.textureVariants.length || manifest.textureVariants[0].id !== 'default' ||
+ Object.keys(manifest.textureVariants[0].replacements).length !== 0) {
+ cosmiqFail('kind.invariant_violation', 'The unchanged default texture variant must be first.', '$.textureVariants');
+ }
+ } else if (manifest.textureVariants.length) {
+ cosmiqFail('kind.invariant_violation', 'Texture variants require textures.', '$.textureVariants');
+ }
+ if (manifest.textureVariants.length > 10) {
+ cosmiqFail('budget.exceeded', 'Texture variant budget exceeded.', '$.textureVariants');
+ }
+
+ const usage = {
+ geometry: Object.create(null),
+ material: Object.create(null),
+ texture: Object.create(null),
+ clip: Object.create(null),
+ audio: Object.create(null),
+ particle: Object.create(null),
+ locator: Object.create(null),
+ outcomeRequest: Object.create(null),
+ outcomeEvent: Object.create(null)
+ };
+ const counts = {
+ nodes: 0,
+ cuboids: 0,
+ indexedPrimitives: 0,
+ vertices: 0,
+ triangles: 0,
+ keyframes: 0
+ };
+ const coverage = {semantic: Object.create(null), ranges: Object.create(null)};
+
+ cosmiqValidateClips(manifest, ids, sectionRecords, coverage, usage, counts);
+ cosmiqValidateActors(manifest, ids);
+ cosmiqValidateGeometry(manifest, ids, sectionRecords, coverage, usage, counts);
+ cosmiqValidateNodes(manifest, ids, usage, counts);
+ cosmiqValidateStateBindings(manifest, ids, usage);
+ cosmiqValidateMovement(manifest);
+ cosmiqValidatePlayerAppearance(manifest);
+
+ manifest.outcomeSets.forEach(function (outcome, index) {
+ const location = '$.outcomeSets[' + index + ']';
+ cosmiqClosedObject(outcome, ['id', 'values'], location, 'logic.invalid');
+ const values = cosmiqArray(outcome.values, location + '.values');
+ if (values.length < 2 || values.length > 8) {
+ cosmiqFail('logic.invalid', 'Outcome Set must contain two through eight values.', location);
+ }
+ let previous = '';
+ values.forEach(function (value, valueIndex) {
+ cosmiqLocalId(value, location + '.values[' + valueIndex + ']');
+ if (value <= previous) cosmiqFail('logic.invalid', 'Outcome values must be sorted and unique.', location);
+ previous = value;
+ });
+ });
+ cosmiqValidateLogic(manifest, ids, usage);
+ cosmiqValidateEffects(manifest, ids, sectionRecords, usage);
+
+ Object.keys(coverage.ranges).forEach(function (sectionIndex) {
+ const section = sectionRecords[Number(sectionIndex)];
+ const ranges = coverage.ranges[sectionIndex].filter(function (range) {
+ return range.end > range.start;
+ }).sort(function (left, right) { return left.start - right.start; });
+ let coveredTo = 0;
+ ranges.forEach(function (range) {
+ if (range.start !== coveredTo) {
+ cosmiqFail('buffer.invalid_view', 'Numeric buffer contains uncovered bytes.', '$.sections');
+ }
+ coveredTo = range.end;
+ });
+ if (!section || coveredTo !== section.bytes.length) {
+ cosmiqFail('buffer.invalid_view', 'Numeric buffer contains uncovered trailing bytes.', '$.sections');
+ }
+ });
+ sectionRecords.slice(1).forEach(function (section) {
+ if ((section.type === 'geometry_buffer' || section.type === 'animation_buffer') &&
+ !coverage.ranges[section.index]) {
+ cosmiqFail('reference.unreferenced', 'Numeric section is unreferenced.', '$.sections');
+ }
+ });
+
+ manifest.materials.forEach(function (material) {
+ if (material.baseColorTextureId !== null) usage.texture[material.baseColorTextureId] = true;
+ if (material.emissiveTextureId !== null) usage.texture[material.emissiveTextureId] = true;
+ });
+ manifest.textureVariants.forEach(function (variant) {
+ Object.keys(variant.replacements).forEach(function (base) {
+ usage.texture[base] = true;
+ usage.texture[variant.replacements[base]] = true;
+ });
+ });
+
+ if (manifest.assetKind === 'accessory') {
+ if (manifest.capeMesh !== null || manifest.movementPolicy !== null || manifest.logic !== null ||
+ manifest.actors.length || manifest.outcomeSets.length || !manifest.nodes.length ||
+ !manifest.geometry.length || !manifest.textures.length) {
+ cosmiqFail('kind.invariant_violation', 'Accessory Asset Kind fields are inconsistent.', '$');
+ }
+ if (!manifest.clips.length && (
+ manifest.audio.length || manifest.particles.length || manifest.locators.length || manifest.cues.length
+ )) {
+ cosmiqFail('kind.invariant_violation', 'Static Accessories cannot declare timed effects.', '$');
+ }
+ } else if (manifest.assetKind === 'emote') {
+ if (manifest.bodyGroup !== null || manifest.capeMesh !== null || !manifest.clips.length ||
+ manifest.stateBindings.length || manifest.logic === null) {
+ cosmiqFail('kind.invariant_violation', 'Emote Asset Kind fields are inconsistent.', '$');
+ }
+ if (!usage.clip[manifest.previewCamera.pose.clipId]) {
+ cosmiqFail('camera.invalid', 'Emote preview clip must be referenced by logic.', '$.previewCamera.pose.clipId');
+ }
+ }
+
+ Object.keys(ids.geometry).forEach(function (id) {
+ if (!usage.geometry[id]) cosmiqFail('reference.unreferenced', 'Geometry is unreferenced.', '$.geometry');
+ });
+ Object.keys(ids.material).forEach(function (id) {
+ if (manifest.assetKind !== 'cape' && !usage.material[id]) {
+ cosmiqFail('reference.unreferenced', 'Material is unreferenced.', '$.materials');
+ }
+ });
+ Object.keys(ids.texture).forEach(function (id) {
+ if (!usage.texture[id]) cosmiqFail('reference.unreferenced', 'Texture is unreferenced.', '$.textures');
+ });
+ Object.keys(ids.clip).forEach(function (id) {
+ if (!usage.clip[id]) cosmiqFail('reference.unreferenced', 'Clip is unreferenced.', '$.clips');
+ });
+ Object.keys(ids.outcome).forEach(function (id) {
+ if (!usage.outcomeRequest[id] || !usage.outcomeEvent[id]) {
+ cosmiqFail('reference.unreferenced', 'Outcome Set needs a request and resolved event.', '$.outcomeSets');
+ }
+ });
+
+ const budgets = manifest.assetKind === 'emote'
+ ? {
+ nodes: 128, cuboids: 256, indexedPrimitives: 128, vertices: 32768,
+ triangles: 65536, clips: 64, keyframes: 32768
+ }
+ : manifest.assetKind === 'accessory'
+ ? {
+ nodes: 128, cuboids: 256, indexedPrimitives: 64, vertices: 16384,
+ triangles: 32768, clips: 32, keyframes: 8192
+ }
+ : {
+ nodes: 0, cuboids: 0, indexedPrimitives: 0, vertices: 0,
+ triangles: 0, clips: 0, keyframes: 0
+ };
+ Object.keys(budgets).forEach(function (field) {
+ const actual = field === 'clips' ? manifest.clips.length : counts[field];
+ if (actual > budgets[field]) cosmiqFail('budget.exceeded', field + ' budget exceeded.', '$');
+ });
+ const texturePixels = manifest.textures.reduce(function (total, texture) {
+ return total + texture.width * texture.height;
+ }, 0);
+ const materialLimit = manifest.assetKind === 'cape' ? 1 : 32;
+ const textureLimit = manifest.assetKind === 'cape' ? 10 : 32;
+ const pixelLimit = manifest.assetKind === 'cape' ? 20480 : 16777216;
+ if (manifest.materials.length > materialLimit || manifest.textures.length > textureLimit ||
+ texturePixels > pixelLimit || manifest.audio.length > 8 ||
+ manifest.particles.length > 16 || manifest.locators.length > 64 ||
+ manifest.outcomeSets.length > (manifest.assetKind === 'emote' ? 8 : 0)) {
+ cosmiqFail('budget.exceeded', 'Content object budget exceeded.', '$');
+ }
+
+ if (manifest.assetKind === 'cape') {
+ if (manifest.bodyGroup !== null || manifest.capeMesh !== 'cosmiq:cape/default-v1' ||
+ manifest.movementPolicy !== null || manifest.logic !== null ||
+ manifest.nodes.length || manifest.geometry.length || manifest.actors.length ||
+ manifest.clips.length || manifest.stateBindings.length || manifest.audio.length ||
+ manifest.particles.length || manifest.locators.length || manifest.cues.length ||
+ manifest.outcomeSets.length) {
+ cosmiqFail('cape.invalid_fixed_asset', 'Cape contains fields forbidden by the fixed asset contract.', '$');
+ }
+ if (manifest.materials.length !== 1 || manifest.textures.length < 1 ||
+ manifest.textures.length > 10 || manifest.textureVariants.length !== manifest.textures.length) {
+ cosmiqFail('cape.invalid_fixed_asset', 'Cape material, texture, or variant count is invalid.', '$');
+ }
+ const material = manifest.materials[0];
+ if (canonicalCosmiqJson(material) !== canonicalCosmiqJson({
+ id: 'cape_material',
+ baseColorTextureId: 'cape_texture',
+ baseColorFactor: [1, 1, 1, 1],
+ alphaMode: 'mask',
+ alphaCutoff: 0.5,
+ doubleSided: true,
+ shading: 'lit',
+ emissiveTextureId: null,
+ emissiveFactor: [0, 0, 0]
+ }) || !ids.texture.cape_texture) {
+ cosmiqFail('cape.invalid_fixed_asset', 'Cape material is not canonical.', '$.materials');
+ }
+ manifest.textures.forEach(function (texture) {
+ if (texture.width !== 64 || texture.height !== 32) {
+ cosmiqFail('cape.invalid_fixed_asset', 'Cape textures must be 64 by 32.', '$.textures');
+ }
+ });
+ manifest.textureVariants.slice(1).forEach(function (variant) {
+ const keys = Object.keys(variant.replacements);
+ if (keys.length !== 1 || keys[0] !== 'cape_texture') {
+ cosmiqFail('cape.invalid_fixed_asset', 'Cape variants replace only cape_texture.', '$.textureVariants');
+ }
+ });
+ }
+ return manifest;
+ }
+
+ function parseAndValidateCosmiqRuntime(content) {
+ const bytes = cosmiqBytes(content);
+ if (bytes.length > 32 * 1024 * 1024) {
+ cosmiqFail('budget.exceeded', 'Package exceeds the absolute input ceiling.');
+ }
+ if (bytes.length < COSMIQ_STANDARD_HEADER_BYTES) {
+ cosmiqFail('header.invalid_length', 'Package is shorter than the 64-byte header.');
+ }
+ for (let index = 0; index < 8; index++) {
+ if (bytes[index] !== COSMIQ_STANDARD_MAGIC.charCodeAt(index)) {
+ cosmiqFail('header.invalid_magic', 'File magic is not COSMIQCP.');
+ }
+ }
+ const view = new DataView(bytes.buffer, bytes.byteOffset, bytes.byteLength);
+ if (view.getUint16(8, true) !== COSMIQ_STANDARD_CONTAINER_VERSION) {
+ cosmiqFail('header.unsupported_version', 'Only container version 2 is supported.');
+ }
+ const sectionCount = view.getUint32(16, true);
+ const expectedFirstPayload = cosmiqAlign8(
+ COSMIQ_STANDARD_HEADER_BYTES + sectionCount * COSMIQ_STANDARD_DIRECTORY_ENTRY_BYTES
+ );
+ if (view.getUint16(10, true) !== COSMIQ_STANDARD_HEADER_BYTES ||
+ view.getUint32(12, true) !== 0 ||
+ sectionCount < 1 || sectionCount > COSMIQ_STANDARD_MAX_SECTIONS ||
+ view.getUint32(20, true) !== COSMIQ_STANDARD_DIRECTORY_ENTRY_BYTES ||
+ cosmiqReadU64(view, 24, 'directory.invalid') !== COSMIQ_STANDARD_HEADER_BYTES ||
+ cosmiqReadU64(view, 32, 'directory.invalid') !== expectedFirstPayload ||
+ cosmiqReadU64(view, 40, 'header.invalid_length') !== bytes.length) {
+ cosmiqFail('header.invalid_length', 'Header fields or exact file length are invalid.');
+ }
+ for (let index = 48; index < 64; index++) {
+ if (bytes[index] !== 0) cosmiqFail('directory.invalid', 'Header reserved bytes must be zero.');
+ }
+ for (let index = COSMIQ_STANDARD_HEADER_BYTES + sectionCount * COSMIQ_STANDARD_DIRECTORY_ENTRY_BYTES;
+ index < expectedFirstPayload; index++) {
+ if (bytes[index] !== 0) cosmiqFail('directory.invalid', 'Directory padding must be zero.');
+ }
+ const sections = [];
+ let expectedOffset = expectedFirstPayload;
+ for (let index = 0; index < sectionCount; index++) {
+ const entryOffset = COSMIQ_STANDARD_HEADER_BYTES + index * COSMIQ_STANDARD_DIRECTORY_ENTRY_BYTES;
+ const entryIndex = view.getUint32(entryOffset, true);
+ const typeValue = view.getUint16(entryOffset + 4, true);
+ const flags = view.getUint16(entryOffset + 6, true);
+ const offset = cosmiqReadU64(view, entryOffset + 8, 'directory.invalid');
+ const length = cosmiqReadU64(view, entryOffset + 16, 'directory.invalid');
+ const type = COSMIQ_STANDARD_SECTION_NAMES[typeValue];
+ if (entryIndex !== index || !type || flags !== 0 || offset !== expectedOffset ||
+ offset % 8 !== 0 || length < 1 || offset + length > bytes.length ||
+ (index === 0 ? type !== 'manifest_json' : type === 'manifest_json')) {
+ cosmiqFail(!type ? 'directory.unknown_section_type' : 'directory.invalid', 'Section directory entry is invalid.');
+ }
+ for (let reserved = entryOffset + 56; reserved < entryOffset + 80; reserved++) {
+ if (bytes[reserved] !== 0) cosmiqFail('directory.invalid', 'Directory reserved bytes must be zero.');
+ }
+ const payload = bytes.subarray(offset, offset + length);
+ if ((type === 'texture_png' && length > 8 * 1024 * 1024) ||
+ (type === 'audio_ogg_vorbis' && length > 16 * 1024 * 1024)) {
+ cosmiqFail('budget.exceeded', 'Media section byte budget exceeded.', '$.sections[' + index + ']');
+ }
+ const expectedHash = new Uint8Array(bytes.subarray(entryOffset + 24, entryOffset + 56));
+ if (index === 0 && !cosmiqEqualBytes(cosmiqSha256(payload), expectedHash)) {
+ cosmiqFail('section.hash_mismatch', 'Section SHA-256 does not match.', '$.sections[' + index + ']');
+ }
+ sections.push({
+ index: index,
+ type: type,
+ bytes: new Uint8Array(payload),
+ expectedHash: expectedHash
+ });
+ const nextOffset = index === sectionCount - 1 ? offset + length : cosmiqAlign8(offset + length);
+ for (let padding = offset + length; padding < nextOffset; padding++) {
+ if (bytes[padding] !== 0) cosmiqFail('directory.invalid', 'Section padding must be zero.');
+ }
+ expectedOffset = nextOffset;
+ }
+ if (expectedOffset !== bytes.length) {
+ cosmiqFail('header.invalid_length', 'Trailing padding or data is forbidden.');
+ }
+ let manifestText;
+ try {
+ manifestText = decodeUtf8(sections[0].bytes);
+ } catch (error) {
+ cosmiqFail('manifest.invalid_utf8', 'Manifest is not canonical UTF-8.');
+ }
+ let manifest;
+ try {
+ manifest = JSON.parse(manifestText);
+ } catch (error) {
+ cosmiqFail('manifest.invalid_json', 'Manifest JSON cannot be parsed.');
+ }
+ if (encodeUtf8(manifestText).length > 256 * 1024) {
+ cosmiqFail('budget.exceeded', 'Manifest byte budget exceeded.', '$');
+ }
+ if (canonicalCosmiqJson(manifest) !== manifestText) {
+ cosmiqFail('manifest.noncanonical_json', 'Manifest is not RFC 8785 canonical JSON.', '$');
+ }
+ cosmiqValidateJsonBounds(manifest);
+ cosmiqClosedManifest(manifest);
+ if (manifest.schemaVersion !== COSMIQ_RUNTIME_SCHEMA) {
+ cosmiqFail('manifest.unsupported_schema', 'Unsupported Package Manifest schema.', '$.schemaVersion');
+ }
+ if (manifest.coordinateProfile !== COSMIQ_STANDARD_COORDINATES ||
+ ['accessory', 'cape', 'emote'].indexOf(manifest.assetKind) === -1) {
+ cosmiqFail('kind.invariant_violation', 'Asset Kind or coordinate profile is invalid.', '$');
+ }
+ const kindByteLimits = {accessory: 16 * 1024 * 1024, cape: 4 * 1024 * 1024, emote: 32 * 1024 * 1024};
+ if (bytes.length > kindByteLimits[manifest.assetKind]) {
+ cosmiqFail('budget.exceeded', 'Asset Kind file-byte budget exceeded.', '$.assetKind');
+ }
+ cosmiqArray(manifest.sections, '$.sections');
+ if (manifest.sections.length + 1 !== sections.length) {
+ cosmiqFail('directory.invalid', 'Every non-manifest directory entry needs one descriptor.', '$.sections');
+ }
+ const descriptorIds = Object.create(null);
+ manifest.sections.forEach(function (descriptor, index) {
+ const location = '$.sections[' + index + ']';
+ cosmiqClosedObject(descriptor, ['index', 'id', 'type'], location);
+ cosmiqLocalId(descriptor.id, location + '.id');
+ if (descriptor.index !== index + 1 || !sections[index + 1] ||
+ sections[index + 1].type !== descriptor.type || descriptorIds[descriptor.id]) {
+ cosmiqFail('directory.invalid', 'Section descriptor does not match the directory.', location);
+ }
+ descriptorIds[descriptor.id] = true;
+ });
+ sections.slice(1).forEach(function (section) {
+ if (!cosmiqEqualBytes(cosmiqSha256(section.bytes), section.expectedHash)) {
+ cosmiqFail(
+ 'section.hash_mismatch',
+ 'Section SHA-256 does not match.',
+ '$.sections[' + section.index + ']'
+ );
+ }
+ });
+ cosmiqValidateManifest(manifest, sections);
+ return Object.freeze({
+ manifest: manifest,
+ sections: Object.freeze(sections.map(function (section) {
+ return Object.freeze({
+ index: section.index,
+ type: section.type,
+ bytes: section.bytes
+ });
+ }))
+ });
+ }
+
+ function v3RequiredObject(value, code, location) {
+ if (!cosmiqPlainObject(value)) {
+ cosmiqFail(code, 'Required v3 project member must be an object.', location);
+ }
+ return value;
+ }
+
+ function v3RequiredArray(value, location) {
+ if (!Array.isArray(value)) {
+ cosmiqFail('package.invalid_project', 'Required v3 project member must be an array.', location);
+ }
+ return value;
+ }
+
+ function validateCosmiqBbmodelProject(project, exactBytes) {
+ const root = v3RequiredObject(project, 'package.invalid_project', '$');
+ const meta = v3RequiredObject(root.meta, 'package.invalid_project', '$.meta');
+ const metadata = v3RequiredObject(root.cosmiq, 'package.invalid_project', '$.cosmiq');
+ if (meta.model_format !== FORMAT_ID) {
+ cosmiqFail(
+ 'package.unsupported_model_format',
+ 'Blockbench model_format must be cosmiq_cosmetic.',
+ '$.meta.model_format'
+ );
+ }
+ if (typeof meta.format_version !== 'string' || !meta.format_version) {
+ cosmiqFail(
+ 'package.unsupported_blockbench_version',
+ 'Blockbench format_version must be an explicitly supported value.',
+ '$.meta.format_version'
+ );
+ }
+ if (metadata.schemaVersion !== PACKAGE_SCHEMA || metadata.formatVersion !== 3) {
+ cosmiqFail(
+ 'package.unsupported_schema',
+ 'Only the COSMIQ v3 Blockbench package schema is supported.',
+ '$.cosmiq.schemaVersion'
+ );
+ }
+ const kindMap = {cosmetic: CATEGORIES.ACCESSORY, cape: CATEGORIES.CAPE, emote: CATEGORIES.EMOTE};
+ const assetKind = kindMap[metadata.kind];
+ if (!assetKind || root.cosmiq_project_kind !== metadata.kind || metadata.category !== assetKind) {
+ cosmiqFail(
+ 'package.invalid_kind',
+ 'Project kind and COSMIQ category must describe one supported asset kind.',
+ '$.cosmiq.kind'
+ );
+ }
+ if (metadata.coordinateProfile !== COORDINATE_PROFILE || metadata.unitsPerBlock !== 16) {
+ cosmiqFail(
+ 'package.invalid_coordinates',
+ 'V3 packages use cosmiq.blockbench.v3 at 16 units per block.',
+ '$.cosmiq.coordinateProfile'
+ );
+ }
+ if (typeof root.name !== 'string' || !root.name.trim() || root.name.length > 128) {
+ cosmiqFail('package.invalid_project', 'Project name must be non-blank and bounded.', '$.name');
+ }
+ if (typeof metadata.projectId !== 'string' ||
+ !/^[a-z0-9][a-z0-9._-]{0,63}:[a-z0-9][a-z0-9._/-]{0,126}$/.test(metadata.projectId)) {
+ cosmiqFail('package.invalid_project_id', 'Project ID is not normalized.', '$.cosmiq.projectId');
+ }
+ if (!Number.isSafeInteger(metadata.sourceRevision) || metadata.sourceRevision < 1) {
+ cosmiqFail(
+ 'package.invalid_project',
+ 'Source revision must be a positive safe integer.',
+ '$.cosmiq.sourceRevision'
+ );
+ }
+ v3RequiredObject(root.resolution, 'package.invalid_project', '$.resolution');
+ v3RequiredArray(root.elements, '$.elements');
+ v3RequiredArray(root.outliner, '$.outliner');
+ v3RequiredArray(root.textures, '$.textures');
+ v3RequiredArray(root.animations, '$.animations');
+ v3RequiredObject(metadata.nodeBindings, 'package.invalid_project', '$.cosmiq.nodeBindings');
+ v3RequiredObject(metadata.animationBindings, 'package.invalid_project', '$.cosmiq.animationBindings');
+ if (metadata.settings != null) {
+ const settings = v3RequiredArray(metadata.settings, '$.cosmiq.settings');
+ if (settings.length > 1 ||
+ JSON.stringify(normalizeCosmeticSettings(settings)) !== JSON.stringify(settings)) {
+ cosmiqFail(
+ 'package.invalid_project',
+ 'Cosmetic Settings must be empty or one enabled, unkeyed SIDE setting.',
+ '$.cosmiq.settings'
+ );
+ }
+ }
+ v3RequiredArray(metadata.resourceReferences, '$.cosmiq.resourceReferences');
+ v3RequiredObject(
+ metadata.effectLocatorBindings,
+ 'package.invalid_project',
+ '$.cosmiq.effectLocatorBindings'
+ );
+ v3RequiredArray(metadata.timelineCues, '$.cosmiq.timelineCues');
+ v3RequiredObject(metadata.previewCamera, 'package.invalid_project', '$.cosmiq.previewCamera');
+ v3RequiredObject(metadata.budgets, 'package.invalid_project', '$.cosmiq.budgets');
+ const limits = {accessory: 16 * 1024 * 1024, cape: 4 * 1024 * 1024, emote: 32 * 1024 * 1024};
+ if (exactBytes.length > limits[assetKind]) {
+ cosmiqFail('budget.exceeded', 'Asset Kind file-byte budget exceeded.', '$.cosmiq.category');
+ }
+ return Object.freeze({
+ assetKind: assetKind,
+ exactBytes: new Uint8Array(exactBytes),
+ metadata: metadata,
+ project: root
+ });
+ }
+
+ function parseAndValidateCosmiq(content) {
+ const bytes = cosmiqBytes(content);
+ const legacyMagic = COSMIQ_STANDARD_MAGIC;
+ let legacy = bytes.length >= legacyMagic.length;
+ for (let index = 0; legacy && index < legacyMagic.length; index++) {
+ legacy = bytes[index] === legacyMagic.charCodeAt(index);
+ }
+ if (legacy) {
+ cosmiqFail(
+ 'package.unsupported_legacy',
+ 'The COSMIQCP container was replaced by the exact v3 .bbmodel project.'
+ );
+ }
+ if (bytes.length > 32 * 1024 * 1024) {
+ cosmiqFail('budget.exceeded', 'Package exceeds the absolute input ceiling.');
+ }
+ let text;
+ try {
+ text = decodeUtf8(bytes);
+ } catch (error) {
+ cosmiqFail('package.invalid_utf8', 'Package is not valid UTF-8.');
+ }
+ let project;
+ try {
+ project = JSON.parse(text);
+ } catch (error) {
+ cosmiqFail('package.invalid_json', 'Blockbench project JSON cannot be parsed.');
+ }
+ return validateCosmiqBbmodelProject(project, bytes);
+ }
+
+ function encodeCosmiqBbmodel(project) {
+ const exactBytes = encodeUtf8(JSON.stringify(project, null, 2) + '\n');
+ parseAndValidateCosmiq(exactBytes);
+ return exactBytes;
+ }
+
+ function encodeCosmiqPackage(manifest, payloadSections) {
+ const descriptors = Array.isArray(manifest && manifest.sections) ? manifest.sections : [];
+ const provided = Array.isArray(payloadSections) ? payloadSections.slice() : [];
+ if (provided.length !== descriptors.length) {
+ cosmiqFail('directory.invalid', 'Payload section count does not match manifest.sections.', '$.sections');
+ }
+ provided.sort(function (left, right) {
+ const leftDescriptor = descriptors.find(function (descriptor) { return descriptor.id === left.id; });
+ const rightDescriptor = descriptors.find(function (descriptor) { return descriptor.id === right.id; });
+ return (leftDescriptor ? leftDescriptor.index : Number.MAX_SAFE_INTEGER) -
+ (rightDescriptor ? rightDescriptor.index : Number.MAX_SAFE_INTEGER);
+ });
+ const payloads = [encodeUtf8(canonicalCosmiqJson(manifest))];
+ const types = ['manifest_json'];
+ descriptors.forEach(function (descriptor, index) {
+ const section = provided[index];
+ if (!section || section.id !== descriptor.id || section.type !== descriptor.type) {
+ cosmiqFail('directory.invalid', 'Payload section identity does not match its descriptor.', '$.sections[' + index + ']');
+ }
+ const bytes = cosmiqBytes(section.bytes);
+ if (!bytes.length) cosmiqFail('directory.invalid', 'Sections cannot be empty.', '$.sections[' + index + ']');
+ payloads.push(bytes);
+ types.push(section.type);
+ });
+ const sectionCount = payloads.length;
+ if (sectionCount > COSMIQ_STANDARD_MAX_SECTIONS) cosmiqFail('budget.exceeded', 'Section budget exceeded.');
+ const firstPayloadOffset = cosmiqAlign8(
+ COSMIQ_STANDARD_HEADER_BYTES + sectionCount * COSMIQ_STANDARD_DIRECTORY_ENTRY_BYTES
+ );
+ const offsets = [];
+ let cursor = firstPayloadOffset;
+ payloads.forEach(function (payload, index) {
+ offsets.push(cursor);
+ cursor = index === payloads.length - 1 ? cursor + payload.length : cosmiqAlign8(cursor + payload.length);
+ });
+ const output = new Uint8Array(cursor);
+ const view = new DataView(output.buffer);
+ for (let index = 0; index < 8; index++) output[index] = COSMIQ_STANDARD_MAGIC.charCodeAt(index);
+ view.setUint16(8, COSMIQ_STANDARD_CONTAINER_VERSION, true);
+ view.setUint16(10, COSMIQ_STANDARD_HEADER_BYTES, true);
+ view.setUint32(12, 0, true);
+ view.setUint32(16, sectionCount, true);
+ view.setUint32(20, COSMIQ_STANDARD_DIRECTORY_ENTRY_BYTES, true);
+ cosmiqWriteU64(view, 24, COSMIQ_STANDARD_HEADER_BYTES);
+ cosmiqWriteU64(view, 32, firstPayloadOffset);
+ cosmiqWriteU64(view, 40, output.length);
+ payloads.forEach(function (payload, index) {
+ const entryOffset = COSMIQ_STANDARD_HEADER_BYTES + index * COSMIQ_STANDARD_DIRECTORY_ENTRY_BYTES;
+ view.setUint32(entryOffset, index, true);
+ view.setUint16(entryOffset + 4, COSMIQ_STANDARD_SECTION_TYPES[types[index]], true);
+ view.setUint16(entryOffset + 6, 0, true);
+ cosmiqWriteU64(view, entryOffset + 8, offsets[index]);
+ cosmiqWriteU64(view, entryOffset + 16, payload.length);
+ output.set(cosmiqSha256(payload), entryOffset + 24);
+ output.set(payload, offsets[index]);
+ });
+ parseAndValidateCosmiqRuntime(output);
+ return output.buffer;
+ }
+
+ function cosmiqClone(value) {
+ return JSON.parse(JSON.stringify(value));
+ }
+
+ function cosmiqDecodeBase64(value, location) {
+ if (typeof value !== 'string' || !/^[A-Za-z0-9+/]*={0,2}$/.test(value) || value.length % 4 !== 0) {
+ cosmiqFail('media.invalid_png', 'Embedded base64 payload is invalid.', location);
+ }
+ if (typeof atob === 'function') {
+ let decoded;
+ try {
+ decoded = atob(value);
+ } catch (error) {
+ cosmiqFail('media.invalid_png', 'Embedded base64 payload cannot be decoded.', location);
+ }
+ const bytes = new Uint8Array(decoded.length);
+ for (let index = 0; index < decoded.length; index++) bytes[index] = decoded.charCodeAt(index);
+ return bytes;
+ }
+ if (typeof Buffer !== 'undefined') {
+ return new Uint8Array(Buffer.from(value, 'base64'));
+ }
+ cosmiqFail('media.invalid_png', 'This Blockbench build cannot decode embedded base64 media.', location);
+ }
+
+ function cosmiqEmbeddedMedia(source, mime, location) {
+ const match = new RegExp('^data:' + mime.replace('/', '\\/') + ';base64,([A-Za-z0-9+/]*={0,2})$').exec(
+ String(source == null ? '' : source)
+ );
+ if (!match) {
+ cosmiqFail(
+ mime === 'image/png' ? 'media.invalid_png' : 'media.invalid_ogg_vorbis',
+ 'Runtime media must be embedded in the editable project.',
+ location
+ );
+ }
+ return cosmiqDecodeBase64(match[1], location);
+ }
+
+ function cosmiqAllocatedId(value, fallback, allocated) {
+ let base = String(value == null ? '' : value)
+ .toLowerCase()
+ .replace(/\.[a-z0-9]{1,8}$/i, '')
+ .replace(/[^a-z0-9._-]+/g, '_')
+ .replace(/^[^a-z]+/, '')
+ .replace(/_+/g, '_')
+ .replace(/^_+|_+$/g, '')
+ .slice(0, 56);
+ if (!base) base = fallback || 'asset';
+ if (!/^[a-z]/.test(base)) base = (fallback || 'asset') + '_' + base;
+ base = base.slice(0, 56);
+ let id = base;
+ let suffix = 2;
+ while (allocated[id]) {
+ id = base.slice(0, 60 - String(suffix).length) + '_' + suffix++;
+ }
+ allocated[id] = true;
+ return id;
+ }
+
+ function cosmiqDefaultTransform(translation) {
+ return {
+ translation: translation ? translation.slice() : [0, 0, 0],
+ rotation: [0, 0, 0, 1],
+ scale: [1, 1, 1],
+ pivot: [0, 0, 0]
+ };
+ }
+
+ function cosmiqBaseManifest(assetKind, previewModelVariant) {
+ return {
+ schemaVersion: COSMIQ_RUNTIME_SCHEMA,
+ assetKind: assetKind,
+ coordinateProfile: COSMIQ_STANDARD_COORDINATES,
+ bodyGroup: null,
+ capeMesh: null,
+ movementPolicy: null,
+ playerAppearance: null,
+ previewCamera: {
+ id: 'thumbnail_camera',
+ transform: cosmiqDefaultTransform(assetKind === 'emote' ? [0, 1, -5] : [0, 1, -3]),
+ projection: {
+ kind: 'perspective',
+ verticalFovDegrees: 35,
+ nearPlane: 0.01,
+ farPlane: 64
+ },
+ pose: {
+ clipId: null,
+ timeMs: 0,
+ modelVariant: assetKind === 'accessory' ? (previewModelVariant || 'universal') : null,
+ visiblePropRootIds: []
+ }
+ },
+ sections: [],
+ actors: [],
+ nodes: [],
+ materials: [],
+ geometry: [],
+ textures: [],
+ textureVariants: [],
+ clips: [],
+ stateBindings: [],
+ audio: [],
+ particles: [],
+ locators: [],
+ cues: [],
+ outcomeSets: [],
+ logic: null
+ };
+ }
+
+ function cosmiqApplyAuthoredCamera(manifest, project) {
+ const camera = project && project.previewCamera;
+ if (!camera || !cosmiqPlainObject(camera)) return;
+ const candidate = cosmiqClone(camera);
+ candidate.id = 'thumbnail_camera';
+ candidate.pose = candidate.pose || {};
+ if (manifest.assetKind === 'accessory' && candidate.pose.modelVariant == null) {
+ candidate.pose.modelVariant = manifest.previewCamera.pose.modelVariant;
+ }
+ if (manifest.assetKind !== 'accessory') candidate.pose.modelVariant = null;
+ manifest.previewCamera = candidate;
+ }
+
+ function cosmiqFinalizeCompiledPackage(manifest, blobs) {
+ const order = {
+ geometry_buffer: 0,
+ animation_buffer: 1,
+ texture_png: 2,
+ audio_ogg_vorbis: 3
+ };
+ blobs.sort(function (left, right) {
+ return order[left.type] - order[right.type] || cosmiqCompareText(left.id, right.id);
+ });
+ const indexById = Object.create(null);
+ manifest.sections = blobs.map(function (blob, index) {
+ const sectionIndex = index + 1;
+ indexById[blob.id] = sectionIndex;
+ return {index: sectionIndex, id: blob.id, type: blob.type};
+ });
+ const pending = [manifest];
+ const seen = new Set();
+ while (pending.length) {
+ const value = pending.pop();
+ if (!value || typeof value !== 'object' || seen.has(value)) continue;
+ seen.add(value);
+ Object.keys(value).forEach(function (key) {
+ const child = value[key];
+ if (key === 'section' && typeof child === 'string') {
+ if (!indexById[child]) cosmiqFail('reference.missing', 'Compiled section reference is missing.');
+ value[key] = indexById[child];
+ } else if (child && typeof child === 'object') {
+ pending.push(child);
+ }
+ });
+ }
+ const sections = blobs.map(function (blob) {
+ return {id: blob.id, type: blob.type, bytes: blob.bytes};
+ });
+ const content = encodeCosmiqPackage(manifest, sections);
+ return {
+ manifest: manifest,
+ sections: sections,
+ content: content,
+ validated: parseAndValidateCosmiqRuntime(content)
+ };
+ }
+
+ function cosmiqRuntimeTextures(rawProject, sourceVariants, cape, allowEmpty) {
+ const rawTextures = (Array.isArray(rawProject && rawProject.textures) ? rawProject.textures : [])
+ .filter(function (texture) {
+ return texture && texture.cosmiq_role !== ROLES.REFERENCE && texture.role !== ROLES.REFERENCE;
+ });
+ if (!rawTextures.length) {
+ if (allowEmpty) {
+ return {
+ records: [],
+ blobs: [],
+ variants: [],
+ idByUuid: Object.create(null),
+ raw: []
+ };
+ }
+ cosmiqFail('kind.invariant_violation', 'Asset has no runtime PNG texture.', '$.textures');
+ }
+ const allocated = Object.create(null);
+ const idByUuid = Object.create(null);
+ const records = [];
+ const blobs = [];
+ rawTextures.forEach(function (texture, index) {
+ const id = cape && index === 0
+ ? 'cape_texture'
+ : cosmiqAllocatedId(texture.name || texture.uuid, 'texture', allocated);
+ if (cape && index === 0) allocated[id] = true;
+ idByUuid[texture.uuid] = id;
+ const bytes = cosmiqEmbeddedMedia(texture.source, 'image/png', '$.textures[' + index + ']');
+ const png = cosmiqInspectPng(bytes, '$.textures[' + index + ']');
+ const sectionId = cosmiqAllocatedId(id + '_png', 'texture_png', allocated);
+ records.push({
+ id: id,
+ section: sectionId,
+ width: png.width,
+ height: png.height,
+ colorSpace: 'srgb',
+ filter: 'nearest',
+ wrap: 'clamp'
+ });
+ blobs.push({id: sectionId, type: 'texture_png', bytes: bytes});
+ });
+ const variants = [{id: 'default', replacements: {}}];
+ (Array.isArray(sourceVariants) ? sourceVariants : []).filter(function (variant) {
+ return variant && variant.id !== 'default';
+ }).forEach(function (variant) {
+ const replacements = {};
+ Object.keys(variant.replacements || {}).sort().forEach(function (baseUuid) {
+ const base = idByUuid[baseUuid];
+ const replacement = idByUuid[variant.replacements[baseUuid]];
+ if (!base || !replacement) {
+ cosmiqFail('reference.missing', 'Texture variant references an unknown editable texture.');
+ }
+ replacements[base] = replacement;
+ });
+ if (!Object.keys(replacements).length) {
+ cosmiqFail('kind.invariant_violation', 'Non-default texture variant is empty.');
+ }
+ variants.push({
+ id: cosmiqAllocatedId(variant.id, 'variant', allocated),
+ replacements: replacements
+ });
+ });
+ variants.splice(1, variants.length - 1, ...variants.slice(1).sort(function (left, right) {
+ return cosmiqCompareText(left.id, right.id);
+ }));
+ return {records: records, blobs: blobs, variants: variants, idByUuid: idByUuid};
+ }
+
+ function compileCapeCosmiqPackage(snapshot, rawProject) {
+ const source = rawProject && rawProject.cosmiq && rawProject.cosmiq.cape;
+ const textures = cosmiqRuntimeTextures(rawProject, source && source.textureVariants, true);
+ const manifest = cosmiqBaseManifest('cape');
+ manifest.capeMesh = 'cosmiq:cape/default-v1';
+ manifest.materials = [{
+ id: 'cape_material',
+ baseColorTextureId: 'cape_texture',
+ baseColorFactor: [1, 1, 1, 1],
+ alphaMode: 'mask',
+ alphaCutoff: 0.5,
+ doubleSided: true,
+ shading: 'lit',
+ emissiveTextureId: null,
+ emissiveFactor: [0, 0, 0]
+ }];
+ manifest.textures = textures.records;
+ manifest.textureVariants = textures.variants;
+ cosmiqApplyAuthoredCamera(manifest, snapshot && snapshot.project);
+ return cosmiqFinalizeCompiledPackage(manifest, textures.blobs);
+ }
+
+ function cosmiqSourceVector(value, fallback, location) {
+ const vector = Array.isArray(value) ? value : fallback;
+ if (!Array.isArray(vector) || vector.length < 3) {
+ cosmiqFail('geometry.invalid', 'Blockbench transform vector is missing.', location);
+ }
+ return vector.slice(0, 3).map(function (component) {
+ const number = Number(component);
+ if (!Number.isFinite(number)) cosmiqFail('geometry.invalid', 'Blockbench vector is not finite.', location);
+ return number;
+ });
+ }
+
+ function cosmiqPositionFromBlockbench(value) {
+ return [-value[0] / 16, value[1] / 16, value[2] / 16].map(function (component) {
+ return Object.is(component, -0) ? 0 : component;
+ });
+ }
+
+ function cosmiqDeltaFromBlockbench(value) {
+ return cosmiqPositionFromBlockbench(value);
+ }
+
+ function cosmiqEulerQuaternionFromBlockbench(value) {
+ const x = value[0] * Math.PI / 180;
+ const y = value[1] * Math.PI / 180;
+ const z = value[2] * Math.PI / 180;
+ const c1 = Math.cos(x / 2);
+ const c2 = Math.cos(y / 2);
+ const c3 = Math.cos(z / 2);
+ const s1 = Math.sin(x / 2);
+ const s2 = Math.sin(y / 2);
+ const s3 = Math.sin(z / 2);
+ const quaternion = [
+ s1 * c2 * c3 + c1 * s2 * s3,
+ -(c1 * s2 * c3 - s1 * c2 * s3),
+ -(c1 * c2 * s3 + s1 * s2 * c3),
+ c1 * c2 * c3 - s1 * s2 * s3
+ ];
+ const length = Math.sqrt(quaternion.reduce(function (sum, component) {
+ return sum + component * component;
+ }, 0));
+ return quaternion.map(function (component) {
+ const normalized = component / length;
+ return Math.abs(normalized) < 1e-12 ? 0 : normalized;
+ });
+ }
+
+ function cosmiqEulerQuaternion(value) {
+ const converted = cosmiqEulerQuaternionFromBlockbench(value);
+ return [converted[0], -converted[1], -converted[2], converted[3]].map(function (component) {
+ return Object.is(component, -0) ? 0 : component;
+ });
+ }
+
+ function cosmiqEditableObjects(rawProject) {
+ const byUuid = Object.create(null);
+ (Array.isArray(rawProject.groups) ? rawProject.groups : []).forEach(function (group) {
+ if (group && group.uuid) byUuid[group.uuid] = group;
+ });
+ (Array.isArray(rawProject.elements) ? rawProject.elements : []).forEach(function (element) {
+ if (element && element.uuid) byUuid[element.uuid] = element;
+ });
+ return byUuid;
+ }
+
+ function cosmiqMaterialForTexture(textureId, materials, materialByTexture, allocated) {
+ const key = textureId || '__untextured__';
+ if (materialByTexture[key]) return materialByTexture[key];
+ const id = cosmiqAllocatedId(textureId ? textureId + '_material' : 'untextured_material', 'material', allocated);
+ materials.push({
+ id: id,
+ baseColorTextureId: textureId || null,
+ baseColorFactor: [1, 1, 1, 1],
+ alphaMode: textureId ? 'mask' : 'opaque',
+ alphaCutoff: 0.5,
+ doubleSided: true,
+ shading: 'lit',
+ emissiveTextureId: null,
+ emissiveFactor: [0, 0, 0]
+ });
+ materialByTexture[key] = id;
+ return id;
+ }
+
+ function cosmiqFaceTextureUuid(face, rawTextures) {
+ if (!face || face.texture === null) return null;
+ if (typeof face.texture === 'string' && face.texture) return face.texture;
+ if (Number.isInteger(face.texture) && rawTextures[face.texture]) return rawTextures[face.texture].uuid;
+ if (rawTextures.length === 1) return rawTextures[0].uuid;
+ return null;
+ }
+
+ function cosmiqCuboidGeometry(element, geometryId, textures, materials, materialByTexture, allocated) {
+ const origin = cosmiqSourceVector(element.origin, [0, 0, 0], '$.elements.' + element.uuid + '.origin');
+ const from = cosmiqSourceVector(element.from, null, '$.elements.' + element.uuid + '.from');
+ const to = cosmiqSourceVector(element.to, null, '$.elements.' + element.uuid + '.to');
+ const first = cosmiqPositionFromBlockbench([
+ from[0] - origin[0], from[1] - origin[1], from[2] - origin[2]
+ ]);
+ const second = cosmiqPositionFromBlockbench([
+ to[0] - origin[0], to[1] - origin[1], to[2] - origin[2]
+ ]);
+ const min = first.map(function (value, axis) { return Math.min(value, second[axis]); });
+ const max = first.map(function (value, axis) { return Math.max(value, second[axis]); });
+ const rawTextures = (Array.isArray(textures.raw) ? textures.raw : []);
+ const directions = {
+ north: 'north',
+ south: 'south',
+ east: 'west',
+ west: 'east',
+ up: 'up',
+ down: 'down'
+ };
+ const faces = {};
+ Object.keys(directions).forEach(function (standardDirection) {
+ const rawFace = element.faces && element.faces[directions[standardDirection]];
+ if (!rawFace || rawFace.enabled === false || rawFace.texture === null) {
+ faces[standardDirection] = null;
+ return;
+ }
+ const textureUuid = cosmiqFaceTextureUuid(rawFace, rawTextures);
+ const textureId = textureUuid && textures.idByUuid[textureUuid];
+ if (!textureId && rawTextures.length) {
+ cosmiqFail('reference.missing', 'Cuboid face texture does not resolve.', '$.elements.' + element.uuid);
+ }
+ const texture = textureId && textures.records.find(function (record) { return record.id === textureId; });
+ const uv = Array.isArray(rawFace.uv) && rawFace.uv.length >= 4
+ ? [
+ Number(rawFace.uv[0]) / texture.width,
+ Number(rawFace.uv[1]) / texture.height,
+ Number(rawFace.uv[2]) / texture.width,
+ Number(rawFace.uv[3]) / texture.height
+ ]
+ : [0, 0, 1, 1];
+ if (uv.some(function (value) { return !Number.isFinite(value); })) {
+ cosmiqFail('geometry.invalid', 'Cuboid face UV is invalid.', '$.elements.' + element.uuid);
+ }
+ faces[standardDirection] = {
+ materialId: cosmiqMaterialForTexture(textureId, materials, materialByTexture, allocated),
+ uv: uv,
+ rotation: [0, 90, 180, 270].indexOf(Number(rawFace.rotation)) === -1
+ ? 0
+ : Number(rawFace.rotation)
+ };
+ });
+ return {
+ id: geometryId,
+ kind: 'cuboid',
+ min: min,
+ max: max,
+ inflate: Number.isFinite(Number(element.inflate)) ? Number(element.inflate) / 16 : 0,
+ faces: faces
+ };
+ }
+
+ function cosmiqMeshGeometry(
+ element,
+ geometryBaseId,
+ textures,
+ materials,
+ materialByTexture,
+ allocated,
+ meshDrafts
+ ) {
+ const rawTextures = textures.raw;
+ const byMaterial = Object.create(null);
+ Object.keys(element.faces || {}).sort().forEach(function (faceKey) {
+ const face = element.faces[faceKey];
+ const vertexIds = face && Array.isArray(face.vertices) ? face.vertices : [];
+ if (vertexIds.length < 3) return;
+ const textureUuid = cosmiqFaceTextureUuid(face, rawTextures);
+ const textureId = textureUuid && textures.idByUuid[textureUuid];
+ if (!textureId && rawTextures.length) {
+ cosmiqFail('reference.missing', 'Mesh face texture does not resolve.', '$.elements.' + element.uuid);
+ }
+ const texture = textureId && textures.records.find(function (record) { return record.id === textureId; });
+ const materialId = cosmiqMaterialForTexture(textureId, materials, materialByTexture, allocated);
+ const draft = byMaterial[materialId] || (byMaterial[materialId] = {
+ materialId: materialId,
+ positions: [],
+ normals: [],
+ uvs: [],
+ indices: []
+ });
+ for (let triangleIndex = 1; triangleIndex < vertexIds.length - 1; triangleIndex++) {
+ const triangleIds = [vertexIds[0], vertexIds[triangleIndex + 1], vertexIds[triangleIndex]];
+ const positions = triangleIds.map(function (vertexId) {
+ const raw = element.vertices && element.vertices[vertexId];
+ const vertex = cosmiqSourceVector(raw, null, '$.elements.' + element.uuid + '.vertices.' + vertexId);
+ // Blockbench mesh vertices are already local to the mesh origin.
+ // The node bind carries that origin, so subtracting it here applies
+ // the pivot twice and moves head accessories down to the player's feet.
+ return cosmiqPositionFromBlockbench(vertex);
+ });
+ const ab = [
+ positions[1][0] - positions[0][0],
+ positions[1][1] - positions[0][1],
+ positions[1][2] - positions[0][2]
+ ];
+ const ac = [
+ positions[2][0] - positions[0][0],
+ positions[2][1] - positions[0][1],
+ positions[2][2] - positions[0][2]
+ ];
+ const cross = [
+ ab[1] * ac[2] - ab[2] * ac[1],
+ ab[2] * ac[0] - ab[0] * ac[2],
+ ab[0] * ac[1] - ab[1] * ac[0]
+ ];
+ const length = Math.sqrt(cross[0] * cross[0] + cross[1] * cross[1] + cross[2] * cross[2]);
+ if (length <= 1e-8) {
+ cosmiqFail('geometry.invalid', 'Blockbench mesh contains a degenerate face.', '$.elements.' + element.uuid);
+ }
+ const normal = cross.map(function (value) { return value / length; });
+ positions.forEach(function (position, vertexOffset) {
+ const sourceVertexId = triangleIds[vertexOffset];
+ const rawUv = face.uv && face.uv[sourceVertexId];
+ const uv = Array.isArray(rawUv) && rawUv.length >= 2 && texture
+ ? [Number(rawUv[0]) / texture.width, Number(rawUv[1]) / texture.height]
+ : [0, 0];
+ if (uv.some(function (value) { return !Number.isFinite(value); })) {
+ cosmiqFail('geometry.invalid', 'Blockbench mesh UV is invalid.', '$.elements.' + element.uuid);
+ }
+ draft.indices.push(draft.positions.length);
+ draft.positions.push(position);
+ draft.normals.push(normal.slice());
+ draft.uvs.push(uv);
+ });
+ }
+ });
+ const records = [];
+ Object.keys(byMaterial).sort().forEach(function (materialId, index) {
+ const draft = byMaterial[materialId];
+ if (!draft.indices.length) return;
+ const id = cosmiqAllocatedId(
+ geometryBaseId + (Object.keys(byMaterial).length > 1 ? '_' + (index + 1) : ''),
+ 'mesh',
+ allocated
+ );
+ draft.record = {
+ id: id,
+ kind: 'indexed_triangles',
+ materialId: materialId,
+ positions: null,
+ normals: null,
+ uvs: null,
+ indices: null
+ };
+ meshDrafts.push(draft);
+ records.push(draft.record);
+ });
+ if (!records.length) cosmiqFail('geometry.invalid', 'Blockbench mesh has no renderable triangles.');
+ return records;
+ }
+
+ function cosmiqPackGeometryBuffer(meshDrafts) {
+ if (!meshDrafts.length) return null;
+ let byteLength = 0;
+ function allocateView(count, bytesPerElement) {
+ const view = {section: 'main_geometry', byteOffset: byteLength, count: count};
+ byteLength += count * bytesPerElement;
+ return view;
+ }
+ meshDrafts.forEach(function (draft) {
+ draft.record.positions = allocateView(draft.positions.length, 12);
+ draft.record.normals = allocateView(draft.normals.length, 12);
+ draft.record.uvs = allocateView(draft.uvs.length, 8);
+ draft.record.indices = allocateView(draft.indices.length, 4);
+ });
+ const bytes = new Uint8Array(byteLength);
+ const view = new DataView(bytes.buffer);
+ function writeFloatVectors(targetView, vectors, components, location) {
+ let offset = targetView.byteOffset;
+ vectors.forEach(function (vector) {
+ for (let component = 0; component < components; component++) {
+ cosmiqWriteBinary32(view, offset, vector[component], location);
+ offset += 4;
+ }
+ });
+ }
+ meshDrafts.forEach(function (draft) {
+ const location = '$.geometry.' + draft.record.id;
+ writeFloatVectors(draft.record.positions, draft.positions, 3, location + '.positions');
+ writeFloatVectors(draft.record.normals, draft.normals, 3, location + '.normals');
+ writeFloatVectors(draft.record.uvs, draft.uvs, 2, location + '.uvs');
+ let offset = draft.record.indices.byteOffset;
+ draft.indices.forEach(function (index) {
+ view.setUint32(offset, index, true);
+ offset += 4;
+ });
+ });
+ return {id: 'main_geometry', type: 'geometry_buffer', bytes: bytes};
+ }
+
+ function cosmiqKeyframeSourceVector(keyframe, location) {
+ if (!keyframe || !Array.isArray(keyframe.data_points) || keyframe.data_points.length !== 1) {
+ cosmiqFail('animation.invalid', 'Keyframe must contain exactly one data point.', location);
+ }
+ const point = keyframe.data_points[0];
+ const values = [Number(point.x), Number(point.y), Number(point.z)];
+ if (values.some(function (value) { return !Number.isFinite(value); })) {
+ cosmiqFail('animation.invalid', 'Keyframe data point is not finite.', location);
+ }
+ return values;
+ }
+
+ function cosmiqKeyframeVector(keyframe, channel, location) {
+ const values = cosmiqKeyframeSourceVector(keyframe, location);
+ if (channel === 'translation') return cosmiqDeltaFromBlockbench(values);
+ if (channel === 'rotation') return cosmiqEulerQuaternionFromBlockbench(values);
+ return values.map(function (value) { return Object.is(value, -0) ? 0 : value; });
+ }
+
+ function cosmiqBezierVector(values, channel) {
+ if (channel === 'translation') return cosmiqDeltaFromBlockbench(values);
+ if (channel === 'rotation') {
+ return [values[0], -values[1], -values[2]].map(function (value) {
+ const radians = value * Math.PI / 180;
+ return Object.is(radians, -0) ? 0 : radians;
+ });
+ }
+ return values.map(function (value) { return Object.is(value, -0) ? 0 : value; });
+ }
+
+ function cosmiqBezierHandle(keyframe, field, fallback, location) {
+ const source = Array.isArray(keyframe[field]) ? keyframe[field] : fallback;
+ if (source.length !== 3) cosmiqFail('animation.invalid', 'Bezier handles need three components.', location);
+ const values = source.map(Number);
+ if (values.some(function (value) { return !Number.isFinite(value); })) {
+ cosmiqFail('animation.invalid', 'Bezier handle is not finite.', location);
+ }
+ return values;
+ }
+
+ function cosmiqBezierControls(left, right, channel, location) {
+ const gap = (right.timeMs - left.timeMs) / 1000;
+ const outgoingTime = cosmiqBezierHandle(left.source, 'bezier_right_time', [0.1, 0.1, 0.1], location)
+ .map(function (value) { return Math.max(0, Math.min(gap, value)) / gap; });
+ const incomingTime = cosmiqBezierHandle(right.source, 'bezier_left_time', [-0.1, -0.1, -0.1], location)
+ .map(function (value) { return 1 + Math.max(-gap, Math.min(0, value)) / gap; });
+ const p0 = cosmiqBezierVector(left.sourceValue, channel);
+ const p3 = cosmiqBezierVector(right.sourceValue, channel);
+ const outgoingValue = cosmiqBezierVector(
+ cosmiqBezierHandle(left.source, 'bezier_right_value', [0, 0, 0], location), channel
+ );
+ const incomingValue = cosmiqBezierVector(
+ cosmiqBezierHandle(right.source, 'bezier_left_value', [0, 0, 0], location), channel
+ );
+ const p1 = p0.map(function (value, index) { return value + outgoingValue[index]; });
+ const p2 = p3.map(function (value, index) { return value + incomingValue[index]; });
+ return outgoingTime.concat(incomingTime, p0, p1, p2, p3);
+ }
+
+ function cosmiqCompileAnimationChannel(keyframes, channel, durationMs, location, channelDrafts) {
+ const selected = keyframes.filter(function (keyframe) {
+ const sourceChannel = keyframe && keyframe.channel;
+ return (channel === 'translation' && sourceChannel === 'position') || sourceChannel === channel;
+ }).map(function (keyframe) {
+ const timeMs = Math.round(Number(keyframe.time) * 1000);
+ if (!Number.isFinite(timeMs) || timeMs < 0 || timeMs > durationMs) {
+ cosmiqFail('animation.invalid', 'Keyframe time is outside the clip.', location);
+ }
+ const interpolation = String(keyframe.interpolation || 'linear').toLowerCase();
+ if (['linear', 'step', 'catmullrom', 'bezier'].indexOf(interpolation) === -1) {
+ cosmiqFail(
+ 'animation.invalid',
+ 'Only linear, step, smooth (Catmull-Rom), and Bezier interpolation can be exported.',
+ location
+ );
+ }
+ return {
+ timeMs: timeMs,
+ value: cosmiqKeyframeVector(keyframe, channel, location),
+ sourceValue: cosmiqKeyframeSourceVector(keyframe, location),
+ interpolation: interpolation,
+ source: keyframe
+ };
+ }).sort(function (left, right) { return left.timeMs - right.timeMs; });
+ if (!selected.length) return null;
+ selected.forEach(function (keyframe, index) {
+ if (index && selected[index - 1].timeMs === keyframe.timeMs) {
+ cosmiqFail('animation.invalid', 'Channel contains duplicate keyframe times.', location);
+ }
+ });
+ const interpolations = selected.slice(0, -1).map(function (keyframe, index) {
+ if (keyframe.interpolation === 'step') return 0;
+ if (keyframe.interpolation === 'catmullrom' ||
+ selected[index + 1].interpolation === 'catmullrom') return 2;
+ return keyframe.interpolation === 'bezier' ||
+ selected[index + 1].interpolation === 'bezier' ? 3 : 1;
+ });
+ const hasBezier = interpolations.indexOf(3) !== -1;
+ const draft = {
+ channel: channel,
+ location: location,
+ times: selected.map(function (keyframe) { return keyframe.timeMs; }),
+ values: selected.map(function (keyframe) { return keyframe.value; }),
+ interpolations: interpolations,
+ bezierControls: hasBezier ? selected.slice(0, -1).map(function (keyframe, index) {
+ return interpolations[index] === 3
+ ? cosmiqBezierControls(keyframe, selected[index + 1], channel, location)
+ : new Array(18).fill(0);
+ }) : null,
+ record: {times: null, values: null, interpolations: null, bezierControls: null}
+ };
+ channelDrafts.push(draft);
+ return draft.record;
+ }
+
+ function cosmiqPackAnimationBuffer(channelDrafts) {
+ if (!channelDrafts.length) return null;
+ let byteLength = 0;
+ channelDrafts.forEach(function (draft) {
+ const valueComponents = draft.channel === 'rotation' ? 4 : 3;
+ draft.record.times = {section: 'main_animation', byteOffset: byteLength, count: draft.times.length};
+ byteLength += draft.times.length * 4;
+ draft.record.values = {section: 'main_animation', byteOffset: byteLength, count: draft.values.length};
+ byteLength += draft.values.length * valueComponents * 4;
+ });
+ channelDrafts.forEach(function (draft) {
+ if (!draft.bezierControls) return;
+ draft.record.bezierControls = {
+ section: 'main_animation',
+ byteOffset: byteLength,
+ count: draft.bezierControls.length
+ };
+ byteLength += draft.bezierControls.length * 72;
+ });
+ channelDrafts.forEach(function (draft) {
+ draft.record.interpolations = {
+ section: 'main_animation',
+ byteOffset: byteLength,
+ count: draft.interpolations.length
+ };
+ byteLength += draft.interpolations.length;
+ });
+ const bytes = new Uint8Array(byteLength);
+ const view = new DataView(bytes.buffer);
+ channelDrafts.forEach(function (draft) {
+ let offset = draft.record.times.byteOffset;
+ draft.times.forEach(function (time) {
+ view.setUint32(offset, time, true);
+ offset += 4;
+ });
+ offset = draft.record.values.byteOffset;
+ draft.values.forEach(function (value) {
+ value.forEach(function (component) {
+ cosmiqWriteBinary32(view, offset, component, draft.location + '.values');
+ offset += 4;
+ });
+ });
+ if (draft.bezierControls) {
+ offset = draft.record.bezierControls.byteOffset;
+ draft.bezierControls.forEach(function (control) {
+ control.forEach(function (component) {
+ cosmiqWriteBinary32(view, offset, component, draft.location + '.bezierControls');
+ offset += 4;
+ });
+ });
+ }
+ offset = draft.record.interpolations.byteOffset;
+ draft.interpolations.forEach(function (interpolation) {
+ view.setUint8(offset++, interpolation);
+ });
+ });
+ return {id: 'main_animation', type: 'animation_buffer', bytes: bytes};
+ }
+
+ function cosmiqCompileAnimations(rawProject, source, targetForUuid, kind, allocated) {
+ const channelDrafts = [];
+ const clips = [];
+ const stateBindings = [];
+ const clipIdByAnimationUuid = Object.create(null);
+ const rawAnimations = Array.isArray(rawProject.animations) ? rawProject.animations : [];
+ rawAnimations.forEach(function (animation) {
+ const binding = source.animationBindings && source.animationBindings[animation.uuid];
+ const keyframeCount = animationKeyframeCount(animation);
+ if (!binding || !keyframeCount) return;
+ clipIdByAnimationUuid[animation.uuid] = cosmiqAllocatedId(
+ binding.sourceName || animation.name || 'clip',
+ 'clip',
+ allocated
+ );
+ });
+ rawAnimations.forEach(function (animation) {
+ const binding = source.animationBindings && source.animationBindings[animation.uuid];
+ const clipId = clipIdByAnimationUuid[animation.uuid];
+ if (!binding || !clipId) return;
+ const durationMs = Math.round(Number(animation.length) * 1000);
+ if (!Number.isFinite(durationMs) || durationMs < 1 || durationMs > 120000) {
+ cosmiqFail('animation.invalid', 'Animation duration cannot be normalized.', '$.animations.' + animation.uuid);
+ }
+ const loopMode = String(animation.loop || binding.loop && binding.loop.mode || 'once');
+ if (loopMode === 'hold') {
+ cosmiqFail('animation.invalid', 'Blockbench hold loops are not part of cosmiq.package.v2.');
+ }
+ const tracks = [];
+ Object.keys(animation.animators || {}).filter(function (uuid) {
+ return uuid !== 'effects';
+ }).sort().forEach(function (targetUuid) {
+ const animator = animation.animators[targetUuid];
+ const keyframes = animator && Array.isArray(animator.keyframes) ? animator.keyframes : [];
+ if (!keyframes.length) return;
+ const target = targetForUuid(targetUuid);
+ if (!target) {
+ cosmiqFail('reference.missing', 'Animation target cannot be normalized.', '$.animations.' + animation.uuid);
+ }
+ const track = {
+ target: target,
+ translation: cosmiqCompileAnimationChannel(
+ keyframes, 'translation', durationMs, '$.animations.' + animation.uuid, channelDrafts
+ ),
+ rotation: cosmiqCompileAnimationChannel(
+ keyframes, 'rotation', durationMs, '$.animations.' + animation.uuid, channelDrafts
+ ),
+ scale: cosmiqCompileAnimationChannel(
+ keyframes, 'scale', durationMs, '$.animations.' + animation.uuid, channelDrafts
+ )
+ };
+ if (track.translation || track.rotation || track.scale) tracks.push(track);
+ });
+ if (!tracks.length) {
+ cosmiqFail('animation.invalid', 'Authored animation has no supported transform tracks.', '$.animations.' + animation.uuid);
+ }
+ tracks.sort(function (left, right) {
+ function key(track) {
+ return track.target.kind === 'node'
+ ? 'node:' + track.target.nodeId
+ : 'actor:' + track.target.actorRole + ':' + track.target.bone;
+ }
+ return cosmiqCompareText(key(left), key(right));
+ });
+ clips.push({
+ id: clipId,
+ durationMs: durationMs,
+ loop: loopMode === 'loop'
+ ? {mode: 'range', startMs: 0, endMs: durationMs}
+ : {mode: 'none', startMs: 0, endMs: durationMs},
+ tracks: tracks
+ });
+ if (kind === 'accessory') {
+ const trigger = binding.trigger;
+ if (!trigger || ['player_state', 'player_event'].indexOf(trigger.kind) === -1) {
+ cosmiqFail('kind.invariant_violation', 'Every Accessory clip needs an explicit state or event binding.');
+ }
+ stateBindings.push({
+ trigger: {kind: trigger.kind, id: trigger.id},
+ clipId: clipId,
+ fallback: binding.fallback === true,
+ playback: trigger.kind === 'player_event'
+ ? 'once'
+ : (loopMode === 'loop' ? 'loop' : 'once'),
+ blendMillis: Math.max(0, Math.min(2000, Math.round(Number(binding.transitionMs) || 0))),
+ priority: Math.max(0, Math.min(1000, Math.round(Number(binding.priority) || 0)))
+ });
+ }
+ });
+ clips.sort(function (left, right) { return cosmiqCompareText(left.id, right.id); });
+ stateBindings.sort(function (left, right) {
+ return cosmiqCompareText(left.trigger.kind, right.trigger.kind) ||
+ cosmiqCompareText(left.trigger.id, right.trigger.id);
+ });
+ return {
+ clips: clips,
+ stateBindings: stateBindings,
+ clipIdByAnimationUuid: clipIdByAnimationUuid,
+ blob: cosmiqPackAnimationBuffer(channelDrafts)
+ };
+ }
+
+ function cosmiqCompileEffects(
+ snapshot,
+ source,
+ animationResult,
+ nodeIdByUuid,
+ actorTargetForUuid,
+ allocated
+ ) {
+ const resources = Array.isArray(source.resourceReferences) ? source.resourceReferences : [];
+ const resourceIdBySource = Object.create(null);
+ const particles = [];
+ const audio = [];
+ const blobs = [];
+ resources.forEach(function (resource) {
+ const id = cosmiqAllocatedId(resource.displayName || resource.id, 'resource', allocated);
+ resourceIdBySource[resource.id] = id;
+ if (resource.type === RESOURCE_TYPES.SOUND) {
+ if (!resource.embeddedOgg) {
+ cosmiqFail(
+ 'media.invalid_ogg_vorbis',
+ 'Sound and music resources must embed an Ogg Vorbis stream before export.'
+ );
+ }
+ const bytes = cosmiqEmbeddedMedia(resource.embeddedOgg, 'audio/ogg', '$.audio.' + id);
+ const inspected = cosmiqInspectOggVorbis(bytes, '$.audio.' + id);
+ const sectionId = cosmiqAllocatedId(id + '_ogg', 'audio_ogg', allocated);
+ audio.push({
+ id: id,
+ section: sectionId,
+ kind: resource.soundKind === 'music' ? 'music' : 'sound_effect',
+ sampleRateHz: inspected.sampleRateHz,
+ channels: inspected.channels,
+ durationMs: inspected.durationMs
+ });
+ blobs.push({id: sectionId, type: 'audio_ogg_vorbis', bytes: bytes});
+ } else {
+ cosmiqFail('kind.invariant_violation', 'Unknown authoring resource type.');
+ }
+ });
+ particles.sort(function (left, right) { return cosmiqCompareText(left.id, right.id); });
+ audio.sort(function (left, right) { return cosmiqCompareText(left.id, right.id); });
+
+ const snapshotNodes = Array.isArray(snapshot && snapshot.nodes) ? snapshot.nodes : [];
+ const snapshotByUuid = Object.create(null);
+ snapshotNodes.forEach(function (node) { if (node && node.uuid) snapshotByUuid[node.uuid] = node; });
+ const locatorIdByUuid = Object.create(null);
+ const locators = [];
+ Object.keys(source.effectLocatorBindings || {}).sort().forEach(function (uuid) {
+ const binding = source.effectLocatorBindings[uuid];
+ const node = snapshotByUuid[uuid];
+ if (!binding || !node) cosmiqFail('reference.missing', 'Effect Locator source is missing.');
+ let current = node;
+ let target = null;
+ let targetSource = null;
+ const seen = Object.create(null);
+ while (current && current.parentUuid && !seen[current.parentUuid]) {
+ seen[current.parentUuid] = true;
+ const parent = current.parentUuid;
+ if (nodeIdByUuid[parent]) {
+ target = {kind: 'node', nodeId: nodeIdByUuid[parent]};
+ targetSource = snapshotByUuid[parent] || null;
+ break;
+ }
+ if (actorTargetForUuid) {
+ const actorTarget = actorTargetForUuid(parent);
+ if (actorTarget && actorTarget.kind === 'actor_bone') {
+ target = {
+ kind: 'actor_attachment',
+ actorRole: actorTarget.actorRole,
+ attachment: actorTarget.bone
+ };
+ targetSource = snapshotByUuid[parent] || null;
+ break;
+ }
+ }
+ current = snapshotByUuid[parent];
+ }
+ if (!target) cosmiqFail('reference.missing', 'Effect Locator has no exported target.');
+ const id = cosmiqAllocatedId(binding.id || node.name, 'locator', allocated);
+ locatorIdByUuid[uuid] = id;
+ const position = cosmiqSourceVector(node.position || node.origin, [0, 0, 0], '$.locator');
+ const targetOrigin = cosmiqSourceVector(
+ targetSource && (targetSource.origin || targetSource.position),
+ [0, 0, 0],
+ '$.locator.target'
+ );
+ locators.push({
+ id: id,
+ target: target,
+ transform: {
+ translation: cosmiqPositionFromBlockbench([
+ position[0] - targetOrigin[0],
+ position[1] - targetOrigin[1],
+ position[2] - targetOrigin[2]
+ ]),
+ rotation: cosmiqEulerQuaternionFromBlockbench(
+ cosmiqSourceVector(node.rotation, [0, 0, 0], '$.locator.rotation')
+ ),
+ scale: [1, 1, 1],
+ pivot: [0, 0, 0]
+ }
+ });
+ });
+ locators.sort(function (left, right) { return cosmiqCompareText(left.id, right.id); });
+ const cues = [];
+ (Array.isArray(source.timelineCues) ? source.timelineCues : []).forEach(function (cue) {
+ if (!cue || cue.type === CUE_TYPES.COMMENT) return;
+ const clipId = animationResult.clipIdByAnimationUuid[cue.animationUuid];
+ if (!clipId) cosmiqFail('reference.missing', 'Timeline Cue clip cannot be normalized.');
+ if (cue.type === CUE_TYPES.NODE_VISIBILITY) {
+ const nodeId = nodeIdByUuid[cue.nodeUuid] || cue.nodeUuid;
+ if (!nodeId) cosmiqFail('reference.missing', 'Node-visibility Cue target cannot be normalized.');
+ cues.push({
+ id: cosmiqAllocatedId(cue.id || cue.markerName, 'cue', allocated),
+ clipId: clipId,
+ atMs: Math.round(Number(cue.atMs)),
+ endAtMs: cue.endAtMs == null ? null : Math.round(Number(cue.endAtMs)),
+ kind: CUE_TYPES.NODE_VISIBILITY,
+ resourceId: null,
+ locatorId: null,
+ nodeId: nodeId,
+ visible: cue.visible === true,
+ gain: null
+ });
+ return;
+ }
+ const resourceId = resourceIdBySource[cue.resourceId];
+ if (!resourceId) cosmiqFail('reference.missing', 'Timeline Cue resource cannot be normalized.');
+ const locatorId = cue.locatorUuid ? locatorIdByUuid[cue.locatorUuid] : null;
+ if (cue.type === CUE_TYPES.PARTICLE && !locatorId) {
+ cosmiqFail('reference.missing', 'Particle Cue requires an Effect Locator.');
+ }
+ cues.push({
+ id: cosmiqAllocatedId(cue.id || cue.markerName, 'cue', allocated),
+ clipId: clipId,
+ atMs: Math.round(Number(cue.atMs)),
+ endAtMs: cue.endAtMs == null ? null : Math.round(Number(cue.endAtMs)),
+ kind: cue.type,
+ resourceId: resourceId,
+ locatorId: locatorId,
+ nodeId: null,
+ visible: null,
+ gain: cue.type === CUE_TYPES.SOUND ? 1 : null
+ });
+ });
+ cues.sort(function (left, right) {
+ return cosmiqCompareText(left.clipId, right.clipId) || left.atMs - right.atMs ||
+ cosmiqCompareText(left.id, right.id);
+ });
+ return {audio: audio, particles: particles, locators: locators, cues: cues, blobs: blobs};
+ }
+
+ function cosmiqNodeBind(object, parentObject) {
+ const origin = cosmiqSourceVector(object && object.origin, [0, 0, 0], '$.origin');
+ const parentOrigin = cosmiqSourceVector(parentObject && parentObject.origin, [0, 0, 0], '$.parent.origin');
+ const rotation = cosmiqSourceVector(object && object.rotation, [0, 0, 0], '$.rotation');
+ return {
+ translation: cosmiqDeltaFromBlockbench([
+ origin[0] - parentOrigin[0],
+ origin[1] - parentOrigin[1],
+ origin[2] - parentOrigin[2]
+ ]),
+ rotation: cosmiqEulerQuaternionFromBlockbench(rotation),
+ scale: [1, 1, 1],
+ pivot: [0, 0, 0]
+ };
+ }
+
+ function compileAccessoryCosmiqPackage(snapshot, rawProject) {
+ const source = rawProject.cosmiq;
+ if (!source || !cosmiqPlainObject(source.nodeBindings) ||
+ !source.cosmetic || !Array.isArray(source.cosmetic.textureVariants)) {
+ cosmiqFail('kind.invariant_violation', 'Accessory source metadata was not refreshed before export.');
+ }
+ const textures = cosmiqRuntimeTextures(rawProject, source.cosmetic.textureVariants, false);
+ textures.raw = (Array.isArray(rawProject.textures) ? rawProject.textures : []).filter(function (texture) {
+ return texture && texture.cosmiq_role !== ROLES.REFERENCE && texture.role !== ROLES.REFERENCE;
+ });
+ const objectByUuid = cosmiqEditableObjects(rawProject);
+ const bindings = source.nodeBindings;
+ const bindingUuidByRuntimeId = Object.create(null);
+ Object.keys(bindings).forEach(function (uuid) {
+ if (bindings[uuid] && bindings[uuid].id) bindingUuidByRuntimeId[bindings[uuid].id] = uuid;
+ });
+ const runtimeUuids = Object.keys(bindings).filter(function (uuid) {
+ return bindings[uuid] && bindings[uuid].role === 'cosmetic_node' && objectByUuid[uuid];
+ });
+ if (!runtimeUuids.length) cosmiqFail('accessory.no_body_group', 'Accessory has no runtime nodes.');
+ const parentUuid = Object.create(null);
+ runtimeUuids.forEach(function (uuid) {
+ parentUuid[uuid] = bindings[uuid].parentNodeId
+ ? bindingUuidByRuntimeId[bindings[uuid].parentNodeId]
+ : null;
+ });
+ const bodyGroupByAnchor = {
+ 'cosmiq:player/head': 'head', 'cosmiq:player/torso': 'torso',
+ 'cosmiq:player/left_arm': 'arms', 'cosmiq:player/right_arm': 'arms',
+ 'cosmiq:player/left_leg': 'legs', 'cosmiq:player/right_leg': 'legs'
+ };
+ const bodyGroupByUuid = Object.create(null);
+ function bodyGroupForUuid(uuid) {
+ if (Object.hasOwn(bodyGroupByUuid, uuid)) return bodyGroupByUuid[uuid];
+ const parent = parentUuid[uuid];
+ const group = parent
+ ? bodyGroupForUuid(parent)
+ : (bodyGroupByAnchor[bindings[uuid].anchorId] || null);
+ bodyGroupByUuid[uuid] = group;
+ return group;
+ }
+ const signatureByUuid = Object.create(null);
+ function variantTopologyName(value) {
+ return stableKey(String(value == null ? '' : value)
+ .replace(/\b(?:wide|slim)\b/gi, ' ')
+ .replace(/\s+/g, ' ')
+ .trim());
+ }
+ function signature(uuid) {
+ if (signatureByUuid[uuid]) return signatureByUuid[uuid];
+ const binding = bindings[uuid];
+ const parent = parentUuid[uuid];
+ const own = variantTopologyName(binding.sourceName || uuid) + ':' + (binding.sourceType || 'node');
+ signatureByUuid[uuid] = parent
+ ? signature(parent) + '/' + own
+ : String(binding.anchorId || '') + '/' + own;
+ return signatureByUuid[uuid];
+ }
+ const pairMembers = Object.create(null);
+ runtimeUuids.forEach(function (uuid) {
+ const binding = bindings[uuid];
+ if (binding.modelVariant === 'wide' || binding.modelVariant === 'slim') {
+ const key = signature(uuid);
+ const pair = pairMembers[key] || (pairMembers[key] = {});
+ if (pair[binding.modelVariant]) {
+ cosmiqFail('reference.duplicate_id', 'Wide/slim branches contain ambiguous matching nodes.');
+ }
+ pair[binding.modelVariant] = uuid;
+ }
+ });
+ const variantGeometryByGroup = Object.create(null);
+ runtimeUuids.forEach(function (uuid) {
+ const binding = bindings[uuid];
+ const object = objectByUuid[uuid];
+ const hasGeometry = binding.hasGeometry === true ||
+ object.type === 'cube' || object.type === 'mesh' || object.vertices;
+ if (!hasGeometry || (binding.modelVariant !== 'wide' && binding.modelVariant !== 'slim')) return;
+ const group = bodyGroupForUuid(uuid);
+ if (!group) return;
+ const variants = variantGeometryByGroup[group] ||
+ (variantGeometryByGroup[group] = {wide: false, slim: false});
+ variants[binding.modelVariant] = true;
+ });
+ const universalBodyGroups = Object.create(null);
+ Object.keys(variantGeometryByGroup).forEach(function (group) {
+ const variants = variantGeometryByGroup[group];
+ if (variants.wide === variants.slim) return;
+ if (group === 'arms') {
+ cosmiqFail('kind.invariant_violation', 'Arm geometry must populate matching Wide and Slim branches.');
+ }
+ universalBodyGroups[group] = true;
+ });
+ Object.keys(pairMembers).forEach(function (key) {
+ const pair = pairMembers[key];
+ const uuid = pair.wide || pair.slim;
+ if (!universalBodyGroups[bodyGroupForUuid(uuid)] && (!pair.wide || !pair.slim)) {
+ cosmiqFail('kind.invariant_violation', 'Accessory must populate matching Wide and Slim branches.');
+ }
+ });
+ const allocated = Object.create(null);
+ textures.records.forEach(function (texture) { allocated[texture.id] = true; });
+ textures.variants.forEach(function (variant) { allocated[variant.id] = true; });
+ const variantKeyBySignature = Object.create(null);
+ Object.keys(pairMembers).sort().forEach(function (key) {
+ const pair = pairMembers[key];
+ if (universalBodyGroups[bodyGroupForUuid(pair.wide || pair.slim)]) return;
+ const sourceName = bindings[pair.wide].sourceName;
+ variantKeyBySignature[key] = cosmiqAllocatedId(sourceName, 'variant_node', allocated);
+ });
+ const nodeIdByUuid = Object.create(null);
+ runtimeUuids.forEach(function (uuid) {
+ const binding = bindings[uuid];
+ const prefix = binding.modelVariant === 'wide' || binding.modelVariant === 'slim'
+ ? binding.modelVariant + '_' : '';
+ nodeIdByUuid[uuid] = cosmiqAllocatedId(prefix + (binding.sourceName || uuid), 'node', allocated);
+ });
+ const depth = Object.create(null);
+ function nodeDepth(uuid, visiting) {
+ if (depth[uuid]) return depth[uuid];
+ if (visiting && visiting[uuid]) cosmiqFail('graph.cycle', 'Accessory hierarchy contains a cycle.');
+ const next = Object.assign({}, visiting || {});
+ next[uuid] = true;
+ depth[uuid] = parentUuid[uuid] ? nodeDepth(parentUuid[uuid], next) + 1 : 1;
+ return depth[uuid];
+ }
+ runtimeUuids.forEach(function (uuid) { nodeDepth(uuid); });
+ runtimeUuids.sort(function (left, right) {
+ return depth[left] - depth[right] || cosmiqCompareText(nodeIdByUuid[left], nodeIdByUuid[right]);
+ });
+ const materials = [];
+ const materialByTexture = Object.create(null);
+ const geometry = [];
+ const meshDrafts = [];
+ const nodes = runtimeUuids.map(function (uuid) {
+ const binding = bindings[uuid];
+ const object = objectByUuid[uuid];
+ const parent = parentUuid[uuid];
+ let parentObject = parent ? objectByUuid[parent] : objectByUuid[binding.anchorGroupUuid];
+ const geometryIds = [];
+ if (object.type === 'cube' || (Array.isArray(object.from) && Array.isArray(object.to))) {
+ const geometryId = cosmiqAllocatedId((binding.sourceName || uuid) + '_cube', 'geometry', allocated);
+ geometry.push(cosmiqCuboidGeometry(
+ object, geometryId, textures, materials, materialByTexture, allocated
+ ));
+ geometryIds.push(geometryId);
+ } else if (object.type === 'mesh' || object.vertices) {
+ cosmiqMeshGeometry(
+ object,
+ stableKey(binding.sourceName || uuid) + '_mesh',
+ textures,
+ materials,
+ materialByTexture,
+ allocated,
+ meshDrafts
+ ).forEach(function (record) {
+ geometry.push(record);
+ geometryIds.push(record.id);
+ });
+ }
+ const modelVariant = !universalBodyGroups[bodyGroupForUuid(uuid)] &&
+ ['wide', 'slim'].indexOf(binding.modelVariant) !== -1
+ ? binding.modelVariant
+ : 'universal';
+ const anchor = parent ? null : binding.anchorId;
+ if (!parent && [
+ 'cosmiq:player/head', 'cosmiq:player/torso', 'cosmiq:player/left_arm',
+ 'cosmiq:player/right_arm', 'cosmiq:player/left_leg', 'cosmiq:player/right_leg'
+ ].indexOf(anchor) === -1) {
+ cosmiqFail('graph.invalid_owner', 'Accessory uses a non-standard player anchor.');
+ }
+ return {
+ id: nodeIdByUuid[uuid],
+ parentId: parent ? nodeIdByUuid[parent] : null,
+ attachment: parent ? null : {kind: 'player_anchor', anchor: anchor},
+ bind: cosmiqNodeBind(object, parentObject),
+ geometryIds: geometryIds.sort(),
+ modelVariant: modelVariant,
+ variantKey: modelVariant === 'universal' ? null : variantKeyBySignature[signature(uuid)],
+ defaultVisible: object.visibility !== false
+ };
+ });
+ materials.sort(function (left, right) { return cosmiqCompareText(left.id, right.id); });
+ geometry.sort(function (left, right) { return cosmiqCompareText(left.id, right.id); });
+ const bodyGroups = Object.create(null);
+ nodes.filter(function (node) { return node.parentId === null; }).forEach(function (node) {
+ bodyGroups[bodyGroupByAnchor[node.attachment.anchor]] = true;
+ });
+ const groups = Object.keys(bodyGroups);
+ if (groups.length > 1) cosmiqFail('accessory.multiple_body_groups', 'Accessory spans multiple Body Groups.');
+ const hasPairs = Object.keys(variantKeyBySignature).length > 0;
+ const manifest = cosmiqBaseManifest('accessory', hasPairs ? 'wide' : 'universal');
+ manifest.bodyGroup = groups[0] || null;
+ manifest.playerAppearance = source.playerAppearance == null
+ ? null
+ : JSON.parse(JSON.stringify(source.playerAppearance));
+ manifest.nodes = nodes;
+ manifest.materials = materials;
+ manifest.geometry = geometry;
+ manifest.textures = textures.records.sort(function (left, right) {
+ return cosmiqCompareText(left.id, right.id);
+ });
+ manifest.textureVariants = textures.variants;
+ const animationResult = cosmiqCompileAnimations(
+ rawProject,
+ source,
+ function (uuid) {
+ return nodeIdByUuid[uuid] ? {kind: 'node', nodeId: nodeIdByUuid[uuid]} : null;
+ },
+ 'accessory',
+ allocated
+ );
+ manifest.clips = animationResult.clips;
+ manifest.stateBindings = animationResult.stateBindings;
+ const effectResult = cosmiqCompileEffects(
+ snapshot,
+ source,
+ animationResult,
+ nodeIdByUuid,
+ null,
+ allocated
+ );
+ manifest.audio = effectResult.audio;
+ manifest.particles = effectResult.particles;
+ manifest.locators = effectResult.locators;
+ manifest.cues = effectResult.cues;
+ cosmiqApplyAuthoredCamera(manifest, snapshot && snapshot.project);
+ const populatedCameraVariants = hasPairs ? ['wide', 'slim'] : ['universal'];
+ if (populatedCameraVariants.indexOf(manifest.previewCamera.pose.modelVariant) === -1) {
+ manifest.previewCamera.pose.modelVariant = populatedCameraVariants[0];
+ }
+ if (manifest.previewCamera.pose.clipId) {
+ const previewAnimationUuid = Object.keys(source.animationBindings || {}).find(function (uuid) {
+ return source.animationBindings[uuid] &&
+ source.animationBindings[uuid].id === manifest.previewCamera.pose.clipId;
+ });
+ if (previewAnimationUuid && animationResult.clipIdByAnimationUuid[previewAnimationUuid]) {
+ manifest.previewCamera.pose.clipId = animationResult.clipIdByAnimationUuid[previewAnimationUuid];
+ }
+ }
+ const geometryBlob = cosmiqPackGeometryBuffer(meshDrafts);
+ return cosmiqFinalizeCompiledPackage(
+ manifest,
+ (geometryBlob ? [geometryBlob] : [])
+ .concat(animationResult.blob ? [animationResult.blob] : [])
+ .concat(textures.blobs)
+ .concat(effectResult.blobs)
+ );
+ }
+
+ function cosmiqNormalizeOutcomeSets(sourceOutcomeSets, allocated) {
+ const idBySource = Object.create(null);
+ const valueBySet = Object.create(null);
+ const records = (Array.isArray(sourceOutcomeSets) ? sourceOutcomeSets : []).map(function (sourceSet) {
+ if (!sourceSet || typeof sourceSet.id !== 'string' || !Array.isArray(sourceSet.values)) {
+ cosmiqFail('logic.invalid', 'Outcome Sets must contain an ID and values.');
+ }
+ const id = cosmiqAllocatedId(sourceSet.id, 'outcome', allocated);
+ idBySource[sourceSet.id] = id;
+ const valueAllocated = Object.create(null);
+ valueBySet[sourceSet.id] = Object.create(null);
+ const values = sourceSet.values.map(function (sourceValue) {
+ const value = cosmiqAllocatedId(sourceValue, 'value', valueAllocated);
+ valueBySet[sourceSet.id][sourceValue] = value;
+ return value;
+ }).sort();
+ return {id: id, values: values};
+ });
+ records.sort(function (left, right) { return cosmiqCompareText(left.id, right.id); });
+ return {records: records, idBySource: idBySource, valueBySet: valueBySet};
+ }
+
+ function cosmiqNormalizeLogic(
+ sourceLogic,
+ source,
+ animationResult,
+ actorRoleBySourceId,
+ outcomeResult,
+ allocated
+ ) {
+ if (!sourceLogic || !Array.isArray(sourceLogic.states)) {
+ cosmiqFail('logic.invalid', 'Emote requires a compiled declarative logic graph.');
+ }
+ const clipIdBySourceId = Object.create(null);
+ Object.keys(source.animationBindings || {}).forEach(function (uuid) {
+ const binding = source.animationBindings[uuid];
+ if (binding && binding.id && animationResult.clipIdByAnimationUuid[uuid]) {
+ clipIdBySourceId[binding.id] = animationResult.clipIdByAnimationUuid[uuid];
+ }
+ });
+ const stateIdBySource = Object.create(null);
+ const stateAllocated = Object.create(null);
+ sourceLogic.states.forEach(function (state) {
+ stateIdBySource[state.id] = cosmiqAllocatedId(state.id, 'state', stateAllocated);
+ });
+ const initialState = stateIdBySource[sourceLogic.initialState];
+ if (!initialState) cosmiqFail('logic.invalid', 'Emote initial logic state does not resolve.');
+ const states = sourceLogic.states.map(function (state) {
+ const rules = (Array.isArray(state.rules) ? state.rules : []).map(function (rule) {
+ const event = cosmiqClone(rule.event);
+ const action = cosmiqClone(rule.action);
+ if (event.clipId != null) {
+ event.clipId = clipIdBySourceId[event.clipId] || event.clipId;
+ }
+ if (event.actorRole != null) {
+ event.actorRole = actorRoleBySourceId[event.actorRole] || event.actorRole;
+ }
+ if (event.type === 'outcome_resolved') {
+ const sourceOutcomeId = event.outcomeSetId;
+ event.outcomeSetId = outcomeResult.idBySource[sourceOutcomeId] || sourceOutcomeId;
+ const values = outcomeResult.valueBySet[sourceOutcomeId] || {};
+ event.value = values[event.value] || event.value;
+ }
+ if (action.type === 'play_animation') {
+ action.clipId = clipIdBySourceId[action.clipId] || action.clipId;
+ action.target = action.target === 'all'
+ ? 'all'
+ : (actorRoleBySourceId[action.target] || action.target);
+ if (!Object.prototype.hasOwnProperty.call(action, 'loopDurationMillis')) {
+ action.loopDurationMillis = null;
+ }
+ } else if (action.type === 'transition') {
+ action.state = stateIdBySource[action.state] || action.state;
+ } else if (action.type === 'request_outcome') {
+ action.outcomeSetId = outcomeResult.idBySource[action.outcomeSetId] || action.outcomeSetId;
+ }
+ return {event: event, action: action};
+ });
+ return {id: stateIdBySource[state.id], rules: rules};
+ });
+ states.sort(function (left, right) {
+ if (left.id === initialState) return -1;
+ if (right.id === initialState) return 1;
+ return cosmiqCompareText(left.id, right.id);
+ });
+ return {initialState: initialState, states: states};
+ }
+
+ function compileEmoteCosmiqPackage(snapshot, rawProject) {
+ const source = rawProject.cosmiq;
+ if (!source || !Array.isArray(source.actors) || !cosmiqPlainObject(source.animationBindings)) {
+ cosmiqFail('kind.invariant_violation', 'Emote source metadata was not refreshed before export.');
+ }
+ const allocated = Object.create(null);
+ const preciseRig = source.emote &&
+ source.emote.rigProfile === EMOTE_RIG_PROFILES.PRECISE;
+ const playerSkeleton = preciseRig
+ ? 'cosmiq:player/humanoid-precise-v1'
+ : 'cosmiq:player/humanoid-v1';
+ const actorRoleByIndex = Object.create(null);
+ const actorRoleBySourceId = Object.create(null);
+ const actors = source.actors.slice().sort(function (left, right) {
+ return Number(left.index) - Number(right.index);
+ }).map(function (actor, index) {
+ const roleId = index === 0 ? 'host' : cosmiqAllocatedId('actor_' + (index + 1), 'actor', allocated);
+ actorRoleByIndex[Number(actor.index)] = roleId;
+ actorRoleBySourceId[actor.id] = roleId;
+ const offset = Number(actor.formationOffsetUnits) || 0;
+ return {
+ id: roleId,
+ skeleton: playerSkeleton,
+ required: index === 0,
+ formation: cosmiqDefaultTransform(cosmiqPositionFromBlockbench([offset, 0, 0]))
+ };
+ });
+ if (!actors.length || actors.length > 4) {
+ cosmiqFail('kind.invariant_violation', 'Emote needs one through four Actor Roles.');
+ }
+
+ const textures = cosmiqRuntimeTextures(
+ rawProject,
+ textureVariantsFromTextures(
+ (Array.isArray(snapshot && snapshot.textures) ? snapshot.textures : []).filter(function (texture) {
+ return texture && texture.role !== ROLES.REFERENCE;
+ })
+ ),
+ false,
+ true
+ );
+ textures.raw = (Array.isArray(rawProject.textures) ? rawProject.textures : []).filter(function (texture) {
+ return texture && texture.cosmiq_role !== ROLES.REFERENCE && texture.role !== ROLES.REFERENCE;
+ });
+ textures.records.forEach(function (texture) { allocated[texture.id] = true; });
+ const objectByUuid = cosmiqEditableObjects(rawProject);
+ const bindings = source.nodeBindings || {};
+ const propUuids = Object.keys(bindings).filter(function (uuid) {
+ const role = bindings[uuid] && bindings[uuid].role;
+ return (role === ROLES.OBJECT_ACTOR || role === ROLES.OBJECT_ACTOR_NODE) && objectByUuid[uuid];
+ });
+ const propSet = Object.create(null);
+ propUuids.forEach(function (uuid) { propSet[uuid] = true; });
+ const nodeIdByUuid = Object.create(null);
+ propUuids.forEach(function (uuid) {
+ nodeIdByUuid[uuid] = cosmiqAllocatedId(bindings[uuid].sourceName || objectByUuid[uuid].name, 'prop', allocated);
+ });
+ const parentUuid = Object.create(null);
+ propUuids.forEach(function (uuid) {
+ const candidate = bindings[uuid].parentUuid;
+ parentUuid[uuid] = propSet[candidate] ? candidate : null;
+ });
+ const depth = Object.create(null);
+ function propDepth(uuid, visiting) {
+ if (depth[uuid]) return depth[uuid];
+ const active = Object.assign({}, visiting || {});
+ if (active[uuid]) cosmiqFail('graph.cycle', 'Emote Prop hierarchy contains a cycle.');
+ active[uuid] = true;
+ depth[uuid] = parentUuid[uuid] ? propDepth(parentUuid[uuid], active) + 1 : 1;
+ return depth[uuid];
+ }
+ propUuids.forEach(function (uuid) { propDepth(uuid); });
+ propUuids.sort(function (left, right) {
+ return depth[left] - depth[right] || cosmiqCompareText(nodeIdByUuid[left], nodeIdByUuid[right]);
+ });
+ const materials = [];
+ const materialByTexture = Object.create(null);
+ const geometry = [];
+ const meshDrafts = [];
+ const nodes = propUuids.map(function (uuid) {
+ const binding = bindings[uuid];
+ const object = objectByUuid[uuid];
+ const parent = parentUuid[uuid];
+ const geometryIds = [];
+ if (object.type === 'cube' || (Array.isArray(object.from) && Array.isArray(object.to))) {
+ const geometryId = cosmiqAllocatedId((binding.sourceName || uuid) + '_cube', 'geometry', allocated);
+ geometry.push(cosmiqCuboidGeometry(
+ object, geometryId, textures, materials, materialByTexture, allocated
+ ));
+ geometryIds.push(geometryId);
+ } else if (object.type === 'mesh' || object.vertices) {
+ cosmiqMeshGeometry(
+ object,
+ stableKey(binding.sourceName || uuid) + '_mesh',
+ textures,
+ materials,
+ materialByTexture,
+ allocated,
+ meshDrafts
+ ).forEach(function (record) {
+ geometry.push(record);
+ geometryIds.push(record.id);
+ });
+ }
+ return {
+ id: nodeIdByUuid[uuid],
+ parentId: parent ? nodeIdByUuid[parent] : null,
+ attachment: parent ? null : {kind: 'scene'},
+ bind: cosmiqNodeBind(object, parent ? objectByUuid[parent] : null),
+ geometryIds: geometryIds.sort(),
+ modelVariant: 'universal',
+ variantKey: null,
+ defaultVisible: object.visibility !== false
+ };
+ });
+ materials.sort(function (left, right) { return cosmiqCompareText(left.id, right.id); });
+ geometry.sort(function (left, right) { return cosmiqCompareText(left.id, right.id); });
+
+ function emoteTargetForUuid(uuid) {
+ const binding = bindings[uuid];
+ if (!binding) return null;
+ if (binding.role === ROLES.PLAYER_BONE) {
+ return {
+ kind: 'actor_bone',
+ actorRole: actorRoleByIndex[Number(binding.actorIndex)],
+ bone: binding.semanticBone
+ };
+ }
+ if (binding.role === ROLES.EMOTE_ACTOR) {
+ return {
+ kind: 'actor_bone',
+ actorRole: actorRoleByIndex[Number(binding.actorIndex)],
+ bone: 'root'
+ };
+ }
+ return nodeIdByUuid[uuid] ? {kind: 'node', nodeId: nodeIdByUuid[uuid]} : null;
+ }
+ const animationResult = cosmiqCompileAnimations(
+ rawProject,
+ source,
+ emoteTargetForUuid,
+ 'emote',
+ allocated
+ );
+ if (!animationResult.clips.length) {
+ cosmiqFail('animation.invalid', 'Emote must contain at least one authored transform clip.');
+ }
+ const outcomeResult = cosmiqNormalizeOutcomeSets(source.outcomeSets, allocated);
+ const logic = cosmiqNormalizeLogic(
+ source.logic,
+ source,
+ animationResult,
+ actorRoleBySourceId,
+ outcomeResult,
+ allocated
+ );
+ const project = snapshot && snapshot.project || {};
+ const movement = project.emoteMovementPolicy && cosmiqPlainObject(project.emoteMovementPolicy)
+ ? cosmiqClone(project.emoteMovementPolicy)
+ : {
+ mode: project.emoteTraversable === true ? 'full_body_traversable' : 'stationary',
+ cancelOnAttack: true,
+ cancelOnDamage: true,
+ allowCameraControl: true
+ };
+ const manifest = cosmiqBaseManifest('emote');
+ manifest.movementPolicy = movement;
+ manifest.actors = actors;
+ manifest.nodes = nodes;
+ manifest.materials = materials;
+ manifest.geometry = geometry;
+ manifest.textures = textures.records.sort(function (left, right) {
+ return cosmiqCompareText(left.id, right.id);
+ });
+ manifest.textureVariants = textures.variants;
+ manifest.clips = animationResult.clips;
+ manifest.outcomeSets = outcomeResult.records;
+ manifest.logic = logic;
+ const effectResult = cosmiqCompileEffects(
+ snapshot,
+ source,
+ animationResult,
+ nodeIdByUuid,
+ emoteTargetForUuid,
+ allocated
+ );
+ manifest.audio = effectResult.audio;
+ manifest.particles = effectResult.particles;
+ manifest.locators = effectResult.locators;
+ manifest.cues = effectResult.cues;
+ manifest.previewCamera.pose.clipId = animationResult.clips[0].id;
+ manifest.previewCamera.pose.visiblePropRootIds = nodes.filter(function (node) {
+ return node.parentId === null && node.defaultVisible;
+ }).map(function (node) { return node.id; }).sort();
+ cosmiqApplyAuthoredCamera(manifest, project);
+ manifest.previewCamera.pose.visiblePropRootIds = manifest.previewCamera.pose.visiblePropRootIds.map(
+ function (sourceId) {
+ if (nodeIdByUuid[sourceId]) return nodeIdByUuid[sourceId];
+ const sourceUuid = Object.keys(bindings).find(function (uuid) {
+ const binding = bindings[uuid];
+ return parentUuid[uuid] === null && binding &&
+ (binding.id === sourceId || binding.sourceName === sourceId);
+ });
+ return sourceUuid ? nodeIdByUuid[sourceUuid] : sourceId;
+ }
+ ).sort();
+ const oldPreviewClip = manifest.previewCamera.pose.clipId;
+ const sourceBindingUuid = Object.keys(source.animationBindings).find(function (uuid) {
+ return source.animationBindings[uuid] && source.animationBindings[uuid].id === oldPreviewClip;
+ });
+ if (sourceBindingUuid && animationResult.clipIdByAnimationUuid[sourceBindingUuid]) {
+ manifest.previewCamera.pose.clipId = animationResult.clipIdByAnimationUuid[sourceBindingUuid];
+ }
+ const geometryBlob = cosmiqPackGeometryBuffer(meshDrafts);
+ return cosmiqFinalizeCompiledPackage(
+ manifest,
+ (geometryBlob ? [geometryBlob] : [])
+ .concat(animationResult.blob ? [animationResult.blob] : [])
+ .concat(textures.blobs)
+ .concat(effectResult.blobs)
+ );
+ }
+
+ function compileBlockbenchCosmiqPackage(snapshot, rawProject) {
+ if (!rawProject || !cosmiqPlainObject(rawProject) ||
+ !rawProject.meta || rawProject.meta.model_format !== FORMAT_ID) {
+ cosmiqFail('kind.invariant_violation', 'Blockbench did not provide a Cosmiq authoring project.');
+ }
+ const kind = snapshot && snapshot.project && snapshot.project.kind;
+ if (kind === PROJECT_KINDS.CAPE) return compileCapeCosmiqPackage(snapshot, rawProject);
+ if (kind === PROJECT_KINDS.EMOTE) return compileEmoteCosmiqPackage(snapshot, rawProject);
+ if (kind === PROJECT_KINDS.COSMETIC) return compileAccessoryCosmiqPackage(snapshot, rawProject);
+ cosmiqFail('kind.invariant_violation', 'Authoring project kind must be cosmetic, cape, or emote.');
+ }
+
+ function stableKey(value) {
+ const normalized = String(value == null ? '' : value)
+ .toLowerCase()
+ .replace(/[^a-z0-9._-]+/g, '_')
+ .replace(/^_+|_+$/g, '');
+ return normalized || 'missing';
+ }
+
+ function runtimeId(projectId, category, sourceKey) {
+ const normalizedProject = normalizeProjectId(projectId) || 'creator:untitled';
+ const separator = normalizedProject.indexOf(':');
+ const namespace = normalizedProject.slice(0, separator);
+ const projectPath = normalizedProject.slice(separator + 1);
+ return namespace + ':' + projectPath + '/' + normalizeToken(category) + '/' + stableKey(sourceKey);
+ }
+
+ function parseParticleMarkerName(name) {
+ const raw = String(name == null ? '' : name).trim();
+ const match = /^(?:particle|emitter)[.:/_ -]+(.+)$/i.exec(raw);
+ if (!match) {
+ return null;
+ }
+ const id = normalizeToken(match[1]);
+ return id || null;
+ }
+
+ function resolveTrigger(kind, value) {
+ const normalizedKind = String(kind == null ? '' : kind).trim().toLowerCase();
+ const normalizedValue = normalizeToken(value);
+ const trigger = triggerById[normalizedValue] || triggerByAlias[normalizedValue] || null;
+ if (!trigger) {
+ return null;
+ }
+ if (normalizedKind && normalizedKind !== 'none' && trigger.kind !== normalizedKind) {
+ return null;
+ }
+ return trigger;
+ }
+
+ function parseAnimationName(name) {
+ const raw = String(name == null ? '' : name).trim().toLowerCase();
+ let kind = '';
+ let value = raw;
+ if (/^state[.:/_ -]/.test(raw)) {
+ kind = 'player_state';
+ value = raw.replace(/^state[.:/_ -]+/, '');
+ } else if (/^event[.:/_ -]/.test(raw)) {
+ kind = 'player_event';
+ value = raw.replace(/^event[.:/_ -]+/, '');
+ }
+ const trigger = resolveTrigger(kind, value);
+ if (!trigger) {
+ return null;
+ }
+ return {
+ kind: trigger.kind,
+ triggerId: trigger.id,
+ inferredFromName: true
+ };
+ }
+
+ function makeIssue(severity, code, message, sourceUuid) {
+ const deferred = severity === 'error' && DEFERRED_SIZE_LIMIT_CODES[code] === true;
+ return {
+ severity: deferred ? 'info' : severity,
+ code: code,
+ message: deferred ? message + ' This size budget is informational for now.' : message,
+ sourceUuid: sourceUuid || null
+ };
+ }
+
+ function resourceContainsRawMedia(resource) {
+ if (!resource || typeof resource !== 'object') {
+ return false;
+ }
+ const allowed = {
+ id: true,
+ type: true,
+ displayName: true,
+ assetRef: true,
+ profileId: true,
+ appearance: true,
+ emitter: true,
+ editorStatus: true,
+ runtimeExport: true,
+ soundKind: true,
+ embeddedOgg: true,
+ referenceOnly: true,
+ rawMediaIncluded: true
+ };
+ if ((resource.rawMediaIncluded === true && !/^data:audio\/ogg;base64,[A-Za-z0-9+/]*={0,2}$/.test(
+ String(resource.embeddedOgg || '')
+ )) || isRawMediaReference(resource.assetRef)) {
+ return true;
+ }
+ return Object.keys(resource).some(function (key) {
+ if (allowed[key]) {
+ return false;
+ }
+ const normalizedKey = key.toLowerCase().replace(/[^a-z0-9]/g, '');
+ if (/(?:path|url|uri|file|bytes|buffer|blob|content|payload|media|audio|gif)$/.test(normalizedKey)) {
+ return true;
+ }
+ const value = resource[key];
+ if (typeof value === 'string' && isRawMediaReference(value)) {
+ return true;
+ }
+ if (typeof ArrayBuffer !== 'undefined' && (
+ value instanceof ArrayBuffer ||
+ (typeof ArrayBuffer.isView === 'function' && ArrayBuffer.isView(value))
+ )) {
+ return true;
+ }
+ return false;
+ });
+ }
+
+ function collectTimelineMarkers(markers) {
+ return (Array.isArray(markers) ? markers : []).map(function (marker) {
+ const source = marker && typeof marker === 'object' ? marker : {};
+ const rawName = source.name === 0 ? '' : source.name;
+ const time = Number(source.timeSeconds == null ? source.time : source.timeSeconds);
+ const color = Number(source.color);
+ return {
+ name: boundedCreatorString(rawName, AUTHORING_LIMITS.markerNameCharacters),
+ timeSeconds: Number.isFinite(time) ? time : null,
+ atMs: Number.isFinite(time) ? Math.round(time * 1000) : null,
+ color: Number.isFinite(color) ? Math.max(0, Math.min(7, Math.floor(color))) : 0
+ };
+ }).sort(function (left, right) {
+ const leftTime = left.timeSeconds == null ? Number.POSITIVE_INFINITY : left.timeSeconds;
+ const rightTime = right.timeSeconds == null ? Number.POSITIVE_INFINITY : right.timeSeconds;
+ return leftTime - rightTime || cosmiqCompareText(left.name, right.name);
+ });
+ }
+
+ function buildAuthoringMetadata(snapshot) {
+ const source = snapshot && typeof snapshot === 'object' ? snapshot : {};
+ const project = source.project && typeof source.project === 'object' ? source.project : {};
+ const nodes = Array.isArray(source.nodes) ? source.nodes : [];
+ const animations = Array.isArray(source.animations) ? source.animations : [];
+ const rawResources = Array.isArray(project.resourceReferences) ? project.resourceReferences : [];
+ const rawCues = Array.isArray(project.timelineCues) ? project.timelineCues : [];
+ const issues = [];
+ const nodeByUuid = Object.create(null);
+ nodes.forEach(function (node) {
+ if (node && node.uuid) nodeByUuid[node.uuid] = node;
+ });
+ const resourceReferences = [];
+ const resourcesById = Object.create(null);
+ const resourceIds = Object.create(null);
+
+ if (rawResources.length > AUTHORING_LIMITS.resources) {
+ issues.push(makeIssue(
+ 'error',
+ 'COSMIQ_RESOURCE_LIMIT',
+ 'Project has ' + rawResources.length + ' resource references; limit is ' +
+ AUTHORING_LIMITS.resources + '.'
+ ));
+ }
+ rawResources.forEach(function (rawResource) {
+ const sourceResource = rawResource && typeof rawResource === 'object' ? rawResource : {};
+ const resource = normalizeResourceReference(sourceResource);
+ if (!resource.id) {
+ issues.push(makeIssue(
+ 'error',
+ 'COSMIQ_RESOURCE_ID_INVALID',
+ 'Every resource reference needs a lowercase namespaced ID.'
+ ));
+ } else if (resourceIds[resource.id]) {
+ issues.push(makeIssue(
+ 'error',
+ 'COSMIQ_RESOURCE_ID_DUPLICATE',
+ 'Resource reference ID ' + resource.id + ' is duplicated.'
+ ));
+ }
+ if (resource.id) {
+ resourceIds[resource.id] = true;
+ resourcesById[resource.id] = resource;
+ }
+ if (resource.type !== RESOURCE_TYPES.PARTICLE_EFFECT &&
+ resource.type !== RESOURCE_TYPES.SOUND) {
+ issues.push(makeIssue(
+ 'error',
+ 'COSMIQ_RESOURCE_TYPE_INVALID',
+ 'Resource ' + (resource.id || '(unnamed)') +
+ ' must be particle_effect or sound.'
+ ));
+ }
+ const rawDisplayName = String(sourceResource.displayName == null ? '' : sourceResource.displayName);
+ if (!resource.displayName || rawDisplayName.length > AUTHORING_LIMITS.displayNameCharacters ||
+ containsForbiddenControl(rawDisplayName, false)) {
+ issues.push(makeIssue(
+ 'error',
+ 'COSMIQ_RESOURCE_NAME_INVALID',
+ 'Resource display names must contain readable text no longer than ' +
+ AUTHORING_LIMITS.displayNameCharacters + ' characters.'
+ ));
+ }
+ if (resource.type === RESOURCE_TYPES.SOUND && !resource.embeddedOgg) {
+ issues.push(makeIssue(
+ 'error',
+ 'COSMIQ_RESOURCE_REF_MISSING',
+ 'Resource ' + (resource.displayName || resource.id || '(unnamed)') +
+ ' has no embedded runtime media.'
+ ));
+ }
+ if (resource.type === RESOURCE_TYPES.PARTICLE_EFFECT &&
+ (!resource.profileId || !resource.appearance || !resource.emitter ||
+ resource.referenceOnly || resource.rawMediaIncluded)) {
+ issues.push(makeIssue(
+ 'error',
+ 'COSMIQ_PARTICLE_EFFECT_INCOMPLETE',
+ 'Authored particles need a profile, embedded-texture appearance, bounded emitter, and runtime export.'
+ ));
+ } else if (resource.type === RESOURCE_TYPES.PARTICLE_EFFECT &&
+ (resource.editorStatus === 'placeholder' || !resource.runtimeExport)) {
+ issues.push(makeIssue(
+ 'warning',
+ 'COSMIQ_PARTICLE_SETUP_PENDING',
+ 'Particle ' + resource.displayName +
+ ' is a timing placeholder. Finish its image, UV face, color, and behavior in the website Particle Editor.'
+ ));
+ }
+ if (resource.type === RESOURCE_TYPES.SOUND &&
+ SOUND_REFERENCE_TYPES.indexOf(sourceResource.soundKind || 'sound_effect') === -1) {
+ issues.push(makeIssue(
+ 'error',
+ 'COSMIQ_SOUND_REFERENCE_TYPE',
+ 'Sound resources must be sound_effect or music references.'
+ ));
+ }
+ if (resourceContainsRawMedia(sourceResource)) {
+ issues.push(makeIssue(
+ 'error',
+ 'COSMIQ_RESOURCE_RAW_MEDIA',
+ 'Raw paths, URLs, audio, GIFs, media bytes, and payloads are not allowed in resource references.'
+ ));
+ }
+ resourceReferences.push(resource);
+ });
+
+ const locatorBindings = {};
+ const locatorIds = Object.create(null);
+ const locatorNodes = nodes.filter(function (node) {
+ return node && (
+ node.role === ROLES.PARTICLE_EFFECT_LOCATOR ||
+ node.role === ROLES.SOUND_EFFECT_LOCATOR
+ );
+ });
+ if (locatorNodes.length > AUTHORING_LIMITS.effectLocators) {
+ issues.push(makeIssue(
+ 'error',
+ 'COSMIQ_EFFECT_LOCATOR_LIMIT',
+ 'Project has ' + locatorNodes.length + ' effect locators; limit is ' +
+ AUTHORING_LIMITS.effectLocators + '.'
+ ));
+ }
+ locatorNodes.forEach(function (node) {
+ const locatorId = normalizeAssetRef(node.effectLocatorId);
+ const resourceId = normalizeAssetRef(node.resourceId);
+ if (node.type !== 'locator') {
+ issues.push(makeIssue(
+ 'error',
+ 'COSMIQ_EFFECT_LOCATOR_TYPE',
+ 'Particle and sound attachment helpers must be Blockbench Locator objects.',
+ node.uuid
+ ));
+ }
+ if (!locatorId) {
+ issues.push(makeIssue(
+ 'error',
+ 'COSMIQ_EFFECT_LOCATOR_ID',
+ 'Effect locator ' + (node.name || '(unnamed)') + ' needs a namespaced locator ID.',
+ node.uuid
+ ));
+ } else if (locatorIds[locatorId]) {
+ issues.push(makeIssue(
+ 'error',
+ 'COSMIQ_EFFECT_LOCATOR_ID_DUPLICATE',
+ 'Effect locator ID ' + locatorId + ' is duplicated.',
+ node.uuid
+ ));
+ }
+ if (locatorId) locatorIds[locatorId] = true;
+ if (!resourceId || !resourcesById[resourceId]) {
+ issues.push(makeIssue(
+ 'error',
+ 'COSMIQ_EFFECT_LOCATOR_RESOURCE',
+ 'Effect locator ' + (node.name || '(unnamed)') + ' references a missing resource.',
+ node.uuid
+ ));
+ } else {
+ const matchesType = node.role === ROLES.PARTICLE_EFFECT_LOCATOR
+ ? isParticleResource(resourcesById[resourceId])
+ : resourcesById[resourceId].type === RESOURCE_TYPES.SOUND;
+ if (!matchesType) {
+ issues.push(makeIssue(
+ 'error',
+ 'COSMIQ_EFFECT_LOCATOR_RESOURCE_TYPE',
+ 'Effect locator resource type does not match its particle or sound role.',
+ node.uuid
+ ));
+ }
+ }
+ if (node.export !== false) {
+ issues.push(makeIssue(
+ 'error',
+ 'COSMIQ_EFFECT_LOCATOR_EXPORT',
+ 'Effect locators must remain non-exporting authoring helpers.',
+ node.uuid
+ ));
+ }
+ if (node.uuid) {
+ locatorBindings[node.uuid] = {
+ id: locatorId,
+ sourceNodeUuid: node.uuid,
+ sourceName: boundedCreatorString(node.name, AUTHORING_LIMITS.displayNameCharacters),
+ role: node.role,
+ resourceId: resourceId || null,
+ exportGeometry: false
+ };
+ }
+ });
+
+ const animationByUuid = Object.create(null);
+ const markerIndexes = Object.create(null);
+ const timelineMarkers = {};
+ animations.forEach(function (animation) {
+ if (!animation || !animation.uuid) {
+ return;
+ }
+ animationByUuid[animation.uuid] = animation;
+ const duration = Number(animation.lengthSeconds);
+ const markers = collectTimelineMarkers(animation.markers);
+ timelineMarkers[animation.uuid] = markers;
+ (Array.isArray(animation.markers) ? animation.markers : []).forEach(function (rawMarker) {
+ const rawName = rawMarker && rawMarker.name !== 0
+ ? String(rawMarker.name == null ? '' : rawMarker.name)
+ : '';
+ if (rawName.length > AUTHORING_LIMITS.markerNameCharacters) {
+ issues.push(makeIssue(
+ 'error',
+ 'COSMIQ_TIMELINE_MARKER_NAME_LENGTH',
+ 'Timeline marker names may not exceed ' +
+ AUTHORING_LIMITS.markerNameCharacters + ' characters.',
+ animation.uuid
+ ));
+ }
+ });
+ if (!Number.isFinite(duration) || duration < 0 || duration > LIMITS.animationSeconds) {
+ issues.push(makeIssue(
+ 'error',
+ 'COSMIQ_CUE_ANIMATION_DURATION',
+ 'Animation ' + (animation.name || animation.uuid) + ' has an invalid cue duration.',
+ animation.uuid
+ ));
+ }
+ if (markers.length > AUTHORING_LIMITS.markersPerAnimation) {
+ issues.push(makeIssue(
+ 'error',
+ 'COSMIQ_TIMELINE_MARKER_LIMIT',
+ 'Animation ' + (animation.name || animation.uuid) + ' has ' + markers.length +
+ ' markers; limit is ' + AUTHORING_LIMITS.markersPerAnimation + '.',
+ animation.uuid
+ ));
+ }
+ const markerIndex = Object.create(null);
+ const markerTimes = [];
+ markers.forEach(function (marker) {
+ if (!marker.name || containsForbiddenControl(marker.name, false)) {
+ issues.push(makeIssue(
+ 'error',
+ 'COSMIQ_TIMELINE_MARKER_NAME',
+ 'Timeline markers used by Cosmiq must have readable names.',
+ animation.uuid
+ ));
+ }
+ if (marker.name && markerIndex[marker.name]) {
+ markerIndex[marker.name].duplicate = true;
+ issues.push(makeIssue(
+ 'error',
+ 'COSMIQ_TIMELINE_MARKER_DUPLICATE',
+ 'Animation ' + (animation.name || animation.uuid) +
+ ' has more than one marker named ' + marker.name + '.',
+ animation.uuid
+ ));
+ } else if (marker.name) {
+ markerIndex[marker.name] = marker;
+ }
+ if (marker.timeSeconds == null || marker.timeSeconds < 0 ||
+ (Number.isFinite(duration) && marker.timeSeconds > duration)) {
+ issues.push(makeIssue(
+ 'error',
+ 'COSMIQ_TIMELINE_MARKER_TIME',
+ 'Timeline marker ' + (marker.name || '(unnamed)') +
+ ' must be within its animation duration.',
+ animation.uuid
+ ));
+ } else if (markerTimes.some(function (time) {
+ return Math.abs(time - marker.timeSeconds) < 0.001;
+ })) {
+ issues.push(makeIssue(
+ 'error',
+ 'COSMIQ_TIMELINE_MARKER_TIME_DUPLICATE',
+ 'Animation ' + (animation.name || animation.uuid) +
+ ' has multiple markers at the same time; Blockbench persists one marker per timestamp.',
+ animation.uuid
+ ));
+ } else {
+ markerTimes.push(marker.timeSeconds);
+ }
+ });
+ markerIndexes[animation.uuid] = markerIndex;
+ });
+
+ const timelineCues = [];
+ const cueIds = Object.create(null);
+ if (rawCues.length > AUTHORING_LIMITS.cues) {
+ issues.push(makeIssue(
+ 'error',
+ 'COSMIQ_TIMELINE_CUE_LIMIT',
+ 'Project has ' + rawCues.length + ' timeline cues; limit is ' + AUTHORING_LIMITS.cues + '.'
+ ));
+ }
+ rawCues.forEach(function (rawCue) {
+ const sourceCue = rawCue && typeof rawCue === 'object' ? rawCue : {};
+ const cue = normalizeTimelineCue(sourceCue);
+ if (!cue.id) {
+ issues.push(makeIssue(
+ 'error',
+ 'COSMIQ_TIMELINE_CUE_ID',
+ 'Every timeline cue needs a lowercase namespaced ID.'
+ ));
+ } else if (cueIds[cue.id]) {
+ issues.push(makeIssue(
+ 'error',
+ 'COSMIQ_TIMELINE_CUE_ID_DUPLICATE',
+ 'Timeline cue ID ' + cue.id + ' is duplicated.'
+ ));
+ }
+ if (cue.id) cueIds[cue.id] = true;
+ if ([
+ CUE_TYPES.PARTICLE,
+ CUE_TYPES.SOUND,
+ CUE_TYPES.NODE_VISIBILITY,
+ CUE_TYPES.COMMENT
+ ].indexOf(cue.type) === -1) {
+ issues.push(makeIssue(
+ 'error',
+ 'COSMIQ_TIMELINE_CUE_TYPE',
+ 'Timeline cues must be particle, sound, node_visibility, or comment.'
+ ));
+ }
+ const animation = animationByUuid[cue.animationUuid];
+ const markerIndex = markerIndexes[cue.animationUuid];
+ const marker = markerIndex && markerIndex[cue.markerName];
+ if (!animation) {
+ issues.push(makeIssue(
+ 'error',
+ 'COSMIQ_TIMELINE_CUE_ANIMATION',
+ 'Cue ' + (cue.id || '(unnamed)') + ' references a missing animation.'
+ ));
+ }
+ if (!cue.markerName || !marker || marker.duplicate) {
+ issues.push(makeIssue(
+ 'error',
+ 'COSMIQ_TIMELINE_CUE_MARKER',
+ 'Cue ' + (cue.id || '(unnamed)') + ' references a missing or ambiguous marker.',
+ cue.animationUuid
+ ));
+ }
+ let endMarker = null;
+ if (cue.endMarkerName) {
+ endMarker = markerIndex && markerIndex[cue.endMarkerName];
+ if (!endMarker || endMarker.duplicate) {
+ issues.push(makeIssue(
+ 'error',
+ 'COSMIQ_TIMELINE_CUE_END_MARKER',
+ 'Cue ' + (cue.id || '(unnamed)') + ' references a missing or ambiguous end marker.',
+ cue.animationUuid
+ ));
+ } else if (marker && endMarker.timeSeconds <= marker.timeSeconds) {
+ issues.push(makeIssue(
+ 'error',
+ 'COSMIQ_TIMELINE_CUE_DURATION',
+ 'Cue end markers must occur after their start markers.',
+ cue.animationUuid
+ ));
+ }
+ }
+ if (cue.type === CUE_TYPES.COMMENT) {
+ const rawComment = String(sourceCue.comment == null ? '' : sourceCue.comment);
+ if (!cue.comment || rawComment.length > AUTHORING_LIMITS.commentCharacters ||
+ containsForbiddenControl(rawComment, true)) {
+ issues.push(makeIssue(
+ 'error',
+ 'COSMIQ_TIMELINE_COMMENT',
+ 'Creator comments must contain readable text no longer than ' +
+ AUTHORING_LIMITS.commentCharacters + ' characters.',
+ cue.animationUuid
+ ));
+ }
+ } else if (cue.type === CUE_TYPES.NODE_VISIBILITY) {
+ const targetNode = cue.nodeUuid && nodeByUuid[cue.nodeUuid];
+ if (project.kind !== PROJECT_KINDS.EMOTE || !targetNode ||
+ [ROLES.OBJECT_ACTOR, ROLES.OBJECT_ACTOR_NODE].indexOf(targetNode.role) === -1 ||
+ typeof sourceCue.visible !== 'boolean') {
+ issues.push(makeIssue(
+ 'error',
+ 'COSMIQ_TIMELINE_CUE_NODE',
+ 'Node-visibility cues require an Emote Prop node and an explicit visible boolean.',
+ cue.animationUuid
+ ));
+ }
+ } else {
+ const resource = cue.resourceId && resourcesById[cue.resourceId];
+ if (!resource) {
+ issues.push(makeIssue(
+ 'error',
+ 'COSMIQ_TIMELINE_CUE_RESOURCE',
+ 'Cue ' + (cue.id || '(unnamed)') + ' references a missing resource.',
+ cue.animationUuid
+ ));
+ } else {
+ const matchingType = cue.type === CUE_TYPES.PARTICLE
+ ? isParticleResource(resource)
+ : resource.type === RESOURCE_TYPES.SOUND;
+ if (!matchingType) {
+ issues.push(makeIssue(
+ 'error',
+ 'COSMIQ_TIMELINE_CUE_RESOURCE_TYPE',
+ 'Cue resource type does not match its particle or sound cue type.',
+ cue.animationUuid
+ ));
+ }
+ }
+ const locator = cue.locatorUuid && locatorBindings[cue.locatorUuid];
+ if (!locator && cue.type === CUE_TYPES.PARTICLE) {
+ issues.push(makeIssue(
+ 'error',
+ 'COSMIQ_TIMELINE_CUE_LOCATOR',
+ 'Particle cues require an existing effect locator.',
+ cue.animationUuid
+ ));
+ } else if (locator) {
+ const expectedLocatorRole = cue.type === CUE_TYPES.PARTICLE
+ ? ROLES.PARTICLE_EFFECT_LOCATOR
+ : ROLES.SOUND_EFFECT_LOCATOR;
+ if (locator.role !== expectedLocatorRole || locator.resourceId !== cue.resourceId) {
+ issues.push(makeIssue(
+ 'error',
+ 'COSMIQ_TIMELINE_CUE_LOCATOR_TYPE',
+ 'Cue locator role or resource binding does not match the cue.',
+ cue.animationUuid
+ ));
+ }
+ }
+ }
+ timelineCues.push(Object.assign({}, cue, {
+ timeSeconds: marker && !marker.duplicate ? marker.timeSeconds : null,
+ atMs: marker && !marker.duplicate ? marker.atMs : null,
+ endTimeSeconds: endMarker && !endMarker.duplicate ? endMarker.timeSeconds : null,
+ endAtMs: endMarker && !endMarker.duplicate ? endMarker.atMs : null
+ }));
+ });
+
+ return {
+ metadata: {
+ authoringSchema: 'cosmiq.blockbench.cues.v0',
+ referenceOnly: false,
+ rawMediaIncluded: resourceReferences.some(function (resource) {
+ return resource.type === RESOURCE_TYPES.SOUND && !!resource.embeddedOgg;
+ }),
+ resourceReferences: resourceReferences,
+ effectLocatorBindings: locatorBindings,
+ timelineMarkers: timelineMarkers,
+ timelineCues: timelineCues,
+ unsupportedMedia: ['non_ogg_audio', 'gif', 'remote_url', 'filesystem_path']
+ },
+ issues: issues
+ };
+ }
+
+ function isKnownAnchor(anchorId) {
+ return Object.prototype.hasOwnProperty.call(anchorById, canonicalAnchorId(anchorId));
+ }
+
+ function sameAnchorOrigin(left, right) {
+ return Array.isArray(left) && Array.isArray(right) && [0, 1, 2].every(function (axis) {
+ return Math.abs(Number(left[axis]) - Number(right[axis])) <= 0.0001;
+ });
+ }
+
+ function anchorIdForAuthoredOrigin(anchorId, origin) {
+ const canonicalId = canonicalAnchorId(anchorId);
+ if (!Array.isArray(origin) || !isKnownAnchor(canonicalId)) return canonicalId;
+ const x = Number(origin[0]);
+ if (!Number.isFinite(x) || Math.abs(x) <= 0.0001) return canonicalId;
+ if (canonicalId === 'cosmiq:player/left_arm' || canonicalId === 'cosmiq:player/right_arm') {
+ return x > 0 ? 'cosmiq:player/left_arm' : 'cosmiq:player/right_arm';
+ }
+ if (canonicalId === 'cosmiq:player/left_leg' || canonicalId === 'cosmiq:player/right_leg') {
+ return x > 0 ? 'cosmiq:player/left_leg' : 'cosmiq:player/right_leg';
+ }
+ return canonicalId;
+ }
+
+ function legacyAnchorNormalizationPlan(nodes) {
+ const changes = [];
+ (nodes || []).forEach(function (node) {
+ if (effectiveRole(node) !== ROLES.ANCHOR && !node.anchorId) return;
+ const previousAnchorId = canonicalAnchorId(node.anchorId);
+ if (!isKnownAnchor(previousAnchorId)) return;
+ const anchorId = anchorIdForAuthoredOrigin(previousAnchorId, node.origin);
+ const origin = anchorById[anchorId].origin.slice();
+ if (anchorId === previousAnchorId && sameAnchorOrigin(node.origin, origin)) return;
+ changes.push({
+ uuid: node.uuid,
+ name: node.name,
+ previousAnchorId: previousAnchorId,
+ anchorId: anchorId,
+ previousOrigin: Array.isArray(node.origin) ? node.origin.slice(0, 3) : null,
+ origin: origin
+ });
+ });
+ return changes;
+ }
+
+ function normalizePackagedNodeVisibility(project, nodeBindings) {
+ if (!project || typeof project !== 'object') return [];
+ const bindings = cosmiqPlainObject(nodeBindings) ? nodeBindings : {};
+ const packaged = Object.create(null);
+ Object.keys(bindings).forEach(function (uuid) {
+ const binding = bindings[uuid];
+ if (binding && (binding.export === true || binding.role === 'cosmetic_node')) {
+ packaged[uuid] = true;
+ }
+ });
+ const visible = Object.create(null);
+ function walk(entries) {
+ let containsPackagedNode = false;
+ (Array.isArray(entries) ? entries : []).forEach(function (entry) {
+ if (typeof entry === 'string') {
+ if (packaged[entry]) {
+ visible[entry] = true;
+ containsPackagedNode = true;
+ }
+ return;
+ }
+ if (!entry || typeof entry !== 'object') return;
+ const childContainsPackagedNode = walk(entry.children);
+ if (packaged[entry.uuid] || childContainsPackagedNode) {
+ if (entry.uuid) visible[entry.uuid] = true;
+ containsPackagedNode = true;
+ }
+ });
+ return containsPackagedNode;
+ }
+ walk(project.outliner);
+ Object.keys(packaged).forEach(function (uuid) {
+ visible[uuid] = true;
+ });
+ const byUuid = Object.create(null);
+ (Array.isArray(project.groups) ? project.groups : [])
+ .concat(Array.isArray(project.elements) ? project.elements : [])
+ .forEach(function (node) {
+ if (node && node.uuid) byUuid[node.uuid] = node;
+ });
+ const normalized = [];
+ function normalize(entries, parentWasVisible) {
+ (Array.isArray(entries) ? entries : []).forEach(function (entry) {
+ if (typeof entry === 'string') {
+ const element = byUuid[entry];
+ if (!element) return;
+ if (visible[entry]) {
+ element.visibility = true;
+ } else if (!parentWasVisible) {
+ element.visibility = false;
+ }
+ return;
+ }
+ if (!entry || typeof entry !== 'object') return;
+ const group = byUuid[entry.uuid];
+ if (!group) return;
+ const groupWasVisible = parentWasVisible && group.visibility !== false;
+ if (visible[entry.uuid]) {
+ group.visibility = true;
+ } else if (!parentWasVisible) {
+ group.visibility = false;
+ }
+ normalize(entry.children, groupWasVisible);
+ });
+ }
+ normalize(project.outliner, true);
+ Object.keys(visible).sort().forEach(function (uuid) {
+ if (!byUuid[uuid]) return;
+ byUuid[uuid].visibility = true;
+ normalized.push(uuid);
+ });
+ return normalized;
+ }
+
+ function effectiveRole(node) {
+ if (node.role && node.role !== ROLES.NONE) {
+ return node.role;
+ }
+ if ((node.type === 'cube' || node.type === 'locator') && parseParticleMarkerName(node.name)) {
+ return ROLES.PARTICLE_MARKER;
+ }
+ return ROLES.NONE;
+ }
+
+ function indexNodes(nodes, issues) {
+ const byUuid = Object.create(null);
+ nodes.forEach(function (node) {
+ if (!node.uuid) {
+ issues.push(makeIssue('error', 'COSMIQ_NODE_UUID_MISSING', 'An outliner node has no UUID.'));
+ return;
+ }
+ if (byUuid[node.uuid]) {
+ issues.push(makeIssue(
+ 'error',
+ 'COSMIQ_NODE_UUID_DUPLICATE',
+ 'Duplicate outliner UUID: ' + node.uuid,
+ node.uuid
+ ));
+ return;
+ }
+ byUuid[node.uuid] = node;
+ });
+ return byUuid;
+ }
+
+ function ancestorWalk(node, byUuid, visit) {
+ const seen = Object.create(null);
+ let current = node;
+ while (current && current.parentUuid) {
+ if (seen[current.parentUuid]) {
+ return {cycle: true};
+ }
+ seen[current.parentUuid] = true;
+ current = byUuid[current.parentUuid] || null;
+ if (!current) {
+ return {missingParent: true};
+ }
+ const result = visit(current);
+ if (result) {
+ return result;
+ }
+ }
+ return {};
+ }
+
+ function nearestAnchor(node, byUuid) {
+ return ancestorWalk(node, byUuid, function (ancestor) {
+ if (effectiveRole(ancestor) === ROLES.ANCHOR || ancestor.anchorId) {
+ return {
+ anchorId: ancestor.anchorId || '',
+ anchorUuid: ancestor.uuid
+ };
+ }
+ return null;
+ });
+ }
+
+ function nearestModelVariant(node, byUuid) {
+ const result = ancestorWalk(node, byUuid, function (ancestor) {
+ if (ancestor.modelVariant === 'wide' || ancestor.modelVariant === 'slim') {
+ return {modelVariant: ancestor.modelVariant};
+ }
+ return null;
+ });
+ return result.modelVariant || 'universal';
+ }
+
+ function normalizeArmWidthMode(value) {
+ return value === ARM_WIDTH_MODES.SHARED || value === ARM_WIDTH_MODES.DEPENDENT
+ ? value
+ : ARM_WIDTH_MODES.UNCONFIGURED;
+ }
+
+ function inspectArmWidthGeometry(snapshot, indexedNodes) {
+ const project = snapshot && snapshot.project || {};
+ const nodes = snapshot && Array.isArray(snapshot.nodes) ? snapshot.nodes : [];
+ const byUuid = indexedNodes || indexNodes(nodes, []);
+ const authoredVariants = {universal: false, wide: false, slim: false};
+ const anchorIds = [];
+ nodes.forEach(function (node) {
+ if (!isRuntimeNodeCandidate(node, byUuid) ||
+ (node.type !== 'cube' && node.type !== 'mesh') ||
+ Number(node.triangleCount) <= 0) {
+ return;
+ }
+ const anchor = nearestAnchor(node, byUuid);
+ const anchorId = canonicalAnchorId(anchor.anchorId);
+ const anchorRecord = anchorById[anchorId];
+ if (!anchorRecord || anchorRecord.slot !== 'cosmiq:slot/arms') {
+ return;
+ }
+ authoredVariants[nearestModelVariant(node, byUuid)] = true;
+ if (anchorIds.indexOf(anchorId) === -1) anchorIds.push(anchorId);
+ });
+ const configuredMode = normalizeArmWidthMode(project.armWidthMode);
+ const hasArmGeometry = authoredVariants.universal ||
+ authoredVariants.wide || authoredVariants.slim;
+ const usesModelBranch = authoredVariants.wide || authoredVariants.slim;
+ return {
+ configuredMode: configuredMode,
+ effectiveMode: configuredMode === ARM_WIDTH_MODES.UNCONFIGURED &&
+ hasArmGeometry && !usesModelBranch
+ ? ARM_WIDTH_MODES.SHARED
+ : configuredMode,
+ hasArmGeometry: hasArmGeometry,
+ usesModelBranch: usesModelBranch,
+ requiresChoice: hasArmGeometry && usesModelBranch &&
+ configuredMode === ARM_WIDTH_MODES.UNCONFIGURED,
+ authoredVariants: authoredVariants,
+ anchorIds: anchorIds.sort()
+ };
+ }
+
+ function modelVariantForArmWidthMode(node, byUuid, armWidthInspection) {
+ const authoredVariant = nearestModelVariant(node, byUuid);
+ if (!armWidthInspection ||
+ armWidthInspection.effectiveMode !== ARM_WIDTH_MODES.SHARED) {
+ return authoredVariant;
+ }
+ const anchor = nearestAnchor(node, byUuid);
+ const anchorRecord = anchorById[canonicalAnchorId(anchor.anchorId)];
+ return anchorRecord && anchorRecord.slot === 'cosmiq:slot/arms'
+ ? 'universal'
+ : authoredVariant;
+ }
+
+ function isInsideExportRoot(node, byUuid) {
+ const result = ancestorWalk(node, byUuid, function (ancestor) {
+ if (effectiveRole(ancestor) === ROLES.EXPORT_ROOT) {
+ return {inside: true};
+ }
+ if (effectiveRole(ancestor) === ROLES.REFERENCE_ROOT) {
+ return {inside: false, excluded: true};
+ }
+ return null;
+ });
+ return result.inside === true;
+ }
+
+ function hasExcludedAncestor(node, byUuid) {
+ const result = ancestorWalk(node, byUuid, function (ancestor) {
+ const role = effectiveRole(ancestor);
+ if (
+ role === ROLES.REFERENCE_ROOT ||
+ role === ROLES.REFERENCE ||
+ role === ROLES.PARTICLE_MARKER
+ ) {
+ return {excluded: true};
+ }
+ return null;
+ });
+ return result.excluded === true;
+ }
+
+ function isRuntimeNodeCandidate(node, byUuid) {
+ const role = effectiveRole(node);
+ if (node.export === false) {
+ return false;
+ }
+ if (node.type !== 'group' && node.type !== 'cube' && node.type !== 'mesh') {
+ return false;
+ }
+ if (
+ role === ROLES.EXPORT_ROOT ||
+ role === ROLES.MODEL_VARIANT ||
+ role === ROLES.STRUCTURE ||
+ role === ROLES.ANCHOR ||
+ role === ROLES.REFERENCE_ROOT ||
+ role === ROLES.REFERENCE ||
+ role === ROLES.PARTICLE_MARKER
+ ) {
+ return false;
+ }
+ return isInsideExportRoot(node, byUuid) && !hasExcludedAncestor(node, byUuid);
+ }
+
+ function runtimeParent(node, byUuid, runtimeNodes) {
+ let current = node;
+ const seen = Object.create(null);
+ while (current && current.parentUuid) {
+ if (seen[current.parentUuid]) {
+ return {cycle: true};
+ }
+ seen[current.parentUuid] = true;
+ current = byUuid[current.parentUuid] || null;
+ if (!current) {
+ return {missingParent: true};
+ }
+ if (runtimeNodes[current.uuid]) {
+ return {parentUuid: current.uuid};
+ }
+ if (effectiveRole(current) === ROLES.ANCHOR || current.anchorId) {
+ return {atAnchor: true};
+ }
+ }
+ return {};
+ }
+
+ function buildSourceMetadata(snapshot) {
+ const issues = [];
+ const authoredAnimations = animationsWithKeyframes(snapshot.animations);
+ const projectId = normalizeProjectId(snapshot.project.id);
+ const safeProjectId = projectId || 'creator:untitled';
+ if (!projectId) {
+ issues.push(makeIssue(
+ 'error',
+ 'COSMIQ_PROJECT_ID_INVALID',
+ 'Project ID must be a lowercase namespaced identifier such as creator:wing_demo.'
+ ));
+ }
+
+ const byUuid = indexNodes(snapshot.nodes, issues);
+ const armWidthInspection = inspectArmWidthGeometry(snapshot, byUuid);
+ const exportRoots = snapshot.nodes.filter(function (node) {
+ return effectiveRole(node) === ROLES.EXPORT_ROOT;
+ });
+ if (exportRoots.length !== 1) {
+ issues.push(makeIssue(
+ 'error',
+ 'COSMIQ_EXPORT_ROOT_COUNT',
+ 'Expected exactly one Cosmiq Export root, found ' + exportRoots.length + '.'
+ ));
+ }
+
+ const anchorBindings = {};
+ const seenAnchorIds = Object.create(null);
+ snapshot.nodes.forEach(function (node) {
+ if (effectiveRole(node) !== ROLES.ANCHOR && !node.anchorId) {
+ return;
+ }
+ const authoredAnchorId = node.anchorId || '';
+ const anchorId = canonicalAnchorId(authoredAnchorId);
+ if (!isKnownAnchor(anchorId)) {
+ issues.push(makeIssue(
+ 'error',
+ 'COSMIQ_ANCHOR_UNKNOWN',
+ 'Unknown player anchor on ' + node.name + ': ' + authoredAnchorId,
+ node.uuid
+ ));
+ } else if (Array.isArray(node.origin) && !sameAnchorOrigin(
+ node.origin,
+ anchorById[anchorId].origin
+ )) {
+ issues.push(makeIssue(
+ 'error',
+ 'COSMIQ_ANCHOR_ORIGIN_MISMATCH',
+ 'Player anchor ' + node.name + ' uses pivot ' + node.origin.slice(0, 3).join(', ') +
+ '; V3 ' + anchorId + ' expected ' + anchorById[anchorId].origin.join(', ') +
+ '. The plugin will normalize this legacy pivot automatically before export.',
+ node.uuid
+ ));
+ }
+ const modelVariant = nearestModelVariant(node, byUuid);
+ const anchorKey = modelVariant + ':' + anchorId;
+ if (seenAnchorIds[anchorKey]) {
+ issues.push(makeIssue(
+ 'warning',
+ 'COSMIQ_ANCHOR_DUPLICATE',
+ 'Multiple ' + modelVariant + ' authoring parents use ' + anchorId +
+ '. This is valid but usually unnecessary.',
+ node.uuid
+ ));
+ }
+ seenAnchorIds[anchorKey] = true;
+ anchorBindings[node.uuid] = {
+ anchorId: anchorId,
+ modelVariant: modelVariant,
+ label: isKnownAnchor(anchorId) ? anchorById[anchorId].label : node.name,
+ export: false
+ };
+ });
+ if (Object.keys(anchorBindings).length === 0) {
+ issues.push(makeIssue(
+ 'error',
+ 'COSMIQ_ANCHOR_MISSING',
+ 'No Cosmiq player anchor parent exists in the project.'
+ ));
+ }
+
+ const runtimeCandidates = Object.create(null);
+ snapshot.nodes.forEach(function (node) {
+ if (isRuntimeNodeCandidate(node, byUuid)) {
+ runtimeCandidates[node.uuid] = node;
+ }
+ });
+ const runtimeNodes = Object.create(null);
+ function retainRuntimeChain(startNode) {
+ const seen = Object.create(null);
+ let current = startNode;
+ while (current && current.uuid && !seen[current.uuid]) {
+ seen[current.uuid] = true;
+ if (runtimeCandidates[current.uuid]) {
+ runtimeNodes[current.uuid] = current;
+ }
+ if (!current.parentUuid) break;
+ current = byUuid[current.parentUuid] || null;
+ }
+ }
+ Object.keys(runtimeCandidates).forEach(function (uuid) {
+ const node = runtimeCandidates[uuid];
+ if ((node.type === 'cube' || node.type === 'mesh') && Number(node.triangleCount) > 0) {
+ retainRuntimeChain(node);
+ }
+ });
+ authoredAnimations.forEach(function (animation) {
+ listValues(animation.targetUuids).forEach(function (uuid) {
+ if (runtimeCandidates[uuid]) retainRuntimeChain(runtimeCandidates[uuid]);
+ });
+ });
+ snapshot.nodes.forEach(function (node) {
+ if (effectiveRole(node) !== ROLES.PARTICLE_EFFECT_LOCATOR &&
+ effectiveRole(node) !== ROLES.SOUND_EFFECT_LOCATOR) {
+ return;
+ }
+ if (node.parentUuid && byUuid[node.parentUuid]) {
+ retainRuntimeChain(byUuid[node.parentUuid]);
+ }
+ });
+
+ const nodeBindings = {};
+ Object.keys(runtimeNodes).forEach(function (uuid) {
+ const node = runtimeNodes[uuid];
+ const parent = runtimeParent(node, byUuid, runtimeNodes);
+ const anchor = nearestAnchor(node, byUuid);
+ if (parent.cycle || anchor.cycle) {
+ issues.push(makeIssue(
+ 'error',
+ 'COSMIQ_HIERARCHY_CYCLE',
+ 'Hierarchy cycle detected for ' + node.name + '.',
+ node.uuid
+ ));
+ return;
+ }
+ if (parent.missingParent || anchor.missingParent) {
+ issues.push(makeIssue(
+ 'error',
+ 'COSMIQ_PARENT_MISSING',
+ 'A parent referenced by ' + node.name + ' is missing.',
+ node.uuid
+ ));
+ return;
+ }
+ const parentNodeId = parent.parentUuid
+ ? runtimeId(safeProjectId, 'node', parent.parentUuid)
+ : null;
+ const anchorId = parentNodeId ? null : (canonicalAnchorId(anchor.anchorId) || null);
+ if (!parentNodeId && !isKnownAnchor(anchorId)) {
+ issues.push(makeIssue(
+ 'error',
+ 'COSMIQ_NODE_WITHOUT_ANCHOR',
+ node.name + ' is inside Cosmiq Export but not beneath a known player anchor parent.',
+ node.uuid
+ ));
+ }
+ nodeBindings[node.uuid] = {
+ id: runtimeId(safeProjectId, 'node', node.uuid),
+ sourceType: node.type,
+ sourceName: node.name,
+ role: 'cosmetic_node',
+ export: true,
+ parentNodeId: parentNodeId,
+ anchorId: anchorId,
+ anchorGroupUuid: anchor.anchorUuid || null,
+ modelVariant: modelVariantForArmWidthMode(node, byUuid, armWidthInspection),
+ hasGeometry: (node.type === 'cube' || node.type === 'mesh') && node.triangleCount > 0
+ };
+ });
+
+ if (armWidthInspection.requiresChoice) {
+ issues.push(makeIssue(
+ 'error',
+ 'COSMIQ_ARM_WIDTH_MODE_REQUIRED',
+ 'Arm-attached geometry was detected. Choose whether this accessory uses the same model ' +
+ 'for Wide and Slim arms or requires separate models.'
+ ));
+ }
+ if (armWidthInspection.effectiveMode === ARM_WIDTH_MODES.DEPENDENT &&
+ (!armWidthInspection.authoredVariants.wide ||
+ !armWidthInspection.authoredVariants.slim)) {
+ issues.push(makeIssue(
+ 'error',
+ 'COSMIQ_MODEL_VARIANT_PAIR_REQUIRED',
+ 'This accessory is marked arm-width dependent, so matching Wide and Slim arm model ' +
+ 'branches are both required before export.'
+ ));
+ }
+ if (armWidthInspection.effectiveMode === ARM_WIDTH_MODES.SHARED &&
+ armWidthInspection.authoredVariants.wide &&
+ armWidthInspection.authoredVariants.slim) {
+ issues.push(makeIssue(
+ 'error',
+ 'COSMIQ_SHARED_ARM_MODEL_DUPLICATED',
+ 'This accessory is marked to share one arm model, but geometry exists in both Wide and ' +
+ 'Slim branches. Keep one authored model or mark the accessory arm-width dependent.'
+ ));
+ }
+
+ if (Object.keys(nodeBindings).length > LIMITS.nodes) {
+ issues.push(makeIssue(
+ 'error',
+ 'COSMIQ_NODE_LIMIT',
+ 'Cosmetic has ' + Object.keys(nodeBindings).length + ' runtime nodes; limit is ' + LIMITS.nodes + '.'
+ ));
+ }
+ const derivedBodySlots = bodySlotsFromNodeBindings(nodeBindings);
+ const derivedModelVariant = armWidthInspection.hasArmGeometry &&
+ armWidthInspection.effectiveMode === ARM_WIDTH_MODES.SHARED
+ ? 'universal'
+ : modelVariantFromNodeBindings(nodeBindings, snapshot.project.modelVariant);
+ const detectedAttachmentAnchorIds = uniqueValues(Object.keys(nodeBindings).map(function (uuid) {
+ return canonicalAnchorId(nodeBindings[uuid].anchorId);
+ }).filter(isKnownAnchor)).sort();
+ const detectedAttachmentBodySlots = bodySlotsForAnchors(
+ detectedAttachmentAnchorIds
+ );
+ if (detectedAttachmentBodySlots.length > 1) {
+ issues.push(makeIssue(
+ 'error',
+ 'COSMIQ_ACCESSORY_ATTACHMENT_AMBIGUOUS',
+ 'Accessory geometry was detected across more than one body group: ' +
+ detectedAttachmentAnchorIds.map(function (anchorId) {
+ return anchorById[anchorId].label;
+ }).join(', ') + '. Keep the accessory within one wardrobe body group.'
+ ));
+ }
+ const textureVariants = textureVariantsFromTextures(snapshot.textures.filter(function (texture) {
+ return texture.role !== ROLES.REFERENCE;
+ }), snapshot.nodes);
+ const authoringAnimationMode = authoredAnimations.length > 0
+ ? ANIMATION_MODES.ANIMATED
+ : ANIMATION_MODES.STATIC;
+ const authoringBodySlots = derivedBodySlots;
+ if (authoringBodySlots.length === 0) {
+ issues.push(makeIssue(
+ 'error',
+ 'COSMIQ_ACCESSORY_BODY_GROUP_MISSING',
+ 'Accessory geometry must resolve to exactly one player body group.'
+ ));
+ }
+ authoringBodySlots.forEach(function (slot) {
+ if (BODY_SLOTS.indexOf(slot) === -1) {
+ issues.push(makeIssue(
+ 'error',
+ 'COSMIQ_ACCESSORY_BODY_GROUP_UNKNOWN',
+ 'Unsupported player body group: ' + slot + '.'
+ ));
+ }
+ });
+
+ let vertexCount = 0;
+ let triangleCount = 0;
+ Object.keys(nodeBindings).forEach(function (uuid) {
+ const node = byUuid[uuid];
+ vertexCount += Number(node.vertexCount) || 0;
+ triangleCount += Number(node.triangleCount) || 0;
+ });
+ if (triangleCount === 0) {
+ issues.push(makeIssue(
+ 'error',
+ 'COSMIQ_COSMETIC_EMPTY',
+ 'The cosmetic has no renderable cube or mesh faces. Add model geometry beneath a selected anchor.'
+ ));
+ }
+ if (vertexCount > LIMITS.vertices) {
+ issues.push(makeIssue(
+ 'error',
+ 'COSMIQ_VERTEX_LIMIT',
+ 'Cosmetic has approximately ' + vertexCount + ' vertices; limit is ' + LIMITS.vertices + '.'
+ ));
+ }
+ if (triangleCount > LIMITS.triangles) {
+ issues.push(makeIssue(
+ 'error',
+ 'COSMIQ_TRIANGLE_LIMIT',
+ 'Cosmetic has approximately ' + triangleCount + ' triangles; limit is ' + LIMITS.triangles + '.'
+ ));
+ }
+
+ const animationBindings = {};
+ const activationRules = [];
+ const boundTriggers = Object.create(null);
+ const fallbackBindings = [];
+ let totalKeyframes = 0;
+ authoredAnimations.forEach(function (animation) {
+ totalKeyframes += animation.keyframeCount;
+ let trigger = null;
+ if (animation.triggerKind && animation.triggerKind !== 'none' && animation.triggerId) {
+ trigger = resolveTrigger(animation.triggerKind, animation.triggerId);
+ if (!trigger) {
+ issues.push(makeIssue(
+ 'error',
+ 'COSMIQ_TRIGGER_UNKNOWN',
+ 'Animation ' + animation.name + ' uses an unknown or mismatched trigger: ' +
+ animation.triggerKind + '/' + animation.triggerId,
+ animation.uuid
+ ));
+ }
+ } else {
+ const inferred = parseAnimationName(animation.name);
+ if (inferred) {
+ trigger = triggerById[inferred.triggerId];
+ }
+ }
+
+ const generatedClipId = runtimeId(safeProjectId, 'animation', animation.uuid);
+ let clipId = animation.clipId || generatedClipId;
+ if (!IDENTIFIER_PATTERN.test(clipId)) {
+ issues.push(makeIssue(
+ 'error',
+ 'COSMIQ_CLIP_ID_INVALID',
+ 'Animation ' + animation.name + ' has an invalid clip ID.',
+ animation.uuid
+ ));
+ clipId = generatedClipId;
+ }
+ const durationMs = Math.round(Number(animation.lengthSeconds) * 1000);
+ if (!Number.isFinite(durationMs) || durationMs < 1 || durationMs > LIMITS.animationSeconds * 1000) {
+ issues.push(makeIssue(
+ 'error',
+ 'COSMIQ_ANIMATION_DURATION',
+ 'Animation ' + animation.name + ' must be between 1 ms and ' +
+ LIMITS.animationSeconds + ' seconds.',
+ animation.uuid
+ ));
+ }
+ const transitionMs = Number.isFinite(Number(animation.transitionMs))
+ ? Math.round(Number(animation.transitionMs))
+ : 150;
+ if (transitionMs < 0 || transitionMs > 5000) {
+ issues.push(makeIssue(
+ 'error',
+ 'COSMIQ_TRANSITION_LIMIT',
+ 'Animation ' + animation.name + ' transition must be between 0 and 5000 ms.',
+ animation.uuid
+ ));
+ }
+ const priority = Number.isFinite(Number(animation.priority))
+ ? Math.round(Number(animation.priority))
+ : 100;
+ if (priority < 0 || priority > 1000) {
+ issues.push(makeIssue(
+ 'error',
+ 'COSMIQ_PRIORITY_LIMIT',
+ 'Animation ' + animation.name + ' priority must be between 0 and 1000.',
+ animation.uuid
+ ));
+ }
+
+ const targetNodeIds = [];
+ animation.targetUuids.forEach(function (targetUuid) {
+ if (nodeBindings[targetUuid]) {
+ targetNodeIds.push(nodeBindings[targetUuid].id);
+ } else if (animation.keyframeCount > 0) {
+ issues.push(makeIssue(
+ 'error',
+ 'COSMIQ_ANIMATION_TARGET_INVALID',
+ 'Animation ' + animation.name + ' targets an anchor, reference, marker, or missing node.',
+ targetUuid
+ ));
+ }
+ });
+
+ const isFallback = animation.fallback === true || (trigger && trigger.id === 'idle');
+ const binding = {
+ id: clipId,
+ sourceAnimationUuid: animation.uuid,
+ sourceName: animation.name,
+ durationMs: Math.max(1, durationMs || 1),
+ loop: {
+ mode: animation.loopMode === 'loop' || animation.loopMode === 'hold'
+ ? animation.loopMode
+ : 'once',
+ startMs: 0,
+ endMs: Math.max(1, durationMs || 1)
+ },
+ targetNodeIds: targetNodeIds,
+ markers: collectTimelineMarkers(animation.markers),
+ trigger: trigger ? {
+ kind: trigger.kind,
+ id: trigger.id,
+ family: trigger.family,
+ crossVersion: trigger.crossVersion
+ } : null,
+ transitionMs: Math.max(0, Math.min(5000, transitionMs)),
+ priority: Math.max(0, Math.min(1000, priority)),
+ fallback: isFallback
+ };
+ animationBindings[animation.uuid] = binding;
+
+ if (trigger) {
+ const triggerKey = trigger.kind + ':' + trigger.id;
+ if (boundTriggers[triggerKey]) {
+ issues.push(makeIssue(
+ 'error',
+ 'COSMIQ_TRIGGER_DUPLICATE',
+ 'More than one animation is bound to ' + trigger.label + '.',
+ animation.uuid
+ ));
+ }
+ boundTriggers[triggerKey] = animation.uuid;
+ const ruleId = runtimeId(safeProjectId, 'rule', animation.uuid);
+ activationRules.push({
+ ruleId: ruleId,
+ trigger: {
+ kind: trigger.kind,
+ stateId: trigger.id,
+ family: trigger.family
+ },
+ command: {
+ type: trigger.kind === 'player_event' ? 'play_clip' : 'blend_to_clip',
+ clipId: clipId,
+ mode: binding.loop.mode
+ },
+ layer: 'cosmetic_state',
+ priority: binding.priority,
+ transitionMs: binding.transitionMs,
+ fallback: isFallback
+ });
+ }
+ if (isFallback) {
+ fallbackBindings.push(binding);
+ }
+ });
+
+ if (authoredAnimations.length > LIMITS.animations) {
+ issues.push(makeIssue(
+ 'error',
+ 'COSMIQ_ANIMATION_LIMIT',
+ 'Project has ' + authoredAnimations.length + ' authored animations; limit is ' + LIMITS.animations + '.'
+ ));
+ }
+ if (totalKeyframes > LIMITS.keyframes) {
+ issues.push(makeIssue(
+ 'error',
+ 'COSMIQ_KEYFRAME_LIMIT',
+ 'Project has ' + totalKeyframes + ' keyframes; global limit is ' + LIMITS.keyframes + '.'
+ ));
+ }
+ if (fallbackBindings.length > 1) {
+ issues.push(makeIssue(
+ 'error',
+ 'COSMIQ_FALLBACK_DUPLICATE',
+ 'Only one cosmetic animation may be the fallback.'
+ ));
+ }
+ if (activationRules.length > 0 && fallbackBindings.length === 0) {
+ issues.push(makeIssue(
+ 'error',
+ 'COSMIQ_FALLBACK_MISSING',
+ 'State-bound cosmetics need one fallback animation, normally state.idle.'
+ ));
+ }
+ const hasOptionalState = activationRules.some(function (rule) {
+ const trigger = triggerById[rule.trigger.stateId];
+ return trigger && !trigger.crossVersion;
+ });
+ if (snapshot.project.compatibilityProfile === 'cross_version' && hasOptionalState && fallbackBindings.length === 0) {
+ issues.push(makeIssue(
+ 'error',
+ 'COSMIQ_CROSS_VERSION_FALLBACK',
+ 'Optional states require a fallback for every compatible target.'
+ ));
+ }
+ const activeAuthoredParticleIds = Object.create(null);
+ (Array.isArray(snapshot.project.resourceReferences)
+ ? snapshot.project.resourceReferences
+ : []
+ ).forEach(function (resource) {
+ if (resource && resource.type === RESOURCE_TYPES.PARTICLE_EFFECT &&
+ resource.runtimeExport !== false && resource.id) {
+ activeAuthoredParticleIds[resource.id] = true;
+ }
+ });
+ const activeAuthoredLocatorUuids = Object.create(null);
+ (Array.isArray(snapshot.project.timelineCues)
+ ? snapshot.project.timelineCues
+ : []
+ ).forEach(function (cue) {
+ if (cue && cue.type === CUE_TYPES.PARTICLE && cue.runtimeExport !== false &&
+ cue.locatorUuid && activeAuthoredParticleIds[cue.resourceId]) {
+ activeAuthoredLocatorUuids[cue.locatorUuid] = true;
+ }
+ });
+ const authoredMarkerAncestors = Object.create(null);
+ snapshot.nodes.forEach(function (node) {
+ if (!node || !activeAuthoredLocatorUuids[node.uuid] ||
+ effectiveRole(node) !== ROLES.PARTICLE_EFFECT_LOCATOR) {
+ return;
+ }
+ const seen = Object.create(null);
+ let parentUuid = node.parentUuid;
+ while (parentUuid && byUuid[parentUuid] && !seen[parentUuid]) {
+ seen[parentUuid] = true;
+ const parent = byUuid[parentUuid];
+ if (effectiveRole(parent) === ROLES.PARTICLE_MARKER) {
+ authoredMarkerAncestors[parentUuid] = true;
+ }
+ parentUuid = parent.parentUuid;
+ }
+ });
+ let expandedAuthoredMarkers = true;
+ while (expandedAuthoredMarkers) {
+ expandedAuthoredMarkers = false;
+ snapshot.nodes.forEach(function (node) {
+ if (!node || authoredMarkerAncestors[node.uuid] ||
+ effectiveRole(node) !== ROLES.PARTICLE_MARKER ||
+ !authoredMarkerAncestors[node.parentUuid]) {
+ return;
+ }
+ authoredMarkerAncestors[node.uuid] = true;
+ expandedAuthoredMarkers = true;
+ });
+ }
+ const particleEmitterBindings = {};
+ snapshot.nodes.forEach(function (node) {
+ if (effectiveRole(node) !== ROLES.PARTICLE_MARKER) {
+ return;
+ }
+ if (authoredMarkerAncestors[node.uuid]) {
+ return;
+ }
+ const markerName = node.particleId || parseParticleMarkerName(node.name) || stableKey(node.uuid);
+ const anchor = nearestAnchor(node, byUuid);
+ const parent = runtimeParent(node, byUuid, runtimeNodes);
+ const textureUuids = [];
+ (node.textureUuids || []).forEach(function (textureUuid) {
+ if (textureUuid && textureUuids.indexOf(textureUuid) === -1) {
+ textureUuids.push(textureUuid);
+ }
+ });
+ if (node.particleTextureUuid && textureUuids.indexOf(node.particleTextureUuid) === -1) {
+ textureUuids.unshift(node.particleTextureUuid);
+ }
+ if (textureUuids.length === 0) {
+ issues.push(makeIssue(
+ 'warning',
+ 'COSMIQ_PARTICLE_TEXTURE_MISSING',
+ 'Future particle marker ' + node.name + ' has no texture reference yet.',
+ node.uuid
+ ));
+ }
+ if (textureUuids.length > 1) {
+ issues.push(makeIssue(
+ 'warning',
+ 'COSMIQ_PARTICLE_TEXTURE_AMBIGUOUS',
+ 'Future particle marker ' + node.name + ' uses multiple textures; choose one marker texture.',
+ node.uuid
+ ));
+ }
+ particleEmitterBindings[node.uuid] = {
+ id: runtimeId(safeProjectId, 'particle', markerName),
+ sourceNodeUuid: node.uuid,
+ sourceName: node.name,
+ role: ROLES.PARTICLE_MARKER,
+ exportGeometry: false,
+ parentNodeId: parent.parentUuid && nodeBindings[parent.parentUuid]
+ ? nodeBindings[parent.parentUuid].id
+ : null,
+ anchorId: parent.parentUuid ? null : (canonicalAnchorId(anchor.anchorId) || null),
+ textureUuid: textureUuids[0] || null,
+ status: 'reserved_not_active'
+ };
+ });
+ const markerCount = Object.keys(particleEmitterBindings).length;
+ if (markerCount > LIMITS.futureParticleMarkers) {
+ issues.push(makeIssue(
+ 'error',
+ 'COSMIQ_PARTICLE_MARKER_LIMIT',
+ 'Project has ' + markerCount + ' future particle markers; reserved limit is ' +
+ LIMITS.futureParticleMarkers + '.'
+ ));
+ }
+ if (markerCount > 0) {
+ issues.push(makeIssue(
+ 'info',
+ 'COSMIQ_PARTICLES_RESERVED',
+ 'Particle markers are metadata-only. No particle emitter or runtime export is enabled yet.'
+ ));
+ }
+
+ const runtimeTextures = snapshot.textures.filter(function (texture) {
+ return texture.role !== ROLES.REFERENCE;
+ });
+ const runtimeTexturesById = Object.create(null);
+ runtimeTextures.forEach(function (texture) { runtimeTexturesById[texture.uuid] = texture; });
+ textureVariants.slice(1).forEach(function (variant) {
+ Object.keys(variant.replacements).forEach(function (baseUuid) {
+ const replacementUuid = variant.replacements[baseUuid];
+ const base = runtimeTexturesById[baseUuid];
+ const replacement = runtimeTexturesById[replacementUuid];
+ if (!base || !replacement) {
+ issues.push(makeIssue(
+ 'error',
+ 'COSMIQ_TEXTURE_VARIANT_REFERENCE',
+ 'Texture variant ' + variant.id + ' references a missing or reference-only texture.'
+ ));
+ } else if (base.width !== replacement.width || base.height !== replacement.height) {
+ issues.push(makeIssue(
+ 'error',
+ 'COSMIQ_TEXTURE_VARIANT_DIMENSIONS',
+ 'Texture variant ' + variant.id + ' must preserve its base texture dimensions.'
+ ));
+ }
+ });
+ });
+ if (runtimeTextures.length > LIMITS.textures) {
+ issues.push(makeIssue(
+ 'error',
+ 'COSMIQ_TEXTURE_LIMIT',
+ 'Project has ' + runtimeTextures.length + ' runtime textures; limit is ' + LIMITS.textures + '.'
+ ));
+ }
+ runtimeTextures.forEach(function (texture) {
+ if (texture.width > LIMITS.textureDimension || texture.height > LIMITS.textureDimension) {
+ issues.push(makeIssue(
+ 'error',
+ 'COSMIQ_TEXTURE_DIMENSION',
+ 'Texture ' + texture.name + ' exceeds ' + LIMITS.textureDimension + ' x ' +
+ LIMITS.textureDimension + '.',
+ texture.uuid
+ ));
+ }
+ });
+
+ const fallback = fallbackBindings.length > 0 ? fallbackBindings[0] : null;
+ const authoringResult = buildAuthoringMetadata(snapshot);
+ authoringResult.issues.forEach(function (issue) { issues.push(issue); });
+ const metadata = {
+ schemaVersion: SOURCE_SCHEMA,
+ formatVersion: COSMETIC_FORMAT_VERSION,
+ kind: PROJECT_KINDS.COSMETIC,
+ category: CATEGORIES.ACCESSORY,
+ animationMode: authoringAnimationMode,
+ projectId: safeProjectId,
+ template: snapshot.project.template || 'cosmetic_animated',
+ sourceRevision: Math.max(1, Number(snapshot.project.sourceRevision) || 1),
+ coordinateProfile: COORDINATE_PROFILE,
+ unitsPerBlock: 16,
+ compatibility: {
+ profile: snapshot.project.compatibilityProfile || 'cross_version',
+ baselineFallbackRequired: true,
+ minecraftSpecificFieldsAllowed: false
+ },
+ previewCamera: cosmiqClone(snapshot.project.previewCamera || {
+ id: 'thumbnail_camera',
+ transform: cosmiqDefaultTransform([0, 1, -3]),
+ projection: {
+ kind: 'perspective',
+ verticalFovDegrees: 35,
+ nearPlane: 0.01,
+ farPlane: 64
+ },
+ pose: {
+ clipId: null,
+ timeMs: 0,
+ modelVariant: 'universal',
+ visiblePropRootIds: []
+ }
+ }),
+ cosmetic: {
+ slotId: authoringBodySlots[0] || null,
+ bodySlots: authoringBodySlots.slice(),
+ modelVariant: derivedModelVariant,
+ attachmentAnchorId: detectedAttachmentAnchorIds.length === 1
+ ? detectedAttachmentAnchorIds[0]
+ : null,
+ textureVariants: textureVariants,
+ defaultClipId: fallback ? fallback.id : null
+ },
+ settings: normalizeCosmeticSettings(snapshot.project.settings),
+ anchorBindings: anchorBindings,
+ nodeBindings: nodeBindings,
+ animationBindings: animationBindings,
+ activationRules: activationRules,
+ stateMachine: {
+ draft: true,
+ fallbackClipId: fallback ? fallback.id : null,
+ transitionCommand: 'blend_to_clip',
+ unknownOrUnavailableState: 'use_fallback',
+ restartWhenStateUnchanged: false
+ },
+ authoringSchema: authoringResult.metadata.authoringSchema,
+ resourceReferences: authoringResult.metadata.resourceReferences,
+ effectLocatorBindings: authoringResult.metadata.effectLocatorBindings,
+ timelineMarkers: authoringResult.metadata.timelineMarkers,
+ timelineCues: authoringResult.metadata.timelineCues,
+ unsupportedMedia: authoringResult.metadata.unsupportedMedia,
+ playerAppearance: normalizePlayerAppearance(
+ snapshot.project.playerAppearance
+ ),
+ particleEmitterBindings: particleEmitterBindings,
+ budgets: {
+ nodes: Object.keys(nodeBindings).length,
+ animations: authoredAnimations.length,
+ keyframes: totalKeyframes,
+ vertices: vertexCount,
+ triangles: triangleCount,
+ textures: runtimeTextures.length,
+ futureParticleMarkers: markerCount
+ }
+ };
+
+ return {
+ metadata: metadata,
+ issues: issues
+ };
+ }
+
+ function buildCapeSourceMetadata(snapshot) {
+ const source = snapshot && typeof snapshot === 'object' ? snapshot : {};
+ const project = source.project && typeof source.project === 'object' ? source.project : {};
+ const issues = [];
+ const projectId = normalizeProjectId(project.id);
+ const runtimeTextures = (source.textures || []).filter(function (texture) {
+ return texture.role !== ROLES.REFERENCE;
+ });
+ const texture = runtimeTextures.length === 1 ? runtimeTextures[0] : null;
+ const resolution = texture && CAPE_RESOLUTIONS.find(function (candidate) {
+ return texture.width === candidate.width &&
+ texture.height >= candidate.height &&
+ texture.height % candidate.height === 0;
+ });
+ const frameCount = resolution ? texture.height / resolution.height : 0;
+ if (!projectId) {
+ issues.push(makeIssue(
+ 'error',
+ 'COSMIQ_PROJECT_ID_INVALID',
+ 'Project ID must be a lowercase namespaced identifier such as creator:my_cape.'
+ ));
+ }
+ if (!texture || !resolution) {
+ issues.push(makeIssue(
+ 'error',
+ 'COSMIQ_CAPE_TEXTURE',
+ 'Cape authoring needs exactly one runtime PNG using a supported 2:1 frame size. ' +
+ 'Vertical frame strips must be W x (frame count x H).'
+ ));
+ }
+ const authoringResult = buildAuthoringMetadata(source);
+ authoringResult.issues.forEach(function (issue) { issues.push(issue); });
+ return {
+ metadata: {
+ schemaVersion: CAPE_SOURCE_SCHEMA,
+ formatVersion: CAPE_FORMAT_VERSION,
+ kind: PROJECT_KINDS.CAPE,
+ category: CATEGORIES.CAPE,
+ animationMode: frameCount > 1 ? ANIMATION_MODES.ANIMATED : ANIMATION_MODES.STATIC,
+ projectId: projectId || 'creator:untitled_cape',
+ template: 'cape',
+ sourceRevision: Math.max(1, Number(project.sourceRevision) || 1),
+ coordinateProfile: COORDINATE_PROFILE,
+ unitsPerBlock: 16,
+ compatibility: {
+ profile: project.compatibilityProfile || 'cross_version',
+ baselineFallbackRequired: true,
+ minecraftSpecificFieldsAllowed: false
+ },
+ previewCamera: cosmiqClone(project.previewCamera || {
+ id: 'thumbnail_camera',
+ transform: cosmiqDefaultTransform([0, 1, -3]),
+ projection: {
+ kind: 'perspective',
+ verticalFovDegrees: 35,
+ nearPlane: 0.01,
+ farPlane: 64
+ },
+ pose: {
+ clipId: null,
+ timeMs: 0,
+ modelVariant: null,
+ visiblePropRootIds: []
+ }
+ }),
+ cape: {
+ backendStatus: 'coming_soon',
+ resolutionId: resolution ? resolution.id : null,
+ frameWidth: resolution ? resolution.width : null,
+ frameHeight: resolution ? resolution.height : null,
+ textureWidth: texture ? texture.width : null,
+ textureHeight: texture ? texture.height : null,
+ frameCount: frameCount,
+ animated: frameCount > 1,
+ sourceEncoding: frameCount > 1 ? 'vertical_png_strip' : 'png',
+ futureAcceptedEncodings: ['png', 'vertical_png_strip', 'gif'],
+ textureVariants: defaultTextureVariants()
+ },
+ authoringSchema: authoringResult.metadata.authoringSchema,
+ resourceReferences: authoringResult.metadata.resourceReferences,
+ effectLocatorBindings: authoringResult.metadata.effectLocatorBindings,
+ timelineMarkers: authoringResult.metadata.timelineMarkers,
+ timelineCues: authoringResult.metadata.timelineCues,
+ unsupportedMedia: authoringResult.metadata.unsupportedMedia.filter(function (type) {
+ return type !== 'gif';
+ }),
+ budgets: {
+ textures: runtimeTextures.length,
+ frames: frameCount
+ }
+ },
+ issues: issues
+ };
+ }
+
+ function emoteObjectActors(nodes) {
+ const records = Array.isArray(nodes) ? nodes : [];
+ const propsRoots = Object.create(null);
+ records.forEach(function (node) {
+ if (node && node.role === ROLES.PROPS_ROOT && node.uuid) {
+ propsRoots[node.uuid] = true;
+ }
+ });
+ return records.filter(function (node) {
+ return node && node.type === 'group' && propsRoots[node.parentUuid] === true;
+ });
+ }
+
+ function emoteNumericComponent(value) {
+ if (typeof value === 'number') {
+ return Number.isFinite(value) ? value : Number.NaN;
+ }
+ if (typeof value !== 'string') {
+ return Number.NaN;
+ }
+ const literal = value.trim();
+ if (!literal || !/^[+-]?(?:(?:\d+(?:\.\d*)?)|(?:\.\d+))(?:[eE][+-]?\d+)?$/.test(literal)) {
+ return Number.NaN;
+ }
+ const parsed = Number(literal);
+ return Number.isFinite(parsed) ? parsed : Number.NaN;
+ }
+
+ function validateEmotePivot(node, expectedOrigin, issues) {
+ const fallback = expectedOrigin.slice(0, 3);
+ let pivot = fallback;
+ if (!node || !Array.isArray(node.origin) || node.origin.length !== 3) {
+ issues.push(makeIssue(
+ 'error',
+ 'COSMIQ_EMOTE_PIVOT_FORMAT',
+ (node && node.name ? node.name : 'Emote node') + ' must have a finite three-axis pivot.',
+ node && node.uuid
+ ));
+ } else {
+ const values = node.origin.slice(0, 3).map(emoteNumericComponent);
+ if (values.some(function (value) { return !Number.isFinite(value); })) {
+ issues.push(makeIssue(
+ 'error',
+ 'COSMIQ_EMOTE_PIVOT_FORMAT',
+ node.name + ' must have a finite three-axis pivot.',
+ node.uuid
+ ));
+ } else {
+ pivot = values;
+ const excessiveOffset = pivot.some(function (value, index) {
+ return Math.abs(value - expectedOrigin[index]) > LIMITS.emotePivotOffsetUnits;
+ });
+ if (excessiveOffset) {
+ issues.push(makeIssue(
+ 'error',
+ 'COSMIQ_EMOTE_PIVOT_BOUNDS',
+ node.name + ' pivot is too far from its generated player rig.',
+ node.uuid
+ ));
+ }
+ }
+ }
+ if (!node || !Array.isArray(node.rotation) || node.rotation.length !== 3) {
+ issues.push(makeIssue(
+ 'error',
+ 'COSMIQ_EMOTE_REST_ROTATION',
+ (node && node.name ? node.name : 'Emote node')
+ + ' must keep a finite three-axis zero rest rotation; animate rotation with keyframes.',
+ node && node.uuid
+ ));
+ } else {
+ const rotation = node.rotation.slice(0, 3).map(emoteNumericComponent);
+ const invalidRotation = rotation.some(function (value) {
+ return !Number.isFinite(value) || value !== 0;
+ });
+ if (invalidRotation) {
+ issues.push(makeIssue(
+ 'error',
+ 'COSMIQ_EMOTE_REST_ROTATION',
+ node.name + ' must keep zero rest rotation; animate rotation with keyframes.',
+ node.uuid
+ ));
+ }
+ }
+ return pivot;
+ }
+
+ function emoteBoneOrigin(actorIndex, boneId) {
+ const x = EMOTE_ACTOR_OFFSETS[actorIndex - 1];
+ switch (boneId) {
+ case 'root': return [x, 0, 0];
+ case 'torso_lower': return [x, 12, 0];
+ case 'torso_upper': return [x, 18, 0];
+ case 'body':
+ case 'head':
+ case 'upper_body': return [x, 24, 0];
+ case 'left_arm_upper': return [x + 5, 22, 0];
+ case 'left_arm_lower': return [x + 5, 18, 0];
+ case 'right_arm_upper': return [x - 5, 22, 0];
+ case 'right_arm_lower': return [x - 5, 18, 0];
+ case 'left_arm': return [x + 5, 22, 0];
+ case 'right_arm': return [x - 5, 22, 0];
+ case 'left_leg_upper': return [x + 2, 12, 0];
+ case 'left_leg_lower': return [x + 2, 6, 0];
+ case 'right_leg_upper': return [x - 2, 12, 0];
+ case 'right_leg_lower': return [x - 2, 6, 0];
+ case 'left_leg': return [x + 2, 12, 0];
+ case 'right_leg': return [x - 2, 12, 0];
+ default: return [x, 0, 0];
+ }
+ }
+
+ function emotePlayerBonesForRigProfile(value) {
+ return value === EMOTE_RIG_PROFILES.PRECISE
+ ? EMOTE_PRECISE_PLAYER_BONES
+ : EMOTE_PLAYER_BONES;
+ }
+
+ function sourceVector(value, fallback) {
+ if (typeof value === 'number' && Number.isFinite(value)) {
+ return [value, value, value];
+ }
+ if (Array.isArray(value) && value.length >= 3) {
+ const vector = value.slice(0, 3).map(Number);
+ if (vector.every(Number.isFinite)) return vector;
+ }
+ return fallback.slice();
+ }
+
+ function sourceKeyValue(value, edge, fallback) {
+ if (value && typeof value === 'object' && !Array.isArray(value) &&
+ Object.prototype.hasOwnProperty.call(value, edge)) {
+ return sourceVector(value[edge], fallback);
+ }
+ return sourceVector(value, fallback);
+ }
+
+ function sourceChannelEntries(channel) {
+ if (!channel || typeof channel !== 'object' || Array.isArray(channel)) return [];
+ return Object.keys(channel).map(function (time) {
+ return {time: Number(time), source: channel[time]};
+ }).filter(function (entry) {
+ return Number.isFinite(entry.time) && entry.time >= 0;
+ }).sort(function (left, right) {
+ return left.time - right.time;
+ });
+ }
+
+ function sourceSampleChannel(channel, time, fallback) {
+ const entries = sourceChannelEntries(channel);
+ if (entries.length === 0) return fallback.slice();
+ let previous = null;
+ let next = null;
+ for (let index = 0; index < entries.length; index++) {
+ if (entries[index].time <= time + 1e-7) previous = entries[index];
+ if (entries[index].time >= time - 1e-7) {
+ next = entries[index];
+ break;
+ }
+ }
+ if (!previous) return sourceKeyValue(next.source, 'pre', fallback);
+ if (!next || Math.abs(previous.time - next.time) <= 1e-7) {
+ return sourceKeyValue(previous.source, 'post', fallback);
+ }
+ const start = sourceKeyValue(previous.source, 'post', fallback);
+ const end = sourceKeyValue(next.source, 'pre', fallback);
+ const weight = (time - previous.time) / (next.time - previous.time);
+ return start.map(function (value, axis) {
+ return value + (end[axis] - value) * weight;
+ });
+ }
+
+ function sourceTrackTimes(bones, names) {
+ const times = Object.create(null);
+ times['0'] = 0;
+ names.forEach(function (name) {
+ const bone = bones[name] || {};
+ ['position', 'rotation', 'scale'].forEach(function (channel) {
+ sourceChannelEntries(bone[channel]).forEach(function (entry) {
+ times[String(entry.time)] = entry.time;
+ });
+ });
+ });
+ return Object.keys(times).map(function (key) { return times[key]; }).sort(function (a, b) {
+ return a - b;
+ });
+ }
+
+ function sourceSemanticTransform(bone, time) {
+ const source = bone || {};
+ const rotation = sourceSampleChannel(source.rotation, time, [0, 0, 0]);
+ return {
+ position: sourceSampleChannel(source.position, time, [0, 0, 0]),
+ rotation: [-rotation[0], rotation[1], -rotation[2]],
+ scale: sourceSampleChannel(source.scale, time, [1, 1, 1])
+ };
+ }
+
+ function matrixIdentity() {
+ return [1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1];
+ }
+
+ function matrixMultiply(left, right) {
+ const result = new Array(16).fill(0);
+ for (let row = 0; row < 4; row++) {
+ for (let column = 0; column < 4; column++) {
+ for (let axis = 0; axis < 4; axis++) {
+ result[row * 4 + column] += left[row * 4 + axis] * right[axis * 4 + column];
+ }
+ }
+ }
+ return result;
+ }
+
+ function matrixTranslation(vector) {
+ const result = matrixIdentity();
+ result[3] = vector[0];
+ result[7] = vector[1];
+ result[11] = vector[2];
+ return result;
+ }
+
+ function matrixScale(vector) {
+ const result = matrixIdentity();
+ result[0] = vector[0];
+ result[5] = vector[1];
+ result[10] = vector[2];
+ return result;
+ }
+
+ function matrixRotation(rotation) {
+ const x = rotation[0] * Math.PI / 180;
+ const y = rotation[1] * Math.PI / 180;
+ const z = rotation[2] * Math.PI / 180;
+ const rx = [
+ 1, 0, 0, 0,
+ 0, Math.cos(x), -Math.sin(x), 0,
+ 0, Math.sin(x), Math.cos(x), 0,
+ 0, 0, 0, 1
+ ];
+ const ry = [
+ Math.cos(y), 0, Math.sin(y), 0,
+ 0, 1, 0, 0,
+ -Math.sin(y), 0, Math.cos(y), 0,
+ 0, 0, 0, 1
+ ];
+ const rz = [
+ Math.cos(z), -Math.sin(z), 0, 0,
+ Math.sin(z), Math.cos(z), 0, 0,
+ 0, 0, 1, 0,
+ 0, 0, 0, 1
+ ];
+ return matrixMultiply(matrixMultiply(rz, ry), rx);
+ }
+
+ function sourcePivotedMatrix(transform, pivot) {
+ return [
+ matrixTranslation(transform.position),
+ matrixTranslation(pivot),
+ matrixRotation(transform.rotation),
+ matrixScale(transform.scale),
+ matrixTranslation(pivot.map(function (value) { return -value; }))
+ ].reduce(matrixMultiply);
+ }
+
+ function cleanSourceNumber(value) {
+ if (Math.abs(value) < 1e-10) return 0;
+ const rounded = Math.round(value * 1e8) / 1e8;
+ return Object.is(rounded, -0) ? 0 : rounded;
+ }
+
+ function matrixTrs(matrix) {
+ const position = [matrix[3], matrix[7], matrix[11]];
+ const columns = [
+ [matrix[0], matrix[4], matrix[8]],
+ [matrix[1], matrix[5], matrix[9]],
+ [matrix[2], matrix[6], matrix[10]]
+ ];
+ const scale = columns.map(function (column) {
+ return Math.hypot(column[0], column[1], column[2]);
+ });
+ const rotation = matrix.slice();
+ for (let column = 0; column < 3; column++) {
+ const divisor = scale[column] > 1e-10 ? scale[column] : 1;
+ rotation[column] /= divisor;
+ rotation[4 + column] /= divisor;
+ rotation[8 + column] /= divisor;
+ }
+ const y = Math.asin(Math.max(-1, Math.min(1, -rotation[8])));
+ const cosineY = Math.cos(y);
+ let x;
+ let z;
+ if (Math.abs(cosineY) > 1e-7) {
+ x = Math.atan2(rotation[9], rotation[10]);
+ z = Math.atan2(rotation[4], rotation[0]);
+ } else {
+ x = Math.atan2(-rotation[6], rotation[5]);
+ z = 0;
+ }
+ return {
+ position: position.map(cleanSourceNumber),
+ rotation: [x, y, z].map(function (value) {
+ return cleanSourceNumber(value * 180 / Math.PI);
+ }),
+ scale: scale.map(cleanSourceNumber)
+ };
+ }
+
+ function sourceCombinedTransform(bones, firstName, secondName, firstPivot, secondPivot, time) {
+ const first = sourcePivotedMatrix(
+ sourceSemanticTransform(bones[firstName], time),
+ firstPivot
+ );
+ const second = sourcePivotedMatrix(
+ sourceSemanticTransform(bones[secondName], time),
+ secondPivot
+ );
+ const combined = matrixMultiply(first, second);
+ const local = matrixMultiply(
+ matrixMultiply(
+ matrixTranslation(secondPivot.map(function (value) { return -value; })),
+ combined
+ ),
+ matrixTranslation(secondPivot)
+ );
+ return matrixTrs(local);
+ }
+
+ function sourceTrackFromBones(bones, names, sample) {
+ const result = {position: [], rotation: [], scale: []};
+ const times = sourceTrackTimes(bones, names);
+ times.forEach(function (time, timeIndex) {
+ const transform = sample(time);
+ ['position', 'rotation', 'scale'].forEach(function (channel) {
+ const nextTime = times[timeIndex + 1];
+ result[channel].push({
+ time: time,
+ value: transform[channel].map(cleanSourceNumber),
+ step: nextTime != null && names.some(function (name) {
+ const source = bones[name] && bones[name][channel];
+ const entry = source && source[String(nextTime)];
+ return entry && typeof entry === 'object' && !Array.isArray(entry) &&
+ Object.prototype.hasOwnProperty.call(entry, 'pre') &&
+ Object.prototype.hasOwnProperty.call(entry, 'post');
+ })
+ });
+ });
+ });
+ return result;
+ }
+
+ function bakeSourcePlayerAnimation(animation) {
+ const source = animation && typeof animation === 'object' ? animation : {};
+ const bones = source.bones && typeof source.bones === 'object' ? source.bones : {};
+ const direct = function (name) {
+ return sourceTrackFromBones(bones, [name], function (time) {
+ return sourceSemanticTransform(bones[name], time);
+ });
+ };
+ const combined = function (first, second, firstPivot, secondPivot) {
+ return sourceTrackFromBones(bones, [first, second], function (time) {
+ return sourceCombinedTransform(
+ bones, first, second, firstPivot, secondPivot, time
+ );
+ });
+ };
+ const effects = Object.keys(source.particle_effects || {}).map(function (time) {
+ const effect = source.particle_effects[time] || {};
+ return {
+ atSeconds: Number(time),
+ effect: String(effect.effect || ''),
+ locator: String(effect.locator || '')
+ };
+ }).filter(function (effect) {
+ return Number.isFinite(effect.atSeconds) && effect.atSeconds >= 0 &&
+ effect.effect && effect.locator;
+ }).sort(function (left, right) {
+ return left.atSeconds - right.atSeconds;
+ });
+ return {
+ lengthSeconds: Number(source.animation_length) || 0,
+ loop: source.loop === true || source.loop === 'loop',
+ tracks: {
+ actor: direct('body_ext'),
+ upper_body: direct('body_wrap'),
+ body: direct('body'),
+ head: combined('head_ext', 'head', [0, 24, 0], [0, 24, 0]),
+ left_arm: combined('arm_left_ext', 'arm_left', [4, 22, 0], [5, 22, 0]),
+ right_arm: combined('arm_right_ext', 'arm_right', [-4, 22, 0], [-5, 22, 0]),
+ left_leg: direct('leg_left'),
+ right_leg: direct('leg_right')
+ },
+ effects: effects
+ };
+ }
+
+ function defaultEmoteRuleSource(animationBindings) {
+ const bindings = animationBindings && typeof animationBindings === 'object'
+ ? animationBindings
+ : {};
+ const firstFinite = Object.keys(bindings).map(function (uuid) {
+ return bindings[uuid];
+ }).find(function (binding) {
+ return binding && Number(binding.keyframeCount) > 0 &&
+ typeof binding.id === 'string' && IDENTIFIER_PATTERN.test(binding.id);
+ });
+ if (!firstFinite) {
+ return [
+ 'state main initial',
+ ' when cancel -> complete()',
+ ''
+ ].join('\n');
+ }
+ const target = firstFinite.scope === EMOTE_ANIMATION_SCOPES.SCENE ? 'all' : 'host';
+ return [
+ 'state main initial',
+ ' enter -> play.once("' + firstFinite.id + '", "' + target + '")',
+ ' when animation.finished("' + firstFinite.id + '") -> complete()',
+ ' when cancel -> complete()',
+ ''
+ ].join('\n');
+ }
+
+ function defaultEmoteLogicGraph(animationBindings) {
+ const bindings = animationBindings && typeof animationBindings === 'object'
+ ? animationBindings
+ : {};
+ const firstFinite = Object.keys(bindings).map(function (uuid) {
+ return bindings[uuid];
+ }).find(function (binding) {
+ return binding && Number(binding.keyframeCount) > 0 &&
+ typeof binding.id === 'string' && IDENTIFIER_PATTERN.test(binding.id);
+ });
+ const rules = [];
+ if (firstFinite) {
+ const target = firstFinite.scope === EMOTE_ANIMATION_SCOPES.SCENE ? 'all' : 'host';
+ rules.push({
+ event: {type: 'enter'},
+ action: {
+ type: 'play_animation',
+ clipId: firstFinite.id,
+ target: target,
+ mode: 'once',
+ blendMillis: 150
+ }
+ });
+ rules.push({
+ event: {type: 'animation_finished', clipId: firstFinite.id},
+ action: {type: 'complete'}
+ });
+ }
+ rules.push({event: {type: 'cancel'}, action: {type: 'complete'}});
+ return {
+ schemaVersion: EMOTE_LOGIC_SCHEMA,
+ initialState: 'main',
+ states: [{id: 'main', rules: rules}]
+ };
+ }
+
+ function inspectEmoteLogicAttachment(logicSource, logic, animationBindings) {
+ const issues = [];
+ const useStarterAttachment = (logicSource == null || logicSource === '') && logic == null;
+ let source = logicSource;
+ if (source == null || source === '') {
+ source = defaultEmoteRuleSource(animationBindings);
+ } else if (typeof source !== 'string') {
+ issues.push(makeIssue(
+ 'error',
+ 'COSMIQ_EMOTE_LOGIC_SOURCE_TYPE',
+ 'Editable emote logic source must be a string.'
+ ));
+ source = defaultEmoteRuleSource(animationBindings);
+ }
+ if (encodeUtf8(source).byteLength > EMOTE_LOGIC_LIMITS.sourceBytes) {
+ issues.push(makeIssue(
+ 'error',
+ 'COSMIQ_EMOTE_LOGIC_SOURCE_SIZE',
+ 'Editable emote logic source exceeds ' + EMOTE_LOGIC_LIMITS.sourceBytes + ' UTF-8 bytes.'
+ ));
+ }
+ if (source.split(/\r?\n/).length > EMOTE_LOGIC_LIMITS.sourceLines) {
+ issues.push(makeIssue(
+ 'error',
+ 'COSMIQ_EMOTE_LOGIC_SOURCE_LINES',
+ 'Editable emote logic source exceeds ' + EMOTE_LOGIC_LIMITS.sourceLines + ' lines.'
+ ));
+ }
+ if (/[^\t\n\r\u0020-\u007e]/.test(source)) {
+ issues.push(makeIssue(
+ 'error',
+ 'COSMIQ_EMOTE_LOGIC_SOURCE_CHARACTERS',
+ 'Editable emote logic source must use bounded printable ASCII text.'
+ ));
+ }
+
+ let graph = useStarterAttachment ? defaultEmoteLogicGraph(animationBindings) : null;
+ if (logic != null) {
+ if (!logic || typeof logic !== 'object' || Array.isArray(logic)) {
+ issues.push(makeIssue(
+ 'error',
+ 'COSMIQ_EMOTE_LOGIC_TYPE',
+ 'Embedded emote logic must be a canonical JSON object or null.'
+ ));
+ } else {
+ try {
+ graph = JSON.parse(JSON.stringify(logic));
+ if (graph.schemaVersion !== EMOTE_LOGIC_SCHEMA) {
+ issues.push(makeIssue(
+ 'error',
+ 'COSMIQ_EMOTE_LOGIC_SCHEMA',
+ 'Embedded emote logic uses an unsupported schema.'
+ ));
+ }
+ if (Object.keys(graph).some(function (key) {
+ return ['schemaVersion', 'initialState', 'states'].indexOf(key) === -1;
+ })) {
+ issues.push(makeIssue(
+ 'error',
+ 'COSMIQ_EMOTE_LOGIC_FIELD',
+ 'Embedded emote logic contains a non-canonical top-level field.'
+ ));
+ }
+ if (encodeUtf8(JSON.stringify(graph)).byteLength > EMOTE_LOGIC_LIMITS.graphBytes) {
+ issues.push(makeIssue(
+ 'error',
+ 'COSMIQ_EMOTE_LOGIC_SIZE',
+ 'Embedded emote logic exceeds ' + EMOTE_LOGIC_LIMITS.graphBytes + ' UTF-8 bytes.'
+ ));
+ }
+ validateJsonBounds(graph).forEach(function (issue) { issues.push(issue); });
+ } catch (error) {
+ issues.push(makeIssue(
+ 'error',
+ 'COSMIQ_EMOTE_LOGIC_JSON',
+ 'Embedded emote logic could not be copied as bounded JSON.'
+ ));
+ graph = null;
+ }
+ }
+ }
+ return {logicSource: source, logic: graph, issues: issues};
+ }
+
+ function buildEmoteSourceMetadata(snapshot) {
+ const source = snapshot || {};
+ const project = source.project || {};
+ const nodes = Array.isArray(source.nodes) ? source.nodes : [];
+ const animations = Array.isArray(source.animations) ? source.animations : [];
+ const issues = [];
+ const byUuid = Object.create(null);
+ const nodeBindings = Object.create(null);
+ const animationBindings = Object.create(null);
+ const normalizedProjectId = normalizeProjectId(project.id);
+ const projectId = normalizedProjectId || 'creator:untitled_emote';
+ const rigProfile = project.rigProfile === EMOTE_RIG_PROFILES.PRECISE
+ ? EMOTE_RIG_PROFILES.PRECISE
+ : EMOTE_RIG_PROFILES.SIMPLE;
+ const playerBoneDefinitions = emotePlayerBonesForRigProfile(rigProfile);
+ const declaredActorCount = Number(project.emoteActorCount);
+ const actorCount = Number.isInteger(declaredActorCount)
+ ? Math.max(EMOTE_ACTOR_MIN, Math.min(EMOTE_ACTOR_MAX, declaredActorCount))
+ : EMOTE_ACTOR_MIN;
+
+ if (!normalizedProjectId) {
+ issues.push(makeIssue(
+ 'error',
+ 'COSMIQ_EMOTE_PROJECT_ID',
+ 'Emote project ID must be a lowercase namespaced ID such as creator:victory_dance.'
+ ));
+ }
+ if (!Number.isInteger(declaredActorCount) ||
+ declaredActorCount < EMOTE_ACTOR_MIN || declaredActorCount > EMOTE_ACTOR_MAX) {
+ issues.push(makeIssue(
+ 'error',
+ 'COSMIQ_EMOTE_ACTOR_COUNT',
+ 'Emote actor count must be an integer from ' + EMOTE_ACTOR_MIN + ' through ' + EMOTE_ACTOR_MAX + '.'
+ ));
+ }
+
+ nodes.forEach(function (node) {
+ if (node && node.uuid) {
+ if (byUuid[node.uuid]) {
+ issues.push(makeIssue(
+ 'error',
+ 'COSMIQ_EMOTE_DUPLICATE_UUID',
+ 'Emote source contains a duplicate node UUID.',
+ node.uuid
+ ));
+ }
+ byUuid[node.uuid] = node;
+ }
+ });
+ if (nodes.length > LIMITS.nodes) {
+ issues.push(makeIssue(
+ 'error',
+ 'COSMIQ_EMOTE_NODE_LIMIT',
+ 'Emote source exceeds the draft limit of ' + LIMITS.nodes + ' nodes.'
+ ));
+ }
+ if (animations.length > LIMITS.animations) {
+ issues.push(makeIssue(
+ 'error',
+ 'COSMIQ_EMOTE_ANIMATION_LIMIT',
+ 'Emote source exceeds the draft limit of ' + LIMITS.animations + ' animations.'
+ ));
+ }
+
+ const sceneRoots = nodes.filter(function (node) { return node.role === ROLES.EMOTE_SCENE; });
+ if (sceneRoots.length !== 1) {
+ issues.push(makeIssue(
+ 'error',
+ 'COSMIQ_EMOTE_SCENE_ROOT',
+ 'An Emote project requires exactly one Emote Scene root.'
+ ));
+ }
+ const sceneRoot = sceneRoots[0] || null;
+ if (sceneRoot && sceneRoot.parentUuid) {
+ issues.push(makeIssue(
+ 'error',
+ 'COSMIQ_EMOTE_SCENE_PARENT',
+ 'Emote Scene must remain a top-level Outliner folder.',
+ sceneRoot.uuid
+ ));
+ }
+ validateEmotePivot(sceneRoot, [0, 0, 0], issues);
+ const actorRoots = nodes.filter(function (node) { return node.role === ROLES.EMOTE_ACTOR; });
+ if (actorRoots.length !== actorCount) {
+ issues.push(makeIssue(
+ 'error',
+ 'COSMIQ_EMOTE_ACTOR_ROOTS',
+ 'Expected ' + actorCount + ' numbered actor root(s), but found ' + actorRoots.length + '.'
+ ));
+ }
+
+ const actorMetadata = [];
+ for (let actorIndex = 1; actorIndex <= actorCount; actorIndex++) {
+ const matchingRoots = actorRoots.filter(function (node) {
+ return Number(node.actorIndex) === actorIndex;
+ });
+ if (matchingRoots.length !== 1) {
+ issues.push(makeIssue(
+ 'error',
+ 'COSMIQ_EMOTE_ACTOR_INDEX',
+ 'Actor ' + actorIndex + ' must have exactly one actor root.'
+ ));
+ continue;
+ }
+ const actorRoot = matchingRoots[0];
+ if (sceneRoot && actorRoot.parentUuid !== sceneRoot.uuid) {
+ issues.push(makeIssue(
+ 'error',
+ 'COSMIQ_EMOTE_ACTOR_PARENT',
+ 'Actor ' + actorIndex + ' must be directly inside Emote Scene.',
+ actorRoot.uuid
+ ));
+ }
+ const actorPivotUnits = validateEmotePivot(
+ actorRoot,
+ [EMOTE_ACTOR_OFFSETS[actorIndex - 1], 0, 0],
+ issues
+ );
+
+ const boneRecords = nodes.filter(function (node) {
+ return node.role === ROLES.PLAYER_BONE && Number(node.actorIndex) === actorIndex;
+ });
+ const boneById = Object.create(null);
+ const serializedBones = Object.create(null);
+ playerBoneDefinitions.forEach(function (boneDefinition) {
+ const matches = boneRecords.filter(function (node) {
+ return node.playerBone === boneDefinition.id;
+ });
+ if (matches.length !== 1) {
+ issues.push(makeIssue(
+ 'error',
+ 'COSMIQ_EMOTE_PLAYER_BONE',
+ 'Actor ' + actorIndex + ' requires exactly one ' + boneDefinition.label + ' bone.'
+ ));
+ return;
+ }
+ const bone = matches[0];
+ const bonePivotUnits = validateEmotePivot(
+ bone,
+ emoteBoneOrigin(actorIndex, boneDefinition.id),
+ issues
+ );
+ boneById[boneDefinition.id] = bone;
+ serializedBones[boneDefinition.id] = bone.uuid;
+ nodeBindings[bone.uuid] = {
+ id: runtimeId(projectId, 'actor_' + actorIndex + '_bone', boneDefinition.id),
+ role: ROLES.PLAYER_BONE,
+ actorIndex: actorIndex,
+ semanticBone: boneDefinition.id,
+ sourceName: bone.name,
+ parentUuid: bone.parentUuid || null,
+ pivotUnits: bonePivotUnits,
+ pivotTarget: 'shared'
+ };
+ });
+
+ playerBoneDefinitions.forEach(function (boneDefinition) {
+ const bone = boneById[boneDefinition.id];
+ if (!bone) {
+ return;
+ }
+ if (rigProfile === EMOTE_RIG_PROFILES.PRECISE && boneDefinition.parent) {
+ const expectedParent = boneById[boneDefinition.parent];
+ if (expectedParent && bone.parentUuid !== expectedParent.uuid) {
+ issues.push(makeIssue(
+ 'error',
+ 'COSMIQ_PRECISE_BONE_PARENT',
+ bone.name + ' must be a direct child of ' +
+ expectedParent.name + ' in the Precise armature.',
+ bone.uuid
+ ));
+ }
+ }
+ let semanticParent = bone.parentUuid ? byUuid[bone.parentUuid] : null;
+ const seenParents = Object.create(null);
+ while (semanticParent && semanticParent.role !== ROLES.EMOTE_ACTOR) {
+ if (!semanticParent.parentUuid || seenParents[semanticParent.parentUuid]) {
+ semanticParent = null;
+ break;
+ }
+ seenParents[semanticParent.parentUuid] = true;
+ semanticParent = byUuid[semanticParent.parentUuid] || null;
+ }
+ if (!semanticParent || semanticParent.uuid !== actorRoot.uuid) {
+ issues.push(makeIssue(
+ 'error',
+ 'COSMIQ_EMOTE_BONE_PARENT',
+ bone.name + ' does not resolve beneath its numbered Actor.',
+ bone.uuid
+ ));
+ }
+ });
+
+ const serializedStructures = {};
+ const upperBodyTransforms = nodes.filter(function (node) {
+ return node.role === ROLES.EMOTE_TRANSFORM &&
+ Number(node.actorIndex) === actorIndex &&
+ node.playerBone === 'upper_body';
+ });
+ if (upperBodyTransforms.length > 1) {
+ issues.push(makeIssue(
+ 'error',
+ 'COSMIQ_EMOTE_TRANSFORM_COUNT',
+ 'Actor ' + actorIndex + ' may contain at most one Upper Body transform.'
+ ));
+ } else if (upperBodyTransforms.length === 1) {
+ const upperBody = upperBodyTransforms[0];
+ const pivotUnits = validateEmotePivot(
+ upperBody,
+ emoteBoneOrigin(actorIndex, 'upper_body'),
+ issues
+ );
+ let semanticParent = upperBody.parentUuid ? byUuid[upperBody.parentUuid] : null;
+ const seenParents = Object.create(null);
+ while (semanticParent && semanticParent.role !== ROLES.EMOTE_ACTOR) {
+ if (!semanticParent.parentUuid || seenParents[semanticParent.parentUuid]) {
+ semanticParent = null;
+ break;
+ }
+ seenParents[semanticParent.parentUuid] = true;
+ semanticParent = byUuid[semanticParent.parentUuid] || null;
+ }
+ if (!semanticParent || semanticParent.uuid !== actorRoot.uuid) {
+ issues.push(makeIssue(
+ 'error',
+ 'COSMIQ_EMOTE_TRANSFORM_PARENT',
+ upperBody.name + ' does not resolve beneath its numbered Actor.',
+ upperBody.uuid
+ ));
+ }
+ serializedStructures.upper_body = upperBody.uuid;
+ nodeBindings[upperBody.uuid] = {
+ id: runtimeId(projectId, 'actor_' + actorIndex + '_transform', 'upper_body'),
+ role: ROLES.EMOTE_TRANSFORM,
+ actorIndex: actorIndex,
+ semanticBone: 'upper_body',
+ sourceName: upperBody.name,
+ parentUuid: upperBody.parentUuid || null,
+ pivotUnits: pivotUnits,
+ pivotTarget: 'shared'
+ };
+ }
+
+ nodeBindings[actorRoot.uuid] = {
+ id: runtimeId(projectId, 'actor', actorIndex),
+ role: ROLES.EMOTE_ACTOR,
+ actorIndex: actorIndex,
+ sourceName: actorRoot.name,
+ parentUuid: actorRoot.parentUuid || null,
+ pivotUnits: actorPivotUnits,
+ pivotTarget: 'shared'
+ };
+ actorMetadata.push({
+ index: actorIndex,
+ id: runtimeId(projectId, 'actor', actorIndex),
+ rootUuid: actorRoot.uuid,
+ formationOffsetUnits: EMOTE_ACTOR_OFFSETS[actorIndex - 1],
+ bones: serializedBones,
+ structures: serializedStructures
+ });
+ }
+
+ actorRoots.forEach(function (actorRoot) {
+ const index = Number(actorRoot.actorIndex);
+ if (!Number.isInteger(index) || index < 1 || index > actorCount) {
+ issues.push(makeIssue(
+ 'error',
+ 'COSMIQ_EMOTE_ACTOR_INDEX_RANGE',
+ 'Actor roots must use contiguous one-based indices.',
+ actorRoot.uuid
+ ));
+ }
+ });
+
+ const containsProps = project.emoteContainsProps === true;
+ const propsRoots = nodes.filter(function (node) { return node.role === ROLES.PROPS_ROOT; });
+ if (containsProps && propsRoots.length !== 1) {
+ issues.push(makeIssue(
+ 'error',
+ 'COSMIQ_EMOTE_PROPS_ROOT',
+ 'An Emote marked as containing props requires exactly one Props root.'
+ ));
+ } else if (!containsProps && propsRoots.length > 0) {
+ issues.push(makeIssue(
+ 'error',
+ 'COSMIQ_EMOTE_UNDECLARED_PROPS',
+ 'The project has a Props root but Contains Props is disabled.',
+ propsRoots[0].uuid
+ ));
+ }
+ if (propsRoots.length === 1 && sceneRoot && propsRoots[0].parentUuid !== sceneRoot.uuid) {
+ issues.push(makeIssue(
+ 'error',
+ 'COSMIQ_EMOTE_PROPS_PARENT',
+ 'Props must be directly inside Emote Scene.',
+ propsRoots[0].uuid
+ ));
+ }
+
+ const objectActors = emoteObjectActors(nodes).map(function (node) {
+ const binding = {
+ id: runtimeId(projectId, 'object_actor', node.uuid),
+ role: ROLES.OBJECT_ACTOR,
+ sourceName: node.name,
+ sourceUuid: node.uuid,
+ parentUuid: node.parentUuid
+ };
+ nodeBindings[node.uuid] = binding;
+ const nodeUuids = [];
+ nodes.forEach(function (candidate) {
+ if (!candidate || candidate.uuid === node.uuid) {
+ return;
+ }
+ let current = candidate;
+ const seen = Object.create(null);
+ let belongsToObjectActor = false;
+ while (current && current.parentUuid && !seen[current.parentUuid]) {
+ if (current.parentUuid === node.uuid) {
+ belongsToObjectActor = true;
+ break;
+ }
+ seen[current.parentUuid] = true;
+ current = byUuid[current.parentUuid] || null;
+ }
+ if (!belongsToObjectActor) {
+ return;
+ }
+ nodeUuids.push(candidate.uuid);
+ nodeBindings[candidate.uuid] = {
+ id: runtimeId(projectId, 'object_actor_node', candidate.uuid),
+ role: ROLES.OBJECT_ACTOR_NODE,
+ objectActorUuid: node.uuid,
+ sourceName: candidate.name,
+ parentUuid: candidate.parentUuid || null
+ };
+ });
+ return {
+ id: binding.id,
+ sourceUuid: node.uuid,
+ name: node.name,
+ nodeUuids: nodeUuids
+ };
+ });
+ propsRoots.forEach(function (propsRoot) {
+ nodes.filter(function (node) { return node.parentUuid === propsRoot.uuid && node.type !== 'group'; })
+ .forEach(function (node) {
+ issues.push(makeIssue(
+ 'error',
+ 'COSMIQ_EMOTE_PROP_FOLDER_REQUIRED',
+ 'Every direct child of Props must be a folder representing one Object-Actor.',
+ node.uuid
+ ));
+ });
+ });
+ if (containsProps && propsRoots.length === 1 && objectActors.length === 0) {
+ issues.push(makeIssue(
+ 'info',
+ 'COSMIQ_EMOTE_PROPS_EMPTY',
+ 'Props is ready. Add one folder per Object-Actor when the prop models are ready.',
+ propsRoots[0].uuid
+ ));
+ }
+
+ let authoredSceneClips = 0;
+ let authoredActorClips = 0;
+ let totalKeyframes = 0;
+ animations.forEach(function (animation) {
+ const scope = animation.emoteScope || EMOTE_ANIMATION_SCOPES.NONE;
+ const keyframeCount = Math.max(0, Math.floor(Number(animation.keyframeCount)) || 0);
+ const rawActorIndex = Number(animation.emoteActorIndex);
+ const actorIndex = Number.isInteger(rawActorIndex) ? rawActorIndex : 0;
+ totalKeyframes += keyframeCount;
+ if (!Number.isFinite(Number(animation.lengthSeconds)) || Number(animation.lengthSeconds) < 0 ||
+ Number(animation.lengthSeconds) > LIMITS.animationSeconds) {
+ issues.push(makeIssue(
+ 'error',
+ 'COSMIQ_EMOTE_ANIMATION_DURATION',
+ 'Emote animation duration must be from 0 through ' + LIMITS.animationSeconds + ' seconds.',
+ animation.uuid
+ ));
+ }
+ (animation.targetUuids || []).forEach(function (targetUuid) {
+ if (!byUuid[targetUuid]) {
+ issues.push(makeIssue(
+ 'error',
+ 'COSMIQ_EMOTE_ANIMATION_TARGET',
+ 'Emote animation references an unknown target UUID.',
+ animation.uuid
+ ));
+ return;
+ }
+ const targetBinding = nodeBindings[targetUuid];
+ const targetNode = byUuid[targetUuid];
+ const effectLocator = targetNode && (
+ targetNode.role === ROLES.PARTICLE_EFFECT_LOCATOR ||
+ targetNode.role === ROLES.SOUND_EFFECT_LOCATOR
+ );
+ if (!effectLocator && (!targetBinding || (
+ targetBinding.role !== ROLES.EMOTE_ACTOR &&
+ targetBinding.role !== ROLES.PLAYER_BONE &&
+ targetBinding.role !== ROLES.EMOTE_TRANSFORM &&
+ targetBinding.role !== ROLES.OBJECT_ACTOR &&
+ targetBinding.role !== ROLES.OBJECT_ACTOR_NODE
+ ))) {
+ issues.push(makeIssue(
+ 'error',
+ 'COSMIQ_EMOTE_ANIMATION_STRUCTURAL_TARGET',
+ 'Emote animations may target only numbered Actor wrappers, semantic player bones, Object-Actor nodes, or effect locators.',
+ animation.uuid
+ ));
+ }
+ });
+ if (scope !== EMOTE_ANIMATION_SCOPES.SCENE && scope !== EMOTE_ANIMATION_SCOPES.ACTOR) {
+ if (keyframeCount > 0) {
+ issues.push(makeIssue(
+ 'error',
+ 'COSMIQ_EMOTE_ANIMATION_SCOPE',
+ 'Authored emote animations require an explicit Scene or Actor scope.',
+ animation.uuid
+ ));
+ }
+ return;
+ }
+ if (scope === EMOTE_ANIMATION_SCOPES.SCENE) {
+ if (keyframeCount > 0) authoredSceneClips++;
+ } else {
+ if (!Number.isInteger(rawActorIndex) || actorIndex < 1 || actorIndex > actorCount) {
+ issues.push(makeIssue(
+ 'error',
+ 'COSMIQ_EMOTE_ANIMATION_ACTOR',
+ 'Actor-scoped animation ' + animation.name + ' has an invalid actor index.',
+ animation.uuid
+ ));
+ }
+ (animation.targetUuids || []).forEach(function (targetUuid) {
+ const target = byUuid[targetUuid];
+ if (target && Number(target.actorIndex) > 0 && Number(target.actorIndex) !== actorIndex) {
+ issues.push(makeIssue(
+ 'error',
+ 'COSMIQ_EMOTE_ANIMATION_FOREIGN_ACTOR',
+ animation.name + ' targets a different numbered actor.',
+ animation.uuid
+ ));
+ }
+ });
+ if (keyframeCount > 0) authoredActorClips++;
+ }
+ animationBindings[animation.uuid] = {
+ id: animation.clipId || runtimeId(projectId, 'animation', animation.uuid),
+ sourceName: animation.name,
+ scope: scope,
+ actorIndex: scope === EMOTE_ANIMATION_SCOPES.ACTOR ? actorIndex : null,
+ keyframeCount: keyframeCount,
+ targetUuids: (animation.targetUuids || []).slice(),
+ markers: collectTimelineMarkers(animation.markers)
+ };
+ });
+ if (authoredSceneClips > 0 && authoredActorClips > 0) {
+ issues.push(makeIssue(
+ 'warning',
+ 'COSMIQ_EMOTE_MIXED_ANIMATION_SCOPES',
+ 'Both combined Scene and individual Actor clips contain keyframes. Leave only the intended playback path authored until precedence is defined.'
+ ));
+ }
+ if (totalKeyframes > LIMITS.keyframes) {
+ issues.push(makeIssue(
+ 'error',
+ 'COSMIQ_EMOTE_KEYFRAME_LIMIT',
+ 'Emote source exceeds the draft limit of ' + LIMITS.keyframes + ' keyframes.'
+ ));
+ }
+
+ const authoringResult = buildAuthoringMetadata(source);
+ authoringResult.issues.forEach(function (issue) { issues.push(issue); });
+ const logicAttachment = inspectEmoteLogicAttachment(
+ project.emoteLogicSource,
+ project.emoteLogic,
+ animationBindings
+ );
+ logicAttachment.issues.forEach(function (issue) { issues.push(issue); });
+ const outputMetadata = {
+ schemaVersion: EMOTE_SOURCE_SCHEMA,
+ formatVersion: COSMETIC_FORMAT_VERSION,
+ kind: PROJECT_KINDS.EMOTE,
+ category: CATEGORIES.EMOTE,
+ animationMode: ANIMATION_MODES.ANIMATED,
+ draft: true,
+ runtimeExportReady: false,
+ projectId: projectId,
+ template: containsProps ? 'emote_with_props' : 'emote_player',
+ sourceRevision: Math.max(1, Math.floor(Number(project.sourceRevision)) || 1),
+ coordinateProfile: COORDINATE_PROFILE,
+ unitsPerBlock: 16,
+ compatibility: {
+ profile: project.compatibilityProfile || 'cross_version',
+ baselineFallbackRequired: true,
+ minecraftSpecificFieldsAllowed: false
+ },
+ previewCamera: cosmiqClone(project.previewCamera || {
+ id: 'thumbnail_camera',
+ transform: cosmiqDefaultTransform([0, 1, -5]),
+ projection: {
+ kind: 'perspective',
+ verticalFovDegrees: 35,
+ nearPlane: 0.01,
+ farPlane: 64
+ },
+ pose: {
+ clipId: null,
+ timeMs: 0,
+ modelVariant: null,
+ visiblePropRootIds: []
+ }
+ }),
+ emote: {
+ rigProfile: rigProfile,
+ actorCount: actorCount,
+ containsProps: containsProps,
+ traversable: project.emoteTraversable === true,
+ movementIntent: project.emoteTraversable === true ? 'movement_blend' : 'stationary',
+ actorNumbering: 'one_based_prefix',
+ objectActorRule: 'direct_group_child_of_props',
+ animationAuthoring: ['scene', 'actor'],
+ detectedAnimationMode: authoredSceneClips > 0 && authoredActorClips > 0
+ ? 'mixed'
+ : (authoredSceneClips > 0 ? 'scene' : (authoredActorClips > 0 ? 'actor' : 'empty'))
+ },
+ actors: actorMetadata,
+ props: {
+ rootUuid: propsRoots.length === 1 ? propsRoots[0].uuid : null,
+ objectActors: objectActors
+ },
+ nodeBindings: nodeBindings,
+ animationBindings: animationBindings,
+ authoringSchema: authoringResult.metadata.authoringSchema,
+ resourceReferences: authoringResult.metadata.resourceReferences,
+ effectLocatorBindings: authoringResult.metadata.effectLocatorBindings,
+ timelineMarkers: authoringResult.metadata.timelineMarkers,
+ timelineCues: authoringResult.metadata.timelineCues,
+ outcomeSets: Array.isArray(project.emoteOutcomeSets)
+ ? cosmiqClone(project.emoteOutcomeSets)
+ : [],
+ unsupportedMedia: authoringResult.metadata.unsupportedMedia,
+ budgets: {
+ nodes: nodes.length,
+ animations: animations.length,
+ keyframes: totalKeyframes,
+ objectActors: objectActors.length
+ },
+ logicSource: logicAttachment.logicSource,
+ logic: logicAttachment.logic
+ };
+ if (!containsProps) {
+ delete outputMetadata.props;
+ }
+ return {
+ metadata: outputMetadata,
+ issues: issues
+ };
+ }
+
+ let creatorFormat = null;
+ let validatorCheck = null;
+ let activeDialog = null;
+ let pluginStyle = null;
+ let cosmiqMenu = null;
+ const actions = [];
+ const properties = [];
+ const listeners = [];
+
+ function isCosmiqProject() {
+ return typeof Format !== 'undefined' && Format && Format.id === FORMAT_ID;
+ }
+
+ function activeCosmiqProjectKind() {
+ if (typeof Project === 'undefined' || !Project) return null;
+ return [
+ PROJECT_KINDS.COSMETIC,
+ PROJECT_KINDS.EMOTE,
+ PROJECT_KINDS.CAPE
+ ].indexOf(Project.cosmiq_project_kind) === -1
+ ? null
+ : Project.cosmiq_project_kind;
+ }
+
+ function projectKindFromMetadata(metadata, fallback) {
+ const source = metadata && typeof metadata === 'object' ? metadata : {};
+ if ([
+ PROJECT_KINDS.COSMETIC,
+ PROJECT_KINDS.EMOTE,
+ PROJECT_KINDS.CAPE
+ ].indexOf(source.kind) !== -1) {
+ return source.kind;
+ }
+ const categoryKind = {
+ accessory: PROJECT_KINDS.COSMETIC,
+ emote: PROJECT_KINDS.EMOTE,
+ cape: PROJECT_KINDS.CAPE
+ }[source.category];
+ if (categoryKind) return categoryKind;
+ return [
+ PROJECT_KINDS.COSMETIC,
+ PROJECT_KINDS.EMOTE,
+ PROJECT_KINDS.CAPE
+ ].indexOf(fallback) === -1 ? PROJECT_KINDS.COSMETIC : fallback;
+ }
+
+ function isCosmiqCosmeticProject() {
+ return isCosmiqProject() && activeCosmiqProjectKind() === PROJECT_KINDS.COSMETIC;
+ }
+
+ function isCosmiqEmoteProject() {
+ return isCosmiqProject() && activeCosmiqProjectKind() === PROJECT_KINDS.EMOTE;
+ }
+
+ function isCosmiqCapeProject() {
+ return isCosmiqProject() && activeCosmiqProjectKind() === PROJECT_KINDS.CAPE;
+ }
+
+ function addProperty(target, type, name, options) {
+ const property = new Property(target, type, name, options);
+ properties.push(property);
+ return property;
+ }
+
+ function registerProperties() {
+ const condition = {formats: [FORMAT_ID]};
+ addProperty(ModelProject, 'string', 'cosmiq_project_id', {
+ label: 'Cosmiq Project ID',
+ description: 'Stable namespaced source ID, for example creator:wing_demo.',
+ default: 'creator:untitled',
+ condition: condition
+ });
+ addProperty(ModelProject, 'enum', 'cosmiq_project_kind', {
+ label: 'Cosmiq Project Type',
+ values: [PROJECT_KINDS.COSMETIC, PROJECT_KINDS.EMOTE, PROJECT_KINDS.CAPE],
+ default: PROJECT_KINDS.COSMETIC,
+ exposed: false,
+ condition: condition
+ });
+ addProperty(ModelProject, 'enum', 'cosmiq_template', {
+ label: 'Cosmiq Template',
+ values: TEMPLATE_VALUES.concat(EMOTE_TEMPLATE_VALUES, ['cape']),
+ default: 'cosmetic_static',
+ condition: condition
+ });
+ addProperty(ModelProject, 'enum', 'cosmiq_compatibility_profile', {
+ label: 'Compatibility Profile',
+ values: ['cross_version', 'advanced'],
+ default: 'cross_version',
+ condition: condition
+ });
+ addProperty(ModelProject, 'enum', 'cosmiq_slot', {
+ label: 'Derived Cosmetic Slot',
+ values: [
+ 'cosmiq:slot/head',
+ 'cosmiq:slot/body',
+ 'cosmiq:slot/arms',
+ 'cosmiq:slot/legs'
+ ],
+ default: 'cosmiq:slot/body',
+ exposed: false,
+ condition: condition
+ });
+ addProperty(ModelProject, 'string', 'cosmiq_body_slots', {
+ label: 'Derived Wardrobe Body Slots',
+ description: 'Automatically derived from the precise anchor parents used by the model.',
+ default: '',
+ exposed: false,
+ condition: condition
+ });
+ addProperty(ModelProject, 'enum', 'cosmiq_model_variant', {
+ label: 'Player Model Variant',
+ values: MODEL_VARIANTS,
+ default: 'universal',
+ condition: condition
+ });
+ addProperty(ModelProject, 'enum', 'cosmiq_arm_width_mode', {
+ label: 'Arm Width Fit',
+ description: 'Use one shared arm model, or author separate Wide and Slim arm models.',
+ values: ARM_WIDTH_MODE_VALUES,
+ default: ARM_WIDTH_MODES.UNCONFIGURED,
+ exposed: false,
+ condition: condition
+ });
+ addProperty(ModelProject, 'number', 'cosmiq_source_revision', {
+ default: 1,
+ exposed: false,
+ condition: condition
+ });
+ addProperty(ModelProject, 'number', 'cosmiq_emote_actor_count', {
+ label: 'Emote Actor Count',
+ description: 'Numbered player-role slots in this draft scene.',
+ default: 1,
+ exposed: false,
+ condition: condition
+ });
+ addProperty(ModelProject, 'boolean', 'cosmiq_emote_contains_props', {
+ label: 'Emote Contains Props',
+ default: false,
+ exposed: false,
+ condition: condition
+ });
+ addProperty(ModelProject, 'boolean', 'cosmiq_emote_traversable', {
+ label: 'Emote Traversable',
+ description: 'Authoring intent only. Minecraft remains authoritative for movement.',
+ default: false,
+ exposed: false,
+ condition: condition
+ });
+ addProperty(ModelProject, 'enum', 'cosmiq_rig_profile', {
+ label: 'Emote Rig',
+ values: [EMOTE_RIG_PROFILES.SIMPLE, EMOTE_RIG_PROFILES.PRECISE],
+ default: EMOTE_RIG_PROFILES.SIMPLE,
+ exposed: false,
+ condition: condition
+ });
+ [
+ ['cosmiq_camera_x', 0],
+ ['cosmiq_camera_y', 1],
+ ['cosmiq_camera_z', -3],
+ ['cosmiq_camera_pitch', 0],
+ ['cosmiq_camera_yaw', 0],
+ ['cosmiq_camera_roll', 0],
+ ['cosmiq_camera_fov', 35],
+ ['cosmiq_camera_time_ms', 0]
+ ].forEach(function (entry) {
+ addProperty(ModelProject, 'number', entry[0], {
+ default: entry[1],
+ exposed: false,
+ condition: condition
+ });
+ });
+ addProperty(ModelProject, 'string', 'cosmiq_camera_clip_id', {
+ default: '',
+ exposed: false,
+ condition: condition
+ });
+ addProperty(ModelProject, 'enum', 'cosmiq_camera_model_variant', {
+ values: ['universal', 'wide', 'slim'],
+ default: 'wide',
+ exposed: false,
+ condition: condition
+ });
+ addProperty(ModelProject, 'string', 'cosmiq_camera_visible_props', {
+ default: '',
+ exposed: false,
+ condition: condition
+ });
+ addProperty(ModelProject, 'object', 'cosmiq', {
+ default: {},
+ exposed: false,
+ condition: condition
+ });
+
+ const anchorOptions = {none: 'Not an anchor'};
+ ANCHORS.forEach(function (anchor) {
+ anchorOptions[anchor.id] = anchor.label;
+ });
+ addProperty(Group, 'enum', 'cosmiq_anchor', {
+ values: ['none'].concat(ANCHORS.map(function (anchor) { return anchor.id; })),
+ default: 'none',
+ copy_value: false,
+ condition: condition,
+ inputs: {
+ element_panel: {
+ input: {
+ label: 'Cosmiq Player Anchor',
+ description: 'Children inherit this player anchor when Cosmiq converts the project.',
+ type: 'select',
+ options: anchorOptions
+ }
+ }
+ }
+ });
+ addProperty(Group, 'enum', 'cosmiq_model_branch', {
+ values: ['none', 'wide', 'slim'],
+ default: 'none',
+ copy_value: false,
+ exposed: false,
+ condition: condition
+ });
+
+ [Group, Cube].forEach(function (type) {
+ addProperty(type, 'enum', 'cosmiq_role', {
+ values: BLOCKBENCH_NODE_ROLE_VALUES,
+ default: ROLES.NONE,
+ copy_value: false,
+ exposed: false,
+ condition: condition
+ });
+ });
+ if (typeof Mesh !== 'undefined') {
+ addProperty(Mesh, 'enum', 'cosmiq_role', {
+ values: BLOCKBENCH_NODE_ROLE_VALUES,
+ default: ROLES.NONE,
+ copy_value: false,
+ exposed: false,
+ condition: condition
+ });
+ }
+ [typeof Armature !== 'undefined' ? Armature : null,
+ typeof ArmatureBone !== 'undefined' ? ArmatureBone : null
+ ].filter(Boolean).forEach(function (type) {
+ addProperty(type, 'enum', 'cosmiq_role', {
+ values: BLOCKBENCH_NODE_ROLE_VALUES,
+ default: ROLES.NONE,
+ copy_value: false,
+ exposed: false,
+ condition: condition
+ });
+ });
+ if (typeof Locator !== 'undefined') {
+ addProperty(Locator, 'enum', 'cosmiq_role', {
+ values: BLOCKBENCH_NODE_ROLE_VALUES,
+ default: ROLES.NONE,
+ copy_value: false,
+ exposed: false,
+ condition: condition
+ });
+ addProperty(Locator, 'string', 'cosmiq_effect_locator_id', {
+ default: '',
+ copy_value: false,
+ exposed: false,
+ condition: condition
+ });
+ addProperty(Locator, 'string', 'cosmiq_resource_id', {
+ default: '',
+ copy_value: true,
+ exposed: false,
+ condition: condition
+ });
+ }
+
+ [Group, Cube]
+ .concat(typeof Mesh !== 'undefined' ? [Mesh] : [])
+ .concat(typeof Armature !== 'undefined' ? [Armature] : [])
+ .concat(typeof ArmatureBone !== 'undefined' ? [ArmatureBone] : [])
+ .forEach(function (type) {
+ addProperty(type, 'number', 'cosmiq_actor_index', {
+ default: 0,
+ copy_value: false,
+ exposed: false,
+ condition: condition
+ });
+ addProperty(type, 'enum', 'cosmiq_player_bone', {
+ values: BLOCKBENCH_PLAYER_BONE_VALUES,
+ default: 'none',
+ copy_value: false,
+ exposed: false,
+ condition: condition
+ });
+ });
+ if (typeof Texture !== 'undefined') {
+ addProperty(Texture, 'enum', 'cosmiq_role', {
+ values: [ROLES.NONE, ROLES.REFERENCE, 'particle_sprite'],
+ default: ROLES.NONE,
+ copy_value: false,
+ exposed: false,
+ condition: condition
+ });
+ addProperty(Texture, 'string', 'cosmiq_variant_id', {
+ default: '', copy_value: false, exposed: false, condition: condition
+ });
+ addProperty(Texture, 'string', 'cosmiq_variant_base', {
+ default: '', copy_value: false, exposed: false, condition: condition
+ });
+ }
+
+ [Cube].concat(typeof Locator !== 'undefined' ? [Locator] : []).forEach(function (type) {
+ addProperty(type, 'string', 'cosmiq_particle_id', {
+ default: '',
+ copy_value: false,
+ exposed: false,
+ condition: condition
+ });
+ addProperty(type, 'string', 'cosmiq_particle_texture', {
+ default: '',
+ exposed: false,
+ condition: condition
+ });
+ });
+
+ const AnimationClass = Blockbench.Animation;
+ addProperty(AnimationClass, 'string', 'cosmiq_clip_id', {
+ default: '',
+ copy_value: false,
+ exposed: false,
+ condition: condition
+ });
+ addProperty(AnimationClass, 'enum', 'cosmiq_trigger_kind', {
+ values: ['none', 'player_state', 'player_event'],
+ default: 'none',
+ exposed: false,
+ condition: condition
+ });
+ addProperty(AnimationClass, 'string', 'cosmiq_trigger', {
+ default: '',
+ exposed: false,
+ condition: condition
+ });
+ addProperty(AnimationClass, 'number', 'cosmiq_transition_ms', {
+ default: 150,
+ exposed: false,
+ condition: condition
+ });
+ addProperty(AnimationClass, 'number', 'cosmiq_priority', {
+ default: 100,
+ exposed: false,
+ condition: condition
+ });
+ addProperty(AnimationClass, 'boolean', 'cosmiq_fallback', {
+ default: false,
+ exposed: false,
+ condition: condition
+ });
+ addProperty(AnimationClass, 'enum', 'cosmiq_emote_scope', {
+ values: [
+ EMOTE_ANIMATION_SCOPES.NONE,
+ EMOTE_ANIMATION_SCOPES.SCENE,
+ EMOTE_ANIMATION_SCOPES.ACTOR
+ ],
+ default: EMOTE_ANIMATION_SCOPES.NONE,
+ copy_value: true,
+ exposed: false,
+ condition: condition
+ });
+ addProperty(AnimationClass, 'number', 'cosmiq_emote_actor_index', {
+ default: 0,
+ copy_value: true,
+ exposed: false,
+ condition: condition
+ });
+ }
+
+ function nodeType(node) {
+ if (typeof Group !== 'undefined' && node instanceof Group) {
+ return 'group';
+ }
+ if (typeof Cube !== 'undefined' && node instanceof Cube) {
+ return 'cube';
+ }
+ if (typeof Mesh !== 'undefined' && node instanceof Mesh) {
+ return 'mesh';
+ }
+ if (typeof Locator !== 'undefined' && node instanceof Locator) {
+ return 'locator';
+ }
+ return node && node.type ? String(node.type) : 'unknown';
+ }
+
+ function faceTextures(node) {
+ const values = [];
+ if (!node || !node.faces) {
+ return values;
+ }
+ Object.keys(node.faces).forEach(function (faceKey) {
+ const texture = node.faces[faceKey] && node.faces[faceKey].texture;
+ if (typeof texture === 'string' && texture && values.indexOf(texture) === -1) {
+ values.push(texture);
+ }
+ });
+ return values;
+ }
+
+ function meshTriangleCount(mesh) {
+ let count = 0;
+ if (!mesh || !mesh.faces) {
+ return count;
+ }
+ Object.keys(mesh.faces).forEach(function (key) {
+ const face = mesh.faces[key];
+ const vertices = face && face.vertices ? face.vertices.length : 0;
+ if (vertices >= 3) {
+ count += vertices - 2;
+ }
+ });
+ return count;
+ }
+
+ function collectLiveNodes() {
+ const records = [];
+ let order = 0;
+ function walk(children) {
+ (children || []).forEach(function (node) {
+ const type = nodeType(node);
+ const textureUuids = type === 'cube' || type === 'mesh' ? faceTextures(node) : [];
+ const vertexCount = type === 'cube'
+ ? 24
+ : (type === 'mesh' && node.vertices ? Object.keys(node.vertices).length : 0);
+ const triangleCount = type === 'cube' ? 12 : (type === 'mesh' ? meshTriangleCount(node) : 0);
+ records.push({
+ uuid: node.uuid,
+ parentUuid: node.parent && node.parent.uuid ? node.parent.uuid : null,
+ name: node.name || type,
+ type: type,
+ role: node.cosmiq_role || ROLES.NONE,
+ modelVariant: node.cosmiq_model_branch === 'wide' || node.cosmiq_model_branch === 'slim'
+ ? node.cosmiq_model_branch
+ : '',
+ anchorId: node.cosmiq_anchor && node.cosmiq_anchor !== 'none' ? node.cosmiq_anchor : '',
+ particleId: node.cosmiq_particle_id || '',
+ particleTextureUuid: node.cosmiq_particle_texture || '',
+ effectLocatorId: node.cosmiq_effect_locator_id || '',
+ resourceId: node.cosmiq_resource_id || '',
+ actorIndex: Number(node.cosmiq_actor_index) || 0,
+ playerBone: node.cosmiq_player_bone && node.cosmiq_player_bone !== 'none'
+ ? node.cosmiq_player_bone
+ : '',
+ origin: Array.isArray(node.origin) ? node.origin.slice(0, 3) : null,
+ position: Array.isArray(node.position) ? node.position.slice(0, 3) : null,
+ rotation: Array.isArray(node.rotation) ? node.rotation.slice(0, 3) : null,
+ textureUuids: textureUuids,
+ export: node.export !== false,
+ vertexCount: vertexCount,
+ triangleCount: triangleCount,
+ sourceOrder: order++
+ });
+ if (node.children) {
+ walk(node.children);
+ }
+ });
+ }
+ walk(typeof Outliner !== 'undefined' ? Outliner.root : []);
+ return records;
+ }
+
+ function collectLiveAnimations() {
+ const animations = Blockbench.Animation.all || [];
+ return animations.map(function (animation) {
+ const targetUuids = [];
+ let keyframeCount = 0;
+ Object.keys(animation.animators || {}).forEach(function (animatorKey) {
+ const animator = animation.animators[animatorKey];
+ const keyframes = animator && animator.keyframes ? animator.keyframes : [];
+ if (animatorKey !== 'effects' && keyframes.length > 0 &&
+ targetUuids.indexOf(animator.uuid) === -1) {
+ targetUuids.push(animator.uuid);
+ }
+ keyframeCount += keyframes.length;
+ });
+ return {
+ uuid: animation.uuid,
+ name: animation.name,
+ loopMode: animation.loop,
+ lengthSeconds: animation.length,
+ targetUuids: targetUuids,
+ keyframeCount: keyframeCount,
+ clipId: animation.cosmiq_clip_id || '',
+ triggerKind: animation.cosmiq_trigger_kind || 'none',
+ triggerId: animation.cosmiq_trigger || '',
+ transitionMs: animation.cosmiq_transition_ms,
+ priority: animation.cosmiq_priority,
+ fallback: animation.cosmiq_fallback === true,
+ emoteScope: animation.cosmiq_emote_scope || EMOTE_ANIMATION_SCOPES.NONE,
+ emoteActorIndex: Number(animation.cosmiq_emote_actor_index) || 0,
+ markers: collectTimelineMarkers(animation.markers)
+ };
+ });
+ }
+
+ function collectLiveTextures() {
+ const textures = typeof Texture !== 'undefined' && Texture.all ? Texture.all : [];
+ return textures.map(function (texture) {
+ return {
+ uuid: texture.uuid,
+ name: texture.name || 'texture',
+ width: Number(texture.width) || 0,
+ height: Number(texture.height) || 0,
+ role: texture.cosmiq_role || ROLES.NONE,
+ variantId: texture.cosmiq_variant_id || '',
+ variantBaseUuid: texture.cosmiq_variant_base || ''
+ };
+ });
+ }
+
+ function liveSnapshot() {
+ return {
+ project: {
+ kind: activeCosmiqProjectKind(),
+ id: Project.cosmiq_project_id || 'creator:untitled',
+ savePath: Project.save_path || '',
+ template: Project.cosmiq_template || 'cosmetic_static',
+ compatibilityProfile: Project.cosmiq_compatibility_profile || 'cross_version',
+ slotId: Project.cosmiq_slot || 'cosmiq:slot/body',
+ bodySlots: Project.cosmiq_body_slots,
+ modelVariant: Project.cosmiq_model_variant || 'universal',
+ armWidthMode: normalizeArmWidthMode(Project.cosmiq_arm_width_mode),
+ sourceRevision: Project.cosmiq_source_revision || 1,
+ emoteActorCount: Math.floor(Number(Project.cosmiq_emote_actor_count)) || 1,
+ emoteContainsProps: Project.cosmiq_emote_contains_props === true,
+ emoteTraversable: Project.cosmiq_emote_traversable === true,
+ rigProfile: Project.cosmiq_rig_profile === EMOTE_RIG_PROFILES.PRECISE
+ ? EMOTE_RIG_PROFILES.PRECISE
+ : EMOTE_RIG_PROFILES.SIMPLE,
+ previewCamera: {
+ id: 'thumbnail_camera',
+ transform: {
+ translation: [
+ Number(Project.cosmiq_camera_x),
+ Number(Project.cosmiq_camera_y),
+ Number(Project.cosmiq_camera_z)
+ ],
+ rotation: cosmiqEulerQuaternion([
+ Number(Project.cosmiq_camera_pitch),
+ Number(Project.cosmiq_camera_yaw),
+ Number(Project.cosmiq_camera_roll)
+ ]),
+ scale: [1, 1, 1],
+ pivot: [0, 0, 0]
+ },
+ projection: {
+ kind: 'perspective',
+ verticalFovDegrees: Number(Project.cosmiq_camera_fov),
+ nearPlane: 0.01,
+ farPlane: 64
+ },
+ pose: {
+ clipId: String(Project.cosmiq_camera_clip_id || '').trim() || null,
+ timeMs: Math.max(0, Math.round(Number(Project.cosmiq_camera_time_ms) || 0)),
+ modelVariant: activeCosmiqProjectKind() === PROJECT_KINDS.COSMETIC
+ ? Project.cosmiq_camera_model_variant
+ : null,
+ visiblePropRootIds: String(Project.cosmiq_camera_visible_props || '')
+ .split(',')
+ .map(function (value) { return value.trim(); })
+ .filter(Boolean)
+ .sort()
+ }
+ },
+ emoteLogicSource: Project.cosmiq && typeof Project.cosmiq.logicSource === 'string'
+ ? Project.cosmiq.logicSource
+ : '',
+ emoteLogic: Project.cosmiq && Project.cosmiq.logic && typeof Project.cosmiq.logic === 'object'
+ ? Project.cosmiq.logic
+ : null,
+ resourceReferences: Project.cosmiq && Array.isArray(Project.cosmiq.resourceReferences)
+ ? Project.cosmiq.resourceReferences.slice()
+ : [],
+ timelineCues: Project.cosmiq && Array.isArray(Project.cosmiq.timelineCues)
+ ? Project.cosmiq.timelineCues.slice()
+ : [],
+ settings: Project.cosmiq && Array.isArray(Project.cosmiq.settings)
+ ? cosmiqClone(Project.cosmiq.settings)
+ : [],
+ playerAppearance: Project.cosmiq
+ ? normalizePlayerAppearance(Project.cosmiq.playerAppearance)
+ : null,
+ emoteOutcomeSets: Project.cosmiq && Array.isArray(Project.cosmiq.outcomeSets)
+ ? cosmiqClone(Project.cosmiq.outcomeSets)
+ : []
+ },
+ nodes: collectLiveNodes(),
+ animations: collectLiveAnimations(),
+ textures: collectLiveTextures()
+ };
+ }
+
+ function liveNodeByUuid(uuid) {
+ let found = null;
+ function walk(children) {
+ (children || []).some(function (node) {
+ if (node.uuid === uuid) {
+ found = node;
+ return true;
+ }
+ if (node.children) {
+ walk(node.children);
+ }
+ return found !== null;
+ });
+ }
+ walk(typeof Outliner !== 'undefined' ? Outliner.root : []);
+ return found;
+ }
+
+ function repairPreciseLegHierarchy() {
+ if (typeof Project === 'undefined' || !Project ||
+ Project.cosmiq_rig_profile !== EMOTE_RIG_PROFILES.PRECISE) {
+ return false;
+ }
+ const records = collectLiveNodes();
+ let changed = false;
+ const actorIndices = Array.from(new Set(records.filter(function (record) {
+ return record.role === ROLES.PLAYER_BONE && record.actorIndex > 0;
+ }).map(function (record) {
+ return record.actorIndex;
+ })));
+ actorIndices.forEach(function (actorIndex) {
+ const bones = Object.create(null);
+ records.forEach(function (record) {
+ if (record.role === ROLES.PLAYER_BONE && record.actorIndex === actorIndex) {
+ bones[record.playerBone] = record;
+ }
+ });
+ const torso = bones.torso_lower && liveNodeByUuid(bones.torso_lower.uuid);
+ if (!torso) return;
+ ['left_leg_upper', 'right_leg_upper'].forEach(function (boneId) {
+ const record = bones[boneId];
+ const bone = record && liveNodeByUuid(record.uuid);
+ if (!bone) return;
+ const expectedOrigin = preciseBoneLayout(boneId).origin;
+ if (record.parentUuid !== torso.uuid && typeof bone.addTo === 'function') {
+ bone.addTo(torso);
+ changed = true;
+ }
+ if (!Array.isArray(bone.origin) ||
+ bone.origin.some(function (value, axis) {
+ return Math.abs(Number(value) - expectedOrigin[axis]) > 1e-9;
+ })) {
+ bone.origin = expectedOrigin;
+ changed = true;
+ }
+ });
+ });
+ if (changed) Project.saved = false;
+ return changed;
+ }
+
+ function repairCapeUpperTorsoHierarchy() {
+ if (typeof Project === 'undefined' || !Project) return false;
+ const records = collectLiveNodes();
+ const capeRecord = records.find(function (record) {
+ return record.name === 'Cape/Elytra' && record.role === ROLES.REFERENCE_ROOT;
+ });
+ const precise = Project.cosmiq_rig_profile === EMOTE_RIG_PROFILES.PRECISE;
+ const targetRecord = records.find(function (record) {
+ return record.actorIndex === 1 && (
+ precise
+ ? record.role === ROLES.PLAYER_BONE && record.playerBone === 'torso_upper'
+ : record.role === ROLES.EMOTE_TRANSFORM && record.playerBone === 'upper_body'
+ );
+ });
+ if (!capeRecord || !targetRecord) return false;
+ const cape = liveNodeByUuid(capeRecord.uuid);
+ const target = liveNodeByUuid(targetRecord.uuid);
+ if (!cape || !target || typeof cape.addTo !== 'function') return false;
+ let changed = false;
+ if (capeRecord.parentUuid !== targetRecord.uuid) {
+ cape.addTo(target);
+ changed = true;
+ }
+ if (precise) {
+ const childUsesWorldCoordinates = (cape.children || []).some(function (child) {
+ return Array.isArray(child.origin) && Number(child.origin[1]) > 12;
+ });
+ if (childUsesWorldCoordinates) {
+ function localize(node, root) {
+ ['origin', 'from', 'to', 'position'].forEach(function (field) {
+ if (!Array.isArray(node[field])) return;
+ if (root && field === 'origin') {
+ node[field] = [
+ Number(node[field][0]),
+ 6,
+ Number(node[field][2])
+ ];
+ return;
+ }
+ node[field] = node[field].map(function (value, axis) {
+ return Number(value) - (axis === 1 ? 18 : 0);
+ });
+ });
+ (node.children || []).forEach(function (child) {
+ localize(child, false);
+ });
+ }
+ localize(cape, true);
+ changed = true;
+ }
+ }
+ if (changed) Project.saved = false;
+ return changed;
+ }
+
+ function adoptNameConventions(markDirty) {
+ const projectId = normalizeProjectId(Project.cosmiq_project_id) || 'creator:untitled';
+ let changed = false;
+ (Blockbench.Animation.all || []).forEach(function (animation) {
+ if (!animation.cosmiq_clip_id) {
+ animation.cosmiq_clip_id = runtimeId(projectId, 'animation', animation.uuid);
+ changed = true;
+ }
+ if (!animation.cosmiq_trigger_kind || animation.cosmiq_trigger_kind === 'none') {
+ const parsed = parseAnimationName(animation.name);
+ if (parsed) {
+ animation.cosmiq_trigger_kind = parsed.kind;
+ animation.cosmiq_trigger = parsed.triggerId;
+ if (parsed.triggerId === 'idle') {
+ animation.cosmiq_fallback = true;
+ }
+ changed = true;
+ }
+ }
+ });
+ collectLiveNodes().forEach(function (record) {
+ if (record.role !== ROLES.NONE || !parseParticleMarkerName(record.name)) {
+ return;
+ }
+ const node = liveNodeByUuid(record.uuid);
+ if (!node) {
+ return;
+ }
+ node.cosmiq_role = ROLES.PARTICLE_MARKER;
+ node.cosmiq_particle_id = parseParticleMarkerName(record.name);
+ node.export = false;
+ changed = true;
+ });
+ if (changed && markDirty) {
+ Project.saved = false;
+ }
+ return changed;
+ }
+
+ function refreshMetadata(markDirty) {
+ if (!isCosmiqProject()) {
+ return null;
+ }
+ const wasSaved = Project.saved;
+ if (isCosmiqEmoteProject()) {
+ const emoteResult = buildEmoteSourceMetadata(liveSnapshot());
+ Project.cosmiq = emoteResult.metadata;
+ if (markDirty) {
+ Project.saved = false;
+ } else {
+ Project.saved = wasSaved;
+ }
+ return emoteResult;
+ }
+ if (isCosmiqCapeProject()) {
+ const capeResult = buildCapeSourceMetadata(liveSnapshot());
+ Project.cosmiq = capeResult.metadata;
+ if (markDirty) {
+ Project.saved = false;
+ } else {
+ Project.saved = wasSaved;
+ }
+ return capeResult;
+ }
+ const normalizedAnchors = applyLegacyPlayerAnchorNormalization();
+ adoptNameConventions(markDirty);
+ let snapshot = liveSnapshot();
+ let result = buildSourceMetadata(snapshot);
+ const derivedSlots = bodySlotsFromNodeBindings(result.metadata.nodeBindings);
+ const currentSlots = bodySlotsForProject(snapshot.project);
+ const derivedVariant = result.metadata.cosmetic.modelVariant;
+ if ((derivedSlots.length > 0 && JSON.stringify(derivedSlots) !== JSON.stringify(currentSlots))
+ || Project.cosmiq_model_variant !== derivedVariant) {
+ if (derivedSlots.length > 0) {
+ Project.cosmiq_slot = derivedSlots[0];
+ Project.cosmiq_body_slots = derivedSlots.join(', ');
+ }
+ Project.cosmiq_model_variant = derivedVariant;
+ snapshot = liveSnapshot();
+ result = buildSourceMetadata(snapshot);
+ }
+ Project.cosmiq = result.metadata;
+ if (markDirty || normalizedAnchors.length > 0) {
+ Project.saved = false;
+ } else {
+ Project.saved = wasSaved;
+ }
+ return result;
+ }
+
+ function restoreAnimationPropertiesFromMetadata() {
+ if (!isCosmiqProject() || !Project.cosmiq || !Project.cosmiq.animationBindings) {
+ return;
+ }
+ const bindings = Project.cosmiq.animationBindings;
+ (Blockbench.Animation.all || []).forEach(function (animation) {
+ const binding = bindings[animation.uuid];
+ if (!binding) {
+ return;
+ }
+ animation.cosmiq_clip_id = binding.id || animation.cosmiq_clip_id;
+ if (binding.scope === EMOTE_ANIMATION_SCOPES.SCENE || binding.scope === EMOTE_ANIMATION_SCOPES.ACTOR) {
+ animation.cosmiq_emote_scope = binding.scope;
+ animation.cosmiq_emote_actor_index = binding.scope === EMOTE_ANIMATION_SCOPES.ACTOR
+ ? (Math.floor(Number(binding.actorIndex)) || 0)
+ : 0;
+ return;
+ }
+ if (binding.trigger) {
+ animation.cosmiq_trigger_kind = binding.trigger.kind || 'none';
+ animation.cosmiq_trigger = binding.trigger.id || '';
+ }
+ animation.cosmiq_transition_ms = binding.transitionMs == null
+ ? animation.cosmiq_transition_ms
+ : binding.transitionMs;
+ animation.cosmiq_priority = binding.priority == null
+ ? animation.cosmiq_priority
+ : binding.priority;
+ animation.cosmiq_fallback = binding.fallback === true;
+ });
+ }
+
+ function registerListener(eventName, callback) {
+ const existing = listeners.find(function (listener) {
+ return listener.eventName === eventName && listener.callback === callback;
+ });
+ if (existing) {
+ return existing.handle;
+ }
+ const handle = Blockbench.on(eventName, callback);
+ listeners.push({
+ eventName: eventName,
+ callback: callback,
+ handle: handle
+ });
+ return handle;
+ }
+
+ function onSaveProject(event) {
+ if (!isCosmiqProject() || !event || !event.model) {
+ return;
+ }
+ const result = refreshMetadata(false);
+ if (result) {
+ event.model.cosmiq_project_kind = activeCosmiqProjectKind();
+ if (result.metadata.kind === PROJECT_KINDS.EMOTE) {
+ event.model.cosmiq_emote_actor_count = result.metadata.emote.actorCount;
+ event.model.cosmiq_emote_contains_props = result.metadata.emote.containsProps;
+ event.model.cosmiq_emote_traversable = result.metadata.emote.traversable;
+ } else if (result.metadata.kind === PROJECT_KINDS.COSMETIC) {
+ event.model.cosmiq_slot = result.metadata.cosmetic.slotId;
+ event.model.cosmiq_body_slots = result.metadata.cosmetic.bodySlots.join(', ');
+ }
+ event.model.cosmiq = JSON.parse(JSON.stringify(result.metadata));
+ }
+ }
+
+ function onLoadProject() {
+ if (!isCosmiqProject()) {
+ return;
+ }
+ Project.cosmiq_project_kind = projectKindFromMetadata(
+ Project.cosmiq,
+ Project.cosmiq_project_kind
+ );
+ const repairedPreciseLegs = repairPreciseLegHierarchy();
+ const repairedCapeParent = repairCapeUpperTorsoHierarchy();
+ restoreAnimationPropertiesFromMetadata();
+ if (repairedPreciseLegs || repairedCapeParent) {
+ refreshMetadata(true);
+ }
+ }
+
+ function anchorOptions() {
+ const options = {};
+ ANCHORS.forEach(function (anchor) {
+ options[anchor.id] = anchor.label;
+ });
+ return options;
+ }
+
+ function selectedAnimation() {
+ return Blockbench.Animation.selected || null;
+ }
+
+ const PLAYER_APPEARANCE_PARTS = Object.freeze([
+ {id: 'body', label: 'Body', group: 'torso', referenceLabel: 'Torso'},
+ {id: 'head', label: 'Head', group: 'head', referenceLabel: 'Head'},
+ {id: 'left_arm', label: 'Left Arm', group: 'arms', referenceLabel: 'L Arm'},
+ {id: 'left_leg', label: 'Left Leg', group: 'legs', referenceLabel: 'L Leg'},
+ {id: 'right_arm', label: 'Right Arm', group: 'arms', referenceLabel: 'R Arm'},
+ {id: 'right_leg', label: 'Right Leg', group: 'legs', referenceLabel: 'R Leg'}
+ ]);
+
+ function normalizePlayerAppearance(value) {
+ if (!value || typeof value !== 'object' || !Array.isArray(value.occlusions)) {
+ return null;
+ }
+ const byPart = Object.create(null);
+ value.occlusions.forEach(function (raw) {
+ const definition = PLAYER_APPEARANCE_PARTS.find(function (part) {
+ return raw && raw.part === part.id;
+ });
+ if (!definition || !Array.isArray(raw.layers)) return;
+ const layers = ['base', 'outer'].filter(function (layer) {
+ return raw.layers.indexOf(layer) !== -1;
+ });
+ if (layers.length) byPart[definition.id] = layers;
+ });
+ const occlusions = PLAYER_APPEARANCE_PARTS.filter(function (part) {
+ return !!byPart[part.id];
+ }).map(function (part) {
+ return {part: part.id, layers: byPart[part.id].slice()};
+ });
+ return occlusions.length ? {occlusions: occlusions} : null;
+ }
+
+ function selectedOutlinerNodes() {
+ const selected = typeof Outliner !== 'undefined' && Array.isArray(Outliner.selected)
+ ? Outliner.selected.slice()
+ : [];
+ return selected.filter(function (node) {
+ return !selected.some(function (possibleParent) {
+ return node !== possibleParent && node.parent === possibleParent;
+ });
+ });
+ }
+
+ const PLAYER_REFERENCE_PARTS = Object.freeze({
+ head: {label: 'Head', anchorId: 'cosmiq:player/head', from: [-4, 24, -4], to: [4, 32, 4], origin: [0, 24, 0], baseUv: [0, 0], outerUv: [32, 0], width: 8, height: 8, depth: 8, outerInflate: 0.5},
+ torso: {label: 'Torso', anchorId: 'cosmiq:player/torso', from: [-4, 12, -2], to: [4, 24, 2], origin: [0, 24, 0], baseUv: [16, 16], outerUv: [16, 32], width: 8, height: 12, depth: 4, outerInflate: 0.25},
+ leftArm: {label: 'L Arm', anchorId: 'cosmiq:player/left_arm', from: [4, 12, -2], to: [8, 24, 2], origin: [5, 22, 0], baseUv: [32, 48], outerUv: [48, 48], width: 4, height: 12, depth: 4, outerInflate: 0.25},
+ rightArm: {label: 'R Arm', anchorId: 'cosmiq:player/right_arm', from: [-8, 12, -2], to: [-4, 24, 2], origin: [-5, 22, 0], baseUv: [40, 16], outerUv: [40, 32], width: 4, height: 12, depth: 4, outerInflate: 0.25},
+ leftLeg: {label: 'L Leg', anchorId: 'cosmiq:player/left_leg', from: [0, 0, -2], to: [4, 12, 2], origin: [2, 12, 0], baseUv: [16, 48], outerUv: [0, 48], width: 4, height: 12, depth: 4, outerInflate: 0.25},
+ rightLeg: {label: 'R Leg', anchorId: 'cosmiq:player/right_leg', from: [-4, 0, -2], to: [0, 12, 2], origin: [-2, 12, 0], baseUv: [0, 16], outerUv: [0, 32], width: 4, height: 12, depth: 4, outerInflate: 0.25}
+ });
+
+ function validateReferenceSkinFile(file, label) {
+ if (!file) {
+ return null;
+ }
+ const source = typeof file === 'object' ? file.content : file;
+ if (typeof source !== 'string' || source.length > 1_400_000) {
+ throw new Error(label + ' must be a PNG no larger than 1 MiB.');
+ }
+ const match = /^data:image\/png;base64,([A-Za-z0-9+/]+={0,2})$/.exec(source);
+ if (!match || typeof atob !== 'function') {
+ throw new Error(label + ' must be an embedded PNG image.');
+ }
+ let bytes;
+ try {
+ bytes = atob(match[1]);
+ } catch (error) {
+ throw new Error(label + ' contains invalid PNG base64 data.');
+ }
+ const signature = [0x89, 0x50, 0x4e, 0x47, 0x0d, 0x0a, 0x1a, 0x0a];
+ if (bytes.length < 24 || !signature.every(function (value, index) {
+ return bytes.charCodeAt(index) === value;
+ })) {
+ throw new Error(label + ' does not contain a valid PNG header.');
+ }
+ function uint32(offset) {
+ return ((bytes.charCodeAt(offset) << 24) >>> 0)
+ + (bytes.charCodeAt(offset + 1) << 16)
+ + (bytes.charCodeAt(offset + 2) << 8)
+ + bytes.charCodeAt(offset + 3);
+ }
+ if (uint32(16) !== 64 || uint32(20) !== 64) {
+ throw new Error(label + ' must be exactly 64 x 64 pixels.');
+ }
+ return source;
+ }
+
+ function fillReferenceSkinBox(context, u, v, width, height, depth, color) {
+ context.fillStyle = color;
+ context.fillRect(u, v, (depth * 2) + (width * 2), depth + height);
+ }
+
+ function defaultReferenceSkinDataUrl(modelVariant) {
+ const canvas = document.createElement('canvas');
+ canvas.width = 64;
+ canvas.height = 64;
+ const context = canvas.getContext('2d');
+ context.clearRect(0, 0, 64, 64);
+ const base = modelVariant === 'slim' ? '#9aa4b5' : '#768091';
+ const overlay = modelVariant === 'slim' ? 'rgba(70, 78, 92, 0.72)' : 'rgba(50, 58, 70, 0.72)';
+ Object.keys(PLAYER_REFERENCE_PARTS).forEach(function (key) {
+ const part = PLAYER_REFERENCE_PARTS[key];
+ const width = modelVariant === 'slim' && (key === 'leftArm' || key === 'rightArm')
+ ? 3
+ : part.width;
+ fillReferenceSkinBox(context, part.baseUv[0], part.baseUv[1], width, part.height, part.depth, base);
+ fillReferenceSkinBox(context, part.outerUv[0], part.outerUv[1], width, part.height, part.depth, overlay);
+ });
+ return canvas.toDataURL('image/png');
+ }
+
+ function referenceTexture(modelVariant, uploadedSource, nameOverride) {
+ try {
+ const texture = new Texture({
+ name: nameOverride || ('cosmiq_reference_' + modelVariant + '.png'),
+ uv_width: 64,
+ uv_height: 64
+ });
+ texture.cosmiq_role = ROLES.REFERENCE;
+ texture.fromDataURL(uploadedSource || defaultReferenceSkinDataUrl(modelVariant)).add();
+ return texture;
+ } catch (error) {
+ console.warn('[Cosmiq Cosmetic Creator] Could not create neutral reference texture.', error);
+ return null;
+ }
+ }
+
+ function defaultCapeTextureDataUrl() {
+ const canvas = document.createElement('canvas');
+ canvas.width = 64;
+ canvas.height = 32;
+ const context = canvas.getContext('2d');
+ context.fillStyle = '#c026d3';
+ context.fillRect(0, 0, 64, 32);
+ context.fillStyle = '#701a75';
+ context.fillRect(1, 1, 62, 30);
+ context.fillStyle = '#ffffff';
+ context.font = 'bold 7px sans-serif';
+ context.textAlign = 'center';
+ context.textBaseline = 'middle';
+ if (typeof context.fillText === 'function') {
+ context.fillText('Change File to', 32, 12);
+ context.fillText('your cape', 32, 21);
+ }
+ return canvas.toDataURL('image/png');
+ }
+
+ function capeTemplateTexture() {
+ const texture = new Texture({
+ name: 'Change File to your cape.png',
+ uv_width: 64,
+ uv_height: 32
+ });
+ texture.fromDataURL(defaultCapeTextureDataUrl()).add();
+ return texture;
+ }
+
+ function playerSkinFaceUvs(u, v, width, height, depth) {
+ return {
+ east: [u, v + depth, u + depth, v + depth + height],
+ north: [u + depth, v + depth, u + depth + width, v + depth + height],
+ west: [u + depth + width, v + depth, u + (depth * 2) + width, v + depth + height],
+ south: [u + (depth * 2) + width, v + depth, u + (depth * 2) + (width * 2), v + depth + height],
+ up: [u + depth, v, u + depth + width, v + depth],
+ down: [u + depth + width, v, u + depth + (width * 2), v + depth]
+ };
+ }
+
+ function createReferenceCube(parent, texture, options) {
+ const cube = new Cube({
+ name: options.name,
+ from: options.from,
+ to: options.to,
+ origin: options.origin,
+ inflate: options.inflate || 0,
+ box_uv: false,
+ export: false
+ }).addTo(parent).init();
+ cube.cosmiq_role = ROLES.REFERENCE;
+ cube.export = false;
+ cube.locked = true;
+ applyBoxTextureUvs(cube, texture, options);
+ return cube;
+ }
+
+ function applyBoxTextureUvs(cube, texture, options) {
+ if (!texture) return cube;
+ cube.applyTexture(texture, true);
+ const faceUvs = playerSkinFaceUvs(
+ options.uv[0], options.uv[1], options.width, options.height, options.depth
+ );
+ Object.keys(faceUvs).forEach(function (faceName) {
+ if (cube.faces && cube.faces[faceName]) {
+ cube.faces[faceName].uv = faceUvs[faceName].slice();
+ }
+ });
+ return cube;
+ }
+
+ function createStructureGroup(parent, name) {
+ const group = new Group({
+ name: name,
+ origin: [0, 0, 0],
+ rotation: [0, 0, 0],
+ export: false
+ }).addTo(parent).init();
+ group.cosmiq_role = ROLES.STRUCTURE;
+ group.export = false;
+ return group;
+ }
+
+ function createAnchorGroup(parent, anchorId, name) {
+ const anchor = anchorById[anchorId];
+ const group = new Group({
+ name: name,
+ origin: anchor.origin.slice(),
+ rotation: [0, 0, 0],
+ export: false
+ }).addTo(parent).init();
+ group.cosmiq_role = ROLES.ANCHOR;
+ group.cosmiq_anchor = anchorId;
+ group.export = false;
+ return group;
+ }
+
+ function createPlayerReferencePart(parent, texture, modelVariant, partKey, createdElements, options) {
+ const part = PLAYER_REFERENCE_PARTS[partKey];
+ const settings = options || {};
+ const variantLabel = modelVariant === 'slim' ? 'Slim' : 'Wide';
+ const namePrefix = settings.namePrefix == null ? variantLabel + ' ' : String(settings.namePrefix);
+ const partLabel = settings.partLabel || part.label;
+ const offsetX = Number(settings.offsetX) || 0;
+ const isSlimArm = modelVariant === 'slim' && (partKey === 'leftArm' || partKey === 'rightArm');
+ const width = isSlimArm ? 3 : part.width;
+ const from = part.from.slice();
+ const to = part.to.slice();
+ if (isSlimArm) {
+ if (partKey === 'leftArm') to[0] = from[0] + 3;
+ else from[0] = to[0] - 3;
+ }
+ from[0] += offsetX;
+ to[0] += offsetX;
+ const origin = part.origin.slice();
+ origin[0] += offsetX;
+ const base = createReferenceCube(parent, texture, {
+ name: namePrefix + partLabel + ' Base Skin',
+ from: from,
+ to: to,
+ origin: origin,
+ uv: part.baseUv,
+ width: width,
+ height: part.height,
+ depth: part.depth
+ });
+ const outer = createReferenceCube(parent, texture, {
+ name: namePrefix + partLabel + ' Outer Layer',
+ from: from,
+ to: to,
+ origin: origin,
+ uv: part.outerUv,
+ width: width,
+ height: part.height,
+ depth: part.depth,
+ inflate: part.outerInflate
+ });
+ if (settings.actorIndex) {
+ const semanticBone = settings.semanticBone || 'none';
+ [base, outer].forEach(function (cube) {
+ cube.cosmiq_actor_index = settings.actorIndex;
+ cube.cosmiq_player_bone = semanticBone;
+ });
+ }
+ createdElements.push(base, outer);
+ return {base: base, outer: outer};
+ }
+
+ function createVariantHierarchy(
+ exportRoot,
+ modelVariant,
+ visible,
+ selectedAnchorIds,
+ texture,
+ createdElements,
+ includeReference
+ ) {
+ const label = modelVariant === 'slim' ? 'Slim' : 'Wide';
+ const variantRoot = new Group({
+ name: label,
+ origin: [0, 0, 0],
+ rotation: [0, 0, 0],
+ export: false,
+ visibility: visible === true
+ }).addTo(exportRoot).init();
+ variantRoot.cosmiq_role = ROLES.MODEL_VARIANT;
+ variantRoot.cosmiq_model_branch = modelVariant;
+ variantRoot.export = false;
+
+ const upper = createStructureGroup(variantRoot, 'Upper');
+ const lower = createStructureGroup(variantRoot, 'Lower');
+ const head = createAnchorGroup(upper, 'cosmiq:player/head', 'Head');
+ const torso = createAnchorGroup(upper, 'cosmiq:player/torso', 'Torso');
+ const leftArm = createAnchorGroup(torso, 'cosmiq:player/left_arm', 'L Arm');
+ const rightArm = createAnchorGroup(torso, 'cosmiq:player/right_arm', 'R Arm');
+ const leftLeg = createAnchorGroup(lower, 'cosmiq:player/left_leg', 'L Leg');
+ const rightLeg = createAnchorGroup(lower, 'cosmiq:player/right_leg', 'R Leg');
+ const anchorGroups = {
+ 'cosmiq:player/head': head,
+ 'cosmiq:player/torso': torso,
+ 'cosmiq:player/left_arm': leftArm,
+ 'cosmiq:player/right_arm': rightArm,
+ 'cosmiq:player/left_leg': leftLeg,
+ 'cosmiq:player/right_leg': rightLeg
+ };
+
+ if (includeReference) {
+ createPlayerReferencePart(head, texture, modelVariant, 'head', createdElements);
+ createPlayerReferencePart(torso, texture, modelVariant, 'torso', createdElements);
+ createPlayerReferencePart(leftArm, texture, modelVariant, 'leftArm', createdElements);
+ createPlayerReferencePart(rightArm, texture, modelVariant, 'rightArm', createdElements);
+ createPlayerReferencePart(leftLeg, texture, modelVariant, 'leftLeg', createdElements);
+ createPlayerReferencePart(rightLeg, texture, modelVariant, 'rightLeg', createdElements);
+ }
+
+ const modelHereGroups = selectedAnchorIds.map(function (anchorId) {
+ const anchor = anchorById[anchorId];
+ const parent = anchorGroups[anchorId];
+ const modelHere = new Group({
+ name: 'Model Here - ' + label + ' ' + anchor.label,
+ origin: parent.origin ? parent.origin.slice() : anchor.origin.slice(),
+ rotation: [0, 0, 0],
+ export: true
+ }).addTo(parent).init();
+ modelHere.cosmiq_role = ROLES.AUTHORING;
+ return modelHere;
+ });
+ return {
+ root: variantRoot,
+ upper: upper,
+ lower: lower,
+ anchorGroups: anchorGroups,
+ modelHereGroups: modelHereGroups
+ };
+ }
+
+ function createAnchorHierarchy(selectedAnchorIds, options) {
+ const settings = options || {};
+ const createdElements = settings.createdElements || [];
+ const createdTextures = settings.createdTextures || [];
+ const includeReference = settings.includeReference === true;
+ const exportRoot = new Group({
+ name: 'Cosmiq Export',
+ origin: [0, 0, 0],
+ rotation: [0, 0, 0]
+ }).addTo('root').init();
+ exportRoot.cosmiq_role = ROLES.EXPORT_ROOT;
+
+ const selected = uniqueValues((selectedAnchorIds || []).filter(isKnownAnchor).map(canonicalAnchorId));
+ if (selected.length === 0) {
+ selected.push('cosmiq:player/torso');
+ }
+ const wideTexture = includeReference ? referenceTexture('wide', settings.wideSkin || null) : null;
+ const slimTexture = includeReference ? referenceTexture('slim', settings.slimSkin || null) : null;
+ if (wideTexture) createdTextures.push(wideTexture);
+ if (slimTexture) createdTextures.push(slimTexture);
+ const showSlim = settings.initialVisibleVariant === 'slim';
+ const wide = createVariantHierarchy(
+ exportRoot, 'wide', !showSlim, selected, wideTexture,
+ createdElements, includeReference
+ );
+ const slim = createVariantHierarchy(
+ exportRoot, 'slim', showSlim, selected, slimTexture,
+ createdElements, includeReference
+ );
+ const modelHereGroups = wide.modelHereGroups.concat(slim.modelHereGroups);
+ return {
+ exportRoot: exportRoot,
+ wide: wide,
+ slim: slim,
+ anchorGroups: {wide: wide.anchorGroups, slim: slim.anchorGroups},
+ modelHere: modelHereGroups[0],
+ modelHereGroups: modelHereGroups
+ };
+ }
+
+ function createCapeTemplateGeometry(hierarchy, createdElements, createdTextures) {
+ const texture = capeTemplateTexture();
+ const capes = [];
+ const elytraWings = [];
+ createdTextures.push(texture);
+ [
+ {label: 'Wide', branch: hierarchy.wide},
+ {label: 'Slim', branch: hierarchy.slim}
+ ].forEach(function (entry) {
+ const modelHere = entry.branch.modelHereGroups[0];
+ modelHere.name = 'Change File to your cape - ' + entry.label;
+ const cape = new Cube({
+ name: 'Cape - Change File to your cape',
+ from: [-5, 8, 2],
+ to: [5, 24, 3],
+ origin: [0, 24, 2],
+ box_uv: false,
+ export: true
+ }).addTo(modelHere).init();
+ applyBoxTextureUvs(cape, texture, {uv: [0, 0], width: 10, height: 16, depth: 1});
+ capes.push(cape);
+ createdElements.push(cape);
+
+ const elytraRoot = new Group({
+ name: 'Elytra Preview - toggle visibility',
+ origin: [0, 24, 2],
+ rotation: [0, 0, 0],
+ visibility: false,
+ export: false
+ }).addTo(entry.branch.anchorGroups['cosmiq:player/torso']).init();
+ elytraRoot.cosmiq_role = ROLES.REFERENCE;
+ elytraRoot.export = false;
+ elytraRoot.locked = true;
+ [
+ {name: 'Left Elytra - uses cape file', from: [0, 4, 2], to: [10, 24, 3], rotation: -15},
+ {name: 'Right Elytra - uses cape file', from: [-10, 4, 2], to: [0, 24, 3], rotation: 15}
+ ].forEach(function (wingOptions) {
+ const wing = createReferenceCube(elytraRoot, texture, {
+ name: wingOptions.name,
+ from: wingOptions.from,
+ to: wingOptions.to,
+ origin: [0, 24, 2],
+ uv: [0, 0],
+ width: 10,
+ height: 20,
+ depth: 1
+ });
+ wing.rotation = [0, 0, wingOptions.rotation];
+ elytraWings.push(wing);
+ createdElements.push(wing);
+ });
+ });
+ return {texture: texture, capes: capes, elytraWings: elytraWings};
+ }
+
+ function emoteActorOffset(actorIndex) {
+ const index = Math.floor(Number(actorIndex));
+ return index >= 1 && index <= EMOTE_ACTOR_OFFSETS.length
+ ? EMOTE_ACTOR_OFFSETS[index - 1]
+ : 0;
+ }
+
+ function createEmoteBoneGroup(parent, actorIndex, boneId, label, origin) {
+ const group = new Group({
+ name: actorIndex + ' ' + label,
+ origin: origin.slice(),
+ rotation: [0, 0, 0],
+ export: false
+ }).addTo(parent).init();
+ group.cosmiq_role = ROLES.PLAYER_BONE;
+ group.cosmiq_actor_index = actorIndex;
+ group.cosmiq_player_bone = boneId;
+ group.export = false;
+ return group;
+ }
+
+ function createEmoteActorRig(sceneRoot, actorIndex, texture, createdElements) {
+ const offsetX = emoteActorOffset(actorIndex);
+ const actorRoot = new Group({
+ name: actorIndex + ' Actor',
+ origin: [offsetX, 0, 0],
+ rotation: [0, 0, 0],
+ export: false
+ }).addTo(sceneRoot).init();
+ actorRoot.cosmiq_role = ROLES.EMOTE_ACTOR;
+ actorRoot.cosmiq_actor_index = actorIndex;
+ actorRoot.export = false;
+
+ const rootBone = createEmoteBoneGroup(
+ actorRoot, actorIndex, 'root', 'Root', [offsetX, 0, 0]
+ );
+ const body = createEmoteBoneGroup(
+ rootBone, actorIndex, 'body', 'Body', [offsetX, 24, 0]
+ );
+ const head = createEmoteBoneGroup(
+ body, actorIndex, 'head', 'Head', [offsetX, 24, 0]
+ );
+ const leftArm = createEmoteBoneGroup(
+ body, actorIndex, 'left_arm', 'Left Arm', [offsetX + 5, 22, 0]
+ );
+ const rightArm = createEmoteBoneGroup(
+ body, actorIndex, 'right_arm', 'Right Arm', [offsetX - 5, 22, 0]
+ );
+ const leftLeg = createEmoteBoneGroup(
+ rootBone, actorIndex, 'left_leg', 'Left Leg', [offsetX + 2, 12, 0]
+ );
+ const rightLeg = createEmoteBoneGroup(
+ rootBone, actorIndex, 'right_leg', 'Right Leg', [offsetX - 2, 12, 0]
+ );
+ const bones = {
+ root: rootBone,
+ body: body,
+ head: head,
+ left_arm: leftArm,
+ right_arm: rightArm,
+ left_leg: leftLeg,
+ right_leg: rightLeg
+ };
+ const partTargets = [
+ {partKey: 'head', boneId: 'head', label: 'Head'},
+ {partKey: 'torso', boneId: 'body', label: 'Body'},
+ {partKey: 'leftArm', boneId: 'left_arm', label: 'Left Arm'},
+ {partKey: 'rightArm', boneId: 'right_arm', label: 'Right Arm'},
+ {partKey: 'leftLeg', boneId: 'left_leg', label: 'Left Leg'},
+ {partKey: 'rightLeg', boneId: 'right_leg', label: 'Right Leg'}
+ ];
+ partTargets.forEach(function (target) {
+ createPlayerReferencePart(
+ bones[target.boneId],
+ texture,
+ 'wide',
+ target.partKey,
+ createdElements,
+ {
+ namePrefix: actorIndex + ' ',
+ partLabel: target.label,
+ offsetX: offsetX,
+ actorIndex: actorIndex,
+ semanticBone: target.boneId
+ }
+ );
+ });
+ return {
+ index: actorIndex,
+ offsetX: offsetX,
+ root: actorRoot,
+ rootBone: rootBone,
+ bones: bones
+ };
+ }
+
+ function createEmoteSceneHierarchy(actorCount, containsProps, options) {
+ const settings = options || {};
+ const createdElements = settings.createdElements || [];
+ const createdTextures = settings.createdTextures || [];
+ const count = Math.max(
+ EMOTE_ACTOR_MIN,
+ Math.min(EMOTE_ACTOR_MAX, Math.floor(Number(actorCount)) || EMOTE_ACTOR_MIN)
+ );
+ const sceneRoot = new Group({
+ name: 'Emote Scene',
+ origin: [0, 0, 0],
+ rotation: [0, 0, 0],
+ export: false
+ }).addTo('root').init();
+ sceneRoot.cosmiq_role = ROLES.EMOTE_SCENE;
+ sceneRoot.export = false;
+
+ const texture = referenceTexture(
+ 'wide', settings.wideSkin || null, 'cosmiq_emote_wide_reference.png'
+ );
+ if (texture) {
+ createdTextures.push(texture);
+ }
+ const actors = [];
+ for (let actorIndex = 1; actorIndex <= count; actorIndex++) {
+ actors.push(createEmoteActorRig(sceneRoot, actorIndex, texture, createdElements));
+ }
+
+ let propsRoot = null;
+ if (containsProps === true) {
+ propsRoot = new Group({
+ name: 'Props',
+ origin: [0, 0, 0],
+ rotation: [0, 0, 0],
+ export: false
+ }).addTo(sceneRoot).init();
+ propsRoot.cosmiq_role = ROLES.PROPS_ROOT;
+ propsRoot.export = false;
+ }
+ return {
+ root: sceneRoot,
+ actors: actors,
+ propsRoot: propsRoot,
+ texture: texture
+ };
+ }
+
+ function addNeutralAnimationTrack(animation, target, lengthSeconds) {
+ const animator = animation.getBoneAnimator(target);
+ if (!animator) {
+ return;
+ }
+ animator.addKeyframe({
+ channel: 'rotation',
+ time: 0,
+ interpolation: 'linear',
+ data_points: [{x: 0, y: 0, z: 0}]
+ });
+ animator.addKeyframe({
+ channel: 'rotation',
+ time: lengthSeconds,
+ interpolation: 'linear',
+ data_points: [{x: 0, y: 0, z: 0}]
+ });
+ }
+
+ function createBoundAnimation(target, trigger, options) {
+ const prefix = trigger.kind === 'player_event' ? 'event.' : 'state.';
+ const animation = new Blockbench.Animation({
+ name: prefix + trigger.id,
+ loop: options.loop ? 'loop' : 'once',
+ length: options.lengthSeconds,
+ snapping: 20
+ }).add(false);
+ animation.cosmiq_clip_id = runtimeId(
+ normalizeProjectId(Project.cosmiq_project_id) || 'creator:untitled',
+ 'animation',
+ animation.uuid
+ );
+ animation.cosmiq_trigger_kind = trigger.kind;
+ animation.cosmiq_trigger = trigger.id;
+ animation.cosmiq_transition_ms = options.transitionMs;
+ animation.cosmiq_priority = options.priority;
+ animation.cosmiq_fallback = options.fallback === true || trigger.id === 'idle';
+
+ if (target) {
+ addNeutralAnimationTrack(animation, target, options.lengthSeconds);
+ }
+ return animation;
+ }
+
+ function createEmptyStateAnimations() {
+ return TRIGGERS.filter(function (trigger) {
+ return trigger.kind === 'player_state' || trigger.id === 'attack' || trigger.id === 'hurt';
+ }).map(function (trigger) {
+ return createBoundAnimation(null, trigger, {
+ lengthSeconds: 2,
+ transitionMs: 150,
+ priority: 100,
+ fallback: trigger.id === 'idle',
+ loop: trigger.defaultLoop
+ });
+ });
+ }
+
+ function createEmptyEmoteAnimation(name, scope, actorIndex) {
+ const animation = new Blockbench.Animation({
+ name: name,
+ loop: 'once',
+ length: 2,
+ snapping: 20
+ }).add(false);
+ animation.cosmiq_clip_id = runtimeId(
+ normalizeProjectId(Project.cosmiq_project_id) || 'creator:untitled_emote',
+ 'animation',
+ animation.uuid
+ );
+ animation.cosmiq_trigger_kind = 'none';
+ animation.cosmiq_trigger = '';
+ animation.cosmiq_transition_ms = 150;
+ animation.cosmiq_priority = 100;
+ animation.cosmiq_fallback = false;
+ animation.cosmiq_emote_scope = scope;
+ animation.cosmiq_emote_actor_index = scope === EMOTE_ANIMATION_SCOPES.ACTOR
+ ? Math.floor(Number(actorIndex))
+ : 0;
+ return animation;
+ }
+
+ function createEmptyEmoteAnimations(actorCount) {
+ const count = Math.max(
+ EMOTE_ACTOR_MIN,
+ Math.min(EMOTE_ACTOR_MAX, Math.floor(Number(actorCount)) || EMOTE_ACTOR_MIN)
+ );
+ const animations = [createEmptyEmoteAnimation(
+ 'scene.main', EMOTE_ANIMATION_SCOPES.SCENE, 0
+ )];
+ for (let actorIndex = 1; actorIndex <= count; actorIndex++) {
+ animations.push(createEmptyEmoteAnimation(
+ 'actor.' + actorIndex + '.main',
+ EMOTE_ANIMATION_SCOPES.ACTOR,
+ actorIndex
+ ));
+ }
+ return animations;
+ }
+
+ function createEmoteProject(value) {
+ const draft = mergeEmoteDraft(value);
+ return launchBaseTemplateCreation(PROJECT_KINDS.EMOTE, {
+ name: draft.creation_name,
+ visibleBody: 'wide',
+ rigProfile: draft.rig_profile
+ });
+ }
+
+ function createCapeTemplateProject() {
+ return startBaseTemplateProject(PROJECT_KINDS.CAPE, defaultCapeTemplateDraft());
+ }
+
+ function launchBaseTemplateCreation(kind, settings) {
+ if (activeDialog && typeof activeDialog.hide === 'function') activeDialog.hide();
+ startBaseTemplateProject(kind, settings).catch(function (error) {
+ Blockbench.showMessageBox({
+ title: 'Could Not Open Cosmiq Base Model',
+ message: error && error.message
+ ? error.message
+ : 'CosmiqPlayerTemplate.bbmodel could not be opened.',
+ buttons: ['OK']
+ });
+ });
+ return true;
+ }
+
+ function showCreatorTypeDialog() {
+ if (activeDialog) {
+ activeDialog.delete();
+ }
+ installCreatorStyles();
+ let selectorDialog = null;
+ selectorDialog = new Dialog('cosmiq_creation_type', {
+ title: 'New Cosmiq Creation',
+ width: 720,
+ buttons: ['Continue'],
+ confirmIndex: 0,
+ cancelIndex: -1,
+ component: creationTypeComponent(),
+ onConfirm: function () {
+ const choice = selectorDialog.content_vue && selectorDialog.content_vue.selected;
+ if (choice !== 'cosmetic' && choice !== 'emote') {
+ Blockbench.showQuickMessage('Choose Accessories or Emotes to continue.');
+ return false;
+ }
+ setTimeout(function () {
+ if (choice === 'cosmetic') {
+ showCreationNameDialog(PROJECT_KINDS.COSMETIC, defaultSimpleCosmeticDraft());
+ } else {
+ showCreationNameDialog(PROJECT_KINDS.EMOTE, defaultEmoteDraft());
+ }
+ }, 0);
+ return true;
+ }
+ });
+ activeDialog = selectorDialog;
+ activeDialog.show();
+ }
+
+ function showCreationNameDialog(kind, value) {
+ if (activeDialog) activeDialog.delete();
+ installCreatorStyles();
+ const flowSteps = kind === PROJECT_KINDS.EMOTE
+ ? EMOTE_FLOW_STEPS
+ : kind === PROJECT_KINDS.CAPE ? CAPE_FLOW_STEPS : COSMETIC_FLOW_STEPS;
+ const draft = kind === PROJECT_KINDS.EMOTE
+ ? mergeEmoteDraft(value)
+ : kind === PROJECT_KINDS.CAPE
+ ? Object.assign(defaultCapeTemplateDraft(), value || {})
+ : mergeSimpleCosmeticDraft(value);
+ const category = creationCategoryLabel(kind);
+ const lines = [];
+ if (kind === PROJECT_KINDS.CAPE) {
+ lines.push(
+ '[COMING SOON]' +
+ '
Local Cape Authoring Only
' +
+ '
You can name and create the .bbmodel, but export and upload remain disabled.
'
+ );
+ }
+ lines.push(wizardPageHeading(
+ flowSteps,
+ 1,
+ 'Name Your ' + category,
+ 'The Blockbench project will open as “' + escapeHtml(draft.creation_name || 'Your Name') +
+ ' ' + category + '”.'
+ ));
+ activeDialog = new Dialog('cosmiq_creation_name', {
+ title: (kind === PROJECT_KINDS.CAPE ? '[COMING SOON] ' : '') + category + ' Name',
+ width: 620,
+ lines: lines,
+ form: {
+ creation_name: {
+ label: category + ' Name',
+ description: 'This becomes “{name} ' + category + '” in Blockbench.',
+ type: 'text',
+ value: draft.creation_name || '',
+ placeholder: kind === PROJECT_KINDS.EMOTE
+ ? 'Victory Dance'
+ : kind === PROJECT_KINDS.CAPE ? 'Starlight' : 'Dragon Wings'
+ }
+ },
+ buttons: ['Back', 'Continue'],
+ confirmIndex: 1,
+ cancelIndex: -1,
+ onConfirm: function (form) {
+ const rawName = String(form.creation_name == null ? '' : form.creation_name);
+ const name = normalizeCreationName(rawName);
+ if (!name || name.length > 64 || containsForbiddenControl(rawName, false)) {
+ Blockbench.showQuickMessage('Enter a readable name from 1 through 64 characters.');
+ return false;
+ }
+ const namedDraft = Object.assign({}, draft, {creation_name: name});
+ setTimeout(function () {
+ if (kind === PROJECT_KINDS.EMOTE) {
+ showEmoteRigDialog(namedDraft);
+ } else if (kind === PROJECT_KINDS.CAPE) {
+ showCapeResolutionDialog(namedDraft);
+ } else {
+ showCosmeticBodyDialog(namedDraft);
+ }
+ }, 0);
+ return true;
+ },
+ onButton: function (buttonIndex) {
+ if (buttonIndex === 0) setTimeout(showCreatorTypeDialog, 0);
+ }
+ });
+ activeDialog.show();
+ }
+
+ function showCosmeticBodyDialog(value) {
+ if (activeDialog) {
+ activeDialog.delete();
+ }
+ installCreatorStyles();
+ const draft = mergeSimpleCosmeticDraft(value);
+ let bodyDialog = null;
+ bodyDialog = new Dialog('cosmiq_cosmetic_body_type', {
+ title: 'Accessory Setup - Body Type',
+ width: 720,
+ buttons: ['Back', 'Start Creating'],
+ confirmIndex: 1,
+ cancelIndex: -1,
+ component: bodyTypeComponent(draft.initial_visible_variant),
+ onConfirm: function () {
+ const choice = bodyDialog.content_vue && bodyDialog.content_vue.selected;
+ if (choice !== 'wide' && choice !== 'slim') {
+ Blockbench.showQuickMessage('Choose Wide or Slim to continue.');
+ return false;
+ }
+ return launchBaseTemplateCreation(PROJECT_KINDS.COSMETIC, {
+ name: draft.creation_name,
+ visibleBody: choice
+ });
+ },
+ onButton: function (buttonIndex) {
+ if (buttonIndex === 0) {
+ setTimeout(function () {
+ showCreationNameDialog(PROJECT_KINDS.COSMETIC, draft);
+ }, 0);
+ }
+ }
+ });
+ activeDialog = bodyDialog;
+ activeDialog.show();
+ }
+
+ function showEmoteRigDialog(value) {
+ if (activeDialog) activeDialog.delete();
+ installCreatorStyles();
+ const draft = mergeEmoteDraft(value);
+ let rigDialog = null;
+ rigDialog = new Dialog('cosmiq_emote_rig_type', {
+ title: 'Emote Setup - Body Type',
+ width: 720,
+ buttons: ['Back', 'Continue'],
+ confirmIndex: 1,
+ cancelIndex: -1,
+ component: emoteRigTypeComponent(draft.rig_profile),
+ onConfirm: function () {
+ const choice = rigDialog.content_vue && rigDialog.content_vue.selected;
+ if (choice !== EMOTE_RIG_PROFILES.SIMPLE && choice !== EMOTE_RIG_PROFILES.PRECISE) {
+ Blockbench.showQuickMessage('Choose Simple or Precise to continue.');
+ return false;
+ }
+ setTimeout(function () {
+ showEmoteReviewDialog(Object.assign({}, draft, {rig_profile: choice}));
+ }, 0);
+ return true;
+ },
+ onButton: function (buttonIndex) {
+ if (buttonIndex === 0) {
+ setTimeout(function () {
+ showCreationNameDialog(PROJECT_KINDS.EMOTE, draft);
+ }, 0);
+ }
+ }
+ });
+ activeDialog = rigDialog;
+ activeDialog.show();
+ }
+
+ function showEmoteReviewDialog(value) {
+ if (activeDialog) activeDialog.delete();
+ installCreatorStyles();
+ const draft = mergeEmoteDraft(value);
+ activeDialog = new Dialog('cosmiq_emote_review', {
+ title: 'Emote Setup - Review',
+ width: 620,
+ lines: [emoteCreationReviewMarkup(draft)],
+ buttons: ['Back', 'Start Creating'],
+ confirmIndex: 1,
+ cancelIndex: -1,
+ onConfirm: function () {
+ return launchBaseTemplateCreation(PROJECT_KINDS.EMOTE, {
+ name: draft.creation_name,
+ visibleBody: 'wide',
+ rigProfile: draft.rig_profile
+ });
+ },
+ onButton: function (buttonIndex) {
+ if (buttonIndex === 0) {
+ setTimeout(function () {
+ showEmoteRigDialog(draft);
+ }, 0);
+ }
+ }
+ });
+ activeDialog.show();
+ }
+
+ function showCapeResolutionDialog(value) {
+ if (activeDialog) activeDialog.delete();
+ installCreatorStyles();
+ const draft = Object.assign(defaultCapeTemplateDraft(), value || {});
+ const resolutionOptions = {};
+ CAPE_RESOLUTIONS.forEach(function (resolution) {
+ resolutionOptions[resolution.id] = resolution.label;
+ });
+ activeDialog = new Dialog('cosmiq_cape_resolution', {
+ title: '[COMING SOON] Cape Setup',
+ width: 620,
+ lines: [
+ '[COMING SOON]' +
+ '
Cape upload is not available yet.
' +
+ '
You can create and save this cape as a .bbmodel now. Runtime export and upload remain disabled.
',
+ wizardProgressMarkup(CAPE_FLOW_STEPS, 2)
+ ],
+ form: {
+ resolution: {
+ label: 'Cape Resolution',
+ description: 'All supported cape canvases use a 2:1 width-to-frame-height ratio.',
+ type: 'select',
+ value: capeResolutionFromId(draft.resolution).id,
+ options: resolutionOptions
+ }
+ },
+ buttons: ['Back', 'Start Creating'],
+ confirmIndex: 1,
+ cancelIndex: -1,
+ onConfirm: function (form) {
+ const resolution = capeResolutionFromId(form.resolution);
+ if (resolution.id !== form.resolution) {
+ Blockbench.showQuickMessage('Choose a supported 2:1 cape resolution.');
+ return false;
+ }
+ return launchBaseTemplateCreation(PROJECT_KINDS.CAPE, {
+ name: draft.creation_name,
+ resolution: resolution.id,
+ visibleBody: 'wide'
+ });
+ },
+ onButton: function (buttonIndex) {
+ if (buttonIndex === 0) {
+ setTimeout(function () {
+ showCreationNameDialog(PROJECT_KINDS.CAPE, draft);
+ }, 0);
+ }
+ }
+ });
+ activeDialog.show();
+ }
+
+ function showEmoteActorDialog(value) {
+ if (activeDialog) {
+ activeDialog.delete();
+ }
+ const draft = mergeEmoteDraft(value);
+ let actorDialog = null;
+ actorDialog = new Dialog('cosmiq_emote_actors', {
+ title: 'Emote Setup - Actors',
+ width: 620,
+ lines: [wizardPageHeading(
+ EMOTE_FLOW_STEPS,
+ 1,
+ 'How Many Actors?',
+ 'Choose how many numbered Wide player rigs participate. One actor is selected by default.'
+ )],
+ form: {
+ actors: {
+ label: 'Amount Of Actors',
+ type: 'number',
+ value: draft.actors,
+ min: EMOTE_ACTOR_MIN,
+ max: EMOTE_ACTOR_MAX,
+ step: 1
+ }
+ },
+ buttons: ['Back', 'Continue'],
+ confirmIndex: 1,
+ cancelIndex: -1,
+ onConfirm: function (form) {
+ const actors = Number(form.actors);
+ if (!Number.isInteger(actors) || actors < EMOTE_ACTOR_MIN || actors > EMOTE_ACTOR_MAX) {
+ Blockbench.showQuickMessage(
+ 'Choose between ' + EMOTE_ACTOR_MIN + ' and ' + EMOTE_ACTOR_MAX + ' actors.'
+ );
+ return false;
+ }
+ setTimeout(function () {
+ showEmotePropsDialog(mergeEmoteDraft(draft, {actors: actors}));
+ }, 0);
+ return true;
+ },
+ onButton: function (buttonIndex) {
+ if (buttonIndex === 0) {
+ setTimeout(showCreatorTypeDialog, 0);
+ }
+ }
+ });
+ activeDialog = actorDialog;
+ activeDialog.show();
+ }
+
+ function showEmotePropsDialog(value) {
+ if (activeDialog) {
+ activeDialog.delete();
+ }
+ const draft = mergeEmoteDraft(value);
+ let propsDialog = null;
+ propsDialog = new Dialog('cosmiq_emote_props', {
+ title: 'Emote Setup - Props',
+ width: 720,
+ component: emoteBooleanComponent(
+ 'Does The Emote Contain Props?',
+ 'No is selected by default. Yes creates an empty Props folder; each direct child folder becomes one Object-Actor.',
+ draft.containsProps,
+ 2
+ ),
+ buttons: ['Back', 'Continue'],
+ confirmIndex: 1,
+ cancelIndex: -1,
+ onConfirm: function () {
+ const choice = propsDialog.content_vue && propsDialog.content_vue.selected;
+ if (choice !== 'yes' && choice !== 'no') {
+ Blockbench.showQuickMessage('Choose Yes or No to continue.');
+ return false;
+ }
+ setTimeout(function () {
+ showEmoteTraversableDialog(mergeEmoteDraft(draft, {containsProps: choice === 'yes'}));
+ }, 0);
+ return true;
+ },
+ onButton: function (buttonIndex) {
+ if (buttonIndex === 0) {
+ setTimeout(function () { showEmoteActorDialog(draft); }, 0);
+ }
+ }
+ });
+ activeDialog = propsDialog;
+ activeDialog.show();
+ }
+
+ function showEmoteTraversableDialog(value) {
+ if (activeDialog) {
+ activeDialog.delete();
+ }
+ const draft = mergeEmoteDraft(value);
+ let traversableDialog = null;
+ traversableDialog = new Dialog('cosmiq_emote_traversable', {
+ title: 'Emote Setup - Traversable',
+ width: 720,
+ component: emoteBooleanComponent(
+ 'Is The Emote Traversable?',
+ 'No is selected by default. Yes records visual movement-blend intent only; Minecraft remains authoritative for player movement.',
+ draft.traversable,
+ 3
+ ),
+ buttons: ['Back', 'Continue'],
+ confirmIndex: 1,
+ cancelIndex: -1,
+ onConfirm: function () {
+ const choice = traversableDialog.content_vue && traversableDialog.content_vue.selected;
+ if (choice !== 'yes' && choice !== 'no') {
+ Blockbench.showQuickMessage('Choose Yes or No to continue.');
+ return false;
+ }
+ return createEmoteProject(mergeEmoteDraft(draft, {traversable: choice === 'yes'}));
+ },
+ onButton: function (buttonIndex) {
+ if (buttonIndex === 0) {
+ setTimeout(function () { showEmotePropsDialog(draft); }, 0);
+ }
+ }
+ });
+ activeDialog = traversableDialog;
+ activeDialog.show();
+ }
+
+ function armWidthBranchSummary(inspection) {
+ const variants = Object.keys(inspection.authoredVariants).filter(function (variant) {
+ return inspection.authoredVariants[variant];
+ });
+ const anchors = inspection.anchorIds.map(function (anchorId) {
+ return anchorById[anchorId] ? anchorById[anchorId].label : anchorId;
+ });
+ return 'Detected ' + (anchors.join(' and ') || 'arm') + ' geometry in ' +
+ (variants.join(' and ') || 'universal') + ' authoring geometry.';
+ }
+
+ function showArmWidthModeDialog(onConfigured) {
+ if (!isCosmiqCosmeticProject()) return false;
+ const inspection = inspectArmWidthGeometry(liveSnapshot());
+ if (!inspection.hasArmGeometry) return false;
+ activeDialog = new Dialog('cosmiq_arm_width_mode', {
+ title: 'Does This Accessory Depend on Arm Width?',
+ width: 650,
+ lines: [
+ '' + escapeHtml(armWidthBranchSummary(inspection)) + '
',
+ 'No exports one authored model as universal so it appears on both ' +
+ 'Wide and Slim players. Yes preserves separate Wide and Slim models.
'
+ ],
+ form: {
+ arm_width_mode: {
+ label: 'Arm Width Fit',
+ type: 'select',
+ options: {
+ unconfigured: 'Choose an answer',
+ shared: 'No - use the same model for Wide and Slim',
+ dependent: 'Yes - use separate Wide and Slim models'
+ },
+ value: inspection.configuredMode
+ }
+ },
+ buttons: ['Save & Continue', 'Cancel'],
+ confirmIndex: 0,
+ cancelIndex: 1,
+ onConfirm: function (form) {
+ const selectedMode = normalizeArmWidthMode(form.arm_width_mode);
+ if (selectedMode === ARM_WIDTH_MODES.UNCONFIGURED) {
+ Blockbench.showQuickMessage('Choose Yes or No to continue.');
+ return false;
+ }
+ Project.cosmiq_arm_width_mode = selectedMode;
+ refreshMetadata(true);
+ if (typeof onConfigured === 'function') {
+ setTimeout(onConfigured, 0);
+ }
+ return true;
+ }
+ });
+ activeDialog.show();
+ return true;
+ }
+
+ function emoteActorOptions() {
+ const count = Math.max(
+ EMOTE_ACTOR_MIN,
+ Math.min(
+ EMOTE_ACTOR_MAX,
+ Math.floor(Number(Project.cosmiq_emote_actor_count)) || EMOTE_ACTOR_MIN
+ )
+ );
+ const options = {};
+ for (let actorIndex = 1; actorIndex <= count; actorIndex++) {
+ options[String(actorIndex)] = 'Actor ' + actorIndex;
+ }
+ return options;
+ }
+
+ function emoteAnimationScopeOptions() {
+ return {
+ scene: 'Combined Scene - all actors and Object-Actors',
+ actor: 'Individual Actor - one numbered player role'
+ };
+ }
+
+ function showAddEmoteAnimationDialog() {
+ activeDialog = new Dialog('cosmiq_add_emote_animation', {
+ title: 'Add Emote Animation',
+ width: 560,
+ form: {
+ animation_name: {
+ label: 'Animation Name',
+ description: 'A readable editor label. Scope and actor identity are stored separately.',
+ type: 'text',
+ value: 'scene.extra'
+ },
+ scope: {
+ label: 'Animation Scope',
+ type: 'select',
+ value: EMOTE_ANIMATION_SCOPES.SCENE,
+ options: emoteAnimationScopeOptions()
+ },
+ actor_index: {
+ label: 'Actor Number',
+ description: 'Used only when Animation Scope is Individual Actor.',
+ type: 'select',
+ value: '1',
+ options: emoteActorOptions()
+ },
+ length_seconds: {
+ label: 'Duration (seconds)',
+ type: 'number',
+ value: 2,
+ min: 0.05,
+ max: LIMITS.animationSeconds,
+ step: 0.05
+ },
+ loop: {
+ label: 'Loop Clip',
+ type: 'checkbox',
+ value: false
+ }
+ },
+ onConfirm: function (form) {
+ const rawName = String(form.animation_name == null ? '' : form.animation_name);
+ const name = normalizeEmoteDraftName(rawName);
+ const scope = form.scope;
+ const actorIndex = Number(form.actor_index);
+ const lengthSeconds = Number(form.length_seconds);
+ if (!name || name.length > 64 || containsForbiddenControl(rawName, false)) {
+ Blockbench.showQuickMessage('Enter a readable animation name up to 64 characters.');
+ return false;
+ }
+ if (scope !== EMOTE_ANIMATION_SCOPES.SCENE && scope !== EMOTE_ANIMATION_SCOPES.ACTOR) {
+ Blockbench.showQuickMessage('Choose Combined Scene or Individual Actor.');
+ return false;
+ }
+ if (scope === EMOTE_ANIMATION_SCOPES.ACTOR &&
+ (!Number.isInteger(actorIndex) || !emoteActorOptions()[String(actorIndex)])) {
+ Blockbench.showQuickMessage('Choose a valid actor number for this project.');
+ return false;
+ }
+ if (!Number.isFinite(lengthSeconds) || lengthSeconds < 0.05 ||
+ lengthSeconds > LIMITS.animationSeconds) {
+ Blockbench.showQuickMessage(
+ 'Animation duration must be from 0.05 through ' + LIMITS.animationSeconds + ' seconds.'
+ );
+ return false;
+ }
+ Undo.initEdit({animations: []});
+ const animation = createEmptyEmoteAnimation(name, scope, actorIndex);
+ animation.length = lengthSeconds;
+ animation.loop = form.loop === true ? 'loop' : 'once';
+ Undo.finishEdit('Add Cosmiq emote animation', {animations: [animation]});
+ animation.select();
+ refreshMetadata(true);
+ activeDialog.hide();
+ Blockbench.showQuickMessage('Added empty ' + name + ' animation lane.');
+ return true;
+ }
+ });
+ activeDialog.show();
+ }
+
+ function showConfigureEmoteAnimationDialog() {
+ const animation = selectedAnimation();
+ if (!animation) {
+ Blockbench.showQuickMessage('Select an animation first.');
+ return;
+ }
+ const savedScope = animation.cosmiq_emote_scope === EMOTE_ANIMATION_SCOPES.ACTOR
+ ? EMOTE_ANIMATION_SCOPES.ACTOR
+ : EMOTE_ANIMATION_SCOPES.SCENE;
+ const savedActorIndex = emoteActorOptions()[String(animation.cosmiq_emote_actor_index)]
+ ? String(animation.cosmiq_emote_actor_index)
+ : '1';
+ activeDialog = new Dialog('cosmiq_configure_emote_animation', {
+ title: 'Configure Emote Animation',
+ width: 560,
+ form: {
+ animation_name: {
+ label: 'Animation Name',
+ description: 'Renaming does not change the stable clip identity.',
+ type: 'text',
+ value: animation.name || 'emote.animation'
+ },
+ scope: {
+ label: 'Animation Scope',
+ type: 'select',
+ value: savedScope,
+ options: emoteAnimationScopeOptions()
+ },
+ actor_index: {
+ label: 'Actor Number',
+ description: 'Used only when Animation Scope is Individual Actor.',
+ type: 'select',
+ value: savedActorIndex,
+ options: emoteActorOptions()
+ }
+ },
+ onConfirm: function (form) {
+ const rawName = String(form.animation_name == null ? '' : form.animation_name);
+ const name = normalizeEmoteDraftName(rawName);
+ const scope = form.scope;
+ const actorIndex = Number(form.actor_index);
+ if (!name || name.length > 64 || containsForbiddenControl(rawName, false)) {
+ Blockbench.showQuickMessage('Enter a readable animation name up to 64 characters.');
+ return false;
+ }
+ if (scope !== EMOTE_ANIMATION_SCOPES.SCENE && scope !== EMOTE_ANIMATION_SCOPES.ACTOR) {
+ Blockbench.showQuickMessage('Choose Combined Scene or Individual Actor.');
+ return false;
+ }
+ if (scope === EMOTE_ANIMATION_SCOPES.ACTOR &&
+ (!Number.isInteger(actorIndex) || !emoteActorOptions()[String(actorIndex)])) {
+ Blockbench.showQuickMessage('Choose a valid actor number for this project.');
+ return false;
+ }
+ Undo.initEdit({animations: [animation]});
+ animation.name = name;
+ animation.cosmiq_clip_id = animation.cosmiq_clip_id || runtimeId(
+ normalizeProjectId(Project.cosmiq_project_id) || 'creator:untitled_emote',
+ 'animation',
+ animation.uuid
+ );
+ animation.cosmiq_trigger_kind = 'none';
+ animation.cosmiq_trigger = '';
+ animation.cosmiq_emote_scope = scope;
+ animation.cosmiq_emote_actor_index = scope === EMOTE_ANIMATION_SCOPES.ACTOR
+ ? actorIndex
+ : 0;
+ Undo.finishEdit('Configure Cosmiq emote animation', {animations: [animation]});
+ refreshMetadata(true);
+ activeDialog.hide();
+ Blockbench.showQuickMessage('Saved ' + name + ' as a ' +
+ (scope === EMOTE_ANIMATION_SCOPES.SCENE ? 'combined Scene' : 'numbered Actor') + ' clip.');
+ return true;
+ }
+ });
+ activeDialog.show();
+ }
+
+ function moveSelectionToAnchorDialog() {
+ const nodes = selectedOutlinerNodes();
+ if (nodes.length === 0) {
+ Blockbench.showQuickMessage('Select one or more cosmetic groups, cubes, or meshes first.');
+ return;
+ }
+ if (nodes.some(function (node) {
+ const role = node.cosmiq_role || ROLES.NONE;
+ return role === ROLES.EXPORT_ROOT || role === ROLES.MODEL_VARIANT || role === ROLES.STRUCTURE ||
+ role === ROLES.ANCHOR || role === ROLES.REFERENCE_ROOT ||
+ role === ROLES.REFERENCE || role === ROLES.PARTICLE_MARKER ||
+ TEMPLATE_ATTACHMENT_POINTS.some(function (point) { return point.modelGroup === node.name; });
+ })) {
+ Blockbench.showQuickMessage(
+ 'Reserved Cosmiq attachment, reference, and marker nodes cannot be moved this way.'
+ );
+ return;
+ }
+ activeDialog = new Dialog('cosmiq_move_to_anchor', {
+ title: 'Move Cosmetic To Player Anchor',
+ width: 480,
+ form: {
+ model_variant: {
+ label: 'Cosmetic Model Type',
+ type: 'select',
+ value: 'wide',
+ options: {wide: 'Wide', slim: 'Slim'}
+ },
+ anchor: {
+ label: 'Player Anchor Parent',
+ type: 'select',
+ value: 'cosmiq:player/torso',
+ options: anchorOptions()
+ }
+ },
+ onConfirm: function (form) {
+ const attachmentPoint = TEMPLATE_ATTACHMENT_POINTS.find(function (point) {
+ return point.anchorId === form.anchor && (
+ point.modelVariant === 'universal' || point.modelVariant === form.model_variant
+ );
+ });
+ const attachmentGroup = attachmentPoint && Group.all.find(function (group) {
+ return group.name === attachmentPoint.modelGroup &&
+ group.cosmiq_role === ROLES.AUTHORING;
+ });
+ if (!attachmentGroup) {
+ Blockbench.showQuickMessage('That [Model bodypart] attachment group is missing.');
+ return false;
+ }
+ Undo.initEdit({outliner: true});
+ nodes.forEach(function (node) {
+ node.addTo(attachmentGroup);
+ });
+ Undo.finishEdit('Move accessory to Cosmiq attachment group', {outliner: true});
+ refreshMetadata(true);
+ activeDialog.hide();
+ Blockbench.showQuickMessage(
+ 'Moved selection beneath ' + attachmentPoint.modelGroup + ' (' + attachmentPoint.label + ').'
+ );
+ return true;
+ }
+ });
+ activeDialog.show();
+ }
+
+ function applyLegacyPlayerAnchorNormalization() {
+ const changes = legacyAnchorNormalizationPlan(collectLiveNodes());
+ const byUuid = Object.create(null);
+ (typeof Group !== 'undefined' && Array.isArray(Group.all) ? Group.all : []).forEach(function (group) {
+ byUuid[group.uuid] = group;
+ });
+ const applied = [];
+ changes.forEach(function (change) {
+ const group = byUuid[change.uuid];
+ if (!group) return;
+ group.cosmiq_role = ROLES.ANCHOR;
+ group.cosmiq_anchor = change.anchorId;
+ group.origin = change.origin.slice();
+ applied.push(change);
+ });
+ if (applied.length > 0 && typeof Canvas !== 'undefined' && typeof Canvas.updateAll === 'function') {
+ Canvas.updateAll();
+ }
+ return applied;
+ }
+
+ function normalizeLegacyPlayerAnchorsAction() {
+ const changes = legacyAnchorNormalizationPlan(collectLiveNodes());
+ if (changes.length === 0) {
+ Blockbench.showQuickMessage('All player anchors already match the Cosmiq V3 rest pose.');
+ return [];
+ }
+ Undo.initEdit({outliner: true});
+ const applied = applyLegacyPlayerAnchorNormalization();
+ Undo.finishEdit('Normalize legacy Cosmiq player anchors', {outliner: true});
+ refreshMetadata(true);
+ Blockbench.showQuickMessage(
+ 'Normalized ' + applied.length + ' player anchor' + (applied.length === 1 ? '' : 's') +
+ ' to the Cosmiq V3 rest pose.'
+ );
+ return applied;
+ }
+
+
+ function issueSummary(result) {
+ const errors = result.issues.filter(function (issue) { return issue.severity === 'error'; });
+ const warnings = result.issues.filter(function (issue) { return issue.severity === 'warning'; });
+ const info = result.issues.filter(function (issue) { return issue.severity === 'info'; });
+ const lines = [
+ errors.length + ' error(s), ' + warnings.length + ' warning(s), ' + info.length + ' information message(s).'
+ ];
+ result.issues.slice(0, 24).forEach(function (issue) {
+ lines.push('[' + issue.severity.toUpperCase() + '] ' + issue.code + ': ' + issue.message);
+ });
+ if (result.issues.length > 24) {
+ lines.push('...and ' + (result.issues.length - 24) + ' more issue(s).');
+ }
+ if (result.issues.length === 0) {
+ lines.push('Cosmiq source metadata is internally consistent. Runtime conversion still validates independently.');
+ }
+ return lines.join('\n');
+ }
+
+ function showExportBlocked(title, issues) {
+ Blockbench.showMessageBox({
+ title: title,
+ message: issueSummary({issues: issues}),
+ buttons: ['OK']
+ });
+ }
+
+ function packageFileName(category) {
+ const safeName = String(category || 'cosmiq')
+ .trim()
+ .replace(/[<>:"/\\|?*\u0000-\u001f]+/g, '_')
+ .replace(/[. ]+$/g, '') || 'cosmiq';
+ return safeName;
+ }
+
+ function escapeHtml(value) {
+ return String(value == null ? '' : value)
+ .replace(/&/g, '&')
+ .replace(//g, '>')
+ .replace(/"/g, '"')
+ .replace(/'/g, ''');
+ }
+
+ function packageSnapshot(snapshot) {
+ const source = snapshot || {};
+ const project = source.project || {};
+ return Object.assign({}, snapshot, {
+ project: Object.assign({}, project),
+ animations: animationsWithPackageContent(source.animations, project.timelineCues)
+ });
+ }
+
+ function uniqueIssues(issues) {
+ const seen = Object.create(null);
+ return (Array.isArray(issues) ? issues : []).filter(function (issue) {
+ if (!issue) return false;
+ const key = [issue.severity, issue.code, issue.message, issue.sourceUuid || ''].join('|');
+ if (seen[key]) return false;
+ seen[key] = true;
+ return true;
+ });
+ }
+
+ function unsupportedCreatorInputs(root) {
+ const entries = [];
+ const issues = [];
+ const seenEntries = Object.create(null);
+ const seenObjects = new Set();
+ const pending = [{value: root, location: 'project'}];
+ function report(type, location, blocking) {
+ const safeLocation = boundedCreatorString(location, 160);
+ const key = type + '|' + safeLocation;
+ if (seenEntries[key] || entries.length >= 64) return;
+ seenEntries[key] = true;
+ entries.push({type: type, location: safeLocation, bundled: false, blocking: blocking !== false});
+ if (blocking === false) return;
+ issues.push(makeIssue(
+ 'error',
+ 'COSMIQ_PACKAGE_UNSUPPORTED_' + type.toUpperCase(),
+ type.replace(/_/g, ' ') + ' input at ' + safeLocation + ' cannot be bundled in a creator package.'
+ ));
+ }
+ while (pending.length > 0 && entries.length < 64) {
+ const current = pending.pop();
+ const value = current.value;
+ if (!value || typeof value !== 'object' || seenObjects.has(value)) continue;
+ seenObjects.add(value);
+ Object.keys(value).forEach(function (key) {
+ const child = value[key];
+ const location = current.location + '.' + key;
+ const normalizedKey = key.toLowerCase();
+ if (PATH_FIELD_NAMES.indexOf(normalizedKey) !== -1 && child != null && child !== '') {
+ const isEmbeddedPngTexturePath = /(?:^|\.)textures\.\d+\.(?:path|relative_path|source_path)$/i.test(location) &&
+ isEmbeddedPngDataUri(value.source);
+ const isRootProjectEditorPath = /^project\.(?:bbmodel\.)?(?:path|relative_path|source_path|save_path)$/i.test(location);
+ if (isEmbeddedPngTexturePath || isRootProjectEditorPath) {
+ report('editor_path_excluded', location, false);
+ } else {
+ report('filesystem_path', location, true);
+ }
+ if (/\.(?:ogg|wav|mp3|m4a|flac)$/i.test(String(child))) report('raw_audio', location, true);
+ if (/\.gif$/i.test(String(child))) report('gif', location, true);
+ }
+ if (typeof child === 'string') {
+ if (/^data:image\/gif/i.test(child) || /\.gif(?:$|[?#])/i.test(child)) report('gif', location, true);
+ const canonicalEmbeddedOgg = /\.embeddedOgg$/i.test(location) &&
+ /^data:audio\/ogg;base64,[A-Za-z0-9+/]*={0,2}$/.test(child);
+ if (!canonicalEmbeddedOgg && (
+ /^data:audio\//i.test(child) || /\.(?:ogg|wav|mp3|m4a|flac)(?:$|[?#])/i.test(child)
+ )) {
+ report('raw_audio', location, true);
+ }
+ if (/^(?:https?:|file:|blob:)/i.test(child)) report('remote_url', location, true);
+ } else if (child && typeof child === 'object') {
+ pending.push({value: child, location: location});
+ }
+ });
+ }
+ return {entries: entries, issues: issues};
+ }
+
+ function buildExportInventory(snapshot, manifest, bbmodel, sourceResult, issues, rawProject) {
+ const source = snapshot || {};
+ const project = source.project || {};
+ const metadata = bbmodel && bbmodel.cosmiq && typeof bbmodel.cosmiq === 'object'
+ ? bbmodel.cosmiq
+ : ((sourceResult && sourceResult.metadata) || {});
+ const packagedAnimations = Array.isArray(bbmodel && bbmodel.animations) ? bbmodel.animations : [];
+ const snapshotAnimations = Array.isArray(source.animations) ? source.animations : [];
+ const packagedIds = Object.create(null);
+ const snapshotById = Object.create(null);
+ snapshotAnimations.forEach(function (animation) {
+ if (animation && animation.uuid) snapshotById[animation.uuid] = animation;
+ });
+ packagedAnimations.forEach(function (animation) {
+ if (animation && animation.uuid) packagedIds[animation.uuid] = true;
+ });
+ const bindingMap = metadata.animationBindings && typeof metadata.animationBindings === 'object'
+ ? metadata.animationBindings
+ : {};
+ const animations = packagedAnimations.map(function (animation) {
+ const snapshotAnimation = snapshotById[animation.uuid] || {};
+ const binding = bindingMap[animation.uuid] || {};
+ const markers = collectTimelineMarkers(animation.markers || binding.markers);
+ const lengthSeconds = numberOrDefault(
+ snapshotAnimation.lengthSeconds,
+ numberOrDefault(animation.length, numberOrDefault(binding.durationMs, 0) / 1000)
+ );
+ return {
+ uuid: animation.uuid || '',
+ name: animation.name || binding.sourceName || snapshotAnimation.name || 'Unnamed animation',
+ scope: binding.scope || snapshotAnimation.emoteScope || null,
+ actorIndex: binding.actorIndex == null ? null : binding.actorIndex,
+ durationMs: Math.max(0, Math.round(lengthSeconds * 1000)),
+ keyframeCount: animationKeyframeCount(animation),
+ markers: markers
+ };
+ });
+ const markers = [];
+ animations.forEach(function (animation) {
+ animation.markers.forEach(function (marker) {
+ markers.push(Object.assign({
+ animationUuid: animation.uuid,
+ animationName: animation.name
+ }, marker));
+ });
+ });
+
+ const nodeBindings = metadata.nodeBindings && typeof metadata.nodeBindings === 'object'
+ ? metadata.nodeBindings
+ : {};
+ const cosmeticNodes = [];
+ const playerBones = [];
+ const objectActorNodes = [];
+ Object.keys(nodeBindings).forEach(function (uuid) {
+ const binding = nodeBindings[uuid] || {};
+ const item = {
+ uuid: uuid,
+ name: binding.sourceName || uuid,
+ role: binding.role || '',
+ modelVariant: binding.modelVariant || '',
+ hasGeometry: binding.hasGeometry === true,
+ actorIndex: binding.actorIndex == null ? null : binding.actorIndex
+ };
+ if (manifest && (manifest.assetKind === 'accessory' || manifest.assetKind === 'cape')) {
+ cosmeticNodes.push(item);
+ }
+ if (binding.role === ROLES.PLAYER_BONE) playerBones.push(item);
+ if (binding.role === ROLES.OBJECT_ACTOR_NODE) objectActorNodes.push(item);
+ });
+ const geometryObjects = (Array.isArray(source.nodes) ? source.nodes : []).filter(function (node) {
+ return node && node.export !== false && (node.type === 'cube' || node.type === 'mesh') &&
+ node.role !== ROLES.REFERENCE && node.role !== ROLES.PARTICLE_EFFECT_LOCATOR &&
+ node.role !== ROLES.SOUND_EFFECT_LOCATOR;
+ }).map(function (node) {
+ return {
+ uuid: node.uuid || '',
+ name: node.name || node.uuid || 'Geometry',
+ type: node.type,
+ role: node.role || '',
+ actorIndex: Number(node.actorIndex) || null,
+ modelVariant: node.modelVariant || ''
+ };
+ });
+
+ const textureRoleById = Object.create(null);
+ (Array.isArray(source.textures) ? source.textures : []).forEach(function (texture) {
+ if (texture && texture.uuid) textureRoleById[texture.uuid] = texture.role;
+ });
+ const runtimePngs = [];
+ const referencePngs = [];
+ (Array.isArray(bbmodel && bbmodel.textures) ? bbmodel.textures : []).forEach(function (texture) {
+ const record = {
+ uuid: texture && texture.uuid || '',
+ name: texture && texture.name || 'texture.png',
+ embeddedPng: !!(texture && isEmbeddedPngDataUri(texture.source)),
+ bundled: true
+ };
+ const role = texture && (texture.cosmiq_role || texture.role) || textureRoleById[record.uuid];
+ (role === ROLES.REFERENCE ? referencePngs : runtimePngs).push(record);
+ });
+
+ const resources = Array.isArray(metadata.resourceReferences) ? metadata.resourceReferences.slice() : [];
+ const cues = Array.isArray(metadata.timelineCues) ? metadata.timelineCues.slice() : [];
+ const particleResources = resources.filter(function (resource) {
+ return isParticleResource(resource);
+ });
+ const soundResources = resources.filter(function (resource) {
+ return resource && resource.type === RESOURCE_TYPES.SOUND;
+ });
+ const particleCues = cues.filter(function (cue) { return cue && cue.type === CUE_TYPES.PARTICLE; });
+ const soundCues = cues.filter(function (cue) { return cue && cue.type === CUE_TYPES.SOUND; });
+ const comments = cues.filter(function (cue) { return cue && cue.type === CUE_TYPES.COMMENT; });
+ const effectLocators = Object.keys(metadata.effectLocatorBindings || {}).map(function (uuid) {
+ return Object.assign({uuid: uuid}, metadata.effectLocatorBindings[uuid]);
+ });
+ const unresolved = resources.filter(function (resource) {
+ return !resource ||
+ (resource.type === RESOURCE_TYPES.SOUND
+ ? !resource.embeddedOgg
+ : !isParticleResource(resource));
+ }).map(function (resource) {
+ return {
+ type: 'resource',
+ id: resource && resource.id || '',
+ name: resource && resource.displayName || 'Unnamed resource',
+ reason: resource && resource.type === RESOURCE_TYPES.SOUND
+ ? 'embedded Ogg Vorbis data is missing'
+ : 'resource declaration is incomplete'
+ };
+ });
+ cues.forEach(function (cue) {
+ if (cue && cue.atMs == null) {
+ unresolved.push({
+ type: 'cue',
+ id: cue.id || '',
+ name: cue.markerName || cue.id || 'Unnamed cue',
+ reason: 'marker timestamp unresolved'
+ });
+ }
+ });
+ const inputResult = unsupportedCreatorInputs(rawProject || {});
+ const inputEntries = inputResult.entries;
+ const unsupported = inputEntries.filter(function (entry) { return entry.blocking !== false; });
+ const excludedInputs = inputEntries.filter(function (entry) { return entry.blocking === false; });
+ const actors = Array.isArray(metadata.actors) ? metadata.actors.slice() : [];
+ const objectActors = metadata.props && Array.isArray(metadata.props.objectActors)
+ ? metadata.props.objectActors.slice()
+ : [];
+ const omittedAnimations = snapshotAnimations.filter(function (animation) {
+ return animation && !packagedIds[animation.uuid];
+ }).map(function (animation) {
+ return {
+ uuid: animation.uuid || '',
+ name: animation.name || 'Unnamed animation',
+ reason: 'empty: no keyframes'
+ };
+ });
+ const wideNodes = cosmeticNodes.filter(function (node) { return node.modelVariant === 'wide'; });
+ const slimNodes = cosmeticNodes.filter(function (node) { return node.modelVariant === 'slim'; });
+ const universalNodes = cosmeticNodes.filter(function (node) {
+ return node.modelVariant !== 'wide' && node.modelVariant !== 'slim';
+ });
+ return {
+ kind: manifest && manifest.assetKind || project.kind || PROJECT_KINDS.COSMETIC,
+ draft: false,
+ runtimeExportReady: true,
+ cosmeticNodes: cosmeticNodes,
+ wideNodes: wideNodes,
+ slimNodes: slimNodes,
+ universalNodes: universalNodes,
+ actors: actors,
+ playerBones: playerBones,
+ objectActors: objectActors,
+ objectActorNodes: objectActorNodes,
+ geometryObjects: geometryObjects,
+ runtimePngs: runtimePngs,
+ referencePngs: referencePngs,
+ animations: animations,
+ markers: markers,
+ particleResources: particleResources,
+ soundResources: soundResources,
+ particleCues: particleCues,
+ soundCues: soundCues,
+ comments: comments,
+ effectLocators: effectLocators,
+ unresolved: unresolved,
+ unsupported: unsupported,
+ excludedInputs: excludedInputs,
+ unsupportedMedia: Array.isArray(metadata.unsupportedMedia) ? metadata.unsupportedMedia.slice() : [],
+ omittedAnimations: omittedAnimations,
+ issues: uniqueIssues((Array.isArray(issues) ? issues : []).concat(inputResult.issues)),
+ counts: {
+ geometryObjects: geometryObjects.length,
+ runtimePngs: runtimePngs.length,
+ referencePngs: referencePngs.length,
+ animations: animations.length,
+ markers: markers.length,
+ particleCues: particleCues.length,
+ soundCues: soundCues.length,
+ comments: comments.length
+ }
+ };
+ }
+
+ function buildCreatorExportCandidate(snapshot, rawProject) {
+ const packagedSnapshot = packageSnapshot(snapshot || {});
+ const requestedKind = packagedSnapshot.project && packagedSnapshot.project.kind;
+ const kind = requestedKind;
+ const validKind = [
+ PROJECT_KINDS.COSMETIC,
+ PROJECT_KINDS.EMOTE,
+ PROJECT_KINDS.CAPE
+ ].indexOf(kind) !== -1;
+ const sourceResult = !validKind
+ ? {
+ metadata: {},
+ issues: [makeIssue(
+ 'error',
+ 'COSMIQ_PACKAGE_KIND',
+ 'Project kind must be explicitly cosmetic, cape, or emote.'
+ )]
+ }
+ : kind === PROJECT_KINDS.EMOTE
+ ? buildEmoteSourceMetadata(packagedSnapshot)
+ : kind === PROJECT_KINDS.CAPE
+ ? buildCapeSourceMetadata(packagedSnapshot)
+ : buildSourceMetadata(packagedSnapshot);
+ const rawInput = rawProject && typeof rawProject === 'object' ? rawProject : null;
+ const preparationIssues = [];
+ let editableProject = null;
+ if (!rawInput) {
+ preparationIssues.push(makeIssue(
+ 'error',
+ 'COSMIQ_PACKAGE_BBMODEL_TYPE',
+ 'Blockbench did not provide an editable project to normalize.'
+ ));
+ } else {
+ try {
+ editableProject = JSON.parse(JSON.stringify(rawInput));
+ editableProject.cosmiq = JSON.parse(JSON.stringify(sourceResult.metadata));
+ normalizePackagedNodeVisibility(
+ editableProject,
+ sourceResult.metadata.nodeBindings
+ );
+ } catch (error) {
+ preparationIssues.push(makeIssue(
+ 'error',
+ 'COSMIQ_PACKAGE_BBMODEL_JSON',
+ 'Blockbench could not serialize the editable project.'
+ ));
+ }
+ }
+ let packageIssues = uniqueIssues([].concat(sourceResult.issues || []).concat(preparationIssues));
+ if (kind === PROJECT_KINDS.CAPE) {
+ packageIssues = uniqueIssues(packageIssues.concat([makeIssue(
+ 'error',
+ 'COSMIQ_CAPE_COMING_SOON',
+ '[COMING SOON] Cape .bbmodel package export and upload are not available yet. ' +
+ 'You can keep authoring and save the .bbmodel locally.'
+ )]));
+ }
+ let compiled = null;
+ let packageContent = null;
+ if (editableProject && !packageIssues.some(function (issue) { return issue.severity === 'error'; })) {
+ try {
+ packageContent = encodeCosmiqBbmodel(editableProject);
+ } catch (error) {
+ packageIssues.push(makeIssue(
+ 'error',
+ error && error.code ? error.code : 'COSMIQ_PACKAGE_ENCODE',
+ error && error.message ? error.message : 'Exact V3 .bbmodel validation failed.'
+ ));
+ }
+ try {
+ compiled = compileBlockbenchCosmiqPackage(packagedSnapshot, editableProject);
+ } catch (error) {
+ packageIssues.push(makeIssue(
+ 'info',
+ 'COSMIQ_LEGACY_REVIEW_PROJECTION',
+ 'The retired V2 review projection could not be generated and was ignored: ' +
+ (error && error.message ? error.message : 'unknown projection error')
+ ));
+ }
+ packageIssues = uniqueIssues(packageIssues);
+ }
+ const manifest = compiled ? compiled.manifest : cosmiqBaseManifest(
+ kind === PROJECT_KINDS.CAPE || kind === PROJECT_KINDS.EMOTE ? kind : 'accessory'
+ );
+ const inventory = buildExportInventory(
+ packagedSnapshot,
+ manifest,
+ editableProject,
+ sourceResult,
+ packageIssues,
+ {snapshot: snapshot, bbmodel: rawInput}
+ );
+ return {
+ kind: kind,
+ assetKind: manifest.assetKind,
+ draft: false,
+ runtimeExportReady: kind !== PROJECT_KINDS.CAPE,
+ filename: packageFileName(manifest.assetKind),
+ manifest: manifest,
+ bbmodel: editableProject,
+ snapshot: packagedSnapshot,
+ sourceResult: sourceResult,
+ inventory: inventory,
+ issues: packageIssues,
+ content: packageContent
+ };
+ }
+
+ function cosmeticExportSummary(snapshot) {
+ const packaged = packageSnapshot(snapshot);
+ const sourceResult = buildSourceMetadata(packaged);
+ const bindings = sourceResult.metadata.nodeBindings || {};
+ const geometry = {wide: 0, slim: 0};
+ Object.keys(bindings).forEach(function (uuid) {
+ const binding = bindings[uuid];
+ if (binding && binding.hasGeometry && (binding.modelVariant === 'wide' || binding.modelVariant === 'slim')) {
+ geometry[binding.modelVariant]++;
+ }
+ });
+ return {
+ snapshot: packaged,
+ sourceResult: sourceResult,
+ wideGeometry: geometry.wide,
+ slimGeometry: geometry.slim,
+ animations: packaged.animations.map(function (animation) {
+ return {
+ uuid: animation.uuid,
+ name: animation.name,
+ keyframeCount: animationKeyframeCount(animation)
+ };
+ }),
+ omittedEmptyAnimations: Math.max(0, snapshot.animations.length - packaged.animations.length)
+ };
+ }
+
+ function boundedReviewList(items, renderItem, emptyMessage) {
+ const records = Array.isArray(items) ? items : [];
+ if (records.length === 0) {
+ return '' + escapeHtml(emptyMessage || 'None') + '
';
+ }
+ const visible = records.slice(0, 80);
+ const remainder = records.length - visible.length;
+ return '' + visible.map(function (item, index) {
+ return '- ' + renderItem(item, index) + '
';
+ }).join('') + (remainder > 0
+ ? '- ' + remainder + ' additional item(s) are included but not expanded here.
'
+ : '') + '
';
+ }
+
+ function reviewText(value, maximum) {
+ return escapeHtml(boundedCreatorString(value, maximum || 160));
+ }
+
+ function detectedAccessoryAnchorIds(candidate) {
+ if (!candidate || candidate.kind !== PROJECT_KINDS.COSMETIC) return [];
+ const metadata = candidate.sourceResult && candidate.sourceResult.metadata || {};
+ const direct = metadata.cosmetic && canonicalAnchorId(metadata.cosmetic.attachmentAnchorId);
+ if (isKnownAnchor(direct)) return [direct];
+ return uniqueValues(Object.keys(metadata.nodeBindings || {}).map(function (uuid) {
+ return canonicalAnchorId(metadata.nodeBindings[uuid] && metadata.nodeBindings[uuid].anchorId);
+ }).filter(isKnownAnchor)).sort();
+ }
+
+ function minecraftAttachmentDiagram(anchorIds) {
+ const selected = Object.create(null);
+ (Array.isArray(anchorIds) ? anchorIds : []).forEach(function (anchorId) {
+ selected[canonicalAnchorId(anchorId)] = true;
+ });
+ function bodyPart(anchorId, tag, attributes) {
+ const className = selected[anchorId] ? 'part selected' : 'part';
+ return '<' + tag + ' class="' + className + '" data-anchor="' + anchorId + '" ' +
+ attributes + '>' + tag + '>';
+ }
+ return [
+ ''
+ ].join('');
+ }
+
+ function exportDiagnosticData(candidate) {
+ const anchorIds = detectedAccessoryAnchorIds(candidate);
+ return {
+ schemaVersion: 'cosmiq.creator.export-diagnostic.v1',
+ pluginVersion: PLUGIN_VERSION,
+ kind: candidate.kind,
+ assetKind: candidate.assetKind,
+ filename: candidate.filename,
+ runtimeExportReady: candidate.runtimeExportReady === true,
+ detectedAttachment: anchorIds.map(function (anchorId) {
+ return {
+ anchorId: anchorId,
+ bodyPart: anchorById[anchorId] ? anchorById[anchorId].label : anchorId
+ };
+ }),
+ sourceMetadata: candidate.sourceResult && candidate.sourceResult.metadata || {},
+ manifest: candidate.manifest,
+ inventory: candidate.inventory,
+ issues: candidate.issues
+ };
+ }
+
+ function exportDiagnosticJson(candidate) {
+ return JSON.stringify(exportDiagnosticData(candidate), null, 2);
+ }
+
+ function copyTextToClipboard(value) {
+ const text = String(value == null ? '' : value);
+ if (typeof Blockbench !== 'undefined' && typeof Blockbench.setClipboardText === 'function') {
+ Blockbench.setClipboardText(text);
+ return Promise.resolve();
+ }
+ if (typeof navigator !== 'undefined' && navigator.clipboard &&
+ typeof navigator.clipboard.writeText === 'function') {
+ return navigator.clipboard.writeText(text);
+ }
+ if (typeof document !== 'undefined' && document.body) {
+ const field = document.createElement('textarea');
+ field.value = text;
+ field.setAttribute('readonly', 'readonly');
+ field.style.position = 'fixed';
+ field.style.opacity = '0';
+ document.body.appendChild(field);
+ field.select();
+ const copied = typeof document.execCommand === 'function' && document.execCommand('copy');
+ document.body.removeChild(field);
+ if (copied) return Promise.resolve();
+ }
+ return Promise.reject(new Error('Clipboard access is unavailable.'));
+ }
+
+ function exportReviewComponent(candidate) {
+ const anchorIds = detectedAccessoryAnchorIds(candidate);
+ const attachmentLabels = anchorIds.map(function (anchorId) {
+ return anchorById[anchorId] ? anchorById[anchorId].label : anchorId;
+ });
+ const metadata = candidate.sourceResult && candidate.sourceResult.metadata || {};
+ const animationMode = metadata.animationMode === ANIMATION_MODES.ANIMATED ? 'Animated' : 'Static';
+ let heading = 'Review ' + (candidate.kind === PROJECT_KINDS.EMOTE ? 'Emote' : 'Export');
+ let description = 'Confirm the detected project before exporting.';
+ let primaryVisual = '';
+ if (candidate.kind === PROJECT_KINDS.COSMETIC) {
+ heading = attachmentLabels.length === 1
+ ? attachmentLabels[0] + ' Detected'
+ : attachmentLabels.length > 1 ? 'Multiple Body Parts Detected' : 'Body Part Not Detected';
+ description = attachmentLabels.length === 1
+ ? 'This accessory will attach to the highlighted body part.'
+ : 'Move all accessory geometry beneath one [Model bodypart] group before exporting.';
+ primaryVisual = minecraftAttachmentDiagram(anchorIds);
+ } else if (candidate.kind === PROJECT_KINDS.CAPE) {
+ const cape = metadata.cape || {};
+ heading = '[COMING SOON]';
+ description = 'Cape authoring is available locally, but .bbmodel package export and upload are disabled.';
+ primaryVisual = '[COMING SOON]' +
+ '
Cape Upload Is Not Available Yet
' +
+ reviewText(cape.frameWidth || '?') + ' x ' + reviewText(cape.frameHeight || '?') +
+ ' per frame · ' + Number(cape.frameCount || 0) + ' frame(s)
';
+ }
+ return {
+ data: function () {
+ return {
+ heading: heading,
+ description: description,
+ copyStatus: ''
+ };
+ },
+ methods: {
+ copyDiagnostic: function () {
+ const component = this;
+ copyTextToClipboard(exportDiagnosticJson(candidate)).then(function () {
+ component.copyStatus = 'Raw export data copied to the clipboard.';
+ }).catch(function (error) {
+ component.copyStatus = error && error.message
+ ? error.message
+ : 'Could not copy export data.';
+ });
+ }
+ },
+ template: [
+ '',
+ '
{{ heading }}
',
+ '
{{ description }}
',
+ primaryVisual,
+ candidate.kind === PROJECT_KINDS.CAPE ? '' :
+ '
Animation detected automatically: ' +
+ animationMode + '',
+ '
',
+ '
{{ copyStatus }}
',
+ '
',
+ ' Advanced export details
',
+ exportReviewMarkup(candidate),
+ ' ',
+ '
'
+ ].join('')
+ };
+ }
+
+ function exportReviewMarkup(candidate) {
+ const inventory = candidate.inventory;
+ const errors = candidate.issues.filter(function (issue) { return issue.severity === 'error'; });
+ const warnings = candidate.issues.filter(function (issue) { return issue.severity === 'warning'; });
+ const packageStatus = 'Normalized package: canonical cosmiq.package.v2 content; ' +
+ 'editable Blockbench source is not embedded.
';
+ const categoryLabel = candidate.manifest.assetKind === CATEGORIES.ACCESSORY
+ ? 'Accessories'
+ : candidate.manifest.assetKind === CATEGORIES.EMOTE
+ ? 'Emotes'
+ : 'Capes';
+ const classification = 'Content Confirmation
' +
+ '- Category: ' + categoryLabel + '
' +
+ '- Animation: ' +
+ (candidate.manifest.clips.length ? 'Animated' : 'Static') +
+ '
' +
+ (candidate.kind === PROJECT_KINDS.COSMETIC
+ ? '- Body group: ' + reviewText(candidate.manifest.bodyGroup) + '
' +
+ '- Preview model: ' +
+ reviewText(candidate.manifest.previewCamera.pose.modelVariant) + '
'
+ : candidate.kind === PROJECT_KINDS.EMOTE
+ ? '- Actors: ' + Number(candidate.manifest.actors.length) + '
' +
+ '- Props: ' + (candidate.manifest.nodes.length ? 'Yes' : 'No') + '
' +
+ '- Movement: ' +
+ reviewText(candidate.manifest.movementPolicy && candidate.manifest.movementPolicy.mode) +
+ '
'
+ : '- Cape frame: ' +
+ reviewText(candidate.sourceResult.metadata.cape &&
+ candidate.sourceResult.metadata.cape.frameWidth) + ' x ' +
+ reviewText(candidate.sourceResult.metadata.cape &&
+ candidate.sourceResult.metadata.cape.frameHeight) + '
') +
+ '
Title, description, price, discovery tags, and the generated thumbnail remain outside the package.
';
+ const projectContent = candidate.kind === PROJECT_KINDS.EMOTE
+ ? 'Emote Actors and Objects
' +
+ boundedReviewList(inventory.actors, function (actor) {
+ return 'Actor ' + Number(actor.index) + ' — ' + reviewText(actor.id || actor.rootUuid);
+ }, 'No actors detected.') +
+ 'Player Bones
' + boundedReviewList(inventory.playerBones, function (bone) {
+ return reviewText(bone.name) + (bone.actorIndex ? ' (Actor ' + Number(bone.actorIndex) + ')' : '');
+ }, 'No player bones detected.') +
+ 'Object-Actors
' + boundedReviewList(inventory.objectActors, function (actor) {
+ return reviewText(actor.name || actor.id) + ' — ' +
+ (Array.isArray(actor.nodeUuids) ? actor.nodeUuids.length : 0) + ' child node(s)';
+ }, 'No Object-Actors included.') +
+ 'Object-Actor Descendants
' + boundedReviewList(inventory.objectActorNodes, function (node) {
+ return reviewText(node.name) + ' — ' + reviewText(node.uuid);
+ }, 'No Object-Actor descendants included.')
+ : 'Accessory Nodes and Branches
' +
+ 'Wide: ' + inventory.wideNodes.length + ' · Slim: ' + inventory.slimNodes.length +
+ ' · Universal/shared: ' + inventory.universalNodes.length + '
' +
+ boundedReviewList(inventory.cosmeticNodes, function (node) {
+ return reviewText(node.name) + ' — ' + reviewText(node.modelVariant || 'shared') +
+ (node.hasGeometry ? ' (geometry)' : '');
+ }, 'No cosmetic nodes included.');
+ const animationRows = boundedReviewList(inventory.animations, function (animation) {
+ const scope = animation.scope ? ' — ' + reviewText(animation.scope) : '';
+ const actor = animation.actorIndex ? ' Actor ' + Number(animation.actorIndex) : '';
+ return '' + reviewText(animation.name) + '' + scope + actor + ' — ' +
+ Number(animation.durationMs) + ' ms, ' + Number(animation.keyframeCount) + ' keyframe(s), ' +
+ animation.markers.length + ' marker(s)';
+ }, 'No authored animation content is included.');
+ const markerRows = boundedReviewList(inventory.markers, function (marker) {
+ return '' + reviewText(marker.name || '(unnamed)') + ' in ' +
+ reviewText(marker.animationName) + ' at ' +
+ (marker.atMs == null ? 'unresolved' : Number(marker.atMs) + ' ms');
+ }, 'No timeline markers included.');
+ const resourceRow = function (resource) {
+ return reviewText(resource.displayName || resource.id) + ' — ' +
+ reviewText(resource.type === RESOURCE_TYPES.SOUND
+ ? 'embedded Ogg Vorbis'
+ : resource.assetRef || 'authored particle effect');
+ };
+ const cueRow = function (cue) {
+ return reviewText(cue.id || cue.markerName) + ' — ' + reviewText(cue.markerName) + ' at ' +
+ (cue.atMs == null ? 'unresolved' : Number(cue.atMs) + ' ms') +
+ (cue.endAtMs == null ? '' : ' through ' + Number(cue.endAtMs) + ' ms');
+ };
+ const issueRows = boundedReviewList(candidate.issues, function (issue) {
+ return '' + reviewText(String(issue.severity || '').toUpperCase(), 16) + ' ' +
+ reviewText(issue.code, 96) + ': ' + reviewText(issue.message, 512);
+ }, 'No validation issues.');
+ return '' + packageStatus + classification + projectContent +
+ '
Geometry Objects
' + boundedReviewList(inventory.geometryObjects, function (item) {
+ return reviewText(item.name) + ' — ' + reviewText(item.type) +
+ (item.modelVariant ? ' / ' + reviewText(item.modelVariant) : '');
+ }, 'No geometry objects included.') +
+ '
Embedded Runtime PNGs
' + boundedReviewList(inventory.runtimePngs, function (texture) {
+ return reviewText(texture.name) + (texture.embeddedPng ? ' (embedded PNG)' : ' (invalid source)');
+ }, 'No runtime PNGs included.') +
+ '
Embedded Reference PNGs
' + boundedReviewList(inventory.referencePngs, function (texture) {
+ return reviewText(texture.name) + (texture.embeddedPng ? ' (embedded PNG)' : ' (invalid source)');
+ }, 'No reference PNGs included.') +
+ '
Animations
' + animationRows + '
Markers and Timestamps
' + markerRows +
+ '
Effect Locators
' + boundedReviewList(inventory.effectLocators, function (locator) {
+ return reviewText(locator.sourceName || locator.id) + ' — ' + reviewText(locator.role) +
+ ' / ' + reviewText(locator.resourceId || 'resource pending');
+ }, 'No effect locators included.') +
+ '
Particle Templates
' + boundedReviewList(inventory.particleResources, resourceRow, 'No particle templates referenced.') +
+ '
Particle Cues
' + boundedReviewList(inventory.particleCues, cueRow, 'No particle cues included.') +
+ '
Sound / Music References
' + boundedReviewList(inventory.soundResources, resourceRow, 'No sounds referenced.') +
+ '
Sound Cues
' + boundedReviewList(inventory.soundCues, cueRow, 'No sound cues included.') +
+ '
Creator Comments
' + boundedReviewList(inventory.comments, function (cue) {
+ return reviewText(cue.markerName) + ' at ' + (cue.atMs == null ? 'unresolved' : Number(cue.atMs) + ' ms') +
+ ' — ' + reviewText(cue.comment, AUTHORING_LIMITS.commentCharacters);
+ }, 'No creator comments included.') +
+ '
Unresolved References
' + boundedReviewList(inventory.unresolved, function (item) {
+ return reviewText(item.name || item.id) + ' — ' + reviewText(item.reason);
+ }, 'No unresolved references.') +
+ '
Unsupported Inputs (Never Bundled)
' + boundedReviewList(inventory.unsupported, function (item) {
+ return reviewText(item.type) + ' at ' + reviewText(item.location);
+ }, 'No unsupported non-Ogg audio, GIF, URL, or filesystem-path inputs detected.') +
+ '
Excluded Editor Paths
' + boundedReviewList(inventory.excludedInputs, function (item) {
+ return reviewText(item.location) + ' (excluded; embedded PNG/project data remains self-contained)';
+ }, 'No ordinary editor paths needed removal.') +
+ '
Omitted Empty Animations
' + boundedReviewList(inventory.omittedAnimations, function (animation) {
+ return reviewText(animation.name) + ' — ' + reviewText(animation.reason);
+ }, 'No truly empty animations were omitted.') +
+ '
Validation
' + errors.length + ' error(s), ' + warnings.length + ' warning(s).
' + issueRows +
+ '
';
+ }
+
+ function compileLiveCreatorExportCandidate() {
+ if (!isCosmiqProject()) {
+ Blockbench.showMessageBox({
+ title: 'No Cosmiq Project Open',
+ message: 'Create or import a Cosmiq cosmetic, emote, or cape before exporting a .bbmodel package.',
+ buttons: ['OK']
+ });
+ return null;
+ }
+ if (!refreshMetadata(true)) return null;
+ if (typeof Codecs === 'undefined' || !Codecs.project || typeof Codecs.project.compile !== 'function') {
+ Blockbench.showMessageBox({
+ title: 'Cosmiq Package Export Unavailable',
+ message: 'This Blockbench build does not expose the editable project codec.',
+ buttons: ['OK']
+ });
+ return null;
+ }
+ let rawProject;
+ try {
+ rawProject = Codecs.project.compile({
+ raw: true,
+ bitmaps: true,
+ absolute_paths: false,
+ reference_images: false
+ });
+ } catch (error) {
+ Blockbench.showMessageBox({
+ title: 'Cosmiq Package Export Failed',
+ message: 'Blockbench could not compile the editable project: ' + error.message,
+ buttons: ['OK']
+ });
+ return null;
+ }
+ return buildCreatorExportCandidate(liveSnapshot(), rawProject);
+ }
+
+ function showCreatorExportReview() {
+ if (isCosmiqCosmeticProject()) {
+ const armWidthInspection = inspectArmWidthGeometry(liveSnapshot());
+ if (armWidthInspection.requiresChoice) {
+ showArmWidthModeDialog(showCreatorExportReview);
+ return;
+ }
+ }
+ const candidate = compileLiveCreatorExportCandidate();
+ if (!candidate) return;
+ const reviewTitle = candidate.kind === PROJECT_KINDS.EMOTE
+ ? 'Review Cosmiq Emote Export'
+ : candidate.kind === PROJECT_KINDS.CAPE
+ ? 'Review Cosmiq Cape Export'
+ : 'Confirm Cosmiq Accessory Export';
+ openCreatorExportReview(candidate, reviewTitle);
+ }
+
+ function openCreatorExportReview(candidate, reviewTitle) {
+ const capeComingSoon = candidate.kind === PROJECT_KINDS.CAPE;
+ activeDialog = new Dialog('cosmiq_export_review', {
+ title: reviewTitle,
+ width: 760,
+ component: exportReviewComponent(candidate),
+ buttons: capeComingSoon ? ['Close'] : ['Confirm & Export .bbmodel', 'Cancel'],
+ confirmIndex: 0,
+ cancelIndex: capeComingSoon ? 0 : 1,
+ onConfirm: function () {
+ if (capeComingSoon) return true;
+ setTimeout(function () { performCreatorPackageExport(candidate); }, 0);
+ return true;
+ }
+ });
+ activeDialog.show();
+ }
+
+ function packageContentBytes(content) {
+ if (typeof Buffer === 'undefined') {
+ throw new Error('Path-aware export requires the Blockbench desktop application.');
+ }
+ if (content instanceof ArrayBuffer) return Buffer.from(new Uint8Array(content));
+ if (ArrayBuffer.isView(content)) {
+ return Buffer.from(content.buffer, content.byteOffset, content.byteLength);
+ }
+ if (Buffer.isBuffer(content)) return Buffer.from(content);
+ throw new Error('The compiled Cosmiq package is not a byte buffer.');
+ }
+
+ function mcpActionRequest(event) {
+ const detail = event && event.detail && typeof event.detail === 'object'
+ ? event.detail
+ : null;
+ const args = event && event.mcpArgs && typeof event.mcpArgs === 'object'
+ ? event.mcpArgs
+ : detail || {};
+ const respond = detail && typeof detail.respond === 'function'
+ ? detail.respond
+ : null;
+ const reject = detail && typeof detail.reject === 'function'
+ ? detail.reject
+ : null;
+ return {
+ active: Boolean(event && (event.mcpArgs || respond || reject)),
+ args: args,
+ respond: respond,
+ reject: reject
+ };
+ }
+
+ function writeAtomicDevelopmentFile(fs, path, destination, bytes) {
+ const directory = path.dirname(destination);
+ const temporary = path.join(
+ directory,
+ '.' + path.basename(destination) + '.' +
+ String(typeof process !== 'undefined' ? process.pid : 0) + '.' +
+ String(Date.now()) + '.tmp'
+ );
+ let descriptor = null;
+ try {
+ if (typeof fs.openSync === 'function' && typeof fs.fsyncSync === 'function' &&
+ typeof fs.closeSync === 'function') {
+ descriptor = fs.openSync(temporary, 'wx', 0o600);
+ fs.writeFileSync(descriptor, bytes);
+ fs.fsyncSync(descriptor);
+ fs.closeSync(descriptor);
+ descriptor = null;
+ } else {
+ fs.writeFileSync(temporary, bytes, {flag: 'wx', mode: 0o600});
+ }
+ fs.renameSync(temporary, destination);
+ } finally {
+ if (descriptor != null && typeof fs.closeSync === 'function') fs.closeSync(descriptor);
+ try {
+ fs.unlinkSync(temporary);
+ } catch (error) {
+ if (!error || error.code !== 'ENOENT') throw error;
+ }
+ }
+ }
+
+ function writeCreatorPackageToPath(candidate, outputPath, dependencies) {
+ if (!candidate || !candidate.content ||
+ candidate.issues.some(function (issue) { return issue.severity === 'error'; })) {
+ throw new Error('Only a valid reviewed Cosmiq package can be exported.');
+ }
+ const modules = dependencies || {};
+ const fs = modules.fs || (typeof require === 'function' ? require('node:fs') : null);
+ const path = modules.path || (typeof require === 'function' ? require('node:path') : null);
+ const cryptoModule = modules.crypto ||
+ (typeof require === 'function' ? require('node:crypto') : null);
+ if (!fs || !path || !cryptoModule) {
+ throw new Error('Path-aware export requires the Blockbench desktop application.');
+ }
+ const requestedPath = String(outputPath || '').trim();
+ if (!requestedPath || !path.isAbsolute(requestedPath)) {
+ throw new Error('Agent export requires an absolute outputPath.');
+ }
+ const destination = path.resolve(requestedPath);
+ if (path.extname(destination).toLowerCase() !== '.' + PACKAGE_EXTENSION) {
+ throw new Error('Agent export outputPath must end in .' + PACKAGE_EXTENSION + '.');
+ }
+ const directory = path.dirname(destination);
+ if (!fs.existsSync(directory) || !fs.statSync(directory).isDirectory()) {
+ throw new Error('Agent export output directory does not exist.');
+ }
+ if (fs.existsSync(destination)) {
+ const destinationInfo = typeof fs.lstatSync === 'function'
+ ? fs.lstatSync(destination)
+ : fs.statSync(destination);
+ if (destinationInfo.isDirectory() ||
+ (typeof destinationInfo.isSymbolicLink === 'function' &&
+ destinationInfo.isSymbolicLink())) {
+ throw new Error('Agent export refuses to overwrite a directory or symbolic link.');
+ }
+ }
+ const packageBytes = packageContentBytes(candidate.content);
+ writeAtomicDevelopmentFile(fs, path, destination, packageBytes);
+ const writtenBytes = fs.readFileSync(destination);
+ const sha256 = cryptoModule.createHash('sha256').update(writtenBytes).digest('hex');
+ return {
+ assetKind: candidate.assetKind,
+ byteLength: writtenBytes.byteLength,
+ fileName: path.basename(destination),
+ outputPath: destination,
+ sha256: sha256
+ };
+ }
+
+ function performCreatorPackageExport(candidate) {
+ if (!candidate || candidate.issues.some(function (issue) { return issue.severity === 'error'; }) ||
+ !candidate.content) {
+ showExportBlocked('Cosmiq Package Export Blocked', candidate ? candidate.issues : [makeIssue(
+ 'error', 'COSMIQ_PACKAGE_CANDIDATE', 'No reviewed export candidate is available.'
+ )]);
+ return;
+ }
+ const exportType = candidate.kind === PROJECT_KINDS.EMOTE
+ ? 'Cosmiq Emote Package'
+ : candidate.kind === PROJECT_KINDS.CAPE
+ ? 'Cosmiq Cape Package'
+ : 'Cosmiq Accessory Package';
+ Blockbench.export({
+ type: exportType,
+ extensions: [PACKAGE_EXTENSION],
+ name: candidate.filename,
+ content: candidate.content,
+ savetype: 'buffer'
+ }, function () {
+ const message = candidate.kind === PROJECT_KINDS.EMOTE
+ ? 'Exported the exact Cosmiq Emote Blockbench package.'
+ : candidate.kind === PROJECT_KINDS.CAPE
+ ? 'Exported the exact Cosmiq Cape Blockbench package.'
+ : 'Exported the exact Cosmiq Accessory Blockbench package.';
+ Blockbench.showQuickMessage(message);
+ });
+ }
+
+ function animationOverwriteAccepted(result) {
+ if (result === 0 || result === true) return true;
+ if (typeof result === 'string') {
+ return /^(?:yes|overwrite)/i.test(result.trim());
+ }
+ if (result && typeof result === 'object') {
+ if (result.button === 0 || result.index === 0 || result.confirmed === true) return true;
+ if (typeof result.button === 'string') {
+ return /^(?:yes|overwrite)/i.test(result.button.trim());
+ }
+ }
+ return false;
+ }
+
+ function confirmAnimationOverwrite(conflict) {
+ return new Promise(function (resolve) {
+ let settled = false;
+ function finish(result) {
+ if (settled) return;
+ settled = true;
+ resolve(animationOverwriteAccepted(result));
+ }
+ const returned = Blockbench.showMessageBox({
+ title: 'Animation Already Has Keyframes',
+ message:
+ 'WARNING: The existing "' + conflict.existingName + '" animation contains ' +
+ conflict.existingKeyframes + ' keyframe' +
+ (conflict.existingKeyframes === 1 ? '' : 's') + '.\n\n' +
+ 'Importing "' + conflict.importedName + '" will replace that animation and its ' +
+ 'keyframes. Do you want to overwrite the existing animation?',
+ buttons: ['Yes, Overwrite', 'No, Keep Existing'],
+ confirm: 0,
+ cancel: 1
+ }, finish);
+ if (returned && typeof returned.then === 'function') {
+ returned.then(finish, function () { finish(1); });
+ }
+ });
+ }
+
+ async function resolvePreparedAnimationImport(project) {
+ const state = project && project.__cosmiqAnimationImport;
+ if (!state) return null;
+ const decisions = Object.assign({}, state.decisions || {});
+ const initialPlan = state.plan || applyPreparedAnimationImportDecisions(project, decisions);
+ for (const conflict of initialPlan.conflicts) {
+ decisions[conflict.decisionId] = await confirmAnimationOverwrite(conflict);
+ }
+ return applyPreparedAnimationImportDecisions(project, decisions);
+ }
+
+ function importedAnimationSummary(plan) {
+ if (!plan) return '';
+ const notes = [];
+ if (plan.autoReplaced.length === 1) {
+ notes.push(
+ '"' + plan.autoReplaced[0].existingName +
+ '" had no keyframes, so it was safe to replace automatically.'
+ );
+ } else if (plan.autoReplaced.length > 1) {
+ notes.push(
+ plan.autoReplaced.length +
+ ' empty animation placeholders had no keyframes and were replaced automatically.'
+ );
+ }
+ if (plan.overwritten.length) {
+ notes.push('Overwrote ' + plan.overwritten.length + ' filled animation' +
+ (plan.overwritten.length === 1 ? '.' : 's.'));
+ }
+ if (plan.kept.length) {
+ notes.push('Kept ' + plan.kept.length + ' existing filled animation' +
+ (plan.kept.length === 1 ? '.' : 's.'));
+ }
+ return notes.length ? ' ' + notes.join(' ') : '';
+ }
+
+ function importCosmiqPackageAction() {
+ if (typeof Blockbench.import !== 'function') {
+ Blockbench.showMessageBox({
+ title: 'Cosmiq Import Unavailable',
+ message: 'This Blockbench build does not expose the file importer.',
+ buttons: ['OK']
+ });
+ return;
+ }
+ Blockbench.import({
+ resource_id: 'model',
+ extensions: LEGACY_IMPORT_EXTENSIONS.slice(),
+ type: 'Cosmiq Accessory or Blockbench Model',
+ readtype: 'buffer',
+ multiple: false
+ }, function (files) {
+ if (!files || !files[0]) {
+ return;
+ }
+ const file = files[0];
+ let decoded;
+ try {
+ decoded = parseLegacyCosmeticImport(
+ file.content,
+ file.name || file.path || 'Imported Accessory'
+ );
+ } catch (error) {
+ Blockbench.showMessageBox({
+ title: 'Cosmiq Import Blocked',
+ message: error.message,
+ buttons: ['OK']
+ });
+ return;
+ }
+ prepareLegacyCosmeticImport(decoded).then(function (project) {
+ return resolvePreparedAnimationImport(project).then(function (animationPlan) {
+ if (activeDialog && typeof activeDialog.hide === 'function') activeDialog.hide();
+ if (typeof Codecs === 'undefined' || !Codecs.project ||
+ typeof Codecs.project.load !== 'function') {
+ throw new Error('This Blockbench build cannot open the imported project.');
+ }
+ Codecs.project.load(project, {path: ''});
+ refreshMetadata(true);
+ if (typeof Validator !== 'undefined') Validator.validate();
+ if (typeof Group !== 'undefined' && Array.isArray(Group.all)) {
+ const imported = Group.all.find(function (group) {
+ return group.name === IMPORTED_GROUP_NAME;
+ });
+ if (imported && typeof imported.select === 'function') imported.select();
+ }
+ Blockbench.showQuickMessage(
+ 'Imported ' + decoded.sourceVersion.toUpperCase() +
+ ' accessory into the ' + IMPORTED_GROUP_NAME + ' group.' +
+ importedAnimationSummary(animationPlan),
+ 5200
+ );
+ });
+ }).catch(function (error) {
+ Blockbench.showMessageBox({
+ title: 'Cosmiq Import Blocked',
+ message: error && error.message ? error.message : String(error),
+ buttons: ['OK']
+ });
+ });
+ });
+ return true;
+ }
+
+ function exportCosmiqProjectAction(event) {
+ const request = mcpActionRequest(event);
+ if (!request.active) {
+ showCreatorExportReview();
+ return null;
+ }
+ try {
+ if (isCosmiqCosmeticProject() &&
+ Object.hasOwn(request.args, 'armWidthMode')) {
+ const requestedArmWidthMode = normalizeArmWidthMode(request.args.armWidthMode);
+ if (requestedArmWidthMode === ARM_WIDTH_MODES.UNCONFIGURED) {
+ throw new Error('args.armWidthMode must be "shared" or "dependent".');
+ }
+ Project.cosmiq_arm_width_mode = requestedArmWidthMode;
+ refreshMetadata(true);
+ }
+ if (isCosmiqCosmeticProject() &&
+ inspectArmWidthGeometry(liveSnapshot()).requiresChoice) {
+ throw new Error(
+ 'Arm-attached geometry needs an explicit fit choice. Set args.armWidthMode to ' +
+ '"shared" or "dependent".'
+ );
+ }
+ const outputPath = typeof request.args.outputPath === 'string'
+ ? request.args.outputPath.trim()
+ : '';
+ if (!outputPath) {
+ throw new Error(
+ 'Agent export requires args.outputPath and will not open a native file chooser.'
+ );
+ }
+ const candidate = compileLiveCreatorExportCandidate();
+ if (!candidate) {
+ throw new Error('Cosmiq could not compile an export candidate.');
+ }
+ const result = writeCreatorPackageToPath(candidate, outputPath);
+ if (request.respond) request.respond(result);
+ Blockbench.showQuickMessage('Exported the exact Cosmiq package to ' + result.fileName + '.');
+ return result;
+ } catch (error) {
+ if (request.reject) request.reject(error);
+ return null;
+ }
+ }
+
+ function validateProjectAction() {
+ const result = refreshMetadata(true);
+ if (!result) {
+ return;
+ }
+ Blockbench.showMessageBox({
+ title: isCosmiqEmoteProject()
+ ? 'Cosmiq Emote Scene Validation'
+ : isCosmiqCapeProject()
+ ? 'Cosmiq Cape Validation'
+ : 'Cosmiq Accessory Validation',
+ message: issueSummary(result),
+ buttons: ['OK']
+ });
+ if (typeof Validator !== 'undefined') {
+ Validator.validate();
+ }
+ }
+
+ function registerValidator() {
+ if (typeof ValidatorCheck === 'undefined') {
+ return;
+ }
+ validatorCheck = new ValidatorCheck('cosmiq_project_structure', {
+ condition: function () {
+ return isCosmiqProject();
+ },
+ update_triggers: ['finished_edit', 'update_selection', 'select_project'],
+ run: function () {
+ const result = isCosmiqEmoteProject()
+ ? buildEmoteSourceMetadata(liveSnapshot())
+ : isCosmiqCapeProject()
+ ? buildCapeSourceMetadata(liveSnapshot())
+ : buildSourceMetadata(liveSnapshot());
+ const check = this;
+ result.issues.slice(0, 32).forEach(function (issue) {
+ if (issue.severity === 'info') {
+ return;
+ }
+ const item = {
+ message: issue.code + ': ' + issue.message
+ };
+ if (issue.sourceUuid) {
+ const sourceUuid = issue.sourceUuid;
+ item.buttons = [{
+ name: 'Select Source',
+ icon: 'my_location',
+ click: function () {
+ const node = liveNodeByUuid(sourceUuid);
+ if (node && typeof node.select === 'function') {
+ node.select();
+ }
+ }
+ }];
+ }
+ if (issue.severity === 'error') {
+ check.fail(item);
+ } else {
+ check.warn(item);
+ }
+ });
+ }
+ });
+ }
+
+ function addToolAction(id, options) {
+ const action = new Action(id, options);
+ MenuBar.menus.tools.addAction(action);
+ actions.push(action);
+ return action;
+ }
+
+ function showPreviewCameraDialog() {
+ if (!isCosmiqProject()) return;
+ const emote = isCosmiqEmoteProject();
+ activeDialog = new Dialog('cosmiq_preview_camera', {
+ title: 'Cosmiq Thumbnail Camera',
+ form: {
+ position_x: {label: 'Position X', type: 'number', value: Number(Project.cosmiq_camera_x)},
+ position_y: {label: 'Position Y', type: 'number', value: Number(Project.cosmiq_camera_y)},
+ position_z: {label: 'Position Z', type: 'number', value: Number(Project.cosmiq_camera_z)},
+ pitch: {label: 'Pitch Degrees', type: 'number', value: Number(Project.cosmiq_camera_pitch)},
+ yaw: {label: 'Yaw Degrees', type: 'number', value: Number(Project.cosmiq_camera_yaw)},
+ roll: {label: 'Roll Degrees', type: 'number', value: Number(Project.cosmiq_camera_roll)},
+ fov: {label: 'Vertical FOV', type: 'number', min: 10, max: 90, value: Number(Project.cosmiq_camera_fov)},
+ clip_id: {
+ label: emote ? 'Required Preview Clip ID' : 'Preview Clip ID (Optional)',
+ type: 'text',
+ value: Project.cosmiq_camera_clip_id || '',
+ description: 'Use the selected animation’s Cosmiq Clip ID. Empty means bind pose where allowed.'
+ },
+ time_ms: {
+ label: 'Preview Time (ms)',
+ type: 'number',
+ min: 0,
+ value: Number(Project.cosmiq_camera_time_ms) || 0
+ },
+ model_variant: {
+ label: 'Accessory Model Variant',
+ type: 'select',
+ options: {universal: 'Universal', wide: 'Wide', slim: 'Slim'},
+ value: Project.cosmiq_camera_model_variant || 'wide',
+ condition: function () { return isCosmiqCosmeticProject(); }
+ },
+ visible_props: {
+ label: 'Visible Prop Root IDs',
+ type: 'text',
+ value: Project.cosmiq_camera_visible_props || '',
+ description: 'Emote-only comma-separated normalized Prop root IDs.',
+ condition: function () { return isCosmiqEmoteProject(); }
+ }
+ },
+ onConfirm: function (form) {
+ const numeric = [
+ form.position_x, form.position_y, form.position_z,
+ form.pitch, form.yaw, form.roll, form.fov, form.time_ms
+ ].map(Number);
+ if (numeric.some(function (value) { return !Number.isFinite(value); }) ||
+ numeric[6] < 10 || numeric[6] > 90 || numeric[7] < 0) {
+ Blockbench.showQuickMessage('Camera values are outside the Cosmiq standard.');
+ return false;
+ }
+ Project.cosmiq_camera_x = numeric[0];
+ Project.cosmiq_camera_y = numeric[1];
+ Project.cosmiq_camera_z = numeric[2];
+ Project.cosmiq_camera_pitch = numeric[3];
+ Project.cosmiq_camera_yaw = numeric[4];
+ Project.cosmiq_camera_roll = numeric[5];
+ Project.cosmiq_camera_fov = numeric[6];
+ Project.cosmiq_camera_clip_id = String(form.clip_id || '').trim();
+ Project.cosmiq_camera_time_ms = Math.round(numeric[7]);
+ Project.cosmiq_camera_model_variant = form.model_variant || 'wide';
+ Project.cosmiq_camera_visible_props = String(form.visible_props || '').trim();
+ Project.saved = false;
+ return true;
+ }
+ });
+ activeDialog.show();
+ }
+
+ function registerCosmiqMenu() {
+ if (typeof BarMenu === 'undefined' || typeof MenuBar === 'undefined') {
+ return;
+ }
+ cosmiqMenu = new BarMenu('cosmiq', [
+ {
+ id: 'cosmiq_menu_camera',
+ name: 'Thumbnail Camera',
+ description: 'Configure the canonical Camera Node and thumbnail pose.',
+ icon: 'photo_camera',
+ condition: isCosmiqProject,
+ click: showPreviewCameraDialog
+ },
+ {
+ id: 'cosmiq_menu_guides',
+ name: 'Guides',
+ description: 'Open the Cosmiq Creator guides.',
+ icon: 'menu_book',
+ click: function () { Blockbench.openLink(CREATOR_GUIDES_URL); }
+ },
+ {
+ id: 'cosmiq_menu_import',
+ name: 'Import Existing Accessory',
+ description: 'Migrate a V1/V2 .cosmiq or Blockbench .bbmodel into an IMPORTED group.',
+ icon: 'file_open',
+ click: importCosmiqPackageAction
+ },
+ {
+ id: 'cosmiq_menu_export',
+ name: 'Export',
+ description: 'Review and export the active Cosmiq project.',
+ icon: 'inventory_2',
+ click: exportCosmiqProjectAction
+ }
+ ], {
+ name: 'Cosmiq',
+ icon: 'accessibility_new'
+ });
+ if (typeof MenuBar.addMenu === 'function') {
+ MenuBar.addMenu(cosmiqMenu, 'tools');
+ } else if (typeof MenuBar.update === 'function') {
+ MenuBar.update();
+ }
+ }
+
+ function registerActions() {
+ addToolAction('cosmiq_move_to_anchor', {
+ name: 'Cosmiq: Move Selection To Player Anchor',
+ description: 'Parent selected cosmetic objects beneath a stable Cosmiq player anchor.',
+ icon: 'link',
+ condition: isCosmiqCosmeticProject,
+ click: moveSelectionToAnchorDialog
+ });
+ addToolAction('cosmiq_normalize_legacy_anchors', {
+ name: 'Cosmiq: Normalize Legacy Player Anchors',
+ description: 'Convert legacy torso height and left/right limb pivots to the canonical V3 rest pose.',
+ icon: 'published_with_changes',
+ condition: isCosmiqCosmeticProject,
+ click: normalizeLegacyPlayerAnchorsAction
+ });
+ addToolAction('cosmiq_validate_cosmetic', {
+ name: 'Cosmiq: Validate And Refresh Metadata',
+ description: 'Recompute parent anchors, stable IDs, state bindings, budgets, and reserved markers.',
+ icon: 'fact_check',
+ condition: isCosmiqCosmeticProject,
+ click: validateProjectAction
+ });
+ addToolAction('cosmiq_export_accessory', {
+ name: 'Cosmiq: Export Accessory',
+ description: 'Review and export the active Cosmiq accessory.',
+ icon: 'inventory_2',
+ condition: isCosmiqCosmeticProject,
+ click: exportCosmiqProjectAction
+ });
+ addToolAction('cosmiq_add_emote_animation', {
+ name: 'Cosmiq: Add Emote Animation',
+ description: 'Add an editable animation lane to the active Cosmiq emote.',
+ icon: 'add',
+ condition: isCosmiqEmoteProject,
+ click: showAddEmoteAnimationDialog
+ });
+ addToolAction('cosmiq_configure_emote_animation', {
+ name: 'Cosmiq: Configure Selected Emote Animation',
+ description: 'Configure the selected animation lane and its actor scope.',
+ icon: 'tune',
+ condition: isCosmiqEmoteProject,
+ click: showConfigureEmoteAnimationDialog
+ });
+ addToolAction('cosmiq_validate_emote_scene', {
+ name: 'Cosmiq: Validate Emote Scene',
+ description: 'Check numbered actors, semantic player bones, Object-Actor folders, and animation scopes.',
+ icon: 'fact_check',
+ condition: isCosmiqEmoteProject,
+ click: validateProjectAction
+ });
+ addToolAction('cosmiq_export_emote', {
+ name: 'Cosmiq: Export Emote',
+ description: 'Review and export the active Cosmiq emote.',
+ icon: 'inventory_2',
+ condition: isCosmiqEmoteProject,
+ click: exportCosmiqProjectAction
+ });
+ }
+
+ function registerFormat() {
+ creatorFormat = new ModelFormat(FORMAT_ID, {
+ name: 'Cosmiq Creator',
+ description: 'Cosmiq accessories and numbered multi-actor emote scenes.',
+ icon: 'accessibility_new',
+ category: 'minecraft',
+ target: ['Minecraft: Java Edition'],
+ show_on_start_screen: true,
+ format_page: {
+ component: formatPageComponent()
+ },
+ box_uv: false,
+ optional_box_uv: true,
+ single_texture: false,
+ per_texture_uv_size: true,
+ bone_rig: true,
+ armature_rig: true,
+ centered_grid: true,
+ block_size: 16,
+ rotate_cubes: true,
+ integer_size: false,
+ meshes: true,
+ locators: true,
+ uv_rotation: true,
+ animation_mode: true,
+ animation_files: false,
+ animation_grouping: 'custom',
+ edit_mode: true,
+ paint_mode: true,
+ display_mode: false,
+ pose_mode: false,
+ animated_textures: false,
+ select_texture_for_particles: false
+ });
+ creatorFormat.new = function () {
+ showCreatorTypeDialog();
+ return true;
+ };
+ }
+
+ function cleanup() {
+ if (activeDialog) {
+ activeDialog.delete();
+ activeDialog = null;
+ }
+ if (pluginStyle && pluginStyle.parentNode) {
+ pluginStyle.parentNode.removeChild(pluginStyle);
+ }
+ pluginStyle = null;
+ actions.splice(0).forEach(function (action) {
+ action.delete();
+ });
+ if (cosmiqMenu) {
+ if (typeof MenuBar !== 'undefined' && MenuBar.menus && MenuBar.menus.cosmiq === cosmiqMenu) {
+ delete MenuBar.menus.cosmiq;
+ if (typeof MenuBar.update === 'function') {
+ MenuBar.update();
+ }
+ }
+ cosmiqMenu.delete();
+ cosmiqMenu = null;
+ }
+ if (validatorCheck) {
+ validatorCheck.delete();
+ validatorCheck = null;
+ }
+ listeners.splice(0).forEach(function (listener) {
+ if (listener.handle && typeof listener.handle.delete === 'function') {
+ listener.handle.delete();
+ } else if (typeof Blockbench.removeListener === 'function') {
+ Blockbench.removeListener(listener.eventName, listener.callback);
+ }
+ });
+ properties.splice(0).reverse().forEach(function (property) {
+ property.delete();
+ });
+ if (creatorFormat) {
+ creatorFormat.delete();
+ creatorFormat = null;
+ }
+ }
+
+ function registerBlockbenchPlugin() {
+ Plugin.register(PLUGIN_ID, {
+ title: 'Cosmiq Creator',
+ author: PLUGIN_AUTHOR,
+ creation_date: PLUGIN_CREATION_DATE,
+ description: 'Create anchored accessories and editable numbered multi-actor emote scenes.',
+ website: PROJECT_WEBSITE_URL,
+ repository: PROJECT_REPOSITORY_URL,
+ bug_tracker: PROJECT_REPOSITORY_URL + '/issues',
+ icon: 'accessibility_new',
+ version: PLUGIN_VERSION,
+ min_version: '5.0.0',
+ variant: 'both',
+ tags: ['Minecraft: Java Edition'],
+ await_loading: true,
+ contributes: {
+ formats: [FORMAT_ID]
+ },
+ onload: function () {
+ const wasActive = typeof Project !== 'undefined' && Project &&
+ Project.format && Project.format.id === FORMAT_ID;
+ registerFormat();
+ installCreatorStyles();
+ registerProperties();
+ registerActions();
+ registerCosmiqMenu();
+ registerValidator();
+ registerListener('save_project', onSaveProject);
+ registerListener('load_project', onLoadProject);
+ registerListener('select_project', onLoadProject);
+ if (wasActive && creatorFormat && typeof creatorFormat.select === 'function') {
+ creatorFormat.select();
+ onLoadProject();
+ }
+ },
+ onunload: cleanup
+ });
+ }
+
+ const api = {
+ PLUGIN_ID: PLUGIN_ID,
+ FORMAT_ID: FORMAT_ID,
+ PLUGIN_VERSION: PLUGIN_VERSION,
+ PLUGIN_AUTHOR: PLUGIN_AUTHOR,
+ PLUGIN_CREATION_DATE: PLUGIN_CREATION_DATE,
+ PROJECT_WEBSITE_URL: PROJECT_WEBSITE_URL,
+ CREATOR_GUIDES_URL: CREATOR_GUIDES_URL,
+ COMMUNITY_DISCORD_URL: COMMUNITY_DISCORD_URL,
+ YOUTUBE_GUIDES_URL: YOUTUBE_GUIDES_URL,
+ PROJECT_REPOSITORY_URL: PROJECT_REPOSITORY_URL,
+ FORMAT_BANNER_FILE: FORMAT_BANNER_FILE,
+ FORMAT_BANNER_DATA_URL: FORMAT_BANNER_DATA_URL,
+ BASE_TEMPLATE_FILE: BASE_TEMPLATE_FILE,
+ BASE_TEMPLATE_GZIP_BASE64: BASE_TEMPLATE_GZIP_BASE64,
+ BASE_TEMPLATE_SHA256: BASE_TEMPLATE_SHA256,
+ IMPORTED_GROUP_NAME: IMPORTED_GROUP_NAME,
+ LEGACY_IMPORT_EXTENSIONS: LEGACY_IMPORT_EXTENSIONS,
+ PLUGIN_ABOUT: PLUGIN_ABOUT,
+ SOURCE_SCHEMA: SOURCE_SCHEMA,
+ CAPE_SOURCE_SCHEMA: CAPE_SOURCE_SCHEMA,
+ EMOTE_SOURCE_SCHEMA: EMOTE_SOURCE_SCHEMA,
+ EMOTE_LOGIC_SCHEMA: EMOTE_LOGIC_SCHEMA,
+ EMOTE_LOGIC_LIMITS: EMOTE_LOGIC_LIMITS,
+ COSMETIC_FORMAT_VERSION: COSMETIC_FORMAT_VERSION,
+ CATEGORIES: CATEGORIES,
+ ANIMATION_MODES: ANIMATION_MODES,
+ PACKAGE_LIMITS: PACKAGE_LIMITS,
+ AUTHORING_LIMITS: AUTHORING_LIMITS,
+ LIMITS: LIMITS,
+ RESOURCE_TYPES: RESOURCE_TYPES,
+ SOUND_REFERENCE_TYPES: SOUND_REFERENCE_TYPES,
+ CUE_TYPES: CUE_TYPES,
+ ROLES: ROLES,
+ BLOCKBENCH_NODE_ROLE_VALUES: BLOCKBENCH_NODE_ROLE_VALUES,
+ BLOCKBENCH_PLAYER_BONE_VALUES: BLOCKBENCH_PLAYER_BONE_VALUES,
+ ANCHORS: ANCHORS,
+ TEMPLATE_ATTACHMENT_POINTS: TEMPLATE_ATTACHMENT_POINTS,
+ BODY_PART_FIELDS: BODY_PART_FIELDS,
+ ALL_BODY_PART_ANCHOR_IDS: ALL_BODY_PART_ANCHOR_IDS,
+ BODY_SLOTS: BODY_SLOTS,
+ CREATOR_CHOICES: CREATOR_CHOICES,
+ BODY_TYPE_CHOICES: BODY_TYPE_CHOICES,
+ EMOTE_RIG_CHOICES: EMOTE_RIG_CHOICES,
+ CAPE_RESOLUTIONS: CAPE_RESOLUTIONS,
+ COSMETIC_MODE_CHOICES: COSMETIC_MODE_CHOICES,
+ YES_NO_CHOICES: YES_NO_CHOICES,
+ CREATION_FLOW_STEPS: CREATION_FLOW_STEPS,
+ COSMETIC_FLOW_STEPS: COSMETIC_FLOW_STEPS,
+ EMOTE_FLOW_STEPS: EMOTE_FLOW_STEPS,
+ CAPE_FLOW_STEPS: CAPE_FLOW_STEPS,
+ MODEL_VARIANTS: MODEL_VARIANTS,
+ ARM_WIDTH_MODES: ARM_WIDTH_MODES,
+ PROJECT_KINDS: PROJECT_KINDS,
+ EMOTE_ACTOR_MIN: EMOTE_ACTOR_MIN,
+ EMOTE_ACTOR_MAX: EMOTE_ACTOR_MAX,
+ EMOTE_ACTOR_OFFSETS: EMOTE_ACTOR_OFFSETS,
+ EMOTE_ANIMATION_SCOPES: EMOTE_ANIMATION_SCOPES,
+ EMOTE_RIG_PROFILES: EMOTE_RIG_PROFILES,
+ EMOTE_PLAYER_BONES: EMOTE_PLAYER_BONES,
+ EMOTE_PRECISE_PLAYER_BONES: EMOTE_PRECISE_PLAYER_BONES,
+ SIMPLE_ARMATURE_UUID: SIMPLE_ARMATURE_UUID,
+ SIMPLE_BONE_UUIDS: SIMPLE_BONE_UUIDS,
+ PRECISE_ARMATURE_UUID: PRECISE_ARMATURE_UUID,
+ PRECISE_BONE_UUIDS: PRECISE_BONE_UUIDS,
+ TRIGGERS: TRIGGERS,
+ PLAYER_APPEARANCE_PARTS: PLAYER_APPEARANCE_PARTS,
+ normalizePlayerAppearance: normalizePlayerAppearance,
+ normalizeToken: normalizeToken,
+ normalizeProjectId: normalizeProjectId,
+ normalizeAssetRef: normalizeAssetRef,
+ isRawMediaReference: isRawMediaReference,
+ normalizeResourceReference: normalizeResourceReference,
+ normalizeTimelineCue: normalizeTimelineCue,
+ normalizeCosmeticSettings: normalizeCosmeticSettings,
+ authoringStateFromMetadata: authoringStateFromMetadata,
+ resourceContainsRawMedia: resourceContainsRawMedia,
+ collectTimelineMarkers: collectTimelineMarkers,
+ buildAuthoringMetadata: buildAuthoringMetadata,
+ creationTypeComponent: creationTypeComponent,
+ bodyTypeComponent: bodyTypeComponent,
+ emoteRigTypeComponent: emoteRigTypeComponent,
+ emoteCreationReviewMarkup: emoteCreationReviewMarkup,
+ emoteBooleanComponent: emoteBooleanComponent,
+ wizardProgressMarkup: wizardProgressMarkup,
+ wizardPageHeading: wizardPageHeading,
+ formatPageComponent: formatPageComponent,
+ normalizeEmoteDraftName: normalizeEmoteDraftName,
+ normalizeCreationName: normalizeCreationName,
+ creationCategoryLabel: creationCategoryLabel,
+ creationProjectName: creationProjectName,
+ creationProjectId: creationProjectId,
+ defaultSimpleCosmeticDraft: defaultSimpleCosmeticDraft,
+ defaultCapeTemplateDraft: defaultCapeTemplateDraft,
+ capeResolutionFromId: capeResolutionFromId,
+ mergeSimpleCosmeticDraft: mergeSimpleCosmeticDraft,
+ defaultEmoteDraft: defaultEmoteDraft,
+ mergeEmoteDraft: mergeEmoteDraft,
+ verifyBaseTemplateStructure: verifyBaseTemplateStructure,
+ verifySimpleRigStructure: verifySimpleRigStructure,
+ convertToSimpleRig: convertToSimpleRig,
+ verifyPreciseRigStructure: verifyPreciseRigStructure,
+ convertToPreciseRig: convertToPreciseRig,
+ annotateBaseTemplate: annotateBaseTemplate,
+ scaleTemplateTextureUvs: scaleTemplateTextureUvs,
+ decodeEmbeddedBaseTemplate: decodeEmbeddedBaseTemplate,
+ loadBaseTemplateJson: loadBaseTemplateJson,
+ parseLegacyV1Container: parseLegacyV1Container,
+ v2RuntimeToBbmodel: v2RuntimeToBbmodel,
+ parseLegacyCosmeticImport: parseLegacyCosmeticImport,
+ extractImportedCosmetic: extractImportedCosmetic,
+ animationImportIdentity: animationImportIdentity,
+ planImportedAnimationMerge: planImportedAnimationMerge,
+ applyPreparedAnimationImportDecisions: applyPreparedAnimationImportDecisions,
+ importedAnimationSummary: importedAnimationSummary,
+ prepareLegacyCosmeticImport: prepareLegacyCosmeticImport,
+ startBaseTemplateProject: startBaseTemplateProject,
+ normalizeBodySlots: normalizeBodySlots,
+ selectedAnchorIdsFromForm: selectedAnchorIdsFromForm,
+ newCosmeticAnchorIds: newCosmeticAnchorIds,
+ bodySlotsForAnchors: bodySlotsForAnchors,
+ bodySlotsFromNodeBindings: bodySlotsFromNodeBindings,
+ modelVariantFromNodeBindings: modelVariantFromNodeBindings,
+ normalizeArmWidthMode: normalizeArmWidthMode,
+ inspectArmWidthGeometry: inspectArmWidthGeometry,
+ modelVariantForArmWidthMode: modelVariantForArmWidthMode,
+ textureVariantsFromTextures: textureVariantsFromTextures,
+ validateReferenceSkinFile: validateReferenceSkinFile,
+ defaultCapeTextureDataUrl: defaultCapeTextureDataUrl,
+ capeTemplateTexture: capeTemplateTexture,
+ createAnchorHierarchy: createAnchorHierarchy,
+ createCapeTemplateGeometry: createCapeTemplateGeometry,
+ createCapeTemplateProject: createCapeTemplateProject,
+ createEmptyStateAnimations: createEmptyStateAnimations,
+ emoteActorOffset: emoteActorOffset,
+ createEmoteSceneHierarchy: createEmoteSceneHierarchy,
+ createEmptyEmoteAnimations: createEmptyEmoteAnimations,
+ emoteActorOptions: emoteActorOptions,
+ emoteAnimationScopeOptions: emoteAnimationScopeOptions,
+ createEmoteProject: createEmoteProject,
+ emoteObjectActors: emoteObjectActors,
+ defaultEmoteRuleSource: defaultEmoteRuleSource,
+ defaultEmoteLogicGraph: defaultEmoteLogicGraph,
+ inspectEmoteLogicAttachment: inspectEmoteLogicAttachment,
+ buildEmoteSourceMetadata: buildEmoteSourceMetadata,
+ repairPreciseLegHierarchy: repairPreciseLegHierarchy,
+ repairCapeUpperTorsoHierarchy: repairCapeUpperTorsoHierarchy,
+ activeCosmiqProjectKind: activeCosmiqProjectKind,
+ projectKindFromMetadata: projectKindFromMetadata,
+ isCosmiqCosmeticProject: isCosmiqCosmeticProject,
+ isCosmiqEmoteProject: isCosmiqEmoteProject,
+ isCosmiqCapeProject: isCosmiqCapeProject,
+ runtimeId: runtimeId,
+ parseParticleMarkerName: parseParticleMarkerName,
+ parseAnimationName: parseAnimationName,
+ resolveTrigger: resolveTrigger,
+ buildSourceMetadata: buildSourceMetadata,
+ legacyAnchorNormalizationPlan: legacyAnchorNormalizationPlan,
+ normalizePackagedNodeVisibility: normalizePackagedNodeVisibility,
+ bakeSourcePlayerAnimation: bakeSourcePlayerAnimation,
+ buildCapeSourceMetadata: buildCapeSourceMetadata,
+ animationKeyframeCount: animationKeyframeCount,
+ animationsWithKeyframes: animationsWithKeyframes,
+ animationHasPackageContent: animationHasPackageContent,
+ animationsWithPackageContent: animationsWithPackageContent,
+ COSMIQ_STANDARD_MAGIC: COSMIQ_STANDARD_MAGIC,
+ COSMIQ_STANDARD_SCHEMA: PACKAGE_SCHEMA,
+ COSMIQ_STANDARD_COORDINATES: COSMIQ_STANDARD_COORDINATES,
+ COSMIQ_STANDARD_CONTAINER_VERSION: COSMIQ_STANDARD_CONTAINER_VERSION,
+ COSMIQ_STANDARD_HEADER_BYTES: COSMIQ_STANDARD_HEADER_BYTES,
+ COSMIQ_STANDARD_DIRECTORY_ENTRY_BYTES: COSMIQ_STANDARD_DIRECTORY_ENTRY_BYTES,
+ COSMIQ_STANDARD_SECTION_TYPES: COSMIQ_STANDARD_SECTION_TYPES,
+ CosmiqValidationError: CosmiqValidationError,
+ canonicalCosmiqJson: canonicalCosmiqJson,
+ encodeCosmiqPackage: encodeCosmiqPackage,
+ encodeCosmiqBbmodel: encodeCosmiqBbmodel,
+ parseAndValidateCosmiq: parseAndValidateCosmiq,
+ parseAndValidateCosmiqRuntime: parseAndValidateCosmiqRuntime,
+ compileBlockbenchCosmiqPackage: compileBlockbenchCosmiqPackage,
+ packageSnapshot: packageSnapshot,
+ cosmeticExportSummary: cosmeticExportSummary,
+ buildExportInventory: buildExportInventory,
+ buildCreatorExportCandidate: buildCreatorExportCandidate,
+ exportReviewMarkup: exportReviewMarkup,
+ detectedAccessoryAnchorIds: detectedAccessoryAnchorIds,
+ minecraftAttachmentDiagram: minecraftAttachmentDiagram,
+ exportDiagnosticData: exportDiagnosticData,
+ exportDiagnosticJson: exportDiagnosticJson,
+ exportReviewComponent: exportReviewComponent,
+ mcpActionRequest: mcpActionRequest,
+ writeCreatorPackageToPath: writeCreatorPackageToPath,
+ exportCosmiqProjectAction: exportCosmiqProjectAction,
+ showPreviewCameraDialog: showPreviewCameraDialog,
+ registerBlockbenchPlugin: registerBlockbenchPlugin
+ };
+
+ if (typeof module === 'object' && module && module.exports) {
+ module.exports = api;
+ }
+ if (typeof Plugin !== 'undefined' && typeof Blockbench !== 'undefined') {
+ registerBlockbenchPlugin();
+ }
+})(typeof globalThis !== 'undefined' ? globalThis : this);