# Sequence of operation — 4-pipe FCU v0.1

**Implemented for FCU v0.1.** All numerical values are simulator teaching
defaults, not equipment specifications or standards requirements. This sequence
is educational and does not include equipment protection or safety interlocks.

## Occupancy and setpoints

Select the occupied heating/cooling pair in OCCUPIED and the unoccupied pair in
UNOCCUPIED. Defaults are 21/24°C occupied and 16/28°C unoccupied. Each heating
setpoint must be at least 0.5°C below its cooling setpoint. Occupancy, operating
mode, and simulator lifecycle are separate state fields.

Let T be room temperature, H the active heating setpoint, and C the active cooling
setpoint. On initialization, use DEADBAND as the previous mode for evaluating
entry conditions; this does not advance the physical model.

## Explicit operating-mode transitions

| Previous mode | Condition (evaluate in listed order within the mode) | Next mode |
| --- | --- | --- |
| DEADBAND | T < H − 0.1°C | HEATING |
| DEADBAND | T > C + 0.1°C | COOLING |
| DEADBAND | Otherwise | DEADBAND |
| HEATING | T > C + 0.1°C | COOLING directly |
| HEATING | Otherwise, T < H | HEATING |
| HEATING | Otherwise, T >= H | DEADBAND |
| COOLING | T < H − 0.1°C | HEATING directly |
| COOLING | Otherwise, T > C | COOLING |
| COOLING | Otherwise, T <= C | DEADBAND |

At exactly H − 0.1°C or C + 0.1°C, DEADBAND does not enter conditioning.
An existing HEATING mode exits at T >= H; an existing COOLING mode exits at
T <= C. This is 0.1°C mode-entry hysteresis, not a shifted setpoint.

After a live setpoint or occupancy change, use the new active setpoints on the
next step. If the opposite entry condition is satisfied, transition directly;
do not insert an artificial deadband step. No minimum dwell time is modeled.

## Legacy comparison valves and mutual exclusion

Each valve defaults to PI using the CO contract below. LEGACY_P explicitly selects the original
proportional band is 2°C. Demand is a fraction from zero to one:

```text
HEATING:  demandFraction = clamp((H − T) / 2°C, 0, 1)
          heatingValve = 100 × demandFraction; coolingValve = 0
COOLING:  demandFraction = clamp((T − C) / 2°C, 0, 1)
          coolingValve = 100 × demandFraction; heatingValve = 0
DEADBAND: demandFraction = 0; heatingValve = 0; coolingValve = 0
```

Valve outputs are percentages, not fractions. Both valves must never be active
simultaneously. Saturating a valid control demand to its command range is
intentional; it is not permission to hide invalid thermal results. In LEGACY_P
mode no integral term is used, so sustained thermal load may produce steady-state
offset. The optional PID extension below changes only the active valve demand;
the operating-mode transition table and valve mutual exclusion remain authoritative.
PID can retain a nonzero output in DEADBAND as specified below; the zero-output
DEADBAND row above applies to LEGACY_P operation.

## Fan operation

- Occupied AUTO: LOW with zero deadband output; stage from demand otherwise,
  including a retained PID output in deadband.
- Occupied fixed LOW, MEDIUM, or HIGH: continuous selected speed, including deadband.
- Unoccupied: OFF only in deadband with zero valve demand. Retained positive PID
  output keeps the fan running with automatic staging or the selected fixed speed.

Automatic staging uses the active valve demand fraction. Apply this state table
after occupancy gating and fixed-fan selection:

| Previous automatic stage | New demand d | Next stage |
| --- | --- | --- |
| OFF / initialization / LOW | d >= 0.75 | HIGH |
| OFF / initialization / LOW | 0.40 <= d < 0.75 | MEDIUM |
| OFF / initialization / LOW | d < 0.40 | LOW |
| MEDIUM | d >= 0.75 | HIGH |
| MEDIUM | 0.30 <= d < 0.75 | MEDIUM |
| MEDIUM | d < 0.30 | LOW |
| HIGH | d >= 0.65 | HIGH |
| HIGH | 0.30 <= d < 0.65 | MEDIUM |
| HIGH | d < 0.30 | LOW |

