Performance stats
avatar stats <path> (crate avatar-stats) computes VRChat's performance
ranking offline — Excellent→Very Poor, against the PC and Android limit tables, with no Unity in
the loop. The overall rank is the worst measured metric. Full text:
docs/reference/performance-stats.md.
Geometry (from an FBX)
avatar stats model.fbx # geometry: triangles, meshes, material slots, bones
avatar stats path/to/UnityProject # components, per avatar found in the project
avatar stats path/to/UnityProject --json
analyze_fbx measures triangles, meshes, material slots, and bones directly from the FBX
geometry.
Components (from a project)
analyze_project measures per-avatar components: PhysBones, colliders, contacts, particles,
lights, renderers, constraints. Most are matched by stable Unity class id; the VRChat
dynamics and constraint components are MonoBehaviours whose script GUID changes across SDK versions, so
they're recognized structurally by their distinctive serialized fields
(collisionTags ⇒ Contact, bonesAsSpheres/insideBounds ⇒
PhysBone collider, endpointPosition/multiChildType ⇒ PhysBone, a
Sources list with a TargetTransform ⇒ VRChat constraint). A project path
additionally resolves:
- Triangles — renderer
m_Meshguid → source FBX via the.metaindex (distinct files summed once; unresolved meshes flagged). - Bones — distinct
m_Bonestransforms. - Texture memory for PC and Android — renderer → material → texture chain; per-texture VRAM estimated from image-header dimensions + the
.metaTextureImporterper-platform format (DXT/BC vs ASTC/ETC2). An estimate, since the imported GPU format isn't knowable offline. - PhysBone affected-transform & collision-check counts — walking the hierarchy under each PhysBone's
rootTransform(descendants minusignoreTransforms, plus an endpoint per chain tip, × assigned colliders). - Total particles — per
ParticleSystem, VRChat's own ceilingmin(maxParticles, ceil(rate × lifetime)), summed; MinMaxCurves are read by their constant term (bursts and sub-emitters approximated, so burst-heavy effects read low). - Mesh-particle polygons — each Mesh-render-mode
ParticleSystemRenderer(paired to its system by GameObject) resolves its mesh to a source FBX and multiplies its triangles by the system's live-particle estimate. Trail and collision flags are read offTrailModule/CollisionModuleenabled. - Constraints — Unity built-in constraint class ids 320–325 + VRChat constraint MonoBehaviours; depth = longest constraint→source chain via a memoized, cycle-safe walk (depth reported only when every source slot resolves).
not_evaluated list is empty by default. An individual metric adds a note there only when its
inputs were incomplete (an unparseable particle module, a mesh that resolves to a non-FBX source, an
unresolvable constraint chain), marking the value as a lower bound rather than silently dropping it.
The threshold tables
Encoded as data on Metric::limits(Platform) (rules as data, not constants), sourced from
VRChat's Avatar Performance Ranking System. Each cell is the inclusive upper bound for that tier; anything
past Poor is Very Poor.
PC (Windows)
| Metric | Excellent | Good | Medium | Poor |
|---|---|---|---|---|
| Triangles | 32,000 | 70,000 | 70,000 | 70,000 |
| Skinned Meshes | 1 | 2 | 8 | 16 |
| Basic Meshes | 4 | 8 | 16 | 24 |
| Material Slots | 4 | 8 | 16 | 32 |
| Bones | 75 | 150 | 256 | 400 |
| PhysBone Components | 4 | 8 | 16 | 32 |
| PhysBone Colliders | 4 | 8 | 16 | 32 |
| PhysBone Affected Transforms | 16 | 64 | 128 | 256 |
| PhysBone Collision Check Count | 32 | 128 | 256 | 512 |
| Contacts | 8 | 16 | 24 | 32 |
| Particle Systems | 0 | 4 | 8 | 16 |
| Total Particles | 0 | 300 | 1,000 | 2,500 |
| Mesh Particle Polygons † | 0 | 2,000 | 20,000 | 50,000 |
| Particle Trails | 0 | 0 | 0 | 1 |
| Particle Collision | 0 | 0 | 0 | 1 |
| Constraints | 100 | 250 | 300 | 350 |
| Constraint Depth | 20 | 50 | 80 | 100 |
| Lights | 0 | 0 | 0 | 1 |
| Audio Sources | 1 | 4 | 8 | 8 |
| Trail Renderers | 1 | 2 | 4 | 8 |
| Line Renderers | 1 | 2 | 4 | 8 |
| Cloths | 0 | 1 | 1 | 1 |
| Physics Colliders | 0 | 1 | 8 | 8 |
| Physics Rigidbodies | 0 | 1 | 8 | 8 |
| Animators | 1 | 4 | 16 | 32 |
Android / Quest (stricter)
| Metric | Excellent | Good | Medium | Poor |
|---|---|---|---|---|
| Triangles | 7,500 | 10,000 | 15,000 | 20,000 |
| Skinned Meshes | 1 | 1 | 2 | 2 |
| Basic Meshes | 1 | 1 | 2 | 2 |
| Material Slots | 1 | 1 | 2 | 4 |
| Bones | 75 | 90 | 150 | 150 |
| PhysBone Components | 0 | 4 | 6 | 8 |
| PhysBone Colliders | 0 | 4 | 8 | 16 |
| PhysBone Affected Transforms | 0 | 16 | 32 | 64 |
| PhysBone Collision Check Count | 0 | 16 | 32 | 64 |
| Contacts | 2 | 4 | 8 | 16 |
| Particle Systems | 0 | 0 | 0 | 2 |
| Total Particles | 0 | 0 | 0 | 200 |
| Mesh Particle Polygons † | 0 | 0 | 2,000 | 20,000 |
| Particle Trails | 0 | 0 | 0 | 1 |
| Particle Collision | 0 | 0 | 0 | 1 |
| Constraints | 30 | 60 | 120 | 150 |
| Constraint Depth | 5 | 15 | 35 | 50 |
| Trail Renderers | 0 | 0 | 0 | 1 |
| Line Renderers | 0 | 0 | 0 | 1 |
| Animators | 1 | 1 | 1 | 2 |
Lights, Audio Sources, Cloths, and the physics colliders/rigidbodies are not ranked on Android (those components are stripped on mobile). † The Mesh Particle Polygons bounds are the repo's own triangle-style ramp — VRChat folds mesh-particle polygons into its particle budget rather than publishing a standalone table.