seedmarkfile
seedmark/src/index.js/

src/index.js raw
1import {render} from './render.js';
2
3export const VERSION = 1;
4// Ordering is part of the versioned visual contract.
5export const FAMILIES = Object.freeze(['masks', 'creatures', 'botanical', 'orbital', 'landscapes', 'glyphs', 'woven', 'machines']);
6const PALETTES = [
7 ['#142d36', '#f5dfad', '#ed7351', '#65c4ae'],
8 ['#24223c', '#f7dfbf', '#a89cfc', '#f7788b'],
9 ['#163b32', '#f4eacb', '#b4cf66', '#f69c64'],
10 ['#3a2330', '#ffe5b9', '#ef7689', '#8dceba'],
11 ['#1d304c', '#e8eff0', '#f4b855', '#72b9d0'],
12 ['#422d26', '#f8e8c8', '#e8a04d', '#72afa0'],
13 ['#283526', '#f4edcf', '#d3c65e', '#ed8863'],
14 ['#292745', '#efe8f5', '#d89ada', '#8fcabc'],
15 ['#18343c', '#f8e4d0', '#f88c66', '#a7cee0'],
16 ['#393127', '#f4ebd8', '#adb77a', '#da8c6f'],
17 ['#2c2440', '#fbe8c2', '#f2b54f', '#b2a1e2'],
18 ['#1b3537', '#e9f0d7', '#79c7b8', '#e5a4aa']
19];
20function freeze(value) {
21 for (const child of Object.values(value)) if (child && typeof child === 'object') freeze(child);
22 return Object.freeze(value);
23}
24export function recipe(seed, {version = VERSION} = {}) {
25 if (version !== VERSION) throw new RangeError('unsupported visual version');
26 const stream = name => createRng(seed, name);
27 const layout = stream('layout'), silhouette = stream('silhouette');
28 const features = stream('features'), texture = stream('texture');
29 return freeze({
30 version,
31 family: stream('family').pick(FAMILIES),
32 palette: [...stream('palette').pick(PALETTES)],
33 layout: {variant: layout.int(0, 3), flip: layout.int(0, 1), tilt: layout.pick([-12, 0, 12])},
34 silhouette: {variant: silhouette.int(0, 3), width: silhouette.int(26, 38), height: silhouette.int(28, 42)},
35 features: {variant: features.int(0, 3), count: features.int(2, 5), accent: features.int(0, 1)},
36 texture: {variant: texture.int(0, 2), marks: texture.int(2, 4)}
37 });
38}
39
40export function avatar(seed, {size = 64, version = VERSION} = {}) {
41 if (!Number.isInteger(size) || size < 1 || size > 4096) throw new RangeError('size must be an integer from 1 to 4096');
42 const r = recipe(seed, {version});
43 return `<svg xmlns="http://www.w3.org/2000/svg" width="${size}" height="${size}" viewBox="0 0 64 64"><rect width="64" height="64" fill="${r.palette[0]}"/>${render(r)}</svg>`;
44}
45
46// Version 1: length-framed UTF-16 input, cyrb128 mixing, sfc32 stream.
47// Noncryptographic. Every call owns its own four-word state.
48function hash(text) {
49 let a = 1779033703, b = 3144134277, c = 1013904242, d = 2773480762;
50 for (let i = 0; i < text.length; i++) {
51 const k = text.charCodeAt(i);
52 a = b ^ Math.imul(a ^ k, 597399067);
53 b = c ^ Math.imul(b ^ k, 2869860233);
54 c = d ^ Math.imul(c ^ k, 951274213);
55 d = a ^ Math.imul(d ^ k, 2716044179);
56 }
57 a = Math.imul(c ^ (a >>> 18), 597399067);
58 b = Math.imul(d ^ (b >>> 22), 2869860233);
59 c = Math.imul(a ^ (c >>> 17), 951274213);
60 d = Math.imul(b ^ (d >>> 19), 2716044179);
61 return [(a ^ b ^ c ^ d) >>> 0, (b ^ a) >>> 0, (c ^ a) >>> 0, (d ^ a) >>> 0];
62}
63const frame = (...parts) => parts.map(s => `${s.length}:${s}`).join('');
64export function createRng(seed, namespace = 'default') {
65 if (typeof seed !== 'string') throw new TypeError('seed must be a string');
66 if (typeof namespace !== 'string') throw new TypeError('namespace must be a string');
67 let [a, b, c, d] = hash(frame('seedmark', '1', seed, namespace));
68 const rng = {
69 next() {
70 const t = ((a + b | 0) + d | 0);
71 d = d + 1 | 0;
72 a = b ^ b >>> 9;
73 b = c + (c << 3) | 0;
74 c = (c << 21 | c >>> 11) + t | 0;
75 return (t >>> 0) / 4294967296;
76 },
77 int(min, max) {
78 const span = max - min + 1;
79 if (!Number.isSafeInteger(min) || !Number.isSafeInteger(max) || min > max || !Number.isSafeInteger(span)) {
80 throw new RangeError('integer bounds must be ordered safe integers with a safe span');
81 }
82 return min + Math.floor(rng.next() * span);
83 },
84 pick(array) {
85 if (!Array.isArray(array)) throw new TypeError('pick expects an array');
86 if (!array.length) throw new RangeError('cannot pick from an empty array');
87 return array[rng.int(0, array.length - 1)];
88 }
89 };
90 return rng;
91}

About

ownerba1b5beed5b5
visibilitypublic
headc808a940bc19

Maintainers

  • ba1b5beed5b5 owner

Clone

  • https://012.run/npub1hgd4hmk4kkukjx7yf402adl7msw0ssnmqmt0y6sgz0j8yjjcj9nqh9znde/seedmark.git

Refs