Create: Computing AgesBrass Docs
Devices

Precision machines

The Coil Winder, Vacuum Chamber, Photolithography Machine, Ion Implanter, Pick-and-Place Machine and Crystal Puller: what they work on, and why they wait.

The precision machines of Computing Ages work like a Create Mechanical Press: they hang above a belt or a depot and bring their head down on the part under them, one step of a vacuum tube or a chip at a time. They hold what they work with (a spool of wire, a dopant, a photomask...) and need rotation, often fast. When a machine waits, Engineer's Goggles tell why: too slow, an ingredient missing... A computer reads the same thing, and can act on it: speed the line up, call for more wire, warn you.

Brass
local implanter = peripheral.find("ion_implanter")
if implanter.running() then
  print("working: " .. math.floor(implanter.progress() * 100) .. " %")
else
  print("waiting: " .. (implanter.problem() or "nothing to fix"))
end
Methods
machine.running()Whether the machine is working: from the moment its head starts down on a part until it is back up.
machine.progress()How far the current cycle is, from 0 (the head leaves the top) to 1 (it is back up), rounded to 0.001. 0 while the machine is idle. A faster shaft makes the cycle shorter.
machine.item()The part waiting under the machine, as an item table: the one on the belt or the depot two blocks below, or else the one lying on the ground right below. nil when nothing is there. count is the size of that stack.
machine.recipe()The recipe the machine would run on the part below:
machine.problem()What keeps the machine from working on the part below, as the goggles show it. nil when there is nothing below, or when the part can be worked on now.
machine.tier()The tier of the light source loaded in a Photolithography Machine: 1 for a UV Lamp, 2 for an Excimer Laser (DUV), 3 for an EUV Light Source. 0 when none is loaded, and always 0 on the four other machines.
puller.running()Whether a crystal is being pulled.
puller.progress()How far the crystal is pulled, from 0 (the seed touches the melt) to 1 (the boule is out), rounded to 0.001. The goggles show it as a percentage.
puller.max_speed()The speed limit of the puller, in RPM: the server config crystalPullerMaxSpeed, 32 by default. The pull is fastest at exactly this speed.
puller.problem()What keeps the puller from working:

Setting up

Five machines, one set of methods. Each machine has its own type, the one you look for; all five have the methods of this page, written precision_machine.* here.

typeblockwhat it holds (its slots)
coil_winderCoil Winder1: the spool of wire or foil
vacuum_chamberVacuum Chamber1: the deposition target
lithography_machinePhotolithography Machine2: the photomask, then the light source
ion_implanterIon Implanter1: the dopant
pick_and_placePick-and-Place Machine8: the component feeders

precision_machine itself is not a type: peripheral.find("precision_machine") returns nil. Ask for the machine you want, like peripheral.find("coil_winder"). On a Data Cable (Minicomputer and newer), a machine is named after its type and its place: ion_implanter@120,64,-35.

Also kinetic and inventory. A precision machine turns and holds items, so it is also a kinetic block and an inventory, with all their methods: machine.speed() reads its RPM, machine.overstressed() tells whether its network has stopped, and machine.list() shows the slots of the table above (how much wire is left on a Coil Winder, for example). peripheral.find("inventory") finds them too.

Where the part is. A machine works on the part on the belt or the depot two blocks below it (with one block of air in between, as for a Mechanical Press), or, when there is no belt or depot there, on an item lying on the ground right under it.

The Crystal Puller works over a Basin instead, and has its own methods: see its section at the end of this page.

When the machine is gone, its methods stop the program with machine removed (crystal puller removed for the Crystal Puller); when its chunk is not loaded, with peripheral is not loaded.

The head

#

machine.running()

→ boolean

Whether the machine is working: from the moment its head starts down on a part until it is back up.

Returns
boolean
true while the head is in a cycle

A "busy" lamp on top of the computer:

Brass
local winder = peripheral.find("coil_winder")
while true do
  rs.set("top", winder.running())
  sleep(0.5)
end

See also precision_machine.progress()

#

machine.progress()

→ number

How far the current cycle is, from 0 (the head leaves the top) to 1 (it is back up), rounded to 0.001. 0 while the machine is idle. A faster shaft makes the cycle shorter.

