Compact Reference
Dense one-page reference for Masterful Machinery on Minecraft 1.20.1 Forge. Use this when you need the whole mental model, file layout, JSON shapes, KubeJS event names, port ids, recipe entry forms, and common debugging checks in one place.
Identity and scope
- Mod id:
mm. - Target in the provided source: Minecraft
1.20.1, Forge. - Masterful Machinery lets pack authors define generated multiblock machine controllers, ports, extra blocks, structures, and process recipes without writing a custom Java mod per machine.
- Generated blocks are config-driven. Structures and recipes are datapack-driven. KubeJS can register the same concepts through events.
- Main concepts: controller = machine brain; port = input/output storage or interaction block; extra block = generated decorative/helper block; structure = multiblock layout; process recipe = work performed by a formed structure.
Downloads and source
- Official CurseForge: https://www.curseforge.com/minecraft/mc-mods/masterful-machinery-fork
- Latest third-party release / JAR: https://github.com/FrozenGalaxy/MasterfulMachinery/releases/latest
- Third-party source and build workflow: https://github.com/FrozenGalaxy/MasterfulMachinery
- Original upstream source: https://github.com/BOLTMAGIC/MasterfulMachinery
File layout and ids
.minecraft/config/mm/
├─ controllers/<id>.json # generated controllers
├─ ports/<id>.json # generated input/output port variants
├─ extras/<id>.json # generated extra blocks
└─ working_effects.json # optional per-controller sound/particle overrides
<datapack>/pack.mcmeta
<datapack>/data/<namespace>/mm/structures/<path>.json # structure id <namespace>:<path>
<datapack>/data/<namespace>/mm/processes/<path>.json # recipe id <namespace>:<path>
<datapack>/data/<namespace>/mm_builder_structures/<path>.nbt # Structure Builder gallery
The builder also recognizes legacy mbtool_structures/ and spatial_structures/ NBT paths. working_effects.json lives at config/mm/working_effects.json, beside the generated-definition folders.
Example ids:
config/mm/controllers/coke_oven.json -> controller id usually mm:coke_oven
data/mypack/mm/structures/coke_oven.json -> structure id mypack:coke_oven
data/mypack/mm/processes/coke_oven/charcoal.json -> process id mypack:coke_oven/charcoal
Generated controller and port block registry names are normally based on the base id plus generated suffixes, for example a controller like coke_oven becomes a controller block such as mm:coke_oven. Port JSON creates input and output variants; structures can match a specific generated port or match by port type.
Common config: config/mm-common.toml
asyncValidation = true # experimental async validation toggle
structureValidationRate = 10 # controller structure check interval; 20 = one second
debugTool = true # enables Debug Tool behavior
splitRecipesJei = true # split JEI categories by structure
portsAutoExtractByDefault = false # default autoPush/auto-extract fallback
portAutoIOInterval = 10 # interval for per-side port transfers, 1..200 ticks
assemblyBlocksPerTick = 2 # controller Assemble placement speed, 1..64
parallelProcessingDefault = false # global recipe parallel default
maxParallelRecipes = 5 # global parallel cap, range 1..100
showJeiMaxParallel = true # show parallel info in JEI structure view
[network_link] # optional AE2 machine link settings
outputInterval = 20 # ticks between output transfers, 1..1200
opBypass = true # operators can open/break linked machines
sendContentsOnRemove = true # send linked port contents to ME when removed
controllerSearchRadius = 16 # controller lookup range, 4..64
[tool] # Structure Builder server settings
energyCapacity = 1000000
energyPerPlacedBlock = 50
energyPerDismantledBlock = 25
energyReceiveRate = 10000
buildRange = 64 # 5..128 blocks; loaded chunks only
buildBlocksPerTick = 2 # sequential/layer speed, 1..1024
buildMode = "SEQUENTIAL" # SEQUENTIAL, LAYER_BY_LAYER, INSTANT
[preview_features]
previewBlueprintScreen = false # experimental blueprint preview UI
[flex]
costScaling = "LINEAR" # LINEAR, ONCE, TABLE
costTable = ["1=1"] # kinds=multiplier steps used by TABLE
Practical defaults: keep portsAutoExtractByDefault = false, raise structureValidationRate for large packs, keep maxParallelRecipes modest, and disable debugTool on public servers if debug dumps should not be available to players. Operators can edit server settings in-game from the Structure Builder config tab.
Port auto I/O side settings are saved per port. autoPush or portsAutoExtractByDefault sets an output port’s initial auto-push state; input pull sides are configured in the port GUI. Client-only controller/port visual settings and per-machine sound mute do not belong to mm-common.toml.
Controller JSON
Path: config/mm/controllers/<file>.json.
{
"type": "mm:machine",
"id": "coke_oven",
"name": "Coke Oven",
"parallelProcessingDefault": false,
"maxParallelRecipes": -1,
"recipeSelectionMode": "default"
}
Fields: type required, currently mm:machine; id required base id; name required display name; parallelProcessingDefault optional boolean default false; controller maxParallelRecipes optional integer, -1 means unspecified/fallback, non-negative values clamp up to 100; recipeSelectionMode optional string: default, avoid_same_recipe, or round_robin_input_item; optional workingSound, workingSoundInterval (default 40 ticks), and workingParticle for working effects. The global common config value is separate and is ranged 1..100.
Controller screen controls include redstone mode (IGNORED, WITH_REDSTONE, WITHOUT_REDSTONE), recipe selection, parallel mode, rename, sound mute, and Assemble. Assemble chooses a structure and port tiers, then places blocks from player inventory at assemblyBlocksPerTick (default 2; 1..64). A comparator reports 0 when unformed/idle and 1..15 based on the furthest-running recipe’s progress. The GUI also shows state, progress, tier, recipe inputs/outputs, and a port contents page.
KubeJS startup equivalent:
MMEvents.registerControllers(event => {
event.create('coke_oven')
.type('mm:machine')
.name('Coke Oven')
.parallelProcessingDefault(false)
.maxParallelRecipes(-1)
.recipeSelectionMode('default')
})
Extra block JSON
Path: config/mm/extras/<file>.json.
{ "id": "basic_circuit", "name": "Basic Circuit", "type": "mm:circuit" }
Supported extra block types: mm:circuit, mm:gearbox, mm:vent.
KubeJS startup equivalent:
MMEvents.registerExtraBlocks(event => {
event.create('basic_circuit').type('mm:circuit').name('Basic Circuit')
})
Port JSON overview
Path: config/mm/ports/<file>.json. A port definition expands into input and output variants.
{
"id": "item_port",
"controllerIds": ["mm:coke_oven"],
"name": "Item Port",
"type": "mm:item",
"config": { "rows": 1, "columns": 3, "autoPush": false, "slotCapacity": 64, "tierRank": 1 }
}
Common fields: id required; controllerIds required string or array of controller ids; name required; type required port type id; config required type-specific object. autoPush causes compatible output ports to push into adjacent storage. If omitted, base auto-push behavior can fall back to portsAutoExtractByDefault. tierRank is used by structure portType matching with minTier/maxTier.
KubeJS startup equivalent:
MMEvents.registerPorts(event => {
event.create('item_port')
.name('Item Port')
.controllerId('mm:coke_oven')
.config('mm:item', c => {
c.rows(1).columns(3).autoPush(false).slotCapacity(64).tierRank(1)
})
})
Port types, config fields, ingredient fields
| Port type | Required mod | Config fields | Recipe ingredient fields |
|---|---|---|---|
mm:item |
base | rows, columns, optional autoPush, slotCapacity, tierRank |
item or tag, count, optional nbt, nbt_snbt, nbt_match |
mm:fluid |
base | rows, columns, slotCapacity, optional autoPush, tierRank |
fluid, amount in mB |
mm:energy |
base | capacity, maxReceive, maxExtract, optional autoPush, tierRank |
amount in FE |
mm:create/kinetic |
Create | stress |
speed |
mm:botania/mana |
Botania | capacity |
mana |
mm:pneumaticcraft/air |
PneumaticCraft | volume, danger, critical |
air, optional pressure |
mm:mekanism/gas |
Mekanism | capacity |
gas, amount |
mm:mekanism/slurry |
Mekanism | capacity |
slurry, amount |
mm:mekanism/pigment |
Mekanism | capacity |
pigment, amount |
mm:mekanism/infuse |
Mekanism | capacity |
infuse, amount |
Port config examples:
{ "type": "mm:item", "config": { "rows": 3, "columns": 3, "autoPush": true, "slotCapacity": 64, "tierRank": 1 } }
{ "type": "mm:fluid", "config": { "rows": 2, "columns": 2, "slotCapacity": 1000, "autoPush": false, "tierRank": 1 } }
{ "type": "mm:energy", "config": { "capacity": 100000, "maxReceive": 1000, "maxExtract": 1000, "autoPush": false, "tierRank": 1 } }
{ "type": "mm:create/kinetic", "config": { "stress": 4 } }
{ "type": "mm:botania/mana", "config": { "capacity": 10000 } }
{ "type": "mm:pneumaticcraft/air", "config": { "volume": 20000, "danger": 11.0, "critical": 12.0 } }
{ "type": "mm:mekanism/gas", "config": { "capacity": 1000 } }
Item slotCapacity: optional; 0 means normal stack behavior; values cap at 16384 per slot. Fluid units are millibuckets. Energy units are FE. Create kinetic input reads speed and contributes stress impact; output provides speed and stress capacity. PneumaticCraft air uses volume and pressure thresholds. Ports can pull/push on selected machine-relative sides; fluid and Mekanism chemical ports also have lock/dump controls. An AE2 Storage Bus can expose an item port to an ME network.
The Input Gateway (mm:input_gateway) forwards items, fluids, FE, and Mekanism gas/infusion/pigment/slurry to machine inputs without storing them. It can replace casing/glass in a structure or sit next to an input port; its contents view is read-only.
Ingredient object examples
{ "type": "mm:item", "item": "minecraft:iron_ingot", "count": 1 }
{ "type": "mm:item", "tag": "forge:ingots/iron", "count": 2 }
{ "type": "mm:fluid", "fluid": "minecraft:water", "amount": 1000 }
{ "type": "mm:energy", "amount": 5000 }
{ "type": "mm:create/kinetic", "speed": 32 }
{ "type": "mm:botania/mana", "mana": 250 }
{ "type": "mm:pneumaticcraft/air", "air": 1000, "pressure": 4.5 }
{ "type": "mm:mekanism/gas", "gas": "mekanism:hydrogen", "amount": 100 }
{ "type": "mm:mekanism/slurry", "slurry": "mekanism:dirty_iron", "amount": 100 }
{ "type": "mm:mekanism/pigment", "pigment": "mekanism:red", "amount": 100 }
{ "type": "mm:mekanism/infuse", "infuse": "mekanism:carbon", "amount": 100 }
Item NBT ingredient forms:
{ "type": "mm:item", "item": "minecraft:potion", "count": 1, "nbt": { "Potion": "minecraft:water" }, "nbt_match": "weak" }
{ "type": "mm:item", "item": "minecraft:potion", "count": 1, "nbt_snbt": "{Potion:\"minecraft:water\"}", "nbt_match": "strong" }
weak/default matching treats the supplied NBT as a subset. strong requires stricter matching.
Structure JSON
Path: data/<namespace>/mm/structures/<path>.json.
{
"name": "Tiny Furnace",
"controllerIds": "mm:tiny_furnace",
"portsAnywhere": false,
"maxParallelRecipes": -1,
"layout": [
[
"III",
"ACA",
"OOO"
]
],
"key": {
"A": { "block": "minecraft:bricks" },
"C": { "block": "mm:tiny_furnace" },
"I": { "portType": "mm:item", "input": true },
"O": { "portType": "mm:item", "input": false }
}
}
Fields: name required; controllerIds required string or array; layout required array of layers, each layer is an array of strings; key required map from layout character to requirement object; portsAnywhere optional boolean; maxParallelRecipes optional integer where -1 means no structure override; stateLists optional map of named blockstate lists.
Layout notes: each character in layout must have a key entry unless it is whitespace. Air/empty space is represented by a space in the layout. Structures are checked from the controller position and support rotations. Directional blockstate properties rotate with the structure; verify rotated structures in-game if using directional properties. Old examples usually use C as the controller marker, but it is just a normal key entry unless a builder/tool treats it specially.
Structure key requirement forms:
{ "block": "minecraft:bricks" }
{ "tag": "minecraft:wool" }
{ "port": "mm:item_port", "input": true }
{ "portType": "mm:item", "input": false, "minTier": 1, "maxTier": 3 }
{ "stateList": "casings", "nameTranslationKey": "block.mypack.machine_casing" }
{ "block": "minecraft:oak_log", "properties": [{ "property": "axis", "value": "x" }] }
Specific port matching uses the base port id plus input, for example { "port": "mm:item_port", "input": true }; do not normally write { "port": "mm:item_port_input" }. port requires a specific port id. portType allows any port of a type. input may be omitted to accept input or output. minTier defaults to 1; maxTier defaults to effectively unlimited; the matched port’s config tierRank is used. portsAnywhere: true lets port/portType pieces match any candidate port positions, still requiring unique matched positions.
KubeJS server equivalent:
MMEvents.createStructures(event => {
event.create('tiny:tiny_furnace')
.name('Tiny Furnace')
.controllerId('mm:tiny_furnace')
.maxParallelRecipes(-1)
.layout(l => {
l.layer(['III', 'ACA', 'OOO'])
l.key('A', { block: 'minecraft:bricks' })
l.key('C', { block: 'mm:tiny_furnace' })
l.key('I', { portType: 'mm:item', input: true })
l.key('O', { portType: 'mm:item', input: false })
l.portsAnywhere(false)
})
})
Process recipe JSON
Path: data/<namespace>/mm/processes/<path>.json.
The mm/processes resource loader identifies these as MM recipes. Do not add a root "type": "mm:process" field; type is used on input/output entries and conditions.
{
"structureId": "tiny:tiny_furnace",
"ticks": 100,
"parallelProcessing": false,
"inputs": [
{
"type": "mm:input/consume",
"ingredient": { "type": "mm:item", "item": "minecraft:cobblestone", "count": 1 },
"chance": 1.0,
"per_tick": false
}
],
"outputs": [
{
"type": "mm:output/simple",
"ingredient": { "type": "mm:item", "item": "minecraft:stone", "count": 1 },
"chance": 1.0,
"per_tick": false
}
],
"conditions": []
}
Fields: structureId required; ticks required integer, 20 ticks = 1 second; inputs required array; outputs required array; conditions optional array; parallelProcessing optional boolean override; structureIds optional additional structures; mode is fixed by default or flex; flex_cost optionally overrides flex cost scaling. A recipe can run only when the controller has a formed structure matching one of its IDs, all inputs are available, output capacity is available, and all conditions pass. Output space is checked before inputs are consumed. A completed recipe waits without losing progress if its output becomes blocked.
Input entry:
{ "type": "mm:input/consume", "ingredient": { "type": "mm:item", "item": "minecraft:iron_ingot", "count": 1 }, "chance": 1.0, "per_tick": false }
Output entry:
{ "type": "mm:output/simple", "ingredient": { "type": "mm:item", "item": "minecraft:iron_nugget", "count": 9 }, "chance": 1.0, "per_tick": false }
Weighted output chooses one option according to its positive integer weight; { "weight": 1, "nothing": true } is a valid no-output option:
{
"type": "mm:output/weighted",
"options": [
{ "weight": 3, "ingredient": { "type": "mm:item", "item": "minecraft:iron_nugget", "count": 4 } },
{ "weight": 1, "ingredient": { "type": "mm:item", "item": "minecraft:gold_nugget", "count": 1 } },
{ "weight": 1, "nothing": true }
]
}
mm:input/count counts matching input items without consuming them; scale_by on simple outputs multiplies item, fluid, energy, or Mekanism chemical amounts by that count. Flex mode uses mm:input/flex, mm:input/flex_match, and mm:output/flex to process multiple matching item kinds in one recipe. See Flex Recipes and Counted Inputs for the full schema and KubeJS API.
chance defaults to 1.0 and is a probability from 0.0 to 1.0. per_tick defaults to false; if true, the ingredient is consumed or produced during recipe ticks instead of only at completion. For item outputs, shorthand fields directly on the output entry may be accepted by the source, but the explicit ingredient object is recommended.
Conditions:
{ "type": "mm:dimension", "dimension": "minecraft:the_nether" }
{ "type": "mm:biome", "biome": "#minecraft:is_ocean" }
{ "type": "mm:time", "time": "night" }
{ "type": "mm:weather", "weather": "clear" }
{ "type": "mm:height", "minY": 0, "maxY": 64 }
{ "type": "mm:redstone", "redstone": "powered" }
{ "type": "mm:tier", "minTier": 2, "maxTier": 4 }
Dimension, biome, time (day/night), weather (clear/rain/thunder), height, redstone (powered/unpowered), and structure tier conditions are supported. All conditions must pass. Height checks controller Y; biome values can be IDs or #-prefixed tags.
KubeJS server equivalent:
MMEvents.createProcesses(event => {
event.create('tiny:smelt_stone')
.structureId('tiny:tiny_furnace')
.ticks(100)
.parallelProcessing(false)
.input({ type: 'mm:input/consume', ingredient: { type: 'mm:item', item: 'minecraft:cobblestone', count: 1 } })
.output({ type: 'mm:output/simple', ingredient: { type: 'mm:item', item: 'minecraft:stone', count: 1 } })
})
Complete minimal datapack/config example
Controller: config/mm/controllers/tiny_furnace.json
{ "type": "mm:machine", "id": "tiny_furnace", "name": "Tiny Furnace" }
Port: config/mm/ports/tiny_item_port.json
{
"id": "tiny_item_port",
"controllerIds": "mm:tiny_furnace",
"name": "Tiny Item Port",
"type": "mm:item",
"config": { "rows": 1, "columns": 3, "autoPush": false }
}
Structure: datapacks/tiny_furnace/data/tiny/mm/structures/tiny_furnace.json
{
"name": "Tiny Furnace",
"controllerIds": "mm:tiny_furnace",
"layout": [["III", "ACA", "OOO"]],
"key": {
"A": { "block": "minecraft:bricks" },
"C": { "block": "mm:tiny_furnace" },
"I": { "portType": "mm:item", "input": true },
"O": { "portType": "mm:item", "input": false }
}
}
Recipe: datapacks/tiny_furnace/data/tiny/mm/processes/smelt_stone.json
{
"structureId": "tiny:tiny_furnace",
"ticks": 100,
"inputs": [{ "type": "mm:input/consume", "ingredient": { "type": "mm:item", "item": "minecraft:cobblestone", "count": 1 } }],
"outputs": [{ "type": "mm:output/simple", "ingredient": { "type": "mm:item", "item": "minecraft:stone", "count": 1 } }]
}
KubeJS id rules
Startup-generated config-backed content uses bare MM ids in event.create(...); datapack/server content uses full resource locations.
| Context | Event | event.create(...) expects |
Example | Generated / referenced id |
|---|---|---|---|---|
| Controller | MMEvents.registerControllers |
bare MM base id/path | event.create('coke_oven') |
mm:coke_oven |
| Extra block | MMEvents.registerExtraBlocks |
bare MM base id/path | event.create('basic_circuit') |
mm:basic_circuit |
| Port | MMEvents.registerPorts |
bare MM base id/path | event.create('item_port') |
mm:item_port_input, mm:item_port_output |
| Structure | MMEvents.createStructures |
full resource location | event.create('expert:coke_oven') |
expert:coke_oven |
| Process recipe | MMEvents.createProcesses |
full resource location | event.create('expert:coke_oven/charcoal') |
expert:coke_oven/charcoal |
Important: MMEvents.registerControllers, MMEvents.registerExtraBlocks, and MMEvents.registerPorts take a bare MM id/path in event.create(...), such as coke_oven, not mm:coke_oven. Passing mm:coke_oven there can create an invalid double namespace like mm:mm:coke_oven.
KubeJS event names and placement
// startup_scripts
MMEvents.registerControllers(event => {})
MMEvents.registerPorts(event => {})
MMEvents.registerExtraBlocks(event => {})
// server_scripts
MMEvents.createStructures(event => {})
MMEvents.createProcesses(event => {})
MMEvents.builderStructures(event => {})
MMEvents.recipeStarted(event => {})
MMEvents.recipeFinished(event => {})
Controllers, ports, and extra blocks are startup content. Structures, recipes, builder gallery edits, and recipe lifecycle events are server content. recipeStarted can be filtered by recipe id and cancelled; recipeFinished can be filtered by recipe id. Both event objects expose recipe/controller/structure ids, level, position, and controller/block references.
Recipe selection modes: default uses normal scan order; avoid_same_recipe avoids immediately repeating the exact recipe when possible; round_robin_input_item prefers the least-recently-used consumed item input among runnable recipes, useful for sieve-like machines.
Common builder method map:
controller: create(id), type(id), name(text), parallelProcessingDefault(bool), maxParallelRecipes(int), recipeSelectionMode(string), workingSound(id), workingSoundInterval(ticks), workingParticle(id)
port: create(id), name(text), controllerId(id) repeatable, config(portType, builder => ...)
extra block: create(id), type(id), name(text)
structure: create(id), name(text), controllerId(id) repeatable, maxParallelRecipes(int), layout(builder => ...)
structure layout: layer([rows...]), key(char, object), portsAnywhere(bool)
process: create(id), structureId(id), structureIds(...ids), ticks(int), parallelProcessing(bool), input(object), output(object), dimension(id), biome(id/tag), time(day/night), weather(value), minY(y), maxY(y), redstone(value), minTier(t), maxTier(t)
builderStructures: remove(id), removeNamespace(namespace), add(id, nbtResource)
port config builders: item rows/columns/autoPush/slotCapacity (max 16384)/tierRank; fluid rows/columns/slotCapacity/autoPush/tierRank; energy capacity/maxReceive/maxExtract/autoPush/tierRank; kinetic stress; mana capacity; air volume/danger/critical; Mekanism chemical capacity/amount depending builder.
Items and commands
- Blueprint: stores a structure id in NBT and is used by structure-view/preview features. Blueprint item names include the structure name when available, for example
Coke Oven - Blueprint. - Debug Tool: right-click a controller to generate a debug dump when
debugTool = true. - Priority Setter: right-click in air cycles priority
0..10; sneak-right-click air resets to0; sneak-right-click output port applies priority; input ports reject priority. Recipe output distribution tries higher-priority ports first for items, fluids, energy, and Mekanism chemicals. Auto-pushing output ports serve adjacent MM input ports by priority too. - Multiblock Saver: marks two corners and captures a structure region to JSON/JS-style output; source has safety checks including a 50,000-block capture limit and a limit on unique key characters.
- Structure Builder (
mm:structure_builder): Shift+use opens gallery/settings; right-click previews/builds; G anchors/releases a remote target; V (rebindable) or Shift+right-click twice dismantles. It uses FE and blocks, can bind to AE2 for inventory/auto-crafting, and also loads supported other-mod NBT structures. See Structure Builder. - Input Gateway (
mm:input_gateway): forwards supported resources to machine inputs; see Ports. - Machine Network Linker / Machine Wrench: link a machine to AE2 and inspect or unlink ownership/network details.
- Command:
/mm reform, permission level 2. Scans loaded chunks around players, finds controller block entities, checks known structures, and reforms matching controllers. Useful after structure edits or migrations.
Known-good KubeJS smoke test
MMEvents.registerControllers(event => {
event.create('test_machine')
.type('mm:machine')
.name('Test Machine')
})
MMEvents.registerExtraBlocks(event => {
event.create('test_casing')
.type('mm:gearbox')
.name('Test Casing')
})
MMEvents.registerPorts(event => {
event.create('test_item_port')
.name('Test Item Port')
.controllerId('mm:test_machine')
.config('mm:item', c => {
c.rows(1).columns(1).slotCapacity(64).tierRank(1)
})
})
MMEvents.createStructures(event => {
event.create('expert:test_machine')
.name('Test Machine')
.controllerId('mm:test_machine')
.layout(l => {
l.layer(['ICI'])
l.key('C', { block: 'mm:test_machine' })
l.key('I', { port: 'mm:test_item_port', input: true })
})
})
Expected registry ids after restart:
mm:test_machine
mm:test_casing
mm:test_item_port_input
mm:test_item_port_output
Not expected:
mm:test_machine_controller
mm:mm:test_machine
mm:mm:test_item_port_input
Troubleshooting checklist
Structure not forming:
-
For KubeJS startup registration crashes, check that
registerControllers,registerPorts, andregisterExtraBlocksuse bare ids inevent.create(...).mm:mm:<id>means a namespaced id was passed where a bare id was expected. - Structure file path is
data/<namespace>/mm/structures/and world/datapack is loaded. controllerIdscontains the controller id, not a wrong block id.- Every non-space layout character has a
keyentry. - Air is a space in the layout string; do not define a fake air key.
- Controller block in the world matches the controller key entry and origin assumptions.
port/portTypeentries use correctinput: truefor input variants andinput: falsefor output variants.tierRanksatisfiesminTier/maxTierwhen usingportTypetier matching.- Blockstate
propertiesexactly match valid placed block properties and account for rotation.
Recipe visible but not running:
structureIdexactly matches the structure id generated from datapack path.- Structure is currently formed at the controller.
- Inputs exist in input ports and outputs have room in output ports.
- Conditions pass: dimension, biome, time, weather, height, redstone, and/or tier.
- Unit amounts are correct: items count, fluid mB, energy FE, chemical amount, mana, air/pressure, Create speed.
per_tick: truemeans the port must satisfy the requirement every tick, not just once.- Parallel settings do not exceed global/controller/structure caps.
NBT mismatch: prefer nbt JSON object or nbt_snbt string, start with default/weak, use strong only for exactness.
Auto input/output does nothing: configure the port’s machine-relative sides in its GUI, confirm the neighbor supports the resource, and check portAutoIOInterval. For output initial state, check port autoPush or portsAutoExtractByDefault.
Recipe stalls at completion: check output port capacity and available space. Finished work remains pending until output space opens; it should not consume the inputs a second time.
Server cost too high: increase structureValidationRate, reduce maxParallelRecipes, avoid huge port inventories/tanks, avoid excessive per-tick recipes, and be cautious with experimental async validation.
After changing structures: reload/restart as needed and try /mm reform for loaded controllers.
Quick decision rules
- Define generated blocks in
config/mm; define world machine behavior in datapacks; use KubeJS only when script-driven registration is preferred. - Use
portTypein structures when any compatible port should work; useportwhen a specific port id is required. - Use
portsAnywherefor flexible port placement; leave it false when exact multiblock shape matters. - Put long-lived machine examples under datapacks; use KubeJS for pack logic, loops, generated variants, or integration with other scripted changes.
- Keep recipe JSON explicit: always put the actual resource object inside
ingredientfor clarity. - Keep id namespaces straight: config-generated controller/port ids usually use
mm:*; datapack structures/processes use the datapack namespace.