#!/usr/bin/env julia
# ═══════════════════════════════════════════════════════════════════════════════
# Jub.jl — V.A.S.K. Functional DNA Organism (CP437 Repertoire)
# ═══════════════════════════════════════════════════════════════════════════════
#
# Julia-native V.A.S.K. Doctrine preserved:
#   - DNA is a deterministic functional grammar, not a semantic language.
#   - Every stage must round-trip to exact original bytes or be rejected.
#   - Original file is the truth; .sdN files are machine routes.
#   - SND DNA book is a read-only super-column export.
#   - MAXIMAL WORK on LIMITED POWER using MINIMAL ENERGY.
#   - INTERACTIONSPACE COMES FIRST ; SPACETIME IS EMERGENT.
#   - Same river, more bridges: never discard a path, only attach new ones.
#
# Repertoire:
#   The 256-glyph table is CP437 (IBM PC OEM-US, 1981).
#   Byte 0x00 -> U+2400 (documented deliberate deviation for text safety).
#   0x7F -> U+2302 (house). 0xFF -> U+00A0 (NBSP).
#
# Encode methods carried (each is an independent, verified route):
#   raw, zlib_raw, bz2_raw, lzma_raw          (byte codecs)
#   rna5_text_zlib                            (AUCGZ + hex)
#   basket_text_zlib                          (basket token grammar)
#   brother_quicky_json_zlib                  (clean+RLE+refs+dict)
#   geometry_text_zlib                        (basket cluster + box fence)
#   metatrit_text_zlib                        (zlib -> trits -> MetaTrit glyphs -> zlib)
#   dna5_text_zlib                            (DNA5:v1: AUCGZ+hex -> zlib)
#
# Requires (from the General registry):
#   SHA, JSON3, Base64, CodecZlib, CodecBzip2, CodecXz,
#   TranscodingStreams, Dates, Random, Printf
#   Optional: Gtk4, ZipArchives
#
# Run:
#   julia Jub.jl                         # GUI (or CLI banner if no Gtk4)
#   julia Jub.jl --self-test             # full roundtrip suite
#   julia Jub.jl --doctor                # environment report
#   julia Jub.jl deploy <in> [out_dir]   # build .sd4..sd0
#   julia Jub.jl download <stage> <out>  # reconstruct raw bytes
#   julia Jub.jl snd-dna <in> [out]      # SND DNA super-column book
#   julia Jub.jl grammar-check           # DNA grammar round-trips
#   julia Jub.jl metatrit <in>           # MetaTrit round-trip on one file
#   julia Jub.jl dna5 <in>               # DNA5 round-trip on one file
# ═══════════════════════════════════════════════════════════════════════════════

module JubJL

using SHA
using Dates
using Random
using JSON3
using Base64
using Printf
using CodecZlib
using CodecBzip2
using CodecXz
using TranscodingStreams

const _HAS_GTK = try
    @eval using Gtk4
    true
catch
    false
end

const _HAS_ZIPARCHIVES = try
    @eval import ZipArchives
    true
catch
    false
end

# ═══════════════════════════════════════════════════════════════════════════════
# SECTION 01 — CONSTANTS
# ═══════════════════════════════════════════════════════════════════════════════
const JUB_VERSION = "Jub.jl-CP437-1.1.0"
const MAGIC         = b"JUBJ-SDO-6\n"
const LEGACY_MAGICS = (b"VASK-SDO-6\n", b"E8I-SDO-6\n", b"E8I-SDO-5\n")
const SPACE_VIS     = "␠"
const ESC           = '~'
const BOUNDARY      = "~|"
const DICT_MARK     = "¤"
const REF_MARK      = "↩"
const MAX_RLE_RUN   = 100_000
const REPO_ZIP_NAME = "REPOSITORY.zip"

# Brother Quicky reserved-symbol escape map.
const RESERVED_MAP = Dict{Char,String}(
    '~' => "~t", '*' => "~s", '?' => "~q", '¤' => "~c",
    '↩' => "~r", '=' => "~e", '\n' => "~n", '\r' => "~d")
const UNRESERVED_MAP = Dict{String,Char}(v => k for (k, v) in RESERVED_MAP)

# Basket grammar.
const BASKET_POWERS     = ["3¹", "3²", "3⁰", "3³"]
const BASKET_POLARITIES = ["+", "±", "-", "∓"]
const BASKET_ROLE = Dict{String,String}(
    "+"  => "Basket Positive",
    "±"  => "Basket Negative",
    "-"  => "Basket Anti Positive",
    "∓"  => "Basket Anti Negative")
const BASKET_RE = r"(3[⁰¹²³])([+±\-∓])\{([0-9A-Fa-f]*)\}"

# SymbolFunctionRegistry roles.
const GLYPH_ROLES   = ["byte_encode", "quart_encode", "parity_check", "z_gate_route",
                       "visual_glow", "sector_marker", "checksum_salt", "sd_stage_marker",
                       "mood_tint", "hysteresis", "generation_cycle", "archive_index"]
const GLYPH_GATES   = ["OZ", "+Z", "-Z", "0Z"]
const GLYPH_SECTORS = ["U1", "SU2", "SU3", "G2", "F4", "E6", "E7", "E8"]

# CoreState248 sector geometry (E8-inspired). Bounds are 0-based; Julia
# indexing shifts to 1-based at use sites.
const SECTOR_BOUNDS = Dict{String,Tuple{Int,Int}}(
    "U1"         => (0, 1),
    "SU2"        => (1, 4),
    "SU3"        => (4, 12),
    "G2"         => (12, 26),
    "GRAVIWEAK"  => (247, 248),
    "F4_G2"      => (12, 26),
    "EXCEPTIONAL"=> (52, 248))

# ═══════════════════════════════════════════════════════════════════════════════
# SECTION 02 — GLYPH RESERVOIR (CP437 canonical 256, bijective)
# ═══════════════════════════════════════════════════════════════════════════════
const _RAW_A =
    "\u2400\u263A\u263B\u2665\u2666\u2663\u2660\u2022\u25D8\u25CB" *
    "\u25D9\u2642\u2640\u266A\u266B\u263C\u25BA\u25C4\u2195\u203C" *
    "\u00B6\u00A7\u25AC\u21A8\u2191\u2193\u2192\u2190\u221F\u2194" *
    "\u25B2\u25BC"

const _RAW_T =
    " !\"#\$%&'()*+,-./0123456789:;<=>?@" *
    "ABCDEFGHIJKLMNOPQRSTUVWXYZ[\\]^_`" *
    "abcdefghijklmnopqrstuvwxyz{|}~" *
    "\u2302"

const _RAW_C =
    "ÇüéâäàåçêëèïîìÄÅÉæÆôöòûùÿÖÜ¢£¥₧ƒ" *
    "áíóúñÑªº¿⌐¬½¼¡«»"

const _RAW_G =
    "░▒▓│┤╡╢╖╕╣║╗╝╜╛┐└┴┬├─┼╞╟╚╔╩╦╠═╬╧╨╤╥╙╘╒╓╫╪┘┌█▄▌▐▀"

const _RAW_Z =
    "αßΓπΣσµτΦΘΩδ∞φε∩" *
    "≡±≥≤⌠⌡÷≈°∙·√ⁿ²■\u00A0"

function dedup_chars(s::AbstractString)::Vector{Char}
    seen = Set{Char}()
    sizehint!(seen, length(s))
    out = Vector{Char}()
    sizehint!(out, length(s))
    @inbounds for c in s
        if !(c in seen)
            push!(seen, c)
            push!(out, c)
        end
    end
    return out
end

const _RESERVOIR = dedup_chars(_RAW_A * _RAW_T * _RAW_C * _RAW_G * _RAW_Z)

if length(_RESERVOIR) < 256
    error("Glyph reservoir underflow: $(length(_RESERVOIR)) unique glyphs (need ≥256).")
end

const GLYPH_256::NTuple{256, Char} = Tuple(_RESERVOIR[1:256])

@assert length(GLYPH_256) == 256
@assert length(Set(GLYPH_256)) == 256

const GLYPH_TO_BYTE::Dict{Char, UInt8} =
    Dict{Char, UInt8}(c => UInt8(i - 1) for (i, c) in enumerate(GLYPH_256))

@assert length(GLYPH_TO_BYTE) == 256

const STRAND_A = String(GLYPH_256[1:32])
const STRAND_T = String(GLYPH_256[33:128])
const STRAND_C = String(GLYPH_256[129:176])
const STRAND_G = String(GLYPH_256[177:224])
const STRAND_Z = String(GLYPH_256[225:256])

@assert length(STRAND_A) == 32
@assert length(STRAND_T) == 96
@assert length(STRAND_C) == 48
@assert length(STRAND_G) == 48
@assert length(STRAND_Z) == 32
@assert length(STRAND_A) + length(STRAND_T) + length(STRAND_C) +
        length(STRAND_G) + length(STRAND_Z) == 256

const DNA_ALPHABET = String(GLYPH_256)
@assert length(DNA_ALPHABET) == 256

# ═══════════════════════════════════════════════════════════════════════════════
# SECTION 03 — UTILITIES
# ═══════════════════════════════════════════════════════════════════════════════

sha256_hex(data::AbstractVector{UInt8})::String = bytes2hex(sha256(data))
sha256_hex(s::AbstractString)::String = sha256_hex(codeunits(s))

