Negative skillIncreasesForAttribute value causes "GameSetting 'iLevelUp-2Mult' not found" every frame

Summary

If a player's skill increase counter for an attribute is negative, NpcStats::getLevelupAttributeMultiplier builds a game setting name that doesn't exist, such as iLevelUp-2Mult, and the lookup throws. The stats window calls this function in onFrame, so the error is logged every frame:

[04:33:09.210 E] Error in frame: GameSetting '"iLevelUp-2Mult"' not found

A mod user reported this. Lua can set the counter to a negative value, and neither the setter nor the getter checks for it.

Tested against master bb9e0ce2a9 (2026-09-15); also present in 0.51.

Cause

apps/openmw/mwmechanics/npcstats.cpp:259-271:

  int MWMechanics::NpcStats::getLevelupAttributeMultiplier(ESM::RefId attribute) const
  {
      auto it = mSkillIncreases.find(attribute);
      if (it == mSkillIncreases.end() || it->second == 0)
          return 1;
      int num = std::min(10, it->second);
      
      // iLevelUp01Mult - iLevelUp10Mult
      std::stringstream gmst;
      gmst << "iLevelUp" << std::setfill('0') << std::setw(2) << num << "Mult";
   
      return MWBase::Environment::get().getESMStore()->get<ESM::GameSetting>().find(gmst.str())->mValue.getInteger();
  }

The value is only capped at 10. Nothing handles values below 1, so a negative count gives iLevelUp-1Mult, iLevelUp-2Mult, and so on.

setSkillIncreasesForAttribute (npcstats.cpp:281-287) stores any non-zero integer, and the Lua binding (apps/openmw/mwlua/stats.cpp:101-111, setNpcValue) passes the value through unchanged. setSkillIncreasesForSpecialization doesn't check for negative values either.

Affected callers:

  • apps/openmw/mwgui/statswindow.cpp:344, in StatsWindow::onFrame, which runs every frame.
  • apps/openmw/mwgui/levelupdialog.cpp:117, :205 and :254, in the level-up dialog.

Steps to reproduce

  1. Add a player-local Lua script:

    local types = require('openmw.types')
    local self = require('openmw.self')
    return {
        engineHandlers = {
            onInit = function()
                types.Actor.stats.level(self).skillIncreasesForAttribute.strength = -2
            end,
        },  
    }   
  2. Start a new game or load a save, and open the stats window.

Expected behavior

A negative counter is treated like 0 (multiplier x1), or the API rejects or clamps negative values.

Actual behavior

GameSetting 'iLevelUp-2Mult' not found is logged every frame. Leveling up raises the same error.

Suggested fix

The smallest fix is in the getter:

if (it == mSkillIncreases.end() || it->second <= 0)
    return 1;

It may also be worth clamping negative values to 0, or rejecting them, in setSkillIncreasesForAttribute and setSkillIncreasesForSpecialization, or in the Lua binding. Saves that already contain a negative value would then load cleanly too.