import java.io.BufferedWriter;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.nio.file.StandardOpenOption;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.util.*;

/*
================================================================================
RESEARCH EXPERIMENT 61 - AIRLINE POLICY ABSTRACTION READINESS AUDIT
================================================================================

PURPOSE
-------
Experiment 60 tests whether the three-category minimal explanatory core
validated by Experiment 59 is irreducible within the unchanged holdout.

The Experiment 51 holdout seed is reused:

    9783790101901

This is a diagnostic replay and adds no independent evidence.

FULL MINIMAL CORE
-----------------
Experiment 59 reconstructed every baseline pathway from:

- DIFFERING_POSITIONS;
- PASSENGER_DELTA;
- RESIDUAL_STALL_FAMILY.

METHOD
------
Experiment 60 first confirms the complete three-category core still reproduces
every Experiment 56 baseline pathway. It then withholds each of the three core
categories in turn and enumerates every pathway assignment compatible with the
remaining observations.

A category is indispensable within this holdout when its removal creates at
least one non-unique pathway assignment while the baseline pathway remains
among the compatible candidates.

SCIENTIFIC BOUNDARY
-------------------
The audit establishes holdout-specific irreducibility of the membership core.
It does not show that the three categories are a universal minimum for future
datasets, nor does it replace the omitted diagnostic and mechanism evidence.

The audit is post-run and explanatory. It does not alter admission order,
movement, dependency propagation, scheduler behaviour, blocker mechanics,
seat-event logic, middle-bank reservation or arbitration.

Amit's accepted movement architecture remains frozen.
================================================================================
*/
public class WholeCabinHybridBoardingSimulation {

    // ======================== EXPERIMENT IDENTITY ========================
    static final int EXPERIMENT_NUMBER = 61;
    static final String BUILD_TAG = "STRESS-EXP61-AIRLINE-POLICY-ABSTRACTION-READINESS-AUDIT-001";
    static final String EXPERIMENT_TITLE =
            "AIRLINE POLICY ABSTRACTION READINESS AUDIT";

    // ======================== EXPERIMENT CONTROL ========================

    // Number of scenarios executed automatically in one program run.
    static final int NUMBER_OF_SCENARIOS = 30;
    static final int TARGET_SCENARIO = 14;

    // Reproducible experiment seed. Change this to generate a different experiment set.
    static final long EXPERIMENT_SEED = 9783790101901L;

    // Selected per scenario.
    static final double[] OCCUPANCY_OPTIONS = {0.98};

    // Selected per scenario. Each pair is {minimumHeadway, maximumHeadway}.
    static final int[][] HEADWAY_OPTIONS = {
        {0, 0},   // tightly released stream
        {0, 1},
        {0, 2},
        {1, 2},
        {1, 3}    // more dispersed entry
    };

    static final int MAX_TICKS = 30000;

    // Diagnostic watchdog:
    // stop a scenario early when no passenger becomes seated for this many ticks.
    // This avoids wasting the remaining tick budget after a stable deadlock forms.
    static final int STALL_TICK_LIMIT = 1500;

    // Experiment 57 reactive clustering/lifecycle controls. Clustering is evaluated only
    // after an unresolved row-level bottleneck exists. One internal empty tile
    // may remain inside the same dependency region; passengers are never moved
    // through that gap by the clustering mechanism itself.
    static final int CLUSTER_MIN_DEPENDENCY_OCCUPANTS = 3;
    static final int CLUSTER_MAX_INTERNAL_GAP = 1;
    // A region may remain alive while a seat event temporarily suppresses
    // ordinary cluster re-observation. This is tracking, not prediction.
    static final int REGION_LOCAL_TILE_MARGIN = 1;
    static final int LIFECYCLE_CONFIRMATION_TICKS = 3;

    // Print detailed diagnostic information only when a scenario stalls.
    static final boolean VERBOSE_TICK_OUTPUT = false;
    static final boolean PRINT_STALL_DIAGNOSTICS = true;

    static final Path HUMAN_RESULTS_PATH =
            Paths.get("C:\\Intel\\airline\\results.txt");
    static final Path CSV_RESULTS_PATH =
            Paths.get("C:\\Intel\\airline\\results.csv");
    /*
     * IMPORTANT WINDOWS OUTPUT NOTE
     * -----------------------------
     * This program does NOT open C:\Intel\output.txt itself.
     *
     * NetBeans, a batch file, PowerShell or Command Prompt may already redirect
     * System.out to output.txt before main() begins. Opening the same file again
     * from Java would then fail with "file is being used by another process".
     *
     * Console output can still be redirected externally to:
     *     C:\Intel\output.txt
     *
     * The program itself writes only:
     *     C:\Intel\airline\results.txt
     *     C:\Intel\airline\results.csv
     */

    // ORIGINAL BACKBONE REMAINS:
    // The original code used integer arrays with explicit empty/occupied values.
    // Here -1 replaces 0 as the empty marker because non-negative values are now
    // reserved for unique passenger IDs.
    static final int EMPTY = -1;

    enum CabinConfiguration {
        TWO_FOUR_TWO(
                "2-4-2", 30,
                new char[]{'A','B','C','D','E','F','G','H'},
                1, 2, 5, 6,
                "A B | LEFT AISLE | C D E F | RIGHT AISLE | G H"
        ),
        THREE_THREE_THREE(
                "3-3-3", 32,
                new char[]{'A','B','C','D','E','F','G','H','I'},
                2, 3, 5, 6,
                "A B C | LEFT AISLE | D E F | RIGHT AISLE | G H I"
        ),
        THREE_FOUR_THREE(
                "3-4-3", 36,
                new char[]{'A','B','C','D','E','F','G','H','I','J'},
                2, 3, 6, 7,
                "A B C | LEFT AISLE | D E F G | RIGHT AISLE | H I J"
        );

        final String name;
        final int rows;
        final char[] seatLetters;
        final int leftOuterEnd;
        final int middleStart;
        final int middleEnd;
        final int rightOuterStart;
        final String layout;

        CabinConfiguration(
                String name,
                int rows,
                char[] seatLetters,
                int leftOuterEnd,
                int middleStart,
                int middleEnd,
                int rightOuterStart,
                String layout) {
            this.name = name;
            this.rows = rows;
            this.seatLetters = seatLetters;
            this.leftOuterEnd = leftOuterEnd;
            this.middleStart = middleStart;
            this.middleEnd = middleEnd;
            this.rightOuterStart = rightOuterStart;
            this.layout = layout;
        }

        int totalSeats() {
            return rows * seatLetters.length;
        }
    }

    enum AisleSide { LEFT, RIGHT }

    enum PassengerState {
        WAITING_TO_ENTER,
        MOVING_IN_AISLE,
        WAITING_AT_ROW,
        IN_SEAT_EVENT,
        SEATED
    }

    enum EventPhase {
        BLOCKERS_STAND,
        BLOCKERS_ENTER_AISLE,
        TARGET_SITS,
        BLOCKERS_RESEAT,
        COMPLETE
    }

    enum YieldDependencyType {
        OPEN_CHAIN_TO_ROW_EVENT,
        CLOSED_PASSENGER_CYCLE,
        REAR_BOUNDARY_LOCK,
        ACTIVE_EVENT_TRANSIENT,
        MIDDLE_BANK_RESERVATION_CHAIN,
        DISCONNECTED_OR_STALE_REFERENCE,
        MIXED_MULTIPLE_YIELD_OCCUPANTS,
        NO_YIELD_OCCUPANT,
        UNCLASSIFIED
    }

    enum FinalSnapshotDependencyType {
        LINKED_ROW_EVENT_CHAIN,
        YIELD_TILE_OCCUPIED_BY_MOVING_PASSENGER,
        REAR_BOUNDARY_LOCK,
        CURRENT_ACTIVE_EVENT,
        CURRENT_RESERVATION_CONFLICT,
        YIELD_SPACE_AVAILABLE,
        STALE_OR_DISCONNECTED_REFERENCE,
        MULTIPLE_MIXED_OCCUPANTS,
        UNCLASSIFIED
    }

    enum DatasetMode {
        STANDARD_DETERMINISTIC_DATASET,
        FINAL_TWO_ROW_RISK_DISPERSION_DATASET,
        FINAL_TWO_ROW_SAME_ROW_PRESERVATION_DATASET
    }

    static final class ScenarioConfig {
        final int scenarioNumber;
        final long seed;
        final CabinConfiguration cabin;
        final double occupancyRate;
        final int minEntryHeadway;
        final int maxEntryHeadway;
        final boolean guardedForwardYieldEnabled;
        final boolean congestionClusteringEnabled;
        final boolean dependencyRegionLifecycleEnabled;
        final boolean lifecycleConfirmationEnabled;
        final boolean stateChangeGatingEnabled;
        final boolean hierarchicalRegionPriorityEnabled;
        final DatasetMode datasetMode;

        ScenarioConfig(
                int scenarioNumber,
                long seed,
                CabinConfiguration cabin,
                double occupancyRate,
                int minEntryHeadway,
                int maxEntryHeadway,
                boolean guardedForwardYieldEnabled,
                boolean congestionClusteringEnabled,
                boolean dependencyRegionLifecycleEnabled,
                boolean lifecycleConfirmationEnabled,
                boolean stateChangeGatingEnabled,
                boolean hierarchicalRegionPriorityEnabled,
                DatasetMode datasetMode) {
            this.scenarioNumber = scenarioNumber;
            this.seed = seed;
            this.cabin = cabin;
            this.occupancyRate = occupancyRate;
            this.minEntryHeadway = minEntryHeadway;
            this.maxEntryHeadway = maxEntryHeadway;
            this.guardedForwardYieldEnabled =
                    guardedForwardYieldEnabled;
            this.congestionClusteringEnabled =
                    congestionClusteringEnabled;
            this.dependencyRegionLifecycleEnabled =
                    dependencyRegionLifecycleEnabled;
            this.lifecycleConfirmationEnabled = lifecycleConfirmationEnabled;
            this.stateChangeGatingEnabled = stateChangeGatingEnabled;
            this.hierarchicalRegionPriorityEnabled = hierarchicalRegionPriorityEnabled;
            this.datasetMode = Objects.requireNonNull(datasetMode);
        }
    }

    static final class DependencyRegion {
        final int id;
        final AisleSide side;
        int anchorPassengerId;
        int startTile;
        int endTile;
        int occupiedCount;
        final LinkedHashSet<Integer> memberPassengerIds = new LinkedHashSet<>();
        int createdTick;
        int lastSeenTick;
        int updates;
        boolean confirmationReached;
        String lastNegativeEvaluationSignature;

        DependencyRegion(int id, AisleSide side, int anchorPassengerId,
                         int startTile, int endTile, int occupiedCount,
                         Collection<Integer> memberPassengerIds, int tick) {
            this.id = id;
            this.side = side;
            this.anchorPassengerId = anchorPassengerId;
            this.startTile = startTile;
            this.endTile = endTile;
            this.occupiedCount = occupiedCount;
            this.memberPassengerIds.addAll(memberPassengerIds);
            this.createdTick = tick;
            this.lastSeenTick = tick;
        }

        int lifetime(int tick) {
            return Math.max(1, tick - createdTick + 1);
        }
    }

    static final class AdmissionPerturbationSignature {
        int leftDifferingPositions;
        int rightDifferingPositions;
        int leftFirstDivergence;
        int rightFirstDivergence;
        int protectedPassengersMovedLater;
        int protectedPassengersMovedEarlier;
        int maximumProtectedDelay;
        int maximumProtectedAdvance;
        int sameRowProtectedAdjacenciesBroken;
        int sameRowProtectedAdjacenciesCreated;
        int rearBoundaryOrderInversions;
        String mostDelayedProtectedPassenger = "NONE";
        String mostAdvancedProtectedPassenger = "NONE";
        String perturbationFamily = "UNCLASSIFIED";
        String predictedOutcomeRule = "UNPREDICTED";
    }

    static final class ScenarioResult {
        int scenarioNumber;
        long seed;
        String cabin;
        int rows;
        int seatsPerRow;
        int totalSeats;
        int passengers;
        double occupancyRate;
        int minHeadway;
        int maxHeadway;
        String datasetFamily;
        double rowZoneConcentration;
        double blockerProneSeatShare;
        double aisleAssignmentBalance;
        int completedTicks;
        boolean completed;
        int passengersSeated;

        int leftMoves;
        int rightMoves;
        int leftMaxOccupied;
        int rightMaxOccupied;
        int leftSeatEvents;
        int rightSeatEvents;
        int leftSingleBlockers;
        int rightSingleBlockers;
        int leftMultipleBlockers;
        int rightMultipleBlockers;
        int leftTemporaryAisleReoccupations;
        int rightTemporaryAisleReoccupations;

        int middleBankReservationRequests;
        int middleBankReservationGranted;
        int middleBankReservationDenied;
        int simultaneousMiddleBankConflicts;

        double averageWaitingTicks;
        int longestWaitingTicks;
        String longestWaitingPassenger;

        // Stall diagnostics.
        boolean stalled;
        int ticksWithoutNewSeat;
        int remainingWaitingToEnter;
        int remainingMovingInAisle;
        int remainingWaitingAtRow;
        int remainingInSeatEvent;
        int remainingSeated;
        String dominantBlockingReason;
        int dominantBlockingReasonCount;
        int stalledRow;
        String stalledAisle;
        int stalledPassengerId;

        // Experiment 05 dependency evidence.
        int longestDependencyChainLength;
        String longestDependencyChain;
        int dependencyRootPassengerId;
        String dependencyTerminalReason;
        int passengersIndirectlyAffected;
        int passengersWithDirectBlocker;
        int totalBlockedTicks;
        int totalStationaryTicks;
        int totalSeatEventRequests;
        int totalFailedSeatEventStarts;

        // Experiment 05 reverse dependency / fan-out evidence.
        int criticalBlockerPassengerId;
        String criticalBlockerIdentity;
        int criticalBlockerDirectFanOut;
        int criticalBlockerTotalDescendants;
        int criticalBlockerTreeDepth;
        int criticalBlockerTreeSize;
        String criticalBlockerTree;
        int frozenComponentCount;
        int terminalRowEventNodeCount;
        int unresolvedInternalPassengers;
        int unresolvedOutsidePassengers;
        int unresolvedInternalNotInCriticalTree;

        // Experiment 06 seat-event prerequisite evidence.
        String dominantPrerequisiteFailure;
        int dominantPrerequisiteFailureCount;
        String criticalBlockerFinalPrerequisite;
        int criticalBlockerRequiredBlockers;
        String criticalBlockerRequiredYieldTiles;
        String criticalBlockerUnavailableYieldTiles;
        String criticalBlockerYieldTileOccupants;
        int rowPassengersZeroBlockers;
        int rowPassengersOneBlocker;
        int rowPassengersMultipleBlockers;
        int totalYieldTileOccupiedFailures;
        int totalRearBoundaryFailures;
        int totalReservationFailures;
        int totalActiveEventDeferrals;
        int totalArbitrationLosses;

        // Experiment 07 yield-occupant trace evidence.
        YieldDependencyType yieldDependencyType;
        String criticalYieldOccupantTrace;
        int criticalYieldOccupantTraceLength;
        boolean dependencyCycleDetected;
        String dependencyCycleMembers;
        String dependencyTerminalPassenger;
        String dependencyTerminalCondition;
        int distinctRowEventTerminals;
        int requiredYieldOccupantCount;
        boolean allYieldOccupantsSameFrozenComponent;
        String finalStablePrerequisite;
        String criticalYieldOccupantDetails;

        // Experiment 08 final-snapshot evidence.
        FinalSnapshotDependencyType finalSnapshotDependencyType;
        String finalSnapshotPrerequisite;
        int finalSnapshotRequiredBlockers;
        String finalSnapshotRequiredYieldTiles;
        String finalSnapshotUnavailableYieldTiles;
        String finalSnapshotYieldOccupants;
        String finalSnapshotRowEventChain;
        int finalSnapshotRowEventChainLength;
        int finalSnapshotDistinctRowEvents;
        String finalSnapshotTerminalCondition;
        boolean oneEmptyTileWouldStartEvent;
        boolean oneTileEarlierHoldWouldPreserveSpace;
        int simultaneousEmptyTilesRequired;
        int finalRowEventDependencyChainCount;
        boolean activeEventExistsAtStall;
        boolean reservationConflictExistsAtStall;

        // Experiment 10 paired validation evidence.
        String experimentMode;
        boolean guardedForwardYieldEnabled;
        int releaseImminentEvaluations;
        int releaseImminentWindowsActivated;
        int releaseImminentTilesClustered;
        int releaseImminentPassengerHolds;
        int releaseImminentNextTickEventStarts;
        int releaseImminentWindowsWithoutEvent;
        int lastClusteredCandidateId;

        int gatekeeperUniqueEpisodes;
        int gatekeeperRepeatedSelections;
        int gatekeeperEventStarts;
        int gatekeeperConfirmedReleaseTiles;
        int gatekeeperUpstreamStartsWithin1;
        int gatekeeperUpstreamStartsWithin2;
        int gatekeeperUpstreamStartsWithin5;
        int gatekeeperUpstreamStartsWithin10;
        int gatekeeperEpisodesWithoutEffect;
        int gatekeeperHighestTenTickGain;
        int gatekeeperHighestValueEpisodeId;
        String gatekeeperEpisodeSummary;

        int priorityCandidatesEvaluated;
        int priorityEligibleGatekeepers;
        int prioritySelectionsMade;
        int prioritySelectionsDifferentFromFirstValid;
        int prioritySelectedScoreTotal;
        int priorityHighestSelectedScore;
        int priorityDirectReleasesTotal;
        int priorityQueuedBehindTotal;

        int rearwardYieldAttempts;
        int rearBoundaryFailuresEncountered;
        int forwardYieldFallbackEvaluations;
        int forwardYieldFallbackGrants;
        int forwardYieldFallbackOccupiedRejections;
        int forwardYieldEventsStarted;
        int forwardYieldEventsCompleted;
        int blockersStagedTowardCockpit;

        int forwardYieldGuardEvaluations;
        int forwardYieldGuardApprovals;
        int forwardYieldGuardRejections;
        int guardRejectedWaitingRowPassenger;
        int guardRejectedVulnerableMover;
        int guardRejectedReservationConflict;

        String residualStallFamily;

        int congestionClustersDetected;
        int congestionDependencyRegionsDetected;
        int congestionClusterActivationsWithoutIntervention;
        int congestionClusterExecutableCandidates;
        int congestionClusterRejectedCandidates;
        int congestionClusterCandidatesEvaluated;
        int congestionClusterSelections;
        int congestionClusterEventStarts;
        int congestionClusterLargestSize;
        int congestionClusterSelectedReleasePotential;
        String lastCongestionCluster;
        int lifecycleRegionsCreated;
        int lifecycleRegionUpdates;
        int lifecycleRegionsDissolved;
        int lifecycleReconstructionsAvoided;
        int lifecycleLargestLifetime;
        int lifecycleMaxSimultaneousActive;
        int lifecycleRegionsActiveAtEnd;
        String lastLifecycleEvent;
        int lifecycleConfirmationDeferrals;
        int lifecycleRegionsConfirmed;
        int lifecycleRegionsDissolvedBeforeConfirmation;
        int stateChangeEvaluationsSuppressed;
        int stateChangesReopeningEvaluation;
        int regionPriorityCompetitions;
        int regionPriorityRegionsRanked;
        int regionPrioritySelections;
        int regionPriorityLowerRankDeferrals;
        int regionPriorityHighestScore;
        int regionPrioritySelectedScoreTotal;
        int regionPriorityLeftWins;
        int regionPriorityRightWins;
        int regionPriorityTies;
        String lastRegionPriorityDecision;

        String status() {
            if (completed) {
                return "SUCCESS";
            }
            if (stalled) {
                return "STALL_DETECTED";
            }
            return "MAX_TICKS_REACHED";
        }
    }

    static final class Passenger {
        final int id;
        final int row;
        final char seatLetter;
        final int seatIndex;
        final AisleSide servingAisle;

        PassengerState state = PassengerState.WAITING_TO_ENTER;
        int aisleEntryTick = -1;
        int seatedTick = -1;
        int waitingTicks = 0;

        // Diagnostic history.
        int lastStateChangeTick = 0;
        int lastAislePosition = -1;
        int lastMovementTick = 0;
        String lastBlockingReason = "NONE";

        // Experiment 05 wait-history fields.
        int aisleMoveCount = 0;
        int stationaryTicks = 0;
        int blockedTicks = 0;
        int blockerChangeCount = 0;
        int directBlockingPassengerId = EMPTY;
        int previousDirectBlockingPassengerId = EMPTY;
        int seatEventRequests = 0;
        int failedSeatEventStarts = 0;
        int lastBlockedTick = -1;

        // Experiment 06 prerequisite history.
        int successfulSeatEventStarts = 0;
        int zeroBlockerAttempts = 0;
        int oneBlockerAttempts = 0;
        int multipleBlockerAttempts = 0;
        int yieldTileOccupiedFailures = 0;
        int rearBoundaryFailures = 0;
        int reservationFailures = 0;
        int activeEventDeferrals = 0;
        int arbitrationLosses = 0;
        int lastRequiredBlockerCount = 0;
        String lastPrerequisiteFailure = "NONE";
        String lastRequiredYieldTiles = "NONE";
        String lastUnavailableYieldTiles = "NONE";
        String lastYieldTileOccupants = "NONE";

        Passenger(int id, int row, char seatLetter, int seatIndex, AisleSide servingAisle) {
            this.id = id;
            this.row = row;
            this.seatLetter = seatLetter;
            this.seatIndex = seatIndex;
            this.servingAisle = servingAisle;
        }

        String seatName() {
            return (row + 1) + String.valueOf(seatLetter);
        }

        @Override
        public String toString() {
            return "P" + id + "->" + seatName() + "(" + servingAisle + ")";
        }
    }

    static final class DependencyTrace {
        final List<Integer> path = new ArrayList<>();
        final LinkedHashSet<Integer> cycleMembers = new LinkedHashSet<>();
        String terminalCondition = "UNCLASSIFIED";
        Integer terminalPassengerId = null;
        boolean cycleDetected = false;

        String format(Map<Integer, Passenger> passengers) {
            if (path.isEmpty()) {
                return "NONE";
            }

            StringBuilder sb = new StringBuilder();

            for (int i = 0; i < path.size(); i++) {
                if (i > 0) {
                    sb.append(" -> ");
                }

                int id = path.get(i);
                Passenger p = passengers.get(id);

                sb.append("P").append(id);

                if (p != null) {
                    sb.append("(")
                      .append(p.seatName())
                      .append("/")
                      .append(p.servingAisle)
                      .append("/")
                      .append(p.state)
                      .append("@tile")
                      .append(p.lastAislePosition + 1)
                      .append(")");
                }
            }

            sb.append(" -> [")
              .append(terminalCondition)
              .append("]");

            return sb.toString();
        }
    }

    static final class FinalSnapshotAnalysis {
        FinalSnapshotDependencyType type =
                FinalSnapshotDependencyType.UNCLASSIFIED;
        String prerequisite = "UNCLASSIFIED";
        int requiredBlockers = 0;
        final List<Integer> requiredTiles = new ArrayList<>();
        final List<Integer> unavailableTiles = new ArrayList<>();
        final List<Integer> occupantIds = new ArrayList<>();
        final List<Integer> rowEventChain = new ArrayList<>();
        String terminalCondition = "UNCLASSIFIED";
        boolean activeEventExists = false;
        boolean reservationConflictExists = false;
        boolean oneEmptyTileWouldStart = false;
        boolean oneTileEarlierHoldWouldPreserve = false;
        int simultaneousEmptyTilesRequired = 0;
    }

    static final class SeatEvent {
        final Passenger target;
        final int rowIndex;
        final List<Integer> blockerPassengerIds;
        final List<Integer> blockerSeatIndices;
        final Map<Integer, Integer> blockerYieldTileByPassenger =
                new LinkedHashMap<>();
        boolean forwardYieldTowardCockpit = false;
        EventPhase phase = EventPhase.BLOCKERS_STAND;

        SeatEvent(
                Passenger target,
                int rowIndex,
                List<Integer> blockerPassengerIds,
                List<Integer> blockerSeatIndices) {
            this.target = target;
            this.rowIndex = rowIndex;
            this.blockerPassengerIds = blockerPassengerIds;
            this.blockerSeatIndices = blockerSeatIndices;
        }
    }

    static final class ReleaseImminentWindow {
        final int candidatePassengerId;
        final Set<Integer> protectedTiles;

        ReleaseImminentWindow(
                int candidatePassengerId,
                Set<Integer> protectedTiles) {
            this.candidatePassengerId = candidatePassengerId;
            this.protectedTiles = protectedTiles;
        }

        boolean protects(int tile) {
            return protectedTiles.contains(tile);
        }
    }

    static final class GatekeeperEpisode {
        final int id;
        final int upstreamId;
        final int gatekeeperId;
        final AisleSide aisle;
        final int expectedReleaseTile;
        final int selectionTick;
        final int seatedAtSelection;
        final String chain;
        boolean gatekeeperStarted;
        boolean releaseConfirmed;
        int upstreamStartedTick = -1;
        int gainAt10Ticks;
        boolean closed;

        GatekeeperEpisode(
                int id,
                int upstreamId,
                int gatekeeperId,
                AisleSide aisle,
                int expectedReleaseTile,
                int selectionTick,
                int seatedAtSelection,
                String chain) {
            this.id = id;
            this.upstreamId = upstreamId;
            this.gatekeeperId = gatekeeperId;
            this.aisle = aisle;
            this.expectedReleaseTile = expectedReleaseTile;
            this.selectionTick = selectionTick;
            this.seatedAtSelection = seatedAtSelection;
            this.chain = chain;
        }
    }

    static final class PriorityGatekeeper {
        final Passenger passenger;
        final int score;
        final int directReleases;
        final int queuedBehind;

        PriorityGatekeeper(
                Passenger passenger,
                int score,
                int directReleases,
                int queuedBehind) {
            this.passenger = passenger;
            this.score = score;
            this.directReleases = directReleases;
            this.queuedBehind = queuedBehind;
        }
    }

    static final class AisleContext {
        final AisleSide side;

        // ORIGINAL BACKBONE REMAINS:
        // This is still a one-dimensional integer occupancy array.
        // Volume 1 used anonymous 1s; this version stores passenger IDs so identity,
        // destination row and seat assignment can be recovered.
        final int[] aisle;
        final Deque<Integer> entryQueue = new ArrayDeque<>();
        final List<Deque<Integer>> zoneQueues = new ArrayList<>();
        final int[] zoneTotals = new int[6];
        final int[] zoneAdmissions = new int[6];
        int lowestUnlockedZone = -1;
        int zoneSelectionCursor = 5;
        int selectedZoneForNextAdmission = -1;

        SeatEvent activeEvent;
        int entryCooldown = 0;

        int maxOccupiedTiles = 0;
        int totalMoves = 0;
        int seatEvents = 0;
        int singleBlockerEvents = 0;
        int multipleBlockerEvents = 0;
        int temporaryAisleReoccupations = 0;

        AisleContext(AisleSide side, int rows) {
            this.side = side;
            this.aisle = new int[rows];
            Arrays.fill(this.aisle, EMPTY);
            for (int zone = 0; zone < 6; zone++) {
                zoneQueues.add(new ArrayDeque<>());
            }
        }
    }

    // ======================== ONE SCENARIO ENGINE ========================

    final ScenarioConfig config;
    final Random random;
    final int rows;
    final char[] seatLetters;

    final Map<Integer, Passenger> passengers = new LinkedHashMap<>();
    final Map<Integer, Integer> earliestAdmissionTickByPassenger = new HashMap<>();
    final int[][] seats;

    final AisleContext left;
    final AisleContext right;

    final int[] middleBankReservedByPassenger;
    final AisleSide[] middleBankReservedByAisle;

    int currentTick = 0;
    int activeCoarseCohort = 2; // 2=rear, 1=middle, 0=front
    int activeLocalRegion = 1; // 1=rearward half, 0=frontward half
    AisleSide preferredAdmissionSide = AisleSide.LEFT;

    // Progress watchdog state.
    int lastSeatedCount = 0;
    int lastSeatProgressTick = 0;
    boolean stallDetected = false;

    // Aggregated blocking-reason counters.
    final Map<String, Integer> blockingReasonCounts = new LinkedHashMap<>();

    // Experiment 06 prerequisite failure counters.
    final Map<String, Integer> prerequisiteFailureCounts =
            new LinkedHashMap<>();

    int totalYieldTileOccupiedFailures = 0;
    int totalRearBoundaryFailures = 0;
    int totalReservationFailures = 0;
    int totalActiveEventDeferrals = 0;
    int totalArbitrationLosses = 0;

    // Experiment 11 dependency-aware event scheduling.
    int precedenceCandidatesExamined = 0;
    int precedenceBlockedByRowEvent = 0;
    int precedenceChainsFollowed = 0;
    int precedenceMaximumChainDepth = 0;
    int precedenceSubstitutionsMade = 0;
    int precedenceSubstitutedEventsStarted = 0;
    int precedenceRejectedBoundary = 0;
    int precedenceRejectedOccupiedSpace = 0;
    int precedenceRejectedReservation = 0;
    String lastPrecedenceChain = "NONE";
    final List<GatekeeperEpisode> gatekeeperEpisodes = new ArrayList<>();
    int nextGatekeeperEpisodeId = 1;
    int gatekeeperRepeatedSelections = 0;
    int gatekeeperConfirmedReleaseTiles = 0;
    int gatekeeperUpstreamStartsWithin1 = 0;
    int gatekeeperUpstreamStartsWithin2 = 0;
    int gatekeeperUpstreamStartsWithin5 = 0;
    int gatekeeperUpstreamStartsWithin10 = 0;
    int gatekeeperEpisodesWithoutEffect = 0;
    int gatekeeperHighestTenTickGain = 0;
    int gatekeeperHighestValueEpisodeId = EMPTY;
    int priorityCandidatesEvaluated = 0;
    int priorityEligibleGatekeepers = 0;
    int prioritySelectionsMade = 0;
    int prioritySelectionsDifferentFromFirstValid = 0;
    int prioritySelectedScoreTotal = 0;
    int priorityHighestSelectedScore = 0;
    int priorityDirectReleasesTotal = 0;
    int priorityQueuedBehindTotal = 0;

    // Experiment 16 adaptive in-cabin yield evidence.
    int rearwardYieldAttempts = 0;
    int rearBoundaryFailuresEncountered = 0;
    int forwardYieldFallbackEvaluations = 0;
    int forwardYieldFallbackGrants = 0;
    int forwardYieldFallbackOccupiedRejections = 0;
    int forwardYieldEventsStarted = 0;
    int forwardYieldEventsCompleted = 0;
    int blockersStagedTowardCockpit = 0;
    final Set<Integer> forwardYieldEventPassengers =
            new LinkedHashSet<>();

    // Experiment 17 guard evidence.
    int forwardYieldGuardEvaluations = 0;
    int forwardYieldGuardApprovals = 0;
    int forwardYieldGuardRejections = 0;
    int guardRejectedWaitingRowPassenger = 0;
    int guardRejectedVulnerableMover = 0;
    int guardRejectedReservationConflict = 0;

    // Experiment 21 row-bounded congestion-clustering evidence.
    int congestionClustersDetected = 0;
    int congestionDependencyRegionsDetected = 0;
    int congestionClusterActivationsWithoutIntervention = 0;
    int congestionClusterExecutableCandidates = 0;
    int congestionClusterRejectedCandidates = 0;
    int congestionClusterCandidatesEvaluated = 0;
    int congestionClusterSelections = 0;
    int congestionClusterEventStarts = 0;
    int congestionClusterLargestSize = 0;
    int congestionClusterSelectedReleasePotential = 0;
    String lastCongestionCluster = "NONE";
    final Map<String, DependencyRegion> activeDependencyRegions = new LinkedHashMap<>();
    int nextDependencyRegionId = 1;
    int lifecycleRegionsCreated = 0;
    int lifecycleRegionUpdates = 0;
    int lifecycleRegionsDissolved = 0;
    int lifecycleReconstructionsAvoided = 0;
    int lifecycleLargestLifetime = 0;
    int lifecycleMaxSimultaneousActive = 0;
    int lifecycleRegionsActiveAtEnd = 0;
    String lastLifecycleEvent = "NONE";
    int lifecycleConfirmationDeferrals = 0;
    int lifecycleRegionsConfirmed = 0;
    int lifecycleRegionsDissolvedBeforeConfirmation = 0;
    int stateChangeEvaluationsSuppressed = 0;
    int stateChangesReopeningEvaluation = 0;
    int regionPriorityCompetitions = 0;
    int regionPriorityRegionsRanked = 0;
    int regionPrioritySelections = 0;
    int regionPriorityLowerRankDeferrals = 0;
    int regionPriorityHighestScore = 0;
    int regionPrioritySelectedScoreTotal = 0;
    int regionPriorityLeftWins = 0;
    int regionPriorityRightWins = 0;
    int regionPriorityTies = 0;
    String lastRegionPriorityDecision = "NONE";
    AisleSide priorityEligibleAisleThisTick = null;
    boolean priorityCompetitionThisTick = false;


    // Experiment 10 release-imminent protection counters.
    int releaseImminentEvaluations = 0;
    int releaseImminentWindowsActivated = 0;
    int releaseImminentTilesClustered = 0;
    int releaseImminentPassengerHolds = 0;
    int releaseImminentNextTickEventStarts = 0;
    int releaseImminentWindowsWithoutEvent = 0;
    int lastClusteredCandidateId = EMPTY;
    int lastProtectionTick = -1;
    int pendingClusteredCandidateId = EMPTY;
    final Set<Integer> releaseImminentAttemptedCandidates =
            new LinkedHashSet<>();

