test: trace scenario 2400 parity

This commit is contained in:
2026-08-05 13:25:58 +00:00
parent ba7676e0f6
commit 191bb6428a
5 changed files with 100 additions and 32 deletions
+32
View File
@@ -0,0 +1,32 @@
<?php
declare(strict_types=1);
namespace sammo;
if (PHP_SAPI !== 'cli' || getenv('REF_DETERMINISTIC_INSTALL_ENABLED') !== '1') {
http_response_code(404);
exit(1);
}
chdir(dirname(__DIR__));
require_once 'lib.php';
$rows = DB::db()->query(
'SELECT general_id, log_type, year, month, text
FROM general_record
WHERE log_type IN ("battle_brief", "battle")
ORDER BY id'
);
echo json_encode([
'schemaVersion' => 1,
'engine' => 'ref',
'logs' => array_map(static fn(array $row): array => [
'generalId' => (int)$row['general_id'],
'category' => $row['log_type'] === 'battle_brief' ? 'BATTLE_BRIEF' : 'BATTLE_DETAIL',
'year' => (int)$row['year'],
'month' => (int)$row['month'],
'text' => (string)$row['text'],
], $rows),
], JSON_THROW_ON_ERROR | JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES), PHP_EOL;
+14 -2
View File
@@ -75,7 +75,11 @@ foreach ($db->query('SELECT * FROM general ORDER BY no') as $row) {
'dex4', 'dex5', 'killTurn', 'affinity', 'birthYear', 'deathYear', 'specAge', 'specAge2'] as $key) {
$general[$key] = (int)$general[$key];
}
$general['turnTime'] = $clock->formatTick(Util::toInt($general['turnTime']), true);
$general['turnTick'] = Util::toInt($general['turnTime']);
$general['turnTime'] = $clock->formatTick($general['turnTick'], true);
$general['recentWarTick'] = $general['recentWarTime'] === null
? null
: Util::toInt($general['recentWarTime']);
$general['recentWarTime'] = $general['recentWarTime'] === null
? null
: $clock->formatTick(Util::toInt($general['recentWarTime']), true);
@@ -102,7 +106,9 @@ foreach ($db->query('SELECT * FROM city ORDER BY city') as $row) {
}
$nations = [];
foreach ($db->query('SELECT * FROM nation ORDER BY nation') as $row) {
$lastAttackableByNation = KVStorage::getValuesFromInterNamespace($db, 'nation_env', 'last_attackable');
$lastCapitalMoveTrialByNation = KVStorage::getValuesFromInterNamespace($db, 'nation_env', 'last천도Trial');
foreach ($db->query('SELECT *, CAST(tech AS DOUBLE) AS tech_raw FROM nation ORDER BY nation') as $row) {
$nation = monthlySnapshotPick($row, [
'id' => 'nation', 'name' => 'name', 'color' => 'color', 'capitalCityId' => 'capital',
'gold' => 'gold', 'rice' => 'rice', 'tech' => 'tech', 'level' => 'level',
@@ -117,6 +123,12 @@ foreach ($db->query('SELECT * FROM nation ORDER BY nation') as $row) {
$nation[$key] = (int)$nation[$key];
}
$nation['tech'] = (float)$nation['tech'];
$nation['techRaw'] = (float)$row['tech_raw'];
$nation['lastAttackable'] = (int)($lastAttackableByNation[$nation['id']] ?? 0);
$lastCapitalMoveTrial = $lastCapitalMoveTrialByNation[$nation['id']] ?? null;
$nation['lastCapitalMoveTrial'] = is_array($lastCapitalMoveTrial)
? [(int)($lastCapitalMoveTrial[0] ?? 0), (int)($lastCapitalMoveTrial[1] ?? 0)]
: null;
$nations[] = $nation;
}
+1 -1
View File
@@ -1734,7 +1734,7 @@ function deleteNation(General $lord, bool $applyDB): array
$nationGeneralList = General::createObjListFromDB(
$db->queryFirstColumn(
'SELECT `no` FROM general WHERE nation=%i AND no != %i',
'SELECT `no` FROM general WHERE nation=%i AND no != %i ORDER BY no ASC',
$nationID,
$lordID
),
+1 -1
View File
@@ -37,7 +37,7 @@ function processWar(string $warSeed, General $attackerGeneral, array $rawAttacke
$city = new WarUnitCity($rng, $rawDefenderCity, $rawDefenderNation, $year, $month, $startYear);
$defenderCityGeneralIDList = $db->queryFirstColumn('SELECT no FROM general WHERE nation=%i AND city=%i AND nation!=0', $city->getVar('nation'), $city->getVar('city'));
$defenderCityGeneralIDList = $db->queryFirstColumn('SELECT no FROM general WHERE nation=%i AND city=%i AND nation!=0 ORDER BY no', $city->getVar('nation'), $city->getVar('city'));
$defenderCityGeneralList = General::createObjListFromDB($defenderCityGeneralIDList, null);
$captureBattleFixture =
+52 -28
View File
@@ -2916,22 +2916,40 @@ class GeneralAI
}
protected function do출병(): ?GeneralCommand
{
if (!$this->attackable) {
return null;
}
if ($this->dipState !== self::d전쟁) {
return null;
protected function do출병(): ?GeneralCommand
{
$traceEnabled = in_array((string)$this->general->getID(), array_filter(explode(',', (string)getenv('REF_AI_TRACE_GENERAL_IDS')), 'strlen'), true);
$trace = static function (string $stage, array $values = []) use ($traceEnabled): void {
if ($traceEnabled) {
fwrite(STDOUT, 'AI_WAR_TRACE ' . Json::encode(['engine' => 'ref', 'stage' => $stage] + $values) . "\n");
}
};
$trace('start', [
'generalId' => $this->general->getID(), 'attackable' => $this->attackable,
'dipState' => $this->dipState, 'nationRice' => $this->nation['rice'],
'baseRice' => GameConst::$baserice, 'train' => $this->general->getVar('train'),
'atmos' => $this->general->getVar('atmos'), 'crew' => $this->general->getVar('crew'),
'fullLeadership' => $this->fullLeadership, 'minWarCrew' => $this->nationPolicy->minWarCrew,
'cityID' => $this->city['city'], 'front' => $this->city['front'],
'warTargetNation' => $this->warTargetNation,
]);
if (!$this->attackable) {
$trace('not-attackable');
return null;
}
if ($this->dipState !== self::d전쟁) {
$trace('not-at-war');
return null;
}
$general = $this->getGeneralObj();
$city = $this->city;
$nation = $this->nation;
if (($nation['rice'] < GameConst::$baserice) && $general->getNPCType() >= 2 && $this->rng->nextBool(0.7)) {
return null;
if (($nation['rice'] < GameConst::$baserice) && $general->getNPCType() >= 2 && $this->rng->nextBool(0.7)) {
$trace('low-nation-rice');
return null;
}
$cityID = $city['city'];
@@ -2939,22 +2957,27 @@ class GeneralAI
$db = DB::db();
if ($general->getVar('train') < min(100, $this->nationPolicy->properWarTrainAtmos)) {
return null;
}
if ($general->getVar('atmos') < min(100, $this->nationPolicy->properWarTrainAtmos)) {
return null;
}
if ($general->getVar('crew') < min(($this->fullLeadership - 2) * 100, $this->nationPolicy->minWarCrew)) {
return null;
}
if ($city['front'] === 0) {
return null;
}
if ($city['front'] === 1) {
return null;
if ($general->getVar('train') < min(100, $this->nationPolicy->properWarTrainAtmos)) {
$trace('low-train');
return null;
}
if ($general->getVar('atmos') < min(100, $this->nationPolicy->properWarTrainAtmos)) {
$trace('low-atmos');
return null;
}
if ($general->getVar('crew') < min(($this->fullLeadership - 2) * 100, $this->nationPolicy->minWarCrew)) {
$trace('low-crew');
return null;
}
if ($city['front'] === 0) {
$trace('front-zero');
return null;
}
if ($city['front'] === 1) {
$trace('front-one');
return null;
}
$attackableNations = [];
@@ -2969,11 +2992,12 @@ class GeneralAI
}
$nearCities = array_keys(CityConst::byID($cityID)->path);
$attackableCities = $db->queryFirstColumn(
$attackableCities = $db->queryFirstColumn(
'SELECT city, nation FROM city WHERE nation IN %li AND city IN %li',
$attackableNations,
$nearCities
);
);
$trace('destinations', ['attackableNations' => $attackableNations, 'nearCities' => $nearCities, 'attackableCities' => $attackableCities]);
if (count($attackableCities) == 0) {
throw new \RuntimeException('출병 불가' . $cityID . var_export($attackableNations, true) . var_export($nearCities, true));