# Chromacode 2D – Protokollspezifikation, Versionen 1 und 2

Status: implementierte, interoperable Protokollspezifikation. Teil I beschreibt das
rückwärtskompatible STATIC-Legacyformat v1. Teil II beschreibt das streamingfähige
Transportprotokoll v2. Alle Koordinaten beginnen bei `0`. Mehrbyte-Integer sind
**Big Endian** (Network Byte Order). Ein Decoder MUSS anhand von Magic und Version
dispatchen und unbekannte Versionen ablehnen.

# Teil I – STATIC Legacy, Version 1

## 1. Begriffe und Konstanten

- Name des Formats: **Chromacode 2D** (`CC2D`)
- Formatversion: `1`
- Symbol: ein vollständiges quadratisches Raster einschließlich Funktionsfeldern
- Modul: ein Rasterfeld
- Datenmodul: eines von genau 16 Farbsymbolen und damit ein Nibble (4 Bit)
- Frame: ein Symbol einer möglicherweise mehrteiligen Datei
- Quiet Zone: 4 weiße Module außerhalb des Symbolrasters
- neutrale Farben: Finder-Schwarz `#101318` und Separator-Weiß `#F7F7F2`; sie sind
  keine Datensymbole

Unterstützte Rasterkantenlängen in Version 1 sind `48`, `64`, `80` und `96` Module.

## 2. Farbalphabet

Der Nibblewert ist der Index in der folgenden normativen Tabelle. Die RGB-Werte sind
Render-Sollwerte in sRGB. Ein Kameradecoder darf sie **nicht** direkt als Messwerte
erwarten, sondern muss die im Symbol mitgeführten Referenzfelder verwenden.

| Nibble | sRGB | Bezeichnung |
|---:|:---:|---|
| `0` | `#7F1D1D` | Dunkelrot |
| `1` | `#EF4444` | Rot |
| `2` | `#F97316` | Orange |
| `3` | `#FACC15` | Gelb |
| `4` | `#65A30D` | Gelbgrün |
| `5` | `#22C55E` | Grün |
| `6` | `#0F766E` | Petrol |
| `7` | `#22D3EE` | Cyan |
| `8` | `#1D4ED8` | Blau |
| `9` | `#60A5FA` | Hellblau |
| `A` | `#581C87` | Dunkelviolett |
| `B` | `#A855F7` | Violett |
| `C` | `#DB2777` | Magenta |
| `D` | `#F9A8D4` | Rosa |
| `E` | `#854D0E` | Braun |
| `F` | `#94A3B8` | Schiefergrau |

Die Palette vermeidet reines Schwarz und reines Weiß, damit Finder und Quiet Zone
getrennt bleiben. Sie kombiniert deutlich verschiedene Farbtöne mit mehreren
Helligkeitsstufen. Die Referenzmessung kompensiert Weißabgleich, Belichtung und einen
großen Teil gleichmäßiger Farbverschiebungen. Klassifiziert wird in CIELAB (D65), nach
kleinstem Delta-E-76-Abstand zu den 16 **gemessenen** Referenzen.

## 3. Geometrie und Finder

Die Ausgabe besteht aus `4 + N + 4` Modulen pro Kante. Nur die inneren `N × N` Module
sind das Symbol; die vier Module breite weiße Zone ist zwingend frei.

Jede Ecke besitzt eine reservierte `10 × 10`-Zone. Der eigentliche Finder ist darin ein
`7 × 7`-Muster mit Ursprung:

- oben links: `(2, 2)`
- oben rechts: `(N-9, 2)`
- unten rechts: `(N-9, N-9)`
- unten links: `(2, N-9)`

Alle übrigen Felder einer Eckzone sind Separator-Weiß. Ein Finder besteht aus einem
schwarzen Außenring, einem weißen `5 × 5`-Inneren und einem schwarzen diagonalen X im
Inneren. Hinzu kommen schwarze ID-/Richtungsfelder an den vier inneren Kardinalpositionen
`T=(3,1)`, `R=(5,3)`, `B=(3,5)`, `L=(1,3)`:

- oben links: `T`
- oben rechts: `T + R`
- unten rechts: `T + R + B`
- unten links: `T + R + B + L`

Die unterschiedliche Anzahl schwarzer ID-Felder identifiziert jede Ecke auch unabhängig
von der Drehung; ihre Position kodiert zusätzlich die lokale Ausrichtung. Das Muster ist
damit kein QR-Finder. Die vier gerichteten X-Marker kodieren gemeinsam die Orientierung. Ihre geometrischen Mittelpunkte liegen bei `(5.5, 5.5)`,
`(N-5.5, 5.5)`, `(N-5.5, N-5.5)` und `(5.5, N-5.5)`. Diese vier Punktkorrespondenzen
definieren eine Homographie. Ein Scanner probiert alle v1-Rastergrößen und vier
Drehungen und akzeptiert nur eine ausreichend hohe Markerkorrelation.

Die äußerste Modulreihe ist weiß reserviert. Zusätzliche alternierende schwarz/weiße
Timing-Spuren liegen zwischen den Eckzonen auf:

- `(x, 8)` und `(x, N-9)` für `10 ≤ x < N-10`
- `(8, y)` und `(N-9, y)` für `10 ≤ y < N-10`

Der Zustand ist schwarz bei gerader, weiß bei ungerader laufender Koordinate. Diese
Spuren dienen der Raster- und Unschärfeprüfung, transportieren aber keine Headerbits.

## 4. Farbkalibrierung

Der Kalibrierungsbereich ist ein `16 × 4` Module großes Rechteck am oberen Rand. Sein
linker Rand ist `cx = floor((N - 16) / 2)`, sein oberer Rand `y = 2`. Er enthält 16
Referenzen als `2 × 2`-Blöcke:

- Werte `0…7`: `x = cx + 2·Wert`, `y = 2…3`
- Werte `8…15`: `x = cx + 2·(Wert-8)`, `y = 4…5`

Der ein Modul breite Bereich um dieses Rechteck ist weiß reserviert. Die Mitte jedes
Referenzmoduls wird mehrfach abgetastet; Randpixel werden verworfen. Aus allen vier
Modulen eines Farbfelds wird ein getrimmter RGB-Mittelwert gebildet und anschließend
nach CIELAB konvertiert.

Für ein Datenmodul sind `d1` und `d2` der kleinste und zweitkleinste Delta-E-Abstand.
Die normative Confidence ist `clamp((d2-d1)/(d2+1), 0, 1)`. Die Referenzimplementierung
markiert ein Modul als unsicher, wenn die Confidence kleiner als `0,12` ist oder `d1`
größer als `38` ist. Sobald eines der zwei Nibbles eines Bytes unsicher ist, wird das
gesamte Byte als Reed-Solomon-Erasure behandelt.

## 5. Datenzellen und Bitreihenfolge

Alle nicht reservierten Zellen werden zeilenweise von links oben nach rechts unten
aufgelistet. Ist ihre Zahl ungerade, bleibt die letzte Zelle unbenutzt und erhält Farbe
`0`. Je zwei Zellen bilden einen physischen Byteslot:

1. erste Zelle: Bits 7…4 (High Nibble)
2. zweite Zelle: Bits 3…0 (Low Nibble)

Beispiel: `0xA7` wird als Farbe `A`, danach Farbe `7` gespeichert.

## 6. Räumliche Permutation und Block-Interleaving

Sei `C = floor(Anzahl_Datenzellen / 2)` die Codewortkapazität in Bytes. Für jeden
logischen Byteindex `i` wird der physische Slot wie folgt bestimmt:

```
offset = (N * 17 + 23) mod C
step   = größte ungerade Zahl <= floor(C * 0.61803398875), die teilerfremd zu C ist
slot(i) = (offset + i * step) mod C
```

Vor dieser räumlichen Permutation werden die RS-Codewörter blockweise
Round-Robin-interleaved: erst Byte 0 jedes Blocks, dann Byte 1 jedes Blocks usw.; bei
unterschiedlicher Blocklänge werden bereits beendete Blöcke übersprungen. So trifft
ein lokaler Schaden typischerweise unterschiedliche RS-Blöcke und entfernte
Datenpositionen.

## 7. Reed-Solomon-Fehlerkorrektur

Chromacode 2D v1 verwendet systematische, verkürzte Reed-Solomon-Codes über GF(256):

- primitives Polynom: `x^8 + x^4 + x^3 + x^2 + 1` (`0x11D`)
- primitives Element: `2`
- erste Generatorwurzel: `α^0`
- Symbolgröße: 8 Bit
- maximale Blocklänge: 255 Bytes

ECC-Stufen und Paritätssymbole **pro Block**:

| Stufe | ID | Parität/Block | maximale unbekannte Bytefehler |
|---|---:|---:|---:|
| Low | 0 | 16 | 8 |
| Medium | 1 | 32 | 16 |
| High | 2 | 48 | 24 |
| Extreme | 3 | 64 | 32 |

Erasures zählen nur einfach, unbekannte Fehler doppelt: Dekodierbar ist grundsätzlich
`2·Fehler + Erasures ≤ Paritätssymbole`.

