Drop7 Research
approaches/lifetime-objective/chain-reveal-leaf/corpus-dump.cppC++114 lines · 5.0 KB
// Seed-free corpus dump for the chain-reveal-leaf corpus gate.  Template:
// approaches/lifetime-objective/entombed-discs/leafdump.cpp (same 72-byte
// STATE_DTYPE record, same 18-feature order); reads no seed.
//
//   corpus-dump <corpus.states> <leaf.f32> <setups.f32> <setups-index.u32>
//
// leaf.f32          float32[26] per record in corpus order: the 18 frozen
//                   features (fastw::kLeafNames order), the frozen leaf value,
//                   then the 7 extra terms (fastx::kExtraNames order).  An
//                   invalid public state writes 26 NaNs.
// setups.f32        float32[98] per DEPTH-4 record (behaviorDepth == 4), in
//                   corpus order: for each of the 49 cells the best
//                   support-disjoint pair product r[a]*r[b] when the cell is
//                   kSolid (else 0), then the same with rel[a]*rel[b].  These
//                   feed the uncollected-setup rate.
// setups-index.u32  uint32 corpus record index of each setups.f32 row.

#include "extra-terms.hpp"

#include <cmath>
#include <cstdio>
#include <cstring>
#include <iostream>
#include <vector>

namespace {
using namespace drop7;
constexpr std::size_t kRecordBytes = 72;
}  // namespace

int main(int argc, char** argv) {
  if (argc != 5) {
    std::cerr << "usage: corpus-dump <corpus.states> <leaf.f32> <setups.f32> <setups-index.u32>\n";
    return 2;
  }
  std::FILE* in = std::fopen(argv[1], "rb");
  std::FILE* out = std::fopen(argv[2], "wb");
  std::FILE* setups = std::fopen(argv[3], "wb");
  std::FILE* index = std::fopen(argv[4], "wb");
  if (!in || !out || !setups || !index) {
    std::cerr << "cannot open input or output\n";
    return 2;
  }
  fast::LeafScratch scratch;
  std::vector<unsigned char> record(kRecordBytes);
  std::uint64_t count = 0, invalid = 0, depth4 = 0;
  while (std::fread(record.data(), 1, kRecordBytes, in) == kRecordBytes) {
    State state;
    std::memcpy(state.board.data(), record.data(), kCellCount);
    state.next_disc = record[49];
    state.moves_remaining = record[50];
    state.game_over = false;
    const int behaviorDepth = record[59];  // STATE_DTYPE offset of behaviorDepth
    float row[26];
    const bool ok = state.moves_remaining >= 1 && state.moves_remaining <= kMovesPerLevel &&
                    state.next_disc >= 1 && state.next_disc <= kBoardSize;
    if (ok) {
      fast::FastLeafFeatures f;
      fast::extractFastLeafFeatures<6>(state, scratch, f);
      row[0] = static_cast<float>(f.open_columns);
      row[1] = static_cast<float>(f.height_load);
      row[2] = static_cast<float>(f.solid_cells);
      row[3] = static_cast<float>(f.cracked_cells);
      row[4] = static_cast<float>(f.numbered_cells);
      row[5] = static_cast<float>(f.high_low_numbers);
      row[6] = static_cast<float>(f.direct_potential);
      row[7] = static_cast<float>(f.latent_chain_potential);
      row[8] = static_cast<float>(f.cracked_exposure);
      row[9] = static_cast<float>(f.solid_exposure);
      row[10] = static_cast<float>(f.adjacent_ones);
      row[11] = static_cast<float>(f.triple_twos);
      row[12] = static_cast<float>(f.dead_low_numbers);
      row[13] = static_cast<float>(f.covered_height_risk);
      row[14] = static_cast<float>(f.low_number_height_risk);
      row[15] = static_cast<float>(f.danger_height_squared);
      row[16] = static_cast<float>(f.rise_pressure);
      row[17] = static_cast<float>(f.next_disc_vertical_options);
      row[18] = static_cast<float>(fast::fastFairLeaf(state, scratch));
      // fastFairLeaf re-extracted into the same scratch: identical board, so
      // the scratch still describes this record.
      fastx::ExtraFeatures extra;
      fastx::extractExtraFeatures(state.board, state.moves_remaining, scratch, extra);
      for (int i = 0; i < fastx::kExtraTerms; ++i) row[19 + i] = static_cast<float>(extra.v[i]);
      if (behaviorDepth == 4) {
        float cells[98];
        auto r = [&](int x) {
          const auto s = static_cast<std::size_t>(x);
          return fast::unionReadinessFast(scratch.addition[s], scratch.release[s]);
        };
        auto rel = [&](int x) { return scratch.release[static_cast<std::size_t>(x)]; };
        for (int cell = 0; cell < kCellCount; ++cell) {
          const bool solid = state.board[static_cast<std::size_t>(cell)] == kSolid;
          cells[cell] = solid ? static_cast<float>(fastx::detail::bestPair(state.board, scratch, cell, r)) : 0.0f;
          cells[49 + cell] = solid ? static_cast<float>(fastx::detail::bestPair(state.board, scratch, cell, rel)) : 0.0f;
        }
        const auto idx = static_cast<std::uint32_t>(count);
        std::fwrite(cells, sizeof(float), 98, setups);
        std::fwrite(&idx, sizeof idx, 1, index);
        ++depth4;
      }
    } else {
      for (float& v : row) v = std::nanf("");
      ++invalid;
    }
    std::fwrite(row, sizeof(float), 26, out);
    ++count;
  }
  std::fclose(in);
  std::fclose(out);
  std::fclose(setups);
  std::fclose(index);
  std::cerr << "corpus-dump: " << count << " records, " << invalid << " invalid, " << depth4 << " depth-4 setup rows\n";
  return 0;
}