Skip to main content

Products and steps

Any unfamiliar word is in the glossary.

A product is a thing you make and the ordered route it takes. A step is one operation on that route.

id: bracket
pieces: 25
steps:
- resource: saw
process_seconds: { kind: constant, value: 60 }
- resource: mill
process_seconds: { kind: erlang, k: 4, mean: 300.0 }
- resource: inspect
process_seconds: { kind: uniform, lo: 30, hi: 90 }

An entity walks these in order. When it runs out of steps, it is complete.

Product​

keytyperequiredwhat it means
idstringyesThe product's name, referenced by releases and by the schedule.
stepsarray of stepyesThe ordered route. An entity visits these in sequence, and may revisit via rework.
piecesintegerno, default 1Pieces per entity — wafers per lot. Read by batching, scrap, a per-piece step time (process_per_piece, piece_interval_seconds) and maintenance that counts pieces; every other quantity in the model is per entity.
pieces is what makes an entity divisible

An entity is a lot of pieces. Scrap can take individual pieces and leave the rest, a batch can be sized in pieces rather than lots, a step can be timed per piece, and maintenance can count pieces. A schedule entry may give one lot its own size. Every other quantity in the model is per entity.

Step​

Only resource and process_seconds are required. Everything else adds a mechanism.

keytyperequiredwhat it means
resourcestringyesWhich resource performs it.
process_secondsdistyesHow long the work itself takes at this step. A zero constant is legal and means an instantaneous step.
setupstringnoThe setup this step needs. None means any.
setup_alwaysbooleanno, default falsePerform setup on every entity, even when the server is already in it — a recalibration rather than a change. Priced by the resource's changeover from any setup (from: "") to this one, which must exist. ⚠️ Only that row: an explicit from into the same setup prices a change, never this.
patience_secondsdistnoHow long an entity will wait in this step's queue before abandoning it. ⚠️ Abandonment is not scrap. The entity leaves the queue and the model, counted in Outcome::abandoned; it is not destroyed at a step and it does not continue. A model that silently kept them would report a fab whose customers all wait for ever. ⚠️ Only meaningful where something waits. An entity already in service cannot abandon — that is a different mechanism, and no queueing result this project checks against assumes it.
operatoroperatornoA person this step needs, as well as the tool.
probabilitynumberno, default 1.0Probability in [0, 1] that the step happens at all. Metrology sampling.
namestringnoA human-readable label, carried through to output.
reworkreworknoSend the entity back to an earlier step, or away on a sub-route, with some probability.
dedicationdedicationnoBind this entity to a server, or require a previously bound one.
queue_timerqueuetimernoA ceiling on the wait between finishing here and starting a later step.
consumesarray of consumesnoConsumables this step draws down. ⚠️ Drawn before processing begins and never returned; an entity that cannot get its materials waits at the step rather than occupying a server while short.
producesarray of producesnoConsumables this step puts back, applied when the step completes. ⚠️ At completion, not at start — a half-stocked shelf is not stock.
commissionsarray of commissionsnoMachines this step puts into service when it completes — see Building capacity.
transport_secondsdistnoTime to travel to this step, before queueing for a server. ⚠️ A delay, not tool time. Folding it into process_seconds was tried and charged every tool 7.5 minutes per visit: SMT2020's HVLM fab went from 41,681 lots per two years to 30,033 and its cycle time from 26 days to 238. Transport occupies the lot, never the equipment.
start_interval_secondsnumbernoThe soonest the station may start ANOTHER lot after starting this one. ⭐ Lot-level cascading, the same shape as a wafer-level part interval. With a resource capacity of c, a saturated station delivers one lot every max(start_interval, process / c). ⚠️ It belongs to the STEP because the interval is a property of the operation being run, not of the machine — although in SMT2020 the ratio process / interval turns out to be constant within a tool area, which is how the column was recognised.
process_per_piecebooleanno, default falseprocess_seconds is per piece: the drawn time is multiplied by the pieces the entity carries — a lot's own, when a schedule entry sets them. ⚠️ Not on a batch step, whose time is per batch.
piece_interval_secondsnumbernoA wafer cascade: the entity's pieces pass through the tool this far apart. The step takes process_seconds + (pieces − 1) × interval, and on a station of capacity above 1 the next entity may start pieces × interval later. ⚠️ Not beside start_interval_seconds or process_per_piece.
transport_operatortransport_operatornoA person who must accompany the lot during transport_seconds.
scrapscrapnoWork destroyed at this step. ⚠️ Scrap is loss, not rework: the pieces do not go anywhere and must show up in the conservation check.
batchbatchnoProcess several entities together. Absent means one at a time. ⚠️ Entities batch only with others at the same step of the same product, which is what SMT2020's crit_sameroutestep specifies. A batcher that pools across steps would fill furnaces far faster than the real one.
scale_profilestringnoA profile id whose factor multiplies this step's process duration. ⚠️⚠️ The reciprocal of a release's rate_profile, which is why they are separate keys: 1.6 here means takes 60% longer, while 1.6 on a rate makes gaps shorter. ⭐ A duration that spans a segment boundary is integrated, not sampled at its start — the job carries a fixed amount of work and accrues it at 1 / factor(t) per second.

