# Recipes

> The setpoints a run writes: an instant set applied at Start, or a temporal Step, Ramp and Soak profile, each with the finalization block every ending run writes.

A **recipe** writes the model's writable channels. An **instant** recipe is a set of setpoints
applied once when a run starts; a **temporal** recipe is an ordered profile of Step, Ramp and Soak
segments driven on the run's own clock. Both may end on a **finalization** block, the safe state
written whenever the run ends. Every write rides the single journaled
[write funnel](logic#writing-values) and is recorded in the run file as executed. Recipes live
under Process > Models > model > Setup > **Recipes** and need the **Edit recipes & evaluations**
permission.

## The Recipes folder {#the-recipes-folder}

Selecting the **Recipes** row opens the folder page ("Setpoint values a procedure applies when a
run starts, written through the single funnel and recorded as-executed.") with one card per
recipe, by name; clicking a card opens it and the X removes it. With none, the page says "No
recipes yet. Add one."

| Command | What it does | Greyed when (situation) | Not drawn when (role) |
| --- | --- | --- | --- |
| **Recipe** (detail bar, folder selected; right-click **Add recipe**) | Adds `Recipe <n>`: Instant, On failure Hold, no setpoints. Selects it. | Any unit holds a run, or the runtime is stopped. | Without Edit recipes & evaluations. |

## The recipe's page {#the-recipes-page}

The header carries the name, the line "Setpoint values a procedure applies when a run starts,
written through the single funnel and recorded as-executed." and a pill with the kind. Every field
autosaves, and every edit redraws the profile preview.

### Definition {#definition}

| Field | What it is | Values / default | Effect |
| --- | --- | --- | --- |
| **Name** | What the recipe is called in the tree, in a procedure's Recipe picker and in the Apply recipe picker of a panel. | Free text; `Recipe <n>` at creation. | Renames the row. |
| **Kind** | The recipe's form. | **Instant** (default): a set of setpoints applied once at Start. **Temporal**: an ordered Step / Ramp / Soak profile on its own clock. | Switches the page between the two forms below. The setpoints and segments already entered are kept; only the form the kind reads is drawn. The kept block is neither applied nor reserved: a run writes and locks only what the kind in force writes. A procedure admits only a recipe whose kind fits its mode. |
| **On failure** (temporal only) | What the profile does when a write fails or a value it needs is unreadable. | **Hold** (default), **Continue**, **Abort**; see the table under the temporal form. | The recipe's rule; each segment may override it. |
| **Notes** | Free text for whoever reads the recipe. | Multiline; empty by default. | Nothing else. |

### Values {#values}

Setpoints and targets are typed as text and parsed to the channel's type when they are applied,
in the invariant culture:

| Channel type | Accepted literals |
| --- | --- |
| Bool | `true`, `false`, `1`, `0` |
| Integer | A whole number, `.` never used as a separator |
| Real | A number with `.` as the decimal separator (`12.5`, `1e-3`) |
| String | Any text, as typed |
| DateTime | A round-trip date and time (`2026-09-02T14:30:00+00:00`) |

A literal that does not fit the channel's type is never written: the write is recorded as failed
in the run file and handled by the failure rule. Every grid of channel rows on this page offers a
row only the writable channels the other rows of the same grid have not taken, so a channel is
written once per set and once per segment; when every writable channel is taken, the button that
adds a row is greyed with "Every writable channel already has a setpoint here." (or "… a target
in this segment."), and with no writable channel at all with "This model has no writable channels
yet."

## An instant recipe {#an-instant-recipe}

Two independent cards, each headed by a **Setpoint** button that adds a row on the first free
writable channel with the value `0`:

| Card | What it writes |
| --- | --- |
| **Setpoints (applied at start)** | Written once, at Start, before the run records anything. |
| **Finalization (safe state)** | "Written whenever the run ends: completion, stop or abort." |

Both grids share the same columns:

| Column | What it is | Values |
| --- | --- | --- |
| **Channel** | The writable channel written, as `Ch<number> · <name>`. | Channels in the Write only and Read and write roles, not yet taken in this grid. |
| **Value** | The literal written. | See Values. |
| Row action | **Remove** asks "Delete setpoint": the channel's name, "This removes the setpoint from the recipe." | |

## A temporal recipe {#a-temporal-recipe}

The temporal form draws the profile first, then the segments beside the one picked in them, then
the finalization block.

### Temporal profile {#temporal-profile}

The chart is the recipe itself: one series per numeric channel the segments target, labelled
`<name> [<unit>]`, drawn as the piecewise curve the segments build (a Step jumps then holds, a
Ramp slopes from the value carried into it, a Soak or an untargeted segment holds), over the
cumulative time in seconds. A target on a channel that is not numeric is not drawn. The chart
carries the toolbar every chart has and can be opened in its own window (see
[Charts](view-charts)); the procedure page draws the same profile under its Apply stage.

### Segments {#segments}

The list is headed **Segments** and its **Segment** button adds a Step of 60000 ms with no
targets and picks it. Clicking a row opens it in the **Segment targets** column; with nothing
picked the column says "Select a segment to edit all of it in one place, with the targets it drives
to."

| Column | What it is | Values / default |
| --- | --- | --- |
| **Kind** | The shape the segment puts on the profile; each option carries that shape beside its name. | **Step** (default), **Ramp**, **Soak**. |
| **Duration (ms)** | How long the recipe stays in the segment before moving to the next. | A number of milliseconds; 60000 at creation. The field takes no negative number: the entry is refused, the note says so under the field, and the previous value comes back. |
| **On failure** | Whether a failure here follows the recipe's rule or one of this segment's own. | **(recipe)** (default: inherit), **Hold**, **Continue**, **Abort**. |
| **Targets** | What the segment drives to, on one line: `<channel> = <value>, …`. A Soak drives nothing, so it reads empty. | Read-only; edited in the column. |
| Row action | **Remove** asks "Delete segment": `Segment <n> · <Kind>`, "This removes the segment and its targets from the recipe." | |

| Segment kind | What it does |
| --- | --- |
| **Step** | Writes its targets at segment start, then dwells for the duration (a Step may dwell 0 ms). |
| **Ramp** | Drives each targeted channel linearly from its last commanded value (or its current value when nothing was commanded yet) to the target over the duration. A ramp moves its channel between two readings, so its target picker offers only the channels that hold a number: a text or a date and time channel stays in the list unselectable, saying that a step writes it once instead. |
| **Soak** | Holds the last commanded values for the duration; writes nothing and carries no targets. |

The column, headed `Segment <n> · <Kind>`, repeats the three fields with a legend and the effect of
the value in force under each:

| Field | Legend | Effect |
| --- | --- | --- |
| **Kind** | The shape beside each option. | |
| **Duration (ms)** | "How long the recipe stays in this segment before it moves to the next." | "Runs for 1 min 30 s." ("no time at all" for 0). |
| **On failure** | "Whether a failure here is handled the way the recipe says, or another way this one segment asks for." | "Follows the recipe: Hold." or "This segment overrides the recipe: Abort." |

Under them, **TARGETS** holds the segment's targets, with a **Target** button in the column's head
that adds a row on the first writable channel the segment does not drive yet, at `0`. On a Soak the
grid is replaced by the line "A soak holds the values the profile has already reached, so it drives
no target of its own." and the button is greyed, saying "A soak holds the values already reached.
Change the kind to drive a target here."; targets a Soak carries from an earlier kind stay stored
and are ignored by the run, the profile preview and the travel sheet alike:

| Column | What it is |
| --- | --- |
| **Channel** | The writable channel driven, offered only among the channels the other targets of this segment have not taken. |
| **Target** | The literal written (Step) or reached (Ramp). |
| Row action | **Remove** asks "Delete target": the channel's name, "This removes the target from the segment." |

### On failure {#on-failure}

The failure rule applies to a temporal recipe when a write is refused or a value it needs is
unreadable:

| Rule | What the run does |
| --- | --- |
| **Hold** | Freezes the profile at the last setpoint and waits for the operator to Resume or Abort. The datalog keeps recording. |
| **Continue** | Records the failure as executed and keeps going; the failed channel is skipped for the rest of the segment. |
| **Abort** | Writes the finalization block and ends the run as aborted. |

### Finalization (safe state) {#finalization-safe-state}

The same card as on an instant recipe, "Written whenever the run ends: completion, stop or
abort.", with its own **Setpoint** button and grid.

## Where a recipe is used {#where-a-recipe-is-used}

| Surface | What it does with the recipe |
| --- | --- |
| A procedure's **Apply** stage | The picker offers every recipe of the model; a recipe whose kind does not fit the procedure's mode is listed as "`<name>` (incompatible)", disabled, with the reason as its tooltip. An Instant or Controlled procedure takes an instant recipe or none; a Temporal one takes a temporal recipe or none, and its profile must fit inside the window. The stage line says what the choice writes: "Writes 4 setpoints at start.", "Writes 4 setpoints at start and 2 setpoints on the way out.", "Drives 3 segments over 2 min 30 s and writes 2 setpoints on the way out.", or "No setpoints are written." See [Procedures](process-procedures). |
| The **Single commands** card of a panel | **Apply recipe** writes an instant recipe's setpoints once, outside any run, through the journaled funnel under a short-lived lock; no run file is created, the journal is the record. Only instant recipes are offered. See [Panels](process-panels#the-card-catalogue). |

## What a run does with it {#what-a-run-does-with-it}

At Start the run takes ownership of every source the recipe writes for its whole duration, so a
Start whose recipe writes a source another run holds is refused by name, and every write from
elsewhere (a dashboard field, a script, an OPC UA client) to those sources is refused while the
run owns them. An instant set is written once at Start; a temporal profile is walked segment by
segment on the run's clock, and a Temporal procedure with no recipe still gets that clock so
every member of the window starts, repeats and ends together. Hold freezes the clock and Resume
releases it; the datalog keeps recording through a hold. Every write is journaled with the run as
its source and recorded in the run file as executed, so a later reader sees what was actually
written and when.

The finalization block is written whenever the run **ends**: completion, Stop or Abort. It is
not written when the application itself is closed mid-run: the run is not ending, and it resumes
held at its persisted segment and cycle on the next launch.

## Remove recipe {#remove-recipe}

**Remove recipe** stands in the detail bar's danger cluster with the recipe selected, on its
right-click menu, and as the X on the folder page's card. It asks first ("Delete recipe", "This
removes the recipe from the model."). Every procedure that applied it empties its Recipe reference
and becomes a recipe-less run. It needs Edit recipes & evaluations and is greyed under the
engineering lock and the stopped runtime.

## What a recipe does not do {#what-a-recipe-does-not-do}

- It reads nothing and judges nothing: limits are an [evaluation](process-evaluations).
- It writes no per-unit values. Every unit receives the same literals on its own bound sources.
- It never retries a refused write; the failure rule is the whole answer.
- A temporal recipe cannot be fired as a single command; only a procedure runs a profile.
- The profile preview draws numeric channels only; a Bool or String target is written but never
  drawn.
