Drop7 Research
approaches/constructive-reservoir/rise-option-qd/rise-option-qd.cppC++1,441 lines · 51.1 KB
#include "../../../src/core/native/engine.hpp"

#include <algorithm>
#include <array>
#include <bit>
#include <cmath>
#include <cstddef>
#include <cstdint>
#include <iostream>
#include <limits>
#include <optional>
#include <stdexcept>
#include <string>
#include <string_view>
#include <type_traits>
#include <utility>
#include <vector>

// Seed-free B0 for a rise-boundary option policy and a deterministic
// MAP-Elites archive.  This executable has no gameplay, corpus, replay, or
// training mode.  Its synthetic transitions place the
// visible disc and exercise the exact row-rise helper, but never pretend to be
// sampled Drop7 games: cascades and future discs are intentionally absent.
namespace drop7::rise_option_qd {

constexpr std::size_t kOptionCapacity = 8;
constexpr std::size_t kArchiveAxis = 4;
constexpr std::size_t kArchiveCells =
    kArchiveAxis * kArchiveAxis * kArchiveAxis;
constexpr std::uint8_t kNoOption = 0xffu;
constexpr std::uint64_t kMaximumWork = 1'000'000;
constexpr std::size_t kMaximumBytes = 1u << 20;
constexpr std::uint64_t kCheckpointMagic = 0x4452'4f51'4442'3031ull;
constexpr std::uint32_t kCheckpointVersion = 1;
constexpr std::array<int, kBoardSize> kColumnOrder{{3, 2, 4, 1, 5, 0, 6}};

static_assert(kMovesPerLevel == 5);
static_assert(kLevelBonus == 17'000);
static_assert(kArchiveCells == 64);

struct PublicState {
  Board board{};
  std::uint8_t next_disc = 1;
  std::uint8_t moves_remaining = kMovesPerLevel;
  bool terminal = false;

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

void validate(const PublicState& state) {
  if (state.next_disc < 1 || state.next_disc > kBoardSize ||
      state.moves_remaining > kMovesPerLevel ||
      (!state.terminal && state.moves_remaining == 0)) {
    throw std::invalid_argument("invalid public rise-option state");
  }
  for (const std::uint8_t cell : state.board) {
    if (cell > kCracked) {
      throw std::invalid_argument("invalid public board token");
    }
  }
}

PublicState publicState(const State& source) {
  PublicState result{source.board, source.next_disc,
                     static_cast<std::uint8_t>(source.moves_remaining),
                     source.game_over};
  validate(result);
  return result;
}

Board mirrorBoard(const Board& source) {
  Board result{};
  for (int row = 0; row < kBoardSize; ++row) {
    for (int column = 0; column < kBoardSize; ++column) {
      result[indexOf(row, kBoardSize - 1 - column)] =
          source[indexOf(row, column)];
    }
  }
  return result;
}

PublicState mirror(const PublicState& source) {
  PublicState result = source;
  result.board = mirrorBoard(source.board);
  return result;
}

enum Feature : std::size_t {
  kBias,
  kTargetErrorImprovement,
  kTargetHeadroom,
  kReservoirImprovement,
  kReservoirMass,
  kImmediateTriggers,
  kQuietBuild,
  kLateRelease,
  kCoverFrontierImprovement,
  kCrackedFrontierImprovement,
  kOpenColumns,
  kMinimumTopSlack,
  kPeakHeight,
  kRoughnessImprovement,
  kAdjacentOnes,
  kTripleTwos,
  kEdgeDistance,
  kLandingHeight,
  kFeatureCount,
};

struct Option {
  std::uint32_t id = 0;
  std::array<std::uint8_t, kBoardSize> target_heights{};
  std::array<double, kFeatureCount> weights{};

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

void validate(const Option& option) {
  for (const std::uint8_t height : option.target_heights) {
    if (height > kBoardSize) {
      throw std::invalid_argument("option target height is out of range");
    }
  }
  for (const double weight : option.weights) {
    if (!std::isfinite(weight) || std::abs(weight) > 64.0) {
      throw std::invalid_argument("option weight is invalid");
    }
  }
}

Option reflectOption(const Option& source) {
  Option result = source;
  std::reverse(result.target_heights.begin(), result.target_heights.end());
  return result;
}

struct WorkCounters {
  std::uint64_t decisions = 0;
  std::uint64_t legal_siblings_scored = 0;
  std::uint64_t feature_extractions = 0;
  std::uint64_t synthetic_steps = 0;
  std::uint64_t archive_attempts = 0;
  std::uint64_t archive_insertions = 0;
  std::uint64_t archive_replacements = 0;
  std::uint64_t mutations = 0;
  std::uint64_t peak_archive_entries = 0;

  bool operator==(const WorkCounters&) const = default;

  std::uint64_t work() const {
    return legal_siblings_scored + feature_extractions + synthetic_steps +
           archive_attempts + mutations;
  }
};

struct BoardStats {
  std::array<int, kBoardSize> heights{};
  int open_columns = 0;
  int minimum_top_slack = kBoardSize;
  int peak_height = 0;
  int roughness = 0;
  int triggers = 0;
  int cover_frontier = 0;
  int cracked_frontier = 0;
  int adjacent_ones = 0;
  int triple_twos = 0;
  double reservoir_mass = 0.0;
};

bool numberedNeighbor(const Board& board, int row, int column) {
  constexpr std::array<std::array<int, 2>, 4> directions{{
      {{-1, 0}}, {{1, 0}}, {{0, -1}}, {{0, 1}},
  }};
  for (const auto& direction : directions) {
    const int next_row = row + direction[0];
    const int next_column = column + direction[1];
    if (inside(next_row, next_column) &&
        isNumbered(board[indexOf(next_row, next_column)])) {
      return true;
    }
  }
  return false;
}

BoardStats analyzeBoard(const Board& board) {
  BoardStats result;
  for (int column = 0; column < kBoardSize; ++column) {
    for (int row = 0; row < kBoardSize; ++row) {
      result.heights[column] += board[indexOf(row, column)] != kEmpty;
    }
    const int slack = kBoardSize - result.heights[column];
    result.open_columns += slack > 0;
    result.minimum_top_slack = std::min(result.minimum_top_slack, slack);
    result.peak_height = std::max(result.peak_height, result.heights[column]);
    if (column > 0) {
      result.roughness +=
          std::abs(result.heights[column] - result.heights[column - 1]);
    }
  }

  for (int row = 0; row < kBoardSize; ++row) {
    for (int column = 0; column < kBoardSize; ++column) {
      const std::uint8_t cell = board[indexOf(row, column)];
      if (cell == kSolid && numberedNeighbor(board, row, column)) {
        ++result.cover_frontier;
      } else if (cell == kCracked && numberedNeighbor(board, row, column)) {
        ++result.cracked_frontier;
      }
      if (!isNumbered(cell)) continue;

      const int horizontal = lineLength(board, row, column, false);
      const int vertical = lineLength(board, row, column, true);
      if (horizontal == cell || vertical == cell) {
        ++result.triggers;
      } else {
        const int horizontal_gap =
            std::abs(static_cast<int>(cell) - horizontal);
        const int vertical_gap = std::abs(static_cast<int>(cell) - vertical);
        result.reservoir_mass +=
            1.0 / (1.0 + static_cast<double>(std::min(horizontal_gap,
                                                      vertical_gap)));
      }

      if (cell == 1) {
        if (column + 1 < kBoardSize &&
            board[indexOf(row, column + 1)] == 1) {
          ++result.adjacent_ones;
        }
        if (row + 1 < kBoardSize && board[indexOf(row + 1, column)] == 1) {
          ++result.adjacent_ones;
        }
      }
      if (cell == 2) {
        if (column + 2 < kBoardSize &&
            board[indexOf(row, column + 1)] == 2 &&
            board[indexOf(row, column + 2)] == 2) {
          ++result.triple_twos;
        }
        if (row + 2 < kBoardSize &&
            board[indexOf(row + 1, column)] == 2 &&
            board[indexOf(row + 2, column)] == 2) {
          ++result.triple_twos;
        }
      }
    }
  }
  return result;
}

double targetError(const std::array<int, kBoardSize>& heights,
                   const Option& option) {
  double result = 0.0;
  for (int column = 0; column < kBoardSize; ++column) {
    result += std::abs(heights[column] -
                       static_cast<int>(option.target_heights[column]));
  }
  return result;
}

std::array<double, kFeatureCount> actionFeatures(
    const PublicState& state, const Option& option, int column,
    const BoardStats& before) {
  Board after_board = state.board;
  if (!placeDisc(after_board, column, state.next_disc)) {
    throw std::invalid_argument("features requested for illegal action");
  }
  const BoardStats after = analyzeBoard(after_board);
  const double target_before = targetError(before.heights, option);
  const double target_after = targetError(after.heights, option);
  const double early = static_cast<double>(state.moves_remaining) /
                       static_cast<double>(kMovesPerLevel);
  const double late = 1.0 - early + 1.0 / kMovesPerLevel;
  const double quiet = after.triggers == 0 ? 1.0 : 0.0;

  std::array<double, kFeatureCount> features{};
  features[kBias] = 1.0;
  features[kTargetErrorImprovement] =
      (target_before - target_after) / kBoardSize;
  features[kTargetHeadroom] =
      (static_cast<int>(option.target_heights[column]) -
       before.heights[column]) /
      static_cast<double>(kBoardSize);
  features[kReservoirImprovement] =
      (after.reservoir_mass - before.reservoir_mass) / kCellCount;
  features[kReservoirMass] = after.reservoir_mass / kCellCount;
  features[kImmediateTriggers] =
      static_cast<double>(after.triggers) / kBoardSize;
  features[kQuietBuild] = quiet * early;
  features[kLateRelease] = after.triggers * late / kBoardSize;
  features[kCoverFrontierImprovement] =
      static_cast<double>(after.cover_frontier - before.cover_frontier) /
      kCellCount;
  features[kCrackedFrontierImprovement] =
      static_cast<double>(after.cracked_frontier - before.cracked_frontier) /
      kCellCount;
  features[kOpenColumns] =
      static_cast<double>(after.open_columns) / kBoardSize;
  features[kMinimumTopSlack] =
      static_cast<double>(after.minimum_top_slack) / kBoardSize;
  features[kPeakHeight] = static_cast<double>(after.peak_height) / kBoardSize;
  features[kRoughnessImprovement] =
      static_cast<double>(before.roughness - after.roughness) /
      (kBoardSize * kBoardSize);
  features[kAdjacentOnes] =
      static_cast<double>(after.adjacent_ones) / kCellCount;
  features[kTripleTwos] =
      static_cast<double>(after.triple_twos) / kCellCount;
  features[kEdgeDistance] = std::abs(column - kBoardSize / 2) /
                            static_cast<double>(kBoardSize / 2);
  features[kLandingHeight] =
      static_cast<double>(after.heights[column]) / kBoardSize;
  return features;
}

double dot(const std::array<double, kFeatureCount>& features,
           const Option& option) {
  double result = 0.0;
  for (std::size_t index = 0; index < kFeatureCount; ++index) {
    result += features[index] * option.weights[index];
  }
  return result;
}

struct CanonicalInput {
  PublicState state{};
  Option option{};
  bool mirrored = false;
};

bool reflectedJointRepresentationIsSmaller(const PublicState& state,
                                            const Option& option) {
  const Board reflected_board = mirrorBoard(state.board);
  if (std::lexicographical_compare(reflected_board.begin(),
                                   reflected_board.end(), state.board.begin(),
                                   state.board.end())) {
    return true;
  }
  if (std::lexicographical_compare(state.board.begin(), state.board.end(),
                                   reflected_board.begin(),
                                   reflected_board.end())) {
    return false;
  }
  const auto reflected_target = reflectOption(option).target_heights;
  return std::lexicographical_compare(
      reflected_target.begin(), reflected_target.end(),
      option.target_heights.begin(), option.target_heights.end());
}

CanonicalInput canonicalize(const PublicState& state, const Option& source,
                            bool option_reflected) {
  validate(state);
  validate(source);
  CanonicalInput result{state,
                        option_reflected ? reflectOption(source) : source,
                        false};
  result.mirrored =
      reflectedJointRepresentationIsSmaller(result.state, result.option);
  if (result.mirrored) {
    result.state = mirror(result.state);
    result.option = reflectOption(result.option);
  }
  return result;
}

struct Decision {
  int action = -1;
  int legal_count = 0;
  bool canonical_mirrored = false;
  std::array<double, kBoardSize> values{};

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

Decision chooseAction(const PublicState& source, const Option& option,
                      bool option_reflected, WorkCounters& counters) {
  Decision result;
  result.values.fill(-std::numeric_limits<double>::infinity());
  validate(source);
  validate(option);
  if (source.terminal) return result;

  const CanonicalInput canonical =
      canonicalize(source, option, option_reflected);
  const BoardStats before = analyzeBoard(canonical.state.board);
  double best = -std::numeric_limits<double>::infinity();
  int selected = -1;
  std::array<double, kBoardSize> canonical_values{};
  canonical_values.fill(-std::numeric_limits<double>::infinity());
  for (const int column : kColumnOrder) {
    if (!isLegal(canonical.state.board, column)) continue;
    const auto features =
        actionFeatures(canonical.state, canonical.option, column, before);
    const double value = dot(features, canonical.option);
    if (!std::isfinite(value)) {
      throw std::runtime_error("non-finite rise-option action value");
    }
    canonical_values[column] = value;
    ++result.legal_count;
    ++counters.legal_siblings_scored;
    ++counters.feature_extractions;
    if (selected < 0 || value > best) {
      selected = column;
      best = value;
    }
  }
  if (selected < 0) return result;

  result.canonical_mirrored = canonical.mirrored;
  if (!canonical.mirrored) {
    result.action = selected;
    result.values = canonical_values;
  } else {
    result.action = kBoardSize - 1 - selected;
    for (int column = 0; column < kBoardSize; ++column) {
      result.values[kBoardSize - 1 - column] = canonical_values[column];
    }
  }
  ++counters.decisions;
  return result;
}

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

struct OptionLibrary {
  std::array<Option, kOptionCapacity> options{};
  std::uint8_t count = 0;

  bool operator==(const OptionLibrary&) const = default;

  void add(const Option& option) {
    validate(option);
    if (count >= kOptionCapacity) {
      throw std::runtime_error("rise-option library is full");
    }
    for (std::uint8_t index = 0; index < count; ++index) {
      if (options[index].id == option.id) {
        throw std::invalid_argument("duplicate rise-option id");
      }
    }
    options[count++] = option;
  }
};

struct ControllerState {
  std::uint8_t active_slot = kNoOption;
  bool active_reflected = false;
  std::uint8_t committed_decisions_remaining = 0;
  bool selection_open = true;
  bool bootstrapped = false;

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

struct Session {
  PublicState observation{};
  ControllerState controller{};

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

Session beginSession(const PublicState& initial) {
  validate(initial);
  if (!initial.terminal && initial.moves_remaining != kMovesPerLevel) {
    throw std::invalid_argument(
        "a rise-option session must begin at a five-drop boundary");
  }
  Session result;
  result.observation = initial;
  result.controller.selection_open = !initial.terminal;
  return result;
}

void selectOption(Session& session, const OptionLibrary& library,
                  std::uint8_t slot, bool reflected) {
  if (session.observation.terminal || !session.controller.selection_open ||
      session.observation.moves_remaining != kMovesPerLevel ||
      slot >= library.count) {
    throw std::invalid_argument("rise option cannot be selected now");
  }
  session.controller.active_slot = slot;
  session.controller.active_reflected = reflected;
  session.controller.committed_decisions_remaining = kMovesPerLevel;
  session.controller.selection_open = false;
  session.controller.bootstrapped = true;
}

Decision chooseCommittedAction(Session& session, const OptionLibrary& library,
                               WorkCounters& counters) {
  const ControllerState& controller = session.controller;
  if (!controller.bootstrapped || controller.selection_open ||
      controller.active_slot >= library.count ||
      controller.committed_decisions_remaining == 0 ||
      controller.committed_decisions_remaining !=
          session.observation.moves_remaining) {
    throw std::logic_error("rise-option commitment is inconsistent");
  }
  return chooseAction(session.observation,
                      library.options[controller.active_slot],
                      controller.active_reflected, counters);
}

void observeTransition(Session& session, int action,
                       const PublicState& after) {
  validate(after);
  const PublicState before = session.observation;
  ControllerState& controller = session.controller;
  if (before.terminal || controller.selection_open ||
      controller.active_slot == kNoOption ||
      controller.committed_decisions_remaining != before.moves_remaining ||
      !isLegal(before.board, action)) {
    throw std::logic_error("invalid committed option transition");
  }

  if (after.terminal) {
    controller.committed_decisions_remaining = 0;
    controller.selection_open = false;
  } else if (before.moves_remaining > 1) {
    if (after.moves_remaining != before.moves_remaining - 1) {
      throw std::logic_error("option transition skipped a rise phase");
    }
    --controller.committed_decisions_remaining;
  } else {
    if (after.moves_remaining != kMovesPerLevel) {
      throw std::logic_error("option transition omitted the row rise");
    }
    controller.committed_decisions_remaining = 0;
    controller.selection_open = true;
  }
  session.observation = after;
}

std::uint64_t fnvByte(std::uint64_t hash, std::uint8_t byte) {
  hash ^= byte;
  return hash * 0x0000'0100'0000'01b3ull;
}

std::uint64_t optionFingerprint(const Option& option) {
  std::uint64_t hash = 0xcbf2'9ce4'8422'2325ull;
  for (int byte = 0; byte < 4; ++byte) {
    hash = fnvByte(hash, static_cast<std::uint8_t>(option.id >> (byte * 8)));
  }
  for (const std::uint8_t height : option.target_heights) {
    hash = fnvByte(hash, height);
  }
  for (const double weight : option.weights) {
    const std::uint64_t bits = std::bit_cast<std::uint64_t>(weight);
    for (int byte = 0; byte < 8; ++byte) {
      hash = fnvByte(
          hash, static_cast<std::uint8_t>(bits >> (static_cast<int>(byte) * 8)));
    }
  }
  return hash;
}

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

Option deterministicMutation(const Option& parent, std::uint64_t serial,
                             WorkCounters& counters) {
  validate(parent);
  if (serial == 0) {
    throw std::invalid_argument("mutation serial must be positive");
  }
  Option result = parent;
  std::uint64_t word = mix64(optionFingerprint(parent) ^ serial);
  constexpr std::size_t coordinates = kBoardSize + kFeatureCount;
  for (int edit = 0; edit < 3; ++edit) {
    word = mix64(word + static_cast<std::uint64_t>(edit + 1));
    const std::size_t coordinate = word % coordinates;
    const int direction = ((word >> 17) & 1u) == 0 ? -1 : 1;
    if (coordinate < kBoardSize) {
      int target = result.target_heights[coordinate] + direction;
      if (target < 0 || target > kBoardSize) {
        target = result.target_heights[coordinate] - direction;
      }
      result.target_heights[coordinate] =
          static_cast<std::uint8_t>(target);
    } else {
      const std::size_t feature = coordinate - kBoardSize;
      const double magnitude =
          0.125 * (1.0 + static_cast<double>((word >> 21) % 8u));
      result.weights[feature] = std::clamp(
          result.weights[feature] + direction * magnitude, -64.0, 64.0);
    }
  }
  result.id = static_cast<std::uint32_t>(
      mix64(optionFingerprint(result) ^ serial ^ 0x4d45'4c49'5445ull));
  ++counters.mutations;
  validate(result);
  return result;
}

struct Descriptor {
  std::uint8_t spread_bin = 0;
  std::uint8_t release_bin = 0;
  std::uint8_t edge_bin = 0;

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

std::size_t descriptorIndex(const Descriptor& descriptor) {
  if (descriptor.spread_bin >= kArchiveAxis ||
      descriptor.release_bin >= kArchiveAxis ||
      descriptor.edge_bin >= kArchiveAxis) {
    throw std::invalid_argument("MAP-Elites descriptor is out of range");
  }
  return (descriptor.spread_bin * kArchiveAxis + descriptor.release_bin) *
             kArchiveAxis +
         descriptor.edge_bin;
}

struct Elite {
  Option option{};
  Descriptor descriptor{};
  double synthetic_quality = 0.0;
  std::uint64_t fingerprint = 0;

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

struct Archive {
  std::array<std::optional<Elite>, kArchiveCells> cells{};
  std::size_t entries = 0;

  bool operator==(const Archive&) const = default;

  bool insert(Elite elite, WorkCounters& counters) {
    validate(elite.option);
    if (!std::isfinite(elite.synthetic_quality)) {
      throw std::invalid_argument("non-finite synthetic archive quality");
    }
    elite.fingerprint = optionFingerprint(elite.option);
    const std::size_t index = descriptorIndex(elite.descriptor);
    ++counters.archive_attempts;
    auto& incumbent = cells[index];
    if (!incumbent.has_value()) {
      incumbent = elite;
      ++entries;
      ++counters.archive_insertions;
      counters.peak_archive_entries =
          std::max(counters.peak_archive_entries,
                   static_cast<std::uint64_t>(entries));
      return true;
    }
    const bool replace =
        elite.synthetic_quality > incumbent->synthetic_quality ||
        (elite.synthetic_quality == incumbent->synthetic_quality &&
         elite.fingerprint < incumbent->fingerprint);
    if (replace) {
      incumbent = elite;
      ++counters.archive_replacements;
    }
    return replace;
  }
};

struct SyntheticTrace {
  std::array<int, kMovesPerLevel> actions{};
  int action_count = 0;
  int distinct_columns = 0;
  int edge_actions = 0;
  int trigger_placements = 0;
  int peak_height = 0;
  PublicState final_state{};

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

PublicState syntheticStructuralStep(const PublicState& source, int action,
                                    bool& triggered) {
  validate(source);
  if (source.terminal || !isLegal(source.board, action)) {
    throw std::invalid_argument("illegal synthetic structural step");
  }
  PublicState result = source;
  if (!placeDisc(result.board, action, result.next_disc)) {
    throw std::logic_error("legal synthetic placement failed");
  }
  int popper_count = 0;
  (void)findPoppers(result.board, popper_count);
  triggered = popper_count > 0;

  if (source.moves_remaining > 1) {
    result.moves_remaining = source.moves_remaining - 1;
  } else {
    Board raised{};
    if (!raiseCoveredRow(result.board, raised)) {
      result.terminal = true;
      result.moves_remaining = 0;
    } else {
      result.board = raised;
      result.moves_remaining = kMovesPerLevel;
    }
  }
  result.next_disc = 7;  // Literal fixture observation, not a sampled disc.
  int legal_count = 0;
  (void)legalColumns(result.board, legal_count);
  if (legal_count == 0) result.terminal = true;
  return result;
}

SyntheticTrace syntheticTrajectory(const PublicState& initial,
                                    const OptionLibrary& library,
                                    std::uint8_t slot, bool reflected,
                                    WorkCounters& counters) {
  Session session = beginSession(initial);
  selectOption(session, library, slot, reflected);
  SyntheticTrace trace;
  std::array<bool, kBoardSize> used{};
  for (int step = 0; step < kMovesPerLevel; ++step) {
    const Decision decision = chooseCommittedAction(session, library, counters);
    if (decision.action < 0 ||
        !isLegal(session.observation.board, decision.action)) {
      throw std::runtime_error("synthetic option selected an illegal action");
    }
    trace.actions[trace.action_count++] = decision.action;
    used[decision.action] = true;
    trace.edge_actions += decision.action == 0 || decision.action == 6;
    bool triggered = false;
    const PublicState after = syntheticStructuralStep(
        session.observation, decision.action, triggered);
    trace.trigger_placements += triggered;
    trace.peak_height =
        std::max(trace.peak_height, analyzeBoard(after.board).peak_height);
    observeTransition(session, decision.action, after);
    ++counters.synthetic_steps;
  }
  trace.distinct_columns =
      static_cast<int>(std::count(used.begin(), used.end(), true));
  trace.final_state = session.observation;
  if (!trace.final_state.terminal && !session.controller.selection_open) {
    throw std::logic_error("synthetic cycle did not reopen option selection");
  }
  return trace;
}

Elite syntheticElite(const Option& option, const SyntheticTrace& trace) {
  Elite result;
  result.option = option;
  result.descriptor.spread_bin = static_cast<std::uint8_t>(
      std::min<int>(kArchiveAxis - 1, trace.distinct_columns));
  result.descriptor.release_bin = static_cast<std::uint8_t>(
      std::min<int>(kArchiveAxis - 1, trace.trigger_placements));
  result.descriptor.edge_bin = static_cast<std::uint8_t>(
      std::min<int>(kArchiveAxis - 1, trace.edge_actions));
  result.synthetic_quality = 10.0 * trace.distinct_columns -
                             2.0 * trace.trigger_placements -
                             static_cast<double>(trace.peak_height);
  result.fingerprint = optionFingerprint(option);
  return result;
}

Option makeBuilderOption() {
  Option result;
  result.id = 0x4255'494cu;
  result.target_heights = {{1, 2, 4, 7, 4, 2, 1}};
  result.weights[kTargetErrorImprovement] = 4.0;
  result.weights[kTargetHeadroom] = 8.0;
  result.weights[kReservoirImprovement] = 5.0;
  result.weights[kReservoirMass] = 1.0;
  result.weights[kImmediateTriggers] = -5.0;
  result.weights[kQuietBuild] = 7.0;
  result.weights[kLateRelease] = 2.0;
  result.weights[kCoverFrontierImprovement] = 3.0;
  result.weights[kCrackedFrontierImprovement] = 2.0;
  result.weights[kOpenColumns] = 2.0;
  result.weights[kMinimumTopSlack] = 4.0;
  result.weights[kPeakHeight] = -3.0;
  result.weights[kRoughnessImprovement] = 2.0;
  result.weights[kAdjacentOnes] = -10.0;
  result.weights[kTripleTwos] = -12.0;
  result.weights[kEdgeDistance] = -1.0;
  return result;
}

Option makeEdgeReservoirOption() {
  Option result;
  result.id = 0x4544'4745u;
  result.target_heights = {{7, 6, 4, 3, 2, 1, 1}};
  result.weights[kTargetErrorImprovement] = 3.0;
  result.weights[kTargetHeadroom] = 10.0;
  result.weights[kReservoirImprovement] = 4.0;
  result.weights[kImmediateTriggers] = -3.0;
  result.weights[kQuietBuild] = 5.0;
  result.weights[kLateRelease] = 4.0;
  result.weights[kOpenColumns] = 1.0;
  result.weights[kMinimumTopSlack] = 2.0;
  result.weights[kPeakHeight] = -1.0;
  result.weights[kAdjacentOnes] = -10.0;
  result.weights[kTripleTwos] = -12.0;
  result.weights[kEdgeDistance] = 3.0;
  return result;
}

OptionLibrary defaultLibrary() {
  OptionLibrary result;
  result.add(makeBuilderOption());
  result.add(makeEdgeReservoirOption());
  return result;
}

struct Prototype {
  OptionLibrary library{};
  Session session{};
  Archive archive{};
  WorkCounters counters{};

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

std::size_t estimatedMemoryBytes(const Prototype&) {
  return sizeof(Prototype);
}

void validate(const Prototype& prototype) {
  if (prototype.library.count == 0 ||
      prototype.library.count > kOptionCapacity) {
    throw std::invalid_argument("invalid rise-option library size");
  }
  for (std::uint8_t index = 0; index < prototype.library.count; ++index) {
    validate(prototype.library.options[index]);
  }
  validate(prototype.session.observation);
  const ControllerState& controller = prototype.session.controller;
  if (controller.committed_decisions_remaining > kMovesPerLevel ||
      (!controller.bootstrapped &&
       (controller.active_slot != kNoOption ||
        controller.committed_decisions_remaining != 0)) ||
      (controller.selection_open &&
       (controller.committed_decisions_remaining != 0 ||
        prototype.session.observation.moves_remaining != kMovesPerLevel)) ||
      (prototype.session.observation.terminal &&
       controller.selection_open)) {
    throw std::invalid_argument("checkpoint has invalid controller state");
  }
  if (controller.bootstrapped && controller.active_slot >=
                                     prototype.library.count) {
    throw std::invalid_argument("checkpoint has invalid active option");
  }
  if (!controller.selection_open && !prototype.session.observation.terminal &&
      controller.committed_decisions_remaining !=
          prototype.session.observation.moves_remaining) {
    throw std::invalid_argument("checkpoint commitment phase mismatch");
  }
  std::size_t entries = 0;
  for (std::size_t index = 0; index < kArchiveCells; ++index) {
    if (!prototype.archive.cells[index].has_value()) continue;
    ++entries;
    const Elite& elite = *prototype.archive.cells[index];
    validate(elite.option);
    if (descriptorIndex(elite.descriptor) != index ||
        !std::isfinite(elite.synthetic_quality) ||
        elite.fingerprint != optionFingerprint(elite.option)) {
      throw std::invalid_argument("checkpoint has invalid archive elite");
    }
  }
  if (entries != prototype.archive.entries ||
      prototype.counters.peak_archive_entries < entries ||
      prototype.counters.work() > kMaximumWork ||
      estimatedMemoryBytes(prototype) > kMaximumBytes) {
    throw std::invalid_argument("rise-option resource accounting failed");
  }
}

void putU8(std::vector<std::uint8_t>& output, std::uint8_t value) {
  output.push_back(value);
}

void putU32(std::vector<std::uint8_t>& output, std::uint32_t value) {
  for (int byte = 0; byte < 4; ++byte) {
    putU8(output, static_cast<std::uint8_t>(value >> (byte * 8)));
  }
}

void putU64(std::vector<std::uint8_t>& output, std::uint64_t value) {
  for (int byte = 0; byte < 8; ++byte) {
    putU8(output, static_cast<std::uint8_t>(value >> (byte * 8)));
  }
}

void putDouble(std::vector<std::uint8_t>& output, double value) {
  putU64(output, std::bit_cast<std::uint64_t>(value));
}

class Reader {
 public:
  explicit Reader(std::string_view bytes)
      : data_(reinterpret_cast<const std::uint8_t*>(bytes.data())),
        size_(bytes.size()) {}

  std::uint8_t u8() {
    require(1);
    return data_[position_++];
  }

  std::uint32_t u32() {
    std::uint32_t value = 0;
    for (int byte = 0; byte < 4; ++byte) {
      value |= static_cast<std::uint32_t>(u8()) << (byte * 8);
    }
    return value;
  }

  std::uint64_t u64() {
    std::uint64_t value = 0;
    for (int byte = 0; byte < 8; ++byte) {
      value |= static_cast<std::uint64_t>(u8()) << (byte * 8);
    }
    return value;
  }

  double number() { return std::bit_cast<double>(u64()); }

  std::size_t remaining() const { return size_ - position_; }

  std::string_view take(std::size_t count) {
    require(count);
    const char* begin = reinterpret_cast<const char*>(data_ + position_);
    position_ += count;
    return {begin, count};
  }

 private:
  void require(std::size_t count) const {
    if (count > size_ - position_) {
      throw std::runtime_error("truncated rise-option checkpoint");
    }
  }

  const std::uint8_t* data_ = nullptr;
  std::size_t size_ = 0;
  std::size_t position_ = 0;
};

bool readBool(Reader& input) {
  const std::uint8_t value = input.u8();
  if (value > 1) {
    throw std::runtime_error("invalid checkpoint boolean");
  }
  return value != 0;
}

std::uint64_t checksum(std::string_view bytes) {
  std::uint64_t hash = 0xcbf2'9ce4'8422'2325ull;
  for (const unsigned char byte : bytes) hash = fnvByte(hash, byte);
  return hash;
}

void writeOption(std::vector<std::uint8_t>& output, const Option& option) {
  putU32(output, option.id);
  for (const std::uint8_t height : option.target_heights) {
    putU8(output, height);
  }
  for (const double weight : option.weights) putDouble(output, weight);
}

Option readOption(Reader& input) {
  Option result;
  result.id = input.u32();
  for (std::uint8_t& height : result.target_heights) height = input.u8();
  for (double& weight : result.weights) weight = input.number();
  validate(result);
  return result;
}

void writePublicState(std::vector<std::uint8_t>& output,
                      const PublicState& state) {
  for (const std::uint8_t cell : state.board) putU8(output, cell);
  putU8(output, state.next_disc);
  putU8(output, state.moves_remaining);
  putU8(output, state.terminal ? 1 : 0);
}

PublicState readPublicState(Reader& input) {
  PublicState result;
  for (std::uint8_t& cell : result.board) cell = input.u8();
  result.next_disc = input.u8();
  result.moves_remaining = input.u8();
  result.terminal = readBool(input);
  validate(result);
  return result;
}

void writeCounters(std::vector<std::uint8_t>& output,
                   const WorkCounters& counters) {
  putU64(output, counters.decisions);
  putU64(output, counters.legal_siblings_scored);
  putU64(output, counters.feature_extractions);
  putU64(output, counters.synthetic_steps);
  putU64(output, counters.archive_attempts);
  putU64(output, counters.archive_insertions);
  putU64(output, counters.archive_replacements);
  putU64(output, counters.mutations);
  putU64(output, counters.peak_archive_entries);
}

WorkCounters readCounters(Reader& input) {
  WorkCounters result;
  result.decisions = input.u64();
  result.legal_siblings_scored = input.u64();
  result.feature_extractions = input.u64();
  result.synthetic_steps = input.u64();
  result.archive_attempts = input.u64();
  result.archive_insertions = input.u64();
  result.archive_replacements = input.u64();
  result.mutations = input.u64();
  result.peak_archive_entries = input.u64();
  return result;
}

std::string writeCheckpoint(const Prototype& prototype) {
  validate(prototype);
  std::vector<std::uint8_t> payload;
  putU8(payload, prototype.library.count);
  for (std::uint8_t index = 0; index < prototype.library.count; ++index) {
    writeOption(payload, prototype.library.options[index]);
  }
  writePublicState(payload, prototype.session.observation);
  const ControllerState& controller = prototype.session.controller;
  putU8(payload, controller.active_slot);
  putU8(payload, controller.active_reflected ? 1 : 0);
  putU8(payload, controller.committed_decisions_remaining);
  putU8(payload, controller.selection_open ? 1 : 0);
  putU8(payload, controller.bootstrapped ? 1 : 0);
  writeCounters(payload, prototype.counters);
  putU8(payload, static_cast<std::uint8_t>(prototype.archive.entries));
  for (std::size_t index = 0; index < kArchiveCells; ++index) {
    if (!prototype.archive.cells[index].has_value()) continue;
    const Elite& elite = *prototype.archive.cells[index];
    putU8(payload, static_cast<std::uint8_t>(index));
    putU8(payload, elite.descriptor.spread_bin);
    putU8(payload, elite.descriptor.release_bin);
    putU8(payload, elite.descriptor.edge_bin);
    putDouble(payload, elite.synthetic_quality);
    putU64(payload, elite.fingerprint);
    writeOption(payload, elite.option);
  }

  const std::string_view payload_view(
      reinterpret_cast<const char*>(payload.data()), payload.size());
  std::vector<std::uint8_t> output;
  putU64(output, kCheckpointMagic);
  putU32(output, kCheckpointVersion);
  putU32(output, static_cast<std::uint32_t>(payload.size()));
  putU64(output, checksum(payload_view));
  output.insert(output.end(), payload.begin(), payload.end());
  return {reinterpret_cast<const char*>(output.data()), output.size()};
}

Prototype readCheckpoint(std::string_view bytes) {
  Reader header(bytes);
  const std::uint64_t magic = header.u64();
  const std::uint32_t version = header.u32();
  const std::uint32_t payload_size = header.u32();
  const std::uint64_t expected_checksum = header.u64();
  if (magic != kCheckpointMagic || version != kCheckpointVersion ||
      header.remaining() != payload_size) {
    throw std::runtime_error("invalid rise-option checkpoint header");
  }
  const std::string_view payload = header.take(payload_size);
  if (checksum(payload) != expected_checksum) {
    throw std::runtime_error("rise-option checkpoint checksum mismatch");
  }

  Reader input(payload);
  Prototype result;
  const std::uint8_t library_count = input.u8();
  if (library_count == 0 || library_count > kOptionCapacity) {
    throw std::runtime_error("invalid checkpoint library count");
  }
  for (std::uint8_t index = 0; index < library_count; ++index) {
    result.library.add(readOption(input));
  }
  result.session.observation = readPublicState(input);
  ControllerState& controller = result.session.controller;
  controller.active_slot = input.u8();
  controller.active_reflected = readBool(input);
  controller.committed_decisions_remaining = input.u8();
  controller.selection_open = readBool(input);
  controller.bootstrapped = readBool(input);
  result.counters = readCounters(input);
  const std::uint8_t entries = input.u8();
  if (entries > kArchiveCells) {
    throw std::runtime_error("invalid checkpoint archive count");
  }
  for (std::uint8_t entry = 0; entry < entries; ++entry) {
    const std::size_t index = input.u8();
    Elite elite;
    elite.descriptor.spread_bin = input.u8();
    elite.descriptor.release_bin = input.u8();
    elite.descriptor.edge_bin = input.u8();
    elite.synthetic_quality = input.number();
    elite.fingerprint = input.u64();
    elite.option = readOption(input);
    if (index >= kArchiveCells ||
        descriptorIndex(elite.descriptor) != index ||
        result.archive.cells[index].has_value()) {
      throw std::runtime_error("invalid checkpoint archive slot");
    }
    result.archive.cells[index] = elite;
    ++result.archive.entries;
  }
  if (input.remaining() != 0) {
    throw std::runtime_error("trailing rise-option checkpoint payload");
  }
  validate(result);
  return result;
}

struct AccessAudit {
  std::uint64_t gameplay_seed_attempts = 0;
  std::uint64_t corpus_attempts = 0;
};

void guardArgument(std::string_view argument, AccessAudit& audit) {
  if (argument.find("seed") != std::string_view::npos ||
      argument.find("replay") != std::string_view::npos ||
      argument.find("game") != std::string_view::npos) {
    ++audit.gameplay_seed_attempts;
    throw std::invalid_argument(
        "B0 forbids gameplay seeds and replay arguments");
  }
  if (argument.find("corpus") != std::string_view::npos ||
      argument.find("train") != std::string_view::npos) {
    ++audit.corpus_attempts;
    throw std::invalid_argument("B0 forbids corpora and production training");
  }
  if (argument != "--selftest") {
    throw std::invalid_argument("usage: drop7_rise_option_qd --selftest");
  }
}

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

template <typename Callable>
bool throws(Callable&& callable) {
  try {
    std::forward<Callable>(callable)();
  } catch (const std::exception&) {
    return true;
  }
  return false;
}

PublicState syntheticInitialState() {
  PublicState result;
  result.board = initialBoard();
  result.next_disc = 7;
  result.moves_remaining = kMovesPerLevel;
  return result;
}

bool allActionsLegalAlongTrace(const PublicState& initial,
                               const SyntheticTrace& trace) {
  PublicState state = initial;
  for (int index = 0; index < trace.action_count; ++index) {
    const int action = trace.actions[index];
    if (!isLegal(state.board, action)) return false;
    bool ignored = false;
    state = syntheticStructuralStep(state, action, ignored);
  }
  return true;
}

struct SelfTestReport {
  SyntheticTrace builder_trace{};
  SyntheticTrace edge_trace{};
  std::size_t checkpoint_bytes = 0;
  std::size_t estimated_bytes = 0;
  std::size_t archive_entries = 0;
  WorkCounters counters{};
};

SelfTestReport runSelfTest() {
  const OptionLibrary library = defaultLibrary();
  const PublicState initial = syntheticInitialState();

  State metadata;
  metadata.board = initial.board;
  metadata.next_disc = initial.next_disc;
  metadata.moves_remaining = initial.moves_remaining;
  metadata.score = 9'999'999;
  metadata.level = 99;
  metadata.moves_played = 777;
  expect(publicState(metadata) == initial,
         "public boundary leaked engine metadata");

  WorkCounters trajectory_work;
  const SyntheticTrace builder =
      syntheticTrajectory(initial, library, 0, false, trajectory_work);
  const SyntheticTrace edge =
      syntheticTrajectory(initial, library, 1, false, trajectory_work);
  expect(builder.actions != edge.actions,
         "distinct options produced the same synthetic trajectory");
  expect(allActionsLegalAlongTrace(initial, builder) &&
             allActionsLegalAlongTrace(initial, edge),
         "synthetic option trajectory was not legal");
  expect(builder.action_count == kMovesPerLevel &&
             edge.action_count == kMovesPerLevel &&
             builder.final_state.moves_remaining == kMovesPerLevel &&
             edge.final_state.moves_remaining == kMovesPerLevel,
         "synthetic trajectory did not span exactly one rise cycle");

  WorkCounters full_sibling_work;
  const Decision complete =
      chooseAction(initial, library.options[0], false, full_sibling_work);
  int legal_count = 0;
  (void)legalColumns(initial.board, legal_count);
  expect(complete.legal_count == legal_count &&
             full_sibling_work.legal_siblings_scored ==
                 static_cast<std::uint64_t>(legal_count),
         "policy did not score every legal sibling");
  for (int column = 0; column < kBoardSize; ++column) {
    expect(std::isfinite(complete.values[column]) ==
               isLegal(initial.board, column),
           "legal sibling mask was incorrect");
  }

  PublicState asymmetric = initial;
  asymmetric.board[indexOf(5, 1)] = 6;
  asymmetric.board[indexOf(4, 1)] = 5;
  WorkCounters reflection_work;
  const Decision forward =
      chooseAction(asymmetric, library.options[1], false, reflection_work);
  const Decision reflected = chooseAction(mirror(asymmetric),
                                          library.options[1], true,
                                          reflection_work);
  expect(reflected.action == kBoardSize - 1 - forward.action,
         "joint state/option action reflection failed");
  for (int column = 0; column < kBoardSize; ++column) {
    expect(reflected.values[kBoardSize - 1 - column] ==
               forward.values[column],
           "joint reflected sibling value failed");
  }
  WorkCounters symmetric_reflection_work;
  const Decision symmetric_forward =
      chooseAction(initial, library.options[1], false,
                   symmetric_reflection_work);
  const Decision symmetric_reflected =
      chooseAction(initial, library.options[1], true,
                   symmetric_reflection_work);
  expect(symmetric_reflected.action ==
             kBoardSize - 1 - symmetric_forward.action,
         "symmetric-board option orientation reflection failed");

  Session lifecycle = beginSession(initial);
  selectOption(lifecycle, library, 0, false);
  WorkCounters lifecycle_work;
  for (int step = 0; step < kMovesPerLevel; ++step) {
    expect(lifecycle.controller.active_slot == 0 &&
               lifecycle.controller.committed_decisions_remaining ==
                   kMovesPerLevel - step,
           "active option did not remain committed");
    expect(throws([&] { selectOption(lifecycle, library, 1, false); }),
           "mid-cycle option switch was accepted");
    const Decision decision =
        chooseCommittedAction(lifecycle, library, lifecycle_work);
    bool ignored = false;
    const PublicState after = syntheticStructuralStep(
        lifecycle.observation, decision.action, ignored);
    observeTransition(lifecycle, decision.action, after);
  }
  expect(lifecycle.controller.selection_open,
         "row rise did not reopen option selection");
  selectOption(lifecycle, library, 1, false);
  expect(lifecycle.controller.active_slot == 1 &&
             lifecycle.controller.committed_decisions_remaining ==
                 kMovesPerLevel,
         "post-rise option switch failed");

  WorkCounters archive_work;
  Archive archive;
  const Elite builder_elite = syntheticElite(library.options[0], builder);
  const Elite edge_elite = syntheticElite(library.options[1], edge);
  expect(archive.insert(builder_elite, archive_work),
         "first MAP-Elites insertion failed");
  Elite worse = builder_elite;
  worse.synthetic_quality -= 1.0;
  expect(!archive.insert(worse, archive_work),
         "worse archive collision replaced incumbent");
  Elite better = builder_elite;
  better.synthetic_quality += 1.0;
  expect(archive.insert(better, archive_work),
         "better archive collision did not replace incumbent");
  (void)archive.insert(edge_elite, archive_work);
  WorkCounters mutation_work_a;
  WorkCounters mutation_work_b;
  const Option mutation_a =
      deterministicMutation(library.options[0], 17, mutation_work_a);
  const Option mutation_b =
      deterministicMutation(library.options[0], 17, mutation_work_b);
  expect(mutation_a == mutation_b && mutation_a != library.options[0] &&
             mutation_work_a == mutation_work_b,
         "deterministic mutation was not reproducible");
  OptionLibrary mutated_library = library;
  mutated_library.add(mutation_a);
  WorkCounters mutation_fixture_work;
  const SyntheticTrace mutation_trace = syntheticTrajectory(
      initial, mutated_library, 2, false, mutation_fixture_work);
  const Elite mutation_elite = syntheticElite(mutation_a, mutation_trace);
  (void)archive.insert(mutation_elite, archive_work);

  Archive reordered_archive;
  WorkCounters reordered_work;
  (void)reordered_archive.insert(worse, reordered_work);
  (void)reordered_archive.insert(edge_elite, reordered_work);
  (void)reordered_archive.insert(mutation_elite, reordered_work);
  (void)reordered_archive.insert(builder_elite, reordered_work);
  (void)reordered_archive.insert(better, reordered_work);
  expect(reordered_archive == archive,
         "MAP-Elites insertion order changed the deterministic archive");

  Prototype prototype;
  prototype.library = library;
  prototype.archive = archive;
  prototype.counters = trajectory_work;
  prototype.counters.archive_attempts += archive_work.archive_attempts;
  prototype.counters.archive_insertions += archive_work.archive_insertions;
  prototype.counters.archive_replacements += archive_work.archive_replacements;
  prototype.counters.peak_archive_entries = archive_work.peak_archive_entries;
  prototype.counters.mutations += mutation_work_a.mutations;
  prototype.counters.decisions += mutation_fixture_work.decisions;
  prototype.counters.legal_siblings_scored +=
      mutation_fixture_work.legal_siblings_scored;
  prototype.counters.feature_extractions +=
      mutation_fixture_work.feature_extractions;
  prototype.counters.synthetic_steps += mutation_fixture_work.synthetic_steps;
  prototype.session = beginSession(initial);
  selectOption(prototype.session, prototype.library, 1, true);
  for (int step = 0; step < 2; ++step) {
    const Decision decision = chooseCommittedAction(
        prototype.session, prototype.library, prototype.counters);
    bool ignored = false;
    const PublicState after = syntheticStructuralStep(
        prototype.session.observation, decision.action, ignored);
    observeTransition(prototype.session, decision.action, after);
    ++prototype.counters.synthetic_steps;
  }
  expect(prototype.session.controller.active_slot == 1 &&
             prototype.session.controller.active_reflected &&
             prototype.session.controller.committed_decisions_remaining == 3,
         "checkpoint fixture lost active option before serialization");
  const std::string checkpoint = writeCheckpoint(prototype);
  const Prototype restored = readCheckpoint(checkpoint);
  expect(restored == prototype,
         "checkpoint did not round-trip the active option/session");
  Prototype uninterrupted = prototype;
  Prototype resumed = restored;
  const Decision next_uninterrupted = chooseCommittedAction(
      uninterrupted.session, uninterrupted.library, uninterrupted.counters);
  const Decision next_resumed = chooseCommittedAction(
      resumed.session, resumed.library, resumed.counters);
  expect(next_uninterrupted == next_resumed &&
             uninterrupted.counters == resumed.counters,
         "checkpoint resume changed the next option decision");
  std::string corrupt = checkpoint;
  corrupt.back() = static_cast<char>(corrupt.back() ^ 1);
  expect(throws([&] { (void)readCheckpoint(corrupt); }),
         "corrupt checkpoint was accepted");
  expect(throws([&] { (void)readCheckpoint(checkpoint + "x"); }),
         "checkpoint with trailing bytes was accepted");
  Prototype non_finite = prototype;
  non_finite.library.options[0].weights[0] =
      std::numeric_limits<double>::quiet_NaN();
  expect(throws([&] { (void)writeCheckpoint(non_finite); }),
         "non-finite option checkpoint was accepted");
  Prototype over_work = prototype;
  over_work.counters.legal_siblings_scored = kMaximumWork + 1;
  expect(throws([&] { validate(over_work); }),
         "over-budget work counter was accepted");

  AccessAudit argument_probe;
  expect(throws([&] { guardArgument("--seed", argument_probe); }) &&
             argument_probe.gameplay_seed_attempts == 1,
         "gameplay-seed argument firewall failed");
  expect(throws([&] { guardArgument("--corpus", argument_probe); }) &&
             argument_probe.corpus_attempts == 1,
         "corpus argument firewall failed");
  expect(prototype.counters.work() <= kMaximumWork &&
             estimatedMemoryBytes(prototype) <= kMaximumBytes,
         "B0 work or memory bound failed");

  return {builder,
          edge,
          checkpoint.size(),
          estimatedMemoryBytes(prototype),
          archive.entries,
          prototype.counters};
}

void writeActions(std::ostream& output,
                  const std::array<int, kMovesPerLevel>& actions) {
  output << '[';
  for (int index = 0; index < kMovesPerLevel; ++index) {
    if (index != 0) output << ',';
    output << actions[index];
  }
  output << ']';
}

}  // namespace drop7::rise_option_qd

int main(int argc, char** argv) {
  using namespace drop7::rise_option_qd;
  AccessAudit audit;
  try {
    if (argc != 2) {
      throw std::invalid_argument("usage: drop7_rise_option_qd --selftest");
    }
    guardArgument(argv[1], audit);
    const SelfTestReport report = runSelfTest();
    std::cout << "RISE_OPTION_QD_B0 {\"passed\":true,"
              << "\"syntheticOnly\":true,\"productionTraining\":false,"
              << "\"gameplaySeedAccesses\":"
              << audit.gameplay_seed_attempts << ",\"corpusAccesses\":"
              << audit.corpus_attempts << ",\"builderActions\":";
    writeActions(std::cout, report.builder_trace.actions);
    std::cout << ",\"edgeActions\":";
    writeActions(std::cout, report.edge_trace.actions);
    std::cout << ",\"archiveEntries\":" << report.archive_entries
              << ",\"checkpointBytes\":" << report.checkpoint_bytes
              << ",\"estimatedMemoryBytes\":" << report.estimated_bytes
              << ",\"work\":" << report.counters.work()
              << ",\"legalSiblingsScored\":"
              << report.counters.legal_siblings_scored << "}\n";
    return 0;
  } catch (const std::exception& error) {
    std::cerr << "rise-option QD B0 failure: " << error.what() << '\n';
    return 1;
  }
}