#!/usr/bin/env python3
"""Shared headless release gate. Never installs a ROM or touches live saves.
Full command and fixture requirements: ../notes/TEST_PLAN.md.
"""
from pathlib import Path
from datetime import datetime,timezone
import argparse,hashlib,json,os,struct,subprocess,time
from concurrent.futures import ThreadPoolExecutor
from threading import RLock
HERE=Path(__file__).resolve().parent
REQUIRED=['critical','critical_flash','critical_trail','season_navigation','finished_season','menu_team','menu_font','season_slots','shootout','four_goalie','goalie_request','shootout4','forum_fixes','critical_velocity','shot_panel','shot_colour','four_lifecycle','season_settings','ai_defence','ai_defence_gameplay','ai_attack','ai_attack_gameplay','ai_gap','ai_gap_gameplay','ai_playbook','ai_playbook_gameplay','crowd_modes','credits','leaders_exit','hud_overlay','season_identity','replay_option','full_cpu_game','rewind_wrap','pwrplay_clear','coach_goalie','coach_lines','goalie_season','crit_option','crit_menu_render']
REQUIRED += ['ai_cross_choice','ai_cross_hooks','ai_steering','ai_steering_frequency','ai_support_spots','ai_keeper_pass','ai_relay_choice','ai_attribution','ai_complete_lanes','ai_actor_layout','finisher_hold']
REQUIRED += ['centered_cross_gameplay']
REQUIRED += ['ai_pass_plan', 'ai_pass_plan_gameplay', 'goalie_attack', 'goalie_attack_reset', 'goalie_attack_gameplay', 'sram_commit_readiness', 'ai_flight_coverage']
REQUIRED += ['crit_lock','b_switch','stars_hud','maintenance_replay']
REQUIRED += ['goalie_lateral','goalie_lateral_gameplay']
REQUIRED += ['outlet_deadline','icing_menu','icing_rule','goalie_catch_control','goalie_catch_gameplay','menu_defaults']
REQUIRED += ['whistle_momentum','mini_division','mini_seven','human_contact','penalty_owner','manual_keeper_pass','net_contact','shootout_rules','downed_receiver','pass_height','pass_speed']
REQUIRED += ['fair_pickup','pass_mode','penalty_rate','easy_stats','pad_colors','pass_charge','pickup_delay','alt_goalies','strict_so','shot_plan','faceoff_dots']
REQUIRED += ['finisher_position','momentum','shot_panel_left','quickgame','quickgame_flow','quickgame_shootout']
REQUIRED += ['allstar','allstar_flow','allstar_shootout','replay_hud','quickgame_cpu','quickgame_cpu_flow','quickgame_cpu_shootout','menu_background','neon_marker']
REQUIRED += ['allstar_cpu','allstar_cpu_flow','allstar_cpu_shootout']
REQUIRED += ['ai_smart_aim','ai_smart_aim_physics','ai_smart_catch','ai_smart_aim_gameplay']
sha=lambda p:hashlib.sha256(p.read_bytes()).hexdigest()
ap=argparse.ArgumentParser(description=__doc__);ap.add_argument('rom',type=Path);ap.add_argument('output',type=Path)
ap.add_argument('--finisher-baseline',type=Path,help='exact candidate with --no-finish-hold for isolated receiver comparison');ap.add_argument('--tactics-baseline',type=Path,help='exact preceding r3 without new tactics');ap.add_argument('--playbook-baseline',type=Path,help='exact r8 before pressure/blue-line/relay decisions');ap.add_argument('--gap-baseline',type=Path,help='matched current ROM without defensive pair hook; historical r7 is separately recorded');ap.add_argument('--intermission-state',type=Path,help='native captured pre-period3 menu for leader-exit regression');ap.add_argument('--attack-baseline',type=Path,help='r4 prior attack/crowd reference ROM');ap.add_argument('--ai-baseline',type=Path,help='preceding ROM without AI coverage hook, private paired comparison');ap.add_argument('--fixtures-root',type=Path);ap.add_argument('--seed',type=Path);ap.add_argument('--playoffs',type=Path);ap.add_argument('--four-state',type=Path);ap.add_argument('--trail-fixture',type=Path);ap.add_argument('--validate-report',type=Path,help='validate an existing full report against this exact ROM and current test sources')
ap.add_argument('--pass-plan-mode',choices=['open','cross']);ap.add_argument('--pass-plan-ttl',type=int,choices=[64,128]);ap.add_argument('--plan-baseline',type=Path);ap.add_argument('--keeper-goal-state',type=Path);ap.add_argument('--keeper-goal-seed',type=Path);ap.add_argument('--commit-state',type=Path);ap.add_argument('--commit-seed',type=Path)
ap.add_argument('--centered-baseline',type=Path)
ap.add_argument('--smart-baseline',type=Path)
ap.add_argument('--maintenance-traces',type=Path);ap.add_argument('--maintenance-base',type=Path,required=False);ap.add_argument('--stars-state',type=Path,required=False);ap.add_argument('--keep-going',action='store_true',help='Diagnostic full matrix: retain every failure and finish all checks; failed reports still cannot validate')
ap.add_argument('--keeper-reference',type=Path,required=True,help='exact current r2 before the authorized lateral keeper correction')
ap.add_argument('--presentation-reference',type=Path,required=True,help='exact Nov1r4 before receiver positioning, Momentum option and lower-left shot panel')
ap.add_argument('--faceoff-dots-reference',type=Path,required=True,help='exact Nov1r3 before optional painted-dot faceoffs')
ap.add_argument('--shot-plan-reference',type=Path,required=True,help='exact Nov1r2 before optional Hard shot planning')
ap.add_argument('--strict-so-reference',type=Path,required=True,help='exact Nov1 before optional additional OT shootout rules')
ap.add_argument('--alt-goalies-reference',type=Path,required=True,help='exact r11 before optional Alt Goalies')
ap.add_argument('--pickup-delay-reference',type=Path,required=True,help='exactr10 before optional skater pass pickup delay')
ap.add_argument('--charge-reference',type=Path,required=True,help='exact r9 before charged passing and reported marker/net repair')
ap.add_argument('--easy-reference',type=Path,required=True,help='exact r8 before Easy skills/energy and marker colors')
ap.add_argument('--contact-race-reference',type=Path,required=True,help='exact pre-atomic private r8 for the demonstrated native buffered-shot contact race')
ap.add_argument('--penalty-rate-reference',type=Path,required=True,help='exact r7 before the separate Penaltys frequency menu')
ap.add_argument('--directional-reference',type=Path,required=True,help='exact r6 before optional human Directional/cone settings')
ap.add_argument('--pickup-reference',type=Path,required=True,help='exact r5 before fair co-op pickup assignment')
ap.add_argument('--speed-reference',type=Path,required=True,help='exact2026-10-31-r4 before independent pass speed and standalone storage correction')
ap.add_argument('--height-reference',type=Path,required=True,help='exact2026-10-31-r3 before optional pass-height menu')
ap.add_argument('--receiver-reference',type=Path,required=True,help='exact shootout r2 before downed-receiver checks')
ap.add_argument('--shootout-reference',type=Path,required=True,help='exact2026-10-31 before shootout rules/automatic countdown')
ap.add_argument('--net-legacy-control',type=Path,required=True,help='matched current ROM with only legacy net thresholds, for isolated collision/motion proof')
ap.add_argument('--net-reference',type=Path,required=True,help='exact r13 before less sensitive net release')
ap.add_argument('--manual-reference',type=Path,required=True,help='exact r12 before manual keeper native pass routing')
ap.add_argument('--seven-reference',type=Path,required=True,help='exact r11 before seven-game Mini schedule/count')
ap.add_argument('--penalty-reference',type=Path,required=True,help='exact r10 before fouled human penalty-shot ownership')
ap.add_argument('--contact-reference',type=Path,required=True,help='exact r9 before physical human buffered-shot contact')
ap.add_argument('--mini-reference',type=Path,required=True,help='exact r8 before division-only Mini fixtures')
ap.add_argument('--rules-reference',type=Path,required=True,help='exact r7 before selected overtime and whistle momentum fixes')
ap.add_argument('--icing-reference',type=Path,required=True,help='exact r6 before restoring Icing On defaults')
ap.add_argument('--control-reference',type=Path,required=True,help='exact r5 before cooperative catch handoff and requested defaults')
ap.add_argument('--icing-default',type=int,choices=(0,1),default=0,help='explicit requested fresh Icing default for native menu assertions')
ap.add_argument('--menu-reference',type=Path,required=True,help='exact r4 before Icing/Edmonton menu changes')
ap.add_argument('--outlet-reference',type=Path,help='exact r3 before the accepted-request timeout correction')
ap.add_argument('--neon-reference',type=Path,required=True,help='fresh same-version/features pre-neon control, independently rebuilt with --legacy-neon-marker')
ap.add_argument('--replay-hud-reference',type=Path,required=True,help='exact R7 before replay title and preserved lower-left HUD repair')
ap.add_argument('--transfers-reference',type=Path,help='exact qualified Nov2 transfer parent')
ap.add_argument('--refinements-reference',type=Path,required=True,help='exact pre-refinements runtime18 reference for guarded font/art scope')
ap.add_argument('--friendly-reference',type=Path,required=True,help='exact failed private61 reproducing the armed friendly-pass lunge')
ap.add_argument('--allstar-reference',type=Path,required=True,help='exact R6 before conditional Allstar teams')
ap.add_argument('--quickgame-reference',type=Path,required=True,help='exact Nov1r5 before human-versus-human exhibitions; retains complete historical comparison chain')
ap.add_argument('--jobs',type=int,choices=[1,2,3,4],default=1,help='Independent suites in parallel; requires --keep-going when greater than1')
a=ap.parse_args()
if a.jobs>1 and not a.keep_going:ap.error('--jobs greater than1 requires --keep-going')
REQUIRED += ['transfers_'+name for name in ['model','engine','persistence','runtime','season','journal','flow']]
REQUIRED += ['refinements','friendly_reception','transfer_dollars','main_menu','downed_y','team_ovr']
def source_hashes():
 paths=list(HERE.glob('*.py'))+list(HERE.glob('*.s'))+list((HERE/'testcases').glob('*.py'))+list((HERE/'testcases').glob('*.s'))
 paths+=[HERE.parent/'data/menu-background-78.png',HERE.parent/'data/menu-background-78.bin',HERE.parent/'data/boot-credits.png',HERE.parent/'data/boot-credits.bin',HERE.parent/'data/boot-credits-legacy.bin']
 paths+=list((HERE/'testcases').glob('transfer_*.txt'))+list((HERE/'testcases').glob('transfer_*.json'))
 paths += [HERE/'testcases/penalty_blue_line_cases.json',HERE/'testcases/preview_scope_fixture.json']
 paths+=list((HERE.parent/'season').glob('*.s'))+list((HERE.parent/'season').glob('*.py'))+list((HERE.parent/'season').glob('*.inc'))+[HERE.parent/'patch.py',HERE/'allstar_catalog.json']
 return {str(p.relative_to(HERE.parent)):sha(p) for p in sorted(paths)}
