From a7bf4641e637ee97590915735890a254c44d3480 Mon Sep 17 00:00:00 2001
From: Lyrae VerseBound <315994013+lyrae-versebound@users.noreply.github.com>
Date: Fri, 28 Aug 2026 14:22:29 -0400
Subject: [PATCH] Fix visual parameter wire serialization
---
.../AppearanceManagerTests.cs | 25 +++++++++++++++++++
LibreMetaverse.Tests/AppearanceTests.cs | 6 ++---
.../Appearance/AppearanceManager.cs | 13 +++-------
LibreMetaverse/Avatar.cs | 3 +--
.../VisualParamGenerator.cs | 6 ++---
5 files changed, 36 insertions(+), 17 deletions(-)
diff --git a/LibreMetaverse.Tests/AppearanceManagerTests.cs b/LibreMetaverse.Tests/AppearanceManagerTests.cs
index fb20697de..da77593eb 100644
--- a/LibreMetaverse.Tests/AppearanceManagerTests.cs
+++ b/LibreMetaverse.Tests/AppearanceManagerTests.cs
@@ -1,3 +1,4 @@
+using System.Linq;
using NUnit.Framework;
namespace LibreMetaverse.Tests
@@ -5,6 +6,30 @@ namespace LibreMetaverse.Tests
[TestFixture]
public class AppearanceManagerTests
{
+ [Test]
+ public void MakeAppearancePacket_UsesCompleteVisualParamWireSequence()
+ {
+ var client = new GridClient();
+ var packet = client.Appearance.MakeAppearancePacket();
+ var wireParamIds = VisualParams.Group0ParamIds;
+ var expectedValues = wireParamIds
+ .Select(id => VisualParams.Params[id])
+ .Select(param => Utils.FloatToByte(param.DefaultValue, param.MinValue, param.MaxValue))
+ .ToArray();
+
+ Assert.Multiple(() =>
+ {
+ Assert.That(wireParamIds, Has.Length.EqualTo(253));
+ Assert.That(wireParamIds, Is.Ordered);
+ Assert.That(wireParamIds.Distinct().Count(), Is.EqualTo(wireParamIds.Length));
+ Assert.That(wireParamIds.All(id =>
+ VisualParams.Params.TryGetValue(id, out var param) &&
+ (param.Group == 0 || param.Group == 3)), Is.True);
+ Assert.That(packet.VisualParam.Select(block => block.ParamValue),
+ Is.EqualTo(expectedValues));
+ });
+ }
+
[Test]
public void WearableTypeToAssetType_BodypartsAndClothing_ReturnsExpected()
{
diff --git a/LibreMetaverse.Tests/AppearanceTests.cs b/LibreMetaverse.Tests/AppearanceTests.cs
index 10a640a36..c025693ad 100644
--- a/LibreMetaverse.Tests/AppearanceTests.cs
+++ b/LibreMetaverse.Tests/AppearanceTests.cs
@@ -70,8 +70,8 @@ public void DecodeVisualParams_CountMatchesGroup0ParamCount()
[Test]
public void DecodeVisualParams_RoundTrip_DefaultValues()
{
- // Build the byte array in Group0ParamIds document order — this is the order
- // DecodeVisualParams consumes, which differs from the SortedList key order.
+ // Build the byte array in Group0ParamIds wire order — this is the order
+ // DecodeVisualParams consumes.
var bytes = VisualParams.Group0ParamIds
.Select(id => VisualParams.Params.TryGetValue(id, out var vp)
? Utils.FloatToByte(vp.DefaultValue, vp.MinValue, vp.MaxValue)
@@ -176,7 +176,7 @@ public void AvatarAppearanceEventArgs_DecodeVisualParams_EmptyList_ReturnsEmptyD
[Test]
public void AvatarAppearanceEventArgs_DecodeVisualParams_RoundTrip_DefaultValues()
{
- // Build bytes in Group0ParamIds document order — this is the order DecodeVisualParams consumes.
+ // Build bytes in Group0ParamIds wire order — this is the order DecodeVisualParams consumes.
var bytes = VisualParams.Group0ParamIds
.Select(id => VisualParams.Params.TryGetValue(id, out var vp)
? Utils.FloatToByte(vp.DefaultValue, vp.MinValue, vp.MaxValue)
diff --git a/LibreMetaverse/Appearance/AppearanceManager.cs b/LibreMetaverse/Appearance/AppearanceManager.cs
index 34c535ef5..ddecc9d9e 100644
--- a/LibreMetaverse/Appearance/AppearanceManager.cs
+++ b/LibreMetaverse/Appearance/AppearanceManager.cs
@@ -2484,16 +2484,12 @@ public AgentSetAppearancePacket MakeAppearancePacket()
{
#region VisualParam
- var vpIndex = 0;
- var wearingPhysics = Wearables.ContainsKey(WearableType.Physics);
+ var wireParamIds = VisualParams.Group0ParamIds;
+ set.VisualParam = new AgentSetAppearancePacket.VisualParamBlock[wireParamIds.Length];
- var nrParams = wearingPhysics ? 251 : 218;
- set.VisualParam = new AgentSetAppearancePacket.VisualParamBlock[nrParams];
-
- foreach (var kvp in VisualParams.Params)
+ for (var vpIndex = 0; vpIndex < wireParamIds.Length; ++vpIndex)
{
- if (vpIndex >= nrParams) break;
- var vp = kvp.Value;
+ var vp = VisualParams.Params[wireParamIds[vpIndex]];
var paramValue = 0f;
var found = Wearables.Any(wearableList => wearableList.Value.Any(wearable => wearable.Asset != null && wearable.Asset.Params.TryGetValue(vp.ParamID, out paramValue)));
@@ -2505,7 +2501,6 @@ public AgentSetAppearancePacket MakeAppearancePacket()
{
ParamValue = Utils.FloatToByte(paramValue, vp.MinValue, vp.MaxValue)
};
- ++vpIndex;
// Check if this is one of the visual params used in the agent height calculation
switch (vp.ParamID)
diff --git a/LibreMetaverse/Avatar.cs b/LibreMetaverse/Avatar.cs
index 217afcef0..6944f4748 100644
--- a/LibreMetaverse/Avatar.cs
+++ b/LibreMetaverse/Avatar.cs
@@ -498,7 +498,7 @@ public string GroupName
///
///
/// The AvatarAppearance packet transmits both TWEAKABLE (group-0) and
- /// TRANSMIT_NOT_TWEAKABLE (group-3) params interleaved in avatar_lad.xml document order.
+ /// TRANSMIT_NOT_TWEAKABLE (group-3) params interleaved in ascending numeric ID order.
/// includes both groups in that order so each
/// byte maps to the correct parameter.
///
@@ -662,4 +662,3 @@ public override OSD GetOSD()
}
}
-
diff --git a/SourceGenerators/VisualParamGenerator/VisualParamGenerator.cs b/SourceGenerators/VisualParamGenerator/VisualParamGenerator.cs
index 97d698b40..667535f49 100644
--- a/SourceGenerators/VisualParamGenerator/VisualParamGenerator.cs
+++ b/SourceGenerators/VisualParamGenerator/VisualParamGenerator.cs
@@ -294,7 +294,7 @@ public static class VisualParams
public static SortedList Params = new SortedList();
///
- /// Group-0 parameter IDs in the exact order they appear in avatar_lad.xml.
+ /// Group-0 and group-3 parameter IDs in ascending numeric ID order.
/// This order matches the byte sequence in the AvatarAppearance packet visual_param block.
///
public static int[] Group0ParamIds = Array.Empty();
@@ -382,7 +382,7 @@ private static string GenerateFromTemplateAndXml(string templateText, string xml
var nodes = doc.GetElementsByTagName("param");
var ids = new SortedList();
- var group0IdsInOrder = new List(); // group-0 and group-3 (TRANSMIT_NOT_TWEAKABLE) IDs in avatar_lad.xml document order
+ var group0IdsInOrder = new List(); // group-0 and group-3 (TRANSMIT_NOT_TWEAKABLE) wire IDs
var alphas = new Dictionary();
var colors = new Dictionary();
var drivenParamInfoMap = new Dictionary();
@@ -640,7 +640,7 @@ private static string GenerateFromTemplateAndXml(string templateText, string xml
{
// group-3 (VISUAL_PARAM_GROUP_TRANSMIT_NOT_TWEAKABLE) params are also
// transmitted in the AvatarAppearance packet interleaved with group-0 params
- // in avatar_lad.xml document order. They must be included here so that
+ // in ascending numeric ID order. They must be included here so that
// DecodeVisualParams() reads each byte from the correct offset.
group0IdsInOrder.Add(id);
}