// Problem D — Datacenter Imprisonment (Midnight Code Cup 2026 finals).
//
// This single file is the submission. It contains:
//   * MerlinBase  — abstract interface to the referee; one method call == one turn
//   * MerlinProd  — stdin/stdout implementation (used when submitted)
//   * Strategy    — the agent logic; issues exactly one Merlin command per tick()
//   * main()      — prod entry point (excluded when LOCAL_RUN is defined;
//                   local_run.cpp includes this file and provides MerlinLocal)
//
// Maze structure (from the provided generator source): N is odd, rooms are the
// odd-odd cells (always empty), even-even cells are always walls, and the only
// unknown cells are corridors between orthogonally adjacent rooms. Open
// corridors form a uniform spanning tree over the (N+1)/2 x (N+1)/2 room grid.

#include <algorithm>
#include <cassert>
#include <climits>
#include <cmath>
#include <cstdint>
#include <cstdio>
#include <cstdlib>
#include <deque>
#include <iostream>
#include <map>
#include <memory>
#include <numeric>
#include <queue>
#include <random>
#include <set>
#include <sstream>
#include <string>
#include <tuple>
#include <unordered_map>
#include <unordered_set>
#include <vector>

// ---------------------------------------------------------------------------
// Merlin interface
// ---------------------------------------------------------------------------

struct Msg {
    bool ok = false;   // false == "- -" (nothing waiting)
    int sender = -1;
    std::string body;
};

class MerlinBase {
public:
    int N = 0;          // maze is N x N, 1-indexed
    int numAgents = 0;  // total agents in the run
    int agentId = 0;    // our 0-based id
    int maxMsgLen = 0;  // max message body length in bytes

    virtual ~MerlinBase() = default;

    // Every call below consumes this agent's single command for the turn.
    virtual bool query(int r, int c) = 0;                    // ? r c
    virtual void send(int to, const std::string& body) = 0;  // > P M
    virtual Msg recvFrom(int sender) = 0;                    // < Q
    virtual Msg recvAny() = 0;                               // < ?
    virtual void skip() = 0;                                 // .
    virtual void halt() = 0;                                 // halt (terminal)
    virtual void claim(const std::string& path) = 0;         // ! ULDR (terminal)
};

// ---------------------------------------------------------------------------
// Production Merlin: line-based stdio, flush after every line
// ---------------------------------------------------------------------------

class MerlinProd : public MerlinBase {
public:
    MerlinProd() {
        std::string line = readLine();
        std::istringstream ss(line);
        ss >> N >> numAgents >> agentId >> maxMsgLen;
    }

    bool query(int r, int c) override {
        emit("? " + std::to_string(r) + " " + std::to_string(c));
        return readLine() == "1";
    }
    void send(int to, const std::string& body) override {
        emit("> " + std::to_string(to) + " " + body);
        readLine();  // "OK"
    }
    Msg recvFrom(int sender) override {
        emit("< " + std::to_string(sender));
        return parseMsg(readLine());
    }
    Msg recvAny() override {
        emit("< ?");
        return parseMsg(readLine());
    }
    void skip() override {
        emit(".");
        readLine();  // "OK"
    }
    void halt() override { emit("halt"); }                       // no reply
    void claim(const std::string& path) override { emit("! " + path); }  // no reply

private:
    void emit(const std::string& s) {
        std::cout << s << '\n';
        std::cout.flush();
    }
    std::string readLine() {
        std::string line;
        if (!std::getline(std::cin, line)) std::exit(0);  // referee is gone
        if (!line.empty() && line.back() == '\r') line.pop_back();
        return line;
    }
    static Msg parseMsg(const std::string& line) {
        if (line == "- -") return {};
        Msg m;
        m.ok = true;
        size_t sp = line.find(' ');
        m.sender = std::stoi(line.substr(0, sp));
        m.body = (sp == std::string::npos) ? std::string() : line.substr(sp + 1);
        return m;
    }
};

// ---------------------------------------------------------------------------
// Strategy
// ---------------------------------------------------------------------------
//
// Cooperative coordinator/worker design.
//
// Agent 0 (coordinator) owns the world model over the m x m room lattice,
// m = (N+1)/2: corridor statuses (unknown/open/wall), a DSU of rooms with two
// inference rules (a corridor inside one component is a wall; a room with all
// other corridors walled must open its last one), and a probe priority queue:
// corridors on the boundary of the start/goal components first (bidirectional
// greedy, ordered by far room's distance to the opposite corner), then a
// dynamic speculative ring: when a room joins a corner component, unknown
// corridors up to D_RING rooms beyond it are queued at lower priority, so
// workers stay fed with a "tube" that tracks the components. (v2 used a
// static diagonal-band flood instead; on the revealed official mazes that
// degenerated into a full-map sweep — ~1.0C probes where a frontier-guided
// search needs 0.33..0.47C — because the band pre-assigned every corridor
// before it could ever become frontier.)
//
// Workers (agents 1..K-1) are dumb: receive a batch of corridor indices
// (base-64, fixed width), probe them one per turn, send results back as
// packed bits, repeat. The coordinator pipelines two outstanding batches per
// worker and simulates each worker's turn schedule exactly (lockstep makes
// this deterministic), so it reads every result on the precise turn it
// becomes available — no wasted polling turns. Idle coordinator turns go to
// probing the best frontier corridor itself, which is what makes the small-K
// case degenerate into plain adaptive greedy.
//
// A worker that dies is detected by its result failing to arrive on schedule
// (3 retries), and its assigned corridors are recycled.

class Strategy {
public:
    explicit Strategy(MerlinBase* merlin) : M(merlin) {}

    bool done() const { return finished; }

    // Issues exactly one Merlin command per call.
    void tick() {
        if (finished) return;
        if (M->N == 1) {
            if (M->agentId == 0) M->claim("");
            else M->halt();
            finished = true;
            return;
        }
        if (!inited) init();
        if (explorer) explorerTick();
        else if (sweep) sweepTick();
        else if (M->agentId == 0) coordTick();
        else workerTick();
        T++;
    }

private:
    // ---- shared state ------------------------------------------------------
    MerlinBase* M;
    bool finished = false;
    bool inited = false;
    long long T = 0;  // my own turn counter == global turn index (lockstep)

    int m = 0;       // rooms per side
    int C = 0;       // total corridors = 2*m*(m-1)
    int mH = 0;      // horizontal corridors = m*(m-1)
    int idxChars = 1;  // base-64 chars per corridor index
    int K = 1, L = 0;  // NUM_AGENTS, MAX_MSG_LEN
    int Bmax = 8;      // batch size target
    bool allowSpec = true;  // may workers be given speculative probes?
    int specMinK = 0, specMode = 0, ringDepth = 2, staticSpec = 0, pipeDepth = 2,
        hotProbe = 1;

    // ---- explorer mode (small K) -------------------------------------------
    // For K in [2,4] the batch pipeline starves: workers get stale batches and
    // waste ~half their probes (measured 2.2x probe dilution at K=3). Instead
    // every agent runs its own fresh greedy dig: agent 0 grows the start
    // component, agent 1 the goal component, agents 2..3 grow "islands" seeded
    // on the diagonal digging toward both corners. Workers stream their probe
    // results to agent 0 every SYNC_W turns; agent 0 keeps the global model,
    // retargets islands when components merge, and claims. Inferred corridors
    // are never transmitted: the receiver re-derives them from probed ones.
    bool explorer = false;
    int mySeed = 0;                  // room my digger grows from
    int myTargets = 3;               // bit 1: dig toward goal, bit 2: toward start
    std::string pendingDelta;        // my probes since last sync (idx + '0'/'1')
    static constexpr int SYNC_W = 32;
    int phaseStep = 8;  // slot spacing per worker inside the sync window
    std::vector<int> wSeed;          // per-agent seed rooms (coordinator view)
    std::vector<int> wTold;          // last target mask hinted to worker
    std::vector<int> wMiss;          // consecutive missed syncs
    bool recordDelta = false;        // record my st transitions into pendingDelta
    long long lastRebuild = -100;

    static int envInt(const char* name, int dflt) {
        const char* v = std::getenv(name);
        return v ? std::atoi(v) : dflt;
    }

    static constexpr char A64 = '0';  // alphabet '0'..'o' (0x30..0x6F)
    static constexpr int SPEC_BASE = 1 << 20;  // speculative prio offset

    void init() {
        inited = true;
        K = M->numAgents;
        L = M->maxMsgLen;
        m = (M->N + 1) / 2;
        mH = m * (m - 1);
        C = 2 * mH;
        idxChars = 1;
        for (int v = 64; v < C; v *= 64) idxChars++;
        int cap = (idxChars > 0) ? L / idxChars : 0;
        // Tuning knobs, env-overridable for local experiments.
        // Mid-size batches amortize the 2-turn send/recv cost per batch; at
        // K<=4 the pipeline is so short that latency wins over amortization.
        int bFloor = envInt("D_B_FLOOR", K >= 5 ? 32 : 8);
        specMinK = envInt("D_SPEC_MIN_K", 0);  // workers get spec probes if K >= this
        specMode = envInt("D_SPEC_MODE", 1);   // 0: rings from corners, 1: diagonal band
        // When the in-flight pipeline (~4K^2 corridor assignments) covers a
        // large fraction of the maze, guided exploration cannot beat a plain
        // full sweep — flood everything (v2 behavior). Otherwise use a
        // speculative tube around the corner components: deep enough that the
        // pipeline fits in a tube of that depth along a frontier of length
        // ~m; floor of 12 keeps small-K runs from starving.
        staticSpec = envInt("D_STATIC_SPEC", 16LL * K * K >= C ? 1 : 0);
        ringDepth = envInt("D_RING",
                           staticSpec ? 0 : std::min(2 * m, std::max(12, 3 * K * K / m)));
        pipeDepth = envInt("D_PIPE", 2);       // outstanding batches per worker
        hotProbe = envInt("D_HOT", 1);         // coord may duplicate in-flight probes
        Bmax = std::min(cap, std::max(bFloor, 2 * (K - 1)));
        allowSpec = (K >= specMinK);
        // D_EXPLORER = max K for explorer mode (0 disables). Explorer beats
        // the batch pipeline up to K~10 (measured at N=249..501); beyond
        // that, K-2 islands overlap and the hub throttles — batches win.
        explorer = K >= 2 && K <= envInt("D_EXPLORER", 10) && L >= idxChars + 1;
        // Sweep mode (gate below may override explorer for K in [9,10])
        // -- sweep mode (from the psych-agent winner): static diagonal-band
        // strided sweep with a listen-only collector. Measured crossover
        // (3-seed grids): K>=13 on small/mid mazes (m<=151), K>=20 large.
        // Boundary re-measured 2026-07-04 evening over 3-seed grids + new
        // official rounds: sweep from K=9 on small mazes, 11 mid, 14 large,
        // 20 huge; explorer keeps K<=10 only where sweep doesn't apply.
        int sweepMin = m <= 120 ? 9 : m <= 160 ? 11 : m <= 190 ? 14 : 20;
        sweep = K >= envInt("D_SWEEP_MIN", sweepMin) && L >= 12;
        if (sweep) explorer = false;
        if (explorer) {
            ringDepth = 0;  // every digger is fresh; no speculative tube
            explorerInit();
        } else if (sweep) {
            sweepInit();
        } else if (M->agentId == 0) {
            coordInit();
        }
    }

    // ---- corridor geometry -------------------------------------------------
    // H corridor idx = i*(m-1)+j       : rooms (i,j)-(i,j+1), cell (2i+1, 2j+2)
    // V corridor idx = mH + i*m + j    : rooms (i,j)-(i+1,j), cell (2i+2, 2j+1)
    std::pair<int, int> corridorCell(int idx) const {
        if (idx < mH) {
            int i = idx / (m - 1), j = idx % (m - 1);
            return {2 * i + 1, 2 * j + 2};
        }
        int t = idx - mH, i = t / m, j = t % m;
        return {2 * i + 2, 2 * j + 1};
    }
    std::pair<int, int> corridorRooms(int idx) const {  // room ids
        if (idx < mH) {
            int i = idx / (m - 1), j = idx % (m - 1);
            return {i * m + j, i * m + j + 1};
        }
        int t = idx - mH, i = t / m, j = t % m;
        return {i * m + j, (i + 1) * m + j};
    }
    int roomDeg(int room) const {
        int i = room / m, j = room % m;
        return (i > 0) + (i < m - 1) + (j > 0) + (j < m - 1);
    }
    // corridors incident to a room (-1 padded)
    void roomCorridors(int room, int out[4]) const {
        int i = room / m, j = room % m, k = 0;
        out[0] = out[1] = out[2] = out[3] = -1;
        if (j > 0) out[k++] = i * (m - 1) + (j - 1);
        if (j < m - 1) out[k++] = i * (m - 1) + j;
        if (i > 0) out[k++] = mH + (i - 1) * m + j;
        if (i < m - 1) out[k++] = mH + i * m + j;
    }
    int manh(int room, int ti, int tj) const {
        return std::abs(room / m - ti) + std::abs(room % m - tj);
    }

    // ---- base-64 message coding --------------------------------------------
    void encIdx(std::string& s, int v) const {
        for (int k = idxChars - 1; k >= 0; k--) s += char(A64 + ((v >> (6 * k)) & 63));
    }
    int decIdx(const std::string& s, int pos) const {
        int v = 0;
        for (int k = 0; k < idxChars; k++) v = (v << 6) | (s[pos + k] - A64);
        return v;
    }

    // =========================================================================
    // Coordinator
    // =========================================================================
    std::vector<int8_t> st;       // 0 unknown, 1 open, 2 wall (per corridor)
    std::vector<char> assigned;   // corridor handed to a worker, result pending
    std::vector<int> dsuP, dsuSz;
    std::vector<std::vector<int>> members;  // rooms per DSU root
    std::vector<int8_t> wallCnt, unkCnt;    // per room
    std::vector<char> inCorner;   // room is in the start- or goal-component
    using MinHeap = std::priority_queue<std::pair<int, int>,
                                        std::vector<std::pair<int, int>>, std::greater<>>;
    MinHeap pq;     // (prio, corridor) — assignment queue (skips assigned)
    MinHeap pqHot;  // same entries; coordinator hot-probes may duplicate
                    // corridors that are assigned and in flight
    int startRoom = 0, goalRoom = 0;
    bool connectedSG = false;

    struct Batch {
        std::vector<int> idx;
        long long resultReady;  // first turn the result is readable
    };
    struct WorkerSim {
        std::deque<Batch> out;   // sent, result not yet applied
        long long idleFrom = 0;  // turn from which the worker idle-receives
        int misses = 0;
        bool dead = false;
    };
    std::vector<WorkerSim> ws;

#ifdef LOCAL_RUN
    // Diagnostics (coordinator only), printed at claim time.
    long long dRecv = 0, dSend = 0, dSelfProbe = 0, dSkip = 0, dHotProbe = 0;
    long long dResults = 0, dWasted = 0;          // worker results: total / already-known
    long long dAsnFront = 0, dAsnSpec = 0;        // assignments by priority class
    void dumpDiag() const {
        std::cerr << "DIAG turns=" << T << " recv=" << dRecv << " send=" << dSend
                  << " selfProbe=" << dSelfProbe << " hotProbe=" << dHotProbe
                  << " skip=" << dSkip
                  << " results=" << dResults << " wasted=" << dWasted
                  << " asnFront=" << dAsnFront << " asnSpec=" << dAsnSpec << "\n";
    }
#endif
    int lastPopPrio = 0;  // priority of the corridor popValid last returned

    int find(int x) {
        while (dsuP[x] != x) x = dsuP[x] = dsuP[dsuP[x]];
        return x;
    }

    void pqPush(int prio, int idx) {
        pq.push({prio, idx});
        pqHot.push({prio, idx});
    }

    void initModel() {
        st.assign(C, 0);
        assigned.assign(C, 0);
        dsuP.resize(m * m);
        std::iota(dsuP.begin(), dsuP.end(), 0);
        dsuSz.assign(m * m, 1);
        members.assign(m * m, {});
        for (int r = 0; r < m * m; r++) members[r] = {r};
        wallCnt.assign(m * m, 0);
        unkCnt.resize(m * m);
        for (int r = 0; r < m * m; r++) unkCnt[r] = (int8_t)roomDeg(r);
        inCorner.assign(m * m, 0);
        startRoom = 0;
        goalRoom = m * m - 1;
        ws.assign(K, {});
        ringStamp.assign(m * m, INT_MAX);  // tube distance labels, decrease-only
    }

    void explorerInit() {
        initModel();
        wSeed.assign(K, 0);
        wTold.assign(K, 3);
        wMiss.assign(K, 0);
        wSeed[0] = startRoom;
        wTold[0] = 1;  // dig toward the goal
        wSeed[1] = goalRoom;
        wTold[1] = 2;  // dig toward the start
        for (int w = 2; w < K; w++) {  // islands on the diagonal
            int d = (m - 1) * (w - 1) / (K - 1);
            wSeed[w] = d * m + d;
        }
        phaseStep = std::max(2, SYNC_W / std::max(1, K - 1));
        mySeed = wSeed[M->agentId];
        myTargets = wTold[M->agentId];
        recordDelta = (M->agentId != 0);
        inCorner[mySeed] = 1;
        pushRoomFrontier(mySeed);
    }

    void coordInit() {
        initModel();
        inCorner[startRoom] = inCorner[goalRoom] = 1;
        pushRoomFrontier(startRoom);
        pushRoomFrontier(goalRoom);
        pushRing(startRoom);
        pushRing(goalRoom);
        if (staticSpec) {
            // v2 fallback: static flood of every corridor, ordered by specMode.
            for (int idx = 0; idx < C; idx++) {
                auto [a, b] = corridorRooms(idx);
                int d;
                if (specMode == 1) {  // band around the S-G diagonal, inside-out
                    d = std::min(manh(a, 0, 0) + manh(a, m - 1, m - 1),
                                 manh(b, 0, 0) + manh(b, m - 1, m - 1));
                } else {  // rings around both corners
                    d = std::min(std::min(manh(a, 0, 0), manh(a, m - 1, m - 1)),
                                 std::min(manh(b, 0, 0), manh(b, m - 1, m - 1)));
                }
                pqPush(SPEC_BASE + d, idx);
            }
        }
    }

    // Speculative tube: unknown corridors within ringDepth rooms of the
    // corner components, pushed with a directional spec priority so the tube
    // leans toward the opposite corner. Incremental multi-source BFS:
    // tubeDist[v] = min distance from any component room, labels only ever
    // decrease, so total relaxation work is O(rooms * ringDepth) for the
    // whole game instead of a fresh O(ringDepth^2) BFS per joining room
    // (which timed out the judge at K=48).
    std::vector<int> ringStamp;  // tubeDist: min dist from tracked comp
    int ringClock = 0;           // unused; kept for layout stability

    void pushRing(int room) {
        if (ringDepth < 2) return;
        if (ringStamp[room] == 0) return;
        bool nearStart = (find(room) == find(startRoom));
        int ti = nearStart ? m - 1 : 0, tj = ti;
        ringStamp[room] = 0;
        std::vector<std::pair<int, int>> cur{{room, 0}};  // (room, depth)
        std::vector<std::pair<int, int>> nxt;
        while (!cur.empty()) {
            nxt.clear();
            for (auto [u, dep] : cur) {
                int cs[4];
                roomCorridors(u, cs);
                for (int k = 0; k < 4 && cs[k] >= 0; k++) {
                    int idx = cs[k];
                    if (st[idx] == 2) continue;  // wall: tube doesn't cross
                    auto [a, b] = corridorRooms(idx);
                    int v = (a == u) ? b : a;
                    if (st[idx] == 0 && dep >= 1) {
                        int rp;
                        if (chasePrio) {
                            rp = nearStart ? distToG[v] : distToS[v];
                            if (rp < 0) rp = 2 * m;
                        } else {
                            rp = manh(v, ti, tj);
                        }
                        pqPush(SPEC_BASE + rp, idx);
                    }
                    if (dep + 1 < ringDepth && !inCorner[v] && dep + 1 < ringStamp[v]) {
                        ringStamp[v] = dep + 1;
                        nxt.push_back({v, dep + 1});
                    }
                }
            }
            std::swap(cur, nxt);
        }
    }

    void pushRoomFrontier(int room) {
        int cs[4];
        roomCorridors(room, cs);
        bool nearStart = !explorer && (find(room) == find(startRoom));
        for (int k = 0; k < 4 && cs[k] >= 0; k++) {
            int idx = cs[k];
            if (st[idx] != 0) continue;
            auto [a, b] = corridorRooms(idx);
            int far = (a == room) ? b : a;
            int prio;
            if (explorer) {
                prio = INT_MAX;
                if (myTargets & 1) prio = std::min(prio, manh(far, m - 1, m - 1));
                if (myTargets & 2) prio = std::min(prio, manh(far, 0, 0));
            } else if (chasePrio) {
                // endgame: close the gap to the OTHER component, not a corner
                prio = nearStart ? distToG[far] : distToS[far];
                if (prio < 0) prio = 2 * m;  // unreachable through known space
            } else {
                prio = nearStart ? manh(far, m - 1, m - 1) : manh(far, 0, 0);
            }
            pqPush(prio, idx);
        }
    }

    void forcedCheck(int room) {
        // A spanning tree touches every room: all-but-one wall => last is open.
        if (unkCnt[room] != 1 || wallCnt[room] != roomDeg(room) - 1) return;
        int cs[4];
        roomCorridors(room, cs);
        for (int k = 0; k < 4 && cs[k] >= 0; k++)
            if (st[cs[k]] == 0) {
                applyOpen(cs[k]);
                return;
            }
    }

    void applyWall(int idx) {
        if (st[idx] != 0) return;
        st[idx] = 2;
        if (recordDelta) {
            encIdx(pendingDelta, idx);
            pendingDelta += '0';
        }
        auto [a, b] = corridorRooms(idx);
        wallCnt[a]++, unkCnt[a]--;
        wallCnt[b]++, unkCnt[b]--;
        forcedCheck(a);
        forcedCheck(b);
    }

    void applyOpen(int idx) {
        if (st[idx] != 0) return;
        st[idx] = 1;
        if (recordDelta) {
            encIdx(pendingDelta, idx);
            pendingDelta += '1';
        }
        auto [a, b] = corridorRooms(idx);
        unkCnt[a]--, unkCnt[b]--;
        int ra = find(a), rb = find(b);
        if (ra == rb) return;  // impossible in a tree; be safe
        int rootS = find(startRoom), rootG = find(goalRoom);
        bool cornerA = (ra == rootS) || (ra == rootG);
        bool cornerB = (rb == rootS) || (rb == rootG);
        if (cornerA && cornerB) connectedSG = true;  // ra,rb are {rootS,rootG}
        int rootMine = explorer ? find(mySeed) : -1;  // before the union
        // union by size, keep member lists
        int big = (dsuSz[ra] >= dsuSz[rb]) ? ra : rb;
        int small = (big == ra) ? rb : ra;
        dsuP[small] = big;
        dsuSz[big] += dsuSz[small];
        if (explorer) {
            // Grow only my own dig: when a merge touches my component on
            // exactly one side, the other side's rooms become my frontier.
            bool mineA = (ra == rootMine), mineB = (rb == rootMine);
            if (!connectedSG && (mineA != mineB)) {
                int myRoot = mineA ? ra : rb;
                std::vector<int>& newly = members[(myRoot == big) ? small : big];
                for (int r : newly)
                    if (!inCorner[r]) {
                        inCorner[r] = 1;
                        pushRoomFrontier(r);
                    }
            }
        } else if (!connectedSG && (cornerA || cornerB)) {
            // Exactly one side was corner-connected: the other side's rooms
            // newly join a corner component; their unknown corridors become
            // frontier. (pushRoomFrontier resolves direction post-union.)
            int cornerRoot = cornerA ? ra : rb;
            std::vector<int>& newly = members[(cornerRoot == big) ? small : big];
            for (int r : newly)
                if (!inCorner[r]) {
                    inCorner[r] = 1;
                    pushRoomFrontier(r);
                    pushRing(r);
                }
        }
        members[big].insert(members[big].end(), members[small].begin(), members[small].end());
        members[small].clear();
        members[small].shrink_to_fit();
    }

    // Pop the best still-unknown, unassigned corridor with prio < ceiling;
    // apply cycle-rule walls for free along the way. -1 if none qualifies.
    int popValid(int ceiling = INT_MAX) {
        while (!pq.empty()) {
            auto [p, idx] = pq.top();
            if (p >= ceiling) return -1;  // best remaining is out of class
            pq.pop();
            if (st[idx] != 0 || assigned[idx]) continue;
            auto [a, b] = corridorRooms(idx);
            if (find(a) == find(b)) {
                applyWall(idx);  // same component: must be a wall
                continue;
            }
            lastPopPrio = p;
            return idx;
        }
        return -1;
    }

    void applyResult(const Batch& batch, const std::string& bits) {
        for (size_t j = 0; j < batch.idx.size(); j++) {
            int idx = batch.idx[j];
            assigned[idx] = 0;
            int ch = bits[j / 6] - A64;
            bool open = (ch >> (j % 6)) & 1;
#ifdef LOCAL_RUN
            dResults++;
            if (st[idx] != 0) dWasted++;
#endif
            if (open) applyOpen(idx);
            else applyWall(idx);
        }
    }