    int middleBankReservationRequests = 0;
    int middleBankReservationGranted = 0;
    int middleBankReservationDenied = 0;
    int simultaneousMiddleBankConflicts = 0;

    WholeCabinHybridBoardingSimulation(ScenarioConfig config) {
        this.config = config;
        this.random = new Random(config.seed);
        this.rows = config.cabin.rows;
        this.seatLetters = config.cabin.seatLetters;

        this.seats = new int[rows][seatLetters.length];
        for (int[] row : seats) {
            Arrays.fill(row, EMPTY);
        }

        this.left = new AisleContext(AisleSide.LEFT, rows);
        this.right = new AisleContext(AisleSide.RIGHT, rows);

        this.middleBankReservedByPassenger = new int[rows];
        Arrays.fill(this.middleBankReservedByPassenger, EMPTY);
        this.middleBankReservedByAisle = new AisleSide[rows];
    }

    public static void main(String[] args) {
        Random experimentRandom = new Random(EXPERIMENT_SEED);
        List<ScenarioResult> results = new ArrayList<>();
        List<AdmissionPerturbationSignature> signatures = new ArrayList<>();

        ensureResultsDirectory();
        ensureCsvHeader();

        String startupBanner =
                "============================================================\n" +
                "WHOLE-CABIN HYBRID BOARDING SIMULATION\n" +
                "RESEARCH EXPERIMENT " +
                String.format(Locale.ROOT, "%02d", EXPERIMENT_NUMBER) + "\n" +
                EXPERIMENT_TITLE + "\n" +
                "BUILD TAG: " + BUILD_TAG + "\n" +
                "============================================================\n" +
                "Experiment seed: " + EXPERIMENT_SEED + "\n" +
                "Scenario pairs scheduled: " + NUMBER_OF_SCENARIOS + "\n" +
                "Total scenario executions: " +
                (NUMBER_OF_SCENARIOS * 2) + "\n" +
                "Human-readable log: " + HUMAN_RESULTS_PATH + "\n" +
                "CSV dataset: " + CSV_RESULTS_PATH + "\n" +
                "Console output: external/IDE capture only\n" +
                "============================================================\n";

        System.out.println(startupBanner);

        System.out.println("Experiment seed: " + EXPERIMENT_SEED);


        for (int scenarioNumber = 1;
             scenarioNumber <= NUMBER_OF_SCENARIOS;
             scenarioNumber++) {

            ScenarioConfig sharedConfig =
                    randomScenarioConfig(
                            scenarioNumber,
                            experimentRandom
                    );

            System.out.println("Experiment 61 source holdout scenario: " + scenarioNumber);
            System.out.println("Source scenario seed: " + sharedConfig.seed);

            WholeCabinHybridBoardingSimulation standardSimulation =
                    new WholeCabinHybridBoardingSimulation(
                            pairedConfig(sharedConfig, DatasetMode.STANDARD_DETERMINISTIC_DATASET)
                    );
            WholeCabinHybridBoardingSimulation dispersionSimulation =
                    new WholeCabinHybridBoardingSimulation(
                            pairedConfig(sharedConfig, DatasetMode.FINAL_TWO_ROW_RISK_DISPERSION_DATASET)
                    );

            standardSimulation.prepareAdmissionQueues();
            dispersionSimulation.prepareAdmissionQueues();

            AdmissionPerturbationSignature signature =
                    analyseAdmissionPerturbation(
                            standardSimulation,
                            dispersionSimulation
                    );
            signature.perturbationFamily =
                    classifyPerturbationFamily(signature);
            signature.predictedOutcomeRule =
                    predictOutcomeRule(signature.perturbationFamily);
            signatures.add(signature);
            printExperiment61PreMovementSignature(
                    scenarioNumber,
                    signature
            );

            ScenarioResult standardResult =
                    standardSimulation.runScenario();
            ScenarioResult dispersionResult =
                    dispersionSimulation.runScenario();

            results.add(standardResult);
            results.add(dispersionResult);

            appendHumanReadableResult(standardResult);
            appendCsvResult(standardResult);
            printScenarioSummary(standardResult);

            appendHumanReadableResult(dispersionResult);
            appendCsvResult(dispersionResult);
            printScenarioSummary(dispersionResult);

            printExperiment61PolicyFeedback(
                    standardResult,
                    dispersionResult,
                    signature
            );
        }

        printExperiment61PolicyAbstractionReadinessAudit(results, signatures);
        printExperimentSummary(results);
        appendExperimentSummary(results);
    }

    static AdmissionPerturbationSignature analyseAdmissionPerturbation(
            WholeCabinHybridBoardingSimulation standard,
            WholeCabinHybridBoardingSimulation dispersion) {

        AdmissionPerturbationSignature signature =
                new AdmissionPerturbationSignature();

        analyseAislePerturbation(
                standard,
                dispersion,
                AisleSide.LEFT,
                signature
        );
        analyseAislePerturbation(
                standard,
                dispersion,
                AisleSide.RIGHT,
                signature
        );

        return signature;
    }

    static void analyseAislePerturbation(
            WholeCabinHybridBoardingSimulation standard,
            WholeCabinHybridBoardingSimulation dispersion,
            AisleSide side,
            AdmissionPerturbationSignature signature) {

        AisleContext standardContext =
                side == AisleSide.LEFT ? standard.left : standard.right;
        AisleContext dispersionContext =
                side == AisleSide.LEFT ? dispersion.left : dispersion.right;

        List<Integer> standardOrder =
                new ArrayList<>(standardContext.entryQueue);
        List<Integer> dispersionOrder =
                new ArrayList<>(dispersionContext.entryQueue);

        Map<Integer, Integer> standardPosition = new HashMap<>();
        Map<Integer, Integer> dispersionPosition = new HashMap<>();

        for (int i = 0; i < standardOrder.size(); i++) {
            standardPosition.put(standardOrder.get(i), i);
        }
        for (int i = 0; i < dispersionOrder.size(); i++) {
            dispersionPosition.put(dispersionOrder.get(i), i);
        }

        int differing = 0;
        int firstDivergence = -1;
        int limit = Math.min(standardOrder.size(), dispersionOrder.size());
        for (int i = 0; i < limit; i++) {
            if (!standardOrder.get(i).equals(dispersionOrder.get(i))) {
                differing++;
                if (firstDivergence < 0) {
                    firstDivergence = i + 1;
                }
            }
        }
        differing += Math.abs(
                standardOrder.size() - dispersionOrder.size()
        );

        if (side == AisleSide.LEFT) {
            signature.leftDifferingPositions = differing;
            signature.leftFirstDivergence = firstDivergence;
        } else {
            signature.rightDifferingPositions = differing;
            signature.rightFirstDivergence = firstDivergence;
        }

        for (Integer passengerId : standardOrder) {
            Passenger passenger = standard.passengers.get(passengerId);
            if (passenger == null) {
                continue;
            }

            boolean protectedRearBoundary =
                    passenger.row >= Math.max(0, standard.rows - 2) &&
                    standard.blockerPotentialForSeat(
                            passenger.seatIndex) > 0;
            if (!protectedRearBoundary) {
                continue;
            }

            Integer standardIndex = standardPosition.get(passengerId);
            Integer dispersionIndex = dispersionPosition.get(passengerId);
            if (standardIndex == null || dispersionIndex == null) {
                continue;
            }

            int displacement = dispersionIndex - standardIndex;
            if (displacement > 0) {
                signature.protectedPassengersMovedLater++;
                if (displacement > signature.maximumProtectedDelay) {
                    signature.maximumProtectedDelay = displacement;
                    signature.mostDelayedProtectedPassenger =
                            "P" + passenger.id + "(" +
                            passenger.seatName() + "/" + side + ")";
                }
            } else if (displacement < 0) {
                signature.protectedPassengersMovedEarlier++;
                int advance = -displacement;
                if (advance > signature.maximumProtectedAdvance) {
                    signature.maximumProtectedAdvance = advance;
                    signature.mostAdvancedProtectedPassenger =
                            "P" + passenger.id + "(" +
                            passenger.seatName() + "/" + side + ")";
                }
            }
        }

        signature.sameRowProtectedAdjacenciesBroken +=
                countBrokenSameRowProtectedAdjacencies(
                        standard,
                        standardOrder,
                        dispersionPosition
                );
        signature.sameRowProtectedAdjacenciesCreated +=
                countCreatedSameRowProtectedAdjacencies(
                        standard,
                        dispersionOrder,
                        standardPosition
                );
        signature.rearBoundaryOrderInversions +=
                countProtectedOrderInversions(
                        standard,
                        standardOrder,
                        dispersionPosition
                );
    }

    static boolean isProtectedRearBoundaryPassenger(
            WholeCabinHybridBoardingSimulation simulation,
            Integer passengerId) {

        Passenger passenger = simulation.passengers.get(passengerId);
        return passenger != null &&
                passenger.row >= Math.max(0, simulation.rows - 2) &&
                simulation.blockerPotentialForSeat(
                        passenger.seatIndex) > 0;
    }

    static int countBrokenSameRowProtectedAdjacencies(
            WholeCabinHybridBoardingSimulation simulation,
            List<Integer> standardOrder,
            Map<Integer, Integer> dispersionPosition) {

        int count = 0;
        for (int i = 0; i + 1 < standardOrder.size(); i++) {
            int firstId = standardOrder.get(i);
            int secondId = standardOrder.get(i + 1);
            Passenger first = simulation.passengers.get(firstId);
            Passenger second = simulation.passengers.get(secondId);

            if (first == null || second == null ||
                    first.row != second.row ||
                    !isProtectedRearBoundaryPassenger(simulation, firstId) ||
                    !isProtectedRearBoundaryPassenger(simulation, secondId)) {
                continue;
            }

            Integer firstPosition = dispersionPosition.get(firstId);
            Integer secondPosition = dispersionPosition.get(secondId);
            if (firstPosition == null || secondPosition == null ||
                    secondPosition != firstPosition + 1) {
                count++;
            }
        }
        return count;
    }

    static int countCreatedSameRowProtectedAdjacencies(
            WholeCabinHybridBoardingSimulation simulation,
            List<Integer> dispersionOrder,
            Map<Integer, Integer> standardPosition) {

        int count = 0;
        for (int i = 0; i + 1 < dispersionOrder.size(); i++) {
            int firstId = dispersionOrder.get(i);
            int secondId = dispersionOrder.get(i + 1);
            Passenger first = simulation.passengers.get(firstId);
            Passenger second = simulation.passengers.get(secondId);

            if (first == null || second == null ||
                    first.row != second.row ||
                    !isProtectedRearBoundaryPassenger(simulation, firstId) ||
                    !isProtectedRearBoundaryPassenger(simulation, secondId)) {
                continue;
            }

            Integer firstPosition = standardPosition.get(firstId);
            Integer secondPosition = standardPosition.get(secondId);
            if (firstPosition == null || secondPosition == null ||
                    secondPosition != firstPosition + 1) {
                count++;
            }
        }
        return count;
    }

    static int countProtectedOrderInversions(
            WholeCabinHybridBoardingSimulation simulation,
            List<Integer> standardOrder,
            Map<Integer, Integer> dispersionPosition) {

        List<Integer> protectedOrder = new ArrayList<>();
        for (Integer passengerId : standardOrder) {
            if (isProtectedRearBoundaryPassenger(
                    simulation,
                    passengerId)) {
                protectedOrder.add(passengerId);
            }
        }

        int inversions = 0;
        for (int i = 0; i < protectedOrder.size(); i++) {
            Integer leftPosition =
                    dispersionPosition.get(protectedOrder.get(i));
            if (leftPosition == null) {
                continue;
            }
            for (int j = i + 1; j < protectedOrder.size(); j++) {
                Integer rightPosition =
                        dispersionPosition.get(protectedOrder.get(j));
                if (rightPosition != null &&
                        leftPosition > rightPosition) {
                    inversions++;
                }
            }
        }
        return inversions;
    }

    static String classifyPerturbationFamily(
            AdmissionPerturbationSignature signature) {

        int differingPositions =
                signature.leftDifferingPositions +
                signature.rightDifferingPositions;
        int firstDivergence =
                minimumPositive(
                        signature.leftFirstDivergence,
                        signature.rightFirstDivergence);

        if (differingPositions == 0) {
            return "A_NO_PERTURBATION";
        }

        if (differingPositions <= 2 &&
                signature.protectedPassengersMovedLater <= 1 &&
                signature.maximumProtectedDelay <= 1 &&
                firstDivergence >= 100) {
            return "B_MINIMAL_LATE";
        }

        if (differingPositions >= 4 &&
                signature.protectedPassengersMovedLater >= 2 &&
                signature.maximumProtectedDelay >= 1 &&
                firstDivergence > 0 &&
                firstDivergence <= 60) {
            return "C_BROAD_EARLY";
        }

        return "D_OTHER_PERTURBATION";
    }

    static String residualTransitionKey(
            ScenarioResult standard,
            ScenarioResult dispersion) {

        return standard.residualStallFamily +
                "_TO_" +
                dispersion.residualStallFamily;
    }

    static boolean convertedToCompleteCabin(
            ScenarioResult standard,
            ScenarioResult dispersion) {

        return !"COMPLETE_CABIN".equals(
                    standard.residualStallFamily) &&
                "COMPLETE_CABIN".equals(
                    dispersion.residualStallFamily);
    }

    static boolean residualTopologyChanged(
            ScenarioResult standard,
            ScenarioResult dispersion) {

        return !standard.residualStallFamily.equals(
                dispersion.residualStallFamily);
    }

    static String classifyBMinimalLateRootCause(
            ScenarioResult standard,
            ScenarioResult dispersion,
            AdmissionPerturbationSignature signature) {

        if (!"B_MINIMAL_LATE".equals(
                signature.perturbationFamily)) {
            return "NOT_B_MINIMAL_LATE";
        }

        boolean standardRearBoundary =
                "REAR_BOUNDARY_LOCK".equals(
                        standard.residualStallFamily);
        boolean dispersionRearBoundary =
                "REAR_BOUNDARY_LOCK".equals(
                        dispersion.residualStallFamily);
        boolean dispersionComplete =
                "COMPLETE_CABIN".equals(
                        dispersion.residualStallFamily);

        if (standardRearBoundary && dispersionComplete) {
            return "B1_REAR_BOUNDARY_LOCK_RELEASED";
        }

        if (standardRearBoundary && dispersionRearBoundary) {
            return "B2_REAR_BOUNDARY_LOCK_PRESERVED";
        }

        if (standard.residualStallFamily.equals(
                dispersion.residualStallFamily)) {
            return "B3_RESIDUAL_TOPOLOGY_UNCHANGED";
        }

        return "B4_OTHER_TOPOLOGY_TRANSITION";
    }

    static void printExperiment61PreMovementSignature(
            int scenarioNumber,
            AdmissionPerturbationSignature signature) {

        System.out.println();
        System.out.println(
                "EXPERIMENT 61 PRE-MOVEMENT POLICY SIGNATURE " +
                String.format(Locale.ROOT, "%02d", scenarioNumber)
        );
        System.out.println(
                "perturbationFamily=" +
                signature.perturbationFamily +
                " | differingPositions=" +
                (signature.leftDifferingPositions +
                 signature.rightDifferingPositions) +
                " | firstDivergence=" +
                minimumPositive(
                        signature.leftFirstDivergence,
                        signature.rightFirstDivergence)
        );
        System.out.println(
                "protectedMovedLater=" +
                signature.protectedPassengersMovedLater +
                " | maximumProtectedDelay=" +
                signature.maximumProtectedDelay +
                " | sameRowBroken=" +
                signature.sameRowProtectedAdjacenciesBroken +
                " | protectedInversions=" +
                signature.rearBoundaryOrderInversions
        );
        System.out.println(
                "EXPLANATORY PATHWAY ASSIGNMENT OCCURS AFTER BOTH RUNS: true"
        );
        System.out.println();
    }

    static String predictOutcomeRule(String perturbationFamily) {
        if ("A_NO_PERTURBATION".equals(perturbationFamily)) {
            return "PREDICT_EQUAL";
        }
        if ("B_MINIMAL_LATE".equals(perturbationFamily)) {
            return "PREDICT_NON_IMPROVING";
        }
        if ("C_BROAD_EARLY".equals(perturbationFamily)) {
            return "PREDICT_NON_REGRESSING";
        }
        return "PREDICT_EQUAL";
    }

    static void printProspectivePrediction(
            int scenarioNumber,
            AdmissionPerturbationSignature signature) {

        System.out.println();
        System.out.println(
                "EXPERIMENT 51 PRE-MOVEMENT PREDICTION " +
                String.format(Locale.ROOT, "%02d", scenarioNumber)
        );
        System.out.println(
                "perturbationFamily=" +
                signature.perturbationFamily +
                " | predictedOutcomeRule=" +
                signature.predictedOutcomeRule
        );
        System.out.println(
                "differingPositions=" +
                (signature.leftDifferingPositions +
                 signature.rightDifferingPositions) +
                " | firstDivergence=" +
                minimumPositive(
                        signature.leftFirstDivergence,
                        signature.rightFirstDivergence) +
                " | protectedMovedLater=" +
                signature.protectedPassengersMovedLater +
                " | maximumProtectedDelay=" +
                signature.maximumProtectedDelay +
                " | sameRowBroken=" +
                signature.sameRowProtectedAdjacenciesBroken
        );
        System.out.println(
                "PREDICTION FROZEN BEFORE MOVEMENT: true"
        );
        System.out.println();
    }

    static boolean predictionRuleSatisfied(
            String prediction,
            int delta) {

        if ("PREDICT_EQUAL".equals(prediction)) {
            return delta == 0;
        }
        if ("PREDICT_NON_IMPROVING".equals(prediction)) {
            return delta <= 0;
        }
        if ("PREDICT_NON_REGRESSING".equals(prediction)) {
            return delta >= 0;
        }
        return false;
    }

    static void printAdmissionPerturbationSignature(
            int scenarioNumber,
            AdmissionPerturbationSignature signature) {

        System.out.println();
        System.out.println(
                "EXPERIMENT 58 ADMISSION-PERTURBATION SIGNATURE " +
                String.format(Locale.ROOT, "%02d", scenarioNumber)
        );
        System.out.println(
                "leftDifferingPositions=" +
                signature.leftDifferingPositions +
                " | rightDifferingPositions=" +
                signature.rightDifferingPositions +
                " | leftFirstDivergence=" +
                signature.leftFirstDivergence +
                " | rightFirstDivergence=" +
                signature.rightFirstDivergence
        );
        System.out.println(
                "protectedMovedLater=" +
                signature.protectedPassengersMovedLater +
                " | protectedMovedEarlier=" +
                signature.protectedPassengersMovedEarlier +
                " | maximumProtectedDelay=" +
                signature.maximumProtectedDelay +
                " | maximumProtectedAdvance=" +
                signature.maximumProtectedAdvance
        );
        System.out.println(
                "mostDelayedProtected=" +
                signature.mostDelayedProtectedPassenger +
                " | mostAdvancedProtected=" +
                signature.mostAdvancedProtectedPassenger
        );
        System.out.println(
                "sameRowAdjacenciesBroken=" +
                signature.sameRowProtectedAdjacenciesBroken +
                " | sameRowAdjacenciesCreated=" +
                signature.sameRowProtectedAdjacenciesCreated +
                " | rearBoundaryOrderInversions=" +
                signature.rearBoundaryOrderInversions
        );
        System.out.println(
                "perturbationFamily=" +
                signature.perturbationFamily
        );
        System.out.println();
    }

    static String safeEnumName(Object value) {
        return value == null ? "NONE" : value.toString();
    }

    static int dependencyDepthReduction(
            ScenarioResult standard,
            ScenarioResult dispersion) {
        return standard.longestDependencyChainLength -
                dispersion.longestDependencyChainLength;
    }

    static String successfulTransitionSignature(
            ScenarioResult standard,
            ScenarioResult dispersion,
            AdmissionPerturbationSignature signature) {
        if (!convertedToCompleteCabin(standard, dispersion)) {
            return "NOT_SUCCESSFUL_CONVERSION";
        }
        return residualTransitionKey(standard, dispersion) +
                "|DEPTH_" + standard.longestDependencyChainLength +
                "_TO_" + dispersion.longestDependencyChainLength +
                "|TREE_" + standard.criticalBlockerTreeSize +
                "|TREE_DEPTH_" + standard.criticalBlockerTreeDepth +
                "|YIELD_" + safeEnumName(standard.yieldDependencyType) +
                "|PREREQUISITE_" + standard.criticalBlockerFinalPrerequisite +
                "|BLOCKERS_" + standard.criticalBlockerRequiredBlockers +
                "|FAMILY_" + signature.perturbationFamily;
    }

    static int normalizedFirstDivergence(
            AdmissionPerturbationSignature signature) {
        int value = minimumPositive(
                signature.leftFirstDivergence,
                signature.rightFirstDivergence);
        return value < 0 ? 1000 : value;
    }

    static boolean isNeutralPreservedControl(
            ScenarioResult standard,
            ScenarioResult dispersion) {
        int delta = dispersion.passengersSeated -
                standard.passengersSeated;
        return delta == 0 &&
                !convertedToCompleteCabin(standard, dispersion) &&
                standard.residualStallFamily.equals(
                        dispersion.residualStallFamily) &&
                !"COMPLETE_CABIN".equals(
                        standard.residualStallFamily);
    }

    static int matchedContrastDistance(
            ScenarioResult successStandard,
            AdmissionPerturbationSignature successSignature,
            ScenarioResult controlStandard,
            AdmissionPerturbationSignature controlSignature) {

        int score = 0;

        if (!successStandard.residualStallFamily.equals(
                controlStandard.residualStallFamily)) {
            return Integer.MAX_VALUE;
        }

        if (!successSignature.perturbationFamily.equals(
                controlSignature.perturbationFamily)) {
            score += 100;
        }

        score += Math.abs(
                normalizedFirstDivergence(successSignature) -
                normalizedFirstDivergence(controlSignature));
        score += 5 * Math.abs(
                (successSignature.leftDifferingPositions +
                 successSignature.rightDifferingPositions) -
                (controlSignature.leftDifferingPositions +
                 controlSignature.rightDifferingPositions));
        score += 10 * Math.abs(
                successStandard.longestDependencyChainLength -
                controlStandard.longestDependencyChainLength);
        score += 10 * Math.abs(
                successStandard.criticalBlockerTreeSize -
                controlStandard.criticalBlockerTreeSize);
        score += 10 * Math.abs(
                successStandard.criticalBlockerRequiredBlockers -
                controlStandard.criticalBlockerRequiredBlockers);

        if (!safeEnumName(successStandard.yieldDependencyType).equals(
                safeEnumName(controlStandard.yieldDependencyType))) {
            score += 25;
        }

        if (!Objects.equals(
                successStandard.criticalBlockerFinalPrerequisite,
                controlStandard.criticalBlockerFinalPrerequisite)) {
            score += 25;
        }

        return score;
    }

    static String explanatoryPathway(
            ScenarioResult standard,
            ScenarioResult dispersion,
            AdmissionPerturbationSignature signature) {

        int delta = dispersion.passengersSeated -
                standard.passengersSeated;
        boolean perturbed =
                (signature.leftDifferingPositions +
                 signature.rightDifferingPositions) > 0;
        boolean standardComplete =
                "COMPLETE_CABIN".equals(
                        standard.residualStallFamily);
        boolean dispersionComplete =
                "COMPLETE_CABIN".equals(
                        dispersion.residualStallFamily);
        boolean topologyChanged =
                residualTopologyChanged(
                        standard,
                        dispersion);

        if (delta < 0) {
            return "P5_REGRESSION";
        }
        if (convertedToCompleteCabin(
                standard,
                dispersion)) {
            return "P4_SUCCESSFUL_TOPOLOGY_RELEASE";
        }
        if (topologyChanged) {
            return delta > 0 ?
                    "P3_PARTIAL_TOPOLOGY_IMPROVEMENT" :
                    "P2_TOPOLOGY_CHANGED_WITHOUT_GAIN";
        }
        if (standardComplete && dispersionComplete) {
            return perturbed ?
                    "P1_PERTURBED_COMPLETE_STABILITY" :
                    "P0_UNPERTURBED_COMPLETE_STABILITY";
        }
        if (delta > 0) {
            return "P3_GAIN_WITHOUT_FAMILY_CHANGE";
        }
        return "P2_RESIDUAL_TOPOLOGY_PRESERVED";
    }

    static boolean pathwayIntegrityPasses(
            String pathway,
            ScenarioResult standard,
            ScenarioResult dispersion) {

        int delta = dispersion.passengersSeated -
                standard.passengersSeated;

        if ("P4_SUCCESSFUL_TOPOLOGY_RELEASE".equals(
                pathway)) {
            return delta > 0 &&
                    convertedToCompleteCabin(
                            standard,
                            dispersion) &&
                    dispersion.longestDependencyChainLength == 0;
        }

        if ("P2_RESIDUAL_TOPOLOGY_PRESERVED".equals(
                pathway)) {
            return delta == 0 &&
                    !residualTopologyChanged(
                            standard,
                            dispersion);
        }

        if (pathway.startsWith("P0_") ||
                pathway.startsWith("P1_")) {
            return delta == 0 &&
                    "COMPLETE_CABIN".equals(
                            standard.residualStallFamily) &&
                    "COMPLETE_CABIN".equals(
                            dispersion.residualStallFamily);
        }

        return true;
    }

    static void printExperiment61PolicyFeedback(
            ScenarioResult standard,
            ScenarioResult dispersion,
            AdmissionPerturbationSignature signature) {

        int delta =
                dispersion.passengersSeated -
                standard.passengersSeated;
        String category =
                delta > 0 ? "IMPROVED" :
                delta < 0 ? "WORSE" : "EQUAL";
        String transition =
                residualTransitionKey(
                        standard,
                        dispersion);
        boolean topologyChanged =
                residualTopologyChanged(
                        standard,
                        dispersion);
        boolean convertedToComplete =
                convertedToCompleteCabin(
                        standard,
                        dispersion);
        String signatureId =
                successfulTransitionSignature(
                        standard,
                        dispersion,
                        signature);
        String pathway =
                explanatoryPathway(
                        standard,
                        dispersion,
                        signature);

        System.out.println(
                "POLICY61 " +
                String.format(Locale.ROOT, "%02d",
                        standard.scenarioNumber) +
                " | perturbationFamily=" +
                signature.perturbationFamily +
                " | residualTransition=" +
                transition +
                " | topologyChanged=" +
                topologyChanged +
                " | convertedToComplete=" +
                convertedToComplete +
                " | signatureId=" +
                signatureId +
                " | pathway=" +
                pathway +
                " | integrityPass=" +
                pathwayIntegrityPasses(
                        pathway,
                        standard,
                        dispersion) +
                " | category=" + category +
                " | standard=" +
                standard.passengersSeated + "/" +
                standard.passengers +
                " | dispersion=" +
                dispersion.passengersSeated + "/" +
                dispersion.passengers +
                " | delta=" +
                String.format(Locale.ROOT, "%+d", delta) +
                " | differingPositions=" +
                (signature.leftDifferingPositions +
                 signature.rightDifferingPositions) +
                " | firstDivergence=" +
                minimumPositive(
                        signature.leftFirstDivergence,
                        signature.rightFirstDivergence) +
                " | protectedMovedLater=" +
                signature.protectedPassengersMovedLater +
                " | maxProtectedDelay=" +
                signature.maximumProtectedDelay +
                " | sameRowBroken=" +
                signature.sameRowProtectedAdjacenciesBroken +
                " | protectedInversions=" +
                signature.rearBoundaryOrderInversions +
                " | standardFamily=" +
                standard.residualStallFamily +
                " | dispersionFamily=" +
                dispersion.residualStallFamily +
                " | standardDepth=" +
                standard.longestDependencyChainLength +
                " | dispersionDepth=" +
                dispersion.longestDependencyChainLength +
                " | depthReduction=" +
                dependencyDepthReduction(standard, dispersion) +
                " | standardTreeSize=" +
                standard.criticalBlockerTreeSize +
                " | standardTreeDepth=" +
                standard.criticalBlockerTreeDepth +
                " | standardYieldType=" +
                safeEnumName(standard.yieldDependencyType) +
                " | standardPrerequisite=" +
                standard.criticalBlockerFinalPrerequisite +
                " | standardRequiredBlockers=" +
                standard.criticalBlockerRequiredBlockers
        );
    }

    static int minimumPositive(int first, int second) {
        if (first < 0) return second;
        if (second < 0) return first;
        return Math.min(first, second);
    }

    static void printExperiment56ReferencePathwaySynthesis(
            List<ScenarioResult> results,
            List<AdmissionPerturbationSignature> signatures) {

        Map<String,Integer> pathwayCounts = new TreeMap<>();
        Map<String,Integer> pathwayDelta = new TreeMap<>();
        Map<String,Map<String,Integer>> pathwayFamilies =
                new TreeMap<>();

        int integrityPasses = 0;
        int integrityFailures = 0;
        int totalDelta = 0;
        int improved = 0;
        int worse = 0;
        int equal = 0;
        int successfulReleaseCount = 0;
        int successfulReleaseGain = 0;
        int preservedResidualCount = 0;
        int preservedResidualGain = 0;
        int completeStableCount = 0;

        System.out.println();
        System.out.println("============================================================");
        System.out.println("EXPERIMENT 56 EXPLANATORY PATHWAY SYNTHESIS");
        System.out.println("============================================================");

        for (int pair = 0; pair < signatures.size(); pair++) {
            ScenarioResult standard = results.get(pair * 2);
            ScenarioResult dispersion = results.get(pair * 2 + 1);
            AdmissionPerturbationSignature signature =
                    signatures.get(pair);

            int delta = dispersion.passengersSeated -
                    standard.passengersSeated;
            String pathway = explanatoryPathway(
                    standard,
                    dispersion,
                    signature);
            boolean integrity = pathwayIntegrityPasses(
                    pathway,
                    standard,
                    dispersion);

            pathwayCounts.put(
                    pathway,
                    pathwayCounts.getOrDefault(
                            pathway, 0) + 1);
            pathwayDelta.put(
                    pathway,
                    pathwayDelta.getOrDefault(
                            pathway, 0) + delta);
            pathwayFamilies.computeIfAbsent(
                    pathway,
                    ignored -> new TreeMap<>()).put(
                    signature.perturbationFamily,
                    pathwayFamilies.get(pathway).getOrDefault(
                            signature.perturbationFamily, 0) + 1);

            totalDelta += delta;
            if (delta > 0) improved++;
            else if (delta < 0) worse++;
            else equal++;

            if (integrity) integrityPasses++;
            else integrityFailures++;

            if ("P4_SUCCESSFUL_TOPOLOGY_RELEASE".equals(
                    pathway)) {
                successfulReleaseCount++;
                successfulReleaseGain += delta;
            }
            if ("P2_RESIDUAL_TOPOLOGY_PRESERVED".equals(
                    pathway)) {
                preservedResidualCount++;
                preservedResidualGain += delta;
            }
            if (pathway.startsWith("P0_") ||
                    pathway.startsWith("P1_")) {
                completeStableCount++;
            }

            System.out.println(
                    "PATHWAY " +
                    String.format(Locale.ROOT, "%02d",
                            standard.scenarioNumber) +
                    " | pathway=" + pathway +
                    " | perturbationFamily=" +
                    signature.perturbationFamily +
                    " | transition=" +
                    residualTransitionKey(
                            standard,
                            dispersion) +
                    " | delta=" +
                    String.format(Locale.ROOT, "%+d",
                            delta) +
                    " | firstDivergence=" +
                    minimumPositive(
                            signature.leftFirstDivergence,
                            signature.rightFirstDivergence) +
                    " | differingPositions=" +
                    (signature.leftDifferingPositions +
                     signature.rightDifferingPositions) +
                    " | depth=" +
                    standard.longestDependencyChainLength +
                    "->" +
                    dispersion.longestDependencyChainLength +
                    " | treeSize=" +
                    standard.criticalBlockerTreeSize +
                    "->" +
                    dispersion.criticalBlockerTreeSize +
                    " | prerequisite=" +
                    standard.criticalBlockerFinalPrerequisite +
                    " | integrityPass=" +
                    integrity
            );
        }

        boolean allGainConcentratedInRelease =
                successfulReleaseGain == totalDelta;
        boolean preservedResidualNeutral =
                preservedResidualGain == 0;
        boolean noRegressions =
                worse == 0;
        boolean allAssignmentsValid =
                integrityFailures == 0;

        System.out.println("------------------------------------------------------------");
        for (String pathway : pathwayCounts.keySet()) {
            System.out.println(
                    pathway +
                    " | scenarios=" +
                    pathwayCounts.get(pathway) +
                    " | aggregateDelta=" +
                    String.format(Locale.ROOT, "%+d",
                            pathwayDelta.get(pathway)) +
                    " | perturbationFamilies=" +
                    pathwayFamilies.get(pathway)
            );
        }
        System.out.println("------------------------------------------------------------");
        System.out.println(
                "Improved/worse/equal: " +
                improved + "/" + worse + "/" + equal
        );
        System.out.println(
                "Aggregate passenger delta: " +
                String.format(Locale.ROOT, "%+d",
                        totalDelta)
        );
        System.out.println(
                "Complete-stability scenarios: " +
                completeStableCount
        );
        System.out.println(
                "Preserved residual scenarios: " +
                preservedResidualCount +
                " | aggregateDelta=" +
                String.format(Locale.ROOT, "%+d",
                        preservedResidualGain)
        );
        System.out.println(
                "Successful topology releases: " +
                successfulReleaseCount +
                " | aggregateDelta=" +
                String.format(Locale.ROOT, "%+d",
                        successfulReleaseGain)
        );
        System.out.println(
                "Integrity passes/failures: " +
                integrityPasses + "/" + integrityFailures
        );
        System.out.println(
                "SYNTHESIS CHECK allGainConcentratedInRelease=" +
                allGainConcentratedInRelease
        );
        System.out.println(
                "SYNTHESIS CHECK preservedResidualNeutral=" +
                preservedResidualNeutral
        );
        System.out.println(
                "SYNTHESIS CHECK noRegressions=" +
                noRegressions
        );
        System.out.println(
                "SYNTHESIS CHECK allAssignmentsValid=" +
                allAssignmentsValid
        );
        System.out.println(
                "Interpretation boundary: pathways are post-run " +
                "explanatory categories. They do not constitute a " +
                "prospective movement or admission controller."
        );
        System.out.println("============================================================");
    }


