# Carrier Visual Protocol 2 — RGB24 Exact

Release **1.0.0**, wire version **2**, RGB profile **2**, ECC profile **1**. The self-contained executable is `cvp2/1.0.0/cvp2.js`, exported as `CarrierVisual2` in browsers and CommonJS in Node. This specification defines byte transport, independently of Carrier concept definitions and the USL1 message grammar. CVP1 remains a separate supported transport; no CVP1 bytes or dictionary allocations change.

## 1. Meaning and scope

A data module stores three bytes as its exact red, green and blue samples: 24 bits, with all 16,777,216 RGB24 values allowed. At cell size one, one physical pixel is one data module. At larger integer sizes, the entire square repeats that same value; the repetitions do not add payload capacity.

CVP2 is for stored digital samples, not colour estimates. The reader applies no palette quantization, colour conversion, median, calibration, resampling, perspective correction or rotation. Its sixteen-colour framing strips are exact reference marks, not the payload alphabet. A change of one channel unit can change a valid symbol into another valid symbol. Reed–Solomon and the required hashes determine whether recovery succeeds.

The caller explicitly selects CVP2. A reader must validate the UCV2 magic, version, profile and complete container; appearance is not proof of protocol identity. CVP1 inputs must go through a CVP1 reader. Existing USL1 packets, their meanings, dictionary pins and namespaces are unchanged.

Integrity checks establish consistency with supplied bytes and hashes. They are not signatures, proof of authorship, authority, truth, confidentiality or protection against an adversary who can replace the object and recompute its hashes.

## 2. Geometry and exact raster samples

The data side `M` is a multiple of 8 from 32 through 4096. Named presets are 64, 128, 256, 1024, 2048 and 4096. Complete side `S=M+24` modules. The data square begins at `(12,12)` and is traversed row-major, left to right. Background is RGB `(255,255,255)`. Coordinates and rectangle ends are zero-based and end-exclusive.

Four 7×7 finders begin at `(2,2)`, `(S-9,2)`, `(S-9,S-9)`, `(2,S-9)`. Each has a black one-module outer ring, white inner 5×5 square, and central 3×3 square. The central colours in that order are red `#FF0000`, green `#00FF00`, blue `#0000FF`, yellow `#FFFF00`.

Two reference strips begin at `((S-32)/2,3)` and `((S-32)/2,S-5)`. Each contains sixteen 2×2 patches in order `v=0..15`, with colour `(85*(v>>2),255*((v>>1)&1),255*(v&1))`. They retain CVP1 framing geometry but are never used to estimate a transform.

For each `i=0..M-1`, timing cells at `(i+12,10)`, `(i+12,S-11)`, `(10,i+12)`, `(S-11,i+12)` are black if `i` is even and white if odd. All other non-data cells are white.

Physical cell side `c` is an integer 1..24, constrained by `(M+24)*c<=4120`. Every pixel within a module must have the exact same RGB samples. RGBA API input must have alpha exactly 255 everywhere. Every finder, strip, timing cell, background pixel and unused data cell is checked. The decoder considers only dimensions that factor into a valid integer `c` and `M`; each candidate must pass its header and entire raster checks. No quarter-turn or noninteger reconstruction is attempted.

Cell size is not stored in the header. An exact nearest-neighbour integer enlargement can constitute another valid canonical raster if it meets these rules. Arbitrary resized copies, colour-managed re-exports, screenshots, JPEG, cameras and print/scan are not supported channels. The exact-file contract does not mean a `.png` extension alone is sufficient.

## 3. Wire bytes, correction and capacity

Use full systematic RS(255,223): 223 data bytes followed by 32 parity bytes, over GF(256), primitive polynomial `0x11D`, alpha 2, roots alpha^0 through alpha^31, descending polynomial coefficients. Padding is part of the full codeword, not shortened RS.

```
K = floor(M*M/85) = floor(3*M*M/255)
capacity = (K-1)*223 bytes per frame
wire[i*K+k] = codeword[k][i]       i=0..254, k=0..K-1
data_module[n].RGB = (wire[3*n], wire[3*n+1], wire[3*n+2])
```

Codeword 0 is the header. Codewords 1..K-1 contain consecutive 223-byte pieces of the current stream chunk, padded with zeros. The wire length is `255*K`, always divisible by three. All remaining data modules must be `#000000`; they are outside wire hashes but mandatory canonical padding. The remainder of the last payload block and every unused payload block must decode to zeros.