utc_now()::String = Dates.format(now(UTC), "yyyy-mm-ddTHH:MM:SSZ")

function safe_name(name::AbstractString; limit::Int=100)::String
    base = last(split(replace(name, '\\' => '/'), '/'))
    allowed = Set{Char}("-_.() ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789")
    cleaned = join(c in allowed ? c : '_' for c in base)
    cleaned = strip(cleaned, [' ', '.'])
    isempty(cleaned) && (cleaned = "unnamed")
    return String(cleaned[1:min(limit, lastindex(cleaned))])
end

function atomic_write(path::AbstractString, data::AbstractVector{UInt8})::Nothing
    dir = dirname(path)
    !isempty(dir) && mkpath(dir)
    tmp = string(path, ".tmp-", getpid(), "-", rand(UInt32))
    open(tmp, "w") do io
        write(io, data)
        flush(io)
        try
            Base.Libc.fsync(fd(io))
        catch
        end
    end
    mv(tmp, path; force=true)
    nothing
end

function read_all(path::AbstractString)::Vector{UInt8}
    open(path, "r") do io
        read(io)
    end
end

function detect_magic(name::AbstractString, data::AbstractVector{UInt8})::String
    ext = lowercase(replace(splitext(name)[2], r"^\." => ""))
    if startswith(data, b"7z\xbc\xaf\x27\x1c") return "7z"  end
    if startswith(data, b"PK\x03\x04")        return "zip" end
    if startswith(data, b"\x1f\x8b")          return "gzip" end
    if startswith(data, b"%PDF")              return "pdf" end
    if startswith(data, b"\x89PNG")           return "png" end
    if startswith(data, b"\xff\xd8\xff")      return "jpeg" end
    !isempty(ext) && return ext
    any(==(0x00), view(data, 1:min(4096, length(data)))) && return "bin"
    try
        String(data[1:min(4096, length(data))])
        return "text"
    catch
        return "bin"
    end
end

function entropy_bits_per_byte(data::AbstractVector{UInt8})::Float64
    isempty(data) && return 0.0
    counts = zeros(Int, 256)
    @inbounds for b in data
        counts[Int(b) + 1] += 1
    end
    n = length(data)
    h = 0.0
    @inbounds for c in counts
        c == 0 && continue
        p = c / n
        h -= p * log2(p)
    end
    return h
end

function upsert_zip(zip_path::AbstractString, arcname::String, data::Vector{UInt8})::Nothing
    dir = dirname(zip_path)
    !isempty(dir) && mkpath(dir)
    existing = Dict{String,Vector{UInt8}}()
    if isfile(zip_path) && filesize(zip_path) > 0 && _HAS_ZIPARCHIVES
        try
            reader = ZipArchives.ZipReader(read(zip_path))
            for n in ZipArchives.zip_names(reader)
                n == arcname && continue
                existing[n] = Vector{UInt8}(ZipArchives.zip_readentry(reader, n, Vector{UInt8}))
            end
        catch
            existing = Dict{String,Vector{UInt8}}()
        end
    end
    existing[arcname] = data
    if _HAS_ZIPARCHIVES
        io = IOBuffer()
        w = ZipArchives.ZipWriter(io)
        for n in sort(collect(keys(existing)))
            ZipArchives.zip_writeentry(w, n, existing[n])
        end
        ZipArchives.zip_finish(w)
        atomic_write(zip_path, take!(io))
    else
        sidecar = zip_path * ".d"
        mkpath(sidecar)
        for (n, d) in existing
            atomic_write(joinpath(sidecar, safe_name(n)), d)
        end
    end
    nothing
end

# ═══════════════════════════════════════════════════════════════════════════════
# SECTION 04 — BYTE ↔ GLYPH TRANSFORMS
# ═══════════════════════════════════════════════════════════════════════════════

function bytes_to_dna(data::AbstractVector{UInt8})::String
    n = length(data)
    out = Vector{Char}(undef, n)
    @inbounds @simd for i in 1:n
        out[i] = GLYPH_256[Int(data[i]) + 1]
    end
    return String(out)
end

function dna_to_bytes(s::AbstractString)::Vector{UInt8}
    out = Vector{UInt8}(undef, length(s))
    i = 0
    @inbounds for c in s
        i += 1
        v = get(GLYPH_TO_BYTE, c, nothing)
        v === nothing && throw(ArgumentError(
            "Unknown DNA glyph: $(repr(c)) (U+$(string(UInt32(c); base=16)))"))
        out[i] = v
    end
    return out
end

# ═══════════════════════════════════════════════════════════════════════════════
# SECTION 05 — BROTHER QUICKY RLE (lossless over the glyph alphabet)
# ═══════════════════════════════════════════════════════════════════════════════

function rle_encode(chars::AbstractVector{Char})::String
    out = IOBuffer()
    i = 1
    n = length(chars)
    last_was_digit = false
    while i <= n
        c = chars[i]
        j = i + 1
        @inbounds while j <= n && chars[j] == c
            j += 1
        end
        run = j - i
        token = string(run, '*', c)
        if run > 1 && length(token) < run
            if last_was_digit
                write(out, BOUNDARY)
                last_was_digit = false
            end
            write(out, token)
        else
            for _ in 1:run
                write(out, c)
            end
            last_was_digit = isdigit(c)
        end
        i = j
    end
    return String(take!(out))
end

function rle_decode(s::AbstractString)::String
    chars = collect(s)
    n = length(chars)
    out = IOBuffer()
    i = 1
    bl = length(BOUNDARY)
    while i <= n
        if i + bl - 1 <= n && String(@view chars[i:i+bl-1]) == BOUNDARY
            i += bl
            continue
        end
        c = chars[i]
        if isdigit(c)
            j = i + 1
            @inbounds while j <= n && isdigit(chars[j])
                j += 1
            end
            if j <= n && chars[j] == '*' && j + 1 <= n
                count = min(parse(Int, String(@view chars[i:j-1])), MAX_RLE_RUN)
                d = chars[j+1]
                for _ in 1:count
                    write(out, d)
                end
                i = j + 2
                continue
            end
            for k in i:j-1
                write(out, chars[k])
            end
            i = j
            continue
        end
        write(out, c)
        i += 1
    end
    return String(take!(out))
end

# ═══════════════════════════════════════════════════════════════════════════════
# SECTION 06 — METATRIT CODEC (ternary run-length)
# ═══════════════════════════════════════════════════════════════════════════════
#
# MetaTrit encodes sequences of ternary values in {-1, 0, 1, 2} as text.
#   -1 -> "jfr",  1 -> "Kan",  2 -> "°jfr",  0 -> "°six"
# Runs >= 3 are emitted as "<sign>3^<w>(<block>)" where sign in {- , +, -0, +0}
# and run = block * 3^w.
#
# IMPORTANT: the C++ reference emits only the first character of the glyph
# for runs < 3 (bug), which is not round-trippable. This port emits the full
# glyph repeated `run` times.
module MetaTrit

glyph(t::Int)::String = t == -1 ? "jfr" :
                        t ==  1 ? "Kan" :
                        t ==  2 ? "°jfr" : "°six"

function encode(trits::AbstractVector{Int})::String
    out = IOBuffer()
    i = 1
    n = length(trits)
    while i <= n
        v = trits[i]
        j = i + 1
        while j <= n && trits[j] == v
            j += 1
        end
        run = j - i
        best = 0
        for w in 0:7
            div = 3^w
            if run % div == 0 && (run ÷ div) <= 999_999
                best = w
            end
        end
        div = 3^best
        block = run ÷ div
        if run >= 3 && block > 0
            sign = v == -1 ? "-" : v == 1 ? "+" : v == 2 ? "-0" : "+0"
            write(out, sign, "3^", string(best), "}(", string(block))
        else
            g = glyph(v)
            for _ in 1:run
                write(out, g)
            end
        end
        i = j
    end
    return String(take!(out))
end

function decode(text::AbstractString)::Vector{Int}
    out = Int[]
    i = 1
    L = length(text)
    while i <= L
        c = text[i]
        if c == '-' || c == '+'
            sign = string(c)
            i += 1
            if i <= L && text[i] == '0'
                sign *= "0"
                i += 1
            end
            (i + 1 <= L && text[i:i+1] == "3^") || error("MetaTrit: missing 3^")
            i += 2
            ws = ""
            while i <= L && isdigit(text[i])
                ws *= text[i]; i += 1
            end
            (i + 1 <= L && text[i:i+1] == "}(") || error("MetaTrit: missing }(")
            i += 2
            ns = ""
            while i <= L && isdigit(text[i])
                ns *= text[i]; i += 1
            end
            i <= L && text[i] == ')' && (i += 1)
            w = isempty(ws) ? 0 : parse(Int, ws)
            n = isempty(ns) ? 0 : parse(Int, ns)
            val = sign == "-" ? -1 : sign == "+" ? 1 : sign == "-0" ? 2 : 0
            append!(out, fill(val, n * 3^w))
        else
            if startswith(text[i:end], "°jfr")
                push!(out, 2);  i += 5
            elseif startswith(text[i:end], "°six")
                push!(out, 0);  i += 5
            elseif startswith(text[i:end], "jfr")
                push!(out, -1); i += 3
            elseif startswith(text[i:end], "Kan")
                push!(out, 1);  i += 3
            else
                error("MetaTrit: bad glyph at $(i)")
            end
        end
    end
    return out
end

