{"version":3,"sources":["names.mjs"],"sourcesContent":["// WHAT A NAME IS, AND WHEN TWO OF THEM ARE THE SAME NAME.\n//\n// IGNs were a direct client write with one rule — 3 to 30 characters — and the uniqueness doc was keyed on the RAW\n// string. So `Mike`, `mike` and `Mikе` (that last one carrying a Cyrillic е) were three different reservations, and\n// impersonating another player needed no exploit at all, just a lookalike character. Names appear in presence, the\n// roster, world ownership and share cards, so it is the most visible surface in the game and it had the least\n// defence of any of them.\n//\n// TWO SEPARATE JOBS, deliberately kept apart:\n//   validName  — is this an acceptable thing to be called? Charset, length, shape, and the reserved words.\n//   foldName   — which OTHER names would this collide with? Case, confusables, and decoration removed.\n// The display name keeps whatever the player typed; the FOLD is what the reservation is keyed on. That is what\n// makes \"already taken\" mean \"taken by something a person would read as the same name\".\nexport const NAME_MIN = 3, NAME_MAX = 20;\n\n// Everything a person would read as the same letter. Not exhaustive — Unicode confusables never are — but it\n// covers the Cyrillic and Greek lookalikes that carry an entire Latin alphabet between them, which is what makes\n// wholesale impersonation cheap, plus the digit/letter pairs any password field has had to handle for decades.\nconst CONFUSABLE = {\n  'а': 'a', 'е': 'e', 'о': 'o', 'р': 'p', 'с': 'c', 'у': 'y', 'х': 'x', 'і': 'i', 'ј': 'j', 'ѕ': 's',\n  'к': 'k', 'м': 'm', 'н': 'h', 'т': 't', 'в': 'b', 'г': 'r', 'ԁ': 'd', 'ӏ': 'l',\n  'ο': 'o', 'α': 'a', 'ν': 'v', 'ρ': 'p', 'τ': 't', 'υ': 'u', 'χ': 'x', 'ι': 'i', 'κ': 'k', 'μ': 'm',\n  '0': 'o', '3': 'e', '4': 'a', '5': 's', '6': 'g', '7': 't', '8': 'b', '9': 'g', '2': 'z',\n  '@': 'a', '$': 's',\n  // ONE CLASS, NOT THREE. In most faces 1, l, I and i are the same handful of pixels, so folding them to different\n  // letters lets Mike and M1ke and Mlke all be taken at once — which is the impersonation this exists to stop.\n  '1': 'i', 'l': 'i', '|': 'i', '!': 'i',\n};\n\n// NAMES NOBODY MAY TAKE, because taking one is a claim about who you are rather than a choice of what to be\n// called. Folded before comparison, so `M0derator` and `Мoderator` are caught by the same list.\n// FOLDED LIKE THE NAMES THEY ARE COMPARED WITH. The fold maps l to i, so an unfolded 'official' could never equal the\n// fold of anything: for months Official, Console, Help, Player and Null were not reserved at all.\nconst RESERVED = new Set([\n  'admin', 'administrator', 'mod', 'moderator', 'staff', 'support', 'official', 'system', 'server', 'console',\n  'kubicor', 'kubi', 'gm', 'gamemaster', 'dev', 'developer', 'owner', 'root', 'null', 'undefined', 'anonymous',\n  'guest', 'player', 'you', 'me', 'everyone', 'here', 'team', 'help', 'bot',\n].map((w) => foldName(w)));\n\n// LETTERS, DIGITS, AND THE TWO SEPARATORS A NAME ACTUALLY NEEDS. No punctuation, no emoji, and nothing from the\n// control or formatting planes — a right-to-left override renders a name backwards, and a zero-width joiner makes\n// two different names paint identically, both of which are impersonation by typography rather than by spelling.\nconst ALLOWED = /^[A-Za-z0-9][A-Za-z0-9 _-]*[A-Za-z0-9]$/;\n\n// WORDS THAT CLAIM AUTHORITY, anywhere in a name. RESERVED stops someone being called exactly \"Admin\"; this stops\n// \"KubicorAdmin\", \"Official_Mike\" and \"M0d Team\", which read as staff just as well. Each word of the name is checked on\n// its own (see nameWords), so \"Badminton\" and \"Modest\" are fine and \"AdminMike\" is not. World names get a shorter list:\n// \"Solar System\" and \"Kubicor Prime\" are fair names for a world, and neither is a person claiming to be staff.\nconst AUTHORITY_PLAYER = new Set(['admin', 'administrator', 'moderator', 'mod', 'mods', 'staff', 'official', 'support',\n  'gamemaster', 'developer', 'dev', 'devs', 'kubicor', 'system', 'team'].map((w) => foldName(w)));\nconst AUTHORITY_WORLD = new Set(['admin', 'administrator', 'moderator', 'mod', 'mods', 'staff', 'official',\n  'gamemaster'].map((w) => foldName(w)));\n\nexport function foldName(name) {\n  let s = String(name || '').normalize('NFKD').replace(/[̀-ͯ]/g, '').toLowerCase();\n  let out = '';\n  for (const ch of s) out += (CONFUSABLE[ch] || ch);\n  return out.replace(/[^a-z]/g, '');       // separators and digits carry no identity once folded\n}\n\n// THE WORDS OF A NAME, each folded: split at spaces, underscores and hyphens, and where a lower-case letter meets a\n// capital (\"AdminMike\" is two words). No regex lookbehind: this file runs in the browser too, and Safari before 16.4\n// (the iPad Air 2 the game still supports) cannot even parse one.\nexport function nameWords(name) {\n  return String(name || '').replace(/([a-z])([A-Z])/g, '$1 $2').split(/[\\s_-]+/).map(foldName).filter(Boolean);\n}\n\n// Null, or why the name reads as someone who works on the game.\nexport function impersonationProblem(name, { world = false } = {}) {\n  const words = world ? AUTHORITY_WORLD : AUTHORITY_PLAYER;\n  for (const w of nameWords(name)) if (words.has(w)) return 'Names can\\u2019t suggest you work on Kubicor.';\n  return null;\n}\n\n// Returns null when the name is fine, or the reason it is not — phrased for the player, since this is what they\n// will be shown while they are typing.\nexport function nameProblem(name) {\n  const raw = String(name == null ? '' : name);\n  if (raw !== raw.trim()) return 'No spaces at the start or end.';\n  if (raw.length < NAME_MIN) return `At least ${NAME_MIN} characters.`;\n  if (raw.length > NAME_MAX) return `At most ${NAME_MAX} characters.`;\n  if (/\\s\\s/.test(raw)) return 'Only single spaces.';\n  if (!ALLOWED.test(raw)) return 'Letters, digits, spaces, hyphens and underscores only.';\n  const f = foldName(raw);\n  // A name that folds away to nothing is all decoration — \"___\", \"1234\" — and would collide with every other one\n  // like it while reading as unique.\n  if (f.length < NAME_MIN) return 'Needs at least three letters.';\n  if (RESERVED.has(f)) return 'That name is reserved.';\n  return impersonationProblem(raw);\n}\n\n// ============ WORLD NAMES ============\n// A world's name is printed on its card, its nameplate and every share picture, so it follows its own rule rather\n// than a player name's: up to 24 characters, one word or several, and IN ANY ALPHABET (QA F-96: \"Café Nova\" was\n// refused as not being letters, because the rule was A-Z). Shared by the rename box, which shows the problem while the\n// owner types, and setWorldName, which decides; this file is twinned into the browser so the two cannot disagree.\n//\n// WHAT IS ALLOWED, AND WHY EACH LIMIT IS THERE:\n//   * letters and digits of any script (\\p{L}, \\p{N}), each carrying at most three combining marks: enough for\n//     Vietnamese tones and Devanagari vowel signs, not enough for the stacked \"Zalgo\" text that paints over the line\n//     above it;\n//   * single spaces between words, and an apostrophe or a hyphen BETWEEN two letters (\"Mike's World\", \"Al-Fir\"), never\n//     leading, trailing or doubled: punctuation that can stand alone becomes decoration;\n//   * ONE ALPHABET PER NAME. A name that mixes Latin and Cyrillic is how \"Glоwer\" (with a Cyrillic о) passes for\n//     \"Glower\"; Japanese and Korean mix their own scripts freely, so those count as one.\n// No emoji and nothing from the control or formatting planes, for the reason ALLOWED above gives.\nexport const WORLD_NAME_MAX = 24;\nexport const WORLD_NAME_RULE = 'Up to 24 letters, digits and spaces, in one alphabet. ’ and - may join two letters.';\nconst WORLD_NAME_RE = /^[\\p{L}\\p{N}]\\p{M}{0,3}(?:(?: |['-])?[\\p{L}\\p{N}]\\p{M}{0,3})*$/u;\nconst WORLD_SCRIPTS = [['Latin'], ['Greek'], ['Cyrillic'], ['Armenian'], ['Georgian'], ['Hebrew'], ['Arabic'],\n  ['Devanagari'], ['Bengali'], ['Gurmukhi'], ['Gujarati'], ['Tamil'], ['Telugu'], ['Kannada'], ['Malayalam'],\n  ['Sinhala'], ['Thai'], ['Lao'], ['Khmer'], ['Myanmar'], ['Tibetan'], ['Ethiopic'], ['Mongolian'],\n  ['Han', 'Hiragana', 'Katakana', 'Hangul', 'Bopomofo']]\n  .map((g) => new RegExp('^[' + g.map((s) => '\\\\p{Script=' + s + '}').join('') + ']$', 'u'));\n// The name as it is stored and shown: composed (so \"é\" typed two ways is one letter), curly apostrophes made straight,\n// trimmed, single-spaced. The server stores exactly this, so the box can draw it before the answer comes back.\nexport function worldNameDisplay(n) {\n  return String(n == null ? '' : n).normalize('NFC').replace(/[‘’ʼ]/g, '\\'').trim().replace(/\\s+/g, ' ');\n}\n// Null when the name may be used (an empty one clears the name), else why not, phrased for the box it is typed in.\nexport function worldNameProblem(n) {\n  const s = worldNameDisplay(n);\n  if (!s) return null;\n  if ([...s].length > WORLD_NAME_MAX) return `At most ${WORLD_NAME_MAX} characters.`;\n  if (!WORLD_NAME_RE.test(s)) {\n    const strayMark = /^['-]|['-]$|['-]['-]|['-] | ['-]/.test(s) && /^[\\p{L}\\p{M}\\p{N} '-]+$/u.test(s);\n    return strayMark ? '’ and - only between two letters.' : 'Letters, digits and spaces only: no emoji or symbols.';\n  }\n  let group = -1;\n  for (const ch of s) {\n    // A letter shared between scripts (the Japanese long-vowel mark ー, modifier letters) belongs to none of them.\n    if (!/\\p{L}/u.test(ch) || /[\\p{Script=Common}\\p{Script=Inherited}]/u.test(ch)) continue;\n    const g = WORLD_SCRIPTS.findIndex((re) => re.test(ch));\n    if (g < 0) return 'Letters, digits and spaces only: no emoji or symbols.';\n    if (group >= 0 && g !== group) return 'Use letters from one alphabet.';\n    group = g;\n  }\n  return null;\n}\n// THE RESERVATION KEY (worldNames/{key}): which other names this one would be taken for. An A-Z name keys exactly as\n// it always has (lower case, spaces to '_'), so every existing reservation still holds. Anything else is keyed without\n// its accents and with the Cyrillic and Greek lookalikes read as the Latin letters they pass for, so \"Café Nova\" and\n// \"Cafe Nova\" are one name and so are \"Glower\" and \"Glоwer\". Names cannot contain '_', so the key stays unambiguous.\nconst LOOKALIKE = Object.fromEntries(Object.entries(CONFUSABLE).filter(([k]) => k.charCodeAt(0) > 127));\nexport function worldNameKey(n) {\n  const s = worldNameDisplay(n).toLowerCase();\n  if (/^[a-z0-9 ]*$/.test(s)) return s.replace(/ /g, '_');\n  let out = '';\n  for (const ch of s.normalize('NFKD').replace(/\\p{M}/gu, '')) out += (LOOKALIKE[ch] || ch);\n  return out.normalize('NFC').replace(/ /g, '_');\n}\n\n// THE NAME A CLAIM WAS ASKED WITH (claimWorld, u6), as setWorldName would store it: { display, key, why }. No name, or a\n// blank one, is no name at all (the world can be named later); one that breaks the rules carries why, and no key.\nexport function worldNameAsked(raw) {\n  if (raw == null || typeof raw !== 'string' || !raw.trim()) return { display: null, key: null, why: null };\n  const display = worldNameDisplay(raw.slice(0, WORLD_NAME_MAX * 4));\n  const why = worldNameProblem(display);\n  return why ? { display, key: null, why } : { display, key: worldNameKey(display), why: null };\n}\n\n// A NAME OFFERED AT CLAIM TIME (the owner's play review, Oct 23, u6: \"Ask for a name when claiming, with a generated\n// suggestion\"). Seven of the owner's fourteen claims were \"Unnamed Planet/Moon\", because a name was asked for only later,\n// in a menu. The claim dialog now opens with a name in its box, which the player keeps, edits or rolls again. Made from\n// two short lists of plain words, so every suggestion passes worldNameProblem and the name filter without asking them;\n// the world's character picks the lists (`hint`: 'cold', 'hot', 'wet', 'green' or nothing), and the address and the roll\n// pick the words, so the same world offers the same first name on every device and a roll offers the next one. Names are\n// unique galaxy-wide, so a taken one is refused by claimWorld (the claim still goes ahead) and the player picks another.\nconst NAME_ADJ = {\n  cold: ['Frost', 'Rime', 'Pale', 'Silver', 'Hoar', 'Winter', 'Glacier', 'Snow', 'Icebound', 'Still'],\n  hot: ['Ember', 'Cinder', 'Ash', 'Copper', 'Scorch', 'Amber', 'Sun', 'Kiln', 'Red', 'Dust'],\n  wet: ['Tide', 'Mist', 'Rain', 'Brine', 'Reed', 'Deep', 'Shore', 'Fen', 'Blue', 'Rill'],\n  green: ['Moss', 'Fern', 'Willow', 'Bloom', 'Clover', 'Verdant', 'Thistle', 'Leaf', 'Bramble', 'Meadow'],\n  any: ['Quiet', 'Hollow', 'Far', 'Lantern', 'Stone', 'Golden', 'Dusk', 'Bright', 'Lone', 'High', 'Wander', 'North'],\n};\nconst NAME_NOUN = ['Haven', 'Reach', 'Rest', 'Vale', 'Crest', 'Harbor', 'Fold', 'Spire', 'Glade', 'Ridge',\n  'Landing', 'Hearth', 'Step', 'Watch', 'Rise', 'Garden', 'Point', 'Field', 'Keep', 'Cove', 'Moor', 'Croft', 'Drift'];\nfunction nameHash(s) { let h = 2166136261; for (const ch of String(s)) { h ^= ch.charCodeAt(0); h = Math.imul(h, 16777619) >>> 0; } return h >>> 0; }\nexport function suggestWorldName(addr, roll = 0, hint = '') {\n  const adj = (NAME_ADJ[hint] || []).concat(NAME_ADJ.any);\n  const h = nameHash(String(addr || '') + '|' + (roll | 0));\n  const name = adj[h % adj.length] + ' ' + NAME_NOUN[(h >>> 8) % NAME_NOUN.length];\n  return worldNameProblem(name) ? 'New Haven' : name;\n}\n"],"mappings":"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","names":[],"file":"names.mjs"}