From 6176b64c28b6980cb3d53003e05ac2593746a766 Mon Sep 17 00:00:00 2001 From: hided62 Date: Sat, 25 Jul 2026 21:57:10 +0000 Subject: [PATCH] test(compare): trace monthly nation statistics --- .../fixtures/monthly_post_nation_stats.json | 162 ++++++++++++++++++ hwe/compare/monthly_event_trace.php | 59 ++++++- 2 files changed, 220 insertions(+), 1 deletion(-) create mode 100644 hwe/compare/fixtures/monthly_post_nation_stats.json diff --git a/hwe/compare/fixtures/monthly_post_nation_stats.json b/hwe/compare/fixtures/monthly_post_nation_stats.json new file mode 100644 index 00000000..17774e60 --- /dev/null +++ b/hwe/compare/fixtures/monthly_post_nation_stats.json @@ -0,0 +1,162 @@ +{ + "action": "PostUpdateMonthly", + "environment": { + "year": 193, + "month": 2, + "startyear": 193, + "seedYear": 193, + "seedMonth": 1 + }, + "setup": { + "world": { + "hiddenSeed": "monthly-post-nation-stats-fixture" + }, + "syncEnvironment": true, + "resetCities": true, + "resetGenerals": true, + "deleteOtherGenerals": true, + "keepGeneralIds": [1, 2], + "resetNations": true, + "deleteOtherNations": true, + "keepNationIds": [1, 2], + "resetDiplomacy": true, + "general": [ + { + "id": 1, + "values": { + "nation": 1, + "npc": 0, + "gold": 1000, + "rice": 2000, + "crew": 100, + "leadership": 50, + "strength": 40, + "intel": 30, + "experience": 100, + "dedication": 200, + "dex1": 1000, + "dex2": 0, + "dex3": 0, + "dex4": 0, + "dex5": 0 + } + }, + { + "id": 2, + "values": { + "nation": 2, + "npc": 2, + "gold": 3000, + "rice": 4000, + "crew": 200, + "leadership": 60, + "strength": 50, + "intel": 40, + "experience": 300, + "dedication": 400, + "dex1": 2000, + "dex2": 1000, + "dex3": 0, + "dex4": 0, + "dex5": 0 + } + } + ], + "rankData": [ + {"generalId": 1, "type": "killcrew_person", "value": 100}, + {"generalId": 1, "type": "deathcrew_person", "value": 0}, + {"generalId": 2, "type": "killcrew_person", "value": 0}, + {"generalId": 2, "type": "deathcrew_person", "value": 100} + ], + "nation": [ + { + "id": 1, + "values": { + "name": "갑국", + "gold": 10000, + "rice": 20000, + "tech": 100, + "level": 2, + "gennum": 9, + "power": 7 + } + }, + { + "id": 2, + "values": { + "name": "을국", + "gold": 2000, + "rice": 3000, + "tech": 50, + "level": 1, + "gennum": 8, + "power": 8 + } + } + ], + "city": [ + { + "id": 1, + "values": { + "name": "갑성", + "nation": 1, + "level": 4, + "supply": 1, + "pop": 1000, + "pop_max": 2000, + "agri": 100, + "agri_max": 200, + "comm": 100, + "comm_max": 200, + "secu": 100, + "secu_max": 200, + "def": 100, + "def_max": 200, + "wall": 100, + "wall_max": 200 + } + }, + { + "id": 2, + "values": { + "name": "을성", + "nation": 2, + "level": 4, + "supply": 1, + "pop": 2000, + "pop_max": 4000, + "agri": 200, + "agri_max": 400, + "comm": 200, + "comm_max": 400, + "secu": 200, + "secu_max": 400, + "def": 200, + "def_max": 400, + "wall": 200, + "wall_max": 400 + } + } + ], + "nationEnvironment": [ + { + "nationId": 1, + "key": "max_power", + "value": { + "maxPower": 999, + "maxCrew": 50, + "maxCities": ["옛도시", "옛도시2"] + } + }, + {"nationId": 1, "key": "available_war_setting_cnt", "value": 1} + ] + }, + "observe": { + "generalIds": [1, 2], + "nationIds": [1, 2], + "cityIds": [1, 2], + "rankGeneralIds": [1, 2], + "rankTypes": ["killcrew_person", "deathcrew_person"], + "nationEnvironmentKeys": ["max_power", "available_war_setting_cnt"] + } +} diff --git a/hwe/compare/monthly_event_trace.php b/hwe/compare/monthly_event_trace.php index f76b55e0..51a4779c 100644 --- a/hwe/compare/monthly_event_trace.php +++ b/hwe/compare/monthly_event_trace.php @@ -166,6 +166,9 @@ function comparisonMonthlyDetails(array $observe, int $worldHistoryAfterId): arr 'rice' => 'rice', 'rate' => 'rate_tmp', 'baseRate' => 'rate', + 'power' => 'power', + 'generalCount' => 'gennum', + 'tech' => 'tech', 'typeCode' => 'type', 'scout' => 'scout', 'strategicCommandLimit' => 'strategic_cmd_limit', @@ -229,6 +232,22 @@ function comparisonMonthlyDetails(array $observe, int $worldHistoryAfterId): arr $accessGeneralIds, )); } + $nationEnvironment = []; + $nationEnvironmentKeys = $observe['nationEnvironmentKeys'] ?? []; + if ( + !is_array($nationEnvironmentKeys) + || array_filter($nationEnvironmentKeys, static fn($key): bool => !is_string($key)) + ) { + throw new \InvalidArgumentException('nationEnvironmentKeys must be an array of strings'); + } + foreach (comparisonIntegerList($observe['nationIds'] ?? [], 'nationIds') as $nationId) { + $entry = ['nationId' => $nationId]; + $storage = KVStorage::getStorage($db, $nationId, 'nation_env'); + foreach ($nationEnvironmentKeys as $key) { + $entry[$key] = $storage->getValue($key); + } + $nationEnvironment[] = $entry; + } return [ 'generals' => $generals, 'cities' => $cities, @@ -237,6 +256,7 @@ function comparisonMonthlyDetails(array $observe, int $worldHistoryAfterId): arr 'inheritancePoints' => $inheritancePoints, 'rankData' => $rankData, 'generalAccessLogs' => $generalAccessLogs, + 'nationEnvironment' => $nationEnvironment, ]; } @@ -273,6 +293,7 @@ function comparisonMonthlyEventTraceMain(): void 'LostUniqueItem', 'MergeInheritPointRank', 'PreUpdateMonthly', + 'PostUpdateMonthly', ]; if (!in_array($actionName, $supportedActions, true)) { throw new \InvalidArgumentException('unsupported monthly action'); @@ -440,6 +461,20 @@ function comparisonMonthlyEventTraceMain(): void 'refresh_score_total' => $row['refreshScoreTotal'], ]); } + foreach ($setup['nationEnvironment'] ?? [] as $row) { + if ( + !is_array($row) + || !is_int($row['nationId'] ?? null) + || !is_string($row['key'] ?? null) + || !array_key_exists('value', $row) + ) { + throw new \InvalidArgumentException('invalid setup.nationEnvironment row'); + } + KVStorage::getStorage(DB::db(), $row['nationId'], 'nation_env')->setValue( + $row['key'], + $row['value'], + ); + } if (($setup['resetDiplomacy'] ?? false) === true) { DB::db()->query('DELETE FROM diplomacy'); } @@ -479,6 +514,16 @@ function comparisonMonthlyEventTraceMain(): void throw new \InvalidArgumentException('setup.gameEnvironment must be an object'); } $gameStorage = KVStorage::getStorage(DB::db(), 'game_env'); + $worldPatch = $setup['world'] ?? []; + if (!is_array($worldPatch)) { + throw new \InvalidArgumentException('setup.world must be an object'); + } + if (array_key_exists('hiddenSeed', $worldPatch)) { + if (!is_string($worldPatch['hiddenSeed']) || $worldPatch['hiddenSeed'] === '') { + throw new \InvalidArgumentException('setup.world.hiddenSeed must be a non-empty string'); + } + UniqueConst::$hiddenSeed = $worldPatch['hiddenSeed']; + } foreach (['isunited', 'refreshLimit', 'block_change_scout', 'lastNPCTroopLeaderID'] as $key) { if (array_key_exists($key, $gameEnvironmentPatch)) { $gameStorage->setValue($key, $gameEnvironmentPatch[$key]); @@ -530,7 +575,7 @@ function comparisonMonthlyEventTraceMain(): void } $actionClass = "\\sammo\\Event\\Action\\{$actionName}"; - if ($actionName === 'PreUpdateMonthly') { + if (in_array($actionName, ['PreUpdateMonthly', 'PostUpdateMonthly'], true)) { $action = null; } elseif ($actionName === 'ProcessSemiAnnual') { $resource = $request['resource'] ?? null; @@ -620,6 +665,18 @@ function comparisonMonthlyEventTraceMain(): void if (!preUpdateMonthly()) { throw new \RuntimeException('preUpdateMonthly failed'); } + } elseif ($actionName === 'PostUpdateMonthly') { + $seedYear = $environment['seedYear'] ?? null; + $seedMonth = $environment['seedMonth'] ?? null; + if (!is_int($seedYear) || !is_int($seedMonth)) { + throw new \InvalidArgumentException('PostUpdateMonthly requires seedYear and seedMonth'); + } + postUpdateMonthly(new RandUtil(new LiteHashDRBG(Util::simpleSerialize( + UniqueConst::$hiddenSeed, + 'monthly', + $seedYear, + $seedMonth, + )))); } else { $action->run($actionEnvironment); }