def validate(r):
 assert r['rom_sha256']==sha(a.rom),'stale ROM hash in QA report'
 assert r['test_sources']==source_hashes(),'test source changed; rerun affected checks and refresh the report'
 tests={x['test']:x for x in r['checks']}
 assert set(REQUIRED)<=tests.keys(),('incomplete test matrix',sorted(set(REQUIRED)-tests.keys()))
 assert all(tests[n]['returncode']==0 and tests[n]['rom_sha256']==r['rom_sha256'] for n in REQUIRED),'failed, stale or skipped required check'
 assert r['status']=='passed','release blocked'
if a.validate_report:
 report=json.loads(a.validate_report.read_text());validate(report);print('PASS release gate:',report['rom_sha256'],len(REQUIRED),'required checks');raise SystemExit(0)
if a.outlet_reference is None:ap.error('--outlet-reference is required for keeper deadline regression')
if a.maintenance_base is None or a.stars_state is None or a.maintenance_traces is None:ap.error('--maintenance-base and --stars-state are required')
if a.smart_baseline is None:ap.error('--smart-baseline is required for Native aim/opening comparison')
if a.centered_baseline is None:ap.error('--centered-baseline is required for the actual centered keeper ablation')
for key in ['pass_plan_mode','pass_plan_ttl','plan_baseline','keeper_goal_state','keeper_goal_seed','commit_state','commit_seed']:
 if getattr(a,key) is None:ap.error('--'+key.replace('_','-')+' is required for running checks')