The reference repairs up to sixteen unknown erroneous bytes per codeword, then verifies all required hashes and lengths. More errors may reject or miscorrect, so zero syndromes alone never establish success. No erasure-location API is supplied. A minimal intact-only receiver may verify all 32 syndromes and refuse any damaged codeword, but must declare that reduced scope. It is not a full repair implementation.

One changed RGB module can alter three wire bytes. Interleaving spreads adjacent channel bytes across different codewords for the supported grids. This is not a universal tolerance stated as a percentage of damaged image pixels; nonuniform tiles and damaged framing can be rejected before RS recovery.

CVP2 carries six times the gross bits per data module compared with CVP1 (24 versus 4). Header overhead, grid rounding, error correction, PNG compression and physical cell size affect net capacity and file size separately. At M=4096 the capacity is **44,015,294 bytes**, with K=197379. The minimum frame raster is 4120×4120 at c=1; it is not the separate 4096×4096 full-namespace atlas.

## 4. Header: exactly 223 bytes

Integers are unsigned little-endian. Hashes are raw digest bytes. Offsets below are decimal; ranges exclude the endpoint.

| Offset | Length | Required meaning |
|---:|---:|---|
| 0 | 4 | ASCII `UCV2` |
| 4 | 1 | wire version 2 |
| 5 | 1 | 0=uncompressed, 1=gzip; other bits forbidden |
| 6 | 1 | RGB24 profile 2 |
| 7 | 1 | ECC profile 1 |
| 8 | 2 | header size 223 |
| 10 | 2 | M, matching decoded geometry |
| 12 | 4 | zero-based frame index |
| 16 | 4 | frame count |
| 20 | 4 | original object byte length |
| 24 | 4 | full stream byte length |
| 28 | 4 | this chunk's length |
| 32 | 4 | this chunk's stream offset |
| 36 | 32 | SHA-256 of this chunk |
| 68 | 32 | SHA-256 of full stream |
| 100 | 32 | SHA-256 of original object |
| 132 | 16 | first 16 original-hash bytes |
| 148 | 2 | metadata UTF-8 byte length, maximum 69 |
| 150 | 69 | metadata followed by zero padding through offset 218 |
| 219 | 4 | CRC-32/ISO-HDLC of bytes 0..218 |

CRC is reflected IEEE with polynomial `0xEDB88320`, initial register `0xFFFFFFFF` and final XOR `0xFFFFFFFF` (ordinary zlib CRC-32). All structural fields, reserved bytes, metadata and message identity are validated after RS, before trusting allocation lengths.

File metadata is exactly canonical UTF-8 JSON `{"n":"basename","t":"media/type"}`, with keys n then t, both strings, no extra keys, whitespace or duplicate keys. Canonical bytes equal JavaScript `JSON.stringify({n:parsed.n,t:parsed.t})` encoded in UTF-8. This also specifies escaping, including escaped unpaired UTF-16 surrogates. n must be nonempty and must not contain U+0000..U+001F or `\ / < > : " | ? *`. Metadata is descriptive, not an instruction to write a particular path. Use safe output paths. The encoder sanitizes and truncates file metadata to the 69-byte allowance; original file content remains exact.

Let `C=capacity(M)`, `L=streamBytes`, `count=max(1,ceil(L/C))`. Require frame index < count, `offset=index*C`, `chunkBytes=max(0,min(C,L-offset))`. Empty input has one frame and zero chunk bytes. With flags zero, originalBytes must equal streamBytes.

## 5. Sequences and bounded expansion

Reference resource policy: original and stream each at most 128 MiB (134217728 bytes), at most 65535 frames. The same wire fields could represent other policies only in a separately declared implementation release. The source must be a valid unsigned byte array; signed values, missing array entries and coercion are rejected.

Each accepted frame carries the same grid, flags, count, original/stream lengths, stream/original hashes, message ID and metadata bytes. All chunks must verify independently. Assemble by index only when every index is present. Identical duplicate chunks may be ignored; conflicting duplicates, mixed messages/protocols, changed metadata or changed verified content must reject. Verify the assembled stream hash before decompression and the exact original length and hash afterward.

