"""Encounter observations and bounded in-tick sampling under the existing CLI lock.""" from . import experiments as lab from . import native from .diagnostics import DiagnosticError def selection(references, generation): if not references: return 'owned' ids = [] for ref in references: native.validate('actor', ref) gen, actor = map(int, ref.split('|')) if gen != generation: raise ValueError('stale_world_generation: all actor references must match the current world') ids.append(str(actor)) value = ','.join(dict.fromkeys(ids)) native.validate('combat_state', f'{generation}|{value}') return value def combat_state(references=()): before = lab.require_paused() gen = before['generation'] return lab.data('combat_state', f'{gen}|{selection(references, gen)}') def discover(radius=100, tag='', limit=16, generation=None, revision=None, start=0): scan = None if revision is None: scan = lab.data('encounter_scan', f'{radius}|{tag}')['data'] generation, revision = scan['generation'], scan['revision'] page = lab.data('encounter_page', f'{generation}|{revision}|{start}|{limit}') page['discovery'] = scan return page def analyze_samples(metadata, samples, end_tick): start, every = metadata['start_tick'], metadata['every_ticks'] dead_ticks = [s['tick'] for s in samples if s and s.get('actors') and s['actors'][0]['actor'].get('alive') is False] terminal_tick = min(dead_ticks) if metadata['termination']=='player_death' and dead_ticks else None coverage_end = min(end_tick, start + metadata['budget_ticks'], terminal_tick if terminal_tick is not None else end_tick) expected = list(range(start, coverage_end + 1, every)) ticks = [s['tick'] for s in samples if s is not None] missing = sorted(set(expected) - set(ticks)) return dict(samples=samples, actual_ticks=ticks, actual_spacing=[b-a for a,b in zip(ticks,ticks[1:])], missing_ticks=missing, missing_pages=[i for i,s in enumerate(samples) if s is None], complete=not missing and all(s is not None for s in samples), termination=metadata['termination'], terminal_tick=terminal_tick, coverage_end_tick=coverage_end, unsampled_after_termination_ticks=max(0,end_tick-terminal_tick) if terminal_tick is not None else 0, metadata=metadata) def sampled_action(command, payload, every=2, references=()): """One input delivery; capture inside OnTick, then collect while paused. No compiler.""" native.validate(command, payload) budget = int(payload.split('|')[-1]) before = lab.require_paused() gen = before['generation'] selected = selection(references, gen) arm_payload = f'{gen}|{selected}|{every}|{budget}' native.validate('sample_arm', arm_payload) armed = lab.data('sample_arm', arm_payload)['data'] reference = f"{gen}|{armed['revision']}" failure = None try: result = lab.bounded_action(command, payload) except (OSError, RuntimeError, ValueError) as error: failure = error try: # Stopping observer bookkeeping is serialized; native uncertainty may block it. stopped = lab.data('sample_stop', reference)['data'] except (OSError, RuntimeError, ValueError) as error: raise DiagnosticError('E3003', 'Sampler stop could not be confirmed; no input was replayed.', dict(arm=armed, reference=reference, action_error=str(failure) if failure else None, stop_error=str(error))) from error if failure: raise DiagnosticError('E3003', 'Bounded action failed; observer stopped without replaying input. Recover stored samples using sample_page.', dict(arm=armed, reference=reference, action_error=str(failure), sampler_stop=stopped)) from failure samples = [lab.data('sample_page', f'{reference}|{i}')['data']['sample'] for i in range(stopped['count'])] end_tick = result.get('after', before)['data']['ticks'] result['sampling'] = analyze_samples(stopped, samples, end_tick) return result