AAS Bookkeeping
TL;DR: Each non-core, non-local, non-curve animator parameter on a CVRAvatar consumes bits from a 3200-bit sync budget — 32 for floats and ints, 1 for bools (packed eight per byte). Cross the cap and additional parameters are silently kept local. Some Advanced Avatar Settings UI types fan out into multiple animator parameter names with -r/-g/-b or -x/-y/-z suffixes — make sure your animator declares the suffixed names, not the machine name.
Bit budget
Section titled “Bit budget”AvatarDefinitions declares the constants:
public const int AAS_MAX_SYNCED_BITS = 3200;public const int AAS_FLOAT_BIT_USAGE = 32;public const int AAS_INT_BIT_USAGE = 32;public const int AAS_BOOL_BIT_USAGE = 1;public const string LOCAL_PARAMETER_PREFIX = "#";| Parameter type | Bits |
|---|---|
Float | 32 |
Int | 32 |
Bool | 1 (packed 8 per byte) |
Trigger | 0 — always local |
Triggers are forced local in AvatarParam’s constructor:
isLocal |= type == AnimatorControllerParameterType.Trigger;…so they never enter the budget.
How parameters count toward the budget
Section titled “How parameters count toward the budget”AvatarAnimatorManager.CreateParameterDefinition is called once per animator parameter at avatar setup. The accounting happens inside:
var avatarParam = new AvatarParam(name, isControlledByCurve, type, isParamInAas);if (avatarParam.IsSynced && AASBitUsage < 3200){ switch (type) { case Float: AASSyncedFloatCount++; AASBitUsage += 32; ... case Int: AASSyncedIntCount++; AASBitUsage += 32; ... case Bool: AASSyncedBoolCount++; AASBitUsage += 1; ... }}IsSynced requires:
isValid— name not null/empty!isLocal— name does not start with#and the parameter is not a trigger!IsReadOnly— not core, not curve-controlled
When Setup() finishes, the manager rounds the bool count up to a byte:
AASSyncedByteCount = (int)Math.Ceiling((float)AASSyncedBoolCount / 8f);The outbound caches (_aasOutboundCacheFloat/Int/Bool) are filled with the parameters’ default values at registration. Subsequent writes (through SetParameter_Internal → SetFloat/Int/Bool_Animator) update the cached value and set AASParameterChangedSinceLastSync = true. Each sync tick GetAASParameterSyncData() packs the caches into:
public struct AvatarAdditionalSyncData{ public float[] AdditionalParametersFloat; public int[] AdditionalParametersInt; public byte[] AdditionalParametersByte; // bool array packed 8/byte}AASParameterChangedSinceLastSync is only reset by GetAASParameterSyncData(), so dirty-then-flush is the actual cadence.
Cap behaviour
Section titled “Cap behaviour”AASBitUsage < 3200 is checked before each parameter is added to the indices. If AASBitUsage is already at the cap when a new synced parameter is encountered:
- the parameter is not added to
_aasOutboundIndicesFloat/Int/Bool - the parameter is not added to
_aasOutboundCacheFloat/Int/Bool - subsequent writes update the underlying
Animatorvalue normally - the value is never packed into an outbound sync packet
So crossing the cap silently demotes additional parameters to local-only. The AASBitUsage property is the running total — query it from a mod to estimate how close to the cap an avatar is.
AAS UI → animator parameter names
Section titled “AAS UI → animator parameter names”Authors expose AAS controls in the inspector by populating CVRAdvancedAvatarSettings.settings. At avatar load, PlayerBase.PopulateAasParameterNameCache walks every CVRAdvancedSettingsEntry and pre-computes the set of expected animator parameter names from the entry’s machineName:
| AAS type | Animator parameter names produced |
|---|---|
MaterialColor | <machineName>-r, <machineName>-g, <machineName>-b |
Joystick3D, InputVector3 | <machineName>-x, <machineName>-y, <machineName>-z |
Joystick2D, InputVector2 | <machineName>-x, <machineName>-y |
Everything else (Slider, Dropdown, Toggle, …) | <machineName> |
So a colour picker called HairColor requires HairColor-r, HairColor-g, and HairColor-b parameters on your animator (Float type). Declaring just HairColor won’t pick up the colour picker’s writes.
The names produced here are added to Avatar.AvatarSettingsParameterNames, which is consulted at parameter creation time:
bool isParamInAas = Avatar.AvatarSettingsParameterNames.Contains(name);This is what makes a parameter IsAAS == true (and therefore eligible for profile saving).
Profile persistence
Section titled “Profile persistence”AvatarAnimatorManager.GetAdditionalSettingsCurrent() returns the current values of every parameter where CanSaveToProfile is true:
public bool CanSaveToProfile => isValid && !isCore && !IsReadOnly && IsAAS;So a parameter is profile-saveable only when:
- it has a valid name
- it isn’t in
CoreParameters(soMovementX,Grounded, etc. are excluded) - it isn’t read-only (so curve-controlled params are excluded)
- it has been declared in the AAS UI (
isParamInAas == true)
That’s why ad-hoc animator parameters that you write to from a script but never expose in the AAS settings will never persist to a profile.
ApplyAdvancedSettingsFileProfile(values) is the inverse: it loops over the loaded CVRAdvancedSettingsFileProfileValue list, calls SetParameter for each, and notifies the menu via SendAdvancedAvatarUpdate(name, value, markChange: false).
Inspectable counters
Section titled “Inspectable counters”AvatarAnimatorManager exposes the running state as public read-only properties:
| Property | Meaning |
|---|---|
AASBitUsage | Total synced bits used. Capped at 3200. |
AASSyncedFloatCount | Synced float parameter count. |
AASSyncedIntCount | Synced int parameter count. |
AASSyncedBoolCount | Synced bool parameter count. |
AASSyncedByteCount | ceil(AASSyncedBoolCount / 8). |
AASParameterChangedSinceLastSync | Dirty flag — set on any synced write, cleared by GetAASParameterSyncData(). |
Useful when building a tool that visualises how much of the 3200-bit budget your avatar is consuming.