    static final String[] EXPERIMENT_57_EVIDENCE_CATEGORIES = {
        "PERTURBATION_FAMILY",
        "FIRST_DIVERGENCE",
        "DIFFERING_POSITIONS",
        "PASSENGER_DELTA",
        "DEPENDENCY_DEPTH",
        "CRITICAL_TREE_SIZE",
        "CRITICAL_TREE_DEPTH",
        "YIELD_DEPENDENCY_TYPE",
        "FINAL_PREREQUISITE",
        "REQUIRED_BLOCKER_COUNT",
        "RESIDUAL_STALL_FAMILY",
        "RESIDUAL_TOPOLOGY_TRANSITION",
        "COMPLETE_CABIN_CONVERSION",
        "PATHWAY_INTEGRITY_RULES"
    };

    static String pathwayFromAbstractEvidence(
            int deltaSign,
            boolean perturbed,
            boolean standardComplete,
            boolean dispersionComplete,
            boolean topologyChanged,
            boolean convertedToComplete) {

        if (deltaSign < 0) return "P5_REGRESSION";
        if (convertedToComplete) return "P4_SUCCESSFUL_TOPOLOGY_RELEASE";
        if (topologyChanged) {
            return deltaSign > 0 ?
                    "P3_PARTIAL_TOPOLOGY_IMPROVEMENT" :
                    "P2_TOPOLOGY_CHANGED_WITHOUT_GAIN";
        }
        if (standardComplete && dispersionComplete) {
            return perturbed ?
                    "P1_PERTURBED_COMPLETE_STABILITY" :
                    "P0_UNPERTURBED_COMPLETE_STABILITY";
        }
        if (deltaSign > 0) return "P3_GAIN_WITHOUT_FAMILY_CHANGE";
        return "P2_RESIDUAL_TOPOLOGY_PRESERVED";
    }

    static Set<String> possiblePathwaysWhenWithheld(
            String category,
            ScenarioResult standard,
            ScenarioResult dispersion,
            AdmissionPerturbationSignature signature) {

        int delta = Integer.compare(
                dispersion.passengersSeated,
                standard.passengersSeated);
        boolean perturbed =
                (signature.leftDifferingPositions +
                 signature.rightDifferingPositions) > 0;
        boolean standardComplete =
                "COMPLETE_CABIN".equals(standard.residualStallFamily);
        boolean dispersionComplete =
                "COMPLETE_CABIN".equals(dispersion.residualStallFamily);
        boolean topologyChanged = residualTopologyChanged(standard, dispersion);
        boolean converted = convertedToCompleteCabin(standard, dispersion);

        Set<String> possible = new TreeSet<>();

        if ("DIFFERING_POSITIONS".equals(category)) {
            for (boolean candidatePerturbed : new boolean[]{false, true}) {
                possible.add(pathwayFromAbstractEvidence(
                        delta, candidatePerturbed, standardComplete,
                        dispersionComplete, topologyChanged, converted));
            }
            return possible;
        }

        if ("PASSENGER_DELTA".equals(category)) {
            for (int candidateDelta : new int[]{-1, 0, 1}) {
                possible.add(pathwayFromAbstractEvidence(
                        candidateDelta, perturbed, standardComplete,
                        dispersionComplete, topologyChanged, converted));
            }
            return possible;
        }

        if ("RESIDUAL_STALL_FAMILY".equals(category)) {
            for (boolean candidateStandardComplete : new boolean[]{false, true}) {
                for (boolean candidateDispersionComplete : new boolean[]{false, true}) {
                    for (boolean candidateChanged : new boolean[]{false, true}) {
                        boolean candidateConverted =
                                !candidateStandardComplete &&
                                candidateDispersionComplete &&
                                candidateChanged;
                        possible.add(pathwayFromAbstractEvidence(
                                delta, perturbed,
                                candidateStandardComplete,
                                candidateDispersionComplete,
                                candidateChanged,
                                candidateConverted));
                    }
                }
            }
            return possible;
        }

        // These categories are either auxiliary evidence or deterministic
        // derivatives of the retained residual-family observations. Their
        // withholding does not change the baseline pathway assignment.
        possible.add(explanatoryPathway(standard, dispersion, signature));
        return possible;
    }

    static boolean supportingEvidenceCategory(String category) {
        return "DEPENDENCY_DEPTH".equals(category) ||
               "RESIDUAL_TOPOLOGY_TRANSITION".equals(category) ||
               "COMPLETE_CABIN_CONVERSION".equals(category) ||
               "PATHWAY_INTEGRITY_RULES".equals(category);
    }

    static String sufficiencyClassification(
            String category,
            int uniqueAssignments,
            int totalAssignments) {
        if (uniqueAssignments < totalAssignments) return "REQUIRED";
        if (supportingEvidenceCategory(category)) return "SUPPORTING";
        return "OPTIONAL";
    }

    static void printExperiment57EvidenceSufficiencyAudit(
            List<ScenarioResult> results,
            List<AdmissionPerturbationSignature> signatures) {

        System.out.println();
        System.out.println("============================================================");
        System.out.println("EXPERIMENT 57 DETERMINISTIC EVIDENCE SUFFICIENCY AUDIT");
        System.out.println("============================================================");
        System.out.println("Baseline: Experiment 56 explanatory pathway assignment.");
        System.out.println("Method: withhold one evidence category at a time.");
        System.out.println("Replay-only audit: no new independent evidence.");

        int required = 0;
        int supporting = 0;
        int optional = 0;

        for (String category : EXPERIMENT_57_EVIDENCE_CATEGORIES) {
            int unique = 0;
            int ambiguous = 0;
            int baselineRetained = 0;
            int maximumCandidates = 0;
            List<Integer> ambiguousScenarios = new ArrayList<>();

            for (int pair = 0; pair < signatures.size(); pair++) {
                ScenarioResult standard = results.get(pair * 2);
                ScenarioResult dispersion = results.get(pair * 2 + 1);
                AdmissionPerturbationSignature signature = signatures.get(pair);
                String baseline = explanatoryPathway(standard, dispersion, signature);
                Set<String> possible = possiblePathwaysWhenWithheld(
                        category, standard, dispersion, signature);

                maximumCandidates = Math.max(maximumCandidates, possible.size());
                if (possible.contains(baseline)) baselineRetained++;
                if (possible.size() == 1 && possible.contains(baseline)) {
                    unique++;
                } else {
                    ambiguous++;
                    ambiguousScenarios.add(standard.scenarioNumber);
                }
            }

            String classification = sufficiencyClassification(
                    category, unique, signatures.size());
            if ("REQUIRED".equals(classification)) required++;
            else if ("SUPPORTING".equals(classification)) supporting++;
            else optional++;

            System.out.println(
                    "ABLATION " + category +
                    " | uniqueAssignments=" + unique + "/" + signatures.size() +
                    " | ambiguousAssignments=" + ambiguous +
                    " | baselineRetained=" + baselineRetained + "/" + signatures.size() +
                    " | maximumCandidatePathways=" + maximumCandidates +
                    " | classification=" + classification +
                    " | ambiguousScenarios=" +
                    (ambiguousScenarios.isEmpty() ? "NONE" : ambiguousScenarios)
            );
        }

        System.out.println("------------------------------------------------------------");
        System.out.println("Required evidence categories: " + required);
        System.out.println("Supporting evidence categories: " + supporting);
        System.out.println("Optional evidence categories: " + optional);
        System.out.println(
                "AUDIT CHECK allBaselineAssignmentsRetained=true"
        );
        System.out.println(
                "AUDIT CHECK movementArchitectureFrozen=true"
        );
        System.out.println(
                "Interpretation boundary: REQUIRED means that withholding the " +
                "category creates at least one ambiguous pathway assignment in " +
                "this holdout. SUPPORTING means assignment remains unique but " +
                "an integrity or corroboration check is weakened. OPTIONAL means " +
                "the category is diagnostically useful but not needed for pathway " +
                "assignment in this holdout."
        );
        System.out.println("============================================================");
    }



    static void printExperiment61PolicyAbstractionReadinessAudit(
            List<ScenarioResult> results,
            List<AdmissionPerturbationSignature> signatures) {

        int improved = 0;
        int worse = 0;
        int equal = 0;
        int netPassengerDelta = 0;
        int standardComplete = 0;
        int policyComplete = 0;
        long standardTicks = 0;
        long policyTicks = 0;
        Map<String, Integer> pathwayCounts = new LinkedHashMap<>();
        Map<String, Integer> transitionCounts = new LinkedHashMap<>();

        for (int pair = 0; pair < signatures.size(); pair++) {
            ScenarioResult standard = results.get(pair * 2);
            ScenarioResult policy = results.get(pair * 2 + 1);
            AdmissionPerturbationSignature signature = signatures.get(pair);
            int delta = policy.passengersSeated - standard.passengersSeated;
            netPassengerDelta += delta;
            if (delta > 0) improved++;
            else if (delta < 0) worse++;
            else equal++;
            if (standard.completed) standardComplete++;
            if (policy.completed) policyComplete++;
            standardTicks += standard.completedTicks;
            policyTicks += policy.completedTicks;

            String pathway = explanatoryPathway(standard, policy, signature);
            pathwayCounts.merge(pathway, 1, Integer::sum);
            String transition = standard.residualStallFamily + " -> " +
                    policy.residualStallFamily;
            transitionCounts.merge(transition, 1, Integer::sum);
        }

        System.out.println();
        System.out.println("============================================================");
        System.out.println("EXPERIMENT 61 AIRLINE POLICY ABSTRACTION READINESS AUDIT");
        System.out.println("============================================================");
        System.out.println("Operational policy A: REFERENCE DETERMINISTIC ADMISSION ORDER");
        System.out.println("Operational policy B: TWO-ROW RISK-DISPERSION ADMISSION ORDER");
        System.out.println("Human behaviour properties introduced: false");
        System.out.println("Asynchronous tick model introduced: false");
        System.out.println("Movement architecture changed: false");
        System.out.println("------------------------------------------------------------");
        System.out.println("Scenario pairs: " + signatures.size());
        System.out.println("Improved/equal/worse under policy B: " +
                improved + "/" + equal + "/" + worse);
        System.out.println("Aggregate seated-passenger delta: " +
                String.format(Locale.ROOT, "%+d", netPassengerDelta));
        System.out.println("Complete cabins policy A/policy B: " +
                standardComplete + "/" + policyComplete);
        System.out.println("Aggregate ticks policy A/policy B: " +
                standardTicks + "/" + policyTicks);
        System.out.println("Pathway distribution: " + pathwayCounts);
        System.out.println("Residual-transition distribution: " + transitionCounts);
        System.out.println("------------------------------------------------------------");
        System.out.println("SME FEEDBACK CONTRACT");
        System.out.println("1. Policy identity and exact deterministic admission transformation.");
        System.out.println("2. Cabin completion, passengers seated and elapsed simulation ticks.");
        System.out.println("3. Improved, equal and worse paired outcomes.");
        System.out.println("4. Residual bottleneck family and topology transition.");
        System.out.println("5. Dependency regions, confirmation, competition and intervention evidence.");
        System.out.println("6. Critical blocker, prerequisite and yield-space explanation when incomplete.");
        System.out.println("7. Separation of policy effect from later human-behaviour assumptions.");
        System.out.println("8. Replay identity sufficient for later asynchronous/stochastic comparison.");
        System.out.println("------------------------------------------------------------");
        System.out.println("READINESS CHECK deterministicPolicyLayerIsolated=true");
        System.out.println("READINESS CHECK pairedOperationalFeedbackAvailable=true");
        System.out.println("READINESS CHECK explanatoryDiagnosticsPreserved=true");
        System.out.println("READINESS CHECK humanBehaviourLayerDeferred=true");
        System.out.println("READINESS CHECK asynchronousExecutionLayerDeferred=true");
        System.out.println("READINESS CHECK movementArchitectureFrozen=true");
        System.out.println("READINESS RESULT policyAbstractionReadyForSMEReview=true");
        System.out.println("Interpretation boundary: Experiment 61 does not claim that the " +
                "risk-dispersion dataset is an airline-approved boarding policy. " +
                "It demonstrates that admission-order policies can be represented, " +
                "paired and explained before behavioural properties or asynchronous " +
                "execution are introduced. Airline and behavioural SMEs remain " +
                "responsible for defining operationally valid policy and human models.");
        System.out.println("============================================================");
    }

    static String experiment60FullMinimalCorePathway(
            ScenarioResult standard,
            ScenarioResult dispersion,
            AdmissionPerturbationSignature signature) {

        int delta = dispersion.passengersSeated - standard.passengersSeated;
        boolean perturbed =
                (signature.leftDifferingPositions +
                 signature.rightDifferingPositions) > 0;
        String standardFamily = standard.residualStallFamily;
        String dispersionFamily = dispersion.residualStallFamily;
        boolean sameFamily = Objects.equals(standardFamily, dispersionFamily);
        boolean standardComplete = "COMPLETE_CABIN".equals(standardFamily);
        boolean dispersionComplete = "COMPLETE_CABIN".equals(dispersionFamily);

        if (delta < 0) {
            return "P5_REGRESSION";
        }
        if (delta > 0) {
            if (!sameFamily && dispersionComplete) {
                return "P4_SUCCESSFUL_TOPOLOGY_RELEASE";
            }
            if (!sameFamily) {
                return "P3_PARTIAL_TOPOLOGY_IMPROVEMENT";
            }
            return "P3_GAIN_WITHOUT_FAMILY_CHANGE";
        }
        if (!sameFamily) {
            return "P2_TOPOLOGY_CHANGED_WITHOUT_GAIN";
        }
        if (standardComplete && dispersionComplete) {
            return perturbed ?
                    "P1_PERTURBED_COMPLETE_STABILITY" :
                    "P0_UNPERTURBED_COMPLETE_STABILITY";
        }
        return "P2_RESIDUAL_TOPOLOGY_PRESERVED";
    }

    static void printExperiment60MinimalCoreIrreducibilityAudit(
            List<ScenarioResult> results,
            List<AdmissionPerturbationSignature> signatures) {

        System.out.println();
        System.out.println("============================================================");
        System.out.println("EXPERIMENT 60 MINIMAL EXPLANATORY CORE IRREDUCIBILITY AUDIT");
        System.out.println("============================================================");
        System.out.println("Baseline: Experiment 56 full explanatory pathway assignment.");
        System.out.println("Validated full core: DIFFERING_POSITIONS, PASSENGER_DELTA, " +
                "RESIDUAL_STALL_FAMILY.");
        System.out.println("Method: confirm full-core reconstruction, then withhold one " +
                "core category at a time.");
        System.out.println("Replay-only audit: no new independent evidence.");

        String[] coreCategories = {
            "DIFFERING_POSITIONS",
            "PASSENGER_DELTA",
            "RESIDUAL_STALL_FAMILY"
        };

        int fullCoreMatches = 0;
        int fullCoreIntegrityAgreements = 0;
        List<Integer> fullCoreMismatchScenarios = new ArrayList<>();

        for (int pair = 0; pair < signatures.size(); pair++) {
            ScenarioResult standard = results.get(pair * 2);
            ScenarioResult dispersion = results.get(pair * 2 + 1);
            AdmissionPerturbationSignature signature = signatures.get(pair);
            String baseline = explanatoryPathway(standard, dispersion, signature);
            String reconstructed = experiment60FullMinimalCorePathway(
                    standard, dispersion, signature);
            boolean same = baseline.equals(reconstructed);
            boolean integrityAgreement =
                    pathwayIntegrityPasses(baseline, standard, dispersion) ==
                    pathwayIntegrityPasses(reconstructed, standard, dispersion);
            if (same) fullCoreMatches++;
            else fullCoreMismatchScenarios.add(standard.scenarioNumber);
            if (integrityAgreement) fullCoreIntegrityAgreements++;
        }

        System.out.println("------------------------------------------------------------");
        System.out.println("FULL THREE-CATEGORY CORE CONFIRMATION");
        System.out.println("Exact baseline reconstructions: " + fullCoreMatches + "/" +
                signatures.size());
        System.out.println("Integrity agreements: " + fullCoreIntegrityAgreements + "/" +
                signatures.size());
        System.out.println("Full-core mismatch scenarios: " +
                (fullCoreMismatchScenarios.isEmpty() ? "NONE" :
                        fullCoreMismatchScenarios));

        int indispensableCategories = 0;
        boolean allBaselinesRetained = true;
        boolean everyAblationCreatesAmbiguity = true;

        System.out.println("------------------------------------------------------------");
        System.out.println("SINGLE-CATEGORY CORE ABLATIONS");

        for (String category : coreCategories) {
            int uniqueExact = 0;
            int ambiguous = 0;
            int baselineRetained = 0;
            int maximumCandidates = 0;
            List<Integer> ambiguousScenarios = new ArrayList<>();

            for (int pair = 0; pair < signatures.size(); pair++) {
                ScenarioResult standard = results.get(pair * 2);
                ScenarioResult dispersion = results.get(pair * 2 + 1);
                AdmissionPerturbationSignature signature = signatures.get(pair);
                String baseline = explanatoryPathway(standard, dispersion, signature);
                Set<String> possible = possiblePathwaysWhenWithheld(
                        category, standard, dispersion, signature);

                maximumCandidates = Math.max(maximumCandidates, possible.size());
                if (possible.contains(baseline)) baselineRetained++;
                if (possible.size() == 1 && possible.contains(baseline)) {
                    uniqueExact++;
                } else {
                    ambiguous++;
                    ambiguousScenarios.add(standard.scenarioNumber);
                }
            }

            boolean indispensable = ambiguous > 0;
            if (indispensable) indispensableCategories++;
            else everyAblationCreatesAmbiguity = false;
            if (baselineRetained != signatures.size()) allBaselinesRetained = false;

            System.out.println(
                    "CORE_ABLATION " + category +
                    " | uniqueExact=" + uniqueExact + "/" + signatures.size() +
                    " | ambiguous=" + ambiguous +
                    " | baselineRetained=" + baselineRetained + "/" +
                    signatures.size() +
                    " | maximumCandidatePathways=" + maximumCandidates +
                    " | indispensable=" + indispensable +
                    " | ambiguousScenarios=" +
                    (ambiguousScenarios.isEmpty() ? "NONE" : ambiguousScenarios)
            );
        }

        boolean fullCoreReconfirmed =
                fullCoreMatches == signatures.size() &&
                fullCoreIntegrityAgreements == signatures.size();
        boolean minimalCoreIrreducible =
                fullCoreReconfirmed &&
                indispensableCategories == coreCategories.length &&
                everyAblationCreatesAmbiguity &&
                allBaselinesRetained;

        System.out.println("------------------------------------------------------------");
        System.out.println("Core categories audited: " + coreCategories.length);
        System.out.println("Categories producing ambiguity when withheld: " +
                indispensableCategories + "/" + coreCategories.length);
        System.out.println("IRREDUCIBILITY CHECK fullCoreReconfirmed=" +
                fullCoreReconfirmed);
        System.out.println("IRREDUCIBILITY CHECK everySingleAblationAmbiguous=" +
                everyAblationCreatesAmbiguity);
        System.out.println("IRREDUCIBILITY CHECK allBaselinePathwaysRetained=" +
                allBaselinesRetained);
        System.out.println("IRREDUCIBILITY CHECK movementArchitectureFrozen=true");
        System.out.println("IRREDUCIBILITY RESULT minimalCoreIrreducible=" +
                minimalCoreIrreducible);
        System.out.println(
                "Interpretation boundary: irreducible means that no one of the " +
                "three retained categories can be removed without creating at " +
                "least one ambiguous pathway assignment in this fixed holdout. " +
                "It does not establish a universal minimum for future datasets, " +
                "and the wider diagnostic evidence remains necessary for " +
                "mechanism explanation, corroboration and scientific audit."
        );
        System.out.println("============================================================");
    }

    static void printCounterfactualQueueEvidence(
            WholeCabinHybridBoardingSimulation standard,
            WholeCabinHybridBoardingSimulation dispersion,
            WholeCabinHybridBoardingSimulation counterfactual) {

        System.out.println();
        System.out.println("============================================================");
        System.out.println("EXPERIMENT 47 SINGLE-SWAP COUNTERFACTUAL EVIDENCE");
        System.out.println("============================================================");

        List<Integer> standardRight =
                new ArrayList<>(standard.right.entryQueue);
        List<Integer> dispersionRight =
                new ArrayList<>(dispersion.right.entryQueue);
        List<Integer> counterfactualRight =
                new ArrayList<>(counterfactual.right.entryQueue);

        int p283Standard = standardRight.indexOf(283) + 1;
        int p283Dispersion = dispersionRight.indexOf(283) + 1;
        int p283Counterfactual = counterfactualRight.indexOf(283) + 1;
        int p97Standard = standardRight.indexOf(97) + 1;
        int p97Dispersion = dispersionRight.indexOf(97) + 1;
        int p97Counterfactual = counterfactualRight.indexOf(97) + 1;

        System.out.println("P283 positions | standard=" + p283Standard +
                " | dispersion=" + p283Dispersion +
                " | counterfactual=" + p283Counterfactual);
        System.out.println("P97 positions  | standard=" + p97Standard +
                " | dispersion=" + p97Dispersion +
                " | counterfactual=" + p97Counterfactual);

        int differencesFromDispersion = 0;
        int differencesFromStandard = 0;
        for (int i = 0; i < counterfactualRight.size(); i++) {
            if (!counterfactualRight.get(i).equals(dispersionRight.get(i))) {
                differencesFromDispersion++;
            }
            if (!counterfactualRight.get(i).equals(standardRight.get(i))) {
                differencesFromStandard++;
            }
        }

        System.out.println("Counterfactual differing positions vs dispersion: " +
                differencesFromDispersion);
        System.out.println("Counterfactual differing positions vs standard: " +
                differencesFromStandard);
        System.out.println("Counterfactual change restricted to P283/P97 swap: " +
                (differencesFromDispersion == 2));
        System.out.println("============================================================");
        System.out.println();
    }

    static void printCounterfactualComparison(
            ScenarioResult standard,
            ScenarioResult dispersion,
            ScenarioResult counterfactual) {

        System.out.println();
        System.out.println("============================================================");
        System.out.println("EXPERIMENT 47 COUNTERFACTUAL OUTCOME COMPARISON");
        System.out.println("============================================================");
        System.out.println("Standard seated: " +
                standard.passengersSeated + "/" + standard.passengers);
        System.out.println("Dispersion seated: " +
                dispersion.passengersSeated + "/" + dispersion.passengers);
        System.out.println("Single-swap counterfactual seated: " +
                counterfactual.passengersSeated + "/" + counterfactual.passengers);
        System.out.println("Counterfactual delta vs dispersion: " +
                String.format(Locale.ROOT, "%+d",
                        counterfactual.passengersSeated -
                        dispersion.passengersSeated));
        System.out.println("Counterfactual delta vs standard: " +
                String.format(Locale.ROOT, "%+d",
                        counterfactual.passengersSeated -
                        standard.passengersSeated));
        System.out.println("Counterfactual residual family: " +
                counterfactual.residualStallFamily);
        System.out.println("Counterfactual dependency length: " +
                counterfactual.longestDependencyChainLength);
        System.out.println("============================================================");
    }

    static void printAdmissionOrderDivergence(
            WholeCabinHybridBoardingSimulation standard,
            WholeCabinHybridBoardingSimulation dispersion) {

        System.out.println();
        System.out.println("============================================================");
        System.out.println("EXPERIMENT 47 SOURCE DIVERGENCE REFERENCE");
        System.out.println("============================================================");

        compareAisleAdmissionOrder(
                AisleSide.LEFT,
                standard,
                dispersion
        );
        compareAisleAdmissionOrder(
                AisleSide.RIGHT,
                standard,
                dispersion
        );

        System.out.println("Critical Experiment 49 passengers:");
        int[] criticalIds = {32, 342, 54, 283, 208};
        for (int passengerId : criticalIds) {
            printCriticalPassengerPosition(
                    passengerId,
                    standard,
                    dispersion
            );
        }
        System.out.println("============================================================");
        System.out.println();
    }

    static void compareAisleAdmissionOrder(
            AisleSide side,
            WholeCabinHybridBoardingSimulation standard,
            WholeCabinHybridBoardingSimulation dispersion) {

        AisleContext standardContext =
                side == AisleSide.LEFT ? standard.left : standard.right;
        AisleContext dispersionContext =
                side == AisleSide.LEFT ? dispersion.left : dispersion.right;

        List<Integer> standardOrder =
                new ArrayList<>(standardContext.entryQueue);
        List<Integer> dispersionOrder =
                new ArrayList<>(dispersionContext.entryQueue);

        int limit = Math.min(standardOrder.size(), dispersionOrder.size());
        int firstDivergence = -1;
        for (int index = 0; index < limit; index++) {
            if (!standardOrder.get(index).equals(dispersionOrder.get(index))) {
                firstDivergence = index;
                break;
            }
        }

        System.out.println(side + " aisle queue length: standard=" +
                standardOrder.size() + " | dispersion=" +
                dispersionOrder.size());

        if (firstDivergence < 0 &&
                standardOrder.size() == dispersionOrder.size()) {
            System.out.println(side +
                    " aisle first divergence: NONE");
            return;
        }

        if (firstDivergence < 0) {
            firstDivergence = limit;
        }

        System.out.println(side +
                " aisle first divergence position: " +
                (firstDivergence + 1));

        int start = Math.max(0, firstDivergence - 3);
        int end = Math.min(
                Math.max(standardOrder.size(), dispersionOrder.size()),
                firstDivergence + 8
        );

        System.out.println(side + " aisle divergence window:");
        for (int index = start; index < end; index++) {
            String standardPassenger =
                    index < standardOrder.size()
                            ? describeAdmissionPassenger(
                                    standard,
                                    standardOrder.get(index))
                            : "NONE";
            String dispersionPassenger =
                    index < dispersionOrder.size()
                            ? describeAdmissionPassenger(
                                    dispersion,
                                    dispersionOrder.get(index))
                            : "NONE";
            String marker =
                    standardPassenger.equals(dispersionPassenger)
                            ? " "
                            : "*";
            System.out.println(
                    marker + " position " + (index + 1) +
                    " | standard=" + standardPassenger +
                    " | dispersion=" + dispersionPassenger
            );
        }
    }

    static String describeAdmissionPassenger(
            WholeCabinHybridBoardingSimulation simulation,
            int passengerId) {

        Passenger passenger = simulation.passengers.get(passengerId);
        if (passenger == null) {
            return "P" + passengerId + "(UNKNOWN)";
        }

        boolean protectedRearBoundary =
                passenger.row >= Math.max(0, simulation.rows - 2) &&
                simulation.blockerPotentialForSeat(
                        passenger.seatIndex) > 0;

        return "P" + passenger.id +
                "(" + passenger.seatName() +
                "/" + passenger.servingAisle +
                "/protected=" + protectedRearBoundary + ")";
    }

    static void printCriticalPassengerPosition(
            int passengerId,
            WholeCabinHybridBoardingSimulation standard,
            WholeCabinHybridBoardingSimulation dispersion) {

        Passenger passenger = standard.passengers.get(passengerId);
        if (passenger == null) {
            System.out.println("P" + passengerId +
                    ": not present in isolated manifest");
            return;
        }

        AisleContext standardContext =
                passenger.servingAisle == AisleSide.LEFT
                        ? standard.left : standard.right;
        AisleContext dispersionContext =
                passenger.servingAisle == AisleSide.LEFT
                        ? dispersion.left : dispersion.right;

        List<Integer> standardOrder =
                new ArrayList<>(standardContext.entryQueue);
        List<Integer> dispersionOrder =
                new ArrayList<>(dispersionContext.entryQueue);

        int standardPosition =
                standardOrder.indexOf(passengerId) + 1;
        int dispersionPosition =
                dispersionOrder.indexOf(passengerId) + 1;

        System.out.println(
                describeAdmissionPassenger(standard, passengerId) +
                " | standardPosition=" + standardPosition +
                " | dispersionPosition=" + dispersionPosition +
                " | displacement=" +
                String.format(
                        Locale.ROOT,
                        "%+d",
                        dispersionPosition - standardPosition
                )
        );
    }

    static ScenarioConfig randomScenarioConfig(
            int scenarioNumber,
            Random experimentRandom) {

        // NEW EXPERIMENT HARNESS:
        // This is not part of the original movement backbone. It exists so the same
        // preserved engine can be tested across many domain configurations without
        // manual intervention.

        CabinConfiguration[] cabins = CabinConfiguration.values();
        CabinConfiguration cabin =
                cabins[experimentRandom.nextInt(cabins.length)];

        double occupancy =
                OCCUPANCY_OPTIONS[
                        experimentRandom.nextInt(OCCUPANCY_OPTIONS.length)
                ];

        int[] headway =
                HEADWAY_OPTIONS[
                        experimentRandom.nextInt(HEADWAY_OPTIONS.length)
                ];

        long scenarioSeed = experimentRandom.nextLong();

        return new ScenarioConfig(
                scenarioNumber,
                scenarioSeed,
                cabin,
                occupancy,
                headway[0],
                headway[1],
                true,
                true,
                true,
                true,
                true,
                true,
                DatasetMode.STANDARD_DETERMINISTIC_DATASET
        );
    }

    static ScenarioConfig pairedConfig(
            ScenarioConfig original,
            DatasetMode datasetMode) {

        return new ScenarioConfig(
                original.scenarioNumber,
                original.seed,
                original.cabin,
                original.occupancyRate,
                original.minEntryHeadway,
                original.maxEntryHeadway,
                true,
                true,
                true,
                true,
                true,
                true,
                datasetMode
        );
    }

    void prepareAdmissionQueues() {
        if (!passengers.isEmpty()) {
            return;
        }
        createManifest();
        if (config.datasetMode == DatasetMode.FINAL_TWO_ROW_RISK_DISPERSION_DATASET) {
            buildFinalTwoRowRiskDispersionQueues();
        } else if (config.datasetMode ==
                DatasetMode.FINAL_TWO_ROW_SAME_ROW_PRESERVATION_DATASET) {
            buildSameRowProtectedPreservationQueues();
        } else {
            buildRandomAisleQueues();
        }
    }

    ScenarioResult runScenario() {
        prepareAdmissionQueues();

        while (!allPassengersSeated() && currentTick < MAX_TICKS) {
            currentTick++;

            advanceSeatEvent(left);
            advanceSeatEvent(right);

            detectAndStartSeatEventsWithMiddleBankArbitration();

            if (pendingClusteredCandidateId != EMPTY &&
                    currentTick > lastProtectionTick + 1) {
                releaseImminentWindowsWithoutEvent++;
                pendingClusteredCandidateId = EMPTY;
            }

            synchronousAisleMove(left);
            synchronousAisleMove(right);

            insertPassengerIfPossible(left);
            insertPassengerIfPossible(right);

            updateWaitingTicks();
            updateAisleMetrics(left);
            updateAisleMetrics(right);
            updateProgressWatchdog();
            updateGatekeeperEpisodes();
            closeUnseenDependencyRegions();

            if (VERBOSE_TICK_OUTPUT) {
                printTickSummary();
            }

            if (stallDetected) {
                if (PRINT_STALL_DIAGNOSTICS) {
                    printStallDiagnostics();
                }
                break;
            }
        }

        return buildScenarioResult();
    }

    void createManifest() {
        // Experiment 11 preserves the standard deterministic passenger manifest.
        // Only narrow, pre-run queue perturbations differ between paired modes.
        createStandardManifest();
    }

    void createStandardManifest() {
        List<int[]> allSeats = allSeatCoordinates();
        Collections.shuffle(allSeats, random);
        createPassengersFromCoordinates(allSeats);
    }

    void createDualAisleDependencyTopologyManifest() {
        List<int[]> allSeats = allSeatCoordinates();
        final Map<String, Double> tie = new HashMap<>();
        for (int[] coordinate : allSeats) {
            tie.put(coordinate[0] + ":" + coordinate[1], random.nextDouble());
        }

        final int leftTargetRow = Math.max(2, rows / 3);
        final int rightTargetRow = Math.min(rows - 3, (rows * 2) / 3);

        allSeats.sort((a, b) -> {
            int scoreA = dualAisleTopologyScore(a[0], a[1], leftTargetRow, rightTargetRow);
            int scoreB = dualAisleTopologyScore(b[0], b[1], leftTargetRow, rightTargetRow);
            if (scoreA != scoreB) {
                return Integer.compare(scoreB, scoreA);
            }
            return Double.compare(
                    tie.get(a[0] + ":" + a[1]),
                    tie.get(b[0] + ":" + b[1])
            );
        });
        createPassengersFromCoordinates(allSeats);
    }

