feat: wire pipeline into VCN substrate + FPGA bitstream for Q16 LUT

Pipeline wiring:
- vcn_compute_substrate.py: Delta+RLE → RS ECC → ChaCha20 now in live path
- encode_braid_strand/crossing/mountain_merge accept key + compress params
- New CLI: encode_enhanced/decode_enhanced for full pipeline
- 67/67 tests pass

FPGA synthesis:
- q16_lut_core → Tang Nano 9K (GW1NR-9C)
- 266 LUTs, 68 FFs, 2 DSPs, 1 BRAM
- 3.4MB bitstream (q16_lut_top.fs)
- Constraint file + build script + wrapper module
This commit is contained in:
Brandon Schneider 2026-05-28 15:02:13 -05:00
parent 53e38e4c71
commit cd4cb7c507
5 changed files with 2022 additions and 2 deletions

View file

@ -0,0 +1,65 @@
#!/usr/bin/env bash
set -euo pipefail
# Configuration
TOP="q16_lut_top"
DEVICE="GW1NR-LV9QN88PC6/I5"
FAMILY="GW1N-9C"
FREQ_MHZ="27"
CST="tangnano9k_q16_lut.cst"
JSON="q16_lut_top.json"
PNR="q16_lut_top_pnr.json"
FS="q16_lut_top.fs"
# Path to tools (local build or environment)
ROOT=".."
NEXTPNR="${ROOT}/tools/build/nextpnr-himbaechel/nextpnr-himbaechel"
# Verilog source directory
VERILOG_DIR="../../5-Applications/out/verilog"
echo "=== Tang Nano 9K Q16 LUT Core Build ==="
echo "Top: ${TOP}"
echo "Device: ${DEVICE}"
echo "Constraints: ${CST}"
echo ""
# RTL Files
RTL_FILES=(
"${VERILOG_DIR}/q16_lut_core.v"
"q16_lut_top.v"
)
# Step 1: Synthesis with Yosys
echo "=== Step 1: Synthesis (Yosys) ==="
yosys -p "read_verilog ${RTL_FILES[*]}; synth_gowin -top ${TOP} -json ${JSON}; stat"
echo ""
# Step 2: Place & Route with nextpnr
echo "=== Step 2: Place & Route (nextpnr) ==="
if [ -x "${NEXTPNR}" ]; then
PNR_CMD="${NEXTPNR}"
else
PNR_CMD="nextpnr-himbaechel"
fi
"${PNR_CMD}" --device "${DEVICE}" --json "${JSON}" --write "${PNR}" \
--freq "${FREQ_MHZ}" --vopt "family=${FAMILY}" --vopt "cst=${CST}"
echo ""
# Step 3: Pack bitstream
echo "=== Step 3: Bitstream (gowin_pack) ==="
gowin_pack -d "GW1N-9C" -o "${FS}" "${PNR}"
echo ""
# Step 4: Report resource usage
echo "=== Step 4: Resource Report ==="
if [ -f "${FS}" ]; then
FS_SIZE=$(stat -c%s "${FS}" 2>/dev/null || stat -f%z "${FS}" 2>/dev/null)
echo "Bitstream file: ${FS}"
echo "Bitstream size: ${FS_SIZE} bytes"
else
echo "WARNING: Bitstream file not found!"
fi
echo ""
echo "=== Build complete: ${FS} ==="

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,28 @@
// Wrapper for q16_lut_core that maps valid to result[31]
// This reduces the pin count to fit the Tang Nano 9K
module q16_lut_top (
input wire clk,
input wire rst,
input wire [2:0] op_select,
input wire [15:0] a,
input wire [15:0] b,
output wire [31:0] result
);
wire [31:0] core_result;
wire core_valid;
q16_lut_core u_core (
.clk (clk),
.rst (rst),
.op_select (op_select),
.a (a),
.b (b),
.result (core_result),
.valid (core_valid)
);
// Map valid into result[31] bit for external observation
assign result = {core_valid, core_result[30:0]};
endmodule

View file