Die Anzahl Blöcke ist `B = ceil(C / 255)`. Die `C` Codewortbytes werden möglichst
gleichmäßig auf die Blöcke verteilt: Block `i` hat
`floor(C/B) + (i < C mod B ? 1 : 0)` Bytes. Jeder Block enthält am Ende die in der
Tabelle angegebene Parität. Die Summe der Datenlängen ist die Frame-Rohkapazität.
Nicht vom Header und Chunk belegte Datenbytes werden vor RS-Encoding mit Null gefüllt.

Beim Dekodieren werden die vier bekannten ECC-Stufen ausprobiert. Nur eine Kombination
mit gültiger Magic, Version, Rastergröße, ECC-ID und Header-CRC wird angenommen.

## 8. Binärer Frame-Header

Der Header beginnt am ersten Datenbyte jedes deinterleavten, RS-dekodierten Frames.
Seine feste Länge ist 104 Bytes; direkt danach folgen UTF-8-Dateiname und UTF-8-MIME.

| Offset | Länge | Typ | Inhalt |
|---:|---:|---|---|
| 0 | 4 | Bytes | Magic ASCII `CC2D` (`43 43 32 44`) |
| 4 | 1 | u8 | Formatversion, `1` |
| 5 | 1 | u8 | ECC-ID `0…3` |
| 6 | 2 | u16 | gesamte Headerlänge inkl. Name und MIME |
| 8 | 2 | u16 | Rasterkantenlänge `N` |
| 10 | 2 | u16 | Flags; in v1 `0` |
| 12 | 16 | Bytes | File-ID |
| 28 | 8 | u64 | ursprüngliche Dateigröße |
| 36 | 32 | Bytes | SHA-256 der vollständigen Originaldatei |
| 68 | 4 | u32 | CRC32 der vollständigen Originaldatei |
| 72 | 4 | u32 | `frameIndex`, nullbasiert |
| 76 | 4 | u32 | `totalFrames` |
| 80 | 8 | u64 | Byteoffset des Chunks in der Datei |
| 88 | 4 | u32 | Chunklänge |
| 92 | 4 | u32 | CRC32 des Chunks |
| 96 | 2 | u16 | UTF-8-Dateinamenlänge |
| 98 | 2 | u16 | UTF-8-MIME-Länge |
| 100 | 4 | u32 | Header-CRC32 |
| 104 | variabel | Bytes | Dateiname, dann MIME-Type |

Für die Header-CRC werden die Bytes `100…103` auf Null gesetzt und anschließend alle
`headerLength` Headerbytes geprüft. Der Payload beginnt bei `headerLength` und hat
exakt `chunkLength` Bytes. Längen und CRCs liefern ein eindeutiges Ende; Padding gehört
nicht zum Payload.

Eine File-ID wird als 16 Bytes gebildet: acht kryptographisch zufällige Bytes gefolgt
von den ersten acht Bytes des SHA-256. Sie ist kein Sicherheitsmerkmal, verhindert aber
praktisch die Vermischung unabhängiger Scanserien.

## 9. CRC32 und SHA-256

CRC32 ist IEEE/ISO-HDLC mit reflektiertem Polynom `0xEDB88320`, Initialwert
`0xFFFFFFFF` und abschließendem XOR `0xFFFFFFFF`. Jeder Chunk, jeder Header und die
Gesamtdatei besitzen eine CRC32-Prüfung. Zusätzlich muss nach vollständigem Zusammenbau
SHA-256 der Datei exakt mit dem Headerwert übereinstimmen.

## 10. Multi-Frame-Zusammenbau

Der Encoder wählt pro Frame die maximale Chunklänge
`RS-Datenkapazität - 104 - filenameLength - mimeLength`. Alle Frames wiederholen die
vollständigen Metadaten. `frameIndex` muss kleiner als `totalFrames` sein; Offset und
Chunklänge dürfen die Dateigröße nicht überschreiten.

Ein Sammler gruppiert anhand der 16-Byte-File-ID, prüft aber zusätzlich SHA-256,
Dateigröße und `totalFrames`. Doppelte Indizes werden ignoriert. Nach Empfang aller
Indizes werden Chunks nach `frameIndex` beziehungsweise Offset zusammengesetzt. Erst
nach gültiger Gesamt-CRC32 und SHA-256 wird ein Download angeboten. Eine leere Datei
erzeugt genau einen Frame mit einem leeren Chunk.

## 11. Bilddekodierung

Eine konforme Kameraimplementierung führt mindestens folgende Schritte aus:

1. dunkle, quadratische X-Ring-Komponenten als Finder-Kandidaten ermitteln;
2. vier ähnlich große Kandidaten zu einem konvexen Viereck ordnen;
3. Rastergröße und Rotation anhand aller vier gerichteten Markermatrizen bewerten;
4. Homographie zwischen normierten Markerzentren und Bildkoordinaten lösen;
5. Timing-Spuren und Quiet-Zone-Plausibilität prüfen;
6. je Modul mehrere Pixel aus den inneren 44 % sampeln und einen getrimmten Mittelwert
   berechnen;
7. 16 gemessene CIELAB-Referenzzentren aufbauen;
8. Datenmodule mit Confidence klassifizieren und unsichere Bytes als Erasures führen;
9. räumlich depermutieren, Blockinterleaving auflösen und RS dekodieren;
10. Header-, Chunk- und später Datei-CRC sowie SHA-256 prüfen.

Live-Erkennung soll dasselbe `(File-ID, frameIndex)` in mindestens zwei zeitnahen
Videobildern mit kompatibler Rastergröße erkennen, bevor ein Frame übernommen wird.

## 12. Sicherheit und Versionsregeln

Dateinamen und MIME-Strings sind Daten, niemals HTML. UIs müssen sie über `textContent`
ausgeben und Dateinamen für Downloads auf den Basenamen reduzieren. Rekonstruierte
Inhalte werden nicht automatisch dargestellt oder ausgeführt. Bei Magic `CC2D` werden Versionen größer oder
kleiner als `1`, unbekannte ECC-IDs, ungültige Längen, überlappende Frames und ungültige
Prüfsummen werden abgelehnt. Magic `CCS2` wird ausschließlich nach Teil II dekodiert.

## 13. Bekannte physikalische Grenzen

16 Kamera-Farbsymbole sind anspruchsvoller als binäre Codes. Die Referenzen können
globale Änderungen gut, lokal verschiedenes Mischlicht, starke Display-Reflexionen,
Clipping und Drucker-Gamutverluste aber nur begrenzt kompensieren. Extreme ECC und
große Module sind für Bildschirm-zu-Kamera-Übertragung vorzuziehen. Ein einzelnes
96er-Symbol sollte auf einem Telefon nicht so klein dargestellt werden, dass ein Modul
weniger als ungefähr 5–6 Kamerapixel breit abgebildet wird.

# Teil II – Transportprotokoll, Version 2

## 14. Ziele, Modi und Schichten

Version 2 heißt **Chromacode Streaming 2**. Seine Magic ist ASCII `CCS2`
(`43 43 53 32`), das Versionsbyte ist `2`. Die eigene Magic verhindert, dass ein
v1-Decoder einen v2-Frame irrtümlich als beschädigten STATIC-Frame behandelt.

Die Betriebsart ist ein `u8`:

| Wert | Modus | Bedeutung |
|---:|---|---|
| `0` | STATIC | v2-Einzel-/Mehrfachframes; v1 bleibt der Standard-Legacypfad |
| `1` | STREAM | gerichtete Sequenz ohne erforderlichen Rückkanal |
| `2` | LINK | Transport mit Control-, ACK- und Flow-Control-Frames |

Die Protokollschichten von oben nach unten sind:

1. **Application Layer:** Datei, Benchmarkdaten oder zukünftiger Bytekanal.
2. **File/Stream Layer:** Metadaten, 64-Bit-Offsets, Chunkreihenfolge und SHA-256.
3. **Crypto Layer:** KDF/Pairing, AES-GCM, Headerbindung und Farbpermutation.
4. **Transport Layer:** Session, Stream, Sequenz, ACK, Flow Control und Resume.
5. **Generation-ECC Layer:** MDS-Erasure-Code gegen verlorene Gesamtframes.
6. **Optical-Frame-ECC Layer:** verkürztes RS(255)-Interleaving gegen Modulfehler.
7. **Optical Frame Layer:** SYNC/DATA-Geometrie, Sequenzmarker und Double Buffer.
8. **Physical Color Layer:** 16 sRGB-Farben, CIELAB und Kamera-Sampling.

File Chunk, Transport Block, ECC Generation und Optical Frame sind verschieden:

- Ein **File Chunk** ist ein Stück Originaldatei mit `plainLength` Bytes.
- Ein **Transport Block** ist der auf Blockgröße gepaddete, optional AEAD-verschlüsselte
  Chunk. Er bildet genau einen DATA-Shard.
- Eine **ECC Generation** gruppiert `K` DATA-Shards und `R` PARITY-Shards.
- Ein **Optical Frame** trägt Header plus genau einen DATA-/PARITY-Shard oder Control-
  Payload und erhält zusätzlich lokale Frame-ECC.

## 15. Frame-Typen

Die Werte sind dauerhaft reserviert:

