Skip to content

feat: Add wearable and emotes model checks#3370

Merged
LautaroPetaccio merged 12 commits intomasterfrom
feat/add-wearable-and-emotes-model-checks
Mar 23, 2026
Merged

feat: Add wearable and emotes model checks#3370
LautaroPetaccio merged 12 commits intomasterfrom
feat/add-wearable-and-emotes-model-checks

Conversation

@LautaroPetaccio
Copy link
Contributor

@LautaroPetaccio LautaroPetaccio commented Mar 18, 2026

When creators upload wearables or emotes, there is no feedback about whether their GLB files comply with Decentraland's technical requirements. Issues like exceeding triangle limits, oversized textures, or incorrect armature naming are only discovered after submission — during the curation review process. This causes a slow feedback loop: creators submit, wait for review, get rejected, fix, and re-submit.

This PR adds client-side validation that checks the uploaded GLB against the documented spec at upload time and in the item editor, giving creators immediate, actionable feedback. All validation issues are reported as warnings — they never block saving or uploading. Creators see the issues but can always proceed.

How

Validation Engine (src/lib/glbValidation/)

A new module that takes a parsed Three.js GLTF object and runs a battery of checks, returning a flat list of issues. Each issue has a WARNING severity, a machine-readable code, and a translation key with parameters for the UI message.

The module is split into:

File Purpose
types.ts ValidationIssue, ValidationResult, ValidationSeverity
constants.ts All numeric limits from the Decentraland docs, plus getEffectiveTriangleLimit() for the Tris Combiner rule
wearableValidators.ts 8 validator functions for wearable-specific checks
emoteValidators.ts 12 validator functions for emote-specific checks (including props and audio)
index.ts Entry points: validateWearableGLTF(), validateEmoteGLTF(), revalidateWearableForCategory(), checkTriangleCount()

Validators receive the Three.js module as a parameter (via dynamic import('three')) so they can use instanceof checks for Mesh, Bone, Camera, etc. This avoids a static dependency on Three.js.

Triangle Count Validation & Tris Combiner

Triangle limits depend on the wearable's category and which slots it hides. The Tris Combiner rule allows creators to combine triangle budgets when hiding other slots (e.g., a jumpsuit using upper_body and hiding lower_body gets 1500 + 1500 = 3000 triangles).

Because of this dependency, triangle validation behaves differently at each stage:

Context Category known? Hides known? Behavior
Import time (file drop) No No Triangle check skipped entirely. Other validations (dimensions, materials, textures, bones, etc.) still run.
CreateSingleItemModal (category selected) Yes No Checks against the base category limit. If exceeded, shows a warning with a hint: "You can increase this limit by hiding other wearable slots in the Item Editor."
Item Editor (category + hides configured) Yes Yes Full Tris Combiner calculation. The combined limit accounts for all hidden slot budgets. Re-runs automatically when the user changes hides or category in the RightPanel.

The getEffectiveTriangleLimit(category, hides) function in constants.ts computes the combined limit: base category budget + sum of each hidden slot's budget.

Integration into the Upload Flow

Single item upload (ImportStep.tsx):

  • processModel() now calls loadAndValidateModel() from getModelData.ts, which loads the GLTF once, determines the item type (emote vs wearable), and runs the validation suite — all in a single WebGL context.
  • Validation issues are passed through ModelDataAcceptedFileProps → reducer state → context, and displayed in the details step via ValidationIssuesPanel.
  • When the user selects a category in the details step, a lightweight checkTriangleCount() runs against the already-computed metrics.triangles (no GLTF reload) and merges the result into the displayed issues.
  • Validation does not block the import or the save. The creator sees the warnings but can proceed.

Multi-item upload (CreateAndEditMultipleItemsModal.tsx):

  • After loading each ZIP file, loadAndValidateModel() runs on the model with the category and hides from the wearable config. Files with warnings are accepted; the warnings are shown in the review table.