for key in ['finisher_baseline','tactics_baseline','fixtures_root','seed','playoffs','four_state','trail_fixture','ai_baseline','attack_baseline','intermission_state','gap_baseline','playbook_baseline']:
 if getattr(a,key) is None:ap.error('--'+key.replace('_','-')+' is required for running checks')
root=a.fixtures_root.resolve();out=a.output.resolve();out.mkdir(parents=True,exist_ok=True);rom=a.rom.resolve();digest=sha(rom)
report=dict(rom_sha256=digest,started_at=datetime.now(timezone.utc).isoformat(),test_sources=source_hashes(),status='running',checks=[])
nav=['--seed',a.seed,'--three-playoffs',a.playoffs,'--empty-save',root/'slots-menu-freeze-20261025-014252/user-before-menu.srm','--empty-save',root/'slots-menu-freeze-20261025-014252/user-after-freeze.srm']
suites=[('critical',[]),('critical_flash',[]),('critical_trail',['--fixture',a.trail_fixture]),('season_navigation',nav),('finished_season',['--seed',a.seed]),('menu_team',['--seed',a.seed]),('menu_font',nav),('season_slots',['--seed',a.seed,'--fixtures-root',root]),('shootout',['--seed',a.seed,'--playoffs',a.playoffs]),('four_goalie',['--state',a.four_state,'--switch-on-b','--catch-handoff']),('goalie_request',['--fixtures-root',root,'--tap-frames','2','--switch-on-b']),('shootout4',['--seed',a.seed,'--playoffs',a.playoffs,'--teammates','4']),('forum_fixes',[]),('critical_velocity',[]),('shot_panel',['--state',a.four_state]),('shot_colour',['--state',a.four_state]),('four_lifecycle',['--seed',a.seed,'--natural']),('season_settings',['--seed',a.seed]),('ai_defence',['--baseline',a.ai_baseline]),('ai_defence_gameplay',['--baseline',a.ai_baseline,'--state',a.trail_fixture/'base.st']),('ai_attack',['--baseline',a.attack_baseline]),('ai_attack_gameplay',['--baseline',a.attack_baseline,'--state',a.trail_fixture/'base.st']),('ai_gap',['--baseline',a.gap_baseline]),('ai_gap_gameplay',['--baseline',a.gap_baseline,'--state',a.trail_fixture/'base.st']),('ai_playbook',[]),('ai_playbook_gameplay',['--baseline',a.playbook_baseline,'--state',a.trail_fixture/'base.st']),('crowd_modes',[]),('credits',[]),('leaders_exit',['--state',a.intermission_state]),('hud_overlay',['--state',a.trail_fixture/'base.st','--baseline',a.attack_baseline])]
suites += [('ai_cross_choice',['--allow-centered']),('ai_cross_hooks',['--allow-centered']),('ai_steering',[]),('ai_steering_frequency',[]),('ai_support_spots',['--baseline',a.tactics_baseline]),('ai_keeper_pass',['--minimum-span','64']),('ai_relay_choice',['--forward-limit','160']),('ai_attribution',[]),('ai_complete_lanes',[]),('ai_actor_layout',[]),('finisher_hold',['--baseline',a.finisher_baseline,'--state',a.trail_fixture/'base.st'])]
suites += [('ai_pass_plan',['--mode',a.pass_plan_mode,'--ttl',str(a.pass_plan_ttl),'--opening-gap','16']),('ai_pass_plan_gameplay',['--baseline',a.plan_baseline,'--state',a.trail_fixture/'base.st']),('goalie_attack',[]),('goalie_attack_reset',[]),('goalie_attack_gameplay',['--state',a.keeper_goal_state,'--seed',a.keeper_goal_seed]),('sram_commit_readiness',['--state',a.commit_state,'--seed',a.commit_seed])]
suites += [('ai_flight_coverage',['--baseline',a.tactics_baseline,'--support'])]
suites += [('centered_cross_gameplay',['--baseline',a.centered_baseline,'--state',a.trail_fixture/'base.st'])]
suites += [('ai_smart_aim',[]),('ai_smart_aim_physics',[]),('ai_smart_catch',['--gap','16']),('ai_smart_aim_gameplay',['--baseline',a.smart_baseline,'--state',a.trail_fixture/'base.st','--minimum-gap','16'])]
suites += [('crit_lock',['--baseline',a.maintenance_base,'--seed',a.seed,'--state',a.four_state]),('b_switch',[]),('stars_hud',['--baseline',a.maintenance_base,'--state',a.stars_state]),('maintenance_replay',['--reference',a.maintenance_traces,'--state',a.trail_fixture/'base.st','--keeper-reference',a.keeper_reference,'--outlet-reference',a.outlet_reference,'--menu-reference',a.menu_reference,'--control-reference',a.control_reference,'--icing-reference',a.icing_reference,'--rules-reference',a.rules_reference,'--mini-reference',a.mini_reference,'--contact-reference',a.contact_reference,'--penalty-reference',a.penalty_reference,'--seven-reference',a.seven_reference,'--manual-reference',a.manual_reference,'--net-reference',a.net_reference,'--shootout-reference',a.shootout_reference,'--receiver-reference',a.receiver_reference,'--height-reference',a.height_reference,'--speed-reference',a.speed_reference,'--pickup-reference',a.pickup_reference,'--directional-reference',a.directional_reference,'--penalty-rate-reference',a.penalty_rate_reference,'--easy-reference',a.easy_reference,'--charge-reference',a.charge_reference,'--pickup-delay-reference',a.pickup_delay_reference,'--alt-goalies-reference',a.alt_goalies_reference,'--strict-so-reference',a.strict_so_reference,'--shot-plan-reference',a.shot_plan_reference,'--faceoff-dots-reference',a.faceoff_dots_reference,'--presentation-reference',a.presentation_reference,'--quickgame-reference',a.quickgame_reference,'--allstar-reference',a.allstar_reference,'--replay-hud-reference',a.replay_hud_reference]+(['--transfers-reference',a.transfers_reference,'--refinements-reference',a.refinements_reference]if a.transfers_reference else[]))]
report_path=out/'qa_report.json'
suites += [('goalie_lateral',['--baseline',a.keeper_reference]),('goalie_lateral_gameplay',['--baseline',a.keeper_reference,'--state',a.four_state])]
suites += [('outlet_deadline',['--baseline',a.outlet_reference,'--fixtures-root',root])]
suites += [('season_identity',['--seed',a.seed]),('replay_option',['--seed',a.seed,'--state',a.four_state]),('rewind_wrap',[]),('pwrplay_clear',[]),('coach_goalie',[]),('coach_lines',[]),('goalie_season',[]),('crit_option',['--seed',a.seed,'--state',a.four_state]),('crit_menu_render',['--seed',a.seed])]
suites += [('icing_menu',['--seed',a.seed]),('icing_rule',['--baseline',a.menu_reference])]
suites += [('goalie_catch_control',[]),('goalie_catch_gameplay',['--baseline',a.control_reference,'--fixtures-root',root]),('menu_defaults',['--seed',a.seed,'--icing-default',str(a.icing_default)])]
suites += [('whistle_momentum',[]),('mini_division',['--seed',a.seed,'--baseline',a.mini_reference])]
suites += [('mini_seven',['--baseline',a.seven_reference,'--qualified',root/'mini-season-20261024-133459/mini-before8-qualified.srm'])]
suites += [('human_contact',['--seed',a.seed,'--baseline',a.contact_reference,'--contact-race-reference',a.contact_race_reference])]
suites += [('penalty_owner',['--baseline',a.penalty_reference,'--state',a.four_state])]
suites += [('manual_keeper_pass',['--baseline',a.manual_reference,'--native-states',root/'manual-keeper-pass-r13-20261030/capture-native','--normal-states',root/'goalie-outlet-20261024-173340/capture'])]
suites += [('net_contact',['--baseline',a.net_reference,'--matched-baseline',a.net_legacy_control,'--state',a.four_state,'--fixtures-root',root])]
suites += [('shootout_rules',['--baseline',a.shootout_reference,'--seed',a.seed])]
suites += [('downed_receiver',['--baseline',a.receiver_reference,'--state',a.four_state,'--seed',a.seed])]
suites += [('pass_height',['--baseline',a.height_reference,'--seed',a.seed])]
suites += [('pass_speed',['--baseline',a.speed_reference,'--seed',a.seed])]
suites += [('fair_pickup',['--baseline',a.pickup_reference,'--state',a.four_state,'--seed',a.seed])]
suites += [('pass_mode',['--baseline',a.directional_reference,'--seed',a.seed])]
suites += [('penalty_rate',['--baseline',a.penalty_rate_reference,'--seed',a.seed])]
suites += [('pickup_delay',['--baseline',a.pickup_delay_reference,'--seed',a.seed])]
suites += [('faceoff_dots',['--baseline',a.faceoff_dots_reference,'--seed',a.seed,'--state',a.four_state])]
suites += [('shot_plan',['--baseline',a.shot_plan_reference,'--seed',a.seed,'--state',a.four_state,'--flow-reference',a.replay_hud_reference])]
suites += [('strict_so',['--baseline',a.strict_so_reference,'--seed',a.seed,'--state',a.four_state])]
suites += [('alt_goalies',['--baseline',a.alt_goalies_reference,'--seed',a.seed,'--state',a.four_state])]
suites += [('pass_charge',['--baseline',a.charge_reference,'--seed',a.seed])]
suites += [('finisher_position',['--baseline',a.finisher_baseline,'--state',a.four_state]),('momentum',['--baseline',a.presentation_reference,'--seed',a.seed,'--state',a.four_state]),('shot_panel_left',['--baseline',a.presentation_reference,'--state',a.four_state])]
suites += [('easy_stats',['--baseline',a.easy_reference,'--seed',a.seed]),('pad_colors',['--baseline',a.easy_reference,'--r9-reference',a.charge_reference,'--seed',a.seed])]
suites += [('quickgame',[]),('quickgame_flow',[]),('quickgame_shootout',[])]
suites += [('transfers_'+name,(['--baseline',a.transfers_reference]if name=='runtime'else[]))for name in ['model','engine','persistence','runtime','season','journal','flow']]
suites += [('refinements',['--baseline',a.refinements_reference])]
suites += [('friendly_reception',['--baseline',a.friendly_reference]),('transfer_dollars',[]),('main_menu',[]),('downed_y',[]),('team_ovr',[])]
report_lock=RLock()
def save():
 with report_lock:
  temporary=report_path.with_suffix('.json.tmp')
  temporary.write_text(json.dumps(report,indent=2)+'\n')
  temporary.replace(report_path)