| Wert | Name | Aufgabe |
|---:|---|---|
| `0x01` | SYNC | vollständige Geometrie, Kalibrierung und Sessionparameter |
| `0x02` | DATA | systematischer Datenshard einer Generation |
| `0x03` | PARITY | MDS-Paritätsshard einer Generation |
| `0x04` | ACK | kumulative/selektive Empfangsbestätigung |
| `0x05` | NACK | explizite Fehlstellen-/Wiederholungsanforderung |
| `0x06` | HELLO | ungebundene Geräteankündigung |
| `0x07` | CAPABILITIES | Fähigkeitsbitmaske und Limits |
| `0x08` | SESSION_INIT | P-256-Pairingangebot |
| `0x09` | SESSION_ACCEPT | P-256-Pairingantwort |
| `0x0A` | SESSION_CLOSE | geordneter Sessionabbau |
| `0x0B` | KEEPALIVE | Session-Liveness ohne Nutzdaten |
| `0x0C` | RESET | Empfängerzustand bzw. Generation zurücksetzen |
| `0x0D` | END | Streamende, SHA-256 und finale Größen |

Unbekannte Typen werden verworfen. Sie dürfen keinen Sessionzustand ändern.

## 16. Identitäten und Zähler

- `sessionId`: 16 kryptographisch zufällige Bytes je STREAM-/LINK-Vorgang.
- `streamId`: zufälliger, von Null verschiedener `u32`; unter einem Session-Key darf
  dasselbe Stream-ID-/Nonce-Prefix-Paar nicht wiederverwendet werden.
- `fileId`: 16 kryptographisch zufällige Bytes je Dateiobjekt, unabhängig vom Hash.
- `generationId`: monotoner `u64`, beginnend bei Null.
- `sequenceNumber`: monotoner `u64` für jeden tatsächlich angezeigten Frame, inklusive
  SYNC und PARITY. Bei Überlauf muss eine neue Session entstehen.
- `chunkIndex`: monotoner `u64` für DATA-Blöcke, beginnend bei Null.
- `chunkOffset`: `u64`-Byteoffset innerhalb der Originaldatei.

Frames mit abweichender Session-ID, Stream-ID oder File-ID dürfen niemals in einen
aktiven Transfer eingehen. JavaScript-Implementierungen verwenden `BigInt` und prüfen
vor jeder Konvertierung in `Number`, ob der betreffende API-Offset höchstens
`Number.MAX_SAFE_INTEGER` ist.

## 17. v2-Basisheader

Jeder v2-Frame beginnt mit demselben **136 Byte** langen Basisheader. Type-spezifische
TLVs folgen bis `headerLength`; anschließend folgen exakt `payloadLength` Bytes.

| Offset | Länge | Typ | Feld |
|---:|---:|---|---|
| 0 | 4 | Bytes | Magic ASCII `CCS2` |
| 4 | 1 | u8 | Protokollversion `2` |
| 5 | 1 | u8 | Frame Type nach Abschnitt 15 |
| 6 | 2 | u16 | Flags |
| 8 | 2 | u16 | gesamte Headerlänge, mindestens 136 |
| 10 | 2 | u16 | Rasterkantenlänge `N` |
| 12 | 16 | Bytes | Session-ID |
| 28 | 4 | u32 | Stream-ID |
| 32 | 16 | Bytes | File-ID |
| 48 | 8 | u64 | Generation-ID |
| 56 | 2 | u16 | Shard-/Frameindex innerhalb der Generation |
| 58 | 2 | u16 | Anzahl DATA-Shards `K` |
| 60 | 2 | u16 | Anzahl PARITY-Shards `R` |
| 62 | 1 | u8 | lokale RS-Parität je Block: 16/32/48/64 |
| 63 | 1 | u8 | Crypto Suite |
| 64 | 8 | u64 | Sequence Number |
| 72 | 8 | u64 | Chunk Index; bei PARITY `0xFFFFFFFFFFFFFFFF` |
| 80 | 8 | u64 | Chunk Offset; bei PARITY Generationstart |
| 88 | 4 | u32 | übertragene Payloadlänge |
| 92 | 4 | u32 | ungepaddete Klartextlänge; DATA, sonst 0 |
| 96 | 8 | u64 | ursprüngliche Dateigröße |
| 104 | 8 | u64 | gesamte Klartext-Payloadlänge des Streams |
| 112 | 8 | u64 | geplante optische Streamlänge in Bytes, 0 falls unbekannt |
| 120 | 4 | u32 | Key-ID; 0 ohne Schlüssel |
| 124 | 4 | u32 | CRC32 über die übertragenen Payloadbytes |
| 128 | 4 | u32 | reserviert, muss 0 sein |
| 132 | 4 | u32 | Header-CRC32 |

Für die Header-CRC werden Bytes `132…135` auf Null gesetzt und alle `headerLength`
Bytes geprüft. Die Payload-CRC wird vor ECC über Ciphertext/Tag beziehungsweise den
unverschlüsselten Shard berechnet. Ungültige CRC, Längen, reservierte Bits oder eine
Headerlänge außerhalb der optischen Kapazität verwerfen den Frame.

Flags:

| Maske | Name |
|---:|---|
| `0x0001` | ENCRYPTED |
| `0x0002` | COLOR_PERMUTED |
| `0x0004` | FINAL_CHUNK |
| `0x0008` | RETRANSMISSION |
| `0x0010` | ACK_REQUESTED |
| `0x0020` | SYNC_REFRESH |
| `0x0040` | RESUMED |
| `0x0080` | BENCHMARK |
| `0x0100` | CONTROL_AUTH_PRESENT |

Alle anderen Bits müssen in v2 Null sein.

## 18. TLV-Erweiterungen

Jedes TLV besitzt `type:u8`, `flags:u8`, `valueLength:u16`, danach genau
`valueLength` Bytes. Bit 0 von TLV-Flags bedeutet **critical**; ein unbekanntes
kritisches TLV verwirft den Frame. TLVs stehen in aufsteigender Typreihenfolge und
dürfen höchstens einmal vorkommen.

| Typ | Name | Verwendung |
|---:|---|---|
| `0x01` | SESSION_PARAMETERS | SYNC/SESSION_INIT |
| `0x02` | FILE_METADATA | SYNC |
| `0x03` | CRYPTO_PARAMETERS | SYNC/Handshake |
| `0x04` | PHYSICAL_PALETTE | SYNC |
| `0x05` | SHA256 | END, optional SYNC bei vorab bekanntem Hash |
| `0x06` | CAPABILITY_BITS | CAPABILITIES/SYNC |
| `0x07` | CONTROL_AUTH_TAG | authentifizierte Control-Frames |
| `0x08` | RESUME_STATE | SESSION_ACCEPT/SYNC |
| `0x09` | ACK_PARAMETERS | ACK/NACK |

`SESSION_PARAMETERS` hat exakt 32 Value-Bytes:

| Relativ | Länge | Feld |
|---:|---:|---|
| 0 | 1 | Modus 0/1/2 |
| 1 | 1 | optische Layout-ID, in v2 `1` |
| 2 | 2 | Ziel-FPS × 100 |
| 4 | 2 | SYNC-Intervall in Payloadframes |
| 6 | 2 | DATA-Shards `K` |
| 8 | 2 | PARITY-Shards `R` |
| 10 | 1 | DATA-Farbanchors, in v2 `8` |
| 11 | 1 | reserviert 0 |
| 12 | 4 | gepaddete Klartextblockgröße |
| 16 | 4 | übertragene Shardgröße, inklusive AEAD-Tag |
| 20 | 8 | Gesamtgenerationen als u64 |
| 28 | 1 | Sequenzmarkerbits, in v2 `12` |
| 29 | 1 | Kalibrierungsrefresh: 0=SYNC-gesteuert |
| 30 | 2 | reserviert 0 |

`FILE_METADATA` beginnt mit `filenameLength:u16`, `mimeLength:u16`, danach UTF-8-
Dateiname und MIME-Type. `PHYSICAL_PALETTE` enthält 16 RGB-Tripel in der Reihenfolge
der physischen Palette aus Teil I. Die Position sagt nichts über den logischen
Nibblewert einer verschlüsselten/permutierten Session aus.

`CRYPTO_PARAMETERS` beginnt mit:

| Relativ | Länge | Feld |
|---:|---:|---|
| 0 | 1 | Crypto Suite |
| 1 | 1 | KDF |
| 2 | 1 | Permutationsmodus |
| 3 | 1 | Kurve: 0=keine, 1=P-256 |
| 4 | 4 | Key-ID |
| 8 | 4 | zufälliges Session-Nonce-Prefix |
| 12 | 4 | PBKDF2-Iterationen, sonst 0 |
| 16 | 1 | Saltlänge |
| 17 | 2 | Public-Key-Länge |
| 19 | variabel | Salt, danach SPKI-Public-Key |

## 19. Physische v2-Layouts

Quiet Zone, Palette, Finder-Schwarz/-Weiß, Eckzonen und X-Finder entsprechen Teil I.
SYNC-Frames verwenden die vollständigen 16 `2×2`-Referenzfelder aus Teil I. DATA,
PARITY und Control verwenden acht je zwei Module breite Anchor-Felder auf Zeile 2.
Bei gerader Sequence Number zeigen sie physische Farben 0…7, bei ungerader 8…15.
SYNC aktualisiert immer alle 16 Referenzen; Anchors führen die andere Hälfte des
Farbmodells nach, ohne die logische Farbpermutation preiszugeben.

Fünf neutrale Sequenzmarker mit je 24 Modulen sind reserviert:

