test(compare): trace deterministic monthly seed progression
This commit is contained in:
@@ -6,8 +6,26 @@ use sammo\Enums\EventTarget;
|
||||
use sammo\Enums\InheritanceKey;
|
||||
use \Symfony\Component\Lock;
|
||||
|
||||
class TurnExecutionHelper
|
||||
{
|
||||
class TurnExecutionHelper
|
||||
{
|
||||
/** @var array<string, int> */
|
||||
private static array $comparisonActionCounts = [];
|
||||
/** @var array<string, list<int>> */
|
||||
private static array $comparisonActionGeneralIds = [];
|
||||
|
||||
/** @return array<string, int> */
|
||||
public static function getComparisonActionCounts(): array
|
||||
{
|
||||
ksort(self::$comparisonActionCounts, SORT_STRING);
|
||||
return self::$comparisonActionCounts;
|
||||
}
|
||||
|
||||
/** @return array<string, list<int>> */
|
||||
public static function getComparisonActionGeneralIds(): array
|
||||
{
|
||||
ksort(self::$comparisonActionGeneralIds, SORT_STRING);
|
||||
return self::$comparisonActionGeneralIds;
|
||||
}
|
||||
/** @var General*/
|
||||
protected $generalObj;
|
||||
|
||||
@@ -241,15 +259,54 @@ class TurnExecutionHelper
|
||||
$currentTurn = null;
|
||||
|
||||
$gameStor = KVStorage::getStorage($db, 'game_env');
|
||||
$autorun_user = $gameStor->autorun_user;
|
||||
$autorun_user = $gameStor->autorun_user;
|
||||
$traceCityChanges = PHP_SAPI === 'cli'
|
||||
&& getenv('REF_DETERMINISTIC_INSTALL_ENABLED') === '1'
|
||||
&& getenv('REF_AI_TRACE_CITY_CHANGES') === '1';
|
||||
$observedCityStates = [];
|
||||
if ($traceCityChanges) {
|
||||
foreach ($db->query('SELECT city, nation, front, def, wall, pop, state, term, trust FROM city ORDER BY city') as $row) {
|
||||
$observedCityStates[(int)$row['city']] = [
|
||||
(int)$row['nation'],
|
||||
(int)$row['front'],
|
||||
(int)$row['def'],
|
||||
(int)$row['wall'],
|
||||
(int)$row['pop'],
|
||||
(int)$row['state'],
|
||||
(int)$row['term'],
|
||||
(float)$row['trust'],
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
foreach ($generalsTodo as $rawGeneral) {
|
||||
$currActionTime = new \DateTimeImmutable();
|
||||
if ($currActionTime > $limitActionTime) {
|
||||
return [true, $currentTurn];
|
||||
}
|
||||
foreach ($generalsTodo as $rawGeneral) {
|
||||
// The comparison harness fixes the logical clock. A real wall-clock
|
||||
// timeout here made the final post-month drain depend on host load,
|
||||
// so identical seeds processed a variable number of new generals.
|
||||
// Preserve the production timeout unless deterministic comparison
|
||||
// mode was explicitly enabled.
|
||||
if (getenv('REF_DETERMINISTIC_INSTALL_ENABLED') !== '1') {
|
||||
$currActionTime = new \DateTimeImmutable();
|
||||
if ($currActionTime > $limitActionTime) {
|
||||
return [true, $currentTurn];
|
||||
}
|
||||
}
|
||||
|
||||
$general = General::createObjFromDB($rawGeneral['no']);
|
||||
$general = General::createObjFromDB($rawGeneral['no']);
|
||||
if (
|
||||
PHP_SAPI === 'cli'
|
||||
&& getenv('REF_DETERMINISTIC_INSTALL_ENABLED') === '1'
|
||||
&& getenv('REF_AI_TRACE_SEQUENCE') === '1'
|
||||
) {
|
||||
fwrite(STDOUT, sprintf(
|
||||
"TURN_START_REF id=%d nation=%d city=%d officer=%d turnTime=%s\n",
|
||||
$general->getID(),
|
||||
$general->getNationID(),
|
||||
$general->getCityID(),
|
||||
$general->getVar('officer_level'),
|
||||
$general->getTurnTime()
|
||||
));
|
||||
}
|
||||
$nationStor = KVStorage::getStorage($db, $general->getNationID(), 'nation_env');
|
||||
$turnObj = new static($general);
|
||||
|
||||
@@ -302,9 +359,26 @@ class TurnExecutionHelper
|
||||
if (!($nationCommandObj instanceof Command\Nation\휴식)) {
|
||||
$hasReservedTurn = true;
|
||||
}
|
||||
if ($ai && ($general->getAuxVar('use_auto_nation_turn') ?? 1)) {
|
||||
$nationCommandObj = $ai->chooseNationTurn($nationCommandObj);
|
||||
$cityName = CityConst::byID($general->getCityID())->name;
|
||||
if ($ai && ($general->getAuxVar('use_auto_nation_turn') ?? 1)) {
|
||||
$nationCommandObj = $ai->chooseNationTurn($nationCommandObj);
|
||||
$traceNationIds = getenv('REF_AI_TRACE_NATION_IDS');
|
||||
if (
|
||||
PHP_SAPI === 'cli'
|
||||
&& getenv('REF_DETERMINISTIC_INSTALL_ENABLED') === '1'
|
||||
&& is_string($traceNationIds)
|
||||
&& in_array((string) $general->getID(), explode(',', $traceNationIds), true)
|
||||
) {
|
||||
fwrite(STDOUT, 'AI_NATION_TRACE ' . Json::encode([
|
||||
'generalId' => $general->getID(),
|
||||
'nationId' => $general->getNationID(),
|
||||
'year' => $year,
|
||||
'month' => $month,
|
||||
'action' => $nationCommandObj->getRawClassName(),
|
||||
'args' => $nationCommandObj->getArg(),
|
||||
'reason' => $nationCommandObj->reason,
|
||||
]) . "\n");
|
||||
}
|
||||
$cityName = CityConst::byID($general->getCityID())->name;
|
||||
LogText("NationTurn", "General, {$general->getName()}, {$general->getID()}, {$cityName}, {$general->getStaticNation()['name']}, {$nationCommandObj->getBrief()}, {$nationCommandObj->reason}, ");
|
||||
}
|
||||
$rng = new RandUtil(new LiteHashDRBG(Util::simpleSerialize(
|
||||
@@ -319,8 +393,20 @@ class TurnExecutionHelper
|
||||
$rng,
|
||||
$nationCommandObj
|
||||
);
|
||||
$nationStor->setValue($lastNationTurnKey, $resultNationTurn->toRaw());
|
||||
$general->setRawCity(null);
|
||||
$nationStor->setValue($lastNationTurnKey, $resultNationTurn->toRaw());
|
||||
if (
|
||||
PHP_SAPI === 'cli'
|
||||
&& getenv('REF_DETERMINISTIC_INSTALL_ENABLED') === '1'
|
||||
&& getenv('REF_AI_TRACE_SEQUENCE') === '1'
|
||||
) {
|
||||
fwrite(STDOUT, sprintf(
|
||||
"ACTION_REF kind=nation actor=%d requested=%s resolved=%s\n",
|
||||
$general->getID(),
|
||||
$nationCommandObj->getRawClassName(),
|
||||
$nationCommandObj->getRawClassName()
|
||||
));
|
||||
}
|
||||
$general->setRawCity(null);
|
||||
}
|
||||
|
||||
$generalCommandObj = $general->getReservedTurn(0, $env);
|
||||
@@ -328,16 +414,57 @@ class TurnExecutionHelper
|
||||
$hasReservedTurn = true;
|
||||
}
|
||||
|
||||
if ($ai) {
|
||||
$newGeneralCommandObj = $ai->chooseGeneralTurn($generalCommandObj); // npc AI 처리
|
||||
if ($generalCommandObj !== $newGeneralCommandObj) {
|
||||
$autorunMode = true;
|
||||
$generalCommandObj = $newGeneralCommandObj;
|
||||
}
|
||||
$cityName = CityConst::byID($general->getCityID())->name;
|
||||
if ($ai) {
|
||||
$newGeneralCommandObj = $ai->chooseGeneralTurn($generalCommandObj); // npc AI 처리
|
||||
if ($generalCommandObj !== $newGeneralCommandObj) {
|
||||
$autorunMode = true;
|
||||
$generalCommandObj = $newGeneralCommandObj;
|
||||
}
|
||||
$traceGeneralIds = getenv('REF_AI_TRACE_GENERAL_IDS');
|
||||
if (
|
||||
PHP_SAPI === 'cli'
|
||||
&& getenv('REF_DETERMINISTIC_INSTALL_ENABLED') === '1'
|
||||
&& is_string($traceGeneralIds)
|
||||
&& in_array((string) $general->getID(), explode(',', $traceGeneralIds), true)
|
||||
) {
|
||||
fwrite(STDOUT, 'AI_TRACE ' . Json::encode([
|
||||
'generalId' => $general->getID(),
|
||||
'year' => $year,
|
||||
'month' => $month,
|
||||
'npc' => $general->getNPCType(),
|
||||
'cityId' => $general->getCityID(),
|
||||
'gold' => $general->getVar('gold'),
|
||||
'rice' => $general->getVar('rice'),
|
||||
'affinity' => $general->getVar('affinity'),
|
||||
'stats' => [
|
||||
'leadership' => $general->getVar('leadership'),
|
||||
'strength' => $general->getVar('strength'),
|
||||
'intel' => $general->getVar('intel'),
|
||||
],
|
||||
'fullStats' => [
|
||||
'leadership' => $general->getLeadership(false),
|
||||
'strength' => $general->getStrength(false),
|
||||
'intel' => $general->getIntel(false),
|
||||
],
|
||||
'action' => $generalCommandObj->getRawClassName(),
|
||||
'args' => $generalCommandObj->getArg(),
|
||||
'reason' => $generalCommandObj->reason,
|
||||
]) . "\n");
|
||||
}
|
||||
$cityName = CityConst::byID($general->getCityID())->name;
|
||||
LogText("turn", "General, {$general->getName()}, {$general->getID()}, {$cityName}, {$general->getStaticNation()['name']}, {$generalCommandObj->getBrief()}, {$generalCommandObj->reason}, ");
|
||||
}
|
||||
$rng = new RandUtil(new LiteHashDRBG(Util::simpleSerialize(
|
||||
if (
|
||||
PHP_SAPI === 'cli'
|
||||
&& getenv('REF_DETERMINISTIC_INSTALL_ENABLED') === '1'
|
||||
&& getenv('REF_AI_TRACE_SUMMARY') === '1'
|
||||
) {
|
||||
$actionName = $generalCommandObj->getRawClassName();
|
||||
self::$comparisonActionCounts[$actionName] =
|
||||
(self::$comparisonActionCounts[$actionName] ?? 0) + 1;
|
||||
self::$comparisonActionGeneralIds[$actionName][] = $general->getID();
|
||||
}
|
||||
$rng = new RandUtil(new LiteHashDRBG(Util::simpleSerialize(
|
||||
UniqueConst::$hiddenSeed,
|
||||
'generalCommand',
|
||||
$year,
|
||||
@@ -345,8 +472,82 @@ class TurnExecutionHelper
|
||||
$general->getID(),
|
||||
$generalCommandObj->getRawClassName()
|
||||
)));
|
||||
$turnObj->processCommand($rng, $generalCommandObj, $autorunMode);
|
||||
}
|
||||
$turnObj->processCommand($rng, $generalCommandObj, $autorunMode);
|
||||
if ($traceCityChanges) {
|
||||
$changes = [];
|
||||
foreach ($db->query('SELECT city, nation, front, def, wall, pop, state, term, trust FROM city ORDER BY city') as $row) {
|
||||
$cityId = (int)$row['city'];
|
||||
$next = [
|
||||
(int)$row['nation'],
|
||||
(int)$row['front'],
|
||||
(int)$row['def'],
|
||||
(int)$row['wall'],
|
||||
(int)$row['pop'],
|
||||
(int)$row['state'],
|
||||
(int)$row['term'],
|
||||
(float)$row['trust'],
|
||||
];
|
||||
if (($observedCityStates[$cityId] ?? null) !== $next) {
|
||||
$changes[] = [
|
||||
'id' => $cityId,
|
||||
'before' => $observedCityStates[$cityId] ?? null,
|
||||
'after' => $next,
|
||||
];
|
||||
$observedCityStates[$cityId] = $next;
|
||||
}
|
||||
}
|
||||
if ($changes) {
|
||||
fwrite(STDOUT, 'CITY_CHANGE_REF ' . Json::encode([
|
||||
'actor' => $general->getID(),
|
||||
'changes' => $changes,
|
||||
]) . "\n");
|
||||
}
|
||||
}
|
||||
$traceCityIds = array_values(array_filter(array_map(
|
||||
'intval',
|
||||
explode(',', (string)getenv('REF_AI_TRACE_CITY_IDS'))
|
||||
)));
|
||||
if (
|
||||
PHP_SAPI === 'cli'
|
||||
&& getenv('REF_DETERMINISTIC_INSTALL_ENABLED') === '1'
|
||||
&& $traceCityIds
|
||||
&& in_array((string)$general->getID(), explode(',', (string)getenv('REF_AI_TRACE_GENERAL_IDS')), true)
|
||||
) {
|
||||
fwrite(STDOUT, 'AI_CITY_STATE_TRACE ' . Json::encode([
|
||||
'generalId' => $general->getID(),
|
||||
'cities' => $db->query(
|
||||
'SELECT city, nation, front, supply, def, wall, state, term FROM city WHERE city IN %li ORDER BY city',
|
||||
$traceCityIds
|
||||
),
|
||||
]) . "\n");
|
||||
}
|
||||
if (
|
||||
PHP_SAPI === 'cli'
|
||||
&& getenv('REF_DETERMINISTIC_INSTALL_ENABLED') === '1'
|
||||
&& getenv('REF_AI_TRACE_SEQUENCE') === '1'
|
||||
) {
|
||||
fwrite(STDOUT, sprintf(
|
||||
"ACTION_REF kind=general actor=%d requested=%s resolved=%s\n",
|
||||
$general->getID(),
|
||||
$generalCommandObj->getRawClassName(),
|
||||
$generalCommandObj->getRawClassName()
|
||||
));
|
||||
}
|
||||
$traceGeneralIds = getenv('REF_AI_TRACE_GENERAL_IDS');
|
||||
if (
|
||||
PHP_SAPI === 'cli'
|
||||
&& getenv('REF_DETERMINISTIC_INSTALL_ENABLED') === '1'
|
||||
&& is_string($traceGeneralIds)
|
||||
&& in_array((string) $general->getID(), explode(',', $traceGeneralIds), true)
|
||||
) {
|
||||
fwrite(STDOUT, 'AI_GENERAL_POST_TRACE ' . Json::encode([
|
||||
'generalId' => $general->getID(),
|
||||
'action' => $generalCommandObj->getRawClassName(),
|
||||
'gold' => $general->getVar('gold'),
|
||||
'rice' => $general->getVar('rice'),
|
||||
]) . "\n");
|
||||
}
|
||||
}
|
||||
pullNationCommand($general->getVar('nation'), $general->getVar('officer_level'));
|
||||
pullGeneralCommand($general->getID());
|
||||
|
||||
@@ -360,8 +561,31 @@ class TurnExecutionHelper
|
||||
$general->setAuxVar('autorun_limit', $autorun_limit);
|
||||
}
|
||||
|
||||
$turnObj->updateTurnTime();
|
||||
$turnObj->applyDB();
|
||||
$turnObj->updateTurnTime();
|
||||
$traceGeneralIds = getenv('REF_AI_TRACE_GENERAL_IDS');
|
||||
$traceCurrentGeneral =
|
||||
PHP_SAPI === 'cli'
|
||||
&& getenv('REF_DETERMINISTIC_INSTALL_ENABLED') === '1'
|
||||
&& is_string($traceGeneralIds)
|
||||
&& in_array((string) $general->getID(), explode(',', $traceGeneralIds), true);
|
||||
if ($traceCurrentGeneral) {
|
||||
fwrite(STDOUT, 'AI_GENERAL_PRE_APPLY_TRACE ' . Json::encode([
|
||||
'generalId' => $general->getID(),
|
||||
'gold' => $general->getVar('gold'),
|
||||
'rice' => $general->getVar('rice'),
|
||||
'updates' => $general->getUpdatedValues(),
|
||||
]) . "\n");
|
||||
}
|
||||
$turnObj->applyDB();
|
||||
if ($traceCurrentGeneral) {
|
||||
fwrite(STDOUT, 'AI_GENERAL_DB_TRACE ' . Json::encode([
|
||||
'generalId' => $general->getID(),
|
||||
'stored' => $db->queryFirstRow(
|
||||
'SELECT nation,city,gold,rice,crew,crewtype,train,atmos,leadership,strength,intel,leadership_exp,strength_exp,intel_exp,dex1,dex2,dex3,dex4,dex5 FROM general WHERE no=%i',
|
||||
$general->getID()
|
||||
),
|
||||
]) . "\n");
|
||||
}
|
||||
}
|
||||
|
||||
return [false, $currentTurn];
|
||||
@@ -458,27 +682,54 @@ class TurnExecutionHelper
|
||||
return $gameStor->turntime;
|
||||
}
|
||||
|
||||
$monthlyRng = new RandUtil(new LiteHashDRBG(Util::simpleSerialize(
|
||||
$monthlyRng = new RandUtil(new LiteHashDRBG(Util::simpleSerialize(
|
||||
UniqueConst::$hiddenSeed,
|
||||
'monthly',
|
||||
$gameStor->year,
|
||||
$gameStor->month
|
||||
)));
|
||||
|
||||
// 1달마다 처리하는 것들, 벌점 감소 및 건국,전턴,합병 -1, 군량 소모
|
||||
static::runEventHandler($db, $gameStor, EventTarget::PreMonth);
|
||||
if (!preUpdateMonthly()) {
|
||||
$gameStor->month
|
||||
)));
|
||||
|
||||
$traceMonthlyGeneral = static function (string $phase) use ($db): void {
|
||||
$traceGeneralIds = getenv('REF_AI_TRACE_GENERAL_IDS');
|
||||
if (
|
||||
PHP_SAPI !== 'cli'
|
||||
|| getenv('REF_DETERMINISTIC_INSTALL_ENABLED') !== '1'
|
||||
|| !is_string($traceGeneralIds)
|
||||
) {
|
||||
return;
|
||||
}
|
||||
foreach (array_filter(explode(',', $traceGeneralIds), 'strlen') as $generalID) {
|
||||
$stored = $db->queryFirstRow('SELECT gold,rice FROM general WHERE no=%i', (int)$generalID);
|
||||
if ($stored) {
|
||||
fwrite(STDOUT, 'AI_MONTH_PHASE_TRACE ' . Json::encode([
|
||||
'phase' => $phase,
|
||||
'generalId' => (int)$generalID,
|
||||
'stored' => $stored,
|
||||
]) . "\n");
|
||||
}
|
||||
}
|
||||
};
|
||||
$traceMonthlyGeneral('before-pre-month-event');
|
||||
|
||||
// 1달마다 처리하는 것들, 벌점 감소 및 건국,전턴,합병 -1, 군량 소모
|
||||
static::runEventHandler($db, $gameStor, EventTarget::PreMonth);
|
||||
$traceMonthlyGeneral('after-pre-month-event');
|
||||
if (!preUpdateMonthly()) {
|
||||
unlock();
|
||||
throw new \RuntimeException('preUpdateMonthly() 처리 에러');
|
||||
}
|
||||
turnDate($nextTurn);
|
||||
throw new \RuntimeException('preUpdateMonthly() 처리 에러');
|
||||
}
|
||||
$traceMonthlyGeneral('after-pre-update');
|
||||
turnDate($nextTurn);
|
||||
$traceMonthlyGeneral('after-turn-date');
|
||||
|
||||
// 분기계산. 장수들 턴보다 먼저 있다면 먼저처리
|
||||
if ($gameStor->month == 1) {
|
||||
checkStatistic();
|
||||
}
|
||||
static::runEventHandler($db, $gameStor, EventTarget::Month);
|
||||
postUpdateMonthly($monthlyRng);
|
||||
static::runEventHandler($db, $gameStor, EventTarget::Month);
|
||||
$traceMonthlyGeneral('after-month-event');
|
||||
postUpdateMonthly($monthlyRng);
|
||||
$traceMonthlyGeneral('after-post-update');
|
||||
|
||||
// 다음달로 넘김
|
||||
$prevTurn = $nextTurn;
|
||||
|
||||
Reference in New Issue
Block a user