Scrap​

scrap destroys work at this step, in one of two ways. entity takes the whole job; piece judges each piece independently, which thins the job rather than removing it.

keytyperequiredwhat it means
entitynumbernoProbability the entire entity is destroyed after this step.
piecenumbernoProbability each remaining piece is destroyed after this step.

A "piece" is whatever the product's pieces counts: bottles in a crate, panels on a pallet, wafers in a lot. A crate of 12 bottles with "piece": 0.05 usually arrives as a crate of 11 or 12; with "entity": 0.05 it occasionally does not arrive at all.

note

These were called lot and wafer until 2026-09-14. Both spellings still parse, so existing models keep working, but the semiconductor names were a claim that this engine is only for semiconductors — and it is not.

Scrap compounds

Two steps at 5% each lose 1 - 0.95² = 9.75%, not 5%. Each step judges the pieces that survived the last one.

Rework​

rework sends an entity somewhere else, with some probability. There are two shapes, and a step declares exactly one of them.

keytyperequiredwhat it means
probabilitynumberyesProbability in [0, 1] of reworking.
to_stepintegerone of the twoThe step index to return to. ⚠️ Must be earlier than this one, or the route would move forward and the "rework" would be a skip.
excursionarray of stepone of the twoA sub-route to run instead, resuming at the step after this one.

to_step is a loop: the entity goes back over operations its own route already contains and walks forward again, so the number of extra visits is geometric.

excursion is a detour: the entity leaves the route, runs a sub-route that may use tools the route never visits, and comes back to where it left off. It redoes nothing, so it runs at most once per visit to the step that sent it.

resource: align
process_seconds: { kind: constant, value: 90.0 }
rework:
probability: 0.08
excursion:
- resource: strip
process_seconds: { kind: constant, value: 120.0 }
- resource: coat
process_seconds: { kind: constant, value: 60.0 }
- resource: develop
process_seconds: { kind: constant, value: 45.0 }

A lot that fails alignment is stripped, re-coated and re-developed — three operations the main route never performs — and then resumes at the step after align.

Why a detour is not a loop

Photolithography rework is the case this exists for. The SEMATECH/MIMAC datasets hold their rework as named sub-routes in a separate file: set3's A1090 is COAT5 → PAL → DEV → SCOP2, four operations on four tools, and all 116 of that dataset's rework steps point into such a sequence. No to_step can express it, because to_step can only name a step the route already has.

A rework loop can be unbounded

Sending work back to a step that can send it back again is a loop with a probability, not a guarantee of termination. The engine reserves a distinct random block per revisit so a lot does not redraw the same outcome for ever, but the model is yours to make sensible.

An excursion does not have this hazard — it resumes past the step that sent it — but a step inside an excursion may not itself declare rework, and the document is refused if it does. The sub-route is not expanded again, so such a field would be accepted and then do nothing.

Drawing material​

consumes makes a step draw from a consumable. If the store cannot meet the quantity, the entity waits here until it can.

keytyperequiredwhat it means
consumablestringyesWhich store this step draws from, by id.
quantitydistyesHow much is drawn per entity. The step waits if the store cannot meet it.

Putting material back​

produces is the mirror of consumes, and together they are what makes a second tier expressible. A step that draws a case from the storeroom and puts it on the shelf is an ordinary product with an ordinary route — so queueing for the stacker, competing with other duties, and the time it takes all fall out of mechanisms that already exist.

That matters because a single store conflates two losses with opposite remedies:

an article iswhose problemwhich lever
on the shelf——
not on the shelf, but in the storeroomshelf-stockinganother stacker
not ordered at allpurchasinga higher reorder point

With two stores they are simply stockouts on different consumables, and the results document reports them separately.