- oben: Zeile 8, 24 Spalten symmetrisch um die Rastermitte;
- unten: Zeile `N-9`, gleiche Spalten;
- links: Spalte 8, 24 Zeilen symmetrisch um die Rastermitte;
- rechts: Spalte `N-9`, gleiche Zeilen;
- Mitte: mittlere Zeile, 24 Spalten symmetrisch um die Rastermitte.

Ein Marker enthält die unteren 12 Bits der Sequence Number MSB-zuerst, gefolgt von
deren bitweisem 12-Bit-Komplement. Bit 0 ist Separator-Weiß, Bit 1 Finder-Schwarz.
Ein Bild ist nur gültig, wenn alle fünf Komplemente gültig, alle fünf Werte identisch
und der Wert gleich `header.sequenceNumber mod 4096` ist. Abweichende obere/untere/
seitliche Marker erkennen Display-Tear und Rolling-Shutter-Mischbilder; solche Bilder
werden ohne ECC-Versuch verworfen.

Alle übrigen, nicht reservierten Module werden zeilenweise enumeriert. High-Nibble
steht weiterhin vor Low-Nibble. Lokales RS-Blockinterleaving und die räumliche
Permutation aus Teil I werden mit der jeweiligen v2-Datenzellenzahl angewandt.

SYNC-Payload wird stets mit kanonischer logischer→physischer Farbzuordnung gerendert.
Nach erfolgreichem SYNC kann DATA/PARITY die Sessionpermutation verwenden.

## 20. SYNC, Lock und Timing

Ein SYNC enthält mindestens SESSION_PARAMETERS, FILE_METADATA, PHYSICAL_PALETTE,
CRYPTO_PARAMETERS und CAPABILITY_BITS. Er trägt Finder und Vollkalibrierung und ist
ohne geheimen Farbmapper lesbar. Der Empfängerzustand lautet:

`IDLE → SEARCHING → SYNCING → LOCKED/RECEIVING → RECOVERING → VERIFYING → DONE`.

Bei sinkender Confidence folgen `DEGRADED → RECALIBRATING`; nach wiederholtem Fehler
`LOST → SEARCHING`. Im LOCKED-State wird die Homographie wiederverwendet. Nur
Sequenzmarker, Anchors, Datenmodule und kleine lokale Plausibilitätskorrekturen werden
gemessen. SYNC aktualisiert Homographie und alle Farbreferenzen.

Der Sender zeigt zu Beginn mindestens zwei identische SYNC-Inhalte mit verschiedenen
Sequence Numbers. Standardintervall sind 20 Payloadframes oder zwei Sekunden, je
nachdem was zuerst eintritt. Das Intervall ist konfigurierbar. Unterstützte Zielraten
sind 1/2/5/10/15/20/30 FPS. Ohne Rückkanal beginnt Adaptive bei 5 FPS und erhöht nicht
automatisch über einen konservativen Benutzerwert. LINK darf anhand von ACK-Statistik
stufenweise erhöhen und bei Verlust/Decoder-Backpressure reduzieren.

Browserrenderer müssen den nächsten Frame vollständig in einen unsichtbaren Buffer
zeichnen und erst in einem `requestAnimationFrame`-Callback atomar zwischen zwei
Canvas-Elementen wechseln. Andere Animationen im Vollbildsender sind zu vermeiden.
Ein angezeigter Frame bleibt mindestens eine vollständige Schedulerperiode stabil.

## 21. Lokale Frame-ECC

Die lokale ECC ist exakt das verkürzte Reed-Solomon-Verfahren aus Teil I. Der Header
nennt die Parität 16/32/48/64 direkt. Decoder müssen nicht vier Profile probieren, wenn
ein gültiger SYNC-Lock das Profil festlegt; beim initialen SYNC dürfen sie die vier
bekannten Werte probieren. Confidence-Erasures werden vor Berlekamp-Massey eingebracht.

Netto-DATA-Kapazität ohne Verschlüsselung ist
`RS-Datenkapazität(v2-DATA-Layout) - 136`. Mit AES-GCM werden weitere 16 Byte pro
DATA-Shard für den Tag abgezogen. Alle DATA-Shards einer Generation werden auf dieselbe
Transportgröße gepaddet.

## 22. Generation-ECC gegen verlorene Frames

Version 2 verwendet einen systematischen MDS-Reed-Solomon-Erasure-Code über GF(256)
mit demselben Polynom `0x11D`. `K + R` darf höchstens 255 sein. Eine Vandermonde-Matrix
für Zeilenwerte `1…K+R` wird rechts mit der Inversen ihrer ersten `K` Zeilen
multipliziert. Dadurch sind die ersten `K` Generatorzeilen die Identität; die übrigen
`R` erzeugen Parität.

Für jede Byteposition aller gleich langen Shards wird die Generatorzeile unabhängig
angewandt. Beliebige `K` der `K+R` Shards reichen zur Rekonstruktion aller DATA-Shards.
Die Referenzkonfiguration ist `K=16, R=4`; robustere Profile dürfen `32+8` oder `64+16`
verwenden. Sender und Empfänger halten höchstens eine kleine Zahl Generationen im RAM, nicht die gesamte Datei. Die Referenzimplementierung begrenzt das Empfänger-Reorder-Fenster auf acht Generationen und die Sequenz-Deduplizierung auf 4.096 Einträge.

Die Pipeline lautet normativ:

`File Chunk → Padding → optionale AEAD-Verschlüsselung → DATA-Shard → Generation-
Parität → optischer Frame-RS → Farbraster`.

Beim Empfang wird sie umgekehrt. Rekonstruierter Ciphertext wird erst nach Erasure-
Recovery AEAD-authentifiziert und entschlüsselt.

## 23. Verschlüsselung, AAD und Nonces

Crypto Suites:

| ID | Verfahren |
|---:|---|
| `0` | keine Verschlüsselung |
| `1` | AES-256-GCM, 128-Bit-Tag |

Jeder DATA-Block wird unabhängig verschlüsselt. Die 96-Bit-GCM-Nonce ist:

```
nonce = sessionNoncePrefix[4] || chunkIndex_u64_big_endian[8]
```

Der AES-Payloadschlüssel wird je `(sessionId, streamId)` separat abgeleitet. Unter
demselben Payloadschlüssel dürfen Nonce-Prefix und Chunk Index niemals wiederverwendet
werden. Resume verwendet dieselbe Zuordnung nur für denselben bereits definierten
Chunk; eine neue Datei/Session erhält neues Zufallsprefix und neuen Schlüssel.

Die kanonische AEAD Additional Data ist genau folgende Verkettung ohne Padding:

```
version:u8 || frameType:u8 || flags:u16 ||
sessionId:16 || streamId:u32 || fileId:16 ||
generationId:u64 || frameIndex:u16 ||
chunkIndex:u64 || chunkOffset:u64 ||
payloadLength:u32 || plainLength:u32 || originalFileSize:u64
```

`payloadLength` enthält Ciphertext plus 16-Byte-Tag. Änderungen an diesen Feldern
führen zu Authentifizierungsfehlern. CRC32 ist weiterhin nur Übertragungsdiagnose,
kein MAC. Authentifizierte Control-Frames tragen TLV `CONTROL_AUTH_TAG` mit den ersten
16 Bytes von HMAC-SHA-256 über Basisheader (Header-CRC und Auth-TLV-Wert Null) plus
Payload.

## 24. Passwort-KDF, Pairing und Key Separation

KDF-IDs:

| ID | KDF |
|---:|---|
| `0` | keine |
| `1` | PBKDF2-HMAC-SHA-256 |
| `2` | ECDH P-256 plus HKDF-SHA-256 |

Passwortmodus verwendet mindestens 16 zufällige Saltbytes und standardmäßig 600.000
PBKDF2-Iterationen. Das PBKDF2-Ergebnis ist nur Input Key Material für HKDF, nie direkt
ein AES-Schlüssel. Passwörter werden niemals gespeichert.

LINK-Pairing verwendet Web-Crypto-ECDH mit P-256. SESSION_INIT und SESSION_ACCEPT
transportieren SPKI-Public-Keys; beide Seiten prüfen Session-ID und leiten 256 Bit
Shared Secret ab. X25519 ist für v2 nicht normativ, weil die Zielbrowserunterstützung
nicht einheitlich genug ist.

Der binäre Payload von SESSION_INIT und SESSION_ACCEPT ist exakt:

| Relativ | Länge | Typ | Feld |
|---:|---:|---|---|
| 0 | 1 | u8 | Handshake-Version, `1` |
| 1 | 1 | u8 | Kurve, `1` = P-256 |
| 2 | 2 | u16 | SPKI-Public-Key-Länge |
| 4 | 8 | u64 | Capability-Bitmaske |
| 12 | 16 | Bytes | kryptographisch zufällige Initiator-Challenge |
| 28 | 1 | u8 | Länge der Schlüsselbestätigung |
| 29 | 1 | u8 | reserviert, Null |
| 30 | variabel | Bytes | DER-SPKI-Public-Key |
| danach | variabel | Bytes | Schlüsselbestätigung |

