@prosopo/puzzle-assets 0.1.4 → 0.2.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 (61) hide show
  1. package/.turbo/turbo-build$colon$cjs.log +13 -15
  2. package/.turbo/turbo-build$colon$tsc.log +5 -8
  3. package/.turbo/turbo-build.log +15 -20
  4. package/CHANGELOG.md +71 -0
  5. package/LICENSE +201 -0
  6. package/dist/browser.d.ts +3 -2
  7. package/dist/browser.d.ts.map +1 -1
  8. package/dist/browser.js +5 -4
  9. package/dist/browser.js.map +1 -1
  10. package/dist/cjs/compose.cjs +117 -1
  11. package/dist/cjs/contrast.cjs +142 -0
  12. package/dist/cjs/index.cjs +24 -6
  13. package/dist/cjs/notch.cjs +115 -93
  14. package/dist/cjs/palette.cjs +36 -3
  15. package/dist/compose.d.ts +4 -0
  16. package/dist/compose.d.ts.map +1 -1
  17. package/dist/compose.js +117 -2
  18. package/dist/compose.js.map +1 -1
  19. package/dist/contrast.d.ts +9 -0
  20. package/dist/contrast.d.ts.map +1 -0
  21. package/dist/contrast.js +137 -0
  22. package/dist/contrast.js.map +1 -0
  23. package/dist/index.d.ts +5 -3
  24. package/dist/index.d.ts.map +1 -1
  25. package/dist/index.js +16 -7
  26. package/dist/index.js.map +1 -1
  27. package/dist/notch.d.ts.map +1 -1
  28. package/dist/notch.js +115 -93
  29. package/dist/notch.js.map +1 -1
  30. package/dist/palette.d.ts +1 -0
  31. package/dist/palette.d.ts.map +1 -1
  32. package/dist/palette.js +36 -4
  33. package/dist/palette.js.map +1 -1
  34. package/dist/prng.d.ts.map +1 -1
  35. package/dist/prng.js.map +1 -1
  36. package/dist/tests/pieceReadability.unit.test.d.ts +2 -0
  37. package/dist/tests/pieceReadability.unit.test.d.ts.map +1 -0
  38. package/dist/tests/pieceReadability.unit.test.js +320 -0
  39. package/dist/tests/pieceReadability.unit.test.js.map +1 -0
  40. package/dist/tests/renderReplay.unit.test.d.ts +2 -0
  41. package/dist/tests/renderReplay.unit.test.d.ts.map +1 -0
  42. package/dist/tests/renderReplay.unit.test.js +77 -0
  43. package/dist/tests/renderReplay.unit.test.js.map +1 -0
  44. package/dist/types.d.ts +1 -0
  45. package/dist/types.d.ts.map +1 -1
  46. package/package.json +59 -60
  47. package/src/background.ts +0 -189
  48. package/src/browser.ts +0 -100
  49. package/src/compose.ts +0 -210
  50. package/src/decoys.ts +0 -191
  51. package/src/encode.ts +0 -59
  52. package/src/index.ts +0 -137
  53. package/src/notch.ts +0 -223
  54. package/src/palette.ts +0 -110
  55. package/src/prng.ts +0 -95
  56. package/src/tests/puzzleAssets.unit.test.ts +0 -438
  57. package/src/types.ts +0 -93
  58. package/tsconfig.cjs.json +0 -14
  59. package/tsconfig.json +0 -19
  60. package/tsconfig.tsbuildinfo +0 -1
  61. package/tsconfig.types.json +0 -9
@@ -1,7 +1,4 @@
1
-
2
- > @prosopo/puzzle-assets@0.1.4 build:cjs
3
- > NODE_ENV=${NODE_ENV:-development}; vite build --config vite.cjs.config.ts --mode $NODE_ENV
4
-
1
+ $ NODE_ENV=${NODE_ENV:-development}; vite build --config vite.cjs.config.ts --mode $NODE_ENV
5
2
  ViteCommonJSConfig: .
6
3
  { tsConfigPaths: [] }
7
4
  { externals: [] }
@@ -9,17 +6,18 @@ ViteCommonJSConfig: .
9
6
  vite v8.1.5 building ssr environment for production...
10
7
  Bundle build started
11
8
  
12
- ✓ 9 modules transformed.
9
+ ✓ 10 modules transformed.
13
10
  rendering chunks...
14
11
  computing gzip size...
15
- dist/cjs/_virtual/_rolldown/runtime.cjs 1.00 kB │ gzip: 0.50 kB
16
- dist/cjs/prng.cjs 1.47 kB │ gzip: 0.75 kB
17
- dist/cjs/encode.cjs 1.91 kB │ gzip: 0.90 kB
18
- dist/cjs/palette.cjs 2.42 kB │ gzip: 1.07 kB
19
- dist/cjs/index.cjs 3.48 kB │ gzip: 1.26 kB
20
- dist/cjs/notch.cjs 4.21 kB │ gzip: 1.37 kB
21
- dist/cjs/background.cjs 4.30 kB │ gzip: 1.84 kB
22
- dist/cjs/decoys.cjs 4.54 kB │ gzip: 1.94 kB
23
- dist/cjs/compose.cjs 5.17 kB │ gzip: 2.00 kB
12
+ dist/cjs/_virtual/_rolldown/runtime.cjs 1.00 kB │ gzip: 0.50 kB
13
+ dist/cjs/prng.cjs 1.47 kB │ gzip: 0.75 kB
14
+ dist/cjs/encode.cjs 1.91 kB │ gzip: 0.90 kB
15
+ dist/cjs/palette.cjs 4.07 kB │ gzip: 1.81 kB
16
+ dist/cjs/contrast.cjs 4.18 kB │ gzip: 1.92 kB
17
+ dist/cjs/background.cjs 4.30 kB │ gzip: 1.84 kB
18
+ dist/cjs/index.cjs 4.44 kB │ gzip: 1.54 kB
19
+ dist/cjs/notch.cjs 4.46 kB │ gzip: 1.81 kB
20
+ dist/cjs/decoys.cjs 4.54 kB │ gzip: 1.94 kB
21
+ dist/cjs/compose.cjs 10.60 kB │ gzip: 4.03 kB
24
22
 
