{"version":3,"sources":["suit-colors.mjs"],"sourcesContent":["// WHAT A SUIT MAY WEAR, decided on the server (BIZ-05).\n//\n// Suit colours used to be whatever the client wrote into its own user document, and the zone server relayed whatever a\n// client said its skin was, so nothing could ever be sold as exclusive. Now setSuitColors (functions) is the only\n// writer, the zone server checks every skin it relays, and both ask this module. It is byte-identical in public/,\n// functions/lib/ and zone-server/lib/ (generate-version-hash.js holds them equal).\n//\n// A LAYER (base, secondary, accent) MAY BE:\n//   one of the 32 free colours: the palette the picker shows (definitions/colors.json; a test holds them equal),\n//   a premium colour the player owns (entitlements/{uid}.suitColors lists ids from PREMIUM_SUIT_COLORS),\n//   the player's own default: the colour every un-customised suit wears, derived from the uid,\n//   or what that layer already is: suits saved before this rule keep what they have.\n\nexport const FREE_SUIT_COLORS = Object.freeze([\n  0xFFFFFF, 0xE5E5E5, 0xB0C4DE, 0x808080, 0x666666, 0x4B4B4B, 0x1A1A1A, 0xFF0000,\n  0xFF4500, 0xD2B48C, 0xA0522D, 0xFFD700, 0xFFFF00, 0xCD853F, 0xFFDEAD, 0xFF69B4,\n  0xFFB6C1, 0xFF00FF, 0xEE82EE, 0x9370DB, 0x800080, 0x4B3621, 0xE6E6FA, 0x32CD32,\n  0x228B22, 0x008080, 0x00FFFF, 0x1E90FF, 0x4169E1, 0x00008B, 0xAFEEEE, 0x708090,\n]);\n\n// Colours that are sold or given, by id. None yet: the Founder's Pack (BIZ-03) adds the first. An id, once shipped,\n// must never be reused for a different colour, because entitlements store ids.\nexport const PREMIUM_SUIT_COLORS = Object.freeze({});\n\nexport const LAYERS = Object.freeze(['base', 'secondary', 'accent']);\nexport const DEFAULT_ACCENT = 0xffd23b;\n\n// THE UID DEFAULT, without three.js. The client drew it as new THREE.Color().setHSL(hue, 0.55, 0.56) and a copy\n// multiplied by 0.42, read back with getHex(). three.js treats setHSL's result as linear (its working colour space)\n// and getHex converts to sRGB, so this does the same: HSL to linear RGB, scale, then the sRGB transfer and rounding.\n// tests/suit-colors.test.mjs checks it against three.js itself for thousands of uids.\nfunction hue2rgb(p, q, t) {\n  if (t < 0) t += 1;\n  if (t > 1) t -= 1;\n  if (t < 1 / 6) return p + (q - p) * 6 * t;\n  if (t < 1 / 2) return q;\n  if (t < 2 / 3) return p + (q - p) * 6 * (2 / 3 - t);\n  return p;\n}\nfunction hslLinear(h, s, l) {\n  const p = l <= 0.5 ? l * (1 + s) : l + s - (l * s);\n  const q = (2 * l) - p;\n  return [hue2rgb(q, p, h + 1 / 3), hue2rgb(q, p, h), hue2rgb(q, p, h - 1 / 3)];\n}\nconst toSrgb = (c) => (c < 0.0031308 ? c * 12.92 : 1.055 * Math.pow(c, 0.41666) - 0.055);\nconst hexOf = (rgb) => rgb.map((c) => Math.round(Math.min(255, Math.max(0, toSrgb(c) * 255))))\n  .reduce((n, v) => n * 256 + v, 0);\nexport function defaultSuitColors(uid) {\n  let h = 0;\n  const s = String(uid || '');\n  for (let i = 0; i < s.length; i++) h = (h * 31 + s.charCodeAt(i)) >>> 0;\n  const rgb = hslLinear((h % 360) / 360, 0.55, 0.56);\n  return { base: hexOf(rgb), secondary: hexOf(rgb.map((c) => c * 0.42)), accent: DEFAULT_ACCENT };\n}\n\nconst isColor = (n) => Number.isInteger(n) && n >= 0 && n <= 0xffffff;\n\n// The colours this player may wear on each layer, given what they own and what they wear now.\nexport function allowedFor({ uid, owned = [], current = null } = {}) {\n  const def = defaultSuitColors(uid);\n  const premium = (Array.isArray(owned) ? owned : []).map((id) => PREMIUM_SUIT_COLORS[id] && PREMIUM_SUIT_COLORS[id].hex)\n    .filter(isColor);\n  const base = new Set([...FREE_SUIT_COLORS, ...premium]);\n  return Object.fromEntries(LAYERS.map((layer) => {\n    const set = new Set(base);\n    set.add(def[layer]);\n    if (current && isColor(current[layer])) set.add(current[layer]);\n    return [layer, set];\n  }));\n}\n\n// Null, or why these colours can't be worn.\nexport function suitColorsProblem(c, ctx = {}) {\n  if (!c || typeof c !== 'object') return 'Suit colours are missing.';\n  for (const layer of LAYERS) if (!isColor(c[layer])) return 'Suit colours must be three colours.';\n  if (Object.keys(c).some((k) => !LAYERS.includes(k))) return 'Suit colours must be three colours.';\n  const allowed = allowedFor(ctx);\n  for (const layer of LAYERS) if (!allowed[layer].has(c[layer])) return 'That colour isn’t available to you.';\n  return null;\n}\n\n// The compact form peers see: \"base-secondary-accent\" in hex, no #.\nexport function skinString(c) {\n  return LAYERS.map((layer) => ((c[layer] >>> 0) & 0xffffff).toString(16).padStart(6, '0')).join('-');\n}\nexport function parseSkin(str) {\n  if (typeof str !== 'string') return null;\n  const p = str.split('-');\n  if (p.length !== 3 || !p.every((x) => /^[0-9a-fA-F]{6}$/.test(x))) return null;\n  const v = p.map((x) => parseInt(x, 16));\n  return { base: v[0], secondary: v[1], accent: v[2] };\n}\n"],"mappings":"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","names":[],"file":"suit-colors.mjs"}