# 模拟引擎与业务状态机模式

> 返回 [SKILL.md](../../SKILL.md)。下文模式提炼自官方参考实现（能源站；罐区+联锁演练+隐患/作业票）。

## 1. 分层：纯函数内核 + runtime 接线

```
points.ts     测点/设备/映射 单一事实源（initialPoints() 同时喂 connectionData 初始行与 sim 初值）
simulator.ts  物理模型：createSimState() + tickSim(state, dtSec, rng) → AlarmEvent[]   ←纯函数，可单测
alarms.ts     告警状态机：applyAlarmEvents / ackAlarm / alarmRows / alarmStats / alarmTrendRows ←纯函数
<domain>.ts   领域状态机（隐患生命周期 / 作业票流转 / 演练脚本）                        ←纯函数
runtime.ts    接线：setInterval(tick) → ctx.setData 各表；返回控制 API 给页面按钮
```

**纯逻辑文件禁止 import `vjvpsdk` 的运行时值**（`import type` 可以）——vitest 不依赖 SDK 构建产物。

## 2. 随机性：注入 rng，同种子可复现

```ts
export type Rng = () => number
export function mulberry32(seed: number): Rng { /* 标准 mulberry32 实现 */ }
// tick 内全部随机来自参数 rng；测试用恒值 rng（0.5=无事件无噪声）或首拍小值 rng 定向注入故障
```

## 3. 模拟量收敛（让数据"像真的"）

- `approach(cur, target, rate)`：模拟量向"运行组合决定的目标值"收敛（流量∝运行泵数、温度∝运行冷机、功率=组合求和…），叠加 `noise(amp)`。
- `clamp` 限界；输出用 `round1/round2` 收小数。
- 缓慢往复量（液位）用方向变量 + 边界翻转 + 小概率随机换向。
- 事件注入：运行设备小概率故障（`faultLeft` 计拍自愈）；演练类（入侵/泄漏）提供 `triggerXxx(state)` 手动入口供演练按钮调用。

## 4. 告警规则：边沿触发（raise/resolve 各一次）

```ts
// state.activeRules: Record<string, boolean> 去抖
const hit = rule.hit(state)
if (hit && !state.activeRules[key]) { state.activeRules[key] = true; events.push({ type: 'raise', ruleKey: key, level, device, content }) }
else if (!hit && state.activeRules[key]) { delete state.activeRules[key]; events.push({ type: 'resolve', ruleKey: key }) }
```

告警状态机（alarms.ts）职责：同 ruleKey 活动期去重入列、resolve 置「已恢复」保留记录、人工 `ack`、容量上限丢最旧已恢复；派生视图 `alarmRows(filter)`/`alarmStatRows`/`alarmTrendRows(now)` 直接是表行形状。

## 5. 控制闭环与联锁

- 控制 API：`setDevice(state, id, run)` 改状态 → 下一拍模拟量随之变化 → 组态/三维/图表全联动。runtime 里控制后立即补推一次（按钮反馈即时）。
- **联锁/演练状态机**：多阶段脚本（如 泄漏演练：normal → leaking(气体爬升) → 越限自动联锁(关阀停泵 + raise 严重告警 + 副作用回调如自动开应急窗) → recovering → normal）。阶段存在 state 上，tick 里推进；对外暴露 `startDrill()`。副作用（开窗/切页）不写在纯函数里——tick 返回事件，runtime 侧解释执行。

## 6. 数据建模五件套（connections 套路）

| 表 | 用途 | 形状 |
|---|---|---|
| 测点长表 `tbl_pts` | 驱动组态/三维/地图元素（`point-key-field`/`point-value-field` 绑定，元素填 `data-key`） | 两列：键/值（值可数值或 JSON 串——热力/人员定位整批刷新） |
| 宽表快照 `tbl_rt` | 2D 指标文本 `value-field` 绑定（取首行） | 单行多列 |
| 台账表 | 表格/列表（设备/企业/风险点/作业票） | 多行业务列（表格运行时给每行注入 `_index`=1 基行号，作 keyField/序号列数据源） |
| 告警四表 | 告警表/等级分布/时段趋势/KPI 未处置数 | `tbl_alarm`+`tbl_alarm_stat`+`tbl_alarm_trend`（+并入 KPI） |
| 历史滚动 `tbl_history` | 趋势图/趋势窗口 | 时间列 + N 指标列，固定窗口（如 48 行）滚动 |

派生视图函数（`toPointRows/toRtRow/toKpiRow/toHistoryRow/...`）**输出列名必须与 connections 字段定义一致**——写一致性单测锁住（见 §8）。

## 7. runtime 接线与推数节流

- 主循环 `setInterval(doTick, 2000)`；测点/快照/KPI 每拍推；**告警/台账只在有事件时推**；历史/慢表每 3 拍推一次并裁窗口。
- 控制 API 对象（含 `setDevice/ack/setFilter/startDrill/onXxxChange` 订阅）由 `startSim(ctx)` 返回，build.ts 传给各页 builder。
- 视图筛选（告警等级/台账关键字）：筛选状态存 runtime，改变即重推该表——"代码筛选 + 全量重推"比在组件里做过滤简单可靠。

## 8. 单测配方（vitest）

```
[ ] 平稳运行：N 拍后模拟量在合理区间（恒值 rng）
[ ] 可复现：同种子两次跑 toPointRows 深等
[ ] 控制闭环：停设备 → 量值衰减 → raise 目标告警；恢复 → resolve
[ ] 事件注入：定向 rng 首拍注入故障/入侵 → 严重告警；复位/超时 → resolve
[ ] 状态机：状态流转合法（隐患 待整改→整改中→已闭环；作业票 待审批→作业中→已完工；非法流转拒绝）
[ ] 派生视图形状：toXxxRow 列名 ⊆ connections 字段（数据层一致性）
[ ] 场景一致性：pieces 引用的 data-key ⊆ initialPoints()；连线端点图元存在；联动映射表目标 uid 存在
```
