@pieai/swimmer-avatar-kit 0.1.0

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.
Files changed (56) hide show
  1. package/LICENSE +21 -0
  2. package/NOTICE.md +23 -0
  3. package/README.md +125 -0
  4. package/UPSTREAM.md +47 -0
  5. package/dist/avatar.d.ts +2 -0
  6. package/dist/avatar.js +69 -0
  7. package/dist/avatar.js.map +1 -0
  8. package/dist/index.d.ts +3 -0
  9. package/dist/index.js +3 -0
  10. package/dist/index.js.map +1 -0
  11. package/dist/materials.d.ts +20 -0
  12. package/dist/materials.js +61 -0
  13. package/dist/materials.js.map +1 -0
  14. package/dist/react-three-fiber.d.ts +16 -0
  15. package/dist/react-three-fiber.js +52 -0
  16. package/dist/react-three-fiber.js.map +1 -0
  17. package/dist/recipe.d.ts +18 -0
  18. package/dist/recipe.js +118 -0
  19. package/dist/recipe.js.map +1 -0
  20. package/dist/types.d.ts +62 -0
  21. package/dist/types.js +2 -0
  22. package/dist/types.js.map +1 -0
  23. package/package.json +102 -0
  24. package/upstream-lock.json +38 -0
  25. package/vendor/kindergrimm/LICENSE +24 -0
  26. package/vendor/kindergrimm/src/gloss/catmullClark.js +129 -0
  27. package/vendor/kindergrimm/src/gloss/gface.d.ts +25 -0
  28. package/vendor/kindergrimm/src/gloss/gface.js +226 -0
  29. package/vendor/kindergrimm/src/gloss/gform.js +201 -0
  30. package/vendor/kindergrimm/src/gloss/ghair.js +534 -0
  31. package/vendor/kindergrimm/src/gloss/glayout.js +365 -0
  32. package/vendor/kindergrimm/src/gloss/gmedia.d.ts +28 -0
  33. package/vendor/kindergrimm/src/gloss/gmedia.js +470 -0
  34. package/vendor/kindergrimm/src/gloss/gpalette.d.ts +10 -0
  35. package/vendor/kindergrimm/src/gloss/gpalette.js +205 -0
  36. package/vendor/kindergrimm/src/gloss/gparts/blush.js +57 -0
  37. package/vendor/kindergrimm/src/gloss/gparts/body.js +47 -0
  38. package/vendor/kindergrimm/src/gloss/gparts/brows.js +55 -0
  39. package/vendor/kindergrimm/src/gloss/gparts/crest.js +88 -0
  40. package/vendor/kindergrimm/src/gloss/gparts/eyes.js +357 -0
  41. package/vendor/kindergrimm/src/gloss/gparts/frame.js +225 -0
  42. package/vendor/kindergrimm/src/gloss/gparts/hair.js +100 -0
  43. package/vendor/kindergrimm/src/gloss/gparts/hat.js +163 -0
  44. package/vendor/kindergrimm/src/gloss/gparts/index.js +33 -0
  45. package/vendor/kindergrimm/src/gloss/gparts/mark.js +139 -0
  46. package/vendor/kindergrimm/src/gloss/gparts/mouth.js +170 -0
  47. package/vendor/kindergrimm/src/gloss/gparts/nose.js +63 -0
  48. package/vendor/kindergrimm/src/gloss/gparts/specs.js +124 -0
  49. package/vendor/kindergrimm/src/gloss/grig.d.ts +41 -0
  50. package/vendor/kindergrimm/src/gloss/grig.js +286 -0
  51. package/vendor/kindergrimm/src/gloss/gshape.js +421 -0
  52. package/vendor/kindergrimm/src/gloss/gspecies.d.ts +10 -0
  53. package/vendor/kindergrimm/src/gloss/gspecies.js +338 -0
  54. package/vendor/kindergrimm/src/gloss/gtexture.js +406 -0
  55. package/vendor/kindergrimm/src/rng.d.ts +11 -0
  56. package/vendor/kindergrimm/src/rng.js +27 -0
