# W-Lab 可视化实验工作室:最终项目方案 > 日期:2026-10-15。本文记录架构审查结论。随后已建立独立 W-Lab 项目。原二维评审页被用户否定,0.2.0 已改为 Three.js 三维器材演示(单摆、凸透镜、清水移液)。画面与读数来自同一轻量教学模型;大规模计算、设备、AI4S 持久化和现有渲染器迁移仍按下文阶段推进。下文包划分和阶段是方案,并非均已实现。 > 标注约定:**[证据]** 指仓库文件或官方文档;**[判断]** 指设计建议;**[未决]** 指需要输入或决策的事项。 > 项目正式命名为 W-Lab(可视化实验室),独立目录为 30-39-w-lab;后续模块包结构仍是实施方案。 ## 1. 结论与两个问题的直接回答 **一句话结论:** 新建 W-Lab 仓库,产品核心是渲染和过程的保真。实验视口是主工作区,流程图、参数和元数据检查器都是辅助界面。共享可视化代码有计划地迁入 W-Lab,之后 WaveWorld 和 AI4S 使用同一份导出实现。W-Sim 只负责计算和契约,不依赖任何前端产品。 **问:W-Sim 是专门做超大型计算的吗?** 从架构定位看,是的。它是大规模、高性能数值计算平面,提供契约、kit、runtime 和求解器。但这只是职责定位,不代表现状已被验证。 - **[证据]** `packages/solvers-lbm` 公开导出 `.`、`./d3q19`、`./mac3d`。README 和文档可能比源码滞后。 - **[证据]** 最新的包元数据成熟度是 `smoke`。 - **[判断]** 本轮没有运行任何求解。因此不宣称它已具备超大规模容量,也不宣称各求解器已达到工程精度。 **问:AI4S 里的实验记录和配方都属于元数据吗?** 不全是。需要按语义分开: - **元数据 / 配置:** 配方和协议版本、计划参数、单位、仪器配置、样品谱系、文件索引。 - 可执行配方有操作语义。它必须先经过校验、带上版本,才能驱动执行,不能当作装饰性元数据。 - **原始与结果数据 / 过程证据:** 原始图像、光谱、曲线、传感器实测值、实际过程事件、设备回执、数值场。 - **实验记录包:** 两者混合,不能笼统地说“全是元数据”。 - **[证据]** AI4S 当前存的是索引和关联关系,例如 `lab_projects.payload` 中带 revision 的 JSON,以及文件索引。它没有托管全部原始文件本体。本方案不虚构已经存在的原始数据存储。 ## 2. 三个职责平面 | 平面 | 负责 | 不负责 | |---|---|---| | **AI4S** | 身份、workspace、研究上下文、元数据和记录管理、设备授权与预约、原始文件索引 | 渲染实现、数值求解 | | **W-Sim** | 数值作业(`JobSpecV1` / `JobResultV1`)、路径实验计算载荷、结果和 provenance | 前端、实验室编排 | | **W-Lab** | 场景运行时、过程回放、交互装配、可视化适配器、流程模型 | 认证、持久化后端、求解 | **数据流 [判断]:** 经过校验、带版本的实验规格 → 在支持的范围内提交 W-Sim 作业或已授权的设备任务,也可以人工执行 → 产生带时间戳的状态、结果和制品引用 → 经可视化适配器转换 → 进行保真渲染和回放。 整条链路上,计划、示意动画、模型预测、模拟结果和实测记录始终分开标记。 ## 3. 现状证据(精简) | 位置 | 事实 | 处置 | |---|---|---| | AI4S `src/graphics/SceneHost.tsx` | R3F `createRoot` 宿主,带可见性暂停和 `PerformanceMonitor`,与 leva、水体材质耦合 | P1 迁移候选(场景、材质、相机) | | AI4S `fabrication.js`、`pipetting-protocol.js`、`coating-annealing.js`、`later-process.js` | 12 个阶段按索引分派;参考时序写死,如 `progress(elapsed,12,4)`、`elapsed>=4&&elapsed<519` | 迁移后标为**示意 / 参考时序**,不作实测 | | AI4S `lab-recipe.js` | `plannedTargets:null`;`validatedReproducibility:false`;`stepSkipped` 写死索引 7/10;证据关联规则严格 | 是追溯导出,不是可执行 SOP;关联规则保留 | | AI4S `robot-rinse-physics.js` / worker | MuJoCo 在 Worker 中运行;驱动目标按 `if(stage===N)` 分支 | 属演示物理;保留 BSD/MuJoCo 许可证 | | AI4S `rinse-fluid-worker.js` | 演示级 PBF + 浅水方程 | 只能作示意,**不是 W-Sim 验证过的计算** | | AI4S `robot-process-layout.js` | 坐标是参考图像素,不是米制 | 显示“未标定” | | AI4S `AGENTS.md` | 构建只依赖本项目文件;禁止**编写**单元和集成测试 | 已有检查和浏览器走查可以照常运行 | | W-Sim `contracts/path-experiment.ts` | `PathExperimentSpecV1`:`experimentId`、`materials`、`model{id,version}`、`analysis` | 只用于受支持路径模型的计算载荷 | | W-Sim `contracts/job-spec.ts` | `JobSpecV1`、`JobResultV1`、`backend.provenance` | 原样使用,不改 | | W-Sim `kit/surfaces.ts`;`check-boundary.mjs` | kit 不含 three/WebGPU;禁止 import WaveWorld,禁止深层 import | W-Sim 保持无前端依赖 | | W-Sim `package.json` | `private:true`,`0.1.0`;观察到的消费方都用 `file:` 链接 | `private` 只能阻止误发布,**不能证明从未分发**;独立版本化分发未经验证 | | WaveWorld `apps/lab/src/lib/cfd/volume-renderer.ts`、`flow-snapshot.ts` | 依赖 `GPUDevice`、按 `displayStride` 布局的缓冲;快照依赖 `@w-sim/solvers-lbm/d3q19` | 当前所有者是 WaveWorld,计划迁入 W-Lab | | WaveWorld `src/lib/sim/browser-agent.ts` | 用 `../../../apps/lab/...` 跨应用相对路径 import | 反例,新结构禁止这种写法 | ## 4. 产品形态:渲染优先 **主视口(约占 70% 面积):** - 显示器材和样品的几何、操作过程、状态变化和科学场。 - 直接复用 AI4S 已有的场景、材质和相机运镜,不重新设计科学图形。 **底部过程时间轴:** - 用分段带区分各类真值来源,同时用纹理和文字标签表达,不只靠颜色: | 来源 | 视觉 | |---|---| | 计划 | 虚线 | | 示意 | 斜线 | | 模型 | 细纹 | | 模拟 | 点纹 + job ID | | 实测 | 实心 + 来源 | | 缺失 | 灰色斜纹 | - 拖动条同时显示物理时间、采集时间戳和回放倍速。 **辅助界面:** - 左侧是可折叠的步骤栏,以序列视图为默认。 - 积木图是次级模式。 - 右侧是证据和参数检查器,显示单位、来源、`unresolved` 徽标和制品哈希。 **风格:** - 沿用 AI4S 令牌:`#233b3a`、`#176b63`、`#f4f7f8`、`#dfe7e8`。 - 外框安静克制,把视觉重心留给视口。 - 不做通用后台的卡片墙,也不放假实时数据。 **无障碍:** - 视口状态同步为文字描述;校验错误写入 `aria-live`。 - 尊重 `prefers-reduced-motion`:过程动画是内容,不自动播放,改为逐步播放。 - 移动端一次只显示一个视口,检查器改为底部抽屉。 ## 5. 渲染管线与真实性规则 **管线:** 1. 科学数据和配置的引用:Spec、Run、`ArtifactRef`。 2. 带时间戳的状态流:引擎、设备、人工记录各自成流。 3. 有界帧适配器:滑动窗口、降采样或 LOD,并记录每一帧对应的源索引。 4. 场景层:几何、材质、相机,加上场渲染器(three/R3F,或 WebGPU 体渲染)。 5. 时间轴与 provenance 面板。 **规则 [判断]:** - **全分辨率结果不进 React 状态。** 只以 `ArtifactRef`(URI + sha256)引用。逐帧数据放在 ref 或 GPU 缓冲中,不在 `useFrame` 里调用 setState。 - **三种时钟分开:** 物理时间、采集时间戳、回放时间。它们之间的映射显式可见。 - **不伪造瞬态过程:** - 两个实测点之间默认保持阶梯,或标注“插值”。 - 只有模型或模拟实际给出了中间态,才能显示连续演化,并且要标出对应的真值来源。 - **单位与坐标:** 单位未知时显示“单位未知”。未标定的机器人坐标显示“未标定 / 示意”。 - **显示变换要可追溯:** 夸张、色标、LOD、采样都只是呈现变换,可以追溯回源值,不改变科学数值。不从外观推导科学结论。 - **静止态和缺失数据要显式呈现:** 没有数据时显示静止态或缺失带,不用占位动画冒充进度,也不渲染“已完成”。 - **WebGPU 状态:** 检测中 / 可用 / 不支持 / 无适配器 / 设备丢失 / 失败。每种状态都有文案和降级路径:静态切片,或远程作业。 **目标与约束:** “完美展示真实过程”是产品目标。实现时,能展示的是已有的真实状态和已记录的模拟结果;数据来源、单位、帧数、标定状态和局限都写在界面上。 ## 6. 数据模型与执行语义 ```ts type Ref = { id: string; version: string; sha256: string } type Truth = 'plan'|'illustrative'|'model'|'simulated'|'measured'|'manual-record' type ArtifactRef = { uri: string; sha256: string; mediaType: string } type Clock = { physical?: { t: number; unit: 's' }; acquiredAt?: string } // 回放时间不存储 type Qty = { v: number|string; unit: string | 'unknown' } type StateSample = { stream: string; truth: Truth; clock: Clock; values: Record; frame?: { id: string; calibrated: boolean }; provenance: { source: 'person'|'device'|'w-sim-job'|'model'; ref?: string; artifact?: ArtifactRef } } type FrameView = { stream: string; sourceRange: [number, number]; stride: number; transform?: string } type PortDef = { key: string; type: 'sample'|'material'|'quantity'|'series'|'file'; access: 'read'|'mutate'|'consume'; unit?: string } type ParamDef = { key: string; unit?: string; status: 'defined'|'unresolved'; source?: SourceRef } type SourceRef = { kind: 'textbook'|'paper'|'sop'|'internal'; citation: string; status: 'unresolved'|'cited'|'reviewed' } interface BlockDefinition { ref: Ref; inputs: PortDef[]; outputs: PortDef[]; params: ParamDef[]; requirements: { resource: string; exclusive: boolean }[]; modes: { manual?: {}; simulation?: { jobKind: JobSpecV1['kind']; mappingRef: Ref }; device?: { capability: string } }; scene?: Ref; sources: SourceRef[] } interface BlockInstance { id: string; block: Ref; mode: 'manual'|'simulation'|'device'; params: Record; bindings: Record } interface WorkflowDefinition { ref: Ref; nodes: BlockInstance[]; edges: { from: [string, string]; to: [string, string] }[]; layout: Record; sources: SourceRef[] } interface Run { id: string; workflow: Ref; snapshot: WorkflowDefinition; scope: { workspaceId: string; projectId: string; experimentId?: string }; parentRunId?: string } type RunEvent = { runId: string; seq: number; nodeId?: string } & ( | { type: 'node.state'; state: 'ready'|'blocked'|'running'|'succeeded'|'failed'|'cancelled'|'uncertain' } | { type: 'state'; sample: StateSample } | { type: 'sample'; op: 'create'|'split'|'consume'; ids: string[]; parents?: string[] } | { type: 'job.linked'; jobId: string } ) ``` **执行语义 [判断]:** - 首版支持显式依赖和有界步骤;条件分支保存决策,重复必须设置次数上限。无依赖且资源相容的步骤才并行。研究问题、假设和结论可关联人工判断;下一轮建立新 Run 并关联父 Run,不使用无界循环。 - 条件采用受限的结构化比较,不执行用户或 AI 提供的任意脚本。 - 服务端逐请求校验所选 workspace/project/角色;宿主传入 scope 仅作界面上下文,不构成授权凭据。 - **样品访问:** - 非破坏性观测(`read`)可以引用同一样品,不需要拆分;只有物理操作和设备资源相容时才允许并行。 - `mutate` 会改变样品状态,需要串行。 - `consume` 消耗样品,之后样品终止。 - 只有真实发生的物理分割才产生 `split` 和子样品 ID。 - 资源竞争(`exclusive` 设备)是另一套机制:冲突时节点进入 `blocked`,由 AI4S 预约状态裁决。 - **只追加、不改写:** Run 固定 workflow 和各积木的 `Ref`;积木出新版本时只给迁移建议,不改写历史。 - **设备回执丢失:** 记为 `uncertain`,需人工核对,禁止自动重试。 - **模块缺失:** 显示只读占位,不静默替换。 - **`PathExperimentSpecV1` 的适用范围:** - 只有当某个节点或子流程能映射到受支持的路径模型作业时,才生成它。 - 它不是通用的实验室、人工或设备编排器。 - 实验室工作流模型与它处在不同层级,二者不竞争。 - `JobSpecV1` / `JobResultV1` 保持不变。 ## 7. 仓库结构、归属与制品交付 ``` w-lab/ packages/visual/ @w-lab/visual:场景/材质/相机、流场体渲染、快照解码、布局契约文档 packages/studio/ @w-lab/studio:src/core(纯 TS:模型、归约、校验、适配器接口)+ src/ui apps/demo/ 唯一的独立外壳(本地持久化适配器) ``` **依赖方向(无环):** ```mermaid graph LR WSIM["W-Sim 公开导出制品"] --> V["@w-lab/visual"] V --> S["@w-lab/studio"] V --> WW[WaveWorld] S --> AI[AI4S] S --> D[apps/demo] ``` 箭头方向为“被依赖 → 依赖者”。W-Sim 不依赖 W-Lab,也不依赖任何产品。 **归属决策(计划中,尚未执行):** - `volume-renderer.ts`、`flow-snapshot.ts` 以及 AI4S 场景中被选定的部分,**迁移**到 `@w-lab/visual`。 - 这是一次所有权转移,不是复制:消费方切换到新制品的同一批变更里,删除本地旧实现。 - 产品外壳(如 `wind-tunnel-canvas`、Zustand store、`hq-lab-adapter`)不迁移。 - 迁移开始之前,AI4S 和 WaveWorld 的现有代码保持不动。 - 迁移按用户授权范围分批推进;涉及外部契约时,更新契约和消费者说明,并逐项目验证。 **制品交付:** - `@w-lab/*` 构建为 `dist` + `.d.ts`,打包成带版本号和 sha256 的 tarball。 - 消费方用锁文件的 integrity 字段固定版本。 - AI4S 把 tarball 纳入本仓库目录,以满足“只依赖本项目文件”的规则。 - 禁止任何源码级的跨兄弟仓库相对 import。 - W-Sim 制品交付给 W-Lab,也走同样的固定制品方式。 ## 8. 技术选型 | 领域 | 建议 | |---|---| | 构建 | Vite 库模式;不用 Next.js,服务端归 AI4S | | 3D | 沿用 three + R3F 9 + drei(v9 对应 React 19,v10 仍是 alpha)。全局只用一个实时画布,多视口用 drei `View`;按需渲染 | | 场渲染 | WebGPU 体渲染迁移自 WaveWorld;WebGPU 不是 Baseline,必须提供降级 | | 辅助图编辑 | 用 `@xyflow/react`,只作辅助视图;执行语义放在 `core` | | 组件 | Base UI,配作用域内的 CSS 变量;不用 Tailwind preflight 污染宿主 | | 状态 | useReducer,与事件归约同构 | | 图表 | ECharts 后续按需引入 | 需要实际构建验证的版本差异:three 0.186 与 0.185、TS 7 与 TS 6。 ## 9. 分阶段验收(不设日程) | 阶段 | 验收标准 | |---|---| | **P1 渲染优先切片** | 1. 把选定的 AI4S 场景、材质、相机迁入 `visual`。
2. demo 中以视口为主,播放已有工艺过程,参考时序标为“示意”。
3. AI4S 改用同一制品后,浏览器走查确认画面一致。
4. 附一个学校光学小例“凸透镜成像”:光具座 3D 场景,改 f 或 u 时光路即时更新,标“模型”;实测值录入前为空;来源 `unresolved`。
5. 缺失段、静止态、三种时钟、“未标定”标签都可见。 | | **P2 流场渲染器迁移** | WaveWorld 改用 `@w-lab/visual` 的体渲染器,删除本地副本;走查确认渲染一致;`displayStride` 布局契约写成文档 | | **P3 嵌入 AI4S** | 宿主适配器只接收已校验的 scope,不从 URL 或 postMessage 继承认证;访客只读;历史事件不可删改;Recipe 导出继续可用 | | **P4 教材扩展** | 提供教材版本后,每个积木都有可核对的 `SourceRef`,或明确标 `unresolved`;不复制教材原文和插图 | | **P5 作业与设备** | 受支持节点映射为 W-Sim 现有作业 kind;`path-experiment` 仅用于相应路径模型;数值核对只在已授权的 GPU 主机上进行;设备适配器带干跑、联锁和 `uncertain` 核对 | **验证方式:** - AI4S 内运行已有的 typecheck、build、现有检查脚本,并做有意义的浏览器走查。不在 AI4S 编写新的单元或集成测试。 - W-Lab 的验证包含类型检查、构建和浏览器走查;数值验收沿用 W-Sim 的指定执行环境。 **化学参数:** - 示例 B(材料工艺拆分为积木)的参数在本轮全部为 `unresolved`。本轮不提供任何配方数值。 - 以后如果有经过核实的配方值,经来源审核和安全审核后可以录入,不做一刀切的禁止。 ## 10. 风险与待决策 **风险:** - W-Sim 制品分发尚未验证。 - 迁移期间可能短暂出现双实现,需要按批次切换。 - three 和 TS 的版本差异。 - WebGPU 可用性有限;WaveWorld 已记录 SwiftShader 下设备丢失的情况。 - 包体积(WaveWorld 曾超出 7,500 kB 预算)。 - 教材版权。 - 化学安全。 **需要的决策:** 1. 正式命名和仓库位置。 2. 每批迁移的文件范围、受影响消费者和回归验收。 3. 制品托管方式。 4. AI4S 的持久化方式:复用 `lab_projects.payload`,还是新增表(需要 CONTRACT IMPACT 说明)。 5. 作业平面和服务间认证。 6. 教材版本清单。 ## 来源(官方文档,访问日期 2026-10-15) | 主题 | 链接 | |---|---| | R3F | [简介](https://r3f.docs.pmnd.rs/getting-started/introduction) · [性能](https://r3f.docs.pmnd.rs/advanced/scaling-performance) · [陷阱](https://r3f.docs.pmnd.rs/advanced/pitfalls) · [drei View](https://drei.docs.pmnd.rs/portals/view) | | WebGPU / 动效 | [MDN WebGPU](https://developer.mozilla.org/en-US/docs/Web/API/WebGPU_API) · [prefers-reduced-motion](https://developer.mozilla.org/en-US/docs/Web/CSS/@media/prefers-reduced-motion) | | 构建 | [Vite 指南](https://vite.dev/guide/) · [库模式](https://vite.dev/guide/build) | | 辅助 UI | [React Flow](https://reactflow.dev/) · [无障碍](https://reactflow.dev/learn/advanced-use/accessibility) · [Base UI](https://base-ui.com/react/overview/accessibility) | | 状态 / 图表 | [reducer + context](https://react.dev/learn/scaling-up-with-reducer-and-context) · [ECharts 按需导入](https://echarts.apache.org/handbook/en/basics/import/) |