25
- ✓ built in 79ms
23
+ ✓ built in 53ms
@@ -1,14 +1,11 @@
1
-
2
- > @prosopo/puzzle-assets@0.1.4 build:tsc
3
- > tsc --build --verbose
4
-
5
- 1:14:42 PM - Projects in this build:
1
+ $ tsc --build --verbose
2
+ 1:37:11 PM - Projects in this build:
6
3
  * ../../dev/config/tsconfig.json
7
4
  * tsconfig.json
8
5
 
9
- 1:14:43 PM - Project '../../dev/config/tsconfig.json' is up to date because newest input '../../dev/config/src/webpack/webpack.config.ts' is older than output '../../dev/config/tsconfig.tsbuildinfo'
6
+ 1:37:12 PM - Project '../../dev/config/tsconfig.json' is up to date because newest input '../../dev/config/src/webpack/webpack.config.ts' is older than output '../../dev/config/tsconfig.tsbuildinfo'
10
7
 
11
- 1:14:43 PM - Project 'tsconfig.json' is out of date because output file 'tsconfig.tsbuildinfo' does not exist
8
+ 1:37:12 PM - Project 'tsconfig.json' is out of date because output file 'tsconfig.tsbuildinfo' does not exist
12
9
 
13
- 1:14:43 PM - Building project '/home/runner/work/captcha/captcha/packages/puzzle-assets/tsconfig.json'...
10
+ 1:37:12 PM - Building project '/home/runner/work/captcha/captcha/packages/puzzle-assets/tsconfig.json'...
14
11
 
@@ -1,11 +1,5 @@
1
-
2
- > @prosopo/puzzle-assets@0.1.4 build
3
- > npm run build:cross-env -- --mode ${NODE_ENV:-development}
4
-
5
-
6
- > @prosopo/puzzle-assets@0.1.4 build:cross-env
7
- > vite build --config vite.esm.config.ts --mode production
8
-
1
+ $ pnpm run build:cross-env --mode ${NODE_ENV:-development}
2
+ $ vite build --config vite.esm.config.ts --mode production
9
3
  ViteEsmConfig: .
10
4
  { tsConfigPaths: [] }
11
5
  { externals: [] }
@@ -13,18 +7,19 @@ ViteEsmConfig: .
13
7
  vite v8.1.5 building ssr environment for production...
14
8
  Bundle build started
15
9
  
16
- ✓ 10 modules transformed.
10
+ ✓ 11 modules transformed.
17
11
  rendering chunks...
18
12
  computing gzip size...
19
- dist/_virtual/_rolldown/runtime.js 0.04 kB │ gzip: 0.06 kB
20
- dist/prng.js 1.40 kB │ gzip: 0.73 kB
21
- dist/browser.js 1.63 kB │ gzip: 0.68 kB
22
- dist/encode.js 1.67 kB │ gzip: 0.82 kB
23
- dist/palette.js 2.39 kB │ gzip: 1.07 kB
24
- dist/index.js 2.84 kB │ gzip: 1.16 kB
25
- dist/notch.js 4.16 kB │ gzip: 1.36 kB
26
- dist/background.js 4.25 kB │ gzip: 1.83 kB
27
- dist/decoys.js 4.50 kB │ gzip: 1.93 kB
28
- dist/compose.js 5.14 kB │ gzip: 1.99 kB
13
+ dist/_virtual/_rolldown/runtime.js 0.04 kB │ gzip: 0.06 kB
14
+ dist/prng.js 1.40 kB │ gzip: 0.73 kB
15
+ dist/browser.js 1.66 kB │ gzip: 0.69 kB
16
+ dist/encode.js 1.67 kB │ gzip: 0.82 kB
17
+ dist/index.js 3.51 kB │ gzip: 1.41 kB
18
+ dist/palette.js 3.99 kB │ gzip: 1.79 kB
19
+ dist/contrast.js 4.03 kB │ gzip: 1.90 kB
20
+ dist/background.js 4.25 kB │ gzip: 1.83 kB
21
+ dist/notch.js 4.41 kB │ gzip: 1.81 kB
22
+ dist/decoys.js 4.50 kB │ gzip: 1.93 kB
23
+ dist/compose.js 10.51 kB │ gzip: 4.01 kB
29
24
 
30
- ✓ built in 47ms
25
+ ✓ built in 33ms
package/CHANGELOG.md CHANGED
@@ -1,5 +1,76 @@
1
1
  # @prosopo/puzzle-assets
2
2
 