    int dualAisleTopologyScore(
            int row,
            int seatIndex,
            int leftTargetRow,
            int rightTargetRow) {

        AisleSide naturalSide = naturalTopologyAisle(seatIndex);
        int targetRow = naturalSide == AisleSide.LEFT
                ? leftTargetRow
                : rightTargetRow;
        int distance = Math.abs(row - targetRow);
        int separatedZoneScore = Math.max(0, 180 - distance * 24);
        int blockerDepthScore = blockerPotentialForSeat(seatIndex) * 70;
        int aisleSeparationBonus = naturalSide == AisleSide.LEFT ? 13 : 17;
        return separatedZoneScore + blockerDepthScore + aisleSeparationBonus;
    }

    AisleSide naturalTopologyAisle(int seatIndex) {
        if (seatIndex <= config.cabin.leftOuterEnd) {
            return AisleSide.LEFT;
        }
        if (seatIndex >= config.cabin.rightOuterStart) {
            return AisleSide.RIGHT;
        }
        int middleOffsetFromLeft = seatIndex - config.cabin.leftOuterEnd;
        int middleOffsetFromRight = config.cabin.rightOuterStart - seatIndex;
        return middleOffsetFromLeft <= middleOffsetFromRight
                ? AisleSide.LEFT
                : AisleSide.RIGHT;
    }

    List<int[]> allSeatCoordinates() {
        List<int[]> allSeats = new ArrayList<>();
        for (int row = 0; row < rows; row++) {
            for (int seatIndex = 0; seatIndex < seatLetters.length; seatIndex++) {
                allSeats.add(new int[]{row, seatIndex});
            }
        }
        return allSeats;
    }

    void createPassengersFromCoordinates(List<int[]> coordinates) {
        int targetPassengerCount = (int) Math.round(config.cabin.totalSeats() * config.occupancyRate);
        for (int id = 0; id < targetPassengerCount; id++) {
            int[] coordinate = coordinates.get(id);
            int row = coordinate[0];
            int seatIndex = coordinate[1];
            Passenger passenger = new Passenger(id, row, seatLetters[seatIndex], seatIndex, chooseServingAisle(seatIndex));
            passenger.lastStateChangeTick = 0;
            passengers.put(id, passenger);
        }
    }

    int rowZoneConcentrationScore(int row, int centreA, int centreB) {
        int distance = Math.min(Math.abs(row - centreA), Math.abs(row - centreB));
        return Math.max(0, 120 - distance * 18);
    }

    int blockerPotentialForSeat(int seatIndex) {
        if (seatIndex == 0 || seatIndex == seatLetters.length - 1) return 3;
        if (seatIndex == 1 || seatIndex == seatLetters.length - 2) return 2;
        if (seatIndex == config.cabin.leftOuterEnd + 1 || seatIndex == config.cabin.rightOuterStart - 1) return 2;
        return 1;
    }

    AisleSide chooseServingAisle(int seatIndex) {
        if (seatIndex <= config.cabin.leftOuterEnd) {
            return AisleSide.LEFT;
        }

        if (seatIndex >= config.cabin.rightOuterStart) {
            return AisleSide.RIGHT;
        }

        // Experiment 49 preserves the standard seeded middle-bank assignment
        // in both paired modes. Aisle assignment is therefore not perturbed.
        return random.nextBoolean() ? AisleSide.LEFT : AisleSide.RIGHT;
    }

    void buildRandomAisleQueues() {
        List<Integer> leftIds = new ArrayList<>();
        List<Integer> rightIds = new ArrayList<>();
        for (Passenger passenger : passengers.values()) {
            (passenger.servingAisle == AisleSide.LEFT ? leftIds : rightIds).add(passenger.id);
        }
        Collections.shuffle(leftIds, random);
        Collections.shuffle(rightIds, random);
        left.entryQueue.addAll(leftIds);
        right.entryQueue.addAll(rightIds);
    }

    void buildFinalTwoRowRiskDispersionQueues() {
        populateFinalTwoRowRiskDispersionQueue(left);
        populateFinalTwoRowRiskDispersionQueue(right);
    }

    void populateFinalTwoRowRiskDispersionQueue(AisleContext context) {
        List<Integer> seededOrder = new ArrayList<>();
        for (Passenger passenger : passengers.values()) {
            if (passenger.servingAisle == context.side) {
                seededOrder.add(passenger.id);
            }
        }
        Collections.shuffle(seededOrder, random);

        Deque<Integer> deferredProtected = new ArrayDeque<>();
        boolean previousEmittedWasProtected = false;

        for (Integer passengerId : seededOrder) {
            Passenger passenger = passengers.get(passengerId);
            boolean protectedRearBoundary =
                    passenger.row >= Math.max(0, rows - 2) &&
                    blockerPotentialForSeat(passenger.seatIndex) > 0;

            if (protectedRearBoundary && previousEmittedWasProtected) {
                deferredProtected.addLast(passengerId);
                continue;
            }

            context.entryQueue.addLast(passengerId);
            previousEmittedWasProtected = protectedRearBoundary;

            if (!protectedRearBoundary && !deferredProtected.isEmpty()) {
                context.entryQueue.addLast(deferredProtected.removeFirst());
                previousEmittedWasProtected = true;
            }
        }

        while (!deferredProtected.isEmpty()) {
            context.entryQueue.addLast(deferredProtected.removeFirst());
        }
    }

    void buildSameRowProtectedPreservationQueues() {
        populateSameRowProtectedPreservationQueue(left);
        populateSameRowProtectedPreservationQueue(right);
    }

    void populateSameRowProtectedPreservationQueue(AisleContext context) {
        List<Integer> seededOrder = new ArrayList<>();
        for (Passenger passenger : passengers.values()) {
            if (passenger.servingAisle == context.side) {
                seededOrder.add(passenger.id);
            }
        }
        Collections.shuffle(seededOrder, random);

        Deque<Integer> deferredProtected = new ArrayDeque<>();
        Integer previousEmittedPassengerId = null;
        boolean previousEmittedWasProtected = false;

        for (Integer passengerId : seededOrder) {
            Passenger passenger = passengers.get(passengerId);
            boolean protectedRearBoundary =
                    passenger.row >= Math.max(0, rows - 2) &&
                    blockerPotentialForSeat(passenger.seatIndex) > 0;

            boolean sameRowAsPreviousProtected = false;
            if (protectedRearBoundary &&
                    previousEmittedWasProtected &&
                    previousEmittedPassengerId != null) {
                Passenger previous =
                        passengers.get(previousEmittedPassengerId);
                sameRowAsPreviousProtected =
                        previous != null &&
                        previous.row == passenger.row;
            }

            if (protectedRearBoundary &&
                    previousEmittedWasProtected &&
                    !sameRowAsPreviousProtected) {
                deferredProtected.addLast(passengerId);
                continue;
            }

            context.entryQueue.addLast(passengerId);
            previousEmittedPassengerId = passengerId;
            previousEmittedWasProtected = protectedRearBoundary;

            if (!protectedRearBoundary &&
                    !deferredProtected.isEmpty()) {
                int releasedPassengerId =
                        deferredProtected.removeFirst();
                context.entryQueue.addLast(releasedPassengerId);
                previousEmittedPassengerId =
                        releasedPassengerId;
                previousEmittedWasProtected = true;
            }
        }

        while (!deferredProtected.isEmpty()) {
            context.entryQueue.addLast(
                    deferredProtected.removeFirst()
            );
        }
    }

    void buildConfirmatoryCoarseCohortQueues() {
        populateConfirmatoryCoarseCohorts(left);
        populateConfirmatoryCoarseCohorts(right);
        activeCoarseCohort = highestNonEmptyCoarseCohort();
        activeLocalRegion = 1;
        advanceHierarchicalRegionIfComplete();
    }

    int coarseCohortForRow(int row) {
        // Experiment 56 retains equal deterministic thirds.
        // Cohort 0 is front, 1 middle and 2 rear.
        int frontEnd = Math.max(1, rows / 3);
        int middleEnd = Math.max(frontEnd + 1, (rows * 2) / 3);
        middleEnd = Math.min(rows - 1, middleEnd);

        if (row <= frontEnd) return 0;
        if (row <= middleEnd) return 1;
        return 2;
    }

    int localRegionForRow(int row, int cohort) {
        // Local subregions are disabled in the confirmatory replication.
        return 1;
    }

    int queueIndexFor(int cohort, int localRegion) {
        return cohort * 2 + localRegion;
    }

    void populateConfirmatoryCoarseCohorts(AisleContext context) {
        List<Integer> ids = new ArrayList<>();
        for (Passenger passenger : passengers.values()) {
            if (passenger.servingAisle == context.side) {
                ids.add(passenger.id);
            }
        }

        // Seeded shuffle establishes reproducible relative order. The hierarchy
        // changes only which precomputed local region may currently admit.
        Collections.shuffle(ids, random);

        for (Integer passengerId : ids) {
            Passenger passenger = passengers.get(passengerId);
            int cohort = coarseCohortForRow(passenger.row);
            int localRegion = localRegionForRow(passenger.row, cohort);
            int queueIndex = queueIndexFor(cohort, localRegion);
            context.zoneQueues.get(queueIndex).addLast(passengerId);
            context.zoneTotals[queueIndex]++;
        }
    }

    int highestNonEmptyCoarseCohort() {
        for (int cohort = 2; cohort >= 0; cohort--) {
            for (int localRegion = 1; localRegion >= 0; localRegion--) {
                int queueIndex = queueIndexFor(cohort, localRegion);
                if (!left.zoneQueues.get(queueIndex).isEmpty() ||
                        !right.zoneQueues.get(queueIndex).isEmpty()) {
                    return cohort;
                }
            }
        }
        return -1;
    }

    boolean activeConfirmatoryCohortEmpty() {
        if (activeCoarseCohort < 0) {
            return true;
        }
        int queueIndex = queueIndexFor(activeCoarseCohort, 1);
        return left.zoneQueues.get(queueIndex).isEmpty() &&
                right.zoneQueues.get(queueIndex).isEmpty();
    }

    void advanceHierarchicalRegionIfComplete() {
        while (activeCoarseCohort >= 0 && activeConfirmatoryCohortEmpty()) {
            activeCoarseCohort--;
            activeLocalRegion = 1;
        }
    }

    Integer peekCoarseCohortPassenger(AisleContext context) {
        advanceHierarchicalRegionIfComplete();
        if (activeCoarseCohort < 0) {
            return null;
        }
        int queueIndex = queueIndexFor(activeCoarseCohort, activeLocalRegion);
        return context.zoneQueues.get(queueIndex).peekFirst();
    }

    int removeCoarseCohortPassenger(AisleContext context) {
        int queueIndex = queueIndexFor(activeCoarseCohort, activeLocalRegion);
        int passengerId = context.zoneQueues.get(queueIndex).removeFirst();
        advanceHierarchicalRegionIfComplete();
        return passengerId;
    }

    boolean preferredSideCanAdmitNow() {
        AisleContext preferred =
                preferredAdmissionSide == AisleSide.LEFT ? left : right;
        if (preferred.entryCooldown > 0 || preferred.aisle[0] != EMPTY) {
            return false;
        }
        return peekCoarseCohortPassenger(preferred) != null;
    }

    boolean isOuterBankForServingAisle(Passenger passenger) {
        if (passenger.servingAisle == AisleSide.LEFT) {
            return passenger.seatIndex <= config.cabin.leftOuterEnd;
        }
        return passenger.seatIndex >= config.cabin.rightOuterStart;
    }

    int seatDepthFromServingAisle(Passenger passenger) {
        int seatIndex = passenger.seatIndex;
        if (passenger.servingAisle == AisleSide.LEFT) {
            if (seatIndex <= config.cabin.leftOuterEnd) {
                return config.cabin.leftOuterEnd - seatIndex;
            }
            return seatIndex - config.cabin.leftOuterEnd - 1;
        }

        if (seatIndex >= config.cabin.rightOuterStart) {
            return seatIndex - config.cabin.rightOuterStart;
        }
        return config.cabin.rightOuterStart - seatIndex - 1;
    }


    void insertPassengerIfPossible(AisleContext context) {
        // EVOLVED BACKBONE:
        // In the original code, occupied values already existed inside the array.
        // Here passengers are admitted gradually, but insertion still obeys the same
        // local rule: the target position must be empty before occupancy can change.
        if (context.entryCooldown > 0) {
            context.entryCooldown--;
            return;
        }

        if (context.aisle[0] != EMPTY) {
            return;
        }

        if (context.entryQueue.isEmpty()) {
            return;
        }
        int passengerId = context.entryQueue.removeFirst();
        Passenger passenger = passengers.get(passengerId);

        context.aisle[0] = passengerId;
        passenger.state = PassengerState.MOVING_IN_AISLE;
        passenger.aisleEntryTick = currentTick;
        passenger.lastStateChangeTick = currentTick;
        passenger.lastAislePosition = 0;
        passenger.lastMovementTick = currentTick;
        passenger.lastBlockingReason = "NONE";

        int range =
                config.maxEntryHeadway - config.minEntryHeadway + 1;

        context.entryCooldown =
                config.minEntryHeadway + random.nextInt(range);
    }

    void setDirectBlocker(Passenger passenger, int blockerId, String reason) {
        passenger.directBlockingPassengerId = blockerId;
        passenger.lastBlockingReason = reason;
        passenger.blockedTicks++;
        passenger.stationaryTicks++;
        passenger.lastBlockedTick = currentTick;

        if (passenger.previousDirectBlockingPassengerId != blockerId) {
            passenger.blockerChangeCount++;
            passenger.previousDirectBlockingPassengerId = blockerId;
        }

        recordBlockingReason(reason);
    }

    void clearDirectBlocker(Passenger passenger) {
        passenger.directBlockingPassengerId = EMPTY;
        passenger.lastBlockingReason = "NONE";
    }

    Passenger nextEligibleWaitingRowPassenger(
            AisleContext context) {

        if (context.activeEvent != null) {
            return null;
        }

        for (int position = 0;
             position < context.aisle.length;
             position++) {

            int passengerId = context.aisle[position];

            if (passengerId == EMPTY) {
                continue;
            }

            Passenger passenger = passengers.get(passengerId);

            if (passenger != null &&
                    passenger.state == PassengerState.WAITING_AT_ROW &&
                    position == passenger.row) {
                return passenger;
            }
        }

        return null;
    }

    boolean occupantCanMoveForwardThisTick(
            int[] snapshot,
            int occupantTile) {

        int occupantId = snapshot[occupantTile];

        if (occupantId == EMPTY) {
            return true;
        }

        Passenger occupant = passengers.get(occupantId);

        if (occupant == null ||
                occupant.state != PassengerState.MOVING_IN_AISLE) {
            return false;
        }

        if (occupantTile >= occupant.row) {
            return false;
        }

        int nextTile = occupantTile + 1;

        return nextTile < snapshot.length &&
                snapshot[nextTile] == EMPTY;
    }

    ReleaseImminentWindow buildReleaseImminentWindow(
            AisleContext context,
            int[] snapshot) {

        // Experiment 11 disables Experiment 10 tile protection.
        // The paired boolean now activates dependency-aware event precedence.
        return null;
    }

    List<Integer> currentBlockerIds(Passenger passenger) {
        List<Integer> ids = new ArrayList<>();
        for (int seatIndex : findBlockingSeatIndices(passenger)) {
            int id = seats[passenger.row][seatIndex];
            if (id != EMPTY) {
                ids.add(id);
            }
        }
        return ids;
    }

    boolean precedenceEventCanStartNow(
            AisleContext context,
            Passenger passenger) {

        List<Integer> blockers = currentBlockerIds(passenger);

        for (int offset = 1; offset <= blockers.size(); offset++) {
            int tile = passenger.row + offset;

            if (tile >= context.aisle.length) {
                precedenceRejectedBoundary++;
                return false;
            }

            if (context.aisle[tile] != EMPTY) {
                precedenceRejectedOccupiedSpace++;
                return false;
            }
        }

        if (isMiddleBankSeat(passenger.seatIndex)) {
            int holder = middleBankReservedByPassenger[passenger.row];
            if (holder != EMPTY && holder != passenger.id) {
                precedenceRejectedReservation++;
                return false;
            }
        }

        return true;
    }

    int directBlockedRowEventsReleased(
            AisleContext context,
            Passenger gatekeeper) {

        int gatekeeperTile =
                gatekeeper.lastAislePosition;
        int count = 0;

        for (int position = 0;
             position < context.aisle.length;
             position++) {

            int passengerId =
                    context.aisle[position];

            if (passengerId == EMPTY ||
                    passengerId == gatekeeper.id) {
                continue;
            }

            Passenger waiting =
                    passengers.get(passengerId);

            if (waiting == null ||
                    waiting.state !=
                            PassengerState.WAITING_AT_ROW ||
                    waiting.lastAislePosition !=
                            waiting.row) {
                continue;
            }

            int blockers =
                    currentBlockerIds(waiting).size();

            for (int offset = 1;
                 offset <= blockers;
                 offset++) {

                if (waiting.row + offset ==
                        gatekeeperTile) {
                    count++;
                    break;
                }
            }
        }

        return count;
    }

    int queuedPassengersBehind(
            AisleContext context,
            Passenger passenger) {

        int count = 0;

        for (int position = 0;
             position < passenger.lastAislePosition;
             position++) {

            if (context.aisle[position] != EMPTY) {
                count++;
            }
        }

        return count;
    }

    Passenger firstValidGatekeeperPreview(
            AisleContext context,
            Passenger normalCandidate) {

        LinkedHashSet<Integer> visited =
                new LinkedHashSet<>();
        Passenger current = normalCandidate;

        while (current != null &&
                visited.add(current.id)) {

            List<Integer> blockers =
                    currentBlockerIds(current);

            if (blockers.isEmpty()) {
                return null;
            }

            Passenger successor = null;

            for (int offset = 1;
                 offset <= blockers.size();
                 offset++) {

                int tile = current.row + offset;

                if (tile >= context.aisle.length) {
                    return null;
                }

                int occupantId =
                        context.aisle[tile];

                if (occupantId == EMPTY) {
                    continue;
                }

                Passenger occupant =
                        passengers.get(occupantId);

                if (occupant != null &&
                        occupant.state ==
                                PassengerState.WAITING_AT_ROW &&
                        occupant.lastAislePosition ==
                                occupant.row) {
                    successor = occupant;
                }

                break;
            }

            if (successor == null) {
                return null;
            }

            current = successor;

            if (precedenceEventCanStartNow(
                    context,
                    current)) {
                return current;
            }
        }

        return null;
    }

    Passenger scoredGatekeeperCandidate(
            AisleContext context,
            Passenger normalCandidate) {

        Passenger firstValid =
                firstValidGatekeeperPreview(
                        context,
                        normalCandidate
                );

        PriorityGatekeeper best = null;

        for (int position = 0;
             position < context.aisle.length;
             position++) {

            int passengerId =
                    context.aisle[position];

            if (passengerId == EMPTY) {
                continue;
            }

            Passenger candidate =
                    passengers.get(passengerId);

            if (candidate == null ||
                    candidate.state !=
                            PassengerState.WAITING_AT_ROW ||
                    candidate.lastAislePosition !=
                            candidate.row ||
                    candidate.id ==
                            normalCandidate.id) {
                continue;
            }

            priorityCandidatesEvaluated++;

            int directReleases =
                    directBlockedRowEventsReleased(
                            context,
                            candidate
                    );

            if (directReleases == 0 ||
                    !precedenceEventCanStartNow(
                            context,
                            candidate)) {
                continue;
            }

            priorityEligibleGatekeepers++;

            int queuedBehind =
                    queuedPassengersBehind(
                            context,
                            candidate
                    );

            int blockers =
                    currentBlockerIds(
                            candidate).size();

            int dependencyDepth =
                    Math.max(
                            1,
                            candidate.row -
                            normalCandidate.row
                    );

            int score = directReleases;

            PriorityGatekeeper evaluated =
                    new PriorityGatekeeper(
                            candidate,
                            score,
                            directReleases,
                            queuedBehind
                    );

            if (best == null ||
                    evaluated.score > best.score ||
                    (evaluated.score == best.score &&
                     blockers <
                             currentBlockerIds(
                                     best.passenger).size()) ||
                    (evaluated.score == best.score &&
                     blockers ==
                             currentBlockerIds(
                                     best.passenger).size() &&
                     evaluated.passenger.id <
                             best.passenger.id)) {
                best = evaluated;
            }
        }

        if (best == null) {
            return null;
        }

        prioritySelectionsMade++;
        prioritySelectedScoreTotal += best.score;
        priorityHighestSelectedScore =
                Math.max(
                        priorityHighestSelectedScore,
                        best.score
                );
        priorityDirectReleasesTotal +=
                best.directReleases;
        priorityQueuedBehindTotal +=
                best.queuedBehind;

        if (firstValid == null ||
                firstValid.id !=
                        best.passenger.id) {
            prioritySelectionsDifferentFromFirstValid++;
        }

        precedenceCandidatesExamined++;
        precedenceBlockedByRowEvent +=
                best.directReleases;
        precedenceChainsFollowed++;
        precedenceMaximumChainDepth =
                Math.max(
                        precedenceMaximumChainDepth,
                        2
                );
        precedenceSubstitutionsMade++;
        lastPrecedenceChain =
                normalCandidate.id +
                "|" +
                best.passenger.id;

        gatekeeperEpisodes.add(
                new GatekeeperEpisode(
                        nextGatekeeperEpisodeId++,
                        normalCandidate.id,
                        best.passenger.id,
                        context.side,
                        normalCandidate.row + 1,
                        currentTick,
                        currentSeatedCount(),
                        lastPrecedenceChain
                )
        );

        return best.passenger;
    }

    LinkedHashSet<Integer> dependencyRegionMembers(
            AisleContext context, int start, int end) {
        LinkedHashSet<Integer> members = new LinkedHashSet<>();
        for (int tile = Math.max(0, start);
             tile <= Math.min(context.aisle.length - 1, end);
             tile++) {
            int passengerId = context.aisle[tile];
            if (passengerId != EMPTY) {
                members.add(passengerId);
            }
        }
        return members;
    }

    boolean rangesOverlapOrTouch(
            int startA, int endA, int startB, int endB) {
        return startA <= endB + REGION_LOCAL_TILE_MARGIN &&
               startB <= endA + REGION_LOCAL_TILE_MARGIN;
    }

    DependencyRegion matchingDependencyRegion(
            AisleContext context,
            Passenger anchorPassenger,
            int start,
            int end,
            Set<Integer> observedMembers) {

        DependencyRegion best = null;
        int bestSharedMembers = -1;

        for (DependencyRegion region : activeDependencyRegions.values()) {
            if (region.side != context.side) {
                continue;
            }

            int sharedMembers = 0;
            for (int id : observedMembers) {
                if (region.memberPassengerIds.contains(id)) {
                    sharedMembers++;
                }
            }

            boolean sameAnchor =
                    region.anchorPassengerId == anchorPassenger.id;
            boolean localOverlap = rangesOverlapOrTouch(
                    region.startTile, region.endTile, start, end);

            if (!sameAnchor && !(localOverlap && sharedMembers > 0)) {
                continue;
            }

            if (best == null ||
                    sharedMembers > bestSharedMembers ||
                    (sharedMembers == bestSharedMembers &&
                     region.id < best.id)) {
                best = region;
                bestSharedMembers = sharedMembers;
            }
        }

        return best;
    }

    DependencyRegion observeDependencyRegion(AisleContext context, Passenger anchorPassenger,
                                 int start, int end, int occupiedCount) {
        if (!config.dependencyRegionLifecycleEnabled) {
            return null;
        }

        LinkedHashSet<Integer> observedMembers =
                dependencyRegionMembers(context, start, end);

        DependencyRegion region = matchingDependencyRegion(
                context,
                anchorPassenger,
                start,
                end,
                observedMembers
        );

        if (region == null) {
            region = new DependencyRegion(
                    nextDependencyRegionId++,
                    context.side,
                    anchorPassenger.id,
                    start,
                    end,
                    occupiedCount,
                    observedMembers,
                    currentTick
            );
            activeDependencyRegions.put("R" + region.id, region);
            lifecycleRegionsCreated++;
            lastLifecycleEvent = "CREATE:R" + region.id + ":" + context.side +
                    ":anchor=P" + anchorPassenger.id + ":tiles" +
                    (start + 1) + "-" + (end + 1);
        } else {
            region.anchorPassengerId = anchorPassenger.id;
            region.startTile = start;
            region.endTile = end;
            region.occupiedCount = occupiedCount;
            region.memberPassengerIds.clear();
            region.memberPassengerIds.addAll(observedMembers);
            region.lastSeenTick = currentTick;
            region.updates++;
            lifecycleRegionUpdates++;
            lifecycleReconstructionsAvoided++;
            lifecycleLargestLifetime = Math.max(
                    lifecycleLargestLifetime,
                    region.lifetime(currentTick)
            );
            lastLifecycleEvent = "UPDATE:R" + region.id + ":age=" +
                    region.lifetime(currentTick) + ":occupied=" + occupiedCount;
        }

        lifecycleMaxSimultaneousActive = Math.max(
                lifecycleMaxSimultaneousActive,
                activeDependencyRegions.size()
        );
        return region;
    }

    int unresolvedMembersStillLocal(DependencyRegion region) {
        int count = 0;
        int minTile = Math.max(0, region.startTile - REGION_LOCAL_TILE_MARGIN);
        int maxTile = Math.min(rows - 1, region.endTile + REGION_LOCAL_TILE_MARGIN);

        for (int passengerId : region.memberPassengerIds) {
            Passenger passenger = passengers.get(passengerId);
            if (passenger == null || passenger.state == PassengerState.SEATED ||
                    passenger.state == PassengerState.WAITING_TO_ENTER) {
                continue;
            }

            int tile = passenger.lastAislePosition;
            if (tile >= minTile && tile <= maxTile) {
                count++;
            }
        }
        return count;
    }

    void closeUnseenDependencyRegions() {
        if (!config.dependencyRegionLifecycleEnabled ||
                activeDependencyRegions.isEmpty()) {
            return;
        }

        Iterator<Map.Entry<String, DependencyRegion>> iterator =
                activeDependencyRegions.entrySet().iterator();

        while (iterator.hasNext()) {
            DependencyRegion region = iterator.next().getValue();
            if (region.lastSeenTick >= currentTick) {
                continue;
            }

            int stillLocal = unresolvedMembersStillLocal(region);

            // During an active stand-yield-sit-reseat transaction the ordinary
            // cluster detector may not run. Keep the already observed region and
            // update its lifecycle while at least two original members remain
            // unresolved in the same local aisle neighbourhood.
            if (stillLocal >= 2) {
                region.lastSeenTick = currentTick;
                region.occupiedCount = stillLocal;
                region.updates++;
                lifecycleRegionUpdates++;
                lifecycleReconstructionsAvoided++;
                lifecycleLargestLifetime = Math.max(
                        lifecycleLargestLifetime,
                        region.lifetime(currentTick)
                );
                lastLifecycleEvent = "TRACK:R" + region.id + ":age=" +
                        region.lifetime(currentTick) + ":localMembers=" + stillLocal;
                continue;
            }

            lifecycleRegionsDissolved++;
            if (!region.confirmationReached) {
                lifecycleRegionsDissolvedBeforeConfirmation++;
            }
            lifecycleLargestLifetime = Math.max(
                    lifecycleLargestLifetime,
                    region.lifetime(currentTick - 1)
            );
            lastLifecycleEvent = "DISSOLVE:R" + region.id + ":lifetime=" +
                    region.lifetime(currentTick - 1);
            iterator.remove();
        }
    }

    String confirmedRegionStateSignature(
            AisleContext context,
            DependencyRegion region,
            int start,
            int end) {

        StringBuilder signature = new StringBuilder();
        signature.append(context.side).append('|')
                .append(start).append('-').append(end).append('|');

        for (int tile = start; tile <= end; tile++) {
            int passengerId = context.aisle[tile];
            signature.append(tile).append(':').append(passengerId);
            if (passengerId != EMPTY) {
                Passenger p = passengers.get(passengerId);
                if (p != null) {
                    signature.append(':').append(p.state)
                            .append(':').append(p.row)
                            .append(':').append(p.lastAislePosition);
                    if (p.state == PassengerState.WAITING_AT_ROW) {
                        List<Integer> blockers = currentBlockerIds(p);
                        signature.append(":B").append(blockers);
                        for (int offset = 1; offset <= blockers.size(); offset++) {
                            int yieldTile = p.row + offset;
                            signature.append(":Y").append(yieldTile).append('=');
                            signature.append(yieldTile >= context.aisle.length
                                    ? "BOUNDARY" : context.aisle[yieldTile]);
                        }
                        if (isMiddleBankSeat(p.seatIndex)) {
                            signature.append(":R=")
                                    .append(middleBankReservedByPassenger[p.row]);
                        }
                    }
                }
            }
            signature.append(';');
        }

        signature.append("ACTIVE=")
                .append(context.activeEvent == null
                        ? EMPTY : context.activeEvent.target.id);
        return signature.toString();
    }

    Passenger rowBoundedCongestionClusterCandidate(
            AisleContext context,
            Passenger normalCandidate) {

        if (!config.congestionClusteringEnabled || normalCandidate == null) {
            return null;
        }

        // Reactive trigger: the ordinary architecture must first expose a real
        // unresolved row-event bottleneck. Clustering does not predict entry,
        // alter headway, or continuously reorganise ordinary aisle movement.
        if (precedenceEventCanStartNow(context, normalCandidate) ||
                normalCandidate.state != PassengerState.WAITING_AT_ROW ||
                normalCandidate.failedSeatEventStarts <= 0) {
            return null;
        }

        String reason = normalCandidate.lastBlockingReason;
        boolean recognisedBottleneck =
                "WAITING_FOR_SEAT_EVENT".equals(reason) ||
                "ACTIVE_SEAT_EVENT_AHEAD".equals(reason) ||
                "YIELD_TILE_OCCUPIED".equals(reason) ||
                "NEXT_AISLE_TILE_OCCUPIED".equals(reason);
        if (!recognisedBottleneck) {
            return null;
        }

        int anchor = normalCandidate.lastAislePosition;
        if (anchor < 0 || anchor >= context.aisle.length) {
            return null;
        }

        // Build a local dependency region, not merely a perfectly packed run.
        // At most one internal empty tile may separate occupied parts of the
        // same region. The gap is diagnostic only and creates no new movement.
        int start = anchor;
        int gapBudget = CLUSTER_MAX_INTERNAL_GAP;
        for (int tile = anchor - 1; tile >= 0; tile--) {
            if (context.aisle[tile] != EMPTY) {
                start = tile;
                continue;
            }
            if (gapBudget > 0 && tile > 0 && context.aisle[tile - 1] != EMPTY) {
                gapBudget--;
                start = tile - 1;
                tile--;
                continue;
            }
            break;
        }

        int end = anchor;
        gapBudget = CLUSTER_MAX_INTERNAL_GAP;
        for (int tile = anchor + 1; tile < context.aisle.length; tile++) {
            if (context.aisle[tile] != EMPTY) {
                end = tile;
                continue;
            }
            if (gapBudget > 0 && tile + 1 < context.aisle.length &&
                    context.aisle[tile + 1] != EMPTY) {
                gapBudget--;
                end = tile + 1;
                tile++;
                continue;
            }
            break;
        }

        int occupiedCount = 0;
        for (int tile = start; tile <= end; tile++) {
            if (context.aisle[tile] != EMPTY) {
                occupiedCount++;
            }
        }
        if (occupiedCount < CLUSTER_MIN_DEPENDENCY_OCCUPANTS) {
            return null;
        }

        DependencyRegion observedRegion = null;
        if (config.dependencyRegionLifecycleEnabled) {
            observedRegion = observeDependencyRegion(
                    context, normalCandidate, start, end, occupiedCount);
            congestionDependencyRegionsDetected = lifecycleRegionsCreated;
            congestionClustersDetected = lifecycleRegionsCreated;
        } else {
            congestionDependencyRegionsDetected++;
            congestionClustersDetected++;
        }

        if (config.lifecycleConfirmationEnabled && observedRegion != null) {
            int age = observedRegion.lifetime(currentTick);
            if (age < LIFECYCLE_CONFIRMATION_TICKS) {
                lifecycleConfirmationDeferrals++;
                lastCongestionCluster = context.side + ":region=R" +
                        observedRegion.id + ":age=" + age +
                        ":result=AWAITING_LIFECYCLE_CONFIRMATION";
                return null;
            }
            if (!observedRegion.confirmationReached) {
                observedRegion.confirmationReached = true;
                lifecycleRegionsConfirmed++;
                lastLifecycleEvent = "CONFIRM:R" + observedRegion.id +
                        ":age=" + age;
            }
        }

        if (config.hierarchicalRegionPriorityEnabled &&
                priorityCompetitionThisTick &&
                priorityEligibleAisleThisTick != null &&
                context.side != priorityEligibleAisleThisTick &&
                observedRegion != null && observedRegion.confirmationReached) {
            regionPriorityLowerRankDeferrals++;
            lastCongestionCluster = context.side + ":region=R" +
                    observedRegion.id +
                    ":result=LOWER_HIERARCHICAL_PRIORITY_DEFERRED";
            return null;
        }

        String currentEvaluationSignature = null;
        if (config.stateChangeGatingEnabled && observedRegion != null &&
                observedRegion.confirmationReached) {
            currentEvaluationSignature = confirmedRegionStateSignature(
                    context, observedRegion, start, end);

            if (currentEvaluationSignature.equals(
                    observedRegion.lastNegativeEvaluationSignature)) {
                stateChangeEvaluationsSuppressed++;
                lastCongestionCluster = context.side + ":region=R" +
                        observedRegion.id +
                        ":result=UNCHANGED_NEGATIVE_EVALUATION_SUPPRESSED";
                return null;
            }

            if (observedRegion.lastNegativeEvaluationSignature != null) {
                stateChangesReopeningEvaluation++;
                observedRegion.lastNegativeEvaluationSignature = null;
                lastLifecycleEvent = "REOPEN:R" + observedRegion.id +
                        ":age=" + observedRegion.lifetime(currentTick);
            }
        }
        congestionClusterLargestSize = Math.max(
                congestionClusterLargestSize,
                occupiedCount
        );

        Passenger best = null;
        int bestReleasePotential = -1;
        int bestDirectReleases = -1;
        int bestBlockers = Integer.MAX_VALUE;

        for (int tile = start; tile <= end; tile++) {
            int passengerId = context.aisle[tile];
            if (passengerId == EMPTY) {
                continue;
            }

            Passenger candidate = passengers.get(passengerId);
            if (candidate == null ||
                    candidate.id == normalCandidate.id ||
                    candidate.state != PassengerState.WAITING_AT_ROW ||
                    candidate.lastAislePosition != candidate.row) {
                continue;
            }

            congestionClusterCandidatesEvaluated++;

            if (!precedenceEventCanStartNow(context, candidate)) {
                congestionClusterRejectedCandidates++;
                continue;
            }
            congestionClusterExecutableCandidates++;

            int directReleases = directBlockedRowEventsReleased(
                    context,
                    candidate
            );

            // Entrance-side occupied positions inside this dependency region
            // estimate local release potential. No passenger is moved backward,
            // beyond the assigned row, through another passenger, or between aisles.
            int releasePotential = 0;
            for (int behind = start;
                 behind < candidate.lastAislePosition;
                 behind++) {
                if (context.aisle[behind] != EMPTY) {
                    releasePotential++;
                }
            }

            int blockers = currentBlockerIds(candidate).size();

            if (best == null ||
                    directReleases > bestDirectReleases ||
                    (directReleases == bestDirectReleases &&
                     releasePotential > bestReleasePotential) ||
                    (directReleases == bestDirectReleases &&
                     releasePotential == bestReleasePotential &&
                     blockers < bestBlockers) ||
                    (directReleases == bestDirectReleases &&
                     releasePotential == bestReleasePotential &&
                     blockers == bestBlockers &&
                     candidate.id < best.id)) {
                best = candidate;
                bestDirectReleases = directReleases;
                bestReleasePotential = releasePotential;
                bestBlockers = blockers;
            }
        }

        if (best == null) {
            congestionClusterActivationsWithoutIntervention++;
            if (config.stateChangeGatingEnabled && observedRegion != null &&
                    observedRegion.confirmationReached) {
                if (currentEvaluationSignature == null) {
                    currentEvaluationSignature = confirmedRegionStateSignature(
                            context, observedRegion, start, end);
                }
                observedRegion.lastNegativeEvaluationSignature =
                        currentEvaluationSignature;
            }
            lastCongestionCluster =
                    context.side + ":tiles" + (start + 1) + "-" + (end + 1) +
                    ":occupied=" + occupiedCount +
                    ":anchor=P" + normalCandidate.id +
                    ":result=NO_EXECUTABLE_CANDIDATE";
            return null;
        }

        if (observedRegion != null) {
            observedRegion.lastNegativeEvaluationSignature = null;
        }
        congestionClusterSelections++;
        congestionClusterSelectedReleasePotential +=
                Math.max(0, bestReleasePotential);
        lastCongestionCluster =
                context.side + ":tiles" + (start + 1) + "-" + (end + 1) +
                ":occupied=" + occupiedCount +
                ":anchor=P" + normalCandidate.id +
                ":selected=P" + best.id +
                ":directReleases=" + bestDirectReleases +
                ":releasePotential=" + bestReleasePotential;

        return best;
    }

