@helix3/helix-cli 0.1.18-helix3.274 → 0.1.18-helix3.276
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/bridge-recipes/unreal/helix-character-creator-male/male-carnival-mask-2.addon.json +1 -0
- package/bridge-recipes/unreal/helix-character-creator-male/male-carnival-mask-2.json +1 -0
- package/bridge-recipes/unreal/helix-character-creator-male/male-carnival-mask-3.addon.json +1 -0
- package/bridge-recipes/unreal/helix-character-creator-male/male-carnival-mask-3.json +1 -0
- package/bridge-recipes/unreal/helix-character-creator-male/male-eye-hole-mask-2.addon.json +1 -0
- package/bridge-recipes/unreal/helix-character-creator-male/male-eye-hole-mask-2.json +1 -0
- package/bridge-recipes/unreal/helix-character-creator-male/male-eye-hole-mask.addon.json +1 -0
- package/bridge-recipes/unreal/helix-character-creator-male/male-eye-hole-mask.json +1 -0
- package/bridge-recipes/unreal/helix-character-creator-male/male-marshmallow-mask.addon.json +1 -0
- package/bridge-recipes/unreal/helix-character-creator-male/male-marshmallow-mask.json +1 -0
- package/bridge-recipes/unreal/helix-character-creator-male/male-nebula-executive-coat.addon.json +1 -0
- package/bridge-recipes/unreal/helix-character-creator-male/male-nebula-executive-coat.json +1 -0
- package/bridge-recipes/unreal/helix-character-creator-male/male-neo-arcane-heir-mantle.addon.json +1 -0
- package/bridge-recipes/unreal/helix-character-creator-male/male-neo-arcane-heir-mantle.json +1 -0
- package/bridge-recipes/unreal/helix-character-creator-male/male-neo-arcane-heir-tunic.addon.json +1 -0
- package/bridge-recipes/unreal/helix-character-creator-male/male-neo-arcane-heir-tunic.json +1 -0
- package/bridge-recipes/unreal/helix-character-creator-male/male-paper-bag-mask.addon.json +1 -0
- package/bridge-recipes/unreal/helix-character-creator-male/male-paper-bag-mask.json +1 -0
- package/bridge-recipes/unreal/helix-character-creator-male/male-pig-mask.addon.json +1 -0
- package/bridge-recipes/unreal/helix-character-creator-male/male-pig-mask.json +1 -0
- package/bridge-recipes/unreal/helix-character-creator-male/male-puffer-jacket.addon.json +1 -0
- package/bridge-recipes/unreal/helix-character-creator-male/male-puffer-jacket.json +1 -0
- package/bridge-recipes/unreal/helix-character-creator-male/male-pullover-hoodie.addon.json +1 -0
- package/bridge-recipes/unreal/helix-character-creator-male/male-pullover-hoodie.json +1 -0
- package/bridge-recipes/unreal/helix-character-creator-male/male-sinth-ascetic-jacket.addon.json +1 -0
- package/bridge-recipes/unreal/helix-character-creator-male/male-sinth-ascetic-jacket.json +1 -0
- package/bridge-recipes/unreal/helix-character-creator-male/male-surgeon-mask.addon.json +1 -0
- package/bridge-recipes/unreal/helix-character-creator-male/male-surgeon-mask.json +1 -0
- package/bridge-recipes/unreal/helix-character-creator-male/male-tech-hoodie.addon.json +1 -0
- package/bridge-recipes/unreal/helix-character-creator-male/male-tech-hoodie.json +1 -0
- package/bridge-recipes/unreal/helix-character-creator-male/test/recipes.test.js +31 -8
- package/dist/bridge/characterAddon.js +18 -3
- package/dist/bridge/characterAddon.js.map +1 -1
- package/dist/character/addonPushOut.d.ts +66 -0
- package/dist/character/addonPushOut.js +211 -0
- package/dist/character/addonPushOut.js.map +1 -0
- package/dist/character/bodyRegionHide.d.ts +40 -0
- package/dist/character/bodyRegionHide.js +75 -0
- package/dist/character/bodyRegionHide.js.map +1 -0
- package/dist/character/importCharacter.d.ts +11 -0
- package/dist/character/importCharacter.js +23 -0
- package/dist/character/importCharacter.js.map +1 -1
- package/dist/tsconfig.build.tsbuildinfo +1 -1
- package/package.json +1 -1
- package/plugins/beamng-vehicle/bin/__pycache__/beamng_abs.cpython-312.pyc +0 -0
- package/plugins/beamng-vehicle/bin/__pycache__/beamng_expression.cpython-312.pyc +0 -0
- package/plugins/beamng-vehicle/bin/__pycache__/beamng_jbeam.cpython-312.pyc +0 -0
- package/plugins/beamng-vehicle/bin/__pycache__/beamng_materials.cpython-312.pyc +0 -0
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"schemaVersion": "helix.bridge-character-addon-profile/1", "title": "Carnival Mask 2", "slug": "male-carnival-mask-2", "hostItemId": "b45e4a1e-0000-4000-8000-000000000001", "hostSha256": "3fdb769bf0dbcc0e1b1b1ae8dd800d6ddedfda976c6422d4de15b80ce71c132b", "slotType": "character.mask@1", "socket": "head", "nodes": [], "ops": []}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"schemaVersion": "helix.bridge.unreal-asset/1", "slug": "male-carnival-mask-2", "assetPath": "/HelixCharacterCreator/Characters/Clothes/Man/M_Carnival_Mask_2/M_Carnival_Mask_2", "exportMorphTargets": true}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"schemaVersion": "helix.bridge-character-addon-profile/1", "title": "Carnival Mask 3", "slug": "male-carnival-mask-3", "hostItemId": "b45e4a1e-0000-4000-8000-000000000001", "hostSha256": "3fdb769bf0dbcc0e1b1b1ae8dd800d6ddedfda976c6422d4de15b80ce71c132b", "slotType": "character.mask@1", "socket": "head", "nodes": [], "ops": []}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"schemaVersion": "helix.bridge.unreal-asset/1", "slug": "male-carnival-mask-3", "assetPath": "/HelixCharacterCreator/Characters/Clothes/Man/M_Carnival_Mask_3/M_Carnival_Mask_3", "exportMorphTargets": true}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"schemaVersion": "helix.bridge-character-addon-profile/1", "title": "Eye Hole Mask 2", "slug": "male-eye-hole-mask-2", "hostItemId": "b45e4a1e-0000-4000-8000-000000000001", "hostSha256": "3fdb769bf0dbcc0e1b1b1ae8dd800d6ddedfda976c6422d4de15b80ce71c132b", "slotType": "character.mask@1", "socket": "head", "nodes": [], "ops": []}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"schemaVersion": "helix.bridge.unreal-asset/1", "slug": "male-eye-hole-mask-2", "assetPath": "/HelixCharacterCreator/Characters/Clothes/Man/M_Eye_Hole_Mask_2/M_Eye_Hole_Mask_2_skinning", "exportMorphTargets": true}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"schemaVersion": "helix.bridge-character-addon-profile/1", "title": "Eye Hole Mask", "slug": "male-eye-hole-mask", "hostItemId": "b45e4a1e-0000-4000-8000-000000000001", "hostSha256": "3fdb769bf0dbcc0e1b1b1ae8dd800d6ddedfda976c6422d4de15b80ce71c132b", "slotType": "character.mask@1", "socket": "head", "nodes": [], "ops": []}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"schemaVersion": "helix.bridge.unreal-asset/1", "slug": "male-eye-hole-mask", "assetPath": "/HelixCharacterCreator/Characters/Clothes/Man/M_Eye_Hole_Mask/M_Eye_Hole_Mask_skinning", "exportMorphTargets": true}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"schemaVersion": "helix.bridge-character-addon-profile/1", "title": "Marshmallow Mask", "slug": "male-marshmallow-mask", "hostItemId": "b45e4a1e-0000-4000-8000-000000000001", "hostSha256": "3fdb769bf0dbcc0e1b1b1ae8dd800d6ddedfda976c6422d4de15b80ce71c132b", "slotType": "character.mask@1", "socket": "head", "nodes": [], "ops": []}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"schemaVersion": "helix.bridge.unreal-asset/1", "slug": "male-marshmallow-mask", "assetPath": "/HelixCharacterCreator/Characters/Clothes/Man/Marshmallow_Mask/M_Marshmallow_Mask", "exportMorphTargets": true}
|
package/bridge-recipes/unreal/helix-character-creator-male/male-nebula-executive-coat.addon.json
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"schemaVersion": "helix.bridge-character-addon-profile/1", "title": "Nebula Executive Coat", "slug": "male-nebula-executive-coat", "hostItemId": "b45e4a1e-0000-4000-8000-000000000001", "hostSha256": "3fdb769bf0dbcc0e1b1b1ae8dd800d6ddedfda976c6422d4de15b80ce71c132b", "slotType": "character.top@1", "socket": "chest", "nodes": [], "ops": []}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"schemaVersion": "helix.bridge.unreal-asset/1", "slug": "male-nebula-executive-coat", "assetPath": "/HelixCharacterCreator/Characters/Clothes/Man/Nebula_Executive_Coat/SKM_M_Nebula_Executive_West_Coat", "exportMorphTargets": true}
|
package/bridge-recipes/unreal/helix-character-creator-male/male-neo-arcane-heir-mantle.addon.json
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"schemaVersion": "helix.bridge-character-addon-profile/1", "title": "NEO Arcane Heir Mantle", "slug": "male-neo-arcane-heir-mantle", "hostItemId": "b45e4a1e-0000-4000-8000-000000000001", "hostSha256": "3fdb769bf0dbcc0e1b1b1ae8dd800d6ddedfda976c6422d4de15b80ce71c132b", "slotType": "character.top@1", "socket": "chest", "nodes": [], "ops": []}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"schemaVersion": "helix.bridge.unreal-asset/1", "slug": "male-neo-arcane-heir-mantle", "assetPath": "/HelixCharacterCreator/Characters/Clothes/Man/NEO_Arcane_Heir_Mantle/SKM_M_NEO_Arcane_Heir_Mantle", "exportMorphTargets": true}
|
package/bridge-recipes/unreal/helix-character-creator-male/male-neo-arcane-heir-tunic.addon.json
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"schemaVersion": "helix.bridge-character-addon-profile/1", "title": "NEO Arcane Heir Tunic", "slug": "male-neo-arcane-heir-tunic", "hostItemId": "b45e4a1e-0000-4000-8000-000000000001", "hostSha256": "3fdb769bf0dbcc0e1b1b1ae8dd800d6ddedfda976c6422d4de15b80ce71c132b", "slotType": "character.top@1", "socket": "chest", "nodes": [], "ops": []}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"schemaVersion": "helix.bridge.unreal-asset/1", "slug": "male-neo-arcane-heir-tunic", "assetPath": "/HelixCharacterCreator/Characters/Clothes/Man/NEO_Arcane_Heir_Tunic/SKM_M_NEO_Arcane_Heir_Tunic", "exportMorphTargets": true}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"schemaVersion": "helix.bridge-character-addon-profile/1", "title": "Paper Bag Mask", "slug": "male-paper-bag-mask", "hostItemId": "b45e4a1e-0000-4000-8000-000000000001", "hostSha256": "3fdb769bf0dbcc0e1b1b1ae8dd800d6ddedfda976c6422d4de15b80ce71c132b", "slotType": "character.mask@1", "socket": "head", "nodes": [], "ops": []}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"schemaVersion": "helix.bridge.unreal-asset/1", "slug": "male-paper-bag-mask", "assetPath": "/HelixCharacterCreator/Characters/Clothes/Man/M_Paper_Bag_Mask/M_Paper_Bag_Mask", "exportMorphTargets": true}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"schemaVersion": "helix.bridge-character-addon-profile/1", "title": "Pig Mask", "slug": "male-pig-mask", "hostItemId": "b45e4a1e-0000-4000-8000-000000000001", "hostSha256": "3fdb769bf0dbcc0e1b1b1ae8dd800d6ddedfda976c6422d4de15b80ce71c132b", "slotType": "character.mask@1", "socket": "head", "nodes": [], "ops": []}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"schemaVersion": "helix.bridge.unreal-asset/1", "slug": "male-pig-mask", "assetPath": "/HelixCharacterCreator/Characters/Clothes/Man/M_Pig_Mask/M_Pig_Mask", "exportMorphTargets": true}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"schemaVersion": "helix.bridge-character-addon-profile/1", "title": "Puffer Jacket", "slug": "male-puffer-jacket", "hostItemId": "b45e4a1e-0000-4000-8000-000000000001", "hostSha256": "3fdb769bf0dbcc0e1b1b1ae8dd800d6ddedfda976c6422d4de15b80ce71c132b", "slotType": "character.top@1", "socket": "chest", "nodes": [], "ops": []}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"schemaVersion": "helix.bridge.unreal-asset/1", "slug": "male-puffer-jacket", "assetPath": "/HelixCharacterCreator/Characters/Clothes/Man/Puffer_Jacket/SKM_M_Puffer_Jacket", "exportMorphTargets": true}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"schemaVersion": "helix.bridge-character-addon-profile/1", "title": "Pullover Hoodie", "slug": "male-pullover-hoodie", "hostItemId": "b45e4a1e-0000-4000-8000-000000000001", "hostSha256": "3fdb769bf0dbcc0e1b1b1ae8dd800d6ddedfda976c6422d4de15b80ce71c132b", "slotType": "character.top@1", "socket": "chest", "nodes": [], "ops": []}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"schemaVersion": "helix.bridge.unreal-asset/1", "slug": "male-pullover-hoodie", "assetPath": "/HelixCharacterCreator/Characters/Clothes/Man/Pullover_Hoodie/SKM_M_Pullover_Hoodie", "exportMorphTargets": true}
|
package/bridge-recipes/unreal/helix-character-creator-male/male-sinth-ascetic-jacket.addon.json
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"schemaVersion": "helix.bridge-character-addon-profile/1", "title": "Sinth Ascetic Jacket", "slug": "male-sinth-ascetic-jacket", "hostItemId": "b45e4a1e-0000-4000-8000-000000000001", "hostSha256": "3fdb769bf0dbcc0e1b1b1ae8dd800d6ddedfda976c6422d4de15b80ce71c132b", "slotType": "character.top@1", "socket": "chest", "nodes": [], "ops": []}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"schemaVersion": "helix.bridge.unreal-asset/1", "slug": "male-sinth-ascetic-jacket", "assetPath": "/HelixCharacterCreator/Characters/Clothes/Man/Sinth_Ascetic_Jacket/SKM_M_Sinth_Ascetic_Jacket", "exportMorphTargets": true}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"schemaVersion": "helix.bridge-character-addon-profile/1", "title": "Surgeon Mask", "slug": "male-surgeon-mask", "hostItemId": "b45e4a1e-0000-4000-8000-000000000001", "hostSha256": "3fdb769bf0dbcc0e1b1b1ae8dd800d6ddedfda976c6422d4de15b80ce71c132b", "slotType": "character.mask@1", "socket": "head", "nodes": [], "ops": []}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"schemaVersion": "helix.bridge.unreal-asset/1", "slug": "male-surgeon-mask", "assetPath": "/HelixCharacterCreator/Characters/Clothes/Man/M_Surgeon_Mask/M_Surgeon_Mask_skinning", "exportMorphTargets": true}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"schemaVersion": "helix.bridge-character-addon-profile/1", "title": "Tech Hoodie", "slug": "male-tech-hoodie", "hostItemId": "b45e4a1e-0000-4000-8000-000000000001", "hostSha256": "3fdb769bf0dbcc0e1b1b1ae8dd800d6ddedfda976c6422d4de15b80ce71c132b", "slotType": "character.top@1", "socket": "chest", "nodes": [], "ops": []}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"schemaVersion": "helix.bridge.unreal-asset/1", "slug": "male-tech-hoodie", "assetPath": "/HelixCharacterCreator/Characters/Clothes/Man/Tech_Hoodie/SKM_M_Tech_Hoodie", "exportMorphTargets": true}
|
|
@@ -8,15 +8,28 @@ const test = require('node:test');
|
|
|
8
8
|
const directory = resolve(__dirname, '..');
|
|
9
9
|
const hostItemId = 'b45e4a1e-0000-4000-8000-000000000001';
|
|
10
10
|
|
|
11
|
+
// The fixed, one-representative-item-per-customizer-slot pilot set that
|
|
12
|
+
// `all.json` is a canonical batch of. This list is intentionally NOT derived
|
|
13
|
+
// from a directory scan: the batch phase adds many more `<item-slug>.json` /
|
|
14
|
+
// `<item-slug>.addon.json` recipe pairs to this same directory (one per
|
|
15
|
+
// wearable, not one per slot), and those must coexist without being folded
|
|
16
|
+
// into — or breaking the identity check against — this specific pilot batch.
|
|
17
|
+
const canonicalSlotFiles = [
|
|
18
|
+
'base-male.json',
|
|
19
|
+
'base-male-face.json',
|
|
20
|
+
'back.json', 'bottom.json', 'eyewear.json', 'footwear.json', 'gloves.json',
|
|
21
|
+
'hair.json', 'headwear.json', 'mask.json', 'neck.json', 'outfit.json', 'top.json',
|
|
22
|
+
];
|
|
23
|
+
const canonicalSlots = [
|
|
24
|
+
'back', 'bottom', 'eyewear', 'footwear', 'gloves', 'hair', 'headwear', 'mask', 'neck', 'outfit', 'top',
|
|
25
|
+
];
|
|
26
|
+
|
|
11
27
|
function read(file) {
|
|
12
28
|
return JSON.parse(readFileSync(join(directory, file), 'utf8'));
|
|
13
29
|
}
|
|
14
30
|
|
|
15
31
|
test('keeps the canonical batch identical to its individual source profiles', () => {
|
|
16
|
-
const
|
|
17
|
-
.filter((file) => file.endsWith('.json') && !['all.json', 'palette-pilot.json'].includes(file) && !file.endsWith('.addon.json'))
|
|
18
|
-
.sort();
|
|
19
|
-
const profiles = files.map(read);
|
|
32
|
+
const profiles = canonicalSlotFiles.map(read);
|
|
20
33
|
const batch = read('all.json');
|
|
21
34
|
assert.equal(batch.schemaVersion, 'helix.bridge.unreal-asset-set/1');
|
|
22
35
|
assert.deepEqual(
|
|
@@ -26,11 +39,19 @@ test('keeps the canonical batch identical to its individual source profiles', ()
|
|
|
26
39
|
assert.equal(new Set(batch.assets.map((asset) => asset.slug)).size, batch.assets.length);
|
|
27
40
|
});
|
|
28
41
|
|
|
29
|
-
test('
|
|
42
|
+
test('every add-on profile in this directory pairs with a runtime customizer add-on profile of matching shape', () => {
|
|
43
|
+
// Covers every `.addon.json` file present — the fixed 11-slot pilot set AND
|
|
44
|
+
// any number of additional per-item batch-phase recipes — rather than a
|
|
45
|
+
// closed exact-list. A new wearable's recipe pair must satisfy the same
|
|
46
|
+
// schema contract as the originals; it does not need to be one of the 11
|
|
47
|
+
// canonical slot names, and there may be many per slot.
|
|
30
48
|
const addonFiles = readdirSync(directory).filter((file) => file.endsWith('.addon.json')).sort();
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
49
|
+
// The 11 canonical per-slot pilot add-ons must still exist under their
|
|
50
|
+
// original slot-named files.
|
|
51
|
+
for (const slot of canonicalSlots) {
|
|
52
|
+
assert.ok(addonFiles.includes(`${slot}.addon.json`), `missing canonical ${slot}.addon.json`);
|
|
53
|
+
}
|
|
54
|
+
const seenSlugs = new Set();
|
|
34
55
|
for (const file of addonFiles) {
|
|
35
56
|
const addon = read(file);
|
|
36
57
|
const source = read(`${basename(file, '.addon.json')}.json`);
|
|
@@ -39,6 +60,8 @@ test('pairs every wearable source with a runtime customizer add-on profile', ()
|
|
|
39
60
|
assert.equal(addon.hostItemId, hostItemId);
|
|
40
61
|
assert.match(addon.slotType, /^character\.[a-z]+@1$/);
|
|
41
62
|
assert.equal(addon.slotType.startsWith('Cosmetic.Slot.'), false);
|
|
63
|
+
assert.equal(seenSlugs.has(addon.slug), false, `duplicate add-on slug ${addon.slug} (${file})`);
|
|
64
|
+
seenSlugs.add(addon.slug);
|
|
42
65
|
}
|
|
43
66
|
});
|
|
44
67
|
|
|
@@ -7,6 +7,7 @@ exports.parseCharacterAddonProfileJson = parseCharacterAddonProfileJson;
|
|
|
7
7
|
const node_fs_1 = require("node:fs");
|
|
8
8
|
const item_1 = require("../item");
|
|
9
9
|
const helixPalette_1 = require("../character/helixPalette");
|
|
10
|
+
const bodyRegionHide_1 = require("../character/bodyRegionHide");
|
|
10
11
|
exports.BRIDGE_CHARACTER_ADDON_PROFILE_SCHEMA = 'helix.bridge-character-addon-profile/1';
|
|
11
12
|
function requiredString(value, label) {
|
|
12
13
|
if (typeof value !== 'string' || value.trim() === '') {
|
|
@@ -27,7 +28,7 @@ function validateCharacterAddonProfile(value) {
|
|
|
27
28
|
throw new Error('Character add-on profile must be an object.');
|
|
28
29
|
}
|
|
29
30
|
const row = value;
|
|
30
|
-
const allowed = new Set(['schemaVersion', 'title', 'slug', 'hostItemId', 'hostSha256', 'slotType', 'socket', 'nodes', 'ops', 'palette']);
|
|
31
|
+
const allowed = new Set(['schemaVersion', 'title', 'slug', 'hostItemId', 'hostSha256', 'slotType', 'socket', 'nodes', 'ops', 'palette', 'hideRegions']);
|
|
31
32
|
const unknown = Object.keys(row).filter((key) => !allowed.has(key));
|
|
32
33
|
if (unknown.length > 0)
|
|
33
34
|
throw new Error(`Character add-on profile has unsupported field(s): ${unknown.join(', ')}.`);
|
|
@@ -51,8 +52,22 @@ function validateCharacterAddonProfile(value) {
|
|
|
51
52
|
throw new Error('Character add-on profile slotType must use the runtime character.<slot>@1 vocabulary.');
|
|
52
53
|
}
|
|
53
54
|
const socket = requiredString(row.socket, 'socket');
|
|
54
|
-
const
|
|
55
|
-
const
|
|
55
|
+
const rawNodes = stringList(row.nodes, 'nodes');
|
|
56
|
+
const rawOps = row.ops === undefined ? [] : row.ops;
|
|
57
|
+
if (!Array.isArray(rawOps))
|
|
58
|
+
throw new Error('Character add-on profile ops must be an array.');
|
|
59
|
+
const hasExplicitHideOp = rawOps.some((op) => typeof op === 'object' && op !== null && op.v === 'hide');
|
|
60
|
+
// `hideRegions` picks which of the platform's `character.<slot>@1` hide grant this add-on actually uses.
|
|
61
|
+
// Explicit wins outright (including an explicit `[]`, opting a garment out of hiding anything). With
|
|
62
|
+
// neither an explicit list nor a hand-authored `hide` op already in `ops`, default to the FULL region set
|
|
63
|
+
// the slot type's platform grant permits (see `bodyRegionHide.ts`) — the fix for the poke-through defect:
|
|
64
|
+
// a conformed garment now hides the skin underneath it by default instead of requiring per-item authoring.
|
|
65
|
+
const hideRegionsOverride = row.hideRegions === undefined ? undefined : stringList(row.hideRegions, 'hideRegions');
|
|
66
|
+
const hideRegions = hideRegionsOverride ?? (hasExplicitHideOp ? [] : (0, bodyRegionHide_1.defaultHideRegionsForSlotType)(slotType));
|
|
67
|
+
const nodes = [...new Set([...rawNodes, ...hideRegions])];
|
|
68
|
+
const ops = hideRegions.length > 0 && !hasExplicitHideOp
|
|
69
|
+
? [...rawOps, { v: 'hide', nodes: hideRegions }]
|
|
70
|
+
: rawOps;
|
|
56
71
|
const palette = row.palette === undefined ? undefined : (0, helixPalette_1.validateHelixPaletteSelection)(row.palette);
|
|
57
72
|
const fits = {
|
|
58
73
|
slotType,
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"characterAddon.js","sourceRoot":"","sources":["../../src/bridge/characterAddon.ts"],"names":[],"mappings":";;;
|
|
1
|
+
{"version":3,"file":"characterAddon.js","sourceRoot":"","sources":["../../src/bridge/characterAddon.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
import type { Document } from '@gltf-transform/core';
|
|
2
|
+
/**
|
|
3
|
+
* Conform-time vertex push-out — the SECOND line of defence against poke-through, for the cases a `hide`
|
|
4
|
+
* op cannot fix.
|
|
5
|
+
*
|
|
6
|
+
* `bodyRegionHide.ts` removes the skin a garment fully covers, which is the right fix for a garment that
|
|
7
|
+
* loosely bags a whole region (the Sinth Ascetic Pants case: pants that reach the ankle, hiding the whole
|
|
8
|
+
* thigh/shin). It is the WRONG fix for a garment that is merely too tight at one edge — a hem, a cuff, a
|
|
9
|
+
* waistband — because hiding the region would also hide the skin the garment does NOT cover (bare ankle
|
|
10
|
+
* below a cropped trouser leg, bare wrist past a short sleeve). For that case the body must stay visible and
|
|
11
|
+
* the GARMENT geometry has to move a little further from the skin instead.
|
|
12
|
+
*
|
|
13
|
+
* This step runs on the conformed garment mesh (after `conformToHelixRest`, in the same rest-pose space as
|
|
14
|
+
* the host body) and the host body's own conformed mesh. For every garment vertex closer to the host
|
|
15
|
+
* surface than `threshold` (measured along the host surface's own outward normal, so a vertex already
|
|
16
|
+
* clear of the body is left alone and one merely GRAZING it is not overcorrected), it is pushed out along
|
|
17
|
+
* that normal until it clears the body by `offset`. Vertices already at or beyond `offset` are untouched —
|
|
18
|
+
* this only closes gaps, it never inflates a garment that already fits.
|
|
19
|
+
*
|
|
20
|
+
* ── WHY THIS IS CONSERVATIVE ────────────────────────────────────────────────────────────────────────────────
|
|
21
|
+
* - Only vertices within `threshold` of the body move at all; the overwhelming majority of a garment's
|
|
22
|
+
* vertices (anywhere away from the skin-tight edges) are never touched, so the garment's silhouette away
|
|
23
|
+
* from those edges is bit-for-bit identical to what conform already produced.
|
|
24
|
+
* - The push distance is clamped to `maxPushOut` regardless of how deep a vertex is embedded, so a
|
|
25
|
+
* badly-fit garment is not silently inflated into a different shape — a violation past the clamp should
|
|
26
|
+
* fail the automated poke-through check (`pokethrough-check.mjs`) and get creator attention instead.
|
|
27
|
+
* - Movement is along the BODY's outward normal at the nearest surface point, not the garment vertex's own
|
|
28
|
+
* normal — stable even where the garment normal is noisy (thin cloth, near-tangent faces) because it is
|
|
29
|
+
* anchored to the coarser, more reliable body surface.
|
|
30
|
+
*/
|
|
31
|
+
export interface PushOutOptions {
|
|
32
|
+
/**
|
|
33
|
+
* A garment vertex closer to the body than this (metres) is considered at risk and pushed out. Default
|
|
34
|
+
* 4mm: the platform's canonical body scale is metres-real-world, and this is comfortably inside normal
|
|
35
|
+
* cloth-to-skin clearance for a well-conformed garment while still catching genuine near-misses from
|
|
36
|
+
* automatic weight transfer. Tune per item family if a garment's natural clearance is tighter (e.g. a
|
|
37
|
+
* skin-suit) or looser (e.g. bulky outerwear).
|
|
38
|
+
*/
|
|
39
|
+
thresholdMeters?: number;
|
|
40
|
+
/** How far past the body surface a corrected vertex ends up (metres). Default 2mm — a hairline gap that
|
|
41
|
+
* reads as "not touching" up close without visibly separating the garment from the body at a distance. */
|
|
42
|
+
offsetMeters?: number;
|
|
43
|
+
/** Hard clamp on how far any single vertex may move (metres). Default 15mm — beyond this the garment is
|
|
44
|
+
* not merely tight, it is the wrong shape for the body, and moving it further would hide that rather
|
|
45
|
+
* than surface it. */
|
|
46
|
+
maxPushOutMeters?: number;
|
|
47
|
+
}
|
|
48
|
+
export interface PushOutReport {
|
|
49
|
+
/** Garment vertices examined across every mesh primitive. */
|
|
50
|
+
verticesExamined: number;
|
|
51
|
+
/** Garment vertices that were within `thresholdMeters` of the body surface. */
|
|
52
|
+
verticesAtRisk: number;
|
|
53
|
+
/** Of those, how many were actually moved (a vertex already exactly at `offsetMeters` needs no push). */
|
|
54
|
+
verticesPushed: number;
|
|
55
|
+
/** Vertices where the required push exceeded `maxPushOutMeters` — moved only to the clamp, still flagged. */
|
|
56
|
+
verticesClamped: number;
|
|
57
|
+
/** The largest correction actually applied, in metres. 0 if nothing needed moving. */
|
|
58
|
+
maxCorrectionMeters: number;
|
|
59
|
+
}
|
|
60
|
+
/**
|
|
61
|
+
* Push garment vertices out of the host body surface where they are closer than `thresholdMeters`. Mutates
|
|
62
|
+
* `garment`'s POSITION accessors in place (NORMAL is left untouched — the push distances are small enough
|
|
63
|
+
* that re-lighting is not worth the extra pass, and a shading seam from a hairline offset is not visible at
|
|
64
|
+
* platform LOD triangle counts).
|
|
65
|
+
*/
|
|
66
|
+
export declare function pushGarmentOutOfBody(garment: Document, hostBody: Document, options?: PushOutOptions): PushOutReport;
|
|
@@ -0,0 +1,211 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.pushGarmentOutOfBody = pushGarmentOutOfBody;
|
|
4
|
+
const mat4_1 = require("./mat4");
|
|
5
|
+
/** Cell size for the uniform grid that buckets host triangles for approximate-nearest lookup. */
|
|
6
|
+
function gridKey(p, cell) {
|
|
7
|
+
return `${Math.floor(p[0] / cell)},${Math.floor(p[1] / cell)},${Math.floor(p[2] / cell)}`;
|
|
8
|
+
}
|
|
9
|
+
function collectHostTriangles(hostBody) {
|
|
10
|
+
const triangles = [];
|
|
11
|
+
for (const mesh of hostBody.getRoot().listMeshes()) {
|
|
12
|
+
for (const prim of mesh.listPrimitives()) {
|
|
13
|
+
const pos = prim.getAttribute('POSITION');
|
|
14
|
+
const indices = prim.getIndices();
|
|
15
|
+
if (!pos)
|
|
16
|
+
continue;
|
|
17
|
+
const posArray = pos.getArray();
|
|
18
|
+
if (!posArray)
|
|
19
|
+
continue;
|
|
20
|
+
const vertexAt = (i) => [posArray[i * 3], posArray[i * 3 + 1], posArray[i * 3 + 2]];
|
|
21
|
+
const idxArray = indices?.getArray();
|
|
22
|
+
const triCount = idxArray ? idxArray.length / 3 : pos.getCount() / 3;
|
|
23
|
+
for (let t = 0; t < triCount; t++) {
|
|
24
|
+
const [ia, ib, ic] = idxArray
|
|
25
|
+
? [idxArray[t * 3], idxArray[t * 3 + 1], idxArray[t * 3 + 2]]
|
|
26
|
+
: [t * 3, t * 3 + 1, t * 3 + 2];
|
|
27
|
+
const a = vertexAt(ia);
|
|
28
|
+
const b = vertexAt(ib);
|
|
29
|
+
const c = vertexAt(ic);
|
|
30
|
+
const normal = (0, mat4_1.normalize3)((0, mat4_1.cross3)((0, mat4_1.sub3)(b, a), (0, mat4_1.sub3)(c, a)));
|
|
31
|
+
if (!Number.isFinite(normal[0]))
|
|
32
|
+
continue; // degenerate triangle
|
|
33
|
+
const centroid = [(a[0] + b[0] + c[0]) / 3, (a[1] + b[1] + c[1]) / 3, (a[2] + b[2] + c[2]) / 3];
|
|
34
|
+
triangles.push({ a, b, c, normal, centroid });
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
// A cell of ~5cm keeps the neighbour search (27 cells) small on a canonical-scale humanoid body while
|
|
39
|
+
// still bucketing enough triangles per cell to be worth indexing.
|
|
40
|
+
const cell = 0.05;
|
|
41
|
+
return { triangles, cell };
|
|
42
|
+
}
|
|
43
|
+
/**
|
|
44
|
+
* Buckets every triangle into EVERY cell its axis-aligned bounding box overlaps, not just its centroid's
|
|
45
|
+
* cell. A character-scale body/garment mesh at platform LOD triangle counts has edge lengths far under one
|
|
46
|
+
* grid cell, so this is normally one cell per triangle in practice — but bucketing by centroid alone is
|
|
47
|
+
* silently wrong for any triangle bigger than the cell (its centroid's neighbourhood search would never
|
|
48
|
+
* see it even though its surface passes right by the query point), and getting that wrong produces a
|
|
49
|
+
* push-out step that quietly does nothing rather than a visible error. The `+ 1` cap on cells-per-axis
|
|
50
|
+
* guards the pathological case (a huge single triangle) from indexing an unbounded number of cells.
|
|
51
|
+
*/
|
|
52
|
+
function indexTriangles(triangles, cell) {
|
|
53
|
+
const grid = new Map();
|
|
54
|
+
const MAX_CELLS_PER_AXIS = 64;
|
|
55
|
+
for (const tri of triangles) {
|
|
56
|
+
const min = [
|
|
57
|
+
Math.min(tri.a[0], tri.b[0], tri.c[0]),
|
|
58
|
+
Math.min(tri.a[1], tri.b[1], tri.c[1]),
|
|
59
|
+
Math.min(tri.a[2], tri.b[2], tri.c[2]),
|
|
60
|
+
];
|
|
61
|
+
const max = [
|
|
62
|
+
Math.max(tri.a[0], tri.b[0], tri.c[0]),
|
|
63
|
+
Math.max(tri.a[1], tri.b[1], tri.c[1]),
|
|
64
|
+
Math.max(tri.a[2], tri.b[2], tri.c[2]),
|
|
65
|
+
];
|
|
66
|
+
const cellMin = min.map((v) => Math.floor(v / cell));
|
|
67
|
+
const cellMax = max.map((v) => Math.floor(v / cell));
|
|
68
|
+
const span = (i) => Math.min(cellMax[i] - cellMin[i], MAX_CELLS_PER_AXIS);
|
|
69
|
+
for (let x = 0; x <= span(0); x++) {
|
|
70
|
+
for (let y = 0; y <= span(1); y++) {
|
|
71
|
+
for (let z = 0; z <= span(2); z++) {
|
|
72
|
+
const key = `${cellMin[0] + x},${cellMin[1] + y},${cellMin[2] + z}`;
|
|
73
|
+
const bucket = grid.get(key);
|
|
74
|
+
if (bucket)
|
|
75
|
+
bucket.push(tri);
|
|
76
|
+
else
|
|
77
|
+
grid.set(key, [tri]);
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
return grid;
|
|
83
|
+
}
|
|
84
|
+
/** Closest point on triangle `tri` to `p`, via the standard barycentric clamp (Ericson, RTCD 5.1.5). */
|
|
85
|
+
function closestPointOnTriangle(p, tri) {
|
|
86
|
+
const { a, b, c } = tri;
|
|
87
|
+
const ab = (0, mat4_1.sub3)(b, a);
|
|
88
|
+
const ac = (0, mat4_1.sub3)(c, a);
|
|
89
|
+
const ap = (0, mat4_1.sub3)(p, a);
|
|
90
|
+
const d1 = (0, mat4_1.dot3)(ab, ap);
|
|
91
|
+
const d2 = (0, mat4_1.dot3)(ac, ap);
|
|
92
|
+
if (d1 <= 0 && d2 <= 0)
|
|
93
|
+
return a;
|
|
94
|
+
const bp = (0, mat4_1.sub3)(p, b);
|
|
95
|
+
const d3 = (0, mat4_1.dot3)(ab, bp);
|
|
96
|
+
const d4 = (0, mat4_1.dot3)(ac, bp);
|
|
97
|
+
if (d3 >= 0 && d4 <= d3)
|
|
98
|
+
return b;
|
|
99
|
+
const vc = d1 * d4 - d3 * d2;
|
|
100
|
+
if (vc <= 0 && d1 >= 0 && d3 <= 0) {
|
|
101
|
+
const v = d1 / (d1 - d3);
|
|
102
|
+
return [a[0] + v * ab[0], a[1] + v * ab[1], a[2] + v * ab[2]];
|
|
103
|
+
}
|
|
104
|
+
const cp = (0, mat4_1.sub3)(p, c);
|
|
105
|
+
const d5 = (0, mat4_1.dot3)(ab, cp);
|
|
106
|
+
const d6 = (0, mat4_1.dot3)(ac, cp);
|
|
107
|
+
if (d6 >= 0 && d5 <= d6)
|
|
108
|
+
return c;
|
|
109
|
+
const vb = d5 * d2 - d1 * d6;
|
|
110
|
+
if (vb <= 0 && d2 >= 0 && d6 <= 0) {
|
|
111
|
+
const w = d2 / (d2 - d6);
|
|
112
|
+
return [a[0] + w * ac[0], a[1] + w * ac[1], a[2] + w * ac[2]];
|
|
113
|
+
}
|
|
114
|
+
const va = d3 * d6 - d5 * d4;
|
|
115
|
+
if (va <= 0 && d4 - d3 >= 0 && d5 - d6 >= 0) {
|
|
116
|
+
const w = (d4 - d3) / (d4 - d3 + (d5 - d6));
|
|
117
|
+
return [b[0] + w * (c[0] - b[0]), b[1] + w * (c[1] - b[1]), b[2] + w * (c[2] - b[2])];
|
|
118
|
+
}
|
|
119
|
+
const denom = 1 / (va + vb + vc);
|
|
120
|
+
const v = vb * denom;
|
|
121
|
+
const w = vc * denom;
|
|
122
|
+
return [a[0] + ab[0] * v + ac[0] * w, a[1] + ab[1] * v + ac[1] * w, a[2] + ab[2] * v + ac[2] * w];
|
|
123
|
+
}
|
|
124
|
+
function nearestSurfacePoint(p, grid, cell) {
|
|
125
|
+
const [cx, cy, cz] = [Math.floor(p[0] / cell), Math.floor(p[1] / cell), Math.floor(p[2] / cell)];
|
|
126
|
+
let best = null;
|
|
127
|
+
// Search the 3x3x3 neighbourhood first; if nothing is found (a sparse region, or the garment vertex sits
|
|
128
|
+
// further than one cell from any body geometry), widen the ring until a triangle is found or we give up.
|
|
129
|
+
for (let radius = 1; radius <= 4 && best === null; radius++) {
|
|
130
|
+
for (let dx = -radius; dx <= radius; dx++) {
|
|
131
|
+
for (let dy = -radius; dy <= radius; dy++) {
|
|
132
|
+
for (let dz = -radius; dz <= radius; dz++) {
|
|
133
|
+
const bucket = grid.get(`${cx + dx},${cy + dy},${cz + dz}`);
|
|
134
|
+
if (!bucket)
|
|
135
|
+
continue;
|
|
136
|
+
for (const tri of bucket) {
|
|
137
|
+
const cp = closestPointOnTriangle(p, tri);
|
|
138
|
+
const d = Math.hypot(p[0] - cp[0], p[1] - cp[1], p[2] - cp[2]);
|
|
139
|
+
if (best === null || d < best.distance)
|
|
140
|
+
best = { point: cp, normal: tri.normal, distance: d };
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
return best;
|
|
147
|
+
}
|
|
148
|
+
/**
|
|
149
|
+
* Push garment vertices out of the host body surface where they are closer than `thresholdMeters`. Mutates
|
|
150
|
+
* `garment`'s POSITION accessors in place (NORMAL is left untouched — the push distances are small enough
|
|
151
|
+
* that re-lighting is not worth the extra pass, and a shading seam from a hairline offset is not visible at
|
|
152
|
+
* platform LOD triangle counts).
|
|
153
|
+
*/
|
|
154
|
+
function pushGarmentOutOfBody(garment, hostBody, options = {}) {
|
|
155
|
+
const threshold = options.thresholdMeters ?? 0.004;
|
|
156
|
+
const offset = options.offsetMeters ?? 0.002;
|
|
157
|
+
const maxPushOut = options.maxPushOutMeters ?? 0.015;
|
|
158
|
+
if (offset > threshold) {
|
|
159
|
+
throw new Error(`addonPushOut: offsetMeters (${offset}) must not exceed thresholdMeters (${threshold}) — otherwise every push-out would immediately re-trigger the risk test.`);
|
|
160
|
+
}
|
|
161
|
+
const { triangles, cell } = collectHostTriangles(hostBody);
|
|
162
|
+
const report = {
|
|
163
|
+
verticesExamined: 0,
|
|
164
|
+
verticesAtRisk: 0,
|
|
165
|
+
verticesPushed: 0,
|
|
166
|
+
verticesClamped: 0,
|
|
167
|
+
maxCorrectionMeters: 0,
|
|
168
|
+
};
|
|
169
|
+
if (triangles.length === 0)
|
|
170
|
+
return report; // nothing to push away from — leave the garment untouched.
|
|
171
|
+
const grid = indexTriangles(triangles, cell);
|
|
172
|
+
for (const mesh of garment.getRoot().listMeshes()) {
|
|
173
|
+
for (const prim of mesh.listPrimitives()) {
|
|
174
|
+
const pos = prim.getAttribute('POSITION');
|
|
175
|
+
if (!pos)
|
|
176
|
+
continue;
|
|
177
|
+
const array = pos.getArray();
|
|
178
|
+
if (!array)
|
|
179
|
+
continue;
|
|
180
|
+
const mutable = Float32Array.from(array);
|
|
181
|
+
const count = pos.getCount();
|
|
182
|
+
for (let i = 0; i < count; i++) {
|
|
183
|
+
report.verticesExamined++;
|
|
184
|
+
const v = [mutable[i * 3], mutable[i * 3 + 1], mutable[i * 3 + 2]];
|
|
185
|
+
const nearest = nearestSurfacePoint(v, grid, cell);
|
|
186
|
+
if (!nearest)
|
|
187
|
+
continue;
|
|
188
|
+
// Signed distance along the BODY's own outward normal: positive means the garment vertex is
|
|
189
|
+
// already outside the body along that normal, negative means it has sunk past the surface.
|
|
190
|
+
const signedDistance = (0, mat4_1.dot3)((0, mat4_1.sub3)(v, nearest.point), nearest.normal);
|
|
191
|
+
if (signedDistance >= threshold)
|
|
192
|
+
continue; // already clear — leave it exactly where conform put it.
|
|
193
|
+
report.verticesAtRisk++;
|
|
194
|
+
const rawPush = offset - signedDistance;
|
|
195
|
+
const push = Math.min(rawPush, maxPushOut);
|
|
196
|
+
if (push < rawPush)
|
|
197
|
+
report.verticesClamped++;
|
|
198
|
+
if (push <= 0)
|
|
199
|
+
continue;
|
|
200
|
+
mutable[i * 3] = v[0] + nearest.normal[0] * push;
|
|
201
|
+
mutable[i * 3 + 1] = v[1] + nearest.normal[1] * push;
|
|
202
|
+
mutable[i * 3 + 2] = v[2] + nearest.normal[2] * push;
|
|
203
|
+
report.verticesPushed++;
|
|
204
|
+
report.maxCorrectionMeters = Math.max(report.maxCorrectionMeters, push);
|
|
205
|
+
}
|
|
206
|
+
pos.setArray(mutable);
|
|
207
|
+
}
|
|
208
|
+
}
|
|
209
|
+
return report;
|
|
210
|
+
}
|
|
211
|
+
//# sourceMappingURL=addonPushOut.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"addonPushOut.js","sourceRoot":"","sources":["../../src/character/addonPushOut.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Default body-region `hide` grants for HELIX character add-on slot types.
|
|
3
|
+
*
|
|
4
|
+
* ── WHY THIS EXISTS ────────────────────────────────────────────────────────────────────────────────────────────
|
|
5
|
+
* The platform ALREADY grants every `wearable`-archetype character slot the right to hide the base body's
|
|
6
|
+
* skin underneath it (`helix-backend-api/src/add-ons/character-slot-pack.ts`, mirrored at runtime by
|
|
7
|
+
* `helix-web-engine-new/systems/humanoid-character/.../core/vehicle/parts/characterBase.ts`) — a
|
|
8
|
+
* `character.bottom@1` slot may hide `body_thigh_*`/`body_shin_*`, a `character.top@1` slot may hide
|
|
9
|
+
* `body_torso`/`body_upperarm_*`, and so on. That grant is a CEILING, not a behaviour: nothing hides until
|
|
10
|
+
* the add-on's own `ops` carries a `{v: 'hide', nodes: [...]}` entry naming regions within it. Before this
|
|
11
|
+
* file, no code path in this CLI ever emitted one — every conformed character add-on shipped `ops: []`
|
|
12
|
+
* regardless of slot type, so the base body's skin rendered in full under every published garment. That is
|
|
13
|
+
* the root cause of the Base Female/Sinth Ascetic Pants poke-through: nothing was ever hiding the thigh/shin
|
|
14
|
+
* skin the pants sit over.
|
|
15
|
+
*
|
|
16
|
+
* This table is this CLI's data-driven default: conform a garment for a given `slotType` and it now emits
|
|
17
|
+
* the region hide the platform already permits for that slot, with no per-item authoring required. It is a
|
|
18
|
+
* DEFAULT, not a mandate — a garment whose geometry does not cover the platform's full granted region (cropped
|
|
19
|
+
* trousers, fingerless gloves, a cap-sleeve top) should pass a narrower `hideRegions` override in its profile
|
|
20
|
+
* JSON (see `characterAddon.ts`) naming only the globs it actually encloses, or `hideRegions: []` to hide
|
|
21
|
+
* nothing and rely on the conform-time push-out step instead.
|
|
22
|
+
*
|
|
23
|
+
* The glob strings below are copied VERBATIM from `CHARACTER_SLOT_PACK`'s `hide` arrays, not re-derived —
|
|
24
|
+
* `bodyRegionHide.spec.ts` locks every entry down, so a platform-side rename is a failing test here rather
|
|
25
|
+
* than a hide op the server rejects as "not covered by fits.requires.nodes".
|
|
26
|
+
*/
|
|
27
|
+
/** The canonical body-region node vocabulary a HELIX base body's publish-time classifier assigns. */
|
|
28
|
+
export declare const HELIX_BODY_REGIONS: readonly ["body_head", "body_neck", "body_torso", "body_upperarm_l", "body_upperarm_r", "body_forearm_l", "body_forearm_r", "body_hand_l", "body_hand_r", "body_thigh_l", "body_thigh_r", "body_shin_l", "body_shin_r", "body_foot_l", "body_foot_r", "hair_base"];
|
|
29
|
+
export type HelixBodyRegion = (typeof HELIX_BODY_REGIONS)[number];
|
|
30
|
+
/**
|
|
31
|
+
* `character.<slot>@1` -> the region globs that slot type's platform grant permits it to hide, copied
|
|
32
|
+
* verbatim from `CHARACTER_SLOT_PACK` (`helix-backend-api/src/add-ons/character-slot-pack.ts`). A slot type
|
|
33
|
+
* absent here, or mapped to `[]`, hides nothing by default — this matches the platform: accessories,
|
|
34
|
+
* cosmetics, underwear and identity slots (eyewear, neck, earrings, nails, back, held props, shoulder,
|
|
35
|
+
* brows/lashes/facial-hair, underwear, skin/eyes/makeup/tattoo, emote/locomotion/aura/voice, nameplate) are
|
|
36
|
+
* granted no `hide` at all and are expected to layer over the body rather than occlude it.
|
|
37
|
+
*/
|
|
38
|
+
export declare const DEFAULT_HIDE_REGIONS_BY_SLOT_TYPE: Readonly<Record<string, readonly string[]>>;
|
|
39
|
+
/** The default hide-region globs for `slotType`, or `[]` when the platform grants that slot no `hide` at all. */
|
|
40
|
+
export declare function defaultHideRegionsForSlotType(slotType: string): readonly string[];
|