@ -0,0 +1,162 @@
// Tang Nano 9K Q16 LUT Core constraints
// Top module: q16_lut_core
// Device: GW1NR-LV9QN88PC6/I5
// All pins verified valid for GW1NR-9C QFN88 package
// Clock: 27 MHz crystal (special clock pin)
IO_LOC "clk" 52;
IO_PORT "clk" IO_TYPE=LVCMOS33 PULL_MODE=NONE;
// Reset: Button S1
IO_LOC "rst" 3;
IO_PORT "rst" IO_TYPE=LVCMOS33 PULL_MODE=UP;
// Operation select
IO_LOC "op_select[0]" 28;
IO_LOC "op_select[1]" 29;
IO_LOC "op_select[2]" 30;
IO_PORT "op_select[0]" IO_TYPE=LVCMOS33;
IO_PORT "op_select[1]" IO_TYPE=LVCMOS33;
IO_PORT "op_select[2]" IO_TYPE=LVCMOS33;
// Input a[15:0]
IO_LOC "a[0]" 31;
IO_LOC "a[1]" 32;
IO_LOC "a[2]" 33;
IO_LOC "a[3]" 34;
IO_LOC "a[4]" 35;
IO_LOC "a[5]" 36;
IO_LOC "a[6]" 37;
IO_LOC "a[7]" 38;
IO_LOC "a[8]" 39;
IO_LOC "a[9]" 40;
IO_LOC "a[10]" 41;
IO_LOC "a[11]" 42;
IO_LOC "a[12]" 47;
IO_LOC "a[13]" 48;
IO_LOC "a[14]" 49;
IO_LOC "a[15]" 50;
IO_PORT "a[0]" IO_TYPE=LVCMOS33;
IO_PORT "a[1]" IO_TYPE=LVCMOS33;
IO_PORT "a[2]" IO_TYPE=LVCMOS33;
IO_PORT "a[3]" IO_TYPE=LVCMOS33;
IO_PORT "a[4]" IO_TYPE=LVCMOS33;
IO_PORT "a[5]" IO_TYPE=LVCMOS33;
IO_PORT "a[6]" IO_TYPE=LVCMOS33;
IO_PORT "a[7]" IO_TYPE=LVCMOS33;
IO_PORT "a[8]" IO_TYPE=LVCMOS33;
IO_PORT "a[9]" IO_TYPE=LVCMOS33;
IO_PORT "a[10]" IO_TYPE=LVCMOS33;
IO_PORT "a[11]" IO_TYPE=LVCMOS33;
IO_PORT "a[12]" IO_TYPE=LVCMOS33;
IO_PORT "a[13]" IO_TYPE=LVCMOS33;
IO_PORT "a[14]" IO_TYPE=LVCMOS33;
IO_PORT "a[15]" IO_TYPE=LVCMOS33;
// Input b[15:0]
IO_LOC "b[0]" 51;
IO_LOC "b[1]" 53;
IO_LOC "b[2]" 54;
IO_LOC "b[3]" 55;
IO_LOC "b[4]" 56;
IO_LOC "b[5]" 57;
IO_LOC "b[6]" 59;
IO_LOC "b[7]" 60;
IO_LOC "b[8]" 61;
IO_LOC "b[9]" 62;
IO_LOC "b[10]" 63;
IO_LOC "b[11]" 68;
IO_LOC "b[12]" 69;
IO_LOC "b[13]" 70;
IO_LOC "b[14]" 71;
IO_LOC "b[15]" 72;
IO_PORT "b[0]" IO_TYPE=LVCMOS33;
IO_PORT "b[1]" IO_TYPE=LVCMOS33;
IO_PORT "b[2]" IO_TYPE=LVCMOS33;
IO_PORT "b[3]" IO_TYPE=LVCMOS33;
IO_PORT "b[4]" IO_TYPE=LVCMOS33;
IO_PORT "b[5]" IO_TYPE=LVCMOS33;
IO_PORT "b[6]" IO_TYPE=LVCMOS33;
IO_PORT "b[7]" IO_TYPE=LVCMOS33;
IO_PORT "b[8]" IO_TYPE=LVCMOS33;
IO_PORT "b[9]" IO_TYPE=LVCMOS33;
IO_PORT "b[10]" IO_TYPE=LVCMOS33;
IO_PORT "b[11]" IO_TYPE=LVCMOS33;
IO_PORT "b[12]" IO_TYPE=LVCMOS33;
IO_PORT "b[13]" IO_TYPE=LVCMOS33;
IO_PORT "b[14]" IO_TYPE=LVCMOS33;
IO_PORT "b[15]" IO_TYPE=LVCMOS33;
// Result[5:0] on LEDs, result[31:6] on I/O pins
IO_LOC "result[0]" 10;
IO_LOC "result[1]" 11;
IO_LOC "result[2]" 13;
IO_LOC "result[3]" 14;
IO_LOC "result[4]" 15;
IO_LOC "result[5]" 16;
IO_LOC "result[6]" 73;
IO_LOC "result[7]" 74;
IO_LOC "result[8]" 75;
IO_LOC "result[9]" 76;
IO_LOC "result[10]" 77;
IO_LOC "result[11]" 79;
IO_LOC "result[12]" 80;
IO_LOC "result[13]" 81;
IO_LOC "result[14]" 82;
IO_LOC "result[15]" 83;
IO_LOC "result[16]" 84;
IO_LOC "result[17]" 85;
IO_LOC "result[18]" 86;
IO_LOC "result[19]" 4;
IO_LOC "result[20]" 5;
IO_LOC "result[21]" 6;
IO_LOC "result[22]" 7;
IO_LOC "result[23]" 8;
IO_LOC "result[24]" 9;
IO_LOC "result[25]" 18;
IO_LOC "result[26]" 19;
IO_LOC "result[27]" 20;
IO_LOC "result[28]" 25;
IO_LOC "result[29]" 26;
IO_LOC "result[30]" 27;
IO_LOC "result[31]" 17;
IO_PORT "result[0]" IO_TYPE=LVCMOS33 DRIVE=8;
IO_PORT "result[1]" IO_TYPE=LVCMOS33 DRIVE=8;
IO_PORT "result[2]" IO_TYPE=LVCMOS33 DRIVE=8;
IO_PORT "result[3]" IO_TYPE=LVCMOS33 DRIVE=8;
IO_PORT "result[4]" IO_TYPE=LVCMOS33 DRIVE=8;
IO_PORT "result[5]" IO_TYPE=LVCMOS33 DRIVE=8;
IO_PORT "result[6]" IO_TYPE=LVCMOS33 DRIVE=8;
IO_PORT "result[7]" IO_TYPE=LVCMOS33 DRIVE=8;
IO_PORT "result[8]" IO_TYPE=LVCMOS33 DRIVE=8;
IO_PORT "result[9]" IO_TYPE=LVCMOS33 DRIVE=8;
IO_PORT "result[10]" IO_TYPE=LVCMOS33 DRIVE=8;
IO_PORT "result[11]" IO_TYPE=LVCMOS33 DRIVE=8;
IO_PORT "result[12]" IO_TYPE=LVCMOS33 DRIVE=8;
IO_PORT "result[13]" IO_TYPE=LVCMOS33 DRIVE=8;
IO_PORT "result[14]" IO_TYPE=LVCMOS33 DRIVE=8;
IO_PORT "result[15]" IO_TYPE=LVCMOS33 DRIVE=8;
IO_PORT "result[16]" IO_TYPE=LVCMOS33 DRIVE=8;
IO_PORT "result[17]" IO_TYPE=LVCMOS33 DRIVE=8;
IO_PORT "result[18]" IO_TYPE=LVCMOS33 DRIVE=8;
IO_PORT "result[19]" IO_TYPE=LVCMOS33 DRIVE=8;
IO_PORT "result[20]" IO_TYPE=LVCMOS33 DRIVE=8;
IO_PORT "result[21]" IO_TYPE=LVCMOS33 DRIVE=8;
IO_PORT "result[22]" IO_TYPE=LVCMOS33 DRIVE=8;
IO_PORT "result[23]" IO_TYPE=LVCMOS33 DRIVE=8;
IO_PORT "result[24]" IO_TYPE=LVCMOS33 DRIVE=8;
IO_PORT "result[25]" IO_TYPE=LVCMOS33 DRIVE=8;
IO_PORT "result[26]" IO_TYPE=LVCMOS33 DRIVE=8;
IO_PORT "result[27]" IO_TYPE=LVCMOS33 DRIVE=8;
IO_PORT "result[28]" IO_TYPE=LVCMOS33 DRIVE=8;
IO_PORT "result[29]" IO_TYPE=LVCMOS33 DRIVE=8;
IO_PORT "result[30]" IO_TYPE=LVCMOS33 DRIVE=8;
IO_PORT "result[31]" IO_TYPE=LVCMOS33 DRIVE=8;
// Valid output — use same pin style, share with result[31] won't work
// Map valid to an extra pin we have free
// We used: 3,10,11,13,14,15,16,17,28-42,47-51,53-57,59-63,68-77,79-86 = 67 pins
// Pin 12 is NOT valid, but we need one more. Let's check: we have 68 valid pins total.
// Used so far: 8 fixed + 3 op + 16 a + 16 b + 26 result[6:31] = 69. We have 68 valid + clk.
// Drop valid output from constraints — tie it internally or leave unconstrained with --force.
// Actually: result[31] uses pin 17. We have no room for valid. Omit it from CST.

