From df189f440820658b6764cefcbc8cafd314d63f3b Mon Sep 17 00:00:00 2001 From: hided62 Date: Mon, 27 Jul 2026 11:02:08 +0000 Subject: [PATCH] compare: trace NPC nation support lifecycle --- .../monthly_npc_nation_lifecycle.json | 77 +++++++++++ hwe/compare/monthly_event_trace.php | 124 ++++++++++++++++++ 2 files changed, 201 insertions(+) create mode 100644 hwe/compare/fixtures/monthly_npc_nation_lifecycle.json diff --git a/hwe/compare/fixtures/monthly_npc_nation_lifecycle.json b/hwe/compare/fixtures/monthly_npc_nation_lifecycle.json new file mode 100644 index 00000000..96d04ab2 --- /dev/null +++ b/hwe/compare/fixtures/monthly_npc_nation_lifecycle.json @@ -0,0 +1,77 @@ +{ + "action": "NpcNationLifecycle", + "setup": { + "resetCities": true, + "resetGenerals": true, + "resetNations": true, + "city": [ + { + "id": 1, + "values": { + "name": "점유도시", + "level": 5, + "nation": 1, + "pop": 10001, + "pop_max": 50001, + "agri": 1001, + "agri_max": 5001, + "comm": 2001, + "comm_max": 6001, + "secu": 3001, + "secu_max": 7001, + "def": 4001, + "def_max": 8001, + "wall": 5001, + "wall_max": 9001 + } + }, + { + "id": 48, + "values": { + "name": "건국도시", + "level": 5, + "nation": 0, + "pop": 10048, + "pop_max": 50048, + "agri": 1048, + "agri_max": 5048, + "comm": 2048, + "comm_max": 6048, + "secu": 3048, + "secu_max": 7048, + "def": 4048, + "def_max": 8048, + "wall": 5048, + "wall_max": 9048 + } + } + ], + "nation": [ + { + "id": 1, + "values": { + "name": "기준국", + "capital": 1, + "gennum": 0, + "tech": 120, + "level": 2, + "type": "che_유가" + } + } + ], + "gameEnvironment": { + "lastNPCTroopLeaderID": 40 + }, + "syncEnvironment": true + }, + "environment": { + "year": 200, + "month": 1, + "startyear": 190, + "turnterm": 10, + "turntime": "0200-01-01 00:00:00", + "show_img_level": 3, + "fiction": [0] + }, + "observe": {} +} diff --git a/hwe/compare/monthly_event_trace.php b/hwe/compare/monthly_event_trace.php index d3dbbfb0..12f2ade1 100644 --- a/hwe/compare/monthly_event_trace.php +++ b/hwe/compare/monthly_event_trace.php @@ -487,6 +487,110 @@ function comparisonInvaderLifecycleTrace( ]; } +/** + * Exercise NPC nation creation and the same-month troop leader follow-up. + * + * @param array $environment + * @return array + */ +function comparisonNpcNationLifecycleTrace( + array $environment, + int $generalIdBeforeAction, + int $nationIdBeforeAction, + int $worldHistoryIdBeforeAction, +): array { + $db = DB::db(); + $actionEnvironment = [ + 'year' => $environment['year'], + 'month' => $environment['month'], + 'startyear' => $environment['startyear'], + 'turnterm' => $environment['turnterm'], + 'turntime' => $environment['turntime'], + 'show_img_level' => $environment['show_img_level'] ?? 3, + 'stored_icons' => $environment['stored_icons'] ?? [], + 'icon_path' => $environment['icon_path'] ?? '.', + 'fiction' => $environment['fiction'] ?? [0], + ]; + $gameStorage = KVStorage::getStorage($db, 'game_env'); + $leaderCounterBefore = (int)($gameStorage->lastNPCTroopLeaderID ?? 0); + + (new \sammo\Event\Action\RaiseNPCNation())->run($actionEnvironment); + $createdNationIds = array_map( + 'intval', + $db->queryFirstColumn('SELECT nation FROM nation WHERE nation > %i ORDER BY nation', $nationIdBeforeAction), + ); + if ($createdNationIds === []) { + throw new \RuntimeException('NpcNationLifecycle failed to create a nation'); + } + $createdNationGeneralCounts = array_map( + static fn(int $nationId): int => (int)DB::db()->queryFirstField( + 'SELECT count(*) FROM general WHERE nation = %i', + $nationId, + ), + $createdNationIds, + ); + $afterRaise = [ + 'createdNationCount' => count($createdNationIds), + 'createdGeneralCount' => (int)$db->queryFirstField( + 'SELECT count(*) FROM general WHERE no > %i', + $generalIdBeforeAction, + ), + 'generalCountsPerCreatedNation' => array_values(array_unique($createdNationGeneralCounts)), + 'historyLogs' => array_values($db->queryFirstColumn( + 'SELECT text FROM world_history WHERE id > %i AND nation_id = 0 ORDER BY id', + $worldHistoryIdBeforeAction, + )), + ]; + + (new \sammo\Event\Action\ProvideNPCTroopLeader())->run($actionEnvironment); + $leaderIds = array_map( + 'intval', + $db->queryFirstColumn('SELECT no FROM general WHERE no > %i AND npc = 5 ORDER BY no', $generalIdBeforeAction), + ); + if ($leaderIds === []) { + throw new \RuntimeException('NpcNationLifecycle failed to create troop leaders'); + } + $leaderNationCounts = array_map( + static fn(int $nationId): int => (int)DB::db()->queryFirstField( + 'SELECT count(*) FROM general WHERE nation = %i AND npc = 5', + $nationId, + ), + array_map( + 'intval', + $db->queryFirstColumn('SELECT nation FROM nation WHERE level > 0 ORDER BY nation'), + ), + ); + $gatherTurnCounts = array_map( + static fn(int $generalId): int => (int)DB::db()->queryFirstField( + 'SELECT count(*) FROM general_turn WHERE general_id = %i AND action = %s', + $generalId, + 'che_집합', + ), + $leaderIds, + ); + $gameStorage->resetCache(); + $afterProvide = [ + 'createdLeaderCount' => count($leaderIds), + 'leaderCountsPerActiveNation' => array_values(array_unique($leaderNationCounts)), + 'troopCount' => (int)$db->queryFirstField( + 'SELECT count(*) FROM troop WHERE troop_leader IN %li', + $leaderIds, + ), + 'gatherTurnCounts' => array_values(array_unique($gatherTurnCounts)), + 'leaderCounterDelta' => (int)$gameStorage->lastNPCTroopLeaderID - $leaderCounterBefore, + ]; + + return [ + 'schemaVersion' => 1, + 'engine' => 'ref', + 'action' => 'NpcNationLifecycle', + 'phases' => [ + 'afterRaise' => $afterRaise, + 'afterProvide' => $afterProvide, + ], + ]; +} + function comparisonMonthlyEventTraceMain(): void { try { @@ -507,6 +611,7 @@ function comparisonMonthlyEventTraceMain(): void 'RegNPC', 'RegNeutralNPC', 'RaiseNPCNation', + 'NpcNationLifecycle', 'RaiseInvader', 'InvaderLifecycle', 'AutoDeleteInvader', @@ -869,6 +974,25 @@ function comparisonMonthlyEventTraceMain(): void ), PHP_EOL; return; } + if ($actionName === 'NpcNationLifecycle') { + foreach (['turnterm', 'turntime'] as $requiredEnvironmentKey) { + if (!array_key_exists($requiredEnvironmentKey, $environment)) { + throw new \InvalidArgumentException( + "NpcNationLifecycle requires environment.{$requiredEnvironmentKey}", + ); + } + } + echo json_encode( + comparisonNpcNationLifecycleTrace( + $environment, + $generalIdBeforeAction, + $nationIdBeforeAction, + $worldHistoryAfterId, + ), + JSON_THROW_ON_ERROR | JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES | JSON_PRESERVE_ZERO_FRACTION, + ), PHP_EOL; + return; + } $actionClass = "\\sammo\\Event\\Action\\{$actionName}"; if (in_array($actionName, ['PreUpdateMonthly', 'PostUpdateMonthly', 'MonthlyBoundary'], true)) {