/*
===============================================================================
DETERMINISTIC BLACKJACK DECISION ENGINE RESEARCH
PHASE 3.55 - TIER ENTRY, RETENTION AND ATTRITION

CODE VERSION: Deterministic Blackjack Phase 3.55
CODE BUILD TAG: DBDE-3.55-TIER-RETENTION-ATTRITION

RESEARCH DIRECTOR: Amit Amlani
PUBLICATION AUTHOR: ChatGPT (OpenAI)

STATUS
Exploratory bankroll-risk / player-behaviour simulation. This phase does not
claim that any next hand is more likely to win.

QUESTION
Phase 3.38 found that cooling earlier than <=100 creates many more rebuilds
without improving 2x target attainment. Phase 3.39 therefore freezes the
strongest cooling point at <=100 and changes only how far the bankroll must
recover before the player leaves stable 10-unit rebuilding and resumes the
profit-spending/aggressive ladder: >=120, >=130 or >=140.
===============================================================================
*/
import java.io.*;
import java.nio.charset.StandardCharsets;
import java.nio.file.*;
import java.security.MessageDigest;
import java.util.*;

public final class DeterministicBlackjackPhase3164 {
    static final int SESSIONS = 5000;
    static final int HANDS = 30;
    static final int SHOE_SIZE = 312;
    static final double START_BANK = 100.0;
    static final double BASE_WAGER = 1.0;
    static final long PRIMARY_SEED = 202201L;
    static final long HOLDOUT_SEED = 202301L;

    enum Action { HIT, STAND, DOUBLE, SPLIT, NONE }
    enum Profile { TEXTBOOK, MAXIMUM_AGGRESSION, SELECTIVE_AGGRESSION }

    static final class Hand {
        final ArrayList<Integer> c = new ArrayList<>();
        int total(){int t=0,a=0;for(int v:c){t+=v;if(v==11)a++;}while(t>21&&a-->0)t-=10;return t;}
        boolean soft(){int raw=0;for(int v:c)raw+=v;return c.contains(11)&&raw==total();}
        boolean bust(){return total()>21;}
        boolean blackjack(){return c.size()==2&&total()==21;}
        int cards(){return c.size();}
    }

    static final class ResolvedHandRecord {
        String cohort;
        int session, round, child;
        boolean splitChild;
        int startTotal;
        boolean startSoft;
        int dealerUp;
        String visibleClass;
        Action textbookAction;
        Action aggressiveAction;
        int startCards;
        int endCards;
        int dealerEndCards;
        boolean playerNaturalBlackjack;
        boolean dealerNaturalBlackjack;
        double baseWager;
        boolean selectiveWagerTrigger;
        double stake;
        boolean thirdCardTaken;
        boolean thirdCardBust;
        boolean bust;
        String outcome;
        double net;
        int startPointer, endPointer;

        String csv(){
            return String.join(",",
                cohort,Integer.toString(session),Integer.toString(round),Integer.toString(child),Boolean.toString(splitChild),
                Integer.toString(startTotal),Boolean.toString(startSoft),Integer.toString(dealerUp),visibleClass,
                textbookAction.toString(),aggressiveAction.toString(),Integer.toString(startCards),Integer.toString(endCards),Integer.toString(dealerEndCards),Boolean.toString(playerNaturalBlackjack),Boolean.toString(dealerNaturalBlackjack),String.format(Locale.ROOT,"%.1f",baseWager),Boolean.toString(selectiveWagerTrigger),
                String.format(Locale.ROOT,"%.1f",stake),Boolean.toString(thirdCardTaken),Boolean.toString(thirdCardBust),
                Boolean.toString(bust),outcome,String.format(Locale.ROOT,"%.1f",net),Integer.toString(startPointer),Integer.toString(endPointer));
        }
    }

    static final class Round {
        double net;
        int endPointer;
        int doubles, splits;
        boolean win, loss, push;
        final ArrayList<ResolvedHandRecord> records = new ArrayList<>();
    }

    static final class PairStats {
        long sessions,tPositive,tNegative,tEven,aPositive,aNegative,aEven,tHigher,aHigher,tie;
        long t2x,t3x,t5x,t10x,a2x,a3x,a5x,a10x;
        double tEnd,aEnd;
        long tWins,tLosses,tPushes,aWins,aLosses,aPushes,tDoubles,aDoubles,tSplits,aSplits;
        long diverged,firstDivSum,sameStart,totalStarts,finalSame,tAhead,aAhead,absDiff,tCards,aCards;
        int maxT,maxA;
        double maxBankT=START_BANK,maxBankA=START_BANK;
        int maxBankTHand=0,maxBankAHand=0,maxBankTSession=0,maxBankASession=0;
        final double[] aBankAfterHand=new double[HANDS+1];
        double sumSessionPeakA,sumMaxDrawdownA;
        long aEver2x,aEver3x,aEver5x,aEver10x,aEverZero,aBankruptcyStops,aHandsSkippedAfterBankruptcy;
        long affordabilityBaseStops,affordabilitySplitFallbacks,affordabilityDoubleFallbacks;
        long selective2xTriggers,selective2xWins,selective2xLosses,selective2xPushes;
        double selective2xNet;
        final long[] peakHandCountsA=new long[HANDS+1];
    }

    static final class StateKey implements Comparable<StateKey> {
        final int total, up;
        final boolean soft, splitChild;
        final Action textbook;
        StateKey(int total,boolean soft,int up,boolean splitChild,Action textbook){this.total=total;this.soft=soft;this.up=up;this.splitChild=splitChild;this.textbook=textbook;}
        public int compareTo(StateKey o){int c=Integer.compare(total,o.total);if(c!=0)return c;c=Boolean.compare(soft,o.soft);if(c!=0)return c;c=Integer.compare(up,o.up);if(c!=0)return c;c=Boolean.compare(splitChild,o.splitChild);if(c!=0)return c;return textbook.compareTo(o.textbook);}
        public boolean equals(Object x){if(!(x instanceof StateKey))return false;StateKey o=(StateKey)x;return total==o.total&&soft==o.soft&&up==o.up&&splitChild==o.splitChild&&textbook==o.textbook;}
        public int hashCode(){return Objects.hash(total,soft,up,splitChild,textbook);}
        String label(){return (soft?"SOFT ":"HARD ")+total+" vs "+up+(splitChild?" [split-child]":"")+" | textbook="+textbook;}
    }

    static final class StateAgg {
        long hands,wins,losses,pushes,busts,thirdTaken,thirdBust;
        double net,stake;
        void add(ResolvedHandRecord r){hands++;net+=r.net;stake+=r.stake;if(r.outcome.equals("WIN"))wins++;else if(r.outcome.equals("LOSS"))losses++;else pushes++;if(r.bust)busts++;if(r.thirdCardTaken)thirdTaken++;if(r.thirdCardBust)thirdBust++;}
    }

    static long sessionSeed(long cohort,int s){return cohort+s*1_000_003L;}

    static int[] sixDeckShoe(long seed){
        int[] a=new int[SHOE_SIZE];int p=0;
        for(int deck=0;deck<6;deck++)for(int suit=0;suit<4;suit++)for(int rank=1;rank<=13;rank++)a[p++]=rank==1?11:Math.min(rank,10);
        Random r=new Random(seed);
        for(int i=a.length-1;i>0;i--){int j=r.nextInt(i+1);int t=a[i];a[i]=a[j];a[j]=t;}
        return a;
    }

    static boolean auditShoe(int[] s){
        if(s.length!=312)return false;int ace=0,ten=0;int[] n=new int[10];
        for(int v:s){if(v==11)ace++;else if(v==10)ten++;else if(v>=2&&v<=9)n[v]++;else return false;}
        if(ace!=24||ten!=96)return false;for(int v=2;v<=9;v++)if(n[v]!=24)return false;return true;
    }

    static int draw(int[] shoe,int[] ptr){if(ptr[0]>=shoe.length)throw new IllegalStateException("Shoe exhausted at pointer "+ptr[0]);return shoe[ptr[0]++];}

    static Action textbook(Hand h,int up,boolean first){
        int t=h.total();
        if(h.soft()){
            if(t>=19)return Action.STAND;
            if(t==18)return first&&up>=3&&up<=6?Action.DOUBLE:(up>=9?Action.HIT:Action.STAND);
            if(t==17)return first&&up>=3&&up<=6?Action.DOUBLE:Action.HIT;
            if(t==15||t==16)return first&&up>=4&&up<=6?Action.DOUBLE:Action.HIT;
            if(t==13||t==14)return first&&up>=5&&up<=6?Action.DOUBLE:Action.HIT;
            return Action.HIT;
        }
        if(t>=17)return Action.STAND;
        if(t>=13)return up>=2&&up<=6?Action.STAND:Action.HIT;
        if(t==12)return up>=4&&up<=6?Action.STAND:Action.HIT;
        if(t==11)return first&&up!=11?Action.DOUBLE:Action.HIT;
        if(t==10)return first&&up<=9?Action.DOUBLE:Action.HIT;
        if(t==9)return first&&up>=3&&up<=6?Action.DOUBLE:Action.HIT;
        return Action.HIT;
    }

    static boolean textbookSplit(int pair,int up){
        if(pair==11||pair==8)return true;if(pair==10||pair==5)return false;
        if(pair==9)return up!=7&&up!=10&&up!=11;if(pair==7)return up>=2&&up<=7;
        if(pair==6)return up>=2&&up<=6;if(pair==4)return up==5||up==6;
        if(pair==3||pair==2)return up>=2&&up<=7;return false;
    }

    // Rank normalisation collapses 10/J/Q/K to value 10. Therefore K+J,
    // Q+10, J+K, etc. arrive here as 10+10 and are split-eligible.
    static boolean splitEligibleByStoredValue(int firstCard,int secondCard){
        return firstCard==secondCard;
    }

    static String tenValueSplitAudit(){
        int ten=10,jack=10,queen=10,king=10;
        int[][] cases={{ten,jack},{ten,queen},{ten,king},{jack,queen},{jack,king},{queen,king},{ten,ten},{jack,jack},{queen,queen},{king,king}};
        String[] names={"10+J","10+Q","10+K","J+Q","J+K","Q+K","10+10","J+J","Q+Q","K+K"};
        StringBuilder b=new StringBuilder("PHASE 3.02 — TEN-VALUE SPLIT PRECEDENCE AUDIT\n\n");
        b.append("Stored-card rule: 10, J, Q and K are all represented as integer value 10.\n");
        b.append("Maximum Aggression precedence: SPLIT eligible initial pair first; only otherwise consider first-decision DOUBLE.\n\n");
        for(int i=0;i<cases.length;i++){
            boolean ok=splitEligibleByStoredValue(cases[i][0],cases[i][1]);
            b.append(String.format(Locale.ROOT,"%-5s -> stored %d+%d -> split eligible = %s -> first aggressive action = %s%n",
                    names[i],cases[i][0],cases[i][1],ok,ok?"SPLIT":"DOUBLE-CANDIDATE"));
            if(!ok)throw new IllegalStateException("Ten-value split audit failed for "+names[i]);
        }
        b.append("\nRESULT: PASS — every two-card ten-value combination is routed to SPLIT before DOUBLE.\n");
        return b.toString();
    }

    static String visibleClass(Hand h){return h.soft()?"SOFT":"HARD";}

    static Round playRound(int[] shoe,int pointer,Profile profile,String cohort,int session,int round,boolean capture,double availableBank,PairStats stats,double wager,boolean selectiveWagerTrigger){
        int[] ptr={pointer};Hand initial=new Hand(),dealer=new Hand();
        initial.c.add(draw(shoe,ptr));dealer.c.add(draw(shoe,ptr));initial.c.add(draw(shoe,ptr));dealer.c.add(draw(shoe,ptr));
        boolean pair=splitEligibleByStoredValue(initial.c.get(0),initial.c.get(1));
        boolean aggressive=profile==Profile.MAXIMUM_AGGRESSION||profile==Profile.SELECTIVE_AGGRESSION;
        boolean selective=profile==Profile.SELECTIVE_AGGRESSION;
        boolean wantsSplit=pair&&(aggressive||textbookSplit(initial.c.get(0),dealer.c.get(0)));
        if(capture && wantsSplit && availableBank < 2.0*wager){
            wantsSplit=false;
            if(stats!=null)stats.affordabilitySplitFallbacks++;
        }
        ArrayList<Hand> hands=new ArrayList<>();ArrayList<Double> stakes=new ArrayList<>();ArrayList<Boolean> natural=new ArrayList<>();ArrayList<Boolean> splitChild=new ArrayList<>();
        Round out=new Round();
        if(wantsSplit){
            out.splits=1;Hand a=new Hand(),b=new Hand();a.c.add(initial.c.get(0));b.c.add(initial.c.get(1));a.c.add(draw(shoe,ptr));b.c.add(draw(shoe,ptr));
            hands.add(a);hands.add(b);stakes.add(wager);stakes.add(wager);natural.add(false);natural.add(false);splitChild.add(true);splitChild.add(true);
        } else {hands.add(initial);stakes.add(wager);natural.add(true);splitChild.add(false);}

        double committedCapital=hands.size()*wager;
        for(int i=0;i<hands.size();i++){
            Hand h=hands.get(i);boolean first=true;
            ResolvedHandRecord rec=null;
            if(capture){
                rec=new ResolvedHandRecord();rec.cohort=cohort;rec.session=session;rec.round=round;rec.child=i+1;rec.splitChild=splitChild.get(i);
                rec.startTotal=h.total();rec.startSoft=h.soft();rec.dealerUp=dealer.c.get(0);rec.visibleClass=visibleClass(h);rec.startCards=h.cards();rec.baseWager=wager;rec.selectiveWagerTrigger=selectiveWagerTrigger;rec.playerNaturalBlackjack=(!splitChild.get(i)&&h.blackjack());rec.dealerNaturalBlackjack=dealer.blackjack();rec.startPointer=ptr[0];
                if(h.blackjack()||dealer.blackjack()){rec.textbookAction=Action.NONE;rec.aggressiveAction=Action.NONE;}
                else {rec.textbookAction=textbook(h,dealer.c.get(0),true);rec.aggressiveAction=aggressive?((selective&&h.soft()&&h.total()>=18&&h.total()<=19&&dealer.c.get(0)>=9)?textbook(h,dealer.c.get(0),true):(selective&&!h.soft()&&h.total()>=5&&h.total()<=8&&dealer.c.get(0)>=2&&dealer.c.get(0)<=6)?Action.HIT:(selective&&!h.soft()&&h.total()==9&&dealer.c.get(0)>=9)?Action.HIT:(selective&&!h.soft()&&h.total()==12&&dealer.c.get(0)>=2&&dealer.c.get(0)<=6)?textbook(h,dealer.c.get(0),true):(selective&&h.soft()&&h.total()>=13&&h.total()<=17&&dealer.c.get(0)>=7)?Action.HIT:(selective&&!h.soft()&&h.total()==13&&dealer.c.get(0)>=2&&dealer.c.get(0)<=6)?Action.STAND:(selective&&!h.soft()&&h.total()==8&&dealer.c.get(0)>=7)?Action.HIT:(selective&&!h.soft()&&h.total()>=12&&h.total()<=13&&dealer.c.get(0)>=7)?Action.HIT:(selective&&!h.soft()&&h.total()>=5&&h.total()<=7&&dealer.c.get(0)>=7)?Action.HIT:(selective&&!h.soft()&&h.total()==14)?textbook(h,dealer.c.get(0),true):(selective&&!h.soft()&&h.total()==15)?textbook(h,dealer.c.get(0),true):(selective&&!h.soft()&&h.total()==16)?textbook(h,dealer.c.get(0),true):(selective&&!h.soft()&&splitChild.get(i)&&h.total()==16&&dealer.c.get(0)==11)?Action.HIT:((selective&&!h.soft()&&(h.total()==19||h.total()==18||h.total()==17||(splitChild.get(i)&&h.total()==20)))?Action.STAND:Action.DOUBLE)):rec.textbookAction;}
            }
            if(!h.blackjack()&&!dealer.blackjack())while(!h.bust()){
                Action a=(aggressive&&first)?((selective&&h.soft()&&h.total()>=18&&h.total()<=19&&dealer.c.get(0)>=9)?textbook(h,dealer.c.get(0),true):(selective&&!h.soft()&&h.total()>=5&&h.total()<=8&&dealer.c.get(0)>=2&&dealer.c.get(0)<=6)?Action.HIT:(selective&&!h.soft()&&h.total()==9&&dealer.c.get(0)>=9)?Action.HIT:(selective&&!h.soft()&&h.total()==12&&dealer.c.get(0)>=2&&dealer.c.get(0)<=6)?textbook(h,dealer.c.get(0),true):(selective&&h.soft()&&h.total()>=13&&h.total()<=17&&dealer.c.get(0)>=7)?Action.HIT:(selective&&!h.soft()&&h.total()==13&&dealer.c.get(0)>=2&&dealer.c.get(0)<=6)?Action.STAND:(selective&&!h.soft()&&h.total()==8&&dealer.c.get(0)>=7)?Action.HIT:(selective&&!h.soft()&&h.total()>=12&&h.total()<=13&&dealer.c.get(0)>=7)?Action.HIT:(selective&&!h.soft()&&h.total()>=5&&h.total()<=7&&dealer.c.get(0)>=7)?Action.HIT:(selective&&!h.soft()&&h.total()==14)?textbook(h,dealer.c.get(0),true):(selective&&!h.soft()&&h.total()==15)?textbook(h,dealer.c.get(0),true):(selective&&!h.soft()&&h.total()==16)?textbook(h,dealer.c.get(0),true):(selective&&!h.soft()&&splitChild.get(i)&&h.total()==16&&dealer.c.get(0)==11)?Action.HIT:((selective&&!h.soft()&&(h.total()==19||h.total()==18||h.total()==17||(splitChild.get(i)&&h.total()==20)))?Action.STAND:Action.DOUBLE)):textbook(h,dealer.c.get(0),first);
                if(a==Action.STAND)break;
                if(a==Action.DOUBLE&&first){
                    if(capture && availableBank < committedCapital + wager){
                        if(stats!=null)stats.affordabilityDoubleFallbacks++;
                        Action fb=textbook(h,dealer.c.get(0),true);
                        if(fb==Action.DOUBLE)fb=Action.HIT;
                        if(fb==Action.STAND)break;
                        h.c.add(draw(shoe,ptr));first=false;
                        continue;
                    }
                    stakes.set(i,stakes.get(i)*2.0);committedCapital+=wager;out.doubles++;h.c.add(draw(shoe,ptr));break;
                }
                h.c.add(draw(shoe,ptr));first=false;
            }
            if(capture){rec.endCards=h.cards();rec.stake=stakes.get(i);rec.thirdCardTaken=rec.startCards==2&&rec.endCards>=3;rec.thirdCardBust=rec.thirdCardTaken&&h.bust()&&rec.endCards==3;rec.bust=h.bust();rec.endPointer=ptr[0];out.records.add(rec);}
        }

        boolean live=false;for(Hand h:hands)if(!h.bust())live=true;
        if(live&&!dealer.blackjack())while(dealer.total()<17)dealer.c.add(draw(shoe,ptr));
        for(int i=0;i<hands.size();i++){
            Hand p=hands.get(i);double st=stakes.get(i),net;
            if(natural.get(i)&&p.blackjack()&&!dealer.blackjack())net=1.5*st;
            else if(natural.get(i)&&dealer.blackjack()&&!p.blackjack())net=-st;
            else if(natural.get(i)&&p.blackjack()&&dealer.blackjack())net=0;
            else if(p.bust())net=-st;
            else if(dealer.bust()||p.total()>dealer.total())net=st;
            else if(p.total()<dealer.total())net=-st;
            else net=0;
            out.net+=net;
            if(capture){ResolvedHandRecord rec=out.records.get(i);rec.net=net;rec.outcome=net>0?"WIN":net<0?"LOSS":"PUSH";rec.dealerEndCards=dealer.cards();rec.endPointer=ptr[0];}
        }
        out.win=out.net>0;out.loss=out.net<0;out.push=out.net==0;out.endPointer=ptr[0];return out;
    }

    static PairStats paired(long cohort,String name,List<ResolvedHandRecord> records){
        PairStats s=new PairStats();
        for(int session=0;session<SESSIONS;session++){
            int[] shoe=sixDeckShoe(sessionSeed(cohort,session));if(!auditShoe(shoe))throw new IllegalStateException("Inventory audit failed");
            int pt=0,pa=0;double bt=START_BANK,ba=START_BANK;boolean div=false;int first=0;long tw=0,tl=0,tp=0,aw=0,al=0,ap=0,td=0,ad=0,ts=0,as=0;
            double sessionPeakA=START_BANK,maxDrawdownA=0.0;int sessionPeakHandA=0;
            boolean ever2xA=false,ever3xA=false,ever5xA=false,ever10xA=false,everZeroA=false;
            boolean selectiveStopped=false;
            ArrayDeque<Double> last3UnitNet=new ArrayDeque<>();
            for(int h=1;h<=HANDS;h++){
                Round rt=playRound(shoe,pt,Profile.TEXTBOOK,name,session+1,h,false,Double.POSITIVE_INFINITY,null,BASE_WAGER,false);
                pt=rt.endPointer;bt+=rt.net;
                if(rt.win)tw++;else if(rt.loss)tl++;else tp++;td+=rt.doubles;ts+=rt.splits;
                if(bt>s.maxBankT){s.maxBankT=bt;s.maxBankTHand=h;s.maxBankTSession=session+1;}

                double recent3=0.0;
                for(double v:last3UnitNet)recent3+=v;
                double drawdownBefore=Math.max(0.0,sessionPeakA-ba);
                boolean wagerTrigger=false;
                double currentWager=BASE_WAGER;
                if(!selectiveStopped){
                    if(ba < currentWager){
                        selectiveStopped=true;s.affordabilityBaseStops++;
                        s.aHandsSkippedAfterBankruptcy+=(HANDS-h+1);
                    }
                }
                if(!selectiveStopped){
                    s.totalStarts++;if(pt==pa)s.sameStart++;
                    Round ra=playRound(shoe,pa,Profile.SELECTIVE_AGGRESSION,name,session+1,h,true,ba,s,currentWager,wagerTrigger);
                    records.addAll(ra.records);
                    pa=ra.endPointer;ba+=ra.net;
                    if(wagerTrigger){
                        s.selective2xTriggers++;s.selective2xNet+=ra.net;
                        if(ra.win)s.selective2xWins++;else if(ra.loss)s.selective2xLosses++;else s.selective2xPushes++;
                    }
                    double unitEquivalentNet=ra.net/currentWager;
                    last3UnitNet.addLast(unitEquivalentNet);
                    if(last3UnitNet.size()>3)last3UnitNet.removeFirst();
                    if(ra.win)aw++;else if(ra.loss)al++;else ap++;ad+=ra.doubles;as+=ra.splits;
                    if(ba>sessionPeakA){sessionPeakA=ba;sessionPeakHandA=h;}
                    maxDrawdownA=Math.max(maxDrawdownA,sessionPeakA-ba);
                    if(ba>=200)ever2xA=true;if(ba>=300)ever3xA=true;if(ba>=500)ever5xA=true;if(ba>=1000)ever10xA=true;
                    if(ba<=0){
                        everZeroA=true;selectiveStopped=true;s.aBankruptcyStops++;
                        s.aHandsSkippedAfterBankruptcy+=(HANDS-h);
                        ba=0.0;
                    }
                    if(ba>s.maxBankA){s.maxBankA=ba;s.maxBankAHand=h;s.maxBankASession=session+1;}
                    if(!div&&pt!=pa){div=true;first=h;}
                }
                s.aBankAfterHand[h]+=ba;
            }
            if(pt>SHOE_SIZE||pa>SHOE_SIZE)throw new IllegalStateException("Shoe exhausted");
            s.sessions++;s.tEnd+=bt;s.aEnd+=ba;s.tWins+=tw;s.tLosses+=tl;s.tPushes+=tp;s.aWins+=aw;s.aLosses+=al;s.aPushes+=ap;s.tDoubles+=td;s.aDoubles+=ad;s.tSplits+=ts;s.aSplits+=as;
            if(bt>START_BANK)s.tPositive++;else if(bt<START_BANK)s.tNegative++;else s.tEven++;if(ba>START_BANK)s.aPositive++;else if(ba<START_BANK)s.aNegative++;else s.aEven++;
            if(bt>=200)s.t2x++;if(bt>=300)s.t3x++;if(bt>=500)s.t5x++;if(bt>=1000)s.t10x++;
            if(ba>=200)s.a2x++;if(ba>=300)s.a3x++;if(ba>=500)s.a5x++;if(ba>=1000)s.a10x++;
            if(bt>ba)s.tHigher++;else if(ba>bt)s.aHigher++;else s.tie++;
            s.sumSessionPeakA+=sessionPeakA;s.sumMaxDrawdownA+=maxDrawdownA;s.peakHandCountsA[sessionPeakHandA]++;
            if(ever2xA)s.aEver2x++;if(ever3xA)s.aEver3x++;if(ever5xA)s.aEver5x++;if(ever10xA)s.aEver10x++;if(everZeroA)s.aEverZero++;
            if(div){s.diverged++;s.firstDivSum+=first;}if(pt==pa)s.finalSame++;else if(pt>pa)s.tAhead++;else s.aAhead++;s.absDiff+=Math.abs(pt-pa);s.tCards+=pt;s.aCards+=pa;s.maxT=Math.max(s.maxT,pt);s.maxA=Math.max(s.maxA,pa);
        }
        return s;
    }

    static String pct(long a,long b){return String.format(Locale.ROOT,"%.2f%%",b==0?0:100.0*a/b);}
    static String avg(double a,long b){return String.format(Locale.ROOT,"%.3f",b==0?0:a/b);}

    static String baseline(PairStats p,PairStats h){
        StringBuilder r=new StringBuilder();
        r.append("PHASE 3.35 SECOND INTERVENTION RESULTS\n\n");
        for(Object[] z:new Object[][]{{"PRIMARY",p},{"HOLDOUT",h}}){String n=(String)z[0];PairStats s=(PairStats)z[1];
            r.append(n).append("\n");
            r.append("Average ending bankroll Textbook/Selective: ").append(avg(s.tEnd,s.sessions)).append(" / ").append(avg(s.aEnd,s.sessions)).append("\n");
            r.append(String.format(Locale.ROOT,"Maximum bankroll observed Textbook/Selective: %.1f at hand %d (session %d) / %.1f at hand %d (session %d)%n",
                s.maxBankT,s.maxBankTHand,s.maxBankTSession,s.maxBankA,s.maxBankAHand,s.maxBankASession));
            r.append("Positive sessions Textbook/Selective: ").append(pct(s.tPositive,s.sessions)).append(" / ").append(pct(s.aPositive,s.sessions)).append("\n");
            r.append("Affordability base stops: ").append(s.affordabilityBaseStops).append("\n");
            r.append("Affordability split fallbacks: ").append(s.affordabilitySplitFallbacks).append("\n");
            r.append("Affordability double fallbacks: ").append(s.affordabilityDoubleFallbacks).append("\n");
            r.append("Selective multiplier trigger wagers: ").append(s.selective2xTriggers).append("\n");
            r.append("Selective multiplier trigger net: ").append(String.format(Locale.ROOT,"%.1f",s.selective2xNet)).append("\n");
            r.append("Selective multiplier trigger win/loss/push: ").append(s.selective2xWins).append(" / ").append(s.selective2xLosses).append(" / ").append(s.selective2xPushes).append("\n");
            r.append("Paired winner Textbook/Selective/tie: ").append(pct(s.tHigher,s.sessions)).append(" / ").append(pct(s.aHigher,s.sessions)).append(" / ").append(pct(s.tie,s.sessions)).append("\n");
            r.append("Doubles Textbook/Selective: ").append(s.tDoubles).append(" / ").append(s.aDoubles).append("; splits Textbook/Selective: ").append(s.tSplits).append(" / ").append(s.aSplits).append("\n");
            r.append("Mean cards consumed Textbook/Selective: ").append(avg(s.tCards,s.sessions)).append(" / ").append(avg(s.aCards,s.sessions)).append("\n");
            r.append("Ending bankroll >=2x (200) Textbook/Selective: ").append(pct(s.t2x,s.sessions)).append(" / ").append(pct(s.a2x,s.sessions)).append("\n");
            r.append("Ending bankroll >=3x (300) Textbook/Selective: ").append(pct(s.t3x,s.sessions)).append(" / ").append(pct(s.a3x,s.sessions)).append("\n");
            r.append("Ending bankroll >=5x (500) Textbook/Selective: ").append(pct(s.t5x,s.sessions)).append(" / ").append(pct(s.a5x,s.sessions)).append("\n");
            r.append("Ending bankroll >=10x (1000) Textbook/Selective: ").append(pct(s.t10x,s.sessions)).append(" / ").append(pct(s.a10x,s.sessions)).append("\n\n");
        }
        return r.toString();
    }

    static String multiplierExperiment(PairStats p,PairStats h){
        StringBuilder r=new StringBuilder();
        r.append("PHASE 3.35 BANKRUPTCY-STOP CALIBRATION — CONSTANT 5x\n\n");
        r.append("SCIENTIFIC BOUNDARY\n");
        r.append("Decision policy: frozen Phase 3.20 checkpoint, validated Phase 3.21.\n");
        r.append("Wager architecture: constant 1-unit observational base wager (5x the Phase 3.22 baseline).\n");
        r.append("One multiplier intervention is active: the base wager is five times the Phase 3.22 baseline (5 units).\n\n");
        for(Object[] z:new Object[][]{{"PRIMARY",p},{"HOLDOUT",h}}){
            String n=(String)z[0];PairStats x=(PairStats)z[1];
            r.append(n).append("\n");
            r.append("Mean ending bankroll: ").append(avg(x.aEnd,x.sessions)).append("\n");
            r.append(String.format(Locale.ROOT,"Maximum bankroll observed anywhere: %.1f at Hand %d (session %d)%n",x.maxBankA,x.maxBankAHand,x.maxBankASession));
            r.append(String.format(Locale.ROOT,"Mean per-session peak bankroll: %.3f%n",x.sumSessionPeakA/x.sessions));
            r.append(String.format(Locale.ROOT,"Mean per-session maximum drawdown: %.3f units%n",x.sumMaxDrawdownA/x.sessions));
            r.append("Sessions ever reaching 2x / 3x / 5x / 10x: ")
             .append(pct(x.aEver2x,x.sessions)).append(" / ").append(pct(x.aEver3x,x.sessions)).append(" / ")
             .append(pct(x.aEver5x,x.sessions)).append(" / ").append(pct(x.aEver10x,x.sessions)).append("\n");
            r.append("Sessions ever reaching bankroll <=0: ").append(pct(x.aEverZero,x.sessions)).append("\n");
            int mode=1;for(int i=2;i<=HANDS;i++)if(x.peakHandCountsA[i]>x.peakHandCountsA[mode])mode=i;
            r.append("Sessions whose highest bankroll never exceeded the 100-unit start: ").append(x.peakHandCountsA[0]).append("\n");
            r.append("Most common positive hand-of-peak among sessions that exceeded start: Hand ").append(mode)
             .append(" [").append(x.peakHandCountsA[mode]).append(" sessions]\n\n");
        }
        r.append("MEAN SELECTIVE BANKROLL TRAJECTORY AFTER EACH HAND\n");
        r.append("Hand | Primary | Holdout\n");
        for(int i=1;i<=HANDS;i++)r.append(String.format(Locale.ROOT,"%2d | %8.3f | %8.3f%n",i,p.aBankAfterHand[i]/p.sessions,h.aBankAfterHand[i]/h.sessions));
        return r.toString();
    }

    static Map<StateKey,StateAgg> aggregate(List<ResolvedHandRecord> rr){
        TreeMap<StateKey,StateAgg> m=new TreeMap<>();
        for(ResolvedHandRecord r:rr){if(r.aggressiveAction==Action.NONE)continue;StateKey k=new StateKey(r.startTotal,r.startSoft,r.dealerUp,r.splitChild,r.textbookAction);m.computeIfAbsent(k,x->new StateAgg()).add(r);}return m;
    }

    static Map<StateKey,StateAgg> aggregateDivergent(List<ResolvedHandRecord> rr){
        TreeMap<StateKey,StateAgg> m=new TreeMap<>();
        for(ResolvedHandRecord r:rr){
            if(r.aggressiveAction==Action.NONE || r.aggressiveAction==r.textbookAction)continue;
            StateKey k=new StateKey(r.startTotal,r.startSoft,r.dealerUp,r.splitChild,r.textbookAction);
            m.computeIfAbsent(k,x->new StateAgg()).add(r);
        }
        return m;
    }

    static String stateReport(String cohort,List<ResolvedHandRecord> rr){
        Map<StateKey,StateAgg> m=aggregate(rr);
        ArrayList<Map.Entry<StateKey,StateAgg>> rows=new ArrayList<>(m.entrySet());
        rows.sort((a,b)->{
            double da=a.getValue().net/a.getValue().hands,db=b.getValue().net/b.getValue().hands;
            int c=Double.compare(da,db);if(c!=0)return c;return Long.compare(b.getValue().hands,a.getValue().hands);
        });
        StringBuilder r=new StringBuilder();
        r.append(cohort).append(" — WORST SELECTIVE-AGGRESSION VISIBLE STATES BY MEAN NET/HAND\n");
        r.append("Rank | Visible state | Hands | Mean net | Loss% | Bust% | 3rd-card bust% of 3rd-card draws | Mean stake\n");
        int rank=0;for(Map.Entry<StateKey,StateAgg> e:rows){if(++rank>25)break;StateAgg a=e.getValue();
            r.append(String.format(Locale.ROOT,"%2d | %-45s | %6d | %+7.3f | %6.2f%% | %6.2f%% | %6.2f%% | %.3f%n",
                rank,e.getKey().label(),a.hands,a.net/a.hands,100.0*a.losses/a.hands,100.0*a.busts/a.hands,a.thirdTaken==0?0:100.0*a.thirdBust/a.thirdTaken,a.stake/a.hands));
        }
        r.append("\n");return r.toString();
    }

    static String summary(List<ResolvedHandRecord> p,List<ResolvedHandRecord> h){
        long pHands=p.size(),hHands=h.size();long pThird=0,pThirdBust=0,hThird=0,hThirdBust=0,pBust=0,hBust=0;double pNet=0,hNet=0,pStake=0,hStake=0;
        EnumMap<Action,Long> pTextbook=new EnumMap<>(Action.class),hTextbook=new EnumMap<>(Action.class);for(Action a:Action.values()){pTextbook.put(a,0L);hTextbook.put(a,0L);}
        for(ResolvedHandRecord r:p){if(r.thirdCardTaken)pThird++;if(r.thirdCardBust)pThirdBust++;if(r.bust)pBust++;pNet+=r.net;pStake+=r.stake;pTextbook.put(r.textbookAction,pTextbook.get(r.textbookAction)+1);}
        for(ResolvedHandRecord r:h){if(r.thirdCardTaken)hThird++;if(r.thirdCardBust)hThirdBust++;if(r.bust)hBust++;hNet+=r.net;hStake+=r.stake;hTextbook.put(r.textbookAction,hTextbook.get(r.textbookAction)+1);}
        return "PHASE 3.35 — SELECTIVE-AGGRESSION INSTRUMENTATION SUMMARY\n\n"+
            "PRIMARY resolved selective-aggression hands: "+pHands+"; mean net/hand "+String.format(Locale.ROOT,"%+.4f",pNet/pHands)+"; mean committed stake "+String.format(Locale.ROOT,"%.4f",pStake/pHands)+"; bust rate "+pct(pBust,pHands)+"; third-card bust rate when a third card was taken "+pct(pThirdBust,pThird)+".\n"+
            "Textbook would have chosen on those same visible first-decision states: HIT "+pTextbook.get(Action.HIT)+", STAND "+pTextbook.get(Action.STAND)+", DOUBLE "+pTextbook.get(Action.DOUBLE)+".\n\n"+
            "HOLDOUT resolved selective-aggression hands: "+hHands+"; mean net/hand "+String.format(Locale.ROOT,"%+.4f",hNet/hHands)+"; mean committed stake "+String.format(Locale.ROOT,"%.4f",hStake/hHands)+"; bust rate "+pct(hBust,hHands)+"; third-card bust rate when a third card was taken "+pct(hThirdBust,hThird)+".\n"+
            "Textbook would have chosen on those same visible first-decision states: HIT "+hTextbook.get(Action.HIT)+", STAND "+hTextbook.get(Action.STAND)+", DOUBLE "+hTextbook.get(Action.DOUBLE)+".\n\n"+
            "INTERPRETATION\nPhase 3.35 carries forward split-child HARD 20 STAND, HARD 19 STAND, and split-child HARD 18 STAND. It adds one new family only: NO POLICY OR WAGER INTERVENTION; frozen policy carried forward at 1x. All other Maximum Aggression behaviour remains frozen.\n";
    }

    static String replicatedCandidates(List<ResolvedHandRecord> primary,List<ResolvedHandRecord> holdout){
        Map<StateKey,StateAgg> p=aggregateDivergent(primary),h=aggregateDivergent(holdout);
        final class C {StateKey k;StateAgg a,b;double score;C(StateKey k,StateAgg a,StateAgg b){this.k=k;this.a=a;this.b=b;this.score=(a.net/a.hands+b.net/b.hands)/2.0;}}
        ArrayList<C> c=new ArrayList<>();
        for(StateKey k:p.keySet())if(h.containsKey(k)){StateAgg a=p.get(k),b=h.get(k);if(a.hands>=100&&b.hands>=100&&a.net/a.hands<0&&b.net/b.hands<0)c.add(new C(k,a,b));}
        c.sort(Comparator.comparingDouble(x->x.score));
        StringBuilder r=new StringBuilder("PHASE 3.35 — REMAINING TRUE POLICY-DIVERGENCE STATES UNDER FROZEN 3.20 POLICY\n\n");
        r.append("Filter: aggregate ONLY hands where Selective Aggression's recorded first action differs from the frozen Textbook first action; then require the same visible state >=100 times in BOTH cohorts and negative mean net/hand in BOTH cohorts. This prevents already-protected states with negative realised outcomes from being mislabelled as remaining policy damage.\n\n");
        r.append("Rank | State | Primary n / net | Holdout n / net | P bust% / H bust% | Textbook first action\n");
        int rank=0;for(C x:c){if(++rank>20)break;r.append(String.format(Locale.ROOT,"%2d | %-34s | %5d / %+6.3f | %5d / %+6.3f | %6.2f%% / %6.2f%% | %s%n",rank,(x.k.soft?"SOFT ":"HARD ")+x.k.total+" vs "+x.k.up+(x.k.splitChild?" [split-child]":""),x.a.hands,x.a.net/x.a.hands,x.b.hands,x.b.net/x.b.hands,100.0*x.a.busts/x.a.hands,100.0*x.b.busts/x.b.hands,x.k.textbook));}
        r.append("\nDESIGN USE\nPhase 3.35 carries forward the validated split-child HARD 20, HARD 19 and split-child HARD 18 protections and adds no intervention; frozen decision policy is measured at the unchanged 1x wager. Remaining replicated damage states are retained for later one-rule-at-a-time interventions.\n");
        return r.toString();
    }

    static String methodology(){
        return "PHASE 3.35 — RUNNING TXT AUDIT RECORD\n\n"+
            "OBJECTIVE\nMake the fourth controlled stabilisation intervention while preserving the frozen architecture. Carry forward the three validated protections, then add one new policy family: HARD 16 follows its corresponding Textbook action rather than taking Maximum Aggression's automatic DOUBLE.\n\n"+
            "EVIDENCE HIERARCHY\n1. Primary design authority: finite six-deck Phase 2.xx evidence and new Phase 3.xx data.\n2. Earlier non-depleting/infinite-generator behaviour-profile data: historical corroboration and hypothesis generation only.\n3. Old behaviour-profile evidence must not directly define a new policy unless finite-shoe evidence independently supports the same conclusion.\n\n"+
            "FROZEN VARIABLES\nSix decks, deterministic session shuffle, 30 hands, 100-unit start, 1-unit base wager, dealer rules, blackjack payout, split limit, no in-session reshuffle, textbook control and Maximum Aggression policy are unchanged from Phase 2.02.\n\n"+
            "POLICY INFORMATION BOUNDARY\nThe future optimiser may use player hand composition/total, pair status, dealer upcard and legal actions. Phase 3.35 deliberately uses no previous-card-history, running count, remaining-rank inventory, hidden card or future-card feature. The accumulated policy uses only current visible states: split-child HARD 20, HARD 19, HARD 18, split-child HARD 16 vs Ace, and HARD 17. No card-history or composition feature is introduced.\n\n"+
            "PHASE 3.35 DECISION RULE\nCarry forward split-child HARD 20 STAND, HARD 19 STAND and split-child HARD 18 STAND. ONE additional Phase 3.35 intervention: if an ordinary first-decision hand is hard (not soft) and totals 18, Selective Aggression STANDS. Otherwise the frozen Maximum Aggression policy is unchanged. Textbook remains the control.\n";
    }

    static String hash(String s)throws Exception{MessageDigest md=MessageDigest.getInstance("SHA-256");byte[] d=md.digest(s.getBytes(StandardCharsets.UTF_8));StringBuilder b=new StringBuilder();for(byte x:d)b.append(String.format("%02x",x));return b.toString();}
    static void write(String n,String s)throws Exception{Files.writeString(Path.of(n),s,StandardCharsets.UTF_8);}
    static void writeCsv(String n,List<ResolvedHandRecord> rr)throws Exception{StringBuilder b=new StringBuilder("cohort,session,round,child,split_child,start_total,start_soft,dealer_up,visible_class,textbook_first_action,aggressive_first_action,start_cards,end_cards,dealer_end_cards,player_natural_blackjack,dealer_natural_blackjack,base_wager,selective_wager_trigger,stake,third_card_taken,third_card_bust,bust,outcome,net,start_pointer,end_pointer\n");for(ResolvedHandRecord r:rr)b.append(r.csv()).append('\n');write(n,b.toString());}
















    static int bal355(int[] h,int count){
        int n=Math.min(3,count),s=0;
        for(int i=0;i<n;i++)s+=h[(count-1-i+h.length)%h.length];
        return s;
    }
    static double normal355(int hand,double bank){
        if(hand<=8)return 10.0;
        if(bank<115)return 10.0;
        if(bank<130)return 15.0;
        if(bank<150)return 20.0;
        return 30.0;
    }
    static double cautious355(int[] hist,int count){
        int b=bal355(hist,count);
        return b<0?10.0:(b==0?15.0:17.5);
    }

    static final int[] TIERS356={100,110,120,130,140,150,160,170,180,190};

    static final class Recovery356 {
        int startHand, endHand;
        double startBank, trough, exposure;
        boolean recovered;
    }

    static final class Session356 {
        String cohort;
        int session, handsPlayed, targetHand, bankruptcyHand;
        double finalBank, peakBank, troughBank, maxDrawdown, cumulativeWager;
        double wagerBelow100, targetExposure, peakGiveBack;
        int handsBelow100, recoveryEpisodes, successfulRecoveries, failedRecoveries;
        long cardsConsumed, splits, doubles;
        long[] tierHands=new long[TIERS356.length];
        long[][] transitions=new long[TIERS356.length][TIERS356.length+2]; // + target / below100
        ArrayList<Recovery356> recoveries=new ArrayList<>();
    }

    static final class Cohort356 {
        String name;
        ArrayList<Session356> sessions=new ArrayList<>();
        Cohort356(String n){name=n;}
    }

    static int tier356(double bank){
        if(bank<100)return -1;
        if(bank>=200)return 10;
        int x=((int)Math.floor(bank)-100)/10;
        return Math.max(0,Math.min(9,x));
    }

    static double roundStake356(Round rr){
        double s=0.0;
        for(ResolvedHandRecord x:rr.records)s+=x.stake;
        return s;
    }

    static Cohort356 runCohort356(long seed,String cohort){
        Cohort356 out=new Cohort356(cohort);
        for(int session=0;session<SESSIONS;session++){
            int[] shoe=sixDeckShoe(sessionSeed(seed,session));
            if(!auditShoe(shoe))throw new IllegalStateException("Inventory audit failed");

            int ptr=0,hc=0; int[] hist=new int[5];
            double bank=START_BANK, peak=START_BANK, trough=START_BANK, maxDD=0.0;
            boolean everPlunge=false,cooling=false,cautious=false;
            boolean negativeEpisode=false,brakeArmed=false;
            Recovery356 activeRecovery=null;

            Session356 s=new Session356();
            s.cohort=cohort;s.session=session+1;

            for(int hand=1;hand<=HANDS;hand++){
                if(bank>=200||bank<=0)break;

                if(cooling&&bank>=120){cooling=false;cautious=true;}
                if(cautious&&bank>=140)cautious=false;

                double wager;
                if(cooling){
                    wager=10.0;negativeEpisode=false;brakeArmed=false;
                }else if(cautious){
                    wager=cautious355(hist,hc);negativeEpisode=false;brakeArmed=false;
                }else if(hand>8&&bank>=140){
                    int b=bal355(hist,hc);boolean neg=b<0;
                    if(neg&&!negativeEpisode)brakeArmed=true;
                    negativeEpisode=neg;
                    double base=bank>=170.0?15.0:normal355(hand,bank);
                    if(brakeArmed){wager=Math.min(base,15.0);brakeArmed=false;}else wager=base;
                }else{
                    wager=normal355(hand,bank);negativeEpisode=false;brakeArmed=false;
                }
                wager=Math.min(wager,bank);
                if(wager<=0)break;
                if(!cooling&&!cautious&&hand>8&&bank>=130)everPlunge=true;

                double before=bank;
                int beforeTier=tier356(before);
                if(beforeTier>=0&&beforeTier<10)s.tierHands[beforeTier]++;

                PairStats ps=new PairStats();
                int ptrBeforeRound=ptr;
                Round rr=playRound(shoe,ptr,Profile.SELECTIVE_AGGRESSION,cohort,session+1,hand,true,bank,ps,wager,false);
                ptr=rr.endPointer;

                double committed=roundStake356(rr);
                s.cumulativeWager+=committed;
                s.cardsConsumed+=(rr.endPointer-ptrBeforeRound);
                s.splits+=rr.splits;s.doubles+=rr.doubles;

                bank+=rr.net;if(bank<0)bank=0;
                s.handsPlayed++;
                if(bank<100){s.handsBelow100++;s.wagerBelow100+=committed;}

                peak=Math.max(peak,bank);
                trough=Math.min(trough,bank);
                maxDD=Math.max(maxDD,peak-bank);

                int afterTier=tier356(bank);
                if(beforeTier>=0&&beforeTier<10){
                    if(afterTier==10)s.transitions[beforeTier][10]++;
                    else if(afterTier<0)s.transitions[beforeTier][11]++;
                    else s.transitions[beforeTier][afterTier]++;
                }

                // Recovery episode = first crossing from >=£100 to <£100,
                // ending only when £100 is regained or the session terminates.
                if(activeRecovery==null && before>=100 && bank<100){
                    activeRecovery=new Recovery356();
                    activeRecovery.startHand=hand;
                    activeRecovery.startBank=before;
                    activeRecovery.trough=bank;
                    activeRecovery.exposure=committed;
                    s.recoveryEpisodes++;
                }else if(activeRecovery!=null){
                    activeRecovery.trough=Math.min(activeRecovery.trough,bank);
                    activeRecovery.exposure+=committed;
                    if(bank>=100){
                        activeRecovery.recovered=true;
                        activeRecovery.endHand=hand;
                        s.successfulRecoveries++;
                        s.recoveries.add(activeRecovery);
                        activeRecovery=null;
                    }
                }

                hist[hc%hist.length]=rr.net>1e-9?1:(rr.net<-1e-9?-1:0);hc++;

                if(bank>=200){
                    s.targetHand=hand;
                    s.targetExposure=s.cumulativeWager;
                    break;
                }
                if(bank<=0){
                    s.bankruptcyHand=hand;
                    break;
                }
                if(everPlunge&&bank<=100&&!cooling){
                    cooling=true;cautious=false;negativeEpisode=false;brakeArmed=false;
                }
            }

            if(activeRecovery!=null){
                activeRecovery.endHand=s.handsPlayed;
                activeRecovery.recovered=false;
                s.failedRecoveries++;
                s.recoveries.add(activeRecovery);
            }

            s.finalBank=bank;s.peakBank=peak;s.troughBank=trough;s.maxDrawdown=maxDD;
            s.peakGiveBack=Math.max(0.0,peak-bank);
            out.sessions.add(s);
        }
        return out;
    }

    static String f3(double x){return String.format(Locale.ROOT,"%.3f",x);}
    static String p356(long n,long d){return d==0?"0.00%":String.format(Locale.ROOT,"%.2f%%",100.0*n/d);}

    static void appendCohort356(StringBuilder r,Cohort356 c){
        long n=c.sessions.size(),prof=0,loss=0,be=0,target=0,bust=0;
        long episodes=0,success=0,failed=0,handsBelow=0,cards=0,splits=0,doubles=0;
        double finalSum=0,wager=0,wagerBelow=0,dd=0,give=0,targetExp=0;
        long targetExpN=0; double recoveryHands=0,recoveryExp=0,recoveryDepth=0; long recoveryN=0;

        for(Session356 s:c.sessions){
            finalSum+=s.finalBank;wager+=s.cumulativeWager;wagerBelow+=s.wagerBelow100;
            dd+=s.maxDrawdown;give+=s.peakGiveBack;handsBelow+=s.handsBelow100;
            cards+=s.cardsConsumed;splits+=s.splits;doubles+=s.doubles;
            if(s.finalBank>100)prof++;else if(s.finalBank<100)loss++;else be++;
            if(s.targetHand>0){target++;targetExp+=s.targetExposure;targetExpN++;}
            if(s.finalBank<=0)bust++;
            episodes+=s.recoveryEpisodes;success+=s.successfulRecoveries;failed+=s.failedRecoveries;
            for(Recovery356 x:s.recoveries)if(x.recovered){
                recoveryN++;recoveryHands+=(x.endHand-x.startHand);
                recoveryExp+=x.exposure;recoveryDepth+=(100.0-x.trough);
            }
        }

        r.append("\n").append(c.name).append(" — EXPANDED MEASUREMENT BASELINE\n");
        r.append("Sessions: ").append(n).append("\n");
        r.append("Average ending bankroll: £").append(f3(finalSum/n)).append("\n");
        r.append("Profitable / Losing / Break-even: ").append(p356(prof,n)).append(" / ")
         .append(p356(loss,n)).append(" / ").append(p356(be,n)).append("\n");
        r.append("Reached £200 / Bankroll exhausted: ").append(p356(target,n)).append(" / ").append(p356(bust,n)).append("\n");
        r.append("Average cumulative wager per session: £").append(f3(wager/n)).append("\n");
        r.append("Average cumulative wager below £100 per session: £").append(f3(wagerBelow/n)).append("\n");
        r.append("Average maximum drawdown: £").append(f3(dd/n)).append("\n");
        r.append("Average peak-to-finish give-back: £").append(f3(give/n)).append("\n");
        r.append("Average hands below £100: ").append(f3((double)handsBelow/n)).append("\n");
        r.append("Recovery episodes / successful / failed: ").append(episodes).append(" / ").append(success).append(" / ").append(failed).append("\n");
        r.append("Successful recovery rate by episode: ").append(p356(success,episodes)).append("\n");
        r.append("Average successful recovery duration (hands after crossing below £100): ")
         .append(recoveryN==0?"0.000":f3(recoveryHands/recoveryN)).append("\n");
        r.append("Average successful recovery exposure: £").append(recoveryN==0?"0.000":f3(recoveryExp/recoveryN)).append("\n");
        r.append("Average successful recovery depth below £100: £").append(recoveryN==0?"0.000":f3(recoveryDepth/recoveryN)).append("\n");
        r.append("Average exposure at first £200 reach: £").append(targetExpN==0?"0.000":f3(targetExp/targetExpN)).append("\n");
        r.append("Cards consumed / splits / doubles: ").append(cards).append(" / ").append(splits).append(" / ").append(doubles).append("\n");
    }

    static String sessionCsv356(Cohort356... cs){
        StringBuilder b=new StringBuilder("cohort,session,hands_played,final_bank,peak_bank,trough_bank,max_drawdown,cumulative_wager,wager_below_100,hands_below_100,recovery_episodes,successful_recoveries,failed_recoveries,target_hand,target_exposure,bankruptcy_hand,peak_give_back,cards_consumed,splits,doubles\n");
        for(Cohort356 c:cs)for(Session356 s:c.sessions)
            b.append(String.format(Locale.ROOT,"%s,%d,%d,%.2f,%.2f,%.2f,%.2f,%.2f,%.2f,%d,%d,%d,%d,%d,%.2f,%d,%.2f,%d,%d,%d%n",
                s.cohort,s.session,s.handsPlayed,s.finalBank,s.peakBank,s.troughBank,s.maxDrawdown,
                s.cumulativeWager,s.wagerBelow100,s.handsBelow100,s.recoveryEpisodes,s.successfulRecoveries,
                s.failedRecoveries,s.targetHand,s.targetExposure,s.bankruptcyHand,s.peakGiveBack,
                s.cardsConsumed,s.splits,s.doubles));
        return b.toString();
    }

    static String recoveryCsv356(Cohort356... cs){
        StringBuilder b=new StringBuilder("cohort,session,episode,start_hand,end_hand,start_bank,trough_bank,depth_below_100,exposure,recovered\n");
        for(Cohort356 c:cs)for(Session356 s:c.sessions){
            int e=0;for(Recovery356 x:s.recoveries){
                e++;b.append(String.format(Locale.ROOT,"%s,%d,%d,%d,%d,%.2f,%.2f,%.2f,%.2f,%s%n",
                    s.cohort,s.session,e,x.startHand,x.endHand,x.startBank,x.trough,
                    Math.max(0,100-x.trough),x.exposure,Boolean.toString(x.recovered)));
            }
        }
        return b.toString();
    }

    static String tierCsv356(Cohort356... cs){
        StringBuilder b=new StringBuilder("cohort,from_tier,to_state,count\n");
        for(Cohort356 c:cs){
            long[][] z=new long[10][12];
            for(Session356 s:c.sessions)for(int i=0;i<10;i++)for(int j=0;j<12;j++)z[i][j]+=s.transitions[i][j];
            for(int i=0;i<10;i++)for(int j=0;j<12;j++)if(z[i][j]>0){
                String to=j==10?"TARGET_200":(j==11?"BELOW_100":"£"+TIERS356[j]+"-"+(TIERS356[j]+9));
                b.append(c.name).append(",£").append(TIERS356[i]).append("-").append(TIERS356[i]+9)
                 .append(",").append(to).append(",").append(z[i][j]).append("\n");
            }
        }
        return b.toString();
    }


    static String depthBand357(double depth){
        if(depth<10)return "£0-<10";
        if(depth<20)return "£10-<20";
        if(depth<30)return "£20-<30";
        if(depth<40)return "£30-<40";
        if(depth<50)return "£40-<50";
        return "£50+";
    }
    static String exposureBand357(double x){
        if(x<25)return "£0-<25";
        if(x<50)return "£25-<50";
        if(x<75)return "£50-<75";
        if(x<100)return "£75-<100";
        if(x<150)return "£100-<150";
        return "£150+";
    }
    static String startHandBand357(int h){
        if(h<=5)return "Hands 1-5";
        if(h<=10)return "Hands 6-10";
        if(h<=15)return "Hands 11-15";
        if(h<=20)return "Hands 16-20";
        if(h<=25)return "Hands 21-25";
        return "Hands 26-30";
    }

    static final class Band357{
        long total,success;
        double exposure,depth,duration;
        void add(Recovery356 r){
            total++;
            if(r.recovered)success++;
            exposure+=r.exposure;
            depth+=Math.max(0,100-r.trough);
            duration+=(r.endHand-r.startHand);
        }
    }

    static void addBand357(Map<String,Band357> m,String key,Recovery356 r){
        Band357 b=m.get(key);
        if(b==null){b=new Band357();m.put(key,b);}
        b.add(r);
    }

    static String bandCsv357(String dimension,Map<String,Band357> m){
        StringBuilder b=new StringBuilder("dimension,band,episodes,successful,failed,success_rate,avg_duration_hands,avg_exposure,avg_depth_below_100\n");
        for(Map.Entry<String,Band357> e:m.entrySet()){
            Band357 x=e.getValue();
            b.append(String.format(Locale.ROOT,"%s,%s,%d,%d,%d,%.4f,%.4f,%.4f,%.4f%n",
                dimension,e.getKey(),x.total,x.success,x.total-x.success,
                x.total==0?0:(100.0*x.success/x.total),
                x.total==0?0:(x.duration/x.total),
                x.total==0?0:(x.exposure/x.total),
                x.total==0?0:(x.depth/x.total)));
        }
        return b.toString();
    }

    static void appendRecoveryAnatomy357(StringBuilder r,Cohort356 c)throws Exception{
        long episodes=0,success=0,failed=0;
        double successDur=0,successExp=0,successDepth=0;
        double failDur=0,failExp=0,failDepth=0;
        long sN=0,fN=0;

        LinkedHashMap<String,Band357> depth=new LinkedHashMap<>();
        LinkedHashMap<String,Band357> exposure=new LinkedHashMap<>();
        LinkedHashMap<String,Band357> start=new LinkedHashMap<>();
        String[] depthOrder={"£0-<10","£10-<20","£20-<30","£30-<40","£40-<50","£50+"};
        String[] expOrder={"£0-<25","£25-<50","£50-<75","£75-<100","£100-<150","£150+"};
        String[] startOrder={"Hands 1-5","Hands 6-10","Hands 11-15","Hands 16-20","Hands 21-25","Hands 26-30"};
        for(String x:depthOrder)depth.put(x,new Band357());
        for(String x:expOrder)exposure.put(x,new Band357());
        for(String x:startOrder)start.put(x,new Band357());

        for(Session356 s:c.sessions){
            for(Recovery356 x:s.recoveries){
                episodes++;
                double d=x.endHand-x.startHand;
                double dep=Math.max(0,100-x.trough);
                if(x.recovered){
                    success++;sN++;successDur+=d;successExp+=x.exposure;successDepth+=dep;
                }else{
                    failed++;fN++;failDur+=d;failExp+=x.exposure;failDepth+=dep;
                }
                addBand357(depth,depthBand357(dep),x);
                addBand357(exposure,exposureBand357(x.exposure),x);
                addBand357(start,startHandBand357(x.startHand),x);
            }
        }

        r.append("\n").append(c.name).append(" — RECOVERY EFFICIENCY ANATOMY\n");
        r.append("Recovery episodes: ").append(episodes).append("\n");
        r.append("Successful / Failed: ").append(success).append(" / ").append(failed)
         .append(" (").append(p356(success,episodes)).append(" successful)\n");
        r.append("Successful episodes — avg duration / exposure / depth: ")
         .append(f3(successDur/sN)).append(" hands / £").append(f3(successExp/sN))
         .append(" / £").append(f3(successDepth/sN)).append("\n");
        r.append("Failed episodes — avg observed duration / exposure / depth: ")
         .append(f3(failDur/fN)).append(" hands / £").append(f3(failExp/fN))
         .append(" / £").append(f3(failDepth/fN)).append("\n");
        r.append("Exposure ratio failed/successful: ").append(f3((failExp/fN)/(successExp/sN))).append("x\n");
        r.append("Depth ratio failed/successful: ").append(f3((failDepth/fN)/(successDepth/sN))).append("x\n");

        r.append("\nSUCCESS RATE BY RECOVERY DEPTH\n");
        for(Map.Entry<String,Band357> e:depth.entrySet()){
            Band357 x=e.getValue();
            r.append(String.format(Locale.ROOT,"%-9s  episodes=%5d  success=%6.2f%%  avg exposure=£%7.2f  avg duration=%5.2f%n",
                e.getKey(),x.total,x.total==0?0:100.0*x.success/x.total,x.total==0?0:x.exposure/x.total,x.total==0?0:x.duration/x.total));
        }
        r.append("\nSUCCESS RATE BY RECOVERY EXPOSURE\n");
        for(Map.Entry<String,Band357> e:exposure.entrySet()){
            Band357 x=e.getValue();
            r.append(String.format(Locale.ROOT,"%-11s episodes=%5d  success=%6.2f%%  avg depth=£%6.2f  avg duration=%5.2f%n",
                e.getKey(),x.total,x.total==0?0:100.0*x.success/x.total,x.total==0?0:x.depth/x.total,x.total==0?0:x.duration/x.total));
        }
        r.append("\nSUCCESS RATE BY EPISODE START HAND\n");
        for(Map.Entry<String,Band357> e:start.entrySet()){
            Band357 x=e.getValue();
            r.append(String.format(Locale.ROOT,"%-12s episodes=%5d  success=%6.2f%%  avg exposure=£%7.2f  avg depth=£%6.2f%n",
                e.getKey(),x.total,x.total==0?0:100.0*x.success/x.total,x.total==0?0:x.exposure/x.total,x.total==0?0:x.depth/x.total));
        }

        write("Phase_3_57_"+c.name+"_Recovery_Depth_Bands.csv",bandCsv357("depth",depth));
        write("Phase_3_57_"+c.name+"_Recovery_Exposure_Bands.csv",bandCsv357("exposure",exposure));
        write("Phase_3_57_"+c.name+"_Recovery_StartHand_Bands.csv",bandCsv357("start_hand",start));
    }


    static String depthGroup358(double depth){
        if(depth<20)return "SHALLOW_<20";
        if(depth<40)return "MODERATE_20_<40";
        if(depth<50)return "DEEP_40_<50";
        return "SEVERE_50_PLUS";
    }

    static String timeGroup358(int startHand){
        int remaining=HANDS-startHand;
        if(remaining>=20)return "20_PLUS_HANDS_LEFT";
        if(remaining>=10)return "10_19_HANDS_LEFT";
        if(remaining>=5)return "5_9_HANDS_LEFT";
        return "0_4_HANDS_LEFT";
    }

    static final class Cell358{
        ArrayList<Recovery356> episodes=new ArrayList<>();
    }

    static double median358(ArrayList<Double> a){
        if(a.isEmpty())return 0.0;
        Collections.sort(a);
        int n=a.size();
        return n%2==1?a.get(n/2):(a.get(n/2-1)+a.get(n/2))/2.0;
    }

    static String decompose358(Cohort356 c,StringBuilder report){
        LinkedHashMap<String,Cell358> cells=new LinkedHashMap<>();
        String[] dg={"SHALLOW_<20","MODERATE_20_<40","DEEP_40_<50","SEVERE_50_PLUS"};
        String[] tg={"20_PLUS_HANDS_LEFT","10_19_HANDS_LEFT","5_9_HANDS_LEFT","0_4_HANDS_LEFT"};
        for(String d:dg)for(String t:tg)cells.put(d+"|"+t,new Cell358());

        for(Session356 s:c.sessions)for(Recovery356 x:s.recoveries){
            double dep=Math.max(0,100-x.trough);
            String key=depthGroup358(dep)+"|"+timeGroup358(x.startHand);
            cells.get(key).episodes.add(x);
        }

        StringBuilder csv=new StringBuilder(
            "cohort,depth_group,time_remaining_group,episodes,median_exposure,low_exposure_n,low_success_n,low_success_rate,high_exposure_n,high_success_n,high_success_rate,high_minus_low_pp,low_avg_duration,high_avg_duration,low_avg_depth,high_avg_depth\n");

        report.append("\n").append(c.name).append(" — DEPTH × TIME-REMAINING × EXPOSURE DECOMPOSITION\n");
        report.append("Within each depth/time cell, episodes are divided at that cell's median observed recovery exposure.\n");
        report.append("This is descriptive, not causal: exposure accumulates as an episode continues, so outcome and exposure are temporally entangled.\n\n");

        for(Map.Entry<String,Cell358> e:cells.entrySet()){
            String[] parts=e.getKey().split("\\|");
            ArrayList<Recovery356> eps=e.getValue().episodes;
            if(eps.isEmpty())continue;

            ArrayList<Double> vals=new ArrayList<>();
            for(Recovery356 x:eps)vals.add(x.exposure);
            double med=median358(vals);

            long ln=0,ls=0,hn=0,hs=0;
            double ldur=0,hdur=0,ldep=0,hdep=0;
            for(Recovery356 x:eps){
                boolean low=x.exposure<=med;
                double dur=x.endHand-x.startHand;
                double dep=Math.max(0,100-x.trough);
                if(low){ln++;if(x.recovered)ls++;ldur+=dur;ldep+=dep;}
                else{hn++;if(x.recovered)hs++;hdur+=dur;hdep+=dep;}
            }
            double lr=ln==0?0:100.0*ls/ln;
            double hr=hn==0?0:100.0*hs/hn;
            double diff=hr-lr;

            report.append(String.format(Locale.ROOT,
                "%-16s | %-18s | n=%4d | median exposure £%6.2f | lower exposure success %6.2f%% | higher exposure success %6.2f%% | difference %+6.2f pp%n",
                parts[0],parts[1],eps.size(),med,lr,hr,diff));

            csv.append(String.format(Locale.ROOT,
                "%s,%s,%s,%d,%.2f,%d,%d,%.4f,%d,%d,%.4f,%.4f,%.4f,%.4f,%.4f,%.4f%n",
                c.name,parts[0],parts[1],eps.size(),med,ln,ls,lr,hn,hs,hr,diff,
                ln==0?0:ldur/ln,hn==0?0:hdur/hn,ln==0?0:ldep/ln,hn==0?0:hdep/hn));
        }
        return csv.toString();
    }























    /* PHASE 3.80 — SAME-SEED PAIRED DISTRIBUTIONAL TEST */
    enum Policy380 { CONTROL, HIT_LE_15, CTX14_16 }

    static final class S380{
        String cohort,policy; int session,hands;
        double finalBank,cumWager,maxDrawdown,peakBank,giveBack;
        long playerBusts,hits,contextHits;
        boolean reached200,exhausted;
    }
    static final class H380{
        ArrayList<Integer> c=new ArrayList<>();
        int total(){int t=0,a=0;for(int v:c){t+=v;if(v==11)a++;}while(t>21&&a-->0)t-=10;return t;}
        boolean bust(){return total()>21;}
    }
    static int draw380(int[]shoe,int ptr,H380 h){h.c.add(shoe[ptr]);return ptr+1;}
    static boolean hit380(Policy380 p,int total,int up){
        if(p==Policy380.HIT_LE_15)return total<=15;
        if(p==Policy380.CTX14_16)return total<=13||(total>=14&&total<=16&&up>=7);
        return false;
    }
    static S380 runOne380(long seed,String cohort,int session,Policy380 policy){
        int[]shoe=sixDeckShoe(sessionSeed(seed,session));if(!auditShoe(shoe))throw new IllegalStateException("Inventory audit failed");
        int ptr=0,hc=0;int[]hist=new int[5];double bank=START_BANK,peak=START_BANK,maxDD=0;
        boolean everPlunge=false,cooling=false,cautious=false,negativeEpisode=false,brakeArmed=false;
        S380 s=new S380();s.cohort=cohort;s.policy=policy.name();s.session=session+1;
        for(int hand=1;hand<=HANDS;hand++){
            if(bank>=200||bank<=0)break;
            if(cooling&&bank>=120){cooling=false;cautious=true;}if(cautious&&bank>=140)cautious=false;
            double wager;
            if(cooling){wager=10;negativeEpisode=false;brakeArmed=false;}
            else if(cautious){wager=cautious355(hist,hc);negativeEpisode=false;brakeArmed=false;}
            else if(hand>8&&bank>=140){int b=bal355(hist,hc);boolean neg=b<0;if(neg&&!negativeEpisode)brakeArmed=true;negativeEpisode=neg;double base=bank>=170?15:normal355(hand,bank);if(brakeArmed){wager=Math.min(base,15);brakeArmed=false;}else wager=base;}
            else{wager=normal355(hand,bank);negativeEpisode=false;brakeArmed=false;}
            wager=Math.min(wager,bank);if(wager<=0)break;if(!cooling&&!cautious&&hand>8&&bank>=130)everPlunge=true;
            double net,stake=wager;
            if(policy==Policy380.CONTROL){
                PairStats ps=new PairStats();Round rr=playRound(shoe,ptr,Profile.SELECTIVE_AGGRESSION,cohort,session+1,hand,true,bank,ps,wager,false);
                ptr=rr.endPointer;net=rr.net;stake=roundStake356(rr);
            }else{
                H380 p=new H380(),d=new H380();
                ptr=draw380(shoe,ptr,p);ptr=draw380(shoe,ptr,d);ptr=draw380(shoe,ptr,p);ptr=draw380(shoe,ptr,d);
                int visibleUpcard=d.c.get(0); // sole dealer information available to player
                while(!p.bust()&&hit380(policy,p.total(),visibleUpcard)){
                    if(policy==Policy380.CTX14_16&&p.total()>=14&&p.total()<=16&&visibleUpcard>=7)s.contextHits++;
                    ptr=draw380(shoe,ptr,p);s.hits++;
                }
                if(p.bust()){s.playerBusts++;net=-wager;}
                else{while(d.total()<17)ptr=draw380(shoe,ptr,d);if(d.bust()||p.total()>d.total())net=wager;else if(p.total()<d.total())net=-wager;else net=0;}
            }
            s.cumWager+=stake;bank+=net;if(bank<0)bank=0;s.hands++;peak=Math.max(peak,bank);maxDD=Math.max(maxDD,peak-bank);
            hist[hc%hist.length]=net>1e-9?1:(net<-1e-9?-1:0);hc++;
            if(bank>=200){s.reached200=true;break;}if(bank<=0){s.exhausted=true;break;}
            if(everPlunge&&bank<=100&&!cooling){cooling=true;cautious=false;negativeEpisode=false;brakeArmed=false;}
        }
        s.finalBank=bank;s.maxDrawdown=maxDD;s.peakBank=peak;s.giveBack=Math.max(0,peak-bank);
        if(bank>=200)s.reached200=true;if(bank<=0)s.exhausted=true;return s;
    }
    static ArrayList<S380> run380(long seed,String cohort,Policy380 p){ArrayList<S380>a=new ArrayList<>();for(int i=0;i<SESSIONS;i++)a.add(runOne380(seed,cohort,i,p));return a;}
    static String pct380(long x,long n){return String.format(Locale.ROOT,"%.2f%%",100.0*x/n);}
    static int bandIndex380(double x){if(x<=1e-9)return 0;if(x<50)return 1;if(x<100)return 2;if(x<150)return 3;if(x<200)return 4;return 5;}
    static String bandName380(int k){return new String[]{"£0","£1-49","£50-99","£100-149","£150-199","£200+"}[k];}

    static void population380(StringBuilder r,String cohort,ArrayList<S380>[]g){
        r.append("\n").append(cohort).append(" — POPULATION SUMMARY\n");
        r.append(String.format(Locale.ROOT,"%-11s %9s %9s %9s %9s %9s%n","Policy","Final","£200","£0","MaxDD","Wager"));
        for(ArrayList<S380>a:g){long n=a.size(),t=0,z=0;double f=0,d=0,w=0;for(S380 s:a){f+=s.finalBank;d+=s.maxDrawdown;w+=s.cumWager;if(s.reached200)t++;if(s.exhausted)z++;}
            r.append(String.format(Locale.ROOT,"%-11s £%8.2f %8s %8s £%8.2f £%8.2f%n",a.get(0).policy,f/n,pct380(t,n),pct380(z,n),d/n,w/n));}
    }
    static void paired380(StringBuilder r,String cohort,ArrayList<S380>c,ArrayList<S380>s,ArrayList<S380>x){
        long n=c.size(),xwc=0,tc=0,cwx=0,xws=0,ts=0,swx=0;double dc=0,ds=0,abs=0,ddDelta=0,wDelta=0;
        long[][]bands=new long[3][6];
        for(int i=0;i<n;i++){
            S380 a=c.get(i),b=s.get(i),e=x.get(i);double d=e.finalBank-a.finalBank,q=e.finalBank-b.finalBank;
            dc+=d;ds+=q;abs+=Math.abs(d);ddDelta+=e.maxDrawdown-a.maxDrawdown;wDelta+=e.cumWager-a.cumWager;
            if(d>1e-9)xwc++;else if(d<-1e-9)cwx++;else tc++;
            if(q>1e-9)xws++;else if(q<-1e-9)swx++;else ts++;
            bands[0][bandIndex380(a.finalBank)]++;bands[1][bandIndex380(b.finalBank)]++;bands[2][bandIndex380(e.finalBank)]++;
        }
        r.append("\n").append(cohort).append(" — SAME-SEED PAIRED EVIDENCE\n");
        r.append("CTX14_16 versus CONTROL\n");
        r.append("CTX higher final: ").append(pct380(xwc,n)).append("\nCONTROL higher final: ").append(pct380(cwx,n)).append("\nExact final tie: ").append(pct380(tc,n)).append("\n");
        r.append(String.format(Locale.ROOT,"Mean paired final difference (CTX - CONTROL): £%.3f%nMean absolute paired final difference: £%.3f%nMean paired max-drawdown difference: £%.3f%nMean paired cumulative-wager difference: £%.3f%n",dc/n,abs/n,ddDelta/n,wDelta/n));
        r.append("\nCTX14_16 versus HIT_LE_15\n");
        r.append("CTX higher final: ").append(pct380(xws,n)).append("\nHIT_LE_15 higher final: ").append(pct380(swx,n)).append("\nExact final tie: ").append(pct380(ts,n)).append("\n");
        r.append(String.format(Locale.ROOT,"Mean paired final difference (CTX - HIT_LE_15): £%.3f%n",ds/n));
        r.append("\nFINAL BANKROLL BANDS\n");
        r.append(String.format(Locale.ROOT,"%-11s %10s %12s %12s%n","Band","CONTROL","HIT_LE_15","CTX14_16"));
        for(int k=0;k<6;k++)r.append(String.format(Locale.ROOT,"%-11s %10s %12s %12s%n",bandName380(k),pct380(bands[0][k],n),pct380(bands[1][k],n),pct380(bands[2][k],n)));
    }
    static String csv380(String cohort,ArrayList<S380>c,ArrayList<S380>s,ArrayList<S380>x){
        StringBuilder b=new StringBuilder("cohort,session,control_final,hit15_final,ctx_final,ctx_minus_control,ctx_minus_hit15,control_drawdown,hit15_drawdown,ctx_drawdown,control_wager,hit15_wager,ctx_wager,control_band,hit15_band,ctx_band\n");
        for(int i=0;i<c.size();i++){S380 a=c.get(i),d=s.get(i),e=x.get(i);b.append(String.format(Locale.ROOT,"%s,%d,%.2f,%.2f,%.2f,%.2f,%.2f,%.2f,%.2f,%.2f,%.2f,%.2f,%.2f,%s,%s,%s%n",cohort,i+1,a.finalBank,d.finalBank,e.finalBank,e.finalBank-a.finalBank,e.finalBank-d.finalBank,a.maxDrawdown,d.maxDrawdown,e.maxDrawdown,a.cumWager,d.cumWager,e.cumWager,bandName380(bandIndex380(a.finalBank)),bandName380(bandIndex380(d.finalBank)),bandName380(bandIndex380(e.finalBank))));}return b.toString();
    }

    /* PHASE 3.91 — EXISTING MAXIMUM-EXPOSURE REGION MAPPING
       No wager is increased. This instruments the frozen Phase 3.55 controller.
       The controller's highest nominal original-round wager is £30.
       This £30 state is an opportunity-mapping region; it is NOT itself re-labelled
       as a new >2x rule and does not reopen the closed 3.22–3.35 history trigger. */

    static final class R391 {
        String cohort; int session,hand;
        double bankBefore,baseWager,committedStake,net,bankAfter,peakBefore,drawdownAfter;
        boolean maxRegion,targetOnHand,bankruptOnHand;
    }
    static final class S391 {
        String cohort; int session,hands,maxRounds;
        double finalBank,peak,maxDD,cumWager,maxRegionExposure,maxRegionNet;
        boolean reached200,exhausted,everMaxRegion;
        ArrayList<R391> rounds=new ArrayList<>();
    }

    static S391 runOne391(long seed,String cohort,int session){
        int[]shoe=sixDeckShoe(sessionSeed(seed,session));
        if(!auditShoe(shoe))throw new IllegalStateException("Inventory audit failed");
        int ptr=0,hc=0;int[]hist=new int[5];
        double bank=START_BANK,peak=START_BANK,maxDD=0;
        boolean everPlunge=false,cooling=false,cautious=false,negativeEpisode=false,brakeArmed=false;
        S391 s=new S391();s.cohort=cohort;s.session=session+1;
        for(int hand=1;hand<=HANDS;hand++){
            if(bank>=200||bank<=0)break;
            if(cooling&&bank>=120){cooling=false;cautious=true;}
            if(cautious&&bank>=140)cautious=false;

            double wager;
            if(cooling){wager=10;negativeEpisode=false;brakeArmed=false;}
            else if(cautious){wager=cautious355(hist,hc);negativeEpisode=false;brakeArmed=false;}
            else if(hand>8&&bank>=140){
                int b=bal355(hist,hc);boolean neg=b<0;
                if(neg&&!negativeEpisode)brakeArmed=true;
                negativeEpisode=neg;
                double base=bank>=170?15:normal355(hand,bank);
                if(brakeArmed){wager=Math.min(base,15);brakeArmed=false;}else wager=base;
            } else {wager=normal355(hand,bank);negativeEpisode=false;brakeArmed=false;}

            wager=Math.min(wager,bank);if(wager<=0)break;
            if(!cooling&&!cautious&&hand>8&&bank>=130)everPlunge=true;

            R391 q=new R391();q.cohort=cohort;q.session=session+1;q.hand=hand;
            q.bankBefore=bank;q.baseWager=wager;q.peakBefore=peak;
            q.maxRegion=Math.abs(wager-30.0)<1e-9;

            PairStats ps=new PairStats();
            Round rr=playRound(shoe,ptr,Profile.SELECTIVE_AGGRESSION,cohort,session+1,hand,true,bank,ps,wager,false);
            ptr=rr.endPointer;
            q.committedStake=roundStake356(rr);q.net=rr.net;
            s.cumWager+=q.committedStake;
            if(q.maxRegion){s.maxRounds++;s.maxRegionExposure+=q.committedStake;s.maxRegionNet+=q.net;s.everMaxRegion=true;}

            bank+=rr.net;if(bank<0)bank=0;s.hands++;
            peak=Math.max(peak,bank);maxDD=Math.max(maxDD,peak-bank);
            q.bankAfter=bank;q.drawdownAfter=peak-bank;
            q.targetOnHand=bank>=200;q.bankruptOnHand=bank<=0;
            s.rounds.add(q);

            hist[hc%hist.length]=rr.net>1e-9?1:(rr.net<-1e-9?-1:0);hc++;
            if(bank>=200){s.reached200=true;break;}
            if(bank<=0){s.exhausted=true;break;}
            if(everPlunge&&bank<=100&&!cooling){cooling=true;cautious=false;negativeEpisode=false;brakeArmed=false;}
        }
        s.finalBank=bank;s.peak=peak;s.maxDD=maxDD;
        return s;
    }

    static ArrayList<S391> run391(long seed,String cohort){
        ArrayList<S391>a=new ArrayList<>();
        for(int i=0;i<SESSIONS;i++)a.add(runOne391(seed,cohort,i));
        return a;
    }

    static void summary391(StringBuilder r,String cohort,ArrayList<S391>a){
        long n=a.size(),ever=0,target=0,bust=0,targetEver=0,bustEver=0,targetNever=0,bustNever=0;
        long rounds=0,maxRounds=0,maxWins=0,maxLosses=0,maxPushes=0,maxTargetHands=0,maxBustHands=0;
        double finalAll=0,ddAll=0,wAll=0,finalEver=0,ddEver=0,wEver=0,exp=0,net=0,bankBefore=0,ddAfter=0;
        for(S391 s:a){
            rounds+=s.hands;maxRounds+=s.maxRounds;finalAll+=s.finalBank;ddAll+=s.maxDD;wAll+=s.cumWager;
            if(s.reached200)target++;if(s.exhausted)bust++;
            if(s.everMaxRegion){
                ever++;finalEver+=s.finalBank;ddEver+=s.maxDD;wEver+=s.cumWager;
                if(s.reached200)targetEver++;if(s.exhausted)bustEver++;
            } else {
                if(s.reached200)targetNever++;if(s.exhausted)bustNever++;
            }
            for(R391 q:s.rounds)if(q.maxRegion){
                exp+=q.committedStake;net+=q.net;bankBefore+=q.bankBefore;ddAfter+=q.drawdownAfter;
                if(q.net>1e-9)maxWins++;else if(q.net<-1e-9)maxLosses++;else maxPushes++;
                if(q.targetOnHand)maxTargetHands++;if(q.bankruptOnHand)maxBustHands++;
            }
        }
        long never=n-ever;
        r.append("\n").append(cohort).append(" — MAXIMUM-EXPOSURE REGION\n");
        r.append("Sessions: ").append(n).append("\n");
        r.append("Total played rounds: ").append(rounds).append("\n");
        r.append("£30 maximum-region rounds: ").append(maxRounds).append(" (").append(pct380(maxRounds,rounds)).append(" of played rounds)\n");
        r.append("Sessions ever entering £30 region: ").append(ever).append(" (").append(pct380(ever,n)).append(")\n");
        r.append(String.format(Locale.ROOT,"Average bankroll immediately before £30 round: £%.3f%n",maxRounds==0?0:bankBefore/maxRounds));
        r.append(String.format(Locale.ROOT,"Total committed exposure on £30 rounds incl. branches: £%.2f%n",exp));
        r.append(String.format(Locale.ROOT,"Average committed exposure per £30 round: £%.3f%n",maxRounds==0?0:exp/maxRounds));
        r.append(String.format(Locale.ROOT,"Net bankroll change on £30 rounds: £%+.2f%n",net));
        r.append(String.format(Locale.ROOT,"Return on committed £30-region exposure: %+.4f%%%n",exp==0?0:100.0*net/exp));
        r.append("£30-region W/L/P: ").append(maxWins).append(" / ").append(maxLosses).append(" / ").append(maxPushes).append("\n");
        r.append("£200 reached ON a £30-region round: ").append(maxTargetHands).append("\n");
        r.append("£0 reached ON a £30-region round: ").append(maxBustHands).append("\n");
        r.append(String.format(Locale.ROOT,"Average drawdown immediately after £30 round: £%.3f%n",maxRounds==0?0:ddAfter/maxRounds));

        r.append("\nSESSION ASSOCIATION — OBSERVATIONAL, NOT CAUSAL\n");
        r.append(String.format(Locale.ROOT,"Ever-£30 sessions average final / maxDD / cumulative wager: £%.3f / £%.3f / £%.3f%n",
            ever==0?0:finalEver/ever,ever==0?0:ddEver/ever,ever==0?0:wEver/ever));
        r.append("Ever-£30 sessions reaching £200 / £0: ").append(pct380(targetEver,ever)).append(" / ").append(pct380(bustEver,ever)).append("\n");
        r.append("Never-£30 sessions reaching £200 / £0: ").append(pct380(targetNever,never)).append(" / ").append(pct380(bustNever,never)).append("\n");
        r.append(String.format(Locale.ROOT,"Whole cohort average final / maxDD / cumulative wager: £%.3f / £%.3f / £%.3f%n",finalAll/n,ddAll/n,wAll/n));
        r.append("Whole cohort £200 / £0: ").append(pct380(target,n)).append(" / ").append(pct380(bust,n)).append("\n");
    }

    static String csv391(ArrayList<S391>...groups){
        StringBuilder b=new StringBuilder("cohort,session,hand,bank_before,base_wager,committed_stake,net,bank_after,peak_before,drawdown_after,max_region_30,target_on_hand,bankrupt_on_hand\n");
        for(ArrayList<S391>g:groups)for(S391 s:g)for(R391 q:s.rounds)
            b.append(String.format(Locale.ROOT,"%s,%d,%d,%.2f,%.2f,%.2f,%.2f,%.2f,%.2f,%.2f,%s,%s,%s%n",
                q.cohort,q.session,q.hand,q.bankBefore,q.baseWager,q.committedStake,q.net,q.bankAfter,q.peakBefore,q.drawdownAfter,
                Boolean.toString(q.maxRegion),Boolean.toString(q.targetOnHand),Boolean.toString(q.bankruptOnHand)));
        return b.toString();
    }


    /* PHASE 3.92 — PROFIT RETENTION / GIVE-BACK
       Measurement only. Frozen Phase 3.55 controller. No wager change.
       Peak-profit give-back is measured relative to accumulated profit above
       the £100 starting bankroll. */

    static final class S392 {
        String cohort; int session,hands,peakHand;
        double finalBank,peak,maxDD,cumWager,postPeakExposure;
        boolean reached200,exhausted;
    }

    static S392 runOne392(long seed,String cohort,int session){
        int[]shoe=sixDeckShoe(sessionSeed(seed,session));
        if(!auditShoe(shoe))throw new IllegalStateException("Inventory audit failed");
        int ptr=0,hc=0;int[]hist=new int[5];
        double bank=START_BANK,peak=START_BANK,maxDD=0,cum=0;
        int peakHand=0;
        boolean everPlunge=false,cooling=false,cautious=false,negativeEpisode=false,brakeArmed=false;
        ArrayList<Double> stakes=new ArrayList<>();
        ArrayList<Double> banks=new ArrayList<>();
        for(int hand=1;hand<=HANDS;hand++){
            if(bank>=200||bank<=0)break;
            if(cooling&&bank>=120){cooling=false;cautious=true;}
            if(cautious&&bank>=140)cautious=false;
            double wager;
            if(cooling){wager=10;negativeEpisode=false;brakeArmed=false;}
            else if(cautious){wager=cautious355(hist,hc);negativeEpisode=false;brakeArmed=false;}
            else if(hand>8&&bank>=140){
                int b=bal355(hist,hc);boolean neg=b<0;
                if(neg&&!negativeEpisode)brakeArmed=true;negativeEpisode=neg;
                double base=bank>=170?15:normal355(hand,bank);
                if(brakeArmed){wager=Math.min(base,15);brakeArmed=false;}else wager=base;
            } else {wager=normal355(hand,bank);negativeEpisode=false;brakeArmed=false;}
            wager=Math.min(wager,bank);if(wager<=0)break;
            if(!cooling&&!cautious&&hand>8&&bank>=130)everPlunge=true;
            PairStats ps=new PairStats();
            Round rr=playRound(shoe,ptr,Profile.SELECTIVE_AGGRESSION,cohort,session+1,hand,true,bank,ps,wager,false);
            ptr=rr.endPointer;
            double st=roundStake356(rr);cum+=st;stakes.add(st);
            bank+=rr.net;if(bank<0)bank=0;banks.add(bank);
            if(bank>peak+1e-9){peak=bank;peakHand=hand;}
            maxDD=Math.max(maxDD,peak-bank);
            hist[hc%hist.length]=rr.net>1e-9?1:(rr.net<-1e-9?-1:0);hc++;
            if(bank>=200||bank<=0)break;
            if(everPlunge&&bank<=100&&!cooling){cooling=true;cautious=false;negativeEpisode=false;brakeArmed=false;}
        }
        double post=0;
        for(int i=peakHand;i<stakes.size();i++)post+=stakes.get(i); // rounds after peak hand
        S392 x=new S392();x.cohort=cohort;x.session=session+1;x.hands=stakes.size();
        x.peak=peak;x.peakHand=peakHand;x.finalBank=bank;x.maxDD=maxDD;x.cumWager=cum;x.postPeakExposure=post;
        x.reached200=bank>=200;x.exhausted=bank<=0;
        return x;
    }

    static ArrayList<S392> run392(long seed,String cohort){
        ArrayList<S392>a=new ArrayList<>();for(int i=0;i<SESSIONS;i++)a.add(runOne392(seed,cohort,i));return a;
    }

    static void summary392(StringBuilder r,String cohort,ArrayList<S392>a){
        long n=a.size(),profitPeak=0,g25=0,g50=0,g75=0,g100=0,finishProfit=0,target=0,bust=0;
        double peakSum=0,finalSum=0,giveSum=0,givePctSum=0,postExp=0,cum=0;
        ArrayList<Double> givePcts=new ArrayList<>();
        for(S392 s:a){
            finalSum+=s.finalBank;cum+=s.cumWager;if(s.reached200)target++;if(s.exhausted)bust++;
            if(s.peak>100+1e-9){
                profitPeak++;double pp=s.peak-100,gb=Math.max(0,s.peak-s.finalBank),gp=100*gb/pp;
                peakSum+=s.peak;giveSum+=gb;givePctSum+=gp;givePcts.add(gp);postExp+=s.postPeakExposure;
                if(gp>=25-1e-9)g25++;if(gp>=50-1e-9)g50++;if(gp>=75-1e-9)g75++;if(gp>=100-1e-9)g100++;
                if(s.finalBank>100+1e-9)finishProfit++;
            }
        }
        Collections.sort(givePcts);
        double med=givePcts.isEmpty()?0:givePcts.get(givePcts.size()/2);
        r.append("\n").append(cohort).append("\n");
        r.append("Sessions: ").append(n).append("\n");
        r.append("Sessions ever above £100: ").append(profitPeak).append(" (").append(pct380(profitPeak,n)).append(")\n");
        r.append(String.format(Locale.ROOT,"Average peak among profit-peaking sessions: £%.3f%n",profitPeak==0?0:peakSum/profitPeak));
        r.append(String.format(Locale.ROOT,"Average final bankroll, whole cohort: £%.3f%n",finalSum/n));
        r.append(String.format(Locale.ROOT,"Average peak-to-finish give-back among profit-peaking sessions: £%.3f%n",profitPeak==0?0:giveSum/profitPeak));
        r.append(String.format(Locale.ROOT,"Average / median give-back as %% of accumulated peak profit: %.3f%% / %.3f%%%n",profitPeak==0?0:givePctSum/profitPeak,med));
        r.append("Surrendered >=25% of peak profit: ").append(pct380(g25,profitPeak)).append("\n");
        r.append("Surrendered >=50%: ").append(pct380(g50,profitPeak)).append("\n");
        r.append("Surrendered >=75%: ").append(pct380(g75,profitPeak)).append("\n");
        r.append("Surrendered >=100% (all peak profit or more): ").append(pct380(g100,profitPeak)).append("\n");
        r.append("Profit-peaking sessions still finishing >£100: ").append(pct380(finishProfit,profitPeak)).append("\n");
        r.append(String.format(Locale.ROOT,"Average exposure AFTER the eventual peak: £%.3f%n",profitPeak==0?0:postExp/profitPeak));
        r.append(String.format(Locale.ROOT,"Whole-cohort average cumulative wager: £%.3f%n",cum/n));
        r.append("Whole-cohort £200 / £0: ").append(pct380(target,n)).append(" / ").append(pct380(bust,n)).append("\n");
    }

    static String csv392(ArrayList<S392>...groups){
        StringBuilder b=new StringBuilder("cohort,session,hands,peak_bankroll,peak_hand,final_bankroll,peak_profit,giveback_amount,giveback_pct_of_peak_profit,post_peak_exposure,max_drawdown,cumulative_wager,reached_200,exhausted\n");
        for(ArrayList<S392>g:groups)for(S392 s:g){
            double pp=Math.max(0,s.peak-100),gb=pp>0?Math.max(0,s.peak-s.finalBank):0,gp=pp>0?100*gb/pp:0;
            b.append(String.format(Locale.ROOT,"%s,%d,%d,%.2f,%d,%.2f,%.2f,%.2f,%.4f,%.2f,%.2f,%.2f,%s,%s%n",
                s.cohort,s.session,s.hands,s.peak,s.peakHand,s.finalBank,pp,gb,gp,s.postPeakExposure,s.maxDD,s.cumWager,
                Boolean.toString(s.reached200),Boolean.toString(s.exhausted)));
        }return b.toString();
    }


    /* PHASE 3.93 — OBSERVABLE PROFIT-TIER RETENTION MAP
       Measurement only. Frozen Phase 3.55 controller.
       Milestones are prospectively observable bankroll states:
       £110, £120, ..., £190.
       We measure what happens AFTER FIRST ATTAINMENT. No stopping rule. */

    static final int[] TIERS393={110,120,130,140,150,160,170,180,190};

    static final class S393 {
        String cohort; int session,hands;
        double finalBank,maxDD,cumWager;
        boolean reached200,exhausted;
        int[] firstHand=new int[TIERS393.length];
        double[] postExposure=new double[TIERS393.length];
        double[] maxAfter=new double[TIERS393.length];
        boolean[] below100After=new boolean[TIERS393.length];
        boolean[] exhaustedAfter=new boolean[TIERS393.length];
        boolean[] reached200After=new boolean[TIERS393.length];
    }

    static S393 runOne393(long seed,String cohort,int session){
        int[]shoe=sixDeckShoe(sessionSeed(seed,session));
        if(!auditShoe(shoe))throw new IllegalStateException("Inventory audit failed");
        int ptr=0,hc=0;int[]hist=new int[5];
        double bank=START_BANK,peak=START_BANK,maxDD=0,cum=0;
        boolean everPlunge=false,cooling=false,cautious=false,negativeEpisode=false,brakeArmed=false;
        S393 x=new S393();x.cohort=cohort;x.session=session+1;
        Arrays.fill(x.firstHand,-1);
        for(int hand=1;hand<=HANDS;hand++){
            if(bank>=200||bank<=0)break;
            if(cooling&&bank>=120){cooling=false;cautious=true;}
            if(cautious&&bank>=140)cautious=false;
            double wager;
            if(cooling){wager=10;negativeEpisode=false;brakeArmed=false;}
            else if(cautious){wager=cautious355(hist,hc);negativeEpisode=false;brakeArmed=false;}
            else if(hand>8&&bank>=140){
                int b=bal355(hist,hc);boolean neg=b<0;
                if(neg&&!negativeEpisode)brakeArmed=true;negativeEpisode=neg;
                double base=bank>=170?15:normal355(hand,bank);
                if(brakeArmed){wager=Math.min(base,15);brakeArmed=false;}else wager=base;
            } else {wager=normal355(hand,bank);negativeEpisode=false;brakeArmed=false;}
            wager=Math.min(wager,bank);if(wager<=0)break;
            if(!cooling&&!cautious&&hand>8&&bank>=130)everPlunge=true;

            PairStats ps=new PairStats();
            Round rr=playRound(shoe,ptr,Profile.SELECTIVE_AGGRESSION,cohort,session+1,hand,true,bank,ps,wager,false);
            ptr=rr.endPointer;double stake=roundStake356(rr);cum+=stake;
            for(int j=0;j<TIERS393.length;j++)if(x.firstHand[j]>=0)x.postExposure[j]+=stake;

            bank+=rr.net;if(bank<0)bank=0;x.hands++;
            peak=Math.max(peak,bank);maxDD=Math.max(maxDD,peak-bank);

            for(int j=0;j<TIERS393.length;j++){
                if(x.firstHand[j]<0 && bank>=TIERS393[j]-1e-9){
                    x.firstHand[j]=hand;x.maxAfter[j]=bank;
                }
                if(x.firstHand[j]>=0){
                    x.maxAfter[j]=Math.max(x.maxAfter[j],bank);
                    if(hand>x.firstHand[j] && bank<100-1e-9)x.below100After[j]=true;
                    if(bank>=200-1e-9)x.reached200After[j]=true;
                    if(bank<=1e-9)x.exhaustedAfter[j]=true;
                }
            }

            hist[hc%hist.length]=rr.net>1e-9?1:(rr.net<-1e-9?-1:0);hc++;
            if(bank>=200){x.reached200=true;break;}
            if(bank<=0){x.exhausted=true;break;}
            if(everPlunge&&bank<=100&&!cooling){cooling=true;cautious=false;negativeEpisode=false;brakeArmed=false;}
        }
        x.finalBank=bank;x.maxDD=maxDD;x.cumWager=cum;return x;
    }

    static ArrayList<S393> run393(long seed,String cohort){
        ArrayList<S393>a=new ArrayList<>();for(int i=0;i<SESSIONS;i++)a.add(runOne393(seed,cohort,i));return a;
    }

    static void tiers393(StringBuilder r,String cohort,ArrayList<S393>a){
        r.append("\n").append(cohort).append("\n");
        r.append("Tier | Reached | Avg first hand | Finish >=tier | Finish >£100 | Later £200 | Later <£100 | Later £0 | Avg post-tier exposure | Avg give-back from max-after-tier\n");
        for(int j=0;j<TIERS393.length;j++){
            long n=0,ret=0,profit=0,t200=0,below=0,bust=0;double fh=0,pex=0,gb=0;
            for(S393 s:a)if(s.firstHand[j]>=0){
                n++;fh+=s.firstHand[j];pex+=s.postExposure[j];
                if(s.finalBank>=TIERS393[j]-1e-9)ret++;
                if(s.finalBank>100+1e-9)profit++;
                if(s.reached200After[j])t200++;
                if(s.below100After[j])below++;
                if(s.exhaustedAfter[j])bust++;
                gb+=Math.max(0,s.maxAfter[j]-s.finalBank);
            }
            r.append(String.format(Locale.ROOT,"£%d | %d (%.2f%%) | %.2f | %.2f%% | %.2f%% | %.2f%% | %.2f%% | %.2f%% | £%.2f | £%.2f%n",
                TIERS393[j],n,100.0*n/a.size(),n==0?0:fh/n,n==0?0:100.0*ret/n,n==0?0:100.0*profit/n,
                n==0?0:100.0*t200/n,n==0?0:100.0*below/n,n==0?0:100.0*bust/n,n==0?0:pex/n,n==0?0:gb/n));
        }
    }

    static String csv393(ArrayList<S393>...groups){
        StringBuilder b=new StringBuilder("cohort,session,tier,first_reach_hand,final_bankroll,finish_ge_tier,finish_gt_100,later_reach_200,later_below_100,later_exhausted,post_tier_exposure,max_after_tier,giveback_from_max_after_tier\n");
        for(ArrayList<S393>g:groups)for(S393 s:g)for(int j=0;j<TIERS393.length;j++)if(s.firstHand[j]>=0)
            b.append(String.format(Locale.ROOT,"%s,%d,%d,%d,%.2f,%s,%s,%s,%s,%s,%.2f,%.2f,%.2f%n",
                s.cohort,s.session,TIERS393[j],s.firstHand[j],s.finalBank,
                Boolean.toString(s.finalBank>=TIERS393[j]-1e-9),Boolean.toString(s.finalBank>100+1e-9),
                Boolean.toString(s.reached200After[j]),Boolean.toString(s.below100After[j]),Boolean.toString(s.exhaustedAfter[j]),
                s.postExposure[j],s.maxAfter[j],Math.max(0,s.maxAfter[j]-s.finalBank)));
        return b.toString();
    }


    /* PHASE 3.94 — PREDECLARED PROFIT-EXIT INTERVENTION SCREEN
       Frozen BEFORE execution from Phase 3.93:
       REFERENCE (unchanged 30-hand architecture), EXIT_140, EXIT_150, EXIT_160.
       Exit policies stop immediately after the first completed round on which
       bankroll reaches/exceeds the threshold. No wager-controller change. */

    static final int[] EXITS394={0,140,150,160};
    static final String[] NAMES394={"REFERENCE_30_HAND","EXIT_140","EXIT_150","EXIT_160"};

    static final class S394 {
        String cohort,policy; int session,hands,exitHand;
        double finalBank,peak,maxDD,cumWager,exitBank;
        boolean reached200,exhausted,exited;
        boolean everBelow100,recovered100;
        double maxProfitGiveback;
    }

    static S394 runOne394(long seed,String cohort,int session,int exitThreshold,String policy){
        int[]shoe=sixDeckShoe(sessionSeed(seed,session));
        if(!auditShoe(shoe))throw new IllegalStateException("Inventory audit failed");
        int ptr=0,hc=0;int[]hist=new int[5];
        double bank=START_BANK,peak=START_BANK,maxDD=0,cum=0;
        boolean everPlunge=false,cooling=false,cautious=false,negativeEpisode=false,brakeArmed=false;
        boolean everBelow=false,recovered=false;
        S394 x=new S394();x.cohort=cohort;x.policy=policy;x.session=session+1;
        for(int hand=1;hand<=HANDS;hand++){
            if(bank>=200||bank<=0)break;
            if(cooling&&bank>=120){cooling=false;cautious=true;}
            if(cautious&&bank>=140)cautious=false;
            double wager;
            if(cooling){wager=10;negativeEpisode=false;brakeArmed=false;}
            else if(cautious){wager=cautious355(hist,hc);negativeEpisode=false;brakeArmed=false;}
            else if(hand>8&&bank>=140){
                int b=bal355(hist,hc);boolean neg=b<0;
                if(neg&&!negativeEpisode)brakeArmed=true;negativeEpisode=neg;
                double base=bank>=170?15:normal355(hand,bank);
                if(brakeArmed){wager=Math.min(base,15);brakeArmed=false;}else wager=base;
            } else {wager=normal355(hand,bank);negativeEpisode=false;brakeArmed=false;}
            wager=Math.min(wager,bank);if(wager<=0)break;
            if(!cooling&&!cautious&&hand>8&&bank>=130)everPlunge=true;

            PairStats ps=new PairStats();
            Round rr=playRound(shoe,ptr,Profile.SELECTIVE_AGGRESSION,cohort,session+1,hand,true,bank,ps,wager,false);
            ptr=rr.endPointer;cum+=roundStake356(rr);
            bank+=rr.net;if(bank<0)bank=0;x.hands++;
            peak=Math.max(peak,bank);maxDD=Math.max(maxDD,peak-bank);

            if(bank<100-1e-9)everBelow=true;
            if(everBelow&&bank>=100-1e-9)recovered=true;

            hist[hc%hist.length]=rr.net>1e-9?1:(rr.net<-1e-9?-1:0);hc++;

            if(bank>=200-1e-9){x.reached200=true;break;}
            if(bank<=1e-9){x.exhausted=true;break;}

            if(exitThreshold>0 && bank>=exitThreshold-1e-9){
                x.exited=true;x.exitHand=hand;x.exitBank=bank;break;
            }

            if(everPlunge&&bank<=100&&!cooling){cooling=true;cautious=false;negativeEpisode=false;brakeArmed=false;}
        }
        x.finalBank=bank;x.peak=peak;x.maxDD=maxDD;x.cumWager=cum;
        x.everBelow100=everBelow;x.recovered100=recovered;
        x.maxProfitGiveback=Math.max(0,peak-bank);
        return x;
    }

    static ArrayList<S394> run394(long seed,String cohort,int threshold,String policy){
        ArrayList<S394>a=new ArrayList<>();
        for(int i=0;i<SESSIONS;i++)a.add(runOne394(seed,cohort,i,threshold,policy));
        return a;
    }

    static void summary394(StringBuilder r,String cohort,String policy,ArrayList<S394>a){
        long n=a.size(),pos=0,be=0,loss=0,t200=0,bust=0,exit=0,below=0,rec=0;
        double fin=0,dd=0,w=0,gb=0,exitHand=0,exitBank=0;
        for(S394 s:a){
            fin+=s.finalBank;dd+=s.maxDD;w+=s.cumWager;gb+=s.maxProfitGiveback;
            if(s.finalBank>100+1e-9)pos++;else if(Math.abs(s.finalBank-100)<1e-9)be++;else loss++;
            if(s.reached200)t200++;if(s.exhausted)bust++;
            if(s.exited){exit++;exitHand+=s.exitHand;exitBank+=s.exitBank;}
            if(s.everBelow100){below++;if(s.recovered100)rec++;}
        }
        double net=fin-100.0*n;
        r.append("\n").append(cohort).append(" / ").append(policy).append("\n");
        r.append(String.format(Locale.ROOT,"Average ending bankroll: £%.3f%n",fin/n));
        r.append(String.format(Locale.ROOT,"Population net change: £%+.2f%n",net));
        r.append(String.format(Locale.ROOT,"Population return on cumulative wager: %+.4f%%%n",w==0?0:100*net/w));
        r.append("Positive / break-even / losing sessions: ").append(pct380(pos,n)).append(" / ").append(pct380(be,n)).append(" / ").append(pct380(loss,n)).append("\n");
        r.append("£200 attainment: ").append(pct380(t200,n)).append("\n");
        r.append("£0 exhaustion: ").append(pct380(bust,n)).append("\n");
        r.append(String.format(Locale.ROOT,"Average maximum drawdown: £%.3f%n",dd/n));
        r.append(String.format(Locale.ROOT,"Average cumulative wager: £%.3f%n",w/n));
        r.append("Sessions ever below £100: ").append(pct380(below,n)).append("\n");
        r.append("Recovery to >=£100 among below-£100 sessions: ").append(pct380(rec,below)).append("\n");
        r.append(String.format(Locale.ROOT,"Average peak-to-finish give-back: £%.3f%n",gb/n));
        r.append(String.format(Locale.ROOT,"Bankroll change per £100 wagered: £%+.3f%n",w==0?0:100*net/w));
        if(exit>0){
            r.append("Policy exits triggered: ").append(pct380(exit,n)).append("\n");
            r.append(String.format(Locale.ROOT,"Average exit hand / retained bankroll when triggered: %.2f / £%.3f%n",exitHand/exit,exitBank/exit));
        } else r.append("Policy exits triggered: N/A (reference)\n");
    }

    static String csv394(ArrayList<S394>...groups){
        StringBuilder b=new StringBuilder("cohort,policy,session,hands,final_bankroll,peak_bankroll,max_drawdown,cumulative_wager,reached_200,exhausted,positive,ever_below_100,recovered_100,peak_to_finish_giveback,exit_triggered,exit_hand,exit_bankroll\n");
        for(ArrayList<S394>g:groups)for(S394 s:g)
            b.append(String.format(Locale.ROOT,"%s,%s,%d,%d,%.2f,%.2f,%.2f,%.2f,%s,%s,%s,%s,%s,%.2f,%s,%d,%.2f%n",
                s.cohort,s.policy,s.session,s.hands,s.finalBank,s.peak,s.maxDD,s.cumWager,
                Boolean.toString(s.reached200),Boolean.toString(s.exhausted),Boolean.toString(s.finalBank>100+1e-9),
                Boolean.toString(s.everBelow100),Boolean.toString(s.recovered100),s.maxProfitGiveback,
                Boolean.toString(s.exited),s.exitHand,s.exitBank));
        return b.toString();
    }


    /* PHASE 3.95 — TRAILING / HIGH-WATER PROFIT-PROTECTION MAP
       MEASUREMENT ONLY.
       No session is stopped and no wager is changed.
       We retrospectively mark the FIRST observable occasion after a session has
       reached a qualifying high-water profit state where bankroll gives back
       25%, 50%, 75%, or 100% of the accumulated profit above £100.
       Activation floors £140/£150/£160 are mapped separately.
       This is candidate discovery, not an intervention. */

    static final int[] FLOORS395={140,150,160};
    static final int[] BANDS395={25,50,75,100};

    static final class S395 {
        String cohort; int session,hands;
        double finalBank,peak,maxDD,cumWager;
        boolean reached200,exhausted;
        int[][] trigHand=new int[FLOORS395.length][BANDS395.length];
        double[][] trigBank=new double[FLOORS395.length][BANDS395.length];
        double[][] trigPeak=new double[FLOORS395.length][BANDS395.length];
        double[][] postTrigExposure=new double[FLOORS395.length][BANDS395.length];
        boolean[][] later200=new boolean[FLOORS395.length][BANDS395.length];
        boolean[][] later0=new boolean[FLOORS395.length][BANDS395.length];
        boolean[][] laterBelow100=new boolean[FLOORS395.length][BANDS395.length];
    }

    static S395 runOne395(long seed,String cohort,int session){
        int[]shoe=sixDeckShoe(sessionSeed(seed,session));
        if(!auditShoe(shoe))throw new IllegalStateException("Inventory audit failed");
        int ptr=0,hc=0;int[]hist=new int[5];
        double bank=START_BANK,peak=START_BANK,maxDD=0,cum=0;
        boolean everPlunge=false,cooling=false,cautious=false,negativeEpisode=false,brakeArmed=false;
        boolean[] activated=new boolean[FLOORS395.length];
        S395 x=new S395();x.cohort=cohort;x.session=session+1;
        for(int i=0;i<FLOORS395.length;i++)Arrays.fill(x.trigHand[i],-1);

        for(int hand=1;hand<=HANDS;hand++){
            if(bank>=200||bank<=0)break;
            if(cooling&&bank>=120){cooling=false;cautious=true;}
            if(cautious&&bank>=140)cautious=false;
            double wager;
            if(cooling){wager=10;negativeEpisode=false;brakeArmed=false;}
            else if(cautious){wager=cautious355(hist,hc);negativeEpisode=false;brakeArmed=false;}
            else if(hand>8&&bank>=140){
                int b=bal355(hist,hc);boolean neg=b<0;
                if(neg&&!negativeEpisode)brakeArmed=true;negativeEpisode=neg;
                double base=bank>=170?15:normal355(hand,bank);
                if(brakeArmed){wager=Math.min(base,15);brakeArmed=false;}else wager=base;
            } else {wager=normal355(hand,bank);negativeEpisode=false;brakeArmed=false;}
            wager=Math.min(wager,bank);if(wager<=0)break;
            if(!cooling&&!cautious&&hand>8&&bank>=130)everPlunge=true;

            PairStats ps=new PairStats();
            Round rr=playRound(shoe,ptr,Profile.SELECTIVE_AGGRESSION,cohort,session+1,hand,true,bank,ps,wager,false);
            ptr=rr.endPointer;double stake=roundStake356(rr);cum+=stake;

            // Any already-triggered candidate continues to accumulate actual exposure.
            for(int i=0;i<FLOORS395.length;i++)for(int j=0;j<BANDS395.length;j++)
                if(x.trigHand[i][j]>=0)x.postTrigExposure[i][j]+=stake;

            bank+=rr.net;if(bank<0)bank=0;x.hands++;
            peak=Math.max(peak,bank);maxDD=Math.max(maxDD,peak-bank);

            for(int i=0;i<FLOORS395.length;i++){
                if(bank>=FLOORS395[i]-1e-9)activated[i]=true;
                if(activated[i] && peak>100+1e-9){
                    double profit=peak-100;
                    double give=Math.max(0,peak-bank);
                    double pct=100*give/profit;
                    for(int j=0;j<BANDS395.length;j++)if(x.trigHand[i][j]<0 && pct>=BANDS395[j]-1e-9){
                        x.trigHand[i][j]=hand;x.trigBank[i][j]=bank;x.trigPeak[i][j]=peak;
                    }
                }
                for(int j=0;j<BANDS395.length;j++)if(x.trigHand[i][j]>=0 && hand>x.trigHand[i][j]){
                    if(bank>=200-1e-9)x.later200[i][j]=true;
                    if(bank<=1e-9)x.later0[i][j]=true;
                    if(bank<100-1e-9)x.laterBelow100[i][j]=true;
                }
            }

            hist[hc%hist.length]=rr.net>1e-9?1:(rr.net<-1e-9?-1:0);hc++;
            if(bank>=200){x.reached200=true;break;}
            if(bank<=0){x.exhausted=true;break;}
            if(everPlunge&&bank<=100&&!cooling){cooling=true;cautious=false;negativeEpisode=false;brakeArmed=false;}
        }
        x.finalBank=bank;x.peak=peak;x.maxDD=maxDD;x.cumWager=cum;return x;
    }

    static ArrayList<S395> run395(long seed,String cohort){
        ArrayList<S395>a=new ArrayList<>();for(int i=0;i<SESSIONS;i++)a.add(runOne395(seed,cohort,i));return a;
    }

    static void map395(StringBuilder r,String cohort,ArrayList<S395>a){
        r.append("\n").append(cohort).append("\n");
        r.append("Activation | Give-back trigger | Triggered sessions | Avg trigger hand | Avg peak at trigger | Avg bankroll at trigger | Later £200 | Later <£100 | Later £0 | Avg exposure after trigger\n");
        for(int i=0;i<FLOORS395.length;i++)for(int j=0;j<BANDS395.length;j++){
            long n=0,t200=0,below=0,bust=0;double h=0,p=0,b=0,e=0;
            for(S395 s:a)if(s.trigHand[i][j]>=0){
                n++;h+=s.trigHand[i][j];p+=s.trigPeak[i][j];b+=s.trigBank[i][j];e+=s.postTrigExposure[i][j];
                if(s.later200[i][j])t200++;if(s.laterBelow100[i][j])below++;if(s.later0[i][j])bust++;
            }
            r.append(String.format(Locale.ROOT,"£%d | %d%% | %d (%.2f%%) | %.2f | £%.2f | £%.2f | %.2f%% | %.2f%% | %.2f%% | £%.2f%n",
                FLOORS395[i],BANDS395[j],n,100.0*n/a.size(),n==0?0:h/n,n==0?0:p/n,n==0?0:b/n,
                n==0?0:100.0*t200/n,n==0?0:100.0*below/n,n==0?0:100.0*bust/n,n==0?0:e/n));
        }
    }

    static String csv395(ArrayList<S395>...groups){
        StringBuilder b=new StringBuilder("cohort,session,activation_floor,giveback_trigger_pct,triggered,trigger_hand,peak_at_trigger,bankroll_at_trigger,post_trigger_exposure,later_200,later_below_100,later_0,final_bankroll\n");
        for(ArrayList<S395>g:groups)for(S395 s:g)for(int i=0;i<FLOORS395.length;i++)for(int j=0;j<BANDS395.length;j++)
            b.append(String.format(Locale.ROOT,"%s,%d,%d,%d,%s,%d,%.2f,%.2f,%.2f,%s,%s,%s,%.2f%n",
                s.cohort,s.session,FLOORS395[i],BANDS395[j],Boolean.toString(s.trigHand[i][j]>=0),s.trigHand[i][j],
                s.trigPeak[i][j],s.trigBank[i][j],s.postTrigExposure[i][j],
                Boolean.toString(s.later200[i][j]),Boolean.toString(s.laterBelow100[i][j]),Boolean.toString(s.later0[i][j]),s.finalBank));
        return b.toString();
    }


    /* PHASE 3.96 — PREDECLARED TRAILING PROFIT-STOP INTERVENTION
       Frozen before execution from Phase 3.95:
       activation floor £160;
       trigger = >=25% surrender of running accumulated profit above £100;
       action = terminate immediately after the completed round producing trigger.
       Comparators:
       unchanged 30-hand reference;
       hard EXIT160 contextual benchmark.
       No wager or multiplier rule is changed. */

    static final String REF396="REFERENCE_30_HAND";
    static final String HARD396="HARD_EXIT_160";
    static final String TRAIL396="TRAIL_160_GIVEBACK_25";

    static final class S396 {
        String cohort,policy; int session,hands,stopHand;
        double finalBank,peak,maxDD,cumWager,stopBank;
        boolean reached200,exhausted,stopped,everBelow100,recovered100;
        double giveback;
    }

    static S396 runOne396(long seed,String cohort,int session,String policy){
        int[]shoe=sixDeckShoe(sessionSeed(seed,session));
        if(!auditShoe(shoe))throw new IllegalStateException("Inventory audit failed");
        int ptr=0,hc=0;int[]hist=new int[5];
        double bank=START_BANK,peak=START_BANK,maxDD=0,cum=0;
        boolean everPlunge=false,cooling=false,cautious=false,negativeEpisode=false,brakeArmed=false;
        boolean activated160=false,everBelow=false,recovered=false;
        S396 x=new S396();x.cohort=cohort;x.policy=policy;x.session=session+1;

        for(int hand=1;hand<=HANDS;hand++){
            if(bank>=200||bank<=0)break;
            if(cooling&&bank>=120){cooling=false;cautious=true;}
            if(cautious&&bank>=140)cautious=false;

            double wager;
            if(cooling){wager=10;negativeEpisode=false;brakeArmed=false;}
            else if(cautious){wager=cautious355(hist,hc);negativeEpisode=false;brakeArmed=false;}
            else if(hand>8&&bank>=140){
                int b=bal355(hist,hc);boolean neg=b<0;
                if(neg&&!negativeEpisode)brakeArmed=true;
                negativeEpisode=neg;
                double base=bank>=170?15:normal355(hand,bank);
                if(brakeArmed){wager=Math.min(base,15);brakeArmed=false;}else wager=base;
            } else {
                wager=normal355(hand,bank);negativeEpisode=false;brakeArmed=false;
            }

            wager=Math.min(wager,bank);if(wager<=0)break;
            if(!cooling&&!cautious&&hand>8&&bank>=130)everPlunge=true;

            PairStats ps=new PairStats();
            Round rr=playRound(shoe,ptr,Profile.SELECTIVE_AGGRESSION,cohort,session+1,hand,true,bank,ps,wager,false);
            ptr=rr.endPointer;
            cum+=roundStake356(rr);
            bank+=rr.net;if(bank<0)bank=0;x.hands++;

            peak=Math.max(peak,bank);
            maxDD=Math.max(maxDD,peak-bank);

            if(bank<100-1e-9)everBelow=true;
            if(everBelow&&bank>=100-1e-9)recovered=true;

            hist[hc%hist.length]=rr.net>1e-9?1:(rr.net<-1e-9?-1:0);hc++;

            // Existing target/exhaustion termination retains precedence.
            if(bank>=200-1e-9){x.reached200=true;break;}
            if(bank<=1e-9){x.exhausted=true;break;}

            if(bank>=160-1e-9)activated160=true;

            if(policy.equals(HARD396) && activated160){
                x.stopped=true;x.stopHand=hand;x.stopBank=bank;break;
            }

            if(policy.equals(TRAIL396) && activated160 && peak>100+1e-9){
                double accumulatedProfit=peak-100.0;
                double surrendered=Math.max(0,peak-bank);
                double givePct=100.0*surrendered/accumulatedProfit;
                if(givePct>=25.0-1e-9){
                    x.stopped=true;x.stopHand=hand;x.stopBank=bank;break;
                }
            }

            if(everPlunge&&bank<=100&&!cooling){
                cooling=true;cautious=false;negativeEpisode=false;brakeArmed=false;
            }
        }

        x.finalBank=bank;x.peak=peak;x.maxDD=maxDD;x.cumWager=cum;
        x.everBelow100=everBelow;x.recovered100=recovered;
        x.giveback=Math.max(0,peak-bank);
        return x;
    }

    static ArrayList<S396> run396(long seed,String cohort,String policy){
        ArrayList<S396>a=new ArrayList<>();
        for(int i=0;i<SESSIONS;i++)a.add(runOne396(seed,cohort,i,policy));
        return a;
    }

    static void summary396(StringBuilder r,String cohort,String policy,ArrayList<S396>a){
        long n=a.size(),pos=0,be=0,loss=0,t200=0,bust=0,stop=0,below=0,rec=0;
        double fin=0,dd=0,w=0,gb=0,sh=0,sb=0;
        for(S396 s:a){
            fin+=s.finalBank;dd+=s.maxDD;w+=s.cumWager;gb+=s.giveback;
            if(s.finalBank>100+1e-9)pos++;else if(Math.abs(s.finalBank-100)<1e-9)be++;else loss++;
            if(s.reached200)t200++;if(s.exhausted)bust++;
            if(s.stopped){stop++;sh+=s.stopHand;sb+=s.stopBank;}
            if(s.everBelow100){below++;if(s.recovered100)rec++;}
        }
        double net=fin-100.0*n;
        r.append("\n").append(cohort).append(" / ").append(policy).append("\n");
        r.append(String.format(Locale.ROOT,"Average ending bankroll: £%.3f%n",fin/n));
        r.append(String.format(Locale.ROOT,"Population net change: £%+.2f%n",net));
        r.append(String.format(Locale.ROOT,"Population return on cumulative wager: %+.4f%%%n",w==0?0:100.0*net/w));
        r.append("Positive / break-even / losing sessions: ").append(pct380(pos,n)).append(" / ").append(pct380(be,n)).append(" / ").append(pct380(loss,n)).append("\n");
        r.append("£200 attainment: ").append(pct380(t200,n)).append("\n");
        r.append("£0 exhaustion: ").append(pct380(bust,n)).append("\n");
        r.append(String.format(Locale.ROOT,"Average maximum drawdown: £%.3f%n",dd/n));
        r.append(String.format(Locale.ROOT,"Average cumulative wager: £%.3f%n",w/n));
        r.append("Sessions ever below £100: ").append(pct380(below,n)).append("\n");
        r.append("Recovery to >=£100 among below-£100 sessions: ").append(pct380(rec,below)).append("\n");
        r.append(String.format(Locale.ROOT,"Average peak-to-finish give-back: £%.3f%n",gb/n));
        r.append(String.format(Locale.ROOT,"Bankroll change per £100 wagered: £%+.3f%n",w==0?0:100.0*net/w));
        if(stop>0){
            r.append("Policy stops triggered: ").append(pct380(stop,n)).append("\n");
            r.append(String.format(Locale.ROOT,"Average stop hand / retained bankroll: %.2f / £%.3f%n",sh/stop,sb/stop));
        } else r.append("Policy stops triggered: N/A / none\n");
    }

    static String csv396(ArrayList<S396>...groups){
        StringBuilder b=new StringBuilder("cohort,policy,session,hands,final_bankroll,peak_bankroll,max_drawdown,cumulative_wager,reached_200,exhausted,positive,ever_below_100,recovered_100,peak_to_finish_giveback,stop_triggered,stop_hand,stop_bankroll\n");
        for(ArrayList<S396>g:groups)for(S396 s:g)
            b.append(String.format(Locale.ROOT,"%s,%s,%d,%d,%.2f,%.2f,%.2f,%.2f,%s,%s,%s,%s,%s,%.2f,%s,%d,%.2f%n",
                s.cohort,s.policy,s.session,s.hands,s.finalBank,s.peak,s.maxDD,s.cumWager,
                Boolean.toString(s.reached200),Boolean.toString(s.exhausted),
                Boolean.toString(s.finalBank>100+1e-9),Boolean.toString(s.everBelow100),
                Boolean.toString(s.recovered100),s.giveback,Boolean.toString(s.stopped),s.stopHand,s.stopBank));
        return b.toString();
    }


    /* PHASE 3.97 — PAIRED CAUSAL DECOMPOSITION
       No new policy and no threshold tuning.
       Pair the frozen Phase 3.96 TRAIL160/25 outcome with the unchanged
       reference outcome for the identical cohort/session seed.
       Classification concerns only sessions in which the trailing stop fires. */

    static final class P397 {
        String cohort; int session;
        S396 ref,trail;
        String className;
        double finalDelta,wagerRemoved,drawdownReduced,profitPreserved;
    }

    static String class397(S396 r,S396 t){
        if(!t.stopped)return "UNCHANGED_NOT_TRIGGERED";
        if(r.reached200)return "PREMATURE_EXIT_SACRIFICED_200";
        if(r.exhausted)return "GOOD_SAVE_PREVENTED_0";
        if(r.finalBank<100-1e-9 && t.finalBank>=100-1e-9)return "GOOD_SAVE_PREVENTED_LOSS_FINISH";
        if(t.finalBank>r.finalBank+1e-9)return "GOOD_SAVE_HIGHER_FINAL";
        if(Math.abs(t.finalBank-r.finalBank)<=1e-9)return "NEUTRAL_SAME_FINAL";
        return "PREMATURE_EXIT_LOWER_FINAL";
    }

    static ArrayList<P397> pair397(long seed,String cohort){
        ArrayList<P397>a=new ArrayList<>();
        for(int i=0;i<SESSIONS;i++){
            S396 r=runOne396(seed,cohort,i,REF396);
            S396 t=runOne396(seed,cohort,i,TRAIL396);
            P397 p=new P397();p.cohort=cohort;p.session=i+1;p.ref=r;p.trail=t;
            p.className=class397(r,t);
            p.finalDelta=t.finalBank-r.finalBank;
            p.wagerRemoved=r.cumWager-t.cumWager;
            p.drawdownReduced=r.maxDD-t.maxDD;
            p.profitPreserved=t.finalBank-r.finalBank;
            a.add(p);
        }return a;
    }

    static void summary397(StringBuilder r,String cohort,ArrayList<P397>a){
        long trig=0,ref200=0,ref0=0,refLoss=0,refPos=0;
        long higher=0,lower=0,same=0,good=0,prem=0;
        double dFinal=0,wRemoved=0,ddReduced=0,stopBank=0,stopHand=0;
        TreeMap<String,Integer> cls=new TreeMap<>();
        for(P397 p:a){
            if(!p.trail.stopped)continue;
            trig++;
            cls.put(p.className,cls.getOrDefault(p.className,0)+1);
            if(p.ref.reached200)ref200++;
            if(p.ref.exhausted)ref0++;
            if(p.ref.finalBank<100-1e-9)refLoss++;
            if(p.ref.finalBank>100+1e-9)refPos++;
            if(p.finalDelta>1e-9)higher++;else if(p.finalDelta<-1e-9)lower++;else same++;
            if(p.className.startsWith("GOOD_SAVE"))good++;
            if(p.className.startsWith("PREMATURE_EXIT"))prem++;
            dFinal+=p.finalDelta;wRemoved+=p.wagerRemoved;ddReduced+=p.drawdownReduced;
            stopBank+=p.trail.stopBank;stopHand+=p.trail.stopHand;
        }
        r.append("\n").append(cohort).append("\n");
        r.append("Trailing-triggered paired sessions: ").append(trig).append(" (").append(pct380(trig,a.size())).append(")\n");
        r.append("REFERENCE fate of those same sessions:\n");
        r.append("  Would later reach £200: ").append(pct380(ref200,trig)).append("\n");
        r.append("  Would later exhaust £0: ").append(pct380(ref0,trig)).append("\n");
        r.append("  Would finish below £100: ").append(pct380(refLoss,trig)).append("\n");
        r.append("  Would finish above £100: ").append(pct380(refPos,trig)).append("\n");
        r.append("TRAILING versus paired REFERENCE final bankroll:\n");
        r.append("  Higher under trailing: ").append(pct380(higher,trig)).append("\n");
        r.append("  Lower under trailing: ").append(pct380(lower,trig)).append("\n");
        r.append("  Same: ").append(pct380(same,trig)).append("\n");
        r.append(String.format(Locale.ROOT,"  Average paired final-bankroll change: £%+.3f%n",trig==0?0:dFinal/trig));
        r.append(String.format(Locale.ROOT,"Average wager removed per triggered session: £%.3f%n",trig==0?0:wRemoved/trig));
        r.append(String.format(Locale.ROOT,"Average max-drawdown reduction per triggered session: £%+.3f%n",trig==0?0:ddReduced/trig));
        r.append(String.format(Locale.ROOT,"Average trigger hand / retained bankroll: %.2f / £%.3f%n",trig==0?0:stopHand/trig,trig==0?0:stopBank/trig));
        r.append("CLASSIFICATION (mutually exclusive):\n");
        for(Map.Entry<String,Integer>e:cls.entrySet())
            r.append("  ").append(e.getKey()).append(": ").append(e.getValue()).append(" (").append(pct380(e.getValue(),trig)).append(")\n");
        r.append("  Broad GOOD_SAVE classes: ").append(pct380(good,trig)).append("\n");
        r.append("  Broad PREMATURE_EXIT classes: ").append(pct380(prem,trig)).append("\n");
    }

    static String csv397(ArrayList<P397>...groups){
        StringBuilder b=new StringBuilder("cohort,session,trail_triggered,class,trigger_hand,trigger_bankroll,reference_final,trailing_final,final_delta,reference_reached_200,reference_exhausted,reference_cumulative_wager,trailing_cumulative_wager,wager_removed,reference_max_drawdown,trailing_max_drawdown,drawdown_reduced\n");
        for(ArrayList<P397>g:groups)for(P397 p:g)
            b.append(String.format(Locale.ROOT,"%s,%d,%s,%s,%d,%.2f,%.2f,%.2f,%.2f,%s,%s,%.2f,%.2f,%.2f,%.2f,%.2f,%.2f%n",
                p.cohort,p.session,Boolean.toString(p.trail.stopped),p.className,p.trail.stopHand,p.trail.stopBank,
                p.ref.finalBank,p.trail.finalBank,p.finalDelta,Boolean.toString(p.ref.reached200),Boolean.toString(p.ref.exhausted),
                p.ref.cumWager,p.trail.cumWager,p.wagerRemoved,p.ref.maxDD,p.trail.maxDD,p.drawdownReduced));
        return b.toString();
    }


    /* PHASE 3.98 — FROZEN TRIGGER-STATE SEPARATION MAP
       No selective intervention.
       Freeze the TRAIL160/25 trigger from Phases 3.95–3.97 and record only
       information legitimately observable at that trigger.
       Then label the same session by its unchanged REFERENCE future:
       LATER_200, LATER_COLLAPSE (<£100 final, including £0), or MIDDLE.
       This is descriptive separation evidence, not future-card prediction. */

    static final class T398 {
        String cohort; int session,triggerHand;
        double triggerBank,highWater,absGiveback,givebackPct,cumExposure;
        int recentBalance,recentWins,recentLosses,recentPushes;
        boolean priorBelow100,cooling,cautious,brakeArmed;
        String futureClass;
        double refFinal;
        boolean ref200,ref0;
    }

    static T398 observe398(long seed,String cohort,int session){
        int[]shoe=sixDeckShoe(sessionSeed(seed,session));
        if(!auditShoe(shoe))throw new IllegalStateException("Inventory audit failed");
        int ptr=0,hc=0;int[]hist=new int[5];
        double bank=START_BANK,peak=START_BANK,cum=0;
        boolean everPlunge=false,cooling=false,cautious=false,negativeEpisode=false,brakeArmed=false;
        boolean activated160=false,priorBelow=false;

        for(int hand=1;hand<=HANDS;hand++){
            if(bank>=200||bank<=0)break;
            if(cooling&&bank>=120){cooling=false;cautious=true;}
            if(cautious&&bank>=140)cautious=false;

            double wager;
            if(cooling){wager=10;negativeEpisode=false;brakeArmed=false;}
            else if(cautious){wager=cautious355(hist,hc);negativeEpisode=false;brakeArmed=false;}
            else if(hand>8&&bank>=140){
                int b=bal355(hist,hc);boolean neg=b<0;
                if(neg&&!negativeEpisode)brakeArmed=true;
                negativeEpisode=neg;
                double base=bank>=170?15:normal355(hand,bank);
                if(brakeArmed){wager=Math.min(base,15);brakeArmed=false;}else wager=base;
            } else {wager=normal355(hand,bank);negativeEpisode=false;brakeArmed=false;}

            wager=Math.min(wager,bank);if(wager<=0)break;
            if(!cooling&&!cautious&&hand>8&&bank>=130)everPlunge=true;

            PairStats ps=new PairStats();
            Round rr=playRound(shoe,ptr,Profile.SELECTIVE_AGGRESSION,cohort,session+1,hand,true,bank,ps,wager,false);
            ptr=rr.endPointer;cum+=roundStake356(rr);
            bank+=rr.net;if(bank<0)bank=0;
            peak=Math.max(peak,bank);

            int outcome=rr.net>1e-9?1:(rr.net<-1e-9?-1:0);
            hist[hc%hist.length]=outcome;hc++;

            if(bank<100-1e-9)priorBelow=true;
            if(bank>=200-1e-9||bank<=1e-9)break;
            if(bank>=160-1e-9)activated160=true;

            if(activated160 && peak>100+1e-9){
                double abs=peak-bank;
                double pct=100.0*abs/(peak-100.0);
                if(pct>=25.0-1e-9){
                    T398 t=new T398();t.cohort=cohort;t.session=session+1;t.triggerHand=hand;
                    t.triggerBank=bank;t.highWater=peak;t.absGiveback=abs;t.givebackPct=pct;t.cumExposure=cum;
                    t.priorBelow100=priorBelow;t.cooling=cooling;t.cautious=cautious;t.brakeArmed=brakeArmed;
                    int lim=Math.min(hc,hist.length);
                    for(int k=0;k<lim;k++){
                        int v=hist[(hc-1-k+hist.length)%hist.length];
                        if(v>0)t.recentWins++;else if(v<0)t.recentLosses++;else t.recentPushes++;
                    }
                    t.recentBalance=t.recentWins-t.recentLosses;
                    return t;
                }
            }
            if(everPlunge&&bank<=100&&!cooling){
                cooling=true;cautious=false;negativeEpisode=false;brakeArmed=false;
            }
        }
        return null;
    }

    static ArrayList<T398> run398(long seed,String cohort){
        ArrayList<T398>a=new ArrayList<>();
        for(int i=0;i<SESSIONS;i++){
            T398 t=observe398(seed,cohort,i);
            if(t==null)continue;
            S396 ref=runOne396(seed,cohort,i,REF396);
            t.refFinal=ref.finalBank;t.ref200=ref.reached200;t.ref0=ref.exhausted;
            if(ref.reached200)t.futureClass="LATER_200";
            else if(ref.finalBank<100-1e-9)t.futureClass="LATER_COLLAPSE";
            else t.futureClass="MIDDLE";
            a.add(t);
        }return a;
    }

    static void classSummary398(StringBuilder r,String cohort,ArrayList<T398>a){
        r.append("\n").append(cohort).append("\n");
        r.append("Future class | N | Trigger hand | Trigger bank | High-water | Abs give-back | Give-back % | Exposure so far | Recent W-L balance | Prior <£100\n");
        for(String c:new String[]{"LATER_200","LATER_COLLAPSE","MIDDLE"}){
            long n=0,prior=0;double hand=0,bank=0,peak=0,abs=0,pct=0,exp=0,bal=0;
            for(T398 t:a)if(t.futureClass.equals(c)){
                n++;hand+=t.triggerHand;bank+=t.triggerBank;peak+=t.highWater;abs+=t.absGiveback;
                pct+=t.givebackPct;exp+=t.cumExposure;bal+=t.recentBalance;if(t.priorBelow100)prior++;
            }
            r.append(String.format(Locale.ROOT,"%s | %d (%.2f%%) | %.2f | £%.2f | £%.2f | £%.2f | %.2f%% | £%.2f | %.3f | %.2f%%%n",
                c,n,100.0*n/a.size(),n==0?0:hand/n,n==0?0:bank/n,n==0?0:peak/n,n==0?0:abs/n,
                n==0?0:pct/n,n==0?0:exp/n,n==0?0:bal/n,n==0?0:100.0*prior/n));
        }

        // Predeclared coarse bins for descriptive separation only.
        r.append("\nCOARSE OBSERVABLE BINS — later £200 rate / later collapse rate\n");
        String[] hn={"hand <=12","hand 13-16","hand >=17"};
        for(int q=0;q<3;q++){
            long n=0,y=0,c=0;
            for(T398 t:a){
                boolean ok=q==0?t.triggerHand<=12:q==1?(t.triggerHand>=13&&t.triggerHand<=16):t.triggerHand>=17;
                if(ok){n++;if(t.ref200)y++;if(t.futureClass.equals("LATER_COLLAPSE"))c++;}
            }
            r.append(String.format(Locale.ROOT,"%s: N=%d, £200 %.2f%%, collapse %.2f%%%n",hn[q],n,n==0?0:100.0*y/n,n==0?0:100.0*c/n));
        }
        String[] bn={"trigger bank <£130","£130-£139.99","trigger bank >=£140"};
        for(int q=0;q<3;q++){
            long n=0,y=0,c=0;
            for(T398 t:a){
                boolean ok=q==0?t.triggerBank<130:q==1?(t.triggerBank>=130&&t.triggerBank<140):t.triggerBank>=140;
                if(ok){n++;if(t.ref200)y++;if(t.futureClass.equals("LATER_COLLAPSE"))c++;}
            }
            r.append(String.format(Locale.ROOT,"%s: N=%d, £200 %.2f%%, collapse %.2f%%%n",bn[q],n,n==0?0:100.0*y/n,n==0?0:100.0*c/n));
        }
        String[] rb={"recent balance <=-2","recent balance -1 to 0","recent balance >=1"};
        for(int q=0;q<3;q++){
            long n=0,y=0,c=0;
            for(T398 t:a){
                boolean ok=q==0?t.recentBalance<=-2:q==1?(t.recentBalance>=-1&&t.recentBalance<=0):t.recentBalance>=1;
                if(ok){n++;if(t.ref200)y++;if(t.futureClass.equals("LATER_COLLAPSE"))c++;}
            }
            r.append(String.format(Locale.ROOT,"%s: N=%d, £200 %.2f%%, collapse %.2f%%%n",rb[q],n,n==0?0:100.0*y/n,n==0?0:100.0*c/n));
        }
    }

    static String csv398(ArrayList<T398>...groups){
        StringBuilder b=new StringBuilder("cohort,session,future_class,trigger_hand,trigger_bankroll,high_water,absolute_giveback,giveback_pct,cumulative_exposure,recent_wins,recent_losses,recent_pushes,recent_balance,prior_below_100,cooling,cautious,brake_armed,reference_final,reference_reached_200,reference_exhausted\n");
        for(ArrayList<T398>g:groups)for(T398 t:g)
            b.append(String.format(Locale.ROOT,"%s,%d,%s,%d,%.2f,%.2f,%.2f,%.4f,%.2f,%d,%d,%d,%d,%s,%s,%s,%s,%.2f,%s,%s%n",
                t.cohort,t.session,t.futureClass,t.triggerHand,t.triggerBank,t.highWater,t.absGiveback,t.givebackPct,
                t.cumExposure,t.recentWins,t.recentLosses,t.recentPushes,t.recentBalance,
                Boolean.toString(t.priorBelow100),Boolean.toString(t.cooling),Boolean.toString(t.cautious),
                Boolean.toString(t.brakeArmed),t.refFinal,Boolean.toString(t.ref200),Boolean.toString(t.ref0)));
        return b.toString();
    }


    /* PHASE 3.99 — PREDECLARED SELECTIVE TRAILING-SEVERITY GATE
       Frozen before execution in Phase 3.98:
       existing activation >=£160;
       existing >=25% high-water-profit give-back trigger;
       STOP only when completed trigger round leaves bankroll <£140;
       otherwise continue normally.
       No threshold tuning. No wager/multiplier change. */

    static final String SELECT399="SELECTIVE_TRAIL_160_25_BANK140";

    static S396 runOne399(long seed,String cohort,int session,String policy){
        int[]shoe=sixDeckShoe(sessionSeed(seed,session));
        if(!auditShoe(shoe))throw new IllegalStateException("Inventory audit failed");
        int ptr=0,hc=0;int[]hist=new int[5];
        double bank=START_BANK,peak=START_BANK,maxDD=0,cum=0;
        boolean everPlunge=false,cooling=false,cautious=false,negativeEpisode=false,brakeArmed=false;
        boolean activated160=false,everBelow=false,recovered=false;
        S396 x=new S396();x.cohort=cohort;x.policy=policy;x.session=session+1;

        for(int hand=1;hand<=HANDS;hand++){
            if(bank>=200||bank<=0)break;
            if(cooling&&bank>=120){cooling=false;cautious=true;}
            if(cautious&&bank>=140)cautious=false;

            double wager;
            if(cooling){wager=10;negativeEpisode=false;brakeArmed=false;}
            else if(cautious){wager=cautious355(hist,hc);negativeEpisode=false;brakeArmed=false;}
            else if(hand>8&&bank>=140){
                int b=bal355(hist,hc);boolean neg=b<0;
                if(neg&&!negativeEpisode)brakeArmed=true;
                negativeEpisode=neg;
                double base=bank>=170?15:normal355(hand,bank);
                if(brakeArmed){wager=Math.min(base,15);brakeArmed=false;}else wager=base;
            } else {wager=normal355(hand,bank);negativeEpisode=false;brakeArmed=false;}

            wager=Math.min(wager,bank);if(wager<=0)break;
            if(!cooling&&!cautious&&hand>8&&bank>=130)everPlunge=true;

            PairStats ps=new PairStats();
            Round rr=playRound(shoe,ptr,Profile.SELECTIVE_AGGRESSION,cohort,session+1,hand,true,bank,ps,wager,false);
            ptr=rr.endPointer;cum+=roundStake356(rr);
            bank+=rr.net;if(bank<0)bank=0;x.hands++;
            peak=Math.max(peak,bank);maxDD=Math.max(maxDD,peak-bank);

            if(bank<100-1e-9)everBelow=true;
            if(everBelow&&bank>=100-1e-9)recovered=true;

            hist[hc%hist.length]=rr.net>1e-9?1:(rr.net<-1e-9?-1:0);hc++;

            if(bank>=200-1e-9){x.reached200=true;break;}
            if(bank<=1e-9){x.exhausted=true;break;}
            if(bank>=160-1e-9)activated160=true;

            if(policy.equals(HARD396) && activated160){
                x.stopped=true;x.stopHand=hand;x.stopBank=bank;break;
            }

            if((policy.equals(TRAIL396)||policy.equals(SELECT399)) && activated160 && peak>100+1e-9){
                double accumulatedProfit=peak-100.0;
                double surrendered=Math.max(0,peak-bank);
                double givePct=100.0*surrendered/accumulatedProfit;
                if(givePct>=25.0-1e-9){
                    boolean stop=policy.equals(TRAIL396) || bank<140.0-1e-9;
                    if(stop){x.stopped=true;x.stopHand=hand;x.stopBank=bank;break;}
                }
            }

            if(everPlunge&&bank<=100&&!cooling){
                cooling=true;cautious=false;negativeEpisode=false;brakeArmed=false;
            }
        }

        x.finalBank=bank;x.peak=peak;x.maxDD=maxDD;x.cumWager=cum;
        x.everBelow100=everBelow;x.recovered100=recovered;
        x.giveback=Math.max(0,peak-bank);
        return x;
    }

    static ArrayList<S396> run399(long seed,String cohort,String policy){
        ArrayList<S396>a=new ArrayList<>();
        for(int i=0;i<SESSIONS;i++)a.add(runOne399(seed,cohort,i,policy));
        return a;
    }

    static String csv399(ArrayList<S396>...groups){
        StringBuilder b=new StringBuilder("cohort,policy,session,hands,final_bankroll,peak_bankroll,max_drawdown,cumulative_wager,reached_200,exhausted,positive,ever_below_100,recovered_100,peak_to_finish_giveback,stop_triggered,stop_hand,stop_bankroll\n");
        for(ArrayList<S396>g:groups)for(S396 x:g)
            b.append(String.format(Locale.ROOT,"%s,%s,%d,%d,%.2f,%.2f,%.2f,%.2f,%s,%s,%s,%s,%s,%.2f,%s,%d,%.2f%n",
                x.cohort,x.policy,x.session,x.hands,x.finalBank,x.peak,x.maxDD,x.cumWager,
                Boolean.toString(x.reached200),Boolean.toString(x.exhausted),Boolean.toString(x.finalBank>100+1e-9),
                Boolean.toString(x.everBelow100),Boolean.toString(x.recovered100),x.giveback,
                Boolean.toString(x.stopped),x.stopHand,x.stopBank));
        return b.toString();
    }


    /* PHASE 3.100 — RELEASED-SESSION PAIRED CAUSAL DECOMPOSITION
       No new intervention and no threshold tuning.
       A RELEASED session is one that unconditional TRAIL160/25 stops but the
       frozen selective £140 gate allows to continue.
       Pair identical deterministic sessions under:
       REFERENCE, unconditional TRAIL160/25, and SELECTIVE £140 gate. */

    static final class P3100 {
        String cohort; int session;
        S396 ref,uncond,select;
        boolean released;
        double selectVsUncondFinal,selectVsUncondWager,selectVsUncondDD;
        String releaseOutcome;
    }

    static ArrayList<P3100> pair3100(long seed,String cohort){
        ArrayList<P3100>a=new ArrayList<>();
        for(int i=0;i<SESSIONS;i++){
            S396 r=runOne399(seed,cohort,i,REF396);
            S396 u=runOne399(seed,cohort,i,TRAIL396);
            S396 q=runOne399(seed,cohort,i,SELECT399);
            P3100 p=new P3100();p.cohort=cohort;p.session=i+1;p.ref=r;p.uncond=u;p.select=q;
            p.released=u.stopped && !q.stopped;
            p.selectVsUncondFinal=q.finalBank-u.finalBank;
            p.selectVsUncondWager=q.cumWager-u.cumWager;
            p.selectVsUncondDD=q.maxDD-u.maxDD;
            if(!p.released)p.releaseOutcome="NOT_RELEASED";
            else if(q.reached200)p.releaseOutcome="RELEASED_TO_200";
            else if(q.exhausted)p.releaseOutcome="RELEASED_TO_0";
            else if(q.finalBank>u.finalBank+1e-9)p.releaseOutcome="RELEASED_HIGHER_FINAL";
            else if(q.finalBank<u.finalBank-1e-9)p.releaseOutcome="RELEASED_LOWER_FINAL";
            else p.releaseOutcome="RELEASED_SAME_FINAL";
            a.add(p);
        }return a;
    }

    static void sum3100(StringBuilder r,String cohort,ArrayList<P3100>a){
        long rel=0,to200=0,to0=0,higher=0,lower=0,same=0,below100=0;
        double df=0,dw=0,ddd=0,rf=0,uf=0,sf=0;
        TreeMap<String,Integer> cls=new TreeMap<>();
        for(P3100 p:a)if(p.released){
            rel++;cls.put(p.releaseOutcome,cls.getOrDefault(p.releaseOutcome,0)+1);
            if(p.select.reached200)to200++;
            if(p.select.exhausted)to0++;
            if(p.select.finalBank<100-1e-9)below100++;
            if(p.selectVsUncondFinal>1e-9)higher++;else if(p.selectVsUncondFinal<-1e-9)lower++;else same++;
            df+=p.selectVsUncondFinal;dw+=p.selectVsUncondWager;ddd+=p.selectVsUncondDD;
            rf+=p.ref.finalBank;uf+=p.uncond.finalBank;sf+=p.select.finalBank;
        }
        r.append("\n").append(cohort).append("\n");
        r.append("Sessions released by £140 gate: ").append(rel).append(" (").append(pct380(rel,a.size())).append(" of all sessions)\n");
        r.append("Released-session selective outcomes:\n");
        r.append("  Reached £200: ").append(pct380(to200,rel)).append("\n");
        r.append("  Exhausted £0: ").append(pct380(to0,rel)).append("\n");
        r.append("  Finished below £100: ").append(pct380(below100,rel)).append("\n");
        r.append("Selective final vs unconditional retained bankroll:\n");
        r.append("  Higher: ").append(pct380(higher,rel)).append("\n");
        r.append("  Lower: ").append(pct380(lower,rel)).append("\n");
        r.append("  Same: ").append(pct380(same,rel)).append("\n");
        r.append(String.format(Locale.ROOT,"Average final bankroll — Reference / Unconditional / Selective: £%.3f / £%.3f / £%.3f%n",
            rel==0?0:rf/rel,rel==0?0:uf/rel,rel==0?0:sf/rel));
        r.append(String.format(Locale.ROOT,"Average selective-minus-unconditional final delta: £%+.3f%n",rel==0?0:df/rel));
        r.append(String.format(Locale.ROOT,"Average extra wager reintroduced by release: £%.3f%n",rel==0?0:dw/rel));
        r.append(String.format(Locale.ROOT,"Average extra maximum drawdown from release: £%+.3f%n",rel==0?0:ddd/rel));
        r.append("Mutually exclusive release outcomes:\n");
        for(Map.Entry<String,Integer>e:cls.entrySet())
            r.append("  ").append(e.getKey()).append(": ").append(e.getValue()).append(" (").append(pct380(e.getValue(),rel)).append(")\n");
    }

    static String csv3100(ArrayList<P3100>...groups){
        StringBuilder b=new StringBuilder("cohort,session,released_by_gate,release_outcome,reference_final,unconditional_final,selective_final,selective_minus_unconditional_final,reference_200,selective_200,selective_0,unconditional_stop_hand,unconditional_stop_bankroll,reference_wager,unconditional_wager,selective_wager,extra_wager_from_release,reference_drawdown,unconditional_drawdown,selective_drawdown,extra_drawdown_from_release\n");
        for(ArrayList<P3100>g:groups)for(P3100 p:g)
            b.append(String.format(Locale.ROOT,"%s,%d,%s,%s,%.2f,%.2f,%.2f,%.2f,%s,%s,%s,%d,%.2f,%.2f,%.2f,%.2f,%.2f,%.2f,%.2f,%.2f,%.2f%n",
                p.cohort,p.session,Boolean.toString(p.released),p.releaseOutcome,p.ref.finalBank,p.uncond.finalBank,
                p.select.finalBank,p.selectVsUncondFinal,Boolean.toString(p.ref.reached200),Boolean.toString(p.select.reached200),
                Boolean.toString(p.select.exhausted),p.uncond.stopHand,p.uncond.stopBank,p.ref.cumWager,p.uncond.cumWager,
                p.select.cumWager,p.selectVsUncondWager,p.ref.maxDD,p.uncond.maxDD,p.select.maxDD,p.selectVsUncondDD));
        return b.toString();
    }


    /* PHASE 3.101 — NEW-COHORT ROBUSTNESS / GENERALISATION TEST
       Frozen architecture carried forward unchanged from Phase 3.99/3.100:
         activation: bankroll has reached >= £160;
         trigger: >=25% give-back from accumulated high-water profit;
         severity gate: STOP only when completed trigger bankroll < £140;
                        CONTINUE when trigger bankroll >= £140.
       No threshold tuning. No policy discovery.
       Two genuinely new deterministic seed cohorts are used here.
       Their seeds are declared before execution and are not selected by outcome. */

    static final long ROBUST_A_SEED = 0x310101A5B7C9D11DL;
    static final long ROBUST_B_SEED = 0x310101B6C8DAE22EL;

    static void population3101(StringBuilder r,String cohort,ArrayList<P3100>a){
        long n=a.size(), rel=0, ref200=0, unc200=0, sel200=0, ref0=0, unc0=0, sel0=0;
        double rf=0,uf=0,sf=0,rw=0,uw=0,sw=0,rdd=0,udd=0,sdd=0;
        for(P3100 p:a){
            if(p.released)rel++;
            if(p.ref.reached200)ref200++;
            if(p.uncond.reached200)unc200++;
            if(p.select.reached200)sel200++;
            if(p.ref.exhausted)ref0++;
            if(p.uncond.exhausted)unc0++;
            if(p.select.exhausted)sel0++;
            rf+=p.ref.finalBank; uf+=p.uncond.finalBank; sf+=p.select.finalBank;
            rw+=p.ref.cumWager; uw+=p.uncond.cumWager; sw+=p.select.cumWager;
            rdd+=p.ref.maxDD; udd+=p.uncond.maxDD; sdd+=p.select.maxDD;
        }
        r.append("\n").append(cohort).append(" — WHOLE POPULATION\n");
        r.append("Released by frozen £140 gate: ").append(rel).append(" (").append(pct380(rel,n)).append(")\n");
        r.append(String.format(Locale.ROOT,"Average final — Reference / Unconditional / Selective: £%.3f / £%.3f / £%.3f%n",rf/n,uf/n,sf/n));
        r.append("Reached £200 — Reference / Unconditional / Selective: ")
         .append(pct380(ref200,n)).append(" / ").append(pct380(unc200,n)).append(" / ").append(pct380(sel200,n)).append("\n");
        r.append("Finished £0 — Reference / Unconditional / Selective: ")
         .append(pct380(ref0,n)).append(" / ").append(pct380(unc0,n)).append(" / ").append(pct380(sel0,n)).append("\n");
        r.append(String.format(Locale.ROOT,"Average cumulative wager — Reference / Unconditional / Selective: £%.3f / £%.3f / £%.3f%n",rw/n,uw/n,sw/n));
        r.append(String.format(Locale.ROOT,"Average max drawdown — Reference / Unconditional / Selective: £%.3f / £%.3f / £%.3f%n",rdd/n,udd/n,sdd/n));
    }

    static String csv3101(ArrayList<P3100>...groups){
        return csv3100(groups);
    }


    /* PHASE 3.102 — INDEPENDENT-SHOE / STARTING-POSITION ROBUSTNESS
       Rationale from the Phase 3.56+ brainstorm:
       test independent deterministic shuffle orders and starting offsets without
       introducing card counting or future-card knowledge.

       IMPORTANT: cut-card/penetration reshuffling is NOT tested here.
       A 30-round session frequently does not reach a conventional cut-card point,
       so that would be a weak/partly inactive intervention in this architecture.

       Frozen selective architecture:
         activation >= £160;
         trigger >=25% give-back from accumulated high-water profit;
         stop iff completed trigger bankroll < £140;
         continue iff trigger bankroll >= £140.
       No threshold tuning. */

    static final long SHOE_A_SEED = 0x310102A4D6E8F11AL;
    static final long SHOE_B_SEED = 0x310102B5E7F9A22BL;
    static final int OFFSET_A = 52;   // exactly one deck into a fresh deterministic six-deck shoe
    static final int OFFSET_B = 104;  // exactly two decks into a fresh deterministic six-deck shoe

    static S396 runOne3102(long seed,String cohort,int session,String policy,int startOffset){
        int[]shoe=sixDeckShoe(sessionSeed(seed,session));
        if(!auditShoe(shoe))throw new IllegalStateException("Inventory audit failed");
        if(startOffset<0 || startOffset>=shoe.length)throw new IllegalArgumentException("Invalid start offset");
        int ptr=startOffset,hc=0;int[]hist=new int[5];
        double bank=START_BANK,peak=START_BANK,maxDD=0,cum=0;
        boolean everPlunge=false,cooling=false,cautious=false,negativeEpisode=false,brakeArmed=false;
        boolean activated160=false,everBelow=false,recovered=false;
        S396 x=new S396();x.cohort=cohort;x.policy=policy;x.session=session+1;

        for(int hand=1;hand<=HANDS;hand++){
            if(bank>=200||bank<=0)break;
            if(cooling&&bank>=120){cooling=false;cautious=true;}
            if(cautious&&bank>=140)cautious=false;

            double wager;
            if(cooling){wager=10;negativeEpisode=false;brakeArmed=false;}
            else if(cautious){wager=cautious355(hist,hc);negativeEpisode=false;brakeArmed=false;}
            else if(hand>8&&bank>=140){
                int b=bal355(hist,hc);boolean neg=b<0;
                if(neg&&!negativeEpisode)brakeArmed=true;
                negativeEpisode=neg;
                double base=bank>=170?15:normal355(hand,bank);
                if(brakeArmed){wager=Math.min(base,15);brakeArmed=false;}else wager=base;
            } else {wager=normal355(hand,bank);negativeEpisode=false;brakeArmed=false;}

            wager=Math.min(wager,bank);if(wager<=0)break;
            if(!cooling&&!cautious&&hand>8&&bank>=130)everPlunge=true;

            PairStats ps=new PairStats();
            Round rr=playRound(shoe,ptr,Profile.SELECTIVE_AGGRESSION,cohort,session+1,hand,true,bank,ps,wager,false);
            ptr=rr.endPointer;cum+=roundStake356(rr);
            bank+=rr.net;if(bank<0)bank=0;x.hands++;
            peak=Math.max(peak,bank);maxDD=Math.max(maxDD,peak-bank);

            if(bank<100-1e-9)everBelow=true;
            if(everBelow&&bank>=100-1e-9)recovered=true;

            hist[hc%hist.length]=rr.net>1e-9?1:(rr.net<-1e-9?-1:0);hc++;

            if(bank>=200-1e-9){x.reached200=true;break;}
            if(bank<=1e-9){x.exhausted=true;break;}
            if(bank>=160-1e-9)activated160=true;

            if(policy.equals(HARD396) && activated160){
                x.stopped=true;x.stopHand=hand;x.stopBank=bank;break;
            }

            if((policy.equals(TRAIL396)||policy.equals(SELECT399)) && activated160 && peak>100+1e-9){
                double accumulatedProfit=peak-100.0;
                double surrendered=Math.max(0,peak-bank);
                double givePct=100.0*surrendered/accumulatedProfit;
                if(givePct>=25.0-1e-9){
                    boolean stop=policy.equals(TRAIL396) || bank<140.0-1e-9;
                    if(stop){x.stopped=true;x.stopHand=hand;x.stopBank=bank;break;}
                }
            }

            if(everPlunge&&bank<=100&&!cooling){
                cooling=true;cautious=false;negativeEpisode=false;brakeArmed=false;
            }
        }

        x.finalBank=bank;x.peak=peak;x.maxDD=maxDD;x.cumWager=cum;
        x.everBelow100=everBelow;x.recovered100=recovered;
        x.giveback=Math.max(0,peak-bank);
        return x;
    }

    static ArrayList<P3100> pair3102(long seed,String cohort,int offset){
        ArrayList<P3100>a=new ArrayList<>();
        for(int i=0;i<SESSIONS;i++){
            S396 r=runOne3102(seed,cohort,i,REF396,offset);
            S396 u=runOne3102(seed,cohort,i,TRAIL396,offset);
            S396 q=runOne3102(seed,cohort,i,SELECT399,offset);
            P3100 p=new P3100();p.cohort=cohort;p.session=i+1;p.ref=r;p.uncond=u;p.select=q;
            p.released=u.stopped && !q.stopped;
            p.selectVsUncondFinal=q.finalBank-u.finalBank;
            p.selectVsUncondWager=q.cumWager-u.cumWager;
            p.selectVsUncondDD=q.maxDD-u.maxDD;
            if(!p.released)p.releaseOutcome="NOT_RELEASED";
            else if(q.reached200)p.releaseOutcome="RELEASED_TO_200";
            else if(q.exhausted)p.releaseOutcome="RELEASED_TO_0";
            else if(q.finalBank>u.finalBank+1e-9)p.releaseOutcome="RELEASED_HIGHER_FINAL";
            else if(q.finalBank<u.finalBank-1e-9)p.releaseOutcome="RELEASED_LOWER_FINAL";
            else p.releaseOutcome="RELEASED_SAME_FINAL";
            a.add(p);
        }return a;
    }


    /* PHASE 3.103 — REALISTIC £5 CHIP-DENOMINATION TRANSLATION
       Brainstorm Stage E: casino chip denominations / table constraints.

       This phase isolates denomination granularity only.
       The frozen Phase 3.100 selective exit architecture is retained.
       No table-minimum change is introduced yet.

       Legal original-round wagers are multiples of £5.
       A controller request exactly halfway between two legal units is rounded DOWN.
       Therefore the existing £17.50 cautious-state request becomes £15.
       Existing £10/£15/£20/£30 requests are unchanged.

       This is a predeclared mechanical translation, not wager optimisation. */

    static final String CONT103="FROZEN_CONTINUOUS_CONTROLLER";
    static final String CHIP103="FIVE_POUND_CHIP_TRANSLATION";

    static final class S103 {
        String cohort,policy; int session,hands,changedWagers;
        double finalBank,peak,maxDD,cumWager,changedWagerAbs;
        boolean reached200,exhausted,stopped;
    }

    static double chip5FloorTie103(double requested,double bank){
        double units=requested/5.0;
        double lo=Math.floor(units)*5.0, hi=Math.ceil(units)*5.0;
        double dlo=requested-lo, dhi=hi-requested;
        double legal=(dlo<=dhi+1e-12)?lo:hi; // exact ties downward
        if(legal<5.0)legal=5.0;
        if(legal>bank){
            legal=Math.floor(bank/5.0)*5.0;
            if(legal<5.0)legal=bank; // preserve ability to finish a sub-£5 residual bankroll
        }
        return Math.min(legal,bank);
    }

    static S103 runOne103(long seed,String cohort,int session,String policy){
        int[]shoe=sixDeckShoe(sessionSeed(seed,session));
        if(!auditShoe(shoe))throw new IllegalStateException("Inventory audit failed");
        int ptr=0,hc=0;int[]hist=new int[5];
        double bank=START_BANK,peak=START_BANK,maxDD=0,cum=0;
        boolean everPlunge=false,cooling=false,cautious=false,negativeEpisode=false,brakeArmed=false;
        boolean activated160=false;
        S103 x=new S103();x.cohort=cohort;x.policy=policy;x.session=session+1;

        for(int hand=1;hand<=HANDS;hand++){
            if(bank>=200||bank<=0)break;
            if(cooling&&bank>=120){cooling=false;cautious=true;}
            if(cautious&&bank>=140)cautious=false;

            double requested;
            if(cooling){requested=10;negativeEpisode=false;brakeArmed=false;}
            else if(cautious){requested=cautious355(hist,hc);negativeEpisode=false;brakeArmed=false;}
            else if(hand>8&&bank>=140){
                int b=bal355(hist,hc);boolean neg=b<0;
                if(neg&&!negativeEpisode)brakeArmed=true;
                negativeEpisode=neg;
                double base=bank>=170?15:normal355(hand,bank);
                if(brakeArmed){requested=Math.min(base,15);brakeArmed=false;}else requested=base;
            } else {requested=normal355(hand,bank);negativeEpisode=false;brakeArmed=false;}

            requested=Math.min(requested,bank);
            double wager=policy.equals(CHIP103)?chip5FloorTie103(requested,bank):requested;
            if(Math.abs(wager-requested)>1e-9){
                x.changedWagers++;
                x.changedWagerAbs+=Math.abs(wager-requested);
            }
            if(wager<=0)break;
            if(!cooling&&!cautious&&hand>8&&bank>=130)everPlunge=true;

            PairStats ps=new PairStats();
            Round rr=playRound(shoe,ptr,Profile.SELECTIVE_AGGRESSION,cohort,session+1,hand,true,bank,ps,wager,false);
            ptr=rr.endPointer;cum+=roundStake356(rr);
            bank+=rr.net;if(bank<0)bank=0;x.hands++;
            peak=Math.max(peak,bank);maxDD=Math.max(maxDD,peak-bank);
            hist[hc%hist.length]=rr.net>1e-9?1:(rr.net<-1e-9?-1:0);hc++;

            if(bank>=200-1e-9){x.reached200=true;break;}
            if(bank<=1e-9){x.exhausted=true;break;}
            if(bank>=160-1e-9)activated160=true;

            if(activated160 && peak>100+1e-9){
                double accumulatedProfit=peak-100.0;
                double surrendered=Math.max(0,peak-bank);
                double givePct=100.0*surrendered/accumulatedProfit;
                if(givePct>=25.0-1e-9 && bank<140.0-1e-9){
                    x.stopped=true;break;
                }
            }

            if(everPlunge&&bank<=100&&!cooling){
                cooling=true;cautious=false;negativeEpisode=false;brakeArmed=false;
            }
        }
        x.finalBank=bank;x.peak=peak;x.maxDD=maxDD;x.cumWager=cum;
        return x;
    }

    static void report103(StringBuilder r,String cohort,ArrayList<S103> c,ArrayList<S103> q){
        int n=c.size(), changedSessions=0, higher=0,lower=0,same=0;
        int c200=0,q200=0,c0=0,q0=0,cpos=0,qpos=0;
        long changedWagers=0; double changeAbs=0;
        double cf=0,qf=0,cw=0,qw=0,cdd=0,qdd=0;
        for(int i=0;i<n;i++){
            S103 a=c.get(i),b=q.get(i);
            cf+=a.finalBank;qf+=b.finalBank;cw+=a.cumWager;qw+=b.cumWager;cdd+=a.maxDD;qdd+=b.maxDD;
            if(a.reached200)c200++;if(b.reached200)q200++;
            if(a.exhausted)c0++;if(b.exhausted)q0++;
            if(a.finalBank>100+1e-9)cpos++;if(b.finalBank>100+1e-9)qpos++;
            if(b.changedWagers>0)changedSessions++;
            changedWagers+=b.changedWagers;changeAbs+=b.changedWagerAbs;
            double d=b.finalBank-a.finalBank;
            if(d>1e-9)higher++;else if(d<-1e-9)lower++;else same++;
        }
        r.append("\n").append(cohort).append("\n");
        r.append(String.format(Locale.US,"Sessions with >=1 denomination translation: %d (%.2f%%)\n",changedSessions,100.0*changedSessions/n));
        r.append(String.format(Locale.US,"Original-round wagers translated: %d\n",changedWagers));
        r.append(String.format(Locale.US,"Mean absolute requested-to-legal adjustment per translated wager: £%.3f\n",changedWagers==0?0:changeAbs/changedWagers));
        r.append(String.format(Locale.US,"Average final — Continuous / £5-chip: £%.3f / £%.3f (delta £%+.3f)\n",cf/n,qf/n,(qf-cf)/n));
        r.append(String.format(Locale.US,"Positive sessions — Continuous / £5-chip: %.2f%% / %.2f%%\n",100.0*cpos/n,100.0*qpos/n));
        r.append(String.format(Locale.US,"Reached £200 — Continuous / £5-chip: %.2f%% / %.2f%%\n",100.0*c200/n,100.0*q200/n));
        r.append(String.format(Locale.US,"Finished £0 — Continuous / £5-chip: %.2f%% / %.2f%%\n",100.0*c0/n,100.0*q0/n));
        r.append(String.format(Locale.US,"Average cumulative wager — Continuous / £5-chip: £%.3f / £%.3f\n",cw/n,qw/n));
        r.append(String.format(Locale.US,"Average max drawdown — Continuous / £5-chip: £%.3f / £%.3f\n",cdd/n,qdd/n));
        r.append(String.format(Locale.US,"Paired final bankroll — £5-chip higher / lower / same: %.2f%% / %.2f%% / %.2f%%\n",
            100.0*higher/n,100.0*lower/n,100.0*same/n));
    }

    static String fmt103(double x){return String.format(Locale.US,"%.3f",x);}
    static String csv103(ArrayList<S103> pc,ArrayList<S103> pq,ArrayList<S103> hc,ArrayList<S103> hq){
        StringBuilder z=new StringBuilder();
        z.append("cohort,session,continuous_final,chip5_final,delta_final,continuous_200,chip5_200,continuous_0,chip5_0,continuous_cum_wager,chip5_cum_wager,continuous_max_dd,chip5_max_dd,translated_wagers,absolute_translation\n");
        ArrayList<S103>[] aa=new ArrayList[]{pc,hc},bb=new ArrayList[]{pq,hq};
        for(int k=0;k<2;k++)for(int i=0;i<aa[k].size();i++){
            S103 a=aa[k].get(i),b=bb[k].get(i);
            z.append(a.cohort).append(',').append(a.session).append(',')
             .append(fmt103(a.finalBank)).append(',').append(fmt103(b.finalBank)).append(',').append(fmt103(b.finalBank-a.finalBank)).append(',')
             .append(a.reached200).append(',').append(b.reached200).append(',')
             .append(a.exhausted).append(',').append(b.exhausted).append(',')
             .append(fmt103(a.cumWager)).append(',').append(fmt103(b.cumWager)).append(',')
             .append(fmt103(a.maxDD)).append(',').append(fmt103(b.maxDD)).append(',')
             .append(b.changedWagers).append(',').append(fmt103(b.changedWagerAbs)).append('\n');
        }
        return z.toString();
    }

    static ArrayList<S103> cohort103(long seed,String cohort,String policy){
        ArrayList<S103>a=new ArrayList<>();
        for(int i=0;i<SESSIONS;i++)a.add(runOne103(seed,cohort,i,policy));
        return a;
    }


    /* PHASE 3.104 — £15 TABLE-MINIMUM ROBUSTNESS
       This follows Phase 3.103's isolated £5 denomination translation.

       Comparison:
       A = frozen Phase 3.103 £5-chip controller (no new table minimum).
       B = identical controller with a £15 minimum original-round wager.

       Both use £5 wager units. The £15 minimum therefore forces requested
       £10 protective/recovery wagers upward to £15. The £17.50 request
       remains translated to £15 under the Phase 3.103 tie-down rule.

       No exit-threshold, action-policy, session-length or shuffle change. */

    static final String BASE104="PHASE_3_103_FIVE_POUND_BASELINE";
    static final String MIN15_104="FIFTEEN_POUND_TABLE_MINIMUM";

    static final class S104 {
        String cohort,policy; int session,hands,minimumForcedRounds,translatedRounds;
        double finalBank,peak,maxDD,cumWager,minimumExtraRequested;
        boolean reached200,exhausted,stopped;
    }

    static double wager104(double requested,double bank,boolean min15,S104 x){
        double chip=chip5FloorTie103(requested,bank);
        if(Math.abs(chip-requested)>1e-9)x.translatedRounds++;
        if(min15 && bank>=15.0-1e-9 && chip<15.0-1e-9){
            x.minimumForcedRounds++;
            x.minimumExtraRequested += 15.0-chip;
            chip=15.0;
        }
        if(chip>bank)chip=bank;
        return chip;
    }

    static S104 runOne104(long seed,String cohort,int session,String policy){
        int[]shoe=sixDeckShoe(sessionSeed(seed,session));
        if(!auditShoe(shoe))throw new IllegalStateException("Inventory audit failed");
        int ptr=0,hc=0;int[]hist=new int[5];
        double bank=START_BANK,peak=START_BANK,maxDD=0,cum=0;
        boolean everPlunge=false,cooling=false,cautious=false,negativeEpisode=false,brakeArmed=false;
        boolean activated160=false;
        S104 x=new S104();x.cohort=cohort;x.policy=policy;x.session=session+1;

        for(int hand=1;hand<=HANDS;hand++){
            if(bank>=200||bank<=0)break;
            if(cooling&&bank>=120){cooling=false;cautious=true;}
            if(cautious&&bank>=140)cautious=false;

            double requested;
            if(cooling){requested=10;negativeEpisode=false;brakeArmed=false;}
            else if(cautious){requested=cautious355(hist,hc);negativeEpisode=false;brakeArmed=false;}
            else if(hand>8&&bank>=140){
                int b=bal355(hist,hc);boolean neg=b<0;
                if(neg&&!negativeEpisode)brakeArmed=true;
                negativeEpisode=neg;
                double base=bank>=170?15:normal355(hand,bank);
                if(brakeArmed){requested=Math.min(base,15);brakeArmed=false;}else requested=base;
            } else {requested=normal355(hand,bank);negativeEpisode=false;brakeArmed=false;}

            requested=Math.min(requested,bank);
            double wager=wager104(requested,bank,policy.equals(MIN15_104),x);
            if(wager<=0)break;
            if(!cooling&&!cautious&&hand>8&&bank>=130)everPlunge=true;

            PairStats ps=new PairStats();
            Round rr=playRound(shoe,ptr,Profile.SELECTIVE_AGGRESSION,cohort,session+1,hand,true,bank,ps,wager,false);
            ptr=rr.endPointer;cum+=roundStake356(rr);
            bank+=rr.net;if(bank<0)bank=0;x.hands++;
            peak=Math.max(peak,bank);maxDD=Math.max(maxDD,peak-bank);
            hist[hc%hist.length]=rr.net>1e-9?1:(rr.net<-1e-9?-1:0);hc++;

            if(bank>=200-1e-9){x.reached200=true;break;}
            if(bank<=1e-9){x.exhausted=true;break;}
            if(bank>=160-1e-9)activated160=true;

            if(activated160 && peak>100+1e-9){
                double accumulatedProfit=peak-100.0;
                double surrendered=Math.max(0,peak-bank);
                double givePct=100.0*surrendered/accumulatedProfit;
                if(givePct>=25.0-1e-9 && bank<140.0-1e-9){
                    x.stopped=true;break;
                }
            }

            if(everPlunge&&bank<=100&&!cooling){
                cooling=true;cautious=false;negativeEpisode=false;brakeArmed=false;
            }
        }
        x.finalBank=bank;x.peak=peak;x.maxDD=maxDD;x.cumWager=cum;
        return x;
    }

    static ArrayList<S104> cohort104(long seed,String cohort,String policy){
        ArrayList<S104>a=new ArrayList<>();
        for(int i=0;i<SESSIONS;i++)a.add(runOne104(seed,cohort,i,policy));
        return a;
    }

    static void report104(StringBuilder r,String cohort,ArrayList<S104>a,ArrayList<S104>b){
        int n=a.size(),affected=0,higher=0,lower=0,same=0,a200=0,b200=0,a0=0,b0=0,apos=0,bpos=0;
        long forced=0;double extraReq=0,af=0,bf=0,aw=0,bw=0,add=0,bdd=0;
        for(int i=0;i<n;i++){
            S104 x=a.get(i),y=b.get(i);
            if(y.minimumForcedRounds>0)affected++;
            forced+=y.minimumForcedRounds;extraReq+=y.minimumExtraRequested;
            af+=x.finalBank;bf+=y.finalBank;aw+=x.cumWager;bw+=y.cumWager;add+=x.maxDD;bdd+=y.maxDD;
            if(x.reached200)a200++;if(y.reached200)b200++;
            if(x.exhausted)a0++;if(y.exhausted)b0++;
            if(x.finalBank>100+1e-9)apos++;if(y.finalBank>100+1e-9)bpos++;
            double d=y.finalBank-x.finalBank;if(d>1e-9)higher++;else if(d<-1e-9)lower++;else same++;
        }
        r.append("\n").append(cohort).append("\n");
        r.append(String.format(Locale.US,"Sessions in which £15 minimum forced >=1 wager upward: %d (%.2f%%)\n",affected,100.0*affected/n));
        r.append(String.format(Locale.US,"Original rounds forced from below £15 to £15: %d\n",forced));
        r.append(String.format(Locale.US,"Nominal extra original-round wager requested by minimum: £%.2f total; £%.3f per session\n",extraReq,extraReq/n));
        r.append(String.format(Locale.US,"Average final — £5 baseline / £15 minimum: £%.3f / £%.3f (delta £%+.3f)\n",af/n,bf/n,(bf-af)/n));
        r.append(String.format(Locale.US,"Positive sessions — baseline / minimum: %.2f%% / %.2f%%\n",100.0*apos/n,100.0*bpos/n));
        r.append(String.format(Locale.US,"Reached £200 — baseline / minimum: %.2f%% / %.2f%%\n",100.0*a200/n,100.0*b200/n));
        r.append(String.format(Locale.US,"Finished £0 — baseline / minimum: %.2f%% / %.2f%%\n",100.0*a0/n,100.0*b0/n));
        r.append(String.format(Locale.US,"Average cumulative wager — baseline / minimum: £%.3f / £%.3f (delta £%+.3f)\n",aw/n,bw/n,(bw-aw)/n));
        r.append(String.format(Locale.US,"Average max drawdown — baseline / minimum: £%.3f / £%.3f (delta £%+.3f)\n",add/n,bdd/n,(bdd-add)/n));
        r.append(String.format(Locale.US,"Paired final bankroll — minimum higher / lower / same: %.2f%% / %.2f%% / %.2f%%\n",
            100.0*higher/n,100.0*lower/n,100.0*same/n));
    }

    static String fmt104(double x){return String.format(Locale.US,"%.3f",x);}
    static String csv104(ArrayList<S104> pa,ArrayList<S104> pb,ArrayList<S104> ha,ArrayList<S104> hb){
        StringBuilder z=new StringBuilder();
        z.append("cohort,session,baseline_final,min15_final,delta_final,baseline_200,min15_200,baseline_0,min15_0,baseline_cum_wager,min15_cum_wager,baseline_max_dd,min15_max_dd,min15_forced_rounds,min15_nominal_extra_requested\n");
        ArrayList<S104>[] aa=new ArrayList[]{pa,ha},bb=new ArrayList[]{pb,hb};
        for(int k=0;k<2;k++)for(int i=0;i<aa[k].size();i++){
            S104 a=aa[k].get(i),b=bb[k].get(i);
            z.append(a.cohort).append(',').append(a.session).append(',')
             .append(fmt104(a.finalBank)).append(',').append(fmt104(b.finalBank)).append(',').append(fmt104(b.finalBank-a.finalBank)).append(',')
             .append(a.reached200).append(',').append(b.reached200).append(',').append(a.exhausted).append(',').append(b.exhausted).append(',')
             .append(fmt104(a.cumWager)).append(',').append(fmt104(b.cumWager)).append(',')
             .append(fmt104(a.maxDD)).append(',').append(fmt104(b.maxDD)).append(',')
             .append(b.minimumForcedRounds).append(',').append(fmt104(b.minimumExtraRequested)).append('\n');
        }return z.toString();
    }


    /* PHASE 3.105 — TABLE-MINIMUM IMPACT ANATOMY
       Measurement-only decomposition of the £15 table-minimum intervention.
       No controller adaptation and no threshold tuning.

       We identify where £10->£15 forced wagers occur by bankroll state, and
       pair each minimum-policy session against its Phase 3.103 £5-unit baseline.

       State bands are frozen before execution:
         BELOW_100
         100_149
         150_169
         170_199

       We then ask whether additional £200 outcomes and additional £0 outcomes
       are associated with different forced-exposure histories.
    */

    static final class A105 {
        String cohort; int session;
        S104 base,min;
        int forcedBelow100,forced100_149,forced150_169,forced170_199;
        double extraBelow100,extra100_149,extra150_169,extra170_199;
        String pairedClass;
    }

    static S104 runOne105(long seed,String cohort,int session,String policy,A105 rec){
        int[]shoe=sixDeckShoe(sessionSeed(seed,session));
        if(!auditShoe(shoe))throw new IllegalStateException("Inventory audit failed");
        int ptr=0,hc=0;int[]hist=new int[5];
        double bank=START_BANK,peak=START_BANK,maxDD=0,cum=0;
        boolean everPlunge=false,cooling=false,cautious=false,negativeEpisode=false,brakeArmed=false;
        boolean activated160=false;
        S104 x=new S104();x.cohort=cohort;x.policy=policy;x.session=session+1;

        for(int hand=1;hand<=HANDS;hand++){
            if(bank>=200||bank<=0)break;
            if(cooling&&bank>=120){cooling=false;cautious=true;}
            if(cautious&&bank>=140)cautious=false;

            double requested;
            if(cooling){requested=10;negativeEpisode=false;brakeArmed=false;}
            else if(cautious){requested=cautious355(hist,hc);negativeEpisode=false;brakeArmed=false;}
            else if(hand>8&&bank>=140){
                int b=bal355(hist,hc);boolean neg=b<0;
                if(neg&&!negativeEpisode)brakeArmed=true;
                negativeEpisode=neg;
                double base=bank>=170?15:normal355(hand,bank);
                if(brakeArmed){requested=Math.min(base,15);brakeArmed=false;}else requested=base;
            } else {requested=normal355(hand,bank);negativeEpisode=false;brakeArmed=false;}

            requested=Math.min(requested,bank);
            double chip=chip5FloorTie103(requested,bank);
            if(Math.abs(chip-requested)>1e-9)x.translatedRounds++;

            double wager=chip;
            if(policy.equals(MIN15_104) && bank>=15.0-1e-9 && chip<15.0-1e-9){
                x.minimumForcedRounds++;
                x.minimumExtraRequested += 15.0-chip;
                if(rec!=null){
                    if(bank<100){
                        rec.forcedBelow100++; rec.extraBelow100 += 15.0-chip;
                    }else if(bank<150){
                        rec.forced100_149++; rec.extra100_149 += 15.0-chip;
                    }else if(bank<170){
                        rec.forced150_169++; rec.extra150_169 += 15.0-chip;
                    }else{
                        rec.forced170_199++; rec.extra170_199 += 15.0-chip;
                    }
                }
                wager=15.0;
            }
            if(wager>bank)wager=bank;
            if(wager<=0)break;
            if(!cooling&&!cautious&&hand>8&&bank>=130)everPlunge=true;

            PairStats ps=new PairStats();
            Round rr=playRound(shoe,ptr,Profile.SELECTIVE_AGGRESSION,cohort,session+1,hand,true,bank,ps,wager,false);
            ptr=rr.endPointer;cum+=roundStake356(rr);
            bank+=rr.net;if(bank<0)bank=0;x.hands++;
            peak=Math.max(peak,bank);maxDD=Math.max(maxDD,peak-bank);
            hist[hc%hist.length]=rr.net>1e-9?1:(rr.net<-1e-9?-1:0);hc++;

            if(bank>=200-1e-9){x.reached200=true;break;}
            if(bank<=1e-9){x.exhausted=true;break;}
            if(bank>=160-1e-9)activated160=true;

            if(activated160 && peak>100+1e-9){
                double accumulatedProfit=peak-100.0;
                double surrendered=Math.max(0,peak-bank);
                double givePct=100.0*surrendered/accumulatedProfit;
                if(givePct>=25.0-1e-9 && bank<140.0-1e-9){
                    x.stopped=true;break;
                }
            }

            if(everPlunge&&bank<=100&&!cooling){
                cooling=true;cautious=false;negativeEpisode=false;brakeArmed=false;
            }
        }
        x.finalBank=bank;x.peak=peak;x.maxDD=maxDD;x.cumWager=cum;
        return x;
    }

    static ArrayList<A105> cohort105(long seed,String cohort){
        ArrayList<A105>a=new ArrayList<>();
        for(int i=0;i<SESSIONS;i++){
            A105 r=new A105();r.cohort=cohort;r.session=i+1;
            r.base=runOne105(seed,cohort,i,BASE104,null);
            r.min=runOne105(seed,cohort,i,MIN15_104,r);

            if(!r.base.reached200 && r.min.reached200)r.pairedClass="CREATED_200";
            else if(!r.base.exhausted && r.min.exhausted)r.pairedClass="CREATED_0";
            else if(r.base.reached200 && !r.min.reached200)r.pairedClass="LOST_200";
            else if(r.base.exhausted && !r.min.exhausted)r.pairedClass="PREVENTED_0";
            else if(r.min.finalBank>r.base.finalBank+1e-9)r.pairedClass="HIGHER_FINAL";
            else if(r.min.finalBank<r.base.finalBank-1e-9)r.pairedClass="LOWER_FINAL";
            else r.pairedClass="SAME_FINAL";
            a.add(r);
        }
        return a;
    }

    static void class105(StringBuilder r,String cohort,ArrayList<A105>a,String cls){
        long n=0;double b=0,m=0;
        long f0=0,f1=0,f2=0,f3=0;double e0=0,e1=0,e2=0,e3=0;
        for(A105 x:a)if(x.pairedClass.equals(cls)){
            n++;b+=x.base.finalBank;m+=x.min.finalBank;
            f0+=x.forcedBelow100;f1+=x.forced100_149;f2+=x.forced150_169;f3+=x.forced170_199;
            e0+=x.extraBelow100;e1+=x.extra100_149;e2+=x.extra150_169;e3+=x.extra170_199;
        }
        if(n==0)return;
        r.append(String.format(Locale.US,
            "%s: N=%d (%.2f%%), avg final baseline/minimum £%.2f/£%.2f, forced rounds per session [<100 %.2f | 100-149 %.2f | 150-169 %.2f | 170-199 %.2f], nominal extra £ per session [%.2f | %.2f | %.2f | %.2f]%n",
            cls,n,100.0*n/a.size(),b/n,m/n,
            (double)f0/n,(double)f1/n,(double)f2/n,(double)f3/n,
            e0/n,e1/n,e2/n,e3/n));
    }

    static void summary105(StringBuilder r,String cohort,ArrayList<A105>a){
        long f0=0,f1=0,f2=0,f3=0;double e0=0,e1=0,e2=0,e3=0;
        for(A105 x:a){
            f0+=x.forcedBelow100;f1+=x.forced100_149;f2+=x.forced150_169;f3+=x.forced170_199;
            e0+=x.extraBelow100;e1+=x.extra100_149;e2+=x.extra150_169;e3+=x.extra170_199;
        }
        long ft=f0+f1+f2+f3;double et=e0+e1+e2+e3;
        r.append("\n").append(cohort).append("\n");
        r.append("FORCED £10->£15 EXPOSURE BY BANKROLL STATE\n");
        r.append(String.format(Locale.US,"Below £100: %d rounds (%.2f%% of forced rounds), nominal extra £%.2f (%.2f%%)%n",
            f0,100.0*f0/ft,e0,100.0*e0/et));
        r.append(String.format(Locale.US,"£100-149: %d (%.2f%%), extra £%.2f (%.2f%%)%n",
            f1,100.0*f1/ft,e1,100.0*e1/et));
        r.append(String.format(Locale.US,"£150-169: %d (%.2f%%), extra £%.2f (%.2f%%)%n",
            f2,100.0*f2/ft,e2,100.0*e2/et));
        r.append(String.format(Locale.US,"£170-199: %d (%.2f%%), extra £%.2f (%.2f%%)%n",
            f3,100.0*f3/ft,e3,100.0*e3/et));

        r.append("\nPAIRED OUTCOME ANATOMY\n");
        for(String cls:new String[]{"CREATED_200","CREATED_0","LOST_200","PREVENTED_0","HIGHER_FINAL","LOWER_FINAL","SAME_FINAL"})
            class105(r,cohort,a,cls);
    }

    static String csv105(ArrayList<A105>...groups){
        StringBuilder z=new StringBuilder();
        z.append("cohort,session,paired_class,baseline_final,min15_final,baseline_200,min15_200,baseline_0,min15_0,forced_below100,forced_100_149,forced_150_169,forced_170_199,extra_below100,extra_100_149,extra_150_169,extra_170_199\n");
        for(ArrayList<A105>g:groups)for(A105 x:g)
            z.append(x.cohort).append(',').append(x.session).append(',').append(x.pairedClass).append(',')
             .append(fmt104(x.base.finalBank)).append(',').append(fmt104(x.min.finalBank)).append(',')
             .append(x.base.reached200).append(',').append(x.min.reached200).append(',')
             .append(x.base.exhausted).append(',').append(x.min.exhausted).append(',')
             .append(x.forcedBelow100).append(',').append(x.forced100_149).append(',')
             .append(x.forced150_169).append(',').append(x.forced170_199).append(',')
             .append(fmt104(x.extraBelow100)).append(',').append(fmt104(x.extra100_149)).append(',')
             .append(fmt104(x.extra150_169)).append(',').append(fmt104(x.extra170_199)).append('\n');
        return z.toString();
    }


    /* PHASE 3.106 — PROSPECTIVE LOW-BANKROLL TABLE-MINIMUM PROTECTION

       Terminology correction carried forward:
       In a £15-minimum casino, £10 is NOT a legal wager.
       "Constrained state" means the frozen controller's internal preferred
       protective stake would be below the table minimum. The casino-legal
       continuation is therefore £15.

       Predeclared candidate response:
       If bankroll is below £100 and the controller enters such a constrained
       state, WALK AWAY rather than continue at the legal £15 minimum.

       Three paired arms:
       A) Phase 3.103 £5-unit research baseline.
       B) Phase 3.104 unadapted £15-minimum environment.
       C) £15-minimum environment + prospective below-£100 walk-away response.

       No future information, no outcome-class predictor, no threshold search.
    */

    static final String PROTECT106="MIN15_BELOW100_WALKAWAY";

    static final class S106 {
        String cohort,policy,termination="HAND_CAP";
        int session,hands,constrainedStates;
        double finalBank,peak,maxDD,cumWager;
        boolean reached200,exhausted,stopped,protectionStop;
    }

    static S106 runOne106(long seed,String cohort,int session,String policy){
        int[]shoe=sixDeckShoe(sessionSeed(seed,session));
        if(!auditShoe(shoe))throw new IllegalStateException("Inventory audit failed");
        int ptr=0,hc=0;int[]hist=new int[5];
        double bank=START_BANK,peak=START_BANK,maxDD=0,cum=0;
        boolean everPlunge=false,cooling=false,cautious=false,negativeEpisode=false,brakeArmed=false;
        boolean activated160=false;
        S106 x=new S106();x.cohort=cohort;x.policy=policy;x.session=session+1;

        for(int hand=1;hand<=HANDS;hand++){
            if(bank>=200||bank<=0)break;
            if(cooling&&bank>=120){cooling=false;cautious=true;}
            if(cautious&&bank>=140)cautious=false;

            double requested;
            if(cooling){requested=10;negativeEpisode=false;brakeArmed=false;}
            else if(cautious){requested=cautious355(hist,hc);negativeEpisode=false;brakeArmed=false;}
            else if(hand>8&&bank>=140){
                int b=bal355(hist,hc);boolean neg=b<0;
                if(neg&&!negativeEpisode)brakeArmed=true;
                negativeEpisode=neg;
                double base=bank>=170?15:normal355(hand,bank);
                if(brakeArmed){requested=Math.min(base,15);brakeArmed=false;}else requested=base;
            } else {requested=normal355(hand,bank);negativeEpisode=false;brakeArmed=false;}

            requested=Math.min(requested,bank);
            double chip=chip5FloorTie103(requested,bank);
            boolean min15 = policy.equals(MIN15_104) || policy.equals(PROTECT106);
            boolean constrained = min15 && bank>=15.0-1e-9 && chip<15.0-1e-9;

            if(constrained){
                x.constrainedStates++;
                if(policy.equals(PROTECT106) && bank<100.0-1e-9){
                    x.protectionStop=true;x.stopped=true;x.termination="BELOW100_MINIMUM_CONSTRAINT_WALKAWAY";
                    break;
                }
                chip=15.0;
            }

            double wager=Math.min(chip,bank);
            if(wager<=0)break;
            if(!cooling&&!cautious&&hand>8&&bank>=130)everPlunge=true;

            PairStats ps=new PairStats();
            Round rr=playRound(shoe,ptr,Profile.SELECTIVE_AGGRESSION,cohort,session+1,hand,true,bank,ps,wager,false);
            ptr=rr.endPointer;cum+=roundStake356(rr);
            bank+=rr.net;if(bank<0)bank=0;x.hands++;
            peak=Math.max(peak,bank);maxDD=Math.max(maxDD,peak-bank);
            hist[hc%hist.length]=rr.net>1e-9?1:(rr.net<-1e-9?-1:0);hc++;

            if(bank>=200-1e-9){x.reached200=true;x.termination="REACHED_200";break;}
            if(bank<=1e-9){x.exhausted=true;x.termination="EXHAUSTED_0";break;}
            if(bank>=160-1e-9)activated160=true;

            if(activated160 && peak>100+1e-9){
                double accumulatedProfit=peak-100.0;
                double surrendered=Math.max(0,peak-bank);
                double givePct=100.0*surrendered/accumulatedProfit;
                if(givePct>=25.0-1e-9 && bank<140.0-1e-9){
                    x.stopped=true;x.termination="FROZEN_SELECTIVE_EXIT";break;
                }
            }

            if(everPlunge&&bank<=100&&!cooling){
                cooling=true;cautious=false;negativeEpisode=false;brakeArmed=false;
            }
        }
        x.finalBank=bank;x.peak=peak;x.maxDD=maxDD;x.cumWager=cum;
        return x;
    }

    static ArrayList<S106> cohort106(long seed,String cohort,String policy){
        ArrayList<S106>a=new ArrayList<>();
        for(int i=0;i<SESSIONS;i++)a.add(runOne106(seed,cohort,i,policy));
        return a;
    }

    static void report106(StringBuilder r,String cohort,ArrayList<S106> base,ArrayList<S106> min,ArrayList<S106> prot){
        int n=base.size(), b200=0,m200=0,p200=0,b0=0,m0=0,p0=0,bpos=0,mpos=0,ppos=0,pstop=0;
        int phigher=0,plower=0,psame=0, created200=0,prevented0=0,lost200=0,created0=0;
        double bf=0,mf=0,pf=0,bw=0,mw=0,pw=0,bdd=0,mdd=0,pdd=0,stopBank=0;
        for(int i=0;i<n;i++){
            S106 b=base.get(i),m=min.get(i),p=prot.get(i);
            bf+=b.finalBank;mf+=m.finalBank;pf+=p.finalBank;
            bw+=b.cumWager;mw+=m.cumWager;pw+=p.cumWager;
            bdd+=b.maxDD;mdd+=m.maxDD;pdd+=p.maxDD;
            if(b.reached200)b200++;if(m.reached200)m200++;if(p.reached200)p200++;
            if(b.exhausted)b0++;if(m.exhausted)m0++;if(p.exhausted)p0++;
            if(b.finalBank>100+1e-9)bpos++;if(m.finalBank>100+1e-9)mpos++;if(p.finalBank>100+1e-9)ppos++;
            if(p.protectionStop){pstop++;stopBank+=p.finalBank;}
            double d=p.finalBank-m.finalBank;
            if(d>1e-9)phigher++;else if(d<-1e-9)plower++;else psame++;
            if(!m.reached200 && p.reached200)created200++;
            if(m.exhausted && !p.exhausted)prevented0++;
            if(m.reached200 && !p.reached200)lost200++;
            if(!m.exhausted && p.exhausted)created0++;
        }
        r.append("\n").append(cohort).append("\n");
        r.append(String.format(Locale.US,"Protection walk-away activated: %d/%d (%.2f%%); mean retained bankroll at activation £%.2f%n",
            pstop,n,100.0*pstop/n,pstop==0?0:stopBank/pstop));
        r.append(String.format(Locale.US,"Average final — £5 baseline / unadapted £15 / protected £15: £%.3f / £%.3f / £%.3f%n",bf/n,mf/n,pf/n));
        r.append(String.format(Locale.US,"Positive sessions: %.2f%% / %.2f%% / %.2f%%%n",100.0*bpos/n,100.0*mpos/n,100.0*ppos/n));
        r.append(String.format(Locale.US,"Reached £200: %.2f%% / %.2f%% / %.2f%%%n",100.0*b200/n,100.0*m200/n,100.0*p200/n));
        r.append(String.format(Locale.US,"Finished £0: %.2f%% / %.2f%% / %.2f%%%n",100.0*b0/n,100.0*m0/n,100.0*p0/n));
        r.append(String.format(Locale.US,"Average cumulative wager: £%.3f / £%.3f / £%.3f%n",bw/n,mw/n,pw/n));
        r.append(String.format(Locale.US,"Average max drawdown: £%.3f / £%.3f / £%.3f%n",bdd/n,mdd/n,pdd/n));
        r.append(String.format(Locale.US,"Protected vs unadapted final — higher/lower/same: %.2f%% / %.2f%% / %.2f%%%n",
            100.0*phigher/n,100.0*plower/n,100.0*psame/n));
        r.append(String.format(Locale.US,"Protected vs unadapted — £200 created/prevented: %d / %d; £0 prevented/created: %d / %d%n",
            created200,lost200,prevented0,created0));
    }

    static String fmt106(double x){return String.format(Locale.US,"%.3f",x);}
    static String csv106(ArrayList<S106> pb,ArrayList<S106> pm,ArrayList<S106> pp,
                         ArrayList<S106> hb,ArrayList<S106> hm,ArrayList<S106> hp){
        StringBuilder z=new StringBuilder();
        z.append("cohort,session,baseline_final,min15_final,protected_final,baseline_200,min15_200,protected_200,baseline_0,min15_0,protected_0,baseline_cum_wager,min15_cum_wager,protected_cum_wager,baseline_max_dd,min15_max_dd,protected_max_dd,protection_stop,protection_termination,protected_minus_min15_final\n");
        ArrayList<S106>[] bbs=new ArrayList[]{pb,hb},mms=new ArrayList[]{pm,hm},pps=new ArrayList[]{pp,hp};
        for(int k=0;k<2;k++)for(int i=0;i<bbs[k].size();i++){
            S106 b=bbs[k].get(i),m=mms[k].get(i),p=pps[k].get(i);
            z.append(b.cohort).append(',').append(b.session).append(',')
             .append(fmt106(b.finalBank)).append(',').append(fmt106(m.finalBank)).append(',').append(fmt106(p.finalBank)).append(',')
             .append(b.reached200).append(',').append(m.reached200).append(',').append(p.reached200).append(',')
             .append(b.exhausted).append(',').append(m.exhausted).append(',').append(p.exhausted).append(',')
             .append(fmt106(b.cumWager)).append(',').append(fmt106(m.cumWager)).append(',').append(fmt106(p.cumWager)).append(',')
             .append(fmt106(b.maxDD)).append(',').append(fmt106(m.maxDD)).append(',').append(fmt106(p.maxDD)).append(',')
             .append(p.protectionStop).append(',').append(p.termination).append(',')
             .append(fmt106(p.finalBank-m.finalBank)).append('\n');
        }return z.toString();
    }


    /* PHASE 3.107 — PROTECTION-STOP ANATOMY
       Measurement only. No new threshold or intervention.

       We observe the first Phase 3.106 protection stop:
       - stop hand and bankroll
       - prior peak
       - cumulative wager before stop
       - whether bankroll had previously recovered to >=£100 after being <£100
       - counterfactual outcome under the unadapted £15-minimum continuation

       Counterfactual outcome is used retrospectively for anatomy only.
       It is never available to the live rule.
    */
    static final class A107 {
        String cohort; int session;
        boolean stopped,priorRecovery;
        int stopHand;
        double stopBank,priorPeak,cumBeforeStop;
        S106 unadapted;
        String counterfactual;
    }

    static A107 anatomy107(long seed,String cohort,int session){
        int[]shoe=sixDeckShoe(sessionSeed(seed,session));
        if(!auditShoe(shoe))throw new IllegalStateException("Inventory audit failed");
        int ptr=0,hc=0;int[]hist=new int[5];
        double bank=START_BANK,peak=START_BANK,cum=0;
        boolean everPlunge=false,cooling=false,cautious=false,negativeEpisode=false,brakeArmed=false;
        boolean activated160=false,beenBelow=false,recovered=false;
        A107 a=new A107();a.cohort=cohort;a.session=session+1;
        a.unadapted=runOne106(seed,cohort,session,MIN15_104);

        for(int hand=1;hand<=HANDS;hand++){
            if(bank>=200||bank<=0)break;
            if(cooling&&bank>=120){cooling=false;cautious=true;}
            if(cautious&&bank>=140)cautious=false;

            double requested;
            if(cooling){requested=10;negativeEpisode=false;brakeArmed=false;}
            else if(cautious){requested=cautious355(hist,hc);negativeEpisode=false;brakeArmed=false;}
            else if(hand>8&&bank>=140){
                int b=bal355(hist,hc);boolean neg=b<0;
                if(neg&&!negativeEpisode)brakeArmed=true;
                negativeEpisode=neg;
                double base=bank>=170?15:normal355(hand,bank);
                if(brakeArmed){requested=Math.min(base,15);brakeArmed=false;}else requested=base;
            } else {requested=normal355(hand,bank);negativeEpisode=false;brakeArmed=false;}

            requested=Math.min(requested,bank);
            double chip=chip5FloorTie103(requested,bank);
            boolean constrained=bank>=15.0-1e-9 && chip<15.0-1e-9;

            if(constrained && bank<100.0-1e-9){
                a.stopped=true;a.stopHand=hand;a.stopBank=bank;a.priorPeak=peak;
                a.cumBeforeStop=cum;a.priorRecovery=recovered;
                if(a.unadapted.reached200)a.counterfactual="UNADAPTED_REACHED_200";
                else if(a.unadapted.exhausted)a.counterfactual="UNADAPTED_EXHAUSTED_0";
                else if(a.unadapted.finalBank>100+1e-9)a.counterfactual="UNADAPTED_FINISHED_ABOVE_100";
                else if(Math.abs(a.unadapted.finalBank-100)<1e-9)a.counterfactual="UNADAPTED_FINISHED_100";
                else a.counterfactual="UNADAPTED_FINISHED_BELOW_100";
                return a;
            }
            if(constrained)chip=15.0;
            double wager=Math.min(chip,bank);
            if(wager<=0)break;
            if(!cooling&&!cautious&&hand>8&&bank>=130)everPlunge=true;

            PairStats ps=new PairStats();
            Round rr=playRound(shoe,ptr,Profile.SELECTIVE_AGGRESSION,cohort,session+1,hand,true,bank,ps,wager,false);
            ptr=rr.endPointer;cum+=roundStake356(rr);
            bank+=rr.net;if(bank<0)bank=0;
            peak=Math.max(peak,bank);
            hist[hc%hist.length]=rr.net>1e-9?1:(rr.net<-1e-9?-1:0);hc++;

            if(bank<100-1e-9)beenBelow=true;
            if(beenBelow && bank>=100-1e-9)recovered=true;

            if(bank>=200-1e-9||bank<=1e-9)break;
            if(bank>=160-1e-9)activated160=true;
            if(activated160 && peak>100+1e-9){
                double profit=peak-100.0,surrendered=Math.max(0,peak-bank);
                if(100.0*surrendered/profit>=25.0-1e-9 && bank<140.0-1e-9)break;
            }
            if(everPlunge&&bank<=100&&!cooling){
                cooling=true;cautious=false;negativeEpisode=false;brakeArmed=false;
            }
        }
        a.counterfactual="NO_PROTECTION_STOP";
        return a;
    }

    static ArrayList<A107> cohort107(long seed,String cohort){
        ArrayList<A107>a=new ArrayList<>();
        for(int i=0;i<SESSIONS;i++)a.add(anatomy107(seed,cohort,i));
        return a;
    }

    static double median107(ArrayList<Double>x){
        Collections.sort(x);int n=x.size();
        return n==0?0:(n%2==1?x.get(n/2):(x.get(n/2-1)+x.get(n/2))/2.0);
    }

    static void summary107(StringBuilder r,String cohort,ArrayList<A107>a){
        int n=0,rec=0;double sh=0,sb=0,pk=0,cw=0;
        ArrayList<Double> hands=new ArrayList<>(),banks=new ArrayList<>(),peaks=new ArrayList<>(),cums=new ArrayList<>();
        TreeMap<String,Integer> cf=new TreeMap<>();
        int[] handBands=new int[4], bankBands=new int[4];
        for(A107 x:a)if(x.stopped){
            n++;if(x.priorRecovery)rec++;
            sh+=x.stopHand;sb+=x.stopBank;pk+=x.priorPeak;cw+=x.cumBeforeStop;
            hands.add((double)x.stopHand);banks.add(x.stopBank);peaks.add(x.priorPeak);cums.add(x.cumBeforeStop);
            cf.put(x.counterfactual,cf.getOrDefault(x.counterfactual,0)+1);
            if(x.stopHand<=5)handBands[0]++; else if(x.stopHand<=10)handBands[1]++;
            else if(x.stopHand<=20)handBands[2]++; else handBands[3]++;
            if(x.stopBank<70)bankBands[0]++; else if(x.stopBank<80)bankBands[1]++;
            else if(x.stopBank<90)bankBands[2]++; else bankBands[3]++;
        }
        r.append("\n").append(cohort).append("\n");
        r.append(String.format(Locale.US,"Protection stops: %d/%d (%.2f%%)%n",n,a.size(),100.0*n/a.size()));
        r.append(String.format(Locale.US,"Stop hand mean/median: %.2f / %.2f%n",sh/n,median107(hands)));
        r.append(String.format(Locale.US,"Stop bankroll mean/median: £%.2f / £%.2f%n",sb/n,median107(banks)));
        r.append(String.format(Locale.US,"Prior peak mean/median: £%.2f / £%.2f%n",pk/n,median107(peaks)));
        r.append(String.format(Locale.US,"Cumulative wager before stop mean/median: £%.2f / £%.2f%n",cw/n,median107(cums)));
        r.append(String.format(Locale.US,"Had previously recovered to >=£100 after falling below it: %d (%.2f%% of stops)%n",rec,100.0*rec/n));
        r.append(String.format(Locale.US,"Stop-hand bands: 1-5 %d (%.2f%%); 6-10 %d (%.2f%%); 11-20 %d (%.2f%%); 21-30 %d (%.2f%%)%n",
            handBands[0],100.0*handBands[0]/n,handBands[1],100.0*handBands[1]/n,handBands[2],100.0*handBands[2]/n,handBands[3],100.0*handBands[3]/n));
        r.append(String.format(Locale.US,"Stop-bankroll bands: <£70 %d (%.2f%%); £70-79.99 %d (%.2f%%); £80-89.99 %d (%.2f%%); £90-99.99 %d (%.2f%%)%n",
            bankBands[0],100.0*bankBands[0]/n,bankBands[1],100.0*bankBands[1]/n,bankBands[2],100.0*bankBands[2]/n,bankBands[3],100.0*bankBands[3]/n));
        r.append("COUNTERFACTUAL UNADAPTED £15 OUTCOME AMONG STOPPED SESSIONS\n");
        for(Map.Entry<String,Integer>e:cf.entrySet())
            r.append(String.format(Locale.US,"%s: %d (%.2f%%)%n",e.getKey(),e.getValue(),100.0*e.getValue()/n));
    }

    static String csv107(ArrayList<A107>...groups){
        StringBuilder z=new StringBuilder();
        z.append("cohort,session,protection_stop,stop_hand,stop_bankroll,prior_peak,cumulative_wager_before_stop,prior_recovery,counterfactual_unadapted_outcome,unadapted_final,unadapted_200,unadapted_0\n");
        for(ArrayList<A107>g:groups)for(A107 x:g)
            z.append(x.cohort).append(',').append(x.session).append(',').append(x.stopped).append(',')
             .append(x.stopHand).append(',').append(fmt106(x.stopBank)).append(',')
             .append(fmt106(x.priorPeak)).append(',').append(fmt106(x.cumBeforeStop)).append(',')
             .append(x.priorRecovery).append(',').append(x.counterfactual).append(',')
             .append(fmt106(x.unadapted.finalBank)).append(',').append(x.unadapted.reached200).append(',')
             .append(x.unadapted.exhausted).append('\n');
        return z.toString();
    }


    /* PHASE 3.108 — OBSERVABLE EARLY-PATH ANATOMY
       Measurement only. No new live stopping rule or threshold.
       Extends 3.107 by recording only information observable by the time
       the first 3.106 protection stop would occur, then retrospectively
       stratifies those paths by paired unadapted £15 outcome.
    */
    static final class P108 {
        String cohort,counterfactual; int session,stopHand;
        double stopBank,priorPeak,cumWager,maxDrawdown,largestLoss;
        int completedRounds,handsBelow100,consecutiveBelow100,wins,losses,pushes;
        boolean stopped;
    }

    static P108 path108(long seed,String cohort,int session){
        int[]shoe=sixDeckShoe(sessionSeed(seed,session));
        if(!auditShoe(shoe))throw new IllegalStateException("Inventory audit failed");
        int ptr=0,hc=0; int[]hist=new int[5];
        double bank=START_BANK,peak=START_BANK,cum=0,maxDD=0,largestLoss=0;
        int below=0,consecBelow=0,wins=0,losses=0,pushes=0,completed=0;
        boolean everPlunge=false,cooling=false,cautious=false,negativeEpisode=false,brakeArmed=false,activated160=false;
        P108 p=new P108();p.cohort=cohort;p.session=session+1;
        S106 u=runOne106(seed,cohort,session,MIN15_104);

        for(int hand=1;hand<=HANDS;hand++){
            if(bank>=200||bank<=0)break;
            if(cooling&&bank>=120){cooling=false;cautious=true;}
            if(cautious&&bank>=140)cautious=false;

            double requested;
            if(cooling){requested=10;negativeEpisode=false;brakeArmed=false;}
            else if(cautious){requested=cautious355(hist,hc);negativeEpisode=false;brakeArmed=false;}
            else if(hand>8&&bank>=140){
                int b=bal355(hist,hc);boolean neg=b<0;
                if(neg&&!negativeEpisode)brakeArmed=true;
                negativeEpisode=neg;
                double base=bank>=170?15:normal355(hand,bank);
                if(brakeArmed){requested=Math.min(base,15);brakeArmed=false;}else requested=base;
            } else {requested=normal355(hand,bank);negativeEpisode=false;brakeArmed=false;}

            requested=Math.min(requested,bank);
            double chip=chip5FloorTie103(requested,bank);
            boolean constrained=bank>=15.0-1e-9 && chip<15.0-1e-9;

            if(constrained && bank<100.0-1e-9){
                p.stopped=true;p.stopHand=hand;p.stopBank=bank;p.priorPeak=peak;p.cumWager=cum;
                p.maxDrawdown=maxDD;p.largestLoss=largestLoss;p.completedRounds=completed;
                p.handsBelow100=below;p.consecutiveBelow100=consecBelow;
                p.wins=wins;p.losses=losses;p.pushes=pushes;
                if(u.reached200)p.counterfactual="REACHED_200";
                else if(u.exhausted)p.counterfactual="EXHAUSTED_0";
                else if(u.finalBank>100+1e-9)p.counterfactual="FINISHED_ABOVE_100";
                else if(Math.abs(u.finalBank-100)<1e-9)p.counterfactual="FINISHED_100";
                else p.counterfactual="FINISHED_BELOW_100";
                return p;
            }
            if(constrained)chip=15.0;
            double wager=Math.min(chip,bank);
            if(wager<=0)break;
            if(!cooling&&!cautious&&hand>8&&bank>=130)everPlunge=true;

            PairStats ps=new PairStats();
            Round rr=playRound(shoe,ptr,Profile.SELECTIVE_AGGRESSION,cohort,session+1,hand,true,bank,ps,wager,false);
            ptr=rr.endPointer; double before=bank;
            cum+=roundStake356(rr); bank+=rr.net;if(bank<0)bank=0;completed++;
            if(rr.net>1e-9)wins++; else if(rr.net<-1e-9){losses++;largestLoss=Math.max(largestLoss,-rr.net);} else pushes++;
            peak=Math.max(peak,bank);maxDD=Math.max(maxDD,peak-bank);
            if(bank<100-1e-9){below++;consecBelow++;}else consecBelow=0;
            hist[hc%hist.length]=rr.net>1e-9?1:(rr.net<-1e-9?-1:0);hc++;

            if(bank>=200-1e-9||bank<=1e-9)break;
            if(bank>=160-1e-9)activated160=true;
            if(activated160&&peak>100+1e-9){
                double profit=peak-100,surr=Math.max(0,peak-bank);
                if(100.0*surr/profit>=25.0-1e-9&&bank<140-1e-9)break;
            }
            if(everPlunge&&bank<=100&&!cooling){cooling=true;cautious=false;negativeEpisode=false;brakeArmed=false;}
        }
        p.counterfactual="NO_PROTECTION_STOP";return p;
    }

    static ArrayList<P108> cohort108(long seed,String cohort){
        ArrayList<P108>a=new ArrayList<>();
        for(int i=0;i<SESSIONS;i++)a.add(path108(seed,cohort,i));
        return a;
    }

    static void group108(StringBuilder r,String cohort,ArrayList<P108>a){
        TreeMap<String,ArrayList<P108>> m=new TreeMap<>();
        for(P108 p:a)if(p.stopped)m.computeIfAbsent(p.counterfactual,k->new ArrayList<>()).add(p);
        r.append("\n").append(cohort).append("\n");
        r.append("Retrospective groups; all predictor columns below were observable at stop time.\n");
        for(Map.Entry<String,ArrayList<P108>>e:m.entrySet()){
            ArrayList<P108>g=e.getValue();double sh=0,sb=0,cw=0,dd=0,ll=0,hb=0,cb=0,w=0,l=0,pu=0;
            for(P108 p:g){sh+=p.stopHand;sb+=p.stopBank;cw+=p.cumWager;dd+=p.maxDrawdown;ll+=p.largestLoss;
                hb+=p.handsBelow100;cb+=p.consecutiveBelow100;w+=p.wins;l+=p.losses;pu+=p.pushes;}
            int n=g.size();
            r.append(String.format(Locale.US,
                "%s n=%d | stop hand %.2f | bankroll £%.2f | wager £%.2f | maxDD £%.2f | largest loss £%.2f | hands<100 %.2f | consecutive<100 %.2f | W/L/P %.2f/%.2f/%.2f%n",
                e.getKey(),n,sh/n,sb/n,cw/n,dd/n,ll/n,hb/n,cb/n,w/n,l/n,pu/n));
        }
    }

    static String csv108(ArrayList<P108>...gs){
        StringBuilder z=new StringBuilder();
        z.append("cohort,session,stopped,counterfactual,stop_hand,stop_bankroll,prior_peak,cumulative_wager,max_drawdown,largest_round_loss,completed_rounds,hands_below_100,consecutive_below_100,wins,losses,pushes\n");
        for(ArrayList<P108>g:gs)for(P108 p:g)z.append(p.cohort).append(',').append(p.session).append(',').append(p.stopped).append(',')
          .append(p.counterfactual).append(',').append(p.stopHand).append(',').append(fmt106(p.stopBank)).append(',')
          .append(fmt106(p.priorPeak)).append(',').append(fmt106(p.cumWager)).append(',').append(fmt106(p.maxDrawdown)).append(',')
          .append(fmt106(p.largestLoss)).append(',').append(p.completedRounds).append(',').append(p.handsBelow100).append(',')
          .append(p.consecutiveBelow100).append(',').append(p.wins).append(',').append(p.losses).append(',').append(p.pushes).append('\n');
        return z.toString();
    }


    /* PHASE 3.109 — BELOW-£100 RECOVERY-EPISODE ANATOMY
       Measurement only under the unadapted £15-minimum continuation.
       No new stop rule, threshold, wager response or future information.
    */
    static final class E109 {
        String cohort,outcome; int session,episode,startHand,endHand,duration;
        double startBank,trough,exposure,recoveredAmount;
        boolean recovered,reached150,reached170,reached200,exhausted;
    }
    static final class R109 { ArrayList<E109> episodes=new ArrayList<>(); }

    static R109 episodes109(long seed,String cohort,int session){
        int[]shoe=sixDeckShoe(sessionSeed(seed,session));
        if(!auditShoe(shoe))throw new IllegalStateException("Inventory audit failed");
        int ptr=0,hc=0;int[]hist=new int[5];
        double bank=START_BANK,peak=START_BANK;
        boolean everPlunge=false,cooling=false,cautious=false,negativeEpisode=false,brakeArmed=false,activated160=false;
        boolean inEp=false; int epNo=0,epStart=0; double epStartBank=0,epTrough=0,epExposure=0;
        boolean r150=false,r170=false,r200=false,exhausted=false;
        R109 rrout=new R109();

        for(int hand=1;hand<=HANDS;hand++){
            if(bank>=200||bank<=0)break;
            if(cooling&&bank>=120){cooling=false;cautious=true;}
            if(cautious&&bank>=140)cautious=false;
            double requested;
            if(cooling){requested=10;negativeEpisode=false;brakeArmed=false;}
            else if(cautious){requested=cautious355(hist,hc);negativeEpisode=false;brakeArmed=false;}
            else if(hand>8&&bank>=140){
                int b=bal355(hist,hc);boolean neg=b<0;if(neg&&!negativeEpisode)brakeArmed=true;negativeEpisode=neg;
                double base=bank>=170?15:normal355(hand,bank);
                if(brakeArmed){requested=Math.min(base,15);brakeArmed=false;}else requested=base;
            } else {requested=normal355(hand,bank);negativeEpisode=false;brakeArmed=false;}
            requested=Math.min(requested,bank);
            double chip=chip5FloorTie103(requested,bank);
            boolean constrained=bank>=15-1e-9&&chip<15-1e-9;
            if(constrained)chip=15;
            double wager=Math.min(chip,bank);if(wager<=0)break;
            if(!cooling&&!cautious&&hand>8&&bank>=130)everPlunge=true;

            PairStats ps=new PairStats();
            Round rr=playRound(shoe,ptr,Profile.SELECTIVE_AGGRESSION,cohort,session+1,hand,true,bank,ps,wager,false);
            ptr=rr.endPointer; double stake=roundStake356(rr);
            bank+=rr.net;if(bank<0)bank=0;peak=Math.max(peak,bank);
            if(bank>=150-1e-9)r150=true;if(bank>=170-1e-9)r170=true;if(bank>=200-1e-9)r200=true;

            if(!inEp && bank<100-1e-9 && bank>1e-9){
                inEp=true;epNo++;epStart=hand;epStartBank=bank;epTrough=bank;epExposure=stake;
            } else if(inEp){
                epExposure+=stake;epTrough=Math.min(epTrough,bank);
            }

            if(inEp && bank>=100-1e-9){
                E109 e=new E109();e.cohort=cohort;e.session=session+1;e.episode=epNo;e.startHand=epStart;e.endHand=hand;
                e.duration=hand-epStart+1;e.startBank=epStartBank;e.trough=epTrough;e.exposure=epExposure;
                e.recovered=true;e.recoveredAmount=100-epTrough;
                rrout.episodes.add(e);inEp=false;
            }

            hist[hc%hist.length]=rr.net>1e-9?1:(rr.net<-1e-9?-1:0);hc++;
            if(bank>=200-1e-9||bank<=1e-9){if(bank<=1e-9)exhausted=true;break;}
            if(bank>=160-1e-9)activated160=true;
            if(activated160&&peak>100+1e-9){
                double profit=peak-100,surr=Math.max(0,peak-bank);
                if(100*surr/profit>=25-1e-9&&bank<140-1e-9)break;
            }
            if(everPlunge&&bank<=100&&!cooling){cooling=true;cautious=false;negativeEpisode=false;brakeArmed=false;}
        }
        if(inEp){
            E109 e=new E109();e.cohort=cohort;e.session=session+1;e.episode=epNo;e.startHand=epStart;e.endHand=HANDS;
            e.duration=Math.max(1,HANDS-epStart+1);e.startBank=epStartBank;e.trough=epTrough;e.exposure=epExposure;
            e.recovered=false;e.recoveredAmount=0;rrout.episodes.add(e);
        }
        String outcome=r200?"REACHED_200":(exhausted?"EXHAUSTED_0":(bank>100+1e-9?"FINISHED_ABOVE_100":(Math.abs(bank-100)<1e-9?"FINISHED_100":"FINISHED_BELOW_100")));
        for(E109 e:rrout.episodes){e.outcome=outcome;e.reached150=r150;e.reached170=r170;e.reached200=r200;e.exhausted=exhausted;}
        return rrout;
    }

    static ArrayList<E109> cohort109(long seed,String cohort){
        ArrayList<E109>a=new ArrayList<>();
        for(int i=0;i<SESSIONS;i++)a.addAll(episodes109(seed,cohort,i).episodes);
        return a;
    }
    static void sum109(StringBuilder r,String cohort,ArrayList<E109>a){
        HashSet<Integer> sess=new HashSet<>(),recSess=new HashSet<>(),failSess=new HashSet<>();
        int rec=0,fail=0;double rd=0,re=0,rt=0,fd=0,fe=0,ft=0;
        TreeMap<Integer,Integer> epsPer=new TreeMap<>();
        HashMap<Integer,Integer> cnt=new HashMap<>();
        for(E109 e:a){sess.add(e.session);cnt.put(e.session,cnt.getOrDefault(e.session,0)+1);
            if(e.recovered){rec++;recSess.add(e.session);rd+=e.duration;re+=e.exposure;rt+=e.trough;}
            else{fail++;failSess.add(e.session);fd+=e.duration;fe+=e.exposure;ft+=e.trough;}}
        for(int v:cnt.values())epsPer.put(v,epsPer.getOrDefault(v,0)+1);
        r.append("\n").append(cohort).append("\n");
        r.append(String.format(Locale.US,"Sessions with >=1 below-£100 episode: %d/%d (%.2f%%)%n",sess.size(),SESSIONS,100.0*sess.size()/SESSIONS));
        r.append(String.format(Locale.US,"Total episodes: %d | recovered: %d (%.2f%%) | failed/unrecovered: %d (%.2f%%)%n",a.size(),rec,100.0*rec/a.size(),fail,100.0*fail/a.size()));
        r.append(String.format(Locale.US,"Sessions with >=1 recovery: %d (%.2f%% of exposed sessions)%n",recSess.size(),100.0*recSess.size()/sess.size()));
        r.append(String.format(Locale.US,"Sessions with >=1 failed episode: %d (%.2f%% of exposed sessions)%n",failSess.size(),100.0*failSess.size()/sess.size()));
        r.append(String.format(Locale.US,"Recovered episodes mean duration %.2f hands | exposure £%.2f | trough £%.2f%n",rd/rec,re/rec,rt/rec));
        r.append(String.format(Locale.US,"Failed episodes mean duration %.2f hands | exposure £%.2f | trough £%.2f%n",fd/fail,fe/fail,ft/fail));
        r.append("Episodes per exposed session distribution: ");
        for(Map.Entry<Integer,Integer>e:epsPer.entrySet())r.append(e.getKey()).append("=").append(e.getValue()).append(" ");
        r.append("\n");
        int rec200=0,rec150=0,rec170=0,rec0=0,fail200=0,fail0=0;
        for(E109 e:a)if(e.recovered){if(e.reached150)rec150++;if(e.reached170)rec170++;if(e.reached200)rec200++;if(e.exhausted)rec0++;}
        else{if(e.reached200)fail200++;if(e.exhausted)fail0++;}
        r.append(String.format(Locale.US,"Recovered episodes associated with session later reaching £150/£170/£200/£0: %.2f%% / %.2f%% / %.2f%% / %.2f%%%n",
            100.0*rec150/rec,100.0*rec170/rec,100.0*rec200/rec,100.0*rec0/rec));
        r.append(String.format(Locale.US,"Failed episodes associated with session £200/£0: %.2f%% / %.2f%%%n",100.0*fail200/fail,100.0*fail0/fail));
    }
    static String csv109(ArrayList<E109>...gs){
        StringBuilder z=new StringBuilder("cohort,session,episode,start_hand,end_hand,duration,start_bankroll,trough,episode_exposure,recovered,recovered_amount,session_outcome,reached150,reached170,reached200,exhausted\n");
        for(ArrayList<E109>g:gs)for(E109 e:g)z.append(e.cohort).append(',').append(e.session).append(',').append(e.episode).append(',').append(e.startHand).append(',').append(e.endHand).append(',').append(e.duration).append(',').append(fmt106(e.startBank)).append(',').append(fmt106(e.trough)).append(',').append(fmt106(e.exposure)).append(',').append(e.recovered).append(',').append(fmt106(e.recoveredAmount)).append(',').append(e.outcome).append(',').append(e.reached150).append(',').append(e.reached170).append(',').append(e.reached200).append(',').append(e.exhausted).append('\n');
        return z.toString();
    }

    static final int[] CHECK110={3,5,10,15};
    static final class C110{
        String cohort,finalOutcome; int session,episode,checkpoint,globalHand;
        double currentBank,trough,exposure,drawdown100;
        boolean eventualRecovery,eventual200,eventual0;
    }
    static final class EP110{int episode,elapsed;double trough,exposure;ArrayList<C110> rows=new ArrayList<>();}
    static boolean check110(int n){for(int x:CHECK110)if(x==n)return true;return false;}

    static ArrayList<C110> run110(long seed,String cohort,int session){
        int[]shoe=sixDeckShoe(sessionSeed(seed,session)); if(!auditShoe(shoe))throw new IllegalStateException("Inventory audit failed");
        int ptr=0,hc=0;int[]hist=new int[5];double bank=START_BANK,peak=START_BANK;
        boolean everPlunge=false,cooling=false,cautious=false,negativeEpisode=false,brakeArmed=false,activated160=false,r200=false,exhausted=false;
        int epNo=0; EP110 ep=null; ArrayList<EP110> all=new ArrayList<>();
        for(int hand=1;hand<=HANDS;hand++){
            if(bank>=200||bank<=0)break;
            if(cooling&&bank>=120){cooling=false;cautious=true;} if(cautious&&bank>=140)cautious=false;
            double requested;
            if(cooling){requested=10;negativeEpisode=false;brakeArmed=false;}
            else if(cautious){requested=cautious355(hist,hc);negativeEpisode=false;brakeArmed=false;}
            else if(hand>8&&bank>=140){int b=bal355(hist,hc);boolean neg=b<0;if(neg&&!negativeEpisode)brakeArmed=true;negativeEpisode=neg;
                double bs=bank>=170?15:normal355(hand,bank);if(brakeArmed){requested=Math.min(bs,15);brakeArmed=false;}else requested=bs;}
            else{requested=normal355(hand,bank);negativeEpisode=false;brakeArmed=false;}
            requested=Math.min(requested,bank);double chip=chip5FloorTie103(requested,bank);
            if(bank>=15-1e-9&&chip<15-1e-9)chip=15;double wager=Math.min(chip,bank);if(wager<=0)break;
            if(!cooling&&!cautious&&hand>8&&bank>=130)everPlunge=true;
            PairStats ps=new PairStats();Round rr=playRound(shoe,ptr,Profile.SELECTIVE_AGGRESSION,cohort,session+1,hand,true,bank,ps,wager,false);
            ptr=rr.endPointer;double stake=roundStake356(rr);bank+=rr.net;if(bank<0)bank=0;peak=Math.max(peak,bank);

            if(ep==null&&bank<100-1e-9&&bank>1e-9){ep=new EP110();ep.episode=++epNo;ep.elapsed=1;ep.trough=bank;ep.exposure=stake;all.add(ep);}
            else if(ep!=null){ep.elapsed++;ep.exposure+=stake;ep.trough=Math.min(ep.trough,bank);}
            if(ep!=null&&bank<100-1e-9&&check110(ep.elapsed)){
                C110 c=new C110();c.cohort=cohort;c.session=session+1;c.episode=ep.episode;c.checkpoint=ep.elapsed;c.globalHand=hand;
                c.currentBank=bank;c.trough=ep.trough;c.exposure=ep.exposure;c.drawdown100=100-bank;ep.rows.add(c);
            }
            if(ep!=null&&bank>=100-1e-9){for(C110 c:ep.rows)c.eventualRecovery=true;ep=null;}
            hist[hc%hist.length]=rr.net>1e-9?1:(rr.net<-1e-9?-1:0);hc++;
            if(bank>=200-1e-9){r200=true;break;} if(bank<=1e-9){exhausted=true;break;}
            if(bank>=160-1e-9)activated160=true;
            if(activated160&&peak>100+1e-9){double profit=peak-100,surr=Math.max(0,peak-bank);if(100*surr/profit>=25-1e-9&&bank<140-1e-9)break;}
            if(everPlunge&&bank<=100&&!cooling){cooling=true;cautious=false;negativeEpisode=false;brakeArmed=false;}
        }
        String outcome=r200?"REACHED_200":(exhausted?"EXHAUSTED_0":(bank>100+1e-9?"FINISHED_ABOVE_100":(Math.abs(bank-100)<1e-9?"FINISHED_100":"FINISHED_BELOW_100")));
        ArrayList<C110> z=new ArrayList<>();for(EP110 e:all)for(C110 c:e.rows){c.eventual200=r200;c.eventual0=exhausted;c.finalOutcome=outcome;z.add(c);}return z;
    }
    static ArrayList<C110> cohort110(long seed,String cohort){ArrayList<C110>a=new ArrayList<>();for(int i=0;i<SESSIONS;i++)a.addAll(run110(seed,cohort,i));return a;}
    static void report110(StringBuilder r,String cohort,ArrayList<C110>a){
        r.append("\n").append(cohort).append("\n");
        for(int cp:CHECK110){ArrayList<C110>g=new ArrayList<>();for(C110 c:a)if(c.checkpoint==cp)g.add(c);
            int n=g.size(),rec=0,r2=0,r0=0;double b=0,t=0,e=0;
            for(C110 c:g){if(c.eventualRecovery)rec++;if(c.eventual200)r2++;if(c.eventual0)r0++;b+=c.currentBank;t+=c.trough;e+=c.exposure;}
            r.append(String.format(Locale.US,"Checkpoint %d: n=%d | later recovery %.2f%% | session £200 %.2f%% | session £0 %.2f%% | bankroll £%.2f | trough £%.2f | exposure £%.2f%n",cp,n,100.0*rec/n,100.0*r2/n,100.0*r0/n,b/n,t/n,e/n));
            for(boolean q:new boolean[]{true,false}){int nn=0;double bb=0,tt=0,ee=0;for(C110 c:g)if(c.eventualRecovery==q){nn++;bb+=c.currentBank;tt+=c.trough;ee+=c.exposure;}
                r.append(String.format(Locale.US,"  %s n=%d | bankroll £%.2f | trough £%.2f | exposure £%.2f%n",q?"LATER_RECOVERED":"LATER_UNRECOVERED",nn,bb/nn,tt/nn,ee/nn));}
        }
    }
    static String csv110(ArrayList<C110>...gs){StringBuilder z=new StringBuilder("cohort,session,episode,checkpoint_active_rounds,global_hand,current_bankroll,trough,episode_exposure,drawdown_from_100,eventual_recovery,eventual_200,eventual_0,session_final_outcome\n");
        for(ArrayList<C110>g:gs)for(C110 c:g)z.append(c.cohort).append(',').append(c.session).append(',').append(c.episode).append(',').append(c.checkpoint).append(',').append(c.globalHand).append(',').append(fmt106(c.currentBank)).append(',').append(fmt106(c.trough)).append(',').append(fmt106(c.exposure)).append(',').append(fmt106(c.drawdown100)).append(',').append(c.eventualRecovery).append(',').append(c.eventual200).append(',').append(c.eventual0).append(',').append(c.finalOutcome).append('\n');return z.toString();}

    /* PHASE 3.117 — FROZEN PROTECTIVE WALK-AWAY INTERVENTION

       Frozen gate inherited unchanged from 3.115:
       - episode remains below £100 for 10 active recovery rounds
       - current bankroll < £50

       Frozen intervention:
       - from gate activation, cap requested wager at the legal £15 table minimum
       - remain in protection until bankroll recovers to >= £100
       - no termination, no future information, no threshold sweep
    */
    static final class S116 {
        String cohort,policy,termination="HAND_CAP"; int session,hands,gateActivations,recoveriesAfterGate;
        double finalBank,peak,maxDD,cumWager,failedGateExposure;
        double gateBankroll,gateEpisodeTrough,gateEpisodeExposure,gateLargestLoss,gateAbsoluteMovement;
        int gateEpisodeWins,gateEpisodeLosses,gateEpisodePushes;
        boolean reached200,exhausted,gateEver;
        boolean obsEnteredRecovery,obsCapitalAlert,obsRecovered100,obsReached150,obsReached170;
        int obsRecoveryHand=-1,obsCapitalAlertHand=-1,obsRecovered100Hand=-1,obsReached150Hand=-1,obsReached170Hand=-1;
        double obsPeak=100.0,obsTrough=100.0;
        int obsPeakHand=0,obsTroughHand=0;
    }

    static S116 runOne116(long seed,String cohort,int session,boolean candidate){
        int[]shoe=sixDeckShoe(sessionSeed(seed,session));if(!auditShoe(shoe))throw new IllegalStateException("Inventory audit failed");
        int ptr=0,hc=0;int[]hist=new int[5];double bank=START_BANK,peak=START_BANK,maxDD=0,cum=0;
        boolean everPlunge=false,cooling=false,cautious=false,negativeEpisode=false,brakeArmed=false,activated160=false;
        boolean inEpisode=false,protect=false;int epElapsed=0;double gateExposure=0;
        double epTrough=0,epExposure=0,epLargestLoss=0,epAbsoluteMovement=0;
        int epWins=0,epLosses=0,epPushes=0;
        S116 x=new S116();x.cohort=cohort;x.policy=candidate?"FROZEN_CP10_LT50_CAP15":"REFERENCE";x.session=session+1;

        for(int hand=1;hand<=HANDS;hand++){
            if(bank>=200||bank<=0)break;
            if(cooling&&bank>=120){cooling=false;cautious=true;}if(cautious&&bank>=140)cautious=false;
            double requested;
            if(cooling){requested=10;negativeEpisode=false;brakeArmed=false;}
            else if(cautious){requested=cautious355(hist,hc);negativeEpisode=false;brakeArmed=false;}
            else if(hand>8&&bank>=140){int b=bal355(hist,hc);boolean neg=b<0;if(neg&&!negativeEpisode)brakeArmed=true;negativeEpisode=neg;
                double bs=bank>=170?15:normal355(hand,bank);if(brakeArmed){requested=Math.min(bs,15);brakeArmed=false;}else requested=bs;}
            else{requested=normal355(hand,bank);negativeEpisode=false;brakeArmed=false;}

            /* 3.117: no wager modification before the frozen gate. */
            requested=Math.min(requested,bank);double chip=chip5FloorTie103(requested,bank);
            if(bank>=15-1e-9&&chip<15-1e-9)chip=15;
            double wager=Math.min(chip,bank);if(wager<=0)break;
            if(!cooling&&!cautious&&hand>8&&bank>=130)everPlunge=true;

            PairStats ps=new PairStats();Round rr=playRound(shoe,ptr,Profile.SELECTIVE_AGGRESSION,cohort,session+1,hand,true,bank,ps,wager,false);
            ptr=rr.endPointer;double stake=roundStake356(rr);double beforeBank=bank;cum+=stake;if(protect)gateExposure+=stake;
            bank+=rr.net;if(bank<0)bank=0;x.hands++;peak=Math.max(peak,bank);maxDD=Math.max(maxDD,peak-bank);

            if(!inEpisode&&bank<100-1e-9&&bank>1e-9){
                if(!x.obsEnteredRecovery){x.obsEnteredRecovery=true;x.obsRecoveryHand=hand;}
                inEpisode=true;epElapsed=1;epTrough=bank;epExposure=stake;epLargestLoss=rr.net<0?-rr.net:0;
                epAbsoluteMovement=Math.abs(bank-beforeBank);epWins=rr.net>1e-9?1:0;epLosses=rr.net<-1e-9?1:0;epPushes=Math.abs(rr.net)<=1e-9?1:0;
            }
            else if(inEpisode){
                epElapsed++;epTrough=Math.min(epTrough,bank);epExposure+=stake;epLargestLoss=Math.max(epLargestLoss,rr.net<0?-rr.net:0);
                epAbsoluteMovement+=Math.abs(bank-beforeBank);if(rr.net>1e-9)epWins++;else if(rr.net<-1e-9)epLosses++;else epPushes++;
            }

            if(inEpisode&&epElapsed>=10&&bank<50-1e-9&&!x.obsCapitalAlert){
                x.obsCapitalAlert=true;x.obsCapitalAlertHand=hand;
            }
            if(candidate&&inEpisode&&!protect&&epElapsed>=10&&bank<50-1e-9){
                protect=true;x.gateEver=true;x.gateActivations++;gateExposure=0;
                x.gateBankroll=bank;x.gateEpisodeTrough=epTrough;x.gateEpisodeExposure=epExposure;
                x.gateLargestLoss=epLargestLoss;x.gateAbsoluteMovement=epAbsoluteMovement;
                x.gateEpisodeWins=epWins;x.gateEpisodeLosses=epLosses;x.gateEpisodePushes=epPushes;
                x.termination="FROZEN_CP10_LT50_WALK_AWAY";
                break;
            }
            if(inEpisode&&bank>=100-1e-9){
                if(!x.obsRecovered100){x.obsRecovered100=true;x.obsRecovered100Hand=hand;}
                if(protect){x.recoveriesAfterGate++;protect=false;gateExposure=0;}
                inEpisode=false;epElapsed=0;epTrough=0;epExposure=0;epLargestLoss=0;epAbsoluteMovement=0;epWins=epLosses=epPushes=0;
            }

            if(bank>x.obsPeak+1e-9){x.obsPeak=bank;x.obsPeakHand=hand;}
            if(bank<x.obsTrough-1e-9){x.obsTrough=bank;x.obsTroughHand=hand;}
            if(bank>=150-1e-9&&!x.obsReached150){x.obsReached150=true;x.obsReached150Hand=hand;}
            if(bank>=170-1e-9&&!x.obsReached170){x.obsReached170=true;x.obsReached170Hand=hand;}
            hist[hc%hist.length]=rr.net>1e-9?1:(rr.net<-1e-9?-1:0);hc++;
            if(bank>=200-1e-9){x.reached200=true;x.termination="REACHED_200";break;}
            if(bank<=1e-9){x.exhausted=true;x.termination="EXHAUSTED_0";break;}
            if(bank>=160-1e-9)activated160=true;
            if(activated160&&peak>100+1e-9){double profit=peak-100,surr=Math.max(0,peak-bank);
                if(100*surr/profit>=25-1e-9&&bank<140-1e-9){x.termination="FROZEN_SELECTIVE_EXIT";break;}}
            if(everPlunge&&bank<=100&&!cooling){cooling=true;cautious=false;negativeEpisode=false;brakeArmed=false;}
        }
        if(protect)x.failedGateExposure+=gateExposure;
        x.finalBank=bank;x.peak=peak;x.maxDD=maxDD;x.cumWager=cum;return x;
    }

    static ArrayList<S116> cohort116(long seed,String cohort,boolean candidate){
        ArrayList<S116>a=new ArrayList<>();for(int i=0;i<SESSIONS;i++)a.add(runOne116(seed,cohort,i,candidate));return a;
    }




    static final long FRESH_A_SEED = 202601L;
    static final long FRESH_B_SEED = 202701L;








    static double pct130(int x,int n){return 100.0*x/n;}
    static final long INT_C_SEED=203301L,INT_D_SEED=203401L;

    static final class Evidence130{
        String cohort;int n,rec,alert,alertRecovered,alert150,alert170;
        int profit150,profitGiveback,near170;
    }
    static Evidence130 evidence130(String cohort,ArrayList<S116>a){
        Evidence130 e=new Evidence130();e.cohort=cohort;e.n=a.size();
        for(S116 s:a){
            if(s.obsEnteredRecovery)e.rec++;
            if(s.obsCapitalAlert){
                e.alert++;
                if(s.obsRecovered100)e.alertRecovered++;
                if(s.obsReached150)e.alert150++;
                if(s.obsReached170)e.alert170++;
            }
            if(s.obsReached150)e.profit150++;
            if(s.obsReached150&&!s.obsReached170&&s.finalBank<100-1e-9)e.profitGiveback++;
            if(s.obsReached170)e.near170++;
        }
        return e;
    }

    static void panel130(StringBuilder r,Evidence130 e){
        r.append("\n").append(e.cohort).append("\n");
        r.append(String.format(Locale.US,"CAPITAL_ALERT incidence: %.2f%%%n",pct130(e.alert,e.n)));
        r.append(String.format(Locale.US,"Historical continuation after CAPITAL_ALERT: later >=£100 %.2f%% | >=£150 %.2f%% | >=£170 %.2f%%%n",
          pct130(e.alertRecovered,e.alert),pct130(e.alert150,e.alert),pct130(e.alert170,e.alert)));
        r.append(String.format(Locale.US,"Reached >=£150: %.2f%% | completed PROFIT_GIVEBACK archetype: %.2f%% of all sessions%n",
          pct130(e.profit150,e.n),pct130(e.profitGiveback,e.n)));
        r.append(String.format(Locale.US,"Reached >=£170: %.2f%%%n",pct130(e.near170,e.n)));
        r.append("LIVE DECISION-SUPPORT INFORMATION BOUNDARY\n");
        r.append("  ALLOWED NOW: current bankroll, past peak/trough, elapsed recovery rounds, cumulative wager, prior state crossings, declared/current objective.\n");
        r.append("  HISTORICAL CONTEXT ONLY: aggregate transition/archetype frequencies from completed populations.\n");
        r.append("  FORBIDDEN AS LIVE INPUT: the current session's eventual archetype, future cards, future bankroll, eventual £0/£200 result.\n");
    }

    static String matrix130(){
        StringBuilder z=new StringBuilder("observed_state,objective_state,live_response,evidence_role,future_information_allowed\n");
        String[] states={"RECOVERY_ACTIVE","CAPITAL_ALERT","RECOVERED","PROFIT_DEPLOYMENT","PROTECTIVE_NEAR_TARGET"};
        String[] obj={"CAPITAL_PRESERVATION","BALANCED_RECOVERY","OPPORTUNITY_PRESERVATION"};
        for(String s:states)for(String o:obj){
            String response;
            if(s.equals("CAPITAL_ALERT")){
                if(o.equals("CAPITAL_PRESERVATION"))response="WALK_AWAY_SUPPORTED";
                else if(o.equals("OPPORTUNITY_PRESERVATION"))response="CONTINUE_REFERENCE_WITH_RUIN_EXPOSURE_WARNING";
                else response="SHOW_TRADEOFF_HUMAN_CHOICE";
            } else {
                response="STATE_EVIDENCE_ONLY_NO_NEW_ACTION_VALIDATED";
            }
            z.append(s).append(',').append(o).append(',').append(response)
             .append(",USE_COMPLETED_POPULATIONS_AS_HISTORICAL_CONTEXT_ONLY,false\n");
        }
        return z.toString();
    }


    /* PHASE 3.131 — PROSPECTIVE £150 PROFIT-PROTECTION ACTION SCREEN
       PREDECLARED BEFORE EXECUTION.

       Purpose:
       Phase 3.130 established £150 as a descriptive live state but explicitly
       refused to manufacture advice from retrospective path labels.
       Phase 3.131 therefore tests three prospective action overlays beginning
       only after the CURRENT session first reaches >=£150:

       1. CONTINUE_REFERENCE
          Preserve the frozen Phase 3.130 continuation architecture.
       2. HARD_EXIT_150
          Capital-preservation boundary: stop immediately on first completed
          round at >=£150.
       3. TRAIL_150_GIVEBACK_25
          Balanced protection boundary: after first reaching >=£150, continue,
          but stop if >=25% of accumulated high-water profit above £100 has
          subsequently been surrendered.

       The frozen Phase 3.130 selective £160/25%/<£140 rule remains available
       inside CONTINUE_REFERENCE and as a fallback in the trailing overlay.
       No wager-controller, hand-action policy, denomination rule, shoe order,
       target, or session horizon is tuned here.

       This is an action-validation screen, NOT an optimisation phase.
    */

    static final String P131_REF="CONTINUE_REFERENCE";
    static final String P131_EXIT150="HARD_EXIT_150";
    static final String P131_TRAIL150="TRAIL_150_GIVEBACK_25";

    static final class S131 {
        String cohort,policy,termination;
        int session,hands,first150Hand=-1,stopHand=-1;
        double finalBank,peak,maxDD,cumWager,first150Bank=0,stopBank=0;
        boolean reached150,reached170,reached200,exhausted,stopped;
    }

    static S131 runOne131(long seed,String cohort,int session,String policy){
        int[]shoe=sixDeckShoe(sessionSeed(seed,session));
        if(!auditShoe(shoe))throw new IllegalStateException("Inventory audit failed");
        int ptr=0,hc=0;int[]hist=new int[5];
        double bank=START_BANK,peak=START_BANK,maxDD=0,cum=0;
        boolean everPlunge=false,cooling=false,cautious=false,negativeEpisode=false,brakeArmed=false;
        boolean activated160=false,activated150=false;
        S131 x=new S131();x.cohort=cohort;x.policy=policy;x.session=session+1;x.termination="30_HAND";

        for(int hand=1;hand<=HANDS;hand++){
            if(bank>=200||bank<=0)break;
            if(cooling&&bank>=120){cooling=false;cautious=true;}
            if(cautious&&bank>=140)cautious=false;

            double requested;
            if(cooling){requested=10;negativeEpisode=false;brakeArmed=false;}
            else if(cautious){requested=cautious355(hist,hc);negativeEpisode=false;brakeArmed=false;}
            else if(hand>8&&bank>=140){
                int b=bal355(hist,hc);boolean neg=b<0;
                if(neg&&!negativeEpisode)brakeArmed=true;negativeEpisode=neg;
                double bs=bank>=170?15:normal355(hand,bank);
                if(brakeArmed){requested=Math.min(bs,15);brakeArmed=false;}else requested=bs;
            }else{requested=normal355(hand,bank);negativeEpisode=false;brakeArmed=false;}

            requested=Math.min(requested,bank);
            double chip=chip5FloorTie103(requested,bank);
            if(bank>=15-1e-9&&chip<15-1e-9)chip=15;
            double wager=Math.min(chip,bank);if(wager<=0)break;
            if(!cooling&&!cautious&&hand>8&&bank>=130)everPlunge=true;

            PairStats ps=new PairStats();
            Round rr=playRound(shoe,ptr,Profile.SELECTIVE_AGGRESSION,cohort,session+1,hand,true,bank,ps,wager,false);
            ptr=rr.endPointer;cum+=roundStake356(rr);
            bank+=rr.net;if(bank<0)bank=0;x.hands++;
            peak=Math.max(peak,bank);maxDD=Math.max(maxDD,peak-bank);

            hist[hc%hist.length]=rr.net>1e-9?1:(rr.net<-1e-9?-1:0);hc++;

            if(bank>=150-1e-9&&!activated150){
                activated150=true;x.reached150=true;x.first150Hand=hand;x.first150Bank=bank;
            }
            if(bank>=170-1e-9)x.reached170=true;

            if(bank>=200-1e-9){x.reached200=true;x.termination="REACHED_200";break;}
            if(bank<=1e-9){x.exhausted=true;x.termination="EXHAUSTED_0";break;}

            // Prospective overlays begin only after the observed >=£150 state exists.
            if(activated150 && policy.equals(P131_EXIT150)){
                x.stopped=true;x.stopHand=hand;x.stopBank=bank;x.termination="HARD_EXIT_150";break;
            }
            if(activated150 && policy.equals(P131_TRAIL150) && peak>100+1e-9){
                double profit=peak-100.0;
                double surrendered=Math.max(0,peak-bank);
                if(100.0*surrendered/profit>=25.0-1e-9){
                    x.stopped=true;x.stopHand=hand;x.stopBank=bank;x.termination="TRAIL_150_GIVEBACK_25";break;
                }
            }

            // Frozen Phase 3.130 selective protection remains part of the reference architecture.
            if(bank>=160-1e-9)activated160=true;
            if(activated160&&peak>100+1e-9){
                double profit=peak-100.0,surr=Math.max(0,peak-bank);
                if(100.0*surr/profit>=25.0-1e-9&&bank<140-1e-9){
                    x.stopped=true;x.stopHand=hand;x.stopBank=bank;x.termination="FROZEN_SELECTIVE_EXIT";break;
                }
            }

            if(everPlunge&&bank<=100&&!cooling){
                cooling=true;cautious=false;negativeEpisode=false;brakeArmed=false;
            }
        }
        x.finalBank=bank;x.peak=peak;x.maxDD=maxDD;x.cumWager=cum;
        return x;
    }

    static ArrayList<S131> cohort131(long seed,String cohort,String policy){
        ArrayList<S131>a=new ArrayList<>();
        for(int i=0;i<SESSIONS;i++)a.add(runOne131(seed,cohort,i,policy));
        return a;
    }

    static final class M131{
        String cohort,policy;int n,reach150,reach170,reach200,bust,positive,be,loss,stopped;
        double finalSum,wagerSum,ddSum,first150HandSum,stopHandSum,stopBankSum;
    }
    static M131 measure131(String cohort,String policy,ArrayList<S131>a){
        M131 m=new M131();m.cohort=cohort;m.policy=policy;m.n=a.size();
        for(S131 s:a){
            m.finalSum+=s.finalBank;m.wagerSum+=s.cumWager;m.ddSum+=s.maxDD;
            if(s.reached150){m.reach150++;m.first150HandSum+=s.first150Hand;}
            if(s.reached170)m.reach170++;if(s.reached200)m.reach200++;if(s.exhausted)m.bust++;
            if(s.finalBank>100+1e-9)m.positive++;else if(Math.abs(s.finalBank-100)<1e-9)m.be++;else m.loss++;
            if(s.stopped){m.stopped++;m.stopHandSum+=s.stopHand;m.stopBankSum+=s.stopBank;}
        }
        return m;
    }
    static double p131(int x,int n){return n==0?0:100.0*x/n;}
    static void panel131(StringBuilder r,M131 m){
        double net=m.finalSum-100.0*m.n;
        r.append("\n").append(m.cohort).append(" / ").append(m.policy).append("\n");
        r.append(String.format(Locale.US,"Average ending bankroll: £%.3f%n",m.finalSum/m.n));
        r.append(String.format(Locale.US,"Population net change: £%+.2f%n",net));
        r.append(String.format(Locale.US,"Population return on cumulative wager: %+.4f%%%n",100.0*net/m.wagerSum));
        r.append(String.format(Locale.US,"Positive / break-even / losing: %.2f%% / %.2f%% / %.2f%%%n",
            p131(m.positive,m.n),p131(m.be,m.n),p131(m.loss,m.n)));
        r.append(String.format(Locale.US,"Reached £150 / £170 / £200: %.2f%% / %.2f%% / %.2f%%%n",
            p131(m.reach150,m.n),p131(m.reach170,m.n),p131(m.reach200,m.n)));
        r.append(String.format(Locale.US,"Finished £0: %.2f%%%n",p131(m.bust,m.n)));
        r.append(String.format(Locale.US,"Average maximum drawdown: £%.3f%n",m.ddSum/m.n));
        r.append(String.format(Locale.US,"Average cumulative wager: £%.3f%n",m.wagerSum/m.n));
        r.append(String.format(Locale.US,"First £150 reach: %.2f%% of sessions | mean hand %.2f%n",
            p131(m.reach150,m.n),m.reach150==0?0:m.first150HandSum/m.reach150));
        if(m.stopped>0)r.append(String.format(Locale.US,
            "Stops: %.2f%% of sessions | mean stop hand %.2f | mean retained bankroll £%.3f%n",
            p131(m.stopped,m.n),m.stopHandSum/m.stopped,m.stopBankSum/m.stopped));
    }

    static String csv131(ArrayList<S131>...groups){
        StringBuilder z=new StringBuilder("cohort,policy,session,hands,first150_hand,first150_bankroll,final_bankroll,peak_bankroll,max_drawdown,cumulative_wager,reached150,reached170,reached200,exhausted,stopped,stop_hand,stop_bankroll,termination\n");
        for(ArrayList<S131>g:groups)for(S131 s:g)z.append(String.format(Locale.US,
            "%s,%s,%d,%d,%d,%.2f,%.2f,%.2f,%.2f,%.2f,%s,%s,%s,%s,%s,%d,%.2f,%s%n",
            s.cohort,s.policy,s.session,s.hands,s.first150Hand,s.first150Bank,s.finalBank,s.peak,s.maxDD,s.cumWager,
            s.reached150,s.reached170,s.reached200,s.exhausted,s.stopped,s.stopHand,s.stopBank,s.termination));
        return z.toString();
    }


    /* PHASE 3.132 — PAIRED CAUSAL DECOMPOSITION OF £150 PROTECTION
       PREDECLARED ANALYSIS. NO THRESHOLD TUNING.

       Replays the identical deterministic cohort/session under the three frozen
       Phase 3.131 policies and compares outcomes pairwise. The purpose is to
       explain WHAT the prospective actions changed, not to select a new threshold.
    */
    static final class Pair132 {
        String cohort, comparison;
        int n, affected, altBetter, refBetter, equalFinal;
        int terminalLossPrevented, terminalLossCreated;
        int target200Removed, target200Preserved, target200Created;
        int profitableContinuationCutShort, interventionSavedLoss;
        double finalDeltaSum, exposureRemovedSum, ddAvoidedSum;
        double absFinalDeltaSum;
    }

    static Pair132 compare132(String cohort, String label, ArrayList<S131> ref, ArrayList<S131> alt){
        Pair132 p=new Pair132(); p.cohort=cohort; p.comparison=label; p.n=ref.size();
        for(int i=0;i<ref.size();i++){
            S131 r=ref.get(i), a=alt.get(i);
            double d=a.finalBank-r.finalBank;
            double ex=r.cumWager-a.cumWager;
            double dd=r.maxDD-a.maxDD;
            p.finalDeltaSum+=d; p.exposureRemovedSum+=ex; p.ddAvoidedSum+=dd; p.absFinalDeltaSum+=Math.abs(d);
            boolean affected=Math.abs(d)>1e-9 || Math.abs(ex)>1e-9 || r.reached200!=a.reached200 || r.exhausted!=a.exhausted;
            if(affected)p.affected++;
            if(d>1e-9)p.altBetter++; else if(d<-1e-9)p.refBetter++; else p.equalFinal++;
            if(r.exhausted&&!a.exhausted){p.terminalLossPrevented++;p.interventionSavedLoss++;}
            if(!r.exhausted&&a.exhausted)p.terminalLossCreated++;
            if(r.reached200&&!a.reached200)p.target200Removed++;
            if(r.reached200&&a.reached200)p.target200Preserved++;
            if(!r.reached200&&a.reached200)p.target200Created++;
            if(a.stopped && r.finalBank>a.finalBank+1e-9)p.profitableContinuationCutShort++;
        }
        return p;
    }

    static void panel132(StringBuilder r, Pair132 p){
        r.append("\n").append(p.cohort).append(" / ").append(p.comparison).append("\n");
        r.append(String.format(Locale.US,"Sessions affected by intervention: %d / %d (%.2f%%)%n",p.affected,p.n,p131(p.affected,p.n)));
        r.append(String.format(Locale.US,"Alternative final bankroll higher / reference higher / equal: %.2f%% / %.2f%% / %.2f%%%n",
            p131(p.altBetter,p.n),p131(p.refBetter,p.n),p131(p.equalFinal,p.n)));
        r.append(String.format(Locale.US,"Mean final-bankroll delta (alternative - reference): £%+.3f%n",p.finalDeltaSum/p.n));
        r.append(String.format(Locale.US,"Mean exposure removed (reference - alternative): £%+.3f%n",p.exposureRemovedSum/p.n));
        r.append(String.format(Locale.US,"Mean max-drawdown avoided (reference - alternative): £%+.3f%n",p.ddAvoidedSum/p.n));
        r.append(String.format(Locale.US,"Terminal £0 losses prevented / created: %d / %d (%.2f%% / %.2f%%)%n",
            p.terminalLossPrevented,p.terminalLossCreated,p131(p.terminalLossPrevented,p.n),p131(p.terminalLossCreated,p.n)));
        r.append(String.format(Locale.US,"£200 outcomes removed / preserved / created: %d / %d / %d (%.2f%% / %.2f%% / %.2f%%)%n",
            p.target200Removed,p.target200Preserved,p.target200Created,
            p131(p.target200Removed,p.n),p131(p.target200Preserved,p.n),p131(p.target200Created,p.n)));
        r.append(String.format(Locale.US,"Stopped sessions where reference later finished higher: %d (%.2f%%)%n",
            p.profitableContinuationCutShort,p131(p.profitableContinuationCutShort,p.n)));
    }

    static String csv132(String cohort, ArrayList<S131> ref, ArrayList<S131> hard, ArrayList<S131> trail){
        StringBuilder z=new StringBuilder("cohort,session,comparison,ref_final,alt_final,final_delta,ref_exposure,alt_exposure,exposure_removed,ref_maxdd,alt_maxdd,drawdown_avoided,ref_200,alt_200,ref_zero,alt_zero,alt_stopped,alt_stop_hand,alt_stop_bankroll\n");
        ArrayList<S131>[] alts=new ArrayList[]{hard,trail};
        String[] labels={"HARD_EXIT_150_vs_CONTINUE_REFERENCE","TRAIL_150_GIVEBACK_25_vs_CONTINUE_REFERENCE"};
        for(int k=0;k<alts.length;k++)for(int i=0;i<ref.size();i++){
            S131 r=ref.get(i),a=alts[k].get(i);
            z.append(String.format(Locale.US,"%s,%d,%s,%.2f,%.2f,%.2f,%.2f,%.2f,%.2f,%.2f,%.2f,%.2f,%s,%s,%s,%s,%s,%d,%.2f%n",
                cohort,i+1,labels[k],r.finalBank,a.finalBank,a.finalBank-r.finalBank,
                r.cumWager,a.cumWager,r.cumWager-a.cumWager,r.maxDD,a.maxDD,r.maxDD-a.maxDD,
                r.reached200,a.reached200,r.exhausted,a.exhausted,a.stopped,a.stopHand,a.stopBank));
        }
        return z.toString();
    }


    /* PHASE 3.133 — HELP-VERSUS-HARM DIVERGENCE ANATOMY
       RETROSPECTIVE CHARACTERISATION ONLY — NOT A LIVE PREDICTOR.

       Phase 3.132 established paired divergence. 3.133 asks what the observable
       session looked like AT THE PREDECLARED intervention point in sessions
       where protection later helped versus harmed. Future outcome labels are
       used only after replay for research grouping and are never fed to policy.
       The 25% threshold remains frozen; no threshold search occurs.
    */

    static final class A133 {
        String cohort,policy,outcome;
        int session,interventionHand;
        double interventionBank,peakAtIntervention,exposureAtIntervention,drawdownAtIntervention;
        double finalRef,finalAlt,finalDelta,exposureRemoved,ddAvoided;
        boolean ref200,alt200,refZero,altZero;
    }

    static A133 anatomy133(String cohort,int session,String policy,S131 ref,S131 alt){
        A133 a=new A133();a.cohort=cohort;a.session=session+1;a.policy=policy;
        a.interventionHand=alt.stopHand;a.interventionBank=alt.stopBank;
        a.finalRef=ref.finalBank;a.finalAlt=alt.finalBank;a.finalDelta=alt.finalBank-ref.finalBank;
        a.exposureRemoved=ref.cumWager-alt.cumWager;a.ddAvoided=ref.maxDD-alt.maxDD;
        a.ref200=ref.reached200;a.alt200=alt.reached200;a.refZero=ref.exhausted;a.altZero=alt.exhausted;
        if(a.finalDelta>1e-9)a.outcome="HELPED";
        else if(a.finalDelta<-1e-9)a.outcome="HARMED";
        else a.outcome="NEUTRAL";

        // Replay only to the already-observed intervention hand to recover state anatomy.
        // This does not alter either policy result and does not use future cards.
        int[]shoe=sixDeckShoe(sessionSeed(policy.equals(P131_EXIT150)||policy.equals(P131_TRAIL150)?seedForCohort133(cohort):seedForCohort133(cohort),session));
        if(!auditShoe(shoe))throw new IllegalStateException("Inventory audit failed");
        int ptr=0,hc=0;int[]hist=new int[5];double bank=START_BANK,peak=START_BANK,cum=0;
        boolean everPlunge=false,cooling=false,cautious=false,negativeEpisode=false,brakeArmed=false;
        for(int hand=1;hand<=a.interventionHand && hand<=HANDS;hand++){
            if(bank>=200||bank<=0)break;
            if(cooling&&bank>=120){cooling=false;cautious=true;} if(cautious&&bank>=140)cautious=false;
            double requested;
            if(cooling){requested=10;negativeEpisode=false;brakeArmed=false;}
            else if(cautious){requested=cautious355(hist,hc);negativeEpisode=false;brakeArmed=false;}
            else if(hand>8&&bank>=140){
                int b=bal355(hist,hc);boolean neg=b<0;if(neg&&!negativeEpisode)brakeArmed=true;negativeEpisode=neg;
                double bs=bank>=170?15:normal355(hand,bank);requested=brakeArmed?Math.min(bs,15):bs;if(brakeArmed)brakeArmed=false;
            } else {requested=normal355(hand,bank);negativeEpisode=false;brakeArmed=false;}
            requested=Math.min(requested,bank);double chip=chip5FloorTie103(requested,bank);
            if(bank>=15-1e-9&&chip<15-1e-9)chip=15;double wager=Math.min(chip,bank);if(wager<=0)break;
            if(!cooling&&!cautious&&hand>8&&bank>=130)everPlunge=true;
            PairStats ps=new PairStats();Round rr=playRound(shoe,ptr,Profile.SELECTIVE_AGGRESSION,cohort,session+1,hand,true,bank,ps,wager,false);
            ptr=rr.endPointer;cum+=roundStake356(rr);bank+=rr.net;if(bank<0)bank=0;peak=Math.max(peak,bank);
            hist[hc%hist.length]=rr.net>1e-9?1:(rr.net<-1e-9?-1:0);hc++;
            if(everPlunge&&bank<=100&&!cooling){cooling=true;cautious=false;negativeEpisode=false;brakeArmed=false;}
        }
        a.peakAtIntervention=peak;a.exposureAtIntervention=cum;a.drawdownAtIntervention=Math.max(0,peak-a.interventionBank);
        return a;
    }

    static long seedForCohort133(String c){
        if(c.equals("FRESH_A"))return FRESH_A_SEED;if(c.equals("FRESH_B"))return FRESH_B_SEED;
        if(c.equals("ROBUST_C"))return INT_C_SEED;return INT_D_SEED;
    }

    static void group133(StringBuilder r,String policy,String outcome,ArrayList<A133> all){
        int n=0;double h=0,b=0,p=0,e=0,d=0,fd=0,er=0,dda=0;int r200=0,r0=0;
        for(A133 a:all)if(a.policy.equals(policy)&&a.outcome.equals(outcome)){
            n++;h+=a.interventionHand;b+=a.interventionBank;p+=a.peakAtIntervention;e+=a.exposureAtIntervention;
            d+=a.drawdownAtIntervention;fd+=a.finalDelta;er+=a.exposureRemoved;dda+=a.ddAvoided;
            if(a.ref200)r200++;if(a.refZero)r0++;
        }
        r.append("\n").append(policy).append(" / ").append(outcome).append("\n");
        r.append("n = ").append(n).append("\n"); if(n==0)return;
        r.append(String.format(Locale.US,"Mean intervention hand: %.3f%n",h/n));
        r.append(String.format(Locale.US,"Mean bankroll at intervention: £%.3f%n",b/n));
        r.append(String.format(Locale.US,"Mean peak at intervention: £%.3f%n",p/n));
        r.append(String.format(Locale.US,"Mean cumulative exposure at intervention: £%.3f%n",e/n));
        r.append(String.format(Locale.US,"Mean drawdown at intervention: £%.3f%n",d/n));
        r.append(String.format(Locale.US,"Mean final delta (protection-reference): £%+.3f%n",fd/n));
        r.append(String.format(Locale.US,"Mean exposure subsequently removed: £%+.3f%n",er/n));
        r.append(String.format(Locale.US,"Mean maxDD subsequently avoided: £%+.3f%n",dda/n));
        r.append(String.format(Locale.US,"Reference later £200 / £0: %.2f%% / %.2f%%%n",p131(r200,n),p131(r0,n)));
    }

    static String csv133(ArrayList<A133> all){
        StringBuilder z=new StringBuilder("cohort,session,policy,outcome,intervention_hand,intervention_bankroll,peak_at_intervention,exposure_at_intervention,drawdown_at_intervention,reference_final,protected_final,final_delta,exposure_removed,maxdd_avoided,reference_200,protected_200,reference_zero,protected_zero\n");
        for(A133 a:all)z.append(String.format(Locale.US,"%s,%d,%s,%s,%d,%.2f,%.2f,%.2f,%.2f,%.2f,%.2f,%.2f,%.2f,%.2f,%s,%s,%s,%s%n",
            a.cohort,a.session,a.policy,a.outcome,a.interventionHand,a.interventionBank,a.peakAtIntervention,a.exposureAtIntervention,a.drawdownAtIntervention,
            a.finalRef,a.finalAlt,a.finalDelta,a.exposureRemoved,a.ddAvoided,a.ref200,a.alt200,a.refZero,a.altZero));
        return z.toString();
    }


    /* PHASE 3.134 — PROSPECTIVE EXPOSURE DISCRIMINATION GATE
       FRESH-COHORT VALIDATION. NO THRESHOLD TUNING.

       Phase 3.133 found a retrospective exposure difference at the frozen
       TRAIL_150_GIVEBACK_25 intervention:
           HELPED mean exposure ≈ £214.86
           HARMED mean exposure ≈ £234.99

       Before running any new cohort, Phase 3.137 predeclares ONE simple,
       rounded, observable discriminator:
           LOW-EXPOSURE GATE: cumulative wager at trigger <= £225

       Hypothesis:
       among sessions where the frozen trailing rule triggers, the <=£225
       subgroup will contain a larger HELPED:HARMED balance than the >£225
       subgroup.

       This phase evaluates classification/enrichment only. It does NOT alter
       the live policy and does NOT tune £225 or the frozen 25% trailing rule.
    */

    static final double P134_EXPOSURE_GATE = 225.0;
    static final long P134_A_SEED = 206101L;
    static final long P134_B_SEED = 206201L;
    static final long P134_C_SEED = 206301L;
    static final long P134_D_SEED = 206401L;

    static final class D134 {
        String cohort, band, outcome;
        int n, helped, harmed, neutral, ref200, refZero;
        double expSum, handSum, bankSum, peakSum, ddSum, deltaSum;
    }

    static void add134(D134 d, A133 a){
        d.n++;
        if(a.outcome.equals("HELPED")) d.helped++;
        else if(a.outcome.equals("HARMED")) d.harmed++;
        else d.neutral++;
        if(a.ref200)d.ref200++;
        if(a.refZero)d.refZero++;
        d.expSum += a.exposureAtIntervention;
        d.handSum += a.interventionHand;
        d.bankSum += a.interventionBank;
        d.peakSum += a.peakAtIntervention;
        d.ddSum += a.drawdownAtIntervention;
        d.deltaSum += a.finalDelta;
    }

    static double ratio134(int a,int b){ return b==0 ? Double.POSITIVE_INFINITY : 1.0*a/b; }

    static void report134(StringBuilder r, D134 d){
        r.append("\n").append(d.cohort).append(" / ").append(d.band).append("\n");
        r.append("n = ").append(d.n).append("\n");
        if(d.n==0)return;
        r.append(String.format(Locale.US,"HELPED / HARMED / NEUTRAL: %d / %d / %d (%.2f%% / %.2f%% / %.2f%%)%n",
            d.helped,d.harmed,d.neutral,p131(d.helped,d.n),p131(d.harmed,d.n),p131(d.neutral,d.n)));
        r.append(String.format(Locale.US,"HELPED:HARMED ratio: %.3f%n",ratio134(d.helped,d.harmed)));
        r.append(String.format(Locale.US,"Mean exposure at trigger: £%.3f%n",d.expSum/d.n));
        r.append(String.format(Locale.US,"Mean trigger hand: %.3f%n",d.handSum/d.n));
        r.append(String.format(Locale.US,"Mean bankroll / peak / drawdown at trigger: £%.3f / £%.3f / £%.3f%n",
            d.bankSum/d.n,d.peakSum/d.n,d.ddSum/d.n));
        r.append(String.format(Locale.US,"Mean final delta from applying trailing protection: £%+.3f%n",d.deltaSum/d.n));
        r.append(String.format(Locale.US,"Reference later £200 / £0: %.2f%% / %.2f%%%n",
            p131(d.ref200,d.n),p131(d.refZero,d.n)));
    }

    static String csv134(ArrayList<A133> all){
        StringBuilder z=new StringBuilder(
            "cohort,session,exposure_gate,gate_band,outcome,intervention_hand,intervention_bankroll,peak_at_intervention,exposure_at_intervention,drawdown_at_intervention,reference_final,protected_final,final_delta,reference_200,reference_zero\n");
        for(A133 a:all){
            String band=a.exposureAtIntervention<=P134_EXPOSURE_GATE+1e-9 ? "LOW_EXPOSURE_LE_225" : "HIGH_EXPOSURE_GT_225";
            z.append(String.format(Locale.US,
                "%s,%d,%.2f,%s,%s,%d,%.2f,%.2f,%.2f,%.2f,%.2f,%.2f,%.2f,%s,%s%n",
                a.cohort,a.session,P134_EXPOSURE_GATE,band,a.outcome,a.interventionHand,
                a.interventionBank,a.peakAtIntervention,a.exposureAtIntervention,
                a.drawdownAtIntervention,a.finalRef,a.finalAlt,a.finalDelta,a.ref200,a.refZero));
        }
        return z.toString();
    }


    /* PHASE 3.137 — FRESH TWO-VARIABLE DISCRIMINATOR VALIDATION
       FROZEN FROM PHASE 3.136:
          cumulative exposure at trailing trigger <= £225
          AND current bankroll at trigger >= £140

       This phase validates discrimination only. It does not make the two-variable
       condition a live action policy and does not tune either cutpoint.
    */
    static final double P137_EXPOSURE_GATE=225.0;
    static final double P137_BANKROLL_GATE=140.0;
    static final long[] P137_SEEDS={206101L,206201L,206301L,206401L};
    static final String[] P137_COHORTS={"FRESH_M","FRESH_N","FRESH_O","FRESH_P"};

    static void report137(StringBuilder r,String cohort,String label,ArrayList<A133> xs){
        int h=0,b=0,n=0;double delta=0,exp=0,hand=0,bank=0,peak=0,dd=0;
        for(A133 a:xs){if(a.outcome.equals("HELPED"))h++;else if(a.outcome.equals("HARMED"))b++;else n++;
            delta+=a.finalDelta;exp+=a.exposureAtIntervention;hand+=a.interventionHand;bank+=a.interventionBank;peak+=a.peakAtIntervention;dd+=a.drawdownAtIntervention;}
        int z=xs.size();double ratio=b==0?Double.POSITIVE_INFINITY:1.0*h/b;
        r.append("\n").append(cohort).append(" / ").append(label).append("\n");
        r.append("n = ").append(z).append("\n");
        if(z>0)r.append(String.format(Locale.US,
          "HELPED / HARMED / NEUTRAL: %d / %d / %d (%.2f%% / %.2f%% / %.2f%%)%nHELPED:HARMED ratio: %.3f%nMean final delta: £%+.3f%nMean trigger hand: %.3f%nMean bankroll / peak / drawdown: £%.3f / £%.3f / £%.3f%nMean trigger exposure: £%.3f%n",
          h,b,n,p131(h,z),p131(b,z),p131(n,z),ratio,delta/z,hand/z,bank/z,peak/z,dd/z,exp/z));
    }


    /* PHASE 3.139 — TWO-VARIABLE GATED TRAILING ACTION TEST
       Entirely fresh deterministic cohorts.

       Frozen from prior evidence:
         exposure <= £225
         bankroll >= £140
         trailing give-back = 25%

       Policies:
         CONTINUE_REFERENCE
         ALWAYS_TRAIL_150_GIVEBACK_25
         TWO_VARIABLE_GATED_TRAIL

       No threshold tuning.
    */
    static final long[] P139_SEEDS={207101L,207201L,207301L,207401L};
    static final String[] P139_COHORTS={"FRESH_Q","FRESH_R","FRESH_S","FRESH_T"};
    static final String P139_GATED="TWO_VARIABLE_GATED_TRAIL";
    static final double P139_EXPOSURE_GATE=225.0,P139_BANKROLL_GATE=140.0;

    static S131 runOne139(long seed,String cohort,int session,String policy){
        int[]shoe=sixDeckShoe(sessionSeed(seed,session));if(!auditShoe(shoe))throw new IllegalStateException("Inventory audit failed");
        int ptr=0,hc=0;int[]hist=new int[5];double bank=START_BANK,peak=START_BANK,maxDD=0,cum=0;
        boolean everPlunge=false,cooling=false,cautious=false,negativeEpisode=false,brakeArmed=false,activated160=false,activated150=false;
        S131 x=new S131();x.cohort=cohort;x.policy=policy;x.session=session+1;x.termination="30_HAND";
        for(int hand=1;hand<=HANDS;hand++){
            if(bank>=200||bank<=0)break;
            if(cooling&&bank>=120){cooling=false;cautious=true;}if(cautious&&bank>=140)cautious=false;
            double requested;
            if(cooling){requested=10;negativeEpisode=false;brakeArmed=false;}
            else if(cautious){requested=cautious355(hist,hc);negativeEpisode=false;brakeArmed=false;}
            else if(hand>8&&bank>=140){int b=bal355(hist,hc);boolean neg=b<0;if(neg&&!negativeEpisode)brakeArmed=true;negativeEpisode=neg;double bs=bank>=170?15:normal355(hand,bank);requested=brakeArmed?Math.min(bs,15):bs;if(brakeArmed)brakeArmed=false;}
            else{requested=normal355(hand,bank);negativeEpisode=false;brakeArmed=false;}
            requested=Math.min(requested,bank);double chip=chip5FloorTie103(requested,bank);if(bank>=15-1e-9&&chip<15-1e-9)chip=15;
            double wager=Math.min(chip,bank);if(wager<=0)break;
            if(!cooling&&!cautious&&hand>8&&bank>=130)everPlunge=true;
            PairStats ps=new PairStats();Round rr=playRound(shoe,ptr,Profile.SELECTIVE_AGGRESSION,cohort,session+1,hand,true,bank,ps,wager,false);
            ptr=rr.endPointer;cum+=roundStake356(rr);bank+=rr.net;if(bank<0)bank=0;x.hands++;peak=Math.max(peak,bank);maxDD=Math.max(maxDD,peak-bank);
            hist[hc%hist.length]=rr.net>1e-9?1:(rr.net<-1e-9?-1:0);hc++;
            if(bank>=150-1e-9&&!activated150){activated150=true;x.reached150=true;x.first150Hand=hand;x.first150Bank=bank;}
            if(bank>=170-1e-9)x.reached170=true;
            if(bank>=200-1e-9){x.reached200=true;x.termination="REACHED_200";break;}
            if(bank<=1e-9){x.exhausted=true;x.termination="EXHAUSTED_0";break;}

            boolean trailTrigger=false;
            if(activated150&&peak>100+1e-9){
                double profit=peak-100.0,surr=Math.max(0,peak-bank);
                trailTrigger=100.0*surr/profit>=25.0-1e-9;
            }

            if(policy.equals(P131_TRAIL150)&&trailTrigger){
                x.stopped=true;x.stopHand=hand;x.stopBank=bank;x.termination="TRAIL_150_GIVEBACK_25";break;
            }

            if(policy.equals(P139_GATED)&&trailTrigger
                &&cum<=P139_EXPOSURE_GATE+1e-9
                &&bank>=P139_BANKROLL_GATE-1e-9){
                x.stopped=true;x.stopHand=hand;x.stopBank=bank;x.termination=P139_GATED;break;
            }

            if(policy.equals(P131_REF)){
                if(bank>=160-1e-9)activated160=true;
                if(activated160&&peak>100+1e-9){
                    double profit=peak-100.0,surr=Math.max(0,peak-bank);
                    if(100.0*surr/profit>=25.0-1e-9&&bank<140-1e-9){
                        x.stopped=true;x.stopHand=hand;x.stopBank=bank;x.termination="FROZEN_SELECTIVE_EXIT";break;
                    }
                }
            }

            if(everPlunge&&bank<=100&&!cooling){cooling=true;cautious=false;negativeEpisode=false;brakeArmed=false;}
        }
        x.finalBank=bank;x.peak=peak;x.maxDD=maxDD;x.cumWager=cum;return x;
    }

    static ArrayList<S131> cohort139(long seed,String cohort,String policy){
        ArrayList<S131>a=new ArrayList<>();for(int i=0;i<SESSIONS;i++)a.add(runOne139(seed,cohort,i,policy));return a;
    }

    static String csv139(ArrayList<ArrayList<S131>> all){
        StringBuilder z=new StringBuilder("cohort,policy,session,hands,final_bankroll,cumulative_wager,mean_wager_per_hand,max_drawdown,reached150,reached170,reached200,exhausted,stopped,stop_hand,stop_bankroll,termination\n");
        for(ArrayList<S131>g:all)for(S131 s:g)z.append(String.format(Locale.US,
            "%s,%s,%d,%d,%.2f,%.2f,%.4f,%.2f,%s,%s,%s,%s,%s,%d,%.2f,%s%n",
            s.cohort,s.policy,s.session,s.hands,s.finalBank,s.cumWager,s.hands>0?s.cumWager/s.hands:0,s.maxDD,
            s.reached150,s.reached170,s.reached200,s.exhausted,s.stopped,s.stopHand,s.stopBank,s.termination));
        return z.toString();
    }

    static void total139(StringBuilder r,String policy,ArrayList<ArrayList<S131>> all,int policyIndex){
        int n=0,pos=0,r150=0,r170=0,r200=0,bust=0,stops=0,hands=0;
        double finalSum=0,wager=0,dd=0;
        for(int c=0;c<4;c++)for(S131 s:all.get(c*3+policyIndex)){
            n++;finalSum+=s.finalBank;wager+=s.cumWager;dd+=s.maxDD;hands+=s.hands;
            if(s.finalBank>100+1e-9)pos++;if(s.reached150)r150++;if(s.reached170)r170++;if(s.reached200)r200++;if(s.exhausted)bust++;if(s.stopped)stops++;
        }
        double net=finalSum-100.0*n;
        r.append(String.format(Locale.US,
          "%s | n %d | avg final £%.3f | return %.4f%% | positive %.2f%% | £150 %.2f%% | £170 %.2f%% | £200 %.2f%% | £0 %.2f%% | maxDD £%.3f | exposure £%.3f | mean wager/hand £%.3f | stops %d (%.2f%%)%n",
          policy,n,finalSum/n,100.0*net/wager,p131(pos,n),p131(r150,n),p131(r170,n),p131(r200,n),p131(bust,n),dd/n,wager/n,hands>0?wager/hands:0,stops,p131(stops,n)));
    }


    /* PHASE 3.140 — POLICY-INDUCED STATE DRIFT AUDIT
       Diagnostic only. All thresholds remain frozen:
         exposure <= £225
         bankroll >= £140
         trailing give-back = 25%

       This phase records what happens when the gated policy encounters a 25%
       trailing trigger and either ACCEPTS it or DECLINES it.
    */
    static final long[] P140_SEEDS={207101L,207201L,207301L,207401L};
    static final String[] P140_COHORTS={"FRESH_Q","FRESH_R","FRESH_S","FRESH_T"};

    static class D140{
        String cohort;int session;
        int firstTriggerHand,acceptedHand,laterTriggerCount;
        double firstTriggerBank,firstTriggerExposure,firstTriggerPeak,firstTriggerDD;
        boolean accepted,declined,laterQualified;
        double finalGate,finalRef,finalAlways,extraExposureAfterFirst;
        int extraHandsAfterFirst;
    }

    static D140 auditOne140(long seed,String cohort,int session){
        S131 ref=runOne139(seed,cohort,session,P131_REF);
        S131 always=runOne139(seed,cohort,session,P131_TRAIL150);

        int[]shoe=sixDeckShoe(sessionSeed(seed,session));if(!auditShoe(shoe))throw new IllegalStateException("Inventory audit failed");
        int ptr=0,hc=0;int[]hist=new int[5];double bank=START_BANK,peak=START_BANK,cum=0;
        boolean everPlunge=false,cooling=false,cautious=false,negativeEpisode=false,brakeArmed=false,activated150=false;
        D140 d=new D140();d.cohort=cohort;d.session=session+1;d.finalRef=ref.finalBank;d.finalAlways=always.finalBank;
        int first=-1;double expAtFirst=0;
        for(int hand=1;hand<=HANDS;hand++){
            if(bank>=200||bank<=0)break;
            if(cooling&&bank>=120){cooling=false;cautious=true;}if(cautious&&bank>=140)cautious=false;
            double requested;
            if(cooling){requested=10;negativeEpisode=false;brakeArmed=false;}
            else if(cautious){requested=cautious355(hist,hc);negativeEpisode=false;brakeArmed=false;}
            else if(hand>8&&bank>=140){int b=bal355(hist,hc);boolean neg=b<0;if(neg&&!negativeEpisode)brakeArmed=true;negativeEpisode=neg;double bs=bank>=170?15:normal355(hand,bank);requested=brakeArmed?Math.min(bs,15):bs;if(brakeArmed)brakeArmed=false;}
            else{requested=normal355(hand,bank);negativeEpisode=false;brakeArmed=false;}
            requested=Math.min(requested,bank);double chip=chip5FloorTie103(requested,bank);if(bank>=15-1e-9&&chip<15-1e-9)chip=15;
            double wager=Math.min(chip,bank);if(wager<=0)break;
            if(!cooling&&!cautious&&hand>8&&bank>=130)everPlunge=true;
            PairStats ps=new PairStats();Round rr=playRound(shoe,ptr,Profile.SELECTIVE_AGGRESSION,cohort,session+1,hand,true,bank,ps,wager,false);
            ptr=rr.endPointer;cum+=roundStake356(rr);bank+=rr.net;if(bank<0)bank=0;peak=Math.max(peak,bank);
            hist[hc%hist.length]=rr.net>1e-9?1:(rr.net<-1e-9?-1:0);hc++;
            if(bank>=150-1e-9)activated150=true;
            if(bank>=200-1e-9||bank<=1e-9)break;

            boolean trigger=false;
            if(activated150&&peak>100+1e-9){
                double profit=peak-100.0,surr=Math.max(0,peak-bank);
                trigger=100.0*surr/profit>=25.0-1e-9;
            }
            if(trigger){
                boolean qualifies=cum<=P139_EXPOSURE_GATE+1e-9&&bank>=P139_BANKROLL_GATE-1e-9;
                if(first<0){
                    first=hand;expAtFirst=cum;d.firstTriggerHand=hand;d.firstTriggerBank=bank;d.firstTriggerExposure=cum;
                    d.firstTriggerPeak=peak;d.firstTriggerDD=Math.max(0,peak-bank);
                    if(qualifies){d.accepted=true;d.acceptedHand=hand;break;}
                    else d.declined=true;
                }else{
                    d.laterTriggerCount++;
                    if(qualifies){d.laterQualified=true;d.accepted=true;d.acceptedHand=hand;break;}
                }
            }
            if(everPlunge&&bank<=100&&!cooling){cooling=true;cautious=false;negativeEpisode=false;brakeArmed=false;}
        }
        d.finalGate=bank;
        if(first>0){d.extraHandsAfterFirst=Math.max(0,(d.accepted?d.acceptedHand:Math.min(HANDS,hc))-first);d.extraExposureAfterFirst=Math.max(0,cum-expAtFirst);}
        return d;
    }


    /* PHASE 3.141 — POST-DECLINE PATHWAY STRATIFICATION
       Diagnostic only. No new action rule and no threshold tuning.

       Starting population:
       Phase 3.140 sessions in which the first frozen 25% trailing trigger was
       DECLINED by the two-variable gate.

       The post-decline path is instrumented into mutually exclusive outcome classes:
       TARGET_PROGRESS      -> reaches £200 after the decline
       CAPITAL_PRESERVED    -> does not reach £200, finishes >=£100
       MID_LOSS             -> finishes £70-£99.99
       SEVERE_LOSS          -> finishes £0.01-£69.99
       TERMINAL_ZERO        -> finishes £0

       Additional descriptive dimensions:
       later trigger count, re-qualification, extra hands, extra exposure,
       post-decline peak, post-decline trough, time below £100 and first
       crossings of £100/£70/£50/£0.
    */

    static final class P141 {
        String cohort,pathClass;
        int session,firstTriggerHand,laterTriggerCount,extraHands,handsBelow100;
        int first100=-1,first70=-1,first50=-1,first0=-1;
        boolean laterQualified,reached200;
        double firstBank,firstExposure,extraExposure,postPeak,postTrough,finalBank,finalRef,finalAlways;
    }

    static P141 runOne141(long seed,String cohort,int session){
        S131 ref=runOne139(seed,cohort,session,P131_REF);
        S131 always=runOne139(seed,cohort,session,P131_TRAIL150);

        int[]shoe=sixDeckShoe(sessionSeed(seed,session));
        if(!auditShoe(shoe))throw new IllegalStateException("Inventory audit failed");
        int ptr=0,hc=0;int[]hist=new int[5];
        double bank=START_BANK,peak=START_BANK,cum=0;
        boolean everPlunge=false,cooling=false,cautious=false,negativeEpisode=false,brakeArmed=false,activated150=false;
        int first=-1;double expAtFirst=0,postPeak=-1,postTrough=Double.POSITIVE_INFINITY;
        P141 p=new P141();p.cohort=cohort;p.session=session+1;p.finalRef=ref.finalBank;p.finalAlways=always.finalBank;

        for(int hand=1;hand<=HANDS;hand++){
            if(bank>=200||bank<=0)break;
            if(cooling&&bank>=120){cooling=false;cautious=true;}if(cautious&&bank>=140)cautious=false;

            double requested;
            if(cooling){requested=10;negativeEpisode=false;brakeArmed=false;}
            else if(cautious){requested=cautious355(hist,hc);negativeEpisode=false;brakeArmed=false;}
            else if(hand>8&&bank>=140){
                int b=bal355(hist,hc);boolean neg=b<0;if(neg&&!negativeEpisode)brakeArmed=true;negativeEpisode=neg;
                double bs=bank>=170?15:normal355(hand,bank);requested=brakeArmed?Math.min(bs,15):bs;if(brakeArmed)brakeArmed=false;
            }else{requested=normal355(hand,bank);negativeEpisode=false;brakeArmed=false;}

            requested=Math.min(requested,bank);double chip=chip5FloorTie103(requested,bank);
            if(bank>=15-1e-9&&chip<15-1e-9)chip=15;double wager=Math.min(chip,bank);if(wager<=0)break;
            if(!cooling&&!cautious&&hand>8&&bank>=130)everPlunge=true;

            PairStats ps=new PairStats();
            Round rr=playRound(shoe,ptr,Profile.SELECTIVE_AGGRESSION,cohort,session+1,hand,true,bank,ps,wager,false);
            ptr=rr.endPointer;cum+=roundStake356(rr);bank+=rr.net;if(bank<0)bank=0;peak=Math.max(peak,bank);
            hist[hc%hist.length]=rr.net>1e-9?1:(rr.net<-1e-9?-1:0);hc++;

            if(bank>=150-1e-9)activated150=true;

            boolean trigger=false;
            if(activated150&&peak>100+1e-9){
                double profit=peak-100.0,surr=Math.max(0,peak-bank);
                trigger=100.0*surr/profit>=25.0-1e-9;
            }

            if(trigger){
                boolean qualifies=cum<=P139_EXPOSURE_GATE+1e-9&&bank>=P139_BANKROLL_GATE-1e-9;
                if(first<0){
                    if(qualifies){
                        // Not part of the declined-first population.
                        p.pathClass="NOT_DECLINED_FIRST";
                        p.finalBank=bank;return p;
                    }
                    first=hand;expAtFirst=cum;p.firstTriggerHand=hand;p.firstBank=bank;p.firstExposure=cum;
                    postPeak=bank;postTrough=bank;
                }else{
                    p.laterTriggerCount++;
                    if(qualifies){
                        p.laterQualified=true;
                        // Under the gated policy, this later qualification stops the session.
                        break;
                    }
                }
            }

            if(first>0){
                postPeak=Math.max(postPeak,bank);postTrough=Math.min(postTrough,bank);
                if(bank<100-1e-9){p.handsBelow100++;if(p.first100<0)p.first100=hand;}
                if(bank<70-1e-9&&p.first70<0)p.first70=hand;
                if(bank<50-1e-9&&p.first50<0)p.first50=hand;
                if(bank<=1e-9&&p.first0<0)p.first0=hand;
                if(bank>=200-1e-9){p.reached200=true;break;}
            }

            if(bank>=200-1e-9||bank<=1e-9)break;
            if(everPlunge&&bank<=100&&!cooling){cooling=true;cautious=false;negativeEpisode=false;brakeArmed=false;}
        }

        if(first<0){p.pathClass="NO_TRIGGER";p.finalBank=bank;return p;}

        p.extraHands=Math.max(0,hc-first);
        p.extraExposure=Math.max(0,cum-expAtFirst);
        p.postPeak=postPeak<0?bank:postPeak;
        p.postTrough=Double.isInfinite(postTrough)?bank:postTrough;
        p.finalBank=bank;

        if(p.reached200||bank>=200-1e-9)p.pathClass="TARGET_PROGRESS";
        else if(bank>=100-1e-9)p.pathClass="CAPITAL_PRESERVED";
        else if(bank>=70-1e-9)p.pathClass="MID_LOSS";
        else if(bank>1e-9)p.pathClass="SEVERE_LOSS";
        else p.pathClass="TERMINAL_ZERO";
        return p;
    }

    static void panel141(StringBuilder r,String cls,ArrayList<P141> xs){
        int n=0,lq=0,tr=0,hb=0;double eb=0,ee=0,pk=0,trough=0,fin=0,ref=0,alw=0;
        for(P141 p:xs)if(cls.equals(p.pathClass)){
            n++;if(p.laterQualified)lq++;tr+=p.laterTriggerCount;hb+=p.handsBelow100;
            eb+=p.extraHands;ee+=p.extraExposure;pk+=p.postPeak;trough+=p.postTrough;fin+=p.finalBank;ref+=p.finalRef;alw+=p.finalAlways;
        }
        r.append("\n").append(cls).append("\n");
        r.append("n = ").append(n).append("\n");if(n==0)return;
        r.append(String.format(Locale.US,"Share of declined-first population: %.2f%%%n",100.0*n/countDeclined141(xs)));
        r.append(String.format(Locale.US,"Mean extra hands / exposure: %.3f / £%.3f%n",eb/n,ee/n));
        r.append(String.format(Locale.US,"Mean post-decline peak / trough: £%.3f / £%.3f%n",pk/n,trough/n));
        r.append(String.format(Locale.US,"Mean hands below £100: %.3f%n",1.0*hb/n));
        r.append(String.format(Locale.US,"Mean later trigger events: %.3f | later qualifying %.2f%%%n",1.0*tr/n,p131(lq,n)));
        r.append(String.format(Locale.US,"Final mean — gated / reference / always-trail: £%.3f / £%.3f / £%.3f%n",fin/n,ref/n,alw/n));
    }

    static int countDeclined141(ArrayList<P141> xs){int n=0;for(P141 p:xs)if(!p.pathClass.equals("NO_TRIGGER")&&!p.pathClass.equals("NOT_DECLINED_FIRST"))n++;return n;}


    /* PHASE 3.143 — INSTRUMENTED +3/+5 POST-DECLINE CHECKPOINT REPLAY
       AND SIGNAL-SEPARATION DATA GENERATION

       This phase executes the checkpoint collection designed in 3.142.
       No policy or threshold is changed.

       Population:
       sessions whose first frozen 25% trailing trigger is DECLINED by the
       exposure<=£225 AND bankroll>=£140 gate.

       Predeclared checkpoint offsets:
       +3 and +5 completed hands after first decline.

       Observable checkpoint fields:
       current bankroll, incremental exposure, hands below £100,
       repeated trigger count, regain of first-trigger bankroll,
       post-decline peak and trough.

       Final path labels are retrospective and are written only after the
       session completes:
       GOOD = TARGET_PROGRESS or CAPITAL_PRESERVED
       LOSS = MID_LOSS, SEVERE_LOSS or TERMINAL_ZERO
    */

    static final class C143 {
        String cohort,finalClass,label;
        int session,offset,handsBelow100,laterTriggers;
        double bank,extraExposure,peak,trough,firstBank,firstExposure;
        boolean regained;
    }

    static ArrayList<C143> collect143(long seed,String cohort,int session){
        ArrayList<C143> out=new ArrayList<>();

        int[]shoe=sixDeckShoe(sessionSeed(seed,session));
        if(!auditShoe(shoe))throw new IllegalStateException("Inventory audit failed");
        int ptr=0,hc=0;int[]hist=new int[5];
        double bank=START_BANK,peak=START_BANK,cum=0;
        boolean everPlunge=false,cooling=false,cautious=false,negativeEpisode=false,brakeArmed=false,activated150=false;

        int first=-1,hb=0,later=0;
        double firstBank=0,firstExp=0,postPeak=0,postTrough=Double.POSITIVE_INFINITY;
        C143 c3=null,c5=null;
        boolean reached200=false,declined=false;

        for(int hand=1;hand<=HANDS;hand++){
            if(bank>=200||bank<=0)break;

            if(cooling&&bank>=120){cooling=false;cautious=true;}
            if(cautious&&bank>=140)cautious=false;

            double requested;
            if(cooling){
                requested=10;negativeEpisode=false;brakeArmed=false;
            }else if(cautious){
                requested=cautious355(hist,hc);negativeEpisode=false;brakeArmed=false;
            }else if(hand>8&&bank>=140){
                int b=bal355(hist,hc);boolean neg=b<0;
                if(neg&&!negativeEpisode)brakeArmed=true;
                negativeEpisode=neg;
                double bs=bank>=170?15:normal355(hand,bank);
                requested=brakeArmed?Math.min(bs,15):bs;
                if(brakeArmed)brakeArmed=false;
            }else{
                requested=normal355(hand,bank);negativeEpisode=false;brakeArmed=false;
            }

            requested=Math.min(requested,bank);
            double chip=chip5FloorTie103(requested,bank);
            if(bank>=15-1e-9&&chip<15-1e-9)chip=15;
            double wager=Math.min(chip,bank);
            if(wager<=0)break;

            if(!cooling&&!cautious&&hand>8&&bank>=130)everPlunge=true;

            PairStats ps=new PairStats();
            Round rr=playRound(shoe,ptr,Profile.SELECTIVE_AGGRESSION,cohort,session+1,hand,true,bank,ps,wager,false);
            ptr=rr.endPointer;
            cum+=roundStake356(rr);
            bank+=rr.net;
            if(bank<0)bank=0;
            peak=Math.max(peak,bank);

            hist[hc%hist.length]=rr.net>1e-9?1:(rr.net<-1e-9?-1:0);
            hc++;

            if(bank>=150-1e-9)activated150=true;

            boolean trigger=false;
            if(activated150&&peak>100+1e-9){
                double profit=peak-100.0;
                double surr=Math.max(0,peak-bank);
                trigger=100.0*surr/profit>=25.0-1e-9;
            }

            if(trigger){
                boolean qualifies=cum<=P139_EXPOSURE_GATE+1e-9&&bank>=P139_BANKROLL_GATE-1e-9;
                if(first<0){
                    if(qualifies){
                        // accepted-first session: outside the 3.143 population
                        return out;
                    }
                    first=hand;
                    declined=true;
                    firstBank=bank;
                    firstExp=cum;
                    postPeak=bank;
                    postTrough=bank;
                }else{
                    later++;
                    if(qualifies){
                        // gated policy would stop here
                        break;
                    }
                }
            }

            if(first>0){
                int off=hand-first;
                postPeak=Math.max(postPeak,bank);
                postTrough=Math.min(postTrough,bank);
                if(bank<100-1e-9)hb++;

                if(off==3 || off==5){
                    C143 c=new C143();
                    c.cohort=cohort;
                    c.session=session+1;
                    c.offset=off;
                    c.bank=bank;
                    c.extraExposure=cum-firstExp;
                    c.handsBelow100=hb;
                    c.laterTriggers=later;
                    c.regained=bank>=firstBank-1e-9;
                    c.peak=postPeak;
                    c.trough=postTrough;
                    c.firstBank=firstBank;
                    c.firstExposure=firstExp;
                    if(off==3)c3=c; else c5=c;
                }

                if(bank>=200-1e-9){
                    reached200=true;
                    break;
                }
            }

            if(bank>=200-1e-9||bank<=1e-9)break;

            if(everPlunge&&bank<=100&&!cooling){
                cooling=true;cautious=false;negativeEpisode=false;brakeArmed=false;
            }
        }

        if(!declined)return out;

        String fc;
        if(reached200||bank>=200-1e-9)fc="TARGET_PROGRESS";
        else if(bank>=100-1e-9)fc="CAPITAL_PRESERVED";
        else if(bank>=70-1e-9)fc="MID_LOSS";
        else if(bank>1e-9)fc="SEVERE_LOSS";
        else fc="TERMINAL_ZERO";

        String label=(fc.equals("TARGET_PROGRESS")||fc.equals("CAPITAL_PRESERVED"))?"GOOD":"LOSS";

        if(c3!=null){c3.finalClass=fc;c3.label=label;out.add(c3);}
        if(c5!=null){c5.finalClass=fc;c5.label=label;out.add(c5);}
        return out;
    }

    static void panel143(StringBuilder r,int off,String lab,ArrayList<C143> all){
        int n=0,hb=0,tr=0,rg=0;
        double b=0,e=0,p=0,t=0,fb=0;
        for(C143 c:all)if(c.offset==off&&c.label.equals(lab)){
            n++;b+=c.bank;e+=c.extraExposure;hb+=c.handsBelow100;tr+=c.laterTriggers;
            if(c.regained)rg++;p+=c.peak;t+=c.trough;fb+=c.firstBank;
        }
        r.append(String.format(Locale.US,"%n+%d HANDS / %s%n",off,lab));
        r.append("n = ").append(n).append("\n");
        if(n==0)return;
        r.append(String.format(Locale.US,
            "Mean bankroll: £%.3f%nMean incremental exposure: £%.3f%nMean hands below £100: %.3f%nMean later triggers: %.3f%nRegained first-trigger bankroll: %.2f%%%nMean post-decline peak / trough: £%.3f / £%.3f%nMean first-trigger bankroll: £%.3f%n",
            b/n,e/n,1.0*hb/n,1.0*tr/n,p131(rg,n),p/n,t/n,fb/n));
    }


    /* PHASE 3.144 — FRESH CHECKPOINT RELATIONSHIP VALIDATION
       Fresh cohorts only. No threshold optimisation and no action-policy change.

       Frozen hypotheses from Phase 3.143:
       H1: GOOD sessions have higher current bankroll than LOSS at +3 and +5.
       H2: GOOD sessions regain first-trigger bankroll more often at +3 and +5.
       H3: GOOD sessions have a higher post-decline trough at +3 and +5.

       The +3/+5 checkpoint definitions and GOOD/LOSS retrospective labels are
       unchanged from Phase 3.143.
    */
    static final long[] P144_SEEDS={208101L,208201L,208301L,208401L};
    static final String[] P144_COHORTS={"FRESH_U","FRESH_V","FRESH_W","FRESH_X"};

    static final class V144{
        int off,ng,nl,rgg,rgl;
        double bg,bl,tg,tl,eg,el,hg,hl,pg,pl,trg,trl;
    }

    static V144 measure144(int off,ArrayList<C143> all){
        V144 v=new V144();v.off=off;
        for(C143 c:all)if(c.offset==off){
            if(c.label.equals("GOOD")){
                v.ng++;v.bg+=c.bank;v.tg+=c.trough;v.eg+=c.extraExposure;
                v.hg+=c.handsBelow100;v.pg+=c.peak;v.trg+=c.laterTriggers;
                if(c.regained)v.rgg++;
            }else{
                v.nl++;v.bl+=c.bank;v.tl+=c.trough;v.el+=c.extraExposure;
                v.hl+=c.handsBelow100;v.pl+=c.peak;v.trl+=c.laterTriggers;
                if(c.regained)v.rgl++;
            }
        }
        return v;
    }

    static void report144(StringBuilder r,V144 v){
        double bg=v.ng>0?v.bg/v.ng:0, bl=v.nl>0?v.bl/v.nl:0;
        double tg=v.ng>0?v.tg/v.ng:0, tl=v.nl>0?v.tl/v.nl:0;
        double rg=v.ng>0?100.0*v.rgg/v.ng:0, rl=v.nl>0?100.0*v.rgl/v.nl:0;
        r.append(String.format(Locale.US,"%n+%d HAND VALIDATION%n",v.off));
        r.append(String.format(Locale.US,"GOOD / LOSS n: %d / %d%n",v.ng,v.nl));
        r.append(String.format(Locale.US,"Current bankroll GOOD / LOSS: £%.3f / £%.3f | difference £%+.3f | H1 %s%n",
            bg,bl,bg-bl,bg>bl?"PASS":"FAIL"));
        r.append(String.format(Locale.US,"Regained first-trigger bankroll GOOD / LOSS: %.2f%% / %.2f%% | difference %+.2f pp | H2 %s%n",
            rg,rl,rg-rl,rg>rl?"PASS":"FAIL"));
        r.append(String.format(Locale.US,"Post-decline trough GOOD / LOSS: £%.3f / £%.3f | difference £%+.3f | H3 %s%n",
            tg,tl,tg-tl,tg>tl?"PASS":"FAIL"));
        r.append(String.format(Locale.US,"Incremental exposure GOOD / LOSS: £%.3f / £%.3f%n",
            v.ng>0?v.eg/v.ng:0,v.nl>0?v.el/v.nl:0));
        r.append(String.format(Locale.US,"Hands below £100 GOOD / LOSS: %.3f / %.3f%n",
            v.ng>0?v.hg/v.ng:0,v.nl>0?v.hl/v.nl:0));
        r.append(String.format(Locale.US,"Post-decline peak GOOD / LOSS: £%.3f / £%.3f%n",
            v.ng>0?v.pg/v.ng:0,v.nl>0?v.pl/v.nl:0));
        r.append(String.format(Locale.US,"Later triggers GOOD / LOSS: %.3f / %.3f%n",
            v.ng>0?v.trg/v.ng:0,v.nl>0?v.trl/v.nl:0));
    }


    /* PHASE 3.147 — FRESH THREE-STATE ARCHITECTURE VALIDATION

       Frozen from Phase 3.146:
       OPPORTUNITY = current bankroll >= £140 AND first-trigger bankroll regained.
       CAPITAL_RISK = current bankroll < £100 AND first-trigger bankroll NOT regained.
       MIDDLE/UNRESOLVED = all other checkpoint states.

       Predeclared expectations:
       A. OPPORTUNITY remains >=75% GOOD and target progression materially exceeds
          severe+terminal deterioration.
       B. CAPITAL_RISK remains <35% GOOD and severe+terminal deterioration materially
          exceeds target progression.
       C. MIDDLE remains substantially more mixed than either extreme.

       Fresh cohorts only. No stop/continue action is executed.
    */
    static final long[] P147_SEEDS={209101L,209201L,209301L,209401L};
    static final String[] P147_COHORTS={"FRESH_Y","FRESH_Z","FRESH_AA","FRESH_AB"};

    static String state147(C143 c){
        if(c.bank>=140-1e-9 && c.regained)return "OPPORTUNITY_HIGH_REGAINED";
        if(c.bank<100-1e-9 && !c.regained)return "CAPITAL_RISK_LOW_NOT_REGAINED";
        return "MIDDLE_UNRESOLVED";
    }

    static void panel147(StringBuilder r,int off,String state,ArrayList<C143> all){
        int n=0,good=0,target=0,cap=0,mid=0,severe=0,zero=0;
        double bank=0,exp=0,hb=0,trough=0;
        for(C143 c:all)if(c.offset==off && state147(c).equals(state)){
            n++;bank+=c.bank;exp+=c.extraExposure;hb+=c.handsBelow100;trough+=c.trough;
            if(c.label.equals("GOOD"))good++;
            if(c.finalClass.equals("TARGET_PROGRESS"))target++;
            else if(c.finalClass.equals("CAPITAL_PRESERVED"))cap++;
            else if(c.finalClass.equals("MID_LOSS"))mid++;
            else if(c.finalClass.equals("SEVERE_LOSS"))severe++;
            else if(c.finalClass.equals("TERMINAL_ZERO"))zero++;
        }
        r.append(String.format(Locale.US,"%n+%d / %s%n",off,state));
        r.append("n = ").append(n).append("\n"); if(n==0)return;
        r.append(String.format(Locale.US,"GOOD: %.2f%%%n",p131(good,n)));
        r.append(String.format(Locale.US,"TARGET_PROGRESS: %.2f%%%n",p131(target,n)));
        r.append(String.format(Locale.US,"CAPITAL_PRESERVED: %.2f%%%n",p131(cap,n)));
        r.append(String.format(Locale.US,"MID_LOSS: %.2f%%%n",p131(mid,n)));
        r.append(String.format(Locale.US,"SEVERE_LOSS: %.2f%%%n",p131(severe,n)));
        r.append(String.format(Locale.US,"TERMINAL_ZERO: %.2f%%%n",p131(zero,n)));
        r.append(String.format(Locale.US,"SEVERE+TERMINAL: %.2f%%%n",p131(severe+zero,n)));
        r.append(String.format(Locale.US,"Mean bankroll / extra exposure / hands below £100 / trough: £%.3f / £%.3f / %.3f / £%.3f%n",
            bank/n,exp/n,hb/n,trough/n));
    }

    static int[] metrics147(int off,String state,ArrayList<C143> all){
        int n=0,g=0,t=0,s=0,z=0;
        for(C143 c:all)if(c.offset==off&&state147(c).equals(state)){
            n++;if(c.label.equals("GOOD"))g++;if(c.finalClass.equals("TARGET_PROGRESS"))t++;
            if(c.finalClass.equals("SEVERE_LOSS"))s++;if(c.finalClass.equals("TERMINAL_ZERO"))z++;
        }
        return new int[]{n,g,t,s,z};
    }


    /* PHASE 3.148 — STATE-ENTRY COUNTERFACTUAL VALUE MAP

       Purpose:
       quantify the theoretical value/cost of freezing bankroll at a validated
       checkpoint state versus allowing the existing post-decline policy path to
       continue.

       Frozen states from Phase 3.147:
       OPPORTUNITY = bankroll >= £140 AND regained first-trigger bankroll.
       CAPITAL_RISK = bankroll < £100 AND NOT regained.
       MIDDLE = all other checkpoint states.

       No live intervention is executed. "FREEZE NOW" is a counterfactual accounting
       benchmark only.

       For each +3 and +5 checkpoint:
       - freeze bankroll = bankroll observable at checkpoint;
       - continue outcome = final bankroll reached by the unchanged existing path;
       - delta = continue outcome - freeze bankroll;
       - target progression / severe deterioration / terminal zero retained.
    */

    static final class V148 {
        String cohort,state,finalClass;
        int session,offset;
        double checkpointBank,finalBank,delta,extraExposureAfterCheckpoint;
        int extraHandsAfterCheckpoint;
        boolean reached200,terminalZero;
    }

    static ArrayList<V148> collect148(long seed,String cohort,int session){
        ArrayList<V148> out=new ArrayList<>();

        int[]shoe=sixDeckShoe(sessionSeed(seed,session));
        if(!auditShoe(shoe))throw new IllegalStateException("Inventory audit failed");

        int ptr=0,hc=0;int[]hist=new int[5];
        double bank=START_BANK,peak=START_BANK,cum=0;
        boolean everPlunge=false,cooling=false,cautious=false,negativeEpisode=false,brakeArmed=false,activated150=false;

        int first=-1;
        double firstBank=0;
        V148 v3=null,v5=null;
        double expAt3=0,expAt5=0;
        int hand3=-1,hand5=-1;
        boolean reached200=false;

        for(int hand=1;hand<=HANDS;hand++){
            if(bank>=200||bank<=0)break;

            if(cooling&&bank>=120){cooling=false;cautious=true;}
            if(cautious&&bank>=140)cautious=false;

            double requested;
            if(cooling){
                requested=10;negativeEpisode=false;brakeArmed=false;
            }else if(cautious){
                requested=cautious355(hist,hc);negativeEpisode=false;brakeArmed=false;
            }else if(hand>8&&bank>=140){
                int b=bal355(hist,hc);boolean neg=b<0;
                if(neg&&!negativeEpisode)brakeArmed=true;
                negativeEpisode=neg;
                double bs=bank>=170?15:normal355(hand,bank);
                requested=brakeArmed?Math.min(bs,15):bs;
                if(brakeArmed)brakeArmed=false;
            }else{
                requested=normal355(hand,bank);negativeEpisode=false;brakeArmed=false;
            }

            requested=Math.min(requested,bank);
            double chip=chip5FloorTie103(requested,bank);
            if(bank>=15-1e-9&&chip<15-1e-9)chip=15;
            double wager=Math.min(chip,bank);
            if(wager<=0)break;

            if(!cooling&&!cautious&&hand>8&&bank>=130)everPlunge=true;

            PairStats ps=new PairStats();
            Round rr=playRound(shoe,ptr,Profile.SELECTIVE_AGGRESSION,cohort,session+1,hand,true,bank,ps,wager,false);
            ptr=rr.endPointer;
            cum+=roundStake356(rr);
            bank+=rr.net;
            if(bank<0)bank=0;
            peak=Math.max(peak,bank);

            hist[hc%hist.length]=rr.net>1e-9?1:(rr.net<-1e-9?-1:0);
            hc++;

            if(bank>=150-1e-9)activated150=true;

            boolean trigger=false;
            if(activated150&&peak>100+1e-9){
                double profit=peak-100.0;
                double surr=Math.max(0,peak-bank);
                trigger=100.0*surr/profit>=25.0-1e-9;
            }

            if(trigger){
                boolean qualifies=cum<=P139_EXPOSURE_GATE+1e-9&&bank>=P139_BANKROLL_GATE-1e-9;
                if(first<0){
                    if(qualifies)return out; // accepted-first outside population
                    first=hand;
                    firstBank=bank;
                }else if(qualifies){
                    // Existing gated policy would stop here.
                    break;
                }
            }

            if(first>0){
                int off=hand-first;
                if(off==3 || off==5){
                    boolean regained=bank>=firstBank-1e-9;
                    String state;
                    if(bank>=140-1e-9 && regained)state="OPPORTUNITY_HIGH_REGAINED";
                    else if(bank<100-1e-9 && !regained)state="CAPITAL_RISK_LOW_NOT_REGAINED";
                    else state="MIDDLE_UNRESOLVED";

                    V148 v=new V148();
                    v.cohort=cohort;v.session=session+1;v.offset=off;v.state=state;
                    v.checkpointBank=bank;
                    if(off==3){v3=v;expAt3=cum;hand3=hand;}
                    else {v5=v;expAt5=cum;hand5=hand;}
                }
                if(bank>=200-1e-9){reached200=true;break;}
            }

            if(bank>=200-1e-9||bank<=1e-9)break;

            if(everPlunge&&bank<=100&&!cooling){
                cooling=true;cautious=false;negativeEpisode=false;brakeArmed=false;
            }
        }

        String fc;
        if(reached200||bank>=200-1e-9)fc="TARGET_PROGRESS";
        else if(bank>=100-1e-9)fc="CAPITAL_PRESERVED";
        else if(bank>=70-1e-9)fc="MID_LOSS";
        else if(bank>1e-9)fc="SEVERE_LOSS";
        else fc="TERMINAL_ZERO";

        if(v3!=null){
            v3.finalClass=fc;v3.finalBank=bank;v3.delta=bank-v3.checkpointBank;
            v3.extraExposureAfterCheckpoint=Math.max(0,cum-expAt3);
            v3.extraHandsAfterCheckpoint=Math.max(0,hc-hand3);
            v3.reached200=fc.equals("TARGET_PROGRESS");v3.terminalZero=fc.equals("TERMINAL_ZERO");
            out.add(v3);
        }
        if(v5!=null){
            v5.finalClass=fc;v5.finalBank=bank;v5.delta=bank-v5.checkpointBank;
            v5.extraExposureAfterCheckpoint=Math.max(0,cum-expAt5);
            v5.extraHandsAfterCheckpoint=Math.max(0,hc-hand5);
            v5.reached200=fc.equals("TARGET_PROGRESS");v5.terminalZero=fc.equals("TERMINAL_ZERO");
            out.add(v5);
        }
        return out;
    }

    static void panel148(StringBuilder r,int off,String state,ArrayList<V148> all){
        int n=0,better=0,worse=0,equal=0,target=0,zero=0;
        double cb=0,fb=0,delta=0,exp=0,hands=0,gain=0,loss=0;
        for(V148 v:all)if(v.offset==off&&v.state.equals(state)){
            n++;cb+=v.checkpointBank;fb+=v.finalBank;delta+=v.delta;exp+=v.extraExposureAfterCheckpoint;hands+=v.extraHandsAfterCheckpoint;
            if(v.delta>1e-9){better++;gain+=v.delta;}else if(v.delta<-1e-9){worse++;loss+=v.delta;}else equal++;
            if(v.reached200)target++;if(v.terminalZero)zero++;
        }
        r.append(String.format(Locale.US,"%n+%d / %s%n",off,state));
        r.append("n = ").append(n).append("\n");if(n==0)return;
        r.append(String.format(Locale.US,"Checkpoint freeze bankroll mean: £%.3f%n",cb/n));
        r.append(String.format(Locale.US,"Continued-path final bankroll mean: £%.3f%n",fb/n));
        r.append(String.format(Locale.US,"Mean continue-minus-freeze delta: £%+.3f%n",delta/n));
        r.append(String.format(Locale.US,"Continue higher / freeze higher / equal: %.2f%% / %.2f%% / %.2f%%%n",
            p131(better,n),p131(worse,n),p131(equal,n)));
        r.append(String.format(Locale.US,"Aggregate positive continuation contribution: £%+.2f%n",gain));
        r.append(String.format(Locale.US,"Aggregate negative continuation contribution: £%+.2f%n",loss));
        r.append(String.format(Locale.US,"Reached £200 under continuation: %.2f%%%n",p131(target,n)));
        r.append(String.format(Locale.US,"Terminal £0 under continuation: %.2f%%%n",p131(zero,n)));
        r.append(String.format(Locale.US,"Mean extra hands / exposure after checkpoint: %.3f / £%.3f%n",hands/n,exp/n));
    }


    /* PHASE 3.151 — FRESH FROZEN INTERVENTION EXECUTION
       Contract frozen in Phase 3.150.

       Fresh cohorts AC/AD/AE/AF only.
       Primary action checkpoint +5; secondary replication +3.

       Arms:
       CONTINUE_REFERENCE
       ALWAYS_TRAIL_150_GIVEBACK_25
       CAPITAL_PRESERVATION_STATE_POLICY
       BALANCED_THREE_STATE_POLICY

       No wager-policy or card-play-policy changes.
    */
    static final long[] P151_SEEDS={210101L,210201L,210301L,210401L};
    static final String[] P151_COHORTS={"FRESH_AC","FRESH_AD","FRESH_AE","FRESH_AF"};

    enum Arm151 {CONTINUE_REFERENCE,ALWAYS_TRAIL_150_GIVEBACK_25,CAPITAL_PRESERVATION_STATE_POLICY,BALANCED_THREE_STATE_POLICY,REFINED_60,REFINED_70}

    static final class R151{
        String cohort,arm,stopReason;
        int session,checkpoint,hand;
        double finalBank,exposure,maxDD,checkpointBank=Double.NaN,firstTriggerBank=Double.NaN;
        boolean reached200,terminalZero,positive,checkpointObserved=false,checkpointRegained=false;
    }

    static R151 run151(long seed,String cohort,int session,Arm151 arm,int checkpoint){
        int[]shoe=sixDeckShoe(sessionSeed(seed,session));
        if(!auditShoe(shoe))throw new IllegalStateException("Inventory audit failed");

        int ptr=0,hc=0;int[]hist=new int[5];
        double bank=START_BANK,peak=START_BANK,cum=0,maxPeak=START_BANK,maxDD=0;
        boolean everPlunge=false,cooling=false,cautious=false,negativeEpisode=false,brakeArmed=false,activated150=false;
        int first=-1;double firstBank=0;
        String stopReason="NATURAL_END";
        boolean reached200=false;

        for(int hand=1;hand<=HANDS;hand++){
            if(bank>=200||bank<=0)break;

            if(cooling&&bank>=120){cooling=false;cautious=true;}
            if(cautious&&bank>=140)cautious=false;

            double requested;
            if(cooling){requested=10;negativeEpisode=false;brakeArmed=false;}
            else if(cautious){requested=cautious355(hist,hc);negativeEpisode=false;brakeArmed=false;}
            else if(hand>8&&bank>=140){
                int b=bal355(hist,hc);boolean neg=b<0;
                if(neg&&!negativeEpisode)brakeArmed=true;
                negativeEpisode=neg;
                double bs=bank>=170?15:normal355(hand,bank);
                requested=brakeArmed?Math.min(bs,15):bs;
                if(brakeArmed)brakeArmed=false;
            }else{requested=normal355(hand,bank);negativeEpisode=false;brakeArmed=false;}

            requested=Math.min(requested,bank);
            double chip=chip5FloorTie103(requested,bank);
            if(bank>=15-1e-9&&chip<15-1e-9)chip=15;
            double wager=Math.min(chip,bank);
            if(wager<=0)break;
            if(!cooling&&!cautious&&hand>8&&bank>=130)everPlunge=true;

            PairStats ps=new PairStats();
            Round rr=playRound(shoe,ptr,Profile.SELECTIVE_AGGRESSION,cohort,session+1,hand,true,bank,ps,wager,false);
            ptr=rr.endPointer;cum+=roundStake356(rr);bank+=rr.net;if(bank<0)bank=0;
            peak=Math.max(peak,bank);maxPeak=Math.max(maxPeak,bank);maxDD=Math.max(maxDD,maxPeak-bank);
            hist[hc%hist.length]=rr.net>1e-9?1:(rr.net<-1e-9?-1:0);hc++;
            if(bank>=150-1e-9)activated150=true;

            boolean trigger=false;
            if(activated150&&peak>100+1e-9){
                double profit=peak-100.0,surr=Math.max(0,peak-bank);
                trigger=100.0*surr/profit>=25.0-1e-9;
            }

            if(trigger){
                if(arm==Arm151.ALWAYS_TRAIL_150_GIVEBACK_25){
                    stopReason="ALWAYS_TRAIL_TRIGGER";break;
                }

                boolean qualifies=cum<=P139_EXPOSURE_GATE+1e-9&&bank>=P139_BANKROLL_GATE-1e-9;
                if(first<0){
                    if(qualifies){
                        // Existing accepted first-trigger gate remains part of reference architecture.
                        stopReason="EXISTING_ACCEPTED_FIRST_TRIGGER";break;
                    }
                    first=hand;firstBank=bank;
                }else if(qualifies){
                    stopReason="EXISTING_LATER_GATE";break;
                }
            }

            if(first>0 && hand-first==checkpoint){
                // Phase 3.156 validation capture: observable checkpoint state only.
                // This is recorded for every arm and does not change execution.
                // The run result receives these values below through local holders.
            }

            if(first>0 && hand-first==checkpoint &&
               (arm==Arm151.CAPITAL_PRESERVATION_STATE_POLICY||arm==Arm151.BALANCED_THREE_STATE_POLICY)){
                boolean regained=bank>=firstBank-1e-9;
                boolean capitalRisk=bank<100-1e-9&&!regained;
                // Balanced also explicitly CONTINUES Opportunity and OBSERVES Middle;
                // only Capital Risk changes execution relative to reference.
                if(capitalRisk){
                    stopReason="FROZEN_CAPITAL_RISK_FREEZE";break;
                }
            }

            if(bank>=200-1e-9){reached200=true;break;}
            if(bank<=1e-9)break;
            if(everPlunge&&bank<=100&&!cooling){cooling=true;cautious=false;negativeEpisode=false;brakeArmed=false;}
        }

        if(bank>=200-1e-9)reached200=true;
        R151 r=new R151();r.cohort=cohort;r.arm=arm.name();r.session=session+1;r.checkpoint=checkpoint;
        r.hand=hc;r.finalBank=bank;r.exposure=cum;r.maxDD=maxDD;r.reached200=reached200;
        r.terminalZero=bank<=1e-9;r.positive=bank>100+1e-9;r.stopReason=stopReason;return r;
    }

    static R151 run156(long seed,String cohort,int session,Arm151 arm,int checkpoint){
        int[]shoe=sixDeckShoe(sessionSeed(seed,session));
        if(!auditShoe(shoe))throw new IllegalStateException("Inventory audit failed");

        int ptr=0,hc=0;int[]hist=new int[5];
        double bank=START_BANK,peak=START_BANK,cum=0,maxPeak=START_BANK,maxDD=0;
        boolean everPlunge=false,cooling=false,cautious=false,negativeEpisode=false,brakeArmed=false,activated150=false;
        int first=-1;double firstBank=0;
        double observedCheckpointBank=Double.NaN,observedFirstBank=Double.NaN;
        boolean observedCheckpoint=false,observedRegained=false;
        String stopReason="NATURAL_END";
        boolean reached200=false;

        for(int hand=1;hand<=HANDS;hand++){
            if(bank>=200||bank<=0)break;

            if(cooling&&bank>=120){cooling=false;cautious=true;}
            if(cautious&&bank>=140)cautious=false;

            double requested;
            if(cooling){requested=10;negativeEpisode=false;brakeArmed=false;}
            else if(cautious){requested=cautious355(hist,hc);negativeEpisode=false;brakeArmed=false;}
            else if(hand>8&&bank>=140){
                int b=bal355(hist,hc);boolean neg=b<0;
                if(neg&&!negativeEpisode)brakeArmed=true;
                negativeEpisode=neg;
                double bs=bank>=170?15:normal355(hand,bank);
                requested=brakeArmed?Math.min(bs,15):bs;
                if(brakeArmed)brakeArmed=false;
            }else{requested=normal355(hand,bank);negativeEpisode=false;brakeArmed=false;}

            requested=Math.min(requested,bank);
            double chip=chip5FloorTie103(requested,bank);
            if(bank>=15-1e-9&&chip<15-1e-9)chip=15;
            double wager=Math.min(chip,bank);
            if(wager<=0)break;
            if(!cooling&&!cautious&&hand>8&&bank>=130)everPlunge=true;

            PairStats ps=new PairStats();
            Round rr=playRound(shoe,ptr,Profile.SELECTIVE_AGGRESSION,cohort,session+1,hand,true,bank,ps,wager,false);
            ptr=rr.endPointer;cum+=roundStake356(rr);bank+=rr.net;if(bank<0)bank=0;
            peak=Math.max(peak,bank);maxPeak=Math.max(maxPeak,bank);maxDD=Math.max(maxDD,maxPeak-bank);
            hist[hc%hist.length]=rr.net>1e-9?1:(rr.net<-1e-9?-1:0);hc++;
            if(bank>=150-1e-9)activated150=true;

            boolean trigger=false;
            if(activated150&&peak>100+1e-9){
                double profit=peak-100.0,surr=Math.max(0,peak-bank);
                trigger=100.0*surr/profit>=25.0-1e-9;
            }

            if(trigger){
                if(arm==Arm151.ALWAYS_TRAIL_150_GIVEBACK_25){
                    stopReason="ALWAYS_TRAIL_TRIGGER";break;
                }

                boolean qualifies=cum<=P139_EXPOSURE_GATE+1e-9&&bank>=P139_BANKROLL_GATE-1e-9;
                if(first<0){
                    if(qualifies){
                        // Existing accepted first-trigger gate remains part of reference architecture.
                        stopReason="EXISTING_ACCEPTED_FIRST_TRIGGER";break;
                    }
                    first=hand;firstBank=bank;
                }else if(qualifies){
                    stopReason="EXISTING_LATER_GATE";break;
                }
            }

            if(first>0 && hand-first==checkpoint && !observedCheckpoint){
                observedCheckpoint=true;
                observedCheckpointBank=bank;
                observedFirstBank=firstBank;
                observedRegained=bank>=firstBank-1e-9;
            }

            if(first>0 && hand-first==checkpoint &&
               (arm==Arm151.CAPITAL_PRESERVATION_STATE_POLICY||
                arm==Arm151.BALANCED_THREE_STATE_POLICY||
                arm==Arm151.REFINED_60||arm==Arm151.REFINED_70)){
                boolean regained=bank>=firstBank-1e-9;
                boolean capitalRisk=bank<100-1e-9&&!regained;
                boolean freeze=false;
                if(arm==Arm151.CAPITAL_PRESERVATION_STATE_POLICY||arm==Arm151.BALANCED_THREE_STATE_POLICY)
                    freeze=capitalRisk;
                else if(arm==Arm151.REFINED_60)
                    freeze=capitalRisk&&bank<=60+1e-9;
                else if(arm==Arm151.REFINED_70)
                    freeze=capitalRisk&&bank<=70+1e-9;
                if(freeze){
                    if(arm==Arm151.REFINED_60)stopReason="REFINED_60_FREEZE";
                    else if(arm==Arm151.REFINED_70)stopReason="REFINED_70_FREEZE";
                    else stopReason="FROZEN_CAPITAL_RISK_FREEZE";
                    break;
                }
            }

            if(bank>=200-1e-9){reached200=true;break;}
            if(bank<=1e-9)break;
            if(everPlunge&&bank<=100&&!cooling){cooling=true;cautious=false;negativeEpisode=false;brakeArmed=false;}
        }

        if(bank>=200-1e-9)reached200=true;
        R151 r=new R151();r.cohort=cohort;r.arm=arm.name();r.session=session+1;r.checkpoint=checkpoint;
        r.hand=hc;r.finalBank=bank;r.exposure=cum;r.maxDD=maxDD;r.reached200=reached200;
        r.terminalZero=bank<=1e-9;r.positive=bank>100+1e-9;r.stopReason=stopReason;
        r.checkpointObserved=observedCheckpoint;r.checkpointBank=observedCheckpointBank;
        r.firstTriggerBank=observedFirstBank;r.checkpointRegained=observedRegained;
        return r;
    }

    static void report151(StringBuilder sb,String arm,int cp,ArrayList<R151> all){
        int n=0,target=0,zero=0,pos=0,freeze=0;double bank=0,exp=0,dd=0,h=0;
        for(R151 r:all)if(r.arm.equals(arm)&&r.checkpoint==cp){
            n++;bank+=r.finalBank;exp+=r.exposure;dd+=r.maxDD;h+=r.hand;
            if(r.reached200)target++;if(r.terminalZero)zero++;if(r.positive)pos++;
            if(r.stopReason.equals("FROZEN_CAPITAL_RISK_FREEZE"))freeze++;
        }
        sb.append(String.format(Locale.US,"%n%s / +%d%n",arm,cp));
        sb.append(String.format(Locale.US,"n=%d | mean final £%.3f | £200 %.2f%% | £0 %.2f%% | positive %.2f%%%n",
            n,bank/n,p131(target,n),p131(zero,n),p131(pos,n)));
        sb.append(String.format(Locale.US,"mean exposure £%.3f | mean hands %.3f | mean max drawdown £%.3f | state freezes %d (%.2f%%)%n",
            exp/n,h/n,dd/n,freeze,p131(freeze,n)));
    }


    /* PHASE 3.152 — FRESH REPLICATION + CHANGED-SESSION MECHANISM
       Frozen +5 Capital Risk intervention only. No tuning.

       Fresh cohorts AG/AH/AI/AJ.
       Compare CONTINUE_REFERENCE vs CAPITAL_PRESERVATION_STATE_POLICY at +5.

       Secondary mechanism analysis is paired and limited to sessions whose final
       outcome differs between the two arms.
    */
    static final long[] P152_SEEDS={211101L,211201L,211301L,211401L};
    static final String[] P152_COHORTS={"FRESH_AG","FRESH_AH","FRESH_AI","FRESH_AJ"};


    /* PHASE 3.156 — FROZEN SIMPLE-SEPARATOR FRESH VALIDATION
       Discovery candidates were frozen in Phase 3.155:
       A: checkpoint bankroll <= £60
       B: checkpoint bankroll <= £70

       Entirely fresh cohorts AK/AL/AM/AN.
       Validation is observational only: reference execution is unchanged.
       Outcome labels are read only after the session ends.
    */
    static final long[] P156_SEEDS={212101L,212201L,212301L,212401L};
    static final String[] P156_COHORTS={"FRESH_AK","FRESH_AL","FRESH_AM","FRESH_AN"};


    /* PHASE 3.158 — FRESH FOUR-ARM REFINEMENT EXECUTION
       Contract frozen in Phase 3.157.
       Fresh cohorts AO/AP/AQ/AR only.
       Same sessions paired across all four arms.
       +5 checkpoint. No tuning.
    */
    static final long[] P158_SEEDS={213101L,213201L,213301L,213401L};
    static final String[] P158_COHORTS={"FRESH_AO","FRESH_AP","FRESH_AQ","FRESH_AR"};

    static String armName158(Arm151 a){
        if(a==Arm151.CAPITAL_PRESERVATION_STATE_POLICY)return "ORIGINAL_CAPITAL_RISK_FREEZE";
        return a.name();
    }


    /* PHASE 3.159 — FRESH FOUR-ARM ORDERING REPLICATION
       Frozen Phase 3.157 contract, unchanged from 3.158.
       Fresh cohorts AS/AT/AU/AV only.
       Primary: replicate activation / terminal-zero prevention / £200-cost ordering.
       Secondary: cohort-level paired mechanism robustness.
       No tuning.
    */
    static final long[] P159_SEEDS={214101L,214201L,214301L,214401L};
    static final String[] P159_COHORTS={"FRESH_AS","FRESH_AT","FRESH_AU","FRESH_AV"};


    /* PHASE 3.162 — PROSPECTIVE BOUNDARY-INSTRUMENTATION REPLICATION

       Contract:
       Four-arm +5 intervention architecture frozen in Phase 3.157.
       Thresholds remain £60 / £70.
       No wager/card-play/trigger/gate tuning.

       New instrumentation:
       checkpoint bankroll, first-trigger bankroll, regained status and
       CAPITAL_RISK membership are exported BEFORE any intervention action.

       Fresh cohorts AW/AX/AY/AZ only.

       Objectives:
       1. replicate policy ordering again;
       2. directly audit 55–60 / 60–65 / 65–70 / 70–75 prospective
          boundary neighbourhoods;
       3. close the export gap documented in Phase 3.161.
    */
    static final long[] P162_SEEDS={215101L,215201L,215301L,215401L};
    static final String[] P162_COHORTS={"FRESH_AW","FRESH_AX","FRESH_AY","FRESH_AZ"};


    /* PHASE 3.164 — FINAL INDEPENDENT HOLDOUT / BRANCH-CLOSURE TEST

       Frozen architecture:
       CONTINUE_REFERENCE
       ORIGINAL_CAPITAL_RISK_FREEZE
       REFINED_60
       REFINED_70
       +5 checkpoint

       Fresh cohorts BA/BB/BC/BD only.

       PREDECLARED PRIMARY HOLDOUT:
       intervention ordering must reproduce:
       ORIGINAL > <=£70 > <=£60
       for activation count, £0 prevention count and £200-loss count.

       PREDECLARED SECONDARY HOLDOUT:
       <=£60 Capital-Risk region must remain more terminal-risk enriched and
       less target-loss enriched than £80-100.

       No tuning.
    */
    static final long[] P164_SEEDS={216101L,216201L,216301L,216401L};
    static final String[] P164_COHORTS={"FRESH_BA","FRESH_BB","FRESH_BC","FRESH_BD"};

    public static void main(String[]args)throws Exception{
        Arm151[] arms={Arm151.CONTINUE_REFERENCE,Arm151.CAPITAL_PRESERVATION_STATE_POLICY,Arm151.REFINED_60,Arm151.REFINED_70};
        ArrayList<R151>[] rr=new ArrayList[4];
        for(int a=0;a<4;a++)rr[a]=new ArrayList<>();

        for(int c=0;c<4;c++)for(int i=0;i<SESSIONS;i++)
            for(int a=0;a<4;a++)
                rr[a].add(run156(P164_SEEDS[c],P164_COHORTS[c],i,arms[a],5));

        int n=rr[0].size();

        StringBuilder session=new StringBuilder(
          "cohort,session,arm,checkpoint_observed,checkpoint_bank,first_trigger_bank,checkpoint_regained,capital_risk,final_bankroll,reached200,terminal_zero,positive,final_below70,exposure,hands,max_drawdown,stop_reason\n");
        StringBuilder paired=new StringBuilder(
          "cohort,session,arm,checkpoint_observed,checkpoint_bank,first_trigger_bank,checkpoint_regained,capital_risk,delta_bankroll,better,worse,equal,zero_prevented,zero_created,target200_lost,target200_created,severe70_prevented,severe70_created,exposure_delta,maxdd_delta\n");

        StringBuilder res=new StringBuilder();
        res.append("DETERMINISTIC BLACKJACK DECISION ENGINE RESEARCH\n");
        res.append("CODE VERSION: Deterministic Blackjack Phase 3.164\n");
        res.append("CODE BUILD TAG: DBDE-3.164-FINAL-INDEPENDENT-HOLDOUT-BRANCH-CLOSURE\n\n");
        res.append("PHASE 3.164 — FINAL INDEPENDENT HOLDOUT / BRANCH-CLOSURE TEST\n");
        res.append("Fresh cohorts BA/BB/BC/BD; 5,000 sessions each; +5 checkpoint.\n");
        res.append("Frozen four-arm contract and boundary instrumentation. No tuning.\n\n");

        int[] activation=new int[4], targetLost=new int[4], zeroPrevent=new int[4];

        for(int a=0;a<4;a++){
            ArrayList<R151> x=rr[a];
            double bank=0,exp=0,dd=0,hands=0;int t200=0,z=0,posi=0,sev=0,act=0,obs=0,cr=0;
            ArrayList<Double> finals=new ArrayList<>();
            for(R151 r:x){
                bank+=r.finalBank;exp+=r.exposure;dd+=r.maxDD;hands+=r.hand;finals.add(r.finalBank);
                if(r.reached200)t200++;if(r.terminalZero)z++;if(r.positive)posi++;if(r.finalBank<70-1e-9)sev++;
                if(r.stopReason.contains("FREEZE"))act++;
                if(r.checkpointObserved)obs++;
                boolean capitalRisk=r.checkpointObserved&&r.checkpointBank<100-1e-9&&!r.checkpointRegained;
                if(capitalRisk)cr++;
                session.append(String.format(Locale.US,
                  "%s,%d,%s,%s,%.2f,%.2f,%s,%s,%.2f,%s,%s,%s,%s,%.2f,%d,%.2f,%s%n",
                  r.cohort,r.session,armName158(arms[a]),r.checkpointObserved,r.checkpointBank,r.firstTriggerBank,
                  r.checkpointRegained,capitalRisk,r.finalBank,r.reached200,r.terminalZero,r.positive,
                  r.finalBank<70-1e-9,r.exposure,r.hand,r.maxDD,r.stopReason));
            }
            Collections.sort(finals);
            double med=(finals.get(n/2-1)+finals.get(n/2))/2.0;
            activation[a]=act;
            res.append(armName158(arms[a])+"\n");
            res.append(String.format(Locale.US,
              "mean final £%.3f | median £%.3f | £200 %.2f%% | £0 %.2f%% | positive %.2f%% | final<£70 %.2f%%%n",
              bank/n,med,p131(t200,n),p131(z,n),p131(posi,n),p131(sev,n)));
            res.append(String.format(Locale.US,
              "mean exposure £%.3f | mean maxDD £%.3f | mean hands %.3f | activation %d (%.2f%%)%n",
              exp/n,dd/n,hands/n,act,p131(act,n)));
            res.append(String.format(Locale.US,
              "checkpoint observed %d (%.2f%%) | observable CAPITAL_RISK %d (%.2f%%)%n%n",
              obs,p131(obs,n),cr,p131(cr,n)));
        }

        ArrayList<R151> ref=rr[0];
        for(int a=1;a<4;a++){
            int better=0,worse=0,equal=0,zp=0,zc=0,tl=0,tc=0,sp=0,sc=0;
            double help=0,harm=0,ed=0,ddd=0;
            for(int i=0;i<n;i++){
                R151 r=ref.get(i),x=rr[a].get(i);double d=x.finalBank-r.finalBank;
                boolean be=d>1e-9,wo=d<-1e-9,eq=Math.abs(d)<=1e-9;
                if(be){better++;help+=d;}else if(wo){worse++;harm+=d;}else equal++;
                boolean zzp=r.terminalZero&&!x.terminalZero, zzc=!r.terminalZero&&x.terminalZero;
                boolean ttl=r.reached200&&!x.reached200, ttc=!r.reached200&&x.reached200;
                boolean ssp=r.finalBank<70-1e-9&&x.finalBank>=70-1e-9;
                boolean ssc=r.finalBank>=70-1e-9&&x.finalBank<70-1e-9;
                if(zzp)zp++;if(zzc)zc++;if(ttl)tl++;if(ttc)tc++;if(ssp)sp++;if(ssc)sc++;
                ed+=x.exposure-r.exposure;ddd+=x.maxDD-r.maxDD;

                boolean capitalRisk=r.checkpointObserved&&r.checkpointBank<100-1e-9&&!r.checkpointRegained;
                paired.append(String.format(Locale.US,
                  "%s,%d,%s,%s,%.2f,%.2f,%s,%s,%.2f,%s,%s,%s,%s,%s,%s,%s,%s,%s,%.2f,%.2f%n",
                  r.cohort,r.session,armName158(arms[a]),r.checkpointObserved,r.checkpointBank,r.firstTriggerBank,
                  r.checkpointRegained,capitalRisk,d,be,wo,eq,zzp,zzc,ttl,ttc,ssp,ssc,
                  x.exposure-r.exposure,x.maxDD-r.maxDD));
            }
            zeroPrevent[a]=zp;targetLost[a]=tl;
            res.append("PAIRED VS CONTINUE_REFERENCE — "+armName158(arms[a])+"\n");
            res.append(String.format(Locale.US,
              "better %d (%.2f%%) | worse %d (%.2f%%) | equal %d (%.2f%%)%n",
              better,p131(better,n),worse,p131(worse,n),equal,p131(equal,n)));
            res.append(String.format(Locale.US,"aggregate HELP £%+.2f | HARM £%+.2f | NET £%+.2f%n",help,harm,help+harm));
            res.append(String.format(Locale.US,
              "£0 prevented/created %d/%d | £200 lost/created %d/%d | severe<£70 prevented/created %d/%d%n",
              zp,zc,tl,tc,sp,sc));
            res.append(String.format(Locale.US,"mean exposure delta £%+.3f | mean maxDD delta £%+.3f%n%n",ed/n,ddd/n));
        }

        // Primary ordering test.
        boolean actOrder=activation[1]>activation[3]&&activation[3]>activation[2];
        boolean zpOrder=zeroPrevent[1]>zeroPrevent[3]&&zeroPrevent[3]>zeroPrevent[2];
        boolean tlOrder=targetLost[1]>targetLost[3]&&targetLost[3]>targetLost[2];

        // Secondary regional enrichment using ORIGINAL paired evidence and reference checkpoint state.
        int lowN=0,lowZ=0,lowT=0,highN=0,highZ=0,highT=0;
        ArrayList<R151> orig=rr[1];
        for(int i=0;i<n;i++){
            R151 r=ref.get(i),x=orig.get(i);
            boolean cr=r.checkpointObserved&&r.checkpointBank<100-1e-9&&!r.checkpointRegained;
            if(!cr)continue;
            boolean zp=r.terminalZero&&!x.terminalZero;
            boolean tl=r.reached200&&!x.reached200;
            if(r.checkpointBank<=60+1e-9){lowN++;if(zp)lowZ++;if(tl)lowT++;}
            else if(r.checkpointBank>80+1e-9&&r.checkpointBank<100-1e-9){highN++;if(zp)highZ++;if(tl)highT++;}
        }
        double lowZp=p131(lowZ,lowN),lowTp=p131(lowT,lowN),highZp=p131(highZ,highN),highTp=p131(highT,highN);
        boolean enrich=(lowZp>highZp)&&(lowTp<highTp);

        res.append("PREDECLARED HOLDOUT TESTS\n");
        res.append(String.format(Locale.US,"Primary activation ordering ORIGINAL > £70 > £60: %s%n",actOrder?"PASS":"FAIL"));
        res.append(String.format(Locale.US,"Primary £0-prevention ordering ORIGINAL > £70 > £60: %s%n",zpOrder?"PASS":"FAIL"));
        res.append(String.format(Locale.US,"Primary £200-loss ordering ORIGINAL > £70 > £60: %s%n",tlOrder?"PASS":"FAIL"));
        res.append(String.format(Locale.US,
          "Secondary <=£60 vs £80-100: PZ %.2f%% vs %.2f%% | LOST200 %.2f%% vs %.2f%% | %s%n",
          lowZp,highZp,lowTp,highTp,enrich?"PASS":"FAIL"));
        res.append(String.format(Locale.US,"TOTAL PREDECLARED HOLDOUT TESTS PASSED: %d / 4%n",
          (actOrder?1:0)+(zpOrder?1:0)+(tlOrder?1:0)+(enrich?1:0)));

        String fp=hash(res.toString()+session.toString()+paired.toString());
        write("Phase_3_164_Results.txt",res.toString());
        write("Phase_3_164_Session_Evidence.csv",session.toString());
        write("Phase_3_164_Paired_Evidence.csv",paired.toString());
        write("Phase_3_164_Replay_Verification.txt",
          "Fresh BA/BB/BC/BD final holdout. Frozen four-arm contract and checkpoint instrumentation. No tuning. SHA-256: "+fp+"\n");
        System.out.print(res+"\nSHA-256: "+fp+"\n");
    }
}