"""6 trits per byte, big-endian balanced-ternary digit order (0..242 per byte)."""
function bytes_to_trits(data::AbstractVector{UInt8})::Vector{Int}
    out = Int[]
    sizehint!(out, 6 * length(data))
    for byte in data
        n = Int(byte)
        d = Vector{Int}(undef, 6)
        for k in 6:-1:1
            d[k] = n % 3
            n ÷= 3
        end
        append!(out, d)
    end
    return out
end

function trits_to_bytes(trits::AbstractVector{Int})::Vector{UInt8}
    length(trits) % 6 == 0 || error("MetaTrit: trit count not a multiple of 6")
    out = UInt8[]
    for i in 1:6:length(trits)
        n = 0
        for k in 0:5
            t = trits[i + k]
            (t < 0 || t > 2) && (t = 0)
            n = n * 3 + t
        end
        n > 255 && error("MetaTrit: trit group out of byte range")
        push!(out, UInt8(n))
    end
    return out
end

end # module MetaTrit

# ═══════════════════════════════════════════════════════════════════════════════
# SECTION 07 — DNA5 CODEC (compact AUCGZ + hex)
# ═══════════════════════════════════════════════════════════════════════════════

const DNA5_PREFIX = "DNA5:v1:"

function dna5_encode(data::AbstractVector{UInt8})::String
    bases = "AUCGZ"
    io = IOBuffer()
    write(io, DNA5_PREFIX)
    @inbounds for (i, b) in enumerate(data)
        c = bases[((i - 1) % 5) + 1]
        write(io, c)
        h = uppercase(string(b; base=16, pad=2))
        write(io, h)
    end
    return String(take!(io))
end

function dna5_decode(text::AbstractString)::Vector{UInt8}
    startswith(text, DNA5_PREFIX) || error("DNA5: missing prefix")
    body = text[nextind(text, 0, length(DNA5_PREFIX) + 1):end]
    length(body) % 3 == 0 || error("DNA5: body length not a multiple of 3")
    out = UInt8[]
    i = firstindex(body)
    while i <= lastindex(body)
        s = body[i]
        s in "AUCGZ" || error("DNA5: bad strand $(repr(s))")
        j = nextind(body, i)
        k = nextind(body, j)
        h = body[j:k]
        push!(out, parse(UInt8, h; base=16))
        i = nextind(body, k)
    end
    return out
end

# ═══════════════════════════════════════════════════════════════════════════════
# SECTION 08 — BASKET GRAMMAR (functional, non-semantic)
# ═══════════════════════════════════════════════════════════════════════════════

struct BasketToken
    power::String
    polarity::String
    payload_hex::String
end

render(bt::BasketToken)::String = string(bt.power, bt.polarity, "{", bt.payload_hex, "}")
decode_payload(bt::BasketToken)::Vector{UInt8} = hex2bytes(bt.payload_hex)

function bytes_to_basket_tokens(data::AbstractVector{UInt8}; group::Int=6)::Vector{BasketToken}
    toks = BasketToken[]
    n = length(data)
    i = 1
    k = 0
    while i <= n
        chunk = data[i:min(i + group - 1, n)]
        kk = (k % 4) + 1
        push!(toks, BasketToken(BASKET_POWERS[kk], BASKET_POLARITIES[kk], bytes2hex(chunk)))
        k += 1
        i += group
    end
    return toks
end

function basket_tokens_to_bytes(text::AbstractString)::Vector{UInt8}
    out = UInt8[]
    for m in eachmatch(BASKET_RE, text)
        append!(out, hex2bytes(m.captures[3]))
    end
    return out
end

function render_cluster(tokens::Vector{BasketToken};
                        width_tokens::Int=2,
                        identity::String="gH")::String
    length(identity) < 2 && (identity = "gH")
    lid = first(identity)
    rid = last(identity)
    rows = String[]
    n = length(tokens)
    i = 1
    while i <= n
        chunk = tokens[i:min(i + width_tokens - 1, n)]
        payload = join(render(t) for t in chunk)
        is_first = (i == 1)
        is_last  = (i + width_tokens > n)
        if is_first && is_last
            push!(rows, "°{" * string(lid) * payload * string(rid) * "|•")
        elseif is_first
            push!(rows, "°{" * string(lid) * payload * "|•")
        elseif is_last
            push!(rows, "o{" * payload * string(rid) * "|•")
        else
            push!(rows, "•|" * payload * string(rid) * "|•")
        end
        i += width_tokens
    end
    return join(rows, "\n")
end

function box_lines(lines::Vector{String}; axis::String="ZYX",
                   width::Union{Int,Nothing}=nothing)::Vector{String}
    isempty(lines) && (lines = [""])
    bw = width === nothing ? maximum(length(l) for l in lines) : width
    bw = max(bw, 4)
    top    = axis[1:2] * string(axis[3])^bw
    bottom = axis[2] * string(axis[3])^(bw + 1)
    out = String[top]
    for l in lines
        push!(out, "X" * rpad(l, bw) * "X")
    end
    push!(out, bottom)
    return out
end

function grammar_report()::Dict{String,Any}
    sample = Vector{UInt8}(codeunits("Jub.jl functional DNA grammar sample"))
    btxt = join(render(t) for t in bytes_to_basket_tokens(sample))
    d5   = dna5_encode(sample)
    return Dict{String,Any}(
        "basket_roundtrip" => basket_tokens_to_bytes(btxt) == sample,
        "dna5_roundtrip"   => dna5_decode(d5) == sample,
        "strand_lengths"   => Dict{String,Int}(
            "A" => length(STRAND_A), "T" => length(STRAND_T),
            "C" => length(STRAND_C), "G" => length(STRAND_G),
            "Z" => length(STRAND_Z)),
    )
end

# ═══════════════════════════════════════════════════════════════════════════════
# SECTION 09 — BROTHER QUICKY PIPELINE (clean + RLE + refs + dict)
# ═══════════════════════════════════════════════════════════════════════════════

clean_encode(s::AbstractString)::String =
    join(get(RESERVED_MAP, c, string(c)) for c in s)

function clean_decode(s::AbstractString)::String
    io = IOBuffer()
    i = firstindex(s)
    bl = length(BOUNDARY)
    while i <= lastindex(s)
        if i + bl - 1 <= lastindex(s) && s[i:i+bl-1] == BOUNDARY
            i = nextind(s, i, bl)
            continue
        end
        c = s[i]
        if c == ESC
            j = nextind(s, i)
            j > lastindex(s) && error("clean_decode: dangling escape")
            pair = s[i:j]
            if haskey(UNRESERVED_MAP, pair)
                write(io, UNRESERVED_MAP[pair])
                i = nextind(s, j)
                continue
            end
            error("clean_decode: unknown escape $(repr(pair))")
        end
        write(io, c)
        i = nextind(s, i)
    end
    return String(take!(io))
end

function number_rows(text::AbstractString; width::Int=158)::Vector{String}
    t = replace(text, " " => SPACE_VIS)
    rows = String[]
    i = firstindex(t)
    while i <= lastindex(t)
        j = min(lastindex(t), nextind(t, i, width) - 1)
        push!(rows, lpad(string(length(rows)), 3, '0') * ":" * t[i:j])
        i = nextind(t, j)
    end
    isempty(rows) && push!(rows, "000:")
    return rows
end

function quicky_q0(rows::Vector{String})::Vector{String}
    out = String[]
    for row in rows
        label = row[1:min(4, lastindex(row))]
        body  = length(row) > 4 ? row[5:end] : ""
        enc = rle_encode(collect(clean_encode(body)))
        if clean_decode(rle_decode(enc)) != replace(body, SPACE_VIS => " ")
            error("quicky_q0: failed at $(label)")
        end
        push!(out, label * enc)
    end
    return out
end

function quicky_q1(rows::Vector{String})::Vector{String}
    seen = Dict{String,String}()
    out = String[]
    for line in rows
        label = line[1:min(4, lastindex(line))]
        body  = length(line) > 4 ? line[5:end] : ""
        if haskey(seen, body)
            push!(out, label * REF_MARK * seen[body])
        else
            seen[body] = label[1:min(3, lastindex(label))]
            push!(out, line)
        end
    end
    return out
end

function quicky_expand_q1(rows::Vector{String})::Vector{String}
    table = Dict{String,String}()
    out = String[]
    for line in rows
        label = line[1:min(4, lastindex(line))]
        body  = length(line) > 4 ? line[5:end] : ""
        if startswith(body, REF_MARK)
            ref = body[nextind(body, firstindex(body)):end]
            haskey(table, ref) || error("quicky_expand_q1: bad ref $(label) -> $(ref)")
            body = table[ref]
        end
        table[label[1:min(3, lastindex(label))]] = body
        push!(out, label * body)
    end
    return out
end

