# DSH 嵌入式硬件沙盒插件 · 项目定位(锁定版) > **基线**:`DSH嵌入式硬件沙盒插件-设计文档-v4.1(1).md`(终版,含 §4.2 3D 场景引擎) > **依据**:`00-宿主API核实.md`(第 0 周交付物,实测运行时 + 源码核对) > **状态**:待确认 → 确认后即为施工基线 --- ## 1. 一句话定位 > **一个项目级硬件在环(HIL)仿真沙盒**:用户在 3D 场景里拖拽真实硬件、连线、热插拔;DS 在同一场景里实时感知拓扑并能反向操作;**用户的真实项目代码,语义等价地跑在虚拟硬件上**。 差异化不在「3D 看硬件」(`dsh-3d-model-viewer` 已占位),而在**「代码真的跑起来了」**。 --- ## 2. 双主线(保留,不改) | | 主线 A:人机协作感知(**地基**) | 主线 B:项目级硬件仿真(**壁垒**) | |---|---|---| | 定义 | 用户和 DS 在同一 3D 场景里协作并互相感知 | 项目代码语义等价地跑在虚拟硬件上 | | 组成 | 3D 场景 + 事件桥 + 状态 SSOT | 虚拟时钟 + 行为模型 + Shim/IPC | | 独立价值 | ✅ 成立 | ✅ 成立(但依赖 A 的状态) | | 卡住的后果 | 项目不成立 | A 仍可交付,失去壁垒 | **保留理由(复核后仍然成立)**:砍掉 A 就退化成「3D 展示 + 报价」,与既有插件重叠;砍掉 B 就只剩一个能连线的沙盒,无壁垒;两条都砍则项目不成立。 --- ## 3. 对设计文档的三处实质性修正 > 这三处不是文字润色,是**架构层面必须改**的地方。全部由实测得出。 ### 修正 1 ★:不是两个 realm,是**三个** 文档 §1.0 / §4.1 的世界观是「DSH 宿主(JS) ↔ 项目进程(Python)」。**实际是三个执行域**: ```text ① 宿主 Node 进程 插件 host 半 ├─ AssemblyState(SSOT) ← 唯一真相 ├─ VirtualClock + 设备行为模型 ├─ BridgeServer(TCP 服务端) └─ 工具注册(DS 可见) ▲ ▲ │ ctx.webServer 路由 │ TCP + token(JSON-RPC/NDJSON) │ (HTTP/WS) │ ② 浏览器 ③ 项目进程 Python 插件 client 半 shim(仅代理,无硬件语义) Three.js 场景 = ① 的投影 用户项目业务代码 交互 / 拾取 / renderViews ``` **后果**: - 文档 §1.0 表格里「3D 联动:**直接**(同一进程)」这一格是**错的**——在真实宿主上是跨域 RPC。 - **但 SSOT 唯一性不但保住,而且更硬**:三个域里只有 ① 持有真相,② 是投影、③ 是代理。 - **SSOT 必须放 ①(宿主)而非 ②(浏览器)**,因为:BridgeServer 在 ①;面板关掉/切走后仿真必须继续跑;DS 的工具调用发生在 ①。 - 这一条其实与文档 §4.2.4 的原则「**场景是状态的投影,不是状态的副本**」完全一致,只是文档没把域边界点出来。 ### 修正 2 ★:`tick()` 与 3D 渲染必须解耦 文档 §5.1 闭环把第 ⑦ 步写成「3D 场景实时渲染 [JS 内核]」,暗示 tick 直接驱动渲染。 **这在真实宿主上会打死链路**:`advance(1.0)` 在 `step=1ms` 下是 **1000 次 tick**。若每次 tick 都推一帧场景数据过浏览器 RPC,一次 `sleep(1)` 就是 1000 条消息 × N 设备。 **改法(写死为铁律)**: > **虚拟时钟 tick 只写 SSOT;场景按显示节奏采样 SSOT(或按事件节流推送),绝不逐 tick 推送。** 两种模式分别处理: - `accelerated`:tick 全速跑完,只推**首末状态 + 关键事件**(供回放/报告) - `realtime`:tick 按真实节拍推进,场景按 `requestAnimationFrame`(§4.2.1 已要求按需渲染)采样 ### 修正 3 ★:`advance()` 的时间片循环会**饿死宿主自身** 文档 §7.4 的 T4 只讨论「精度不够」。**真正的风险更靠前**: ```ts advance(dt) { const target = this.now + dt while (this.now < target) { // ← 同步忙循环 this.now += this.step for (const dev of this.devices) dev.tick(this.step) } } ``` `advance(1.0)` = 1000 次同步迭代。这个循环跑在**宿主 Node 的事件循环**上——期间 DSH 自身(会话、UI 通信、其他插件)**全部停摆**。多设备或大步长下会明显卡顿甚至误判为死机。 **改法(写死为硬要求)**: > **`advance()` 必须在时间片循环中周期性 `await` 让出事件循环**(按墙钟预算),且预算可配;`accelerated` 模式必须跳过空闲区间,不得退化为暴力步进。 > **落地修正(步骤①实测)**:让出预算不是「每 N 片」而是**按墙钟毫秒**—— > 这样「算得快就不让出」(T5 不受损)、「算得久就让出」(T6 得到保证)。 > 跳空也不是「事件优先队列」,而是设备以 `nextEventIn()` **声明可跳空**; > **只要有一台设备返回 `null`(需要每片)就绝不跳空**,因为积分型模型(BME280 漂移) > 的结果与片数相关,跳空会改变结果。 §7.4 的 T1–T5 判据**保留**,但需**新增 T6**: | 判据 | 通过标准 | |---|---| | **T6(新增)** | 单次 `advance(10.0)`(step=1ms,1 台设备)期间,宿主事件循环阻塞 **p95 < 5ms、max < 50ms**;即宿主心跳在仿真中不掉线 | > **★ 判据为何盯分位数而非 max(已实测,详见 `02-步骤①-虚拟时钟内核.md`)**: > 关闭让出时阻塞随总耗时线性增长(**1022ms 的推进就阻塞 1022ms**); > 打开让出后 **p50 精确跟随让出预算**,但 **max 有一个 ~16ms 的环境地板** > (Node 定时器/GC 抖动),任何预算都压不下去 —— **不是时钟引入的阻塞**。 > 只盯 max 会把环境抖动误读成时钟缺陷,并让 CI 偶发红灯,正是本文档 §0.4 > 批评过的「用概率性行为验证确定性判据」。 > > **默认让出预算取 1ms**:4ms 预算的 p95 已达 9.18ms、p99 达 15ms,几乎吃掉一个 60fps 帧; > 1ms 预算的 p95 只有 1.19ms,代价仅 ~3% 总耗时。 --- ## 4. 其余修正(工程级,不改变架构) | # | 文档处 | 实测结论 | 处置 | |---|---|---|---| | 1 | §12.1 通道「stdio 或 unix domain socket」 | Windows 无原生 unix socket;且 stdio 在不同沙箱模式下行为有差异 | **默认 TCP 回环 + 一次性 token**。顺带白拿 §1.2 形态二(attach 独立启动的进程) | | 2 | §0.3 `.dsh-plugin` 被删先例 | 现行 API 是 Cordis 服务化,`.dsh-plugin` 不存在;且**可运行时自省** | 适配层**保留**,理由换成「脱离宿主单测」+「能力探测降级」 | | 3 | §9.3 风险「时钟协调无先例」 | 精度不是主要风险(tick 在 JS 侧,不受 Python 定时器影响——文档这点判断正确) | 风险重排,见 §5 | | 4 | §1.1 通道 stdio | DSH 自己的 Python SDK 就是 **NDJSON over stdio** | **对齐本仓库协议风格**:换行分隔 JSON-RPC(但承载走 TCP) | | 5 | §3.3 通用 SDK | 仍推荐长期走 `hw_sdk`,与 shim 并存 | 不变 | > **注意别搞混**:`D:\dsh\deepseek-harness\python\sdk` 是「**Python 驱动 DSH**」,方向与本项目(**DSH 驱动用户的 Python 项目**)**相反**,不能复用。它的价值只在于:证明了「宿主 ↔ 外部进程走 NDJSON-RPC」是本仓库的既定做法。 --- ## 5. 风险重排(这是定位的核心产出) 文档说「唯一未知数是时钟协调」。**修正后**: | 风险 | 文档评级 | 复核后 | 依据 | |---|---|---|---| | **A:3D 场景做不做得出来** | 隐含高风险 | 🟢 **≈0** | slot `main` 存在;three.js 可内联打包;React 已注入 | | **B1:时间片循环饿死宿主** | ❌ 文档未识别 | 🟢 **已缓解**(步骤①交付) | 让出机制已实现并入判据 T6;实测 p95 **1.19ms**。**判据须盯分位数**,见 `02-步骤①-虚拟时钟内核.md` | | **B2:IPC 通道** | 🟡 「形态一/二」 | 🟡 **中** | Windows 无 unix socket;改 TCP+token 后可控 | | **B3:拦截完整性(P4)** | 🟡 | 🟡 **中** | 需专门覆盖测试;`sys.modules` 预置 + 编译期报错 | | **B4:时钟精度(T4)** | 🔴 「唯一风险」 | 🟢 **低** | 权威侧在 JS,不受 Python 定时器影响;**T1/T2/T3/T5 已实测全绿** | | **B5:适配成本(P5)** | 🟡 | 🟡 **中** | 收窄到「已预置型号 + 已知库」 | | **B6:运行时依赖链接** | ❌ 文档未识别 | 🟡 **已定位并修正** | checkout `vendor/*` 是源码态无 JS;`schemastery` 是唯一值导入,须链 profile 已构建副本(`00-宿主API核实.md` §4.2) | **结论**:主线的**技术确定性大幅高于文档预期**(A 几乎零风险、外部进程能力已具备、B1 已缓解),但**B 的真实瓶颈落在「拦截完整(P4)」与「IPC 通道健壮性」两条**上。 --- ## 6. 交付边界(什么算成) ### 本期做(MVP) - **A**:`main` 全屏 Three.js 场景 + 拖拽/磁吸/连线 + `AssemblyState` SSOT + 事件桥 + L1 拓扑校验 - **B**:虚拟时钟 + 确定性设备 + BME280(寄存器级)+ BridgeServer + Python shim + `test-project` - **工具**:`hw_run_simulation` / `hw_inject_fault` / `hw_get_sim_log` + 装配类工具 - **验收**:§0.4 P1–P6 全绿 + §7.4 T1–T6 全绿 ### 本期不做(明确划出) - ❌ QEMU / 真实固件(文档已划出) - ❌ BOM 估值 —— **条件性并行线,冻结**。需先过 30 样本准确率验证(§9.4),未验证前不进排期 - ❌ 视觉仲裁 —— 并行线,**规格保留、实现后置**(依赖 `vision_analyze` 工具存在性,另需核实) - ❌ Arduino / ESP32、SPI / UART、多语言 SDK(M2/M3) - ⏳ **远程第三方建模库(SnapEDA / Ultra Librarian / 立创商城)** —— **方向已确认、优先级不低**,但按 §8 拆成两个独立阶段:**语义优先、几何后置**。下一步是**可行性验证**(给定一个 LCSC 料号,能否产出可用的 `HardwareModel` 记录),验证通过才排期。**自建 STEP/IGES 解析器不在计划内**(理由见 §8)。 --- ## 7. 施工顺序(照 §12.3,按实测调整) ```text ① 虚拟时钟 + 确定性设备 纯 JS,单测跑通 T1/T2/T6 ✅ 已完成 ② BridgeServer(TCP) + bridge_client.py 打通 IPC,T3 ← 进行中(协议已冻结) ③ BME280 寄存器级 替换确定性设备 ④ test-project 适配 P1 / P5 ⑤ SimEngine + 工具注册 P6(DS 可见) ⑥ HostAdapter 接真实 DSH A + B 合流,P2/P3/P4 🔄 SSOT + 前后端桥已完成并实测 ⑦ client 半:Three.js 场景 主线 A 合流 🔄 并行会话推进,界面已可见 ``` > ①–② **不依赖任何 UI 工作**,是最高性价比的起点;**T6 必须在 ① 就立起来**(修正 3 是新增最高风险)。 --- ## 8. 远程模型库与 STEP 管线(方向已定,但**不进 MVP**) > 来源:并行会话转述的用户方向。**尚未写进设计文档**,因此只作为已记录的方向,不改变本期范围。 ### 结论:方向**已确认**(用户 2026-10-07 确认,优先级不低),但**顺序要反过来** > 用户确认:「它确实是已定方向,且优先级不低」。因此下面不是"做不做",是"先做哪一半"。 #### 拆成两个独立阶段——成本与价值差很远 | | 阶段一:几何 | 阶段二:语义 | |---|---|---| | 产出 | glTF,组件**看起来对** | `schema/` 端口,组件**能用** | | 输入 | 厂商 3D 模型(STEP/IGES) | 厂商符号/封装数据(**引脚名 + 编号**) | | 依赖 | 需要 CAD 解析(重) | 纯结构化数据解析(轻) | | 解锁什么 | 视觉正确 | **连线校验、地址冲突、仿真** | | 与阶段二的关系 | **互不依赖** | **互不依赖** | **只做阶段一 ⇒ 得到「好看但不能连」的组件**。而阶段二正是设计文档 §6.6 点名的「核心壁垒」,且不依赖任何 CAD 技术。 ⇒ **建议:阶段二优先,阶段一后置。** 这与"优先级不低"不冲突——它只是把同样的优先级投在能解锁功能的那一半上。 #### 为什么仍**不**建议自建 STEP 解析器 1. **转换买不到功能。** 理由同上表。 2. **不能靠视觉反推端口。** 设计文档 v2 已明确**废除**「B 通道视觉标注」(从渲染图反推接口语义),原话是「有精确坐标不用却从渲染图反推 = 自我制造误差」。所以"转完自动认出端口"这条捷径**在设计上已被否掉**,不是没做。 3. **不划算。** Node 侧只能走 `opencascade.js`(WASM 约 30MB)。而在拿到引脚元数据之前,几何本来就可以先用占位盒——**现在正是这么做的**(4 个预置型号都是盒体)。 #### ★ 关键发现:目标厂商本来就有结构化元数据 API 2026-10-07 查证:**嘉立创EDA(EasyEDA)专业版有成体系的公开扩展 API**,且 API 里直接就有我们要的图元类别: - `SCH_PrimitivePin` / `ISCH_PrimitiveComponentPin` / `ISCH_PrimitivePin` —— **引脚** - `PCB_PrimitiveComponentPad` / `IPCB_PrimitiveComponentPad` —— **焊盘** - `SYS_FormatConversion` —— 格式转换 - `SCH_ManufactureData` / `PCB_ManufactureData` —— BOM / 制造数据 - 开发指南另含「**命令行客户端**」(headless 化的希望所在) **而这个厂商正是设计文档 §6.5 已经列为对接目标的那家**(「DigiKey、Mouser、LCSC、立创商城」)。也就是说:**BOM 的供应商适配层与模型库的语义来源,很可能是同一个集成**——不是两笔投入。 **兜底路径**:KiCad 的符号/封装格式是**开放且有文档**的(引脚由「图形表示 + 名字 + 编号」三元组定义),可完全离线使用,无需任何厂商关系。 #### 下一步:先做**可行性验证**(spike),不是开工建管线 **验证目标**:给定一个 LCSC 料号,产出**一条可用的 `HardwareModel` 记录**(端口名 + 编号 + 包围盒尺寸),且过程可重复、可脚本化。 **两条候选路径,先各验一条**: - **路径 A**:嘉立创EDA/EasyEDA Pro 的 CLI / 扩展 API → 取符号与封装 → 抽引脚名与编号 → 生成 `schema/` 条目 - **路径 B**:KiCad 官方符号库(开放格式、离线)→ 同样抽取 **明确不在 spike 范围内**:STEP/IGES 解析、glTF 几何转换。几何继续用占位盒。 **准入判据**:能对**一个真实型号**端到端跑通并产出可用 schema,才谈排期。 #### 架构上不是新东西 ② 正在建的「宿主 spawn Python 进程 + IPC」这套基础设施**可以直接复用**给厂商数据抓取/转换 worker。所以它不需要新架构,只是**多一个 worker 进程**。 #### 仍然建议:把这条正式入档 它源自用户口述 + 并行会话转述,**v4.1 设计文档里没有**。建议正式写进文档并给一个排期位置(M2 / M3),否则它会以「大家都知道要做」的形式长期悬着,反过来干扰范围控制(§9.2)。