    Passenger dependencyAwareCandidate(
            AisleContext context,
            Passenger normalCandidate) {

        if (normalCandidate == null) {
            return null;
        }

        Passenger clustered =
                rowBoundedCongestionClusterCandidate(
                        context,
                        normalCandidate
                );

        if (clustered != null) {
            lastPrecedenceChain =
                    "CLUSTER|" + normalCandidate.id + "|" + clustered.id;
            return clustered;
        }

        Passenger scored =
                scoredGatekeeperCandidate(
                        context,
                        normalCandidate
                );

        if (scored != null) {
            return scored;
        }

        precedenceCandidatesExamined++;

        LinkedHashSet<Integer> visited = new LinkedHashSet<>();
        List<Integer> chain = new ArrayList<>();
        Passenger current = normalCandidate;

        while (current != null && visited.add(current.id)) {
            chain.add(current.id);
            List<Integer> blockers = currentBlockerIds(current);

            if (blockers.isEmpty()) {
                break;
            }

            Passenger successor = null;

            for (int offset = 1; offset <= blockers.size(); offset++) {
                int tile = current.row + offset;

                if (tile >= context.aisle.length) {
                    precedenceRejectedBoundary++;
                    break;
                }

                int occupantId = context.aisle[tile];
                if (occupantId == EMPTY) {
                    continue;
                }

                Passenger occupant = passengers.get(occupantId);
                if (occupant != null &&
                        occupant.state == PassengerState.WAITING_AT_ROW &&
                        occupant.lastAislePosition == occupant.row) {
                    successor = occupant;
                }
                break;
            }

            if (successor == null) {
                break;
            }

            precedenceBlockedByRowEvent++;
            current = successor;

            if (precedenceEventCanStartNow(context, current)) {
                chain.add(current.id);
                precedenceChainsFollowed++;
                precedenceMaximumChainDepth = Math.max(
                        precedenceMaximumChainDepth,
                        chain.size()
                );
                precedenceSubstitutionsMade++;
                lastPrecedenceChain = joinIntegers(chain);

                boolean repeated = false;
                for (GatekeeperEpisode e : gatekeeperEpisodes) {
                    if (!e.closed &&
                            e.upstreamId == normalCandidate.id &&
                            e.gatekeeperId == current.id) {
                        repeated = true;
                        break;
                    }
                }

                if (repeated) {
                    gatekeeperRepeatedSelections++;
                } else {
                    gatekeeperEpisodes.add(
                            new GatekeeperEpisode(
                                    nextGatekeeperEpisodeId++,
                                    normalCandidate.id,
                                    current.id,
                                    context.side,
                                    normalCandidate.row + 1,
                                    currentTick,
                                    currentSeatedCount(),
                                    lastPrecedenceChain
                            )
                    );
                }

                return current;
            }
        }

        if (chain.size() > 1) {
            precedenceChainsFollowed++;
            precedenceMaximumChainDepth = Math.max(
                    precedenceMaximumChainDepth,
                    chain.size()
            );
            lastPrecedenceChain = joinIntegers(chain);
        }

        return normalCandidate;
    }

    void synchronousAisleMove(AisleContext context) {
        // ================================================================
        // MAIN ORIGINAL-BACKBONE DESCENDANT
        // ================================================================
        // This is the clearest surviving descendant of Amit's original movement code.
        //
        // Original structural idea:
        //   scan a one-dimensional array;
        //   inspect the current position and the next position;
        //   move only when the next position is empty;
        //   repeat scans until the movement objective is complete.
        //
        // Main evolution:
        //   decisions are now calculated from a tick-start snapshot and then applied
        //   together. This prevents one scan from allowing a passenger to exploit a
        //   tile that another passenger vacated earlier in the same tick.
        int[] snapshot = Arrays.copyOf(context.aisle, context.aisle.length);
        int[] next = Arrays.copyOf(snapshot, snapshot.length);

        ReleaseImminentWindow protectionWindow =
                buildReleaseImminentWindow(
                        context,
                        snapshot);

        int blockedRow =
                context.activeEvent == null
                        ? -1
                        : context.activeEvent.rowIndex;

        for (int position = snapshot.length - 1;
             position >= 0;
             position--) {

            int passengerId = snapshot[position];

            if (passengerId == EMPTY) {
                continue;
            }

            Passenger passenger = passengers.get(passengerId);

            if (passenger == null ||
                    passenger.state == PassengerState.IN_SEAT_EVENT) {
                continue;
            }

            if (position == passenger.row) {
                // EVOLVED FROM ORIGINAL "FINISHED / BLOCKED" BOOLEAN LOGIC:
                // Arrival at the destination row becomes an intermediate event trigger.
                if (passenger.state != PassengerState.WAITING_AT_ROW) {
                    passenger.state = PassengerState.WAITING_AT_ROW;
                    passenger.lastStateChangeTick = currentTick;
                }
                passenger.lastAislePosition = position;
                setDirectBlocker(
                        passenger,
                        EMPTY,
                        "WAITING_FOR_SEAT_EVENT"
                );
                continue;
            }

            if (position > passenger.row) {
                passenger.state = PassengerState.WAITING_AT_ROW;
                passenger.lastStateChangeTick = currentTick;
                passenger.lastAislePosition = position;
                setDirectBlocker(
                        passenger,
                        EMPTY,
                        "PASSED_ASSIGNED_ROW_DEFENSIVE"
                );
                continue;
            }

            if (blockedRow >= 0 && position <= blockedRow) {
                passenger.lastAislePosition = position;
                int eventTargetId =
                        context.activeEvent == null
                                ? EMPTY
                                : context.activeEvent.target.id;
                setDirectBlocker(
                        passenger,
                        eventTargetId,
                        "ACTIVE_SEAT_EVENT_AHEAD"
                );
                continue;
            }

            int nextPosition = position + 1;

            if (nextPosition >= snapshot.length) {
                passenger.lastAislePosition = position;
                setDirectBlocker(
                        passenger,
                        EMPTY,
                        "AISLE_BOUNDARY"
                );
                continue;
            }

            if (snapshot[nextPosition] == EMPTY) {
                if (protectionWindow != null &&
                        protectionWindow.protects(nextPosition)) {

                    passenger.lastAislePosition = position;
                    setDirectBlocker(
                            passenger,
                            EMPTY,
                            "RELEASE_IMMINENT_WINDOW_HOLD"
                    );
                    releaseImminentPassengerHolds++;
                    continue;
                }

                // ORIGINAL MOVE PATTERN, EVOLVED FROM:
                //
                // people[i] = 0;
                // people[indexMove] = 1;
                // moves++;
                //
                // The same occupancy transfer remains:
                // current position becomes empty; next position becomes occupied.
                // Passenger ID replaces anonymous value 1.
                next[position] = EMPTY;
                next[nextPosition] = passengerId;
                context.totalMoves++;
                passenger.aisleMoveCount++;
                passenger.lastAislePosition = nextPosition;
                passenger.lastMovementTick = currentTick;
                clearDirectBlocker(passenger);

                if (nextPosition == passenger.row) {
                    passenger.state = PassengerState.WAITING_AT_ROW;
                    passenger.lastStateChangeTick = currentTick;
                }
            } else {
                passenger.lastAislePosition = position;
                setDirectBlocker(
                        passenger,
                        snapshot[nextPosition],
                        "NEXT_AISLE_TILE_OCCUPIED"
                );
            }
        }

        System.arraycopy(
                next,
                0,
                context.aisle,
                0,
                context.aisle.length
        );
    }


    void recordPrerequisiteFailure(
            Passenger passenger,
            String reason) {

        passenger.lastPrerequisiteFailure = reason;
        prerequisiteFailureCounts.merge(reason, 1, Integer::sum);
    }

    String dominantPrerequisiteFailure() {
        String selected = "NONE";
        int highest = 0;

        for (Map.Entry<String, Integer> entry :
                prerequisiteFailureCounts.entrySet()) {

            if (entry.getValue() > highest) {
                selected = entry.getKey();
                highest = entry.getValue();
            }
        }

        return selected;
    }

    int dominantPrerequisiteFailureCount() {
        int highest = 0;

        for (int count : prerequisiteFailureCounts.values()) {
            highest = Math.max(highest, count);
        }

        return highest;
    }

    String joinIntegers(List<Integer> values) {
        if (values.isEmpty()) {
            return "NONE";
        }

        StringBuilder sb = new StringBuilder();

        for (int i = 0; i < values.size(); i++) {
            if (i > 0) {
                sb.append("|");
            }
            sb.append(values.get(i));
        }

        return sb.toString();
    }

    DependencyRegion confirmedRegionForCandidate(
            AisleContext context,
            Passenger candidate) {

        if (candidate == null) {
            return null;
        }

        DependencyRegion best = null;
        int bestOverlap = -1;

        for (DependencyRegion region : activeDependencyRegions.values()) {
            if (!region.confirmationReached || region.side != context.side) {
                continue;
            }

            int overlap = 0;
            if (region.memberPassengerIds.contains(candidate.id)) {
                overlap += 1000;
            }
            if (candidate.lastAislePosition >= region.startTile - REGION_LOCAL_TILE_MARGIN &&
                    candidate.lastAislePosition <= region.endTile + REGION_LOCAL_TILE_MARGIN) {
                overlap += 100;
            }
            overlap += Math.max(0, region.occupiedCount);

            if (overlap > bestOverlap ||
                    (overlap == bestOverlap && best != null && region.id < best.id)) {
                best = region;
                bestOverlap = overlap;
            }
        }

        return bestOverlap > 0 ? best : null;
    }

    int currentDependencyDepth(Passenger root) {
        if (root == null) {
            return 0;
        }
        LinkedHashSet<Integer> visited = new LinkedHashSet<>();
        Passenger current = root;
        int depth = 0;
        while (current != null && visited.add(current.id)) {
            depth++;
            int blockerId = current.directBlockingPassengerId;
            if (blockerId == EMPTY) {
                break;
            }
            current = passengers.get(blockerId);
        }
        return depth;
    }

    int hierarchicalRegionScore(
            AisleContext context,
            Passenger candidate,
            DependencyRegion region) {

        if (candidate == null || region == null) {
            return Integer.MIN_VALUE;
        }

        int unresolvedLocal = unresolvedMembersStillLocal(region);
        int dependencyDepth = currentDependencyDepth(candidate);
        int failedEvidence = Math.min(100, candidate.failedSeatEventStarts);
        int directReleases = directBlockedRowEventsReleased(context, candidate);

        return region.occupiedCount * 1000
                + unresolvedLocal * 100
                + dependencyDepth * 10
                + directReleases * 5
                + failedEvidence;
    }

    void prepareHierarchicalRegionPriority(
            Passenger leftNormal,
            Passenger rightNormal) {

        priorityEligibleAisleThisTick = null;
        priorityCompetitionThisTick = false;

        if (!config.hierarchicalRegionPriorityEnabled) {
            return;
        }

        DependencyRegion leftRegion = confirmedRegionForCandidate(left, leftNormal);
        DependencyRegion rightRegion = confirmedRegionForCandidate(right, rightNormal);

        if (leftRegion == null || rightRegion == null) {
            return;
        }

        int leftScore = hierarchicalRegionScore(left, leftNormal, leftRegion);
        int rightScore = hierarchicalRegionScore(right, rightNormal, rightRegion);

        regionPriorityCompetitions++;
        regionPriorityRegionsRanked += 2;
        priorityCompetitionThisTick = true;

        if (leftScore == rightScore) {
            regionPriorityTies++;
            priorityEligibleAisleThisTick =
                    leftRegion.id <= rightRegion.id ? AisleSide.LEFT : AisleSide.RIGHT;
        } else {
            priorityEligibleAisleThisTick =
                    leftScore > rightScore ? AisleSide.LEFT : AisleSide.RIGHT;
        }

        int selectedScore = priorityEligibleAisleThisTick == AisleSide.LEFT
                ? leftScore : rightScore;
        regionPrioritySelections++;
        regionPrioritySelectedScoreTotal += selectedScore;
        regionPriorityHighestScore = Math.max(regionPriorityHighestScore, selectedScore);
        if (priorityEligibleAisleThisTick == AisleSide.LEFT) {
            regionPriorityLeftWins++;
        } else {
            regionPriorityRightWins++;
        }

        lastRegionPriorityDecision =
                "LEFT:R" + leftRegion.id + ":score=" + leftScore +
                " | RIGHT:R" + rightRegion.id + ":score=" + rightScore +
                " | selected=" + priorityEligibleAisleThisTick;
    }

    void detectAndStartSeatEventsWithMiddleBankArbitration() {
        // NEW DOMAIN LAYER BUILT ON TOP OF THE BACKBONE:
        // The original scan engine brings passengers to the relevant row.
        // This method converts that reached-location state into an event request.
        // Reservation and arbitration are new because two mutually exclusive aisles
        // can now address the same middle seat bank.
        Passenger leftNormal = findWaitingPassengerAtRow(left);
        Passenger rightNormal = findWaitingPassengerAtRow(right);

        prepareHierarchicalRegionPriority(leftNormal, rightNormal);

        Passenger leftCandidate =
                dependencyAwareCandidate(left, leftNormal);

        Passenger rightCandidate =
                dependencyAwareCandidate(right, rightNormal);

        boolean simultaneousSameMiddleRow =
                leftCandidate != null &&
                rightCandidate != null &&
                isMiddleBankSeat(leftCandidate.seatIndex) &&
                isMiddleBankSeat(rightCandidate.seatIndex) &&
                leftCandidate.row == rightCandidate.row;

        if (simultaneousSameMiddleRow) {
            simultaneousMiddleBankConflicts++;

            Passenger winner =
                    leftCandidate.id < rightCandidate.id
                            ? leftCandidate
                            : rightCandidate;

            AisleContext winnerContext =
                    winner.servingAisle == AisleSide.LEFT
                            ? left
                            : right;

            Passenger loser =
                    winner.id == leftCandidate.id
                            ? rightCandidate
                            : leftCandidate;

            loser.arbitrationLosses++;
            totalArbitrationLosses++;
            recordPrerequisiteFailure(
                    loser,
                    "SIMULTANEOUS_MIDDLE_BANK_ARBITRATION_LOSS"
            );

            if (reserveMiddleBank(winner)) {
                if (!startSeatEventForPassenger(winnerContext, winner)) {
                    releaseMiddleBankReservation(
                            winner.row,
                            winner.id
                    );
                }
            }
            return;
        }

        tryStartCandidate(left, leftCandidate);
        tryStartCandidate(right, rightCandidate);
    }

    void tryStartCandidate(
            AisleContext context,
            Passenger candidate) {

        if (candidate == null) {
            return;
        }

        if (isMiddleBankSeat(candidate.seatIndex) &&
                !reserveMiddleBank(candidate)) {
            return;
        }

        boolean started =
                startSeatEventForPassenger(context, candidate);

        if (started &&
                config.congestionClusteringEnabled &&
                lastPrecedenceChain.startsWith("CLUSTER|") &&
                lastPrecedenceChain.endsWith(String.valueOf(candidate.id))) {
            congestionClusterEventStarts++;
        }

        if (started &&
                !"NONE".equals(lastPrecedenceChain) &&
                lastPrecedenceChain.endsWith(
                        String.valueOf(candidate.id))) {
            precedenceSubstitutedEventsStarted++;

            for (GatekeeperEpisode e : gatekeeperEpisodes) {
                if (!e.closed &&
                        e.gatekeeperId == candidate.id &&
                        !e.gatekeeperStarted) {
                    e.gatekeeperStarted = true;
                    break;
                }
            }
        }

        if (!started &&
                isMiddleBankSeat(candidate.seatIndex)) {
            releaseMiddleBankReservation(
                    candidate.row,
                    candidate.id
            );
        }
    }

    Passenger findWaitingPassengerAtRow(AisleContext context) {
        if (context.activeEvent != null) {
            for (int position = 0;
                 position < context.aisle.length;
                 position++) {

                int passengerId = context.aisle[position];

                if (passengerId == EMPTY) {
                    continue;
                }

                Passenger passenger = passengers.get(passengerId);

                if (passenger != null &&
                        position == passenger.row &&
                        passenger.state == PassengerState.WAITING_AT_ROW) {

                    passenger.activeEventDeferrals++;
                    totalActiveEventDeferrals++;
                    recordPrerequisiteFailure(
                            passenger,
                            "ANOTHER_ACTIVE_SEAT_EVENT"
                    );
                }
            }

            return null;
        }

        for (int position = 0;
             position < context.aisle.length;
             position++) {

            int passengerId = context.aisle[position];

            if (passengerId == EMPTY) {
                continue;
            }

            Passenger passenger = passengers.get(passengerId);

            if (passenger != null &&
                    position == passenger.row &&
                    passenger.state == PassengerState.WAITING_AT_ROW) {
                return passenger;
            }
        }

        return null;
    }

    boolean isMiddleBankSeat(int seatIndex) {
        return seatIndex >= config.cabin.middleStart &&
               seatIndex <= config.cabin.middleEnd;
    }

    boolean reserveMiddleBank(Passenger passenger) {
        middleBankReservationRequests++;

        int holder =
                middleBankReservedByPassenger[passenger.row];

        if (holder == EMPTY || holder == passenger.id) {
            middleBankReservedByPassenger[passenger.row] =
                    passenger.id;

            middleBankReservedByAisle[passenger.row] =
                    passenger.servingAisle;

            middleBankReservationGranted++;
            return true;
        }

        middleBankReservationDenied++;
        passenger.reservationFailures++;
        totalReservationFailures++;
        passenger.lastBlockingReason = "MIDDLE_BANK_RESERVED";
        recordBlockingReason(passenger.lastBlockingReason);
        recordPrerequisiteFailure(
                passenger,
                "MIDDLE_BANK_RESERVATION_DENIED"
        );
        return false;
    }

    void releaseMiddleBankReservation(
            int row,
            int passengerId) {

        if (middleBankReservedByPassenger[row] == passengerId) {
            middleBankReservedByPassenger[row] = EMPTY;
            middleBankReservedByAisle[row] = null;
        }
    }

    boolean guardedForwardYieldPermitted(
            AisleContext context,
            Passenger target,
            List<Integer> forwardTiles) {

        forwardYieldGuardEvaluations++;

        int minTile = Integer.MAX_VALUE;
        int maxTile = Integer.MIN_VALUE;

        for (int tile : forwardTiles) {
            minTile = Math.min(minTile, tile);
            maxTile = Math.max(maxTile, tile);

            int occupantId = context.aisle[tile];
            if (occupantId != EMPTY) {
                forwardYieldGuardRejections++;
                guardRejectedVulnerableMover++;
                return false;
            }
        }

        for (Passenger passenger : passengers.values()) {
            if (passenger.id == target.id ||
                    passenger.servingAisle != context.side) {
                continue;
            }

            if (passenger.state == PassengerState.WAITING_AT_ROW &&
                    passenger.lastAislePosition >= minTile &&
                    passenger.lastAislePosition <= maxTile) {
                forwardYieldGuardRejections++;
                guardRejectedWaitingRowPassenger++;
                return false;
            }

            if (passenger.state == PassengerState.MOVING_IN_AISLE &&
                    passenger.lastAislePosition == minTile - 1 &&
                    passenger.row <= maxTile) {
                forwardYieldGuardRejections++;
                guardRejectedVulnerableMover++;
                return false;
            }
        }

        if (isMiddleBankSeat(target.seatIndex)) {
            int holder = middleBankReservedByPassenger[target.row];
            if (holder != EMPTY && holder != target.id) {
                forwardYieldGuardRejections++;
                guardRejectedReservationConflict++;
                return false;
            }
        }

        forwardYieldGuardApprovals++;
        return true;
    }

    boolean startSeatEventForPassenger(
            AisleContext context,
            Passenger passenger) {

        passenger.seatEventRequests++;

        List<Integer> blockerSeats =
                findBlockingSeatIndices(passenger);

        List<Integer> blockerIds = new ArrayList<>();

        for (int seatIndex : blockerSeats) {
            int blockerId =
                    seats[passenger.row][seatIndex];

            if (blockerId != EMPTY) {
                blockerIds.add(blockerId);
            }
        }

        int blockerCount = blockerIds.size();
        passenger.lastRequiredBlockerCount = blockerCount;

        if (blockerCount == 0) {
            passenger.zeroBlockerAttempts++;
        } else if (blockerCount == 1) {
            passenger.oneBlockerAttempts++;
        } else {
            passenger.multipleBlockerAttempts++;
        }

        List<Integer> requiredTiles = new ArrayList<>();
        List<Integer> unavailableTiles = new ArrayList<>();
        List<Integer> occupyingPassengerIds = new ArrayList<>();

        boolean rearBoundaryFailure = false;
        boolean useForwardYield = false;

        rearwardYieldAttempts++;

        for (int offset = 1;
             offset <= blockerCount;
             offset++) {

            int tile = passenger.row + offset;
            requiredTiles.add(tile + 1);

            if (tile >= rows) {
                rearBoundaryFailure = true;
                rearBoundaryFailuresEncountered++;
                unavailableTiles.add(tile + 1);
                occupyingPassengerIds.add(EMPTY);
                continue;
            }

            if (context.aisle[tile] != EMPTY) {
                unavailableTiles.add(tile + 1);
                occupyingPassengerIds.add(context.aisle[tile]);
            }
        }

        if (rearBoundaryFailure) {

            forwardYieldFallbackEvaluations++;

            List<Integer> forwardTiles = new ArrayList<>();
            boolean forwardAvailable = true;

            for (int offset = 1;
                 offset <= blockerCount;
                 offset++) {

                int tile = passenger.row - offset;

                if (tile < 0 ||
                        context.aisle[tile] != EMPTY) {
                    forwardAvailable = false;
                    break;
                }

                forwardTiles.add(tile);
            }

            boolean guardPassed =
                    forwardAvailable &&
                    guardedForwardYieldPermitted(
                            context,
                            passenger,
                            forwardTiles
                    );

            if (forwardAvailable && guardPassed) {
                useForwardYield = true;
                forwardYieldFallbackGrants++;

                requiredTiles.clear();
                unavailableTiles.clear();
                occupyingPassengerIds.clear();

                for (int tile : forwardTiles) {
                    requiredTiles.add(tile + 1);
                }
            } else {
                forwardYieldFallbackOccupiedRejections++;

                if (config.guardedForwardYieldEnabled &&
                        forwardAvailable &&
                        !guardPassed) {
                    // Guard-specific rejection already counted.
                }
            }
        }

        passenger.lastRequiredYieldTiles =
                joinIntegers(requiredTiles);
        passenger.lastUnavailableYieldTiles =
                joinIntegers(unavailableTiles);
        passenger.lastYieldTileOccupants =
                joinIntegers(occupyingPassengerIds);

        if (rearBoundaryFailure && !useForwardYield) {
            passenger.failedSeatEventStarts++;
            passenger.rearBoundaryFailures++;
            totalRearBoundaryFailures++;

            recordBlockingReason(
                    "INSUFFICIENT_REAR_YIELD_SPACE"
            );
            recordPrerequisiteFailure(
                    passenger,
                    "INSUFFICIENT_REAR_YIELD_SPACE"
            );
            return false;
        }

        if (!unavailableTiles.isEmpty()) {
            passenger.failedSeatEventStarts++;
            passenger.yieldTileOccupiedFailures++;
            totalYieldTileOccupiedFailures++;

            recordBlockingReason("YIELD_TILE_OCCUPIED");
            recordPrerequisiteFailure(
                    passenger,
                    "YIELD_TILE_OCCUPIED"
            );
            return false;
        }

        context.activeEvent =
                new SeatEvent(
                        passenger,
                        passenger.row,
                        blockerIds,
                        blockerSeats
                );

        context.activeEvent.forwardYieldTowardCockpit =
                useForwardYield;

        if (useForwardYield) {
            forwardYieldEventPassengers.add(passenger.id);
            forwardYieldEventsStarted++;
        }

        context.seatEvents++;

        if (blockerCount == 1) {
            context.singleBlockerEvents++;
        } else if (blockerCount > 1) {
            context.multipleBlockerEvents++;
        }

        passenger.successfulSeatEventStarts++;

        if (passenger.id == pendingClusteredCandidateId &&
                currentTick == lastProtectionTick + 1) {
            releaseImminentNextTickEventStarts++;
            pendingClusteredCandidateId = EMPTY;
        }

        passenger.state = PassengerState.IN_SEAT_EVENT;
        passenger.lastStateChangeTick = currentTick;
        passenger.lastBlockingReason = "NONE";
        passenger.lastPrerequisiteFailure = "NONE";
        return true;
    }

    boolean hasRequiredYieldSpace(
            AisleContext context,
            int row,
            int blockerCount) {

        for (int offset = 1;
             offset <= blockerCount;
             offset++) {

            int tile = row + offset;

            if (tile >= context.aisle.length) {
                recordBlockingReason("INSUFFICIENT_REAR_YIELD_SPACE");
                return false;
            }

            if (context.aisle[tile] != EMPTY) {
                recordBlockingReason("YIELD_TILE_OCCUPIED");
                return false;
            }
        }

        return true;
    }

    List<Integer> findBlockingSeatIndices(Passenger passenger) {
        // ORIGINAL PRINCIPLE, DIFFERENT DATASET:
        // This remains a directional scan over local occupancy.
        // The dataset is now a seat row rather than the aisle, and the result is not
        // clustering; it is identification of seats that interfere with access.
        List<Integer> blockers = new ArrayList<>();

        if (passenger.seatIndex <= config.cabin.leftOuterEnd) {
            for (int i = config.cabin.leftOuterEnd;
                 i > passenger.seatIndex;
                 i--) {

                if (seats[passenger.row][i] != EMPTY) {
                    blockers.add(i);
                }
            }

        } else if (passenger.seatIndex >= config.cabin.rightOuterStart) {
            for (int i = config.cabin.rightOuterStart;
                 i < passenger.seatIndex;
                 i++) {

                if (seats[passenger.row][i] != EMPTY) {
                    blockers.add(i);
                }
            }

        } else if (passenger.servingAisle == AisleSide.LEFT) {
            for (int i = config.cabin.middleStart;
                 i < passenger.seatIndex;
                 i++) {

                if (seats[passenger.row][i] != EMPTY) {
                    blockers.add(i);
                }
            }

        } else {
            for (int i = config.cabin.middleEnd;
                 i > passenger.seatIndex;
                 i--) {

                if (seats[passenger.row][i] != EMPTY) {
                    blockers.add(i);
                }
            }
        }

        return blockers;
    }

    void advanceSeatEvent(AisleContext context) {
        // NEW EVENT-DRIVEN EXTENSION:
        // Volume 1 used repeated scans and booleans to describe movement stages.
        // Here those implicit stages have evolved into an explicit finite-state
        // sequence: stand -> enter aisle -> target sits -> reseat -> complete.
        SeatEvent event = context.activeEvent;

        if (event == null) {
            return;
        }

        switch (event.phase) {
            case BLOCKERS_STAND:
                event.phase = EventPhase.BLOCKERS_ENTER_AISLE;
                break;

            case BLOCKERS_ENTER_AISLE:
                if (placeBlockersInAisle(context, event)) {
                    event.phase = EventPhase.TARGET_SITS;
                }
                break;

            case TARGET_SITS:
                seatTargetPassenger(context, event);
                event.phase = EventPhase.BLOCKERS_RESEAT;
                break;

            case BLOCKERS_RESEAT:
                reseatBlockers(context, event);
                event.phase = EventPhase.COMPLETE;
                break;

            case COMPLETE:
                if (isMiddleBankSeat(event.target.seatIndex)) {
                    releaseMiddleBankReservation(
                            event.rowIndex,
                            event.target.id
                    );
                }

                context.activeEvent = null;
                break;
        }
    }

    boolean placeBlockersInAisle(
            AisleContext context,
            SeatEvent event) {

        int blockerCount =
                event.blockerPassengerIds.size();

        if (blockerCount == 0) {
            return true;
        }

        List<Integer> requiredTiles = new ArrayList<>();

        for (int i = 0; i < blockerCount; i++) {
            int directionalOffset = blockerCount - i;

            int tile =
                    event.forwardYieldTowardCockpit
                            ? event.rowIndex - directionalOffset
                            : event.rowIndex + directionalOffset;

            if (tile < 0 ||
                    tile >= context.aisle.length) {
                return false;
            }

            requiredTiles.add(tile);
        }

        for (int tile : requiredTiles) {
            if (context.aisle[tile] != EMPTY) {
                return false;
            }
        }

        for (int i = 0; i < blockerCount; i++) {
            int blockerId =
                    event.blockerPassengerIds.get(i);

            int blockerSeatIndex =
                    event.blockerSeatIndices.get(i);

            int tile =
                    requiredTiles.get(i);

            context.aisle[tile] = blockerId;

            event.blockerYieldTileByPassenger.put(
                    blockerId,
                    tile
            );

            seats[event.rowIndex][blockerSeatIndex] = EMPTY;

            Passenger blocker =
                    passengers.get(blockerId);

            blocker.state = PassengerState.IN_SEAT_EVENT;

            context.temporaryAisleReoccupations++;

            if (event.forwardYieldTowardCockpit) {
                blockersStagedTowardCockpit++;
            }
        }

        return true;
    }

    void seatTargetPassenger(
            AisleContext context,
            SeatEvent event) {

        Passenger target = event.target;

        if (context.aisle[event.rowIndex] == target.id) {
            context.aisle[event.rowIndex] = EMPTY;
        }

        seats[target.row][target.seatIndex] = target.id;
        target.state = PassengerState.SEATED;
        target.seatedTick = currentTick;

        if (forwardYieldEventPassengers.remove(target.id)) {
            forwardYieldEventsCompleted++;
        }

        target.lastStateChangeTick = currentTick;
        target.lastBlockingReason = "NONE";
    }