function choose_dictionary(lines::Vector{String};
                           max_entries::Int=64,
                           min_count::Int=3)::Tuple{Vector{Tuple{String,String}}, Vector{String}}
    current = copy(lines)
    dict = Tuple{String,String}[]
    slens = (96, 80, 64, 48, 40, 32, 24, 20, 16, 12, 10, 8, 6)
    for idx in 0:max_entries-1
        tok = string(DICT_MARK, lpad(string(idx), 2, '0'), DICT_MARK)
        counter = Dict{String,Int}()
        for line in current
            body = length(line) > 4 ? line[5:end] : ""
            for sl in slens
                sl > length(body) && continue
                for pos in firstindex(body):(lastindex(body) - sl + 1)
                    sub = body[pos:nextind(body, pos, sl) - 1]
                    (occursin(DICT_MARK, sub) || occursin(REF_MARK, sub)) && continue
                    counter[sub] = get(counter, sub, 0) + 1
                end
            end
        end
        best, best_gain = nothing, 0
        for (sub, cnt) in counter
            cnt < min_count && continue
            gain = (length(sub) - length(tok)) * cnt - length(sub) - length(tok) - 4
            if gain > best_gain
                best, best_gain = sub, gain
            end
        end
        best === nothing && break
        push!(dict, (tok, best))
        current = [line[1:min(4, lastindex(line))] *
                   replace(line[length(line) > 4 ? 5 : firstindex(line):end], best => tok)
                   for line in current]
    end
    return (dict, current)
end

function expand_dictionary(dict::Vector{Tuple{String,String}}, lines::Vector{String})::Vector{String}
    out = copy(lines)
    for (tok, phrase) in reverse(dict)
        out = [replace(line, tok => phrase) for line in out]
    end
    return out
end

function brother_quicky_package(raw::Vector{UInt8}; width::Int=158)::Dict{String,Any}
    text = String(raw)
    rows0 = number_rows(text; width=width)
    q0 = quicky_q0(rows0)
    q1 = quicky_q1(q0)
    dict, q2 = choose_dictionary(q1)
    ex1 = expand_dictionary(dict, q2)
    ex0 = quicky_expand_q1(ex1)
    decoded_lines = [clean_decode(rle_decode(l[5:end])) for l in ex0]
    restored_text = replace(join(decoded_lines), SPACE_VIS => " ")
    restored = Vector{UInt8}(codeunits(restored_text))
    restored == raw || error("brother_quicky_package: round-trip failed")
    return Dict{String,Any}(
        "kind"       => "brother_quicky_v2",
        "width"      => width,
        "dictionary" => dict,
        "rows"       => q2,
        "stats"      => Dict{String,Any}(
            "raw_chars"    => length(text),
            "q2_chars"     => sum(length, q2),
            "dict_entries" => length(dict)))
end

function decode_brother_quicky_package(pkg::Dict{String,Any})::Vector{UInt8}
    dict = [(String(a), String(b)) for (a, b) in get(pkg, "dictionary", [])]
    rows = [string(x) for x in get(pkg, "rows", [])]
    ex1 = expand_dictionary(dict, rows)
    ex0 = quicky_expand_q1(ex1)
    dec = replace(join(clean_decode(rle_decode(l[5:end])) for l in ex0), SPACE_VIS => " ")
    return Vector{UInt8}(codeunits(dec))
end

# ═══════════════════════════════════════════════════════════════════════════════
# SECTION 10 — GEOMETRY PROJECTION (.sd2 payload)
# ═══════════════════════════════════════════════════════════════════════════════

function geometry_projection(raw::Vector{UInt8}; name::String="payload")::Dict{String,Any}
    head = raw[1:min(length(raw), 512)]
    toks = bytes_to_basket_tokens(head)
    cluster = render_cluster(toks; width_tokens=2, identity="gH")
    box = box_lines(split(cluster, '\n'); axis="ZYX")
    return Dict{String,Any}(
        "kind"    => "geometry_projection_v2",
        "name"    => name,
        "raw_b64" => base64encode(raw),
        "preview" => box,
        "rules"   => Dict{String,Any}("dna_is_functional_not_semantic" => true))
end

# ═══════════════════════════════════════════════════════════════════════════════
# SECTION 11 — SDO ENVELOPE
# ═══════════════════════════════════════════════════════════════════════════════

struct SDOMeta
    program::String
    created_utc::String
    method::String
    raw_sha256::String
    raw_len::Int
    payload_len::Int
    payload_sha256::String
    extra::Dict{String,Any}
end

function pack_sdo(payload::Vector{UInt8}, meta::SDOMeta)::Vector{UInt8}
    meta_dict = Dict{String,Any}(
        "program"        => meta.program,
        "created_utc"    => meta.created_utc,
        "method"         => meta.method,
        "raw_sha256"     => meta.raw_sha256,
        "raw_len"        => meta.raw_len,
        "payload_len"    => meta.payload_len,
        "payload_sha256" => meta.payload_sha256,
        "extra"          => meta.extra)
    meta_json = JSON3.write(meta_dict)
    meta_bytes = codeunits(meta_json)
    meta_len = length(meta_bytes)

    out = Vector{UInt8}(undef, length(MAGIC) + 8 + meta_len + length(payload))
    pos = 1
    @inbounds for b in MAGIC
        out[pos] = b; pos += 1
    end
    @inbounds for shift in (56, 48, 40, 32, 24, 16, 8, 0)
        out[pos] = UInt8((meta_len >> shift) & 0xFF); pos += 1
    end
    @inbounds for b in meta_bytes
        out[pos] = b; pos += 1
    end
    @inbounds for b in payload
        out[pos] = b; pos += 1
    end
    return out
end

function unpack_sdo(blob::AbstractVector{UInt8})::Tuple{Dict{String,Any},Vector{UInt8}}
    magic = nothing
    for m in (MAGIC, LEGACY_MAGICS...)
        if length(blob) >= length(m) && view(blob, 1:length(m)) == m
            magic = m
            break
        end
    end
    magic === nothing && throw(ArgumentError("not a Jub.jl SDO envelope"))
    pos = length(magic) + 1
    length(blob) < pos + 7 && throw(ArgumentError("truncated SDO"))
    meta_len::UInt64 = 0
    @inbounds for i in 0:7
        meta_len = (meta_len << 8) | UInt64(blob[pos + i])
    end
    pos += 8
    length(blob) < pos + Int(meta_len) - 1 && throw(ArgumentError("truncated SDO meta"))
    meta = JSON3.read(String(view(blob, pos:pos + Int(meta_len) - 1)), Dict{String,Any})
    pos += Int(meta_len)
    payload = Vector{UInt8}(view(blob, pos:length(blob)))
    return meta, payload
end

# ═══════════════════════════════════════════════════════════════════════════════
# SECTION 12 — ENCODE METHODS (multiple dispatch)
# ═══════════════════════════════════════════════════════════════════════════════

abstract type EncodeMethod end

struct RawMethod          <: EncodeMethod end
struct ZlibMethod         <: EncodeMethod end
struct Bzip2Method        <: EncodeMethod end
struct XzMethod           <: EncodeMethod end
struct DNA5Method         <: EncodeMethod end
struct BasketMethod       <: EncodeMethod end
struct BrotherQuickyMethod <: EncodeMethod end
struct GeometryMethod     <: EncodeMethod end
struct MetaTritMethod     <: EncodeMethod end

method_name(::RawMethod)           = "raw"
method_name(::ZlibMethod)          = "zlib_raw"
method_name(::Bzip2Method)         = "bz2_raw"
method_name(::XzMethod)            = "lzma_raw"
method_name(::DNA5Method)          = "dna5_text_zlib"
method_name(::BasketMethod)        = "basket_text_zlib"
method_name(::BrotherQuickyMethod) = "brother_quicky_json_zlib"
method_name(::GeometryMethod)      = "geometry_text_zlib"
method_name(::MetaTritMethod)      = "metatrit_text_zlib"

# Byte codecs -------------------------------------------------------------
encode_data(::RawMethod,   data::Vector{UInt8}) = copy(data)
encode_data(::ZlibMethod,  data::Vector{UInt8}) = transcode(ZlibCompressor,  data)
encode_data(::Bzip2Method, data::Vector{UInt8}) = transcode(Bzip2Compressor, data)
encode_data(::XzMethod,    data::Vector{UInt8}) = transcode(XzCompressor,    data)

decode_data(::RawMethod,   data::Vector{UInt8}) = copy(data)
decode_data(::ZlibMethod,  data::Vector{UInt8}) = transcode(ZlibDecompressor,  data)
decode_data(::Bzip2Method, data::Vector{UInt8}) = transcode(Bzip2Decompressor, data)
decode_data(::XzMethod,    data::Vector{UInt8}) = transcode(XzDecompressor,    data)

# Textual codecs ----------------------------------------------------------
function encode_data(::DNA5Method, data::Vector{UInt8})::Vector{UInt8}
    return transcode(ZlibCompressor, codeunits(dna5_encode(data)))
end
function decode_data(::DNA5Method, payload::Vector{UInt8})::Vector{UInt8}
    return dna5_decode(String(transcode(ZlibDecompressor, payload)))
end

function encode_data(::BasketMethod, data::Vector{UInt8})::Vector{UInt8}
    txt = join(render(t) for t in bytes_to_basket_tokens(data))
    return transcode(ZlibCompressor, codeunits(txt))
end
function decode_data(::BasketMethod, payload::Vector{UInt8})::Vector{UInt8}
    return basket_tokens_to_bytes(String(transcode(ZlibDecompressor, payload)))
end

function encode_data(::BrotherQuickyMethod, data::Vector{UInt8})::Vector{UInt8}
    pkg = brother_quicky_package(data)
    return transcode(ZlibCompressor, codeunits(JSON3.write(pkg)))
end
function decode_data(::BrotherQuickyMethod, payload::Vector{UInt8})::Vector{UInt8}
    pkg = JSON3.read(String(transcode(ZlibDecompressor, payload)), Dict{String,Any})
    return decode_brother_quicky_package(pkg)
end

