{
  "_class" : "hudson.model.FreeStyleBuild",
  "actions" : [
    {
      "_class" : "hudson.model.CauseAction",
      "causes" : [
        {
          "_class" : "hudson.triggers.SCMTrigger$SCMTriggerCause",
          "shortDescription" : "Started by an SCM change"
        }
      ]
    },
    {
      
    },
    {
      "_class" : "hudson.plugins.git.util.BuildData",
      "buildsByBranchName" : {
        "refs/remotes/origin/master" : {
          "_class" : "hudson.plugins.git.util.Build",
          "buildNumber" : 355,
          "buildResult" : None,
          "marked" : {
            "SHA1" : "c1aad166e5bd658877dd44d5441269579047e959",
            "branch" : [
              {
                "SHA1" : "c1aad166e5bd658877dd44d5441269579047e959",
                "name" : "refs/remotes/origin/master"
              }
            ]
          },
          "revision" : {
            "SHA1" : "c1aad166e5bd658877dd44d5441269579047e959",
            "branch" : [
              {
                "SHA1" : "c1aad166e5bd658877dd44d5441269579047e959",
                "name" : "refs/remotes/origin/master"
              }
            ]
          }
        }
      },
      "lastBuiltRevision" : {
        "SHA1" : "c1aad166e5bd658877dd44d5441269579047e959",
        "branch" : [
          {
            "SHA1" : "c1aad166e5bd658877dd44d5441269579047e959",
            "name" : "refs/remotes/origin/master"
          }
        ]
      },
      "remoteUrls" : [
        "https://github.com/mcMMO-Dev/mcMMO.git"
      ],
      "scmName" : ""
    },
    {
      
    },
    {
      
    },
    {
      
    },
    {
      "_class" : "org.jenkinsci.plugins.displayurlapi.actions.RunDisplayAction"
    }
  ],
  "artifacts" : [
    {
      "displayPath" : "mcMMO.jar",
      "fileName" : "mcMMO.jar",
      "relativePath" : "target/mcMMO.jar"
    },
    {
      "displayPath" : "original-mcMMO.jar",
      "fileName" : "original-mcMMO.jar",
      "relativePath" : "target/original-mcMMO.jar"
    }
  ],
  "building" : False,
  "description" : None,
  "displayName" : "#355",
  "duration" : 107352,
  "estimatedDuration" : 89124,
  "executor" : None,
  "fullDisplayName" : "mcMMO #355",
  "id" : "355",
  "inProgress" : False,
  "keepLog" : False,
  "number" : 355,
  "queueId" : 268,
  "result" : "SUCCESS",
  "timestamp" : 1787175241041,
  "url" : "https://popicraft.net/jenkins/job/mcMMO/355/",
  "builtOn" : "Builder",
  "changeSet" : {
    "_class" : "hudson.plugins.git.GitChangeSetList",
    "items" : [
      {
        "_class" : "hudson.plugins.git.GitChangeSet",
        "affectedPaths" : [
          "src/main/resources/locale/locale_zh_CN.properties"
        ],
        "commitId" : "787246ec22f08c88da40e298d5d73e76626e027c",
        "timestamp" : 1784126393000,
        "author" : {
          "absoluteUrl" : "https://popicraft.net/jenkins/user/github",
          "fullName" : "github"
        },
        "authorEmail" : "noreply@github.com",
        "comment" : "Update locale_zh_CN.properties\u000a\u000a- Sync with latest translation keys\u000a- Reorder keys to match en_US file\u000a- Add and revise some translation entries\u000a",
        "date" : "2026-07-15 22:39:53 +0800",
        "id" : "787246ec22f08c88da40e298d5d73e76626e027c",
        "msg" : "Update locale_zh_CN.properties",
        "paths" : [
          {
            "editType" : "edit",
            "file" : "src/main/resources/locale/locale_zh_CN.properties"
          }
        ]
      },
      {
        "_class" : "hudson.plugins.git.GitChangeSet",
        "affectedPaths" : [
          "src/test/java/com/gmail/nossr50/datatypes/player/McMMOPlayerBankedXpConversionTest.java",
          "src/test/java/com/gmail/nossr50/util/experience/FormulaManagerTest.java",
          "src/main/java/com/gmail/nossr50/util/experience/FormulaManager.java",
          "Changelog.txt"
        ],
        "commitId" : "48a049b8d9e8bff5bdd4e71db0df44eab1fdfe61",
        "timestamp" : 1787174265000,
        "author" : {
          "absoluteUrl" : "https://popicraft.net/jenkins/user/nossr50",
          "fullName" : "nossr50"
        },
        "authorEmail" : "nossr50@gmail.com",
        "comment" : "Keep next-level XP requirements positive and overflow-safe\u000a\u000aThe Standard-mode requirement sums ten Retro levels in an int, which\u000aoverflows at extreme skill levels and wraps to a garbage value, negative\u000aor far too small. Zero or negative experience.yml formula values could\u000aalso push a requirement to zero or below. A non-positive requirement\u000anever drains banked XP, so the level-up loop in checkXp gains levels\u000anonstop until the level cap, which defaults to Integer.MAX_VALUE.\u000a\u000aThe ten-level sum now accumulates in a long and saturates at\u000aInteger.MAX_VALUE, and every computed requirement is clamped to at\u000aleast 1. Results are unchanged for curves that never left int range.\u000a\u000aTests pin the default-curve requirement values for both scaling modes,\u000athe saturation and clamp behavior, and how banked XP converts into\u000alevels on the next gain after requirements shrink, grow, or hit the\u000alevel cap.\u000a",
        "date" : "2026-08-19 14:17:45 -0700",
        "id" : "48a049b8d9e8bff5bdd4e71db0df44eab1fdfe61",
        "msg" : "Keep next-level XP requirements positive and overflow-safe",
        "paths" : [
          {
            "editType" : "add",
            "file" : "src/test/java/com/gmail/nossr50/datatypes/player/McMMOPlayerBankedXpConversionTest.java"
          },
          {
            "editType" : "edit",
            "file" : "Changelog.txt"
          },
          {
            "editType" : "edit",
            "file" : "src/main/java/com/gmail/nossr50/util/experience/FormulaManager.java"
          },
          {
            "editType" : "add",
            "file" : "src/test/java/com/gmail/nossr50/util/experience/FormulaManagerTest.java"
          }
        ]
      },
      {
        "_class" : "hudson.plugins.git.GitChangeSet",
        "affectedPaths" : [
          "src/main/java/com/gmail/nossr50/api/ExperienceAPI.java",
          "Changelog.txt",
          "src/test/java/com/gmail/nossr50/api/ExperienceAPIOfflinePersistenceTest.java"
        ],
        "commitId" : "a3b0c3518c07cf9cc686852439d785498b10af36",
        "timestamp" : 1787174265000,
        "author" : {
          "absoluteUrl" : "https://popicraft.net/jenkins/user/nossr50",
          "fullName" : "nossr50"
        },
        "authorEmail" : "nossr50@gmail.com",
        "comment" : "Persist offline ExperienceAPI edits\u000a\u000asetXPOffline, setLevelOffline, and removeXPOffline mutated a profile\u000afreshly loaded from the database and let it go out of scope without\u000asaving, so the calls were silent no-ops. They now schedule an async\u000asave like their addOfflineXP and addLevelOffline counterparts.\u000a",
        "date" : "2026-08-19 14:17:45 -0700",
        "id" : "a3b0c3518c07cf9cc686852439d785498b10af36",
        "msg" : "Persist offline ExperienceAPI edits",
        "paths" : [
          {
            "editType" : "edit",
            "file" : "src/main/java/com/gmail/nossr50/api/ExperienceAPI.java"
          },
          {
            "editType" : "add",
            "file" : "src/test/java/com/gmail/nossr50/api/ExperienceAPIOfflinePersistenceTest.java"
          },
          {
            "editType" : "edit",
            "file" : "Changelog.txt"
          }
        ]
      },
      {
        "_class" : "hudson.plugins.git.GitChangeSet",
        "affectedPaths" : [
          "src/main/java/com/gmail/nossr50/util/EventUtils.java",
          "Changelog.txt",
          "src/test/java/com/gmail/nossr50/util/EventUtilsTest.java"
        ],
        "commitId" : "f15e53e9f179250083b3c1f9897b8c6a70afa843",
        "timestamp" : 1787174265000,
        "author" : {
          "absoluteUrl" : "https://popicraft.net/jenkins/user/nossr50",
          "fullName" : "nossr50"
        },
        "authorEmail" : "nossr50@gmail.com",
        "comment" : "Keep leftover banked XP when a level change event is cancelled\u000a\u000aThe cancel rollback restored the skill level with modifySkill, which\u000azeroes banked XP, then re-added only the XP the reverted levels had\u000aconsumed; any XP banked beyond those levels was destroyed. The rollback\u000anow captures the leftover before the level restore and returns it\u000atogether with the consumed XP. Command paths like /mmoedit and\u000a/skillreset are unaffected because they zero XP before firing the\u000aevent, leaving no leftover to capture.\u000a",
        "date" : "2026-08-19 14:17:45 -0700",
        "id" : "f15e53e9f179250083b3c1f9897b8c6a70afa843",
        "msg" : "Keep leftover banked XP when a level change event is cancelled",
        "paths" : [
          {
            "editType" : "edit",
            "file" : "src/test/java/com/gmail/nossr50/util/EventUtilsTest.java"
          },
          {
            "editType" : "edit",
            "file" : "Changelog.txt"
          },
          {
            "editType" : "edit",
            "file" : "src/main/java/com/gmail/nossr50/util/EventUtils.java"
          }
        ]
      },
      {
        "_class" : "hudson.plugins.git.GitChangeSet",
        "affectedPaths" : [
          "src/test/java/com/gmail/nossr50/util/skills/SkillToolsTest.java",
          "src/main/java/com/gmail/nossr50/util/skills/SkillTools.java"
        ],
        "commitId" : "c41f2e4ae2984d2b4aef2d42812eebbf89d2c240",
        "timestamp" : 1787174265000,
        "author" : {
          "absoluteUrl" : "https://popicraft.net/jenkins/user/nossr50",
          "fullName" : "nossr50"
        },
        "authorEmail" : "nossr50@gmail.com",
        "comment" : "Swap the skill name match cache wholesale on locale reloads\u000a\u000amatchSkill cleared its cache map in place when the locale generation\u000achanged, so a lookup racing a locale reload could insert a match\u000acomputed against the old locale after the clear, and that stale entry\u000awould survive until the next reload. The cache now lives in a record\u000astamped with the generation it was built for and is replaced wholesale\u000aon a generation change; an in-flight lookup writes into its own\u000ageneration map, which the swap makes unreachable.\u000a\u000aTests pin the cache contract: hits are served without re-reading the\u000alocale until the generation changes, a generation change drops every\u000acached match, and failed lookups are never cached.\u000a",
        "date" : "2026-08-19 14:17:45 -0700",
        "id" : "c41f2e4ae2984d2b4aef2d42812eebbf89d2c240",
        "msg" : "Swap the skill name match cache wholesale on locale reloads",
        "paths" : [
          {
            "editType" : "edit",
            "file" : "src/test/java/com/gmail/nossr50/util/skills/SkillToolsTest.java"
          },
          {
            "editType" : "edit",
            "file" : "src/main/java/com/gmail/nossr50/util/skills/SkillTools.java"
          }
        ]
      },
      {
        "_class" : "hudson.plugins.git.GitChangeSet",
        "affectedPaths" : [
          "src/test/java/com/gmail/nossr50/locale/LocaleLoaderSnapshotTest.java",
          "Changelog.txt",
          "src/main/java/com/gmail/nossr50/locale/LocaleLoader.java"
        ],
        "commitId" : "10f6bd31ed29214ea8b0ca81919d4dcd7e7dc83d",
        "timestamp" : 1787174265000,
        "author" : {
          "absoluteUrl" : "https://popicraft.net/jenkins/user/nossr50",
          "fullName" : "nossr50"
        },
        "authorEmail" : "nossr50@gmail.com",
        "comment" : "Publish locale bundles as an immutable snapshot\u000a\u000aThe bundles and their string cache were separate static fields mutated\u000ain place by reloadLocale, so a reader on another thread could pair\u000abundles from one locale with cached strings from the other, observe the\u000ahalf-cleared state and throw, or race the override-file copy when two\u000areloads overlapped. The bundles and cache now live in one immutable\u000asnapshot behind a volatile field; a reload builds the new snapshot\u000afully and publishes it with a single write, and loads are serialized\u000aunder a lock. The locale generation bumps after the snapshot is\u000apublished so generation-keyed consumers rebuild from the new strings.\u000a",
        "date" : "2026-08-19 14:17:45 -0700",
        "id" : "10f6bd31ed29214ea8b0ca81919d4dcd7e7dc83d",
        "msg" : "Publish locale bundles as an immutable snapshot",
        "paths" : [
          {
            "editType" : "add",
            "file" : "src/test/java/com/gmail/nossr50/locale/LocaleLoaderSnapshotTest.java"
          },
          {
            "editType" : "edit",
            "file" : "Changelog.txt"
          },
          {
            "editType" : "edit",
            "file" : "src/main/java/com/gmail/nossr50/locale/LocaleLoader.java"
          }
        ]
      },
      {
        "_class" : "hudson.plugins.git.GitChangeSet",
        "affectedPaths" : [
          "Changelog.txt"
        ],
        "commitId" : "8d1c1442f7f8e448a1311bb5395b35def1e1b953",
        "timestamp" : 1787174265000,
        "author" : {
          "absoluteUrl" : "https://popicraft.net/jenkins/user/nossr50",
          "fullName" : "nossr50"
        },
        "authorEmail" : "nossr50@gmail.com",
        "comment" : "Update changelog\u000a",
        "date" : "2026-08-19 14:17:45 -0700",
        "id" : "8d1c1442f7f8e448a1311bb5395b35def1e1b953",
        "msg" : "Update changelog",
        "paths" : [
          {
            "editType" : "edit",
            "file" : "Changelog.txt"
          }
        ]
      }
    ],
    "kind" : "git"
  },
  "culprits" : [
    {
      "absoluteUrl" : "https://popicraft.net/jenkins/user/nossr50",
      "fullName" : "nossr50"
    },
    {
      "absoluteUrl" : "https://popicraft.net/jenkins/user/github",
      "fullName" : "github"
    }
  ]
}