# VAV v0.1 — implemented sequence and model contract

Implemented 2026-09-11 at `vav-simulator.html`. This is a single-duct VAV with
hot-water reheat, for learning. It is not physically validated, a duct-sizing
calculation, a ventilation compliance check, a protective sequence, or firmware emulation.
The FCU sequence and modules remain independent and unchanged.

## Modules

| File | Responsibility |
| --- | --- |
| `js/vav/config.js` | Central constants, defaults, input bounds, units and validation |
| `js/vav/flow-co.js` | Pure airflow CO; explicit previous memory and simulated dt |
| `js/vav/sat-control.js` | Conventional PI for cascade or manual post-reheat SAT targets |
| `js/vav/controller.js` | Room demand, airflow SP and reheat ownership |
| `js/vav/model.js` | Damper travel, actual flow, coil heat transfer, room energy |
| `js/vav/engine.js` | One-second steps, lifecycle, atomic patches, samples and events |
| `js/vav/charts.js` | Unified SVG trend, tag definitions and independent time-view state |
| `js/vav/ui.js` | Forms, schematic, diagnostics and playback scheduling |

All production scripts are classic deferred scripts. No fetch, server, packages,
build step or external resources are required. Airflow is stored in m³/s; CFM and
L/s are display/input units. An untouched field retains its precise canonical
value across display unit changes, including tuning bands and pending drafts.

## User settings and teaching defaults

| Setting | Initial value |
| --- | --- |
| Mode | Room Auto; Flow SP Test available |
| Airflow setpoint source / reheat control | Auto room demand / Direct room demand; Cascade and independent overrides available |
| Post-reheat SAT override setpoint | 30°C; active only with SAT setpoint override |
| Cascade SAT maximum | 35°C; editable 15–50°C, must be at least AHU inlet temperature when active |
| Room SP / initial room / AHU primary air | 22 / 26 / 13°C |
| Min / Max / manual flow SP | 200 / 800 / 500 CFM |
| Room net heat load | +1500 W; negative values represent net heat loss |
| Duration | 60 minutes; initial temperature and duration lock after Start |
| FLOW_CO type / PB / integral rate | PI / 600 CFM / 12 percentage points per minute |
| Initial Bias / deadband / reset band | 50% / 4 CFM full width / 80 CFM full width |
| Derivative Gain | 0, read-only; nonzero D unsupported |
| Damper full stroke / available flow at full opening | 60 s / 1000 CFM |

These are teaching choices, not manufacturer settings or standards. Input limits
are centralized in `config.js` and displayed next to fields. Min must not exceed
Max; an active manual SP must be inside that range. An inactive manual SP does not
block automatic Min/Max edits. Reset band must be less
than PB, or zero to disable. Invalid drafts/patches are rejected atomically.

## Room demand and output ownership

The room controller is a conventional signed PI, **not** a Delta CO object.
For error e = room temperature − room SP:

```
P = 100 × e / 2°C
room integral increment = 0.04 × e × dt    [% points, dt in seconds]
demand = limit(P + accumulated integral, −100, +100)
```

Integral accumulation farther into output saturation is prevented; increments
toward recovery are allowed. Initial room integral is zero. It is retained at
setpoint and through live setpoint edits, rather than clearing useful load-balancing
output. There is no separate room deadband, occupancy sequence or heating maximum
airflow stage in this version. The mode label uses a 0.01-point display threshold;
the actual command calculation does not use that display threshold.

With Direct reheat and no airflow override:

- Positive demand: Flow SP = Min + demand/100 × (Max − Min); reheat valve = 0.
- Negative demand: Flow SP = Min; reheat valve = −demand %.
- Zero demand: Flow SP = Min; valve = 0.
- Flow SP Test: use the manually set flow SP, suspend room control and command
  reheat off. The physical room continues responding. This is a manual **setpoint**,
  never a manual damper-position bypass.

Returning from Flow SP Test to Room Auto reinitializes the room integral to zero. FLOW_CO Bias is retained
on mode changes, but a changed SP may produce a proportional command step. No
bumpless-transfer guarantee is made. AHU primary temperature is editable and actual
post-reheat SAT remains a physical model result, including when overridden.

## Optional RT → SAT SP → reheat cascade

Select Room Auto and **Reheat control → Cascade · RT → SAT SP → valve**,
then Apply settings. This option was added on 2026-09-13; Direct remains the default.
The same signed room PI produces demand. Its heating fraction sets:

```
heating fraction = max(0, −demand) / 100
SAT SP = AHU primary temperature
       + heating fraction × (Cascade SAT maximum − AHU primary temperature)
```