Flag 1 contains a gzip stream of one or more valid consecutive members. Concatenate their uncompressed bytes; reject trailing junk, invalid gzip checks and output exceeding the declared original size or resource bound. The canonical encoder writes one member and uses compression only if it is smaller than the original. Compression is optional, not part of the concept grammar.

## 6. Exact PNG file profile

Writer: PNG signature, one IHDR, consecutive IDAT data, one IEND; colour type 2, depth 8, compression method 0, filter method 0, interlace method 0. Pixel samples are three unsigned bytes in RGB order. The reference writer uses scanline filter 0 and zlib-compressed scanlines. No display canvas is used to produce the file.

Reader: all chunk CRCs, ordering, lengths and EOF must verify. Require square 56..4120 dimensions for CVP2 frames. Accept standard per-row PNG filters 0,1,2,3,4 with bytes-per-pixel 3. Accept at most one optional sRGB chunk (one byte, rendering intent 0) before IDAT; it is ignored as a rendering hint. No other ancillary or critical chunks are accepted. In particular palette/alpha/16-bit/interlaced PNG, tRNS, gAMA, cHRM, iCCP, cICP, sBIT, eXIf, text chunks and APNG chunks reject in this release.

Require 1..4096 consecutive IDAT chunks, at most 64 MiB aggregate compressed data and at most 80 MiB total PNG file size. Inflation must consume a single complete zlib stream with no concatenated stream or trailing data and produce exactly `height*(1+3*width)` bytes. The largest supported scanline stream is 50,927,320 bytes. Reject before exceeding that bound. IEND has zero length and is the final file bytes. Unknown or reordered chunks do not silently fall back to another transport.

Read unfiltered stored samples directly. A library that applies an ICC/gamma conversion, premultiplication, palette quantization or resampling is not an acceptable byte source unless its exact preservation is independently verified. PNG optimization that changes samples or strips/adds unsupported metadata can violate this release's contract. Display previews and original-file decoding are separate paths.

## 7. API and fitting

`prepare(bytes,{grid:'auto'|M,compress,name,type})` returns a lazy immutable plan with frameCount, capacity, lengths, hashes, `getFrame(index)` and `dispose()`. Auto fit selects the smallest supported M fitting the stream, or M=4096 with multiple frames. `encode` eagerly materializes all frames and is intended for small sequences.

`encodePNG(frame,c)` produces exact PNG bytes. `decodePNG(bytes)` returns a verified chunk. `assemble(chunks)` verifies the entire original object. `renderRGB`, `renderRGBA`, `decodeRGB`, `decodeRGBA` operate on already-exact samples; UI use of renderRGBA is preview-only. `encodeRasterPNG` is a lower-level square RGB8 helper and can write a small non-frame raster; that does not make such a raster a CVP2 message.

`capacity(M)`, `chooseGrid(length)` and `recommendedCellSize(M)` support UI planning. Recommended physical size is min(4,floor(4120/(M+24))). Explicit 1/2/4 pixel modes remain independent of the colour mapping. Select protocol explicitly for both encoding and reading; sequences cannot mix CVP1 and CVP2 frames.

## 8. Reproduction and evidence

Experiment 1 uses the unchanged 100551-byte USL1 object with SHA-256 `47f19645d26ec974b89dab7ecb920b981477655e01bc104ecd98aea15da91ae5`. CVP2 auto-fit uses M=200, c=4 and an 896×896 raster. The controlled CVP1 comparison uses M=488, c=4 and a 2048×2048 raster. The independent receiver verifies equality with both the original USL1 and the prior independent CVP1 recovery. It uses syndrome verification only and refuses RS damage.

Separate exhaustive raw-PNG tests cover every RGB24 value once in a 4096×4096 atlas and compare stored samples with two independent image libraries. That atlas contains no CVP2 framing and is not itself an encoded Carrier expression. Protocol tests separately cover headers, PNG parsing, hashes, sequences, valid/invalid cases and implemented byte repair. Detailed test reports and fixture hashes accompany the release; no external AI interpretation replay or optical robustness result is implied by software tests.

Primary PNG reference: [W3C PNG Third Edition](https://www.w3.org/TR/png-3/). The reference's strict container acceptance is deliberately narrower than all legal PNG files. USL1 and Carrier semantics are unchanged; a direct concept-colour expressive grammar would be a separate development.
