fix: spread random usernames so adjacent YouTube comments stop sharing suffixes
Browse filesThe earlier DJB2 hash produced near-identical bucket positions for seeds that only differed in the trailing index, which is why ten consecutive fetched comments often ended up with the same noun and the same _official or _skater suffix. Replaces the hash with FNV-1a plus three independent mix steps and almost doubles both word lists so the rail now shows visibly distinct authors.
frontend/src/utils/toxicity.ts
CHANGED
|
@@ -57,6 +57,10 @@ const ADJECTIVES = [
|
|
| 57 |
"brave", "fuzzy", "shiny", "sleepy", "quick", "noble", "loud", "humble",
|
| 58 |
"stormy", "sunny", "rainy", "frosty", "spicy", "salty", "sweet", "crazy",
|
| 59 |
"mighty", "silent", "epic", "cosmic", "rusty", "neon", "wandering", "lone",
|
|
|
|
|
|
|
|
|
|
|
|
|
| 60 |
];
|
| 61 |
|
| 62 |
const NOUNS = [
|
|
@@ -64,23 +68,47 @@ const NOUNS = [
|
|
| 64 |
"lynx", "shark", "raven", "viper", "phoenix", "dragon", "jaguar", "koala",
|
| 65 |
"moose", "lion", "owl", "octopus", "rabbit", "hawk", "badger", "robin",
|
| 66 |
"cosmonaut", "drifter", "ninja", "wizard", "rider", "pilot", "skater", "gamer",
|
|
|
|
|
|
|
|
|
|
|
|
|
| 67 |
];
|
| 68 |
|
| 69 |
-
const SUFFIXES = [
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 70 |
|
| 71 |
/**
|
| 72 |
* Generate a YouTube-style random username, deterministic per seed.
|
|
|
|
|
|
|
|
|
|
| 73 |
*/
|
| 74 |
export function randomUsername(seed: string | number | undefined | null): string {
|
| 75 |
const str = seed == null ? "anon" : String(seed);
|
| 76 |
-
|
| 77 |
-
|
| 78 |
-
|
| 79 |
-
|
| 80 |
-
const adj = ADJECTIVES[hash % ADJECTIVES.length] ?? "anon";
|
| 81 |
-
const noun = NOUNS[Math.floor(hash / 32) % NOUNS.length] ?? "user";
|
| 82 |
-
const suffix = SUFFIXES[Math.floor(hash / 1024) % SUFFIXES.length] ?? "";
|
| 83 |
const needsNum = suffix === "" || suffix.endsWith("_");
|
| 84 |
-
const num = needsNum ? String(
|
| 85 |
return `${adj}_${noun}${suffix}${num}`;
|
| 86 |
}
|
|
|
|
| 57 |
"brave", "fuzzy", "shiny", "sleepy", "quick", "noble", "loud", "humble",
|
| 58 |
"stormy", "sunny", "rainy", "frosty", "spicy", "salty", "sweet", "crazy",
|
| 59 |
"mighty", "silent", "epic", "cosmic", "rusty", "neon", "wandering", "lone",
|
| 60 |
+
"electric", "atomic", "midnight", "golden", "silver", "crimson", "violet", "azure",
|
| 61 |
+
"ancient", "feral", "tidal", "lunar", "solar", "ember", "frost", "iron",
|
| 62 |
+
"velvet", "wicked", "groovy", "funky", "chill", "vibrant", "moody", "savage",
|
| 63 |
+
"tropical", "alpine", "urban", "rogue", "stellar", "nomadic", "boreal", "obsidian",
|
| 64 |
];
|
| 65 |
|
| 66 |
const NOUNS = [
|
|
|
|
| 68 |
"lynx", "shark", "raven", "viper", "phoenix", "dragon", "jaguar", "koala",
|
| 69 |
"moose", "lion", "owl", "octopus", "rabbit", "hawk", "badger", "robin",
|
| 70 |
"cosmonaut", "drifter", "ninja", "wizard", "rider", "pilot", "skater", "gamer",
|
| 71 |
+
"barista", "hacker", "painter", "poet", "sailor", "ranger", "archer", "dancer",
|
| 72 |
+
"chef", "cyclist", "writer", "drummer", "diver", "climber", "biker", "florist",
|
| 73 |
+
"comet", "nebula", "nova", "pulsar", "quasar", "asteroid", "meteor", "orbiter",
|
| 74 |
+
"knight", "monk", "scribe", "merchant", "wanderer", "voyager", "explorer", "pirate",
|
| 75 |
];
|
| 76 |
|
| 77 |
+
const SUFFIXES = [
|
| 78 |
+
"", "", "", "", "",
|
| 79 |
+
"_", "_x", "_yt", "_hd", "_real", "_official", "_tv", "_live", "_xd", "_v2",
|
| 80 |
+
"99", "01", "07", "23", "42",
|
| 81 |
+
"_irl", "_pro",
|
| 82 |
+
];
|
| 83 |
+
|
| 84 |
+
function hashString(str: string): number {
|
| 85 |
+
let h = 0x811c9dc5;
|
| 86 |
+
for (let i = 0; i < str.length; i++) {
|
| 87 |
+
h ^= str.charCodeAt(i);
|
| 88 |
+
h = Math.imul(h, 0x01000193) >>> 0;
|
| 89 |
+
}
|
| 90 |
+
return h >>> 0;
|
| 91 |
+
}
|
| 92 |
+
|
| 93 |
+
function mix(h: number, salt: number): number {
|
| 94 |
+
let x = (h ^ Math.imul(salt + 1, 0x85ebca6b)) >>> 0;
|
| 95 |
+
x = Math.imul(x ^ (x >>> 13), 0xc2b2ae35) >>> 0;
|
| 96 |
+
return (x ^ (x >>> 16)) >>> 0;
|
| 97 |
+
}
|
| 98 |
|
| 99 |
/**
|
| 100 |
* Generate a YouTube-style random username, deterministic per seed.
|
| 101 |
+
* Three independent hash mixes pick adjective, noun, and suffix so that
|
| 102 |
+
* seeds that share a prefix (e.g. `yt-VIDEOID-0`, `yt-VIDEOID-1`) still
|
| 103 |
+
* produce visibly different usernames.
|
| 104 |
*/
|
| 105 |
export function randomUsername(seed: string | number | undefined | null): string {
|
| 106 |
const str = seed == null ? "anon" : String(seed);
|
| 107 |
+
const base = hashString(str);
|
| 108 |
+
const adj = ADJECTIVES[mix(base, 1) % ADJECTIVES.length] ?? "anon";
|
| 109 |
+
const noun = NOUNS[mix(base, 2) % NOUNS.length] ?? "user";
|
| 110 |
+
const suffix = SUFFIXES[mix(base, 3) % SUFFIXES.length] ?? "";
|
|
|
|
|
|
|
|
|
|
| 111 |
const needsNum = suffix === "" || suffix.endsWith("_");
|
| 112 |
+
const num = needsNum ? String(mix(base, 4) % 1000) : "";
|
| 113 |
return `${adj}_${noun}${suffix}${num}`;
|
| 114 |
}
|