def execute(name,cmd,env=None):
 start=time.monotonic();assert sha(rom)==digest,'ROM changed during QA'
 # This nine-section suite launches thousands of independent native contracts.
 # Its host wall budget is separate from unchanged native-frame/child deadlines.
 # Retain the preceding complete-group host budgets. Native frame and child
 # deadlines/criteria are unchanged; large menu/strict groups need host time.
 wall_budget=7200 if name.startswith('transfers_') or name=='refinements' else 3600 if name in ['main_menu','strict_so'] else 2400 if name=='shot_plan' else 1200
 with (out/(name+'.log')).open('w') as log:
  try:code=subprocess.run(list(map(str,cmd)),env=env,stdout=log,stderr=log,timeout=wall_budget).returncode
  except subprocess.TimeoutExpired:code=124
 assert sha(rom)==digest,'ROM changed during QA'
 with report_lock:
  report['checks'].append(dict(test=name,returncode=code,seconds=round(time.monotonic()-start,2),wall_budget_seconds=wall_budget,rom_sha256=digest,log=name+'.log'));save();print(name,'PASS' if not code else 'FAIL',flush=True)
 if code and not a.keep_going:report['status']='failed';save();raise SystemExit(f'Release blocked: {name}; see {out/(name+".log")}')
suites += [('neon_marker',['--baseline',a.neon_reference,'--seed',a.seed])]
suites += [('menu_background',['--baseline',a.replay_hud_reference])]
suites += [('quickgame_cpu',[]),('quickgame_cpu_flow',[]),('quickgame_cpu_shootout',[])]
suites += [('allstar_cpu',[]),('allstar_cpu_flow',[]),('allstar_cpu_shootout',[])]
suites += [('replay_hud',['--baseline',a.replay_hud_reference,'--state',a.four_state])]
suites += [('allstar',['--baseline',a.allstar_reference]),('allstar_flow',[]),('allstar_shootout',[])]
def run_suite(item):
 name,flags=item
 script='shootout' if name=='shootout4' else name
 execute(name,['nice','-n','19','python3',HERE/('verify_'+script+'.py'),rom,out/name,*flags])
