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

Downloads and source

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

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:

  1. For KubeJS startup registration crashes, check that registerControllers, registerPorts, and registerExtraBlocks use bare ids in event.create(...). mm:mm:<id> means a namespaced id was passed where a bare id was expected.

  2. Structure file path is data/<namespace>/mm/structures/ and world/datapack is loaded.
  3. controllerIds contains the controller id, not a wrong block id.
  4. Every non-space layout character has a key entry.
  5. Air is a space in the layout string; do not define a fake air key.
  6. Controller block in the world matches the controller key entry and origin assumptions.
  7. port/portType entries use correct input: true for input variants and input: false for output variants.
  8. tierRank satisfies minTier/maxTier when using portType tier matching.
  9. Blockstate properties exactly match valid placed block properties and account for rotation.

Recipe visible but not running:

  1. structureId exactly matches the structure id generated from datapack path.
  2. Structure is currently formed at the controller.
  3. Inputs exist in input ports and outputs have room in output ports.
  4. Conditions pass: dimension, biome, time, weather, height, redstone, and/or tier.
  5. Unit amounts are correct: items count, fluid mB, energy FE, chemical amount, mana, air/pressure, Create speed.
  6. per_tick: true means the port must satisfy the requirement every tick, not just once.
  7. 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