Define a Quality Chain
A quality tier is one file at data/<namespace>/mxt/quality/<path>.json, in the mxt:quality registry. A tier says three things: how it is drawn (color and friends); which tier sits above it (next); what the step up costs and requires (upgrade_costs / upgrade_condition); and which ladder it belongs to (quality). What it is called is not up to the definition: the name and description are always derived from the registered id, with the keys quality.mxt.<definition namespace>.<path> (the description adding .description), so translate those two keys in your language file to give the tier words.
The order of the tiers is written nowhere else. No table holds the ladder on its own: the runtime walks next once and gets a straight line.
| Field | Default | What it does |
|---|---|---|
color | none | Tints the name, written as #RRGGBB (an integer or an [r,g,b] float array is accepted too); always treated as opaque. |
value_multiplier | 1 | Multiplies the item's currency value. |
forging_modifier | 1 | Divides the extra steps forging reads. |
alchemy_modifier | 1 | Divides the brewing duration. |
condition | mxt:always | Whether an item of this tier can be used. |
next | none | The id of the tier above; the top tier leaves it out. |
upgrade_costs | [] | What the step up costs. |
upgrade_condition | mxt:always | What the step up requires. |
quality | none | The name of the ladder, written on the entry tier. |
Every field is optional. The three modifiers share one shape, {"description": …, "modifier": …}: modifier is a number or a formula string and defaults to 1, and description gets its own line in the item tooltip only when you write one — leaving it out does not stop the modifier from working. A modifier that is not a finite positive number is treated as 1.
condition is the same gate a binding uses: while it fails, the item cannot be used. The full field list is in Quality.
What You Are Building
| File | Purpose |
|---|---|
data/example/mxt/quality/common.json | The lowest tier, which is the entry of this ladder; the ladder's name goes here. |
data/example/mxt/quality/refined.json | The middle tier; the price and the modifier of the step up from common go here. |
data/example/mxt/quality/flawless.json | The top tier, with no next. |
The price is paid in example:qi, which already has a resource/qi.json in Define Aura and Realms; the item carrying the tiers is the KubeJS-registered kubejs:qi_pill from Create Items and Bind Actions with KubeJS.
Step 1 — Three Tiers
// data/example/mxt/quality/common.json
{
"quality": "example:pill",
"next": "example:refined"
}// data/example/mxt/quality/refined.json
{
"next": "example:flawless",
"upgrade_costs": [{"id": "example:qi", "amount": 20}],
"value_multiplier": {
"description": "mxt.quality.example.refined.value",
"modifier": 1.25
}
}// data/example/mxt/quality/flawless.json
{}nextpoints at the tier above, so walkingnextupwards readscommon→refined→flawless.flawlessis an empty object: the top tier leavesnextout and takes every modifier default. Its name and description are generated from the id like any other tier (mxt.quality.example.flawless), since the definition has neither field in the first place.{"id": …, "amount": …}insideupgrade_costsis the shorthand for amxt:resourcecost; the long form with atypeworks too.- A modifier sits on the tier it settles with:
refined'svalue_multipliermultiplies the currency value of its items by1.25, and its owndescriptiondecides whether that extra tooltip line is drawn.commonandflawlessdeclare no modifiers, so both behave as1.
Step 2 — The Ladder's Name Goes on the Entry Tier
The entry tier is the one nothing writes as its next. Of the three above, nothing points at common, so common is the entry. The ladder's name is the quality field, written on the entry tier, and it follows next to every tier of the ladder: here all three belong to example:pill.
The name is read from the entry tier alone. Move "quality": "example:pill" from common to refined and the entry tier common has no name — so the whole ladder has no name, and /quality chain prints that name as null. The copy on the middle tier only takes part in the "one tier receives two different names" check below. Nothing is reported: no check can tell that you meant to name the ladder.
A ladder's name is just an id, and writing it once per ladder is enough. Only two things about the name are reported:
- A tier whose own name differs from the one it inherits from above reports
names quality <written> inside <inherited>at load. - Two entry tiers claiming one name report
starts a second ladder named <name>, which another tier already names.
Step 3 — The Price Sits on the Tier You Climb Into
Going from common to refined reads refined's upgrade_costs and upgrade_condition: that cost is what "climbing into this tier" takes, so the 20 example:qi is written on refined, not on common.
On the source tier it never applies, and nothing is reported: common's upgrade_costs would mean "climbing into common itself", and no tier climbs into an entry tier. That cost is never collected, so the step is in practice free.
- A tier with no
upgrade_costsmakes the step into it free; an empty array is free too. - To make a tier the top, leave its
nextout. A tier that writesnextwithoutupgrade_costsstill has that step, at an empty price. - A tier with no
nextthat writesupgrade_costsor anupgrade_conditionother thanmxt:alwaysreportsdeclares upgrade_costs or upgrade_condition but no next tier. - Upgrading is atomic: when the condition fails or the cost cannot be paid, the stack does not move and no tier is written.
upgrade_condition decides whether the step may be walked, and is checked before anything is paid; condition decides whether an item of that tier can be used at all.
Step 4 — Put an Item on the Ladder
A stack takes the first tier it can get, in this order:
| Way | How to write it |
|---|---|
The mxt:quality component on the stack | /give @s kubejs:qi_pill[mxt:quality="example:common"]; /quality set, a successful upgrade, a Forge Table settlement and a talisman inscription write this one too. |
The quality the definition the stack carries declares | Whichever component on that item names a definition identity, read for the tier that definition writes itself. |
| The default_quality registry | When a definition's items matches the item, the quality that definition writes applies; a bare creative or /give item and the item-identity-free artifact / spirit_herb definitions take this route. |
Layer 2 answers for "several definitions of a type share one built-in item, so the item cannot say which tier applies": every technique manual defaults to mxt:technique_book (a technique_binding may name another carrier_item), every pill is mxt:pill, every furnace specification is the block item mxt:alchemy_furnace, the spirit-root stone is mxt:spirit_root and the physique stone is mxt:physique. The nine definitions declaring quality are technique, alchemy_furnace, alchemy_wall_material, spirit_root, physique, pill, formation, secret_realm and contract_type.
The component holds a whole quality object, so it decides both the tier and the ladder it belongs to: example:common puts that pill on example:pill. Binding tables declare no quality, so do not write a ladder into a binding. The forging record mxt:forging_result keeps only the blueprint id and the step counts and never a tier. This pill is an ordinary KubeJS-registered item with no component on it that names a definition, so only layers 1 and 3 can answer for it. When none of the three layers answers, the pill simply has no quality and no ladder's entry tier is supplied for it.
| Command | What it does |
|---|---|
/quality get [player] | Shows the tier of the item in the main hand; passing a player needs gamemaster permission. |
/quality set <targets> <quality> | Writes the override component onto the target's main-hand item; needs gamemaster permission. |
/quality clear <targets> | Removes the override component; needs gamemaster permission. |
/quality upgrade <targets> | Walks one step up the ladder; needs gamemaster permission. |
/quality chain <quality> | Draws the whole ladder, with the ladder's name first; needs no permission. |
The top-level alias /quality can be switched off in the Command Aliases tab of the server configuration; /mxt quality keeps working when it is off.
Step 5 — Load and Validate
Quality is a data pack registry, read once when the world loads, and /reload does not read it again. After editing the files, leave to the title screen and open the world again, or restart the server.
(load the world again)
/mxt registries validate → every problem in one pass
/mxt registries list → mxt:quality=3
/quality chain example:common → the whole ladder, named example:pill
/quality get → the tier of the item in your main hand/mxt registries validate reports every problem at once, and strings only the first 12 together when there are more. The ladder's own problems show up here: a cycle, a fork, two entries claiming one name. A next pointing at a tier the pack does not provide is not among them — that fails the whole data pack load.
Verify
- Open the world again and check that
/mxt registries validatereports nothing. /give @s kubejs:qi_pill[mxt:quality="example:common"], then/quality get: it showscommon./quality chain example:common: the name isexample:pilland all three tiers sit on that one line in order.- Holding the pill, run
/quality upgrade @s: with 20example:qion you it goes up torefined,/quality getfollows, and the tooltip gains thevalue_multiplierdescription line; with fewer than 20 it reports that the cost cannot be paid and the tier does not move. /quality set @s example:flawlessoverrides the tier to the top one, and/quality clear @sremoves the component again. The pill has no entry indefault_qualityand carries no component naming a definition, so after the clear/quality getreports that it currently has no quality.- Delete
refined'supgrade_costs, open the world again and upgrade once more: that step costs nothing.
Common Mistakes
| Symptom | Cause |
|---|---|
The ladder's name prints as null | The name was written on a middle tier, so the entry never receives it and the whole ladder is unnamed. Nothing is reported. |
| The upgrade never collects its cost | upgrade_costs was written on the source tier, so the cost means "climbing into itself", and nothing climbs into an entry tier. That step is in practice free. Nothing is reported. |
names quality <written> inside <inherited> | A tier's own name differs from the one inherited from above. |
starts a second ladder named <name>, which another tier already names | Two entry tiers claim one name. |
next <id> is not a quality | The pack never loads that far: a next pointing at a tier that does not exist makes the world refuse to load, so it is a failed load, not "that ladder is dropped". |
follows both <A> and <B> | One tier is written as the next of two tiers (a fork); reported on the tier being pointed at. Every line running into that fork point is left out of the index whole (the other entry's line included) — no winner is picked for you. |
the chain is cyclic at <id> | A cycle. A pure cycle whose tiers name nothing reports nothing at all — those tiers are simply on no ladder. |
declares upgrade_costs or upgrade_condition but no next tier | A tier has no next but writes upgrade_costs or an upgrade_condition other than mxt:always. |
cannot be reached from the start of its ladder: it is cyclic, points into another, or names a quality that is already taken | A tier names a ladder but cannot be reached from its entry. |
Writing a chain field does nothing | chain is not a field, so it is silently ignored as an unknown key; quality is the only source of a ladder's name. |
| Editing the files changes nothing | Data pack registries are read when the world loads, and /reload does not read them again. |
Next
- Quality — every field in full, plus colours, modifiers and the order quality resolves in.
- Create Items and Bind Actions with KubeJS — put your own items on this ladder.
- Define Aura and Realms — where the number an upgrade spends comes from.