function encode_data(::GeometryMethod, data::Vector{UInt8})::Vector{UInt8}
    geom = geometry_projection(data)
    return transcode(ZlibCompressor, codeunits(JSON3.write(geom)))
end
function decode_data(::GeometryMethod, payload::Vector{UInt8})::Vector{UInt8}
    g = JSON3.read(String(transcode(ZlibDecompressor, payload)), Dict{String,Any})
    return base64decode(g["raw_b64"])
end

# MetaTrit: raw -> zlib -> trits -> MetaTrit text -> zlib
function encode_data(::MetaTritMethod, data::Vector{UInt8})::Vector{UInt8}
    z = transcode(ZlibCompressor, data)
    trits = MetaTrit.bytes_to_trits(z)
    mt_text = MetaTrit.encode(trits)
    return transcode(ZlibCompressor, codeunits(mt_text))
end
function decode_data(::MetaTritMethod, payload::Vector{UInt8})::Vector{UInt8}
    mt_bytes = transcode(ZlibDecompressor, payload)
    trits = MetaTrit.decode(String(mt_bytes))
    z = MetaTrit.trits_to_bytes(trits)
    return transcode(ZlibDecompressor, z)
end

function build_sdo(method::EncodeMethod, raw::Vector{UInt8};
                   stage::String="",
                   extra::Dict{String,Any}=Dict{String,Any}())::Vector{UInt8}
    payload = encode_data(method, raw)
    meta = SDOMeta(
        JUB_VERSION,
        utc_now(),
        method_name(method),
        sha256_hex(raw),
        length(raw),
        length(payload),
        sha256_hex(payload),
        merge(extra, isempty(stage) ? Dict{String,Any}() :
                     Dict{String,Any}("stage" => stage)))
    return pack_sdo(payload, meta)
end

function decode_sdo(blob::AbstractVector{UInt8})::Vector{UInt8}
    meta, payload = unpack_sdo(blob)
    method = get(meta, "method", "raw")
    raw =
        method == "raw"                        ? copy(payload) :
        method == "zlib_raw"                   ? decode_data(ZlibMethod(),  payload) :
        method == "bz2_raw"                    ? decode_data(Bzip2Method(), payload) :
        method == "lzma_raw"                   ? decode_data(XzMethod(),    payload) :
        method == "dna5_text_zlib"             ? decode_data(DNA5Method(),  payload) :
        method == "basket_text_zlib"           ? decode_data(BasketMethod(), payload) :
        method == "brother_quicky_json_zlib"   ? decode_data(BrotherQuickyMethod(), payload) :
        method == "geometry_text_zlib"         ? decode_data(GeometryMethod(), payload) :
        method == "metatrit_text_zlib"         ? decode_data(MetaTritMethod(), payload) :
        throw(ArgumentError("unknown SDO method: $(method)"))
    expected_sha = get(meta, "raw_sha256", "")
    !isempty(expected_sha) && sha256_hex(raw) != expected_sha &&
        throw(ErrorException("SDO raw SHA-256 mismatch"))
    expected_len = get(meta, "raw_len", 0)
    expected_len > 0 && length(raw) != expected_len &&
        throw(ErrorException("SDO raw length mismatch"))
    return raw
end

sdo_metadata(blob::AbstractVector{UInt8})::Dict{String,Any} = unpack_sdo(blob)[1]

# ═══════════════════════════════════════════════════════════════════════════════
# SECTION 13 — SND DNA BOOK (super-column export)
# ═══════════════════════════════════════════════════════════════════════════════

function generate_super_column(data::AbstractVector{UInt8}; width_override::Union{Int,Nothing}=nothing)::String
    dna = bytes_to_dna(data)
    n = length(dna)
    width = width_override === nothing ? min(999, max(162, floor(Int, sqrt(n)))) : width_override
    chars = collect(dna)

    n_rows = cld(n, width)
    while length(chars) < n_rows * width
        push!(chars, first(SPACE_VIS))
    end

    stalks = Vector{Vector{Char}}(undef, width)
    @inbounds for c in 1:width
        col = Vector{Char}(undef, n_rows)
        for r in 1:n_rows
            col[r] = chars[(r - 1) * width + c]
        end
        stalks[c] = col
    end

    out = IOBuffer()
    println(out, "000:ZXY", "Y"^(width - 3))
    println(out)
    for (t, x) in enumerate((100, 10, 1))
        h = String([string(((i - 1) ÷ x) % 10) for i in 1:width])
        prefix = t == 1 ? 'X' : 'Y'
        println(out, lpad(string(t), 3, '0'), ":", prefix, h, "Y")
        println(out)
    end
    println(out, "004:Y", ":"^width, "Y"); println(out)
    println(out, "005:Y", SPACE_VIS^width, "Y"); println(out)

    for (i, s) in enumerate(stalks)
        compact = rle_encode(s)
        stripped = rstrip(String(s), first(SPACE_VIS))
        chk = length(stripped)
        println(out, lpad(string(i + 5), 3, '0'), ":Y", compact, "Y", lpad(string(chk), 3, '0'))
        println(out)
    end
    println(out, lpad(string(length(stalks) + 6), 3, '0'), ":X", "Y"^width)
    return String(take!(out))
end

# ═══════════════════════════════════════════════════════════════════════════════
# SECTION 14 — SymbolFunctionRegistry (256 glyphs, role/gate/sector)
# ═══════════════════════════════════════════════════════════════════════════════

struct GlyphEntry
    glyph::Char
    index::Int
    role::String
    gate::String
    sector::String
end

function build_glyph_registry()::Vector{GlyphEntry}
    [GlyphEntry(
        GLYPH_256[i], i - 1,
        GLYPH_ROLES[mod1(i, length(GLYPH_ROLES))],
        GLYPH_GATES[mod1(i, length(GLYPH_GATES))],
        GLYPH_SECTORS[mod1(i, length(GLYPH_SECTORS))])
     for i in 1:256]
end

function glyph_registry_summary()::Dict{String,Any}
    reg = build_glyph_registry()
    role_counts = Dict{String,Int}()
    for e in reg
        role_counts[e.role] = get(role_counts, e.role, 0) + 1
    end
    Dict{String,Any}(
        "size"  => length(reg),
        "roles" => role_counts,
        "sample_first" => Dict{String,Any}(
            "glyph"  => string(reg[1].glyph),
            "index"  => reg[1].index,
            "role"   => reg[1].role,
            "gate"   => reg[1].gate,
            "sector" => reg[1].sector),
        "sample_last"  => Dict{String,Any}(
            "glyph"  => string(reg[end].glyph),
            "index"  => reg[end].index,
            "role"   => reg[end].role,
            "gate"   => reg[end].gate,
            "sector" => reg[end].sector))
end

# ═══════════════════════════════════════════════════════════════════════════════
# SECTION 15 — CoreState248 (E8-inspired state)
# ═══════════════════════════════════════════════════════════════════════════════

mutable struct CoreState248
    real::Vector{Float64}
    imag::Vector{Float64}
    birth::Float64
    lock::ReentrantLock
    function CoreState248()
        nrm = 1.0 / sqrt(248.0)
        new(fill(nrm, 248), zeros(248), time(), ReentrantLock())
    end
end

function _nudge!(s::CoreState248, sector::String, scale::Float64)
    bounds = get(SECTOR_BOUNDS, sector, (0, 0))
    s0, s1 = bounds
    s1 <= s0 && return
    nv = scale / max(1, s1 - s0)
    @inbounds for i in (s0 + 1):min(s1, 248)
        s.real[i] = clamp(s.real[i] + nv, -10.0, 10.0)
    end
end

function _renormalise!(s::CoreState248)
    mag = sqrt(sum(r*r + im*im for (r, im) in zip(s.real, s.imag)))
    mag < 1e-15 && (mag = 1e-15)
    inv = 1.0 / mag
    @inbounds for i in 1:248
        s.real[i] *= inv
        s.imag[i] *= inv
    end
end

function perceive!(s::CoreState248, metadata::Dict{String,Any}; scale::Float64=1.0)
    ent = Float64(get(metadata, "entropy_bits_per_byte", 0.0))
    rl  = Int(get(metadata, "raw_len", 0))
    mg  = String(get(metadata, "magic", ""))
    lock(s.lock) do
        ent > 7.5 && _nudge!(s, "EXCEPTIONAL", 0.015 * (ent / 8.0) * scale)
        rl  > 100_000 && _nudge!(s, "F4_G2", 0.01 * log2(max(1, rl) / 1000.0) * scale)
        mg in ("python", "cpp", "c", "h", "json", "md", "tex", "txt", "jl") &&
            _nudge!(s, "SU3", 0.02 * scale)
        mg in ("exe", "dll", "elf", "bin", "7z", "zip") &&
            _nudge!(s, "F4_G2", 0.015 * scale)
        _renormalise!(s)
    end
end

function metabolic_decay!(s::CoreState248; dc::Float64=0.0002)
    lock(s.lock) do
        base = 1.0 / sqrt(248.0)
        @inbounds for i in 1:248
            s.real[i] = s.real[i] * (1 - dc) + base * dc
            s.imag[i] *= (1 - dc)
        end
        _renormalise!(s)
    end
end

function get_activations(s::CoreState248)::Dict{String,Float64}
    out = Dict{String,Float64}()
    for (name, (s0, s1)) in SECTOR_BOUNDS
        out[name] = sum(s.real[i]^2 + s.imag[i]^2 for i in (s0 + 1):min(s1, 248))
    end
    return out