SESSION_INIT setzt die Bestätigungslänge auf Null. SESSION_ACCEPT wiederholt dieselbe
Challenge und trägt eine 16-Byte-Bestätigung. Beide Frames verwenden identische
Session-ID, Stream-ID und File-ID. SESSION_ACCEPT setzt außerdem `keyId` im
Basisheader auf die ersten vier Big-Endian-Bytes von SHA-256 des abgeleiteten
Payloadschlüssels; der Wert Null wird auf Eins abgebildet.

Das kanonische Bestätigungstranskript ist ohne Längenfelder:

```text
sessionId[16] || streamId_u32_be || fileId[16] || challenge[16] ||
initiatorPublicSpki || responderPublicSpki
```

Die Bestätigung sind die ersten 16 Bytes von
`HMAC-SHA-256(headerAuthKey, transcript)`. Der Initiator akzeptiert SESSION_ACCEPT nur
bei identischer dreiteiliger Sessionidentität, identischer Challenge, passender
Key-ID und konstanter Übereinstimmung des Tags. Dieser Handshake authentifiziert, dass
beide optischen Endpunkte dasselbe ECDH-Geheimnis besitzen; ohne zusätzlichen
fingerprint-basierten oder extern authentisierten Vergleich verhindert er keinen
aktiven Man-in-the-Middle-Angriff.

HKDF-Salt ist `sessionId`; HKDF-Info ist UTF-8 plus Stream-ID-BE. Getrennte Infos sind:

- `CCS2 payload key` → 32 Byte AES-256-GCM-Schlüssel
- `CCS2 header auth` → 32 Byte HMAC-SHA-256-Schlüssel
- `CCS2 color permutation` → 32 Byte Permutationsschlüssel
- `CCS2 future` → reserviertes, nicht anderweitig genutztes Material

Schlüsselmaterial verschiedener Zwecke darf nicht vertauscht werden.

## 25. Farbpermutation

Eine aktivierte Session bildet logische Nibbles über eine deterministische
Fisher-Yates-Permutation auf physische Palettenindizes ab. Zufallswörter entstehen aus
`HMAC-SHA-256(colorPermutationKey, "CCS2 permutation" || counter_u32_be)`.
Zur verzerrungsfreien Auswahl wird Rejection Sampling benutzt; Modulo-Bias ist nicht
zulässig. Der Decoder verwendet die inverse Permutation.

SYNC-Kalibrierfelder enthalten weiterhin alle physischen Farben, aber keine logischen
Nibblelabels. Ohne Permutationsschlüssel ist die Zuordnung der DATA-Farben unbekannt.
Dies ist nur Verschleierung. Sicherheit stammt ausschließlich aus AES-GCM.

## 26. ACK, NACK, Flow Control und Retransmission

ACK-/NACK-Payload beginnt mit:

| Offset | Länge | Feld |
|---:|---:|---|
| 0 | 1 | ACK-Formatversion `1` |
| 1 | 1 | Flow Action |
| 2 | 2 | Bitmaplänge in Bytes |
| 4 | 8 | Generation-ID |
| 12 | 2 | erster Shardindex |
| 14 | 2 | Anzahl dargestellter Shards |
| 16 | 8 | höchste lückenlos empfangene Sequence Number |
| 24 | 2 | Receiver Window in Generationen |
| 26 | 2 | gewünschte FPS × 100, 0=unverändert |
| 28 | variabel | Bitmap, MSB-zuerst; 1=vorhanden, 0=fehlend |

Flow Actions: `0=NONE`, `1=PAUSE`, `2=RESUME`, `3=SLOW_DOWN`, `4=SPEED_UP`.
ACKs bestätigen Generationen kumulativ und selektiv, niemals zwingend jeden Frame.
LINK darf fehlende Shards mit gesetztem RETRANSMISSION-Flag wiederholen. STREAM darf
nicht von ACK oder Retransmission abhängen und verlässt sich auf Generation-ECC und
optionale Wiederholungszyklen.

## 27. Capability Bits

`CAPABILITY_BITS` ist ein Big-Endian-`u64`:

| Bit | Fähigkeit |
|---:|---|
| 0 | Kamera/getUserMedia |
| 1 | File System Access API |
| 2 | OPFS |
| 3 | Web Crypto AES-GCM |
| 4 | Web Worker |
| 5 | Screen Wake Lock |
| 6 | Fullscreen |
| 7 | OffscreenCanvas |
| 8 | ECDH P-256 |
| 9 | IndexedDB Resume |
| 10 | SharedArrayBuffer, optional |

Zusätzlich können Implementierungen lokale Limits anzeigen; Bits allein versprechen
keine Performance.

## 28. Große Dateien, Storage und Resume

Sender lesen höchstens eine Generation mit `Blob.slice()`/`File.stream()` und halten
nur aktuelle DATA/PARITY-Shards. Der SHA-256 wird inkrementell über Chunks in
Dateireihenfolge berechnet. Web Crypto besitzt kein Streaming-Digest; eine konforme
kleine inkrementelle SHA-256-Implementierung ist zulässig.

Empfänger schreiben verifizierte Klartextchunks sofort positionsgenau:

1. bevorzugt in einen explizit gewählten File-System-Access-Handle;
2. sonst in eine OPFS-Temporärdatei mit Random-Access;
3. sonst segmentiert in IndexedDB;
4. nur für kleine Dateien in einen klar begrenzten RAM-Fallback.

Resume-Metadaten enthalten Session-/Stream-/File-ID, Parameter, abgeschlossene
Generationen und Zielreferenz, aber niemals Passwort oder nicht ausdrücklich
persistierte Sessionkeys. Ohne Schlüssel kann ein verschlüsselter Transfer nach Reload
nur nach erneuter Passworteingabe bzw. neuem autorisiertem Handshake fortgesetzt werden.

Offsets in Protokoll und Resume bleiben `u64`. Browser-Datei-APIs mit JavaScript-
`Number` werden nur bis `2^53-1` verwendet; darüber muss sauber abgelehnt werden.
Eine 20-GiB-Datei benötigt somit keinen proportionalen RAM, ist aber optisch je nach
Datenrate möglicherweise viele Stunden oder Tage unterwegs.

## 29. SHA-256 und END

SHA-256 wird über exakt `originalFileSize` Klartextbytes in Offsetreihenfolge gebildet.
END trägt TLV SHA256 mit 32 Bytes sowie die finalen Größen im Basisheader. END darf
wiederholt werden. Ein Empfänger meldet DONE/VERIFIED erst, wenn:

- alle Dateiintervalle lückenlos geschrieben sind;
- alle DATA-Blöcke CRC-/AEAD-validiert wurden;
- die Originalgröße exakt stimmt;
- der inkrementelle bzw. aus Storage nachgelesene SHA-256 dem END-Wert entspricht.

Bei Abweichung ist der Zustand ERROR; ein Download als verifiziert darf nicht angeboten
werden.

## 30. State Machines

Senderzustände und zulässige Hauptpfade:

`IDLE → PREPARING → [PAIRING] → SYNC → STREAMING ↔ PAUSED → FINISHING → DONE`.
Jeder aktive Zustand darf nach `ERROR`; RESET/Abbruch führt geordnet nach IDLE.

Empfänger:

`IDLE → SEARCHING → SYNCING → RECEIVING ↔ RECOVERING → VERIFYING → DONE`.
Kamera-Lock-Unterzustände sind `SEARCHING, SYNCING, LOCKED, RECALIBRATING, DEGRADED,
LOST`. Ungültige Übergänge sind Implementierungsfehler und müssen abgelehnt werden.

## 31. Pause, Resume und END-Behandlung

PAUSED zeigt einen stabilen SYNC- oder PAUSE-Control-Frame und erzeugt keine neuen
DATA-Sequenzen. Resume setzt Generation/Chunk deterministisch fort; bereits vollständig
bestätigte/gespeicherte Generationen werden nicht neu geschrieben. Im unidirektionalen
STREAM kann der Sender konservativ am letzten Generationanfang wiederholen.

SESSION_CLOSE beendet eine LINK-Session nach ACK; RESET verwirft nur den explizit
adressierten Sessionzustand. END ohne passenden SYNC, falsche Identitäten, nicht
abgeschlossene Generationen oder falschen Hash führt niemals zu DONE.

## 32. Kapazitätsbuchhaltung

Für ein Raster ist auszuweisen:

- `totalModules = N²`
- `reservedModules`: Quiet-Zone-exklusive Funktionsmodule
- `calibrationModules`: SYNC 64, DATA 16 Anchor-Module
- `sequenceModules`: 5×24 = 120, abzüglich geometrischer Überlappungen (in v2 keine)
- `payloadModules`: alle verbleibenden enumerierten Datenmodule
- `rawBytes = floor(payloadModules/2)`
- `frameEccBytes = blockCount × parityPerBlock`
- `rsDataBytes = rawBytes - frameEccBytes`
- `netPlainBytes = rsDataBytes - 136 - (encrypted ? 16 : 0)`

Headerbytes werden durch Interleaving räumlich verteilt; `headerModules=272` bezeichnet
ihre logische Farbfeldmenge, nicht einen zusammenhängenden Bildbereich.

Bruttorate ist `rawBytes × angezeigte FPS`; Nettorate berücksichtigt Header, lokale
ECC, Generationparität, SYNC-Frequenz, Duplikate und Verluste. Zeitprognosen werden aus
gemessener/aktueller Nettorate berechnet, nicht aus nominalen 30 FPS.