3
+ ## 0.2.0
4
+ ### Minor Changes
5
+
6
+ - f961dab: Store everything needed to redraw a puzzle, so the portal can show the exact challenge a user was served.
7
+
8
+ Until now a puzzle record kept the target, the origin and the accuracy required (`tolerance`), and
9
+ nothing else. The things that decide what the puzzle actually looked like — how many decoy shapes were
10
+ scattered on it, how dark they were, how big the piece was, and which background was used — were
11
+ picked per challenge and then thrown away. So you could see that someone failed by 11px and had no way
12
+ to know whether they had been handed an easy puzzle or a nasty one.
13
+
14
+ Puzzle records now carry a `render` object with all of it: the difficulty ladder level, the piece size,
15
+ the frame geometry, the five render settings, and the two 128-bit seeds that regenerate the imagery.
16
+ Given those, the portal reproduces the background and the piece byte for byte.
17
+
18
+ To make that possible the renderer had to become reproducible. `renderPuzzle` and `createBackground`
19
+ now take an optional seed and report the one they used, instead of generating one internally and
20
+ discarding it; the background pool hands out each image together with its seed. Default behaviour is
21
+ unchanged — omit the seed and you still get a fresh random one.
22
+
23
+ The ladder level is new on the session too (`puzzleLevel`), because it was computed when the puzzle
24
+ was escalated and never written down anywhere. It is absent when the ladder did not choose the
25
+ puzzle — a site-configured puzzle or a traffic-filter override has no level.
26
+
27
+ The record is patched after the imagery is produced rather than written with the rest of the record,
28
+ because the piece size and the seeds only exist once the render has run, and the target has to be
29
+ durable before it is expressed in pixels. If that patch fails it is logged and ignored: losing a
30
+ diagnostic must not cost the user their solve. Records written before this change, and any challenge
31
+ whose render threw, simply have no `render`.
32
+
33
+ **The seeds are secret.** `puzzle-assets/src/prng.ts` explains why: they regenerate the clean
34
+ background, and a clean background can be diffed against the composite to read the target position
35
+ straight off. They were previously barred from leaving the provider entirely. They may now sit on the
36
+ captcha record, and nowhere else — not in a response, not in a log, not on the session. Anything that
37
+ exposes a puzzle record to a caller has to drop `render.backgroundSeed` and `render.renderSeed`, and
38
+ must not serve them at all while the challenge is still live. The comment in `prng.ts` now says so.
39
+
40
+ Test coverage. Three guards, each confirmed to fail when the thing it protects is broken:
41
+ `renderReplay.unit.test.ts` re-renders from the stored fields and asserts the bytes match, with one
42
+ case per field asserting that changing *only* that field changes the output — so a record missing a
43
+ field cannot pass; `puzzleRenderer.unit.test.ts` replays through the same entry points the portal would
44
+ use, which catches a background paired with the wrong seed; and `schemas.test.ts` asserts every
45
+ sub-field survives a strict-mode mongoose write, since a field absent from the schema is dropped
46
+ silently and would store nothing at all.
47
+
48
+ ### Patch Changes
49
+
50
+ - 6e2eb55: Make the puzzle piece look like a puzzle piece, sit apart from the background, and survive colour-blind vision.
51
+
52
+ Three complaints from the Twickets call, all about how the challenge looks.
53
+
54
+ **The silhouette is now always a jigsaw piece.** The generator had five unrelated shape families — jigsaw, circle, rounded rectangle, regular polygon, flower — so four pieces in five were not jigsaw-shaped at all, and a first-time user was as likely to be handed a heptagon as anything recognisable. There is now one grammar: a rounded body where each of the four edges independently gets a tab, a blank or nothing, with the knob's position along its edge, its radius and the corner rounding all varying.
55
+
56
+ Silhouette variety is deliberate anti-automation and it has not been given up — it has moved inside the grammar. 3^4 edge combinations before any of the continuous parameters, against five families with a handful of parameters each, so the space did not shrink; what changed is that every sample in it reads as a piece of a jigsaw. A sweep over 400 seeds finds more than 320 distinct coarse signatures, so there is nothing like a template to match against.
57
+
58
+ The piece is also scaled to fit its bounding box now, measured from the knobs actually drawn, rather than the body being inset by the worst case the parameters could produce. Insetting for the worst case shrinks every piece to make room for the largest tab that was never drawn. A tab reaching the border used to come out clipped flat, which looks like a manufacturing defect rather than a jigsaw tab.
59
+
60
+ **The piece is separated by a luminance step, not by hue.** It is cut from the same mesh the background is drawn from, so on an unlucky palette it was the same colour as whatever it was resting on and the only thing dividing them was a CSS drop-shadow. It now carries an outline whose brightness steps away from its own, in the direction chosen from the piece's own mean luminance — a dark piece gets a light rim, a light piece a dark one. A fixed direction vanishes at one end of the range, which is how a cue that is present in the code ends up absent on screen.
61
+
62
+ The size of the step is solved for per piece to reach a target contrast ratio, not set as a fixed byte offset: 40 units out of 255 is obvious on a mid grey and nearly invisible near white. Being a brightness step rather than a hue, it survives greyscale, a monochrome display, every dichromacy and a phone screen in sunlight — which the tests assert by measuring luminance only, and again after flattening the piece to grey.
63
+
64
+ **Palette generation now has a measured contrast floor.** The six palettes are analogous-hue families, which is what makes a mesh gradient look deliberate and is also the worst case for colour-blind vision: a band of neighbouring hues at one lightness is exactly what a dichromat collapses to a single colour. Nothing checked for that, so a palette could lose all its structure for 1 in 12 men and no test would notice.
65
+
66
+ A new `contrast` module does WCAG relative luminance on linearised sRGB and Viénot–Brettel–Mollon simulation of protanopia, deuteranopia and tritanopia. `drawPalette` now redraws while its widest pair falls below the floor under *any* of the four vision types, widening the lightness band on each retry rather than giving up after N tries — a floor that can be returned past is not a floor. The first attempts are unstretched, so an ordinary draw is exactly what it was before and only a failing palette gets touched.
67
+
68
+ The floor is on the palette's *range*, not on every pair: a gradient needs near-neighbours, and demanding contrast between them would produce stripes. What it rules out is a field that is uniformly one brightness.
69
+
70
+ **Tests sweep seeds rather than checking an example.** 45 tests, including 400-seed sweeps of the palette floor, the presence of tabs, silhouette variety, the bounding box, and the area covered; and 120-seed sweeps of the rim's luminance step, its direction, its survival in greyscale, and its survival under each dichromacy. A palette or parameter that is fine on the one seed somebody happened to look at and unreadable on a tenth of the rest is the failure that shipped, and a single-seed test cannot see it.
71
+
72
+ One thing the tests could not see, found by rendering the output and looking at it: a fixed 4px rim is a tidy outline on a large piece and most of the surface area of a 44px one — the default — so the whole piece read as a pale blob rather than as a piece with a lit edge. No measurement of rim-versus-core contrast catches that, because by that measure a piece which is entirely rim scores perfectly. The rim is now proportional to the piece size, and the tests read the band geometry from the renderer so the measurement cannot drift away from what was drawn.
73
+
3
74
  ## 0.1.4
4
75
  ### Patch Changes
5
76
 