end

# ═══════════════════════════════════════════════════════════════════════════════
# SECTION 16 — PhaseRouter (LARVAL / TRANSITIONING / AUTOPOIETIC)
# ═══════════════════════════════════════════════════════════════════════════════

struct PhaseRouter
    repo_zip::String
    critical_mass_file_count::Int
end

PhaseRouter(repo_zip::AbstractString) = PhaseRouter(String(repo_zip), 30)

function phase_of(repo_zip::AbstractString)::String
    isfile(repo_zip) || return "LARVAL"
    count = 0
    if _HAS_ZIPARCHIVES
        try
            r = ZipArchives.ZipReader(read(repo_zip))
            for n in ZipArchives.zip_names(r)
                endswith(lowercase(n), ".sd0") && (count += 1)
            end
        catch
        end
    end
    mass = min(1.0, count / 30.0)
    return mass < 0.3 ? "LARVAL" :
           mass < 1.0 ? "TRANSITIONING" : "AUTOPOIETIC"
end

# ═══════════════════════════════════════════════════════════════════════════════
# SECTION 17 — STAGE BUILDER (parallel .sd4 .. .sd0 with extended arena)
# ═══════════════════════════════════════════════════════════════════════════════

function candidate_methods()::Vector{EncodeMethod}
    return EncodeMethod[
        RawMethod(),
        ZlibMethod(),
        Bzip2Method(),
        XzMethod(),
        DNA5Method(),
        BasketMethod(),
        BrotherQuickyMethod(),
        GeometryMethod(),
        MetaTritMethod(),
    ]
end

function build_stages(raw::Vector{UInt8}, out_dir::AbstractString;
                      base_name::AbstractString="payload",
                      add_to_zip::Bool=true)::Dict{String,Any}
    mkpath(out_dir)
    base = safe_name(base_name)
    raw_sha = sha256_hex(raw)
    scan = Dict{String,Any}(
        "name"                  => base,
        "raw_len"               => length(raw),
        "sha256"                => raw_sha,
        "entropy_bits_per_byte" => round(entropy_bits_per_byte(raw); digits=6),
        "magic"                 => detect_magic(base, raw))

    # Fixed-slot parallelism: pre-populate the dict keys so no rehash runs
    # concurrently. Under JULIA_NUM_THREADS=1 this is already safe; under
    # multi-thread it is the minimal-diff thread-safe pattern.
    stage_blobs = Dict{String,Vector{UInt8}}(
        "sd4" => UInt8[], "sd3" => UInt8[], "sd2" => UInt8[], "sd1" => UInt8[], "sd0" => UInt8[])

    @sync begin
        @spawn (stage_blobs["sd4"] = build_sdo(RawMethod(),   raw; stage="sd4", extra=Dict{String,Any}("scan"=>scan)))
        @spawn (stage_blobs["sd3"] = build_sdo(ZlibMethod(),  raw; stage="sd3"))
        @spawn (stage_blobs["sd2"] = build_sdo(GeometryMethod(), raw; stage="sd2"))
        @spawn (stage_blobs["sd1"] = try
            build_sdo(BrotherQuickyMethod(), raw; stage="sd1")
        catch
            build_sdo(BasketMethod(), raw; stage="sd1",
                      extra=Dict{String,Any}("fallback"=>"basket"))
        end)
    end

    # .sd0 candidate arena — smallest verified wins among all methods.
    verified = Tuple{String,Vector{UInt8},Dict{String,Any},Int}[]
    for m in candidate_methods()
        try
            blob = build_sdo(m, raw; stage="sd0_candidate")
            if decode_sdo(blob) == raw
                push!(verified, (method_name(m), blob, Dict{String,Any}(), length(blob)))
            end
        catch
            # candidate rejected
        end
    end
    isempty(verified) && error("no verified sd0 candidates")
    sort!(verified; by=x -> x[4])
    winner_name, winner_blob = verified[1][1], verified[1][2]
    stage_blobs["sd0"] = winner_blob

    stage_files = Dict{String,Any}()
    for (stage, blob) in stage_blobs
        p = joinpath(out_dir, "$(base).$(stage)")
        atomic_write(p, blob)
        decode_sdo(read_all(p)) == raw ||
            error("stage $(stage) roundtrip verification failed")
        meta = sdo_metadata(blob)
        stage_files[stage] = Dict{String,Any}(
            "path"   => p,
            "bytes"  => length(blob),
            "method" => get(meta, "method", "?"))
    end

    repo_zip_path = nothing
    if add_to_zip
        repo_zip_path = joinpath(out_dir, REPO_ZIP_NAME)
        try
            upsert_zip(repo_zip_path, basename(stage_files["sd0"]["path"]), stage_blobs["sd0"])
        catch
            repo_zip_path = nothing
        end
    end

    manifest = Dict{String,Any}(
        "program"             => JUB_VERSION,
        "raw_len"             => length(raw),
        "raw_sha256"          => raw_sha,
        "scan"                => scan,
        "sd0_winner"          => winner_name,
        "verified_candidates" => [Dict{String,Any}("method"=>m, "bytes"=>n)
                                   for (m, _, _, n) in verified],
        "stage_files"         => stage_files,
        "repository_zip"      => repo_zip_path)

    atomic_write(joinpath(out_dir, "$(base)_manifest.json"),
                 codeunits(JSON3.write(manifest)))
    return manifest
end

# ═══════════════════════════════════════════════════════════════════════════════
# SECTION 18 — DEPLOY / DOWNLOAD WORKFLOWS
# ═══════════════════════════════════════════════════════════════════════════════

function read_or_unwrap(path::AbstractString)::Vector{UInt8}
    data = read_all(path)
    for m in (MAGIC, LEGACY_MAGICS...)
        if length(data) >= length(m) && view(data, 1:length(m)) == m
            return decode_sdo(data)
        end
    end
    return data
end

function deploy(input_path::AbstractString, out_dir::AbstractString;
                base_name::Union{Nothing,AbstractString}=nothing)::Dict{String,Any}
    raw = read_or_unwrap(input_path)
    base = base_name === nothing ? splitext(basename(input_path))[1] : base_name
    return build_stages(raw, out_dir; base_name=base, add_to_zip=true)
end

function download(stage_path::AbstractString, output_path::AbstractString)::Int
    blob = read_all(stage_path)
    raw = decode_sdo(blob)
    atomic_write(output_path, raw)
    return length(raw)
end

function snd_dna(input_path::AbstractString, out_path::AbstractString)::Int
    raw = read_or_unwrap(input_path)
    book = generate_super_column(raw)
    atomic_write(out_path, codeunits(book))
    return length(book)
end

# ═══════════════════════════════════════════════════════════════════════════════
# SECTION 19 — SELF-TEST
# ═══════════════════════════════════════════════════════════════════════════════

struct TestResult
    name::String
    passed::Bool
    detail::String
end

