Summary
AppearanceManager.MakeAppearancePacket() hard-caps the VisualParam block
count at 218 (251 with a Physics wearable):
// LibreMetaverse/Appearance/AppearanceManager.cs (master @ 9455bdb, line 2490)
var nrParams = wearingPhysics ? 251 : 218;
set.VisualParam = new AgentSetAppearancePacket.VisualParamBlock[nrParams];
Modern viewers (tested against Firestorm) send 253 VisualParam blocks.
The array is positional — the receiver maps values onto its ordered param
table by index — so a library-driven client that sets its appearance on an
OpenSim region deforms the avatar: the first ~23 values line up, then the
membership of the library's VisualParams.Params enumeration diverges from
the viewer's ordering and every subsequent value lands on the wrong
slider (torso/leg/height params get unrelated values → grossly deformed,
"squashed" body). OpenSim persists the received array, so the corruption
also survives to subsequent sessions.
Reproduction
- Dress an avatar with a viewer (Firestorm) on an OpenSim grid
(0.9.3.0 standalone in our case) and log out. The grid's avatars.db
VisualParams row now holds 253 comma-separated values.
- Log the same avatar in with a LibreMetaverse 3.1.3 client with
Settings.Agent.SendAppearance enabled (default) and let the CSB
pipeline run to completion (all wearables download, bake succeeds,
AppearanceSet fires with Success = true).
- Observe in any viewer: the avatar's body is deformed. Inspect the
persisted VisualParams row: now 218 values.
Direct comparison of the persisted arrays (same avatar, same wearables):
viewer count=253: ... 63, 165, 88, 132, 0, 136, 81, 85, 103, 136 ...
^ index 23
library count=218: ... 63, 165, 88, 0, 132, 0, 63, 63, 0, 0 ...
Identical through index 22, then the library's sequence contains an entry
the viewer's ordering doesn't have (or vice versa) and everything shifts.
The values the library computes are correct — the height-related params
it looks up by ID for AgentData.Size produce the right avatar height —
only the positional serialization is wrong.
Expected
The VisualParam array should match the current viewer wire format: 253
entries, same param membership and ordering, so index N means the same
slider to every receiver.
Suggested fix
Bring VisualParams.Params (and the nrParams cap) up to date with the
current avatar_lad param set used by SL/Firestorm-era viewers (253 params,
Bakes-on-Mesh era), and derive the block count from the table rather than
the hardcoded 218/251. The special-casing on wearingPhysics suggests the
table already contains more params than are sent — the cap, not the table,
may be the immediate problem, but membership/order needs to match too
(the divergence starts at index ~23, well before the physics params).
Workaround we're using
Send appearance packets ourselves: let the CSB pipeline bake, then rebuild
the packet via the (public) MakeAppearancePacket() and replace
pkt.VisualParam with the 253-value array the grid has persisted from a
viewer session before sending. This renders correctly everywhere and
round-trips through OpenSim persistence.
Happy to provide logs or test on a branch.
Environment: LibreMetaverse 3.1.3 (NuGet, repo commit 70877a6), .NET 10,
OpenSim 0.9.3.0 standalone, observed with Firestorm viewers.
Summary
AppearanceManager.MakeAppearancePacket()hard-caps theVisualParamblockcount at 218 (251 with a Physics wearable):
Modern viewers (tested against Firestorm) send 253 VisualParam blocks.
The array is positional — the receiver maps values onto its ordered param
table by index — so a library-driven client that sets its appearance on an
OpenSim region deforms the avatar: the first ~23 values line up, then the
membership of the library's
VisualParams.Paramsenumeration diverges fromthe viewer's ordering and every subsequent value lands on the wrong
slider (torso/leg/height params get unrelated values → grossly deformed,
"squashed" body). OpenSim persists the received array, so the corruption
also survives to subsequent sessions.
Reproduction
(0.9.3.0 standalone in our case) and log out. The grid's
avatars.dbVisualParamsrow now holds 253 comma-separated values.Settings.Agent.SendAppearanceenabled (default) and let the CSBpipeline run to completion (all wearables download, bake succeeds,
AppearanceSetfires withSuccess = true).persisted
VisualParamsrow: now 218 values.Direct comparison of the persisted arrays (same avatar, same wearables):
Identical through index 22, then the library's sequence contains an entry
the viewer's ordering doesn't have (or vice versa) and everything shifts.
The values the library computes are correct — the height-related params
it looks up by ID for
AgentData.Sizeproduce the right avatar height —only the positional serialization is wrong.
Expected
The
VisualParamarray should match the current viewer wire format: 253entries, same param membership and ordering, so index N means the same
slider to every receiver.
Suggested fix
Bring
VisualParams.Params(and thenrParamscap) up to date with thecurrent avatar_lad param set used by SL/Firestorm-era viewers (253 params,
Bakes-on-Mesh era), and derive the block count from the table rather than
the hardcoded 218/251. The special-casing on
wearingPhysicssuggests thetable already contains more params than are sent — the cap, not the table,
may be the immediate problem, but membership/order needs to match too
(the divergence starts at index ~23, well before the physics params).
Workaround we're using
Send appearance packets ourselves: let the CSB pipeline bake, then rebuild
the packet via the (public)
MakeAppearancePacket()and replacepkt.VisualParamwith the 253-value array the grid has persisted from aviewer session before sending. This renders correctly everywhere and
round-trips through OpenSim persistence.
Happy to provide logs or test on a branch.
Environment: LibreMetaverse 3.1.3 (NuGet, repo commit 70877a6), .NET 10,
OpenSim 0.9.3.0 standalone, observed with Firestorm viewers.