package/LICENSE ADDED
@@ -0,0 +1,201 @@
1
+ Apache License
2
+ Version 2.0, January 2004
3
+ http://www.apache.org/licenses/
4
+
5
+ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
6
+
7
+ 1. Definitions.
8
+
9
+ "License" shall mean the terms and conditions for use, reproduction,
10
+ and distribution as defined by Sections 1 through 9 of this document.
11
+
12
+ "Licensor" shall mean the copyright owner or entity authorized by
13
+ the copyright owner that is granting the License.
14
+
15
+ "Legal Entity" shall mean the union of the acting entity and all
16
+ other entities that control, are controlled by, or are under common
17
+ control with that entity. For the purposes of this definition,
18
+ "control" means (i) the power, direct or indirect, to cause the
19
+ direction or management of such entity, whether by contract or
20
+ otherwise, or (ii) ownership of fifty percent (50%) or more of the
21
+ outstanding shares, or (iii) beneficial ownership of such entity.
22
+
23
+ "You" (or "Your") shall mean an individual or Legal Entity
24
+ exercising permissions granted by this License.
25
+
26
+ "Source" form shall mean the preferred form for making modifications,
27
+ including but not limited to software source code, documentation
28
+ source, and configuration files.
29
+
30
+ "Object" form shall mean any form resulting from mechanical
31
+ transformation or translation of a Source form, including but
32
+ not limited to compiled object code, generated documentation,
33
+ and conversions to other media types.
34
+
35
+ "Work" shall mean the work of authorship, whether in Source or
36
+ Object form, made available under the License, as indicated by a
37
+ copyright notice that is included in or attached to the work
38
+ (an example is provided in the Appendix below).
39
+
40
+ "Derivative Works" shall mean any work, whether in Source or Object
41
+ form, that is based on (or derived from) the Work and for which the
42
+ editorial revisions, annotations, elaborations, or other modifications
43
+ represent, as a whole, an original work of authorship. For the purposes
44
+ of this License, Derivative Works shall not include works that remain
45
+ separable from, or merely link (or bind by name) to the interfaces of,
46
+ the Work and Derivative Works thereof.
47
+
48
+ "Contribution" shall mean any work of authorship, including
49
+ the original version of the Work and any modifications or additions
50
+ to that Work or Derivative Works thereof, that is intentionally
51
+ submitted to Licensor for inclusion in the Work by the copyright owner
52
+ or by an individual or Legal Entity authorized to submit on behalf of
53
+ the copyright owner. For the purposes of this definition, "submitted"
54
+ means any form of electronic, verbal, or written communication sent
55
+ to the Licensor or its representatives, including but not limited to
56
+ communication on electronic mailing lists, source code control systems,
57
+ and issue tracking systems that are managed by, or on behalf of, the
58
+ Licensor for the purpose of discussing and improving the Work, but
59
+ excluding communication that is conspicuously marked or otherwise
60
+ designated in writing by the copyright owner as "Not a Contribution."
61
+
62
+ "Contributor" shall mean Licensor and any individual or Legal Entity
63
+ on behalf of whom a Contribution has been received by Licensor and
64
+ subsequently incorporated within the Work.
65
+
66
+ 2. Grant of Copyright License. Subject to the terms and conditions of
67
+ this License, each Contributor hereby grants to You a perpetual,
68
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
69
+ copyright license to reproduce, prepare Derivative Works of,
70
+ publicly display, publicly perform, sublicense, and distribute the
71
+ Work and such Derivative Works in Source or Object form.
72
+
73
+ 3. Grant of Patent License. Subject to the terms and conditions of
74
+ this License, each Contributor hereby grants to You a perpetual,
75
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
76
+ (except as stated in this section) patent license to make, have made,
77
+ use, offer to sell, sell, import, and otherwise transfer the Work,
78
+ where such license applies only to those patent claims licensable
79
+ by such Contributor that are necessarily infringed by their
80
+ Contribution(s) alone or by combination of their Contribution(s)
81
+ with the Work to which such Contribution(s) was submitted. If You
82
+ institute patent litigation against any entity (including a
83
+ cross-claim or counterclaim in a lawsuit) alleging that the Work
84
+ or a Contribution incorporated within the Work constitutes direct
85
+ or contributory patent infringement, then any patent licenses
86
+ granted to You under this License for that Work shall terminate
87
+ as of the date such litigation is filed.
88
+
89
+ 4. Redistribution. You may reproduce and distribute copies of the
90
+ Work or Derivative Works thereof in any medium, with or without
91
+ modifications, and in Source or Object form, provided that You
92
+ meet the following conditions:
93
+
94
+ (a) You must give any other recipients of the Work or
95
+ Derivative Works a copy of this License; and
96
+
97
+ (b) You must cause any modified files to carry prominent notices
98
+ stating that You changed the files; and
99
+
100
+ (c) You must retain, in the Source form of any Derivative Works
101
+ that You distribute, all copyright, patent, trademark, and
102
+ attribution notices from the Source form of the Work,
103
+ excluding those notices that do not pertain to any part of
104
+ the Derivative Works; and
105
+
106
+ (d) If the Work includes a "NOTICE" text file as part of its
107
+ distribution, then any Derivative Works that You distribute must
108
+ include a readable copy of the attribution notices contained
109
+ within such NOTICE file, excluding those notices that do not
110
+ pertain to any part of the Derivative Works, in at least one
111
+ of the following places: within a NOTICE text file distributed
112
+ as part of the Derivative Works; within the Source form or
113
+ documentation, if provided along with the Derivative Works; or,
114
+ within a display generated by the Derivative Works, if and
115
+ wherever such third-party notices normally appear. The contents
116
+ of the NOTICE file are for informational purposes only and
117
+ do not modify the License. You may add Your own attribution
118
+ notices within Derivative Works that You distribute, alongside
119
+ or as an addendum to the NOTICE text from the Work, provided
120
+ that such additional attribution notices cannot be construed
121
+ as modifying the License.
122
+
123
+ You may add Your own copyright statement to Your modifications and
124
+ may provide additional or different license terms and conditions
125
+ for use, reproduction, or distribution of Your modifications, or
126
+ for any such Derivative Works as a whole, provided Your use,
127
+ reproduction, and distribution of the Work otherwise complies with
128
+ the conditions stated in this License.
129
+
130
+ 5. Submission of Contributions. Unless You explicitly state otherwise,
131
+ any Contribution intentionally submitted for inclusion in the Work
132
+ by You to the Licensor shall be under the terms and conditions of
133
+ this License, without any additional terms or conditions.
134
+ Notwithstanding the above, nothing herein shall supersede or modify
135
+ the terms of any separate license agreement you may have executed
136
+ with Licensor regarding such Contributions.
137
+
138
+ 6. Trademarks. This License does not grant permission to use the trade
139
+ names, trademarks, service marks, or product names of the Licensor,
140
+ except as required for reasonable and customary use in describing the
141
+ origin of the Work and reproducing the content of the NOTICE file.
142
+
143
+ 7. Disclaimer of Warranty. Unless required by applicable law or
144
+ agreed to in writing, Licensor provides the Work (and each
145
+ Contributor provides its Contributions) on an "AS IS" BASIS,
146
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
147
+ implied, including, without limitation, any warranties or conditions
148
+ of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
149
+ PARTICULAR PURPOSE. You are solely responsible for determining the
150
+ appropriateness of using or redistributing the Work and assume any
151
+ risks associated with Your exercise of permissions under this License.
152
+
153
+ 8. Limitation of Liability. In no event and under no legal theory,
154
+ whether in tort (including negligence), contract, or otherwise,
155
+ unless required by applicable law (such as deliberate and grossly
156
+ negligent acts) or agreed to in writing, shall any Contributor be
157
+ liable to You for damages, including any direct, indirect, special,
158
+ incidental, or consequential damages of any character arising as a
159
+ result of this License or out of the use or inability to use the
160
+ Work (including but not limited to damages for loss of goodwill,
161
+ work stoppage, computer failure or malfunction, or any and all
162
+ other commercial damages or losses), even if such Contributor
163
+ has been advised of the possibility of such damages.
164
+
165
+ 9. Accepting Warranty or Additional Liability. While redistributing
166
+ the Work or Derivative Works thereof, You may choose to offer,
167
+ and charge a fee for, acceptance of support, warranty, indemnity,
168
+ or other liability obligations and/or rights consistent with this
169
+ License. However, in accepting such obligations, You may act only
170
+ on Your own behalf and on Your sole responsibility, not on behalf
171
+ of any other Contributor, and only if You agree to indemnify,
172
+ defend, and hold each Contributor harmless for any liability
173
+ incurred by, or claims asserted against, such Contributor by reason
174
+ of your accepting any such warranty or additional liability.
175
+
176
+ END OF TERMS AND CONDITIONS
177
+
178
+ APPENDIX: How to apply the Apache License to your work.
179
+
180
+ To apply the Apache License to your work, attach the following
181
+ boilerplate notice, with the fields enclosed by brackets "[]"
182
+ replaced with your own identifying information. (Don't include
183
+ the brackets!) The text should be enclosed in the appropriate
184
+ comment syntax for the file format. We also recommend that a
185
+ file or class name and description of purpose be included on the
186
+ same "printed page" as the copyright notice for easier
187
+ identification within third-party archives.
188
+
189
+ Copyright 2022 PROSOPO LIMITED
190
+
191
+ Licensed under the Apache License, Version 2.0 (the "License");
192
+ you may not use this file except in compliance with the License.
193
+ You may obtain a copy of the License at
194
+
195
+ http://www.apache.org/licenses/LICENSE-2.0
196
+
197
+ Unless required by applicable law or agreed to in writing, software
198
+ distributed under the License is distributed on an "AS IS" BASIS,
199
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
200
+ See the License for the specific language governing permissions and
201
+ limitations under the License.
package/dist/browser.d.ts CHANGED
@@ -9,7 +9,8 @@ export interface RenderedPuzzleRgba {
9
9
  background: RgbaImage;
10
10
  piece: RgbaImage;
11
11
  pieceSize: number;
12
+ seed: Buffer;
12
13
  }