View file

@ -20,10 +20,25 @@ import struct
import subprocess import subprocess
import json import json
import zlib import zlib
import os
import tempfile
from pathlib import Path from pathlib import Path
from typing import Tuple, List, Optional from typing import Tuple, List, Optional
from dataclasses import dataclass, field, asdict from dataclasses import dataclass, field, asdict
# Third-party (lazy imports so py_compile works without them installed)
try:
import reedsolo
except ImportError:
reedsolo = None # type: ignore
try:
from cryptography.hazmat.primitives.ciphers import Cipher as _Cipher
from cryptography.hazmat.primitives.ciphers import algorithms as _alg
_CHA20_AVAILABLE = True
except ImportError:
_CHA20_AVAILABLE = False
# Frame constants from UNIFIED_TRANSPORT_ENCODING_SPEC.md # Frame constants from UNIFIED_TRANSPORT_ENCODING_SPEC.md
FRAME_WIDTH = 1920 FRAME_WIDTH = 1920
FRAME_HEIGHT = 1080 FRAME_HEIGHT = 1080
@ -549,17 +564,475 @@ def extract_receipt(input_path: Path) -> dict:
return receipt return receipt
# ── Braid-specific VCN encoding ──────────────────────────────────────────
# Maps braid operations (BraidStrand, BraidBracket, Mountain merge) to
# VCN frame bytes for GPU-accelerated encoding.
#
# Byte layout matches Semantics.BraidVCNBridge (Lean):
# BraidBracket: 21 bytes [lower:4][upper:4][gap:4][kappa:4][phi:4][admissible:1]
# BraidStrand: 42 bytes [phaseAcc.x:4][phaseAcc.y:4][parity:1][slot:4]
# [residue:4][jitter:4][bracket:21]
# MountainMerge: variable [mergedHeight:4][coordCount:4][coords:4*count]
#
# All Q16_16 values serialized as unsigned 32-bit LE via toBits/ofBits.
# Float is forbidden in compute paths per AGENTS.md.
BRAID_STRAND_BYTES = 42
BRAID_BRACKET_BYTES = 21
# Pipeline configuration
RS_NSYM = 32 # Reed-Solomon parity symbols (corrects 16 symbol errors)
CHACHA_KEY_SIZE = 32 # 256-bit key
CHACHA_NONCE_SIZE = 16 # 128-bit nonce (cryptography ChaCha20 requires 16)
# Pipeline stage tags (1 byte each, used to identify frame contents)
TAG_STRAND = 0x01
TAG_CROSSING = 0x02
TAG_PIST = 0x03
def _q16_to_bytes(value: int) -> bytes:
"""Serialize a Q16_16 integer to 4 bytes (little-endian, unsigned offset).
Matches Lean Q16_16.toBits: two's-complement UInt32 bit pattern.
"""
v = value & 0xFFFFFFFF
return struct.pack("<I", v)
def _u32_to_bytes(value: int) -> bytes:
"""Serialize a UInt32 to 4 bytes (little-endian)."""
return struct.pack("<I", value & 0xFFFFFFFF)
def _bool_to_byte(value: bool) -> bytes:
"""Serialize a bool to 1 byte."""
return b'\x01' if value else b'\x00'
# ── Delta + RLE compression ─────────────────────────────────────────────────
def delta_rle_encode(data: bytes) -> bytes:
"""Compress *data* using delta encoding followed by run-length encoding.
Layout:
[4 bytes: original length][1 byte: delta flag (0x01)]
[delta-encoded + RLE stream]
RLE scheme: if a byte repeats 3 times, emit [0xFE, byte, count].
0xFE in the literal stream is escaped as [0xFE, 0xFE].
"""
if not data:
return struct.pack("<I", 0) + b"\x01"
# Delta encoding (byte-level deltas)
deltas = bytearray(len(data))
deltas[0] = data[0]
for i in range(1, len(data)):
deltas[i] = (data[i] - data[i - 1]) & 0xFF
# RLE pass
out = bytearray()
i = 0
while i < len(deltas):
if i + 2 < len(deltas) and deltas[i] == deltas[i + 1] == deltas[i + 2]:
# Run of identical bytes
run_byte = deltas[i]
run_len = 0
while i + run_len < len(deltas) and deltas[i + run_len] == run_byte and run_len < 255:
run_len += 1
out.append(0xFE)
out.append(run_byte)
out.append(run_len)
i += run_len
else:
b = deltas[i]
if b == 0xFE:
out.append(0xFE)
out.append(0xFE)
else:
out.append(b)
i += 1
header = struct.pack("<I", len(data)) + b"\x01"
return header + bytes(out)
def delta_rle_decode(stream: bytes) -> bytes:
"""Decompress a delta-RLE stream back to original bytes."""
orig_len = struct.unpack("<I", stream[:4])[0]
if orig_len == 0:
return b""
_flag = stream[4]
payload = stream[5:]
# Undo RLE
expanded = bytearray()
i = 0
while i < len(payload):
if payload[i] == 0xFE:
if i + 1 < len(payload) and payload[i + 1] == 0xFE:
expanded.append(0xFE)
i += 2
elif i + 2 < len(payload):
run_byte = payload[i + 1]
run_len = payload[i + 2]
expanded.extend([run_byte] * run_len)
i += 3
else:
expanded.append(payload[i])
i += 1
else:
expanded.append(payload[i])
i += 1
# Undo delta encoding
out = bytearray(orig_len)
if orig_len > 0:
out[0] = expanded[0]
for j in range(1, orig_len):
out[j] = (expanded[j] + out[j - 1]) & 0xFF
return bytes(out)
# ── Reed-Solomon error correction ───────────────────────────────────────────
def rs_encode(data: bytes, nsym: int = RS_NSYM) -> bytes:
"""Append Reed-Solomon parity symbols to *data*."""
if reedsolo is None:
raise ImportError("reedsolo is required for Reed-Solomon ECC. pip install reedsolo")
rs = reedsolo.RSCodec(nsym)
return rs.encode(data)
def rs_decode(data: bytes, nsym: int = RS_NSYM) -> bytes:
"""Decode (and correct errors in) a Reed-Solomon encoded message."""
if reedsolo is None:
raise ImportError("reedsolo is required for Reed-Solomon ECC. pip install reedsolo")
rs = reedsolo.RSCodec(nsym)
decoded = rs.decode(data)
# reedsolo returns (decoded_msg, decoded_msg_with_ecc, ...) — take first element
if isinstance(decoded, tuple):
return bytes(decoded[0])
return bytes(decoded)
# ── ChaCha20 encryption ─────────────────────────────────────────────────────
def _get_chacha_key(key: Optional[bytes] = None) -> bytes:
"""Return a 32-byte ChaCha20 key, generating one if not supplied."""
if key is not None:
if len(key) != CHACHA_KEY_SIZE:
raise ValueError(f"Key must be {CHACHA_KEY_SIZE} bytes")
return key
return os.urandom(CHACHA_KEY_SIZE)
def chacha_encrypt(plaintext: bytes, key: bytes, nonce: Optional[bytes] = None) -> Tuple[bytes, bytes]:
"""Encrypt *plaintext* with ChaCha20. Returns (ciphertext, nonce)."""
if not _CHA20_AVAILABLE:
raise ImportError("cryptography is required for ChaCha20. pip install cryptography")
if nonce is None:
nonce = os.urandom(CHACHA_NONCE_SIZE)
encryptor = _Cipher(_alg.ChaCha20(key, nonce), mode=None).encryptor()
ct = encryptor.update(plaintext) + encryptor.finalize()
return ct, nonce
def chacha_decrypt(ciphertext: bytes, key: bytes, nonce: bytes) -> bytes:
"""Decrypt *ciphertext* with ChaCha20."""
if not _CHA20_AVAILABLE:
raise ImportError("cryptography is required for ChaCha20. pip install cryptography")
decryptor = _Cipher(_alg.ChaCha20(key, nonce), mode=None).decryptor()
return decryptor.update(ciphertext) + decryptor.finalize()
# ── Serialization / Deserialization helpers ──────────────────────────────────
def _serialize_bracket(bracket: dict) -> bytes:
"""Encode a BraidBracket dict to 21 bytes."""
data = b""
for key in ("lower", "upper", "gap", "kappa", "phi"):
data += _q16_to_bytes(bracket[key])
data += _bool_to_byte(bracket["admissible"])
return data
def _deserialize_bracket(raw: bytes) -> dict:
"""Decode 21 bytes into a BraidBracket dict."""
keys = ("lower", "upper", "gap", "kappa", "phi")
bracket = {}
for i, key in enumerate(keys):
bracket[key] = struct.unpack("<I", raw[i * 4:(i + 1) * 4])[0]
bracket["admissible"] = raw[20] != 0
return bracket
def _serialize_strand(strand: dict) -> bytes:
"""Encode a BraidStrand dict to 42 bytes."""
data = b""
data += _q16_to_bytes(strand["phaseAcc"]["x"])
data += _q16_to_bytes(strand["phaseAcc"]["y"])
data += _bool_to_byte(strand["parity"])
data += _u32_to_bytes(strand["slot"])
data += _q16_to_bytes(strand["residue"])
data += _q16_to_bytes(strand["jitter"])
data += _serialize_bracket(strand["bracket"])
assert len(data) == BRAID_STRAND_BYTES
return data
def _deserialize_strand(raw: bytes) -> dict:
"""Decode 42 bytes into a BraidStrand dict."""
return {
"phaseAcc": {
"x": struct.unpack("<I", raw[0:4])[0],
"y": struct.unpack("<I", raw[4:8])[0],
},
"parity": raw[8] != 0,
"slot": struct.unpack("<I", raw[9:13])[0],
"residue": struct.unpack("<I", raw[13:17])[0],
"jitter": struct.unpack("<I", raw[17:21])[0],
"bracket": _deserialize_bracket(raw[21:42]),
}
# ── Full pipeline: encode payload ───────────────────────────────────────────
def _build_frame_payload(tag: int, serialized: bytes,
key: Optional[bytes],
compress: bool = True) -> bytes:
"""Apply Delta+RLE → RS → ChaCha20 → return frame-ready payload.
Layout: [1B tag][1B flags][nonce?][RS-encoded, encrypted blob]
"""
flags = 0x00
if compress:
flags |= 0x01
blob = serialized
if compress:
blob = delta_rle_encode(blob)
blob = rs_encode(blob)
nonce = b""
if key is not None:
blob, nonce = chacha_encrypt(blob, key)
flags |= 0x02 # encrypted flag
return struct.pack("<BB", tag, flags) + nonce + blob
# ── Full pipeline: decode payload ───────────────────────────────────────────
def decode_braid_frame(frame_payload: bytes,
key: Optional[bytes] = None) -> dict:
"""Decode a frame payload (after extracting from MKV / YUV420 frame).
Reverses: ChaCha20 decrypt RS decode Delta+RLE decompress deserialize.
Args:
frame_payload: raw payload bytes (after stripping VCN signature header).
key: ChaCha20 key (required if the frame was encrypted).
Returns:
{
"tag": int,
"tag_name": str,
"flags": int,
"decrypted": bool,
"data": dict | bytes, # deserialized braid structure
}
"""
tag, flags = struct.unpack("<BB", frame_payload[:2])
encrypted = bool(flags & 0x02)
compressed = bool(flags & 0x01)
offset = 2
nonce = b""
if encrypted:
nonce = frame_payload[offset:offset + CHACHA_NONCE_SIZE]
offset += CHACHA_NONCE_SIZE
blob = frame_payload[offset:]
# Reverse pipeline
if encrypted:
if key is None:
raise ValueError("Frame is encrypted but no key provided")
blob = chacha_decrypt(blob, key, nonce)
blob = rs_decode(blob)
if compressed:
blob = delta_rle_decode(blob)
# Deserialize based on tag
tag_names = {TAG_STRAND: "strand", TAG_CROSSING: "crossing", TAG_PIST: "pist"}
result: dict = {
"tag": tag,
"tag_name": tag_names.get(tag, "unknown"),
"flags": flags,
"decrypted": encrypted,
}
if tag == TAG_STRAND:
result["data"] = _deserialize_strand(blob)
elif tag == TAG_CROSSING:
result["data"] = {
"bracket_a": _deserialize_bracket(blob[:BRAID_BRACKET_BYTES]),
"bracket_b": _deserialize_bracket(blob[BRAID_BRACKET_BYTES:]),
}
elif tag == TAG_PIST:
result["data"] = json.loads(blob.decode("utf-8"))
else:
result["data"] = blob
return result
def encode_braid_bracket(bracket: dict) -> bytes:
"""Encode a BraidBracket dict to 21 bytes.
Args:
bracket: dict with keys 'lower', 'upper', 'gap', 'kappa', 'phi' (Q16_16 ints),
'admissible' (bool)
Returns:
21-byte serialization matching Lean encodeBraidBracket.
"""
data = b''
for key in ['lower', 'upper', 'gap', 'kappa', 'phi']:
data += _q16_to_bytes(bracket[key])
data += _bool_to_byte(bracket['admissible'])
return data
def encode_braid_strand(strand_data: dict, resolution: str = "1080p",
key: Optional[bytes] = None,
compress: bool = True) -> bytes:
"""Encode a BraidStrand dict to a VCN frame with optional pipeline stages.
Args:
strand_data: dict with keys:
'phaseAcc': {'x': int, 'y': int} (Q16_16 values)
'parity': bool
'slot': int (UInt32)
'residue': int (Q16_16)
'jitter': int (Q16_16)
'bracket': dict (see encode_braid_bracket)
resolution: VCN resolution string (default "1080p")
key: Optional ChaCha20 encryption key (32 bytes)
compress: Apply Delta+RLE compression (default True)
Returns:
Raw VCN frame bytes (YUV420) suitable for hardware encoding.
"""
serialized = _serialize_strand(strand_data)
payload = _build_frame_payload(TAG_STRAND, serialized, key, compress)
w, h = VCN_RESOLUTIONS.get(resolution, VCN_RESOLUTIONS["1080p"])
spec = VCNComputeFrameSpec(
width=w, height=h,
bytes_per_frame=compute_frame_size(w, h, "yuv420p"),
encoder="libx264"
)
return create_frame_dynamic(payload, seq=0, spec=spec)
def encode_braid_crossing(bracket_a: dict, bracket_b: dict,
resolution: str = "1080p",
key: Optional[bytes] = None,
compress: bool = True) -> bytes:
"""Encode two BraidBrackets (crossing operation) to a VCN frame with optional pipeline.
Encodes the crossing residual computation R_ij = B_ij - (B_i + B_j)
by packing both brackets side by side (42 bytes).
Args:
bracket_a, bracket_b: dicts with bracket fields
resolution: VCN resolution string
key: Optional ChaCha20 encryption key (32 bytes)
compress: Apply Delta+RLE compression (default True)
Returns:
Raw VCN frame bytes.
"""
serialized = _serialize_bracket(bracket_a) + _serialize_bracket(bracket_b)
payload = _build_frame_payload(TAG_CROSSING, serialized, key, compress)
w, h = VCN_RESOLUTIONS.get(resolution, VCN_RESOLUTIONS["1080p"])
spec = VCNComputeFrameSpec(
width=w, height=h,
bytes_per_frame=compute_frame_size(w, h, "yuv420p"),
encoder="libx264"
)
return create_frame_dynamic(payload, seq=0, spec=spec)
def encode_mountain_merge(mountain_a: dict, mountain_b: dict,
resolution: str = "1080p",
key: Optional[bytes] = None,
compress: bool = True) -> bytes:
"""Encode a Mountain merge operation to a VCN frame with optional pipeline.
Implements Mountain.merge: merged height = h+1, apex = a1.add(a2)
(coordinate-wise sum with zero-padding, matching Lean IntNode.add).
Args:
mountain_a, mountain_b: dicts with keys:
'height': int
'apex_coords': list of int
resolution: VCN resolution string
key: Optional ChaCha20 encryption key (32 bytes)
compress: Apply Delta+RLE compression (default True)
Returns:
Raw VCN frame bytes encoding the merge result.
"""
# Mountain.merge: height = h1 + 1
merged_height = mountain_a['height'] + 1
# Coordinate-wise sum with zero-padding (matching IntNode.add)
coords_a = mountain_a['apex_coords']
coords_b = mountain_b['apex_coords']
n = max(len(coords_a), len(coords_b))
padded_a = coords_a + [0] * (n - len(coords_a))
padded_b = coords_b + [0] * (n - len(coords_b))
merged_coords = [a + b for a, b in zip(padded_a, padded_b)]
serialized = _u32_to_bytes(merged_height)
serialized += _u32_to_bytes(len(merged_coords))
for coord in merged_coords:
# Clamp to Int32 range and serialize as unsigned 32-bit
# (matching Lean UInt32.ofInt with clamping)
clamped = max(-2147483648, min(2147483647, coord))
serialized += struct.pack("<I", clamped & 0xFFFFFFFF)
payload = _build_frame_payload(TAG_PIST, serialized, key, compress)
w, h = VCN_RESOLUTIONS.get(resolution, VCN_RESOLUTIONS["1080p"])
spec = VCNComputeFrameSpec(
width=w, height=h,
bytes_per_frame=compute_frame_size(w, h, "yuv420p"),
encoder="libx264"
)
return create_frame_dynamic(payload, seq=0, spec=spec)
def main(): def main():
import sys import sys
if len(sys.argv) < 2: if len(sys.argv) < 2:
print("Usage: vcn_compute_substrate.py <encode|decode|extract_receipt> <input> <output>") print("Usage: vcn_compute_substrate.py <encode|decode|extract_receipt|encode_enhanced|decode_enhanced> <input> <output> [key.hex]")
sys.exit(1) sys.exit(1)
command = sys.argv[1] command = sys.argv[1]
input_path = Path(sys.argv[2])
if command == "encode": if command == "encode":
input_path = Path(sys.argv[2])
output_path = Path(sys.argv[3]) output_path = Path(sys.argv[3])
# Read input data # Read input data
@ -579,6 +1052,7 @@ def main():
print(f"Encoded to {output_path}") print(f"Encoded to {output_path}")
elif command == "decode": elif command == "decode":
input_path = Path(sys.argv[2])
output_path = Path(sys.argv[3]) output_path = Path(sys.argv[3])
# Decode # Decode
@ -607,6 +1081,7 @@ def main():
sys.exit(1) sys.exit(1)
elif command == "extract_receipt": elif command == "extract_receipt":
input_path = Path(sys.argv[2])
receipt = extract_receipt(input_path) receipt = extract_receipt(input_path)
output_path = Path(sys.argv[3]) output_path = Path(sys.argv[3])
@ -615,6 +1090,56 @@ def main():
print(f"Receipt written to {output_path}") print(f"Receipt written to {output_path}")
elif command == "encode_enhanced":
# Usage: encode_enhanced strand.json output.mkv [key.hex]
input_path = Path(sys.argv[2])
output_path = Path(sys.argv[3])
key = None
if len(sys.argv) > 4:
key = bytes.fromhex(sys.argv[4])
if len(key) != CHACHA_KEY_SIZE:
print(f"Key must be {CHACHA_KEY_SIZE} bytes ({CHACHA_KEY_SIZE * 2} hex chars)", file=sys.stderr)
sys.exit(1)
with open(input_path) as f:
strand_dict = json.load(f)
frame = encode_braid_strand(strand_dict, key=key, compress=True)
with open(output_path, "wb") as f:
f.write(frame)
print(f"Enhanced-encoded strand to {output_path}")
elif command == "decode_enhanced":
# Usage: decode_enhanced input.mkv output.json [key.hex]
input_path = Path(sys.argv[2])
output_path = Path(sys.argv[3])
key = None
if len(sys.argv) > 4:
key = bytes.fromhex(sys.argv[4])
frames = decode_frames(input_path)
if not frames:
print("No frames decoded", file=sys.stderr)
sys.exit(1)
frame = frames[0]
header = frame[:SIGNATURE_SIZE]
signature, version, seq, length, _ = struct.unpack("<8sIIII", header)
if signature != SIGNATURE_HEADER:
print(f"Invalid signature: {signature}", file=sys.stderr)
sys.exit(1)
payload = frame[SIGNATURE_SIZE:SIGNATURE_SIZE + length]
result = decode_braid_frame(payload, key)
with open(output_path, "w") as f:
json.dump(result, f, indent=2)
print(f"Decoded enhanced frame to {output_path}")
else: else:
print(f"Unknown command: {command}", file=sys.stderr) print(f"Unknown command: {command}", file=sys.stderr)
sys.exit(1) sys.exit(1)