Typed asset readers
avatar-unity-asset is the typed layer over avatar-unity-yaml:
where that crate splits a Unity file into class-tagged documents of raw YAML, this one reads a specific
asset graph into Rust structs — the AnimatorController
(.controller) VRChat avatars drive through their playable layers, and the
AnimationClip (.anim) those controllers play. It is the reader the
VRC04x+ lint rules consume. Library crate only, no CLI
surface of its own. Full text: docs/reference/unity-asset.md.
What a .controller file is
A .controller is a multi-document Unity YAML stream: one AnimatorController
object (class id 91) owns a graph of further objects, linked by local fileIDs.
| Class id | Object | What this crate reads from it |
|---|---|---|
91 | AnimatorController | name, declared parameters (m_AnimatorParameters) |
1107 | AnimatorStateMachine | child-state count, whether m_DefaultState resolves |
1102 | AnimatorState | m_WriteDefaultValues and the m_Motion reference (one per state) |
1101 / 1109 | AnimatorStateTransition / AnimatorTransition | each m_Conditions entry |
206 | BlendTree | blend type, blend parameter(s), per-child direct parameters + external child-motion guids |
Reading a controller
use avatar_unity_asset::{AnimatorController, AnimationClip};
use avatar_unity_yaml::UnityFile;
let file = UnityFile::parse(&std::fs::read_to_string("Assets/FX.controller")?)?;
let ctrl = AnimatorController::from_file(&file).expect("no controller document");
for c in &ctrl.conditions {
println!("{} mode={} threshold={}", c.parameter, c.mode, c.threshold);
}
let clip = AnimationClip::from_file(&UnityFile::parse(&std::fs::read_to_string("Assets/Smile.anim")?)?);
assert!(clip.map_or(true, |c| !c.animates_muscles()));
There is no CLI for this crate; avatar lint is its consumer.
Aggregate, not full graph
For the questions the lint rules ask — which parameters are referenced, are write-defaults
consistent, is a default state set — the full linked graph is unnecessary. So
from_file walks the file's documents and aggregates the relevant fields by Unity
class id rather than rebuilding the state-machine topology. That is robust to SDK version
drift, because the field names are stable Unity serialization rather than VRChat specifics.
Key controller types
AnimatorController— the parsed result:name,parameters,conditions(every condition across every transition),blend_trees,state_machines,write_defaults(one bool per state, in document order),states,state_count.from_file(&UnityFile)returnsNoneif the file has no controller document.AnimatorParameter—raw_typeis Unity'sm_Type(1 Float, 3 Int, 4 Bool, 9 Trigger).AnimatorCondition—parameter, rawm_ConditionMode(1 If, 2 IfNot, 3 Greater, 4 Less, 6 Equals, 7 NotEqual), threshold (Unity's misspelledm_EventTresholdfield).BlendTreeInfo—blend_type(0 = 1D, 1–3 = 2D variants, 4 = Direct);referenced_parameters()returns only what the tree actually reads given its type: a 1D tree reads X, a 2D tree X and Y, a Direct tree each child's direct parameter.StateMachineInfo—has_default_stateistrueonly whenm_DefaultStatepoints at a real state.StateInfo— per-state name, write-defaults flag, and aMotionRef { file_id, guid }: a local blend tree (fileIDonly), an external clip (guidset), or null.blend_tree_motion_guidscollects every external guid blend-tree children reference.
AnimationClip (.anim, class 74)
AnimationClip::from_file reads a clip down to its curve bindings —
what each curve animates, not the keyframe data, which is all the clip-content lint rules need:
float_curves— everym_FloatCurvesentry as{ path, attribute, class_id }(blendshapes bind class 137, GameObject toggles class 1, humanoid muscle curves class 95 with an empty path —is_muscle()tests the latter).transform_curves— total entries across position/rotation/euler/scale curves;pptr_curves—m_PPtrCurvesentries (material swaps).- Predicates:
is_empty(),animates_transforms(),animates_muscles().
What lint asks of it
The controller and clip lint rules are direct consumers: undeclared parameters in transition conditions and blend trees (respecting blend type), state machines with no default state, duplicate parameter names, and mixed Write Defaults across states — a controller should be all-on or all-off. The clip bindings feed the clip-content rules (empty clips, transform or muscle curves where they don't belong).
crates/vrc-descriptor/README.md. Material / scene typing is still to come.