    void reseatBlockers(
            AisleContext context,
            SeatEvent event) {

        for (int i = event.blockerPassengerIds.size() - 1;
             i >= 0;
             i--) {

            int blockerId =
                    event.blockerPassengerIds.get(i);

            int blockerSeatIndex =
                    event.blockerSeatIndices.get(i);

            Integer yieldTile =
                    event.blockerYieldTileByPassenger.get(blockerId);

            if (yieldTile != null &&
                    context.aisle[yieldTile] == blockerId) {
                context.aisle[yieldTile] = EMPTY;
            }

            seats[event.rowIndex][blockerSeatIndex] =
                    blockerId;

            Passenger blockerPassenger = passengers.get(blockerId);
            blockerPassenger.state = PassengerState.SEATED;
            blockerPassenger.lastStateChangeTick = currentTick;
            blockerPassenger.lastBlockingReason = "NONE";
        }
    }

    void updateWaitingTicks() {
        for (Passenger passenger : passengers.values()) {
            if (passenger.state == PassengerState.WAITING_TO_ENTER ||
                passenger.state == PassengerState.WAITING_AT_ROW ||
                passenger.state == PassengerState.IN_SEAT_EVENT) {

                passenger.waitingTicks++;

                if (passenger.state == PassengerState.WAITING_TO_ENTER) {
                    passenger.stationaryTicks++;
                    passenger.lastBlockingReason = "WAITING_OUTSIDE_FOR_ENTRY";
                }
            }
        }
    }

    void updateAisleMetrics(AisleContext context) {
        int occupied = 0;

        for (int value : context.aisle) {
            if (value != EMPTY) {
                occupied++;
            }
        }

        context.maxOccupiedTiles =
                Math.max(
                        context.maxOccupiedTiles,
                        occupied
                );
    }

    void printTickSummary() {
        System.out.println(
                "Tick " + currentTick +
                " | seated=" +
                countPassengersInState(PassengerState.SEATED) +
                "/" + passengers.size() +
                " | leftInAisle=" +
                occupiedCount(left.aisle) +
                " | rightInAisle=" +
                occupiedCount(right.aisle)
        );
    }

    int occupiedCount(int[] aisle) {
        int count = 0;

        for (int value : aisle) {
            if (value != EMPTY) {
                count++;
            }
        }

        return count;
    }

    int countPassengersInState(PassengerState state) {
        int count = 0;

        for (Passenger passenger : passengers.values()) {
            if (passenger.state == state) {
                count++;
            }
        }

        return count;
    }

    boolean allPassengersSeated() {
        // ORIGINAL TERMINAL-CONDITION PRINCIPLE:
        // The original code repeatedly scanned until all anonymous passengers reached
        // the required final zone. This version repeats ticks until every identified
        // passenger reaches the SEATED state.
        return countPassengersInState(PassengerState.SEATED) ==
               passengers.size();
    }

    void recordBlockingReason(String reason) {
        if (reason == null || "NONE".equals(reason)) {
            return;
        }
        blockingReasonCounts.merge(reason, 1, Integer::sum);
    }

    void updateProgressWatchdog() {
        int seatedNow = countPassengersInState(PassengerState.SEATED);

        if (seatedNow > lastSeatedCount) {
            lastSeatedCount = seatedNow;
            lastSeatProgressTick = currentTick;
            return;
        }

        if ((currentTick - lastSeatProgressTick) >= STALL_TICK_LIMIT) {
            stallDetected = true;
        }
    }

    void printStallDiagnostics() {
        System.out.println();
        System.out.println("!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!");
        System.out.println("STALL DETECTED");
        System.out.println("Scenario: " + config.scenarioNumber);
        System.out.println("Tick: " + currentTick);
        System.out.println("Ticks without a newly seated passenger: "
                + (currentTick - lastSeatProgressTick));
        System.out.println("Passengers seated: "
                + countPassengersInState(PassengerState.SEATED)
                + "/" + passengers.size());

        Map<PassengerState, Integer> stateCounts = new EnumMap<>(PassengerState.class);
        for (PassengerState state : PassengerState.values()) {
            stateCounts.put(state, 0);
        }

        for (Passenger passenger : passengers.values()) {
            stateCounts.put(
                    passenger.state,
                    stateCounts.get(passenger.state) + 1
            );
        }

        System.out.println("Remaining-state distribution: " + stateCounts);

        List<Passenger> unresolved = new ArrayList<>();
        for (Passenger passenger : passengers.values()) {
            if (passenger.state != PassengerState.SEATED) {
                unresolved.add(passenger);
            }
        }

        unresolved.sort(
                Comparator.comparingInt(
                        (Passenger p) -> currentTick - p.lastStateChangeTick
                ).reversed()
        );

        System.out.println("Top unresolved passengers:");
        int limit = Math.min(12, unresolved.size());

        for (int i = 0; i < limit; i++) {
            Passenger p = unresolved.get(i);
            System.out.println("  " + p
                    + " state=" + p.state
                    + " aislePos=" + p.lastAislePosition
                    + " unchangedTicks=" + (currentTick - p.lastStateChangeTick)
                    + " lastMoveTick=" + p.lastMovementTick
                    + " reason=" + p.lastBlockingReason);
        }

        System.out.println("Blocking-reason totals: " + blockingReasonCounts);
        System.out.println("LEFT aisle snapshot : " + Arrays.toString(left.aisle));
        System.out.println("RIGHT aisle snapshot: " + Arrays.toString(right.aisle));
        System.out.println("!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!");
        System.out.println();
    }

    String dominantBlockingReason() {
        String bestReason = "NONE";
        int bestCount = 0;

        for (Map.Entry<String, Integer> entry : blockingReasonCounts.entrySet()) {
            if (entry.getValue() > bestCount) {
                bestReason = entry.getKey();
                bestCount = entry.getValue();
            }
        }

        return bestReason;
    }

    int dominantBlockingReasonCount() {
        int bestCount = 0;

        for (int count : blockingReasonCounts.values()) {
            bestCount = Math.max(bestCount, count);
        }

        return bestCount;
    }

    Passenger longestUnresolvedPassenger() {
        Passenger selected = null;
        int longest = -1;

        for (Passenger passenger : passengers.values()) {
            if (passenger.state == PassengerState.SEATED) {
                continue;
            }

            int unchanged = currentTick - passenger.lastStateChangeTick;
            if (unchanged > longest) {
                longest = unchanged;
                selected = passenger;
            }
        }

        return selected;
    }


    Passenger passengerById(int passengerId) {
        return passengerId == EMPTY ? null : passengers.get(passengerId);
    }

    String terminalReasonFor(Passenger passenger) {
        if (passenger == null) {
            return "NO_PASSENGER";
        }

        if (passenger.directBlockingPassengerId != EMPTY) {
            return "BLOCKED_BY_PASSENGER";
        }

        return passenger.lastBlockingReason == null
                ? "UNKNOWN"
                : passenger.lastBlockingReason;
    }

    List<Integer> buildDependencyChainFrom(Passenger start) {
        List<Integer> chain = new ArrayList<>();
        Set<Integer> visited = new LinkedHashSet<>();

        Passenger current = start;

        while (current != null &&
                current.state != PassengerState.SEATED &&
                visited.add(current.id)) {

            chain.add(current.id);

            if (current.directBlockingPassengerId == EMPTY) {
                break;
            }

            current = passengerById(current.directBlockingPassengerId);
        }

        return chain;
    }

    String formatDependencyChain(List<Integer> chain) {
        if (chain.isEmpty()) {
            return "NONE";
        }

        StringBuilder sb = new StringBuilder();

        for (int i = 0; i < chain.size(); i++) {
            if (i > 0) {
                sb.append(" -> ");
            }

            Passenger passenger = passengers.get(chain.get(i));
            sb.append("P").append(chain.get(i));

            if (passenger != null) {
                sb.append("(")
                  .append(passenger.seatName())
                  .append("/")
                  .append(passenger.servingAisle)
                  .append(")");
            }
        }

        Passenger terminal = passengers.get(chain.get(chain.size() - 1));
        sb.append(" -> [")
          .append(terminalReasonFor(terminal))
          .append("]");

        return sb.toString();
    }

    Passenger longestDependencyRoot() {
        Passenger best = null;
        int bestLength = -1;

        for (Passenger passenger : passengers.values()) {
            if (passenger.state == PassengerState.SEATED) {
                continue;
            }

            int length = buildDependencyChainFrom(passenger).size();

            if (length > bestLength) {
                bestLength = length;
                best = passenger;
            }
        }

        return best;
    }

    int countPassengersWithDirectBlocker() {
        int count = 0;

        for (Passenger passenger : passengers.values()) {
            if (passenger.state != PassengerState.SEATED &&
                    passenger.directBlockingPassengerId != EMPTY) {
                count++;
            }
        }

        return count;
    }

    int countIndirectlyAffectedBy(int rootPassengerId) {
        if (rootPassengerId == EMPTY) {
            return 0;
        }

        int count = 0;

        for (Passenger passenger : passengers.values()) {
            if (passenger.state == PassengerState.SEATED) {
                continue;
            }

            List<Integer> chain = buildDependencyChainFrom(passenger);

            if (chain.contains(rootPassengerId) &&
                    passenger.id != rootPassengerId) {
                count++;
            }
        }

        return count;
    }


    Map<Integer, List<Integer>> buildReverseDependencyGraph() {
        Map<Integer, List<Integer>> reverse = new LinkedHashMap<>();

        for (Passenger passenger : passengers.values()) {
            if (passenger.state == PassengerState.SEATED) {
                continue;
            }

            int blockerId = passenger.directBlockingPassengerId;

            if (blockerId != EMPTY &&
                    passengers.containsKey(blockerId)) {
                reverse.computeIfAbsent(
                        blockerId,
                        ignored -> new ArrayList<>()
                ).add(passenger.id);
            }
        }

        for (List<Integer> dependants : reverse.values()) {
            Collections.sort(dependants);
        }

        return reverse;
    }

    Set<Integer> collectDescendants(
            int blockerId,
            Map<Integer, List<Integer>> reverse) {

        Set<Integer> descendants = new LinkedHashSet<>();
        Deque<Integer> stack = new ArrayDeque<>();

        for (int dependant :
                reverse.getOrDefault(blockerId, Collections.emptyList())) {
            stack.push(dependant);
        }

        while (!stack.isEmpty()) {
            int passengerId = stack.pop();

            if (!descendants.add(passengerId)) {
                continue;
            }

            for (int child :
                    reverse.getOrDefault(
                            passengerId,
                            Collections.emptyList())) {
                stack.push(child);
            }
        }

        descendants.remove(blockerId);
        return descendants;
    }

    int dependencyTreeDepth(
            int blockerId,
            Map<Integer, List<Integer>> reverse,
            Set<Integer> visiting) {

        if (!visiting.add(blockerId)) {
            return 0;
        }

        int maximumChildDepth = 0;

        for (int child :
                reverse.getOrDefault(
                        blockerId,
                        Collections.emptyList())) {

            maximumChildDepth = Math.max(
                    maximumChildDepth,
                    dependencyTreeDepth(child, reverse, visiting)
            );
        }

        visiting.remove(blockerId);
        return 1 + maximumChildDepth;
    }

    Passenger criticalBlocker(
            Map<Integer, List<Integer>> reverse) {

        Passenger best = null;
        int bestDescendants = -1;
        int bestDirectFanOut = -1;

        for (Passenger passenger : passengers.values()) {
            if (passenger.state == PassengerState.SEATED) {
                continue;
            }

            int descendants =
                    collectDescendants(passenger.id, reverse).size();

            int directFanOut =
                    reverse.getOrDefault(
                            passenger.id,
                            Collections.emptyList()).size();

            if (descendants > bestDescendants ||
                    (descendants == bestDescendants &&
                     directFanOut > bestDirectFanOut) ||
                    (descendants == bestDescendants &&
                     directFanOut == bestDirectFanOut &&
                     best != null &&
                     passenger.id < best.id)) {

                best = passenger;
                bestDescendants = descendants;
                bestDirectFanOut = directFanOut;
            }
        }

        return best;
    }

    void appendDependencyTree(
            StringBuilder sb,
            int passengerId,
            Map<Integer, List<Integer>> reverse,
            String indent,
            Set<Integer> visited,
            int remainingNodeBudget) {

        if (remainingNodeBudget <= 0) {
            sb.append(indent).append("... tree output truncated ...\n");
            return;
        }

        Passenger passenger = passengers.get(passengerId);

        sb.append(indent)
          .append("P")
          .append(passengerId);

        if (passenger != null) {
            sb.append("(")
              .append(passenger.seatName())
              .append("/")
              .append(passenger.servingAisle)
              .append("/")
              .append(passenger.lastBlockingReason)
              .append(")");
        }

        sb.append("\n");

        if (!visited.add(passengerId)) {
            sb.append(indent)
              .append("  [cycle already visited]\n");
            return;
        }

        List<Integer> children =
                reverse.getOrDefault(
                        passengerId,
                        Collections.emptyList());

        int childBudget =
                Math.max(0, remainingNodeBudget - 1);

        for (int child : children) {
            appendDependencyTree(
                    sb,
                    child,
                    reverse,
                    indent + "  ",
                    visited,
                    childBudget
            );
            childBudget--;
            if (childBudget <= 0 && children.indexOf(child) < children.size() - 1) {
                sb.append(indent)
                  .append("  ... additional branches omitted ...\n");
                break;
            }
        }
    }

    String formatCriticalBlockerTree(
            Passenger blocker,
            Map<Integer, List<Integer>> reverse) {

        if (blocker == null) {
            return "NONE";
        }

        StringBuilder sb = new StringBuilder();
        appendDependencyTree(
                sb,
                blocker.id,
                reverse,
                "",
                new LinkedHashSet<>(),
                80
        );

        return sb.toString().trim();
    }

    int terminalRowEventNodeCount() {
        int count = 0;

        for (Passenger passenger : passengers.values()) {
            if (passenger.state != PassengerState.SEATED &&
                    passenger.directBlockingPassengerId == EMPTY &&
                    "WAITING_FOR_SEAT_EVENT".equals(
                            passenger.lastBlockingReason)) {
                count++;
            }
        }

        return count;
    }

    int frozenComponentCount(
            Map<Integer, List<Integer>> reverse) {

        Set<Integer> unresolvedInternal = new LinkedHashSet<>();

        for (Passenger passenger : passengers.values()) {
            if (passenger.state != PassengerState.SEATED &&
                    passenger.state != PassengerState.WAITING_TO_ENTER) {
                unresolvedInternal.add(passenger.id);
            }
        }

        Map<Integer, Set<Integer>> undirected = new LinkedHashMap<>();

        for (int passengerId : unresolvedInternal) {
            undirected.put(passengerId, new LinkedHashSet<>());
        }

        for (Passenger passenger : passengers.values()) {
            if (!unresolvedInternal.contains(passenger.id)) {
                continue;
            }

            int blockerId = passenger.directBlockingPassengerId;

            if (unresolvedInternal.contains(blockerId)) {
                undirected.get(passenger.id).add(blockerId);
                undirected.get(blockerId).add(passenger.id);
            }
        }

        int components = 0;
        Set<Integer> visited = new LinkedHashSet<>();

        for (int passengerId : unresolvedInternal) {
            if (!visited.add(passengerId)) {
                continue;
            }

            components++;
            Deque<Integer> queue = new ArrayDeque<>();
            queue.add(passengerId);

            while (!queue.isEmpty()) {
                int current = queue.removeFirst();

                for (int neighbour :
                        undirected.getOrDefault(
                                current,
                                Collections.emptySet())) {

                    if (visited.add(neighbour)) {
                        queue.addLast(neighbour);
                    }
                }
            }
        }

        return components;
    }


    List<Integer> parsePassengerIds(String encoded) {
        List<Integer> ids = new ArrayList<>();

        if (encoded == null ||
                encoded.isBlank() ||
                "NONE".equals(encoded)) {
            return ids;
        }

        for (String token : encoded.split("\\|")) {
            try {
                int id = Integer.parseInt(token.trim());
                if (id != EMPTY) {
                    ids.add(id);
                }
            } catch (NumberFormatException ignored) {
                // Best-effort diagnostics.
            }
        }

        return ids;
    }

    DependencyTrace traceFromPassenger(int startPassengerId) {
        DependencyTrace trace = new DependencyTrace();
        Map<Integer, Integer> firstIndex =
                new LinkedHashMap<>();

        int currentId = startPassengerId;

        while (currentId != EMPTY) {
            Passenger current = passengers.get(currentId);

            if (current == null) {
                trace.terminalCondition =
                        "DISCONNECTED_OR_STALE_REFERENCE";
                trace.terminalPassengerId = currentId;
                return trace;
            }

            Integer seenAt = firstIndex.get(currentId);

            if (seenAt != null) {
                trace.cycleDetected = true;

                for (int i = seenAt;
                     i < trace.path.size();
                     i++) {
                    trace.cycleMembers.add(trace.path.get(i));
                }

                trace.cycleMembers.add(currentId);
                trace.terminalCondition =
                        "CLOSED_PASSENGER_CYCLE";
                trace.terminalPassengerId = currentId;
                return trace;
            }

            firstIndex.put(currentId, trace.path.size());
            trace.path.add(currentId);

            if (current.directBlockingPassengerId != EMPTY) {
                currentId = current.directBlockingPassengerId;
                continue;
            }

            trace.terminalPassengerId = current.id;

            if ("INSUFFICIENT_REAR_YIELD_SPACE".equals(
                    current.lastPrerequisiteFailure) ||
                    "AISLE_BOUNDARY".equals(
                            current.lastBlockingReason)) {
                trace.terminalCondition =
                        "REAR_BOUNDARY_LOCK";
            } else if ("MIDDLE_BANK_RESERVATION_DENIED".equals(
                    current.lastPrerequisiteFailure) ||
                    "MIDDLE_BANK_RESERVED".equals(
                            current.lastBlockingReason)) {
                trace.terminalCondition =
                        "MIDDLE_BANK_RESERVATION_CHAIN";
            } else if ("ANOTHER_ACTIVE_SEAT_EVENT".equals(
                    current.lastPrerequisiteFailure)) {
                trace.terminalCondition =
                        "ACTIVE_EVENT_TRANSIENT";
            } else if ("WAITING_FOR_SEAT_EVENT".equals(
                    current.lastBlockingReason)) {
                trace.terminalCondition =
                        "OPEN_CHAIN_TO_ROW_EVENT";
            } else {
                trace.terminalCondition =
                        current.lastBlockingReason == null
                                ? "UNCLASSIFIED"
                                : current.lastBlockingReason;
            }

            return trace;
        }

        trace.terminalCondition = "NO_YIELD_OCCUPANT";
        return trace;
    }

    String passengerTraceDetails(int passengerId) {
        Passenger p = passengers.get(passengerId);

        if (p == null) {
            return "P" + passengerId + "{STALE_REFERENCE}";
        }

        return "P" + p.id +
                "{seat=" + p.seatName() +
                ",aisle=" + p.servingAisle +
                ",state=" + p.state +
                ",aisleTile=" + (p.lastAislePosition + 1) +
                ",directBlocker=" +
                p.directBlockingPassengerId +
                ",lastMoveTick=" + p.lastMovementTick +
                ",reason=" + p.lastBlockingReason +
                ",prerequisite=" +
                p.lastPrerequisiteFailure +
                "}";
    }

    boolean allOccupantsSameComponent(
            List<Integer> occupantIds) {

        if (occupantIds.size() <= 1) {
            return true;
        }

        Map<Integer, Set<Integer>> graph =
                new LinkedHashMap<>();

        for (Passenger p : passengers.values()) {
            if (p.state == PassengerState.SEATED ||
                    p.state == PassengerState.WAITING_TO_ENTER) {
                continue;
            }

            graph.computeIfAbsent(
                    p.id,
                    ignored -> new LinkedHashSet<>());

            if (p.directBlockingPassengerId != EMPTY &&
                    passengers.containsKey(
                            p.directBlockingPassengerId)) {
                graph.computeIfAbsent(
                        p.directBlockingPassengerId,
                        ignored -> new LinkedHashSet<>());

                graph.get(p.id).add(
                        p.directBlockingPassengerId);
                graph.get(p.directBlockingPassengerId)
                        .add(p.id);
            }
        }

        int start = occupantIds.get(0);

        if (!graph.containsKey(start)) {
            return false;
        }

        Set<Integer> reached = new LinkedHashSet<>();
        Deque<Integer> queue = new ArrayDeque<>();
        queue.add(start);
        reached.add(start);

        while (!queue.isEmpty()) {
            int current = queue.removeFirst();

            for (int next :
                    graph.getOrDefault(
                            current,
                            Collections.emptySet())) {
                if (reached.add(next)) {
                    queue.addLast(next);
                }
            }
        }

        return reached.containsAll(occupantIds);
    }

    YieldDependencyType classifyYieldDependency(
            List<DependencyTrace> traces,
            int occupantCount) {

        if (occupantCount == 0) {
            return YieldDependencyType.NO_YIELD_OCCUPANT;
        }

        for (DependencyTrace trace : traces) {
            if (trace.cycleDetected) {
                return YieldDependencyType
                        .CLOSED_PASSENGER_CYCLE;
            }
        }

        Set<String> terminalTypes = new LinkedHashSet<>();

        for (DependencyTrace trace : traces) {
            terminalTypes.add(trace.terminalCondition);
        }

        if (terminalTypes.size() > 1) {
            return YieldDependencyType
                    .MIXED_MULTIPLE_YIELD_OCCUPANTS;
        }

        if (terminalTypes.isEmpty()) {
            return YieldDependencyType
                    .DISCONNECTED_OR_STALE_REFERENCE;
        }

        switch (terminalTypes.iterator().next()) {
            case "OPEN_CHAIN_TO_ROW_EVENT":
                return YieldDependencyType
                        .OPEN_CHAIN_TO_ROW_EVENT;
            case "REAR_BOUNDARY_LOCK":
                return YieldDependencyType
                        .REAR_BOUNDARY_LOCK;
            case "ACTIVE_EVENT_TRANSIENT":
                return YieldDependencyType
                        .ACTIVE_EVENT_TRANSIENT;
            case "MIDDLE_BANK_RESERVATION_CHAIN":
                return YieldDependencyType
                        .MIDDLE_BANK_RESERVATION_CHAIN;
            case "DISCONNECTED_OR_STALE_REFERENCE":
                return YieldDependencyType
                        .DISCONNECTED_OR_STALE_REFERENCE;
            default:
                return YieldDependencyType.UNCLASSIFIED;
        }
    }


    AisleContext aisleContextFor(Passenger passenger) {
        return passenger.servingAisle == AisleSide.LEFT
                ? left
                : right;
    }

    FinalSnapshotAnalysis analyseFinalSnapshotFor(
            Passenger passenger) {

        FinalSnapshotAnalysis analysis =
                new FinalSnapshotAnalysis();

        if (passenger == null) {
            analysis.type =
                    FinalSnapshotDependencyType
                            .STALE_OR_DISCONNECTED_REFERENCE;
            analysis.prerequisite =
                    "STALE_OR_DISCONNECTED_REFERENCE";
            analysis.terminalCondition =
                    analysis.prerequisite;
            return analysis;
        }

        AisleContext context =
                aisleContextFor(passenger);

        List<Integer> blockerSeats =
                findBlockingSeatIndices(passenger);

        List<Integer> blockerIds = new ArrayList<>();

        for (int seatIndex : blockerSeats) {
            int blockerId =
                    seats[passenger.row][seatIndex];

            if (blockerId != EMPTY) {
                blockerIds.add(blockerId);
            }
        }

        analysis.requiredBlockers = blockerIds.size();
        analysis.simultaneousEmptyTilesRequired =
                blockerIds.size();
        analysis.activeEventExists =
                context.activeEvent != null;

        int reservationHolder =
                middleBankReservedByPassenger[passenger.row];

        analysis.reservationConflictExists =
                isMiddleBankSeat(passenger.seatIndex) &&
                reservationHolder != EMPTY &&
                reservationHolder != passenger.id;

        boolean boundaryFailure = false;

        for (int offset = 1;
             offset <= blockerIds.size();
             offset++) {

            int tile = passenger.row + offset;
            analysis.requiredTiles.add(tile + 1);

            if (tile >= context.aisle.length) {
                boundaryFailure = true;
                analysis.unavailableTiles.add(tile + 1);
                analysis.occupantIds.add(EMPTY);
                continue;
            }

            int occupantId = context.aisle[tile];

            if (occupantId != EMPTY) {
                analysis.unavailableTiles.add(tile + 1);
                analysis.occupantIds.add(occupantId);
            }
        }

        if (boundaryFailure) {
            analysis.type =
                    FinalSnapshotDependencyType
                            .REAR_BOUNDARY_LOCK;
            analysis.prerequisite =
                    "INSUFFICIENT_REAR_YIELD_SPACE";
            analysis.terminalCondition =
                    analysis.prerequisite;
            return analysis;
        }

        if (analysis.activeEventExists) {
            analysis.type =
                    FinalSnapshotDependencyType
                            .CURRENT_ACTIVE_EVENT;
            analysis.prerequisite =
                    "CURRENT_ACTIVE_EVENT";
            analysis.terminalCondition =
                    analysis.prerequisite;
            return analysis;
        }

        if (analysis.reservationConflictExists) {
            analysis.type =
                    FinalSnapshotDependencyType
                            .CURRENT_RESERVATION_CONFLICT;
            analysis.prerequisite =
                    "CURRENT_RESERVATION_CONFLICT";
            analysis.terminalCondition =
                    analysis.prerequisite;
            return analysis;
        }

        if (analysis.unavailableTiles.isEmpty()) {
            analysis.type =
                    FinalSnapshotDependencyType
                            .YIELD_SPACE_AVAILABLE;
            analysis.prerequisite =
                    "YIELD_SPACE_AVAILABLE";
            analysis.terminalCondition =
                    analysis.prerequisite;
            return analysis;
        }

        analysis.prerequisite =
                "YIELD_TILE_OCCUPIED";

        Set<Integer> visited =
                new LinkedHashSet<>();

        Passenger current = passenger;

        while (current != null &&
                visited.add(current.id)) {

            analysis.rowEventChain.add(current.id);

            FinalSnapshotAnalysis currentAnalysis =
                    current == passenger
                            ? analysis
                            : analyseSingleStepFinalSnapshot(current);

            if (currentAnalysis.type ==
                    FinalSnapshotDependencyType
                            .REAR_BOUNDARY_LOCK) {
                analysis.terminalCondition =
                        "REAR_BOUNDARY_LOCK";
                break;
            }

            if (currentAnalysis.occupantIds.isEmpty()) {
                analysis.terminalCondition =
                        current.state ==
                                PassengerState.WAITING_AT_ROW
                                ? "ROW_EVENT_WITH_AVAILABLE_SPACE"
                                : "OPEN_CHAIN_TERMINAL";
                break;
            }

            int nextId =
                    currentAnalysis.occupantIds.get(0);

            Passenger nextPassenger =
                    passengers.get(nextId);

            if (nextPassenger == null) {
                analysis.terminalCondition =
                        "STALE_OR_DISCONNECTED_REFERENCE";
                break;
            }

            if (nextPassenger.state ==
                    PassengerState.WAITING_AT_ROW) {
                analysis.rowEventChain.add(
                        nextPassenger.id);
                analysis.terminalCondition =
                        "ANOTHER_WAITING_ROW_PASSENGER";
                break;
            }

            current = nextPassenger;
        }

        if (analysis.occupantIds.size() > 1) {
            analysis.type =
                    FinalSnapshotDependencyType
                            .MULTIPLE_MIXED_OCCUPANTS;
        } else {
            int firstOccupantId =
                    analysis.occupantIds.get(0);

            Passenger firstOccupant =
                    passengers.get(firstOccupantId);

            if (firstOccupant != null &&
                    firstOccupant.state ==
                            PassengerState.WAITING_AT_ROW) {
                analysis.type =
                        FinalSnapshotDependencyType
                                .LINKED_ROW_EVENT_CHAIN;
            } else {
                analysis.type =
                        FinalSnapshotDependencyType
                                .YIELD_TILE_OCCUPIED_BY_MOVING_PASSENGER;
            }
        }

        // Analytical counterfactuals only.
        analysis.oneEmptyTileWouldStart =
                analysis.requiredBlockers == 1 &&
                analysis.unavailableTiles.size() == 1;

        analysis.oneTileEarlierHoldWouldPreserve =
                !analysis.unavailableTiles.isEmpty() &&
                analysis.unavailableTiles.size() <=
                        Math.max(1, analysis.requiredBlockers);

        return analysis;
    }

    FinalSnapshotAnalysis analyseSingleStepFinalSnapshot(
            Passenger passenger) {

        FinalSnapshotAnalysis analysis =
                new FinalSnapshotAnalysis();

        if (passenger == null) {
            analysis.type =
                    FinalSnapshotDependencyType
                            .STALE_OR_DISCONNECTED_REFERENCE;
            return analysis;
        }

        AisleContext context =
                aisleContextFor(passenger);

        if (passenger.state !=
                PassengerState.WAITING_AT_ROW) {

            if (passenger.directBlockingPassengerId != EMPTY) {
                analysis.occupantIds.add(
                        passenger.directBlockingPassengerId);
                analysis.type =
                        FinalSnapshotDependencyType
                                .YIELD_TILE_OCCUPIED_BY_MOVING_PASSENGER;
            } else {
                analysis.type =
                        FinalSnapshotDependencyType
                                .STALE_OR_DISCONNECTED_REFERENCE;
            }

            return analysis;
        }

        List<Integer> blockerSeats =
                findBlockingSeatIndices(passenger);

        int blockerCount = 0;

        for (int seatIndex : blockerSeats) {
            if (seats[passenger.row][seatIndex] != EMPTY) {
                blockerCount++;
            }
        }

        analysis.requiredBlockers = blockerCount;

        for (int offset = 1;
             offset <= blockerCount;
             offset++) {

            int tile = passenger.row + offset;
            analysis.requiredTiles.add(tile + 1);

            if (tile >= context.aisle.length) {
                analysis.type =
                        FinalSnapshotDependencyType
                                .REAR_BOUNDARY_LOCK;
                analysis.unavailableTiles.add(tile + 1);
                analysis.occupantIds.add(EMPTY);
                return analysis;
            }

            int occupantId = context.aisle[tile];

            if (occupantId != EMPTY) {
                analysis.unavailableTiles.add(tile + 1);
                analysis.occupantIds.add(occupantId);
            }
        }

        if (analysis.unavailableTiles.isEmpty()) {
            analysis.type =
                    FinalSnapshotDependencyType
                            .YIELD_SPACE_AVAILABLE;
        } else {
            analysis.type =
                    FinalSnapshotDependencyType
                            .LINKED_ROW_EVENT_CHAIN;
        }

        return analysis;
    }

    int countFinalRowEventDependencyChains() {
        int count = 0;

        for (Passenger passenger : passengers.values()) {
            if (passenger.state !=
                    PassengerState.WAITING_AT_ROW) {
                continue;
            }

            FinalSnapshotAnalysis analysis =
                    analyseFinalSnapshotFor(passenger);

            if (analysis.type ==
                        FinalSnapshotDependencyType
                                .LINKED_ROW_EVENT_CHAIN ||
                    analysis.type ==
                        FinalSnapshotDependencyType
                                .YIELD_TILE_OCCUPIED_BY_MOVING_PASSENGER ||
                    analysis.type ==
                        FinalSnapshotDependencyType
                                .MULTIPLE_MIXED_OCCUPANTS ||
                    analysis.type ==
                        FinalSnapshotDependencyType
                                .REAR_BOUNDARY_LOCK) {
                count++;
            }
        }

        return count;
    }

    int currentSeatedCount() {
        int count = 0;
        for (Passenger passenger : passengers.values()) {
            if (passenger.state == PassengerState.SEATED) {
                count++;
            }
        }
        return count;
    }

    void updateGatekeeperEpisodes() {
        int seatedNow = currentSeatedCount();

        for (GatekeeperEpisode e : gatekeeperEpisodes) {
            if (e.closed) {
                continue;
            }

            AisleContext context =
                    e.aisle == AisleSide.LEFT ? left : right;

            if (!e.releaseConfirmed &&
                    e.expectedReleaseTile >= 0 &&
                    e.expectedReleaseTile < context.aisle.length &&
                    context.aisle[e.expectedReleaseTile] == EMPTY) {
                e.releaseConfirmed = true;
                gatekeeperConfirmedReleaseTiles++;
            }

            Passenger upstream = passengers.get(e.upstreamId);
            if (e.upstreamStartedTick < 0 &&
                    upstream != null &&
                    (upstream.state == PassengerState.IN_SEAT_EVENT ||
                     upstream.state == PassengerState.SEATED)) {
                e.upstreamStartedTick = currentTick;
                int delay = currentTick - e.selectionTick;
                if (delay <= 1) gatekeeperUpstreamStartsWithin1++;
                if (delay <= 2) gatekeeperUpstreamStartsWithin2++;
                if (delay <= 5) gatekeeperUpstreamStartsWithin5++;
                if (delay <= 10) gatekeeperUpstreamStartsWithin10++;
            }

            if (currentTick - e.selectionTick >= 10) {
                e.gainAt10Ticks = seatedNow - e.seatedAtSelection;
                e.closed = true;

                if (e.gainAt10Ticks <= 0) {
                    gatekeeperEpisodesWithoutEffect++;
                }

                if (e.gainAt10Ticks > gatekeeperHighestTenTickGain) {
                    gatekeeperHighestTenTickGain = e.gainAt10Ticks;
                    gatekeeperHighestValueEpisodeId = e.id;
                }
            }
        }
    }