function run_self_test()::Vector{TestResult}
    results = TestResult[]

    # 1. Glyph bijection.
    try
        all_bytes = Vector{UInt8}(0:255)
        s = bytes_to_dna(all_bytes)
        @assert dna_to_bytes(s) == all_bytes
        @assert length(s) == 256
        @assert length(Set(collect(s))) == 256
        push!(results, TestResult("glyph_bijection_256", true, "256<->256 exact"))
    catch e
        push!(results, TestResult("glyph_bijection_256", false, string(e)))
    end

    # 2. Random DNA roundtrip.
    try
        sample = rand(UInt8, 8192)
        @assert dna_to_bytes(bytes_to_dna(sample)) == sample
        push!(results, TestResult("dna_roundtrip_random_8k", true, "8 KiB exact"))
    catch e
        push!(results, TestResult("dna_roundtrip_random_8k", false, string(e)))
    end

    # 3. RLE roundtrip.
    try
        for _ in 1:20
            raw = Vector{Char}(rand([collect('a':'z'); collect('0':'9'); first(SPACE_VIS)], 500))
            @assert collect(rle_decode(rle_encode(raw))) == raw
        end
        push!(results, TestResult("rle_roundtrip_20x500", true, "20 random 500-char vectors"))
    catch e
        push!(results, TestResult("rle_roundtrip_20x500", false, string(e)))
    end

    # 4. MetaTrit codec (including runs < 3, which the C++ reference botches).
    try
        for _ in 1:20
            sample = rand(-1:2, 500)
            enc = MetaTrit.encode(sample)
            dec = MetaTrit.decode(enc)
            @assert dec == sample
        end
        # Spot-check the exact short-run path: [-1] should decode to [-1].
        @assert MetaTrit.decode(MetaTrit.encode([-1])) == [-1]
        @assert MetaTrit.decode(MetaTrit.encode([-1, -1])) == [-1, -1]
        @assert MetaTrit.decode(MetaTrit.encode([ 1,  1,  1])) == [1, 1, 1]
        push!(results, TestResult("metatrit_roundtrip_20x500", true, "full ternary vectors, no truncation"))
    catch e
        push!(results, TestResult("metatrit_roundtrip_20x500", false, string(e)))
    end

    # 5. DNA5 codec.
    try
        sample = rand(UInt8, 4096)
        @assert dna5_decode(dna5_encode(sample)) == sample
        push!(results, TestResult("dna5_roundtrip_4k", true, "4 KiB exact"))
    catch e
        push!(results, TestResult("dna5_roundtrip_4k", false, string(e)))
    end

    # 6. Basket grammar.
    try
        sample = rand(UInt8, 2048)
        btxt = join(render(t) for t in bytes_to_basket_tokens(sample))
        @assert basket_tokens_to_bytes(btxt) == sample
        push!(results, TestResult("basket_roundtrip_2k", true, "2 KiB exact"))
    catch e
        push!(results, TestResult("basket_roundtrip_2k", false, string(e)))
    end

    # 7. Brother Quicky (text route).
    try
        sample = Vector{UInt8}(codeunits(
            "The quick brown fox.\nPack my box.\n~Test*reserved¤chars↩here=\r\n" ^ 8))
        pkg = brother_quicky_package(sample)
        @assert decode_brother_quicky_package(pkg) == sample
        push!(results, TestResult("brother_quicky_roundtrip", true, "$(length(sample)) bytes exact"))
    catch e
        push!(results, TestResult("brother_quicky_roundtrip", false, string(e)))
    end

    # 8. SDO roundtrip on every encode method.
    for m in candidate_methods()
        nm = method_name(m)
        try
            data = rand(UInt8, 4096)
            blob = build_sdo(m, data; stage="sd0_candidate")
            @assert decode_sdo(blob) == data
            push!(results, TestResult("sdo_roundtrip_$(nm)", true, "4 KiB exact"))
        catch e
            push!(results, TestResult("sdo_roundtrip_$(nm)", false, string(e)))
        end
    end

    # 9. Full stage build.
    try
        mktempdir() do tmp
            inp = joinpath(tmp, "sample.bin")
            atomic_write(inp, rand(UInt8, 4096))
            manifest = deploy(inp, tmp; base_name="sample")
            @assert haskey(manifest, "sd0_winner")
            for stage in ("sd4", "sd3", "sd2", "sd1", "sd0")
                p = manifest["stage_files"][stage]["path"]
                @assert isfile(p)
                @assert decode_sdo(read_all(p)) == read_all(inp)
            end
        end
        push!(results, TestResult("full_stage_build", true, "5 stages verified"))
    catch e
        push!(results, TestResult("full_stage_build", false, string(e)))
    end

    # 10. SND DNA book.
    try
        data = codeunits("Jub.jl SND DNA test payload.")
        book = generate_super_column(data)
        @assert occursin("000:ZXY", book)
        @assert occursin(":X", book)
        push!(results, TestResult("snd_dna_generation", true, "$(length(book)) chars"))
    catch e
        push!(results, TestResult("snd_dna_generation", false, string(e)))
    end

    # 11. Strand slices reconstruct the canonical table.
    try
        union_str = STRAND_A * STRAND_T * STRAND_C * STRAND_G * STRAND_Z
        @assert length(union_str) == 256
        @assert union_str == DNA_ALPHABET
        push!(results, TestResult("strand_slices_exact_256", true, "5 slices, no overlap"))
    catch e
        push!(results, TestResult("strand_slices_exact_256", false, string(e)))
    end

    # 12. CP437 spot-checks.
    try
        spot = Dict{UInt8,Char}(
            UInt8(0x20) => ' ', UInt8(0x30) => '0', UInt8(0x41) => 'A',
            UInt8(0x61) => 'a', UInt8(0x7E) => '~', UInt8(0xB3) => '│',
            UInt8(0xDB) => '█', UInt8(0xE0) => 'α', UInt8(0xFE) => '■',
            UInt8(0x03) => '♥', UInt8(0x04) => '♦', UInt8(0x0B) => '♂')
        for (b, expected) in spot
            got = GLYPH_256[Int(b) + 1]
            @assert got == expected "byte 0x$(string(b; base=16)): expected $(expected), got $(got)"
        end
        push!(results, TestResult("cp437_spot_checks", true, "$(length(spot)) known positions"))
    catch e
        push!(results, TestResult("cp437_spot_checks", false, string(e)))
    end

    # 13. SymbolFunctionRegistry.
    try
        reg = build_glyph_registry()
        @assert length(reg) == 256
        @assert length(Set(e.role for e in reg)) == 12
        @assert length(Set(e.gate for e in reg)) == 4
        @assert length(Set(e.sector for e in reg)) == 8
        push!(results, TestResult("glyph_registry_256", true, "12 roles / 4 gates / 8 sectors"))
    catch e
        push!(results, TestResult("glyph_registry_256", false, string(e)))
    end

    # 14. CoreState248 invariants.
    try
        s = CoreState248()
        @assert length(s.real) == 248
        @assert abs(sqrt(sum(x^2 for x in s.real)) - 1.0) < 1e-9
        perceive!(s, Dict{String,Any}("entropy_bits_per_byte"=>7.9,
                                       "raw_len"=>1_000_000,
                                       "magic"=>"bin"))
        @assert abs(sqrt(sum(x^2 for x in s.real)) - 1.0) < 1e-9
        push!(results, TestResult("corestate248_invariants", true, "norm preserved"))
    catch e
        push!(results, TestResult("corestate248_invariants", false, string(e)))
    end

    return results
end

function print_self_test(results::Vector{TestResult})::Int
    passed = count(r -> r.passed, results)
    failed = length(results) - passed
    println("═══ Jub.jl Self-Test (CP437, extended) ═══")
    for r in results
        mark = r.passed ? "PASS" : "FAIL"
        @printf("  [%s] %-34s  %s\n", mark, r.name, r.detail)
    end
    @printf("\n  %d passed, %d failed\n", passed, failed)
    return failed == 0 ? 0 : 1
end

# ═══════════════════════════════════════════════════════════════════════════════
# SECTION 20 — DOCTOR (environment report)
# ═══════════════════════════════════════════════════════════════════════════════

function doctor()::Dict{String,Any}
    return Dict{String,Any}(
        "program"          => JUB_VERSION,
        "julia_version"    => string(VERSION),
        "threads"          => Threads.nthreads(),
        "utc_now"          => utc_now(),
        "cwd"              => pwd(),
        "repertoire"       => "CP437 (IBM PC OEM-US, 1981)",
        "glyph_map_length" => length(GLYPH_256),
        "unique_glyphs"    => length(Set(GLYPH_256)),
        "reservoir_size"   => length(_RESERVOIR),
        "first_glyph"      => "U+$(string(UInt32(GLYPH_256[1]); base=16))",
        "last_glyph"       => "U+$(string(UInt32(GLYPH_256[256]); base=16))",
        "strand_sizes"     => Dict{String,Int}(
            "A"=>length(STRAND_A), "T"=>length(STRAND_T),
            "C"=>length(STRAND_C), "G"=>length(STRAND_G),
            "Z"=>length(STRAND_Z)),
        "gtk_available"    => _HAS_GTK,
        "ziparchives"      => _HAS_ZIPARCHIVES,
        "method_names"     => [method_name(m) for m in candidate_methods()],
        "doctrine"         => Dict{String,Bool}(
            "maximal_work_limited_power" => true,
            "interactionspace_first"     => true,
            "spacetime_is_emergent"      => true,
            "sha256_used_as_hash_not_codec" => true,
            "more_bridges_same_river"    => true))
end

# ═══════════════════════════════════════════════════════════════════════════════
# SECTION 21 — GUI (Gtk4, optional)
# ═══════════════════════════════════════════════════════════════════════════════

function _g4_open_dialog(title::String)::String
    try
        if isdefined(Gtk4, :open_dialog)
            r = Gtk4.open_dialog(title)
            return r === nothing ? "" : String(r)
        end
    catch
    end
    try
        return strip(read(`zenity --file-selection --title=$title`, String))
    catch
    end
    return ""
end

function _g4_save_dialog(title::String, initial::String)::String
    try
        if isdefined(Gtk4, :save_dialog)
            r = Gtk4.save_dialog(title)
            return r === nothing ? "" : String(r)
        end
    catch
    end
    try
        return strip(read(`zenity --file-selection --save --title=$title --filename=$initial`, String))
    catch
    end
    return ""
end

