From 9e125f93b4ce8b1c64107daf8d57a09e57fcbb7b Mon Sep 17 00:00:00 2001 From: Claude Date: Thu, 5 Nov 2026 21:08:02 +0000 Subject: [PATCH 1/2] Show sleep state and every region in host status Civ13's gaia_host.py status now reports asleep and cold worlds and, for a region host, a regions list (Civ13 PR #3806 follow-up). The host status line names the sleep state and adds one line with each region's state and players. Region names are filtered to plain text so they cannot ping. Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_016WTP2SYbvPmyLiTWu38djA --- src/Civ13/Local/GaiaHostControl.php | 17 +++++++++++++++-- tests/local/gaia-host.php | 4 ++++ 2 files changed, 19 insertions(+), 2 deletions(-) diff --git a/src/Civ13/Local/GaiaHostControl.php b/src/Civ13/Local/GaiaHostControl.php index d4b388b0..811eb5c0 100644 --- a/src/Civ13/Local/GaiaHostControl.php +++ b/src/Civ13/Local/GaiaHostControl.php @@ -44,7 +44,11 @@ public function request(string $action): PromiseInterface /** One line for Discord from a status answer. */ public static function describe(array $status): string { - $parts = [($status['listening'] ?? false) ? 'Server port is open.' : 'Server is not running.']; + $parts = [match ($status['state'] ?? null) { + 'asleep' => 'Server is asleep (frozen in memory; a join or travel wakes it).', + 'cold' => 'Server is asleep (stopped after its last save; a join or travel starts it).', + default => ($status['listening'] ?? false) ? 'Server port is open.' : 'Server is not running.', + }]; $world = $status['world'] ?? null; if (is_array($world)) { $parts[] = ($world['ready'] ?? false) ? 'World ready' : 'World not ready'; @@ -57,7 +61,16 @@ public static function describe(array $status): string if (is_string($operation['error'] ?? null) && $operation['error'] !== '') { $line .= ': '.substr($operation['error'], 0, 200); } $parts[] = $line; } - return implode(' ', array_map(static fn ($part) => str_ends_with($part, '.') ? $part : $part.'.', $parts)); + $line = implode(' ', array_map(static fn ($part) => str_ends_with($part, '.') ? $part : $part.'.', $parts)); + // A region host (Civ13 scripts/shard-group) also lists every region of its group. + $regions = []; + foreach (is_array($status['regions'] ?? null) ? array_slice($status['regions'], 0, 16) : [] as $region) { + if (!is_array($region) || !is_string($region['name'] ?? null) || !is_string($region['state'] ?? null)) { continue; } + $name = substr(preg_replace('/[^\w \-\'().]/u', '', $region['name']) ?? '', 0, 64); + $state = preg_replace('/[^a-z]/', '', $region['state']) ?? ''; + $regions[] = "{$name}: {$state}".(is_int($region['players'] ?? null) ? ", {$region['players']} players" : ''); + } + return $regions === [] ? $line : $line."\nRegions: ".implode('; ', $regions).'.'; } private static function absolute(string $path): bool { return str_starts_with($path, '/') || (bool) preg_match('/^[A-Za-z]:[\\\\\/]/', $path); } diff --git a/tests/local/gaia-host.php b/tests/local/gaia-host.php index 27ad25be..690cad48 100644 --- a/tests/local/gaia-host.php +++ b/tests/local/gaia-host.php @@ -42,6 +42,10 @@ function gaiaCheck(bool $ok, string $what): void { if (!$ok) { throw new Runtime [$status, $error] = $run('status'); gaiaCheck($error === null && $status['world']['clients'] === 3, 'status answer'); gaiaCheck(GaiaHostControl::describe($status) === 'Server port is open. World ready. 3 players. Last action: stop (done).', 'status line: '.GaiaHostControl::describe($status)); + $region = ['listening' => true, 'state' => 'awake', 'world' => ['ready' => true, 'clients' => 2], + 'regions' => [['id' => 'gaia', 'name' => 'Gaia', 'state' => 'awake', 'players' => 2], ['id' => 'deep', 'name' => 'The Deep <@&1>', 'state' => 'asleep', 'players' => null], 'junk']]; + gaiaCheck(GaiaHostControl::describe($region) === "Server port is open. World ready. 2 players.\nRegions: Gaia: awake, 2 players; The Deep 1: asleep.", 'region line: '.GaiaHostControl::describe($region)); + gaiaCheck(str_starts_with(GaiaHostControl::describe(['state' => 'cold', 'listening' => false]), 'Server is asleep (stopped'), 'cold line'); [$value, $error] = $run('start'); gaiaCheck($value === null && $error instanceof RuntimeException && $error->getMessage() === 'Refused: the server is already running', 'refusal text'); [$value, $error] = $run('stop'); From ce59ce0f2f2803773991865754c4e881fb6e7462 Mon Sep 17 00:00:00 2001 From: Claude Date: Thu, 5 Nov 2026 21:17:06 +0000 Subject: [PATCH 2/2] Keep Unicode marks in region names and cut them by character; clarify cold start Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_016WTP2SYbvPmyLiTWu38djA --- src/Civ13/Local/GaiaHostControl.php | 5 +++-- tests/local/gaia-host.php | 2 ++ 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/src/Civ13/Local/GaiaHostControl.php b/src/Civ13/Local/GaiaHostControl.php index 811eb5c0..d6f81d50 100644 --- a/src/Civ13/Local/GaiaHostControl.php +++ b/src/Civ13/Local/GaiaHostControl.php @@ -46,7 +46,7 @@ public static function describe(array $status): string { $parts = [match ($status['state'] ?? null) { 'asleep' => 'Server is asleep (frozen in memory; a join or travel wakes it).', - 'cold' => 'Server is asleep (stopped after its last save; a join or travel starts it).', + 'cold' => 'Server is asleep (stopped after its last save; a launcher wake or travel starts it).', default => ($status['listening'] ?? false) ? 'Server port is open.' : 'Server is not running.', }]; $world = $status['world'] ?? null; @@ -66,7 +66,8 @@ public static function describe(array $status): string $regions = []; foreach (is_array($status['regions'] ?? null) ? array_slice($status['regions'], 0, 16) : [] as $region) { if (!is_array($region) || !is_string($region['name'] ?? null) || !is_string($region['state'] ?? null)) { continue; } - $name = substr(preg_replace('/[^\w \-\'().]/u', '', $region['name']) ?? '', 0, 64); + // Letters, marks and digits in any script, cut to 64 characters, never mid-character. + $name = preg_replace('/^(.{0,64}).*$/us', '$1', preg_replace('/[^\p{L}\p{M}\p{N} \-\'().]/u', '', $region['name']) ?? '') ?? ''; $state = preg_replace('/[^a-z]/', '', $region['state']) ?? ''; $regions[] = "{$name}: {$state}".(is_int($region['players'] ?? null) ? ", {$region['players']} players" : ''); } diff --git a/tests/local/gaia-host.php b/tests/local/gaia-host.php index 690cad48..d2e634c4 100644 --- a/tests/local/gaia-host.php +++ b/tests/local/gaia-host.php @@ -46,6 +46,8 @@ function gaiaCheck(bool $ok, string $what): void { if (!$ok) { throw new Runtime 'regions' => [['id' => 'gaia', 'name' => 'Gaia', 'state' => 'awake', 'players' => 2], ['id' => 'deep', 'name' => 'The Deep <@&1>', 'state' => 'asleep', 'players' => null], 'junk']]; gaiaCheck(GaiaHostControl::describe($region) === "Server port is open. World ready. 2 players.\nRegions: Gaia: awake, 2 players; The Deep 1: asleep.", 'region line: '.GaiaHostControl::describe($region)); gaiaCheck(str_starts_with(GaiaHostControl::describe(['state' => 'cold', 'listening' => false]), 'Server is asleep (stopped'), 'cold line'); + $long = GaiaHostControl::describe(['regions' => [['name' => 'की'.str_repeat('北', 70), 'state' => 'awake']]]); + gaiaCheck(mb_check_encoding($long, 'UTF-8') && str_contains($long, 'Regions: की'.str_repeat('北', 62).': awake.'), 'unicode region name: '.$long); [$value, $error] = $run('start'); gaiaCheck($value === null && $error instanceof RuntimeException && $error->getMessage() === 'Refused: the server is already running', 'refusal text'); [$value, $error] = $run('stop');