"""Freeze a small real MaleCNS subgraph and independent Python reference.

This is a NEW reduced experiment, not a rerun of the full 2026-09-11 assay.
No live state, credentials, wallet access or network requests.
"""
from pathlib import Path
import datetime, hashlib, json, math
import numpy as np
import scipy.sparse as sp

ROOT = Path(__file__).resolve().parents[1]
OUT = ROOT / 'public/science'
def digest(raw): return hashlib.sha256(raw).hexdigest()
def canonical(obj): return json.dumps(obj, separators=(',', ':'), ensure_ascii=False)

def run(f, condition):
    n = len(f['cells']); inputs = set(f['inputs']); motor = set(f['motors'])
    v = [-52.] * n; g = [0.] * n; last = [-1000000] * n
    queue = [[] for _ in range(10)]; rng = f['seed']; frames = []
    em = math.exp(-.2/20); es = math.exp(-.2/5); coupling = 5/15*(em-es)
    edges = [[] for _ in range(n)]
    for source,target,weight in f['edges']: edges[source].append((target,weight))
    for window in range(14):
        counts = [0]*n; events = 0
        for sub in range(100):
            tick = window*100+sub
            free = [tick-last[i] >= (0 if i in inputs else 11) for i in range(n)]
            for i in range(n):
                if free[i]:
                    v[i] = -52+(v[i]+52)*em+g[i]*coupling; g[i] *= es
            fired = [i for i in range(n) if free[i] and v[i] > -45]
            for i in fired: last[i] = tick; free[i] = False; counts[i] += 1
            slot = tick%10; queue[(tick+9)%10] = fired
            sources = queue[slot]; queue[slot] = []
            delta = [0.]*n
            for source in sources:
                if condition == 'blocked' and source in inputs: continue
                for target,weight in edges[source]: delta[target] += weight
            for i in range(n):
                if free[i]: g[i] += delta[i]
            for i in f['inputs']:
                rng ^= (rng << 13) & 0xffffffff; rng ^= rng >> 17; rng ^= (rng << 5) & 0xffffffff; rng &= 0xffffffff
                if condition != 'no-input' and 2 <= window <= 9 and rng/4294967296 < .02:
                    events += 1
                    if free[i]: v[i] += 68.75
            for i in fired: v[i] = -52.; g[i] = 0.
        frames.append({'modelMs':(window+1)*20,'inputEvents':events,'active':sum(x>0 for x in counts),'outside':sum(counts[i]>0 for i in range(n) if i not in inputs),'spikes':sum(counts),'motorSpikes':sum(counts[i] for i in motor)})
    return {'condition':condition,'frames':frames}

def main():
    graph = ROOT/'work/science/graph.npz'
    manifest = json.loads((ROOT/'work/science/manifest.json').read_text())
    assert digest(graph.read_bytes()) == manifest['graphSha256']
    z = np.load(graph, allow_pickle=False)
    w = sp.csr_matrix((z['data'], z['indices'], z['indptr']), shape=tuple(z['shape'])).tocsc()
    bodies=z['bodies']; types=z['types'].astype(str)
    inputs=[]
    for kind in ['L1','L2']:
        candidates=np.flatnonzero(types==kind)
        inputs.extend(sorted(candidates,key=lambda i:int(bodies[i]))[:32])
    score=np.asarray(abs(w[:,inputs]).sum(axis=1)).ravel(); score[inputs]=0
    downstream=sorted(np.flatnonzero(score>0),key=lambda i:(-float(score[i]),int(bodies[i])))[:96]
    motors=list(np.flatnonzero(np.isin(types,['DNa02','DNa01','MDN','DNp09'])))
    selected=sorted(set(inputs+downstream+motors),key=lambda i:int(bodies[i])); index={old:new for new,old in enumerate(selected)}
    cut=w[selected,:][:,selected].tocoo()
    edges=sorted([[int(c),int(r),float(v)] for r,c,v in zip(cut.row,cut.col,cut.data)])
    fixture={'schema':'fruit.browser-lif.v1','createdAt':datetime.datetime.now(datetime.timezone.utc).isoformat(),'sourceGraphSha256':manifest['graphSha256'],'sourceNeurons':int(z['shape'][0]),'selection':'32 lowest-bodyId L1 + 32 lowest-bodyId L2; 96 strongest direct downstream targets by summed absolute input weight; all DNa01/DNa02/MDN/DNp09. Retain induced edges; omit all external edges. Anatomy-based selection, not a representative whole brain.','model':'Reduced LIF; dt 0.2 ms, rest/reset -52 mV, threshold > -45 mV, tau m 20 ms, tau g 5 ms, delay 1.8 ms, refractory 2.2 ms (inputs 0). Original signed weights; no gain tuning.','stimulus':'100 Hz artificial input to all selected L1/L2 cells, 40–200 ms; xorshift32 seed 17. Not the full visual encoder or original Poisson draws.','seed':17,'cells':[{'bodyId':int(bodies[i]),'type':str(types[i])} for i in selected],'inputs':[index[i] for i in inputs],'motors':[index[i] for i in motors],'edges':edges}
    raw=canonical(fixture).encode(); (OUT/'browser-assay-fixture.json').write_bytes(raw)
    result=[run(fixture,c) for c in ['connected','no-input','blocked']]
    reference={'schema':'fruit.browser-reference.v1','createdAt':fixture['createdAt'],'fixtureSha256':digest(raw),'expectedResultsSha256':digest(canonical(result).encode()),'results':result,'archiveDate':'2026-09-11','archiveSha256':digest((OUT/'habitat-assay.json').read_bytes()),'scope':'Archive checksum verifies bytes only. Reduced LIF match verifies this newly frozen Python reference only, not the original full-network assay.','neurons':len(selected),'edges':len(edges)}
    (OUT/'browser-assay-reference.json').write_text(canonical(reference),encoding='utf-8')
    print(canonical({'neurons':len(selected),'edges':len(edges),'peaks':[max(f['outside'] for f in r['frames']) for r in result],'checksum':reference['expectedResultsSha256']}))
if __name__ == '__main__': main()
