Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
122 commits
Select commit Hold shift + click to select a range
5cdc44f
feat(utf8): add character-semantic UTF-8 helpers + migration audit (#…
kvirund Aug 3, 2026
8c7b16f
feat(utf8): internal_encoding flag + route format_text via native_tex…
kvirund Aug 3, 2026
d3dcb5d
feat(utf8): route pager next_page + string_add truncation via native_…
kvirund Aug 3, 2026
796fbe8
feat(utf8): route str_cmp/strn_cmp case folding via native_text (#3681)
kvirund Aug 3, 2026
421524b
feat(utf8): make isname walk characters instead of bytes (#3681)
kvirund Aug 3, 2026
8aed38f
feat(utf8): character-aware argument splitting and name declension (#…
kvirund Aug 3, 2026
d0d59dc
test(utf8): regression tests for the byte-vs-character text routines …
kvirund Aug 3, 2026
4fa3ac3
feat(utf8): character-aware ctype scanning, closing track A2 (#3681)
kvirund Aug 3, 2026
35f4170
feat(utf8): character-width column padding, first A3 subsystems (#3681)
kvirund Aug 3, 2026
360c6ee
feat(utf8): character-width columns across the remaining game screens…
kvirund Aug 3, 2026
455521c
feat(utf8): character-width columns in god commands, closing track A3…
kvirund Aug 3, 2026
9be6a19
docs: fix the declared C++ standard (C++17 -> C++20)
kvirund Aug 4, 2026
702bd7d
refactor(utf8): use fmt for column widths instead of the pad_right he…
kvirund Aug 4, 2026
4b57f5e
feat(utf8): character iteration API + the case sites the first sweep …
kvirund Aug 4, 2026
1875b40
perf(utf8): make case folding cost what the byte loop cost (#3681)
kvirund Aug 4, 2026
baba4e7
feat(utf8): encoding-neutral Russian menu keys (A4 groundwork, #3681)
kvirund Aug 4, 2026
6132bf0
feat(utf8): convert all Cyrillic menu keys to encoding-neutral consta…
kvirund Aug 4, 2026
118f1e9
fix(utf8): keep save-file names identical across the flip (C1a, #3681)
kvirund Aug 4, 2026
7567c29
ci(utf8): rehearse the encoding flip on every build (#3681)
kvirund Aug 4, 2026
bd49034
feat(utf8): funnel world text through the encoding boundary (#3681)
kvirund Aug 4, 2026
2ea4dc0
feat(utf8): put the encoding boundary at XML load, not at each field …
kvirund Aug 5, 2026
bb45585
feat(utf8): make the client encoding boundary work in both runtimes (…
kvirund Aug 5, 2026
129d918
fix(utf8): spell the code-page tables as byte escapes, not string lit…
kvirund Aug 5, 2026
254e0ed
feat(utf8): take every data source through the encoding boundary (C3,…
kvirund Aug 5, 2026
16dc44c
fix(utf8): parse player names per character, not per byte (#3681)
kvirund Aug 5, 2026
822bc12
fix(utf8): convert the player index when it is read (#3681)
kvirund Aug 5, 2026
f135eeb
fix(utf8): fold case per character in the player-name index (#3681)
kvirund Aug 5, 2026
c685b9d
fix(utf8): fold the name per character when building the player index…
kvirund Aug 5, 2026
ca7ae79
feat: report the internal encoding, and keep the revision in containe…
kvirund Aug 6, 2026
353507f
feat(utf8): take boards and the changelog through the encoding bounda…
kvirund Aug 6, 2026
b7bdd1c
test(utf8): cover board loading, which had only a stub test (#3681)
kvirund Aug 6, 2026
1358d53
feat(utf8): транслитерация вместо потери символа на границе KOI8-R (#…
kvirund Aug 6, 2026
8bf6487
chore(version): свернуть вывод версии, кодировка -- в строку компилят…
kvirund Aug 6, 2026
eda833d
feat(utf8): нарядный экран приветствия для UTF-8-клиентов (#3681)
kvirund Aug 6, 2026
453c918
fix(utf8): справка читается через границу кодировки (#3681)
kvirund Aug 6, 2026
4a6f238
feat(utf8): одна шапка на все кодировки, деградирует через словарь (#…
kvirund Aug 6, 2026
a3ac664
fix(utf8): колонки в "закл все" считаются в символах (#3681)
kvirund Aug 6, 2026
8f352df
fix(utf8): без лигатуры в шапке -- 'ae' на символ длиннее и рамку пер…
kvirund Aug 6, 2026
ac1a906
fix(utf8): динамическая справка портилась на сортировке (#3681)
kvirund Aug 6, 2026
eff885d
feat(utf8): карта рисуется псевдографикой (#3681)
kvirund Aug 6, 2026
c3e687d
fix(map): карта обрывалась на первой пустой строке
kvirund Aug 6, 2026
ea03efe
feat(map): по два начертания на клетку, символы вместо букв для посто…
kvirund Aug 6, 2026
e42ce89
revert(map): вернуть исходные символы карты
kvirund Aug 6, 2026
3f5781c
fix(utf8): конфиги перекодировались дважды и росли на каждой загрузке…
kvirund Aug 6, 2026
89cb871
test(utf8): цикл "загрузили - записали" для конфига закреплён тестом …
kvirund Aug 6, 2026
6dc62a6
fix(utf8): запись на диск идёт в кодировке мира, а не в нативной (#3681)
kvirund Aug 7, 2026
9a926a8
fix(utf8): имена файлов строятся из байтов KOI8-R, а не из нативных (…
kvirund Aug 7, 2026
6436526
fix(utf8): аудит побайтовых операций над текстом, часть 1 -- регистр …
kvirund Aug 9, 2026
6b6c669
fix(utf8): аудит побайтовых операций, часть 2 -- ширина колонок (#3681)
kvirund Aug 9, 2026
ccb60fa
fix(utf8): аудит побайтовых операций, часть 3 -- обрезка по ширине (#…
kvirund Aug 9, 2026
d558be1
feat(utf8): флип -- исходники и рантайм переключены на UTF-8 (#3681)
kvirund Aug 9, 2026
83a7f0e
fix(utf8): вернуть в приглашении обычные кавычки -- на ёлочках ломают…
kvirund Aug 9, 2026
df2be73
Revert "fix(utf8): вернуть в приглашении обычные кавычки" (#3681)
kvirund Aug 9, 2026
ec5bc6c
fix(utf8): шапка центрируется целиком и рисуется знаками, которые ест…
kvirund Aug 9, 2026
83d1106
refactor(utf8): выкинуть мёртвые байтовые ветки (#3681)
kvirund Aug 9, 2026
da6824c
refactor(utf8): выкинуть мёртвые байтовые ветки (#3681)
kvirund Aug 9, 2026
0100e08
refactor(utf8): убрать native_is_utf8 -- последний остаток развилки (…
kvirund Aug 17, 2026
d31ad24
fix(utf8): убрать перекодировку на границах, где её больше нет (C4, #…
kvirund Aug 17, 2026
ea09960
fix(utf8): вернуть в UTF-8 файлы, перекодированные при ребэйзе (#3681)
kvirund Aug 17, 2026
2b2a4ff
refactor(utf8): выкинуть тождественные обёртки и мёртвый pred_separat…
kvirund Aug 17, 2026
b4b8903
revert(utf8): убрать свою шапку входа (#3681)
kvirund Aug 17, 2026
40808b2
fix(utf8): провести через границу записи оставшиеся подсистемы (#3681)
kvirund Aug 17, 2026
1b5f900
fix(utf8): титул с кириллицей отвергался валидатором (#3681)
kvirund Aug 17, 2026
fbcd57e
fix(utf8): XML-файлы тоже писать в кодировке мира (#3681)
kvirund Aug 17, 2026
c68254e
fix(utf8): границу чтения к тем же подсистемам, что и границу записи …
kvirund Aug 17, 2026
b38b68c
fix(utf8): pk-лог и именные вещи дружины тоже через границу чтения (#…
kvirund Aug 17, 2026
f5cad34
fix(utf8): телеметрия и admin API слали текст, конвертируя его второй…
kvirund Aug 17, 2026
c0313c3
Merge pull request #3735 from bylins/master
bylins Aug 18, 2026
c535efc
fix(utf8): логи снова пишутся в кодировке мира (#3681)
kvirund Aug 18, 2026
ee932a6
fix(utf8): файл вещей игрока читался мимо границы (#3681)
kvirund Aug 18, 2026
51af253
fix(utf8): списки состояния тоже через границу (#3681)
kvirund Aug 18, 2026
e02f36b
fix(utf8): кириллический пароль перестал подходить (#3681)
kvirund Aug 18, 2026
4cbeb77
fix(utf8): длина имени считалась в байтах (#3681)
kvirund Aug 18, 2026
d427dba
fix(utf8): данные дружин портились с двух сторон (#3681)
kvirund Aug 18, 2026
b5f8e14
fix(utf8): советчик имён сравнивал полимени и не читал границу (#3681)
kvirund Aug 18, 2026
ff62b2b
feat(utf8): to_disk больше не может испортить данные молча (#3681)
kvirund Aug 18, 2026
5eb3b65
fix(utf8): пределы длины текста считаются в символах (#3681)
kvirund Aug 18, 2026
a044152
fix(utf8): обрезание по буферу отступает до границы символа (#3681)
kvirund Aug 18, 2026
f5449dd
Merge branch 'master' into kvirund/utf8-migration
bylins Aug 19, 2026
ec9ddd0
build(utf8): не дать собрать из KOI8-дерева (#3681)
kvirund Aug 19, 2026
8489d9d
Merge pull request #3748 from bylins/master
bylins Aug 20, 2026
030d39c
fix(utf8): step over a whole filter letter, not one byte
bylins Aug 20, 2026
2fe86d2
fix(utf8): measure wrap width in characters, not bytes
bylins Aug 20, 2026
1efecdf
fix(utf8): take prefix lengths from the literal, not a KOI8 byte count
bylins Aug 20, 2026
7ea06ba
fix(utf8): write world files back in the on-disk encoding
bylins Aug 20, 2026
75f12da
fix(utf8): pad table columns by characters in colored_name
bylins Aug 20, 2026
67e669a
fix(world): миграция полей объектов метила зоны, где менять нечего (#…
kvirund Aug 20, 2026
992b370
refactor(utf8): префиксы сравниваются через starts_with, без ручной д…
kvirund Aug 20, 2026
ce3b834
fix(observability): хвост выключения не долетал до Loki (#3681)
kvirund Aug 20, 2026
7a2fd9d
refactor(utf8): свой starts_with заменён на существующий IsAbbr (#3681)
kvirund Aug 20, 2026
0663449
refactor(utf8): оставшиеся сравнения префиксов переведены на IsAbbr (…
kvirund Aug 20, 2026
9f912ec
refactor(utf8): strn_cmp и ncompare_ci удалены, префикс считается в с…
kvirund Aug 20, 2026
6656b6a
test(utf8): закрыть тестами то, что уронило боевой (#3681, #3749)
kvirund Aug 20, 2026
d1be606
fix(utf8): в сборке без crypt() хэш тоже считается по дисковой форме …
kvirund Aug 20, 2026
df9ad52
test(utf8): неверный пароль в тесте отличается первой буквой (#3681)
kvirund Aug 20, 2026
9c9a67f
fix(utf8): cut the liquid separator by its own length
bylins Aug 21, 2026
bcb7aa8
fix(utf8): build the saving-throw trace with fmt, not a 256-byte sprintf
bylins Aug 21, 2026
7aa9abb
fix(utf8): log the unknown obj prototype through fmt
bylins Aug 21, 2026
e529a94
fix(utf8): drop the 128-byte buffer from the drinkcon fill line
bylins Aug 21, 2026
9ce2702
fix(utf8): build the rent report line without a 256-byte buffer
bylins Aug 21, 2026
f923bf7
refactor(dg): build script log lines with fmt instead of 32K stack bu…
bylins Aug 21, 2026
ebfec4d
refactor(dg): replace the UID table with std::replace_if, add UID_CHA…
bylins Aug 21, 2026
5adeac4
Merge pull request #3754 from bylins/master
bylins Aug 21, 2026
59b6990
fix(utf8): убрать осиротевшую переменную и закрыть тестом round-trip …
kvirund Aug 21, 2026
ebccf68
Merge branch 'master' into kvirund/utf8-migration
bylins Aug 21, 2026
2f52f89
fix(utf8): «где» переполняло буфер вывода на длинных списках (#3681)
kvirund Aug 21, 2026
54eca26
Merge pull request #3759 from bylins/master
bylins Aug 21, 2026
0e0aa71
fix(io): не писать пароль в лог переполнения вывода
kvirund Aug 21, 2026
2627ae4
Merge pull request #3764 from bylins/master
bylins Aug 22, 2026
a06b106
fix(utf8): convert text files read into a buffer to the native encoding
bylins Aug 22, 2026
3927c7c
Merge pull request #3767 from bylins/master
bylins Aug 22, 2026
57db1fa
fix(utf8): convert alias files at the encoding boundary
bylins Aug 24, 2026
f1c4c86
Merge branch 'master' into kvirund/utf8-migration
bylins Aug 24, 2026
6ff7b4d
fix(utf8): heal drinkcon names left with a stray space
bylins Aug 24, 2026
308404a
fix(sight): drop the extra space before the item state
bylins Aug 24, 2026
0302850
Merge pull request #3772 from bylins/master
bylins Aug 26, 2026
32c5696
feat(sight): shorten the drinkcon line in item lists
bylins Aug 27, 2026
4cabad3
Merge pull request #3782 from bylins/feat/short-drinkcon-line
bylins Aug 27, 2026
545e973
Merge branch 'master' into kvirund/utf8-migration
kvirund Aug 27, 2026
41eed8c
feat(utf8): ловить не-UTF-8 на выводе игроку (#3681)
kvirund Aug 27, 2026
7887137
build(utf8): переполнение буфера строкой формата -- ошибка сборки (#3…
kvirund Aug 27, 2026
3a39a6a
docs: сборка без предупреждений -- требование, а не пожелание (#3784)
kvirund Aug 28, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 4 additions & 3 deletions .gitattributes
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
/src/** working-tree-encoding=KOI8-R eol=lf
/tests/** working-tree-encoding=KOI8-R eol=lf
/tests/**/*.py working-tree-encoding=UTF-8 eol=lf
# Исходники в UTF-8 (issue #3681). До флипа тут стояло working-tree-encoding=KOI8-R:
# в git блобы всегда лежали в UTF-8, а в рабочее дерево выкладывались в KOI8-R.
/src/** eol=lf
/tests/** eol=lf
*.md working-tree-encoding=UTF-8 eol=lf
/.githooks/** working-tree-encoding=UTF-8 eol=lf
/lib.template/** working-tree-encoding=KOI8-R eol=lf
Expand Down
17 changes: 15 additions & 2 deletions .githooks/pre-commit
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
# ============================================================================
# Блокирует коммит если:
# 1. Найден символ замены U+FFFD (О©╫)
# 2. Файл должен быть KOI8-R, но находится в UTF-8
# 2. Файл должен быть KOI8-R, но находится в UTF-8 (данные мира; исходники наоборот -- UTF-8)
# 3. Найдены типичные паттерны битой кодировки
# ============================================================================

Expand Down Expand Up @@ -97,8 +97,21 @@ for FILE in $FILES; do
fi

echo " ✅ Кодировка правильная"
elif [[ "$FILE" == src/* || "$FILE" == tests/* ]]; then
# Исходники после флипа (issue #3681) обязаны быть валидным UTF-8. Файл, случайно
# сохранённый редактором в KOI8-R, внешне выглядит нормально, но ломает литералы.
if ! iconv -f utf-8 -t utf-8 "$FILE" >/dev/null 2>&1; then
echo "📄 $FILE"
echo " ❌ ОШИБКА: не UTF-8. Исходники должны быть в UTF-8."
ERROR_FOUND=1
continue
fi
if grep -q "$BAD_CHAR" "$FILE" 2>/dev/null; then
echo "❌ [BLOCKER] Символ замены (О©╫) найден в: $FILE"
ERROR_FOUND=1
fi
else
# Для не-KOI8-R файлов проверяем только символ замены
# Для остальных файлов проверяем только символ замены
if grep -q "$BAD_CHAR" "$FILE" 2>/dev/null; then
echo "❌ [BLOCKER] Символ замены (О©╫) найден в: $FILE"
ERROR_FOUND=1
Expand Down
85 changes: 30 additions & 55 deletions CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -213,46 +213,14 @@ Access world state via `MUD::` namespace functions:
- Telnet protocol with MSDP (Mud Server Data Protocol) and MCCP compression
- Multiple codepage support (Alt, Win, UTF-8, KOI8-R)

## Development Guidelines (from CONTRIBUTING.md)
## Development Guidelines

### Code Style
- **Standard**: C++20 (C++17 minimum)
- **Indentation**: Tabs (size 4), no spaces for indentation
- **Braces**: An opening brace on the same line separated by a space
```cpp
if (condition) {
// code here
}
```
- **One statement per line** (applies to variable declarations too)
- **Always use braces** for if/else/for/while bodies, even single statements
- **Pointers/References**: `*` and `&` attached to type, space after
```cpp
const char* message = "example";
const auto& reference = message;
```

### File Management
- All new files must be added to `meson.build` (sources go into `main_sources` / `library_sources`, depending on the section)
- Include vim modeline at end of files:
```cpp
// vim: ts=4 sw=4 tw=0 noet syntax=cpp :
```

### Memory Management
- Prefer `new`/`delete` over `malloc`/`free`
- Use smart pointers (`CharData::shared_ptr`, `ObjData::shared_ptr`) where possible
- Avoid raw pointers for ownership

### Testing
- Run unit tests before committing: `./build/tests/tests`
- Write tests for new code using GoogleTest framework
- Tests are in `tests/` directory, named by module (e.g., `char.affects.cpp`, `fight.penalties.cpp`)
**Правила разработки живут в [CONTRIBUTING.md](CONTRIBUTING.md) — читай его перед тем, как
править код, и следуй ему.** Здесь их копии нет намеренно: две копии расходятся, и потом
непонятно, какая верна.

### Compiler Warnings
- Must compile on Windows, Linux, and Cygwin
- Fix all compiler warnings, especially in new code
- Build with `-Wall -Wextra` enabled
Там про стиль (отступы табами, скобки, по одному объявлению в строке), добавление новых файлов
в `meson.build`, работу с памятью, unit-тесты и требование к сборке без предупреждений.

## Common Development Patterns

Expand Down Expand Up @@ -367,12 +335,12 @@ The heartbeat system includes built-in profiling:

## Critical Notes

- **File Encoding**: All project source files MUST be in KOI8-R encoding (not UTF-8). This is critical for proper Russian text handling in the codebase. Use `iconv` to convert if needed: `iconv -f utf8 -t koi8-r input.cpp > output.cpp`
- **File Encoding**: All project source files are UTF-8 (issue #3681). Edit them directly — no iconv dance. The world data on disk (`lib/`, `lib.template/`) is still KOI8-R; see `.gitattributes` for exactly which trees.
- **Thread Safety**: Main game loop is single-threaded; use `BlockingQueue` for cross-thread communication
- **Shared Pointers**: Always use `CharData::shared_ptr` and `ObjData::shared_ptr` to prevent use-after-free
- **Pulse Timing**: Never use wall-clock delays; register actions with heartbeat system
- **Script Depth**: DG Scripts limited to 512 recursion depth to prevent stack overflow
- **Runtime Encoding**: While source files are KOI8-R, runtime text can be multiple encodings (Alt, Win, UTF-8, KOI8-R) based on client settings
- **Runtime Encoding**: The engine holds text in UTF-8 and works on it per character (`utils/native_text.*`). Client codepages (Alt, Win, KOI8-R, UTF-8) are converted at the network boundary only; the disk boundary (`from_disk_text` / `to_disk`) keeps world files in KOI8-R.

## Claude Code Workflow Rules

Expand Down Expand Up @@ -401,33 +369,40 @@ meson setup build_otel \
ninja -C build_otel -j$(($(nproc)/2))
```

### File Encoding - CRITICAL
**Proper workflow for editing KOI8-R files:**
### File Encoding
Sources are UTF-8. Edit `.cpp`/`.h` directly with the Edit tool — the KOI8-R conversion dance is
gone (issue #3681). Git always stored these blobs as UTF-8; what changed is that the working tree
no longer converts them on checkout.

For files marked as `working-tree-encoding=KOI8-R` in .gitattributes (all files in /src/**, /tests/**):
**Переключились на ветку с флипом в уже существующем дереве — перевыкачайте исходники:**

```bash
# 1. Convert to UTF-8 for editing
iconv -f koi8-r -t utf-8 src/file.cpp > /tmp/file_utf8.cpp
rm -rf src tests && git checkout -- src tests
```

`working-tree-encoding` применяется во время checkout'а, а содержимое блоба при флипе не менялось,
поэтому git оставляет ранее выкаченные файлы в KOI8-R и считает дерево чистым — `git status` молчит.
Собранный из такого дерева бинарь получает кои-восьмые строковые литералы при движке, который
считает весь текст UTF-8: расходятся сравнения имён, доски, кодировка лога, а запись на диск гонит
такие литералы через словарь транслита. `meson setup` теперь это проверяет и отказывается собирать.

# 2. Edit the UTF-8 version with Edit tool or text editor
# (make your changes here)
Still KOI8-R on disk, and still needing care: the world and configs (`lib/`, `lib.template/`,
`/lib/cfg/**`, `/lib/misc/**`, `/lib/text/help/**`, `/lib/etc/board/**`). For those the old rule
holds — convert, edit, convert back:

# 3. Convert back to KOI8-R
iconv -f utf-8 -t koi8-r /tmp/file_utf8.cpp > src/file.cpp
```bash
iconv -f koi8-r -t utf-8 lib/cfg/some.xml > /tmp/some_utf8.xml
# edit /tmp/some_utf8.xml
iconv -f utf-8 -t koi8-r /tmp/some_utf8.xml > lib/cfg/some.xml
```

**NEVER use the Edit tool directly on existing .cpp/.h files that contain Russian text.**
Only use Edit for:
- Newly created files that will be pure ASCII/English
- Temporary UTF-8 converted files (in /tmp)
- Files in .gitattributes marked as UTF-8 (e.g., Python files)
The pre-commit hook enforces both directions: sources must parse as UTF-8, world files must stay
in the encoding `.gitattributes` declares.

**NEVER use sed for editing source files.** Sed has tendency to:
- Modify files in unexpected places (matching wrong lines)
- Lead to file corruption detection and accidental `git checkout` (losing all uncommitted work)
- Cause cumulative errors from multiple sed operations
- Corrupt KOI8-R encoding

**Alternative: unified diff patches** - for small targeted changes:
```bash
Expand Down
27 changes: 22 additions & 5 deletions CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
При написании кода вы должны следовать правилу "одна команда - одна строка".
Желательно это же правило распространять и на объявления переменных.

Былины используют стандарт C++17.
Былины используют стандарт C++20 (задан в meson.build: cpp_std=c++20).

Тела конструкций if, if ... else for, while, do ... while всегда должны быть
заключены в символы '{', '}'.
Expand All @@ -22,10 +22,27 @@

using meat_mapping_t = std::pair<ObjVnum, ObjVnum>;

Код должен быть компилируемым на Windows, Linux. При компиляции вы должны
уделять особое внимание предупреждениям компилятора и исправлять их при первой
же возможности. Особенно если предупреждения появились вследствие добавленного
вами кода.
Код должен быть компилируемым на Windows, Linux.

**Перед коммитом сборка должна быть без единого предупреждения.** Достаточно
проверить на своей платформе; смотреть сборки CI не обязательно, хотя и полезно.

Предупреждения надо чинить по существу — понять, от чего оно, и устранить
причину. Заглушать нельзя: ни понижением уровня говорливости компилятора, ни
`-Wno-…`, ни `#pragma diagnostic ignored`, ни приведением типа ради тишины.

Предупреждение компилятора — это бесплатный статический анализ, который знает
то, чего не найти ни глазами, ни поиском по тексту. Живой пример: два падения
боевого сервера (issue #3751 и #3752) компилятор назвал заранее строкой
`directive writing 170 bytes into a region of size 162` — русский текст в UTF-8
занимает вдвое больше, чем занимал в KOI8-R, и фраза перестала влезать в буфер.
Предупреждение лежало в логе сборки всё время, пока по нему не начали падать
процессы. С тех пор именно это переполнение — уже ошибка сборки, а не
предупреждение (`-Werror=format-overflow` в meson.build).

Имейте в виду: **инкрементальная сборка предупреждения прячет** — уже собранные
файлы не перекомпилируются, и лог выглядит чистым. Когда важно увидеть весь лог,
собирайте с нуля в отдельном каталоге.

Старайтесь перед коммитом своих изменений запускать unit-тесты. Старайтесь
писать свои unit-тесты для написанного вами кода. Часто ошибки можно обнаружить
Expand Down
9 changes: 9 additions & 0 deletions Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,12 @@ ARG WITH_OTEL=false
ARG WITH_ADMIN_API=false
ARG WITH_YAML=true
ARG WITH_SQLITE=false
# Ревизия и счётчик коммитов: внутри контейнера git бесполезен (контекст может быть worktree,
# где .git -- файл-ссылка наружу, или распакованный архив), поэтому пробрасываем их снаружи:
# --build-arg GIT_REV=$(git rev-parse --short HEAD) \
# --build-arg GIT_COUNT=$(git rev-list --count HEAD)
ARG GIT_REV=
ARG GIT_COUNT=

RUN apk add --no-cache \
build-base make meson ninja git cmake samurai \
Expand Down Expand Up @@ -61,6 +67,9 @@ RUN if [ "$WITH_SQLITE" = "true" ]; then apk add --no-cache sqlite-dev; fi
WORKDIR /mud/mud
COPY . /mud/mud

ENV BYLINS_GIT_REV=${GIT_REV} \
BYLINS_COMMIT_COUNT=${GIT_COUNT}

RUN OTEL_OPT=$([ "$WITH_OTEL" = "true" ] && echo system || echo disabled); \
ADMIN_OPT=$([ "$WITH_ADMIN_API" = "true" ] && echo true || echo false); \
YAML_OPT=$([ "$WITH_YAML" = "true" ] && echo builtin || echo disabled); \
Expand Down
34 changes: 25 additions & 9 deletions meson.build
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,18 @@ if cpp.get_argument_syntax() == 'gcc'
project_args += ['-Ofast']
endif
project_args += ['-Wno-format-truncation']

# Переполнение буфера строкой формата -- ошибка, а не предупреждение. Компилятор считает,
# сколько байт запишет sprintf, и сравнивает с размером буфера; предупреждение об этом
# входит в -Wall и лежало в логе сборки, пока по нему не начали падать боевые процессы
# (#3751, #3752): русский текст в UTF-8 занимает вдвое больше, чем занимал в KOI8-R,
# и фразы, укладывавшиеся впритык, перестали влезать. Числа в коде при этом нет -- растёт
# сам литерал, поэтому глазами и грепом такое не ищется, а компилятором ищется даром.
#
# Уровень по умолчанию, не =2: тот цепляется к теоретической оценке ширины %f.
if cpp.has_argument('-Werror=format-overflow')
project_args += ['-Werror=format-overflow']
endif
if fortify_source
# -U на случай, если тулчейн уже определил _FORTIFY_SOURCE (иначе redefine-варнинг)
project_args += ['-U_FORTIFY_SOURCE', '-D_FORTIFY_SOURCE=3']
Expand Down Expand Up @@ -88,9 +100,6 @@ if host_system == 'windows'
endif
endif

if cpp.get_id() == 'clang'
project_args += '-Wno-invalid-source-encoding'
endif

cpu = host_machine.cpu_family()
# GNU-driver compilers only. clang-cl (get_id()=='clang-cl') takes its arch from the MSVC
Expand All @@ -108,12 +117,7 @@ if host_system == 'windows'
if cpp.get_argument_syntax() == 'msvc' # MSVC and clang-cl
project_args += ['/MP', '/bigobj', '-DFMT_UNICODE=0', '/external:anglebrackets', '/external:W0']
if cpp.get_id() == 'msvc'
# MSVC accepts the KOI8-R codepage (20866) for source/exec charset.
project_args += ['/source-charset:koi8-r', '/execution-charset:koi8-r']
else
# clang-cl rejects non-UTF-8 /source-charset ('invalid value koi8-r'); it keeps narrow-literal
# bytes as-is, so just silence the KOI8-R source-encoding warnings (as for the GNU clang driver).
project_args += ['-Wno-invalid-source-encoding']
project_args += ['/source-charset:utf-8', '/execution-charset:utf-8']
endif
elif cpp.get_id() == 'gcc' or cpp.get_id() == 'clang'
project_args += ['-Wa,-mbig-obj', '-D__USE_MINGW_ANSI_STDIO=1']
Expand Down Expand Up @@ -357,6 +361,15 @@ endif
subdir('src/third_party_libs/libfort')
dependencies += [fmt_dep, fort_dep]

# Исходники обязаны быть в UTF-8 (issue #3681). Дерево, выкаченное до флипа, при
# переключении ветки остаётся в KOI8-R -- git молчит, дерево числится чистым, а
# собранный отсюда бинарь получает кои-восьмые строковые литералы, и движок расходится
# сам с собой. Ловим это здесь, а не по кракозябрам в логе боевого сервера.
encoding_check = run_command(py3, files('tools/meson/check_sources_utf8.py'), meson.project_source_root(), check: false)
if encoding_check.returncode() != 0
error(encoding_check.stdout().strip())
endif

git = find_program('git', required: false)
git_rev = 'unknown'
if git.found()
Expand Down Expand Up @@ -645,7 +658,10 @@ main_sources = files(
'src/gameplay/mechanics/title.cpp',
'src/gameplay/statistics/top.cpp',
'src/utils/utils.cpp',
'src/utils/utf8.cpp',
'src/utils/native_text.cpp',
'src/utils/utils_encoding.cpp',
'src/utils/translit_koi8.cpp',
'src/gameplay/mechanics/weather.cpp',
'src/engine/olc/zedit.cpp',
'src/utils/utils_string.cpp',
Expand Down
3 changes: 3 additions & 0 deletions meson_options.txt
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,9 @@ option('lua_formatter', type: 'boolean', value: true, description: 'Build the em
option('with_asan', type: 'boolean', value: false, description: 'Build with Address Sanitizer')
option('use_pch', type: 'boolean', value: true, description: 'Use precompiled headers for circle.library')

# KOI8-R -> UTF-8 migration (issue #3681). Selects the engine's native runtime string encoding.
# koi8r = current byte semantics (default); utf8 = character semantics via the utf8:: helpers.

# Options for Admin API and Web features
option('admin_api', type: 'boolean', value: false, description: 'Enable Admin API and JSON-related features')

Expand Down
10 changes: 7 additions & 3 deletions src/administration/accounts.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
* 2018 (c) bodrich
*/
#include "accounts.h"
#include "utils/native_text.h"
#include "password.h"
#include "engine/entities/zone.h"
#include <sstream>
Expand Down Expand Up @@ -85,7 +86,7 @@ void Account::show_players(CharData *ch) {
ss << "Данные аккаунта: " << this->email << "\r\n";
for (auto &x : this->players_list) {
std::string name = GetNameByUnique(x);
name[0] = UPPER(name[0]);
native_text::capitalize_first(name);
ss << count << ") " << name << "\r\n";
count++;
}
Expand All @@ -101,7 +102,7 @@ void Account::list_players(DescriptorData *d) {
for (auto &x : this->players_list) {
std::string name = GetNameByUnique(x);
iosystem::write_to_output((std::to_string(count) + ") ").c_str(), d);
name[0] = UPPER(name[0]);
native_text::capitalize_first(name);
iosystem::write_to_output(name.c_str(), d);
iosystem::write_to_output("\r\n", d);
count++;
Expand Down Expand Up @@ -244,7 +245,10 @@ void Account::set_password(const std::string &password) {
}

bool Account::compare_password(const std::string &password) {
return CompareParam(this->hash_password, CRYPT(password.c_str(), this->hash_password.c_str()), true);
// Тот же приём, что в Password::compare_password: хэш посчитан по дисковым байтам,
// поэтому пароль приводим к дисковой кодировке перед crypt (issue #3681).
return CompareParam(this->hash_password,
CRYPT(native_text::to_disk(password).c_str(), this->hash_password.c_str()), true);
}

bool Account::quest_is_available(int id) {
Expand Down
Loading
Loading