13
- export declare const renderPuzzleToRgba: (background: RgbaImage, placement: NotchPlacement, geometry?: PuzzleGeometry, settings?: PuzzleRenderSettings) => RenderedPuzzleRgba;
14
- export declare const createBackground: (geometry?: PuzzleGeometry) => RgbaImage;
14
+ export declare const renderPuzzleToRgba: (background: RgbaImage, placement: NotchPlacement, geometry?: PuzzleGeometry, settings?: PuzzleRenderSettings, seed?: Buffer) => RenderedPuzzleRgba;
15
+ export declare const createBackground: (geometry?: PuzzleGeometry, seed?: Buffer) => RgbaImage;
15
16
  //# sourceMappingURL=browser.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"browser.d.ts","sourceRoot":"","sources":["../src/browser.ts"],"names":[],"mappings":"AAuBA,OAAO,KAAK,EACX,cAAc,EACd,cAAc,EACd,oBAAoB,EACpB,SAAS,EACT,MAAM,YAAY,CAAC;AAEpB,OAAO,EAAE,UAAU,EAAE,UAAU,EAAE,UAAU,EAAE,MAAM,WAAW,CAAC;AAC/D,OAAO,EAAE,kBAAkB,EAAE,MAAM,iBAAiB,CAAC;AACrD,OAAO,EAAE,WAAW,EAAE,eAAe,EAAE,MAAM,aAAa,CAAC;AAC3D,YAAY,EACX,cAAc,EACd,cAAc,EACd,oBAAoB,EACpB,cAAc,EACd,SAAS,GACT,MAAM,YAAY,CAAC;AAEpB,eAAO,MAAM,gBAAgB,EAAE,cAI9B,CAAC;AAEF,eAAO,MAAM,uBAAuB,EAAE,oBAMrC,CAAC;AAEF,MAAM,WAAW,kBAAkB;IAClC,UAAU,EAAE,SAAS,CAAC;IACtB,KAAK,EAAE,SAAS,CAAC;IACjB,SAAS,EAAE,MAAM,CAAC;CAClB;AAOD,eAAO,MAAM,kBAAkB,eAClB,SAAS,aACV,cAAc,aACf,cAAc,aACd,oBAAoB,KAC5B,kBAuBF,CAAC;AAEF,eAAO,MAAM,gBAAgB,cAClB,cAAc,KACtB,SAC2E,CAAC"}