if a.jobs==1:
 for item in suites:run_suite(item)
else:
 with ThreadPoolExecutor(max_workers=a.jobs) as pool:list(pool.map(run_suite,suites))
p=out/'full_cpu_game';p.mkdir(exist_ok=True);b=bytearray(a.seed.read_bytes()[1:16384:2]);b[0xe20:0x1ffe]=bytes(0x11de);struct.pack_into('>4s6H',b,0x1000,b'SESN',0,0,0,84,1,3);b[0x1ef4:0x1ef6]=bytes([1,254]);b[0x1fff]=sum(b[:0x1ffe])%256;b[0x1ffe]=b[0x1fff]^255;raw=bytearray(16384);raw[1::2]=b;(p/'battery.srm').write_bytes(raw)
(p/'commands.txt').write_text('1000 press 1 Right 5\n1100 press 1 Right 5\n1200 press 1 Down 5\n1500 press 1 Start 5\n1900 press 1 Start 5\n90000 snap\n90001 exit\n')
env={k:v for k,v in os.environ.items() if not k.startswith('GPGX_')};env.update(GPGX_DEV='513',GPGX_SRM=str(p/'battery.srm'),GPGX_HOOK=str(HERE/'testcases/full_cpu_hook.py'),CASE_DIR=str(p),CASE_KIND='cpu',FINAL_FRAME='90000')
execute('full_cpu_game',['nice','-n','19','python3',HERE/'test_core.py',rom,p/'commands.txt',p],env)
assert (p/'checks.json').exists() and json.loads((p/'checks.json').read_text()),'CPU game did not finish/save'
assert all(x['difficulty']==1 for x in json.loads((p/'checks.json').read_text())),'full CPU game did not exercise Hard AI'
assert report['test_sources']==source_hashes(),'test sources changed during QA'
report['status']='failed' if any(x['returncode'] for x in report['checks']) else 'passed';report['finished_at']=datetime.now(timezone.utc).isoformat();save();validate(report);print('PASS all required release checks; exact ROM',digest)
