"""Exploratory association of modulo-29 rune jumps with word boundaries.

One aggregate statistic across eight sections. Null: independently rotate the
boundary labels within each section's transition stream by a nonzero amount.
This preserves the exact rune-transition stream and each boundary label count.
It assumes shifted boundary locations are a suitable comparison, not that the
ciphertext is independent random data. No decryption follows from significance.
"""
import json
from word_core import *


def mutual_information(gap, boundary):
    counts=np.bincount(gap*2+boundary,minlength=58).reshape(29,2).astype(float)
    joint=counts/counts.sum();independent=joint.sum(1)[:,None]*joint.sum(0)[None,:]
    mask=joint>0
    return float(np.sum(joint[mask]*np.log(joint[mask]/independent[mask])))


def main():
    sections=[]
    for i,source in enumerate('%'.join(lp[:56]).split('$')):
        c=vals(source)
        if len(c)<60:continue
        b=np.zeros(len(c)-1,dtype=int)
        for w in tokenize(source)[1:]:b[w['rune_offset']-1]=1
        gap=(c[1:]-c[:-1])%29
        sections.append((i,gap,b))
    total=sum(len(g) for _,g,_ in sections)
    observed=sum(len(g)*mutual_information(g,b) for _,g,b in sections)/total
    rng=np.random.default_rng(330114);null=[]
    for _ in range(1999):
        null.append(sum(len(g)*mutual_information(g,np.roll(b,int(rng.integers(1,len(b))))) for _,g,b in sections)/total)
    hits=sum(x>=observed for x in null)
    out={'statistic':'section-weighted mutual information in nats: modulo-29 adjacent-rune gap versus word-boundary label',
         'observed':observed,'null_mean':float(np.mean(null)),'null_95_percentile':float(np.quantile(null,.95)),
         'null_at_least_observed':hits,'null_draws':len(null),'upper_tail_estimate':(hits+1)/(len(null)+1),
         'seed':330114,'transitions':total,
         'sections':[{'section':i,'transitions':len(g),'word_boundaries':int(b.sum()),
                      'observed_information':mutual_information(g,b)} for i,g,b in sections],
         'null_statistics':null,
         'limits':['Exploratory hypothesis using previously inspected corpus, not independent confirmation.',
                   'Tests only one-step differences of rune indices modulo29.',
                   'Rotation null may be affected by spatial nonstationarity; it is not a universal cipher null.',
                   'An ordinary tail value cannot establish absence of all word-dependent structure.']}
    (ROOT/'boundary_gap_results.json').write_text(json.dumps(out,indent=2)+'\n')
    print(json.dumps({k:v for k,v in out.items() if k!='null_statistics'},indent=2))


if __name__=='__main__':main()