This allows direct LOW-to-HIGH and HIGH-to-LOW transitions. HIGH is retained
at exactly 0.65 and MEDIUM at exactly 0.30. Upward thresholds include exactly
0.40 and 0.75. On switching from fixed fan to AUTO, initialize the automatic
stage with the upward thresholds rather than inheriting a forced fixed stage.
In occupied deadband with zero output the fan is LOW; nonzero retained output
continues normal staging.

## Live changes, time, and history

Live fields: occupied heating/cooling setpoints, unoccupied heating/cooling
setpoints, occupancy, outdoor temperature, internal heat gain, fan selection, and
each valve’s CO type, proportional band, integral rate, configured bias, full-width
deadband, reset band, derivative gain (zero only), and derivative sample time.
Initial room temperature, capacities, and duration lock after a run starts.

Apply a validated live patch atomically on the next one-second simulation step;
paused patches wait for resume. At that boundary, apply the patch, evaluate the
controller with current temperature, and force a history sample **before** thermal
advancement. This records the changed inputs and commands at the actual simulated
time of application. Then advance the thermal model and simulated time. Log the
applied changes. Speed is a playback setting and never changes the physical dt.

Normal samples occur every ten simulated seconds; initial and final samples are
mandatory. Samples carry simulatedSeconds, roomTempC, activeHeatingSetpointC,
activeCoolingSetpointC, heatingValve, coolingValve, fan, mode, and occupancy.
When a patch shares a timestamp with an existing sample, retain ordered before/
after samples so charts can show a step at that timestamp. Avoid redundant
identical normal/final samples. Setpoint series use step interpolation.

## Initialization, lifecycle, and reset

Initialization/loading a scenario and reset must:

1. Stop scheduling and set simulated time to zero.
2. Use the supplied initial scenario on first load; on reset after Start, restore
   the configuration captured when that run began (including initial speed).
3. Clear pending live patches, previous history, and previous event log.
4. Evaluate the controller once with initial temperature and no previous control state.
5. Do not advance the thermal model.
6. Record a fresh initial sample and leave lifecycle READY.

Start captures a detached run-start configuration and enters RUNNING. Resume does
not overwrite that capture. Pause enters PAUSED; completion enters COMPLETED and
preserves results until reset. Before a first run, reset reinitializes the current
configured scenario. A reset leaves the event log empty; it does not preserve a
reset event from the previous run.

The deterministic engine core exposes advanceOneStep() for numerical tests,
independent of requestAnimationFrame or real elapsed browser time. A scheduler
wraps this core for playback. History and events are engine-owned; UI and charts
consume detached snapshots. The fixed-step engine and separate playback scheduler
implement these contracts. A final shortened step ends fractional-minute durations
exactly; all preceding steps use the fixed one-second timestep.

## Controller state and display interpretation

The controller returns the existing mode, demandFraction, heatingValve, coolingValve,
and fan fields plus fanSelection (the current requested AUTO/LOW/MEDIUM/HIGH
selection). Pass this complete result back as previous on the next evaluation.
The selection field distinguishes a fixed-to-AUTO change; no hidden staging state
is retained. Previous null initializes the sequence. Inputs and tuning are validated
explicitly and never mutated.

Controller commands are evaluated at the beginning of each numerical step. The
reported room temperature includes that step’s thermal response. At initialization
and reset, the temperature is unchanged and commands reflect the initial evaluation.
UI Apply in READY reinitializes the configured scenario; it does not start playback.
During a run, Apply queues live fields. An unapplied draft must be applied or reset
before Start/Resume so displayed draft values cannot be mistaken for running inputs.

## Independent heating and cooling CO loops

The supplied Delta CO documents supersede the previous I-only deadband and Td
approximation. See [source mapping and limitations](delta-co-conformance.md).
Each valve offers P, I, PI, PID, or LEGACY_P. Defaults are PI, PB 2°C, Bias 50%,
I rate 1%/min, full deadband 0.2°C (SP ±0.1°C), reset band 0°C (disabled),
Derivative Gain 0 and derivative sample time 1 second. Nonzero derivative gain
is rejected because the supplied references do not specify its equation.
PID with zero derivative gain behaves as PI. Sample time currently has no effect.

For an enabled loop outside its own deadband:

```text
e = T - cooling SP                 [direct acting cooling]
e = heating SP - T                 [reverse acting heating]
effective PB = configured PB + configured deadband
P = limit(100 × e / effective PB, -50, +50)  [P, PI, PID]
P = 0                                      [I only]
CO = limit(P + Bias + D, 0, 100)
D = 0                                      [supported setting]
```

