@twardoch/namzy 1.0.19 → 1.0.23
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/cli.js +19 -16
- package/dist/index.d.ts +7 -8
- package/dist/index.js +15 -21
- package/dist/mangle.d.ts +4 -14
- package/dist/mangle.js +55 -64
- package/dist/web.d.ts +1 -1
- package/dist/web.js +1 -1
- package/dist/wordlist.d.ts +3 -2
- package/dist/wordlist.js +286 -172
- package/package.json +1 -1
- package/src/cli.ts +20 -18
- package/src/index.ts +17 -25
- package/src/mangle.ts +57 -66
- package/src/web.ts +1 -1
- package/src/wordlist.ts +288 -172
package/src/index.ts
CHANGED
|
@@ -1,35 +1,27 @@
|
|
|
1
1
|
// this_file: src/index.ts
|
|
2
2
|
|
|
3
|
-
import {
|
|
4
|
-
import {
|
|
3
|
+
import { applyRotation, buildName, mulberry32 } from "./mangle.js";
|
|
4
|
+
import { ROTATIONS, STEMS } from "./wordlist.js";
|
|
5
5
|
|
|
6
6
|
export interface NamzyOptions {
|
|
7
|
-
|
|
7
|
+
seed?: number;
|
|
8
8
|
}
|
|
9
9
|
|
|
10
|
-
|
|
11
|
-
|
|
10
|
+
/** Generate one namzy name. Default seed is the current timestamp. */
|
|
11
|
+
export function generate(opts?: NamzyOptions): string {
|
|
12
|
+
const seed = opts?.seed ?? Date.now();
|
|
13
|
+
return buildName(mulberry32(seed));
|
|
12
14
|
}
|
|
13
15
|
|
|
14
|
-
|
|
15
|
-
|
|
16
|
+
/** Generate `count` names. Distinct seeds derived from the base seed. */
|
|
17
|
+
export function generateMany(count: number, opts?: NamzyOptions): string[] {
|
|
18
|
+
const base = opts?.seed ?? Date.now();
|
|
19
|
+
const out: string[] = [];
|
|
20
|
+
for (let i = 0; i < count; i++) {
|
|
21
|
+
out.push(generate({ seed: base + i * 2654435761 }));
|
|
22
|
+
}
|
|
23
|
+
return out;
|
|
16
24
|
}
|
|
17
25
|
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
* Two raw words → junction-cleaned fusion → consonant rotation → capitalize.
|
|
21
|
-
*/
|
|
22
|
-
export async function generate(opts?: NamzyOptions): Promise<string> {
|
|
23
|
-
const seed = opts?.seed ?? Date.now();
|
|
24
|
-
const rng = mulberry32(seed);
|
|
25
|
-
|
|
26
|
-
const w1 = pick(GEO, rng).toLowerCase();
|
|
27
|
-
const w2 = pick(COMMON, rng).toLowerCase();
|
|
28
|
-
const [first, second] = rng() < 0.5 ? [w1, w2] : [w2, w1];
|
|
29
|
-
|
|
30
|
-
const fused = joinClean(first, second);
|
|
31
|
-
return capitalize(mangle(fused, activeRotationMask(rng)));
|
|
32
|
-
}
|
|
33
|
-
|
|
34
|
-
export { COMMON, GEO } from "./wordlist.js";
|
|
35
|
-
export { activeRotationMask, joinClean, mangle, mulberry32 };
|
|
26
|
+
export { STEMS, ROTATIONS } from "./wordlist.js";
|
|
27
|
+
export { applyRotation, buildName, mulberry32 };
|
package/src/mangle.ts
CHANGED
|
@@ -1,92 +1,83 @@
|
|
|
1
1
|
// this_file: src/mangle.ts
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
import { BAD_SEAMS, ROTATIONS, STEMS } from "./wordlist.js";
|
|
4
|
+
|
|
5
|
+
const MAX_LEN = 12;
|
|
6
|
+
const MAX_TRIES = 8;
|
|
7
|
+
|
|
4
8
|
export function mulberry32(seed: number): () => number {
|
|
5
9
|
let s = seed >>> 0;
|
|
6
10
|
return () => {
|
|
7
|
-
s
|
|
11
|
+
s = (s + 0x6d2b79f5) >>> 0;
|
|
8
12
|
let t = Math.imul(s ^ (s >>> 15), 1 | s);
|
|
9
13
|
t ^= t + Math.imul(t ^ (t >>> 7), 61 | t);
|
|
10
14
|
return ((t ^ (t >>> 14)) >>> 0) / 4294967296;
|
|
11
15
|
};
|
|
12
16
|
}
|
|
13
17
|
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
["c", "q"],
|
|
18
|
-
["f", "v"],
|
|
19
|
-
["k", "c"],
|
|
20
|
-
["q", "k"],
|
|
21
|
-
["s", "z"],
|
|
22
|
-
["z", "s"],
|
|
23
|
-
["v", "f"],
|
|
24
|
-
["w", "u"],
|
|
25
|
-
["b", "p"],
|
|
26
|
-
["p", "b"],
|
|
27
|
-
];
|
|
28
|
-
|
|
29
|
-
const ALL_ROTATIONS = (1 << ROTATION_RULES.length) - 1;
|
|
18
|
+
function pick<T>(arr: readonly T[], rng: () => number): T {
|
|
19
|
+
return arr[Math.floor(rng() * arr.length)];
|
|
20
|
+
}
|
|
30
21
|
|
|
31
|
-
function
|
|
32
|
-
for (let i =
|
|
33
|
-
if (
|
|
34
|
-
continue;
|
|
35
|
-
}
|
|
36
|
-
const [from, to] = ROTATION_RULES[i];
|
|
37
|
-
if (lower === from) {
|
|
38
|
-
return to;
|
|
39
|
-
}
|
|
22
|
+
function hasTripleLetter(s: string): boolean {
|
|
23
|
+
for (let i = 2; i < s.length; i++) {
|
|
24
|
+
if (s[i] === s[i - 1] && s[i] === s[i - 2]) return true;
|
|
40
25
|
}
|
|
41
|
-
return
|
|
26
|
+
return false;
|
|
42
27
|
}
|
|
43
28
|
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
const
|
|
47
|
-
const
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
29
|
+
function junctionUgly(compound: string, junction: number): boolean {
|
|
30
|
+
// Inspect the 4-char window centered on the junction.
|
|
31
|
+
const start = Math.max(0, junction - 2);
|
|
32
|
+
const end = Math.min(compound.length, junction + 2);
|
|
33
|
+
const win = compound.slice(start, end);
|
|
34
|
+
for (const seam of BAD_SEAMS) {
|
|
35
|
+
if (win.includes(seam)) return true;
|
|
51
36
|
}
|
|
52
|
-
return
|
|
37
|
+
return false;
|
|
53
38
|
}
|
|
54
39
|
|
|
55
|
-
/**
|
|
56
|
-
export function
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
const
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
out += ch === lower ? repl : repl.toUpperCase();
|
|
40
|
+
/** Apply one rotation at a randomly chosen matching position. */
|
|
41
|
+
export function applyRotation(s: string, rng: () => number): string {
|
|
42
|
+
type Match = { i: number; src: string; dst: string };
|
|
43
|
+
const matches: Match[] = [];
|
|
44
|
+
for (let i = 0; i < s.length; i++) {
|
|
45
|
+
for (const [src, dst] of ROTATIONS) {
|
|
46
|
+
if (s.slice(i, i + src.length) === src) {
|
|
47
|
+
matches.push({ i, src, dst });
|
|
48
|
+
}
|
|
65
49
|
}
|
|
66
50
|
}
|
|
67
|
-
return
|
|
51
|
+
if (matches.length === 0) return s;
|
|
52
|
+
const m = matches[Math.floor(rng() * matches.length)];
|
|
53
|
+
return s.slice(0, m.i) + m.dst + s.slice(m.i + m.src.length);
|
|
68
54
|
}
|
|
69
55
|
|
|
70
|
-
|
|
56
|
+
function capitalize(s: string): string {
|
|
57
|
+
return s.length === 0 ? s : s[0].toUpperCase() + s.slice(1);
|
|
58
|
+
}
|
|
71
59
|
|
|
72
|
-
/**
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
if (
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
60
|
+
/** Build one name with junction validation + one rotation. */
|
|
61
|
+
export function buildName(rng: () => number): string {
|
|
62
|
+
let best = "";
|
|
63
|
+
for (let attempt = 0; attempt < MAX_TRIES; attempt++) {
|
|
64
|
+
const a = pick(STEMS, rng);
|
|
65
|
+
const b = pick(STEMS, rng);
|
|
66
|
+
const compound = a + b;
|
|
67
|
+
if (compound.length > MAX_LEN) {
|
|
68
|
+
best = best || compound;
|
|
69
|
+
continue;
|
|
70
|
+
}
|
|
71
|
+
if (hasTripleLetter(compound)) {
|
|
72
|
+
best = best || compound;
|
|
73
|
+
continue;
|
|
74
|
+
}
|
|
75
|
+
if (junctionUgly(compound, a.length)) {
|
|
76
|
+
best = best || compound;
|
|
77
|
+
continue;
|
|
89
78
|
}
|
|
79
|
+
return capitalize(applyRotation(compound, rng));
|
|
90
80
|
}
|
|
91
|
-
|
|
81
|
+
// All attempts had some flaw; rotate and ship the first candidate anyway.
|
|
82
|
+
return capitalize(applyRotation(best, rng));
|
|
92
83
|
}
|
package/src/web.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
// this_file: src/web.ts
|
|
2
2
|
// Browser entry point — exposes namzy.generate() on window.namzy.
|
|
3
3
|
|
|
4
|
-
export { generate,
|
|
4
|
+
export { generate, generateMany, applyRotation, buildName, mulberry32, STEMS, ROTATIONS } from "./index.js";
|
|
5
5
|
export type { NamzyOptions } from "./index.js";
|