During heating, the normal flow target stays at Min and the independent SAT PI
modulates the valve from measured SAT. A manual airflow override changes only the
flow target and still permits cascade heating. During cooling or zero demand,
reheat is off, the target is primary temperature and normal cooling flow modulation
continues. There is no VAV cooling coil. Flow SP Test suspends the outer room loop
and cascade reheat; only explicit Manual SAT control can enable reheat in that mode.

The upper bound limits the target, not physical temperature or valve command.
Changing it while heating retains active SAT integral. Switching to an inactive
loop clears SAT integral; a transfer may produce a command step. Manual SAT target
is stored separately and is not overwritten by reset. The active cascade target
is shown in the schematic and SAT SP trend. The room and SAT loops remain separate
from the physical model. This is a teaching sequence, not a universal VAV sequence.

## Independent airflow and SAT overrides

`flowSource = manual` replaces only Flow SP in Room Auto with `manualFlow`. The
damper remains under FLOW_CO feedback control, and room-driven reheat continues
unless separately overridden. Flow SP Test always uses manual airflow. The input
is disabled while inactive, with activation instructions, and the schematic shows
the applied source and target. Source/setpoint changes require Apply and use the
same queued atomic boundary semantics as other live edits. Reset restores sources
and targets captured at run start; releasing an override restores its automatic owner.

`satSource = setpoint` replaces room-driven valve demand with conventional SAT PI
feedback. It also explicitly enables SAT control in Flow SP Test. `satSP` is an
independent 0–50°C target (default 30°C), not a limit or a Room SP reset schedule.
It can command heat even when the room wants cooling, so it may move the room away
from Room SP. Both overrides can be used together. Room integral increments into
an overridden demand branch are suppressed, while increments toward recovery are
allowed. Direct room control remains unchanged when neither override is active.

The engine supplies SAT measured from current actual flow and the previous applied
valve command. The SAT controller does not call or invert the coil model. Teaching
gains are Kp = 0.05 output percentage points/°C and Ki = 0.02 percentage points/(°C·s),
centralized in `config.js`. This is a conventional error-proportional PI, **not**
the source-based Delta CO integral algorithm. Gains are fixed in this version.
Its integral is explicit, initializes to zero, holds against saturation, accepts
partial increments at limits, and can recover when the error reverses. A live SAT
target change retains that integral. Disabling the loop clears it. Each boundary
performs one controller evaluation at dt=0, avoiding duplicate algebraic SAT feedback.

Both cascade and manual SAT control are inhibited below the 10 CFM teaching minimum or when SAT is
unavailable; its command and integral are zero there. This numerical/model operating
limit is not a manufacturer airflow requirement or validated equipment interlock.
When target SAT is at/below primary air temperature, reheat is off: a heating coil
cannot cool below inlet air temperature. At insufficient coil capacity, valve output
can reach 100% while actual SAT remains below target. The UI reports this condition
instead of forcing SAT to the target. The temperature trend includes the active SAT
setpoint, with gaps when Direct reheat is selected.

## FLOW_CO

The implementation follows the supported source semantics in
[Delta CO conformance](delta-co-conformance.md), with airflow-named inputs. Its
independent implementation avoids changing FCU code. Differential tests compare
the numerical result against the existing FCU CO for equivalent numerical inputs.
This comparison is a software regression check, not independent firmware validation.

```
e = Flow SP − actual flow                    [reverse action]
effective band = PB + full deadband
P correction = limit(100 × e / effective band, −50, +50)  [P or PI]
P correction = 0                            [I only]
CO = limit(P correction + Bias, 0, 100)
```

All types hold their entire previous output inside the inclusive full-width
deadband. An initial in-band evaluation uses configured Bias. Outside deadband,
I/PI adjust Bias at a fixed signed percentage-point rate per minute, independent
of error magnitude. A nonzero full-width reset band tapers this rate by
`min(1, abs(e)/(resetBand/2))`. At output 0/100, integral holds. An increment that
would cross a limit is accepted only up to that limit. Pure I at a limit can require
an Initial Bias change or reset to recover, as in the supported FCU contract.

Retuning preserves accumulated Bias except an explicit changed configured Bias
replaces it. First observation of a changed tuning does not integrate. There is
no derivative formula in the supplied source, so nonzero D is rejected. The UI
offers P, I and PI and shows the disabled D=0 field, without suggesting functional PID.

## Physical model and energy accounting

Initial damper position and flow are zero. Each step moves position toward command,
limited by 100 × dt / full-travel-seconds percentage points. This is a deliberate
actuator rate limit, not correction of an invalid model result.

Available flow is independent of the configured maximum SP. With mean damper
position over the step, target flow is `availableFlow × (position/100)^1.5`.
Actual flow approaches that target using the exact first-order factor
`1 − exp(−dt/2 seconds)`. This assumes fixed supply conditions and does not model
a pressure network, duct geometry, sensor noise, actuator backlash or leakage.
Supply may be insufficient; the controller does not force measured flow to match SP.

