Compare commits

..

2 Commits

Author SHA1 Message Date
sindoring 44542be5e2 Fix Russian character deletion confirmation casing
Fold Cyrillic only in the ruRU glue layer so the stock delete confirmation accepts typed mixed case while preserving its button and Enter guards.

Verified on Windows with actual stock GlueXML and the ruRU MPQ: reproduced the rejected title-case confirmation before the fix; engine-free offline checks pass for 9 Russian and 7 English inputs, button/Enter dispatch, and dialog reset. The new application regression body also passes through the engine-free loader adapter. cargo fmt --all -- --check and git diff --check pass. Full workspace/app gates and production build were not run for this change; no player account or server deletion used.
2026-10-18 00:42:17 +04:00
sindoring d1961e79fc Sync upstream through b2473e60 and retain ruRU (#8)
Integrate 182 upstream commits through b2473e60 while retaining MoonWell packaging, branding and the community ruRU pack.

Upstream replaces the C Lua/mlua host with benilla-lua/benilla-mlua and the native glue screens with stock GlueXML. The update also includes the new locale/DBC/name rules, cache and interaction handling, rendering, quest log and chat behavior, including the latest quest completion and timed-quest failure fixes. Fork adaptations use the new APIs: stock Wow.ini locales remain intact, while the ruRU overlay reads column zero and retains its ruRU authentication tag and Lua identity. Russian names keep their twelve-letter policy and Unicode roster matching; fonts, spell costs, race/class labels and translated options retain real-pack regression coverage.

Windows regression fixes keep the locale-default test valid under instrumented window overrides and the shader assertion independent of checkout line endings. The original C Lua 5.1 host, rebuilt from c0630f3d in an isolated directory, reproduces the complete Windows cos-sweep answer byte for byte; cos now uses platform-specific goldens.

Validation on Windows: scripts/gates.sh passed all automatic gates, including real-data and no-install workspace tests, reference tables, documentation, message catalog, render-pass lint, player clippy/tests and engine boots with and without data. MoonWell player clippy also passed with -D warnings. The committed tree exactly matches the full green stamp; five vendored Kira files retain the tested checkout's line endings, with normalized contents unchanged. All three actual ruRU pack regressions passed in both builds. Fifteen test archives were restored read-only from the published client and SHA-256 checked.

Each real-data/player rung skipped 31 tests because the optional addon corpus is absent (24 general, 7 Quiver). Live smoke was not run: no declared probe account; no player-account login or character creation was performed. Linux/macOS changes were not locally built.
2026-10-17 21:10:31 +03:00
2 changed files with 93 additions and 0 deletions
@@ -5,6 +5,27 @@
BENILLA_REALMLIST and BENILLA_REALMLIST_PINNED (set while $WOW_HOST fixes the address),
and reopens the dialog with the typed text when it is not an address. -->
<Script>
-- CharacterSelect.xml:679 compares the confirmation through strupper. Native 1.12 upper
-- (0x7fc180) leaves Cyrillic unchanged; the community ruRU pack needs its own case fold.
if GetLocale() == "ruRU" then
local function upper(text)
local result = strupper(text)
local lower = "абвгдеёжзийклмнопрстуфхцчшщъыьэюя"
local capitals = "АБВГДЕЁЖЗИЙКЛМНОПРСТУФХЦЧШЩЪЫЬЭЮЯ"
for i = 1, string.len(lower), 2 do
result = string.gsub(result, string.sub(lower, i, i + 1), string.sub(capitals, i, i + 1))
end
return result
end
CharacterDeleteEditBox:SetScript("OnTextChanged", function()
if upper(this:GetText()) == upper(DELETE_CONFIRM_STRING) then
CharacterDeleteButton1:Enable()
else
CharacterDeleteButton1:Disable()
end
end)
end
GlueDialogTypes["BENILLA_REALMLIST"] = {
-- The realmList CVar's registered help text.
text = "Address of realm list server",
@@ -38,6 +38,78 @@ fn ruru_stock_glue_loads_and_keeps_cyrillic_character_names() {
assert!(script.take_errors().is_empty());
}
#[test]
fn ruru_delete_confirmation_accepts_typed_cyrillic_without_case_sensitivity() {
let data = benilla_formats::wow_data_or_skip!();
let locale = benilla_formats::ClientLocale::RuRu;
if locale.pack_folder(&data).is_none() {
benilla_formats::skipped_optional("no ruRU language pack", &[data.join("ruRU")]);
return;
}
let chain = benilla_formats::Chain::open_localized(&data, locale).unwrap();
let _selected = super::reference_ui::fixture::use_chain(chain);
let mut script = UiScript::new_glue().unwrap();
script.set_russian_language_pack(true);
script.set_screen_size(1024.0, 768.0);
assert!(load_glue(&mut script).is_empty());
// Offline only: exercise the stock buttons and Enter without requesting a real deletion.
script
.run(
"GetCharacterInfo = function() return 'Probe', 'Human', 'Warrior', 1 end; \
DELETE_REQUESTS = 0; \
DeleteCharacter = function() DELETE_REQUESTS = DELETE_REQUESTS + 1 end; \
CharacterSelect.selectedIndex = 1; GlueParent:Show()",
)
.unwrap();
for (typed, accepted) in [
("Удалить", true),
("удалить", true),
("УдАлИтЬ", true),
("УДАЛИТЬ", true),
("Удали", false),
("Удалить!", false),
(" Удалить", false),
("DELETE", false),
("", false),
] {
for enter in [false, true] {
script
.run(
"CharacterDeleteDialog:Hide(); CharacterDeleteDialog:Show(); \
CharacterDeleteEditBox:SetFocus()",
)
.unwrap();
script.tick(0.01);
assert_eq!(text(&script, "CharacterDeleteEditBox"), "");
for c in typed.chars() {
script.char_input(&c.to_string());
}
script.tick(0.01);
assert_eq!(text(&script, "CharacterDeleteEditBox"), typed);
assert_eq!(
script
.eval::<bool>("return CharacterDeleteButton1:IsEnabled() == 1")
.unwrap(),
accepted,
"{typed:?}"
);
let before: i64 = script.eval("return DELETE_REQUESTS").unwrap();
if enter {
script.frame_key_input("ENTER");
script.key_up("ENTER");
} else {
script.run("CharacterDeleteButton1:Click()").unwrap();
}
assert_eq!(
script.eval::<i64>("return DELETE_REQUESTS").unwrap() - before,
i64::from(accepted),
"{typed:?}, enter={enter}"
);
}
}
assert!(script.take_errors().is_empty());
}
/// The stock glue VM on `screen` with both agreements accepted, as a returning player's is.
fn glue_on(screen: GlueScreen) -> UiScript {
let mut s = UiScript::new_glue().unwrap();