diff --git a/src/AcDream.App/UI/Layout/CharacterStatController.cs b/src/AcDream.App/UI/Layout/CharacterStatController.cs
index 4fb6c0c4..ad3e0538 100644
--- a/src/AcDream.App/UI/Layout/CharacterStatController.cs
+++ b/src/AcDream.App/UI/Layout/CharacterStatController.cs
@@ -138,6 +138,8 @@ public static class CharacterStatController
/// Row highlight color — semi-translucent gold, matches retail
/// UIStateId.Highlight (0x06) sprite 0x06001397 visual intent.
private static readonly Vector4 HighlightBg = new(1f, 0.75f, 0.2f, 0.25f);
+ private static readonly Vector4 SkillRowBackground = new(0f, 0f, 0f, 1f);
+ private static readonly Vector4 BuffedSkillGreen = new(0.55f, 1f, 0.55f, 1f);
// ── Row layout constants ─────────────────────────────────────────────────
// RowHeight 22px + IconSize 16px: retail spec (2026-06-26) says icons ~icon-height
@@ -155,8 +157,8 @@ public static class CharacterStatController
private const uint SkillHeaderSpecializedSprite = 0x06000F90u;
private const uint SkillHeaderTrainedSprite = 0x06000F86u;
- private const uint SkillHeaderUntrainedSprite = 0x06000F89u;
- private const uint SkillRowBgSprite = 0x06000F98u;
+ private const uint SkillHeaderUntrainedSprite = 0x06000F98u;
+ private const uint SkillHeaderUnusableSprite = 0x06000F89u;
private const uint RowHighlightSprite = 0x06001397u;
private enum CharacterStatTab
@@ -583,9 +585,9 @@ public static class CharacterStatController
OrderedSkills(data(), CharacterSkillAdvancementClass.Specialized, usableUntrained: null));
AddBucket("Trained Skills", SkillHeaderTrainedSprite,
OrderedSkills(data(), CharacterSkillAdvancementClass.Trained, usableUntrained: null));
- AddBucket("Useable Untrained Skills", SkillHeaderUntrainedSprite,
+ AddBucket("Untrained Skills", SkillHeaderUntrainedSprite,
OrderedSkills(data(), CharacterSkillAdvancementClass.Untrained, usableUntrained: true));
- AddBucket("Unuseable Untrained Skills", SkillHeaderUntrainedSprite,
+ AddBucket("Unusable Skills", SkillHeaderUnusableSprite,
OrderedSkills(data(), CharacterSkillAdvancementClass.Untrained, usableUntrained: false));
skillRows = bindings;
@@ -606,7 +608,8 @@ public static class CharacterStatController
nameText: skill.Name,
valueProvider: () => skill.CurrentLevel.ToString(),
valueColorProvider: () => SkillValueColor(skill),
- backgroundSprite: SkillRowBgSprite,
+ nameColor: Vector4.One,
+ backgroundColor: SkillRowBackground,
useSelectionBars: false);
row.OnClick = () => HandleSkillRowClick(rowIndex, sel, bindings, spriteResolve, data, allRaise1, allRaise10);
bindings.Add(new SkillRowBinding(row, skill));
@@ -656,7 +659,7 @@ public static class CharacterStatController
Anchors = AnchorEdges.Left | AnchorEdges.Top,
};
string captured = title;
- label.LinesProvider = () => new[] { new UiText.Line(captured, Gold) };
+ label.LinesProvider = () => new[] { new UiText.Line(captured, Vector4.One) };
header.AddChild(label);
list.AddChild(header);
return header;
@@ -700,9 +703,9 @@ public static class CharacterStatController
}
private static Vector4 SkillValueColor(CharacterSkill skill)
- => skill.CurrentLevel > skill.BaseLevel ? new Vector4(0.55f, 1f, 0.55f, 1f)
+ => skill.CurrentLevel > skill.BaseLevel ? BuffedSkillGreen
: skill.CurrentLevel < skill.BaseLevel ? new Vector4(1f, 0.45f, 0.45f, 1f)
- : Body;
+ : Vector4.One;
///
/// Handles a row click: toggle (same row → deselect), else select new row.
@@ -781,7 +784,7 @@ public static class CharacterStatController
{
if (spriteResolve is not null)
{
- row.BackgroundColor = Vector4.Zero;
+ row.BackgroundColor = SkillRowBackground;
row.BackgroundSprite = RowHighlightSprite;
row.SpriteResolve = id => { var (h, w, ht) = spriteResolve(id); return (h, w, ht); };
row.UseSelectionBars = true;
@@ -796,11 +799,9 @@ public static class CharacterStatController
}
else
{
- row.BackgroundColor = Vector4.Zero;
- row.BackgroundSprite = spriteResolve is not null ? SkillRowBgSprite : 0u;
- row.SpriteResolve = spriteResolve is not null
- ? id => { var (h, w, ht) = spriteResolve(id); return (h, w, ht); }
- : null;
+ row.BackgroundColor = SkillRowBackground;
+ row.BackgroundSprite = 0u;
+ row.SpriteResolve = null;
row.UseSelectionBars = false;
}
}
@@ -931,6 +932,8 @@ public static class CharacterStatController
string nameText,
Func valueProvider,
Func? valueColorProvider = null,
+ Vector4? nameColor = null,
+ Vector4? backgroundColor = null,
uint backgroundSprite = 0u,
bool useSelectionBars = true)
{
@@ -940,7 +943,7 @@ public static class CharacterStatController
Top = top,
Width = width,
Height = height,
- BackgroundColor = Vector4.Zero, // transparent until selected
+ BackgroundColor = backgroundColor ?? Vector4.Zero,
BackgroundSprite = spriteResolve is not null ? backgroundSprite : 0u,
SpriteResolve = spriteResolve is not null && backgroundSprite != 0u
? id => { var (h, w, ht) = spriteResolve(id); return (h, w, ht); }
@@ -973,6 +976,7 @@ public static class CharacterStatController
float nameY = 0f;
string capturedName = nameText;
+ Vector4 capturedNameColor = nameColor ?? Body;
var nameEl = new UiText
{
Left = nameX,
@@ -986,7 +990,7 @@ public static class CharacterStatController
Padding = 1f,
Anchors = AnchorEdges.Left | AnchorEdges.Top,
};
- nameEl.LinesProvider = () => new[] { new UiText.Line(capturedName, Body) };
+ nameEl.LinesProvider = () => new[] { new UiText.Line(capturedName, capturedNameColor) };
float valueW = width - nameX - nameW - RowPadX;
float valueX = nameX + nameW;
diff --git a/src/AcDream.App/UI/UiPanel.cs b/src/AcDream.App/UI/UiPanel.cs
index de31ecda..b16a5466 100644
--- a/src/AcDream.App/UI/UiPanel.cs
+++ b/src/AcDream.App/UI/UiPanel.cs
@@ -175,6 +175,9 @@ public class UiClickablePanel : UiPanel
{
if (UseSelectionBars && BackgroundSprite != 0 && SpriteResolve is { } sr)
{
+ if (BackgroundColor.W > 0f)
+ ctx.DrawRect(0, 0, Width, Height, BackgroundColor);
+
// Draw the selection highlight as a thin bar at the TOP and BOTTOM of the row.
// The sprite (0x06001397) is 300×32 px — we draw it as horizontal strips at
// native height (SelectionBarHeight), stretched to full panel width (UV tile
diff --git a/tests/AcDream.App.Tests/UI/Layout/CharacterStatControllerTests.cs b/tests/AcDream.App.Tests/UI/Layout/CharacterStatControllerTests.cs
index a9dac56d..f58c7c49 100644
--- a/tests/AcDream.App.Tests/UI/Layout/CharacterStatControllerTests.cs
+++ b/tests/AcDream.App.Tests/UI/Layout/CharacterStatControllerTests.cs
@@ -1,6 +1,7 @@
using AcDream.App.Studio;
using AcDream.App.UI;
using AcDream.App.UI.Layout;
+using System.Numerics;
namespace AcDream.App.Tests.UI.Layout;
@@ -634,20 +635,32 @@ public class CharacterStatControllerTests
ClickTab(layout, left: 92f);
- var headers = list.Children
+ var headerPanels = list.Children
.Where(c => c is UiPanel and not UiClickablePanel)
+ .Cast()
+ .ToList();
+ var headers = headerPanels
.Select(c => c.Children.OfType().First().LinesProvider()[0].Text)
.ToList();
Assert.Equal(new[]
{
"Specialized Skills",
"Trained Skills",
- "Useable Untrained Skills",
- "Unuseable Untrained Skills",
+ "Untrained Skills",
+ "Unusable Skills",
}, headers);
+ Assert.Equal(new[] { 0x06000F90u, 0x06000F86u, 0x06000F98u, 0x06000F89u },
+ headerPanels.Select(h => h.BackgroundSprite).ToArray());
+ Assert.All(headerPanels, h =>
+ Assert.Equal(Vector4.One, h.Children.OfType().First().LinesProvider()[0].Color));
var rows = list.Children.OfType().ToList();
Assert.Equal(12, rows.Count);
+ Assert.All(rows, row =>
+ {
+ Assert.Equal(new Vector4(0f, 0f, 0f, 1f), row.BackgroundColor);
+ Assert.Equal(0u, row.BackgroundSprite);
+ });
var rowNames = rows
.Select(r => r.Children.OfType().ToList()[1].LinesProvider()[0].Text)
.ToList();
@@ -658,6 +671,14 @@ public class CharacterStatControllerTests
"Healing", "Jump", "Loyalty", "Run",
"Alchemy", "Cooking", "Fletching",
}, rowNames);
+
+ var meleeTexts = rows[0].Children.OfType().ToList();
+ Assert.Equal(Vector4.One, meleeTexts[1].LinesProvider()[0].Color);
+ Assert.Equal(new Vector4(0.55f, 1f, 0.55f, 1f), meleeTexts[2].LinesProvider()[0].Color);
+
+ var healingTexts = rows[5].Children.OfType().ToList();
+ Assert.Equal(Vector4.One, healingTexts[1].LinesProvider()[0].Color);
+ Assert.Equal(Vector4.One, healingTexts[2].LinesProvider()[0].Color);
}
[Fact]