quadqr-js 1.4.2 → 1.5.2

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/docs/NODE.md CHANGED
@@ -1,159 +1,159 @@
1
- # Node.js
2
-
3
- QuadQR uses the same matrix codec and scanner core in Node.js and the browser. The `quadqr-js/node` entry adds Node-specific PNG, file, and buffer helpers.
4
-
5
- ## Requirements
6
-
7
- Node.js 20.19 or newer.
8
-
9
- ## ESM
10
-
11
- ```js
12
- import { encodeText } from "quadqr-js";
13
- import { savePNG, saveSVG, scanFile } from "quadqr-js/node";
14
- ```
15
-
16
- ## CommonJS
17
-
18
- ```js
19
- const QuadQR = require("quadqr-js");
20
- const QuadQRNode = require("quadqr-js/node");
21
- ```
22
-
23
- ## Generate a PNG
24
-
25
- ```js
26
- const code = QuadQR.encodeText("Server generated");
27
-
28
- await QuadQRNode.savePNG(code, "output.png", {
29
- imageSize: 720,
30
- quietZone: 4
31
- });
32
- ```
33
-
34
- ## Generate an SVG
35
-
36
- ```js
37
- await QuadQRNode.saveSVG(code, "output.svg", {
38
- imageSize: 720,
39
- quietZone: 4,
40
- style: "classic"
41
- });
42
- ```
43
-
44
- `imageSize` is the exact PNG/SVG output dimension in pixels. If you omit both `imageSize` and `moduleSize`, rendering defaults to 720 × 720 px. `moduleSize` remains available for legacy pixels-per-module sizing.
45
-
46
- Use `toSVG()` when you need the SVG string in memory. SVG logo sources can be URL/data URL strings. For PNG generation with a logo through `renderToImageData()`, pass decoded ImageData-like RGBA pixels as the logo source.
47
-
48
- Use `toPNG()` when you need an in-memory `Buffer`:
49
-
50
- ```js
51
- const png = QuadQRNode.toPNG(code);
52
- ```
53
-
54
- This works well for HTTP responses, object storage, attachments, and other buffer-based workflows.
55
-
56
- ## Scan a PNG file
57
-
58
- ```js
59
- const result = await QuadQRNode.scanFile("output.png");
60
- console.log(result.text);
61
- ```
62
-
63
- Or scan a buffer:
64
-
65
- ```js
66
- const result = await QuadQRNode.scanBuffer(pngBuffer);
67
- ```
68
-
69
- PNG generation and decoding do not require external native dependencies.
70
-
71
- ## Server-side compression
72
-
73
- Compression uses the same synchronous JavaScript codec on Node.js as it does in the browser. No DOM API, browser `CompressionStream`, or Node `zlib` call is required by the QuadQR payload codec.
74
-
75
- ```js
76
- import { encodeText, decodeMatrix } from "quadqr-js";
77
-
78
- const code = encodeText("hello ".repeat(1000), {
79
- compression: "auto"
80
- });
81
-
82
- const smallest = encodeText("structured payload ".repeat(500), {
83
- compression: "smart"
84
- });
85
-
86
- const decoded = decodeMatrix(code.matrix);
87
- console.log(decoded.compression); // usually "brotli" for repetitive data
88
- console.log(decoded.text);
89
- ```
90
-
91
- `compression: "auto"` performs one balanced comparison using LZ level 6, DEFLATE level 6, and Brotli quality 6. `compression: "smart"` is the opt-in CPU-heavy mode and escalates to stronger levels only when a smaller QuadQR version is realistically reachable. Explicit `lz` and `deflate` accept `compressionLevel: 1..9` (default 6), while explicit `brotli` accepts `compressionLevel: 0..11` (default 11). `lzLevel`, `deflateLevel`, and `brotliQuality` are available as algorithm-specific aliases.
92
-
93
- ## Other image formats
94
-
95
- For JPEG, WebP, or AVIF input, the Node adapter can use `sharp` when the consuming application has it installed:
96
-
97
- ```bash
98
- npm install sharp
99
- ```
100
-
101
- If `sharp` is not installed, decode the image to RGBA in your own image pipeline and pass the pixels to `scanImageData()`.
102
-
103
- ## Secure password workflow
104
-
105
- ```js
106
- import {
107
- decryptDecoded,
108
- encodeSecureText
109
- } from "quadqr-js";
110
- import {
111
- savePNG,
112
- scanFile
113
- } from "quadqr-js/node";
114
-
115
- const code = await encodeSecureText("private", {
116
- security: {
117
- mode: "password",
118
- password: process.env.QUADQR_PASSWORD
119
- }
120
- });
121
-
122
- await savePNG(code, "secure.png");
123
-
124
- const locked = await scanFile("secure.png");
125
-
126
- const unlocked = await decryptDecoded(locked, {
127
- password: process.env.QUADQR_PASSWORD
128
- });
129
-
130
- console.log(unlocked.text);
131
- ```
132
-
133
- ## Secure raw-key workflow
134
-
135
- ```js
136
- import {
137
- decryptDecoded,
138
- encodeSecureText,
139
- generateRaw256Key
140
- } from "quadqr-js";
141
- import { toPNG, scanBuffer } from "quadqr-js/node";
142
-
143
- const key = generateRaw256Key();
144
-
145
- const code = await encodeSecureText("machine secret", {
146
- security: {
147
- mode: "raw-key",
148
- key
149
- }
150
- });
151
-
152
- const png = toPNG(code);
153
- const locked = await scanBuffer(png);
154
- const unlocked = await decryptDecoded(locked, { key });
155
-
156
- console.log(unlocked.text);
157
- ```
158
-
159
- Keep long-lived application keys in an appropriate secret-management or platform key-storage system.
1
+ # Node.js
2
+
3
+ QuadQR uses the same matrix codec and scanner core in Node.js and the browser. The `quadqr-js/node` entry adds Node-specific PNG, file, and buffer helpers.
4
+
5
+ ## Requirements
6
+
7
+ Node.js 20.19 or newer.
8
+
9
+ ## ESM
10
+
11
+ ```js
12
+ import { encodeText } from "quadqr-js";
13
+ import { savePNG, saveSVG, scanFile } from "quadqr-js/node";
14
+ ```
15
+
16
+ ## CommonJS
17
+
18
+ ```js
19
+ const QuadQR = require("quadqr-js");
20
+ const QuadQRNode = require("quadqr-js/node");
21
+ ```
22
+
23
+ ## Generate a PNG
24
+
25
+ ```js
26
+ const code = QuadQR.encodeText("Server generated");
27
+
28
+ await QuadQRNode.savePNG(code, "output.png", {
29
+ imageSize: 720,
30
+ quietZone: 4
31
+ });
32
+ ```
33
+
34
+ ## Generate an SVG
35
+
36
+ ```js
37
+ await QuadQRNode.saveSVG(code, "output.svg", {
38
+ imageSize: 720,
39
+ quietZone: 4,
40
+ style: "classic"
41
+ });
42
+ ```
43
+
44
+ `imageSize` is the exact PNG/SVG output dimension in pixels. If you omit both `imageSize` and `moduleSize`, rendering defaults to 720 × 720 px. `moduleSize` remains available for legacy pixels-per-module sizing.
45
+
46
+ Use `toSVG()` when you need the SVG string in memory. SVG logo sources can be URL/data URL strings. For PNG generation with a logo through `renderToImageData()`, pass decoded ImageData-like RGBA pixels as the logo source.
47
+
48
+ Use `toPNG()` when you need an in-memory `Buffer`:
49
+
50
+ ```js
51
+ const png = QuadQRNode.toPNG(code);
52
+ ```
53
+
54
+ This works well for HTTP responses, object storage, attachments, and other buffer-based workflows.
55
+
56
+ ## Scan a PNG file
57
+
58
+ ```js
59
+ const result = await QuadQRNode.scanFile("output.png");
60
+ console.log(result.text);
61
+ ```
62
+
63
+ Or scan a buffer:
64
+
65
+ ```js
66
+ const result = await QuadQRNode.scanBuffer(pngBuffer);
67
+ ```
68
+
69
+ PNG generation and decoding do not require external native dependencies.
70
+
71
+ ## Server-side compression
72
+
73
+ Compression uses the same synchronous JavaScript codec on Node.js as it does in the browser. No DOM API, browser `CompressionStream`, or Node `zlib` call is required by the QuadQR payload codec.
74
+
75
+ ```js
76
+ import { encodeText, decodeMatrix } from "quadqr-js";
77
+
78
+ const code = encodeText("hello ".repeat(1000), {
79
+ compression: "auto"
80
+ });
81
+
82
+ const smallest = encodeText("structured payload ".repeat(500), {
83
+ compression: "smart"
84
+ });
85
+
86
+ const decoded = decodeMatrix(code.matrix);
87
+ console.log(decoded.compression); // usually "brotli" for repetitive data
88
+ console.log(decoded.text);
89
+ ```
90
+
91
+ `compression: "auto"` performs one balanced comparison using LZ level 6, DEFLATE level 6, and Brotli quality 6. `compression: "smart"` is the opt-in CPU-heavy mode and escalates to stronger levels only when a smaller QuadQR version is realistically reachable. Explicit `lz` and `deflate` accept `compressionLevel: 1..9` (default 6), while explicit `brotli` accepts `compressionLevel: 0..11` (default 11). `lzLevel`, `deflateLevel`, and `brotliQuality` are available as algorithm-specific aliases.
92
+
93
+ ## Other image formats
94
+
95
+ For JPEG, WebP, or AVIF input, the Node adapter can use `sharp` when the consuming application has it installed:
96
+
97
+ ```bash
98
+ npm install sharp
99
+ ```
100
+
101
+ If `sharp` is not installed, decode the image to RGBA in your own image pipeline and pass the pixels to `scanImageData()`.
102
+
103
+ ## Secure password workflow
104
+
105
+ ```js
106
+ import {
107
+ decryptDecoded,
108
+ encodeSecureText
109
+ } from "quadqr-js";
110
+ import {
111
+ savePNG,
112
+ scanFile
113
+ } from "quadqr-js/node";
114
+
115
+ const code = await encodeSecureText("private", {
116
+ security: {
117
+ mode: "password",
118
+ password: process.env.QUADQR_PASSWORD
119
+ }
120
+ });
121
+
122
+ await savePNG(code, "secure.png");
123
+
124
+ const locked = await scanFile("secure.png");
125
+
126
+ const unlocked = await decryptDecoded(locked, {
127
+ password: process.env.QUADQR_PASSWORD
128
+ });
129
+
130
+ console.log(unlocked.text);
131
+ ```
132
+
133
+ ## Secure raw-key workflow
134
+
135
+ ```js
136
+ import {
137
+ decryptDecoded,
138
+ encodeSecureText,
139
+ generateRaw256Key
140
+ } from "quadqr-js";
141
+ import { toPNG, scanBuffer } from "quadqr-js/node";
142
+
143
+ const key = generateRaw256Key();
144
+
145
+ const code = await encodeSecureText("machine secret", {
146
+ security: {
147
+ mode: "raw-key",
148
+ key
149
+ }
150
+ });
151
+
152
+ const png = toPNG(code);
153
+ const locked = await scanBuffer(png);
154
+ const unlocked = await decryptDecoded(locked, { key });
155
+
156
+ console.log(unlocked.text);
157
+ ```
158
+
159
+ Keep long-lived application keys in an appropriate secret-management or platform key-storage system.
package/docs/README.md CHANGED
@@ -1,62 +1,62 @@
1
- # QuadQR Documentation
2
-
3
- QuadQR is an experimental RGBW matrix symbology. Default RGBW cells carry two bits using red, green, blue, or white. The experimental High Density Mode splits payload cells into two RGBW triangles and carries four raw bits per body data cell. The JavaScript library supports encoding, canvas/RGBA/SVG rendering, adjustable quiet zones, optional centered logos with transparent or cleared backgrounds, matrix decoding, image and camera scanning, Spectrum ECC, optional authenticated encryption, Node.js PNG/SVG workflows, CDN usage, a CLI, and optional prebuilt WebAssembly acceleration.
4
-
5
- ## Live links
6
-
7
- - [Documentation Site](https://akanshsirohi.github.io/QuadQR/documentation/)
8
- - [Interactive Demo](https://akanshsirohi.github.io/QuadQR/demo/)
9
- - [quadqr-js on npm](https://www.npmjs.com/package/quadqr-js)
10
- - [GitHub Repository](https://github.com/akanshsirohi/QuadQR)
11
-
12
- ## Documentation
13
-
14
- - [Getting Started](./GETTING_STARTED.md)
15
- - [API Reference](./API.md)
16
- - [Compression 3.0](./COMPRESSION.md)
17
- - [High Density Mode (Experimental)](./HIGH_DENSITY_MODE.md)
18
- - [Reliability Lab](./RELIABILITY_LAB.md)
19
- - [Browser and CDN](./BROWSER_CDN.md)
20
- - [Node.js](./NODE.md)
21
- - [CLI](./CLI.md)
22
- - [Secure Payloads](./SECURITY.md)
23
- - [WebAssembly](./WASM.md)
24
- - [Wire Format](../FORMAT.md)
25
- - [Technical Specification](../SPECIFICATION.md)
26
-
27
- ## Package entry points
28
-
29
- | Entry | Purpose |
30
- | --- | --- |
31
- | `quadqr-js` | Runtime-neutral core API, rendering, scanning, secure payloads, utilities, and optional WASM |
32
- | `quadqr-js/browser` | Browser ESM entry for canvas, files, video, and camera workflows |
33
- | `quadqr-js/node` | Node.js core plus PNG/SVG, file, and buffer helpers |
34
- | `quadqr-js/benchmark` | Capacity and codec benchmark helpers |
35
- | `quadqr-js/quadqr.min.js` | Classic browser global bundle for CDN/script-tag usage |
36
-
37
- The matrix codec is shared across runtimes. Browser and Node.js adapters only handle environment-specific input and output.
38
-
39
- ## Compatibility note
40
-
41
- QuadQR is a custom experimental format, not ISO QR Code. Standard QR scanner applications cannot decode QuadQR symbols.
42
-
43
- ## License
44
-
45
- QuadQR is licensed under AGPL-3.0. See [`LICENSE`](../LICENSE).
46
-
47
- ## Advanced reliability and payload features
48
-
49
- The current library also includes:
50
-
51
- - normal text/byte payloads with optional internal Compression 3.0;
52
- - bundled synchronous Brotli + portable DEFLATE + level-aware legacy LZ compatibility;
53
- - first-class binary `Uint8Array` APIs;
54
- - Ed25519 signed QuadQR payloads and trusted-key verification;
55
- - signed + AES-256-GCM encrypted composition;
56
- - screen and print rendering modes;
57
- - ECC-aware automatic logo sizing;
58
- - normalized scanner confidence and detailed debug mode;
59
- - deterministic scanability/torture testing;
60
- - a dedicated browser Reliability Lab with 3D perspective sweeps, plus the capacity calculator.
61
-
62
- See [`../SPECIFICATION.md`](../SPECIFICATION.md) for the layering and interoperability rules and [`API.md`](./API.md) for the public APIs.
1
+ # QuadQR Documentation
2
+
3
+ QuadQR is an experimental RGBW matrix symbology. Default RGBW cells carry two bits using red, green, blue, or white. The experimental High Density Mode splits payload cells into two RGBW triangles and carries four raw bits per body data cell. The JavaScript library supports encoding, canvas/RGBA/SVG rendering, adjustable quiet zones, optional centered logos with transparent or cleared backgrounds, matrix decoding, image and camera scanning, Spectrum ECC, optional authenticated encryption, Node.js PNG/SVG workflows, CDN usage, a CLI, and optional prebuilt WebAssembly acceleration.
4
+
5
+ ## Live links
6
+
7
+ - [Documentation Site](https://akanshsirohi.github.io/QuadQR/documentation/)
8
+ - [Interactive Demo](https://akanshsirohi.github.io/QuadQR/demo/)
9
+ - [quadqr-js on npm](https://www.npmjs.com/package/quadqr-js)
10
+ - [GitHub Repository](https://github.com/akanshsirohi/QuadQR)
11
+
12
+ ## Documentation
13
+
14
+ - [Getting Started](./GETTING_STARTED.md)
15
+ - [API Reference](./API.md)
16
+ - [Compression 3.0](./COMPRESSION.md)
17
+ - [High Density Mode (Experimental)](./HIGH_DENSITY_MODE.md)
18
+ - [Reliability Lab](./RELIABILITY_LAB.md)
19
+ - [Browser and CDN](./BROWSER_CDN.md)
20
+ - [Node.js](./NODE.md)
21
+ - [CLI](./CLI.md)
22
+ - [Secure Payloads](./SECURITY.md)
23
+ - [WebAssembly](./WASM.md)
24
+ - [Wire Format](../FORMAT.md)
25
+ - [Technical Specification](../SPECIFICATION.md)
26
+
27
+ ## Package entry points
28
+
29
+ | Entry | Purpose |
30
+ | --- | --- |
31
+ | `quadqr-js` | Runtime-neutral core API, rendering, scanning, secure payloads, utilities, and optional WASM |
32
+ | `quadqr-js/browser` | Browser ESM entry for canvas, files, video, and camera workflows |
33
+ | `quadqr-js/node` | Node.js core plus PNG/SVG, file, and buffer helpers |
34
+ | `quadqr-js/benchmark` | Capacity and codec benchmark helpers |
35
+ | `quadqr-js/quadqr.min.js` | Classic browser global bundle for CDN/script-tag usage |
36
+
37
+ The matrix codec is shared across runtimes. Browser and Node.js adapters only handle environment-specific input and output.
38
+
39
+ ## Compatibility note
40
+
41
+ QuadQR is a custom experimental format, not ISO QR Code. Standard QR scanner applications cannot decode QuadQR symbols.
42
+
43
+ ## License
44
+
45
+ QuadQR is licensed under AGPL-3.0. See [`LICENSE`](../LICENSE).
46
+
47
+ ## Advanced reliability and payload features
48
+
49
+ The current library also includes:
50
+
51
+ - normal text/byte payloads with optional internal Compression 3.0;
52
+ - bundled synchronous Brotli + portable DEFLATE + level-aware legacy LZ compatibility;
53
+ - first-class binary `Uint8Array` APIs;
54
+ - Ed25519 signed QuadQR payloads and trusted-key verification;
55
+ - signed + AES-256-GCM encrypted composition;
56
+ - screen and print rendering modes;
57
+ - ECC-aware automatic logo sizing;
58
+ - normalized scanner confidence and detailed debug mode;
59
+ - deterministic scanability/torture testing;
60
+ - a dedicated browser Reliability Lab with 3D perspective sweeps, plus the capacity calculator.
61
+
62
+ See [`../SPECIFICATION.md`](../SPECIFICATION.md) for the layering and interoperability rules and [`API.md`](./API.md) for the public APIs.
@@ -1,64 +1,64 @@
1
- # Reliability Lab
2
-
3
- QuadQR includes a deterministic Reliability Lab for regression testing generated symbols against camera-style damage. It is intended for comparing scanner changes, render settings, ECC profiles, output size, and the experimental High Density Mode under repeatable conditions.
4
-
5
- The lab verifies the decoded payload CRC. Finding three locators is not counted as success unless the final payload is recovered correctly.
6
-
7
- ## Browser demo
8
-
9
- Open the **Reliability Lab** tab after generating a QuadQR. The lab provides:
10
-
11
- - Quick, Full, and Extreme suites;
12
- - per-scenario pass/fail, confidence, Reed-Solomon correction count, and decode time;
13
- - category-level scoring for optics, lighting, color, resampling, sensor damage, and perspective;
14
- - a 3D perspective playground with X pitch, Y yaw, and Z rotation controls;
15
- - angle sweeps to find the largest tested pitch, yaw, or Z rotation that still decodes.
16
-
17
- X pitch and Y yaw simulate a code plane tilting in depth. Z rotation is an in-plane rotation. The controls can be combined.
18
-
19
- ## Public API
20
-
21
- ```js
22
- import {
23
- applyStressDistortion,
24
- runReliabilityLab,
25
- runPerspectiveSweep
26
- } from "quadqr-js";
27
-
28
- const transformed = applyStressDistortion(imageData, "perspective-3d", 0.5, {
29
- pitchDegrees: 20,
30
- yawDegrees: 45,
31
- rollDegrees: 15
32
- });
33
-
34
- const report = runReliabilityLab(imageData, {
35
- version: encoded.version,
36
- crc32: encoded.crc32
37
- }, {
38
- suite: "full"
39
- });
40
-
41
- const sweep = runPerspectiveSweep(imageData, {
42
- version: encoded.version,
43
- crc32: encoded.crc32
44
- }, {
45
- axis: "yaw",
46
- angles: [0, 15, 25, 35, 45, 55]
47
- });
48
- ```
49
-
50
- ## Suites
51
-
52
- The Reliability Lab extends the smaller scanability suite with deterministic cases for lens blur, motion blur, low/high exposure, gradient shadow, glare, warm/cool color casts, contrast loss, sensor noise, JPEG-like damage, downscaling, projective skew, 3D pitch/yaw, Z rotation, and combined 3D tilt.
53
-
54
- The **Extreme** suite adds stronger perspective cases. These are deliberately demanding and may fail at low pixels-per-module even when the same angle succeeds at a larger rendered size.
55
-
56
- ## Perspective scanner recovery
57
-
58
- The scanner keeps its normal fast path for ordinary frames. When locator scale differences indicate projective foreshortening, the geometry stage can perform a bounded coarse-to-fine search for the primary alignment reference. The fine stage uses denser sub-cell scoring so payload cells are less likely to impersonate the alignment marker. This improves steep-angle recovery without weakening normal structure checks globally.
59
-
60
- Distributed secondary alignment references are still used to validate the resulting homography.
61
-
62
- ## Interpreting results
63
-
64
- Synthetic results are regression aids, not a substitute for real phone-camera and print testing. A useful practical metric is the largest reliable angle at the intended physical size and scanning distance, not only the theoretical bits per cell.
1
+ # Reliability Lab
2
+
3
+ QuadQR includes a deterministic Reliability Lab for regression testing generated symbols against camera-style damage. It is intended for comparing scanner changes, render settings, ECC profiles, output size, and the experimental High Density Mode under repeatable conditions.
4
+
5
+ The lab verifies the decoded payload CRC. Finding three locators is not counted as success unless the final payload is recovered correctly.
6
+
7
+ ## Browser demo
8
+
9
+ Open the **Reliability Lab** tab after generating a QuadQR. The lab provides:
10
+
11
+ - Quick, Full, and Extreme suites;
12
+ - per-scenario pass/fail, confidence, Reed-Solomon correction count, and decode time;
13
+ - category-level scoring for optics, lighting, color, resampling, sensor damage, and perspective;
14
+ - a 3D perspective playground with X pitch, Y yaw, and Z rotation controls;
15
+ - angle sweeps to find the largest tested pitch, yaw, or Z rotation that still decodes.
16
+
17
+ X pitch and Y yaw simulate a code plane tilting in depth. Z rotation is an in-plane rotation. The controls can be combined.
18
+
19
+ ## Public API
20
+
21
+ ```js
22
+ import {
23
+ applyStressDistortion,
24
+ runReliabilityLab,
25
+ runPerspectiveSweep
26
+ } from "quadqr-js";
27
+
28
+ const transformed = applyStressDistortion(imageData, "perspective-3d", 0.5, {
29
+ pitchDegrees: 20,
30
+ yawDegrees: 45,
31
+ rollDegrees: 15
32
+ });
33
+
34
+ const report = runReliabilityLab(imageData, {
35
+ version: encoded.version,
36
+ crc32: encoded.crc32
37
+ }, {
38
+ suite: "full"
39
+ });
40
+
41
+ const sweep = runPerspectiveSweep(imageData, {
42
+ version: encoded.version,
43
+ crc32: encoded.crc32
44
+ }, {
45
+ axis: "yaw",
46
+ angles: [0, 15, 25, 35, 45, 55]
47
+ });
48
+ ```
49
+
50
+ ## Suites
51
+
52
+ The Reliability Lab extends the smaller scanability suite with deterministic cases for lens blur, motion blur, low/high exposure, gradient shadow, glare, warm/cool color casts, contrast loss, sensor noise, JPEG-like damage, downscaling, projective skew, 3D pitch/yaw, Z rotation, and combined 3D tilt.
53
+
54
+ The **Extreme** suite adds stronger perspective cases. These are deliberately demanding and may fail at low pixels-per-module even when the same angle succeeds at a larger rendered size.
55
+
56
+ ## Perspective scanner recovery
57
+
58
+ The scanner keeps its normal fast path for ordinary frames. When locator scale differences indicate projective foreshortening, the geometry stage can perform a bounded coarse-to-fine search for the primary alignment reference. The fine stage uses denser sub-cell scoring so payload cells are less likely to impersonate the alignment marker. This improves steep-angle recovery without weakening normal structure checks globally.
59
+
60
+ Distributed secondary alignment references are still used to validate the resulting homography.
61
+
62
+ ## Interpreting results
63
+
64
+ Synthetic results are regression aids, not a substitute for real phone-camera and print testing. A useful practical metric is the largest reliable angle at the intended physical size and scanning distance, not only the theoretical bits per cell.