@@ -0,0 +1,406 @@
1
+ // ---------------------------------------------------------------
2
+ // THE TILE BAKERY — `gshape.js` cuts and stamps, this one WEAVES,
3
+ // TURNS and CRACKS. Four finishes in `gmedia.js` need a surface that
4
+ // parameters alone cannot say: a knit stitch, a lathe's ring grain, a
5
+ // printer's layer lines, a glaze's crazing. Every one of them is baked
6
+ // here, procedurally, into a canvas — nothing is loaded, the same way
7
+ // the studio is a hand-built room and the cyc is a gradient.
8
+ //
9
+ // THREE RULES.
10
+ //
11
+ // 1. A TILE IS BAKED ONCE. Every generator below is wrapped in a lazy
12
+ // singleton, because the material factory caches on colour and a
13
+ // palette would otherwise re-weave the same jumper twelve times.
14
+ //
15
+ // 2. A TILE MUST WRAP. It is laid on `solidGeometry`'s UVs, which come
16
+ // straight off `SphereGeometry`: u runs once around the equator, v
17
+ // from pole to pole. So a pattern is drawn with its neighbours
18
+ // already present outside the edges, or built out of terms that are
19
+ // periodic by construction, and the seam has nothing to show.
20
+ //
21
+ // 3. THE BODY ONLY. These land on the shell and never on a face
22
+ // feature — a plate is an `ExtrudeGeometry` whose UVs are three's
23
+ // world-space default, which is to say noise. `gmedia.js` enforces
24
+ // that; this file only has to be worth laying down.
25
+ //
26
+ // A note on which way is up: a normal map here is OpenGL convention
27
+ // (+Y up) and `CanvasTexture` uploads with `flipY`, so canvas y runs
28
+ // OPPOSITE to v. That is why the green channel takes +dy and the red
29
+ // takes −dx below. Get it backwards and every bump becomes a dent —
30
+ // which is legible, but only once you know to look for it.
31
+ // ---------------------------------------------------------------
32
+ import * as THREE from 'three';
33
+ import { mulberry32 } from '../rng.js';
34
+
35
+ /** a 2D context of a given size, with the canvas kept alive by it. */
36
+ function surface(w, h) {
37
+ const c = document.createElement('canvas');
38
+ c.width = w; c.height = h;
39
+ return c.getContext('2d', { willReadFrequently: true });
40
+ }
41
+
42
+ /** everything here tiles, so everything here repeats. */
43
+ function wrap(tex, rx, ry) {
44
+ tex.wrapS = tex.wrapT = THREE.RepeatWrapping;
45
+ tex.repeat.set(rx, ry);
46
+ // grazing angles are most of a character's silhouette and that is exactly
47
+ // where an unfiltered repeat turns to mush. three clamps this to
48
+ // whatever the card actually supports.
49
+ tex.anisotropy = 8;
50
+ return tex;
51
+ }
52
+
53
+ /**
54
+ * height field → tangent-space normal map.
55
+ *
56
+ * Central differences, wrapped at the edges so the derivative is
57
+ * continuous across the seam too — a tile that wraps but whose
58
+ * GRADIENT does not still draws a bright line down the back of the
59
+ * head. Reads the red channel; ignores the rest.
60
+ */
61
+ function normalFromHeight(ctx, strength) {
62
+ const { width: w, height: h } = ctx.canvas;
63
+ const src = ctx.getImageData(0, 0, w, h).data;
64
+ const out = new ImageData(w, h);
65
+ const d = out.data;
66
+ const at = (x, y) => src[(((y + h) % h) * w + ((x + w) % w)) * 4] / 255;
67
+
68
+ for (let y = 0; y < h; y++) {
69
+ for (let x = 0; x < w; x++) {
70
+ const dx = (at(x + 1, y) - at(x - 1, y)) * strength;
71
+ const dy = (at(x, y + 1) - at(x, y - 1)) * strength;
72
+ // OpenGL tangent space, and `flipY` has already turned v over
73
+ let nx = -dx, ny = dy, nz = 1;
74
+ const l = Math.hypot(nx, ny, nz);
75
+ nx /= l; ny /= l; nz /= l;
76
+ const i = (y * w + x) * 4;
77
+ d[i] = (nx * .5 + .5) * 255;
78
+ d[i + 1] = (ny * .5 + .5) * 255;
79
+ d[i + 2] = (nz * .5 + .5) * 255;
80
+ d[i + 3] = 255;
81
+ }
82
+ }
83
+ const o = surface(w, h);
84
+ o.putImageData(out, 0, 0);
85
+ const tex = new THREE.CanvasTexture(o.canvas);
86
+ // a normal map is DATA. Tag it sRGB and every slope comes out wrong.
87
+ tex.colorSpace = THREE.NoColorSpace;
88
+ return tex;
89
+ }
90
+
91
+ /**
92
+ * blur a height field in place — WRAPPING.
93
+ *
94
+ * Canvas blur does not know the tile repeats: at the edges it mixes the
95
+ * pattern with nothing, so every edge comes back slightly flattened and
96
+ * the seam shows up on the character as a fine line ruled around it. Which it
97
+ * did, right across the crown of the first knitted head.
98
+ *
99
+ * So the tile is laid out three by three, blurred whole, and the middle
100
+ * one taken back. Every edge then had a real neighbour to blur into.
101
+ * It costs nine times the pixels of a blur that happens exactly once
102
+ * per finish per page, which is nothing at all.
103
+ */
104
+ function soften(ctx, px) {
105
+ const { width: w, height: h } = ctx.canvas;
106
+ const big = surface(w * 3, h * 3);
107
+ for (let x = 0; x < 3; x++) for (let y = 0; y < 3; y++) big.drawImage(ctx.canvas, x * w, y * h);
108
+ const b = surface(w * 3, h * 3);
109
+ b.filter = `blur(${px}px)`;
110
+ b.drawImage(big.canvas, 0, 0);
111
+ ctx.clearRect(0, 0, w, h);
112
+ ctx.drawImage(b.canvas, -w, -h);
113
+ return ctx;
114
+ }
115
+
116
+ /** bake once, hand back forever. */
117
+ function once(fn) {
118
+ let cached = null;
119
+ return () => (cached ??= fn());
120
+ }
121
+
122
+ // ---- 1. KNIT ----------------------------------------------------------
123
+ // Stockinette: columns of V's, each one two yarn legs, every leg
124
+ // overlapping its neighbours. The overlap is drawn ADDITIVELY on
125
+ // purpose — where two legs cross, the height stacks, and that little
126
+ // bump is what reads as one strand passing over another. Modelling the
127
+ // over/under properly would need depth sorting for a bump nobody can
128
+ // see from across a sheet.
129
+ const KNIT = 8; // stitches per tile, both ways
130
+
131
+ export const knitNormal = once(() => {
132
+ const N = 512, cell = N / KNIT;
133
+ const g = surface(N, N);
134
+ g.fillStyle = '#000';
135
+ g.fillRect(0, 0, N, N);
136
+ g.globalCompositeOperation = 'lighter';
137
+ g.strokeStyle = 'rgba(255,255,255,.58)';
138
+ g.lineWidth = cell * .28;
139
+ g.lineCap = 'round';
140
+
141
+ // A V IS TALLER THAN ITS CELL, and that is the whole trick. Its arms
142
+ // have to reach up PAST the bottom of the V in the row above — a real
143
+ // stitch is pulled through the one before it, so the two overlap. Stop
144
+ // the arms inside the cell and the rows no longer touch: you get a
145
+ // clean horizontal band of bare yarn every row, which on a sphere
146
+ // reads as a dashed line ruled around the head. It did, on the first
147
+ // pass; that is what these numbers are for.
148
+ const TOP = -.38, BOT = .70; // in cells, and BOT - 1 > TOP is the rule
149
+ for (let row = -1; row <= KNIT; row++) {
150
+ for (let col = -1; col <= KNIT; col++) {
151
+ const x = col * cell, y = row * cell;
152
+ for (const dir of [-1, 1]) {
153
+ g.beginPath();
154
+ g.moveTo(x + cell * .5, y + cell * BOT);
155
+ g.quadraticCurveTo(x + cell * (.5 + dir * .24), y + cell * .30,
156
+ x + cell * (.5 + dir * .48), y + cell * TOP);
157
+ g.stroke();
158
+ }
159
+ }
160
+ }
161
+ g.globalCompositeOperation = 'source-over';
162
+ soften(g, cell * .075);
163
+ return wrap(normalFromHeight(g, 2.8), 3, 1.5);
164
+ });
165
+
166
+ // ---- 2. TURNED WOOD ---------------------------------------------------
167
+ // A character on a lathe takes its grain as RINGS around the spin axis, and
168
+ // the sphere's v runs pole to pole — so a ring is a band of constant
169
+ // v and the mapping is free. The wander across u is built from whole
170
+ // harmonics of u only, which is what makes it periodic and therefore
171
+ // seamless, rather than something that has to be drawn twice.
172
+ function woodProfile(depth) {
173
+ const W = 256, H = 1024;
174
+ const g = surface(W, H);
175
+ const img = new ImageData(W, H);
176
+ const d = img.data;
177
+ const rnd = mulberry32(0x5EED); // deterministic: same grain every load
178
+ // a few fixed harmonics of v, so the ring spacing is uneven the way
179
+ // real growth rings are
180
+ const bands = Array.from({ length: 5 }, () => [rnd() * 2 - 1, 1 + ((rnd() * 6) | 0)]);
181
+
182
+ for (let y = 0; y < H; y++) {
183
+ const v = y / H;
184
+ for (let x = 0; x < W; x++) {
185
+ const u = x / W;
186
+ // The wander, in whole cycles of u only. It has to be BIG enough
187
+ // to see: rings that run dead straight around a sphere read as a
188
+ // machined thread, not a grain, and the first pass at a twelfth
189
+ // of this was exactly that.
190
+ const wob = Math.sin(u * Math.PI * 2) * .045 + Math.sin(u * Math.PI * 4 + 1.1) * .022;
191
+ // and FEW enough to count. About twenty rings pole to pole is what
192
+ // a turning this size shows; eighty is corrugation.
193
+ let t = (v + wob) * 9;
194
+ for (const [amp, k] of bands) t += amp * 1.6 * Math.sin((v + wob) * Math.PI * 2 * k);
195
+ // a ring is a hard early-wood line with a soft late-wood fade
196
+ const f = t - Math.floor(t);
197
+ const ring = Math.pow(1 - Math.abs(f * 2 - 1), 2.4);
198
+ const val = (1 - ring * depth) * 255;
199
+ const i = (y * W + x) * 4;
200
+ d[i] = d[i + 1] = d[i + 2] = val;
201
+ d[i + 3] = 255;
202
+ }
203
+ }
204
+ g.putImageData(img, 0, 0);
205
+ return g;
206
+ }
207
+
208
+ /** the grain as ROUGHNESS: late wood drinks the light, early wood
209
+ * polishes. This is the half of wood you actually see on a painted
210
+ * character, where the colour is the palette's and not the timber's. */
211
+ export const woodRough = once(() => {
212
+ // SHALLOW on purpose. `roughnessMap` MULTIPLIES `material.roughness`,
213
+ // so a full-contrast profile would take the early wood down to a
214
+ // near-mirror and the character would read as striped plastic. A ring is a
215
+ // change of sheen, not a change of surface.
216
+ const g = woodProfile(.42);
217
+ const tex = new THREE.CanvasTexture(g.canvas);
218
+ tex.colorSpace = THREE.NoColorSpace; // data, not colour
219
+ return wrap(tex, 1, 2);
220
+ });
221
+
222
+ /** and the same grain as a whisper of relief. */
223
+ export const woodNormal = once(() => wrap(normalFromHeight(soften(woodProfile(.85), 1.2), 1.5), 1, 2));
224
+
225
+ // ---- 5. PEARL FILM ----------------------------------------------------
226
+ // Where a thin film is THICKER, and therefore what colour it returns.
227
+ //
228
+ // This one exists because of a trap in three: without a thickness map,
229
+ // `iridescenceThicknessRange[0]` is dead — the shader takes the MAXIMUM
230
+ // and nothing else, so the whole character is one flat film and the range you
231
+ // wrote is a lie. Give it a map and the low number wakes up, and a
232
+ // pearl gets what a pearl actually has: bands of colour that swirl over
233
+ // each other rather than one even sheen.
234
+ //
235
+ // It is built from WHOLE harmonics of u and v only, which is what makes
236
+ // it periodic — a smooth blob field is the one pattern that would show
237
+ // its seam most.
238
+ export const pearlThickness = once(() => {
239
+ const N = 256;
240
+ const g = surface(N, N);
241
+ const img = new ImageData(N, N);
242
+ const d = img.data;
243
+ const TAU = Math.PI * 2;
244
+ for (let y = 0; y < N; y++) {
245
+ const v = y / N;
246
+ for (let x = 0; x < N; x++) {
247
+ const u = x / N;
248
+ const h = .5 + .5 * (
249
+ .52 * Math.sin(TAU * (u + v) + .7) +
250
+ .30 * Math.sin(TAU * (2 * u - v) + 2.4) +
251
+ .18 * Math.sin(TAU * (u - 3 * v) + 4.1));
252
+ const val = Math.max(0, Math.min(1, h)) * 255;
253
+ const i = (y * N + x) * 4;
254
+ d[i] = d[i + 1] = d[i + 2] = val; // the shader reads GREEN
255
+ d[i + 3] = 255;
256
+ }
257
+ }
258
+ g.putImageData(img, 0, 0);
259
+ const tex = new THREE.CanvasTexture(g.canvas);
260
+ tex.colorSpace = THREE.NoColorSpace; // data, not colour
261
+ return wrap(tex, 1, 1); // one big swirl, not a tile
262
+ });
263
+
264
+ // ---- 3. LAYER LINES ---------------------------------------------------
265
+ // The lab printing its own output. Layers stack perpendicular to the
266
+ // build axis, which is v again — so this is a pure function of v, a
267
+ // sawtooth with a rounded shoulder, and the tile can be four pixels
268
+ // wide without anyone knowing.
269
+ export const layerNormal = once(() => {
270
+ const W = 8, H = 512, LAYERS = 42;
271
+ const g = surface(W, H);
272
+ const img = new ImageData(W, H);
273
+ const d = img.data;
274
+ for (let y = 0; y < H; y++) {
275
+ const t = (y / H) * LAYERS;
276
+ const f = t - Math.floor(t);
277
+ // each layer is a squat bead: rounded on top, cut sharp underneath
278
+ const bead = Math.pow(Math.sin(f * Math.PI), .55);
279
+ const val = bead * 255;
280
+ for (let x = 0; x < W; x++) {
281
+ const i = (y * W + x) * 4;
282
+ d[i] = d[i + 1] = d[i + 2] = val;
283
+ d[i + 3] = 255;
284
+ }
285
+ }
286
+ g.putImageData(img, 0, 0);
287
+ return wrap(normalFromHeight(g, 1.1), 1, 8);
288
+ });
289
+
290
+ // ---- 4. CRAZING -------------------------------------------------------
291
+ // The hairline map that opens up in an old glaze. Cracks are GROOVES,
292
+ // so they are drawn dark into a light field, and every stroke is drawn
293
+ // nine times — the tile and its eight neighbours — which is the
294
+ // cheapest way to make a scattered pattern wrap when it has no
295
+ // periodicity of its own.
296
+ export const crazeNormal = once(() => {
297
+ const N = 512;
298
+ const g = surface(N, N);
299
+ g.fillStyle = '#fff';
300
+ g.fillRect(0, 0, N, N);
301
+ g.strokeStyle = 'rgba(0,0,0,.85)';
302
+ g.lineWidth = 1.6;
303
+ g.lineCap = 'round';
304
+
305
+ const rnd = mulberry32(0xC7A2);
306
+ // walk a crack: short segments turning a little each time, so it
307
+ // wanders like a split rather than a scratch
308
+ const cracks = [];
309
+ for (let i = 0; i < 34; i++) {
310
+ const pts = [[rnd() * N, rnd() * N]];
311
+ let a = rnd() * Math.PI * 2;
312
+ const steps = 5 + ((rnd() * 7) | 0);
313
+ for (let s = 0; s < steps; s++) {
314
+ a += (rnd() - .5) * 1.1;
315
+ const len = N * (.03 + rnd() * .07);
316
+ const [px, py] = pts[pts.length - 1];
317
+ pts.push([px + Math.cos(a) * len, py + Math.sin(a) * len]);
318
+ }
319
+ cracks.push(pts);
320
+ }
321
+
322
+ for (let ox = -1; ox <= 1; ox++) {
323
+ for (let oy = -1; oy <= 1; oy++) {
324
+ for (const pts of cracks) {
325
+ g.beginPath();
326
+ pts.forEach(([x, y], i) => (i ? g.lineTo : g.moveTo).call(g, x + ox * N, y + oy * N));
327
+ g.stroke();
328
+ }
329
+ }
330
+ }
331
+ soften(g, .9);
332
+ return wrap(normalFromHeight(g, 2.6), 2, 1);
333
+ });
334
+
335
+ // ---- THE PRINT SHOP ---------------------------------------------------
336
+ // A dressed torso can carry a PRINT — the screen-printed graphic real
337
+ // vinyl blanks get, and the one place a texture is the right tool for
338
+ // clothing: a painted hem has no thickness and reads as a mistake, but
339
+ // a star on the chest reads as print because it IS print.
340
+ //
341
+ // Unlike the tiles above, a print is not colour-free: the map carries
342
+ // the whole diffuse (cloth ground + motif ink) and the factory sets the
343
+ // material's colour to white, so a light motif can sit on dark cloth —
344
+ // a map that only multiplied the cloth colour could never lighten it.
345
+ // Cached by (motif, cloth, ink): the combinations on a sheet are few.
346
+ //
347
+ // The chest lands at u = .25 (the front of `SphereGeometry`'s
348
+ // parametrisation — +z is a quarter-turn in), v ≈ .55. `flipY` turns
349
+ // canvas y over, so that is canvas (W * .25, H * .45).
350
+
351
+ const PRINT_CACHE = new Map();
352
+ const N_PRINT = 256;
353
+
354
+ export const PRINT_MOTIFS = ['none', 'stripes', 'star', 'heart', 'spot', 'zig'];
355
+
356
+ export function clothPrint(motif, cloth, ink) {
357
+ const key = `${motif}:${cloth}:${ink}`;
358
+ if (PRINT_CACHE.has(key)) return PRINT_CACHE.get(key);
359
+
360
+ const g = surface(N_PRINT, N_PRINT);
361
+ g.fillStyle = cloth;
362
+ g.fillRect(0, 0, N_PRINT, N_PRINT);
363
+ g.fillStyle = ink;
364
+ g.strokeStyle = ink;
365
+
366
+ const cx = N_PRINT * .25, cy = N_PRINT * .45, s = N_PRINT * .16;
367
+ if (motif === 'stripes') {
368
+ // horizontal bands: periodic in u by construction, so no seam
369
+ for (let y = 0; y < 5; y++)
370
+ g.fillRect(0, N_PRINT * (.14 + y * .19), N_PRINT, N_PRINT * .085);
371
+ } else if (motif === 'zig') {
372
+ g.lineWidth = N_PRINT * .05;
373
+ g.lineJoin = 'round';
374
+ g.beginPath();
375
+ // one full period across the canvas, so the seam at u = 0 joins
376
+ for (let i = 0; i <= 16; i++)
377
+ g[i ? 'lineTo' : 'moveTo'](N_PRINT * i / 16, cy + (i % 2 ? -1 : 1) * N_PRINT * .05);
378
+ g.stroke();
379
+ } else if (motif === 'spot') {
380
+ g.beginPath();
381
+ g.arc(cx, cy, s * .8, 0, Math.PI * 2);
382
+ g.fill();
383
+ } else if (motif === 'star') {
384
+ g.beginPath();
385
+ for (let i = 0; i < 10; i++) {
386
+ const a = -Math.PI / 2 + i * Math.PI / 5;
387
+ const r = (i % 2 ? .45 : 1) * s;
388
+ g[i ? 'lineTo' : 'moveTo'](cx + Math.cos(a) * r, cy + Math.sin(a) * r);
389
+ }
390
+ g.closePath();
391
+ g.fill();
392
+ } else if (motif === 'heart') {
393
+ g.beginPath();
394
+ g.moveTo(cx, cy + s * .9);
395
+ g.bezierCurveTo(cx - s * 1.2, cy - s * .1, cx - s * .6, cy - s * 1.1, cx, cy - s * .35);
396
+ g.bezierCurveTo(cx + s * .6, cy - s * 1.1, cx + s * 1.2, cy - s * .1, cx, cy + s * .9);
397
+ g.fill();
398
+ }
399
+
400
+ const tex = new THREE.CanvasTexture(g.canvas);
401
+ tex.colorSpace = THREE.SRGBColorSpace;
402
+ tex.anisotropy = 8;
403
+ const out = { key, tex };
404
+ PRINT_CACHE.set(key, out);
405
+ return out;
406
+ }
@@ -0,0 +1,11 @@
1
+ export function mulberry32(seed: number): () => number;
2
+
3
+ export function makeRng(seed: number): {
4
+ next: () => number;
5
+ r: (a: number, b: number) => number;
6
+ ri: (a: number, b: number) => number;
7
+ pick: <T>(values: readonly T[]) => T;
8
+ chance: (probability: number) => boolean;
9
+ };
10
+
11
+ export function hashStr(value: string): number;
@@ -0,0 +1,27 @@
1
+ // Seeded randomness. Every character is fully reproducible from its seed:
2
+ // same seed -> same face, same clothes, same walk.
3
+ export function mulberry32(seed) {
4
+ return function () {
5
+ seed |= 0; seed = seed + 0x6D2B79F5 | 0;
6
+ let t = Math.imul(seed ^ seed >>> 15, 1 | seed);
7
+ t = t + Math.imul(t ^ t >>> 7, 61 | t) ^ t;
8
+ return ((t ^ t >>> 14) >>> 0) / 4294967296;
9
+ };
10
+ }
11
+
12
+ export function makeRng(seed) {
13
+ const f = mulberry32(seed);
14
+ return {
15
+ next: f,
16
+ r: (a, b) => a + f() * (b - a),
17
+ ri: (a, b) => Math.floor(a + f() * (b - a + 1)),
18
+ pick: arr => arr[Math.floor(f() * arr.length)],
19
+ chance: p => f() < p,
20
+ };
21
+ }
22
+
23
+ export function hashStr(s) {
24
+ let h = 2166136261;
25
+ for (let i = 0; i < s.length; i++) { h ^= s.charCodeAt(i); h = Math.imul(h, 16777619); }
26
+ return h >>> 0;
27
+ }