@leftium/gg 0.0.30 → 0.0.31
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/gg.js +47 -21
- package/dist/words.d.ts +25 -0
- package/dist/words.js +105 -0
- package/package.json +1 -1
package/dist/gg.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import debugFactory from './debug.js';
|
|
2
2
|
import ErrorStackParser from 'error-stack-parser';
|
|
3
3
|
import { BROWSER, DEV } from 'esm-env';
|
|
4
|
+
import { toWordTuple } from './words.js';
|
|
4
5
|
const _ggCallSitesPlugin = typeof __GG_TAG_PLUGIN__ !== 'undefined' ? __GG_TAG_PLUGIN__ : false;
|
|
5
6
|
/**
|
|
6
7
|
* Creates a debug instance with custom formatArgs to add namespace padding
|
|
@@ -166,6 +167,8 @@ const srcRootRegex = new RegExp(ggConfig.srcRootPattern, 'i');
|
|
|
166
167
|
// - Cache and reuse the same log function for a given callpoint.
|
|
167
168
|
const namespaceToLogFunction = new Map();
|
|
168
169
|
let maxCallpointLength = 0;
|
|
170
|
+
// Cache: raw stack line → word tuple (avoids re-hashing the same call site)
|
|
171
|
+
const stackLineCache = new Map();
|
|
169
172
|
/**
|
|
170
173
|
* Reset the namespace width tracking.
|
|
171
174
|
* Useful after configuration checks that may have long callpoint paths.
|
|
@@ -189,27 +192,50 @@ export function gg(...args) {
|
|
|
189
192
|
// When ggCallSitesPlugin is installed, all bare gg() calls are rewritten to gg.ns()
|
|
190
193
|
// at build time, so this code path only runs for un-transformed calls.
|
|
191
194
|
// Skip expensive stack parsing if the plugin is handling callpoints.
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
195
|
+
if (!_ggCallSitesPlugin) {
|
|
196
|
+
if (DEV) {
|
|
197
|
+
// Development without plugin: full stack parsing for detailed callpoint info
|
|
198
|
+
// Ignore first stack frame, which is always the call to gg() itself.
|
|
199
|
+
stack = ErrorStackParser.parse(new Error()).splice(1);
|
|
200
|
+
// Example: http://localhost:5173/src/routes/+page.svelte
|
|
201
|
+
const filename = stack[0].fileName?.replace(timestampRegex, '') || '';
|
|
202
|
+
// Example: src/routes/+page.svelte
|
|
203
|
+
const filenameToOpen = filename.replace(srcRootRegex, '$<folderName>/');
|
|
204
|
+
url = openInEditorUrl(filenameToOpen);
|
|
205
|
+
// Example: routes/+page.svelte
|
|
206
|
+
fileName = filename.replace(srcRootRegex, '');
|
|
207
|
+
functionName = stack[0].functionName || '';
|
|
208
|
+
// A callpoint is uniquely identified by the filename plus function name
|
|
209
|
+
const callpoint = `${fileName}${functionName ? `@${functionName}` : ''}`;
|
|
210
|
+
if (callpoint.length < 80 && callpoint.length > maxCallpointLength) {
|
|
211
|
+
maxCallpointLength = callpoint.length;
|
|
212
|
+
}
|
|
213
|
+
// Namespace without padding - keeps colors stable
|
|
214
|
+
// Editor link appended if enabled
|
|
215
|
+
namespace = `gg:${callpoint}${ggConfig.editorLink ? url : ''}`;
|
|
216
|
+
}
|
|
217
|
+
else {
|
|
218
|
+
// Production without plugin: cheap stack hash → deterministic word tuple
|
|
219
|
+
// Avoids expensive ErrorStackParser; just grabs the raw stack line and hashes it.
|
|
220
|
+
// Same call site always produces the same word pair (e.g. "calm-fox").
|
|
221
|
+
const rawStack = new Error().stack || '';
|
|
222
|
+
// Stack line [2]: skip "Error" header [0] and gg() frame [1]
|
|
223
|
+
const callerLine = rawStack.split('\n')[2] || rawStack;
|
|
224
|
+
// Strip line:col numbers so all gg() calls within the same function
|
|
225
|
+
// hash to the same word tuple. In minified builds, multiple gg() calls
|
|
226
|
+
// in one function differ only by column offset — we want them grouped.
|
|
227
|
+
// Chrome: "at handleClick (chunk-abc.js:1:45892)" → "at handleClick (chunk-abc.js)"
|
|
228
|
+
// Firefox: "handleClick@https://...:1:45892" → "handleClick@https://..."
|
|
229
|
+
const callerKey = callerLine.replace(/:\d+:\d+\)?$/, '').trim();
|
|
230
|
+
const callpoint = stackLineCache.get(callerKey) ?? toWordTuple(callerKey);
|
|
231
|
+
if (!stackLineCache.has(callerKey)) {
|
|
232
|
+
stackLineCache.set(callerKey, callpoint);
|
|
233
|
+
}
|
|
234
|
+
if (callpoint.length < 80 && callpoint.length > maxCallpointLength) {
|
|
235
|
+
maxCallpointLength = callpoint.length;
|
|
236
|
+
}
|
|
237
|
+
namespace = `gg:${callpoint}`;
|
|
209
238
|
}
|
|
210
|
-
// Namespace without padding - keeps colors stable
|
|
211
|
-
// Editor link appended if enabled
|
|
212
|
-
namespace = `gg:${callpoint}${ggConfig.editorLink ? url : ''}`;
|
|
213
239
|
}
|
|
214
240
|
const ggLogFunction = namespaceToLogFunction.get(namespace) ||
|
|
215
241
|
namespaceToLogFunction.set(namespace, createGgDebugger(namespace)).get(namespace);
|
|
@@ -527,7 +553,7 @@ export async function runGgDiagnostics() {
|
|
|
527
553
|
message(`${checkbox(ggLogTest.enabled)} DEBUG env variable: ${process?.env?.DEBUG}${hint}`);
|
|
528
554
|
}
|
|
529
555
|
// Optional plugin diagnostics listed last
|
|
530
|
-
message(makeHint(_ggCallSitesPlugin, `✅ (optional) gg-call-sites vite plugin detected! Call-site namespaces baked in at build time.`, `⚠️ (optional) gg-call-sites vite plugin not detected. Add ggCallSitesPlugin() to vite.config.ts for build-time call-site namespaces (
|
|
556
|
+
message(makeHint(_ggCallSitesPlugin, `✅ (optional) gg-call-sites vite plugin detected! Call-site namespaces baked in at build time.`, `⚠️ (optional) gg-call-sites vite plugin not detected. Add ggCallSitesPlugin() to vite.config.ts for build-time call-site namespaces (faster/more reliable). Without plugin, prod uses word-tuple names (e.g. calm-fox) as unique call-site identifiers.`));
|
|
531
557
|
if (BROWSER && DEV) {
|
|
532
558
|
const { status } = await fetch('/__open-in-editor?file=+');
|
|
533
559
|
message(makeHint(status === 222, `✅ (optional) open-in-editor vite plugin detected! (status code: ${status}) Clickable links open source files in editor.`, `⚠️ (optional) open-in-editor vite plugin not detected. (status code: ${status}) Add openInEditorPlugin() to vite.config.ts for clickable links that open source files in editor`));
|
package/dist/words.d.ts
ADDED
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Word lists for generating deterministic human-readable callpoint names.
|
|
3
|
+
* Used as a fallback in production when the gg-call-sites Vite plugin is not installed.
|
|
4
|
+
*
|
|
5
|
+
* Each list has exactly 256 entries (8-bit index) so a 32-bit hash cleanly maps to
|
|
6
|
+
* an adjective-noun pair: adjectives[hash & 0xFF] + "-" + nouns[(hash >> 8) & 0xFF]
|
|
7
|
+
* giving 65,536 unique combinations.
|
|
8
|
+
*
|
|
9
|
+
* Selection criteria:
|
|
10
|
+
* - Short (3-6 chars) for compact console output
|
|
11
|
+
* - Visually distinct (no near-homoglyphs like fast/last)
|
|
12
|
+
* - Inoffensive
|
|
13
|
+
*/
|
|
14
|
+
export declare const adjectives: readonly ["able", "acid", "aged", "airy", "apt", "avid", "awry", "balmy", "bare", "beefy", "bent", "big", "bland", "bleak", "blind", "bliss", "blue", "blunt", "bold", "brash", "brave", "brief", "briny", "brisk", "broad", "buff", "bulky", "bumpy", "burly", "busy", "calm", "cheap", "chewy", "chief", "chilly", "civil", "clean", "clear", "close", "cold", "cool", "coral", "cozy", "crisp", "cubic", "curly", "curvy", "cute", "cyan", "damp", "dark", "dear", "deep", "dense", "dewy", "dim", "dizzy", "dopey", "dorky", "draft", "dry", "dual", "dull", "dusty", "eager", "early", "easy", "edgy", "elfin", "elite", "empty", "equal", "even", "every", "evil", "exact", "extra", "faded", "fair", "fancy", "fast", "few", "fine", "firm", "first", "fishy", "fit", "five", "fixed", "fizzy", "flat", "fleet", "fluid", "foggy", "fond", "four", "free", "fresh", "front", "full", "funky", "funny", "furry", "fussy", "fuzzy", "gaudy", "giant", "glad", "gold", "good", "grand", "gray", "great", "green", "grim", "gross", "grown", "gusty", "hairy", "half", "happy", "hard", "hardy", "harsh", "hasty", "hazy", "hefty", "high", "holy", "honey", "hot", "huge", "humid", "husky", "icy", "ideal", "idle", "inner", "ionic", "iron", "ivory", "jade", "jazzy", "jolly", "juicy", "jumbo", "jumpy", "just", "keen", "kind", "known", "lanky", "large", "last", "late", "lazy", "lean", "legal", "light", "limp", "live", "local", "lofty", "lone", "long", "lost", "loud", "loved", "low", "loyal", "lucky", "lumpy", "lusty", "mad", "magic", "main", "major", "meek", "merry", "messy", "mild", "minty", "misty", "mixed", "moist", "moody", "mossy", "muddy", "murky", "mushy", "muted", "naive", "neat", "nerdy", "new", "next", "nice", "nimby", "noble", "noisy", "north", "novel", "numb", "nutty", "oaken", "odd", "oily", "old", "olive", "only", "open", "other", "outer", "oval", "paid", "pale", "pasty", "perky", "petty", "pink", "plain", "plump", "plush", "polar", "poor", "prime", "proud", "pulpy", "pure", "pushy", "quick", "quiet", "rare", "raw", "ready", "real", "rich", "rigid", "ripe", "rosy", "rough", "round", "royal", "ruby", "rusty", "safe", "salty", "same", "sandy", "sharp", "shiny", "silky", "slim", "slow", "small", "snug"];
|
|
15
|
+
export declare const nouns: readonly ["ant", "ape", "asp", "auk", "bass", "bat", "bear", "bee", "bird", "bison", "boar", "bream", "buck", "bug", "bull", "bunny", "calf", "carp", "cat", "chick", "chimp", "clam", "cobra", "cod", "colt", "conch", "coon", "cow", "crab", "crane", "crow", "cub", "dart", "deer", "dingo", "dodo", "doe", "dog", "dove", "drake", "drum", "duck", "eagle", "eel", "egret", "elk", "emu", "ewe", "fawn", "finch", "fish", "flea", "fly", "foal", "fox", "frog", "gator", "gecko", "goat", "goose", "grub", "gull", "guppy", "hare", "hawk", "hen", "heron", "hog", "hornet", "horse", "hound", "hyena", "ibex", "ibis", "iguana", "imp", "jackal", "jay", "joey", "kite", "kiwi", "koala", "koi", "lamb", "lark", "lemur", "lion", "llama", "lynx", "macaw", "mako", "mare", "mink", "mite", "mole", "moose", "moth", "mouse", "mule", "newt", "okapi", "orca", "oryx", "otter", "owl", "ox", "panda", "parrot", "perch", "pig", "pike", "plover", "pony", "prawn", "pug", "puma", "quail", "ram", "rat", "raven", "ray", "robin", "rook", "roach", "sail", "seal", "shad", "shark", "sheep", "shrew", "shrimp", "skate", "skink", "skua", "skunk", "sloth", "slug", "smelt", "snail", "snake", "snipe", "sole", "squid", "stag", "stork", "swan", "swift", "tapir", "tern", "thrush", "toad", "trout", "tuna", "turkey", "turtle", "viper", "vole", "vulture", "wasp", "whale", "wolf", "wombat", "worm", "wren", "yak", "zebra", "adder", "akita", "alpaca", "anole", "bongo", "camel", "civet", "coati", "coral", "corgi", "dhole", "drill", "dugong", "dunnit", "eland", "ermine", "falcon", "ferret", "gibbon", "gopher", "grouse", "haddok", "hermit", "hippo", "hoopoe", "hutia", "impala", "indri", "isopod", "jacana", "jerboa", "kakapo", "kudu", "loris", "magpie", "marten", "mayfly", "merlin", "murre", "nandu", "numbat", "ocelot", "osprey", "oyster", "paca", "pangol", "pariah", "peahen", "pipit", "pollock", "possum", "potoo", "python", "quokka", "rail", "redfin", "reebok", "remora", "rhea", "sable", "saola", "serval", "siskin", "snapper", "snoek", "sparrow", "spider", "sponge", "sprat", "stoat", "stilt", "stint", "sunbird", "tanuki", "tarpon", "tenrec", "tigon", "toucan", "treefr", "uguisu", "urutu", "vervet", "vizsla", "walrus", "weasel", "weevil", "whimbr", "whydah", "wisent", "zorilla"];
|
|
16
|
+
/**
|
|
17
|
+
* FNV-1a hash (32-bit). Fast, good distribution, zero dependencies.
|
|
18
|
+
* Reference: http://www.isthe.com/chongo/tech/comp/fnv/
|
|
19
|
+
*/
|
|
20
|
+
export declare function fnv1a(str: string): number;
|
|
21
|
+
/**
|
|
22
|
+
* Map a string to a deterministic adjective-noun pair.
|
|
23
|
+
* Same input always produces the same word tuple.
|
|
24
|
+
*/
|
|
25
|
+
export declare function toWordTuple(str: string): string;
|
package/dist/words.js
ADDED
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Word lists for generating deterministic human-readable callpoint names.
|
|
3
|
+
* Used as a fallback in production when the gg-call-sites Vite plugin is not installed.
|
|
4
|
+
*
|
|
5
|
+
* Each list has exactly 256 entries (8-bit index) so a 32-bit hash cleanly maps to
|
|
6
|
+
* an adjective-noun pair: adjectives[hash & 0xFF] + "-" + nouns[(hash >> 8) & 0xFF]
|
|
7
|
+
* giving 65,536 unique combinations.
|
|
8
|
+
*
|
|
9
|
+
* Selection criteria:
|
|
10
|
+
* - Short (3-6 chars) for compact console output
|
|
11
|
+
* - Visually distinct (no near-homoglyphs like fast/last)
|
|
12
|
+
* - Inoffensive
|
|
13
|
+
*/
|
|
14
|
+
// prettier-ignore
|
|
15
|
+
export const adjectives = [
|
|
16
|
+
'able', 'acid', 'aged', 'airy', 'apt', 'avid', 'awry', 'balmy',
|
|
17
|
+
'bare', 'beefy', 'bent', 'big', 'bland', 'bleak', 'blind', 'bliss',
|
|
18
|
+
'blue', 'blunt', 'bold', 'brash', 'brave', 'brief', 'briny', 'brisk',
|
|
19
|
+
'broad', 'buff', 'bulky', 'bumpy', 'burly', 'busy', 'calm', 'cheap',
|
|
20
|
+
'chewy', 'chief', 'chilly', 'civil', 'clean', 'clear', 'close', 'cold',
|
|
21
|
+
'cool', 'coral', 'cozy', 'crisp', 'cubic', 'curly', 'curvy', 'cute',
|
|
22
|
+
'cyan', 'damp', 'dark', 'dear', 'deep', 'dense', 'dewy', 'dim',
|
|
23
|
+
'dizzy', 'dopey', 'dorky', 'draft', 'dry', 'dual', 'dull', 'dusty',
|
|
24
|
+
'eager', 'early', 'easy', 'edgy', 'elfin', 'elite', 'empty', 'equal',
|
|
25
|
+
'even', 'every', 'evil', 'exact', 'extra', 'faded', 'fair', 'fancy',
|
|
26
|
+
'fast', 'few', 'fine', 'firm', 'first', 'fishy', 'fit', 'five',
|
|
27
|
+
'fixed', 'fizzy', 'flat', 'fleet', 'fluid', 'foggy', 'fond', 'four',
|
|
28
|
+
'free', 'fresh', 'front', 'full', 'funky', 'funny', 'furry', 'fussy',
|
|
29
|
+
'fuzzy', 'gaudy', 'giant', 'glad', 'gold', 'good', 'grand', 'gray',
|
|
30
|
+
'great', 'green', 'grim', 'gross', 'grown', 'gusty', 'hairy', 'half',
|
|
31
|
+
'happy', 'hard', 'hardy', 'harsh', 'hasty', 'hazy', 'hefty', 'high',
|
|
32
|
+
'holy', 'honey', 'hot', 'huge', 'humid', 'husky', 'icy', 'ideal',
|
|
33
|
+
'idle', 'inner', 'ionic', 'iron', 'ivory', 'jade', 'jazzy', 'jolly',
|
|
34
|
+
'juicy', 'jumbo', 'jumpy', 'just', 'keen', 'kind', 'known', 'lanky',
|
|
35
|
+
'large', 'last', 'late', 'lazy', 'lean', 'legal', 'light', 'limp',
|
|
36
|
+
'live', 'local', 'lofty', 'lone', 'long', 'lost', 'loud', 'loved',
|
|
37
|
+
'low', 'loyal', 'lucky', 'lumpy', 'lusty', 'mad', 'magic', 'main',
|
|
38
|
+
'major', 'meek', 'merry', 'messy', 'mild', 'minty', 'misty', 'mixed',
|
|
39
|
+
'moist', 'moody', 'mossy', 'muddy', 'murky', 'mushy', 'muted', 'naive',
|
|
40
|
+
'neat', 'nerdy', 'new', 'next', 'nice', 'nimby', 'noble', 'noisy',
|
|
41
|
+
'north', 'novel', 'numb', 'nutty', 'oaken', 'odd', 'oily', 'old',
|
|
42
|
+
'olive', 'only', 'open', 'other', 'outer', 'oval', 'paid', 'pale',
|
|
43
|
+
'pasty', 'perky', 'petty', 'pink', 'plain', 'plump', 'plush', 'polar',
|
|
44
|
+
'poor', 'prime', 'proud', 'pulpy', 'pure', 'pushy', 'quick', 'quiet',
|
|
45
|
+
'rare', 'raw', 'ready', 'real', 'rich', 'rigid', 'ripe', 'rosy',
|
|
46
|
+
'rough', 'round', 'royal', 'ruby', 'rusty', 'safe', 'salty', 'same',
|
|
47
|
+
'sandy', 'sharp', 'shiny', 'silky', 'slim', 'slow', 'small', 'snug',
|
|
48
|
+
];
|
|
49
|
+
// prettier-ignore
|
|
50
|
+
export const nouns = [
|
|
51
|
+
'ant', 'ape', 'asp', 'auk', 'bass', 'bat', 'bear', 'bee',
|
|
52
|
+
'bird', 'bison', 'boar', 'bream', 'buck', 'bug', 'bull', 'bunny',
|
|
53
|
+
'calf', 'carp', 'cat', 'chick', 'chimp', 'clam', 'cobra', 'cod',
|
|
54
|
+
'colt', 'conch', 'coon', 'cow', 'crab', 'crane', 'crow', 'cub',
|
|
55
|
+
'dart', 'deer', 'dingo', 'dodo', 'doe', 'dog', 'dove', 'drake',
|
|
56
|
+
'drum', 'duck', 'eagle', 'eel', 'egret', 'elk', 'emu', 'ewe',
|
|
57
|
+
'fawn', 'finch', 'fish', 'flea', 'fly', 'foal', 'fox', 'frog',
|
|
58
|
+
'gator', 'gecko', 'goat', 'goose', 'grub', 'gull', 'guppy', 'hare',
|
|
59
|
+
'hawk', 'hen', 'heron', 'hog', 'hornet', 'horse', 'hound', 'hyena',
|
|
60
|
+
'ibex', 'ibis', 'iguana', 'imp', 'jackal', 'jay', 'joey', 'kite',
|
|
61
|
+
'kiwi', 'koala', 'koi', 'lamb', 'lark', 'lemur', 'lion', 'llama',
|
|
62
|
+
'lynx', 'macaw', 'mako', 'mare', 'mink', 'mite', 'mole', 'moose',
|
|
63
|
+
'moth', 'mouse', 'mule', 'newt', 'okapi', 'orca', 'oryx', 'otter',
|
|
64
|
+
'owl', 'ox', 'panda', 'parrot', 'perch', 'pig', 'pike', 'plover',
|
|
65
|
+
'pony', 'prawn', 'pug', 'puma', 'quail', 'ram', 'rat', 'raven',
|
|
66
|
+
'ray', 'robin', 'rook', 'roach', 'sail', 'seal', 'shad', 'shark',
|
|
67
|
+
'sheep', 'shrew', 'shrimp', 'skate', 'skink', 'skua', 'skunk', 'sloth',
|
|
68
|
+
'slug', 'smelt', 'snail', 'snake', 'snipe', 'sole', 'squid', 'stag',
|
|
69
|
+
'stork', 'swan', 'swift', 'tapir', 'tern', 'thrush', 'toad', 'trout',
|
|
70
|
+
'tuna', 'turkey', 'turtle', 'viper', 'vole', 'vulture', 'wasp', 'whale',
|
|
71
|
+
'wolf', 'wombat', 'worm', 'wren', 'yak', 'zebra', 'adder', 'akita',
|
|
72
|
+
'alpaca', 'anole', 'bongo', 'camel', 'civet', 'coati', 'coral', 'corgi',
|
|
73
|
+
'dhole', 'drill', 'dugong', 'dunnit', 'eland', 'ermine', 'falcon', 'ferret',
|
|
74
|
+
'gibbon', 'gopher', 'grouse', 'haddok', 'hermit', 'hippo', 'hoopoe', 'hutia',
|
|
75
|
+
'impala', 'indri', 'isopod', 'jacana', 'jerboa', 'kakapo', 'kudu', 'loris',
|
|
76
|
+
'magpie', 'marten', 'mayfly', 'merlin', 'murre', 'nandu', 'numbat', 'ocelot',
|
|
77
|
+
'osprey', 'oyster', 'paca', 'pangol', 'pariah', 'peahen', 'pipit', 'pollock',
|
|
78
|
+
'possum', 'potoo', 'python', 'quokka', 'rail', 'redfin', 'reebok', 'remora',
|
|
79
|
+
'rhea', 'sable', 'saola', 'serval', 'siskin', 'snapper', 'snoek', 'sparrow',
|
|
80
|
+
'spider', 'sponge', 'sprat', 'stoat', 'stilt', 'stint', 'sunbird', 'tanuki',
|
|
81
|
+
'tarpon', 'tenrec', 'tigon', 'toucan', 'treefr', 'uguisu', 'urutu', 'vervet',
|
|
82
|
+
'vizsla', 'walrus', 'weasel', 'weevil', 'whimbr', 'whydah', 'wisent', 'zorilla',
|
|
83
|
+
];
|
|
84
|
+
/**
|
|
85
|
+
* FNV-1a hash (32-bit). Fast, good distribution, zero dependencies.
|
|
86
|
+
* Reference: http://www.isthe.com/chongo/tech/comp/fnv/
|
|
87
|
+
*/
|
|
88
|
+
export function fnv1a(str) {
|
|
89
|
+
let hash = 0x811c9dc5; // FNV offset basis
|
|
90
|
+
for (let i = 0; i < str.length; i++) {
|
|
91
|
+
hash ^= str.charCodeAt(i);
|
|
92
|
+
hash = Math.imul(hash, 0x01000193); // FNV prime
|
|
93
|
+
}
|
|
94
|
+
return hash >>> 0; // Ensure unsigned 32-bit
|
|
95
|
+
}
|
|
96
|
+
/**
|
|
97
|
+
* Map a string to a deterministic adjective-noun pair.
|
|
98
|
+
* Same input always produces the same word tuple.
|
|
99
|
+
*/
|
|
100
|
+
export function toWordTuple(str) {
|
|
101
|
+
const hash = fnv1a(str);
|
|
102
|
+
const adj = adjectives[hash & 0xff];
|
|
103
|
+
const noun = nouns[(hash >>> 8) & 0xff];
|
|
104
|
+
return `${adj}-${noun}`;
|
|
105
|
+
}
|