P is a signed correction in percentage points, not valve position. At setpoint,
a newly evaluated P object with configured Bias 50 has an intrinsic output of 50%.
The FCU sequence may inhibit that object's valve independently.

Deadband is a FULL width centered on the active loop setpoint. Inside the inclusive
band the active object's previous CO is held, for every CO type. The simulator
also holds the applied P and Bias diagnostics so they continue to explain that CO;
it does not display a changing hypothetical P as an applied contribution. A fresh
loop in-band initializes P to zero and CO to its configured bias. The broader
heating-to-cooling mode gap is not the CO deadband.

I/PI/PID adjust Bias at the configured percentage-point rate per simulated minute,
in the direction of signed error. This is time-based Delta-style reset, not an
error-magnitude integral. With a positive reset band, multiply the rate by
min(1, abs(e)/(resetBand/2)). Reset band is full width and must be less than PB;
zero disables tapering. Deadband takes precedence and stops adjustment. I rate
zero preserves current Bias.

At CO output limits (0 or 100), integral adjustment stops. When a discrete
increment would cross a limit, accept only the part reaching that limit, then
hold Bias. This avoids displaying a just-below-limit output with a rejected
whole increment. A later P change can move the raw sum off the limit and release
the hold. Endpoint release for pure I is underspecified by the source; this
implementation conservatively holds at the limit until an explicit Bias change
or reinitialization. Do not present this as verified Delta firmware behavior.

Initialization and reactivation seed the configured Bias. An active loop retains
its accumulated Bias across tuning/type edits; changing the configured Bias
explicitly replaces it. Reapplying the same configured Bias is not a BACnet Bias
property write. Reset restores the run-start configuration and reinitializes Bias.
No integral accumulates on initialization, zero-dt evaluations, or the first
observation of changed tuning. These are explicit simulator scheduling choices.

The FCU temperature-mode transitions remain as above. An established CO loop can
retain output ownership through DEADBAND, even at zero output. Opposite-mode entry
inhibits the old loop before enabling the new one, preventing simultaneous valves.
Initializing the FCU in its mode gap does not enable either loop. Inactive loop
valves are zero because of this external sequence gate, not intrinsic CO behavior.
Switching to LEGACY_P releases retained CO ownership. Positive retained output
continues fan operation, including in unoccupied mode.

Controller input includes elapsed simulated dtSeconds for I/PI/PID. Returned
loops.heating/cooling contains explicit CO state; LEGACY_P returns null for its
loop. The existing iPercent history field now represents total Bias, including the
initial bias; it is labeled Bias in the UI. No thermal calculation is in this module.

Loop edits commit independently on change, Enter, or Apply loop. Invalid drafts
are rejected atomically. During playback, accepted edits apply on the next fixed
step; paused edits wait for Resume. READY recalculates time-zero state. No edit
advances simulation time. Speed affects playback scheduling only.

## Trend tags

Room Temperature and Setpoint are initially selected. Setpoint draws both active
heating and cooling limits, avoiding an invented single setpoint in deadband.
Optional tags: estimated SAT, both valve outputs, Heating CO output, Cooling CO
output. Temperatures and percentage outputs share one plot with left °C and right %
axes. Zoom and earlier/later controls change the visible time range only; All time
follows the full run. Zoomed windows stay fixed and traces are clipped at the plot
boundary. Supplementary diagnostics use the same visible time range. Tag changes redraw existing history without modifying simulation.
CO denotes the bounded, sequence-gated 0–100% loop output; valve commands equal
CO because actuator lag/overrides are not modeled. Raw P+Bias+D is separately labeled
in each loop's diagnostics, never mislabeled CO. SAT is null with the fan OFF;
charts break at unavailable samples. All tags are sampled even when not selected.

OAT and fan selection validate and commit independently on change or Enter without
Apply scenario. Invalid input preserves the accepted point and unrelated draft
edits remain unapplied. READY refreshes initial commands; RUNNING queues the next
fixed step; PAUSED waits for Resume. These actions never advance simulated time.
The room graphic displays the active occupancy's heating/cooling setpoints above
Room Temperature, with separate warm/cool colors and explicit SP labels. Pending
setpoint drafts are not displayed as active setpoints.