## 33. Benchmark, Auto-Tuning und Messwerte

Benchmarkframes setzen Flag BENCHMARK und verwenden synthetische, nicht persistierte
Daten. Zu messen sind mindestens: dekodierte/fehlgeschlagene/duplizierte Frames,
Finder-, Perspektiv-, Farbsampling-, Header-, ECC-, Crypto-, Storage- und Gesamtdauer,
Confidence, Netto-Durchsatz und Fehlerrate.

Auto-Tuning beginnt mit großem Farbmuster, prüft Raster und läuft konservativ über
5/10/15 FPS. Eine höhere Rate wird nur gewählt, wenn Sequenzkonsistenz, Decode-Latenz
und Verlustquote sie tragen. 60-/120-Hz-Display und 30-/60-FPS-Kamera garantieren keine
entsprechende optische Framerate; Exposure, PWM, Moiré und Rolling Shutter begrenzen
die reale Rate.

## 34. Sicherheits- und Fehlerregeln

- Keine Datei-/Metadaten werden ausgeführt oder ungefragt dargestellt.
- AEAD-Fehler, falsche Schlüssel und falsche Sessionidentitäten werden hart verworfen.
- Nonce-Wiederverwendung ist ein Sessionfehler und muss vor Verschlüsselung erkannt
  werden.
- Schlüssel sind standardmäßig nur im RAM; Passwörter werden nie persistiert.
- CRC32 ersetzt weder AEAD noch HMAC.
- Ein Mischbild mit widersprüchlichen Sequenzmarkern wird vor Header/ECC verworfen.
- Unbekannte Version, unbekannter kritischer TLV, ungültige Reserven oder Überläufe
  werden sauber abgelehnt.
- Capability Negotiation darf nie zu einem schwächeren Verfahren als vom Benutzer
  verlangt downgraden.

## 35. Realistische Leistungsgrenzen

Version 2 adressiert und verarbeitet Multi-GB-Dateien mit konstantem, generations-
begrenztem RAM. Das ist keine Aussage, dass optische Übertragung dieser Größen praktisch
schnell ist. Beispiel: 420 KiB/s ergeben ideal etwa 41,6 Minuten/GiB und 6,9 Stunden für
10 GiB; reale Kameraquoten können deutlich niedriger liegen. Der Sender muss deshalb
geschätzte Dauer, tatsächliche Netto-Datenrate und Storage-Fallback transparent zeigen.

---

# Teil III – ColorCode Protocol Version 3 (verbindlich)

## 36. Geltungsbereich, Versionserkennung und Schichten

V3 erweitert die optische und adaptive Transportschicht; die V1-STATIC- und
V2-STATIC/STREAM/LINK-Decoder bleiben verfügbar. Die Versionswahl ist eindeutig:

1. `CC2D` bezeichnet Legacy V1 STATIC.
2. `CCS2`, Version `0x02`, bezeichnet V2 mit festem C16.
3. Ein gültiger binärer `CB3`-Bootstrap bezeichnet V3; der danach dekodierte
   Transportheader muss `CCS3`, Version `0x03`, enthalten.

Nach einer gültigen Kennung darf nicht heuristisch auf eine andere Version
zurückgefallen werden. Unbekannte Versionen, Modulationen, Paletten oder Grid-Versionen
werden abgelehnt. V2-C16 besitzt keinen B1-Bootstrap und reserviert Headerbytes 128–131
mit Null; V3-C16 besitzt immer `CB3` und nutzt diese Bytes. Beide sind unterscheidbar.

Alle Mehrbyte-Integer sind Big Endian. Bit 7 ist das höchstwertige Bytebit. Datei- und
Transportgrößen/Offsets bleiben u64 und werden in JavaScript als `BigInt` verarbeitet.
Die Schichten sind Application, File/Stream, Crypto, Transport, Generation Erasure ECC,
Local Frame ECC, Optical und Physical Color. Senderichtung:
`Chunk -> AEAD -> Generation ECC -> Header -> Local RS -> Bit Packing -> Modulation`;
Empfang ist exakt invers. Farbmodulation und -permutation sind keine Verschlüsselung.

## 37. Modulationen und Standardpaletten

### 37.1 Verbindliche IDs

| ID | Name | Bits/Modul | Zustände | Zweck |
|---:|---|---:|---:|---|
| `0x00` | B1 | 1 | 2 | robustester Modus, Bootstrap/Fallback |
| `0x01` | C4 | 2 | 4 | ausgewogener V3-Standard |
| `0x02` | C8 | 3 | 8 | Performance bei guten Bedingungen |
| `0x03` | C16 | 4 | 16 | experimentelle hohe Dichte |
| `0x04`–`0xFF` | reserved | – | – | ablehnen |

Die Standard-Palette hat dieselbe ID wie ihre Modulation; andere Palette IDs sind
reserviert. STATIC verwendet ohne Messung C4, STREAM/LINK standardmäßig Auto. Der
Empfänger liest die Modulation ausschließlich aus B1-`CB3`, niemals durch Farbraten.

### 37.2 Nominale Paletten

Die Werte sind sRGB sowie Referenz-CIELAB D65/2°. Klassifiziert wird gegen die im Bild
gemessenen Referenzen, nicht gegen nominale RGB-Werte.

**`0x00 palette-b1`**

| Symbol | sRGB | L*,a*,b* |
|---:|---|---|
| 0 | `#101318` | 5.80, 0.05, -3.67 |
| 1 | `#F7F7F2` | 97.11, -0.87, 2.40 |

Kleinster nominaler ΔE76: 91.52.

**`0x01 palette-c4`**

| Symbol | sRGB | L*,a*,b* |
|---:|---|---|
| 0 | `#172554` | 16.23, 12.01, -30.45 |
| 1 | `#DC2626` | 47.94, 66.94, 46.91 |
| 2 | `#16A34A` | 58.84, -54.43, 36.21 |
| 3 | `#FDE047` | 89.23, -5.72, 74.22 |

Kleinster nominaler ΔE76: 68.86.

**`0x02 palette-c8`**

| Symbol | sRGB | L*,a*,b* |
|---:|---|---|
| 0 | `#2F2F7A` | 23.81, 24.12, -42.90 |
| 1 | `#3434C9` | 32.70, 48.89, -76.25 |
| 2 | `#2F7A2F` | 45.26, -39.19, 33.64 |
| 3 | `#D93636` | 49.08, 62.17, 39.25 |
| 4 | `#D936A8` | 52.09, 71.62, -24.64 |
| 5 | `#31A8D0` | 64.33, -18.99, -29.62 |
| 6 | `#D0A831` | 70.62, 3.10, 62.86 |
| 7 | `#31C94B` | 71.51, -63.72, 50.64 |

Kleinster nominaler ΔE76: 39.75.

**`0x03 palette-c16`** (die eigenständig für 16 Zustände entworfene V2-Palette)

| Sym. | sRGB | L*,a*,b* | Sym. | sRGB | L*,a*,b* |
|---:|---|---|---:|---|---|
| 0 | `#7F1D1D` | 28.06,41.42,25.75 | 8 | `#1D4ED8` | 39.01,36.76,-74.57 |
| 1 | `#EF4444` | 54.98,64.69,39.13 | 9 | `#60A5FA` | 66.75,3.51,-48.82 |
| 2 | `#F97316` | 63.73,47.05,67.61 | 10 | `#581C87` | 25.73,46.44,-47.77 |
| 3 | `#FACC15` | 83.74,1.74,82.46 | 11 | `#A855F7` | 53.53,62.66,-67.97 |
| 4 | `#65A30D` | 60.77,-40.96,60.75 | 12 | `#DB2777` | 49.31,70.87,0.56 |
| 5 | `#22C55E` | 70.23,-61.72,40.30 | 13 | `#F9A8D4` | 77.82,36.03,-10.09 |
| 6 | `#0F766E` | 44.49,-28.80,-3.37 | 14 | `#854D0E` | 38.42,19.05,43.31 |
| 7 | `#22D3EE` | 77.93,-32.13,-25.08 | 15 | `#94A3B8` | 66.48,-0.73,-12.59 |

Kleinster nominaler ΔE76: 30.26. C4 und C8 sind eigene Maximin-Paletten und keine
abgeschnittenen C16-Präfixe.

### 37.3 Farbpermutation

Die V2-HKDF-Schlüsseltrennung gilt weiter. Aus `ColorPermutationKey`, Session-ID,
Stream-ID, Palette-ID und Profile-ID wird per HMAC-SHA-256 ein Fisher-Yates-Seed
abgeleitet. Fisher-Yates läuft von `n-1` bis 1; Rejection Sampling bildet ohne
Modulo-Bias auf `[0,i]` ab. `n` ist 2, 4, 8 oder 16. Bootstrap, Finder, Timing und
Sequenzmarker bleiben kanonisches, unpermutiertes B1. Kalibrierung benennt nur
physikalische Cluster. Kryptographische Sicherheit liefert ausschließlich AEAD.

## 38. Modulationsunabhängiger B1-Bootstrap

### 38.1 Code, Größe und Layout

Jeder V3-Frame enthält drei disjunkte Kopien. Eine Kopie ist 32 Bytes/256 B1-Module:
24 systematische Bytes plus 8 Reed-Solomon-Paritätsbytes. Alle drei belegen 768 Module.
RS verwendet die vorhandene GF(256)-Implementierung, primitives Polynom `0x11D`,
verkürztes systematisches RS(32,24), `fcr=0`, Generator 2.

