Skip to content

refactor(items): the wings', capes' and pets' tooltip values come from their data - #818

Merged
Mosch0512 merged 16 commits into
sven-n:mainfrom
ezequielzacca:feat/item-tooltip-values-to-data
Oct 23, 2026
Merged

Mosch0512 merged 16 commits into
sven-n:mainfrom
ezequielzacca:feat/item-tooltip-values-to-data

Conversation

@ezequielzacca

@ezequielzacca ezequielzacca commented Oct 21, 2026 •

Copy link
Copy Markdown
Contributor

Summary

Step 4 of #815 (data-driven item tooltips). #817, #816 and #813 are merged, so this PR's diff is this step only.

The damage increase and absorption that RenderItemInfo() computed for wings, capes and pets come from the item data now. They are descriptions with placeholders (#817) over new level bonus tables in Stats/ItemStats.json:

"description": [
  {"text": {"en": "Increase {table:wingDamageIncrease1st}% of Damage", "de": "Erhöht den Schaden um {table:wingDamageIncrease1st}%", …}},
  {"text": {"en": "Absorb {table:wingDamageAbsorb1st}% of Damage", …}},
  {"text": {"en": "Increase speed", …}}
]
Items Damage increase Absorption Speed line
Wings of Elf, Heaven, Satan, Wing of Curse; Small Wing of Curse, Small Wings of Elf, Heaven, Satan wingDamageIncrease1st 12 + 2/level wingDamageAbsorb1st 12 + 2/level yes
Wings of Spirits, Soul, Dragon, Darkness; Wind of Despair wingDamageIncrease2nd 32 + 1/level wingDamageAbsorb2nd 25 + 2/level yes
Wings of Storm, Eternal, Illusion, Ruin, Dimension wingDamageIncrease3rd 39 + 2/level wingDamageAbsorb3rd 39 + 2/level yes
Cape of Emperor, Cape of Overrule wingDamageIncrease3rd capeDamageAbsorb3rd 24 + 2/level yes
Small Cape of Lord, Little Warrior's Cloak capeDamageIncrease 20 + 2/level smallCapeDamageAbsorb 20 + 2/level yes
Cape of Fighter capeDamageIncrease capeOfFighterDamageAbsorb 10 + 2/level no
Cape of Lord capeDamageIncrease capeOfLordDamageAbsorb 10 + 1/level no
Horn of Dinorant 15 (in the text) 10 (in the text) no
Guardian Angel – 20, and "Max HP +50 increased" (in the text) no
Imp "Increase 30% of attacking & Wizardry Dmg" – no
  • Tables: each is {"values": <+0 value>, "perLevel": <per level>}.
    • They're named after what the tooltip shows. OpenMU's DamageIncrease2ndWings and similar hold only the per-level factor (0.01 × level), so I used names OpenMU doesn't have, to avoid mixing the two up in a later item exchange.
    • These tables are only for descriptions: no stat profile names them.
  • Texts: the printf texts become data as the game showed them, in all 11 languages: %d is the placeholder (or the fixed number), %% is %.
    • Two translations of the Imp's text had broken escapes: Spanish and Russian % %, which printf showed as %, and Portuguese % o, which printf read as an octal number with no argument. They read as plain text now.
    • The Imp's Portuguese text read "… de ataque e magia de % of"; it's "Aumenta em 30% o dano de ataque e magia" now. Its English "% of" in the other languages is fixed with the other "of" texts in fix(items): damage texts without a stray "of", and the Fruits' stat words #828.
    • Otherwise the translations are unchanged, including their wording: "Increase speed" is the game's own text in every language (Polish "Zwiększa prędkość").
  • The same lines in 22 wings: each wing and cape writes its three lines out, with their 11 languages, so a translation fix has to be made in each of them for now. feat(items): the descriptions' repeated texts are written once #827 writes them once, as shared texts.
  • Removed:
    • the cases in ZzzInventory.cpp (88 lines);
    • the two .resx texts no code uses any more, "Max HP +%d increased" (legacy id 739) and "Increase 30%% of attacking & Wizardry Dmg" (576). No Lookup() range reaches those ids.
    • "Increase %d%% of Damage" and "Increase speed" stay: other code prints them.
  • Table uses (ValidateTableUses, run at Load, in Save Items and in the bmd import, next to the placeholder check):
    • A table no stat uses is no longer a warning of the stat file on its own, since it can be one for descriptions: a table that neither a stat nor a description names is.
    • Wing tiers: the old code chose the wings' lines by their wingTier (and cape tag). Now each wing names its tables, so a wing whose description names other tables than most of the other wings of its tier, cape tag and group is a warning: a new third-tier wing without the lines, or a wing re-tiered in the data that keeps its old tier's tables, gets "names no table for its wing lines; the other third-tier wings of group 12 name {table:wingDamageAbsorb3rd}, {table:wingDamageIncrease3rd}". Cape of Fighter (12) and Cape of Lord (13) are apart by their group, as the old code had them.
    • The shipped-data test checks that the shipped items give neither warning.
  • Comparison test: "The wings', capes' and pets' tooltip values come from their data as from the old formulas" fills in the description of each of the 27 items at every level from +0 to +15 and compares it with the lines the old code printed. It keeps a copy of the old formulas. In English for every line, and in every language for the lines whose .resx texts other code still prints ("Increase %d%% of Damage", "Absorb %d%% of Damage", "Increase speed"), filled as printf fills them.

Nothing changes in game.

Not in this PR

The other cases of that chain are fixed texts that depend on the item level: Town Portal scroll, Spirit, Loch's Feather, Fruits, Siege Potion, Contract Summon, Life Stone, Rena. Three plain ones are in the same chain too: Scroll of Archangel, Blood Bone and Invisibility Cloak. They need a per-line level condition and come next, as step 4b. The Armor of Guardsman's Chaos Castle table and the Lost Map / Symbol of Kundun stay code (they read the hero and other tables).

Screenshots

Before (#817) and after, the same. The values come from the data now.

Wing of Storm +15 (69 = 39 + 2 × 15):

Legacy theme Modern theme
Before
After
The item data of the "after" images (12,36)

src/bin/Data/Items/Group12_Wing.json:

{
  "number": 36,
  "name": {
    "en": "Wing of Storm",
    "de": "Sturmflügel",
    "es": "Ala de Tormenta",
    "pt": "Asa da Tempestade"
  },
  "tags": ["valuable"],
  "description": [
    {
      "text": {
        "en": "Increase {table:wingDamageIncrease3rd}% of Damage",
        "de": "Erhöht den Schaden um {table:wingDamageIncrease3rd}%",
        "es": "Aumenta el daño de {table:wingDamageIncrease3rd}% of",
        "id": "Meningkatkan Kerusakan {table:wingDamageIncrease3rd}% of",
        "ja": "ダメージ {table:wingDamageIncrease3rd}%増加",
        "pl": "Zwiększa obrażenia o {table:wingDamageIncrease3rd}%",
        "pt": "Aumentar o dano de {table:wingDamageIncrease3rd}% of",
        "ru": "Увеличение урона {table:wingDamageIncrease3rd}% of",
        "tl": "Dagdagan ang {table:wingDamageIncrease3rd}% of Pinsala",
        "uk": "Збільшення шкоди на {table:wingDamageIncrease3rd}% of",
        "zh-TW": "增加 {table:wingDamageIncrease3rd}% of 傷害"
      }
    },
    {
      "text": {
        "en": "Absorb {table:wingDamageAbsorb3rd}% of Damage",
        "de": "Absorbiert {table:wingDamageAbsorb3rd}% des Schadens",
        "es": "Absorber daño {table:wingDamageAbsorb3rd}% of",
        "id": "Menyerap Kerusakan {table:wingDamageAbsorb3rd}% of",
        "ja": "ダメージ {table:wingDamageAbsorb3rd}%吸収",
        "pl": "Pochłania {table:wingDamageAbsorb3rd}% obrażeń",
        "pt": "Absorver Dano {table:wingDamageAbsorb3rd}% of",
        "ru": "Поглощение {table:wingDamageAbsorb3rd}% of урона",
        "tl": "Isipsip ang {table:wingDamageAbsorb3rd}% of Pinsala",
        "uk": "Поглинання шкоди {table:wingDamageAbsorb3rd}% of",
        "zh-TW": "吸收{table:wingDamageAbsorb3rd}% of傷害"
      }
    },
    {
      "text": {
        "en": "Increase speed",
        "de": "Erhöht die Geschwindigkeit",
        "es": "aumentar la velocidad",
        "id": "Tingkatkan kecepatan",
        "ja": "移動速度向上",
        "pl": "Zwiększa prędkość",
        "pt": "Aumentar a velocidade",
        "ru": "Увеличить скорость",
        "tl": "Dagdagan ang bilis",
        "uk": "Збільште швидкість",
        "zh-TW": "提高速度"
      }
    }
  ]
  …
}

The tables of src/bin/Data/Items/Stats/ItemStats.json it names:

[
  {
    "name": "wingDamageAbsorb3rd",
    "caption": {"en": "Damage Absorption in % (3rd Wings)"},
    "values": 39,
    "perLevel": 2
  },
  {
    "name": "wingDamageIncrease3rd",
    "caption": {"en": "Damage Increase in % (3rd Wings and Capes)"},
    "values": 39,
    "perLevel": 2
  }
]

Guardian Angel:

Legacy theme Modern theme
Before
After
The item data of the "after" images (13,0)

src/bin/Data/Items/Group13_Helper.json:

{
  "number": 0,
  "name": {
    "en": "Guardian Angel",
    "de": "Schutzengel",
    "es": "Angel Guardián",
    "pt": "Anjo guardião"
  },
  "description": [
    {
      "text": {
        "en": "Absorb 20% of Damage",
        "de": "Absorbiert 20% des Schadens",
        "es": "Absorber daño 20% of",
        "id": "Menyerap Kerusakan 20% of",
        "ja": "ダメージ 20%吸収",
        "pl": "Pochłania 20% obrażeń",
        "pt": "Absorver Dano 20% of",
        "ru": "Поглощение 20% of урона",
        "tl": "Isipsip ang 20% of Pinsala",
        "uk": "Поглинання шкоди 20% of",
        "zh-TW": "吸收20% of傷害"
      }
    },
    {
      "text": {
        "en": "Max HP +50 increased",
        "de": "Max. HP +50 erhöht",
        "es": "HP máximo +50 aumentado",
        "id": "HP Maks +50 meningkat",
        "ja": "最大生命力 +50増加",
        "pl": "Zwiększono maksymalne HP +50",
        "pt": "HP máximo +50 aumentado",
        "ru": "Макс. HP +50 увеличен.",
        "tl": "Tumaas ang Max HP +50",
        "uk": "Макс HP +50 збільшено",
        "zh-TW": "最大生命值+50增加"
      }
    }
  ]
  …
}

Related issues

Step 4 of #815. Builds on #817, #816 and #813.

Checklist

  • I have read and followed docs/CODING_RULES.md.
  • I have built the project locally (see docs/build/README.md).
    • Windows x64 Release (editor): all 1266 tests pass, including the comparison test above.
    • CI's quality gate run locally: clang-format 21.1.8 on the changed lines and cppcheck 2.13 with CI's flags pass.
    • In game (Release x64 against a local OpenMU), the Wing of Storm +15 and the Guardian Angel were compared before and after in both themes.
  • Changes are focused on one concern; the diff is as small as it reasonably can be.

…m their data

The damage increase and absorption the tooltip code computed for the
wings, capes and pets are their descriptions now, with placeholders over
11 new level bonus tables of Stats/ItemStats.json (wingDamageIncrease1st
is 12 + 2 per level, ...): 24 wings and capes, plus Horn of Dinorant,
Guardian Angel and Imp, whose fixed numbers are written in the text.
Their cases leave RenderItemInfo; the two .resx texts no code uses any
more go too (the shared "Increase %d%% of Damage" and "Increase speed"
stay, other code prints them).

The printf texts become data as the game showed them: %d is the
placeholder or the number, %% is %. Two translations of the Imp's text
had broken escapes ("% %", shown as %; Portuguese "% o", read as an
octal number); they read as plain text now.

A table no stat uses is no longer a warning of the stat file alone: it
can be one for descriptions, so ValidateDescriptionPlaceholders warns
about a table that neither a stat nor a description uses.

A test compares every one of the 27 items at every level from +0 to +15
with the old formulas.
…at/item-tooltip-values-to-data

# Conflicts:
#	docs/item-data.md
#	src/Localization/Game.pl.resx
#	src/source/Data/GameData/ItemData/ItemStatProfiles.cpp
#	src/source/Data/GameData/ItemData/ItemStatsJson.cpp
…at/item-tooltip-values-to-data

# Conflicts:
#	docs/item-data.md
…at/item-tooltip-values-to-data

# Conflicts:
#	docs/item-data.md
…at/item-tooltip-values-to-data

# Conflicts:
#	docs/item-data.md
…at/item-tooltip-values-to-data

# Conflicts:
#	docs/item-data.md
#	src/source/Data/GameData/ItemData/ItemDataValidation.cpp
#	src/source/Data/GameData/ItemData/ItemDataValidation.h

@Mosch0512 Mosch0512 left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Verdict: changes requested for two items; the rest is non-blocking. The move itself is right:

  • Values: the tables give exactly what the old formulas printed, for all 27 items at every level from +0 to +15. The comparison test covers every shipped item with a wing tier.
  • Tooltip order: unchanged. The Cape of Fighter and Cape of Lord lines were the last link of the same chain, and the rest of that chain only matches other items.
  • Translations: apart from the Polish speed text below, every language matches what printf showed, including the Imp's Spanish and Russian % %. The Portuguese % o is the deliberate fix you describe.
  • Removed texts: nothing reads ids 576 and 739 any more; server command 4's 725 + Cmd2 only covers the Rena texts 725–727.
  • Warning routing: the "unused table" warning still reaches Item Formulas from Item Checks, because its source is the stat file.

Required

  • Wing tiers. The second-tier, third-tier and small wings' lines used to follow the item's wingTier in the data. Now they come only from each item's description, so a custom or re-tiered wing loses them or keeps the old tier's values (inline, ZzzInventory.cpp:3805).
  • Polish "Increase speed". It changed in all 22 wings, so the game now spells this line two ways in Polish (inline, Group12_Wing.json:52).

Non-blocking

  • The comparison test checks English only, though the remaining .resx keys would let it check every language (inline, test_item_database_files.cpp:303).
  • The Imp's Portuguese text now shows its broken "de % of" (inline, Group13_Helper.json:82).
  • The unused-table warning lives inside the placeholder check (inline, ItemDataValidation.cpp:423).
  • The same three lines are copied into 22 items until the shared texts of #827 (inline, Group12_Wing.json:123).

Before merging (not code)

  • The PR description is outdated. It still shows the placeholders without table: ({wingDamageIncrease1st}, and the Wing of Storm's JSON), while the second commit writes them as {table:wingDamageIncrease1st}.
  • The branch needs updating. It conflicts with main, and it doesn't have #817's three newest commits yet (0a8ac43, 4022b75, 1b899b1). The hint fix touches the same lines in ItemDataValidation.cpp, which this branch has unchanged, so it should merge cleanly.

What I checked:

  • this step's two commits against #817, and the chain around the removed cases;
  • the wing tier helpers the old code used;
  • every item with a wing tier in the shipped data, against the comparison test;
  • each language of every moved text, against printf of the old .resx formats;
  • every Lookup() base near the removed ids;
  • how Item Checks routes the moved warning.

I didn't build this branch or run the game.

TextListColor[TextNum] = TEXT_COLOR_WHITE; TextBold[TextNum] = false; TextNum++;
}
else if (ip->Type == ITEM_SPIRIT)
if (ip->Type == ITEM_SPIRIT)

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Required. The removed branches for the second-tier, third-tier and small wings weren't hard-coded: IsSecondTierWingExceptCape, IsThirdTierWing and IsSmallWing read the item's wingTier (and its cape tag) from the item data. Now the lines come only from each item's own description, so the tier no longer decides them. That stays so through the rest of the stack: at #836 each wing still names its own tables.

Two cases break:

  • A custom wing. A server adds a wing with "wingTier": "third". Before, its tooltip showed "Increase 39+2L% of Damage", "Absorb 39+2L% of Damage" and "Increase speed" with no extra data. Now it shows none of them unless its description repeats the three lines.
  • A re-tiered wing. Changing a wing's tier in the data moves its stat profile, since the profile rules follow the tier, but leaves its tooltip on the old tier's tables. The two then disagree without any message.

Either of these would close the gap:

  • a check that an item with a wing tier names that tier's tables, as a warning or an error;
  • tooltip lines conditioned on the wing tier, once the layout steps can express that.

Comment thread src/bin/Data/Items/Group12_Wing.json Outdated
"es": "aumentar la velocidad",
"id": "Tingkatkan kecepatan",
"ja": "移動速度向上",
"pl": "Zwiększ prędkość",

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Required. All 22 "Increase speed" lines say "Zwiększ prędkość" in Polish. But the .resx key the old code printed, "Increase speed" (I18N::Game::IncreaseSpeed), is "Zwiększa prędkość". That key stays in the game: the tooltip code still prints it at ZzzInventory.cpp:4640 and 4650. So Polish players now see the line in two spellings, "Zwiększ" ("Increase!") on wings and "Zwiększa" ("Increases") elsewhere. The description also says the translations are otherwise unchanged, including their wording. Using "Zwiększa prędkość" in the 22 items keeps it as it was.

return {};
}

TEST_CASE("The wings', capes' and pets' tooltip values come from their data as from the old formulas [data][items]")

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Non-blocking. This compares the English text only. Three of the formats stay in .resx, because other code prints them: "Increase %d%% of Damage", "Absorb %d%% of Damage" and "Increase speed". So the test can compare every language: printf the locale's format with the expected value, and compare it with the filled description line. That would have caught the Polish speed text, and it keeps guarding the translations as later steps copy them.

Comment thread src/bin/Data/Items/Group13_Helper.json Outdated
"id": "Meningkatkan 30% of serangan & Dmg Sihir",
"ja": "攻撃力、力 30%増加",
"pl": "Zwiększa obrażenia fizyczne i magiczne o 30%",
"pt": "Aumenta em 30% o dano de ataque e magia de % of",

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Non-blocking, follow-up. The Portuguese Imp text is now shown as written: "… de ataque e magia de % of". Before, printf read % o as an octal conversion with no argument and printed garbage, so it was broken either way. As data, it's now a one-line fix ("Aumenta em 30% o dano de ataque e magia"). The Imp text keeps an English "% of" in es, ru, id, tl, uk and zh-TW too, so it could join the "of" fixes of #828.

if (!profiles.IsTableUsedByStats(table) && !named.contains(table))
{
issues.push_back({ItemDataIssueSeverity::Warning, statFile, ItemDataIssue::NoItem, ItemDataIssue::NoItem,
"tables." + table, "neither a stat nor an item description uses this table"});

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Non-blocking. The stat file's "unused table" warning now lives in ValidateDescriptionPlaceholders. The function name no longer says what it does, and callers that only want the name check get stat file warnings too: the unit tests now filter out NoItem issues for that reason. A separate ValidateTableUses(items, profiles, statFile, issues), called where this one is (Load, Save Items, bmd import), keeps both jobs visible.

Comment thread src/bin/Data/Items/Group12_Wing.json Outdated
"es": "aumentar la velocidad",
"id": "Tingkatkan kecepatan",
"ja": "移動速度向上",
"pl": "Zwiększ prędkość",

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Non-blocking. The same three lines, each with 11 languages, are written out in all 22 wings and capes: about 1,000 lines of JSON, where only the table names differ by tier. A translation fix, like the Polish one above, has to be made in 22 places per language until #827's shared texts land. That's fine as a step; a line about it in the description would help.

…ip-values-to-data

# Conflicts:
#	tests/data/test_item_description_placeholders.cpp
… names its tier's tables

The unused-table warning moves out of the placeholder check into
ValidateTableUses, run where that check is (Load, Save Items, bmd import).
It also warns about a wing whose description names other tables than most of
the other wings of its tier, cape tag and group: the tooltip code chose the
wings' damage and absorption lines by the tier, so a new wing without them, or
a re-tiered wing that keeps its old tier's tables, is pointed out.
…t are the game's

"Zwiększa prędkość" as the game's "Increase speed" text has it, and the
Imp's Portuguese text without its broken "de % of".

@sven-n sven-n left a comment

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Reviewed the wing/cape/pet tooltip value migration, including src/source/Engine/Object/ZzzInventory.cpp and ItemDescriptionPlaceholders.cpp. No independently actionable defect identified in the inspected code. The value tables and localized placeholder output are especially worth checking in-game for level 0 and 15, and for the distinct cape absorption formulas. I did not run the build or gameplay tests.

Reviewed with AGENTS.md and docs/CODING_RULES.md as the quality baseline.

@ezequielzacca

Copy link
Copy Markdown
Contributor Author

Thanks! All addressed in this PR, and main is merged in (with your three #817 commits):

Required

  • Wing tiers (bdb0235): the tooltip's lines can't follow the tier any more, so the check does. ValidateTableUses warns about a wing whose description names other tables than most of the other wings of its tier, cape tag and group: a new third-tier wing without the lines gets "names no table for its wing lines; the other third-tier wings of group 12 name {table:wingDamageAbsorb3rd}, {table:wingDamageIncrease3rd}", and a re-tiered wing that keeps its old tier's tables is pointed out the same way. The group keeps Cape of Fighter (12) and Cape of Lord (13) apart, as the old code had them, and no tier → table map is written in code (the capes' exceptions would make one wrong). The shipped data gives no such warning, and the tests cover the new wing, the re-tiered one, a cape and another group. From feat(items): the descriptions' repeated texts are written once #827 on it also reads the lines' shared texts and values, and from feat(items): the tooltip's item names come from the data (#815 step 5d-1) #833 the name lines.
  • Polish "Increase speed" (52fd730): "Zwiększa prędkość" in all 22 wings, and in feat(items): the descriptions' repeated texts are written once #827's shared text.

Non-blocking

The description shows table: now. cppcheck also caught a range-for over a temporary's member in the new check (737e35d); that's fixed, and CI's quality gate passes locally.

…s own item table

Run in one process after a test that loaded the item data (the MU
Helper pickup filter tests do), the comparison test crashed: it
switches the UI language for every language, the item data follows
each switch and refills ItemAttribute, which is null outside a
ClientDataScope. CTest runs each test case in its own process, so CI
did not show it. The test now runs inside a ClientDataScope, as
ClientDataScope's comment asks of a test that switches the language.
@Mosch0512

Copy link
Copy Markdown
Collaborator

Thanks, the fixes look good: the Polish speed text, the every-language comparison (it fails on the old Polish text) and the Imp's Portuguese text. The new wing check catches both cases from the review, and the shipped data gives no warnings.

A test crash, fixed in 9980fdb. Please pull before your next push.

  • The crash: the comparison test switches the UI language for every language (I18N::SetLocale) outside a ClientDataScope. Run in one process after a test that loaded the item data, the item data's language listener refills ItemAttribute, which is null outside a scope, and the test crashes with SIGSEGV. The MU Helper pickup filter tests load the item data, each inside its own scope.
  • Reproducing it: two test cases are enough: "Helper extra filters distinguish level-aware box names", then this one.
  • Why CI misses it: CTest runs each case in its own process. A run of the whole binary, as an IDE does it, crashed every time.
  • The fix: it's the same trap as test(items): the description language test switches the language with its own item table #847. ClientDataScope's comment says a test that switches the language needs one, and 9980fdb gives the test one. The whole binary now passes all 166 test cases, also in random order.

The wing check: remaining gaps, all non-blocking

  • Lone wings aren't checked. FindTierTables returns nothing when a wing is alone in its tier, cape tag and group. That covers the Cape of Fighter (12,49), the Cape of Lord (13,30), and any custom wing in a combination of its own. Removing all their lines, or giving the Cape of Lord the third tier, gives no warning. Two ways to close it:

    • compare a lone wing with the wings of the same tier and cape tag in the other groups;
    • write a small tier-to-tables map in the data, where the capes' exceptions fit too.
  • A tie picks whichever set of tables sorts first. max_element returns the first of equal counts, so:

    • In a pair (12,40 and 12,50, or 12,130 and 12,135), one wrong table warns both, including the correct one.
    • In a 2:2 split among the third-tier wings, the correct wings can be told they're the odd ones. Switch two of them to the second-tier tables, and the three unchanged ones get the warning.
    • With no lines: the empty set sorts first, so a tie with wings that have no lines gives no warning at all. For example, two new second-tier capes without lines next to the Cape of Fighter.

    Reporting a tie as "the wings of this tier disagree", without picking a side, would avoid the wrong blame.

  • Lines without a table aren't covered. The check compares sets of tables only, so a wing that loses only "Increase speed", or gains one, isn't pointed out.

  • Docs:

    • the new warning isn't in docs/item-data.md's list of warnings;
    • the comment at test_item_stats_json.cpp:395 still says the unused-table warning comes from ValidateDescriptionPlaceholders, while it now comes from ValidateTableUses.

… a tie, and checks a lone wing against the other groups

Following the review: a wing is compared by its lines (their texts, tables
included), so one that loses or gains a line without a table is pointed out
too; wings that split evenly get one warning that they don't agree, so neither
side is blamed; wings without lines count as a set of lines; and a wing alone
in its tier, cape tag and group is compared with its tier's wings in the other
groups that have more than one (Cape of Fighter and Cape of Lord, each alone,
aren't compared with each other). The warning is in the docs, and the stat
file test's comment names ValidateTableUses.
@ezequielzacca

Copy link
Copy Markdown
Contributor Author

Thanks! Pulled 9980fdb, and the wing check now covers the gaps (b706374):

  • Whole lines, not only tables. A wing is compared by its lines: their English texts with the tables in them (from feat(items): the descriptions' repeated texts are written once #827 on, the shared text's name and the line's values). So a wing that loses or gains "Increase speed" is pointed out too.
  • No blame in a tie. When two sets of lines are as common in a group (a pair with one wrong line, or a 2:2 split), the check says once that the wings disagree ("the third-tier wings of group 12 don't agree on their wing lines: 2 have 3 lines naming …, 2 have 3 lines naming …") and blames neither side. Wings without lines count as a set of lines, so a tie with them isn't silent any more.
  • Lone wings. A wing alone in its tier, cape tag and group is compared with its tier's wings with the same cape tag in the other groups, where those are more than one. That covers a custom third-tier wing in a group of its own. Cape of Fighter (12) and Cape of Lord (13) are each alone, so they aren't compared with each other: their absorption differs, and the old code wrote them apart by item type, not by tier. A data map of tier → tables would be the way to check those two as well; I left that out, since nothing chose their lines by the tier before.
  • Docs: the warning is in docs/item-data.md (under Description placeholders, linked from the stat file's warnings), and the comment in test_item_stats_json.cpp names ValidateTableUses.

Tests cover the speed line, both ties, the lines-less tie, a lone group-13 wing, and Fighter/Lord staying apart. The whole test binary passes, and CI's quality gate passes locally. Carried through the stack to #836.

The wing check took the lines most wings of a tier have as the ones the
others are measured by, and wings without lines counted as one set of
lines. New wings imported without their lines (a bmd import gives new
items none), more of them than the wings with lines, then made "no
lines" the majority: the wings with the right lines were pointed out and
the new ones were not. Wings without lines no longer set the lines the
others are compared with, so the new ones are the ones pointed out; a
tie among the wings with lines is still reported as such.
@Mosch0512
Mosch0512 merged commit 7a0a02a into sven-n:main Oct 23, 2026
8 checks passed
ezequielzacca added a commit to ezequielzacca/MuMain that referenced this pull request Oct 23, 2026
ezequielzacca added a commit to ezequielzacca/MuMain that referenced this pull request Oct 23, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants