Drop7 Research
approaches/ntuple-rl/manifold-ppo/manifold-root-prior.cppC++1,352 lines · 52.9 KB
#define DROP7_CURRICULUM_OPTION_PPO_LIBRARY
#include "../curriculum-option-ppo/curriculum-option-ppo.cpp"
#undef DROP7_CURRICULUM_OPTION_PPO_LIBRARY

#include <map>
#include <optional>

// Fits a standalone scalar to the fixed, exact-load-matched public
// oracle-manifold dataset.  The scalar can break only close exact-fair-D3 root
// decisions and is not used as a search-leaf value.
namespace drop7::manifold_root_prior {

namespace prior = drop7::curriculum_option_ppo;
namespace vr = drop7::viability_reservoir_controller;
namespace fair = drop7::fair_only_horizon;
namespace detail = drop7::cfpi::detail;

using Clock = std::chrono::steady_clock;
using PublicState = prior::PublicState;

// Reconstruct the checksum-stable matching set and exact public feature
// transform locally so this executable has no external entry-point or artifact
// dependency.  The negative games are development-only replay data; replay
// does not inspect a new seed.
namespace manifold {

constexpr std::uint32_t kNegativeSeedStart = 0x3d6b'0000u;
constexpr std::uint32_t kNegativeSeedEndExclusive = 0x3d6b'0400u;
constexpr int kNegativeMaximumMoves = 1'000;
constexpr std::uint32_t kPositiveFoldDomain = 0x504f'5346u;
constexpr std::uint32_t kNegativeFoldDomain = 0x4e45'4746u;

std::uint64_t mix64(std::uint64_t value) {
  value ^= value >> 30u;
  value *= 0xbf58'476d'1ce4'e5b9ull;
  value ^= value >> 27u;
  value *= 0x94d0'49bb'1331'11ebull;
  return value ^ (value >> 31u);
}

std::uint64_t publicHash(const PublicState& source) {
  bool ignored = false;
  const PublicState state = vr::canonicalState(source, ignored);
  std::uint64_t hash = 0xcbf2'9ce4'8422'2325ull;
  for (const std::uint8_t cell : state.board) {
    hash ^= static_cast<std::uint64_t>(cell + 1u);
    hash *= 0x0000'0100'0000'01b3ull;
  }
  hash ^= state.next_disc;
  hash *= 0x0000'0100'0000'01b3ull;
  hash ^= static_cast<std::uint64_t>(state.moves_remaining + 1u);
  hash *= 0x0000'0100'0000'01b3ull;
  return mix64(hash);
}

struct Stratum {
  std::uint8_t phase = 0;
  std::uint8_t occupied = 0;
  std::uint8_t maximum_height = 0;
  auto operator<=>(const Stratum&) const = default;
};

Stratum stratum(const PublicState& state) {
  const auto heights = vr::columnHeights(state.board);
  int occupied = 0;
  int maximum = 0;
  for (const int height : heights) {
    occupied += height;
    maximum = std::max(maximum, height);
  }
  return {state.moves_remaining, static_cast<std::uint8_t>(occupied),
          static_cast<std::uint8_t>(maximum)};
}

struct NegativeState {
  PublicState state{};
  std::uint32_t origin_seed = 0;
  std::uint64_t hash = 0;
};

struct MatchedPair {
  PublicState positive{};
  PublicState negative{};
  Stratum load{};
  std::uint64_t positive_hash = 0;
  std::uint64_t negative_hash = 0;
  std::uint32_t negative_origin = 0;
  int fold = 0;
};

struct MatchedDataset {
  std::array<std::vector<MatchedPair>, 2> folds;
  std::size_t matched = 0;
  std::uint64_t fingerprint = 0xcbf2'9ce4'8422'2325ull;
};

void fingerprintWord(std::uint64_t& hash, std::uint64_t value) {
  for (int shift = 0; shift < 64; shift += 8) {
    prior::fingerprintByte(hash, static_cast<std::uint8_t>(value >> shift));
  }
}

template <typename DeadlineType>
MatchedDataset buildMatchedDataset(const prior::Curriculum& curriculum,
                                   const DeadlineType& deadline) {
  using Bucket = std::array<std::vector<NegativeState>, 2>;
  std::map<Stratum, Bucket> negatives;
  for (std::uint32_t seed = kNegativeSeedStart;
       seed < kNegativeSeedEndExclusive; ++seed) {
    if (((seed - kNegativeSeedStart) & 31u) == 0) deadline.check();
    State state = initialHeadlessState(seed);
    const int fold =
        static_cast<int>(mix32(seed ^ kNegativeFoldDomain) & 1u);
    while (!state.game_over && state.moves_played < kNegativeMaximumMoves) {
      const PublicState public_state = vr::publicState(state);
      const vr::BaselineDecision decision =
          vr::chooseFairDepthOne(public_state);
      if (!decision.complete || !isLegal(state.board, decision.action)) {
        throw std::runtime_error("fair-D1 matching replay was incomplete");
      }
      negatives[stratum(public_state)][fold].push_back(
          {public_state, seed, publicHash(public_state)});
      MoveResult move;
      if (!playHeadlessMove(state, seed, decision.action, move)) {
        throw std::runtime_error("matching replay transition failed");
      }
    }
  }
  for (auto& [ignored, bucket] : negatives) {
    static_cast<void>(ignored);
    for (auto& values : bucket) {
      std::sort(values.begin(), values.end(), [](const NegativeState& a,
                                                 const NegativeState& b) {
        return std::tie(a.hash, a.origin_seed) <
               std::tie(b.hash, b.origin_seed);
      });
    }
  }
  std::array<std::vector<std::pair<std::uint64_t, PublicState>>, 2> positives;
  for (const PublicState& state : curriculum.states) {
    const std::uint64_t hash = publicHash(state);
    const int fold =
        static_cast<int>(mix64(hash ^ kPositiveFoldDomain) & 1u);
    positives[fold].push_back({hash, state});
  }
  for (auto& values : positives) {
    std::sort(values.begin(), values.end(), [](const auto& a, const auto& b) {
      return a.first < b.first;
    });
  }
  MatchedDataset result;
  std::map<std::pair<Stratum, int>, std::size_t> cursor;
  for (int fold = 0; fold < 2; ++fold) {
    for (const auto& [positive_hash, positive] : positives[fold]) {
      const Stratum load = stratum(positive);
      auto found = negatives.find(load);
      if (found == negatives.end()) continue;
      std::vector<NegativeState>& pool = found->second[fold];
      std::size_t& index = cursor[{load, fold}];
      if (index >= pool.size()) continue;
      const NegativeState& negative = pool[index++];
      result.folds[fold].push_back(
          {positive, negative.state, load, positive_hash, negative.hash,
           negative.origin_seed, fold});
    }
  }
  for (int fold = 0; fold < 2; ++fold) {
    for (const MatchedPair& pair : result.folds[fold]) {
      if (pair.load != stratum(pair.positive) ||
          pair.load != stratum(pair.negative) || pair.fold != fold ||
          static_cast<int>(mix32(pair.negative_origin ^
                                 kNegativeFoldDomain) &
                           1u) != fold ||
          static_cast<int>(mix64(pair.positive_hash ^
                                 kPositiveFoldDomain) &
                           1u) != fold) {
        throw std::runtime_error("matched dataset integrity failure");
      }
      fingerprintWord(result.fingerprint, pair.positive_hash);
      fingerprintWord(result.fingerprint, pair.negative_hash);
    }
    result.matched += result.folds[fold].size();
  }
  return result;
}

constexpr int kDiscriminatorHidden = 24;
constexpr int kTokenTotalInputs = prior::kBoardCategories;
constexpr int kRowTokenInputs = kBoardSize * prior::kBoardCategories;
constexpr int kColumnPairTokenInputs = 4 * prior::kBoardCategories;
constexpr int kNextInputs = kBoardSize;
constexpr int kPhaseInputs = kMovesPerLevel;
constexpr int kHeightPairInputs = 8;
constexpr int kGraphInputs = prior::kGraphInputs;
constexpr int kTriggerInputs = prior::kTriggerSummaryInputs;
constexpr int kKeyAggregateInputs = kBoardSize * prior::kKeyInputs * 3;
constexpr int kDiscriminatorInputs =
    kTokenTotalInputs + kRowTokenInputs + kColumnPairTokenInputs +
    kNextInputs + kPhaseInputs + kHeightPairInputs + kGraphInputs +
    kTriggerInputs + kKeyAggregateInputs;
static_assert(kDiscriminatorInputs == 295);

using TopologyFeatures = std::array<float, kDiscriminatorInputs>;

TopologyFeatures topologyFeatures(const PublicState& source) {
  bool ignored = false;
  const PublicState state = vr::canonicalState(source, ignored);
  const prior::Observation observation = prior::observePublic(state);
  TopologyFeatures result{};
  int offset = 0;
  for (int category = 0; category < prior::kBoardCategories; ++category) {
    float total = 0.0f;
    for (int cell = 0; cell < kCellCount; ++cell) {
      total += observation.input[prior::kBoardOffset +
                                 cell * prior::kBoardCategories + category];
    }
    result[offset++] = total / 49.0f;
  }
  for (int row = 0; row < kBoardSize; ++row) {
    for (int category = 0; category < prior::kBoardCategories; ++category) {
      float total = 0.0f;
      for (int column = 0; column < kBoardSize; ++column) {
        total += observation.input[
            prior::kBoardOffset + indexOf(row, column) *
                                      prior::kBoardCategories + category];
      }
      result[offset++] = total / 7.0f;
    }
  }
  for (int pair = 0; pair < 4; ++pair) {
    const int left = pair;
    const int right = kBoardSize - 1 - pair;
    const float denominator = left == right ? 7.0f : 14.0f;
    for (int category = 0; category < prior::kBoardCategories; ++category) {
      float total = 0.0f;
      for (int row = 0; row < kBoardSize; ++row) {
        total += observation.input[
            prior::kBoardOffset + indexOf(row, left) *
                                      prior::kBoardCategories + category];
        if (left != right) {
          total += observation.input[
              prior::kBoardOffset + indexOf(row, right) *
                                        prior::kBoardCategories + category];
        }
      }
      result[offset++] = total / denominator;
    }
  }
  std::copy(observation.input.begin() + prior::kNextOffset,
            observation.input.begin() + prior::kPhaseOffset,
            result.begin() + offset);
  offset += kNextInputs;
  std::copy(observation.input.begin() + prior::kPhaseOffset,
            observation.input.begin() + prior::kHeightOffset,
            result.begin() + offset);
  offset += kPhaseInputs;
  for (int pair = 0; pair < 4; ++pair) {
    const int left = pair;
    const int right = kBoardSize - 1 - pair;
    const float left_height = observation.input[prior::kHeightOffset + left];
    const float right_height = observation.input[prior::kHeightOffset + right];
    result[offset++] = 0.5f * (left_height + right_height);
    result[offset++] = std::abs(left_height - right_height);
  }
  std::copy(observation.input.begin() + prior::kGraphOffset,
            observation.input.begin() + prior::kTriggerSummaryOffset,
            result.begin() + offset);
  offset += kGraphInputs;
  std::copy(observation.input.begin() + prior::kTriggerSummaryOffset,
            observation.input.begin() + prior::kOptionOffset,
            result.begin() + offset);
  offset += kTriggerInputs;
  for (int disc = 0; disc < kBoardSize; ++disc) {
    for (int feature = 0; feature < prior::kKeyInputs; ++feature) {
      float sum = 0.0f;
      float minimum = std::numeric_limits<float>::infinity();
      float maximum = -std::numeric_limits<float>::infinity();
      for (int column = 0; column < kBoardSize; ++column) {
        const float value = observation.input[
            prior::kKeysOffset +
            (disc * kBoardSize + column) * prior::kKeyInputs + feature];
        sum += value;
        minimum = std::min(minimum, value);
        maximum = std::max(maximum, value);
      }
      result[offset++] = sum / 7.0f;
      result[offset++] = minimum;
      result[offset++] = maximum;
    }
  }
  if (offset != kDiscriminatorInputs) {
    throw std::runtime_error("root-prior feature layout mismatch");
  }
  return result;
}

}  // namespace manifold

constexpr std::uint32_t kFitSeedStart = 0x3d6f'0000u;
constexpr std::uint32_t kFitSeedEndExclusive = 0x3d6f'0010u;
constexpr int kFitGames = 16;
constexpr std::uint32_t kScreenSeedStart = 0x3d6f'1000u;
constexpr std::uint32_t kScreenSeedEndExclusive = 0x3d6f'1020u;
constexpr int kScreenGames = 32;
constexpr int kMaximumMoves = 1'000;
constexpr int kMaximumThreads = 4;
constexpr int kSearchDepth = 3;
constexpr double kRootQWindow = 2'500.0;
constexpr int kMaximumAdmittedActions = 2;
constexpr int kSuccessorScenarios = 7;
constexpr std::uint32_t kSuccessorPolicySeed = 0x4d52'5052u;

constexpr double kFitScoreRatio = 1.15;
constexpr double kFitMoveRatio = 1.15;
constexpr double kFitFlowGain = 0.05;
constexpr int kFitJointWins = 11;
constexpr double kScreenScoreRatio = 1.10;
constexpr double kScreenMoveRatio = 1.10;
constexpr int kScreenJointWins = 20;

constexpr int kEpochs = 48;
constexpr int kBatchPairs = 128;
constexpr float kLearningRate = 0.0015f;
constexpr float kGradientNorm = 1.0f;
constexpr float kL2 = 0.00002f;
constexpr std::uint32_t kModelSeed = 0x4d41'4e31u;
constexpr std::uint32_t kShuffleDomain = 0x4449'5348u;
constexpr std::uint64_t kExpectedCurriculumFingerprint =
    0x8657'ac0d'c83c'6041ull;
constexpr std::uint64_t kExpectedMatchedFingerprint =
    0xc1ad'c1ba'7dae'1d99ull;
constexpr int kExpectedMatchedPairs = 3'032;
constexpr double kWallLimitSeconds = 45.0 * 60.0;
constexpr std::uint64_t kRssLimitBytes = 256ull * 1024ull * 1024ull;
constexpr std::uint64_t kCheckpointMagic = 0x4437'4d52'5052'3031ull;
constexpr std::uint32_t kCheckpointVersion = 1;

static_assert(kLevelBonus == 17'000);
static_assert(kFitSeedEndExclusive - kFitSeedStart == kFitGames);
static_assert(kScreenSeedEndExclusive - kScreenSeedStart == kScreenGames);
static_assert(kSearchDepth == fair::kDepth);
static_assert(kSuccessorScenarios == kBoardSize);
static_assert(manifold::kDiscriminatorInputs == 295);
static_assert(manifold::kDiscriminatorHidden == 24);

struct Deadline {
  Clock::time_point started = Clock::now();
  double elapsedSeconds() const {
    return std::chrono::duration<double>(Clock::now() - started).count();
  }
  void check() const {
    if (elapsedSeconds() > kWallLimitSeconds) {
      throw std::runtime_error("manifold root prior exceeded 45 minute cap");
    }
  }
};

std::uint64_t peakRssBytes() { return prior::peakRssBytes(); }

void enforceRss() {
  if (peakRssBytes() > kRssLimitBytes) {
    throw std::runtime_error("manifold root prior exceeded 256 MiB RSS");
  }
}

enum class SeedUse : std::uint8_t { kFit, kScreen };

bool allowedSeed(std::uint32_t seed, SeedUse use) {
  const std::uint32_t begin =
      use == SeedUse::kFit ? kFitSeedStart : kScreenSeedStart;
  const std::uint32_t end = use == SeedUse::kFit
                                ? kFitSeedEndExclusive
                                : kScreenSeedEndExclusive;
  const std::uint8_t prefix = static_cast<std::uint8_t>(seed >> 24u);
  return seed >= begin && seed < end && prefix != 0x4d && prefix != 0x7d &&
         prefix != 0xd7;
}

void requireSeed(std::uint32_t seed, SeedUse use) {
  if (!allowedSeed(seed, use)) {
    throw std::invalid_argument("seed outside frozen 3d6f root-prior lanes");
  }
}

struct Layout {
  static constexpr int w1 = 0;
  static constexpr int b1 =
      w1 + manifold::kDiscriminatorHidden * manifold::kDiscriminatorInputs;
  static constexpr int w2 = b1 + manifold::kDiscriminatorHidden;
  static constexpr int b2 = w2 + manifold::kDiscriminatorHidden;
  static constexpr int count = b2 + 1;
};
static_assert(Layout::count == 7'129);

struct Cache {
  std::array<float, manifold::kDiscriminatorHidden> hidden{};
  float logit = 0.0f;
};

class Model {
 public:
  explicit Model(std::uint32_t seed = kModelSeed)
      : parameters_(Layout::count, 0.0f), first_(Layout::count, 0.0f),
        second_(Layout::count, 0.0f) {
    Mulberry32 random(seed);
    const float radius = std::sqrt(
        6.0f / static_cast<float>(manifold::kDiscriminatorInputs +
                                  manifold::kDiscriminatorHidden));
    for (int index = 0;
         index < manifold::kDiscriminatorHidden *
                     manifold::kDiscriminatorInputs;
         ++index) {
      parameters_[Layout::w1 + index] = static_cast<float>(
          (2.0 * random.nextUnit() - 1.0) * radius);
    }
  }

  Cache forward(const manifold::TopologyFeatures& features) const {
    Cache cache;
    for (int output = 0; output < manifold::kDiscriminatorHidden; ++output) {
      float value = parameters_[Layout::b1 + output];
      const int weights =
          Layout::w1 + output * manifold::kDiscriminatorInputs;
      for (int input = 0; input < manifold::kDiscriminatorInputs; ++input) {
        value += parameters_[weights + input] * features[input];
      }
      cache.hidden[output] = std::tanh(value);
    }
    cache.logit = parameters_[Layout::b2];
    for (int hidden = 0; hidden < manifold::kDiscriminatorHidden; ++hidden) {
      cache.logit +=
          parameters_[Layout::w2 + hidden] * cache.hidden[hidden];
    }
    return cache;
  }

  float logit(const PublicState& state) const {
    return forward(manifold::topologyFeatures(state)).logit;
  }

  std::vector<float> gradient() const {
    return std::vector<float>(Layout::count, 0.0f);
  }

  void accumulate(const manifold::TopologyFeatures& features,
                  const Cache& cache, float derivative,
                  std::vector<float>& gradient) const {
    gradient[Layout::b2] += derivative;
    for (int hidden = 0; hidden < manifold::kDiscriminatorHidden; ++hidden) {
      gradient[Layout::w2 + hidden] += derivative * cache.hidden[hidden];
      const float hidden_derivative =
          derivative * parameters_[Layout::w2 + hidden] *
          (1.0f - cache.hidden[hidden] * cache.hidden[hidden]);
      gradient[Layout::b1 + hidden] += hidden_derivative;
      const int weights =
          Layout::w1 + hidden * manifold::kDiscriminatorInputs;
      for (int input = 0; input < manifold::kDiscriminatorInputs; ++input) {
        gradient[weights + input] += hidden_derivative * features[input];
      }
    }
  }

  void apply(std::vector<float>& gradient) {
    double squared_norm = 0.0;
    for (int index = 0; index < Layout::count; ++index) {
      if (index < Layout::b2) gradient[index] += kL2 * parameters_[index];
      squared_norm += gradient[index] * gradient[index];
    }
    const double norm = std::sqrt(squared_norm);
    const float scale = norm > kGradientNorm
                            ? static_cast<float>(kGradientNorm / norm)
                            : 1.0f;
    ++step_;
    constexpr float beta1 = 0.9f;
    constexpr float beta2 = 0.999f;
    constexpr float epsilon = 1.0e-8f;
    const float correction1 = 1.0f - std::pow(beta1, static_cast<float>(step_));
    const float correction2 = 1.0f - std::pow(beta2, static_cast<float>(step_));
    for (int index = 0; index < Layout::count; ++index) {
      const float value = gradient[index] * scale;
      first_[index] = beta1 * first_[index] + (1.0f - beta1) * value;
      second_[index] =
          beta2 * second_[index] + (1.0f - beta2) * value * value;
      parameters_[index] -=
          kLearningRate * (first_[index] / correction1) /
          (std::sqrt(second_[index] / correction2) + epsilon);
      if (!std::isfinite(parameters_[index])) {
        throw std::runtime_error("non-finite root-prior parameter");
      }
    }
  }

  const std::vector<float>& parameters() const { return parameters_; }

  void setParameters(const std::vector<float>& parameters) {
    if (parameters.size() != parameters_.size()) {
      throw std::invalid_argument("root-prior parameter count mismatch");
    }
    parameters_ = parameters;
    std::fill(first_.begin(), first_.end(), 0.0f);
    std::fill(second_.begin(), second_.end(), 0.0f);
    step_ = 0;
  }

 private:
  std::vector<float> parameters_;
  std::vector<float> first_;
  std::vector<float> second_;
  std::uint64_t step_ = 0;
};

using PublicScalar = float (Model::*)(const PublicState&) const;
static_assert(std::is_same_v<decltype(&Model::logit), PublicScalar>);
static_assert(
    !std::is_invocable_v<PublicScalar, const Model&, const State&>);

std::uint64_t modelFingerprint(const Model& model) {
  std::uint64_t hash = 0xcbf2'9ce4'8422'2325ull;
  for (const float parameter : model.parameters()) {
    const std::uint32_t bits = std::bit_cast<std::uint32_t>(parameter);
    for (int shift = 0; shift < 32; shift += 8) {
      prior::fingerprintByte(hash,
                             static_cast<std::uint8_t>(bits >> shift));
    }
  }
  return hash;
}

void saveCheckpoint(const std::string& path, const Model& model) {
  std::ofstream output(path, std::ios::binary);
  if (!output) throw std::runtime_error("could not open root-prior checkpoint");
  const std::uint32_t count = Layout::count;
  const std::uint64_t fingerprint = modelFingerprint(model);
  output.write(reinterpret_cast<const char*>(&kCheckpointMagic),
               sizeof(kCheckpointMagic));
  output.write(reinterpret_cast<const char*>(&kCheckpointVersion),
               sizeof(kCheckpointVersion));
  output.write(reinterpret_cast<const char*>(&count), sizeof(count));
  output.write(reinterpret_cast<const char*>(&fingerprint),
               sizeof(fingerprint));
  output.write(reinterpret_cast<const char*>(model.parameters().data()),
               static_cast<std::streamsize>(model.parameters().size() *
                                            sizeof(float)));
  if (!output) throw std::runtime_error("failed writing root-prior checkpoint");
}

Model loadCheckpoint(const std::string& path) {
  std::ifstream input(path, std::ios::binary);
  if (!input) throw std::runtime_error("could not open root-prior checkpoint");
  std::uint64_t magic = 0;
  std::uint32_t version = 0;
  std::uint32_t count = 0;
  std::uint64_t expected = 0;
  input.read(reinterpret_cast<char*>(&magic), sizeof(magic));
  input.read(reinterpret_cast<char*>(&version), sizeof(version));
  input.read(reinterpret_cast<char*>(&count), sizeof(count));
  input.read(reinterpret_cast<char*>(&expected), sizeof(expected));
  if (magic != kCheckpointMagic || version != kCheckpointVersion ||
      count != Layout::count) {
    throw std::runtime_error("invalid root-prior checkpoint header");
  }
  std::vector<float> parameters(count);
  input.read(reinterpret_cast<char*>(parameters.data()),
             static_cast<std::streamsize>(parameters.size() * sizeof(float)));
  char trailing = 0;
  if (!input || input.read(&trailing, 1)) {
    throw std::runtime_error("invalid root-prior checkpoint payload");
  }
  Model result;
  result.setParameters(parameters);
  if (modelFingerprint(result) != expected) {
    throw std::runtime_error("root-prior checkpoint fingerprint mismatch");
  }
  return result;
}

float sigmoid(float value) {
  if (value >= 0.0f) {
    const float exponential = std::exp(-value);
    return 1.0f / (1.0f + exponential);
  }
  const float exponential = std::exp(value);
  return exponential / (1.0f + exponential);
}

Model fitModel(const manifold::MatchedDataset& dataset,
               const Deadline& deadline) {
  std::vector<const manifold::MatchedPair*> pairs;
  pairs.reserve(dataset.matched);
  for (const auto& fold : dataset.folds) {
    for (const manifold::MatchedPair& pair : fold) pairs.push_back(&pair);
  }
  if (pairs.size() != kExpectedMatchedPairs) {
    throw std::runtime_error("root-prior fit did not receive 3,032 pairs");
  }
  Model model;
  std::vector<std::size_t> indexes(pairs.size());
  std::iota(indexes.begin(), indexes.end(), 0u);
  Mulberry32 random(mix32(kModelSeed ^ kShuffleDomain));
  for (int epoch = 0; epoch < kEpochs; ++epoch) {
    for (std::size_t index = indexes.size(); index > 1; --index) {
      const std::size_t selected = static_cast<std::size_t>(
          (static_cast<std::uint64_t>(random.nextBits()) * index) >> 32u);
      std::swap(indexes[index - 1], indexes[selected]);
    }
    for (std::size_t begin = 0; begin < indexes.size();
         begin += kBatchPairs) {
      if ((begin & 1'023u) == 0) deadline.check();
      const std::size_t end = std::min(indexes.size(), begin + kBatchPairs);
      const float inverse = 1.0f / static_cast<float>(2 * (end - begin));
      std::vector<float> gradient = model.gradient();
      for (std::size_t offset = begin; offset < end; ++offset) {
        const manifold::MatchedPair& pair = *pairs[indexes[offset]];
        const manifold::TopologyFeatures positive =
            manifold::topologyFeatures(pair.positive);
        const manifold::TopologyFeatures negative =
            manifold::topologyFeatures(pair.negative);
        const Cache positive_cache = model.forward(positive);
        const Cache negative_cache = model.forward(negative);
        model.accumulate(positive, positive_cache,
                         (sigmoid(positive_cache.logit) - 1.0f) * inverse,
                         gradient);
        model.accumulate(negative, negative_cache,
                         sigmoid(negative_cache.logit) * inverse, gradient);
      }
      model.apply(gradient);
    }
  }
  return model;
}

struct LabelMetrics {
  double auc = 0.0;
  double pair_ranking = 0.0;
  double loss = 0.0;
};

LabelMetrics labelMetrics(const Model& model,
                          const manifold::MatchedDataset& dataset) {
  struct Scored {
    double score = 0.0;
    bool positive = false;
  };
  std::vector<Scored> scores;
  scores.reserve(dataset.matched * 2);
  double wins = 0.0;
  double loss = 0.0;
  for (const auto& fold : dataset.folds) {
    for (const manifold::MatchedPair& pair : fold) {
      const double positive = model.logit(pair.positive);
      const double negative = model.logit(pair.negative);
      scores.push_back({positive, true});
      scores.push_back({negative, false});
      wins += positive > negative ? 1.0 : (positive == negative ? 0.5 : 0.0);
      loss += std::log1p(std::exp(-std::clamp(positive, -40.0, 40.0)));
      loss += std::log1p(std::exp(std::clamp(negative, -40.0, 40.0)));
    }
  }
  std::sort(scores.begin(), scores.end(), [](const Scored& a,
                                             const Scored& b) {
    return a.score < b.score;
  });
  double rank_sum = 0.0;
  std::size_t begin = 0;
  while (begin < scores.size()) {
    std::size_t end = begin + 1;
    while (end < scores.size() && scores[end].score == scores[begin].score) {
      ++end;
    }
    const double rank =
        0.5 * (static_cast<double>(begin + 1) + static_cast<double>(end));
    for (std::size_t index = begin; index < end; ++index) {
      if (scores[index].positive) rank_sum += rank;
    }
    begin = end;
  }
  const double count = static_cast<double>(dataset.matched);
  return {(rank_sum - count * (count + 1.0) * 0.5) / (count * count),
          wins / count, loss / (2.0 * count)};
}

std::vector<int> admittedActions(
    const std::array<double, kBoardSize>& values,
    const Board& board, int anchor) {
  if (anchor < 0 || !isLegal(board, anchor) ||
      !std::isfinite(values[anchor])) {
    throw std::invalid_argument("invalid D3 anchor");
  }
  std::vector<int> eligible;
  for (int column = 0; column < kBoardSize; ++column) {
    if (isLegal(board, column) && std::isfinite(values[column]) &&
        values[column] >= values[anchor] - kRootQWindow) {
      eligible.push_back(column);
    }
  }
  std::sort(eligible.begin(), eligible.end(), [&](int a, int b) {
    if (a == anchor) return true;
    if (b == anchor) return false;
    if (values[a] != values[b]) return values[a] > values[b];
    int a_order = 0;
    int b_order = 0;
    for (int order = 0; order < kBoardSize; ++order) {
      if (vr::kColumnOrder[order] == a) a_order = order;
      if (vr::kColumnOrder[order] == b) b_order = order;
    }
    return a_order < b_order;
  });
  if (eligible.size() > kMaximumAdmittedActions) {
    eligible.resize(kMaximumAdmittedActions);
  }
  if (eligible.empty() || eligible.front() != anchor) {
    throw std::runtime_error("D3 anchor was not retained");
  }
  return eligible;
}

double expectedSuccessorLogit(const PublicState& canonical, int action,
                              const Model& model) {
  const State public_state = vr::materialize(canonical);
  const std::uint32_t state_seed =
      detail::scenarioSeedForState(public_state, kSuccessorPolicySeed, 0);
  double total = 0.0;
  for (int scenario = 0; scenario < kSuccessorScenarios; ++scenario) {
    detail::StratifiedRandom random{state_seed, scenario,
                                    kSuccessorScenarios, 0};
    MoveResult move;
    if (!detail::playMoveSampled(public_state, action, random, move)) {
      throw std::runtime_error("root-prior successor rejected legal action");
    }
    if (move.state.game_over) {
      total += -10.0;
      continue;
    }
    move.state.score = 0;
    move.state.level = 1;
    move.state.moves_played = 0;
    move.state.next_disc = detail::sampledNextDisc(
        state_seed, scenario, kSuccessorScenarios);
    total += model.logit(vr::publicState(move.state));
  }
  return total / kSuccessorScenarios;
}

struct Decision {
  int action = -1;
  int d3_action = -1;
  int admitted = 0;
  bool changed = false;
  std::uint64_t work = 0;
  std::array<double, kBoardSize> root_values{};
  std::array<double, kBoardSize> manifold_values{};

  bool operator==(const Decision&) const = default;
};

Decision chooseAction(const PublicState& source, const Model& model) {
  if (source.terminal) return {};
  bool mirrored = false;
  const PublicState canonical = vr::canonicalState(source, mirrored);
  const State state = vr::materialize(canonical);
  const fair::SearchDecision search = fair::chooseFairAction(state);
  if (!search.complete || search.completed_depth != kSearchDepth) {
    throw std::runtime_error("root prior did not complete exact fair D3");
  }
  const std::vector<int> admitted =
      admittedActions(search.root_values, canonical.board, search.action);
  int selected = search.action;
  std::array<double, kBoardSize> manifold_values{};
  manifold_values.fill(-std::numeric_limits<double>::infinity());
  if (admitted.size() == 2) {
    for (const int action : admitted) {
      manifold_values[action] =
          expectedSuccessorLogit(canonical, action, model);
    }
    const int challenger = admitted[1];
    if (manifold_values[challenger] > manifold_values[search.action]) {
      selected = challenger;
    }
  }
  Decision result;
  result.action = mirrored ? kBoardSize - 1 - selected : selected;
  result.d3_action =
      mirrored ? kBoardSize - 1 - search.action : search.action;
  result.admitted = static_cast<int>(admitted.size());
  result.changed = selected != search.action;
  result.work = search.work;
  result.root_values.fill(-std::numeric_limits<double>::infinity());
  result.manifold_values.fill(-std::numeric_limits<double>::infinity());
  for (int canonical_action = 0; canonical_action < kBoardSize;
       ++canonical_action) {
    const int physical = mirrored ? kBoardSize - 1 - canonical_action
                                  : canonical_action;
    result.root_values[physical] = search.root_values[canonical_action];
    result.manifold_values[physical] = manifold_values[canonical_action];
  }
  return result;
}

using PublicPolicy = Decision (*)(const PublicState&, const Model&);
static_assert(std::is_same_v<decltype(&chooseAction), PublicPolicy>);
static_assert(!std::is_invocable_v<PublicPolicy, const State&, const Model&>);

enum class Policy : std::uint8_t { kRootPrior, kFairD3 };

struct GameResult {
  std::uint32_t seed = 0;
  std::int64_t score = 0;
  int moves = 0;
  int clears = 0;
  int reveals = 0;
  int maximum_chain = 0;
  int changed = 0;
  int two_admitted = 0;
  bool censored = false;
  std::uint64_t work = 0;
};

GameResult playGame(const Model& model, std::uint32_t seed, Policy policy,
                    SeedUse use, const Deadline& deadline) {
  requireSeed(seed, use);
  State state = initialHeadlessState(seed);
  GameResult result;
  result.seed = seed;
  while (!state.game_over && state.moves_played < kMaximumMoves) {
    if ((state.moves_played & 15) == 0) deadline.check();
    const PublicState public_state = vr::publicState(state);
    int action = -1;
    if (policy == Policy::kRootPrior) {
      const Decision decision = chooseAction(public_state, model);
      action = decision.action;
      result.changed += decision.changed;
      result.two_admitted += decision.admitted == 2;
      result.work += decision.work;
    } else {
      const State stripped = vr::materialize(public_state);
      const fair::SearchDecision decision = fair::chooseFairAction(stripped);
      if (!decision.complete || decision.completed_depth != kSearchDepth) {
        throw std::runtime_error("baseline did not complete exact fair D3");
      }
      action = decision.action;
      result.work += decision.work;
    }
    if (!isLegal(state.board, action)) {
      throw std::runtime_error("root-prior evaluation chose illegal action");
    }
    MoveResult move;
    if (!playHeadlessMove(state, seed, action, move)) {
      throw std::runtime_error("root-prior evaluation transition failed");
    }
    for (const Wave& wave : move.waves) {
      result.clears += wave.cleared;
      result.reveals += wave.revealed;
      result.maximum_chain = std::max(result.maximum_chain, wave.depth);
    }
  }
  result.score = state.score;
  result.moves = state.moves_played;
  result.censored = !state.game_over;
  return result;
}

struct Cohort {
  std::vector<GameResult> candidate;
  std::vector<GameResult> baseline;
  double wall_seconds = 0.0;
};

Cohort runCohort(const Model& model, std::uint32_t seed_start, int games,
                 SeedUse use, int threads, const Deadline& deadline) {
  const Clock::time_point started = Clock::now();
  Cohort result;
  result.candidate.resize(games);
  result.baseline.resize(games);
  std::atomic<int> next{0};
  std::vector<std::future<void>> workers;
  for (int worker = 0; worker < std::min(threads, games); ++worker) {
    workers.push_back(std::async(std::launch::async, [&] {
      for (;;) {
        const int game = next.fetch_add(1);
        if (game >= games) return;
        const std::uint32_t seed =
            seed_start + static_cast<std::uint32_t>(game);
        result.candidate[game] =
            playGame(model, seed, Policy::kRootPrior, use, deadline);
        result.baseline[game] =
            playGame(model, seed, Policy::kFairD3, use, deadline);
        std::cerr << "root-prior seed 0x" << std::hex << seed << std::dec
                  << " candidate " << result.candidate[game].score << '/'
                  << result.candidate[game].moves << " D3 "
                  << result.baseline[game].score << '/'
                  << result.baseline[game].moves << '\n';
      }
    }));
  }
  for (auto& worker : workers) worker.get();
  result.wall_seconds =
      std::chrono::duration<double>(Clock::now() - started).count();
  enforceRss();
  return result;
}

struct Summary {
  double mean_score = 0.0;
  double mean_moves = 0.0;
  double clears_per_move = 0.0;
  double reveals_per_move = 0.0;
  double changed_per_move = 0.0;
  double two_admitted_per_move = 0.0;
  double work_per_move = 0.0;
  int maximum_chain = 0;
  int censored = 0;
};

Summary summarize(const std::vector<GameResult>& games) {
  if (games.empty()) throw std::invalid_argument("cannot summarize no games");
  Summary result;
  std::int64_t score = 0;
  std::int64_t moves = 0;
  std::int64_t clears = 0;
  std::int64_t reveals = 0;
  std::int64_t changed = 0;
  std::int64_t admitted = 0;
  std::uint64_t work = 0;
  for (const GameResult& game : games) {
    score += game.score;
    moves += game.moves;
    clears += game.clears;
    reveals += game.reveals;
    changed += game.changed;
    admitted += game.two_admitted;
    work += game.work;
    result.maximum_chain = std::max(result.maximum_chain, game.maximum_chain);
    result.censored += game.censored;
  }
  result.mean_score = static_cast<double>(score) / games.size();
  result.mean_moves = static_cast<double>(moves) / games.size();
  result.clears_per_move = static_cast<double>(clears) / moves;
  result.reveals_per_move = static_cast<double>(reveals) / moves;
  result.changed_per_move = static_cast<double>(changed) / moves;
  result.two_admitted_per_move = static_cast<double>(admitted) / moves;
  result.work_per_move = static_cast<double>(work) / moves;
  return result;
}

struct Paired {
  int score_wins = 0;
  int move_wins = 0;
  int joint_wins = 0;
  double score_delta = 0.0;
  double move_delta = 0.0;
};

Paired pair(const Cohort& cohort) {
  if (cohort.candidate.size() != cohort.baseline.size() ||
      cohort.candidate.empty()) {
    throw std::invalid_argument("invalid paired cohort");
  }
  Paired result;
  for (std::size_t index = 0; index < cohort.candidate.size(); ++index) {
    const GameResult& candidate = cohort.candidate[index];
    const GameResult& baseline = cohort.baseline[index];
    if (candidate.seed != baseline.seed) {
      throw std::runtime_error("paired seed mismatch");
    }
    const bool score_win = candidate.score > baseline.score;
    const bool move_win = candidate.moves > baseline.moves;
    result.score_wins += score_win;
    result.move_wins += move_win;
    result.joint_wins += score_win && move_win;
    result.score_delta += candidate.score - baseline.score;
    result.move_delta += candidate.moves - baseline.moves;
  }
  result.score_delta /= cohort.candidate.size();
  result.move_delta /= cohort.candidate.size();
  return result;
}

bool passesFit(const Summary& candidate, const Summary& baseline,
               const Paired& paired) {
  return candidate.mean_score >= kFitScoreRatio * baseline.mean_score &&
         candidate.mean_moves >= kFitMoveRatio * baseline.mean_moves &&
         candidate.clears_per_move >=
             baseline.clears_per_move + kFitFlowGain &&
         candidate.reveals_per_move >=
             baseline.reveals_per_move + kFitFlowGain &&
         paired.joint_wins >= kFitJointWins;
}

bool passesScreen(const Summary& candidate, const Summary& baseline,
                  const Paired& paired) {
  return candidate.mean_score >= kScreenScoreRatio * baseline.mean_score &&
         candidate.mean_moves >= kScreenMoveRatio * baseline.mean_moves &&
         candidate.clears_per_move >= baseline.clears_per_move &&
         candidate.reveals_per_move >= baseline.reveals_per_move &&
         paired.joint_wins >= kScreenJointWins;
}

struct Options {
  std::string curriculum = "/tmp/drop7-oracle-curriculum-states.jsonl";
  std::string checkpoint = "/tmp/drop7-manifold-root-prior.bin";
  std::string output = "/tmp/drop7-manifold-root-prior.json";
  int threads = 4;
};

Options parseOptions(int argc, char** argv, int begin) {
  Options result;
  for (int index = begin; index < argc; ++index) {
    const std::string_view argument(argv[index]);
    if (argument == "--curriculum" && index + 1 < argc) {
      result.curriculum = argv[++index];
    } else if (argument == "--checkpoint" && index + 1 < argc) {
      result.checkpoint = argv[++index];
    } else if (argument == "--output" && index + 1 < argc) {
      result.output = argv[++index];
    } else if (argument == "--threads" && index + 1 < argc) {
      result.threads = std::stoi(argv[++index]);
    } else {
      throw std::invalid_argument("unknown or incomplete option");
    }
  }
  if (result.curriculum.empty() || result.checkpoint.empty() ||
      result.output.empty() || result.threads < 1 ||
      result.threads > kMaximumThreads) {
    throw std::invalid_argument("invalid root-prior options");
  }
  return result;
}

void writeSummary(std::ostream& output, const Summary& summary) {
  output << "{\"meanScore\":" << summary.mean_score
         << ",\"meanMoves\":" << summary.mean_moves
         << ",\"clearsPerMove\":" << summary.clears_per_move
         << ",\"revealsPerMove\":" << summary.reveals_per_move
         << ",\"changedPerMove\":" << summary.changed_per_move
         << ",\"twoAdmittedPerMove\":" << summary.two_admitted_per_move
         << ",\"workPerMove\":" << summary.work_per_move
         << ",\"maximumChain\":" << summary.maximum_chain
         << ",\"censored\":" << summary.censored << '}';
}

void writePaired(std::ostream& output, const Paired& paired) {
  output << "{\"scoreWins\":" << paired.score_wins
         << ",\"moveWins\":" << paired.move_wins
         << ",\"jointWins\":" << paired.joint_wins
         << ",\"meanScoreDelta\":" << paired.score_delta
         << ",\"meanMoveDelta\":" << paired.move_delta << '}';
}

void writeCohort(std::ostream& output, const Cohort& cohort,
                 const Summary& candidate, const Summary& baseline,
                 const Paired& paired, bool passed) {
  output << "{\"candidate\":";
  writeSummary(output, candidate);
  output << ",\"fairD3\":";
  writeSummary(output, baseline);
  output << ",\"paired\":";
  writePaired(output, paired);
  output << ",\"passed\":" << (passed ? "true" : "false")
         << ",\"wallSeconds\":" << cohort.wall_seconds << '}';
}

void writeArtifact(const Options& options,
                   const manifold::MatchedDataset& dataset,
                   const LabelMetrics& labels, const Model& model,
                   const Cohort& fit, const Summary& fit_candidate,
                   const Summary& fit_baseline, const Paired& fit_paired,
                   bool fit_passed, const std::optional<Cohort>& screen,
                   const std::optional<Summary>& screen_candidate,
                   const std::optional<Summary>& screen_baseline,
                   const std::optional<Paired>& screen_paired,
                   bool screen_passed, double wall_seconds) {
  std::ofstream output(options.output);
  if (!output) throw std::runtime_error("could not open root-prior artifact");
  output << std::setprecision(12)
         << "{\n  \"format\":\"drop7-manifold-root-prior-v1\","
         << "\n  \"hypothesis\":\"fixed public manifold scalar as close-D3 root tie-break only\","
         << "\n  \"model\":{\"input\":295,\"hidden\":24,\"output\":1,\"parameters\":7129,"
         << "\"reflection\":\"exact canonical public state plus invariant aggregates\","
         << "\"epochs\":" << kEpochs << ",\"pairs\":"
         << dataset.matched << ",\"datasetFingerprint\":\"0x" << std::hex
         << dataset.fingerprint << "\",\"modelFingerprint\":\"0x"
         << modelFingerprint(model) << std::dec << "\",\"trainingAuc\":"
         << labels.auc << ",\"trainingPairRanking\":"
         << labels.pair_ranking << ",\"trainingLoss\":" << labels.loss
         << "},"
         << "\n  \"rootPolicy\":{\"search\":\"exact fair D3\",\"qWindow\":"
         << kRootQWindow << ",\"maximumAdmittedActions\":"
         << kMaximumAdmittedActions << ",\"successors\":"
         << kSuccessorScenarios
         << ",\"successorSource\":\"public-state-derived stratified immediate outcomes\",\"ties\":\"fair D3\"},"
         << "\n  \"seedLanes\":{\"discriminatorNegatives\":\"replay of already-opened 0x3d6b0000..0x3d6b03ff\",\"fit\":\"0x3d6f0000..0x3d6f000f\",\"screen\":"
         << (screen ? "\"0x3d6f1000..0x3d6f101f\"" : "\"unopened\"")
         << "},"
         << "\n  \"fitGate\":{\"scoreRatio\":" << kFitScoreRatio
         << ",\"moveRatio\":" << kFitMoveRatio
         << ",\"clearGainPerMove\":" << kFitFlowGain
         << ",\"revealGainPerMove\":" << kFitFlowGain
         << ",\"jointWins\":" << kFitJointWins << "},"
         << "\n  \"fit\":";
  writeCohort(output, fit, fit_candidate, fit_baseline, fit_paired,
              fit_passed);
  output << ",\n  \"screenGate\":{\"scoreRatio\":" << kScreenScoreRatio
         << ",\"moveRatio\":" << kScreenMoveRatio
         << ",\"flowNonregression\":true,\"jointWins\":"
         << kScreenJointWins << "},\n  \"screen\":";
  if (screen) {
    writeCohort(output, *screen, *screen_candidate, *screen_baseline,
                *screen_paired, screen_passed);
  } else {
    output << "null";
  }
  output << ",\n  \"checkpoint\":\"" << options.checkpoint
         << "\",\n  \"passed\":"
         << (fit_passed && screen_passed ? "true" : "false")
         << ",\n  \"wallSeconds\":" << wall_seconds
         << ",\n  \"peakRssBytes\":" << peakRssBytes() << "\n}\n";
  if (!output) throw std::runtime_error("failed writing root-prior artifact");
}

void expect(bool condition, std::string_view message) {
  if (!condition) throw std::runtime_error(std::string(message));
}

template <typename Function>
bool throwsInvalid(Function&& function) {
  try {
    function();
  } catch (const std::invalid_argument&) {
    return true;
  }
  return false;
}

bool selfTest(const Options& options, std::ostream& output) {
  const Deadline deadline;
  const prior::Curriculum curriculum = prior::loadCurriculum(options.curriculum);
  expect(curriculum.fingerprint == kExpectedCurriculumFingerprint,
         "curriculum checksum self-test failed");
  const manifold::MatchedDataset dataset =
      manifold::buildMatchedDataset(curriculum, deadline);
  expect(dataset.matched == kExpectedMatchedPairs &&
             dataset.fingerprint == kExpectedMatchedFingerprint,
         "matched dataset self-test failed");
  const Model first = fitModel(dataset, deadline);
  const Model second = fitModel(dataset, deadline);
  expect(first.parameters() == second.parameters() &&
             modelFingerprint(first) == modelFingerprint(second),
         "deterministic fit self-test failed");

  const PublicState fixture = prior::fixtureState();
  expect(first.logit(fixture) == first.logit(vr::mirror(fixture)),
         "scalar reflection self-test failed");
  const Decision action = chooseAction(fixture, first);
  const Decision reflected = chooseAction(vr::mirror(fixture), first);
  expect(reflected.action == kBoardSize - 1 - action.action &&
             reflected.d3_action == kBoardSize - 1 - action.d3_action,
         "root policy reflection self-test failed");
  State metadata = vr::materialize(fixture);
  metadata.score = 8'888'888;
  metadata.level = 444;
  metadata.moves_played = 333;
  expect(vr::publicState(metadata) == fixture &&
             first.logit(vr::publicState(metadata)) == first.logit(fixture) &&
             chooseAction(vr::publicState(metadata), first) == action,
         "root policy retained hidden metadata");

  const std::string checkpoint = options.checkpoint + ".self-test";
  saveCheckpoint(checkpoint, first);
  const Model restored = loadCheckpoint(checkpoint);
  expect(restored.parameters() == first.parameters() &&
             modelFingerprint(restored) == modelFingerprint(first) &&
             restored.logit(fixture) == first.logit(fixture),
         "checkpoint round-trip self-test failed");

  Board empty{};
  empty.fill(kEmpty);
  std::array<double, kBoardSize> values{};
  values.fill(-10'000.0);
  values[3] = 10'000.0;
  values[2] = 7'501.0;
  values[4] = 7'499.0;
  const std::vector<int> admitted = admittedActions(values, empty, 3);
  expect(admitted.size() == 2 && admitted[0] == 3 && admitted[1] == 2,
         "top-two/window admission self-test failed");

  Summary fit_candidate;
  fit_candidate.mean_score = 116;
  fit_candidate.mean_moves = 116;
  fit_candidate.clears_per_move = 2.051;
  fit_candidate.reveals_per_move = 1.151;
  Summary baseline;
  baseline.mean_score = 100;
  baseline.mean_moves = 100;
  baseline.clears_per_move = 2.0;
  baseline.reveals_per_move = 1.1;
  Paired fit_pair;
  fit_pair.joint_wins = 11;
  expect(passesFit(fit_candidate, baseline, fit_pair),
         "positive fit gate self-test failed");
  fit_candidate.mean_score = 114;
  expect(!passesFit(fit_candidate, baseline, fit_pair),
         "negative fit gate self-test failed");
  Summary screen_candidate = baseline;
  screen_candidate.mean_score = 111;
  screen_candidate.mean_moves = 111;
  Paired screen_pair;
  screen_pair.joint_wins = 20;
  expect(passesScreen(screen_candidate, baseline, screen_pair),
         "positive screen gate self-test failed");
  screen_candidate.reveals_per_move = 1.09;
  expect(!passesScreen(screen_candidate, baseline, screen_pair),
         "negative screen gate self-test failed");

  expect(allowedSeed(kFitSeedStart, SeedUse::kFit) &&
             allowedSeed(kFitSeedEndExclusive - 1, SeedUse::kFit) &&
             allowedSeed(kScreenSeedStart, SeedUse::kScreen) &&
             allowedSeed(kScreenSeedEndExclusive - 1, SeedUse::kScreen) &&
             throwsInvalid([] {
               requireSeed(0x3d6f'0010u, SeedUse::kFit);
             }) &&
             throwsInvalid([] {
               requireSeed(0x3d6f'1020u, SeedUse::kScreen);
             }) &&
             throwsInvalid([] {
               requireSeed(0x3d68'0000u, SeedUse::kFit);
             }) &&
             throwsInvalid([] {
               requireSeed(0x3d69'0000u, SeedUse::kScreen);
             }) &&
             throwsInvalid([] {
               requireSeed(0x4d6f'0000u, SeedUse::kFit);
             }) &&
             throwsInvalid([] {
               requireSeed(0x7d6f'0000u, SeedUse::kScreen);
             }) &&
             throwsInvalid([] {
               requireSeed(0xd76f'0000u, SeedUse::kScreen);
             }),
         "seed guards self-test failed");
  enforceRss();
  output << std::setprecision(12)
         << "MANIFOLD_ROOT_PRIOR_SELF_TEST {\"passed\":true,"
         << "\"curriculumChecksum\":true,\"matchedDataset\":"
         << dataset.matched << ",\"deterministicFit\":true,"
         << "\"reflectionExact\":true,\"metadataBlind\":true,"
         << "\"checkpointRoundTrip\":true,\"rootAdmission\":true,"
         << "\"gateWiring\":true,\"seedGuards\":true,"
         << "\"modelFingerprint\":\"0x" << std::hex
         << modelFingerprint(first) << std::dec << "\",\"peakRssBytes\":"
         << peakRssBytes() << "}\n";
  return true;
}

int run(const Options& options, std::ostream& output) {
  const Deadline deadline;
  const prior::Curriculum curriculum = prior::loadCurriculum(options.curriculum);
  if (curriculum.fingerprint != kExpectedCurriculumFingerprint) {
    throw std::runtime_error("public curriculum checksum mismatch");
  }
  const manifold::MatchedDataset dataset =
      manifold::buildMatchedDataset(curriculum, deadline);
  if (dataset.matched != kExpectedMatchedPairs ||
      dataset.fingerprint != kExpectedMatchedFingerprint) {
    throw std::runtime_error("exact matched dataset fingerprint mismatch");
  }
  const Model trained = fitModel(dataset, deadline);
  const LabelMetrics labels = labelMetrics(trained, dataset);
  saveCheckpoint(options.checkpoint, trained);
  const Model model = loadCheckpoint(options.checkpoint);
  if (modelFingerprint(model) != modelFingerprint(trained)) {
    throw std::runtime_error("frozen root-prior checkpoint mismatch");
  }
  output << std::setprecision(8)
         << "MANIFOLD_ROOT_PRIOR_MODEL {\"pairs\":" << dataset.matched
         << ",\"auc\":" << labels.auc << ",\"pairRanking\":"
         << labels.pair_ranking << ",\"loss\":" << labels.loss
         << ",\"fingerprint\":\"0x" << std::hex
         << modelFingerprint(model) << std::dec << "\"}\n";

  const Cohort fit = runCohort(model, kFitSeedStart, kFitGames, SeedUse::kFit,
                               options.threads, deadline);
  const Summary fit_candidate = summarize(fit.candidate);
  const Summary fit_baseline = summarize(fit.baseline);
  const Paired fit_paired = pair(fit);
  const bool fit_passed = passesFit(fit_candidate, fit_baseline, fit_paired);
  output << std::fixed << std::setprecision(3)
         << "MANIFOLD_ROOT_PRIOR_FIT {\"candidateScore\":"
         << fit_candidate.mean_score << ",\"candidateMoves\":"
         << fit_candidate.mean_moves << ",\"d3Score\":"
         << fit_baseline.mean_score << ",\"d3Moves\":"
         << fit_baseline.mean_moves << ",\"candidateFlow\":"
         << fit_candidate.clears_per_move << '/'
         << fit_candidate.reveals_per_move << ",\"d3Flow\":"
         << fit_baseline.clears_per_move << '/'
         << fit_baseline.reveals_per_move << ",\"jointWins\":"
         << fit_paired.joint_wins << ",\"passed\":"
         << (fit_passed ? "true" : "false") << "}\n";

  std::optional<Cohort> screen;
  std::optional<Summary> screen_candidate;
  std::optional<Summary> screen_baseline;
  std::optional<Paired> screen_paired;
  bool screen_passed = false;
  if (fit_passed) {
    screen = runCohort(model, kScreenSeedStart, kScreenGames,
                       SeedUse::kScreen, options.threads, deadline);
    screen_candidate = summarize(screen->candidate);
    screen_baseline = summarize(screen->baseline);
    screen_paired = pair(*screen);
    screen_passed = passesScreen(*screen_candidate, *screen_baseline,
                                 *screen_paired);
  }
  deadline.check();
  enforceRss();
  writeArtifact(options, dataset, labels, model, fit, fit_candidate,
                fit_baseline, fit_paired, fit_passed, screen,
                screen_candidate, screen_baseline, screen_paired,
                screen_passed, deadline.elapsedSeconds());
  output << std::fixed << std::setprecision(3)
         << "MANIFOLD_ROOT_PRIOR_RESULT {\"fitPassed\":"
         << (fit_passed ? "true" : "false") << ",\"screenOpened\":"
         << (screen ? "true" : "false") << ",\"screenPassed\":"
         << (screen_passed ? "true" : "false") << ",\"wallSeconds\":"
         << deadline.elapsedSeconds() << ",\"peakRssBytes\":"
         << peakRssBytes() << ",\"artifact\":\"" << options.output
         << "\"}\n";
  return fit_passed && screen_passed ? EXIT_SUCCESS : 2;
}

}  // namespace drop7::manifold_root_prior

int main(int argc, char** argv) {
  try {
    if (argc < 2) throw std::invalid_argument("missing mode");
    const std::string_view mode(argv[1]);
    const drop7::manifold_root_prior::Options options =
        drop7::manifold_root_prior::parseOptions(argc, argv, 2);
    if (mode == "--self-test") {
      return drop7::manifold_root_prior::selfTest(options, std::cout)
                 ? EXIT_SUCCESS
                 : EXIT_FAILURE;
    }
    if (mode == "--run") {
      return drop7::manifold_root_prior::run(options, std::cout);
    }
    throw std::invalid_argument(
        "usage: drop7_manifold_root_prior --self-test | --run [--curriculum PATH] [--checkpoint PATH] [--output PATH] [--threads 1..4]");
  } catch (const std::exception& error) {
    std::cerr << "drop7_manifold_root_prior: " << error.what() << '\n';
    return EXIT_FAILURE;
  }
}