"""Minimal throughput gate; exercises primitives before expanding batch machinery. One cold launch, finite passive episodes, no policy changes. Every attempted reset is persisted before dispatch. A failed gate does not authorize more batch work. """ import hashlib import json import math from pathlib import Path import sys import time import traceback from . import debugger from .debug_runtime import validate from .debug_journal import durable_json from . import support def run(plan_path): plan_path=Path(plan_path); raw=plan_path.read_bytes(); plan=json.loads(raw) if plan['kind']!='minimal_debugger_throughput_pilot': raise ValueError('Wrong pilot schema') limits=plan['limits'] if limits['wall_seconds']!=300 or limits['episodes']!=20 or limits['cleanup_reserve_seconds']!=45: raise ValueError('This pilot must retain the reviewed 20-attempt / 300-second target and 45-second cleanup reserve') validate('load',plan['fixture']) validate('controls',{'payload':plan['scenario']['controls']}) validate('teleport',{'position':plan['scenario']['position']}) validate('spawn',{k:plan['scenario'][k] for k in ('template','level','count','distance')}) validate('step',{'ticks':plan['controller']['step_ticks']}) support.manage.ensure_game_closed() started=time.monotonic(); deadline=started+300; work_end=deadline-45 folder=support.ARTIFACTS/('debug-pilot-'+str(time.time_ns())); folder.mkdir(parents=True) report=dict(schema_version=1,kind='minimal_debugger_pilot',started=support.now(),plan=plan, plan_sha256=hashlib.sha256(raw).hexdigest(),episodes=[],budget_seconds=300, policy='No player inputs; explicit native controls only; no balancing modifications.', calls=[],acceptance_scope='Primitive throughput only; no clean outcome metric eligibility') client=None def save(): durable_json(folder/'report.json',report) def op(kind,args=None,wall=15): limit=deadline if kind=='close' else work_end wall=min(wall,limit-time.monotonic()) if wall<=0: raise TimeoutError('pilot wall budget reached before '+kind) t=time.monotonic(); r=client.submit(kind,args or {},wall_seconds=wall) if not r.get('ok'): raise RuntimeError(str(r)) oid=r['operation']['operation_id']; report['calls'].append(dict(kind=kind,operation_id=oid)); save() r=client.wait(oid,wall+1) report['calls'][-1]['wall_seconds']=time.monotonic()-t if r.get('operation',{}).get('status')!='completed': raise RuntimeError('operation did not complete: '+json.dumps(r)) return r['operation']['result'] mods=support.manage.files(support.manage.GAME/'mods') try: report['launch']=debugger.start('open',90); save() while not debugger.active_owner(): if time.monotonic()>min(started+95,work_end): raise TimeoutError('broker startup deadline') time.sleep(.2) client=debugger.Client(); report['session']=client.receipt['session_id']; report['journal']=client.receipt['folder'] report['startup_wall_seconds']=time.monotonic()-started for index in range(20): admission=limits['load_max_seconds']+limits['setup_max_seconds']+plan['horizon']['wall_seconds'] if time.monotonic()+admission>work_end: report['stop_reason']='insufficient_remaining_budget_for_bounded_reset_setup_and_combat'; break ep=dict(index=index,command_status='started',experimental_validity='unknown',outcome_metrics_eligible=False) report['episodes'].append(ep); save(); et=time.monotonic() t=time.monotonic(); loaded=op('load',plan['fixture'],limits['load_max_seconds']) ep['load_wall_seconds']=time.monotonic()-t; ep['generation']=loaded['snapshot']['generation'] setup_start=time.monotonic() def setup(kind,args=None): return op(kind,args,min(15,limits['setup_max_seconds']-(time.monotonic()-setup_start))) setup('controls',{'payload':plan['scenario']['controls']}) arrival=setup('teleport',{'position':plan['scenario']['position']}); ep['arrival']=arrival if not arrival.get('ok'): raise RuntimeError('teleport_arrival_not_verified') spawned=setup('spawn',{k:plan['scenario'][k] for k in ('template','level','count','distance')}) ep['initialization']=spawned if not spawned.get('ok'): raise RuntimeError('spawn_initialization_not_verified') actor=spawned['observed'][0]['actor']['id']; ep['actor_id']=actor ep['setup_wall_seconds']=time.monotonic()-setup_start before=op('inspect',{'view':'world'}); ep['before']=before ct=time.monotonic(); elapsed=0; events=[]; gaps=[]; after=before while elapsed