fix: honour caller skin, hair and eye color in marketplace mode - #76
Conversation
Marketplace previews ignored the skinColor / hairColor / eyeColor URL params and the matching SetSkinColor / SetHairColor / SetEyeColor JSBridge calls, so a consumer could not try color combinations on an avatar the way builder mode allows. LoadForMarketplace took its colors straight from the fetched profile (avatar.GetAvatarColors()); AangConfiguration.SkinColor / HairColor / EyeColor were only ever read in the Builder branch. It now merges them: a color the caller asked for wins, and every channel left unset keeps the profile's own. Both views follow, since the avatar and the item-alone wearable already receive the same AvatarColors. The method now takes the AangConfiguration snapshot Reload() holds (matching LoadUrns) instead of individual values, so it never reads the singleton that RecreateFrom can swap mid-load. LoadForBuilder's skin fallback moves from black to #cc9b76, the value the wearable-preview wrapper used to send on every builder request. The wrapper is about to stop sending colors nobody asked for (so that profile-less marketplace previews keep their default profile's colors), which would otherwise leave builder callers with black skin. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
decentraland-bot
left a comment
There was a problem hiding this comment.
Review — PR #76: fix: honour caller skin, hair and eye color in marketplace mode
Verdict: ✅ Approve — clean, well-scoped fix with no correctness or security issues.
What this PR does
LoadForMarketplace now merges caller-supplied colors (from URL params / JSBridge) with the fetched profile's colors via null-coalescing, exactly the way LoadForBuilder already worked. The method signature changes from individual values to the AangConfiguration snapshot Reload() already holds, which is both cleaner and consistent with LoadUrns(config).
The builder skin fallback moves from Color.black to #cc9b76, encoding the default the JS wrapper was already sending.
Checks performed
| Area | Result |
|---|---|
| Correctness | ✅ AvatarColors(eyes, hair, skin) constructor order matches all call sites. Null-coalescing logic is correct — caller color wins, profile color fills the gap. |
| Race safety | ✅ RecreateFrom creates a new AangConfiguration instance, so the config reference captured at the top of Reload()'s do-while loop stays stable across every await. |
| Input validation | ✅ Color values go through ColorUtility.TryParseHtmlString in the setter methods; invalid input falls back to null, which correctly defers to the profile's own color. |
| Security | ✅ No injection vectors — colors are parsed safely, no secrets, no auth changes. |
| API surface | ✅ The change is internal (LoadForMarketplace is private). The public-facing URL params and JSBridge calls are unchanged — they now just take effect in marketplace mode as they already did in builder mode. No downstream consumer impact. |
| Git conventions | ✅ Title follows fix: <summary>, branch follows fix/<summary>. |
Minor observations (P2 — non-blocking)
[P2] DEFAULT_SKIN_COLOR precision:
new Color(0.8f, 0.607f, 0.462f) is close to #cc9b76 but has tiny floating-point drift (0.607 vs 155/255 ≈ 0.60784). Writing new Color(204f/255f, 155f/255f, 118f/255f) would be exact and self-documenting (the compiler evaluates the division at compile time). Purely cosmetic — the visual difference is sub-pixel.
CI status
- Prebuild: ✅ pass
- Build: ⏳ queued
- Vercel: ✅ pass
Reviewed by Jarvis 🤖 · Requested by RocioCM via GitHub
| @@ -37,6 +37,9 @@ public class PreviewController : MonoBehaviour | |||
| // survives across sessions: only ever a fallback, never an override of what was requested. | |||
| private const string PREF_AVATAR_SHOWN = "PreviewAvatarShown"; | |||
|
|
|||
There was a problem hiding this comment.
[P2] Nit: the float literals 0.607 and 0.462 have minor rounding drift from #cc9b76 (155/255 ≈ 0.60784, 118/255 ≈ 0.46275). Using compile-time division would be exact:
| private static readonly Color DEFAULT_SKIN_COLOR = new(204f / 255f, 155f / 255f, 118f / 255f); |
Visually imperceptible — just a readability/precision nit.
Vercel Preview is ready!
|
Replace approximate float literals (0.8f, 0.607f, 0.462f) with exact compile-time division (204f/255f, 155f/255f, 118f/255f) to match #cc9b76 precisely. Addresses P2 review finding. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Problem
With
mode=marketplace, theskinColor/hairColor/eyeColorURL params and the matchingSetSkinColor/SetHairColor/SetEyeColorJSBridge calls were silently dropped. A consumercould not let a user try color combinations on an avatar the way
mode=builderallows.Repro (through the wearable-preview wrapper):
…/?unity=true&mode=marketplace&profile=default1&skin=%23ff0000rendersdefault1's own skin, not red.Cause
PreviewController.LoadForMarketplacetook its colors straight from the fetched profile(
avatar.GetAvatarColors()).AangConfiguration.SkinColor / HairColor / EyeColor— populated fromboth the URL and the bridge — were only ever read in the
PreviewMode.Builderbranch.Fix
LoadForMarketplacemerges the two: a color the caller asked for wins, and every channel left unsetkeeps the profile's own. Both views follow for free, since the avatar (
avatarLoader.LoadAvatar) andthe item-alone view (
wearableLoader.LoadWearable) already receive the sameAvatarColors. Thatalso makes it work regardless of the
profileparam — a real address, adefaultN, or none at all.The method now takes the
AangConfigurationsnapshotReload()already holds (matchingLoadUrns)instead of individual values, so it never reaches for the singleton that
RecreateFromcan swapmid-load.
LoadForBuilder's skin fallback moves fromColor.blackto#cc9b76— the value thewearable-preview wrapper has always sent on every builder request. The companion wrapper PR
(decentraland/wearable-preview) stops sending colors nobody asked for, which is what keeps
profile-less marketplace previews on their default profile's colors; without this constant it would
leave builder callers with black skin.
Tests
No test project in this repo, so this is untested here beyond compiling. The wrapper side of the
change is verified in the companion PR (color params now travel only when the caller asks). Worth a
manual pass on: marketplace with an explicit
skin, marketplace with only aprofile, marketplacewith neither, and builder with no color params.
🤖 Generated with Claude Code