package com.android.commands.monkey.ape.jev;

import java.util.*;
import java.util.concurrent.*;
import com.android.commands.monkey.ape.model.*;
import com.android.commands.monkey.ape.naming.NameManager;
import com.android.commands.monkey.ape.tree.*;
import com.android.commands.monkey.ape.utils.Config;

/** Main-thread derived evidence index; the worker receives only immutable value snapshots. */
public final class JevRegion implements AutoCloseable {
    public static void validateConfiguration(boolean decision) {
        if(Config.getBoolean("ape.jev.region_locator_shadow",false)) {
            if(Config.getBoolean("ape.jev.probe.enabled",true) || Config.getBoolean("ape.jev.region_auto",false)
                    || Config.isJevDecisionEnabled())
                throw new IllegalArgumentException("region_locator_shadow requires decisions and Probe off");
            JevRegionLocator.validateConfiguration();
        }
        if(Config.getBoolean("ape.jev.region_auto",false) && (!decision || !Config.getBoolean("ape.jev.regionBlocking.enabled",false)
                || Config.getBoolean("ape.jev.region_shadow_audit",false) || Config.getBoolean("ape.jev.probe.enabled",true)))
            throw new IllegalArgumentException("region_auto requires Region decision mode, Probe off and shadow audit off");
        if(Config.getBoolean("ape.jev.region_auto",false))autoSkipProbability();
        if(decision && Config.getBoolean("ape.jev.regionBlocking.enabled",false) && !Config.getBoolean("ape.jev.region_auto",false))JevRegionLocator.validateConfiguration();
        if (!decision || !Config.getBoolean("ape.jev.regionBlocking.enabled",false)) return;
        if (Long.parseLong(Config.get("ape.jev.regionBlocking.updateIntervalMs","30000"))<=0)
            throw new IllegalArgumentException("Region updateIntervalMs must be positive");
        if (Long.parseLong(Config.get("ape.jev.regionBlocking.warmupMs","300000"))<0)
            throw new IllegalArgumentException("Region warmupMs must be nonnegative");
    }
    private static double autoSkipProbability() {
        double p=Double.parseDouble(Config.get("ape.jev.region_auto.skipProbability","0.5"));
        if(p!=0.5 && p!=1.0)throw new IllegalArgumentException("Region Auto skipProbability must be 0.5 or 1.0");
        return p;
    }
    static final class Fact {
        final boolean known,saturated,settled;
        Fact(boolean k,boolean s,boolean f){known=k;saturated=s;settled=f;}
        boolean complete(){return known && saturated && settled;}
        public boolean equals(Object o){if(!(o instanceof Fact))return false;Fact f=(Fact)o;return known==f.known && saturated==f.saturated && settled==f.settled;}
        public int hashCode(){return (known?4:0)+(saturated?2:0)+(settled?1:0);}
    }
    static final class Link {
        final int source,target,concrete;
        Link(int s,int t,int c){source=s;target=t;concrete=c;}
    }
    static final class Concrete {
        final int id,source;final Set<Integer> targets;final boolean eligible;
        Concrete(int i,int s,Set<Integer> t,boolean e){id=i;source=s;targets=Collections.unmodifiableSet(new HashSet<Integer>(t));eligible=e;}
    }
    static final class Snapshot {
        final long abstraction,topology,concretes,facts;final int current;final String tree;
        final List<Fact> states;final List<Link> links;final Set<Integer> roots;final List<Concrete> entries;
        Snapshot(long a,long t,long c,long f,int cur,String tr,List<Fact> s,List<Link> l,Set<Integer> r,List<Concrete> e){
            abstraction=a;topology=t;concretes=c;facts=f;current=cur;tree=tr;
            states=Collections.unmodifiableList(new ArrayList<Fact>(s));links=Collections.unmodifiableList(new ArrayList<Link>(l));
            roots=Collections.unmodifiableSet(new HashSet<Integer>(r));entries=Collections.unmodifiableList(new ArrayList<Concrete>(e));
        }
    }
    static final class Result {
        final Snapshot input;final Set<Integer> blocked,isolated,members;final List<Set<Integer>> regions;final String reason;final long nanos,dominatorNanos,summaryNanos,reachabilityNanos;
        Result(Snapshot s,Set<Integer>b,Set<Integer>i,Set<Integer>m,List<Set<Integer>> regions,String r,long n,long d,long a,long reach){
            input=s;blocked=Collections.unmodifiableSet(b);isolated=Collections.unmodifiableSet(i);members=Collections.unmodifiableSet(m);
            List<Set<Integer>> copy=new ArrayList<Set<Integer>>();for(Set<Integer> region:regions)copy.add(Collections.unmodifiableSet(new HashSet<Integer>(region)));
            this.regions=Collections.unmodifiableList(copy);reason=r;nanos=n;dominatorNanos=d;summaryNanos=a;reachabilityNanos=reach;
        }
    }
    /** Tree reuse depends only on confirmed State connections and real roots, never strength. */
    static final class Calculator {
        long abstraction=-1,topology=-1;BitSet[] dominators;BitSet reachable;
        int[] parents;List<List<Integer>> children;List<Integer> order;
        void prepare(Snapshot s) {
            int n=s.states.size();
            if(abstraction!=s.abstraction || topology!=s.topology) {
                reachable=reach(s,s.roots,Collections.<Integer>emptySet());dominators=new BitSet[n];
                for(int i=0;i<n;i++){dominators[i]=new BitSet(n);if(reachable.get(i)){dominators[i].or(reachable);if(s.roots.contains(i)){dominators[i].clear();dominators[i].set(i);}}}
                List<List<Integer>> predecessors=new ArrayList<List<Integer>>();for(int i=0;i<n;i++)predecessors.add(new ArrayList<Integer>());
                for(Link l:s.links)if(reachable.get(l.source))predecessors.get(l.target).add(l.source);
                boolean changed;
                do {changed=false;for(int v=0;v<n;v++)if(reachable.get(v) && !s.roots.contains(v)) {
                    checkInterrupted();BitSet d=(BitSet)reachable.clone();
                    for(int p:predecessors.get(v))d.and(dominators[p]);
                    d.set(v);if(!d.equals(dominators[v])){dominators[v]=d;changed=true;}
                }}while(changed);
                parents=new int[n];Arrays.fill(parents,-1);children=new ArrayList<List<Integer>>();order=new ArrayList<Integer>();
                for(int i=0;i<n;i++)children.add(new ArrayList<Integer>());
                for(int v=0;v<n;v++)if(reachable.get(v)) {
                    checkInterrupted();int depth=-1;
                    for(int d=dominators[v].nextSetBit(0);d>=0;d=dominators[v].nextSetBit(d+1))if(d!=v && dominators[d].cardinality()>depth){parents[v]=d;depth=dominators[d].cardinality();}
                    if(parents[v]>=0)children.get(parents[v]).add(v);
                }
                for(int v=0;v<n;v++)if(reachable.get(v) && parents[v]<0)order.add(v);
                for(int i=0;i<order.size();i++)order.addAll(children.get(order.get(i)));
                abstraction=s.abstraction;topology=s.topology;
            }
        }
        /** All non-entry dominator subtrees, independent of exploration facts. */
        List<JevRegionLocator.Region> structure(Snapshot s) {
            prepare(s);List<JevRegionLocator.Region> regions=new ArrayList<JevRegionLocator.Region>();
            for(int d:order)if(!s.roots.contains(d)) {
                Set<Integer> members=new TreeSet<Integer>();ArrayDeque<Integer> queue=new ArrayDeque<Integer>();queue.add(d);
                while(!queue.isEmpty()){checkInterrupted();int v=queue.remove();members.add(v);queue.addAll(children.get(v));}
                regions.add(new JevRegionLocator.Region(d,members));
            }
            return regions;
        }
        Result compute(Snapshot s) {
            long start=System.nanoTime();prepare(s);int n=s.states.size();
            long dominatorNanos=System.nanoTime()-start,summaryStart=System.nanoTime();
            boolean[] complete=new boolean[n];
            for(int i=order.size()-1;i>=0;i--){checkInterrupted();int v=order.get(i);complete[v]=s.states.get(v).complete();for(int child:children.get(v))complete[v] &= complete[child];}
            Set<Integer> union=new HashSet<Integer>();List<Set<Integer>> regions=new ArrayList<Set<Integer>>();
            for(int d:order)if(!s.roots.contains(d) && complete[d] && !union.contains(d)) {
                Set<Integer> members=new HashSet<Integer>();ArrayDeque<Integer> queue=new ArrayDeque<Integer>();queue.add(d);
                while(!queue.isEmpty()){int v=queue.remove();members.add(v);queue.addAll(children.get(v));}
                regions.add(members);union.addAll(members);
            }
            return validate(s,regions,null,start,dominatorNanos,System.nanoTime()-summaryStart);
        }
    }
    /** Shared entry/exit checks for worker proposals and current-fact revalidation; no new dominator search. */
    private static Result validate(Snapshot s,List<Set<Integer>> proposed,Set<Integer> permitted,long start,long dominatorNanos,long summaryNanos) {
        List<Set<Integer>> regions=new ArrayList<Set<Integer>>();Set<Integer> union=new HashSet<Integer>();
        for(Set<Integer> region:proposed) {
            boolean complete=true;for(int v:region)complete &= v>=0 && v<s.states.size() && !s.roots.contains(v) && s.states.get(v).complete();
            if(complete){regions.add(region);union.addAll(region);}
        }
        Set<Integer> blocked=new HashSet<Integer>();
        for(Concrete c:s.entries)if((permitted==null || permitted.contains(c.id)) && c.eligible && !union.contains(c.source)
                && !c.targets.isEmpty() && union.containsAll(c.targets))blocked.add(c.id);
        long reachStart=System.nanoTime();Guard guard=guard(s,union,blocked);blocked=new HashSet<Integer>(guard.allowed);
        Set<Integer> isolated=new HashSet<Integer>();
        for(Set<Integer> region:regions){boolean closed=true;
            for(Link l:s.links)if(!region.contains(l.source) && region.contains(l.target) && !blocked.contains(l.concrete)){closed=false;break;}
            if(closed)isolated.addAll(region);
        }
        return new Result(s,blocked,isolated,union,regions,blocked.isEmpty() && !guard.rejected.isEmpty()?"reachability_rejected":"computed",System.nanoTime()-start,dominatorNanos,summaryNanos,System.nanoTime()-reachStart);
    }
    static final class Guard {
        final Set<Integer> allowed;final Map<Integer,String> rejected=new TreeMap<Integer,String>();
        final Map<Integer,Set<Integer>> obligations=new TreeMap<Integer,Set<Integer>>();
        Guard(Set<Integer> proposed){allowed=new HashSet<Integer>(proposed);}
    }
    /** Existing collective reachability protection; saturation is a protected-work fact, not a locator gate. */
    static Guard guard(Snapshot s,Set<Integer> union,Set<Integer> proposed) {
        Guard guard=new Guard(proposed);Set<Integer> blocked=guard.allowed;
        for(Set<Integer> origins:Arrays.asList(s.roots,Collections.singleton(s.current))) {
            BitSet before=reach(s,origins,Collections.<Integer>emptySet()),after=reach(s,origins,blocked),lost=new BitSet();
            for(int v=0;v<s.states.size();v++)if(before.get(v) && !after.get(v) &&
                    (!s.states.get(v).complete() || (union.contains(s.current) && !union.contains(v))))lost.set(v);
            if(!lost.isEmpty()) {
                // Reuse the same reachability walk to release only cuts leading to lost obligations.
                // Unblocking cannot reduce reachability; unrelated surviving entries remain installed.
                for(Concrete c:s.entries)if(blocked.contains(c.id)
                        && reach(s,c.targets,Collections.<Integer>emptySet()).intersects(lost)) {
                    blocked.remove(c.id);boolean open=false;
                    BitSet affected=reach(s,c.targets,Collections.<Integer>emptySet());affected.and(lost);
                    for(int v=affected.nextSetBit(0);v>=0;v=affected.nextSetBit(v+1))open |= !s.states.get(v).complete();
                    Set<Integer> protectedStates=new TreeSet<Integer>();for(int v=affected.nextSetBit(0);v>=0;v=affected.nextSetBit(v+1))protectedStates.add(v);
                    guard.obligations.put(c.id,protectedStates);
                    guard.rejected.put(c.id,(origins==s.roots?"roots:":"current:")+(open?"protected_open_work":"protected_exit"));
                }
                after=reach(s,origins,blocked);lost.andNot(after);
                if(!lost.isEmpty()){for(int id:blocked)guard.rejected.put(id,"unresolved_protected_reachability");blocked.clear();return guard;}
            }
        }
        return guard;
    }
    private static void checkInterrupted(){if(Thread.currentThread().isInterrupted())throw new CancellationException("Region worker interrupted");}
    private static BitSet reach(Snapshot s,Set<Integer> starts,Set<Integer> blocked) {
        BitSet seen=new BitSet(s.states.size());ArrayDeque<Integer> queue=new ArrayDeque<Integer>();
        for(int v:starts)if(v>=0 && !seen.get(v)){seen.set(v);queue.add(v);}
        List<List<Link>> out=new ArrayList<List<Link>>();for(int i=0;i<s.states.size();i++)out.add(new ArrayList<Link>());
        for(Link l:s.links)out.get(l.source).add(l);
        while(!queue.isEmpty()){checkInterrupted();int v=queue.remove();for(Link l:out.get(v))if(!blocked.contains(l.concrete) && !seen.get(l.target)){seen.set(l.target);queue.add(l.target);}}
        return seen;
    }
    static final class Bucket {
        final int id;final ModelAction action;final GUITreeAction anchor;
        State source;long abstraction;GUITreeAction historicalAnchor,ruleAnchor;
        final Set<Integer> targets=new HashSet<Integer>();
        final Map<String,org.json.JSONObject> outcomes=new TreeMap<String,org.json.JSONObject>();
        final Map<String,String> confirmedPatterns=new HashMap<String,String>();
        final Set<GUITree> confirmedTargets=Collections.newSetFromMap(new IdentityHashMap<GUITree,Boolean>());
        boolean ambiguous;long executions;
        Bucket(int id,ModelAction action,GUITreeAction anchor){this.id=id;this.action=action;this.anchor=anchor;this.source=anchor.getGUITree().getCurrentState();this.historicalAnchor=anchor;this.ruleAnchor=anchor;}
    }
    private static final class SnapshotInvalid extends RuntimeException {
        final org.json.JSONObject detail;
        SnapshotInvalid(String reason,org.json.JSONObject detail){super(reason);this.detail=detail;try{detail.put("reason",reason);}catch(org.json.JSONException e){throw new IllegalStateException(e);}}
    }
    private SnapshotInvalid shadowInvalid;private Model shadowModel;private boolean shadowHistoryIncomplete;
    private static String objectTag(Object object){return object==null?"null":object.getClass().getSimpleName()+"@"+Integer.toHexString(System.identityHashCode(object));}
    private static String stateTag(State state){return state==null?"null":state.getGraphId()+"/"+objectTag(state);}
    private org.json.JSONObject shadowDiagnostic(Bucket b,GUITreeTransition transition){
        GUITreeAction historical=transition!=null?transition.getAction():b==null?null:b.historicalAnchor;
        ModelAction raw=historical==null?null:historical.getModelAction();
        State source=transition!=null?transition.getSource().getCurrentState():b==null?null:b.source;
        org.json.JSONArray actions=new org.json.JSONArray();
        try{if(source!=null)for(ModelAction candidate:source.getActions())actions.put(JevRegistry.fields("id",candidate.getGraphId(),"object",objectTag(candidate),
            "state_object",stateTag(candidate.getState()),"type",candidate.getType().name(),"target",candidate.requireTarget()?candidate.getTarget().toString():org.json.JSONObject.NULL));}
        catch(RuntimeException unavailable){actions.put(JevRegistry.fields("inventory_error",unavailable.getClass().getName()));}
        return JevRegistry.fields("abstraction_version",abstraction,"model_version",shadowModel==null?org.json.JSONObject.NULL:shadowModel.getVersion(),
            "topology_version",topology,"concrete_version",concretes,"fact_version",facts,"bucket_id",b==null?org.json.JSONObject.NULL:b.id,
            "bucket_version",b==null?org.json.JSONObject.NULL:b.abstraction,"source_object",stateTag(source),
            "action_object",b==null?objectTag(raw):objectTag(b.action),"action_state_object",stateTag(b==null?raw==null?null:raw.getState():b.action.getState()),
            "action_id",b==null?raw==null?org.json.JSONObject.NULL:raw.getGraphId():b.action.getGraphId(),"current_source_actions",actions,
            "anchor_source_object",stateTag(historical==null?null:historical.getGUITree().getCurrentState()),
            "node_xpath",historical==null || historical.getGUITreeNode()==null?org.json.JSONObject.NULL:String.valueOf(historical.getGUITreeNode().getXPathName()),
            "historical_action_object",objectTag(raw),"historical_action_id",raw==null?org.json.JSONObject.NULL:raw.getGraphId(),
            "historical_action_state",stateTag(raw==null?null:raw.getState()),"historical_anchor_object",objectTag(historical),
            "source_gui_id",transition!=null?transition.getSource().getId():b==null?org.json.JSONObject.NULL:b.anchor.getGUITree().getId(),
            "source_index",source==null?org.json.JSONObject.NULL:ids.containsKey(source)?ids.get(source):org.json.JSONObject.NULL,
            "target_gui_id",transition==null?org.json.JSONObject.NULL:transition.getTarget().getId(),
            "target_state_object",stateTag(transition==null?null:transition.getTarget().getCurrentState()),"indexed_states",states.size());
    }
    /** Read native current naming only; never calls Model.update or GUITreeAction.rebuild. */
    private ModelAction shadowAction(GUITreeTransition transition){
        GUITree tree=transition.getSource();State source=tree.getCurrentState();GUITreeAction historical=transition.getAction();
        GUITreeNode node=historical.getGUITreeNode();ModelAction found=null;
        if(source==null || historical.getGUITree()!=tree)throw new SnapshotInvalid("historical_anchor_source_mismatch",shadowDiagnostic(null,transition));
        found=shadowAction(tree,node,historical.getActionType());
        if(found==null)throw new SnapshotInvalid("current_action_unresolved",shadowDiagnostic(null,transition));
        return found;
    }
    static ModelAction shadowAction(GUITree tree,GUITreeNode node,ActionType type){
        try{return shadowActionNow(tree,node,type);}catch(RuntimeException unavailable){return null;}
    }
    private static ModelAction shadowActionNow(GUITree tree,GUITreeNode node,ActionType type){
        State source=tree.getCurrentState();if(source==null)return null;ModelAction found=null;
        for(ModelAction candidate:source.getActions())if(candidate.getType()==type){
            boolean matches=node==null?!candidate.requireTarget():candidate.requireTarget() && candidate.getTarget().equals(node.getXPathName()) && tree.getNodes(candidate.getTarget()).contains(node);
            if(matches){if(found!=null)return null;found=candidate;}
        }
        return found!=null && found.getState()==source?found:null;
    }
    private static final class Rule {
        final GUITreeAction anchor;final String identity,state,action,fullName;final int concreteId;
        final JevRegionProtocol.Decision decision;final String source;final double pSkip;final long generation,abstraction,executions;
        long firstInstallStep=-1,firstGateStep=-1,firstSkipStep=-1,gates,skips,confirmedExecutions;
        Rule(Bucket bucket,String identity,JevRegionProtocol.Decision decision,long generation,long abstraction,String source){
            anchor=bucket.ruleAnchor;this.identity=identity;this.decision=decision;this.generation=generation;this.abstraction=abstraction;
            this.source=source;pSkip=decision==null?0.3:decision.pSkip;
            concreteId=bucket.id;state=bucket.action.getState().getGraphId();action=bucket.action.getGraphId();
            fullName=observedFullName(anchor.getGUITreeNode());executions=bucket.executions;
        }
    }
    private static final class Review {
        final JevRegionProtocol.Request request;final JevTransport.Call call;final List<Bucket> buckets;final GUITree tree;final long abstraction,generation;final String identity;
        final List<ReviewEvidence> evidence=new ArrayList<ReviewEvidence>();
        Review(JevRegionProtocol.Request request,JevTransport.Call call,List<Bucket> buckets,GUITree tree,long version,long generation){
            this.request=request;this.call=call;this.buckets=buckets;this.tree=tree;abstraction=version;this.generation=generation;identity=JevEvidence.guiIdentity(tree);
            for(Bucket bucket:buckets)evidence.add(new ReviewEvidence(bucket));
        }
    }
    /** Confirmed evidence frozen at submission, not at asynchronous receipt time. */
    private static final class ReviewEvidence {
        final long executions;
        final Set<String> patterns;
        final Set<String> transitions;
        final List<GUITree> targets;
        ReviewEvidence(Bucket bucket){executions=bucket.executions;patterns=new TreeSet<String>(bucket.outcomes.keySet());transitions=new HashSet<String>(bucket.confirmedPatterns.keySet());targets=new ArrayList<GUITree>(bucket.confirmedTargets);}
        String change(Bucket bucket) {
            // Rebuilding the same confirmed transitions after State refinement is not new execution evidence.
            for(Map.Entry<String,String> pattern:bucket.confirmedPatterns.entrySet())
                if(!transitions.contains(pattern.getKey()) && !patterns.contains(pattern.getValue()))return "new_confirmed_outcome";
            for(GUITree target:bucket.confirmedTargets) {
                boolean found=false;for(GUITree old:targets)if(old.getCurrentState()==target.getCurrentState()){found=true;break;}
                if(!found)return "new_confirmed_target";
            }
            return executions>0 && bucket.executions>=executions && bucket.executions-executions>=executions?"execution_count_doubled":null;
        }
        org.json.JSONObject json(){Set<String> names=new TreeSet<String>();for(GUITree target:targets)names.add(target.getCurrentState().getGraphId());
            return JevRegistry.fields("execution_count",executions,"confirmed_target_states",new org.json.JSONArray(names),"confirmed_transition_ids",new org.json.JSONArray(new TreeSet<String>(transitions)),"outcome_patterns",new org.json.JSONArray(patterns));}
    }
    private static final class Keep {
        final Rule anchor;final String id;final long firstStep;
        ReviewEvidence evidence;JevRegionProtocol.Decision decision;String requestId,candidateId;
        long sourceMisses,sourceMatches,actionMatches,exactMatches,selected,executed;
        Keep(Rule anchor,String id,long step){this.anchor=anchor;this.id=id;firstStep=step;}
    }
    private static final class Shadow {
        final Keep keep;final GUITree tree;final ModelAction action;final GUITreeNode node;
        final long step,version;final String id;GUITreeAction selected;
        Shadow(Keep keep,GUITree tree,ModelAction action,GUITreeNode node,long step,long version,String id){
            this.keep=keep;this.tree=tree;this.action=action;this.node=node;this.step=step;this.version=version;this.id=id;
        }
    }
    private static final class AuditCandidate {
        final Bucket bucket;final String id;final GUITreeAction anchor;final long firstStep,firstTimeMs;
        final org.json.JSONObject firstStructure;final int firstResolvedNodeCount;
        long repeatNominations,futureExactAvailability,modelActionSelected,wouldGate,confirmedExecuted;
        long firstWouldGateDistance=-1,wouldGateSequence;
        final List<GUITreeAction> pendingExecutions=new ArrayList<GUITreeAction>();
        AuditCandidate(Bucket b,String i,long step,long time,org.json.JSONObject structure,int nodes){
            bucket=b;id=i;anchor=b.anchor;firstStep=step;firstTimeMs=time;firstStructure=structure;firstResolvedNodeCount=nodes;
        }
    }
    private static final class AuditPending {
        final AuditCandidate candidate;final GUITreeAction action;final long step;final String gateContextId;
        AuditPending(AuditCandidate c,GUITreeAction a,long s,String contextId){candidate=c;action=a;step=s;gateContextId=contextId;}
    }
    private static final class Attempt {
        final Rule rule;final GUITreeAction action;final String strategy,id,sourceState;final long step,selectionVersion;
        Attempt(Rule rule,GUITreeAction action,String strategy,long step,String id,long version){
            this.rule=rule;this.action=action;this.strategy=strategy;this.step=step;this.id=id;
            sourceState=action.getGUITree().getCurrentState().getGraphId();selectionVersion=version;
        }
    }
    private final JevRuntime runtime;private final JevTiming timing;private final JevEvents events;private final JevTransport transport;
    private final int maxBytes,timeoutMs;private final String providerModel;private final Random random;
    private final long interval,warmup;private long next=Long.MAX_VALUE,nextReview,abstraction=-1,topology,concretes,facts,generation,requestSequence;private int nextConcreteId;
    private final Map<State,Integer> ids=new IdentityHashMap<State,Integer>();
    private final List<State> states=new ArrayList<State>();private final List<Fact> stateFacts=new ArrayList<Fact>();
    private final Set<Integer> roots=new HashSet<Integer>();private final Set<String> implementations=new HashSet<String>(),transitionIds=new HashSet<String>();
    private final List<Link> links=new ArrayList<Link>();private final Map<Integer,Bucket> buckets=new LinkedHashMap<Integer,Bucket>();
    private final Map<ModelAction,Map<String,Bucket>> byAction=new IdentityHashMap<ModelAction,Map<String,Bucket>>();
    private final Set<State> touched=Collections.newSetFromMap(new IdentityHashMap<State,Boolean>());
    private final Map<Integer,Rule> rules=new HashMap<Integer,Rule>();private final Map<Integer,Boolean> sampled=new HashMap<Integer,Boolean>();
    private final Map<Integer,org.json.JSONObject> nominationStructures=new HashMap<Integer,org.json.JSONObject>();
    private final Set<String> eligibleCandidates=new HashSet<String>();
    private long eligibleOccurrences,duplicateInstallsAvoided;
    private final boolean auto=Config.getBoolean("ape.jev.region_auto",false);
    private final double autoProbability=auto?autoSkipProbability():0.5;
    private final boolean membershipAudit=Config.getBoolean("ape.jev.region_membership_audit",false);
    private long membershipVersion=-1;
    private final Map<Set<Integer>,Integer> membershipComponents=new HashMap<Set<Integer>,Integer>();
    private final List<Keep> keeps=new ArrayList<Keep>();
    private final Map<String,AuditCandidate> auditCandidates=new LinkedHashMap<String,AuditCandidate>();
    private final List<AuditPending> auditPending=new ArrayList<AuditPending>();
    private final List<Shadow> shadowRound=new ArrayList<Shadow>(),shadowInputs=new ArrayList<Shadow>();
    private final boolean shadowEnabled=Config.getBoolean("ape.jev.regionBlocking.keepShadow",true);
    private long keepSequence,shadowSequence,modelInvocations,keepCacheHits,keepRereviews,nativeActions;
    private int utgStateCount;
    // Supplied by Monkey's actual timed-run loop. Zero means no known time budget.
    private static volatile long runDeadlineElapsedMs;
    private static volatile long runStartNanos=Long.MIN_VALUE;
    public static void observeRunStart(long monotonicNanos,long deadlineElapsedMs){runStartNanos=monotonicNanos;runDeadlineElapsedMs=deadlineElapsedMs;}
    static long runStartNanos(){return runStartNanos;}
    private final boolean shadowAudit;
    private final ThreadPoolExecutor worker;private final Calculator calculator=new Calculator();
    private final JevRegionLocator locator;
    private Future<Result> pending;private Review review;private boolean dirty=true,closed,warmupStarted;private Result active;
    static final class Nomination {
        final byte[] frozen;
        final Map<Integer,Bucket> buckets=new HashMap<Integer,Bucket>();
        static final class Input {
            final org.json.JSONObject snapshot;final JevRegionLocator.Evidence evidence=new JevRegionLocator.Evidence();
            Input(org.json.JSONObject record,Map<Integer,Bucket> buckets)throws org.json.JSONException {
                snapshot=record.getJSONObject("snapshot");evidence.buckets.putAll(buckets);
                org.json.JSONObject candidates=record.getJSONObject("candidates");
                for(int id:buckets.keySet())evidence.candidates.put(id,candidates.getJSONObject(Integer.toString(id)));
            }
        }
        Nomination(org.json.JSONObject snapshot,JevRegionLocator.Evidence source)throws org.json.JSONException,java.io.IOException {
            org.json.JSONObject candidates=new org.json.JSONObject();
            for(int id:new TreeSet<Integer>(source.candidates.keySet())){
                candidates.put(Integer.toString(id),source.candidates.get(id));buckets.put(id,source.buckets.get(id));
            }
            // Internal lossless storage only. No compressed format reaches the
            // model or receipt protocol; expand just the existing FIFO head.
            frozen=pack(JevRegistry.fields("snapshot",JevRegionEvidence.pendingSnapshot(snapshot,source.candidates.keySet()),"candidates",candidates));
        }
        static byte[] pack(org.json.JSONObject record)throws java.io.IOException {
            java.io.ByteArrayOutputStream bytes=new java.io.ByteArrayOutputStream();
            try(java.io.Writer out=new java.io.OutputStreamWriter(new java.util.zip.GZIPOutputStream(bytes),java.nio.charset.StandardCharsets.UTF_8)){out.write(record.toString());}
            return bytes.toByteArray();
        }
        Input read()throws java.io.IOException,org.json.JSONException {
            java.io.ByteArrayOutputStream bytes=new java.io.ByteArrayOutputStream();
            try(java.io.InputStream in=new java.util.zip.GZIPInputStream(new java.io.ByteArrayInputStream(frozen))){byte[] buffer=new byte[8192];int n;while((n=in.read(buffer))!=-1)bytes.write(buffer,0,n);}
            return new Input(new org.json.JSONObject(new String(bytes.toByteArray(),java.nio.charset.StandardCharsets.UTF_8)),buckets);
        }
    }
    // Complete frozen inputs waiting for the existing single in-flight review / packet drain.
    private final ArrayDeque<Nomination> locatorNominations=new ArrayDeque<Nomination>();
    long modelInvocationCount(){return modelInvocations;}
    int installedRuleCount(){return rules.size();}
    private State current;private GUITree currentTree;private long step,sampleStep=-1,attemptSequence;private Attempt attempt;
    private final List<Attempt> skippedForFallback=new ArrayList<Attempt>();

