From ff3ef4e4f32c7072a46f96aadc0c504f0a0355fa Mon Sep 17 00:00:00 2001 From: hided62 Date: Sat, 25 Jul 2026 16:56:26 +0000 Subject: [PATCH] test: add monthly city supply trace --- .../fixtures/monthly_update_city_supply.json | 140 ++++++++++++ hwe/compare/monthly_event_trace.php | 212 ++++++++++++++++++ 2 files changed, 352 insertions(+) create mode 100644 hwe/compare/fixtures/monthly_update_city_supply.json create mode 100644 hwe/compare/monthly_event_trace.php diff --git a/hwe/compare/fixtures/monthly_update_city_supply.json b/hwe/compare/fixtures/monthly_update_city_supply.json new file mode 100644 index 00000000..c9cc3b7c --- /dev/null +++ b/hwe/compare/fixtures/monthly_update_city_supply.json @@ -0,0 +1,140 @@ +{ + "action": "UpdateCitySupply", + "environment": { + "year": 193, + "month": 1, + "startyear": 190 + }, + "setup": { + "nation": [ + { + "id": 99, + "values": { + "name": "보급검증국", + "color": "#777777", + "capital": 1, + "level": 1, + "type": "che_중립" + } + }, + { + "id": 98, + "values": { + "name": "불일치수도국", + "color": "#888888", + "capital": 2, + "level": 1, + "type": "che_중립" + } + } + ], + "city": [ + { + "id": 1, + "values": { + "nation": 99, + "supply": 0, + "front": 2, + "pop": 1001, + "agri": 501, + "comm": 499, + "secu": 99, + "trust": 50, + "def": 101, + "wall": 50, + "officer_set": 7, + "term": 2, + "conflict": {"98": 3} + } + }, + { + "id": 9, + "values": { + "nation": 99, + "supply": 0, + "front": 2, + "pop": 1001, + "agri": 501, + "comm": 499, + "secu": 99, + "trust": 50, + "def": 101, + "wall": 50, + "officer_set": 7, + "term": 2, + "conflict": {"98": 3} + } + }, + { + "id": 2, + "values": { + "nation": 99, + "supply": 1, + "front": 2, + "pop": 1001, + "agri": 501, + "comm": 499, + "secu": 99, + "trust": 33, + "def": 101, + "wall": 50, + "officer_set": 7, + "term": 2, + "conflict": {"98": 3} + } + }, + { + "id": 3, + "values": { + "nation": 0, + "supply": 0, + "front": 0, + "trust": 50 + } + } + ], + "general": [ + { + "id": 1, + "values": { + "nation": 99, + "city": 2, + "officer_level": 4, + "officer_city": 2, + "crew": 101, + "train": 51, + "atmos": 99 + } + }, + { + "id": 2, + "values": { + "nation": 99, + "city": 1, + "officer_level": 4, + "officer_city": 2, + "crew": 101, + "train": 51, + "atmos": 99 + } + }, + { + "id": 3, + "values": { + "nation": 98, + "city": 2, + "officer_level": 3, + "officer_city": 1, + "crew": 101, + "train": 51, + "atmos": 99 + } + } + ] + }, + "observe": { + "generalIds": [1, 2, 3], + "cityIds": [1, 2, 3, 9], + "nationIds": [98, 99] + } +} diff --git a/hwe/compare/monthly_event_trace.php b/hwe/compare/monthly_event_trace.php new file mode 100644 index 00000000..d00ba15c --- /dev/null +++ b/hwe/compare/monthly_event_trace.php @@ -0,0 +1,212 @@ + $values */ +function comparisonMonthlyPatch(string $table, int $id, array $values): void +{ + $definitions = [ + 'city' => [ + 'idColumn' => 'city', + 'allowed' => [ + 'name', 'level', 'nation', 'supply', 'front', 'pop', 'pop_max', + 'agri', 'agri_max', 'comm', 'comm_max', 'secu', 'secu_max', + 'trust', 'trade', 'def', 'def_max', 'wall', 'wall_max', + 'officer_set', 'state', 'region', 'term', 'conflict', + ], + ], + 'nation' => [ + 'idColumn' => 'nation', + 'allowed' => ['name', 'color', 'capital', 'gold', 'rice', 'level', 'type'], + ], + 'general' => [ + 'idColumn' => 'no', + 'allowed' => [ + 'name', 'nation', 'city', 'officer_level', 'officer_city', + 'crew', 'train', 'atmos', + ], + ], + ]; + $definition = $definitions[$table] ?? null; + if ($definition === null) { + throw new \InvalidArgumentException("unsupported setup table: {$table}"); + } + $patch = []; + foreach ($values as $key => $value) { + if (!is_string($key) || !in_array($key, $definition['allowed'], true)) { + throw new \InvalidArgumentException("unsupported {$table} setup field"); + } + if (in_array($key, ['conflict'], true) && is_array($value)) { + $value = json_encode($value, JSON_THROW_ON_ERROR | JSON_UNESCAPED_UNICODE); + } + $patch[$key] = $value; + } + if ($table === 'nation') { + $patch += [ + 'nation' => $id, + 'name' => "fixture-nation-{$id}", + 'color' => '#777777', + 'capital' => 0, + 'gold' => 0, + 'rice' => 0, + 'level' => 1, + 'type' => 'che_중립', + 'aux' => '{}', + ]; + DB::db()->insertUpdate('nation', $patch, $patch); + } elseif ($patch !== []) { + DB::db()->update($table, $patch, "`{$definition['idColumn']}` = %i", $id); + } +} + +/** @param array $observe */ +function comparisonMonthlyDetails(array $observe, int $worldHistoryAfterId): array +{ + $db = DB::db(); + $generals = array_map( + static fn(array $row): array => comparisonPickRow( + $row, + [ + 'id' => 'no', + 'nationId' => 'nation', + 'cityId' => 'city', + 'officerLevel' => 'officer_level', + 'officerCityId' => 'officer_city', + 'crew' => 'crew', + 'train' => 'train', + 'atmos' => 'atmos', + ], + ), + comparisonRowsById('general', 'no', comparisonIntegerList($observe['generalIds'] ?? [], 'generalIds')), + ); + $cities = array_map( + static fn(array $row): array => comparisonPickRow( + $row, + [ + 'id' => 'city', + 'nationId' => 'nation', + 'supplyState' => 'supply', + 'frontState' => 'front', + 'officerSet' => 'officer_set', + 'term' => 'term', + 'conflict' => 'conflict', + ], + ['conflict'], + ), + comparisonRowsById('city', 'city', comparisonIntegerList($observe['cityIds'] ?? [], 'cityIds')), + ); + $worldHistory = array_map( + static fn(array $row): array => comparisonPickRow( + $row, + [ + 'id' => 'id', + 'nationId' => 'nation_id', + 'year' => 'year', + 'month' => 'month', + 'text' => 'text', + ], + ), + $db->query( + 'SELECT id, nation_id, year, month, text FROM world_history WHERE id > %i ORDER BY id', + $worldHistoryAfterId, + ), + ); + return [ + 'generals' => $generals, + 'cities' => $cities, + 'worldHistory' => $worldHistory, + ]; +} + +function comparisonMonthlyEventTraceMain(): void +{ + try { + $input = stream_get_contents(STDIN); + $request = json_decode($input === '' ? '{}' : $input, true, flags: JSON_THROW_ON_ERROR); + if (!is_array($request)) { + throw new \InvalidArgumentException('request must be an object'); + } + if (($request['action'] ?? null) !== 'UpdateCitySupply') { + throw new \InvalidArgumentException('unsupported monthly action'); + } + $setup = $request['setup'] ?? []; + if (!is_array($setup)) { + throw new \InvalidArgumentException('setup must be an object'); + } + foreach (['city', 'nation', 'general'] as $table) { + $rows = $setup[$table] ?? []; + if (!is_array($rows)) { + throw new \InvalidArgumentException("setup.{$table} must be an array"); + } + foreach ($rows as $row) { + if (!is_array($row) || !is_int($row['id'] ?? null) || !is_array($row['values'] ?? null)) { + throw new \InvalidArgumentException("invalid setup.{$table} row"); + } + comparisonMonthlyPatch($table, $row['id'], $row['values']); + } + } + + $db = DB::db(); + $snapshotRequest = ['observe' => $request['observe'] ?? []]; + $snapshotRequest['observe']['logAfterId'] = + (int)($db->queryFirstField('SELECT COALESCE(MAX(id), 0) FROM general_record') ?? 0); + $worldHistoryAfterId = + (int)($db->queryFirstField('SELECT COALESCE(MAX(id), 0) FROM world_history') ?? 0); + $before = comparisonTurnStateSnapshot($snapshotRequest); + $beforeDetails = comparisonMonthlyDetails($snapshotRequest['observe'], $worldHistoryAfterId); + + $environment = $request['environment'] ?? []; + if (!is_array($environment)) { + throw new \InvalidArgumentException('environment must be an object'); + } + $year = $environment['year'] ?? null; + $month = $environment['month'] ?? null; + $startYear = $environment['startyear'] ?? null; + if (!is_int($year) || !is_int($month) || !is_int($startYear)) { + throw new \InvalidArgumentException('environment year, month, and startyear must be integers'); + } + + $action = new Event\Action\UpdateCitySupply(); + $action->run([ + 'year' => $year, + 'month' => $month, + 'startyear' => $startYear, + ]); + unset($action); + gc_collect_cycles(); + + $after = comparisonTurnStateSnapshot($snapshotRequest); + $afterDetails = comparisonMonthlyDetails($snapshotRequest['observe'], $worldHistoryAfterId); + echo json_encode( + [ + 'schemaVersion' => 1, + 'engine' => 'ref', + 'action' => 'UpdateCitySupply', + 'before' => $before, + 'beforeDetails' => $beforeDetails, + 'after' => $after, + 'afterDetails' => $afterDetails, + ], + JSON_THROW_ON_ERROR | JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES | JSON_PRESERVE_ZERO_FRACTION, + ), PHP_EOL; + } catch (\Throwable $throwable) { + fwrite(STDERR, $throwable::class . ': ' . $throwable->getMessage() . PHP_EOL); + exit(1); + } +} + +comparisonMonthlyEventTraceMain();