    void killWorker(int w) {
        ws[w].dead = true;
        for (auto& b : ws[w].out)
            for (int idx : b.idx)
                if (st[idx] == 0) {
                    assigned[idx] = 0;
                    pqPush(0, idx);  // urgent: it was frontier-ish already
                }
        ws[w].out.clear();
    }

    std::string buildPath() {
        // BFS over open corridors, rooms startRoom -> goalRoom
        std::vector<int> par(m * m, -1);
        std::deque<int> q{startRoom};
        par[startRoom] = startRoom;
        while (!q.empty()) {
            int r = q.front();
            q.pop_front();
            if (r == goalRoom) break;
            int cs[4];
            roomCorridors(r, cs);
            for (int k = 0; k < 4 && cs[k] >= 0; k++) {
                if (st[cs[k]] != 1) continue;
                auto [a, b] = corridorRooms(cs[k]);
                int nxt = (a == r) ? b : a;
                if (par[nxt] == -1) {
                    par[nxt] = r;
                    q.push_back(nxt);
                }
            }
        }
        std::string path;
        int cur = goalRoom;
        while (cur != startRoom) {
            int p = par[cur];
            int ci = cur / m, cj = cur % m, pi = p / m, pj = p % m;
            char mv = (ci > pi) ? 'D' : (ci < pi) ? 'U' : (cj > pj) ? 'R' : 'L';
            path += mv;
            path += mv;
            cur = p;
        }
        std::reverse(path.begin(), path.end());
        return path;
    }

    // Re-seed the tube from the current frontier: distance labels only ever
    // decrease, so without this the tube never re-orients as the components
    // sweep forward. Bounded work (one scan + one BFS over the tube), run
    // every 64 turns instead of per join (per-join re-BFS blew the judge's
    // CPU budget at K=48).
    void refreshTube() {
        std::fill(ringStamp.begin(), ringStamp.end(), INT_MAX);
        for (int r = 0; r < m * m; r++)
            if (inCorner[r] && unkCnt[r] > 0) pushRing(r);
    }

    void coordTick() {
        if (connectedSG) {
#ifdef LOCAL_RUN
            dumpDiag();
#endif
            M->claim(buildPath());
            finished = true;
            return;
        }
        if (chasePrio && (T & 127) == 63) chaseRebuild();
        else if (ringDepth >= 2 && (T & 63) == 63) refreshTube();
        // 1. A worker result is due: read it (earliest due first).
        int due = -1;
        long long dueT = LLONG_MAX;
        for (int w = 1; w < K; w++)
            if (!ws[w].dead && !ws[w].out.empty() && ws[w].out.front().resultReady <= T &&
                ws[w].out.front().resultReady < dueT) {
                due = w;
                dueT = ws[w].out.front().resultReady;
            }
        if (due != -1) {
#ifdef LOCAL_RUN
            dRecv++;
#endif
            Msg r = M->recvFrom(due);
            if (r.ok) {
                applyResult(ws[due].out.front(), r.body);
                ws[due].out.pop_front();
                ws[due].misses = 0;
            } else {
#ifdef LOCAL_RUN
                std::cerr << "coord: unexpected miss from worker " << due << " at turn " << T
                          << "\n";
#endif
                if (++ws[due].misses > 3) killWorker(due);
            }
            return;
        }
        // 2. Keep worker pipelines full (2 outstanding batches each).
        if (Bmax >= 1) {
            int pick = -1;
            long long best = LLONG_MAX;
            for (int w = 1; w < K; w++)
                if (!ws[w].dead && (int)ws[w].out.size() < pipeDepth && ws[w].idleFrom < best) {
                    pick = w;
                    best = ws[w].idleFrom;
                }
            if (pick != -1) {
                Batch b;
                std::string payload;
                while ((int)b.idx.size() < Bmax) {
                    int idx = popValid(allowSpec ? INT_MAX : SPEC_BASE);
                    if (idx < 0) break;
                    assigned[idx] = 1;
#ifdef LOCAL_RUN
                    (lastPopPrio >= SPEC_BASE ? dAsnSpec : dAsnFront)++;
#endif
                    b.idx.push_back(idx);
                    encIdx(payload, idx);
                }
                if (!b.idx.empty()) {
                    long long r = std::max(ws[pick].idleFrom, T + 1);
                    long long s = r + (long long)b.idx.size() + 1;
                    b.resultReady = s + 1;
                    ws[pick].idleFrom = s + 1;
                    ws[pick].out.push_back(std::move(b));
#ifdef LOCAL_RUN
                    dSend++;
#endif
                    M->send(pick, payload);
                    return;
                }
            }
        }
        // 3. Probe the best frontier corridor myself.
        int idx = popValid();
        if (idx >= 0) {
#ifdef LOCAL_RUN
            dSelfProbe++;
#endif
            auto [r, c] = corridorCell(idx);
            if (M->query(r, c)) applyOpen(idx);
            else applyWall(idx);
            return;
        }
        // 4. Everything queued is in flight: hot-probe the best unknown
        // corridor anyway (zero latency; duplicates one worker probe, but
        // that worker turn was already committed when the batch went out).
        while (hotProbe && !pqHot.empty()) {
            int hot = pqHot.top().second;
            pqHot.pop();
            if (st[hot] != 0) continue;
            auto [a, b] = corridorRooms(hot);
            if (find(a) == find(b)) {
                applyWall(hot);
                continue;
            }
#ifdef LOCAL_RUN
            dHotProbe++;
#endif
            auto [r, c] = corridorCell(hot);
            if (M->query(r, c)) applyOpen(hot);
            else applyWall(hot);
            return;
        }
        // 5. Nothing to do (results in flight will wake us up).
#ifdef LOCAL_RUN
        dSkip++;
#endif
        M->skip();
    }

    // =========================================================================
    // Sweep mode (large K): static diagonal-band strided sweep
    // =========================================================================
    //
    // Faithful port of the psych-agent winner. Corridors are sorted once by
    // (distance from the main diagonal, position along it, tiny hash noise);
    // worker `w` probes list positions (w-1), (w-1)+Q, ... (Q = K-1) in blocks
    // of 56, shipping each block to agent 0 as "block:bits". Agent 0 only
    // listens (`< ?` every turn), maintains a DSU over rooms, and claims the
    // moment start and goal connect. The static partition means zero
    // duplication and no assignment latency; the order encodes where the S-G
    // path lives (it hugs the diagonal), so connectivity arrives long before
    // the sweep completes. The exact sort must not change: its numbers are
    // validated against the official rounds.