    JevRegion(JevRuntime runtime,JevTiming timing,JevEvents events,JevTransport transport,int maxBytes,String model,int timeoutMs,long seed) {
        this(runtime,timing,events,transport,maxBytes,model,timeoutMs,seed,false);
    }
    JevRegion(JevRuntime runtime,JevTiming timing,JevEvents events,JevTransport transport,int maxBytes,String model,int timeoutMs,long seed,boolean shadowAudit) {
        this.runtime=runtime;this.timing=timing;this.events=events;this.transport=transport;this.maxBytes=maxBytes;providerModel=model;this.timeoutMs=timeoutMs;this.shadowAudit=shadowAudit;random=new Random(seed);
        interval=TimeUnit.MILLISECONDS.toNanos(Config.getLong("ape.jev.regionBlocking.updateIntervalMs",30000));
        warmup=Config.getBoolean("ape.jev.region_locator_shadow",false) || !auto && !shadowAudit?TimeUnit.MINUTES.toNanos(JevRegionLocator.minutes()):TimeUnit.MILLISECONDS.toNanos(Config.getLong("ape.jev.regionBlocking.warmupMs",300000));
        events.emit("REGION_CONFIG",0,0,null,null,JevRegistry.fields("region_enabled",!shadowAudit,"region_shadow_audit",shadowAudit,"update_interval_ms",TimeUnit.NANOSECONDS.toMillis(interval),
            "warmup_ms",TimeUnit.NANOSECONDS.toMillis(warmup),"region_receipt_schema",7,"region_seed",seed,"keep_shadow_enabled",shadowEnabled,
            "region_auto",auto,"auto_skip_probability",auto?autoProbability:org.json.JSONObject.NULL,"region_context_audit",runtime.regionContextAuditEnabled(),
            "region_membership_audit",membershipAudit));
        worker=new ThreadPoolExecutor(1,1,0,TimeUnit.MILLISECONDS,new ArrayBlockingQueue<Runnable>(1),new ThreadFactory(){public Thread newThread(Runnable task){Thread thread=new Thread(task,"ape-jev-region");thread.setDaemon(true);return thread;}});
        locator=(Config.getBoolean("ape.jev.region_locator_shadow",false) || !auto && !shadowAudit)?new JevRegionLocator(runtime,timing,events,worker,calculator):null;
    }
    private void event(String name,String reason){events.emit(name,step,abstraction,current==null?null:current.getGraphId(),null,
        JevRegistry.fields("reason",reason,"generation",generation,"topology_version",topology,"concrete_version",concretes,"fact_version",facts,
            "blocked",rules.size(),"isolated_states",active==null?0:active.isolated.size()));}
    private void ruleEvent(String name,Rule rule,org.json.JSONObject fields) {
        events.emit(name,step,rule.abstraction,rule.state,rule.action,JevRegistry.merge(JevRegistry.fields(
            "concrete_id",rule.concreteId,"generation",rule.generation,"rule_source",rule.source,"action_type",rule.anchor.getActionType().name(),
            "full_name",rule.fullName,"confirmed_executions",rule.executions),fields));
    }
    private Rule matchingRule(ModelAction action,GUITree tree,GUITreeNode node) {
        long started=System.nanoTime();
        try{return matchingRuleNow(action,tree,node);}
        finally{timing.record("region_matcher",System.nanoTime()-started);}
    }
    private Rule matchingRuleNow(ModelAction action,GUITree tree,GUITreeNode node) {
        if(node==null)return null;
        // Native refinement updates the anchor's State/action; installation IDs and
        // whole-page identity are provenance only, shared by dedup and the final gate.
        for(Rule rule:rules.values())if(JevRegionMatcher.match(rule.anchor,tree,action).exact(node))return rule;
        return null;
    }
    private Keep matchingKeep(Bucket bucket) {
        for(Keep keep:keeps)if(JevRegionMatcher.match(keep.anchor.anchor,bucket.anchor.getGUITree(),bucket.anchor.getModelAction()).exact(bucket.anchor.getGUITreeNode()))return keep;
        return null;
    }
    private org.json.JSONObject keepFields(Keep keep) {GUITreeAction anchor=keep.anchor.anchor;
        return JevRegistry.fields("keep_id",keep.id,"review_status",matchingRule(anchor.getModelAction(),anchor.getGUITree(),anchor.getGUITreeNode())==null?"KEEP_REVIEWED":"SUPPRESS_INSTALLED","first_keep_step",keep.firstStep,
        "last_review_execution_count",keep.evidence.executions,"confirmed_executions",keep.evidence.executions,"request_id",keep.requestId,"candidate_id",keep.candidateId,
        "keep_probability",keep.decision.pKeep,"skip_probability",keep.decision.pSkip);}
    private void keepEvent(String name,Keep keep,org.json.JSONObject fields){ruleEvent(name,keep.anchor,JevRegistry.merge(keepFields(keep),fields));}
    private boolean alreadySuppressed(Bucket bucket) {
        GUITreeAction anchor=bucket.ruleAnchor;
        return matchingRule(anchor.getModelAction(),anchor.getGUITree(),anchor.getGUITreeNode())!=null;
    }
    /** Model probabilities and budget fallback use the same persistent concrete rule. */
    private void installRule(Bucket bucket,String identity,JevRegionProtocol.Decision decision,long gen,long version,String requestId) {
        long started=System.nanoTime();
        try{installRuleNow(bucket,identity,decision,gen,version,requestId);}
        finally{timing.record("region_rule_install",System.nanoTime()-started);}
    }
    private void installRuleNow(Bucket bucket,String identity,JevRegionProtocol.Decision decision,long gen,long version,String requestId) {
        Rule rule=new Rule(bucket,identity,decision,gen,version,decision==null?"BUDGET_FALLBACK":auto?"AUTO":"MODEL");rule.firstInstallStep=step;
        org.json.JSONObject nominated=nominationStructures.get(bucket.id);
        rules.put(rule.concreteId,rule);
        ruleEvent("REGION_ENTRY_ADDED",rule,JevRegistry.fields("request_id",requestId,"choice",decision==null?org.json.JSONObject.NULL:decision.decision,
            "keep_probability",decision==null?org.json.JSONObject.NULL:decision.pKeep,"skip_probability",rule.pSkip,"gui_identity",rule.identity,"region_auto",auto,
            "first_install_step",step,"nomination_step",nominated==null?org.json.JSONObject.NULL:nominated.optLong("nomination_step"),
            "nomination_region_state_count",nominated==null?org.json.JSONObject.NULL:nominated.optInt("region_state_count"),
            "nomination_execution_count",nominated==null?org.json.JSONObject.NULL:nominated.optLong("confirmed_executions"),
            "nomination_membership_receipt",nominated==null?org.json.JSONObject.NULL:nominated.opt("region_membership_receipt"),
            "nomination_component_ids",nominated==null?org.json.JSONObject.NULL:nominated.opt("region_component_ids"),
            "nomination_resolved_node_count",nominated==null?org.json.JSONObject.NULL:nominated.optInt("resolved_node_count")));
    }
    private org.json.JSONObject attemptFields(Attempt a) {
        return JevRegistry.fields("attempt_id",a.id,"selection_step",a.step,"generation",a.rule.generation,
            "source_state",a.sourceState,"selection_version",a.selectionVersion,
            "gui_identity",a.rule.identity,"skip_probability",a.rule.pSkip,"selection_strategy",a.strategy);
    }
    private void finishFallback(Action action) {
        for(Attempt skipped:skippedForFallback) {
            org.json.JSONObject fields=attemptFields(skipped);
            boolean restored=action instanceof ModelAction && skipped.action.getModelAction()==action
                && skipped.action.getGUITree()==((ModelAction)action).getResolvedGUITree()
                && skipped.action.getGUITreeNode()==((ModelAction)action).getResolvedNode();
            if(restored)ruleEvent("REGION_SKIP_RESTORED",skipped.rule,fields);
            else if(action==null)ruleEvent("REGION_SKIP_UNOBSERVED",skipped.rule,fields);
            else {
                skipped.rule.skips++;
                if(skipped.rule.firstSkipStep<0)skipped.rule.firstSkipStep=skipped.step;
                ruleEvent("REGION_ACTUAL_SKIP",skipped.rule,fields);
            }
            if(action!=null)JevRegistry.merge(fields,JevRegistry.fields("fallback_action_type",action.getType().name(),
                "fallback_model_action_id",action instanceof ModelAction?((ModelAction)action).getGraphId():org.json.JSONObject.NULL,
                "fallback_full_name",action instanceof ModelAction && ((ModelAction)action).getResolvedNode()!=null?
                    observedFullName(((ModelAction)action).getResolvedNode()):org.json.JSONObject.NULL));
            ruleEvent(action==null?"REGION_FALLBACK_UNOBSERVED":"REGION_FALLBACK_INPUT",skipped.rule,fields);
        }
        skippedForFallback.clear();
    }
    void reset(long version) {
        if(locator!=null){locator.refined(version);shadowInvalid=null;}
        finishFallback(null);active=null;dirty=true;if(pending!=null){pending.cancel(true);pending=null;}
        interrupted("abstraction_changed");abstraction=version;topology++;concretes++;facts++;
        ids.clear();states.clear();stateFacts.clear();roots.clear();implementations.clear();transitionIds.clear();links.clear();buckets.clear();byAction.clear();touched.clear();sampled.clear();nominationStructures.clear();
        current=null;currentTree=null;
    }
    private int id(State state){Integer id=ids.get(state);if(id==null){id=states.size();ids.put(state,id);states.add(state);stateFacts.add(new Fact(false,false,false));topology++;dirty=true;touch(state);}return id;}
    void root(State state){if(roots.add(id(state))){topology++;dirty=true;}}
    void touch(State state){if(state!=null)touched.add(state);}
    void transition(GUITreeTransition transition,String transitionId,boolean confirmed,String executionContextId,org.json.JSONObject sourceContext) {
        if(locator==null){transitionNow(transition,transitionId,confirmed,executionContextId,sourceContext);return;}
        try{transitionNow(transition,transitionId,confirmed,executionContextId,sourceContext);}
        catch(RuntimeException error){
            shadowInvalid=error instanceof SnapshotInvalid?(SnapshotInvalid)error:new SnapshotInvalid("shadow_transition_exception",JevRegistry.merge(shadowDiagnostic(null,transition),JevRegistry.fields("exception",error.getClass().getName(),"message",String.valueOf(error.getMessage()))));
            State source=transition.getSource().getCurrentState(),target=transition.getTarget().getCurrentState();
            try{if(source!=null && target!=null)locator.transitionUnmapped(transition,confirmed,id(source),id(target));else shadowHistoryIncomplete=true;}
            catch(RuntimeException unavailable){shadowHistoryIncomplete=true;}
        }
    }
    private void transitionNow(GUITreeTransition transition,String transitionId,boolean confirmed,String executionContextId,org.json.JSONObject sourceContext) {
        if(!transitionIds.add(transitionId))return;
        State source=transition.getSource().getCurrentState(),target=transition.getTarget().getCurrentState();int from=id(source),to=id(target);
        GUITreeAction historical=transition.getAction();GUITreeNode node=historical.getGUITreeNode();
        ModelAction action=locator==null?historical.getModelAction():shadowAction(transition);
        GUITreeAction concrete=locator==null?historical:new GUITreeAction(transition.getSource(),node,action);
        String name=node==null?"<lifecycle>":observedFullName(node);
        Map<String,Bucket> group=byAction.get(action);if(group==null){group=new HashMap<String,Bucket>();byAction.put(action,group);}
        Bucket bucket=group.get(name);if(bucket==null){bucket=new Bucket(nextConcreteId++,action,concrete);bucket.abstraction=abstraction;bucket.historicalAnchor=historical;buckets.put(bucket.id,bucket);group.put(name,bucket);concretes++;dirty=true;
            if(locator!=null && action!=historical.getModelAction())locator.rebound(step,abstraction,shadowDiagnostic(bucket,transition));}
        if(node!=null && observedNode(bucket.anchor,transition.getSource(),action)!=node)bucket.ambiguous=true;
        if(implementations.add(from+":"+to+":"+bucket.id)){links.add(new Link(from,to,bucket.id));topology++;dirty=true;}
        if(bucket.targets.add(to)){concretes++;dirty=true;}
        if(confirmed) {
            bucket.executions++;
            bucket.confirmedTargets.add(transition.getTarget());
            org.json.JSONArray labels=JevEvidence.resultLabels(transition.getTarget().getRootNode());
            boolean sameGui=JevEvidence.guiIdentity(transition.getSource()).equals(JevEvidence.guiIdentity(transition.getTarget()));
            String key=new org.json.JSONArray().put(target.getActivity()).put(labels).put(source==target).put(sameGui).toString();
            bucket.confirmedPatterns.put(transitionId,key);
            org.json.JSONObject outcome=bucket.outcomes.get(key);
            if(outcome==null){outcome=JevRegistry.fields("target_activity",JevEvidence.boundedText(target.getActivity()),"result_labels",labels,"count",0,
                "self_loop",source==target,"activity_unchanged",Objects.equals(source.getActivity(),target.getActivity()),"gui_unchanged",sameGui);bucket.outcomes.put(key,outcome);}
            try{outcome.put("count",outcome.getInt("count")+1);}catch(org.json.JSONException impossible){throw new IllegalStateException(impossible);}
        }
        if(attempt!=null && attempt.action==historical){if(confirmed)attempt.rule.confirmedExecutions++;ruleEvent(confirmed?"REGION_ACTUAL_EXECUTION":"REGION_INPUT_FAILED",attempt.rule,
            JevRegistry.merge(attemptFields(attempt),JevRegistry.fields("transition_id",transitionId,"input_confirmed",confirmed)));attempt=null;}
        for(Iterator<AuditPending> it=auditPending.iterator();it.hasNext();) {
            AuditPending pending=it.next();
            if(pending.action!=concrete)continue;
            if(confirmed)pending.candidate.confirmedExecuted++;
            events.emit("REGION_SHADOW_EXECUTION",step,abstraction,pending.candidate.bucket.action.getState().getGraphId(),pending.candidate.bucket.action.getGraphId(),
                JevRegistry.merge(auditFields(pending.candidate,pending.step,confirmed?"confirmed_execution":"unconfirmed_transition",confirmed),
                    JevRegistry.fields("transition_id",transitionId,"execution_context_id",executionContextId==null?org.json.JSONObject.NULL:executionContextId,
                        "gate_context_id",pending.gateContextId==null?org.json.JSONObject.NULL:pending.gateContextId)));
            it.remove();
        }
        for(Iterator<Shadow> it=shadowInputs.iterator();it.hasNext();) {Shadow shadow=it.next();if(shadow.selected!=concrete)continue;
            if(confirmed)shadow.keep.executed++;
            shadowEvent("REGION_KEEP_SHADOW_EXECUTION",shadow,JevRegistry.fields("transition_id",transitionId,"input_confirmed",confirmed,"reason",confirmed?"confirmed_transition":"unconfirmed_input"));it.remove();}
        touch(source);touch(target);
        if(locator!=null)locator.transition(transition,confirmed,bucket.id,from,to,transitionId,bucket.confirmedPatterns.get(transitionId));
    }
    private boolean eligible(Bucket bucket){return bucket.executions>0 && bucket.action.requireTarget() && !bucket.action.isBack() && !bucket.ambiguous;}
    private Snapshot snapshot() {
        if(locator!=null)validateShadowSnapshot();
        List<Concrete> entries=new ArrayList<Concrete>();for(Bucket bucket:buckets.values())entries.add(new Concrete(bucket.id,ids.get(bucket.action.getState()),bucket.targets,eligible(bucket)));
        return new Snapshot(abstraction,topology,concretes,facts,ids.get(current),JevEvidence.guiIdentity(currentTree),stateFacts,links,roots,entries);
    }
    private void validateShadowSnapshot(){
        if(shadowInvalid!=null)throw shadowInvalid;
        if(shadowHistoryIncomplete)throw new SnapshotInvalid("history_incomplete",shadowDiagnostic(null,null));
        if(shadowModel==null || shadowModel.getVersion()!=abstraction)throw new SnapshotInvalid("graph_version_mismatch",shadowDiagnostic(null,null));
        Set<State> activeStates=Collections.newSetFromMap(new IdentityHashMap<State,Boolean>());activeStates.addAll(shadowModel.getGraph().getStates());
        if(current==null || currentTree==null || currentTree.getCurrentState()!=current || !ids.containsKey(current) || !activeStates.contains(current))
            throw new SnapshotInvalid("current_source_unresolved",shadowDiagnostic(null,null));
        for(State state:states)if(!activeStates.contains(state))throw new SnapshotInvalid("indexed_state_retired",JevRegistry.merge(shadowDiagnostic(null,null),JevRegistry.fields("retired_state",stateTag(state))));
        for(Bucket bucket:buckets.values()){
            if(bucket.abstraction!=abstraction || bucket.action.getState()!=bucket.source || bucket.anchor.getGUITree().getCurrentState()!=bucket.source || bucket.anchor.getModelAction()!=bucket.action
                    || !ids.containsKey(bucket.source) || !activeStates.contains(bucket.source)
                    || shadowAction(bucket.anchor.getGUITree(),bucket.anchor.getGUITreeNode(),bucket.anchor.getActionType())!=bucket.action)throw new SnapshotInvalid("bucket_source_action_mismatch",shadowDiagnostic(bucket,null));
            for(int target:bucket.targets)if(target<0 || target>=states.size())throw new SnapshotInvalid("bucket_target_unresolved",shadowDiagnostic(bucket,null));
        }
        for(Link link:links){Bucket bucket=buckets.get(link.concrete);
            if(bucket==null || link.source<0 || link.source>=states.size() || link.target<0 || link.target>=states.size() || states.get(link.source)!=bucket.source || !bucket.targets.contains(link.target))
                throw new SnapshotInvalid("link_bucket_mismatch",shadowDiagnostic(bucket,null));}
        if(shadowModel.getVersion()!=abstraction)throw new SnapshotInvalid("graph_changed_during_snapshot",shadowDiagnostic(null,null));
    }
    void locatorEvidence(JevRegionLocator.Evidence stats) {
        for(Bucket b:buckets.values()){
            org.json.JSONArray outcomes=new org.json.JSONArray();for(org.json.JSONObject outcome:b.outcomes.values())try{outcomes.put(new org.json.JSONObject(outcome.toString()));}
            catch(org.json.JSONException error){throw new IllegalStateException(error);}
            stats.anchors.put(b.id,b.anchor);stats.buckets.put(b.id,b);
            if(!shadowAudit && eligible(b))try{
                GUITree context=b.source.getLatestGUITree();boolean historical=observedNode(b.anchor,context,b.action)==null;
                if(historical)context=b.anchor.getGUITree();
                org.json.JSONObject view=candidate(b,context,"pending");
                if(view==null)throw new IllegalStateException("identity-valid concrete has no source evidence: "+b.id);
                view.put("source_gui_id",context.getId());view.put("evidence_gaps",new org.json.JSONArray());
                if(historical)view.getJSONArray("evidence_gaps").put("control context is a historical confirmed source capture; no match on latest source capture");
                stats.candidates.put(b.id,view);
            }catch(org.json.JSONException error){throw new IllegalStateException(error);}
            stats.evidence.put(b.id,JevRegistry.fields("state_id",b.action.getState().getGraphId(),"model_action_id",b.action.getGraphId(),
                "action_type",b.action.getType().name(),"full_name",b.anchor.getGUITreeNode()==null?"<lifecycle>":observedFullName(b.anchor.getGUITreeNode()),"confirmed_executions",b.executions,"outcomes",outcomes));
        }
        for(int i=0;i<states.size();i++){
            State s=states.get(i);org.json.JSONArray guis=new org.json.JSONArray();for(GUITree gui:s.getGUITrees())guis.put(gui.getId());
            stats.stateRows.put(JevRegistry.fields("index",i,"state_id",s.getGraphId(),"activity",s.getActivity(),"gui_ids",guis));
            if(!shadowAudit)try {
                org.json.JSONArray operations=new org.json.JSONArray();
                for(ModelAction action:s.getActions()){
                    org.json.JSONArray controls=new org.json.JSONArray();GUITree latest=s.getLatestGUITree();
                    if(action.requireTarget())for(GUITreeNode node:latest.getNodes(action.getTarget()))controls.put(JevRegistry.fields("full_name",observedFullName(node),"control",control(node)));
                    long confirmed=0;for(Bucket bucket:buckets.values())if(bucket.action==action)confirmed+=bucket.executions;
                    operations.put(JevRegistry.fields("model_action_id",action.getGraphId(),"action_type",action.getType().name(),
                        "native_visits",action.getVisitedCount(),"unvisited",action.isUnvisited(),"enabled",action.isEnabled(),"valid",action.isValid(),
                        "confirmed_executions",confirmed,"concrete_controls",controls));
                }
                stats.stateRows.getJSONObject(i).put("source_context",JevEvidence.sourceContext(s.getLatestGUITree())).put("operations",operations).put("operations_status","snapshot_inventory");
            }catch(org.json.JSONException error){throw new IllegalStateException(error);}
            try{org.json.JSONObject row=stats.stateRows.getJSONObject(i);Fact fact=stateFacts.get(i);row.put("known",fact.known).put("saturated",fact.saturated).put("settled",fact.settled);}
            catch(org.json.JSONException error){throw new IllegalStateException(error);}
        }
    }
    void safePoint(Model model,State state,GUITree tree,long timestamp) {
        long started=System.nanoTime();
        try{if(locator!=null)shadowModel=model;safePointNow(model,state,tree,timestamp);}
        catch(RuntimeException error){
            if(locator==null)throw error;
            shadowInvalid=error instanceof SnapshotInvalid?(SnapshotInvalid)error:new SnapshotInvalid("shadow_snapshot_exception",JevRegistry.merge(shadowDiagnostic(null,null),JevRegistry.fields("exception",error.getClass().getName(),"message",String.valueOf(error.getMessage()))));
            if(locator.needsSnapshot())locator.snapshotInvalid(timestamp,abstraction,shadowInvalid.detail);
        }
        finally{timing.record("region_safe_point",System.nanoTime()-started);}
    }
    private void safePointNow(Model model,State state,GUITree tree,long timestamp) {
        step=timestamp;current=state;currentTree=tree;utgStateCount=model.getGraph().getStates().size();id(state);for(State root:model.getGraph().getEntryStates())root(root);touch(state);
        recordMembershipVersion(model);
        if(locator==null && !warmupStarted){warmupStarted=true;next=System.nanoTime()+warmup;events.emit("REGION_WARMUP_STARTED",step,abstraction,state.getGraphId(),null,JevRegistry.fields("warmup_ms",TimeUnit.NANOSECONDS.toMillis(warmup)));}
        for(State touchedState:touched){Integer id=ids.get(touchedState);if(id==null)continue;Fact fact=runtime.explorationFact(touchedState);
            if(!fact.equals(stateFacts.get(id))){stateFacts.set(id,fact);facts++;dirty=true;}}
        touched.clear();
        if(locator!=null){
            locator.safePoint(tree,locator.needsSnapshot()?snapshot():null,states,step);
            if(!shadowAudit){pollReview();if(!closed && review==null && System.nanoTime()>=nextReview)submitLocatorReview();}
            return;
        }
        if(active!=null)active=validate(snapshot(),active.regions,active.blocked,System.nanoTime(),0,0);
        pollNomination();pollReview();
        if(shadowAudit)observeAuditAvailability(state,tree);
        long now=System.nanoTime();
        if(!closed && pending==null && dirty && now>=next) {
            next=now+interval;final Snapshot snapshot=snapshot();
            pending=worker.submit(new Callable<Result>(){public Result call(){return calculator.compute(snapshot);}});dirty=false;
        }
        if(!shadowAudit && !closed && review==null && active!=null && now>=nextReview)submitReview(state,tree);
        if(!shadowAudit)observeShadow(state,tree);
    }
    private void pollNomination() {
        if(pending==null || !pending.isDone())return;
        try {
            Result result=pending.get();timing.record("region_worker",result.nanos);
            // State indices survive topology growth only within one abstraction.
            // Recheck the old candidate set on today's graph without rerunning dominators.
            if(result.input.abstraction!=abstraction){dirty=true;event("REGION_STALE","nomination_abstraction_changed");return;}
            long expectedTopology=result.input.topology;
            result=validate(snapshot(),result.regions,result.blocked,System.nanoTime(),0,0);
            active=result;generation++;
            recordMembershipNomination(result);
            events.emit("REGION_NOMINATED",step,abstraction,current.getGraphId(),null,JevRegistry.fields(
                "reason",expectedTopology==topology?result.reason:"current_graph_revalidated","generation",generation,
                "expected_topology_version",expectedTopology,"topology_version",topology,"concrete_version",concretes,"fact_version",facts,
                "candidate_count",result.blocked.size(),"blocked",rules.size(),"isolated_states",result.isolated.size()));
            events.emit("REGION_MEMBERS",step,abstraction,null,null,JevRegistry.fields("generation",generation,"region_members",stateNames(result.members),"saturated_settled_count",result.members.size()));
            for(int id:new TreeSet<Integer>(result.blocked)) {Bucket bucket=buckets.get(id);
                org.json.JSONObject nominated=JevRegistry.merge(structure(bucket),JevRegistry.fields("nomination_step",step));
                nominationStructures.put(id,nominated);
                events.emit("REGION_CANDIDATE_NOMINATED",step,abstraction,bucket.action.getState().getGraphId(),bucket.action.getGraphId(),nominated);
                if(shadowAudit)recordAuditNomination(bucket);
            }
        }catch(Exception failure){dirty=true;event("REGION_FAILED",failure.getClass().getSimpleName());}
        finally{pending=null;}
    }
    private String stateNames(Set<Integer> members){List<String> names=new ArrayList<String>();for(int id:members)names.add(states.get(id).getGraphId());Collections.sort(names);return names.toString();}
    private org.json.JSONObject membershipState(State state) {
        // IDs and counters only: never naming, GUI rebuilding, matching, binding or RNG.
        Set<Integer> guiIds=new TreeSet<Integer>();for(GUITree tree:state.getGUITrees())guiIds.add(tree.getId());
        return JevRegistry.fields("state_id",state.getGraphId(),"gui_ids",new org.json.JSONArray(guiIds),"native_visits",state.getVisitedCount());
    }
    private String membershipName(){return "region-membership-v"+abstraction+"-g"+generation;}
    private void saveMembership(String name,String event,org.json.JSONObject value) {
        try{runtime.save(name,value);events.emit(event,step,abstraction,null,null,JevRegistry.fields("membership_receipt",name,"generation",generation));}
        catch(java.io.IOException error){runtime.receiptFailed(error);}
    }
    private void recordMembershipVersion(Model model) {
        if(!membershipAudit || membershipVersion==abstraction)return;
        long started=System.nanoTime();
        try {
            org.json.JSONArray rows=new org.json.JSONArray();Map<String,State> ordered=new TreeMap<String,State>();
            for(State state:model.getGraph().getStates())ordered.put(state.getGraphId(),state);
            for(State state:ordered.values())rows.put(membershipState(state));
            saveMembership("region-membership-version-"+abstraction,"REGION_MEMBERSHIP_VERSION",
                JevRegistry.fields("schema",1,"kind","native_partition","abstraction_version",abstraction,"step",step,"states",rows));
            membershipVersion=abstraction;
        }finally{timing.record("region_membership_record",System.nanoTime()-started);}
    }
    private void recordMembershipNomination(Result result) {
        if(!membershipAudit)return;
        long started=System.nanoTime();
        try {
            org.json.JSONArray components=new org.json.JSONArray();
            membershipComponents.clear();
            for(int i=0;i<result.regions.size();i++) {
                membershipComponents.put(result.regions.get(i),i);
                org.json.JSONArray rows=new org.json.JSONArray();
                for(int id:new TreeSet<Integer>(result.regions.get(i)))rows.put(membershipState(states.get(id)));
                components.put(JevRegistry.fields("component_id",i,"states",rows));
            }
            saveMembership(membershipName(),"REGION_MEMBERSHIP_NOMINATION",JevRegistry.fields("schema",1,"kind","screened_components",
                "abstraction_version",abstraction,"generation",generation,"nomination_step",step,
                "worker_topology_version",result.input.topology,"current_topology_version",topology,"components",components));
        }finally{timing.record("region_membership_record",System.nanoTime()-started);}
    }
    private org.json.JSONObject structure(Bucket bucket) {
        Set<Integer> union=new HashSet<Integer>();int regions=0,edges=0;long visits=0,revisits=0;
        org.json.JSONArray componentIds=new org.json.JSONArray();
        if(active!=null) {
            for(Set<Integer> region:active.regions)if(!Collections.disjoint(region,bucket.targets)) {
                union.addAll(region);regions++;
                // Current-fact validation can remove whole components; surviving
                // components retain their index in the frozen nomination receipt.
                Integer index=membershipComponents.get(region);componentIds.put(index==null?org.json.JSONObject.NULL:index);
            }
            // Unique (source State, concrete bucket, target State) links, including known
            // unconfirmed transitions, exactly as represented in the locator's UTG.
            for(Link link:active.input.links)if(union.contains(link.source) && union.contains(link.target))edges++;
        }
        for(int id:union){int count=states.get(id).getVisitedCount();visits+=count;revisits+=Math.max(0,count-1);}
        Keep keep=matchingKeep(bucket);int resolvedNodes=bucket.action.getResolvedNodes()==null?0:bucket.action.getResolvedNodes().length;
        return JevRegistry.fields("concrete_id",bucket.id,"confirmed_executions",bucket.executions,"generation",generation,
            "review_status",alreadySuppressed(bucket)?"SUPPRESS_INSTALLED":keep==null?"UNREVIEWED":"KEEP_REVIEWED",
            "region_count",regions,"region_state_count",union.size(),"union_state_count",regions>1?union.size():org.json.JSONObject.NULL,
            "region_membership_receipt",membershipAudit && active!=null?membershipName():org.json.JSONObject.NULL,
            "region_component_ids",membershipAudit?componentIds.toString():org.json.JSONObject.NULL,
            "region_edge_count",edges,"region_edge_definition","induced_unique_source_concrete_target",
            "region_native_state_visits",visits,"region_native_state_revisits",revisits,"known_target_count",bucket.targets.size(),
            "utg_state_count",utgStateCount,"native_actions_so_far",nativeActions,"remaining_native_actions",org.json.JSONObject.NULL,"remaining_native_actions_known",false,
            "resolved_node_count",resolvedNodes,
            "remaining_time_ms",runDeadlineElapsedMs>0?Math.max(0,runDeadlineElapsedMs-android.os.SystemClock.elapsedRealtime()):org.json.JSONObject.NULL);
    }
    private org.json.JSONObject auditStructure(Bucket bucket) {
        org.json.JSONObject structure=structure(bucket);
        if(structure.isNull("union_state_count"))
            JevRegistry.merge(structure,JevRegistry.fields("union_state_count",structure.optInt("region_state_count",0)));
        return JevRegistry.merge(structure,JevRegistry.fields("shadow_audit",true));
    }
    private org.json.JSONObject auditFields(AuditCandidate candidate,long selectionStep,String reason,boolean confirmed) {
        org.json.JSONObject fields;
        try {fields=new org.json.JSONObject(candidate.firstStructure.toString());}
        catch(org.json.JSONException error){throw new IllegalStateException(error);}
        return JevRegistry.merge(fields,JevRegistry.fields("shadow_audit",true,"shadow_candidate_id",candidate.id,
            "first_nomination_step",candidate.firstStep,"first_nomination_time_ms",candidate.firstTimeMs,
            "repeat_nomination_count",candidate.repeatNominations,"future_exact_availability_count",candidate.futureExactAvailability,
            "future_model_action_selection_count",candidate.modelActionSelected,"future_would_gate_count",candidate.wouldGate,
            "future_confirmed_execution_count",candidate.confirmedExecuted,"selection_step",selectionStep,
            "steps_since_nomination",selectionStep-candidate.firstStep,"first_would_gate_distance",candidate.firstWouldGateDistance<0?org.json.JSONObject.NULL:candidate.firstWouldGateDistance,
            "would_gate_sequence",candidate.wouldGateSequence,"reason",reason,"input_confirmed",confirmed));
    }
    private String auditKey(Bucket bucket) {
        String full=observedFullName(bucket.anchor.getGUITreeNode());
        return bucket.action.getState().getGraphId()+"|"+bucket.action.getGraphId()+"|"+bucket.action.getType().name()+"|"+full;
    }
    private int resolvedNodeCount(Bucket bucket) {
        return bucket.action.getResolvedNodes()==null?0:bucket.action.getResolvedNodes().length;
    }
    private void recordAuditNomination(Bucket bucket) {
        String key=auditKey(bucket);AuditCandidate candidate=auditCandidates.get(key);
        if(candidate!=null) {
            candidate.repeatNominations++;
            events.emit("REGION_SHADOW_CANDIDATE_NOMINATED",step,abstraction,bucket.action.getState().getGraphId(),bucket.action.getGraphId(),
                JevRegistry.merge(auditStructure(bucket),JevRegistry.fields("shadow_candidate_id",candidate.id,"first_nomination",false,
                    "first_nomination_step",candidate.firstStep,"first_nomination_time_ms",candidate.firstTimeMs,"repeat_nomination_count",candidate.repeatNominations)));
            return;
        }
        String id="region-shadow-c"+(auditCandidates.size()+1);
        AuditCandidate created=new AuditCandidate(bucket,id,step,System.currentTimeMillis(),auditStructure(bucket),resolvedNodeCount(bucket));
        auditCandidates.put(key,created);
        events.emit("REGION_SHADOW_CANDIDATE_NOMINATED",step,abstraction,bucket.action.getState().getGraphId(),bucket.action.getGraphId(),
            JevRegistry.merge(auditStructure(bucket),JevRegistry.fields("shadow_candidate_id",id,"first_nomination",true,
                "first_nomination_step",step,"first_nomination_time_ms",created.firstTimeMs,"repeat_nomination_count",0)));
    }
    private void observeAuditAvailability(State state,GUITree tree) {
        for(AuditCandidate candidate:auditCandidates.values()) {
            ModelAction action=null;for(ModelAction currentAction:state.getActions())if(currentAction==candidate.bucket.action){action=currentAction;break;}
            if(action==null)continue;
            JevRegionMatcher.Match match=JevRegionMatcher.match(candidate.anchor,tree,action);
            boolean available=match.action && match.concrete!=null && action.getResolvedNodes()!=null;
            if(available) {
                candidate.futureExactAvailability++;
                events.emit("REGION_SHADOW_EXACT_AVAILABLE",step,abstraction,state.getGraphId(),action.getGraphId(),auditFields(candidate,step,"exact_available",false));
            }
        }
    }
    void auditSelection(ModelAction action,GUITree tree,long timestamp,String strategy) {
        if(!shadowAudit || action==null || tree==null)return;
        step=timestamp;
        for(AuditCandidate candidate:auditCandidates.values()) {
            JevRegionMatcher.Match match=JevRegionMatcher.match(candidate.anchor,tree,action);
            if(!match.action)continue;
            candidate.modelActionSelected++;
            events.emit("REGION_SHADOW_MODEL_ACTION_SELECTED",step,abstraction,action.getState().getGraphId(),action.getGraphId(),
                JevRegistry.merge(auditFields(candidate,step,"model_action_selected",false),JevRegistry.fields("selection_strategy",strategy==null?"unknown":strategy,
                    "selected_action_fingerprint",action.getActionFingerprint(),"selected_resolved_node_count",resolvedNodeCount(candidate.bucket))));
                if(match.exact(action.getResolvedNode())) {
                candidate.wouldGate++;candidate.wouldGateSequence++;
                if(candidate.firstWouldGateDistance<0)candidate.firstWouldGateDistance=step-candidate.firstStep;
                    GUITreeAction selected=action.getResolvedGUITreeAction();
                    String gateContextId=null;
                    if(selected!=null && runtime.regionContextAuditEnabled()) {
                        org.json.JSONObject snapshot=runtime.contextSnapshot(tree,action.getState(),action,action.getResolvedNode());
                        org.json.JSONObject receipt=new org.json.JSONObject();
                        try {receipt.put("schema",1).put("kind","region_shadow_gate_context").put("candidate_id",candidate.id)
                            .put("gate_step",step).put("region_concrete_id",candidate.bucket.id)
                            .put("candidate_matcher",new org.json.JSONObject().put("state_id",candidate.bucket.action.getState().getGraphId())
                                .put("model_action_id",candidate.bucket.action.getGraphId()).put("action_type",candidate.bucket.action.getType().name())
                                .put("full_name",observedFullName(candidate.anchor.getGUITreeNode())))
                            .put("snapshot",snapshot);} catch(org.json.JSONException error){throw new IllegalStateException(error);}
                        gateContextId=runtime.saveDiagnostic("region-context-gate-"+candidate.id+"-"+candidate.wouldGateSequence,receipt);
                    }
                    if(selected!=null)auditPending.add(new AuditPending(candidate,selected,step,gateContextId));
                events.emit("REGION_SHADOW_WOULD_GATE",step,abstraction,action.getState().getGraphId(),action.getGraphId(),
                    JevRegistry.merge(auditFields(candidate,step,"would_gate",false),JevRegistry.fields("selection_strategy",strategy==null?"unknown":strategy,
                        "selected_action_fingerprint",action.getActionFingerprint(),"selected_resolved_node_count",resolvedNodeCount(candidate.bucket),
                        "gate_context_id",gateContextId==null?org.json.JSONObject.NULL:gateContextId)));
            }
        }
    }
    static boolean safeFrom(Result result,int current) {
        if(current<0 || current>=result.input.states.size())return false;
        BitSet lost=reach(result.input,Collections.singleton(current),Collections.<Integer>emptySet());lost.andNot(reach(result.input,Collections.singleton(current),result.blocked));
        for(int state=lost.nextSetBit(0);state>=0;state=lost.nextSetBit(state+1))if(!result.input.states.get(state).complete() || result.members.contains(current) && !result.members.contains(state))return false;
        return true;
    }
    // Region evidence and ledger projections must not assign native Name order.
    private static String observedFullName(final GUITreeNode node) {
        return NameManager.withoutInterning(new java.util.function.Supplier<String>() {
            public String get(){return JevIdentity.fullName(node).toString();}});
    }
    private static GUITreeNode observedNode(final GUITreeAction anchor,final GUITree tree,final ModelAction action) {
        return NameManager.withoutInterning(new java.util.function.Supplier<GUITreeNode>() {
            public GUITreeNode get(){return JevRegistry.correspondingNode(anchor,tree,action);}});
    }
    private static String observedLabels(final String type,final GUITreeNode node) {
        return NameManager.withoutInterning(new java.util.function.Supplier<String>() {
            public String get(){return JevEvidence.member(type,node).labelsJson;}});
    }
    private org.json.JSONObject control(GUITreeNode node){return JevRegistry.fields("class",JevEvidence.boundedText(node.getClassName()),"resource_id",JevEvidence.boundedText(node.getResourceID()),
        "text",JevEvidence.rawText(node),"content_desc",JevEvidence.boundedText(node.getContentDesc()),"enabled",node.isEnabled(),"clickable",node.isClickable(),"long_clickable",node.isLongClickable(),
        "scrollable",node.isScrollable(),"checked",node.isChecked(),"selected",node.getJevSelected()==null?org.json.JSONObject.NULL:node.getJevSelected());}
    private org.json.JSONObject candidate(Bucket bucket,GUITree tree,String candidateId)throws org.json.JSONException {
        GUITreeNode node=observedNode(bucket.anchor,tree,bucket.action);if(node==null)return null;
        org.json.JSONArray outcomes=new org.json.JSONArray();for(org.json.JSONObject outcome:bucket.outcomes.values())outcomes.put(new org.json.JSONObject(outcome.toString()));
        org.json.JSONArray alternatives=new org.json.JSONArray();
        for(ModelAction other:tree.getCurrentState().getActions()) {
            if(!other.requireTarget() || !other.isEnabled() || !other.isValid() || other.getResolvedGUITree()!=tree || other.getResolvedNodes()==null)continue;
            for(GUITreeNode alternative:other.getResolvedNodes())if(alternative.isEnabled() && (alternative!=node || other!=bucket.action) && alternatives.length()<8)
                alternatives.put(JevRegistry.fields("action_type",other.getType().name(),"full_name",JevEvidence.boundedText(observedFullName(alternative)),"control",control(alternative)));
        }
        return JevRegistry.fields("candidate_id",candidateId,"source_context",JevEvidence.sourceContext(tree),"action_type",bucket.action.getType().name(),
            "full_name",JevEvidence.boundedText(observedFullName(node)),"control",control(node),"target_subtree_labels",new org.json.JSONArray(observedLabels(bucket.action.getType().name(),node)),
            "local_context",JevEvidence.localContext(node),"execution_count",bucket.executions,"outcomes",outcomes,"alternatives",alternatives);
    }
    void acceptLocator(org.json.JSONObject snapshot,JevRegionLocator.Evidence evidence){
        if(shadowAudit || evidence.candidates.isEmpty())return;
        try{locatorNominations.add(new Nomination(snapshot,evidence));}
        catch(org.json.JSONException error){throw new IllegalStateException(error);}
        catch(java.io.IOException error){throw new IllegalStateException(error);}
    }
    private void submitLocatorReview(){
        String requestId="region-r"+(++requestSequence);
        try {
          while(!locatorNominations.isEmpty()){
            Nomination frame=locatorNominations.peek();Nomination.Input nomination=frame.read();org.json.JSONObject locatorSnapshot=nomination.snapshot;JevRegionLocator.Evidence locatorReviewEvidence=nomination.evidence;
            List<Integer> selected=new ArrayList<Integer>();JevRegionProtocol.Request request=null;
            for(int id:new TreeSet<Integer>(locatorReviewEvidence.candidates.keySet())){
                Bucket bucket=locatorReviewEvidence.buckets.get(id);if(alreadySuppressed(bucket))continue;
                List<Integer> expanded=new ArrayList<Integer>(selected);expanded.add(id);
                org.json.JSONObject state=JevRegionEvidence.state(locatorSnapshot,locatorReviewEvidence.candidates,expanded);
                // Evidence.state only accepts nominated entries; no other eligibility test is added here.
                JevRegionProtocol.Request next=JevRegionProtocol.snapshot(requestId,state,Integer.MAX_VALUE,providerModel);
                if(next.requestBytes>maxBytes){
                    if(!selected.isEmpty())break; // Send the fitting packet first; test this candidate alone next.
                    String receipt="budget-fallback-"+requestId+"-"+id;
                    runtime.save(receipt,JevRegistry.fields("rule_source","BUDGET_FALLBACK","model_called",false,
                        "concrete_id",id,"request_bytes",next.requestBytes,"max_request_bytes",maxBytes,"skip_probability",0.3,
                        "locator_receipt",locatorSnapshot.getJSONObject("schedule").getString("locator_receipt"),
                        "request",new org.json.JSONObject(next.bodyJson)));
                    installRule(bucket,JevEvidence.guiIdentity(bucket.anchor.getGUITree()),null,++generation,locatorSnapshot.getLong("abstraction_version"),null);
                    ruleEvent("REGION_BUDGET_FALLBACK",rules.get(bucket.id),JevRegistry.fields("reason","request_byte_budget_exceeded",
                        "request_bytes",next.requestBytes,"max_request_bytes",maxBytes,"skip_probability",0.3,"fallback_receipt",receipt));
                    frame.buckets.remove(id);
                    continue;
                }
                request=next;selected=expanded;
            }
            if(request==null){locatorNominations.remove();continue;}
            List<Bucket> selectedBuckets=new ArrayList<Bucket>();org.json.JSONArray mapping=new org.json.JSONArray();
            org.json.JSONArray cs=new org.json.JSONObject(request.stateJson).getJSONArray("candidates");
            for(int i=0;i<cs.length();i++){
                org.json.JSONObject c=cs.getJSONObject(i);int id=c.getJSONObject("identity").getInt("concrete_id");
                selectedBuckets.add(locatorReviewEvidence.buckets.get(id));mapping.put(JevRegistry.fields("candidate_id",c.getString("candidate_id"),
                    "concrete_id",id,"identity",c.getJSONObject("identity"),"associated_regions",c.getJSONArray("associated_regions")));
            }
            long reviewGeneration=generation+1;
            runtime.save("request-"+requestId,JevRegistry.fields("request_id",requestId,"generation",reviewGeneration,"evidence_digest",request.evidenceDigest,
                "abstraction_version",locatorSnapshot.getLong("abstraction_version"),"snapshot_monotonic_ns",locatorSnapshot.getLong("snapshot_monotonic_ns"),"locator_receipt",locatorSnapshot.getJSONObject("schedule").getString("locator_receipt"),
                "candidates",mapping,"request",new org.json.JSONObject(request.bodyJson)));
            JevTransport.Call call=transport.submit(requestId,request.bodyJson,timeoutMs,Math.max(16384,maxBytes));
            events.emit("REGION_REVIEW_REQUEST",step,abstraction,null,null,JevRegistry.fields("request_id",requestId,"candidate_count",selectedBuckets.size(),"request_bytes",request.requestBytes,"generation",reviewGeneration));
            if(call==null){nextReview=System.nanoTime()+interval;event("REGION_FAILED","QUEUE_FULL");return;}
            modelInvocations++;generation=reviewGeneration;review=new Review(request,call,selectedBuckets,selectedBuckets.get(0).anchor.getGUITree(),locatorSnapshot.getLong("abstraction_version"),reviewGeneration);
            return;
          }
        }catch(Exception error){
            nextReview=System.nanoTime()+interval;
            if(error instanceof java.io.IOException)runtime.receiptFailed(error);
            events.emit("REGION_REVIEW_ERROR",step,abstraction,null,null,JevRegistry.fields("request_id",requestId,"reason","complete_evidence_request_failed", "message",String.valueOf(error.getMessage())));
        }
    }
    private void submitReview(State state,GUITree tree) {
        org.json.JSONArray candidates=new org.json.JSONArray();List<Bucket> selected=new ArrayList<Bucket>();JevRegionProtocol.Request request=null;
        boolean autoInstalled=false;
        List<String> reasons=new ArrayList<String>();
        String requestId="region-r"+(++requestSequence);
        Bucket constructing=null;org.json.JSONObject autoDraft=null;
        try {
            for(int id:new TreeSet<Integer>(active.blocked)) {
                Bucket bucket=buckets.get(id);if(bucket.action.getState()!=state)continue;
                GUITreeNode node=observedNode(bucket.anchor,tree,bucket.action);
                if(node==null || !node.isEnabled())continue;
                eligibleOccurrences++;boolean firstEligible=eligibleCandidates.add(auditKey(bucket));
                events.emit("REGION_REVIEW_ELIGIBLE",step,abstraction,state.getGraphId(),bucket.action.getGraphId(),
                    JevRegistry.merge(structure(bucket),JevRegistry.fields("first_eligible",firstEligible,"region_auto",auto)));
                if(alreadySuppressed(bucket)) {
                    if(auto){duplicateInstallsAvoided++;events.emit("REGION_AUTO_DUPLICATE_AVOIDED",step,abstraction,state.getGraphId(),bucket.action.getGraphId(),structure(bucket));}
                    continue;
                }
                constructing=bucket;autoDraft=null;
                if(auto) {
                    org.json.JSONObject value=candidate(bucket,tree,"c1");if(value==null)continue;
                    autoDraft=value;
                    // Reuse production packet/program validation, but never submit transport.
                    JevRegionProtocol.snapshot(requestId,JevRegistry.fields("candidates",new org.json.JSONArray().put(value)),maxBytes,providerModel);
                    installRule(bucket,JevEvidence.guiIdentity(tree),new JevRegionProtocol.Decision("c1","SUPPRESS",1-autoProbability,autoProbability),generation,abstraction,requestId);
                    autoInstalled=true;
                    continue;
                }
                Keep keep=matchingKeep(bucket);String reason=keep==null?"first_review":keep.evidence.change(bucket);
                if(keep!=null && reason==null){keepCacheHits++;keepEvent("REGION_KEEP_CACHE_HIT",keep,
                    JevRegistry.merge(structure(bucket),JevRegistry.fields("reason","unchanged_confirmed_evidence")));continue;}
                org.json.JSONObject candidate=candidate(bucket,tree,"c"+(selected.size()+1));if(candidate==null)continue;
                org.json.JSONArray expanded=new org.json.JSONArray(candidates.toString());expanded.put(candidate);
                try{request=JevRegionProtocol.snapshot(requestId,JevRegistry.fields("candidates",expanded),maxBytes,providerModel);}
                catch(org.json.JSONException tooLarge){if(selected.isEmpty())throw tooLarge;break;}
                candidates=expanded;selected.add(bucket);reasons.add(reason);
            }
            if(auto){if(autoInstalled)nextReview=System.nanoTime()+interval;return;}
            if(request==null)return;
            org.json.JSONArray mapping=new org.json.JSONArray();for(int i=0;i<selected.size();i++)mapping.put(JevRegistry.merge(structure(selected.get(i)),
                JevRegistry.fields("candidate_id","c"+(i+1),"review_reason",reasons.get(i),"decision_evidence",new ReviewEvidence(selected.get(i)).json())));
            runtime.save("request-"+requestId,JevRegistry.fields("request_id",requestId,"abstraction_version",abstraction,"generation",generation,
                "evidence_digest",request.evidenceDigest,"gui_identity",JevEvidence.guiIdentity(tree),"candidates",mapping,"request",new org.json.JSONObject(request.bodyJson)));
            JevTransport.Call call=transport.submit(requestId,request.bodyJson,timeoutMs,Math.max(16384,maxBytes));
            events.emit("REGION_REVIEW_REQUEST",step,abstraction,state.getGraphId(),null,JevRegistry.fields("request_id",requestId,"candidate_count",selected.size(),"request_bytes",request.requestBytes,"generation",generation));
            if(call==null){nextReview=System.nanoTime()+interval;events.emit("REGION_REVIEW_RESULT",step,abstraction,state.getGraphId(),null,JevRegistry.fields("request_id",requestId,"reason","QUEUE_FULL","wait_ns",0));return;}
            modelInvocations++;
            for(int i=0;i<selected.size();i++){Keep keep=matchingKeep(selected.get(i));if(keep!=null){keepRereviews++;
                keepEvent("REGION_KEEP_REREVIEW",keep,JevRegistry.merge(structure(selected.get(i)),JevRegistry.fields("request_id",requestId,"candidate_id","c"+(i+1),"reason",reasons.get(i))));}}
            review=new Review(request,call,selected,tree,abstraction,generation);
        }catch(Exception failure){
            nextReview=System.nanoTime()+interval;if(failure instanceof java.io.IOException)runtime.receiptFailed(failure);
            org.json.JSONObject error=JevRegistry.fields("request_id",requestId,"phase","construction","region_auto",auto,
                "exception",failure.getClass().getSimpleName(),"message",String.valueOf(failure.getMessage()),
                "concrete_id",constructing==null?org.json.JSONObject.NULL:constructing.id);
            events.emit("REGION_REVIEW_ERROR",step,abstraction,state.getGraphId(),constructing==null?null:constructing.action.getGraphId(),error);
            if(auto && autoDraft!=null)try {
                runtime.save("region-auto-rejected-"+requestId,JevRegistry.fields("step",step,"error",error,"candidate",autoDraft));
            }catch(java.io.IOException receiptFailure){runtime.receiptFailed(receiptFailure);}
            event("REGION_FAILED","review_construction_failed");
        }
    }
    private void reviewResult(String reason,JevTransport.Result result){events.emit("REGION_REVIEW_RESULT",step,abstraction,review.tree.getCurrentState().getGraphId(),null,
        JevRegistry.fields("request_id",review.request.requestId,"reason",reason,"wait_ns",result.waitNanos(),"latency_ns",result.latencyNanos(),"http_status",result.httpStatus));}
    private void pollReview() {
        if(review==null)return;JevTransport.Result response=review.call.poll();if(response==null)return;
        try {
            if(response.error!=null){reviewResult(response.error,response);nextReview=System.nanoTime()+interval;return;}
            runtime.save("provider-"+review.request.requestId,JevRegistry.fields("request_id",review.request.requestId,
                "abstraction_version",review.abstraction,"evidence_digest",review.request.evidenceDigest,
                "raw_response",response.raw,"wait_ns",response.waitNanos(),"http_status",response.httpStatus));
            JevRegionProtocol.Result result=JevRegionProtocol.fromProvider(response.raw,review.request);
            runtime.save("response-"+review.request.requestId,JevRegistry.fields("request_id",review.request.requestId,"abstraction_version",review.abstraction,
                "evidence_digest",review.request.evidenceDigest,"raw_response",new org.json.JSONObject(response.raw),"response",result.toJson(),"wait_ns",response.waitNanos()));
            for(int i=0;i<review.buckets.size();i++) {
                Bucket bucket=review.buckets.get(i);JevRegionProtocol.Decision decision=result.decisions.get("c"+(i+1));
                if(!alreadySuppressed(bucket))installRule(bucket,JevEvidence.guiIdentity(bucket.anchor.getGUITree()),decision,review.generation,review.abstraction,review.request.requestId);
            }
            nextReview=System.nanoTime()+interval;
            reviewResult("success",response);event("REGION_INSTALLED","review_applied");
        }catch(Exception failure){
            if(failure instanceof java.io.IOException)runtime.receiptFailed(failure);
            events.emit("REGION_REVIEW_ERROR",step,abstraction,null,null,JevRegistry.fields("request_id",review.request.requestId,
                "exception",failure.getClass().getSimpleName(),"message",String.valueOf(failure.getMessage())));
            reviewResult("INVALID_RESPONSE_OR_RECEIPT",response);nextReview=System.nanoTime()+interval;
        }
        finally{review=null;}
    }
    public boolean gate(ModelAction action,GUITree tree,String strategy,long timestamp) {
        long started=System.nanoTime();
        try{return gateNow(action,tree,strategy,timestamp);}
        finally{timing.record("region_gate",System.nanoTime()-started);}
    }
    private boolean gateNow(ModelAction action,GUITree tree,String strategy,long timestamp) {
        step=timestamp;if(sampleStep!=step){finishFallback(null);sampled.clear();sampleStep=step;}
        if(!action.requireTarget() || rules.isEmpty())return true;
        Rule rule=matchingRule(action,tree,action.getResolvedNode());if(rule==null)return true;
        Boolean previous=sampled.get(rule.concreteId);if(previous!=null)return !previous;
        double p=rule.pSkip;Double draw=p>0 && p<1?random.nextDouble():null;boolean skip=p==1 || draw!=null && draw<p;
        sampled.put(rule.concreteId,skip);
        rule.gates++;if(rule.firstGateStep<0)rule.firstGateStep=step;
        Attempt selected=new Attempt(rule,action.getResolvedGUITreeAction(),strategy,step,"region-a"+(++attemptSequence),abstraction);
        ruleEvent("REGION_GATE_ATTEMPT",rule,JevRegistry.merge(attemptFields(selected),JevRegistry.fields(
            "sample",draw==null?org.json.JSONObject.NULL:draw,"skipped",skip)));
        if(skip)skippedForFallback.add(selected);
        else attempt=selected;
        return !skip;
    }
    private void shadowEvent(String name,Shadow shadow,org.json.JSONObject fields) {
        keepEvent(name,shadow.keep,JevRegistry.merge(JevRegistry.fields("shadow_observation_id",shadow.id,"selection_step",shadow.step,
            "selection_version",shadow.version,"source_state",shadow.tree.getCurrentState().getGraphId()),fields));
    }
    /** One observation per native selection round; no binding, sampling or mutation of APE actions. */
    private void observeShadow(State state,GUITree tree) {
        finishShadowSelection(null,"selection_not_observed");
        if(!shadowEnabled)return;
        for(Keep keep:keeps) {
            ModelAction action=keep.anchor.anchor.getModelAction();
            boolean inGroup=false;for(ModelAction candidate:state.getActions())if(candidate==action){inGroup=true;break;}
            JevRegionMatcher.Match match=JevRegionMatcher.match(keep.anchor.anchor,tree,inGroup?action:null);
            if(!match.source){keep.sourceMisses++;continue;} // Aggregate source_state_mismatch; detailed near misses start at the matched State.
            keep.sourceMatches++;if(match.action)keep.actionMatches++;
            boolean available=false;
            if(match.action && action.getResolvedGUITree()==tree && action.getResolvedNodes()!=null)
                for(GUITreeNode node:action.getResolvedNodes())if(match.exact(node)){available=true;break;}
            // Exact is the production identity predicate. Availability is a separate observation.
            boolean exact=match.concrete!=null;
            Shadow shadow=new Shadow(keep,tree,action,match.concrete,step,abstraction,"region-s"+(++shadowSequence));
            if(exact){keep.exactMatches++;shadowRound.add(shadow);}
            shadowEvent("REGION_KEEP_SHADOW_MATCH",shadow,JevRegistry.fields("source_match",true,"model_action_match",match.action,
                "exact_match",exact,"reason",match.reason,"concrete_available",available,
                "concrete_enabled",match.concrete!=null && match.concrete.isEnabled(),"action_enabled_valid",match.action && action.isEnabled() && action.isValid()));
        }
    }
    private void finishShadowSelection(Action selected,String reason) {
        for(Shadow shadow:shadowRound) {
            boolean exact=selected instanceof ModelAction && selected==shadow.action
                && ((ModelAction)selected).getResolvedGUITree()==shadow.tree
                && JevRegionMatcher.match(shadow.keep.anchor.anchor,shadow.tree,shadow.action).exact(((ModelAction)selected).getResolvedNode());
            shadowEvent("REGION_KEEP_SHADOW_SELECTION",shadow,JevRegistry.fields("shadow_selected",exact,"reason",reason));
            if(exact){shadow.keep.selected++;shadow.selected=shadow.action.getResolvedGUITreeAction();shadowInputs.add(shadow);}
        }
        shadowRound.clear();
    }
    public void beforeInput(Action action) {
        if(locator!=null)locator.beforeInput(action,step);
        if(action instanceof ModelAction)nativeActions++;
        finishShadowSelection(action,"final_action_before_input");
        finishFallback(action);
        if(attempt==null)return;
        if(!(action instanceof ModelAction) || ((ModelAction)action).getResolvedGUITreeAction()!=attempt.action){interrupted("selection_replaced");return;}
        ruleEvent("REGION_INPUT_STARTED",attempt.rule,attemptFields(attempt));
    }
    void interrupted(String reason){if(attempt!=null){ruleEvent("REGION_INPUT_INTERRUPTED",attempt.rule,JevRegistry.merge(attemptFields(attempt),JevRegistry.fields("reason",reason)));attempt=null;}
        for(Iterator<AuditPending> it=auditPending.iterator();it.hasNext();) {AuditPending pending=it.next();
            events.emit("REGION_SHADOW_EXECUTION",step,abstraction,pending.candidate.bucket.action.getState().getGraphId(),pending.candidate.bucket.action.getGraphId(),
                JevRegistry.merge(auditFields(pending.candidate,pending.step,reason,false),JevRegistry.fields("gate_context_id",pending.gateContextId==null?org.json.JSONObject.NULL:pending.gateContextId)));it.remove();}
        for(Shadow shadow:shadowInputs)shadowEvent("REGION_KEEP_SHADOW_EXECUTION",shadow,JevRegistry.fields("input_confirmed",org.json.JSONObject.NULL,"reason",reason));shadowInputs.clear();}
    public void close(){if(closed)return;closed=true;finishFallback(null);interrupted("run_end");if(pending!=null)pending.cancel(true);
        if(locator!=null)locator.close(step);
        finishShadowSelection(null,"run_end");
        if(review!=null){review.call.cancel("CLOSED");reviewResult("CLOSED",review.call.poll());review=null;}worker.shutdownNow();
        long gated=0,gates=0,skips=0,ruleExecutions=0;
        for(Rule rule:rules.values()) {
            if(rule.gates>0)gated++;gates+=rule.gates;skips+=rule.skips;ruleExecutions+=rule.confirmedExecutions;
            ruleEvent("REGION_RULE_TOTAL",rule,JevRegistry.fields("region_auto",auto,"gate_attempts",rule.gates,"actual_skips",rule.skips,
                "gate_confirmed_executions",rule.confirmedExecutions,"first_install_step",rule.firstInstallStep,
                "first_gate_step",rule.firstGateStep<0?org.json.JSONObject.NULL:rule.firstGateStep,
                "first_skip_step",rule.firstSkipStep<0?org.json.JSONObject.NULL:rule.firstSkipStep));
        }
        if(!shadowAudit)for(String source:Arrays.asList("MODEL","BUDGET_FALLBACK")){
            long installed=0,sourceGates=0,sourceSkips=0;
            for(Rule rule:rules.values())if(source.equals(rule.source)){installed++;sourceGates+=rule.gates;sourceSkips+=rule.skips;}
            events.emit("REGION_RULE_SOURCE_TOTAL",step,abstraction,null,null,JevRegistry.fields("rule_source",source,
                "rules_installed",installed,"gate_attempts",sourceGates,"actual_skips",sourceSkips));
        }
        if(!shadowAudit)events.emit("REGION_INTERVENTION_TOTAL",step,abstraction,null,null,JevRegistry.fields("region_auto",auto,
            "eligible_candidate_occurrences",eligibleOccurrences,"unique_eligible_candidates",eligibleCandidates.size(),
            "auto_rules_installed",auto?rules.size():0,"rules_installed",rules.size(),"duplicate_installs_avoided",duplicateInstallsAvoided,
            "rules_with_gate",gated,"gate_attempts",gates,"actual_skips",skips,"gate_confirmed_executions",ruleExecutions,"model_invocations",modelInvocations));
        long source=0,action=0,exact=0,selected=0,executed=0;
        for(Keep keep:keeps){source+=keep.sourceMatches;action+=keep.actionMatches;exact+=keep.exactMatches;selected+=keep.selected;executed+=keep.executed;
            keepEvent("REGION_KEEP_SHADOW_TOTAL",keep,JevRegistry.fields("shadow_source_misses",keep.sourceMisses,"shadow_source_matches",keep.sourceMatches,"shadow_action_matches",keep.actionMatches,
                "shadow_exact_matches",keep.exactMatches,"shadow_selected",keep.selected,"shadow_executed",keep.executed));}
        if(!shadowAudit)events.emit("REGION_KEEP_TOTAL",step,abstraction,null,null,JevRegistry.fields("model_invocations",modelInvocations,"keep_cache_hits",keepCacheHits,"keep_rereviews",keepRereviews,
            "keep_candidates",keeps.size(),"shadow_source_matches",source,"shadow_action_matches",action,"shadow_exact_matches",exact,"shadow_selected",selected,"shadow_executed",executed,"suppress_rules",rules.size(),"native_actions_so_far",nativeActions));
        if(shadowAudit && locator==null) {
            long nominations=0,exactAvailable=0,modelSelected=0,wouldGate=0,shadowExecuted=0,singleton=0,multi=0;
            for(AuditCandidate candidate:auditCandidates.values()) {
                nominations+=1+candidate.repeatNominations;exactAvailable+=candidate.futureExactAvailability;modelSelected+=candidate.modelActionSelected;
                wouldGate+=candidate.wouldGate;shadowExecuted+=candidate.confirmedExecuted;
                int size=candidate.firstStructure.optInt("region_state_count",0);if(size<=1)singleton++;else multi++;
                events.emit("REGION_SHADOW_CANDIDATE_TOTAL",step,abstraction,candidate.bucket.action.getState().getGraphId(),candidate.bucket.action.getGraphId(),
                    JevRegistry.merge(auditFields(candidate,step,"candidate_total",false),JevRegistry.fields("repeat_nomination_count",candidate.repeatNominations,
                        "future_exact_availability_count",candidate.futureExactAvailability,"future_model_action_selection_count",candidate.modelActionSelected,
                        "future_would_gate_count",candidate.wouldGate,"future_confirmed_execution_count",candidate.confirmedExecuted,
                        "future_execution_zero",candidate.confirmedExecuted==0,"would_gate_zero",candidate.wouldGate==0,
                        "resolved_node_count",candidate.firstResolvedNodeCount)));
            }
            events.emit("REGION_SHADOW_TOTAL",step,abstraction,null,null,JevRegistry.fields("shadow_audit",true,"unique_candidates",auditCandidates.size(),
                "nomination_occurrences",nominations,"future_exact_availability_count",exactAvailable,"future_model_action_selection_count",modelSelected,
                "future_would_gate_count",wouldGate,"future_confirmed_execution_count",shadowExecuted,"singleton_first_candidates",singleton,"multi_state_first_candidates",multi,
                "model_invocations",0,"suppress_rules",0,"gate_attempts",0,"actual_skips",0));
        }
        event("REGION_CLOSED","run_end");}
}