Air heat-capacity rate is actual flow × 1206 J/(m³·K). At positive flow, the coil
uses constant 55°C water and UA = 160 W/K × valve fraction:

```
SAT = primaryTemp + (55 − primaryTemp) × (1 − exp(−UA / airCapacityRate))
reheatWatts = airCapacityRate × (SAT − primaryTemp)
airToRoomWatts = airCapacityRate × (SAT − roomTemp)
room temperature increment = (airToRoomWatts + roomHeatLoad) × dt / 3,000,000 J/K
```

The room step uses average flow over the step and the pre-step room temperature;
displayed SAT uses end-of-step actual flow. Reheat energy is included once through
SAT. At exactly zero flow, SAT is undefined and both air heat terms are zero.
No finite SAT is invented for a dry/no-flow sensor. Invalid/nonfinite physical
results stop the engine with an explicit error, retaining the last valid physical state.

## Time, state and history

- Initialization evaluates commands at dt=0 and records time zero without physical
  advancement. Snapshot commands/CO diagnostics refer to the last controller
  evaluation; after a step the physical readings include that step's response.
- Every numerical step is one simulated second, with a final shorter step for
  fractional durations. Playback 1×/10×/60×/300× changes scheduling only.
- Start captures the run-start config. Pause stops time. Resume preserves state.
  Reset restores that config, clears pending edits/history/events and returns READY.
  Display unit and speed are view/playback preferences retained across Reset.
- Apply in READY reinitializes without starting. During a run, a validated complete
  patch waits for the next step. Paused patches wait for Resume. Boundary samples
  preserve before/after changes at the same simulated timestamp, before physical
  advancement. The boundary evaluation uses dt=0 to avoid integrating on retuning.
- Draft changes must be applied or reset before Start/Resume. An invalid patch
  leaves an earlier valid pending patch intact. Initial room temperature and duration
  are locked after Start; other visible scenario/tuning fields support live edits.
- Normal history every 5 simulated seconds, plus initial/final/edit samples. One
  combined chart shows selectable airflow, command/position and temperature tags.
  Left axis: airflow (CFM/L/s); right: temperature (°C); outer right: output (%).
  Percent remains 0–100; airflow and temperature scales follow the visible data.
  All 12 recorded tags are selectable; Min/Max and AHU primary temperature start
  hidden. Dashed setpoint lines use step interpolation. SAT gaps remain gaps.
- Zoom in halves the view, down to the 5-second sample interval. Zoom out doubles
  it; Earlier/Later move by half a window, bounded by the recorded time range.
  Zoomed windows stay fixed while the simulation continues. All time follows the
  full run. Reset and READY reinitialization restore that full view. View/tag/unit
  edits never write simulation state or history. Small screens scroll the graph
  horizontally to preserve readable axes; navigation controls wrap.
- Long views thin plotted points while retaining edit boundaries and missing-value
  transitions; recorded history is not rewritten. Edge neighbors and plot clipping
  preserve step/line continuity when inspecting a partial time window.
- Events record lifecycle, configuration edits and room-mode transitions; latest
  100 retained. Background visibility pauses playback, as does a wall-clock gap
  over 30 seconds. Per-frame work is bounded; leftover simulated time is retained.

## Verification and limits

Run `node tests/vav-tests.cjs` for 60 VAV numerical and actual-page DOM checks:
CO source equivalence, tuning/limits, mapping, thermal energy, default flow response,
capacity shortfall, zero flow, 24-hour room stability, lifecycle/atomic edits,
display-unit round trips, playback speeds, chart generation and hidden-page pause.
Override tests cover independent output ownership, SAT feedback convergence after
flow changes, unreachable/below-inlet targets, zero/low flow, anti-windup recovery,
return to automatic control, queued paused edits, reset and UI activation states.
Trend checks cover frozen vs following windows, zoom/pan bounds, tag visibility,
empty-state recovery, unit changes, interpolation/gaps and simulation independence.
Cascade checks cover demand-to-target mapping and bounds, cooling exclusion,
independent manual airflow, Flow SP Test suspension, no-flow inhibition, paused
application/reset, and 24-hour heating/cooling convergence of room and SAT targets.
The 300→600 CFM test reaches the ±2 CFM default deadband after each 900-second
settling interval. This is a result for the configured educational model only.

FCU (91) and engineering/calculator (71) regression tests also pass. Actual browser
layout, native controls and browser console verification remain pending: the tool's
local file-URL security restriction previously blocked access. Node DOM checks do
not resolve that limitation or establish physical validation.
