"""Build an independent Bingus plaintext addon. Never install or execute in game."""
from pathlib import Path
import hashlib,json,struct,zipfile
ROOT=Path(__file__).resolve().parents[1]
RESOURCE='mods/arena_local/fivex_spawns'
GUID='1acbdc87-8454-4d99-bf71-d3f29b88323b'
PATCH='9ba626afa44a3aa3.patch_0'
LUA_TYPE=0xA14E8DFA2CD117E2
def murmur64(data):
    mask=(1<<64)-1
    m=0xc6a4a7935bd1e995
    h=(len(data)*m)&mask
    end=len(data)//8*8
    for offset in range(0,end,8):
        k=int.from_bytes(data[offset:offset+8],'little')
        k=k*m&mask
        k^=k>>47
        k=k*m&mask
        h=((h^k)*m)&mask
    if end<len(data):
        h^=int.from_bytes(data[end:],'little')
        h=h*m&mask
    h^=h>>47
    h=h*m&mask
    return h^(h>>47)

def archive(source):
    marker=('-- HD2-Addon: '+RESOURCE+'\n').encode()
    assert source.startswith(marker) and len(marker)<=256
    source.decode('utf-8')
    assert b'\0' not in source
    payload=struct.pack('<II',len(source),2)+source
    offset=192
    length=(offset+len(payload)+15)//16*16
    header=struct.pack('<III20sQQ24s',0xf0000011,1,1,b'',length,0,b'')
    type_entry=struct.pack('<IIQIIII',0,0,LUA_TYPE,1,0,16,16)
    resource_entry=struct.pack('<7Q6I',murmur64(RESOURCE.encode()),LUA_TYPE,offset,0,0,0,0,len(payload),0,0,16,16,0)
    result=header+type_entry+resource_entry
    result+=b'\0'*(offset-len(result))+payload
    return result+b'\0'*(length-len(result))