Returns
number
how far the head's cycle is, from 0 to 1; 0 when idle
Brass
local chamber = peripheral.find("vacuum_chamber")
print("vacuum cycle: " .. math.floor(chamber.progress() * 100) .. " %")

See also precision_machine.running()

The part below

#

machine.item()

→ table|nil⚙ cost 4

The part waiting under the machine, as an item table: the one on the belt or the depot two blocks below, or else the one lying on the ground right below. nil when nothing is there. count is the size of that stack.

Returns
table|nil
the part under the machine, {name =, count =, display =}; nil when there is none
Brass
local litho = peripheral.find("lithography_machine")
local part = litho.item()
if part then
  print("under the machine: " .. part.display)
else
  print("no wafer below")
end

See also precision_machine.recipe()

#

machine.recipe()

→ table|nil⚙ cost 4

The recipe the machine would run on the part below:

Returns
table|nil
{id =, output =, min_speed =, tier =}; nil without a part, or when nothing can be made of it
fieldcontent
idthe id of the recipe
outputthe item id of what it makes
min_speedthe slowest rotation the recipe accepts, in RPM; 0 for any speed
tierthe light source it needs on a Photolithography Machine, from 1 to 3; 0 on the other machines

Several recipes can start from the same part: every chip begins as the same coated wafer under the Photolithography Machine. The machine picks the one it is equipped for, so the photomask, dopant or wire you load decides the product, and recipe() tells which one it will be. In a sequenced assembly, output is the unfinished part this step gives, until the last step.

Brass
local implanter = peripheral.find("ion_implanter")
local r = implanter.recipe()
if r then
  print("makes " .. r.output .. ", at " .. r.min_speed .. " RPM or more")
end

See also precision_machine.item() precision_machine.problem()

Why it waits

#

machine.problem()

→ string|nil⚙ cost 4

What keeps the machine from working on the part below, as the goggles show it. nil when there is nothing below, or when the part can be worked on now.

Returns
string|nil
"no_recipe", "too_slow", "tier_too_low" or "missing_ingredient"; nil when the part below can be worked on, or when nothing is below
problemwhywhat to do
"no_recipe"this machine makes nothing of the part belowsend the part to the right machine
"too_slow"the machine does not turn, or turns slower than the recipe's min_speedspeed it up, or add power if the network is overstressed
"tier_too_low"the light source of the Photolithography Machine is too weak for the mask, or missingload a stronger light source
"missing_ingredient"the spool, target, dopant, mask or a component the recipe takes from the machine is missingrefill the machine

One problem at a time, in this order: once the speed is right, the machine may still lack its dopant. Ask again after each fix.

Brass
local winder = peripheral.find("coil_winder")
local problem = winder.problem()
if problem == "missing_ingredient" then
  print("the Coil Winder needs a new spool of wire")
elseif problem == "too_slow" then
  print("the Coil Winder does not turn fast enough")
end

See also precision_machine.recipe() precision_machine.tier()

#

machine.tier()

→ number

The tier of the light source loaded in a Photolithography Machine: 1 for a UV Lamp, 2 for an Excimer Laser (DUV), 3 for an EUV Light Source. 0 when none is loaded, and always 0 on the four other machines.

Returns
number
the tier of the light source in a Photolithography Machine, from 1 to 3; 0 without one, and on the other machines

A recipe runs when the machine's tier is at least the tier of the recipe: a stronger light works for older masks too.

Brass
local litho = peripheral.find("lithography_machine")
local r = litho.recipe()
if r and litho.tier() < r.tier then
  print("this mask needs a light source of tier " .. r.tier)
end

See also precision_machine.recipe()

Crystal Puller

The Crystal Puller grows a single crystal out of the melt in the Basin two blocks below it, by the Czochralski process: a silicon boule from molten silicon, or synthetic quartz. Its type is crystal_puller, and it is also a kinetic block.

It wants a slow shaft. Above its speed limit (32 RPM by default) the crystal would break: the puller does not start, and a pull already under way waits until the shaft slows down. Below the limit, the slower it turns, the longer the pull: twice as long at half the limit, up to eight times as long. The length is set when the pull starts, so the best speed is the limit itself, set before the pull begins.