    bool sweep = false;
    static constexpr int SWEEP_B = 56;
    static constexpr const char* SW_ALPH =
        "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
    struct SwEdge {
        int r, c, d;  // room (r,c) 0-based, d: 2 = down, 3 = right
    };
    std::vector<SwEdge> swEdges;
    int swE = 0, swQ = 1;
    long long swBlock = 0;
    int swPos = 0;
    std::vector<char> swBits;
    // collector state (self-contained; the global model is not initialized)
    std::vector<int> swDsu, swRank;
    std::vector<std::vector<std::pair<int, int>>> swAdj;  // (room, dir)
    bool swConnected = false;
    std::string swAnswer;
    // The collector probes the first swHead edges of the order itself before
    // going listen-only: the innermost diagonal band at zero latency (from
    // the strongest psych lineage; worth ~1-3% on big mazes).
    int swHead = 0, swCollPos = 0;
    // ---- sweep -> batch chase ---------------------------------------------
    // A static band order must sweep ~70%+ of a big maze before it can
    // certify a wandering path (measured: median path deviation 100/220).
    // So once chaseTrig of the map is known, hand everything to the guided
    // batch machinery: collector broadcasts "S", each worker acks with "="
    // after its in-flight block (so no message ambiguity), collector builds
    // its global model from all sweep knowledge and runs plain batch mode
    // for the endgame chase along the components' frontier.
    int chaseOn = 1;
    double chaseTrig = 0.30;
    bool chasePrio = false;               // batch prios = distance to other comp
    std::vector<int> distToS, distToG;    // BFS dist through non-wall corridors
    int chaseStage = 0;  // 0 sweeping, 1 broadcasting S, 2 awaiting acks, 3 batch
    std::vector<int8_t> swState;   // collector: full corridor statuses
    long long swKnownCnt = 0;
    std::vector<char> swAck;
    std::deque<int> swSwitchQ;
    bool wChase = false;       // worker: batch mode reached
    bool wSendMarker = false;  // worker: send "=" next turn
    bool wPollInbox = false;   // worker: check inbox after this block

    static uint64_t smix(uint64_t x) {
        x += 0x9e3779b97f4a7c15ULL;
        x = (x ^ (x >> 30)) * 0xbf58476d1ce4e5b9ULL;
        x = (x ^ (x >> 27)) * 0x94d049bb133111ebULL;
        return x ^ (x >> 31);
    }

    void sweepInit() {
        swQ = std::max(1, K - 1);
        swEdges.reserve(C);
        for (int r = 0; r < m; r++)
            for (int c = 0; c < m; c++) {
                if (r + 1 < m) swEdges.push_back({r, c, 2});
                if (c + 1 < m) swEdges.push_back({r, c, 3});
            }
        swE = (int)swEdges.size();
        // Head-probing pays only when workers have plenty of blocks to chew
        // through while the collector is busy; on tiny mazes the collector's
        // solo phase starves ingestion instead (r1830: 152 -> 263 turns).
        swHead = envInt("D_SWEEP_HEAD", 184);
        if ((long long)swE < 4LL * swHead * std::max(1, K - 1)) swHead = 0;
        // Two validated scan orders inside each diagonal ring: 0 = along the
        // diagonal with hash noise (psych agent_007 lineage), 1 = from the
        // ring's center outward (psych agent_008). Same geo mean on the
        // official rounds; keep 0 as default, D_SWEEP_KEY to flip.
        int keyMode = envInt("D_SWEEP_KEY", 2);
        int mm = m;
        std::sort(swEdges.begin(), swEdges.end(),
                  [keyMode, mm](const SwEdge& a, const SwEdge& b) {
            auto key = [keyMode, mm](const SwEdge& e) {
                int x, y;
                if (e.d == 2) x = 2 * e.r + 1, y = 2 * e.c;
                else x = 2 * e.r, y = 2 * e.c + 1;
                if (keyMode == 2)  // psych3 lineage: direction-grouped, no noise
                    return std::tuple<int, int, int, int, int>(
                        std::abs(x - y), x + y, e.d, e.r, e.c);
                if (keyMode == 1)
                    return std::tuple<int, int, int, int, int>(
                        std::abs(x - y), std::abs(x + y - 2 * (mm - 1)), e.d, x + y, e.r);
                int noise = (int)(smix(((uint64_t)e.r << 32) ^ ((uint64_t)e.c << 16) ^ e.d) % 3);
                return std::tuple<int, int, int, int, int>(
                    std::abs(x - y), x + y, noise, e.r, e.c * 4 + e.d);
            };
            return key(a) < key(b);
        });
        // Chase pays only on long rounds: measured over the 22 official
        // mazes, every chase win had C/(K-1) >= 1314 and every loss but one
        // was <= 1233 (switch overhead + batch-phase inefficiency dominate
        // short rounds, where the plain band sweep certifies quickly anyway).
        chaseOn = envInt("D_CHASE", C >= 1300 * std::max(1, K - 1) ? 1 : 0);
        chaseTrig = envInt("D_TRIG", 45) / 100.0;
        if (M->agentId == 0) {
            swDsu.resize(m * m);
            std::iota(swDsu.begin(), swDsu.end(), 0);
            swRank.assign(m * m, 0);
            swAdj.assign(m * m, {});
            swState.assign(C, 0);
            swAck.assign(K, 0);
        }
    }

    int swCorridorIdx(int pos) const {  // sorted-order position -> corridor idx
        const SwEdge& e = swEdges[pos];
        return e.d == 3 ? e.r * (m - 1) + e.c : mH + e.r * m + e.c;
    }

    void swMarkPos(long long pos, bool open) {
        if (pos < 0 || pos >= swE) return;
        int idx = swCorridorIdx((int)pos);
        if (swState[idx] != 0) return;
        swState[idx] = open ? 1 : 2;
        swKnownCnt++;
        if (open) swAddOpen(pos);
    }

    void compDistBFS(int srcRoot, std::vector<int>& dist) {
        dist.assign(m * m, -1);
        std::deque<int> q;
        for (int room = 0; room < m * m; room++)
            if (find(room) == srcRoot) {
                dist[room] = 0;
                q.push_back(room);
            }
        while (!q.empty()) {
            int u = q.front();
            q.pop_front();
            int cs[4];
            roomCorridors(u, cs);
            for (int k = 0; k < 4 && cs[k] >= 0; k++) {
                if (st[cs[k]] == 2) continue;  // known wall: gap can't cross
                auto [a, b] = corridorRooms(cs[k]);
                int v = (a == u) ? b : a;
                if (dist[v] == -1) {
                    dist[v] = dist[u] + 1;
                    q.push_back(v);
                }
            }
        }
    }