def main():
    assembled='-- HD2-Addon: '+RESOURCE+'\n'
    for variable,file in [('create_api','windows_api.lua'),('patch','spawn_patch.lua'),('frequency','frequency.lua'),('reinforcement','reinforcement.lua'),('cooldown','call_cooldown.lua'),('diagnostics','diagnostics.lua'),('install','lifecycle.lua'),('unit_asset','unit_asset.lua'),('transport_plan','transport_plan.lua'),('robot_weight_policy','robot_weight.lua'),('robot_factory','robot_runtime.lua'),('weight_layer','robot_weights.lua'),('illuminate_factory','illuminate_runtime.lua'),('illuminate_layer','illuminate_reinforcement.lua')]:
        assembled+='local '+variable+'=(function()\n'+(ROOT/'src'/file).read_text()+'\nend)()\n'
    assembled+="local robot=robot_factory(unit_asset,transport_plan,robot_weight_policy);patch.robot=robot\nlocal illuminate=illuminate_layer(illuminate_factory(unit_asset));patch.illuminate=illuminate\npatch=diagnostics(cooldown(weight_layer(reinforcement(frequency(patch),robot,illuminate),robot,true),true))\n"
    assembled+="install(create_api,patch,{revision='arena-illuminate-v1.2.0-exp1-host-budget3-cd50',exe_sha256='a09ff52663e73b94fb0cac0dcb5ba84ffd10ecf44f74a8921ac66af923988cc3',game_sha256='cc75948d90fdfde259dcb519e9933db7ffa3ccb281ce4fb89e6b1b011557470c'})\n"
    for forbidden in ['VirtualProtect','FlushInstructionCache','CreateRemoteThread','LoadLibrary']:
        assert forbidden not in assembled,forbidden
    from lupa.luajit21 import LuaRuntime
    LuaRuntime().execute('assert(loadstring(...))',assembled)
    (ROOT/'src/fivex_spawns.lua').write_text(assembled)
    source=assembled.encode();data=archive(source)
    title='Arena Illuminate v1.2.0-exp1 Host Call Budget 3x CD50'
    description='EXPERIMENTAL Illuminate extension of user-accepted Robot v1.1.0. HOST ONLY, 1-4 players. Illuminate call budget x3, native weights/costs/members/transport capacity; target about3x cumulative units, not exact delivery. All factions patrol up to5 cap32 interval /5, delays /3, current-faction CD base50. Terminid/Automaton tuning unchanged. New Illuminate actual spawning and multiplayer NOT verified. Fully restart before replacement.'
    manifest={'Version':1,'Guid':GUID,'Name':title,'Description':description,'Options':[{'Name':title,'Description':description,'Include':['Addon']}]}
    files={'manifest.json':(json.dumps(manifest,indent=2)+'\n').encode(),'README.md':(ROOT/'README.md').read_bytes(),'NOTICE.md':(ROOT/'NOTICE.md').read_bytes(),'Addon/'+PATCH:data,'Addon/'+PATCH+'.stream':b'','Addon/'+PATCH+'.gpu_resources':b''}
    assert struct.unpack_from('<III',data)==(0xf0000011,1,1)
    assert struct.unpack_from('<Q',data,32)[0]==len(data)
    identity,type_id,start=struct.unpack_from('<QQQ',data,104);size=struct.unpack_from('<I',data,160)[0];length,version=struct.unpack_from('<II',data,start)
    assert identity==murmur64(RESOURCE.encode()) and type_id==LUA_TYPE and start%16==0
    assert size==length+8 and version==2 and data[start+8:start+8+length]==source
    report={'archive_roundtrip':True,'lua_syntax_checked':True,'resource':RESOURCE,'auto_install':False,'in_game_tested':False,'experimental':True,'integrated_build':True,'runtime_acceptance_complete':False,'patrol_quantity_cap':32,'reinforcement_budget_multiplier':{'terminid':1,'automaton':1,'illuminate':3},'reinforcement_member_multiplier':{'terminid':5,'automaton_up_to':5,'illuminate':1},'reinforcement_member_cap':32,'reinforcement_delay_divisor':3,'exact_reinforcement_headcount':False,'weights_changed':True,'weight_scope':'Automaton pure-heavy only','robot_heavy_weight_multiplier':2.5,'illuminate_enabled':True,'illuminate_call_budget_multiplier':3,'illuminate_member_multiplier':1,'illuminate_weights_changed':False,'illuminate_runtime_layout_sampled':True,'illuminate_spawn_runtime_verified':False,'native_illuminate_overcapacity_templates_observed':2,'transport_capacity_preserved':True,'robot_runtime_layout_sampled':True,'robot_spawn_runtime_verified':False,'current_timer_writes':False,'ordinary_patrol_interval_divisor':5,'enemy_call_cooldown_base_seconds':50,'call_cd_runtime_tested':False,'host_only':True,'allowed_team_size':[1,4],'multiplayer_runtime_tested':False}
    (ROOT/'validation/package-check.json').write_text(json.dumps(report,indent=2)+'\n')
    for name in ['project.json','README.md','NOTICE.md','tools/build.py','tools/test.py','tools/test_factions.py','tools/check_native.py']:
        files['Source/'+name]=(ROOT/name).read_bytes()
    for folder in ['src','tests','validation']:
        for p in (ROOT/folder).rglob('*'):
            if p.is_file():files['Source/'+p.relative_to(ROOT).as_posix()]=p.read_bytes()
    files['SHA256SUMS.txt']=''.join(hashlib.sha256(b).hexdigest()+'  '+name+'\n' for name,b in sorted(files.items())).encode()
    release=ROOT/'releases/Arena-Illuminate-v1.2.0-exp1-host-budget3-cd50.zip';release.parent.mkdir(exist_ok=True)
    with zipfile.ZipFile(release,'w')as z:
        for name,b in sorted(files.items()):
            i=zipfile.ZipInfo(name,(2026,9,21,0,0,0));i.compress_type=zipfile.ZIP_DEFLATED;i.external_attr=0o100644<<16;z.writestr(i,b)
    with zipfile.ZipFile(release)as z:
        assert z.testzip() is None
        for name,b in files.items():assert z.read(name)==b
    sha=hashlib.sha256(release.read_bytes()).hexdigest();release.with_suffix('.zip.sha256').write_text(sha+'  '+release.name+'\n');print(release,release.stat().st_size,sha)
if __name__=='__main__':main()
