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 json
import zlib
import os
import tempfile
from pathlib import Path
from typing import Tuple, List, Optional
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_WIDTH = 1920
FRAME_HEIGHT = 1080
@ -549,17 +564,475 @@ def extract_receipt(input_path: Path) -> dict:
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():
import sys
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)
command = sys.argv[1]
input_path = Path(sys.argv[2])
if command == "encode":
input_path = Path(sys.argv[2])
output_path = Path(sys.argv[3])
# Read input data
@ -579,6 +1052,7 @@ def main():
print(f"Encoded to {output_path}")
elif command == "decode":
input_path = Path(sys.argv[2])
output_path = Path(sys.argv[3])
# Decode
@ -607,6 +1081,7 @@ def main():
sys.exit(1)
elif command == "extract_receipt":
input_path = Path(sys.argv[2])
receipt = extract_receipt(input_path)
output_path = Path(sys.argv[3])
@ -615,6 +1090,56 @@ def main():
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:
print(f"Unknown command: {command}", file=sys.stderr)
sys.exit(1)