16#include <emulatorList.hpp>
18#include <gameList.hpp>
19#include <jaffarCommon/deserializers/base.hpp>
20#include <jaffarCommon/file.hpp>
21#include <jaffarCommon/hash.hpp>
22#include <jaffarCommon/json.hpp>
23#include <jaffarCommon/logger.hpp>
24#include <jaffarCommon/parallel.hpp>
25#include <jaffarCommon/serializers/base.hpp>
26#include <jaffarCommon/timing.hpp>
37#ifdef JAFFARPLUS_DETAILED_PROFILING
39#define JAFFAR_PROF_DECL(var) const auto var = jaffarCommon::timing::now()
41#define JAFFAR_PROF_ACC(field, var) field += jaffarCommon::timing::timeDeltaMicroseconds(jaffarCommon::timing::now(), var)
43#define JAFFAR_PROF_DECL(var) ((void)0)
44#define JAFFAR_PROF_ACC(field, var) ((void)0)
71 Engine(
const nlohmann::json& emulatorConfig,
const nlohmann::json& gameConfig,
const nlohmann::json& runnerConfig,
const nlohmann::json& engineConfig)
77 if (_threadCount == 0) JAFFAR_THROW_LOGIC(
"The number of worker threads must be at least one. Provided: %lu\n", _threadCount);
80 jaffarCommon::logger::log(
"[J+] Using %lu worker threads.\n", _threadCount);
95 _runners[jaffarCommon::parallel::getThreadId()] = std::move(r);
102 auto engineConfigRemaining = engineConfig;
105 auto stateDatabaseJs = jaffarCommon::json::popObject(engineConfigRemaining,
"State Database");
106 _stateDb = std::make_unique<jaffarPlus::StateDb>(r, stateDatabaseJs);
109 auto hashDbConfig = jaffarCommon::json::popObject(engineConfigRemaining,
"Hash Database");
110 _hashDbEnabled = jaffarCommon::json::getBoolean(hashDbConfig,
"Enabled");
117 _baseStateBatch = engineConfigRemaining.contains(
"Base State Batch Size") ? jaffarCommon::json::popNumber<size_t>(engineConfigRemaining,
"Base State Batch Size") : 0;
125 if (engineConfigRemaining.contains(
"Log Verbosity"))
127 const auto v = jaffarCommon::json::popString(engineConfigRemaining,
"Log Verbosity");
128 if (v !=
"Full" && v !=
"Compact") JAFFAR_THROW_LOGIC(
"[ERROR] 'Log Verbosity' must be 'Full' or 'Compact' (got '%s')\n", v.c_str());
145 _hashLookahead = engineConfigRemaining.contains(
"Hash Lookahead") ? jaffarCommon::json::popNumber<size_t>(engineConfigRemaining,
"Hash Lookahead") : 0;
148 if (engineConfigRemaining.contains(
"Reference Pinning"))
150 auto pinJs = jaffarCommon::json::popObject(engineConfigRemaining,
"Reference Pinning");
151 _refPinEnabled = jaffarCommon::json::popBoolean(pinJs,
"Enabled");
152 _refPinBonus = jaffarCommon::json::popNumber<float>(pinJs,
"Bonus");
153 _refPinLookahead = jaffarCommon::json::popNumber<size_t>(pinJs,
"Lookahead");
155 const auto pinPath = jaffarCommon::json::popString(pinJs,
"Path");
161 _refPinExactVerify = pinJs.contains(
"Exact Verification") ? jaffarCommon::json::popBoolean(pinJs,
"Exact Verification") :
true;
162 jaffarCommon::json::checkEmpty(pinJs,
"Engine Configuration > Reference Pinning");
163 if (
_refPinEnabled && std::getenv(
"JAFFAR_IS_DRY_RUN") ==
nullptr)
165 std::ifstream f(pinPath);
166 if (f.good() ==
false) JAFFAR_THROW_RUNTIME(
"[ERROR] Could not open 'Reference Pinning' > 'Path': '%s'\n", pinPath.c_str());
170 while (f >> step >> hex)
172 if (hex.size() != 32)
continue;
173 jaffarCommon::hash::hash_t h;
174 h.first = std::stoull(hex.substr(0, 16),
nullptr, 16);
175 h.second = std::stoull(hex.substr(16, 16),
nullptr, 16);
181 jaffarCommon::logger::log(
"[J+] Reference pinning enabled: %lu hashes loaded, bonus %.1f, lookahead %lu (+%.1f/frame ahead)\n",
_refPinHashes.size(),
_refPinBonus,
190 if (engineConfigRemaining.contains(
"Reference Reward Prune"))
192 auto pruneJs = jaffarCommon::json::popObject(engineConfigRemaining,
"Reference Reward Prune");
198 _refPruneStepGrace = pruneJs.contains(
"Step Grace") ? jaffarCommon::json::popNumber<size_t>(pruneJs,
"Step Grace") : 0;
199 jaffarCommon::json::checkEmpty(pruneJs,
"Engine Configuration > Reference Reward Prune");
203 jaffarCommon::json::checkEmpty(engineConfigRemaining,
"Engine Configuration");
277 auto& r =
_runners[jaffarCommon::parallel::getThreadId()];
280 r->setInputHistoryBacking(
_inputHistoryBacking, (uint32_t)jaffarCommon::parallel::getThreadId(), _threadCount + 1);
289 const size_t stateBudget =
_stateDb->getMaxBudgetBytes();
298 const size_t hashPeak = hashBudget * 2;
299 const size_t totalBudget = stateBudget + hashPeak + historyBound;
301 for (
int i = 0; i < _numaCount; i++)
303 long long nodeFree = 0;
304 numa_node_size64(i, &nodeFree);
305 freeRam += (size_t)nodeFree;
307 const size_t usable = (size_t)((
double)freeRam * 0.90);
308 if (totalBudget > usable)
310 const double GB = 1024.0 * 1024.0 * 1024.0;
311 JAFFAR_THROW_RUNTIME(
"Configured database budget exceeds available memory:\n"
312 " State DB ('Max Size (Mb)', fixed pool) = %.1f GB\n"
313 " Hash DB peak (2 x 'Max Store Size' x 'Max Store Count') = %.1f GB\n"
314 " Input-history trie (hard node-storage ceiling) = %.1f GB\n"
316 " Usable (90%% of %.1f GB free RAM) = %.1f GB\n"
317 "Reduce 'State Database/Max Size (Mb)' and/or 'Hash Database/Max Store Size (Mb)'.\n"
318 "NOTE: the hash DB phmap doubles on growth, so a rehash briefly holds the old + new (2x) table; the\n"
319 "Trie input-history is a separate structure that grows up to ~384 GiB. Both are otherwise uncounted.\n",
320 (
double)stateBudget / GB, (
double)hashPeak / GB, (
double)historyBound / GB, (
double)totalBudget / GB, (
double)freeRam / GB, (
double)usable / GB);
342 const size_t stateSize = r.getStateSize();
343 std::vector<char> scratch(stateSize);
345 jaffarCommon::serializer::Contiguous s(scratch.data(), stateSize);
349 const auto allowedInputs = r.getAllowedInputs();
352 const size_t CAL_FRAMES = 200;
353 const auto tCal0 = jaffarCommon::timing::now();
354 for (
size_t i = 0; i < CAL_FRAMES; i++) r.advanceState(calInput);
355 const size_t perStateNs = jaffarCommon::timing::timeDeltaNanoseconds(jaffarCommon::timing::now(), tCal0) / CAL_FRAMES;
359 jaffarCommon::deserializer::Contiguous d(scratch.data(), stateSize);
360 r.deserializeState(d);
364 constexpr size_t TARGET_BATCH_NS = 200000;
365 size_t b = (perStateNs > 0) ? ((TARGET_BATCH_NS + perStateNs / 2) / perStateNs) :
BASE_STATE_BATCH_MAX;
369 jaffarCommon::logger::log(
"[J+] Auto-tuned base-state batch size: %lu (measured %.1f us/state)\n",
_baseStateBatch, (
double)perStateNs / 1000.0);
374 r.getGame()->evaluateRules();
377 r.getGame()->updateGameStateType();
380 r.getGame()->runGameSpecificRuleActions();
383 r.getGame()->updateReward();
386 const auto reward = r.getGame()->getReward();
389 auto stateData =
_stateDb->getFreeState(0);
392 _stateDb->pushState(reward, r, stateData);
427 const auto tStep = jaffarCommon::timing::now();
469 for (ssize_t i = 0; i < _threadCount; i++)
497 const auto t0 = jaffarCommon::timing::now();
502 const auto t1 = jaffarCommon::timing::now();
514 _currentStepTime = jaffarCommon::timing::timeDeltaMicroseconds(jaffarCommon::timing::now(), tStep);
522 for (ssize_t i = 0; i < _threadCount; i++)
610 jaffarCommon::logger::log(
"[J+] Checkpoint level %lu cohort extinct -- demoting to %lu\n", lvl, lvl - 1);
662 void setWinStateCollection(
const std::vector<std::string>& dedupProps,
const std::string& pathPrefix,
const size_t maxFiles)
711 jaffarCommon::logger::log(
"[J+] + Elapsed Time (Step/Total): %9.3fs / %9.3fs\n", 1.0e-6 * (
double)(
_currentStepTime),
715 jaffarCommon::logger::log(
"[J+] + New States Processed: %.3f Mstates (Total: %.3f Mstates) @ %.3f Mstates/s\n",
720 jaffarCommon::logger::log(
"[J+] + Win States: %lu (%5.3f%% of New States Processed) \n",
_winStates.load(),
725 jaffarCommon::logger::log(
"[J+] + Dropped States (Below Reference): %lu (%5.3f%% of New States Processed, prune tol %.1f) \n",
728 jaffarCommon::logger::log(
"[J+] + Reference Pin Hits (cumulative): %lu (deepest %lu / %lu; ref-depth matches seen %lu)\n",
_refPinHits.load(),
730 jaffarCommon::logger::log(
"[J+] + State Db States: %lu (%.2f / %.2f GB, %.1f%% full)\n",
_stateDb->getStateCount(),
731 (
double)(
_stateDb->getStateCount() *
_stateDb->getStateSizeInDatabase()) / (1024.0 * 1024.0 * 1024.0),
732 (
double)
_stateDb->getMaxBudgetBytes() / (1024.0 * 1024.0 * 1024.0),
733 100.0 * (
double)(
_stateDb->getStateCount() *
_stateDb->getStateSizeInDatabase()) / (
double)
_stateDb->getMaxBudgetBytes());
737 jaffarCommon::logger::log(
"[J+] Thread Count / NUMA Domains: %3d / %d\n", _threadCount, _numaCount);
738#ifdef JAFFARPLUS_DETAILED_PROFILING
739 jaffarCommon::logger::log(
"[J+] Elapsed Time (Step/Total): %9.3fs (%7.3f%%) / %9.3fs (%3.3f%%)\n", 1.0e-6 * (
double)(
_currentStepTime),
743 jaffarCommon::logger::log(
"[J+] + Runner State Avance (Step/Total): %9.3fs (%7.3f%%) / %9.3fs (%3.3f%%)\n", 1.0e-6 * (
double)(
_runnerStateAdvanceAverageTime),
747 jaffarCommon::logger::log(
"[J+] + Runner State Load (Step/Total): %9.3fs (%7.3f%%) / %9.3fs (%3.3f%%)\n", 1.0e-6 * (
double)(
_runnerStateLoadAverageTime),
751 jaffarCommon::logger::log(
"[J+] + Runner State Save (Step/Total): %9.3fs (%7.3f%%) / %9.3fs (%3.3f%%)\n", 1.0e-6 * (
double)(
_runnerStateSaveAverageTime),
755 jaffarCommon::logger::log(
"[J+] + Hash Calculation (Step/Total): %9.3fs (%7.3f%%) / %9.3fs (%3.3f%%)\n", 1.0e-6 * (
double)(
_calculateHashAverageTime),
759 jaffarCommon::logger::log(
"[J+] + Hash Checking (Step/Total): %9.3fs (%7.3f%%) / %9.3fs (%3.3f%%)\n", 1.0e-6 * (
double)(
_checkHashAverageTime),
763 jaffarCommon::logger::log(
"[J+] + Rule Checking (Step/Total): %9.3fs (%7.3f%%) / %9.3fs (%3.3f%%)\n", 1.0e-6 * (
double)(
_ruleCheckingAverageTime),
767 jaffarCommon::logger::log(
"[J+] + Get Free State (Step/Total): %9.3fs (%7.3f%%) / %9.3fs (%3.3f%%)\n", 1.0e-6 * (
double)(
_getFreeStateAverageTime),
771 jaffarCommon::logger::log(
"[J+] + Return Free State (Step/Total): %9.3fs (%7.3f%%) / %9.3fs (%3.3f%%)\n", 1.0e-6 * (
double)(
_returnFreeStateAverageTime),
775 jaffarCommon::logger::log(
"[J+] + Calculate Reward (Step/Total): %9.3fs (%7.3f%%) / %9.3fs (%3.3f%%)\n", 1.0e-6 * (
double)(
_calculateRewardAverageTime),
779 jaffarCommon::logger::log(
"[J+] + Popping Base State (Step/Total): %9.3fs (%7.3f%%) / %9.3fs (%3.3f%%)\n", 1.0e-6 * (
double)(
_popBaseStateDbAverageTime),
783 jaffarCommon::logger::log(
"[J+] + Get Allowed Inputs (Step/Total): %9.3fs (%7.3f%%) / %9.3fs (%3.3f%%)\n", 1.0e-6 * (
double)(
_getAllowedInputsAverageTime),
787 jaffarCommon::logger::log(
"[J+] + Get Candidate Inputs (Step/Total): %9.3fs (%7.3f%%) / %9.3fs (%3.3f%%)\n", 1.0e-6 * (
double)(
_getCandidateInputsAverageTime),
794 jaffarCommon::logger::log(
"[J+] Elapsed Time (Step/Total): %9.3fs / %9.3fs (per-operation breakdown disabled; build -DdetailedProfiling=true)\n",
798 jaffarCommon::logger::log(
"[J+] + Advance Hash Db (Step/Total): %9.3fs (%7.3f%%) / %9.3fs (%3.3f%%)\n", 1.0e-6 * (
double)(
_advanceHashDbAverageTime),
802 jaffarCommon::logger::log(
"[J+] + Advance State Db (Step/Total): %9.3fs (%7.3f%%) / %9.3fs (%3.3f%%)\n", 1.0e-6 * (
double)(
_advanceStateDbAverageTime),
808 jaffarCommon::logger::log(
"[J+] Base States Processed: %.3f Mstates (Total: %.3f Mstates)\n", 1.0e-6 * (
double)
_stepBaseStatesProcessed,
810 jaffarCommon::logger::log(
"[J+] New States Processed: %.3f Mstates (Total: %.3f Mstates)\n", 1.0e-6 * (
double)
_stepNewStatesProcessed,
813 jaffarCommon::logger::log(
"[J+] Reference Pin Hits (cumulative): %lu (deepest %lu / %lu; ref-depth matches seen %lu)\n",
_refPinHits.load(),
816 jaffarCommon::logger::log(
"[J+] Base States Performance: %.3f Mstates/s (Average: %.3f Mstates/s)\n",
819 jaffarCommon::logger::log(
"[J+] New States Performance: %.3f Mstates/s (Average: %.3f Mstates/s)\n",
823 jaffarCommon::logger::log(
"[J+] Dropped States (No Storage Available): %lu (%5.3f%% of New States Processed) \n",
_droppedStatesNoStorage.load(),
827 jaffarCommon::logger::log(
"[J+] Dropped States (Checkpoint): %lu (%5.3f%% of New States Processed) \n",
_droppedStatesCheckpoint.load(),
830 jaffarCommon::logger::log(
"[J+] Dropped States (Below Reference): %lu (%5.3f%% of New States Processed, prune tol %.1f) \n",
_droppedStatesBelowReference.load(),
832 jaffarCommon::logger::log(
"[J+] Failed States: %lu (%5.3f%% of New States Processed) \n",
_failedStates.load(),
834 jaffarCommon::logger::log(
"[J+] Repeated States: %lu (%5.3f%% of New States Processed) \n",
_repeatedStates.load(),
836 jaffarCommon::logger::log(
"[J+] Normal States: %lu (%5.3f%% of New States Processed) \n",
_normalStates.load(),
838 jaffarCommon::logger::log(
"[J+] Win States: %lu (%5.3f%% of New States Processed) \n",
_winStates.load(),
844 jaffarCommon::logger::log(
"[J+] State Database Information:\n");
849 jaffarCommon::logger::log(
"[J+] Hash Database Information:\n");
853 jaffarCommon::logger::log(
"[J+] Manually Saved Solution:\n");
860 jaffarCommon::logger::log(
"[J+] Candidate Inputs:\n");
863 jaffarCommon::logger::log(
"[J+] + Hash: %s\n", jaffarCommon::hash::hashToString(entry.first).c_str());
864 for (
const auto input : entry.second) jaffarCommon::logger::log(
"[J+] + %3lu %s\n", input,
_runners[0]->getInputStringFromIndex(input).c_str());
1010 const auto threadId = jaffarCommon::parallel::getThreadId();
1016 const auto threadTime0 = jaffarCommon::timing::now();
1029 size_t batchIdx = 0;
1032 while (batchIdx < batchCount)
1035 void* baseStateData = baseStateBatch[batchIdx++];
1038 acc.baseStatesProcessed++;
1042 _stateDb->loadStateFromSlot(*r, baseStateData);
1048 const auto allowedInputs = r->getAllowedInputs();
1054 auto candidateInputs = r->getCandidateInputs();
1058 std::vector<InputSet::inputIndex_t> uniqueCandidateInputs;
1059 for (
const auto& input : candidateInputs)
1060 if (std::find(allowedInputs.begin(), allowedInputs.end(), input) == allowedInputs.end()) uniqueCandidateInputs.push_back(input);
1065 jaffarCommon::hash::hash_t baseStateInputHash{};
1066 if (uniqueCandidateInputs.empty() ==
false) baseStateInputHash = r->getGame()->getStateInputHash();
1069 for (
auto inputItr = allowedInputs.begin(); inputItr != allowedInputs.end(); inputItr++)
runNewInput(*r, baseStateData, *inputItr, acc, threadId);
1072 for (
const auto input : uniqueCandidateInputs)
1079 const auto result =
runNewInput(*r, baseStateData, input, acc, threadId);
1087 _stateDb->returnFreeState(baseStateData, threadId);
1091 if (batchIdx >= batchCount)
1101 _threadStepTime[threadId] = jaffarCommon::timing::timeDeltaMicroseconds(jaffarCommon::timing::now(), threadTime0);
1120 _stateDb->loadStateFromSlot(r, baseStateData);
1125 const auto result =
runInput(r, baseStateData, input, acc, threadId);
1146 if (stateCheckpointLevel >
_checkpointLevel.load(std::memory_order_relaxed))
1152 if (stateCheckpointLevel >
_checkpointLevel.load(std::memory_order_relaxed))
1214 float pinBonus = 0.0f;
1215 bool isRefPin =
false;
1221 const size_t idx = newDepth + k;
1228 bool verified =
true;
1231 thread_local std::vector<uint8_t> scratch;
1233 jaffarCommon::serializer::Contiguous s(scratch.data(), scratch.size());
1236 for (
size_t i = 0; i < scratch.size(); i++)
1252 uint8_t expected = 0;
1253 if (
_refPinnedAtDepth[idx].compare_exchange_strong(expected, 1, std::memory_order_relaxed))
1257 _refPinHits.fetch_add(1, std::memory_order_relaxed);
1275 _stateDb->loadStateFromSlot(r, baseStateData);
1288 const auto globalCheckpointLevel =
_checkpointLevel.load(std::memory_order_acquire);
1307 void* newStateData =
_stateDb->getFreeState(threadId);
1332 _stateDb->returnFreeState(newStateData, threadId);
1352 if (prop ==
nullptr) JAFFAR_THROW_LOGIC(
"[ERROR] Win State Collection dedup property '%s' not registered\n", pn.c_str());
1353 const auto* bytes = (
const uint8_t*)prop->getPointer();
1354 for (
size_t bi = 0; bi < prop->getSize(); bi++)
1356 snprintf(hx,
sizeof(hx),
"%02x", bytes[bi]);
1365 jaffarCommon::file::saveStringToFile(solution,
_winCollectPrefix + key +
".sol");
1370 mf <<
"key " << key <<
" step " << r.
getStepCount() <<
"\n";
1396 _stateDb->returnFreeState(newStateData, threadId);
1410 static const long sampleRate = []
1412 const char* e = std::getenv(
"JAFFAR_DEBUG_SAMPLE_RATE");
1413 return e ? std::atol(e) : 0;
1417 static std::atomic<uint64_t> pushCounter{0};
1418 const auto n = pushCounter.fetch_add(1, std::memory_order_relaxed);
1423 if (n % (uint64_t)sampleRate == 0)
1427 const char* names[] = {
"Player Pos X",
"Player Pos Y",
"RNG State 1",
"RNG State 2",
"RNG State 3",
"RNG State 4"};
1428 for (
const auto* pn : names)
1431 if (prop ==
nullptr)
continue;
1432 const auto* b = (
const uint8_t*)prop->
getPointer();
1433 for (
size_t bi = 0; bi < prop->getSize() && pp < 480; bi++) pp += snprintf(props + pp,
sizeof(props) - pp,
"%02x", b[bi]);
1434 pp += snprintf(props + pp,
sizeof(props) - pp,
" ");
1436 jaffarCommon::logger::log(
"[J+] SAMPLE depth %lu reward %.6f floor %.6f hash %016lX%016lX props %s\n", cd, reward, fr, hash.first, hash.second, props);
1442 auto success =
_stateDb->pushState(reward, r, newStateData);
1446 if (success ==
false)
1450 _stateDb->returnFreeState(newStateData, threadId);
1486 jaffarCommon::concurrent::HashMap_t<jaffarCommon::hash::hash_t, jaffarCommon::concurrent::HashSet_t<InputSet::inputIndex_t>>
_candidateInputsDetected;
1532 thread_local std::vector<uint8_t> scratch;
1535 jaffarCommon::serializer::Contiguous ser(scratch.data(), scratch.size());
1542 jaffarCommon::deserializer::Contiguous des(scratch.data(), scratch.size());
1559 void setReferenceStates(std::vector<std::vector<uint8_t>>&& states,
const std::vector<std::string>& refInputs)
1562 if (
_refStates.size() < 2 || refInputs.size() == 0)
return;
1563 std::vector<uint8_t> scratch(
_refStates[0].size());
1568 const size_t maxDepth = std::min(
_refStates.size() - 1, refInputs.size());
1569 const size_t probeCount = std::min<size_t>(32, maxDepth);
1572 const size_t runnersToProbe = std::min<size_t>(16,
_runners.size());
1573 for (
size_t ri = 0; ri < runnersToProbe; ri++)
1574 for (
size_t pi = 0; pi < probeCount; pi++)
1577 const size_t k = (pi * maxDepth) / probeCount;
1578 if (refInputs[k].empty())
continue;
1581 r.deserializeState(d);
1583 r.advanceState(r.getGame()->getEmulator()->registerInput(refInputs[k]));
1585 jaffarCommon::serializer::Contiguous s(scratch.data(), scratch.size());
1586 r.serializeState(s);
1588 for (
size_t i = 0; i < scratch.size(); i++)
1591 const size_t lo = i >= 128 ? i - 128 : 0;
1592 const size_t hi = std::min(i + 128, scratch.size() - 1);
1598 jaffarCommon::logger::log(
"[J+] Reference pin exact-verification: volatile mask covers %lu / %lu bytes\n", total,
_refVolatileMask.size());
Parallel state-space search engine.
std::atomic< size_t > _getFreeStateAverageTime
Per-thread-average get-free-state time for the step.
std::atomic< size_t > _advanceHashDbAverageTime
Hash-DB advance time reported for the step.
std::atomic< size_t > _getCandidateInputsAverageTime
Per-thread-average get-candidate-inputs time for the step.
std::atomic< size_t > _checkHashThreadRawTime
Summed per-thread hash-checking time for the step.
bool _refPruneRequested
Whether "Reference Reward Prune" was enabled in the engine configuration.
std::atomic< size_t > _advanceStateDbThreadRawTime
Serially-measured state-DB advance time for the step.
size_t getStateSizeInDatabase() const
Returns the size, in bytes, of a single state as stored in the database.
bool isWinCollectionFull()
Whether win collection is enabled and has reached its Max Files cap.
std::atomic< size_t > _droppedStatesFailedSerialization
Counter for states dropped due to failed serialization.
std::atomic< size_t > _popBaseStateDbThreadRawTime
Summed per-thread base-state pop time for the step.
std::atomic< size_t > _stepMaxLevelStored
States stored this step whose level >= the global checkpoint level.
void setReferenceStates(std::vector< std::vector< uint8_t > > &&states, const std::vector< std::string > &refInputs)
Installs the per-depth canonical reference states (from the driver's floor replay) and derives the vo...
size_t getInputHistoryMaxMemoryBytes() const
Hard memory ceiling (bytes) of the shared input-history backing; 0 for None/Raw (no ceiling).
std::atomic< size_t > _advanceHashDbThreadRawTime
Serially-measured hash-DB advance time for the step.
std::atomic< size_t > _getCandidateInputsAverageCumulativeTime
Cumulative per-thread-average get-candidate-inputs time.
void setWinStateCollection(const std::vector< std::string > &dedupProps, const std::string &pathPrefix, const size_t maxFiles)
Enables win-state collection: every win state's solution is saved to pathPrefix + hex(dedup property ...
inputResult_t runInput(Runner &r, const void *baseStateData, const InputSet::inputIndex_t input, threadAccumulator_t &acc, const size_t threadId)
Advances the runner by one input and classifies/stores the resulting state.
std::atomic< size_t > _checkHashAverageCumulativeTime
Cumulative per-thread-average hash-checking time.
std::vector< float > _refPruneTrace
Per-depth floor-reward trace of the reference solution for pruning.
std::atomic< size_t > _totalBaseStatesProcessed
Base states processed across all steps so far.
std::atomic< size_t > _returnFreeStateAverageTime
Per-thread-average return-free-state time for the step.
static constexpr size_t BASE_STATE_BATCH_MAX
Number of base states a worker pulls from the state-DB queue per lock acquisition (batch size).
std::atomic< size_t > _ruleCheckingThreadRawTime
Summed per-thread rule-checking time for the step.
size_t _hashLookahead
N null-advances before hashing (see "Hash Lookahead"); 0 = hash the current state.
size_t getWinCollectedCount()
Number of distinct win solutions collected so far (see setWinStateCollection).
std::atomic< size_t > _refPinHits
Cumulative reference-pin hits (diagnostic).
std::string _winCollectPrefix
Path prefix for saved win-collection .sol files and the manifest.
std::unique_ptr< jaffarPlus::HashDb > _hashDb
Thread-safe hash database used to detect repeated states.
std::atomic< size_t > _getFreeStateThreadRawTime
Summed per-thread get-free-state time for the step.
void initialize()
Resets execution back to step zero and clears all databases and counters.
std::atomic< size_t > _checkpointLevel
Highest checkpoint level reached so far.
size_t _fullStateSize
Full self-contained serialized state size ([hot]+[history]) for standalone snapshot buffers.
std::atomic< size_t > _advanceStateDbAverageCumulativeTime
Cumulative state-DB advance time.
std::string _manualSaveSolutionLastPath
Path of the most recently activated manual-save solution.
std::atomic< size_t > _runnerStateLoadAverageTime
Per-thread-average state-load time for the step.
std::atomic< size_t > _stepBaseStatesProcessed
Base states processed during the current step.
std::atomic< size_t > _calculateRewardAverageTime
Per-thread-average reward-calculation time for the step.
std::atomic< size_t > _runnerStateSaveAverageTime
Per-thread-average state-save time for the step.
size_t _maxThreadStepTime
Maximum per-thread step time for the current step.
std::vector< std::unique_ptr< Runner > > _runners
Collection of runners for the workers to use (one per thread).
manualSaveSolution_t _manualSaveSolution
Best manually saved solution for the current step.
size_t _totalRunningTime
Total running time so far, in microseconds.
std::atomic< size_t > _popBaseStateDbAverageTime
Per-thread-average base-state pop time for the step.
std::atomic< size_t > _calculateHashAverageCumulativeTime
Cumulative per-thread-average hash-calculation time.
std::atomic< size_t > _checkHashAverageTime
Per-thread-average hash-checking time for the step.
size_t _subTotalAverageTime
Sum of all per-operation average times for the current step.
std::atomic< size_t > _winStates
Counter for win states.
stateInfo_t _stepBestWinState
Best win state (by reward) found during the current step.
std::atomic< size_t > _runnerStateSaveThreadRawTime
Summed per-thread state-save time for the step.
std::atomic< size_t > _calculateRewardAverageCumulativeTime
Cumulative per-thread-average reward-calculation time.
size_t _currentStepTime
Overall running time of the current step, in microseconds.
std::atomic< size_t > _runnerStateAdvanceAverageCumulativeTime
Cumulative per-thread-average runner-advance time.
std::atomic< size_t > _droppedStatesBelowReference
Number of states dropped for falling below the reference reward trace (reference pruning).
bool _refPinEnabled
Whether reference pinning is active.
bool _hashDbEnabled
Whether hashing is enabled. Games that cannot loop skip the hash DB to save memory and computation.
std::atomic< size_t > _droppedStatesCheckpoint
Counter for states dropped due to not meeting the checkpoint.
std::vector< std::string > _winDedupPropNames
Names of the game properties whose bytes form the win-collection dedup key.
auto getStepBestWinState() const
Returns a copy of the best win state recorded in the current step.
std::atomic< size_t > _popBaseStateDbAverageCumulativeTime
Cumulative per-thread-average base-state pop time.
std::atomic< size_t > _getAllowedInputsAverageCumulativeTime
Cumulative per-thread-average get-allowed-inputs time.
jaffarCommon::concurrent::HashMap_t< jaffarCommon::hash::hash_t, jaffarCommon::concurrent::HashSet_t< InputSet::inputIndex_t > > _candidateInputsDetected
Per-base-state-input-hash set of candidate inputs already detected, used to dedup candidate-input pro...
std::atomic< size_t > _getAllowedInputsThreadRawTime
Summed per-thread get-allowed-inputs time for the step.
void workerFunction()
Worker body executed in parallel by every thread during a step.
std::vector< threadAccumulator_t > _threadAccumulators
Per-thread accumulators for hot-loop timing/counters, reduced once per step.
const std::vector< std::vector< uint8_t > > & getRefStates() const
Audit accessors: the per-depth canonical reference states and the volatile-byte mask (instance-depend...
std::mutex _winCollectLock
Guards _winKeysSeen and the win-collection file writes across worker threads.
size_t getFullStateSize() const
Full self-contained state size ([hot]+[history]); for standalone snapshots outside the slabs.
std::atomic< size_t > _refPinMaxDepthHit
Deepest reference depth a pin matched (diagnostic).
std::unique_ptr< jaffarPlus::StateDb > _stateDb
Thread-safe state database holding the current and next step's states.
size_t _refPinLookahead
How many future depths to also match (being ahead).
jaffarCommon::hash::hash_t computeStateHash(Runner &r)
Supplies per-depth serialized reference states for exact pin verification, and computes the volatile-...
std::atomic< size_t > _calculateHashAverageTime
Per-thread-average hash-calculation time for the step.
bool isWinCollectionEnabled() const
Whether win collection is enabled.
std::atomic< size_t > _runnerStateSaveAverageCumulativeTime
Cumulative per-thread-average state-save time.
std::atomic< size_t > _checkpointCutoff
Step index after which states below _checkpointLevel are dropped.
bool _manualSaveSolutionUpdatedLastRuleId
Whether the manual-save last-rule id changed this step.
void setReferencePruneTrace(const std::vector< float > &trace)
Arms reference-reward pruning with the per-depth trace: every produced non-win state whose floor rewa...
std::atomic< size_t > _runnerStateLoadThreadRawTime
Summed per-thread state-load time for the step.
std::mutex _stepBestWinStateLock
Guards updates to _stepBestWinState.
std::string _winSolutionPath
File the best win-state's input history is written to (see _bestWinSolutionReward).
std::atomic< size_t > _advanceStateDbAverageTime
State-DB advance time reported for the step.
std::atomic< size_t > _getAllowedInputsAverageTime
Per-thread-average get-allowed-inputs time for the step.
bool _winCollectEnabled
Whether win-state collection is enabled (see setWinStateCollection).
auto getManualSaveSolution() const
Returns a copy of the most recent manually saved solution.
inputResult_t runNewInput(Runner &r, const void *baseStateData, const InputSet::inputIndex_t input, threadAccumulator_t &acc, const size_t threadId)
Re-loads the base state, runs a single input, updates per-outcome counters and checkpoint tracking.
size_t _maxThreadStepTimeThreadId
Id of the thread with the maximum step time.
auto getStateCount() const
Returns the number of states currently held in the state database.
size_t _stateSizeInDatabase
Size of a single state as stored in the database, in bytes.
bool _refPinExactVerify
Whether hash-matched pin candidates are byte-verified against captured reference states.
ssize_t _manualSaveSolutionActiveLastRuleId
Currently active manual-save last-rule id across steps.
std::shared_ptr< void > _inputHistoryBacking
The one shared input-history backing (e.g.
std::atomic< size_t > _repeatedStates
Counter for repeated states (detected via hash collision).
std::unique_ptr< std::atomic< uint8_t >[]> _refPinnedAtDepth
Per reference depth: claimed(1)/unclaimed(0), so exactly one copy is pinned.
std::atomic< size_t > _runnerStateAdvanceAverageTime
Per-thread-average runner-advance time for the step.
std::atomic< size_t > _calculateRewardThreadRawTime
Summed per-thread reward-calculation time for the step.
std::atomic< size_t > _normalStates
Counter for normal states.
std::atomic< size_t > _returnFreeStateAverageCumulativeTime
Cumulative per-thread-average return-free-state time.
std::vector< uint8_t > _refVolatileMask
1 = byte is instance-residue (excluded from exact pin verification)
inputResult_t
Outcome of running a single input on a base state.
@ normal
Resulting state was a normal state and was stored.
@ repeated
Resulting state's hash was already seen.
@ win
Resulting state was a win state.
@ droppedNoStorage
No free state slot was available to store the new state.
@ droppedFailedSerialization
Pushing the state into the database failed (e.g. serialization error).
@ failed
Resulting state was classified as a loss.
@ droppedBelowReference
State's reward fell below the reference trace at its depth (beyond the prune tolerance).
@ droppedCheckpoint
State did not meet the current checkpoint level past the cutoff step.
std::atomic< size_t > _refPinSeenPreDedup
Reference-depth matches seen (diagnostic).
void runStep()
Runs a single search step: expands all current base states in parallel and advances the databases.
std::atomic< size_t > _failedStates
Counter for failed states (reached a point in the game considered a loss).
float _refPruneTolerance
Allowed slack below the reference trace before a state is pruned.
size_t _currentStep
Counter for the current step.
std::atomic< size_t > _returnFreeStateThreadRawTime
Summed per-thread return-free-state time for the step.
float _refPinBonus
Reward bonus for matching the reference at the state's own depth.
std::vector< std::vector< uint8_t > > _refStates
Per-depth serialized reference states for EXACT pin verification (empty = hash-only pinning).
std::atomic< size_t > _checkpointTolerance
Tolerance (in steps) associated with the current checkpoint level.
std::atomic< size_t > _stepNewStatesProcessed
New states processed during the current step.
std::atomic< size_t > _getFreeStateAverageCumulativeTime
Cumulative per-thread-average get-free-state time.
size_t _winCollectMax
Maximum number of distinct win solutions to collect before terminating.
size_t _subTotalAverageCumulativeTime
Sum of all per-operation cumulative average times.
std::mutex _checkpointMutex
Serializes checkpoint level rises (rare path).
bool isReferencePruneRequested() const
Whether "Reference Reward Prune" is enabled in the engine configuration (the driver checks this to kn...
std::atomic< size_t > _advanceHashDbAverageCumulativeTime
Cumulative hash-DB advance time.
std::vector< size_t > _threadStepTime
Per-thread running time of the current step, in microseconds.
std::atomic< size_t > _runnerStateLoadAverageCumulativeTime
Cumulative per-thread-average state-load time.
bool isInputHistoryExhausted() const
True if the shared input-history backing (the Trie) has hit its hard node-storage ceiling.
void printInfo()
Logs engine status: timing breakdown, throughput, state counts, checkpoints, databases,...
auto & getStateDb() const
Returns a reference to the owned state database.
size_t getWinCollectionMax() const
Win collection Max Files cap.
float _bestWinSolutionReward
Highest-reward win seen across ALL steps, and the file its input history is written to at detection t...
bool _refPruneEnabled
Whether reference-reward pruning is armed (requested AND trace supplied).
std::atomic< size_t > _ruleCheckingAverageCumulativeTime
Cumulative per-thread-average rule-checking time.
std::atomic< size_t > _runnerStateAdvanceThreadRawTime
Summed per-thread runner-advance time for the step.
const std::vector< uint8_t > & getRefVolatileMask() const
Audit accessor: per-byte volatile mask (1 = instance-dependent byte) over the reference states.
std::vector< jaffarCommon::hash::hash_t > _refPinHashes
Reference state hash at each search depth.
size_t _baseStateBatch
Active base-state pull batch size ("Base State Batch Size").
float _refPinLookaheadBonus
Extra bonus per frame ahead of the reference.
auto getWinStatesFound() const
Returns the cumulative number of win states found so far.
std::atomic< size_t > _calculateHashThreadRawTime
Summed per-thread hash-calculation time for the step.
size_t getInputHistoryApproxMemoryBytes() const
Current (approximate) live memory (bytes) of the shared input-history backing; 0 for None/Raw.
std::atomic< size_t > _ruleCheckingAverageTime
Per-thread-average rule-checking time for the step.
std::atomic< size_t > _droppedStatesNoStorage
Counter for states dropped due to lack of free states.
Engine(const nlohmann::json &emulatorConfig, const nlohmann::json &gameConfig, const nlohmann::json &runnerConfig, const nlohmann::json &engineConfig)
Constructs the engine, building one runner per worker thread and the state/hash databases.
std::atomic< size_t > _totalNewStatesProcessed
New states processed across all steps so far.
std::mutex _manualSaveSolutionLock
Guards updates to _manualSaveSolution.
size_t _refPruneStepGrace
Prune each state vs the reference this many steps earlier (transient-wait allowance)
~Engine()
Frees the best-win and manual-save state buffers allocated in initialize.
bool _compactLog
"Log Verbosity": "Compact" collapses the per-step engine block to essentials.
std::set< std::string > _winKeysSeen
Dedup keys of the win solutions collected so far.
std::atomic< size_t > _getCandidateInputsThreadRawTime
Summed per-thread get-candidate-inputs time for the step.
size_t getCheckpointTolerance() const
Returns the current state's checkpoint tolerance.
virtual InputSet::inputIndex_t getNullInputIndex() const
The input index representing "no buttons pressed" – used by the engine's Hash Lookahead (advance-with...
bool isSaveSolution() const
Indicates whether the current state should trigger a save solution.
ssize_t getSaveSolutionCurrentLastRuleIdx() const
Returns the current last rule index that set a save solution.
virtual float getFloorReward() const
Reward used for the Reference Reward Floor comparison: the un-biased progress reward,...
void updateReward()
Recomputes the current state's reward from the satisfied rules.
size_t getCheckpointLevel() const
Returns the current state's checkpoint level.
Property * findProperty(const std::string &propertyName)
Finds a registered game property by name.
void evaluateRules()
Evaluates the rule set against the current state.
float getReward() const
Returns the current state's reward.
void updateGameStateType()
Recomputes the state type and checkpoint level from the satisfied rules.
stateType_t getStateType() const
Returns the current state type (normal, win or fail).
@ normal
No win or fail rule is currently satisfied.
@ fail
A fail rule is currently satisfied.
@ win
A win rule is currently satisfied.
const std::string getSaveSolutionPath() const
Returns the save path of the rule that activated the current save solution.
void * getPointer() const
Returns the raw pointer to the property's value in memory.
Owns a Game instance and advances it according to configured inputs.
size_t getStateSize() const
Computes the size in bytes of the serialized runner state.
static std::unique_ptr< Runner > getRunner(const nlohmann::json &emulatorConfig, const nlohmann::json &gameConfig, const nlohmann::json &runnerConfig)
Creates a runner from the emulator, game and runner configurations.
void advanceState(const InputSet::inputIndex_t inputIdx)
Advances the game by one input, then by the configured number of frameskip frames.
void setSearchStep(const size_t searchStep)
Sets the step counter from a search step.
jaffarCommon::hash::hash_t computeHash() const
Computes a hash of the current runner state.
Game * getGame() const
Returns a pointer to the owned game instance.
void serializeState(jaffarCommon::serializer::Base &serializer) const
Serializes the runner state: the game state, the input history, and the input counter.
std::string getInputHistoryString() const
Builds a newline-separated string of the recorded input history.
void deserializeState(jaffarCommon::deserializer::Base &deserializer)
Restores the runner state: the game state, the input history, and the input counter.
size_t getStepCount() const
Returns the current step counter (number of inputs applied / the state's depth).
#define JAFFAR_PROF_ACC(field, var)
Accumulates the microseconds elapsed since timestamp var into field.
#define JAFFAR_PROF_DECL(var)
Declares a timestamp variable var holding the current time (detailed-profiling build).
Abstract base for a JaffarPlus game: wraps an emulator, registers game properties,...
Two-tier (per-domain L1 + shared global L2) hash database used to deduplicate visited search states,...
NUMA topology detection: distance/preference matrices and per-domain delegate-thread selection,...
void initializeNUMA()
Initializes NUMA / core-affinity state.
Drives a Game forward one input at a time, managing the allowed/candidate input sets,...
Per-NUMA-domain database of serialized game states, with reward-ordered queues that feed the search o...
A manually saved solution: its input path, reward, serialized state, and triggering rule index.
ssize_t lastRuleIdx
Index of the last rule active when the state was saved.
float reward
Reward of the saved state.
std::string path
Input sequence (solution path) that reached the saved state.
size_t stepCount
Depth (input count) of the saved state, recorded at capture.
void * stateData
Raw buffer holding the serialized saved state, or nullptr if unset.
A reward value paired with a serialized state buffer.
size_t stepCount
Depth (input count) of the saved state, recorded at capture (the count is not serialized per-state).
void * stateData
Raw buffer holding the serialized state, or nullptr if unset.
float reward
Reward associated with the stored state.
Per-thread accumulator for timing and counters.
size_t normalStates
Number of normal states produced.
size_t runnerStateAdvance
Time spent advancing the runner state with an input.
size_t baseStatesProcessed
Number of base states this thread expanded.
size_t calculateHash
Time spent computing state hashes.
size_t getAllowedInputs
Time spent querying the runner's allowed inputs.
size_t droppedStatesCheckpoint
Number of states dropped for not meeting the checkpoint.
void reset()
Resets all timers and counters to zero.
size_t getCandidateInputs
Time spent querying the runner's candidate inputs.
size_t droppedStatesFailedSerialization
Number of states dropped due to failed serialization.
size_t runnerStateLoad
Time spent loading states into the runner.
size_t repeatedStates
Number of states dropped as repeated.
size_t runnerStateSave
Time spent saving runner states into the state database.
size_t newStatesProcessed
Number of new states this thread produced via inputs.
size_t popBaseStateDb
Time spent popping base-state batches from the state database.
size_t failedStates
Number of states classified as failures.
size_t calculateReward
Time spent computing state rewards.
size_t checkHash
Time spent checking hashes against the hash database.
size_t returnFreeState
Time spent returning state slots to the free queue.
size_t droppedStatesNoStorage
Number of states dropped for lack of free storage.
size_t ruleChecking
Time spent evaluating rules and determining state type.
size_t droppedStatesBelowReference
Number of states dropped for falling below the reference reward trace.
size_t winStates
Number of win states produced.
size_t getFreeState
Time spent acquiring free state slots.