#

puller.running()

→ boolean

Whether a crystal is being pulled.

Returns
boolean
true while it pulls a crystal
Brass
local puller = peripheral.find("crystal_puller")
print(puller.running())

See also crystal_puller.progress()

#

puller.progress()

→ number

How far the crystal is pulled, from 0 (the seed touches the melt) to 1 (the boule is out), rounded to 0.001. The goggles show it as a percentage.

Returns
number
how far the pull is, from 0 to 1; 0 when it is not pulling
Brass
local puller = peripheral.find("crystal_puller")
if puller.running() then
  print("boule " .. math.floor(puller.progress() * 100) .. " % pulled")
end

See also crystal_puller.running()

#

puller.max_speed()

→ number

The speed limit of the puller, in RPM: the server config crystalPullerMaxSpeed, 32 by default. The pull is fastest at exactly this speed.

Returns
number
the fastest rotation before the crystal would break, in RPM

With the cogwheel of a Rotation Speed Controller turning the puller directly, this gives the fastest pull the server allows (see speed_controller.set_target_speed()):

Brass
local puller = peripheral.find("crystal_puller")
local controller = peripheral.find("speed_controller")
controller.set_target_speed(puller.max_speed())

See also crystal_puller.problem()

#

puller.problem()

→ string|nil

What keeps the puller from working:

Returns
string|nil
"too_fast", "too_slow", "no_basin", or nil
problemwhy
"too_fast"it turns faster than max_speed(): the pull waits
"too_slow"it does not turn
"no_basin"there is no Basin two blocks below

nil means none of these, not that a pull is coming: the Basin still needs the right melt and enough heat (a superheated Blaze Burner for a silicon boule).

Brass
local puller = peripheral.find("crystal_puller")
if puller.problem() == "too_fast" then
  print("slow down: " .. puller.max_speed() .. " RPM at most")
end

See also crystal_puller.max_speed()

Examples

Never too slow

The recipes of the Ion Implanter ask for 128 RPM. A Rotation Speed Controller drives it, and this program raises the controller step by step while the implanter is too slow, up to Create's limit. When the network is overstressed, more speed would only make it worse: the program says so instead.

Brass
local implanter = peripheral.find("ion_implanter")
local controller = peripheral.find("speed_controller")
local STEP = 16   -- RPM added each time

while true do
  if implanter.overstressed() then
    print("overstressed: add power, more speed would not help")
  elseif implanter.problem() == "too_slow" then
    local target = controller.target_speed()
    if math.abs(target) >= 256 then
      print("the controller is at full speed already")
    else
      local rpm = math.min(256, math.abs(target) + STEP)
      if target < 0 then
        rpm = -rpm   -- keep the direction
      end
      controller.set_target_speed(rpm)
      print("too slow: controller set to " .. rpm .. " RPM")
    end
  end
  sleep(2)
end

It works through any gearing between the controller and the implanter, since it only watches the implanter's own problem. overstressed() comes from the kinetic methods every machine has.

A status board for the line

A Minicomputer on the Data Cable that runs along a chip line prints one line per machine it reaches: working with its progress, waiting with its problem, or idle. The Crystal Puller has running, progress and problem too, so it fits on the same board.

Brass
local NAMES = {
  coil_winder = "Coil Winder",
  vacuum_chamber = "Vacuum Chamber",
  lithography_machine = "Photolithography",
  ion_implanter = "Ion Implanter",
  pick_and_place = "Pick-and-Place",
  crystal_puller = "Crystal Puller",
}

local function status(machine)
  if machine.running() then
    return "working, " .. math.floor(machine.progress() * 100) .. " %"
  end
  return machine.problem() or "idle"
end

while true do
  term.clear()
  term.set_cursor(1, 1)
  for _, name in ipairs(peripheral.list()) do
    local label = NAMES[peripheral.type(name)]
    if label then
      print(string.format("%-17s %s", label, status(peripheral.wrap(name))))
    end
  end
  sleep(2)
end

peripheral.type gives the first type of each name, which for these blocks is their own (coil_winder...), not kinetic or inventory.