| Offset | Länge | Feld |
|---:|---:|---|
| 0 | 3 | ASCII `CB3` |
| 3 | 1 | Protocol Version `0x03` |
| 4 | 1 | Bits 7–6 Mode: 0 STATIC, 1 STREAM, 2 LINK; Bits 5–0 Frame Type |
| 5 | 1 | Bits 7–6 Modulation ID; Bits 5–0 Palette ID |
| 6 | 1 | Grid Size in Modulen |
| 7 | 1 | Bits 7–6 Local-ECC-Profil; Bits 5–0 Profile-ID low 6 |
| 8 | 3 | Session Short ID u24 BE |
| 11 | 4 | Sequence Number low 32 u32 BE |
| 15 | 2 | Generation ID low 16 u16 BE |
| 17 | 2 | `opticalBitLength` u16 BE |
| 19 | 1 | Bootstrap Length, immer 24 |
| 20 | 4 | CRC32 über Bytes 0–19 u32 BE |
| 24 | 8 | RS-Parität über Bytes 0–23 |

ECC-Profile 0,1,2,3 bedeuten 16,32,48,64 lokale Nutzdaten-Paritätsbytes.
`sessionShort` sind die niederwertigen 24 Bit von CRC32 über die vollständige
16-Byte-Session-ID und nur ein Demultiplex-Hinweis. `opticalBitLength` ist die exakte
Zahl bedeutungstragender Bits des lokal RS-codierten Byte-Stroms vor Symbol-Padding.
Aktuelle Grids dürfen höchstens 65535 solcher Bits tragen.

### 38.2 B1-Abbildung und Positionen

Bootstrap-Bytes werden MSB-first in kanonisches Schwarz/Weiß geschrieben. Nach
Reservierung von Finder, Rand, Timing, Sequenz und Kalibrierung werden freie Zellen als
`(y,x)` row-major sortiert:

1. `TOP` erhält die ersten 256 Kandidaten.
2. `BOTTOM` erhält die letzten 256 Kandidaten in umgekehrter Reihenfolge.
3. `SIDE` erhält aus dem Rest die ersten 256 nach
   `(min(x,N-1-x), y, x)` aufsteigend.
4. Alle übrigen Zellen sind Nutzdaten und werden row-major traversiert.

Ein Grid mit weniger als 768 Kandidaten oder ohne Raum für Header, Local ECC und ein
Payload-Byte ist im betreffenden Profil ungültig und muss abgelehnt werden.

### 38.3 Annahme und Transition-Erkennung

Jede Kopie wird separat mit B1-Confidence, RS-Erasures und CRC dekodiert. Mindestens eine
muss gültig sein. Alle gültigen Kopien müssen byteidentisch sein. Zwei widersprüchliche
gültige Kopien – insbesondere verschiedene Sequence-, Profile- oder Modulation-Werte –
bedeuten Rolling-Shutter-/Display-Transition; der Frame wird verworfen. Eine ungültige
Kopie überstimmt keine gültige. Bootstrap-CRC ist Erkennung, keine Authentifizierung.

## 39. V3-Transportheader und Frame Types

V3 behält den 136-Byte-V2-Basisheader. Magic ist `CCS3`, Version `0x03`. Offsets 0–127
behalten ihre V2-Semantik. Die bisherige Reserve wird belegt:

| Offset | Länge | Feld |
|---:|---:|---|
| 128 | 1 | Modulation ID |
| 129 | 1 | Palette ID |
| 130 | 2 | Profile ID u16 BE |
| 132 | 4 | Header CRC32 über Bytes 0–131 u32 BE |

Bootstrap und voller Header müssen bei Frame Type, Modulation, Palette, Grid, Profil,
Sequence, Generation und Session Short übereinstimmen.

V2-Typen `0x01 SYNC`, `0x02 DATA`, `0x03 PARITY`, `0x04 ACK`, `0x05 NACK`,
`0x06 HELLO`, `0x07 CAPABILITIES`, `0x08 SESSION_INIT`, `0x09 SESSION_ACCEPT`,
`0x0A SESSION_CLOSE`, `0x0B KEEPALIVE`, `0x0C RESET`, `0x0D END` bleiben unverändert.
V3 ergänzt:

| Wert | Typ | Wert | Typ |
|---:|---|---:|---|
| `0x0E` | MODULATION_CHANGE | `0x14` | TRAINING_START |
| `0x0F` | MODULATION_ACK | `0x15` | TRAINING_RESULT |
| `0x10` | MODULATION_PROBE | `0x16` | TRAINING_END |
| `0x11` | PROBE_RESULT | `0x17` | RESET_TO_B1 |
| `0x12` | PROFILE_CHANGE | `0x18`–`0x3F` | reserved |
| `0x13` | PROFILE_ACK | | |

Neue Extension-TLVs sind `0x0A TRANSMISSION_PROFILE`, `0x0B TRAINING_METRICS`,
`0x0C MODULATION_CONTROL`, `0x0D PROBE_METRICS`. Unbekannte kritische TLVs werden
abgelehnt.

## 40. Transmission Profile

`TRANSMISSION_PROFILE` ist genau 24 Bytes:

| Offset | Länge | Feld |
|---:|---:|---|
| 0 | 1 | Format Version = 1 |
| 1 | 1 | Modulation ID |
| 2 | 1 | Palette ID |
| 3 | 1 | Local RS parity bytes: 16,32,48,64 |
| 4 | 2 | Profile ID u16 BE |
| 6 | 2 | FPS × 100 u16 BE |
| 8 | 2 | Grid Size u16 BE |
| 10 | 2 | nominale Module Size in CSS-Pixeln u16 BE, nur Hinweis |
| 12 | 2 | Generation K u16 BE |
| 14 | 2 | Generation R u16 BE |
| 16 | 2 | SYNC interval in DATA-Frames u16 BE |
| 18 | 2 | Stability Margin in Basispunkten; 1000 = 10 % |
| 20 | 2 | Flags u16 BE |
| 22 | 2 | reserviert, Null |

Grid und Module Size sind vorbereitet für gemeinsames Tuning. Eine Änderung erzeugt
eine neue Profile-ID. Die Bootstrap-ID low 6 ist nur ein früher Hinweis.

## 41. Optisches Layout, Kalibrierung und Sequenz

Finder und Quiet Zone bleiben geometrisch kompatibel zum bestehenden Detektor. Die
Reservierungsreihenfolge ist Finder/Orientierung, B1-Timing, fünf verteilte
B1-Sequenzmarker, Kalibrierung, drei Bootstraps, Payload.

SYNC und STATIC reservieren 18 Referenzblöcke à 2×2 Module in einem 18×4-Modul-Feld:
zuerst kanonisches Schwarz und Weiß, danach alle physikalischen Palettenfarben. B1 nutzt
2, C4 6, C8 10, C16 18 Blöcke; unbenutzte Blöcke bleiben Weiß und sind nie Payload.
DATA/PARITY behalten acht 2×2-Anchors mit zyklisch Schwarz, Weiß und Palettenfarben:
`(sequenceNumber + anchorIndex) mod (n+2)`.

Timing und alle fünf Sequenzmarker sind immer kanonisches B1. Ein Marker enthält
Gray-codiert dieselben niederwertigen Sequence-/Profile-Bits plus Parität. Gültige Marker
müssen untereinander und mit Bootstrap übereinstimmen, sonst wird der Mischframe
verworfen. Kritische Bereiche sind B1; nur Payload benutzt die gewählte Modulation.

## 42. Bit Packing, Traversal und Kapazität

Der lokal RS-codierte Byte-Strom wird kontinuierlich MSB-first gelesen: Bytes in
Transportreihenfolge, innerhalb jedes Bytes Bit 7 bis 0. Gruppen zu `b=1,2,3,4` Bits
werden als Symbolbinärzahl interpretiert, erstes Bit ist das höchstwertige Symbolbit.
Eine letzte unvollständige Gruppe wird rechts/niederwertig mit Null aufgefüllt. Decoder
verketten Symbolbits MSB-first und schneiden bei `opticalBitLength` ab.

Damit gilt auch für C8 ohne Sonderfall: beispielsweise beginnt `10110011 010...` mit
den Symbolen `101`, `100`, `110`, ...; Bytegrenzen werden überschritten. Padding ist
nie Nutzlast. B1/C4/C8/C16 verwenden Symbolwerte 0..1/3/7/15.

Für `P` Payloadmodule und `b` Bits/Modul:

`rawCodewordBytes = floor(P * b / 8)`

`netPayloadBytes = max(0, rawCodewordBytes - localParityBytes - 136 - extensionLength)`

Local RS arbeitet auf Bytes vor räumlichem Interleaving und Symbol-Packing. Unsichere
Symbole markieren alle berührten Bytes als Erasures. Kapazitäten werden aus der
normativen Reservierungsmaske berechnet, niemals durch Abschneiden passend gemacht.

## 43. Aufnahme, Normalisierung, Lab und Confidence