Item editor (CenterPanel.tsx):

  • A validation status indicator is rendered in the footer next to the scene boundaries (cylinder) button.
  • When an item is selected, the model is fetched from the builder's content storage and validated with the item's category and hides.
  • The indicator shows: a loader while validating, a green checkmark on pass, or a yellow exclamation on warnings.
  • Clicking the indicator when there are warnings opens a modal with the full ValidationIssuesPanel (non-collapsible in this context).
  • A tooltip on hover describes the current status.
  • Validation re-runs automatically when the user changes the item's category or hides in the RightPanel.

UI Component (ValidationIssuesPanel)

A panel that lists all validation issues, styled for dark theme using CSS modules (ValidationIssuesPanel.module.css). Warnings are shown in yellow with exclamation icons. The header shows a count.

The component accepts a collapsible prop (defaults to true):

  • Collapsible (inline in details step): the header is clickable and toggles the list open/closed. The list is capped at 200px max-height to avoid pushing form fields down. Uses width: 100% to fill its container.
  • Non-collapsible (inside the item editor modal): the list is always visible with up to 60vh max-height. No toggle arrow or click behavior.

The component directory includes an index.ts barrel export. Used in three places:

  1. WearableDetails — shown above the form when creating/editing a wearable
  2. EmoteDetails — shown above the form when creating/editing an emote
  3. CenterPanel validation modal — shown when clicking the status indicator in the item editor (with collapsible={false})

State Management

  • validationIssues added to StateData and AcceptedFileProps in the CreateSingleItemModal types
  • SET_VALIDATION_ISSUES action added to the reducer
  • validationIssues exposed through the modal's React context
  • In CreateSingleItemModal, a useMemo merges import-time issues with the category-dependent triangle check, updating whenever the category changes

Translations

All translation keys added under create_single_item_modal.error.glb_validation.*, validation_issues_panel.*, and item_editor.center_panel.validation_* in en.json, es.json, and zh.json. Spanish and Chinese translations use real localized text.


Validation Rules

All rules report WARNING severity — none block saving or uploading.

Wearable Validations

Check Limit Code
Triangle count (with category, no hides) Base category limit + hint about Tris Combiner TRIANGLE_COUNT_EXCEEDED
Triangle count (with category + hides) Base + sum of hidden slot budgets TRIANGLE_COUNT_EXCEEDED
Model dimensions 2.42m width, 2.42m height, 1.4m depth DIMENSIONS_EXCEEDED
Material count 2 (standard), 5 (skin) — excludes AvatarSkin_MAT MATERIALS_EXCEEDED
Texture count 2 (standard), 5 (skin) — excludes AvatarSkin_MAT TEXTURES_EXCEEDED
Texture resolution 512x512 px (standard), 256x256 px (facial: eyes, eyebrows, mouth) TEXTURE_RESOLUTION_EXCEEDED
Bone influences per vertex 4 BONE_INFLUENCES_EXCEEDED
Leaf/end bones Must not have _end or _neutral suffixed bones LEAF_BONES_FOUND
Cameras in GLB Not allowed CAMERAS_FOUND
Lights in GLB Not allowed LIGHTS_FOUND
Animations in wearable GLB Not allowed (only emotes should have animations) ANIMATIONS_IN_WEARABLE
Forbidden material naming Must not contain _mouth, _eyebrows, _eyes outside facial categories FORBIDDEN_MATERIAL_NAME

Triangle limits per category (base, before Tris Combiner):

Category Limit
mask, eyewear, earring, tiara, top_head, facial_hair 500
hands_wear 1,000
hat, helmet, hair, upper_body, lower_body, feet 1,500
skin 5,000

Tris Combiner examples:

  • Jumpsuit (upper_body hiding lower_body): 1,500 + 1,500 = 3,000
  • Helmet hiding hair + hat: 1,500 + 1,500 + 1,500 = 4,500

Emote Validations