warning

Production happens at completion, and before the scrap and rework decisions.

A step that drew its materials and finished the work put those units somewhere, whatever becomes of the entity next. Producing after the scrap roll would let a destroyed restock task delete stock it had already taken out of the storeroom.

⚠️ A store never exceeds its capacity, so production that does not fit is lost at the cap and reported as total_spilled. validate refuses the case it can see in the document — a constant quantity larger than the store itself — but a distribution's upper tail is a property of the run, so the rest is counted rather than refused.

Building capacity​

commissions makes the completion of a step add machines to a resource that declared fewer installed than servers. A tool installation in a fab ramp and a building in a factory game are the same thing: an ordinary step that can draw its materials, need a person and travel, whose completion is the moment capacity grows.

resources:
- id: assembler
servers: 4
installed: 1
- id: crew
servers: 1
products:
- id: build-assembler
steps:
- resource: crew
process_seconds: { kind: constant, value: 3600.0 }
consumes:
- { consumable: frames, quantity: { kind: constant, value: 8.0 } }
commissions:
- { resource: assembler, servers: 1 }

⚠️ Stations come into service lowest index first, each the instant the step completes, and work waiting for them starts at once. A commission that finds nothing left to install is counted as commissions_unfilled, never silently dropped.

⚠️ An uninstalled station is NonScheduled — where SEMI E10 books installation — so it is outside the availability denominator. A group with uninstalled stations may not also have a roster_profile, a capacity policy or downtime: each would bring a machine that is not yet built into service, or into failure.

⚠️ A step that commissions may not also batch, scrap or rework: a batch finishes every member, a scrapped entity still completes the step and rework repeats it, so each would build once per entity or per pass rather than once per building. validate refuses the combination.

Types used above​

operator​

A person who must be present for this step, drawn from an operator pool.

pool: technicians
attendance: { load: 1.0, process: 0.0, unload: 1.0 }

pool takes either one pool name or a list, in which case any of them will do.

attendance says what fraction of each phase the person is held for, as a number in [0, 1]. The example above is the common case: the technician loads the machine and unloads it, and walks away while it runs. 1.0 for all three holds them for the whole operation.

warning

These are fractions, not switches. "process": 0.5 means the operator is tied up for half the processing time, not that they attend every other job.

dedication​

Forces an entity back onto the same physical machine it used earlier — a tool lock.

chain: litho
mode: bind
modemeaning
"bind"remember whichever server ran this step, under this chain name
"use"run on the server remembered for this chain
warning

"use" does not fall back. If the remembered machine is down, the entity waits for it, because that is what a real tool lock does. This can idle a whole bank of free machines, which is correct and is also worth knowing before you model it.

queue_timer​

A maximum time an entity may spend between this step and a later one — a bake-to-etch limit, a shelf life, a cure window.

until_step: 7
max_seconds: 14400.0

until_step is the index of the later step in the route, counting from zero. Breaches are counted in the results document as timer_violations.

consumes​

Material this step draws down, from a consumable.

consumable: adhesive
quantity: { kind: constant, value: 4.0 }

quantity is a distribution, not a fixed number — real consumption varies. Use constant if it genuinely does not.

A step may list several. If any of them is short, the entity waits, holding the ones it has already drawn.

commissions​

Machines a step puts into service when it completes.

resource: assembler
servers: 1
keytyperequiredwhat it means
resourcestringyesThe resource that gains machines, by id. It must declare installed below servers.
serversintegeryesHow many machines come into service. 0 is refused — omit the entry instead.

produces​

Material this step puts back, into a consumable.

consumable: shelf
quantity: { kind: constant, value: 24.0 }

Same shape as consumes, and a step may list several. The quantity is a distribution — a picker who sometimes drops one is a real thing to model.

⭐ A step may consume and produce at once, which is the whole point: drawing a case from storeroom and producing it into shelf is one step that moves stock between two tiers.

transport_operator​

A person who must accompany the entity while it travels — a driver, a crane operator.

pool: drivers
attendance: 1.0

Only meaningful alongside transport_seconds; validate refuses one without the other. Here attendance is a single fraction of the journey, not the three phases a machine step has.

batch​

Process several entities together as one load. Fully described on the batching page.

min_pieces: 30
max_pieces: 60
group: cure
warning

Sizes are in pieces, not entities, because that is what the equipment constrains — an oven holds 150 panels whether they arrive as six pallets of 25 or five of 30. ProductIr.pieces converts.