15#include <jaffarCommon/deserializers/contiguous.hpp>
16#include <jaffarCommon/file.hpp>
17#include <jaffarCommon/serializers/contiguous.hpp>
18#include <jaffarCommon/string.hpp>
71 auto currentTime = std::chrono::system_clock::now();
72 _jobId = std::chrono::duration_cast<std::chrono::seconds>(currentTime.time_since_epoch()).count();
76 auto configRemaining = config;
79 auto driverConfig = jaffarCommon::json::popObject(configRemaining,
"Driver Configuration");
89 if (driverConfig.contains(
"End On First Win State"))
_stopFramesAfterFirstWin = jaffarCommon::json::popBoolean(driverConfig,
"End On First Win State") ? 0 : -1;
90 if (driverConfig.contains(
"Stop Frames After First Win"))
91 _stopFramesAfterFirstWin = (ssize_t)jaffarCommon::json::popNumber<size_t>(driverConfig,
"Stop Frames After First Win");
95 if (driverConfig.contains(
"Win State Collection"))
97 auto wc = jaffarCommon::json::popObject(driverConfig,
"Win State Collection");
98 auto enabled = jaffarCommon::json::popBoolean(wc,
"Enabled");
99 std::vector<std::string> props;
100 for (
const auto& n : jaffarCommon::json::getArray<nlohmann::json>(wc,
"Dedup Properties")) props.push_back(n.get<std::string>());
101 wc.erase(
"Dedup Properties");
102 auto prefix = jaffarCommon::json::popString(wc,
"Path Prefix");
103 size_t maxF = wc.contains(
"Max Files") ? jaffarCommon::json::popNumber<size_t>(wc,
"Max Files") : 1000;
104 jaffarCommon::json::checkEmpty(wc,
"Driver Configuration > Win State Collection");
116 _maxSteps = jaffarCommon::json::popNumber<uint32_t>(driverConfig,
"Max Steps");
119 if (
auto* value = std::getenv(
"JAFFAR_DRIVER_OVERRIDE_DRIVER_MAX_STEP"))
_maxSteps = std::stoul(value);
122 auto saveIntermediateResultsJs = jaffarCommon::json::popObject(driverConfig,
"Save Intermediate Results");
127 jaffarCommon::json::checkEmpty(saveIntermediateResultsJs,
"Driver Configuration > Save Intermediate Results");
137 if (driverConfig.contains(
"Reference Reward Floor"))
139 auto refJs = jaffarCommon::json::popObject(driverConfig,
"Reference Reward Floor");
147 _referenceFloorStepGrace = refJs.contains(
"Step Grace") ? jaffarCommon::json::popNumber<uint32_t>(refJs,
"Step Grace") : 0;
153 _referenceSolutionPath = refJs.contains(
"Solution File") ? jaffarCommon::json::popString(refJs,
"Solution File") : std::string();
154 const auto refPath = refJs.contains(
"Path") ? jaffarCommon::json::popString(refJs,
"Path") : std::string();
162 _referenceFloorInitialStatePath = refJs.contains(
"Initial State File Path") ? jaffarCommon::json::popString(refJs,
"Initial State File Path") : std::string();
163 _referenceFloorInitialSequencePath = refJs.contains(
"Initial Sequence File Path") ? jaffarCommon::json::popString(refJs,
"Initial Sequence File Path") : std::string();
167 _cancelIfReferenceBelowWorst = refJs.contains(
"Cancel If Reference Below Worst") ? jaffarCommon::json::popBoolean(refJs,
"Cancel If Reference Below Worst") :
false;
171 _referenceBelowWorstMargin = refJs.contains(
"Below Worst Margin") ? jaffarCommon::json::popNumber<float>(refJs,
"Below Worst Margin") : 0.0f;
172 jaffarCommon::json::checkEmpty(refJs,
"Driver Configuration > Reference Reward Floor");
174 JAFFAR_THROW_LOGIC(
"[ERROR] 'Reference Reward Floor' is enabled but neither 'Solution File' nor 'Path' was provided\n");
181 std::ifstream f(refPath);
182 if (f.good() ==
false) JAFFAR_THROW_RUNTIME(
"[ERROR] Could not open 'Reference Reward Floor' > 'Path': '%s'\n", refPath.c_str());
189 jaffarCommon::json::checkEmpty(driverConfig,
"Driver Configuration");
192 auto emulatorConfig = jaffarCommon::json::popObject(configRemaining,
"Emulator Configuration");
193 auto gameConfig = jaffarCommon::json::popObject(configRemaining,
"Game Configuration");
194 auto runnerConfig = jaffarCommon::json::popObject(configRemaining,
"Runner Configuration");
195 auto engineConfig = jaffarCommon::json::popObject(configRemaining,
"Engine Configuration");
198 jaffarCommon::json::checkEmpty(configRemaining,
"configuration root");
204 _engine = std::make_unique<Engine>(emulatorConfig, gameConfig, runnerConfig, engineConfig);
250 const bool referencePruneRequested =
_engine->isReferencePruneRequested();
254 std::string solutionString;
256 JAFFAR_THROW_RUNTIME(
"[ERROR] Could not open 'Reference Reward Floor' > 'Solution File': '%s'\n",
_referenceSolutionPath.c_str());
257 const auto referenceInputs = jaffarCommon::string::split(solutionString,
'\0');
261 std::vector<uint8_t> initialStateStorage(
_runner->getStateSize());
263 jaffarCommon::serializer::Contiguous s(initialStateStorage.data(), initialStateStorage.size());
267 auto* game =
_runner->getGame();
268 auto* emulator = game->getEmulator();
281 emulator->enableAllStateProperties();
282 jaffarCommon::deserializer::Contiguous d(st.data(), st.size());
283 emulator->deserializeState(d);
284 emulator->reapplyDisabledStateProperties();
288 std::string seqString;
291 const auto prefixInputs = jaffarCommon::string::split(seqString,
'\0');
292 for (
const auto& inputString : prefixInputs)
293 if (inputString.empty() ==
false) emulator->advanceState(emulator->registerInput(inputString));
299 std::vector<std::vector<uint8_t>> referenceStates;
300 const auto captureState = [&]()
305 std::vector<uint8_t> st(
_runner->getStateSize());
307 jaffarCommon::serializer::Contiguous s(st.data(), st.size());
311 jaffarCommon::deserializer::Contiguous d(st.data(), st.size());
315 jaffarCommon::serializer::Contiguous s(st.data(), st.size());
318 referenceStates.push_back(std::move(st));
322 game->evaluateRules();
323 game->updateGameStateType();
324 game->updateReward();
333 std::vector<uint8_t> rt(
_runner->getStateSize());
334 for (
const auto& inputString : referenceInputs)
336 if (inputString.empty())
continue;
338 jaffarCommon::serializer::Contiguous s(rt.data(), rt.size());
342 jaffarCommon::deserializer::Contiguous d(rt.data(), rt.size());
345 _runner->advanceState(emulator->registerInput(inputString));
346 game->evaluateRules();
347 game->updateGameStateType();
348 game->updateReward();
353 std::vector<std::string> refInputStrings;
354 for (
const auto& s : referenceInputs)
355 if (s.empty() ==
false) refInputStrings.push_back(s);
356 _engine->setReferenceStates(std::move(referenceStates), refInputStrings);
360 if (
const char* tracePath = std::getenv(
"JAFFAR_DUMP_REF_TRACE"); tracePath !=
nullptr)
365 jaffarCommon::file::saveStringToFile(out, tracePath);
371 jaffarCommon::deserializer::Contiguous d(initialStateStorage.data(), initialStateStorage.size());
382 if (referencePruneRequested)
387 if (f.good() ==
false) JAFFAR_THROW_RUNTIME(
"[ERROR] Could not open 'Reference Reward Floor' > 'Path': '%s'\n",
_referenceTracePath.c_str());
391 if (
_referenceReward.empty() && std::getenv(
"JAFFAR_IS_DRY_RUN") ==
nullptr)
392 JAFFAR_THROW_LOGIC(
"[ERROR] 'Reference Reward Prune' is enabled but the 'Reference Reward Floor' section provides no trace ('Solution File' or 'Path')\n");
396 jaffarCommon::logger::log(
"[J+] Reference pruning armed: %lu-step trace supplied to the engine\n",
_referenceReward.size());
417 jaffarCommon::logger::initializeTerminal();
423 std::thread intermediateResultSaverThread;
430 if (
_engine->isWinCollectionFull())
443 if (
_engine->getStateCount() == 0)
466 jaffarCommon::logger::log(
"[J+] Reached reference frame count (%lu frames) at step %lu without winning -- can no longer beat the reference, cancelling.\n",
482 const auto& refStates =
_engine->getRefStates();
483 const auto& mask =
_engine->getRefVolatileMask();
484 if (floorRefStep < refStates.size() && refStates[floorRefStep].size() > 0)
487 std::vector<uint8_t> st(
_runner->getStateSize());
489 jaffarCommon::serializer::Contiguous ser(st.data(), st.size());
493 jaffarCommon::deserializer::Contiguous des(st.data(), st.size());
494 _runner->deserializeState(des);
497 jaffarCommon::serializer::Contiguous ser(st.data(), st.size());
500 const auto& ref = refStates[floorRefStep];
501 size_t total = 0, volat = 0;
503 for (
size_t i = 0; i < std::min(st.size(), ref.size()); i++)
507 if (i < mask.size() && mask[i] != 0)
512 if (offs.size() < 400) offs +=
" " + std::to_string(i) +
"(" + std::to_string(ref[i]) +
"->" + std::to_string(st[i]) +
")";
514 jaffarCommon::logger::log(
"[J+] FLOOR AUDIT step %lu: best-vs-ref diffs=%lu (volatile-masked=%lu, causal=%lu); causal offsets:%s\n",
_currentStep, total, volat,
515 total - volat, offs.c_str());
520 jaffarCommon::logger::log(
"[J+] Best (%.6f) fell below reference floor (%.6f, tol %.4f, grace %u steps) at step %lu by %.6f -- cancelling.\n",
_bestStateFloorReward,
540 const size_t ihCeiling =
_engine->getInputHistoryMaxMemoryBytes();
543 const size_t ihNow =
_engine->getInputHistoryApproxMemoryBytes();
544 const bool exhausted =
_engine->isInputHistoryExhausted();
547 const double GB = 1024.0 * 1024.0 * 1024.0;
548 jaffarCommon::logger::log(
"[J+] Input-history trie at %.1f / %.1f GB (%.0f%% of its hard ceiling)%s at step %lu -- stopping "
549 "gracefully. The Trie node pool grows ~ live-states x depth and cannot be enlarged past RAM; switch "
550 "Store Input History Type to \"Raw\" (bounded by 'State Database/Max Size (Mb)'), or lower the State "
551 "DB size so fewer live states slow the trie's growth.\n",
552 (
double)ihNow / GB, (
double)ihCeiling / GB, 100.0 * (
double)ihNow / (
double)ihCeiling, exhausted ?
" (pool exhausted)" :
"",
_currentStep);
582 jaffarCommon::logger::finalizeTerminal();
608 auto manualSaveSolution =
_engine->getManualSaveSolution();
610 if (manualSaveSolution.path !=
"")
614 _runner->setStepCount(manualSaveSolution.stepCount);
615 _engine->getStateDb()->loadStateIntoRunner(*
_runner, manualSaveSolution.stateData);
618 std::string solutionData =
_runner->getInputHistoryString();
619 jaffarCommon::file::saveStringToFile(solutionData, manualSaveSolution.path);
635 std::string jobSuffix = std::string(
".") + std::to_string(
_jobId);
636 std::string stepSuffix = std::string(
".") + std::to_string(
_currentStep);
660 std::string jobSuffix = std::string(
".") + std::to_string(
_jobId);
682 auto lastSaveTime = jaffarCommon::timing::now();
691 auto currentTime = jaffarCommon::timing::now();
692 auto timeElapsedSinceLastSave = jaffarCommon::timing::timeDeltaSeconds(currentTime, lastSaveTime);
702 lastSaveTime = jaffarCommon::timing::now();
717 if (
_engine->getStateDb()->getStateCount() == 0)
return;
723 auto worstState =
_engine->getStateDb()->getWorstState();
764 auto bestState =
_engine->getStateDb()->getBestState();
771 if (
_engine->getWinStatesFound() > 0)
774 auto winStateEntry =
_engine->getStepBestWinState();
805 const auto& refStates =
_engine->getRefStates();
806 const auto& mask =
_engine->getRefVolatileMask();
808 if (depth < refStates.size() && refStates[depth].size() > 0)
810 std::vector<uint8_t> st(
_runner->getStateSize());
812 jaffarCommon::serializer::Contiguous ser(st.data(), st.size());
816 jaffarCommon::deserializer::Contiguous des(st.data(), st.size());
817 _runner->deserializeState(des);
820 jaffarCommon::serializer::Contiguous ser(st.data(), st.size());
823 const auto& ref = refStates[depth];
826 for (
size_t i = 0; i < std::min(st.size(), ref.size()); i++)
827 if (st[i] != ref[i] && (i >= mask.size() || mask[i] == 0))
830 if (offs.size() < 240) offs +=
" " + std::to_string(i) +
"(" + std::to_string(ref[i]) +
"->" + std::to_string(st[i]) +
")";
832 jaffarCommon::logger::log(
"[J+] FLOOR AUDIT step %lu: best-vs-ref causal diffs=%lu; bestFloor=%.6f refFloor=%.6f%s\n", depth, causal,
_bestStateFloorReward,
854 jaffarCommon::logger::clearTerminal();
857 jaffarCommon::logger::log(
"[J+] Job Id: %lu\n",
_jobId);
858 jaffarCommon::logger::log(
"[J+] Script File: '%s'\n",
_configFilePath.c_str());
859 jaffarCommon::logger::log(
"[J+] Emulator Name: '%s'\n",
_runner->getGame()->getEmulator()->getName().c_str());
860 jaffarCommon::logger::log(
"[J+] Game Name: '%s'\n",
_runner->getGame()->getName().c_str());
861 jaffarCommon::logger::log(
"[J+] Current Step #: %lu",
_currentStep);
863 jaffarCommon::logger::log(
"\n");
885 jaffarCommon::logger::log(
886 "[J+] Reference Reward (Ref / Best-Ref): %.6f (Best-Ref %+.6f; graced check vs step %lu: %+.6f, floor tol %.4f) [step %lu / %lu ref steps]%s\n",
890 jaffarCommon::logger::log(
"[J+] Reference Reward (Ref / Best-Ref): %.6f (Best-Ref %+.6f, floor tol %.4f) [step %lu / %lu ref steps]%s\n",
895 jaffarCommon::logger::log(
"[J+] Reference Reward (Ref / Best-Ref): (none: step %lu beyond reference trace of %lu steps)\n",
_currentStep,
910 jaffarCommon::logger::log(
"[J+] Reference Parity Step: %lu (projected frames saved: %+ld)\n", parityStep,
913 jaffarCommon::logger::log(
"[J+] Reference Parity Step: beyond trace end (best exceeds final reference reward; projected savings >= %+ld)\n",
919 jaffarCommon::logger::log(
"[J+] Engine Information: \n");
927 jaffarCommon::logger::log(
"[J+] Runner Information (Best State): \n");
929 jaffarCommon::logger::log(
"[J+] Game Information (Best State): \n");
930 _runner->getGame()->printInfo();
931 jaffarCommon::logger::log(
"[J+] Emulator Information (Best State): \n");
932 _runner->getGame()->getEmulator()->printInfo();
935 jaffarCommon::logger::log(
"[J+] --------------------------------------------------------------\n");
938 jaffarCommon::logger::refreshTerminal();
947 static std::unique_ptr<Driver>
getDriver(
const std::string& configFilePath,
const nlohmann::json& config)
950 auto d = std::make_unique<Driver>(configFilePath, config);
Owns and runs the engine's step loop and reports how the run ended.
std::mutex _updateIntermediateResultMutex
Guards intermediate result storage between the main and saver threads.
size_t _bestWinStateStepCount
Depth of the best win state (recorded at capture; the count is not stored per-state).
float _saveIntermediateFrequency
Minimum interval, in seconds, between intermediate result saves.
bool _cancelIfReferenceBelowWorst
Opt-in: enable the below-worst check (surfaced as a log addendum, not a cancel).
std::string _bestSolutionStorage
Storage for the current best solution's input history.
size_t _winStatesFound
Total number of win states found so far.
float _worstStateReward
Reward for the worst state found so far.
std::string _referenceSolutionPath
Optional reference solution (.sol) replayed at init to build _referenceReward.
int run()
Runs the engine's step loop until a termination condition is met.
size_t getCurrentStep()
Returns the current step counter.
bool _winCollectArm
Whether "Win State Collection" was enabled in the config (applied to the engine post-construction)
exitReason_t
Reason the run loop terminated, returned by run.
@ referenceBelowWorst
The reference reward fell below the worst kept state (reference evicted from the frontier)
@ outOfStates
Engine ran out of states.
@ bestBelowReference
The best state's reward fell below the reference reward floor at this step.
@ exceededReferenceFrames
The run reached the reference's frame count without winning (can no longer beat it)
@ winStateFound
Found a win state.
@ winCollectionFull
Win-state collection reached its Max Files cap.
@ maximumStepReached
Maximum step reached.
@ inputHistoryNearCapacity
The shared input-history trie neared/hit its hard memory ceiling.
size_t _jobId
Job identifier (derived from system time) distinguishing intermediate values between jobs.
std::string _winCollectPrefix
Win-state collection: output path prefix.
std::string _bestStateStorage
Storage for the current best (win or otherwise) state.
std::unique_ptr< Runner > _runner
Runner used for printing information and saving partial results.
float _bestStateFloorReward
Un-biased progress (position) reward of the best state; used for the Reference Reward Floor compariso...
float _referenceFloorTolerance
Allowed shortfall of best below the reference per step.
std::vector< float > _referenceReward
Per-step reference reward floor (index = step).
uint32_t _referenceFloorStepGrace
Compare best against the reference this many steps earlier (bounded time slack for jumpy rewards).
std::string _referenceFloorInitialSequencePath
Optional emulator-level prefix sequence for the reference-lineage rebase.
size_t _currentStep
Counter for the number of steps performed; the initial state counts as step zero.
void storeManualSaveSolution()
Saves a solution explicitly requested by the engine, if any.
~Driver()
Destroys the driver.
void updateWorstState()
Refreshes the tracked worst state, its solution, and its reward.
__volatile__ bool _hasFinished
Internal flag indicating the driver has finished.
bool _referenceFloorEnabled
Whether the reference reward floor cancel is active.
static std::unique_ptr< Driver > getDriver(const std::string &configFilePath, const nlohmann::json &config)
Factory that constructs a driver from configuration.
float _bestStateReward
Ranking reward (magnet-biased) for the best state found so far; drives eviction/display.
void printInfo()
Prints the current state of execution to the logger.
ssize_t _firstWinStep
Step at which the first win was found.
std::string _saveIntermediateBestSolutionPath
Path to store the best solution found so far.
size_t _maxSteps
Maximum number of steps (zero = not established).
void updateBestState()
Refreshes the tracked best state, its solution, and its reward.
ssize_t _stopFramesAfterFirstWin
Stop N steps after the first win (0 = immediately; -1 = never stop on wins)
std::vector< int > _referenceStateType
Per-step reference state type (normal/win/fail) recorded during the floor replay.
std::vector< std::string > _winCollectProps
Win-state collection: dedup property names.
std::string _referenceFloorInitialStatePath
Optional raw state file rebasing the floor replay onto the reference's own lineage.
std::unique_ptr< Engine > _engine
Pointer to the internal Jaffar engine.
float _referenceBelowWorstMargin
Margin added to the reference before the below-worst comparison (typically the pinning bonus).
void initialize()
Resets the execution back to the starting point.
void saveBestStateInformation()
Writes the current best solution to file, under the mutex.
std::string _worstSolutionStorage
Storage for the current worst solution's input history.
bool _saveIntermediateResultsEnabled
Whether to store intermediate results at all.
void saveWorstStateInformation()
Writes the current worst solution to file, under the mutex.
bool _referenceBelowWorstNow
Whether the reference is currently below the worst kept state (per-step log addendum).
float _bestWinStateReward
Reward for the best win state found so far.
size_t _stateSize
Storage size of a runner state.
Driver(const std::string &configFilePath, const nlohmann::json &config)
Constructs the driver and its engine/runner from the parsed configuration.
std::string _saveIntermediateWorstSolutionPath
Path to store the worst solution found so far.
void intermediateResultSaveLoop()
Background loop that periodically saves best and worst solutions to file.
std::string _worstStateStorage
Storage for the current worst (win or otherwise) state.
size_t _winCollectMax
Win-state collection: Max Files cap.
size_t _bestStateStepCount
Depth of the current best state, set by updateBestState() and reused by the printInfo reload.
std::string _referenceTracePath
Legacy precomputed-trace file path (loaded on demand if only the engine-side prune needs it)
const std::string _configFilePath
Path to the config file, kept for reference.
double _inputHistoryCapacityWatermark
Fraction of the input-history trie's hard ceiling at which the run stops gracefully (high-water mark)...
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.
Parallel breadth-first search engine that expands game states step by step, deduplicating via a hash ...
Abstract base for a JaffarPlus game: wraps an emulator, registers game properties,...
Drives a Game forward one input at a time, managing the allowed/candidate input sets,...