function launch_gui()::Int
    if !_HAS_GTK
        println("Gtk4.jl is not installed.")
        println("Install with:  julia -e 'using Pkg; Pkg.add(\"Gtk4\")'")
        println()
        println("CLI usage summary:")
        println("  julia Jub.jl self-test")
        println("  julia Jub.jl doctor")
        println("  julia Jub.jl deploy <input> [out_dir]")
        println("  julia Jub.jl download <stage> <out>")
        println("  julia Jub.jl snd-dna <input> [out]")
        println("  julia Jub.jl grammar-check")
        println("  julia Jub.jl metatrit <input>")
        println("  julia Jub.jl dna5 <input>")
        return 2
    end

    state = Dict{String,Any}("active_raw"=>nothing, "active_path"=>"")

    win = Gtk4.GtkWindow("Jub.jl — V.A.S.K. Cockpit (CP437, extended)")
    Gtk4.set_gtk_property!(win, :default_width,  720)
    Gtk4.set_gtk_property!(win, :default_height, 900)

    root = Gtk4.GtkBox(:v)
    win[] = root

    title = Gtk4.GtkLabel("V.A.S.K. — Julia Cockpit (CP437, extended)")
    Gtk4.set_gtk_property!(title, :margin_top, 12)
    Gtk4.set_gtk_property!(title, :margin_bottom, 12)
    push!(root, title)

    action_row = Gtk4.GtkBox(:h)
    push!(root, action_row)
    deploy_btn   = Gtk4.GtkButton("DEPLOY")
    download_btn = Gtk4.GtkButton("DOWNLOAD")
    Gtk4.set_gtk_property!(deploy_btn,   :hexpand, true)
    Gtk4.set_gtk_property!(download_btn, :hexpand, true)
    push!(action_row, deploy_btn)
    push!(action_row, download_btn)

    grid = Gtk4.GtkGrid()
    push!(root, grid)

    formats = (".any", ".sd4", ".sd3", ".sd2", ".sd1", ".sd0")
    deploy_group = nothing
    dl_group     = nothing
    deploy_vars  = Dict{String,Any}()
    dl_vars      = Dict{String,Any}()

    for fmt in formats
        rb_d = Gtk4.GtkRadioButton("DEPLOY $(fmt)")
        if deploy_group === nothing
            deploy_group = rb_d
        else
            Gtk4.set_gtk_property!(rb_d, :group, deploy_group)
        end
        deploy_vars[fmt] = rb_d
        push!(grid, rb_d)

        rb_g = Gtk4.GtkRadioButton("DOWNLOAD $(fmt)")
        if dl_group === nothing
            dl_group = rb_g
        else
            Gtk4.set_gtk_property!(rb_g, :group, dl_group)
        end
        dl_vars[fmt] = rb_g
        push!(grid, rb_g)
    end

    status = Gtk4.GtkLabel("MACHINE AWAKE | STANDBY")
    Gtk4.set_gtk_property!(status, :margin_top, 12)
    push!(root, status)

    snd_btn = Gtk4.GtkButton("SND DNA")
    Gtk4.set_gtk_property!(snd_btn, :hexpand, true)
    Gtk4.set_gtk_property!(snd_btn, :sensitive, false)
    push!(root, snd_btn)

    function selected_deploy_format()::String
        for (fmt, rb) in deploy_vars
            Gtk4.get_gtk_property(rb, :active, Bool) && return fmt
        end
        return ".any"
    end
    function selected_download_format()::String
        for (fmt, rb) in dl_vars
            Gtk4.get_gtk_property(rb, :active, Bool) && return fmt
        end
        return ".any"
    end

    Gtk4.signal_connect(deploy_btn, "clicked") do _
        p = _g4_open_dialog("Select file to DEPLOY")
        isempty(p) && return
        out = p
        try
            raw = read_or_unwrap(p)
            state["active_raw"]  = raw
            state["active_path"] = p
            fmt = selected_deploy_format()
            if fmt == ".any"
                out = p * ".any"
                atomic_write(out, raw)
            else
                mktempdir() do tmp
                    manifest = build_stages(raw, tmp;
                                            base_name=splitext(basename(p))[1],
                                            add_to_zip=false)
                    stage = fmt[2:end]
                    src = manifest["stage_files"][stage]["path"]
                    out = p * fmt
                    atomic_write(out, read_all(src))
                end
            end
            Gtk4.set_gtk_property!(status, :label, "DEPLOYED: $(basename(out))")
            Gtk4.set_gtk_property!(snd_btn, :sensitive, true)
        catch e
            Gtk4.set_gtk_property!(status, :label, "DEPLOY FAILED: $(sprint(showerror, e))")
        end
    end

    Gtk4.signal_connect(download_btn, "clicked") do _
        raw = state["active_raw"]
        raw === nothing && (Gtk4.set_gtk_property!(status, :label, "Deploy a file first."); return)
        fmt = selected_download_format()
        try
            out = _g4_save_dialog("Save $(fmt)", "restored$(fmt)")
            isempty(out) && return
            if fmt == ".any"
                atomic_write(out, raw)
            else
                mktempdir() do tmp
                    manifest = build_stages(raw, tmp;
                                            base_name="restored",
                                            add_to_zip=false)
                    stage = fmt[2:end]
                    src = manifest["stage_files"][stage]["path"]
                    atomic_write(out, read_all(src))
                end
            end
            Gtk4.set_gtk_property!(status, :label, "DOWNLOADED: $(basename(out))")
        catch e
            Gtk4.set_gtk_property!(status, :label, "DOWNLOAD FAILED: $(sprint(showerror, e))")
        end
    end

    Gtk4.signal_connect(snd_btn, "clicked") do _
        raw = state["active_raw"]
        raw === nothing && return
        try
            book = generate_super_column(raw)
            out = state["active_path"] * ".DNA_BOOK.txt"
            atomic_write(out, codeunits(book))
            Gtk4.set_gtk_property!(status, :label,
                "SND DNA → $(basename(out)) ($(length(book)) chars)")
        catch e
            Gtk4.set_gtk_property!(status, :label, "SND DNA FAILED: $(sprint(showerror, e))")
        end
    end

    Gtk4.present(win)
    if isdefined(Gtk4, :gtk_main)
        Gtk4.gtk_main()
    elseif isdefined(Gtk4, :GLib) && isdefined(Gtk4.GLib, :MainLoop)
        loop = Gtk4.GLib.MainLoop()
        Gtk4.GLib.run(loop)
    end
    return 0
end

# ═══════════════════════════════════════════════════════════════════════════════
# SECTION 22 — CLI ENTRY
# ═══════════════════════════════════════════════════════════════════════════════

function cmd_grammar_check(args::Vector{String})::Int
    println(JSON3.write(grammar_report()))
    return 0
end

function cmd_metatrit(args::Vector{String})::Int
    isempty(args) && (println("usage: metatrit <input>"); return 1)
    raw = read_or_unwrap(args[1])
    z = transcode(ZlibCompressor, raw)
    trits = MetaTrit.bytes_to_trits(z)
    mt_text = MetaTrit.encode(trits)
    back = MetaTrit.trits_to_bytes(MetaTrit.decode(mt_text))
    ok = back == z
    println(JSON3.write(Dict{String,Any}(
        "input"        => args[1],
        "raw_len"      => length(raw),
        "zlib_len"     => length(z),
        "trit_count"   => length(trits),
        "metatrit_chars" => length(mt_text),
        "roundtrip_ok" => ok)))
    return ok ? 0 : 1
end

function cmd_dna5(args::Vector{String})::Int
    isempty(args) && (println("usage: dna5 <input>"); return 1)
    raw = read_or_unwrap(args[1])
    enc = dna5_encode(raw)
    back = dna5_decode(enc)
    ok = back == raw
    println(JSON3.write(Dict{String,Any}(
        "input"    => args[1],
        "raw_len"  => length(raw),
        "encoded_chars" => length(enc),
        "roundtrip_ok" => ok)))
    return ok ? 0 : 1
end

function main(args::Vector{String}=ARGS)::Int
    if isempty(args)
        if _HAS_GTK
            return launch_gui()
        else
            println("Jub.jl — V.A.S.K. Functional DNA Organism (CP437, extended)")
            println("Gtk4 not installed; use one of the CLI commands below.")
            println()
            println("Commands:")
            println("  (no args)              Launch GUI (requires Gtk4)")
            println("  self-test              Full roundtrip suite")
            println("  doctor                 Environment report (JSON)")
            println("  version                Print version")
            println("  deploy <in> [out_dir]  Build all stages (.sd4..sd0)")
            println("  download <stage> <out> Reconstruct raw bytes")
            println("  snd-dna <in> [out]     Generate SND DNA super-column book")
            println("  grammar-check          DNA grammar round-trip report")
            println("  metatrit <in>          MetaTrit round-trip on one file")
            println("  dna5 <in>              DNA5 round-trip on one file")
            println("  gui                    Explicit GUI launch")
            return 0
        end
    end

    cmd = lowercase(args[1])
    rest = args[2:end]

    if cmd == "--self-test" || cmd == "self-test"
        return print_self_test(run_self_test())
    elseif cmd == "--doctor" || cmd == "doctor"
        println(JSON3.write(doctor()))
        return 0
    elseif cmd == "--version" || cmd == "version"
        println(JUB_VERSION)
        return 0
    elseif cmd == "deploy"
        isempty(rest) && (println("usage: deploy <input> [out_dir]"); return 1)
        input   = rest[1]
        out_dir = length(rest) >= 2 ? rest[2] : joinpath(pwd(), "REPOSITORY")
        println(JSON3.write(deploy(input, out_dir)))
        return 0
    elseif cmd == "download"
        length(rest) < 2 && (println("usage: download <stage> <out>"); return 1)
        n = download(rest[1], rest[2])
        println("Decoded $(n) bytes → $(rest[2])")
        return 0
    elseif cmd == "snd-dna"
        isempty(rest) && (println("usage: snd-dna <input> [out]"); return 1)
        input = rest[1]
        out   = length(rest) >= 2 ? rest[2] : input * ".DNA_BOOK.txt"
        n = snd_dna(input, out)
        println("SND DNA written: $(out) ($(n) chars)")
        return 0
    elseif cmd == "grammar-check"
        return cmd_grammar_check(rest)
    elseif cmd == "metatrit"
        return cmd_metatrit(rest)
    elseif cmd == "dna5"
        return cmd_dna5(rest)
    elseif cmd == "gui"
        return launch_gui()
    else
        println("Jub.jl — V.A.S.K. Functional DNA Organism (CP437, extended)")
        println("Unknown command: $(cmd). Use --help or run with no args.")
        return 0
    end
end

end # module JubJL

if abspath(PROGRAM_FILE) == @__FILE__
    exit(JubJL.main(ARGS))
end