    // Rebuild the whole priority structure around fresh component distances.
    void chaseRebuild() {
        compDistBFS(find(startRoom), distToS);
        compDistBFS(find(goalRoom), distToG);
        pq = MinHeap();
        pqHot = MinHeap();
        std::fill(ringStamp.begin(), ringStamp.end(), INT_MAX);
        for (int room = 0; room < m * m; room++)
            if (inCorner[room] && unkCnt[room] > 0) {
                pushRoomFrontier(room);
                pushRing(room);
            }
    }

    void buildChaseModel() {
        // guided tube, never flood: the chase must focus, and ringDepth=0
        // (flood regime) starves the batch pipeline outright. Shallow tube:
        // the component-distance priorities already focus the work, and each
        // periodic rebuild re-BFSes the whole tube (CPU on the 10s budget).
        staticSpec = 0;
        ringDepth = envInt("D_CHASE_RING", std::max(14, 3 * K * K / m));
        coordInit();  // frontier/tube model
        for (int idx = 0; idx < C; idx++) {
            if (swState[idx] == 1) applyOpen(idx);
            else if (swState[idx] == 2) applyWall(idx);
        }
        ws.assign(K, {});
        for (int w = 1; w < K; w++) ws[w].idleFrom = T + 1;
        chasePrio = true;
        chaseRebuild();
    }

    int swFind(int x) {
        while (swDsu[x] != x) x = swDsu[x] = swDsu[swDsu[x]];
        return x;
    }
    long long swEdgePos(long long block, int t, int agent) const {
        return swHead + block * swQ * SWEEP_B + (long long)t * swQ + (agent - 1);
    }
    std::pair<int, int> swCell(const SwEdge& e) const {  // 1-indexed probe cell
        if (e.d == 2) return {2 * e.r + 2, 2 * e.c + 1};
        return {2 * e.r + 1, 2 * e.c + 2};
    }
    static int swDec(char ch) {
        if ('A' <= ch && ch <= 'Z') return ch - 'A';
        if ('a' <= ch && ch <= 'z') return 26 + ch - 'a';
        if ('0' <= ch && ch <= '9') return 52 + ch - '0';
        if (ch == '+') return 62;
        if (ch == '/') return 63;
        return 0;
    }

    void swAddOpen(long long pos) {
        if (pos < 0 || pos >= swE) return;
        const SwEdge& e = swEdges[pos];
        static constexpr int SDR[4] = {-1, 0, 1, 0}, SDC[4] = {0, -1, 0, 1};
        int u = e.r * m + e.c;
        int v = (e.r + SDR[e.d]) * m + (e.c + SDC[e.d]);
        int ru = swFind(u), rv = swFind(v);
        if (ru != rv) {
            if (swRank[ru] < swRank[rv]) std::swap(ru, rv);
            swDsu[rv] = ru;
            if (swRank[ru] == swRank[rv]) swRank[ru]++;
            swAdj[u].push_back({v, e.d});
            swAdj[v].push_back({u, e.d ^ 2});
        }
        if (!swConnected && swFind(0) == swFind(m * m - 1)) {
            swBuildAnswer();
            swConnected = true;
        }
    }

    void swBuildAnswer() {
        static constexpr char CHD[4] = {'U', 'L', 'D', 'R'};
        int total = m * m;
        std::vector<int> par(total, -1), pd(total, -1);
        std::deque<int> q{0};
        par[0] = 0;
        while (!q.empty()) {
            int v = q.front();
            q.pop_front();
            if (v == total - 1) break;
            for (auto [to, d] : swAdj[v])
                if (par[to] == -1) {
                    par[to] = v;
                    pd[to] = d;
                    q.push_back(to);
                }
        }
        std::vector<int> dirs;
        for (int cur = total - 1; cur != 0 && par[cur] != -1; cur = par[cur])
            dirs.push_back(pd[cur]);
        std::reverse(dirs.begin(), dirs.end());
        swAnswer.clear();
        for (int d : dirs) {
            swAnswer += CHD[d];
            swAnswer += CHD[d];
        }
    }

    void sweepTick() {
        if (M->agentId == 0) {
            if (chaseStage == 3) {  // handed over to the batch machinery
                coordTick();
                return;
            }
            if (swConnected) {
                M->claim(swAnswer);
                finished = true;
                return;
            }
            if (chaseStage == 0 && chaseOn && swKnownCnt >= chaseTrig * C) {
                chaseStage = 1;
                for (int w = 1; w < K; w++) swSwitchQ.push_back(w);
            }
            if (chaseStage == 1) {
                M->send(swSwitchQ.front(), "S");
                swSwitchQ.pop_front();
                if (swSwitchQ.empty()) chaseStage = 2;
                return;
            }
            if (chaseStage == 0 && swCollPos < swHead) {  // zero-latency hot band
                auto [r, c] = swCell(swEdges[swCollPos]);
                bool open = M->query(r, c);
                swMarkPos(swCollPos, open);
                swCollPos++;
                return;
            }
            Msg r = M->recvAny();
            if (!r.ok || r.sender <= 0 || r.sender >= K) return;
            if (r.body == "=") {
                swAck[r.sender] = 1;
                bool all = true;
                for (int w = 1; w < K; w++)
                    if (!swAck[w]) all = false;
                if (all) {
                    buildChaseModel();
                    chaseStage = 3;
                }
                return;
            }
            size_t cp = r.body.find(':');
            if (cp == std::string::npos) return;
            long long block = 0;
            for (size_t i = 0; i < cp; i++)
                if (r.body[i] >= '0' && r.body[i] <= '9') block = block * 10 + (r.body[i] - '0');
            int t = 0;
            for (size_t i = cp + 1; i < r.body.size() && t < SWEEP_B; i++) {
                int val = swDec(r.body[i]);
                for (int k = 0; k < 6 && t < SWEEP_B; k++, t++) {
                    long long pos = swEdgePos(block, t, r.sender);
                    if (pos < swE) swMarkPos(pos, ((val >> k) & 1) != 0);
                }
            }
            return;
        }
        // worker: probe my stride in blocks of SWEEP_B, ship each block
        if (wChase) {
            workerTick();
            return;
        }
        if (wSendMarker) {  // handshake: all my sweep blocks precede this
            M->send(0, "=");
            wSendMarker = false;
            wChase = true;
            return;
        }
        if (wPollInbox) {
            wPollInbox = false;
            Msg a = M->recvFrom(0);
            if (a.ok && a.body == "S") wSendMarker = true;
            return;
        }
        while (swPos < SWEEP_B) {
            long long ei = swEdgePos(swBlock, swPos, M->agentId);
            if (ei < swE) {
                auto [r, c] = swCell(swEdges[ei]);
                swBits.push_back(M->query(r, c) ? 1 : 0);
                swPos++;
                return;
            }
            if (swBits.empty()) {  // exhausted: wait for a possible chase switch
                Msg a = M->recvFrom(0);
                if (a.ok && a.body == "S") wSendMarker = true;
                return;
            }
            swBits.push_back(0);  // pad the final partial block
            swPos++;
        }
        std::string body = std::to_string(swBlock) + ":";
        for (size_t k = 0; k < swBits.size(); k += 6) {
            int val = 0;
            for (int b = 0; b < 6 && k + b < swBits.size(); b++)
                if (swBits[k + b]) val |= 1 << b;
            body += SW_ALPH[val];
        }
        M->send(0, body);
        swBlock++;
        swPos = 0;
        swBits.clear();
        if (chaseOn && (swBlock & 1) == 0) wPollInbox = true;
    }

    // =========================================================================
    // Explorer mode (K in [2,4])
    // =========================================================================

    void rebuildFrontier() {
        pq = MinHeap();
        pqHot = MinHeap();
        int root = find(mySeed);
        for (int r : members[root])
            if (unkCnt[r] > 0) pushRoomFrontier(r);
    }

    void retarget(int mask) {
        if (mask < 1 || mask > 3 || mask == myTargets) return;
        myTargets = mask;
        rebuildFrontier();
    }

    void applyDelta(const std::string& body) {
        int step = idxChars + 1;
        for (size_t p = 0; p + step <= body.size(); p += (size_t)step) {
            int idx = decIdx(body, (int)p);
            if (idx < 0 || idx >= C) continue;
            if (body[p + idxChars] == '1') applyOpen(idx);
            else applyWall(idx);
        }
    }

    int targetsFor(int w) {
        int root = find(wSeed[w]);
        int rs = find(startRoom), rg = find(goalRoom);
        if (root == rs && root == rg) return wTold[w];  // connected: moot
        if (root == rs) return 1;  // absorbed into start side: dig toward goal
        if (root == rg) return 2;
        return wTold[w];  // still floating: keep as is
    }

    // One fresh greedy probe of my own frontier.
    void digTick() {
        int idx = popValid();
        if (idx < 0 && T - lastRebuild >= 16) {
            lastRebuild = T;
            rebuildFrontier();
            idx = popValid();
        }
        if (idx >= 0) {
#ifdef LOCAL_RUN
            dSelfProbe++;
#endif
            auto [r, c] = corridorCell(idx);
            if (M->query(r, c)) applyOpen(idx);
            else applyWall(idx);
            return;
        }
#ifdef LOCAL_RUN
        dSkip++;
#endif
        M->skip();
    }

    void explorerTick() {
        int ph = (int)(T % SYNC_W);
        if (M->agentId == 0) {
            if (connectedSG) {
#ifdef LOCAL_RUN
                dumpDiag();
#endif
                M->claim(buildPath());
                finished = true;
                return;
            }
            for (int w = 1; w < K; w++) {
                int base = (w - 1) * phaseStep;
                if (ph == base + 1) {
#ifdef LOCAL_RUN
                    dRecv++;
#endif
                    Msg r = M->recvFrom(w);
                    if (r.ok) {
                        wMiss[w] = 0;
                        applyDelta(r.body);
                    } else {
                        wMiss[w]++;
                    }
                    return;
                }
                if (ph == base + 2 && wMiss[w] < 5) {
                    int want = targetsFor(w);
                    if (want != wTold[w]) {
                        wTold[w] = want;
#ifdef LOCAL_RUN
                        dSend++;
#endif
                        M->send(w, std::string("T") + char('0' + want));
                        return;
                    }
                }
            }
            digTick();
            return;
        }
        // worker digger
        int base = (M->agentId - 1) * phaseStep;
        if (ph == base) {
            // ship up to a message worth of accumulated probe/inference deltas
            int step = idxChars + 1;
            int maxEntries = L / step;
            size_t take = std::min(pendingDelta.size(), (size_t)maxEntries * step);
#ifdef LOCAL_RUN
            dSend++;
#endif
            M->send(0, pendingDelta.substr(0, take));
            pendingDelta.erase(0, take);
            return;
        }
        if (ph == base + 4) {
#ifdef LOCAL_RUN
            dRecv++;
#endif
            Msg h = M->recvFrom(0);
            if (h.ok && h.body.size() >= 2 && h.body[0] == 'T') retarget(h.body[1] - '0');
            return;
        }
        digTick();
    }

    // =========================================================================
    // Worker
    // =========================================================================
    std::vector<int> wIdx;   // active batch corridor indices
    size_t wPos = 0;         // next probe position
    std::string wBits;       // accumulated result chars

    void workerTick() {
        if (wPos < wIdx.size()) {  // probing
            auto [r, c] = corridorCell(wIdx[wPos]);
            bool open = M->query(r, c);
            if (open) wBits[wPos / 6] = char(wBits[wPos / 6] + (1 << (wPos % 6)));
            wPos++;
            return;
        }
        if (!wIdx.empty()) {  // batch done: report
            M->send(0, wBits);
            wIdx.clear();
            wBits.clear();
            wPos = 0;
            return;
        }
        Msg a = M->recvFrom(0);  // idle: wait for an assignment
        if (a.ok) {
            int n = (int)a.body.size() / idxChars;
            wIdx.resize(n);
            for (int j = 0; j < n; j++) wIdx[j] = decIdx(a.body, j * idxChars);
            wBits.assign((n + 5) / 6, A64);
            wPos = 0;
        }
    }
};

// ---------------------------------------------------------------------------
// Prod entry point
// ---------------------------------------------------------------------------

#ifndef LOCAL_RUN
int main() {
    MerlinProd merlin;
    Strategy strategy(&merlin);
    while (!strategy.done()) strategy.tick();
    return 0;
}
#endif