Nur der innere Modulbereich wird gesampelt; Ränder entfallen. Pro Kanal wird Median oder
20-%-getrimmter Mittelwert gebildet. Gemessene Schwarz-/Weißreferenzen bestimmen Offset,
Kanal-Gain und Luminanzspanne. Die normalisierte sRGB-Probe wird begrenzt, linearisiert
und in CIELAB D65 transformiert. Entscheidung ist ΔE76 gegen gemessene Cluster, nie nur
RGB-Euklid.

Für besten/zweiten Abstand `d1`,`d2` gilt `margin=(d2-d1)/(d2+1)`. Mit
Referenzvarianz `sigmaRef`, Samplevarianz `sigmaSample` und lokaler
Luminanzabweichung `deltaL`:

`confidence = clamp(margin * exp(-sigmaSample/18) * exp(-sigmaRef/22) *
exp(-abs(deltaL)/35), 0, 1)`

0 bedeutet unbrauchbar, 1 eindeutig. Standard-Erasure-Schwellen: B1 0.20, C4 0.28,
C8 0.36, C16 0.44. Außergewöhnlich großes `d1` oder kollabierte Schwarz-/Weißspanne
erzeugt ebenfalls eine Erasure. Debugdaten enthalten d1, d2, Margin, Varianzen,
Confidence und Nachbarabstand.

SYNC ersetzt Kalibrierung erst nach gültigem Bootstrap/Header/CRC. Sichere DATA-Anchors
aktualisieren per `referenceNew = 0.9*referenceOld + 0.1*measurement`. Ein Sprung >25
ΔE, Luminanzspannungsänderung >20 % oder Kanal-Gain außerhalb 0.6–1.6 wird nicht blind
übernommen und setzt RECALIBRATING/DEGRADED.

## 44. Training und Auto-Auswahl

Training: TRAINING_START, deterministische Testframes pro Profil, TRAINING_RESULT,
TRAINING_END. Empfohlene FPS: B1/C4 10,15,20,30; C8 5,10,15,20; C16 2,5,10,15.
Ungeeignete Gerätewerte dürfen ausgelassen werden; Testdaten sind keine Dateidaten.

`TRAINING_METRICS` ist 44 Bytes:

| Off. | Len. | Feld | Off. | Len. | Feld |
|---:|---:|---|---:|---:|---|
| 0 | 1 | Metrics Version=1 | 24 | 2 | failed ECC frames u16 |
| 1 | 1 | Modulation | 26 | 2 | CRC failures u16 |
| 2 | 1 | Palette | 28 | 2 | avg confidence Q0.16 |
| 3 | 1 | Flags | 30 | 2 | worst confidence Q0.16 |
| 4 | 2 | tested frames u16 | 32 | 2 | min cluster ΔE ×100 |
| 6 | 2 | successful frames u16 | 34 | 4 | avg decode µs u32 |
| 8 | 4 | decoded modules u32 | 38 | 2 | successful FPS ×100 |
| 12 | 4 | symbol errors u32 | 40 | 4 | measured net bytes/s |
| 16 | 4 | uncertain modules u32 | | | |
| 20 | 4 | ECC-corrected bytes u32 | | | |

`effectiveNetThroughput = netPayloadBytesPerFrame * successfulFramesPerSecond * safety`.
Standard-Sicherheitsfaktoren: B1 0.98, C4 0.95, C8 0.90, C16 0.82. CRC-instabile oder
zu unsichere Profile sind ineligible. Von robust nach dicht wird nur gewechselt, wenn
das dichtere Profil mindestens Stability Margin (Standard 10 %) schneller ist; sonst
gewinnt das robustere. UI-Werte tragen `simulated`, `estimated` oder `measured`.

## 45. Modulations-/Profilwechsel, Probe und Hysterese

`MODULATION_CONTROL` ist genau 26 Bytes:

| Offset | Länge | Feld |
|---:|---:|---|
| 0 | 1 | Format Version = 1 |
| 1 | 1 | current modulation |
| 2 | 1 | requested modulation |
| 3 | 1 | Reason Code |
| 4 | 2 | requested Profile ID u16 BE |
| 6 | 8 | effective Sequence u64 BE |
| 14 | 8 | effective Generation u64 BE |
| 22 | 4 | CRC32 über Bytes 0–21 u32 BE |

Reasons: 0 MANUAL, 1 HIGH_ERROR_RATE, 2 LOW_CONFIDENCE, 3 LOCK_LOSS, 4 PROBE_OK,
5 THROUGHPUT_GAIN, 6 RECEIVER_LOAD, 7 EMERGENCY. Control wird zusätzlich durch die
bestehende Headerauthentifizierung/AEAD-AAD gebunden. ACK spiegelt Session, Stream,
Profil, effektive Sequence und Generation exakt.

Wechsel erfolgen nur an Generationengrenzen:
`CHANGE -> ACK -> alter Abschluss -> neuer B1-SYNC -> Kalibrierung -> DATA`.
Ohne ACK bleibt LINK im alten Profil. Einseitiges STREAM wechselt nicht während der
Übertragung.

Downgrade standardmäßig eine Stufe bei Framefehler >8 %, steigenden CRC/ECC-Fehlern oder
Confidence <0.30; Lockverlust darf direkt RESET_TO_B1 auslösen. Upgrade erst bei Fehler
<1.5 %, Confidence >0.70, mindestens 8 s stabil, 10 s Cooldown, erfolgreicher Probe und
>Stability-Margin erwartetem Nettogewinn. Die verschiedenen Schwellen bilden Hysterese.

MODULATION_PROBE enthält Zielprofil, zufällige Probe-ID und deterministische Testdaten,
keine Dateidaten. PROBE_RESULT verwendet `PROBE_METRICS` im Training-Layout plus
Probe-ID. Upgrade erfordert positives Resultat; Downgrade nicht. RESET_TO_B1 trägt
kanonischen B1-Bootstrap und B1-Control, verwirft ausstehende Wechsel und erzwingt einen
vollständigen B1-SYNC.

## 46. Betriebsarten und State Machines

STATIC-V3 enthält in jedem Bild Finder, Bootstraps, vollständige Kalibrierung, Header,
Local ECC und Payload; PNG/JPEG-Import nutzt dieselbe automatische Pipeline.

STREAM startet und wiederholt SYNC im Profilintervall. DATA/PARITY verwenden gelockte
Homographie, B1-Bootstrap/-Timing/-Sequence und laufende Anchors. Ohne Rückkanal bleibt
das vorab gewählte konservative Profil fix; Generations-ECC behandelt Frameverlust.

LINK darf Training, ACK, Flow Control, Probe und Wechsel verwenden. Nach jedem Wechsel
ist SYNC Pflicht. AES-256-GCM, Nonce-Schema, P-256-ECDH/HKDF, Passwort-KDF,
Generationen, Storage und Resume behalten V2-Regeln; Modulation/Profile werden zusätzlich
in Headerauthentifizierung/AAD gebunden.

Sender: `IDLE, PREPARING, TRAINING, PAIRING, SYNC, STREAMING, PROBING,
SWITCHING_PROFILE, PAUSED, FINISHING, DONE, ERROR`.

Empfänger: `IDLE, SEARCHING, SYNCING, TRAINING, LOCKED, RECEIVING, RECOVERING,
PROBING, RECALIBRATING, DEGRADED, LOST, VERIFYING, DONE, ERROR`.

SWITCHING_PROFILE darf ohne passendes ACK und neuen SYNC nicht zu STREAMING werden.

## 47. Kapazität, große Dateien und Grenzen

Bei gleichem Payloadmodulcount ist die Rohdichte B1:C4:C8:C16 = 1:2:3:4; Bootstrap,
Header, Kalibrierung und ECC sind feste/modusabhängige Abzüge. Kleine Grids können in B1
keine Nutzkapazität besitzen und werden deaktiviert. C16 ist Experimental und nur bei
hoher gemessener Trennung automatisch zulässig. B1/C4 können eher für Druckversuche
geeignet sein, ohne unbelegte Robustheitszusage.

V3 ändert das Multi-GB-Prinzip nicht: File/Blob wird chunk-/generationsweise gelesen,
Offsets sind u64, OPFS/File-System-Access oder segmentierter Fallback verhindern RAM
proportional zur Dateigröße. Displayrefresh, Fokus, PWM, Moiré und Rolling Shutter
begrenzen reale Datenraten. Simulatorwerte sind keine realen Gerätebenchmarks.

## 48. Normative V3-Abnahme

Pflichttests: Roundtrip B1/C4/C8/C16; C8 für alle Bytewerte/Längen; automatische
Bootstrap-Erkennung und zerstörte C16-Payload bei lesbarem B1-Bootstrap; Konfliktkopien
und Sequenzmarker; Helligkeit, Kanal-Gain, Gamma und Drift; Confidence/Erasures; Auto-
Fälle, in denen jeweils B1/C4/C8/C16 gewinnt; Margin/Hysterese; Probe/ACK/Generation-
Wechsel/RESET_TO_B1; Farbpermutation; verschlüsselter SHA-256-identischer Roundtrip pro
Modus sowie alle V1/V2-Regressionen.

Der Vergleichssimulator verwendet denselben Payload unter PERFECT, GOOD, MEDIUM, BAD
und VERY_BAD und meldet Rohkapazität, Erfolg, Symbolfehler, ECC-Recovery, Frameverlust,
Netto und Decodezeit. Ergebnisse müssen ausdrücklich als simuliert markiert sein.
