#include "../../../src/core/native/engine.hpp"
// The 100m development-cohort gate compares against the fixed exact-D4
// implementation on identical seeds. Library inclusion avoids
// accepting an unverifiable external table of comparator scores.
#define DROP7_FAIR_ONLY_DEPTH4_LIBRARY
#define DROP7_FAIR_ONLY_DEPTH4_NO_MAIN
#if defined(__GNUC__) && !defined(__clang__)
#pragma GCC diagnostic push
// GCC 14's optimized std::sort on fixed arrays emits a known false positive
// in the fixed exact-D4 dependency despite its constant four-element
// range. Keep -Werror for this translation unit and isolate that diagnostic
// to the dependency's lexical include only.
#pragma GCC diagnostic ignored "-Warray-bounds"
#endif
#include "../../fair-expectimax/reference/fair-only-depth4.cpp"
#if defined(__GNUC__) && !defined(__clang__)
#pragma GCC diagnostic pop
#endif
#undef DROP7_FAIR_ONLY_DEPTH4_NO_MAIN
#undef DROP7_FAIR_ONLY_DEPTH4_LIBRARY
#include <algorithm>
#include <array>
#include <bit>
#include <chrono>
#include <cmath>
#include <cstddef>
#include <cstdint>
#include <cstdlib>
#include <cstring>
#include <deque>
#include <filesystem>
#include <fstream>
#include <iomanip>
#include <iostream>
#include <limits>
#include <numeric>
#include <optional>
#include <sstream>
#include <stdexcept>
#include <string>
#include <string_view>
#include <sys/resource.h>
#include <utility>
#include <vector>
// Learns the public chance-state value U(board, moves-to-rise) before the next
// visible numbered disc is sampled, using an isolated phase-aware n-tuple
// model.
// Drop7 does not have 2048's deterministic afterstate: covered-disc reveals
// can occur while an action's cascade is resolving.
namespace drop7::optimistic_phase_ntuple {
constexpr int kAlphabet = 10;
constexpr int kPatternCount = 10'000;
constexpr int kSharedTables = 17;
constexpr int kAbsoluteTables = 92;
constexpr int kTuplePlacements = 92;
constexpr int kActiveOccurrences = 2 * kTuplePlacements;
constexpr int kPooledEntries =
(kSharedTables + kAbsoluteTables) * kPatternCount;
constexpr int kPhaseEntries =
(kSharedTables + kAbsoluteTables) * kMovesPerLevel * kPatternCount;
constexpr float kOptimisticValue = 60.0f;
constexpr float kScoreScale = 17'000.0f;
constexpr float kGamma = 1.0f;
constexpr float kLambda = 0.5f;
constexpr int kTraceHorizon = 3;
constexpr std::uint64_t kPromotionTransition = 20'000'000;
constexpr std::uint64_t kLearningRateDrop1 = 50'000'000;
constexpr std::uint64_t kLearningRateDrop2 = 75'000'000;
constexpr std::uint64_t kTemporalCoherenceTransition = 90'000'000;
constexpr std::uint64_t kFrozenTrainingTransitions = 100'000'000;
constexpr std::uint64_t kBurnedStageATransitions = 50'000'000;
constexpr int kFrozenMaximumMoves = 2'000;
constexpr int kFrozenRevealSamples = 7;
constexpr int kFrozenEventBoundaries = 2;
constexpr int kFrozenInternalActionWidth = 2;
constexpr std::uint64_t kFrozenMaximumSearchWork = 100'000;
constexpr std::uint32_t kCheckpointFormatVersion = 3;
constexpr float kLearningRate0 = 0.1f;
constexpr float kLearningRate1 = 0.01f;
constexpr float kLearningRate2 = 0.001f;
constexpr float kTemporalCoherenceRate = 1.0f;
constexpr std::uint32_t kPolicyRevealDomain = 0x4f50'5452u;
constexpr std::uint32_t kSearchRevealDomain = 0x4556'4e54u;
constexpr std::uint32_t kSearchOrderDomain = 0x4f52'4452u;
constexpr std::uint32_t kChanceCoordinateDomain = 0x434f'4f52u;
// Seed authority is a build capability rather than a source edit: the
// immutable audited source is compiled with exact half-open ranges and the
// SHA-256 of a canonical lane manifest. Missing macros keep a lane closed.
#if defined(DROP7_TRAINING_SEED_BEGIN)
constexpr std::uint32_t kTrainingSeedBegin = DROP7_TRAINING_SEED_BEGIN;
#else
constexpr std::uint32_t kTrainingSeedBegin = 0;
#endif
#if defined(DROP7_TRAINING_SEED_END)
constexpr std::uint32_t kTrainingSeedEnd = DROP7_TRAINING_SEED_END;
#else
constexpr std::uint32_t kTrainingSeedEnd = 0;
#endif
#if defined(DROP7_TRAINING_LANE_MANIFEST_SHA256)
constexpr std::string_view kCompiledTrainingLaneManifestSha256 =
DROP7_TRAINING_LANE_MANIFEST_SHA256;
#else
constexpr std::string_view kCompiledTrainingLaneManifestSha256 = "";
#endif
constexpr std::uint32_t kBurnedStageASeedBegin = 0x3d20'0000u;
constexpr std::uint32_t kBurnedStageASeedEnd = 0x3d20'0040u;
#if defined(DROP7_DEVELOPMENT_SEED_BEGIN)
constexpr std::uint32_t kDevelopmentSeedBegin =
DROP7_DEVELOPMENT_SEED_BEGIN;
#else
constexpr std::uint32_t kDevelopmentSeedBegin = 0;
#endif
#if defined(DROP7_DEVELOPMENT_SEED_END)
constexpr std::uint32_t kDevelopmentSeedEnd = DROP7_DEVELOPMENT_SEED_END;
#else
constexpr std::uint32_t kDevelopmentSeedEnd = 0;
#endif
#if defined(DROP7_DEVELOPMENT_LANE_MANIFEST_SHA256)
constexpr std::string_view kCompiledDevelopmentLaneManifestSha256 =
DROP7_DEVELOPMENT_LANE_MANIFEST_SHA256;
#else
constexpr std::string_view kCompiledDevelopmentLaneManifestSha256 = "";
#endif
#if defined(DROP7_STAGE_A_QUALIFICATION_SHA256)
constexpr std::string_view kCompiledStageAQualificationSha256 =
DROP7_STAGE_A_QUALIFICATION_SHA256;
#else
constexpr std::string_view kCompiledStageAQualificationSha256 = "";
#endif
#if defined(DROP7_OPTIMISTIC_PHASE_NTUPLE_SOURCE_SHA256)
constexpr std::string_view kCompiledSourceSha256 =
DROP7_OPTIMISTIC_PHASE_NTUPLE_SOURCE_SHA256;
#else
constexpr std::string_view kCompiledSourceSha256 = "";
#endif
#if defined(DROP7_ENGINE_SOURCE_SHA256)
constexpr std::string_view kCompiledEngineSha256 = DROP7_ENGINE_SOURCE_SHA256;
#else
constexpr std::string_view kCompiledEngineSha256 = "";
#endif
#if defined(DROP7_CORRECTED_D4_SOURCE_SHA256)
constexpr std::string_view kCompiledD4Sha256 =
DROP7_CORRECTED_D4_SOURCE_SHA256;
#else
constexpr std::string_view kCompiledD4Sha256 = "";
#endif
#if defined(DROP7_CORRECTED_D4_LEAF_SOURCE_SHA256)
constexpr std::string_view kCompiledD4LeafSha256 =
DROP7_CORRECTED_D4_LEAF_SOURCE_SHA256;
#else
constexpr std::string_view kCompiledD4LeafSha256 = "";
#endif
#if defined(DROP7_CFPI_BEHAVIOR_SHA256)
constexpr std::string_view kCompiledCfpiBehaviorSha256 =
DROP7_CFPI_BEHAVIOR_SHA256;
#else
constexpr std::string_view kCompiledCfpiBehaviorSha256 = "";
#endif
static_assert(kAlphabet * kAlphabet * kAlphabet * kAlphabet ==
kPatternCount);
static_assert(kSharedTables == 4 + 4 + 9);
static_assert(kTuplePlacements == 28 + 28 + 36);
static_assert(kPooledEntries == 1'090'000);
static_assert(kPhaseEntries == 5'450'000);
static_assert(kPromotionTransition < kLearningRateDrop1);
static_assert(kLearningRateDrop1 < kLearningRateDrop2);
static_assert(kLearningRateDrop2 < kTemporalCoherenceTransition);
static_assert(kTemporalCoherenceTransition < kFrozenTrainingTransitions);
static_assert(kTrainingSeedBegin <= kTrainingSeedEnd);
static_assert(kBurnedStageASeedEnd - kBurnedStageASeedBegin == 64u);
static_assert(kDevelopmentSeedBegin <= kDevelopmentSeedEnd);
enum class ModelStage : std::uint8_t { kPooled = 0, kPhase = 1 };
enum class UpdateRule : std::uint8_t { kFixed = 0, kTemporalCoherence = 1 };
enum class SeedUse : std::uint8_t {
kTraining = 0,
kBurnedStageA = 1,
kDevelopment = 2
};
enum class EvaluationStage : std::uint8_t {
kBurnedStageA = 0,
kFinalDevelopment = 1
};
struct ValueState {
Board board{};
std::uint8_t moves_remaining = kMovesPerLevel;
bool terminal = false;
bool operator==(const ValueState&) const = default;
};
ValueState valueState(const State& state) {
return {state.board, static_cast<std::uint8_t>(state.moves_remaining),
state.game_over};
}
Board mirrorBoard(const Board& source) {
Board result{};
for (int row = 0; row < kBoardSize; ++row) {
for (int column = 0; column < kBoardSize; ++column) {
result[indexOf(row, column)] =
source[indexOf(row, kBoardSize - 1 - column)];
}
}
return result;
}
bool mirrorIsSmaller(const Board& board) {
for (int row = 0; row < kBoardSize; ++row) {
for (int column = 0; column < kBoardSize; ++column) {
const std::uint8_t forward = board[indexOf(row, column)];
const std::uint8_t reflected =
board[indexOf(row, kBoardSize - 1 - column)];
if (reflected < forward) return true;
if (reflected > forward) return false;
}
}
return false;
}
struct CanonicalState {
State state{};
bool mirrored = false;
};
CanonicalState canonicalize(const State& source) {
CanonicalState result{source, mirrorIsSmaller(source.board)};
if (result.mirrored) result.state.board = mirrorBoard(source.board);
return result;
}
ValueState canonicalize(const ValueState& source) {
ValueState result = source;
if (mirrorIsSmaller(source.board)) result.board = mirrorBoard(source.board);
return result;
}
int physicalAction(int canonical_action, bool mirrored) {
if (canonical_action < 0) return canonical_action;
return mirrored ? kBoardSize - 1 - canonical_action : canonical_action;
}
int patternCode(std::uint8_t first, std::uint8_t second,
std::uint8_t third, std::uint8_t fourth) {
return ((first * kAlphabet + second) * kAlphabet + third) * kAlphabet +
fourth;
}
struct FeatureOccurrences {
std::array<std::uint32_t, kActiveOccurrences> ids{};
};
std::uint32_t sharedFeatureId(int table, int phase, int pattern,
ModelStage stage) {
if (table < 0 || table >= kSharedTables || pattern < 0 ||
pattern >= kPatternCount) {
throw std::logic_error("shared n-tuple feature outside table bounds");
}
if (stage == ModelStage::kPooled) {
return static_cast<std::uint32_t>(table * kPatternCount + pattern);
}
return static_cast<std::uint32_t>(
((phase * kSharedTables + table) * kPatternCount) + pattern);
}
std::uint32_t absoluteFeatureId(int table, int phase, int pattern,
ModelStage stage) {
if (table < 0 || table >= kAbsoluteTables || pattern < 0 ||
pattern >= kPatternCount) {
throw std::logic_error("absolute n-tuple feature outside table bounds");
}
if (stage == ModelStage::kPooled) {
return static_cast<std::uint32_t>(
(kSharedTables + table) * kPatternCount + pattern);
}
const int shared_entries = kSharedTables * kMovesPerLevel * kPatternCount;
return static_cast<std::uint32_t>(
shared_entries +
((phase * kAbsoluteTables + table) * kPatternCount) + pattern);
}
FeatureOccurrences featureOccurrences(const ValueState& source,
ModelStage stage) {
if (source.moves_remaining < 1 ||
source.moves_remaining > kMovesPerLevel) {
throw std::logic_error("invalid moves-to-rise phase");
}
const ValueState state = canonicalize(source);
const int phase = static_cast<int>(state.moves_remaining) - 1;
FeatureOccurrences result;
int occurrence = 0;
int absolute_table = 0;
auto add = [&](int shared_table, int pattern) {
result.ids[occurrence++] =
sharedFeatureId(shared_table, phase, pattern, stage);
result.ids[occurrence++] =
absoluteFeatureId(absolute_table++, phase, pattern, stage);
};
for (int row = 0; row < kBoardSize; ++row) {
for (int start = 0; start <= kBoardSize - 4; ++start) {
add(start,
patternCode(state.board[indexOf(row, start)],
state.board[indexOf(row, start + 1)],
state.board[indexOf(row, start + 2)],
state.board[indexOf(row, start + 3)]));
}
}
for (int column = 0; column < kBoardSize; ++column) {
for (int start = 0; start <= kBoardSize - 4; ++start) {
add(4 + start,
patternCode(state.board[indexOf(start, column)],
state.board[indexOf(start + 1, column)],
state.board[indexOf(start + 2, column)],
state.board[indexOf(start + 3, column)]));
}
}
for (int row = 0; row < kBoardSize - 1; ++row) {
for (int column = 0; column < kBoardSize - 1; ++column) {
add(8 + (row % 3) * 3 + (column % 3),
patternCode(state.board[indexOf(row, column)],
state.board[indexOf(row, column + 1)],
state.board[indexOf(row + 1, column)],
state.board[indexOf(row + 1, column + 1)]));
}
}
if (occurrence != kActiveOccurrences ||
absolute_table != kAbsoluteTables) {
throw std::logic_error("n-tuple occurrence count invariant failed");
}
return result;
}
struct ActiveFeatures {
std::array<std::uint32_t, kActiveOccurrences> ids{};
std::array<std::uint16_t, kActiveOccurrences> multiplicities{};
int count = 0;
int squared_norm = 0;
int maximum_multiplicity = 0;
};
ActiveFeatures activeFeatures(const ValueState& state, ModelStage stage) {
auto sorted = featureOccurrences(state, stage).ids;
std::sort(sorted.begin(), sorted.end());
ActiveFeatures result;
for (std::uint32_t id : sorted) {
if (result.count == 0 || result.ids[result.count - 1] != id) {
result.ids[result.count] = id;
result.multiplicities[result.count] = 1;
++result.count;
} else {
++result.multiplicities[result.count - 1];
}
}
for (int index = 0; index < result.count; ++index) {
const int multiplicity = result.multiplicities[index];
result.squared_norm += multiplicity * multiplicity;
result.maximum_multiplicity =
std::max(result.maximum_multiplicity, multiplicity);
}
if (result.count < 1 || result.squared_norm < kActiveOccurrences) {
throw std::logic_error("n-tuple gradient aggregation invariant failed");
}
return result;
}
struct UpdateReport {
float prediction_before = 0;
float prediction_after = 0;
float mean_beta = 1;
int unique_parameters = 0;
int squared_norm = 0;
int maximum_multiplicity = 0;
};
struct Progress {
std::uint64_t transitions = 0;
std::uint64_t completed_games = 0;
std::uint32_t training_seed_start = 0;
bool game_active = false;
bool training_finalized = false;
std::uint32_t active_game_seed = 0;
State active_state{};
std::uint64_t cumulative_score = 0;
std::uint64_t cumulative_moves = 0;
std::uint64_t censored_training_games = 0;
};
struct TrainingContract {
std::uint32_t maximum_moves = kFrozenMaximumMoves;
std::uint32_t reveal_samples = kFrozenRevealSamples;
std::uint32_t training_seed_begin = kTrainingSeedBegin;
std::uint32_t training_seed_end = kTrainingSeedEnd;
std::string training_lane_manifest_sha256{
kCompiledTrainingLaneManifestSha256};
std::string source_sha256{kCompiledSourceSha256};
std::string engine_sha256{kCompiledEngineSha256};
std::string corrected_d4_sha256{kCompiledD4Sha256};
std::string corrected_d4_leaf_sha256{kCompiledD4LeafSha256};
std::string cfpi_behavior_sha256{kCompiledCfpiBehaviorSha256};
bool operator==(const TrainingContract&) const = default;
};
template <typename T>
void writeScalar(std::ostream& output, const T& value) {
output.write(reinterpret_cast<const char*>(&value), sizeof(value));
if (!output) throw std::runtime_error("failed writing n-tuple checkpoint");
}
template <typename T>
T readScalar(std::istream& input) {
T value{};
input.read(reinterpret_cast<char*>(&value), sizeof(value));
if (!input) throw std::runtime_error("truncated n-tuple checkpoint");
return value;
}
void writeFloats(std::ostream& output, const std::vector<float>& values) {
output.write(reinterpret_cast<const char*>(values.data()),
static_cast<std::streamsize>(values.size() * sizeof(float)));
if (!output) throw std::runtime_error("failed writing n-tuple checkpoint");
}
void readFloats(std::istream& input, std::vector<float>& values) {
input.read(reinterpret_cast<char*>(values.data()),
static_cast<std::streamsize>(values.size() * sizeof(float)));
if (!input) throw std::runtime_error("truncated n-tuple checkpoint");
if (std::any_of(values.begin(), values.end(),
[](float value) { return !std::isfinite(value); })) {
throw std::runtime_error("non-finite parameter in n-tuple checkpoint");
}
}
std::uint64_t checksumBytes(std::string_view bytes) {
std::uint64_t hash = 0xcbf2'9ce4'8422'2325ull;
for (const unsigned char byte : bytes) {
hash ^= byte;
hash *= 0x0000'0100'0000'01b3ull;
}
return hash;
}
constexpr std::array<std::uint32_t, 64> kSha256Constants{{
0x428a2f98u, 0x71374491u, 0xb5c0fbcfu, 0xe9b5dba5u,
0x3956c25bu, 0x59f111f1u, 0x923f82a4u, 0xab1c5ed5u,
0xd807aa98u, 0x12835b01u, 0x243185beu, 0x550c7dc3u,
0x72be5d74u, 0x80deb1feu, 0x9bdc06a7u, 0xc19bf174u,
0xe49b69c1u, 0xefbe4786u, 0x0fc19dc6u, 0x240ca1ccu,
0x2de92c6fu, 0x4a7484aau, 0x5cb0a9dcu, 0x76f988dau,
0x983e5152u, 0xa831c66du, 0xb00327c8u, 0xbf597fc7u,
0xc6e00bf3u, 0xd5a79147u, 0x06ca6351u, 0x14292967u,
0x27b70a85u, 0x2e1b2138u, 0x4d2c6dfcu, 0x53380d13u,
0x650a7354u, 0x766a0abbu, 0x81c2c92eu, 0x92722c85u,
0xa2bfe8a1u, 0xa81a664bu, 0xc24b8b70u, 0xc76c51a3u,
0xd192e819u, 0xd6990624u, 0xf40e3585u, 0x106aa070u,
0x19a4c116u, 0x1e376c08u, 0x2748774cu, 0x34b0bcb5u,
0x391c0cb3u, 0x4ed8aa4au, 0x5b9cca4fu, 0x682e6ff3u,
0x748f82eeu, 0x78a5636fu, 0x84c87814u, 0x8cc70208u,
0x90befffau, 0xa4506cebu, 0xbef9a3f7u, 0xc67178f2u,
}};
std::string sha256(std::string_view source) {
std::vector<std::uint8_t> message(source.begin(), source.end());
const std::uint64_t bit_length =
static_cast<std::uint64_t>(message.size()) * 8u;
message.push_back(0x80u);
while (message.size() % 64 != 56) message.push_back(0u);
for (int byte = 7; byte >= 0; --byte) {
message.push_back(
static_cast<std::uint8_t>((bit_length >> (byte * 8)) & 0xffu));
}
std::array<std::uint32_t, 8> hash{{
0x6a09e667u, 0xbb67ae85u, 0x3c6ef372u, 0xa54ff53au,
0x510e527fu, 0x9b05688cu, 0x1f83d9abu, 0x5be0cd19u,
}};
for (std::size_t offset = 0; offset < message.size(); offset += 64) {
std::array<std::uint32_t, 64> words{};
for (int word = 0; word < 16; ++word) {
const std::size_t begin = offset + static_cast<std::size_t>(word * 4);
words[word] = (static_cast<std::uint32_t>(message[begin]) << 24) |
(static_cast<std::uint32_t>(message[begin + 1]) << 16) |
(static_cast<std::uint32_t>(message[begin + 2]) << 8) |
static_cast<std::uint32_t>(message[begin + 3]);
}
for (int word = 16; word < 64; ++word) {
const std::uint32_t s0 =
std::rotr(words[word - 15], 7) ^
std::rotr(words[word - 15], 18) ^ (words[word - 15] >> 3);
const std::uint32_t s1 =
std::rotr(words[word - 2], 17) ^
std::rotr(words[word - 2], 19) ^ (words[word - 2] >> 10);
words[word] = words[word - 16] + s0 + words[word - 7] + s1;
}
std::uint32_t a = hash[0];
std::uint32_t b = hash[1];
std::uint32_t c = hash[2];
std::uint32_t d = hash[3];
std::uint32_t e = hash[4];
std::uint32_t f = hash[5];
std::uint32_t g = hash[6];
std::uint32_t h = hash[7];
for (int round = 0; round < 64; ++round) {
const std::uint32_t upper =
std::rotr(e, 6) ^ std::rotr(e, 11) ^ std::rotr(e, 25);
const std::uint32_t choose = (e & f) ^ (~e & g);
const std::uint32_t first =
h + upper + choose + kSha256Constants[round] + words[round];
const std::uint32_t lower =
std::rotr(a, 2) ^ std::rotr(a, 13) ^ std::rotr(a, 22);
const std::uint32_t majority = (a & b) ^ (a & c) ^ (b & c);
const std::uint32_t second = lower + majority;
h = g;
g = f;
f = e;
e = d + first;
d = c;
c = b;
b = a;
a = first + second;
}
hash[0] += a;
hash[1] += b;
hash[2] += c;
hash[3] += d;
hash[4] += e;
hash[5] += f;
hash[6] += g;
hash[7] += h;
}
std::string result;
constexpr char digits[] = "0123456789abcdef";
result.reserve(64);
for (const std::uint32_t value : hash) {
for (int nibble = 7; nibble >= 0; --nibble) {
result.push_back(digits[(value >> (nibble * 4)) & 0x0fu]);
}
}
return result;
}
std::string readWholeFile(const std::filesystem::path& path) {
std::ifstream input(path, std::ios::binary | std::ios::ate);
if (!input) throw std::runtime_error("could not read artifact for SHA-256");
const std::streampos end = input.tellg();
if (end < 0) throw std::runtime_error("invalid artifact byte count");
std::string result(static_cast<std::size_t>(end), '\0');
input.seekg(0);
input.read(result.data(), static_cast<std::streamsize>(result.size()));
if (!input) throw std::runtime_error("artifact SHA-256 read failed");
return result;
}
std::string fileSha256(const std::filesystem::path& path) {
return sha256(readWholeFile(path));
}
bool validSha256Hex(std::string_view value) {
return value.size() == 64 &&
std::all_of(value.begin(), value.end(), [](char character) {
return (character >= '0' && character <= '9') ||
(character >= 'a' && character <= 'f');
});
}
struct ProvenanceHashes {
std::string source;
std::string engine;
std::string corrected_d4;
std::string corrected_d4_leaf;
std::string cfpi_behavior;
};
bool compiledProvenanceLocksValid() {
return validSha256Hex(kCompiledSourceSha256) &&
validSha256Hex(kCompiledEngineSha256) &&
validSha256Hex(kCompiledD4Sha256) &&
validSha256Hex(kCompiledD4LeafSha256) &&
validSha256Hex(kCompiledCfpiBehaviorSha256);
}
ProvenanceHashes verifyCompiledProvenance(
const std::filesystem::path& source_directory) {
if (!compiledProvenanceLocksValid()) {
throw std::runtime_error(
"binary lacks complete compile-time source SHA-256 locks");
}
const std::filesystem::path canonical_directory =
std::filesystem::canonical(source_directory);
ProvenanceHashes result{
fileSha256(canonical_directory / "approaches/ntuple-rl/optimistic-phase/optimistic-phase-ntuple.cpp"),
fileSha256(canonical_directory / "src/core/native/engine.hpp"),
fileSha256(canonical_directory / "approaches/fair-expectimax/reference/fair-only-depth4.cpp"),
fileSha256(canonical_directory / "approaches/fair-expectimax/reference/fair-only-horizon.cpp"),
fileSha256(canonical_directory / "src/core/native/public-behavior.hpp")};
if (result.source != kCompiledSourceSha256 ||
result.engine != kCompiledEngineSha256 ||
result.corrected_d4 != kCompiledD4Sha256 ||
result.corrected_d4_leaf != kCompiledD4LeafSha256 ||
result.cfpi_behavior != kCompiledCfpiBehaviorSha256) {
throw std::runtime_error(
"on-disk source differs from the binary's build locks");
}
return result;
}
std::string_view compiledLaneManifestSha256(SeedUse use) {
if (use == SeedUse::kTraining) {
return kCompiledTrainingLaneManifestSha256;
}
if (use == SeedUse::kDevelopment) {
return kCompiledDevelopmentLaneManifestSha256;
}
throw std::invalid_argument("burned Stage A has no external lane manifest");
}
std::string canonicalLaneManifest(SeedUse use) {
if (use == SeedUse::kBurnedStageA) {
throw std::invalid_argument("burned Stage A lane is source-locked");
}
const bool training = use == SeedUse::kTraining;
const std::uint32_t begin =
training ? kTrainingSeedBegin : kDevelopmentSeedBegin;
const std::uint32_t end =
training ? kTrainingSeedEnd : kDevelopmentSeedEnd;
std::ostringstream manifest;
manifest << "drop7-seed-lane-v1\n"
<< "purpose=" << (training ? "training" : "final-development")
<< "\nseedBegin=0x" << std::hex << std::setw(8)
<< std::setfill('0') << begin << "\nseedEndExclusive=0x"
<< std::setw(8) << end << std::dec << std::setfill(' ')
<< "\nsourceSha256=" << kCompiledSourceSha256
<< "\nengineSha256=" << kCompiledEngineSha256
<< "\nmaximumMoves=" << kFrozenMaximumMoves
<< "\nrevealSamples=" << kFrozenRevealSamples
<< "\ntrainingTransitions=" << kFrozenTrainingTransitions;
if (!training) {
manifest << "\ngames=256"
<< "\neventBoundaries=" << kFrozenEventBoundaries
<< "\ninternalActionWidth=" << kFrozenInternalActionWidth
<< "\nmaximumWork=" << kFrozenMaximumSearchWork;
}
manifest << '\n';
return manifest.str();
}
std::string verifyLaneManifest(const std::filesystem::path& path,
SeedUse use) {
const std::string_view compiled_hash = compiledLaneManifestSha256(use);
if (!validSha256Hex(compiled_hash)) {
throw std::runtime_error("binary lacks a compile-time lane-manifest lock");
}
const std::string contents = readWholeFile(path);
const std::string hash = sha256(contents);
if (hash != compiled_hash || contents != canonicalLaneManifest(use)) {
throw std::runtime_error(
"lane manifest differs from the compiled seed capability");
}
return hash;
}
bool sameFilesystemTarget(const std::filesystem::path& first,
const std::filesystem::path& second) {
std::error_code error;
if (std::filesystem::exists(first, error) && !error &&
std::filesystem::exists(second, error) && !error &&
std::filesystem::equivalent(first, second, error) && !error) {
return true;
}
error.clear();
const std::filesystem::path first_resolved =
std::filesystem::weakly_canonical(first, error);
if (error) throw std::runtime_error("could not resolve output path");
const std::filesystem::path second_resolved =
std::filesystem::weakly_canonical(second, error);
if (error) throw std::runtime_error("could not resolve protected path");
return first_resolved == second_resolved;
}
void writeCheckpointHash(std::ostream& output, std::string_view value) {
if (!value.empty() && !validSha256Hex(value)) {
throw std::runtime_error("invalid checkpoint provenance hash");
}
writeScalar<std::uint8_t>(output,
static_cast<std::uint8_t>(value.size()));
output.write(value.data(), static_cast<std::streamsize>(value.size()));
if (!output) throw std::runtime_error("failed writing checkpoint provenance");
}
std::string readCheckpointHash(std::istream& input) {
const std::uint8_t size = readScalar<std::uint8_t>(input);
if (size != 0 && size != 64) {
throw std::runtime_error("invalid checkpoint provenance hash length");
}
std::string result(size, '\0');
input.read(result.data(), static_cast<std::streamsize>(result.size()));
if (!input) throw std::runtime_error("truncated checkpoint provenance");
if (!result.empty() && !validSha256Hex(result)) {
throw std::runtime_error("invalid checkpoint provenance hash");
}
return result;
}
void writeState(std::ostream& output, const State& state) {
output.write(reinterpret_cast<const char*>(state.board.data()),
static_cast<std::streamsize>(state.board.size()));
if (!output) throw std::runtime_error("failed writing n-tuple checkpoint");
writeScalar<std::uint8_t>(output, state.next_disc);
writeScalar<std::int64_t>(output, state.score);
writeScalar<std::int32_t>(output, state.level);
writeScalar<std::int32_t>(output, state.moves_remaining);
writeScalar<std::int32_t>(output, state.moves_played);
writeScalar<std::uint8_t>(output, state.game_over ? 1u : 0u);
}
State readState(std::istream& input) {
State state;
input.read(reinterpret_cast<char*>(state.board.data()),
static_cast<std::streamsize>(state.board.size()));
if (!input) throw std::runtime_error("truncated n-tuple checkpoint");
state.next_disc = readScalar<std::uint8_t>(input);
state.score = readScalar<std::int64_t>(input);
state.level = readScalar<std::int32_t>(input);
state.moves_remaining = readScalar<std::int32_t>(input);
state.moves_played = readScalar<std::int32_t>(input);
const std::uint8_t terminal = readScalar<std::uint8_t>(input);
if (std::any_of(state.board.begin(), state.board.end(),
[](std::uint8_t cell) { return cell > kCracked; }) ||
state.next_disc < 1 || state.next_disc > 7 || state.score < 0 ||
state.level < 1 || state.moves_remaining < 0 ||
state.moves_remaining > kMovesPerLevel || state.moves_played < 0 ||
terminal > 1 || (state.moves_remaining == 0 && terminal == 0)) {
throw std::runtime_error("invalid game state in n-tuple checkpoint");
}
state.game_over = terminal != 0;
return state;
}
class Model {
public:
explicit Model(float optimistic_value = kOptimisticValue)
: weights_(kPooledEntries,
optimistic_value /
static_cast<float>(kActiveOccurrences)) {}
ModelStage stage() const { return stage_; }
bool promoted() const { return stage_ == ModelStage::kPhase; }
bool temporalCoherenceEnabled() const { return tc_enabled_; }
std::size_t entries() const { return weights_.size(); }
std::size_t parameterBytes() const {
return (weights_.size() + signed_error_.size() +
absolute_error_.size()) *
sizeof(float);
}
std::uint64_t fingerprint() const {
std::uint64_t hash = 0xcbf2'9ce4'8422'2325ull;
const auto append = [&](const void* data, std::size_t size) {
const auto* bytes = static_cast<const unsigned char*>(data);
for (std::size_t index = 0; index < size; ++index) {
hash ^= bytes[index];
hash *= 0x0000'0100'0000'01b3ull;
}
};
const std::uint8_t stage = static_cast<std::uint8_t>(stage_);
const std::uint8_t tc = tc_enabled_ ? 1u : 0u;
append(&stage, sizeof(stage));
append(&tc, sizeof(tc));
append(weights_.data(), weights_.size() * sizeof(float));
append(signed_error_.data(), signed_error_.size() * sizeof(float));
append(absolute_error_.data(), absolute_error_.size() * sizeof(float));
return hash;
}
std::string parameterSha256() const {
std::string bytes;
bytes.reserve(2u + parameterBytes());
bytes.push_back(static_cast<char>(stage_));
bytes.push_back(tc_enabled_ ? '\1' : '\0');
const auto append = [&](const std::vector<float>& values) {
if (!values.empty()) {
bytes.append(reinterpret_cast<const char*>(values.data()),
values.size() * sizeof(float));
}
};
append(weights_);
append(signed_error_);
append(absolute_error_);
return sha256(bytes);
}
float value(const ValueState& state) const {
if (state.terminal) return 0.0f;
const FeatureOccurrences active = featureOccurrences(state, stage_);
double result = 0.0;
for (std::uint32_t id : active.ids) result += weights_[id];
return static_cast<float>(result);
}
float value(const State& state) const { return value(valueState(state)); }
UpdateReport update(const ValueState& state, float credit, float rate,
UpdateRule rule) {
if (state.terminal) {
throw std::invalid_argument("cannot update a terminal chance state");
}
if (!std::isfinite(credit) || !std::isfinite(rate) || rate <= 0.0f) {
throw std::invalid_argument("invalid n-tuple update");
}
if (rule == UpdateRule::kTemporalCoherence && !tc_enabled_) {
throw std::logic_error("temporal coherence used before schedule switch");
}
const ActiveFeatures active = activeFeatures(state, stage_);
UpdateReport report;
report.prediction_before = valueFromFeatures(active);
report.unique_parameters = active.count;
report.squared_norm = active.squared_norm;
report.maximum_multiplicity = active.maximum_multiplicity;
const float normalized =
rate * credit / static_cast<float>(active.squared_norm);
double beta_total = 0.0;
for (int index = 0; index < active.count; ++index) {
const std::uint32_t id = active.ids[index];
const int multiplicity = active.multiplicities[index];
float beta = 1.0f;
if (rule == UpdateRule::kTemporalCoherence) {
beta = absolute_error_[id] > 0.0f
? std::abs(signed_error_[id]) / absolute_error_[id]
: 1.0f;
}
weights_[id] += beta * normalized * multiplicity;
beta_total += beta;
if (rule == UpdateRule::kTemporalCoherence) {
const float parameter_error = credit * multiplicity;
signed_error_[id] += parameter_error;
absolute_error_[id] += std::abs(parameter_error);
if (absolute_error_[id] > 1.0e7f) {
signed_error_[id] *= 0.5f;
absolute_error_[id] *= 0.5f;
}
}
}
report.mean_beta =
static_cast<float>(beta_total / static_cast<double>(active.count));
report.prediction_after = valueFromFeatures(active);
return report;
}
void promote() {
if (promoted()) throw std::logic_error("phase model already promoted");
if (tc_enabled_) {
throw std::logic_error("cannot promote after temporal coherence starts");
}
std::vector<float> promoted(kPhaseEntries);
const int phase_shared_entries =
kSharedTables * kMovesPerLevel * kPatternCount;
for (int phase = 0; phase < kMovesPerLevel; ++phase) {
for (int table = 0; table < kSharedTables; ++table) {
const std::size_t source =
static_cast<std::size_t>(table) * kPatternCount;
const std::size_t destination =
static_cast<std::size_t>(phase * kSharedTables + table) *
kPatternCount;
std::copy_n(weights_.begin() + static_cast<std::ptrdiff_t>(source),
kPatternCount,
promoted.begin() +
static_cast<std::ptrdiff_t>(destination));
}
for (int table = 0; table < kAbsoluteTables; ++table) {
const std::size_t source =
static_cast<std::size_t>(kSharedTables + table) * kPatternCount;
const std::size_t destination =
static_cast<std::size_t>(phase_shared_entries) +
static_cast<std::size_t>(phase * kAbsoluteTables + table) *
kPatternCount;
std::copy_n(weights_.begin() + static_cast<std::ptrdiff_t>(source),
kPatternCount,
promoted.begin() +
static_cast<std::ptrdiff_t>(destination));
}
}
weights_ = std::move(promoted);
stage_ = ModelStage::kPhase;
}
void enableTemporalCoherence() {
if (!promoted()) {
throw std::logic_error("temporal coherence requires promoted phase heads");
}
if (tc_enabled_) {
throw std::logic_error("temporal coherence already enabled");
}
signed_error_.assign(weights_.size(), 0.0f);
absolute_error_.assign(weights_.size(), 0.0f);
tc_enabled_ = true;
}
void writeCheckpoint(std::ostream& output, const Progress& progress,
const TrainingContract& contract) const {
constexpr std::array<char, 8> magic{{'D', '7', 'O', 'P', 'N', 'T', '3', 0}};
output.write(magic.data(), static_cast<std::streamsize>(magic.size()));
if (!output) throw std::runtime_error("failed writing n-tuple checkpoint");
writeScalar<std::uint32_t>(output, kCheckpointFormatVersion);
writeScalar<std::uint8_t>(output, static_cast<std::uint8_t>(stage_));
writeScalar<std::uint8_t>(output, tc_enabled_ ? 1u : 0u);
writeScalar<std::uint8_t>(output, progress.game_active ? 1u : 0u);
writeScalar<std::uint8_t>(output, progress.training_finalized ? 1u : 0u);
writeScalar<std::uint64_t>(output, progress.transitions);
writeScalar<std::uint64_t>(output, progress.completed_games);
writeScalar<std::uint32_t>(output, progress.training_seed_start);
writeScalar<std::uint32_t>(output, progress.active_game_seed);
writeState(output, progress.active_state);
writeScalar<std::uint64_t>(output, progress.cumulative_score);
writeScalar<std::uint64_t>(output, progress.cumulative_moves);
writeScalar<std::uint64_t>(output, progress.censored_training_games);
writeScalar<std::uint32_t>(output, contract.maximum_moves);
writeScalar<std::uint32_t>(output, contract.reveal_samples);
writeScalar<std::uint32_t>(output, contract.training_seed_begin);
writeScalar<std::uint32_t>(output, contract.training_seed_end);
writeCheckpointHash(output, contract.training_lane_manifest_sha256);
writeCheckpointHash(output, contract.source_sha256);
writeCheckpointHash(output, contract.engine_sha256);
writeCheckpointHash(output, contract.corrected_d4_sha256);
writeCheckpointHash(output, contract.corrected_d4_leaf_sha256);
writeCheckpointHash(output, contract.cfpi_behavior_sha256);
writeScalar<std::uint64_t>(output, kPromotionTransition);
writeScalar<std::uint64_t>(output, kLearningRateDrop1);
writeScalar<std::uint64_t>(output, kLearningRateDrop2);
writeScalar<std::uint64_t>(output, kTemporalCoherenceTransition);
writeScalar<std::uint64_t>(output, kFrozenTrainingTransitions);
writeScalar<float>(output, kOptimisticValue);
writeScalar<float>(output, kScoreScale);
writeScalar<float>(output, kGamma);
writeScalar<float>(output, kLambda);
writeScalar<float>(output, kLearningRate0);
writeScalar<float>(output, kLearningRate1);
writeScalar<float>(output, kLearningRate2);
writeScalar<float>(output, kTemporalCoherenceRate);
writeScalar<std::uint32_t>(output, kTraceHorizon);
writeScalar<std::uint64_t>(output, weights_.size());
writeScalar<std::uint64_t>(output, signed_error_.size());
writeScalar<std::uint64_t>(output, absolute_error_.size());
writeFloats(output, weights_);
writeFloats(output, signed_error_);
writeFloats(output, absolute_error_);
}
Progress readCheckpoint(std::istream& input, TrainingContract& contract) {
std::array<char, 8> magic{};
input.read(magic.data(), static_cast<std::streamsize>(magic.size()));
constexpr std::array<char, 8> expected{{'D', '7', 'O', 'P', 'N', 'T', '3', 0}};
if (!input || magic != expected) {
throw std::runtime_error("incompatible optimistic n-tuple checkpoint");
}
if (readScalar<std::uint32_t>(input) != kCheckpointFormatVersion) {
throw std::runtime_error("unsupported optimistic n-tuple checkpoint");
}
const std::uint8_t stage = readScalar<std::uint8_t>(input);
const std::uint8_t tc_byte = readScalar<std::uint8_t>(input);
const std::uint8_t game_active = readScalar<std::uint8_t>(input);
const std::uint8_t finalized = readScalar<std::uint8_t>(input);
Progress progress;
progress.game_active = game_active != 0;
progress.training_finalized = finalized != 0;
progress.transitions = readScalar<std::uint64_t>(input);
progress.completed_games = readScalar<std::uint64_t>(input);
progress.training_seed_start = readScalar<std::uint32_t>(input);
progress.active_game_seed = readScalar<std::uint32_t>(input);
progress.active_state = readState(input);
progress.cumulative_score = readScalar<std::uint64_t>(input);
progress.cumulative_moves = readScalar<std::uint64_t>(input);
progress.censored_training_games = readScalar<std::uint64_t>(input);
contract.maximum_moves = readScalar<std::uint32_t>(input);
contract.reveal_samples = readScalar<std::uint32_t>(input);
contract.training_seed_begin = readScalar<std::uint32_t>(input);
contract.training_seed_end = readScalar<std::uint32_t>(input);
contract.training_lane_manifest_sha256 = readCheckpointHash(input);
contract.source_sha256 = readCheckpointHash(input);
contract.engine_sha256 = readCheckpointHash(input);
contract.corrected_d4_sha256 = readCheckpointHash(input);
contract.corrected_d4_leaf_sha256 = readCheckpointHash(input);
contract.cfpi_behavior_sha256 = readCheckpointHash(input);
const auto promotion = readScalar<std::uint64_t>(input);
const auto learning_rate_drop1 = readScalar<std::uint64_t>(input);
const auto learning_rate_drop2 = readScalar<std::uint64_t>(input);
const auto temporal_coherence = readScalar<std::uint64_t>(input);
const auto frozen_transitions = readScalar<std::uint64_t>(input);
const float optimistic = readScalar<float>(input);
const float score_scale = readScalar<float>(input);
const float gamma = readScalar<float>(input);
const float lambda = readScalar<float>(input);
const float learning_rate0 = readScalar<float>(input);
const float learning_rate1 = readScalar<float>(input);
const float learning_rate2 = readScalar<float>(input);
const float tc_rate = readScalar<float>(input);
const auto horizon = readScalar<std::uint32_t>(input);
const auto weight_count = readScalar<std::uint64_t>(input);
const auto signed_count = readScalar<std::uint64_t>(input);
const auto absolute_count = readScalar<std::uint64_t>(input);
if (stage > static_cast<std::uint8_t>(ModelStage::kPhase) || tc_byte > 1 ||
game_active > 1 || finalized > 1 ||
progress.transitions > kFrozenTrainingTransitions ||
(progress.training_finalized &&
progress.transitions != kFrozenTrainingTransitions) ||
(progress.training_finalized && progress.game_active) ||
contract.maximum_moves != kFrozenMaximumMoves ||
contract.reveal_samples != kFrozenRevealSamples ||
contract.training_seed_begin != kTrainingSeedBegin ||
contract.training_seed_end != kTrainingSeedEnd ||
contract.training_lane_manifest_sha256 !=
kCompiledTrainingLaneManifestSha256 ||
progress.training_seed_start != contract.training_seed_begin ||
contract.source_sha256 != kCompiledSourceSha256 ||
contract.engine_sha256 != kCompiledEngineSha256 ||
contract.corrected_d4_sha256 != kCompiledD4Sha256 ||
contract.corrected_d4_leaf_sha256 != kCompiledD4LeafSha256 ||
contract.cfpi_behavior_sha256 != kCompiledCfpiBehaviorSha256 ||
promotion != kPromotionTransition ||
learning_rate_drop1 != kLearningRateDrop1 ||
learning_rate_drop2 != kLearningRateDrop2 ||
temporal_coherence != kTemporalCoherenceTransition ||
frozen_transitions != kFrozenTrainingTransitions ||
optimistic != kOptimisticValue || score_scale != kScoreScale ||
gamma != kGamma || lambda != kLambda ||
learning_rate0 != kLearningRate0 ||
learning_rate1 != kLearningRate1 ||
learning_rate2 != kLearningRate2 ||
tc_rate != kTemporalCoherenceRate || horizon != kTraceHorizon) {
throw std::runtime_error("checkpoint training contract mismatch");
}
const bool tc = tc_byte != 0;
const ModelStage loaded_stage = static_cast<ModelStage>(stage);
const std::uint64_t expected_weights =
loaded_stage == ModelStage::kPooled ? kPooledEntries : kPhaseEntries;
const std::uint64_t expected_aux = tc ? expected_weights : 0u;
if (weight_count != expected_weights || signed_count != expected_aux ||
absolute_count != expected_aux ||
(tc && loaded_stage != ModelStage::kPhase)) {
throw std::runtime_error("checkpoint parameter shape mismatch");
}
stage_ = loaded_stage;
tc_enabled_ = tc;
weights_.assign(static_cast<std::size_t>(weight_count), 0.0f);
signed_error_.assign(static_cast<std::size_t>(signed_count), 0.0f);
absolute_error_.assign(static_cast<std::size_t>(absolute_count), 0.0f);
readFloats(input, weights_);
readFloats(input, signed_error_);
readFloats(input, absolute_error_);
if ((progress.transitions >= kPromotionTransition) != promoted() ||
(progress.transitions >= kTemporalCoherenceTransition) !=
tc_enabled_) {
throw std::runtime_error("checkpoint stage disagrees with transition count");
}
return progress;
}
private:
float valueFromFeatures(const ActiveFeatures& active) const {
double result = 0.0;
for (int index = 0; index < active.count; ++index) {
result += static_cast<double>(weights_[active.ids[index]]) *
active.multiplicities[index];
}
return static_cast<float>(result);
}
ModelStage stage_ = ModelStage::kPooled;
bool tc_enabled_ = false;
std::vector<float> weights_;
std::vector<float> signed_error_;
std::vector<float> absolute_error_;
};
float rewardForMove(const MoveResult& move) {
return static_cast<float>(move.score_delta) / kScoreScale;
}
float tdTarget(float reward, float continuation, bool terminal) {
return reward + (terminal ? 0.0f : kGamma * continuation);
}
std::uint64_t publicHash(const State& source) {
const State state = canonicalize(source).state;
std::uint64_t hash = 0xcbf2'9ce4'8422'2325ull;
for (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);
hash *= 0x0000'0100'0000'01b3ull;
return hash;
}
std::uint32_t chanceEventBits(const State& state, std::uint32_t domain,
std::uint64_t nonce,
int depth, std::uint32_t event) {
const std::uint64_t hash = publicHash(state);
std::uint32_t value = static_cast<std::uint32_t>(hash) ^
static_cast<std::uint32_t>(hash >> 32u) ^ domain ^
kChanceCoordinateDomain;
value = mix32(value ^ static_cast<std::uint32_t>(nonce));
value = mix32(value ^ static_cast<std::uint32_t>(nonce >> 32u));
value = mix32(value ^ static_cast<std::uint32_t>(depth + 1) *
0x9e37'79b9u);
return mix32(value ^ (event + 1u) * 0xc2b2'ae35u);
}
// A Latin-hypercube chance pack. Every stable reveal coordinate and every
// sequential next-visible-disc event is exactly 1..7 over strata 0..6. The
// action resolves reflection orientation on reflection-fixed boards, so side
// actions mirror exactly; center actions retain distinct left/right events.
class ChancePackRandom {
public:
ChancePackRandom(const State& state, int action, std::uint32_t domain,
std::uint64_t nonce, int depth, int stratum,
std::uint32_t event_prefix = 0)
: state_(state),
domain_(domain),
nonce_(nonce),
depth_(depth),
stratum_(stratum),
event_(event_prefix),
event_prefix_(event_prefix),
mirrored_(mirrorIsSmaller(state.board) ||
(state.board == mirrorBoard(state.board) &&
action > kBoardSize / 2)) {
if (action < 0 || action >= kBoardSize || stratum < 0 || stratum >= 7) {
throw std::invalid_argument("chance-pack coordinate outside domain");
}
}
std::uint8_t nextDisc() {
const int rotation = static_cast<int>(
chanceEventBits(state_, domain_, nonce_, depth_, event_++) %
7u);
return static_cast<std::uint8_t>(1 + (stratum_ + rotation) % 7);
}
std::uint8_t nextDiscFor(int row, int column, int wave_depth) const {
const int canonical_column =
mirrored_ ? kBoardSize - 1 - column : column;
const std::uint32_t event =
event_prefix_ + static_cast<std::uint32_t>(wave_depth * 64) +
static_cast<std::uint32_t>(row * kBoardSize + canonical_column);
const int rotation = static_cast<int>(
chanceEventBits(state_, domain_, nonce_, depth_, event) % 7u);
return static_cast<std::uint8_t>(1 + (stratum_ + rotation) % 7);
}
private:
State state_;
std::uint32_t domain_;
std::uint64_t nonce_;
int depth_;
int stratum_;
std::uint32_t event_;
std::uint32_t event_prefix_;
bool mirrored_;
};
template <typename Random>
void resolveCascadeWithChance(Board& board, Random& random,
int starting_depth, std::int64_t& score,
std::vector<Wave>& waves) {
for (int depth = starting_depth;; ++depth) {
int popper_count = 0;
const auto poppers = findPoppers(board, popper_count);
if (popper_count == 0) return;
std::array<bool, kCellCount> popping{};
Board cleared = board;
for (int offset = 0; offset < popper_count; ++offset) {
popping[poppers[offset]] = true;
cleared[poppers[offset]] = kEmpty;
}
std::array<int, kCellCount> reveals{};
int reveal_count = 0;
constexpr std::array<std::array<int, 2>, 4> directions{{
{{-1, 0}}, {{1, 0}}, {{0, -1}}, {{0, 1}},
}};
for (int row = 0; row < kBoardSize; ++row) {
for (int column = 0; column < kBoardSize; ++column) {
const int index = indexOf(row, column);
const std::uint8_t cell = board[index];
if (cell != kSolid && cell != kCracked) continue;
int hits = 0;
for (const auto& direction : directions) {
const int neighbor_row = row + direction[0];
const int neighbor_column = column + direction[1];
if (inside(neighbor_row, neighbor_column) &&
popping[indexOf(neighbor_row, neighbor_column)]) {
++hits;
}
}
if (hits == 0) continue;
const int required = cell == kSolid ? 2 : 1;
if (hits >= required) {
reveals[reveal_count++] = index;
} else {
cleared[index] = kCracked;
}
}
}
for (int offset = 0; offset < reveal_count; ++offset) {
const int reveal_index = reveals[offset];
cleared[reveal_index] = random.nextDiscFor(
reveal_index / kBoardSize, reveal_index % kBoardSize, depth);
}
const std::int64_t points = popper_count * scoreForWave(depth);
score += points;
waves.push_back({depth, popper_count, reveal_count, points});
board = applyGravity(cleared);
}
}
template <typename Random>
bool playMoveWithChance(const State& state, int column, Random& random,
MoveResult& result) {
if (state.game_over) return false;
Board board = state.board;
if (!placeDisc(board, column, state.next_disc)) return false;
result = MoveResult{};
std::int64_t first_score = 0;
resolveCascadeWithChance(board, random, 1, first_score, result.waves);
result.score_delta = first_score;
result.cleared_board = isBoardEmpty(board);
if (result.cleared_board) result.score_delta += kClearBonus;
int level = state.level;
int moves_remaining = state.moves_remaining - 1;
bool game_over = false;
if (moves_remaining == 0) {
Board raised{};
if (!raiseCoveredRow(board, raised)) {
game_over = true;
} else {
result.level_advanced = true;
++level;
moves_remaining = kMovesPerLevel;
result.score_delta += kLevelBonus;
board = raised;
std::int64_t level_score = 0;
const int next_depth =
result.waves.empty() ? 1 : result.waves.back().depth + 1;
resolveCascadeWithChance(board, random, next_depth, level_score,
result.waves);
result.score_delta += level_score;
if (isBoardEmpty(board)) {
result.score_delta += kClearBonus;
result.cleared_board = true;
}
}
}
int legal_count = 0;
legalColumns(board, legal_count);
if (!game_over && legal_count == 0) game_over = true;
result.state.board = board;
result.state.next_disc = game_over ? state.next_disc : random.nextDisc();
result.state.score = state.score + result.score_delta;
result.state.level = level;
result.state.moves_remaining = moves_remaining;
result.state.moves_played = state.moves_played + 1;
result.state.game_over = game_over;
return true;
}
std::array<float, kBoardSize> oneStepActionValues(
const Model& model, const State& source, int reveal_samples,
std::uint32_t domain, std::uint64_t nonce) {
if (reveal_samples < 1 || reveal_samples > 7) {
throw std::invalid_argument("reveal sample count must be in [1, 7]");
}
const CanonicalState canonical = canonicalize(source);
std::array<float, kBoardSize> physical_values{};
physical_values.fill(-std::numeric_limits<float>::infinity());
for (int action = 0; action < kBoardSize; ++action) {
if (!isLegal(canonical.state.board, action)) continue;
double total = 0.0;
for (int sample = 0; sample < reveal_samples; ++sample) {
ChancePackRandom random(canonical.state, action, domain, nonce, 0,
sample);
MoveResult move;
if (!playMoveWithChance(canonical.state, action, random, move)) {
throw std::logic_error("one-step evaluator selected illegal action");
}
const float reward = rewardForMove(move);
total += tdTarget(reward, model.value(move.state),
move.state.game_over);
}
physical_values[physicalAction(action, canonical.mirrored)] =
static_cast<float>(total / reveal_samples);
}
return physical_values;
}
int greedyAction(const Model& model, const State& state, int reveal_samples,
std::uint32_t domain, std::uint64_t nonce) {
const auto values =
oneStepActionValues(model, state, reveal_samples, domain, nonce);
const bool mirrored = canonicalize(state).mirrored;
constexpr std::array<int, kBoardSize> order{{3, 2, 4, 1, 5, 0, 6}};
int selected = -1;
float best = -std::numeric_limits<float>::infinity();
for (int canonical_action : order) {
const int physical = physicalAction(canonical_action, mirrored);
if (values[physical] > best) {
best = values[physical];
selected = physical;
}
}
if (!isLegal(state.board, selected)) {
throw std::logic_error("optimistic n-tuple selected illegal action");
}
return selected;
}
struct Schedule {
UpdateRule rule = UpdateRule::kFixed;
float rate = kLearningRate0;
bool should_promote = false;
bool should_enable_tc = false;
};
Schedule scheduleForTransition(std::uint64_t transitions,
const Model& model) {
Schedule result;
result.should_promote =
transitions >= kPromotionTransition && !model.promoted();
result.should_enable_tc =
transitions >= kTemporalCoherenceTransition &&
!model.temporalCoherenceEnabled();
if (transitions >= kTemporalCoherenceTransition) {
result.rule = UpdateRule::kTemporalCoherence;
result.rate = kTemporalCoherenceRate;
} else if (transitions >= kLearningRateDrop2) {
result.rate = kLearningRate2;
} else if (transitions >= kLearningRateDrop1) {
result.rate = kLearningRate1;
}
return result;
}
// Conventional three-delta forward view: each state receives exactly
// delta_t + lambda*delta_{t+1} + lambda^2*delta_{t+2}. The queue is delayed
// so that checkpoint boundaries can preserve the two not-yet-complete views.
class DelayedForwardTrace {
public:
struct Summary {
std::uint64_t state_updates = 0;
double absolute_credit = 0.0;
double beta_sum = 0.0;
std::uint64_t beta_count = 0;
float last_credit = 0.0f;
};
void observe(const ValueState& state, float td_error, UpdateRule rule,
float rate, Model& model) {
if (!std::isfinite(td_error)) {
throw std::invalid_argument("non-finite TD error");
}
for (Pending& pending : pending_) {
pending.credit += pending.next_coefficient * td_error;
pending.next_coefficient *= kLambda;
++pending.terms;
}
pending_.push_back({state, td_error, kLambda, 1, rule, rate});
if (pending_.front().terms >= kTraceHorizon) applyFront(model);
}
void flush(Model& model) {
while (!pending_.empty()) applyFront(model);
}
bool empty() const { return pending_.empty(); }
std::size_t size() const { return pending_.size(); }
const Summary& summary() const { return summary_; }
void writeCheckpoint(std::ostream& output) const {
writeScalar<std::uint64_t>(output, pending_.size());
for (const Pending& pending : pending_) {
output.write(reinterpret_cast<const char*>(pending.state.board.data()),
static_cast<std::streamsize>(pending.state.board.size()));
if (!output) throw std::runtime_error("failed writing n-tuple checkpoint");
writeScalar<std::uint8_t>(output, pending.state.moves_remaining);
writeScalar<std::uint8_t>(output, pending.state.terminal ? 1u : 0u);
writeScalar<float>(output, pending.credit);
writeScalar<float>(output, pending.next_coefficient);
writeScalar<std::int32_t>(output, pending.terms);
writeScalar<std::uint8_t>(output,
static_cast<std::uint8_t>(pending.rule));
writeScalar<float>(output, pending.rate);
}
writeScalar<std::uint64_t>(output, summary_.state_updates);
writeScalar<double>(output, summary_.absolute_credit);
writeScalar<double>(output, summary_.beta_sum);
writeScalar<std::uint64_t>(output, summary_.beta_count);
writeScalar<float>(output, summary_.last_credit);
}
void readCheckpoint(std::istream& input) {
pending_.clear();
const std::uint64_t count = readScalar<std::uint64_t>(input);
if (count >= static_cast<std::uint64_t>(kTraceHorizon)) {
throw std::runtime_error("invalid pending forward-view queue");
}
for (std::uint64_t index = 0; index < count; ++index) {
Pending pending;
input.read(reinterpret_cast<char*>(pending.state.board.data()),
static_cast<std::streamsize>(pending.state.board.size()));
if (!input) throw std::runtime_error("truncated n-tuple checkpoint");
pending.state.moves_remaining = readScalar<std::uint8_t>(input);
const std::uint8_t terminal = readScalar<std::uint8_t>(input);
pending.credit = readScalar<float>(input);
pending.next_coefficient = readScalar<float>(input);
pending.terms = readScalar<std::int32_t>(input);
const std::uint8_t rule = readScalar<std::uint8_t>(input);
pending.rate = readScalar<float>(input);
if (std::any_of(pending.state.board.begin(), pending.state.board.end(),
[](std::uint8_t cell) { return cell > kCracked; }) ||
pending.state.moves_remaining < 1 ||
pending.state.moves_remaining > kMovesPerLevel || terminal > 1 ||
terminal != 0 || !std::isfinite(pending.credit) ||
!std::isfinite(pending.next_coefficient) ||
!std::isfinite(pending.rate) || pending.rate <= 0.0f ||
pending.terms < 1 || pending.terms >= kTraceHorizon ||
rule > static_cast<std::uint8_t>(UpdateRule::kTemporalCoherence)) {
throw std::runtime_error("invalid pending forward-view entry");
}
pending.state.terminal = false;
pending.rule = static_cast<UpdateRule>(rule);
pending_.push_back(pending);
}
summary_.state_updates = readScalar<std::uint64_t>(input);
summary_.absolute_credit = readScalar<double>(input);
summary_.beta_sum = readScalar<double>(input);
summary_.beta_count = readScalar<std::uint64_t>(input);
summary_.last_credit = readScalar<float>(input);
if (!std::isfinite(summary_.absolute_credit) ||
!std::isfinite(summary_.beta_sum) ||
!std::isfinite(summary_.last_credit) ||
summary_.absolute_credit < 0.0 ||
summary_.beta_count != summary_.state_updates) {
throw std::runtime_error("invalid forward-view summary");
}
}
private:
struct Pending {
ValueState state{};
float credit = 0.0f;
float next_coefficient = kLambda;
int terms = 1;
UpdateRule rule = UpdateRule::kFixed;
float rate = kLearningRate0;
};
void applyFront(Model& model) {
const Pending pending = pending_.front();
pending_.pop_front();
const UpdateReport report =
model.update(pending.state, pending.credit, pending.rate,
pending.rule);
++summary_.state_updates;
summary_.absolute_credit += std::abs(pending.credit);
summary_.beta_sum += report.mean_beta;
++summary_.beta_count;
summary_.last_credit = pending.credit;
}
std::deque<Pending> pending_;
Summary summary_;
};
void applyScheduleBoundary(std::uint64_t transitions, Model& model,
DelayedForwardTrace& trace) {
const Schedule schedule = scheduleForTransition(transitions, model);
if (schedule.should_promote) {
trace.flush(model);
model.promote();
}
if (schedule.should_enable_tc) {
trace.flush(model);
model.enableTemporalCoherence();
}
}
struct SearchOptions {
int maximum_boundaries = 1;
int reveal_samples = 7;
int internal_action_width = 2;
std::uint64_t maximum_work = 100'000;
};
struct SearchDecision {
int action = -1;
float value = -std::numeric_limits<float>::infinity();
int completed_boundaries = 0;
std::uint64_t work = 0;
bool last_iteration_complete = false;
bool full_root = false;
bool used_direct_fallback = true;
};
struct SearchContext {
const Model& model;
const SearchOptions& options;
std::uint64_t nonce = 0;
std::uint64_t work = 0;
bool complete = true;
};
std::uint64_t boundaryWorkUpperBound(const State& state, int boundaries) {
int legal_count = 0;
legalColumns(state.board, legal_count);
const int moves_through_boundary =
state.moves_remaining + (boundaries - 1) * kMovesPerLevel;
const std::uint64_t root_work =
static_cast<std::uint64_t>(legal_count) * 7u;
const std::uint64_t per_internal_move =
static_cast<std::uint64_t>(kBoardSize * 7 +
kFrozenInternalActionWidth * 7);
return root_work +
static_cast<std::uint64_t>(legal_count) *
static_cast<std::uint64_t>(moves_through_boundary - 1) *
per_internal_move;
}
struct ActionBundle {
int action = -1;
float expected_total = -std::numeric_limits<float>::infinity();
float expected_reward = 0.0f;
MoveResult representative{};
};
ActionBundle evaluateActionBundle(const State& canonical, int action,
std::uint32_t domain, int depth,
SearchContext& context) {
std::array<MoveResult, 7> outcomes{};
std::array<float, 7> backed_values{};
double total_value = 0.0;
double total_reward = 0.0;
for (int stratum = 0; stratum < 7; ++stratum) {
if (context.work >= context.options.maximum_work) {
context.complete = false;
return {};
}
ChancePackRandom random(canonical, action, domain, context.nonce, depth,
stratum);
if (!playMoveWithChance(canonical, action, random, outcomes[stratum])) {
throw std::logic_error("chance bundle selected illegal action");
}
++context.work;
const float reward = rewardForMove(outcomes[stratum]);
backed_values[stratum] =
tdTarget(reward, context.model.value(outcomes[stratum].state),
outcomes[stratum].state.game_over);
total_reward += reward;
total_value += backed_values[stratum];
}
ActionBundle result;
result.action = action;
result.expected_total = static_cast<float>(total_value / 7.0);
result.expected_reward = static_cast<float>(total_reward / 7.0);
int representative = 0;
float best_distance =
std::abs(backed_values[0] - result.expected_total);
for (int stratum = 1; stratum < 7; ++stratum) {
const float distance =
std::abs(backed_values[stratum] - result.expected_total);
if (distance < best_distance) {
best_distance = distance;
representative = stratum;
}
}
result.representative = std::move(outcomes[representative]);
return result;
}
std::optional<ActionBundle> selectInternalBundle(const State& source,
int depth,
SearchContext& context) {
const CanonicalState canonical = canonicalize(source);
int legal_count = 0;
const auto legal = legalColumns(canonical.state.board, legal_count);
struct RankedAction {
int action = -1;
float value = -std::numeric_limits<float>::infinity();
};
std::array<RankedAction, kBoardSize> ranked{};
for (int offset = 0; offset < legal_count; ++offset) {
const int action = legal[offset];
const ActionBundle ordering = evaluateActionBundle(
canonical.state, action, kSearchOrderDomain, depth, context);
ranked[offset] = {action, ordering.expected_total};
if (!context.complete) return std::nullopt;
}
constexpr std::array<int, kBoardSize> tie_rank{{5, 3, 1, 0, 2, 4, 6}};
const auto ranks_before = [&](const RankedAction& first,
const RankedAction& second) {
if (first.value != second.value) return first.value > second.value;
return tie_rank[first.action] < tie_rank[second.action];
};
for (int index = 1; index < legal_count; ++index) {
const RankedAction value = ranked[index];
int destination = index;
while (destination > 0 &&
ranks_before(value, ranked[destination - 1])) {
ranked[destination] = ranked[destination - 1];
--destination;
}
ranked[destination] = value;
}
const int admitted =
std::min(context.options.internal_action_width, legal_count);
if (admitted < 1) return std::nullopt;
ActionBundle selected;
for (int index = 0; index < admitted; ++index) {
ActionBundle candidate = evaluateActionBundle(
canonical.state, ranked[index].action, kSearchRevealDomain, depth,
context);
if (!context.complete) return std::nullopt;
if (candidate.expected_total > selected.expected_total ||
(candidate.expected_total == selected.expected_total &&
(selected.action < 0 ||
tie_rank[candidate.action] < tie_rank[selected.action]))) {
selected = std::move(candidate);
}
}
return selected;
}
float rolloutRootAction(const State& canonical, int root_action,
int boundaries, SearchContext& context) {
ActionBundle root = evaluateActionBundle(
canonical, root_action, kSearchRevealDomain, 0, context);
if (!context.complete) return context.model.value(canonical);
float value = root.expected_reward;
State state = root.representative.state;
int remaining =
boundaries - (root.representative.level_advanced ? 1 : 0);
int depth = 1;
while (!state.game_over && remaining > 0) {
const std::optional<ActionBundle> selected =
selectInternalBundle(state, depth, context);
if (!context.complete || !selected.has_value()) {
return context.model.value(canonical);
}
value += selected->expected_reward;
state = selected->representative.state;
if (selected->representative.level_advanced) --remaining;
++depth;
}
if (!state.game_over) value += context.model.value(state);
return value;
}
SearchDecision searchRoot(const State& source, int boundaries,
SearchContext& context) {
const CanonicalState canonical = canonicalize(source);
int legal_count = 0;
const auto legal = legalColumns(canonical.state.board, legal_count);
constexpr std::array<int, kBoardSize> tie_rank{{5, 3, 1, 0, 2, 4, 6}};
int best_action = -1;
float best = -std::numeric_limits<float>::infinity();
for (int offset = 0; offset < legal_count; ++offset) {
const int action = legal[offset];
const float value =
rolloutRootAction(canonical.state, action, boundaries, context);
if (!context.complete) return {};
if (value > best ||
(value == best && best_action >= 0 &&
tie_rank[action] < tie_rank[best_action])) {
best = value;
best_action = action;
}
}
return {physicalAction(best_action, canonical.mirrored), best, boundaries,
context.work, true, true, false};
}
SearchDecision chooseEventBoundaryAction(const Model& model,
const State& source,
const SearchOptions& options,
std::uint64_t nonce = 0) {
if (source.game_over) return {};
if (options.maximum_boundaries < 1 || options.maximum_boundaries > 2 ||
options.reveal_samples != 7 || options.internal_action_width != 2 ||
options.maximum_work < 1) {
throw std::invalid_argument("invalid event-boundary search options");
}
const int fallback = centerFirstMove(source.board);
SearchDecision best;
best.action = fallback;
best.value = model.value(source);
best.work = 0;
best.used_direct_fallback = true;
SearchContext context{model, options, nonce};
for (int boundaries = 1; boundaries <= options.maximum_boundaries;
++boundaries) {
const std::uint64_t bound = boundaryWorkUpperBound(source, boundaries);
if (bound > options.maximum_work - context.work) {
best.work = context.work;
best.last_iteration_complete = false;
break;
}
context.complete = true;
SearchDecision candidate = searchRoot(source, boundaries, context);
if (!context.complete || !candidate.last_iteration_complete ||
!candidate.full_root ||
!isLegal(source.board, candidate.action)) {
best.work = context.work;
best.last_iteration_complete = false;
break;
}
candidate.work = context.work;
candidate.used_direct_fallback = false;
best = candidate;
}
best.work = context.work;
return best;
}
bool laneOpen(SeedUse use) {
switch (use) {
case SeedUse::kTraining:
return kTrainingSeedBegin < kTrainingSeedEnd &&
validSha256Hex(kCompiledTrainingLaneManifestSha256);
case SeedUse::kBurnedStageA:
return kBurnedStageASeedBegin < kBurnedStageASeedEnd;
case SeedUse::kDevelopment:
return kDevelopmentSeedBegin < kDevelopmentSeedEnd &&
validSha256Hex(kCompiledDevelopmentLaneManifestSha256) &&
validSha256Hex(kCompiledStageAQualificationSha256);
}
return false;
}
bool allowedSeed(std::uint32_t seed, SeedUse use) {
const std::uint32_t family = seed >> 24u;
if (family == 0x4du || family == 0x7du || family == 0xd7u) return false;
std::uint32_t begin = 0;
std::uint32_t end = 0;
switch (use) {
case SeedUse::kTraining:
begin = kTrainingSeedBegin;
end = kTrainingSeedEnd;
break;
case SeedUse::kBurnedStageA:
begin = kBurnedStageASeedBegin;
end = kBurnedStageASeedEnd;
break;
case SeedUse::kDevelopment:
begin = kDevelopmentSeedBegin;
end = kDevelopmentSeedEnd;
break;
}
return laneOpen(use) && seed >= begin && seed < end;
}
void requireSeedRange(std::uint32_t begin, std::uint64_t games, SeedUse use) {
if (games < 1 || games > std::numeric_limits<std::uint32_t>::max()) {
throw std::invalid_argument("invalid game-seed range length");
}
const std::uint64_t last =
static_cast<std::uint64_t>(begin) + games - 1u;
if (last > std::numeric_limits<std::uint32_t>::max() ||
!allowedSeed(begin, use) ||
!allowedSeed(static_cast<std::uint32_t>(last), use)) {
throw std::invalid_argument(
"gameplay seed lane is closed pending a preregistered source freeze");
}
}
std::uint64_t peakResidentBytes() {
rusage usage{};
if (getrusage(RUSAGE_SELF, &usage) != 0) return 0;
#if defined(__APPLE__)
return static_cast<std::uint64_t>(usage.ru_maxrss);
#else
return static_cast<std::uint64_t>(usage.ru_maxrss) * 1024u;
#endif
}
constexpr std::array<char, 8> kCheckpointEnvelopeMagic{
{'D', '7', 'C', 'K', 'P', 'T', '3', 0}};
constexpr std::uint64_t kCheckpointHeaderBytes =
kCheckpointEnvelopeMagic.size() + sizeof(std::uint32_t) +
sizeof(std::uint64_t) + sizeof(std::uint64_t);
std::uint64_t checksumStream(std::istream& input, std::uint64_t bytes) {
std::uint64_t hash = 0xcbf2'9ce4'8422'2325ull;
std::array<char, 64 * 1024> buffer{};
while (bytes > 0) {
const std::size_t chunk = static_cast<std::size_t>(
std::min<std::uint64_t>(bytes, buffer.size()));
input.read(buffer.data(), static_cast<std::streamsize>(chunk));
if (!input) throw std::runtime_error("truncated n-tuple checkpoint");
for (std::size_t index = 0; index < chunk; ++index) {
hash ^= static_cast<unsigned char>(buffer[index]);
hash *= 0x0000'0100'0000'01b3ull;
}
bytes -= chunk;
}
return hash;
}
void saveCheckpoint(const std::string& path, const Model& model,
const Progress& progress,
const DelayedForwardTrace& trace,
const TrainingContract& contract) {
if (path.empty()) throw std::invalid_argument("empty checkpoint path");
const std::filesystem::path destination(path);
const std::filesystem::path temporary = destination.string() + ".tmp";
{
std::ofstream output(temporary, std::ios::binary | std::ios::trunc);
if (!output) throw std::runtime_error("could not open checkpoint temp file");
output.write(kCheckpointEnvelopeMagic.data(),
static_cast<std::streamsize>(kCheckpointEnvelopeMagic.size()));
writeScalar<std::uint32_t>(output, kCheckpointFormatVersion);
writeScalar<std::uint64_t>(output, 0u);
writeScalar<std::uint64_t>(output, 0u);
model.writeCheckpoint(output, progress, contract);
trace.writeCheckpoint(output);
output.flush();
if (!output) throw std::runtime_error("failed flushing checkpoint temp file");
}
const std::uint64_t file_bytes = std::filesystem::file_size(temporary);
if (file_bytes <= kCheckpointHeaderBytes) {
throw std::runtime_error("empty n-tuple checkpoint payload");
}
const std::uint64_t payload_bytes = file_bytes - kCheckpointHeaderBytes;
std::uint64_t checksum = 0;
{
std::ifstream input(temporary, std::ios::binary);
if (!input) throw std::runtime_error("could not verify checkpoint temp file");
input.seekg(static_cast<std::streamoff>(kCheckpointHeaderBytes));
checksum = checksumStream(input, payload_bytes);
}
{
std::fstream output(temporary,
std::ios::binary | std::ios::in | std::ios::out);
if (!output) throw std::runtime_error("could not seal checkpoint temp file");
output.seekp(static_cast<std::streamoff>(kCheckpointEnvelopeMagic.size() +
sizeof(std::uint32_t)));
writeScalar<std::uint64_t>(output, payload_bytes);
writeScalar<std::uint64_t>(output, checksum);
output.flush();
if (!output) throw std::runtime_error("failed sealing checkpoint temp file");
}
std::error_code error;
std::filesystem::rename(temporary, destination, error);
if (error) {
std::filesystem::remove(temporary);
throw std::runtime_error("atomic checkpoint rename failed: " +
error.message());
}
}
Progress loadCheckpoint(const std::string& path, Model& model,
DelayedForwardTrace& trace,
TrainingContract& contract) {
std::ifstream input(path, std::ios::binary);
if (!input) throw std::runtime_error("could not open checkpoint for read");
std::array<char, 8> magic{};
input.read(magic.data(), static_cast<std::streamsize>(magic.size()));
const std::uint32_t version = readScalar<std::uint32_t>(input);
const std::uint64_t payload_bytes = readScalar<std::uint64_t>(input);
const std::uint64_t expected_checksum = readScalar<std::uint64_t>(input);
const std::uint64_t file_bytes = std::filesystem::file_size(path);
if (magic != kCheckpointEnvelopeMagic ||
version != kCheckpointFormatVersion ||
payload_bytes == 0 ||
payload_bytes != file_bytes - kCheckpointHeaderBytes) {
throw std::runtime_error("invalid n-tuple checkpoint envelope");
}
const std::streampos payload_position = input.tellg();
if (checksumStream(input, payload_bytes) != expected_checksum) {
throw std::runtime_error("n-tuple checkpoint checksum mismatch");
}
input.clear();
input.seekg(payload_position);
Progress progress = model.readCheckpoint(input, contract);
trace.readCheckpoint(input);
const std::streampos consumed = input.tellg();
if (consumed < payload_position ||
static_cast<std::uint64_t>(consumed - payload_position) !=
payload_bytes || input.peek() != std::char_traits<char>::eof()) {
throw std::runtime_error("trailing bytes in n-tuple checkpoint payload");
}
if (!progress.game_active && !trace.empty()) {
throw std::runtime_error("checkpoint game/trace boundary mismatch");
}
return progress;
}
struct TrainingOptions {
std::uint64_t additional_games = 1;
std::uint64_t stop_after_transitions = kFrozenTrainingTransitions;
int maximum_moves = kFrozenMaximumMoves;
int reveal_samples = kFrozenRevealSamples;
std::uint32_t training_seed_start = 0;
bool training_seed_start_set = false;
std::string checkpoint;
std::string resume;
std::string source_directory;
std::string lane_manifest;
};
struct EvaluationOptions {
std::uint64_t games = 64;
int maximum_moves = kFrozenMaximumMoves;
int reveal_samples = kFrozenRevealSamples;
int event_boundaries = kFrozenEventBoundaries;
int internal_action_width = kFrozenInternalActionWidth;
std::uint64_t maximum_work = kFrozenMaximumSearchWork;
std::uint32_t seed_start = 0;
bool seed_start_set = false;
EvaluationStage stage = EvaluationStage::kFinalDevelopment;
bool stage_set = false;
std::string checkpoint;
std::string source_directory;
std::string lane_manifest;
std::string qualification;
std::string qualification_output;
};
std::uint64_t parseUnsigned(std::string_view text, std::string_view name) {
std::size_t consumed = 0;
const std::string copy(text);
const unsigned long long value = std::stoull(copy, &consumed, 0);
if (consumed != copy.size()) {
throw std::invalid_argument("invalid " + std::string(name));
}
return static_cast<std::uint64_t>(value);
}
int parseInt(std::string_view text, std::string_view name) {
const std::uint64_t value = parseUnsigned(text, name);
if (value > static_cast<std::uint64_t>(std::numeric_limits<int>::max())) {
throw std::invalid_argument("out-of-range " + std::string(name));
}
return static_cast<int>(value);
}
std::uint32_t parseSeed(std::string_view text, std::string_view name) {
const std::uint64_t value = parseUnsigned(text, name);
if (value > std::numeric_limits<std::uint32_t>::max()) {
throw std::invalid_argument("out-of-range " + std::string(name));
}
return static_cast<std::uint32_t>(value);
}
TrainingOptions parseTrainingOptions(int argc, char** argv, int begin) {
TrainingOptions result;
for (int index = begin; index < argc; index += 2) {
if (index + 1 >= argc) throw std::invalid_argument("missing option value");
const std::string_view option(argv[index]);
const std::string_view value(argv[index + 1]);
if (option == "--games") {
result.additional_games = parseUnsigned(value, "games");
} else if (option == "--stop-after-transitions") {
result.stop_after_transitions =
parseUnsigned(value, "stop-after-transitions");
} else if (option == "--max-moves") {
result.maximum_moves = parseInt(value, "max-moves");
} else if (option == "--reveal-samples") {
result.reveal_samples = parseInt(value, "reveal-samples");
} else if (option == "--training-seed-start") {
result.training_seed_start = parseSeed(value, "training-seed-start");
result.training_seed_start_set = true;
} else if (option == "--checkpoint") {
result.checkpoint = value;
} else if (option == "--resume") {
result.resume = value;
} else if (option == "--source-directory") {
result.source_directory = value;
} else if (option == "--lane-manifest") {
result.lane_manifest = value;
} else {
throw std::invalid_argument("unknown training option " +
std::string(option));
}
}
return result;
}
EvaluationOptions parseEvaluationOptions(int argc, char** argv, int begin) {
EvaluationOptions result;
for (int index = begin; index < argc; index += 2) {
if (index + 1 >= argc) throw std::invalid_argument("missing option value");
const std::string_view option(argv[index]);
const std::string_view value(argv[index + 1]);
if (option == "--games") {
result.games = parseUnsigned(value, "games");
} else if (option == "--max-moves") {
result.maximum_moves = parseInt(value, "max-moves");
} else if (option == "--reveal-samples") {
result.reveal_samples = parseInt(value, "reveal-samples");
} else if (option == "--event-boundaries") {
result.event_boundaries = parseInt(value, "event-boundaries");
} else if (option == "--internal-action-width") {
result.internal_action_width =
parseInt(value, "internal-action-width");
} else if (option == "--max-work") {
result.maximum_work = parseUnsigned(value, "max-work");
} else if (option == "--seed-start") {
result.seed_start = parseSeed(value, "seed-start");
result.seed_start_set = true;
} else if (option == "--evaluation-stage") {
if (value == "burned-stage-a") {
result.stage = EvaluationStage::kBurnedStageA;
} else if (value == "final-development") {
result.stage = EvaluationStage::kFinalDevelopment;
} else {
throw std::invalid_argument("unknown evaluation stage");
}
result.stage_set = true;
} else if (option == "--checkpoint") {
result.checkpoint = value;
} else if (option == "--source-directory") {
result.source_directory = value;
} else if (option == "--lane-manifest") {
result.lane_manifest = value;
} else if (option == "--qualification") {
result.qualification = value;
} else if (option == "--qualification-output") {
result.qualification_output = value;
} else {
throw std::invalid_argument("unknown evaluation option " +
std::string(option));
}
}
return result;
}
int train(const TrainingOptions& options, std::ostream& output) {
if (options.additional_games < 1 ||
options.maximum_moves != kFrozenMaximumMoves ||
options.reveal_samples != kFrozenRevealSamples ||
options.stop_after_transitions < 1 ||
options.stop_after_transitions > kFrozenTrainingTransitions ||
options.checkpoint.empty() || options.source_directory.empty() ||
options.lane_manifest.empty()) {
throw std::invalid_argument("invalid optimistic n-tuple training options");
}
const std::filesystem::path source_directory(options.source_directory);
const std::array<std::filesystem::path, 6> protected_paths{{
std::filesystem::path(options.lane_manifest),
source_directory / "approaches/ntuple-rl/optimistic-phase/optimistic-phase-ntuple.cpp",
source_directory / "src/core/native/engine.hpp",
source_directory / "approaches/fair-expectimax/reference/fair-only-depth4.cpp",
source_directory / "approaches/fair-expectimax/reference/fair-only-horizon.cpp",
source_directory / "src/core/native/public-behavior.hpp",
}};
const std::filesystem::path checkpoint_output(options.checkpoint);
const std::filesystem::path checkpoint_temporary =
checkpoint_output.string() + ".tmp";
for (const std::filesystem::path& protected_path : protected_paths) {
if (sameFilesystemTarget(checkpoint_output, protected_path) ||
sameFilesystemTarget(checkpoint_temporary, protected_path)) {
throw std::invalid_argument("checkpoint output aliases a protected input");
}
}
const ProvenanceHashes provenance =
verifyCompiledProvenance(options.source_directory);
const std::string training_lane_manifest_sha256 =
verifyLaneManifest(options.lane_manifest, SeedUse::kTraining);
Model model;
Progress progress;
DelayedForwardTrace trace;
TrainingContract contract;
if (!options.resume.empty()) {
progress = loadCheckpoint(options.resume, model, trace, contract);
if (options.training_seed_start_set &&
options.training_seed_start != progress.training_seed_start) {
throw std::invalid_argument("resume seed start differs from checkpoint");
}
if (contract != TrainingContract{}) {
throw std::invalid_argument("resume training contract differs");
}
} else {
if (!options.training_seed_start_set) {
throw std::invalid_argument("new training requires --training-seed-start");
}
if (options.training_seed_start != kTrainingSeedBegin) {
throw std::invalid_argument("training must start at the fit-lane begin");
}
progress.training_seed_start = options.training_seed_start;
}
if (progress.training_finalized ||
options.stop_after_transitions < progress.transitions) {
throw std::invalid_argument("cannot overtrain or rewind a checkpoint");
}
const std::uint64_t first_game = progress.completed_games;
if (first_game > std::numeric_limits<std::uint32_t>::max()) {
throw std::invalid_argument("training game cursor overflow");
}
const std::uint64_t first_seed_wide = progress.game_active
? progress.active_game_seed
: static_cast<std::uint64_t>(progress.training_seed_start) + first_game;
if (first_seed_wide > std::numeric_limits<std::uint32_t>::max()) {
throw std::invalid_argument("training seed cursor overflow");
}
requireSeedRange(static_cast<std::uint32_t>(first_seed_wide),
options.additional_games, SeedUse::kTraining);
const auto started = std::chrono::steady_clock::now();
const std::uint64_t score_before = progress.cumulative_score;
const std::uint64_t moves_before = progress.cumulative_moves;
std::uint64_t games_run = 0;
while (games_run < options.additional_games &&
progress.transitions < options.stop_after_transitions) {
if (!progress.game_active) {
const std::uint64_t seed_wide =
static_cast<std::uint64_t>(progress.training_seed_start) +
progress.completed_games;
if (seed_wide > std::numeric_limits<std::uint32_t>::max()) {
throw std::runtime_error("training seed overflow");
}
progress.active_game_seed = static_cast<std::uint32_t>(seed_wide);
if (!allowedSeed(progress.active_game_seed, SeedUse::kTraining)) {
throw std::runtime_error("training escaped frozen seed lane");
}
progress.active_state = initialHeadlessState(progress.active_game_seed);
progress.game_active = true;
if (!trace.empty()) {
throw std::logic_error("new game started with a pending forward view");
}
}
State& state = progress.active_state;
const std::uint32_t seed = progress.active_game_seed;
while (!state.game_over && state.moves_played < options.maximum_moves &&
progress.transitions < options.stop_after_transitions) {
applyScheduleBoundary(progress.transitions, model, trace);
const Schedule schedule =
scheduleForTransition(progress.transitions, model);
const ValueState previous = valueState(state);
const float prediction = model.value(previous);
const int action = greedyAction(
model, state, options.reveal_samples, kPolicyRevealDomain,
progress.transitions);
MoveResult move;
if (!playHeadlessMove(state, seed, action, move)) {
throw std::logic_error("training policy selected illegal action");
}
if (!state.game_over &&
state.next_disc != headlessDisc(seed, state.moves_played)) {
throw std::logic_error("headless visible-disc stream mismatch");
}
const float target =
tdTarget(rewardForMove(move), model.value(state), state.game_over);
trace.observe(previous, target - prediction, schedule.rule,
schedule.rate, model);
++progress.transitions;
}
const bool game_finished =
state.game_over || state.moves_played >= options.maximum_moves;
const bool frozen_limit =
progress.transitions == kFrozenTrainingTransitions;
if (game_finished || frozen_limit) {
trace.flush(model);
progress.cumulative_score += static_cast<std::uint64_t>(state.score);
progress.cumulative_moves +=
static_cast<std::uint64_t>(state.moves_played);
if (!state.game_over) ++progress.censored_training_games;
progress.game_active = false;
++progress.completed_games;
++games_run;
if (frozen_limit) progress.training_finalized = true;
} else {
break;
}
}
applyScheduleBoundary(progress.transitions, model, trace);
if (progress.transitions == kFrozenTrainingTransitions) {
if (progress.game_active) {
throw std::logic_error("frozen training limit retained an active game");
}
progress.training_finalized = true;
}
saveCheckpoint(options.checkpoint, model, progress, trace, contract);
const double seconds = std::chrono::duration<double>(
std::chrono::steady_clock::now() - started)
.count();
const auto& trace_summary = trace.summary();
output << std::fixed << std::setprecision(6)
<< "OPTIMISTIC_PHASE_NTUPLE_TRAIN {\"games\":" << games_run
<< ",\"transitions\":" << progress.transitions
<< ",\"stage\":\""
<< (model.promoted() ? "phase" : "pooled") << "\""
<< ",\"temporalCoherence\":"
<< (model.temporalCoherenceEnabled() ? "true" : "false")
<< ",\"trainingSourceSha256\":\"" << provenance.source
<< "\",\"trainingEngineSha256\":\"" << provenance.engine
<< "\",\"trainingCorrectedD4Sha256\":\""
<< provenance.corrected_d4
<< "\",\"trainingCorrectedD4LeafSha256\":\""
<< provenance.corrected_d4_leaf
<< "\",\"trainingCfpiBehaviorSha256\":\""
<< provenance.cfpi_behavior << "\""
<< ",\"trainingLaneManifestSha256\":\""
<< training_lane_manifest_sha256 << "\""
<< ",\"meanScore\":"
<< (games_run > 0
? static_cast<double>(progress.cumulative_score -
score_before) /
games_run
: 0.0)
<< ",\"meanMoves\":"
<< (games_run > 0
? static_cast<double>(progress.cumulative_moves -
moves_before) /
games_run
: 0.0)
<< ",\"stateUpdates\":" << trace_summary.state_updates
<< ",\"meanAbsoluteTraceCredit\":"
<< (trace_summary.state_updates > 0
? trace_summary.absolute_credit /
trace_summary.state_updates
: 0.0)
<< ",\"meanTcBeta\":"
<< (trace_summary.beta_count > 0
? trace_summary.beta_sum / trace_summary.beta_count
: 0.0)
<< ",\"seconds\":" << seconds
<< ",\"parameterBytes\":" << model.parameterBytes()
<< ",\"gameActive\":"
<< (progress.game_active ? "true" : "false")
<< ",\"trainingFinalized\":"
<< (progress.training_finalized ? "true" : "false")
<< ",\"pendingForwardViews\":" << trace.size()
<< ",\"censoredTrainingGames\":"
<< progress.censored_training_games
<< ",\"peakResidentBytes\":" << peakResidentBytes()
<< ",\"checkpoint\":\"" << options.checkpoint << "\"}\n";
return 0;
}
enum class PolicyKind : std::uint8_t { kDirect = 0, kBoundarySearch = 1 };
struct PolicyGameResult {
std::uint32_t seed = 0;
std::int64_t score = 0;
int moves = 0;
bool censored = false;
std::uint64_t numbered_cleared = 0;
std::uint64_t covered_revealed = 0;
std::uint64_t chain_depth_sum = 0;
int maximum_chain_depth = 0;
std::uint64_t work = 0;
std::string checksum;
};
std::string resultChecksum(const PolicyGameResult& result,
const Board& final_board) {
std::ostringstream canonical;
canonical << result.seed << ':' << result.score << ':' << result.moves << ':'
<< (result.censored ? 1 : 0) << ':' << result.numbered_cleared
<< ':' << result.covered_revealed << ':'
<< result.chain_depth_sum << ':' << result.maximum_chain_depth
<< ':' << result.work << ':' << serializeBoard(final_board);
return sha256(canonical.str());
}
PolicyGameResult runPolicyGame(const Model& model, std::uint32_t seed,
PolicyKind policy,
const EvaluationOptions& options) {
State state = initialHeadlessState(seed);
PolicyGameResult result;
result.seed = seed;
SearchOptions search_options{options.event_boundaries,
options.reveal_samples,
options.internal_action_width,
options.maximum_work};
while (!state.game_over && state.moves_played < options.maximum_moves) {
int action = -1;
if (policy == PolicyKind::kDirect) {
action = greedyAction(model, state, options.reveal_samples,
kPolicyRevealDomain, 0);
} else {
const SearchDecision decision =
chooseEventBoundaryAction(model, state, search_options, 0);
if (!decision.last_iteration_complete ||
decision.completed_boundaries != options.event_boundaries ||
!decision.full_root || decision.used_direct_fallback) {
throw std::runtime_error(
"frozen boundary search failed its fixed-work completion proof");
}
action = decision.action;
result.work += decision.work;
}
MoveResult move;
if (!playHeadlessMove(state, seed, action, move)) {
throw std::logic_error("evaluation policy selected illegal action");
}
int move_chain = 0;
for (const Wave& wave : move.waves) {
result.numbered_cleared += static_cast<std::uint64_t>(wave.cleared);
result.covered_revealed += static_cast<std::uint64_t>(wave.revealed);
move_chain = std::max(move_chain, wave.depth);
}
result.chain_depth_sum += static_cast<std::uint64_t>(move_chain);
result.maximum_chain_depth =
std::max(result.maximum_chain_depth, move_chain);
}
result.score = state.score;
result.moves = state.moves_played;
result.censored = !state.game_over;
result.checksum = resultChecksum(result, state.board);
return result;
}
PolicyGameResult runCorrectedD4Game(std::uint32_t seed) {
State state = initialHeadlessState(seed);
PolicyGameResult result;
result.seed = seed;
while (!state.game_over &&
state.moves_played < drop7::fair_only_depth4::kMaximumMoves) {
const auto decision = drop7::fair_only_depth4::chooseDepth4Action(state);
if (!decision.complete || decision.completed_depth != 4 ||
!isLegal(state.board, decision.action)) {
throw std::runtime_error("corrected fair-D4 comparator did not complete");
}
result.work += decision.work;
MoveResult move;
if (!playHeadlessMove(state, seed, decision.action, move)) {
throw std::logic_error("corrected fair-D4 selected illegal action");
}
int move_chain = 0;
for (const Wave& wave : move.waves) {
result.numbered_cleared += static_cast<std::uint64_t>(wave.cleared);
result.covered_revealed += static_cast<std::uint64_t>(wave.revealed);
move_chain = std::max(move_chain, wave.depth);
}
result.chain_depth_sum += static_cast<std::uint64_t>(move_chain);
result.maximum_chain_depth =
std::max(result.maximum_chain_depth, move_chain);
}
result.score = state.score;
result.moves = state.moves_played;
result.censored = !state.game_over;
result.checksum = resultChecksum(result, state.board);
return result;
}
double empiricalQuantile(std::vector<double> values, double probability) {
if (values.empty() || probability < 0.0 || probability > 1.0) {
throw std::invalid_argument("invalid empirical quantile");
}
std::sort(values.begin(), values.end());
const double position = probability * (values.size() - 1);
const std::size_t lower = static_cast<std::size_t>(position);
const std::size_t upper = std::min(lower + 1, values.size() - 1);
const double fraction = position - static_cast<double>(lower);
return values[lower] * (1.0 - fraction) + values[upper] * fraction;
}
double arithmeticMean(const std::vector<double>& values) {
if (values.empty()) throw std::invalid_argument("mean of empty sample");
return std::accumulate(values.begin(), values.end(), 0.0) /
static_cast<double>(values.size());
}
double studentCritical(std::size_t games) {
if (games == 64) return 1.669013;
if (games == 256) return 1.650851;
throw std::invalid_argument("no frozen one-sided t critical for cohort");
}
double studentLower95(const std::vector<double>& values) {
const double mean = arithmeticMean(values);
double squared = 0.0;
for (double value : values) squared += (value - mean) * (value - mean);
const double deviation =
std::sqrt(squared / static_cast<double>(values.size() - 1));
return mean - studentCritical(values.size()) * deviation /
std::sqrt(static_cast<double>(values.size()));
}
double bootstrapLower95(const std::vector<double>& values,
int replicates = 100'000) {
if (values.empty() || replicates < 1) {
throw std::invalid_argument("invalid whole-game bootstrap");
}
Mulberry32 random(0xd7b0'057au);
std::vector<double> means(static_cast<std::size_t>(replicates));
for (double& mean : means) {
double total = 0.0;
for (std::size_t draw = 0; draw < values.size(); ++draw) {
const std::size_t index = static_cast<std::size_t>(
(static_cast<std::uint64_t>(random.nextBits()) * values.size()) >>
32u);
total += values[index];
}
mean = total / static_cast<double>(values.size());
}
std::sort(means.begin(), means.end());
return means[static_cast<std::size_t>(
std::floor(0.05 * static_cast<double>(means.size() - 1)))];
}
struct EvaluationSummary {
double mean_score = 0.0;
double median_score = 0.0;
double lower_quartile_score = 0.0;
double minimum_score = 0.0;
double mean_moves = 0.0;
double lower_quartile_moves = 0.0;
double mean_work = 0.0;
double numbered_clears_per_move = 0.0;
double covered_reveals_per_move = 0.0;
double mean_chain_depth = 0.0;
int maximum_chain_depth = 0;
int censored = 0;
double bootstrap_lower95 = 0.0;
double student_t_lower95 = 0.0;
};
EvaluationSummary summarizeGames(
const std::vector<PolicyGameResult>& games) {
if (games.size() != 64 && games.size() != 256) {
throw std::invalid_argument("summary requires a frozen cohort size");
}
std::vector<double> scores;
std::vector<double> moves;
scores.reserve(games.size());
moves.reserve(games.size());
std::uint64_t total_moves = 0;
std::uint64_t total_clears = 0;
std::uint64_t total_reveals = 0;
std::uint64_t total_chain = 0;
std::uint64_t total_work = 0;
EvaluationSummary result;
for (const PolicyGameResult& game : games) {
scores.push_back(static_cast<double>(game.score));
moves.push_back(static_cast<double>(game.moves));
total_moves += static_cast<std::uint64_t>(game.moves);
total_clears += game.numbered_cleared;
total_reveals += game.covered_revealed;
total_chain += game.chain_depth_sum;
total_work += game.work;
result.maximum_chain_depth =
std::max(result.maximum_chain_depth, game.maximum_chain_depth);
result.censored += game.censored ? 1 : 0;
}
result.mean_score = arithmeticMean(scores);
result.median_score = empiricalQuantile(scores, 0.5);
result.lower_quartile_score = empiricalQuantile(scores, 0.25);
result.minimum_score = *std::min_element(scores.begin(), scores.end());
result.mean_moves = arithmeticMean(moves);
result.lower_quartile_moves = empiricalQuantile(moves, 0.25);
result.mean_work = static_cast<double>(total_work) / games.size();
if (total_moves > 0) {
result.numbered_clears_per_move =
static_cast<double>(total_clears) / total_moves;
result.covered_reveals_per_move =
static_cast<double>(total_reveals) / total_moves;
result.mean_chain_depth = static_cast<double>(total_chain) / total_moves;
}
result.bootstrap_lower95 = bootstrapLower95(scores);
result.student_t_lower95 = studentLower95(scores);
return result;
}
std::vector<double> pairedValues(
const std::vector<PolicyGameResult>& candidate,
const std::vector<PolicyGameResult>& comparator, bool score) {
if (candidate.size() != comparator.size()) {
throw std::invalid_argument("paired cohorts differ in size");
}
std::vector<double> result(candidate.size());
for (std::size_t index = 0; index < candidate.size(); ++index) {
if (candidate[index].seed != comparator[index].seed) {
throw std::invalid_argument("paired cohorts differ in seed order");
}
result[index] = score
? static_cast<double>(candidate[index].score - comparator[index].score)
: static_cast<double>(candidate[index].moves - comparator[index].moves);
}
return result;
}
double halfMean(const std::vector<PolicyGameResult>& games,
std::size_t begin, bool score) {
double total = 0.0;
for (std::size_t index = begin; index < begin + 32; ++index) {
total += score ? static_cast<double>(games[index].score)
: static_cast<double>(games[index].moves);
}
return total / 32.0;
}
void writeSummaryJson(std::ostream& output, const EvaluationSummary& summary) {
output << "{\"meanScore\":" << summary.mean_score
<< ",\"medianScore\":" << summary.median_score
<< ",\"lowerQuartileScore\":" << summary.lower_quartile_score
<< ",\"minimumScore\":" << summary.minimum_score
<< ",\"meanMoves\":" << summary.mean_moves
<< ",\"lowerQuartileMoves\":" << summary.lower_quartile_moves
<< ",\"meanWorkPerGame\":" << summary.mean_work
<< ",\"numberedClearsPerMove\":"
<< summary.numbered_clears_per_move
<< ",\"coveredRevealsPerMove\":"
<< summary.covered_reveals_per_move
<< ",\"meanChainDepthPerMove\":" << summary.mean_chain_depth
<< ",\"maximumChainDepth\":" << summary.maximum_chain_depth
<< ",\"censored\":" << summary.censored
<< ",\"bootstrapOneSidedLower95\":"
<< summary.bootstrap_lower95
<< ",\"studentTOneSidedLower95\":"
<< summary.student_t_lower95 << '}';
}
void writeGamesJson(std::ostream& output,
const std::vector<PolicyGameResult>& games) {
output << '[';
for (std::size_t index = 0; index < games.size(); ++index) {
if (index != 0) output << ',';
const PolicyGameResult& game = games[index];
output << "{\"seed\":\"0x" << std::hex << std::setw(8)
<< std::setfill('0') << game.seed << std::dec << std::setfill(' ')
<< "\",\"score\":" << game.score << ",\"moves\":" << game.moves
<< ",\"censored\":" << (game.censored ? "true" : "false")
<< ",\"numberedCleared\":" << game.numbered_cleared
<< ",\"coveredRevealed\":" << game.covered_revealed
<< ",\"chainDepthSum\":" << game.chain_depth_sum
<< ",\"maximumChainDepth\":" << game.maximum_chain_depth
<< ",\"work\":" << game.work << ",\"checksum\":\""
<< game.checksum << "\"}";
}
output << ']';
}
std::string compilerIdentity() {
#if defined(__clang__)
return std::string("clang ") + __clang_version__;
#elif defined(__GNUC__)
return std::string("gcc ") + __VERSION__;
#else
return "unknown-cxx20-compiler";
#endif
}
bool passesStageAAbsoluteGate(double mean_score, double mean_moves) {
return mean_score >= 300'000.0 && mean_moves >= 90.0;
}
bool passesOrderedHalfGate(double direct_score, double direct_moves,
double search_score, double search_moves) {
return search_score > direct_score && search_moves > direct_moves;
}
bool requiresCorrectedD4Comparison(std::uint64_t transitions) {
return transitions == kFrozenTrainingTransitions;
}
bool passesCorrectedD4Gate(double search_score, double search_moves,
double d4_score, double d4_moves,
double paired_score_lower95,
double paired_moves_lower95) {
return search_score >= d4_score && search_moves >= d4_moves &&
paired_score_lower95 >= 0.0 && paired_moves_lower95 >= 0.0;
}
bool passesFinalDevelopmentGate(double mean_score,
double bootstrap_lower95) {
return mean_score > 1'050'000.0 && bootstrap_lower95 > 1'000'000.0;
}
bool eligibleForFutureDevelopment(std::uint64_t transitions,
bool absolute_gate, bool half_gate,
bool corrected_d4_gate) {
return requiresCorrectedD4Comparison(transitions) && absolute_gate &&
half_gate && corrected_d4_gate;
}
struct QualificationArtifact {
std::string training_source_sha256;
std::string engine_sha256;
std::string corrected_d4_sha256;
std::string corrected_d4_leaf_sha256;
std::string cfpi_behavior_sha256;
std::string training_lane_manifest_sha256;
std::string checkpoint_sha256;
std::string model_sha256;
std::string ordered_results_sha256;
std::uint64_t training_transitions = 0;
std::uint32_t seed_start = 0;
std::uint64_t games = 0;
double search_mean_score = 0.0;
double search_mean_moves = 0.0;
std::array<double, 2> direct_half_scores{};
std::array<double, 2> direct_half_moves{};
std::array<double, 2> search_half_scores{};
std::array<double, 2> search_half_moves{};
double corrected_d4_mean_score = 0.0;
double corrected_d4_mean_moves = 0.0;
double paired_d4_score_lower95 = 0.0;
double paired_d4_moves_lower95 = 0.0;
};
void writeQualificationArtifact(const std::filesystem::path& path,
const QualificationArtifact& artifact) {
if (path.empty()) throw std::invalid_argument("empty qualification path");
const std::array<std::string_view, 9> hashes{{
artifact.training_source_sha256,
artifact.engine_sha256,
artifact.corrected_d4_sha256,
artifact.corrected_d4_leaf_sha256,
artifact.cfpi_behavior_sha256,
artifact.training_lane_manifest_sha256,
artifact.checkpoint_sha256,
artifact.model_sha256,
artifact.ordered_results_sha256,
}};
if (std::any_of(hashes.begin(), hashes.end(),
[](std::string_view value) {
return !validSha256Hex(value);
})) {
throw std::invalid_argument("invalid qualification provenance hash");
}
const std::filesystem::path temporary = path.string() + ".tmp";
{
std::ofstream output(temporary, std::ios::binary | std::ios::trunc);
if (!output) {
throw std::runtime_error("could not open qualification temp file");
}
constexpr std::array<char, 8> magic{
{'D', '7', 'Q', 'U', 'A', 'L', '1', 0}};
output.write(magic.data(), static_cast<std::streamsize>(magic.size()));
writeScalar<std::uint32_t>(output, 1u);
writeCheckpointHash(output, artifact.training_source_sha256);
writeCheckpointHash(output, artifact.engine_sha256);
writeCheckpointHash(output, artifact.corrected_d4_sha256);
writeCheckpointHash(output, artifact.corrected_d4_leaf_sha256);
writeCheckpointHash(output, artifact.cfpi_behavior_sha256);
writeCheckpointHash(output, artifact.training_lane_manifest_sha256);
writeCheckpointHash(output, artifact.checkpoint_sha256);
writeCheckpointHash(output, artifact.model_sha256);
writeCheckpointHash(output, artifact.ordered_results_sha256);
writeScalar<std::uint64_t>(output, artifact.training_transitions);
writeScalar<std::uint32_t>(output, artifact.seed_start);
writeScalar<std::uint64_t>(output, artifact.games);
writeScalar<double>(output, artifact.search_mean_score);
writeScalar<double>(output, artifact.search_mean_moves);
for (double value : artifact.direct_half_scores) {
writeScalar<double>(output, value);
}
for (double value : artifact.direct_half_moves) {
writeScalar<double>(output, value);
}
for (double value : artifact.search_half_scores) {
writeScalar<double>(output, value);
}
for (double value : artifact.search_half_moves) {
writeScalar<double>(output, value);
}
writeScalar<double>(output, artifact.corrected_d4_mean_score);
writeScalar<double>(output, artifact.corrected_d4_mean_moves);
writeScalar<double>(output, artifact.paired_d4_score_lower95);
writeScalar<double>(output, artifact.paired_d4_moves_lower95);
output.flush();
if (!output) throw std::runtime_error("failed writing qualification file");
}
std::error_code error;
std::filesystem::rename(temporary, path, error);
if (error) {
std::filesystem::remove(temporary);
throw std::runtime_error("atomic qualification rename failed: " +
error.message());
}
}
QualificationArtifact readQualificationArtifact(
const std::filesystem::path& path) {
const std::string bytes = readWholeFile(path);
std::istringstream input(bytes, std::ios::in | std::ios::binary);
std::array<char, 8> magic{};
input.read(magic.data(), static_cast<std::streamsize>(magic.size()));
constexpr std::array<char, 8> expected{
{'D', '7', 'Q', 'U', 'A', 'L', '1', 0}};
if (!input || magic != expected || readScalar<std::uint32_t>(input) != 1u) {
throw std::runtime_error("invalid Stage-A qualification artifact");
}
QualificationArtifact result;
result.training_source_sha256 = readCheckpointHash(input);
result.engine_sha256 = readCheckpointHash(input);
result.corrected_d4_sha256 = readCheckpointHash(input);
result.corrected_d4_leaf_sha256 = readCheckpointHash(input);
result.cfpi_behavior_sha256 = readCheckpointHash(input);
result.training_lane_manifest_sha256 = readCheckpointHash(input);
result.checkpoint_sha256 = readCheckpointHash(input);
result.model_sha256 = readCheckpointHash(input);
result.ordered_results_sha256 = readCheckpointHash(input);
result.training_transitions = readScalar<std::uint64_t>(input);
result.seed_start = readScalar<std::uint32_t>(input);
result.games = readScalar<std::uint64_t>(input);
result.search_mean_score = readScalar<double>(input);
result.search_mean_moves = readScalar<double>(input);
for (double& value : result.direct_half_scores) {
value = readScalar<double>(input);
}
for (double& value : result.direct_half_moves) {
value = readScalar<double>(input);
}
for (double& value : result.search_half_scores) {
value = readScalar<double>(input);
}
for (double& value : result.search_half_moves) {
value = readScalar<double>(input);
}
result.corrected_d4_mean_score = readScalar<double>(input);
result.corrected_d4_mean_moves = readScalar<double>(input);
result.paired_d4_score_lower95 = readScalar<double>(input);
result.paired_d4_moves_lower95 = readScalar<double>(input);
if (input.peek() != std::char_traits<char>::eof()) {
throw std::runtime_error("trailing bytes in qualification artifact");
}
return result;
}
bool qualificationMatchesCheckpoint(
const QualificationArtifact& artifact, const TrainingContract& contract,
std::string_view checkpoint_sha256, std::string_view model_sha256) {
const std::array<std::string_view, 11> hashes{{
artifact.training_source_sha256,
artifact.engine_sha256,
artifact.corrected_d4_sha256,
artifact.corrected_d4_leaf_sha256,
artifact.cfpi_behavior_sha256,
artifact.training_lane_manifest_sha256,
artifact.checkpoint_sha256,
artifact.model_sha256,
artifact.ordered_results_sha256,
checkpoint_sha256,
model_sha256,
}};
if (std::any_of(hashes.begin(), hashes.end(),
[](std::string_view value) {
return !validSha256Hex(value);
})) {
return false;
}
const std::array<double, 14> numeric{{
artifact.search_mean_score,
artifact.search_mean_moves,
artifact.direct_half_scores[0],
artifact.direct_half_scores[1],
artifact.direct_half_moves[0],
artifact.direct_half_moves[1],
artifact.search_half_scores[0],
artifact.search_half_scores[1],
artifact.search_half_moves[0],
artifact.search_half_moves[1],
artifact.corrected_d4_mean_score,
artifact.corrected_d4_mean_moves,
artifact.paired_d4_score_lower95,
artifact.paired_d4_moves_lower95,
}};
if (std::any_of(numeric.begin(), numeric.end(),
[](double value) { return !std::isfinite(value); })) {
return false;
}
bool halves_pass = true;
for (std::size_t half = 0; half < 2; ++half) {
halves_pass = halves_pass &&
passesOrderedHalfGate(
artifact.direct_half_scores[half],
artifact.direct_half_moves[half],
artifact.search_half_scores[half],
artifact.search_half_moves[half]);
}
return artifact.training_transitions == kFrozenTrainingTransitions &&
artifact.seed_start == kBurnedStageASeedBegin &&
artifact.games == 64 &&
artifact.training_source_sha256 == contract.source_sha256 &&
artifact.engine_sha256 == contract.engine_sha256 &&
artifact.corrected_d4_sha256 == contract.corrected_d4_sha256 &&
artifact.corrected_d4_leaf_sha256 ==
contract.corrected_d4_leaf_sha256 &&
artifact.cfpi_behavior_sha256 == contract.cfpi_behavior_sha256 &&
artifact.training_lane_manifest_sha256 ==
contract.training_lane_manifest_sha256 &&
artifact.checkpoint_sha256 == checkpoint_sha256 &&
artifact.model_sha256 == model_sha256 &&
artifact.corrected_d4_mean_score == 176'925.25 &&
artifact.corrected_d4_mean_moves == 116.375 &&
passesStageAAbsoluteGate(artifact.search_mean_score,
artifact.search_mean_moves) &&
halves_pass &&
passesCorrectedD4Gate(
artifact.search_mean_score, artifact.search_mean_moves,
artifact.corrected_d4_mean_score,
artifact.corrected_d4_mean_moves,
artifact.paired_d4_score_lower95,
artifact.paired_d4_moves_lower95);
}
bool developmentAdmission(bool compiled_lane_open,
bool lane_manifest_verified,
bool qualification_token_verified,
bool qualification_matches_checkpoint) {
return compiled_lane_open && lane_manifest_verified &&
qualification_token_verified && qualification_matches_checkpoint;
}
bool evaluationCheckpointEligible(const Progress& progress,
const DelayedForwardTrace& trace,
EvaluationStage stage) {
if (progress.transitions > kFrozenTrainingTransitions) return false;
if (stage == EvaluationStage::kBurnedStageA) {
if (progress.transitions == kBurnedStageATransitions) return true;
if (progress.transitions != kFrozenTrainingTransitions) return false;
} else if (progress.transitions != kFrozenTrainingTransitions) {
return false;
}
return progress.training_finalized && !progress.game_active && trace.empty();
}
int evaluate(const EvaluationOptions& options, std::ostream& output) {
if (!options.stage_set || options.maximum_moves != kFrozenMaximumMoves ||
options.reveal_samples != kFrozenRevealSamples ||
options.event_boundaries != kFrozenEventBoundaries ||
options.internal_action_width != kFrozenInternalActionWidth ||
options.maximum_work != kFrozenMaximumSearchWork ||
!options.seed_start_set || options.checkpoint.empty() ||
options.source_directory.empty()) {
throw std::invalid_argument("evaluation differs from frozen configuration");
}
const bool stage_a = options.stage == EvaluationStage::kBurnedStageA;
if ((stage_a &&
(options.qualification_output.empty() ||
!options.qualification.empty() || !options.lane_manifest.empty())) ||
(!stage_a &&
(!options.qualification_output.empty() ||
options.qualification.empty() || options.lane_manifest.empty()))) {
throw std::invalid_argument("evaluation authority options differ by stage");
}
if (stage_a) {
const std::filesystem::path source_directory(options.source_directory);
const std::array<std::filesystem::path, 6> protected_paths{{
std::filesystem::path(options.checkpoint),
source_directory / "approaches/ntuple-rl/optimistic-phase/optimistic-phase-ntuple.cpp",
source_directory / "src/core/native/engine.hpp",
source_directory / "approaches/fair-expectimax/reference/fair-only-depth4.cpp",
source_directory / "approaches/fair-expectimax/reference/fair-only-horizon.cpp",
source_directory / "src/core/native/public-behavior.hpp",
}};
const std::filesystem::path qualification_output(
options.qualification_output);
const std::filesystem::path qualification_temporary =
qualification_output.string() + ".tmp";
for (const std::filesystem::path& protected_path : protected_paths) {
if (sameFilesystemTarget(qualification_output, protected_path) ||
sameFilesystemTarget(qualification_temporary, protected_path)) {
throw std::invalid_argument(
"qualification output aliases a protected input");
}
}
}
if ((stage_a &&
(options.games != 64 || options.seed_start != kBurnedStageASeedBegin)) ||
(!stage_a &&
(options.games != 256 || options.seed_start != kDevelopmentSeedBegin))) {
throw std::invalid_argument("evaluation cohort differs from stage contract");
}
const ProvenanceHashes provenance =
verifyCompiledProvenance(options.source_directory);
std::string development_lane_manifest_sha256;
if (!stage_a) {
development_lane_manifest_sha256 =
verifyLaneManifest(options.lane_manifest, SeedUse::kDevelopment);
}
requireSeedRange(options.seed_start, options.games,
stage_a ? SeedUse::kBurnedStageA
: SeedUse::kDevelopment);
Model model;
DelayedForwardTrace trace;
TrainingContract contract;
const Progress progress =
loadCheckpoint(options.checkpoint, model, trace, contract);
if (!evaluationCheckpointEligible(progress, trace, options.stage)) {
throw std::invalid_argument(
"checkpoint is partial, pooled, unfinalized, or overtrained");
}
const std::string model_sha256 = model.parameterSha256();
const std::string checkpoint_sha256 = fileSha256(options.checkpoint);
std::string qualification_sha256;
bool development_authorized = false;
if (!stage_a) {
qualification_sha256 = fileSha256(options.qualification);
const QualificationArtifact qualification =
readQualificationArtifact(options.qualification);
development_authorized = developmentAdmission(
laneOpen(SeedUse::kDevelopment),
development_lane_manifest_sha256 ==
kCompiledDevelopmentLaneManifestSha256,
qualification_sha256 == kCompiledStageAQualificationSha256,
qualificationMatchesCheckpoint(
qualification, contract, checkpoint_sha256, model_sha256));
if (!development_authorized) {
throw std::runtime_error(
"development capability does not authorize this checkpoint");
}
}
const auto started = std::chrono::steady_clock::now();
std::vector<PolicyGameResult> direct;
std::vector<PolicyGameResult> search;
direct.reserve(options.games);
search.reserve(options.games);
for (std::uint64_t game = 0; game < options.games; ++game) {
const std::uint32_t seed =
options.seed_start + static_cast<std::uint32_t>(game);
direct.push_back(
runPolicyGame(model, seed, PolicyKind::kDirect, options));
search.push_back(
runPolicyGame(model, seed, PolicyKind::kBoundarySearch, options));
}
const EvaluationSummary direct_summary = summarizeGames(direct);
const EvaluationSummary search_summary = summarizeGames(search);
bool stage_a_absolute_gate = false;
bool stage_a_half_gate = false;
bool d4_gate = false;
std::array<double, 2> direct_half_scores{};
std::array<double, 2> direct_half_moves{};
std::array<double, 2> search_half_scores{};
std::array<double, 2> search_half_moves{};
double paired_d4_score_lower95 =
-std::numeric_limits<double>::infinity();
double paired_d4_moves_lower95 =
-std::numeric_limits<double>::infinity();
std::vector<PolicyGameResult> d4;
std::optional<EvaluationSummary> d4_summary;
if (stage_a) {
stage_a_absolute_gate = passesStageAAbsoluteGate(
search_summary.mean_score, search_summary.mean_moves);
stage_a_half_gate = true;
for (std::size_t half = 0; half < 2; ++half) {
const std::size_t begin = half * 32;
direct_half_scores[half] = halfMean(direct, begin, true);
direct_half_moves[half] = halfMean(direct, begin, false);
search_half_scores[half] = halfMean(search, begin, true);
search_half_moves[half] = halfMean(search, begin, false);
stage_a_half_gate =
stage_a_half_gate &&
passesOrderedHalfGate(direct_half_scores[half],
direct_half_moves[half],
search_half_scores[half],
search_half_moves[half]);
}
if (requiresCorrectedD4Comparison(progress.transitions)) {
d4.reserve(options.games);
for (std::uint64_t game = 0; game < options.games; ++game) {
const std::uint32_t seed =
options.seed_start + static_cast<std::uint32_t>(game);
d4.push_back(runCorrectedD4Game(seed));
}
d4_summary = summarizeGames(d4);
if (d4_summary->censored != 0) {
throw std::runtime_error(
"corrected fair-D4 comparator was censored at its locked horizon");
}
if (std::abs(d4_summary->mean_score - 176'925.25) > 1.0e-6 ||
std::abs(d4_summary->mean_moves - 116.375) > 1.0e-6) {
throw std::runtime_error(
"corrected fair-D4 burned reference no longer matches ledger");
}
const std::vector<double> paired_scores =
pairedValues(search, d4, true);
const std::vector<double> paired_moves =
pairedValues(search, d4, false);
paired_d4_score_lower95 = studentLower95(paired_scores);
paired_d4_moves_lower95 = studentLower95(paired_moves);
d4_gate = passesCorrectedD4Gate(
search_summary.mean_score, search_summary.mean_moves,
d4_summary->mean_score, d4_summary->mean_moves,
paired_d4_score_lower95, paired_d4_moves_lower95);
}
}
const bool final_development_gate =
!stage_a && passesFinalDevelopmentGate(
search_summary.mean_score,
search_summary.bootstrap_lower95);
std::ostringstream ordered_results;
for (std::size_t index = 0; index < search.size(); ++index) {
ordered_results << direct[index].checksum << search[index].checksum;
if (!d4.empty()) ordered_results << d4[index].checksum;
}
const std::string ordered_results_sha256 =
sha256(ordered_results.str());
bool qualification_artifact_written = false;
if (stage_a &&
eligibleForFutureDevelopment(
progress.transitions, stage_a_absolute_gate,
stage_a_half_gate, d4_gate)) {
if (!d4_summary.has_value()) {
throw std::logic_error("qualified Stage A lacks corrected-D4 results");
}
QualificationArtifact qualification;
qualification.training_source_sha256 = contract.source_sha256;
qualification.engine_sha256 = contract.engine_sha256;
qualification.corrected_d4_sha256 = contract.corrected_d4_sha256;
qualification.corrected_d4_leaf_sha256 =
contract.corrected_d4_leaf_sha256;
qualification.cfpi_behavior_sha256 = contract.cfpi_behavior_sha256;
qualification.training_lane_manifest_sha256 =
contract.training_lane_manifest_sha256;
qualification.checkpoint_sha256 = checkpoint_sha256;
qualification.model_sha256 = model_sha256;
qualification.ordered_results_sha256 = ordered_results_sha256;
qualification.training_transitions = progress.transitions;
qualification.seed_start = options.seed_start;
qualification.games = options.games;
qualification.search_mean_score = search_summary.mean_score;
qualification.search_mean_moves = search_summary.mean_moves;
qualification.direct_half_scores = direct_half_scores;
qualification.direct_half_moves = direct_half_moves;
qualification.search_half_scores = search_half_scores;
qualification.search_half_moves = search_half_moves;
qualification.corrected_d4_mean_score = d4_summary->mean_score;
qualification.corrected_d4_mean_moves = d4_summary->mean_moves;
qualification.paired_d4_score_lower95 = paired_d4_score_lower95;
qualification.paired_d4_moves_lower95 = paired_d4_moves_lower95;
writeQualificationArtifact(options.qualification_output, qualification);
qualification_sha256 = fileSha256(options.qualification_output);
qualification_artifact_written = true;
}
std::ostringstream config;
config << std::setprecision(17);
config << "format=opnt-evaluation-v3"
<< "|stage=" << static_cast<int>(options.stage)
<< "|games=" << options.games
<< "|seedStart=" << options.seed_start
<< "|maximumMoves=" << options.maximum_moves
<< "|revealSamples=" << options.reveal_samples
<< "|eventBoundaries=" << options.event_boundaries
<< "|internalActionWidth=" << options.internal_action_width
<< "|maximumWork=" << options.maximum_work
<< "|trainingTransitions=" << progress.transitions
<< "|traceLambda=" << kLambda
<< "|traceDeltas=" << kTraceHorizon
<< "|scoreScale=" << kScoreScale
<< "|bootstrapReplicates=100000"
<< "|bootstrapSeed=3618637178"
<< "|studentCritical=" << studentCritical(options.games)
<< "|stageAMinScore=300000|stageAMinMoves=90"
<< "|developmentMinMeanExclusive=1050000"
<< "|developmentMinBootstrapExclusive=1000000"
<< "|compiler=" << compilerIdentity()
<< "|source=" << provenance.source
<< "|engine=" << provenance.engine
<< "|d4=" << provenance.corrected_d4
<< "|d4Leaf=" << provenance.corrected_d4_leaf
<< "|cfpiBehavior=" << provenance.cfpi_behavior
<< "|trainingSource=" << contract.source_sha256
<< "|trainingEngine=" << contract.engine_sha256
<< "|trainingD4=" << contract.corrected_d4_sha256
<< "|trainingD4Leaf=" << contract.corrected_d4_leaf_sha256
<< "|trainingCfpiBehavior=" << contract.cfpi_behavior_sha256
<< "|trainingLaneManifest="
<< contract.training_lane_manifest_sha256
<< "|developmentLaneManifest="
<< development_lane_manifest_sha256
<< "|stageAQualification=" << qualification_sha256
<< "|model=" << model_sha256
<< "|checkpoint=" << checkpoint_sha256;
const double seconds = std::chrono::duration<double>(
std::chrono::steady_clock::now() - started)
.count();
output << std::fixed << std::setprecision(6)
<< "OPTIMISTIC_PHASE_NTUPLE_EVALUATION {\"evaluationStage\":\""
<< (stage_a ? "burned-stage-a" : "final-development")
<< "\",\"games\":" << options.games
<< ",\"seedStart\":\"0x" << std::hex << std::setw(8)
<< std::setfill('0') << options.seed_start << std::dec
<< std::setfill(' ') << "\""
<< ",\"trainingTransitions\":" << progress.transitions
<< ",\"trainingFinalized\":"
<< (progress.training_finalized ? "true" : "false")
<< ",\"configuration\":{\"maximumMoves\":"
<< options.maximum_moves << ",\"revealSamples\":"
<< options.reveal_samples << ",\"eventBoundaries\":"
<< options.event_boundaries << ",\"internalActionWidth\":"
<< options.internal_action_width << ",\"maximumWork\":"
<< options.maximum_work
<< ",\"bootstrapReplicates\":100000"
<< ",\"bootstrapSeed\":\"0xd7b0057a\",\"compiler\":\""
<< compilerIdentity()
<< "\"},\"hashes\":{\"configurationSha256\":\""
<< sha256(config.str()) << "\",\"sourceSha256\":\""
<< provenance.source << "\",\"engineSha256\":\""
<< provenance.engine
<< "\",\"correctedD4SourceSha256\":\""
<< provenance.corrected_d4
<< "\",\"correctedD4LeafSourceSha256\":\""
<< provenance.corrected_d4_leaf
<< "\",\"cfpiBehaviorSourceSha256\":\""
<< provenance.cfpi_behavior
<< "\",\"trainingSourceSha256\":\""
<< contract.source_sha256
<< "\",\"trainingEngineSha256\":\""
<< contract.engine_sha256
<< "\",\"trainingCorrectedD4SourceSha256\":\""
<< contract.corrected_d4_sha256
<< "\",\"trainingCorrectedD4LeafSourceSha256\":\""
<< contract.corrected_d4_leaf_sha256
<< "\",\"trainingCfpiBehaviorSourceSha256\":\""
<< contract.cfpi_behavior_sha256
<< "\",\"trainingLaneManifestSha256\":\""
<< contract.training_lane_manifest_sha256
<< "\",\"developmentLaneManifestSha256\":\""
<< development_lane_manifest_sha256
<< "\",\"stageAQualificationSha256\":\""
<< qualification_sha256
<< "\",\"modelParametersSha256\":\"" << model_sha256
<< "\",\"checkpointSha256\":\"" << checkpoint_sha256
<< "\",\"orderedResultsSha256\":\""
<< ordered_results_sha256 << "\"},\"directSummary\":";
writeSummaryJson(output, direct_summary);
output << ",\"searchSummary\":";
writeSummaryJson(output, search_summary);
if (d4_summary.has_value()) {
output << ",\"correctedD4Summary\":";
writeSummaryJson(output, *d4_summary);
}
if (stage_a) {
output << ",\"orderedHalves\":[";
for (std::size_t half = 0; half < 2; ++half) {
if (half != 0) output << ',';
output << "{\"index\":" << half
<< ",\"directMeanScore\":" << direct_half_scores[half]
<< ",\"directMeanMoves\":" << direct_half_moves[half]
<< ",\"searchMeanScore\":" << search_half_scores[half]
<< ",\"searchMeanMoves\":" << search_half_moves[half]
<< ",\"scoreImproved\":"
<< (search_half_scores[half] > direct_half_scores[half]
? "true"
: "false")
<< ",\"movesImproved\":"
<< (search_half_moves[half] > direct_half_moves[half]
? "true"
: "false")
<< '}';
}
output << ']';
}
output << ",\"gates\":{\"stageAAbsolute\":"
<< (stage_a_absolute_gate ? "true" : "false")
<< ",\"stageAHalfComparisons\":"
<< (stage_a_half_gate ? "true" : "false")
<< ",\"correctedD4Noninferiority\":"
<< (d4_gate ? "true" : "false")
<< ",\"burnedStageAPassed\":"
<< ((stage_a_absolute_gate && stage_a_half_gate) ? "true" : "false")
<< ",\"eligibleForFuturePreregisteredDevelopment\":"
<< (eligibleForFutureDevelopment(
progress.transitions, stage_a_absolute_gate,
stage_a_half_gate, d4_gate)
? "true"
: "false")
<< ",\"qualificationArtifactWritten\":"
<< (qualification_artifact_written ? "true" : "false")
<< ",\"developmentAuthorityVerified\":"
<< (development_authorized ? "true" : "false")
<< ",\"pairedD4ScoreStudentTLower95\":";
if (std::isfinite(paired_d4_score_lower95)) {
output << paired_d4_score_lower95;
} else {
output << "null";
}
output << ",\"pairedD4MovesStudentTLower95\":";
if (std::isfinite(paired_d4_moves_lower95)) {
output << paired_d4_moves_lower95;
} else {
output << "null";
}
output
<< ",\"finalDevelopment\":"
<< (final_development_gate ? "true" : "false")
<< ",\"freshDevelopmentLaneOpened\":"
<< (development_authorized ? "true" : "false")
<< "},\"directGames\":";
writeGamesJson(output, direct);
output << ",\"searchGames\":";
writeGamesJson(output, search);
if (!d4.empty()) {
output << ",\"correctedD4Games\":";
writeGamesJson(output, d4);
}
output << ",\"illegalMoves\":0,\"runnerFailures\":0"
<< ",\"incompleteDecisions\":0,\"seconds\":" << seconds
<< ",\"peakResidentBytes\":" << peakResidentBytes() << "}\n";
return 0;
}
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;
}
template <typename Function>
bool throwsRuntime(Function function) {
try {
function();
} catch (const std::runtime_error&) {
return true;
}
return false;
}
bool approximatelyEqual(float first, float second, float tolerance = 1e-4f) {
return std::abs(first - second) <= tolerance;
}
State fixtureState() {
State state;
state.board.fill(kEmpty);
for (int column = 0; column < kBoardSize; ++column) {
state.board[indexOf(kBoardSize - 1, column)] = kSolid;
}
state.board[indexOf(kBoardSize - 2, 0)] = kCracked;
state.board[indexOf(kBoardSize - 2, 4)] = kSolid;
state.board[indexOf(kBoardSize - 3, 4)] = kCracked;
state.next_disc = 3;
state.moves_remaining = 3;
state.game_over = false;
return state;
}
bool selfTest(std::ostream& output) {
State fixture = fixtureState();
State metadata = fixture;
metadata.score = 9'999'999;
metadata.level = 777;
metadata.moves_played = 888;
State reflected = fixture;
reflected.board = mirrorBoard(fixture.board);
State alternate_disc = fixture;
alternate_disc.next_disc = 7;
Model gradient_model;
expect(approximatelyEqual(gradient_model.value(fixture), kOptimisticValue,
2e-3f),
"optimistic initialization did not sum to 60");
expect(gradient_model.value(fixture) == gradient_model.value(metadata),
"chance-state value used forbidden metadata");
expect(gradient_model.value(fixture) ==
gradient_model.value(alternate_disc),
"chance-state value depended on the visible disc");
expect(gradient_model.value(fixture) == gradient_model.value(reflected),
"chance-state value violated reflection");
const ActiveFeatures gradient =
activeFeatures(valueState(fixture), ModelStage::kPooled);
expect(gradient.count < kActiveOccurrences &&
gradient.maximum_multiplicity > 1 &&
gradient.squared_norm > kActiveOccurrences,
"synthetic fixture did not exercise shared-feature collisions");
const float before_gradient = gradient_model.value(fixture);
const UpdateReport fixed = gradient_model.update(
valueState(fixture), 2.0f, 0.25f, UpdateRule::kFixed);
expect(approximatelyEqual(gradient_model.value(fixture) - before_gradient,
0.5f, 2e-3f) &&
fixed.squared_norm == gradient.squared_norm,
"collision-correct normalized gradient failed");
Model promotion_model;
(void)promotion_model.update(valueState(fixture), 1.25f, 0.1f,
UpdateRule::kFixed);
std::array<float, kMovesPerLevel> before_promotion{};
for (int phase = 1; phase <= kMovesPerLevel; ++phase) {
State phased = fixture;
phased.moves_remaining = phase;
before_promotion[phase - 1] = promotion_model.value(phased);
}
promotion_model.promote();
for (int phase = 1; phase <= kMovesPerLevel; ++phase) {
State phased = fixture;
phased.moves_remaining = phase;
expect(approximatelyEqual(promotion_model.value(phased),
before_promotion[phase - 1], 2e-4f),
"pooled-to-phase promotion changed a value");
}
State phase_three = fixture;
phase_three.moves_remaining = 3;
State phase_two = fixture;
phase_two.moves_remaining = 2;
const float phase_two_before = promotion_model.value(phase_two);
(void)promotion_model.update(valueState(phase_three), 1.0f, 0.1f,
UpdateRule::kFixed);
expect(promotion_model.value(phase_two) == phase_two_before,
"phase-head update leaked into another phase");
Model tc_model;
tc_model.promote();
tc_model.enableTemporalCoherence();
const UpdateReport tc_positive = tc_model.update(
valueState(fixture), 1.0f, 1.0f,
UpdateRule::kTemporalCoherence);
const UpdateReport tc_negative = tc_model.update(
valueState(fixture), -1.0f, 1.0f,
UpdateRule::kTemporalCoherence);
const UpdateReport tc_after_reversal = tc_model.update(
valueState(fixture), 1.0f, 1.0f,
UpdateRule::kTemporalCoherence);
expect(approximatelyEqual(tc_positive.mean_beta, 1.0f) &&
approximatelyEqual(tc_negative.mean_beta, 1.0f) &&
approximatelyEqual(tc_after_reversal.mean_beta, 0.0f) &&
approximatelyEqual(tc_after_reversal.prediction_before,
tc_after_reversal.prediction_after),
"temporal-coherence prior-history ordering failed");
tc_model = Model(0.0f);
DelayedForwardTrace trace;
Model trace_model;
const float trace_prediction_before = trace_model.value(fixture);
trace.observe(valueState(fixture), 1.0f, UpdateRule::kFixed, 0.1f,
trace_model);
trace.observe(valueState(fixture), 2.0f, UpdateRule::kFixed, 0.1f,
trace_model);
expect(trace.summary().state_updates == 0 && trace.size() == 2,
"forward view updated before three deltas existed");
trace.observe(valueState(fixture), 4.0f, UpdateRule::kFixed, 0.1f,
trace_model);
expect(trace.summary().state_updates == 1 &&
trace.size() == static_cast<std::size_t>(kTraceHorizon - 1) &&
approximatelyEqual(trace.summary().last_credit, 3.0f) &&
approximatelyEqual(trace_model.value(fixture) -
trace_prediction_before,
0.3f, 2e-3f),
"three-delta forward TD(lambda) numeric credit failed");
trace.flush(trace_model);
expect(trace.summary().state_updates == kTraceHorizon && trace.empty(),
"three-delta forward TD(lambda) flush failed");
expect(tdTarget(2.0f, 5.0f, true) == 2.0f &&
tdTarget(2.0f, 5.0f, false) == 7.0f,
"terminal/truncation bootstrap distinction failed");
expect(scheduleForTransition(0, gradient_model).rate == kLearningRate0 &&
scheduleForTransition(kPromotionTransition, gradient_model)
.should_promote &&
scheduleForTransition(kLearningRateDrop1, promotion_model).rate ==
kLearningRate1 &&
scheduleForTransition(kLearningRateDrop2, promotion_model).rate ==
kLearningRate2 &&
scheduleForTransition(kTemporalCoherenceTransition,
promotion_model)
.should_enable_tc,
"fixed-rate to delayed-TC schedule hooks failed");
gradient_model = Model(0.0f);
trace_model = Model(0.0f);
const auto values = oneStepActionValues(
promotion_model, fixture, 7, kPolicyRevealDomain, 0);
const auto repeated = oneStepActionValues(
promotion_model, fixture, 7, kPolicyRevealDomain, 0);
const auto metadata_values = oneStepActionValues(
promotion_model, metadata, 7, kPolicyRevealDomain, 0);
const auto reflected_values = oneStepActionValues(
promotion_model, reflected, 7, kPolicyRevealDomain, 0);
expect(values == repeated && values == metadata_values,
"public chance sampler was nondeterministic or metadata-dependent");
for (int column = 0; column < kBoardSize; ++column) {
const float left = values[column];
const float right = reflected_values[kBoardSize - 1 - column];
expect(std::isfinite(left) == std::isfinite(right) &&
(!std::isfinite(left) || approximatelyEqual(left, right)),
"chance-sampled action values violated reflection");
}
expect(greedyAction(promotion_model, fixture, 7, kPolicyRevealDomain, 0) ==
kBoardSize - 1 -
greedyAction(promotion_model, reflected, 7,
kPolicyRevealDomain, 0),
"greedy action violated reflection");
State symmetric;
constexpr std::array<std::string_view, kBoardSize> symmetric_rows{{
"0000000", "0100010", "0600060", "9180819", "2273722",
"7632367", "4783874",
}};
for (int row = 0; row < kBoardSize; ++row) {
for (int column = 0; column < kBoardSize; ++column) {
symmetric.board[indexOf(row, column)] =
static_cast<std::uint8_t>(symmetric_rows[row][column] - '0');
}
}
symmetric.next_disc = 4;
symmetric.moves_remaining = 3;
expect(symmetric.board == mirrorBoard(symmetric.board),
"symmetric chance fixture was not reflection-fixed");
bool center_coordinates_distinct = false;
for (int depth = 0; depth < 3; ++depth) {
std::array<int, 7> visible_counts{};
std::array<int, 7> coordinate_counts{};
for (int stratum = 0; stratum < 7; ++stratum) {
ChancePackRandom sequential(fixture, 0, kPolicyRevealDomain, 11,
depth, stratum, 17);
ChancePackRandom coordinate(fixture, 0, kPolicyRevealDomain, 11,
depth, stratum, 17);
++visible_counts[sequential.nextDisc() - 1];
++coordinate_counts[coordinate.nextDiscFor(4, 2, 3) - 1];
ChancePackRandom left(symmetric, 0, kSearchRevealDomain, 13, depth,
stratum);
ChancePackRandom right(symmetric, 6, kSearchRevealDomain, 13, depth,
stratum);
ChancePackRandom center(symmetric, 3, kSearchRevealDomain, 13, depth,
stratum);
for (int row = 0; row < kBoardSize; ++row) {
for (int column = 0; column < kBoardSize; ++column) {
const int reverse = kBoardSize - 1 - column;
expect(left.nextDiscFor(row, column, 2) ==
right.nextDiscFor(row, reverse, 2),
"side-action reveal coordinates did not reflect exactly");
center_coordinates_distinct |=
center.nextDiscFor(row, column, 2) !=
center.nextDiscFor(row, reverse, 2);
}
}
}
expect(std::all_of(visible_counts.begin(), visible_counts.end(),
[](int count) { return count == 1; }) &&
std::all_of(coordinate_counts.begin(), coordinate_counts.end(),
[](int count) { return count == 1; }),
"chance pack did not enumerate every visible/reveal value once");
}
expect(center_coordinates_distinct,
"center action collapsed mirrored reveal coordinates");
for (int action = 0; action < kBoardSize; ++action) {
std::array<int, 7> next_disc_counts{};
for (int stratum = 0; stratum < 7; ++stratum) {
ChancePackRandom random(fixture, action, kPolicyRevealDomain, 23, 1,
stratum);
MoveResult move;
expect(playMoveWithChance(fixture, action, random, move) &&
!move.state.game_over,
"exact visible-disc transition fixture terminated");
++next_disc_counts[move.state.next_disc - 1];
}
expect(std::all_of(next_disc_counts.begin(), next_disc_counts.end(),
[](int count) { return count == 1; }),
"full chance transition did not enumerate visible discs 1..7");
}
bool exercised_multiple_reveals = false;
for (int stratum = 0; stratum < 7; ++stratum) {
ChancePackRandom left(symmetric, 0, kSearchRevealDomain, 29, 2,
stratum);
ChancePackRandom right(symmetric, 6, kSearchRevealDomain, 29, 2,
stratum);
MoveResult left_move;
MoveResult right_move;
expect(playMoveWithChance(symmetric, 0, left, left_move) &&
playMoveWithChance(symmetric, 6, right, right_move) &&
left_move.state.board == mirrorBoard(right_move.state.board) &&
left_move.state.next_disc == right_move.state.next_disc &&
left_move.state.moves_remaining ==
right_move.state.moves_remaining &&
left_move.state.game_over == right_move.state.game_over &&
left_move.score_delta == right_move.score_delta &&
left_move.waves.size() == right_move.waves.size(),
"multi-reveal symmetric transition did not reflect exactly");
int reveal_count = 0;
for (const Wave& wave : left_move.waves) reveal_count += wave.revealed;
exercised_multiple_reveals |= reveal_count > 1;
}
expect(exercised_multiple_reveals,
"symmetric chance fixture did not exercise multiple reveals");
for (int action = 0; action < kBoardSize; ++action) {
for (int stratum = 0; stratum < 7; ++stratum) {
ChancePackRandom direct_random(fixture, action, kSearchRevealDomain,
31, 2, stratum);
ChancePackRandom reverse_random(reflected, kBoardSize - 1 - action,
kSearchRevealDomain, 31, 2, stratum);
MoveResult direct_move;
MoveResult reverse_move;
expect(playMoveWithChance(fixture, action, direct_random, direct_move) &&
playMoveWithChance(reflected, kBoardSize - 1 - action,
reverse_random, reverse_move) &&
direct_move.state.board ==
mirrorBoard(reverse_move.state.board) &&
direct_move.state.next_disc == reverse_move.state.next_disc &&
direct_move.score_delta == reverse_move.score_delta &&
direct_move.level_advanced == reverse_move.level_advanced,
"asymmetric chance transition did not reflect exactly");
}
}
const std::uint32_t policy_bits = chanceEventBits(
fixture, kPolicyRevealDomain, 0, 1, 7);
const std::uint32_t search_bits = chanceEventBits(
fixture, kSearchRevealDomain, 0, 1, 7);
expect(publicHash(fixture) == publicHash(metadata) &&
policy_bits != search_bits,
"coordinate/domain-safe chance separation failed");
State boundary = fixture;
// Two decisions are required before the covered-row rise. This exercises
// both the exact seven-way next-visible-disc branch and the event stop.
boundary.moves_remaining = 2;
SearchOptions search_options;
search_options.maximum_boundaries = 1;
search_options.reveal_samples = 7;
search_options.internal_action_width = 2;
search_options.maximum_work = 100'000;
const SearchDecision search = chooseEventBoundaryAction(
promotion_model, boundary, search_options);
State boundary_reflected = boundary;
boundary_reflected.board = mirrorBoard(boundary.board);
const SearchDecision search_reflected = chooseEventBoundaryAction(
promotion_model, boundary_reflected, search_options);
State boundary_metadata = boundary;
boundary_metadata.score = 1'234'567;
boundary_metadata.level = 99;
boundary_metadata.moves_played = 456;
const SearchDecision search_metadata = chooseEventBoundaryAction(
promotion_model, boundary_metadata, search_options);
expect(search.completed_boundaries == 1 &&
search.last_iteration_complete && search.full_root &&
!search.used_direct_fallback &&
search.work > kBoardSize &&
search.work <= search_options.maximum_work &&
isLegal(boundary.board, search.action),
"event-boundary search did not complete exactly one rise boundary");
expect(search_reflected.action ==
kBoardSize - 1 - search.action &&
approximatelyEqual(search_reflected.value, search.value) &&
search_reflected.work == search.work,
"event-boundary search violated reflection");
expect(search_metadata.action == search.action &&
search_metadata.value == search.value &&
search_metadata.work == search.work,
"event-boundary search used forbidden metadata");
for (int phase = 1; phase <= kMovesPerLevel; ++phase) {
State phased = fixture;
phased.moves_remaining = phase;
const SearchDecision completed = chooseEventBoundaryAction(
promotion_model, phased, search_options, 71);
expect(completed.completed_boundaries == 1 &&
completed.last_iteration_complete && completed.full_root &&
!completed.used_direct_fallback &&
completed.work <= search_options.maximum_work,
"one-boundary bounded rollout failed a rise phase");
}
SearchOptions two_boundary_options = search_options;
two_boundary_options.maximum_boundaries = 2;
const SearchDecision two_boundary = chooseEventBoundaryAction(
promotion_model, fixture, two_boundary_options, 73);
expect(two_boundary.completed_boundaries == 2 &&
two_boundary.last_iteration_complete &&
two_boundary.full_root &&
!two_boundary.used_direct_fallback &&
two_boundary.work <= two_boundary_options.maximum_work,
"two-boundary bounded rollout did not complete");
for (int phase = 1; phase <= kMovesPerLevel; ++phase) {
State phased = fixture;
phased.moves_remaining = phase;
const SearchDecision completed = chooseEventBoundaryAction(
promotion_model, phased, two_boundary_options,
80 + static_cast<std::uint64_t>(phase));
expect(completed.completed_boundaries == 2 &&
completed.last_iteration_complete && completed.full_root &&
!completed.used_direct_fallback &&
completed.work <= two_boundary_options.maximum_work,
"two-boundary bounded rollout failed a rise phase");
}
SearchOptions capped_options = search_options;
capped_options.maximum_work = 1;
const SearchDecision capped = chooseEventBoundaryAction(
promotion_model, boundary, capped_options);
expect(capped.completed_boundaries == 0 && capped.used_direct_fallback &&
capped.work <= capped_options.maximum_work &&
isLegal(boundary.board, capped.action),
"event-boundary work cap/fallback failed");
SearchOptions wrong_width = search_options;
wrong_width.internal_action_width = 1;
expect(throwsInvalid([&] {
(void)chooseEventBoundaryAction(promotion_model, boundary,
wrong_width);
}),
"frozen search accepted a non-two internal action width");
Progress checkpoint_progress;
checkpoint_progress.transitions = kPromotionTransition;
checkpoint_progress.completed_games = 123;
checkpoint_progress.training_seed_start = kTrainingSeedBegin;
DelayedForwardTrace checkpoint_trace;
TrainingContract checkpoint_contract;
const std::filesystem::path checkpoint_path =
std::filesystem::temp_directory_path() /
("optimistic-phase-ntuple-selftest-" +
std::to_string(std::chrono::steady_clock::now()
.time_since_epoch()
.count()) +
".bin");
saveCheckpoint(checkpoint_path.string(), promotion_model,
checkpoint_progress, checkpoint_trace, checkpoint_contract);
Model loaded;
DelayedForwardTrace loaded_trace;
TrainingContract loaded_contract;
const Progress loaded_progress = loadCheckpoint(
checkpoint_path.string(), loaded, loaded_trace, loaded_contract);
expect(loaded_progress.transitions == checkpoint_progress.transitions &&
loaded_progress.completed_games ==
checkpoint_progress.completed_games &&
loaded_contract == checkpoint_contract && loaded_trace.empty() &&
loaded.promoted() && !loaded.temporalCoherenceEnabled() &&
loaded.fingerprint() == promotion_model.fingerprint() &&
!std::filesystem::exists(checkpoint_path.string() + ".tmp"),
"atomic checkpoint round trip failed");
{
std::string trailing = readWholeFile(checkpoint_path);
trailing.push_back('x');
const std::uint64_t payload_bytes =
trailing.size() - kCheckpointHeaderBytes;
const std::uint64_t checksum = checksumBytes(std::string_view(
trailing.data() + kCheckpointHeaderBytes,
static_cast<std::size_t>(payload_bytes)));
std::memcpy(trailing.data() + kCheckpointEnvelopeMagic.size() +
sizeof(std::uint32_t),
&payload_bytes, sizeof(payload_bytes));
std::memcpy(trailing.data() + kCheckpointEnvelopeMagic.size() +
sizeof(std::uint32_t) + sizeof(std::uint64_t),
&checksum, sizeof(checksum));
std::ofstream output_file(checkpoint_path,
std::ios::binary | std::ios::trunc);
output_file.write(trailing.data(),
static_cast<std::streamsize>(trailing.size()));
}
expect(throwsRuntime([&] {
Model rejected;
DelayedForwardTrace rejected_trace;
TrainingContract rejected_contract;
(void)loadCheckpoint(checkpoint_path.string(), rejected,
rejected_trace, rejected_contract);
}),
"checkpoint accepted checksum-valid trailing payload bytes");
saveCheckpoint(checkpoint_path.string(), promotion_model,
checkpoint_progress, checkpoint_trace, checkpoint_contract);
{
std::fstream corrupt(checkpoint_path,
std::ios::binary | std::ios::in | std::ios::out);
corrupt.seekp(static_cast<std::streamoff>(kCheckpointHeaderBytes + 37));
const char changed = static_cast<char>(0xa5);
corrupt.write(&changed, 1);
}
expect(throwsRuntime([&] {
Model rejected;
DelayedForwardTrace rejected_trace;
TrainingContract rejected_contract;
(void)loadCheckpoint(checkpoint_path.string(), rejected,
rejected_trace, rejected_contract);
}),
"checkpoint accepted a checksum mismatch");
{
std::ostringstream valid(std::ios::out | std::ios::binary);
promotion_model.writeCheckpoint(valid, checkpoint_progress,
checkpoint_contract);
std::string nonfinite = valid.str();
const float not_finite = std::numeric_limits<float>::quiet_NaN();
std::memcpy(nonfinite.data() + nonfinite.size() - sizeof(float),
¬_finite, sizeof(float));
std::istringstream input(nonfinite,
std::ios::in | std::ios::binary);
expect(throwsRuntime([&] {
Model rejected;
TrainingContract rejected_contract;
(void)rejected.readCheckpoint(input, rejected_contract);
}),
"checkpoint model parser accepted a non-finite parameter");
}
{
TrainingContract foreign_contract = checkpoint_contract;
foreign_contract.source_sha256 = sha256("foreign training source");
std::ostringstream foreign(std::ios::out | std::ios::binary);
promotion_model.writeCheckpoint(foreign, checkpoint_progress,
foreign_contract);
std::istringstream input(foreign.str(),
std::ios::in | std::ios::binary);
expect(throwsRuntime([&] {
Model rejected;
TrainingContract rejected_contract;
(void)rejected.readCheckpoint(input, rejected_contract);
}),
"checkpoint accepted a different training implementation hash");
}
promotion_model = Model(0.0f);
loaded = Model(0.0f);
tc_model = Model();
tc_model.promote();
tc_model.enableTemporalCoherence();
DelayedForwardTrace tc_checkpoint_trace;
tc_checkpoint_trace.observe(valueState(fixture), 0.5f,
UpdateRule::kTemporalCoherence, 1.0f, tc_model);
tc_checkpoint_trace.observe(valueState(phase_two), -0.25f,
UpdateRule::kTemporalCoherence, 1.0f, tc_model);
tc_checkpoint_trace.observe(valueState(phase_three), 0.125f,
UpdateRule::kTemporalCoherence, 1.0f, tc_model);
Progress tc_progress;
tc_progress.transitions = kTemporalCoherenceTransition;
tc_progress.completed_games = 17;
tc_progress.training_seed_start = kTrainingSeedBegin;
tc_progress.game_active = true;
tc_progress.active_game_seed = 0;
tc_progress.active_state = fixture;
tc_progress.active_state.score = 123'456;
tc_progress.active_state.level = 9;
tc_progress.active_state.moves_played = 42;
tc_progress.cumulative_score = 7'654'321;
tc_progress.cumulative_moves = 9'876;
tc_progress.censored_training_games = 3;
saveCheckpoint(checkpoint_path.string(), tc_model, tc_progress,
tc_checkpoint_trace, checkpoint_contract);
Model tc_loaded;
DelayedForwardTrace tc_loaded_trace;
TrainingContract tc_loaded_contract;
const Progress tc_loaded_progress = loadCheckpoint(
checkpoint_path.string(), tc_loaded, tc_loaded_trace,
tc_loaded_contract);
expect(tc_loaded_progress.game_active &&
tc_loaded_progress.active_state.board == fixture.board &&
tc_loaded_progress.active_state.score == 123'456 &&
tc_loaded_progress.active_state.level == 9 &&
tc_loaded_progress.active_state.moves_played == 42 &&
tc_loaded_progress.cumulative_score == 7'654'321 &&
tc_loaded_progress.cumulative_moves == 9'876 &&
tc_loaded_progress.censored_training_games == 3 &&
tc_loaded_trace.size() == 2 &&
tc_loaded_trace.summary().state_updates == 1 &&
tc_loaded_trace.summary().absolute_credit ==
tc_checkpoint_trace.summary().absolute_credit &&
tc_loaded.fingerprint() == tc_model.fingerprint() &&
tc_loaded_contract == checkpoint_contract,
"temporal-coherence checkpoint did not preserve pending state");
tc_model = Model(0.0f);
tc_loaded = Model(0.0f);
constexpr std::array<float, 7> synthetic_errors{{
1.0f, -0.5f, 2.0f, 0.25f, -1.5f, 0.75f, 3.0f,
}};
const auto synthetic_step = [&](std::size_t index, Model& model,
DelayedForwardTrace& forward) {
State state = fixture;
state.moves_remaining = 1 + static_cast<int>(index % kMovesPerLevel);
forward.observe(valueState(state), synthetic_errors[index],
UpdateRule::kFixed, 0.01f, model);
};
Model uninterrupted_model;
DelayedForwardTrace uninterrupted_trace;
for (std::size_t index = 0; index < synthetic_errors.size(); ++index) {
synthetic_step(index, uninterrupted_model, uninterrupted_trace);
}
uninterrupted_trace.flush(uninterrupted_model);
Model interrupted_model;
DelayedForwardTrace interrupted_trace;
synthetic_step(0, interrupted_model, interrupted_trace);
synthetic_step(1, interrupted_model, interrupted_trace);
Progress interrupted_progress;
interrupted_progress.transitions = 2;
interrupted_progress.training_seed_start = kTrainingSeedBegin;
interrupted_progress.game_active = true;
interrupted_progress.active_state = fixture;
saveCheckpoint(checkpoint_path.string(), interrupted_model,
interrupted_progress, interrupted_trace,
checkpoint_contract);
Model resumed_model;
DelayedForwardTrace resumed_trace;
TrainingContract resumed_contract;
Progress resumed_progress = loadCheckpoint(
checkpoint_path.string(), resumed_model, resumed_trace,
resumed_contract);
for (std::size_t index = 2; index < synthetic_errors.size(); ++index) {
synthetic_step(index, resumed_model, resumed_trace);
++resumed_progress.transitions;
}
resumed_trace.flush(resumed_model);
Progress uninterrupted_progress = interrupted_progress;
uninterrupted_progress.transitions = synthetic_errors.size();
std::ostringstream uninterrupted_bytes(std::ios::out | std::ios::binary);
uninterrupted_model.writeCheckpoint(uninterrupted_bytes,
uninterrupted_progress,
checkpoint_contract);
uninterrupted_trace.writeCheckpoint(uninterrupted_bytes);
std::ostringstream resumed_bytes(std::ios::out | std::ios::binary);
resumed_model.writeCheckpoint(resumed_bytes, resumed_progress,
resumed_contract);
resumed_trace.writeCheckpoint(resumed_bytes);
expect(resumed_progress.transitions == synthetic_errors.size() &&
resumed_progress.game_active &&
resumed_trace.summary().state_updates ==
uninterrupted_trace.summary().state_updates &&
resumed_trace.summary().absolute_credit ==
uninterrupted_trace.summary().absolute_credit &&
resumed_model.fingerprint() == uninterrupted_model.fingerprint() &&
resumed_bytes.str() == uninterrupted_bytes.str(),
"transition checkpoint/resume differed from uninterrupted training");
uninterrupted_model = Model(0.0f);
interrupted_model = Model(0.0f);
resumed_model = Model(0.0f);
std::filesystem::remove(checkpoint_path);
const bool training_lane_compiled =
kTrainingSeedBegin < kTrainingSeedEnd &&
validSha256Hex(kCompiledTrainingLaneManifestSha256);
const bool development_lane_compiled =
kDevelopmentSeedBegin < kDevelopmentSeedEnd &&
validSha256Hex(kCompiledDevelopmentLaneManifestSha256) &&
validSha256Hex(kCompiledStageAQualificationSha256);
expect(laneOpen(SeedUse::kTraining) == training_lane_compiled &&
laneOpen(SeedUse::kBurnedStageA) &&
laneOpen(SeedUse::kDevelopment) ==
development_lane_compiled &&
allowedSeed(kBurnedStageASeedBegin,
SeedUse::kBurnedStageA) &&
allowedSeed(kBurnedStageASeedEnd - 1,
SeedUse::kBurnedStageA) &&
!allowedSeed(kBurnedStageASeedEnd,
SeedUse::kBurnedStageA) &&
!allowedSeed(0x4d00'0000u, SeedUse::kDevelopment) &&
!allowedSeed(0x7d00'0000u, SeedUse::kTraining) &&
!allowedSeed(0xd700'0000u, SeedUse::kDevelopment),
"compile-time capability seed guards failed");
Progress eligibility;
DelayedForwardTrace eligibility_trace;
eligibility.transitions = kBurnedStageATransitions - 1;
expect(!evaluationCheckpointEligible(
eligibility, eligibility_trace,
EvaluationStage::kBurnedStageA),
"Stage A accepted a partial checkpoint");
eligibility.transitions = kBurnedStageATransitions;
expect(evaluationCheckpointEligible(
eligibility, eligibility_trace,
EvaluationStage::kBurnedStageA) &&
!evaluationCheckpointEligible(
eligibility, eligibility_trace,
EvaluationStage::kFinalDevelopment),
"50m checkpoint was not isolated to burned Stage A");
eligibility.transitions = kFrozenTrainingTransitions;
eligibility.training_finalized = true;
expect(evaluationCheckpointEligible(
eligibility, eligibility_trace,
EvaluationStage::kBurnedStageA) &&
evaluationCheckpointEligible(
eligibility, eligibility_trace,
EvaluationStage::kFinalDevelopment),
"finalized 100m checkpoint failed evaluation admission");
eligibility.game_active = true;
expect(!evaluationCheckpointEligible(
eligibility, eligibility_trace,
EvaluationStage::kFinalDevelopment),
"development accepted an active-game checkpoint");
eligibility.game_active = false;
eligibility.transitions = kFrozenTrainingTransitions + 1;
expect(!evaluationCheckpointEligible(
eligibility, eligibility_trace,
EvaluationStage::kBurnedStageA),
"evaluation accepted an overtrained checkpoint");
expect(passesStageAAbsoluteGate(300'000.0, 90.0) &&
!passesStageAAbsoluteGate(299'999.0, 90.0) &&
!passesStageAAbsoluteGate(300'000.0, 89.999) &&
passesOrderedHalfGate(100.0, 10.0, 100.001, 10.001) &&
!passesOrderedHalfGate(100.0, 10.0, 100.0, 11.0) &&
!passesOrderedHalfGate(100.0, 10.0, 101.0, 10.0),
"Stage-A absolute/ordered-half gate boundaries failed");
expect(!requiresCorrectedD4Comparison(kBurnedStageATransitions) &&
requiresCorrectedD4Comparison(kFrozenTrainingTransitions) &&
passesCorrectedD4Gate(200.0, 100.0, 200.0, 100.0, 0.0,
0.0) &&
!passesCorrectedD4Gate(199.0, 100.0, 200.0, 100.0, 0.0,
0.0) &&
!passesCorrectedD4Gate(200.0, 99.0, 200.0, 100.0, 0.0,
0.0) &&
!passesCorrectedD4Gate(200.0, 100.0, 200.0, 100.0, -0.001,
0.0) &&
!passesCorrectedD4Gate(200.0, 100.0, 200.0, 100.0, 0.0,
-0.001),
"corrected-D4 gate boundaries failed");
expect(passesFinalDevelopmentGate(1'050'000.001, 1'000'000.001) &&
!passesFinalDevelopmentGate(1'050'000.0, 1'000'000.001) &&
!passesFinalDevelopmentGate(1'050'000.001, 1'000'000.0),
"final-development gate boundaries failed");
for (int mask = 0; mask < 8; ++mask) {
const bool absolute = (mask & 1) != 0;
const bool halves = (mask & 2) != 0;
const bool d4 = (mask & 4) != 0;
expect(!eligibleForFutureDevelopment(
kBurnedStageATransitions, absolute, halves, d4) &&
eligibleForFutureDevelopment(
kFrozenTrainingTransitions, absolute, halves, d4) ==
(mask == 7),
"future-development conjunction admitted the wrong gate mask");
}
TrainingContract qualified_contract;
qualified_contract.source_sha256 = sha256("qualified source");
qualified_contract.engine_sha256 = sha256("qualified engine");
qualified_contract.corrected_d4_sha256 = sha256("qualified d4");
qualified_contract.corrected_d4_leaf_sha256 = sha256("qualified d4 leaf");
qualified_contract.cfpi_behavior_sha256 = sha256("qualified behavior");
qualified_contract.training_lane_manifest_sha256 =
sha256("qualified fit lane");
const std::string qualified_checkpoint_sha256 =
sha256("qualified checkpoint");
const std::string qualified_model_sha256 = sha256("qualified model");
QualificationArtifact qualified;
qualified.training_source_sha256 = qualified_contract.source_sha256;
qualified.engine_sha256 = qualified_contract.engine_sha256;
qualified.corrected_d4_sha256 = qualified_contract.corrected_d4_sha256;
qualified.corrected_d4_leaf_sha256 =
qualified_contract.corrected_d4_leaf_sha256;
qualified.cfpi_behavior_sha256 =
qualified_contract.cfpi_behavior_sha256;
qualified.training_lane_manifest_sha256 =
qualified_contract.training_lane_manifest_sha256;
qualified.checkpoint_sha256 = qualified_checkpoint_sha256;
qualified.model_sha256 = qualified_model_sha256;
qualified.ordered_results_sha256 = sha256("qualified ordered results");
qualified.training_transitions = kFrozenTrainingTransitions;
qualified.seed_start = kBurnedStageASeedBegin;
qualified.games = 64;
qualified.search_mean_score = 300'000.0;
qualified.search_mean_moves = 120.0;
qualified.direct_half_scores = {{100.0, 200.0}};
qualified.direct_half_moves = {{10.0, 20.0}};
qualified.search_half_scores = {{101.0, 201.0}};
qualified.search_half_moves = {{11.0, 21.0}};
qualified.corrected_d4_mean_score = 176'925.25;
qualified.corrected_d4_mean_moves = 116.375;
qualified.paired_d4_score_lower95 = 0.0;
qualified.paired_d4_moves_lower95 = 0.0;
const std::filesystem::path qualification_path =
std::filesystem::temp_directory_path() /
("optimistic-phase-ntuple-qualification-selftest-" +
std::to_string(std::chrono::steady_clock::now()
.time_since_epoch()
.count()) +
".bin");
writeQualificationArtifact(qualification_path, qualified);
const QualificationArtifact qualified_round_trip =
readQualificationArtifact(qualification_path);
const bool qualification_matches = qualificationMatchesCheckpoint(
qualified_round_trip, qualified_contract,
qualified_checkpoint_sha256, qualified_model_sha256);
QualificationArtifact failed_qualification = qualified_round_trip;
failed_qualification.paired_d4_score_lower95 = -0.001;
expect(qualification_matches &&
!developmentAdmission(false, true, true, true) &&
!developmentAdmission(true, true, false, true) &&
!developmentAdmission(true, true, true, false) &&
developmentAdmission(true, true, true, true) &&
!qualificationMatchesCheckpoint(
failed_qualification, qualified_contract,
qualified_checkpoint_sha256, qualified_model_sha256) &&
!qualificationMatchesCheckpoint(
qualified_round_trip, qualified_contract,
sha256("arbitrary checkpoint"), qualified_model_sha256) &&
!std::filesystem::exists(qualification_path.string() + ".tmp"),
"staged qualification admitted an unqualified checkpoint");
std::filesystem::remove(qualification_path);
const std::string closed_training_manifest =
canonicalLaneManifest(SeedUse::kTraining);
const std::string closed_development_manifest =
canonicalLaneManifest(SeedUse::kDevelopment);
expect(closed_training_manifest.find("purpose=training") !=
std::string::npos &&
closed_development_manifest.find(
"purpose=final-development") != std::string::npos &&
closed_training_manifest != closed_development_manifest,
"canonical lane manifests did not bind their purpose/configuration");
std::vector<PolicyGameResult> synthetic_games(64);
std::vector<PolicyGameResult> synthetic_comparator(64);
for (std::size_t index = 0; index < synthetic_games.size(); ++index) {
PolicyGameResult game;
game.seed = kBurnedStageASeedBegin + static_cast<std::uint32_t>(index);
game.score = 1'000 + static_cast<std::int64_t>(index) * 10;
game.moves = 20 + static_cast<int>(index % 8);
game.censored = index == 63;
game.numbered_cleared = static_cast<std::uint64_t>(game.moves * 2);
game.covered_revealed = static_cast<std::uint64_t>(game.moves);
game.chain_depth_sum = static_cast<std::uint64_t>(game.moves * 3);
game.maximum_chain_depth = 5;
game.work = index;
game.checksum = sha256(std::to_string(index));
synthetic_games[index] = game;
synthetic_comparator[index] = game;
synthetic_comparator[index].score -= 100;
synthetic_comparator[index].moves -= 1;
}
const EvaluationSummary synthetic_summary =
summarizeGames(synthetic_games);
const std::vector<double> paired_scores =
pairedValues(synthetic_games, synthetic_comparator, true);
const std::vector<double> paired_moves =
pairedValues(synthetic_games, synthetic_comparator, false);
std::ostringstream synthetic_json;
writeSummaryJson(synthetic_json, synthetic_summary);
writeGamesJson(synthetic_json, synthetic_games);
expect(synthetic_summary.mean_score == 1'315.0 &&
synthetic_summary.median_score == 1'315.0 &&
synthetic_summary.lower_quartile_score == 1'157.5 &&
synthetic_summary.minimum_score == 1'000.0 &&
synthetic_summary.censored == 1 &&
synthetic_summary.numbered_clears_per_move == 2.0 &&
synthetic_summary.covered_reveals_per_move == 1.0 &&
synthetic_summary.mean_chain_depth == 3.0 &&
synthetic_summary.maximum_chain_depth == 5 &&
synthetic_summary.bootstrap_lower95 <
synthetic_summary.mean_score &&
synthetic_summary.student_t_lower95 <
synthetic_summary.mean_score &&
approximatelyEqual(
static_cast<float>(studentLower95(paired_scores)), 100.0f) &&
approximatelyEqual(
static_cast<float>(studentLower95(paired_moves)), 1.0f) &&
synthetic_json.str().find("bootstrapOneSidedLower95") !=
std::string::npos &&
synthetic_json.str().find("checksum") != std::string::npos,
"synthetic evaluation artifact/statistics failed");
expect(sha256("abc") ==
"ba7816bf8f01cfea414140de5dae2223b00361a396177a9cb410ff61f20015ad",
"SHA-256 implementation failed known vector");
expect(validSha256Hex(sha256("abc")) &&
!validSha256Hex(
"BA7816BF8F01CFEA414140DE5DAE2223B00361A396177A9CB410FF61F20015AD") &&
!validSha256Hex("abc"),
"compile-time provenance lock validation failed");
output << std::fixed << std::setprecision(6)
<< "OPTIMISTIC_PHASE_NTUPLE_SELF_TEST {\"passed\":true"
<< ",\"publicChanceState\":true"
<< ",\"discIndependentValue\":true"
<< ",\"metadataBlind\":true"
<< ",\"reflectionExact\":true"
<< ",\"optimisticValue\":" << kOptimisticValue
<< ",\"scoreScale\":" << kScoreScale
<< ",\"lambda\":" << kLambda
<< ",\"traceHorizon\":" << kTraceHorizon
<< ",\"pooledEntries\":" << kPooledEntries
<< ",\"phaseEntries\":" << kPhaseEntries
<< ",\"activeOccurrences\":" << kActiveOccurrences
<< ",\"fixtureUniqueParameters\":" << gradient.count
<< ",\"fixtureSquaredNorm\":" << gradient.squared_norm
<< ",\"fixtureMaximumMultiplicity\":"
<< gradient.maximum_multiplicity
<< ",\"promotionParity\":true"
<< ",\"temporalCoherencePriorHistory\":true"
<< ",\"threeDeltaForwardView\":true"
<< ",\"terminalVsTruncationBootstrap\":true"
<< ",\"coordinateStratifiedChanceSampling\":true"
<< ",\"eventBoundaryBoundedRollout\":true"
<< ",\"eventBoundaryWork\":" << search.work
<< ",\"twoBoundaryWork\":" << two_boundary.work
<< ",\"atomicCheckpointRoundTrip\":true"
<< ",\"transitionResumeBitEqual\":true"
<< ",\"tcCheckpointRoundTrip\":true"
<< ",\"checkpointCorruptionRejected\":true"
<< ",\"trainingImplementationCheckpointBound\":true"
<< ",\"capabilityGuardedSeedLanes\":true"
<< ",\"burnedStageAOnly\":true"
<< ",\"evaluationArtifactStatistics\":true"
<< ",\"parameterBytesWithTc\":"
<< static_cast<std::uint64_t>(kPhaseEntries) * 3u * sizeof(float)
<< ",\"peakResidentBytes\":" << peakResidentBytes() << "}\n";
return true;
}
} // namespace drop7::optimistic_phase_ntuple
#ifndef DROP7_OPTIMISTIC_PHASE_NTUPLE_LIBRARY
int main(int argc, char** argv) {
try {
using namespace drop7::optimistic_phase_ntuple;
if (argc >= 2 && std::string_view(argv[1]) == "--self-test") {
return selfTest(std::cout) ? EXIT_SUCCESS : EXIT_FAILURE;
}
if (argc >= 2 && std::string_view(argv[1]) == "--train") {
return train(parseTrainingOptions(argc, argv, 2), std::cout);
}
if (argc >= 2 && std::string_view(argv[1]) == "--evaluate") {
return evaluate(parseEvaluationOptions(argc, argv, 2), std::cout);
}
if (argc == 2 &&
std::string_view(argv[1]) == "--print-training-lane-manifest") {
std::cout << canonicalLaneManifest(SeedUse::kTraining);
return 0;
}
if (argc == 2 &&
std::string_view(argv[1]) == "--print-development-lane-manifest") {
std::cout << canonicalLaneManifest(SeedUse::kDevelopment);
return 0;
}
std::cerr
<< "usage: drop7_optimistic_phase_ntuple --self-test | "
"--train OPTIONS | --evaluate OPTIONS | "
"--print-training-lane-manifest | "
"--print-development-lane-manifest\n"
<< "gameplay lanes require compile-time manifest capabilities\n";
return 2;
} catch (const std::exception& error) {
std::cerr << "error: " << error.what() << '\n';
return 1;
}
}
#endif