Check Limit Code
Frame rate Expected 30 fps (tolerance: +/- 5 fps) EMOTE_FRAME_RATE
Max frames 300 EMOTE_MAX_FRAMES
Animation clip count 1 (basic), 2 (with props) EMOTE_MAX_CLIPS
Deform bone keyframes All deform bones must have position, quaternion, and scale tracks EMOTE_MISSING_KEYFRAMES
Avatar displacement 1 meter from starting position (euclidean distance on Hips bone) EMOTE_DISPLACEMENT
Armature naming Must be Armature, Armature_Prop, or Armature_Other ARMATURE_NAMING
Animation clip naming (with props) Clips must end with _Avatar or _Prop ANIMATION_NAMING
Audio format Only .mp3 and .ogg allowed AUDIO_FORMAT

Emote Prop Validations (when props are detected)

Check Limit Code
Prop triangle count 3,000 PROP_TRIANGLE_COUNT
Prop material count 2 PROP_MATERIALS
Prop texture count 2 PROP_TEXTURES
Prop armature bones 62 PROP_ARMATURE_BONES

Screenshots

The screenshots are illustrative, as the styles of the errors were changed to be better under the dark theme

Screenshot 2026-03-18 at 1 36 07 PM Screenshot 2026-03-18 at 12 57 14 PM Screenshot 2026-03-18 at 12 57 08 PM

@vercel
Copy link

vercel bot commented Mar 18, 2026

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
builder Ready Ready Preview, Comment Mar 23, 2026 4:54pm

Request Review

@coveralls
Copy link

coveralls commented Mar 18, 2026

Pull Request Test Coverage Report for Build 23449301748

Details

  • 361 of 410 (88.05%) changed or added relevant lines in 8 files are covered.
  • 2 unchanged lines in 2 files lost coverage.
  • Overall coverage increased (+1.5%) to 49.798%

Changes Missing Coverage Covered Lines Changed/Added Lines %
src/lib/glbValidation/wearableValidators.ts 118 120 98.33%
src/lib/glbValidation/emoteValidators.ts 172 175 98.29%
src/lib/getModelData.ts 2 14 14.29%
src/lib/glbValidation/index.ts 4 36 11.11%
Files with Coverage Reduction New Missed Lines %
src/lib/getModelData.ts 1 8.14%
src/modules/item/errors.tsx 1 47.62%
Totals Coverage Status
Change from base Build 23287840377: 1.5%
Covered Lines: 6667
Relevant Lines: 12108

💛 - Coveralls

Comment on lines +379 to +382
"title": "Your file has validation issues",
"has_errors": "Your file has validation errors that must be fixed before uploading.",
"triangle_count_exceeded": "Triangle count ({count}) exceeds the limit of {limit} for {category} wearables.",
"triangle_count_warning": "Triangle count ({count}) may exceed the limit for some wearable categories. The standard limit is 1,500 triangles.",
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

es and zh translations are en translations 😛

@@ -0,0 +1,39 @@
/** Severity level for a GLB validation issue. */
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

the AI went extra verbose here. I think we can trim most of the comments since they are pretty self-explanatory already

@ShibuyaMartin
Copy link

Wearable Tris Combiner:

If the wearable hide other wearables the creator is allowed to combine the tris per slot. For example: if you want to do a jumpsuit you could create it using the upper body category hiding lower body; in that case you could have 1.5K2= 3K triangles.*

In the case of the helmet, if you hide all the head wearables (head, earrings, eyewear, tiara, hat, facial_hair, hair and top_head you can reach the 4k tris, 2 materials and 2 textures)

Does it have this into account @LautaroPetaccio ? docs

@LautaroPetaccio LautaroPetaccio merged commit 365bf52 into master Mar 23, 2026
7 checks passed
@LautaroPetaccio LautaroPetaccio deleted the feat/add-wearable-and-emotes-model-checks branch March 23, 2026 17:02
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants