Complete V1 RC deployment capability gates
This commit is contained in:
@@ -95,12 +95,46 @@ React 工作区和编辑器
|
|||||||
| `npm --prefix web run test:status-consistency` | 通过;12 parity blocked、0 release blocked |
|
| `npm --prefix web run test:status-consistency` | 通过;12 parity blocked、0 release blocked |
|
||||||
| `npm --prefix web run test:release-evidence` | 通过;17 records、0 missing、`READY` |
|
| `npm --prefix web run test:release-evidence` | 通过;17 records、0 missing、`READY` |
|
||||||
|
|
||||||
### 4.3 真实发布缺口
|
### 4.3 2026-08-15 重新盘点结论
|
||||||
|
|
||||||
当前 V1 release gate 已无 evidence 字段缺失,`faults.oom` 由独立 Chromium 四场景命令和
|
这次盘点把“V1 发布切片”和“完整 Blender Web 对标”分开统计,禁止用一个百分比混合两种
|
||||||
构件 hash 绑定。`geometry1M`、4K/8K texture、600 帧 simulation cache、断网、device loss、
|
完成定义:
|
||||||
恶意 blend、zip bomb、离线包和 V1 用户闭环仍须在 M5 全矩阵中持续回归。Blender 全域
|
|
||||||
`parityStatus=BLOCKED` 的长期差距不因 V1 gate `READY` 而关闭。
|
| 观察轴 | 已完成 | 未完成/阻断 | 当前判断 |
|
||||||
|
| --- | --- | --- | --- |
|
||||||
|
| V1 family release | 12/12 `releaseStatus=READY` | 0 family release blocked | V1 功能切片已实现 |
|
||||||
|
| Blender 5.2 全域 parity | 0/12 `parityStatus=COMPLETE` | 12/12 `BLOCKED` | 不是完整 Web Blender |
|
||||||
|
| 发布证据字段 | 17 条成功 record、0 missing;49/49 唯一 acceptance 通过 | 无 V1 证据缺口 | release gate 与 acceptance 审计链已闭环 |
|
||||||
|
| 浏览器 | Chromium 主线程/Offscreen 两条路径 | Firefox、WebKit 未纳入 | Chromium-only |
|
||||||
|
| Blender runtime | Main/Depsgraph/WebEngine 有界 WASM 子集 | 完整窗口系统、GPU 后端、Python/add-on、全量 operator 未移植 | 子集架构,不是桌面二进制直编 |
|
||||||
|
| 交付 | 离线 binary/source archive、SBOM、SHA-256 可复现 | 正式部署模板、持续发布流水线、跨机器复验待做 | 本地 V1 RC 已收口 |
|
||||||
|
|
||||||
|
2026-08-15 重新执行 `typecheck`、`lint`、14 个 Node tests、status consistency、release
|
||||||
|
evidence checker 和 `git diff --check`,均退出 0。acceptance runner 解析 12 个 family 的
|
||||||
|
50 条声明,去重为 49 条命令并全部通过,命令总耗时 454,106 ms;清单 SHA-256 为
|
||||||
|
`74c01764987c3dce0601a20cdf494f8076102f6d54262a1d13df953abee96160`。官方 Blender 5.2.0
|
||||||
|
Linux archive SHA-256 为 `96f6c181a30f4950607839dc84d42a354b250d8a0231b098b59b7bc69c351c48`,
|
||||||
|
运行时 `USD=True`,desktop USD roundtrip 已通过。
|
||||||
|
|
||||||
|
最终 release evidence 生成时间为 `2026-08-15T04:43:09.832Z`,17 条 record 均为 exit code 0、
|
||||||
|
非空输出和非空构件 hash,总命令耗时 553,427 ms。`v1-user-loop` record 已绑定上述 acceptance
|
||||||
|
清单 hash。
|
||||||
|
|
||||||
|
### 4.4 当前阻断与决策顺序
|
||||||
|
|
||||||
|
1. `P0`:V1 本地 RC 已无阻断;下一领取点为 `M6-01` 部署响应头合同。
|
||||||
|
2. `P1`:当前离线包已在本机连续两次可复现,还缺独立目录/独立机器复验和真实 HTTP 部署门。
|
||||||
|
3. `P1`:pthread/single-thread 的生产选择、隔离头、fallback 和缓存升级合同尚未形成发布证据。
|
||||||
|
4. `P2`:完整 Blender 功能域继续受 VDB demand paging、PBVH、Geometry Nodes evaluator、
|
||||||
|
通用 Shader、完整编辑器/渲染/媒体和安全脚本边界阻断。
|
||||||
|
|
||||||
|
### 4.5 真实发布缺口
|
||||||
|
|
||||||
|
当前 V1 release gate 已无 evidence 或 acceptance 缺口,`faults.oom` 由独立 Chromium 四场景
|
||||||
|
命令和构件 hash 绑定。`geometry1M`、4K/8K texture、600 帧 simulation cache、断网、device
|
||||||
|
loss、恶意 blend、zip bomb、离线包和 V1 用户闭环均已在最终 M5 全矩阵回归。后续代码变化
|
||||||
|
必须使 source/tool/package hash 过期并重跑对应证据。Blender 全域 `parityStatus=BLOCKED` 的
|
||||||
|
长期差距不因 V1 gate `READY` 而关闭。
|
||||||
|
|
||||||
## 5. 里程碑总览
|
## 5. 里程碑总览
|
||||||
|
|
||||||
@@ -111,7 +145,7 @@ React 工作区和编辑器
|
|||||||
| M2 10M geometry | 大几何浏览器性能与恢复证据 | 已完成,持续回归 | `performance.geometry10M=true` |
|
| M2 10M geometry | 大几何浏览器性能与恢复证据 | 已完成,持续回归 | `performance.geometry10M=true` |
|
||||||
| M3 long media | 长时间线索引、seek、取消与重启证据 | 已完成,持续回归 | `performance.longMedia=true` |
|
| M3 long media | 长时间线索引、seek、取消与重启证据 | 已完成,持续回归 | `performance.longMedia=true` |
|
||||||
| M4 OOM | 四类确定性内存故障与恢复证据 | 已完成,持续回归 | `faults.oom=true` |
|
| M4 OOM | 四类确定性内存故障与恢复证据 | 已完成,持续回归 | `faults.oom=true` |
|
||||||
| M5 V1 RC | 可复现离线候选包、SBOM、校验和 | 未开始 | release gate `READY` |
|
| M5 V1 RC | 可复现离线候选包、SBOM、校验和 | 已完成,持续回归 | release gate `READY`、49/49 acceptance |
|
||||||
|
|
||||||
任务严格按 M1 -> M2 -> M3 -> M4 -> M5 领取。里程碑内部允许先写纯校验任务,但不得在
|
任务严格按 M1 -> M2 -> M3 -> M4 -> M5 领取。里程碑内部允许先写纯校验任务,但不得在
|
||||||
前一里程碑失败时把后一里程碑标为完成。
|
前一里程碑失败时把后一里程碑标为完成。
|
||||||
@@ -347,44 +381,354 @@ WebGL GPU 资源组和 NanoVDB WebGPU resident/page-table 四类确定性失败
|
|||||||
|
|
||||||
## 11. M5 V1 发布候选
|
## 11. M5 V1 发布候选
|
||||||
|
|
||||||
### M5.1 全矩阵
|
M5 按 `PREP -> STATIC -> ACCEPT -> LOOP -> PERF -> FAULT -> VDB -> PACKAGE -> AUDIT`
|
||||||
|
串行推进。每个编号只对应一条命令或一个可独立判断的断言;失败项标为 `[!]` 并附退出码、
|
||||||
|
错误摘要和重跑命令。浏览器任务统一使用空闲的 `WEB_TEST_PORT`,同一任务的 Web server 退出后
|
||||||
|
才领取下一任务,避免端口复用掩盖真实启动失败。
|
||||||
|
|
||||||
- [ ] `M5-RC-01` clean install 后通过 typecheck、lint、unit。
|
### M5.0 环境和输入冻结
|
||||||
- [ ] `M5-RC-02` 运行所有 V1 required slice acceptance commands。
|
|
||||||
- [ ] `M5-RC-03` 运行 P0 用户闭环和 Chromium 全套 E2E。
|
|
||||||
- [ ] `M5-RC-04` 运行 geometry/media/texture/simulation 性能矩阵。
|
|
||||||
- [ ] `M5-RC-05` 运行 OOM/device-loss/network/malformed/zip-bomb 故障矩阵。
|
|
||||||
- [ ] `M5-RC-06` 运行 VDB 全矩阵和双生产视口。
|
|
||||||
|
|
||||||
### M5.2 构件和可复现性
|
- [x] `M5-PREP-01` 开始执行前运行 `git status --short --branch`,确认基线为
|
||||||
|
`main...origin/main` 且无用户未提交改动。
|
||||||
|
- [x] `M5-PREP-02` 记录 Node、npm、CMake、Ninja、Emscripten、Chromium 和 desktop Blender
|
||||||
|
的实际版本或明确缺失项。
|
||||||
|
- [x] `M5-PREP-03` 运行 `tools/web/check-emscripten.sh`,确认 Web toolchain 可用。
|
||||||
|
- [x] `M5-PREP-04` 运行 `tools/web/check-local-deps.sh`,确认运行时不依赖 CDN。
|
||||||
|
- [x] `M5-PREP-05` 检查 `build_web_blender6/bin/web_engine.{js,wasm}` 和安装到 Web app 的
|
||||||
|
engine manifest/hash 一致。
|
||||||
|
- [x] `M5-PREP-06` 检查发布所需 fixture、VDB native tools、Chrome 可执行文件和本地
|
||||||
|
Blender 可执行文件均存在。
|
||||||
|
- [x] `M5-PREP-07` 记录磁盘空间、内存和 CPU 数;资源不足时在运行长矩阵前停止并记录。
|
||||||
|
- [x] `M5-PREP-08` 运行 `npm --prefix web ci`,只使用 lockfile 完成 clean install。
|
||||||
|
- [x] `M5-PREP-09` 运行 `npm audit`;将构建链间接依赖 `nanoid` 从 3.3.17 更新到
|
||||||
|
3.3.18 后重新 clean install,确认 high/critical/total 均为 0。
|
||||||
|
|
||||||
- [ ] `M5-RC-07` 重新生成 SBOM 和 third-party notices。
|
### M5.1 静态和单元门
|
||||||
- [ ] `M5-RC-08` 生成离线二进制包和 corresponding source 包。
|
|
||||||
- [ ] `M5-RC-09` 连续生成两次并比较规范化摘要。
|
|
||||||
- [ ] `M5-RC-10` 生成 `SHA256SUMS.txt`,每个引用文件存在且 hash 匹配。
|
|
||||||
- [ ] `M5-RC-11` 检查包内无 CDN、绝对开发路径、测试 secret 或未声明运行时依赖。
|
|
||||||
|
|
||||||
### M5.3 声明和最终状态
|
- [x] `M5-STATIC-01` 运行 `npm --prefix web run typecheck`。
|
||||||
|
- [x] `M5-STATIC-02` 运行 `npm --prefix web run lint`。
|
||||||
|
- [x] `M5-STATIC-03` 运行 `npm --prefix web test`,记录通过/失败/跳过计数:14/0/0。
|
||||||
|
- [x] `M5-STATIC-04` 运行 `npm --prefix web run build`,确认生产构建可生成。
|
||||||
|
- [x] `M5-STATIC-05` 运行 `npm --prefix web run test:status-consistency`。
|
||||||
|
- [x] `M5-STATIC-06` 运行 `npm --prefix web run test:release-evidence`,只作为旧证据可解析性
|
||||||
|
检查,不据此刷新 M5 证据。
|
||||||
|
- [x] `M5-STATIC-07` 运行 `bash tools/web/check-dual-engine.sh`,确认 native 与 WASM 引擎契约。
|
||||||
|
- [x] `M5-STATIC-08` 运行 `node tools/web/run-web-engine-smoke.mjs`,确认安装后的生产 WASM
|
||||||
|
可实际启动。
|
||||||
|
- [x] `M5-STATIC-09` 重建 standalone single-thread 与 pthread Emscripten 变体;两者均使用
|
||||||
|
Blender 5.2 的 C++20 标准并成功生成 JS/WASM。
|
||||||
|
|
||||||
- [ ] `M5-RC-12` UI、README、scope 和 capability report 只声明 V1 白名单。
|
### M5.2 V1 required slice 验收
|
||||||
- [ ] `M5-RC-13` 长期 parity 差距继续保持,不因发布 V1 被改成全域 COMPLETE。
|
|
||||||
- [ ] `M5-RC-14` 重新收集 release evidence,不复用与当前构件 hash 不一致的旧 record。
|
本节命令来源必须是 `status/parity-ledger.json` 的 `families[].acceptance`。重复命令只执行一次;
|
||||||
- [ ] `M5-RC-15` release gate 输出 `READY` 且 missing 为空。
|
带 `web:e2e:<pattern>` 的项转换成 Playwright `-g` 过滤器,不能把协议单测替代为浏览器测试。
|
||||||
- [ ] `M5-RC-16` `git diff --check`、状态一致性和 release evidence checker 全部通过。
|
|
||||||
- [ ] `M5-RC-17` 工作区生成物均被忽略、纳入构件或有文档解释。
|
- [x] `M5-ACCEPT-01` N-015 non-mesh native roundtrip 与 desktop golden。
|
||||||
|
- [x] `M5-ACCEPT-02` N-015 non-mesh GLB/USD serialization 和 desktop USD roundtrip 通过;
|
||||||
|
官方 Blender 5.2.0 LTS runtime `USD=True`。
|
||||||
|
- [x] `M5-ACCEPT-03` N-015 1M binary、selection history 和 curve interaction 浏览器门。
|
||||||
|
- [x] `M5-ACCEPT-04` N-015 `N-015|non-mesh` 聚合 E2E 与真实 OPFS quota。
|
||||||
|
- [x] `M5-ACCEPT-05` N-016 Grease Pencil native roundtrip、editor 和聚合 E2E。
|
||||||
|
- [x] `M5-ACCEPT-06` N-017 Paint/weight native roundtrip 和聚合 E2E。
|
||||||
|
- [x] `M5-ACCEPT-07` N-018 Physics Main reader、simulation cache 和聚合 E2E。
|
||||||
|
- [x] `M5-ACCEPT-08` N-019 Lighting Main roundtrip 和 Scene exposure E2E。
|
||||||
|
- [x] `M5-ACCEPT-09` N-020 Compositor Main reader 和有界 CPU compositor E2E。
|
||||||
|
- [x] `M5-ACCEPT-10` N-021 Sequencer Main reader 和 timeline/codec gate E2E。
|
||||||
|
- [x] `M5-ACCEPT-11` N-022 Mask Main reader 和 tracking/mask E2E。
|
||||||
|
- [x] `M5-ACCEPT-12` N-023 Library Main reader、asset/zip 安全 E2E 和共享 VDB fault 门。
|
||||||
|
- [x] `M5-ACCEPT-13` N-024 Editor Main reader 和 context/keymap E2E。
|
||||||
|
- [x] `M5-ACCEPT-14` N-025 Script Main reader、isolation 和默认拒绝 E2E。
|
||||||
|
- [x] `M5-ACCEPT-15` N-026 release gate E2E;断言 Chromium-only 声明和 V1 release 状态。
|
||||||
|
- [x] `M5-ACCEPT-16` 实现 acceptance coverage checker:解析 12 个 family、校验命名空间、
|
||||||
|
npm script 存在、E2E pattern 非空并去重。
|
||||||
|
- [x] `M5-ACCEPT-17` 实现 acceptance runner:顺序执行去重后的命令,浏览器命令使用独立端口,
|
||||||
|
写入 ledger SHA-256、环境版本、退出码、耗时和输出摘要。
|
||||||
|
- [x] `M5-ACCEPT-18` 对未知命名空间、缺失 script、空 pattern、重复声明、stale source/tool/package、
|
||||||
|
漏项、失败退出码、空输出和 hash 漂移建立负例/断言。
|
||||||
|
- [x] `M5-ACCEPT-19` 使用 SHA-256 校验的官方 USD-enabled Blender 5.2 重跑 desktop USD
|
||||||
|
roundtrip;归档 hash 和 executable hash 均写入 acceptance evidence。
|
||||||
|
- [x] `M5-ACCEPT-20` 运行完整 acceptance runner:50 declarations、49 unique、49 passed、
|
||||||
|
0 failed,总命令耗时 454,106 ms。
|
||||||
|
- [x] `M5-ACCEPT-21` 将 acceptance evidence SHA-256
|
||||||
|
`74c01764987c3dce0601a20cdf494f8076102f6d54262a1d13df953abee96160` 绑定到最终
|
||||||
|
`v1-user-loop` release record。
|
||||||
|
|
||||||
|
### M5.3 P0 用户闭环和 Chromium 回归
|
||||||
|
|
||||||
|
- [x] `M5-LOOP-01` 独立运行 `test:v1-user-loop`,覆盖 import/edit/undo/redo/save。
|
||||||
|
- [x] `M5-LOOP-02` 在同一 P0 测试中确认 StorageWorker 与 WebEngineWorker 均终止后重建。
|
||||||
|
- [x] `M5-LOOP-03` 确认重开后 object/mesh/material stable ID 和 geometry hash 不变。
|
||||||
|
- [x] `M5-LOOP-04` 确认主线程与 OffscreenCanvas 两个生产视口均产生非空像素。
|
||||||
|
- [x] `M5-LOOP-05` 确认 GLB 严格回读通过且未支持 Shader 返回稳定阻断。
|
||||||
|
- [x] `M5-LOOP-06` 运行 `test:e2e -- --workers=1` 全套 Chromium E2E:122/122 通过,
|
||||||
|
0 失败、0 跳过,总耗时 6.4 分钟。
|
||||||
|
- [x] `M5-LOOP-07` 运行 `test:browser` 离线引擎发布配置 smoke:3/3 通过。
|
||||||
|
|
||||||
|
### M5.4 性能和容量矩阵
|
||||||
|
|
||||||
|
- [x] `M5-PERF-01` 运行 100k/1M geometry `test:release-performance`。
|
||||||
|
- [x] `M5-PERF-02` 运行 10M geometry 分块、双视口、取消、释放和恢复门。
|
||||||
|
- [x] `M5-PERF-03` 校验 10M report 精确为 `triangleCount=10_000_000` 且没有缩小 fixture 的开关。
|
||||||
|
- [x] `M5-PERF-04` 运行 4K texture decode/upload/budget/release 门。
|
||||||
|
- [x] `M5-PERF-05` 运行 8K texture decode/upload/budget/release 门。
|
||||||
|
- [x] `M5-PERF-06` 运行 600 帧 simulation cache 性能、LRU 和 Worker restart 门。
|
||||||
|
- [x] `M5-PERF-07` 运行 long media index/seek/cancel/LRU/restart 门。
|
||||||
|
- [x] `M5-PERF-08` 每份性能报告均输出 fixture 规模、耗时、峰值/预算、释放后值和恢复状态。
|
||||||
|
|
||||||
|
### M5.5 故障和恶意输入矩阵
|
||||||
|
|
||||||
|
- [x] `M5-FAULT-01` 运行 WASM/Main、OPFS、GPU、VDB 四场景 OOM recovery。
|
||||||
|
- [x] `M5-FAULT-02` 运行主线程 WebGL device-loss 恢复门。
|
||||||
|
- [x] `M5-FAULT-03` 运行网络 range 中断、续传、hash 和 rollback 门。
|
||||||
|
- [x] `M5-FAULT-04` 运行 malicious `.blend` 截断、长度溢出和预算门,拒绝 5 类输入。
|
||||||
|
- [x] `M5-FAULT-05` 运行 asset archive zip-bomb、路径穿越和膨胀预算门。
|
||||||
|
- [x] `M5-FAULT-06` 运行 OPFS quota/staging/journal rollback 浏览器门。
|
||||||
|
- [x] `M5-FAULT-07` 核对每个失败均返回稳定 error code/stage,旧 revision/hash 不变。
|
||||||
|
- [x] `M5-FAULT-08` 核对每个故障后同会话或 Worker 重启的小场景恢复成功且临时资源归零。
|
||||||
|
|
||||||
|
### M5.6 VDB/WebGPU 发布矩阵
|
||||||
|
|
||||||
|
- [x] `M5-VDB-01` 校验 VDB resource catalog、license、固定 SHA-256 和 availability。
|
||||||
|
- [x] `M5-VDB-02` 运行 desktop OpenVDB -> NanoVDB native converter 正例与畸形输入。
|
||||||
|
- [x] `M5-VDB-03` 运行 server job isolation/cancel/timeout 和 desktop/server hash 等价。
|
||||||
|
- [x] `M5-VDB-04` 运行浏览器 conversion boundary 和稳定 capability block。
|
||||||
|
- [x] `M5-VDB-05` 运行 HTTP range -> OPFS 原子绑定、tamper 和 Worker restart。
|
||||||
|
- [x] `M5-VDB-06` 运行真实 Float32/Vec3 NanoVDB CPU/WGSL sampling。
|
||||||
|
- [x] `M5-VDB-07` 运行主线程 WebGL 与 OffscreenCanvas Worker 生产视口非空像素。
|
||||||
|
- [x] `M5-VDB-08` 运行多 grid 材质、resident LRU、网络/Worker/device-loss fault 恢复。
|
||||||
|
- [x] `M5-VDB-09` 核对自动 page fault、深度合成、大 bundle 和发布三视角 golden 仍明确排除。
|
||||||
|
|
||||||
|
### M5.7 构件、许可证和可复现性
|
||||||
|
|
||||||
|
- [x] `M5-PACKAGE-01` 从 lockfile 重新生成 SPDX 2.3 SBOM,共 150 个 package。
|
||||||
|
- [x] `M5-PACKAGE-02` 检查 third-party notices 覆盖 React、Three、Vite、TypeScript、Playwright、
|
||||||
|
Blender 和本地 native/WASM 依赖。
|
||||||
|
- [x] `M5-PACKAGE-03` 重新执行生产 build,检查 engine JS/WASM 和 manifest 均进入 dist。
|
||||||
|
- [x] `M5-PACKAGE-04` 检查 dist 的 HTML/JS/CSS/JSON 不引用 HTTP(S) CDN。
|
||||||
|
- [x] `M5-PACKAGE-05` 生成离线 binary archive,大小 7,585,557 bytes。
|
||||||
|
- [x] `M5-PACKAGE-06` 生成 corresponding source archive 和 `SOURCE_OFFER.txt`,大小
|
||||||
|
205,827,673 bytes。
|
||||||
|
- [x] `M5-PACKAGE-07` 第一次记录 binary/source archive SHA-256。
|
||||||
|
- [x] `M5-PACKAGE-08` 从相同输入再次生成两个 archive。
|
||||||
|
- [x] `M5-PACKAGE-09` 断言第二次 binary/source SHA-256 分别与第一次完全相同。
|
||||||
|
- [x] `M5-PACKAGE-10` 生成顶层 `release/SHA256SUMS.txt`。
|
||||||
|
- [x] `M5-PACKAGE-11` 逐行校验 `SHA256SUMS.txt` 引用文件存在且 `sha256sum -c` 通过。
|
||||||
|
- [x] `M5-PACKAGE-12` 扫描构件中的 CDN、绝对工作区路径、token/password/private key 特征。
|
||||||
|
- [x] `M5-PACKAGE-13` binary archive 与 manifest 完全一致;source archive 无缓存、trace、
|
||||||
|
build、dist、测试报告或日志。
|
||||||
|
|
||||||
|
### M5.8 声明、证据和最终审计
|
||||||
|
|
||||||
|
- [x] `M5-AUDIT-01` UI、README 和 scope 统一使用 `Web Blender Modeler V1`,浏览器范围为
|
||||||
|
Chromium-only。
|
||||||
|
- [x] `M5-AUDIT-02` UI 不把 `SERVER/EXCLUDED/BLOCKED` 能力呈现为可本地成功。
|
||||||
|
- [x] `M5-AUDIT-03` 确认 12 个 family 仍为 `parityStatus=BLOCKED`。
|
||||||
|
- [x] `M5-AUDIT-04` 确认 12 个 V1 required slice 均为 `releaseStatus=READY`。
|
||||||
|
- [x] `M5-AUDIT-05` 运行完整 `release:evidence`,用本轮命令输出和构件 hash 替换旧 records;
|
||||||
|
当前生成时间 `2026-08-15T04:43:09.832Z`,17 条 record,总命令耗时 553,427 ms。
|
||||||
|
- [x] `M5-AUDIT-06` 检查 17 条 evidence record 均为 exit code 0、非空输出和非空构件 hash。
|
||||||
|
- [x] `M5-AUDIT-07` 检查 evidence 的 ledger SHA-256 与当前文件完全一致。
|
||||||
|
- [x] `M5-AUDIT-08` 运行 `test:release-evidence`,结果 `READY`、`missing=[]`。
|
||||||
|
- [x] `M5-AUDIT-09` 运行 `test:status-consistency`,结果 12 parity blocked、0 release blocked。
|
||||||
|
- [x] `M5-AUDIT-10` 运行 `git diff --check`。
|
||||||
|
- [x] `M5-AUDIT-11` 运行 `git status --short --ignored`;源码/证据为 tracked 工作项,build、dist、
|
||||||
|
release、node_modules、测试结果和工具链 cache 均为预期 ignored 生成物。
|
||||||
|
- [x] `M5-AUDIT-12` 将实际命令、计数、耗时、失败重跑和最终 archive hash 写回本计划与
|
||||||
|
`PROJECT_STATUS_AND_NEXT_WORK.md`,不得把未运行项标完成。
|
||||||
|
|
||||||
M5 退出条件:所有必需命令退出码 0;release gate `READY`;离线包可复现;当前构件、源码、
|
M5 退出条件:所有必需命令退出码 0;release gate `READY`;离线包可复现;当前构件、源码、
|
||||||
SBOM、状态和 evidence SHA-256 相互一致。
|
SBOM、状态和 evidence SHA-256 相互一致。
|
||||||
|
|
||||||
## 12. V1 后任务
|
M5 最终 archive SHA-256:binary
|
||||||
|
`f2903a75026a0a28615aeb5f4834a3859af83022c82bc50e28437dcbed68e549`;source
|
||||||
|
`9a9c1b505c68a618de842be7c248f628b104b11b80e8a40d111f62540fcba327`。下一任务:`M6-01`。
|
||||||
|
|
||||||
V1 RC 完成前不领取以下任务:
|
## 12. V1 后原子里程碑
|
||||||
|
|
||||||
1. VDB 自动 page-fault feedback、渐进重绘和生产 Offscreen device-loss 联合恢复。
|
本节是 V1 RC 之后的领取顺序。每个里程碑控制在一个可独立回归的行为闭环;同一功能域不再
|
||||||
2. Volume Main + asset binding + 生产视口联合重开,以及三视角 desktop/Chromium golden。
|
一次领取 reader、writer、UI、GPU 和发布门。前一里程碑未达到退出条件时,后一里程碑只能做
|
||||||
3. 64 MiB/512 MiB/1 GiB sparse bundle、深度合成和完整 VDB 大包/取消矩阵。
|
不会改变产品声明的测试准备。
|
||||||
4. 新外部字体、PBVH、完整 paint、Geometry Nodes/Simulation evaluator、任意 Shader、完整
|
|
||||||
NLA、Compositor、Sequencer、Tracking、Asset Browser、Editor 和 Scripting 对标。
|
|
||||||
5. Firefox/WebKit 支持和跨浏览器发布门。
|
|
||||||
|
|
||||||
V1 后按产品需求和风险重新排序,不再仅按 N-015 至 N-026 编号顺序扩大范围。
|
### M6 可部署 RC
|
||||||
|
|
||||||
|
- [x] `M6-01` 新建机器可读部署合同,固定 MIME、缓存、COOP、COEP、CORP 和 Range 响应头;
|
||||||
|
`node tools/web/check-deployment-contract.mjs`、typecheck、lint 和 production build 通过。
|
||||||
|
- [x] `M6-02` 复用 `CapabilityGateResult` 新增纯函数 `gateWasmThreadingCapability`;仅当
|
||||||
|
`crossOriginIsolated`、`SharedArrayBuffer` 和 `Worker` 同时可用时放行 pthread 引擎,
|
||||||
|
缺失项逐条返回稳定结构,single-thread 初始化路径不调用该门。
|
||||||
|
- [ ] `M6-03` 验证 single-thread 引擎在无隔离环境启动。
|
||||||
|
- [ ] `M6-04` 验证 pthread 引擎只在隔离和 SharedArrayBuffer 可用时启动。
|
||||||
|
- [ ] `M6-05` 验证 pthread 初始化失败回退 single-thread,且不重复打开项目。
|
||||||
|
- [ ] `M6-06` 为 engine JS/WASM、Worker、CSS 和 fixture 固定正确 MIME。
|
||||||
|
- [ ] `M6-07` 为 WASM 与大资产验证 byte-range 206、`Content-Range` 和稳定 ETag。
|
||||||
|
- [ ] `M6-08` 验证版本更新后旧 HTML 不加载新旧混合 hash 资源。
|
||||||
|
- [ ] `M6-09` 验证完全断网冷启动离线包,不访问 CDN 或外部字体。
|
||||||
|
- [ ] `M6-10` 验证断网重开 OPFS 项目、编辑、保存和 GLB 导出。
|
||||||
|
- [ ] `M6-11` 在全新临时目录解包 binary archive 并运行 browser smoke。
|
||||||
|
- [ ] `M6-12` 在全新临时目录解包 source archive 并核对 source offer/SBOM。
|
||||||
|
- [ ] `M6-13` 建立 CI quick lane:typecheck、lint、unit、status、evidence schema。
|
||||||
|
- [ ] `M6-14` 建立 CI Chromium lane:P0 用户闭环、双视口和 fault smoke。
|
||||||
|
- [ ] `M6-15` 建立 CI release lane:全 E2E、性能、离线复现和 acceptance evidence。
|
||||||
|
- [ ] `M6-16` 所有 CI lane 输出机器可读 JSON 和构件 SHA-256。
|
||||||
|
- [ ] `M6-17` 写部署/升级/回滚 runbook,命令在空目录实测。
|
||||||
|
- [ ] `M6-18` 生成 RC 版本号、变更说明、已知限制和恢复说明。
|
||||||
|
|
||||||
|
M6 退出条件:离线包和一个真实 HTTP 部署均通过同一 P0 流程;single/pthread 选择可解释;
|
||||||
|
CI 可从 lockfile 和对应源码重现当前 binary/source hash。
|
||||||
|
|
||||||
|
### M7 核心项目体验硬化
|
||||||
|
|
||||||
|
- [ ] `M7-01` 记录 import/open/save/save-as/export 五个用户动作的状态机。
|
||||||
|
- [ ] `M7-02` 对重复 open、并发 save 和 close-during-save 建立互斥规则。
|
||||||
|
- [ ] `M7-03` 大文件导入显示真实字节进度并支持取消。
|
||||||
|
- [ ] `M7-04` 取消 import 后释放 input buffer、native handle 和 staging 文件。
|
||||||
|
- [ ] `M7-05` save 中断时旧 revision 和旧 hash 保持不变。
|
||||||
|
- [ ] `M7-06` dirty state 只由成功 Main transaction 改变。
|
||||||
|
- [ ] `M7-07` undo 回到保存 revision 时 dirty state 清除。
|
||||||
|
- [ ] `M7-08` Worker 崩溃时展示可恢复错误,不清空当前项目列表。
|
||||||
|
- [ ] `M7-09` 重启 Worker 后恢复 selection、frame、workspace 和项目 revision。
|
||||||
|
- [ ] `M7-10` recent projects 对缺失/损坏 OPFS 条目执行隔离和修复提示。
|
||||||
|
- [ ] `M7-11` 存储预算面板报告项目、快照、LOD、媒体、VDB 分项字节。
|
||||||
|
- [ ] `M7-12` 清理单个项目时只删除该项目 content-addressed 引用的孤儿资源。
|
||||||
|
- [ ] `M7-13` 键盘焦点、菜单、modal、Escape 取消形成一致上下文规则。
|
||||||
|
- [ ] `M7-14` 1440x900、1280x720、移动窄屏下无控制重叠和文本溢出。
|
||||||
|
- [ ] `M7-15` 主线程与 Offscreen 视口的选择、相机和 gizmo 行为一致。
|
||||||
|
- [ ] `M7-16` P0 用户流程增加无鼠标键盘路径和基础可访问性检查。
|
||||||
|
- [ ] `M7-17` 用户可见错误只显示稳定短消息,详细诊断进入可导出报告。
|
||||||
|
- [ ] `M7-18` 完成 30 分钟持续编辑、自动保存、重开 soak 测试。
|
||||||
|
|
||||||
|
### M8 VDB 自动分页最小闭环
|
||||||
|
|
||||||
|
- [ ] `M8-01` 定义 GPU page-fault feedback buffer schema、容量和 overflow code。
|
||||||
|
- [ ] `M8-02` shader 对未驻留 leaf 记录唯一 page ID,不越界写 feedback。
|
||||||
|
- [ ] `M8-03` CPU 读取 feedback 后排序、去重并绑定 render revision。
|
||||||
|
- [ ] `M8-04` stale frame feedback 不触发 I/O。
|
||||||
|
- [ ] `M8-05` page request 使用现有 manifest range/hash,不建立第二套地址模型。
|
||||||
|
- [ ] `M8-06` 同页并发请求合并,取消最后一个订阅者时中止 range。
|
||||||
|
- [ ] `M8-07` page hash 错误不写 resident cache,并返回稳定错误。
|
||||||
|
- [ ] `M8-08` LRU 淘汰跳过当前 frame pin 的 page。
|
||||||
|
- [ ] `M8-09` page upload 成功后只安排一次渐进重绘。
|
||||||
|
- [ ] `M8-10` 重绘上限防止坏数据造成无限 render loop。
|
||||||
|
- [ ] `M8-11` 主线程 WebGPU 验证缺页、加载、重绘到确定性像素。
|
||||||
|
- [ ] `M8-12` Offscreen Worker 验证相同页序列和最终像素。
|
||||||
|
- [ ] `M8-13` page fetch 中 network interruption 可从精确 byte offset 续传。
|
||||||
|
- [ ] `M8-14` Worker restart 从 OPFS 恢复 manifest,但不信任未复验 resident 状态。
|
||||||
|
- [ ] `M8-15` device loss 重建 page table,并按可见集合有界回放。
|
||||||
|
- [ ] `M8-16` OOM 释放 page table/resident/feedback buffer 各一次。
|
||||||
|
- [ ] `M8-17` 64 MiB sparse bundle 性能与取消门。
|
||||||
|
- [ ] `M8-18` Volume Main、asset binding、双视口联合保存重开。
|
||||||
|
- [ ] `M8-19` desktop/主线程/Offscreen 三视角 golden 和误差阈值。
|
||||||
|
- [ ] `M8-20` 只有 M8 全部通过后更新 N-015 对应 slice,N-015 全域仍不自动 COMPLETE。
|
||||||
|
|
||||||
|
### M9 非 Mesh、Grease Pencil 与 Paint 增量
|
||||||
|
|
||||||
|
- [ ] `M9-01` 外部字体导入先做路径、类型、大小和 hash 校验。
|
||||||
|
- [ ] `M9-02` 字体进入 OPFS content-addressed asset 后才写 Main VFont。
|
||||||
|
- [ ] `M9-03` 字体替换/撤销/保存/重开/缺失资产形成闭环。
|
||||||
|
- [ ] `M9-04` Curve 完整 topology editor 先冻结 operator 白名单和预算。
|
||||||
|
- [ ] `M9-05` 每个新增 Curve operator 单独完成 Main/undo/save/golden 后再开放 UI。
|
||||||
|
- [ ] `M9-06` Grease Pencil marquee 只操作当前 drawing 的稳定 point/stroke ID。
|
||||||
|
- [ ] `M9-07` Grease Pencil 2D canvas 与 3D viewport 使用同一 selection revision。
|
||||||
|
- [ ] `M9-08` Grease Pencil layer/frame reorder 完成 undo/save/reopen。
|
||||||
|
- [ ] `M9-09` Paint 建立真实深度可见性采样,不用 CPU proxy 冒充。
|
||||||
|
- [ ] `M9-10` Paint stroke 分块提交且一次 pointer session 只产生一个 undo step。
|
||||||
|
- [ ] `M9-11` Texture paint dirty tile 原子写入 packed/UDIM 资产。
|
||||||
|
- [ ] `M9-12` Weight paint normalize/limit/mirror 与 Blender desktop golden 对比。
|
||||||
|
- [ ] `M9-13` PBVH 若不进入 WASM,则相关笔刷保持明确 capability block。
|
||||||
|
- [ ] `M9-14` 三个编辑域分别通过 Worker restart、OOM、GPU release 和小场景恢复。
|
||||||
|
|
||||||
|
### M10 Geometry Nodes、Shader、NLA 与 Simulation
|
||||||
|
|
||||||
|
- [ ] `M10-01` 从 Blender Main 读取图拓扑、socket 默认值、link 和稳定 node ID。
|
||||||
|
- [ ] `M10-02` 为 Geometry Nodes 建立 allowlist,不支持节点保留原数据并阻断求值。
|
||||||
|
- [ ] `M10-03` 每个 allowlist 节点增加 desktop fixture、WASM 输出和误差阈值。
|
||||||
|
- [ ] `M10-04` field/domain 转换显式预算,禁止按无限域展开 JSON。
|
||||||
|
- [ ] `M10-05` Simulation Zone cache 绑定 graph/source/revision hash。
|
||||||
|
- [ ] `M10-06` cache 取消、LRU、重启和损坏隔离通过后才允许播放。
|
||||||
|
- [ ] `M10-07` Shader 图只编译声明的 Principled/Image/Normal/Math 子集。
|
||||||
|
- [ ] `M10-08` shader compile key 包含 graph、texture、color space 和 renderer backend。
|
||||||
|
- [ ] `M10-09` compile failure 不替换上一份可用 material pipeline。
|
||||||
|
- [ ] `M10-10` 任意未支持 Shader 节点返回 stable capability block,不静默降级。
|
||||||
|
- [ ] `M10-11` NLA track/strip/action/time mapping 先完成只读精确求值。
|
||||||
|
- [ ] `M10-12` NLA 单个编辑 operator 分别完成 Main transaction、undo 和保存重开。
|
||||||
|
- [ ] `M10-13` Physics 本地 solver 按 family 探测;不支持项只消费 desktop/server bake。
|
||||||
|
- [ ] `M10-14` 每类 cache 验证 source hash、frame range、字节预算和版本。
|
||||||
|
- [ ] `M10-15` GN/Shader/NLA/Simulation 分别建立浏览器性能、OOM 和恶意图输入门。
|
||||||
|
|
||||||
|
### M11 Lighting、Render、Compositor 与 Sequencer
|
||||||
|
|
||||||
|
- [ ] `M11-01` Camera/Light/World/Scene color management 建立字段级 parity 表。
|
||||||
|
- [ ] `M11-02` 每个支持字段通过 Main edit、undo、save/reopen 和 viewport 映射。
|
||||||
|
- [ ] `M11-03` Three/WebGPU 灯光数量、shadow map 和纹理预算显式化。
|
||||||
|
- [ ] `M11-04` Web 实时渲染结果与 Blender reference 采用可解释图像误差指标。
|
||||||
|
- [ ] `M11-05` Cycles/复杂 Eevee/硬件后端固定为 server job,不在浏览器伪造等价。
|
||||||
|
- [ ] `M11-06` server render job 绑定 source hash、Blender build、设置和输出 hash。
|
||||||
|
- [ ] `M11-07` Compositor allowlist 每新增一个 node 单独增加 CPU/WebGPU golden。
|
||||||
|
- [ ] `M11-08` unsupported compositor graph 保留原图并阻断执行。
|
||||||
|
- [ ] `M11-09` Sequencer IMAGE/SOUND/MOVIE codec 由运行时 probe 决定,不按扩展名猜测。
|
||||||
|
- [ ] `M11-10` long media proxy/cache 绑定源 hash 和 decode capability。
|
||||||
|
- [ ] `M11-11` seek/scrub/decode 迟到结果全部受 revision gate 控制。
|
||||||
|
- [ ] `M11-12` 最终视频编码固定 server/export capability,不误报本地完成。
|
||||||
|
- [ ] `M11-13` audio context suspend/resume、设备缺失和静音恢复有专项测试。
|
||||||
|
- [ ] `M11-14` render/compositor/media 全部覆盖取消、重启、预算释放和恢复。
|
||||||
|
|
||||||
|
### M12 Asset、IO、Editor 与工作流
|
||||||
|
|
||||||
|
- [ ] `M12-01` asset catalog schema migration 有向前/向后兼容 fixture。
|
||||||
|
- [ ] `M12-02` append/link/override 分别定义 stable ID、所有权和失效语义。
|
||||||
|
- [ ] `M12-03` library source 只允许声明 origin,路径穿越和符号链接逃逸被拒绝。
|
||||||
|
- [ ] `M12-04` archive 每个 entry 在解压前检查数量、压缩比、总字节和目标路径。
|
||||||
|
- [ ] `M12-05` GLB、OBJ、STL、PLY 每种格式建立独立导入/导出能力矩阵。
|
||||||
|
- [ ] `M12-06` 每种支持格式至少一个 desktop roundtrip 和一个 loss report。
|
||||||
|
- [ ] `M12-07` USD/Alembic 依赖固定 desktop runtime identity,不由本机偶然能力决定 gate。
|
||||||
|
- [ ] `M12-08` View3D/Outliner/Properties 的 selection/context/revision 使用同一 store。
|
||||||
|
- [ ] `M12-09` UV/Node/Graph/Dope/NLA editor 每次只开放有 Main writer 的操作。
|
||||||
|
- [ ] `M12-10` operator search 从 capability registry 生成,不维护第二份成功列表。
|
||||||
|
- [ ] `M12-11` keymap resolution 按 editor/modal/tool 上下文确定性排序。
|
||||||
|
- [ ] `M12-12` workspace/layout 保存重开不修改 Blender 场景 revision。
|
||||||
|
- [ ] `M12-13` editor dispose 后无 DOM listener、Worker request 或 GPU resource 泄漏。
|
||||||
|
- [ ] `M12-14` 典型建模、动画、资产复用流程建立任务级 E2E,不只做控件 smoke。
|
||||||
|
|
||||||
|
### M13 Scripting、安全与平台边界
|
||||||
|
|
||||||
|
- [ ] `M13-01` Python/Text autorun/add-on 默认拒绝保持产品级不可绕过。
|
||||||
|
- [ ] `M13-02` script manifest 只读解析限制文本、模块、路径和依赖数量。
|
||||||
|
- [ ] `M13-03` 若引入签名脚本,先定义签名者、权限、撤销和审计模型。
|
||||||
|
- [ ] `M13-04` 浏览器 sandbox 不提供 DOM、网络、OPFS、Worker 的隐式全权限。
|
||||||
|
- [ ] `M13-05` server Blender job 使用一次性目录、只读源、资源限制和网络策略。
|
||||||
|
- [ ] `M13-06` 所有 server result 验证 source/settings/build/output hash 后才绑定项目。
|
||||||
|
- [ ] `M13-07` CSP 禁止 inline script、eval 和未声明 origin。
|
||||||
|
- [ ] `M13-08` fixture 覆盖恶意 blend、图片、字体、媒体、archive、node graph 和 manifest。
|
||||||
|
- [ ] `M13-09` fuzz crash 不写入 release evidence,修复后必须加入最小回归样本。
|
||||||
|
- [ ] `M13-10` capability report 明确 native window/CUDA/Metal/HIP/OptiX 不等价。
|
||||||
|
- [ ] `M13-11` 依赖审计区分生产、构建、测试依赖并设置严重级别门。
|
||||||
|
- [ ] `M13-12` SBOM、license、source offer 与每个 release archive hash 绑定。
|
||||||
|
|
||||||
|
### M14 跨浏览器与设备扩展
|
||||||
|
|
||||||
|
- [ ] `M14-01` 先冻结 Chromium RC,不在同一变更中同时追三个浏览器差异。
|
||||||
|
- [ ] `M14-02` Firefox capability probe 覆盖 OPFS、WebGL2、WebGPU、Worker 和 isolation。
|
||||||
|
- [ ] `M14-03` Firefox P0 流程逐项记录 PASS/BLOCKED,不启用静默 fallback。
|
||||||
|
- [ ] `M14-04` Firefox 主线程视口先通过,再领取 Offscreen 路径。
|
||||||
|
- [ ] `M14-05` WebKit capability probe 和 P0 流程采用相同规则。
|
||||||
|
- [ ] `M14-06` WebKit 主线程视口先通过,再领取 Offscreen 路径。
|
||||||
|
- [ ] `M14-07` 浏览器不支持 WebGPU 时只隐藏明确依赖 WebGPU 的能力。
|
||||||
|
- [ ] `M14-08` 内存上限按浏览器/设备档位选择已声明预算,不自动扩大。
|
||||||
|
- [ ] `M14-09` 触控、笔、HiDPI、IME 和非 US 键盘分别建立输入 fixture。
|
||||||
|
- [ ] `M14-10` 每个新增浏览器加入 quick/P0/full 三层 CI 后才进入发布声明。
|
||||||
|
|
||||||
|
### M15 全域对标和下一发布
|
||||||
|
|
||||||
|
- [ ] `M15-01` 每季度从 Blender 5.2 operator/data-block 清单重新生成 parity 差距。
|
||||||
|
- [ ] `M15-02` 每个 family 的 `COMPLETE` 必须没有 summary-only、proxy-success 或隐式 loss。
|
||||||
|
- [ ] `M15-03` LOCAL_EXACT 必须有相同 fixture 的 desktop/WASM 语义或像素 golden。
|
||||||
|
- [ ] `M15-04` LOCAL_BOUNDED 必须同时验证白名单正例和非白名单稳定阻断。
|
||||||
|
- [ ] `M15-05` SERVER 必须验证提交、取消、隔离、预算、hash 和结果绑定。
|
||||||
|
- [ ] `M15-06` EXCLUDED 必须验证 UI 不开放、文件数据不丢失、保存不破坏原项目。
|
||||||
|
- [ ] `M15-07` 12 个 family 分别达到自身退出条件,禁止由 N-026 聚合状态反推完成。
|
||||||
|
- [ ] `M15-08` 全域发布候选重新跑 clean build、acceptance、性能、fault 和安全矩阵。
|
||||||
|
- [ ] `M15-09` 在独立机器复验 binary/source archive 和所有 hash。
|
||||||
|
- [ ] `M15-10` 发布说明逐项列出支持、受限、server 和 excluded 能力。
|
||||||
|
|
||||||
|
实际领取顺序固定为:先完成 M5;随后 M6、M7;再按产品需求在 M8 至 M13 中一次选择一个
|
||||||
|
最小闭环;M14 只在 Chromium RC 稳定后开始;M15 是状态审计,不是把剩余功能一次性打包。
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
# Web Blender 项目现状与后续连续任务
|
# Web Blender 项目现状与后续连续任务
|
||||||
|
|
||||||
更新时间:2026-08-14
|
更新时间:2026-08-15
|
||||||
|
|
||||||
当前短周期任务、领取顺序和阶段退出条件统一维护在
|
当前短周期任务、领取顺序和阶段退出条件统一维护在
|
||||||
`docs/CURRENT_EXECUTION_PLAN.md`。本文件保留实现事实、长期能力台账和完整验收命令,不再作为
|
`docs/CURRENT_EXECUTION_PLAN.md`。本文件保留实现事实、长期能力台账和完整验收命令,不再作为
|
||||||
@@ -19,6 +19,10 @@ Blender C/C++ 子集编译为 WASM,OPFS 保存大文件,IndexedDB 保存小
|
|||||||
SQLite WASM 和 Bitbybit/OCCT 均不在当前依赖范围内。Three.js、WASM 和其他
|
SQLite WASM 和 Bitbybit/OCCT 均不在当前依赖范围内。Three.js、WASM 和其他
|
||||||
运行时资源全部使用项目本地文件,不依赖 CDN。
|
运行时资源全部使用项目本地文件,不依赖 CDN。
|
||||||
|
|
||||||
|
`Web Blender Modeler V1` 的本地 RC 已收口:ledger 的 50 条 acceptance 声明去重为 49 条
|
||||||
|
命令并全部通过,122/122 Chromium E2E 通过,17 条 release record 为 `READY` 且 0 missing。
|
||||||
|
这不改变完整 Blender 5.2 对标结论:N-015 至 N-026 仍为 12/12 `parityStatus=BLOCKED`。
|
||||||
|
|
||||||
## 2. 已完成并有测试覆盖的能力
|
## 2. 已完成并有测试覆盖的能力
|
||||||
|
|
||||||
| 能力 | 当前证据 | 状态 |
|
| 能力 | 当前证据 | 状态 |
|
||||||
@@ -216,6 +220,19 @@ demand paging、大 bundle 和发布 golden。
|
|||||||
阻塞 V1。P0 用户闭环、10M geometry、long media 和 OOM 均有独立 command/output/artifact-hash
|
阻塞 V1。P0 用户闭环、10M geometry、long media 和 OOM 均有独立 command/output/artifact-hash
|
||||||
记录;当前为 0 个 V1 缺失,release gate 为 `READY`。浏览器范围仅为 Chromium。
|
记录;当前为 0 个 V1 缺失,release gate 为 `READY`。浏览器范围仅为 Chromium。
|
||||||
|
|
||||||
|
`docs/status/v1-acceptance-evidence.json` 逐条覆盖 12 个 family 的 50 条声明、49 个唯一命令,
|
||||||
|
结果为 49 passed、0 failed;其 SHA-256
|
||||||
|
`74c01764987c3dce0601a20cdf494f8076102f6d54262a1d13df953abee96160` 已绑定到最终
|
||||||
|
`v1-user-loop` release record。desktop USD 使用经发布 SHA-256 校验的官方 Blender 5.2.0 LTS,
|
||||||
|
runtime `USD=True`。最终离线 binary/source archive SHA-256 分别为
|
||||||
|
`f2903a75026a0a28615aeb5f4834a3859af83022c82bc50e28437dcbed68e549` 和
|
||||||
|
`9a9c1b505c68a618de842be7c248f628b104b11b80e8a40d111f62540fcba327`。
|
||||||
|
|
||||||
|
`M6-01` 已新增机器可读部署合同并让 Vite/离线包生成器复用该事实源。`M6-02` 已新增纯
|
||||||
|
pthread 能力门:`crossOriginIsolated`、`SharedArrayBuffer` 和 `Worker` 任一缺失均返回稳定的
|
||||||
|
`PLATFORM_CAPABILITY_UNAVAILABLE`,且 single-thread 初始化路径不调用该门。下一领取点是
|
||||||
|
`CURRENT_EXECUTION_PLAN.md` 的 `M6-03`:验证 single-thread 引擎在无隔离环境启动。
|
||||||
|
|
||||||
## 6. 当前验收命令
|
## 6. 当前验收命令
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
@@ -253,6 +270,12 @@ npm --prefix web run test:browser
|
|||||||
npm --prefix web run release:offline
|
npm --prefix web run release:offline
|
||||||
npm --prefix web run release:evidence
|
npm --prefix web run release:evidence
|
||||||
npm --prefix web run test:release-evidence
|
npm --prefix web run test:release-evidence
|
||||||
|
npm --prefix web run test:v1-acceptance-coverage
|
||||||
|
BLENDER_BIN=/path/to/usd-enabled/blender \
|
||||||
|
BLENDER_ARCHIVE_SHA256=<sha256> npm --prefix web run release:v1-acceptance
|
||||||
|
npm --prefix web run test:v1-acceptance-evidence
|
||||||
|
npm --prefix web run test:v1-acceptance-release-binding
|
||||||
|
node tools/web/check-deployment-contract.mjs
|
||||||
WEB_TEST_PORT=5193 npm --prefix web run test:e2e # 5173 被占用时指定可用端口
|
WEB_TEST_PORT=5193 npm --prefix web run test:e2e # 5173 被占用时指定可用端口
|
||||||
WEB_TEST_PORT=5193 npm --prefix web run test:e2e -- -g "N-015|non-mesh"
|
WEB_TEST_PORT=5193 npm --prefix web run test:e2e -- -g "N-015|non-mesh"
|
||||||
```
|
```
|
||||||
|
|||||||
@@ -2,7 +2,7 @@
|
|||||||
"schemaVersion": 4,
|
"schemaVersion": 4,
|
||||||
"source": "docs/status/parity-ledger.json",
|
"source": "docs/status/parity-ledger.json",
|
||||||
"sourceSha256": "1e16518a25c6b6bed4ab7f605ea1ad5babe529f2842c858b20ae6daae30bd478",
|
"sourceSha256": "1e16518a25c6b6bed4ab7f605ea1ad5babe529f2842c858b20ae6daae30bd478",
|
||||||
"generatedAt": "2026-08-15T02:29:05.407Z",
|
"generatedAt": "2026-08-15T04:43:09.832Z",
|
||||||
"families": [
|
"families": [
|
||||||
{
|
{
|
||||||
"id": "N-015",
|
"id": "N-015",
|
||||||
@@ -676,12 +676,12 @@
|
|||||||
],
|
],
|
||||||
"command": "npm --prefix web run release:sbom",
|
"command": "npm --prefix web run release:sbom",
|
||||||
"exitCode": 0,
|
"exitCode": 0,
|
||||||
"durationMs": 372,
|
"durationMs": 343,
|
||||||
"output": "> blender-web-editor@0.1.0 release:sbom\n> node ../tools/web/generate-sbom.mjs\n\nsbom-ok packages=150 sha256=8b04215a993f62ba64e0adcb1ba36ac830191f8215bf8cf2681fdf7bbb63da30",
|
"output": "> blender-web-editor@0.1.0 release:sbom\n> node ../tools/web/generate-sbom.mjs\n\nsbom-ok packages=150 sha256=aa50a5b98ebb51c98c6eced2ec32dff96b8df6d90e10cc101ce9e8eb44695060",
|
||||||
"artifactSha256": [
|
"artifactSha256": [
|
||||||
"8b04215a993f62ba64e0adcb1ba36ac830191f8215bf8cf2681fdf7bbb63da30",
|
"aa50a5b98ebb51c98c6eced2ec32dff96b8df6d90e10cc101ce9e8eb44695060",
|
||||||
"d5ec6e6ec9e1e0a50fe4455513a2938838609d3809224eaf26a2ebfac474f0ae",
|
"d5ec6e6ec9e1e0a50fe4455513a2938838609d3809224eaf26a2ebfac474f0ae",
|
||||||
"3c65ff96cc15c9072d2517555484ccfd0009a2814d96faadcc5bd7b2e3458503"
|
"e7b16da68885fe43f32ab7b3eeb2c037b4fd8bf54fe6d5e77049aa51e294b1fb"
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@@ -689,14 +689,27 @@
|
|||||||
"fields": [],
|
"fields": [],
|
||||||
"command": "npm --prefix web run test:vdb-availability && npm --prefix web run test:vdb-native && npm --prefix web run test:vdb",
|
"command": "npm --prefix web run test:vdb-availability && npm --prefix web run test:vdb-native && npm --prefix web run test:vdb",
|
||||||
"exitCode": 0,
|
"exitCode": 0,
|
||||||
"durationMs": 5823,
|
"durationMs": 5619,
|
||||||
"output": "> blender-web-editor@0.1.0 test:vdb-availability\n> node ../tools/web/check-vdb-availability.mjs\n\nvdb-availability-ok core=READY release=BLOCKED browser-openvdb=disabled desktop=ready server=ready opfs=ready webgpu-core=ready main-roundtrip=ready viewport=blocked advanced-material=blocked resources=9\n\n> blender-web-editor@0.1.0 test:vdb-native\n> node ../tools/web/check-vdb-native-pipeline.mjs\n\nvdb-native-pipeline-ok resources=14 converter=openvdb13-nanovdb32 conversion-deterministic=1 semantic-deterministic=1 official-deterministic=1 malformed=blocked browser-openvdb=off\n\n> blender-web-editor@0.1.0 test:vdb\n> playwright test --config playwright.config.ts -g \"VDB conversion boundary\"\n\n\nRunning 1 test using 1 worker\n\n ✓ 1 tests/e2e/smoke.spec.ts:1156:1 › validates the VDB conversion boundary and NanoVDB streaming contract (1.5s)\n\n 1 passed (3.3s)\n\n[WebServer] (node:1987436) Warning: The 'NO_COLOR' env is ignored due to the 'FORCE_COLOR' env being set.\n[WebServer] (Use `node --trace-warnings ...` to show where the warning was created)\n[WebServer] (node:1987453) Warning: The 'NO_COLOR' env is ignored due to the 'FORCE_COLOR' env being set.\n[WebServer] (Use `node --trace-warnings ...` to show where the warning was created)",
|
"output": "> blender-web-editor@0.1.0 test:vdb-availability\n> node ../tools/web/check-vdb-availability.mjs\n\nvdb-availability-ok core=READY release=BLOCKED browser-openvdb=disabled desktop=ready server=ready opfs=ready webgpu-core=ready main-roundtrip=ready viewport=blocked advanced-material=blocked resources=9\n\n> blender-web-editor@0.1.0 test:vdb-native\n> node ../tools/web/check-vdb-native-pipeline.mjs\n\nvdb-native-pipeline-ok resources=14 converter=openvdb13-nanovdb32 conversion-deterministic=1 semantic-deterministic=1 official-deterministic=1 malformed=blocked browser-openvdb=off\n\n> blender-web-editor@0.1.0 test:vdb\n> playwright test --config playwright.config.ts -g \"VDB conversion boundary\"\n\n\nRunning 1 test using 1 worker\n\n ✓ 1 tests/e2e/smoke.spec.ts:1156:1 › validates the VDB conversion boundary and NanoVDB streaming contract (1.5s)\n\n 1 passed (3.2s)\n\n[WebServer] (node:2314584) Warning: The 'NO_COLOR' env is ignored due to the 'FORCE_COLOR' env being set.\n[WebServer] (Use `node --trace-warnings ...` to show where the warning was created)\n[WebServer] (node:2314596) Warning: The 'NO_COLOR' env is ignored due to the 'FORCE_COLOR' env being set.\n[WebServer] (Use `node --trace-warnings ...` to show where the warning was created)",
|
||||||
"artifactSha256": [
|
"artifactSha256": [
|
||||||
"885edcc156f87abdbddb0e70fde5566130acfc2770a0b6764d66d12c344973a6",
|
"885edcc156f87abdbddb0e70fde5566130acfc2770a0b6764d66d12c344973a6",
|
||||||
"7b2d576a613dff314f4b40177f10efa3c01e160b7aeb10bf95a69f6553f8f73b",
|
"7b2d576a613dff314f4b40177f10efa3c01e160b7aeb10bf95a69f6553f8f73b",
|
||||||
"a4c57345a4ea7302b9e2a7e0f8b0ed48f8f27c3ac70d41e02f6d92475a751298",
|
"a4c57345a4ea7302b9e2a7e0f8b0ed48f8f27c3ac70d41e02f6d92475a751298",
|
||||||
"179868fde557873b4c74c48e2f0d3199784efb6309ed00dd17310b494b975483",
|
"179868fde557873b4c74c48e2f0d3199784efb6309ed00dd17310b494b975483",
|
||||||
"a13cf2747dacfbf4c0e6b50dd341db3adac32c7c3c9c07814349a90aa4436a3b"
|
"379dcc11dc7de0dfec4745763bddf3cc415a5f5dbe48500fefae2f7508095bb5"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "v1-user-loop",
|
||||||
|
"fields": [],
|
||||||
|
"command": "npm --prefix web run test:v1-user-loop && npm --prefix web run test:v1-acceptance-evidence",
|
||||||
|
"exitCode": 0,
|
||||||
|
"durationMs": 5366,
|
||||||
|
"output": "> blender-web-editor@0.1.0 test:v1-user-loop\n> playwright test --config playwright.config.ts tests/e2e/v1-user-loop.spec.ts\n\n\nRunning 1 test using 1 worker\n\n ✓ 1 tests/e2e/v1-user-loop.spec.ts:6:1 › completes the V1 import-edit-save-restart-reopen-GLB loop (2.7s)\n\n 1 passed (4.2s)\n\n> blender-web-editor@0.1.0 test:v1-acceptance-evidence\n> node ../tools/web/check-v1-acceptance-evidence.mjs\n\nv1-acceptance-evidence-ok families=12 declarations=50 unique=49 sha256=74c01764987c3dce0601a20cdf494f8076102f6d54262a1d13df953abee96160\n\n[WebServer] (node:2314858) Warning: The 'NO_COLOR' env is ignored due to the 'FORCE_COLOR' env being set.\n[WebServer] (Use `node --trace-warnings ...` to show where the warning was created)\n[WebServer] (node:2314870) Warning: The 'NO_COLOR' env is ignored due to the 'FORCE_COLOR' env being set.\n[WebServer] (Use `node --trace-warnings ...` to show where the warning was created)",
|
||||||
|
"artifactSha256": [
|
||||||
|
"3defbaa426195254fcaefaddabf9b128002a94def03aa255513c33eaf4ac0926",
|
||||||
|
"5d87a9ea57bd7a1888f16a5f4306e1c6d3a1bdfcf832c9485fe8518aac528fd8",
|
||||||
|
"74c01764987c3dce0601a20cdf494f8076102f6d54262a1d13df953abee96160"
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@@ -709,8 +722,8 @@
|
|||||||
],
|
],
|
||||||
"command": "npm --prefix web run test:e2e -- --workers=1 && npm --prefix web run test:browser",
|
"command": "npm --prefix web run test:e2e -- --workers=1 && npm --prefix web run test:browser",
|
||||||
"exitCode": 0,
|
"exitCode": 0,
|
||||||
"durationMs": 219085,
|
"durationMs": 366958,
|
||||||
"output": " (1.4s)\n ✓ 95 tests/e2e/smoke.spec.ts:2607:1 › reports GLB export blockers before any binary export (1.1s)\n ✓ 96 tests/e2e/smoke.spec.ts:2619:1 › blocks Shader graphs that cannot be mapped to glTF PBR (1.0s)\n ✓ 97 tests/e2e/smoke.spec.ts:2646:1 › maps bounded RGB and Value Shader constants to glTF PBR factors (1.0s)\n ✓ 98 tests/e2e/smoke.spec.ts:2679:1 › exports a local SceneIR mesh as a standards-shaped GLB (1.0s)\n ✓ 99 tests/e2e/smoke.spec.ts:2712:1 › keeps per-vertex UV and color attributes in GLB output (1.1s)\n ✓ 100 tests/e2e/smoke.spec.ts:2739:1 › evaluates modifier dependency order and blocks unevaluated or cyclic stacks (1.1s)\n ✓ 101 tests/e2e/smoke.spec.ts:2754:1 › embeds local textures and exports glTF skin and animation records (1.1s)\n ✓ 102 tests/e2e/smoke.spec.ts:2804:1 › reports lightweight budget violations without altering usage (1.1s)\n ✓ 103 tests/e2e/smoke.spec.ts:2822:1 › aggregates project, collection, object and LOD budgets without double counting LOD (1.0s)\n ✓ 104 tests/e2e/smoke.spec.ts:2848:1 › round-trips LOD geometry through the local binary mesh cache container (1.0s)\n ✓ 105 tests/e2e/smoke.spec.ts:2872:1 › blocks ImageIR paths outside the project asset sandbox (1.3s)\n ✓ 106 tests/e2e/smoke.spec.ts:2915:1 › extracts Blender packed image bytes through the local asset request API (1.4s)\n ✓ 107 tests/e2e/smoke.spec.ts:2960:1 › matches Blender Depsgraph deformation golden within the declared error budget (1.4s)\n ✓ 108 tests/e2e/smoke.spec.ts:2999:1 › evaluates the full Blender Depsgraph or reports its safe capability gate (1.4s)\n ✓ 109 tests/e2e/smoke.spec.ts:3059:1 › exports layered Action keyframes through SceneIR (1.3s)\n ✓ 110 tests/e2e/smoke.spec.ts:3100:1 › patches changed mesh buffer ranges without replacing stable topology (1.1s)\n ✓ 111 tests/e2e/smoke.spec.ts:3124:1 › renders through the capability-gated OffscreenCanvas worker (1.4s)\n ✓ 112 tests/e2e/smoke.spec.ts:3137:1 › coalesces linked mesh objects into a raycastable instance group (1.9s)\n ✓ 113 tests/e2e/smoke.spec.ts:3145:1 › returns structured gates for the undeclared capability protocols (1.2s)\n ✓ 114 tests/e2e/smoke.spec.ts:3204:1 › exposes PBR-007 to PBR-012 renderer security gates (1.1s)\n ✓ 115 tests/e2e/smoke.spec.ts:3238:1 › transfers packed raster assets into the PBR viewport with an explicit status (1.7s)\n ✓ 116 tests/e2e/smoke.spec.ts:3247:1 › uses the same packed texture payload in the OffscreenCanvas renderer (1.8s)\n ✓ 117 tests/e2e/texture-4k-performance.spec.ts:3:1 › decodes, uploads and renders a validated 4K texture in Chromium (1.8s)\n ✓ 118 tests/e2e/texture-8k-performance.spec.ts:3:1 › decodes, uploads and renders a validated 8K texture in Chromium (4.2s)\n\n 118 passed (3.5m)\n\n> blender-web-editor@0.1.0 test:browser\n> playwright test --config playwright.release.config.ts\n\n\nRunning 3 tests using 1 worker\n\n ✓ 1 [chromium] › tests/e2e/cross-browser.spec.ts:6:1 › boots the offline engine, renders SceneIR and performs a Main edit (2.8s)\n ✓ 2 [chromium] › tests/e2e/cross-browser.spec.ts:32:1 › keeps content-addressed asset recovery available in Chromium (1.0s)\n ✓ 3 [chromium] › tests/e2e/cross-browser.spec.ts:51:1 › keeps the committed project after quota failure and Worker restart in Chromium (1.1s)\n\n 3 passed (6.6s)\n\n[WebServer] (node:1987737) Warning: The 'NO_COLOR' env is ignored due to the 'FORCE_COLOR' env being set.\n[WebServer] (Use `node --trace-warnings ...` to show where the warning was created)\n[WebServer] (node:1987749) Warning: The 'NO_COLOR' env is ignored due to the 'FORCE_COLOR' env being set.\n[WebServer] (Use `node --trace-warnings ...` to show where the warning was created)\n[WebServer] (node:1993877) Warning: The 'NO_COLOR' env is ignored due to the 'FORCE_COLOR' env being set.\n[WebServer] (Use `node --trace-warnings ...` to show where the warning was created)\n[WebServer] (node:1993889) Warning: The 'NO_COLOR' env is ignored due to the 'FORCE_COLOR' env being set.\n[WebServer] (Use `node --trace-warnings ...` to show where the warning was created)",
|
"output": "udgets without double counting LOD (1.0s)\n ✓ 107 tests/e2e/smoke.spec.ts:2874:1 › round-trips LOD geometry through the local binary mesh cache container (993ms)\n ✓ 108 tests/e2e/smoke.spec.ts:2898:1 › blocks ImageIR paths outside the project asset sandbox (1.3s)\n ✓ 109 tests/e2e/smoke.spec.ts:2941:1 › extracts Blender packed image bytes through the local asset request API (1.3s)\n ✓ 110 tests/e2e/smoke.spec.ts:2986:1 › matches Blender Depsgraph deformation golden within the declared error budget (1.4s)\n ✓ 111 tests/e2e/smoke.spec.ts:3025:1 › evaluates the full Blender Depsgraph or reports its safe capability gate (1.3s)\n ✓ 112 tests/e2e/smoke.spec.ts:3085:1 › exports layered Action keyframes through SceneIR (1.3s)\n ✓ 113 tests/e2e/smoke.spec.ts:3126:1 › patches changed mesh buffer ranges without replacing stable topology (1.0s)\n ✓ 114 tests/e2e/smoke.spec.ts:3150:1 › renders through the capability-gated OffscreenCanvas worker (1.3s)\n ✓ 115 tests/e2e/smoke.spec.ts:3163:1 › coalesces linked mesh objects into a raycastable instance group (1.8s)\n ✓ 116 tests/e2e/smoke.spec.ts:3171:1 › returns structured gates for the undeclared capability protocols (1.1s)\n ✓ 117 tests/e2e/smoke.spec.ts:3230:1 › exposes PBR-007 to PBR-012 renderer security gates (1.0s)\n ✓ 118 tests/e2e/smoke.spec.ts:3264:1 › transfers packed raster assets into the PBR viewport with an explicit status (1.6s)\n ✓ 119 tests/e2e/smoke.spec.ts:3273:1 › uses the same packed texture payload in the OffscreenCanvas renderer (1.3s)\ntexture-4k-performance {\"width\":4096,\"height\":4096,\"status\":{\"loaded\":1,\"rejected\":0,\"bytes\":254670,\"errors\":[],\"errorCodes\":[]},\"byteLength\":254670,\"estimatedPeakGpuBytes\":67108864,\"released\":true,\"recoveryVisible\":true,\"colored\":4096,\"elapsedMs\":705}\n ✓ 120 tests/e2e/texture-4k-performance.spec.ts:3:1 › decodes, uploads and renders a validated 4K texture in Chromium (1.8s)\ntexture-8k-performance {\"supported\":true,\"maxTextureSize\":8192,\"width\":8192,\"height\":8192,\"status\":{\"loaded\":1,\"rejected\":0,\"bytes\":972714,\"errors\":[],\"errorCodes\":[]},\"byteLength\":972714,\"estimatedPeakGpuBytes\":268435456,\"released\":true,\"recoveryVisible\":true,\"colored\":4096,\"elapsedMs\":2650}\n ✓ 121 tests/e2e/texture-8k-performance.spec.ts:3:1 › decodes, uploads and renders a validated 8K texture in Chromium (4.0s)\n ✓ 122 tests/e2e/v1-user-loop.spec.ts:6:1 › completes the V1 import-edit-save-restart-reopen-GLB loop (2.3s)\n\n Slow test file: tests/e2e/smoke.spec.ts (5.5m)\n Consider running tests from slow files in parallel. See: https://playwright.dev/docs/test-parallel\n 122 passed (6.0m)\n\n> blender-web-editor@0.1.0 test:browser\n> playwright test --config playwright.release.config.ts\n\n\nRunning 3 tests using 1 worker\n\n ✓ 1 [chromium] › tests/e2e/cross-browser.spec.ts:6:1 › boots the offline engine, renders SceneIR and performs a Main edit (2.7s)\n ✓ 2 [chromium] › tests/e2e/cross-browser.spec.ts:32:1 › keeps content-addressed asset recovery available in Chromium (1.1s)\n ✓ 3 [chromium] › tests/e2e/cross-browser.spec.ts:51:1 › keeps the committed project after quota failure and Worker restart in Chromium (1.1s)\n\n 3 passed (6.5s)\n\n[WebServer] (node:2315163) Warning: The 'NO_COLOR' env is ignored due to the 'FORCE_COLOR' env being set.\n[WebServer] (Use `node --trace-warnings ...` to show where the warning was created)\n[WebServer] (node:2315175) Warning: The 'NO_COLOR' env is ignored due to the 'FORCE_COLOR' env being set.\n[WebServer] (Use `node --trace-warnings ...` to show where the warning was created)\n(node:2315221) Warning: The 'NO_COLOR' env is ignored due to the 'FORCE_COLOR' env being set.\n(Use `node --trace-warnings ...` to show where the warning was created)\n[WebServer] (node:2321103) Warning: The 'NO_COLOR' env is ignored due to the 'FORCE_COLOR' env being set.\n[WebServer] (Use `node --trace-warnings ...` to show where the warning was created)\n[WebServer] (node:2321115) Warning: The 'NO_COLOR' env is ignored due to the 'FORCE_COLOR' env being set.\n[WebServer] (Use `node --trace-warnings ...` to show where the warning was created)",
|
||||||
"artifactSha256": [
|
"artifactSha256": [
|
||||||
"5d87a9ea57bd7a1888f16a5f4306e1c6d3a1bdfcf832c9485fe8518aac528fd8"
|
"5d87a9ea57bd7a1888f16a5f4306e1c6d3a1bdfcf832c9485fe8518aac528fd8"
|
||||||
]
|
]
|
||||||
@@ -722,12 +735,45 @@
|
|||||||
],
|
],
|
||||||
"command": "npm --prefix web run test:release-performance",
|
"command": "npm --prefix web run test:release-performance",
|
||||||
"exitCode": 0,
|
"exitCode": 0,
|
||||||
"durationMs": 93399,
|
"durationMs": 84015,
|
||||||
"output": "> blender-web-editor@0.1.0 test:release-performance\n> node ../tools/web/check-release-performance.mjs\n\nrelease-performance-ok [{\"target\":100000,\"ratio\":0.9,\"outputTriangles\":89999,\"elapsedMs\":92308,\"heapBytes\":67108864},{\"target\":1000000,\"ratio\":1,\"outputTriangles\":1000000,\"elapsedMs\":540,\"heapBytes\":346554368}]\n\nHeap resize call from 67108864 to 80543744 took 0.22668800000974443 msecs. Success: true\nHeap resize call from 80543744 to 96665600 took 0.08722699999634642 msecs. Success: true\nHeap resize call from 96665600 to 115998720 took 0.04216300000553019 msecs. Success: true\nHeap resize call from 115998720 to 139198464 took 1.3969159999978729 msecs. Success: true\nHeap resize call from 139198464 to 167051264 took 1.700714999999036 msecs. Success: true\nHeap resize call from 167051264 to 200474624 took 1.4438179999997374 msecs. Success: true\nHeap resize call from 200474624 to 240582656 took 1.4305880000028992 msecs. Success: true\nHeap resize call from 240582656 to 288751616 took 1.5049569999973755 msecs. Success: true\nHeap resize call from 288751616 to 346554368 took 1.4472269999969285 msecs. Success: true",
|
"output": "> blender-web-editor@0.1.0 test:release-performance\n> node ../tools/web/check-release-performance.mjs\n\nrelease-performance-ok [{\"target\":100000,\"ratio\":0.9,\"inputTriangles\":100000,\"outputTriangles\":89999,\"elapsedMs\":82975,\"peakHeapBytes\":67108864,\"releasedPointerCount\":10,\"recoveryAllocation\":true},{\"target\":1000000,\"ratio\":1,\"inputTriangles\":1000000,\"outputTriangles\":1000000,\"elapsedMs\":518,\"peakHeapBytes\":346554368,\"releasedPointerCount\":10,\"recoveryAllocation\":true}]\n\nHeap resize call from 67108864 to 80543744 took 0.18726400000741705 msecs. Success: true\nHeap resize call from 80543744 to 96665600 took 0.08590900000126567 msecs. Success: true\nHeap resize call from 96665600 to 115998720 took 0.04599199999938719 msecs. Success: true\nHeap resize call from 115998720 to 139198464 took 0.059525000004214235 msecs. Success: true\nHeap resize call from 139198464 to 167051264 took 1.4840919999987818 msecs. Success: true\nHeap resize call from 167051264 to 200474624 took 1.4312449999997625 msecs. Success: true\nHeap resize call from 200474624 to 240582656 took 1.4865769999887561 msecs. Success: true\nHeap resize call from 240582656 to 288751616 took 1.4735839999921154 msecs. Success: true\nHeap resize call from 288751616 to 346554368 took 1.4832890000107 msecs. Success: true",
|
||||||
"artifactSha256": [
|
"artifactSha256": [
|
||||||
"5d87a9ea57bd7a1888f16a5f4306e1c6d3a1bdfcf832c9485fe8518aac528fd8"
|
"5d87a9ea57bd7a1888f16a5f4306e1c6d3a1bdfcf832c9485fe8518aac528fd8"
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"id": "geometry-10m",
|
||||||
|
"fields": [
|
||||||
|
"performance.geometry10M"
|
||||||
|
],
|
||||||
|
"command": "npm --prefix web run test:geometry-10m-performance",
|
||||||
|
"exitCode": 0,
|
||||||
|
"durationMs": 5565,
|
||||||
|
"output": "> blender-web-editor@0.1.0 test:geometry-10m-performance\n> playwright test --config playwright.config.ts tests/e2e/geometry-10m-performance.spec.ts\n\n\nRunning 1 test using 1 worker\n\ngeometry-10m-performance {\"triangleCount\":10000000,\"chunkCount\":40,\"transferredBytes\":180492000,\"peakWorkingSetBytes\":4512300,\"manifestSha256\":\"224738e0c3aa28ae42167bf8974455a3d0f940f59f19eb51c5768f435c66b24f\",\"firstInteractiveMs\":1002,\"chunksAtFirstInteractive\":18,\"estimatedGpuBytes\":1124880,\"wasmAllocatedBytes\":0,\"baselineHeapBytes\":40421007,\"peakHeapBytes\":128051538,\"afterHeapBytes\":99970221,\"cacheDeleted\":true,\"recoveryVisible\":2372,\"cancelLatencyMs\":51,\"elapsedMs\":1681}\n ✓ 1 tests/e2e/geometry-10m-performance.spec.ts:3:1 › streams ten-million-triangle SceneIR ranges into a bounded interactive LOD (3.2s)\n\n 1 passed (4.7s)\n\n[WebServer] (node:2321602) Warning: The 'NO_COLOR' env is ignored due to the 'FORCE_COLOR' env being set.\n[WebServer] (Use `node --trace-warnings ...` to show where the warning was created)\n[WebServer] (node:2321614) Warning: The 'NO_COLOR' env is ignored due to the 'FORCE_COLOR' env being set.\n[WebServer] (Use `node --trace-warnings ...` to show where the warning was created)\n(node:2321660) Warning: The 'NO_COLOR' env is ignored due to the 'FORCE_COLOR' env being set.\n(Use `node --trace-warnings ...` to show where the warning was created)",
|
||||||
|
"artifactSha256": [
|
||||||
|
"6f2708499d59a5e0e90e4581359d6131428c22da0da1543bb4aa0fc38e5e6b29",
|
||||||
|
"04743733e385f33efbb144b0c6cd831a21840dad9b6e0715e8a73791d8a95e33",
|
||||||
|
"969bda967852622b78bd5c0a56b0c0832bf1e46b19fa09b6b26316f2c367fdaf",
|
||||||
|
"f7501e2b4ad6f8df11167197b0d10f77f16b9b385459937011ab486d6b145157"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "long-media",
|
||||||
|
"fields": [
|
||||||
|
"performance.longMedia"
|
||||||
|
],
|
||||||
|
"command": "npm --prefix web run test:long-media-performance",
|
||||||
|
"exitCode": 0,
|
||||||
|
"durationMs": 4707,
|
||||||
|
"output": "> blender-web-editor@0.1.0 test:long-media-performance\n> playwright test --config playwright.config.ts tests/e2e/long-media-performance.spec.ts\n\n\nRunning 1 test using 1 worker\n\nlong-media-performance {\"frameCount\":1000000,\"stripCount\":10001,\"referenceCount\":11914,\"indexBuildMs\":207,\"coldSeekMs\":1,\"hotSeekMs\":0,\"randomSeekMs\":6,\"cacheBytes\":16305,\"evictions\":71,\"releasedCacheBytes\":16305,\"cacheBytesAfterDispose\":0,\"restartedReleasedCacheBytes\":16305,\"restartedCacheBytesAfterDispose\":0,\"workerRestartRecovered\":true,\"manifestSha256\":\"d2e7e3c5ed9ae4fda6fe358cebb474f5774f1d2037dff3df9b6dfde9e0bebd2d\",\"elapsedMs\":861}\n ✓ 1 tests/e2e/long-media-performance.spec.ts:8:1 › indexes, seeks, cancels and reopens a bounded one-million-frame media timeline (2.3s)\n\n 1 passed (3.9s)\n\n[WebServer] (node:2321890) Warning: The 'NO_COLOR' env is ignored due to the 'FORCE_COLOR' env being set.\n[WebServer] (Use `node --trace-warnings ...` to show where the warning was created)\n[WebServer] (node:2321902) Warning: The 'NO_COLOR' env is ignored due to the 'FORCE_COLOR' env being set.\n[WebServer] (Use `node --trace-warnings ...` to show where the warning was created)\n(node:2321948) Warning: The 'NO_COLOR' env is ignored due to the 'FORCE_COLOR' env being set.\n(Use `node --trace-warnings ...` to show where the warning was created)",
|
||||||
|
"artifactSha256": [
|
||||||
|
"3427a057e564c9e1b16d464d06e65cde90ae8557f4f51cb98ad43774658e8dca",
|
||||||
|
"70faaf13e2a55af6ff40951ef29a27f7b391ed6dd55310cb2283678cc9b8a993",
|
||||||
|
"42be8d81cb0fc170986fcf187666e8bd8b93fb5992d7e13d1d569ba1a30da1f3",
|
||||||
|
"295b083db2299ab904947eb39c17173de70c211882b0d855537d8f3cc27698ed",
|
||||||
|
"56d4af65701c26df20bd4021eda95b6e830348ce3a746086079fe89285548dc9"
|
||||||
|
]
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"id": "simulation-cache",
|
"id": "simulation-cache",
|
||||||
"fields": [
|
"fields": [
|
||||||
@@ -735,8 +781,8 @@
|
|||||||
],
|
],
|
||||||
"command": "npm --prefix web run test:simulation-cache-performance",
|
"command": "npm --prefix web run test:simulation-cache-performance",
|
||||||
"exitCode": 0,
|
"exitCode": 0,
|
||||||
"durationMs": 7549,
|
"durationMs": 7118,
|
||||||
"output": "> blender-web-editor@0.1.0 test:simulation-cache-performance\n> playwright test --config playwright.config.ts tests/e2e/simulation-cache-performance.spec.ts\n\n\nRunning 1 test using 1 worker\n\n ✓ 1 tests/e2e/simulation-cache-performance.spec.ts:3:1 › meets the Chromium OPFS Simulation cache playback performance gate (5.1s)\n\n 1 passed (6.7s)\n\n[WebServer] (node:1994548) Warning: The 'NO_COLOR' env is ignored due to the 'FORCE_COLOR' env being set.\n[WebServer] (Use `node --trace-warnings ...` to show where the warning was created)\n[WebServer] (node:1994560) Warning: The 'NO_COLOR' env is ignored due to the 'FORCE_COLOR' env being set.\n[WebServer] (Use `node --trace-warnings ...` to show where the warning was created)",
|
"output": "> blender-web-editor@0.1.0 test:simulation-cache-performance\n> playwright test --config playwright.config.ts tests/e2e/simulation-cache-performance.spec.ts\n\n\nRunning 1 test using 1 worker\n\nsimulation-cache-performance {\"backend\":\"opfs\",\"frameCount\":600,\"byteLength\":52800,\"status\":\"COMPLETED\",\"appliedFrames\":600,\"lastFrame\":600,\"publishedFrames\":600,\"lastTranslation\":60,\"storeMs\":78,\"playbackMs\":3171,\"elapsedMs\":3249,\"peakCacheBytes\":52800,\"pendingRequestsAfterTerminate\":0,\"workerRestartRecovered\":true}\n ✓ 1 tests/e2e/simulation-cache-performance.spec.ts:3:1 › meets the Chromium OPFS Simulation cache playback performance gate (4.7s)\n\n 1 passed (6.3s)\n\n[WebServer] (node:2322188) Warning: The 'NO_COLOR' env is ignored due to the 'FORCE_COLOR' env being set.\n[WebServer] (Use `node --trace-warnings ...` to show where the warning was created)\n[WebServer] (node:2322200) Warning: The 'NO_COLOR' env is ignored due to the 'FORCE_COLOR' env being set.\n[WebServer] (Use `node --trace-warnings ...` to show where the warning was created)\n(node:2322246) Warning: The 'NO_COLOR' env is ignored due to the 'FORCE_COLOR' env being set.\n(Use `node --trace-warnings ...` to show where the warning was created)",
|
||||||
"artifactSha256": [
|
"artifactSha256": [
|
||||||
"c8074425a4556c8d08c2382dc9fd7be8aa6078d11a45640c4b19bf6ef45aa590",
|
"c8074425a4556c8d08c2382dc9fd7be8aa6078d11a45640c4b19bf6ef45aa590",
|
||||||
"331d5c6a59dc80a12cc7105abbd60d25a64a113809eae9ad3d4061d81ec5f59f",
|
"331d5c6a59dc80a12cc7105abbd60d25a64a113809eae9ad3d4061d81ec5f59f",
|
||||||
@@ -750,7 +796,7 @@
|
|||||||
],
|
],
|
||||||
"command": "npm --prefix web run test:malicious-blends",
|
"command": "npm --prefix web run test:malicious-blends",
|
||||||
"exitCode": 0,
|
"exitCode": 0,
|
||||||
"durationMs": 541,
|
"durationMs": 510,
|
||||||
"output": "> blender-web-editor@0.1.0 test:malicious-blends\n> node ../tools/web/check-malicious-blends.mjs\n\nmalicious-blends-ok rejected=5",
|
"output": "> blender-web-editor@0.1.0 test:malicious-blends\n> node ../tools/web/check-malicious-blends.mjs\n\nmalicious-blends-ok rejected=5",
|
||||||
"artifactSha256": [
|
"artifactSha256": [
|
||||||
"6fcc55bda74da8c95da96ba068b60339bf60ca44147ff8000fbb95d296db2a73"
|
"6fcc55bda74da8c95da96ba068b60339bf60ca44147ff8000fbb95d296db2a73"
|
||||||
@@ -763,8 +809,8 @@
|
|||||||
],
|
],
|
||||||
"command": "npm --prefix web run test:network-interruption",
|
"command": "npm --prefix web run test:network-interruption",
|
||||||
"exitCode": 0,
|
"exitCode": 0,
|
||||||
"durationMs": 5632,
|
"durationMs": 5448,
|
||||||
"output": "> blender-web-editor@0.1.0 test:network-interruption\n> playwright test --config playwright.config.ts tests/e2e/network-interruption.spec.ts\n\n\nRunning 1 test using 1 worker\n\n ✓ 1 tests/e2e/network-interruption.spec.ts:6:1 › keeps Main edit and save-reopen available during a Chromium network interruption (3.1s)\n\n 1 passed (4.7s)\n\n[WebServer] (node:1994872) Warning: The 'NO_COLOR' env is ignored due to the 'FORCE_COLOR' env being set.\n[WebServer] (Use `node --trace-warnings ...` to show where the warning was created)\n[WebServer] (node:1994884) Warning: The 'NO_COLOR' env is ignored due to the 'FORCE_COLOR' env being set.\n[WebServer] (Use `node --trace-warnings ...` to show where the warning was created)",
|
"output": "> blender-web-editor@0.1.0 test:network-interruption\n> playwright test --config playwright.config.ts tests/e2e/network-interruption.spec.ts\n\n\nRunning 1 test using 1 worker\n\n ✓ 1 tests/e2e/network-interruption.spec.ts:6:1 › keeps Main edit and save-reopen available during a Chromium network interruption (3.0s)\n\n 1 passed (4.6s)\n\n[WebServer] (node:2322506) Warning: The 'NO_COLOR' env is ignored due to the 'FORCE_COLOR' env being set.\n[WebServer] (Use `node --trace-warnings ...` to show where the warning was created)\n[WebServer] (node:2322518) Warning: The 'NO_COLOR' env is ignored due to the 'FORCE_COLOR' env being set.\n[WebServer] (Use `node --trace-warnings ...` to show where the warning was created)",
|
||||||
"artifactSha256": [
|
"artifactSha256": [
|
||||||
"b1c03863f44026d44691042536ff1d1095617a35c8d767cc70372e5cbdc70db5",
|
"b1c03863f44026d44691042536ff1d1095617a35c8d767cc70372e5cbdc70db5",
|
||||||
"5d87a9ea57bd7a1888f16a5f4306e1c6d3a1bdfcf832c9485fe8518aac528fd8"
|
"5d87a9ea57bd7a1888f16a5f4306e1c6d3a1bdfcf832c9485fe8518aac528fd8"
|
||||||
@@ -777,13 +823,32 @@
|
|||||||
],
|
],
|
||||||
"command": "npm --prefix web run test:device-loss",
|
"command": "npm --prefix web run test:device-loss",
|
||||||
"exitCode": 0,
|
"exitCode": 0,
|
||||||
"durationMs": 5138,
|
"durationMs": 4948,
|
||||||
"output": "> blender-web-editor@0.1.0 test:device-loss\n> playwright test --config playwright.config.ts tests/e2e/device-loss.spec.ts\n\n\nRunning 1 test using 1 worker\n\n ✓ 1 tests/e2e/device-loss.spec.ts:6:1 › recovers the main-thread Chromium viewport after a real WebGL context loss (2.6s)\n\n 1 passed (4.2s)\n\n[WebServer] (node:1995187) Warning: The 'NO_COLOR' env is ignored due to the 'FORCE_COLOR' env being set.\n[WebServer] (Use `node --trace-warnings ...` to show where the warning was created)\n[WebServer] (node:1995199) Warning: The 'NO_COLOR' env is ignored due to the 'FORCE_COLOR' env being set.\n[WebServer] (Use `node --trace-warnings ...` to show where the warning was created)",
|
"output": "> blender-web-editor@0.1.0 test:device-loss\n> playwright test --config playwright.config.ts tests/e2e/device-loss.spec.ts\n\n\nRunning 1 test using 1 worker\n\n ✓ 1 tests/e2e/device-loss.spec.ts:6:1 › recovers the main-thread Chromium viewport after a real WebGL context loss (2.5s)\n\n 1 passed (4.1s)\n\n[WebServer] (node:2322781) Warning: The 'NO_COLOR' env is ignored due to the 'FORCE_COLOR' env being set.\n[WebServer] (Use `node --trace-warnings ...` to show where the warning was created)\n[WebServer] (node:2322793) Warning: The 'NO_COLOR' env is ignored due to the 'FORCE_COLOR' env being set.\n[WebServer] (Use `node --trace-warnings ...` to show where the warning was created)",
|
||||||
"artifactSha256": [
|
"artifactSha256": [
|
||||||
"3b18c884f3baed68d4deb72feafbd7fe8b4af7935aaf02206ccc583ea9c527e2",
|
"3878ce0f4d25cfef35eefe90070df56e06e2d597cd1bdf3655aed707450590e9",
|
||||||
"76d781c0f8508ee68fde09b1bb0d3f7db963e3e2be7d10d60708af2a6a582c24"
|
"76d781c0f8508ee68fde09b1bb0d3f7db963e3e2be7d10d60708af2a6a582c24"
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"id": "oom-recovery",
|
||||||
|
"fields": [
|
||||||
|
"faults.oom"
|
||||||
|
],
|
||||||
|
"command": "npm --prefix web run test:oom-recovery",
|
||||||
|
"exitCode": 0,
|
||||||
|
"durationMs": 4897,
|
||||||
|
"output": "> blender-web-editor@0.1.0 test:oom-recovery\n> playwright test --config playwright.config.ts tests/e2e/oom-recovery.spec.ts\n\n\nRunning 1 test using 1 worker\n\noom-recovery [{\"scenario\":\"WASM_MAIN\",\"errors\":[\"WASM_OUT_OF_MEMORY@WASM_OPEN_INPUT\",\"WASM_OUT_OF_MEMORY@MAIN_EDIT_COMMAND\",\"WASM_OUT_OF_MEMORY@WASM_SAVE_RESULT\"],\"revision\":[2,2],\"hashStable\":true,\"releasedBytes\":205638,\"temporaryResourcesAfter\":0,\"recovered\":true},{\"scenario\":\"OPFS_STAGING\",\"errors\":[\"STORAGE_QUOTA@OPFS_STAGING_WRITE\"],\"revision\":[7,7],\"hashStable\":true,\"releasedBytes\":47804,\"temporaryResourcesAfter\":0,\"recovered\":true},{\"scenario\":\"GPU_RESOURCES\",\"errors\":[\"GPU_TEXTURE_BUDGET_EXCEEDED@GPU_TEXTURE_UPLOAD\"],\"revision\":[0,0],\"hashStable\":true,\"releasedBytes\":36,\"temporaryResourcesAfter\":0,\"recovered\":true},{\"scenario\":\"NANOVDB_RESIDENT\",\"errors\":[\"NANOVDB_GPU_BUDGET_EXCEEDED@NANOVDB_PAGE_TABLE\"],\"revision\":[0,0],\"hashStable\":true,\"releasedBytes\":65536,\"temporaryResourcesAfter\":0,\"recovered\":true}]\n ✓ 1 tests/e2e/oom-recovery.spec.ts:7:1 › recovers deterministically from WASM, OPFS, GPU and NanoVDB allocation faults (2.5s)\n\n 1 passed (4.0s)\n\n[WebServer] (node:2323065) Warning: The 'NO_COLOR' env is ignored due to the 'FORCE_COLOR' env being set.\n[WebServer] (Use `node --trace-warnings ...` to show where the warning was created)\n[WebServer] (node:2323077) Warning: The 'NO_COLOR' env is ignored due to the 'FORCE_COLOR' env being set.\n[WebServer] (Use `node --trace-warnings ...` to show where the warning was created)\n(node:2323123) Warning: The 'NO_COLOR' env is ignored due to the 'FORCE_COLOR' env being set.\n(Use `node --trace-warnings ...` to show where the warning was created)",
|
||||||
|
"artifactSha256": [
|
||||||
|
"d5fe9184c693eb5b5d3ede8797cb09859669f43d9a82b55b9591cabb2a2c4a56",
|
||||||
|
"50fa383e7c7b3cf7a87eb652671dd9c180e30c86b99a8b129726d8af2f76a5a0",
|
||||||
|
"e6cec43ffa61b97cadf0926cd6468257cd47a38b05a121c4ea4a252fe257e585",
|
||||||
|
"3d0b7d7b98d1c9ffeda1b4c4ea43c4ced400e36814d5a819f7d26df2bd25d873",
|
||||||
|
"aac12afe4c92684dc74d546701d3f9e56f305f5ceb83fc4699bf8a06e6abed1b",
|
||||||
|
"ce2a44329068e6e9aafbb37259e8a47c1d9e306e563cae8b957d0b2f6b2b61cb",
|
||||||
|
"5d87a9ea57bd7a1888f16a5f4306e1c6d3a1bdfcf832c9485fe8518aac528fd8"
|
||||||
|
]
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"id": "texture-4k",
|
"id": "texture-4k",
|
||||||
"fields": [
|
"fields": [
|
||||||
@@ -791,8 +856,8 @@
|
|||||||
],
|
],
|
||||||
"command": "npm --prefix web run test:texture-4k-performance",
|
"command": "npm --prefix web run test:texture-4k-performance",
|
||||||
"exitCode": 0,
|
"exitCode": 0,
|
||||||
"durationMs": 4857,
|
"durationMs": 4670,
|
||||||
"output": "> blender-web-editor@0.1.0 test:texture-4k-performance\n> playwright test --config playwright.config.ts tests/e2e/texture-4k-performance.spec.ts\n\n\nRunning 1 test using 1 worker\n\n ✓ 1 tests/e2e/texture-4k-performance.spec.ts:3:1 › decodes, uploads and renders a validated 4K texture in Chromium (2.3s)\n\n 1 passed (4.0s)\n\n[WebServer] (node:1995463) Warning: The 'NO_COLOR' env is ignored due to the 'FORCE_COLOR' env being set.\n[WebServer] (Use `node --trace-warnings ...` to show where the warning was created)\n[WebServer] (node:1995475) Warning: The 'NO_COLOR' env is ignored due to the 'FORCE_COLOR' env being set.\n[WebServer] (Use `node --trace-warnings ...` to show where the warning was created)",
|
"output": "> blender-web-editor@0.1.0 test:texture-4k-performance\n> playwright test --config playwright.config.ts tests/e2e/texture-4k-performance.spec.ts\n\n\nRunning 1 test using 1 worker\n\ntexture-4k-performance {\"width\":4096,\"height\":4096,\"status\":{\"loaded\":1,\"rejected\":0,\"bytes\":254670,\"errors\":[],\"errorCodes\":[]},\"byteLength\":254670,\"estimatedPeakGpuBytes\":67108864,\"released\":true,\"recoveryVisible\":true,\"colored\":4096,\"elapsedMs\":668}\n ✓ 1 tests/e2e/texture-4k-performance.spec.ts:3:1 › decodes, uploads and renders a validated 4K texture in Chromium (2.3s)\n\n 1 passed (3.8s)\n\n[WebServer] (node:2323343) Warning: The 'NO_COLOR' env is ignored due to the 'FORCE_COLOR' env being set.\n[WebServer] (Use `node --trace-warnings ...` to show where the warning was created)\n[WebServer] (node:2323355) Warning: The 'NO_COLOR' env is ignored due to the 'FORCE_COLOR' env being set.\n[WebServer] (Use `node --trace-warnings ...` to show where the warning was created)\n(node:2323401) Warning: The 'NO_COLOR' env is ignored due to the 'FORCE_COLOR' env being set.\n(Use `node --trace-warnings ...` to show where the warning was created)",
|
||||||
"artifactSha256": [
|
"artifactSha256": [
|
||||||
"093588bcdf08f8b5a5aa8baceddbd7b499c43e5acda7f793b34ac54c79782490",
|
"093588bcdf08f8b5a5aa8baceddbd7b499c43e5acda7f793b34ac54c79782490",
|
||||||
"75f2f33753a160f60acc56ad850bc149cef7fa2b54a42d5e75cf22402c9ac91d"
|
"75f2f33753a160f60acc56ad850bc149cef7fa2b54a42d5e75cf22402c9ac91d"
|
||||||
@@ -805,8 +870,8 @@
|
|||||||
],
|
],
|
||||||
"command": "npm --prefix web run test:texture-8k-performance",
|
"command": "npm --prefix web run test:texture-8k-performance",
|
||||||
"exitCode": 0,
|
"exitCode": 0,
|
||||||
"durationMs": 7112,
|
"durationMs": 6818,
|
||||||
"output": "> blender-web-editor@0.1.0 test:texture-8k-performance\n> playwright test --config playwright.config.ts tests/e2e/texture-8k-performance.spec.ts\n\n\nRunning 1 test using 1 worker\n\n ✓ 1 tests/e2e/texture-8k-performance.spec.ts:3:1 › decodes, uploads and renders a validated 8K texture in Chromium (4.6s)\n\n 1 passed (6.2s)\n\n[WebServer] (node:1995736) Warning: The 'NO_COLOR' env is ignored due to the 'FORCE_COLOR' env being set.\n[WebServer] (Use `node --trace-warnings ...` to show where the warning was created)\n[WebServer] (node:1995748) Warning: The 'NO_COLOR' env is ignored due to the 'FORCE_COLOR' env being set.\n[WebServer] (Use `node --trace-warnings ...` to show where the warning was created)",
|
"output": "> blender-web-editor@0.1.0 test:texture-8k-performance\n> playwright test --config playwright.config.ts tests/e2e/texture-8k-performance.spec.ts\n\n\nRunning 1 test using 1 worker\n\ntexture-8k-performance {\"supported\":true,\"maxTextureSize\":8192,\"width\":8192,\"height\":8192,\"status\":{\"loaded\":1,\"rejected\":0,\"bytes\":972714,\"errors\":[],\"errorCodes\":[]},\"byteLength\":972714,\"estimatedPeakGpuBytes\":268435456,\"released\":true,\"recoveryVisible\":true,\"colored\":4096,\"elapsedMs\":2579}\n ✓ 1 tests/e2e/texture-8k-performance.spec.ts:3:1 › decodes, uploads and renders a validated 8K texture in Chromium (4.4s)\n\n 1 passed (6.0s)\n\n[WebServer] (node:2323628) Warning: The 'NO_COLOR' env is ignored due to the 'FORCE_COLOR' env being set.\n[WebServer] (Use `node --trace-warnings ...` to show where the warning was created)\n[WebServer] (node:2323640) Warning: The 'NO_COLOR' env is ignored due to the 'FORCE_COLOR' env being set.\n[WebServer] (Use `node --trace-warnings ...` to show where the warning was created)\n(node:2323686) Warning: The 'NO_COLOR' env is ignored due to the 'FORCE_COLOR' env being set.\n(Use `node --trace-warnings ...` to show where the warning was created)",
|
||||||
"artifactSha256": [
|
"artifactSha256": [
|
||||||
"093588bcdf08f8b5a5aa8baceddbd7b499c43e5acda7f793b34ac54c79782490",
|
"093588bcdf08f8b5a5aa8baceddbd7b499c43e5acda7f793b34ac54c79782490",
|
||||||
"75f2f33753a160f60acc56ad850bc149cef7fa2b54a42d5e75cf22402c9ac91d"
|
"75f2f33753a160f60acc56ad850bc149cef7fa2b54a42d5e75cf22402c9ac91d"
|
||||||
@@ -819,8 +884,8 @@
|
|||||||
],
|
],
|
||||||
"command": "WEB_TEST_PORT=5323 npm --prefix web run test:asset-library",
|
"command": "WEB_TEST_PORT=5323 npm --prefix web run test:asset-library",
|
||||||
"exitCode": 0,
|
"exitCode": 0,
|
||||||
"durationMs": 4198,
|
"durationMs": 4136,
|
||||||
"output": "> blender-web-editor@0.1.0 test:asset-library\n> playwright test --config playwright.config.ts -g \"N-023 asset\"\n\n\nRunning 1 test using 1 worker\n\n ✓ 1 tests/e2e/smoke.spec.ts:969:1 › validates N-023 asset catalogs, library graphs, archive budgets and IO gates (1.5s)\n\n 1 passed (3.3s)\n\n[WebServer] (node:1996023) Warning: The 'NO_COLOR' env is ignored due to the 'FORCE_COLOR' env being set.\n[WebServer] (Use `node --trace-warnings ...` to show where the warning was created)\n[WebServer] (node:1996035) Warning: The 'NO_COLOR' env is ignored due to the 'FORCE_COLOR' env being set.\n[WebServer] (Use `node --trace-warnings ...` to show where the warning was created)",
|
"output": "> blender-web-editor@0.1.0 test:asset-library\n> playwright test --config playwright.config.ts -g \"N-023 asset\"\n\n\nRunning 1 test using 1 worker\n\n ✓ 1 tests/e2e/smoke.spec.ts:969:1 › validates N-023 asset catalogs, library graphs, archive budgets and IO gates (1.5s)\n\n 1 passed (3.3s)\n\n[WebServer] (node:2323896) Warning: The 'NO_COLOR' env is ignored due to the 'FORCE_COLOR' env being set.\n[WebServer] (Use `node --trace-warnings ...` to show where the warning was created)\n[WebServer] (node:2323908) Warning: The 'NO_COLOR' env is ignored due to the 'FORCE_COLOR' env being set.\n[WebServer] (Use `node --trace-warnings ...` to show where the warning was created)",
|
||||||
"artifactSha256": [
|
"artifactSha256": [
|
||||||
"5c62539db3833a2113007dfbdfd12ca06704d86e0cf0e57ad97f7fbf6acda7f4"
|
"5c62539db3833a2113007dfbdfd12ca06704d86e0cf0e57ad97f7fbf6acda7f4"
|
||||||
]
|
]
|
||||||
@@ -830,10 +895,10 @@
|
|||||||
"fields": [],
|
"fields": [],
|
||||||
"command": "npm --prefix web run test:release-package",
|
"command": "npm --prefix web run test:release-package",
|
||||||
"exitCode": 0,
|
"exitCode": 0,
|
||||||
"durationMs": 5619,
|
"durationMs": 5339,
|
||||||
"output": "> blender-web-editor@0.1.0 test:release-package\n> npm run build && npm run release:sbom && node ../tools/web/check-release-package.mjs\n\n\n> blender-web-editor@0.1.0 build\n> tsc -p tsconfig.json && vite build --config app/vite.config.ts\n\nvite v8.2.0 building client environment for production...\n\u001b[2K\rtransforming...✓ 51 modules transformed.\nrendering chunks...\ncomputing gzip size...\ndist/index.html 0.45 kB │ gzip: 0.29 kB\ndist/assets/storage.worker-hjSYzFAQ.js 36.77 kB\ndist/assets/web-engine.worker-CWkf48pG.js 245.76 kB\ndist/assets/viewport-render.worker-DXSzkSWB.js 592.78 kB\ndist/assets/web_engine-CvVHxFlQ.wasm 15,048.04 kB │ gzip: 3,562.32 kB\ndist/assets/index-QI0bg0OR.css 11.41 kB │ gzip: 3.18 kB\ndist/assets/index-Ce6I8dqP.js 951.79 kB │ gzip: 256.93 kB\n\n✓ built in 623ms\n\n> blender-web-editor@0.1.0 release:sbom\n> node ../tools/web/generate-sbom.mjs\n\nsbom-ok packages=150 sha256=8b04215a993f62ba64e0adcb1ba36ac830191f8215bf8cf2681fdf7bbb63da30\nrelease-package-ok files=10 bytes=32067024\n\n[plugin rolldown:vite-resolve] Module \"module\" has been externalized for browser compatibility, imported by \"/home/mes123456/workinf_Blender_Wasm/web/app/src/vendor/blender/web_engine.js\". See https://vite.dev/guide/troubleshooting.html#module-externalized-for-browser-compatibility for more details.\n[plugin builtin:vite-reporter] \n(!) Some chunks are larger than 500 kB after minification. Consider:\n- Using dynamic import() to code-split the application\n- Use build.rolldownOptions.output.codeSplitting to improve chunking: https://rolldown.rs/reference/OutputOptions.codeSplitting\n- Adjust chunk size limit for this warning via build.chunkSizeWarningLimit.",
|
"output": "> blender-web-editor@0.1.0 test:release-package\n> npm run build && npm run release:sbom && node ../tools/web/check-release-package.mjs\n\n\n> blender-web-editor@0.1.0 build\n> tsc -p tsconfig.json && vite build --config app/vite.config.ts\n\nvite v8.2.0 building client environment for production...\n\u001b[2K\rtransforming...✓ 54 modules transformed.\nrendering chunks...\ncomputing gzip size...\ndist/index.html 0.46 kB │ gzip: 0.30 kB\ndist/assets/storage.worker-COUVNftM.js 36.81 kB\ndist/assets/web-engine.worker-CWkf48pG.js 245.76 kB\ndist/assets/viewport-render.worker-DZmZ4IRD.js 601.32 kB\ndist/assets/web_engine-CvVHxFlQ.wasm 15,048.04 kB │ gzip: 3,562.32 kB\ndist/assets/index-QI0bg0OR.css 11.41 kB │ gzip: 3.18 kB\ndist/assets/index-Cv6ZsBTp.js 973.17 kB │ gzip: 263.15 kB\n\n✓ built in 562ms\n\n> blender-web-editor@0.1.0 release:sbom\n> node ../tools/web/generate-sbom.mjs\n\nsbom-ok packages=150 sha256=aa50a5b98ebb51c98c6eced2ec32dff96b8df6d90e10cc101ce9e8eb44695060\nrelease-package-ok files=10 bytes=32096983\n\n[plugin rolldown:vite-resolve] Module \"module\" has been externalized for browser compatibility, imported by \"/home/mes123456/workinf_Blender_Wasm/web/app/src/vendor/blender/web_engine.js\". See https://vite.dev/guide/troubleshooting.html#module-externalized-for-browser-compatibility for more details.\n[plugin builtin:vite-reporter] \n(!) Some chunks are larger than 500 kB after minification. Consider:\n- Using dynamic import() to code-split the application\n- Use build.rolldownOptions.output.codeSplitting to improve chunking: https://rolldown.rs/reference/OutputOptions.codeSplitting\n- Adjust chunk size limit for this warning via build.chunkSizeWarningLimit.",
|
||||||
"artifactSha256": [
|
"artifactSha256": [
|
||||||
"8b04215a993f62ba64e0adcb1ba36ac830191f8215bf8cf2681fdf7bbb63da30",
|
"aa50a5b98ebb51c98c6eced2ec32dff96b8df6d90e10cc101ce9e8eb44695060",
|
||||||
"5d87a9ea57bd7a1888f16a5f4306e1c6d3a1bdfcf832c9485fe8518aac528fd8"
|
"5d87a9ea57bd7a1888f16a5f4306e1c6d3a1bdfcf832c9485fe8518aac528fd8"
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
@@ -845,76 +910,12 @@
|
|||||||
],
|
],
|
||||||
"command": "npm --prefix web run release:offline",
|
"command": "npm --prefix web run release:offline",
|
||||||
"exitCode": 0,
|
"exitCode": 0,
|
||||||
"durationMs": 41303,
|
"durationMs": 36970,
|
||||||
"output": "> blender-web-editor@0.1.0 release:offline\n> npm run build && node ../tools/web/check-offline-reproducibility.mjs\n\n\n> blender-web-editor@0.1.0 build\n> tsc -p tsconfig.json && vite build --config app/vite.config.ts\n\nvite v8.2.0 building client environment for production...\n\u001b[2K\rtransforming...✓ 51 modules transformed.\nrendering chunks...\ncomputing gzip size...\ndist/index.html 0.45 kB │ gzip: 0.29 kB\ndist/assets/storage.worker-hjSYzFAQ.js 36.77 kB\ndist/assets/web-engine.worker-CWkf48pG.js 245.76 kB\ndist/assets/viewport-render.worker-DXSzkSWB.js 592.78 kB\ndist/assets/web_engine-CvVHxFlQ.wasm 15,048.04 kB │ gzip: 3,562.32 kB\ndist/assets/index-QI0bg0OR.css 11.41 kB │ gzip: 3.18 kB\ndist/assets/index-Ce6I8dqP.js 951.79 kB │ gzip: 256.93 kB\n\n✓ built in 723ms\nsbom-ok packages=150 sha256=8b04215a993f62ba64e0adcb1ba36ac830191f8215bf8cf2681fdf7bbb63da30\noffline-release-ok binary=7575306 source=205785402 sha256=078f45513ab579800ab3688f7c5e35d31be0d9dc283db5cee1a68d77553bfb34\nsbom-ok packages=150 sha256=8b04215a993f62ba64e0adcb1ba36ac830191f8215bf8cf2681fdf7bbb63da30\noffline-release-ok binary=7575306 source=205785402 sha256=078f45513ab579800ab3688f7c5e35d31be0d9dc283db5cee1a68d77553bfb34\noffline-reproducibility-ok binary=078f45513ab579800ab3688f7c5e35d31be0d9dc283db5cee1a68d77553bfb34 source=ca97bef8cbe7943586e7af1df631775ce6ea041677c8bbb98da2fcc73baa9e1a\n\n[plugin rolldown:vite-resolve] Module \"module\" has been externalized for browser compatibility, imported by \"/home/mes123456/workinf_Blender_Wasm/web/app/src/vendor/blender/web_engine.js\". See https://vite.dev/guide/troubleshooting.html#module-externalized-for-browser-compatibility for more details.\n[plugin builtin:vite-reporter] \n(!) Some chunks are larger than 500 kB after minification. Consider:\n- Using dynamic import() to code-split the application\n- Use build.rolldownOptions.output.codeSplitting to improve chunking: https://rolldown.rs/reference/OutputOptions.codeSplitting\n- Adjust chunk size limit for this warning via build.chunkSizeWarningLimit.",
|
"output": "> blender-web-editor@0.1.0 release:offline\n> npm run build && node ../tools/web/check-offline-reproducibility.mjs\n\n\n> blender-web-editor@0.1.0 build\n> tsc -p tsconfig.json && vite build --config app/vite.config.ts\n\nvite v8.2.0 building client environment for production...\n\u001b[2K\rtransforming...✓ 54 modules transformed.\nrendering chunks...\ncomputing gzip size...\ndist/index.html 0.46 kB │ gzip: 0.30 kB\ndist/assets/storage.worker-COUVNftM.js 36.81 kB\ndist/assets/web-engine.worker-CWkf48pG.js 245.76 kB\ndist/assets/viewport-render.worker-DZmZ4IRD.js 601.32 kB\ndist/assets/web_engine-CvVHxFlQ.wasm 15,048.04 kB │ gzip: 3,562.32 kB\ndist/assets/index-QI0bg0OR.css 11.41 kB │ gzip: 3.18 kB\ndist/assets/index-Cv6ZsBTp.js 973.17 kB │ gzip: 263.15 kB\n\n✓ built in 581ms\nsbom-ok packages=150 sha256=aa50a5b98ebb51c98c6eced2ec32dff96b8df6d90e10cc101ce9e8eb44695060\noffline-release-ok binary=7585557 source=205827673 sha256=f2903a75026a0a28615aeb5f4834a3859af83022c82bc50e28437dcbed68e549\nsbom-ok packages=150 sha256=aa50a5b98ebb51c98c6eced2ec32dff96b8df6d90e10cc101ce9e8eb44695060\noffline-release-ok binary=7585557 source=205827673 sha256=f2903a75026a0a28615aeb5f4834a3859af83022c82bc50e28437dcbed68e549\noffline-reproducibility-ok binary=f2903a75026a0a28615aeb5f4834a3859af83022c82bc50e28437dcbed68e549 source=9a9c1b505c68a618de842be7c248f628b104b11b80e8a40d111f62540fcba327\n\n[plugin rolldown:vite-resolve] Module \"module\" has been externalized for browser compatibility, imported by \"/home/mes123456/workinf_Blender_Wasm/web/app/src/vendor/blender/web_engine.js\". See https://vite.dev/guide/troubleshooting.html#module-externalized-for-browser-compatibility for more details.\n[plugin builtin:vite-reporter] \n(!) Some chunks are larger than 500 kB after minification. Consider:\n- Using dynamic import() to code-split the application\n- Use build.rolldownOptions.output.codeSplitting to improve chunking: https://rolldown.rs/reference/OutputOptions.codeSplitting\n- Adjust chunk size limit for this warning via build.chunkSizeWarningLimit.",
|
||||||
"artifactSha256": [
|
"artifactSha256": [
|
||||||
"078f45513ab579800ab3688f7c5e35d31be0d9dc283db5cee1a68d77553bfb34",
|
"f2903a75026a0a28615aeb5f4834a3859af83022c82bc50e28437dcbed68e549",
|
||||||
"ca97bef8cbe7943586e7af1df631775ce6ea041677c8bbb98da2fcc73baa9e1a",
|
"9a9c1b505c68a618de842be7c248f628b104b11b80e8a40d111f62540fcba327",
|
||||||
"39c7f9be555f2727519a733237de22e66a33d33ac1b0aecfacd2ae695de10b28"
|
"288b0bb969e670b458059adf8897f87bafc76a87a479c8aa0a305905accf2de9"
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": "v1-user-loop",
|
|
||||||
"fields": [],
|
|
||||||
"command": "npm --prefix web run test:v1-user-loop",
|
|
||||||
"exitCode": 0,
|
|
||||||
"durationMs": 5240,
|
|
||||||
"output": "> blender-web-editor@0.1.0 test:v1-user-loop\n> playwright test --config playwright.config.ts tests/e2e/v1-user-loop.spec.ts\n\n\nRunning 1 test using 1 worker\n\n ✓ 1 tests/e2e/v1-user-loop.spec.ts:6:1 › completes the V1 import-edit-save-restart-reopen-GLB loop (2.9s)\n\n 1 passed (4.4s)\n\n[WebServer] (node:2124514) Warning: The 'NO_COLOR' env is ignored due to the 'FORCE_COLOR' env being set.\n[WebServer] (Use `node --trace-warnings ...` to show where the warning was created)\n[WebServer] (node:2124526) Warning: The 'NO_COLOR' env is ignored due to the 'FORCE_COLOR' env being set.\n[WebServer] (Use `node --trace-warnings ...` to show where the warning was created)",
|
|
||||||
"artifactSha256": [
|
|
||||||
"3defbaa426195254fcaefaddabf9b128002a94def03aa255513c33eaf4ac0926",
|
|
||||||
"5d87a9ea57bd7a1888f16a5f4306e1c6d3a1bdfcf832c9485fe8518aac528fd8"
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": "geometry-10m",
|
|
||||||
"fields": [
|
|
||||||
"performance.geometry10M"
|
|
||||||
],
|
|
||||||
"command": "npm --prefix web run test:geometry-10m-performance",
|
|
||||||
"exitCode": 0,
|
|
||||||
"durationMs": 5954,
|
|
||||||
"output": "> blender-web-editor@0.1.0 test:geometry-10m-performance\n> playwright test --config playwright.config.ts tests/e2e/geometry-10m-performance.spec.ts\n\n\nRunning 1 test using 1 worker\n\ngeometry-10m-performance {\"triangleCount\":10000000,\"chunkCount\":40,\"transferredBytes\":180492000,\"peakWorkingSetBytes\":4512300,\"manifestSha256\":\"224738e0c3aa28ae42167bf8974455a3d0f940f59f19eb51c5768f435c66b24f\",\"firstInteractiveMs\":1220,\"chunksAtFirstInteractive\":20,\"estimatedGpuBytes\":1124880,\"wasmAllocatedBytes\":0,\"cancelLatencyMs\":62,\"elapsedMs\":1886}\n ✓ 1 tests/e2e/geometry-10m-performance.spec.ts:3:1 › streams ten-million-triangle SceneIR ranges into a bounded interactive LOD (3.4s)\n\n 1 passed (5.0s)\n\n[WebServer] (node:2142662) Warning: The 'NO_COLOR' env is ignored due to the 'FORCE_COLOR' env being set.\n[WebServer] (Use `node --trace-warnings ...` to show where the warning was created)\n[WebServer] (node:2142686) Warning: The 'NO_COLOR' env is ignored due to the 'FORCE_COLOR' env being set.\n[WebServer] (Use `node --trace-warnings ...` to show where the warning was created)\n(node:2142761) Warning: The 'NO_COLOR' env is ignored due to the 'FORCE_COLOR' env being set.\n(Use `node --trace-warnings ...` to show where the warning was created)",
|
|
||||||
"artifactSha256": [
|
|
||||||
"6f2708499d59a5e0e90e4581359d6131428c22da0da1543bb4aa0fc38e5e6b29",
|
|
||||||
"04743733e385f33efbb144b0c6cd831a21840dad9b6e0715e8a73791d8a95e33",
|
|
||||||
"969bda967852622b78bd5c0a56b0c0832bf1e46b19fa09b6b26316f2c367fdaf",
|
|
||||||
"101ba99e9f75617acb7105845c49f500f6f65a5b4ce5be20c4d8c1e0a9d458e2"
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": "long-media",
|
|
||||||
"fields": [
|
|
||||||
"performance.longMedia"
|
|
||||||
],
|
|
||||||
"command": "npm --prefix web run test:long-media-performance",
|
|
||||||
"exitCode": 0,
|
|
||||||
"durationMs": 5319,
|
|
||||||
"output": "> blender-web-editor@0.1.0 test:long-media-performance\n> playwright test --config playwright.config.ts tests/e2e/long-media-performance.spec.ts\n\n\nRunning 1 test using 1 worker\n\nlong-media-performance {\"frameCount\":1000000,\"stripCount\":10001,\"referenceCount\":11914,\"indexBuildMs\":227,\"coldSeekMs\":1,\"hotSeekMs\":0,\"randomSeekMs\":14,\"cacheBytes\":16305,\"evictions\":71,\"manifestSha256\":\"d2e7e3c5ed9ae4fda6fe358cebb474f5774f1d2037dff3df9b6dfde9e0bebd2d\",\"elapsedMs\":1010}\n ✓ 1 tests/e2e/long-media-performance.spec.ts:8:1 › indexes, seeks, cancels and reopens a bounded one-million-frame media timeline (2.6s)\n\n 1 passed (4.4s)\n\n[WebServer] (node:2156644) Warning: The 'NO_COLOR' env is ignored due to the 'FORCE_COLOR' env being set.\n[WebServer] (Use `node --trace-warnings ...` to show where the warning was created)\n[WebServer] (node:2156658) Warning: The 'NO_COLOR' env is ignored due to the 'FORCE_COLOR' env being set.\n[WebServer] (Use `node --trace-warnings ...` to show where the warning was created)\n(node:2156737) Warning: The 'NO_COLOR' env is ignored due to the 'FORCE_COLOR' env being set.\n(Use `node --trace-warnings ...` to show where the warning was created)",
|
|
||||||
"artifactSha256": [
|
|
||||||
"3427a057e564c9e1b16d464d06e65cde90ae8557f4f51cb98ad43774658e8dca",
|
|
||||||
"a3088cadf92c99377856e403d0c844231e9d96e0479016befbb1b7606d35bf62",
|
|
||||||
"9b48e65b35a7f5fd6b86568d20255a0446882c3f3281f753af625b9c2fa4995c",
|
|
||||||
"295b083db2299ab904947eb39c17173de70c211882b0d855537d8f3cc27698ed",
|
|
||||||
"56d4af65701c26df20bd4021eda95b6e830348ce3a746086079fe89285548dc9"
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": "oom-recovery",
|
|
||||||
"fields": [
|
|
||||||
"faults.oom"
|
|
||||||
],
|
|
||||||
"command": "npm --prefix web run test:oom-recovery",
|
|
||||||
"exitCode": 0,
|
|
||||||
"durationMs": 4874,
|
|
||||||
"output": "> blender-web-editor@0.1.0 test:oom-recovery\n> playwright test --config playwright.config.ts tests/e2e/oom-recovery.spec.ts\n\n\nRunning 1 test using 1 worker\n\noom-recovery [{\"scenario\":\"WASM_MAIN\",\"errors\":[\"WASM_OUT_OF_MEMORY@WASM_OPEN_INPUT\",\"WASM_OUT_OF_MEMORY@MAIN_EDIT_COMMAND\",\"WASM_OUT_OF_MEMORY@WASM_SAVE_RESULT\"],\"revision\":[2,2],\"hashStable\":true,\"releasedBytes\":205638,\"temporaryResourcesAfter\":0,\"recovered\":true},{\"scenario\":\"OPFS_STAGING\",\"errors\":[\"STORAGE_QUOTA@OPFS_STAGING_WRITE\"],\"revision\":[7,7],\"hashStable\":true,\"releasedBytes\":47804,\"temporaryResourcesAfter\":0,\"recovered\":true},{\"scenario\":\"GPU_RESOURCES\",\"errors\":[\"GPU_TEXTURE_BUDGET_EXCEEDED@GPU_TEXTURE_UPLOAD\"],\"revision\":[0,0],\"hashStable\":true,\"releasedBytes\":36,\"temporaryResourcesAfter\":0,\"recovered\":true},{\"scenario\":\"NANOVDB_RESIDENT\",\"errors\":[\"NANOVDB_GPU_BUDGET_EXCEEDED@NANOVDB_PAGE_TABLE\"],\"revision\":[0,0],\"hashStable\":true,\"releasedBytes\":65536,\"temporaryResourcesAfter\":0,\"recovered\":true}]\n ✓ 1 tests/e2e/oom-recovery.spec.ts:7:1 › recovers deterministically from WASM, OPFS, GPU and NanoVDB allocation faults (2.5s)\n\n 1 passed (4.1s)\n\n[WebServer] (node:2169927) Warning: The 'NO_COLOR' env is ignored due to the 'FORCE_COLOR' env being set.\n[WebServer] (Use `node --trace-warnings ...` to show where the warning was created)\n[WebServer] (node:2169939) Warning: The 'NO_COLOR' env is ignored due to the 'FORCE_COLOR' env being set.\n[WebServer] (Use `node --trace-warnings ...` to show where the warning was created)\n(node:2169985) Warning: The 'NO_COLOR' env is ignored due to the 'FORCE_COLOR' env being set.\n(Use `node --trace-warnings ...` to show where the warning was created)",
|
|
||||||
"artifactSha256": [
|
|
||||||
"d5fe9184c693eb5b5d3ede8797cb09859669f43d9a82b55b9591cabb2a2c4a56",
|
|
||||||
"50fa383e7c7b3cf7a87eb652671dd9c180e30c86b99a8b129726d8af2f76a5a0",
|
|
||||||
"e6cec43ffa61b97cadf0926cd6468257cd47a38b05a121c4ea4a252fe257e585",
|
|
||||||
"3d0b7d7b98d1c9ffeda1b4c4ea43c4ced400e36814d5a819f7d26df2bd25d873",
|
|
||||||
"aac12afe4c92684dc74d546701d3f9e56f305f5ceb83fc4699bf8a06e6abed1b",
|
|
||||||
"ce2a44329068e6e9aafbb37259e8a47c1d9e306e563cae8b957d0b2f6b2b61cb",
|
|
||||||
"5d87a9ea57bd7a1888f16a5f4306e1c6d3a1bdfcf832c9485fe8518aac528fd8"
|
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
|
|||||||
572
docs/status/v1-acceptance-evidence.json
Normal file
572
docs/status/v1-acceptance-evidence.json
Normal file
@@ -0,0 +1,572 @@
|
|||||||
|
{
|
||||||
|
"schemaVersion": 1,
|
||||||
|
"source": "docs/status/parity-ledger.json",
|
||||||
|
"sourceSha256": "1e16518a25c6b6bed4ab7f605ea1ad5babe529f2842c858b20ae6daae30bd478",
|
||||||
|
"package": "web/package.json",
|
||||||
|
"packageSha256": "4259c8c085fe94f5b109416230482d0791e6d9b47559546be42415bb91a4c224",
|
||||||
|
"toolSourceSha256": {
|
||||||
|
"tools/web/v1-acceptance-lib.mjs": "48005c8fc9d97955513e104ee1b8950cab2c3520c166eff4358e10bd7318f535",
|
||||||
|
"tools/web/check-v1-acceptance-coverage.mjs": "6448fcc673fde091cff4a299d77889570d3104bd0cfabe5434f45ba0b46de432",
|
||||||
|
"tools/web/run-v1-acceptance.mjs": "f12d04f7d74450f1973eb88aa58355a225019c6b9906aedc746c005a1958f1a5",
|
||||||
|
"tools/web/check-v1-acceptance-evidence.mjs": "af1a3029a1148e8e5d346d66f3a58f7063b73fee8dd95e6e41ed0504ed56dfea"
|
||||||
|
},
|
||||||
|
"generatedAt": "2026-08-15T04:33:26.940Z",
|
||||||
|
"environment": {
|
||||||
|
"node": "v20.19.2",
|
||||||
|
"npm": "9.2.0",
|
||||||
|
"chromium": "Google Chrome 150.0.7871.128",
|
||||||
|
"blenderBin": "BLENDER_BIN",
|
||||||
|
"blenderExecutableSha256": "83e8261eace07a5337f71b52d156c1eece1a6ba913403cc6406182ae58bacf27",
|
||||||
|
"blenderArchiveSha256": "96f6c181a30f4950607839dc84d42a354b250d8a0231b098b59b7bc69c351c48",
|
||||||
|
"blender": "ACCEPTANCE_BLENDER=5.2.0 LTS;USD=True"
|
||||||
|
},
|
||||||
|
"summary": {
|
||||||
|
"families": 12,
|
||||||
|
"declarations": 50,
|
||||||
|
"unique": 49,
|
||||||
|
"passed": 49,
|
||||||
|
"failed": 0
|
||||||
|
},
|
||||||
|
"results": [
|
||||||
|
{
|
||||||
|
"token": "web:test:nonmesh-roundtrip",
|
||||||
|
"familyIds": [
|
||||||
|
"N-015"
|
||||||
|
],
|
||||||
|
"command": "npm --prefix web run test:nonmesh-roundtrip",
|
||||||
|
"exitCode": 0,
|
||||||
|
"durationMs": 1486,
|
||||||
|
"outputSha256": "f0f98be0be03fab64d04f9e7a3d696161dc3c9fb5729c09a41f34cc401e61822",
|
||||||
|
"output": "> blender-web-editor@0.1.0 test:nonmesh-roundtrip\n> node ../tools/web/check-nonmesh-roundtrip.mjs\n\nnonmesh-roundtrip-ok types=CURVE,SURFACE,FONT,METABALL,VOLUME multispline-create-delete-bulk-handle-cyclic-surface-2d-topology-font-links-volume-properties=passed undo-redo=passed save-reopen=passed\n\nweb-depsgraph-stage=runtime-init-begin\nweb-depsgraph-stage=runtime-init-end\nweb-depsgraph-stage=blend-read-end\nweb-depsgraph-stage=view-layer-sync-end\nweb-depsgraph-data scene=0xaf0538 master=0xa18618 nodetree=0 world=0 collections=0\nweb-depsgraph-stage=global-main-swap-end\nweb-depsgraph-stage=graph-new-end\nweb-depsgraph-stage=graph-build-end\nweb-depsgraph-stage=frame-change-end\nweb-depsgraph-stage=graph-free-end\nweb-depsgraph-stage=global-main-restore-end\nweb-depsgraph-stage=blend-free-end"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"token": "web:test:nonmesh-desktop-golden",
|
||||||
|
"familyIds": [
|
||||||
|
"N-015"
|
||||||
|
],
|
||||||
|
"command": "npm --prefix web run test:nonmesh-desktop-golden",
|
||||||
|
"exitCode": 0,
|
||||||
|
"durationMs": 686,
|
||||||
|
"outputSha256": "e5339560846d774c039f493679a373b37b35ea7bfa68f1dc2a019188ecc91d45",
|
||||||
|
"output": "> blender-web-editor@0.1.0 test:nonmesh-desktop-golden\n> node ../tools/web/check-nonmesh-desktop-golden.mjs\n\nnonmesh-desktop-golden-ok fixture=nonmesh_scene.blend geometries=4\n\nweb-depsgraph-stage=runtime-init-begin\nweb-depsgraph-stage=runtime-init-end\nweb-depsgraph-stage=blend-read-end\nweb-depsgraph-stage=view-layer-sync-end\nweb-depsgraph-data scene=0xa71220 master=0xacb470 nodetree=0 world=0 collections=0\nweb-depsgraph-stage=global-main-swap-end\nweb-depsgraph-stage=graph-new-end\nweb-depsgraph-stage=graph-build-end\nweb-depsgraph-stage=frame-change-end\nweb-depsgraph-stage=graph-free-end\nweb-depsgraph-stage=global-main-restore-end\nweb-depsgraph-stage=blend-free-end"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"token": "web:test:nonmesh-glb-blender-roundtrip",
|
||||||
|
"familyIds": [
|
||||||
|
"N-015"
|
||||||
|
],
|
||||||
|
"command": "npm --prefix web run test:nonmesh-glb-blender-roundtrip",
|
||||||
|
"exitCode": 0,
|
||||||
|
"durationMs": 1668,
|
||||||
|
"outputSha256": "8627de96cfe96c0c7490cfe54fa59a680f787d5d3dcdadacc20e79a2c20f72c7",
|
||||||
|
"output": "> blender-web-editor@0.1.0 test:nonmesh-glb-blender-roundtrip\n> node ../tools/web/check-nonmesh-glb-blender-roundtrip.mjs\n\nnonmesh-glb-blender-roundtrip-ok bytes=53172 objects=7 points=1 lines=3 triangles=3\n\nweb-depsgraph-stage=runtime-init-begin\nweb-depsgraph-stage=runtime-init-end\nweb-depsgraph-stage=blend-read-end\nweb-depsgraph-stage=view-layer-sync-end\nweb-depsgraph-data scene=0xa71260 master=0xacb460 nodetree=0 world=0 collections=0\nweb-depsgraph-stage=global-main-swap-end\nweb-depsgraph-stage=graph-new-end\nweb-depsgraph-stage=graph-build-end\nweb-depsgraph-stage=frame-change-end\nweb-depsgraph-stage=graph-free-end\nweb-depsgraph-stage=global-main-restore-end\nweb-depsgraph-stage=blend-free-end"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"token": "web:test:nonmesh-usd-serialization",
|
||||||
|
"familyIds": [
|
||||||
|
"N-015"
|
||||||
|
],
|
||||||
|
"command": "npm --prefix web run test:nonmesh-usd-serialization",
|
||||||
|
"exitCode": 0,
|
||||||
|
"durationMs": 2031,
|
||||||
|
"outputSha256": "b80732c965d85c19cfb3c3a629777bacfdc1a920abcc384d436831a64f6e3101",
|
||||||
|
"output": "> blender-web-editor@0.1.0 test:nonmesh-usd-serialization\n> USD_DESKTOP_REQUIRED=0 node ../tools/web/check-nonmesh-usd-blender-roundtrip.mjs\n\nnonmesh-usd-blender-roundtrip-ok bytes=65405 objects=7 points=1 basisCurves=3 meshes=3\n\nweb-depsgraph-stage=runtime-init-begin\nweb-depsgraph-stage=runtime-init-end\nweb-depsgraph-stage=blend-read-end\nweb-depsgraph-stage=view-layer-sync-end\nweb-depsgraph-data scene=0xa71260 master=0xacb460 nodetree=0 world=0 collections=0\nweb-depsgraph-stage=global-main-swap-end\nweb-depsgraph-stage=graph-new-end\nweb-depsgraph-stage=graph-build-end\nweb-depsgraph-stage=frame-change-end\nweb-depsgraph-stage=graph-free-end\nweb-depsgraph-stage=global-main-restore-end\nweb-depsgraph-stage=blend-free-end"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"token": "web:test:nonmesh-usd-blender-roundtrip",
|
||||||
|
"familyIds": [
|
||||||
|
"N-015"
|
||||||
|
],
|
||||||
|
"command": "npm --prefix web run test:nonmesh-usd-blender-roundtrip",
|
||||||
|
"exitCode": 0,
|
||||||
|
"durationMs": 2033,
|
||||||
|
"outputSha256": "ce0580b3b5d047a1c3c5d2d1a338c6c001b5d9a69c5d22ed35322bd353fb16af",
|
||||||
|
"output": "> blender-web-editor@0.1.0 test:nonmesh-usd-blender-roundtrip\n> USD_DESKTOP_REQUIRED=1 node ../tools/web/check-nonmesh-usd-blender-roundtrip.mjs\n\nnonmesh-usd-blender-roundtrip-ok bytes=65405 objects=7 points=1 basisCurves=3 meshes=3\n\nweb-depsgraph-stage=runtime-init-begin\nweb-depsgraph-stage=runtime-init-end\nweb-depsgraph-stage=blend-read-end\nweb-depsgraph-stage=view-layer-sync-end\nweb-depsgraph-data scene=0xa71260 master=0xacb460 nodetree=0 world=0 collections=0\nweb-depsgraph-stage=global-main-swap-end\nweb-depsgraph-stage=graph-new-end\nweb-depsgraph-stage=graph-build-end\nweb-depsgraph-stage=frame-change-end\nweb-depsgraph-stage=graph-free-end\nweb-depsgraph-stage=global-main-restore-end\nweb-depsgraph-stage=blend-free-end"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"token": "web:test:nonmesh-binary",
|
||||||
|
"familyIds": [
|
||||||
|
"N-015"
|
||||||
|
],
|
||||||
|
"command": "npm --prefix web run test:nonmesh-binary",
|
||||||
|
"exitCode": 0,
|
||||||
|
"durationMs": 4132,
|
||||||
|
"outputSha256": "0b7d02b2b5ba6c772edf687e2270ae8672b9a16be61e3793f23a2987278413e3",
|
||||||
|
"output": "> blender-web-editor@0.1.0 test:nonmesh-binary\n> playwright test --config playwright.config.ts -g \"one-million-point binary transfer gate\"\n\n\nRunning 1 test using 1 worker\n\n ✓ 1 tests/e2e/smoke.spec.ts:402:1 › enforces the N-015 one-million-point binary transfer gate (1.5s)\n\n 1 passed (3.3s)"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"token": "web:test:selection-history",
|
||||||
|
"familyIds": [
|
||||||
|
"N-015"
|
||||||
|
],
|
||||||
|
"command": "npm --prefix web run test:selection-history",
|
||||||
|
"exitCode": 0,
|
||||||
|
"durationMs": 4046,
|
||||||
|
"outputSha256": "b130771034a5d42212e4df33b6738159b73c7353bf985233393c506e8a274cb1",
|
||||||
|
"output": "> blender-web-editor@0.1.0 test:selection-history\n> playwright test --config playwright.config.ts -g \"N-015 selection history\"\n\n\nRunning 1 test using 1 worker\n\n ✓ 1 tests/e2e/smoke.spec.ts:1060:1 › keeps N-015 selection history bounded and rejects stale raycast hits (1.5s)\n\n 1 passed (3.2s)"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"token": "web:test:nonmesh-interaction",
|
||||||
|
"familyIds": [
|
||||||
|
"N-015"
|
||||||
|
],
|
||||||
|
"command": "npm --prefix web run test:nonmesh-interaction",
|
||||||
|
"exitCode": 0,
|
||||||
|
"durationMs": 4296,
|
||||||
|
"outputSha256": "7c96e5f69ac5c5a567bb2345fbd8b0793a80d54d8b87ef4a55b7c8cdb223e231",
|
||||||
|
"output": "> blender-web-editor@0.1.0 test:nonmesh-interaction\n> playwright test --config playwright.config.ts -g \"N-015 curve gizmo interaction\"\n\n\nRunning 1 test using 1 worker\n\n ✓ 1 tests/e2e/smoke.spec.ts:1081:1 › validates the N-015 curve gizmo interaction transaction boundary (1.7s)\n\n 1 passed (3.5s)"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"token": "web:test:vdb",
|
||||||
|
"familyIds": [
|
||||||
|
"N-015"
|
||||||
|
],
|
||||||
|
"command": "npm --prefix web run test:vdb",
|
||||||
|
"exitCode": 0,
|
||||||
|
"durationMs": 4025,
|
||||||
|
"outputSha256": "1aa70cf5c66b18abf4f2214c67834377c7c50b46a207e84fc50de89dbeb1c1cc",
|
||||||
|
"output": "> blender-web-editor@0.1.0 test:vdb\n> playwright test --config playwright.config.ts -g \"VDB conversion boundary\"\n\n\nRunning 1 test using 1 worker\n\n ✓ 1 tests/e2e/smoke.spec.ts:1156:1 › validates the VDB conversion boundary and NanoVDB streaming contract (1.5s)\n\n 1 passed (3.2s)"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"token": "web:test:vdb-availability",
|
||||||
|
"familyIds": [
|
||||||
|
"N-015"
|
||||||
|
],
|
||||||
|
"command": "npm --prefix web run test:vdb-availability",
|
||||||
|
"exitCode": 0,
|
||||||
|
"durationMs": 389,
|
||||||
|
"outputSha256": "09d74a026344f6f10405b879be73c1bdf060ef1eb1d11b4aa2c1ab60ff256d49",
|
||||||
|
"output": "> blender-web-editor@0.1.0 test:vdb-availability\n> node ../tools/web/check-vdb-availability.mjs\n\nvdb-availability-ok core=READY release=BLOCKED browser-openvdb=disabled desktop=ready server=ready opfs=ready webgpu-core=ready main-roundtrip=ready viewport=blocked advanced-material=blocked resources=9"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"token": "web:test:vdb-native",
|
||||||
|
"familyIds": [
|
||||||
|
"N-015"
|
||||||
|
],
|
||||||
|
"command": "npm --prefix web run test:vdb-native",
|
||||||
|
"exitCode": 0,
|
||||||
|
"durationMs": 1259,
|
||||||
|
"outputSha256": "1a431ee0076697fa70cfc3230f3400b85dd45cf668a645c2a83276f615e241a4",
|
||||||
|
"output": "> blender-web-editor@0.1.0 test:vdb-native\n> node ../tools/web/check-vdb-native-pipeline.mjs\n\nvdb-native-pipeline-ok resources=14 converter=openvdb13-nanovdb32 conversion-deterministic=1 semantic-deterministic=1 official-deterministic=1 malformed=blocked browser-openvdb=off"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"token": "web:test:vdb-server",
|
||||||
|
"familyIds": [
|
||||||
|
"N-015"
|
||||||
|
],
|
||||||
|
"command": "npm --prefix web run test:vdb-server",
|
||||||
|
"exitCode": 0,
|
||||||
|
"durationMs": 648,
|
||||||
|
"outputSha256": "5ed0afb34328b5da0ec5702504cb1178999da5e11953129caec407d2c81c0d43",
|
||||||
|
"output": "> blender-web-editor@0.1.0 test:vdb-server\n> node ../tools/web/check-vdb-server-job.mjs\n\nvdb-server-job-ok job=vdb-b59105490496a2fee0dda646 bytes=2563944 idempotent=1 signed=1 isolated=1 cancelled=1 atomic=1 desktop-server-hash=equal"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"token": "web:test:vdb-opfs",
|
||||||
|
"familyIds": [
|
||||||
|
"N-015"
|
||||||
|
],
|
||||||
|
"command": "npm --prefix web run test:vdb-opfs",
|
||||||
|
"exitCode": 0,
|
||||||
|
"durationMs": 5297,
|
||||||
|
"outputSha256": "cabf007e624b204168d00a5f292c33fbdc560e8584a609c11f7286d16e26b6e9",
|
||||||
|
"output": "> blender-web-editor@0.1.0 test:vdb-opfs\n> playwright test --config playwright.config.ts -g \"hash-bound NanoVDB project through OPFS\"\n\n\nRunning 1 test using 1 worker\n\n ✓ 1 tests/e2e/smoke.spec.ts:1206:1 › commits and reopens a hash-bound NanoVDB project through OPFS (2.7s)\n\n 1 passed (4.5s)"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"token": "web:test:vdb-webgpu",
|
||||||
|
"familyIds": [
|
||||||
|
"N-015"
|
||||||
|
],
|
||||||
|
"command": "npm --prefix web run test:vdb-webgpu",
|
||||||
|
"exitCode": 0,
|
||||||
|
"durationMs": 62192,
|
||||||
|
"outputSha256": "3885ee85fd05995c04f1a790fa134f1bdbf39503dc6a00e471fc99a77dbccf6c",
|
||||||
|
"output": "> blender-web-editor@0.1.0 test:vdb-webgpu\n> playwright test --config playwright.config.ts -g \"real NanoVDB Float32 tree with WebGPU\"\n\n\nRunning 1 test using 1 worker\n\n ✓ 1 tests/e2e/smoke.spec.ts:1232:1 › samples and integrates a real NanoVDB Float32 tree with WebGPU (59.6s)\n\n 1 passed (1.0m)"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"token": "web:test:vdb-viewport",
|
||||||
|
"familyIds": [
|
||||||
|
"N-015"
|
||||||
|
],
|
||||||
|
"command": "npm --prefix web run test:vdb-viewport",
|
||||||
|
"exitCode": 0,
|
||||||
|
"durationMs": 116435,
|
||||||
|
"outputSha256": "eb54044817e597de7a1978fe2b8cf9d97324a2aeb0cd4ab1574a4385401f7211",
|
||||||
|
"output": "> blender-web-editor@0.1.0 test:vdb-viewport\n> playwright test --config playwright.config.ts -g \"both production viewport backends\"\n\n\nRunning 1 test using 1 worker\n\n ✓ 1 tests/e2e/smoke.spec.ts:1264:1 › renders a real NanoVDB volume through both production viewport backends (1.9m)\n\n 1 passed (1.9m)"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"token": "web:test:vdb-faults",
|
||||||
|
"familyIds": [
|
||||||
|
"N-015",
|
||||||
|
"N-023"
|
||||||
|
],
|
||||||
|
"command": "npm --prefix web run test:vdb-faults",
|
||||||
|
"exitCode": 0,
|
||||||
|
"durationMs": 5724,
|
||||||
|
"outputSha256": "ba420837572b94192e0dd81660674ef5cc406d77e675ad1322d705749fe785cb",
|
||||||
|
"output": "> blender-web-editor@0.1.0 test:vdb-faults\n> playwright test --config playwright.config.ts -g \"NanoVDB paging from network, Worker and WebGPU device faults\"\n\n\nRunning 1 test using 1 worker\n\n ✓ 1 tests/e2e/smoke.spec.ts:1344:1 › recovers NanoVDB paging from network, Worker and WebGPU device faults (3.2s)\n\n 1 passed (4.9s)"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"token": "web:e2e:N-015|non-mesh",
|
||||||
|
"familyIds": [
|
||||||
|
"N-015"
|
||||||
|
],
|
||||||
|
"command": "npm --prefix web run test:e2e -- --workers=1 -g \"N-015|non-mesh\"",
|
||||||
|
"exitCode": 0,
|
||||||
|
"durationMs": 17945,
|
||||||
|
"outputSha256": "dabeaa176c79b4688db7d0c11faac4026ca2ea98994d582ff8006eb76a4a533d",
|
||||||
|
"output": "> blender-web-editor@0.1.0 test:e2e\n> playwright test --config playwright.config.ts --workers=1 -g N-015|non-mesh\n\n\nRunning 10 tests using 1 worker\n\n ✓ 1 tests/e2e/smoke.spec.ts:295:1 › reads bounded non-mesh data blocks and previews supported geometry (2.3s)\n ✓ 2 tests/e2e/smoke.spec.ts:341:1 › reports structured N-015 gates for previewable and resource-backed data (1.2s)\n ✓ 3 tests/e2e/smoke.spec.ts:402:1 › enforces the N-015 one-million-point binary transfer gate (946ms)\n ✓ 4 tests/e2e/smoke.spec.ts:417:1 › rejects malformed N-015 binary chunks, integer overflow and budget overflow (919ms)\n ✓ 5 tests/e2e/smoke.spec.ts:1060:1 › keeps N-015 selection history bounded and rejects stale raycast hits (912ms)\n ✓ 6 tests/e2e/smoke.spec.ts:1081:1 › validates the N-015 curve gizmo interaction transaction boundary (1.2s)\n ✓ 7 tests/e2e/smoke.spec.ts:1098:40 › previews an N-015 Curve handle drag and commits Main once in main-thread Chromium (3.0s)\n ✓ 8 tests/e2e/smoke.spec.ts:1098:40 › previews an N-015 Curve handle drag and commits Main once in OffscreenCanvas Chromium (2.2s)\n ✓ 9 tests/e2e/smoke.spec.ts:1435:1 › returns real Blender evaluations for legacy non-mesh geometry (1.4s)\n ✓ 10 tests/e2e/smoke.spec.ts:1467:1 › keeps N-015 non-mesh previews in the OffscreenCanvas renderer (1.4s)\n\n 10 passed (17.1s)"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"token": "web:e2e:real OPFS quota",
|
||||||
|
"familyIds": [
|
||||||
|
"N-015"
|
||||||
|
],
|
||||||
|
"command": "npm --prefix web run test:e2e -- --workers=1 -g \"real OPFS quota\"",
|
||||||
|
"exitCode": 0,
|
||||||
|
"durationMs": 4220,
|
||||||
|
"outputSha256": "3b5bbe03b09487bee7d7d21821833cdd863c7e6ccc77a62329a1c83ea8a69526",
|
||||||
|
"output": "> blender-web-editor@0.1.0 test:e2e\n> playwright test --config playwright.config.ts --workers=1 -g real OPFS quota\n\n\nRunning 1 test using 1 worker\n\n ✓ 1 tests/e2e/smoke.spec.ts:1595:1 › enforces a real OPFS quota without replacing the committed project (1.6s)\n\n 1 passed (3.4s)"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"token": "web:test:grease-pencil",
|
||||||
|
"familyIds": [
|
||||||
|
"N-016"
|
||||||
|
],
|
||||||
|
"command": "npm --prefix web run test:grease-pencil",
|
||||||
|
"exitCode": 0,
|
||||||
|
"durationMs": 702,
|
||||||
|
"outputSha256": "54038fad56465ca7ac6826cf69854c227c3812ce375031af549f2771861c9473",
|
||||||
|
"output": "> blender-web-editor@0.1.0 test:grease-pencil\n> node ../tools/web/check-grease-pencil-roundtrip.mjs\n\ngrease-pencil-roundtrip-ok layer-frame-stroke=passed undo-redo=passed save-reopen=passed"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"token": "web:test:grease-pencil-editor",
|
||||||
|
"familyIds": [
|
||||||
|
"N-016"
|
||||||
|
],
|
||||||
|
"command": "npm --prefix web run test:grease-pencil-editor",
|
||||||
|
"exitCode": 0,
|
||||||
|
"durationMs": 4097,
|
||||||
|
"outputSha256": "c4109eb01c634356551cf19e26a3a3859f612e7511adfbeb25f092e9591e77bc",
|
||||||
|
"output": "> blender-web-editor@0.1.0 test:grease-pencil-editor\n> playwright test --config playwright.config.ts -g \"N-016 Grease Pencil editor context\"\n\n\nRunning 1 test using 1 worker\n\n ✓ 1 tests/e2e/smoke.spec.ts:510:1 › validates the N-016 Grease Pencil editor context transaction boundary (1.5s)\n\n 1 passed (3.3s)"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"token": "web:e2e:N-016 Grease Pencil",
|
||||||
|
"familyIds": [
|
||||||
|
"N-016"
|
||||||
|
],
|
||||||
|
"command": "npm --prefix web run test:e2e -- --workers=1 -g \"N-016 Grease Pencil\"",
|
||||||
|
"exitCode": 0,
|
||||||
|
"durationMs": 19150,
|
||||||
|
"outputSha256": "613880a4c3506a4671fa7283c37890d85b37b10d899af99a5202625130ccf463",
|
||||||
|
"output": "> blender-web-editor@0.1.0 test:e2e\n> playwright test --config playwright.config.ts --workers=1 -g N-016 Grease Pencil\n\n\nRunning 12 tests using 1 worker\n\n ✓ 1 tests/e2e/smoke.spec.ts:437:1 › enforces the N-016 Grease Pencil layer/frame/drawing/stroke/point schema budget (1.4s)\n ✓ 2 tests/e2e/smoke.spec.ts:453:1 › renders a bounded previous/next N-016 Grease Pencil onion-skin preview (872ms)\n ✓ 3 tests/e2e/smoke.spec.ts:485:3 › renders the N-016 Grease Pencil current-frame strokes in main-thread Chromium (1.3s)\n ✓ 4 tests/e2e/smoke.spec.ts:485:3 › renders the N-016 Grease Pencil current-frame strokes in OffscreenCanvas Chromium (1.2s)\n ✓ 5 tests/e2e/smoke.spec.ts:510:1 › validates the N-016 Grease Pencil editor context transaction boundary (917ms)\n ✓ 6 tests/e2e/smoke.spec.ts:526:1 › raycasts and highlights N-016 Grease Pencil points with stable drawing identity (1.3s)\n ✓ 7 tests/e2e/smoke.spec.ts:541:40 › previews N-016 Grease Pencil points and commits Main once in main-thread Chromium (1.8s)\n ✓ 8 tests/e2e/smoke.spec.ts:541:40 › previews N-016 Grease Pencil points and commits Main once in OffscreenCanvas Chromium (1.5s)\n ✓ 9 tests/e2e/smoke.spec.ts:565:1 › edits N-016 Grease Pencil layers and frames from the bounded editor panel (1.6s)\n ✓ 10 tests/e2e/smoke.spec.ts:580:1 › navigates real N-016 Grease Pencil drawing frames in the bounded Dope Sheet (1.6s)\n ✓ 11 tests/e2e/smoke.spec.ts:595:1 › moves an N-016 Grease Pencil point through one revision-bound Main transaction (1.5s)\n ✓ 12 tests/e2e/smoke.spec.ts:606:1 › reopens an edited N-016 Grease Pencil drawing after WebEngine Worker restart (1.5s)\n\n 12 passed (18.3s)"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"token": "web:test:paint-roundtrip",
|
||||||
|
"familyIds": [
|
||||||
|
"N-017"
|
||||||
|
],
|
||||||
|
"command": "npm --prefix web run test:paint-roundtrip",
|
||||||
|
"exitCode": 0,
|
||||||
|
"durationMs": 757,
|
||||||
|
"outputSha256": "80170948f7a18d6e0fa314c0a3ddc660abb67b1fc018f66a944d080516523fc0",
|
||||||
|
"output": "> blender-web-editor@0.1.0 test:paint-roundtrip\n> node ../tools/web/check-paint-roundtrip.mjs\n\npaint-roundtrip-ok vertex-color=passed vertex-weight-normalize=passed mirror-gate=passed undo-redo=passed save-reopen=passed"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"token": "web:e2e:N-017 paint",
|
||||||
|
"familyIds": [
|
||||||
|
"N-017"
|
||||||
|
],
|
||||||
|
"command": "npm --prefix web run test:e2e -- --workers=1 -g \"N-017 paint\"",
|
||||||
|
"exitCode": 0,
|
||||||
|
"durationMs": 4194,
|
||||||
|
"outputSha256": "b2b4157a480bfa0ddc1d3d5031baec6b784e7917b1b6781072791724d96fe655",
|
||||||
|
"output": "> blender-web-editor@0.1.0 test:e2e\n> playwright test --config playwright.config.ts --workers=1 -g N-017 paint\n\n\nRunning 1 test using 1 worker\n\n ✓ 1 tests/e2e/smoke.spec.ts:648:1 › enforces the N-017 paint stroke, bounded brush and UDIM patch budgets (1.5s)\n\n 1 passed (3.3s)"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"token": "web:e2e:N-018 physics",
|
||||||
|
"familyIds": [
|
||||||
|
"N-018"
|
||||||
|
],
|
||||||
|
"command": "npm --prefix web run test:e2e -- --workers=1 -g \"N-018 physics\"",
|
||||||
|
"exitCode": 0,
|
||||||
|
"durationMs": 4083,
|
||||||
|
"outputSha256": "67606d91993447632a709a163b3ca2a39634ffe343d523327f6a2084a5b253d1",
|
||||||
|
"output": "> blender-web-editor@0.1.0 test:e2e\n> playwright test --config playwright.config.ts --workers=1 -g N-018 physics\n\n\nRunning 1 test using 1 worker\n\n ✓ 1 tests/e2e/smoke.spec.ts:746:1 › validates the N-018 physics capability and cache manifests without claiming solvers (1.5s)\n\n 1 passed (3.3s)"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"token": "web:test:simulation-cache",
|
||||||
|
"familyIds": [
|
||||||
|
"N-018"
|
||||||
|
],
|
||||||
|
"command": "npm --prefix web run test:simulation-cache",
|
||||||
|
"exitCode": 0,
|
||||||
|
"durationMs": 4157,
|
||||||
|
"outputSha256": "995c1e31bb89e4124999b1e60d7cb0ac09cd5276f07e15f7affb796624a177c6",
|
||||||
|
"output": "> blender-web-editor@0.1.0 test:simulation-cache\n> playwright test --config playwright.config.ts -g \"Simulation caches\"\n\n\nRunning 1 test using 1 worker\n\n ✓ 1 tests/e2e/smoke.spec.ts:1631:1 › persists and revalidates content-addressed Simulation caches across Worker restarts (1.5s)\n\n 1 passed (3.3s)"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"token": "web:test:physics-main-reader",
|
||||||
|
"familyIds": [
|
||||||
|
"N-018"
|
||||||
|
],
|
||||||
|
"command": "npm --prefix web run test:physics-main-reader",
|
||||||
|
"exitCode": 0,
|
||||||
|
"durationMs": 733,
|
||||||
|
"outputSha256": "40b88699ab714c717a59461345880235d337e833b614056b13d43594f797fa03",
|
||||||
|
"output": "> blender-web-editor@0.1.0 test:physics-main-reader\n> node ../tools/web/check-physics-main-reader.mjs\n\nphysics-main-reader-ok families=CLOTH,SOFT_BODY dependencies=COLLISION settings-sha256=passed save-reopen=passed playback=BLOCKED solver=BLOCKED"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"token": "web:test:lighting-roundtrip",
|
||||||
|
"familyIds": [
|
||||||
|
"N-019"
|
||||||
|
],
|
||||||
|
"command": "npm --prefix web run test:lighting-roundtrip",
|
||||||
|
"exitCode": 0,
|
||||||
|
"durationMs": 717,
|
||||||
|
"outputSha256": "14be8ff1c568f56cdbdb4fe9b0cd091f2014e024a049d1b11aa6a800d5d76e02",
|
||||||
|
"output": "> blender-web-editor@0.1.0 test:lighting-roundtrip\n> node ../tools/web/check-lighting-roundtrip.mjs\n\nlighting-roundtrip-ok camera-writer-dof=passed light-shadow-exposure=passed world-mist=passed color-management-reader=passed white-balance-gate=passed undo-redo=passed save-reopen=passed"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"token": "web:e2e:N-019 Scene exposure",
|
||||||
|
"familyIds": [
|
||||||
|
"N-019"
|
||||||
|
],
|
||||||
|
"command": "npm --prefix web run test:e2e -- --workers=1 -g \"N-019 Scene exposure\"",
|
||||||
|
"exitCode": 0,
|
||||||
|
"durationMs": 4550,
|
||||||
|
"outputSha256": "55f18e2d99876fafdb02d050c9a712cca9e3d618b90d018160d9387244279420",
|
||||||
|
"output": "> blender-web-editor@0.1.0 test:e2e\n> playwright test --config playwright.config.ts --workers=1 -g N-019 Scene exposure\n\n\nRunning 1 test using 1 worker\n\n ✓ 1 tests/e2e/smoke.spec.ts:772:1 › maps N-019 Scene exposure and light shadow metadata without using legacy World exposure (2.0s)\n\n 1 passed (3.7s)"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"token": "web:test:compositor-main-reader",
|
||||||
|
"familyIds": [
|
||||||
|
"N-020"
|
||||||
|
],
|
||||||
|
"command": "npm --prefix web run test:compositor-main-reader",
|
||||||
|
"exitCode": 0,
|
||||||
|
"durationMs": 692,
|
||||||
|
"outputSha256": "abcaddcb533bf00bfd0a0aa151e351e03615c3ed3a8b8e0b8d7a26ca7c0e25d6",
|
||||||
|
"output": "> blender-web-editor@0.1.0 test:compositor-main-reader\n> node ../tools/web/check-compositor-main-reader.mjs\n\ncompositor-main-reader-ok graph-structure=passed exposure-invert-parameters=passed socket-links=passed unsupported-preserved=passed"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"token": "web:e2e:N-020 CPU compositor",
|
||||||
|
"familyIds": [
|
||||||
|
"N-020"
|
||||||
|
],
|
||||||
|
"command": "npm --prefix web run test:e2e -- --workers=1 -g \"N-020 CPU compositor\"",
|
||||||
|
"exitCode": 0,
|
||||||
|
"durationMs": 4177,
|
||||||
|
"outputSha256": "af1cc149bed90ef32bc0c85c705ccecdd96208299fafd90a73b6fa8896f102ef",
|
||||||
|
"output": "> blender-web-editor@0.1.0 test:e2e\n> playwright test --config playwright.config.ts --workers=1 -g N-020 CPU compositor\n\n\nRunning 1 test using 1 worker\n\n ✓ 1 tests/e2e/smoke.spec.ts:834:1 › executes the bounded N-020 CPU compositor and preserves unsupported nodes as gates (1.5s)\n\n 1 passed (3.3s)"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"token": "web:test:sequencer-main-reader",
|
||||||
|
"familyIds": [
|
||||||
|
"N-021"
|
||||||
|
],
|
||||||
|
"command": "npm --prefix web run test:sequencer-main-reader",
|
||||||
|
"exitCode": 0,
|
||||||
|
"durationMs": 680,
|
||||||
|
"outputSha256": "5b798b9549d5ab530f123b91fdd8d1347b49277c294725d77ba37421a4b1b739",
|
||||||
|
"output": "> blender-web-editor@0.1.0 test:sequencer-main-reader\n> node ../tools/web/check-sequencer-main-reader.mjs\n\nsequencer-main-reader-ok media-paths=passed still-image-range=passed fps=passed effect-dependencies=passed"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"token": "web:e2e:N-021 sequencer",
|
||||||
|
"familyIds": [
|
||||||
|
"N-021"
|
||||||
|
],
|
||||||
|
"command": "npm --prefix web run test:e2e -- --workers=1 -g \"N-021 sequencer\"",
|
||||||
|
"exitCode": 0,
|
||||||
|
"durationMs": 4076,
|
||||||
|
"outputSha256": "3aa8c2ae171d05a17169afe602dbbf3be7ee541bbe2f24c60e06b21754f779d4",
|
||||||
|
"output": "> blender-web-editor@0.1.0 test:e2e\n> playwright test --config playwright.config.ts --workers=1 -g N-021 sequencer\n\n\nRunning 1 test using 1 worker\n\n ✓ 1 tests/e2e/smoke.spec.ts:879:1 › validates N-021 sequencer strips, deterministic edits, sandbox paths and codec gates (1.4s)\n\n 1 passed (3.2s)"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"token": "web:test:mask-main-reader",
|
||||||
|
"familyIds": [
|
||||||
|
"N-022"
|
||||||
|
],
|
||||||
|
"command": "npm --prefix web run test:mask-main-reader",
|
||||||
|
"exitCode": 0,
|
||||||
|
"durationMs": 718,
|
||||||
|
"outputSha256": "ebf3ada68dda29abe2008343900b2ffad6d41e9750a3e9204c7b70674bbee692",
|
||||||
|
"output": "> blender-web-editor@0.1.0 test:mask-main-reader\n> node ../tools/web/check-mask-main-reader.mjs\n\nmask-main-reader-ok layers-splines=passed handles-feather=passed locked-editable-selection=passed"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"token": "web:e2e:N-022 tracking",
|
||||||
|
"familyIds": [
|
||||||
|
"N-022"
|
||||||
|
],
|
||||||
|
"command": "npm --prefix web run test:e2e -- --workers=1 -g \"N-022 tracking\"",
|
||||||
|
"exitCode": 0,
|
||||||
|
"durationMs": 4098,
|
||||||
|
"outputSha256": "1858048af0a5d9c8926a4aea37e8acf62e0ef6988f0c1e6dd4ce0c28ab4a8870",
|
||||||
|
"output": "> blender-web-editor@0.1.0 test:e2e\n> playwright test --config playwright.config.ts --workers=1 -g N-022 tracking\n\n\nRunning 1 test using 1 worker\n\n ✓ 1 tests/e2e/smoke.spec.ts:922:1 › validates N-022 tracking markers, masks, resource bindings and solve gates (1.5s)\n\n 1 passed (3.3s)"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"token": "web:test:library-main-reader",
|
||||||
|
"familyIds": [
|
||||||
|
"N-023"
|
||||||
|
],
|
||||||
|
"command": "npm --prefix web run test:library-main-reader",
|
||||||
|
"exitCode": 0,
|
||||||
|
"durationMs": 712,
|
||||||
|
"outputSha256": "fa46d8ff58c3f7d52cc6b4744e47db2c80827462557fdbbec3fe504b67c356cf",
|
||||||
|
"output": "> blender-web-editor@0.1.0 test:library-main-reader\n> node ../tools/web/check-library-main-reader.mjs\n\nreports | Read library: 'resources/image_resource_library.blend', '//resources/image_resource_library.blend', parent '<direct>'\n | \u001b[2;37msource/blender/blenkernel/intern/report.cc:51 BKE_report_log\u001b[0m\nreports | \u001b[1;33mWARNING\u001b[0m Unable to open 'resources/image_resource_library.blend': No such file or directory\n | \u001b[2;37msource/blender/blenkernel/intern/report.cc:54 BKE_report_log\u001b[0m\nreports | Cannot find lib 'resources/image_resource_library.blend'\n | \u001b[2;37msource/blender/blenkernel/intern/report.cc:51 BKE_report_log\u001b[0m\nlibrary-main-reader-ok relative-path=passed readonly=passed dependency-inventory=passed"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"token": "web:e2e:N-023 asset",
|
||||||
|
"familyIds": [
|
||||||
|
"N-023"
|
||||||
|
],
|
||||||
|
"command": "npm --prefix web run test:e2e -- --workers=1 -g \"N-023 asset\"",
|
||||||
|
"exitCode": 0,
|
||||||
|
"durationMs": 4172,
|
||||||
|
"outputSha256": "e8d063ee602df3b253284024afc2dfe18ea7c12de31f0fa902ceffa9036c5f7d",
|
||||||
|
"output": "> blender-web-editor@0.1.0 test:e2e\n> playwright test --config playwright.config.ts --workers=1 -g N-023 asset\n\n\nRunning 1 test using 1 worker\n\n ✓ 1 tests/e2e/smoke.spec.ts:969:1 › validates N-023 asset catalogs, library graphs, archive budgets and IO gates (1.5s)\n\n 1 passed (3.3s)"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"token": "web:test:editor-main-reader",
|
||||||
|
"familyIds": [
|
||||||
|
"N-024"
|
||||||
|
],
|
||||||
|
"command": "npm --prefix web run test:editor-main-reader",
|
||||||
|
"exitCode": 0,
|
||||||
|
"durationMs": 691,
|
||||||
|
"outputSha256": "814ab7c0fe0c737a9cbafe6f8fe59564dcb94f9244ac4af1e7b60b9d675e815b",
|
||||||
|
"output": "> blender-web-editor@0.1.0 test:editor-main-reader\n> node ../tools/web/check-editor-main-reader.mjs\n\neditor-main-reader-ok workspaces=9 areas=36 regions=167"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"token": "web:e2e:N-024 editor",
|
||||||
|
"familyIds": [
|
||||||
|
"N-024"
|
||||||
|
],
|
||||||
|
"command": "npm --prefix web run test:e2e -- --workers=1 -g \"N-024 editor\"",
|
||||||
|
"exitCode": 0,
|
||||||
|
"durationMs": 4084,
|
||||||
|
"outputSha256": "f36025cb33bfa95624cd2c0db5aaa948ef13c768a75cbcefbc67f41feaf3e48e",
|
||||||
|
"output": "> blender-web-editor@0.1.0 test:e2e\n> playwright test --config playwright.config.ts --workers=1 -g N-024 editor\n\n\nRunning 1 test using 1 worker\n\n ✓ 1 tests/e2e/smoke.spec.ts:997:1 › validates N-024 editor context, selection sync, layout budgets and workflow gates (1.5s)\n\n 1 passed (3.3s)"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"token": "web:test:script-main-reader",
|
||||||
|
"familyIds": [
|
||||||
|
"N-025"
|
||||||
|
],
|
||||||
|
"command": "npm --prefix web run test:script-main-reader",
|
||||||
|
"exitCode": 0,
|
||||||
|
"durationMs": 700,
|
||||||
|
"outputSha256": "5d06bf4178fb048dcfc30fa197812632c84e2c2266e328b325c941290c971581",
|
||||||
|
"output": "> blender-web-editor@0.1.0 test:script-main-reader\n> node ../tools/web/check-script-main-reader.mjs\n\nscript-main-reader-ok full-source=passed sha256=passed autorun-default-deny=passed"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"token": "web:test:scripting-isolation",
|
||||||
|
"familyIds": [
|
||||||
|
"N-025"
|
||||||
|
],
|
||||||
|
"command": "npm --prefix web run test:scripting-isolation",
|
||||||
|
"exitCode": 0,
|
||||||
|
"durationMs": 863,
|
||||||
|
"outputSha256": "d124b300b4531fae73575afdec53baff2e0656207a1b9f96969f8761e277cd25",
|
||||||
|
"output": "> blender-web-editor@0.1.0 test:scripting-isolation\n> node ../tools/web/check-scripting-isolation.mjs\n\nscripting-isolation-ok status=BLOCKED audit=DENY approved-key=sandbox-unavailable server=unavailable execution=disabled"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"token": "web:e2e:N-025 script",
|
||||||
|
"familyIds": [
|
||||||
|
"N-025"
|
||||||
|
],
|
||||||
|
"command": "npm --prefix web run test:e2e -- --workers=1 -g \"N-025 script\"",
|
||||||
|
"exitCode": 0,
|
||||||
|
"durationMs": 4072,
|
||||||
|
"outputSha256": "1d4a3ba828574a63106cceb1b809be66bb60d167a7c9f870c297683021954a5d",
|
||||||
|
"output": "> blender-web-editor@0.1.0 test:e2e\n> playwright test --config playwright.config.ts --workers=1 -g N-025 script\n\n\nRunning 1 test using 1 worker\n\n ✓ 1 tests/e2e/smoke.spec.ts:1017:1 › validates N-025 script policy, signatures, budgets and platform gates (1.5s)\n\n 1 passed (3.3s)"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"token": "web:e2e:N-026 release",
|
||||||
|
"familyIds": [
|
||||||
|
"N-026"
|
||||||
|
],
|
||||||
|
"command": "npm --prefix web run test:e2e -- --workers=1 -g \"N-026 release\"",
|
||||||
|
"exitCode": 0,
|
||||||
|
"durationMs": 4106,
|
||||||
|
"outputSha256": "eeffeacdf49d91e2fbaf754723d147796dedbd25721d46a3931684a9ac32fe91",
|
||||||
|
"output": "> blender-web-editor@0.1.0 test:e2e\n> playwright test --config playwright.config.ts --workers=1 -g N-026 release\n\n\nRunning 1 test using 1 worker\n\n ✓ 1 tests/e2e/smoke.spec.ts:1040:1 › keeps N-026 release manifest deterministic and blocks missing evidence (1.5s)\n\n 1 passed (3.3s)"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"token": "web:test:v1-user-loop",
|
||||||
|
"familyIds": [
|
||||||
|
"N-026"
|
||||||
|
],
|
||||||
|
"command": "npm --prefix web run test:v1-user-loop",
|
||||||
|
"exitCode": 0,
|
||||||
|
"durationMs": 5032,
|
||||||
|
"outputSha256": "81703418e4a01edfa77fa5ca7251a37d49fe9732e0e98d8a90182162014e3a94",
|
||||||
|
"output": "> blender-web-editor@0.1.0 test:v1-user-loop\n> playwright test --config playwright.config.ts tests/e2e/v1-user-loop.spec.ts\n\n\nRunning 1 test using 1 worker\n\n ✓ 1 tests/e2e/v1-user-loop.spec.ts:6:1 › completes the V1 import-edit-save-restart-reopen-GLB loop (2.7s)\n\n 1 passed (4.2s)"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"token": "web:test:release-evidence",
|
||||||
|
"familyIds": [
|
||||||
|
"N-026"
|
||||||
|
],
|
||||||
|
"command": "npm --prefix web run test:release-evidence",
|
||||||
|
"exitCode": 0,
|
||||||
|
"durationMs": 842,
|
||||||
|
"outputSha256": "12b1eec0e7653d11ae97fdfbecafeb0a71cab66ea5f343d9992826ce4a4fa9d0",
|
||||||
|
"output": "> blender-web-editor@0.1.0 test:release-evidence\n> node ../tools/web/check-release-evidence.mjs\n\nrelease-evidence-check-ok records=17 missing=0 status=READY"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"token": "web:test:browser",
|
||||||
|
"familyIds": [
|
||||||
|
"N-026"
|
||||||
|
],
|
||||||
|
"command": "npm --prefix web run test:browser",
|
||||||
|
"exitCode": 0,
|
||||||
|
"durationMs": 6933,
|
||||||
|
"outputSha256": "beec8f6ae62d79680a1b3d5a13cc0ed75c13ed2620ce0da2c1b8db4a77a5ad13",
|
||||||
|
"output": "> blender-web-editor@0.1.0 test:browser\n> playwright test --config playwright.release.config.ts\n\n\nRunning 3 tests using 1 worker\n\n ✓ 1 [chromium] › tests/e2e/cross-browser.spec.ts:6:1 › boots the offline engine, renders SceneIR and performs a Main edit (2.6s)\n ✓ 2 [chromium] › tests/e2e/cross-browser.spec.ts:32:1 › keeps content-addressed asset recovery available in Chromium (973ms)\n ✓ 3 [chromium] › tests/e2e/cross-browser.spec.ts:51:1 › keeps the committed project after quota failure and Worker restart in Chromium (994ms)\n\n 3 passed (6.1s)"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"token": "web:test:release-package",
|
||||||
|
"familyIds": [
|
||||||
|
"N-026"
|
||||||
|
],
|
||||||
|
"command": "npm --prefix web run test:release-package",
|
||||||
|
"exitCode": 0,
|
||||||
|
"durationMs": 5691,
|
||||||
|
"outputSha256": "e44e4a3d2b6698b700be7ed6deda864ecba337e467d341cf7dd6b43aaee8f846",
|
||||||
|
"output": "> blender-web-editor@0.1.0 test:release-package\n> npm run build && npm run release:sbom && node ../tools/web/check-release-package.mjs\n\n\n> blender-web-editor@0.1.0 build\n> tsc -p tsconfig.json && vite build --config app/vite.config.ts\n\nvite v8.2.0 building client environment for production...\n\u001b[2K\rtransforming...✓ 54 modules transformed.\nrendering chunks...\ncomputing gzip size...\ndist/index.html 0.46 kB │ gzip: 0.30 kB\ndist/assets/storage.worker-COUVNftM.js 36.81 kB\ndist/assets/web-engine.worker-CWkf48pG.js 245.76 kB\ndist/assets/viewport-render.worker-DZmZ4IRD.js 601.32 kB\ndist/assets/web_engine-CvVHxFlQ.wasm 15,048.04 kB │ gzip: 3,562.32 kB\ndist/assets/index-QI0bg0OR.css 11.41 kB │ gzip: 3.18 kB\ndist/assets/index-Cv6ZsBTp.js 973.17 kB │ gzip: 263.15 kB\n\n✓ built in 584ms\n\n> blender-web-editor@0.1.0 release:sbom\n> node ../tools/web/generate-sbom.mjs\n\nsbom-ok packages=150 sha256=aa50a5b98ebb51c98c6eced2ec32dff96b8df6d90e10cc101ce9e8eb44695060\nrelease-package-ok files=10 bytes=32096983\n\n[plugin rolldown:vite-resolve] Module \"module\" has been externalized for browser compatibility, imported by \"/home/mes123456/workinf_Blender_Wasm/web/app/src/vendor/blender/web_engine.js\". See https://vite.dev/guide/troubleshooting.html#module-externalized-for-browser-compatibility for more details.\n[plugin builtin:vite-reporter] \n(!) Some chunks are larger than 500 kB after minification. Consider:\n- Using dynamic import() to code-split the application\n- Use build.rolldownOptions.output.codeSplitting to improve chunking: https://rolldown.rs/reference/OutputOptions.codeSplitting\n- Adjust chunk size limit for this warning via build.chunkSizeWarningLimit."
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"token": "web:release:offline",
|
||||||
|
"familyIds": [
|
||||||
|
"N-026"
|
||||||
|
],
|
||||||
|
"command": "npm --prefix web run release:offline",
|
||||||
|
"exitCode": 0,
|
||||||
|
"durationMs": 36973,
|
||||||
|
"outputSha256": "e7202bf7f4d4a7fb171ad29073d92f1846951e5785f91dea2f23ea65121603ae",
|
||||||
|
"output": "> blender-web-editor@0.1.0 release:offline\n> npm run build && node ../tools/web/check-offline-reproducibility.mjs\n\n\n> blender-web-editor@0.1.0 build\n> tsc -p tsconfig.json && vite build --config app/vite.config.ts\n\nvite v8.2.0 building client environment for production...\n\u001b[2K\rtransforming...✓ 54 modules transformed.\nrendering chunks...\ncomputing gzip size...\ndist/index.html 0.46 kB │ gzip: 0.30 kB\ndist/assets/storage.worker-COUVNftM.js 36.81 kB\ndist/assets/web-engine.worker-CWkf48pG.js 245.76 kB\ndist/assets/viewport-render.worker-DZmZ4IRD.js 601.32 kB\ndist/assets/web_engine-CvVHxFlQ.wasm 15,048.04 kB │ gzip: 3,562.32 kB\ndist/assets/index-QI0bg0OR.css 11.41 kB │ gzip: 3.18 kB\ndist/assets/index-Cv6ZsBTp.js 973.17 kB │ gzip: 263.15 kB\n\n✓ built in 584ms\nsbom-ok packages=150 sha256=aa50a5b98ebb51c98c6eced2ec32dff96b8df6d90e10cc101ce9e8eb44695060\noffline-release-ok binary=7585557 source=205827355 sha256=f2903a75026a0a28615aeb5f4834a3859af83022c82bc50e28437dcbed68e549\nsbom-ok packages=150 sha256=aa50a5b98ebb51c98c6eced2ec32dff96b8df6d90e10cc101ce9e8eb44695060\noffline-release-ok binary=7585557 source=205827355 sha256=f2903a75026a0a28615aeb5f4834a3859af83022c82bc50e28437dcbed68e549\noffline-reproducibility-ok binary=f2903a75026a0a28615aeb5f4834a3859af83022c82bc50e28437dcbed68e549 source=8324d6353954ca782d84cbfd26f18a2a03fada17128d4f32c13a29e9c8aa50cc\n\n[plugin rolldown:vite-resolve] Module \"module\" has been externalized for browser compatibility, imported by \"/home/mes123456/workinf_Blender_Wasm/web/app/src/vendor/blender/web_engine.js\". See https://vite.dev/guide/troubleshooting.html#module-externalized-for-browser-compatibility for more details.\n[plugin builtin:vite-reporter] \n(!) Some chunks are larger than 500 kB after minification. Consider:\n- Using dynamic import() to code-split the application\n- Use build.rolldownOptions.output.codeSplitting to improve chunking: https://rolldown.rs/reference/OutputOptions.codeSplitting\n- Adjust chunk size limit for this warning via build.chunkSizeWarningLimit."
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"token": "web:test:release-performance",
|
||||||
|
"familyIds": [
|
||||||
|
"N-026"
|
||||||
|
],
|
||||||
|
"command": "npm --prefix web run test:release-performance",
|
||||||
|
"exitCode": 0,
|
||||||
|
"durationMs": 82653,
|
||||||
|
"outputSha256": "ac3c6ae6138f9434cf9c627d626694f08c24715b903cbfae3f1b5ce58f4faf3e",
|
||||||
|
"output": "> blender-web-editor@0.1.0 test:release-performance\n> node ../tools/web/check-release-performance.mjs\n\nrelease-performance-ok [{\"target\":100000,\"ratio\":0.9,\"inputTriangles\":100000,\"outputTriangles\":89999,\"elapsedMs\":81667,\"peakHeapBytes\":67108864,\"releasedPointerCount\":10,\"recoveryAllocation\":true},{\"target\":1000000,\"ratio\":1,\"inputTriangles\":1000000,\"outputTriangles\":1000000,\"elapsedMs\":507,\"peakHeapBytes\":346554368,\"releasedPointerCount\":10,\"recoveryAllocation\":true}]\n\nHeap resize call from 67108864 to 80543744 took 0.1827109999867389 msecs. Success: true\nHeap resize call from 80543744 to 96665600 took 0.1000789999961853 msecs. Success: true\nHeap resize call from 96665600 to 115998720 took 0.05835200000728946 msecs. Success: true\nHeap resize call from 115998720 to 139198464 took 0.03199099999619648 msecs. Success: true\nHeap resize call from 139198464 to 167051264 took 1.2570010000054026 msecs. Success: true\nHeap resize call from 167051264 to 200474624 took 1.3920889999862993 msecs. Success: true\nHeap resize call from 200474624 to 240582656 took 1.357361000002129 msecs. Success: true\nHeap resize call from 240582656 to 288751616 took 1.3448809999972582 msecs. Success: true\nHeap resize call from 288751616 to 346554368 took 1.3298830000130692 msecs. Success: true"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"token": "web:test:malicious-blends",
|
||||||
|
"familyIds": [
|
||||||
|
"N-026"
|
||||||
|
],
|
||||||
|
"command": "npm --prefix web run test:malicious-blends",
|
||||||
|
"exitCode": 0,
|
||||||
|
"durationMs": 489,
|
||||||
|
"outputSha256": "cbac9faec5d96aa047fb40c0e21a3d06cb3eb741d943ddd55ab4188a9cc328c0",
|
||||||
|
"output": "> blender-web-editor@0.1.0 test:malicious-blends\n> node ../tools/web/check-malicious-blends.mjs\n\nmalicious-blends-ok rejected=5"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
36
docs/web/DEPLOYMENT.md
Normal file
36
docs/web/DEPLOYMENT.md
Normal file
@@ -0,0 +1,36 @@
|
|||||||
|
# Web Blender Modeler V1 Deployment Contract
|
||||||
|
|
||||||
|
The normative machine-readable contract is `deployment-contract.json`. A server is compatible only
|
||||||
|
when every response, including errors and SPA fallbacks, preserves these headers:
|
||||||
|
|
||||||
|
| Header | Required value |
|
||||||
|
| --- | --- |
|
||||||
|
| `Cross-Origin-Opener-Policy` | `same-origin` |
|
||||||
|
| `Cross-Origin-Embedder-Policy` | `require-corp` |
|
||||||
|
| `Cross-Origin-Resource-Policy` | `same-origin` |
|
||||||
|
|
||||||
|
Production uses HTTPS. Loopback development may use `http://127.0.0.1`; `file://` is unsupported.
|
||||||
|
Runtime assets are same-origin. A reverse proxy must not strip the isolation, range, cache, MIME or
|
||||||
|
ETag headers.
|
||||||
|
|
||||||
|
## Cache policy
|
||||||
|
|
||||||
|
- `/` and `/index.html`: `Cache-Control: no-cache` so a deployment is revalidated.
|
||||||
|
- `/engine-manifest.json`: `Cache-Control: no-cache` so engine identity is revalidated.
|
||||||
|
- `/assets/*`: `Cache-Control: public, max-age=31536000, immutable`; Vite content hashes these names.
|
||||||
|
- `/vendor/blender/*`: `Cache-Control: no-cache`; these stable URLs are verified by the engine manifest.
|
||||||
|
- Other paths: `Cache-Control: no-cache` until a more specific content-addressed rule exists.
|
||||||
|
|
||||||
|
The server must use the MIME map in the JSON contract. In particular, WebAssembly is
|
||||||
|
`application/wasm`, JavaScript is `text/javascript; charset=utf-8`, and NanoVDB is
|
||||||
|
`application/x-nanovdb`.
|
||||||
|
|
||||||
|
## Range and validators
|
||||||
|
|
||||||
|
`.wasm`, `.blend` and `.nvdb` accept a single `bytes` range. A valid range returns `206` with
|
||||||
|
`Accept-Ranges`, `Content-Range`, `Content-Length` and a strong content SHA-256 ETag. An invalid or
|
||||||
|
unsatisfied range returns `416`. An `If-Range` mismatch ignores the range and returns the complete
|
||||||
|
resource with status `200`; partial bytes from a different revision must never be combined.
|
||||||
|
|
||||||
|
Run `node tools/web/check-deployment-contract.mjs` for the static contract gate. Production HTTP
|
||||||
|
behavior is a separate M6 acceptance gate and must exercise an actual server before release.
|
||||||
67
docs/web/deployment-contract.json
Normal file
67
docs/web/deployment-contract.json
Normal file
@@ -0,0 +1,67 @@
|
|||||||
|
{
|
||||||
|
"schemaVersion": 1,
|
||||||
|
"product": "Web Blender Modeler V1",
|
||||||
|
"browserScope": "Chromium",
|
||||||
|
"transport": {
|
||||||
|
"production": "https",
|
||||||
|
"localDevelopment": "http://127.0.0.1",
|
||||||
|
"fileProtocolSupported": false,
|
||||||
|
"sameOriginRuntimeAssets": true
|
||||||
|
},
|
||||||
|
"methods": ["GET", "HEAD"],
|
||||||
|
"responseHeaders": {
|
||||||
|
"allResponses": {
|
||||||
|
"Cross-Origin-Opener-Policy": "same-origin",
|
||||||
|
"Cross-Origin-Embedder-Policy": "require-corp",
|
||||||
|
"Cross-Origin-Resource-Policy": "same-origin"
|
||||||
|
},
|
||||||
|
"routes": [
|
||||||
|
{
|
||||||
|
"id": "entry-document",
|
||||||
|
"patterns": ["/", "/index.html"],
|
||||||
|
"cacheControl": "no-cache"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "runtime-manifest",
|
||||||
|
"patterns": ["/engine-manifest.json"],
|
||||||
|
"cacheControl": "no-cache"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "content-hashed-assets",
|
||||||
|
"patterns": ["/assets/*"],
|
||||||
|
"cacheControl": "public, max-age=31536000, immutable"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "stable-engine-assets",
|
||||||
|
"patterns": ["/vendor/blender/*"],
|
||||||
|
"cacheControl": "no-cache"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "fallback",
|
||||||
|
"patterns": ["*"],
|
||||||
|
"cacheControl": "no-cache"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"mimeTypes": {
|
||||||
|
".html": "text/html; charset=utf-8",
|
||||||
|
".js": "text/javascript; charset=utf-8",
|
||||||
|
".css": "text/css; charset=utf-8",
|
||||||
|
".wasm": "application/wasm",
|
||||||
|
".json": "application/json; charset=utf-8",
|
||||||
|
".png": "image/png",
|
||||||
|
".wav": "audio/wav",
|
||||||
|
".blend": "application/octet-stream",
|
||||||
|
".nvdb": "application/x-nanovdb"
|
||||||
|
},
|
||||||
|
"rangeRequests": {
|
||||||
|
"extensions": [".wasm", ".blend", ".nvdb"],
|
||||||
|
"unit": "bytes",
|
||||||
|
"singleRangeOnly": true,
|
||||||
|
"etag": "strong-sha256",
|
||||||
|
"satisfiedStatus": 206,
|
||||||
|
"unsatisfiedStatus": 416,
|
||||||
|
"requiredSatisfiedHeaders": ["Accept-Ranges", "Content-Range", "Content-Length", "ETag"],
|
||||||
|
"ifRangeMismatch": "return-full-200"
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -3,7 +3,7 @@
|
|||||||
"dataLicense": "CC0-1.0",
|
"dataLicense": "CC0-1.0",
|
||||||
"SPDXID": "SPDXRef-DOCUMENT",
|
"SPDXID": "SPDXRef-DOCUMENT",
|
||||||
"name": "blender-web-editor-sbom",
|
"name": "blender-web-editor-sbom",
|
||||||
"documentNamespace": "https://blender-web.local/spdx/0ce47f13cb0c6d4aa08df7f3df6d81f63db9574c3cdac8a2ee6abc0cc6f8e91e",
|
"documentNamespace": "https://blender-web.local/spdx/e62e3ed568907b9c7d85050e4f2b6bd874d425cca768a0201c3c57cdd9ad9926",
|
||||||
"creationInfo": {
|
"creationInfo": {
|
||||||
"created": "1970-01-01T00:00:00Z",
|
"created": "1970-01-01T00:00:00Z",
|
||||||
"creators": [
|
"creators": [
|
||||||
@@ -2524,8 +2524,8 @@
|
|||||||
{
|
{
|
||||||
"SPDXID": "SPDXRef-npm-node-modules-nanoid-2e287a9dc6ed",
|
"SPDXID": "SPDXRef-npm-node-modules-nanoid-2e287a9dc6ed",
|
||||||
"name": "nanoid",
|
"name": "nanoid",
|
||||||
"versionInfo": "3.3.17",
|
"versionInfo": "3.3.18",
|
||||||
"downloadLocation": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.17.tgz",
|
"downloadLocation": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.18.tgz",
|
||||||
"filesAnalyzed": false,
|
"filesAnalyzed": false,
|
||||||
"licenseConcluded": "NOASSERTION",
|
"licenseConcluded": "NOASSERTION",
|
||||||
"licenseDeclared": "NOASSERTION",
|
"licenseDeclared": "NOASSERTION",
|
||||||
@@ -2534,13 +2534,13 @@
|
|||||||
{
|
{
|
||||||
"referenceCategory": "PACKAGE-MANAGER",
|
"referenceCategory": "PACKAGE-MANAGER",
|
||||||
"referenceType": "purl",
|
"referenceType": "purl",
|
||||||
"referenceLocator": "pkg:npm/nanoid@3.3.17"
|
"referenceLocator": "pkg:npm/nanoid@3.3.18"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"checksums": [
|
"checksums": [
|
||||||
{
|
{
|
||||||
"algorithm": "SHA512",
|
"algorithm": "SHA512",
|
||||||
"checksumValue": "c502dfd00dc738c9601ead27db8eff2d1b80398981edd5c9fc3bc0c46bec4818a3e395ed052990c9cbbe17c3836c7c27b3f5f21596a0c8bd7e27439f7f0d44d2"
|
"checksumValue": "0d38383096c631691f8ba55915d36ddbf71a31b432e0ebbe3a9fe1250bc611672755fa00d5003ec7344a033c3d8c3ebe19538e798aff98872e0d276e83a17afb"
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
@@ -3292,7 +3292,7 @@
|
|||||||
"checksums": [
|
"checksums": [
|
||||||
{
|
{
|
||||||
"algorithm": "SHA256",
|
"algorithm": "SHA256",
|
||||||
"checksumValue": "3c65ff96cc15c9072d2517555484ccfd0009a2814d96faadcc5bd7b2e3458503"
|
"checksumValue": "e7b16da68885fe43f32ab7b3eeb2c037b4fd8bf54fe6d5e77049aa51e294b1fb"
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -3,5 +3,5 @@
|
|||||||
"viewport": {"width": 1440, "height": 900},
|
"viewport": {"width": 1440, "height": 900},
|
||||||
"screenshot": "desktop-1440x900.png",
|
"screenshot": "desktop-1440x900.png",
|
||||||
"browser": "Google Chrome headless",
|
"browser": "Google Chrome headless",
|
||||||
"checks": ["Blender Web", "Layout", "Outliner", "Properties", "Timeline"]
|
"checks": ["Web Blender Modeler V1", "Layout", "Outliner", "Properties", "Timeline"]
|
||||||
}
|
}
|
||||||
|
|||||||
63
tools/web/check-deployment-contract.mjs
Normal file
63
tools/web/check-deployment-contract.mjs
Normal file
@@ -0,0 +1,63 @@
|
|||||||
|
import assert from "node:assert/strict";
|
||||||
|
import fs from "node:fs";
|
||||||
|
import path from "node:path";
|
||||||
|
import { fileURLToPath } from "node:url";
|
||||||
|
|
||||||
|
const root = path.resolve(path.dirname(fileURLToPath(import.meta.url)), "../..");
|
||||||
|
const contractPath = path.join(root, "docs/web/deployment-contract.json");
|
||||||
|
const contract = JSON.parse(fs.readFileSync(contractPath, "utf8"));
|
||||||
|
const guide = fs.readFileSync(path.join(root, "docs/web/DEPLOYMENT.md"), "utf8");
|
||||||
|
const vite = fs.readFileSync(path.join(root, "web/app/vite.config.ts"), "utf8");
|
||||||
|
const releaseCreator = fs.readFileSync(path.join(root, "tools/web/create-offline-release.mjs"), "utf8");
|
||||||
|
|
||||||
|
assert.equal(contract.schemaVersion, 1);
|
||||||
|
assert.equal(contract.product, "Web Blender Modeler V1");
|
||||||
|
assert.equal(contract.browserScope, "Chromium");
|
||||||
|
assert.deepEqual(contract.methods, ["GET", "HEAD"]);
|
||||||
|
assert.equal(contract.transport.production, "https");
|
||||||
|
assert.equal(contract.transport.localDevelopment, "http://127.0.0.1");
|
||||||
|
assert.equal(contract.transport.fileProtocolSupported, false);
|
||||||
|
assert.equal(contract.transport.sameOriginRuntimeAssets, true);
|
||||||
|
|
||||||
|
assert.deepEqual(contract.responseHeaders.allResponses, {
|
||||||
|
"Cross-Origin-Opener-Policy": "same-origin",
|
||||||
|
"Cross-Origin-Embedder-Policy": "require-corp",
|
||||||
|
"Cross-Origin-Resource-Policy": "same-origin",
|
||||||
|
});
|
||||||
|
const routes = Object.fromEntries(contract.responseHeaders.routes.map((route) => [route.id, route]));
|
||||||
|
assert.deepEqual(routes["entry-document"], { id: "entry-document", patterns: ["/", "/index.html"], cacheControl: "no-cache" });
|
||||||
|
assert.deepEqual(routes["runtime-manifest"], { id: "runtime-manifest", patterns: ["/engine-manifest.json"], cacheControl: "no-cache" });
|
||||||
|
assert.deepEqual(routes["content-hashed-assets"], { id: "content-hashed-assets", patterns: ["/assets/*"], cacheControl: "public, max-age=31536000, immutable" });
|
||||||
|
assert.deepEqual(routes["stable-engine-assets"], { id: "stable-engine-assets", patterns: ["/vendor/blender/*"], cacheControl: "no-cache" });
|
||||||
|
assert.deepEqual(routes.fallback, { id: "fallback", patterns: ["*"], cacheControl: "no-cache" });
|
||||||
|
|
||||||
|
for (const [extension, mime] of Object.entries({
|
||||||
|
".html": "text/html; charset=utf-8",
|
||||||
|
".js": "text/javascript; charset=utf-8",
|
||||||
|
".css": "text/css; charset=utf-8",
|
||||||
|
".wasm": "application/wasm",
|
||||||
|
".json": "application/json; charset=utf-8",
|
||||||
|
".png": "image/png",
|
||||||
|
".wav": "audio/wav",
|
||||||
|
".blend": "application/octet-stream",
|
||||||
|
".nvdb": "application/x-nanovdb",
|
||||||
|
})) assert.equal(contract.mimeTypes[extension], mime, `${extension} MIME drifted`);
|
||||||
|
|
||||||
|
assert.deepEqual(contract.rangeRequests.extensions, [".wasm", ".blend", ".nvdb"]);
|
||||||
|
assert.equal(contract.rangeRequests.unit, "bytes");
|
||||||
|
assert.equal(contract.rangeRequests.singleRangeOnly, true);
|
||||||
|
assert.equal(contract.rangeRequests.etag, "strong-sha256");
|
||||||
|
assert.equal(contract.rangeRequests.satisfiedStatus, 206);
|
||||||
|
assert.equal(contract.rangeRequests.unsatisfiedStatus, 416);
|
||||||
|
assert.deepEqual(contract.rangeRequests.requiredSatisfiedHeaders, ["Accept-Ranges", "Content-Range", "Content-Length", "ETag"]);
|
||||||
|
assert.equal(contract.rangeRequests.ifRangeMismatch, "return-full-200");
|
||||||
|
|
||||||
|
for (const value of Object.values(contract.responseHeaders.allResponses)) assert.ok(guide.includes(`\`${value}\``));
|
||||||
|
for (const status of ["206", "416", "200"]) assert.ok(guide.includes(`\`${status}\``));
|
||||||
|
assert.match(vite, /deployment-contract\.json/);
|
||||||
|
assert.match(vite, /responseHeaders\.allResponses/);
|
||||||
|
assert.match(releaseCreator, /deployment-contract\.json/);
|
||||||
|
assert.match(releaseCreator, /DEPLOYMENT\.md/);
|
||||||
|
assert.doesNotMatch(releaseCreator, /Serve app\/ from any local static HTTP server/);
|
||||||
|
|
||||||
|
process.stdout.write(`deployment-contract-ok headers=3 routes=${contract.responseHeaders.routes.length} mime=${Object.keys(contract.mimeTypes).length} rangeExtensions=${contract.rangeRequests.extensions.length}\n`);
|
||||||
@@ -5,7 +5,7 @@ repo_root="$(cd "$(dirname "${BASH_SOURCE[0]}")/../.." && pwd)"
|
|||||||
native_dir="${repo_root}/build_web/native"
|
native_dir="${repo_root}/build_web/native"
|
||||||
mkdir -p "${native_dir}"
|
mkdir -p "${native_dir}"
|
||||||
|
|
||||||
c++ -std=c++17 -O2 \
|
c++ -std=c++20 -O2 \
|
||||||
-I"${repo_root}/blender-5.2.0/source/blender/web_engine" \
|
-I"${repo_root}/blender-5.2.0/source/blender/web_engine" \
|
||||||
-I"${repo_root}/blender-5.2.0/extern/json/include" \
|
-I"${repo_root}/blender-5.2.0/extern/json/include" \
|
||||||
"${repo_root}/blender-5.2.0/source/blender/web_engine/web_engine_api.cpp" \
|
"${repo_root}/blender-5.2.0/source/blender/web_engine/web_engine_api.cpp" \
|
||||||
|
|||||||
@@ -48,6 +48,7 @@ for (const target of [100_000, 1_000_000]) {
|
|||||||
const engine = await factory({ wasmBinary });
|
const engine = await factory({ wasmBinary });
|
||||||
const source = grid(target);
|
const source = grid(target);
|
||||||
const pointers = [];
|
const pointers = [];
|
||||||
|
let report;
|
||||||
try {
|
try {
|
||||||
const positions = alloc(engine, source.positions); pointers.push(positions);
|
const positions = alloc(engine, source.positions); pointers.push(positions);
|
||||||
const indices = alloc(engine, source.indices); pointers.push(indices);
|
const indices = alloc(engine, source.indices); pointers.push(indices);
|
||||||
@@ -71,11 +72,15 @@ for (const target of [100_000, 1_000_000]) {
|
|||||||
const heapBytes = engine.HEAPU8.byteLength;
|
const heapBytes = engine.HEAPU8.byteLength;
|
||||||
assert.ok(elapsedMs <= limits[target], `${target} triangle gate exceeded ${limits[target]}ms: ${elapsedMs.toFixed(1)}ms`);
|
assert.ok(elapsedMs <= limits[target], `${target} triangle gate exceeded ${limits[target]}ms: ${elapsedMs.toFixed(1)}ms`);
|
||||||
assert.ok(heapBytes <= limits.heapBytes, `${target} triangle gate exceeded WASM heap limit: ${heapBytes}`);
|
assert.ok(heapBytes <= limits.heapBytes, `${target} triangle gate exceeded WASM heap limit: ${heapBytes}`);
|
||||||
results.push({ target, ratio, outputTriangles, elapsedMs: Math.round(elapsedMs), heapBytes });
|
report = { target, ratio, inputTriangles: source.triangleCount, outputTriangles, elapsedMs: Math.round(elapsedMs), peakHeapBytes: heapBytes };
|
||||||
}
|
}
|
||||||
finally {
|
finally {
|
||||||
for (const pointer of pointers.reverse()) if (pointer) engine._free(pointer);
|
for (const pointer of pointers.reverse()) if (pointer) engine._free(pointer);
|
||||||
}
|
}
|
||||||
|
const recoveryPointer = engine._malloc(16);
|
||||||
|
assert.ok(recoveryPointer > 0, `${target} triangle gate did not recover a small allocation`);
|
||||||
|
engine._free(recoveryPointer);
|
||||||
|
results.push({ ...report, releasedPointerCount: pointers.length, recoveryAllocation: true });
|
||||||
}
|
}
|
||||||
|
|
||||||
process.stdout.write(`release-performance-ok ${JSON.stringify(results)}\n`);
|
process.stdout.write(`release-performance-ok ${JSON.stringify(results)}\n`);
|
||||||
|
|||||||
15
tools/web/check-v1-acceptance-coverage.mjs
Normal file
15
tools/web/check-v1-acceptance-coverage.mjs
Normal file
@@ -0,0 +1,15 @@
|
|||||||
|
import assert from "node:assert/strict";
|
||||||
|
import { buildAcceptancePlan, readAcceptanceInputs } from "./v1-acceptance-lib.mjs";
|
||||||
|
|
||||||
|
const { ledger, packageManifest } = readAcceptanceInputs();
|
||||||
|
const plan = buildAcceptancePlan(ledger, packageManifest);
|
||||||
|
|
||||||
|
assert.equal(plan.entries.some((entry) => entry.token === "web:test:nonmesh-usd-blender-roundtrip"), true);
|
||||||
|
assert.equal(plan.entries.some((entry) => entry.token === "web:test:v1-user-loop"), true);
|
||||||
|
assert.equal(plan.entries.some((entry) => entry.token === "web:release:offline"), true);
|
||||||
|
assert.ok(plan.e2eCount > 0);
|
||||||
|
assert.ok(plan.reusedCount > 0);
|
||||||
|
|
||||||
|
process.stdout.write(
|
||||||
|
`v1-acceptance-coverage-ok families=${plan.familyCount} declarations=${plan.declarationCount} unique=${plan.uniqueCount} scripts=${plan.scriptCount} e2e=${plan.e2eCount} reused=${plan.reusedCount}\n`,
|
||||||
|
);
|
||||||
38
tools/web/check-v1-acceptance-evidence.mjs
Normal file
38
tools/web/check-v1-acceptance-evidence.mjs
Normal file
@@ -0,0 +1,38 @@
|
|||||||
|
import assert from "node:assert/strict";
|
||||||
|
import fs from "node:fs";
|
||||||
|
import { evidencePath, expectedEvidenceIdentity, sha256 } from "./v1-acceptance-lib.mjs";
|
||||||
|
|
||||||
|
const { ledgerSha256, packageSha256, plan, toolSourceSha256 } = expectedEvidenceIdentity();
|
||||||
|
assert.ok(fs.existsSync(evidencePath), "V1 acceptance evidence is missing; run release:v1-acceptance");
|
||||||
|
const manifest = JSON.parse(fs.readFileSync(evidencePath, "utf8"));
|
||||||
|
|
||||||
|
assert.equal(manifest.schemaVersion, 1);
|
||||||
|
assert.equal(manifest.sourceSha256, ledgerSha256, "V1 acceptance evidence is stale relative to parity ledger");
|
||||||
|
assert.equal(manifest.packageSha256, packageSha256, "V1 acceptance evidence is stale relative to web package scripts");
|
||||||
|
assert.deepEqual(manifest.toolSourceSha256, toolSourceSha256, "V1 acceptance evidence is stale relative to acceptance tools");
|
||||||
|
assert.ok(Number.isFinite(Date.parse(manifest.generatedAt)), "V1 acceptance generatedAt is invalid");
|
||||||
|
assert.equal(manifest.summary.families, plan.familyCount);
|
||||||
|
assert.equal(manifest.summary.declarations, plan.declarationCount);
|
||||||
|
assert.equal(manifest.summary.unique, plan.uniqueCount);
|
||||||
|
assert.equal(manifest.summary.passed, plan.uniqueCount);
|
||||||
|
assert.equal(manifest.summary.failed, 0);
|
||||||
|
assert.equal(manifest.environment.blender.includes("USD=True"), true, "acceptance Blender must provide USD");
|
||||||
|
assert.match(manifest.environment.blenderExecutableSha256, /^[a-f0-9]{64}$/, "acceptance Blender executable hash is missing");
|
||||||
|
assert.match(manifest.environment.blenderArchiveSha256, /^[a-f0-9]{64}$/, "acceptance Blender archive hash is missing");
|
||||||
|
assert.equal(manifest.results.length, plan.uniqueCount);
|
||||||
|
assert.deepEqual(manifest.results.map((result) => result.token), plan.entries.map((entry) => entry.token));
|
||||||
|
|
||||||
|
for (const [index, result] of manifest.results.entries()) {
|
||||||
|
const expected = plan.entries[index];
|
||||||
|
assert.deepEqual(result.familyIds, expected.familyIds, `${result.token}: family mapping drifted`);
|
||||||
|
assert.equal(result.command, expected.command, `${result.token}: command drifted`);
|
||||||
|
assert.equal(result.exitCode, 0, `${result.token}: command failed`);
|
||||||
|
assert.ok(result.durationMs >= 0, `${result.token}: duration is invalid`);
|
||||||
|
assert.match(result.outputSha256, /^[a-f0-9]{64}$/, `${result.token}: output hash is missing`);
|
||||||
|
assert.equal(result.outputSha256, sha256(result.output), `${result.token}: stored output does not match its hash`);
|
||||||
|
assert.ok(result.output.trim().length > 0, `${result.token}: output is empty`);
|
||||||
|
}
|
||||||
|
|
||||||
|
process.stdout.write(
|
||||||
|
`v1-acceptance-evidence-ok families=${plan.familyCount} declarations=${plan.declarationCount} unique=${plan.uniqueCount} sha256=${sha256(fs.readFileSync(evidencePath))}\n`,
|
||||||
|
);
|
||||||
21
tools/web/check-v1-acceptance-release-binding.mjs
Normal file
21
tools/web/check-v1-acceptance-release-binding.mjs
Normal file
@@ -0,0 +1,21 @@
|
|||||||
|
import assert from "node:assert/strict";
|
||||||
|
import fs from "node:fs";
|
||||||
|
import path from "node:path";
|
||||||
|
import { evidencePath, root, sha256 } from "./v1-acceptance-lib.mjs";
|
||||||
|
|
||||||
|
assert.ok(fs.existsSync(evidencePath), "V1 acceptance evidence is missing");
|
||||||
|
const acceptanceSha256 = sha256(fs.readFileSync(evidencePath));
|
||||||
|
const releaseEvidence = JSON.parse(fs.readFileSync(path.join(root, "docs/status/release-evidence.json"), "utf8"));
|
||||||
|
const userLoopRecord = releaseEvidence.evidence?.records?.find((record) => record.id === "v1-user-loop");
|
||||||
|
|
||||||
|
assert.ok(userLoopRecord, "release evidence has no v1-user-loop record");
|
||||||
|
assert.equal(userLoopRecord.exitCode, 0, "release v1-user-loop record failed");
|
||||||
|
assert.ok(userLoopRecord.output?.trim().length > 0, "release v1-user-loop output is empty");
|
||||||
|
assert.ok(
|
||||||
|
userLoopRecord.artifactSha256?.includes(acceptanceSha256),
|
||||||
|
"current V1 acceptance evidence is not bound to release evidence",
|
||||||
|
);
|
||||||
|
|
||||||
|
process.stdout.write(
|
||||||
|
`v1-acceptance-release-binding-ok acceptanceSha256=${acceptanceSha256} releaseGeneratedAt=${releaseEvidence.generatedAt}\n`,
|
||||||
|
);
|
||||||
@@ -47,8 +47,8 @@ if (process.argv.includes("--record-v1-user-loop")) {
|
|||||||
const record = executeEvidenceCommand(
|
const record = executeEvidenceCommand(
|
||||||
"v1-user-loop",
|
"v1-user-loop",
|
||||||
[],
|
[],
|
||||||
"npm --prefix web run test:v1-user-loop",
|
"npm --prefix web run test:v1-user-loop && npm --prefix web run test:v1-acceptance-evidence",
|
||||||
["web/tests/e2e/v1-user-loop.spec.ts", "web/app/src/vendor/blender/web_engine.wasm"],
|
["web/tests/e2e/v1-user-loop.spec.ts", "web/app/src/vendor/blender/web_engine.wasm", "docs/status/v1-acceptance-evidence.json"],
|
||||||
);
|
);
|
||||||
current.evidence.records = [...current.evidence.records.filter((item) => item.id !== record.id), record];
|
current.evidence.records = [...current.evidence.records.filter((item) => item.id !== record.id), record];
|
||||||
const manifest = {
|
const manifest = {
|
||||||
@@ -173,7 +173,7 @@ function run(id, fields, command, artifacts = []) {
|
|||||||
|
|
||||||
run("sbom", ["provenance.license", "provenance.sbom"], "npm --prefix web run release:sbom", ["docs/web/sbom.spdx.json", "docs/web/third-party-notices.json", "web/package-lock.json"]);
|
run("sbom", ["provenance.license", "provenance.sbom"], "npm --prefix web run release:sbom", ["docs/web/sbom.spdx.json", "docs/web/third-party-notices.json", "web/package-lock.json"]);
|
||||||
run("vdb-boundary", [], "npm --prefix web run test:vdb-availability && npm --prefix web run test:vdb-native && npm --prefix web run test:vdb", ["web/protocol/volume-vdb.ts", "web/app/src/volume/nanovdb-stream.ts", "tools/vdb/vdb_to_nanovdb.cc", "docs/status/vdb-native-evidence.json", "docs/VDB_NANOVDB_WEBGPU_IMPLEMENTATION_PLAN.md"]);
|
run("vdb-boundary", [], "npm --prefix web run test:vdb-availability && npm --prefix web run test:vdb-native && npm --prefix web run test:vdb", ["web/protocol/volume-vdb.ts", "web/app/src/volume/nanovdb-stream.ts", "tools/vdb/vdb_to_nanovdb.cc", "docs/status/vdb-native-evidence.json", "docs/VDB_NANOVDB_WEBGPU_IMPLEMENTATION_PLAN.md"]);
|
||||||
run("v1-user-loop", [], "npm --prefix web run test:v1-user-loop", ["web/tests/e2e/v1-user-loop.spec.ts", "web/app/src/vendor/blender/web_engine.wasm"]);
|
run("v1-user-loop", [], "npm --prefix web run test:v1-user-loop && npm --prefix web run test:v1-acceptance-evidence", ["web/tests/e2e/v1-user-loop.spec.ts", "web/app/src/vendor/blender/web_engine.wasm", "docs/status/v1-acceptance-evidence.json"]);
|
||||||
run("chromium", ["browser.chromium", "runtime.offline", "runtime.workerRestart", "runtime.opfsRecovery"], "npm --prefix web run test:e2e -- --workers=1 && npm --prefix web run test:browser", ["web/app/src/vendor/blender/web_engine.wasm"]);
|
run("chromium", ["browser.chromium", "runtime.offline", "runtime.workerRestart", "runtime.opfsRecovery"], "npm --prefix web run test:e2e -- --workers=1 && npm --prefix web run test:browser", ["web/app/src/vendor/blender/web_engine.wasm"]);
|
||||||
run("geometry-1m", ["performance.geometry1M"], "npm --prefix web run test:release-performance", ["web/app/src/vendor/blender/web_engine.wasm"]);
|
run("geometry-1m", ["performance.geometry1M"], "npm --prefix web run test:release-performance", ["web/app/src/vendor/blender/web_engine.wasm"]);
|
||||||
run("geometry-10m", ["performance.geometry10M"], "npm --prefix web run test:geometry-10m-performance", ["web/protocol/geometry-stream.ts", "web/app/src/performance/large-geometry.ts", "web/app/src/workers/geometry-stream.worker.ts", "web/tests/e2e/geometry-10m-performance.spec.ts"]);
|
run("geometry-10m", ["performance.geometry10M"], "npm --prefix web run test:geometry-10m-performance", ["web/protocol/geometry-stream.ts", "web/app/src/performance/large-geometry.ts", "web/app/src/workers/geometry-stream.worker.ts", "web/tests/e2e/geometry-10m-performance.spec.ts"]);
|
||||||
|
|||||||
@@ -15,12 +15,16 @@ fs.cpSync(path.join(root, "web/dist"), path.join(bundle, "app"), { recursive: tr
|
|||||||
fs.copyFileSync(path.join(root, "blender-5.2.0/COPYING"), path.join(bundle, "COPYING"));
|
fs.copyFileSync(path.join(root, "blender-5.2.0/COPYING"), path.join(bundle, "COPYING"));
|
||||||
fs.copyFileSync(path.join(root, "docs/web/third-party-notices.json"), path.join(bundle, "third-party-notices.json"));
|
fs.copyFileSync(path.join(root, "docs/web/third-party-notices.json"), path.join(bundle, "third-party-notices.json"));
|
||||||
fs.copyFileSync(path.join(root, "docs/web/sbom.spdx.json"), path.join(bundle, "sbom.spdx.json"));
|
fs.copyFileSync(path.join(root, "docs/web/sbom.spdx.json"), path.join(bundle, "sbom.spdx.json"));
|
||||||
|
fs.copyFileSync(path.join(root, "docs/web/deployment-contract.json"), path.join(bundle, "deployment-contract.json"));
|
||||||
|
fs.copyFileSync(path.join(root, "docs/web/DEPLOYMENT.md"), path.join(bundle, "DEPLOYMENT.md"));
|
||||||
fs.copyFileSync(path.join(root, "blender-5.2.0/extern/opensubdiv-source/LICENSE.txt"), path.join(bundle, "LICENSE-OpenSubdiv.txt"));
|
fs.copyFileSync(path.join(root, "blender-5.2.0/extern/opensubdiv-source/LICENSE.txt"), path.join(bundle, "LICENSE-OpenSubdiv.txt"));
|
||||||
fs.copyFileSync(path.join(root, "blender-5.2.0/extern/gmp-source/COPYING.LESSERv3"), path.join(bundle, "LICENSE-GMP-LGPLv3.txt"));
|
fs.copyFileSync(path.join(root, "blender-5.2.0/extern/gmp-source/COPYING.LESSERv3"), path.join(bundle, "LICENSE-GMP-LGPLv3.txt"));
|
||||||
fs.writeFileSync(path.join(bundle, "README.txt"), [
|
fs.writeFileSync(path.join(bundle, "README.txt"), [
|
||||||
"Blender Web offline release",
|
"Web Blender Modeler V1 offline release",
|
||||||
"",
|
"",
|
||||||
"Serve app/ from any local static HTTP server. The application has no runtime CDN dependency.",
|
"Serve app/ from a static HTTP server that implements deployment-contract.json.",
|
||||||
|
"See DEPLOYMENT.md for the required isolation, cache, MIME, ETag and byte-range behavior.",
|
||||||
|
"The application has no runtime CDN dependency.",
|
||||||
"Opening index.html directly is unsupported because browsers restrict module Workers and WASM under file://.",
|
"Opening index.html directly is unsupported because browsers restrict module Workers and WASM under file://.",
|
||||||
"See SOURCE_OFFER.txt and third-party-notices.json for licensing and corresponding source.",
|
"See SOURCE_OFFER.txt and third-party-notices.json for licensing and corresponding source.",
|
||||||
"",
|
"",
|
||||||
|
|||||||
103
tools/web/run-v1-acceptance.mjs
Normal file
103
tools/web/run-v1-acceptance.mjs
Normal file
@@ -0,0 +1,103 @@
|
|||||||
|
import fs from "node:fs";
|
||||||
|
import { spawnSync } from "node:child_process";
|
||||||
|
import {
|
||||||
|
evidencePath,
|
||||||
|
expectedEvidenceIdentity,
|
||||||
|
root,
|
||||||
|
sha256,
|
||||||
|
} from "./v1-acceptance-lib.mjs";
|
||||||
|
|
||||||
|
const { ledgerSha256, packageSha256, plan, toolSourceSha256 } = expectedEvidenceIdentity();
|
||||||
|
const basePort = Number.parseInt(process.env.WEB_ACCEPTANCE_PORT ?? "5400", 10);
|
||||||
|
if (!Number.isInteger(basePort) || basePort < 1024 || basePort > 65000 - plan.entries.length) {
|
||||||
|
throw new Error(`WEB_ACCEPTANCE_PORT must leave ${plan.entries.length} sequential ports available`);
|
||||||
|
}
|
||||||
|
|
||||||
|
function capture(command, args) {
|
||||||
|
const result = spawnSync(command, args, {
|
||||||
|
cwd: root,
|
||||||
|
encoding: "utf8",
|
||||||
|
env: { ...process.env, FORCE_COLOR: "0", NO_COLOR: "1" },
|
||||||
|
maxBuffer: 16 * 1024 * 1024,
|
||||||
|
});
|
||||||
|
return `${result.stdout ?? ""}\n${result.stderr ?? ""}`.trim();
|
||||||
|
}
|
||||||
|
|
||||||
|
const blenderBin = process.env.BLENDER_BIN ?? "build_blender_5.2.0/bin/blender";
|
||||||
|
const blenderArchiveSha256 = process.env.BLENDER_ARCHIVE_SHA256 ?? "";
|
||||||
|
if (blenderArchiveSha256 && !/^[a-f0-9]{64}$/.test(blenderArchiveSha256)) {
|
||||||
|
throw new Error("BLENDER_ARCHIVE_SHA256 must be a lowercase SHA-256");
|
||||||
|
}
|
||||||
|
const environment = {
|
||||||
|
node: process.version,
|
||||||
|
npm: capture("npm", ["--version"]).split(/\r?\n/)[0],
|
||||||
|
chromium: capture(process.env.CHROME_PATH ?? "/home/mes123456/.local/bin/google-chrome", ["--version"]).split(/\r?\n/)[0],
|
||||||
|
blenderBin: process.env.BLENDER_BIN ? "BLENDER_BIN" : blenderBin,
|
||||||
|
blenderExecutableSha256: fs.existsSync(blenderBin) ? sha256(fs.readFileSync(blenderBin)) : "unavailable",
|
||||||
|
blenderArchiveSha256: blenderArchiveSha256 || "unavailable",
|
||||||
|
blender: capture(blenderBin, [
|
||||||
|
"--background",
|
||||||
|
"--factory-startup",
|
||||||
|
"--python-expr",
|
||||||
|
"import bpy; print('ACCEPTANCE_BLENDER=' + bpy.app.version_string + ';USD=' + str(bpy.app.build_options.usd))",
|
||||||
|
]).split(/\r?\n/).filter((line) => line.includes("ACCEPTANCE_BLENDER=")).at(-1) ?? "unavailable",
|
||||||
|
};
|
||||||
|
|
||||||
|
const results = [];
|
||||||
|
for (const [index, entry] of plan.entries.entries()) {
|
||||||
|
const started = Date.now();
|
||||||
|
process.stdout.write(`[${index + 1}/${plan.entries.length}] ${entry.token}\n`);
|
||||||
|
const result = spawnSync("npm", ["--prefix", "web", "run", entry.script, ...entry.args], {
|
||||||
|
cwd: root,
|
||||||
|
encoding: "utf8",
|
||||||
|
env: {
|
||||||
|
...process.env,
|
||||||
|
FORCE_COLOR: "0",
|
||||||
|
NO_COLOR: "1",
|
||||||
|
WEB_TEST_PORT: String(basePort + index),
|
||||||
|
},
|
||||||
|
maxBuffer: 32 * 1024 * 1024,
|
||||||
|
});
|
||||||
|
const output = `${result.stdout ?? ""}\n${result.stderr ?? ""}`.trim();
|
||||||
|
const storedOutput = output.slice(-4096) || `${entry.token} produced no output`;
|
||||||
|
const exitCode = result.status ?? 1;
|
||||||
|
results.push({
|
||||||
|
token: entry.token,
|
||||||
|
familyIds: entry.familyIds,
|
||||||
|
command: entry.command,
|
||||||
|
exitCode,
|
||||||
|
durationMs: Date.now() - started,
|
||||||
|
outputSha256: sha256(storedOutput),
|
||||||
|
output: storedOutput,
|
||||||
|
});
|
||||||
|
process.stdout.write(` exit=${exitCode} durationMs=${results.at(-1).durationMs}\n`);
|
||||||
|
}
|
||||||
|
|
||||||
|
const manifest = {
|
||||||
|
schemaVersion: 1,
|
||||||
|
source: "docs/status/parity-ledger.json",
|
||||||
|
sourceSha256: ledgerSha256,
|
||||||
|
package: "web/package.json",
|
||||||
|
packageSha256,
|
||||||
|
toolSourceSha256,
|
||||||
|
generatedAt: new Date().toISOString(),
|
||||||
|
environment,
|
||||||
|
summary: {
|
||||||
|
families: plan.familyCount,
|
||||||
|
declarations: plan.declarationCount,
|
||||||
|
unique: plan.uniqueCount,
|
||||||
|
passed: results.filter((result) => result.exitCode === 0).length,
|
||||||
|
failed: results.filter((result) => result.exitCode !== 0).length,
|
||||||
|
},
|
||||||
|
results,
|
||||||
|
};
|
||||||
|
fs.writeFileSync(evidencePath, `${JSON.stringify(manifest, null, 2)}\n`);
|
||||||
|
|
||||||
|
const failed = results.filter((result) => result.exitCode !== 0);
|
||||||
|
process.stdout.write(
|
||||||
|
`v1-acceptance-run-complete passed=${manifest.summary.passed} failed=${manifest.summary.failed} evidence=${evidencePath}\n`,
|
||||||
|
);
|
||||||
|
if (failed.length > 0) {
|
||||||
|
process.stderr.write(`failed acceptance: ${failed.map((result) => result.token).join(", ")}\n`);
|
||||||
|
process.exitCode = 1;
|
||||||
|
}
|
||||||
107
tools/web/v1-acceptance-lib.mjs
Normal file
107
tools/web/v1-acceptance-lib.mjs
Normal file
@@ -0,0 +1,107 @@
|
|||||||
|
import assert from "node:assert/strict";
|
||||||
|
import crypto from "node:crypto";
|
||||||
|
import fs from "node:fs";
|
||||||
|
import path from "node:path";
|
||||||
|
import { fileURLToPath } from "node:url";
|
||||||
|
|
||||||
|
export const root = path.resolve(path.dirname(fileURLToPath(import.meta.url)), "../..");
|
||||||
|
export const ledgerPath = path.join(root, "docs/status/parity-ledger.json");
|
||||||
|
export const packagePath = path.join(root, "web/package.json");
|
||||||
|
export const evidencePath = path.join(root, "docs/status/v1-acceptance-evidence.json");
|
||||||
|
export const toolRelativePaths = [
|
||||||
|
"tools/web/v1-acceptance-lib.mjs",
|
||||||
|
"tools/web/check-v1-acceptance-coverage.mjs",
|
||||||
|
"tools/web/run-v1-acceptance.mjs",
|
||||||
|
"tools/web/check-v1-acceptance-evidence.mjs",
|
||||||
|
];
|
||||||
|
|
||||||
|
export function sha256(value) {
|
||||||
|
return crypto.createHash("sha256").update(value).digest("hex");
|
||||||
|
}
|
||||||
|
|
||||||
|
export function readAcceptanceInputs() {
|
||||||
|
const ledgerBytes = fs.readFileSync(ledgerPath);
|
||||||
|
const packageBytes = fs.readFileSync(packagePath);
|
||||||
|
return {
|
||||||
|
ledgerBytes,
|
||||||
|
packageBytes,
|
||||||
|
ledger: JSON.parse(ledgerBytes),
|
||||||
|
packageManifest: JSON.parse(packageBytes),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
function resolveAcceptance(token, scripts) {
|
||||||
|
assert.equal(typeof token, "string", "acceptance token must be a string");
|
||||||
|
if (token.startsWith("web:e2e:")) {
|
||||||
|
const pattern = token.slice("web:e2e:".length);
|
||||||
|
assert.ok(pattern.trim().length > 0, `${token}: E2E pattern must not be empty`);
|
||||||
|
assert.equal(typeof scripts["test:e2e"], "string", `${token}: web test:e2e script is missing`);
|
||||||
|
return {
|
||||||
|
token,
|
||||||
|
kind: "e2e",
|
||||||
|
script: "test:e2e",
|
||||||
|
args: ["--", "--workers=1", "-g", pattern],
|
||||||
|
command: `npm --prefix web run test:e2e -- --workers=1 -g ${JSON.stringify(pattern)}`,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
const match = /^web:(test|release):(.+)$/.exec(token);
|
||||||
|
assert.ok(match, `${token}: unsupported acceptance namespace`);
|
||||||
|
const script = `${match[1]}:${match[2]}`;
|
||||||
|
assert.equal(typeof scripts[script], "string", `${token}: web package script ${script} is missing`);
|
||||||
|
return {
|
||||||
|
token,
|
||||||
|
kind: "script",
|
||||||
|
script,
|
||||||
|
args: [],
|
||||||
|
command: `npm --prefix web run ${script}`,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
export function buildAcceptancePlan(ledger, packageManifest) {
|
||||||
|
assert.equal(ledger.schemaVersion, 2, "unsupported parity ledger schema");
|
||||||
|
assert.ok(Array.isArray(ledger.families), "parity ledger families must be an array");
|
||||||
|
assert.equal(ledger.families.length, 12, "V1 acceptance expects N-015 through N-026");
|
||||||
|
assert.ok(packageManifest.scripts && typeof packageManifest.scripts === "object", "web package scripts are missing");
|
||||||
|
|
||||||
|
const byToken = new Map();
|
||||||
|
let declarationCount = 0;
|
||||||
|
for (const family of ledger.families) {
|
||||||
|
assert.match(family.id, /^N-0(?:1[5-9]|2[0-6])$/, `${family.id}: unexpected family id`);
|
||||||
|
assert.ok(Array.isArray(family.acceptance) && family.acceptance.length > 0, `${family.id}: acceptance is empty`);
|
||||||
|
assert.equal(new Set(family.acceptance).size, family.acceptance.length, `${family.id}: duplicate acceptance token`);
|
||||||
|
for (const token of family.acceptance) {
|
||||||
|
declarationCount += 1;
|
||||||
|
const existing = byToken.get(token);
|
||||||
|
if (existing) {
|
||||||
|
existing.familyIds.push(family.id);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
byToken.set(token, { ...resolveAcceptance(token, packageManifest.scripts), familyIds: [family.id] });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const entries = [...byToken.values()];
|
||||||
|
return {
|
||||||
|
familyCount: ledger.families.length,
|
||||||
|
declarationCount,
|
||||||
|
uniqueCount: entries.length,
|
||||||
|
scriptCount: entries.filter((entry) => entry.kind === "script").length,
|
||||||
|
e2eCount: entries.filter((entry) => entry.kind === "e2e").length,
|
||||||
|
reusedCount: entries.filter((entry) => entry.familyIds.length > 1).length,
|
||||||
|
entries,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
export function expectedEvidenceIdentity() {
|
||||||
|
const inputs = readAcceptanceInputs();
|
||||||
|
return {
|
||||||
|
...inputs,
|
||||||
|
plan: buildAcceptancePlan(inputs.ledger, inputs.packageManifest),
|
||||||
|
ledgerSha256: sha256(inputs.ledgerBytes),
|
||||||
|
packageSha256: sha256(inputs.packageBytes),
|
||||||
|
toolSourceSha256: Object.fromEntries(
|
||||||
|
toolRelativePaths.map((relativePath) => [relativePath, sha256(fs.readFileSync(path.join(root, relativePath)))]),
|
||||||
|
),
|
||||||
|
};
|
||||||
|
}
|
||||||
@@ -4,7 +4,7 @@
|
|||||||
<meta charset="UTF-8" />
|
<meta charset="UTF-8" />
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||||
<meta name="theme-color" content="#202124" />
|
<meta name="theme-color" content="#202124" />
|
||||||
<title>Blender Web Editor</title>
|
<title>Web Blender Modeler V1</title>
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
<div id="root"></div>
|
<div id="root"></div>
|
||||||
|
|||||||
@@ -302,7 +302,7 @@ function ViewportPlaceholder({ snapshot, geometryBuffers, nonMeshGeometryBuffers
|
|||||||
<canvas ref={canvasRef} className="viewport-canvas" aria-label="Three.js WebGL2 视口" />
|
<canvas ref={canvasRef} className="viewport-canvas" aria-label="Three.js WebGL2 视口" />
|
||||||
<div className="axis-gizmo" aria-hidden="true"><span className="axis-x">X</span><span className="axis-y">Y</span><span className="axis-z">Z</span></div>
|
<div className="axis-gizmo" aria-hidden="true"><span className="axis-x">X</span><span className="axis-y">Y</span><span className="axis-z">Z</span></div>
|
||||||
{viewportError || !snapshot || snapshot.nodes.length === 0 ? <div className="viewport-message">
|
{viewportError || !snapshot || snapshot.nodes.length === 0 ? <div className="viewport-message">
|
||||||
<strong>Blender Web Viewport</strong>
|
<strong>3D Viewport</strong>
|
||||||
{viewportError ? <span>WebGL 不可用,已保留场景编辑界面:{viewportError}</span> : <span>Three.js WebGL2 适配器</span>}
|
{viewportError ? <span>WebGL 不可用,已保留场景编辑界面:{viewportError}</span> : <span>Three.js WebGL2 适配器</span>}
|
||||||
</div> : null}
|
</div> : null}
|
||||||
<div className="viewport-toolbar" aria-label="视口工具">
|
<div className="viewport-toolbar" aria-label="视口工具">
|
||||||
@@ -1344,7 +1344,7 @@ export function App() {
|
|||||||
return (
|
return (
|
||||||
<main className="blender-app" data-workspace={workspace} data-ui-revision={uiState.context.revision}>
|
<main className="blender-app" data-workspace={workspace} data-ui-revision={uiState.context.revision}>
|
||||||
<header className="topbar">
|
<header className="topbar">
|
||||||
<div className="brand"><span className="brand-mark" aria-hidden="true">◈</span><span>Blender Web</span></div>
|
<div className="brand"><span className="brand-mark" aria-hidden="true">◈</span><span>Web Blender Modeler V1</span></div>
|
||||||
<nav className="menu-bar" aria-label="主菜单"><button type="button" onClick={() => fileInputRef.current?.click()}>文件</button><button type="button">编辑</button><button type="button">渲染</button><button type="button">窗口</button><button type="button">帮助</button></nav>
|
<nav className="menu-bar" aria-label="主菜单"><button type="button" onClick={() => fileInputRef.current?.click()}>文件</button><button type="button">编辑</button><button type="button">渲染</button><button type="button">窗口</button><button type="button">帮助</button></nav>
|
||||||
<nav className="workspace-tabs" aria-label="工作区">
|
<nav className="workspace-tabs" aria-label="工作区">
|
||||||
{(["Layout", "Modeling", "Animation"] as WorkspaceId[]).map((item) => <button key={item} className={item === workspace ? "workspace-tab active" : "workspace-tab"} type="button" onClick={() => dispatchUI({ type: "switchWorkspace", workspaceId: item })}>{item}</button>)}
|
{(["Layout", "Modeling", "Animation"] as WorkspaceId[]).map((item) => <button key={item} className={item === workspace ? "workspace-tab active" : "workspace-tab"} type="button" onClick={() => dispatchUI({ type: "switchWorkspace", workspaceId: item })}>{item}</button>)}
|
||||||
@@ -1360,7 +1360,7 @@ export function App() {
|
|||||||
<Area className="timeline-area" editor="Timeline"><Timeline snapshot={snapshot} frame={frame} start={frameRange.start} end={frameRange.end} onFrameChange={(value) => void applyEditCommand({ type: "setFrame", frame: value })} onCommand={(command) => void applyEditCommand(command)} /></Area>
|
<Area className="timeline-area" editor="Timeline"><Timeline snapshot={snapshot} frame={frame} start={frameRange.start} end={frameRange.end} onFrameChange={(value) => void applyEditCommand({ type: "setFrame", frame: value })} onCommand={(command) => void applyEditCommand(command)} /></Area>
|
||||||
</div>
|
</div>
|
||||||
{uiState.operatorSearchOpen ? <OperatorSearch commands={operatorCommands} onClose={() => dispatchUI({ type: "toggleOperatorSearch", open: false })} /> : null}
|
{uiState.operatorSearchOpen ? <OperatorSearch commands={operatorCommands} onClose={() => dispatchUI({ type: "toggleOperatorSearch", open: false })} /> : null}
|
||||||
<footer className="status-bar"><span>Blender Web 0.1.0</span><span data-testid="scene-stats">Objects {objectCount} · Vertices {vertexCount} · Faces {faceCount}</span>{openProgress ? <span data-testid="open-progress">{openProgress.message ?? "Opening"}</span> : null}<span className="status-spacer" /><span>{manifestStatus}</span><span>{wasmStatus}</span><span data-testid="engine-status">{engineStatus}</span><span>{storageStatus}</span></footer>
|
<footer className="status-bar"><span>Web Blender Modeler V1</span><span data-testid="scene-stats">Objects {objectCount} · Vertices {vertexCount} · Faces {faceCount}</span>{openProgress ? <span data-testid="open-progress">{openProgress.message ?? "Opening"}</span> : null}<span className="status-spacer" /><span>{manifestStatus}</span><span>{wasmStatus}</span><span data-testid="engine-status">{engineStatus}</span><span>{storageStatus}</span></footer>
|
||||||
</main>
|
</main>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,9 +1,15 @@
|
|||||||
export interface BrowserCapabilities {
|
import { blockedGate, capabilityIssue, readyGate, type CapabilityGateResult } from "../../../protocol/capability-gates";
|
||||||
|
|
||||||
|
export interface WasmThreadingCapabilities {
|
||||||
|
crossOriginIsolated: boolean;
|
||||||
|
sharedArrayBuffer: boolean;
|
||||||
|
worker: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface BrowserCapabilities extends WasmThreadingCapabilities {
|
||||||
webgl2: boolean;
|
webgl2: boolean;
|
||||||
offscreenCanvas: boolean;
|
offscreenCanvas: boolean;
|
||||||
opfs: boolean;
|
opfs: boolean;
|
||||||
sharedArrayBuffer: boolean;
|
|
||||||
worker: boolean;
|
|
||||||
wasm: boolean;
|
wasm: boolean;
|
||||||
wasmSimd: boolean;
|
wasmSimd: boolean;
|
||||||
wasmThreads: boolean;
|
wasmThreads: boolean;
|
||||||
@@ -11,6 +17,34 @@ export interface BrowserCapabilities {
|
|||||||
indexedDb: boolean;
|
indexedDb: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function gateWasmThreadingCapability(capabilities: WasmThreadingCapabilities): CapabilityGateResult {
|
||||||
|
const issues = [];
|
||||||
|
if (!capabilities.crossOriginIsolated) {
|
||||||
|
issues.push(capabilityIssue(
|
||||||
|
"PLATFORM_CAPABILITY_UNAVAILABLE",
|
||||||
|
"Cross-origin isolation is required for the pthread WASM engine",
|
||||||
|
"crossOriginIsolated",
|
||||||
|
));
|
||||||
|
}
|
||||||
|
if (!capabilities.sharedArrayBuffer) {
|
||||||
|
issues.push(capabilityIssue(
|
||||||
|
"PLATFORM_CAPABILITY_UNAVAILABLE",
|
||||||
|
"SharedArrayBuffer is required for the pthread WASM engine",
|
||||||
|
"sharedArrayBuffer",
|
||||||
|
));
|
||||||
|
}
|
||||||
|
if (!capabilities.worker) {
|
||||||
|
issues.push(capabilityIssue(
|
||||||
|
"PLATFORM_CAPABILITY_UNAVAILABLE",
|
||||||
|
"Worker is required for the pthread WASM engine",
|
||||||
|
"worker",
|
||||||
|
));
|
||||||
|
}
|
||||||
|
return issues.length > 0
|
||||||
|
? blockedGate("M6-02", "WASM_PTHREAD_ENGINE", issues)
|
||||||
|
: readyGate("M6-02", "WASM_PTHREAD_ENGINE");
|
||||||
|
}
|
||||||
|
|
||||||
function supportsWasmFeature(feature: "simd" | "threads"): boolean {
|
function supportsWasmFeature(feature: "simd" | "threads"): boolean {
|
||||||
if (typeof WebAssembly === "undefined") return false;
|
if (typeof WebAssembly === "undefined") return false;
|
||||||
|
|
||||||
@@ -40,6 +74,7 @@ export function detectBrowserCapabilities(): BrowserCapabilities {
|
|||||||
const storage = typeof navigator === "undefined" ? undefined : navigator.storage;
|
const storage = typeof navigator === "undefined" ? undefined : navigator.storage;
|
||||||
|
|
||||||
return {
|
return {
|
||||||
|
crossOriginIsolated: globalThis.crossOriginIsolated === true,
|
||||||
webgl2,
|
webgl2,
|
||||||
offscreenCanvas: typeof OffscreenCanvas !== "undefined",
|
offscreenCanvas: typeof OffscreenCanvas !== "undefined",
|
||||||
opfs: Boolean(storage?.getDirectory),
|
opfs: Boolean(storage?.getDirectory),
|
||||||
|
|||||||
@@ -135,6 +135,10 @@ export class StorageClient {
|
|||||||
return this.request({ type: "listSimulationCaches", projectId }) as Promise<StorageSimulationCacheListResult>;
|
return this.request({ type: "listSimulationCaches", projectId }) as Promise<StorageSimulationCacheListResult>;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
getPendingRequestCount(): number {
|
||||||
|
return this.pending.size;
|
||||||
|
}
|
||||||
|
|
||||||
private request(command: StorageRequest["command"], transfer: Transferable[] = []): Promise<NonNullable<StorageResponse["result"]>> {
|
private request(command: StorageRequest["command"], transfer: Transferable[] = []): Promise<NonNullable<StorageResponse["result"]>> {
|
||||||
const requestId = `storage-${++this.counter}`;
|
const requestId = `storage-${++this.counter}`;
|
||||||
const request: StorageRequest = { requestId, command };
|
const request: StorageRequest = { requestId, command };
|
||||||
|
|||||||
@@ -18,7 +18,7 @@ type Request =
|
|||||||
type Response =
|
type Response =
|
||||||
| { type: "ready"; index: { stripCount: number; bucketCount: number; referenceCount: number; estimatedBytes: number } }
|
| { type: "ready"; index: { stripCount: number; bucketCount: number; referenceCount: number; estimatedBytes: number } }
|
||||||
| { type: "seekResult"; requestId: string; result: LongMediaSeekResultIR }
|
| { type: "seekResult"; requestId: string; result: LongMediaSeekResultIR }
|
||||||
| { type: "disposed"; cacheBytes: number }
|
| { type: "disposed"; releasedCacheBytes: number; cacheBytesAfter: number }
|
||||||
| { type: "error"; requestId?: string; message: string };
|
| { type: "error"; requestId?: string; message: string };
|
||||||
|
|
||||||
const scope = self as unknown as {
|
const scope = self as unknown as {
|
||||||
@@ -71,9 +71,9 @@ scope.onmessage = (event): void => {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (message.type === "cancel") { session?.cancel(); return; }
|
if (message.type === "cancel") { session?.cancel(); return; }
|
||||||
const cacheBytes = session?.cache.stats().bytes ?? 0;
|
const releasedCacheBytes = session?.cache.stats().bytes ?? 0;
|
||||||
session?.dispose();
|
session?.dispose();
|
||||||
session = null;
|
session = null;
|
||||||
assets.clear();
|
assets.clear();
|
||||||
scope.postMessage({ type: "disposed", cacheBytes });
|
scope.postMessage({ type: "disposed", releasedCacheBytes, cacheBytesAfter: 0 });
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -4,12 +4,13 @@ import react from "@vitejs/plugin-react";
|
|||||||
import fs from "node:fs";
|
import fs from "node:fs";
|
||||||
// @ts-expect-error The runtime is Node; this project intentionally avoids a browser dependency on Node types.
|
// @ts-expect-error The runtime is Node; this project intentionally avoids a browser dependency on Node types.
|
||||||
import path from "node:path";
|
import path from "node:path";
|
||||||
|
// @ts-expect-error The runtime is Node; this project intentionally avoids a browser dependency on Node types.
|
||||||
|
import { fileURLToPath } from "node:url";
|
||||||
|
|
||||||
const isolationHeaders = {
|
const deploymentContract = JSON.parse(
|
||||||
"Cross-Origin-Opener-Policy": "same-origin",
|
fs.readFileSync(path.resolve(path.dirname(fileURLToPath(import.meta.url)), "../../docs/web/deployment-contract.json"), "utf8"),
|
||||||
"Cross-Origin-Embedder-Policy": "require-corp",
|
) as { responseHeaders: { allResponses: Record<string, string> } };
|
||||||
"Cross-Origin-Resource-Policy": "same-origin",
|
const isolationHeaders = deploymentContract.responseHeaders.allResponses;
|
||||||
};
|
|
||||||
|
|
||||||
function preserveIsolationHeaders(): Plugin {
|
function preserveIsolationHeaders(): Plugin {
|
||||||
const install = (server: { middlewares: { use: (handler: (request: unknown, response: { setHeader: (name: string, value: string) => void }, next: () => void) => void) => void } }) => {
|
const install = (server: { middlewares: { use: (handler: (request: unknown, response: { setHeader: (name: string, value: string) => void }, next: () => void) => void) => void } }) => {
|
||||||
|
|||||||
@@ -1,6 +1,10 @@
|
|||||||
cmake_minimum_required(VERSION 3.24)
|
cmake_minimum_required(VERSION 3.24)
|
||||||
project(blender_web_engine LANGUAGES C CXX)
|
project(blender_web_engine LANGUAGES C CXX)
|
||||||
|
|
||||||
|
set(CMAKE_CXX_STANDARD 20)
|
||||||
|
set(CMAKE_CXX_STANDARD_REQUIRED ON)
|
||||||
|
set(CMAKE_CXX_EXTENSIONS OFF)
|
||||||
|
|
||||||
set(WITH_WEB ON CACHE BOOL "Build the browser Blender engine")
|
set(WITH_WEB ON CACHE BOOL "Build the browser Blender engine")
|
||||||
option(WEB_ENGINE_THREADS "Build the pthread variant" OFF)
|
option(WEB_ENGINE_THREADS "Build the pthread variant" OFF)
|
||||||
include("${CMAKE_CURRENT_LIST_DIR}/../../blender-5.2.0/build_files/cmake/platform/web.cmake")
|
include("${CMAKE_CURRENT_LIST_DIR}/../../blender-5.2.0/build_files/cmake/platform/web.cmake")
|
||||||
@@ -16,6 +20,7 @@ add_library(blender_web_engine_core STATIC
|
|||||||
)
|
)
|
||||||
target_include_directories(blender_web_engine_core PUBLIC
|
target_include_directories(blender_web_engine_core PUBLIC
|
||||||
"${CMAKE_CURRENT_LIST_DIR}/../../blender-5.2.0/source/blender/web_engine"
|
"${CMAKE_CURRENT_LIST_DIR}/../../blender-5.2.0/source/blender/web_engine"
|
||||||
|
"${CMAKE_CURRENT_LIST_DIR}/../../blender-5.2.0/extern/json/include"
|
||||||
)
|
)
|
||||||
|
|
||||||
add_executable(web_engine web_engine_smoke.cpp)
|
add_executable(web_engine web_engine_smoke.cpp)
|
||||||
|
|||||||
@@ -64,6 +64,7 @@ bool web_engine_blend_main_create_primitive(WebBlendMainState *,
|
|||||||
bool web_engine_blend_main_duplicate_object(WebBlendMainState *,
|
bool web_engine_blend_main_duplicate_object(WebBlendMainState *,
|
||||||
const char *,
|
const char *,
|
||||||
const std::vector<float> &,
|
const std::vector<float> &,
|
||||||
|
bool,
|
||||||
std::string &,
|
std::string &,
|
||||||
std::string &error)
|
std::string &error)
|
||||||
{
|
{
|
||||||
@@ -98,6 +99,270 @@ bool web_engine_blend_main_translate_vertices(WebBlendMainState *,
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
namespace {
|
||||||
|
|
||||||
|
bool native_main_unavailable(std::string &error)
|
||||||
|
{
|
||||||
|
error = "native smoke Main stub";
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace
|
||||||
|
|
||||||
|
#define WEB_NATIVE_MAIN_STUB(name, ...) \
|
||||||
|
bool name(__VA_ARGS__, std::string &error) \
|
||||||
|
{ \
|
||||||
|
return native_main_unavailable(error); \
|
||||||
|
}
|
||||||
|
|
||||||
|
WEB_NATIVE_MAIN_STUB(web_engine_blend_main_set_modifier_visibility,
|
||||||
|
WebBlendMainState *,
|
||||||
|
const char *,
|
||||||
|
const char *,
|
||||||
|
bool,
|
||||||
|
bool,
|
||||||
|
bool,
|
||||||
|
bool)
|
||||||
|
WEB_NATIVE_MAIN_STUB(web_engine_blend_main_set_sculpt_attributes,
|
||||||
|
WebBlendMainState *,
|
||||||
|
const char *,
|
||||||
|
const std::vector<float> &,
|
||||||
|
const std::vector<uint32_t> &)
|
||||||
|
WEB_NATIVE_MAIN_STUB(web_engine_blend_main_sculpt_stroke, WebBlendMainState *, const char *)
|
||||||
|
WEB_NATIVE_MAIN_STUB(web_engine_blend_main_mesh_edit,
|
||||||
|
WebBlendMainState *,
|
||||||
|
const char *,
|
||||||
|
const char *,
|
||||||
|
const char *,
|
||||||
|
const std::vector<uint32_t> &,
|
||||||
|
const std::vector<float> &,
|
||||||
|
float,
|
||||||
|
int)
|
||||||
|
WEB_NATIVE_MAIN_STUB(
|
||||||
|
web_engine_blend_main_create_uv_map, WebBlendMainState *, const char *, const char *)
|
||||||
|
WEB_NATIVE_MAIN_STUB(
|
||||||
|
web_engine_blend_main_set_active_uv_map, WebBlendMainState *, const char *, const char *)
|
||||||
|
WEB_NATIVE_MAIN_STUB(web_engine_blend_main_unwrap_uv,
|
||||||
|
WebBlendMainState *,
|
||||||
|
const char *,
|
||||||
|
const std::vector<uint32_t> &,
|
||||||
|
const char *)
|
||||||
|
WEB_NATIVE_MAIN_STUB(web_engine_blend_main_add_material_slot,
|
||||||
|
WebBlendMainState *,
|
||||||
|
const char *,
|
||||||
|
const char *,
|
||||||
|
std::string &)
|
||||||
|
WEB_NATIVE_MAIN_STUB(
|
||||||
|
web_engine_blend_main_remove_material_slot, WebBlendMainState *, const char *, int)
|
||||||
|
WEB_NATIVE_MAIN_STUB(web_engine_blend_main_assign_material_faces,
|
||||||
|
WebBlendMainState *,
|
||||||
|
const char *,
|
||||||
|
const std::vector<uint32_t> &,
|
||||||
|
int)
|
||||||
|
WEB_NATIVE_MAIN_STUB(web_engine_blend_main_set_material_principled,
|
||||||
|
WebBlendMainState *,
|
||||||
|
const char *,
|
||||||
|
const std::vector<float> &,
|
||||||
|
float,
|
||||||
|
float,
|
||||||
|
const std::vector<float> &,
|
||||||
|
float,
|
||||||
|
float,
|
||||||
|
float,
|
||||||
|
float,
|
||||||
|
float,
|
||||||
|
float,
|
||||||
|
float)
|
||||||
|
WEB_NATIVE_MAIN_STUB(web_engine_blend_main_import_image,
|
||||||
|
WebBlendMainState *,
|
||||||
|
const char *,
|
||||||
|
const char *,
|
||||||
|
int,
|
||||||
|
int,
|
||||||
|
const char *,
|
||||||
|
std::string &)
|
||||||
|
WEB_NATIVE_MAIN_STUB(web_engine_blend_main_set_material_image,
|
||||||
|
WebBlendMainState *,
|
||||||
|
const char *,
|
||||||
|
const char *,
|
||||||
|
const char *,
|
||||||
|
const char *)
|
||||||
|
WEB_NATIVE_MAIN_STUB(
|
||||||
|
web_engine_blend_main_set_shader_graph, WebBlendMainState *, const char *, const char *)
|
||||||
|
WEB_NATIVE_MAIN_STUB(web_engine_blend_main_insert_keyframe,
|
||||||
|
WebBlendMainState *,
|
||||||
|
const char *,
|
||||||
|
int,
|
||||||
|
const char *,
|
||||||
|
const char *)
|
||||||
|
WEB_NATIVE_MAIN_STUB(web_engine_blend_main_delete_keyframe,
|
||||||
|
WebBlendMainState *,
|
||||||
|
const char *,
|
||||||
|
int,
|
||||||
|
const char *)
|
||||||
|
WEB_NATIVE_MAIN_STUB(web_engine_blend_main_set_fcurve_interpolation,
|
||||||
|
WebBlendMainState *,
|
||||||
|
const char *,
|
||||||
|
const char *,
|
||||||
|
const char *)
|
||||||
|
WEB_NATIVE_MAIN_STUB(
|
||||||
|
web_engine_blend_main_set_active_action, WebBlendMainState *, const char *, const char *)
|
||||||
|
WEB_NATIVE_MAIN_STUB(
|
||||||
|
web_engine_blend_main_set_nla_stack, WebBlendMainState *, const char *, const char *)
|
||||||
|
WEB_NATIVE_MAIN_STUB(web_engine_blend_main_set_constraint,
|
||||||
|
WebBlendMainState *,
|
||||||
|
const char *,
|
||||||
|
const char *,
|
||||||
|
bool,
|
||||||
|
float)
|
||||||
|
WEB_NATIVE_MAIN_STUB(
|
||||||
|
web_engine_blend_main_set_parent, WebBlendMainState *, const char *, const char *, bool)
|
||||||
|
WEB_NATIVE_MAIN_STUB(web_engine_blend_main_create_collection,
|
||||||
|
WebBlendMainState *,
|
||||||
|
const char *,
|
||||||
|
const char *,
|
||||||
|
std::string &)
|
||||||
|
WEB_NATIVE_MAIN_STUB(
|
||||||
|
web_engine_blend_main_move_to_collection, WebBlendMainState *, const char *, const char *)
|
||||||
|
WEB_NATIVE_MAIN_STUB(web_engine_blend_main_rename_id,
|
||||||
|
WebBlendMainState *,
|
||||||
|
const char *,
|
||||||
|
const char *,
|
||||||
|
std::string &)
|
||||||
|
WEB_NATIVE_MAIN_STUB(web_engine_blend_main_join_objects,
|
||||||
|
WebBlendMainState *,
|
||||||
|
const char *,
|
||||||
|
const std::vector<std::string> &)
|
||||||
|
WEB_NATIVE_MAIN_STUB(web_engine_blend_main_separate_faces,
|
||||||
|
WebBlendMainState *,
|
||||||
|
const char *,
|
||||||
|
const std::vector<uint32_t> &,
|
||||||
|
const char *,
|
||||||
|
std::string &)
|
||||||
|
WEB_NATIVE_MAIN_STUB(web_engine_blend_main_apply_transform, WebBlendMainState *, const char *)
|
||||||
|
WEB_NATIVE_MAIN_STUB(
|
||||||
|
web_engine_blend_main_set_origin, WebBlendMainState *, const char *, const char *)
|
||||||
|
WEB_NATIVE_MAIN_STUB(web_engine_blend_main_create_curve,
|
||||||
|
WebBlendMainState *,
|
||||||
|
const char *,
|
||||||
|
const char *,
|
||||||
|
const std::vector<float> &,
|
||||||
|
bool,
|
||||||
|
int,
|
||||||
|
std::string &,
|
||||||
|
std::string &)
|
||||||
|
WEB_NATIVE_MAIN_STUB(
|
||||||
|
web_engine_blend_main_delete_non_mesh_data, WebBlendMainState *, const char *)
|
||||||
|
WEB_NATIVE_MAIN_STUB(web_engine_blend_main_set_curve_control_points,
|
||||||
|
WebBlendMainState *,
|
||||||
|
const char *,
|
||||||
|
const std::vector<float> &,
|
||||||
|
const std::vector<uint32_t> &,
|
||||||
|
int)
|
||||||
|
WEB_NATIVE_MAIN_STUB(web_engine_blend_main_set_curve_handle,
|
||||||
|
WebBlendMainState *,
|
||||||
|
const char *,
|
||||||
|
uint32_t,
|
||||||
|
bool,
|
||||||
|
const std::vector<float> &)
|
||||||
|
WEB_NATIVE_MAIN_STUB(web_engine_blend_main_set_curve_topology,
|
||||||
|
WebBlendMainState *,
|
||||||
|
const char *,
|
||||||
|
const WebCurveTopologyEdit &)
|
||||||
|
WEB_NATIVE_MAIN_STUB(web_engine_blend_main_set_curve_splines,
|
||||||
|
WebBlendMainState *,
|
||||||
|
const char *,
|
||||||
|
const std::vector<WebCurveSplineEdit> &,
|
||||||
|
const std::vector<float> &,
|
||||||
|
const std::vector<float> &,
|
||||||
|
const std::vector<int> &,
|
||||||
|
const std::vector<float> &)
|
||||||
|
WEB_NATIVE_MAIN_STUB(web_engine_blend_main_set_surface_topology,
|
||||||
|
WebBlendMainState *,
|
||||||
|
const char *,
|
||||||
|
const std::vector<WebSurfaceSplineEdit> &,
|
||||||
|
const std::vector<float> &,
|
||||||
|
const std::vector<float> &)
|
||||||
|
WEB_NATIVE_MAIN_STUB(
|
||||||
|
web_engine_blend_main_set_font_body, WebBlendMainState *, const char *, const char *)
|
||||||
|
WEB_NATIVE_MAIN_STUB(web_engine_blend_main_set_font_properties,
|
||||||
|
WebBlendMainState *,
|
||||||
|
const char *,
|
||||||
|
const WebFontPropertiesEdit &)
|
||||||
|
WEB_NATIVE_MAIN_STUB(web_engine_blend_main_set_font_advanced,
|
||||||
|
WebBlendMainState *,
|
||||||
|
const char *,
|
||||||
|
const std::vector<WebFontCharacterEdit> &,
|
||||||
|
const std::vector<WebFontTextBoxEdit> &,
|
||||||
|
int)
|
||||||
|
WEB_NATIVE_MAIN_STUB(web_engine_blend_main_set_font_links,
|
||||||
|
WebBlendMainState *,
|
||||||
|
const char *,
|
||||||
|
const std::array<std::string, 4> &)
|
||||||
|
WEB_NATIVE_MAIN_STUB(web_engine_blend_main_set_volume_properties,
|
||||||
|
WebBlendMainState *,
|
||||||
|
const char *,
|
||||||
|
const WebVolumePropertiesEdit &)
|
||||||
|
WEB_NATIVE_MAIN_STUB(web_engine_blend_main_set_metaball_elements,
|
||||||
|
WebBlendMainState *,
|
||||||
|
const char *,
|
||||||
|
const std::vector<WebMetaballElementEdit> &)
|
||||||
|
WEB_NATIVE_MAIN_STUB(web_engine_blend_main_grease_pencils_json,
|
||||||
|
WebBlendMainState *,
|
||||||
|
std::string &)
|
||||||
|
WEB_NATIVE_MAIN_STUB(web_engine_blend_main_physics_simulation_json,
|
||||||
|
WebBlendMainState *,
|
||||||
|
std::string &)
|
||||||
|
WEB_NATIVE_MAIN_STUB(web_engine_blend_main_create_grease_pencil_layer,
|
||||||
|
WebBlendMainState *,
|
||||||
|
const char *,
|
||||||
|
const char *)
|
||||||
|
WEB_NATIVE_MAIN_STUB(web_engine_blend_main_remove_grease_pencil_layer,
|
||||||
|
WebBlendMainState *,
|
||||||
|
const char *,
|
||||||
|
const char *)
|
||||||
|
WEB_NATIVE_MAIN_STUB(web_engine_blend_main_move_grease_pencil_layer,
|
||||||
|
WebBlendMainState *,
|
||||||
|
const char *,
|
||||||
|
const char *,
|
||||||
|
const char *)
|
||||||
|
WEB_NATIVE_MAIN_STUB(web_engine_blend_main_insert_grease_pencil_frame,
|
||||||
|
WebBlendMainState *,
|
||||||
|
const char *,
|
||||||
|
const char *,
|
||||||
|
int,
|
||||||
|
int)
|
||||||
|
WEB_NATIVE_MAIN_STUB(web_engine_blend_main_remove_grease_pencil_frame,
|
||||||
|
WebBlendMainState *,
|
||||||
|
const char *,
|
||||||
|
const char *,
|
||||||
|
int)
|
||||||
|
WEB_NATIVE_MAIN_STUB(web_engine_blend_main_set_grease_pencil_strokes,
|
||||||
|
WebBlendMainState *,
|
||||||
|
const char *,
|
||||||
|
const char *,
|
||||||
|
int,
|
||||||
|
const std::vector<WebGreasePencilStrokeEdit> &)
|
||||||
|
WEB_NATIVE_MAIN_STUB(web_engine_blend_main_set_vertex_colors,
|
||||||
|
WebBlendMainState *,
|
||||||
|
const char *,
|
||||||
|
const char *,
|
||||||
|
const char *,
|
||||||
|
const std::vector<uint32_t> &,
|
||||||
|
const std::vector<float> &)
|
||||||
|
WEB_NATIVE_MAIN_STUB(web_engine_blend_main_set_vertex_weights,
|
||||||
|
WebBlendMainState *,
|
||||||
|
const char *,
|
||||||
|
const char *,
|
||||||
|
const std::vector<uint32_t> &,
|
||||||
|
const std::vector<float> &,
|
||||||
|
bool,
|
||||||
|
bool)
|
||||||
|
WEB_NATIVE_MAIN_STUB(
|
||||||
|
web_engine_blend_main_set_render_properties, WebBlendMainState *, const char *, const char *)
|
||||||
|
|
||||||
|
#undef WEB_NATIVE_MAIN_STUB
|
||||||
|
|
||||||
bool web_engine_blend_main_write(WebBlendMainState *, std::vector<uint8_t> &, std::string &error)
|
bool web_engine_blend_main_write(WebBlendMainState *, std::vector<uint8_t> &, std::string &error)
|
||||||
{
|
{
|
||||||
error = "native smoke Main stub";
|
error = "native smoke Main stub";
|
||||||
|
|||||||
6
web/package-lock.json
generated
6
web/package-lock.json
generated
@@ -1533,9 +1533,9 @@
|
|||||||
"dev": true
|
"dev": true
|
||||||
},
|
},
|
||||||
"node_modules/nanoid": {
|
"node_modules/nanoid": {
|
||||||
"version": "3.3.17",
|
"version": "3.3.18",
|
||||||
"resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.17.tgz",
|
"resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.18.tgz",
|
||||||
"integrity": "sha512-xQLf0A3HOMlgHq0n247/LRuAOYmB7dXJ/DvAxGvsSBij45XtBSmQycu+F8ODbHwns/XyFZagyL1+J0Offw1E0g==",
|
"integrity": "sha512-DTg4MJbGMWkfi6VZFdNt2/caMbQy4Ou+Op/hJQvGEWcnVfoA1QA+xzRKAzw9jD6+GVOOeYr/mIcuDSdug6F6+w==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"funding": [
|
"funding": [
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -84,7 +84,11 @@
|
|||||||
"release:evidence-long-media": "node ../tools/web/collect-release-evidence.mjs --record-long-media",
|
"release:evidence-long-media": "node ../tools/web/collect-release-evidence.mjs --record-long-media",
|
||||||
"release:evidence-oom": "node ../tools/web/collect-release-evidence.mjs --record-oom-recovery",
|
"release:evidence-oom": "node ../tools/web/collect-release-evidence.mjs --record-oom-recovery",
|
||||||
"test:release-evidence": "node ../tools/web/check-release-evidence.mjs",
|
"test:release-evidence": "node ../tools/web/check-release-evidence.mjs",
|
||||||
|
"test:v1-acceptance-coverage": "node ../tools/web/check-v1-acceptance-coverage.mjs",
|
||||||
|
"test:v1-acceptance-evidence": "node ../tools/web/check-v1-acceptance-evidence.mjs",
|
||||||
|
"test:v1-acceptance-release-binding": "node ../tools/web/check-v1-acceptance-release-binding.mjs",
|
||||||
"test:status-consistency": "node ../tools/web/check-status-consistency.mjs",
|
"test:status-consistency": "node ../tools/web/check-status-consistency.mjs",
|
||||||
|
"release:v1-acceptance": "node ../tools/web/run-v1-acceptance.mjs",
|
||||||
"release:offline": "npm run build && node ../tools/web/check-offline-reproducibility.mjs",
|
"release:offline": "npm run build && node ../tools/web/check-offline-reproducibility.mjs",
|
||||||
"diagnose:depsgraph": "node ../tools/web/diagnose-depsgraph.mjs"
|
"diagnose:depsgraph": "node ../tools/web/diagnose-depsgraph.mjs"
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -8,7 +8,7 @@ export interface CapabilityIssue {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export interface CapabilityGateResult {
|
export interface CapabilityGateResult {
|
||||||
taskId: "N-011" | "N-012" | "N-013" | "N-014" | "N-015" | "N-018" | "N-020" | "N-021" | "N-022" | "N-023" | "N-024" | "N-025" | "N-026" | "PBR-007" | "PBR-008" | "PBR-009" | "PBR-010" | "PBR-011" | "PBR-012";
|
taskId: "M6-02" | "N-011" | "N-012" | "N-013" | "N-014" | "N-015" | "N-018" | "N-020" | "N-021" | "N-022" | "N-023" | "N-024" | "N-025" | "N-026" | "PBR-007" | "PBR-008" | "PBR-009" | "PBR-010" | "PBR-011" | "PBR-012";
|
||||||
capability: string;
|
capability: string;
|
||||||
status: "READY" | "BLOCKED";
|
status: "READY" | "BLOCKED";
|
||||||
issues: CapabilityIssue[];
|
issues: CapabilityIssue[];
|
||||||
|
|||||||
@@ -319,11 +319,13 @@ test("streams ten-million-triangle SceneIR ranges into a bounded interactive LOD
|
|||||||
const recoveryVisible = visiblePixels(recovery);
|
const recoveryVisible = visiblePixels(recovery);
|
||||||
recovery.dispose();
|
recovery.dispose();
|
||||||
recoveryCanvas.remove();
|
recoveryCanvas.remove();
|
||||||
|
const afterHeapBytes = heap();
|
||||||
|
|
||||||
return {
|
return {
|
||||||
elapsedMs: Math.round(performance.now() - started),
|
elapsedMs: Math.round(performance.now() - started),
|
||||||
baselineHeapBytes,
|
baselineHeapBytes,
|
||||||
peakHeapBytes,
|
peakHeapBytes,
|
||||||
|
afterHeapBytes,
|
||||||
wasmAllocatedBytes: wasm.allocatedBytes,
|
wasmAllocatedBytes: wasm.allocatedBytes,
|
||||||
invalidCode,
|
invalidCode,
|
||||||
cancelled,
|
cancelled,
|
||||||
@@ -342,6 +344,11 @@ test("streams ten-million-triangle SceneIR ranges into a bounded interactive LOD
|
|||||||
chunksAtFirstInteractive: result.completed.viewport?.chunksAtFirstInteractive,
|
chunksAtFirstInteractive: result.completed.viewport?.chunksAtFirstInteractive,
|
||||||
estimatedGpuBytes: result.completed.viewport?.estimatedGpuBytes,
|
estimatedGpuBytes: result.completed.viewport?.estimatedGpuBytes,
|
||||||
wasmAllocatedBytes: result.wasmAllocatedBytes,
|
wasmAllocatedBytes: result.wasmAllocatedBytes,
|
||||||
|
baselineHeapBytes: result.baselineHeapBytes,
|
||||||
|
peakHeapBytes: result.peakHeapBytes,
|
||||||
|
afterHeapBytes: result.afterHeapBytes,
|
||||||
|
cacheDeleted: result.completed.viewport?.cache.deleted,
|
||||||
|
recoveryVisible: result.recoveryVisible,
|
||||||
cancelLatencyMs: result.cancelled.cancelLatencyMs,
|
cancelLatencyMs: result.cancelled.cancelLatencyMs,
|
||||||
elapsedMs: result.elapsedMs,
|
elapsedMs: result.elapsedMs,
|
||||||
}));
|
}));
|
||||||
|
|||||||
@@ -76,7 +76,7 @@ test("indexes, seeks, cancels and reopens a bounded one-million-frame media time
|
|||||||
init(timelineValue: unknown, assets: Array<{ sourceId: string; data: ArrayBuffer }>): Promise<{ stripCount: number; bucketCount: number; referenceCount: number; estimatedBytes: number }>;
|
init(timelineValue: unknown, assets: Array<{ sourceId: string; data: ArrayBuffer }>): Promise<{ stripCount: number; bucketCount: number; referenceCount: number; estimatedBytes: number }>;
|
||||||
seek(frame: number, decodeDelayMs?: number): Promise<import("/src/sequencer/LongMediaTimeline.ts").LongMediaSeekResultIR>;
|
seek(frame: number, decodeDelayMs?: number): Promise<import("/src/sequencer/LongMediaTimeline.ts").LongMediaSeekResultIR>;
|
||||||
cancel(): void;
|
cancel(): void;
|
||||||
dispose(): Promise<number>;
|
dispose(): Promise<{ releasedCacheBytes: number; cacheBytesAfter: number }>;
|
||||||
terminate(): void;
|
terminate(): void;
|
||||||
}
|
}
|
||||||
const createWorkerClient = (): WorkerClient => {
|
const createWorkerClient = (): WorkerClient => {
|
||||||
@@ -84,12 +84,12 @@ test("indexes, seeks, cancels and reopens a bounded one-million-frame media time
|
|||||||
let counter = 0;
|
let counter = 0;
|
||||||
let readyResolve: ((value: { stripCount: number; bucketCount: number; referenceCount: number; estimatedBytes: number }) => void) | undefined;
|
let readyResolve: ((value: { stripCount: number; bucketCount: number; referenceCount: number; estimatedBytes: number }) => void) | undefined;
|
||||||
let readyReject: ((reason: Error) => void) | undefined;
|
let readyReject: ((reason: Error) => void) | undefined;
|
||||||
let disposeResolve: ((bytes: number) => void) | undefined;
|
let disposeResolve: ((status: { releasedCacheBytes: number; cacheBytesAfter: number }) => void) | undefined;
|
||||||
const pending = new Map<string, { resolve: (value: import("/src/sequencer/LongMediaTimeline.ts").LongMediaSeekResultIR) => void; reject: (reason: Error) => void }>();
|
const pending = new Map<string, { resolve: (value: import("/src/sequencer/LongMediaTimeline.ts").LongMediaSeekResultIR) => void; reject: (reason: Error) => void }>();
|
||||||
worker.onmessage = (event: MessageEvent<any>) => {
|
worker.onmessage = (event: MessageEvent<any>) => {
|
||||||
const message = event.data;
|
const message = event.data;
|
||||||
if (message.type === "ready") { readyResolve?.(message.index); readyResolve = undefined; readyReject = undefined; return; }
|
if (message.type === "ready") { readyResolve?.(message.index); readyResolve = undefined; readyReject = undefined; return; }
|
||||||
if (message.type === "disposed") { disposeResolve?.(message.cacheBytes); disposeResolve = undefined; return; }
|
if (message.type === "disposed") { disposeResolve?.({ releasedCacheBytes: message.releasedCacheBytes, cacheBytesAfter: message.cacheBytesAfter }); disposeResolve = undefined; return; }
|
||||||
if (message.type === "error") {
|
if (message.type === "error") {
|
||||||
if (message.requestId) { pending.get(message.requestId)?.reject(new Error(message.message)); pending.delete(message.requestId); }
|
if (message.requestId) { pending.get(message.requestId)?.reject(new Error(message.message)); pending.delete(message.requestId); }
|
||||||
else { readyReject?.(new Error(message.message)); readyResolve = undefined; readyReject = undefined; }
|
else { readyReject?.(new Error(message.message)); readyResolve = undefined; readyReject = undefined; }
|
||||||
@@ -164,7 +164,7 @@ test("indexes, seeks, cancels and reopens a bounded one-million-frame media time
|
|||||||
const serialized = serializeLongMediaSessionManifest(manifest);
|
const serialized = serializeLongMediaSessionManifest(manifest);
|
||||||
const manifestSha256 = Array.from(new Uint8Array(await crypto.subtle.digest("SHA-256", serialized.slice(0))), (value) => value.toString(16).padStart(2, "0")).join("");
|
const manifestSha256 = Array.from(new Uint8Array(await crypto.subtle.digest("SHA-256", serialized.slice(0))), (value) => value.toString(16).padStart(2, "0")).join("");
|
||||||
const storedSession = await writer.putAsset(projectId, serialized, "application/vnd.blender.long-media+json", "cache/long-media-session.json");
|
const storedSession = await writer.putAsset(projectId, serialized, "application/vnd.blender.long-media+json", "cache/long-media-session.json");
|
||||||
const cacheBytesBeforeDispose = await client.dispose();
|
const disposed = await client.dispose();
|
||||||
client.terminate();
|
client.terminate();
|
||||||
writer.terminate();
|
writer.terminate();
|
||||||
|
|
||||||
@@ -180,7 +180,7 @@ test("indexes, seeks, cancels and reopens a bounded one-million-frame media time
|
|||||||
{ sourceId: "media:audio", data: reopenedAudio.data },
|
{ sourceId: "media:audio", data: reopenedAudio.data },
|
||||||
]);
|
]);
|
||||||
const reopenedSeek = await restarted.seek(reopenedManifest.currentFrame);
|
const reopenedSeek = await restarted.seek(reopenedManifest.currentFrame);
|
||||||
const restartedCacheBytes = await restarted.dispose();
|
const restartedDisposed = await restarted.dispose();
|
||||||
restarted.terminate();
|
restarted.terminate();
|
||||||
|
|
||||||
const codec = gateSequencerCodec("video/mp4", new Set(["image/png", "audio/wav"]));
|
const codec = gateSequencerCodec("video/mp4", new Set(["image/png", "audio/wav"]));
|
||||||
@@ -207,8 +207,8 @@ test("indexes, seeks, cancels and reopens a bounded one-million-frame media time
|
|||||||
randomSeekMs,
|
randomSeekMs,
|
||||||
latest: [superseded.status, latest.status, latest.frame],
|
latest: [superseded.status, latest.status, latest.frame],
|
||||||
cancelledSeek: cancelledSeek.status,
|
cancelledSeek: cancelledSeek.status,
|
||||||
cacheBytesBeforeDispose,
|
disposed,
|
||||||
restartedCacheBytes,
|
restartedDisposed,
|
||||||
manifestSha256,
|
manifestSha256,
|
||||||
storedSessionSha256: storedSession.sha256,
|
storedSessionSha256: storedSession.sha256,
|
||||||
reopenedFrame: reopenedSeek.frame,
|
reopenedFrame: reopenedSeek.frame,
|
||||||
@@ -230,6 +230,11 @@ test("indexes, seeks, cancels and reopens a bounded one-million-frame media time
|
|||||||
randomSeekMs: result.randomSeekMs,
|
randomSeekMs: result.randomSeekMs,
|
||||||
cacheBytes: result.randomCache?.bytes,
|
cacheBytes: result.randomCache?.bytes,
|
||||||
evictions: result.randomCache?.evictions,
|
evictions: result.randomCache?.evictions,
|
||||||
|
releasedCacheBytes: result.disposed.releasedCacheBytes,
|
||||||
|
cacheBytesAfterDispose: result.disposed.cacheBytesAfter,
|
||||||
|
restartedReleasedCacheBytes: result.restartedDisposed.releasedCacheBytes,
|
||||||
|
restartedCacheBytesAfterDispose: result.restartedDisposed.cacheBytesAfter,
|
||||||
|
workerRestartRecovered: result.reopenedStatus === "COMPLETED",
|
||||||
manifestSha256: result.manifestSha256,
|
manifestSha256: result.manifestSha256,
|
||||||
elapsedMs: result.elapsedMs,
|
elapsedMs: result.elapsedMs,
|
||||||
}));
|
}));
|
||||||
@@ -264,8 +269,10 @@ test("indexes, seeks, cancels and reopens a bounded one-million-frame media time
|
|||||||
expect(result.randomCache?.evictions).toBeGreaterThan(0);
|
expect(result.randomCache?.evictions).toBeGreaterThan(0);
|
||||||
expect(result.randomCache?.keys).toEqual(expect.arrayContaining(["media:audio:597927", "media:image:1"]));
|
expect(result.randomCache?.keys).toEqual(expect.arrayContaining(["media:audio:597927", "media:image:1"]));
|
||||||
expect(result.randomSeekMs).toBeLessThan(15_000);
|
expect(result.randomSeekMs).toBeLessThan(15_000);
|
||||||
expect(result.cacheBytesBeforeDispose).toBeLessThanOrEqual(20 * 1024);
|
expect(result.disposed.releasedCacheBytes).toBeLessThanOrEqual(20 * 1024);
|
||||||
expect(result.restartedCacheBytes).toBeLessThanOrEqual(20 * 1024);
|
expect(result.disposed.cacheBytesAfter).toBe(0);
|
||||||
|
expect(result.restartedDisposed.releasedCacheBytes).toBeLessThanOrEqual(20 * 1024);
|
||||||
|
expect(result.restartedDisposed.cacheBytesAfter).toBe(0);
|
||||||
expect(result.manifestSha256).toBe(result.storedSessionSha256);
|
expect(result.manifestSha256).toBe(result.storedSessionSha256);
|
||||||
expect(result.manifestSha256).toBe("d2e7e3c5ed9ae4fda6fe358cebb474f5774f1d2037dff3df9b6dfde9e0bebd2d");
|
expect(result.manifestSha256).toBe("d2e7e3c5ed9ae4fda6fe358cebb474f5774f1d2037dff3df9b6dfde9e0bebd2d");
|
||||||
expect(result.elapsedMs).toBeLessThan(30_000);
|
expect(result.elapsedMs).toBeLessThan(30_000);
|
||||||
|
|||||||
@@ -54,6 +54,7 @@ test("meets the Chromium OPFS Simulation cache playback performance gate", async
|
|||||||
const saved = await writer.saveProject(projectId, 1, sourceBlend.slice(0));
|
const saved = await writer.saveProject(projectId, 1, sourceBlend.slice(0));
|
||||||
const stored = await writer.putSimulationCache(projectId, manifest, payload);
|
const stored = await writer.putSimulationCache(projectId, manifest, payload);
|
||||||
writer.terminate();
|
writer.terminate();
|
||||||
|
const writerPendingAfterTerminate = writer.getPendingRequestCount();
|
||||||
const storedAt = performance.now();
|
const storedAt = performance.now();
|
||||||
|
|
||||||
const reader = new StorageClient();
|
const reader = new StorageClient();
|
||||||
@@ -87,6 +88,11 @@ test("meets the Chromium OPFS Simulation cache playback performance gate", async
|
|||||||
const playbackResult = await playback.play();
|
const playbackResult = await playback.play();
|
||||||
const finished = performance.now();
|
const finished = performance.now();
|
||||||
reader.terminate();
|
reader.terminate();
|
||||||
|
const readerPendingAfterTerminate = reader.getPendingRequestCount();
|
||||||
|
const recovery = new StorageClient();
|
||||||
|
const recovered = await recovery.readSimulationCacheFrame(projectId, stored.cacheKey, frameCount);
|
||||||
|
recovery.terminate();
|
||||||
|
const recoveryPendingAfterTerminate = recovery.getPendingRequestCount();
|
||||||
return {
|
return {
|
||||||
backend: saved.backend,
|
backend: saved.backend,
|
||||||
frameCount,
|
frameCount,
|
||||||
@@ -99,12 +105,18 @@ test("meets the Chromium OPFS Simulation cache playback performance gate", async
|
|||||||
storeMs: Math.round(storedAt - started),
|
storeMs: Math.round(storedAt - started),
|
||||||
playbackMs: Math.round(finished - storedAt),
|
playbackMs: Math.round(finished - storedAt),
|
||||||
elapsedMs: Math.round(finished - started),
|
elapsedMs: Math.round(finished - started),
|
||||||
|
peakCacheBytes: manifest.byteLength,
|
||||||
|
pendingRequestsAfterTerminate: writerPendingAfterTerminate + readerPendingAfterTerminate + recoveryPendingAfterTerminate,
|
||||||
|
workerRestartRecovered: recovered.data.byteLength === frameBytes,
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
|
console.log("simulation-cache-performance", JSON.stringify(result));
|
||||||
expect(result.backend).toBe("opfs");
|
expect(result.backend).toBe("opfs");
|
||||||
expect(result).toMatchObject({ frameCount: 600, byteLength: 52_800, status: "COMPLETED", appliedFrames: 600, lastFrame: 600, publishedFrames: 600 });
|
expect(result).toMatchObject({ frameCount: 600, byteLength: 52_800, status: "COMPLETED", appliedFrames: 600, lastFrame: 600, publishedFrames: 600 });
|
||||||
expect(result.lastTranslation).toBeCloseTo(60, 5);
|
expect(result.lastTranslation).toBeCloseTo(60, 5);
|
||||||
expect(result.storeMs).toBeLessThan(30_000);
|
expect(result.storeMs).toBeLessThan(30_000);
|
||||||
expect(result.playbackMs).toBeLessThan(30_000);
|
expect(result.playbackMs).toBeLessThan(30_000);
|
||||||
expect(result.elapsedMs).toBeLessThan(30_000);
|
expect(result.elapsedMs).toBeLessThan(30_000);
|
||||||
|
expect(result.pendingRequestsAfterTerminate).toBe(0);
|
||||||
|
expect(result.workerRestartRecovered).toBe(true);
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -48,7 +48,7 @@ test("opens the Blender-style editor and initializes workers", async ({ page })
|
|||||||
await page.goto("/");
|
await page.goto("/");
|
||||||
appOrigin = new URL(page.url()).origin;
|
appOrigin = new URL(page.url()).origin;
|
||||||
|
|
||||||
await expect(page.getByText("Blender Web", { exact: true }).first()).toBeVisible();
|
await expect(page.getByText("Web Blender Modeler V1", { exact: true }).first()).toBeVisible();
|
||||||
await expect(page.getByRole("button", { name: "Layout" })).toHaveClass(/active/);
|
await expect(page.getByRole("button", { name: "Layout" })).toHaveClass(/active/);
|
||||||
await expect(page.locator(".status-bar")).toContainText("Manifest: verified r1");
|
await expect(page.locator(".status-bar")).toContainText("Manifest: verified r1");
|
||||||
await expect(page.locator(".status-bar")).toContainText("Engine: ready, open a .blend file", { timeout: 20_000 });
|
await expect(page.locator(".status-bar")).toContainText("Engine: ready, open a .blend file", { timeout: 20_000 });
|
||||||
@@ -2291,7 +2291,7 @@ test("undoes and redoes native scene commands without a Three.js shadow state",
|
|||||||
expect(result.redoRevision).toBe(4);
|
expect(result.redoRevision).toBe(4);
|
||||||
});
|
});
|
||||||
|
|
||||||
test("wires Blender Web top-bar undo and redo to native history", async ({ page }) => {
|
test("wires Web Blender Modeler top-bar undo and redo to native history", async ({ page }) => {
|
||||||
await page.goto("/");
|
await page.goto("/");
|
||||||
await page.setInputFiles("[data-testid=blend-file-input]", basicBlend);
|
await page.setInputFiles("[data-testid=blend-file-input]", basicBlend);
|
||||||
await expect(page.getByText("BasicCube", { exact: true })).toBeVisible();
|
await expect(page.getByText("BasicCube", { exact: true })).toBeVisible();
|
||||||
|
|||||||
@@ -23,15 +23,27 @@ test("decodes, uploads and renders a validated 4K texture in Chromium", async ({
|
|||||||
const renderer = new WebGLRenderer({ canvas, preserveDrawingBuffer: true }); renderer.setSize(64, 64, false);
|
const renderer = new WebGLRenderer({ canvas, preserveDrawingBuffer: true }); renderer.setSize(64, 64, false);
|
||||||
const scene = new Scene(); const camera = new OrthographicCamera(-1, 1, 1, -1, 0.1, 10); camera.position.z = 1;
|
const scene = new Scene(); const camera = new OrthographicCamera(-1, 1, 1, -1, 0.1, 10); camera.position.z = 1;
|
||||||
const material = new MeshBasicMaterial({ map: store.get("image:4k", "BASE_COLOR") });
|
const material = new MeshBasicMaterial({ map: store.get("image:4k", "BASE_COLOR") });
|
||||||
scene.add(new Mesh(new PlaneGeometry(2, 2), material)); renderer.render(scene, camera);
|
const geometry = new PlaneGeometry(2, 2);
|
||||||
|
scene.add(new Mesh(geometry, material)); renderer.render(scene, camera);
|
||||||
const gl = renderer.getContext(); const pixels = new Uint8Array(64 * 64 * 4); gl.readPixels(0, 0, 64, 64, gl.RGBA, gl.UNSIGNED_BYTE, pixels);
|
const gl = renderer.getContext(); const pixels = new Uint8Array(64 * 64 * 4); gl.readPixels(0, 0, 64, 64, gl.RGBA, gl.UNSIGNED_BYTE, pixels);
|
||||||
let colored = 0; for (let index = 0; index < pixels.length; index += 4) if (pixels[index] > 60 || pixels[index + 1] > 60) colored += 1;
|
let colored = 0; for (let index = 0; index < pixels.length; index += 4) if (pixels[index] > 60 || pixels[index + 1] > 60) colored += 1;
|
||||||
const elapsedMs = Math.round(performance.now() - started);
|
const elapsedMs = Math.round(performance.now() - started);
|
||||||
material.dispose(); renderer.dispose(); store.dispose(); canvas.remove();
|
material.dispose(); geometry.dispose(); renderer.dispose(); store.dispose(); canvas.remove();
|
||||||
return { status, byteLength: data.byteLength, colored, elapsedMs };
|
const released = store.get("image:4k", "BASE_COLOR") === undefined;
|
||||||
|
const recoveryCanvas = document.createElement("canvas"); recoveryCanvas.width = 16; recoveryCanvas.height = 16; document.body.append(recoveryCanvas);
|
||||||
|
const recoveryRenderer = new WebGLRenderer({ canvas: recoveryCanvas, preserveDrawingBuffer: true }); recoveryRenderer.setSize(16, 16, false);
|
||||||
|
const recoveryScene = new Scene(); const recoveryMaterial = new MeshBasicMaterial({ color: 0x33cc66 }); const recoveryGeometry = new PlaneGeometry(2, 2);
|
||||||
|
recoveryScene.add(new Mesh(recoveryGeometry, recoveryMaterial)); recoveryRenderer.render(recoveryScene, camera);
|
||||||
|
const recoveryPixels = new Uint8Array(16 * 16 * 4); recoveryRenderer.getContext().readPixels(0, 0, 16, 16, recoveryRenderer.getContext().RGBA, recoveryRenderer.getContext().UNSIGNED_BYTE, recoveryPixels);
|
||||||
|
const recoveryVisible = recoveryPixels.some((value, index) => index % 4 !== 3 && value > 40);
|
||||||
|
recoveryMaterial.dispose(); recoveryGeometry.dispose(); recoveryRenderer.dispose(); recoveryCanvas.remove();
|
||||||
|
return { width: 4096, height: 4096, status, byteLength: data.byteLength, estimatedPeakGpuBytes: 4096 * 4096 * 4, released, recoveryVisible, colored, elapsedMs };
|
||||||
});
|
});
|
||||||
|
console.log("texture-4k-performance", JSON.stringify(result));
|
||||||
expect(result.status).toMatchObject({ loaded: 1, rejected: 0, bytes: result.byteLength });
|
expect(result.status).toMatchObject({ loaded: 1, rejected: 0, bytes: result.byteLength });
|
||||||
expect(result.byteLength).toBeGreaterThan(0);
|
expect(result.byteLength).toBeGreaterThan(0);
|
||||||
expect(result.colored).toBeGreaterThan(3_000);
|
expect(result.colored).toBeGreaterThan(3_000);
|
||||||
expect(result.elapsedMs).toBeLessThan(30_000);
|
expect(result.elapsedMs).toBeLessThan(30_000);
|
||||||
|
expect(result.released).toBe(true);
|
||||||
|
expect(result.recoveryVisible).toBe(true);
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -21,16 +21,27 @@ test("decodes, uploads and renders a validated 8K texture in Chromium", async ({
|
|||||||
const asset = await createGPUTextureAsset({ assetId: "asset:8k", imageId: "image:8k", mimeType: "image/png", width: 8192, height: 8192, usage: "BASE_COLOR", colorSpace: "SRGB" }, data);
|
const asset = await createGPUTextureAsset({ assetId: "asset:8k", imageId: "image:8k", mimeType: "image/png", width: 8192, height: 8192, usage: "BASE_COLOR", colorSpace: "SRGB" }, data);
|
||||||
const started = performance.now(); const store = new GPUTextureStore(); const status = await store.upload([asset]);
|
const started = performance.now(); const store = new GPUTextureStore(); const status = await store.upload([asset]);
|
||||||
const scene = new Scene(); const camera = new OrthographicCamera(-1, 1, 1, -1, 0.1, 10); camera.position.z = 1;
|
const scene = new Scene(); const camera = new OrthographicCamera(-1, 1, 1, -1, 0.1, 10); camera.position.z = 1;
|
||||||
const material = new MeshBasicMaterial({ map: store.get("image:8k", "BASE_COLOR") }); scene.add(new Mesh(new PlaneGeometry(2, 2), material)); renderer.render(scene, camera);
|
const material = new MeshBasicMaterial({ map: store.get("image:8k", "BASE_COLOR") }); const geometry = new PlaneGeometry(2, 2); scene.add(new Mesh(geometry, material)); renderer.render(scene, camera);
|
||||||
const pixels = new Uint8Array(64 * 64 * 4); gl.readPixels(0, 0, 64, 64, gl.RGBA, gl.UNSIGNED_BYTE, pixels);
|
const pixels = new Uint8Array(64 * 64 * 4); gl.readPixels(0, 0, 64, 64, gl.RGBA, gl.UNSIGNED_BYTE, pixels);
|
||||||
let colored = 0; for (let index = 0; index < pixels.length; index += 4) if (pixels[index] > 60 || pixels[index + 1] > 60 || pixels[index + 2] > 60) colored += 1;
|
let colored = 0; for (let index = 0; index < pixels.length; index += 4) if (pixels[index] > 60 || pixels[index + 1] > 60 || pixels[index + 2] > 60) colored += 1;
|
||||||
const elapsedMs = Math.round(performance.now() - started);
|
const elapsedMs = Math.round(performance.now() - started);
|
||||||
material.dispose(); renderer.dispose(); store.dispose(); target.remove();
|
material.dispose(); geometry.dispose(); renderer.dispose(); store.dispose(); target.remove();
|
||||||
return { supported: true, maxTextureSize, status, byteLength: data.byteLength, colored, elapsedMs };
|
const released = store.get("image:8k", "BASE_COLOR") === undefined;
|
||||||
|
const recoveryCanvas = document.createElement("canvas"); recoveryCanvas.width = 16; recoveryCanvas.height = 16; document.body.append(recoveryCanvas);
|
||||||
|
const recoveryRenderer = new WebGLRenderer({ canvas: recoveryCanvas, preserveDrawingBuffer: true }); recoveryRenderer.setSize(16, 16, false);
|
||||||
|
const recoveryScene = new Scene(); const recoveryMaterial = new MeshBasicMaterial({ color: 0x3366cc }); const recoveryGeometry = new PlaneGeometry(2, 2);
|
||||||
|
recoveryScene.add(new Mesh(recoveryGeometry, recoveryMaterial)); recoveryRenderer.render(recoveryScene, camera);
|
||||||
|
const recoveryPixels = new Uint8Array(16 * 16 * 4); recoveryRenderer.getContext().readPixels(0, 0, 16, 16, recoveryRenderer.getContext().RGBA, recoveryRenderer.getContext().UNSIGNED_BYTE, recoveryPixels);
|
||||||
|
const recoveryVisible = recoveryPixels.some((value, index) => index % 4 !== 3 && value > 40);
|
||||||
|
recoveryMaterial.dispose(); recoveryGeometry.dispose(); recoveryRenderer.dispose(); recoveryCanvas.remove();
|
||||||
|
return { supported: true, maxTextureSize, width: 8192, height: 8192, status, byteLength: data.byteLength, estimatedPeakGpuBytes: 8192 * 8192 * 4, released, recoveryVisible, colored, elapsedMs };
|
||||||
});
|
});
|
||||||
|
console.log("texture-8k-performance", JSON.stringify(result));
|
||||||
expect(result.supported, `WebGL MAX_TEXTURE_SIZE=${result.maxTextureSize}`).toBe(true);
|
expect(result.supported, `WebGL MAX_TEXTURE_SIZE=${result.maxTextureSize}`).toBe(true);
|
||||||
expect(result.status).toMatchObject({ loaded: 1, rejected: 0, bytes: result.byteLength });
|
expect(result.status).toMatchObject({ loaded: 1, rejected: 0, bytes: result.byteLength });
|
||||||
expect(result.byteLength).toBeGreaterThan(0);
|
expect(result.byteLength).toBeGreaterThan(0);
|
||||||
expect(result.colored).toBeGreaterThan(3_000);
|
expect(result.colored).toBeGreaterThan(3_000);
|
||||||
expect(result.elapsedMs).toBeLessThan(45_000);
|
expect(result.elapsedMs).toBeLessThan(45_000);
|
||||||
|
expect(result.released).toBe(true);
|
||||||
|
expect(result.recoveryVisible).toBe(true);
|
||||||
});
|
});
|
||||||
|
|||||||
41
web/tests/e2e/wasm-threading-capability.spec.ts
Normal file
41
web/tests/e2e/wasm-threading-capability.spec.ts
Normal file
@@ -0,0 +1,41 @@
|
|||||||
|
import { expect, test } from "@playwright/test";
|
||||||
|
|
||||||
|
test("gates only the pthread WASM engine on its three platform requirements", async ({ page }) => {
|
||||||
|
await page.goto("/");
|
||||||
|
const gates = await page.evaluate(async () => {
|
||||||
|
const { gateWasmThreadingCapability } = await import("/src/platform/capabilities.ts");
|
||||||
|
return {
|
||||||
|
ready: gateWasmThreadingCapability({ crossOriginIsolated: true, sharedArrayBuffer: true, worker: true }),
|
||||||
|
noIsolation: gateWasmThreadingCapability({ crossOriginIsolated: false, sharedArrayBuffer: true, worker: true }),
|
||||||
|
noSharedArrayBuffer: gateWasmThreadingCapability({ crossOriginIsolated: true, sharedArrayBuffer: false, worker: true }),
|
||||||
|
noWorker: gateWasmThreadingCapability({ crossOriginIsolated: true, sharedArrayBuffer: true, worker: false }),
|
||||||
|
none: gateWasmThreadingCapability({ crossOriginIsolated: false, sharedArrayBuffer: false, worker: false }),
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(gates.ready).toEqual({
|
||||||
|
taskId: "M6-02",
|
||||||
|
capability: "WASM_PTHREAD_ENGINE",
|
||||||
|
status: "READY",
|
||||||
|
issues: [],
|
||||||
|
});
|
||||||
|
expect(gates.noIsolation.issues.map(({ code, path }) => ({ code, path }))).toEqual([
|
||||||
|
{ code: "PLATFORM_CAPABILITY_UNAVAILABLE", path: "crossOriginIsolated" },
|
||||||
|
]);
|
||||||
|
expect(gates.noSharedArrayBuffer.issues.map(({ code, path }) => ({ code, path }))).toEqual([
|
||||||
|
{ code: "PLATFORM_CAPABILITY_UNAVAILABLE", path: "sharedArrayBuffer" },
|
||||||
|
]);
|
||||||
|
expect(gates.noWorker.issues.map(({ code, path }) => ({ code, path }))).toEqual([
|
||||||
|
{ code: "PLATFORM_CAPABILITY_UNAVAILABLE", path: "worker" },
|
||||||
|
]);
|
||||||
|
expect(gates.none).toMatchObject({
|
||||||
|
taskId: "M6-02",
|
||||||
|
capability: "WASM_PTHREAD_ENGINE",
|
||||||
|
status: "BLOCKED",
|
||||||
|
});
|
||||||
|
expect(gates.none.issues.map(({ code, path }) => ({ code, path }))).toEqual([
|
||||||
|
{ code: "PLATFORM_CAPABILITY_UNAVAILABLE", path: "crossOriginIsolated" },
|
||||||
|
{ code: "PLATFORM_CAPABILITY_UNAVAILABLE", path: "sharedArrayBuffer" },
|
||||||
|
{ code: "PLATFORM_CAPABILITY_UNAVAILABLE", path: "worker" },
|
||||||
|
]);
|
||||||
|
});
|
||||||
44
web/tests/unit/v1-acceptance.test.mjs
Normal file
44
web/tests/unit/v1-acceptance.test.mjs
Normal file
@@ -0,0 +1,44 @@
|
|||||||
|
import assert from "node:assert/strict";
|
||||||
|
import test from "node:test";
|
||||||
|
import { buildAcceptancePlan, readAcceptanceInputs } from "../../../tools/web/v1-acceptance-lib.mjs";
|
||||||
|
|
||||||
|
const inputs = readAcceptanceInputs();
|
||||||
|
|
||||||
|
function clonedInputs() {
|
||||||
|
return {
|
||||||
|
ledger: structuredClone(inputs.ledger),
|
||||||
|
packageManifest: structuredClone(inputs.packageManifest),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
test("V1 acceptance declarations resolve to executable Web commands", () => {
|
||||||
|
const plan = buildAcceptancePlan(inputs.ledger, inputs.packageManifest);
|
||||||
|
assert.deepEqual(
|
||||||
|
{ families: plan.familyCount, declarations: plan.declarationCount, unique: plan.uniqueCount },
|
||||||
|
{ families: 12, declarations: 50, unique: 49 },
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
test("V1 acceptance rejects an unknown namespace", () => {
|
||||||
|
const { ledger, packageManifest } = clonedInputs();
|
||||||
|
ledger.families[0].acceptance[0] = "desktop:test:unknown";
|
||||||
|
assert.throws(() => buildAcceptancePlan(ledger, packageManifest), /unsupported acceptance namespace/);
|
||||||
|
});
|
||||||
|
|
||||||
|
test("V1 acceptance rejects a missing package script", () => {
|
||||||
|
const { ledger, packageManifest } = clonedInputs();
|
||||||
|
delete packageManifest.scripts["test:nonmesh-roundtrip"];
|
||||||
|
assert.throws(() => buildAcceptancePlan(ledger, packageManifest), /package script test:nonmesh-roundtrip is missing/);
|
||||||
|
});
|
||||||
|
|
||||||
|
test("V1 acceptance rejects an empty E2E pattern", () => {
|
||||||
|
const { ledger, packageManifest } = clonedInputs();
|
||||||
|
ledger.families[0].acceptance[0] = "web:e2e: ";
|
||||||
|
assert.throws(() => buildAcceptancePlan(ledger, packageManifest), /E2E pattern must not be empty/);
|
||||||
|
});
|
||||||
|
|
||||||
|
test("V1 acceptance rejects duplicate declarations within one family", () => {
|
||||||
|
const { ledger, packageManifest } = clonedInputs();
|
||||||
|
ledger.families[0].acceptance.push(ledger.families[0].acceptance[0]);
|
||||||
|
assert.throws(() => buildAcceptancePlan(ledger, packageManifest), /duplicate acceptance token/);
|
||||||
|
});
|
||||||
Reference in New Issue
Block a user