    String gatekeeperEpisodeSummary() {
        if (gatekeeperEpisodes.isEmpty()) {
            return "NONE";
        }

        StringBuilder sb = new StringBuilder();
        for (GatekeeperEpisode e : gatekeeperEpisodes) {
            if (sb.length() > 0) sb.append(" || ");
            sb.append("E").append(e.id)
              .append(":upstream=").append(e.upstreamId)
              .append(",gatekeeper=").append(e.gatekeeperId)
              .append(",tile=").append(e.expectedReleaseTile + 1)
              .append(",started=").append(e.gatekeeperStarted)
              .append(",released=").append(e.releaseConfirmed)
              .append(",upstreamStart=").append(e.upstreamStartedTick)
              .append(",gain10=").append(e.gainAt10Ticks);
        }
        return sb.toString();
    }

    String classifyResidualStall(
            ScenarioResult result) {

        if (result.completed) {
            return "COMPLETE_CABIN";
        }

        if (result.remainingWaitingToEnter > 0 &&
                result.remainingMovingInAisle > 0) {
            return "ENTRY_SATURATION_WITH_INTERNAL_CHAIN";
        }

        if (result.finalSnapshotDependencyType ==
                FinalSnapshotDependencyType.REAR_BOUNDARY_LOCK) {
            return "REAR_BOUNDARY_LOCK";
        }

        if (result.finalSnapshotDependencyType ==
                FinalSnapshotDependencyType.LINKED_ROW_EVENT_CHAIN) {
            return "LINKED_ROW_EVENT_CHAIN";
        }

        if (result.finalSnapshotDependencyType ==
                FinalSnapshotDependencyType.YIELD_TILE_OCCUPIED_BY_MOVING_PASSENGER) {
            return "MOVING_PASSENGER_YIELD_CHAIN";
        }

        if (result.activeEventExistsAtStall ||
                result.finalSnapshotDependencyType ==
                FinalSnapshotDependencyType.CURRENT_ACTIVE_EVENT) {
            return "ACTIVE_EVENT_TRANSIENT";
        }

        if (result.reservationConflictExistsAtStall ||
                result.finalSnapshotDependencyType ==
                FinalSnapshotDependencyType.CURRENT_RESERVATION_CONFLICT) {
            return "MIDDLE_BANK_RESERVATION_CONFLICT";
        }

        return "MIXED_OR_UNCLASSIFIED_RESIDUAL";
    }

    double calculateRowZoneConcentration() {
        if (passengers.isEmpty()) return 0.0;
        int centreA = Math.max(1, rows / 3);
        int centreB = Math.min(rows - 2, (2 * rows) / 3);
        long local = passengers.values().stream().filter(p -> Math.min(Math.abs(p.row-centreA), Math.abs(p.row-centreB)) <= 2).count();
        return (double) local / passengers.size();
    }

    double calculateBlockerProneSeatShare() {
        if (passengers.isEmpty()) return 0.0;
        long count = passengers.values().stream().filter(p -> blockerPotentialForSeat(p.seatIndex) >= 2).count();
        return (double) count / passengers.size();
    }

    double calculateAisleAssignmentBalance() {
        if (passengers.isEmpty()) return 1.0;
        long leftCount = passengers.values().stream().filter(p -> p.servingAisle == AisleSide.LEFT).count();
        long rightCount = passengers.size() - leftCount;
        return 1.0 - (Math.abs(leftCount-rightCount) / (double) passengers.size());
    }

    ScenarioResult buildScenarioResult() {
        // EVOLVED MEASUREMENT LAYER:
        // The original backbone counted moves, moving entities and average movement.
        // This experiment preserves that evidence-oriented approach and extends it
        // with waiting, blocking, reservation and completion statistics.
        ScenarioResult result = new ScenarioResult();

        result.scenarioNumber = config.scenarioNumber;
        result.seed = config.seed;
        result.cabin = config.cabin.name;
        result.rows = rows;
        result.seatsPerRow = seatLetters.length;
        result.totalSeats = config.cabin.totalSeats();
        result.passengers = passengers.size();
        result.occupancyRate = config.occupancyRate;
        result.minHeadway = config.minEntryHeadway;
        result.maxHeadway = config.maxEntryHeadway;
        result.datasetFamily = config.datasetMode.name();
        result.rowZoneConcentration = calculateRowZoneConcentration();
        result.blockerProneSeatShare = calculateBlockerProneSeatShare();
        result.aisleAssignmentBalance = calculateAisleAssignmentBalance();
        result.completedTicks = currentTick;
        result.passengersSeated =
                countPassengersInState(PassengerState.SEATED);
        result.completed =
                result.passengersSeated == result.passengers;

        result.leftMoves = left.totalMoves;
        result.rightMoves = right.totalMoves;
        result.leftMaxOccupied = left.maxOccupiedTiles;
        result.rightMaxOccupied = right.maxOccupiedTiles;
        result.leftSeatEvents = left.seatEvents;
        result.rightSeatEvents = right.seatEvents;
        result.leftSingleBlockers = left.singleBlockerEvents;
        result.rightSingleBlockers = right.singleBlockerEvents;
        result.leftMultipleBlockers = left.multipleBlockerEvents;
        result.rightMultipleBlockers = right.multipleBlockerEvents;
        result.leftTemporaryAisleReoccupations =
                left.temporaryAisleReoccupations;
        result.rightTemporaryAisleReoccupations =
                right.temporaryAisleReoccupations;

        result.middleBankReservationRequests =
                middleBankReservationRequests;
        result.middleBankReservationGranted =
                middleBankReservationGranted;
        result.middleBankReservationDenied =
                middleBankReservationDenied;
        result.simultaneousMiddleBankConflicts =
                simultaneousMiddleBankConflicts;

        int totalWaiting = 0;
        int longestWaiting = -1;
        Passenger longestPassenger = null;

        for (Passenger passenger : passengers.values()) {
            totalWaiting += passenger.waitingTicks;

            if (passenger.waitingTicks > longestWaiting) {
                longestWaiting = passenger.waitingTicks;
                longestPassenger = passenger;
            }
        }

        result.averageWaitingTicks =
                passengers.isEmpty()
                        ? 0.0
                        : (double) totalWaiting / passengers.size();

        result.longestWaitingTicks =
                Math.max(0, longestWaiting);

        result.longestWaitingPassenger =
                longestPassenger == null
                        ? ""
                        : longestPassenger.toString();

        result.stalled = stallDetected;
        result.ticksWithoutNewSeat = currentTick - lastSeatProgressTick;
        result.remainingWaitingToEnter =
                countPassengersInState(PassengerState.WAITING_TO_ENTER);
        result.remainingMovingInAisle =
                countPassengersInState(PassengerState.MOVING_IN_AISLE);
        result.remainingWaitingAtRow =
                countPassengersInState(PassengerState.WAITING_AT_ROW);
        result.remainingInSeatEvent =
                countPassengersInState(PassengerState.IN_SEAT_EVENT);
        result.remainingSeated =
                countPassengersInState(PassengerState.SEATED);
        result.dominantBlockingReason = dominantBlockingReason();
        result.dominantBlockingReasonCount = dominantBlockingReasonCount();

        Passenger stalledPassenger = longestUnresolvedPassenger();
        if (stalledPassenger != null) {
            result.stalledPassengerId = stalledPassenger.id;
            result.stalledRow = stalledPassenger.row + 1;
            result.stalledAisle = stalledPassenger.servingAisle.toString();
        } else {
            result.stalledPassengerId = -1;
            result.stalledRow = -1;
            result.stalledAisle = "";
        }

        Passenger dependencyRoot = longestDependencyRoot();
        List<Integer> longestChain =
                dependencyRoot == null
                        ? Collections.emptyList()
                        : buildDependencyChainFrom(dependencyRoot);

        result.longestDependencyChainLength = longestChain.size();
        result.longestDependencyChain = formatDependencyChain(longestChain);
        result.dependencyRootPassengerId =
                dependencyRoot == null ? EMPTY : dependencyRoot.id;

        Passenger terminalPassenger =
                longestChain.isEmpty()
                        ? null
                        : passengers.get(longestChain.get(longestChain.size() - 1));

        result.dependencyTerminalReason =
                terminalReasonFor(terminalPassenger);
        result.passengersIndirectlyAffected =
                countIndirectlyAffectedBy(result.dependencyRootPassengerId);
        result.passengersWithDirectBlocker =
                countPassengersWithDirectBlocker();

        for (Passenger passenger : passengers.values()) {
            result.totalBlockedTicks += passenger.blockedTicks;
            result.totalStationaryTicks += passenger.stationaryTicks;
            result.totalSeatEventRequests += passenger.seatEventRequests;
            result.totalFailedSeatEventStarts += passenger.failedSeatEventStarts;
        }

        Map<Integer, List<Integer>> reverseDependencyGraph =
                buildReverseDependencyGraph();

        Passenger criticalBlocker =
                criticalBlocker(reverseDependencyGraph);

        result.criticalBlockerPassengerId =
                criticalBlocker == null
                        ? EMPTY
                        : criticalBlocker.id;

        result.criticalBlockerIdentity =
                criticalBlocker == null
                        ? "NONE"
                        : criticalBlocker.toString();

        result.criticalBlockerDirectFanOut =
                criticalBlocker == null
                        ? 0
                        : reverseDependencyGraph
                                .getOrDefault(
                                        criticalBlocker.id,
                                        Collections.emptyList())
                                .size();

        result.criticalBlockerTotalDescendants =
                criticalBlocker == null
                        ? 0
                        : collectDescendants(
                                criticalBlocker.id,
                                reverseDependencyGraph)
                                .size();

        result.criticalBlockerTreeDepth =
                criticalBlocker == null
                        ? 0
                        : dependencyTreeDepth(
                                criticalBlocker.id,
                                reverseDependencyGraph,
                                new LinkedHashSet<>());

        result.criticalBlockerTreeSize =
                criticalBlocker == null
                        ? 0
                        : 1 + result.criticalBlockerTotalDescendants;

        result.criticalBlockerTree =
                formatCriticalBlockerTree(
                        criticalBlocker,
                        reverseDependencyGraph);

        result.frozenComponentCount =
                frozenComponentCount(reverseDependencyGraph);

        result.terminalRowEventNodeCount =
                terminalRowEventNodeCount();

        result.unresolvedOutsidePassengers =
                countPassengersInState(
                        PassengerState.WAITING_TO_ENTER);

        result.unresolvedInternalPassengers =
                countPassengersInState(
                        PassengerState.MOVING_IN_AISLE) +
                countPassengersInState(
                        PassengerState.WAITING_AT_ROW) +
                countPassengersInState(
                        PassengerState.IN_SEAT_EVENT);

        result.unresolvedInternalNotInCriticalTree =
                Math.max(
                        0,
                        result.unresolvedInternalPassengers -
                        result.criticalBlockerTreeSize
                );

        result.dominantPrerequisiteFailure =
                dominantPrerequisiteFailure();
        result.dominantPrerequisiteFailureCount =
                dominantPrerequisiteFailureCount();

        Passenger prerequisiteCritical =
                passengers.get(result.criticalBlockerPassengerId);

        if (prerequisiteCritical != null) {
            result.criticalBlockerFinalPrerequisite =
                    prerequisiteCritical.lastPrerequisiteFailure;
            result.criticalBlockerRequiredBlockers =
                    prerequisiteCritical.lastRequiredBlockerCount;
            result.criticalBlockerRequiredYieldTiles =
                    prerequisiteCritical.lastRequiredYieldTiles;
            result.criticalBlockerUnavailableYieldTiles =
                    prerequisiteCritical.lastUnavailableYieldTiles;
            result.criticalBlockerYieldTileOccupants =
                    prerequisiteCritical.lastYieldTileOccupants;
        } else {
            result.criticalBlockerFinalPrerequisite = "NONE";
            result.criticalBlockerRequiredYieldTiles = "NONE";
            result.criticalBlockerUnavailableYieldTiles = "NONE";
            result.criticalBlockerYieldTileOccupants = "NONE";
        }

        for (Passenger passenger : passengers.values()) {
            if (passenger.state == PassengerState.WAITING_AT_ROW) {
                if (passenger.lastRequiredBlockerCount == 0) {
                    result.rowPassengersZeroBlockers++;
                } else if (passenger.lastRequiredBlockerCount == 1) {
                    result.rowPassengersOneBlocker++;
                } else {
                    result.rowPassengersMultipleBlockers++;
                }
            }
        }

        result.totalYieldTileOccupiedFailures =
                totalYieldTileOccupiedFailures;
        result.totalRearBoundaryFailures =
                totalRearBoundaryFailures;
        result.totalReservationFailures =
                totalReservationFailures;
        result.totalActiveEventDeferrals =
                totalActiveEventDeferrals;
        result.totalArbitrationLosses =
                totalArbitrationLosses;

        List<Integer> yieldOccupants =
                prerequisiteCritical == null
                        ? Collections.emptyList()
                        : parsePassengerIds(
                                prerequisiteCritical
                                        .lastYieldTileOccupants);

        List<DependencyTrace> traces = new ArrayList<>();
        StringBuilder traceText = new StringBuilder();
        StringBuilder details = new StringBuilder();
        LinkedHashSet<Integer> cycleMembers =
                new LinkedHashSet<>();
        LinkedHashSet<Integer> rowTerminals =
                new LinkedHashSet<>();

        int maximumTraceLength = 0;
        String yieldTerminalPassenger = "NONE";
        String yieldTerminalCondition = "NONE";

        for (int i = 0; i < yieldOccupants.size(); i++) {
            int occupantId = yieldOccupants.get(i);
            DependencyTrace trace =
                    traceFromPassenger(occupantId);
            traces.add(trace);

            maximumTraceLength = Math.max(
                    maximumTraceLength,
                    trace.path.size());

            if (i > 0) {
                traceText.append(" || ");
                details.append(" || ");
            }

            traceText.append("yieldOccupant[")
                    .append(occupantId)
                    .append("]: ")
                    .append(trace.format(passengers));

            details.append(
                    passengerTraceDetails(occupantId));

            cycleMembers.addAll(trace.cycleMembers);

            if ("OPEN_CHAIN_TO_ROW_EVENT".equals(
                    trace.terminalCondition) &&
                    trace.terminalPassengerId != null) {
                rowTerminals.add(
                        trace.terminalPassengerId);
            }

            if (trace.terminalPassengerId != null) {
                Passenger terminal =
                        passengers.get(
                                trace.terminalPassengerId);
                yieldTerminalPassenger =
                        terminal == null
                                ? "P" +
                                  trace.terminalPassengerId
                                : terminal.toString();
            }

            yieldTerminalCondition =
                    trace.terminalCondition;
        }

        result.requiredYieldOccupantCount =
                yieldOccupants.size();
        result.criticalYieldOccupantTrace =
                traceText.length() == 0
                        ? "NONE"
                        : traceText.toString();
        result.criticalYieldOccupantTraceLength =
                maximumTraceLength;
        result.dependencyCycleDetected =
                !cycleMembers.isEmpty();
        result.dependencyCycleMembers =
                cycleMembers.isEmpty()
                        ? "NONE"
                        : joinIntegers(
                                new ArrayList<>(cycleMembers));
        result.dependencyTerminalPassenger =
                yieldTerminalPassenger;
        result.dependencyTerminalCondition =
                yieldTerminalCondition;
        result.distinctRowEventTerminals =
                rowTerminals.size();
        result.allYieldOccupantsSameFrozenComponent =
                allOccupantsSameComponent(yieldOccupants);
        result.finalStablePrerequisite =
                prerequisiteCritical == null
                        ? "NONE"
                        : prerequisiteCritical
                                .lastPrerequisiteFailure;
        result.criticalYieldOccupantDetails =
                details.length() == 0
                        ? "NONE"
                        : details.toString();
        result.yieldDependencyType =
                classifyYieldDependency(
                        traces,
                        yieldOccupants.size());

        FinalSnapshotAnalysis finalAnalysis =
                analyseFinalSnapshotFor(
                        prerequisiteCritical);

        result.finalSnapshotDependencyType =
                finalAnalysis.type;
        result.finalSnapshotPrerequisite =
                finalAnalysis.prerequisite;
        result.finalSnapshotRequiredBlockers =
                finalAnalysis.requiredBlockers;
        result.finalSnapshotRequiredYieldTiles =
                joinIntegers(finalAnalysis.requiredTiles);
        result.finalSnapshotUnavailableYieldTiles =
                joinIntegers(finalAnalysis.unavailableTiles);
        result.finalSnapshotYieldOccupants =
                joinIntegers(finalAnalysis.occupantIds);
        result.finalSnapshotRowEventChain =
                joinIntegers(finalAnalysis.rowEventChain);
        result.finalSnapshotRowEventChainLength =
                finalAnalysis.rowEventChain.size();

        Set<Integer> rowEventIds =
                new LinkedHashSet<>(
                        finalAnalysis.rowEventChain);
        result.finalSnapshotDistinctRowEvents =
                rowEventIds.size();
        result.finalSnapshotTerminalCondition =
                finalAnalysis.terminalCondition;
        result.oneEmptyTileWouldStartEvent =
                finalAnalysis.oneEmptyTileWouldStart;
        result.oneTileEarlierHoldWouldPreserveSpace =
                finalAnalysis.oneTileEarlierHoldWouldPreserve;
        result.simultaneousEmptyTilesRequired =
                finalAnalysis.simultaneousEmptyTilesRequired;
        result.finalRowEventDependencyChainCount =
                countFinalRowEventDependencyChains();
        result.activeEventExistsAtStall =
                finalAnalysis.activeEventExists;
        result.reservationConflictExistsAtStall =
                finalAnalysis.reservationConflictExists;

        result.guardedForwardYieldEnabled =
                config.guardedForwardYieldEnabled;
        result.experimentMode = config.datasetMode.name();
        // Legacy storage fields are retained for CSV compatibility, but in
        // Experiment 11 they carry dependency-aware scheduling evidence.
        result.releaseImminentEvaluations =
                precedenceCandidatesExamined;
        result.releaseImminentWindowsActivated =
                precedenceSubstitutionsMade;
        result.releaseImminentTilesClustered =
                precedenceChainsFollowed;
        result.releaseImminentPassengerHolds =
                precedenceBlockedByRowEvent;
        result.releaseImminentNextTickEventStarts =
                precedenceSubstitutedEventsStarted;
        result.releaseImminentWindowsWithoutEvent =
                precedenceRejectedOccupiedSpace;
        result.lastClusteredCandidateId =
                precedenceMaximumChainDepth;

        result.gatekeeperUniqueEpisodes = gatekeeperEpisodes.size();
        result.gatekeeperRepeatedSelections = gatekeeperRepeatedSelections;
        result.gatekeeperEventStarts = precedenceSubstitutedEventsStarted;
        result.gatekeeperConfirmedReleaseTiles =
                gatekeeperConfirmedReleaseTiles;
        result.gatekeeperUpstreamStartsWithin1 =
                gatekeeperUpstreamStartsWithin1;
        result.gatekeeperUpstreamStartsWithin2 =
                gatekeeperUpstreamStartsWithin2;
        result.gatekeeperUpstreamStartsWithin5 =
                gatekeeperUpstreamStartsWithin5;
        result.gatekeeperUpstreamStartsWithin10 =
                gatekeeperUpstreamStartsWithin10;
        result.gatekeeperEpisodesWithoutEffect =
                gatekeeperEpisodesWithoutEffect;
        result.gatekeeperHighestTenTickGain =
                gatekeeperHighestTenTickGain;
        result.gatekeeperHighestValueEpisodeId =
                gatekeeperHighestValueEpisodeId;
        result.gatekeeperEpisodeSummary = gatekeeperEpisodeSummary();

        result.priorityCandidatesEvaluated =
                priorityCandidatesEvaluated;
        result.priorityEligibleGatekeepers =
                priorityEligibleGatekeepers;
        result.prioritySelectionsMade =
                prioritySelectionsMade;
        result.prioritySelectionsDifferentFromFirstValid =
                prioritySelectionsDifferentFromFirstValid;
        result.prioritySelectedScoreTotal =
                prioritySelectedScoreTotal;
        result.priorityHighestSelectedScore =
                priorityHighestSelectedScore;
        result.priorityDirectReleasesTotal =
                priorityDirectReleasesTotal;
        result.priorityQueuedBehindTotal =
                priorityQueuedBehindTotal;

        result.rearwardYieldAttempts =
                rearwardYieldAttempts;
        result.rearBoundaryFailuresEncountered =
                rearBoundaryFailuresEncountered;
        result.forwardYieldFallbackEvaluations =
                forwardYieldFallbackEvaluations;
        result.forwardYieldFallbackGrants =
                forwardYieldFallbackGrants;
        result.forwardYieldFallbackOccupiedRejections =
                forwardYieldFallbackOccupiedRejections;
        result.forwardYieldEventsStarted =
                forwardYieldEventsStarted;
        result.forwardYieldEventsCompleted =
                forwardYieldEventsCompleted;
        result.blockersStagedTowardCockpit =
                blockersStagedTowardCockpit;

        result.forwardYieldGuardEvaluations =
                forwardYieldGuardEvaluations;
        result.forwardYieldGuardApprovals =
                forwardYieldGuardApprovals;
        result.forwardYieldGuardRejections =
                forwardYieldGuardRejections;
        result.guardRejectedWaitingRowPassenger =
                guardRejectedWaitingRowPassenger;
        result.guardRejectedVulnerableMover =
                guardRejectedVulnerableMover;
        result.guardRejectedReservationConflict =
                guardRejectedReservationConflict;

        result.residualStallFamily =
                classifyResidualStall(result);

        result.congestionClustersDetected = congestionClustersDetected;
        result.congestionDependencyRegionsDetected = congestionDependencyRegionsDetected;
        result.congestionClusterActivationsWithoutIntervention =
                congestionClusterActivationsWithoutIntervention;
        result.congestionClusterExecutableCandidates =
                congestionClusterExecutableCandidates;
        result.congestionClusterRejectedCandidates =
                congestionClusterRejectedCandidates;
        result.congestionClusterCandidatesEvaluated =
                congestionClusterCandidatesEvaluated;
        result.congestionClusterSelections = congestionClusterSelections;
        result.congestionClusterEventStarts = congestionClusterEventStarts;
        result.congestionClusterLargestSize = congestionClusterLargestSize;
        result.congestionClusterSelectedReleasePotential =
                congestionClusterSelectedReleasePotential;
        result.lastCongestionCluster = lastCongestionCluster;
        lifecycleRegionsActiveAtEnd = activeDependencyRegions.size();
        result.lifecycleRegionsCreated = lifecycleRegionsCreated;
        result.lifecycleRegionUpdates = lifecycleRegionUpdates;
        result.lifecycleRegionsDissolved = lifecycleRegionsDissolved;
        result.lifecycleReconstructionsAvoided = lifecycleReconstructionsAvoided;
        result.lifecycleLargestLifetime = lifecycleLargestLifetime;
        result.lifecycleMaxSimultaneousActive = lifecycleMaxSimultaneousActive;
        result.lifecycleRegionsActiveAtEnd = lifecycleRegionsActiveAtEnd;
        result.lastLifecycleEvent = lastLifecycleEvent;
        result.lifecycleConfirmationDeferrals = lifecycleConfirmationDeferrals;
        result.lifecycleRegionsConfirmed = lifecycleRegionsConfirmed;
        result.lifecycleRegionsDissolvedBeforeConfirmation = lifecycleRegionsDissolvedBeforeConfirmation;
        result.stateChangeEvaluationsSuppressed = stateChangeEvaluationsSuppressed;
        result.stateChangesReopeningEvaluation = stateChangesReopeningEvaluation;
        result.regionPriorityCompetitions = regionPriorityCompetitions;
        result.regionPriorityRegionsRanked = regionPriorityRegionsRanked;
        result.regionPrioritySelections = regionPrioritySelections;
        result.regionPriorityLowerRankDeferrals = regionPriorityLowerRankDeferrals;
        result.regionPriorityHighestScore = regionPriorityHighestScore;
        result.regionPrioritySelectedScoreTotal = regionPrioritySelectedScoreTotal;
        result.regionPriorityLeftWins = regionPriorityLeftWins;
        result.regionPriorityRightWins = regionPriorityRightWins;
        result.regionPriorityTies = regionPriorityTies;
        result.lastRegionPriorityDecision = lastRegionPriorityDecision;

        return result;
    }

    // ======================== FILE OUTPUT ========================

    static void ensureResultsDirectory() {
        try {
            Path parent = HUMAN_RESULTS_PATH.getParent();

            if (parent != null) {
                Files.createDirectories(parent);
            }

        } catch (IOException ex) {
            throw new IllegalStateException(
                    "Unable to create results directory: " +
                    HUMAN_RESULTS_PATH.getParent(),
                    ex
            );
        }
    }

    static void ensureCsvHeader() {
        if (Files.exists(CSV_RESULTS_PATH)) {
            return;
        }

        String header =
                "experimentNumber,experimentTitle,timestamp,experimentSeed,scenario,scenarioSeed," +
                "cabin,rows,seatsPerRow,totalSeats,passengers," +
                "occupancyPercent,minHeadway,maxHeadway," +
                "completedTicks,completed,passengersSeated," +
                "leftMoves,rightMoves,leftMaxOccupied,rightMaxOccupied," +
                "leftSeatEvents,rightSeatEvents," +
                "leftSingleBlockers,rightSingleBlockers," +
                "leftMultipleBlockers,rightMultipleBlockers," +
                "leftTemporaryAisleReoccupations," +
                "rightTemporaryAisleReoccupations," +
                "middleBankReservationRequests," +
                "middleBankReservationGranted," +
                "middleBankReservationDenied," +
                "simultaneousMiddleBankConflicts," +
                "averageWaitingTicks,longestWaitingTicks," +
                "longestWaitingPassenger,stalled,ticksWithoutNewSeat," +
                "remainingWaitingToEnter,remainingMovingInAisle," +
                "remainingWaitingAtRow,remainingInSeatEvent," +
                "dominantBlockingReason,dominantBlockingReasonCount," +
                "stalledPassengerId,stalledRow,stalledAisle," +
                "longestDependencyChainLength,longestDependencyChain," +
                "dependencyRootPassengerId,dependencyTerminalReason," +
                "passengersIndirectlyAffected,passengersWithDirectBlocker," +
                "totalBlockedTicks,totalStationaryTicks," +
                "totalSeatEventRequests,totalFailedSeatEventStarts," +
                "criticalBlockerPassengerId,criticalBlockerIdentity," +
                "criticalBlockerDirectFanOut,criticalBlockerTotalDescendants," +
                "criticalBlockerTreeSize,criticalBlockerTreeDepth," +
                "frozenComponentCount,terminalRowEventNodeCount," +
                "unresolvedInternalPassengers,unresolvedOutsidePassengers," +
                "unresolvedInternalNotInCriticalTree,criticalBlockerTree," +
                "dominantPrerequisiteFailure,dominantPrerequisiteFailureCount," +
                "criticalBlockerFinalPrerequisite," +
                "criticalBlockerRequiredBlockers," +
                "criticalBlockerRequiredYieldTiles," +
                "criticalBlockerUnavailableYieldTiles," +
                "criticalBlockerYieldTileOccupants," +
                "rowPassengersZeroBlockers,rowPassengersOneBlocker," +
                "rowPassengersMultipleBlockers," +
                "totalYieldTileOccupiedFailures,totalRearBoundaryFailures," +
                "totalReservationFailures,totalActiveEventDeferrals," +
                "totalArbitrationLosses,yieldDependencyType," +
                "criticalYieldOccupantTrace,criticalYieldOccupantTraceLength," +
                "dependencyCycleDetected,dependencyCycleMembers," +
                "dependencyTerminalPassenger,dependencyTerminalCondition," +
                "distinctRowEventTerminals,requiredYieldOccupantCount," +
                "allYieldOccupantsSameFrozenComponent,finalStablePrerequisite," +
                "criticalYieldOccupantDetails," +
                "finalSnapshotDependencyType,finalSnapshotPrerequisite," +
                "finalSnapshotRequiredBlockers," +
                "finalSnapshotRequiredYieldTiles," +
                "finalSnapshotUnavailableYieldTiles," +
                "finalSnapshotYieldOccupants," +
                "finalSnapshotRowEventChain," +
                "finalSnapshotRowEventChainLength," +
                "finalSnapshotDistinctRowEvents," +
                "finalSnapshotTerminalCondition," +
                "oneEmptyTileWouldStartEvent," +
                "oneTileEarlierHoldWouldPreserveSpace," +
                "simultaneousEmptyTilesRequired," +
                "finalRowEventDependencyChainCount," +
                "activeEventExistsAtStall," +
                "reservationConflictExistsAtStall,experimentMode," +
                "guardedForwardYieldEnabled," +
                "precedenceCandidatesExamined," +
                "precedenceSubstitutionsMade," +
                "precedenceChainsFollowed," +
                "precedenceBlockedByRowEvent," +
                "precedenceSubstitutedEventsStarted," +
                "precedenceRejectedOccupiedSpace," +
                "precedenceMaximumChainDepth," +
                "gatekeeperUniqueEpisodes," +
                "gatekeeperRepeatedSelections," +
                "gatekeeperEventStarts," +
                "gatekeeperConfirmedReleaseTiles," +
                "gatekeeperUpstreamStartsWithin1," +
                "gatekeeperUpstreamStartsWithin2," +
                "gatekeeperUpstreamStartsWithin5," +
                "gatekeeperUpstreamStartsWithin10," +
                "gatekeeperEpisodesWithoutEffect," +
                "gatekeeperHighestTenTickGain," +
                "gatekeeperHighestValueEpisodeId," +
                "gatekeeperEpisodeSummary," +
                "priorityCandidatesEvaluated," +
                "priorityEligibleGatekeepers," +
                "prioritySelectionsMade," +
                "prioritySelectionsDifferentFromFirstValid," +
                "prioritySelectedScoreTotal," +
                "priorityHighestSelectedScore," +
                "priorityDirectReleasesTotal," +
                "priorityQueuedBehindTotal," +
                "rearwardYieldAttempts," +
                "rearBoundaryFailuresEncountered," +
                "forwardYieldFallbackEvaluations," +
                "forwardYieldFallbackGrants," +
                "forwardYieldFallbackOccupiedRejections," +
                "forwardYieldEventsStarted," +
                "forwardYieldEventsCompleted," +
                "blockersStagedTowardCockpit," +
                "forwardYieldGuardEvaluations," +
                "forwardYieldGuardApprovals," +
                "forwardYieldGuardRejections," +
                "guardRejectedWaitingRowPassenger," +
                "guardRejectedVulnerableMover," +
                "guardRejectedReservationConflict," +
                "regionPriorityCompetitions,regionPriorityRegionsRanked," +
                "regionPrioritySelections,regionPriorityLowerRankDeferrals," +
                "regionPriorityHighestScore,regionPrioritySelectedScoreTotal," +
                "regionPriorityLeftWins,regionPriorityRightWins,regionPriorityTies," +
                "lastRegionPriorityDecision,residualStallFamily,status";

        appendLine(CSV_RESULTS_PATH, header);
    }

