From b3217703fe3896a987fa5e2fa8c0a398f016d34c Mon Sep 17 00:00:00 2001 From: frozensnowy Date: Thu, 5 Feb 2026 21:50:21 +0100 Subject: [PATCH] Fix #25910: Folders starting with numbers sort incorrectly (#25914) Commit 43f7d2d91 introduced a regression where logicalCmp compared digit starting strings lexicographically instead of numerically, causing folders to sort as "1, 10, 11, 2..." instead of "1, 2... 10, 11". This commit removes the broken special case, the main loop already handles numeric comparison correctly --- distribution/changelog.txt | 1 + src/openrct2/core/String.cpp | 34 ++++------------------------------ test/tests/StringTest.cpp | 8 +++++--- 3 files changed, 10 insertions(+), 33 deletions(-) diff --git a/distribution/changelog.txt b/distribution/changelog.txt index dda93f4697..958368cfda 100644 --- a/distribution/changelog.txt +++ b/distribution/changelog.txt @@ -3,6 +3,7 @@ - Feature: [#22704] Rides can be made invisible more easily. - Improved: [#25941] The command line sprite build command is now faster. - Fix: [#25237] Wrong colours on the Knight costume. +- Fix: [#25910] Folders starting with numbers are sorted incorrectly (e.g. 1, 10, 2 instead of 1, 2, 10). 0.4.31 (2026-02-01) ------------------------------------------------------------------------ diff --git a/src/openrct2/core/String.cpp b/src/openrct2/core/String.cpp index a7983d83cd..8136cd6988 100644 --- a/src/openrct2/core/String.cpp +++ b/src/openrct2/core/String.cpp @@ -721,13 +721,8 @@ namespace OpenRCT2::String return escaped.str(); } - /* Case insensitive logical compare, produces the same output as Notepad++ lexicographical sort */ - // Example: - // - Guest 10 - // - Guest 99 - // - Guest 100 - // - John v2.0 - // - John v2.1 + // Case insensitive natural sort (numbers compared numerically). + // Strings starting with digits sort before alphabetic strings. int32_t logicalCmp(const char* s1, const char* s2) { const auto isDigit = [](char c) { return std::isdigit(static_cast(c)); }; @@ -737,30 +732,9 @@ namespace OpenRCT2::String bool s1StartsDigit = isDigit(*s1); bool s2StartsDigit = isDigit(*s2); if (s1StartsDigit && !s2StartsDigit) - { - return -1; // s1 (starts with digit) comes before s2 - } + return -1; if (!s1StartsDigit && s2StartsDigit) - { - return 1; // s2 (starts with digit) comes before s1 - } - - // If both start with digits, compare lexicographically - if (s1StartsDigit && s2StartsDigit) - { - while (*s1 != '\0' && *s2 != '\0') - { - char c1 = toUpper(*s1); - char c2 = toUpper(*s2); - if (c1 != c2) - { - return c1 - c2; - } - s1++; - s2++; - } - return *s1 == '\0' ? (*s2 == '\0' ? 0 : -1) : 1; - } + return 1; while (*s1 != '\0' && *s2 != '\0') { diff --git a/test/tests/StringTest.cpp b/test/tests/StringTest.cpp index 7a30610fd9..e97f86020c 100644 --- a/test/tests/StringTest.cpp +++ b/test/tests/StringTest.cpp @@ -255,10 +255,12 @@ TEST_F(CodepointViewTest, CodepointView_iterate) TEST_F(StringTest, LogicalCompare) { + // Strings starting with digits come first, sorted numerically + // Then alphabetic strings sorted case-insensitively std::vector expected = { - "1001 Troubles", "3D Cinema 1", "Aerial Cycles", "Batflyer", "bpb", - "bpb.sv6", "Drive-by", "foo", "foobar", "Guest 10", - "Guest 99", "Guest 100", "John v2.0", "John v2.1", "River of the Damned", + "3D Cinema 1", "1001 Troubles", "Aerial Cycles", "Batflyer", "bpb", + "bpb.sv6", "Drive-by", "foo", "foobar", "Guest 10", + "Guest 99", "Guest 100", "John v2.0", "John v2.1", "River of the Damned", "Terror-dactyl", };