1
+ {"version":3,"file":"browser.d.ts","sourceRoot":"","sources":["../src/browser.ts"],"names":[],"mappings":"AAuBA,OAAO,KAAK,EACX,cAAc,EACd,cAAc,EACd,oBAAoB,EACpB,SAAS,EACT,MAAM,YAAY,CAAC;AAEpB,OAAO,EAAE,UAAU,EAAE,UAAU,EAAE,UAAU,EAAE,MAAM,WAAW,CAAC;AAC/D,OAAO,EAAE,kBAAkB,EAAE,MAAM,iBAAiB,CAAC;AACrD,OAAO,EAAE,WAAW,EAAE,eAAe,EAAE,MAAM,aAAa,CAAC;AAC3D,YAAY,EACX,cAAc,EACd,cAAc,EACd,oBAAoB,EACpB,cAAc,EACd,SAAS,GACT,MAAM,YAAY,CAAC;AAEpB,eAAO,MAAM,gBAAgB,EAAE,cAI9B,CAAC;AAEF,eAAO,MAAM,uBAAuB,EAAE,oBAMrC,CAAC;AAEF,MAAM,WAAW,kBAAkB;IAClC,UAAU,EAAE,SAAS,CAAC;IACtB,KAAK,EAAE,SAAS,CAAC;IACjB,SAAS,EAAE,MAAM,CAAC;IAElB,IAAI,EAAE,MAAM,CAAC;CACb;AAOD,eAAO,MAAM,kBAAkB,eAClB,SAAS,aACV,cAAc,aACf,cAAc,aACd,oBAAoB,SACxB,MAAM,KACV,kBAuBF,CAAC;AAEF,eAAO,MAAM,gBAAgB,cAClB,cAAc,SAClB,MAAM,KACV,SACmE,CAAC"}
package/dist/browser.js CHANGED
@@ -22,17 +22,18 @@ var DEFAULT_RENDER_SETTINGS = {
22
22
  * than WebP-encoded buffers, so browser consumers can `putImageData` onto
23
23
  * a canvas without needing sharp.
24
24
  */
25
- var renderPuzzleToRgba = (background, placement, geometry = DEFAULT_GEOMETRY, settings = DEFAULT_RENDER_SETTINGS) => {
26
- const prng = createPrng(createSeed());
25
+ var renderPuzzleToRgba = (background, placement, geometry = DEFAULT_GEOMETRY, settings = DEFAULT_RENDER_SETTINGS, seed = createSeed()) => {
26
+ const prng = createPrng(seed);
27
27
  const pieceSize = geometry.pieceSize ?? geometry.notchSize;
28
28
  paintDecoys(background, prng, settings.decoyCount, pieceSize, placement, settings.decoyEdgeDarkness, settings.decoyBodyBrightness, settings.decoyHoleDarken);
29
29
  const { background: cut, piece } = cutNotch(prng, background, createNotchShape(prng, pieceSize), pieceSize, placement, settings.holeDarken);
30
30
  return {
31
31
  background: cut,
32
32
  piece,
33
- pieceSize
33
+ pieceSize,
34
+ seed
34
35
  };
35
36
  };
36
- var createBackground = (geometry = DEFAULT_GEOMETRY) => generateBackground(createPrng(createSeed()), geometry.width, geometry.height);
37
+ var createBackground = (geometry = DEFAULT_GEOMETRY, seed = createSeed()) => generateBackground(createPrng(seed), geometry.width, geometry.height);
37
38
  //#endregion
38
39
  export { DEFAULT_GEOMETRY, DEFAULT_RENDER_SETTINGS, SEED_BYTES, createBackground, createPrng, createSeed, generateBackground, paintDecoyPiece, paintDecoys, renderPuzzleToRgba };
@@ -1 +1 @@
1
- {"version":3,"file":"browser.js","sourceRoot":"","sources":["../src/browser.ts"],"names":[],"mappings":"AAkBA,OAAO,EAAE,kBAAkB,EAAE,MAAM,iBAAiB,CAAC;AACrD,OAAO,EAAE,QAAQ,EAAE,MAAM,cAAc,CAAC;AACxC,OAAO,EAAE,WAAW,EAAE,MAAM,aAAa,CAAC;AAC1C,OAAO,EAAE,gBAAgB,EAAE,MAAM,YAAY,CAAC;AAC9C,OAAO,EAAE,UAAU,EAAE,UAAU,EAAE,MAAM,WAAW,CAAC;AAQnD,OAAO,EAAE,UAAU,EAAE,UAAU,EAAE,UAAU,EAAE,MAAM,WAAW,CAAC;AAC/D,OAAO,EAAE,kBAAkB,EAAE,MAAM,iBAAiB,CAAC;AACrD,OAAO,EAAE,WAAW,EAAE,eAAe,EAAE,MAAM,aAAa,CAAC;AAS3D,MAAM,CAAC,MAAM,gBAAgB,GAAmB;IAC/C,KAAK,EAAE,GAAG;IACV,MAAM,EAAE,GAAG;IACX,SAAS,EAAE,EAAE;CACb,CAAC;AAEF,MAAM,CAAC,MAAM,uBAAuB,GAAyB;IAC5D,UAAU,EAAE,CAAC;IACb,iBAAiB,EAAE,EAAE;IACrB,mBAAmB,EAAE,CAAC;IACtB,UAAU,EAAE,IAAI;IAChB,eAAe,EAAE,GAAG;CACpB,CAAC;AAaF,MAAM,CAAC,MAAM,kBAAkB,GAAG,CACjC,UAAqB,EACrB,SAAyB,EACzB,WAA2B,gBAAgB,EAC3C,WAAiC,uBAAuB,EACnC,EAAE;IACvB,MAAM,IAAI,GAAG,UAAU,CAAC,UAAU,EAAE,CAAC,CAAC;IACtC,MAAM,SAAS,GAAG,QAAQ,CAAC,SAAS,IAAI,QAAQ,CAAC,SAAS,CAAC;IAC3D,WAAW,CACV,UAAU,EACV,IAAI,EACJ,QAAQ,CAAC,UAAU,EACnB,SAAS,EACT,SAAS,EACT,QAAQ,CAAC,iBAAiB,EAC1B,QAAQ,CAAC,mBAAmB,EAC5B,QAAQ,CAAC,eAAe,CACxB,CAAC;IACF,MAAM,KAAK,GAAG,gBAAgB,CAAC,IAAI,EAAE,SAAS,CAAC,CAAC;IAChD,MAAM,EAAE,UAAU,EAAE,GAAG,EAAE,KAAK,EAAE,GAAG,QAAQ,CAC1C,IAAI,EACJ,UAAU,EACV,KAAK,EACL,SAAS,EACT,SAAS,EACT,QAAQ,CAAC,UAAU,CACnB,CAAC;IACF,OAAO,EAAE,UAAU,EAAE,GAAG,EAAE,KAAK,EAAE,SAAS,EAAE,CAAC;AAC9C,CAAC,CAAC;AAEF,MAAM,CAAC,MAAM,gBAAgB,GAAG,CAC/B,WAA2B,gBAAgB,EAC/B,EAAE,CACd,kBAAkB,CAAC,UAAU,CAAC,UAAU,EAAE,CAAC,EAAE,QAAQ,CAAC,KAAK,EAAE,QAAQ,CAAC,MAAM,CAAC,CAAC"}
1
+ {"version":3,"file":"browser.js","sourceRoot":"","sources":["../src/browser.ts"],"names":[],"mappings":"AAkBA,OAAO,EAAE,kBAAkB,EAAE,MAAM,iBAAiB,CAAC;AACrD,OAAO,EAAE,QAAQ,EAAE,MAAM,cAAc,CAAC;AACxC,OAAO,EAAE,WAAW,EAAE,MAAM,aAAa,CAAC;AAC1C,OAAO,EAAE,gBAAgB,EAAE,MAAM,YAAY,CAAC;AAC9C,OAAO,EAAE,UAAU,EAAE,UAAU,EAAE,MAAM,WAAW,CAAC;AAQnD,OAAO,EAAE,UAAU,EAAE,UAAU,EAAE,UAAU,EAAE,MAAM,WAAW,CAAC;AAC/D,OAAO,EAAE,kBAAkB,EAAE,MAAM,iBAAiB,CAAC;AACrD,OAAO,EAAE,WAAW,EAAE,eAAe,EAAE,MAAM,aAAa,CAAC;AAS3D,MAAM,CAAC,MAAM,gBAAgB,GAAmB;IAC/C,KAAK,EAAE,GAAG;IACV,MAAM,EAAE,GAAG;IACX,SAAS,EAAE,EAAE;CACb,CAAC;AAEF,MAAM,CAAC,MAAM,uBAAuB,GAAyB;IAC5D,UAAU,EAAE,CAAC;IACb,iBAAiB,EAAE,EAAE;IACrB,mBAAmB,EAAE,CAAC;IACtB,UAAU,EAAE,IAAI;IAChB,eAAe,EAAE,GAAG;CACpB,CAAC;AAeF,MAAM,CAAC,MAAM,kBAAkB,GAAG,CACjC,UAAqB,EACrB,SAAyB,EACzB,WAA2B,gBAAgB,EAC3C,WAAiC,uBAAuB,EACxD,OAAe,UAAU,EAAE,EACN,EAAE;IACvB,MAAM,IAAI,GAAG,UAAU,CAAC,IAAI,CAAC,CAAC;IAC9B,MAAM,SAAS,GAAG,QAAQ,CAAC,SAAS,IAAI,QAAQ,CAAC,SAAS,CAAC;IAC3D,WAAW,CACV,UAAU,EACV,IAAI,EACJ,QAAQ,CAAC,UAAU,EACnB,SAAS,EACT,SAAS,EACT,QAAQ,CAAC,iBAAiB,EAC1B,QAAQ,CAAC,mBAAmB,EAC5B,QAAQ,CAAC,eAAe,CACxB,CAAC;IACF,MAAM,KAAK,GAAG,gBAAgB,CAAC,IAAI,EAAE,SAAS,CAAC,CAAC;IAChD,MAAM,EAAE,UAAU,EAAE,GAAG,EAAE,KAAK,EAAE,GAAG,QAAQ,CAC1C,IAAI,EACJ,UAAU,EACV,KAAK,EACL,SAAS,EACT,SAAS,EACT,QAAQ,CAAC,UAAU,CACnB,CAAC;IACF,OAAO,EAAE,UAAU,EAAE,GAAG,EAAE,KAAK,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC;AACpD,CAAC,CAAC;AAEF,MAAM,CAAC,MAAM,gBAAgB,GAAG,CAC/B,WAA2B,gBAAgB,EAC3C,OAAe,UAAU,EAAE,EACf,EAAE,CACd,kBAAkB,CAAC,UAAU,CAAC,IAAI,CAAC,EAAE,QAAQ,CAAC,KAAK,EAAE,QAAQ,CAAC,MAAM,CAAC,CAAC"}
@@ -1,3 +1,4 @@
1
+ const require_contrast = require("./contrast.cjs");
1
2
  const require_notch = require("./notch.cjs");
2
3
  //#region src/compose.ts
3
4
  /**
@@ -39,6 +40,74 @@ var HOLE_INNER_SHADOW = .45;
39
40
  * against the darkened cutout it drops into.
40
41
  */
41
42
  var PIECE_EDGE_LIGHT = 8;
43
+ /**
44
+ * The piece's outline, measured inwards from the silhouette, as a fraction of
45
+ * the piece's size.
46
+ *
47
+ * Full strength out to `PLATEAU`, then tapering to nothing by `WIDTH`. A rim that
48
+ * tapers from the very first pixel has its strongest values exactly where
49
+ * anti-aliasing makes the pixels partly transparent, so the part of it that is
50
+ * fully opaque is only ever half strength — the cue measures far weaker than the
51
+ * number configuring it suggests. The plateau puts the full step on pixels that
52
+ * are actually opaque, and the taper then stops it ending in a hard line.
53
+ *
54
+ * PROPORTIONAL, NOT A FIXED PIXEL COUNT. A 4px band is a tidy outline on a
55
+ * 120px piece and most of the surface area of a 44px one — the default
56
+ * `notchSize` — which made the whole piece read as a pale blob rather than as a
57
+ * piece with a lit edge. Caught by rendering it and looking; no measurement of
58
+ * rim-versus-core contrast can see it, because by that measure a piece that is
59
+ * entirely rim scores perfectly.
60
+ */
61
+ var PIECE_RIM_PLATEAU_RATIO = .03;
62
+ var PIECE_RIM_WIDTH_RATIO = .065;
63
+ /** Floors, so the rim does not vanish on a very small piece. */
64
+ var PIECE_RIM_MIN_PLATEAU = 1;
65
+ var PIECE_RIM_MIN_WIDTH = 2.2;
66
+ /** Rim geometry for a piece of `size` px. Exported so tests measure the band the renderer actually drew. */
67
+ var pieceRimGeometry = (size) => {
68
+ const plateau = Math.max(PIECE_RIM_MIN_PLATEAU, size * PIECE_RIM_PLATEAU_RATIO);
69
+ return {
70
+ plateau,
71
+ width: Math.max(PIECE_RIM_MIN_WIDTH, size * PIECE_RIM_WIDTH_RATIO, plateau + 1)
72
+ };
73
+ };
74
+ /**
75
+ * The WCAG contrast ratio the rim aims to reach against the piece's own body.
76
+ *
77
+ * This is the separation cue, and it is deliberately not a hue. The piece is cut
78
+ * from the same mesh the background is drawn from, so on an unlucky palette it
79
+ * was the same colour as whatever it was resting on and the only thing dividing
80
+ * them was a CSS drop-shadow. A luminance step survives what hue does not:
81
+ * greyscale, a monochrome display, every dichromacy, and a phone screen in
82
+ * sunlight.
83
+ *
84
+ * Expressed as a ratio rather than as a fixed byte offset because a fixed offset
85
+ * is not a fixed cue: 40 units out of 255 is obvious on a mid grey and almost
86
+ * invisible near white, where the same step is a much smaller fraction of the
87
+ * luminance. The required offset is solved for per piece from the luminance it
88
+ * actually has.
89
+ *
90
+ * 1.5 is comfortably visible without reading as a drawn border. The tests assert
91
+ * a lower bound than this, because the taper and the per-pixel noise cost a
92
+ * little of it.
93
+ */
94
+ var PIECE_RIM_TARGET_CONTRAST = 1.5;
95
+ /**
96
+ * Cap on the rim offset, in 0-255 units. Past roughly this the rim stops reading
97
+ * as a lit or shaded edge and starts reading as a stroke drawn around a sticker,
98
+ * which is the look this is trying to avoid.
99
+ */
100
+ var PIECE_RIM_MAX_STEP = 72;
101
+ /**
102
+ * Above this relative luminance the piece is treated as light, and gets a dark
103
+ * rim rather than a light one.
104
+ *
105
+ * Picking the direction from the piece's own mean rather than fixing it is the
106
+ * whole point: a fixed light rim vanishes on a pale background and a fixed dark
107
+ * one vanishes on a dark background, which is how a cue that is present in the
108
+ * code ends up absent on screen.
109
+ */
110
+ var PIECE_RIM_DIRECTION_THRESHOLD = .4;
42
111
  var clamp255 = (v) => v < 0 ? 0 : v > 255 ? 255 : v;
43
112
  /**
44
113
  * Sample the background at `(x, y)`. Returns `null` when the coordinate
@@ -56,6 +125,49 @@ var sampleBackground = (image, x, y) => {
56
125
  ];
57
126
  };
58
127
  /**
128
+ * Rim strength in [0, 1] for a signed distance: 1 on the plateau, tapering to 0
129
+ * at the full width inside the silhouette, 0 beyond it.
130
+ */
131
+ var rimProfile = (d, plateau, width) => {
132
+ if (d <= -width) return 0;
133
+ if (d >= -plateau) return 1;
134
+ return (d + width) / (width - plateau);
135
+ };
136
+ /** Approximate sRGB byte for a relative luminance. */
137
+ var byteForLuminance = (luminance) => 255 * Math.max(0, Math.min(1, luminance)) ** (1 / 2.2);
138
+ /**
139
+ * The signed offset, in 0-255 units, to apply at the piece's rim.
140
+ *
141
+ * Averages the luminance of the pixels the piece will be cut from, picks the
142
+ * direction that steps *away* from it, then solves for the offset that reaches
143
+ * [`PIECE_RIM_TARGET_CONTRAST`] against it. A dark piece gets a light rim and a
144
+ * light piece a dark one, so the cue is present whatever the palette produced —
145
+ * which neither a fixed direction nor a fixed magnitude can promise.
146
+ */
147
+ var pieceRimDelta = (background, shape, size, left, top) => {
148
+ let total = 0;
149
+ let counted = 0;
150
+ for (let ly = 0; ly < size; ly += 4) for (let lx = 0; lx < size; lx += 4) {
151
+ if (require_notch.coverageFromDistance(shape.distance(lx, ly)) <= 0) continue;
152
+ const sample = sampleBackground(background, left + lx, top + ly);
153
+ if (sample === null) continue;
154
+ total += require_contrast.relativeLuminance({
155
+ r: sample[0],
156
+ g: sample[1],
157
+ b: sample[2]
158
+ });
159
+ counted++;
160
+ }
161
+ if (counted === 0) return PIECE_RIM_MAX_STEP;
162
+ const pieceLuminance = total / counted;
163
+ const lighten = pieceLuminance <= PIECE_RIM_DIRECTION_THRESHOLD;
164
+ const delta = byteForLuminance(lighten ? PIECE_RIM_TARGET_CONTRAST * (pieceLuminance + .05) - .05 : (pieceLuminance + .05) / PIECE_RIM_TARGET_CONTRAST - .05) - byteForLuminance(pieceLuminance);
165
+ const capped = Math.max(-PIECE_RIM_MAX_STEP, Math.min(PIECE_RIM_MAX_STEP, delta));
166
+ const floor = 24;
167
+ if (Math.abs(capped) < floor) return lighten ? floor : -24;
168
+ return capped;
169
+ };
170
+ /**
59
171
  * Cut the notch out of `background` and produce the matching piece.
60
172
  *
61
173
  * `background` is mutated in place — it is a freshly generated, single-use
@@ -67,6 +179,8 @@ var cutNotch = (prng, background, shape, size, placement, holeDarken) => {
67
179
  const top = Math.round(placement.targetY - half);
68
180
  const pieceData = Buffer.alloc(size * size * 4);
69
181
  const brightness = prng.range(PIECE_BRIGHTNESS_RANGE[0], PIECE_BRIGHTNESS_RANGE[1]);
182
+ const rimStep = pieceRimDelta(background, shape, size, left, top);
183
+ const rim = pieceRimGeometry(size);
70
184
  for (let ly = 0; ly < size; ly++) for (let lx = 0; lx < size; lx++) {
71
185
  const d = shape.distance(lx, ly);
72
186
  const coverage = require_notch.coverageFromDistance(d);
@@ -82,8 +196,9 @@ var cutNotch = (prng, background, shape, size, placement, holeDarken) => {
82
196
  }
83
197
  const [r, g, b] = sample;
84
198
  const edgeLight = d > -2 ? PIECE_EDGE_LIGHT * (1 + d / 2) : 0;
199
+ const rimDelta = rimStep * rimProfile(d, rim.plateau, rim.width);
85
200
  const noise = (prng.next() - .5) * 2 * PIECE_NOISE;
86
- const delta = brightness + edgeLight + noise;
201
+ const delta = brightness + edgeLight + rimDelta + noise;
87
202
  pieceData[pi] = clamp255(Math.round(r + delta));
88
203
  pieceData[pi + 1] = clamp255(Math.round(g + delta));
89
204
  pieceData[pi + 2] = clamp255(Math.round(b + delta));
@@ -124,3 +239,4 @@ var cutNotch = (prng, background, shape, size, placement, holeDarken) => {
124
239
  };
125
240
  //#endregion
126
241
  exports.cutNotch = cutNotch;
242
+ exports.pieceRimGeometry = pieceRimGeometry;