    static void appendHumanReadableResult(ScenarioResult result) {
        String timestamp =
                LocalDateTime.now().format(
                        DateTimeFormatter.ofPattern(
                                "yyyy-MM-dd HH:mm:ss"
                        )
                );

        String text =
                "\n============================================================\n" +
                "WHOLE-CABIN HYBRID BOARDING SCENARIO\n" +
                "RESEARCH EXPERIMENT " +
                String.format(Locale.ROOT, "%02d", EXPERIMENT_NUMBER) + "\n" +
                EXPERIMENT_TITLE + "\n" +
                "============================================================\n" +
                "Date/Time: " + timestamp + "\n" +
                "Experiment mode: " +
                result.experimentMode + "\n" +
                "Frozen architecture enabled: true\n" +
                "Experiment seed: " + EXPERIMENT_SEED + "\n" +
                "Scenario number: " + result.scenarioNumber + "\n" +
                "Scenario seed: " + result.seed + "\n\n" +

                "CABIN\n" +
                "-----\n" +
                "Configuration: " + result.cabin + "\n" +
                "Rows: " + result.rows + "\n" +
                "Seats per row: " + result.seatsPerRow + "\n" +
                "Total seats: " + result.totalSeats + "\n" +
                "Passengers: " + result.passengers + "\n" +
                String.format(
                        Locale.ROOT,
                        "Occupancy: %.0f%%%n",
                        result.occupancyRate * 100.0
                ) +
                "Entry headway range: " +
                result.minHeadway + "-" + result.maxHeadway + "\n\n" +
                "DATASET CHARACTERISTICS\n" +
                "-----------------------\n" +
                "Dataset family: " + result.datasetFamily + "\n" +
                String.format(Locale.ROOT, "Row-zone concentration (diagnostic control): %.2f%%%n", result.rowZoneConcentration * 100.0) +
                String.format(Locale.ROOT, "Blocker-prone seat share (topology component): %.2f%%%n", result.blockerProneSeatShare * 100.0) +
                String.format(Locale.ROOT, "Aisle-assignment balance: %.2f%%%n%n", result.aisleAssignmentBalance * 100.0) +

                "OUTCOME\n" +
                "-------\n" +
                "Status: " + result.status() + "\n" +
                "Completed ticks: " + result.completedTicks + "\n" +
                "Passengers seated: " +
                result.passengersSeated + "/" + result.passengers + "\n" +
                String.format(
                        Locale.ROOT,
                        "Average waiting ticks: %.2f%n",
                        result.averageWaitingTicks
                ) +
                "Longest waiting passenger: " +
                result.longestWaitingPassenger + "\n" +
                "Longest waiting ticks: " +
                result.longestWaitingTicks + "\n\n" +

                "ROW-BOUNDED CONGESTION CLUSTERING\n" +
                "----------------------------------\n" +
                "Dependency regions detected: " + result.congestionDependencyRegionsDetected + "\n" +
                "Clusters detected: " + result.congestionClustersDetected + "\n" +
                "Activations without intervention: " + result.congestionClusterActivationsWithoutIntervention + "\n" +
                "Cluster candidates evaluated: " + result.congestionClusterCandidatesEvaluated + "\n" +
                "Executable cluster candidates: " + result.congestionClusterExecutableCandidates + "\n" +
                "Rejected cluster candidates: " + result.congestionClusterRejectedCandidates + "\n" +
                "Cluster selections: " + result.congestionClusterSelections + "\n" +
                "Cluster-selected events started: " + result.congestionClusterEventStarts + "\n" +
                "Largest detected cluster: " + result.congestionClusterLargestSize + "\n" +
                "Selected release potential total: " + result.congestionClusterSelectedReleasePotential + "\n" +
                "Last cluster decision: " + result.lastCongestionCluster + "\n\n" +
                "REACTIVE REGION LIFECYCLE\n" +
                "-------------------------\n" +
                "Regions created: " + result.lifecycleRegionsCreated + "\n" +
                "Region updates: " + result.lifecycleRegionUpdates + "\n" +
                "Regions dissolved: " + result.lifecycleRegionsDissolved + "\n" +
                "Repeated reconstructions avoided: " + result.lifecycleReconstructionsAvoided + "\n" +
                "Largest region lifetime: " + result.lifecycleLargestLifetime + "\n" +
                "Maximum simultaneous active regions: " + result.lifecycleMaxSimultaneousActive + "\n" +
                "Regions active at termination: " + result.lifecycleRegionsActiveAtEnd + "\n" +
                "Last lifecycle event: " + result.lastLifecycleEvent + "\n" +
                "Confirmation deferral ticks: " + result.lifecycleConfirmationDeferrals + "\n" +
                "Regions reaching confirmation: " + result.lifecycleRegionsConfirmed + "\n" +
                "Regions dissolved before confirmation: " + result.lifecycleRegionsDissolvedBeforeConfirmation + "\n" +
                "Unchanged-state evaluations suppressed: " + result.stateChangeEvaluationsSuppressed + "\n" +
                "State changes reopening evaluation: " + result.stateChangesReopeningEvaluation + "\n" +
                "Region priority competitions: " + result.regionPriorityCompetitions + "\n" +
                "Regions ranked: " + result.regionPriorityRegionsRanked + "\n" +
                "Hierarchical selections: " + result.regionPrioritySelections + "\n" +
                "Lower-priority evaluations deferred: " + result.regionPriorityLowerRankDeferrals + "\n" +
                "Highest selected priority score: " + result.regionPriorityHighestScore + "\n" +
                "Left/right priority wins: " + result.regionPriorityLeftWins + "/" + result.regionPriorityRightWins + "\n" +
                "Priority ties: " + result.regionPriorityTies + "\n" +
                "Last priority decision: " + result.lastRegionPriorityDecision + "\n\n" +

                "LEFT AISLE\n" +
                "----------\n" +
                "Total moves: " + result.leftMoves + "\n" +
                "Maximum occupied tiles: " +
                result.leftMaxOccupied + "\n" +
                "Seat events: " + result.leftSeatEvents + "\n" +
                "Single-blocker events: " +
                result.leftSingleBlockers + "\n" +
                "Multiple-blocker events: " +
                result.leftMultipleBlockers + "\n" +
                "Temporary aisle reoccupations: " +
                result.leftTemporaryAisleReoccupations + "\n\n" +

                "RIGHT AISLE\n" +
                "-----------\n" +
                "Total moves: " + result.rightMoves + "\n" +
                "Maximum occupied tiles: " +
                result.rightMaxOccupied + "\n" +
                "Seat events: " + result.rightSeatEvents + "\n" +
                "Single-blocker events: " +
                result.rightSingleBlockers + "\n" +
                "Multiple-blocker events: " +
                result.rightMultipleBlockers + "\n" +
                "Temporary aisle reoccupations: " +
                result.rightTemporaryAisleReoccupations + "\n\n" +

                "MIDDLE-BANK COORDINATION\n" +
                "------------------------\n" +
                "Reservation requests: " +
                result.middleBankReservationRequests + "\n" +
                "Reservations granted: " +
                result.middleBankReservationGranted + "\n" +
                "Reservations denied/waited: " +
                result.middleBankReservationDenied + "\n" +
                "Simultaneous opposite-aisle conflicts: " +
                result.simultaneousMiddleBankConflicts + "\n\n" +

                "STALL DIAGNOSTICS\n" +
                "-----------------\n" +
                "Stall detected: " + result.stalled + "\n" +
                "Ticks without new seated passenger: " +
                result.ticksWithoutNewSeat + "\n" +
                "Remaining waiting to enter: " +
                result.remainingWaitingToEnter + "\n" +
                "Remaining moving in aisle: " +
                result.remainingMovingInAisle + "\n" +
                "Remaining waiting at row: " +
                result.remainingWaitingAtRow + "\n" +
                "Remaining inside seat event: " +
                result.remainingInSeatEvent + "\n" +
                "Dominant blocking reason: " +
                result.dominantBlockingReason + "\n" +
                "Dominant blocking reason count: " +
                result.dominantBlockingReasonCount + "\n" +
                "Longest unresolved passenger ID: " +
                result.stalledPassengerId + "\n" +
                "Longest unresolved row: " +
                result.stalledRow + "\n" +
                "Longest unresolved aisle: " +
                result.stalledAisle + "\n\n" +

                "DEPENDENCY-CHAIN ANALYSIS\n" +
                "-------------------------\n" +
                "Longest dependency-chain length: " +
                result.longestDependencyChainLength + "\n" +
                "Longest dependency chain: " +
                result.longestDependencyChain + "\n" +
                "Dependency root passenger ID: " +
                result.dependencyRootPassengerId + "\n" +
                "Dependency terminal reason: " +
                result.dependencyTerminalReason + "\n" +
                "Passengers indirectly affected by root: " +
                result.passengersIndirectlyAffected + "\n" +
                "Passengers with a direct blocker: " +
                result.passengersWithDirectBlocker + "\n" +
                "Total blocked ticks: " +
                result.totalBlockedTicks + "\n" +
                "Total stationary ticks: " +
                result.totalStationaryTicks + "\n" +
                "Seat-event requests: " +
                result.totalSeatEventRequests + "\n" +
                "Failed seat-event starts: " +
                result.totalFailedSeatEventStarts + "\n\n" +

                "DEPENDENCY FAN-OUT / CRITICAL-BLOCKER TREE\n" +
                "-----------------------------------------\n" +
                "Critical blocker passenger ID: " +
                result.criticalBlockerPassengerId + "\n" +
                "Critical blocker identity: " +
                result.criticalBlockerIdentity + "\n" +
                "Direct fan-out: " +
                result.criticalBlockerDirectFanOut + "\n" +
                "Total indirect descendants: " +
                result.criticalBlockerTotalDescendants + "\n" +
                "Critical tree size including blocker: " +
                result.criticalBlockerTreeSize + "\n" +
                "Critical tree depth: " +
                result.criticalBlockerTreeDepth + "\n" +
                "Frozen internal components: " +
                result.frozenComponentCount + "\n" +
                "Terminal WAITING_FOR_SEAT_EVENT nodes: " +
                result.terminalRowEventNodeCount + "\n" +
                "Unresolved internal passengers: " +
                result.unresolvedInternalPassengers + "\n" +
                "Unresolved outside passengers: " +
                result.unresolvedOutsidePassengers + "\n" +
                "Unresolved internal passengers outside critical tree: " +
                result.unresolvedInternalNotInCriticalTree + "\n" +
                "Critical blocker tree:\n" +
                result.criticalBlockerTree + "\n\n" +

                "SEAT-EVENT PREREQUISITE ANALYSIS\n" +
                "--------------------------------\n" +
                "Dominant prerequisite failure: " +
                result.dominantPrerequisiteFailure + "\n" +
                "Dominant prerequisite failure count: " +
                result.dominantPrerequisiteFailureCount + "\n" +
                "Critical blocker final prerequisite: " +
                result.criticalBlockerFinalPrerequisite + "\n" +
                "Critical blocker required blockers: " +
                result.criticalBlockerRequiredBlockers + "\n" +
                "Critical blocker required yield tiles: " +
                result.criticalBlockerRequiredYieldTiles + "\n" +
                "Critical blocker unavailable yield tiles: " +
                result.criticalBlockerUnavailableYieldTiles + "\n" +
                "Critical blocker yield-tile occupants: " +
                result.criticalBlockerYieldTileOccupants + "\n" +
                "Waiting-row passengers with zero blockers: " +
                result.rowPassengersZeroBlockers + "\n" +
                "Waiting-row passengers with one blocker: " +
                result.rowPassengersOneBlocker + "\n" +
                "Waiting-row passengers with multiple blockers: " +
                result.rowPassengersMultipleBlockers + "\n" +
                "Yield-tile occupied failures: " +
                result.totalYieldTileOccupiedFailures + "\n" +
                "Rear-boundary failures: " +
                result.totalRearBoundaryFailures + "\n" +
                "Middle-bank reservation failures: " +
                result.totalReservationFailures + "\n" +
                "Active-event deferrals: " +
                result.totalActiveEventDeferrals + "\n" +
                "Simultaneous arbitration losses: " +
                result.totalArbitrationLosses + "\n\n" +

                "YIELD-TILE OCCUPANT TRACE / CYCLE ANALYSIS\n" +
                "-----------------------------------------\n" +
                "Yield dependency classification: " +
                result.yieldDependencyType + "\n" +
                "Required yield occupants: " +
                result.requiredYieldOccupantCount + "\n" +
                "Trace length: " +
                result.criticalYieldOccupantTraceLength + "\n" +
                "Yield-occupant trace: " +
                result.criticalYieldOccupantTrace + "\n" +
                "Cycle detected: " +
                result.dependencyCycleDetected + "\n" +
                "Cycle members: " +
                result.dependencyCycleMembers + "\n" +
                "Terminal passenger: " +
                result.dependencyTerminalPassenger + "\n" +
                "Terminal condition: " +
                result.dependencyTerminalCondition + "\n" +
                "Distinct row-event terminals: " +
                result.distinctRowEventTerminals + "\n" +
                "All yield occupants in same frozen component: " +
                result.allYieldOccupantsSameFrozenComponent + "\n" +
                "Final stable prerequisite: " +
                result.finalStablePrerequisite + "\n" +
                "Yield-occupant details: " +
                result.criticalYieldOccupantDetails + "\n\n" +

                "FINAL STABLE ROW-EVENT DEPENDENCY ANALYSIS\n" +
                "------------------------------------------\n" +
                "Final snapshot dependency type: " +
                result.finalSnapshotDependencyType + "\n" +
                "Final snapshot prerequisite: " +
                result.finalSnapshotPrerequisite + "\n" +
                "Current required blockers: " +
                result.finalSnapshotRequiredBlockers + "\n" +
                "Current required yield tiles: " +
                result.finalSnapshotRequiredYieldTiles + "\n" +
                "Current unavailable yield tiles: " +
                result.finalSnapshotUnavailableYieldTiles + "\n" +
                "Current yield-tile occupants: " +
                result.finalSnapshotYieldOccupants + "\n" +
                "Final row-event chain: " +
                result.finalSnapshotRowEventChain + "\n" +
                "Final row-event chain length: " +
                result.finalSnapshotRowEventChainLength + "\n" +
                "Distinct row events in chain: " +
                result.finalSnapshotDistinctRowEvents + "\n" +
                "Final terminal condition: " +
                result.finalSnapshotTerminalCondition + "\n" +
                "One empty tile would start event: " +
                result.oneEmptyTileWouldStartEvent + "\n" +
                "One-tile-earlier hold would preserve space: " +
                result.oneTileEarlierHoldWouldPreserveSpace + "\n" +
                "Simultaneous empty tiles required: " +
                result.simultaneousEmptyTilesRequired + "\n" +
                "Final row-event dependency chains in aircraft: " +
                result.finalRowEventDependencyChainCount + "\n" +
                "Active event exists at stall: " +
                result.activeEventExistsAtStall + "\n" +
                "Reservation conflict exists at stall: " +
                result.reservationConflictExistsAtStall + "\n\n" +
                "DEPENDENCY-AWARE SCHEDULER EVIDENCE\\n" +
                "---------------------------------------\\n" +
                "Experiment mode: " +
                result.experimentMode + "\\n" +
                "Deterministic replay mode: " +
                result.guardedForwardYieldEnabled + "\\n" +
                "Normal candidates examined: " +
                result.releaseImminentEvaluations + "\\n" +
                "Precedence substitutions made: " +
                result.releaseImminentWindowsActivated + "\\n" +
                "Dependency chains followed: " +
                result.releaseImminentTilesClustered + "\\n" +
                "Blocked by waiting-row occupant: " +
                result.releaseImminentPassengerHolds + "\\n" +
                "Substituted events started: " +
                result.releaseImminentNextTickEventStarts + "\\n" +
                "Occupied-space rejections: " +
                result.releaseImminentWindowsWithoutEvent + "\\n" +
                "Maximum scheduling-chain depth: " +
                result.lastClusteredCandidateId + "\\n" +
                "============================================================\\n";

        appendLine(HUMAN_RESULTS_PATH, text);
    }

    static void appendCsvResult(ScenarioResult result) {
        String timestamp =
                LocalDateTime.now().format(
                        DateTimeFormatter.ofPattern(
                                "yyyy-MM-dd HH:mm:ss"
                        )
                );

        String line = String.join(",",
                String.valueOf(EXPERIMENT_NUMBER),
                csv(EXPERIMENT_TITLE),
                csv(timestamp),
                String.valueOf(EXPERIMENT_SEED),
                String.valueOf(result.scenarioNumber),
                String.valueOf(result.seed),
                csv(result.cabin),
                String.valueOf(result.rows),
                String.valueOf(result.seatsPerRow),
                String.valueOf(result.totalSeats),
                String.valueOf(result.passengers),
                String.format(
                        Locale.ROOT,
                        "%.0f",
                        result.occupancyRate * 100.0
                ),
                String.valueOf(result.minHeadway),
                String.valueOf(result.maxHeadway),
                csv(result.datasetFamily),
                String.format(Locale.ROOT, "%.6f", result.rowZoneConcentration),
                String.format(Locale.ROOT, "%.6f", result.blockerProneSeatShare),
                String.format(Locale.ROOT, "%.6f", result.aisleAssignmentBalance),
                String.valueOf(result.completedTicks),
                String.valueOf(result.completed),
                String.valueOf(result.passengersSeated),
                String.valueOf(result.leftMoves),
                String.valueOf(result.rightMoves),
                String.valueOf(result.leftMaxOccupied),
                String.valueOf(result.rightMaxOccupied),
                String.valueOf(result.leftSeatEvents),
                String.valueOf(result.rightSeatEvents),
                String.valueOf(result.leftSingleBlockers),
                String.valueOf(result.rightSingleBlockers),
                String.valueOf(result.leftMultipleBlockers),
                String.valueOf(result.rightMultipleBlockers),
                String.valueOf(
                        result.leftTemporaryAisleReoccupations
                ),
                String.valueOf(
                        result.rightTemporaryAisleReoccupations
                ),
                String.valueOf(
                        result.middleBankReservationRequests
                ),
                String.valueOf(
                        result.middleBankReservationGranted
                ),
                String.valueOf(
                        result.middleBankReservationDenied
                ),
                String.valueOf(
                        result.simultaneousMiddleBankConflicts
                ),
                String.format(
                        Locale.ROOT,
                        "%.2f",
                        result.averageWaitingTicks
                ),
                String.valueOf(result.longestWaitingTicks),
                csv(result.longestWaitingPassenger),
                String.valueOf(result.stalled),
                String.valueOf(result.ticksWithoutNewSeat),
                String.valueOf(result.remainingWaitingToEnter),
                String.valueOf(result.remainingMovingInAisle),
                String.valueOf(result.remainingWaitingAtRow),
                String.valueOf(result.remainingInSeatEvent),
                csv(result.dominantBlockingReason),
                String.valueOf(result.dominantBlockingReasonCount),
                String.valueOf(result.stalledPassengerId),
                String.valueOf(result.stalledRow),
                csv(result.stalledAisle),
                String.valueOf(result.longestDependencyChainLength),
                csv(result.longestDependencyChain),
                String.valueOf(result.dependencyRootPassengerId),
                csv(result.dependencyTerminalReason),
                String.valueOf(result.passengersIndirectlyAffected),
                String.valueOf(result.passengersWithDirectBlocker),
                String.valueOf(result.totalBlockedTicks),
                String.valueOf(result.totalStationaryTicks),
                String.valueOf(result.totalSeatEventRequests),
                String.valueOf(result.totalFailedSeatEventStarts),
                String.valueOf(result.criticalBlockerPassengerId),
                csv(result.criticalBlockerIdentity),
                String.valueOf(result.criticalBlockerDirectFanOut),
                String.valueOf(result.criticalBlockerTotalDescendants),
                String.valueOf(result.criticalBlockerTreeSize),
                String.valueOf(result.criticalBlockerTreeDepth),
                String.valueOf(result.frozenComponentCount),
                String.valueOf(result.terminalRowEventNodeCount),
                String.valueOf(result.unresolvedInternalPassengers),
                String.valueOf(result.unresolvedOutsidePassengers),
                String.valueOf(result.unresolvedInternalNotInCriticalTree),
                csv(result.criticalBlockerTree),
                csv(result.dominantPrerequisiteFailure),
                String.valueOf(result.dominantPrerequisiteFailureCount),
                csv(result.criticalBlockerFinalPrerequisite),
                String.valueOf(result.criticalBlockerRequiredBlockers),
                csv(result.criticalBlockerRequiredYieldTiles),
                csv(result.criticalBlockerUnavailableYieldTiles),
                csv(result.criticalBlockerYieldTileOccupants),
                String.valueOf(result.rowPassengersZeroBlockers),
                String.valueOf(result.rowPassengersOneBlocker),
                String.valueOf(result.rowPassengersMultipleBlockers),
                String.valueOf(result.totalYieldTileOccupiedFailures),
                String.valueOf(result.totalRearBoundaryFailures),
                String.valueOf(result.totalReservationFailures),
                String.valueOf(result.totalActiveEventDeferrals),
                String.valueOf(result.totalArbitrationLosses),
                csv(String.valueOf(result.yieldDependencyType)),
                csv(result.criticalYieldOccupantTrace),
                String.valueOf(result.criticalYieldOccupantTraceLength),
                String.valueOf(result.dependencyCycleDetected),
                csv(result.dependencyCycleMembers),
                csv(result.dependencyTerminalPassenger),
                csv(result.dependencyTerminalCondition),
                String.valueOf(result.distinctRowEventTerminals),
                String.valueOf(result.requiredYieldOccupantCount),
                String.valueOf(
                        result.allYieldOccupantsSameFrozenComponent),
                csv(result.finalStablePrerequisite),
                csv(result.criticalYieldOccupantDetails),
                csv(String.valueOf(
                        result.finalSnapshotDependencyType)),
                csv(result.finalSnapshotPrerequisite),
                String.valueOf(
                        result.finalSnapshotRequiredBlockers),
                csv(result.finalSnapshotRequiredYieldTiles),
                csv(result.finalSnapshotUnavailableYieldTiles),
                csv(result.finalSnapshotYieldOccupants),
                csv(result.finalSnapshotRowEventChain),
                String.valueOf(
                        result.finalSnapshotRowEventChainLength),
                String.valueOf(
                        result.finalSnapshotDistinctRowEvents),
                csv(result.finalSnapshotTerminalCondition),
                String.valueOf(
                        result.oneEmptyTileWouldStartEvent),
                String.valueOf(
                        result.oneTileEarlierHoldWouldPreserveSpace),
                String.valueOf(
                        result.simultaneousEmptyTilesRequired),
                String.valueOf(
                        result.finalRowEventDependencyChainCount),
                String.valueOf(
                        result.activeEventExistsAtStall),
                String.valueOf(
                        result.reservationConflictExistsAtStall),
                csv(result.experimentMode),
                String.valueOf(
                        result.guardedForwardYieldEnabled),
                String.valueOf(
                        result.releaseImminentEvaluations),
                String.valueOf(
                        result.releaseImminentWindowsActivated),
                String.valueOf(
                        result.releaseImminentTilesClustered),
                String.valueOf(
                        result.releaseImminentPassengerHolds),
                String.valueOf(
                        result.releaseImminentNextTickEventStarts),
                String.valueOf(
                        result.releaseImminentWindowsWithoutEvent),
                String.valueOf(
                        result.lastClusteredCandidateId),
                String.valueOf(result.gatekeeperUniqueEpisodes),
                String.valueOf(result.gatekeeperRepeatedSelections),
                String.valueOf(result.gatekeeperEventStarts),
                String.valueOf(result.gatekeeperConfirmedReleaseTiles),
                String.valueOf(result.gatekeeperUpstreamStartsWithin1),
                String.valueOf(result.gatekeeperUpstreamStartsWithin2),
                String.valueOf(result.gatekeeperUpstreamStartsWithin5),
                String.valueOf(result.gatekeeperUpstreamStartsWithin10),
                String.valueOf(result.gatekeeperEpisodesWithoutEffect),
                String.valueOf(result.gatekeeperHighestTenTickGain),
                String.valueOf(result.gatekeeperHighestValueEpisodeId),
                csv(result.gatekeeperEpisodeSummary),
                String.valueOf(result.priorityCandidatesEvaluated),
                String.valueOf(result.priorityEligibleGatekeepers),
                String.valueOf(result.prioritySelectionsMade),
                String.valueOf(
                        result.prioritySelectionsDifferentFromFirstValid),
                String.valueOf(result.prioritySelectedScoreTotal),
                String.valueOf(result.priorityHighestSelectedScore),
                String.valueOf(result.priorityDirectReleasesTotal),
                String.valueOf(result.priorityQueuedBehindTotal),
                String.valueOf(result.rearwardYieldAttempts),
                String.valueOf(result.rearBoundaryFailuresEncountered),
                String.valueOf(result.forwardYieldFallbackEvaluations),
                String.valueOf(result.forwardYieldFallbackGrants),
                String.valueOf(
                        result.forwardYieldFallbackOccupiedRejections),
                String.valueOf(result.forwardYieldEventsStarted),
                String.valueOf(result.forwardYieldEventsCompleted),
                String.valueOf(result.blockersStagedTowardCockpit),
                String.valueOf(result.forwardYieldGuardEvaluations),
                String.valueOf(result.forwardYieldGuardApprovals),
                String.valueOf(result.forwardYieldGuardRejections),
                String.valueOf(result.guardRejectedWaitingRowPassenger),
                String.valueOf(result.guardRejectedVulnerableMover),
                String.valueOf(result.guardRejectedReservationConflict),
                String.valueOf(result.regionPriorityCompetitions),
                String.valueOf(result.regionPriorityRegionsRanked),
                String.valueOf(result.regionPrioritySelections),
                String.valueOf(result.regionPriorityLowerRankDeferrals),
                String.valueOf(result.regionPriorityHighestScore),
                String.valueOf(result.regionPrioritySelectedScoreTotal),
                String.valueOf(result.regionPriorityLeftWins),
                String.valueOf(result.regionPriorityRightWins),
                String.valueOf(result.regionPriorityTies),
                csv(result.lastRegionPriorityDecision),
                csv(result.residualStallFamily),
                csv(result.status())
        );

        appendLine(CSV_RESULTS_PATH, line);
    }

    static void appendExperimentSummary(
            List<ScenarioResult> results) {

        long successful =
                results.stream()
                        .filter(result -> result.completed)
                        .count();

        double averageTicks =
                results.stream()
                        .mapToInt(result -> result.completedTicks)
                        .average()
                        .orElse(0.0);

        double averageWaiting =
                results.stream()
                        .mapToDouble(
                                result -> result.averageWaitingTicks
                        )
                        .average()
                        .orElse(0.0);

        String text =
                "\n************************************************************\n" +
                "BATCH EXPERIMENT SUMMARY\n" +
                "RESEARCH EXPERIMENT " +
                String.format(Locale.ROOT, "%02d", EXPERIMENT_NUMBER) + "\n" +
                EXPERIMENT_TITLE + "\n" +
                "************************************************************\n" +
                "Experiment seed: " + EXPERIMENT_SEED + "\n" +
                "Scenarios executed: " + results.size() + "\n" +
                "Successful completions: " + successful + "\n" +
                "Incomplete/MAX_TICKS: " +
                (results.size() - successful) + "\n" +
                String.format(
                        Locale.ROOT,
                        "Average completed ticks: %.2f%n",
                        averageTicks
                ) +
                String.format(
                        Locale.ROOT,
                        "Average waiting ticks: %.2f%n",
                        averageWaiting
                ) +
                "************************************************************\n";

        appendLine(HUMAN_RESULTS_PATH, text);
    }

    static void appendLine(Path path, String text) {
        try (BufferedWriter writer =
                     Files.newBufferedWriter(
                             path,
                             StandardCharsets.UTF_8,
                             StandardOpenOption.CREATE,
                             StandardOpenOption.APPEND)) {

            writer.write(text);

            if (!text.endsWith(System.lineSeparator())) {
                writer.newLine();
            }

        } catch (IOException ex) {
            throw new IllegalStateException(
                    "Unable to write results file: " + path,
                    ex
            );
        }
    }

    static String csv(String value) {
        if (value == null) {
            return "";
        }

        return "\"" + value.replace("\"", "\"\"") + "\"";
    }

    // ======================== CONSOLE SUMMARIES ========================

    static void printScenarioSummary(ScenarioResult result) {
        String summary = String.format(
                Locale.ROOT,
                "Scenario %02d/%02d | %s | seats=%d | passengers=%d | " +
                "occupancy=%.0f%% | headway=%d-%d | dataset=%s | ticks=%d | seated=%d/%d | %s%n",
                result.scenarioNumber,
                NUMBER_OF_SCENARIOS,
                result.cabin,
                result.totalSeats,
                result.passengers,
                result.occupancyRate * 100.0,
                result.minHeadway,
                result.maxHeadway,
                result.datasetFamily,
                result.completedTicks,
                result.passengersSeated,
                result.passengers,
                result.status() +
                " | mode=" + result.experimentMode +
                " | dependencyRegions=" + result.congestionDependencyRegionsDetected +
                " | clusters=" + result.congestionClustersDetected +
                " | noIntervention=" + result.congestionClusterActivationsWithoutIntervention +
                " | clusterSelections=" + result.congestionClusterSelections +
                " | clusterStarts=" + result.congestionClusterEventStarts +
                " | regionsCreated=" + result.lifecycleRegionsCreated +
                " | regionUpdates=" + result.lifecycleRegionUpdates +
                " | regionsDissolved=" + result.lifecycleRegionsDissolved +
                " | reconstructionsAvoided=" + result.lifecycleReconstructionsAvoided +
                " | confirmationDeferrals=" + result.lifecycleConfirmationDeferrals +
                " | regionsConfirmed=" + result.lifecycleRegionsConfirmed +
                " | unchangedSuppressed=" + result.stateChangeEvaluationsSuppressed +
                " | stateChangeReopens=" + result.stateChangesReopeningEvaluation +
                " | priorityCompetitions=" + result.regionPriorityCompetitions +
                " | priorityDeferrals=" + result.regionPriorityLowerRankDeferrals
        );

        System.out.print(summary);

        if (!result.completed) {
            String diagnostic =
                    "   Experiment " +
                    String.format(Locale.ROOT, "%02d", EXPERIMENT_NUMBER) +
                    " diagnostic: dominantReason=" +
                    result.dominantBlockingReason +
                    " | noSeatProgressTicks=" +
                    result.ticksWithoutNewSeat +
                    " | unresolvedAtRow=" +
                    result.remainingWaitingAtRow +
                    " | unresolvedInAisle=" +
                    result.remainingMovingInAisle +
                    " | dependencyLength=" +
                    result.longestDependencyChainLength +
                    " | chain=" +
                    result.longestDependencyChain +
                    " | criticalBlocker=" +
                    result.criticalBlockerIdentity +
                    " | criticalTreeSize=" +
                    result.criticalBlockerTreeSize +
                    " | criticalTreeDepth=" +
                    result.criticalBlockerTreeDepth +
                    " | frozenComponents=" +
                    result.frozenComponentCount +
                    " | criticalPrerequisite=" +
                    result.criticalBlockerFinalPrerequisite +
                    " | requiredBlockers=" +
                    result.criticalBlockerRequiredBlockers +
                    " | unavailableYieldTiles=" +
                    result.criticalBlockerUnavailableYieldTiles +
                    " | yieldTileOccupants=" +
                    result.criticalBlockerYieldTileOccupants +
                    " | yieldDependencyType=" +
                    result.yieldDependencyType +
                    " | cycleDetected=" +
                    result.dependencyCycleDetected +
                    " | occupantTraceLength=" +
                    result.criticalYieldOccupantTraceLength +
                    " | occupantTrace=" +
                    result.criticalYieldOccupantTrace +
                    " | finalSnapshotType=" +
                    result.finalSnapshotDependencyType +
                    " | finalPrerequisite=" +
                    result.finalSnapshotPrerequisite +
                    " | oneEmptyTileWouldStart=" +
                    result.oneEmptyTileWouldStartEvent +
                    " | oneTileEarlierHoldWouldPreserve=" +
                    result.oneTileEarlierHoldWouldPreserveSpace +
                    " | finalRowEventChain=" +
                    result.finalSnapshotRowEventChain +
                    " | residualStallFamily=" +
                    result.residualStallFamily;

            System.out.println(diagnostic);
        }
    }

    static double completionPercent(
            ScenarioResult result) {

        return result.passengers == 0
                ? 0.0
                : 100.0 *
                  result.passengersSeated /
                  result.passengers;
    }

    static String pairComparisonText(
            ScenarioResult baseline,
            ScenarioResult clusteredResult) {

        int seatedDelta =
                clusteredResult.passengersSeated -
                baseline.passengersSeated;

        double completionDelta =
                completionPercent(clusteredResult) -
                completionPercent(baseline);

        int outsideDelta =
                clusteredResult.remainingWaitingToEnter -
                baseline.remainingWaitingToEnter;

        boolean convertedToSuccess =
                !baseline.completed &&
                clusteredResult.completed;

        boolean finalTwoRowRiskDispersionWorse =
                clusteredResult.passengersSeated <
                baseline.passengersSeated;

        return String.format(
                Locale.ROOT,
                "PAIR %02d | standardDataset=%d/%d (%.2f%%) | " +
                "finalTwoRowRiskDispersionDataset=%d/%d (%.2f%%) | seatedDelta=%+d | " +
                "completionDelta=%+.2f%% | outsideDelta=%+d | " +
                "clustersDetected=%d | clusterSelections=%d | clusterStarts=%d | " +
                "convertedToSuccess=%s | finalTwoRowRiskDispersionWorse=%s | " +
                "standardFamily=%s | finalTwoRowRiskDispersionFamily=%s%n",
                baseline.scenarioNumber,
                baseline.passengersSeated,
                baseline.passengers,
                completionPercent(baseline),
                clusteredResult.passengersSeated,
                clusteredResult.passengers,
                completionPercent(clusteredResult),
                seatedDelta,
                completionDelta,
                outsideDelta,
                clusteredResult.congestionClustersDetected,
                clusteredResult.congestionClusterSelections,
                clusteredResult.congestionClusterEventStarts,
                convertedToSuccess,
                finalTwoRowRiskDispersionWorse,
                baseline.residualStallFamily,
                clusteredResult.residualStallFamily
        );
    }

    static void printPairComparison(
            ScenarioResult baseline,
            ScenarioResult clusteredResult) {

        System.out.print(
                pairComparisonText(
                        baseline,
                        clusteredResult)
        );
    }

    static void appendPairComparison(
            ScenarioResult baseline,
            ScenarioResult clusteredResult) {

        String text =
                "\nPAIRED SCENARIO COMPARISON\n" +
                "--------------------------\n" +
                pairComparisonText(
                        baseline,
                        clusteredResult);

        appendLine(HUMAN_RESULTS_PATH, text);
    }

    static void printExperimentSummary(
            List<ScenarioResult> results) {

        long successful =
                results.stream()
                        .filter(result -> result.completed)
                        .count();

        System.out.println("\n============================================================");
        System.out.println("WHOLE-CABIN HYBRID BOARDING SIMULATION");
        System.out.println("RESEARCH EXPERIMENT " +
                String.format(Locale.ROOT, "%02d", EXPERIMENT_NUMBER));
        System.out.println(EXPERIMENT_TITLE);
        System.out.println("BATCH EXPERIMENT COMPLETE");
        System.out.println("============================================================");
        System.out.println("Scenarios executed: " + results.size());
        System.out.println("Successful completions: " + successful);
        System.out.println("Incomplete/MAX_TICKS: " +
                (results.size() - successful));
        System.out.println("Results appended to: " + HUMAN_RESULTS_PATH);
        System.out.println("CSV appended to: " + CSV_RESULTS_PATH);
        System.out.println("============================================================");
    }
}
