diff --git a/.gitattributes b/.gitattributes
new file mode 100644
index 0000000..0cba3b7
--- /dev/null
+++ b/.gitattributes
@@ -0,0 +1,4 @@
+# These generated reports attest exact artifact bytes with SHA-256. Preserve
+# their original line endings on Windows and Linux, including embedded bundles.
+reports/model-editor-closure-20260906/** -text -whitespace
+reports/scene-editor-closure-20260906/** -text -whitespace
diff --git a/model-editor-closure.config.ts b/model-editor-closure.config.ts
new file mode 100644
index 0000000..93aeadb
--- /dev/null
+++ b/model-editor-closure.config.ts
@@ -0,0 +1,19 @@
+import { defineConfig } from '@playwright/test'
+
+export default defineConfig({
+ testDir: './tests', testMatch: 'model-editor-*closure.spec.ts',
+ outputDir: './reports/model-editor-closure-20260906/test-results',
+ workers: 1, fullyParallel: false, retries: 0, timeout: 180_000,
+ expect: { timeout: 25_000 },
+ reporter: [
+ ['list'],
+ ['html', { outputFolder: 'reports/model-editor-closure-20260906/playwright', open: 'never' }],
+ ['json', { outputFile: 'reports/model-editor-closure-20260906/results.json' }],
+ ],
+ use: {
+ baseURL: 'http://127.0.0.1:6180', browserName: 'chromium', channel: 'msedge', headless: true,
+ locale: 'zh-CN', timezoneId: 'Asia/Shanghai', viewport: { width: 1920, height: 1080 },
+ navigationTimeout: 60_000, actionTimeout: 25_000,
+ screenshot: 'only-on-failure', trace: 'off', video: 'off',
+ },
+})
diff --git a/reports/model-editor-closure-20260906/README.md b/reports/model-editor-closure-20260906/README.md
new file mode 100644
index 0000000..1f3228f
--- /dev/null
+++ b/reports/model-editor-closure-20260906/README.md
@@ -0,0 +1,48 @@
+# 装备模型编辑器闭环验收报告
+
+打开 [index.html](index.html)。本报告区分源码实现、单元/合同测试、真实接口、浏览器 UI 与脚本调用运行时,不把截图或套件总通过数替代逐功能验证。
+
+从 `ute2e` 目录生成:
+
+```sh
+node tools/build-model-editor-closure-report.mjs
+```
+
+- `feature-catalog.json`:103 个实际可达功能的源码位置、边界、修复和验收要求。
+- `coverage.json`:生成时逐项关联当前证据的矩阵。
+- `results.json`:最终 Playwright JSON 原始结果。
+- `run-history.json`:首轮失败和定向重跑的安全摘要;只含次数、标题、时间与源文件哈希,保留失败事实,不用定向重跑替换全量结果。
+- `validation.json`:最终构建、单元测试等验证记录;`e2eComplete: true` 标记最终轮已完成。
+- `evidence/*.json`:经过脱敏的真实 API、运行时及界面断言记录。
+- `screenshots/*`:真实截图,报告自动枚举全部 PNG/JPEG/WebP。
+- `screenshot-captions.json`:以文件名或不含后缀文件名为键,值为 `{ "title": "图名", "description": "实际证明的范围" }`。
+- `screenshot-verification.json`:每张图片与当前 `results.json` 正式附件的 SHA256 比对;同名重拍或预跑图不自动计当前轮通过。
+- `cleanup.json`:临时测试数据的清理或保留记录。
+- `artifact-checks.json`:四仓库变动及本轮报告的凭据/令牌、ZIP内元数据和图片文本元数据扫描;`finalArtifactScan: true` 才表示最终文件已扫描,初查不替代最终验收。
+- `data/inventory-main.md`:修复前只读基线及本轮源码实现记录。
+- `data/final-corrections.json`:只读查看交互与连接健康检查补修的解释和边界;`data/kingbase-validation-probe-result.json` 只含脱敏探针结果,不含连接信息或凭据。
+
+证据推荐格式(仅示例,不是实际通过声明):
+
+```json
+{
+ "time": "真实执行时间",
+ "data": {
+ "title": "当前曲线编辑与刷新",
+ "status": "PASS",
+ "kind": "e2e",
+ "testTitle": "与 results.json 完全对应的真实用例名称",
+ "featureIds": ["timeline.curve", "curve.time", "curve.value", "curve.easing"],
+ "details": "记录真正执行和断言的内容,不按同类功能推测覆盖",
+ "screenshots": ["screenshots/实际文件.png"]
+ }
+}
+```
+
+`kind` 应明确使用 `e2e`、`api`、`runtime` 或 `unit`,不要把通过脚本调用编辑器方法说成纯 UI 操作。带 `testTitle` 的证据会与当前 Playwright 结果按完整标题及执行时间交叉核对;对应用例不存在、失败,或证据来自不同时段时,不能标为本轮通过。未关联证据的功能保持待验证。
+
+全量首轮的 17/19 和随后仅两条定向重跑的 2/2 不可相加后改写成“全量19/19通过”。必须另跑完整套件,保留新的完整 `results.json` 以及匹配时段证据;历史503的诊断判断和已证明的因果关系要分开陈述。
+
+总状态只有在结果最终完成、最终验证通过、无失败、全矩阵都有证据且最终交付物扫描通过时才为 PASS;已执行用例通过但仍有未覆盖功能时,会如实显示待验证范围。组件运行系统、完整三维离线包、下游自动执行模型动作属于三项另行确认的范围,不应虚构完成证据。
+
+此目录不得存入账号文件、密码、令牌、cookie、私有 fixtures、原始服务日志或数据库备份。原始 PDF 只相对链接至工作区 `文档`,不复制全文。
diff --git a/reports/model-editor-closure-20260906/artifact-checks.json b/reports/model-editor-closure-20260906/artifact-checks.json
new file mode 100644
index 0000000..3504877
--- /dev/null
+++ b/reports/model-editor-closure-20260906/artifact-checks.json
@@ -0,0 +1,81 @@
+{
+ "time": "2026-09-06T08:34:43.610816+00:00",
+ "status": "PASS",
+ "secretScan": "PASS",
+ "phase": "final",
+ "finalArtifactScan": true,
+ "scope": "四个仓库相对 HEAD 的变动/新增文件与本轮完整报告;真实本地账号密码精确匹配、可解码 JWT、ZIP/Playwright 内嵌报告逐项解压扫描及 PNG 文本元数据。输出不包含任何命中值或附近正文。",
+ "baselinePolicy": "只排除 HEAD 已存在且邻近至少64字符完全一致的普通源码/固定库字符串(统一CRLF/LF);新哈希模型bundle另允许至少25个邻近词元在仅归一化压缩变量名后完全一致,且命中须为更长固定常量的子串、不能是完整密码值。唯一经本轮复核的配置例外是 ut_auth/src/main/resources/application.yml 的 bootstrap-admin-password 环境变量默认项:文件、键、整行须与 HEAD 完全相同,且新旧文件各只出现一次;改值、改变量名、复制到其他文件或新增同一行均仍失败。其他真实凭据赋值与ZIP内元数据不作排除。Playwright静态库对比HEAD上一轮报告壳。",
+ "limitations": [
+ "图片仅扫描可读元数据,不执行OCR;图像正文由浏览器报告视觉QA核对。",
+ "只扫描本轮交付范围,不声称已审计整个历史仓库。",
+ "初查通过不代表最终结果文件已完成,最终需 --final 再执行。"
+ ],
+ "selfChecks": {
+ "status": "PASS",
+ "tests": 15
+ },
+ "repositoryHeads": {
+ "unreal_tran": "d16184acf1686bb4a6ad44949d0587c5373ffbde",
+ "unreal_tran_web": "3f39e2c94df9cdadf3545ae14bf0df41f2c37bf1",
+ "unreal_tran_api": "71df00dffa39a8f49a4f0654969a3014c2f78f67",
+ "ute2e": "b3d4ce8ffd8144090c18c8a28f5ca824170042c8"
+ },
+ "changedFileCounts": {
+ "unreal_tran": 14,
+ "unreal_tran_web": 8,
+ "unreal_tran_api": 15,
+ "ute2e": 184
+ },
+ "deletedFileCounts": {
+ "unreal_tran": 0,
+ "unreal_tran_web": 2,
+ "unreal_tran_api": 0,
+ "ute2e": 0
+ },
+ "textArtifactsScanned": 104,
+ "pngMetadataFilesScanned": 122,
+ "archives": [
+ {
+ "file": "ute2e/reports/model-editor-closure-20260906/playwright/index.html!embedded-report-1.zip",
+ "entries": 7,
+ "textEntries": 7
+ }
+ ],
+ "finalInputs": {
+ "results.json": true,
+ "validation.json": true,
+ "cleanup.json": true,
+ "playwright/index.html": true
+ },
+ "finalInputSha256": {
+ "results.json": "f37013658c8c38a5ec5993e636a30ad57eee9d3bcc119650c929952de8d556e9",
+ "validation.json": "6e0e87d0df73061bc8549d3405f03731fecf077d7425f52eeed45008f7d09959",
+ "cleanup.json": "ab2776afa143d9937651daa8fa18b531a3fb84c703f5f5b0918930e78c76eb26",
+ "playwright/index.html": "92dcca3fe570530bde973b9e09199c064fc2b343bb6f8a21b67fb3c72b8949ed"
+ },
+ "unchangedBaselineExclusions": [
+ {
+ "file": "unreal_tran_api/ut_auth/src/main/resources/application.yml",
+ "baseline": "unreal_tran_api/HEAD:ut_auth/src/main/resources/application.yml",
+ "occurrences": 1
+ },
+ {
+ "file": "unreal_tran_api/wiki/11-内容制作模块.md",
+ "baseline": "unreal_tran_api/HEAD:wiki/11-内容制作模块.md",
+ "occurrences": 17
+ },
+ {
+ "file": "unreal_tran_web/public/legacy-model-editor/assets/editor-BAqfQ3VC.js",
+ "baseline": "unreal_tran_web/HEAD:public/legacy-model-editor/assets/editor-CunHwJSa.js",
+ "occurrences": 1
+ },
+ {
+ "file": "ute2e/reports/model-editor-closure-20260906/playwright/index.html",
+ "baseline": "ute2e/HEAD:reports/role-permissions-20260906/playwright/index.html",
+ "occurrences": 1
+ }
+ ],
+ "unscannedBinaryFiles": [],
+ "findings": []
+}
\ No newline at end of file
diff --git a/reports/model-editor-closure-20260906/cleanup.json b/reports/model-editor-closure-20260906/cleanup.json
new file mode 100644
index 0000000..247c796
--- /dev/null
+++ b/reports/model-editor-closure-20260906/cleanup.json
@@ -0,0 +1,428 @@
+{
+ "time": "2026-09-06T08:27:13.581Z",
+ "status": "PASS",
+ "originals": [
+ {
+ "id": "157",
+ "name": "实测模型-狐狸动画-已验证-20260905",
+ "version": 15,
+ "unchanged": true
+ },
+ {
+ "id": "156",
+ "name": "实测模型-牛奶卡车-已验证-20260905",
+ "version": 20,
+ "unchanged": true
+ },
+ {
+ "id": "94",
+ "name": "桥梁架设装备整机模型",
+ "version": 253,
+ "unchanged": true
+ }
+ ],
+ "projects": [
+ {
+ "id": "168",
+ "alreadyAbsent": true
+ },
+ {
+ "id": "169",
+ "alreadyAbsent": true
+ },
+ {
+ "id": "170",
+ "alreadyAbsent": true
+ },
+ {
+ "id": "171",
+ "alreadyAbsent": true
+ },
+ {
+ "id": "172",
+ "alreadyAbsent": true
+ },
+ {
+ "id": "173",
+ "alreadyAbsent": true
+ },
+ {
+ "id": "174",
+ "alreadyAbsent": true
+ },
+ {
+ "id": "175",
+ "alreadyAbsent": true
+ },
+ {
+ "id": "176",
+ "alreadyAbsent": true
+ },
+ {
+ "id": "177",
+ "alreadyAbsent": true
+ },
+ {
+ "id": "178",
+ "alreadyAbsent": true
+ },
+ {
+ "id": "179",
+ "alreadyAbsent": true
+ },
+ {
+ "id": "180",
+ "alreadyAbsent": true
+ },
+ {
+ "id": "181",
+ "alreadyAbsent": true
+ },
+ {
+ "id": "182",
+ "alreadyAbsent": true
+ },
+ {
+ "id": "183",
+ "name": "闭环导航名称-183",
+ "withdrawn": false,
+ "removed": true
+ },
+ {
+ "id": "184",
+ "name": "闭环验收-资源往返-mtph19ll",
+ "withdrawn": false,
+ "removed": true
+ },
+ {
+ "id": "185",
+ "name": "闭环验收-三维手柄-1788678747289",
+ "withdrawn": false,
+ "removed": true
+ },
+ {
+ "id": "186",
+ "name": "闭环验收-离开保护-1788678780620",
+ "withdrawn": false,
+ "removed": true
+ },
+ {
+ "id": "187",
+ "name": "闭环验收-恢复副本-mtph6hw8",
+ "withdrawn": false,
+ "removed": true
+ },
+ {
+ "id": "188",
+ "name": "闭环验收-加载与预检-mtph6ubj",
+ "withdrawn": false,
+ "removed": true
+ },
+ {
+ "id": "189",
+ "name": "闭环验收-三维手柄-1788678838243",
+ "withdrawn": false,
+ "removed": true
+ },
+ {
+ "id": "190",
+ "name": "闭环验收-离开保护-1788678850196",
+ "withdrawn": false,
+ "removed": true
+ },
+ {
+ "id": "191",
+ "name": "闭环工具补充-卡车控件-mtpha049",
+ "withdrawn": false,
+ "removed": true
+ },
+ {
+ "id": "192",
+ "name": "闭环工具补充-卡车控件-mtphbg8t",
+ "withdrawn": false,
+ "removed": true
+ },
+ {
+ "id": "193",
+ "name": "闭环工具补充-基准匹配-mtphbpqq",
+ "withdrawn": false,
+ "removed": true
+ },
+ {
+ "id": "194",
+ "name": "闭环工具补充-卡车控件-mtphfk1m",
+ "withdrawn": false,
+ "removed": true
+ },
+ {
+ "id": "195",
+ "name": "闭环工具补充-基准匹配-mtphfu32",
+ "withdrawn": false,
+ "removed": true
+ },
+ {
+ "id": "196",
+ "name": "闭环验收-资源往返-mtphjhoi",
+ "withdrawn": false,
+ "removed": true
+ },
+ {
+ "id": "197",
+ "name": "闭环验收-拒绝非法资源-mtphjv5x",
+ "withdrawn": false,
+ "removed": true
+ },
+ {
+ "id": "198",
+ "name": "闭环验收-保存失败恢复-mtphk0kk",
+ "withdrawn": false,
+ "removed": true
+ },
+ {
+ "id": "199",
+ "name": "闭环验收-发布封面版本-mtphk960",
+ "withdrawn": true,
+ "removed": true
+ },
+ {
+ "id": "200",
+ "name": "闭环工具补充-卡车控件-mtphkrbz",
+ "withdrawn": false,
+ "removed": true
+ },
+ {
+ "id": "201",
+ "name": "闭环工具补充-基准匹配-mtphkzlj",
+ "withdrawn": false,
+ "removed": true
+ },
+ {
+ "id": "202",
+ "name": "闭环新建-1788679561949",
+ "withdrawn": false,
+ "removed": true
+ },
+ {
+ "id": "203",
+ "name": "闭环验收-三维手柄-1788679613072",
+ "withdrawn": false,
+ "removed": true
+ },
+ {
+ "id": "204",
+ "name": "闭环验收-离开保护-1788679623529",
+ "withdrawn": false,
+ "removed": true
+ },
+ {
+ "id": "205",
+ "name": "闭环补充-资源库-mtphobxb",
+ "withdrawn": false,
+ "removed": true
+ },
+ {
+ "id": "206",
+ "name": "闭环补充-内置环境-mtphomov",
+ "withdrawn": false,
+ "removed": true
+ },
+ {
+ "id": "207",
+ "name": "闭环导航名称-207",
+ "withdrawn": false,
+ "removed": true
+ },
+ {
+ "id": "208",
+ "name": "闭环验收-恢复副本-mtphpz7v",
+ "withdrawn": false,
+ "removed": true
+ },
+ {
+ "id": "209",
+ "name": "闭环验收-加载与预检-mtphq9y1",
+ "withdrawn": false,
+ "removed": true
+ },
+ {
+ "id": "210",
+ "name": "闭环验收-离开保护-1788681347476",
+ "withdrawn": false,
+ "removed": true
+ },
+ {
+ "id": "211",
+ "name": "闭环补充-内置环境-mtpip60q",
+ "withdrawn": false,
+ "removed": true
+ },
+ {
+ "id": "212",
+ "name": "闭环验收-资源往返-mtpjbnl2",
+ "withdrawn": false,
+ "removed": true
+ },
+ {
+ "id": "213",
+ "name": "闭环验收-拒绝非法资源-mtpjbyqo",
+ "withdrawn": false,
+ "removed": true
+ },
+ {
+ "id": "214",
+ "name": "闭环验收-保存失败恢复-mtpjc2vu",
+ "withdrawn": false,
+ "removed": true
+ },
+ {
+ "id": "215",
+ "name": "闭环验收-发布封面版本-mtpjcafv",
+ "withdrawn": true,
+ "removed": true
+ },
+ {
+ "id": "216",
+ "name": "闭环工具补充-卡车控件-mtpjcof8",
+ "withdrawn": false,
+ "removed": true
+ },
+ {
+ "id": "217",
+ "name": "闭环工具补充-基准匹配-mtpjcvxe",
+ "withdrawn": false,
+ "removed": true
+ },
+ {
+ "id": "218",
+ "name": "闭环新建-1788682528069",
+ "withdrawn": false,
+ "removed": true
+ },
+ {
+ "id": "219",
+ "name": "闭环验收-三维手柄-1788682536810",
+ "withdrawn": false,
+ "removed": true
+ },
+ {
+ "id": "220",
+ "name": "闭环验收-离开保护-1788682545004",
+ "withdrawn": false,
+ "removed": true
+ },
+ {
+ "id": "221",
+ "name": "闭环补充-资源库-mtpjesgj",
+ "withdrawn": false,
+ "removed": true
+ },
+ {
+ "id": "222",
+ "name": "闭环补充-内置环境-mtpjezfi",
+ "withdrawn": false,
+ "removed": true
+ },
+ {
+ "id": "223",
+ "name": "闭环导航名称-223",
+ "withdrawn": false,
+ "removed": true
+ },
+ {
+ "id": "224",
+ "name": "闭环验收-恢复副本-mtpjg6y2",
+ "withdrawn": false,
+ "removed": true
+ },
+ {
+ "id": "225",
+ "name": "闭环验收-加载与预检-mtpjgfnd",
+ "withdrawn": false,
+ "removed": true
+ },
+ {
+ "id": "226",
+ "name": "闭环验收-资源往返-mtpjkcpq",
+ "withdrawn": false,
+ "removed": true
+ },
+ {
+ "id": "227",
+ "name": "闭环验收-拒绝非法资源-mtpjko7g",
+ "withdrawn": false,
+ "removed": true
+ },
+ {
+ "id": "228",
+ "name": "闭环验收-保存失败恢复-mtpjkshn",
+ "withdrawn": false,
+ "removed": true
+ },
+ {
+ "id": "229",
+ "name": "闭环验收-发布封面版本-mtpjkzt1",
+ "withdrawn": true,
+ "removed": true
+ },
+ {
+ "id": "230",
+ "name": "闭环工具补充-卡车控件-mtpjlf69",
+ "withdrawn": false,
+ "removed": true
+ },
+ {
+ "id": "231",
+ "name": "闭环工具补充-基准匹配-mtpjlmiy",
+ "withdrawn": false,
+ "removed": true
+ },
+ {
+ "id": "232",
+ "name": "闭环新建-1788682938712",
+ "withdrawn": false,
+ "removed": true
+ },
+ {
+ "id": "233",
+ "name": "闭环验收-三维手柄-1788682951454",
+ "withdrawn": false,
+ "removed": true
+ },
+ {
+ "id": "234",
+ "name": "闭环验收-离开保护-1788682960426",
+ "withdrawn": false,
+ "removed": true
+ },
+ {
+ "id": "235",
+ "name": "闭环补充-资源库-mtpjnoh9",
+ "withdrawn": false,
+ "removed": true
+ },
+ {
+ "id": "236",
+ "name": "闭环补充-内置环境-mtpjnv0t",
+ "withdrawn": false,
+ "removed": true
+ },
+ {
+ "id": "237",
+ "name": "闭环导航名称-237",
+ "withdrawn": false,
+ "removed": true
+ },
+ {
+ "id": "238",
+ "name": "闭环验收-恢复副本-mtpjp3nr",
+ "withdrawn": false,
+ "removed": true
+ },
+ {
+ "id": "239",
+ "name": "闭环验收-加载与预检-mtpjpdaj",
+ "withdrawn": false,
+ "removed": true
+ }
+ ]
+}
\ No newline at end of file
diff --git a/reports/model-editor-closure-20260906/coverage-plan.md b/reports/model-editor-closure-20260906/coverage-plan.md
new file mode 100644
index 0000000..b392836
--- /dev/null
+++ b/reports/model-editor-closure-20260906/coverage-plan.md
@@ -0,0 +1,55 @@
+# 全功能证据映射与未覆盖规划
+
+本文件是测试设计与源码断言核对,不是执行通过证据。最终结果由 `results.json`、`validation.json`、`evidence/*.json` 与 `coverage.json` 决定。
+
+## 已校准的浏览器脚本
+
+|脚本|实际范围|证据边界|
+|---|---|---|
+|model-editor-closure.spec.ts|属性数值、PBR首槽、扩展标记、保存重开、撤销重做、视图开关/原生片段、新建/权限|已删除不存在的 object.* / viewport.* / permissions.* ID;变换模式按钮不等同真实 gizmo 拖拽,不能代填 view.move/rotate/scale|
+|model-editor-assets-closure.spec.ts|主模型/外部环境真实上传、JSON往返、无效导入、保存失败和重试、发布封面/固定版本/离线步骤HTML|异常通过定向请求中断注入;恢复副本“存在”不等于恢复/隔离/下载全部UI已测;固定版本可取得不等于下游自动运行|
+|model-editor-blueprint-closure.spec.ts|真实轨道/帧/曲线/蓝图库/接线/剪贴板/执行控制、真实点击/双击/auto事件;另有刻度/按键/录帧/分量、基准动作匹配/不匹配、搜索拖入/工具栏添加/画布操作专项|条件真假出口的补充检查标记为 runtime;第4专项才覆盖搜索/拖入和曲线分量路径断言,不把第1例仅切换选择器当成已验|
+|model-editor-navigation-closure.spec.ts|独立原模型157副本;资源搜索/卡片切换、三内置环境保存重开、折叠树、鼠标镜头/聚焦、页签/分割条/Tab、预览±2秒/Esc、日志/统计、名称保存/真实30秒自动保存|三项真实用例已在私有预跑通过,截图41~47;此预跑不替代最终results,最终统一结果仍须核对|
+|model-editor-interaction-closure.spec.ts|真实三维 gizmo 平移/旋转/缩放,撤销重做、保存重开;离开时关闭确认、保存失败保留、重试成功再离开|独立副本;只读投影定位手柄,不脚本改变模型姿态;注入503验证失败分支|
+|model-editor-recovery-closure.spec.ts|同版本真实未存恢复→显式保存、旧版本下载隔离;受控GLB下载失败→UI重试、预检开关无写入和错误资源导入拒绝|恢复稿来自真实beforeunload,不预注入localStorage;较新服务端内容不被旧稿覆盖;服务端发布严格合同另由API测试验证|
+
+首批四份脚本显式计划关联 **88/103** 个功能 ID;随后新增交互/恢复两份脚本补上其中 7 项,时间轴/蓝图专项补 7 项,最终六份脚本显式关联 **102/103** 个功能 ID,多轨计算以明确 unit 证据关联。全部 ID 与 catalog 对齐,未发现未知 ID。这里记录的是“已写断言计划”,不是最终通过数;最终结果以 coverage.json 为准。
+
+后续并行补测分工:时间轴/蓝图专项负责 `timeline.seek/record/baseline`、`curve.axis`、`blueprint.palette/add/canvas`;资产专项负责恢复稿/加载失败/发布预检;宿主专项负责真实 gizmo 拖拽与离开确认。`timeline.multitrack` 以真实计算方法单元测试作独立证据,映射在 [unit-coverage-plan.json](data/unit-coverage-plan.json),该计划文件本身不代表通过。最终生成器按实际证据动态计算,下面保留补测前的差距,便于对照补测来源。
+
+## 补测前的15项差距(追溯记录)
+
+|功能 ID|补测前已有的验证|据此安排的补测范围|
+|---|---|---|
+|project.recovery|本轮 API 指纹回归与真实 beforeunload 方法回归:键顺序相同内容不写假稿;真实变化仍保留;现有 API 恢复合同|用户明确恢复/放弃/隔离/下载的真实对话框;同版本真实修改与旧版本分叉|
+|project.leave|上一轮角色/权限回归及宿主聚焦合同|本轮模型编辑器脏状态取消、放弃、保存失败与上传忙时离开;不能用干净页面切路由代替|
+|resource.loading|启动后readyFrame状态;本轮renderAssets真实DOM空目录/卡片回归|GLB下载或解析失败后中性失败状态、重试成功;不能把未加载空白当成功|
+|view.move|属性XYZ保存重开;多轨中性基值单测|真实TransformControls平移手柄拖动、保存重开;当前只点击W模式|
+|view.rotate|旋转数值输入保存;多轨旋转单测|真实旋转环拖动及撤销重做;当前只点击E模式|
+|view.scale|缩放数值输入保存重开|真实缩放手柄拖动及保存;当前只点击R模式|
+|timeline.seek|时间轴拖帧触发timechange合同;键盘/seek运行时已有回归|真实播放头/刻度点击、左右/Shift按键、时间码与视口同步|
+|timeline.record|主类addKeyframeFromSelection源码已接;中性姿态单测|真实“记录关键帧”按钮捕获当前帧偏移,保存后回放一致|
+|timeline.multitrack|本轮main实际capture/apply方法单测:最后有效轨道、静音/删除/重绑、保存基值|可用unit证据明确覆盖计算合同;需要多轨真实视口可见性截图时另加UI,不能冒称已拍|
+|timeline.baseline|既有装备profile/动作生成合同|有匹配实际载入并保存;无匹配保留数据;当前没有单独确认框,不能写“取消验证通过”|
+|curve.axis|现有UI只选Y再选X;向量其他分量保持的unit回归|修改不同轴数值后切显示轴,断言曲线变化且不改其他数据|
+|blueprint.palette|当前点选创建七类型节点|搜索/清空、HTML5拖入节点,不能用脚本addNode冒充拖放|
+|blueprint.add|当前节点库点选与节点新增unit|工具栏“添加节点”确实新增默认提示节点并进入历史/JSON|
+|blueprint.canvas|现有用例点击“适应画布”;节点自身拖动|蓝图画布滚轮缩放、Space平移/适应,并验证节点图数据未意外变化|
+|publish.preflight|发布API拒绝非法资源/固定版本合同;当前面板打开并真实发布|界面校验清单和服务端阻断对应,打开/关闭不改变工程;不能仅凭绿标判定有效|
+
+## 已有测试中的局部限制
+
+- `property.identity` 新增可见性/阴影真实DOM回归;补充UI目前检查只读标识与流程绑定字段。权限注入的“只读”标签需由宿主最终用例确认。
+- `timeline.drag-key` 已有真实拖动与碰撞合同,Alt取消吸附尚未单独通过浏览器验证。
+- `blueprint.move` 有单节点真实拖动与全选剪贴板;多选框选拖移并未单独截图。
+- `blueprint.highlight` 有真实执行与缺目标/隐藏目标/临时材质单测;呼吸幅度当前不是像素级曲线验收。
+- `publish.cover` 的真实成功上传、版本、封面URI已测;503/409失败保旧图由独立API合同补充,不能称该真实服务自然出现503。
+- `selection.scope` 补充用例增加明确整机根/部件对象断言;仍需避免把仅点击按钮当成三维目标操作。
+
+## 本轮新增阻断回归
+
+1. 资源库启动:真实DOM复现两处单节点选择器误用后修复,覆盖卡片绑定/空目录。
+2. 保存后刷新假恢复稿:真实175快照逐字段无差异,仅environment键顺序不同;指纹统一递归排序对象键,保留数组顺序和真实值变化;beforeunload假稿/真稿均有回归。
+3. 状态标签:真实DOM验证隐藏祖先及castShadow;宿主通过span[data-model-editability]显示会话权限。
+
+以上属于修复和证据分层,不新增 solo,也不把组件运行系统、完整离线三维包、下游自动运行三项待确认范围伪装成已实现。
diff --git a/reports/model-editor-closure-20260906/coverage.json b/reports/model-editor-closure-20260906/coverage.json
new file mode 100644
index 0000000..61d6895
--- /dev/null
+++ b/reports/model-editor-closure-20260906/coverage.json
@@ -0,0 +1,4578 @@
+{
+ "generatedAt": "2026-09-06T08:34:35.367Z",
+ "overall": "PASS",
+ "featureCount": {
+ "total": 103,
+ "pass": 103,
+ "fail": 0,
+ "pending": 0
+ },
+ "browserCount": {
+ "total": 19,
+ "pass": 19,
+ "fail": 0,
+ "pending": 0
+ },
+ "finalRun": true,
+ "finalArtifactsVerified": true,
+ "features": [
+ {
+ "id": "project.new",
+ "group": "工程与数据",
+ "name": "新建工程",
+ "source": {
+ "file": "../../../unreal_tran_web/src/views/content/LegacyModelEditorView.vue",
+ "line": 118,
+ "symbol": "createNewProject"
+ },
+ "implementation": "本轮修复",
+ "boundary": "宿主创建新项目 ID 并跳转;新建不是清空并覆盖当前工程。",
+ "fix": "新建复用正式 create API;创建失败或取消保留原工程。",
+ "acceptance": "新建前后 ID 不同,原工程内容/版本不被覆盖;取消不发创建请求。",
+ "verification": "PASS",
+ "evidence": [
+ {
+ "ids": [
+ "project.new"
+ ],
+ "file": "evidence/new-project.json",
+ "status": "PASS",
+ "testTitle": "新建工程取消及失败保留输入;成功创建新ID且不覆盖当前模型",
+ "recordedAt": "2026-09-06T08:22:24.723Z",
+ "kind": "e2e+api",
+ "title": "新建工程取消及失败保留输入;成功创建新ID且不覆盖当前模型",
+ "screenshots": [],
+ "details": ""
+ }
+ ]
+ },
+ {
+ "id": "project.import-glb",
+ "group": "工程与数据",
+ "name": "导入装备 GLB",
+ "source": {
+ "file": "../../../unreal_tran/src/main.js",
+ "line": 1264,
+ "symbol": "importModelFile"
+ },
+ "implementation": "已有实现",
+ "boundary": "只支持单文件 GLB;上传→登记→解析→最终保存。",
+ "fix": "延续导入 busy/失败阻断及稳定资产校验。",
+ "acceptance": "选择轻量 GLB,真实模型可见;刷新仍加载同一资产。",
+ "verification": "PASS",
+ "evidence": [
+ {
+ "ids": [
+ "project.import-glb",
+ "environment.external",
+ "project.import-json",
+ "project.export-json",
+ "project.save"
+ ],
+ "file": "evidence/30-assets-roundtrip.json",
+ "status": "PASS",
+ "testTitle": "受控模型与外部环境 GLB 上传、JSON 往返和刷新重开",
+ "recordedAt": "2026-09-06T08:20:27.495Z",
+ "kind": "e2e+api",
+ "title": "受控模型与外部环境 GLB 上传、JSON 往返和刷新重开",
+ "screenshots": [],
+ "details": "真实上传、注册、保存、JSON重导入、刷新后的实际网格和原生动画目录;未断言下游场景执行动画。"
+ }
+ ]
+ },
+ {
+ "id": "project.import-json",
+ "group": "工程与数据",
+ "name": "导入工程 JSON",
+ "source": {
+ "file": "../../../unreal_tran/src/main.js",
+ "line": 2863,
+ "symbol": "importProjectFile"
+ },
+ "implementation": "本轮修复",
+ "boundary": "导入到当前工程,不创建副本;受控引用必须属于当前可用资产。JSON 不含 GLB 字节。",
+ "fix": "导入前 materialize 稳定资产引用。",
+ "acceptance": "同项目导出后再导入,材质/动画/环境不丢;无效或跨项目缺失引用报错。",
+ "verification": "PASS",
+ "evidence": [
+ {
+ "ids": [
+ "project.import-glb",
+ "environment.external",
+ "project.import-json",
+ "project.export-json",
+ "project.save"
+ ],
+ "file": "evidence/30-assets-roundtrip.json",
+ "status": "PASS",
+ "testTitle": "受控模型与外部环境 GLB 上传、JSON 往返和刷新重开",
+ "recordedAt": "2026-09-06T08:20:27.495Z",
+ "kind": "e2e+api",
+ "title": "受控模型与外部环境 GLB 上传、JSON 往返和刷新重开",
+ "screenshots": [],
+ "details": "真实上传、注册、保存、JSON重导入、刷新后的实际网格和原生动画目录;未断言下游场景执行动画。"
+ },
+ {
+ "ids": [
+ "project.import-json"
+ ],
+ "file": "evidence/31-invalid-import.json",
+ "status": "PASS",
+ "testTitle": "非法 JSON 与未登记 blob 引用导入失败时保留当前文档",
+ "recordedAt": "2026-09-06T08:20:33.535Z",
+ "kind": "e2e+api",
+ "title": "非法 JSON 与未登记 blob 引用导入失败时保留当前文档",
+ "screenshots": [],
+ "details": ""
+ }
+ ]
+ },
+ {
+ "id": "project.save",
+ "group": "工程与数据",
+ "name": "手动保存 / Ctrl+S",
+ "source": {
+ "file": "../../../unreal_tran/src/main.js",
+ "line": 2748,
+ "symbol": "saveProject"
+ },
+ "implementation": "已有实现",
+ "boundary": "同一工程保存内容和当前视口封面;成功以 API 响应为准。",
+ "fix": "保留队列、版本保护和封面失败回退。",
+ "acceptance": "保存真实 PUT 成功,刷新字段不丢;重复点击不交错。",
+ "verification": "PASS",
+ "evidence": [
+ {
+ "ids": [
+ "project.import-glb",
+ "environment.external",
+ "project.import-json",
+ "project.export-json",
+ "project.save"
+ ],
+ "file": "evidence/30-assets-roundtrip.json",
+ "status": "PASS",
+ "testTitle": "受控模型与外部环境 GLB 上传、JSON 往返和刷新重开",
+ "recordedAt": "2026-09-06T08:20:27.495Z",
+ "kind": "e2e+api",
+ "title": "受控模型与外部环境 GLB 上传、JSON 往返和刷新重开",
+ "screenshots": [],
+ "details": "真实上传、注册、保存、JSON重导入、刷新后的实际网格和原生动画目录;未断言下游场景执行动画。"
+ },
+ {
+ "ids": [
+ "project.conflict",
+ "project.save",
+ "feedback.toast"
+ ],
+ "file": "evidence/32-save-failure-recovery.json",
+ "status": "PASS",
+ "testTitle": "保存请求超时和 409 保留恢复副本,解除故障后可重试",
+ "recordedAt": "2026-09-06T08:20:43.214Z",
+ "kind": "e2e+api",
+ "title": "保存请求超时和 409 保留恢复副本,解除故障后可重试",
+ "screenshots": [],
+ "details": "真实页面保存及真实服务端重试;失败由浏览器定向注入 timedout/409,非宣称自然发生的服务故障。"
+ },
+ {
+ "ids": [
+ "property.name",
+ "property.position",
+ "property.rotation",
+ "property.scale",
+ "property.color",
+ "property.metalness",
+ "property.roughness",
+ "property.opacity",
+ "property.code",
+ "property.status",
+ "component.tag",
+ "project.undo",
+ "project.redo",
+ "project.save"
+ ],
+ "file": "evidence/properties.json",
+ "status": "PASS",
+ "testTitle": "对象名称、变换、PBR、属性与扩展标记保存重开;撤销重做",
+ "recordedAt": "2026-09-06T08:21:58.738Z",
+ "kind": "e2e+api",
+ "title": "对象名称、变换、PBR、属性与扩展标记保存重开;撤销重做",
+ "screenshots": [],
+ "details": "真实属性输入/材质参数/标签、保存与刷新;变换只验数值输入,不代替三维 gizmo 拖动。"
+ }
+ ]
+ },
+ {
+ "id": "project.autosave",
+ "group": "工程与数据",
+ "name": "30 秒自动保存",
+ "source": {
+ "file": "../../../unreal_tran/src/main.js",
+ "line": 2122,
+ "symbol": "bindShellEvents"
+ },
+ "implementation": "已有实现",
+ "boundary": "自动保存工程内容;不反复截图更新封面,恢复稿待确认期间暂停。",
+ "fix": "导入/封面保存期间阻断重入。",
+ "acceptance": "修改后等待自动保存并刷新;忙碌时不提交半成品。",
+ "verification": "PASS",
+ "evidence": [
+ {
+ "ids": [
+ "project.name",
+ "project.autosave",
+ "view.orbit",
+ "view.focus",
+ "view.panels",
+ "view.preview-step",
+ "feedback.log",
+ "feedback.stats",
+ "property.identity",
+ "property.binding",
+ "selection.scope",
+ "component.inspect"
+ ],
+ "file": "evidence/46-navigation-panels-autosave.json",
+ "status": "PASS",
+ "testTitle": "补充视口与工作区:鼠标镜头、聚焦、面板、预览导航、日志与自动保存",
+ "recordedAt": "2026-09-06T08:23:54.656Z",
+ "kind": "e2e+api",
+ "title": "补充视口与工作区:鼠标镜头、聚焦、面板、预览导航、日志与自动保存",
+ "screenshots": [],
+ "details": "真实鼠标镜头操作;聚焦只验证相机目标;真实30秒自动保存,无测试加速定时器;统计内存估值未当作性能结论。"
+ }
+ ]
+ },
+ {
+ "id": "project.name",
+ "group": "工程与数据",
+ "name": "修改工程名",
+ "source": {
+ "file": "../../../unreal_tran/src/main.js",
+ "line": 457,
+ "symbol": "projectNameInput"
+ },
+ "implementation": "已有实现",
+ "boundary": "输入→失焦/Enter→保存;当前模型工程名。",
+ "fix": "无本轮专门改动。",
+ "acceptance": "改名保存后列表和重新打开一致。",
+ "verification": "PASS",
+ "evidence": [
+ {
+ "ids": [
+ "project.name",
+ "project.autosave",
+ "view.orbit",
+ "view.focus",
+ "view.panels",
+ "view.preview-step",
+ "feedback.log",
+ "feedback.stats",
+ "property.identity",
+ "property.binding",
+ "selection.scope",
+ "component.inspect"
+ ],
+ "file": "evidence/46-navigation-panels-autosave.json",
+ "status": "PASS",
+ "testTitle": "补充视口与工作区:鼠标镜头、聚焦、面板、预览导航、日志与自动保存",
+ "recordedAt": "2026-09-06T08:23:54.656Z",
+ "kind": "e2e+api",
+ "title": "补充视口与工作区:鼠标镜头、聚焦、面板、预览导航、日志与自动保存",
+ "screenshots": [],
+ "details": "真实鼠标镜头操作;聚焦只验证相机目标;真实30秒自动保存,无测试加速定时器;统计内存估值未当作性能结论。"
+ }
+ ]
+ },
+ {
+ "id": "project.export-json",
+ "group": "工程与数据",
+ "name": "另存项目 JSON",
+ "source": {
+ "file": "../../../unreal_tran/src/main.js",
+ "line": 2883,
+ "symbol": "saveProjectAs"
+ },
+ "implementation": "已有实现",
+ "boundary": "下载工程 JSON;不是另建服务端项目,也不内嵌 GLB 文件。",
+ "fix": "稳定资产引用保留,临时 blob 地址清除。",
+ "acceptance": "下载文件可解析,包含轨道/蓝图/自定义字段,配套资源可重新解析。",
+ "verification": "PASS",
+ "evidence": [
+ {
+ "ids": [
+ "project.import-glb",
+ "environment.external",
+ "project.import-json",
+ "project.export-json",
+ "project.save"
+ ],
+ "file": "evidence/30-assets-roundtrip.json",
+ "status": "PASS",
+ "testTitle": "受控模型与外部环境 GLB 上传、JSON 往返和刷新重开",
+ "recordedAt": "2026-09-06T08:20:27.495Z",
+ "kind": "e2e+api",
+ "title": "受控模型与外部环境 GLB 上传、JSON 往返和刷新重开",
+ "screenshots": [],
+ "details": "真实上传、注册、保存、JSON重导入、刷新后的实际网格和原生动画目录;未断言下游场景执行动画。"
+ }
+ ]
+ },
+ {
+ "id": "project.undo",
+ "group": "工程与数据",
+ "name": "撤销 / Ctrl+Z",
+ "source": {
+ "file": "../../../unreal_tran/src/main.js",
+ "line": 160,
+ "symbol": "class HistoryStack"
+ },
+ "implementation": "本轮修复",
+ "boundary": "最多 60 个历史快照;不是数据库版本回滚。",
+ "fix": "异步互斥,失败不移动索引,托管 GLB 快照重新解析。",
+ "acceptance": "属性/曲线修改撤销;连续点击不并发;下载失败索引与修改保留。",
+ "verification": "PASS",
+ "evidence": [
+ {
+ "ids": [
+ "property.name",
+ "property.position",
+ "property.rotation",
+ "property.scale",
+ "property.color",
+ "property.metalness",
+ "property.roughness",
+ "property.opacity",
+ "property.code",
+ "property.status",
+ "component.tag",
+ "project.undo",
+ "project.redo",
+ "project.save"
+ ],
+ "file": "evidence/properties.json",
+ "status": "PASS",
+ "testTitle": "对象名称、变换、PBR、属性与扩展标记保存重开;撤销重做",
+ "recordedAt": "2026-09-06T08:21:58.738Z",
+ "kind": "e2e+api",
+ "title": "对象名称、变换、PBR、属性与扩展标记保存重开;撤销重做",
+ "screenshots": [],
+ "details": "真实属性输入/材质参数/标签、保存与刷新;变换只验数值输入,不代替三维 gizmo 拖动。"
+ },
+ {
+ "ids": [
+ "blueprint.parameters",
+ "blueprint.move",
+ "blueprint.connect",
+ "blueprint.delete",
+ "blueprint.copy",
+ "blueprint.run",
+ "blueprint.stop",
+ "blueprint.reset",
+ "blueprint.timeline",
+ "blueprint.wait",
+ "blueprint.highlight",
+ "blueprint.message",
+ "blueprint.finish",
+ "project.undo",
+ "project.redo"
+ ],
+ "file": "evidence/truck-blueprint-ui.json",
+ "status": "PASS",
+ "testTitle": "蓝图真实节点库与端口拖线:七种参数、内部边复制/剪切、撤销重做及保存",
+ "recordedAt": "2026-09-06T08:21:37.144Z",
+ "kind": "e2e+runtime-read",
+ "title": "蓝图真实节点库与端口拖线:七种参数、内部边复制/剪切、撤销重做及保存",
+ "screenshots": [],
+ "details": "节点库点选添加及实际拖动/接线/执行;不把点选当成节点拖入,不把适应画布当成鼠标平移缩放全部已验。"
+ },
+ {
+ "ids": [
+ "project.undo",
+ "project.redo"
+ ],
+ "file": "validation.json",
+ "status": "PASS",
+ "kind": "unit",
+ "title": "history retains 60 states, truncates redo after edits and ignores duplicate snapshots",
+ "screenshots": [],
+ "details": "60 个快照上限、重复状态忽略及新编辑截断 redo 分支。"
+ },
+ {
+ "ids": [
+ "project.undo",
+ "project.redo"
+ ],
+ "file": "validation.json",
+ "status": "PASS",
+ "kind": "unit",
+ "title": "history serializes async restores and leaves index unchanged when restoration fails",
+ "screenshots": [],
+ "details": "异步互斥、下载失败不移动索引,解除锁后可继续操作。"
+ },
+ {
+ "ids": [
+ "project.undo",
+ "project.redo"
+ ],
+ "file": "validation.json",
+ "status": "PASS",
+ "kind": "unit",
+ "title": "managed-GLB history downloads the snapshot stable reference before restoring, without rewriting it",
+ "screenshots": [],
+ "details": "受控 GLB 快照先物化再恢复,不把临时 URL 写回快照。"
+ }
+ ]
+ },
+ {
+ "id": "project.redo",
+ "group": "工程与数据",
+ "name": "重做 / Ctrl+Y",
+ "source": {
+ "file": "../../../unreal_tran/src/main.js",
+ "line": 160,
+ "symbol": "class HistoryStack"
+ },
+ "implementation": "本轮修复",
+ "boundary": "撤销后重做;产生新编辑时清掉旧 redo 分支。",
+ "fix": "与撤销共用异步恢复保护。",
+ "acceptance": "重做恢复相同字段及姿态,不回放无效分支。",
+ "verification": "PASS",
+ "evidence": [
+ {
+ "ids": [
+ "property.name",
+ "property.position",
+ "property.rotation",
+ "property.scale",
+ "property.color",
+ "property.metalness",
+ "property.roughness",
+ "property.opacity",
+ "property.code",
+ "property.status",
+ "component.tag",
+ "project.undo",
+ "project.redo",
+ "project.save"
+ ],
+ "file": "evidence/properties.json",
+ "status": "PASS",
+ "testTitle": "对象名称、变换、PBR、属性与扩展标记保存重开;撤销重做",
+ "recordedAt": "2026-09-06T08:21:58.738Z",
+ "kind": "e2e+api",
+ "title": "对象名称、变换、PBR、属性与扩展标记保存重开;撤销重做",
+ "screenshots": [],
+ "details": "真实属性输入/材质参数/标签、保存与刷新;变换只验数值输入,不代替三维 gizmo 拖动。"
+ },
+ {
+ "ids": [
+ "blueprint.parameters",
+ "blueprint.move",
+ "blueprint.connect",
+ "blueprint.delete",
+ "blueprint.copy",
+ "blueprint.run",
+ "blueprint.stop",
+ "blueprint.reset",
+ "blueprint.timeline",
+ "blueprint.wait",
+ "blueprint.highlight",
+ "blueprint.message",
+ "blueprint.finish",
+ "project.undo",
+ "project.redo"
+ ],
+ "file": "evidence/truck-blueprint-ui.json",
+ "status": "PASS",
+ "testTitle": "蓝图真实节点库与端口拖线:七种参数、内部边复制/剪切、撤销重做及保存",
+ "recordedAt": "2026-09-06T08:21:37.144Z",
+ "kind": "e2e+runtime-read",
+ "title": "蓝图真实节点库与端口拖线:七种参数、内部边复制/剪切、撤销重做及保存",
+ "screenshots": [],
+ "details": "节点库点选添加及实际拖动/接线/执行;不把点选当成节点拖入,不把适应画布当成鼠标平移缩放全部已验。"
+ },
+ {
+ "ids": [
+ "project.undo",
+ "project.redo"
+ ],
+ "file": "validation.json",
+ "status": "PASS",
+ "kind": "unit",
+ "title": "history retains 60 states, truncates redo after edits and ignores duplicate snapshots",
+ "screenshots": [],
+ "details": "60 个快照上限、重复状态忽略及新编辑截断 redo 分支。"
+ },
+ {
+ "ids": [
+ "project.undo",
+ "project.redo"
+ ],
+ "file": "validation.json",
+ "status": "PASS",
+ "kind": "unit",
+ "title": "history serializes async restores and leaves index unchanged when restoration fails",
+ "screenshots": [],
+ "details": "异步互斥、下载失败不移动索引,解除锁后可继续操作。"
+ },
+ {
+ "ids": [
+ "project.undo",
+ "project.redo"
+ ],
+ "file": "validation.json",
+ "status": "PASS",
+ "kind": "unit",
+ "title": "managed-GLB history downloads the snapshot stable reference before restoring, without rewriting it",
+ "screenshots": [],
+ "details": "受控 GLB 快照先物化再恢复,不把临时 URL 写回快照。"
+ }
+ ]
+ },
+ {
+ "id": "project.recovery",
+ "group": "工程与数据",
+ "name": "异常恢复副本",
+ "source": {
+ "file": "../../../unreal_tran/src/main.js",
+ "line": 2554,
+ "symbol": "reconcileApiRecovery"
+ },
+ "implementation": "本轮修复",
+ "boundary": "同版本恢复稿需用户确认;旧版本隔离/下载,不静默覆盖服务器。",
+ "fix": "恢复稿重新解析受控资源;指纹忽略对象键顺序,避免已保存页面刷新误生恢复稿。",
+ "acceptance": "同版本恢复可见;旧版本保留/下载;主动保存前不自动 PUT。",
+ "verification": "PASS",
+ "evidence": [
+ {
+ "ids": [
+ "project.recovery"
+ ],
+ "file": "evidence/60-project-recovery.json",
+ "status": "PASS",
+ "testTitle": "真实未保存稿同版本恢复并显式保存;旧版本恢复稿下载隔离保留",
+ "recordedAt": "2026-09-06T08:24:07.184Z",
+ "kind": "e2e+api",
+ "title": "真实未保存稿同版本恢复并显式保存;旧版本恢复稿下载隔离保留",
+ "screenshots": [],
+ "details": "通过真实beforeunload创建未保存稿;同版恢复后仅显式保存落库;旧版只下载隔离,不自动覆盖服务端。丢弃/跨标签竞态另由既有恢复合同测试覆盖。"
+ },
+ {
+ "ids": [
+ "project.recovery"
+ ],
+ "file": "validation.json",
+ "status": "PASS",
+ "kind": "unit",
+ "title": "beforeunload recovery does not create a draft for key order alone, but preserves real unsaved changes",
+ "screenshots": [],
+ "details": "同语义对象键顺序不产生假恢复稿;真正修改仍保留。不包含全部恢复/隔离/下载 UI 分支。"
+ }
+ ]
+ },
+ {
+ "id": "project.conflict",
+ "group": "工程与数据",
+ "name": "保存/发布版本冲突",
+ "source": {
+ "file": "../../../unreal_tran/src/model-content-api.js",
+ "line": 1025,
+ "symbol": "queueSave"
+ },
+ "implementation": "已有实现",
+ "boundary": "409 阻断;恢复稿保留。",
+ "fix": "发布封面队列同样遵守冲突保护。",
+ "acceptance": "定向模拟 409,无后续发布请求,不误报成功。",
+ "verification": "PASS",
+ "evidence": [
+ {
+ "ids": [
+ "project.conflict",
+ "project.save",
+ "feedback.toast"
+ ],
+ "file": "evidence/32-save-failure-recovery.json",
+ "status": "PASS",
+ "testTitle": "保存请求超时和 409 保留恢复副本,解除故障后可重试",
+ "recordedAt": "2026-09-06T08:20:43.214Z",
+ "kind": "e2e+api",
+ "title": "保存请求超时和 409 保留恢复副本,解除故障后可重试",
+ "screenshots": [],
+ "details": "真实页面保存及真实服务端重试;失败由浏览器定向注入 timedout/409,非宣称自然发生的服务故障。"
+ },
+ {
+ "ids": [
+ "project.conflict",
+ "publish.api",
+ "publish.cover"
+ ],
+ "file": "validation.json",
+ "status": "PASS",
+ "kind": "unit",
+ "title": "API conflict leaves publication failed and releases the cover guard",
+ "screenshots": [],
+ "details": "409 分支不误报已发布并释放 busy 标记;模拟 API。"
+ }
+ ]
+ },
+ {
+ "id": "project.permissions",
+ "group": "工程与数据",
+ "name": "查看/编辑/新增/发布权限",
+ "source": {
+ "file": "../../../unreal_tran_web/src/features/editors/model/legacy-iframe-permissions.ts",
+ "line": 32,
+ "symbol": "installLegacyModelPermissions"
+ },
+ "implementation": "本轮修复",
+ "boundary": "按 content.model.* 权限,不按 teacher/admin 身份推断写权限;只读仍能搜索、选择原生动画和键盘激活预览。",
+ "fix": "新建接真实 create 能力;只发布可发布已保存版本;修复查看控件键盘事件误拦截。",
+ "acceptance": "只读查看操作可用且无内容写请求;不可编辑/保存;publisher 不上传/PUT,正常发布已有版本。",
+ "verification": "PASS",
+ "evidence": [
+ {
+ "ids": [
+ "project.permissions"
+ ],
+ "file": "evidence/permissions.json",
+ "status": "PASS",
+ "testTitle": "管理员只读权限:编辑、新建、导入、保存发布均不可越权",
+ "recordedAt": "2026-09-06T08:22:29.489Z",
+ "kind": "e2e+api",
+ "title": "管理员只读权限:编辑、新建、导入、保存发布均不可越权",
+ "screenshots": [],
+ "details": "管理员只读UI、真实键盘搜索、原生片段切换与Enter播放/暂停、预览无内容写请求及创建403;纯publisher无需update的发布流程由独立权限合同回归补充。"
+ }
+ ]
+ },
+ {
+ "id": "project.leave",
+ "group": "工程与数据",
+ "name": "离开/切角色未保存确认",
+ "source": {
+ "file": "../../../unreal_tran_web/src/views/content/LegacyModelEditorView.vue",
+ "line": 147,
+ "symbol": "confirmLeave"
+ },
+ "implementation": "已有实现",
+ "boundary": "取消留在当前页;保存失败不得离开;上传/保存/发布忙时保留状态。",
+ "fix": "新建也复用现有未保存确认。",
+ "acceptance": "取消、放弃、保存失败与 busy 分别验证,没有误清 dirty。",
+ "verification": "PASS",
+ "evidence": [
+ {
+ "ids": [
+ "environment.builtin",
+ "tree.browse",
+ "tree.expand",
+ "project.leave"
+ ],
+ "file": "evidence/43-builtin-environments-tree.json",
+ "status": "PASS",
+ "testTitle": "补充环境与树:内置三环境保存重开、节点计数与折叠展开",
+ "recordedAt": "2026-09-06T08:23:19.759Z",
+ "kind": "e2e+api",
+ "title": "补充环境与树:内置三环境保存重开、节点计数与折叠展开",
+ "screenshots": [],
+ "details": "真实切换、保存和刷新三种环境;树计数与展开折叠;装备车库保存后返回列表不误报未保存。树220行上限仍属已说明边界。"
+ },
+ {
+ "ids": [
+ "project.leave"
+ ],
+ "file": "evidence/52-leave.json",
+ "status": "PASS",
+ "testTitle": "未保存离开:关闭确认保留工作、保存失败留在原页、重试保存后离开",
+ "recordedAt": "2026-09-06T08:22:48.155Z",
+ "kind": "e2e+api",
+ "title": "未保存离开:关闭确认保留工作、保存失败留在原页、重试保存后离开",
+ "screenshots": [],
+ "details": ""
+ }
+ ]
+ },
+ {
+ "id": "resource.catalog",
+ "group": "资源库与场景树",
+ "name": "资源库浏览 / 选择装备",
+ "source": {
+ "file": "../../../unreal_tran/src/main.js",
+ "line": 1383,
+ "symbol": "renderAssets"
+ },
+ "implementation": "本轮修复",
+ "boundary": "切换装备可能应用其基准时间轴/蓝图;不是把第二个主装备追加进当前模型。",
+ "fix": "修复单节点选择器误用导致启动报错;忙碌期间禁止切换资源。",
+ "acceptance": "完整资源库 DOM 初始化、卡片事件、空目录启动正常;切换实际几何可见,保存后资源一致。",
+ "verification": "PASS",
+ "evidence": [
+ {
+ "ids": [
+ "resource.search",
+ "resource.catalog"
+ ],
+ "file": "evidence/41-resource-search-switch.json",
+ "status": "PASS",
+ "testTitle": "补充资源库:搜索清空、真实卡片切换与刷新保留",
+ "recordedAt": "2026-09-06T08:22:56.960Z",
+ "kind": "e2e+api",
+ "title": "补充资源库:搜索清空、真实卡片切换与刷新保留",
+ "screenshots": [],
+ "details": "真实搜索与资源卡片点击、服务端保存、刷新后原模型网格;副本独立登记待清理。"
+ },
+ {
+ "ids": [
+ "resource.catalog"
+ ],
+ "file": "validation.json",
+ "status": "PASS",
+ "kind": "unit",
+ "title": "resource catalog startup renders all model/environment cards and binds every action with a real DOM",
+ "screenshots": [],
+ "details": "真实 DOM 渲染、卡片绑定、空目录及 busy 保护;不包含 GLB 网络加载。"
+ }
+ ]
+ },
+ {
+ "id": "resource.search",
+ "group": "资源库与场景树",
+ "name": "资源搜索",
+ "source": {
+ "file": "../../../unreal_tran/src/main.js",
+ "line": 1467,
+ "symbol": "applyAssetSearch"
+ },
+ "implementation": "已有实现",
+ "boundary": "按资源名称/关键词过滤现有目录,不搜索服务器全站。",
+ "fix": "无本轮专门改动。",
+ "acceptance": "关键字命中、空结果与清空恢复。",
+ "verification": "PASS",
+ "evidence": [
+ {
+ "ids": [
+ "resource.search",
+ "resource.catalog"
+ ],
+ "file": "evidence/41-resource-search-switch.json",
+ "status": "PASS",
+ "testTitle": "补充资源库:搜索清空、真实卡片切换与刷新保留",
+ "recordedAt": "2026-09-06T08:22:56.960Z",
+ "kind": "e2e+api",
+ "title": "补充资源库:搜索清空、真实卡片切换与刷新保留",
+ "screenshots": [],
+ "details": "真实搜索与资源卡片点击、服务端保存、刷新后原模型网格;副本独立登记待清理。"
+ }
+ ]
+ },
+ {
+ "id": "environment.builtin",
+ "group": "资源库与场景树",
+ "name": "内置车间 / 野外 / 装备车库",
+ "source": {
+ "file": "../../../unreal_tran/src/main.js",
+ "line": 789,
+ "symbol": "resetEnvironmentPlaceholder"
+ },
+ "implementation": "已有实现",
+ "boundary": "车间/野外为程序化环境;车库为随包 GLB。",
+ "fix": "保留真实 GLB 和占位环境来源区别。",
+ "acceptance": "三种切换与重新打开可重现相应环境。",
+ "verification": "PASS",
+ "evidence": [
+ {
+ "ids": [
+ "environment.builtin",
+ "tree.browse",
+ "tree.expand",
+ "project.leave"
+ ],
+ "file": "evidence/43-builtin-environments-tree.json",
+ "status": "PASS",
+ "testTitle": "补充环境与树:内置三环境保存重开、节点计数与折叠展开",
+ "recordedAt": "2026-09-06T08:23:19.759Z",
+ "kind": "e2e+api",
+ "title": "补充环境与树:内置三环境保存重开、节点计数与折叠展开",
+ "screenshots": [],
+ "details": "真实切换、保存和刷新三种环境;树计数与展开折叠;装备车库保存后返回列表不误报未保存。树220行上限仍属已说明边界。"
+ }
+ ]
+ },
+ {
+ "id": "environment.external",
+ "group": "资源库与场景树",
+ "name": "导入外部车间 / 野外 GLB",
+ "source": {
+ "file": "../../../unreal_tran/src/main.js",
+ "line": 1028,
+ "symbol": "loadEnvironmentSceneFile"
+ },
+ "implementation": "本轮修复",
+ "boundary": "一个环境根;受控 MODEL_FILE 资产 metadata.usage=environment,与主装备独立。",
+ "fix": "上传、最终保存、JSON 与刷新恢复稳定引用;失败阻断半提交。",
+ "acceptance": "小 GLB 上传后主装备不被替换;环境刷新仍可见且资产引用一致。",
+ "verification": "PASS",
+ "evidence": [
+ {
+ "ids": [
+ "project.import-glb",
+ "environment.external",
+ "project.import-json",
+ "project.export-json",
+ "project.save"
+ ],
+ "file": "evidence/30-assets-roundtrip.json",
+ "status": "PASS",
+ "testTitle": "受控模型与外部环境 GLB 上传、JSON 往返和刷新重开",
+ "recordedAt": "2026-09-06T08:20:27.495Z",
+ "kind": "e2e+api",
+ "title": "受控模型与外部环境 GLB 上传、JSON 往返和刷新重开",
+ "screenshots": [],
+ "details": "真实上传、注册、保存、JSON重导入、刷新后的实际网格和原生动画目录;未断言下游场景执行动画。"
+ }
+ ]
+ },
+ {
+ "id": "resource.loading",
+ "group": "资源库与场景树",
+ "name": "模型加载进度 / 失败 / 重试",
+ "source": {
+ "file": "../../../unreal_tran_web/src/views/content/LegacyModelEditorView.vue",
+ "line": 33,
+ "symbol": "installLegacyModelIframeLoadingScope"
+ },
+ "implementation": "已有实现",
+ "boundary": "局部加载状态;错误短提示,中性失败状态可重试。",
+ "fix": "复用前轮加载范围与错误消息修复。",
+ "acceptance": "资源失败不无限转圈;重试恢复;无凭据泄露。",
+ "verification": "PASS",
+ "evidence": [
+ {
+ "ids": [
+ "resource.loading",
+ "publish.preflight"
+ ],
+ "file": "evidence/63-loading-publish-preflight.json",
+ "status": "PASS",
+ "testTitle": "受控 GLB 下载失败阻止编辑,重试成功;发布预检开关无写入且拒绝错误资源 JSON",
+ "recordedAt": "2026-09-06T08:24:17.025Z",
+ "kind": "e2e+api",
+ "title": "受控 GLB 下载失败阻止编辑,重试成功;发布预检开关无写入且拒绝错误资源 JSON",
+ "screenshots": [],
+ "details": "真实受控下载定向503→中性失败与禁止编辑→解除故障后UI重试成功;预检面板开关不写项目,错误资源JSON在替换当前模型之前被拒绝。服务端发布时code/URI一致性拒绝由ContentProjectServiceTest合同回归覆盖,未把面板绿标代替服务端校验。"
+ }
+ ]
+ },
+ {
+ "id": "tree.browse",
+ "group": "资源库与场景树",
+ "name": "场景树节点与计数",
+ "source": {
+ "file": "../../../unreal_tran/src/main.js",
+ "line": 1925,
+ "symbol": "buildSceneTree"
+ },
+ "implementation": "现有边界",
+ "boundary": "树行上限 220;统计节点总数不等于当前展开行数。",
+ "fix": "无本轮容量重构。",
+ "acceptance": "模型/环境节点可达,计数说明不当作全部节点已经逐项验收。",
+ "verification": "PASS",
+ "evidence": [
+ {
+ "ids": [
+ "environment.builtin",
+ "tree.browse",
+ "tree.expand",
+ "project.leave"
+ ],
+ "file": "evidence/43-builtin-environments-tree.json",
+ "status": "PASS",
+ "testTitle": "补充环境与树:内置三环境保存重开、节点计数与折叠展开",
+ "recordedAt": "2026-09-06T08:23:19.759Z",
+ "kind": "e2e+api",
+ "title": "补充环境与树:内置三环境保存重开、节点计数与折叠展开",
+ "screenshots": [],
+ "details": "真实切换、保存和刷新三种环境;树计数与展开折叠;装备车库保存后返回列表不误报未保存。树220行上限仍属已说明边界。"
+ }
+ ]
+ },
+ {
+ "id": "tree.expand",
+ "group": "资源库与场景树",
+ "name": "节点展开 / 折叠",
+ "source": {
+ "file": "../../../unreal_tran/src/main.js",
+ "line": 1925,
+ "symbol": "buildSceneTree"
+ },
+ "implementation": "已有实现",
+ "boundary": "当前界面浏览状态;无须持久化展开布局。",
+ "fix": "无本轮专门改动。",
+ "acceptance": "展开和折叠子级不修改模型对象。",
+ "verification": "PASS",
+ "evidence": [
+ {
+ "ids": [
+ "environment.builtin",
+ "tree.browse",
+ "tree.expand",
+ "project.leave"
+ ],
+ "file": "evidence/43-builtin-environments-tree.json",
+ "status": "PASS",
+ "testTitle": "补充环境与树:内置三环境保存重开、节点计数与折叠展开",
+ "recordedAt": "2026-09-06T08:23:19.759Z",
+ "kind": "e2e+api",
+ "title": "补充环境与树:内置三环境保存重开、节点计数与折叠展开",
+ "screenshots": [],
+ "details": "真实切换、保存和刷新三种环境;树计数与展开折叠;装备车库保存后返回列表不误报未保存。树220行上限仍属已说明边界。"
+ }
+ ]
+ },
+ {
+ "id": "tree.search",
+ "group": "资源库与场景树",
+ "name": "场景树搜索",
+ "source": {
+ "file": "../../../unreal_tran/src/main.js",
+ "line": 1925,
+ "symbol": "buildSceneTree"
+ },
+ "implementation": "已有实现",
+ "boundary": "搜索当前载入对象,不新增/删除对象。",
+ "fix": "无本轮专门改动。",
+ "acceptance": "搜索节点后选中正确对象,清空恢复树。",
+ "verification": "PASS",
+ "evidence": [
+ {
+ "ids": [
+ "tree.search",
+ "tree.visibility",
+ "selection.sync",
+ "view.grid",
+ "view.pbr",
+ "view.wireframe",
+ "view.outline",
+ "view.perspective",
+ "view.preview",
+ "view.snapshot",
+ "timeline.native"
+ ],
+ "file": "evidence/viewport.json",
+ "status": "PASS",
+ "testTitle": "场景树搜索、可见性、选择、视口工具、原生动画与截图",
+ "recordedAt": "2026-09-06T08:22:14.169Z",
+ "kind": "e2e",
+ "title": "场景树搜索、可见性、选择、视口工具、原生动画与截图",
+ "screenshots": [],
+ "details": "树搜索、对象隐藏刷新、轮廓及视图开关、原生动画和PNG下载。移动/旋转/缩放只切换模式,真实gizmo拖动仍需补验;聚焦未做几何断言。"
+ }
+ ]
+ },
+ {
+ "id": "tree.visibility",
+ "group": "资源库与场景树",
+ "name": "显示 / 隐藏对象",
+ "source": {
+ "file": "../../../unreal_tran/src/main.js",
+ "line": 1925,
+ "symbol": "buildSceneTree"
+ },
+ "implementation": "本轮修复",
+ "boundary": "对象 visible 保存;隐藏祖先的子对象不接受射线拾取。",
+ "fix": "拾取与高亮统一检查祖先可见性。",
+ "acceptance": "隐藏父级后不能点到子级,保存重开仍隐藏。",
+ "verification": "PASS",
+ "evidence": [
+ {
+ "ids": [
+ "tree.search",
+ "tree.visibility",
+ "selection.sync",
+ "view.grid",
+ "view.pbr",
+ "view.wireframe",
+ "view.outline",
+ "view.perspective",
+ "view.preview",
+ "view.snapshot",
+ "timeline.native"
+ ],
+ "file": "evidence/viewport.json",
+ "status": "PASS",
+ "testTitle": "场景树搜索、可见性、选择、视口工具、原生动画与截图",
+ "recordedAt": "2026-09-06T08:22:14.169Z",
+ "kind": "e2e",
+ "title": "场景树搜索、可见性、选择、视口工具、原生动画与截图",
+ "screenshots": [],
+ "details": "树搜索、对象隐藏刷新、轮廓及视图开关、原生动画和PNG下载。移动/旋转/缩放只切换模式,真实gizmo拖动仍需补验;聚焦未做几何断言。"
+ },
+ {
+ "ids": [
+ "tree.visibility",
+ "blueprint.highlight"
+ ],
+ "file": "validation.json",
+ "status": "PASS",
+ "kind": "unit",
+ "title": "visible child under a hidden ancestor cannot be picked or highlighted; trigger aliases include ancestors",
+ "screenshots": [],
+ "details": "共用可见性和祖先别名函数;鼠标事件另由 E2E 验证。"
+ }
+ ]
+ },
+ {
+ "id": "selection.scope",
+ "group": "资源库与场景树",
+ "name": "整体 / 部件级选择",
+ "source": {
+ "file": "../../../unreal_tran/src/main.js",
+ "line": 1938,
+ "symbol": "setSelectionScope"
+ },
+ "implementation": "已有实现",
+ "boundary": "整体作用主装备根;部件作用实际节点。",
+ "fix": "保留当前选择层级合同。",
+ "acceptance": "点击可见 mesh,整体/部件切换选择目标不同。",
+ "verification": "PASS",
+ "evidence": [
+ {
+ "ids": [
+ "project.name",
+ "project.autosave",
+ "view.orbit",
+ "view.focus",
+ "view.panels",
+ "view.preview-step",
+ "feedback.log",
+ "feedback.stats",
+ "property.identity",
+ "property.binding",
+ "selection.scope",
+ "component.inspect"
+ ],
+ "file": "evidence/46-navigation-panels-autosave.json",
+ "status": "PASS",
+ "testTitle": "补充视口与工作区:鼠标镜头、聚焦、面板、预览导航、日志与自动保存",
+ "recordedAt": "2026-09-06T08:23:54.656Z",
+ "kind": "e2e+api",
+ "title": "补充视口与工作区:鼠标镜头、聚焦、面板、预览导航、日志与自动保存",
+ "screenshots": [],
+ "details": "真实鼠标镜头操作;聚焦只验证相机目标;真实30秒自动保存,无测试加速定时器;统计内存估值未当作性能结论。"
+ }
+ ]
+ },
+ {
+ "id": "selection.sync",
+ "group": "资源库与场景树",
+ "name": "树 / 视口 / 属性选择联动",
+ "source": {
+ "file": "../../../unreal_tran/src/main.js",
+ "line": 1947,
+ "symbol": "selectObject"
+ },
+ "implementation": "本轮修复",
+ "boundary": "显示实际对象并更新辅助框;环境也是可编辑对象。",
+ "fix": "关闭轮廓后重选不恢复辅助框。",
+ "acceptance": "三处名称/对象 ID 一致,重选后轮廓状态保持。",
+ "verification": "PASS",
+ "evidence": [
+ {
+ "ids": [
+ "tree.search",
+ "tree.visibility",
+ "selection.sync",
+ "view.grid",
+ "view.pbr",
+ "view.wireframe",
+ "view.outline",
+ "view.perspective",
+ "view.preview",
+ "view.snapshot",
+ "timeline.native"
+ ],
+ "file": "evidence/viewport.json",
+ "status": "PASS",
+ "testTitle": "场景树搜索、可见性、选择、视口工具、原生动画与截图",
+ "recordedAt": "2026-09-06T08:22:14.169Z",
+ "kind": "e2e",
+ "title": "场景树搜索、可见性、选择、视口工具、原生动画与截图",
+ "screenshots": [],
+ "details": "树搜索、对象隐藏刷新、轮廓及视图开关、原生动画和PNG下载。移动/旋转/缩放只切换模式,真实gizmo拖动仍需补验;聚焦未做几何断言。"
+ }
+ ]
+ },
+ {
+ "id": "view.orbit",
+ "group": "三维视图与操作",
+ "name": "鼠标旋转 / 平移 / 缩放视角",
+ "source": {
+ "file": "../../../unreal_tran/src/main.js",
+ "line": 2,
+ "symbol": "OrbitControls"
+ },
+ "implementation": "已有实现",
+ "boundary": "当前三维相机;不是模型变换。",
+ "fix": "无本轮专门改动。",
+ "acceptance": "鼠标操作改变镜头但不改变对象局部坐标。",
+ "verification": "PASS",
+ "evidence": [
+ {
+ "ids": [
+ "project.name",
+ "project.autosave",
+ "view.orbit",
+ "view.focus",
+ "view.panels",
+ "view.preview-step",
+ "feedback.log",
+ "feedback.stats",
+ "property.identity",
+ "property.binding",
+ "selection.scope",
+ "component.inspect"
+ ],
+ "file": "evidence/46-navigation-panels-autosave.json",
+ "status": "PASS",
+ "testTitle": "补充视口与工作区:鼠标镜头、聚焦、面板、预览导航、日志与自动保存",
+ "recordedAt": "2026-09-06T08:23:54.656Z",
+ "kind": "e2e+api",
+ "title": "补充视口与工作区:鼠标镜头、聚焦、面板、预览导航、日志与自动保存",
+ "screenshots": [],
+ "details": "真实鼠标镜头操作;聚焦只验证相机目标;真实30秒自动保存,无测试加速定时器;统计内存估值未当作性能结论。"
+ }
+ ]
+ },
+ {
+ "id": "view.focus",
+ "group": "三维视图与操作",
+ "name": "聚焦选中 / F",
+ "source": {
+ "file": "../../../unreal_tran/src/main.js",
+ "line": 2272,
+ "symbol": "fitCamera"
+ },
+ "implementation": "已有实现",
+ "boundary": "按当前对象包围盒定位相机。",
+ "fix": "无本轮专门改动。",
+ "acceptance": "小模型/部件聚焦后仍可见,不被裁剪。",
+ "verification": "PASS",
+ "evidence": [
+ {
+ "ids": [
+ "project.name",
+ "project.autosave",
+ "view.orbit",
+ "view.focus",
+ "view.panels",
+ "view.preview-step",
+ "feedback.log",
+ "feedback.stats",
+ "property.identity",
+ "property.binding",
+ "selection.scope",
+ "component.inspect"
+ ],
+ "file": "evidence/46-navigation-panels-autosave.json",
+ "status": "PASS",
+ "testTitle": "补充视口与工作区:鼠标镜头、聚焦、面板、预览导航、日志与自动保存",
+ "recordedAt": "2026-09-06T08:23:54.656Z",
+ "kind": "e2e+api",
+ "title": "补充视口与工作区:鼠标镜头、聚焦、面板、预览导航、日志与自动保存",
+ "screenshots": [],
+ "details": "真实鼠标镜头操作;聚焦只验证相机目标;真实30秒自动保存,无测试加速定时器;统计内存估值未当作性能结论。"
+ }
+ ]
+ },
+ {
+ "id": "view.move",
+ "group": "三维视图与操作",
+ "name": "移动工具 / W",
+ "source": {
+ "file": "../../../unreal_tran/src/main.js",
+ "line": 2234,
+ "symbol": "setTransformMode"
+ },
+ "implementation": "本轮修复",
+ "boundary": "拖拽编辑对象局部位置,保存后重开一致。",
+ "fix": "动画姿态下更新基值只扣实际偏移。",
+ "acceptance": "移动后保存、刷新及动画再播放不漂移。",
+ "verification": "PASS",
+ "evidence": [
+ {
+ "ids": [
+ "view.move",
+ "view.rotate",
+ "view.scale"
+ ],
+ "file": "evidence/51-gizmos.json",
+ "status": "PASS",
+ "testTitle": "真实三维移动、旋转、缩放手柄拖动及撤销重做保存重开",
+ "recordedAt": "2026-09-06T08:22:38.815Z",
+ "kind": "e2e+runtime-readback",
+ "title": "真实三维移动、旋转、缩放手柄拖动及撤销重做保存重开",
+ "screenshots": [],
+ "details": ""
+ }
+ ]
+ },
+ {
+ "id": "view.rotate",
+ "group": "三维视图与操作",
+ "name": "旋转工具 / E",
+ "source": {
+ "file": "../../../unreal_tran/src/main.js",
+ "line": 2234,
+ "symbol": "setTransformMode"
+ },
+ "implementation": "本轮修复",
+ "boundary": "对象旋转;与当前时间轴旋转叠加需要一致。",
+ "fix": "统一动画基值处理。",
+ "acceptance": "旋转后保存/撤销/重做姿态一致。",
+ "verification": "PASS",
+ "evidence": [
+ {
+ "ids": [
+ "view.move",
+ "view.rotate",
+ "view.scale"
+ ],
+ "file": "evidence/51-gizmos.json",
+ "status": "PASS",
+ "testTitle": "真实三维移动、旋转、缩放手柄拖动及撤销重做保存重开",
+ "recordedAt": "2026-09-06T08:22:38.815Z",
+ "kind": "e2e+runtime-readback",
+ "title": "真实三维移动、旋转、缩放手柄拖动及撤销重做保存重开",
+ "screenshots": [],
+ "details": ""
+ }
+ ]
+ },
+ {
+ "id": "view.scale",
+ "group": "三维视图与操作",
+ "name": "缩放工具 / R",
+ "source": {
+ "file": "../../../unreal_tran/src/main.js",
+ "line": 2234,
+ "symbol": "setTransformMode"
+ },
+ "implementation": "已有实现",
+ "boundary": "对象缩放持久化;时间轴没有缩放轨道 UI。",
+ "fix": "无新增缩放动画能力。",
+ "acceptance": "对象缩放保存和重开正确。",
+ "verification": "PASS",
+ "evidence": [
+ {
+ "ids": [
+ "view.move",
+ "view.rotate",
+ "view.scale"
+ ],
+ "file": "evidence/51-gizmos.json",
+ "status": "PASS",
+ "testTitle": "真实三维移动、旋转、缩放手柄拖动及撤销重做保存重开",
+ "recordedAt": "2026-09-06T08:22:38.815Z",
+ "kind": "e2e+runtime-readback",
+ "title": "真实三维移动、旋转、缩放手柄拖动及撤销重做保存重开",
+ "screenshots": [],
+ "details": ""
+ }
+ ]
+ },
+ {
+ "id": "view.grid",
+ "group": "三维视图与操作",
+ "name": "网格开关",
+ "source": {
+ "file": "../../../unreal_tran/src/main.js",
+ "line": 2283,
+ "symbol": "toggleGrid"
+ },
+ "implementation": "已有实现",
+ "boundary": "临时视图选项,不要求写入工程。",
+ "fix": "非持久化不计保存缺陷。",
+ "acceptance": "开关只影响辅助网格,不改变模型。",
+ "verification": "PASS",
+ "evidence": [
+ {
+ "ids": [
+ "tree.search",
+ "tree.visibility",
+ "selection.sync",
+ "view.grid",
+ "view.pbr",
+ "view.wireframe",
+ "view.outline",
+ "view.perspective",
+ "view.preview",
+ "view.snapshot",
+ "timeline.native"
+ ],
+ "file": "evidence/viewport.json",
+ "status": "PASS",
+ "testTitle": "场景树搜索、可见性、选择、视口工具、原生动画与截图",
+ "recordedAt": "2026-09-06T08:22:14.169Z",
+ "kind": "e2e",
+ "title": "场景树搜索、可见性、选择、视口工具、原生动画与截图",
+ "screenshots": [],
+ "details": "树搜索、对象隐藏刷新、轮廓及视图开关、原生动画和PNG下载。移动/旋转/缩放只切换模式,真实gizmo拖动仍需补验;聚焦未做几何断言。"
+ }
+ ]
+ },
+ {
+ "id": "view.pbr",
+ "group": "三维视图与操作",
+ "name": "PBR 光照开关",
+ "source": {
+ "file": "../../../unreal_tran/src/main.js",
+ "line": 2236,
+ "symbol": "handleView"
+ },
+ "implementation": "已有实现",
+ "boundary": "当前照明预览选项;不是批量材质编辑。",
+ "fix": "非持久化不计保存缺陷。",
+ "acceptance": "开关改变显示,物体材质字段不被覆盖。",
+ "verification": "PASS",
+ "evidence": [
+ {
+ "ids": [
+ "tree.search",
+ "tree.visibility",
+ "selection.sync",
+ "view.grid",
+ "view.pbr",
+ "view.wireframe",
+ "view.outline",
+ "view.perspective",
+ "view.preview",
+ "view.snapshot",
+ "timeline.native"
+ ],
+ "file": "evidence/viewport.json",
+ "status": "PASS",
+ "testTitle": "场景树搜索、可见性、选择、视口工具、原生动画与截图",
+ "recordedAt": "2026-09-06T08:22:14.169Z",
+ "kind": "e2e",
+ "title": "场景树搜索、可见性、选择、视口工具、原生动画与截图",
+ "screenshots": [],
+ "details": "树搜索、对象隐藏刷新、轮廓及视图开关、原生动画和PNG下载。移动/旋转/缩放只切换模式,真实gizmo拖动仍需补验;聚焦未做几何断言。"
+ }
+ ]
+ },
+ {
+ "id": "view.wireframe",
+ "group": "三维视图与操作",
+ "name": "线框开关",
+ "source": {
+ "file": "../../../unreal_tran/src/main.js",
+ "line": 2236,
+ "symbol": "handleView"
+ },
+ "implementation": "已有实现",
+ "boundary": "当前视图选项;不承诺保存到工程。",
+ "fix": "非持久化不计保存缺陷。",
+ "acceptance": "线框开关恢复正常着色。",
+ "verification": "PASS",
+ "evidence": [
+ {
+ "ids": [
+ "tree.search",
+ "tree.visibility",
+ "selection.sync",
+ "view.grid",
+ "view.pbr",
+ "view.wireframe",
+ "view.outline",
+ "view.perspective",
+ "view.preview",
+ "view.snapshot",
+ "timeline.native"
+ ],
+ "file": "evidence/viewport.json",
+ "status": "PASS",
+ "testTitle": "场景树搜索、可见性、选择、视口工具、原生动画与截图",
+ "recordedAt": "2026-09-06T08:22:14.169Z",
+ "kind": "e2e",
+ "title": "场景树搜索、可见性、选择、视口工具、原生动画与截图",
+ "screenshots": [],
+ "details": "树搜索、对象隐藏刷新、轮廓及视图开关、原生动画和PNG下载。移动/旋转/缩放只切换模式,真实gizmo拖动仍需补验;聚焦未做几何断言。"
+ }
+ ]
+ },
+ {
+ "id": "view.outline",
+ "group": "三维视图与操作",
+ "name": "选中轮廓开关",
+ "source": {
+ "file": "../../../unreal_tran/src/main.js",
+ "line": 2236,
+ "symbol": "handleView"
+ },
+ "implementation": "本轮修复",
+ "boundary": "当前会话偏好,进出预览后保持;不要求跨重载存储。",
+ "fix": "重选时不重新泄露轮廓。",
+ "acceptance": "关闭→重选→预览→退出,轮廓仍关闭。",
+ "verification": "PASS",
+ "evidence": [
+ {
+ "ids": [
+ "tree.search",
+ "tree.visibility",
+ "selection.sync",
+ "view.grid",
+ "view.pbr",
+ "view.wireframe",
+ "view.outline",
+ "view.perspective",
+ "view.preview",
+ "view.snapshot",
+ "timeline.native"
+ ],
+ "file": "evidence/viewport.json",
+ "status": "PASS",
+ "testTitle": "场景树搜索、可见性、选择、视口工具、原生动画与截图",
+ "recordedAt": "2026-09-06T08:22:14.169Z",
+ "kind": "e2e",
+ "title": "场景树搜索、可见性、选择、视口工具、原生动画与截图",
+ "screenshots": [],
+ "details": "树搜索、对象隐藏刷新、轮廓及视图开关、原生动画和PNG下载。移动/旋转/缩放只切换模式,真实gizmo拖动仍需补验;聚焦未做几何断言。"
+ }
+ ]
+ },
+ {
+ "id": "view.perspective",
+ "group": "三维视图与操作",
+ "name": "透视视角按钮",
+ "source": {
+ "file": "../../../unreal_tran/src/main.js",
+ "line": 2236,
+ "symbol": "handleView"
+ },
+ "implementation": "现有边界",
+ "boundary": "重置透视观察角度;当前没有正交视角菜单。",
+ "fix": "不把临时视图状态当作保存缺陷。",
+ "acceptance": "点击后相机和聚焦目标正常。",
+ "verification": "PASS",
+ "evidence": [
+ {
+ "ids": [
+ "tree.search",
+ "tree.visibility",
+ "selection.sync",
+ "view.grid",
+ "view.pbr",
+ "view.wireframe",
+ "view.outline",
+ "view.perspective",
+ "view.preview",
+ "view.snapshot",
+ "timeline.native"
+ ],
+ "file": "evidence/viewport.json",
+ "status": "PASS",
+ "testTitle": "场景树搜索、可见性、选择、视口工具、原生动画与截图",
+ "recordedAt": "2026-09-06T08:22:14.169Z",
+ "kind": "e2e",
+ "title": "场景树搜索、可见性、选择、视口工具、原生动画与截图",
+ "screenshots": [],
+ "details": "树搜索、对象隐藏刷新、轮廓及视图开关、原生动画和PNG下载。移动/旋转/缩放只切换模式,真实gizmo拖动仍需补验;聚焦未做几何断言。"
+ }
+ ]
+ },
+ {
+ "id": "view.snapshot",
+ "group": "三维视图与操作",
+ "name": "下载视口截图",
+ "source": {
+ "file": "../../../unreal_tran/src/main.js",
+ "line": 3092,
+ "symbol": "downloadSnapshot"
+ },
+ "implementation": "已有实现",
+ "boundary": "当前 canvas PNG,可包含编辑辅助元素;保存封面是独立截图流程。",
+ "fix": "无本轮专门改动。",
+ "acceptance": "PNG 下载可打开,内容与当前视口对应。",
+ "verification": "PASS",
+ "evidence": [
+ {
+ "ids": [
+ "tree.search",
+ "tree.visibility",
+ "selection.sync",
+ "view.grid",
+ "view.pbr",
+ "view.wireframe",
+ "view.outline",
+ "view.perspective",
+ "view.preview",
+ "view.snapshot",
+ "timeline.native"
+ ],
+ "file": "evidence/viewport.json",
+ "status": "PASS",
+ "testTitle": "场景树搜索、可见性、选择、视口工具、原生动画与截图",
+ "recordedAt": "2026-09-06T08:22:14.169Z",
+ "kind": "e2e",
+ "title": "场景树搜索、可见性、选择、视口工具、原生动画与截图",
+ "screenshots": [],
+ "details": "树搜索、对象隐藏刷新、轮廓及视图开关、原生动画和PNG下载。移动/旋转/缩放只切换模式,真实gizmo拖动仍需补验;聚焦未做几何断言。"
+ }
+ ]
+ },
+ {
+ "id": "view.panels",
+ "group": "三维视图与操作",
+ "name": "底部页签 / Tab / 面板高度",
+ "source": {
+ "file": "../../../unreal_tran/src/main.js",
+ "line": 2215,
+ "symbol": "switchBottomMode"
+ },
+ "implementation": "已有实现",
+ "boundary": "时间轴、蓝图、曲线、日志切换;拖分割条只调整当前工作区。",
+ "fix": "曲线入口接真实数据。",
+ "acceptance": "各页签显示正确内容,调整高度后画布正确 resize。",
+ "verification": "PASS",
+ "evidence": [
+ {
+ "ids": [
+ "project.name",
+ "project.autosave",
+ "view.orbit",
+ "view.focus",
+ "view.panels",
+ "view.preview-step",
+ "feedback.log",
+ "feedback.stats",
+ "property.identity",
+ "property.binding",
+ "selection.scope",
+ "component.inspect"
+ ],
+ "file": "evidence/46-navigation-panels-autosave.json",
+ "status": "PASS",
+ "testTitle": "补充视口与工作区:鼠标镜头、聚焦、面板、预览导航、日志与自动保存",
+ "recordedAt": "2026-09-06T08:23:54.656Z",
+ "kind": "e2e+api",
+ "title": "补充视口与工作区:鼠标镜头、聚焦、面板、预览导航、日志与自动保存",
+ "screenshots": [],
+ "details": "真实鼠标镜头操作;聚焦只验证相机目标;真实30秒自动保存,无测试加速定时器;统计内存估值未当作性能结论。"
+ }
+ ]
+ },
+ {
+ "id": "view.preview",
+ "group": "三维视图与操作",
+ "name": "进入 / 退出运行预览",
+ "source": {
+ "file": "../../../unreal_tran/src/main.js",
+ "line": 2284,
+ "symbol": "togglePreview"
+ },
+ "implementation": "本轮修复",
+ "boundary": "真实当前三维场景;隐藏辅助工具,退出不丢编辑状态。",
+ "fix": "进入显式派发 auto;退出停止运行并恢复轮廓策略。",
+ "acceptance": "进入/退出与 Esc,三维仍可见,无辅助框泄漏。",
+ "verification": "PASS",
+ "evidence": [
+ {
+ "ids": [
+ "blueprint.click",
+ "blueprint.double-click",
+ "blueprint.auto",
+ "view.preview"
+ ],
+ "file": "evidence/truck-blueprint-events.json",
+ "status": "PASS",
+ "testTitle": "已保存蓝图:条件出口与目标匹配 runtime 检查,真实模型单击/双击/进入预览自动触发",
+ "recordedAt": "2026-09-06T08:21:50.227Z",
+ "kind": "e2e",
+ "title": "已保存蓝图:条件出口与目标匹配 runtime 检查,真实模型单击/双击/进入预览自动触发",
+ "screenshots": [],
+ "details": "执行使用正式模型编辑器的真实模型/时间轴/高亮/提示 handler;不等同于学员端任务下发。"
+ },
+ {
+ "ids": [
+ "tree.search",
+ "tree.visibility",
+ "selection.sync",
+ "view.grid",
+ "view.pbr",
+ "view.wireframe",
+ "view.outline",
+ "view.perspective",
+ "view.preview",
+ "view.snapshot",
+ "timeline.native"
+ ],
+ "file": "evidence/viewport.json",
+ "status": "PASS",
+ "testTitle": "场景树搜索、可见性、选择、视口工具、原生动画与截图",
+ "recordedAt": "2026-09-06T08:22:14.169Z",
+ "kind": "e2e",
+ "title": "场景树搜索、可见性、选择、视口工具、原生动画与截图",
+ "screenshots": [],
+ "details": "树搜索、对象隐藏刷新、轮廓及视图开关、原生动画和PNG下载。移动/旋转/缩放只切换模式,真实gizmo拖动仍需补验;聚焦未做几何断言。"
+ }
+ ]
+ },
+ {
+ "id": "view.preview-step",
+ "group": "三维视图与操作",
+ "name": "预览播放 / 前后导航",
+ "source": {
+ "file": "../../../unreal_tran/src/main.js",
+ "line": 2122,
+ "symbol": "bindShellEvents"
+ },
+ "implementation": "现有边界",
+ "boundary": "播放运行蓝图;前后按钮当前按时间轴 ±2 秒,不是按业务工序跳转。",
+ "fix": "无新增业务工序导航。",
+ "acceptance": "播放触发真实流程;前后跳时与时间轴一致。",
+ "verification": "PASS",
+ "evidence": [
+ {
+ "ids": [
+ "project.name",
+ "project.autosave",
+ "view.orbit",
+ "view.focus",
+ "view.panels",
+ "view.preview-step",
+ "feedback.log",
+ "feedback.stats",
+ "property.identity",
+ "property.binding",
+ "selection.scope",
+ "component.inspect"
+ ],
+ "file": "evidence/46-navigation-panels-autosave.json",
+ "status": "PASS",
+ "testTitle": "补充视口与工作区:鼠标镜头、聚焦、面板、预览导航、日志与自动保存",
+ "recordedAt": "2026-09-06T08:23:54.656Z",
+ "kind": "e2e+api",
+ "title": "补充视口与工作区:鼠标镜头、聚焦、面板、预览导航、日志与自动保存",
+ "screenshots": [],
+ "details": "真实鼠标镜头操作;聚焦只验证相机目标;真实30秒自动保存,无测试加速定时器;统计内存估值未当作性能结论。"
+ }
+ ]
+ },
+ {
+ "id": "property.name",
+ "group": "对象属性与扩展标记",
+ "name": "对象名称",
+ "source": {
+ "file": "../../../unreal_tran/src/main.js",
+ "line": 1996,
+ "symbol": "bindInspectorEvents"
+ },
+ "implementation": "已有实现",
+ "boundary": "修改当前节点名,标识保持稳定。",
+ "fix": "无本轮专门改动。",
+ "acceptance": "树和属性同步,保存重开名称保留。",
+ "verification": "PASS",
+ "evidence": [
+ {
+ "ids": [
+ "property.name",
+ "property.position",
+ "property.rotation",
+ "property.scale",
+ "property.color",
+ "property.metalness",
+ "property.roughness",
+ "property.opacity",
+ "property.code",
+ "property.status",
+ "component.tag",
+ "project.undo",
+ "project.redo",
+ "project.save"
+ ],
+ "file": "evidence/properties.json",
+ "status": "PASS",
+ "testTitle": "对象名称、变换、PBR、属性与扩展标记保存重开;撤销重做",
+ "recordedAt": "2026-09-06T08:21:58.738Z",
+ "kind": "e2e+api",
+ "title": "对象名称、变换、PBR、属性与扩展标记保存重开;撤销重做",
+ "screenshots": [],
+ "details": "真实属性输入/材质参数/标签、保存与刷新;变换只验数值输入,不代替三维 gizmo 拖动。"
+ }
+ ]
+ },
+ {
+ "id": "property.identity",
+ "group": "对象属性与扩展标记",
+ "name": "对象标识 / 状态说明",
+ "source": {
+ "file": "../../../unreal_tran/src/main.js",
+ "line": 1958,
+ "symbol": "renderInspector"
+ },
+ "implementation": "本轮修复",
+ "boundary": "标识只读;可见性含祖先状态,阴影取实际castShadow;编辑能力由宿主权限注入。",
+ "fix": "不再固定显示“可见/投射阴影”,补宿主可编辑状态钩子。",
+ "acceptance": "标识不可编辑;选中对象、隐藏状态、阴影及只读权限标签一致。",
+ "verification": "PASS",
+ "evidence": [
+ {
+ "ids": [
+ "project.name",
+ "project.autosave",
+ "view.orbit",
+ "view.focus",
+ "view.panels",
+ "view.preview-step",
+ "feedback.log",
+ "feedback.stats",
+ "property.identity",
+ "property.binding",
+ "selection.scope",
+ "component.inspect"
+ ],
+ "file": "evidence/46-navigation-panels-autosave.json",
+ "status": "PASS",
+ "testTitle": "补充视口与工作区:鼠标镜头、聚焦、面板、预览导航、日志与自动保存",
+ "recordedAt": "2026-09-06T08:23:54.656Z",
+ "kind": "e2e+api",
+ "title": "补充视口与工作区:鼠标镜头、聚焦、面板、预览导航、日志与自动保存",
+ "screenshots": [],
+ "details": "真实鼠标镜头操作;聚焦只验证相机目标;真实30秒自动保存,无测试加速定时器;统计内存估值未当作性能结论。"
+ },
+ {
+ "ids": [
+ "property.identity"
+ ],
+ "file": "validation.json",
+ "status": "PASS",
+ "kind": "unit",
+ "title": "inspector state tags reflect ancestor visibility and actual shadow flags, with a host editability hook",
+ "screenshots": [],
+ "details": "真实属性 DOM 显示隐藏/阴影状态,并提供宿主权限标签 hook;不伪称此单测完成会话鉴权。"
+ }
+ ]
+ },
+ {
+ "id": "property.position",
+ "group": "对象属性与扩展标记",
+ "name": "位置 XYZ 数值",
+ "source": {
+ "file": "../../../unreal_tran/src/main.js",
+ "line": 1996,
+ "symbol": "bindInspectorEvents"
+ },
+ "implementation": "本轮修复",
+ "boundary": "局部坐标;动画当前帧下编辑也能生成正确基值。",
+ "fix": "中性姿态与实际应用偏移统一。",
+ "acceptance": "三个轴编辑、保存重开、动画回放一致。",
+ "verification": "PASS",
+ "evidence": [
+ {
+ "ids": [
+ "property.name",
+ "property.position",
+ "property.rotation",
+ "property.scale",
+ "property.color",
+ "property.metalness",
+ "property.roughness",
+ "property.opacity",
+ "property.code",
+ "property.status",
+ "component.tag",
+ "project.undo",
+ "project.redo",
+ "project.save"
+ ],
+ "file": "evidence/properties.json",
+ "status": "PASS",
+ "testTitle": "对象名称、变换、PBR、属性与扩展标记保存重开;撤销重做",
+ "recordedAt": "2026-09-06T08:21:58.738Z",
+ "kind": "e2e+api",
+ "title": "对象名称、变换、PBR、属性与扩展标记保存重开;撤销重做",
+ "screenshots": [],
+ "details": "真实属性输入/材质参数/标签、保存与刷新;变换只验数值输入,不代替三维 gizmo 拖动。"
+ }
+ ]
+ },
+ {
+ "id": "property.rotation",
+ "group": "对象属性与扩展标记",
+ "name": "旋转 XYZ 数值",
+ "source": {
+ "file": "../../../unreal_tran/src/main.js",
+ "line": 1996,
+ "symbol": "bindInspectorEvents"
+ },
+ "implementation": "本轮修复",
+ "boundary": "输入角度转弧度存储,显示角度。",
+ "fix": "多轨旋转基值不重复扣除。",
+ "acceptance": "度数显示与存储/播放的弧度一致。",
+ "verification": "PASS",
+ "evidence": [
+ {
+ "ids": [
+ "property.name",
+ "property.position",
+ "property.rotation",
+ "property.scale",
+ "property.color",
+ "property.metalness",
+ "property.roughness",
+ "property.opacity",
+ "property.code",
+ "property.status",
+ "component.tag",
+ "project.undo",
+ "project.redo",
+ "project.save"
+ ],
+ "file": "evidence/properties.json",
+ "status": "PASS",
+ "testTitle": "对象名称、变换、PBR、属性与扩展标记保存重开;撤销重做",
+ "recordedAt": "2026-09-06T08:21:58.738Z",
+ "kind": "e2e+api",
+ "title": "对象名称、变换、PBR、属性与扩展标记保存重开;撤销重做",
+ "screenshots": [],
+ "details": "真实属性输入/材质参数/标签、保存与刷新;变换只验数值输入,不代替三维 gizmo 拖动。"
+ }
+ ]
+ },
+ {
+ "id": "property.scale",
+ "group": "对象属性与扩展标记",
+ "name": "缩放 XYZ 数值",
+ "source": {
+ "file": "../../../unreal_tran/src/main.js",
+ "line": 1996,
+ "symbol": "bindInspectorEvents"
+ },
+ "implementation": "已有实现",
+ "boundary": "对象当前缩放,不添加新的缩放轨道。",
+ "fix": "无本轮专门改动。",
+ "acceptance": "非均匀缩放可保存和重开。",
+ "verification": "PASS",
+ "evidence": [
+ {
+ "ids": [
+ "property.name",
+ "property.position",
+ "property.rotation",
+ "property.scale",
+ "property.color",
+ "property.metalness",
+ "property.roughness",
+ "property.opacity",
+ "property.code",
+ "property.status",
+ "component.tag",
+ "project.undo",
+ "project.redo",
+ "project.save"
+ ],
+ "file": "evidence/properties.json",
+ "status": "PASS",
+ "testTitle": "对象名称、变换、PBR、属性与扩展标记保存重开;撤销重做",
+ "recordedAt": "2026-09-06T08:21:58.738Z",
+ "kind": "e2e+api",
+ "title": "对象名称、变换、PBR、属性与扩展标记保存重开;撤销重做",
+ "screenshots": [],
+ "details": "真实属性输入/材质参数/标签、保存与刷新;变换只验数值输入,不代替三维 gizmo 拖动。"
+ }
+ ]
+ },
+ {
+ "id": "property.color",
+ "group": "对象属性与扩展标记",
+ "name": "材质基础色",
+ "source": {
+ "file": "../../../unreal_tran/src/main.js",
+ "line": 1996,
+ "symbol": "bindInspectorEvents"
+ },
+ "implementation": "现有边界",
+ "boundary": "只编辑当前对象第一个材质槽;共享材质可能影响共享该实例的网格。",
+ "fix": "界面明确材质槽 1/N。",
+ "acceptance": "颜色修改、保存重开对应当前槽;不宣称所有槽可编辑。",
+ "verification": "PASS",
+ "evidence": [
+ {
+ "ids": [
+ "property.name",
+ "property.position",
+ "property.rotation",
+ "property.scale",
+ "property.color",
+ "property.metalness",
+ "property.roughness",
+ "property.opacity",
+ "property.code",
+ "property.status",
+ "component.tag",
+ "project.undo",
+ "project.redo",
+ "project.save"
+ ],
+ "file": "evidence/properties.json",
+ "status": "PASS",
+ "testTitle": "对象名称、变换、PBR、属性与扩展标记保存重开;撤销重做",
+ "recordedAt": "2026-09-06T08:21:58.738Z",
+ "kind": "e2e+api",
+ "title": "对象名称、变换、PBR、属性与扩展标记保存重开;撤销重做",
+ "screenshots": [],
+ "details": "真实属性输入/材质参数/标签、保存与刷新;变换只验数值输入,不代替三维 gizmo 拖动。"
+ }
+ ]
+ },
+ {
+ "id": "property.metalness",
+ "group": "对象属性与扩展标记",
+ "name": "材质金属度",
+ "source": {
+ "file": "../../../unreal_tran/src/main.js",
+ "line": 1996,
+ "symbol": "bindInspectorEvents"
+ },
+ "implementation": "现有边界",
+ "boundary": "当前首槽 PBR 参数 0~1。",
+ "fix": "标明材质槽范围。",
+ "acceptance": "拖动参数有效,保存重开值一致。",
+ "verification": "PASS",
+ "evidence": [
+ {
+ "ids": [
+ "property.name",
+ "property.position",
+ "property.rotation",
+ "property.scale",
+ "property.color",
+ "property.metalness",
+ "property.roughness",
+ "property.opacity",
+ "property.code",
+ "property.status",
+ "component.tag",
+ "project.undo",
+ "project.redo",
+ "project.save"
+ ],
+ "file": "evidence/properties.json",
+ "status": "PASS",
+ "testTitle": "对象名称、变换、PBR、属性与扩展标记保存重开;撤销重做",
+ "recordedAt": "2026-09-06T08:21:58.738Z",
+ "kind": "e2e+api",
+ "title": "对象名称、变换、PBR、属性与扩展标记保存重开;撤销重做",
+ "screenshots": [],
+ "details": "真实属性输入/材质参数/标签、保存与刷新;变换只验数值输入,不代替三维 gizmo 拖动。"
+ }
+ ]
+ },
+ {
+ "id": "property.roughness",
+ "group": "对象属性与扩展标记",
+ "name": "材质粗糙度",
+ "source": {
+ "file": "../../../unreal_tran/src/main.js",
+ "line": 1996,
+ "symbol": "bindInspectorEvents"
+ },
+ "implementation": "现有边界",
+ "boundary": "当前首槽 PBR 参数 0~1。",
+ "fix": "标明材质槽范围。",
+ "acceptance": "参数与当前槽持久化一致。",
+ "verification": "PASS",
+ "evidence": [
+ {
+ "ids": [
+ "property.name",
+ "property.position",
+ "property.rotation",
+ "property.scale",
+ "property.color",
+ "property.metalness",
+ "property.roughness",
+ "property.opacity",
+ "property.code",
+ "property.status",
+ "component.tag",
+ "project.undo",
+ "project.redo",
+ "project.save"
+ ],
+ "file": "evidence/properties.json",
+ "status": "PASS",
+ "testTitle": "对象名称、变换、PBR、属性与扩展标记保存重开;撤销重做",
+ "recordedAt": "2026-09-06T08:21:58.738Z",
+ "kind": "e2e+api",
+ "title": "对象名称、变换、PBR、属性与扩展标记保存重开;撤销重做",
+ "screenshots": [],
+ "details": "真实属性输入/材质参数/标签、保存与刷新;变换只验数值输入,不代替三维 gizmo 拖动。"
+ }
+ ]
+ },
+ {
+ "id": "property.opacity",
+ "group": "对象属性与扩展标记",
+ "name": "材质透明度",
+ "source": {
+ "file": "../../../unreal_tran/src/main.js",
+ "line": 1996,
+ "symbol": "bindInspectorEvents"
+ },
+ "implementation": "现有边界",
+ "boundary": "当前首槽;联动 transparent/depthWrite。",
+ "fix": "保留实际透明度保存字段。",
+ "acceptance": "透明效果与重开结果一致。",
+ "verification": "PASS",
+ "evidence": [
+ {
+ "ids": [
+ "property.name",
+ "property.position",
+ "property.rotation",
+ "property.scale",
+ "property.color",
+ "property.metalness",
+ "property.roughness",
+ "property.opacity",
+ "property.code",
+ "property.status",
+ "component.tag",
+ "project.undo",
+ "project.redo",
+ "project.save"
+ ],
+ "file": "evidence/properties.json",
+ "status": "PASS",
+ "testTitle": "对象名称、变换、PBR、属性与扩展标记保存重开;撤销重做",
+ "recordedAt": "2026-09-06T08:21:58.738Z",
+ "kind": "e2e+api",
+ "title": "对象名称、变换、PBR、属性与扩展标记保存重开;撤销重做",
+ "screenshots": [],
+ "details": "真实属性输入/材质参数/标签、保存与刷新;变换只验数值输入,不代替三维 gizmo 拖动。"
+ }
+ ]
+ },
+ {
+ "id": "property.code",
+ "group": "对象属性与扩展标记",
+ "name": "自定义资源编码",
+ "source": {
+ "file": "../../../unreal_tran/src/main.js",
+ "line": 1996,
+ "symbol": "bindInspectorEvents"
+ },
+ "implementation": "本轮修复",
+ "boundary": "对象 customProperties.resourceCode,不是服务器资产主键。",
+ "fix": "输入真正回写、保存、恢复及撤销。",
+ "acceptance": "编辑、清空、重选、JSON 往返、刷新均一致。",
+ "verification": "PASS",
+ "evidence": [
+ {
+ "ids": [
+ "property.name",
+ "property.position",
+ "property.rotation",
+ "property.scale",
+ "property.color",
+ "property.metalness",
+ "property.roughness",
+ "property.opacity",
+ "property.code",
+ "property.status",
+ "component.tag",
+ "project.undo",
+ "project.redo",
+ "project.save"
+ ],
+ "file": "evidence/properties.json",
+ "status": "PASS",
+ "testTitle": "对象名称、变换、PBR、属性与扩展标记保存重开;撤销重做",
+ "recordedAt": "2026-09-06T08:21:58.738Z",
+ "kind": "e2e+api",
+ "title": "对象名称、变换、PBR、属性与扩展标记保存重开;撤销重做",
+ "screenshots": [],
+ "details": "真实属性输入/材质参数/标签、保存与刷新;变换只验数值输入,不代替三维 gizmo 拖动。"
+ },
+ {
+ "ids": [
+ "property.code",
+ "property.status",
+ "component.tag"
+ ],
+ "file": "validation.json",
+ "status": "PASS",
+ "kind": "unit",
+ "title": "custom properties and extension labels survive actual capture, project JSON and object restore",
+ "screenshots": [],
+ "details": "真实对象 capture、normalization、JSON 与 restore 往返;清空和撤销到未存在字段状态。"
+ }
+ ]
+ },
+ {
+ "id": "property.status",
+ "group": "对象属性与扩展标记",
+ "name": "自定义运行状态",
+ "source": {
+ "file": "../../../unreal_tran/src/main.js",
+ "line": 1996,
+ "symbol": "bindInspectorEvents"
+ },
+ "implementation": "本轮修复",
+ "boundary": "NORMAL/SERVICE 标签,不会发起真实设备控制。",
+ "fix": "真正写回对象,首次变更可撤销。",
+ "acceptance": "两状态切换及刷新保留。",
+ "verification": "PASS",
+ "evidence": [
+ {
+ "ids": [
+ "property.name",
+ "property.position",
+ "property.rotation",
+ "property.scale",
+ "property.color",
+ "property.metalness",
+ "property.roughness",
+ "property.opacity",
+ "property.code",
+ "property.status",
+ "component.tag",
+ "project.undo",
+ "project.redo",
+ "project.save"
+ ],
+ "file": "evidence/properties.json",
+ "status": "PASS",
+ "testTitle": "对象名称、变换、PBR、属性与扩展标记保存重开;撤销重做",
+ "recordedAt": "2026-09-06T08:21:58.738Z",
+ "kind": "e2e+api",
+ "title": "对象名称、变换、PBR、属性与扩展标记保存重开;撤销重做",
+ "screenshots": [],
+ "details": "真实属性输入/材质参数/标签、保存与刷新;变换只验数值输入,不代替三维 gizmo 拖动。"
+ },
+ {
+ "ids": [
+ "property.code",
+ "property.status",
+ "component.tag"
+ ],
+ "file": "validation.json",
+ "status": "PASS",
+ "kind": "unit",
+ "title": "custom properties and extension labels survive actual capture, project JSON and object restore",
+ "screenshots": [],
+ "details": "真实对象 capture、normalization、JSON 与 restore 往返;清空和撤销到未存在字段状态。"
+ }
+ ]
+ },
+ {
+ "id": "property.binding",
+ "group": "对象属性与扩展标记",
+ "name": "流程绑定说明",
+ "source": {
+ "file": "../../../unreal_tran/src/main.js",
+ "line": 1958,
+ "symbol": "renderInspector"
+ },
+ "implementation": "现有边界",
+ "boundary": "只读显示模板/时间轴名称,不是教学任务的设备或物联网绑定。",
+ "fix": "无本轮业务绑定改造。",
+ "acceptance": "只读,不将其当作任务绑定操作验收。",
+ "verification": "PASS",
+ "evidence": [
+ {
+ "ids": [
+ "project.name",
+ "project.autosave",
+ "view.orbit",
+ "view.focus",
+ "view.panels",
+ "view.preview-step",
+ "feedback.log",
+ "feedback.stats",
+ "property.identity",
+ "property.binding",
+ "selection.scope",
+ "component.inspect"
+ ],
+ "file": "evidence/46-navigation-panels-autosave.json",
+ "status": "PASS",
+ "testTitle": "补充视口与工作区:鼠标镜头、聚焦、面板、预览导航、日志与自动保存",
+ "recordedAt": "2026-09-06T08:23:54.656Z",
+ "kind": "e2e+api",
+ "title": "补充视口与工作区:鼠标镜头、聚焦、面板、预览导航、日志与自动保存",
+ "screenshots": [],
+ "details": "真实鼠标镜头操作;聚焦只验证相机目标;真实30秒自动保存,无测试加速定时器;统计内存估值未当作性能结论。"
+ }
+ ]
+ },
+ {
+ "id": "component.inspect",
+ "group": "对象属性与扩展标记",
+ "name": "组件页内置能力说明",
+ "source": {
+ "file": "../../../unreal_tran/src/main.js",
+ "line": 1975,
+ "symbol": "renderComponents"
+ },
+ "implementation": "现有边界",
+ "boundary": "变换、场景节点、渲染等派生说明,没有通用组件启停系统。",
+ "fix": "区分内置说明与自定义标记。",
+ "acceptance": "显示与对象类型一致,不虚报可执行组件配置。",
+ "verification": "PASS",
+ "evidence": [
+ {
+ "ids": [
+ "project.name",
+ "project.autosave",
+ "view.orbit",
+ "view.focus",
+ "view.panels",
+ "view.preview-step",
+ "feedback.log",
+ "feedback.stats",
+ "property.identity",
+ "property.binding",
+ "selection.scope",
+ "component.inspect"
+ ],
+ "file": "evidence/46-navigation-panels-autosave.json",
+ "status": "PASS",
+ "testTitle": "补充视口与工作区:鼠标镜头、聚焦、面板、预览导航、日志与自动保存",
+ "recordedAt": "2026-09-06T08:23:54.656Z",
+ "kind": "e2e+api",
+ "title": "补充视口与工作区:鼠标镜头、聚焦、面板、预览导航、日志与自动保存",
+ "screenshots": [],
+ "details": "真实鼠标镜头操作;聚焦只验证相机目标;真实30秒自动保存,无测试加速定时器;统计内存估值未当作性能结论。"
+ }
+ ]
+ },
+ {
+ "id": "component.tag",
+ "group": "对象属性与扩展标记",
+ "name": "添加扩展标记",
+ "source": {
+ "file": "../../../unreal_tran/src/main.js",
+ "line": 1975,
+ "symbol": "renderComponents"
+ },
+ "implementation": "本轮澄清",
+ "boundary": "只保存名称标签;没有脚本、启停、运行时行为。",
+ "fix": "从“添加组件”改为“扩展标记,仅保存标签”。",
+ "acceptance": "新增标签保存/重开;不得声称已启用新行为。",
+ "verification": "PASS",
+ "evidence": [
+ {
+ "ids": [
+ "property.name",
+ "property.position",
+ "property.rotation",
+ "property.scale",
+ "property.color",
+ "property.metalness",
+ "property.roughness",
+ "property.opacity",
+ "property.code",
+ "property.status",
+ "component.tag",
+ "project.undo",
+ "project.redo",
+ "project.save"
+ ],
+ "file": "evidence/properties.json",
+ "status": "PASS",
+ "testTitle": "对象名称、变换、PBR、属性与扩展标记保存重开;撤销重做",
+ "recordedAt": "2026-09-06T08:21:58.738Z",
+ "kind": "e2e+api",
+ "title": "对象名称、变换、PBR、属性与扩展标记保存重开;撤销重做",
+ "screenshots": [],
+ "details": "真实属性输入/材质参数/标签、保存与刷新;变换只验数值输入,不代替三维 gizmo 拖动。"
+ },
+ {
+ "ids": [
+ "property.code",
+ "property.status",
+ "component.tag"
+ ],
+ "file": "validation.json",
+ "status": "PASS",
+ "kind": "unit",
+ "title": "custom properties and extension labels survive actual capture, project JSON and object restore",
+ "screenshots": [],
+ "details": "真实对象 capture、normalization、JSON 与 restore 往返;清空和撤销到未存在字段状态。"
+ }
+ ]
+ },
+ {
+ "id": "timeline.play",
+ "group": "时间轴与曲线",
+ "name": "播放 / 暂停 / Space",
+ "source": {
+ "file": "../../../unreal_tran/src/timeline.js",
+ "line": 393,
+ "symbol": "play()"
+ },
+ "implementation": "已有实现",
+ "boundary": "播放当前编辑时间轴;原生 GLB 动画单独控制。",
+ "fix": "停止清理与时间同步由本轮补强。",
+ "acceptance": "播放进度/真实对象姿态变化,暂停停在当前帧。",
+ "verification": "PASS",
+ "evidence": [
+ {
+ "ids": [
+ "timeline.play",
+ "timeline.stop",
+ "timeline.loop",
+ "timeline.key-nav",
+ "timeline.add-track",
+ "timeline.remove-track",
+ "timeline.target",
+ "timeline.property",
+ "timeline.mute",
+ "timeline.add-key",
+ "timeline.delete-key",
+ "timeline.drag-key",
+ "timeline.zoom",
+ "timeline.curve",
+ "curve.time",
+ "curve.value",
+ "curve.easing"
+ ],
+ "file": "evidence/truck-timeline-curve.json",
+ "status": "PASS",
+ "testTitle": "时间轴真实控件:轨道/关键帧/播放与曲线 5 插值,保存后重新加载一致",
+ "recordedAt": "2026-09-06T08:21:02.898Z",
+ "kind": "e2e+runtime-read",
+ "title": "时间轴真实控件:轨道/关键帧/播放与曲线 5 插值,保存后重新加载一致",
+ "screenshots": [],
+ "details": "真实控件和指针操作,运行时读取数值校验;curve.axis仅切换无数值独立断言,timeline.seek播放头与Alt吸附未在本例覆盖。"
+ }
+ ]
+ },
+ {
+ "id": "timeline.stop",
+ "group": "时间轴与曲线",
+ "name": "停止",
+ "source": {
+ "file": "../../../unreal_tran/src/timeline.js",
+ "line": 429,
+ "symbol": "stop()"
+ },
+ "implementation": "已有实现",
+ "boundary": "停止并回到起始时间,按起始帧应用动画。",
+ "fix": "无本轮语义扩展。",
+ "acceptance": "按钮停止后不再自行前进。",
+ "verification": "PASS",
+ "evidence": [
+ {
+ "ids": [
+ "timeline.play",
+ "timeline.stop",
+ "timeline.loop",
+ "timeline.key-nav",
+ "timeline.add-track",
+ "timeline.remove-track",
+ "timeline.target",
+ "timeline.property",
+ "timeline.mute",
+ "timeline.add-key",
+ "timeline.delete-key",
+ "timeline.drag-key",
+ "timeline.zoom",
+ "timeline.curve",
+ "curve.time",
+ "curve.value",
+ "curve.easing"
+ ],
+ "file": "evidence/truck-timeline-curve.json",
+ "status": "PASS",
+ "testTitle": "时间轴真实控件:轨道/关键帧/播放与曲线 5 插值,保存后重新加载一致",
+ "recordedAt": "2026-09-06T08:21:02.898Z",
+ "kind": "e2e+runtime-read",
+ "title": "时间轴真实控件:轨道/关键帧/播放与曲线 5 插值,保存后重新加载一致",
+ "screenshots": [],
+ "details": "真实控件和指针操作,运行时读取数值校验;curve.axis仅切换无数值独立断言,timeline.seek播放头与Alt吸附未在本例覆盖。"
+ }
+ ]
+ },
+ {
+ "id": "timeline.loop",
+ "group": "时间轴与曲线",
+ "name": "循环开关",
+ "source": {
+ "file": "../../../unreal_tran/src/timeline.js",
+ "line": 198,
+ "symbol": "#bindEvents"
+ },
+ "implementation": "本轮修复",
+ "boundary": "循环设置随 timeline 保存;不循环到达终点只完成一次。",
+ "fix": "循环变更进入历史/dirty。",
+ "acceptance": "开关、保存重开、到末尾/循环边界正确。",
+ "verification": "PASS",
+ "evidence": [
+ {
+ "ids": [
+ "timeline.play",
+ "timeline.stop",
+ "timeline.loop",
+ "timeline.key-nav",
+ "timeline.add-track",
+ "timeline.remove-track",
+ "timeline.target",
+ "timeline.property",
+ "timeline.mute",
+ "timeline.add-key",
+ "timeline.delete-key",
+ "timeline.drag-key",
+ "timeline.zoom",
+ "timeline.curve",
+ "curve.time",
+ "curve.value",
+ "curve.easing"
+ ],
+ "file": "evidence/truck-timeline-curve.json",
+ "status": "PASS",
+ "testTitle": "时间轴真实控件:轨道/关键帧/播放与曲线 5 插值,保存后重新加载一致",
+ "recordedAt": "2026-09-06T08:21:02.898Z",
+ "kind": "e2e+runtime-read",
+ "title": "时间轴真实控件:轨道/关键帧/播放与曲线 5 插值,保存后重新加载一致",
+ "screenshots": [],
+ "details": "真实控件和指针操作,运行时读取数值校验;curve.axis仅切换无数值独立断言,timeline.seek播放头与Alt吸附未在本例覆盖。"
+ }
+ ]
+ },
+ {
+ "id": "timeline.key-nav",
+ "group": "时间轴与曲线",
+ "name": "上一 / 下一关键帧",
+ "source": {
+ "file": "../../../unreal_tran/src/timeline.js",
+ "line": 232,
+ "symbol": "#onClick"
+ },
+ "implementation": "已有实现",
+ "boundary": "按时间轴关键帧时间定位。",
+ "fix": "无本轮专门改动。",
+ "acceptance": "多个轨道时跳到正确相邻关键时刻。",
+ "verification": "PASS",
+ "evidence": [
+ {
+ "ids": [
+ "timeline.play",
+ "timeline.stop",
+ "timeline.loop",
+ "timeline.key-nav",
+ "timeline.add-track",
+ "timeline.remove-track",
+ "timeline.target",
+ "timeline.property",
+ "timeline.mute",
+ "timeline.add-key",
+ "timeline.delete-key",
+ "timeline.drag-key",
+ "timeline.zoom",
+ "timeline.curve",
+ "curve.time",
+ "curve.value",
+ "curve.easing"
+ ],
+ "file": "evidence/truck-timeline-curve.json",
+ "status": "PASS",
+ "testTitle": "时间轴真实控件:轨道/关键帧/播放与曲线 5 插值,保存后重新加载一致",
+ "recordedAt": "2026-09-06T08:21:02.898Z",
+ "kind": "e2e+runtime-read",
+ "title": "时间轴真实控件:轨道/关键帧/播放与曲线 5 插值,保存后重新加载一致",
+ "screenshots": [],
+ "details": "真实控件和指针操作,运行时读取数值校验;curve.axis仅切换无数值独立断言,timeline.seek播放头与Alt吸附未在本例覆盖。"
+ }
+ ]
+ },
+ {
+ "id": "timeline.seek",
+ "group": "时间轴与曲线",
+ "name": "拖播放头 / 点击刻度",
+ "source": {
+ "file": "../../../unreal_tran/src/timeline.js",
+ "line": 263,
+ "symbol": "#onPointerDown"
+ },
+ "implementation": "本轮修复",
+ "boundary": "实时求值当前帧;键盘左右移动一帧,Shift 移动一秒。",
+ "fix": "拖动补发 timechange,解绑指针监听。",
+ "acceptance": "时间码、曲线和视口姿态同步。",
+ "verification": "PASS",
+ "evidence": [
+ {
+ "ids": [
+ "timeline.seek",
+ "timeline.record",
+ "timeline.baseline",
+ "curve.axis",
+ "blueprint.palette",
+ "blueprint.add",
+ "blueprint.canvas"
+ ],
+ "file": "evidence/truck-blueprint-tool-supplement.json",
+ "status": "PASS",
+ "testTitle": "补充时间轴与蓝图工具:刻度/按键/记录/分量、基准匹配、节点搜索拖入与画布平移",
+ "recordedAt": "2026-09-06T08:21:23.003Z",
+ "kind": "e2e+runtime-read",
+ "title": "补充时间轴与蓝图工具:刻度/按键/记录/分量、基准匹配、节点搜索拖入与画布平移",
+ "screenshots": [],
+ "details": "真实模型编辑参数/基准轨道生成验收,不构成装备操作或维修指导。桥梁原工程94仅GET和copy。"
+ }
+ ]
+ },
+ {
+ "id": "timeline.add-track",
+ "group": "时间轴与曲线",
+ "name": "添加轨道",
+ "source": {
+ "file": "../../../unreal_tran/src/timeline.js",
+ "line": 471,
+ "symbol": "addTrack("
+ },
+ "implementation": "已有实现",
+ "boundary": "当前主装备节点的变换轨道。",
+ "fix": "无新增轨道类型。",
+ "acceptance": "增加后可绑定节点并保存。",
+ "verification": "PASS",
+ "evidence": [
+ {
+ "ids": [
+ "timeline.play",
+ "timeline.stop",
+ "timeline.loop",
+ "timeline.key-nav",
+ "timeline.add-track",
+ "timeline.remove-track",
+ "timeline.target",
+ "timeline.property",
+ "timeline.mute",
+ "timeline.add-key",
+ "timeline.delete-key",
+ "timeline.drag-key",
+ "timeline.zoom",
+ "timeline.curve",
+ "curve.time",
+ "curve.value",
+ "curve.easing"
+ ],
+ "file": "evidence/truck-timeline-curve.json",
+ "status": "PASS",
+ "testTitle": "时间轴真实控件:轨道/关键帧/播放与曲线 5 插值,保存后重新加载一致",
+ "recordedAt": "2026-09-06T08:21:02.898Z",
+ "kind": "e2e+runtime-read",
+ "title": "时间轴真实控件:轨道/关键帧/播放与曲线 5 插值,保存后重新加载一致",
+ "screenshots": [],
+ "details": "真实控件和指针操作,运行时读取数值校验;curve.axis仅切换无数值独立断言,timeline.seek播放头与Alt吸附未在本例覆盖。"
+ }
+ ]
+ },
+ {
+ "id": "timeline.remove-track",
+ "group": "时间轴与曲线",
+ "name": "删除轨道",
+ "source": {
+ "file": "../../../unreal_tran/src/timeline.js",
+ "line": 495,
+ "symbol": "removeTrack("
+ },
+ "implementation": "本轮修复",
+ "boundary": "删除后不再保留该轨道施加的旧姿态。",
+ "fix": "宿主恢复此前作用的属性基值。",
+ "acceptance": "删除即时复原,再保存/重开一致。",
+ "verification": "PASS",
+ "evidence": [
+ {
+ "ids": [
+ "timeline.play",
+ "timeline.stop",
+ "timeline.loop",
+ "timeline.key-nav",
+ "timeline.add-track",
+ "timeline.remove-track",
+ "timeline.target",
+ "timeline.property",
+ "timeline.mute",
+ "timeline.add-key",
+ "timeline.delete-key",
+ "timeline.drag-key",
+ "timeline.zoom",
+ "timeline.curve",
+ "curve.time",
+ "curve.value",
+ "curve.easing"
+ ],
+ "file": "evidence/truck-timeline-curve.json",
+ "status": "PASS",
+ "testTitle": "时间轴真实控件:轨道/关键帧/播放与曲线 5 插值,保存后重新加载一致",
+ "recordedAt": "2026-09-06T08:21:02.898Z",
+ "kind": "e2e+runtime-read",
+ "title": "时间轴真实控件:轨道/关键帧/播放与曲线 5 插值,保存后重新加载一致",
+ "screenshots": [],
+ "details": "真实控件和指针操作,运行时读取数值校验;curve.axis仅切换无数值独立断言,timeline.seek播放头与Alt吸附未在本例覆盖。"
+ }
+ ]
+ },
+ {
+ "id": "timeline.target",
+ "group": "时间轴与曲线",
+ "name": "修改轨道目标节点",
+ "source": {
+ "file": "../../../unreal_tran/src/timeline.js",
+ "line": 506,
+ "symbol": "updateSelectedTrack"
+ },
+ "implementation": "本轮修复",
+ "boundary": "用稳定对象标识绑定,不靠同名第一项。",
+ "fix": "重绑清理旧目标的应用姿态。",
+ "acceptance": "从 A 改为 B,A 复原,B 播放。",
+ "verification": "PASS",
+ "evidence": [
+ {
+ "ids": [
+ "timeline.play",
+ "timeline.stop",
+ "timeline.loop",
+ "timeline.key-nav",
+ "timeline.add-track",
+ "timeline.remove-track",
+ "timeline.target",
+ "timeline.property",
+ "timeline.mute",
+ "timeline.add-key",
+ "timeline.delete-key",
+ "timeline.drag-key",
+ "timeline.zoom",
+ "timeline.curve",
+ "curve.time",
+ "curve.value",
+ "curve.easing"
+ ],
+ "file": "evidence/truck-timeline-curve.json",
+ "status": "PASS",
+ "testTitle": "时间轴真实控件:轨道/关键帧/播放与曲线 5 插值,保存后重新加载一致",
+ "recordedAt": "2026-09-06T08:21:02.898Z",
+ "kind": "e2e+runtime-read",
+ "title": "时间轴真实控件:轨道/关键帧/播放与曲线 5 插值,保存后重新加载一致",
+ "screenshots": [],
+ "details": "真实控件和指针操作,运行时读取数值校验;curve.axis仅切换无数值独立断言,timeline.seek播放头与Alt吸附未在本例覆盖。"
+ },
+ {
+ "ids": [
+ "timeline.multitrack",
+ "timeline.target"
+ ],
+ "file": "validation.json",
+ "status": "PASS",
+ "kind": "unit",
+ "title": "changing a binding resets the former target and keeps manual transform edits as base",
+ "screenshots": [],
+ "details": "轨道重绑后旧目标复位,手动变换成为新基值;不假称鼠标拖动已验。"
+ }
+ ]
+ },
+ {
+ "id": "timeline.property",
+ "group": "时间轴与曲线",
+ "name": "选择位置 / 旋转属性",
+ "source": {
+ "file": "../../../unreal_tran/src/timeline.js",
+ "line": 506,
+ "symbol": "updateSelectedTrack"
+ },
+ "implementation": "已有实现",
+ "boundary": "位置 XYZ 向量、旋转 X/Y/Z;没有缩放轨道 UI。",
+ "fix": "保留当前实际能力。",
+ "acceptance": "属性切换后关键帧类型和播放一致。",
+ "verification": "PASS",
+ "evidence": [
+ {
+ "ids": [
+ "timeline.play",
+ "timeline.stop",
+ "timeline.loop",
+ "timeline.key-nav",
+ "timeline.add-track",
+ "timeline.remove-track",
+ "timeline.target",
+ "timeline.property",
+ "timeline.mute",
+ "timeline.add-key",
+ "timeline.delete-key",
+ "timeline.drag-key",
+ "timeline.zoom",
+ "timeline.curve",
+ "curve.time",
+ "curve.value",
+ "curve.easing"
+ ],
+ "file": "evidence/truck-timeline-curve.json",
+ "status": "PASS",
+ "testTitle": "时间轴真实控件:轨道/关键帧/播放与曲线 5 插值,保存后重新加载一致",
+ "recordedAt": "2026-09-06T08:21:02.898Z",
+ "kind": "e2e+runtime-read",
+ "title": "时间轴真实控件:轨道/关键帧/播放与曲线 5 插值,保存后重新加载一致",
+ "screenshots": [],
+ "details": "真实控件和指针操作,运行时读取数值校验;curve.axis仅切换无数值独立断言,timeline.seek播放头与Alt吸附未在本例覆盖。"
+ }
+ ]
+ },
+ {
+ "id": "timeline.mute",
+ "group": "时间轴与曲线",
+ "name": "轨道静音",
+ "source": {
+ "file": "../../../unreal_tran/src/timeline.js",
+ "line": 232,
+ "symbol": "#onClick"
+ },
+ "implementation": "本轮修复",
+ "boundary": "仅忽略当前轨道,不关闭其他轨道。",
+ "fix": "不再停留旧姿态或保存时重复扣值。",
+ "acceptance": "静音/恢复后实际姿态与重开一致。",
+ "verification": "PASS",
+ "evidence": [
+ {
+ "ids": [
+ "timeline.play",
+ "timeline.stop",
+ "timeline.loop",
+ "timeline.key-nav",
+ "timeline.add-track",
+ "timeline.remove-track",
+ "timeline.target",
+ "timeline.property",
+ "timeline.mute",
+ "timeline.add-key",
+ "timeline.delete-key",
+ "timeline.drag-key",
+ "timeline.zoom",
+ "timeline.curve",
+ "curve.time",
+ "curve.value",
+ "curve.easing"
+ ],
+ "file": "evidence/truck-timeline-curve.json",
+ "status": "PASS",
+ "testTitle": "时间轴真实控件:轨道/关键帧/播放与曲线 5 插值,保存后重新加载一致",
+ "recordedAt": "2026-09-06T08:21:02.898Z",
+ "kind": "e2e+runtime-read",
+ "title": "时间轴真实控件:轨道/关键帧/播放与曲线 5 插值,保存后重新加载一致",
+ "screenshots": [],
+ "details": "真实控件和指针操作,运行时读取数值校验;curve.axis仅切换无数值独立断言,timeline.seek播放头与Alt吸附未在本例覆盖。"
+ }
+ ]
+ },
+ {
+ "id": "timeline.record",
+ "group": "时间轴与曲线",
+ "name": "记录当前姿态关键帧",
+ "source": {
+ "file": "../../../unreal_tran/src/main.js",
+ "line": 2276,
+ "symbol": "addKeyframeFromSelection"
+ },
+ "implementation": "本轮修复",
+ "boundary": "记录相对对象基值的当前值。",
+ "fix": "与多轨实际应用/保存采用同一基值。",
+ "acceptance": "操作对象后记录,回放命中相同姿态。",
+ "verification": "PASS",
+ "evidence": [
+ {
+ "ids": [
+ "timeline.seek",
+ "timeline.record",
+ "timeline.baseline",
+ "curve.axis",
+ "blueprint.palette",
+ "blueprint.add",
+ "blueprint.canvas"
+ ],
+ "file": "evidence/truck-blueprint-tool-supplement.json",
+ "status": "PASS",
+ "testTitle": "补充时间轴与蓝图工具:刻度/按键/记录/分量、基准匹配、节点搜索拖入与画布平移",
+ "recordedAt": "2026-09-06T08:21:23.003Z",
+ "kind": "e2e+runtime-read",
+ "title": "补充时间轴与蓝图工具:刻度/按键/记录/分量、基准匹配、节点搜索拖入与画布平移",
+ "screenshots": [],
+ "details": "真实模型编辑参数/基准轨道生成验收,不构成装备操作或维修指导。桥梁原工程94仅GET和copy。"
+ }
+ ]
+ },
+ {
+ "id": "timeline.add-key",
+ "group": "时间轴与曲线",
+ "name": "双击新增关键帧",
+ "source": {
+ "file": "../../../unreal_tran/src/timeline.js",
+ "line": 255,
+ "symbol": "#onDoubleClick"
+ },
+ "implementation": "已有实现",
+ "boundary": "在轨道对应时刻创建/更新关键帧。",
+ "fix": "时间碰撞保留当前编辑帧。",
+ "acceptance": "新增后节点可选,时间/数值可保存。",
+ "verification": "PASS",
+ "evidence": [
+ {
+ "ids": [
+ "timeline.play",
+ "timeline.stop",
+ "timeline.loop",
+ "timeline.key-nav",
+ "timeline.add-track",
+ "timeline.remove-track",
+ "timeline.target",
+ "timeline.property",
+ "timeline.mute",
+ "timeline.add-key",
+ "timeline.delete-key",
+ "timeline.drag-key",
+ "timeline.zoom",
+ "timeline.curve",
+ "curve.time",
+ "curve.value",
+ "curve.easing"
+ ],
+ "file": "evidence/truck-timeline-curve.json",
+ "status": "PASS",
+ "testTitle": "时间轴真实控件:轨道/关键帧/播放与曲线 5 插值,保存后重新加载一致",
+ "recordedAt": "2026-09-06T08:21:02.898Z",
+ "kind": "e2e+runtime-read",
+ "title": "时间轴真实控件:轨道/关键帧/播放与曲线 5 插值,保存后重新加载一致",
+ "screenshots": [],
+ "details": "真实控件和指针操作,运行时读取数值校验;curve.axis仅切换无数值独立断言,timeline.seek播放头与Alt吸附未在本例覆盖。"
+ }
+ ]
+ },
+ {
+ "id": "timeline.delete-key",
+ "group": "时间轴与曲线",
+ "name": "删除关键帧 / Delete",
+ "source": {
+ "file": "../../../unreal_tran/src/timeline.js",
+ "line": 584,
+ "symbol": "deleteSelected"
+ },
+ "implementation": "已有实现",
+ "boundary": "删除选中的关键帧;输入框编辑时不截获删除键。",
+ "fix": "删除和监听清理回归。",
+ "acceptance": "删除后曲线和播放变化,撤销可恢复。",
+ "verification": "PASS",
+ "evidence": [
+ {
+ "ids": [
+ "timeline.play",
+ "timeline.stop",
+ "timeline.loop",
+ "timeline.key-nav",
+ "timeline.add-track",
+ "timeline.remove-track",
+ "timeline.target",
+ "timeline.property",
+ "timeline.mute",
+ "timeline.add-key",
+ "timeline.delete-key",
+ "timeline.drag-key",
+ "timeline.zoom",
+ "timeline.curve",
+ "curve.time",
+ "curve.value",
+ "curve.easing"
+ ],
+ "file": "evidence/truck-timeline-curve.json",
+ "status": "PASS",
+ "testTitle": "时间轴真实控件:轨道/关键帧/播放与曲线 5 插值,保存后重新加载一致",
+ "recordedAt": "2026-09-06T08:21:02.898Z",
+ "kind": "e2e+runtime-read",
+ "title": "时间轴真实控件:轨道/关键帧/播放与曲线 5 插值,保存后重新加载一致",
+ "screenshots": [],
+ "details": "真实控件和指针操作,运行时读取数值校验;curve.axis仅切换无数值独立断言,timeline.seek播放头与Alt吸附未在本例覆盖。"
+ }
+ ]
+ },
+ {
+ "id": "timeline.drag-key",
+ "group": "时间轴与曲线",
+ "name": "关键帧拖动 / Alt 取消吸附",
+ "source": {
+ "file": "../../../unreal_tran/src/timeline.js",
+ "line": 263,
+ "symbol": "#onPointerDown"
+ },
+ "implementation": "本轮修复",
+ "boundary": "默认 1/30 秒吸附;重叠时合并,保留正在编辑帧。",
+ "fix": "拖动时间同步,碰撞处理确定。",
+ "acceptance": "拖动/重叠/Alt 行为和保存后时间正确。",
+ "verification": "PASS",
+ "evidence": [
+ {
+ "ids": [
+ "timeline.play",
+ "timeline.stop",
+ "timeline.loop",
+ "timeline.key-nav",
+ "timeline.add-track",
+ "timeline.remove-track",
+ "timeline.target",
+ "timeline.property",
+ "timeline.mute",
+ "timeline.add-key",
+ "timeline.delete-key",
+ "timeline.drag-key",
+ "timeline.zoom",
+ "timeline.curve",
+ "curve.time",
+ "curve.value",
+ "curve.easing"
+ ],
+ "file": "evidence/truck-timeline-curve.json",
+ "status": "PASS",
+ "testTitle": "时间轴真实控件:轨道/关键帧/播放与曲线 5 插值,保存后重新加载一致",
+ "recordedAt": "2026-09-06T08:21:02.898Z",
+ "kind": "e2e+runtime-read",
+ "title": "时间轴真实控件:轨道/关键帧/播放与曲线 5 插值,保存后重新加载一致",
+ "screenshots": [],
+ "details": "真实控件和指针操作,运行时读取数值校验;curve.axis仅切换无数值独立断言,timeline.seek播放头与Alt吸附未在本例覆盖。"
+ }
+ ]
+ },
+ {
+ "id": "timeline.zoom",
+ "group": "时间轴与曲线",
+ "name": "时间轴缩放",
+ "source": {
+ "file": "../../../unreal_tran/src/timeline.js",
+ "line": 451,
+ "symbol": "setZoom"
+ },
+ "implementation": "已有实现",
+ "boundary": "显示缩放随 timeline 记录;不改变时长/关键帧数值。",
+ "fix": "无本轮专门改动。",
+ "acceptance": "缩放不改变动作内容。",
+ "verification": "PASS",
+ "evidence": [
+ {
+ "ids": [
+ "timeline.play",
+ "timeline.stop",
+ "timeline.loop",
+ "timeline.key-nav",
+ "timeline.add-track",
+ "timeline.remove-track",
+ "timeline.target",
+ "timeline.property",
+ "timeline.mute",
+ "timeline.add-key",
+ "timeline.delete-key",
+ "timeline.drag-key",
+ "timeline.zoom",
+ "timeline.curve",
+ "curve.time",
+ "curve.value",
+ "curve.easing"
+ ],
+ "file": "evidence/truck-timeline-curve.json",
+ "status": "PASS",
+ "testTitle": "时间轴真实控件:轨道/关键帧/播放与曲线 5 插值,保存后重新加载一致",
+ "recordedAt": "2026-09-06T08:21:02.898Z",
+ "kind": "e2e+runtime-read",
+ "title": "时间轴真实控件:轨道/关键帧/播放与曲线 5 插值,保存后重新加载一致",
+ "screenshots": [],
+ "details": "真实控件和指针操作,运行时读取数值校验;curve.axis仅切换无数值独立断言,timeline.seek播放头与Alt吸附未在本例覆盖。"
+ }
+ ]
+ },
+ {
+ "id": "timeline.multitrack",
+ "group": "时间轴与曲线",
+ "name": "多轨组合与保存基值",
+ "source": {
+ "file": "../../../unreal_tran/src/model-editor-state.js",
+ "line": 12,
+ "symbol": "applyEditorTimelinePose"
+ },
+ "implementation": "本轮修复",
+ "boundary": "不同属性组合;同对象同属性最后有效轨道生效。",
+ "fix": "求值、手工编辑基值、序列化一致。",
+ "acceptance": "两轨同属性、静音、删轨、重绑、重开无漂移。",
+ "verification": "PASS",
+ "evidence": [
+ {
+ "ids": [
+ "timeline.multitrack"
+ ],
+ "file": "validation.json",
+ "status": "PASS",
+ "kind": "unit",
+ "title": "multiple tracks on one property use last effective value; capture subtracts it only once",
+ "screenshots": [],
+ "details": "调用实际 apply/capture/update 基值方法;验证同属性最后有效轨道、静音/删除后中性姿态,偏移只扣一次。"
+ },
+ {
+ "ids": [
+ "timeline.multitrack",
+ "timeline.target"
+ ],
+ "file": "validation.json",
+ "status": "PASS",
+ "kind": "unit",
+ "title": "changing a binding resets the former target and keeps manual transform edits as base",
+ "screenshots": [],
+ "details": "轨道重绑后旧目标复位,手动变换成为新基值;不假称鼠标拖动已验。"
+ }
+ ]
+ },
+ {
+ "id": "timeline.baseline",
+ "group": "时间轴与曲线",
+ "name": "载入装备基准动作",
+ "source": {
+ "file": "../../../unreal_tran/src/main.js",
+ "line": 3058,
+ "symbol": "loadBaselineAnimation"
+ },
+ "implementation": "现有边界",
+ "boundary": "匹配部件后直接替换当前时间轴/蓝图;当前没有单独确认框,不是任意维修流程自动生成。",
+ "fix": "保持当前装备限定;无匹配只提示,不改工程。",
+ "acceptance": "匹配时载入/保存/重开;无匹配时保留原数据并给出提示。",
+ "verification": "PASS",
+ "evidence": [
+ {
+ "ids": [
+ "timeline.seek",
+ "timeline.record",
+ "timeline.baseline",
+ "curve.axis",
+ "blueprint.palette",
+ "blueprint.add",
+ "blueprint.canvas"
+ ],
+ "file": "evidence/truck-blueprint-tool-supplement.json",
+ "status": "PASS",
+ "testTitle": "补充时间轴与蓝图工具:刻度/按键/记录/分量、基准匹配、节点搜索拖入与画布平移",
+ "recordedAt": "2026-09-06T08:21:23.003Z",
+ "kind": "e2e+runtime-read",
+ "title": "补充时间轴与蓝图工具:刻度/按键/记录/分量、基准匹配、节点搜索拖入与画布平移",
+ "screenshots": [],
+ "details": "真实模型编辑参数/基准轨道生成验收,不构成装备操作或维修指导。桥梁原工程94仅GET和copy。"
+ }
+ ]
+ },
+ {
+ "id": "timeline.native",
+ "group": "时间轴与曲线",
+ "name": "GLB 原生动画选择 / 播放",
+ "source": {
+ "file": "../../../unreal_tran/src/main.js",
+ "line": 1715,
+ "symbol": "selectNativeAnimation"
+ },
+ "implementation": "已有实现",
+ "boundary": "真实 GLB 自带片段;保存选择索引,不是蓝图 clip 字段。",
+ "fix": "无片段时禁用。",
+ "acceptance": "狐狸实际动画播放/暂停,选择保存后重开一致。",
+ "verification": "PASS",
+ "evidence": [
+ {
+ "ids": [
+ "tree.search",
+ "tree.visibility",
+ "selection.sync",
+ "view.grid",
+ "view.pbr",
+ "view.wireframe",
+ "view.outline",
+ "view.perspective",
+ "view.preview",
+ "view.snapshot",
+ "timeline.native"
+ ],
+ "file": "evidence/viewport.json",
+ "status": "PASS",
+ "testTitle": "场景树搜索、可见性、选择、视口工具、原生动画与截图",
+ "recordedAt": "2026-09-06T08:22:14.169Z",
+ "kind": "e2e",
+ "title": "场景树搜索、可见性、选择、视口工具、原生动画与截图",
+ "screenshots": [],
+ "details": "树搜索、对象隐藏刷新、轮廓及视图开关、原生动画和PNG下载。移动/旋转/缩放只切换模式,真实gizmo拖动仍需补验;聚焦未做几何断言。"
+ }
+ ]
+ },
+ {
+ "id": "timeline.curve",
+ "group": "时间轴与曲线",
+ "name": "曲线选择轨道 / 关键帧",
+ "source": {
+ "file": "../../../unreal_tran/src/model-curve-editor.js",
+ "line": 5,
+ "symbol": "class ModelCurveEditor"
+ },
+ "implementation": "本轮修复",
+ "boundary": "曲线与时间轴共用一份轨道和关键帧数据。",
+ "fix": "移除固定 SVG 假数据。",
+ "acceptance": "实际轨道名和点数匹配时间轴,点击同步当前帧。",
+ "verification": "PASS",
+ "evidence": [
+ {
+ "ids": [
+ "timeline.play",
+ "timeline.stop",
+ "timeline.loop",
+ "timeline.key-nav",
+ "timeline.add-track",
+ "timeline.remove-track",
+ "timeline.target",
+ "timeline.property",
+ "timeline.mute",
+ "timeline.add-key",
+ "timeline.delete-key",
+ "timeline.drag-key",
+ "timeline.zoom",
+ "timeline.curve",
+ "curve.time",
+ "curve.value",
+ "curve.easing"
+ ],
+ "file": "evidence/truck-timeline-curve.json",
+ "status": "PASS",
+ "testTitle": "时间轴真实控件:轨道/关键帧/播放与曲线 5 插值,保存后重新加载一致",
+ "recordedAt": "2026-09-06T08:21:02.898Z",
+ "kind": "e2e+runtime-read",
+ "title": "时间轴真实控件:轨道/关键帧/播放与曲线 5 插值,保存后重新加载一致",
+ "screenshots": [],
+ "details": "真实控件和指针操作,运行时读取数值校验;curve.axis仅切换无数值独立断言,timeline.seek播放头与Alt吸附未在本例覆盖。"
+ },
+ {
+ "ids": [
+ "timeline.curve",
+ "curve.time",
+ "curve.value",
+ "curve.easing"
+ ],
+ "file": "validation.json",
+ "status": "PASS",
+ "kind": "unit",
+ "title": "curve edits real scalar key values, time and interpolation, persisting through document reload",
+ "screenshots": [],
+ "details": "真实曲线控件与 TimelineEditor,标量编辑与文档往返。"
+ }
+ ]
+ },
+ {
+ "id": "curve.time",
+ "group": "时间轴与曲线",
+ "name": "曲线编辑时间",
+ "source": {
+ "file": "../../../unreal_tran/src/model-curve-editor.js",
+ "line": 30,
+ "symbol": "change(input)"
+ },
+ "implementation": "本轮修复",
+ "boundary": "更新实际 key.time,约束在时间轴时长内。",
+ "fix": "调用 timeline.updateKeyframe 并保持编辑目标。",
+ "acceptance": "改时间后重排、撤销、JSON 往返一致。",
+ "verification": "PASS",
+ "evidence": [
+ {
+ "ids": [
+ "timeline.play",
+ "timeline.stop",
+ "timeline.loop",
+ "timeline.key-nav",
+ "timeline.add-track",
+ "timeline.remove-track",
+ "timeline.target",
+ "timeline.property",
+ "timeline.mute",
+ "timeline.add-key",
+ "timeline.delete-key",
+ "timeline.drag-key",
+ "timeline.zoom",
+ "timeline.curve",
+ "curve.time",
+ "curve.value",
+ "curve.easing"
+ ],
+ "file": "evidence/truck-timeline-curve.json",
+ "status": "PASS",
+ "testTitle": "时间轴真实控件:轨道/关键帧/播放与曲线 5 插值,保存后重新加载一致",
+ "recordedAt": "2026-09-06T08:21:02.898Z",
+ "kind": "e2e+runtime-read",
+ "title": "时间轴真实控件:轨道/关键帧/播放与曲线 5 插值,保存后重新加载一致",
+ "screenshots": [],
+ "details": "真实控件和指针操作,运行时读取数值校验;curve.axis仅切换无数值独立断言,timeline.seek播放头与Alt吸附未在本例覆盖。"
+ },
+ {
+ "ids": [
+ "timeline.curve",
+ "curve.time",
+ "curve.value",
+ "curve.easing"
+ ],
+ "file": "validation.json",
+ "status": "PASS",
+ "kind": "unit",
+ "title": "curve edits real scalar key values, time and interpolation, persisting through document reload",
+ "screenshots": [],
+ "details": "真实曲线控件与 TimelineEditor,标量编辑与文档往返。"
+ }
+ ]
+ },
+ {
+ "id": "curve.value",
+ "group": "时间轴与曲线",
+ "name": "曲线编辑标量 / XYZ 数值",
+ "source": {
+ "file": "../../../unreal_tran/src/model-curve-editor.js",
+ "line": 30,
+ "symbol": "change(input)"
+ },
+ "implementation": "本轮修复",
+ "boundary": "修改实际关键帧值,向量编辑保留其他分量。",
+ "fix": "无效空值拒绝提交。",
+ "acceptance": "标量和向量都驱动真实姿态并保存。",
+ "verification": "PASS",
+ "evidence": [
+ {
+ "ids": [
+ "timeline.play",
+ "timeline.stop",
+ "timeline.loop",
+ "timeline.key-nav",
+ "timeline.add-track",
+ "timeline.remove-track",
+ "timeline.target",
+ "timeline.property",
+ "timeline.mute",
+ "timeline.add-key",
+ "timeline.delete-key",
+ "timeline.drag-key",
+ "timeline.zoom",
+ "timeline.curve",
+ "curve.time",
+ "curve.value",
+ "curve.easing"
+ ],
+ "file": "evidence/truck-timeline-curve.json",
+ "status": "PASS",
+ "testTitle": "时间轴真实控件:轨道/关键帧/播放与曲线 5 插值,保存后重新加载一致",
+ "recordedAt": "2026-09-06T08:21:02.898Z",
+ "kind": "e2e+runtime-read",
+ "title": "时间轴真实控件:轨道/关键帧/播放与曲线 5 插值,保存后重新加载一致",
+ "screenshots": [],
+ "details": "真实控件和指针操作,运行时读取数值校验;curve.axis仅切换无数值独立断言,timeline.seek播放头与Alt吸附未在本例覆盖。"
+ },
+ {
+ "ids": [
+ "timeline.curve",
+ "curve.time",
+ "curve.value",
+ "curve.easing"
+ ],
+ "file": "validation.json",
+ "status": "PASS",
+ "kind": "unit",
+ "title": "curve edits real scalar key values, time and interpolation, persisting through document reload",
+ "screenshots": [],
+ "details": "真实曲线控件与 TimelineEditor,标量编辑与文档往返。"
+ },
+ {
+ "ids": [
+ "curve.value"
+ ],
+ "file": "validation.json",
+ "status": "PASS",
+ "kind": "unit",
+ "title": "curve vector editing preserves other components and selected track; invalid values do not mutate",
+ "screenshots": [],
+ "details": "向量其它分量保留,当前轨道、非法值保护;不包含显示轴切换或可视像素验收。"
+ }
+ ]
+ },
+ {
+ "id": "curve.easing",
+ "group": "时间轴与曲线",
+ "name": "曲线插值",
+ "source": {
+ "file": "../../../unreal_tran/src/model-curve-editor.js",
+ "line": 30,
+ "symbol": "change(input)"
+ },
+ "implementation": "本轮修复",
+ "boundary": "支持线性、平滑、缓入、缓出、阶梯;没有 Bezier 切线 UI。",
+ "fix": "阶梯插值与已有下游时间轴求值一致。",
+ "acceptance": "修改插值改变中间帧,重开保留。",
+ "verification": "PASS",
+ "evidence": [
+ {
+ "ids": [
+ "timeline.play",
+ "timeline.stop",
+ "timeline.loop",
+ "timeline.key-nav",
+ "timeline.add-track",
+ "timeline.remove-track",
+ "timeline.target",
+ "timeline.property",
+ "timeline.mute",
+ "timeline.add-key",
+ "timeline.delete-key",
+ "timeline.drag-key",
+ "timeline.zoom",
+ "timeline.curve",
+ "curve.time",
+ "curve.value",
+ "curve.easing"
+ ],
+ "file": "evidence/truck-timeline-curve.json",
+ "status": "PASS",
+ "testTitle": "时间轴真实控件:轨道/关键帧/播放与曲线 5 插值,保存后重新加载一致",
+ "recordedAt": "2026-09-06T08:21:02.898Z",
+ "kind": "e2e+runtime-read",
+ "title": "时间轴真实控件:轨道/关键帧/播放与曲线 5 插值,保存后重新加载一致",
+ "screenshots": [],
+ "details": "真实控件和指针操作,运行时读取数值校验;curve.axis仅切换无数值独立断言,timeline.seek播放头与Alt吸附未在本例覆盖。"
+ },
+ {
+ "ids": [
+ "timeline.curve",
+ "curve.time",
+ "curve.value",
+ "curve.easing"
+ ],
+ "file": "validation.json",
+ "status": "PASS",
+ "kind": "unit",
+ "title": "curve edits real scalar key values, time and interpolation, persisting through document reload",
+ "screenshots": [],
+ "details": "真实曲线控件与 TimelineEditor,标量编辑与文档往返。"
+ }
+ ]
+ },
+ {
+ "id": "curve.axis",
+ "group": "时间轴与曲线",
+ "name": "曲线 XYZ 分量显示",
+ "source": {
+ "file": "../../../unreal_tran/src/model-curve-editor.js",
+ "line": 30,
+ "symbol": "change(input)"
+ },
+ "implementation": "已有实现",
+ "boundary": "改变图上显示分量,不改另外两轴的数据。",
+ "fix": "当前实际向量轨道才出现。",
+ "acceptance": "切分量显示不同曲线,数值不被改写。",
+ "verification": "PASS",
+ "evidence": [
+ {
+ "ids": [
+ "timeline.seek",
+ "timeline.record",
+ "timeline.baseline",
+ "curve.axis",
+ "blueprint.palette",
+ "blueprint.add",
+ "blueprint.canvas"
+ ],
+ "file": "evidence/truck-blueprint-tool-supplement.json",
+ "status": "PASS",
+ "testTitle": "补充时间轴与蓝图工具:刻度/按键/记录/分量、基准匹配、节点搜索拖入与画布平移",
+ "recordedAt": "2026-09-06T08:21:23.003Z",
+ "kind": "e2e+runtime-read",
+ "title": "补充时间轴与蓝图工具:刻度/按键/记录/分量、基准匹配、节点搜索拖入与画布平移",
+ "screenshots": [],
+ "details": "真实模型编辑参数/基准轨道生成验收,不构成装备操作或维修指导。桥梁原工程94仅GET和copy。"
+ }
+ ]
+ },
+ {
+ "id": "blueprint.palette",
+ "group": "蓝图与交互反馈",
+ "name": "节点库搜索 / 拖入节点",
+ "source": {
+ "file": "../../../unreal_tran/src/blueprint.js",
+ "line": 287,
+ "symbol": "_renderPalette"
+ },
+ "implementation": "已有实现",
+ "boundary": "当前七类节点:触发、时间轴、等待、高亮、提示、条件、完成。",
+ "fix": "不新增任意脚本节点。",
+ "acceptance": "搜索、拖入后节点类型和参数可保存。",
+ "verification": "PASS",
+ "evidence": [
+ {
+ "ids": [
+ "timeline.seek",
+ "timeline.record",
+ "timeline.baseline",
+ "curve.axis",
+ "blueprint.palette",
+ "blueprint.add",
+ "blueprint.canvas"
+ ],
+ "file": "evidence/truck-blueprint-tool-supplement.json",
+ "status": "PASS",
+ "testTitle": "补充时间轴与蓝图工具:刻度/按键/记录/分量、基准匹配、节点搜索拖入与画布平移",
+ "recordedAt": "2026-09-06T08:21:23.003Z",
+ "kind": "e2e+runtime-read",
+ "title": "补充时间轴与蓝图工具:刻度/按键/记录/分量、基准匹配、节点搜索拖入与画布平移",
+ "screenshots": [],
+ "details": "真实模型编辑参数/基准轨道生成验收,不构成装备操作或维修指导。桥梁原工程94仅GET和copy。"
+ }
+ ]
+ },
+ {
+ "id": "blueprint.add",
+ "group": "蓝图与交互反馈",
+ "name": "添加节点按钮",
+ "source": {
+ "file": "../../../unreal_tran/src/blueprint.js",
+ "line": 257,
+ "symbol": "_bind()"
+ },
+ "implementation": "已有实现",
+ "boundary": "工具栏默认添加提示节点;其他类型来自节点库。",
+ "fix": "无本轮专门改动。",
+ "acceptance": "新增进入画布、历史与 JSON。",
+ "verification": "PASS",
+ "evidence": [
+ {
+ "ids": [
+ "timeline.seek",
+ "timeline.record",
+ "timeline.baseline",
+ "curve.axis",
+ "blueprint.palette",
+ "blueprint.add",
+ "blueprint.canvas"
+ ],
+ "file": "evidence/truck-blueprint-tool-supplement.json",
+ "status": "PASS",
+ "testTitle": "补充时间轴与蓝图工具:刻度/按键/记录/分量、基准匹配、节点搜索拖入与画布平移",
+ "recordedAt": "2026-09-06T08:21:23.003Z",
+ "kind": "e2e+runtime-read",
+ "title": "补充时间轴与蓝图工具:刻度/按键/记录/分量、基准匹配、节点搜索拖入与画布平移",
+ "screenshots": [],
+ "details": "真实模型编辑参数/基准轨道生成验收,不构成装备操作或维修指导。桥梁原工程94仅GET和copy。"
+ }
+ ]
+ },
+ {
+ "id": "blueprint.parameters",
+ "group": "蓝图与交互反馈",
+ "name": "节点标题 / 参数编辑",
+ "source": {
+ "file": "../../../unreal_tran/src/blueprint.js",
+ "line": 431,
+ "symbol": "_renderInspector"
+ },
+ "implementation": "已有实现",
+ "boundary": "按节点类型提供数字、文字、枚举、复选框。",
+ "fix": "保留数值边界与真实数据写回。",
+ "acceptance": "改参数与标题后重开保留。",
+ "verification": "PASS",
+ "evidence": [
+ {
+ "ids": [
+ "blueprint.parameters",
+ "blueprint.move",
+ "blueprint.connect",
+ "blueprint.delete",
+ "blueprint.copy",
+ "blueprint.run",
+ "blueprint.stop",
+ "blueprint.reset",
+ "blueprint.timeline",
+ "blueprint.wait",
+ "blueprint.highlight",
+ "blueprint.message",
+ "blueprint.finish",
+ "project.undo",
+ "project.redo"
+ ],
+ "file": "evidence/truck-blueprint-ui.json",
+ "status": "PASS",
+ "testTitle": "蓝图真实节点库与端口拖线:七种参数、内部边复制/剪切、撤销重做及保存",
+ "recordedAt": "2026-09-06T08:21:37.144Z",
+ "kind": "e2e+runtime-read",
+ "title": "蓝图真实节点库与端口拖线:七种参数、内部边复制/剪切、撤销重做及保存",
+ "screenshots": [],
+ "details": "节点库点选添加及实际拖动/接线/执行;不把点选当成节点拖入,不把适应画布当成鼠标平移缩放全部已验。"
+ }
+ ]
+ },
+ {
+ "id": "blueprint.move",
+ "group": "蓝图与交互反馈",
+ "name": "节点拖动 / 多选框选",
+ "source": {
+ "file": "../../../unreal_tran/src/blueprint.js",
+ "line": 566,
+ "symbol": "_onViewportPointerDown"
+ },
+ "implementation": "已有实现",
+ "boundary": "节点布局随蓝图 JSON 保存。",
+ "fix": "无本轮专门改动。",
+ "acceptance": "多选移动位置正确,撤销恢复。",
+ "verification": "PASS",
+ "evidence": [
+ {
+ "ids": [
+ "blueprint.parameters",
+ "blueprint.move",
+ "blueprint.connect",
+ "blueprint.delete",
+ "blueprint.copy",
+ "blueprint.run",
+ "blueprint.stop",
+ "blueprint.reset",
+ "blueprint.timeline",
+ "blueprint.wait",
+ "blueprint.highlight",
+ "blueprint.message",
+ "blueprint.finish",
+ "project.undo",
+ "project.redo"
+ ],
+ "file": "evidence/truck-blueprint-ui.json",
+ "status": "PASS",
+ "testTitle": "蓝图真实节点库与端口拖线:七种参数、内部边复制/剪切、撤销重做及保存",
+ "recordedAt": "2026-09-06T08:21:37.144Z",
+ "kind": "e2e+runtime-read",
+ "title": "蓝图真实节点库与端口拖线:七种参数、内部边复制/剪切、撤销重做及保存",
+ "screenshots": [],
+ "details": "节点库点选添加及实际拖动/接线/执行;不把点选当成节点拖入,不把适应画布当成鼠标平移缩放全部已验。"
+ }
+ ]
+ },
+ {
+ "id": "blueprint.connect",
+ "group": "蓝图与交互反馈",
+ "name": "连接 / 替换 / 删除执行线",
+ "source": {
+ "file": "../../../unreal_tran/src/blueprint.js",
+ "line": 767,
+ "symbol": "connect("
+ },
+ "implementation": "本轮修复",
+ "boundary": "单输出重连替换;允许多个来源汇聚同一输入。",
+ "fix": "不再清掉同一输入的其他合法来源。",
+ "acceptance": "true/false 汇聚保留两条,替换输出只影响该线。",
+ "verification": "PASS",
+ "evidence": [
+ {
+ "ids": [
+ "blueprint.parameters",
+ "blueprint.move",
+ "blueprint.connect",
+ "blueprint.delete",
+ "blueprint.copy",
+ "blueprint.run",
+ "blueprint.stop",
+ "blueprint.reset",
+ "blueprint.timeline",
+ "blueprint.wait",
+ "blueprint.highlight",
+ "blueprint.message",
+ "blueprint.finish",
+ "project.undo",
+ "project.redo"
+ ],
+ "file": "evidence/truck-blueprint-ui.json",
+ "status": "PASS",
+ "testTitle": "蓝图真实节点库与端口拖线:七种参数、内部边复制/剪切、撤销重做及保存",
+ "recordedAt": "2026-09-06T08:21:37.144Z",
+ "kind": "e2e+runtime-read",
+ "title": "蓝图真实节点库与端口拖线:七种参数、内部边复制/剪切、撤销重做及保存",
+ "screenshots": [],
+ "details": "节点库点选添加及实际拖动/接线/执行;不把点选当成节点拖入,不把适应画布当成鼠标平移缩放全部已验。"
+ }
+ ]
+ },
+ {
+ "id": "blueprint.delete",
+ "group": "蓝图与交互反馈",
+ "name": "删除节点 / 连线",
+ "source": {
+ "file": "../../../unreal_tran/src/blueprint.js",
+ "line": 786,
+ "symbol": "deleteSelection"
+ },
+ "implementation": "已有实现",
+ "boundary": "删除节点一并清理相关边。",
+ "fix": "复制/剪切使用同一图数据合同。",
+ "acceptance": "无悬空边;撤销恢复完整图。",
+ "verification": "PASS",
+ "evidence": [
+ {
+ "ids": [
+ "blueprint.parameters",
+ "blueprint.move",
+ "blueprint.connect",
+ "blueprint.delete",
+ "blueprint.copy",
+ "blueprint.run",
+ "blueprint.stop",
+ "blueprint.reset",
+ "blueprint.timeline",
+ "blueprint.wait",
+ "blueprint.highlight",
+ "blueprint.message",
+ "blueprint.finish",
+ "project.undo",
+ "project.redo"
+ ],
+ "file": "evidence/truck-blueprint-ui.json",
+ "status": "PASS",
+ "testTitle": "蓝图真实节点库与端口拖线:七种参数、内部边复制/剪切、撤销重做及保存",
+ "recordedAt": "2026-09-06T08:21:37.144Z",
+ "kind": "e2e+runtime-read",
+ "title": "蓝图真实节点库与端口拖线:七种参数、内部边复制/剪切、撤销重做及保存",
+ "screenshots": [],
+ "details": "节点库点选添加及实际拖动/接线/执行;不把点选当成节点拖入,不把适应画布当成鼠标平移缩放全部已验。"
+ }
+ ]
+ },
+ {
+ "id": "blueprint.copy",
+ "group": "蓝图与交互反馈",
+ "name": "复制 / 剪切 / 粘贴",
+ "source": {
+ "file": "../../../unreal_tran/src/blueprint.js",
+ "line": 714,
+ "symbol": "copySelection"
+ },
+ "implementation": "本轮修复",
+ "boundary": "Ctrl/Cmd+C/X/V;复制选区内部边,不复制指向外部的边。",
+ "fix": "新节点 ID 和内部连线同步生成,阻止快捷键冒泡。",
+ "acceptance": "副本内部连接正确,不误删外部对象。",
+ "verification": "PASS",
+ "evidence": [
+ {
+ "ids": [
+ "blueprint.parameters",
+ "blueprint.move",
+ "blueprint.connect",
+ "blueprint.delete",
+ "blueprint.copy",
+ "blueprint.run",
+ "blueprint.stop",
+ "blueprint.reset",
+ "blueprint.timeline",
+ "blueprint.wait",
+ "blueprint.highlight",
+ "blueprint.message",
+ "blueprint.finish",
+ "project.undo",
+ "project.redo"
+ ],
+ "file": "evidence/truck-blueprint-ui.json",
+ "status": "PASS",
+ "testTitle": "蓝图真实节点库与端口拖线:七种参数、内部边复制/剪切、撤销重做及保存",
+ "recordedAt": "2026-09-06T08:21:37.144Z",
+ "kind": "e2e+runtime-read",
+ "title": "蓝图真实节点库与端口拖线:七种参数、内部边复制/剪切、撤销重做及保存",
+ "screenshots": [],
+ "details": "节点库点选添加及实际拖动/接线/执行;不把点选当成节点拖入,不把适应画布当成鼠标平移缩放全部已验。"
+ }
+ ]
+ },
+ {
+ "id": "blueprint.canvas",
+ "group": "蓝图与交互反馈",
+ "name": "平移 / 缩放 / 适应画布",
+ "source": {
+ "file": "../../../unreal_tran/src/blueprint.js",
+ "line": 801,
+ "symbol": "frameAll"
+ },
+ "implementation": "已有实现",
+ "boundary": "蓝图画布浏览;不是三维相机。",
+ "fix": "无本轮专门改动。",
+ "acceptance": "滚轮、Space 拖动和适应可见全部节点。",
+ "verification": "PASS",
+ "evidence": [
+ {
+ "ids": [
+ "timeline.seek",
+ "timeline.record",
+ "timeline.baseline",
+ "curve.axis",
+ "blueprint.palette",
+ "blueprint.add",
+ "blueprint.canvas"
+ ],
+ "file": "evidence/truck-blueprint-tool-supplement.json",
+ "status": "PASS",
+ "testTitle": "补充时间轴与蓝图工具:刻度/按键/记录/分量、基准匹配、节点搜索拖入与画布平移",
+ "recordedAt": "2026-09-06T08:21:23.003Z",
+ "kind": "e2e+runtime-read",
+ "title": "补充时间轴与蓝图工具:刻度/按键/记录/分量、基准匹配、节点搜索拖入与画布平移",
+ "screenshots": [],
+ "details": "真实模型编辑参数/基准轨道生成验收,不构成装备操作或维修指导。桥梁原工程94仅GET和copy。"
+ }
+ ]
+ },
+ {
+ "id": "blueprint.run",
+ "group": "蓝图与交互反馈",
+ "name": "运行 / F5",
+ "source": {
+ "file": "../../../unreal_tran/src/blueprint.js",
+ "line": 871,
+ "symbol": "async run("
+ },
+ "implementation": "本轮修复",
+ "boundary": "在当前模型编辑器驱动真实场景;有 200 步循环上限。",
+ "fix": "条件与等待流程回归。",
+ "acceptance": "执行高亮、动作、反馈;循环图可终止不挂死。",
+ "verification": "PASS",
+ "evidence": [
+ {
+ "ids": [
+ "blueprint.parameters",
+ "blueprint.move",
+ "blueprint.connect",
+ "blueprint.delete",
+ "blueprint.copy",
+ "blueprint.run",
+ "blueprint.stop",
+ "blueprint.reset",
+ "blueprint.timeline",
+ "blueprint.wait",
+ "blueprint.highlight",
+ "blueprint.message",
+ "blueprint.finish",
+ "project.undo",
+ "project.redo"
+ ],
+ "file": "evidence/truck-blueprint-ui.json",
+ "status": "PASS",
+ "testTitle": "蓝图真实节点库与端口拖线:七种参数、内部边复制/剪切、撤销重做及保存",
+ "recordedAt": "2026-09-06T08:21:37.144Z",
+ "kind": "e2e+runtime-read",
+ "title": "蓝图真实节点库与端口拖线:七种参数、内部边复制/剪切、撤销重做及保存",
+ "screenshots": [],
+ "details": "节点库点选添加及实际拖动/接线/执行;不把点选当成节点拖入,不把适应画布当成鼠标平移缩放全部已验。"
+ }
+ ]
+ },
+ {
+ "id": "blueprint.stop",
+ "group": "蓝图与交互反馈",
+ "name": "停止运行",
+ "source": {
+ "file": "../../../unreal_tran/src/blueprint.js",
+ "line": 997,
+ "symbol": "stop("
+ },
+ "implementation": "本轮修复",
+ "boundary": "停止中断挂起等待,旧运行不覆盖新运行状态。",
+ "fix": "AbortSignal 贯穿宿主动作与高亮清理。",
+ "acceptance": "等待/动画中停止及时退出,无迟到完成提示。",
+ "verification": "PASS",
+ "evidence": [
+ {
+ "ids": [
+ "blueprint.parameters",
+ "blueprint.move",
+ "blueprint.connect",
+ "blueprint.delete",
+ "blueprint.copy",
+ "blueprint.run",
+ "blueprint.stop",
+ "blueprint.reset",
+ "blueprint.timeline",
+ "blueprint.wait",
+ "blueprint.highlight",
+ "blueprint.message",
+ "blueprint.finish",
+ "project.undo",
+ "project.redo"
+ ],
+ "file": "evidence/truck-blueprint-ui.json",
+ "status": "PASS",
+ "testTitle": "蓝图真实节点库与端口拖线:七种参数、内部边复制/剪切、撤销重做及保存",
+ "recordedAt": "2026-09-06T08:21:37.144Z",
+ "kind": "e2e+runtime-read",
+ "title": "蓝图真实节点库与端口拖线:七种参数、内部边复制/剪切、撤销重做及保存",
+ "screenshots": [],
+ "details": "节点库点选添加及实际拖动/接线/执行;不把点选当成节点拖入,不把适应画布当成鼠标平移缩放全部已验。"
+ }
+ ]
+ },
+ {
+ "id": "blueprint.reset",
+ "group": "蓝图与交互反馈",
+ "name": "重置执行",
+ "source": {
+ "file": "../../../unreal_tran/src/blueprint.js",
+ "line": 1014,
+ "symbol": "resetExecution"
+ },
+ "implementation": "已有实现",
+ "boundary": "复位执行状态、时间轴和高亮;保留蓝图编辑内容。",
+ "fix": "高亮恢复原材质。",
+ "acceptance": "运行后重置,进度归零且节点图不丢。",
+ "verification": "PASS",
+ "evidence": [
+ {
+ "ids": [
+ "blueprint.parameters",
+ "blueprint.move",
+ "blueprint.connect",
+ "blueprint.delete",
+ "blueprint.copy",
+ "blueprint.run",
+ "blueprint.stop",
+ "blueprint.reset",
+ "blueprint.timeline",
+ "blueprint.wait",
+ "blueprint.highlight",
+ "blueprint.message",
+ "blueprint.finish",
+ "project.undo",
+ "project.redo"
+ ],
+ "file": "evidence/truck-blueprint-ui.json",
+ "status": "PASS",
+ "testTitle": "蓝图真实节点库与端口拖线:七种参数、内部边复制/剪切、撤销重做及保存",
+ "recordedAt": "2026-09-06T08:21:37.144Z",
+ "kind": "e2e+runtime-read",
+ "title": "蓝图真实节点库与端口拖线:七种参数、内部边复制/剪切、撤销重做及保存",
+ "screenshots": [],
+ "details": "节点库点选添加及实际拖动/接线/执行;不把点选当成节点拖入,不把适应画布当成鼠标平移缩放全部已验。"
+ }
+ ]
+ },
+ {
+ "id": "blueprint.click",
+ "group": "蓝图与交互反馈",
+ "name": "点击触发目标",
+ "source": {
+ "file": "../../../unreal_tran/src/blueprint.js",
+ "line": 933,
+ "symbol": "async trigger("
+ },
+ "implementation": "本轮修复",
+ "boundary": "匹配事件和目标 ID/名称/祖先别名;空目标可通配。",
+ "fix": "不再点击 B 触发 A。",
+ "acceptance": "两个不同目标分别点击,只运行匹配分支。",
+ "verification": "PASS",
+ "evidence": [
+ {
+ "ids": [
+ "blueprint.click",
+ "blueprint.double-click",
+ "blueprint.auto",
+ "view.preview"
+ ],
+ "file": "evidence/truck-blueprint-events.json",
+ "status": "PASS",
+ "testTitle": "已保存蓝图:条件出口与目标匹配 runtime 检查,真实模型单击/双击/进入预览自动触发",
+ "recordedAt": "2026-09-06T08:21:50.227Z",
+ "kind": "e2e",
+ "title": "已保存蓝图:条件出口与目标匹配 runtime 检查,真实模型单击/双击/进入预览自动触发",
+ "screenshots": [],
+ "details": "执行使用正式模型编辑器的真实模型/时间轴/高亮/提示 handler;不等同于学员端任务下发。"
+ }
+ ]
+ },
+ {
+ "id": "blueprint.double-click",
+ "group": "蓝图与交互反馈",
+ "name": "双击触发目标",
+ "source": {
+ "file": "../../../unreal_tran/src/main.js",
+ "line": 2256,
+ "symbol": "pickObject"
+ },
+ "implementation": "本轮修复",
+ "boundary": "双击独立派发 double-click,与单击区分。",
+ "fix": "增加真实视口双击派发并抑制待发单击。",
+ "acceptance": "双击只触发目标的双击分支。",
+ "verification": "PASS",
+ "evidence": [
+ {
+ "ids": [
+ "blueprint.click",
+ "blueprint.double-click",
+ "blueprint.auto",
+ "view.preview"
+ ],
+ "file": "evidence/truck-blueprint-events.json",
+ "status": "PASS",
+ "testTitle": "已保存蓝图:条件出口与目标匹配 runtime 检查,真实模型单击/双击/进入预览自动触发",
+ "recordedAt": "2026-09-06T08:21:50.227Z",
+ "kind": "e2e",
+ "title": "已保存蓝图:条件出口与目标匹配 runtime 检查,真实模型单击/双击/进入预览自动触发",
+ "screenshots": [],
+ "details": "执行使用正式模型编辑器的真实模型/时间轴/高亮/提示 handler;不等同于学员端任务下发。"
+ }
+ ]
+ },
+ {
+ "id": "blueprint.auto",
+ "group": "蓝图与交互反馈",
+ "name": "自动触发",
+ "source": {
+ "file": "../../../unreal_tran/src/main.js",
+ "line": 2284,
+ "symbol": "togglePreview"
+ },
+ "implementation": "本轮修复",
+ "boundary": "仅进入预览显式派发 auto,不把每次单击当 auto。",
+ "fix": "事件匹配与进入预览联动。",
+ "acceptance": "进入预览触发一次,普通点击不重放自动分支。",
+ "verification": "PASS",
+ "evidence": [
+ {
+ "ids": [
+ "blueprint.click",
+ "blueprint.double-click",
+ "blueprint.auto",
+ "view.preview"
+ ],
+ "file": "evidence/truck-blueprint-events.json",
+ "status": "PASS",
+ "testTitle": "已保存蓝图:条件出口与目标匹配 runtime 检查,真实模型单击/双击/进入预览自动触发",
+ "recordedAt": "2026-09-06T08:21:50.227Z",
+ "kind": "e2e",
+ "title": "已保存蓝图:条件出口与目标匹配 runtime 检查,真实模型单击/双击/进入预览自动触发",
+ "screenshots": [],
+ "details": "执行使用正式模型编辑器的真实模型/时间轴/高亮/提示 handler;不等同于学员端任务下发。"
+ }
+ ]
+ },
+ {
+ "id": "blueprint.timeline",
+ "group": "蓝图与交互反馈",
+ "name": "播放时间轴节点",
+ "source": {
+ "file": "../../../unreal_tran/src/main.js",
+ "line": 2024,
+ "symbol": "playTimelineForBlueprint"
+ },
+ "implementation": "本轮修复",
+ "boundary": "播放当前唯一时间轴,动作名称仅描述;支持起始时间、速度、等待。",
+ "fix": "起始末帧不反跳零,终止可取消。",
+ "acceptance": "起始时间和速度实际生效,等待与不等待分支正确。",
+ "verification": "PASS",
+ "evidence": [
+ {
+ "ids": [
+ "blueprint.parameters",
+ "blueprint.move",
+ "blueprint.connect",
+ "blueprint.delete",
+ "blueprint.copy",
+ "blueprint.run",
+ "blueprint.stop",
+ "blueprint.reset",
+ "blueprint.timeline",
+ "blueprint.wait",
+ "blueprint.highlight",
+ "blueprint.message",
+ "blueprint.finish",
+ "project.undo",
+ "project.redo"
+ ],
+ "file": "evidence/truck-blueprint-ui.json",
+ "status": "PASS",
+ "testTitle": "蓝图真实节点库与端口拖线:七种参数、内部边复制/剪切、撤销重做及保存",
+ "recordedAt": "2026-09-06T08:21:37.144Z",
+ "kind": "e2e+runtime-read",
+ "title": "蓝图真实节点库与端口拖线:七种参数、内部边复制/剪切、撤销重做及保存",
+ "screenshots": [],
+ "details": "节点库点选添加及实际拖动/接线/执行;不把点选当成节点拖入,不把适应画布当成鼠标平移缩放全部已验。"
+ }
+ ]
+ },
+ {
+ "id": "blueprint.wait",
+ "group": "蓝图与交互反馈",
+ "name": "等待节点",
+ "source": {
+ "file": "../../../unreal_tran/src/blueprint.js",
+ "line": 1,
+ "symbol": "NODE_TYPES"
+ },
+ "implementation": "已有实现",
+ "boundary": "指定毫秒等待;可被停止取消。",
+ "fix": "异步停止及时结束。",
+ "acceptance": "等待期间停止不会继续执行后续节点。",
+ "verification": "PASS",
+ "evidence": [
+ {
+ "ids": [
+ "blueprint.parameters",
+ "blueprint.move",
+ "blueprint.connect",
+ "blueprint.delete",
+ "blueprint.copy",
+ "blueprint.run",
+ "blueprint.stop",
+ "blueprint.reset",
+ "blueprint.timeline",
+ "blueprint.wait",
+ "blueprint.highlight",
+ "blueprint.message",
+ "blueprint.finish",
+ "project.undo",
+ "project.redo"
+ ],
+ "file": "evidence/truck-blueprint-ui.json",
+ "status": "PASS",
+ "testTitle": "蓝图真实节点库与端口拖线:七种参数、内部边复制/剪切、撤销重做及保存",
+ "recordedAt": "2026-09-06T08:21:37.144Z",
+ "kind": "e2e+runtime-read",
+ "title": "蓝图真实节点库与端口拖线:七种参数、内部边复制/剪切、撤销重做及保存",
+ "screenshots": [],
+ "details": "节点库点选添加及实际拖动/接线/执行;不把点选当成节点拖入,不把适应画布当成鼠标平移缩放全部已验。"
+ }
+ ]
+ },
+ {
+ "id": "blueprint.highlight",
+ "group": "蓝图与交互反馈",
+ "name": "高亮节点 / 呼吸效果",
+ "source": {
+ "file": "../../../unreal_tran/src/main.js",
+ "line": 2052,
+ "symbol": "highlightPart"
+ },
+ "implementation": "本轮修复",
+ "boundary": "准确命中可见对象;同名歧义要求标识;没有可高亮材质则报错。",
+ "fix": "拒绝错误整机回退,真实呼吸效果,保存忽略临时高亮。",
+ "acceptance": "目标正确、找不到报错、结束恢复、保存不永久染色。",
+ "verification": "PASS",
+ "evidence": [
+ {
+ "ids": [
+ "blueprint.parameters",
+ "blueprint.move",
+ "blueprint.connect",
+ "blueprint.delete",
+ "blueprint.copy",
+ "blueprint.run",
+ "blueprint.stop",
+ "blueprint.reset",
+ "blueprint.timeline",
+ "blueprint.wait",
+ "blueprint.highlight",
+ "blueprint.message",
+ "blueprint.finish",
+ "project.undo",
+ "project.redo"
+ ],
+ "file": "evidence/truck-blueprint-ui.json",
+ "status": "PASS",
+ "testTitle": "蓝图真实节点库与端口拖线:七种参数、内部边复制/剪切、撤销重做及保存",
+ "recordedAt": "2026-09-06T08:21:37.144Z",
+ "kind": "e2e+runtime-read",
+ "title": "蓝图真实节点库与端口拖线:七种参数、内部边复制/剪切、撤销重做及保存",
+ "screenshots": [],
+ "details": "节点库点选添加及实际拖动/接线/执行;不把点选当成节点拖入,不把适应画布当成鼠标平移缩放全部已验。"
+ },
+ {
+ "ids": [
+ "tree.visibility",
+ "blueprint.highlight"
+ ],
+ "file": "validation.json",
+ "status": "PASS",
+ "kind": "unit",
+ "title": "visible child under a hidden ancestor cannot be picked or highlighted; trigger aliases include ancestors",
+ "screenshots": [],
+ "details": "共用可见性和祖先别名函数;鼠标事件另由 E2E 验证。"
+ },
+ {
+ "ids": [
+ "blueprint.highlight"
+ ],
+ "file": "validation.json",
+ "status": "PASS",
+ "kind": "unit",
+ "title": "highlight rejects missing/hidden targets and transient colors never enter a saved project",
+ "screenshots": [],
+ "details": "目标不存在/隐藏报错,执行时暂态发光颜色不进入保存文档,结束恢复原材质。"
+ }
+ ]
+ },
+ {
+ "id": "blueprint.message",
+ "group": "蓝图与交互反馈",
+ "name": "操作提示节点",
+ "source": {
+ "file": "../../../unreal_tran/src/main.js",
+ "line": 2099,
+ "symbol": "showTeachingMessage"
+ },
+ "implementation": "现有边界",
+ "boundary": "显示标题/文本,可点击关闭或约 1.8/2.5 秒自动继续;不是强制人员确认。",
+ "fix": "不将其当作业务审核/签名。",
+ "acceptance": "文本出现、关闭和停止不留弹层。",
+ "verification": "PASS",
+ "evidence": [
+ {
+ "ids": [
+ "blueprint.parameters",
+ "blueprint.move",
+ "blueprint.connect",
+ "blueprint.delete",
+ "blueprint.copy",
+ "blueprint.run",
+ "blueprint.stop",
+ "blueprint.reset",
+ "blueprint.timeline",
+ "blueprint.wait",
+ "blueprint.highlight",
+ "blueprint.message",
+ "blueprint.finish",
+ "project.undo",
+ "project.redo"
+ ],
+ "file": "evidence/truck-blueprint-ui.json",
+ "status": "PASS",
+ "testTitle": "蓝图真实节点库与端口拖线:七种参数、内部边复制/剪切、撤销重做及保存",
+ "recordedAt": "2026-09-06T08:21:37.144Z",
+ "kind": "e2e+runtime-read",
+ "title": "蓝图真实节点库与端口拖线:七种参数、内部边复制/剪切、撤销重做及保存",
+ "screenshots": [],
+ "details": "节点库点选添加及实际拖动/接线/执行;不把点选当成节点拖入,不把适应画布当成鼠标平移缩放全部已验。"
+ }
+ ]
+ },
+ {
+ "id": "blueprint.condition",
+ "group": "蓝图与交互反馈",
+ "name": "条件判断节点",
+ "source": {
+ "file": "../../../unreal_tran/src/main.js",
+ "line": 2095,
+ "symbol": "evaluateBlueprintCondition"
+ },
+ "implementation": "本轮修复",
+ "boundary": "当前运行变量、嵌套字段、truthy/布尔/数字比较。",
+ "fix": "false 没有出口不走 true,字符串布尔正确解析。",
+ "acceptance": "真假分支分别验证,缺出口不误执行。",
+ "verification": "PASS",
+ "evidence": [
+ {
+ "ids": [
+ "blueprint.condition"
+ ],
+ "file": "evidence/truck-blueprint-events.json",
+ "status": "PASS",
+ "testTitle": "已保存蓝图:条件出口与目标匹配 runtime 检查,真实模型单击/双击/进入预览自动触发",
+ "recordedAt": "2026-09-06T08:21:50.227Z",
+ "kind": "runtime",
+ "title": "已保存蓝图:条件出口与目标匹配 runtime 检查,真实模型单击/双击/进入预览自动触发",
+ "screenshots": [],
+ "details": ""
+ },
+ {
+ "ids": [
+ "blueprint.condition"
+ ],
+ "file": "validation.json",
+ "status": "PASS",
+ "kind": "unit",
+ "title": "boolean, numeric and nested blueprint conditions select their true/false branches",
+ "screenshots": [],
+ "details": "布尔、数字、truthy 与嵌套路径值解析。"
+ }
+ ]
+ },
+ {
+ "id": "blueprint.finish",
+ "group": "蓝图与交互反馈",
+ "name": "流程完成节点",
+ "source": {
+ "file": "../../../unreal_tran/src/main.js",
+ "line": 1075,
+ "symbol": "initEditors"
+ },
+ "implementation": "已有实现",
+ "boundary": "本次预览结果/进度提示,不写教学任务成绩。",
+ "fix": "保持本次运行反馈边界。",
+ "acceptance": "成功/完成提示与实际执行结果一致。",
+ "verification": "PASS",
+ "evidence": [
+ {
+ "ids": [
+ "blueprint.parameters",
+ "blueprint.move",
+ "blueprint.connect",
+ "blueprint.delete",
+ "blueprint.copy",
+ "blueprint.run",
+ "blueprint.stop",
+ "blueprint.reset",
+ "blueprint.timeline",
+ "blueprint.wait",
+ "blueprint.highlight",
+ "blueprint.message",
+ "blueprint.finish",
+ "project.undo",
+ "project.redo"
+ ],
+ "file": "evidence/truck-blueprint-ui.json",
+ "status": "PASS",
+ "testTitle": "蓝图真实节点库与端口拖线:七种参数、内部边复制/剪切、撤销重做及保存",
+ "recordedAt": "2026-09-06T08:21:37.144Z",
+ "kind": "e2e+runtime-read",
+ "title": "蓝图真实节点库与端口拖线:七种参数、内部边复制/剪切、撤销重做及保存",
+ "screenshots": [],
+ "details": "节点库点选添加及实际拖动/接线/执行;不把点选当成节点拖入,不把适应画布当成鼠标平移缩放全部已验。"
+ }
+ ]
+ },
+ {
+ "id": "publish.preflight",
+ "group": "发布、交付与运行信息",
+ "name": "生成 / 发布检查面板",
+ "source": {
+ "file": "../../../unreal_tran/src/main.js",
+ "line": 2978,
+ "symbol": "togglePublishPanel"
+ },
+ "implementation": "现有边界",
+ "boundary": "本地主要检查资源引用存在及轨道/节点数量,不执行完整蓝图可达性、终止性或业务校验;服务端发布校验最终生效。",
+ "fix": "发布目标与离线交付能力明确。",
+ "acceptance": "可打开关闭;不完整工程不能凭界面绿标绕过服务端。",
+ "verification": "PASS",
+ "evidence": [
+ {
+ "ids": [
+ "resource.loading",
+ "publish.preflight"
+ ],
+ "file": "evidence/63-loading-publish-preflight.json",
+ "status": "PASS",
+ "testTitle": "受控 GLB 下载失败阻止编辑,重试成功;发布预检开关无写入且拒绝错误资源 JSON",
+ "recordedAt": "2026-09-06T08:24:17.025Z",
+ "kind": "e2e+api",
+ "title": "受控 GLB 下载失败阻止编辑,重试成功;发布预检开关无写入且拒绝错误资源 JSON",
+ "screenshots": [],
+ "details": "真实受控下载定向503→中性失败与禁止编辑→解除故障后UI重试成功;预检面板开关不写项目,错误资源JSON在替换当前模型之前被拒绝。服务端发布时code/URI一致性拒绝由ContentProjectServiceTest合同回归覆盖,未把面板绿标代替服务端校验。"
+ }
+ ]
+ },
+ {
+ "id": "publish.api",
+ "group": "发布、交付与运行信息",
+ "name": "发布到场景资源库",
+ "source": {
+ "file": "../../../unreal_tran/src/main.js",
+ "line": 2897,
+ "symbol": "publishToSceneLibrary"
+ },
+ "implementation": "本轮修复",
+ "boundary": "先保存当前工程再发布固定版本;纯 publisher 发布已保存版本。",
+ "fix": "整个保存/发布队列互斥,失败不发后续发布。",
+ "acceptance": "发布版本/模型资产/列表状态一致,保存失败不发布。",
+ "verification": "PASS",
+ "evidence": [
+ {
+ "ids": [
+ "publish.api",
+ "publish.cover",
+ "publish.html",
+ "publish.downstream"
+ ],
+ "file": "evidence/34-publish-fixed-version.json",
+ "status": "PASS",
+ "testTitle": "直接发布同步截图封面、固定版本目录与真实离线步骤 HTML",
+ "recordedAt": "2026-09-06T08:20:51.047Z",
+ "kind": "e2e+api",
+ "title": "直接发布同步截图封面、固定版本目录与真实离线步骤 HTML",
+ "screenshots": [],
+ "details": "目录可取得固定发布版本及同版封面;HTML已真实打开并确认演示边界,未声明下游动画执行或完整离线三维运行。"
+ },
+ {
+ "ids": [
+ "publish.api",
+ "publish.cover",
+ "feedback.toast"
+ ],
+ "file": "validation.json",
+ "status": "PASS",
+ "kind": "unit",
+ "title": "API publish supplies current cover, blocks duplicate publication and reports preserved old cover once",
+ "screenshots": [],
+ "details": "实际发布宿主方法传封面、重入阻断和上传失败只提示一次;模拟 API,不是实际服务响应。"
+ },
+ {
+ "ids": [
+ "project.conflict",
+ "publish.api",
+ "publish.cover"
+ ],
+ "file": "validation.json",
+ "status": "PASS",
+ "kind": "unit",
+ "title": "API conflict leaves publication failed and releases the cover guard",
+ "screenshots": [],
+ "details": "409 分支不误报已发布并释放 busy 标记;模拟 API。"
+ }
+ ]
+ },
+ {
+ "id": "publish.cover",
+ "group": "发布、交付与运行信息",
+ "name": "发布时当前三维截图封面",
+ "source": {
+ "file": "../../../unreal_tran/src/main.js",
+ "line": 2897,
+ "symbol": "publishToSceneLibrary"
+ },
+ "implementation": "本轮修复",
+ "boundary": "JPEG 当前视口;截图或上传失败可保留旧封面并明确提示。",
+ "fix": "与手动保存共用封面链路,409 阻断。",
+ "acceptance": "真实封面图对应当前模型;上传失败提示一次,旧图保留。",
+ "verification": "PASS",
+ "evidence": [
+ {
+ "ids": [
+ "publish.api",
+ "publish.cover",
+ "publish.html",
+ "publish.downstream"
+ ],
+ "file": "evidence/34-publish-fixed-version.json",
+ "status": "PASS",
+ "testTitle": "直接发布同步截图封面、固定版本目录与真实离线步骤 HTML",
+ "recordedAt": "2026-09-06T08:20:51.047Z",
+ "kind": "e2e+api",
+ "title": "直接发布同步截图封面、固定版本目录与真实离线步骤 HTML",
+ "screenshots": [],
+ "details": "目录可取得固定发布版本及同版封面;HTML已真实打开并确认演示边界,未声明下游动画执行或完整离线三维运行。"
+ },
+ {
+ "ids": [
+ "publish.api",
+ "publish.cover",
+ "feedback.toast"
+ ],
+ "file": "validation.json",
+ "status": "PASS",
+ "kind": "unit",
+ "title": "API publish supplies current cover, blocks duplicate publication and reports preserved old cover once",
+ "screenshots": [],
+ "details": "实际发布宿主方法传封面、重入阻断和上传失败只提示一次;模拟 API,不是实际服务响应。"
+ },
+ {
+ "ids": [
+ "project.conflict",
+ "publish.api",
+ "publish.cover"
+ ],
+ "file": "validation.json",
+ "status": "PASS",
+ "kind": "unit",
+ "title": "API conflict leaves publication failed and releases the cover guard",
+ "screenshots": [],
+ "details": "409 分支不误报已发布并释放 busy 标记;模拟 API。"
+ }
+ ]
+ },
+ {
+ "id": "publish.html",
+ "group": "发布、交付与运行信息",
+ "name": "导出步骤演示 HTML",
+ "source": {
+ "file": "../../../unreal_tran/src/main.js",
+ "line": 3011,
+ "symbol": "exportStandaloneHtml"
+ },
+ "implementation": "本轮澄清",
+ "boundary": "工程数据、静态截图和步骤演示;不包含 GLB、不执行真实三维交互。",
+ "fix": "生成面板和按钮移除完整离线运行的误导。",
+ "acceptance": "下载可打开、步骤导航可用,醒目标明不含三维模型。",
+ "verification": "PASS",
+ "evidence": [
+ {
+ "ids": [
+ "publish.api",
+ "publish.cover",
+ "publish.html",
+ "publish.downstream"
+ ],
+ "file": "evidence/34-publish-fixed-version.json",
+ "status": "PASS",
+ "testTitle": "直接发布同步截图封面、固定版本目录与真实离线步骤 HTML",
+ "recordedAt": "2026-09-06T08:20:51.047Z",
+ "kind": "e2e+api",
+ "title": "直接发布同步截图封面、固定版本目录与真实离线步骤 HTML",
+ "screenshots": [],
+ "details": "目录可取得固定发布版本及同版封面;HTML已真实打开并确认演示边界,未声明下游动画执行或完整离线三维运行。"
+ }
+ ]
+ },
+ {
+ "id": "publish.downstream",
+ "group": "发布、交付与运行信息",
+ "name": "下游读取模型固定版本",
+ "source": {
+ "file": "../../../unreal_tran/src/model-content-api.js",
+ "line": 1159,
+ "symbol": "publishDocument"
+ },
+ "implementation": "现有边界",
+ "boundary": "已发布模型作为资源引用;下游自动执行模型 timeline/blueprint 尚无统一运行桥接。",
+ "fix": "本轮不将资源加载成功等同动作自动运行成功。",
+ "acceptance": "固定引用/可见性单独测;自动动作执行列为待确认范围。",
+ "verification": "PASS",
+ "evidence": [
+ {
+ "ids": [
+ "publish.api",
+ "publish.cover",
+ "publish.html",
+ "publish.downstream"
+ ],
+ "file": "evidence/34-publish-fixed-version.json",
+ "status": "PASS",
+ "testTitle": "直接发布同步截图封面、固定版本目录与真实离线步骤 HTML",
+ "recordedAt": "2026-09-06T08:20:51.047Z",
+ "kind": "e2e+api",
+ "title": "直接发布同步截图封面、固定版本目录与真实离线步骤 HTML",
+ "screenshots": [],
+ "details": "目录可取得固定发布版本及同版封面;HTML已真实打开并确认演示边界,未声明下游动画执行或完整离线三维运行。"
+ }
+ ]
+ },
+ {
+ "id": "feedback.log",
+ "group": "发布、交付与运行信息",
+ "name": "运行日志",
+ "source": {
+ "file": "../../../unreal_tran/src/main.js",
+ "line": 3116,
+ "symbol": "log(message"
+ },
+ "implementation": "已有实现",
+ "boundary": "本次内存运行日志,最多 160 条;不是服务端审计日志。",
+ "fix": "错误信息转义,保留清晰运行结果。",
+ "acceptance": "选中/运行/保存产生相应日志,不要求刷新保留。",
+ "verification": "PASS",
+ "evidence": [
+ {
+ "ids": [
+ "project.name",
+ "project.autosave",
+ "view.orbit",
+ "view.focus",
+ "view.panels",
+ "view.preview-step",
+ "feedback.log",
+ "feedback.stats",
+ "property.identity",
+ "property.binding",
+ "selection.scope",
+ "component.inspect"
+ ],
+ "file": "evidence/46-navigation-panels-autosave.json",
+ "status": "PASS",
+ "testTitle": "补充视口与工作区:鼠标镜头、聚焦、面板、预览导航、日志与自动保存",
+ "recordedAt": "2026-09-06T08:23:54.656Z",
+ "kind": "e2e+api",
+ "title": "补充视口与工作区:鼠标镜头、聚焦、面板、预览导航、日志与自动保存",
+ "screenshots": [],
+ "details": "真实鼠标镜头操作;聚焦只验证相机目标;真实30秒自动保存,无测试加速定时器;统计内存估值未当作性能结论。"
+ }
+ ]
+ },
+ {
+ "id": "feedback.stats",
+ "group": "发布、交付与运行信息",
+ "name": "节点 / 三角面 / FPS 等状态",
+ "source": {
+ "file": "../../../unreal_tran/src/main.js",
+ "line": 3094,
+ "symbol": "updateStats"
+ },
+ "implementation": "现有边界",
+ "boundary": "节点与三角面统计、当前 FPS;内存为估算值,不是精确浏览器内存剖析。",
+ "fix": "无本轮性能仪表重构。",
+ "acceptance": "载入模型后统计有合理变化,状态不作为性能验收数据。",
+ "verification": "PASS",
+ "evidence": [
+ {
+ "ids": [
+ "project.name",
+ "project.autosave",
+ "view.orbit",
+ "view.focus",
+ "view.panels",
+ "view.preview-step",
+ "feedback.log",
+ "feedback.stats",
+ "property.identity",
+ "property.binding",
+ "selection.scope",
+ "component.inspect"
+ ],
+ "file": "evidence/46-navigation-panels-autosave.json",
+ "status": "PASS",
+ "testTitle": "补充视口与工作区:鼠标镜头、聚焦、面板、预览导航、日志与自动保存",
+ "recordedAt": "2026-09-06T08:23:54.656Z",
+ "kind": "e2e+api",
+ "title": "补充视口与工作区:鼠标镜头、聚焦、面板、预览导航、日志与自动保存",
+ "screenshots": [],
+ "details": "真实鼠标镜头操作;聚焦只验证相机目标;真实30秒自动保存,无测试加速定时器;统计内存估值未当作性能结论。"
+ }
+ ]
+ },
+ {
+ "id": "feedback.toast",
+ "group": "发布、交付与运行信息",
+ "name": "成功 / 错误短消息",
+ "source": {
+ "file": "../../../unreal_tran/src/main.js",
+ "line": 3117,
+ "symbol": "toast(message"
+ },
+ "implementation": "已有实现",
+ "boundary": "瞬时反馈;保存/发布成功以接口完成为准。",
+ "fix": "封面失败不伪报全部成功。",
+ "acceptance": "失败出现一次明确提示,自动消失,不形成重复常驻错误条。",
+ "verification": "PASS",
+ "evidence": [
+ {
+ "ids": [
+ "project.conflict",
+ "project.save",
+ "feedback.toast"
+ ],
+ "file": "evidence/32-save-failure-recovery.json",
+ "status": "PASS",
+ "testTitle": "保存请求超时和 409 保留恢复副本,解除故障后可重试",
+ "recordedAt": "2026-09-06T08:20:43.214Z",
+ "kind": "e2e+api",
+ "title": "保存请求超时和 409 保留恢复副本,解除故障后可重试",
+ "screenshots": [],
+ "details": "真实页面保存及真实服务端重试;失败由浏览器定向注入 timedout/409,非宣称自然发生的服务故障。"
+ },
+ {
+ "ids": [
+ "publish.api",
+ "publish.cover",
+ "feedback.toast"
+ ],
+ "file": "validation.json",
+ "status": "PASS",
+ "kind": "unit",
+ "title": "API publish supplies current cover, blocks duplicate publication and reports preserved old cover once",
+ "screenshots": [],
+ "details": "实际发布宿主方法传封面、重入阻断和上传失败只提示一次;模拟 API,不是实际服务响应。"
+ }
+ ]
+ }
+ ]
+}
\ No newline at end of file
diff --git a/reports/model-editor-closure-20260906/data/final-corrections.json b/reports/model-editor-closure-20260906/data/final-corrections.json
new file mode 100644
index 0000000..d8d1c06
--- /dev/null
+++ b/reports/model-editor-closure-20260906/data/final-corrections.json
@@ -0,0 +1,43 @@
+{
+ "kind": "implementation-notes",
+ "scope": "补修说明与已确认的聚焦验证;不替代最终完整浏览器结果、后端构建或最终验证记录。",
+ "items": [
+ {
+ "title": "只读模型的搜索、原生动画选择与键盘预览",
+ "problem": "权限桥误把查看控件的键盘交互当成内容编辑,导致只读用户不能在搜索框输入、切换原生动画或按 Enter 进入预览。",
+ "change": "按实际查看控件放行键盘输入/选择/激活;模型编辑、内容上传/保存/发布仍依对应权限阻断。",
+ "validation": "真实 DOM 权限桥 14/14 通过;最终完整19条E2E全部通过,其中管理员用例验证真实键盘搜索、原生动画切换与Enter播放/暂停,期间没有内容写请求,直接创建接口返回403。",
+ "featureIds": [
+ "project.permissions",
+ "resource.search",
+ "tree.search",
+ "timeline.native",
+ "view.preview"
+ ],
+ "source": "../../../unreal_tran_web/src/features/editors/model/legacy-iframe-permissions.ts",
+ "testSource": "../../tools/content-permissions.test.mjs"
+ },
+ {
+ "title": "Auth / Tran 连接有效性检查增加时限与空闲保活",
+ "problem": "数据库连接健康检查可能在驱动网络读取上长时间阻塞,拖住借用连接的请求;不能据此把每个历史503都逐条归因为此缺陷。",
+ "change": "两个模块统一使用有时限的 SELECT 1 健康检查,默认临时网络时限 2 秒;检查结束恢复原连接超时,开启空闲检查和保活。已有普通业务 SQL 超时策略不变。",
+ "validation": "实际 Kingbase 驱动隔离探针:正常检查 12ms,原0ms和原7000ms网络超时均恢复;隔离网络黑洞约2010ms抛出 SocketTimeoutException 并关闭连接。探针不是整个平台可用性测试,后端打包和全量E2E结果另记。",
+ "source": "../../../unreal_tran_api/ut_auth/src/main/java/com/unrealtran/auth/config/BoundedConnectionValidator.java",
+ "secondSource": "../../../unreal_tran_api/ut_tran/src/main/java/com/unrealtran/tran/config/BoundedConnectionValidator.java",
+ "evidence": "data/kingbase-validation-probe-result.json"
+ },
+ {
+ "title": "曲线轨道与分量控件的可读性",
+ "problem": "曲线轨道改为按钮后遗漏背景和边框样式,显示为灰底灰字;分量下拉沿用浏览器默认样式并与数值标签重叠。",
+ "change": "补齐整宽暗色轨道按钮、主副文字与选中/悬停/键盘焦点样式;分量下拉使用暗色样式并定位右上。仅修改曲线面板CSS。",
+ "validation": "最终浏览器轮次重新操作轨道、关键帧、五种插值和分量选择并截图;可视效果以最终22与28号截图核对。",
+ "featureIds": [
+ "timeline.curve",
+ "curve.easing",
+ "curve.value"
+ ],
+ "source": "../../../unreal_tran/src/style.css",
+ "evidence": "screenshots/22-truck-curve-value-five.png"
+ }
+ ]
+}
\ No newline at end of file
diff --git a/reports/model-editor-closure-20260906/data/inventory-main.md b/reports/model-editor-closure-20260906/data/inventory-main.md
new file mode 100644
index 0000000..012366c
--- /dev/null
+++ b/reports/model-editor-closure-20260906/data/inventory-main.md
@@ -0,0 +1,115 @@
+# 装备模型编辑器闭环清单(2026-09-06)
+
+本清单为只读审查基线,后续修复/验收须单独记录结果,不把源码已接通当作 E2E 已通过。
+
+## 实际入口
+
+Web config/content.ts:101 默认 legacy-demo;LegacyModelEditorView.vue 使用同源 iframe 并注入权限;stage 脚本从 unreal_tran/src/main.js 打包。公开产物为 editor-CunHwJSa.js,原清单生成于 2026-09-05T10:06:01Z。正式备用 features/editors/model/DigitalTwinEditor.js 不是当前默认执行入口。
+
+## 功能逐项
+
+|功能|原路径|审查基线|
+|---|---|---|
+|新建|main.js:2984|重置并 PUT 当前工程,无新增 ID;由主代理宿主改造|
+|导入 GLB|1273|受控上传、解析、最终保存,单文件 GLB|
+|导入 JSON|2829|恢复并覆盖当前工程,非服务端副本|
+|保存/Ctrl+S|2714|恢复副本、截图、队列 PUT/乐观锁|
+|自动保存|2177|30秒自动 PUT,不更新封面|
+|工程名|2134|失焦保存|
+|另存|2846|JSON 下载,不包含 GLB 字节|
+|撤销重做|158|35 个快照,缺异步互斥/失败索引回滚|
+|移动旋转缩放|2010/2215|可回存,动画基值计算有漂移|
+|聚焦|2242|相机可存|
+|网格/PBR/线框/轮廓|2217/2253|当前视图;关闭轮廓后重选泄漏|
+|透视|2217|固定透视重置,无菜单|
+|截图|3037|当前 canvas PNG|
+|预览|2254|真实三维;前后步实际跳时间轴2秒|
+|运行蓝图|1061/2189|有执行器,分支/触发有bug|
+|生成发布|2923|预检只做存在性/数量|
+|场景资源发布|2860|固定服务端版本;未直接捕获封面|
+|离线HTML|2956/io:432|截图+CSS示意+固定节拍,不是真实三维运行|
+|场景树/展开/搜索|1939|最多220行,计数为全部|
+|显示隐藏|1949|能回存;拾取未排除隐藏祖先|
+|整体部件选择|1952|可序列化|
+|装备资源切换|1440|自动保存,模板可覆盖时间轴/蓝图|
+|资源搜索|1474|多词/简称,重绘保持|
+|内置环境|775/972|可恢复车间/野外/车库|
+|外部环境GLB|1014/1802/1896|仅会话字节,重开需重选;本轮评估受控资源闭环|
+|训练场景JSON|816|无明确可达按钮,通用localStorage,不进入工程|
+|名称/变换属性|1972/2010|可回存|
+|PBR材质|1972/2010|可回存,多槽仅编辑第一槽|
+|资源编码/运行状态|1984|控件无回写,重选即丢|
+|流程绑定|1984|只读模板名称,非真实业务绑定|
+|组件|1989/2111|派生说明+持久化名称标签,无运行行为|
+|时间轴播放暂停停止循环|timeline:369|实际执行;循环可存|
+|前后关键帧/播放头|timeline:261/555|已接通|
+|轨道增删/目标/属性|timeline:444|可存,仅位置XYZ/旋转XYZ|
+|关键帧增删拖动|timeline:491|可存,默认1/30秒吸附、Alt取消|
+|记录关键帧|main:2246|从目标相对基值取值|
+|静音|timeline:239|忽略轨道但宿主不复位,保存仍扣值|
+|时间轴缩放|timeline:427|可存|
+|关键帧数值/插值|timeline:518|方法存在,曲线面板未连接|
+|基准动作|main:3003|匹配部件,替换时间轴/蓝图|
+|原生GLB动画|1709|选择播放,保存片段编号|
+|蓝图库/参数|blueprint:274/418/710|可存|
+|连线移动缩放框选删除|blueprint:522/728|可存,输入端口来源被覆盖|
+|复制粘贴|blueprint:669|只复制节点,不复制内部连线|
+|蓝图运行停止重置|blueprint:835|实际执行、200步上限|
+|点击双击自动|blueprint:898/main:2237|未匹配目标,无双击独立派发|
+|时间轴节点|main:2037|唯一当前时间轴,clip仅名称|
+|条件节点|main:2076/blueprint:867|缺truthy、布尔字符串解析,false出口缺失走任意出口|
+|高亮|main:2062|无匹配回退整机,pulse只有不同等待时长|
+|提示|main:2083|1.8/2.5秒自动继续或人工关闭|
+|曲线编辑器|1090|固定轨道/SVG/参数,无数据写回|
+|日志|3061|内存160行,非服务端审计|
+
+## 已做只读原方法探针
+
+- condition 返回 false,仅 true 连线:仍执行 true-only 完成节点并返回 true。
+- trigger('click',{object:'B'}):A/B 两个目标节点时仍选择 A。
+- 相同目标 rotation.z 两轨值 1/2:可见旋转 2,序列化基值 -1,应为0。
+- 静音后 seek:可见姿态1、序列化基值-1,说明显示与重开存在漂移。
+
+## 本轮已授权修复与边界
+
+main 负责真实曲线输入/插值、自定义属性、动画基值与静音、条件值解析、准确高亮、点击/双击/进入预览auto、隐藏拾取、轮廓、历史互斥及环境受控资源集成。新建由主代理宿主负责。Blueprint/Timeline 和内容 API/IO 各由其他代理负责。真正离线三维包与完整组件系统属于较大调整,最终确认;现有入口应明确“步骤演示 HTML(不含三维模型)”及“扩展标记,仅保存标签”。
+
+快捷键:全局 Ctrl+S/Z/Y、W/E/R、F、Tab、Esc;时间轴 Space/Delete/Backspace/左右键及Shift快进;蓝图 Ctrl/Cmd+A/C/V、Delete、Space平移、Esc、F5。当前没有模型对象的删除/复制上下文菜单。
+
+## 本轮 main 实施完成(2026-09-06,源码检查与聚焦测试,非真实 UI 验收结论)
+
+上表保留修复前定位和基线;以下为实际实施后状态。源码目录 `unreal_tran/unreal_tran/src`,由正式 Web 的 stage 脚本复制并构建。未在子任务提交、推送、生成 bundle 或执行业务 E2E。
+
+- 新增 `model-curve-editor.js`,曲线轨道名、关键帧点、时间、标量/向量值及插值均读取/修改当前 TimelineEditor;实际调用 updateKeyframe/selectKeyframe,随工程 JSON 重开。画面不再呈现与时间轴脱节的假控制点;本轮没有新增 Bezier 切线或 solo。
+- 自定义资源编码、NORMAL/SERVICE 状态写入对象 customProperties;环境/模型捕获、恢复及撤销均保留明确的修改或清空。扩展标记只存名称,文案如实说明不提供运行行为。多材质对象明确显示“材质槽 1/N”。
+- 新增 `model-editor-state.js` 统一时间轴有效偏移:同一目标同一属性采用最后一个有效轨道;静音、删轨、重绑复原此前作用对象;保存和手工变换基值只扣除实际应用偏移一次。
+- 蓝图条件支持 truthy、布尔/数值字符串和嵌套路径比较;高亮不再找不到对象就回退整机,隐藏或重名目标反馈错误,pulse 实际调节亮度,停止恢复材质。预览临时高亮不会被自动保存为永久材质。
+- 点击携带命中对象及祖先别名;双击单独派发 double-click;进入预览仅显式派发 auto。时间轴节点保留 clip 数据键,标签改“动作名称(播放当前时间轴)”,没有虚构原生片段选择功能。
+- 轮廓关闭状态在重新选择/进出预览后保持;射线拾取排除隐藏祖先下面的子对象。
+- History 保存 60 个快照,异步恢复互斥,恢复失败不移动索引;托管 GLB 历史恢复通过当前项目资产重新 materialize,避免 blob 被 JSON 清掉后撤销失败。
+- 外部 GLB 环境调用 uploadEnvironment,保存稳定 assetCode/storageUri;刷新/JSON/恢复稿重新下载受控资源。导入最终工程提交前保持 busy;上传已登记但解析/绑定失败时阻断保存/发布,防止旧文档误覆盖新资源。
+- 发布时捕获当前真实视口封面,使用 API 队列保存后发布;封面上传/截图失败提示保留旧封面,版本冲突阻断发布;保存/发布封面期间阻止重入。
+- 生成面板、按钮和完成提示明确为“步骤演示 HTML(不含三维模型)”;真实离线三维包、下游自动执行蓝图、完整组件运行系统仍属于较大调整待确认。
+
+## 聚焦验证与真实 UI 待验入口
+
+1. `node --test tests/model-main-closure.test.mjs`:13/13,包含实际 TimelineEditor + happy-dom 曲线交互、工程 JSON 往返、真实主类方法提取、材质/历史/发布失败分支。
+2. `node --test tests/blueprint-timeline-closure.test.mjs tests/model-content-api-integrity.test.mjs`:本轮同批 32/32(各16),与 main 首次12项联合44/44;main补托管历史后单跑13/13。
+3. `node --test tests/model-editor-import.test.mjs tests/editor-timeline-playback.test.mjs tests/editor-delivery.test.mjs tests/model-content-api.test.mjs`:4/4脚本通过。脚本内旧“离线HTML均通过”仅说明现有步骤演示输出合同,不代表完整离线三维。
+4. main.js、model-curve-editor.js、model-editor-state.js 的 node --check 均通过;git diff --check 通过(仅 Git LF/CRLF 通知)。
+
+真实 E2E 由主代理执行:底部曲线页点关键帧后编辑右栏 `data-curve-field=time/value/easing`;对象属性栏 `#custom-resource-code`/`#custom-operating-status`;资源库外部 GLB 入口;顶部撤销/重做、发布面板及刷新。不得将上述单元/合同测试记作真实 API 或三维可见性截图验收。
+
+### 真实浏览器预跑暴露的启动阻断补充
+
+初次 13 项聚焦测试没有覆盖完整资源库 DOM 初始化。真实浏览器发现 renderAssets 两处误用单节点 `$` 后调用 `forEach`,使资源目录初始化抛错、整个模型启动中断。已修为节点集合 `$$`,并新增真实 DOM 回归先复现相同 TypeError,再验证两模型/五环境卡片都渲染、事件都绑定、导入忙时阻断、空目录也可启动。现在 `tests/model-main-closure.test.mjs` 为 **14/14 通过**;此回归不能替代随后正式浏览器重载验收。同类单节点选择器误用复扫无新增匹配。
+
+### 保存后刷新假恢复稿与状态标签补充
+
+真实 175 号测试副本快照逐字段比较没有语义差异,但 normalize 调整了 environment 对象的键顺序。原始 JSON.stringify 指纹因此误认为内容改变,beforeunload 在已保存页面刷新时写出同版本恢复稿。`model-content-api.js` 指纹现统一递归排序对象键,保留数组顺序和所有真实值变化;saveNow 与 dirty 比较共用该算法。新增 API 合同先复现再通过,并对真实 beforeunload 恢复方法断言“仅键顺序不写稿、真实名称变动仍写稿”。没有通过测试自动丢弃恢复稿来掩盖故障。
+
+属性状态从硬编码“可见/投射阴影”改为真实祖先可见性和 castShadow;`span[data-model-editability]` 供正式宿主按权限显示可编辑/只读。最新 main 聚焦测试 **16/16**,API integrity **17/17**;这些是阶段结果,最终统计仍以报告 validation.json 为准。
+
+### 独立补充浏览器预跑
+
+新增 `model-editor-navigation-closure.spec.ts`,每例从原狐狸模型 157 创建独立副本并登记清理 ID,不修改原始工程或共享测试狐狸。三个用例完成真实资源搜索与卡片切换、三种内置环境保存重开、树折叠/计数、实际鼠标镜头、面板切换/拖动、预览前后步与 Escape、实际 30 秒自动保存和刷新核对;私有预跑 **3/3** 通过,截图 41~47。此结果不替代最终统一 E2E,最终报告只按 results.json 及匹配 testTitle 的证据计算。更细的播放头/录帧、蓝图画布、恢复/离开和 gizmo 补测由团队分工执行。
diff --git a/reports/model-editor-closure-20260906/data/kingbase-validation-probe-result.json b/reports/model-editor-closure-20260906/data/kingbase-validation-probe-result.json
new file mode 100644
index 0000000..33095cb
--- /dev/null
+++ b/reports/model-editor-closure-20260906/data/kingbase-validation-probe-result.json
@@ -0,0 +1,24 @@
+{
+ "exitCode": 0,
+ "checks": [
+ {
+ "case": "actual-driver-check",
+ "valid": true,
+ "beforeMs": 0,
+ "afterMs": 0,
+ "elapsedMs": 12
+ },
+ {
+ "case": "actual-driver-nonzero-restore",
+ "beforeMs": 7000,
+ "afterMs": 7000
+ },
+ {
+ "case": "actual-driver-blackhole",
+ "elapsedMs": 2010,
+ "closed": true,
+ "exceptionType": "KSQLException",
+ "causeType": "SocketTimeoutException"
+ }
+ ]
+}
\ No newline at end of file
diff --git a/reports/model-editor-closure-20260906/data/unit-coverage-plan.json b/reports/model-editor-closure-20260906/data/unit-coverage-plan.json
new file mode 100644
index 0000000..3f9a64e
--- /dev/null
+++ b/reports/model-editor-closure-20260906/data/unit-coverage-plan.json
@@ -0,0 +1,26 @@
+{
+ "kind": "unit-coverage-plan",
+ "status": "PENDING",
+ "note": "本文件只说明真实测试与功能的对应关系,不声明执行成功。最终通过状态须由 validation.json 中对应命令的退出码与测试结果确认;单元测试不等同浏览器操作。",
+ "sourceFile": "../../../../unreal_tran/tests/model-main-closure.test.mjs",
+ "command": "node --test tests/model-main-closure.test.mjs",
+ "cwd": "unreal_tran/unreal_tran",
+ "tests": [
+ { "title": "resource catalog startup renders all model/environment cards and binds every action with a real DOM", "featureIds": ["resource.catalog"], "scope": "真实 DOM 渲染、卡片绑定、空目录及 busy 保护;不包含 GLB 网络加载。" },
+ { "title": "curve edits real scalar key values, time and interpolation, persisting through document reload", "featureIds": ["timeline.curve", "curve.time", "curve.value", "curve.easing"], "scope": "真实曲线控件与 TimelineEditor,标量编辑与文档往返。" },
+ { "title": "curve vector editing preserves other components and selected track; invalid values do not mutate", "featureIds": ["curve.value"], "scope": "向量其它分量保留,当前轨道、非法值保护;不包含显示轴切换或可视像素验收。" },
+ { "title": "multiple tracks on one property use last effective value; capture subtracts it only once", "featureIds": ["timeline.multitrack"], "scope": "调用实际 apply/capture/update 基值方法;验证同属性最后有效轨道、静音/删除后中性姿态,偏移只扣一次。" },
+ { "title": "changing a binding resets the former target and keeps manual transform edits as base", "featureIds": ["timeline.multitrack", "timeline.target"], "scope": "轨道重绑后旧目标复位,手动变换成为新基值;不假称鼠标拖动已验。" },
+ { "title": "custom properties and extension labels survive actual capture, project JSON and object restore", "featureIds": ["property.code", "property.status", "component.tag"], "scope": "真实对象 capture、normalization、JSON 与 restore 往返;清空和撤销到未存在字段状态。" },
+ { "title": "boolean, numeric and nested blueprint conditions select their true/false branches", "featureIds": ["blueprint.condition"], "scope": "布尔、数字、truthy 与嵌套路径值解析。" },
+ { "title": "visible child under a hidden ancestor cannot be picked or highlighted; trigger aliases include ancestors", "featureIds": ["tree.visibility", "blueprint.highlight"], "scope": "共用可见性和祖先别名函数;鼠标事件另由 E2E 验证。" },
+ { "title": "inspector state tags reflect ancestor visibility and actual shadow flags, with a host editability hook", "featureIds": ["property.identity"], "scope": "真实属性 DOM 显示隐藏/阴影状态,并提供宿主权限标签 hook;不伪称此单测完成会话鉴权。" },
+ { "title": "history retains 60 states, truncates redo after edits and ignores duplicate snapshots", "featureIds": ["project.undo", "project.redo"], "scope": "60 个快照上限、重复状态忽略及新编辑截断 redo 分支。" },
+ { "title": "history serializes async restores and leaves index unchanged when restoration fails", "featureIds": ["project.undo", "project.redo"], "scope": "异步互斥、下载失败不移动索引,解除锁后可继续操作。" },
+ { "title": "managed-GLB history downloads the snapshot stable reference before restoring, without rewriting it", "featureIds": ["project.undo", "project.redo"], "scope": "受控 GLB 快照先物化再恢复,不把临时 URL 写回快照。" },
+ { "title": "beforeunload recovery does not create a draft for key order alone, but preserves real unsaved changes", "featureIds": ["project.recovery"], "scope": "同语义对象键顺序不产生假恢复稿;真正修改仍保留。不包含全部恢复/隔离/下载 UI 分支。" },
+ { "title": "highlight rejects missing/hidden targets and transient colors never enter a saved project", "featureIds": ["blueprint.highlight"], "scope": "目标不存在/隐藏报错,执行时暂态发光颜色不进入保存文档,结束恢复原材质。" },
+ { "title": "API publish supplies current cover, blocks duplicate publication and reports preserved old cover once", "featureIds": ["publish.api", "publish.cover", "feedback.toast"], "scope": "实际发布宿主方法传封面、重入阻断和上传失败只提示一次;模拟 API,不是实际服务响应。" },
+ { "title": "API conflict leaves publication failed and releases the cover guard", "featureIds": ["project.conflict", "publish.api", "publish.cover"], "scope": "409 分支不误报已发布并释放 busy 标记;模拟 API。" }
+ ]
+}
diff --git a/reports/model-editor-closure-20260906/data/visual-qa.json b/reports/model-editor-closure-20260906/data/visual-qa.json
new file mode 100644
index 0000000..cb1d2ee
--- /dev/null
+++ b/reports/model-editor-closure-20260906/data/visual-qa.json
@@ -0,0 +1,154 @@
+{
+ "recordedAt": "2026-09-06T08:24:22Z",
+ "kind": "manual-visual-review",
+ "status": "PASS_WITH_NOTES",
+ "scope": "逐张只读查看报告 screenshots 目录当前 41 张原始 PNG,并核对各自图注;没有运行业务接口、E2E,没有创建、修改或重拍图片。此记录仅说明视觉复核,不替代最终 E2E、附件一致性或凭据扫描。",
+ "imageCount": 41,
+ "missingCaptions": [],
+ "findings": [
+ {
+ "topic": "真实模型可见性",
+ "result": "狐狸在属性重开、预览、受控环境导入重开和下载重试成功等图中清楚可辨;牛奶卡车在时间轴零秒、蓝图运行和点击/双击/自动触发预览图中完整可辨。没有将树节点名称或成功提示单独作为模型可见的证据。",
+ "examples": [
+ "02-properties-reopened.png",
+ "03-fox-preview.png",
+ "21-truck-timeline-controls.png",
+ "27-truck-preview-click.png",
+ "27-truck-preview-double-click.png",
+ "27-truck-preview-auto.png",
+ "30-assets-environment-reopened.png",
+ "64-controlled-glb-retry-loaded.png"
+ ]
+ },
+ {
+ "topic": "故意改变的测试姿态",
+ "result": "22 的车身 X=5、28 的 X=5/Y=-2,以及 29/30 延续的录制偏移导致车身与轮子分离;51 中狐狸侧立和 X 轴压缩用于真实手柄测试。已按对应图注理解为测试姿态,没有误判为模型丢失或加载变形。",
+ "examples": [
+ "22-truck-curve-value-five.png",
+ "28-curve-axis-y-negative.png",
+ "29-timeline-seek-record.png",
+ "30-blueprint-palette-canvas.png",
+ "51-transform-gizmos.png"
+ ]
+ },
+ {
+ "topic": "失败与恢复证据",
+ "result": "04、31、32、52、63 的失败提示与故障注入/非法输入图注相符;60/62 的模态遮罩用于真实恢复保护。63 空视口明确标为加载失败且保留重试,不作为成功加载证据。64 显示重试后真实狐狸。",
+ "examples": [
+ "04-new-retry-dialog.png",
+ "31-invalid-import-keeps-model.png",
+ "32-save-conflict-keeps-recovery.png",
+ "52-leave-save-failure.png",
+ "60-same-version-real-recovery.png",
+ "62-stale-version-recovery-protection.png",
+ "63-controlled-glb-download-failed.png",
+ "64-controlled-glb-retry-loaded.png"
+ ]
+ },
+ {
+ "topic": "环境全景的边界",
+ "result": "43/44 以环境全景为主,狐狸较小;45 中狐狸被车库外墙遮挡。45 图注只声称车库 GLB 加载和保存重开,未声称该图证明狐狸可见。35 为真实导出的静态步骤演示 HTML,图注没有将它称为完整三维离线运行包。",
+ "examples": [
+ "35-offline-step-demo-actual-file.png",
+ "43-builtin-workshop-reopened.png",
+ "44-builtin-outdoor-reopened.png",
+ "45-builtin-depot-reopened.png"
+ ]
+ },
+ {
+ "topic": "可见敏感信息",
+ "result": "在本次逐图目视范围内未发现密码、令牌、JWT 或登录凭据。画面中的角色显示名、测试模型名称/编号及故障注入请求标识属于测试上下文;此结论不覆盖文件内嵌元数据,元数据由独立 artifact-checks 扫描。"
+ }
+ ],
+ "notes": [
+ {
+ "image": "05-new-separate-project.png",
+ "observation": "本次较早读取的成功图仍留有前一故障注入的红色提示。主代理已在最终用例中增加等待其自然消失后重拍;最终新版图片需由主代理再次核对。"
+ },
+ {
+ "image": "33-save-retry-success.png",
+ "observation": "截图同时显示上一轮注入的 409 红色提示与当前重试保存成功提示,属于提示自动消退周期重叠。已补充图注,明确当前成功由 PUT 响应及刷新断言验证;没有重拍或覆盖已关联附件。"
+ },
+ {
+ "images": [
+ "22-truck-curve-value-five.png",
+ "28-curve-axis-y-negative.png"
+ ],
+ "observation": "本次较早读取的曲线图存在已知低对比/分量选择器布局问题,随后读取的 28 文件中文字与选择器已清楚。主代理正在最后 CSS 修复后的完整重拍,需以最终 22/28 原图复核为准。"
+ }
+ ],
+ "finalRetakeCaveat": "截图目录会在最终全量用例运行时原路径重拍。本记录说明实际读取时的 41 张图,不推定覆盖之后的新图已逐张复核;主代理最终重点复核 05、22、28、33,并用最终结果与附件校验确认报告关联。",
+ "images": [
+ "01-components-saved.png",
+ "02-properties-reopened.png",
+ "03-fox-preview.png",
+ "04-new-retry-dialog.png",
+ "05-new-separate-project.png",
+ "06-readonly-permissions.png",
+ "21-truck-timeline-controls.png",
+ "22-truck-curve-value-five.png",
+ "23-truck-blueprint-seven-nodes.png",
+ "24-truck-blueprint-real-message.png",
+ "25-truck-blueprint-completed-log.png",
+ "26-truck-condition-branches-runtime.png",
+ "27-truck-preview-auto.png",
+ "27-truck-preview-click.png",
+ "27-truck-preview-double-click.png",
+ "28-curve-axis-y-negative.png",
+ "29-timeline-seek-record.png",
+ "30-assets-environment-reopened.png",
+ "30-blueprint-palette-canvas.png",
+ "31-bridge-baseline-reopened.png",
+ "31-invalid-import-keeps-model.png",
+ "32-save-conflict-keeps-recovery.png",
+ "33-save-retry-success.png",
+ "34-published-current-cover.png",
+ "35-offline-step-demo-actual-file.png",
+ "41-resource-search-restored.png",
+ "42-resource-card-switch-reopened.png",
+ "43-builtin-workshop-reopened.png",
+ "44-builtin-outdoor-reopened.png",
+ "45-builtin-depot-reopened.png",
+ "46-preview-navigation.png",
+ "47-navigation-autosave-log.png",
+ "51-transform-gizmos.png",
+ "52-leave-save-failure.png",
+ "60-same-version-real-recovery.png",
+ "61-recovered-draft-explicitly-saved.png",
+ "62-stale-version-recovery-protection.png",
+ "63-controlled-glb-download-failed.png",
+ "64-controlled-glb-retry-loaded.png",
+ "65-publish-preflight-readonly.png",
+ "66-depot-saved-return-list.png"
+ ],
+ "finalRecheck": {
+ "time": "2026-09-06T08:28:08.093Z",
+ "reviewer": "主代理逐张查看最终PNG",
+ "items": [
+ {
+ "file": "screenshots/05-new-separate-project.png",
+ "sha256": "bdcdff92c9ea09f4a8d0d477de7f23c4628823f98030b7f631ae2d28b8f70899",
+ "note": "新建成功画面已无上一轮注入失败提示;独立新工程可见。",
+ "status": "PASS"
+ },
+ {
+ "file": "screenshots/22-truck-curve-value-five.png",
+ "sha256": "9dc49ad7aac39f0c5119a3dfb3d19130c5ae323fd71c95396697726a0b736af1",
+ "note": "轨道文字与暗色背景清晰,分量下拉位于右上不重叠;车身X=5为测试姿态。",
+ "status": "PASS"
+ },
+ {
+ "file": "screenshots/28-curve-axis-y-negative.png",
+ "sha256": "afa1ced1f79603933e04b59462507903c1bbc9be70a1c374a6636f41fc1c3686",
+ "note": "已选轨道、负数曲线和Y分量清晰可读;部件分离与图注一致。",
+ "status": "PASS"
+ },
+ {
+ "file": "screenshots/33-save-retry-success.png",
+ "sha256": "a04c75c96960a149781ab3274c52b75ce64c1e7f7f1501736bdcaddf7f3ee09f",
+ "note": "狐狸真实可见;旧409提示与当前成功提示短暂并存,图注已解释,以成功PUT和刷新断言为准。",
+ "status": "PASS"
+ }
+ ]
+ }
+}
\ No newline at end of file
diff --git a/reports/model-editor-closure-20260906/downloads/assets-roundtrip.json b/reports/model-editor-closure-20260906/downloads/assets-roundtrip.json
new file mode 100644
index 0000000..fbfe4bd
--- /dev/null
+++ b/reports/model-editor-closure-20260906/downloads/assets-roundtrip.json
@@ -0,0 +1,1326 @@
+{
+ "schema": "digital-twin-editor-project",
+ "version": 4,
+ "name": "闭环验收-资源往返-mtpjkcpq",
+ "modelId": "imported-model_mtpjkfpw_09-fox",
+ "modelResource": {
+ "kind": "imported",
+ "id": "imported-model_mtpjkfpw_09-fox",
+ "name": "09-Fox",
+ "assetCode": "MODEL_MTPJKFPW_09-FOX",
+ "code": "MODEL_MTPJKFPW_09-FOX",
+ "storageUri": "fsvc://unreal-tran/model/2026/09/06/b85bd2722c4e133e61469c9f413380f0.glb",
+ "assetKey": "",
+ "resourceUrl": "",
+ "sourceName": "09-Fox.glb",
+ "sizeBytes": 162852,
+ "lastModified": 0,
+ "mimeType": "model/gltf-binary",
+ "thumbnail": "",
+ "vertices": 1728,
+ "triangles": 576,
+ "nodes": 27,
+ "meshes": 1,
+ "materials": 1,
+ "textures": 1,
+ "animations": 3,
+ "animationDurationSeconds": 3.4166667461395264,
+ "modelMetadata": {
+ "nodeCount": 27,
+ "meshCount": 1,
+ "materialCount": 1,
+ "textureCount": 1,
+ "animations": [
+ {
+ "index": 0,
+ "name": "Survey",
+ "start": 0,
+ "end": 3.4166667461395264,
+ "duration": 3.4166667461395264,
+ "trackCount": 21
+ },
+ {
+ "index": 1,
+ "name": "Walk",
+ "start": 0,
+ "end": 0.7083333134651184,
+ "duration": 0.7083333134651184,
+ "trackCount": 21
+ },
+ {
+ "index": 2,
+ "name": "Run",
+ "start": 0,
+ "end": 1.1583333015441895,
+ "duration": 1.1583333015441895,
+ "trackCount": 21
+ }
+ ],
+ "animationDurationSeconds": 3.4166667461395264
+ },
+ "nativeAnimations": [
+ {
+ "index": 0,
+ "name": "Survey",
+ "start": 0,
+ "end": 3.4166667461395264,
+ "duration": 3.4166667461395264,
+ "trackCount": 21
+ },
+ {
+ "index": 1,
+ "name": "Walk",
+ "start": 0,
+ "end": 0.7083333134651184,
+ "duration": 0.7083333134651184,
+ "trackCount": 21
+ },
+ {
+ "index": 2,
+ "name": "Run",
+ "start": 0,
+ "end": 1.1583333015441895,
+ "duration": 1.1583333015441895,
+ "trackCount": 21
+ }
+ ]
+ },
+ "meta": {
+ "description": "用于装备维修实训场景编排、编辑器回归与离线发布验证的系统内置基准工程",
+ "author": "数字孪生项目组",
+ "scene": "车间与野外装备维修实训环境",
+ "tags": [
+ "三维场景",
+ "时间轴动画",
+ "蓝图交互",
+ "实训流程"
+ ],
+ "migratedFrom": "legacy-model-document",
+ "source": "content-api-glb",
+ "sourceName": "09-Fox.glb",
+ "assetCode": "MODEL_MTPJKFPW_09-FOX",
+ "storageUri": "fsvc://unreal-tran/model/2026/09/06/b85bd2722c4e133e61469c9f413380f0.glb"
+ },
+ "timeline": {
+ "version": 1,
+ "duration": 8,
+ "currentTime": 0,
+ "loop": true,
+ "zoom": 1,
+ "tracks": [
+ {
+ "id": "track-0-zajplcg",
+ "name": "模型整体位移",
+ "targetId": "interaction-8790bed3",
+ "property": "position",
+ "color": "#ffb22e",
+ "muted": false,
+ "keyframes": [
+ {
+ "id": "key-0-0-8y3px84",
+ "time": 0,
+ "value": [
+ 0,
+ 0,
+ 0
+ ],
+ "easing": "easeInOut"
+ },
+ {
+ "id": "key-0-1-oj1q9d5",
+ "time": 8,
+ "value": [
+ 0,
+ 0,
+ 0
+ ],
+ "easing": "easeInOut"
+ }
+ ]
+ }
+ ]
+ },
+ "blueprint": {
+ "version": 1,
+ "name": "挖掘机作业实训闭环",
+ "viewport": {
+ "x": 55,
+ "y": 55,
+ "zoom": 0.82
+ },
+ "nodes": [
+ {
+ "id": "bp-start",
+ "type": "trigger-click",
+ "title": "点击挖掘机",
+ "x": 40,
+ "y": 55,
+ "params": {
+ "event": "click",
+ "target": "挖掘机"
+ }
+ },
+ {
+ "id": "bp-highlight",
+ "type": "highlight",
+ "title": "高亮工作装置",
+ "x": 300,
+ "y": 55,
+ "params": {
+ "target": "铲斗",
+ "color": "#38e8ff",
+ "pulse": true
+ }
+ },
+ {
+ "id": "bp-message",
+ "type": "message",
+ "title": "显示操作提示",
+ "x": 560,
+ "y": 55,
+ "params": {
+ "title": "挖掘作业实训",
+ "text": "观察动臂、斗杆和铲斗的协同动作",
+ "level": "info"
+ }
+ },
+ {
+ "id": "bp-play",
+ "type": "play-timeline",
+ "title": "播放作业动画",
+ "x": 820,
+ "y": 55,
+ "params": {
+ "clip": "挖掘机作业实训",
+ "from": 0,
+ "speed": 1,
+ "waitForEnd": true
+ }
+ },
+ {
+ "id": "bp-wait",
+ "type": "wait",
+ "title": "动作稳定",
+ "x": 560,
+ "y": 245,
+ "params": {
+ "duration": 600
+ }
+ },
+ {
+ "id": "bp-check",
+ "type": "condition",
+ "title": "动画是否完成",
+ "x": 300,
+ "y": 245,
+ "params": {
+ "variable": "timelineComplete",
+ "operator": "==",
+ "value": true
+ }
+ },
+ {
+ "id": "bp-retry",
+ "type": "message",
+ "title": "等待动画完成",
+ "x": 820,
+ "y": 245,
+ "params": {
+ "title": "流程校验",
+ "text": "动画尚未完成,请重新执行",
+ "level": "warning"
+ }
+ },
+ {
+ "id": "bp-finish",
+ "type": "finish",
+ "title": "流程完成",
+ "x": 40,
+ "y": 245,
+ "params": {
+ "result": "success",
+ "text": "挖掘机作业实训流程完成"
+ }
+ }
+ ],
+ "edges": [
+ {
+ "id": "bp-e1",
+ "from": {
+ "node": "bp-start",
+ "port": "then"
+ },
+ "to": {
+ "node": "bp-highlight",
+ "port": "in"
+ }
+ },
+ {
+ "id": "bp-e2",
+ "from": {
+ "node": "bp-highlight",
+ "port": "then"
+ },
+ "to": {
+ "node": "bp-message",
+ "port": "in"
+ }
+ },
+ {
+ "id": "bp-e3",
+ "from": {
+ "node": "bp-message",
+ "port": "then"
+ },
+ "to": {
+ "node": "bp-play",
+ "port": "in"
+ }
+ },
+ {
+ "id": "bp-e4",
+ "from": {
+ "node": "bp-play",
+ "port": "then"
+ },
+ "to": {
+ "node": "bp-wait",
+ "port": "in"
+ }
+ },
+ {
+ "id": "bp-e5",
+ "from": {
+ "node": "bp-wait",
+ "port": "then"
+ },
+ "to": {
+ "node": "bp-check",
+ "port": "in"
+ }
+ },
+ {
+ "id": "bp-e6",
+ "from": {
+ "node": "bp-check",
+ "port": "true"
+ },
+ "to": {
+ "node": "bp-finish",
+ "port": "in"
+ }
+ },
+ {
+ "id": "bp-e7",
+ "from": {
+ "node": "bp-check",
+ "port": "false"
+ },
+ "to": {
+ "node": "bp-retry",
+ "port": "in"
+ }
+ },
+ {
+ "id": "bp-e8",
+ "from": {
+ "node": "bp-retry",
+ "port": "then"
+ },
+ "to": {
+ "node": "bp-play",
+ "port": "in"
+ }
+ }
+ ]
+ },
+ "camera": {
+ "position": [
+ 11.138380637709293,
+ 8.526809324389628,
+ 13.922975975299929
+ ],
+ "target": [
+ 3.875009990661482e-8,
+ 2.4006999949620704,
+ 2.2660093712545404e-7
+ ]
+ },
+ "model": {
+ "objects": {
+ "equipment-root": {
+ "name": "09-Fox",
+ "nodeName": "09-Fox",
+ "nodePath": "@root",
+ "interactionId": "interaction-8790bed3",
+ "capabilities": {
+ "selectable": true,
+ "transform": true,
+ "visible": true,
+ "material": false,
+ "animatable": true
+ },
+ "position": [
+ 0,
+ 0,
+ 0
+ ],
+ "rotation": [
+ 0,
+ 0,
+ 0
+ ],
+ "scale": [
+ 1,
+ 1,
+ 1
+ ],
+ "visible": true,
+ "customComponents": []
+ },
+ "model-imported-model_mtpjkfpw_09-fox-0": {
+ "name": "",
+ "nodeName": "Group",
+ "nodePath": "0",
+ "interactionId": "interaction-552fee7f",
+ "capabilities": {
+ "selectable": true,
+ "transform": true,
+ "visible": true,
+ "material": false,
+ "animatable": true
+ },
+ "position": [
+ 3.875009990661482e-8,
+ 0.007396563645075124,
+ 0.6522089834776823
+ ],
+ "rotation": [
+ 0,
+ 0,
+ 0
+ ],
+ "scale": [
+ 0.06075496488891343,
+ 0.06075496488891343,
+ 0.06075496488891343
+ ],
+ "visible": true,
+ "customComponents": []
+ },
+ "model-imported-model_mtpjkfpw_09-fox-1": {
+ "name": "root",
+ "nodeName": "root",
+ "nodePath": "0/0",
+ "interactionId": "interaction-54da9340",
+ "capabilities": {
+ "selectable": true,
+ "transform": true,
+ "visible": true,
+ "material": false,
+ "animatable": true
+ },
+ "position": [
+ 0,
+ 0,
+ 0
+ ],
+ "rotation": [
+ 0,
+ 0,
+ 0
+ ],
+ "scale": [
+ 1,
+ 1,
+ 1
+ ],
+ "visible": true,
+ "customComponents": []
+ },
+ "model-imported-model_mtpjkfpw_09-fox-2": {
+ "name": "_rootJoint",
+ "nodeName": "_rootJoint",
+ "nodePath": "0/0/0",
+ "interactionId": "interaction-68c712f7",
+ "capabilities": {
+ "selectable": true,
+ "transform": true,
+ "visible": true,
+ "material": false,
+ "animatable": true
+ },
+ "position": [
+ 0,
+ 0,
+ 0
+ ],
+ "rotation": [
+ 0,
+ 0,
+ 0
+ ],
+ "scale": [
+ 1,
+ 1,
+ 1
+ ],
+ "visible": true,
+ "customComponents": []
+ },
+ "model-imported-model_mtpjkfpw_09-fox-3": {
+ "name": "b_Root_00",
+ "nodeName": "b_Root_00",
+ "nodePath": "0/0/0/0",
+ "interactionId": "interaction-3e51f628",
+ "capabilities": {
+ "selectable": true,
+ "transform": true,
+ "visible": true,
+ "material": false,
+ "animatable": true
+ },
+ "position": [
+ 0,
+ 0,
+ 0
+ ],
+ "rotation": [
+ -1.5708000357176293,
+ 0,
+ 0
+ ],
+ "scale": [
+ 1,
+ 1,
+ 1
+ ],
+ "visible": true,
+ "customComponents": []
+ },
+ "model-imported-model_mtpjkfpw_09-fox-4": {
+ "name": "b_Hip_01",
+ "nodeName": "b_Hip_01",
+ "nodePath": "0/0/0/0/0",
+ "interactionId": "interaction-c4eb8a6f",
+ "capabilities": {
+ "selectable": true,
+ "transform": true,
+ "visible": true,
+ "material": false,
+ "animatable": true
+ },
+ "position": [
+ 0,
+ 26.748403549194336,
+ 42.93817138671875
+ ],
+ "rotation": [
+ 0.36315579795853176,
+ -1.5707952841394475,
+ 0
+ ],
+ "scale": [
+ 1,
+ 1,
+ 1
+ ],
+ "visible": true,
+ "customComponents": []
+ },
+ "model-imported-model_mtpjkfpw_09-fox-5": {
+ "name": "b_Spine01_02",
+ "nodeName": "b_Spine01_02",
+ "nodePath": "0/0/0/0/0/0",
+ "interactionId": "interaction-c8fe7fd0",
+ "capabilities": {
+ "selectable": true,
+ "transform": true,
+ "visible": true,
+ "material": false,
+ "animatable": true
+ },
+ "position": [
+ 12.850601196289062,
+ 0,
+ 0
+ ],
+ "rotation": [
+ 0,
+ 0,
+ -1.2631477544403162
+ ],
+ "scale": [
+ 1,
+ 1,
+ 1
+ ],
+ "visible": true,
+ "customComponents": []
+ },
+ "model-imported-model_mtpjkfpw_09-fox-6": {
+ "name": "b_Spine02_03",
+ "nodeName": "b_Spine02_03",
+ "nodePath": "0/0/0/0/0/0/0",
+ "interactionId": "interaction-835fec67",
+ "capabilities": {
+ "selectable": true,
+ "transform": true,
+ "visible": true,
+ "material": false,
+ "animatable": true
+ },
+ "position": [
+ 21.65575408935547,
+ -0.000118255615234375,
+ 0
+ ],
+ "rotation": [
+ 0,
+ 0,
+ 0.03482566467786662
+ ],
+ "scale": [
+ 1,
+ 1,
+ 1
+ ],
+ "visible": true,
+ "customComponents": []
+ },
+ "model-imported-model_mtpjkfpw_09-fox-7": {
+ "name": "b_Neck_04",
+ "nodeName": "b_Neck_04",
+ "nodePath": "0/0/0/0/0/0/0/0",
+ "interactionId": "interaction-318252b8",
+ "capabilities": {
+ "selectable": true,
+ "transform": true,
+ "visible": true,
+ "material": false,
+ "animatable": true
+ },
+ "position": [
+ 25.64914321899414,
+ 0,
+ 0
+ ],
+ "rotation": [
+ 0,
+ 0,
+ 0.616473876457289
+ ],
+ "scale": [
+ 1,
+ 1,
+ 1
+ ],
+ "visible": true,
+ "customComponents": []
+ },
+ "model-imported-model_mtpjkfpw_09-fox-8": {
+ "name": "b_Head_05",
+ "nodeName": "b_Head_05",
+ "nodePath": "0/0/0/0/0/0/0/0/0",
+ "interactionId": "interaction-48fd8a5f",
+ "capabilities": {
+ "selectable": true,
+ "transform": true,
+ "visible": true,
+ "material": false,
+ "animatable": true
+ },
+ "position": [
+ 13.376960754394531,
+ 0,
+ 0
+ ],
+ "rotation": [
+ 0,
+ 0,
+ -0.8236565613916008
+ ],
+ "scale": [
+ 1,
+ 1,
+ 1
+ ],
+ "visible": true,
+ "customComponents": []
+ },
+ "model-imported-model_mtpjkfpw_09-fox-9": {
+ "name": "b_RightUpperArm_06",
+ "nodeName": "b_RightUpperArm_06",
+ "nodePath": "0/0/0/0/0/0/0/1",
+ "interactionId": "interaction-3282544b",
+ "capabilities": {
+ "selectable": true,
+ "transform": true,
+ "visible": true,
+ "material": false,
+ "animatable": true
+ },
+ "position": [
+ 18.677913665771484,
+ -4.297340393066406,
+ 6.9675750732421875
+ ],
+ "rotation": [
+ 0.000020649684502855715,
+ -0.0012920321082461929,
+ -1.5851959113548921
+ ],
+ "scale": [
+ 1,
+ 1,
+ 1
+ ],
+ "visible": true,
+ "customComponents": []
+ },
+ "model-imported-model_mtpjkfpw_09-fox-10": {
+ "name": "b_RightForeArm_07",
+ "nodeName": "b_RightForeArm_07",
+ "nodePath": "0/0/0/0/0/0/0/1/0",
+ "interactionId": "interaction-44742bd4",
+ "capabilities": {
+ "selectable": true,
+ "transform": true,
+ "visible": true,
+ "material": false,
+ "animatable": true
+ },
+ "position": [
+ 23.04512596130371,
+ 0,
+ 0
+ ],
+ "rotation": [
+ 0,
+ 0,
+ 0.07426886741269867
+ ],
+ "scale": [
+ 1,
+ 1,
+ 1
+ ],
+ "visible": true,
+ "customComponents": []
+ },
+ "model-imported-model_mtpjkfpw_09-fox-11": {
+ "name": "b_RightHand_08",
+ "nodeName": "b_RightHand_08",
+ "nodePath": "0/0/0/0/0/0/0/1/0/0",
+ "interactionId": "interaction-fa918ec3",
+ "capabilities": {
+ "selectable": true,
+ "transform": true,
+ "visible": true,
+ "material": false,
+ "animatable": true
+ },
+ "position": [
+ 19.350055694580078,
+ -0.14598655700683594,
+ 0
+ ],
+ "rotation": [
+ -0.014166329641847676,
+ -0.024975147923384556,
+ 0.9571872589843109
+ ],
+ "scale": [
+ 1,
+ 1,
+ 1
+ ],
+ "visible": true,
+ "customComponents": []
+ },
+ "model-imported-model_mtpjkfpw_09-fox-12": {
+ "name": "b_LeftUpperArm_09",
+ "nodeName": "b_LeftUpperArm_09",
+ "nodePath": "0/0/0/0/0/0/0/2",
+ "interactionId": "interaction-338255de",
+ "capabilities": {
+ "selectable": true,
+ "transform": true,
+ "visible": true,
+ "material": false,
+ "animatable": true
+ },
+ "position": [
+ 18.67791748046875,
+ -4.297344207763672,
+ -6.967987060546875
+ ],
+ "rotation": [
+ -0.0005581520651140974,
+ -0.0019469460390509152,
+ -1.5849355765797046
+ ],
+ "scale": [
+ 1,
+ 1,
+ 1
+ ],
+ "visible": true,
+ "customComponents": []
+ },
+ "model-imported-model_mtpjkfpw_09-fox-13": {
+ "name": "b_LeftForeArm_010",
+ "nodeName": "b_LeftForeArm_010",
+ "nodePath": "0/0/0/0/0/0/0/2/0",
+ "interactionId": "interaction-9f4c00a9",
+ "capabilities": {
+ "selectable": true,
+ "transform": true,
+ "visible": true,
+ "material": false,
+ "animatable": true
+ },
+ "position": [
+ 23.045124053955078,
+ 0,
+ 0
+ ],
+ "rotation": [
+ 0,
+ 0,
+ 0.07426886741269867
+ ],
+ "scale": [
+ 1,
+ 1,
+ 1
+ ],
+ "visible": true,
+ "customComponents": []
+ },
+ "model-imported-model_mtpjkfpw_09-fox-14": {
+ "name": "b_LeftHand_011",
+ "nodeName": "b_LeftHand_011",
+ "nodePath": "0/0/0/0/0/0/0/2/0/0",
+ "interactionId": "interaction-4377c766",
+ "capabilities": {
+ "selectable": true,
+ "transform": true,
+ "visible": true,
+ "material": false,
+ "animatable": true
+ },
+ "position": [
+ 19.350051879882812,
+ -0.14599037170410156,
+ 0
+ ],
+ "rotation": [
+ 0.016442303987465122,
+ 0.04008733863576039,
+ 0.9560742489627883
+ ],
+ "scale": [
+ 1,
+ 1,
+ 1
+ ],
+ "visible": true,
+ "customComponents": []
+ },
+ "model-imported-model_mtpjkfpw_09-fox-15": {
+ "name": "b_Tail01_012",
+ "nodeName": "b_Tail01_012",
+ "nodePath": "0/0/0/0/0/1",
+ "interactionId": "interaction-c9fe8163",
+ "capabilities": {
+ "selectable": true,
+ "transform": true,
+ "visible": true,
+ "material": false,
+ "animatable": true
+ },
+ "position": [
+ 4.2603759765625,
+ 15.958770751953125,
+ 0
+ ],
+ "rotation": [
+ 0,
+ 0,
+ 2.760415362743526
+ ],
+ "scale": [
+ 1,
+ 1,
+ 1
+ ],
+ "visible": true,
+ "customComponents": []
+ },
+ "model-imported-model_mtpjkfpw_09-fox-16": {
+ "name": "b_Tail02_013",
+ "nodeName": "b_Tail02_013",
+ "nodePath": "0/0/0/0/0/1/0",
+ "interactionId": "interaction-5d9926fc",
+ "capabilities": {
+ "selectable": true,
+ "transform": true,
+ "visible": true,
+ "material": false,
+ "animatable": true
+ },
+ "position": [
+ 12.411918640136719,
+ 0,
+ 0
+ ],
+ "rotation": [
+ 0,
+ 0,
+ -0.1393470463625701
+ ],
+ "scale": [
+ 1,
+ 1,
+ 1
+ ],
+ "visible": true,
+ "customComponents": []
+ },
+ "model-imported-model_mtpjkfpw_09-fox-17": {
+ "name": "b_Tail03_014",
+ "nodeName": "b_Tail03_014",
+ "nodePath": "0/0/0/0/0/1/0/0",
+ "interactionId": "interaction-eff7615b",
+ "capabilities": {
+ "selectable": true,
+ "transform": true,
+ "visible": true,
+ "material": false,
+ "animatable": true
+ },
+ "position": [
+ 24.24032211303711,
+ 0,
+ 0
+ ],
+ "rotation": [
+ 0,
+ 0,
+ -0.10771755940908084
+ ],
+ "scale": [
+ 1,
+ 1,
+ 1
+ ],
+ "visible": true,
+ "customComponents": []
+ },
+ "model-imported-model_mtpjkfpw_09-fox-18": {
+ "name": "b_LeftLeg01_015",
+ "nodeName": "b_LeftLeg01_015",
+ "nodePath": "0/0/0/0/0/2",
+ "interactionId": "interaction-cafe82f6",
+ "capabilities": {
+ "selectable": true,
+ "transform": true,
+ "visible": true,
+ "material": false,
+ "animatable": true
+ },
+ "position": [
+ 4.813770294189453,
+ 5.154018402099609,
+ -6.968006134033203
+ ],
+ "rotation": [
+ 0.0003332048354397326,
+ 0.00008348584072415229,
+ -2.6505924384110937
+ ],
+ "scale": [
+ 1,
+ 1,
+ 1
+ ],
+ "visible": true,
+ "customComponents": []
+ },
+ "model-imported-model_mtpjkfpw_09-fox-19": {
+ "name": "b_LeftLeg02_016",
+ "nodeName": "b_LeftLeg02_016",
+ "nodePath": "0/0/0/0/0/2/0",
+ "interactionId": "interaction-d8712e31",
+ "capabilities": {
+ "selectable": true,
+ "transform": true,
+ "visible": true,
+ "material": false,
+ "animatable": true
+ },
+ "position": [
+ 18.944175720214844,
+ 0,
+ 0
+ ],
+ "rotation": [
+ 0,
+ 0,
+ -0.7538085117951059
+ ],
+ "scale": [
+ 1,
+ 1,
+ 1
+ ],
+ "visible": true,
+ "customComponents": []
+ },
+ "model-imported-model_mtpjkfpw_09-fox-20": {
+ "name": "b_LeftFoot01_017",
+ "nodeName": "b_LeftFoot01_017",
+ "nodePath": "0/0/0/0/0/2/0/0",
+ "interactionId": "interaction-56a1eabe",
+ "capabilities": {
+ "selectable": true,
+ "transform": true,
+ "visible": true,
+ "material": false,
+ "animatable": true
+ },
+ "position": [
+ 17.942811965942383,
+ 0,
+ 0
+ ],
+ "rotation": [
+ 0.00044152629033825986,
+ 0.0002277846223534801,
+ 0.9525774122187588
+ ],
+ "scale": [
+ 1,
+ 1,
+ 1
+ ],
+ "visible": true,
+ "customComponents": []
+ },
+ "model-imported-model_mtpjkfpw_09-fox-21": {
+ "name": "b_LeftFoot02_018",
+ "nodeName": "b_LeftFoot02_018",
+ "nodePath": "0/0/0/0/0/2/0/0/0",
+ "interactionId": "interaction-3d549b09",
+ "capabilities": {
+ "selectable": true,
+ "transform": true,
+ "visible": true,
+ "material": false,
+ "animatable": true
+ },
+ "position": [
+ 15.779938697814941,
+ 0,
+ 0
+ ],
+ "rotation": [
+ 0,
+ 0,
+ 1.158241570830627
+ ],
+ "scale": [
+ 1,
+ 1,
+ 1
+ ],
+ "visible": true,
+ "customComponents": []
+ },
+ "model-imported-model_mtpjkfpw_09-fox-22": {
+ "name": "b_RightLeg01_019",
+ "nodeName": "b_RightLeg01_019",
+ "nodePath": "0/0/0/0/0/3",
+ "interactionId": "interaction-cbfe8489",
+ "capabilities": {
+ "selectable": true,
+ "transform": true,
+ "visible": true,
+ "material": false,
+ "animatable": true
+ },
+ "position": [
+ 4.813777923583984,
+ 5.154026031494141,
+ 6.967563629150391
+ ],
+ "rotation": [
+ 0,
+ 0,
+ -2.6501210928667223
+ ],
+ "scale": [
+ 1,
+ 1,
+ 1
+ ],
+ "visible": true,
+ "customComponents": []
+ },
+ "model-imported-model_mtpjkfpw_09-fox-23": {
+ "name": "b_RightLeg02_020",
+ "nodeName": "b_RightLeg02_020",
+ "nodePath": "0/0/0/0/0/3/0",
+ "interactionId": "interaction-b3e79d46",
+ "capabilities": {
+ "selectable": true,
+ "transform": true,
+ "visible": true,
+ "material": false,
+ "animatable": true
+ },
+ "position": [
+ 18.944183349609375,
+ 0,
+ 0
+ ],
+ "rotation": [
+ 0,
+ 0,
+ -0.7538085672160094
+ ],
+ "scale": [
+ 1,
+ 1,
+ 1
+ ],
+ "visible": true,
+ "customComponents": []
+ },
+ "model-imported-model_mtpjkfpw_09-fox-24": {
+ "name": "b_RightFoot01_021",
+ "nodeName": "b_RightFoot01_021",
+ "nodePath": "0/0/0/0/0/3/0/0",
+ "interactionId": "interaction-2bd5e5a1",
+ "capabilities": {
+ "selectable": true,
+ "transform": true,
+ "visible": true,
+ "material": false,
+ "animatable": true
+ },
+ "position": [
+ 17.94281005859375,
+ 0,
+ 0
+ ],
+ "rotation": [
+ -0.00027284186307510456,
+ -0.00014053656253775514,
+ 0.9512842084783865
+ ],
+ "scale": [
+ 1,
+ 1,
+ 1
+ ],
+ "visible": true,
+ "customComponents": []
+ },
+ "model-imported-model_mtpjkfpw_09-fox-25": {
+ "name": "b_RightFoot02_022",
+ "nodeName": "b_RightFoot02_022",
+ "nodePath": "0/0/0/0/0/3/0/0/0",
+ "interactionId": "interaction-f93d1ece",
+ "capabilities": {
+ "selectable": true,
+ "transform": true,
+ "visible": true,
+ "material": false,
+ "animatable": true
+ },
+ "position": [
+ 15.779935836791992,
+ 0,
+ 0
+ ],
+ "rotation": [
+ 0,
+ 0,
+ 1.158241570830627
+ ],
+ "scale": [
+ 1,
+ 1,
+ 1
+ ],
+ "visible": true,
+ "customComponents": []
+ },
+ "model-imported-model_mtpjkfpw_09-fox-26": {
+ "name": "fox",
+ "nodeName": "fox",
+ "nodePath": "0/1",
+ "interactionId": "interaction-55da94d3",
+ "capabilities": {
+ "selectable": true,
+ "transform": true,
+ "visible": true,
+ "material": true,
+ "animatable": true
+ },
+ "position": [
+ 0,
+ 0,
+ 0
+ ],
+ "rotation": [
+ 0,
+ 0,
+ 0
+ ],
+ "scale": [
+ 1,
+ 1,
+ 1
+ ],
+ "visible": true,
+ "customComponents": [],
+ "materials": [
+ {
+ "color": "ffffff",
+ "emissive": "000000",
+ "emissiveIntensity": 1,
+ "metalness": 0,
+ "roughness": 0.58,
+ "opacity": 1,
+ "transparent": false,
+ "depthWrite": true
+ }
+ ]
+ }
+ },
+ "selectionScope": "part",
+ "nativeAnimation": {
+ "clipIndex": 0,
+ "clipName": "Survey"
+ }
+ },
+ "environment": {
+ "mode": "external",
+ "environmentType": "outdoor",
+ "sourceName": "01-Box.glb",
+ "sizeBytes": 1664,
+ "assetCode": "ENV_MODEL_MTPJKH7H_01-BOX",
+ "storageUri": "fsvc://unreal-tran/model/2026/09/06/9bd7351f3ee917431354690f44a449b5.glb",
+ "resourceUrl": "blob:http://127.0.0.1:6180/14a979f4-f70c-4226-83b2-8e4db545f4a2",
+ "objects": {
+ "outdoor-scene": {
+ "name": "野外训练环境 · 01-Box.glb",
+ "position": [
+ 6,
+ 0,
+ -7
+ ],
+ "rotation": [
+ 0,
+ 0,
+ 0
+ ],
+ "scale": [
+ 0.15,
+ 0.15,
+ 0.15
+ ],
+ "visible": true,
+ "customComponents": []
+ },
+ "environment-outdoor-0": {
+ "name": "",
+ "position": [
+ 0,
+ 21.96,
+ 0
+ ],
+ "rotation": [
+ 0,
+ 0,
+ 0
+ ],
+ "scale": [
+ 43.99999999999999,
+ 43.99999999999999,
+ 43.99999999999999
+ ],
+ "visible": true,
+ "customComponents": []
+ },
+ "environment-outdoor-1": {
+ "name": "",
+ "position": [
+ 0,
+ 0,
+ 0
+ ],
+ "rotation": [
+ -1.5707963267948963,
+ 0,
+ 0
+ ],
+ "scale": [
+ 1,
+ 1,
+ 1
+ ],
+ "visible": true,
+ "customComponents": []
+ },
+ "environment-outdoor-2": {
+ "name": "Mesh",
+ "position": [
+ 0,
+ 0,
+ 0
+ ],
+ "rotation": [
+ 0,
+ 0,
+ 0
+ ],
+ "scale": [
+ 1,
+ 1,
+ 1
+ ],
+ "visible": true,
+ "customComponents": [],
+ "materials": [
+ {
+ "color": "e70000",
+ "emissive": "000000",
+ "emissiveIntensity": 1,
+ "metalness": 0,
+ "roughness": 1,
+ "opacity": 1,
+ "transparent": false,
+ "depthWrite": true
+ }
+ ]
+ }
+ }
+ },
+ "runtime": {
+ "startMode": "guided",
+ "showStepPanel": true,
+ "showProgress": true,
+ "allowReplay": true,
+ "language": "zh-CN"
+ },
+ "publish": {
+ "target": "standalone-html",
+ "deployment": "offline",
+ "includeProjectData": true,
+ "includeViewportPreview": true
+ }
+}
\ No newline at end of file
diff --git a/reports/model-editor-closure-20260906/downloads/invalid-import-baseline.json b/reports/model-editor-closure-20260906/downloads/invalid-import-baseline.json
new file mode 100644
index 0000000..fe6f6c2
--- /dev/null
+++ b/reports/model-editor-closure-20260906/downloads/invalid-import-baseline.json
@@ -0,0 +1,3414 @@
+{
+ "schema": "digital-twin-editor-project",
+ "version": 4,
+ "name": "闭环验收-拒绝非法资源-mtpjko7g",
+ "modelId": "imported-model_mto6v5w6_09-fox",
+ "modelResource": {
+ "kind": "imported",
+ "id": "imported-model_mto6v5w6_09-fox",
+ "name": "09-Fox",
+ "assetCode": "MODEL_MTO6V5W6_09-FOX",
+ "code": "MODEL_MTO6V5W6_09-FOX",
+ "storageUri": "fsvc://unreal-tran/model/2026/09/05/3fc69ae0d435f869afca1fcd943486d1.glb",
+ "assetKey": "",
+ "resourceUrl": "",
+ "sourceName": "09-Fox.glb",
+ "sizeBytes": 162852,
+ "lastModified": 0,
+ "mimeType": "model/gltf-binary",
+ "thumbnail": "",
+ "vertices": 1728,
+ "triangles": 576,
+ "nodes": 27,
+ "meshes": 1,
+ "materials": 1,
+ "textures": 1,
+ "animations": 3,
+ "animationDurationSeconds": 3.4166667461395264,
+ "modelMetadata": {
+ "nodeCount": 27,
+ "meshCount": 1,
+ "materialCount": 1,
+ "textureCount": 1,
+ "animations": [
+ {
+ "index": 0,
+ "name": "Survey",
+ "start": 0,
+ "end": 3.4166667461395264,
+ "duration": 3.4166667461395264,
+ "trackCount": 21
+ },
+ {
+ "index": 1,
+ "name": "Walk",
+ "start": 0,
+ "end": 0.7083333134651184,
+ "duration": 0.7083333134651184,
+ "trackCount": 21
+ },
+ {
+ "index": 2,
+ "name": "Run",
+ "start": 0,
+ "end": 1.1583333015441895,
+ "duration": 1.1583333015441895,
+ "trackCount": 21
+ }
+ ],
+ "animationDurationSeconds": 3.4166667461395264
+ },
+ "nativeAnimations": [
+ {
+ "index": 0,
+ "name": "Survey",
+ "start": 0,
+ "end": 3.4166667461395264,
+ "duration": 3.4166667461395264,
+ "trackCount": 21
+ },
+ {
+ "index": 1,
+ "name": "Walk",
+ "start": 0,
+ "end": 0.7083333134651184,
+ "duration": 0.7083333134651184,
+ "trackCount": 21
+ },
+ {
+ "index": 2,
+ "name": "Run",
+ "start": 0,
+ "end": 1.1583333015441895,
+ "duration": 1.1583333015441895,
+ "trackCount": 21
+ }
+ ]
+ },
+ "meta": {
+ "description": "用于装备维修实训场景编排、编辑器回归与离线发布验证的系统内置基准工程",
+ "author": "数字孪生项目组",
+ "scene": "车间与野外装备维修实训环境",
+ "tags": [
+ "三维场景",
+ "时间轴动画",
+ "蓝图交互",
+ "实训流程"
+ ],
+ "migratedFrom": "legacy-model-document",
+ "source": "content-api-glb",
+ "sourceName": "09-Fox.glb",
+ "assetCode": "MODEL_MTO6V5W6_09-FOX",
+ "storageUri": "fsvc://unreal-tran/model/2026/09/05/3fc69ae0d435f869afca1fcd943486d1.glb"
+ },
+ "timeline": {
+ "version": 1,
+ "duration": 8,
+ "currentTime": 0,
+ "loop": true,
+ "zoom": 1,
+ "tracks": [
+ {
+ "id": "track-0-1yjfz9t",
+ "name": "模型整体位移",
+ "targetId": "interaction-b9240aaa",
+ "property": "position",
+ "color": "#ffb22e",
+ "muted": false,
+ "keyframes": [
+ {
+ "id": "key-0-0-kmfo820",
+ "time": 0,
+ "value": [
+ 0,
+ 0,
+ 0
+ ],
+ "easing": "easeInOut"
+ },
+ {
+ "id": "key-0-1-snsj2e2",
+ "time": 8,
+ "value": [
+ 0,
+ 0,
+ 0
+ ],
+ "easing": "easeInOut"
+ }
+ ]
+ }
+ ]
+ },
+ "blueprint": {
+ "version": 1,
+ "name": "挖掘机作业实训闭环",
+ "viewport": {
+ "x": 55,
+ "y": 55,
+ "zoom": 0.82
+ },
+ "nodes": [
+ {
+ "id": "bp-start",
+ "type": "trigger-click",
+ "title": "点击挖掘机",
+ "x": 40,
+ "y": 55,
+ "params": {
+ "event": "click",
+ "target": "挖掘机"
+ }
+ },
+ {
+ "id": "bp-highlight",
+ "type": "highlight",
+ "title": "高亮工作装置",
+ "x": 300,
+ "y": 55,
+ "params": {
+ "target": "铲斗",
+ "color": "#38e8ff",
+ "pulse": true
+ }
+ },
+ {
+ "id": "bp-message",
+ "type": "message",
+ "title": "显示操作提示",
+ "x": 560,
+ "y": 55,
+ "params": {
+ "title": "挖掘作业实训",
+ "text": "观察动臂、斗杆和铲斗的协同动作",
+ "level": "info"
+ }
+ },
+ {
+ "id": "bp-play",
+ "type": "play-timeline",
+ "title": "播放作业动画",
+ "x": 820,
+ "y": 55,
+ "params": {
+ "clip": "挖掘机作业实训",
+ "from": 0,
+ "speed": 1,
+ "waitForEnd": true
+ }
+ },
+ {
+ "id": "bp-wait",
+ "type": "wait",
+ "title": "动作稳定",
+ "x": 560,
+ "y": 245,
+ "params": {
+ "duration": 600
+ }
+ },
+ {
+ "id": "bp-check",
+ "type": "condition",
+ "title": "动画是否完成",
+ "x": 300,
+ "y": 245,
+ "params": {
+ "variable": "timelineComplete",
+ "operator": "==",
+ "value": true
+ }
+ },
+ {
+ "id": "bp-retry",
+ "type": "message",
+ "title": "等待动画完成",
+ "x": 820,
+ "y": 245,
+ "params": {
+ "title": "流程校验",
+ "text": "动画尚未完成,请重新执行",
+ "level": "warning"
+ }
+ },
+ {
+ "id": "bp-finish",
+ "type": "finish",
+ "title": "流程完成",
+ "x": 40,
+ "y": 245,
+ "params": {
+ "result": "success",
+ "text": "挖掘机作业实训流程完成"
+ }
+ }
+ ],
+ "edges": [
+ {
+ "id": "bp-e1",
+ "from": {
+ "node": "bp-start",
+ "port": "then"
+ },
+ "to": {
+ "node": "bp-highlight",
+ "port": "in"
+ }
+ },
+ {
+ "id": "bp-e2",
+ "from": {
+ "node": "bp-highlight",
+ "port": "then"
+ },
+ "to": {
+ "node": "bp-message",
+ "port": "in"
+ }
+ },
+ {
+ "id": "bp-e3",
+ "from": {
+ "node": "bp-message",
+ "port": "then"
+ },
+ "to": {
+ "node": "bp-play",
+ "port": "in"
+ }
+ },
+ {
+ "id": "bp-e4",
+ "from": {
+ "node": "bp-play",
+ "port": "then"
+ },
+ "to": {
+ "node": "bp-wait",
+ "port": "in"
+ }
+ },
+ {
+ "id": "bp-e5",
+ "from": {
+ "node": "bp-wait",
+ "port": "then"
+ },
+ "to": {
+ "node": "bp-check",
+ "port": "in"
+ }
+ },
+ {
+ "id": "bp-e6",
+ "from": {
+ "node": "bp-check",
+ "port": "true"
+ },
+ "to": {
+ "node": "bp-finish",
+ "port": "in"
+ }
+ },
+ {
+ "id": "bp-e7",
+ "from": {
+ "node": "bp-check",
+ "port": "false"
+ },
+ "to": {
+ "node": "bp-retry",
+ "port": "in"
+ }
+ },
+ {
+ "id": "bp-e8",
+ "from": {
+ "node": "bp-retry",
+ "port": "then"
+ },
+ "to": {
+ "node": "bp-play",
+ "port": "in"
+ }
+ }
+ ]
+ },
+ "camera": {
+ "position": [
+ 4.643347096844712,
+ 4.616668289605231,
+ 12.832677927580558
+ ],
+ "target": [
+ 3.875009990661482e-8,
+ 2.4006999949620704,
+ 2.2660093712545404e-7
+ ]
+ },
+ "model": {
+ "objects": {
+ "equipment-root": {
+ "name": "09-Fox",
+ "nodeName": "09-Fox",
+ "nodePath": "@root",
+ "interactionId": "interaction-b9240aaa",
+ "capabilities": {
+ "selectable": true,
+ "transform": true,
+ "visible": true,
+ "material": false,
+ "animatable": true
+ },
+ "position": [
+ 0,
+ 0,
+ 0
+ ],
+ "rotation": [
+ 0,
+ 0,
+ 0
+ ],
+ "scale": [
+ 1,
+ 1,
+ 1
+ ],
+ "visible": true,
+ "customComponents": []
+ },
+ "model-imported-model_mto6v5w6_09-fox-0": {
+ "name": "",
+ "nodeName": "Group",
+ "nodePath": "0",
+ "interactionId": "interaction-02351bce",
+ "capabilities": {
+ "selectable": true,
+ "transform": true,
+ "visible": true,
+ "material": false,
+ "animatable": true
+ },
+ "position": [
+ 3.875009990661482e-8,
+ 0.007396563645075124,
+ 0.6522089834776823
+ ],
+ "rotation": [
+ 0,
+ 0,
+ 0
+ ],
+ "scale": [
+ 0.06075496488891343,
+ 0.06075496488891343,
+ 0.06075496488891343
+ ],
+ "visible": true,
+ "customComponents": []
+ },
+ "model-imported-model_mto6v5w6_09-fox-1": {
+ "name": "root",
+ "nodeName": "root",
+ "nodePath": "0/0",
+ "interactionId": "interaction-a4d35db9",
+ "capabilities": {
+ "selectable": true,
+ "transform": true,
+ "visible": true,
+ "material": false,
+ "animatable": true
+ },
+ "position": [
+ 0,
+ 0,
+ 0
+ ],
+ "rotation": [
+ 0,
+ 0,
+ 0
+ ],
+ "scale": [
+ 1,
+ 1,
+ 1
+ ],
+ "visible": true,
+ "customComponents": []
+ },
+ "model-imported-model_mto6v5w6_09-fox-2": {
+ "name": "_rootJoint",
+ "nodeName": "_rootJoint",
+ "nodePath": "0/0/0",
+ "interactionId": "interaction-4476c556",
+ "capabilities": {
+ "selectable": true,
+ "transform": true,
+ "visible": true,
+ "material": false,
+ "animatable": true
+ },
+ "position": [
+ 0,
+ 0,
+ 0
+ ],
+ "rotation": [
+ 0,
+ 0,
+ 0
+ ],
+ "scale": [
+ 1,
+ 1,
+ 1
+ ],
+ "visible": true,
+ "customComponents": []
+ },
+ "model-imported-model_mto6v5w6_09-fox-3": {
+ "name": "b_Root_00",
+ "nodeName": "b_Root_00",
+ "nodePath": "0/0/0/0",
+ "interactionId": "interaction-ffc45d11",
+ "capabilities": {
+ "selectable": true,
+ "transform": true,
+ "visible": true,
+ "material": false,
+ "animatable": true
+ },
+ "position": [
+ 0,
+ 0,
+ 0
+ ],
+ "rotation": [
+ -1.5708000357176293,
+ 0,
+ 0
+ ],
+ "scale": [
+ 1,
+ 1,
+ 1
+ ],
+ "visible": true,
+ "customComponents": []
+ },
+ "model-imported-model_mto6v5w6_09-fox-4": {
+ "name": "b_Hip_01",
+ "nodeName": "b_Hip_01",
+ "nodePath": "0/0/0/0/0",
+ "interactionId": "interaction-7a75e39e",
+ "capabilities": {
+ "selectable": true,
+ "transform": true,
+ "visible": true,
+ "material": false,
+ "animatable": true
+ },
+ "position": [
+ 0.000001241521920852674,
+ 22.061977333358605,
+ 34.62373020761327
+ ],
+ "rotation": [
+ 0.44722050284761794,
+ -1.5707951637404125,
+ 0
+ ],
+ "scale": [
+ 1,
+ 1,
+ 1
+ ],
+ "visible": true,
+ "customComponents": []
+ },
+ "model-imported-model_mto6v5w6_09-fox-5": {
+ "name": "b_Spine01_02",
+ "nodeName": "b_Spine01_02",
+ "nodePath": "0/0/0/0/0/0",
+ "interactionId": "interaction-663ea469",
+ "capabilities": {
+ "selectable": true,
+ "transform": true,
+ "visible": true,
+ "material": false,
+ "animatable": true
+ },
+ "position": [
+ 12.850601196289062,
+ 0,
+ 0
+ ],
+ "rotation": [
+ -3.357310257291308e-7,
+ -3.316574913360067e-8,
+ -1.2102038452161539
+ ],
+ "scale": [
+ 1,
+ 1,
+ 1
+ ],
+ "visible": true,
+ "customComponents": []
+ },
+ "model-imported-model_mto6v5w6_09-fox-6": {
+ "name": "b_Spine02_03",
+ "nodeName": "b_Spine02_03",
+ "nodePath": "0/0/0/0/0/0/0",
+ "interactionId": "interaction-46867126",
+ "capabilities": {
+ "selectable": true,
+ "transform": true,
+ "visible": true,
+ "material": false,
+ "animatable": true
+ },
+ "position": [
+ 21.65575408935547,
+ -0.000118255615234375,
+ 0
+ ],
+ "rotation": [
+ 8.772651057591018e-8,
+ 3.3355428835535634e-8,
+ 0.3633406824515624
+ ],
+ "scale": [
+ 1,
+ 1,
+ 1
+ ],
+ "visible": true,
+ "customComponents": []
+ },
+ "model-imported-model_mto6v5w6_09-fox-7": {
+ "name": "b_Neck_04",
+ "nodeName": "b_Neck_04",
+ "nodePath": "0/0/0/0/0/0/0/0",
+ "interactionId": "interaction-27148d81",
+ "capabilities": {
+ "selectable": true,
+ "transform": true,
+ "visible": true,
+ "material": false,
+ "animatable": true
+ },
+ "position": [
+ 25.64914321899414,
+ 0,
+ 0
+ ],
+ "rotation": [
+ 6.423807629998062e-15,
+ 1.9993472402144555e-16,
+ 0.031113979193075042
+ ],
+ "scale": [
+ 1,
+ 1,
+ 1
+ ],
+ "visible": true,
+ "customComponents": []
+ },
+ "model-imported-model_mto6v5w6_09-fox-8": {
+ "name": "b_Head_05",
+ "nodeName": "b_Head_05",
+ "nodePath": "0/0/0/0/0/0/0/0/0",
+ "interactionId": "interaction-b24eef2e",
+ "capabilities": {
+ "selectable": true,
+ "transform": true,
+ "visible": true,
+ "material": false,
+ "animatable": true
+ },
+ "position": [
+ 13.376960754394531,
+ 0,
+ 0
+ ],
+ "rotation": [
+ 3.253825627358364e-8,
+ -1.577320798309453e-8,
+ -0.451380500897834
+ ],
+ "scale": [
+ 1,
+ 1,
+ 1
+ ],
+ "visible": true,
+ "customComponents": []
+ },
+ "model-imported-model_mto6v5w6_09-fox-9": {
+ "name": "b_RightUpperArm_06",
+ "nodeName": "b_RightUpperArm_06",
+ "nodePath": "0/0/0/0/0/0/0/1",
+ "interactionId": "interaction-26148bee",
+ "capabilities": {
+ "selectable": true,
+ "transform": true,
+ "visible": true,
+ "material": false,
+ "animatable": true
+ },
+ "position": [
+ 18.677913665771484,
+ -4.297340393066406,
+ 6.9675750732421875
+ ],
+ "rotation": [
+ -0.035644184619881884,
+ -0.047001863061712265,
+ -1.2972005346348685
+ ],
+ "scale": [
+ 1,
+ 1,
+ 1
+ ],
+ "visible": true,
+ "customComponents": []
+ },
+ "model-imported-model_mto6v5w6_09-fox-10": {
+ "name": "b_RightForeArm_07",
+ "nodeName": "b_RightForeArm_07",
+ "nodePath": "0/0/0/0/0/0/0/1/0",
+ "interactionId": "interaction-36896559",
+ "capabilities": {
+ "selectable": true,
+ "transform": true,
+ "visible": true,
+ "material": false,
+ "animatable": true
+ },
+ "position": [
+ 23.04512596130371,
+ 0,
+ 0
+ ],
+ "rotation": [
+ 2.1355818997049898e-8,
+ 7.233656206674673e-8,
+ 1.2561843405841544
+ ],
+ "scale": [
+ 1,
+ 1,
+ 1
+ ],
+ "visible": true,
+ "customComponents": []
+ },
+ "model-imported-model_mto6v5w6_09-fox-11": {
+ "name": "b_RightHand_08",
+ "nodeName": "b_RightHand_08",
+ "nodePath": "0/0/0/0/0/0/0/1/0/0",
+ "interactionId": "interaction-57a124f6",
+ "capabilities": {
+ "selectable": true,
+ "transform": true,
+ "visible": true,
+ "material": false,
+ "animatable": true
+ },
+ "position": [
+ 19.350055694580078,
+ -0.14598655700683594,
+ 0
+ ],
+ "rotation": [
+ 0.005245101551986586,
+ 0.05200705984217871,
+ -0.267165337534188
+ ],
+ "scale": [
+ 1,
+ 1,
+ 1
+ ],
+ "visible": true,
+ "customComponents": []
+ },
+ "model-imported-model_mto6v5w6_09-fox-12": {
+ "name": "b_LeftUpperArm_09",
+ "nodeName": "b_LeftUpperArm_09",
+ "nodePath": "0/0/0/0/0/0/0/2",
+ "interactionId": "interaction-25148a5b",
+ "capabilities": {
+ "selectable": true,
+ "transform": true,
+ "visible": true,
+ "material": false,
+ "animatable": true
+ },
+ "position": [
+ 18.67791748046875,
+ -4.297344207763672,
+ -6.967987060546875
+ ],
+ "rotation": [
+ -0.00020118104561539401,
+ 0.0009350561550523671,
+ -1.5148902559141275
+ ],
+ "scale": [
+ 1,
+ 1,
+ 1
+ ],
+ "visible": true,
+ "customComponents": []
+ },
+ "model-imported-model_mto6v5w6_09-fox-13": {
+ "name": "b_LeftForeArm_010",
+ "nodeName": "b_LeftForeArm_010",
+ "nodePath": "0/0/0/0/0/0/0/2/0",
+ "interactionId": "interaction-5c502ac4",
+ "capabilities": {
+ "selectable": true,
+ "transform": true,
+ "visible": true,
+ "material": false,
+ "animatable": true
+ },
+ "position": [
+ 23.045124053955078,
+ 0,
+ 0
+ ],
+ "rotation": [
+ 2.5220861991717696e-8,
+ 7.109903232964989e-8,
+ 1.2307106255258764
+ ],
+ "scale": [
+ 1,
+ 1,
+ 1
+ ],
+ "visible": true,
+ "customComponents": []
+ },
+ "model-imported-model_mto6v5w6_09-fox-14": {
+ "name": "b_LeftHand_011",
+ "nodeName": "b_LeftHand_011",
+ "nodePath": "0/0/0/0/0/0/0/2/0/0",
+ "interactionId": "interaction-192b4cd3",
+ "capabilities": {
+ "selectable": true,
+ "transform": true,
+ "visible": true,
+ "material": false,
+ "animatable": true
+ },
+ "position": [
+ 19.350051879882812,
+ -0.14599037170410156,
+ 0
+ ],
+ "rotation": [
+ 0.004279557751527162,
+ -0.03344276494440009,
+ -1.5985291598689726
+ ],
+ "scale": [
+ 1,
+ 1,
+ 1
+ ],
+ "visible": true,
+ "customComponents": []
+ },
+ "model-imported-model_mto6v5w6_09-fox-15": {
+ "name": "b_Tail01_012",
+ "nodeName": "b_Tail01_012",
+ "nodePath": "0/0/0/0/0/1",
+ "interactionId": "interaction-653ea2d6",
+ "capabilities": {
+ "selectable": true,
+ "transform": true,
+ "visible": true,
+ "material": false,
+ "animatable": true
+ },
+ "position": [
+ 4.2603759765625,
+ 15.958770751953125,
+ 0
+ ],
+ "rotation": [
+ -5.364745216052604e-7,
+ 3.424797608447095e-7,
+ 1.9941304874941974
+ ],
+ "scale": [
+ 1,
+ 1,
+ 1
+ ],
+ "visible": true,
+ "customComponents": []
+ },
+ "model-imported-model_mto6v5w6_09-fox-16": {
+ "name": "b_Tail02_013",
+ "nodeName": "b_Tail02_013",
+ "nodePath": "0/0/0/0/0/1/0",
+ "interactionId": "interaction-6c4d3691",
+ "capabilities": {
+ "selectable": true,
+ "transform": true,
+ "visible": true,
+ "material": false,
+ "animatable": true
+ },
+ "position": [
+ 12.411918640136719,
+ 0,
+ 0
+ ],
+ "rotation": [
+ -6.738507958375962e-23,
+ -5.2439168243310865e-23,
+ 0.3128105211171357
+ ],
+ "scale": [
+ 1,
+ 1,
+ 1
+ ],
+ "visible": true,
+ "customComponents": []
+ },
+ "model-imported-model_mto6v5w6_09-fox-17": {
+ "name": "b_Tail03_014",
+ "nodeName": "b_Tail03_014",
+ "nodePath": "0/0/0/0/0/1/0/0",
+ "interactionId": "interaction-e93e191e",
+ "capabilities": {
+ "selectable": true,
+ "transform": true,
+ "visible": true,
+ "material": false,
+ "animatable": true
+ },
+ "position": [
+ 24.24032211303711,
+ 0,
+ 0
+ ],
+ "rotation": [
+ -5.7486339033124146e-24,
+ 1.1669178332583734e-22,
+ -0.012937892408322462
+ ],
+ "scale": [
+ 1,
+ 1,
+ 1
+ ],
+ "visible": true,
+ "customComponents": []
+ },
+ "model-imported-model_mto6v5w6_09-fox-18": {
+ "name": "b_LeftLeg01_015",
+ "nodeName": "b_LeftLeg01_015",
+ "nodePath": "0/0/0/0/0/2",
+ "interactionId": "interaction-643ea143",
+ "capabilities": {
+ "selectable": true,
+ "transform": true,
+ "visible": true,
+ "material": false,
+ "animatable": true
+ },
+ "position": [
+ 4.813770294189453,
+ 5.154018402099609,
+ -6.968006134033203
+ ],
+ "rotation": [
+ 0.1223493637902729,
+ -0.09724770930295147,
+ 2.9630295165414986
+ ],
+ "scale": [
+ 1,
+ 1,
+ 1
+ ],
+ "visible": true,
+ "customComponents": []
+ },
+ "model-imported-model_mto6v5w6_09-fox-19": {
+ "name": "b_LeftLeg02_016",
+ "nodeName": "b_LeftLeg02_016",
+ "nodePath": "0/0/0/0/0/2/0",
+ "interactionId": "interaction-7175f8dc",
+ "capabilities": {
+ "selectable": true,
+ "transform": true,
+ "visible": true,
+ "material": false,
+ "animatable": true
+ },
+ "position": [
+ 18.944175720214844,
+ 0,
+ 0
+ ],
+ "rotation": [
+ 1.2545483692289454e-8,
+ 3.728483680761731e-8,
+ -0.522293355530293
+ ],
+ "scale": [
+ 1,
+ 1,
+ 1
+ ],
+ "visible": true,
+ "customComponents": []
+ },
+ "model-imported-model_mto6v5w6_09-fox-20": {
+ "name": "b_LeftFoot01_017",
+ "nodeName": "b_LeftFoot01_017",
+ "nodePath": "0/0/0/0/0/2/0/0",
+ "interactionId": "interaction-f5ed353b",
+ "capabilities": {
+ "selectable": true,
+ "transform": true,
+ "visible": true,
+ "material": false,
+ "animatable": true
+ },
+ "position": [
+ 17.942811965942383,
+ 0,
+ 0
+ ],
+ "rotation": [
+ -0.03128474386729028,
+ -0.15471927051895154,
+ 0.7774909055508243
+ ],
+ "scale": [
+ 1,
+ 1,
+ 1
+ ],
+ "visible": true,
+ "customComponents": []
+ },
+ "model-imported-model_mto6v5w6_09-fox-21": {
+ "name": "b_LeftFoot02_018",
+ "nodeName": "b_LeftFoot02_018",
+ "nodePath": "0/0/0/0/0/2/0/0/0",
+ "interactionId": "interaction-1bbe01a4",
+ "capabilities": {
+ "selectable": true,
+ "transform": true,
+ "visible": true,
+ "material": false,
+ "animatable": true
+ },
+ "position": [
+ 15.779938697814941,
+ 0,
+ 0
+ ],
+ "rotation": [
+ 0,
+ 0,
+ 1.158241570830627
+ ],
+ "scale": [
+ 1,
+ 1,
+ 1
+ ],
+ "visible": true,
+ "customComponents": []
+ },
+ "model-imported-model_mto6v5w6_09-fox-22": {
+ "name": "b_RightLeg01_019",
+ "nodeName": "b_RightLeg01_019",
+ "nodePath": "0/0/0/0/0/3",
+ "interactionId": "interaction-633e9fb0",
+ "capabilities": {
+ "selectable": true,
+ "transform": true,
+ "visible": true,
+ "material": false,
+ "animatable": true
+ },
+ "position": [
+ 4.813777923583984,
+ 5.154026031494141,
+ 6.967563629150391
+ ],
+ "rotation": [
+ -0.10137208185544387,
+ 0.05534659522395753,
+ -2.3613690068554236
+ ],
+ "scale": [
+ 1,
+ 1,
+ 1
+ ],
+ "visible": true,
+ "customComponents": []
+ },
+ "model-imported-model_mto6v5w6_09-fox-23": {
+ "name": "b_RightLeg02_020",
+ "nodeName": "b_RightLeg02_020",
+ "nodePath": "0/0/0/0/0/3/0",
+ "interactionId": "interaction-95ff89c7",
+ "capabilities": {
+ "selectable": true,
+ "transform": true,
+ "visible": true,
+ "material": false,
+ "animatable": true
+ },
+ "position": [
+ 18.944183349609375,
+ 0,
+ 0
+ ],
+ "rotation": [
+ 2.7890192431350882e-8,
+ 1.4750479199499402e-8,
+ -1.0710323806538369
+ ],
+ "scale": [
+ 1,
+ 1,
+ 1
+ ],
+ "visible": true,
+ "customComponents": []
+ },
+ "model-imported-model_mto6v5w6_09-fox-24": {
+ "name": "b_RightFoot01_021",
+ "nodeName": "b_RightFoot01_021",
+ "nodePath": "0/0/0/0/0/3/0/0",
+ "interactionId": "interaction-a157d498",
+ "capabilities": {
+ "selectable": true,
+ "transform": true,
+ "visible": true,
+ "material": false,
+ "animatable": true
+ },
+ "position": [
+ 17.94281005859375,
+ 0,
+ 0
+ ],
+ "rotation": [
+ -0.14401413968163027,
+ 0.2814735368347316,
+ 1.4591240233677443
+ ],
+ "scale": [
+ 1,
+ 1,
+ 1
+ ],
+ "visible": true,
+ "customComponents": []
+ },
+ "model-imported-model_mto6v5w6_09-fox-25": {
+ "name": "b_RightFoot02_022",
+ "nodeName": "b_RightFoot02_022",
+ "nodePath": "0/0/0/0/0/3/0/0/0",
+ "interactionId": "interaction-ebd38e3f",
+ "capabilities": {
+ "selectable": true,
+ "transform": true,
+ "visible": true,
+ "material": false,
+ "animatable": true
+ },
+ "position": [
+ 15.779935836791992,
+ 0,
+ 0
+ ],
+ "rotation": [
+ 0,
+ 0,
+ 1.158241570830627
+ ],
+ "scale": [
+ 1,
+ 1,
+ 1
+ ],
+ "visible": true,
+ "customComponents": []
+ },
+ "model-imported-model_mto6v5w6_09-fox-26": {
+ "name": "fox",
+ "nodeName": "fox",
+ "nodePath": "0/1",
+ "interactionId": "interaction-a3d35c26",
+ "capabilities": {
+ "selectable": true,
+ "transform": true,
+ "visible": true,
+ "material": true,
+ "animatable": true
+ },
+ "position": [
+ 0.25,
+ 0,
+ 0
+ ],
+ "rotation": [
+ 0,
+ 0,
+ 0
+ ],
+ "scale": [
+ 1,
+ 1,
+ 1
+ ],
+ "visible": true,
+ "customComponents": [],
+ "materials": [
+ {
+ "color": "ffffff",
+ "emissive": "000000",
+ "emissiveIntensity": 1,
+ "metalness": 0,
+ "roughness": 0.3,
+ "opacity": 1,
+ "transparent": false,
+ "depthWrite": true
+ }
+ ]
+ }
+ },
+ "selectionScope": "part",
+ "nativeAnimation": {
+ "clipIndex": 2,
+ "clipName": "Run"
+ }
+ },
+ "environment": {
+ "mode": "placeholder",
+ "environmentType": "outdoor",
+ "sourceName": "内置野外综合训练环境",
+ "sizeBytes": 0,
+ "assetCode": "",
+ "storageUri": "",
+ "resourceUrl": "",
+ "objects": {
+ "outdoor-scene": {
+ "name": "野外综合训练环境",
+ "position": [
+ 0,
+ 0,
+ 0
+ ],
+ "rotation": [
+ 0,
+ 0,
+ 0
+ ],
+ "scale": [
+ 1,
+ 1,
+ 1
+ ],
+ "visible": true,
+ "customComponents": []
+ },
+ "outdoor-site": {
+ "name": "场地与道路",
+ "position": [
+ 0,
+ 0,
+ 0
+ ],
+ "rotation": [
+ 0,
+ 0,
+ 0
+ ],
+ "scale": [
+ 1,
+ 1,
+ 1
+ ],
+ "visible": true,
+ "customComponents": []
+ },
+ "outdoor-ground": {
+ "name": "室外训练场地",
+ "position": [
+ 0,
+ -0.065,
+ 0
+ ],
+ "rotation": [
+ -1.5707963267948966,
+ 0,
+ 0
+ ],
+ "scale": [
+ 1,
+ 1,
+ 1
+ ],
+ "visible": true,
+ "customComponents": [],
+ "materials": [
+ {
+ "color": "263529",
+ "emissive": "000000",
+ "emissiveIntensity": 1,
+ "metalness": 0,
+ "roughness": 0.98,
+ "opacity": 1,
+ "transparent": false,
+ "depthWrite": true
+ }
+ ]
+ },
+ "outdoor-apron": {
+ "name": "装备操作坪",
+ "position": [
+ 0,
+ -0.045,
+ 0
+ ],
+ "rotation": [
+ -1.5707963267948966,
+ 0,
+ 0
+ ],
+ "scale": [
+ 1,
+ 1,
+ 1
+ ],
+ "visible": true,
+ "customComponents": [],
+ "materials": [
+ {
+ "color": "39444a",
+ "emissive": "000000",
+ "emissiveIntensity": 1,
+ "metalness": 0.03,
+ "roughness": 0.92,
+ "opacity": 1,
+ "transparent": false,
+ "depthWrite": true
+ }
+ ]
+ },
+ "outdoor-road": {
+ "name": "保障通道",
+ "position": [
+ 0,
+ -0.035,
+ 17
+ ],
+ "rotation": [
+ -1.5707963267948966,
+ 0,
+ 0
+ ],
+ "scale": [
+ 1,
+ 1,
+ 1
+ ],
+ "visible": true,
+ "customComponents": [],
+ "materials": [
+ {
+ "color": "202a31",
+ "emissive": "000000",
+ "emissiveIntensity": 1,
+ "metalness": 0.02,
+ "roughness": 0.95,
+ "opacity": 1,
+ "transparent": false,
+ "depthWrite": true
+ }
+ ]
+ },
+ "outdoor-road-line-1": {
+ "name": "道路中心线 1",
+ "position": [
+ -28.699999999999996,
+ 0.01,
+ 17
+ ],
+ "rotation": [
+ 0,
+ 0,
+ 0
+ ],
+ "scale": [
+ 1,
+ 1,
+ 1
+ ],
+ "visible": true,
+ "customComponents": [],
+ "materials": [
+ {
+ "color": "e5c64c",
+ "emissive": "302600",
+ "emissiveIntensity": 0.18,
+ "metalness": 0.08,
+ "roughness": 0.72,
+ "opacity": 1,
+ "transparent": false,
+ "depthWrite": true
+ }
+ ]
+ },
+ "outdoor-road-line-2": {
+ "name": "道路中心线 2",
+ "position": [
+ -24.599999999999998,
+ 0.01,
+ 17
+ ],
+ "rotation": [
+ 0,
+ 0,
+ 0
+ ],
+ "scale": [
+ 1,
+ 1,
+ 1
+ ],
+ "visible": true,
+ "customComponents": [],
+ "materials": [
+ {
+ "color": "e5c64c",
+ "emissive": "302600",
+ "emissiveIntensity": 0.18,
+ "metalness": 0.08,
+ "roughness": 0.72,
+ "opacity": 1,
+ "transparent": false,
+ "depthWrite": true
+ }
+ ]
+ },
+ "outdoor-road-line-3": {
+ "name": "道路中心线 3",
+ "position": [
+ -20.5,
+ 0.01,
+ 17
+ ],
+ "rotation": [
+ 0,
+ 0,
+ 0
+ ],
+ "scale": [
+ 1,
+ 1,
+ 1
+ ],
+ "visible": true,
+ "customComponents": [],
+ "materials": [
+ {
+ "color": "e5c64c",
+ "emissive": "302600",
+ "emissiveIntensity": 0.18,
+ "metalness": 0.08,
+ "roughness": 0.72,
+ "opacity": 1,
+ "transparent": false,
+ "depthWrite": true
+ }
+ ]
+ },
+ "outdoor-road-line-4": {
+ "name": "道路中心线 4",
+ "position": [
+ -16.4,
+ 0.01,
+ 17
+ ],
+ "rotation": [
+ 0,
+ 0,
+ 0
+ ],
+ "scale": [
+ 1,
+ 1,
+ 1
+ ],
+ "visible": true,
+ "customComponents": [],
+ "materials": [
+ {
+ "color": "e5c64c",
+ "emissive": "302600",
+ "emissiveIntensity": 0.18,
+ "metalness": 0.08,
+ "roughness": 0.72,
+ "opacity": 1,
+ "transparent": false,
+ "depthWrite": true
+ }
+ ]
+ },
+ "outdoor-road-line-5": {
+ "name": "道路中心线 5",
+ "position": [
+ -12.299999999999999,
+ 0.01,
+ 17
+ ],
+ "rotation": [
+ 0,
+ 0,
+ 0
+ ],
+ "scale": [
+ 1,
+ 1,
+ 1
+ ],
+ "visible": true,
+ "customComponents": [],
+ "materials": [
+ {
+ "color": "e5c64c",
+ "emissive": "302600",
+ "emissiveIntensity": 0.18,
+ "metalness": 0.08,
+ "roughness": 0.72,
+ "opacity": 1,
+ "transparent": false,
+ "depthWrite": true
+ }
+ ]
+ },
+ "outdoor-road-line-6": {
+ "name": "道路中心线 6",
+ "position": [
+ -8.2,
+ 0.01,
+ 17
+ ],
+ "rotation": [
+ 0,
+ 0,
+ 0
+ ],
+ "scale": [
+ 1,
+ 1,
+ 1
+ ],
+ "visible": true,
+ "customComponents": [],
+ "materials": [
+ {
+ "color": "e5c64c",
+ "emissive": "302600",
+ "emissiveIntensity": 0.18,
+ "metalness": 0.08,
+ "roughness": 0.72,
+ "opacity": 1,
+ "transparent": false,
+ "depthWrite": true
+ }
+ ]
+ },
+ "outdoor-road-line-7": {
+ "name": "道路中心线 7",
+ "position": [
+ -4.1,
+ 0.01,
+ 17
+ ],
+ "rotation": [
+ 0,
+ 0,
+ 0
+ ],
+ "scale": [
+ 1,
+ 1,
+ 1
+ ],
+ "visible": true,
+ "customComponents": [],
+ "materials": [
+ {
+ "color": "e5c64c",
+ "emissive": "302600",
+ "emissiveIntensity": 0.18,
+ "metalness": 0.08,
+ "roughness": 0.72,
+ "opacity": 1,
+ "transparent": false,
+ "depthWrite": true
+ }
+ ]
+ },
+ "outdoor-road-line-8": {
+ "name": "道路中心线 8",
+ "position": [
+ 0,
+ 0.01,
+ 17
+ ],
+ "rotation": [
+ 0,
+ 0,
+ 0
+ ],
+ "scale": [
+ 1,
+ 1,
+ 1
+ ],
+ "visible": true,
+ "customComponents": [],
+ "materials": [
+ {
+ "color": "e5c64c",
+ "emissive": "302600",
+ "emissiveIntensity": 0.18,
+ "metalness": 0.08,
+ "roughness": 0.72,
+ "opacity": 1,
+ "transparent": false,
+ "depthWrite": true
+ }
+ ]
+ },
+ "outdoor-road-line-9": {
+ "name": "道路中心线 9",
+ "position": [
+ 4.1,
+ 0.01,
+ 17
+ ],
+ "rotation": [
+ 0,
+ 0,
+ 0
+ ],
+ "scale": [
+ 1,
+ 1,
+ 1
+ ],
+ "visible": true,
+ "customComponents": [],
+ "materials": [
+ {
+ "color": "e5c64c",
+ "emissive": "302600",
+ "emissiveIntensity": 0.18,
+ "metalness": 0.08,
+ "roughness": 0.72,
+ "opacity": 1,
+ "transparent": false,
+ "depthWrite": true
+ }
+ ]
+ },
+ "outdoor-road-line-10": {
+ "name": "道路中心线 10",
+ "position": [
+ 8.2,
+ 0.01,
+ 17
+ ],
+ "rotation": [
+ 0,
+ 0,
+ 0
+ ],
+ "scale": [
+ 1,
+ 1,
+ 1
+ ],
+ "visible": true,
+ "customComponents": [],
+ "materials": [
+ {
+ "color": "e5c64c",
+ "emissive": "302600",
+ "emissiveIntensity": 0.18,
+ "metalness": 0.08,
+ "roughness": 0.72,
+ "opacity": 1,
+ "transparent": false,
+ "depthWrite": true
+ }
+ ]
+ },
+ "outdoor-road-line-11": {
+ "name": "道路中心线 11",
+ "position": [
+ 12.299999999999999,
+ 0.01,
+ 17
+ ],
+ "rotation": [
+ 0,
+ 0,
+ 0
+ ],
+ "scale": [
+ 1,
+ 1,
+ 1
+ ],
+ "visible": true,
+ "customComponents": [],
+ "materials": [
+ {
+ "color": "e5c64c",
+ "emissive": "302600",
+ "emissiveIntensity": 0.18,
+ "metalness": 0.08,
+ "roughness": 0.72,
+ "opacity": 1,
+ "transparent": false,
+ "depthWrite": true
+ }
+ ]
+ },
+ "outdoor-road-line-12": {
+ "name": "道路中心线 12",
+ "position": [
+ 16.4,
+ 0.01,
+ 17
+ ],
+ "rotation": [
+ 0,
+ 0,
+ 0
+ ],
+ "scale": [
+ 1,
+ 1,
+ 1
+ ],
+ "visible": true,
+ "customComponents": [],
+ "materials": [
+ {
+ "color": "e5c64c",
+ "emissive": "302600",
+ "emissiveIntensity": 0.18,
+ "metalness": 0.08,
+ "roughness": 0.72,
+ "opacity": 1,
+ "transparent": false,
+ "depthWrite": true
+ }
+ ]
+ },
+ "outdoor-road-line-13": {
+ "name": "道路中心线 13",
+ "position": [
+ 20.5,
+ 0.01,
+ 17
+ ],
+ "rotation": [
+ 0,
+ 0,
+ 0
+ ],
+ "scale": [
+ 1,
+ 1,
+ 1
+ ],
+ "visible": true,
+ "customComponents": [],
+ "materials": [
+ {
+ "color": "e5c64c",
+ "emissive": "302600",
+ "emissiveIntensity": 0.18,
+ "metalness": 0.08,
+ "roughness": 0.72,
+ "opacity": 1,
+ "transparent": false,
+ "depthWrite": true
+ }
+ ]
+ },
+ "outdoor-road-line-14": {
+ "name": "道路中心线 14",
+ "position": [
+ 24.599999999999998,
+ 0.01,
+ 17
+ ],
+ "rotation": [
+ 0,
+ 0,
+ 0
+ ],
+ "scale": [
+ 1,
+ 1,
+ 1
+ ],
+ "visible": true,
+ "customComponents": [],
+ "materials": [
+ {
+ "color": "e5c64c",
+ "emissive": "302600",
+ "emissiveIntensity": 0.18,
+ "metalness": 0.08,
+ "roughness": 0.72,
+ "opacity": 1,
+ "transparent": false,
+ "depthWrite": true
+ }
+ ]
+ },
+ "outdoor-road-line-15": {
+ "name": "道路中心线 15",
+ "position": [
+ 28.699999999999996,
+ 0.01,
+ 17
+ ],
+ "rotation": [
+ 0,
+ 0,
+ 0
+ ],
+ "scale": [
+ 1,
+ 1,
+ 1
+ ],
+ "visible": true,
+ "customComponents": [],
+ "materials": [
+ {
+ "color": "e5c64c",
+ "emissive": "302600",
+ "emissiveIntensity": 0.18,
+ "metalness": 0.08,
+ "roughness": 0.72,
+ "opacity": 1,
+ "transparent": false,
+ "depthWrite": true
+ }
+ ]
+ },
+ "outdoor-safety--17.6--11": {
+ "name": "操作区安全标线",
+ "position": [
+ -17.6,
+ 0.012,
+ -11
+ ],
+ "rotation": [
+ 0,
+ 0.5654866776461628,
+ 0
+ ],
+ "scale": [
+ 1,
+ 1,
+ 1
+ ],
+ "visible": true,
+ "customComponents": [],
+ "materials": [
+ {
+ "color": "e5c64c",
+ "emissive": "302600",
+ "emissiveIntensity": 0.18,
+ "metalness": 0.08,
+ "roughness": 0.72,
+ "opacity": 1,
+ "transparent": false,
+ "depthWrite": true
+ }
+ ]
+ },
+ "outdoor-safety--17.6--8.4": {
+ "name": "操作区安全标线",
+ "position": [
+ -17.6,
+ 0.012,
+ -8.4
+ ],
+ "rotation": [
+ 0,
+ 0.5654866776461628,
+ 0
+ ],
+ "scale": [
+ 1,
+ 1,
+ 1
+ ],
+ "visible": true,
+ "customComponents": [],
+ "materials": [
+ {
+ "color": "e5c64c",
+ "emissive": "302600",
+ "emissiveIntensity": 0.18,
+ "metalness": 0.08,
+ "roughness": 0.72,
+ "opacity": 1,
+ "transparent": false,
+ "depthWrite": true
+ }
+ ]
+ },
+ "outdoor-safety--17.6--5.800000000000001": {
+ "name": "操作区安全标线",
+ "position": [
+ -17.6,
+ 0.012,
+ -5.800000000000001
+ ],
+ "rotation": [
+ 0,
+ 0.5654866776461628,
+ 0
+ ],
+ "scale": [
+ 1,
+ 1,
+ 1
+ ],
+ "visible": true,
+ "customComponents": [],
+ "materials": [
+ {
+ "color": "e5c64c",
+ "emissive": "302600",
+ "emissiveIntensity": 0.18,
+ "metalness": 0.08,
+ "roughness": 0.72,
+ "opacity": 1,
+ "transparent": false,
+ "depthWrite": true
+ }
+ ]
+ },
+ "outdoor-safety--17.6--3.2000000000000006": {
+ "name": "操作区安全标线",
+ "position": [
+ -17.6,
+ 0.012,
+ -3.2000000000000006
+ ],
+ "rotation": [
+ 0,
+ 0.5654866776461628,
+ 0
+ ],
+ "scale": [
+ 1,
+ 1,
+ 1
+ ],
+ "visible": true,
+ "customComponents": [],
+ "materials": [
+ {
+ "color": "e5c64c",
+ "emissive": "302600",
+ "emissiveIntensity": 0.18,
+ "metalness": 0.08,
+ "roughness": 0.72,
+ "opacity": 1,
+ "transparent": false,
+ "depthWrite": true
+ }
+ ]
+ },
+ "outdoor-safety--17.6--0.6000000000000005": {
+ "name": "操作区安全标线",
+ "position": [
+ -17.6,
+ 0.012,
+ -0.6000000000000005
+ ],
+ "rotation": [
+ 0,
+ 0.5654866776461628,
+ 0
+ ],
+ "scale": [
+ 1,
+ 1,
+ 1
+ ],
+ "visible": true,
+ "customComponents": [],
+ "materials": [
+ {
+ "color": "e5c64c",
+ "emissive": "302600",
+ "emissiveIntensity": 0.18,
+ "metalness": 0.08,
+ "roughness": 0.72,
+ "opacity": 1,
+ "transparent": false,
+ "depthWrite": true
+ }
+ ]
+ },
+ "outdoor-safety--17.6-1.9999999999999996": {
+ "name": "操作区安全标线",
+ "position": [
+ -17.6,
+ 0.012,
+ 1.9999999999999996
+ ],
+ "rotation": [
+ 0,
+ 0.5654866776461628,
+ 0
+ ],
+ "scale": [
+ 1,
+ 1,
+ 1
+ ],
+ "visible": true,
+ "customComponents": [],
+ "materials": [
+ {
+ "color": "e5c64c",
+ "emissive": "302600",
+ "emissiveIntensity": 0.18,
+ "metalness": 0.08,
+ "roughness": 0.72,
+ "opacity": 1,
+ "transparent": false,
+ "depthWrite": true
+ }
+ ]
+ },
+ "outdoor-safety--17.6-4.6": {
+ "name": "操作区安全标线",
+ "position": [
+ -17.6,
+ 0.012,
+ 4.6
+ ],
+ "rotation": [
+ 0,
+ 0.5654866776461628,
+ 0
+ ],
+ "scale": [
+ 1,
+ 1,
+ 1
+ ],
+ "visible": true,
+ "customComponents": [],
+ "materials": [
+ {
+ "color": "e5c64c",
+ "emissive": "302600",
+ "emissiveIntensity": 0.18,
+ "metalness": 0.08,
+ "roughness": 0.72,
+ "opacity": 1,
+ "transparent": false,
+ "depthWrite": true
+ }
+ ]
+ },
+ "outdoor-safety--17.6-7.199999999999999": {
+ "name": "操作区安全标线",
+ "position": [
+ -17.6,
+ 0.012,
+ 7.199999999999999
+ ],
+ "rotation": [
+ 0,
+ 0.5654866776461628,
+ 0
+ ],
+ "scale": [
+ 1,
+ 1,
+ 1
+ ],
+ "visible": true,
+ "customComponents": [],
+ "materials": [
+ {
+ "color": "e5c64c",
+ "emissive": "302600",
+ "emissiveIntensity": 0.18,
+ "metalness": 0.08,
+ "roughness": 0.72,
+ "opacity": 1,
+ "transparent": false,
+ "depthWrite": true
+ }
+ ]
+ },
+ "outdoor-safety--17.6-9.799999999999999": {
+ "name": "操作区安全标线",
+ "position": [
+ -17.6,
+ 0.012,
+ 9.799999999999999
+ ],
+ "rotation": [
+ 0,
+ 0.5654866776461628,
+ 0
+ ],
+ "scale": [
+ 1,
+ 1,
+ 1
+ ],
+ "visible": true,
+ "customComponents": [],
+ "materials": [
+ {
+ "color": "e5c64c",
+ "emissive": "302600",
+ "emissiveIntensity": 0.18,
+ "metalness": 0.08,
+ "roughness": 0.72,
+ "opacity": 1,
+ "transparent": false,
+ "depthWrite": true
+ }
+ ]
+ },
+ "outdoor-safety-17.6--11": {
+ "name": "操作区安全标线",
+ "position": [
+ 17.6,
+ 0.012,
+ -11
+ ],
+ "rotation": [
+ 0,
+ 0.5654866776461628,
+ 0
+ ],
+ "scale": [
+ 1,
+ 1,
+ 1
+ ],
+ "visible": true,
+ "customComponents": [],
+ "materials": [
+ {
+ "color": "e5c64c",
+ "emissive": "302600",
+ "emissiveIntensity": 0.18,
+ "metalness": 0.08,
+ "roughness": 0.72,
+ "opacity": 1,
+ "transparent": false,
+ "depthWrite": true
+ }
+ ]
+ },
+ "outdoor-safety-17.6--8.4": {
+ "name": "操作区安全标线",
+ "position": [
+ 17.6,
+ 0.012,
+ -8.4
+ ],
+ "rotation": [
+ 0,
+ 0.5654866776461628,
+ 0
+ ],
+ "scale": [
+ 1,
+ 1,
+ 1
+ ],
+ "visible": true,
+ "customComponents": [],
+ "materials": [
+ {
+ "color": "e5c64c",
+ "emissive": "302600",
+ "emissiveIntensity": 0.18,
+ "metalness": 0.08,
+ "roughness": 0.72,
+ "opacity": 1,
+ "transparent": false,
+ "depthWrite": true
+ }
+ ]
+ },
+ "outdoor-safety-17.6--5.800000000000001": {
+ "name": "操作区安全标线",
+ "position": [
+ 17.6,
+ 0.012,
+ -5.800000000000001
+ ],
+ "rotation": [
+ 0,
+ 0.5654866776461628,
+ 0
+ ],
+ "scale": [
+ 1,
+ 1,
+ 1
+ ],
+ "visible": true,
+ "customComponents": [],
+ "materials": [
+ {
+ "color": "e5c64c",
+ "emissive": "302600",
+ "emissiveIntensity": 0.18,
+ "metalness": 0.08,
+ "roughness": 0.72,
+ "opacity": 1,
+ "transparent": false,
+ "depthWrite": true
+ }
+ ]
+ },
+ "outdoor-safety-17.6--3.2000000000000006": {
+ "name": "操作区安全标线",
+ "position": [
+ 17.6,
+ 0.012,
+ -3.2000000000000006
+ ],
+ "rotation": [
+ 0,
+ 0.5654866776461628,
+ 0
+ ],
+ "scale": [
+ 1,
+ 1,
+ 1
+ ],
+ "visible": true,
+ "customComponents": [],
+ "materials": [
+ {
+ "color": "e5c64c",
+ "emissive": "302600",
+ "emissiveIntensity": 0.18,
+ "metalness": 0.08,
+ "roughness": 0.72,
+ "opacity": 1,
+ "transparent": false,
+ "depthWrite": true
+ }
+ ]
+ },
+ "outdoor-safety-17.6--0.6000000000000005": {
+ "name": "操作区安全标线",
+ "position": [
+ 17.6,
+ 0.012,
+ -0.6000000000000005
+ ],
+ "rotation": [
+ 0,
+ 0.5654866776461628,
+ 0
+ ],
+ "scale": [
+ 1,
+ 1,
+ 1
+ ],
+ "visible": true,
+ "customComponents": [],
+ "materials": [
+ {
+ "color": "e5c64c",
+ "emissive": "302600",
+ "emissiveIntensity": 0.18,
+ "metalness": 0.08,
+ "roughness": 0.72,
+ "opacity": 1,
+ "transparent": false,
+ "depthWrite": true
+ }
+ ]
+ },
+ "outdoor-safety-17.6-1.9999999999999996": {
+ "name": "操作区安全标线",
+ "position": [
+ 17.6,
+ 0.012,
+ 1.9999999999999996
+ ],
+ "rotation": [
+ 0,
+ 0.5654866776461628,
+ 0
+ ],
+ "scale": [
+ 1,
+ 1,
+ 1
+ ],
+ "visible": true,
+ "customComponents": [],
+ "materials": [
+ {
+ "color": "e5c64c",
+ "emissive": "302600",
+ "emissiveIntensity": 0.18,
+ "metalness": 0.08,
+ "roughness": 0.72,
+ "opacity": 1,
+ "transparent": false,
+ "depthWrite": true
+ }
+ ]
+ },
+ "outdoor-safety-17.6-4.6": {
+ "name": "操作区安全标线",
+ "position": [
+ 17.6,
+ 0.012,
+ 4.6
+ ],
+ "rotation": [
+ 0,
+ 0.5654866776461628,
+ 0
+ ],
+ "scale": [
+ 1,
+ 1,
+ 1
+ ],
+ "visible": true,
+ "customComponents": [],
+ "materials": [
+ {
+ "color": "e5c64c",
+ "emissive": "302600",
+ "emissiveIntensity": 0.18,
+ "metalness": 0.08,
+ "roughness": 0.72,
+ "opacity": 1,
+ "transparent": false,
+ "depthWrite": true
+ }
+ ]
+ },
+ "outdoor-safety-17.6-7.199999999999999": {
+ "name": "操作区安全标线",
+ "position": [
+ 17.6,
+ 0.012,
+ 7.199999999999999
+ ],
+ "rotation": [
+ 0,
+ 0.5654866776461628,
+ 0
+ ],
+ "scale": [
+ 1,
+ 1,
+ 1
+ ],
+ "visible": true,
+ "customComponents": [],
+ "materials": [
+ {
+ "color": "e5c64c",
+ "emissive": "302600",
+ "emissiveIntensity": 0.18,
+ "metalness": 0.08,
+ "roughness": 0.72,
+ "opacity": 1,
+ "transparent": false,
+ "depthWrite": true
+ }
+ ]
+ },
+ "outdoor-safety-17.6-9.799999999999999": {
+ "name": "操作区安全标线",
+ "position": [
+ 17.6,
+ 0.012,
+ 9.799999999999999
+ ],
+ "rotation": [
+ 0,
+ 0.5654866776461628,
+ 0
+ ],
+ "scale": [
+ 1,
+ 1,
+ 1
+ ],
+ "visible": true,
+ "customComponents": [],
+ "materials": [
+ {
+ "color": "e5c64c",
+ "emissive": "302600",
+ "emissiveIntensity": 0.18,
+ "metalness": 0.08,
+ "roughness": 0.72,
+ "opacity": 1,
+ "transparent": false,
+ "depthWrite": true
+ }
+ ]
+ },
+ "outdoor-workshop": {
+ "name": "维修保障厂房",
+ "position": [
+ 0,
+ 0,
+ 0
+ ],
+ "rotation": [
+ 0,
+ 0,
+ 0
+ ],
+ "scale": [
+ 1,
+ 1,
+ 1
+ ],
+ "visible": true,
+ "customComponents": []
+ },
+ "outdoor-workshop-body": {
+ "name": "维修保障厂房主体",
+ "position": [
+ -10,
+ 3.25,
+ -18
+ ],
+ "rotation": [
+ 0,
+ 0,
+ 0
+ ],
+ "scale": [
+ 1,
+ 1,
+ 1
+ ],
+ "visible": true,
+ "customComponents": [],
+ "materials": [
+ {
+ "color": "536675",
+ "emissive": "000000",
+ "emissiveIntensity": 1,
+ "metalness": 0.18,
+ "roughness": 0.78,
+ "opacity": 1,
+ "transparent": false,
+ "depthWrite": true
+ }
+ ]
+ },
+ "outdoor-workshop-roof": {
+ "name": "厂房金属屋面",
+ "position": [
+ -10,
+ 6.65,
+ -18
+ ],
+ "rotation": [
+ 0,
+ 0,
+ 0
+ ],
+ "scale": [
+ 1,
+ 1,
+ 1
+ ],
+ "visible": true,
+ "customComponents": [],
+ "materials": [
+ {
+ "color": "263846",
+ "emissive": "000000",
+ "emissiveIntensity": 1,
+ "metalness": 0.35,
+ "roughness": 0.62,
+ "opacity": 1,
+ "transparent": false,
+ "depthWrite": true
+ }
+ ]
+ },
+ "outdoor-workshop-window-1": {
+ "name": "厂房观察窗 1",
+ "position": [
+ -17,
+ 3.7,
+ -14.21
+ ],
+ "rotation": [
+ 0,
+ 0,
+ 0
+ ],
+ "scale": [
+ 1,
+ 1,
+ 1
+ ],
+ "visible": true,
+ "customComponents": [],
+ "materials": [
+ {
+ "color": "4a89a2",
+ "emissive": "000000",
+ "emissiveIntensity": 1,
+ "metalness": 0.12,
+ "roughness": 0.25,
+ "opacity": 0.68,
+ "transparent": true,
+ "depthWrite": true
+ }
+ ]
+ },
+ "outdoor-workshop-window-2": {
+ "name": "厂房观察窗 2",
+ "position": [
+ -12.4,
+ 3.7,
+ -14.21
+ ],
+ "rotation": [
+ 0,
+ 0,
+ 0
+ ],
+ "scale": [
+ 1,
+ 1,
+ 1
+ ],
+ "visible": true,
+ "customComponents": [],
+ "materials": [
+ {
+ "color": "4a89a2",
+ "emissive": "000000",
+ "emissiveIntensity": 1,
+ "metalness": 0.12,
+ "roughness": 0.25,
+ "opacity": 0.68,
+ "transparent": true,
+ "depthWrite": true
+ }
+ ]
+ },
+ "outdoor-workshop-window-3": {
+ "name": "厂房观察窗 3",
+ "position": [
+ -7.800000000000001,
+ 3.7,
+ -14.21
+ ],
+ "rotation": [
+ 0,
+ 0,
+ 0
+ ],
+ "scale": [
+ 1,
+ 1,
+ 1
+ ],
+ "visible": true,
+ "customComponents": [],
+ "materials": [
+ {
+ "color": "4a89a2",
+ "emissive": "000000",
+ "emissiveIntensity": 1,
+ "metalness": 0.12,
+ "roughness": 0.25,
+ "opacity": 0.68,
+ "transparent": true,
+ "depthWrite": true
+ }
+ ]
+ },
+ "outdoor-workshop-window-4": {
+ "name": "厂房观察窗 4",
+ "position": [
+ -3.200000000000001,
+ 3.7,
+ -14.21
+ ],
+ "rotation": [
+ 0,
+ 0,
+ 0
+ ],
+ "scale": [
+ 1,
+ 1,
+ 1
+ ],
+ "visible": true,
+ "customComponents": [],
+ "materials": [
+ {
+ "color": "4a89a2",
+ "emissive": "000000",
+ "emissiveIntensity": 1,
+ "metalness": 0.12,
+ "roughness": 0.25,
+ "opacity": 0.68,
+ "transparent": true,
+ "depthWrite": true
+ }
+ ]
+ },
+ "outdoor-workshop-door": {
+ "name": "装备出入门",
+ "position": [
+ -3.6,
+ 2.16,
+ -14.18
+ ],
+ "rotation": [
+ 0,
+ 0,
+ 0
+ ],
+ "scale": [
+ 1,
+ 1,
+ 1
+ ],
+ "visible": true,
+ "customComponents": [],
+ "materials": [
+ {
+ "color": "202a31",
+ "emissive": "000000",
+ "emissiveIntensity": 1,
+ "metalness": 0.02,
+ "roughness": 0.95,
+ "opacity": 1,
+ "transparent": false,
+ "depthWrite": true
+ }
+ ]
+ },
+ "outdoor-canopy": {
+ "name": "室外检修棚",
+ "position": [
+ 0,
+ 0,
+ 0
+ ],
+ "rotation": [
+ 0,
+ 0,
+ 0
+ ],
+ "scale": [
+ 1,
+ 1,
+ 1
+ ],
+ "visible": true,
+ "customComponents": []
+ },
+ "outdoor-canopy-roof": {
+ "name": "检修棚顶",
+ "position": [
+ 18,
+ 5.05,
+ -8
+ ],
+ "rotation": [
+ 0,
+ 0,
+ 0
+ ],
+ "scale": [
+ 1,
+ 1,
+ 1
+ ],
+ "visible": true,
+ "customComponents": [],
+ "materials": [
+ {
+ "color": "263846",
+ "emissive": "000000",
+ "emissiveIntensity": 1,
+ "metalness": 0.35,
+ "roughness": 0.62,
+ "opacity": 1,
+ "transparent": false,
+ "depthWrite": true
+ }
+ ]
+ },
+ "outdoor-canopy-column-1": {
+ "name": "检修棚立柱 1",
+ "position": [
+ 12.7,
+ 2.5,
+ -11
+ ],
+ "rotation": [
+ 0,
+ 0,
+ 0
+ ],
+ "scale": [
+ 1,
+ 1,
+ 1
+ ],
+ "visible": true,
+ "customComponents": [],
+ "materials": [
+ {
+ "color": "66757c",
+ "emissive": "000000",
+ "emissiveIntensity": 1,
+ "metalness": 0.48,
+ "roughness": 0.56,
+ "opacity": 1,
+ "transparent": false,
+ "depthWrite": true
+ }
+ ]
+ },
+ "outdoor-canopy-column-2": {
+ "name": "检修棚立柱 2",
+ "position": [
+ 23.3,
+ 2.5,
+ -11
+ ],
+ "rotation": [
+ 0,
+ 0,
+ 0
+ ],
+ "scale": [
+ 1,
+ 1,
+ 1
+ ],
+ "visible": true,
+ "customComponents": [],
+ "materials": [
+ {
+ "color": "66757c",
+ "emissive": "000000",
+ "emissiveIntensity": 1,
+ "metalness": 0.48,
+ "roughness": 0.56,
+ "opacity": 1,
+ "transparent": false,
+ "depthWrite": true
+ }
+ ]
+ },
+ "outdoor-canopy-column-3": {
+ "name": "检修棚立柱 3",
+ "position": [
+ 12.7,
+ 2.5,
+ -5
+ ],
+ "rotation": [
+ 0,
+ 0,
+ 0
+ ],
+ "scale": [
+ 1,
+ 1,
+ 1
+ ],
+ "visible": true,
+ "customComponents": [],
+ "materials": [
+ {
+ "color": "66757c",
+ "emissive": "000000",
+ "emissiveIntensity": 1,
+ "metalness": 0.48,
+ "roughness": 0.56,
+ "opacity": 1,
+ "transparent": false,
+ "depthWrite": true
+ }
+ ]
+ },
+ "outdoor-canopy-column-4": {
+ "name": "检修棚立柱 4",
+ "position": [
+ 23.3,
+ 2.5,
+ -5
+ ],
+ "rotation": [
+ 0,
+ 0,
+ 0
+ ],
+ "scale": [
+ 1,
+ 1,
+ 1
+ ],
+ "visible": true,
+ "customComponents": [],
+ "materials": [
+ {
+ "color": "66757c",
+ "emissive": "000000",
+ "emissiveIntensity": 1,
+ "metalness": 0.48,
+ "roughness": 0.56,
+ "opacity": 1,
+ "transparent": false,
+ "depthWrite": true
+ }
+ ]
+ },
+ "outdoor-service-box": {
+ "name": "室外工具柜",
+ "position": [
+ 22.2,
+ 1.05,
+ -9.8
+ ],
+ "rotation": [
+ 0,
+ 0,
+ 0
+ ],
+ "scale": [
+ 1,
+ 1,
+ 1
+ ],
+ "visible": true,
+ "customComponents": [],
+ "materials": [
+ {
+ "color": "d98d2b",
+ "emissive": "000000",
+ "emissiveIntensity": 1,
+ "metalness": 0.12,
+ "roughness": 0.7,
+ "opacity": 1,
+ "transparent": false,
+ "depthWrite": true
+ }
+ ]
+ },
+ "outdoor-perimeter": {
+ "name": "场地边界与安全设施",
+ "position": [
+ 0,
+ 0,
+ 0
+ ],
+ "rotation": [
+ 0,
+ 0,
+ 0
+ ],
+ "scale": [
+ 1,
+ 1,
+ 1
+ ],
+ "visible": true,
+ "customComponents": []
+ },
+ "outdoor-fence-back-1": {
+ "name": "后侧围栏立柱 1",
+ "position": [
+ -29,
+ 1.1,
+ -25
+ ],
+ "rotation": [
+ 0,
+ 0,
+ 0
+ ],
+ "scale": [
+ 1,
+ 1,
+ 1
+ ],
+ "visible": true,
+ "customComponents": [],
+ "materials": [
+ {
+ "color": "66757c",
+ "emissive": "000000",
+ "emissiveIntensity": 1,
+ "metalness": 0.48,
+ "roughness": 0.56,
+ "opacity": 1,
+ "transparent": false,
+ "depthWrite": true
+ }
+ ]
+ },
+ "outdoor-fence-back-2": {
+ "name": "后侧围栏立柱 2",
+ "position": [
+ -23.2,
+ 1.1,
+ -25
+ ],
+ "rotation": [
+ 0,
+ 0,
+ 0
+ ],
+ "scale": [
+ 1,
+ 1,
+ 1
+ ],
+ "visible": true,
+ "customComponents": [],
+ "materials": [
+ {
+ "color": "66757c",
+ "emissive": "000000",
+ "emissiveIntensity": 1,
+ "metalness": 0.48,
+ "roughness": 0.56,
+ "opacity": 1,
+ "transparent": false,
+ "depthWrite": true
+ }
+ ]
+ },
+ "outdoor-fence-back-3": {
+ "name": "后侧围栏立柱 3",
+ "position": [
+ -17.4,
+ 1.1,
+ -25
+ ],
+ "rotation": [
+ 0,
+ 0,
+ 0
+ ],
+ "scale": [
+ 1,
+ 1,
+ 1
+ ],
+ "visible": true,
+ "customComponents": [],
+ "materials": [
+ {
+ "color": "66757c",
+ "emissive": "000000",
+ "emissiveIntensity": 1,
+ "metalness": 0.48,
+ "roughness": 0.56,
+ "opacity": 1,
+ "transparent": false,
+ "depthWrite": true
+ }
+ ]
+ },
+ "outdoor-fence-back-4": {
+ "name": "后侧围栏立柱 4",
+ "position": [
+ -11.6,
+ 1.1,
+ -25
+ ],
+ "rotation": [
+ 0,
+ 0,
+ 0
+ ],
+ "scale": [
+ 1,
+ 1,
+ 1
+ ],
+ "visible": true,
+ "customComponents": [],
+ "materials": [
+ {
+ "color": "66757c",
+ "emissive": "000000",
+ "emissiveIntensity": 1,
+ "metalness": 0.48,
+ "roughness": 0.56,
+ "opacity": 1,
+ "transparent": false,
+ "depthWrite": true
+ }
+ ]
+ },
+ "outdoor-fence-back-5": {
+ "name": "后侧围栏立柱 5",
+ "position": [
+ -5.8,
+ 1.1,
+ -25
+ ],
+ "rotation": [
+ 0,
+ 0,
+ 0
+ ],
+ "scale": [
+ 1,
+ 1,
+ 1
+ ],
+ "visible": true,
+ "customComponents": [],
+ "materials": [
+ {
+ "color": "66757c",
+ "emissive": "000000",
+ "emissiveIntensity": 1,
+ "metalness": 0.48,
+ "roughness": 0.56,
+ "opacity": 1,
+ "transparent": false,
+ "depthWrite": true
+ }
+ ]
+ },
+ "outdoor-fence-back-6": {
+ "name": "后侧围栏立柱 6",
+ "position": [
+ 0,
+ 1.1,
+ -25
+ ],
+ "rotation": [
+ 0,
+ 0,
+ 0
+ ],
+ "scale": [
+ 1,
+ 1,
+ 1
+ ],
+ "visible": true,
+ "customComponents": [],
+ "materials": [
+ {
+ "color": "66757c",
+ "emissive": "000000",
+ "emissiveIntensity": 1,
+ "metalness": 0.48,
+ "roughness": 0.56,
+ "opacity": 1,
+ "transparent": false,
+ "depthWrite": true
+ }
+ ]
+ },
+ "outdoor-fence-back-7": {
+ "name": "后侧围栏立柱 7",
+ "position": [
+ 5.8,
+ 1.1,
+ -25
+ ],
+ "rotation": [
+ 0,
+ 0,
+ 0
+ ],
+ "scale": [
+ 1,
+ 1,
+ 1
+ ],
+ "visible": true,
+ "customComponents": [],
+ "materials": [
+ {
+ "color": "66757c",
+ "emissive": "000000",
+ "emissiveIntensity": 1,
+ "metalness": 0.48,
+ "roughness": 0.56,
+ "opacity": 1,
+ "transparent": false,
+ "depthWrite": true
+ }
+ ]
+ },
+ "outdoor-fence-back-8": {
+ "name": "后侧围栏立柱 8",
+ "position": [
+ 11.6,
+ 1.1,
+ -25
+ ],
+ "rotation": [
+ 0,
+ 0,
+ 0
+ ],
+ "scale": [
+ 1,
+ 1,
+ 1
+ ],
+ "visible": true,
+ "customComponents": [],
+ "materials": [
+ {
+ "color": "66757c",
+ "emissive": "000000",
+ "emissiveIntensity": 1,
+ "metalness": 0.48,
+ "roughness": 0.56,
+ "opacity": 1,
+ "transparent": false,
+ "depthWrite": true
+ }
+ ]
+ },
+ "outdoor-fence-back-9": {
+ "name": "后侧围栏立柱 9",
+ "position": [
+ 17.4,
+ 1.1,
+ -25
+ ],
+ "rotation": [
+ 0,
+ 0,
+ 0
+ ],
+ "scale": [
+ 1,
+ 1,
+ 1
+ ],
+ "visible": true,
+ "customComponents": [],
+ "materials": [
+ {
+ "color": "66757c",
+ "emissive": "000000",
+ "emissiveIntensity": 1,
+ "metalness": 0.48,
+ "roughness": 0.56,
+ "opacity": 1,
+ "transparent": false,
+ "depthWrite": true
+ }
+ ]
+ },
+ "outdoor-fence-back-10": {
+ "name": "后侧围栏立柱 10",
+ "position": [
+ 23.2,
+ 1.1,
+ -25
+ ],
+ "rotation": [
+ 0,
+ 0,
+ 0
+ ],
+ "scale": [
+ 1,
+ 1,
+ 1
+ ],
+ "visible": true,
+ "customComponents": [],
+ "materials": [
+ {
+ "color": "66757c",
+ "emissive": "000000",
+ "emissiveIntensity": 1,
+ "metalness": 0.48,
+ "roughness": 0.56,
+ "opacity": 1,
+ "transparent": false,
+ "depthWrite": true
+ }
+ ]
+ },
+ "outdoor-fence-back-11": {
+ "name": "后侧围栏立柱 11",
+ "position": [
+ 29,
+ 1.1,
+ -25
+ ],
+ "rotation": [
+ 0,
+ 0,
+ 0
+ ],
+ "scale": [
+ 1,
+ 1,
+ 1
+ ],
+ "visible": true,
+ "customComponents": [],
+ "materials": [
+ {
+ "color": "66757c",
+ "emissive": "000000",
+ "emissiveIntensity": 1,
+ "metalness": 0.48,
+ "roughness": 0.56,
+ "opacity": 1,
+ "transparent": false,
+ "depthWrite": true
+ }
+ ]
+ },
+ "outdoor-fence-rail-a": {
+ "name": "后侧围栏上横杆",
+ "position": [
+ 0,
+ 1.85,
+ -25
+ ],
+ "rotation": [
+ 0,
+ 0,
+ 0
+ ],
+ "scale": [
+ 1,
+ 1,
+ 1
+ ],
+ "visible": true,
+ "customComponents": [],
+ "materials": [
+ {
+ "color": "66757c",
+ "emissive": "000000",
+ "emissiveIntensity": 1,
+ "metalness": 0.48,
+ "roughness": 0.56,
+ "opacity": 1,
+ "transparent": false,
+ "depthWrite": true
+ }
+ ]
+ },
+ "outdoor-fence-rail-b": {
+ "name": "后侧围栏下横杆",
+ "position": [
+ 0,
+ 0.75,
+ -25
+ ],
+ "rotation": [
+ 0,
+ 0,
+ 0
+ ],
+ "scale": [
+ 1,
+ 1,
+ 1
+ ],
+ "visible": true,
+ "customComponents": [],
+ "materials": [
+ {
+ "color": "66757c",
+ "emissive": "000000",
+ "emissiveIntensity": 1,
+ "metalness": 0.48,
+ "roughness": 0.56,
+ "opacity": 1,
+ "transparent": false,
+ "depthWrite": true
+ }
+ ]
+ },
+ "outdoor-scene-sign": {
+ "name": "室外实训区域标识",
+ "position": [
+ 26,
+ 2.4,
+ 11.5
+ ],
+ "rotation": [
+ 0,
+ 0,
+ 0
+ ],
+ "scale": [
+ 1,
+ 1,
+ 1
+ ],
+ "visible": true,
+ "customComponents": [],
+ "materials": [
+ {
+ "color": "536675",
+ "emissive": "000000",
+ "emissiveIntensity": 1,
+ "metalness": 0.18,
+ "roughness": 0.78,
+ "opacity": 1,
+ "transparent": false,
+ "depthWrite": true
+ }
+ ]
+ },
+ "outdoor-scene-sign-post-a": {
+ "name": "区域标识支柱 A",
+ "position": [
+ 24,
+ 1.4,
+ 11.5
+ ],
+ "rotation": [
+ 0,
+ 0,
+ 0
+ ],
+ "scale": [
+ 1,
+ 1,
+ 1
+ ],
+ "visible": true,
+ "customComponents": [],
+ "materials": [
+ {
+ "color": "66757c",
+ "emissive": "000000",
+ "emissiveIntensity": 1,
+ "metalness": 0.48,
+ "roughness": 0.56,
+ "opacity": 1,
+ "transparent": false,
+ "depthWrite": true
+ }
+ ]
+ },
+ "outdoor-scene-sign-post-b": {
+ "name": "区域标识支柱 B",
+ "position": [
+ 28,
+ 1.4,
+ 11.5
+ ],
+ "rotation": [
+ 0,
+ 0,
+ 0
+ ],
+ "scale": [
+ 1,
+ 1,
+ 1
+ ],
+ "visible": true,
+ "customComponents": [],
+ "materials": [
+ {
+ "color": "66757c",
+ "emissive": "000000",
+ "emissiveIntensity": 1,
+ "metalness": 0.48,
+ "roughness": 0.56,
+ "opacity": 1,
+ "transparent": false,
+ "depthWrite": true
+ }
+ ]
+ }
+ }
+ },
+ "runtime": {
+ "startMode": "guided",
+ "showStepPanel": true,
+ "showProgress": true,
+ "allowReplay": true,
+ "language": "zh-CN"
+ },
+ "publish": {
+ "target": "standalone-html",
+ "deployment": "offline",
+ "includeProjectData": true,
+ "includeViewportPreview": true
+ }
+}
\ No newline at end of file
diff --git a/reports/model-editor-closure-20260906/downloads/model-offline-steps.html b/reports/model-editor-closure-20260906/downloads/model-offline-steps.html
new file mode 100644
index 0000000..11a5cb7
--- /dev/null
+++ b/reports/model-editor-closure-20260906/downloads/model-offline-steps.html
@@ -0,0 +1,104 @@
+
+
+
+
+
+
+ 闭环验收-发布封面版本-mtpjkzt1 · 步骤演示
+
+
+
+
+
+
+
+
运行视图 实训模式
● 步骤演示就绪
+ 当前任务
第 1 步 准备运行 操作说明 点击“播放步骤”查看步骤说明;本文件不运行三维模型、时间轴动画或蓝图分支。
✓ 步骤演示已就绪
+
+
‹ 上一步 ▶ 播放步骤 下一步 › 00:00 / 00:08
+
+
+
+
+
\ No newline at end of file
diff --git a/reports/model-editor-closure-20260906/downloads/preflight-188.json b/reports/model-editor-closure-20260906/downloads/preflight-188.json
new file mode 100644
index 0000000..c63c294
--- /dev/null
+++ b/reports/model-editor-closure-20260906/downloads/preflight-188.json
@@ -0,0 +1,3414 @@
+{
+ "schema": "digital-twin-editor-project",
+ "version": 4,
+ "name": "闭环验收-加载与预检-mtph6ubj",
+ "modelId": "imported-model_mto6v5w6_09-fox",
+ "modelResource": {
+ "kind": "imported",
+ "id": "imported-model_mto6v5w6_09-fox",
+ "name": "09-Fox",
+ "assetCode": "MODEL_MTO6V5W6_09-FOX",
+ "code": "MODEL_MTO6V5W6_09-FOX",
+ "storageUri": "fsvc://unreal-tran/model/2026/09/05/3fc69ae0d435f869afca1fcd943486d1.glb",
+ "assetKey": "",
+ "resourceUrl": "",
+ "sourceName": "09-Fox.glb",
+ "sizeBytes": 162852,
+ "lastModified": 0,
+ "mimeType": "model/gltf-binary",
+ "thumbnail": "",
+ "vertices": 1728,
+ "triangles": 576,
+ "nodes": 27,
+ "meshes": 1,
+ "materials": 1,
+ "textures": 1,
+ "animations": 3,
+ "animationDurationSeconds": 3.4166667461395264,
+ "modelMetadata": {
+ "nodeCount": 27,
+ "meshCount": 1,
+ "materialCount": 1,
+ "textureCount": 1,
+ "animations": [
+ {
+ "index": 0,
+ "name": "Survey",
+ "start": 0,
+ "end": 3.4166667461395264,
+ "duration": 3.4166667461395264,
+ "trackCount": 21
+ },
+ {
+ "index": 1,
+ "name": "Walk",
+ "start": 0,
+ "end": 0.7083333134651184,
+ "duration": 0.7083333134651184,
+ "trackCount": 21
+ },
+ {
+ "index": 2,
+ "name": "Run",
+ "start": 0,
+ "end": 1.1583333015441895,
+ "duration": 1.1583333015441895,
+ "trackCount": 21
+ }
+ ],
+ "animationDurationSeconds": 3.4166667461395264
+ },
+ "nativeAnimations": [
+ {
+ "index": 0,
+ "name": "Survey",
+ "start": 0,
+ "end": 3.4166667461395264,
+ "duration": 3.4166667461395264,
+ "trackCount": 21
+ },
+ {
+ "index": 1,
+ "name": "Walk",
+ "start": 0,
+ "end": 0.7083333134651184,
+ "duration": 0.7083333134651184,
+ "trackCount": 21
+ },
+ {
+ "index": 2,
+ "name": "Run",
+ "start": 0,
+ "end": 1.1583333015441895,
+ "duration": 1.1583333015441895,
+ "trackCount": 21
+ }
+ ]
+ },
+ "meta": {
+ "description": "用于装备维修实训场景编排、编辑器回归与离线发布验证的系统内置基准工程",
+ "author": "数字孪生项目组",
+ "scene": "车间与野外装备维修实训环境",
+ "tags": [
+ "三维场景",
+ "时间轴动画",
+ "蓝图交互",
+ "实训流程"
+ ],
+ "migratedFrom": "legacy-model-document",
+ "source": "content-api-glb",
+ "sourceName": "09-Fox.glb",
+ "assetCode": "MODEL_MTO6V5W6_09-FOX",
+ "storageUri": "fsvc://unreal-tran/model/2026/09/05/3fc69ae0d435f869afca1fcd943486d1.glb"
+ },
+ "timeline": {
+ "version": 1,
+ "duration": 8,
+ "currentTime": 0,
+ "loop": true,
+ "zoom": 1,
+ "tracks": [
+ {
+ "id": "track-0-1yjfz9t",
+ "name": "模型整体位移",
+ "targetId": "interaction-b9240aaa",
+ "property": "position",
+ "color": "#ffb22e",
+ "muted": false,
+ "keyframes": [
+ {
+ "id": "key-0-0-kmfo820",
+ "time": 0,
+ "value": [
+ 0,
+ 0,
+ 0
+ ],
+ "easing": "easeInOut"
+ },
+ {
+ "id": "key-0-1-snsj2e2",
+ "time": 8,
+ "value": [
+ 0,
+ 0,
+ 0
+ ],
+ "easing": "easeInOut"
+ }
+ ]
+ }
+ ]
+ },
+ "blueprint": {
+ "version": 1,
+ "name": "挖掘机作业实训闭环",
+ "viewport": {
+ "x": 55,
+ "y": 55,
+ "zoom": 0.82
+ },
+ "nodes": [
+ {
+ "id": "bp-start",
+ "type": "trigger-click",
+ "title": "点击挖掘机",
+ "x": 40,
+ "y": 55,
+ "params": {
+ "event": "click",
+ "target": "挖掘机"
+ }
+ },
+ {
+ "id": "bp-highlight",
+ "type": "highlight",
+ "title": "高亮工作装置",
+ "x": 300,
+ "y": 55,
+ "params": {
+ "target": "铲斗",
+ "color": "#38e8ff",
+ "pulse": true
+ }
+ },
+ {
+ "id": "bp-message",
+ "type": "message",
+ "title": "显示操作提示",
+ "x": 560,
+ "y": 55,
+ "params": {
+ "title": "挖掘作业实训",
+ "text": "观察动臂、斗杆和铲斗的协同动作",
+ "level": "info"
+ }
+ },
+ {
+ "id": "bp-play",
+ "type": "play-timeline",
+ "title": "播放作业动画",
+ "x": 820,
+ "y": 55,
+ "params": {
+ "clip": "挖掘机作业实训",
+ "from": 0,
+ "speed": 1,
+ "waitForEnd": true
+ }
+ },
+ {
+ "id": "bp-wait",
+ "type": "wait",
+ "title": "动作稳定",
+ "x": 560,
+ "y": 245,
+ "params": {
+ "duration": 600
+ }
+ },
+ {
+ "id": "bp-check",
+ "type": "condition",
+ "title": "动画是否完成",
+ "x": 300,
+ "y": 245,
+ "params": {
+ "variable": "timelineComplete",
+ "operator": "==",
+ "value": true
+ }
+ },
+ {
+ "id": "bp-retry",
+ "type": "message",
+ "title": "等待动画完成",
+ "x": 820,
+ "y": 245,
+ "params": {
+ "title": "流程校验",
+ "text": "动画尚未完成,请重新执行",
+ "level": "warning"
+ }
+ },
+ {
+ "id": "bp-finish",
+ "type": "finish",
+ "title": "流程完成",
+ "x": 40,
+ "y": 245,
+ "params": {
+ "result": "success",
+ "text": "挖掘机作业实训流程完成"
+ }
+ }
+ ],
+ "edges": [
+ {
+ "id": "bp-e1",
+ "from": {
+ "node": "bp-start",
+ "port": "then"
+ },
+ "to": {
+ "node": "bp-highlight",
+ "port": "in"
+ }
+ },
+ {
+ "id": "bp-e2",
+ "from": {
+ "node": "bp-highlight",
+ "port": "then"
+ },
+ "to": {
+ "node": "bp-message",
+ "port": "in"
+ }
+ },
+ {
+ "id": "bp-e3",
+ "from": {
+ "node": "bp-message",
+ "port": "then"
+ },
+ "to": {
+ "node": "bp-play",
+ "port": "in"
+ }
+ },
+ {
+ "id": "bp-e4",
+ "from": {
+ "node": "bp-play",
+ "port": "then"
+ },
+ "to": {
+ "node": "bp-wait",
+ "port": "in"
+ }
+ },
+ {
+ "id": "bp-e5",
+ "from": {
+ "node": "bp-wait",
+ "port": "then"
+ },
+ "to": {
+ "node": "bp-check",
+ "port": "in"
+ }
+ },
+ {
+ "id": "bp-e6",
+ "from": {
+ "node": "bp-check",
+ "port": "true"
+ },
+ "to": {
+ "node": "bp-finish",
+ "port": "in"
+ }
+ },
+ {
+ "id": "bp-e7",
+ "from": {
+ "node": "bp-check",
+ "port": "false"
+ },
+ "to": {
+ "node": "bp-retry",
+ "port": "in"
+ }
+ },
+ {
+ "id": "bp-e8",
+ "from": {
+ "node": "bp-retry",
+ "port": "then"
+ },
+ "to": {
+ "node": "bp-play",
+ "port": "in"
+ }
+ }
+ ]
+ },
+ "camera": {
+ "position": [
+ 11.153569378931522,
+ 8.526809324389628,
+ 13.922975975299929
+ ],
+ "target": [
+ 0.01518877997232826,
+ 2.4006999949620704,
+ 2.266009371254541e-7
+ ]
+ },
+ "model": {
+ "objects": {
+ "equipment-root": {
+ "name": "09-Fox",
+ "nodeName": "09-Fox",
+ "nodePath": "@root",
+ "interactionId": "interaction-b9240aaa",
+ "capabilities": {
+ "selectable": true,
+ "transform": true,
+ "visible": true,
+ "material": false,
+ "animatable": true
+ },
+ "position": [
+ 0,
+ 0,
+ 0
+ ],
+ "rotation": [
+ 0,
+ 0,
+ 0
+ ],
+ "scale": [
+ 1,
+ 1,
+ 1
+ ],
+ "visible": true,
+ "customComponents": []
+ },
+ "model-imported-model_mto6v5w6_09-fox-0": {
+ "name": "",
+ "nodeName": "Group",
+ "nodePath": "0",
+ "interactionId": "interaction-02351bce",
+ "capabilities": {
+ "selectable": true,
+ "transform": true,
+ "visible": true,
+ "material": false,
+ "animatable": true
+ },
+ "position": [
+ 3.875009990661482e-8,
+ 0.007396563645075124,
+ 0.6522089834776823
+ ],
+ "rotation": [
+ 0,
+ 0,
+ 0
+ ],
+ "scale": [
+ 0.06075496488891343,
+ 0.06075496488891343,
+ 0.06075496488891343
+ ],
+ "visible": true,
+ "customComponents": []
+ },
+ "model-imported-model_mto6v5w6_09-fox-1": {
+ "name": "root",
+ "nodeName": "root",
+ "nodePath": "0/0",
+ "interactionId": "interaction-a4d35db9",
+ "capabilities": {
+ "selectable": true,
+ "transform": true,
+ "visible": true,
+ "material": false,
+ "animatable": true
+ },
+ "position": [
+ 0,
+ 0,
+ 0
+ ],
+ "rotation": [
+ 0,
+ 0,
+ 0
+ ],
+ "scale": [
+ 1,
+ 1,
+ 1
+ ],
+ "visible": true,
+ "customComponents": []
+ },
+ "model-imported-model_mto6v5w6_09-fox-2": {
+ "name": "_rootJoint",
+ "nodeName": "_rootJoint",
+ "nodePath": "0/0/0",
+ "interactionId": "interaction-4476c556",
+ "capabilities": {
+ "selectable": true,
+ "transform": true,
+ "visible": true,
+ "material": false,
+ "animatable": true
+ },
+ "position": [
+ 0,
+ 0,
+ 0
+ ],
+ "rotation": [
+ 0,
+ 0,
+ 0
+ ],
+ "scale": [
+ 1,
+ 1,
+ 1
+ ],
+ "visible": true,
+ "customComponents": []
+ },
+ "model-imported-model_mto6v5w6_09-fox-3": {
+ "name": "b_Root_00",
+ "nodeName": "b_Root_00",
+ "nodePath": "0/0/0/0",
+ "interactionId": "interaction-ffc45d11",
+ "capabilities": {
+ "selectable": true,
+ "transform": true,
+ "visible": true,
+ "material": false,
+ "animatable": true
+ },
+ "position": [
+ 0,
+ 0,
+ 0
+ ],
+ "rotation": [
+ -1.5708000357176293,
+ 0,
+ 0
+ ],
+ "scale": [
+ 1,
+ 1,
+ 1
+ ],
+ "visible": true,
+ "customComponents": []
+ },
+ "model-imported-model_mto6v5w6_09-fox-4": {
+ "name": "b_Hip_01",
+ "nodeName": "b_Hip_01",
+ "nodePath": "0/0/0/0/0",
+ "interactionId": "interaction-7a75e39e",
+ "capabilities": {
+ "selectable": true,
+ "transform": true,
+ "visible": true,
+ "material": false,
+ "animatable": true
+ },
+ "position": [
+ 0.000001241521920852674,
+ 22.061977333358605,
+ 34.62373020761327
+ ],
+ "rotation": [
+ 0.44722050284761794,
+ -1.5707951637404125,
+ 0
+ ],
+ "scale": [
+ 1,
+ 1,
+ 1
+ ],
+ "visible": true,
+ "customComponents": []
+ },
+ "model-imported-model_mto6v5w6_09-fox-5": {
+ "name": "b_Spine01_02",
+ "nodeName": "b_Spine01_02",
+ "nodePath": "0/0/0/0/0/0",
+ "interactionId": "interaction-663ea469",
+ "capabilities": {
+ "selectable": true,
+ "transform": true,
+ "visible": true,
+ "material": false,
+ "animatable": true
+ },
+ "position": [
+ 12.850601196289062,
+ 0,
+ 0
+ ],
+ "rotation": [
+ -3.357310257291308e-7,
+ -3.316574913360067e-8,
+ -1.2102038452161539
+ ],
+ "scale": [
+ 1,
+ 1,
+ 1
+ ],
+ "visible": true,
+ "customComponents": []
+ },
+ "model-imported-model_mto6v5w6_09-fox-6": {
+ "name": "b_Spine02_03",
+ "nodeName": "b_Spine02_03",
+ "nodePath": "0/0/0/0/0/0/0",
+ "interactionId": "interaction-46867126",
+ "capabilities": {
+ "selectable": true,
+ "transform": true,
+ "visible": true,
+ "material": false,
+ "animatable": true
+ },
+ "position": [
+ 21.65575408935547,
+ -0.000118255615234375,
+ 0
+ ],
+ "rotation": [
+ 8.772651057591018e-8,
+ 3.3355428835535634e-8,
+ 0.3633406824515624
+ ],
+ "scale": [
+ 1,
+ 1,
+ 1
+ ],
+ "visible": true,
+ "customComponents": []
+ },
+ "model-imported-model_mto6v5w6_09-fox-7": {
+ "name": "b_Neck_04",
+ "nodeName": "b_Neck_04",
+ "nodePath": "0/0/0/0/0/0/0/0",
+ "interactionId": "interaction-27148d81",
+ "capabilities": {
+ "selectable": true,
+ "transform": true,
+ "visible": true,
+ "material": false,
+ "animatable": true
+ },
+ "position": [
+ 25.64914321899414,
+ 0,
+ 0
+ ],
+ "rotation": [
+ 6.423807629998062e-15,
+ 1.9993472402144555e-16,
+ 0.031113979193075042
+ ],
+ "scale": [
+ 1,
+ 1,
+ 1
+ ],
+ "visible": true,
+ "customComponents": []
+ },
+ "model-imported-model_mto6v5w6_09-fox-8": {
+ "name": "b_Head_05",
+ "nodeName": "b_Head_05",
+ "nodePath": "0/0/0/0/0/0/0/0/0",
+ "interactionId": "interaction-b24eef2e",
+ "capabilities": {
+ "selectable": true,
+ "transform": true,
+ "visible": true,
+ "material": false,
+ "animatable": true
+ },
+ "position": [
+ 13.376960754394531,
+ 0,
+ 0
+ ],
+ "rotation": [
+ 3.253825627358364e-8,
+ -1.577320798309453e-8,
+ -0.451380500897834
+ ],
+ "scale": [
+ 1,
+ 1,
+ 1
+ ],
+ "visible": true,
+ "customComponents": []
+ },
+ "model-imported-model_mto6v5w6_09-fox-9": {
+ "name": "b_RightUpperArm_06",
+ "nodeName": "b_RightUpperArm_06",
+ "nodePath": "0/0/0/0/0/0/0/1",
+ "interactionId": "interaction-26148bee",
+ "capabilities": {
+ "selectable": true,
+ "transform": true,
+ "visible": true,
+ "material": false,
+ "animatable": true
+ },
+ "position": [
+ 18.677913665771484,
+ -4.297340393066406,
+ 6.9675750732421875
+ ],
+ "rotation": [
+ -0.035644184619881884,
+ -0.047001863061712265,
+ -1.2972005346348685
+ ],
+ "scale": [
+ 1,
+ 1,
+ 1
+ ],
+ "visible": true,
+ "customComponents": []
+ },
+ "model-imported-model_mto6v5w6_09-fox-10": {
+ "name": "b_RightForeArm_07",
+ "nodeName": "b_RightForeArm_07",
+ "nodePath": "0/0/0/0/0/0/0/1/0",
+ "interactionId": "interaction-36896559",
+ "capabilities": {
+ "selectable": true,
+ "transform": true,
+ "visible": true,
+ "material": false,
+ "animatable": true
+ },
+ "position": [
+ 23.04512596130371,
+ 0,
+ 0
+ ],
+ "rotation": [
+ 2.1355818997049898e-8,
+ 7.233656206674673e-8,
+ 1.2561843405841544
+ ],
+ "scale": [
+ 1,
+ 1,
+ 1
+ ],
+ "visible": true,
+ "customComponents": []
+ },
+ "model-imported-model_mto6v5w6_09-fox-11": {
+ "name": "b_RightHand_08",
+ "nodeName": "b_RightHand_08",
+ "nodePath": "0/0/0/0/0/0/0/1/0/0",
+ "interactionId": "interaction-57a124f6",
+ "capabilities": {
+ "selectable": true,
+ "transform": true,
+ "visible": true,
+ "material": false,
+ "animatable": true
+ },
+ "position": [
+ 19.350055694580078,
+ -0.14598655700683594,
+ 0
+ ],
+ "rotation": [
+ 0.005245101551986586,
+ 0.05200705984217871,
+ -0.267165337534188
+ ],
+ "scale": [
+ 1,
+ 1,
+ 1
+ ],
+ "visible": true,
+ "customComponents": []
+ },
+ "model-imported-model_mto6v5w6_09-fox-12": {
+ "name": "b_LeftUpperArm_09",
+ "nodeName": "b_LeftUpperArm_09",
+ "nodePath": "0/0/0/0/0/0/0/2",
+ "interactionId": "interaction-25148a5b",
+ "capabilities": {
+ "selectable": true,
+ "transform": true,
+ "visible": true,
+ "material": false,
+ "animatable": true
+ },
+ "position": [
+ 18.67791748046875,
+ -4.297344207763672,
+ -6.967987060546875
+ ],
+ "rotation": [
+ -0.00020118104561539401,
+ 0.0009350561550523671,
+ -1.5148902559141275
+ ],
+ "scale": [
+ 1,
+ 1,
+ 1
+ ],
+ "visible": true,
+ "customComponents": []
+ },
+ "model-imported-model_mto6v5w6_09-fox-13": {
+ "name": "b_LeftForeArm_010",
+ "nodeName": "b_LeftForeArm_010",
+ "nodePath": "0/0/0/0/0/0/0/2/0",
+ "interactionId": "interaction-5c502ac4",
+ "capabilities": {
+ "selectable": true,
+ "transform": true,
+ "visible": true,
+ "material": false,
+ "animatable": true
+ },
+ "position": [
+ 23.045124053955078,
+ 0,
+ 0
+ ],
+ "rotation": [
+ 2.5220861991717696e-8,
+ 7.109903232964989e-8,
+ 1.2307106255258764
+ ],
+ "scale": [
+ 1,
+ 1,
+ 1
+ ],
+ "visible": true,
+ "customComponents": []
+ },
+ "model-imported-model_mto6v5w6_09-fox-14": {
+ "name": "b_LeftHand_011",
+ "nodeName": "b_LeftHand_011",
+ "nodePath": "0/0/0/0/0/0/0/2/0/0",
+ "interactionId": "interaction-192b4cd3",
+ "capabilities": {
+ "selectable": true,
+ "transform": true,
+ "visible": true,
+ "material": false,
+ "animatable": true
+ },
+ "position": [
+ 19.350051879882812,
+ -0.14599037170410156,
+ 0
+ ],
+ "rotation": [
+ 0.004279557751527162,
+ -0.03344276494440009,
+ -1.5985291598689726
+ ],
+ "scale": [
+ 1,
+ 1,
+ 1
+ ],
+ "visible": true,
+ "customComponents": []
+ },
+ "model-imported-model_mto6v5w6_09-fox-15": {
+ "name": "b_Tail01_012",
+ "nodeName": "b_Tail01_012",
+ "nodePath": "0/0/0/0/0/1",
+ "interactionId": "interaction-653ea2d6",
+ "capabilities": {
+ "selectable": true,
+ "transform": true,
+ "visible": true,
+ "material": false,
+ "animatable": true
+ },
+ "position": [
+ 4.2603759765625,
+ 15.958770751953125,
+ 0
+ ],
+ "rotation": [
+ -5.364745216052604e-7,
+ 3.424797608447095e-7,
+ 1.9941304874941974
+ ],
+ "scale": [
+ 1,
+ 1,
+ 1
+ ],
+ "visible": true,
+ "customComponents": []
+ },
+ "model-imported-model_mto6v5w6_09-fox-16": {
+ "name": "b_Tail02_013",
+ "nodeName": "b_Tail02_013",
+ "nodePath": "0/0/0/0/0/1/0",
+ "interactionId": "interaction-6c4d3691",
+ "capabilities": {
+ "selectable": true,
+ "transform": true,
+ "visible": true,
+ "material": false,
+ "animatable": true
+ },
+ "position": [
+ 12.411918640136719,
+ 0,
+ 0
+ ],
+ "rotation": [
+ -6.738507958375962e-23,
+ -5.2439168243310865e-23,
+ 0.3128105211171357
+ ],
+ "scale": [
+ 1,
+ 1,
+ 1
+ ],
+ "visible": true,
+ "customComponents": []
+ },
+ "model-imported-model_mto6v5w6_09-fox-17": {
+ "name": "b_Tail03_014",
+ "nodeName": "b_Tail03_014",
+ "nodePath": "0/0/0/0/0/1/0/0",
+ "interactionId": "interaction-e93e191e",
+ "capabilities": {
+ "selectable": true,
+ "transform": true,
+ "visible": true,
+ "material": false,
+ "animatable": true
+ },
+ "position": [
+ 24.24032211303711,
+ 0,
+ 0
+ ],
+ "rotation": [
+ -5.7486339033124146e-24,
+ 1.1669178332583734e-22,
+ -0.012937892408322462
+ ],
+ "scale": [
+ 1,
+ 1,
+ 1
+ ],
+ "visible": true,
+ "customComponents": []
+ },
+ "model-imported-model_mto6v5w6_09-fox-18": {
+ "name": "b_LeftLeg01_015",
+ "nodeName": "b_LeftLeg01_015",
+ "nodePath": "0/0/0/0/0/2",
+ "interactionId": "interaction-643ea143",
+ "capabilities": {
+ "selectable": true,
+ "transform": true,
+ "visible": true,
+ "material": false,
+ "animatable": true
+ },
+ "position": [
+ 4.813770294189453,
+ 5.154018402099609,
+ -6.968006134033203
+ ],
+ "rotation": [
+ 0.1223493637902729,
+ -0.09724770930295147,
+ 2.9630295165414986
+ ],
+ "scale": [
+ 1,
+ 1,
+ 1
+ ],
+ "visible": true,
+ "customComponents": []
+ },
+ "model-imported-model_mto6v5w6_09-fox-19": {
+ "name": "b_LeftLeg02_016",
+ "nodeName": "b_LeftLeg02_016",
+ "nodePath": "0/0/0/0/0/2/0",
+ "interactionId": "interaction-7175f8dc",
+ "capabilities": {
+ "selectable": true,
+ "transform": true,
+ "visible": true,
+ "material": false,
+ "animatable": true
+ },
+ "position": [
+ 18.944175720214844,
+ 0,
+ 0
+ ],
+ "rotation": [
+ 1.2545483692289454e-8,
+ 3.728483680761731e-8,
+ -0.522293355530293
+ ],
+ "scale": [
+ 1,
+ 1,
+ 1
+ ],
+ "visible": true,
+ "customComponents": []
+ },
+ "model-imported-model_mto6v5w6_09-fox-20": {
+ "name": "b_LeftFoot01_017",
+ "nodeName": "b_LeftFoot01_017",
+ "nodePath": "0/0/0/0/0/2/0/0",
+ "interactionId": "interaction-f5ed353b",
+ "capabilities": {
+ "selectable": true,
+ "transform": true,
+ "visible": true,
+ "material": false,
+ "animatable": true
+ },
+ "position": [
+ 17.942811965942383,
+ 0,
+ 0
+ ],
+ "rotation": [
+ -0.03128474386729028,
+ -0.15471927051895154,
+ 0.7774909055508243
+ ],
+ "scale": [
+ 1,
+ 1,
+ 1
+ ],
+ "visible": true,
+ "customComponents": []
+ },
+ "model-imported-model_mto6v5w6_09-fox-21": {
+ "name": "b_LeftFoot02_018",
+ "nodeName": "b_LeftFoot02_018",
+ "nodePath": "0/0/0/0/0/2/0/0/0",
+ "interactionId": "interaction-1bbe01a4",
+ "capabilities": {
+ "selectable": true,
+ "transform": true,
+ "visible": true,
+ "material": false,
+ "animatable": true
+ },
+ "position": [
+ 15.779938697814941,
+ 0,
+ 0
+ ],
+ "rotation": [
+ 0,
+ 0,
+ 1.158241570830627
+ ],
+ "scale": [
+ 1,
+ 1,
+ 1
+ ],
+ "visible": true,
+ "customComponents": []
+ },
+ "model-imported-model_mto6v5w6_09-fox-22": {
+ "name": "b_RightLeg01_019",
+ "nodeName": "b_RightLeg01_019",
+ "nodePath": "0/0/0/0/0/3",
+ "interactionId": "interaction-633e9fb0",
+ "capabilities": {
+ "selectable": true,
+ "transform": true,
+ "visible": true,
+ "material": false,
+ "animatable": true
+ },
+ "position": [
+ 4.813777923583984,
+ 5.154026031494141,
+ 6.967563629150391
+ ],
+ "rotation": [
+ -0.10137208185544387,
+ 0.05534659522395753,
+ -2.3613690068554236
+ ],
+ "scale": [
+ 1,
+ 1,
+ 1
+ ],
+ "visible": true,
+ "customComponents": []
+ },
+ "model-imported-model_mto6v5w6_09-fox-23": {
+ "name": "b_RightLeg02_020",
+ "nodeName": "b_RightLeg02_020",
+ "nodePath": "0/0/0/0/0/3/0",
+ "interactionId": "interaction-95ff89c7",
+ "capabilities": {
+ "selectable": true,
+ "transform": true,
+ "visible": true,
+ "material": false,
+ "animatable": true
+ },
+ "position": [
+ 18.944183349609375,
+ 0,
+ 0
+ ],
+ "rotation": [
+ 2.7890192431350882e-8,
+ 1.4750479199499402e-8,
+ -1.0710323806538369
+ ],
+ "scale": [
+ 1,
+ 1,
+ 1
+ ],
+ "visible": true,
+ "customComponents": []
+ },
+ "model-imported-model_mto6v5w6_09-fox-24": {
+ "name": "b_RightFoot01_021",
+ "nodeName": "b_RightFoot01_021",
+ "nodePath": "0/0/0/0/0/3/0/0",
+ "interactionId": "interaction-a157d498",
+ "capabilities": {
+ "selectable": true,
+ "transform": true,
+ "visible": true,
+ "material": false,
+ "animatable": true
+ },
+ "position": [
+ 17.94281005859375,
+ 0,
+ 0
+ ],
+ "rotation": [
+ -0.14401413968163027,
+ 0.2814735368347316,
+ 1.4591240233677443
+ ],
+ "scale": [
+ 1,
+ 1,
+ 1
+ ],
+ "visible": true,
+ "customComponents": []
+ },
+ "model-imported-model_mto6v5w6_09-fox-25": {
+ "name": "b_RightFoot02_022",
+ "nodeName": "b_RightFoot02_022",
+ "nodePath": "0/0/0/0/0/3/0/0/0",
+ "interactionId": "interaction-ebd38e3f",
+ "capabilities": {
+ "selectable": true,
+ "transform": true,
+ "visible": true,
+ "material": false,
+ "animatable": true
+ },
+ "position": [
+ 15.779935836791992,
+ 0,
+ 0
+ ],
+ "rotation": [
+ 0,
+ 0,
+ 1.158241570830627
+ ],
+ "scale": [
+ 1,
+ 1,
+ 1
+ ],
+ "visible": true,
+ "customComponents": []
+ },
+ "model-imported-model_mto6v5w6_09-fox-26": {
+ "name": "fox",
+ "nodeName": "fox",
+ "nodePath": "0/1",
+ "interactionId": "interaction-a3d35c26",
+ "capabilities": {
+ "selectable": true,
+ "transform": true,
+ "visible": true,
+ "material": true,
+ "animatable": true
+ },
+ "position": [
+ 0.25,
+ 0,
+ 0
+ ],
+ "rotation": [
+ 0,
+ 0,
+ 0
+ ],
+ "scale": [
+ 1,
+ 1,
+ 1
+ ],
+ "visible": true,
+ "customComponents": [],
+ "materials": [
+ {
+ "color": "ffffff",
+ "emissive": "000000",
+ "emissiveIntensity": 1,
+ "metalness": 0,
+ "roughness": 0.3,
+ "opacity": 1,
+ "transparent": false,
+ "depthWrite": true
+ }
+ ]
+ }
+ },
+ "selectionScope": "part",
+ "nativeAnimation": {
+ "clipIndex": 2,
+ "clipName": "Run"
+ }
+ },
+ "environment": {
+ "mode": "placeholder",
+ "environmentType": "outdoor",
+ "sourceName": "内置野外综合训练环境",
+ "sizeBytes": 0,
+ "assetCode": "",
+ "storageUri": "",
+ "resourceUrl": "",
+ "objects": {
+ "outdoor-scene": {
+ "name": "野外综合训练环境",
+ "position": [
+ 0,
+ 0,
+ 0
+ ],
+ "rotation": [
+ 0,
+ 0,
+ 0
+ ],
+ "scale": [
+ 1,
+ 1,
+ 1
+ ],
+ "visible": true,
+ "customComponents": []
+ },
+ "outdoor-site": {
+ "name": "场地与道路",
+ "position": [
+ 0,
+ 0,
+ 0
+ ],
+ "rotation": [
+ 0,
+ 0,
+ 0
+ ],
+ "scale": [
+ 1,
+ 1,
+ 1
+ ],
+ "visible": true,
+ "customComponents": []
+ },
+ "outdoor-ground": {
+ "name": "室外训练场地",
+ "position": [
+ 0,
+ -0.065,
+ 0
+ ],
+ "rotation": [
+ -1.5707963267948966,
+ 0,
+ 0
+ ],
+ "scale": [
+ 1,
+ 1,
+ 1
+ ],
+ "visible": true,
+ "customComponents": [],
+ "materials": [
+ {
+ "color": "263529",
+ "emissive": "000000",
+ "emissiveIntensity": 1,
+ "metalness": 0,
+ "roughness": 0.98,
+ "opacity": 1,
+ "transparent": false,
+ "depthWrite": true
+ }
+ ]
+ },
+ "outdoor-apron": {
+ "name": "装备操作坪",
+ "position": [
+ 0,
+ -0.045,
+ 0
+ ],
+ "rotation": [
+ -1.5707963267948966,
+ 0,
+ 0
+ ],
+ "scale": [
+ 1,
+ 1,
+ 1
+ ],
+ "visible": true,
+ "customComponents": [],
+ "materials": [
+ {
+ "color": "39444a",
+ "emissive": "000000",
+ "emissiveIntensity": 1,
+ "metalness": 0.03,
+ "roughness": 0.92,
+ "opacity": 1,
+ "transparent": false,
+ "depthWrite": true
+ }
+ ]
+ },
+ "outdoor-road": {
+ "name": "保障通道",
+ "position": [
+ 0,
+ -0.035,
+ 17
+ ],
+ "rotation": [
+ -1.5707963267948966,
+ 0,
+ 0
+ ],
+ "scale": [
+ 1,
+ 1,
+ 1
+ ],
+ "visible": true,
+ "customComponents": [],
+ "materials": [
+ {
+ "color": "202a31",
+ "emissive": "000000",
+ "emissiveIntensity": 1,
+ "metalness": 0.02,
+ "roughness": 0.95,
+ "opacity": 1,
+ "transparent": false,
+ "depthWrite": true
+ }
+ ]
+ },
+ "outdoor-road-line-1": {
+ "name": "道路中心线 1",
+ "position": [
+ -28.699999999999996,
+ 0.01,
+ 17
+ ],
+ "rotation": [
+ 0,
+ 0,
+ 0
+ ],
+ "scale": [
+ 1,
+ 1,
+ 1
+ ],
+ "visible": true,
+ "customComponents": [],
+ "materials": [
+ {
+ "color": "e5c64c",
+ "emissive": "302600",
+ "emissiveIntensity": 0.18,
+ "metalness": 0.08,
+ "roughness": 0.72,
+ "opacity": 1,
+ "transparent": false,
+ "depthWrite": true
+ }
+ ]
+ },
+ "outdoor-road-line-2": {
+ "name": "道路中心线 2",
+ "position": [
+ -24.599999999999998,
+ 0.01,
+ 17
+ ],
+ "rotation": [
+ 0,
+ 0,
+ 0
+ ],
+ "scale": [
+ 1,
+ 1,
+ 1
+ ],
+ "visible": true,
+ "customComponents": [],
+ "materials": [
+ {
+ "color": "e5c64c",
+ "emissive": "302600",
+ "emissiveIntensity": 0.18,
+ "metalness": 0.08,
+ "roughness": 0.72,
+ "opacity": 1,
+ "transparent": false,
+ "depthWrite": true
+ }
+ ]
+ },
+ "outdoor-road-line-3": {
+ "name": "道路中心线 3",
+ "position": [
+ -20.5,
+ 0.01,
+ 17
+ ],
+ "rotation": [
+ 0,
+ 0,
+ 0
+ ],
+ "scale": [
+ 1,
+ 1,
+ 1
+ ],
+ "visible": true,
+ "customComponents": [],
+ "materials": [
+ {
+ "color": "e5c64c",
+ "emissive": "302600",
+ "emissiveIntensity": 0.18,
+ "metalness": 0.08,
+ "roughness": 0.72,
+ "opacity": 1,
+ "transparent": false,
+ "depthWrite": true
+ }
+ ]
+ },
+ "outdoor-road-line-4": {
+ "name": "道路中心线 4",
+ "position": [
+ -16.4,
+ 0.01,
+ 17
+ ],
+ "rotation": [
+ 0,
+ 0,
+ 0
+ ],
+ "scale": [
+ 1,
+ 1,
+ 1
+ ],
+ "visible": true,
+ "customComponents": [],
+ "materials": [
+ {
+ "color": "e5c64c",
+ "emissive": "302600",
+ "emissiveIntensity": 0.18,
+ "metalness": 0.08,
+ "roughness": 0.72,
+ "opacity": 1,
+ "transparent": false,
+ "depthWrite": true
+ }
+ ]
+ },
+ "outdoor-road-line-5": {
+ "name": "道路中心线 5",
+ "position": [
+ -12.299999999999999,
+ 0.01,
+ 17
+ ],
+ "rotation": [
+ 0,
+ 0,
+ 0
+ ],
+ "scale": [
+ 1,
+ 1,
+ 1
+ ],
+ "visible": true,
+ "customComponents": [],
+ "materials": [
+ {
+ "color": "e5c64c",
+ "emissive": "302600",
+ "emissiveIntensity": 0.18,
+ "metalness": 0.08,
+ "roughness": 0.72,
+ "opacity": 1,
+ "transparent": false,
+ "depthWrite": true
+ }
+ ]
+ },
+ "outdoor-road-line-6": {
+ "name": "道路中心线 6",
+ "position": [
+ -8.2,
+ 0.01,
+ 17
+ ],
+ "rotation": [
+ 0,
+ 0,
+ 0
+ ],
+ "scale": [
+ 1,
+ 1,
+ 1
+ ],
+ "visible": true,
+ "customComponents": [],
+ "materials": [
+ {
+ "color": "e5c64c",
+ "emissive": "302600",
+ "emissiveIntensity": 0.18,
+ "metalness": 0.08,
+ "roughness": 0.72,
+ "opacity": 1,
+ "transparent": false,
+ "depthWrite": true
+ }
+ ]
+ },
+ "outdoor-road-line-7": {
+ "name": "道路中心线 7",
+ "position": [
+ -4.1,
+ 0.01,
+ 17
+ ],
+ "rotation": [
+ 0,
+ 0,
+ 0
+ ],
+ "scale": [
+ 1,
+ 1,
+ 1
+ ],
+ "visible": true,
+ "customComponents": [],
+ "materials": [
+ {
+ "color": "e5c64c",
+ "emissive": "302600",
+ "emissiveIntensity": 0.18,
+ "metalness": 0.08,
+ "roughness": 0.72,
+ "opacity": 1,
+ "transparent": false,
+ "depthWrite": true
+ }
+ ]
+ },
+ "outdoor-road-line-8": {
+ "name": "道路中心线 8",
+ "position": [
+ 0,
+ 0.01,
+ 17
+ ],
+ "rotation": [
+ 0,
+ 0,
+ 0
+ ],
+ "scale": [
+ 1,
+ 1,
+ 1
+ ],
+ "visible": true,
+ "customComponents": [],
+ "materials": [
+ {
+ "color": "e5c64c",
+ "emissive": "302600",
+ "emissiveIntensity": 0.18,
+ "metalness": 0.08,
+ "roughness": 0.72,
+ "opacity": 1,
+ "transparent": false,
+ "depthWrite": true
+ }
+ ]
+ },
+ "outdoor-road-line-9": {
+ "name": "道路中心线 9",
+ "position": [
+ 4.1,
+ 0.01,
+ 17
+ ],
+ "rotation": [
+ 0,
+ 0,
+ 0
+ ],
+ "scale": [
+ 1,
+ 1,
+ 1
+ ],
+ "visible": true,
+ "customComponents": [],
+ "materials": [
+ {
+ "color": "e5c64c",
+ "emissive": "302600",
+ "emissiveIntensity": 0.18,
+ "metalness": 0.08,
+ "roughness": 0.72,
+ "opacity": 1,
+ "transparent": false,
+ "depthWrite": true
+ }
+ ]
+ },
+ "outdoor-road-line-10": {
+ "name": "道路中心线 10",
+ "position": [
+ 8.2,
+ 0.01,
+ 17
+ ],
+ "rotation": [
+ 0,
+ 0,
+ 0
+ ],
+ "scale": [
+ 1,
+ 1,
+ 1
+ ],
+ "visible": true,
+ "customComponents": [],
+ "materials": [
+ {
+ "color": "e5c64c",
+ "emissive": "302600",
+ "emissiveIntensity": 0.18,
+ "metalness": 0.08,
+ "roughness": 0.72,
+ "opacity": 1,
+ "transparent": false,
+ "depthWrite": true
+ }
+ ]
+ },
+ "outdoor-road-line-11": {
+ "name": "道路中心线 11",
+ "position": [
+ 12.299999999999999,
+ 0.01,
+ 17
+ ],
+ "rotation": [
+ 0,
+ 0,
+ 0
+ ],
+ "scale": [
+ 1,
+ 1,
+ 1
+ ],
+ "visible": true,
+ "customComponents": [],
+ "materials": [
+ {
+ "color": "e5c64c",
+ "emissive": "302600",
+ "emissiveIntensity": 0.18,
+ "metalness": 0.08,
+ "roughness": 0.72,
+ "opacity": 1,
+ "transparent": false,
+ "depthWrite": true
+ }
+ ]
+ },
+ "outdoor-road-line-12": {
+ "name": "道路中心线 12",
+ "position": [
+ 16.4,
+ 0.01,
+ 17
+ ],
+ "rotation": [
+ 0,
+ 0,
+ 0
+ ],
+ "scale": [
+ 1,
+ 1,
+ 1
+ ],
+ "visible": true,
+ "customComponents": [],
+ "materials": [
+ {
+ "color": "e5c64c",
+ "emissive": "302600",
+ "emissiveIntensity": 0.18,
+ "metalness": 0.08,
+ "roughness": 0.72,
+ "opacity": 1,
+ "transparent": false,
+ "depthWrite": true
+ }
+ ]
+ },
+ "outdoor-road-line-13": {
+ "name": "道路中心线 13",
+ "position": [
+ 20.5,
+ 0.01,
+ 17
+ ],
+ "rotation": [
+ 0,
+ 0,
+ 0
+ ],
+ "scale": [
+ 1,
+ 1,
+ 1
+ ],
+ "visible": true,
+ "customComponents": [],
+ "materials": [
+ {
+ "color": "e5c64c",
+ "emissive": "302600",
+ "emissiveIntensity": 0.18,
+ "metalness": 0.08,
+ "roughness": 0.72,
+ "opacity": 1,
+ "transparent": false,
+ "depthWrite": true
+ }
+ ]
+ },
+ "outdoor-road-line-14": {
+ "name": "道路中心线 14",
+ "position": [
+ 24.599999999999998,
+ 0.01,
+ 17
+ ],
+ "rotation": [
+ 0,
+ 0,
+ 0
+ ],
+ "scale": [
+ 1,
+ 1,
+ 1
+ ],
+ "visible": true,
+ "customComponents": [],
+ "materials": [
+ {
+ "color": "e5c64c",
+ "emissive": "302600",
+ "emissiveIntensity": 0.18,
+ "metalness": 0.08,
+ "roughness": 0.72,
+ "opacity": 1,
+ "transparent": false,
+ "depthWrite": true
+ }
+ ]
+ },
+ "outdoor-road-line-15": {
+ "name": "道路中心线 15",
+ "position": [
+ 28.699999999999996,
+ 0.01,
+ 17
+ ],
+ "rotation": [
+ 0,
+ 0,
+ 0
+ ],
+ "scale": [
+ 1,
+ 1,
+ 1
+ ],
+ "visible": true,
+ "customComponents": [],
+ "materials": [
+ {
+ "color": "e5c64c",
+ "emissive": "302600",
+ "emissiveIntensity": 0.18,
+ "metalness": 0.08,
+ "roughness": 0.72,
+ "opacity": 1,
+ "transparent": false,
+ "depthWrite": true
+ }
+ ]
+ },
+ "outdoor-safety--17.6--11": {
+ "name": "操作区安全标线",
+ "position": [
+ -17.6,
+ 0.012,
+ -11
+ ],
+ "rotation": [
+ 0,
+ 0.5654866776461628,
+ 0
+ ],
+ "scale": [
+ 1,
+ 1,
+ 1
+ ],
+ "visible": true,
+ "customComponents": [],
+ "materials": [
+ {
+ "color": "e5c64c",
+ "emissive": "302600",
+ "emissiveIntensity": 0.18,
+ "metalness": 0.08,
+ "roughness": 0.72,
+ "opacity": 1,
+ "transparent": false,
+ "depthWrite": true
+ }
+ ]
+ },
+ "outdoor-safety--17.6--8.4": {
+ "name": "操作区安全标线",
+ "position": [
+ -17.6,
+ 0.012,
+ -8.4
+ ],
+ "rotation": [
+ 0,
+ 0.5654866776461628,
+ 0
+ ],
+ "scale": [
+ 1,
+ 1,
+ 1
+ ],
+ "visible": true,
+ "customComponents": [],
+ "materials": [
+ {
+ "color": "e5c64c",
+ "emissive": "302600",
+ "emissiveIntensity": 0.18,
+ "metalness": 0.08,
+ "roughness": 0.72,
+ "opacity": 1,
+ "transparent": false,
+ "depthWrite": true
+ }
+ ]
+ },
+ "outdoor-safety--17.6--5.800000000000001": {
+ "name": "操作区安全标线",
+ "position": [
+ -17.6,
+ 0.012,
+ -5.800000000000001
+ ],
+ "rotation": [
+ 0,
+ 0.5654866776461628,
+ 0
+ ],
+ "scale": [
+ 1,
+ 1,
+ 1
+ ],
+ "visible": true,
+ "customComponents": [],
+ "materials": [
+ {
+ "color": "e5c64c",
+ "emissive": "302600",
+ "emissiveIntensity": 0.18,
+ "metalness": 0.08,
+ "roughness": 0.72,
+ "opacity": 1,
+ "transparent": false,
+ "depthWrite": true
+ }
+ ]
+ },
+ "outdoor-safety--17.6--3.2000000000000006": {
+ "name": "操作区安全标线",
+ "position": [
+ -17.6,
+ 0.012,
+ -3.2000000000000006
+ ],
+ "rotation": [
+ 0,
+ 0.5654866776461628,
+ 0
+ ],
+ "scale": [
+ 1,
+ 1,
+ 1
+ ],
+ "visible": true,
+ "customComponents": [],
+ "materials": [
+ {
+ "color": "e5c64c",
+ "emissive": "302600",
+ "emissiveIntensity": 0.18,
+ "metalness": 0.08,
+ "roughness": 0.72,
+ "opacity": 1,
+ "transparent": false,
+ "depthWrite": true
+ }
+ ]
+ },
+ "outdoor-safety--17.6--0.6000000000000005": {
+ "name": "操作区安全标线",
+ "position": [
+ -17.6,
+ 0.012,
+ -0.6000000000000005
+ ],
+ "rotation": [
+ 0,
+ 0.5654866776461628,
+ 0
+ ],
+ "scale": [
+ 1,
+ 1,
+ 1
+ ],
+ "visible": true,
+ "customComponents": [],
+ "materials": [
+ {
+ "color": "e5c64c",
+ "emissive": "302600",
+ "emissiveIntensity": 0.18,
+ "metalness": 0.08,
+ "roughness": 0.72,
+ "opacity": 1,
+ "transparent": false,
+ "depthWrite": true
+ }
+ ]
+ },
+ "outdoor-safety--17.6-1.9999999999999996": {
+ "name": "操作区安全标线",
+ "position": [
+ -17.6,
+ 0.012,
+ 1.9999999999999996
+ ],
+ "rotation": [
+ 0,
+ 0.5654866776461628,
+ 0
+ ],
+ "scale": [
+ 1,
+ 1,
+ 1
+ ],
+ "visible": true,
+ "customComponents": [],
+ "materials": [
+ {
+ "color": "e5c64c",
+ "emissive": "302600",
+ "emissiveIntensity": 0.18,
+ "metalness": 0.08,
+ "roughness": 0.72,
+ "opacity": 1,
+ "transparent": false,
+ "depthWrite": true
+ }
+ ]
+ },
+ "outdoor-safety--17.6-4.6": {
+ "name": "操作区安全标线",
+ "position": [
+ -17.6,
+ 0.012,
+ 4.6
+ ],
+ "rotation": [
+ 0,
+ 0.5654866776461628,
+ 0
+ ],
+ "scale": [
+ 1,
+ 1,
+ 1
+ ],
+ "visible": true,
+ "customComponents": [],
+ "materials": [
+ {
+ "color": "e5c64c",
+ "emissive": "302600",
+ "emissiveIntensity": 0.18,
+ "metalness": 0.08,
+ "roughness": 0.72,
+ "opacity": 1,
+ "transparent": false,
+ "depthWrite": true
+ }
+ ]
+ },
+ "outdoor-safety--17.6-7.199999999999999": {
+ "name": "操作区安全标线",
+ "position": [
+ -17.6,
+ 0.012,
+ 7.199999999999999
+ ],
+ "rotation": [
+ 0,
+ 0.5654866776461628,
+ 0
+ ],
+ "scale": [
+ 1,
+ 1,
+ 1
+ ],
+ "visible": true,
+ "customComponents": [],
+ "materials": [
+ {
+ "color": "e5c64c",
+ "emissive": "302600",
+ "emissiveIntensity": 0.18,
+ "metalness": 0.08,
+ "roughness": 0.72,
+ "opacity": 1,
+ "transparent": false,
+ "depthWrite": true
+ }
+ ]
+ },
+ "outdoor-safety--17.6-9.799999999999999": {
+ "name": "操作区安全标线",
+ "position": [
+ -17.6,
+ 0.012,
+ 9.799999999999999
+ ],
+ "rotation": [
+ 0,
+ 0.5654866776461628,
+ 0
+ ],
+ "scale": [
+ 1,
+ 1,
+ 1
+ ],
+ "visible": true,
+ "customComponents": [],
+ "materials": [
+ {
+ "color": "e5c64c",
+ "emissive": "302600",
+ "emissiveIntensity": 0.18,
+ "metalness": 0.08,
+ "roughness": 0.72,
+ "opacity": 1,
+ "transparent": false,
+ "depthWrite": true
+ }
+ ]
+ },
+ "outdoor-safety-17.6--11": {
+ "name": "操作区安全标线",
+ "position": [
+ 17.6,
+ 0.012,
+ -11
+ ],
+ "rotation": [
+ 0,
+ 0.5654866776461628,
+ 0
+ ],
+ "scale": [
+ 1,
+ 1,
+ 1
+ ],
+ "visible": true,
+ "customComponents": [],
+ "materials": [
+ {
+ "color": "e5c64c",
+ "emissive": "302600",
+ "emissiveIntensity": 0.18,
+ "metalness": 0.08,
+ "roughness": 0.72,
+ "opacity": 1,
+ "transparent": false,
+ "depthWrite": true
+ }
+ ]
+ },
+ "outdoor-safety-17.6--8.4": {
+ "name": "操作区安全标线",
+ "position": [
+ 17.6,
+ 0.012,
+ -8.4
+ ],
+ "rotation": [
+ 0,
+ 0.5654866776461628,
+ 0
+ ],
+ "scale": [
+ 1,
+ 1,
+ 1
+ ],
+ "visible": true,
+ "customComponents": [],
+ "materials": [
+ {
+ "color": "e5c64c",
+ "emissive": "302600",
+ "emissiveIntensity": 0.18,
+ "metalness": 0.08,
+ "roughness": 0.72,
+ "opacity": 1,
+ "transparent": false,
+ "depthWrite": true
+ }
+ ]
+ },
+ "outdoor-safety-17.6--5.800000000000001": {
+ "name": "操作区安全标线",
+ "position": [
+ 17.6,
+ 0.012,
+ -5.800000000000001
+ ],
+ "rotation": [
+ 0,
+ 0.5654866776461628,
+ 0
+ ],
+ "scale": [
+ 1,
+ 1,
+ 1
+ ],
+ "visible": true,
+ "customComponents": [],
+ "materials": [
+ {
+ "color": "e5c64c",
+ "emissive": "302600",
+ "emissiveIntensity": 0.18,
+ "metalness": 0.08,
+ "roughness": 0.72,
+ "opacity": 1,
+ "transparent": false,
+ "depthWrite": true
+ }
+ ]
+ },
+ "outdoor-safety-17.6--3.2000000000000006": {
+ "name": "操作区安全标线",
+ "position": [
+ 17.6,
+ 0.012,
+ -3.2000000000000006
+ ],
+ "rotation": [
+ 0,
+ 0.5654866776461628,
+ 0
+ ],
+ "scale": [
+ 1,
+ 1,
+ 1
+ ],
+ "visible": true,
+ "customComponents": [],
+ "materials": [
+ {
+ "color": "e5c64c",
+ "emissive": "302600",
+ "emissiveIntensity": 0.18,
+ "metalness": 0.08,
+ "roughness": 0.72,
+ "opacity": 1,
+ "transparent": false,
+ "depthWrite": true
+ }
+ ]
+ },
+ "outdoor-safety-17.6--0.6000000000000005": {
+ "name": "操作区安全标线",
+ "position": [
+ 17.6,
+ 0.012,
+ -0.6000000000000005
+ ],
+ "rotation": [
+ 0,
+ 0.5654866776461628,
+ 0
+ ],
+ "scale": [
+ 1,
+ 1,
+ 1
+ ],
+ "visible": true,
+ "customComponents": [],
+ "materials": [
+ {
+ "color": "e5c64c",
+ "emissive": "302600",
+ "emissiveIntensity": 0.18,
+ "metalness": 0.08,
+ "roughness": 0.72,
+ "opacity": 1,
+ "transparent": false,
+ "depthWrite": true
+ }
+ ]
+ },
+ "outdoor-safety-17.6-1.9999999999999996": {
+ "name": "操作区安全标线",
+ "position": [
+ 17.6,
+ 0.012,
+ 1.9999999999999996
+ ],
+ "rotation": [
+ 0,
+ 0.5654866776461628,
+ 0
+ ],
+ "scale": [
+ 1,
+ 1,
+ 1
+ ],
+ "visible": true,
+ "customComponents": [],
+ "materials": [
+ {
+ "color": "e5c64c",
+ "emissive": "302600",
+ "emissiveIntensity": 0.18,
+ "metalness": 0.08,
+ "roughness": 0.72,
+ "opacity": 1,
+ "transparent": false,
+ "depthWrite": true
+ }
+ ]
+ },
+ "outdoor-safety-17.6-4.6": {
+ "name": "操作区安全标线",
+ "position": [
+ 17.6,
+ 0.012,
+ 4.6
+ ],
+ "rotation": [
+ 0,
+ 0.5654866776461628,
+ 0
+ ],
+ "scale": [
+ 1,
+ 1,
+ 1
+ ],
+ "visible": true,
+ "customComponents": [],
+ "materials": [
+ {
+ "color": "e5c64c",
+ "emissive": "302600",
+ "emissiveIntensity": 0.18,
+ "metalness": 0.08,
+ "roughness": 0.72,
+ "opacity": 1,
+ "transparent": false,
+ "depthWrite": true
+ }
+ ]
+ },
+ "outdoor-safety-17.6-7.199999999999999": {
+ "name": "操作区安全标线",
+ "position": [
+ 17.6,
+ 0.012,
+ 7.199999999999999
+ ],
+ "rotation": [
+ 0,
+ 0.5654866776461628,
+ 0
+ ],
+ "scale": [
+ 1,
+ 1,
+ 1
+ ],
+ "visible": true,
+ "customComponents": [],
+ "materials": [
+ {
+ "color": "e5c64c",
+ "emissive": "302600",
+ "emissiveIntensity": 0.18,
+ "metalness": 0.08,
+ "roughness": 0.72,
+ "opacity": 1,
+ "transparent": false,
+ "depthWrite": true
+ }
+ ]
+ },
+ "outdoor-safety-17.6-9.799999999999999": {
+ "name": "操作区安全标线",
+ "position": [
+ 17.6,
+ 0.012,
+ 9.799999999999999
+ ],
+ "rotation": [
+ 0,
+ 0.5654866776461628,
+ 0
+ ],
+ "scale": [
+ 1,
+ 1,
+ 1
+ ],
+ "visible": true,
+ "customComponents": [],
+ "materials": [
+ {
+ "color": "e5c64c",
+ "emissive": "302600",
+ "emissiveIntensity": 0.18,
+ "metalness": 0.08,
+ "roughness": 0.72,
+ "opacity": 1,
+ "transparent": false,
+ "depthWrite": true
+ }
+ ]
+ },
+ "outdoor-workshop": {
+ "name": "维修保障厂房",
+ "position": [
+ 0,
+ 0,
+ 0
+ ],
+ "rotation": [
+ 0,
+ 0,
+ 0
+ ],
+ "scale": [
+ 1,
+ 1,
+ 1
+ ],
+ "visible": true,
+ "customComponents": []
+ },
+ "outdoor-workshop-body": {
+ "name": "维修保障厂房主体",
+ "position": [
+ -10,
+ 3.25,
+ -18
+ ],
+ "rotation": [
+ 0,
+ 0,
+ 0
+ ],
+ "scale": [
+ 1,
+ 1,
+ 1
+ ],
+ "visible": true,
+ "customComponents": [],
+ "materials": [
+ {
+ "color": "536675",
+ "emissive": "000000",
+ "emissiveIntensity": 1,
+ "metalness": 0.18,
+ "roughness": 0.78,
+ "opacity": 1,
+ "transparent": false,
+ "depthWrite": true
+ }
+ ]
+ },
+ "outdoor-workshop-roof": {
+ "name": "厂房金属屋面",
+ "position": [
+ -10,
+ 6.65,
+ -18
+ ],
+ "rotation": [
+ 0,
+ 0,
+ 0
+ ],
+ "scale": [
+ 1,
+ 1,
+ 1
+ ],
+ "visible": true,
+ "customComponents": [],
+ "materials": [
+ {
+ "color": "263846",
+ "emissive": "000000",
+ "emissiveIntensity": 1,
+ "metalness": 0.35,
+ "roughness": 0.62,
+ "opacity": 1,
+ "transparent": false,
+ "depthWrite": true
+ }
+ ]
+ },
+ "outdoor-workshop-window-1": {
+ "name": "厂房观察窗 1",
+ "position": [
+ -17,
+ 3.7,
+ -14.21
+ ],
+ "rotation": [
+ 0,
+ 0,
+ 0
+ ],
+ "scale": [
+ 1,
+ 1,
+ 1
+ ],
+ "visible": true,
+ "customComponents": [],
+ "materials": [
+ {
+ "color": "4a89a2",
+ "emissive": "000000",
+ "emissiveIntensity": 1,
+ "metalness": 0.12,
+ "roughness": 0.25,
+ "opacity": 0.68,
+ "transparent": true,
+ "depthWrite": true
+ }
+ ]
+ },
+ "outdoor-workshop-window-2": {
+ "name": "厂房观察窗 2",
+ "position": [
+ -12.4,
+ 3.7,
+ -14.21
+ ],
+ "rotation": [
+ 0,
+ 0,
+ 0
+ ],
+ "scale": [
+ 1,
+ 1,
+ 1
+ ],
+ "visible": true,
+ "customComponents": [],
+ "materials": [
+ {
+ "color": "4a89a2",
+ "emissive": "000000",
+ "emissiveIntensity": 1,
+ "metalness": 0.12,
+ "roughness": 0.25,
+ "opacity": 0.68,
+ "transparent": true,
+ "depthWrite": true
+ }
+ ]
+ },
+ "outdoor-workshop-window-3": {
+ "name": "厂房观察窗 3",
+ "position": [
+ -7.800000000000001,
+ 3.7,
+ -14.21
+ ],
+ "rotation": [
+ 0,
+ 0,
+ 0
+ ],
+ "scale": [
+ 1,
+ 1,
+ 1
+ ],
+ "visible": true,
+ "customComponents": [],
+ "materials": [
+ {
+ "color": "4a89a2",
+ "emissive": "000000",
+ "emissiveIntensity": 1,
+ "metalness": 0.12,
+ "roughness": 0.25,
+ "opacity": 0.68,
+ "transparent": true,
+ "depthWrite": true
+ }
+ ]
+ },
+ "outdoor-workshop-window-4": {
+ "name": "厂房观察窗 4",
+ "position": [
+ -3.200000000000001,
+ 3.7,
+ -14.21
+ ],
+ "rotation": [
+ 0,
+ 0,
+ 0
+ ],
+ "scale": [
+ 1,
+ 1,
+ 1
+ ],
+ "visible": true,
+ "customComponents": [],
+ "materials": [
+ {
+ "color": "4a89a2",
+ "emissive": "000000",
+ "emissiveIntensity": 1,
+ "metalness": 0.12,
+ "roughness": 0.25,
+ "opacity": 0.68,
+ "transparent": true,
+ "depthWrite": true
+ }
+ ]
+ },
+ "outdoor-workshop-door": {
+ "name": "装备出入门",
+ "position": [
+ -3.6,
+ 2.16,
+ -14.18
+ ],
+ "rotation": [
+ 0,
+ 0,
+ 0
+ ],
+ "scale": [
+ 1,
+ 1,
+ 1
+ ],
+ "visible": true,
+ "customComponents": [],
+ "materials": [
+ {
+ "color": "202a31",
+ "emissive": "000000",
+ "emissiveIntensity": 1,
+ "metalness": 0.02,
+ "roughness": 0.95,
+ "opacity": 1,
+ "transparent": false,
+ "depthWrite": true
+ }
+ ]
+ },
+ "outdoor-canopy": {
+ "name": "室外检修棚",
+ "position": [
+ 0,
+ 0,
+ 0
+ ],
+ "rotation": [
+ 0,
+ 0,
+ 0
+ ],
+ "scale": [
+ 1,
+ 1,
+ 1
+ ],
+ "visible": true,
+ "customComponents": []
+ },
+ "outdoor-canopy-roof": {
+ "name": "检修棚顶",
+ "position": [
+ 18,
+ 5.05,
+ -8
+ ],
+ "rotation": [
+ 0,
+ 0,
+ 0
+ ],
+ "scale": [
+ 1,
+ 1,
+ 1
+ ],
+ "visible": true,
+ "customComponents": [],
+ "materials": [
+ {
+ "color": "263846",
+ "emissive": "000000",
+ "emissiveIntensity": 1,
+ "metalness": 0.35,
+ "roughness": 0.62,
+ "opacity": 1,
+ "transparent": false,
+ "depthWrite": true
+ }
+ ]
+ },
+ "outdoor-canopy-column-1": {
+ "name": "检修棚立柱 1",
+ "position": [
+ 12.7,
+ 2.5,
+ -11
+ ],
+ "rotation": [
+ 0,
+ 0,
+ 0
+ ],
+ "scale": [
+ 1,
+ 1,
+ 1
+ ],
+ "visible": true,
+ "customComponents": [],
+ "materials": [
+ {
+ "color": "66757c",
+ "emissive": "000000",
+ "emissiveIntensity": 1,
+ "metalness": 0.48,
+ "roughness": 0.56,
+ "opacity": 1,
+ "transparent": false,
+ "depthWrite": true
+ }
+ ]
+ },
+ "outdoor-canopy-column-2": {
+ "name": "检修棚立柱 2",
+ "position": [
+ 23.3,
+ 2.5,
+ -11
+ ],
+ "rotation": [
+ 0,
+ 0,
+ 0
+ ],
+ "scale": [
+ 1,
+ 1,
+ 1
+ ],
+ "visible": true,
+ "customComponents": [],
+ "materials": [
+ {
+ "color": "66757c",
+ "emissive": "000000",
+ "emissiveIntensity": 1,
+ "metalness": 0.48,
+ "roughness": 0.56,
+ "opacity": 1,
+ "transparent": false,
+ "depthWrite": true
+ }
+ ]
+ },
+ "outdoor-canopy-column-3": {
+ "name": "检修棚立柱 3",
+ "position": [
+ 12.7,
+ 2.5,
+ -5
+ ],
+ "rotation": [
+ 0,
+ 0,
+ 0
+ ],
+ "scale": [
+ 1,
+ 1,
+ 1
+ ],
+ "visible": true,
+ "customComponents": [],
+ "materials": [
+ {
+ "color": "66757c",
+ "emissive": "000000",
+ "emissiveIntensity": 1,
+ "metalness": 0.48,
+ "roughness": 0.56,
+ "opacity": 1,
+ "transparent": false,
+ "depthWrite": true
+ }
+ ]
+ },
+ "outdoor-canopy-column-4": {
+ "name": "检修棚立柱 4",
+ "position": [
+ 23.3,
+ 2.5,
+ -5
+ ],
+ "rotation": [
+ 0,
+ 0,
+ 0
+ ],
+ "scale": [
+ 1,
+ 1,
+ 1
+ ],
+ "visible": true,
+ "customComponents": [],
+ "materials": [
+ {
+ "color": "66757c",
+ "emissive": "000000",
+ "emissiveIntensity": 1,
+ "metalness": 0.48,
+ "roughness": 0.56,
+ "opacity": 1,
+ "transparent": false,
+ "depthWrite": true
+ }
+ ]
+ },
+ "outdoor-service-box": {
+ "name": "室外工具柜",
+ "position": [
+ 22.2,
+ 1.05,
+ -9.8
+ ],
+ "rotation": [
+ 0,
+ 0,
+ 0
+ ],
+ "scale": [
+ 1,
+ 1,
+ 1
+ ],
+ "visible": true,
+ "customComponents": [],
+ "materials": [
+ {
+ "color": "d98d2b",
+ "emissive": "000000",
+ "emissiveIntensity": 1,
+ "metalness": 0.12,
+ "roughness": 0.7,
+ "opacity": 1,
+ "transparent": false,
+ "depthWrite": true
+ }
+ ]
+ },
+ "outdoor-perimeter": {
+ "name": "场地边界与安全设施",
+ "position": [
+ 0,
+ 0,
+ 0
+ ],
+ "rotation": [
+ 0,
+ 0,
+ 0
+ ],
+ "scale": [
+ 1,
+ 1,
+ 1
+ ],
+ "visible": true,
+ "customComponents": []
+ },
+ "outdoor-fence-back-1": {
+ "name": "后侧围栏立柱 1",
+ "position": [
+ -29,
+ 1.1,
+ -25
+ ],
+ "rotation": [
+ 0,
+ 0,
+ 0
+ ],
+ "scale": [
+ 1,
+ 1,
+ 1
+ ],
+ "visible": true,
+ "customComponents": [],
+ "materials": [
+ {
+ "color": "66757c",
+ "emissive": "000000",
+ "emissiveIntensity": 1,
+ "metalness": 0.48,
+ "roughness": 0.56,
+ "opacity": 1,
+ "transparent": false,
+ "depthWrite": true
+ }
+ ]
+ },
+ "outdoor-fence-back-2": {
+ "name": "后侧围栏立柱 2",
+ "position": [
+ -23.2,
+ 1.1,
+ -25
+ ],
+ "rotation": [
+ 0,
+ 0,
+ 0
+ ],
+ "scale": [
+ 1,
+ 1,
+ 1
+ ],
+ "visible": true,
+ "customComponents": [],
+ "materials": [
+ {
+ "color": "66757c",
+ "emissive": "000000",
+ "emissiveIntensity": 1,
+ "metalness": 0.48,
+ "roughness": 0.56,
+ "opacity": 1,
+ "transparent": false,
+ "depthWrite": true
+ }
+ ]
+ },
+ "outdoor-fence-back-3": {
+ "name": "后侧围栏立柱 3",
+ "position": [
+ -17.4,
+ 1.1,
+ -25
+ ],
+ "rotation": [
+ 0,
+ 0,
+ 0
+ ],
+ "scale": [
+ 1,
+ 1,
+ 1
+ ],
+ "visible": true,
+ "customComponents": [],
+ "materials": [
+ {
+ "color": "66757c",
+ "emissive": "000000",
+ "emissiveIntensity": 1,
+ "metalness": 0.48,
+ "roughness": 0.56,
+ "opacity": 1,
+ "transparent": false,
+ "depthWrite": true
+ }
+ ]
+ },
+ "outdoor-fence-back-4": {
+ "name": "后侧围栏立柱 4",
+ "position": [
+ -11.6,
+ 1.1,
+ -25
+ ],
+ "rotation": [
+ 0,
+ 0,
+ 0
+ ],
+ "scale": [
+ 1,
+ 1,
+ 1
+ ],
+ "visible": true,
+ "customComponents": [],
+ "materials": [
+ {
+ "color": "66757c",
+ "emissive": "000000",
+ "emissiveIntensity": 1,
+ "metalness": 0.48,
+ "roughness": 0.56,
+ "opacity": 1,
+ "transparent": false,
+ "depthWrite": true
+ }
+ ]
+ },
+ "outdoor-fence-back-5": {
+ "name": "后侧围栏立柱 5",
+ "position": [
+ -5.8,
+ 1.1,
+ -25
+ ],
+ "rotation": [
+ 0,
+ 0,
+ 0
+ ],
+ "scale": [
+ 1,
+ 1,
+ 1
+ ],
+ "visible": true,
+ "customComponents": [],
+ "materials": [
+ {
+ "color": "66757c",
+ "emissive": "000000",
+ "emissiveIntensity": 1,
+ "metalness": 0.48,
+ "roughness": 0.56,
+ "opacity": 1,
+ "transparent": false,
+ "depthWrite": true
+ }
+ ]
+ },
+ "outdoor-fence-back-6": {
+ "name": "后侧围栏立柱 6",
+ "position": [
+ 0,
+ 1.1,
+ -25
+ ],
+ "rotation": [
+ 0,
+ 0,
+ 0
+ ],
+ "scale": [
+ 1,
+ 1,
+ 1
+ ],
+ "visible": true,
+ "customComponents": [],
+ "materials": [
+ {
+ "color": "66757c",
+ "emissive": "000000",
+ "emissiveIntensity": 1,
+ "metalness": 0.48,
+ "roughness": 0.56,
+ "opacity": 1,
+ "transparent": false,
+ "depthWrite": true
+ }
+ ]
+ },
+ "outdoor-fence-back-7": {
+ "name": "后侧围栏立柱 7",
+ "position": [
+ 5.8,
+ 1.1,
+ -25
+ ],
+ "rotation": [
+ 0,
+ 0,
+ 0
+ ],
+ "scale": [
+ 1,
+ 1,
+ 1
+ ],
+ "visible": true,
+ "customComponents": [],
+ "materials": [
+ {
+ "color": "66757c",
+ "emissive": "000000",
+ "emissiveIntensity": 1,
+ "metalness": 0.48,
+ "roughness": 0.56,
+ "opacity": 1,
+ "transparent": false,
+ "depthWrite": true
+ }
+ ]
+ },
+ "outdoor-fence-back-8": {
+ "name": "后侧围栏立柱 8",
+ "position": [
+ 11.6,
+ 1.1,
+ -25
+ ],
+ "rotation": [
+ 0,
+ 0,
+ 0
+ ],
+ "scale": [
+ 1,
+ 1,
+ 1
+ ],
+ "visible": true,
+ "customComponents": [],
+ "materials": [
+ {
+ "color": "66757c",
+ "emissive": "000000",
+ "emissiveIntensity": 1,
+ "metalness": 0.48,
+ "roughness": 0.56,
+ "opacity": 1,
+ "transparent": false,
+ "depthWrite": true
+ }
+ ]
+ },
+ "outdoor-fence-back-9": {
+ "name": "后侧围栏立柱 9",
+ "position": [
+ 17.4,
+ 1.1,
+ -25
+ ],
+ "rotation": [
+ 0,
+ 0,
+ 0
+ ],
+ "scale": [
+ 1,
+ 1,
+ 1
+ ],
+ "visible": true,
+ "customComponents": [],
+ "materials": [
+ {
+ "color": "66757c",
+ "emissive": "000000",
+ "emissiveIntensity": 1,
+ "metalness": 0.48,
+ "roughness": 0.56,
+ "opacity": 1,
+ "transparent": false,
+ "depthWrite": true
+ }
+ ]
+ },
+ "outdoor-fence-back-10": {
+ "name": "后侧围栏立柱 10",
+ "position": [
+ 23.2,
+ 1.1,
+ -25
+ ],
+ "rotation": [
+ 0,
+ 0,
+ 0
+ ],
+ "scale": [
+ 1,
+ 1,
+ 1
+ ],
+ "visible": true,
+ "customComponents": [],
+ "materials": [
+ {
+ "color": "66757c",
+ "emissive": "000000",
+ "emissiveIntensity": 1,
+ "metalness": 0.48,
+ "roughness": 0.56,
+ "opacity": 1,
+ "transparent": false,
+ "depthWrite": true
+ }
+ ]
+ },
+ "outdoor-fence-back-11": {
+ "name": "后侧围栏立柱 11",
+ "position": [
+ 29,
+ 1.1,
+ -25
+ ],
+ "rotation": [
+ 0,
+ 0,
+ 0
+ ],
+ "scale": [
+ 1,
+ 1,
+ 1
+ ],
+ "visible": true,
+ "customComponents": [],
+ "materials": [
+ {
+ "color": "66757c",
+ "emissive": "000000",
+ "emissiveIntensity": 1,
+ "metalness": 0.48,
+ "roughness": 0.56,
+ "opacity": 1,
+ "transparent": false,
+ "depthWrite": true
+ }
+ ]
+ },
+ "outdoor-fence-rail-a": {
+ "name": "后侧围栏上横杆",
+ "position": [
+ 0,
+ 1.85,
+ -25
+ ],
+ "rotation": [
+ 0,
+ 0,
+ 0
+ ],
+ "scale": [
+ 1,
+ 1,
+ 1
+ ],
+ "visible": true,
+ "customComponents": [],
+ "materials": [
+ {
+ "color": "66757c",
+ "emissive": "000000",
+ "emissiveIntensity": 1,
+ "metalness": 0.48,
+ "roughness": 0.56,
+ "opacity": 1,
+ "transparent": false,
+ "depthWrite": true
+ }
+ ]
+ },
+ "outdoor-fence-rail-b": {
+ "name": "后侧围栏下横杆",
+ "position": [
+ 0,
+ 0.75,
+ -25
+ ],
+ "rotation": [
+ 0,
+ 0,
+ 0
+ ],
+ "scale": [
+ 1,
+ 1,
+ 1
+ ],
+ "visible": true,
+ "customComponents": [],
+ "materials": [
+ {
+ "color": "66757c",
+ "emissive": "000000",
+ "emissiveIntensity": 1,
+ "metalness": 0.48,
+ "roughness": 0.56,
+ "opacity": 1,
+ "transparent": false,
+ "depthWrite": true
+ }
+ ]
+ },
+ "outdoor-scene-sign": {
+ "name": "室外实训区域标识",
+ "position": [
+ 26,
+ 2.4,
+ 11.5
+ ],
+ "rotation": [
+ 0,
+ 0,
+ 0
+ ],
+ "scale": [
+ 1,
+ 1,
+ 1
+ ],
+ "visible": true,
+ "customComponents": [],
+ "materials": [
+ {
+ "color": "536675",
+ "emissive": "000000",
+ "emissiveIntensity": 1,
+ "metalness": 0.18,
+ "roughness": 0.78,
+ "opacity": 1,
+ "transparent": false,
+ "depthWrite": true
+ }
+ ]
+ },
+ "outdoor-scene-sign-post-a": {
+ "name": "区域标识支柱 A",
+ "position": [
+ 24,
+ 1.4,
+ 11.5
+ ],
+ "rotation": [
+ 0,
+ 0,
+ 0
+ ],
+ "scale": [
+ 1,
+ 1,
+ 1
+ ],
+ "visible": true,
+ "customComponents": [],
+ "materials": [
+ {
+ "color": "66757c",
+ "emissive": "000000",
+ "emissiveIntensity": 1,
+ "metalness": 0.48,
+ "roughness": 0.56,
+ "opacity": 1,
+ "transparent": false,
+ "depthWrite": true
+ }
+ ]
+ },
+ "outdoor-scene-sign-post-b": {
+ "name": "区域标识支柱 B",
+ "position": [
+ 28,
+ 1.4,
+ 11.5
+ ],
+ "rotation": [
+ 0,
+ 0,
+ 0
+ ],
+ "scale": [
+ 1,
+ 1,
+ 1
+ ],
+ "visible": true,
+ "customComponents": [],
+ "materials": [
+ {
+ "color": "66757c",
+ "emissive": "000000",
+ "emissiveIntensity": 1,
+ "metalness": 0.48,
+ "roughness": 0.56,
+ "opacity": 1,
+ "transparent": false,
+ "depthWrite": true
+ }
+ ]
+ }
+ }
+ },
+ "runtime": {
+ "startMode": "guided",
+ "showStepPanel": true,
+ "showProgress": true,
+ "allowReplay": true,
+ "language": "zh-CN"
+ },
+ "publish": {
+ "target": "standalone-html",
+ "deployment": "offline",
+ "includeProjectData": true,
+ "includeViewportPreview": true
+ }
+}
\ No newline at end of file
diff --git a/reports/model-editor-closure-20260906/downloads/preflight-209.json b/reports/model-editor-closure-20260906/downloads/preflight-209.json
new file mode 100644
index 0000000..a9eee7f
--- /dev/null
+++ b/reports/model-editor-closure-20260906/downloads/preflight-209.json
@@ -0,0 +1,3414 @@
+{
+ "schema": "digital-twin-editor-project",
+ "version": 4,
+ "name": "闭环验收-加载与预检-mtphq9y1",
+ "modelId": "imported-model_mto6v5w6_09-fox",
+ "modelResource": {
+ "kind": "imported",
+ "id": "imported-model_mto6v5w6_09-fox",
+ "name": "09-Fox",
+ "assetCode": "MODEL_MTO6V5W6_09-FOX",
+ "code": "MODEL_MTO6V5W6_09-FOX",
+ "storageUri": "fsvc://unreal-tran/model/2026/09/05/3fc69ae0d435f869afca1fcd943486d1.glb",
+ "assetKey": "",
+ "resourceUrl": "",
+ "sourceName": "09-Fox.glb",
+ "sizeBytes": 162852,
+ "lastModified": 0,
+ "mimeType": "model/gltf-binary",
+ "thumbnail": "",
+ "vertices": 1728,
+ "triangles": 576,
+ "nodes": 27,
+ "meshes": 1,
+ "materials": 1,
+ "textures": 1,
+ "animations": 3,
+ "animationDurationSeconds": 3.4166667461395264,
+ "modelMetadata": {
+ "nodeCount": 27,
+ "meshCount": 1,
+ "materialCount": 1,
+ "textureCount": 1,
+ "animations": [
+ {
+ "index": 0,
+ "name": "Survey",
+ "start": 0,
+ "end": 3.4166667461395264,
+ "duration": 3.4166667461395264,
+ "trackCount": 21
+ },
+ {
+ "index": 1,
+ "name": "Walk",
+ "start": 0,
+ "end": 0.7083333134651184,
+ "duration": 0.7083333134651184,
+ "trackCount": 21
+ },
+ {
+ "index": 2,
+ "name": "Run",
+ "start": 0,
+ "end": 1.1583333015441895,
+ "duration": 1.1583333015441895,
+ "trackCount": 21
+ }
+ ],
+ "animationDurationSeconds": 3.4166667461395264
+ },
+ "nativeAnimations": [
+ {
+ "index": 0,
+ "name": "Survey",
+ "start": 0,
+ "end": 3.4166667461395264,
+ "duration": 3.4166667461395264,
+ "trackCount": 21
+ },
+ {
+ "index": 1,
+ "name": "Walk",
+ "start": 0,
+ "end": 0.7083333134651184,
+ "duration": 0.7083333134651184,
+ "trackCount": 21
+ },
+ {
+ "index": 2,
+ "name": "Run",
+ "start": 0,
+ "end": 1.1583333015441895,
+ "duration": 1.1583333015441895,
+ "trackCount": 21
+ }
+ ]
+ },
+ "meta": {
+ "description": "用于装备维修实训场景编排、编辑器回归与离线发布验证的系统内置基准工程",
+ "author": "数字孪生项目组",
+ "scene": "车间与野外装备维修实训环境",
+ "tags": [
+ "三维场景",
+ "时间轴动画",
+ "蓝图交互",
+ "实训流程"
+ ],
+ "migratedFrom": "legacy-model-document",
+ "source": "content-api-glb",
+ "sourceName": "09-Fox.glb",
+ "assetCode": "MODEL_MTO6V5W6_09-FOX",
+ "storageUri": "fsvc://unreal-tran/model/2026/09/05/3fc69ae0d435f869afca1fcd943486d1.glb"
+ },
+ "timeline": {
+ "version": 1,
+ "duration": 8,
+ "currentTime": 0,
+ "loop": true,
+ "zoom": 1,
+ "tracks": [
+ {
+ "id": "track-0-1yjfz9t",
+ "name": "模型整体位移",
+ "targetId": "interaction-b9240aaa",
+ "property": "position",
+ "color": "#ffb22e",
+ "muted": false,
+ "keyframes": [
+ {
+ "id": "key-0-0-kmfo820",
+ "time": 0,
+ "value": [
+ 0,
+ 0,
+ 0
+ ],
+ "easing": "easeInOut"
+ },
+ {
+ "id": "key-0-1-snsj2e2",
+ "time": 8,
+ "value": [
+ 0,
+ 0,
+ 0
+ ],
+ "easing": "easeInOut"
+ }
+ ]
+ }
+ ]
+ },
+ "blueprint": {
+ "version": 1,
+ "name": "挖掘机作业实训闭环",
+ "viewport": {
+ "x": 55,
+ "y": 55,
+ "zoom": 0.82
+ },
+ "nodes": [
+ {
+ "id": "bp-start",
+ "type": "trigger-click",
+ "title": "点击挖掘机",
+ "x": 40,
+ "y": 55,
+ "params": {
+ "event": "click",
+ "target": "挖掘机"
+ }
+ },
+ {
+ "id": "bp-highlight",
+ "type": "highlight",
+ "title": "高亮工作装置",
+ "x": 300,
+ "y": 55,
+ "params": {
+ "target": "铲斗",
+ "color": "#38e8ff",
+ "pulse": true
+ }
+ },
+ {
+ "id": "bp-message",
+ "type": "message",
+ "title": "显示操作提示",
+ "x": 560,
+ "y": 55,
+ "params": {
+ "title": "挖掘作业实训",
+ "text": "观察动臂、斗杆和铲斗的协同动作",
+ "level": "info"
+ }
+ },
+ {
+ "id": "bp-play",
+ "type": "play-timeline",
+ "title": "播放作业动画",
+ "x": 820,
+ "y": 55,
+ "params": {
+ "clip": "挖掘机作业实训",
+ "from": 0,
+ "speed": 1,
+ "waitForEnd": true
+ }
+ },
+ {
+ "id": "bp-wait",
+ "type": "wait",
+ "title": "动作稳定",
+ "x": 560,
+ "y": 245,
+ "params": {
+ "duration": 600
+ }
+ },
+ {
+ "id": "bp-check",
+ "type": "condition",
+ "title": "动画是否完成",
+ "x": 300,
+ "y": 245,
+ "params": {
+ "variable": "timelineComplete",
+ "operator": "==",
+ "value": true
+ }
+ },
+ {
+ "id": "bp-retry",
+ "type": "message",
+ "title": "等待动画完成",
+ "x": 820,
+ "y": 245,
+ "params": {
+ "title": "流程校验",
+ "text": "动画尚未完成,请重新执行",
+ "level": "warning"
+ }
+ },
+ {
+ "id": "bp-finish",
+ "type": "finish",
+ "title": "流程完成",
+ "x": 40,
+ "y": 245,
+ "params": {
+ "result": "success",
+ "text": "挖掘机作业实训流程完成"
+ }
+ }
+ ],
+ "edges": [
+ {
+ "id": "bp-e1",
+ "from": {
+ "node": "bp-start",
+ "port": "then"
+ },
+ "to": {
+ "node": "bp-highlight",
+ "port": "in"
+ }
+ },
+ {
+ "id": "bp-e2",
+ "from": {
+ "node": "bp-highlight",
+ "port": "then"
+ },
+ "to": {
+ "node": "bp-message",
+ "port": "in"
+ }
+ },
+ {
+ "id": "bp-e3",
+ "from": {
+ "node": "bp-message",
+ "port": "then"
+ },
+ "to": {
+ "node": "bp-play",
+ "port": "in"
+ }
+ },
+ {
+ "id": "bp-e4",
+ "from": {
+ "node": "bp-play",
+ "port": "then"
+ },
+ "to": {
+ "node": "bp-wait",
+ "port": "in"
+ }
+ },
+ {
+ "id": "bp-e5",
+ "from": {
+ "node": "bp-wait",
+ "port": "then"
+ },
+ "to": {
+ "node": "bp-check",
+ "port": "in"
+ }
+ },
+ {
+ "id": "bp-e6",
+ "from": {
+ "node": "bp-check",
+ "port": "true"
+ },
+ "to": {
+ "node": "bp-finish",
+ "port": "in"
+ }
+ },
+ {
+ "id": "bp-e7",
+ "from": {
+ "node": "bp-check",
+ "port": "false"
+ },
+ "to": {
+ "node": "bp-retry",
+ "port": "in"
+ }
+ },
+ {
+ "id": "bp-e8",
+ "from": {
+ "node": "bp-retry",
+ "port": "then"
+ },
+ "to": {
+ "node": "bp-play",
+ "port": "in"
+ }
+ }
+ ]
+ },
+ "camera": {
+ "position": [
+ 11.153569378931522,
+ 8.526809324389628,
+ 13.922975975299929
+ ],
+ "target": [
+ 0.01518877997232826,
+ 2.4006999949620704,
+ 2.266009371254541e-7
+ ]
+ },
+ "model": {
+ "objects": {
+ "equipment-root": {
+ "name": "09-Fox",
+ "nodeName": "09-Fox",
+ "nodePath": "@root",
+ "interactionId": "interaction-b9240aaa",
+ "capabilities": {
+ "selectable": true,
+ "transform": true,
+ "visible": true,
+ "material": false,
+ "animatable": true
+ },
+ "position": [
+ 0,
+ 0,
+ 0
+ ],
+ "rotation": [
+ 0,
+ 0,
+ 0
+ ],
+ "scale": [
+ 1,
+ 1,
+ 1
+ ],
+ "visible": true,
+ "customComponents": []
+ },
+ "model-imported-model_mto6v5w6_09-fox-0": {
+ "name": "",
+ "nodeName": "Group",
+ "nodePath": "0",
+ "interactionId": "interaction-02351bce",
+ "capabilities": {
+ "selectable": true,
+ "transform": true,
+ "visible": true,
+ "material": false,
+ "animatable": true
+ },
+ "position": [
+ 3.875009990661482e-8,
+ 0.007396563645075124,
+ 0.6522089834776823
+ ],
+ "rotation": [
+ 0,
+ 0,
+ 0
+ ],
+ "scale": [
+ 0.06075496488891343,
+ 0.06075496488891343,
+ 0.06075496488891343
+ ],
+ "visible": true,
+ "customComponents": []
+ },
+ "model-imported-model_mto6v5w6_09-fox-1": {
+ "name": "root",
+ "nodeName": "root",
+ "nodePath": "0/0",
+ "interactionId": "interaction-a4d35db9",
+ "capabilities": {
+ "selectable": true,
+ "transform": true,
+ "visible": true,
+ "material": false,
+ "animatable": true
+ },
+ "position": [
+ 0,
+ 0,
+ 0
+ ],
+ "rotation": [
+ 0,
+ 0,
+ 0
+ ],
+ "scale": [
+ 1,
+ 1,
+ 1
+ ],
+ "visible": true,
+ "customComponents": []
+ },
+ "model-imported-model_mto6v5w6_09-fox-2": {
+ "name": "_rootJoint",
+ "nodeName": "_rootJoint",
+ "nodePath": "0/0/0",
+ "interactionId": "interaction-4476c556",
+ "capabilities": {
+ "selectable": true,
+ "transform": true,
+ "visible": true,
+ "material": false,
+ "animatable": true
+ },
+ "position": [
+ 0,
+ 0,
+ 0
+ ],
+ "rotation": [
+ 0,
+ 0,
+ 0
+ ],
+ "scale": [
+ 1,
+ 1,
+ 1
+ ],
+ "visible": true,
+ "customComponents": []
+ },
+ "model-imported-model_mto6v5w6_09-fox-3": {
+ "name": "b_Root_00",
+ "nodeName": "b_Root_00",
+ "nodePath": "0/0/0/0",
+ "interactionId": "interaction-ffc45d11",
+ "capabilities": {
+ "selectable": true,
+ "transform": true,
+ "visible": true,
+ "material": false,
+ "animatable": true
+ },
+ "position": [
+ 0,
+ 0,
+ 0
+ ],
+ "rotation": [
+ -1.5708000357176293,
+ 0,
+ 0
+ ],
+ "scale": [
+ 1,
+ 1,
+ 1
+ ],
+ "visible": true,
+ "customComponents": []
+ },
+ "model-imported-model_mto6v5w6_09-fox-4": {
+ "name": "b_Hip_01",
+ "nodeName": "b_Hip_01",
+ "nodePath": "0/0/0/0/0",
+ "interactionId": "interaction-7a75e39e",
+ "capabilities": {
+ "selectable": true,
+ "transform": true,
+ "visible": true,
+ "material": false,
+ "animatable": true
+ },
+ "position": [
+ 0.000001241521920852674,
+ 22.061977333358605,
+ 34.62373020761327
+ ],
+ "rotation": [
+ 0.44722050284761794,
+ -1.5707951637404125,
+ 0
+ ],
+ "scale": [
+ 1,
+ 1,
+ 1
+ ],
+ "visible": true,
+ "customComponents": []
+ },
+ "model-imported-model_mto6v5w6_09-fox-5": {
+ "name": "b_Spine01_02",
+ "nodeName": "b_Spine01_02",
+ "nodePath": "0/0/0/0/0/0",
+ "interactionId": "interaction-663ea469",
+ "capabilities": {
+ "selectable": true,
+ "transform": true,
+ "visible": true,
+ "material": false,
+ "animatable": true
+ },
+ "position": [
+ 12.850601196289062,
+ 0,
+ 0
+ ],
+ "rotation": [
+ -3.357310257291308e-7,
+ -3.316574913360067e-8,
+ -1.2102038452161539
+ ],
+ "scale": [
+ 1,
+ 1,
+ 1
+ ],
+ "visible": true,
+ "customComponents": []
+ },
+ "model-imported-model_mto6v5w6_09-fox-6": {
+ "name": "b_Spine02_03",
+ "nodeName": "b_Spine02_03",
+ "nodePath": "0/0/0/0/0/0/0",
+ "interactionId": "interaction-46867126",
+ "capabilities": {
+ "selectable": true,
+ "transform": true,
+ "visible": true,
+ "material": false,
+ "animatable": true
+ },
+ "position": [
+ 21.65575408935547,
+ -0.000118255615234375,
+ 0
+ ],
+ "rotation": [
+ 8.772651057591018e-8,
+ 3.3355428835535634e-8,
+ 0.3633406824515624
+ ],
+ "scale": [
+ 1,
+ 1,
+ 1
+ ],
+ "visible": true,
+ "customComponents": []
+ },
+ "model-imported-model_mto6v5w6_09-fox-7": {
+ "name": "b_Neck_04",
+ "nodeName": "b_Neck_04",
+ "nodePath": "0/0/0/0/0/0/0/0",
+ "interactionId": "interaction-27148d81",
+ "capabilities": {
+ "selectable": true,
+ "transform": true,
+ "visible": true,
+ "material": false,
+ "animatable": true
+ },
+ "position": [
+ 25.64914321899414,
+ 0,
+ 0
+ ],
+ "rotation": [
+ 6.423807629998062e-15,
+ 1.9993472402144555e-16,
+ 0.031113979193075042
+ ],
+ "scale": [
+ 1,
+ 1,
+ 1
+ ],
+ "visible": true,
+ "customComponents": []
+ },
+ "model-imported-model_mto6v5w6_09-fox-8": {
+ "name": "b_Head_05",
+ "nodeName": "b_Head_05",
+ "nodePath": "0/0/0/0/0/0/0/0/0",
+ "interactionId": "interaction-b24eef2e",
+ "capabilities": {
+ "selectable": true,
+ "transform": true,
+ "visible": true,
+ "material": false,
+ "animatable": true
+ },
+ "position": [
+ 13.376960754394531,
+ 0,
+ 0
+ ],
+ "rotation": [
+ 3.253825627358364e-8,
+ -1.577320798309453e-8,
+ -0.451380500897834
+ ],
+ "scale": [
+ 1,
+ 1,
+ 1
+ ],
+ "visible": true,
+ "customComponents": []
+ },
+ "model-imported-model_mto6v5w6_09-fox-9": {
+ "name": "b_RightUpperArm_06",
+ "nodeName": "b_RightUpperArm_06",
+ "nodePath": "0/0/0/0/0/0/0/1",
+ "interactionId": "interaction-26148bee",
+ "capabilities": {
+ "selectable": true,
+ "transform": true,
+ "visible": true,
+ "material": false,
+ "animatable": true
+ },
+ "position": [
+ 18.677913665771484,
+ -4.297340393066406,
+ 6.9675750732421875
+ ],
+ "rotation": [
+ -0.035644184619881884,
+ -0.047001863061712265,
+ -1.2972005346348685
+ ],
+ "scale": [
+ 1,
+ 1,
+ 1
+ ],
+ "visible": true,
+ "customComponents": []
+ },
+ "model-imported-model_mto6v5w6_09-fox-10": {
+ "name": "b_RightForeArm_07",
+ "nodeName": "b_RightForeArm_07",
+ "nodePath": "0/0/0/0/0/0/0/1/0",
+ "interactionId": "interaction-36896559",
+ "capabilities": {
+ "selectable": true,
+ "transform": true,
+ "visible": true,
+ "material": false,
+ "animatable": true
+ },
+ "position": [
+ 23.04512596130371,
+ 0,
+ 0
+ ],
+ "rotation": [
+ 2.1355818997049898e-8,
+ 7.233656206674673e-8,
+ 1.2561843405841544
+ ],
+ "scale": [
+ 1,
+ 1,
+ 1
+ ],
+ "visible": true,
+ "customComponents": []
+ },
+ "model-imported-model_mto6v5w6_09-fox-11": {
+ "name": "b_RightHand_08",
+ "nodeName": "b_RightHand_08",
+ "nodePath": "0/0/0/0/0/0/0/1/0/0",
+ "interactionId": "interaction-57a124f6",
+ "capabilities": {
+ "selectable": true,
+ "transform": true,
+ "visible": true,
+ "material": false,
+ "animatable": true
+ },
+ "position": [
+ 19.350055694580078,
+ -0.14598655700683594,
+ 0
+ ],
+ "rotation": [
+ 0.005245101551986586,
+ 0.05200705984217871,
+ -0.267165337534188
+ ],
+ "scale": [
+ 1,
+ 1,
+ 1
+ ],
+ "visible": true,
+ "customComponents": []
+ },
+ "model-imported-model_mto6v5w6_09-fox-12": {
+ "name": "b_LeftUpperArm_09",
+ "nodeName": "b_LeftUpperArm_09",
+ "nodePath": "0/0/0/0/0/0/0/2",
+ "interactionId": "interaction-25148a5b",
+ "capabilities": {
+ "selectable": true,
+ "transform": true,
+ "visible": true,
+ "material": false,
+ "animatable": true
+ },
+ "position": [
+ 18.67791748046875,
+ -4.297344207763672,
+ -6.967987060546875
+ ],
+ "rotation": [
+ -0.00020118104561539401,
+ 0.0009350561550523671,
+ -1.5148902559141275
+ ],
+ "scale": [
+ 1,
+ 1,
+ 1
+ ],
+ "visible": true,
+ "customComponents": []
+ },
+ "model-imported-model_mto6v5w6_09-fox-13": {
+ "name": "b_LeftForeArm_010",
+ "nodeName": "b_LeftForeArm_010",
+ "nodePath": "0/0/0/0/0/0/0/2/0",
+ "interactionId": "interaction-5c502ac4",
+ "capabilities": {
+ "selectable": true,
+ "transform": true,
+ "visible": true,
+ "material": false,
+ "animatable": true
+ },
+ "position": [
+ 23.045124053955078,
+ 0,
+ 0
+ ],
+ "rotation": [
+ 2.5220861991717696e-8,
+ 7.109903232964989e-8,
+ 1.2307106255258764
+ ],
+ "scale": [
+ 1,
+ 1,
+ 1
+ ],
+ "visible": true,
+ "customComponents": []
+ },
+ "model-imported-model_mto6v5w6_09-fox-14": {
+ "name": "b_LeftHand_011",
+ "nodeName": "b_LeftHand_011",
+ "nodePath": "0/0/0/0/0/0/0/2/0/0",
+ "interactionId": "interaction-192b4cd3",
+ "capabilities": {
+ "selectable": true,
+ "transform": true,
+ "visible": true,
+ "material": false,
+ "animatable": true
+ },
+ "position": [
+ 19.350051879882812,
+ -0.14599037170410156,
+ 0
+ ],
+ "rotation": [
+ 0.004279557751527162,
+ -0.03344276494440009,
+ -1.5985291598689726
+ ],
+ "scale": [
+ 1,
+ 1,
+ 1
+ ],
+ "visible": true,
+ "customComponents": []
+ },
+ "model-imported-model_mto6v5w6_09-fox-15": {
+ "name": "b_Tail01_012",
+ "nodeName": "b_Tail01_012",
+ "nodePath": "0/0/0/0/0/1",
+ "interactionId": "interaction-653ea2d6",
+ "capabilities": {
+ "selectable": true,
+ "transform": true,
+ "visible": true,
+ "material": false,
+ "animatable": true
+ },
+ "position": [
+ 4.2603759765625,
+ 15.958770751953125,
+ 0
+ ],
+ "rotation": [
+ -5.364745216052604e-7,
+ 3.424797608447095e-7,
+ 1.9941304874941974
+ ],
+ "scale": [
+ 1,
+ 1,
+ 1
+ ],
+ "visible": true,
+ "customComponents": []
+ },
+ "model-imported-model_mto6v5w6_09-fox-16": {
+ "name": "b_Tail02_013",
+ "nodeName": "b_Tail02_013",
+ "nodePath": "0/0/0/0/0/1/0",
+ "interactionId": "interaction-6c4d3691",
+ "capabilities": {
+ "selectable": true,
+ "transform": true,
+ "visible": true,
+ "material": false,
+ "animatable": true
+ },
+ "position": [
+ 12.411918640136719,
+ 0,
+ 0
+ ],
+ "rotation": [
+ -6.738507958375962e-23,
+ -5.2439168243310865e-23,
+ 0.3128105211171357
+ ],
+ "scale": [
+ 1,
+ 1,
+ 1
+ ],
+ "visible": true,
+ "customComponents": []
+ },
+ "model-imported-model_mto6v5w6_09-fox-17": {
+ "name": "b_Tail03_014",
+ "nodeName": "b_Tail03_014",
+ "nodePath": "0/0/0/0/0/1/0/0",
+ "interactionId": "interaction-e93e191e",
+ "capabilities": {
+ "selectable": true,
+ "transform": true,
+ "visible": true,
+ "material": false,
+ "animatable": true
+ },
+ "position": [
+ 24.24032211303711,
+ 0,
+ 0
+ ],
+ "rotation": [
+ -5.7486339033124146e-24,
+ 1.1669178332583734e-22,
+ -0.012937892408322462
+ ],
+ "scale": [
+ 1,
+ 1,
+ 1
+ ],
+ "visible": true,
+ "customComponents": []
+ },
+ "model-imported-model_mto6v5w6_09-fox-18": {
+ "name": "b_LeftLeg01_015",
+ "nodeName": "b_LeftLeg01_015",
+ "nodePath": "0/0/0/0/0/2",
+ "interactionId": "interaction-643ea143",
+ "capabilities": {
+ "selectable": true,
+ "transform": true,
+ "visible": true,
+ "material": false,
+ "animatable": true
+ },
+ "position": [
+ 4.813770294189453,
+ 5.154018402099609,
+ -6.968006134033203
+ ],
+ "rotation": [
+ 0.1223493637902729,
+ -0.09724770930295147,
+ 2.9630295165414986
+ ],
+ "scale": [
+ 1,
+ 1,
+ 1
+ ],
+ "visible": true,
+ "customComponents": []
+ },
+ "model-imported-model_mto6v5w6_09-fox-19": {
+ "name": "b_LeftLeg02_016",
+ "nodeName": "b_LeftLeg02_016",
+ "nodePath": "0/0/0/0/0/2/0",
+ "interactionId": "interaction-7175f8dc",
+ "capabilities": {
+ "selectable": true,
+ "transform": true,
+ "visible": true,
+ "material": false,
+ "animatable": true
+ },
+ "position": [
+ 18.944175720214844,
+ 0,
+ 0
+ ],
+ "rotation": [
+ 1.2545483692289454e-8,
+ 3.728483680761731e-8,
+ -0.522293355530293
+ ],
+ "scale": [
+ 1,
+ 1,
+ 1
+ ],
+ "visible": true,
+ "customComponents": []
+ },
+ "model-imported-model_mto6v5w6_09-fox-20": {
+ "name": "b_LeftFoot01_017",
+ "nodeName": "b_LeftFoot01_017",
+ "nodePath": "0/0/0/0/0/2/0/0",
+ "interactionId": "interaction-f5ed353b",
+ "capabilities": {
+ "selectable": true,
+ "transform": true,
+ "visible": true,
+ "material": false,
+ "animatable": true
+ },
+ "position": [
+ 17.942811965942383,
+ 0,
+ 0
+ ],
+ "rotation": [
+ -0.03128474386729028,
+ -0.15471927051895154,
+ 0.7774909055508243
+ ],
+ "scale": [
+ 1,
+ 1,
+ 1
+ ],
+ "visible": true,
+ "customComponents": []
+ },
+ "model-imported-model_mto6v5w6_09-fox-21": {
+ "name": "b_LeftFoot02_018",
+ "nodeName": "b_LeftFoot02_018",
+ "nodePath": "0/0/0/0/0/2/0/0/0",
+ "interactionId": "interaction-1bbe01a4",
+ "capabilities": {
+ "selectable": true,
+ "transform": true,
+ "visible": true,
+ "material": false,
+ "animatable": true
+ },
+ "position": [
+ 15.779938697814941,
+ 0,
+ 0
+ ],
+ "rotation": [
+ 0,
+ 0,
+ 1.158241570830627
+ ],
+ "scale": [
+ 1,
+ 1,
+ 1
+ ],
+ "visible": true,
+ "customComponents": []
+ },
+ "model-imported-model_mto6v5w6_09-fox-22": {
+ "name": "b_RightLeg01_019",
+ "nodeName": "b_RightLeg01_019",
+ "nodePath": "0/0/0/0/0/3",
+ "interactionId": "interaction-633e9fb0",
+ "capabilities": {
+ "selectable": true,
+ "transform": true,
+ "visible": true,
+ "material": false,
+ "animatable": true
+ },
+ "position": [
+ 4.813777923583984,
+ 5.154026031494141,
+ 6.967563629150391
+ ],
+ "rotation": [
+ -0.10137208185544387,
+ 0.05534659522395753,
+ -2.3613690068554236
+ ],
+ "scale": [
+ 1,
+ 1,
+ 1
+ ],
+ "visible": true,
+ "customComponents": []
+ },
+ "model-imported-model_mto6v5w6_09-fox-23": {
+ "name": "b_RightLeg02_020",
+ "nodeName": "b_RightLeg02_020",
+ "nodePath": "0/0/0/0/0/3/0",
+ "interactionId": "interaction-95ff89c7",
+ "capabilities": {
+ "selectable": true,
+ "transform": true,
+ "visible": true,
+ "material": false,
+ "animatable": true
+ },
+ "position": [
+ 18.944183349609375,
+ 0,
+ 0
+ ],
+ "rotation": [
+ 2.7890192431350882e-8,
+ 1.4750479199499402e-8,
+ -1.0710323806538369
+ ],
+ "scale": [
+ 1,
+ 1,
+ 1
+ ],
+ "visible": true,
+ "customComponents": []
+ },
+ "model-imported-model_mto6v5w6_09-fox-24": {
+ "name": "b_RightFoot01_021",
+ "nodeName": "b_RightFoot01_021",
+ "nodePath": "0/0/0/0/0/3/0/0",
+ "interactionId": "interaction-a157d498",
+ "capabilities": {
+ "selectable": true,
+ "transform": true,
+ "visible": true,
+ "material": false,
+ "animatable": true
+ },
+ "position": [
+ 17.94281005859375,
+ 0,
+ 0
+ ],
+ "rotation": [
+ -0.14401413968163027,
+ 0.2814735368347316,
+ 1.4591240233677443
+ ],
+ "scale": [
+ 1,
+ 1,
+ 1
+ ],
+ "visible": true,
+ "customComponents": []
+ },
+ "model-imported-model_mto6v5w6_09-fox-25": {
+ "name": "b_RightFoot02_022",
+ "nodeName": "b_RightFoot02_022",
+ "nodePath": "0/0/0/0/0/3/0/0/0",
+ "interactionId": "interaction-ebd38e3f",
+ "capabilities": {
+ "selectable": true,
+ "transform": true,
+ "visible": true,
+ "material": false,
+ "animatable": true
+ },
+ "position": [
+ 15.779935836791992,
+ 0,
+ 0
+ ],
+ "rotation": [
+ 0,
+ 0,
+ 1.158241570830627
+ ],
+ "scale": [
+ 1,
+ 1,
+ 1
+ ],
+ "visible": true,
+ "customComponents": []
+ },
+ "model-imported-model_mto6v5w6_09-fox-26": {
+ "name": "fox",
+ "nodeName": "fox",
+ "nodePath": "0/1",
+ "interactionId": "interaction-a3d35c26",
+ "capabilities": {
+ "selectable": true,
+ "transform": true,
+ "visible": true,
+ "material": true,
+ "animatable": true
+ },
+ "position": [
+ 0.25,
+ 0,
+ 0
+ ],
+ "rotation": [
+ 0,
+ 0,
+ 0
+ ],
+ "scale": [
+ 1,
+ 1,
+ 1
+ ],
+ "visible": true,
+ "customComponents": [],
+ "materials": [
+ {
+ "color": "ffffff",
+ "emissive": "000000",
+ "emissiveIntensity": 1,
+ "metalness": 0,
+ "roughness": 0.3,
+ "opacity": 1,
+ "transparent": false,
+ "depthWrite": true
+ }
+ ]
+ }
+ },
+ "selectionScope": "part",
+ "nativeAnimation": {
+ "clipIndex": 2,
+ "clipName": "Run"
+ }
+ },
+ "environment": {
+ "mode": "placeholder",
+ "environmentType": "outdoor",
+ "sourceName": "内置野外综合训练环境",
+ "sizeBytes": 0,
+ "assetCode": "",
+ "storageUri": "",
+ "resourceUrl": "",
+ "objects": {
+ "outdoor-scene": {
+ "name": "野外综合训练环境",
+ "position": [
+ 0,
+ 0,
+ 0
+ ],
+ "rotation": [
+ 0,
+ 0,
+ 0
+ ],
+ "scale": [
+ 1,
+ 1,
+ 1
+ ],
+ "visible": true,
+ "customComponents": []
+ },
+ "outdoor-site": {
+ "name": "场地与道路",
+ "position": [
+ 0,
+ 0,
+ 0
+ ],
+ "rotation": [
+ 0,
+ 0,
+ 0
+ ],
+ "scale": [
+ 1,
+ 1,
+ 1
+ ],
+ "visible": true,
+ "customComponents": []
+ },
+ "outdoor-ground": {
+ "name": "室外训练场地",
+ "position": [
+ 0,
+ -0.065,
+ 0
+ ],
+ "rotation": [
+ -1.5707963267948966,
+ 0,
+ 0
+ ],
+ "scale": [
+ 1,
+ 1,
+ 1
+ ],
+ "visible": true,
+ "customComponents": [],
+ "materials": [
+ {
+ "color": "263529",
+ "emissive": "000000",
+ "emissiveIntensity": 1,
+ "metalness": 0,
+ "roughness": 0.98,
+ "opacity": 1,
+ "transparent": false,
+ "depthWrite": true
+ }
+ ]
+ },
+ "outdoor-apron": {
+ "name": "装备操作坪",
+ "position": [
+ 0,
+ -0.045,
+ 0
+ ],
+ "rotation": [
+ -1.5707963267948966,
+ 0,
+ 0
+ ],
+ "scale": [
+ 1,
+ 1,
+ 1
+ ],
+ "visible": true,
+ "customComponents": [],
+ "materials": [
+ {
+ "color": "39444a",
+ "emissive": "000000",
+ "emissiveIntensity": 1,
+ "metalness": 0.03,
+ "roughness": 0.92,
+ "opacity": 1,
+ "transparent": false,
+ "depthWrite": true
+ }
+ ]
+ },
+ "outdoor-road": {
+ "name": "保障通道",
+ "position": [
+ 0,
+ -0.035,
+ 17
+ ],
+ "rotation": [
+ -1.5707963267948966,
+ 0,
+ 0
+ ],
+ "scale": [
+ 1,
+ 1,
+ 1
+ ],
+ "visible": true,
+ "customComponents": [],
+ "materials": [
+ {
+ "color": "202a31",
+ "emissive": "000000",
+ "emissiveIntensity": 1,
+ "metalness": 0.02,
+ "roughness": 0.95,
+ "opacity": 1,
+ "transparent": false,
+ "depthWrite": true
+ }
+ ]
+ },
+ "outdoor-road-line-1": {
+ "name": "道路中心线 1",
+ "position": [
+ -28.699999999999996,
+ 0.01,
+ 17
+ ],
+ "rotation": [
+ 0,
+ 0,
+ 0
+ ],
+ "scale": [
+ 1,
+ 1,
+ 1
+ ],
+ "visible": true,
+ "customComponents": [],
+ "materials": [
+ {
+ "color": "e5c64c",
+ "emissive": "302600",
+ "emissiveIntensity": 0.18,
+ "metalness": 0.08,
+ "roughness": 0.72,
+ "opacity": 1,
+ "transparent": false,
+ "depthWrite": true
+ }
+ ]
+ },
+ "outdoor-road-line-2": {
+ "name": "道路中心线 2",
+ "position": [
+ -24.599999999999998,
+ 0.01,
+ 17
+ ],
+ "rotation": [
+ 0,
+ 0,
+ 0
+ ],
+ "scale": [
+ 1,
+ 1,
+ 1
+ ],
+ "visible": true,
+ "customComponents": [],
+ "materials": [
+ {
+ "color": "e5c64c",
+ "emissive": "302600",
+ "emissiveIntensity": 0.18,
+ "metalness": 0.08,
+ "roughness": 0.72,
+ "opacity": 1,
+ "transparent": false,
+ "depthWrite": true
+ }
+ ]
+ },
+ "outdoor-road-line-3": {
+ "name": "道路中心线 3",
+ "position": [
+ -20.5,
+ 0.01,
+ 17
+ ],
+ "rotation": [
+ 0,
+ 0,
+ 0
+ ],
+ "scale": [
+ 1,
+ 1,
+ 1
+ ],
+ "visible": true,
+ "customComponents": [],
+ "materials": [
+ {
+ "color": "e5c64c",
+ "emissive": "302600",
+ "emissiveIntensity": 0.18,
+ "metalness": 0.08,
+ "roughness": 0.72,
+ "opacity": 1,
+ "transparent": false,
+ "depthWrite": true
+ }
+ ]
+ },
+ "outdoor-road-line-4": {
+ "name": "道路中心线 4",
+ "position": [
+ -16.4,
+ 0.01,
+ 17
+ ],
+ "rotation": [
+ 0,
+ 0,
+ 0
+ ],
+ "scale": [
+ 1,
+ 1,
+ 1
+ ],
+ "visible": true,
+ "customComponents": [],
+ "materials": [
+ {
+ "color": "e5c64c",
+ "emissive": "302600",
+ "emissiveIntensity": 0.18,
+ "metalness": 0.08,
+ "roughness": 0.72,
+ "opacity": 1,
+ "transparent": false,
+ "depthWrite": true
+ }
+ ]
+ },
+ "outdoor-road-line-5": {
+ "name": "道路中心线 5",
+ "position": [
+ -12.299999999999999,
+ 0.01,
+ 17
+ ],
+ "rotation": [
+ 0,
+ 0,
+ 0
+ ],
+ "scale": [
+ 1,
+ 1,
+ 1
+ ],
+ "visible": true,
+ "customComponents": [],
+ "materials": [
+ {
+ "color": "e5c64c",
+ "emissive": "302600",
+ "emissiveIntensity": 0.18,
+ "metalness": 0.08,
+ "roughness": 0.72,
+ "opacity": 1,
+ "transparent": false,
+ "depthWrite": true
+ }
+ ]
+ },
+ "outdoor-road-line-6": {
+ "name": "道路中心线 6",
+ "position": [
+ -8.2,
+ 0.01,
+ 17
+ ],
+ "rotation": [
+ 0,
+ 0,
+ 0
+ ],
+ "scale": [
+ 1,
+ 1,
+ 1
+ ],
+ "visible": true,
+ "customComponents": [],
+ "materials": [
+ {
+ "color": "e5c64c",
+ "emissive": "302600",
+ "emissiveIntensity": 0.18,
+ "metalness": 0.08,
+ "roughness": 0.72,
+ "opacity": 1,
+ "transparent": false,
+ "depthWrite": true
+ }
+ ]
+ },
+ "outdoor-road-line-7": {
+ "name": "道路中心线 7",
+ "position": [
+ -4.1,
+ 0.01,
+ 17
+ ],
+ "rotation": [
+ 0,
+ 0,
+ 0
+ ],
+ "scale": [
+ 1,
+ 1,
+ 1
+ ],
+ "visible": true,
+ "customComponents": [],
+ "materials": [
+ {
+ "color": "e5c64c",
+ "emissive": "302600",
+ "emissiveIntensity": 0.18,
+ "metalness": 0.08,
+ "roughness": 0.72,
+ "opacity": 1,
+ "transparent": false,
+ "depthWrite": true
+ }
+ ]
+ },
+ "outdoor-road-line-8": {
+ "name": "道路中心线 8",
+ "position": [
+ 0,
+ 0.01,
+ 17
+ ],
+ "rotation": [
+ 0,
+ 0,
+ 0
+ ],
+ "scale": [
+ 1,
+ 1,
+ 1
+ ],
+ "visible": true,
+ "customComponents": [],
+ "materials": [
+ {
+ "color": "e5c64c",
+ "emissive": "302600",
+ "emissiveIntensity": 0.18,
+ "metalness": 0.08,
+ "roughness": 0.72,
+ "opacity": 1,
+ "transparent": false,
+ "depthWrite": true
+ }
+ ]
+ },
+ "outdoor-road-line-9": {
+ "name": "道路中心线 9",
+ "position": [
+ 4.1,
+ 0.01,
+ 17
+ ],
+ "rotation": [
+ 0,
+ 0,
+ 0
+ ],
+ "scale": [
+ 1,
+ 1,
+ 1
+ ],
+ "visible": true,
+ "customComponents": [],
+ "materials": [
+ {
+ "color": "e5c64c",
+ "emissive": "302600",
+ "emissiveIntensity": 0.18,
+ "metalness": 0.08,
+ "roughness": 0.72,
+ "opacity": 1,
+ "transparent": false,
+ "depthWrite": true
+ }
+ ]
+ },
+ "outdoor-road-line-10": {
+ "name": "道路中心线 10",
+ "position": [
+ 8.2,
+ 0.01,
+ 17
+ ],
+ "rotation": [
+ 0,
+ 0,
+ 0
+ ],
+ "scale": [
+ 1,
+ 1,
+ 1
+ ],
+ "visible": true,
+ "customComponents": [],
+ "materials": [
+ {
+ "color": "e5c64c",
+ "emissive": "302600",
+ "emissiveIntensity": 0.18,
+ "metalness": 0.08,
+ "roughness": 0.72,
+ "opacity": 1,
+ "transparent": false,
+ "depthWrite": true
+ }
+ ]
+ },
+ "outdoor-road-line-11": {
+ "name": "道路中心线 11",
+ "position": [
+ 12.299999999999999,
+ 0.01,
+ 17
+ ],
+ "rotation": [
+ 0,
+ 0,
+ 0
+ ],
+ "scale": [
+ 1,
+ 1,
+ 1
+ ],
+ "visible": true,
+ "customComponents": [],
+ "materials": [
+ {
+ "color": "e5c64c",
+ "emissive": "302600",
+ "emissiveIntensity": 0.18,
+ "metalness": 0.08,
+ "roughness": 0.72,
+ "opacity": 1,
+ "transparent": false,
+ "depthWrite": true
+ }
+ ]
+ },
+ "outdoor-road-line-12": {
+ "name": "道路中心线 12",
+ "position": [
+ 16.4,
+ 0.01,
+ 17
+ ],
+ "rotation": [
+ 0,
+ 0,
+ 0
+ ],
+ "scale": [
+ 1,
+ 1,
+ 1
+ ],
+ "visible": true,
+ "customComponents": [],
+ "materials": [
+ {
+ "color": "e5c64c",
+ "emissive": "302600",
+ "emissiveIntensity": 0.18,
+ "metalness": 0.08,
+ "roughness": 0.72,
+ "opacity": 1,
+ "transparent": false,
+ "depthWrite": true
+ }
+ ]
+ },
+ "outdoor-road-line-13": {
+ "name": "道路中心线 13",
+ "position": [
+ 20.5,
+ 0.01,
+ 17
+ ],
+ "rotation": [
+ 0,
+ 0,
+ 0
+ ],
+ "scale": [
+ 1,
+ 1,
+ 1
+ ],
+ "visible": true,
+ "customComponents": [],
+ "materials": [
+ {
+ "color": "e5c64c",
+ "emissive": "302600",
+ "emissiveIntensity": 0.18,
+ "metalness": 0.08,
+ "roughness": 0.72,
+ "opacity": 1,
+ "transparent": false,
+ "depthWrite": true
+ }
+ ]
+ },
+ "outdoor-road-line-14": {
+ "name": "道路中心线 14",
+ "position": [
+ 24.599999999999998,
+ 0.01,
+ 17
+ ],
+ "rotation": [
+ 0,
+ 0,
+ 0
+ ],
+ "scale": [
+ 1,
+ 1,
+ 1
+ ],
+ "visible": true,
+ "customComponents": [],
+ "materials": [
+ {
+ "color": "e5c64c",
+ "emissive": "302600",
+ "emissiveIntensity": 0.18,
+ "metalness": 0.08,
+ "roughness": 0.72,
+ "opacity": 1,
+ "transparent": false,
+ "depthWrite": true
+ }
+ ]
+ },
+ "outdoor-road-line-15": {
+ "name": "道路中心线 15",
+ "position": [
+ 28.699999999999996,
+ 0.01,
+ 17
+ ],
+ "rotation": [
+ 0,
+ 0,
+ 0
+ ],
+ "scale": [
+ 1,
+ 1,
+ 1
+ ],
+ "visible": true,
+ "customComponents": [],
+ "materials": [
+ {
+ "color": "e5c64c",
+ "emissive": "302600",
+ "emissiveIntensity": 0.18,
+ "metalness": 0.08,
+ "roughness": 0.72,
+ "opacity": 1,
+ "transparent": false,
+ "depthWrite": true
+ }
+ ]
+ },
+ "outdoor-safety--17.6--11": {
+ "name": "操作区安全标线",
+ "position": [
+ -17.6,
+ 0.012,
+ -11
+ ],
+ "rotation": [
+ 0,
+ 0.5654866776461628,
+ 0
+ ],
+ "scale": [
+ 1,
+ 1,
+ 1
+ ],
+ "visible": true,
+ "customComponents": [],
+ "materials": [
+ {
+ "color": "e5c64c",
+ "emissive": "302600",
+ "emissiveIntensity": 0.18,
+ "metalness": 0.08,
+ "roughness": 0.72,
+ "opacity": 1,
+ "transparent": false,
+ "depthWrite": true
+ }
+ ]
+ },
+ "outdoor-safety--17.6--8.4": {
+ "name": "操作区安全标线",
+ "position": [
+ -17.6,
+ 0.012,
+ -8.4
+ ],
+ "rotation": [
+ 0,
+ 0.5654866776461628,
+ 0
+ ],
+ "scale": [
+ 1,
+ 1,
+ 1
+ ],
+ "visible": true,
+ "customComponents": [],
+ "materials": [
+ {
+ "color": "e5c64c",
+ "emissive": "302600",
+ "emissiveIntensity": 0.18,
+ "metalness": 0.08,
+ "roughness": 0.72,
+ "opacity": 1,
+ "transparent": false,
+ "depthWrite": true
+ }
+ ]
+ },
+ "outdoor-safety--17.6--5.800000000000001": {
+ "name": "操作区安全标线",
+ "position": [
+ -17.6,
+ 0.012,
+ -5.800000000000001
+ ],
+ "rotation": [
+ 0,
+ 0.5654866776461628,
+ 0
+ ],
+ "scale": [
+ 1,
+ 1,
+ 1
+ ],
+ "visible": true,
+ "customComponents": [],
+ "materials": [
+ {
+ "color": "e5c64c",
+ "emissive": "302600",
+ "emissiveIntensity": 0.18,
+ "metalness": 0.08,
+ "roughness": 0.72,
+ "opacity": 1,
+ "transparent": false,
+ "depthWrite": true
+ }
+ ]
+ },
+ "outdoor-safety--17.6--3.2000000000000006": {
+ "name": "操作区安全标线",
+ "position": [
+ -17.6,
+ 0.012,
+ -3.2000000000000006
+ ],
+ "rotation": [
+ 0,
+ 0.5654866776461628,
+ 0
+ ],
+ "scale": [
+ 1,
+ 1,
+ 1
+ ],
+ "visible": true,
+ "customComponents": [],
+ "materials": [
+ {
+ "color": "e5c64c",
+ "emissive": "302600",
+ "emissiveIntensity": 0.18,
+ "metalness": 0.08,
+ "roughness": 0.72,
+ "opacity": 1,
+ "transparent": false,
+ "depthWrite": true
+ }
+ ]
+ },
+ "outdoor-safety--17.6--0.6000000000000005": {
+ "name": "操作区安全标线",
+ "position": [
+ -17.6,
+ 0.012,
+ -0.6000000000000005
+ ],
+ "rotation": [
+ 0,
+ 0.5654866776461628,
+ 0
+ ],
+ "scale": [
+ 1,
+ 1,
+ 1
+ ],
+ "visible": true,
+ "customComponents": [],
+ "materials": [
+ {
+ "color": "e5c64c",
+ "emissive": "302600",
+ "emissiveIntensity": 0.18,
+ "metalness": 0.08,
+ "roughness": 0.72,
+ "opacity": 1,
+ "transparent": false,
+ "depthWrite": true
+ }
+ ]
+ },
+ "outdoor-safety--17.6-1.9999999999999996": {
+ "name": "操作区安全标线",
+ "position": [
+ -17.6,
+ 0.012,
+ 1.9999999999999996
+ ],
+ "rotation": [
+ 0,
+ 0.5654866776461628,
+ 0
+ ],
+ "scale": [
+ 1,
+ 1,
+ 1
+ ],
+ "visible": true,
+ "customComponents": [],
+ "materials": [
+ {
+ "color": "e5c64c",
+ "emissive": "302600",
+ "emissiveIntensity": 0.18,
+ "metalness": 0.08,
+ "roughness": 0.72,
+ "opacity": 1,
+ "transparent": false,
+ "depthWrite": true
+ }
+ ]
+ },
+ "outdoor-safety--17.6-4.6": {
+ "name": "操作区安全标线",
+ "position": [
+ -17.6,
+ 0.012,
+ 4.6
+ ],
+ "rotation": [
+ 0,
+ 0.5654866776461628,
+ 0
+ ],
+ "scale": [
+ 1,
+ 1,
+ 1
+ ],
+ "visible": true,
+ "customComponents": [],
+ "materials": [
+ {
+ "color": "e5c64c",
+ "emissive": "302600",
+ "emissiveIntensity": 0.18,
+ "metalness": 0.08,
+ "roughness": 0.72,
+ "opacity": 1,
+ "transparent": false,
+ "depthWrite": true
+ }
+ ]
+ },
+ "outdoor-safety--17.6-7.199999999999999": {
+ "name": "操作区安全标线",
+ "position": [
+ -17.6,
+ 0.012,
+ 7.199999999999999
+ ],
+ "rotation": [
+ 0,
+ 0.5654866776461628,
+ 0
+ ],
+ "scale": [
+ 1,
+ 1,
+ 1
+ ],
+ "visible": true,
+ "customComponents": [],
+ "materials": [
+ {
+ "color": "e5c64c",
+ "emissive": "302600",
+ "emissiveIntensity": 0.18,
+ "metalness": 0.08,
+ "roughness": 0.72,
+ "opacity": 1,
+ "transparent": false,
+ "depthWrite": true
+ }
+ ]
+ },
+ "outdoor-safety--17.6-9.799999999999999": {
+ "name": "操作区安全标线",
+ "position": [
+ -17.6,
+ 0.012,
+ 9.799999999999999
+ ],
+ "rotation": [
+ 0,
+ 0.5654866776461628,
+ 0
+ ],
+ "scale": [
+ 1,
+ 1,
+ 1
+ ],
+ "visible": true,
+ "customComponents": [],
+ "materials": [
+ {
+ "color": "e5c64c",
+ "emissive": "302600",
+ "emissiveIntensity": 0.18,
+ "metalness": 0.08,
+ "roughness": 0.72,
+ "opacity": 1,
+ "transparent": false,
+ "depthWrite": true
+ }
+ ]
+ },
+ "outdoor-safety-17.6--11": {
+ "name": "操作区安全标线",
+ "position": [
+ 17.6,
+ 0.012,
+ -11
+ ],
+ "rotation": [
+ 0,
+ 0.5654866776461628,
+ 0
+ ],
+ "scale": [
+ 1,
+ 1,
+ 1
+ ],
+ "visible": true,
+ "customComponents": [],
+ "materials": [
+ {
+ "color": "e5c64c",
+ "emissive": "302600",
+ "emissiveIntensity": 0.18,
+ "metalness": 0.08,
+ "roughness": 0.72,
+ "opacity": 1,
+ "transparent": false,
+ "depthWrite": true
+ }
+ ]
+ },
+ "outdoor-safety-17.6--8.4": {
+ "name": "操作区安全标线",
+ "position": [
+ 17.6,
+ 0.012,
+ -8.4
+ ],
+ "rotation": [
+ 0,
+ 0.5654866776461628,
+ 0
+ ],
+ "scale": [
+ 1,
+ 1,
+ 1
+ ],
+ "visible": true,
+ "customComponents": [],
+ "materials": [
+ {
+ "color": "e5c64c",
+ "emissive": "302600",
+ "emissiveIntensity": 0.18,
+ "metalness": 0.08,
+ "roughness": 0.72,
+ "opacity": 1,
+ "transparent": false,
+ "depthWrite": true
+ }
+ ]
+ },
+ "outdoor-safety-17.6--5.800000000000001": {
+ "name": "操作区安全标线",
+ "position": [
+ 17.6,
+ 0.012,
+ -5.800000000000001
+ ],
+ "rotation": [
+ 0,
+ 0.5654866776461628,
+ 0
+ ],
+ "scale": [
+ 1,
+ 1,
+ 1
+ ],
+ "visible": true,
+ "customComponents": [],
+ "materials": [
+ {
+ "color": "e5c64c",
+ "emissive": "302600",
+ "emissiveIntensity": 0.18,
+ "metalness": 0.08,
+ "roughness": 0.72,
+ "opacity": 1,
+ "transparent": false,
+ "depthWrite": true
+ }
+ ]
+ },
+ "outdoor-safety-17.6--3.2000000000000006": {
+ "name": "操作区安全标线",
+ "position": [
+ 17.6,
+ 0.012,
+ -3.2000000000000006
+ ],
+ "rotation": [
+ 0,
+ 0.5654866776461628,
+ 0
+ ],
+ "scale": [
+ 1,
+ 1,
+ 1
+ ],
+ "visible": true,
+ "customComponents": [],
+ "materials": [
+ {
+ "color": "e5c64c",
+ "emissive": "302600",
+ "emissiveIntensity": 0.18,
+ "metalness": 0.08,
+ "roughness": 0.72,
+ "opacity": 1,
+ "transparent": false,
+ "depthWrite": true
+ }
+ ]
+ },
+ "outdoor-safety-17.6--0.6000000000000005": {
+ "name": "操作区安全标线",
+ "position": [
+ 17.6,
+ 0.012,
+ -0.6000000000000005
+ ],
+ "rotation": [
+ 0,
+ 0.5654866776461628,
+ 0
+ ],
+ "scale": [
+ 1,
+ 1,
+ 1
+ ],
+ "visible": true,
+ "customComponents": [],
+ "materials": [
+ {
+ "color": "e5c64c",
+ "emissive": "302600",
+ "emissiveIntensity": 0.18,
+ "metalness": 0.08,
+ "roughness": 0.72,
+ "opacity": 1,
+ "transparent": false,
+ "depthWrite": true
+ }
+ ]
+ },
+ "outdoor-safety-17.6-1.9999999999999996": {
+ "name": "操作区安全标线",
+ "position": [
+ 17.6,
+ 0.012,
+ 1.9999999999999996
+ ],
+ "rotation": [
+ 0,
+ 0.5654866776461628,
+ 0
+ ],
+ "scale": [
+ 1,
+ 1,
+ 1
+ ],
+ "visible": true,
+ "customComponents": [],
+ "materials": [
+ {
+ "color": "e5c64c",
+ "emissive": "302600",
+ "emissiveIntensity": 0.18,
+ "metalness": 0.08,
+ "roughness": 0.72,
+ "opacity": 1,
+ "transparent": false,
+ "depthWrite": true
+ }
+ ]
+ },
+ "outdoor-safety-17.6-4.6": {
+ "name": "操作区安全标线",
+ "position": [
+ 17.6,
+ 0.012,
+ 4.6
+ ],
+ "rotation": [
+ 0,
+ 0.5654866776461628,
+ 0
+ ],
+ "scale": [
+ 1,
+ 1,
+ 1
+ ],
+ "visible": true,
+ "customComponents": [],
+ "materials": [
+ {
+ "color": "e5c64c",
+ "emissive": "302600",
+ "emissiveIntensity": 0.18,
+ "metalness": 0.08,
+ "roughness": 0.72,
+ "opacity": 1,
+ "transparent": false,
+ "depthWrite": true
+ }
+ ]
+ },
+ "outdoor-safety-17.6-7.199999999999999": {
+ "name": "操作区安全标线",
+ "position": [
+ 17.6,
+ 0.012,
+ 7.199999999999999
+ ],
+ "rotation": [
+ 0,
+ 0.5654866776461628,
+ 0
+ ],
+ "scale": [
+ 1,
+ 1,
+ 1
+ ],
+ "visible": true,
+ "customComponents": [],
+ "materials": [
+ {
+ "color": "e5c64c",
+ "emissive": "302600",
+ "emissiveIntensity": 0.18,
+ "metalness": 0.08,
+ "roughness": 0.72,
+ "opacity": 1,
+ "transparent": false,
+ "depthWrite": true
+ }
+ ]
+ },
+ "outdoor-safety-17.6-9.799999999999999": {
+ "name": "操作区安全标线",
+ "position": [
+ 17.6,
+ 0.012,
+ 9.799999999999999
+ ],
+ "rotation": [
+ 0,
+ 0.5654866776461628,
+ 0
+ ],
+ "scale": [
+ 1,
+ 1,
+ 1
+ ],
+ "visible": true,
+ "customComponents": [],
+ "materials": [
+ {
+ "color": "e5c64c",
+ "emissive": "302600",
+ "emissiveIntensity": 0.18,
+ "metalness": 0.08,
+ "roughness": 0.72,
+ "opacity": 1,
+ "transparent": false,
+ "depthWrite": true
+ }
+ ]
+ },
+ "outdoor-workshop": {
+ "name": "维修保障厂房",
+ "position": [
+ 0,
+ 0,
+ 0
+ ],
+ "rotation": [
+ 0,
+ 0,
+ 0
+ ],
+ "scale": [
+ 1,
+ 1,
+ 1
+ ],
+ "visible": true,
+ "customComponents": []
+ },
+ "outdoor-workshop-body": {
+ "name": "维修保障厂房主体",
+ "position": [
+ -10,
+ 3.25,
+ -18
+ ],
+ "rotation": [
+ 0,
+ 0,
+ 0
+ ],
+ "scale": [
+ 1,
+ 1,
+ 1
+ ],
+ "visible": true,
+ "customComponents": [],
+ "materials": [
+ {
+ "color": "536675",
+ "emissive": "000000",
+ "emissiveIntensity": 1,
+ "metalness": 0.18,
+ "roughness": 0.78,
+ "opacity": 1,
+ "transparent": false,
+ "depthWrite": true
+ }
+ ]
+ },
+ "outdoor-workshop-roof": {
+ "name": "厂房金属屋面",
+ "position": [
+ -10,
+ 6.65,
+ -18
+ ],
+ "rotation": [
+ 0,
+ 0,
+ 0
+ ],
+ "scale": [
+ 1,
+ 1,
+ 1
+ ],
+ "visible": true,
+ "customComponents": [],
+ "materials": [
+ {
+ "color": "263846",
+ "emissive": "000000",
+ "emissiveIntensity": 1,
+ "metalness": 0.35,
+ "roughness": 0.62,
+ "opacity": 1,
+ "transparent": false,
+ "depthWrite": true
+ }
+ ]
+ },
+ "outdoor-workshop-window-1": {
+ "name": "厂房观察窗 1",
+ "position": [
+ -17,
+ 3.7,
+ -14.21
+ ],
+ "rotation": [
+ 0,
+ 0,
+ 0
+ ],
+ "scale": [
+ 1,
+ 1,
+ 1
+ ],
+ "visible": true,
+ "customComponents": [],
+ "materials": [
+ {
+ "color": "4a89a2",
+ "emissive": "000000",
+ "emissiveIntensity": 1,
+ "metalness": 0.12,
+ "roughness": 0.25,
+ "opacity": 0.68,
+ "transparent": true,
+ "depthWrite": true
+ }
+ ]
+ },
+ "outdoor-workshop-window-2": {
+ "name": "厂房观察窗 2",
+ "position": [
+ -12.4,
+ 3.7,
+ -14.21
+ ],
+ "rotation": [
+ 0,
+ 0,
+ 0
+ ],
+ "scale": [
+ 1,
+ 1,
+ 1
+ ],
+ "visible": true,
+ "customComponents": [],
+ "materials": [
+ {
+ "color": "4a89a2",
+ "emissive": "000000",
+ "emissiveIntensity": 1,
+ "metalness": 0.12,
+ "roughness": 0.25,
+ "opacity": 0.68,
+ "transparent": true,
+ "depthWrite": true
+ }
+ ]
+ },
+ "outdoor-workshop-window-3": {
+ "name": "厂房观察窗 3",
+ "position": [
+ -7.800000000000001,
+ 3.7,
+ -14.21
+ ],
+ "rotation": [
+ 0,
+ 0,
+ 0
+ ],
+ "scale": [
+ 1,
+ 1,
+ 1
+ ],
+ "visible": true,
+ "customComponents": [],
+ "materials": [
+ {
+ "color": "4a89a2",
+ "emissive": "000000",
+ "emissiveIntensity": 1,
+ "metalness": 0.12,
+ "roughness": 0.25,
+ "opacity": 0.68,
+ "transparent": true,
+ "depthWrite": true
+ }
+ ]
+ },
+ "outdoor-workshop-window-4": {
+ "name": "厂房观察窗 4",
+ "position": [
+ -3.200000000000001,
+ 3.7,
+ -14.21
+ ],
+ "rotation": [
+ 0,
+ 0,
+ 0
+ ],
+ "scale": [
+ 1,
+ 1,
+ 1
+ ],
+ "visible": true,
+ "customComponents": [],
+ "materials": [
+ {
+ "color": "4a89a2",
+ "emissive": "000000",
+ "emissiveIntensity": 1,
+ "metalness": 0.12,
+ "roughness": 0.25,
+ "opacity": 0.68,
+ "transparent": true,
+ "depthWrite": true
+ }
+ ]
+ },
+ "outdoor-workshop-door": {
+ "name": "装备出入门",
+ "position": [
+ -3.6,
+ 2.16,
+ -14.18
+ ],
+ "rotation": [
+ 0,
+ 0,
+ 0
+ ],
+ "scale": [
+ 1,
+ 1,
+ 1
+ ],
+ "visible": true,
+ "customComponents": [],
+ "materials": [
+ {
+ "color": "202a31",
+ "emissive": "000000",
+ "emissiveIntensity": 1,
+ "metalness": 0.02,
+ "roughness": 0.95,
+ "opacity": 1,
+ "transparent": false,
+ "depthWrite": true
+ }
+ ]
+ },
+ "outdoor-canopy": {
+ "name": "室外检修棚",
+ "position": [
+ 0,
+ 0,
+ 0
+ ],
+ "rotation": [
+ 0,
+ 0,
+ 0
+ ],
+ "scale": [
+ 1,
+ 1,
+ 1
+ ],
+ "visible": true,
+ "customComponents": []
+ },
+ "outdoor-canopy-roof": {
+ "name": "检修棚顶",
+ "position": [
+ 18,
+ 5.05,
+ -8
+ ],
+ "rotation": [
+ 0,
+ 0,
+ 0
+ ],
+ "scale": [
+ 1,
+ 1,
+ 1
+ ],
+ "visible": true,
+ "customComponents": [],
+ "materials": [
+ {
+ "color": "263846",
+ "emissive": "000000",
+ "emissiveIntensity": 1,
+ "metalness": 0.35,
+ "roughness": 0.62,
+ "opacity": 1,
+ "transparent": false,
+ "depthWrite": true
+ }
+ ]
+ },
+ "outdoor-canopy-column-1": {
+ "name": "检修棚立柱 1",
+ "position": [
+ 12.7,
+ 2.5,
+ -11
+ ],
+ "rotation": [
+ 0,
+ 0,
+ 0
+ ],
+ "scale": [
+ 1,
+ 1,
+ 1
+ ],
+ "visible": true,
+ "customComponents": [],
+ "materials": [
+ {
+ "color": "66757c",
+ "emissive": "000000",
+ "emissiveIntensity": 1,
+ "metalness": 0.48,
+ "roughness": 0.56,
+ "opacity": 1,
+ "transparent": false,
+ "depthWrite": true
+ }
+ ]
+ },
+ "outdoor-canopy-column-2": {
+ "name": "检修棚立柱 2",
+ "position": [
+ 23.3,
+ 2.5,
+ -11
+ ],
+ "rotation": [
+ 0,
+ 0,
+ 0
+ ],
+ "scale": [
+ 1,
+ 1,
+ 1
+ ],
+ "visible": true,
+ "customComponents": [],
+ "materials": [
+ {
+ "color": "66757c",
+ "emissive": "000000",
+ "emissiveIntensity": 1,
+ "metalness": 0.48,
+ "roughness": 0.56,
+ "opacity": 1,
+ "transparent": false,
+ "depthWrite": true
+ }
+ ]
+ },
+ "outdoor-canopy-column-3": {
+ "name": "检修棚立柱 3",
+ "position": [
+ 12.7,
+ 2.5,
+ -5
+ ],
+ "rotation": [
+ 0,
+ 0,
+ 0
+ ],
+ "scale": [
+ 1,
+ 1,
+ 1
+ ],
+ "visible": true,
+ "customComponents": [],
+ "materials": [
+ {
+ "color": "66757c",
+ "emissive": "000000",
+ "emissiveIntensity": 1,
+ "metalness": 0.48,
+ "roughness": 0.56,
+ "opacity": 1,
+ "transparent": false,
+ "depthWrite": true
+ }
+ ]
+ },
+ "outdoor-canopy-column-4": {
+ "name": "检修棚立柱 4",
+ "position": [
+ 23.3,
+ 2.5,
+ -5
+ ],
+ "rotation": [
+ 0,
+ 0,
+ 0
+ ],
+ "scale": [
+ 1,
+ 1,
+ 1
+ ],
+ "visible": true,
+ "customComponents": [],
+ "materials": [
+ {
+ "color": "66757c",
+ "emissive": "000000",
+ "emissiveIntensity": 1,
+ "metalness": 0.48,
+ "roughness": 0.56,
+ "opacity": 1,
+ "transparent": false,
+ "depthWrite": true
+ }
+ ]
+ },
+ "outdoor-service-box": {
+ "name": "室外工具柜",
+ "position": [
+ 22.2,
+ 1.05,
+ -9.8
+ ],
+ "rotation": [
+ 0,
+ 0,
+ 0
+ ],
+ "scale": [
+ 1,
+ 1,
+ 1
+ ],
+ "visible": true,
+ "customComponents": [],
+ "materials": [
+ {
+ "color": "d98d2b",
+ "emissive": "000000",
+ "emissiveIntensity": 1,
+ "metalness": 0.12,
+ "roughness": 0.7,
+ "opacity": 1,
+ "transparent": false,
+ "depthWrite": true
+ }
+ ]
+ },
+ "outdoor-perimeter": {
+ "name": "场地边界与安全设施",
+ "position": [
+ 0,
+ 0,
+ 0
+ ],
+ "rotation": [
+ 0,
+ 0,
+ 0
+ ],
+ "scale": [
+ 1,
+ 1,
+ 1
+ ],
+ "visible": true,
+ "customComponents": []
+ },
+ "outdoor-fence-back-1": {
+ "name": "后侧围栏立柱 1",
+ "position": [
+ -29,
+ 1.1,
+ -25
+ ],
+ "rotation": [
+ 0,
+ 0,
+ 0
+ ],
+ "scale": [
+ 1,
+ 1,
+ 1
+ ],
+ "visible": true,
+ "customComponents": [],
+ "materials": [
+ {
+ "color": "66757c",
+ "emissive": "000000",
+ "emissiveIntensity": 1,
+ "metalness": 0.48,
+ "roughness": 0.56,
+ "opacity": 1,
+ "transparent": false,
+ "depthWrite": true
+ }
+ ]
+ },
+ "outdoor-fence-back-2": {
+ "name": "后侧围栏立柱 2",
+ "position": [
+ -23.2,
+ 1.1,
+ -25
+ ],
+ "rotation": [
+ 0,
+ 0,
+ 0
+ ],
+ "scale": [
+ 1,
+ 1,
+ 1
+ ],
+ "visible": true,
+ "customComponents": [],
+ "materials": [
+ {
+ "color": "66757c",
+ "emissive": "000000",
+ "emissiveIntensity": 1,
+ "metalness": 0.48,
+ "roughness": 0.56,
+ "opacity": 1,
+ "transparent": false,
+ "depthWrite": true
+ }
+ ]
+ },
+ "outdoor-fence-back-3": {
+ "name": "后侧围栏立柱 3",
+ "position": [
+ -17.4,
+ 1.1,
+ -25
+ ],
+ "rotation": [
+ 0,
+ 0,
+ 0
+ ],
+ "scale": [
+ 1,
+ 1,
+ 1
+ ],
+ "visible": true,
+ "customComponents": [],
+ "materials": [
+ {
+ "color": "66757c",
+ "emissive": "000000",
+ "emissiveIntensity": 1,
+ "metalness": 0.48,
+ "roughness": 0.56,
+ "opacity": 1,
+ "transparent": false,
+ "depthWrite": true
+ }
+ ]
+ },
+ "outdoor-fence-back-4": {
+ "name": "后侧围栏立柱 4",
+ "position": [
+ -11.6,
+ 1.1,
+ -25
+ ],
+ "rotation": [
+ 0,
+ 0,
+ 0
+ ],
+ "scale": [
+ 1,
+ 1,
+ 1
+ ],
+ "visible": true,
+ "customComponents": [],
+ "materials": [
+ {
+ "color": "66757c",
+ "emissive": "000000",
+ "emissiveIntensity": 1,
+ "metalness": 0.48,
+ "roughness": 0.56,
+ "opacity": 1,
+ "transparent": false,
+ "depthWrite": true
+ }
+ ]
+ },
+ "outdoor-fence-back-5": {
+ "name": "后侧围栏立柱 5",
+ "position": [
+ -5.8,
+ 1.1,
+ -25
+ ],
+ "rotation": [
+ 0,
+ 0,
+ 0
+ ],
+ "scale": [
+ 1,
+ 1,
+ 1
+ ],
+ "visible": true,
+ "customComponents": [],
+ "materials": [
+ {
+ "color": "66757c",
+ "emissive": "000000",
+ "emissiveIntensity": 1,
+ "metalness": 0.48,
+ "roughness": 0.56,
+ "opacity": 1,
+ "transparent": false,
+ "depthWrite": true
+ }
+ ]
+ },
+ "outdoor-fence-back-6": {
+ "name": "后侧围栏立柱 6",
+ "position": [
+ 0,
+ 1.1,
+ -25
+ ],
+ "rotation": [
+ 0,
+ 0,
+ 0
+ ],
+ "scale": [
+ 1,
+ 1,
+ 1
+ ],
+ "visible": true,
+ "customComponents": [],
+ "materials": [
+ {
+ "color": "66757c",
+ "emissive": "000000",
+ "emissiveIntensity": 1,
+ "metalness": 0.48,
+ "roughness": 0.56,
+ "opacity": 1,
+ "transparent": false,
+ "depthWrite": true
+ }
+ ]
+ },
+ "outdoor-fence-back-7": {
+ "name": "后侧围栏立柱 7",
+ "position": [
+ 5.8,
+ 1.1,
+ -25
+ ],
+ "rotation": [
+ 0,
+ 0,
+ 0
+ ],
+ "scale": [
+ 1,
+ 1,
+ 1
+ ],
+ "visible": true,
+ "customComponents": [],
+ "materials": [
+ {
+ "color": "66757c",
+ "emissive": "000000",
+ "emissiveIntensity": 1,
+ "metalness": 0.48,
+ "roughness": 0.56,
+ "opacity": 1,
+ "transparent": false,
+ "depthWrite": true
+ }
+ ]
+ },
+ "outdoor-fence-back-8": {
+ "name": "后侧围栏立柱 8",
+ "position": [
+ 11.6,
+ 1.1,
+ -25
+ ],
+ "rotation": [
+ 0,
+ 0,
+ 0
+ ],
+ "scale": [
+ 1,
+ 1,
+ 1
+ ],
+ "visible": true,
+ "customComponents": [],
+ "materials": [
+ {
+ "color": "66757c",
+ "emissive": "000000",
+ "emissiveIntensity": 1,
+ "metalness": 0.48,
+ "roughness": 0.56,
+ "opacity": 1,
+ "transparent": false,
+ "depthWrite": true
+ }
+ ]
+ },
+ "outdoor-fence-back-9": {
+ "name": "后侧围栏立柱 9",
+ "position": [
+ 17.4,
+ 1.1,
+ -25
+ ],
+ "rotation": [
+ 0,
+ 0,
+ 0
+ ],
+ "scale": [
+ 1,
+ 1,
+ 1
+ ],
+ "visible": true,
+ "customComponents": [],
+ "materials": [
+ {
+ "color": "66757c",
+ "emissive": "000000",
+ "emissiveIntensity": 1,
+ "metalness": 0.48,
+ "roughness": 0.56,
+ "opacity": 1,
+ "transparent": false,
+ "depthWrite": true
+ }
+ ]
+ },
+ "outdoor-fence-back-10": {
+ "name": "后侧围栏立柱 10",
+ "position": [
+ 23.2,
+ 1.1,
+ -25
+ ],
+ "rotation": [
+ 0,
+ 0,
+ 0
+ ],
+ "scale": [
+ 1,
+ 1,
+ 1
+ ],
+ "visible": true,
+ "customComponents": [],
+ "materials": [
+ {
+ "color": "66757c",
+ "emissive": "000000",
+ "emissiveIntensity": 1,
+ "metalness": 0.48,
+ "roughness": 0.56,
+ "opacity": 1,
+ "transparent": false,
+ "depthWrite": true
+ }
+ ]
+ },
+ "outdoor-fence-back-11": {
+ "name": "后侧围栏立柱 11",
+ "position": [
+ 29,
+ 1.1,
+ -25
+ ],
+ "rotation": [
+ 0,
+ 0,
+ 0
+ ],
+ "scale": [
+ 1,
+ 1,
+ 1
+ ],
+ "visible": true,
+ "customComponents": [],
+ "materials": [
+ {
+ "color": "66757c",
+ "emissive": "000000",
+ "emissiveIntensity": 1,
+ "metalness": 0.48,
+ "roughness": 0.56,
+ "opacity": 1,
+ "transparent": false,
+ "depthWrite": true
+ }
+ ]
+ },
+ "outdoor-fence-rail-a": {
+ "name": "后侧围栏上横杆",
+ "position": [
+ 0,
+ 1.85,
+ -25
+ ],
+ "rotation": [
+ 0,
+ 0,
+ 0
+ ],
+ "scale": [
+ 1,
+ 1,
+ 1
+ ],
+ "visible": true,
+ "customComponents": [],
+ "materials": [
+ {
+ "color": "66757c",
+ "emissive": "000000",
+ "emissiveIntensity": 1,
+ "metalness": 0.48,
+ "roughness": 0.56,
+ "opacity": 1,
+ "transparent": false,
+ "depthWrite": true
+ }
+ ]
+ },
+ "outdoor-fence-rail-b": {
+ "name": "后侧围栏下横杆",
+ "position": [
+ 0,
+ 0.75,
+ -25
+ ],
+ "rotation": [
+ 0,
+ 0,
+ 0
+ ],
+ "scale": [
+ 1,
+ 1,
+ 1
+ ],
+ "visible": true,
+ "customComponents": [],
+ "materials": [
+ {
+ "color": "66757c",
+ "emissive": "000000",
+ "emissiveIntensity": 1,
+ "metalness": 0.48,
+ "roughness": 0.56,
+ "opacity": 1,
+ "transparent": false,
+ "depthWrite": true
+ }
+ ]
+ },
+ "outdoor-scene-sign": {
+ "name": "室外实训区域标识",
+ "position": [
+ 26,
+ 2.4,
+ 11.5
+ ],
+ "rotation": [
+ 0,
+ 0,
+ 0
+ ],
+ "scale": [
+ 1,
+ 1,
+ 1
+ ],
+ "visible": true,
+ "customComponents": [],
+ "materials": [
+ {
+ "color": "536675",
+ "emissive": "000000",
+ "emissiveIntensity": 1,
+ "metalness": 0.18,
+ "roughness": 0.78,
+ "opacity": 1,
+ "transparent": false,
+ "depthWrite": true
+ }
+ ]
+ },
+ "outdoor-scene-sign-post-a": {
+ "name": "区域标识支柱 A",
+ "position": [
+ 24,
+ 1.4,
+ 11.5
+ ],
+ "rotation": [
+ 0,
+ 0,
+ 0
+ ],
+ "scale": [
+ 1,
+ 1,
+ 1
+ ],
+ "visible": true,
+ "customComponents": [],
+ "materials": [
+ {
+ "color": "66757c",
+ "emissive": "000000",
+ "emissiveIntensity": 1,
+ "metalness": 0.48,
+ "roughness": 0.56,
+ "opacity": 1,
+ "transparent": false,
+ "depthWrite": true
+ }
+ ]
+ },
+ "outdoor-scene-sign-post-b": {
+ "name": "区域标识支柱 B",
+ "position": [
+ 28,
+ 1.4,
+ 11.5
+ ],
+ "rotation": [
+ 0,
+ 0,
+ 0
+ ],
+ "scale": [
+ 1,
+ 1,
+ 1
+ ],
+ "visible": true,
+ "customComponents": [],
+ "materials": [
+ {
+ "color": "66757c",
+ "emissive": "000000",
+ "emissiveIntensity": 1,
+ "metalness": 0.48,
+ "roughness": 0.56,
+ "opacity": 1,
+ "transparent": false,
+ "depthWrite": true
+ }
+ ]
+ }
+ }
+ },
+ "runtime": {
+ "startMode": "guided",
+ "showStepPanel": true,
+ "showProgress": true,
+ "allowReplay": true,
+ "language": "zh-CN"
+ },
+ "publish": {
+ "target": "standalone-html",
+ "deployment": "offline",
+ "includeProjectData": true,
+ "includeViewportPreview": true
+ }
+}
\ No newline at end of file
diff --git a/reports/model-editor-closure-20260906/downloads/preflight-225.json b/reports/model-editor-closure-20260906/downloads/preflight-225.json
new file mode 100644
index 0000000..9129ca8
--- /dev/null
+++ b/reports/model-editor-closure-20260906/downloads/preflight-225.json
@@ -0,0 +1,3414 @@
+{
+ "schema": "digital-twin-editor-project",
+ "version": 4,
+ "name": "闭环验收-加载与预检-mtpjgfnd",
+ "modelId": "imported-model_mto6v5w6_09-fox",
+ "modelResource": {
+ "kind": "imported",
+ "id": "imported-model_mto6v5w6_09-fox",
+ "name": "09-Fox",
+ "assetCode": "MODEL_MTO6V5W6_09-FOX",
+ "code": "MODEL_MTO6V5W6_09-FOX",
+ "storageUri": "fsvc://unreal-tran/model/2026/09/05/3fc69ae0d435f869afca1fcd943486d1.glb",
+ "assetKey": "",
+ "resourceUrl": "",
+ "sourceName": "09-Fox.glb",
+ "sizeBytes": 162852,
+ "lastModified": 0,
+ "mimeType": "model/gltf-binary",
+ "thumbnail": "",
+ "vertices": 1728,
+ "triangles": 576,
+ "nodes": 27,
+ "meshes": 1,
+ "materials": 1,
+ "textures": 1,
+ "animations": 3,
+ "animationDurationSeconds": 3.4166667461395264,
+ "modelMetadata": {
+ "nodeCount": 27,
+ "meshCount": 1,
+ "materialCount": 1,
+ "textureCount": 1,
+ "animations": [
+ {
+ "index": 0,
+ "name": "Survey",
+ "start": 0,
+ "end": 3.4166667461395264,
+ "duration": 3.4166667461395264,
+ "trackCount": 21
+ },
+ {
+ "index": 1,
+ "name": "Walk",
+ "start": 0,
+ "end": 0.7083333134651184,
+ "duration": 0.7083333134651184,
+ "trackCount": 21
+ },
+ {
+ "index": 2,
+ "name": "Run",
+ "start": 0,
+ "end": 1.1583333015441895,
+ "duration": 1.1583333015441895,
+ "trackCount": 21
+ }
+ ],
+ "animationDurationSeconds": 3.4166667461395264
+ },
+ "nativeAnimations": [
+ {
+ "index": 0,
+ "name": "Survey",
+ "start": 0,
+ "end": 3.4166667461395264,
+ "duration": 3.4166667461395264,
+ "trackCount": 21
+ },
+ {
+ "index": 1,
+ "name": "Walk",
+ "start": 0,
+ "end": 0.7083333134651184,
+ "duration": 0.7083333134651184,
+ "trackCount": 21
+ },
+ {
+ "index": 2,
+ "name": "Run",
+ "start": 0,
+ "end": 1.1583333015441895,
+ "duration": 1.1583333015441895,
+ "trackCount": 21
+ }
+ ]
+ },
+ "meta": {
+ "description": "用于装备维修实训场景编排、编辑器回归与离线发布验证的系统内置基准工程",
+ "author": "数字孪生项目组",
+ "scene": "车间与野外装备维修实训环境",
+ "tags": [
+ "三维场景",
+ "时间轴动画",
+ "蓝图交互",
+ "实训流程"
+ ],
+ "migratedFrom": "legacy-model-document",
+ "source": "content-api-glb",
+ "sourceName": "09-Fox.glb",
+ "assetCode": "MODEL_MTO6V5W6_09-FOX",
+ "storageUri": "fsvc://unreal-tran/model/2026/09/05/3fc69ae0d435f869afca1fcd943486d1.glb"
+ },
+ "timeline": {
+ "version": 1,
+ "duration": 8,
+ "currentTime": 0,
+ "loop": true,
+ "zoom": 1,
+ "tracks": [
+ {
+ "id": "track-0-1yjfz9t",
+ "name": "模型整体位移",
+ "targetId": "interaction-b9240aaa",
+ "property": "position",
+ "color": "#ffb22e",
+ "muted": false,
+ "keyframes": [
+ {
+ "id": "key-0-0-kmfo820",
+ "time": 0,
+ "value": [
+ 0,
+ 0,
+ 0
+ ],
+ "easing": "easeInOut"
+ },
+ {
+ "id": "key-0-1-snsj2e2",
+ "time": 8,
+ "value": [
+ 0,
+ 0,
+ 0
+ ],
+ "easing": "easeInOut"
+ }
+ ]
+ }
+ ]
+ },
+ "blueprint": {
+ "version": 1,
+ "name": "挖掘机作业实训闭环",
+ "viewport": {
+ "x": 55,
+ "y": 55,
+ "zoom": 0.82
+ },
+ "nodes": [
+ {
+ "id": "bp-start",
+ "type": "trigger-click",
+ "title": "点击挖掘机",
+ "x": 40,
+ "y": 55,
+ "params": {
+ "event": "click",
+ "target": "挖掘机"
+ }
+ },
+ {
+ "id": "bp-highlight",
+ "type": "highlight",
+ "title": "高亮工作装置",
+ "x": 300,
+ "y": 55,
+ "params": {
+ "target": "铲斗",
+ "color": "#38e8ff",
+ "pulse": true
+ }
+ },
+ {
+ "id": "bp-message",
+ "type": "message",
+ "title": "显示操作提示",
+ "x": 560,
+ "y": 55,
+ "params": {
+ "title": "挖掘作业实训",
+ "text": "观察动臂、斗杆和铲斗的协同动作",
+ "level": "info"
+ }
+ },
+ {
+ "id": "bp-play",
+ "type": "play-timeline",
+ "title": "播放作业动画",
+ "x": 820,
+ "y": 55,
+ "params": {
+ "clip": "挖掘机作业实训",
+ "from": 0,
+ "speed": 1,
+ "waitForEnd": true
+ }
+ },
+ {
+ "id": "bp-wait",
+ "type": "wait",
+ "title": "动作稳定",
+ "x": 560,
+ "y": 245,
+ "params": {
+ "duration": 600
+ }
+ },
+ {
+ "id": "bp-check",
+ "type": "condition",
+ "title": "动画是否完成",
+ "x": 300,
+ "y": 245,
+ "params": {
+ "variable": "timelineComplete",
+ "operator": "==",
+ "value": true
+ }
+ },
+ {
+ "id": "bp-retry",
+ "type": "message",
+ "title": "等待动画完成",
+ "x": 820,
+ "y": 245,
+ "params": {
+ "title": "流程校验",
+ "text": "动画尚未完成,请重新执行",
+ "level": "warning"
+ }
+ },
+ {
+ "id": "bp-finish",
+ "type": "finish",
+ "title": "流程完成",
+ "x": 40,
+ "y": 245,
+ "params": {
+ "result": "success",
+ "text": "挖掘机作业实训流程完成"
+ }
+ }
+ ],
+ "edges": [
+ {
+ "id": "bp-e1",
+ "from": {
+ "node": "bp-start",
+ "port": "then"
+ },
+ "to": {
+ "node": "bp-highlight",
+ "port": "in"
+ }
+ },
+ {
+ "id": "bp-e2",
+ "from": {
+ "node": "bp-highlight",
+ "port": "then"
+ },
+ "to": {
+ "node": "bp-message",
+ "port": "in"
+ }
+ },
+ {
+ "id": "bp-e3",
+ "from": {
+ "node": "bp-message",
+ "port": "then"
+ },
+ "to": {
+ "node": "bp-play",
+ "port": "in"
+ }
+ },
+ {
+ "id": "bp-e4",
+ "from": {
+ "node": "bp-play",
+ "port": "then"
+ },
+ "to": {
+ "node": "bp-wait",
+ "port": "in"
+ }
+ },
+ {
+ "id": "bp-e5",
+ "from": {
+ "node": "bp-wait",
+ "port": "then"
+ },
+ "to": {
+ "node": "bp-check",
+ "port": "in"
+ }
+ },
+ {
+ "id": "bp-e6",
+ "from": {
+ "node": "bp-check",
+ "port": "true"
+ },
+ "to": {
+ "node": "bp-finish",
+ "port": "in"
+ }
+ },
+ {
+ "id": "bp-e7",
+ "from": {
+ "node": "bp-check",
+ "port": "false"
+ },
+ "to": {
+ "node": "bp-retry",
+ "port": "in"
+ }
+ },
+ {
+ "id": "bp-e8",
+ "from": {
+ "node": "bp-retry",
+ "port": "then"
+ },
+ "to": {
+ "node": "bp-play",
+ "port": "in"
+ }
+ }
+ ]
+ },
+ "camera": {
+ "position": [
+ 11.153569378931522,
+ 8.526809324389628,
+ 13.922975975299929
+ ],
+ "target": [
+ 0.01518877997232826,
+ 2.4006999949620704,
+ 2.266009371254541e-7
+ ]
+ },
+ "model": {
+ "objects": {
+ "equipment-root": {
+ "name": "09-Fox",
+ "nodeName": "09-Fox",
+ "nodePath": "@root",
+ "interactionId": "interaction-b9240aaa",
+ "capabilities": {
+ "selectable": true,
+ "transform": true,
+ "visible": true,
+ "material": false,
+ "animatable": true
+ },
+ "position": [
+ 0,
+ 0,
+ 0
+ ],
+ "rotation": [
+ 0,
+ 0,
+ 0
+ ],
+ "scale": [
+ 1,
+ 1,
+ 1
+ ],
+ "visible": true,
+ "customComponents": []
+ },
+ "model-imported-model_mto6v5w6_09-fox-0": {
+ "name": "",
+ "nodeName": "Group",
+ "nodePath": "0",
+ "interactionId": "interaction-02351bce",
+ "capabilities": {
+ "selectable": true,
+ "transform": true,
+ "visible": true,
+ "material": false,
+ "animatable": true
+ },
+ "position": [
+ 3.875009990661482e-8,
+ 0.007396563645075124,
+ 0.6522089834776823
+ ],
+ "rotation": [
+ 0,
+ 0,
+ 0
+ ],
+ "scale": [
+ 0.06075496488891343,
+ 0.06075496488891343,
+ 0.06075496488891343
+ ],
+ "visible": true,
+ "customComponents": []
+ },
+ "model-imported-model_mto6v5w6_09-fox-1": {
+ "name": "root",
+ "nodeName": "root",
+ "nodePath": "0/0",
+ "interactionId": "interaction-a4d35db9",
+ "capabilities": {
+ "selectable": true,
+ "transform": true,
+ "visible": true,
+ "material": false,
+ "animatable": true
+ },
+ "position": [
+ 0,
+ 0,
+ 0
+ ],
+ "rotation": [
+ 0,
+ 0,
+ 0
+ ],
+ "scale": [
+ 1,
+ 1,
+ 1
+ ],
+ "visible": true,
+ "customComponents": []
+ },
+ "model-imported-model_mto6v5w6_09-fox-2": {
+ "name": "_rootJoint",
+ "nodeName": "_rootJoint",
+ "nodePath": "0/0/0",
+ "interactionId": "interaction-4476c556",
+ "capabilities": {
+ "selectable": true,
+ "transform": true,
+ "visible": true,
+ "material": false,
+ "animatable": true
+ },
+ "position": [
+ 0,
+ 0,
+ 0
+ ],
+ "rotation": [
+ 0,
+ 0,
+ 0
+ ],
+ "scale": [
+ 1,
+ 1,
+ 1
+ ],
+ "visible": true,
+ "customComponents": []
+ },
+ "model-imported-model_mto6v5w6_09-fox-3": {
+ "name": "b_Root_00",
+ "nodeName": "b_Root_00",
+ "nodePath": "0/0/0/0",
+ "interactionId": "interaction-ffc45d11",
+ "capabilities": {
+ "selectable": true,
+ "transform": true,
+ "visible": true,
+ "material": false,
+ "animatable": true
+ },
+ "position": [
+ 0,
+ 0,
+ 0
+ ],
+ "rotation": [
+ -1.5708000357176293,
+ 0,
+ 0
+ ],
+ "scale": [
+ 1,
+ 1,
+ 1
+ ],
+ "visible": true,
+ "customComponents": []
+ },
+ "model-imported-model_mto6v5w6_09-fox-4": {
+ "name": "b_Hip_01",
+ "nodeName": "b_Hip_01",
+ "nodePath": "0/0/0/0/0",
+ "interactionId": "interaction-7a75e39e",
+ "capabilities": {
+ "selectable": true,
+ "transform": true,
+ "visible": true,
+ "material": false,
+ "animatable": true
+ },
+ "position": [
+ 0.000001241521920852674,
+ 22.061977333358605,
+ 34.62373020761327
+ ],
+ "rotation": [
+ 0.44722050284761794,
+ -1.5707951637404125,
+ 0
+ ],
+ "scale": [
+ 1,
+ 1,
+ 1
+ ],
+ "visible": true,
+ "customComponents": []
+ },
+ "model-imported-model_mto6v5w6_09-fox-5": {
+ "name": "b_Spine01_02",
+ "nodeName": "b_Spine01_02",
+ "nodePath": "0/0/0/0/0/0",
+ "interactionId": "interaction-663ea469",
+ "capabilities": {
+ "selectable": true,
+ "transform": true,
+ "visible": true,
+ "material": false,
+ "animatable": true
+ },
+ "position": [
+ 12.850601196289062,
+ 0,
+ 0
+ ],
+ "rotation": [
+ -3.357310257291308e-7,
+ -3.316574913360067e-8,
+ -1.2102038452161539
+ ],
+ "scale": [
+ 1,
+ 1,
+ 1
+ ],
+ "visible": true,
+ "customComponents": []
+ },
+ "model-imported-model_mto6v5w6_09-fox-6": {
+ "name": "b_Spine02_03",
+ "nodeName": "b_Spine02_03",
+ "nodePath": "0/0/0/0/0/0/0",
+ "interactionId": "interaction-46867126",
+ "capabilities": {
+ "selectable": true,
+ "transform": true,
+ "visible": true,
+ "material": false,
+ "animatable": true
+ },
+ "position": [
+ 21.65575408935547,
+ -0.000118255615234375,
+ 0
+ ],
+ "rotation": [
+ 8.772651057591018e-8,
+ 3.3355428835535634e-8,
+ 0.3633406824515624
+ ],
+ "scale": [
+ 1,
+ 1,
+ 1
+ ],
+ "visible": true,
+ "customComponents": []
+ },
+ "model-imported-model_mto6v5w6_09-fox-7": {
+ "name": "b_Neck_04",
+ "nodeName": "b_Neck_04",
+ "nodePath": "0/0/0/0/0/0/0/0",
+ "interactionId": "interaction-27148d81",
+ "capabilities": {
+ "selectable": true,
+ "transform": true,
+ "visible": true,
+ "material": false,
+ "animatable": true
+ },
+ "position": [
+ 25.64914321899414,
+ 0,
+ 0
+ ],
+ "rotation": [
+ 6.423807629998062e-15,
+ 1.9993472402144555e-16,
+ 0.031113979193075042
+ ],
+ "scale": [
+ 1,
+ 1,
+ 1
+ ],
+ "visible": true,
+ "customComponents": []
+ },
+ "model-imported-model_mto6v5w6_09-fox-8": {
+ "name": "b_Head_05",
+ "nodeName": "b_Head_05",
+ "nodePath": "0/0/0/0/0/0/0/0/0",
+ "interactionId": "interaction-b24eef2e",
+ "capabilities": {
+ "selectable": true,
+ "transform": true,
+ "visible": true,
+ "material": false,
+ "animatable": true
+ },
+ "position": [
+ 13.376960754394531,
+ 0,
+ 0
+ ],
+ "rotation": [
+ 3.253825627358364e-8,
+ -1.577320798309453e-8,
+ -0.451380500897834
+ ],
+ "scale": [
+ 1,
+ 1,
+ 1
+ ],
+ "visible": true,
+ "customComponents": []
+ },
+ "model-imported-model_mto6v5w6_09-fox-9": {
+ "name": "b_RightUpperArm_06",
+ "nodeName": "b_RightUpperArm_06",
+ "nodePath": "0/0/0/0/0/0/0/1",
+ "interactionId": "interaction-26148bee",
+ "capabilities": {
+ "selectable": true,
+ "transform": true,
+ "visible": true,
+ "material": false,
+ "animatable": true
+ },
+ "position": [
+ 18.677913665771484,
+ -4.297340393066406,
+ 6.9675750732421875
+ ],
+ "rotation": [
+ -0.035644184619881884,
+ -0.047001863061712265,
+ -1.2972005346348685
+ ],
+ "scale": [
+ 1,
+ 1,
+ 1
+ ],
+ "visible": true,
+ "customComponents": []
+ },
+ "model-imported-model_mto6v5w6_09-fox-10": {
+ "name": "b_RightForeArm_07",
+ "nodeName": "b_RightForeArm_07",
+ "nodePath": "0/0/0/0/0/0/0/1/0",
+ "interactionId": "interaction-36896559",
+ "capabilities": {
+ "selectable": true,
+ "transform": true,
+ "visible": true,
+ "material": false,
+ "animatable": true
+ },
+ "position": [
+ 23.04512596130371,
+ 0,
+ 0
+ ],
+ "rotation": [
+ 2.1355818997049898e-8,
+ 7.233656206674673e-8,
+ 1.2561843405841544
+ ],
+ "scale": [
+ 1,
+ 1,
+ 1
+ ],
+ "visible": true,
+ "customComponents": []
+ },
+ "model-imported-model_mto6v5w6_09-fox-11": {
+ "name": "b_RightHand_08",
+ "nodeName": "b_RightHand_08",
+ "nodePath": "0/0/0/0/0/0/0/1/0/0",
+ "interactionId": "interaction-57a124f6",
+ "capabilities": {
+ "selectable": true,
+ "transform": true,
+ "visible": true,
+ "material": false,
+ "animatable": true
+ },
+ "position": [
+ 19.350055694580078,
+ -0.14598655700683594,
+ 0
+ ],
+ "rotation": [
+ 0.005245101551986586,
+ 0.05200705984217871,
+ -0.267165337534188
+ ],
+ "scale": [
+ 1,
+ 1,
+ 1
+ ],
+ "visible": true,
+ "customComponents": []
+ },
+ "model-imported-model_mto6v5w6_09-fox-12": {
+ "name": "b_LeftUpperArm_09",
+ "nodeName": "b_LeftUpperArm_09",
+ "nodePath": "0/0/0/0/0/0/0/2",
+ "interactionId": "interaction-25148a5b",
+ "capabilities": {
+ "selectable": true,
+ "transform": true,
+ "visible": true,
+ "material": false,
+ "animatable": true
+ },
+ "position": [
+ 18.67791748046875,
+ -4.297344207763672,
+ -6.967987060546875
+ ],
+ "rotation": [
+ -0.00020118104561539401,
+ 0.0009350561550523671,
+ -1.5148902559141275
+ ],
+ "scale": [
+ 1,
+ 1,
+ 1
+ ],
+ "visible": true,
+ "customComponents": []
+ },
+ "model-imported-model_mto6v5w6_09-fox-13": {
+ "name": "b_LeftForeArm_010",
+ "nodeName": "b_LeftForeArm_010",
+ "nodePath": "0/0/0/0/0/0/0/2/0",
+ "interactionId": "interaction-5c502ac4",
+ "capabilities": {
+ "selectable": true,
+ "transform": true,
+ "visible": true,
+ "material": false,
+ "animatable": true
+ },
+ "position": [
+ 23.045124053955078,
+ 0,
+ 0
+ ],
+ "rotation": [
+ 2.5220861991717696e-8,
+ 7.109903232964989e-8,
+ 1.2307106255258764
+ ],
+ "scale": [
+ 1,
+ 1,
+ 1
+ ],
+ "visible": true,
+ "customComponents": []
+ },
+ "model-imported-model_mto6v5w6_09-fox-14": {
+ "name": "b_LeftHand_011",
+ "nodeName": "b_LeftHand_011",
+ "nodePath": "0/0/0/0/0/0/0/2/0/0",
+ "interactionId": "interaction-192b4cd3",
+ "capabilities": {
+ "selectable": true,
+ "transform": true,
+ "visible": true,
+ "material": false,
+ "animatable": true
+ },
+ "position": [
+ 19.350051879882812,
+ -0.14599037170410156,
+ 0
+ ],
+ "rotation": [
+ 0.004279557751527162,
+ -0.03344276494440009,
+ -1.5985291598689726
+ ],
+ "scale": [
+ 1,
+ 1,
+ 1
+ ],
+ "visible": true,
+ "customComponents": []
+ },
+ "model-imported-model_mto6v5w6_09-fox-15": {
+ "name": "b_Tail01_012",
+ "nodeName": "b_Tail01_012",
+ "nodePath": "0/0/0/0/0/1",
+ "interactionId": "interaction-653ea2d6",
+ "capabilities": {
+ "selectable": true,
+ "transform": true,
+ "visible": true,
+ "material": false,
+ "animatable": true
+ },
+ "position": [
+ 4.2603759765625,
+ 15.958770751953125,
+ 0
+ ],
+ "rotation": [
+ -5.364745216052604e-7,
+ 3.424797608447095e-7,
+ 1.9941304874941974
+ ],
+ "scale": [
+ 1,
+ 1,
+ 1
+ ],
+ "visible": true,
+ "customComponents": []
+ },
+ "model-imported-model_mto6v5w6_09-fox-16": {
+ "name": "b_Tail02_013",
+ "nodeName": "b_Tail02_013",
+ "nodePath": "0/0/0/0/0/1/0",
+ "interactionId": "interaction-6c4d3691",
+ "capabilities": {
+ "selectable": true,
+ "transform": true,
+ "visible": true,
+ "material": false,
+ "animatable": true
+ },
+ "position": [
+ 12.411918640136719,
+ 0,
+ 0
+ ],
+ "rotation": [
+ -6.738507958375962e-23,
+ -5.2439168243310865e-23,
+ 0.3128105211171357
+ ],
+ "scale": [
+ 1,
+ 1,
+ 1
+ ],
+ "visible": true,
+ "customComponents": []
+ },
+ "model-imported-model_mto6v5w6_09-fox-17": {
+ "name": "b_Tail03_014",
+ "nodeName": "b_Tail03_014",
+ "nodePath": "0/0/0/0/0/1/0/0",
+ "interactionId": "interaction-e93e191e",
+ "capabilities": {
+ "selectable": true,
+ "transform": true,
+ "visible": true,
+ "material": false,
+ "animatable": true
+ },
+ "position": [
+ 24.24032211303711,
+ 0,
+ 0
+ ],
+ "rotation": [
+ -5.7486339033124146e-24,
+ 1.1669178332583734e-22,
+ -0.012937892408322462
+ ],
+ "scale": [
+ 1,
+ 1,
+ 1
+ ],
+ "visible": true,
+ "customComponents": []
+ },
+ "model-imported-model_mto6v5w6_09-fox-18": {
+ "name": "b_LeftLeg01_015",
+ "nodeName": "b_LeftLeg01_015",
+ "nodePath": "0/0/0/0/0/2",
+ "interactionId": "interaction-643ea143",
+ "capabilities": {
+ "selectable": true,
+ "transform": true,
+ "visible": true,
+ "material": false,
+ "animatable": true
+ },
+ "position": [
+ 4.813770294189453,
+ 5.154018402099609,
+ -6.968006134033203
+ ],
+ "rotation": [
+ 0.1223493637902729,
+ -0.09724770930295147,
+ 2.9630295165414986
+ ],
+ "scale": [
+ 1,
+ 1,
+ 1
+ ],
+ "visible": true,
+ "customComponents": []
+ },
+ "model-imported-model_mto6v5w6_09-fox-19": {
+ "name": "b_LeftLeg02_016",
+ "nodeName": "b_LeftLeg02_016",
+ "nodePath": "0/0/0/0/0/2/0",
+ "interactionId": "interaction-7175f8dc",
+ "capabilities": {
+ "selectable": true,
+ "transform": true,
+ "visible": true,
+ "material": false,
+ "animatable": true
+ },
+ "position": [
+ 18.944175720214844,
+ 0,
+ 0
+ ],
+ "rotation": [
+ 1.2545483692289454e-8,
+ 3.728483680761731e-8,
+ -0.522293355530293
+ ],
+ "scale": [
+ 1,
+ 1,
+ 1
+ ],
+ "visible": true,
+ "customComponents": []
+ },
+ "model-imported-model_mto6v5w6_09-fox-20": {
+ "name": "b_LeftFoot01_017",
+ "nodeName": "b_LeftFoot01_017",
+ "nodePath": "0/0/0/0/0/2/0/0",
+ "interactionId": "interaction-f5ed353b",
+ "capabilities": {
+ "selectable": true,
+ "transform": true,
+ "visible": true,
+ "material": false,
+ "animatable": true
+ },
+ "position": [
+ 17.942811965942383,
+ 0,
+ 0
+ ],
+ "rotation": [
+ -0.03128474386729028,
+ -0.15471927051895154,
+ 0.7774909055508243
+ ],
+ "scale": [
+ 1,
+ 1,
+ 1
+ ],
+ "visible": true,
+ "customComponents": []
+ },
+ "model-imported-model_mto6v5w6_09-fox-21": {
+ "name": "b_LeftFoot02_018",
+ "nodeName": "b_LeftFoot02_018",
+ "nodePath": "0/0/0/0/0/2/0/0/0",
+ "interactionId": "interaction-1bbe01a4",
+ "capabilities": {
+ "selectable": true,
+ "transform": true,
+ "visible": true,
+ "material": false,
+ "animatable": true
+ },
+ "position": [
+ 15.779938697814941,
+ 0,
+ 0
+ ],
+ "rotation": [
+ 0,
+ 0,
+ 1.158241570830627
+ ],
+ "scale": [
+ 1,
+ 1,
+ 1
+ ],
+ "visible": true,
+ "customComponents": []
+ },
+ "model-imported-model_mto6v5w6_09-fox-22": {
+ "name": "b_RightLeg01_019",
+ "nodeName": "b_RightLeg01_019",
+ "nodePath": "0/0/0/0/0/3",
+ "interactionId": "interaction-633e9fb0",
+ "capabilities": {
+ "selectable": true,
+ "transform": true,
+ "visible": true,
+ "material": false,
+ "animatable": true
+ },
+ "position": [
+ 4.813777923583984,
+ 5.154026031494141,
+ 6.967563629150391
+ ],
+ "rotation": [
+ -0.10137208185544387,
+ 0.05534659522395753,
+ -2.3613690068554236
+ ],
+ "scale": [
+ 1,
+ 1,
+ 1
+ ],
+ "visible": true,
+ "customComponents": []
+ },
+ "model-imported-model_mto6v5w6_09-fox-23": {
+ "name": "b_RightLeg02_020",
+ "nodeName": "b_RightLeg02_020",
+ "nodePath": "0/0/0/0/0/3/0",
+ "interactionId": "interaction-95ff89c7",
+ "capabilities": {
+ "selectable": true,
+ "transform": true,
+ "visible": true,
+ "material": false,
+ "animatable": true
+ },
+ "position": [
+ 18.944183349609375,
+ 0,
+ 0
+ ],
+ "rotation": [
+ 2.7890192431350882e-8,
+ 1.4750479199499402e-8,
+ -1.0710323806538369
+ ],
+ "scale": [
+ 1,
+ 1,
+ 1
+ ],
+ "visible": true,
+ "customComponents": []
+ },
+ "model-imported-model_mto6v5w6_09-fox-24": {
+ "name": "b_RightFoot01_021",
+ "nodeName": "b_RightFoot01_021",
+ "nodePath": "0/0/0/0/0/3/0/0",
+ "interactionId": "interaction-a157d498",
+ "capabilities": {
+ "selectable": true,
+ "transform": true,
+ "visible": true,
+ "material": false,
+ "animatable": true
+ },
+ "position": [
+ 17.94281005859375,
+ 0,
+ 0
+ ],
+ "rotation": [
+ -0.14401413968163027,
+ 0.2814735368347316,
+ 1.4591240233677443
+ ],
+ "scale": [
+ 1,
+ 1,
+ 1
+ ],
+ "visible": true,
+ "customComponents": []
+ },
+ "model-imported-model_mto6v5w6_09-fox-25": {
+ "name": "b_RightFoot02_022",
+ "nodeName": "b_RightFoot02_022",
+ "nodePath": "0/0/0/0/0/3/0/0/0",
+ "interactionId": "interaction-ebd38e3f",
+ "capabilities": {
+ "selectable": true,
+ "transform": true,
+ "visible": true,
+ "material": false,
+ "animatable": true
+ },
+ "position": [
+ 15.779935836791992,
+ 0,
+ 0
+ ],
+ "rotation": [
+ 0,
+ 0,
+ 1.158241570830627
+ ],
+ "scale": [
+ 1,
+ 1,
+ 1
+ ],
+ "visible": true,
+ "customComponents": []
+ },
+ "model-imported-model_mto6v5w6_09-fox-26": {
+ "name": "fox",
+ "nodeName": "fox",
+ "nodePath": "0/1",
+ "interactionId": "interaction-a3d35c26",
+ "capabilities": {
+ "selectable": true,
+ "transform": true,
+ "visible": true,
+ "material": true,
+ "animatable": true
+ },
+ "position": [
+ 0.25,
+ 0,
+ 0
+ ],
+ "rotation": [
+ 0,
+ 0,
+ 0
+ ],
+ "scale": [
+ 1,
+ 1,
+ 1
+ ],
+ "visible": true,
+ "customComponents": [],
+ "materials": [
+ {
+ "color": "ffffff",
+ "emissive": "000000",
+ "emissiveIntensity": 1,
+ "metalness": 0,
+ "roughness": 0.3,
+ "opacity": 1,
+ "transparent": false,
+ "depthWrite": true
+ }
+ ]
+ }
+ },
+ "selectionScope": "part",
+ "nativeAnimation": {
+ "clipIndex": 2,
+ "clipName": "Run"
+ }
+ },
+ "environment": {
+ "mode": "placeholder",
+ "environmentType": "outdoor",
+ "sourceName": "内置野外综合训练环境",
+ "sizeBytes": 0,
+ "assetCode": "",
+ "storageUri": "",
+ "resourceUrl": "",
+ "objects": {
+ "outdoor-scene": {
+ "name": "野外综合训练环境",
+ "position": [
+ 0,
+ 0,
+ 0
+ ],
+ "rotation": [
+ 0,
+ 0,
+ 0
+ ],
+ "scale": [
+ 1,
+ 1,
+ 1
+ ],
+ "visible": true,
+ "customComponents": []
+ },
+ "outdoor-site": {
+ "name": "场地与道路",
+ "position": [
+ 0,
+ 0,
+ 0
+ ],
+ "rotation": [
+ 0,
+ 0,
+ 0
+ ],
+ "scale": [
+ 1,
+ 1,
+ 1
+ ],
+ "visible": true,
+ "customComponents": []
+ },
+ "outdoor-ground": {
+ "name": "室外训练场地",
+ "position": [
+ 0,
+ -0.065,
+ 0
+ ],
+ "rotation": [
+ -1.5707963267948966,
+ 0,
+ 0
+ ],
+ "scale": [
+ 1,
+ 1,
+ 1
+ ],
+ "visible": true,
+ "customComponents": [],
+ "materials": [
+ {
+ "color": "263529",
+ "emissive": "000000",
+ "emissiveIntensity": 1,
+ "metalness": 0,
+ "roughness": 0.98,
+ "opacity": 1,
+ "transparent": false,
+ "depthWrite": true
+ }
+ ]
+ },
+ "outdoor-apron": {
+ "name": "装备操作坪",
+ "position": [
+ 0,
+ -0.045,
+ 0
+ ],
+ "rotation": [
+ -1.5707963267948966,
+ 0,
+ 0
+ ],
+ "scale": [
+ 1,
+ 1,
+ 1
+ ],
+ "visible": true,
+ "customComponents": [],
+ "materials": [
+ {
+ "color": "39444a",
+ "emissive": "000000",
+ "emissiveIntensity": 1,
+ "metalness": 0.03,
+ "roughness": 0.92,
+ "opacity": 1,
+ "transparent": false,
+ "depthWrite": true
+ }
+ ]
+ },
+ "outdoor-road": {
+ "name": "保障通道",
+ "position": [
+ 0,
+ -0.035,
+ 17
+ ],
+ "rotation": [
+ -1.5707963267948966,
+ 0,
+ 0
+ ],
+ "scale": [
+ 1,
+ 1,
+ 1
+ ],
+ "visible": true,
+ "customComponents": [],
+ "materials": [
+ {
+ "color": "202a31",
+ "emissive": "000000",
+ "emissiveIntensity": 1,
+ "metalness": 0.02,
+ "roughness": 0.95,
+ "opacity": 1,
+ "transparent": false,
+ "depthWrite": true
+ }
+ ]
+ },
+ "outdoor-road-line-1": {
+ "name": "道路中心线 1",
+ "position": [
+ -28.699999999999996,
+ 0.01,
+ 17
+ ],
+ "rotation": [
+ 0,
+ 0,
+ 0
+ ],
+ "scale": [
+ 1,
+ 1,
+ 1
+ ],
+ "visible": true,
+ "customComponents": [],
+ "materials": [
+ {
+ "color": "e5c64c",
+ "emissive": "302600",
+ "emissiveIntensity": 0.18,
+ "metalness": 0.08,
+ "roughness": 0.72,
+ "opacity": 1,
+ "transparent": false,
+ "depthWrite": true
+ }
+ ]
+ },
+ "outdoor-road-line-2": {
+ "name": "道路中心线 2",
+ "position": [
+ -24.599999999999998,
+ 0.01,
+ 17
+ ],
+ "rotation": [
+ 0,
+ 0,
+ 0
+ ],
+ "scale": [
+ 1,
+ 1,
+ 1
+ ],
+ "visible": true,
+ "customComponents": [],
+ "materials": [
+ {
+ "color": "e5c64c",
+ "emissive": "302600",
+ "emissiveIntensity": 0.18,
+ "metalness": 0.08,
+ "roughness": 0.72,
+ "opacity": 1,
+ "transparent": false,
+ "depthWrite": true
+ }
+ ]
+ },
+ "outdoor-road-line-3": {
+ "name": "道路中心线 3",
+ "position": [
+ -20.5,
+ 0.01,
+ 17
+ ],
+ "rotation": [
+ 0,
+ 0,
+ 0
+ ],
+ "scale": [
+ 1,
+ 1,
+ 1
+ ],
+ "visible": true,
+ "customComponents": [],
+ "materials": [
+ {
+ "color": "e5c64c",
+ "emissive": "302600",
+ "emissiveIntensity": 0.18,
+ "metalness": 0.08,
+ "roughness": 0.72,
+ "opacity": 1,
+ "transparent": false,
+ "depthWrite": true
+ }
+ ]
+ },
+ "outdoor-road-line-4": {
+ "name": "道路中心线 4",
+ "position": [
+ -16.4,
+ 0.01,
+ 17
+ ],
+ "rotation": [
+ 0,
+ 0,
+ 0
+ ],
+ "scale": [
+ 1,
+ 1,
+ 1
+ ],
+ "visible": true,
+ "customComponents": [],
+ "materials": [
+ {
+ "color": "e5c64c",
+ "emissive": "302600",
+ "emissiveIntensity": 0.18,
+ "metalness": 0.08,
+ "roughness": 0.72,
+ "opacity": 1,
+ "transparent": false,
+ "depthWrite": true
+ }
+ ]
+ },
+ "outdoor-road-line-5": {
+ "name": "道路中心线 5",
+ "position": [
+ -12.299999999999999,
+ 0.01,
+ 17
+ ],
+ "rotation": [
+ 0,
+ 0,
+ 0
+ ],
+ "scale": [
+ 1,
+ 1,
+ 1
+ ],
+ "visible": true,
+ "customComponents": [],
+ "materials": [
+ {
+ "color": "e5c64c",
+ "emissive": "302600",
+ "emissiveIntensity": 0.18,
+ "metalness": 0.08,
+ "roughness": 0.72,
+ "opacity": 1,
+ "transparent": false,
+ "depthWrite": true
+ }
+ ]
+ },
+ "outdoor-road-line-6": {
+ "name": "道路中心线 6",
+ "position": [
+ -8.2,
+ 0.01,
+ 17
+ ],
+ "rotation": [
+ 0,
+ 0,
+ 0
+ ],
+ "scale": [
+ 1,
+ 1,
+ 1
+ ],
+ "visible": true,
+ "customComponents": [],
+ "materials": [
+ {
+ "color": "e5c64c",
+ "emissive": "302600",
+ "emissiveIntensity": 0.18,
+ "metalness": 0.08,
+ "roughness": 0.72,
+ "opacity": 1,
+ "transparent": false,
+ "depthWrite": true
+ }
+ ]
+ },
+ "outdoor-road-line-7": {
+ "name": "道路中心线 7",
+ "position": [
+ -4.1,
+ 0.01,
+ 17
+ ],
+ "rotation": [
+ 0,
+ 0,
+ 0
+ ],
+ "scale": [
+ 1,
+ 1,
+ 1
+ ],
+ "visible": true,
+ "customComponents": [],
+ "materials": [
+ {
+ "color": "e5c64c",
+ "emissive": "302600",
+ "emissiveIntensity": 0.18,
+ "metalness": 0.08,
+ "roughness": 0.72,
+ "opacity": 1,
+ "transparent": false,
+ "depthWrite": true
+ }
+ ]
+ },
+ "outdoor-road-line-8": {
+ "name": "道路中心线 8",
+ "position": [
+ 0,
+ 0.01,
+ 17
+ ],
+ "rotation": [
+ 0,
+ 0,
+ 0
+ ],
+ "scale": [
+ 1,
+ 1,
+ 1
+ ],
+ "visible": true,
+ "customComponents": [],
+ "materials": [
+ {
+ "color": "e5c64c",
+ "emissive": "302600",
+ "emissiveIntensity": 0.18,
+ "metalness": 0.08,
+ "roughness": 0.72,
+ "opacity": 1,
+ "transparent": false,
+ "depthWrite": true
+ }
+ ]
+ },
+ "outdoor-road-line-9": {
+ "name": "道路中心线 9",
+ "position": [
+ 4.1,
+ 0.01,
+ 17
+ ],
+ "rotation": [
+ 0,
+ 0,
+ 0
+ ],
+ "scale": [
+ 1,
+ 1,
+ 1
+ ],
+ "visible": true,
+ "customComponents": [],
+ "materials": [
+ {
+ "color": "e5c64c",
+ "emissive": "302600",
+ "emissiveIntensity": 0.18,
+ "metalness": 0.08,
+ "roughness": 0.72,
+ "opacity": 1,
+ "transparent": false,
+ "depthWrite": true
+ }
+ ]
+ },
+ "outdoor-road-line-10": {
+ "name": "道路中心线 10",
+ "position": [
+ 8.2,
+ 0.01,
+ 17
+ ],
+ "rotation": [
+ 0,
+ 0,
+ 0
+ ],
+ "scale": [
+ 1,
+ 1,
+ 1
+ ],
+ "visible": true,
+ "customComponents": [],
+ "materials": [
+ {
+ "color": "e5c64c",
+ "emissive": "302600",
+ "emissiveIntensity": 0.18,
+ "metalness": 0.08,
+ "roughness": 0.72,
+ "opacity": 1,
+ "transparent": false,
+ "depthWrite": true
+ }
+ ]
+ },
+ "outdoor-road-line-11": {
+ "name": "道路中心线 11",
+ "position": [
+ 12.299999999999999,
+ 0.01,
+ 17
+ ],
+ "rotation": [
+ 0,
+ 0,
+ 0
+ ],
+ "scale": [
+ 1,
+ 1,
+ 1
+ ],
+ "visible": true,
+ "customComponents": [],
+ "materials": [
+ {
+ "color": "e5c64c",
+ "emissive": "302600",
+ "emissiveIntensity": 0.18,
+ "metalness": 0.08,
+ "roughness": 0.72,
+ "opacity": 1,
+ "transparent": false,
+ "depthWrite": true
+ }
+ ]
+ },
+ "outdoor-road-line-12": {
+ "name": "道路中心线 12",
+ "position": [
+ 16.4,
+ 0.01,
+ 17
+ ],
+ "rotation": [
+ 0,
+ 0,
+ 0
+ ],
+ "scale": [
+ 1,
+ 1,
+ 1
+ ],
+ "visible": true,
+ "customComponents": [],
+ "materials": [
+ {
+ "color": "e5c64c",
+ "emissive": "302600",
+ "emissiveIntensity": 0.18,
+ "metalness": 0.08,
+ "roughness": 0.72,
+ "opacity": 1,
+ "transparent": false,
+ "depthWrite": true
+ }
+ ]
+ },
+ "outdoor-road-line-13": {
+ "name": "道路中心线 13",
+ "position": [
+ 20.5,
+ 0.01,
+ 17
+ ],
+ "rotation": [
+ 0,
+ 0,
+ 0
+ ],
+ "scale": [
+ 1,
+ 1,
+ 1
+ ],
+ "visible": true,
+ "customComponents": [],
+ "materials": [
+ {
+ "color": "e5c64c",
+ "emissive": "302600",
+ "emissiveIntensity": 0.18,
+ "metalness": 0.08,
+ "roughness": 0.72,
+ "opacity": 1,
+ "transparent": false,
+ "depthWrite": true
+ }
+ ]
+ },
+ "outdoor-road-line-14": {
+ "name": "道路中心线 14",
+ "position": [
+ 24.599999999999998,
+ 0.01,
+ 17
+ ],
+ "rotation": [
+ 0,
+ 0,
+ 0
+ ],
+ "scale": [
+ 1,
+ 1,
+ 1
+ ],
+ "visible": true,
+ "customComponents": [],
+ "materials": [
+ {
+ "color": "e5c64c",
+ "emissive": "302600",
+ "emissiveIntensity": 0.18,
+ "metalness": 0.08,
+ "roughness": 0.72,
+ "opacity": 1,
+ "transparent": false,
+ "depthWrite": true
+ }
+ ]
+ },
+ "outdoor-road-line-15": {
+ "name": "道路中心线 15",
+ "position": [
+ 28.699999999999996,
+ 0.01,
+ 17
+ ],
+ "rotation": [
+ 0,
+ 0,
+ 0
+ ],
+ "scale": [
+ 1,
+ 1,
+ 1
+ ],
+ "visible": true,
+ "customComponents": [],
+ "materials": [
+ {
+ "color": "e5c64c",
+ "emissive": "302600",
+ "emissiveIntensity": 0.18,
+ "metalness": 0.08,
+ "roughness": 0.72,
+ "opacity": 1,
+ "transparent": false,
+ "depthWrite": true
+ }
+ ]
+ },
+ "outdoor-safety--17.6--11": {
+ "name": "操作区安全标线",
+ "position": [
+ -17.6,
+ 0.012,
+ -11
+ ],
+ "rotation": [
+ 0,
+ 0.5654866776461628,
+ 0
+ ],
+ "scale": [
+ 1,
+ 1,
+ 1
+ ],
+ "visible": true,
+ "customComponents": [],
+ "materials": [
+ {
+ "color": "e5c64c",
+ "emissive": "302600",
+ "emissiveIntensity": 0.18,
+ "metalness": 0.08,
+ "roughness": 0.72,
+ "opacity": 1,
+ "transparent": false,
+ "depthWrite": true
+ }
+ ]
+ },
+ "outdoor-safety--17.6--8.4": {
+ "name": "操作区安全标线",
+ "position": [
+ -17.6,
+ 0.012,
+ -8.4
+ ],
+ "rotation": [
+ 0,
+ 0.5654866776461628,
+ 0
+ ],
+ "scale": [
+ 1,
+ 1,
+ 1
+ ],
+ "visible": true,
+ "customComponents": [],
+ "materials": [
+ {
+ "color": "e5c64c",
+ "emissive": "302600",
+ "emissiveIntensity": 0.18,
+ "metalness": 0.08,
+ "roughness": 0.72,
+ "opacity": 1,
+ "transparent": false,
+ "depthWrite": true
+ }
+ ]
+ },
+ "outdoor-safety--17.6--5.800000000000001": {
+ "name": "操作区安全标线",
+ "position": [
+ -17.6,
+ 0.012,
+ -5.800000000000001
+ ],
+ "rotation": [
+ 0,
+ 0.5654866776461628,
+ 0
+ ],
+ "scale": [
+ 1,
+ 1,
+ 1
+ ],
+ "visible": true,
+ "customComponents": [],
+ "materials": [
+ {
+ "color": "e5c64c",
+ "emissive": "302600",
+ "emissiveIntensity": 0.18,
+ "metalness": 0.08,
+ "roughness": 0.72,
+ "opacity": 1,
+ "transparent": false,
+ "depthWrite": true
+ }
+ ]
+ },
+ "outdoor-safety--17.6--3.2000000000000006": {
+ "name": "操作区安全标线",
+ "position": [
+ -17.6,
+ 0.012,
+ -3.2000000000000006
+ ],
+ "rotation": [
+ 0,
+ 0.5654866776461628,
+ 0
+ ],
+ "scale": [
+ 1,
+ 1,
+ 1
+ ],
+ "visible": true,
+ "customComponents": [],
+ "materials": [
+ {
+ "color": "e5c64c",
+ "emissive": "302600",
+ "emissiveIntensity": 0.18,
+ "metalness": 0.08,
+ "roughness": 0.72,
+ "opacity": 1,
+ "transparent": false,
+ "depthWrite": true
+ }
+ ]
+ },
+ "outdoor-safety--17.6--0.6000000000000005": {
+ "name": "操作区安全标线",
+ "position": [
+ -17.6,
+ 0.012,
+ -0.6000000000000005
+ ],
+ "rotation": [
+ 0,
+ 0.5654866776461628,
+ 0
+ ],
+ "scale": [
+ 1,
+ 1,
+ 1
+ ],
+ "visible": true,
+ "customComponents": [],
+ "materials": [
+ {
+ "color": "e5c64c",
+ "emissive": "302600",
+ "emissiveIntensity": 0.18,
+ "metalness": 0.08,
+ "roughness": 0.72,
+ "opacity": 1,
+ "transparent": false,
+ "depthWrite": true
+ }
+ ]
+ },
+ "outdoor-safety--17.6-1.9999999999999996": {
+ "name": "操作区安全标线",
+ "position": [
+ -17.6,
+ 0.012,
+ 1.9999999999999996
+ ],
+ "rotation": [
+ 0,
+ 0.5654866776461628,
+ 0
+ ],
+ "scale": [
+ 1,
+ 1,
+ 1
+ ],
+ "visible": true,
+ "customComponents": [],
+ "materials": [
+ {
+ "color": "e5c64c",
+ "emissive": "302600",
+ "emissiveIntensity": 0.18,
+ "metalness": 0.08,
+ "roughness": 0.72,
+ "opacity": 1,
+ "transparent": false,
+ "depthWrite": true
+ }
+ ]
+ },
+ "outdoor-safety--17.6-4.6": {
+ "name": "操作区安全标线",
+ "position": [
+ -17.6,
+ 0.012,
+ 4.6
+ ],
+ "rotation": [
+ 0,
+ 0.5654866776461628,
+ 0
+ ],
+ "scale": [
+ 1,
+ 1,
+ 1
+ ],
+ "visible": true,
+ "customComponents": [],
+ "materials": [
+ {
+ "color": "e5c64c",
+ "emissive": "302600",
+ "emissiveIntensity": 0.18,
+ "metalness": 0.08,
+ "roughness": 0.72,
+ "opacity": 1,
+ "transparent": false,
+ "depthWrite": true
+ }
+ ]
+ },
+ "outdoor-safety--17.6-7.199999999999999": {
+ "name": "操作区安全标线",
+ "position": [
+ -17.6,
+ 0.012,
+ 7.199999999999999
+ ],
+ "rotation": [
+ 0,
+ 0.5654866776461628,
+ 0
+ ],
+ "scale": [
+ 1,
+ 1,
+ 1
+ ],
+ "visible": true,
+ "customComponents": [],
+ "materials": [
+ {
+ "color": "e5c64c",
+ "emissive": "302600",
+ "emissiveIntensity": 0.18,
+ "metalness": 0.08,
+ "roughness": 0.72,
+ "opacity": 1,
+ "transparent": false,
+ "depthWrite": true
+ }
+ ]
+ },
+ "outdoor-safety--17.6-9.799999999999999": {
+ "name": "操作区安全标线",
+ "position": [
+ -17.6,
+ 0.012,
+ 9.799999999999999
+ ],
+ "rotation": [
+ 0,
+ 0.5654866776461628,
+ 0
+ ],
+ "scale": [
+ 1,
+ 1,
+ 1
+ ],
+ "visible": true,
+ "customComponents": [],
+ "materials": [
+ {
+ "color": "e5c64c",
+ "emissive": "302600",
+ "emissiveIntensity": 0.18,
+ "metalness": 0.08,
+ "roughness": 0.72,
+ "opacity": 1,
+ "transparent": false,
+ "depthWrite": true
+ }
+ ]
+ },
+ "outdoor-safety-17.6--11": {
+ "name": "操作区安全标线",
+ "position": [
+ 17.6,
+ 0.012,
+ -11
+ ],
+ "rotation": [
+ 0,
+ 0.5654866776461628,
+ 0
+ ],
+ "scale": [
+ 1,
+ 1,
+ 1
+ ],
+ "visible": true,
+ "customComponents": [],
+ "materials": [
+ {
+ "color": "e5c64c",
+ "emissive": "302600",
+ "emissiveIntensity": 0.18,
+ "metalness": 0.08,
+ "roughness": 0.72,
+ "opacity": 1,
+ "transparent": false,
+ "depthWrite": true
+ }
+ ]
+ },
+ "outdoor-safety-17.6--8.4": {
+ "name": "操作区安全标线",
+ "position": [
+ 17.6,
+ 0.012,
+ -8.4
+ ],
+ "rotation": [
+ 0,
+ 0.5654866776461628,
+ 0
+ ],
+ "scale": [
+ 1,
+ 1,
+ 1
+ ],
+ "visible": true,
+ "customComponents": [],
+ "materials": [
+ {
+ "color": "e5c64c",
+ "emissive": "302600",
+ "emissiveIntensity": 0.18,
+ "metalness": 0.08,
+ "roughness": 0.72,
+ "opacity": 1,
+ "transparent": false,
+ "depthWrite": true
+ }
+ ]
+ },
+ "outdoor-safety-17.6--5.800000000000001": {
+ "name": "操作区安全标线",
+ "position": [
+ 17.6,
+ 0.012,
+ -5.800000000000001
+ ],
+ "rotation": [
+ 0,
+ 0.5654866776461628,
+ 0
+ ],
+ "scale": [
+ 1,
+ 1,
+ 1
+ ],
+ "visible": true,
+ "customComponents": [],
+ "materials": [
+ {
+ "color": "e5c64c",
+ "emissive": "302600",
+ "emissiveIntensity": 0.18,
+ "metalness": 0.08,
+ "roughness": 0.72,
+ "opacity": 1,
+ "transparent": false,
+ "depthWrite": true
+ }
+ ]
+ },
+ "outdoor-safety-17.6--3.2000000000000006": {
+ "name": "操作区安全标线",
+ "position": [
+ 17.6,
+ 0.012,
+ -3.2000000000000006
+ ],
+ "rotation": [
+ 0,
+ 0.5654866776461628,
+ 0
+ ],
+ "scale": [
+ 1,
+ 1,
+ 1
+ ],
+ "visible": true,
+ "customComponents": [],
+ "materials": [
+ {
+ "color": "e5c64c",
+ "emissive": "302600",
+ "emissiveIntensity": 0.18,
+ "metalness": 0.08,
+ "roughness": 0.72,
+ "opacity": 1,
+ "transparent": false,
+ "depthWrite": true
+ }
+ ]
+ },
+ "outdoor-safety-17.6--0.6000000000000005": {
+ "name": "操作区安全标线",
+ "position": [
+ 17.6,
+ 0.012,
+ -0.6000000000000005
+ ],
+ "rotation": [
+ 0,
+ 0.5654866776461628,
+ 0
+ ],
+ "scale": [
+ 1,
+ 1,
+ 1
+ ],
+ "visible": true,
+ "customComponents": [],
+ "materials": [
+ {
+ "color": "e5c64c",
+ "emissive": "302600",
+ "emissiveIntensity": 0.18,
+ "metalness": 0.08,
+ "roughness": 0.72,
+ "opacity": 1,
+ "transparent": false,
+ "depthWrite": true
+ }
+ ]
+ },
+ "outdoor-safety-17.6-1.9999999999999996": {
+ "name": "操作区安全标线",
+ "position": [
+ 17.6,
+ 0.012,
+ 1.9999999999999996
+ ],
+ "rotation": [
+ 0,
+ 0.5654866776461628,
+ 0
+ ],
+ "scale": [
+ 1,
+ 1,
+ 1
+ ],
+ "visible": true,
+ "customComponents": [],
+ "materials": [
+ {
+ "color": "e5c64c",
+ "emissive": "302600",
+ "emissiveIntensity": 0.18,
+ "metalness": 0.08,
+ "roughness": 0.72,
+ "opacity": 1,
+ "transparent": false,
+ "depthWrite": true
+ }
+ ]
+ },
+ "outdoor-safety-17.6-4.6": {
+ "name": "操作区安全标线",
+ "position": [
+ 17.6,
+ 0.012,
+ 4.6
+ ],
+ "rotation": [
+ 0,
+ 0.5654866776461628,
+ 0
+ ],
+ "scale": [
+ 1,
+ 1,
+ 1
+ ],
+ "visible": true,
+ "customComponents": [],
+ "materials": [
+ {
+ "color": "e5c64c",
+ "emissive": "302600",
+ "emissiveIntensity": 0.18,
+ "metalness": 0.08,
+ "roughness": 0.72,
+ "opacity": 1,
+ "transparent": false,
+ "depthWrite": true
+ }
+ ]
+ },
+ "outdoor-safety-17.6-7.199999999999999": {
+ "name": "操作区安全标线",
+ "position": [
+ 17.6,
+ 0.012,
+ 7.199999999999999
+ ],
+ "rotation": [
+ 0,
+ 0.5654866776461628,
+ 0
+ ],
+ "scale": [
+ 1,
+ 1,
+ 1
+ ],
+ "visible": true,
+ "customComponents": [],
+ "materials": [
+ {
+ "color": "e5c64c",
+ "emissive": "302600",
+ "emissiveIntensity": 0.18,
+ "metalness": 0.08,
+ "roughness": 0.72,
+ "opacity": 1,
+ "transparent": false,
+ "depthWrite": true
+ }
+ ]
+ },
+ "outdoor-safety-17.6-9.799999999999999": {
+ "name": "操作区安全标线",
+ "position": [
+ 17.6,
+ 0.012,
+ 9.799999999999999
+ ],
+ "rotation": [
+ 0,
+ 0.5654866776461628,
+ 0
+ ],
+ "scale": [
+ 1,
+ 1,
+ 1
+ ],
+ "visible": true,
+ "customComponents": [],
+ "materials": [
+ {
+ "color": "e5c64c",
+ "emissive": "302600",
+ "emissiveIntensity": 0.18,
+ "metalness": 0.08,
+ "roughness": 0.72,
+ "opacity": 1,
+ "transparent": false,
+ "depthWrite": true
+ }
+ ]
+ },
+ "outdoor-workshop": {
+ "name": "维修保障厂房",
+ "position": [
+ 0,
+ 0,
+ 0
+ ],
+ "rotation": [
+ 0,
+ 0,
+ 0
+ ],
+ "scale": [
+ 1,
+ 1,
+ 1
+ ],
+ "visible": true,
+ "customComponents": []
+ },
+ "outdoor-workshop-body": {
+ "name": "维修保障厂房主体",
+ "position": [
+ -10,
+ 3.25,
+ -18
+ ],
+ "rotation": [
+ 0,
+ 0,
+ 0
+ ],
+ "scale": [
+ 1,
+ 1,
+ 1
+ ],
+ "visible": true,
+ "customComponents": [],
+ "materials": [
+ {
+ "color": "536675",
+ "emissive": "000000",
+ "emissiveIntensity": 1,
+ "metalness": 0.18,
+ "roughness": 0.78,
+ "opacity": 1,
+ "transparent": false,
+ "depthWrite": true
+ }
+ ]
+ },
+ "outdoor-workshop-roof": {
+ "name": "厂房金属屋面",
+ "position": [
+ -10,
+ 6.65,
+ -18
+ ],
+ "rotation": [
+ 0,
+ 0,
+ 0
+ ],
+ "scale": [
+ 1,
+ 1,
+ 1
+ ],
+ "visible": true,
+ "customComponents": [],
+ "materials": [
+ {
+ "color": "263846",
+ "emissive": "000000",
+ "emissiveIntensity": 1,
+ "metalness": 0.35,
+ "roughness": 0.62,
+ "opacity": 1,
+ "transparent": false,
+ "depthWrite": true
+ }
+ ]
+ },
+ "outdoor-workshop-window-1": {
+ "name": "厂房观察窗 1",
+ "position": [
+ -17,
+ 3.7,
+ -14.21
+ ],
+ "rotation": [
+ 0,
+ 0,
+ 0
+ ],
+ "scale": [
+ 1,
+ 1,
+ 1
+ ],
+ "visible": true,
+ "customComponents": [],
+ "materials": [
+ {
+ "color": "4a89a2",
+ "emissive": "000000",
+ "emissiveIntensity": 1,
+ "metalness": 0.12,
+ "roughness": 0.25,
+ "opacity": 0.68,
+ "transparent": true,
+ "depthWrite": true
+ }
+ ]
+ },
+ "outdoor-workshop-window-2": {
+ "name": "厂房观察窗 2",
+ "position": [
+ -12.4,
+ 3.7,
+ -14.21
+ ],
+ "rotation": [
+ 0,
+ 0,
+ 0
+ ],
+ "scale": [
+ 1,
+ 1,
+ 1
+ ],
+ "visible": true,
+ "customComponents": [],
+ "materials": [
+ {
+ "color": "4a89a2",
+ "emissive": "000000",
+ "emissiveIntensity": 1,
+ "metalness": 0.12,
+ "roughness": 0.25,
+ "opacity": 0.68,
+ "transparent": true,
+ "depthWrite": true
+ }
+ ]
+ },
+ "outdoor-workshop-window-3": {
+ "name": "厂房观察窗 3",
+ "position": [
+ -7.800000000000001,
+ 3.7,
+ -14.21
+ ],
+ "rotation": [
+ 0,
+ 0,
+ 0
+ ],
+ "scale": [
+ 1,
+ 1,
+ 1
+ ],
+ "visible": true,
+ "customComponents": [],
+ "materials": [
+ {
+ "color": "4a89a2",
+ "emissive": "000000",
+ "emissiveIntensity": 1,
+ "metalness": 0.12,
+ "roughness": 0.25,
+ "opacity": 0.68,
+ "transparent": true,
+ "depthWrite": true
+ }
+ ]
+ },
+ "outdoor-workshop-window-4": {
+ "name": "厂房观察窗 4",
+ "position": [
+ -3.200000000000001,
+ 3.7,
+ -14.21
+ ],
+ "rotation": [
+ 0,
+ 0,
+ 0
+ ],
+ "scale": [
+ 1,
+ 1,
+ 1
+ ],
+ "visible": true,
+ "customComponents": [],
+ "materials": [
+ {
+ "color": "4a89a2",
+ "emissive": "000000",
+ "emissiveIntensity": 1,
+ "metalness": 0.12,
+ "roughness": 0.25,
+ "opacity": 0.68,
+ "transparent": true,
+ "depthWrite": true
+ }
+ ]
+ },
+ "outdoor-workshop-door": {
+ "name": "装备出入门",
+ "position": [
+ -3.6,
+ 2.16,
+ -14.18
+ ],
+ "rotation": [
+ 0,
+ 0,
+ 0
+ ],
+ "scale": [
+ 1,
+ 1,
+ 1
+ ],
+ "visible": true,
+ "customComponents": [],
+ "materials": [
+ {
+ "color": "202a31",
+ "emissive": "000000",
+ "emissiveIntensity": 1,
+ "metalness": 0.02,
+ "roughness": 0.95,
+ "opacity": 1,
+ "transparent": false,
+ "depthWrite": true
+ }
+ ]
+ },
+ "outdoor-canopy": {
+ "name": "室外检修棚",
+ "position": [
+ 0,
+ 0,
+ 0
+ ],
+ "rotation": [
+ 0,
+ 0,
+ 0
+ ],
+ "scale": [
+ 1,
+ 1,
+ 1
+ ],
+ "visible": true,
+ "customComponents": []
+ },
+ "outdoor-canopy-roof": {
+ "name": "检修棚顶",
+ "position": [
+ 18,
+ 5.05,
+ -8
+ ],
+ "rotation": [
+ 0,
+ 0,
+ 0
+ ],
+ "scale": [
+ 1,
+ 1,
+ 1
+ ],
+ "visible": true,
+ "customComponents": [],
+ "materials": [
+ {
+ "color": "263846",
+ "emissive": "000000",
+ "emissiveIntensity": 1,
+ "metalness": 0.35,
+ "roughness": 0.62,
+ "opacity": 1,
+ "transparent": false,
+ "depthWrite": true
+ }
+ ]
+ },
+ "outdoor-canopy-column-1": {
+ "name": "检修棚立柱 1",
+ "position": [
+ 12.7,
+ 2.5,
+ -11
+ ],
+ "rotation": [
+ 0,
+ 0,
+ 0
+ ],
+ "scale": [
+ 1,
+ 1,
+ 1
+ ],
+ "visible": true,
+ "customComponents": [],
+ "materials": [
+ {
+ "color": "66757c",
+ "emissive": "000000",
+ "emissiveIntensity": 1,
+ "metalness": 0.48,
+ "roughness": 0.56,
+ "opacity": 1,
+ "transparent": false,
+ "depthWrite": true
+ }
+ ]
+ },
+ "outdoor-canopy-column-2": {
+ "name": "检修棚立柱 2",
+ "position": [
+ 23.3,
+ 2.5,
+ -11
+ ],
+ "rotation": [
+ 0,
+ 0,
+ 0
+ ],
+ "scale": [
+ 1,
+ 1,
+ 1
+ ],
+ "visible": true,
+ "customComponents": [],
+ "materials": [
+ {
+ "color": "66757c",
+ "emissive": "000000",
+ "emissiveIntensity": 1,
+ "metalness": 0.48,
+ "roughness": 0.56,
+ "opacity": 1,
+ "transparent": false,
+ "depthWrite": true
+ }
+ ]
+ },
+ "outdoor-canopy-column-3": {
+ "name": "检修棚立柱 3",
+ "position": [
+ 12.7,
+ 2.5,
+ -5
+ ],
+ "rotation": [
+ 0,
+ 0,
+ 0
+ ],
+ "scale": [
+ 1,
+ 1,
+ 1
+ ],
+ "visible": true,
+ "customComponents": [],
+ "materials": [
+ {
+ "color": "66757c",
+ "emissive": "000000",
+ "emissiveIntensity": 1,
+ "metalness": 0.48,
+ "roughness": 0.56,
+ "opacity": 1,
+ "transparent": false,
+ "depthWrite": true
+ }
+ ]
+ },
+ "outdoor-canopy-column-4": {
+ "name": "检修棚立柱 4",
+ "position": [
+ 23.3,
+ 2.5,
+ -5
+ ],
+ "rotation": [
+ 0,
+ 0,
+ 0
+ ],
+ "scale": [
+ 1,
+ 1,
+ 1
+ ],
+ "visible": true,
+ "customComponents": [],
+ "materials": [
+ {
+ "color": "66757c",
+ "emissive": "000000",
+ "emissiveIntensity": 1,
+ "metalness": 0.48,
+ "roughness": 0.56,
+ "opacity": 1,
+ "transparent": false,
+ "depthWrite": true
+ }
+ ]
+ },
+ "outdoor-service-box": {
+ "name": "室外工具柜",
+ "position": [
+ 22.2,
+ 1.05,
+ -9.8
+ ],
+ "rotation": [
+ 0,
+ 0,
+ 0
+ ],
+ "scale": [
+ 1,
+ 1,
+ 1
+ ],
+ "visible": true,
+ "customComponents": [],
+ "materials": [
+ {
+ "color": "d98d2b",
+ "emissive": "000000",
+ "emissiveIntensity": 1,
+ "metalness": 0.12,
+ "roughness": 0.7,
+ "opacity": 1,
+ "transparent": false,
+ "depthWrite": true
+ }
+ ]
+ },
+ "outdoor-perimeter": {
+ "name": "场地边界与安全设施",
+ "position": [
+ 0,
+ 0,
+ 0
+ ],
+ "rotation": [
+ 0,
+ 0,
+ 0
+ ],
+ "scale": [
+ 1,
+ 1,
+ 1
+ ],
+ "visible": true,
+ "customComponents": []
+ },
+ "outdoor-fence-back-1": {
+ "name": "后侧围栏立柱 1",
+ "position": [
+ -29,
+ 1.1,
+ -25
+ ],
+ "rotation": [
+ 0,
+ 0,
+ 0
+ ],
+ "scale": [
+ 1,
+ 1,
+ 1
+ ],
+ "visible": true,
+ "customComponents": [],
+ "materials": [
+ {
+ "color": "66757c",
+ "emissive": "000000",
+ "emissiveIntensity": 1,
+ "metalness": 0.48,
+ "roughness": 0.56,
+ "opacity": 1,
+ "transparent": false,
+ "depthWrite": true
+ }
+ ]
+ },
+ "outdoor-fence-back-2": {
+ "name": "后侧围栏立柱 2",
+ "position": [
+ -23.2,
+ 1.1,
+ -25
+ ],
+ "rotation": [
+ 0,
+ 0,
+ 0
+ ],
+ "scale": [
+ 1,
+ 1,
+ 1
+ ],
+ "visible": true,
+ "customComponents": [],
+ "materials": [
+ {
+ "color": "66757c",
+ "emissive": "000000",
+ "emissiveIntensity": 1,
+ "metalness": 0.48,
+ "roughness": 0.56,
+ "opacity": 1,
+ "transparent": false,
+ "depthWrite": true
+ }
+ ]
+ },
+ "outdoor-fence-back-3": {
+ "name": "后侧围栏立柱 3",
+ "position": [
+ -17.4,
+ 1.1,
+ -25
+ ],
+ "rotation": [
+ 0,
+ 0,
+ 0
+ ],
+ "scale": [
+ 1,
+ 1,
+ 1
+ ],
+ "visible": true,
+ "customComponents": [],
+ "materials": [
+ {
+ "color": "66757c",
+ "emissive": "000000",
+ "emissiveIntensity": 1,
+ "metalness": 0.48,
+ "roughness": 0.56,
+ "opacity": 1,
+ "transparent": false,
+ "depthWrite": true
+ }
+ ]
+ },
+ "outdoor-fence-back-4": {
+ "name": "后侧围栏立柱 4",
+ "position": [
+ -11.6,
+ 1.1,
+ -25
+ ],
+ "rotation": [
+ 0,
+ 0,
+ 0
+ ],
+ "scale": [
+ 1,
+ 1,
+ 1
+ ],
+ "visible": true,
+ "customComponents": [],
+ "materials": [
+ {
+ "color": "66757c",
+ "emissive": "000000",
+ "emissiveIntensity": 1,
+ "metalness": 0.48,
+ "roughness": 0.56,
+ "opacity": 1,
+ "transparent": false,
+ "depthWrite": true
+ }
+ ]
+ },
+ "outdoor-fence-back-5": {
+ "name": "后侧围栏立柱 5",
+ "position": [
+ -5.8,
+ 1.1,
+ -25
+ ],
+ "rotation": [
+ 0,
+ 0,
+ 0
+ ],
+ "scale": [
+ 1,
+ 1,
+ 1
+ ],
+ "visible": true,
+ "customComponents": [],
+ "materials": [
+ {
+ "color": "66757c",
+ "emissive": "000000",
+ "emissiveIntensity": 1,
+ "metalness": 0.48,
+ "roughness": 0.56,
+ "opacity": 1,
+ "transparent": false,
+ "depthWrite": true
+ }
+ ]
+ },
+ "outdoor-fence-back-6": {
+ "name": "后侧围栏立柱 6",
+ "position": [
+ 0,
+ 1.1,
+ -25
+ ],
+ "rotation": [
+ 0,
+ 0,
+ 0
+ ],
+ "scale": [
+ 1,
+ 1,
+ 1
+ ],
+ "visible": true,
+ "customComponents": [],
+ "materials": [
+ {
+ "color": "66757c",
+ "emissive": "000000",
+ "emissiveIntensity": 1,
+ "metalness": 0.48,
+ "roughness": 0.56,
+ "opacity": 1,
+ "transparent": false,
+ "depthWrite": true
+ }
+ ]
+ },
+ "outdoor-fence-back-7": {
+ "name": "后侧围栏立柱 7",
+ "position": [
+ 5.8,
+ 1.1,
+ -25
+ ],
+ "rotation": [
+ 0,
+ 0,
+ 0
+ ],
+ "scale": [
+ 1,
+ 1,
+ 1
+ ],
+ "visible": true,
+ "customComponents": [],
+ "materials": [
+ {
+ "color": "66757c",
+ "emissive": "000000",
+ "emissiveIntensity": 1,
+ "metalness": 0.48,
+ "roughness": 0.56,
+ "opacity": 1,
+ "transparent": false,
+ "depthWrite": true
+ }
+ ]
+ },
+ "outdoor-fence-back-8": {
+ "name": "后侧围栏立柱 8",
+ "position": [
+ 11.6,
+ 1.1,
+ -25
+ ],
+ "rotation": [
+ 0,
+ 0,
+ 0
+ ],
+ "scale": [
+ 1,
+ 1,
+ 1
+ ],
+ "visible": true,
+ "customComponents": [],
+ "materials": [
+ {
+ "color": "66757c",
+ "emissive": "000000",
+ "emissiveIntensity": 1,
+ "metalness": 0.48,
+ "roughness": 0.56,
+ "opacity": 1,
+ "transparent": false,
+ "depthWrite": true
+ }
+ ]
+ },
+ "outdoor-fence-back-9": {
+ "name": "后侧围栏立柱 9",
+ "position": [
+ 17.4,
+ 1.1,
+ -25
+ ],
+ "rotation": [
+ 0,
+ 0,
+ 0
+ ],
+ "scale": [
+ 1,
+ 1,
+ 1
+ ],
+ "visible": true,
+ "customComponents": [],
+ "materials": [
+ {
+ "color": "66757c",
+ "emissive": "000000",
+ "emissiveIntensity": 1,
+ "metalness": 0.48,
+ "roughness": 0.56,
+ "opacity": 1,
+ "transparent": false,
+ "depthWrite": true
+ }
+ ]
+ },
+ "outdoor-fence-back-10": {
+ "name": "后侧围栏立柱 10",
+ "position": [
+ 23.2,
+ 1.1,
+ -25
+ ],
+ "rotation": [
+ 0,
+ 0,
+ 0
+ ],
+ "scale": [
+ 1,
+ 1,
+ 1
+ ],
+ "visible": true,
+ "customComponents": [],
+ "materials": [
+ {
+ "color": "66757c",
+ "emissive": "000000",
+ "emissiveIntensity": 1,
+ "metalness": 0.48,
+ "roughness": 0.56,
+ "opacity": 1,
+ "transparent": false,
+ "depthWrite": true
+ }
+ ]
+ },
+ "outdoor-fence-back-11": {
+ "name": "后侧围栏立柱 11",
+ "position": [
+ 29,
+ 1.1,
+ -25
+ ],
+ "rotation": [
+ 0,
+ 0,
+ 0
+ ],
+ "scale": [
+ 1,
+ 1,
+ 1
+ ],
+ "visible": true,
+ "customComponents": [],
+ "materials": [
+ {
+ "color": "66757c",
+ "emissive": "000000",
+ "emissiveIntensity": 1,
+ "metalness": 0.48,
+ "roughness": 0.56,
+ "opacity": 1,
+ "transparent": false,
+ "depthWrite": true
+ }
+ ]
+ },
+ "outdoor-fence-rail-a": {
+ "name": "后侧围栏上横杆",
+ "position": [
+ 0,
+ 1.85,
+ -25
+ ],
+ "rotation": [
+ 0,
+ 0,
+ 0
+ ],
+ "scale": [
+ 1,
+ 1,
+ 1
+ ],
+ "visible": true,
+ "customComponents": [],
+ "materials": [
+ {
+ "color": "66757c",
+ "emissive": "000000",
+ "emissiveIntensity": 1,
+ "metalness": 0.48,
+ "roughness": 0.56,
+ "opacity": 1,
+ "transparent": false,
+ "depthWrite": true
+ }
+ ]
+ },
+ "outdoor-fence-rail-b": {
+ "name": "后侧围栏下横杆",
+ "position": [
+ 0,
+ 0.75,
+ -25
+ ],
+ "rotation": [
+ 0,
+ 0,
+ 0
+ ],
+ "scale": [
+ 1,
+ 1,
+ 1
+ ],
+ "visible": true,
+ "customComponents": [],
+ "materials": [
+ {
+ "color": "66757c",
+ "emissive": "000000",
+ "emissiveIntensity": 1,
+ "metalness": 0.48,
+ "roughness": 0.56,
+ "opacity": 1,
+ "transparent": false,
+ "depthWrite": true
+ }
+ ]
+ },
+ "outdoor-scene-sign": {
+ "name": "室外实训区域标识",
+ "position": [
+ 26,
+ 2.4,
+ 11.5
+ ],
+ "rotation": [
+ 0,
+ 0,
+ 0
+ ],
+ "scale": [
+ 1,
+ 1,
+ 1
+ ],
+ "visible": true,
+ "customComponents": [],
+ "materials": [
+ {
+ "color": "536675",
+ "emissive": "000000",
+ "emissiveIntensity": 1,
+ "metalness": 0.18,
+ "roughness": 0.78,
+ "opacity": 1,
+ "transparent": false,
+ "depthWrite": true
+ }
+ ]
+ },
+ "outdoor-scene-sign-post-a": {
+ "name": "区域标识支柱 A",
+ "position": [
+ 24,
+ 1.4,
+ 11.5
+ ],
+ "rotation": [
+ 0,
+ 0,
+ 0
+ ],
+ "scale": [
+ 1,
+ 1,
+ 1
+ ],
+ "visible": true,
+ "customComponents": [],
+ "materials": [
+ {
+ "color": "66757c",
+ "emissive": "000000",
+ "emissiveIntensity": 1,
+ "metalness": 0.48,
+ "roughness": 0.56,
+ "opacity": 1,
+ "transparent": false,
+ "depthWrite": true
+ }
+ ]
+ },
+ "outdoor-scene-sign-post-b": {
+ "name": "区域标识支柱 B",
+ "position": [
+ 28,
+ 1.4,
+ 11.5
+ ],
+ "rotation": [
+ 0,
+ 0,
+ 0
+ ],
+ "scale": [
+ 1,
+ 1,
+ 1
+ ],
+ "visible": true,
+ "customComponents": [],
+ "materials": [
+ {
+ "color": "66757c",
+ "emissive": "000000",
+ "emissiveIntensity": 1,
+ "metalness": 0.48,
+ "roughness": 0.56,
+ "opacity": 1,
+ "transparent": false,
+ "depthWrite": true
+ }
+ ]
+ }
+ }
+ },
+ "runtime": {
+ "startMode": "guided",
+ "showStepPanel": true,
+ "showProgress": true,
+ "allowReplay": true,
+ "language": "zh-CN"
+ },
+ "publish": {
+ "target": "standalone-html",
+ "deployment": "offline",
+ "includeProjectData": true,
+ "includeViewportPreview": true
+ }
+}
\ No newline at end of file
diff --git a/reports/model-editor-closure-20260906/downloads/preflight-239.json b/reports/model-editor-closure-20260906/downloads/preflight-239.json
new file mode 100644
index 0000000..329568e
--- /dev/null
+++ b/reports/model-editor-closure-20260906/downloads/preflight-239.json
@@ -0,0 +1,3414 @@
+{
+ "schema": "digital-twin-editor-project",
+ "version": 4,
+ "name": "闭环验收-加载与预检-mtpjpdaj",
+ "modelId": "imported-model_mto6v5w6_09-fox",
+ "modelResource": {
+ "kind": "imported",
+ "id": "imported-model_mto6v5w6_09-fox",
+ "name": "09-Fox",
+ "assetCode": "MODEL_MTO6V5W6_09-FOX",
+ "code": "MODEL_MTO6V5W6_09-FOX",
+ "storageUri": "fsvc://unreal-tran/model/2026/09/05/3fc69ae0d435f869afca1fcd943486d1.glb",
+ "assetKey": "",
+ "resourceUrl": "",
+ "sourceName": "09-Fox.glb",
+ "sizeBytes": 162852,
+ "lastModified": 0,
+ "mimeType": "model/gltf-binary",
+ "thumbnail": "",
+ "vertices": 1728,
+ "triangles": 576,
+ "nodes": 27,
+ "meshes": 1,
+ "materials": 1,
+ "textures": 1,
+ "animations": 3,
+ "animationDurationSeconds": 3.4166667461395264,
+ "modelMetadata": {
+ "nodeCount": 27,
+ "meshCount": 1,
+ "materialCount": 1,
+ "textureCount": 1,
+ "animations": [
+ {
+ "index": 0,
+ "name": "Survey",
+ "start": 0,
+ "end": 3.4166667461395264,
+ "duration": 3.4166667461395264,
+ "trackCount": 21
+ },
+ {
+ "index": 1,
+ "name": "Walk",
+ "start": 0,
+ "end": 0.7083333134651184,
+ "duration": 0.7083333134651184,
+ "trackCount": 21
+ },
+ {
+ "index": 2,
+ "name": "Run",
+ "start": 0,
+ "end": 1.1583333015441895,
+ "duration": 1.1583333015441895,
+ "trackCount": 21
+ }
+ ],
+ "animationDurationSeconds": 3.4166667461395264
+ },
+ "nativeAnimations": [
+ {
+ "index": 0,
+ "name": "Survey",
+ "start": 0,
+ "end": 3.4166667461395264,
+ "duration": 3.4166667461395264,
+ "trackCount": 21
+ },
+ {
+ "index": 1,
+ "name": "Walk",
+ "start": 0,
+ "end": 0.7083333134651184,
+ "duration": 0.7083333134651184,
+ "trackCount": 21
+ },
+ {
+ "index": 2,
+ "name": "Run",
+ "start": 0,
+ "end": 1.1583333015441895,
+ "duration": 1.1583333015441895,
+ "trackCount": 21
+ }
+ ]
+ },
+ "meta": {
+ "description": "用于装备维修实训场景编排、编辑器回归与离线发布验证的系统内置基准工程",
+ "author": "数字孪生项目组",
+ "scene": "车间与野外装备维修实训环境",
+ "tags": [
+ "三维场景",
+ "时间轴动画",
+ "蓝图交互",
+ "实训流程"
+ ],
+ "migratedFrom": "legacy-model-document",
+ "source": "content-api-glb",
+ "sourceName": "09-Fox.glb",
+ "assetCode": "MODEL_MTO6V5W6_09-FOX",
+ "storageUri": "fsvc://unreal-tran/model/2026/09/05/3fc69ae0d435f869afca1fcd943486d1.glb"
+ },
+ "timeline": {
+ "version": 1,
+ "duration": 8,
+ "currentTime": 0,
+ "loop": true,
+ "zoom": 1,
+ "tracks": [
+ {
+ "id": "track-0-1yjfz9t",
+ "name": "模型整体位移",
+ "targetId": "interaction-b9240aaa",
+ "property": "position",
+ "color": "#ffb22e",
+ "muted": false,
+ "keyframes": [
+ {
+ "id": "key-0-0-kmfo820",
+ "time": 0,
+ "value": [
+ 0,
+ 0,
+ 0
+ ],
+ "easing": "easeInOut"
+ },
+ {
+ "id": "key-0-1-snsj2e2",
+ "time": 8,
+ "value": [
+ 0,
+ 0,
+ 0
+ ],
+ "easing": "easeInOut"
+ }
+ ]
+ }
+ ]
+ },
+ "blueprint": {
+ "version": 1,
+ "name": "挖掘机作业实训闭环",
+ "viewport": {
+ "x": 55,
+ "y": 55,
+ "zoom": 0.82
+ },
+ "nodes": [
+ {
+ "id": "bp-start",
+ "type": "trigger-click",
+ "title": "点击挖掘机",
+ "x": 40,
+ "y": 55,
+ "params": {
+ "event": "click",
+ "target": "挖掘机"
+ }
+ },
+ {
+ "id": "bp-highlight",
+ "type": "highlight",
+ "title": "高亮工作装置",
+ "x": 300,
+ "y": 55,
+ "params": {
+ "target": "铲斗",
+ "color": "#38e8ff",
+ "pulse": true
+ }
+ },
+ {
+ "id": "bp-message",
+ "type": "message",
+ "title": "显示操作提示",
+ "x": 560,
+ "y": 55,
+ "params": {
+ "title": "挖掘作业实训",
+ "text": "观察动臂、斗杆和铲斗的协同动作",
+ "level": "info"
+ }
+ },
+ {
+ "id": "bp-play",
+ "type": "play-timeline",
+ "title": "播放作业动画",
+ "x": 820,
+ "y": 55,
+ "params": {
+ "clip": "挖掘机作业实训",
+ "from": 0,
+ "speed": 1,
+ "waitForEnd": true
+ }
+ },
+ {
+ "id": "bp-wait",
+ "type": "wait",
+ "title": "动作稳定",
+ "x": 560,
+ "y": 245,
+ "params": {
+ "duration": 600
+ }
+ },
+ {
+ "id": "bp-check",
+ "type": "condition",
+ "title": "动画是否完成",
+ "x": 300,
+ "y": 245,
+ "params": {
+ "variable": "timelineComplete",
+ "operator": "==",
+ "value": true
+ }
+ },
+ {
+ "id": "bp-retry",
+ "type": "message",
+ "title": "等待动画完成",
+ "x": 820,
+ "y": 245,
+ "params": {
+ "title": "流程校验",
+ "text": "动画尚未完成,请重新执行",
+ "level": "warning"
+ }
+ },
+ {
+ "id": "bp-finish",
+ "type": "finish",
+ "title": "流程完成",
+ "x": 40,
+ "y": 245,
+ "params": {
+ "result": "success",
+ "text": "挖掘机作业实训流程完成"
+ }
+ }
+ ],
+ "edges": [
+ {
+ "id": "bp-e1",
+ "from": {
+ "node": "bp-start",
+ "port": "then"
+ },
+ "to": {
+ "node": "bp-highlight",
+ "port": "in"
+ }
+ },
+ {
+ "id": "bp-e2",
+ "from": {
+ "node": "bp-highlight",
+ "port": "then"
+ },
+ "to": {
+ "node": "bp-message",
+ "port": "in"
+ }
+ },
+ {
+ "id": "bp-e3",
+ "from": {
+ "node": "bp-message",
+ "port": "then"
+ },
+ "to": {
+ "node": "bp-play",
+ "port": "in"
+ }
+ },
+ {
+ "id": "bp-e4",
+ "from": {
+ "node": "bp-play",
+ "port": "then"
+ },
+ "to": {
+ "node": "bp-wait",
+ "port": "in"
+ }
+ },
+ {
+ "id": "bp-e5",
+ "from": {
+ "node": "bp-wait",
+ "port": "then"
+ },
+ "to": {
+ "node": "bp-check",
+ "port": "in"
+ }
+ },
+ {
+ "id": "bp-e6",
+ "from": {
+ "node": "bp-check",
+ "port": "true"
+ },
+ "to": {
+ "node": "bp-finish",
+ "port": "in"
+ }
+ },
+ {
+ "id": "bp-e7",
+ "from": {
+ "node": "bp-check",
+ "port": "false"
+ },
+ "to": {
+ "node": "bp-retry",
+ "port": "in"
+ }
+ },
+ {
+ "id": "bp-e8",
+ "from": {
+ "node": "bp-retry",
+ "port": "then"
+ },
+ "to": {
+ "node": "bp-play",
+ "port": "in"
+ }
+ }
+ ]
+ },
+ "camera": {
+ "position": [
+ 11.153569378931522,
+ 8.526809324389628,
+ 13.922975975299929
+ ],
+ "target": [
+ 0.01518877997232826,
+ 2.4006999949620704,
+ 2.266009371254541e-7
+ ]
+ },
+ "model": {
+ "objects": {
+ "equipment-root": {
+ "name": "09-Fox",
+ "nodeName": "09-Fox",
+ "nodePath": "@root",
+ "interactionId": "interaction-b9240aaa",
+ "capabilities": {
+ "selectable": true,
+ "transform": true,
+ "visible": true,
+ "material": false,
+ "animatable": true
+ },
+ "position": [
+ 0,
+ 0,
+ 0
+ ],
+ "rotation": [
+ 0,
+ 0,
+ 0
+ ],
+ "scale": [
+ 1,
+ 1,
+ 1
+ ],
+ "visible": true,
+ "customComponents": []
+ },
+ "model-imported-model_mto6v5w6_09-fox-0": {
+ "name": "",
+ "nodeName": "Group",
+ "nodePath": "0",
+ "interactionId": "interaction-02351bce",
+ "capabilities": {
+ "selectable": true,
+ "transform": true,
+ "visible": true,
+ "material": false,
+ "animatable": true
+ },
+ "position": [
+ 3.875009990661482e-8,
+ 0.007396563645075124,
+ 0.6522089834776823
+ ],
+ "rotation": [
+ 0,
+ 0,
+ 0
+ ],
+ "scale": [
+ 0.06075496488891343,
+ 0.06075496488891343,
+ 0.06075496488891343
+ ],
+ "visible": true,
+ "customComponents": []
+ },
+ "model-imported-model_mto6v5w6_09-fox-1": {
+ "name": "root",
+ "nodeName": "root",
+ "nodePath": "0/0",
+ "interactionId": "interaction-a4d35db9",
+ "capabilities": {
+ "selectable": true,
+ "transform": true,
+ "visible": true,
+ "material": false,
+ "animatable": true
+ },
+ "position": [
+ 0,
+ 0,
+ 0
+ ],
+ "rotation": [
+ 0,
+ 0,
+ 0
+ ],
+ "scale": [
+ 1,
+ 1,
+ 1
+ ],
+ "visible": true,
+ "customComponents": []
+ },
+ "model-imported-model_mto6v5w6_09-fox-2": {
+ "name": "_rootJoint",
+ "nodeName": "_rootJoint",
+ "nodePath": "0/0/0",
+ "interactionId": "interaction-4476c556",
+ "capabilities": {
+ "selectable": true,
+ "transform": true,
+ "visible": true,
+ "material": false,
+ "animatable": true
+ },
+ "position": [
+ 0,
+ 0,
+ 0
+ ],
+ "rotation": [
+ 0,
+ 0,
+ 0
+ ],
+ "scale": [
+ 1,
+ 1,
+ 1
+ ],
+ "visible": true,
+ "customComponents": []
+ },
+ "model-imported-model_mto6v5w6_09-fox-3": {
+ "name": "b_Root_00",
+ "nodeName": "b_Root_00",
+ "nodePath": "0/0/0/0",
+ "interactionId": "interaction-ffc45d11",
+ "capabilities": {
+ "selectable": true,
+ "transform": true,
+ "visible": true,
+ "material": false,
+ "animatable": true
+ },
+ "position": [
+ 0,
+ 0,
+ 0
+ ],
+ "rotation": [
+ -1.5708000357176293,
+ 0,
+ 0
+ ],
+ "scale": [
+ 1,
+ 1,
+ 1
+ ],
+ "visible": true,
+ "customComponents": []
+ },
+ "model-imported-model_mto6v5w6_09-fox-4": {
+ "name": "b_Hip_01",
+ "nodeName": "b_Hip_01",
+ "nodePath": "0/0/0/0/0",
+ "interactionId": "interaction-7a75e39e",
+ "capabilities": {
+ "selectable": true,
+ "transform": true,
+ "visible": true,
+ "material": false,
+ "animatable": true
+ },
+ "position": [
+ 0.000001241521920852674,
+ 22.061977333358605,
+ 34.62373020761327
+ ],
+ "rotation": [
+ 0.44722050284761794,
+ -1.5707951637404125,
+ 0
+ ],
+ "scale": [
+ 1,
+ 1,
+ 1
+ ],
+ "visible": true,
+ "customComponents": []
+ },
+ "model-imported-model_mto6v5w6_09-fox-5": {
+ "name": "b_Spine01_02",
+ "nodeName": "b_Spine01_02",
+ "nodePath": "0/0/0/0/0/0",
+ "interactionId": "interaction-663ea469",
+ "capabilities": {
+ "selectable": true,
+ "transform": true,
+ "visible": true,
+ "material": false,
+ "animatable": true
+ },
+ "position": [
+ 12.850601196289062,
+ 0,
+ 0
+ ],
+ "rotation": [
+ -3.357310257291308e-7,
+ -3.316574913360067e-8,
+ -1.2102038452161539
+ ],
+ "scale": [
+ 1,
+ 1,
+ 1
+ ],
+ "visible": true,
+ "customComponents": []
+ },
+ "model-imported-model_mto6v5w6_09-fox-6": {
+ "name": "b_Spine02_03",
+ "nodeName": "b_Spine02_03",
+ "nodePath": "0/0/0/0/0/0/0",
+ "interactionId": "interaction-46867126",
+ "capabilities": {
+ "selectable": true,
+ "transform": true,
+ "visible": true,
+ "material": false,
+ "animatable": true
+ },
+ "position": [
+ 21.65575408935547,
+ -0.000118255615234375,
+ 0
+ ],
+ "rotation": [
+ 8.772651057591018e-8,
+ 3.3355428835535634e-8,
+ 0.3633406824515624
+ ],
+ "scale": [
+ 1,
+ 1,
+ 1
+ ],
+ "visible": true,
+ "customComponents": []
+ },
+ "model-imported-model_mto6v5w6_09-fox-7": {
+ "name": "b_Neck_04",
+ "nodeName": "b_Neck_04",
+ "nodePath": "0/0/0/0/0/0/0/0",
+ "interactionId": "interaction-27148d81",
+ "capabilities": {
+ "selectable": true,
+ "transform": true,
+ "visible": true,
+ "material": false,
+ "animatable": true
+ },
+ "position": [
+ 25.64914321899414,
+ 0,
+ 0
+ ],
+ "rotation": [
+ 6.423807629998062e-15,
+ 1.9993472402144555e-16,
+ 0.031113979193075042
+ ],
+ "scale": [
+ 1,
+ 1,
+ 1
+ ],
+ "visible": true,
+ "customComponents": []
+ },
+ "model-imported-model_mto6v5w6_09-fox-8": {
+ "name": "b_Head_05",
+ "nodeName": "b_Head_05",
+ "nodePath": "0/0/0/0/0/0/0/0/0",
+ "interactionId": "interaction-b24eef2e",
+ "capabilities": {
+ "selectable": true,
+ "transform": true,
+ "visible": true,
+ "material": false,
+ "animatable": true
+ },
+ "position": [
+ 13.376960754394531,
+ 0,
+ 0
+ ],
+ "rotation": [
+ 3.253825627358364e-8,
+ -1.577320798309453e-8,
+ -0.451380500897834
+ ],
+ "scale": [
+ 1,
+ 1,
+ 1
+ ],
+ "visible": true,
+ "customComponents": []
+ },
+ "model-imported-model_mto6v5w6_09-fox-9": {
+ "name": "b_RightUpperArm_06",
+ "nodeName": "b_RightUpperArm_06",
+ "nodePath": "0/0/0/0/0/0/0/1",
+ "interactionId": "interaction-26148bee",
+ "capabilities": {
+ "selectable": true,
+ "transform": true,
+ "visible": true,
+ "material": false,
+ "animatable": true
+ },
+ "position": [
+ 18.677913665771484,
+ -4.297340393066406,
+ 6.9675750732421875
+ ],
+ "rotation": [
+ -0.035644184619881884,
+ -0.047001863061712265,
+ -1.2972005346348685
+ ],
+ "scale": [
+ 1,
+ 1,
+ 1
+ ],
+ "visible": true,
+ "customComponents": []
+ },
+ "model-imported-model_mto6v5w6_09-fox-10": {
+ "name": "b_RightForeArm_07",
+ "nodeName": "b_RightForeArm_07",
+ "nodePath": "0/0/0/0/0/0/0/1/0",
+ "interactionId": "interaction-36896559",
+ "capabilities": {
+ "selectable": true,
+ "transform": true,
+ "visible": true,
+ "material": false,
+ "animatable": true
+ },
+ "position": [
+ 23.04512596130371,
+ 0,
+ 0
+ ],
+ "rotation": [
+ 2.1355818997049898e-8,
+ 7.233656206674673e-8,
+ 1.2561843405841544
+ ],
+ "scale": [
+ 1,
+ 1,
+ 1
+ ],
+ "visible": true,
+ "customComponents": []
+ },
+ "model-imported-model_mto6v5w6_09-fox-11": {
+ "name": "b_RightHand_08",
+ "nodeName": "b_RightHand_08",
+ "nodePath": "0/0/0/0/0/0/0/1/0/0",
+ "interactionId": "interaction-57a124f6",
+ "capabilities": {
+ "selectable": true,
+ "transform": true,
+ "visible": true,
+ "material": false,
+ "animatable": true
+ },
+ "position": [
+ 19.350055694580078,
+ -0.14598655700683594,
+ 0
+ ],
+ "rotation": [
+ 0.005245101551986586,
+ 0.05200705984217871,
+ -0.267165337534188
+ ],
+ "scale": [
+ 1,
+ 1,
+ 1
+ ],
+ "visible": true,
+ "customComponents": []
+ },
+ "model-imported-model_mto6v5w6_09-fox-12": {
+ "name": "b_LeftUpperArm_09",
+ "nodeName": "b_LeftUpperArm_09",
+ "nodePath": "0/0/0/0/0/0/0/2",
+ "interactionId": "interaction-25148a5b",
+ "capabilities": {
+ "selectable": true,
+ "transform": true,
+ "visible": true,
+ "material": false,
+ "animatable": true
+ },
+ "position": [
+ 18.67791748046875,
+ -4.297344207763672,
+ -6.967987060546875
+ ],
+ "rotation": [
+ -0.00020118104561539401,
+ 0.0009350561550523671,
+ -1.5148902559141275
+ ],
+ "scale": [
+ 1,
+ 1,
+ 1
+ ],
+ "visible": true,
+ "customComponents": []
+ },
+ "model-imported-model_mto6v5w6_09-fox-13": {
+ "name": "b_LeftForeArm_010",
+ "nodeName": "b_LeftForeArm_010",
+ "nodePath": "0/0/0/0/0/0/0/2/0",
+ "interactionId": "interaction-5c502ac4",
+ "capabilities": {
+ "selectable": true,
+ "transform": true,
+ "visible": true,
+ "material": false,
+ "animatable": true
+ },
+ "position": [
+ 23.045124053955078,
+ 0,
+ 0
+ ],
+ "rotation": [
+ 2.5220861991717696e-8,
+ 7.109903232964989e-8,
+ 1.2307106255258764
+ ],
+ "scale": [
+ 1,
+ 1,
+ 1
+ ],
+ "visible": true,
+ "customComponents": []
+ },
+ "model-imported-model_mto6v5w6_09-fox-14": {
+ "name": "b_LeftHand_011",
+ "nodeName": "b_LeftHand_011",
+ "nodePath": "0/0/0/0/0/0/0/2/0/0",
+ "interactionId": "interaction-192b4cd3",
+ "capabilities": {
+ "selectable": true,
+ "transform": true,
+ "visible": true,
+ "material": false,
+ "animatable": true
+ },
+ "position": [
+ 19.350051879882812,
+ -0.14599037170410156,
+ 0
+ ],
+ "rotation": [
+ 0.004279557751527162,
+ -0.03344276494440009,
+ -1.5985291598689726
+ ],
+ "scale": [
+ 1,
+ 1,
+ 1
+ ],
+ "visible": true,
+ "customComponents": []
+ },
+ "model-imported-model_mto6v5w6_09-fox-15": {
+ "name": "b_Tail01_012",
+ "nodeName": "b_Tail01_012",
+ "nodePath": "0/0/0/0/0/1",
+ "interactionId": "interaction-653ea2d6",
+ "capabilities": {
+ "selectable": true,
+ "transform": true,
+ "visible": true,
+ "material": false,
+ "animatable": true
+ },
+ "position": [
+ 4.2603759765625,
+ 15.958770751953125,
+ 0
+ ],
+ "rotation": [
+ -5.364745216052604e-7,
+ 3.424797608447095e-7,
+ 1.9941304874941974
+ ],
+ "scale": [
+ 1,
+ 1,
+ 1
+ ],
+ "visible": true,
+ "customComponents": []
+ },
+ "model-imported-model_mto6v5w6_09-fox-16": {
+ "name": "b_Tail02_013",
+ "nodeName": "b_Tail02_013",
+ "nodePath": "0/0/0/0/0/1/0",
+ "interactionId": "interaction-6c4d3691",
+ "capabilities": {
+ "selectable": true,
+ "transform": true,
+ "visible": true,
+ "material": false,
+ "animatable": true
+ },
+ "position": [
+ 12.411918640136719,
+ 0,
+ 0
+ ],
+ "rotation": [
+ -6.738507958375962e-23,
+ -5.2439168243310865e-23,
+ 0.3128105211171357
+ ],
+ "scale": [
+ 1,
+ 1,
+ 1
+ ],
+ "visible": true,
+ "customComponents": []
+ },
+ "model-imported-model_mto6v5w6_09-fox-17": {
+ "name": "b_Tail03_014",
+ "nodeName": "b_Tail03_014",
+ "nodePath": "0/0/0/0/0/1/0/0",
+ "interactionId": "interaction-e93e191e",
+ "capabilities": {
+ "selectable": true,
+ "transform": true,
+ "visible": true,
+ "material": false,
+ "animatable": true
+ },
+ "position": [
+ 24.24032211303711,
+ 0,
+ 0
+ ],
+ "rotation": [
+ -5.7486339033124146e-24,
+ 1.1669178332583734e-22,
+ -0.012937892408322462
+ ],
+ "scale": [
+ 1,
+ 1,
+ 1
+ ],
+ "visible": true,
+ "customComponents": []
+ },
+ "model-imported-model_mto6v5w6_09-fox-18": {
+ "name": "b_LeftLeg01_015",
+ "nodeName": "b_LeftLeg01_015",
+ "nodePath": "0/0/0/0/0/2",
+ "interactionId": "interaction-643ea143",
+ "capabilities": {
+ "selectable": true,
+ "transform": true,
+ "visible": true,
+ "material": false,
+ "animatable": true
+ },
+ "position": [
+ 4.813770294189453,
+ 5.154018402099609,
+ -6.968006134033203
+ ],
+ "rotation": [
+ 0.1223493637902729,
+ -0.09724770930295147,
+ 2.9630295165414986
+ ],
+ "scale": [
+ 1,
+ 1,
+ 1
+ ],
+ "visible": true,
+ "customComponents": []
+ },
+ "model-imported-model_mto6v5w6_09-fox-19": {
+ "name": "b_LeftLeg02_016",
+ "nodeName": "b_LeftLeg02_016",
+ "nodePath": "0/0/0/0/0/2/0",
+ "interactionId": "interaction-7175f8dc",
+ "capabilities": {
+ "selectable": true,
+ "transform": true,
+ "visible": true,
+ "material": false,
+ "animatable": true
+ },
+ "position": [
+ 18.944175720214844,
+ 0,
+ 0
+ ],
+ "rotation": [
+ 1.2545483692289454e-8,
+ 3.728483680761731e-8,
+ -0.522293355530293
+ ],
+ "scale": [
+ 1,
+ 1,
+ 1
+ ],
+ "visible": true,
+ "customComponents": []
+ },
+ "model-imported-model_mto6v5w6_09-fox-20": {
+ "name": "b_LeftFoot01_017",
+ "nodeName": "b_LeftFoot01_017",
+ "nodePath": "0/0/0/0/0/2/0/0",
+ "interactionId": "interaction-f5ed353b",
+ "capabilities": {
+ "selectable": true,
+ "transform": true,
+ "visible": true,
+ "material": false,
+ "animatable": true
+ },
+ "position": [
+ 17.942811965942383,
+ 0,
+ 0
+ ],
+ "rotation": [
+ -0.03128474386729028,
+ -0.15471927051895154,
+ 0.7774909055508243
+ ],
+ "scale": [
+ 1,
+ 1,
+ 1
+ ],
+ "visible": true,
+ "customComponents": []
+ },
+ "model-imported-model_mto6v5w6_09-fox-21": {
+ "name": "b_LeftFoot02_018",
+ "nodeName": "b_LeftFoot02_018",
+ "nodePath": "0/0/0/0/0/2/0/0/0",
+ "interactionId": "interaction-1bbe01a4",
+ "capabilities": {
+ "selectable": true,
+ "transform": true,
+ "visible": true,
+ "material": false,
+ "animatable": true
+ },
+ "position": [
+ 15.779938697814941,
+ 0,
+ 0
+ ],
+ "rotation": [
+ 0,
+ 0,
+ 1.158241570830627
+ ],
+ "scale": [
+ 1,
+ 1,
+ 1
+ ],
+ "visible": true,
+ "customComponents": []
+ },
+ "model-imported-model_mto6v5w6_09-fox-22": {
+ "name": "b_RightLeg01_019",
+ "nodeName": "b_RightLeg01_019",
+ "nodePath": "0/0/0/0/0/3",
+ "interactionId": "interaction-633e9fb0",
+ "capabilities": {
+ "selectable": true,
+ "transform": true,
+ "visible": true,
+ "material": false,
+ "animatable": true
+ },
+ "position": [
+ 4.813777923583984,
+ 5.154026031494141,
+ 6.967563629150391
+ ],
+ "rotation": [
+ -0.10137208185544387,
+ 0.05534659522395753,
+ -2.3613690068554236
+ ],
+ "scale": [
+ 1,
+ 1,
+ 1
+ ],
+ "visible": true,
+ "customComponents": []
+ },
+ "model-imported-model_mto6v5w6_09-fox-23": {
+ "name": "b_RightLeg02_020",
+ "nodeName": "b_RightLeg02_020",
+ "nodePath": "0/0/0/0/0/3/0",
+ "interactionId": "interaction-95ff89c7",
+ "capabilities": {
+ "selectable": true,
+ "transform": true,
+ "visible": true,
+ "material": false,
+ "animatable": true
+ },
+ "position": [
+ 18.944183349609375,
+ 0,
+ 0
+ ],
+ "rotation": [
+ 2.7890192431350882e-8,
+ 1.4750479199499402e-8,
+ -1.0710323806538369
+ ],
+ "scale": [
+ 1,
+ 1,
+ 1
+ ],
+ "visible": true,
+ "customComponents": []
+ },
+ "model-imported-model_mto6v5w6_09-fox-24": {
+ "name": "b_RightFoot01_021",
+ "nodeName": "b_RightFoot01_021",
+ "nodePath": "0/0/0/0/0/3/0/0",
+ "interactionId": "interaction-a157d498",
+ "capabilities": {
+ "selectable": true,
+ "transform": true,
+ "visible": true,
+ "material": false,
+ "animatable": true
+ },
+ "position": [
+ 17.94281005859375,
+ 0,
+ 0
+ ],
+ "rotation": [
+ -0.14401413968163027,
+ 0.2814735368347316,
+ 1.4591240233677443
+ ],
+ "scale": [
+ 1,
+ 1,
+ 1
+ ],
+ "visible": true,
+ "customComponents": []
+ },
+ "model-imported-model_mto6v5w6_09-fox-25": {
+ "name": "b_RightFoot02_022",
+ "nodeName": "b_RightFoot02_022",
+ "nodePath": "0/0/0/0/0/3/0/0/0",
+ "interactionId": "interaction-ebd38e3f",
+ "capabilities": {
+ "selectable": true,
+ "transform": true,
+ "visible": true,
+ "material": false,
+ "animatable": true
+ },
+ "position": [
+ 15.779935836791992,
+ 0,
+ 0
+ ],
+ "rotation": [
+ 0,
+ 0,
+ 1.158241570830627
+ ],
+ "scale": [
+ 1,
+ 1,
+ 1
+ ],
+ "visible": true,
+ "customComponents": []
+ },
+ "model-imported-model_mto6v5w6_09-fox-26": {
+ "name": "fox",
+ "nodeName": "fox",
+ "nodePath": "0/1",
+ "interactionId": "interaction-a3d35c26",
+ "capabilities": {
+ "selectable": true,
+ "transform": true,
+ "visible": true,
+ "material": true,
+ "animatable": true
+ },
+ "position": [
+ 0.25,
+ 0,
+ 0
+ ],
+ "rotation": [
+ 0,
+ 0,
+ 0
+ ],
+ "scale": [
+ 1,
+ 1,
+ 1
+ ],
+ "visible": true,
+ "customComponents": [],
+ "materials": [
+ {
+ "color": "ffffff",
+ "emissive": "000000",
+ "emissiveIntensity": 1,
+ "metalness": 0,
+ "roughness": 0.3,
+ "opacity": 1,
+ "transparent": false,
+ "depthWrite": true
+ }
+ ]
+ }
+ },
+ "selectionScope": "part",
+ "nativeAnimation": {
+ "clipIndex": 2,
+ "clipName": "Run"
+ }
+ },
+ "environment": {
+ "mode": "placeholder",
+ "environmentType": "outdoor",
+ "sourceName": "内置野外综合训练环境",
+ "sizeBytes": 0,
+ "assetCode": "",
+ "storageUri": "",
+ "resourceUrl": "",
+ "objects": {
+ "outdoor-scene": {
+ "name": "野外综合训练环境",
+ "position": [
+ 0,
+ 0,
+ 0
+ ],
+ "rotation": [
+ 0,
+ 0,
+ 0
+ ],
+ "scale": [
+ 1,
+ 1,
+ 1
+ ],
+ "visible": true,
+ "customComponents": []
+ },
+ "outdoor-site": {
+ "name": "场地与道路",
+ "position": [
+ 0,
+ 0,
+ 0
+ ],
+ "rotation": [
+ 0,
+ 0,
+ 0
+ ],
+ "scale": [
+ 1,
+ 1,
+ 1
+ ],
+ "visible": true,
+ "customComponents": []
+ },
+ "outdoor-ground": {
+ "name": "室外训练场地",
+ "position": [
+ 0,
+ -0.065,
+ 0
+ ],
+ "rotation": [
+ -1.5707963267948966,
+ 0,
+ 0
+ ],
+ "scale": [
+ 1,
+ 1,
+ 1
+ ],
+ "visible": true,
+ "customComponents": [],
+ "materials": [
+ {
+ "color": "263529",
+ "emissive": "000000",
+ "emissiveIntensity": 1,
+ "metalness": 0,
+ "roughness": 0.98,
+ "opacity": 1,
+ "transparent": false,
+ "depthWrite": true
+ }
+ ]
+ },
+ "outdoor-apron": {
+ "name": "装备操作坪",
+ "position": [
+ 0,
+ -0.045,
+ 0
+ ],
+ "rotation": [
+ -1.5707963267948966,
+ 0,
+ 0
+ ],
+ "scale": [
+ 1,
+ 1,
+ 1
+ ],
+ "visible": true,
+ "customComponents": [],
+ "materials": [
+ {
+ "color": "39444a",
+ "emissive": "000000",
+ "emissiveIntensity": 1,
+ "metalness": 0.03,
+ "roughness": 0.92,
+ "opacity": 1,
+ "transparent": false,
+ "depthWrite": true
+ }
+ ]
+ },
+ "outdoor-road": {
+ "name": "保障通道",
+ "position": [
+ 0,
+ -0.035,
+ 17
+ ],
+ "rotation": [
+ -1.5707963267948966,
+ 0,
+ 0
+ ],
+ "scale": [
+ 1,
+ 1,
+ 1
+ ],
+ "visible": true,
+ "customComponents": [],
+ "materials": [
+ {
+ "color": "202a31",
+ "emissive": "000000",
+ "emissiveIntensity": 1,
+ "metalness": 0.02,
+ "roughness": 0.95,
+ "opacity": 1,
+ "transparent": false,
+ "depthWrite": true
+ }
+ ]
+ },
+ "outdoor-road-line-1": {
+ "name": "道路中心线 1",
+ "position": [
+ -28.699999999999996,
+ 0.01,
+ 17
+ ],
+ "rotation": [
+ 0,
+ 0,
+ 0
+ ],
+ "scale": [
+ 1,
+ 1,
+ 1
+ ],
+ "visible": true,
+ "customComponents": [],
+ "materials": [
+ {
+ "color": "e5c64c",
+ "emissive": "302600",
+ "emissiveIntensity": 0.18,
+ "metalness": 0.08,
+ "roughness": 0.72,
+ "opacity": 1,
+ "transparent": false,
+ "depthWrite": true
+ }
+ ]
+ },
+ "outdoor-road-line-2": {
+ "name": "道路中心线 2",
+ "position": [
+ -24.599999999999998,
+ 0.01,
+ 17
+ ],
+ "rotation": [
+ 0,
+ 0,
+ 0
+ ],
+ "scale": [
+ 1,
+ 1,
+ 1
+ ],
+ "visible": true,
+ "customComponents": [],
+ "materials": [
+ {
+ "color": "e5c64c",
+ "emissive": "302600",
+ "emissiveIntensity": 0.18,
+ "metalness": 0.08,
+ "roughness": 0.72,
+ "opacity": 1,
+ "transparent": false,
+ "depthWrite": true
+ }
+ ]
+ },
+ "outdoor-road-line-3": {
+ "name": "道路中心线 3",
+ "position": [
+ -20.5,
+ 0.01,
+ 17
+ ],
+ "rotation": [
+ 0,
+ 0,
+ 0
+ ],
+ "scale": [
+ 1,
+ 1,
+ 1
+ ],
+ "visible": true,
+ "customComponents": [],
+ "materials": [
+ {
+ "color": "e5c64c",
+ "emissive": "302600",
+ "emissiveIntensity": 0.18,
+ "metalness": 0.08,
+ "roughness": 0.72,
+ "opacity": 1,
+ "transparent": false,
+ "depthWrite": true
+ }
+ ]
+ },
+ "outdoor-road-line-4": {
+ "name": "道路中心线 4",
+ "position": [
+ -16.4,
+ 0.01,
+ 17
+ ],
+ "rotation": [
+ 0,
+ 0,
+ 0
+ ],
+ "scale": [
+ 1,
+ 1,
+ 1
+ ],
+ "visible": true,
+ "customComponents": [],
+ "materials": [
+ {
+ "color": "e5c64c",
+ "emissive": "302600",
+ "emissiveIntensity": 0.18,
+ "metalness": 0.08,
+ "roughness": 0.72,
+ "opacity": 1,
+ "transparent": false,
+ "depthWrite": true
+ }
+ ]
+ },
+ "outdoor-road-line-5": {
+ "name": "道路中心线 5",
+ "position": [
+ -12.299999999999999,
+ 0.01,
+ 17
+ ],
+ "rotation": [
+ 0,
+ 0,
+ 0
+ ],
+ "scale": [
+ 1,
+ 1,
+ 1
+ ],
+ "visible": true,
+ "customComponents": [],
+ "materials": [
+ {
+ "color": "e5c64c",
+ "emissive": "302600",
+ "emissiveIntensity": 0.18,
+ "metalness": 0.08,
+ "roughness": 0.72,
+ "opacity": 1,
+ "transparent": false,
+ "depthWrite": true
+ }
+ ]
+ },
+ "outdoor-road-line-6": {
+ "name": "道路中心线 6",
+ "position": [
+ -8.2,
+ 0.01,
+ 17
+ ],
+ "rotation": [
+ 0,
+ 0,
+ 0
+ ],
+ "scale": [
+ 1,
+ 1,
+ 1
+ ],
+ "visible": true,
+ "customComponents": [],
+ "materials": [
+ {
+ "color": "e5c64c",
+ "emissive": "302600",
+ "emissiveIntensity": 0.18,
+ "metalness": 0.08,
+ "roughness": 0.72,
+ "opacity": 1,
+ "transparent": false,
+ "depthWrite": true
+ }
+ ]
+ },
+ "outdoor-road-line-7": {
+ "name": "道路中心线 7",
+ "position": [
+ -4.1,
+ 0.01,
+ 17
+ ],
+ "rotation": [
+ 0,
+ 0,
+ 0
+ ],
+ "scale": [
+ 1,
+ 1,
+ 1
+ ],
+ "visible": true,
+ "customComponents": [],
+ "materials": [
+ {
+ "color": "e5c64c",
+ "emissive": "302600",
+ "emissiveIntensity": 0.18,
+ "metalness": 0.08,
+ "roughness": 0.72,
+ "opacity": 1,
+ "transparent": false,
+ "depthWrite": true
+ }
+ ]
+ },
+ "outdoor-road-line-8": {
+ "name": "道路中心线 8",
+ "position": [
+ 0,
+ 0.01,
+ 17
+ ],
+ "rotation": [
+ 0,
+ 0,
+ 0
+ ],
+ "scale": [
+ 1,
+ 1,
+ 1
+ ],
+ "visible": true,
+ "customComponents": [],
+ "materials": [
+ {
+ "color": "e5c64c",
+ "emissive": "302600",
+ "emissiveIntensity": 0.18,
+ "metalness": 0.08,
+ "roughness": 0.72,
+ "opacity": 1,
+ "transparent": false,
+ "depthWrite": true
+ }
+ ]
+ },
+ "outdoor-road-line-9": {
+ "name": "道路中心线 9",
+ "position": [
+ 4.1,
+ 0.01,
+ 17
+ ],
+ "rotation": [
+ 0,
+ 0,
+ 0
+ ],
+ "scale": [
+ 1,
+ 1,
+ 1
+ ],
+ "visible": true,
+ "customComponents": [],
+ "materials": [
+ {
+ "color": "e5c64c",
+ "emissive": "302600",
+ "emissiveIntensity": 0.18,
+ "metalness": 0.08,
+ "roughness": 0.72,
+ "opacity": 1,
+ "transparent": false,
+ "depthWrite": true
+ }
+ ]
+ },
+ "outdoor-road-line-10": {
+ "name": "道路中心线 10",
+ "position": [
+ 8.2,
+ 0.01,
+ 17
+ ],
+ "rotation": [
+ 0,
+ 0,
+ 0
+ ],
+ "scale": [
+ 1,
+ 1,
+ 1
+ ],
+ "visible": true,
+ "customComponents": [],
+ "materials": [
+ {
+ "color": "e5c64c",
+ "emissive": "302600",
+ "emissiveIntensity": 0.18,
+ "metalness": 0.08,
+ "roughness": 0.72,
+ "opacity": 1,
+ "transparent": false,
+ "depthWrite": true
+ }
+ ]
+ },
+ "outdoor-road-line-11": {
+ "name": "道路中心线 11",
+ "position": [
+ 12.299999999999999,
+ 0.01,
+ 17
+ ],
+ "rotation": [
+ 0,
+ 0,
+ 0
+ ],
+ "scale": [
+ 1,
+ 1,
+ 1
+ ],
+ "visible": true,
+ "customComponents": [],
+ "materials": [
+ {
+ "color": "e5c64c",
+ "emissive": "302600",
+ "emissiveIntensity": 0.18,
+ "metalness": 0.08,
+ "roughness": 0.72,
+ "opacity": 1,
+ "transparent": false,
+ "depthWrite": true
+ }
+ ]
+ },
+ "outdoor-road-line-12": {
+ "name": "道路中心线 12",
+ "position": [
+ 16.4,
+ 0.01,
+ 17
+ ],
+ "rotation": [
+ 0,
+ 0,
+ 0
+ ],
+ "scale": [
+ 1,
+ 1,
+ 1
+ ],
+ "visible": true,
+ "customComponents": [],
+ "materials": [
+ {
+ "color": "e5c64c",
+ "emissive": "302600",
+ "emissiveIntensity": 0.18,
+ "metalness": 0.08,
+ "roughness": 0.72,
+ "opacity": 1,
+ "transparent": false,
+ "depthWrite": true
+ }
+ ]
+ },
+ "outdoor-road-line-13": {
+ "name": "道路中心线 13",
+ "position": [
+ 20.5,
+ 0.01,
+ 17
+ ],
+ "rotation": [
+ 0,
+ 0,
+ 0
+ ],
+ "scale": [
+ 1,
+ 1,
+ 1
+ ],
+ "visible": true,
+ "customComponents": [],
+ "materials": [
+ {
+ "color": "e5c64c",
+ "emissive": "302600",
+ "emissiveIntensity": 0.18,
+ "metalness": 0.08,
+ "roughness": 0.72,
+ "opacity": 1,
+ "transparent": false,
+ "depthWrite": true
+ }
+ ]
+ },
+ "outdoor-road-line-14": {
+ "name": "道路中心线 14",
+ "position": [
+ 24.599999999999998,
+ 0.01,
+ 17
+ ],
+ "rotation": [
+ 0,
+ 0,
+ 0
+ ],
+ "scale": [
+ 1,
+ 1,
+ 1
+ ],
+ "visible": true,
+ "customComponents": [],
+ "materials": [
+ {
+ "color": "e5c64c",
+ "emissive": "302600",
+ "emissiveIntensity": 0.18,
+ "metalness": 0.08,
+ "roughness": 0.72,
+ "opacity": 1,
+ "transparent": false,
+ "depthWrite": true
+ }
+ ]
+ },
+ "outdoor-road-line-15": {
+ "name": "道路中心线 15",
+ "position": [
+ 28.699999999999996,
+ 0.01,
+ 17
+ ],
+ "rotation": [
+ 0,
+ 0,
+ 0
+ ],
+ "scale": [
+ 1,
+ 1,
+ 1
+ ],
+ "visible": true,
+ "customComponents": [],
+ "materials": [
+ {
+ "color": "e5c64c",
+ "emissive": "302600",
+ "emissiveIntensity": 0.18,
+ "metalness": 0.08,
+ "roughness": 0.72,
+ "opacity": 1,
+ "transparent": false,
+ "depthWrite": true
+ }
+ ]
+ },
+ "outdoor-safety--17.6--11": {
+ "name": "操作区安全标线",
+ "position": [
+ -17.6,
+ 0.012,
+ -11
+ ],
+ "rotation": [
+ 0,
+ 0.5654866776461628,
+ 0
+ ],
+ "scale": [
+ 1,
+ 1,
+ 1
+ ],
+ "visible": true,
+ "customComponents": [],
+ "materials": [
+ {
+ "color": "e5c64c",
+ "emissive": "302600",
+ "emissiveIntensity": 0.18,
+ "metalness": 0.08,
+ "roughness": 0.72,
+ "opacity": 1,
+ "transparent": false,
+ "depthWrite": true
+ }
+ ]
+ },
+ "outdoor-safety--17.6--8.4": {
+ "name": "操作区安全标线",
+ "position": [
+ -17.6,
+ 0.012,
+ -8.4
+ ],
+ "rotation": [
+ 0,
+ 0.5654866776461628,
+ 0
+ ],
+ "scale": [
+ 1,
+ 1,
+ 1
+ ],
+ "visible": true,
+ "customComponents": [],
+ "materials": [
+ {
+ "color": "e5c64c",
+ "emissive": "302600",
+ "emissiveIntensity": 0.18,
+ "metalness": 0.08,
+ "roughness": 0.72,
+ "opacity": 1,
+ "transparent": false,
+ "depthWrite": true
+ }
+ ]
+ },
+ "outdoor-safety--17.6--5.800000000000001": {
+ "name": "操作区安全标线",
+ "position": [
+ -17.6,
+ 0.012,
+ -5.800000000000001
+ ],
+ "rotation": [
+ 0,
+ 0.5654866776461628,
+ 0
+ ],
+ "scale": [
+ 1,
+ 1,
+ 1
+ ],
+ "visible": true,
+ "customComponents": [],
+ "materials": [
+ {
+ "color": "e5c64c",
+ "emissive": "302600",
+ "emissiveIntensity": 0.18,
+ "metalness": 0.08,
+ "roughness": 0.72,
+ "opacity": 1,
+ "transparent": false,
+ "depthWrite": true
+ }
+ ]
+ },
+ "outdoor-safety--17.6--3.2000000000000006": {
+ "name": "操作区安全标线",
+ "position": [
+ -17.6,
+ 0.012,
+ -3.2000000000000006
+ ],
+ "rotation": [
+ 0,
+ 0.5654866776461628,
+ 0
+ ],
+ "scale": [
+ 1,
+ 1,
+ 1
+ ],
+ "visible": true,
+ "customComponents": [],
+ "materials": [
+ {
+ "color": "e5c64c",
+ "emissive": "302600",
+ "emissiveIntensity": 0.18,
+ "metalness": 0.08,
+ "roughness": 0.72,
+ "opacity": 1,
+ "transparent": false,
+ "depthWrite": true
+ }
+ ]
+ },
+ "outdoor-safety--17.6--0.6000000000000005": {
+ "name": "操作区安全标线",
+ "position": [
+ -17.6,
+ 0.012,
+ -0.6000000000000005
+ ],
+ "rotation": [
+ 0,
+ 0.5654866776461628,
+ 0
+ ],
+ "scale": [
+ 1,
+ 1,
+ 1
+ ],
+ "visible": true,
+ "customComponents": [],
+ "materials": [
+ {
+ "color": "e5c64c",
+ "emissive": "302600",
+ "emissiveIntensity": 0.18,
+ "metalness": 0.08,
+ "roughness": 0.72,
+ "opacity": 1,
+ "transparent": false,
+ "depthWrite": true
+ }
+ ]
+ },
+ "outdoor-safety--17.6-1.9999999999999996": {
+ "name": "操作区安全标线",
+ "position": [
+ -17.6,
+ 0.012,
+ 1.9999999999999996
+ ],
+ "rotation": [
+ 0,
+ 0.5654866776461628,
+ 0
+ ],
+ "scale": [
+ 1,
+ 1,
+ 1
+ ],
+ "visible": true,
+ "customComponents": [],
+ "materials": [
+ {
+ "color": "e5c64c",
+ "emissive": "302600",
+ "emissiveIntensity": 0.18,
+ "metalness": 0.08,
+ "roughness": 0.72,
+ "opacity": 1,
+ "transparent": false,
+ "depthWrite": true
+ }
+ ]
+ },
+ "outdoor-safety--17.6-4.6": {
+ "name": "操作区安全标线",
+ "position": [
+ -17.6,
+ 0.012,
+ 4.6
+ ],
+ "rotation": [
+ 0,
+ 0.5654866776461628,
+ 0
+ ],
+ "scale": [
+ 1,
+ 1,
+ 1
+ ],
+ "visible": true,
+ "customComponents": [],
+ "materials": [
+ {
+ "color": "e5c64c",
+ "emissive": "302600",
+ "emissiveIntensity": 0.18,
+ "metalness": 0.08,
+ "roughness": 0.72,
+ "opacity": 1,
+ "transparent": false,
+ "depthWrite": true
+ }
+ ]
+ },
+ "outdoor-safety--17.6-7.199999999999999": {
+ "name": "操作区安全标线",
+ "position": [
+ -17.6,
+ 0.012,
+ 7.199999999999999
+ ],
+ "rotation": [
+ 0,
+ 0.5654866776461628,
+ 0
+ ],
+ "scale": [
+ 1,
+ 1,
+ 1
+ ],
+ "visible": true,
+ "customComponents": [],
+ "materials": [
+ {
+ "color": "e5c64c",
+ "emissive": "302600",
+ "emissiveIntensity": 0.18,
+ "metalness": 0.08,
+ "roughness": 0.72,
+ "opacity": 1,
+ "transparent": false,
+ "depthWrite": true
+ }
+ ]
+ },
+ "outdoor-safety--17.6-9.799999999999999": {
+ "name": "操作区安全标线",
+ "position": [
+ -17.6,
+ 0.012,
+ 9.799999999999999
+ ],
+ "rotation": [
+ 0,
+ 0.5654866776461628,
+ 0
+ ],
+ "scale": [
+ 1,
+ 1,
+ 1
+ ],
+ "visible": true,
+ "customComponents": [],
+ "materials": [
+ {
+ "color": "e5c64c",
+ "emissive": "302600",
+ "emissiveIntensity": 0.18,
+ "metalness": 0.08,
+ "roughness": 0.72,
+ "opacity": 1,
+ "transparent": false,
+ "depthWrite": true
+ }
+ ]
+ },
+ "outdoor-safety-17.6--11": {
+ "name": "操作区安全标线",
+ "position": [
+ 17.6,
+ 0.012,
+ -11
+ ],
+ "rotation": [
+ 0,
+ 0.5654866776461628,
+ 0
+ ],
+ "scale": [
+ 1,
+ 1,
+ 1
+ ],
+ "visible": true,
+ "customComponents": [],
+ "materials": [
+ {
+ "color": "e5c64c",
+ "emissive": "302600",
+ "emissiveIntensity": 0.18,
+ "metalness": 0.08,
+ "roughness": 0.72,
+ "opacity": 1,
+ "transparent": false,
+ "depthWrite": true
+ }
+ ]
+ },
+ "outdoor-safety-17.6--8.4": {
+ "name": "操作区安全标线",
+ "position": [
+ 17.6,
+ 0.012,
+ -8.4
+ ],
+ "rotation": [
+ 0,
+ 0.5654866776461628,
+ 0
+ ],
+ "scale": [
+ 1,
+ 1,
+ 1
+ ],
+ "visible": true,
+ "customComponents": [],
+ "materials": [
+ {
+ "color": "e5c64c",
+ "emissive": "302600",
+ "emissiveIntensity": 0.18,
+ "metalness": 0.08,
+ "roughness": 0.72,
+ "opacity": 1,
+ "transparent": false,
+ "depthWrite": true
+ }
+ ]
+ },
+ "outdoor-safety-17.6--5.800000000000001": {
+ "name": "操作区安全标线",
+ "position": [
+ 17.6,
+ 0.012,
+ -5.800000000000001
+ ],
+ "rotation": [
+ 0,
+ 0.5654866776461628,
+ 0
+ ],
+ "scale": [
+ 1,
+ 1,
+ 1
+ ],
+ "visible": true,
+ "customComponents": [],
+ "materials": [
+ {
+ "color": "e5c64c",
+ "emissive": "302600",
+ "emissiveIntensity": 0.18,
+ "metalness": 0.08,
+ "roughness": 0.72,
+ "opacity": 1,
+ "transparent": false,
+ "depthWrite": true
+ }
+ ]
+ },
+ "outdoor-safety-17.6--3.2000000000000006": {
+ "name": "操作区安全标线",
+ "position": [
+ 17.6,
+ 0.012,
+ -3.2000000000000006
+ ],
+ "rotation": [
+ 0,
+ 0.5654866776461628,
+ 0
+ ],
+ "scale": [
+ 1,
+ 1,
+ 1
+ ],
+ "visible": true,
+ "customComponents": [],
+ "materials": [
+ {
+ "color": "e5c64c",
+ "emissive": "302600",
+ "emissiveIntensity": 0.18,
+ "metalness": 0.08,
+ "roughness": 0.72,
+ "opacity": 1,
+ "transparent": false,
+ "depthWrite": true
+ }
+ ]
+ },
+ "outdoor-safety-17.6--0.6000000000000005": {
+ "name": "操作区安全标线",
+ "position": [
+ 17.6,
+ 0.012,
+ -0.6000000000000005
+ ],
+ "rotation": [
+ 0,
+ 0.5654866776461628,
+ 0
+ ],
+ "scale": [
+ 1,
+ 1,
+ 1
+ ],
+ "visible": true,
+ "customComponents": [],
+ "materials": [
+ {
+ "color": "e5c64c",
+ "emissive": "302600",
+ "emissiveIntensity": 0.18,
+ "metalness": 0.08,
+ "roughness": 0.72,
+ "opacity": 1,
+ "transparent": false,
+ "depthWrite": true
+ }
+ ]
+ },
+ "outdoor-safety-17.6-1.9999999999999996": {
+ "name": "操作区安全标线",
+ "position": [
+ 17.6,
+ 0.012,
+ 1.9999999999999996
+ ],
+ "rotation": [
+ 0,
+ 0.5654866776461628,
+ 0
+ ],
+ "scale": [
+ 1,
+ 1,
+ 1
+ ],
+ "visible": true,
+ "customComponents": [],
+ "materials": [
+ {
+ "color": "e5c64c",
+ "emissive": "302600",
+ "emissiveIntensity": 0.18,
+ "metalness": 0.08,
+ "roughness": 0.72,
+ "opacity": 1,
+ "transparent": false,
+ "depthWrite": true
+ }
+ ]
+ },
+ "outdoor-safety-17.6-4.6": {
+ "name": "操作区安全标线",
+ "position": [
+ 17.6,
+ 0.012,
+ 4.6
+ ],
+ "rotation": [
+ 0,
+ 0.5654866776461628,
+ 0
+ ],
+ "scale": [
+ 1,
+ 1,
+ 1
+ ],
+ "visible": true,
+ "customComponents": [],
+ "materials": [
+ {
+ "color": "e5c64c",
+ "emissive": "302600",
+ "emissiveIntensity": 0.18,
+ "metalness": 0.08,
+ "roughness": 0.72,
+ "opacity": 1,
+ "transparent": false,
+ "depthWrite": true
+ }
+ ]
+ },
+ "outdoor-safety-17.6-7.199999999999999": {
+ "name": "操作区安全标线",
+ "position": [
+ 17.6,
+ 0.012,
+ 7.199999999999999
+ ],
+ "rotation": [
+ 0,
+ 0.5654866776461628,
+ 0
+ ],
+ "scale": [
+ 1,
+ 1,
+ 1
+ ],
+ "visible": true,
+ "customComponents": [],
+ "materials": [
+ {
+ "color": "e5c64c",
+ "emissive": "302600",
+ "emissiveIntensity": 0.18,
+ "metalness": 0.08,
+ "roughness": 0.72,
+ "opacity": 1,
+ "transparent": false,
+ "depthWrite": true
+ }
+ ]
+ },
+ "outdoor-safety-17.6-9.799999999999999": {
+ "name": "操作区安全标线",
+ "position": [
+ 17.6,
+ 0.012,
+ 9.799999999999999
+ ],
+ "rotation": [
+ 0,
+ 0.5654866776461628,
+ 0
+ ],
+ "scale": [
+ 1,
+ 1,
+ 1
+ ],
+ "visible": true,
+ "customComponents": [],
+ "materials": [
+ {
+ "color": "e5c64c",
+ "emissive": "302600",
+ "emissiveIntensity": 0.18,
+ "metalness": 0.08,
+ "roughness": 0.72,
+ "opacity": 1,
+ "transparent": false,
+ "depthWrite": true
+ }
+ ]
+ },
+ "outdoor-workshop": {
+ "name": "维修保障厂房",
+ "position": [
+ 0,
+ 0,
+ 0
+ ],
+ "rotation": [
+ 0,
+ 0,
+ 0
+ ],
+ "scale": [
+ 1,
+ 1,
+ 1
+ ],
+ "visible": true,
+ "customComponents": []
+ },
+ "outdoor-workshop-body": {
+ "name": "维修保障厂房主体",
+ "position": [
+ -10,
+ 3.25,
+ -18
+ ],
+ "rotation": [
+ 0,
+ 0,
+ 0
+ ],
+ "scale": [
+ 1,
+ 1,
+ 1
+ ],
+ "visible": true,
+ "customComponents": [],
+ "materials": [
+ {
+ "color": "536675",
+ "emissive": "000000",
+ "emissiveIntensity": 1,
+ "metalness": 0.18,
+ "roughness": 0.78,
+ "opacity": 1,
+ "transparent": false,
+ "depthWrite": true
+ }
+ ]
+ },
+ "outdoor-workshop-roof": {
+ "name": "厂房金属屋面",
+ "position": [
+ -10,
+ 6.65,
+ -18
+ ],
+ "rotation": [
+ 0,
+ 0,
+ 0
+ ],
+ "scale": [
+ 1,
+ 1,
+ 1
+ ],
+ "visible": true,
+ "customComponents": [],
+ "materials": [
+ {
+ "color": "263846",
+ "emissive": "000000",
+ "emissiveIntensity": 1,
+ "metalness": 0.35,
+ "roughness": 0.62,
+ "opacity": 1,
+ "transparent": false,
+ "depthWrite": true
+ }
+ ]
+ },
+ "outdoor-workshop-window-1": {
+ "name": "厂房观察窗 1",
+ "position": [
+ -17,
+ 3.7,
+ -14.21
+ ],
+ "rotation": [
+ 0,
+ 0,
+ 0
+ ],
+ "scale": [
+ 1,
+ 1,
+ 1
+ ],
+ "visible": true,
+ "customComponents": [],
+ "materials": [
+ {
+ "color": "4a89a2",
+ "emissive": "000000",
+ "emissiveIntensity": 1,
+ "metalness": 0.12,
+ "roughness": 0.25,
+ "opacity": 0.68,
+ "transparent": true,
+ "depthWrite": true
+ }
+ ]
+ },
+ "outdoor-workshop-window-2": {
+ "name": "厂房观察窗 2",
+ "position": [
+ -12.4,
+ 3.7,
+ -14.21
+ ],
+ "rotation": [
+ 0,
+ 0,
+ 0
+ ],
+ "scale": [
+ 1,
+ 1,
+ 1
+ ],
+ "visible": true,
+ "customComponents": [],
+ "materials": [
+ {
+ "color": "4a89a2",
+ "emissive": "000000",
+ "emissiveIntensity": 1,
+ "metalness": 0.12,
+ "roughness": 0.25,
+ "opacity": 0.68,
+ "transparent": true,
+ "depthWrite": true
+ }
+ ]
+ },
+ "outdoor-workshop-window-3": {
+ "name": "厂房观察窗 3",
+ "position": [
+ -7.800000000000001,
+ 3.7,
+ -14.21
+ ],
+ "rotation": [
+ 0,
+ 0,
+ 0
+ ],
+ "scale": [
+ 1,
+ 1,
+ 1
+ ],
+ "visible": true,
+ "customComponents": [],
+ "materials": [
+ {
+ "color": "4a89a2",
+ "emissive": "000000",
+ "emissiveIntensity": 1,
+ "metalness": 0.12,
+ "roughness": 0.25,
+ "opacity": 0.68,
+ "transparent": true,
+ "depthWrite": true
+ }
+ ]
+ },
+ "outdoor-workshop-window-4": {
+ "name": "厂房观察窗 4",
+ "position": [
+ -3.200000000000001,
+ 3.7,
+ -14.21
+ ],
+ "rotation": [
+ 0,
+ 0,
+ 0
+ ],
+ "scale": [
+ 1,
+ 1,
+ 1
+ ],
+ "visible": true,
+ "customComponents": [],
+ "materials": [
+ {
+ "color": "4a89a2",
+ "emissive": "000000",
+ "emissiveIntensity": 1,
+ "metalness": 0.12,
+ "roughness": 0.25,
+ "opacity": 0.68,
+ "transparent": true,
+ "depthWrite": true
+ }
+ ]
+ },
+ "outdoor-workshop-door": {
+ "name": "装备出入门",
+ "position": [
+ -3.6,
+ 2.16,
+ -14.18
+ ],
+ "rotation": [
+ 0,
+ 0,
+ 0
+ ],
+ "scale": [
+ 1,
+ 1,
+ 1
+ ],
+ "visible": true,
+ "customComponents": [],
+ "materials": [
+ {
+ "color": "202a31",
+ "emissive": "000000",
+ "emissiveIntensity": 1,
+ "metalness": 0.02,
+ "roughness": 0.95,
+ "opacity": 1,
+ "transparent": false,
+ "depthWrite": true
+ }
+ ]
+ },
+ "outdoor-canopy": {
+ "name": "室外检修棚",
+ "position": [
+ 0,
+ 0,
+ 0
+ ],
+ "rotation": [
+ 0,
+ 0,
+ 0
+ ],
+ "scale": [
+ 1,
+ 1,
+ 1
+ ],
+ "visible": true,
+ "customComponents": []
+ },
+ "outdoor-canopy-roof": {
+ "name": "检修棚顶",
+ "position": [
+ 18,
+ 5.05,
+ -8
+ ],
+ "rotation": [
+ 0,
+ 0,
+ 0
+ ],
+ "scale": [
+ 1,
+ 1,
+ 1
+ ],
+ "visible": true,
+ "customComponents": [],
+ "materials": [
+ {
+ "color": "263846",
+ "emissive": "000000",
+ "emissiveIntensity": 1,
+ "metalness": 0.35,
+ "roughness": 0.62,
+ "opacity": 1,
+ "transparent": false,
+ "depthWrite": true
+ }
+ ]
+ },
+ "outdoor-canopy-column-1": {
+ "name": "检修棚立柱 1",
+ "position": [
+ 12.7,
+ 2.5,
+ -11
+ ],
+ "rotation": [
+ 0,
+ 0,
+ 0
+ ],
+ "scale": [
+ 1,
+ 1,
+ 1
+ ],
+ "visible": true,
+ "customComponents": [],
+ "materials": [
+ {
+ "color": "66757c",
+ "emissive": "000000",
+ "emissiveIntensity": 1,
+ "metalness": 0.48,
+ "roughness": 0.56,
+ "opacity": 1,
+ "transparent": false,
+ "depthWrite": true
+ }
+ ]
+ },
+ "outdoor-canopy-column-2": {
+ "name": "检修棚立柱 2",
+ "position": [
+ 23.3,
+ 2.5,
+ -11
+ ],
+ "rotation": [
+ 0,
+ 0,
+ 0
+ ],
+ "scale": [
+ 1,
+ 1,
+ 1
+ ],
+ "visible": true,
+ "customComponents": [],
+ "materials": [
+ {
+ "color": "66757c",
+ "emissive": "000000",
+ "emissiveIntensity": 1,
+ "metalness": 0.48,
+ "roughness": 0.56,
+ "opacity": 1,
+ "transparent": false,
+ "depthWrite": true
+ }
+ ]
+ },
+ "outdoor-canopy-column-3": {
+ "name": "检修棚立柱 3",
+ "position": [
+ 12.7,
+ 2.5,
+ -5
+ ],
+ "rotation": [
+ 0,
+ 0,
+ 0
+ ],
+ "scale": [
+ 1,
+ 1,
+ 1
+ ],
+ "visible": true,
+ "customComponents": [],
+ "materials": [
+ {
+ "color": "66757c",
+ "emissive": "000000",
+ "emissiveIntensity": 1,
+ "metalness": 0.48,
+ "roughness": 0.56,
+ "opacity": 1,
+ "transparent": false,
+ "depthWrite": true
+ }
+ ]
+ },
+ "outdoor-canopy-column-4": {
+ "name": "检修棚立柱 4",
+ "position": [
+ 23.3,
+ 2.5,
+ -5
+ ],
+ "rotation": [
+ 0,
+ 0,
+ 0
+ ],
+ "scale": [
+ 1,
+ 1,
+ 1
+ ],
+ "visible": true,
+ "customComponents": [],
+ "materials": [
+ {
+ "color": "66757c",
+ "emissive": "000000",
+ "emissiveIntensity": 1,
+ "metalness": 0.48,
+ "roughness": 0.56,
+ "opacity": 1,
+ "transparent": false,
+ "depthWrite": true
+ }
+ ]
+ },
+ "outdoor-service-box": {
+ "name": "室外工具柜",
+ "position": [
+ 22.2,
+ 1.05,
+ -9.8
+ ],
+ "rotation": [
+ 0,
+ 0,
+ 0
+ ],
+ "scale": [
+ 1,
+ 1,
+ 1
+ ],
+ "visible": true,
+ "customComponents": [],
+ "materials": [
+ {
+ "color": "d98d2b",
+ "emissive": "000000",
+ "emissiveIntensity": 1,
+ "metalness": 0.12,
+ "roughness": 0.7,
+ "opacity": 1,
+ "transparent": false,
+ "depthWrite": true
+ }
+ ]
+ },
+ "outdoor-perimeter": {
+ "name": "场地边界与安全设施",
+ "position": [
+ 0,
+ 0,
+ 0
+ ],
+ "rotation": [
+ 0,
+ 0,
+ 0
+ ],
+ "scale": [
+ 1,
+ 1,
+ 1
+ ],
+ "visible": true,
+ "customComponents": []
+ },
+ "outdoor-fence-back-1": {
+ "name": "后侧围栏立柱 1",
+ "position": [
+ -29,
+ 1.1,
+ -25
+ ],
+ "rotation": [
+ 0,
+ 0,
+ 0
+ ],
+ "scale": [
+ 1,
+ 1,
+ 1
+ ],
+ "visible": true,
+ "customComponents": [],
+ "materials": [
+ {
+ "color": "66757c",
+ "emissive": "000000",
+ "emissiveIntensity": 1,
+ "metalness": 0.48,
+ "roughness": 0.56,
+ "opacity": 1,
+ "transparent": false,
+ "depthWrite": true
+ }
+ ]
+ },
+ "outdoor-fence-back-2": {
+ "name": "后侧围栏立柱 2",
+ "position": [
+ -23.2,
+ 1.1,
+ -25
+ ],
+ "rotation": [
+ 0,
+ 0,
+ 0
+ ],
+ "scale": [
+ 1,
+ 1,
+ 1
+ ],
+ "visible": true,
+ "customComponents": [],
+ "materials": [
+ {
+ "color": "66757c",
+ "emissive": "000000",
+ "emissiveIntensity": 1,
+ "metalness": 0.48,
+ "roughness": 0.56,
+ "opacity": 1,
+ "transparent": false,
+ "depthWrite": true
+ }
+ ]
+ },
+ "outdoor-fence-back-3": {
+ "name": "后侧围栏立柱 3",
+ "position": [
+ -17.4,
+ 1.1,
+ -25
+ ],
+ "rotation": [
+ 0,
+ 0,
+ 0
+ ],
+ "scale": [
+ 1,
+ 1,
+ 1
+ ],
+ "visible": true,
+ "customComponents": [],
+ "materials": [
+ {
+ "color": "66757c",
+ "emissive": "000000",
+ "emissiveIntensity": 1,
+ "metalness": 0.48,
+ "roughness": 0.56,
+ "opacity": 1,
+ "transparent": false,
+ "depthWrite": true
+ }
+ ]
+ },
+ "outdoor-fence-back-4": {
+ "name": "后侧围栏立柱 4",
+ "position": [
+ -11.6,
+ 1.1,
+ -25
+ ],
+ "rotation": [
+ 0,
+ 0,
+ 0
+ ],
+ "scale": [
+ 1,
+ 1,
+ 1
+ ],
+ "visible": true,
+ "customComponents": [],
+ "materials": [
+ {
+ "color": "66757c",
+ "emissive": "000000",
+ "emissiveIntensity": 1,
+ "metalness": 0.48,
+ "roughness": 0.56,
+ "opacity": 1,
+ "transparent": false,
+ "depthWrite": true
+ }
+ ]
+ },
+ "outdoor-fence-back-5": {
+ "name": "后侧围栏立柱 5",
+ "position": [
+ -5.8,
+ 1.1,
+ -25
+ ],
+ "rotation": [
+ 0,
+ 0,
+ 0
+ ],
+ "scale": [
+ 1,
+ 1,
+ 1
+ ],
+ "visible": true,
+ "customComponents": [],
+ "materials": [
+ {
+ "color": "66757c",
+ "emissive": "000000",
+ "emissiveIntensity": 1,
+ "metalness": 0.48,
+ "roughness": 0.56,
+ "opacity": 1,
+ "transparent": false,
+ "depthWrite": true
+ }
+ ]
+ },
+ "outdoor-fence-back-6": {
+ "name": "后侧围栏立柱 6",
+ "position": [
+ 0,
+ 1.1,
+ -25
+ ],
+ "rotation": [
+ 0,
+ 0,
+ 0
+ ],
+ "scale": [
+ 1,
+ 1,
+ 1
+ ],
+ "visible": true,
+ "customComponents": [],
+ "materials": [
+ {
+ "color": "66757c",
+ "emissive": "000000",
+ "emissiveIntensity": 1,
+ "metalness": 0.48,
+ "roughness": 0.56,
+ "opacity": 1,
+ "transparent": false,
+ "depthWrite": true
+ }
+ ]
+ },
+ "outdoor-fence-back-7": {
+ "name": "后侧围栏立柱 7",
+ "position": [
+ 5.8,
+ 1.1,
+ -25
+ ],
+ "rotation": [
+ 0,
+ 0,
+ 0
+ ],
+ "scale": [
+ 1,
+ 1,
+ 1
+ ],
+ "visible": true,
+ "customComponents": [],
+ "materials": [
+ {
+ "color": "66757c",
+ "emissive": "000000",
+ "emissiveIntensity": 1,
+ "metalness": 0.48,
+ "roughness": 0.56,
+ "opacity": 1,
+ "transparent": false,
+ "depthWrite": true
+ }
+ ]
+ },
+ "outdoor-fence-back-8": {
+ "name": "后侧围栏立柱 8",
+ "position": [
+ 11.6,
+ 1.1,
+ -25
+ ],
+ "rotation": [
+ 0,
+ 0,
+ 0
+ ],
+ "scale": [
+ 1,
+ 1,
+ 1
+ ],
+ "visible": true,
+ "customComponents": [],
+ "materials": [
+ {
+ "color": "66757c",
+ "emissive": "000000",
+ "emissiveIntensity": 1,
+ "metalness": 0.48,
+ "roughness": 0.56,
+ "opacity": 1,
+ "transparent": false,
+ "depthWrite": true
+ }
+ ]
+ },
+ "outdoor-fence-back-9": {
+ "name": "后侧围栏立柱 9",
+ "position": [
+ 17.4,
+ 1.1,
+ -25
+ ],
+ "rotation": [
+ 0,
+ 0,
+ 0
+ ],
+ "scale": [
+ 1,
+ 1,
+ 1
+ ],
+ "visible": true,
+ "customComponents": [],
+ "materials": [
+ {
+ "color": "66757c",
+ "emissive": "000000",
+ "emissiveIntensity": 1,
+ "metalness": 0.48,
+ "roughness": 0.56,
+ "opacity": 1,
+ "transparent": false,
+ "depthWrite": true
+ }
+ ]
+ },
+ "outdoor-fence-back-10": {
+ "name": "后侧围栏立柱 10",
+ "position": [
+ 23.2,
+ 1.1,
+ -25
+ ],
+ "rotation": [
+ 0,
+ 0,
+ 0
+ ],
+ "scale": [
+ 1,
+ 1,
+ 1
+ ],
+ "visible": true,
+ "customComponents": [],
+ "materials": [
+ {
+ "color": "66757c",
+ "emissive": "000000",
+ "emissiveIntensity": 1,
+ "metalness": 0.48,
+ "roughness": 0.56,
+ "opacity": 1,
+ "transparent": false,
+ "depthWrite": true
+ }
+ ]
+ },
+ "outdoor-fence-back-11": {
+ "name": "后侧围栏立柱 11",
+ "position": [
+ 29,
+ 1.1,
+ -25
+ ],
+ "rotation": [
+ 0,
+ 0,
+ 0
+ ],
+ "scale": [
+ 1,
+ 1,
+ 1
+ ],
+ "visible": true,
+ "customComponents": [],
+ "materials": [
+ {
+ "color": "66757c",
+ "emissive": "000000",
+ "emissiveIntensity": 1,
+ "metalness": 0.48,
+ "roughness": 0.56,
+ "opacity": 1,
+ "transparent": false,
+ "depthWrite": true
+ }
+ ]
+ },
+ "outdoor-fence-rail-a": {
+ "name": "后侧围栏上横杆",
+ "position": [
+ 0,
+ 1.85,
+ -25
+ ],
+ "rotation": [
+ 0,
+ 0,
+ 0
+ ],
+ "scale": [
+ 1,
+ 1,
+ 1
+ ],
+ "visible": true,
+ "customComponents": [],
+ "materials": [
+ {
+ "color": "66757c",
+ "emissive": "000000",
+ "emissiveIntensity": 1,
+ "metalness": 0.48,
+ "roughness": 0.56,
+ "opacity": 1,
+ "transparent": false,
+ "depthWrite": true
+ }
+ ]
+ },
+ "outdoor-fence-rail-b": {
+ "name": "后侧围栏下横杆",
+ "position": [
+ 0,
+ 0.75,
+ -25
+ ],
+ "rotation": [
+ 0,
+ 0,
+ 0
+ ],
+ "scale": [
+ 1,
+ 1,
+ 1
+ ],
+ "visible": true,
+ "customComponents": [],
+ "materials": [
+ {
+ "color": "66757c",
+ "emissive": "000000",
+ "emissiveIntensity": 1,
+ "metalness": 0.48,
+ "roughness": 0.56,
+ "opacity": 1,
+ "transparent": false,
+ "depthWrite": true
+ }
+ ]
+ },
+ "outdoor-scene-sign": {
+ "name": "室外实训区域标识",
+ "position": [
+ 26,
+ 2.4,
+ 11.5
+ ],
+ "rotation": [
+ 0,
+ 0,
+ 0
+ ],
+ "scale": [
+ 1,
+ 1,
+ 1
+ ],
+ "visible": true,
+ "customComponents": [],
+ "materials": [
+ {
+ "color": "536675",
+ "emissive": "000000",
+ "emissiveIntensity": 1,
+ "metalness": 0.18,
+ "roughness": 0.78,
+ "opacity": 1,
+ "transparent": false,
+ "depthWrite": true
+ }
+ ]
+ },
+ "outdoor-scene-sign-post-a": {
+ "name": "区域标识支柱 A",
+ "position": [
+ 24,
+ 1.4,
+ 11.5
+ ],
+ "rotation": [
+ 0,
+ 0,
+ 0
+ ],
+ "scale": [
+ 1,
+ 1,
+ 1
+ ],
+ "visible": true,
+ "customComponents": [],
+ "materials": [
+ {
+ "color": "66757c",
+ "emissive": "000000",
+ "emissiveIntensity": 1,
+ "metalness": 0.48,
+ "roughness": 0.56,
+ "opacity": 1,
+ "transparent": false,
+ "depthWrite": true
+ }
+ ]
+ },
+ "outdoor-scene-sign-post-b": {
+ "name": "区域标识支柱 B",
+ "position": [
+ 28,
+ 1.4,
+ 11.5
+ ],
+ "rotation": [
+ 0,
+ 0,
+ 0
+ ],
+ "scale": [
+ 1,
+ 1,
+ 1
+ ],
+ "visible": true,
+ "customComponents": [],
+ "materials": [
+ {
+ "color": "66757c",
+ "emissive": "000000",
+ "emissiveIntensity": 1,
+ "metalness": 0.48,
+ "roughness": 0.56,
+ "opacity": 1,
+ "transparent": false,
+ "depthWrite": true
+ }
+ ]
+ }
+ }
+ },
+ "runtime": {
+ "startMode": "guided",
+ "showStepPanel": true,
+ "showProgress": true,
+ "allowReplay": true,
+ "language": "zh-CN"
+ },
+ "publish": {
+ "target": "standalone-html",
+ "deployment": "offline",
+ "includeProjectData": true,
+ "includeViewportPreview": true
+ }
+}
\ No newline at end of file
diff --git a/reports/model-editor-closure-20260906/downloads/recovery-187-isolated.json b/reports/model-editor-closure-20260906/downloads/recovery-187-isolated.json
new file mode 100644
index 0000000..325397d
--- /dev/null
+++ b/reports/model-editor-closure-20260906/downloads/recovery-187-isolated.json
@@ -0,0 +1,3422 @@
+{
+ "schema": "unreal-tran.model-api-recovery/v1",
+ "snapshotId": "c5e59ceb-13bc-4bd5-afc3-974b4ae38bbd",
+ "projectId": "187",
+ "projectVersion": 2,
+ "savedAt": "2026-09-06T07:13:40.544Z",
+ "ownerPageNonce": "39826742-3c8c-41d2-b163-12c6dd04af89",
+ "document": {
+ "schema": "digital-twin-editor-project",
+ "version": 4,
+ "name": "闭环验收-恢复副本-mtph6hw8",
+ "modelId": "imported-model_mto6v5w6_09-fox",
+ "modelResource": {
+ "kind": "imported",
+ "id": "imported-model_mto6v5w6_09-fox",
+ "name": "09-Fox",
+ "assetCode": "MODEL_MTO6V5W6_09-FOX",
+ "code": "MODEL_MTO6V5W6_09-FOX",
+ "storageUri": "fsvc://unreal-tran/model/2026/09/05/3fc69ae0d435f869afca1fcd943486d1.glb",
+ "assetKey": "",
+ "resourceUrl": "",
+ "sourceName": "09-Fox.glb",
+ "sizeBytes": 162852,
+ "lastModified": 0,
+ "mimeType": "model/gltf-binary",
+ "thumbnail": "",
+ "vertices": 1728,
+ "triangles": 576,
+ "nodes": 27,
+ "meshes": 1,
+ "materials": 1,
+ "textures": 1,
+ "animations": 3,
+ "animationDurationSeconds": 3.4166667461395264,
+ "modelMetadata": {
+ "nodeCount": 27,
+ "meshCount": 1,
+ "materialCount": 1,
+ "textureCount": 1,
+ "animations": [
+ {
+ "index": 0,
+ "name": "Survey",
+ "start": 0,
+ "end": 3.4166667461395264,
+ "duration": 3.4166667461395264,
+ "trackCount": 21
+ },
+ {
+ "index": 1,
+ "name": "Walk",
+ "start": 0,
+ "end": 0.7083333134651184,
+ "duration": 0.7083333134651184,
+ "trackCount": 21
+ },
+ {
+ "index": 2,
+ "name": "Run",
+ "start": 0,
+ "end": 1.1583333015441895,
+ "duration": 1.1583333015441895,
+ "trackCount": 21
+ }
+ ],
+ "animationDurationSeconds": 3.4166667461395264
+ },
+ "nativeAnimations": [
+ {
+ "index": 0,
+ "name": "Survey",
+ "start": 0,
+ "end": 3.4166667461395264,
+ "duration": 3.4166667461395264,
+ "trackCount": 21
+ },
+ {
+ "index": 1,
+ "name": "Walk",
+ "start": 0,
+ "end": 0.7083333134651184,
+ "duration": 0.7083333134651184,
+ "trackCount": 21
+ },
+ {
+ "index": 2,
+ "name": "Run",
+ "start": 0,
+ "end": 1.1583333015441895,
+ "duration": 1.1583333015441895,
+ "trackCount": 21
+ }
+ ]
+ },
+ "meta": {
+ "description": "用于装备维修实训场景编排、编辑器回归与离线发布验证的系统内置基准工程",
+ "author": "数字孪生项目组",
+ "scene": "车间与野外装备维修实训环境",
+ "tags": [
+ "三维场景",
+ "时间轴动画",
+ "蓝图交互",
+ "实训流程"
+ ],
+ "migratedFrom": "legacy-model-document",
+ "source": "content-api-glb",
+ "sourceName": "09-Fox.glb",
+ "assetCode": "MODEL_MTO6V5W6_09-FOX",
+ "storageUri": "fsvc://unreal-tran/model/2026/09/05/3fc69ae0d435f869afca1fcd943486d1.glb"
+ },
+ "timeline": {
+ "version": 1,
+ "duration": 8,
+ "currentTime": 0,
+ "loop": true,
+ "zoom": 1,
+ "tracks": [
+ {
+ "id": "track-0-1yjfz9t",
+ "name": "模型整体位移",
+ "targetId": "interaction-b9240aaa",
+ "property": "position",
+ "color": "#ffb22e",
+ "muted": false,
+ "keyframes": [
+ {
+ "id": "key-0-0-kmfo820",
+ "time": 0,
+ "value": [
+ 0,
+ 0,
+ 0
+ ],
+ "easing": "easeInOut"
+ },
+ {
+ "id": "key-0-1-snsj2e2",
+ "time": 8,
+ "value": [
+ 0,
+ 0,
+ 0
+ ],
+ "easing": "easeInOut"
+ }
+ ]
+ }
+ ]
+ },
+ "blueprint": {
+ "version": 1,
+ "name": "挖掘机作业实训闭环",
+ "viewport": {
+ "x": 55,
+ "y": 55,
+ "zoom": 0.82
+ },
+ "nodes": [
+ {
+ "id": "bp-start",
+ "type": "trigger-click",
+ "title": "点击挖掘机",
+ "x": 40,
+ "y": 55,
+ "params": {
+ "event": "click",
+ "target": "挖掘机"
+ }
+ },
+ {
+ "id": "bp-highlight",
+ "type": "highlight",
+ "title": "高亮工作装置",
+ "x": 300,
+ "y": 55,
+ "params": {
+ "target": "铲斗",
+ "color": "#38e8ff",
+ "pulse": true
+ }
+ },
+ {
+ "id": "bp-message",
+ "type": "message",
+ "title": "显示操作提示",
+ "x": 560,
+ "y": 55,
+ "params": {
+ "title": "挖掘作业实训",
+ "text": "观察动臂、斗杆和铲斗的协同动作",
+ "level": "info"
+ }
+ },
+ {
+ "id": "bp-play",
+ "type": "play-timeline",
+ "title": "播放作业动画",
+ "x": 820,
+ "y": 55,
+ "params": {
+ "clip": "挖掘机作业实训",
+ "from": 0,
+ "speed": 1,
+ "waitForEnd": true
+ }
+ },
+ {
+ "id": "bp-wait",
+ "type": "wait",
+ "title": "动作稳定",
+ "x": 560,
+ "y": 245,
+ "params": {
+ "duration": 600
+ }
+ },
+ {
+ "id": "bp-check",
+ "type": "condition",
+ "title": "动画是否完成",
+ "x": 300,
+ "y": 245,
+ "params": {
+ "variable": "timelineComplete",
+ "operator": "==",
+ "value": true
+ }
+ },
+ {
+ "id": "bp-retry",
+ "type": "message",
+ "title": "等待动画完成",
+ "x": 820,
+ "y": 245,
+ "params": {
+ "title": "流程校验",
+ "text": "动画尚未完成,请重新执行",
+ "level": "warning"
+ }
+ },
+ {
+ "id": "bp-finish",
+ "type": "finish",
+ "title": "流程完成",
+ "x": 40,
+ "y": 245,
+ "params": {
+ "result": "success",
+ "text": "挖掘机作业实训流程完成"
+ }
+ }
+ ],
+ "edges": [
+ {
+ "id": "bp-e1",
+ "from": {
+ "node": "bp-start",
+ "port": "then"
+ },
+ "to": {
+ "node": "bp-highlight",
+ "port": "in"
+ }
+ },
+ {
+ "id": "bp-e2",
+ "from": {
+ "node": "bp-highlight",
+ "port": "then"
+ },
+ "to": {
+ "node": "bp-message",
+ "port": "in"
+ }
+ },
+ {
+ "id": "bp-e3",
+ "from": {
+ "node": "bp-message",
+ "port": "then"
+ },
+ "to": {
+ "node": "bp-play",
+ "port": "in"
+ }
+ },
+ {
+ "id": "bp-e4",
+ "from": {
+ "node": "bp-play",
+ "port": "then"
+ },
+ "to": {
+ "node": "bp-wait",
+ "port": "in"
+ }
+ },
+ {
+ "id": "bp-e5",
+ "from": {
+ "node": "bp-wait",
+ "port": "then"
+ },
+ "to": {
+ "node": "bp-check",
+ "port": "in"
+ }
+ },
+ {
+ "id": "bp-e6",
+ "from": {
+ "node": "bp-check",
+ "port": "true"
+ },
+ "to": {
+ "node": "bp-finish",
+ "port": "in"
+ }
+ },
+ {
+ "id": "bp-e7",
+ "from": {
+ "node": "bp-check",
+ "port": "false"
+ },
+ "to": {
+ "node": "bp-retry",
+ "port": "in"
+ }
+ },
+ {
+ "id": "bp-e8",
+ "from": {
+ "node": "bp-retry",
+ "port": "then"
+ },
+ "to": {
+ "node": "bp-play",
+ "port": "in"
+ }
+ }
+ ]
+ },
+ "camera": {
+ "position": [
+ 4.643347096844712,
+ 4.616668289605231,
+ 12.832677927580558
+ ],
+ "target": [
+ 3.875009990661482e-8,
+ 2.4006999949620704,
+ 2.2660093712545404e-7
+ ]
+ },
+ "model": {
+ "objects": {
+ "equipment-root": {
+ "name": "09-Fox",
+ "nodeName": "09-Fox",
+ "nodePath": "@root",
+ "interactionId": "interaction-b9240aaa",
+ "capabilities": {
+ "selectable": true,
+ "transform": true,
+ "visible": true,
+ "material": false,
+ "animatable": true
+ },
+ "position": [
+ 0,
+ 0,
+ 0
+ ],
+ "rotation": [
+ 0,
+ 0,
+ 0
+ ],
+ "scale": [
+ 1,
+ 1,
+ 1
+ ],
+ "visible": true,
+ "customComponents": []
+ },
+ "model-imported-model_mto6v5w6_09-fox-0": {
+ "name": "",
+ "nodeName": "Group",
+ "nodePath": "0",
+ "interactionId": "interaction-02351bce",
+ "capabilities": {
+ "selectable": true,
+ "transform": true,
+ "visible": true,
+ "material": false,
+ "animatable": true
+ },
+ "position": [
+ 3.875009990661482e-8,
+ 0.007396563645075124,
+ 0.6522089834776823
+ ],
+ "rotation": [
+ 0,
+ 0,
+ 0
+ ],
+ "scale": [
+ 0.06075496488891343,
+ 0.06075496488891343,
+ 0.06075496488891343
+ ],
+ "visible": true,
+ "customComponents": []
+ },
+ "model-imported-model_mto6v5w6_09-fox-1": {
+ "name": "root",
+ "nodeName": "root",
+ "nodePath": "0/0",
+ "interactionId": "interaction-a4d35db9",
+ "capabilities": {
+ "selectable": true,
+ "transform": true,
+ "visible": true,
+ "material": false,
+ "animatable": true
+ },
+ "position": [
+ 0,
+ 0,
+ 0
+ ],
+ "rotation": [
+ 0,
+ 0,
+ 0
+ ],
+ "scale": [
+ 1,
+ 1,
+ 1
+ ],
+ "visible": true,
+ "customComponents": []
+ },
+ "model-imported-model_mto6v5w6_09-fox-2": {
+ "name": "_rootJoint",
+ "nodeName": "_rootJoint",
+ "nodePath": "0/0/0",
+ "interactionId": "interaction-4476c556",
+ "capabilities": {
+ "selectable": true,
+ "transform": true,
+ "visible": true,
+ "material": false,
+ "animatable": true
+ },
+ "position": [
+ 0,
+ 0,
+ 0
+ ],
+ "rotation": [
+ 0,
+ 0,
+ 0
+ ],
+ "scale": [
+ 1,
+ 1,
+ 1
+ ],
+ "visible": true,
+ "customComponents": []
+ },
+ "model-imported-model_mto6v5w6_09-fox-3": {
+ "name": "b_Root_00",
+ "nodeName": "b_Root_00",
+ "nodePath": "0/0/0/0",
+ "interactionId": "interaction-ffc45d11",
+ "capabilities": {
+ "selectable": true,
+ "transform": true,
+ "visible": true,
+ "material": false,
+ "animatable": true
+ },
+ "position": [
+ 0,
+ 0,
+ 0
+ ],
+ "rotation": [
+ -1.5708000357176293,
+ 0,
+ 0
+ ],
+ "scale": [
+ 1,
+ 1,
+ 1
+ ],
+ "visible": true,
+ "customComponents": []
+ },
+ "model-imported-model_mto6v5w6_09-fox-4": {
+ "name": "b_Hip_01",
+ "nodeName": "b_Hip_01",
+ "nodePath": "0/0/0/0/0",
+ "interactionId": "interaction-7a75e39e",
+ "capabilities": {
+ "selectable": true,
+ "transform": true,
+ "visible": true,
+ "material": false,
+ "animatable": true
+ },
+ "position": [
+ 0.000001241521920852674,
+ 22.061977333358605,
+ 34.62373020761327
+ ],
+ "rotation": [
+ 0.44722050284761794,
+ -1.5707951637404125,
+ 0
+ ],
+ "scale": [
+ 1,
+ 1,
+ 1
+ ],
+ "visible": true,
+ "customComponents": []
+ },
+ "model-imported-model_mto6v5w6_09-fox-5": {
+ "name": "b_Spine01_02",
+ "nodeName": "b_Spine01_02",
+ "nodePath": "0/0/0/0/0/0",
+ "interactionId": "interaction-663ea469",
+ "capabilities": {
+ "selectable": true,
+ "transform": true,
+ "visible": true,
+ "material": false,
+ "animatable": true
+ },
+ "position": [
+ 12.850601196289062,
+ 0,
+ 0
+ ],
+ "rotation": [
+ -3.357310257291308e-7,
+ -3.316574913360067e-8,
+ -1.2102038452161539
+ ],
+ "scale": [
+ 1,
+ 1,
+ 1
+ ],
+ "visible": true,
+ "customComponents": []
+ },
+ "model-imported-model_mto6v5w6_09-fox-6": {
+ "name": "b_Spine02_03",
+ "nodeName": "b_Spine02_03",
+ "nodePath": "0/0/0/0/0/0/0",
+ "interactionId": "interaction-46867126",
+ "capabilities": {
+ "selectable": true,
+ "transform": true,
+ "visible": true,
+ "material": false,
+ "animatable": true
+ },
+ "position": [
+ 21.65575408935547,
+ -0.000118255615234375,
+ 0
+ ],
+ "rotation": [
+ 8.772651057591018e-8,
+ 3.3355428835535634e-8,
+ 0.3633406824515624
+ ],
+ "scale": [
+ 1,
+ 1,
+ 1
+ ],
+ "visible": true,
+ "customComponents": []
+ },
+ "model-imported-model_mto6v5w6_09-fox-7": {
+ "name": "b_Neck_04",
+ "nodeName": "b_Neck_04",
+ "nodePath": "0/0/0/0/0/0/0/0",
+ "interactionId": "interaction-27148d81",
+ "capabilities": {
+ "selectable": true,
+ "transform": true,
+ "visible": true,
+ "material": false,
+ "animatable": true
+ },
+ "position": [
+ 25.64914321899414,
+ 0,
+ 0
+ ],
+ "rotation": [
+ 6.423807629998062e-15,
+ 1.9993472402144555e-16,
+ 0.031113979193075042
+ ],
+ "scale": [
+ 1,
+ 1,
+ 1
+ ],
+ "visible": true,
+ "customComponents": []
+ },
+ "model-imported-model_mto6v5w6_09-fox-8": {
+ "name": "b_Head_05",
+ "nodeName": "b_Head_05",
+ "nodePath": "0/0/0/0/0/0/0/0/0",
+ "interactionId": "interaction-b24eef2e",
+ "capabilities": {
+ "selectable": true,
+ "transform": true,
+ "visible": true,
+ "material": false,
+ "animatable": true
+ },
+ "position": [
+ 13.376960754394531,
+ 0,
+ 0
+ ],
+ "rotation": [
+ 3.253825627358364e-8,
+ -1.577320798309453e-8,
+ -0.451380500897834
+ ],
+ "scale": [
+ 1,
+ 1,
+ 1
+ ],
+ "visible": true,
+ "customComponents": []
+ },
+ "model-imported-model_mto6v5w6_09-fox-9": {
+ "name": "b_RightUpperArm_06",
+ "nodeName": "b_RightUpperArm_06",
+ "nodePath": "0/0/0/0/0/0/0/1",
+ "interactionId": "interaction-26148bee",
+ "capabilities": {
+ "selectable": true,
+ "transform": true,
+ "visible": true,
+ "material": false,
+ "animatable": true
+ },
+ "position": [
+ 18.677913665771484,
+ -4.297340393066406,
+ 6.9675750732421875
+ ],
+ "rotation": [
+ -0.035644184619881884,
+ -0.047001863061712265,
+ -1.2972005346348685
+ ],
+ "scale": [
+ 1,
+ 1,
+ 1
+ ],
+ "visible": true,
+ "customComponents": []
+ },
+ "model-imported-model_mto6v5w6_09-fox-10": {
+ "name": "b_RightForeArm_07",
+ "nodeName": "b_RightForeArm_07",
+ "nodePath": "0/0/0/0/0/0/0/1/0",
+ "interactionId": "interaction-36896559",
+ "capabilities": {
+ "selectable": true,
+ "transform": true,
+ "visible": true,
+ "material": false,
+ "animatable": true
+ },
+ "position": [
+ 23.04512596130371,
+ 0,
+ 0
+ ],
+ "rotation": [
+ 2.1355818997049898e-8,
+ 7.233656206674673e-8,
+ 1.2561843405841544
+ ],
+ "scale": [
+ 1,
+ 1,
+ 1
+ ],
+ "visible": true,
+ "customComponents": []
+ },
+ "model-imported-model_mto6v5w6_09-fox-11": {
+ "name": "b_RightHand_08",
+ "nodeName": "b_RightHand_08",
+ "nodePath": "0/0/0/0/0/0/0/1/0/0",
+ "interactionId": "interaction-57a124f6",
+ "capabilities": {
+ "selectable": true,
+ "transform": true,
+ "visible": true,
+ "material": false,
+ "animatable": true
+ },
+ "position": [
+ 19.350055694580078,
+ -0.14598655700683594,
+ 0
+ ],
+ "rotation": [
+ 0.005245101551986586,
+ 0.05200705984217871,
+ -0.267165337534188
+ ],
+ "scale": [
+ 1,
+ 1,
+ 1
+ ],
+ "visible": true,
+ "customComponents": []
+ },
+ "model-imported-model_mto6v5w6_09-fox-12": {
+ "name": "b_LeftUpperArm_09",
+ "nodeName": "b_LeftUpperArm_09",
+ "nodePath": "0/0/0/0/0/0/0/2",
+ "interactionId": "interaction-25148a5b",
+ "capabilities": {
+ "selectable": true,
+ "transform": true,
+ "visible": true,
+ "material": false,
+ "animatable": true
+ },
+ "position": [
+ 18.67791748046875,
+ -4.297344207763672,
+ -6.967987060546875
+ ],
+ "rotation": [
+ -0.00020118104561539401,
+ 0.0009350561550523671,
+ -1.5148902559141275
+ ],
+ "scale": [
+ 1,
+ 1,
+ 1
+ ],
+ "visible": true,
+ "customComponents": []
+ },
+ "model-imported-model_mto6v5w6_09-fox-13": {
+ "name": "b_LeftForeArm_010",
+ "nodeName": "b_LeftForeArm_010",
+ "nodePath": "0/0/0/0/0/0/0/2/0",
+ "interactionId": "interaction-5c502ac4",
+ "capabilities": {
+ "selectable": true,
+ "transform": true,
+ "visible": true,
+ "material": false,
+ "animatable": true
+ },
+ "position": [
+ 23.045124053955078,
+ 0,
+ 0
+ ],
+ "rotation": [
+ 2.5220861991717696e-8,
+ 7.109903232964989e-8,
+ 1.2307106255258764
+ ],
+ "scale": [
+ 1,
+ 1,
+ 1
+ ],
+ "visible": true,
+ "customComponents": []
+ },
+ "model-imported-model_mto6v5w6_09-fox-14": {
+ "name": "b_LeftHand_011",
+ "nodeName": "b_LeftHand_011",
+ "nodePath": "0/0/0/0/0/0/0/2/0/0",
+ "interactionId": "interaction-192b4cd3",
+ "capabilities": {
+ "selectable": true,
+ "transform": true,
+ "visible": true,
+ "material": false,
+ "animatable": true
+ },
+ "position": [
+ 19.350051879882812,
+ -0.14599037170410156,
+ 0
+ ],
+ "rotation": [
+ 0.004279557751527162,
+ -0.03344276494440009,
+ -1.5985291598689726
+ ],
+ "scale": [
+ 1,
+ 1,
+ 1
+ ],
+ "visible": true,
+ "customComponents": []
+ },
+ "model-imported-model_mto6v5w6_09-fox-15": {
+ "name": "b_Tail01_012",
+ "nodeName": "b_Tail01_012",
+ "nodePath": "0/0/0/0/0/1",
+ "interactionId": "interaction-653ea2d6",
+ "capabilities": {
+ "selectable": true,
+ "transform": true,
+ "visible": true,
+ "material": false,
+ "animatable": true
+ },
+ "position": [
+ 4.2603759765625,
+ 15.958770751953125,
+ 0
+ ],
+ "rotation": [
+ -5.364745216052604e-7,
+ 3.424797608447095e-7,
+ 1.9941304874941974
+ ],
+ "scale": [
+ 1,
+ 1,
+ 1
+ ],
+ "visible": true,
+ "customComponents": []
+ },
+ "model-imported-model_mto6v5w6_09-fox-16": {
+ "name": "b_Tail02_013",
+ "nodeName": "b_Tail02_013",
+ "nodePath": "0/0/0/0/0/1/0",
+ "interactionId": "interaction-6c4d3691",
+ "capabilities": {
+ "selectable": true,
+ "transform": true,
+ "visible": true,
+ "material": false,
+ "animatable": true
+ },
+ "position": [
+ 12.411918640136719,
+ 0,
+ 0
+ ],
+ "rotation": [
+ -6.738507958375962e-23,
+ -5.2439168243310865e-23,
+ 0.3128105211171357
+ ],
+ "scale": [
+ 1,
+ 1,
+ 1
+ ],
+ "visible": true,
+ "customComponents": []
+ },
+ "model-imported-model_mto6v5w6_09-fox-17": {
+ "name": "b_Tail03_014",
+ "nodeName": "b_Tail03_014",
+ "nodePath": "0/0/0/0/0/1/0/0",
+ "interactionId": "interaction-e93e191e",
+ "capabilities": {
+ "selectable": true,
+ "transform": true,
+ "visible": true,
+ "material": false,
+ "animatable": true
+ },
+ "position": [
+ 24.24032211303711,
+ 0,
+ 0
+ ],
+ "rotation": [
+ -5.7486339033124146e-24,
+ 1.1669178332583734e-22,
+ -0.012937892408322462
+ ],
+ "scale": [
+ 1,
+ 1,
+ 1
+ ],
+ "visible": true,
+ "customComponents": []
+ },
+ "model-imported-model_mto6v5w6_09-fox-18": {
+ "name": "b_LeftLeg01_015",
+ "nodeName": "b_LeftLeg01_015",
+ "nodePath": "0/0/0/0/0/2",
+ "interactionId": "interaction-643ea143",
+ "capabilities": {
+ "selectable": true,
+ "transform": true,
+ "visible": true,
+ "material": false,
+ "animatable": true
+ },
+ "position": [
+ 4.813770294189453,
+ 5.154018402099609,
+ -6.968006134033203
+ ],
+ "rotation": [
+ 0.1223493637902729,
+ -0.09724770930295147,
+ 2.9630295165414986
+ ],
+ "scale": [
+ 1,
+ 1,
+ 1
+ ],
+ "visible": true,
+ "customComponents": []
+ },
+ "model-imported-model_mto6v5w6_09-fox-19": {
+ "name": "b_LeftLeg02_016",
+ "nodeName": "b_LeftLeg02_016",
+ "nodePath": "0/0/0/0/0/2/0",
+ "interactionId": "interaction-7175f8dc",
+ "capabilities": {
+ "selectable": true,
+ "transform": true,
+ "visible": true,
+ "material": false,
+ "animatable": true
+ },
+ "position": [
+ 18.944175720214844,
+ 0,
+ 0
+ ],
+ "rotation": [
+ 1.2545483692289454e-8,
+ 3.728483680761731e-8,
+ -0.522293355530293
+ ],
+ "scale": [
+ 1,
+ 1,
+ 1
+ ],
+ "visible": true,
+ "customComponents": []
+ },
+ "model-imported-model_mto6v5w6_09-fox-20": {
+ "name": "b_LeftFoot01_017",
+ "nodeName": "b_LeftFoot01_017",
+ "nodePath": "0/0/0/0/0/2/0/0",
+ "interactionId": "interaction-f5ed353b",
+ "capabilities": {
+ "selectable": true,
+ "transform": true,
+ "visible": true,
+ "material": false,
+ "animatable": true
+ },
+ "position": [
+ 17.942811965942383,
+ 0,
+ 0
+ ],
+ "rotation": [
+ -0.03128474386729028,
+ -0.15471927051895154,
+ 0.7774909055508243
+ ],
+ "scale": [
+ 1,
+ 1,
+ 1
+ ],
+ "visible": true,
+ "customComponents": []
+ },
+ "model-imported-model_mto6v5w6_09-fox-21": {
+ "name": "b_LeftFoot02_018",
+ "nodeName": "b_LeftFoot02_018",
+ "nodePath": "0/0/0/0/0/2/0/0/0",
+ "interactionId": "interaction-1bbe01a4",
+ "capabilities": {
+ "selectable": true,
+ "transform": true,
+ "visible": true,
+ "material": false,
+ "animatable": true
+ },
+ "position": [
+ 15.779938697814941,
+ 0,
+ 0
+ ],
+ "rotation": [
+ 0,
+ 0,
+ 1.158241570830627
+ ],
+ "scale": [
+ 1,
+ 1,
+ 1
+ ],
+ "visible": true,
+ "customComponents": []
+ },
+ "model-imported-model_mto6v5w6_09-fox-22": {
+ "name": "b_RightLeg01_019",
+ "nodeName": "b_RightLeg01_019",
+ "nodePath": "0/0/0/0/0/3",
+ "interactionId": "interaction-633e9fb0",
+ "capabilities": {
+ "selectable": true,
+ "transform": true,
+ "visible": true,
+ "material": false,
+ "animatable": true
+ },
+ "position": [
+ 4.813777923583984,
+ 5.154026031494141,
+ 6.967563629150391
+ ],
+ "rotation": [
+ -0.10137208185544387,
+ 0.05534659522395753,
+ -2.3613690068554236
+ ],
+ "scale": [
+ 1,
+ 1,
+ 1
+ ],
+ "visible": true,
+ "customComponents": []
+ },
+ "model-imported-model_mto6v5w6_09-fox-23": {
+ "name": "b_RightLeg02_020",
+ "nodeName": "b_RightLeg02_020",
+ "nodePath": "0/0/0/0/0/3/0",
+ "interactionId": "interaction-95ff89c7",
+ "capabilities": {
+ "selectable": true,
+ "transform": true,
+ "visible": true,
+ "material": false,
+ "animatable": true
+ },
+ "position": [
+ 18.944183349609375,
+ 0,
+ 0
+ ],
+ "rotation": [
+ 2.7890192431350882e-8,
+ 1.4750479199499402e-8,
+ -1.0710323806538369
+ ],
+ "scale": [
+ 1,
+ 1,
+ 1
+ ],
+ "visible": true,
+ "customComponents": []
+ },
+ "model-imported-model_mto6v5w6_09-fox-24": {
+ "name": "b_RightFoot01_021",
+ "nodeName": "b_RightFoot01_021",
+ "nodePath": "0/0/0/0/0/3/0/0",
+ "interactionId": "interaction-a157d498",
+ "capabilities": {
+ "selectable": true,
+ "transform": true,
+ "visible": true,
+ "material": false,
+ "animatable": true
+ },
+ "position": [
+ 17.94281005859375,
+ 0,
+ 0
+ ],
+ "rotation": [
+ -0.14401413968163027,
+ 0.2814735368347316,
+ 1.4591240233677443
+ ],
+ "scale": [
+ 1,
+ 1,
+ 1
+ ],
+ "visible": true,
+ "customComponents": []
+ },
+ "model-imported-model_mto6v5w6_09-fox-25": {
+ "name": "b_RightFoot02_022",
+ "nodeName": "b_RightFoot02_022",
+ "nodePath": "0/0/0/0/0/3/0/0/0",
+ "interactionId": "interaction-ebd38e3f",
+ "capabilities": {
+ "selectable": true,
+ "transform": true,
+ "visible": true,
+ "material": false,
+ "animatable": true
+ },
+ "position": [
+ 15.779935836791992,
+ 0,
+ 0
+ ],
+ "rotation": [
+ 0,
+ 0,
+ 1.158241570830627
+ ],
+ "scale": [
+ 1,
+ 1,
+ 1
+ ],
+ "visible": true,
+ "customComponents": []
+ },
+ "model-imported-model_mto6v5w6_09-fox-26": {
+ "name": "闭环验收-旧版本需人工合并",
+ "nodeName": "fox",
+ "nodePath": "0/1",
+ "interactionId": "interaction-a3d35c26",
+ "capabilities": {
+ "selectable": true,
+ "transform": true,
+ "visible": true,
+ "material": true,
+ "animatable": true
+ },
+ "position": [
+ 0.25,
+ 0,
+ 0
+ ],
+ "rotation": [
+ 0,
+ 0,
+ 0
+ ],
+ "scale": [
+ 1,
+ 1,
+ 1
+ ],
+ "visible": true,
+ "customComponents": [],
+ "materials": [
+ {
+ "color": "ffffff",
+ "emissive": "000000",
+ "emissiveIntensity": 1,
+ "metalness": 0,
+ "roughness": 0.3,
+ "opacity": 1,
+ "transparent": false,
+ "depthWrite": true
+ }
+ ]
+ }
+ },
+ "selectionScope": "part",
+ "nativeAnimation": {
+ "clipIndex": 2,
+ "clipName": "Run"
+ }
+ },
+ "environment": {
+ "mode": "placeholder",
+ "environmentType": "outdoor",
+ "sourceName": "内置野外综合训练环境",
+ "sizeBytes": 0,
+ "assetCode": "",
+ "storageUri": "",
+ "resourceUrl": "",
+ "objects": {
+ "outdoor-scene": {
+ "name": "野外综合训练环境",
+ "position": [
+ 0,
+ 0,
+ 0
+ ],
+ "rotation": [
+ 0,
+ 0,
+ 0
+ ],
+ "scale": [
+ 1,
+ 1,
+ 1
+ ],
+ "visible": true,
+ "customComponents": []
+ },
+ "outdoor-site": {
+ "name": "场地与道路",
+ "position": [
+ 0,
+ 0,
+ 0
+ ],
+ "rotation": [
+ 0,
+ 0,
+ 0
+ ],
+ "scale": [
+ 1,
+ 1,
+ 1
+ ],
+ "visible": true,
+ "customComponents": []
+ },
+ "outdoor-ground": {
+ "name": "室外训练场地",
+ "position": [
+ 0,
+ -0.065,
+ 0
+ ],
+ "rotation": [
+ -1.5707963267948966,
+ 0,
+ 0
+ ],
+ "scale": [
+ 1,
+ 1,
+ 1
+ ],
+ "visible": true,
+ "customComponents": [],
+ "materials": [
+ {
+ "color": "263529",
+ "emissive": "000000",
+ "emissiveIntensity": 1,
+ "metalness": 0,
+ "roughness": 0.98,
+ "opacity": 1,
+ "transparent": false,
+ "depthWrite": true
+ }
+ ]
+ },
+ "outdoor-apron": {
+ "name": "装备操作坪",
+ "position": [
+ 0,
+ -0.045,
+ 0
+ ],
+ "rotation": [
+ -1.5707963267948966,
+ 0,
+ 0
+ ],
+ "scale": [
+ 1,
+ 1,
+ 1
+ ],
+ "visible": true,
+ "customComponents": [],
+ "materials": [
+ {
+ "color": "39444a",
+ "emissive": "000000",
+ "emissiveIntensity": 1,
+ "metalness": 0.03,
+ "roughness": 0.92,
+ "opacity": 1,
+ "transparent": false,
+ "depthWrite": true
+ }
+ ]
+ },
+ "outdoor-road": {
+ "name": "保障通道",
+ "position": [
+ 0,
+ -0.035,
+ 17
+ ],
+ "rotation": [
+ -1.5707963267948966,
+ 0,
+ 0
+ ],
+ "scale": [
+ 1,
+ 1,
+ 1
+ ],
+ "visible": true,
+ "customComponents": [],
+ "materials": [
+ {
+ "color": "202a31",
+ "emissive": "000000",
+ "emissiveIntensity": 1,
+ "metalness": 0.02,
+ "roughness": 0.95,
+ "opacity": 1,
+ "transparent": false,
+ "depthWrite": true
+ }
+ ]
+ },
+ "outdoor-road-line-1": {
+ "name": "道路中心线 1",
+ "position": [
+ -28.699999999999996,
+ 0.01,
+ 17
+ ],
+ "rotation": [
+ 0,
+ 0,
+ 0
+ ],
+ "scale": [
+ 1,
+ 1,
+ 1
+ ],
+ "visible": true,
+ "customComponents": [],
+ "materials": [
+ {
+ "color": "e5c64c",
+ "emissive": "302600",
+ "emissiveIntensity": 0.18,
+ "metalness": 0.08,
+ "roughness": 0.72,
+ "opacity": 1,
+ "transparent": false,
+ "depthWrite": true
+ }
+ ]
+ },
+ "outdoor-road-line-2": {
+ "name": "道路中心线 2",
+ "position": [
+ -24.599999999999998,
+ 0.01,
+ 17
+ ],
+ "rotation": [
+ 0,
+ 0,
+ 0
+ ],
+ "scale": [
+ 1,
+ 1,
+ 1
+ ],
+ "visible": true,
+ "customComponents": [],
+ "materials": [
+ {
+ "color": "e5c64c",
+ "emissive": "302600",
+ "emissiveIntensity": 0.18,
+ "metalness": 0.08,
+ "roughness": 0.72,
+ "opacity": 1,
+ "transparent": false,
+ "depthWrite": true
+ }
+ ]
+ },
+ "outdoor-road-line-3": {
+ "name": "道路中心线 3",
+ "position": [
+ -20.5,
+ 0.01,
+ 17
+ ],
+ "rotation": [
+ 0,
+ 0,
+ 0
+ ],
+ "scale": [
+ 1,
+ 1,
+ 1
+ ],
+ "visible": true,
+ "customComponents": [],
+ "materials": [
+ {
+ "color": "e5c64c",
+ "emissive": "302600",
+ "emissiveIntensity": 0.18,
+ "metalness": 0.08,
+ "roughness": 0.72,
+ "opacity": 1,
+ "transparent": false,
+ "depthWrite": true
+ }
+ ]
+ },
+ "outdoor-road-line-4": {
+ "name": "道路中心线 4",
+ "position": [
+ -16.4,
+ 0.01,
+ 17
+ ],
+ "rotation": [
+ 0,
+ 0,
+ 0
+ ],
+ "scale": [
+ 1,
+ 1,
+ 1
+ ],
+ "visible": true,
+ "customComponents": [],
+ "materials": [
+ {
+ "color": "e5c64c",
+ "emissive": "302600",
+ "emissiveIntensity": 0.18,
+ "metalness": 0.08,
+ "roughness": 0.72,
+ "opacity": 1,
+ "transparent": false,
+ "depthWrite": true
+ }
+ ]
+ },
+ "outdoor-road-line-5": {
+ "name": "道路中心线 5",
+ "position": [
+ -12.299999999999999,
+ 0.01,
+ 17
+ ],
+ "rotation": [
+ 0,
+ 0,
+ 0
+ ],
+ "scale": [
+ 1,
+ 1,
+ 1
+ ],
+ "visible": true,
+ "customComponents": [],
+ "materials": [
+ {
+ "color": "e5c64c",
+ "emissive": "302600",
+ "emissiveIntensity": 0.18,
+ "metalness": 0.08,
+ "roughness": 0.72,
+ "opacity": 1,
+ "transparent": false,
+ "depthWrite": true
+ }
+ ]
+ },
+ "outdoor-road-line-6": {
+ "name": "道路中心线 6",
+ "position": [
+ -8.2,
+ 0.01,
+ 17
+ ],
+ "rotation": [
+ 0,
+ 0,
+ 0
+ ],
+ "scale": [
+ 1,
+ 1,
+ 1
+ ],
+ "visible": true,
+ "customComponents": [],
+ "materials": [
+ {
+ "color": "e5c64c",
+ "emissive": "302600",
+ "emissiveIntensity": 0.18,
+ "metalness": 0.08,
+ "roughness": 0.72,
+ "opacity": 1,
+ "transparent": false,
+ "depthWrite": true
+ }
+ ]
+ },
+ "outdoor-road-line-7": {
+ "name": "道路中心线 7",
+ "position": [
+ -4.1,
+ 0.01,
+ 17
+ ],
+ "rotation": [
+ 0,
+ 0,
+ 0
+ ],
+ "scale": [
+ 1,
+ 1,
+ 1
+ ],
+ "visible": true,
+ "customComponents": [],
+ "materials": [
+ {
+ "color": "e5c64c",
+ "emissive": "302600",
+ "emissiveIntensity": 0.18,
+ "metalness": 0.08,
+ "roughness": 0.72,
+ "opacity": 1,
+ "transparent": false,
+ "depthWrite": true
+ }
+ ]
+ },
+ "outdoor-road-line-8": {
+ "name": "道路中心线 8",
+ "position": [
+ 0,
+ 0.01,
+ 17
+ ],
+ "rotation": [
+ 0,
+ 0,
+ 0
+ ],
+ "scale": [
+ 1,
+ 1,
+ 1
+ ],
+ "visible": true,
+ "customComponents": [],
+ "materials": [
+ {
+ "color": "e5c64c",
+ "emissive": "302600",
+ "emissiveIntensity": 0.18,
+ "metalness": 0.08,
+ "roughness": 0.72,
+ "opacity": 1,
+ "transparent": false,
+ "depthWrite": true
+ }
+ ]
+ },
+ "outdoor-road-line-9": {
+ "name": "道路中心线 9",
+ "position": [
+ 4.1,
+ 0.01,
+ 17
+ ],
+ "rotation": [
+ 0,
+ 0,
+ 0
+ ],
+ "scale": [
+ 1,
+ 1,
+ 1
+ ],
+ "visible": true,
+ "customComponents": [],
+ "materials": [
+ {
+ "color": "e5c64c",
+ "emissive": "302600",
+ "emissiveIntensity": 0.18,
+ "metalness": 0.08,
+ "roughness": 0.72,
+ "opacity": 1,
+ "transparent": false,
+ "depthWrite": true
+ }
+ ]
+ },
+ "outdoor-road-line-10": {
+ "name": "道路中心线 10",
+ "position": [
+ 8.2,
+ 0.01,
+ 17
+ ],
+ "rotation": [
+ 0,
+ 0,
+ 0
+ ],
+ "scale": [
+ 1,
+ 1,
+ 1
+ ],
+ "visible": true,
+ "customComponents": [],
+ "materials": [
+ {
+ "color": "e5c64c",
+ "emissive": "302600",
+ "emissiveIntensity": 0.18,
+ "metalness": 0.08,
+ "roughness": 0.72,
+ "opacity": 1,
+ "transparent": false,
+ "depthWrite": true
+ }
+ ]
+ },
+ "outdoor-road-line-11": {
+ "name": "道路中心线 11",
+ "position": [
+ 12.299999999999999,
+ 0.01,
+ 17
+ ],
+ "rotation": [
+ 0,
+ 0,
+ 0
+ ],
+ "scale": [
+ 1,
+ 1,
+ 1
+ ],
+ "visible": true,
+ "customComponents": [],
+ "materials": [
+ {
+ "color": "e5c64c",
+ "emissive": "302600",
+ "emissiveIntensity": 0.18,
+ "metalness": 0.08,
+ "roughness": 0.72,
+ "opacity": 1,
+ "transparent": false,
+ "depthWrite": true
+ }
+ ]
+ },
+ "outdoor-road-line-12": {
+ "name": "道路中心线 12",
+ "position": [
+ 16.4,
+ 0.01,
+ 17
+ ],
+ "rotation": [
+ 0,
+ 0,
+ 0
+ ],
+ "scale": [
+ 1,
+ 1,
+ 1
+ ],
+ "visible": true,
+ "customComponents": [],
+ "materials": [
+ {
+ "color": "e5c64c",
+ "emissive": "302600",
+ "emissiveIntensity": 0.18,
+ "metalness": 0.08,
+ "roughness": 0.72,
+ "opacity": 1,
+ "transparent": false,
+ "depthWrite": true
+ }
+ ]
+ },
+ "outdoor-road-line-13": {
+ "name": "道路中心线 13",
+ "position": [
+ 20.5,
+ 0.01,
+ 17
+ ],
+ "rotation": [
+ 0,
+ 0,
+ 0
+ ],
+ "scale": [
+ 1,
+ 1,
+ 1
+ ],
+ "visible": true,
+ "customComponents": [],
+ "materials": [
+ {
+ "color": "e5c64c",
+ "emissive": "302600",
+ "emissiveIntensity": 0.18,
+ "metalness": 0.08,
+ "roughness": 0.72,
+ "opacity": 1,
+ "transparent": false,
+ "depthWrite": true
+ }
+ ]
+ },
+ "outdoor-road-line-14": {
+ "name": "道路中心线 14",
+ "position": [
+ 24.599999999999998,
+ 0.01,
+ 17
+ ],
+ "rotation": [
+ 0,
+ 0,
+ 0
+ ],
+ "scale": [
+ 1,
+ 1,
+ 1
+ ],
+ "visible": true,
+ "customComponents": [],
+ "materials": [
+ {
+ "color": "e5c64c",
+ "emissive": "302600",
+ "emissiveIntensity": 0.18,
+ "metalness": 0.08,
+ "roughness": 0.72,
+ "opacity": 1,
+ "transparent": false,
+ "depthWrite": true
+ }
+ ]
+ },
+ "outdoor-road-line-15": {
+ "name": "道路中心线 15",
+ "position": [
+ 28.699999999999996,
+ 0.01,
+ 17
+ ],
+ "rotation": [
+ 0,
+ 0,
+ 0
+ ],
+ "scale": [
+ 1,
+ 1,
+ 1
+ ],
+ "visible": true,
+ "customComponents": [],
+ "materials": [
+ {
+ "color": "e5c64c",
+ "emissive": "302600",
+ "emissiveIntensity": 0.18,
+ "metalness": 0.08,
+ "roughness": 0.72,
+ "opacity": 1,
+ "transparent": false,
+ "depthWrite": true
+ }
+ ]
+ },
+ "outdoor-safety--17.6--11": {
+ "name": "操作区安全标线",
+ "position": [
+ -17.6,
+ 0.012,
+ -11
+ ],
+ "rotation": [
+ 0,
+ 0.5654866776461628,
+ 0
+ ],
+ "scale": [
+ 1,
+ 1,
+ 1
+ ],
+ "visible": true,
+ "customComponents": [],
+ "materials": [
+ {
+ "color": "e5c64c",
+ "emissive": "302600",
+ "emissiveIntensity": 0.18,
+ "metalness": 0.08,
+ "roughness": 0.72,
+ "opacity": 1,
+ "transparent": false,
+ "depthWrite": true
+ }
+ ]
+ },
+ "outdoor-safety--17.6--8.4": {
+ "name": "操作区安全标线",
+ "position": [
+ -17.6,
+ 0.012,
+ -8.4
+ ],
+ "rotation": [
+ 0,
+ 0.5654866776461628,
+ 0
+ ],
+ "scale": [
+ 1,
+ 1,
+ 1
+ ],
+ "visible": true,
+ "customComponents": [],
+ "materials": [
+ {
+ "color": "e5c64c",
+ "emissive": "302600",
+ "emissiveIntensity": 0.18,
+ "metalness": 0.08,
+ "roughness": 0.72,
+ "opacity": 1,
+ "transparent": false,
+ "depthWrite": true
+ }
+ ]
+ },
+ "outdoor-safety--17.6--5.800000000000001": {
+ "name": "操作区安全标线",
+ "position": [
+ -17.6,
+ 0.012,
+ -5.800000000000001
+ ],
+ "rotation": [
+ 0,
+ 0.5654866776461628,
+ 0
+ ],
+ "scale": [
+ 1,
+ 1,
+ 1
+ ],
+ "visible": true,
+ "customComponents": [],
+ "materials": [
+ {
+ "color": "e5c64c",
+ "emissive": "302600",
+ "emissiveIntensity": 0.18,
+ "metalness": 0.08,
+ "roughness": 0.72,
+ "opacity": 1,
+ "transparent": false,
+ "depthWrite": true
+ }
+ ]
+ },
+ "outdoor-safety--17.6--3.2000000000000006": {
+ "name": "操作区安全标线",
+ "position": [
+ -17.6,
+ 0.012,
+ -3.2000000000000006
+ ],
+ "rotation": [
+ 0,
+ 0.5654866776461628,
+ 0
+ ],
+ "scale": [
+ 1,
+ 1,
+ 1
+ ],
+ "visible": true,
+ "customComponents": [],
+ "materials": [
+ {
+ "color": "e5c64c",
+ "emissive": "302600",
+ "emissiveIntensity": 0.18,
+ "metalness": 0.08,
+ "roughness": 0.72,
+ "opacity": 1,
+ "transparent": false,
+ "depthWrite": true
+ }
+ ]
+ },
+ "outdoor-safety--17.6--0.6000000000000005": {
+ "name": "操作区安全标线",
+ "position": [
+ -17.6,
+ 0.012,
+ -0.6000000000000005
+ ],
+ "rotation": [
+ 0,
+ 0.5654866776461628,
+ 0
+ ],
+ "scale": [
+ 1,
+ 1,
+ 1
+ ],
+ "visible": true,
+ "customComponents": [],
+ "materials": [
+ {
+ "color": "e5c64c",
+ "emissive": "302600",
+ "emissiveIntensity": 0.18,
+ "metalness": 0.08,
+ "roughness": 0.72,
+ "opacity": 1,
+ "transparent": false,
+ "depthWrite": true
+ }
+ ]
+ },
+ "outdoor-safety--17.6-1.9999999999999996": {
+ "name": "操作区安全标线",
+ "position": [
+ -17.6,
+ 0.012,
+ 1.9999999999999996
+ ],
+ "rotation": [
+ 0,
+ 0.5654866776461628,
+ 0
+ ],
+ "scale": [
+ 1,
+ 1,
+ 1
+ ],
+ "visible": true,
+ "customComponents": [],
+ "materials": [
+ {
+ "color": "e5c64c",
+ "emissive": "302600",
+ "emissiveIntensity": 0.18,
+ "metalness": 0.08,
+ "roughness": 0.72,
+ "opacity": 1,
+ "transparent": false,
+ "depthWrite": true
+ }
+ ]
+ },
+ "outdoor-safety--17.6-4.6": {
+ "name": "操作区安全标线",
+ "position": [
+ -17.6,
+ 0.012,
+ 4.6
+ ],
+ "rotation": [
+ 0,
+ 0.5654866776461628,
+ 0
+ ],
+ "scale": [
+ 1,
+ 1,
+ 1
+ ],
+ "visible": true,
+ "customComponents": [],
+ "materials": [
+ {
+ "color": "e5c64c",
+ "emissive": "302600",
+ "emissiveIntensity": 0.18,
+ "metalness": 0.08,
+ "roughness": 0.72,
+ "opacity": 1,
+ "transparent": false,
+ "depthWrite": true
+ }
+ ]
+ },
+ "outdoor-safety--17.6-7.199999999999999": {
+ "name": "操作区安全标线",
+ "position": [
+ -17.6,
+ 0.012,
+ 7.199999999999999
+ ],
+ "rotation": [
+ 0,
+ 0.5654866776461628,
+ 0
+ ],
+ "scale": [
+ 1,
+ 1,
+ 1
+ ],
+ "visible": true,
+ "customComponents": [],
+ "materials": [
+ {
+ "color": "e5c64c",
+ "emissive": "302600",
+ "emissiveIntensity": 0.18,
+ "metalness": 0.08,
+ "roughness": 0.72,
+ "opacity": 1,
+ "transparent": false,
+ "depthWrite": true
+ }
+ ]
+ },
+ "outdoor-safety--17.6-9.799999999999999": {
+ "name": "操作区安全标线",
+ "position": [
+ -17.6,
+ 0.012,
+ 9.799999999999999
+ ],
+ "rotation": [
+ 0,
+ 0.5654866776461628,
+ 0
+ ],
+ "scale": [
+ 1,
+ 1,
+ 1
+ ],
+ "visible": true,
+ "customComponents": [],
+ "materials": [
+ {
+ "color": "e5c64c",
+ "emissive": "302600",
+ "emissiveIntensity": 0.18,
+ "metalness": 0.08,
+ "roughness": 0.72,
+ "opacity": 1,
+ "transparent": false,
+ "depthWrite": true
+ }
+ ]
+ },
+ "outdoor-safety-17.6--11": {
+ "name": "操作区安全标线",
+ "position": [
+ 17.6,
+ 0.012,
+ -11
+ ],
+ "rotation": [
+ 0,
+ 0.5654866776461628,
+ 0
+ ],
+ "scale": [
+ 1,
+ 1,
+ 1
+ ],
+ "visible": true,
+ "customComponents": [],
+ "materials": [
+ {
+ "color": "e5c64c",
+ "emissive": "302600",
+ "emissiveIntensity": 0.18,
+ "metalness": 0.08,
+ "roughness": 0.72,
+ "opacity": 1,
+ "transparent": false,
+ "depthWrite": true
+ }
+ ]
+ },
+ "outdoor-safety-17.6--8.4": {
+ "name": "操作区安全标线",
+ "position": [
+ 17.6,
+ 0.012,
+ -8.4
+ ],
+ "rotation": [
+ 0,
+ 0.5654866776461628,
+ 0
+ ],
+ "scale": [
+ 1,
+ 1,
+ 1
+ ],
+ "visible": true,
+ "customComponents": [],
+ "materials": [
+ {
+ "color": "e5c64c",
+ "emissive": "302600",
+ "emissiveIntensity": 0.18,
+ "metalness": 0.08,
+ "roughness": 0.72,
+ "opacity": 1,
+ "transparent": false,
+ "depthWrite": true
+ }
+ ]
+ },
+ "outdoor-safety-17.6--5.800000000000001": {
+ "name": "操作区安全标线",
+ "position": [
+ 17.6,
+ 0.012,
+ -5.800000000000001
+ ],
+ "rotation": [
+ 0,
+ 0.5654866776461628,
+ 0
+ ],
+ "scale": [
+ 1,
+ 1,
+ 1
+ ],
+ "visible": true,
+ "customComponents": [],
+ "materials": [
+ {
+ "color": "e5c64c",
+ "emissive": "302600",
+ "emissiveIntensity": 0.18,
+ "metalness": 0.08,
+ "roughness": 0.72,
+ "opacity": 1,
+ "transparent": false,
+ "depthWrite": true
+ }
+ ]
+ },
+ "outdoor-safety-17.6--3.2000000000000006": {
+ "name": "操作区安全标线",
+ "position": [
+ 17.6,
+ 0.012,
+ -3.2000000000000006
+ ],
+ "rotation": [
+ 0,
+ 0.5654866776461628,
+ 0
+ ],
+ "scale": [
+ 1,
+ 1,
+ 1
+ ],
+ "visible": true,
+ "customComponents": [],
+ "materials": [
+ {
+ "color": "e5c64c",
+ "emissive": "302600",
+ "emissiveIntensity": 0.18,
+ "metalness": 0.08,
+ "roughness": 0.72,
+ "opacity": 1,
+ "transparent": false,
+ "depthWrite": true
+ }
+ ]
+ },
+ "outdoor-safety-17.6--0.6000000000000005": {
+ "name": "操作区安全标线",
+ "position": [
+ 17.6,
+ 0.012,
+ -0.6000000000000005
+ ],
+ "rotation": [
+ 0,
+ 0.5654866776461628,
+ 0
+ ],
+ "scale": [
+ 1,
+ 1,
+ 1
+ ],
+ "visible": true,
+ "customComponents": [],
+ "materials": [
+ {
+ "color": "e5c64c",
+ "emissive": "302600",
+ "emissiveIntensity": 0.18,
+ "metalness": 0.08,
+ "roughness": 0.72,
+ "opacity": 1,
+ "transparent": false,
+ "depthWrite": true
+ }
+ ]
+ },
+ "outdoor-safety-17.6-1.9999999999999996": {
+ "name": "操作区安全标线",
+ "position": [
+ 17.6,
+ 0.012,
+ 1.9999999999999996
+ ],
+ "rotation": [
+ 0,
+ 0.5654866776461628,
+ 0
+ ],
+ "scale": [
+ 1,
+ 1,
+ 1
+ ],
+ "visible": true,
+ "customComponents": [],
+ "materials": [
+ {
+ "color": "e5c64c",
+ "emissive": "302600",
+ "emissiveIntensity": 0.18,
+ "metalness": 0.08,
+ "roughness": 0.72,
+ "opacity": 1,
+ "transparent": false,
+ "depthWrite": true
+ }
+ ]
+ },
+ "outdoor-safety-17.6-4.6": {
+ "name": "操作区安全标线",
+ "position": [
+ 17.6,
+ 0.012,
+ 4.6
+ ],
+ "rotation": [
+ 0,
+ 0.5654866776461628,
+ 0
+ ],
+ "scale": [
+ 1,
+ 1,
+ 1
+ ],
+ "visible": true,
+ "customComponents": [],
+ "materials": [
+ {
+ "color": "e5c64c",
+ "emissive": "302600",
+ "emissiveIntensity": 0.18,
+ "metalness": 0.08,
+ "roughness": 0.72,
+ "opacity": 1,
+ "transparent": false,
+ "depthWrite": true
+ }
+ ]
+ },
+ "outdoor-safety-17.6-7.199999999999999": {
+ "name": "操作区安全标线",
+ "position": [
+ 17.6,
+ 0.012,
+ 7.199999999999999
+ ],
+ "rotation": [
+ 0,
+ 0.5654866776461628,
+ 0
+ ],
+ "scale": [
+ 1,
+ 1,
+ 1
+ ],
+ "visible": true,
+ "customComponents": [],
+ "materials": [
+ {
+ "color": "e5c64c",
+ "emissive": "302600",
+ "emissiveIntensity": 0.18,
+ "metalness": 0.08,
+ "roughness": 0.72,
+ "opacity": 1,
+ "transparent": false,
+ "depthWrite": true
+ }
+ ]
+ },
+ "outdoor-safety-17.6-9.799999999999999": {
+ "name": "操作区安全标线",
+ "position": [
+ 17.6,
+ 0.012,
+ 9.799999999999999
+ ],
+ "rotation": [
+ 0,
+ 0.5654866776461628,
+ 0
+ ],
+ "scale": [
+ 1,
+ 1,
+ 1
+ ],
+ "visible": true,
+ "customComponents": [],
+ "materials": [
+ {
+ "color": "e5c64c",
+ "emissive": "302600",
+ "emissiveIntensity": 0.18,
+ "metalness": 0.08,
+ "roughness": 0.72,
+ "opacity": 1,
+ "transparent": false,
+ "depthWrite": true
+ }
+ ]
+ },
+ "outdoor-workshop": {
+ "name": "维修保障厂房",
+ "position": [
+ 0,
+ 0,
+ 0
+ ],
+ "rotation": [
+ 0,
+ 0,
+ 0
+ ],
+ "scale": [
+ 1,
+ 1,
+ 1
+ ],
+ "visible": true,
+ "customComponents": []
+ },
+ "outdoor-workshop-body": {
+ "name": "维修保障厂房主体",
+ "position": [
+ -10,
+ 3.25,
+ -18
+ ],
+ "rotation": [
+ 0,
+ 0,
+ 0
+ ],
+ "scale": [
+ 1,
+ 1,
+ 1
+ ],
+ "visible": true,
+ "customComponents": [],
+ "materials": [
+ {
+ "color": "536675",
+ "emissive": "000000",
+ "emissiveIntensity": 1,
+ "metalness": 0.18,
+ "roughness": 0.78,
+ "opacity": 1,
+ "transparent": false,
+ "depthWrite": true
+ }
+ ]
+ },
+ "outdoor-workshop-roof": {
+ "name": "厂房金属屋面",
+ "position": [
+ -10,
+ 6.65,
+ -18
+ ],
+ "rotation": [
+ 0,
+ 0,
+ 0
+ ],
+ "scale": [
+ 1,
+ 1,
+ 1
+ ],
+ "visible": true,
+ "customComponents": [],
+ "materials": [
+ {
+ "color": "263846",
+ "emissive": "000000",
+ "emissiveIntensity": 1,
+ "metalness": 0.35,
+ "roughness": 0.62,
+ "opacity": 1,
+ "transparent": false,
+ "depthWrite": true
+ }
+ ]
+ },
+ "outdoor-workshop-window-1": {
+ "name": "厂房观察窗 1",
+ "position": [
+ -17,
+ 3.7,
+ -14.21
+ ],
+ "rotation": [
+ 0,
+ 0,
+ 0
+ ],
+ "scale": [
+ 1,
+ 1,
+ 1
+ ],
+ "visible": true,
+ "customComponents": [],
+ "materials": [
+ {
+ "color": "4a89a2",
+ "emissive": "000000",
+ "emissiveIntensity": 1,
+ "metalness": 0.12,
+ "roughness": 0.25,
+ "opacity": 0.68,
+ "transparent": true,
+ "depthWrite": true
+ }
+ ]
+ },
+ "outdoor-workshop-window-2": {
+ "name": "厂房观察窗 2",
+ "position": [
+ -12.4,
+ 3.7,
+ -14.21
+ ],
+ "rotation": [
+ 0,
+ 0,
+ 0
+ ],
+ "scale": [
+ 1,
+ 1,
+ 1
+ ],
+ "visible": true,
+ "customComponents": [],
+ "materials": [
+ {
+ "color": "4a89a2",
+ "emissive": "000000",
+ "emissiveIntensity": 1,
+ "metalness": 0.12,
+ "roughness": 0.25,
+ "opacity": 0.68,
+ "transparent": true,
+ "depthWrite": true
+ }
+ ]
+ },
+ "outdoor-workshop-window-3": {
+ "name": "厂房观察窗 3",
+ "position": [
+ -7.800000000000001,
+ 3.7,
+ -14.21
+ ],
+ "rotation": [
+ 0,
+ 0,
+ 0
+ ],
+ "scale": [
+ 1,
+ 1,
+ 1
+ ],
+ "visible": true,
+ "customComponents": [],
+ "materials": [
+ {
+ "color": "4a89a2",
+ "emissive": "000000",
+ "emissiveIntensity": 1,
+ "metalness": 0.12,
+ "roughness": 0.25,
+ "opacity": 0.68,
+ "transparent": true,
+ "depthWrite": true
+ }
+ ]
+ },
+ "outdoor-workshop-window-4": {
+ "name": "厂房观察窗 4",
+ "position": [
+ -3.200000000000001,
+ 3.7,
+ -14.21
+ ],
+ "rotation": [
+ 0,
+ 0,
+ 0
+ ],
+ "scale": [
+ 1,
+ 1,
+ 1
+ ],
+ "visible": true,
+ "customComponents": [],
+ "materials": [
+ {
+ "color": "4a89a2",
+ "emissive": "000000",
+ "emissiveIntensity": 1,
+ "metalness": 0.12,
+ "roughness": 0.25,
+ "opacity": 0.68,
+ "transparent": true,
+ "depthWrite": true
+ }
+ ]
+ },
+ "outdoor-workshop-door": {
+ "name": "装备出入门",
+ "position": [
+ -3.6,
+ 2.16,
+ -14.18
+ ],
+ "rotation": [
+ 0,
+ 0,
+ 0
+ ],
+ "scale": [
+ 1,
+ 1,
+ 1
+ ],
+ "visible": true,
+ "customComponents": [],
+ "materials": [
+ {
+ "color": "202a31",
+ "emissive": "000000",
+ "emissiveIntensity": 1,
+ "metalness": 0.02,
+ "roughness": 0.95,
+ "opacity": 1,
+ "transparent": false,
+ "depthWrite": true
+ }
+ ]
+ },
+ "outdoor-canopy": {
+ "name": "室外检修棚",
+ "position": [
+ 0,
+ 0,
+ 0
+ ],
+ "rotation": [
+ 0,
+ 0,
+ 0
+ ],
+ "scale": [
+ 1,
+ 1,
+ 1
+ ],
+ "visible": true,
+ "customComponents": []
+ },
+ "outdoor-canopy-roof": {
+ "name": "检修棚顶",
+ "position": [
+ 18,
+ 5.05,
+ -8
+ ],
+ "rotation": [
+ 0,
+ 0,
+ 0
+ ],
+ "scale": [
+ 1,
+ 1,
+ 1
+ ],
+ "visible": true,
+ "customComponents": [],
+ "materials": [
+ {
+ "color": "263846",
+ "emissive": "000000",
+ "emissiveIntensity": 1,
+ "metalness": 0.35,
+ "roughness": 0.62,
+ "opacity": 1,
+ "transparent": false,
+ "depthWrite": true
+ }
+ ]
+ },
+ "outdoor-canopy-column-1": {
+ "name": "检修棚立柱 1",
+ "position": [
+ 12.7,
+ 2.5,
+ -11
+ ],
+ "rotation": [
+ 0,
+ 0,
+ 0
+ ],
+ "scale": [
+ 1,
+ 1,
+ 1
+ ],
+ "visible": true,
+ "customComponents": [],
+ "materials": [
+ {
+ "color": "66757c",
+ "emissive": "000000",
+ "emissiveIntensity": 1,
+ "metalness": 0.48,
+ "roughness": 0.56,
+ "opacity": 1,
+ "transparent": false,
+ "depthWrite": true
+ }
+ ]
+ },
+ "outdoor-canopy-column-2": {
+ "name": "检修棚立柱 2",
+ "position": [
+ 23.3,
+ 2.5,
+ -11
+ ],
+ "rotation": [
+ 0,
+ 0,
+ 0
+ ],
+ "scale": [
+ 1,
+ 1,
+ 1
+ ],
+ "visible": true,
+ "customComponents": [],
+ "materials": [
+ {
+ "color": "66757c",
+ "emissive": "000000",
+ "emissiveIntensity": 1,
+ "metalness": 0.48,
+ "roughness": 0.56,
+ "opacity": 1,
+ "transparent": false,
+ "depthWrite": true
+ }
+ ]
+ },
+ "outdoor-canopy-column-3": {
+ "name": "检修棚立柱 3",
+ "position": [
+ 12.7,
+ 2.5,
+ -5
+ ],
+ "rotation": [
+ 0,
+ 0,
+ 0
+ ],
+ "scale": [
+ 1,
+ 1,
+ 1
+ ],
+ "visible": true,
+ "customComponents": [],
+ "materials": [
+ {
+ "color": "66757c",
+ "emissive": "000000",
+ "emissiveIntensity": 1,
+ "metalness": 0.48,
+ "roughness": 0.56,
+ "opacity": 1,
+ "transparent": false,
+ "depthWrite": true
+ }
+ ]
+ },
+ "outdoor-canopy-column-4": {
+ "name": "检修棚立柱 4",
+ "position": [
+ 23.3,
+ 2.5,
+ -5
+ ],
+ "rotation": [
+ 0,
+ 0,
+ 0
+ ],
+ "scale": [
+ 1,
+ 1,
+ 1
+ ],
+ "visible": true,
+ "customComponents": [],
+ "materials": [
+ {
+ "color": "66757c",
+ "emissive": "000000",
+ "emissiveIntensity": 1,
+ "metalness": 0.48,
+ "roughness": 0.56,
+ "opacity": 1,
+ "transparent": false,
+ "depthWrite": true
+ }
+ ]
+ },
+ "outdoor-service-box": {
+ "name": "室外工具柜",
+ "position": [
+ 22.2,
+ 1.05,
+ -9.8
+ ],
+ "rotation": [
+ 0,
+ 0,
+ 0
+ ],
+ "scale": [
+ 1,
+ 1,
+ 1
+ ],
+ "visible": true,
+ "customComponents": [],
+ "materials": [
+ {
+ "color": "d98d2b",
+ "emissive": "000000",
+ "emissiveIntensity": 1,
+ "metalness": 0.12,
+ "roughness": 0.7,
+ "opacity": 1,
+ "transparent": false,
+ "depthWrite": true
+ }
+ ]
+ },
+ "outdoor-perimeter": {
+ "name": "场地边界与安全设施",
+ "position": [
+ 0,
+ 0,
+ 0
+ ],
+ "rotation": [
+ 0,
+ 0,
+ 0
+ ],
+ "scale": [
+ 1,
+ 1,
+ 1
+ ],
+ "visible": true,
+ "customComponents": []
+ },
+ "outdoor-fence-back-1": {
+ "name": "后侧围栏立柱 1",
+ "position": [
+ -29,
+ 1.1,
+ -25
+ ],
+ "rotation": [
+ 0,
+ 0,
+ 0
+ ],
+ "scale": [
+ 1,
+ 1,
+ 1
+ ],
+ "visible": true,
+ "customComponents": [],
+ "materials": [
+ {
+ "color": "66757c",
+ "emissive": "000000",
+ "emissiveIntensity": 1,
+ "metalness": 0.48,
+ "roughness": 0.56,
+ "opacity": 1,
+ "transparent": false,
+ "depthWrite": true
+ }
+ ]
+ },
+ "outdoor-fence-back-2": {
+ "name": "后侧围栏立柱 2",
+ "position": [
+ -23.2,
+ 1.1,
+ -25
+ ],
+ "rotation": [
+ 0,
+ 0,
+ 0
+ ],
+ "scale": [
+ 1,
+ 1,
+ 1
+ ],
+ "visible": true,
+ "customComponents": [],
+ "materials": [
+ {
+ "color": "66757c",
+ "emissive": "000000",
+ "emissiveIntensity": 1,
+ "metalness": 0.48,
+ "roughness": 0.56,
+ "opacity": 1,
+ "transparent": false,
+ "depthWrite": true
+ }
+ ]
+ },
+ "outdoor-fence-back-3": {
+ "name": "后侧围栏立柱 3",
+ "position": [
+ -17.4,
+ 1.1,
+ -25
+ ],
+ "rotation": [
+ 0,
+ 0,
+ 0
+ ],
+ "scale": [
+ 1,
+ 1,
+ 1
+ ],
+ "visible": true,
+ "customComponents": [],
+ "materials": [
+ {
+ "color": "66757c",
+ "emissive": "000000",
+ "emissiveIntensity": 1,
+ "metalness": 0.48,
+ "roughness": 0.56,
+ "opacity": 1,
+ "transparent": false,
+ "depthWrite": true
+ }
+ ]
+ },
+ "outdoor-fence-back-4": {
+ "name": "后侧围栏立柱 4",
+ "position": [
+ -11.6,
+ 1.1,
+ -25
+ ],
+ "rotation": [
+ 0,
+ 0,
+ 0
+ ],
+ "scale": [
+ 1,
+ 1,
+ 1
+ ],
+ "visible": true,
+ "customComponents": [],
+ "materials": [
+ {
+ "color": "66757c",
+ "emissive": "000000",
+ "emissiveIntensity": 1,
+ "metalness": 0.48,
+ "roughness": 0.56,
+ "opacity": 1,
+ "transparent": false,
+ "depthWrite": true
+ }
+ ]
+ },
+ "outdoor-fence-back-5": {
+ "name": "后侧围栏立柱 5",
+ "position": [
+ -5.8,
+ 1.1,
+ -25
+ ],
+ "rotation": [
+ 0,
+ 0,
+ 0
+ ],
+ "scale": [
+ 1,
+ 1,
+ 1
+ ],
+ "visible": true,
+ "customComponents": [],
+ "materials": [
+ {
+ "color": "66757c",
+ "emissive": "000000",
+ "emissiveIntensity": 1,
+ "metalness": 0.48,
+ "roughness": 0.56,
+ "opacity": 1,
+ "transparent": false,
+ "depthWrite": true
+ }
+ ]
+ },
+ "outdoor-fence-back-6": {
+ "name": "后侧围栏立柱 6",
+ "position": [
+ 0,
+ 1.1,
+ -25
+ ],
+ "rotation": [
+ 0,
+ 0,
+ 0
+ ],
+ "scale": [
+ 1,
+ 1,
+ 1
+ ],
+ "visible": true,
+ "customComponents": [],
+ "materials": [
+ {
+ "color": "66757c",
+ "emissive": "000000",
+ "emissiveIntensity": 1,
+ "metalness": 0.48,
+ "roughness": 0.56,
+ "opacity": 1,
+ "transparent": false,
+ "depthWrite": true
+ }
+ ]
+ },
+ "outdoor-fence-back-7": {
+ "name": "后侧围栏立柱 7",
+ "position": [
+ 5.8,
+ 1.1,
+ -25
+ ],
+ "rotation": [
+ 0,
+ 0,
+ 0
+ ],
+ "scale": [
+ 1,
+ 1,
+ 1
+ ],
+ "visible": true,
+ "customComponents": [],
+ "materials": [
+ {
+ "color": "66757c",
+ "emissive": "000000",
+ "emissiveIntensity": 1,
+ "metalness": 0.48,
+ "roughness": 0.56,
+ "opacity": 1,
+ "transparent": false,
+ "depthWrite": true
+ }
+ ]
+ },
+ "outdoor-fence-back-8": {
+ "name": "后侧围栏立柱 8",
+ "position": [
+ 11.6,
+ 1.1,
+ -25
+ ],
+ "rotation": [
+ 0,
+ 0,
+ 0
+ ],
+ "scale": [
+ 1,
+ 1,
+ 1
+ ],
+ "visible": true,
+ "customComponents": [],
+ "materials": [
+ {
+ "color": "66757c",
+ "emissive": "000000",
+ "emissiveIntensity": 1,
+ "metalness": 0.48,
+ "roughness": 0.56,
+ "opacity": 1,
+ "transparent": false,
+ "depthWrite": true
+ }
+ ]
+ },
+ "outdoor-fence-back-9": {
+ "name": "后侧围栏立柱 9",
+ "position": [
+ 17.4,
+ 1.1,
+ -25
+ ],
+ "rotation": [
+ 0,
+ 0,
+ 0
+ ],
+ "scale": [
+ 1,
+ 1,
+ 1
+ ],
+ "visible": true,
+ "customComponents": [],
+ "materials": [
+ {
+ "color": "66757c",
+ "emissive": "000000",
+ "emissiveIntensity": 1,
+ "metalness": 0.48,
+ "roughness": 0.56,
+ "opacity": 1,
+ "transparent": false,
+ "depthWrite": true
+ }
+ ]
+ },
+ "outdoor-fence-back-10": {
+ "name": "后侧围栏立柱 10",
+ "position": [
+ 23.2,
+ 1.1,
+ -25
+ ],
+ "rotation": [
+ 0,
+ 0,
+ 0
+ ],
+ "scale": [
+ 1,
+ 1,
+ 1
+ ],
+ "visible": true,
+ "customComponents": [],
+ "materials": [
+ {
+ "color": "66757c",
+ "emissive": "000000",
+ "emissiveIntensity": 1,
+ "metalness": 0.48,
+ "roughness": 0.56,
+ "opacity": 1,
+ "transparent": false,
+ "depthWrite": true
+ }
+ ]
+ },
+ "outdoor-fence-back-11": {
+ "name": "后侧围栏立柱 11",
+ "position": [
+ 29,
+ 1.1,
+ -25
+ ],
+ "rotation": [
+ 0,
+ 0,
+ 0
+ ],
+ "scale": [
+ 1,
+ 1,
+ 1
+ ],
+ "visible": true,
+ "customComponents": [],
+ "materials": [
+ {
+ "color": "66757c",
+ "emissive": "000000",
+ "emissiveIntensity": 1,
+ "metalness": 0.48,
+ "roughness": 0.56,
+ "opacity": 1,
+ "transparent": false,
+ "depthWrite": true
+ }
+ ]
+ },
+ "outdoor-fence-rail-a": {
+ "name": "后侧围栏上横杆",
+ "position": [
+ 0,
+ 1.85,
+ -25
+ ],
+ "rotation": [
+ 0,
+ 0,
+ 0
+ ],
+ "scale": [
+ 1,
+ 1,
+ 1
+ ],
+ "visible": true,
+ "customComponents": [],
+ "materials": [
+ {
+ "color": "66757c",
+ "emissive": "000000",
+ "emissiveIntensity": 1,
+ "metalness": 0.48,
+ "roughness": 0.56,
+ "opacity": 1,
+ "transparent": false,
+ "depthWrite": true
+ }
+ ]
+ },
+ "outdoor-fence-rail-b": {
+ "name": "后侧围栏下横杆",
+ "position": [
+ 0,
+ 0.75,
+ -25
+ ],
+ "rotation": [
+ 0,
+ 0,
+ 0
+ ],
+ "scale": [
+ 1,
+ 1,
+ 1
+ ],
+ "visible": true,
+ "customComponents": [],
+ "materials": [
+ {
+ "color": "66757c",
+ "emissive": "000000",
+ "emissiveIntensity": 1,
+ "metalness": 0.48,
+ "roughness": 0.56,
+ "opacity": 1,
+ "transparent": false,
+ "depthWrite": true
+ }
+ ]
+ },
+ "outdoor-scene-sign": {
+ "name": "室外实训区域标识",
+ "position": [
+ 26,
+ 2.4,
+ 11.5
+ ],
+ "rotation": [
+ 0,
+ 0,
+ 0
+ ],
+ "scale": [
+ 1,
+ 1,
+ 1
+ ],
+ "visible": true,
+ "customComponents": [],
+ "materials": [
+ {
+ "color": "536675",
+ "emissive": "000000",
+ "emissiveIntensity": 1,
+ "metalness": 0.18,
+ "roughness": 0.78,
+ "opacity": 1,
+ "transparent": false,
+ "depthWrite": true
+ }
+ ]
+ },
+ "outdoor-scene-sign-post-a": {
+ "name": "区域标识支柱 A",
+ "position": [
+ 24,
+ 1.4,
+ 11.5
+ ],
+ "rotation": [
+ 0,
+ 0,
+ 0
+ ],
+ "scale": [
+ 1,
+ 1,
+ 1
+ ],
+ "visible": true,
+ "customComponents": [],
+ "materials": [
+ {
+ "color": "66757c",
+ "emissive": "000000",
+ "emissiveIntensity": 1,
+ "metalness": 0.48,
+ "roughness": 0.56,
+ "opacity": 1,
+ "transparent": false,
+ "depthWrite": true
+ }
+ ]
+ },
+ "outdoor-scene-sign-post-b": {
+ "name": "区域标识支柱 B",
+ "position": [
+ 28,
+ 1.4,
+ 11.5
+ ],
+ "rotation": [
+ 0,
+ 0,
+ 0
+ ],
+ "scale": [
+ 1,
+ 1,
+ 1
+ ],
+ "visible": true,
+ "customComponents": [],
+ "materials": [
+ {
+ "color": "66757c",
+ "emissive": "000000",
+ "emissiveIntensity": 1,
+ "metalness": 0.48,
+ "roughness": 0.56,
+ "opacity": 1,
+ "transparent": false,
+ "depthWrite": true
+ }
+ ]
+ }
+ }
+ },
+ "runtime": {
+ "startMode": "guided",
+ "showStepPanel": true,
+ "showProgress": true,
+ "allowReplay": true,
+ "language": "zh-CN"
+ },
+ "publish": {
+ "target": "standalone-html",
+ "deployment": "offline",
+ "includeProjectData": true,
+ "includeViewportPreview": true
+ }
+ }
+}
\ No newline at end of file
diff --git a/reports/model-editor-closure-20260906/downloads/recovery-208-isolated.json b/reports/model-editor-closure-20260906/downloads/recovery-208-isolated.json
new file mode 100644
index 0000000..01b9da9
--- /dev/null
+++ b/reports/model-editor-closure-20260906/downloads/recovery-208-isolated.json
@@ -0,0 +1,3422 @@
+{
+ "schema": "unreal-tran.model-api-recovery/v1",
+ "snapshotId": "30903f42-133d-43e0-a9b5-be53455cf6d0",
+ "projectId": "208",
+ "projectVersion": 2,
+ "savedAt": "2026-09-06T07:28:46.859Z",
+ "ownerPageNonce": "d1d7f312-16b6-494a-a517-adb8a1a55029",
+ "document": {
+ "schema": "digital-twin-editor-project",
+ "version": 4,
+ "name": "闭环验收-恢复副本-mtphpz7v",
+ "modelId": "imported-model_mto6v5w6_09-fox",
+ "modelResource": {
+ "kind": "imported",
+ "id": "imported-model_mto6v5w6_09-fox",
+ "name": "09-Fox",
+ "assetCode": "MODEL_MTO6V5W6_09-FOX",
+ "code": "MODEL_MTO6V5W6_09-FOX",
+ "storageUri": "fsvc://unreal-tran/model/2026/09/05/3fc69ae0d435f869afca1fcd943486d1.glb",
+ "assetKey": "",
+ "resourceUrl": "",
+ "sourceName": "09-Fox.glb",
+ "sizeBytes": 162852,
+ "lastModified": 0,
+ "mimeType": "model/gltf-binary",
+ "thumbnail": "",
+ "vertices": 1728,
+ "triangles": 576,
+ "nodes": 27,
+ "meshes": 1,
+ "materials": 1,
+ "textures": 1,
+ "animations": 3,
+ "animationDurationSeconds": 3.4166667461395264,
+ "modelMetadata": {
+ "nodeCount": 27,
+ "meshCount": 1,
+ "materialCount": 1,
+ "textureCount": 1,
+ "animations": [
+ {
+ "index": 0,
+ "name": "Survey",
+ "start": 0,
+ "end": 3.4166667461395264,
+ "duration": 3.4166667461395264,
+ "trackCount": 21
+ },
+ {
+ "index": 1,
+ "name": "Walk",
+ "start": 0,
+ "end": 0.7083333134651184,
+ "duration": 0.7083333134651184,
+ "trackCount": 21
+ },
+ {
+ "index": 2,
+ "name": "Run",
+ "start": 0,
+ "end": 1.1583333015441895,
+ "duration": 1.1583333015441895,
+ "trackCount": 21
+ }
+ ],
+ "animationDurationSeconds": 3.4166667461395264
+ },
+ "nativeAnimations": [
+ {
+ "index": 0,
+ "name": "Survey",
+ "start": 0,
+ "end": 3.4166667461395264,
+ "duration": 3.4166667461395264,
+ "trackCount": 21
+ },
+ {
+ "index": 1,
+ "name": "Walk",
+ "start": 0,
+ "end": 0.7083333134651184,
+ "duration": 0.7083333134651184,
+ "trackCount": 21
+ },
+ {
+ "index": 2,
+ "name": "Run",
+ "start": 0,
+ "end": 1.1583333015441895,
+ "duration": 1.1583333015441895,
+ "trackCount": 21
+ }
+ ]
+ },
+ "meta": {
+ "description": "用于装备维修实训场景编排、编辑器回归与离线发布验证的系统内置基准工程",
+ "author": "数字孪生项目组",
+ "scene": "车间与野外装备维修实训环境",
+ "tags": [
+ "三维场景",
+ "时间轴动画",
+ "蓝图交互",
+ "实训流程"
+ ],
+ "migratedFrom": "legacy-model-document",
+ "source": "content-api-glb",
+ "sourceName": "09-Fox.glb",
+ "assetCode": "MODEL_MTO6V5W6_09-FOX",
+ "storageUri": "fsvc://unreal-tran/model/2026/09/05/3fc69ae0d435f869afca1fcd943486d1.glb"
+ },
+ "timeline": {
+ "version": 1,
+ "duration": 8,
+ "currentTime": 0,
+ "loop": true,
+ "zoom": 1,
+ "tracks": [
+ {
+ "id": "track-0-1yjfz9t",
+ "name": "模型整体位移",
+ "targetId": "interaction-b9240aaa",
+ "property": "position",
+ "color": "#ffb22e",
+ "muted": false,
+ "keyframes": [
+ {
+ "id": "key-0-0-kmfo820",
+ "time": 0,
+ "value": [
+ 0,
+ 0,
+ 0
+ ],
+ "easing": "easeInOut"
+ },
+ {
+ "id": "key-0-1-snsj2e2",
+ "time": 8,
+ "value": [
+ 0,
+ 0,
+ 0
+ ],
+ "easing": "easeInOut"
+ }
+ ]
+ }
+ ]
+ },
+ "blueprint": {
+ "version": 1,
+ "name": "挖掘机作业实训闭环",
+ "viewport": {
+ "x": 55,
+ "y": 55,
+ "zoom": 0.82
+ },
+ "nodes": [
+ {
+ "id": "bp-start",
+ "type": "trigger-click",
+ "title": "点击挖掘机",
+ "x": 40,
+ "y": 55,
+ "params": {
+ "event": "click",
+ "target": "挖掘机"
+ }
+ },
+ {
+ "id": "bp-highlight",
+ "type": "highlight",
+ "title": "高亮工作装置",
+ "x": 300,
+ "y": 55,
+ "params": {
+ "target": "铲斗",
+ "color": "#38e8ff",
+ "pulse": true
+ }
+ },
+ {
+ "id": "bp-message",
+ "type": "message",
+ "title": "显示操作提示",
+ "x": 560,
+ "y": 55,
+ "params": {
+ "title": "挖掘作业实训",
+ "text": "观察动臂、斗杆和铲斗的协同动作",
+ "level": "info"
+ }
+ },
+ {
+ "id": "bp-play",
+ "type": "play-timeline",
+ "title": "播放作业动画",
+ "x": 820,
+ "y": 55,
+ "params": {
+ "clip": "挖掘机作业实训",
+ "from": 0,
+ "speed": 1,
+ "waitForEnd": true
+ }
+ },
+ {
+ "id": "bp-wait",
+ "type": "wait",
+ "title": "动作稳定",
+ "x": 560,
+ "y": 245,
+ "params": {
+ "duration": 600
+ }
+ },
+ {
+ "id": "bp-check",
+ "type": "condition",
+ "title": "动画是否完成",
+ "x": 300,
+ "y": 245,
+ "params": {
+ "variable": "timelineComplete",
+ "operator": "==",
+ "value": true
+ }
+ },
+ {
+ "id": "bp-retry",
+ "type": "message",
+ "title": "等待动画完成",
+ "x": 820,
+ "y": 245,
+ "params": {
+ "title": "流程校验",
+ "text": "动画尚未完成,请重新执行",
+ "level": "warning"
+ }
+ },
+ {
+ "id": "bp-finish",
+ "type": "finish",
+ "title": "流程完成",
+ "x": 40,
+ "y": 245,
+ "params": {
+ "result": "success",
+ "text": "挖掘机作业实训流程完成"
+ }
+ }
+ ],
+ "edges": [
+ {
+ "id": "bp-e1",
+ "from": {
+ "node": "bp-start",
+ "port": "then"
+ },
+ "to": {
+ "node": "bp-highlight",
+ "port": "in"
+ }
+ },
+ {
+ "id": "bp-e2",
+ "from": {
+ "node": "bp-highlight",
+ "port": "then"
+ },
+ "to": {
+ "node": "bp-message",
+ "port": "in"
+ }
+ },
+ {
+ "id": "bp-e3",
+ "from": {
+ "node": "bp-message",
+ "port": "then"
+ },
+ "to": {
+ "node": "bp-play",
+ "port": "in"
+ }
+ },
+ {
+ "id": "bp-e4",
+ "from": {
+ "node": "bp-play",
+ "port": "then"
+ },
+ "to": {
+ "node": "bp-wait",
+ "port": "in"
+ }
+ },
+ {
+ "id": "bp-e5",
+ "from": {
+ "node": "bp-wait",
+ "port": "then"
+ },
+ "to": {
+ "node": "bp-check",
+ "port": "in"
+ }
+ },
+ {
+ "id": "bp-e6",
+ "from": {
+ "node": "bp-check",
+ "port": "true"
+ },
+ "to": {
+ "node": "bp-finish",
+ "port": "in"
+ }
+ },
+ {
+ "id": "bp-e7",
+ "from": {
+ "node": "bp-check",
+ "port": "false"
+ },
+ "to": {
+ "node": "bp-retry",
+ "port": "in"
+ }
+ },
+ {
+ "id": "bp-e8",
+ "from": {
+ "node": "bp-retry",
+ "port": "then"
+ },
+ "to": {
+ "node": "bp-play",
+ "port": "in"
+ }
+ }
+ ]
+ },
+ "camera": {
+ "position": [
+ 4.643347096844712,
+ 4.616668289605231,
+ 12.832677927580558
+ ],
+ "target": [
+ 3.875009990661482e-8,
+ 2.4006999949620704,
+ 2.2660093712545404e-7
+ ]
+ },
+ "model": {
+ "objects": {
+ "equipment-root": {
+ "name": "09-Fox",
+ "nodeName": "09-Fox",
+ "nodePath": "@root",
+ "interactionId": "interaction-b9240aaa",
+ "capabilities": {
+ "selectable": true,
+ "transform": true,
+ "visible": true,
+ "material": false,
+ "animatable": true
+ },
+ "position": [
+ 0,
+ 0,
+ 0
+ ],
+ "rotation": [
+ 0,
+ 0,
+ 0
+ ],
+ "scale": [
+ 1,
+ 1,
+ 1
+ ],
+ "visible": true,
+ "customComponents": []
+ },
+ "model-imported-model_mto6v5w6_09-fox-0": {
+ "name": "",
+ "nodeName": "Group",
+ "nodePath": "0",
+ "interactionId": "interaction-02351bce",
+ "capabilities": {
+ "selectable": true,
+ "transform": true,
+ "visible": true,
+ "material": false,
+ "animatable": true
+ },
+ "position": [
+ 3.875009990661482e-8,
+ 0.007396563645075124,
+ 0.6522089834776823
+ ],
+ "rotation": [
+ 0,
+ 0,
+ 0
+ ],
+ "scale": [
+ 0.06075496488891343,
+ 0.06075496488891343,
+ 0.06075496488891343
+ ],
+ "visible": true,
+ "customComponents": []
+ },
+ "model-imported-model_mto6v5w6_09-fox-1": {
+ "name": "root",
+ "nodeName": "root",
+ "nodePath": "0/0",
+ "interactionId": "interaction-a4d35db9",
+ "capabilities": {
+ "selectable": true,
+ "transform": true,
+ "visible": true,
+ "material": false,
+ "animatable": true
+ },
+ "position": [
+ 0,
+ 0,
+ 0
+ ],
+ "rotation": [
+ 0,
+ 0,
+ 0
+ ],
+ "scale": [
+ 1,
+ 1,
+ 1
+ ],
+ "visible": true,
+ "customComponents": []
+ },
+ "model-imported-model_mto6v5w6_09-fox-2": {
+ "name": "_rootJoint",
+ "nodeName": "_rootJoint",
+ "nodePath": "0/0/0",
+ "interactionId": "interaction-4476c556",
+ "capabilities": {
+ "selectable": true,
+ "transform": true,
+ "visible": true,
+ "material": false,
+ "animatable": true
+ },
+ "position": [
+ 0,
+ 0,
+ 0
+ ],
+ "rotation": [
+ 0,
+ 0,
+ 0
+ ],
+ "scale": [
+ 1,
+ 1,
+ 1
+ ],
+ "visible": true,
+ "customComponents": []
+ },
+ "model-imported-model_mto6v5w6_09-fox-3": {
+ "name": "b_Root_00",
+ "nodeName": "b_Root_00",
+ "nodePath": "0/0/0/0",
+ "interactionId": "interaction-ffc45d11",
+ "capabilities": {
+ "selectable": true,
+ "transform": true,
+ "visible": true,
+ "material": false,
+ "animatable": true
+ },
+ "position": [
+ 0,
+ 0,
+ 0
+ ],
+ "rotation": [
+ -1.5708000357176293,
+ 0,
+ 0
+ ],
+ "scale": [
+ 1,
+ 1,
+ 1
+ ],
+ "visible": true,
+ "customComponents": []
+ },
+ "model-imported-model_mto6v5w6_09-fox-4": {
+ "name": "b_Hip_01",
+ "nodeName": "b_Hip_01",
+ "nodePath": "0/0/0/0/0",
+ "interactionId": "interaction-7a75e39e",
+ "capabilities": {
+ "selectable": true,
+ "transform": true,
+ "visible": true,
+ "material": false,
+ "animatable": true
+ },
+ "position": [
+ 0.000001241521920852674,
+ 22.061977333358605,
+ 34.62373020761327
+ ],
+ "rotation": [
+ 0.44722050284761794,
+ -1.5707951637404125,
+ 0
+ ],
+ "scale": [
+ 1,
+ 1,
+ 1
+ ],
+ "visible": true,
+ "customComponents": []
+ },
+ "model-imported-model_mto6v5w6_09-fox-5": {
+ "name": "b_Spine01_02",
+ "nodeName": "b_Spine01_02",
+ "nodePath": "0/0/0/0/0/0",
+ "interactionId": "interaction-663ea469",
+ "capabilities": {
+ "selectable": true,
+ "transform": true,
+ "visible": true,
+ "material": false,
+ "animatable": true
+ },
+ "position": [
+ 12.850601196289062,
+ 0,
+ 0
+ ],
+ "rotation": [
+ -3.357310257291308e-7,
+ -3.316574913360067e-8,
+ -1.2102038452161539
+ ],
+ "scale": [
+ 1,
+ 1,
+ 1
+ ],
+ "visible": true,
+ "customComponents": []
+ },
+ "model-imported-model_mto6v5w6_09-fox-6": {
+ "name": "b_Spine02_03",
+ "nodeName": "b_Spine02_03",
+ "nodePath": "0/0/0/0/0/0/0",
+ "interactionId": "interaction-46867126",
+ "capabilities": {
+ "selectable": true,
+ "transform": true,
+ "visible": true,
+ "material": false,
+ "animatable": true
+ },
+ "position": [
+ 21.65575408935547,
+ -0.000118255615234375,
+ 0
+ ],
+ "rotation": [
+ 8.772651057591018e-8,
+ 3.3355428835535634e-8,
+ 0.3633406824515624
+ ],
+ "scale": [
+ 1,
+ 1,
+ 1
+ ],
+ "visible": true,
+ "customComponents": []
+ },
+ "model-imported-model_mto6v5w6_09-fox-7": {
+ "name": "b_Neck_04",
+ "nodeName": "b_Neck_04",
+ "nodePath": "0/0/0/0/0/0/0/0",
+ "interactionId": "interaction-27148d81",
+ "capabilities": {
+ "selectable": true,
+ "transform": true,
+ "visible": true,
+ "material": false,
+ "animatable": true
+ },
+ "position": [
+ 25.64914321899414,
+ 0,
+ 0
+ ],
+ "rotation": [
+ 6.423807629998062e-15,
+ 1.9993472402144555e-16,
+ 0.031113979193075042
+ ],
+ "scale": [
+ 1,
+ 1,
+ 1
+ ],
+ "visible": true,
+ "customComponents": []
+ },
+ "model-imported-model_mto6v5w6_09-fox-8": {
+ "name": "b_Head_05",
+ "nodeName": "b_Head_05",
+ "nodePath": "0/0/0/0/0/0/0/0/0",
+ "interactionId": "interaction-b24eef2e",
+ "capabilities": {
+ "selectable": true,
+ "transform": true,
+ "visible": true,
+ "material": false,
+ "animatable": true
+ },
+ "position": [
+ 13.376960754394531,
+ 0,
+ 0
+ ],
+ "rotation": [
+ 3.253825627358364e-8,
+ -1.577320798309453e-8,
+ -0.451380500897834
+ ],
+ "scale": [
+ 1,
+ 1,
+ 1
+ ],
+ "visible": true,
+ "customComponents": []
+ },
+ "model-imported-model_mto6v5w6_09-fox-9": {
+ "name": "b_RightUpperArm_06",
+ "nodeName": "b_RightUpperArm_06",
+ "nodePath": "0/0/0/0/0/0/0/1",
+ "interactionId": "interaction-26148bee",
+ "capabilities": {
+ "selectable": true,
+ "transform": true,
+ "visible": true,
+ "material": false,
+ "animatable": true
+ },
+ "position": [
+ 18.677913665771484,
+ -4.297340393066406,
+ 6.9675750732421875
+ ],
+ "rotation": [
+ -0.035644184619881884,
+ -0.047001863061712265,
+ -1.2972005346348685
+ ],
+ "scale": [
+ 1,
+ 1,
+ 1
+ ],
+ "visible": true,
+ "customComponents": []
+ },
+ "model-imported-model_mto6v5w6_09-fox-10": {
+ "name": "b_RightForeArm_07",
+ "nodeName": "b_RightForeArm_07",
+ "nodePath": "0/0/0/0/0/0/0/1/0",
+ "interactionId": "interaction-36896559",
+ "capabilities": {
+ "selectable": true,
+ "transform": true,
+ "visible": true,
+ "material": false,
+ "animatable": true
+ },
+ "position": [
+ 23.04512596130371,
+ 0,
+ 0
+ ],
+ "rotation": [
+ 2.1355818997049898e-8,
+ 7.233656206674673e-8,
+ 1.2561843405841544
+ ],
+ "scale": [
+ 1,
+ 1,
+ 1
+ ],
+ "visible": true,
+ "customComponents": []
+ },
+ "model-imported-model_mto6v5w6_09-fox-11": {
+ "name": "b_RightHand_08",
+ "nodeName": "b_RightHand_08",
+ "nodePath": "0/0/0/0/0/0/0/1/0/0",
+ "interactionId": "interaction-57a124f6",
+ "capabilities": {
+ "selectable": true,
+ "transform": true,
+ "visible": true,
+ "material": false,
+ "animatable": true
+ },
+ "position": [
+ 19.350055694580078,
+ -0.14598655700683594,
+ 0
+ ],
+ "rotation": [
+ 0.005245101551986586,
+ 0.05200705984217871,
+ -0.267165337534188
+ ],
+ "scale": [
+ 1,
+ 1,
+ 1
+ ],
+ "visible": true,
+ "customComponents": []
+ },
+ "model-imported-model_mto6v5w6_09-fox-12": {
+ "name": "b_LeftUpperArm_09",
+ "nodeName": "b_LeftUpperArm_09",
+ "nodePath": "0/0/0/0/0/0/0/2",
+ "interactionId": "interaction-25148a5b",
+ "capabilities": {
+ "selectable": true,
+ "transform": true,
+ "visible": true,
+ "material": false,
+ "animatable": true
+ },
+ "position": [
+ 18.67791748046875,
+ -4.297344207763672,
+ -6.967987060546875
+ ],
+ "rotation": [
+ -0.00020118104561539401,
+ 0.0009350561550523671,
+ -1.5148902559141275
+ ],
+ "scale": [
+ 1,
+ 1,
+ 1
+ ],
+ "visible": true,
+ "customComponents": []
+ },
+ "model-imported-model_mto6v5w6_09-fox-13": {
+ "name": "b_LeftForeArm_010",
+ "nodeName": "b_LeftForeArm_010",
+ "nodePath": "0/0/0/0/0/0/0/2/0",
+ "interactionId": "interaction-5c502ac4",
+ "capabilities": {
+ "selectable": true,
+ "transform": true,
+ "visible": true,
+ "material": false,
+ "animatable": true
+ },
+ "position": [
+ 23.045124053955078,
+ 0,
+ 0
+ ],
+ "rotation": [
+ 2.5220861991717696e-8,
+ 7.109903232964989e-8,
+ 1.2307106255258764
+ ],
+ "scale": [
+ 1,
+ 1,
+ 1
+ ],
+ "visible": true,
+ "customComponents": []
+ },
+ "model-imported-model_mto6v5w6_09-fox-14": {
+ "name": "b_LeftHand_011",
+ "nodeName": "b_LeftHand_011",
+ "nodePath": "0/0/0/0/0/0/0/2/0/0",
+ "interactionId": "interaction-192b4cd3",
+ "capabilities": {
+ "selectable": true,
+ "transform": true,
+ "visible": true,
+ "material": false,
+ "animatable": true
+ },
+ "position": [
+ 19.350051879882812,
+ -0.14599037170410156,
+ 0
+ ],
+ "rotation": [
+ 0.004279557751527162,
+ -0.03344276494440009,
+ -1.5985291598689726
+ ],
+ "scale": [
+ 1,
+ 1,
+ 1
+ ],
+ "visible": true,
+ "customComponents": []
+ },
+ "model-imported-model_mto6v5w6_09-fox-15": {
+ "name": "b_Tail01_012",
+ "nodeName": "b_Tail01_012",
+ "nodePath": "0/0/0/0/0/1",
+ "interactionId": "interaction-653ea2d6",
+ "capabilities": {
+ "selectable": true,
+ "transform": true,
+ "visible": true,
+ "material": false,
+ "animatable": true
+ },
+ "position": [
+ 4.2603759765625,
+ 15.958770751953125,
+ 0
+ ],
+ "rotation": [
+ -5.364745216052604e-7,
+ 3.424797608447095e-7,
+ 1.9941304874941974
+ ],
+ "scale": [
+ 1,
+ 1,
+ 1
+ ],
+ "visible": true,
+ "customComponents": []
+ },
+ "model-imported-model_mto6v5w6_09-fox-16": {
+ "name": "b_Tail02_013",
+ "nodeName": "b_Tail02_013",
+ "nodePath": "0/0/0/0/0/1/0",
+ "interactionId": "interaction-6c4d3691",
+ "capabilities": {
+ "selectable": true,
+ "transform": true,
+ "visible": true,
+ "material": false,
+ "animatable": true
+ },
+ "position": [
+ 12.411918640136719,
+ 0,
+ 0
+ ],
+ "rotation": [
+ -6.738507958375962e-23,
+ -5.2439168243310865e-23,
+ 0.3128105211171357
+ ],
+ "scale": [
+ 1,
+ 1,
+ 1
+ ],
+ "visible": true,
+ "customComponents": []
+ },
+ "model-imported-model_mto6v5w6_09-fox-17": {
+ "name": "b_Tail03_014",
+ "nodeName": "b_Tail03_014",
+ "nodePath": "0/0/0/0/0/1/0/0",
+ "interactionId": "interaction-e93e191e",
+ "capabilities": {
+ "selectable": true,
+ "transform": true,
+ "visible": true,
+ "material": false,
+ "animatable": true
+ },
+ "position": [
+ 24.24032211303711,
+ 0,
+ 0
+ ],
+ "rotation": [
+ -5.7486339033124146e-24,
+ 1.1669178332583734e-22,
+ -0.012937892408322462
+ ],
+ "scale": [
+ 1,
+ 1,
+ 1
+ ],
+ "visible": true,
+ "customComponents": []
+ },
+ "model-imported-model_mto6v5w6_09-fox-18": {
+ "name": "b_LeftLeg01_015",
+ "nodeName": "b_LeftLeg01_015",
+ "nodePath": "0/0/0/0/0/2",
+ "interactionId": "interaction-643ea143",
+ "capabilities": {
+ "selectable": true,
+ "transform": true,
+ "visible": true,
+ "material": false,
+ "animatable": true
+ },
+ "position": [
+ 4.813770294189453,
+ 5.154018402099609,
+ -6.968006134033203
+ ],
+ "rotation": [
+ 0.1223493637902729,
+ -0.09724770930295147,
+ 2.9630295165414986
+ ],
+ "scale": [
+ 1,
+ 1,
+ 1
+ ],
+ "visible": true,
+ "customComponents": []
+ },
+ "model-imported-model_mto6v5w6_09-fox-19": {
+ "name": "b_LeftLeg02_016",
+ "nodeName": "b_LeftLeg02_016",
+ "nodePath": "0/0/0/0/0/2/0",
+ "interactionId": "interaction-7175f8dc",
+ "capabilities": {
+ "selectable": true,
+ "transform": true,
+ "visible": true,
+ "material": false,
+ "animatable": true
+ },
+ "position": [
+ 18.944175720214844,
+ 0,
+ 0
+ ],
+ "rotation": [
+ 1.2545483692289454e-8,
+ 3.728483680761731e-8,
+ -0.522293355530293
+ ],
+ "scale": [
+ 1,
+ 1,
+ 1
+ ],
+ "visible": true,
+ "customComponents": []
+ },
+ "model-imported-model_mto6v5w6_09-fox-20": {
+ "name": "b_LeftFoot01_017",
+ "nodeName": "b_LeftFoot01_017",
+ "nodePath": "0/0/0/0/0/2/0/0",
+ "interactionId": "interaction-f5ed353b",
+ "capabilities": {
+ "selectable": true,
+ "transform": true,
+ "visible": true,
+ "material": false,
+ "animatable": true
+ },
+ "position": [
+ 17.942811965942383,
+ 0,
+ 0
+ ],
+ "rotation": [
+ -0.03128474386729028,
+ -0.15471927051895154,
+ 0.7774909055508243
+ ],
+ "scale": [
+ 1,
+ 1,
+ 1
+ ],
+ "visible": true,
+ "customComponents": []
+ },
+ "model-imported-model_mto6v5w6_09-fox-21": {
+ "name": "b_LeftFoot02_018",
+ "nodeName": "b_LeftFoot02_018",
+ "nodePath": "0/0/0/0/0/2/0/0/0",
+ "interactionId": "interaction-1bbe01a4",
+ "capabilities": {
+ "selectable": true,
+ "transform": true,
+ "visible": true,
+ "material": false,
+ "animatable": true
+ },
+ "position": [
+ 15.779938697814941,
+ 0,
+ 0
+ ],
+ "rotation": [
+ 0,
+ 0,
+ 1.158241570830627
+ ],
+ "scale": [
+ 1,
+ 1,
+ 1
+ ],
+ "visible": true,
+ "customComponents": []
+ },
+ "model-imported-model_mto6v5w6_09-fox-22": {
+ "name": "b_RightLeg01_019",
+ "nodeName": "b_RightLeg01_019",
+ "nodePath": "0/0/0/0/0/3",
+ "interactionId": "interaction-633e9fb0",
+ "capabilities": {
+ "selectable": true,
+ "transform": true,
+ "visible": true,
+ "material": false,
+ "animatable": true
+ },
+ "position": [
+ 4.813777923583984,
+ 5.154026031494141,
+ 6.967563629150391
+ ],
+ "rotation": [
+ -0.10137208185544387,
+ 0.05534659522395753,
+ -2.3613690068554236
+ ],
+ "scale": [
+ 1,
+ 1,
+ 1
+ ],
+ "visible": true,
+ "customComponents": []
+ },
+ "model-imported-model_mto6v5w6_09-fox-23": {
+ "name": "b_RightLeg02_020",
+ "nodeName": "b_RightLeg02_020",
+ "nodePath": "0/0/0/0/0/3/0",
+ "interactionId": "interaction-95ff89c7",
+ "capabilities": {
+ "selectable": true,
+ "transform": true,
+ "visible": true,
+ "material": false,
+ "animatable": true
+ },
+ "position": [
+ 18.944183349609375,
+ 0,
+ 0
+ ],
+ "rotation": [
+ 2.7890192431350882e-8,
+ 1.4750479199499402e-8,
+ -1.0710323806538369
+ ],
+ "scale": [
+ 1,
+ 1,
+ 1
+ ],
+ "visible": true,
+ "customComponents": []
+ },
+ "model-imported-model_mto6v5w6_09-fox-24": {
+ "name": "b_RightFoot01_021",
+ "nodeName": "b_RightFoot01_021",
+ "nodePath": "0/0/0/0/0/3/0/0",
+ "interactionId": "interaction-a157d498",
+ "capabilities": {
+ "selectable": true,
+ "transform": true,
+ "visible": true,
+ "material": false,
+ "animatable": true
+ },
+ "position": [
+ 17.94281005859375,
+ 0,
+ 0
+ ],
+ "rotation": [
+ -0.14401413968163027,
+ 0.2814735368347316,
+ 1.4591240233677443
+ ],
+ "scale": [
+ 1,
+ 1,
+ 1
+ ],
+ "visible": true,
+ "customComponents": []
+ },
+ "model-imported-model_mto6v5w6_09-fox-25": {
+ "name": "b_RightFoot02_022",
+ "nodeName": "b_RightFoot02_022",
+ "nodePath": "0/0/0/0/0/3/0/0/0",
+ "interactionId": "interaction-ebd38e3f",
+ "capabilities": {
+ "selectable": true,
+ "transform": true,
+ "visible": true,
+ "material": false,
+ "animatable": true
+ },
+ "position": [
+ 15.779935836791992,
+ 0,
+ 0
+ ],
+ "rotation": [
+ 0,
+ 0,
+ 1.158241570830627
+ ],
+ "scale": [
+ 1,
+ 1,
+ 1
+ ],
+ "visible": true,
+ "customComponents": []
+ },
+ "model-imported-model_mto6v5w6_09-fox-26": {
+ "name": "闭环验收-旧版本需人工合并",
+ "nodeName": "fox",
+ "nodePath": "0/1",
+ "interactionId": "interaction-a3d35c26",
+ "capabilities": {
+ "selectable": true,
+ "transform": true,
+ "visible": true,
+ "material": true,
+ "animatable": true
+ },
+ "position": [
+ 0.25,
+ 0,
+ 0
+ ],
+ "rotation": [
+ 0,
+ 0,
+ 0
+ ],
+ "scale": [
+ 1,
+ 1,
+ 1
+ ],
+ "visible": true,
+ "customComponents": [],
+ "materials": [
+ {
+ "color": "ffffff",
+ "emissive": "000000",
+ "emissiveIntensity": 1,
+ "metalness": 0,
+ "roughness": 0.3,
+ "opacity": 1,
+ "transparent": false,
+ "depthWrite": true
+ }
+ ]
+ }
+ },
+ "selectionScope": "part",
+ "nativeAnimation": {
+ "clipIndex": 2,
+ "clipName": "Run"
+ }
+ },
+ "environment": {
+ "mode": "placeholder",
+ "environmentType": "outdoor",
+ "sourceName": "内置野外综合训练环境",
+ "sizeBytes": 0,
+ "assetCode": "",
+ "storageUri": "",
+ "resourceUrl": "",
+ "objects": {
+ "outdoor-scene": {
+ "name": "野外综合训练环境",
+ "position": [
+ 0,
+ 0,
+ 0
+ ],
+ "rotation": [
+ 0,
+ 0,
+ 0
+ ],
+ "scale": [
+ 1,
+ 1,
+ 1
+ ],
+ "visible": true,
+ "customComponents": []
+ },
+ "outdoor-site": {
+ "name": "场地与道路",
+ "position": [
+ 0,
+ 0,
+ 0
+ ],
+ "rotation": [
+ 0,
+ 0,
+ 0
+ ],
+ "scale": [
+ 1,
+ 1,
+ 1
+ ],
+ "visible": true,
+ "customComponents": []
+ },
+ "outdoor-ground": {
+ "name": "室外训练场地",
+ "position": [
+ 0,
+ -0.065,
+ 0
+ ],
+ "rotation": [
+ -1.5707963267948966,
+ 0,
+ 0
+ ],
+ "scale": [
+ 1,
+ 1,
+ 1
+ ],
+ "visible": true,
+ "customComponents": [],
+ "materials": [
+ {
+ "color": "263529",
+ "emissive": "000000",
+ "emissiveIntensity": 1,
+ "metalness": 0,
+ "roughness": 0.98,
+ "opacity": 1,
+ "transparent": false,
+ "depthWrite": true
+ }
+ ]
+ },
+ "outdoor-apron": {
+ "name": "装备操作坪",
+ "position": [
+ 0,
+ -0.045,
+ 0
+ ],
+ "rotation": [
+ -1.5707963267948966,
+ 0,
+ 0
+ ],
+ "scale": [
+ 1,
+ 1,
+ 1
+ ],
+ "visible": true,
+ "customComponents": [],
+ "materials": [
+ {
+ "color": "39444a",
+ "emissive": "000000",
+ "emissiveIntensity": 1,
+ "metalness": 0.03,
+ "roughness": 0.92,
+ "opacity": 1,
+ "transparent": false,
+ "depthWrite": true
+ }
+ ]
+ },
+ "outdoor-road": {
+ "name": "保障通道",
+ "position": [
+ 0,
+ -0.035,
+ 17
+ ],
+ "rotation": [
+ -1.5707963267948966,
+ 0,
+ 0
+ ],
+ "scale": [
+ 1,
+ 1,
+ 1
+ ],
+ "visible": true,
+ "customComponents": [],
+ "materials": [
+ {
+ "color": "202a31",
+ "emissive": "000000",
+ "emissiveIntensity": 1,
+ "metalness": 0.02,
+ "roughness": 0.95,
+ "opacity": 1,
+ "transparent": false,
+ "depthWrite": true
+ }
+ ]
+ },
+ "outdoor-road-line-1": {
+ "name": "道路中心线 1",
+ "position": [
+ -28.699999999999996,
+ 0.01,
+ 17
+ ],
+ "rotation": [
+ 0,
+ 0,
+ 0
+ ],
+ "scale": [
+ 1,
+ 1,
+ 1
+ ],
+ "visible": true,
+ "customComponents": [],
+ "materials": [
+ {
+ "color": "e5c64c",
+ "emissive": "302600",
+ "emissiveIntensity": 0.18,
+ "metalness": 0.08,
+ "roughness": 0.72,
+ "opacity": 1,
+ "transparent": false,
+ "depthWrite": true
+ }
+ ]
+ },
+ "outdoor-road-line-2": {
+ "name": "道路中心线 2",
+ "position": [
+ -24.599999999999998,
+ 0.01,
+ 17
+ ],
+ "rotation": [
+ 0,
+ 0,
+ 0
+ ],
+ "scale": [
+ 1,
+ 1,
+ 1
+ ],
+ "visible": true,
+ "customComponents": [],
+ "materials": [
+ {
+ "color": "e5c64c",
+ "emissive": "302600",
+ "emissiveIntensity": 0.18,
+ "metalness": 0.08,
+ "roughness": 0.72,
+ "opacity": 1,
+ "transparent": false,
+ "depthWrite": true
+ }
+ ]
+ },
+ "outdoor-road-line-3": {
+ "name": "道路中心线 3",
+ "position": [
+ -20.5,
+ 0.01,
+ 17
+ ],
+ "rotation": [
+ 0,
+ 0,
+ 0
+ ],
+ "scale": [
+ 1,
+ 1,
+ 1
+ ],
+ "visible": true,
+ "customComponents": [],
+ "materials": [
+ {
+ "color": "e5c64c",
+ "emissive": "302600",
+ "emissiveIntensity": 0.18,
+ "metalness": 0.08,
+ "roughness": 0.72,
+ "opacity": 1,
+ "transparent": false,
+ "depthWrite": true
+ }
+ ]
+ },
+ "outdoor-road-line-4": {
+ "name": "道路中心线 4",
+ "position": [
+ -16.4,
+ 0.01,
+ 17
+ ],
+ "rotation": [
+ 0,
+ 0,
+ 0
+ ],
+ "scale": [
+ 1,
+ 1,
+ 1
+ ],
+ "visible": true,
+ "customComponents": [],
+ "materials": [
+ {
+ "color": "e5c64c",
+ "emissive": "302600",
+ "emissiveIntensity": 0.18,
+ "metalness": 0.08,
+ "roughness": 0.72,
+ "opacity": 1,
+ "transparent": false,
+ "depthWrite": true
+ }
+ ]
+ },
+ "outdoor-road-line-5": {
+ "name": "道路中心线 5",
+ "position": [
+ -12.299999999999999,
+ 0.01,
+ 17
+ ],
+ "rotation": [
+ 0,
+ 0,
+ 0
+ ],
+ "scale": [
+ 1,
+ 1,
+ 1
+ ],
+ "visible": true,
+ "customComponents": [],
+ "materials": [
+ {
+ "color": "e5c64c",
+ "emissive": "302600",
+ "emissiveIntensity": 0.18,
+ "metalness": 0.08,
+ "roughness": 0.72,
+ "opacity": 1,
+ "transparent": false,
+ "depthWrite": true
+ }
+ ]
+ },
+ "outdoor-road-line-6": {
+ "name": "道路中心线 6",
+ "position": [
+ -8.2,
+ 0.01,
+ 17
+ ],
+ "rotation": [
+ 0,
+ 0,
+ 0
+ ],
+ "scale": [
+ 1,
+ 1,
+ 1
+ ],
+ "visible": true,
+ "customComponents": [],
+ "materials": [
+ {
+ "color": "e5c64c",
+ "emissive": "302600",
+ "emissiveIntensity": 0.18,
+ "metalness": 0.08,
+ "roughness": 0.72,
+ "opacity": 1,
+ "transparent": false,
+ "depthWrite": true
+ }
+ ]
+ },
+ "outdoor-road-line-7": {
+ "name": "道路中心线 7",
+ "position": [
+ -4.1,
+ 0.01,
+ 17
+ ],
+ "rotation": [
+ 0,
+ 0,
+ 0
+ ],
+ "scale": [
+ 1,
+ 1,
+ 1
+ ],
+ "visible": true,
+ "customComponents": [],
+ "materials": [
+ {
+ "color": "e5c64c",
+ "emissive": "302600",
+ "emissiveIntensity": 0.18,
+ "metalness": 0.08,
+ "roughness": 0.72,
+ "opacity": 1,
+ "transparent": false,
+ "depthWrite": true
+ }
+ ]
+ },
+ "outdoor-road-line-8": {
+ "name": "道路中心线 8",
+ "position": [
+ 0,
+ 0.01,
+ 17
+ ],
+ "rotation": [
+ 0,
+ 0,
+ 0
+ ],
+ "scale": [
+ 1,
+ 1,
+ 1
+ ],
+ "visible": true,
+ "customComponents": [],
+ "materials": [
+ {
+ "color": "e5c64c",
+ "emissive": "302600",
+ "emissiveIntensity": 0.18,
+ "metalness": 0.08,
+ "roughness": 0.72,
+ "opacity": 1,
+ "transparent": false,
+ "depthWrite": true
+ }
+ ]
+ },
+ "outdoor-road-line-9": {
+ "name": "道路中心线 9",
+ "position": [
+ 4.1,
+ 0.01,
+ 17
+ ],
+ "rotation": [
+ 0,
+ 0,
+ 0
+ ],
+ "scale": [
+ 1,
+ 1,
+ 1
+ ],
+ "visible": true,
+ "customComponents": [],
+ "materials": [
+ {
+ "color": "e5c64c",
+ "emissive": "302600",
+ "emissiveIntensity": 0.18,
+ "metalness": 0.08,
+ "roughness": 0.72,
+ "opacity": 1,
+ "transparent": false,
+ "depthWrite": true
+ }
+ ]
+ },
+ "outdoor-road-line-10": {
+ "name": "道路中心线 10",
+ "position": [
+ 8.2,
+ 0.01,
+ 17
+ ],
+ "rotation": [
+ 0,
+ 0,
+ 0
+ ],
+ "scale": [
+ 1,
+ 1,
+ 1
+ ],
+ "visible": true,
+ "customComponents": [],
+ "materials": [
+ {
+ "color": "e5c64c",
+ "emissive": "302600",
+ "emissiveIntensity": 0.18,
+ "metalness": 0.08,
+ "roughness": 0.72,
+ "opacity": 1,
+ "transparent": false,
+ "depthWrite": true
+ }
+ ]
+ },
+ "outdoor-road-line-11": {
+ "name": "道路中心线 11",
+ "position": [
+ 12.299999999999999,
+ 0.01,
+ 17
+ ],
+ "rotation": [
+ 0,
+ 0,
+ 0
+ ],
+ "scale": [
+ 1,
+ 1,
+ 1
+ ],
+ "visible": true,
+ "customComponents": [],
+ "materials": [
+ {
+ "color": "e5c64c",
+ "emissive": "302600",
+ "emissiveIntensity": 0.18,
+ "metalness": 0.08,
+ "roughness": 0.72,
+ "opacity": 1,
+ "transparent": false,
+ "depthWrite": true
+ }
+ ]
+ },
+ "outdoor-road-line-12": {
+ "name": "道路中心线 12",
+ "position": [
+ 16.4,
+ 0.01,
+ 17
+ ],
+ "rotation": [
+ 0,
+ 0,
+ 0
+ ],
+ "scale": [
+ 1,
+ 1,
+ 1
+ ],
+ "visible": true,
+ "customComponents": [],
+ "materials": [
+ {
+ "color": "e5c64c",
+ "emissive": "302600",
+ "emissiveIntensity": 0.18,
+ "metalness": 0.08,
+ "roughness": 0.72,
+ "opacity": 1,
+ "transparent": false,
+ "depthWrite": true
+ }
+ ]
+ },
+ "outdoor-road-line-13": {
+ "name": "道路中心线 13",
+ "position": [
+ 20.5,
+ 0.01,
+ 17
+ ],
+ "rotation": [
+ 0,
+ 0,
+ 0
+ ],
+ "scale": [
+ 1,
+ 1,
+ 1
+ ],
+ "visible": true,
+ "customComponents": [],
+ "materials": [
+ {
+ "color": "e5c64c",
+ "emissive": "302600",
+ "emissiveIntensity": 0.18,
+ "metalness": 0.08,
+ "roughness": 0.72,
+ "opacity": 1,
+ "transparent": false,
+ "depthWrite": true
+ }
+ ]
+ },
+ "outdoor-road-line-14": {
+ "name": "道路中心线 14",
+ "position": [
+ 24.599999999999998,
+ 0.01,
+ 17
+ ],
+ "rotation": [
+ 0,
+ 0,
+ 0
+ ],
+ "scale": [
+ 1,
+ 1,
+ 1
+ ],
+ "visible": true,
+ "customComponents": [],
+ "materials": [
+ {
+ "color": "e5c64c",
+ "emissive": "302600",
+ "emissiveIntensity": 0.18,
+ "metalness": 0.08,
+ "roughness": 0.72,
+ "opacity": 1,
+ "transparent": false,
+ "depthWrite": true
+ }
+ ]
+ },
+ "outdoor-road-line-15": {
+ "name": "道路中心线 15",
+ "position": [
+ 28.699999999999996,
+ 0.01,
+ 17
+ ],
+ "rotation": [
+ 0,
+ 0,
+ 0
+ ],
+ "scale": [
+ 1,
+ 1,
+ 1
+ ],
+ "visible": true,
+ "customComponents": [],
+ "materials": [
+ {
+ "color": "e5c64c",
+ "emissive": "302600",
+ "emissiveIntensity": 0.18,
+ "metalness": 0.08,
+ "roughness": 0.72,
+ "opacity": 1,
+ "transparent": false,
+ "depthWrite": true
+ }
+ ]
+ },
+ "outdoor-safety--17.6--11": {
+ "name": "操作区安全标线",
+ "position": [
+ -17.6,
+ 0.012,
+ -11
+ ],
+ "rotation": [
+ 0,
+ 0.5654866776461628,
+ 0
+ ],
+ "scale": [
+ 1,
+ 1,
+ 1
+ ],
+ "visible": true,
+ "customComponents": [],
+ "materials": [
+ {
+ "color": "e5c64c",
+ "emissive": "302600",
+ "emissiveIntensity": 0.18,
+ "metalness": 0.08,
+ "roughness": 0.72,
+ "opacity": 1,
+ "transparent": false,
+ "depthWrite": true
+ }
+ ]
+ },
+ "outdoor-safety--17.6--8.4": {
+ "name": "操作区安全标线",
+ "position": [
+ -17.6,
+ 0.012,
+ -8.4
+ ],
+ "rotation": [
+ 0,
+ 0.5654866776461628,
+ 0
+ ],
+ "scale": [
+ 1,
+ 1,
+ 1
+ ],
+ "visible": true,
+ "customComponents": [],
+ "materials": [
+ {
+ "color": "e5c64c",
+ "emissive": "302600",
+ "emissiveIntensity": 0.18,
+ "metalness": 0.08,
+ "roughness": 0.72,
+ "opacity": 1,
+ "transparent": false,
+ "depthWrite": true
+ }
+ ]
+ },
+ "outdoor-safety--17.6--5.800000000000001": {
+ "name": "操作区安全标线",
+ "position": [
+ -17.6,
+ 0.012,
+ -5.800000000000001
+ ],
+ "rotation": [
+ 0,
+ 0.5654866776461628,
+ 0
+ ],
+ "scale": [
+ 1,
+ 1,
+ 1
+ ],
+ "visible": true,
+ "customComponents": [],
+ "materials": [
+ {
+ "color": "e5c64c",
+ "emissive": "302600",
+ "emissiveIntensity": 0.18,
+ "metalness": 0.08,
+ "roughness": 0.72,
+ "opacity": 1,
+ "transparent": false,
+ "depthWrite": true
+ }
+ ]
+ },
+ "outdoor-safety--17.6--3.2000000000000006": {
+ "name": "操作区安全标线",
+ "position": [
+ -17.6,
+ 0.012,
+ -3.2000000000000006
+ ],
+ "rotation": [
+ 0,
+ 0.5654866776461628,
+ 0
+ ],
+ "scale": [
+ 1,
+ 1,
+ 1
+ ],
+ "visible": true,
+ "customComponents": [],
+ "materials": [
+ {
+ "color": "e5c64c",
+ "emissive": "302600",
+ "emissiveIntensity": 0.18,
+ "metalness": 0.08,
+ "roughness": 0.72,
+ "opacity": 1,
+ "transparent": false,
+ "depthWrite": true
+ }
+ ]
+ },
+ "outdoor-safety--17.6--0.6000000000000005": {
+ "name": "操作区安全标线",
+ "position": [
+ -17.6,
+ 0.012,
+ -0.6000000000000005
+ ],
+ "rotation": [
+ 0,
+ 0.5654866776461628,
+ 0
+ ],
+ "scale": [
+ 1,
+ 1,
+ 1
+ ],
+ "visible": true,
+ "customComponents": [],
+ "materials": [
+ {
+ "color": "e5c64c",
+ "emissive": "302600",
+ "emissiveIntensity": 0.18,
+ "metalness": 0.08,
+ "roughness": 0.72,
+ "opacity": 1,
+ "transparent": false,
+ "depthWrite": true
+ }
+ ]
+ },
+ "outdoor-safety--17.6-1.9999999999999996": {
+ "name": "操作区安全标线",
+ "position": [
+ -17.6,
+ 0.012,
+ 1.9999999999999996
+ ],
+ "rotation": [
+ 0,
+ 0.5654866776461628,
+ 0
+ ],
+ "scale": [
+ 1,
+ 1,
+ 1
+ ],
+ "visible": true,
+ "customComponents": [],
+ "materials": [
+ {
+ "color": "e5c64c",
+ "emissive": "302600",
+ "emissiveIntensity": 0.18,
+ "metalness": 0.08,
+ "roughness": 0.72,
+ "opacity": 1,
+ "transparent": false,
+ "depthWrite": true
+ }
+ ]
+ },
+ "outdoor-safety--17.6-4.6": {
+ "name": "操作区安全标线",
+ "position": [
+ -17.6,
+ 0.012,
+ 4.6
+ ],
+ "rotation": [
+ 0,
+ 0.5654866776461628,
+ 0
+ ],
+ "scale": [
+ 1,
+ 1,
+ 1
+ ],
+ "visible": true,
+ "customComponents": [],
+ "materials": [
+ {
+ "color": "e5c64c",
+ "emissive": "302600",
+ "emissiveIntensity": 0.18,
+ "metalness": 0.08,
+ "roughness": 0.72,
+ "opacity": 1,
+ "transparent": false,
+ "depthWrite": true
+ }
+ ]
+ },
+ "outdoor-safety--17.6-7.199999999999999": {
+ "name": "操作区安全标线",
+ "position": [
+ -17.6,
+ 0.012,
+ 7.199999999999999
+ ],
+ "rotation": [
+ 0,
+ 0.5654866776461628,
+ 0
+ ],
+ "scale": [
+ 1,
+ 1,
+ 1
+ ],
+ "visible": true,
+ "customComponents": [],
+ "materials": [
+ {
+ "color": "e5c64c",
+ "emissive": "302600",
+ "emissiveIntensity": 0.18,
+ "metalness": 0.08,
+ "roughness": 0.72,
+ "opacity": 1,
+ "transparent": false,
+ "depthWrite": true
+ }
+ ]
+ },
+ "outdoor-safety--17.6-9.799999999999999": {
+ "name": "操作区安全标线",
+ "position": [
+ -17.6,
+ 0.012,
+ 9.799999999999999
+ ],
+ "rotation": [
+ 0,
+ 0.5654866776461628,
+ 0
+ ],
+ "scale": [
+ 1,
+ 1,
+ 1
+ ],
+ "visible": true,
+ "customComponents": [],
+ "materials": [
+ {
+ "color": "e5c64c",
+ "emissive": "302600",
+ "emissiveIntensity": 0.18,
+ "metalness": 0.08,
+ "roughness": 0.72,
+ "opacity": 1,
+ "transparent": false,
+ "depthWrite": true
+ }
+ ]
+ },
+ "outdoor-safety-17.6--11": {
+ "name": "操作区安全标线",
+ "position": [
+ 17.6,
+ 0.012,
+ -11
+ ],
+ "rotation": [
+ 0,
+ 0.5654866776461628,
+ 0
+ ],
+ "scale": [
+ 1,
+ 1,
+ 1
+ ],
+ "visible": true,
+ "customComponents": [],
+ "materials": [
+ {
+ "color": "e5c64c",
+ "emissive": "302600",
+ "emissiveIntensity": 0.18,
+ "metalness": 0.08,
+ "roughness": 0.72,
+ "opacity": 1,
+ "transparent": false,
+ "depthWrite": true
+ }
+ ]
+ },
+ "outdoor-safety-17.6--8.4": {
+ "name": "操作区安全标线",
+ "position": [
+ 17.6,
+ 0.012,
+ -8.4
+ ],
+ "rotation": [
+ 0,
+ 0.5654866776461628,
+ 0
+ ],
+ "scale": [
+ 1,
+ 1,
+ 1
+ ],
+ "visible": true,
+ "customComponents": [],
+ "materials": [
+ {
+ "color": "e5c64c",
+ "emissive": "302600",
+ "emissiveIntensity": 0.18,
+ "metalness": 0.08,
+ "roughness": 0.72,
+ "opacity": 1,
+ "transparent": false,
+ "depthWrite": true
+ }
+ ]
+ },
+ "outdoor-safety-17.6--5.800000000000001": {
+ "name": "操作区安全标线",
+ "position": [
+ 17.6,
+ 0.012,
+ -5.800000000000001
+ ],
+ "rotation": [
+ 0,
+ 0.5654866776461628,
+ 0
+ ],
+ "scale": [
+ 1,
+ 1,
+ 1
+ ],
+ "visible": true,
+ "customComponents": [],
+ "materials": [
+ {
+ "color": "e5c64c",
+ "emissive": "302600",
+ "emissiveIntensity": 0.18,
+ "metalness": 0.08,
+ "roughness": 0.72,
+ "opacity": 1,
+ "transparent": false,
+ "depthWrite": true
+ }
+ ]
+ },
+ "outdoor-safety-17.6--3.2000000000000006": {
+ "name": "操作区安全标线",
+ "position": [
+ 17.6,
+ 0.012,
+ -3.2000000000000006
+ ],
+ "rotation": [
+ 0,
+ 0.5654866776461628,
+ 0
+ ],
+ "scale": [
+ 1,
+ 1,
+ 1
+ ],
+ "visible": true,
+ "customComponents": [],
+ "materials": [
+ {
+ "color": "e5c64c",
+ "emissive": "302600",
+ "emissiveIntensity": 0.18,
+ "metalness": 0.08,
+ "roughness": 0.72,
+ "opacity": 1,
+ "transparent": false,
+ "depthWrite": true
+ }
+ ]
+ },
+ "outdoor-safety-17.6--0.6000000000000005": {
+ "name": "操作区安全标线",
+ "position": [
+ 17.6,
+ 0.012,
+ -0.6000000000000005
+ ],
+ "rotation": [
+ 0,
+ 0.5654866776461628,
+ 0
+ ],
+ "scale": [
+ 1,
+ 1,
+ 1
+ ],
+ "visible": true,
+ "customComponents": [],
+ "materials": [
+ {
+ "color": "e5c64c",
+ "emissive": "302600",
+ "emissiveIntensity": 0.18,
+ "metalness": 0.08,
+ "roughness": 0.72,
+ "opacity": 1,
+ "transparent": false,
+ "depthWrite": true
+ }
+ ]
+ },
+ "outdoor-safety-17.6-1.9999999999999996": {
+ "name": "操作区安全标线",
+ "position": [
+ 17.6,
+ 0.012,
+ 1.9999999999999996
+ ],
+ "rotation": [
+ 0,
+ 0.5654866776461628,
+ 0
+ ],
+ "scale": [
+ 1,
+ 1,
+ 1
+ ],
+ "visible": true,
+ "customComponents": [],
+ "materials": [
+ {
+ "color": "e5c64c",
+ "emissive": "302600",
+ "emissiveIntensity": 0.18,
+ "metalness": 0.08,
+ "roughness": 0.72,
+ "opacity": 1,
+ "transparent": false,
+ "depthWrite": true
+ }
+ ]
+ },
+ "outdoor-safety-17.6-4.6": {
+ "name": "操作区安全标线",
+ "position": [
+ 17.6,
+ 0.012,
+ 4.6
+ ],
+ "rotation": [
+ 0,
+ 0.5654866776461628,
+ 0
+ ],
+ "scale": [
+ 1,
+ 1,
+ 1
+ ],
+ "visible": true,
+ "customComponents": [],
+ "materials": [
+ {
+ "color": "e5c64c",
+ "emissive": "302600",
+ "emissiveIntensity": 0.18,
+ "metalness": 0.08,
+ "roughness": 0.72,
+ "opacity": 1,
+ "transparent": false,
+ "depthWrite": true
+ }
+ ]
+ },
+ "outdoor-safety-17.6-7.199999999999999": {
+ "name": "操作区安全标线",
+ "position": [
+ 17.6,
+ 0.012,
+ 7.199999999999999
+ ],
+ "rotation": [
+ 0,
+ 0.5654866776461628,
+ 0
+ ],
+ "scale": [
+ 1,
+ 1,
+ 1
+ ],
+ "visible": true,
+ "customComponents": [],
+ "materials": [
+ {
+ "color": "e5c64c",
+ "emissive": "302600",
+ "emissiveIntensity": 0.18,
+ "metalness": 0.08,
+ "roughness": 0.72,
+ "opacity": 1,
+ "transparent": false,
+ "depthWrite": true
+ }
+ ]
+ },
+ "outdoor-safety-17.6-9.799999999999999": {
+ "name": "操作区安全标线",
+ "position": [
+ 17.6,
+ 0.012,
+ 9.799999999999999
+ ],
+ "rotation": [
+ 0,
+ 0.5654866776461628,
+ 0
+ ],
+ "scale": [
+ 1,
+ 1,
+ 1
+ ],
+ "visible": true,
+ "customComponents": [],
+ "materials": [
+ {
+ "color": "e5c64c",
+ "emissive": "302600",
+ "emissiveIntensity": 0.18,
+ "metalness": 0.08,
+ "roughness": 0.72,
+ "opacity": 1,
+ "transparent": false,
+ "depthWrite": true
+ }
+ ]
+ },
+ "outdoor-workshop": {
+ "name": "维修保障厂房",
+ "position": [
+ 0,
+ 0,
+ 0
+ ],
+ "rotation": [
+ 0,
+ 0,
+ 0
+ ],
+ "scale": [
+ 1,
+ 1,
+ 1
+ ],
+ "visible": true,
+ "customComponents": []
+ },
+ "outdoor-workshop-body": {
+ "name": "维修保障厂房主体",
+ "position": [
+ -10,
+ 3.25,
+ -18
+ ],
+ "rotation": [
+ 0,
+ 0,
+ 0
+ ],
+ "scale": [
+ 1,
+ 1,
+ 1
+ ],
+ "visible": true,
+ "customComponents": [],
+ "materials": [
+ {
+ "color": "536675",
+ "emissive": "000000",
+ "emissiveIntensity": 1,
+ "metalness": 0.18,
+ "roughness": 0.78,
+ "opacity": 1,
+ "transparent": false,
+ "depthWrite": true
+ }
+ ]
+ },
+ "outdoor-workshop-roof": {
+ "name": "厂房金属屋面",
+ "position": [
+ -10,
+ 6.65,
+ -18
+ ],
+ "rotation": [
+ 0,
+ 0,
+ 0
+ ],
+ "scale": [
+ 1,
+ 1,
+ 1
+ ],
+ "visible": true,
+ "customComponents": [],
+ "materials": [
+ {
+ "color": "263846",
+ "emissive": "000000",
+ "emissiveIntensity": 1,
+ "metalness": 0.35,
+ "roughness": 0.62,
+ "opacity": 1,
+ "transparent": false,
+ "depthWrite": true
+ }
+ ]
+ },
+ "outdoor-workshop-window-1": {
+ "name": "厂房观察窗 1",
+ "position": [
+ -17,
+ 3.7,
+ -14.21
+ ],
+ "rotation": [
+ 0,
+ 0,
+ 0
+ ],
+ "scale": [
+ 1,
+ 1,
+ 1
+ ],
+ "visible": true,
+ "customComponents": [],
+ "materials": [
+ {
+ "color": "4a89a2",
+ "emissive": "000000",
+ "emissiveIntensity": 1,
+ "metalness": 0.12,
+ "roughness": 0.25,
+ "opacity": 0.68,
+ "transparent": true,
+ "depthWrite": true
+ }
+ ]
+ },
+ "outdoor-workshop-window-2": {
+ "name": "厂房观察窗 2",
+ "position": [
+ -12.4,
+ 3.7,
+ -14.21
+ ],
+ "rotation": [
+ 0,
+ 0,
+ 0
+ ],
+ "scale": [
+ 1,
+ 1,
+ 1
+ ],
+ "visible": true,
+ "customComponents": [],
+ "materials": [
+ {
+ "color": "4a89a2",
+ "emissive": "000000",
+ "emissiveIntensity": 1,
+ "metalness": 0.12,
+ "roughness": 0.25,
+ "opacity": 0.68,
+ "transparent": true,
+ "depthWrite": true
+ }
+ ]
+ },
+ "outdoor-workshop-window-3": {
+ "name": "厂房观察窗 3",
+ "position": [
+ -7.800000000000001,
+ 3.7,
+ -14.21
+ ],
+ "rotation": [
+ 0,
+ 0,
+ 0
+ ],
+ "scale": [
+ 1,
+ 1,
+ 1
+ ],
+ "visible": true,
+ "customComponents": [],
+ "materials": [
+ {
+ "color": "4a89a2",
+ "emissive": "000000",
+ "emissiveIntensity": 1,
+ "metalness": 0.12,
+ "roughness": 0.25,
+ "opacity": 0.68,
+ "transparent": true,
+ "depthWrite": true
+ }
+ ]
+ },
+ "outdoor-workshop-window-4": {
+ "name": "厂房观察窗 4",
+ "position": [
+ -3.200000000000001,
+ 3.7,
+ -14.21
+ ],
+ "rotation": [
+ 0,
+ 0,
+ 0
+ ],
+ "scale": [
+ 1,
+ 1,
+ 1
+ ],
+ "visible": true,
+ "customComponents": [],
+ "materials": [
+ {
+ "color": "4a89a2",
+ "emissive": "000000",
+ "emissiveIntensity": 1,
+ "metalness": 0.12,
+ "roughness": 0.25,
+ "opacity": 0.68,
+ "transparent": true,
+ "depthWrite": true
+ }
+ ]
+ },
+ "outdoor-workshop-door": {
+ "name": "装备出入门",
+ "position": [
+ -3.6,
+ 2.16,
+ -14.18
+ ],
+ "rotation": [
+ 0,
+ 0,
+ 0
+ ],
+ "scale": [
+ 1,
+ 1,
+ 1
+ ],
+ "visible": true,
+ "customComponents": [],
+ "materials": [
+ {
+ "color": "202a31",
+ "emissive": "000000",
+ "emissiveIntensity": 1,
+ "metalness": 0.02,
+ "roughness": 0.95,
+ "opacity": 1,
+ "transparent": false,
+ "depthWrite": true
+ }
+ ]
+ },
+ "outdoor-canopy": {
+ "name": "室外检修棚",
+ "position": [
+ 0,
+ 0,
+ 0
+ ],
+ "rotation": [
+ 0,
+ 0,
+ 0
+ ],
+ "scale": [
+ 1,
+ 1,
+ 1
+ ],
+ "visible": true,
+ "customComponents": []
+ },
+ "outdoor-canopy-roof": {
+ "name": "检修棚顶",
+ "position": [
+ 18,
+ 5.05,
+ -8
+ ],
+ "rotation": [
+ 0,
+ 0,
+ 0
+ ],
+ "scale": [
+ 1,
+ 1,
+ 1
+ ],
+ "visible": true,
+ "customComponents": [],
+ "materials": [
+ {
+ "color": "263846",
+ "emissive": "000000",
+ "emissiveIntensity": 1,
+ "metalness": 0.35,
+ "roughness": 0.62,
+ "opacity": 1,
+ "transparent": false,
+ "depthWrite": true
+ }
+ ]
+ },
+ "outdoor-canopy-column-1": {
+ "name": "检修棚立柱 1",
+ "position": [
+ 12.7,
+ 2.5,
+ -11
+ ],
+ "rotation": [
+ 0,
+ 0,
+ 0
+ ],
+ "scale": [
+ 1,
+ 1,
+ 1
+ ],
+ "visible": true,
+ "customComponents": [],
+ "materials": [
+ {
+ "color": "66757c",
+ "emissive": "000000",
+ "emissiveIntensity": 1,
+ "metalness": 0.48,
+ "roughness": 0.56,
+ "opacity": 1,
+ "transparent": false,
+ "depthWrite": true
+ }
+ ]
+ },
+ "outdoor-canopy-column-2": {
+ "name": "检修棚立柱 2",
+ "position": [
+ 23.3,
+ 2.5,
+ -11
+ ],
+ "rotation": [
+ 0,
+ 0,
+ 0
+ ],
+ "scale": [
+ 1,
+ 1,
+ 1
+ ],
+ "visible": true,
+ "customComponents": [],
+ "materials": [
+ {
+ "color": "66757c",
+ "emissive": "000000",
+ "emissiveIntensity": 1,
+ "metalness": 0.48,
+ "roughness": 0.56,
+ "opacity": 1,
+ "transparent": false,
+ "depthWrite": true
+ }
+ ]
+ },
+ "outdoor-canopy-column-3": {
+ "name": "检修棚立柱 3",
+ "position": [
+ 12.7,
+ 2.5,
+ -5
+ ],
+ "rotation": [
+ 0,
+ 0,
+ 0
+ ],
+ "scale": [
+ 1,
+ 1,
+ 1
+ ],
+ "visible": true,
+ "customComponents": [],
+ "materials": [
+ {
+ "color": "66757c",
+ "emissive": "000000",
+ "emissiveIntensity": 1,
+ "metalness": 0.48,
+ "roughness": 0.56,
+ "opacity": 1,
+ "transparent": false,
+ "depthWrite": true
+ }
+ ]
+ },
+ "outdoor-canopy-column-4": {
+ "name": "检修棚立柱 4",
+ "position": [
+ 23.3,
+ 2.5,
+ -5
+ ],
+ "rotation": [
+ 0,
+ 0,
+ 0
+ ],
+ "scale": [
+ 1,
+ 1,
+ 1
+ ],
+ "visible": true,
+ "customComponents": [],
+ "materials": [
+ {
+ "color": "66757c",
+ "emissive": "000000",
+ "emissiveIntensity": 1,
+ "metalness": 0.48,
+ "roughness": 0.56,
+ "opacity": 1,
+ "transparent": false,
+ "depthWrite": true
+ }
+ ]
+ },
+ "outdoor-service-box": {
+ "name": "室外工具柜",
+ "position": [
+ 22.2,
+ 1.05,
+ -9.8
+ ],
+ "rotation": [
+ 0,
+ 0,
+ 0
+ ],
+ "scale": [
+ 1,
+ 1,
+ 1
+ ],
+ "visible": true,
+ "customComponents": [],
+ "materials": [
+ {
+ "color": "d98d2b",
+ "emissive": "000000",
+ "emissiveIntensity": 1,
+ "metalness": 0.12,
+ "roughness": 0.7,
+ "opacity": 1,
+ "transparent": false,
+ "depthWrite": true
+ }
+ ]
+ },
+ "outdoor-perimeter": {
+ "name": "场地边界与安全设施",
+ "position": [
+ 0,
+ 0,
+ 0
+ ],
+ "rotation": [
+ 0,
+ 0,
+ 0
+ ],
+ "scale": [
+ 1,
+ 1,
+ 1
+ ],
+ "visible": true,
+ "customComponents": []
+ },
+ "outdoor-fence-back-1": {
+ "name": "后侧围栏立柱 1",
+ "position": [
+ -29,
+ 1.1,
+ -25
+ ],
+ "rotation": [
+ 0,
+ 0,
+ 0
+ ],
+ "scale": [
+ 1,
+ 1,
+ 1
+ ],
+ "visible": true,
+ "customComponents": [],
+ "materials": [
+ {
+ "color": "66757c",
+ "emissive": "000000",
+ "emissiveIntensity": 1,
+ "metalness": 0.48,
+ "roughness": 0.56,
+ "opacity": 1,
+ "transparent": false,
+ "depthWrite": true
+ }
+ ]
+ },
+ "outdoor-fence-back-2": {
+ "name": "后侧围栏立柱 2",
+ "position": [
+ -23.2,
+ 1.1,
+ -25
+ ],
+ "rotation": [
+ 0,
+ 0,
+ 0
+ ],
+ "scale": [
+ 1,
+ 1,
+ 1
+ ],
+ "visible": true,
+ "customComponents": [],
+ "materials": [
+ {
+ "color": "66757c",
+ "emissive": "000000",
+ "emissiveIntensity": 1,
+ "metalness": 0.48,
+ "roughness": 0.56,
+ "opacity": 1,
+ "transparent": false,
+ "depthWrite": true
+ }
+ ]
+ },
+ "outdoor-fence-back-3": {
+ "name": "后侧围栏立柱 3",
+ "position": [
+ -17.4,
+ 1.1,
+ -25
+ ],
+ "rotation": [
+ 0,
+ 0,
+ 0
+ ],
+ "scale": [
+ 1,
+ 1,
+ 1
+ ],
+ "visible": true,
+ "customComponents": [],
+ "materials": [
+ {
+ "color": "66757c",
+ "emissive": "000000",
+ "emissiveIntensity": 1,
+ "metalness": 0.48,
+ "roughness": 0.56,
+ "opacity": 1,
+ "transparent": false,
+ "depthWrite": true
+ }
+ ]
+ },
+ "outdoor-fence-back-4": {
+ "name": "后侧围栏立柱 4",
+ "position": [
+ -11.6,
+ 1.1,
+ -25
+ ],
+ "rotation": [
+ 0,
+ 0,
+ 0
+ ],
+ "scale": [
+ 1,
+ 1,
+ 1
+ ],
+ "visible": true,
+ "customComponents": [],
+ "materials": [
+ {
+ "color": "66757c",
+ "emissive": "000000",
+ "emissiveIntensity": 1,
+ "metalness": 0.48,
+ "roughness": 0.56,
+ "opacity": 1,
+ "transparent": false,
+ "depthWrite": true
+ }
+ ]
+ },
+ "outdoor-fence-back-5": {
+ "name": "后侧围栏立柱 5",
+ "position": [
+ -5.8,
+ 1.1,
+ -25
+ ],
+ "rotation": [
+ 0,
+ 0,
+ 0
+ ],
+ "scale": [
+ 1,
+ 1,
+ 1
+ ],
+ "visible": true,
+ "customComponents": [],
+ "materials": [
+ {
+ "color": "66757c",
+ "emissive": "000000",
+ "emissiveIntensity": 1,
+ "metalness": 0.48,
+ "roughness": 0.56,
+ "opacity": 1,
+ "transparent": false,
+ "depthWrite": true
+ }
+ ]
+ },
+ "outdoor-fence-back-6": {
+ "name": "后侧围栏立柱 6",
+ "position": [
+ 0,
+ 1.1,
+ -25
+ ],
+ "rotation": [
+ 0,
+ 0,
+ 0
+ ],
+ "scale": [
+ 1,
+ 1,
+ 1
+ ],
+ "visible": true,
+ "customComponents": [],
+ "materials": [
+ {
+ "color": "66757c",
+ "emissive": "000000",
+ "emissiveIntensity": 1,
+ "metalness": 0.48,
+ "roughness": 0.56,
+ "opacity": 1,
+ "transparent": false,
+ "depthWrite": true
+ }
+ ]
+ },
+ "outdoor-fence-back-7": {
+ "name": "后侧围栏立柱 7",
+ "position": [
+ 5.8,
+ 1.1,
+ -25
+ ],
+ "rotation": [
+ 0,
+ 0,
+ 0
+ ],
+ "scale": [
+ 1,
+ 1,
+ 1
+ ],
+ "visible": true,
+ "customComponents": [],
+ "materials": [
+ {
+ "color": "66757c",
+ "emissive": "000000",
+ "emissiveIntensity": 1,
+ "metalness": 0.48,
+ "roughness": 0.56,
+ "opacity": 1,
+ "transparent": false,
+ "depthWrite": true
+ }
+ ]
+ },
+ "outdoor-fence-back-8": {
+ "name": "后侧围栏立柱 8",
+ "position": [
+ 11.6,
+ 1.1,
+ -25
+ ],
+ "rotation": [
+ 0,
+ 0,
+ 0
+ ],
+ "scale": [
+ 1,
+ 1,
+ 1
+ ],
+ "visible": true,
+ "customComponents": [],
+ "materials": [
+ {
+ "color": "66757c",
+ "emissive": "000000",
+ "emissiveIntensity": 1,
+ "metalness": 0.48,
+ "roughness": 0.56,
+ "opacity": 1,
+ "transparent": false,
+ "depthWrite": true
+ }
+ ]
+ },
+ "outdoor-fence-back-9": {
+ "name": "后侧围栏立柱 9",
+ "position": [
+ 17.4,
+ 1.1,
+ -25
+ ],
+ "rotation": [
+ 0,
+ 0,
+ 0
+ ],
+ "scale": [
+ 1,
+ 1,
+ 1
+ ],
+ "visible": true,
+ "customComponents": [],
+ "materials": [
+ {
+ "color": "66757c",
+ "emissive": "000000",
+ "emissiveIntensity": 1,
+ "metalness": 0.48,
+ "roughness": 0.56,
+ "opacity": 1,
+ "transparent": false,
+ "depthWrite": true
+ }
+ ]
+ },
+ "outdoor-fence-back-10": {
+ "name": "后侧围栏立柱 10",
+ "position": [
+ 23.2,
+ 1.1,
+ -25
+ ],
+ "rotation": [
+ 0,
+ 0,
+ 0
+ ],
+ "scale": [
+ 1,
+ 1,
+ 1
+ ],
+ "visible": true,
+ "customComponents": [],
+ "materials": [
+ {
+ "color": "66757c",
+ "emissive": "000000",
+ "emissiveIntensity": 1,
+ "metalness": 0.48,
+ "roughness": 0.56,
+ "opacity": 1,
+ "transparent": false,
+ "depthWrite": true
+ }
+ ]
+ },
+ "outdoor-fence-back-11": {
+ "name": "后侧围栏立柱 11",
+ "position": [
+ 29,
+ 1.1,
+ -25
+ ],
+ "rotation": [
+ 0,
+ 0,
+ 0
+ ],
+ "scale": [
+ 1,
+ 1,
+ 1
+ ],
+ "visible": true,
+ "customComponents": [],
+ "materials": [
+ {
+ "color": "66757c",
+ "emissive": "000000",
+ "emissiveIntensity": 1,
+ "metalness": 0.48,
+ "roughness": 0.56,
+ "opacity": 1,
+ "transparent": false,
+ "depthWrite": true
+ }
+ ]
+ },
+ "outdoor-fence-rail-a": {
+ "name": "后侧围栏上横杆",
+ "position": [
+ 0,
+ 1.85,
+ -25
+ ],
+ "rotation": [
+ 0,
+ 0,
+ 0
+ ],
+ "scale": [
+ 1,
+ 1,
+ 1
+ ],
+ "visible": true,
+ "customComponents": [],
+ "materials": [
+ {
+ "color": "66757c",
+ "emissive": "000000",
+ "emissiveIntensity": 1,
+ "metalness": 0.48,
+ "roughness": 0.56,
+ "opacity": 1,
+ "transparent": false,
+ "depthWrite": true
+ }
+ ]
+ },
+ "outdoor-fence-rail-b": {
+ "name": "后侧围栏下横杆",
+ "position": [
+ 0,
+ 0.75,
+ -25
+ ],
+ "rotation": [
+ 0,
+ 0,
+ 0
+ ],
+ "scale": [
+ 1,
+ 1,
+ 1
+ ],
+ "visible": true,
+ "customComponents": [],
+ "materials": [
+ {
+ "color": "66757c",
+ "emissive": "000000",
+ "emissiveIntensity": 1,
+ "metalness": 0.48,
+ "roughness": 0.56,
+ "opacity": 1,
+ "transparent": false,
+ "depthWrite": true
+ }
+ ]
+ },
+ "outdoor-scene-sign": {
+ "name": "室外实训区域标识",
+ "position": [
+ 26,
+ 2.4,
+ 11.5
+ ],
+ "rotation": [
+ 0,
+ 0,
+ 0
+ ],
+ "scale": [
+ 1,
+ 1,
+ 1
+ ],
+ "visible": true,
+ "customComponents": [],
+ "materials": [
+ {
+ "color": "536675",
+ "emissive": "000000",
+ "emissiveIntensity": 1,
+ "metalness": 0.18,
+ "roughness": 0.78,
+ "opacity": 1,
+ "transparent": false,
+ "depthWrite": true
+ }
+ ]
+ },
+ "outdoor-scene-sign-post-a": {
+ "name": "区域标识支柱 A",
+ "position": [
+ 24,
+ 1.4,
+ 11.5
+ ],
+ "rotation": [
+ 0,
+ 0,
+ 0
+ ],
+ "scale": [
+ 1,
+ 1,
+ 1
+ ],
+ "visible": true,
+ "customComponents": [],
+ "materials": [
+ {
+ "color": "66757c",
+ "emissive": "000000",
+ "emissiveIntensity": 1,
+ "metalness": 0.48,
+ "roughness": 0.56,
+ "opacity": 1,
+ "transparent": false,
+ "depthWrite": true
+ }
+ ]
+ },
+ "outdoor-scene-sign-post-b": {
+ "name": "区域标识支柱 B",
+ "position": [
+ 28,
+ 1.4,
+ 11.5
+ ],
+ "rotation": [
+ 0,
+ 0,
+ 0
+ ],
+ "scale": [
+ 1,
+ 1,
+ 1
+ ],
+ "visible": true,
+ "customComponents": [],
+ "materials": [
+ {
+ "color": "66757c",
+ "emissive": "000000",
+ "emissiveIntensity": 1,
+ "metalness": 0.48,
+ "roughness": 0.56,
+ "opacity": 1,
+ "transparent": false,
+ "depthWrite": true
+ }
+ ]
+ }
+ }
+ },
+ "runtime": {
+ "startMode": "guided",
+ "showStepPanel": true,
+ "showProgress": true,
+ "allowReplay": true,
+ "language": "zh-CN"
+ },
+ "publish": {
+ "target": "standalone-html",
+ "deployment": "offline",
+ "includeProjectData": true,
+ "includeViewportPreview": true
+ }
+ }
+}
\ No newline at end of file
diff --git a/reports/model-editor-closure-20260906/downloads/recovery-224-isolated.json b/reports/model-editor-closure-20260906/downloads/recovery-224-isolated.json
new file mode 100644
index 0000000..e6493fe
--- /dev/null
+++ b/reports/model-editor-closure-20260906/downloads/recovery-224-isolated.json
@@ -0,0 +1,3422 @@
+{
+ "schema": "unreal-tran.model-api-recovery/v1",
+ "snapshotId": "19cf8887-dd9e-48ee-a6b7-ecf37e1c5a10",
+ "projectId": "224",
+ "projectVersion": 2,
+ "savedAt": "2026-09-06T08:17:07.705Z",
+ "ownerPageNonce": "e6759ee4-03b5-4ec6-9e2d-b969e587757b",
+ "document": {
+ "schema": "digital-twin-editor-project",
+ "version": 4,
+ "name": "闭环验收-恢复副本-mtpjg6y2",
+ "modelId": "imported-model_mto6v5w6_09-fox",
+ "modelResource": {
+ "kind": "imported",
+ "id": "imported-model_mto6v5w6_09-fox",
+ "name": "09-Fox",
+ "assetCode": "MODEL_MTO6V5W6_09-FOX",
+ "code": "MODEL_MTO6V5W6_09-FOX",
+ "storageUri": "fsvc://unreal-tran/model/2026/09/05/3fc69ae0d435f869afca1fcd943486d1.glb",
+ "assetKey": "",
+ "resourceUrl": "",
+ "sourceName": "09-Fox.glb",
+ "sizeBytes": 162852,
+ "lastModified": 0,
+ "mimeType": "model/gltf-binary",
+ "thumbnail": "",
+ "vertices": 1728,
+ "triangles": 576,
+ "nodes": 27,
+ "meshes": 1,
+ "materials": 1,
+ "textures": 1,
+ "animations": 3,
+ "animationDurationSeconds": 3.4166667461395264,
+ "modelMetadata": {
+ "nodeCount": 27,
+ "meshCount": 1,
+ "materialCount": 1,
+ "textureCount": 1,
+ "animations": [
+ {
+ "index": 0,
+ "name": "Survey",
+ "start": 0,
+ "end": 3.4166667461395264,
+ "duration": 3.4166667461395264,
+ "trackCount": 21
+ },
+ {
+ "index": 1,
+ "name": "Walk",
+ "start": 0,
+ "end": 0.7083333134651184,
+ "duration": 0.7083333134651184,
+ "trackCount": 21
+ },
+ {
+ "index": 2,
+ "name": "Run",
+ "start": 0,
+ "end": 1.1583333015441895,
+ "duration": 1.1583333015441895,
+ "trackCount": 21
+ }
+ ],
+ "animationDurationSeconds": 3.4166667461395264
+ },
+ "nativeAnimations": [
+ {
+ "index": 0,
+ "name": "Survey",
+ "start": 0,
+ "end": 3.4166667461395264,
+ "duration": 3.4166667461395264,
+ "trackCount": 21
+ },
+ {
+ "index": 1,
+ "name": "Walk",
+ "start": 0,
+ "end": 0.7083333134651184,
+ "duration": 0.7083333134651184,
+ "trackCount": 21
+ },
+ {
+ "index": 2,
+ "name": "Run",
+ "start": 0,
+ "end": 1.1583333015441895,
+ "duration": 1.1583333015441895,
+ "trackCount": 21
+ }
+ ]
+ },
+ "meta": {
+ "description": "用于装备维修实训场景编排、编辑器回归与离线发布验证的系统内置基准工程",
+ "author": "数字孪生项目组",
+ "scene": "车间与野外装备维修实训环境",
+ "tags": [
+ "三维场景",
+ "时间轴动画",
+ "蓝图交互",
+ "实训流程"
+ ],
+ "migratedFrom": "legacy-model-document",
+ "source": "content-api-glb",
+ "sourceName": "09-Fox.glb",
+ "assetCode": "MODEL_MTO6V5W6_09-FOX",
+ "storageUri": "fsvc://unreal-tran/model/2026/09/05/3fc69ae0d435f869afca1fcd943486d1.glb"
+ },
+ "timeline": {
+ "version": 1,
+ "duration": 8,
+ "currentTime": 0,
+ "loop": true,
+ "zoom": 1,
+ "tracks": [
+ {
+ "id": "track-0-1yjfz9t",
+ "name": "模型整体位移",
+ "targetId": "interaction-b9240aaa",
+ "property": "position",
+ "color": "#ffb22e",
+ "muted": false,
+ "keyframes": [
+ {
+ "id": "key-0-0-kmfo820",
+ "time": 0,
+ "value": [
+ 0,
+ 0,
+ 0
+ ],
+ "easing": "easeInOut"
+ },
+ {
+ "id": "key-0-1-snsj2e2",
+ "time": 8,
+ "value": [
+ 0,
+ 0,
+ 0
+ ],
+ "easing": "easeInOut"
+ }
+ ]
+ }
+ ]
+ },
+ "blueprint": {
+ "version": 1,
+ "name": "挖掘机作业实训闭环",
+ "viewport": {
+ "x": 55,
+ "y": 55,
+ "zoom": 0.82
+ },
+ "nodes": [
+ {
+ "id": "bp-start",
+ "type": "trigger-click",
+ "title": "点击挖掘机",
+ "x": 40,
+ "y": 55,
+ "params": {
+ "event": "click",
+ "target": "挖掘机"
+ }
+ },
+ {
+ "id": "bp-highlight",
+ "type": "highlight",
+ "title": "高亮工作装置",
+ "x": 300,
+ "y": 55,
+ "params": {
+ "target": "铲斗",
+ "color": "#38e8ff",
+ "pulse": true
+ }
+ },
+ {
+ "id": "bp-message",
+ "type": "message",
+ "title": "显示操作提示",
+ "x": 560,
+ "y": 55,
+ "params": {
+ "title": "挖掘作业实训",
+ "text": "观察动臂、斗杆和铲斗的协同动作",
+ "level": "info"
+ }
+ },
+ {
+ "id": "bp-play",
+ "type": "play-timeline",
+ "title": "播放作业动画",
+ "x": 820,
+ "y": 55,
+ "params": {
+ "clip": "挖掘机作业实训",
+ "from": 0,
+ "speed": 1,
+ "waitForEnd": true
+ }
+ },
+ {
+ "id": "bp-wait",
+ "type": "wait",
+ "title": "动作稳定",
+ "x": 560,
+ "y": 245,
+ "params": {
+ "duration": 600
+ }
+ },
+ {
+ "id": "bp-check",
+ "type": "condition",
+ "title": "动画是否完成",
+ "x": 300,
+ "y": 245,
+ "params": {
+ "variable": "timelineComplete",
+ "operator": "==",
+ "value": true
+ }
+ },
+ {
+ "id": "bp-retry",
+ "type": "message",
+ "title": "等待动画完成",
+ "x": 820,
+ "y": 245,
+ "params": {
+ "title": "流程校验",
+ "text": "动画尚未完成,请重新执行",
+ "level": "warning"
+ }
+ },
+ {
+ "id": "bp-finish",
+ "type": "finish",
+ "title": "流程完成",
+ "x": 40,
+ "y": 245,
+ "params": {
+ "result": "success",
+ "text": "挖掘机作业实训流程完成"
+ }
+ }
+ ],
+ "edges": [
+ {
+ "id": "bp-e1",
+ "from": {
+ "node": "bp-start",
+ "port": "then"
+ },
+ "to": {
+ "node": "bp-highlight",
+ "port": "in"
+ }
+ },
+ {
+ "id": "bp-e2",
+ "from": {
+ "node": "bp-highlight",
+ "port": "then"
+ },
+ "to": {
+ "node": "bp-message",
+ "port": "in"
+ }
+ },
+ {
+ "id": "bp-e3",
+ "from": {
+ "node": "bp-message",
+ "port": "then"
+ },
+ "to": {
+ "node": "bp-play",
+ "port": "in"
+ }
+ },
+ {
+ "id": "bp-e4",
+ "from": {
+ "node": "bp-play",
+ "port": "then"
+ },
+ "to": {
+ "node": "bp-wait",
+ "port": "in"
+ }
+ },
+ {
+ "id": "bp-e5",
+ "from": {
+ "node": "bp-wait",
+ "port": "then"
+ },
+ "to": {
+ "node": "bp-check",
+ "port": "in"
+ }
+ },
+ {
+ "id": "bp-e6",
+ "from": {
+ "node": "bp-check",
+ "port": "true"
+ },
+ "to": {
+ "node": "bp-finish",
+ "port": "in"
+ }
+ },
+ {
+ "id": "bp-e7",
+ "from": {
+ "node": "bp-check",
+ "port": "false"
+ },
+ "to": {
+ "node": "bp-retry",
+ "port": "in"
+ }
+ },
+ {
+ "id": "bp-e8",
+ "from": {
+ "node": "bp-retry",
+ "port": "then"
+ },
+ "to": {
+ "node": "bp-play",
+ "port": "in"
+ }
+ }
+ ]
+ },
+ "camera": {
+ "position": [
+ 4.643347096844712,
+ 4.616668289605231,
+ 12.832677927580558
+ ],
+ "target": [
+ 3.875009990661482e-8,
+ 2.4006999949620704,
+ 2.2660093712545404e-7
+ ]
+ },
+ "model": {
+ "objects": {
+ "equipment-root": {
+ "name": "09-Fox",
+ "nodeName": "09-Fox",
+ "nodePath": "@root",
+ "interactionId": "interaction-b9240aaa",
+ "capabilities": {
+ "selectable": true,
+ "transform": true,
+ "visible": true,
+ "material": false,
+ "animatable": true
+ },
+ "position": [
+ 0,
+ 0,
+ 0
+ ],
+ "rotation": [
+ 0,
+ 0,
+ 0
+ ],
+ "scale": [
+ 1,
+ 1,
+ 1
+ ],
+ "visible": true,
+ "customComponents": []
+ },
+ "model-imported-model_mto6v5w6_09-fox-0": {
+ "name": "",
+ "nodeName": "Group",
+ "nodePath": "0",
+ "interactionId": "interaction-02351bce",
+ "capabilities": {
+ "selectable": true,
+ "transform": true,
+ "visible": true,
+ "material": false,
+ "animatable": true
+ },
+ "position": [
+ 3.875009990661482e-8,
+ 0.007396563645075124,
+ 0.6522089834776823
+ ],
+ "rotation": [
+ 0,
+ 0,
+ 0
+ ],
+ "scale": [
+ 0.06075496488891343,
+ 0.06075496488891343,
+ 0.06075496488891343
+ ],
+ "visible": true,
+ "customComponents": []
+ },
+ "model-imported-model_mto6v5w6_09-fox-1": {
+ "name": "root",
+ "nodeName": "root",
+ "nodePath": "0/0",
+ "interactionId": "interaction-a4d35db9",
+ "capabilities": {
+ "selectable": true,
+ "transform": true,
+ "visible": true,
+ "material": false,
+ "animatable": true
+ },
+ "position": [
+ 0,
+ 0,
+ 0
+ ],
+ "rotation": [
+ 0,
+ 0,
+ 0
+ ],
+ "scale": [
+ 1,
+ 1,
+ 1
+ ],
+ "visible": true,
+ "customComponents": []
+ },
+ "model-imported-model_mto6v5w6_09-fox-2": {
+ "name": "_rootJoint",
+ "nodeName": "_rootJoint",
+ "nodePath": "0/0/0",
+ "interactionId": "interaction-4476c556",
+ "capabilities": {
+ "selectable": true,
+ "transform": true,
+ "visible": true,
+ "material": false,
+ "animatable": true
+ },
+ "position": [
+ 0,
+ 0,
+ 0
+ ],
+ "rotation": [
+ 0,
+ 0,
+ 0
+ ],
+ "scale": [
+ 1,
+ 1,
+ 1
+ ],
+ "visible": true,
+ "customComponents": []
+ },
+ "model-imported-model_mto6v5w6_09-fox-3": {
+ "name": "b_Root_00",
+ "nodeName": "b_Root_00",
+ "nodePath": "0/0/0/0",
+ "interactionId": "interaction-ffc45d11",
+ "capabilities": {
+ "selectable": true,
+ "transform": true,
+ "visible": true,
+ "material": false,
+ "animatable": true
+ },
+ "position": [
+ 0,
+ 0,
+ 0
+ ],
+ "rotation": [
+ -1.5708000357176293,
+ 0,
+ 0
+ ],
+ "scale": [
+ 1,
+ 1,
+ 1
+ ],
+ "visible": true,
+ "customComponents": []
+ },
+ "model-imported-model_mto6v5w6_09-fox-4": {
+ "name": "b_Hip_01",
+ "nodeName": "b_Hip_01",
+ "nodePath": "0/0/0/0/0",
+ "interactionId": "interaction-7a75e39e",
+ "capabilities": {
+ "selectable": true,
+ "transform": true,
+ "visible": true,
+ "material": false,
+ "animatable": true
+ },
+ "position": [
+ 0.000001241521920852674,
+ 22.061977333358605,
+ 34.62373020761327
+ ],
+ "rotation": [
+ 0.44722050284761794,
+ -1.5707951637404125,
+ 0
+ ],
+ "scale": [
+ 1,
+ 1,
+ 1
+ ],
+ "visible": true,
+ "customComponents": []
+ },
+ "model-imported-model_mto6v5w6_09-fox-5": {
+ "name": "b_Spine01_02",
+ "nodeName": "b_Spine01_02",
+ "nodePath": "0/0/0/0/0/0",
+ "interactionId": "interaction-663ea469",
+ "capabilities": {
+ "selectable": true,
+ "transform": true,
+ "visible": true,
+ "material": false,
+ "animatable": true
+ },
+ "position": [
+ 12.850601196289062,
+ 0,
+ 0
+ ],
+ "rotation": [
+ -3.357310257291308e-7,
+ -3.316574913360067e-8,
+ -1.2102038452161539
+ ],
+ "scale": [
+ 1,
+ 1,
+ 1
+ ],
+ "visible": true,
+ "customComponents": []
+ },
+ "model-imported-model_mto6v5w6_09-fox-6": {
+ "name": "b_Spine02_03",
+ "nodeName": "b_Spine02_03",
+ "nodePath": "0/0/0/0/0/0/0",
+ "interactionId": "interaction-46867126",
+ "capabilities": {
+ "selectable": true,
+ "transform": true,
+ "visible": true,
+ "material": false,
+ "animatable": true
+ },
+ "position": [
+ 21.65575408935547,
+ -0.000118255615234375,
+ 0
+ ],
+ "rotation": [
+ 8.772651057591018e-8,
+ 3.3355428835535634e-8,
+ 0.3633406824515624
+ ],
+ "scale": [
+ 1,
+ 1,
+ 1
+ ],
+ "visible": true,
+ "customComponents": []
+ },
+ "model-imported-model_mto6v5w6_09-fox-7": {
+ "name": "b_Neck_04",
+ "nodeName": "b_Neck_04",
+ "nodePath": "0/0/0/0/0/0/0/0",
+ "interactionId": "interaction-27148d81",
+ "capabilities": {
+ "selectable": true,
+ "transform": true,
+ "visible": true,
+ "material": false,
+ "animatable": true
+ },
+ "position": [
+ 25.64914321899414,
+ 0,
+ 0
+ ],
+ "rotation": [
+ 6.423807629998062e-15,
+ 1.9993472402144555e-16,
+ 0.031113979193075042
+ ],
+ "scale": [
+ 1,
+ 1,
+ 1
+ ],
+ "visible": true,
+ "customComponents": []
+ },
+ "model-imported-model_mto6v5w6_09-fox-8": {
+ "name": "b_Head_05",
+ "nodeName": "b_Head_05",
+ "nodePath": "0/0/0/0/0/0/0/0/0",
+ "interactionId": "interaction-b24eef2e",
+ "capabilities": {
+ "selectable": true,
+ "transform": true,
+ "visible": true,
+ "material": false,
+ "animatable": true
+ },
+ "position": [
+ 13.376960754394531,
+ 0,
+ 0
+ ],
+ "rotation": [
+ 3.253825627358364e-8,
+ -1.577320798309453e-8,
+ -0.451380500897834
+ ],
+ "scale": [
+ 1,
+ 1,
+ 1
+ ],
+ "visible": true,
+ "customComponents": []
+ },
+ "model-imported-model_mto6v5w6_09-fox-9": {
+ "name": "b_RightUpperArm_06",
+ "nodeName": "b_RightUpperArm_06",
+ "nodePath": "0/0/0/0/0/0/0/1",
+ "interactionId": "interaction-26148bee",
+ "capabilities": {
+ "selectable": true,
+ "transform": true,
+ "visible": true,
+ "material": false,
+ "animatable": true
+ },
+ "position": [
+ 18.677913665771484,
+ -4.297340393066406,
+ 6.9675750732421875
+ ],
+ "rotation": [
+ -0.035644184619881884,
+ -0.047001863061712265,
+ -1.2972005346348685
+ ],
+ "scale": [
+ 1,
+ 1,
+ 1
+ ],
+ "visible": true,
+ "customComponents": []
+ },
+ "model-imported-model_mto6v5w6_09-fox-10": {
+ "name": "b_RightForeArm_07",
+ "nodeName": "b_RightForeArm_07",
+ "nodePath": "0/0/0/0/0/0/0/1/0",
+ "interactionId": "interaction-36896559",
+ "capabilities": {
+ "selectable": true,
+ "transform": true,
+ "visible": true,
+ "material": false,
+ "animatable": true
+ },
+ "position": [
+ 23.04512596130371,
+ 0,
+ 0
+ ],
+ "rotation": [
+ 2.1355818997049898e-8,
+ 7.233656206674673e-8,
+ 1.2561843405841544
+ ],
+ "scale": [
+ 1,
+ 1,
+ 1
+ ],
+ "visible": true,
+ "customComponents": []
+ },
+ "model-imported-model_mto6v5w6_09-fox-11": {
+ "name": "b_RightHand_08",
+ "nodeName": "b_RightHand_08",
+ "nodePath": "0/0/0/0/0/0/0/1/0/0",
+ "interactionId": "interaction-57a124f6",
+ "capabilities": {
+ "selectable": true,
+ "transform": true,
+ "visible": true,
+ "material": false,
+ "animatable": true
+ },
+ "position": [
+ 19.350055694580078,
+ -0.14598655700683594,
+ 0
+ ],
+ "rotation": [
+ 0.005245101551986586,
+ 0.05200705984217871,
+ -0.267165337534188
+ ],
+ "scale": [
+ 1,
+ 1,
+ 1
+ ],
+ "visible": true,
+ "customComponents": []
+ },
+ "model-imported-model_mto6v5w6_09-fox-12": {
+ "name": "b_LeftUpperArm_09",
+ "nodeName": "b_LeftUpperArm_09",
+ "nodePath": "0/0/0/0/0/0/0/2",
+ "interactionId": "interaction-25148a5b",
+ "capabilities": {
+ "selectable": true,
+ "transform": true,
+ "visible": true,
+ "material": false,
+ "animatable": true
+ },
+ "position": [
+ 18.67791748046875,
+ -4.297344207763672,
+ -6.967987060546875
+ ],
+ "rotation": [
+ -0.00020118104561539401,
+ 0.0009350561550523671,
+ -1.5148902559141275
+ ],
+ "scale": [
+ 1,
+ 1,
+ 1
+ ],
+ "visible": true,
+ "customComponents": []
+ },
+ "model-imported-model_mto6v5w6_09-fox-13": {
+ "name": "b_LeftForeArm_010",
+ "nodeName": "b_LeftForeArm_010",
+ "nodePath": "0/0/0/0/0/0/0/2/0",
+ "interactionId": "interaction-5c502ac4",
+ "capabilities": {
+ "selectable": true,
+ "transform": true,
+ "visible": true,
+ "material": false,
+ "animatable": true
+ },
+ "position": [
+ 23.045124053955078,
+ 0,
+ 0
+ ],
+ "rotation": [
+ 2.5220861991717696e-8,
+ 7.109903232964989e-8,
+ 1.2307106255258764
+ ],
+ "scale": [
+ 1,
+ 1,
+ 1
+ ],
+ "visible": true,
+ "customComponents": []
+ },
+ "model-imported-model_mto6v5w6_09-fox-14": {
+ "name": "b_LeftHand_011",
+ "nodeName": "b_LeftHand_011",
+ "nodePath": "0/0/0/0/0/0/0/2/0/0",
+ "interactionId": "interaction-192b4cd3",
+ "capabilities": {
+ "selectable": true,
+ "transform": true,
+ "visible": true,
+ "material": false,
+ "animatable": true
+ },
+ "position": [
+ 19.350051879882812,
+ -0.14599037170410156,
+ 0
+ ],
+ "rotation": [
+ 0.004279557751527162,
+ -0.03344276494440009,
+ -1.5985291598689726
+ ],
+ "scale": [
+ 1,
+ 1,
+ 1
+ ],
+ "visible": true,
+ "customComponents": []
+ },
+ "model-imported-model_mto6v5w6_09-fox-15": {
+ "name": "b_Tail01_012",
+ "nodeName": "b_Tail01_012",
+ "nodePath": "0/0/0/0/0/1",
+ "interactionId": "interaction-653ea2d6",
+ "capabilities": {
+ "selectable": true,
+ "transform": true,
+ "visible": true,
+ "material": false,
+ "animatable": true
+ },
+ "position": [
+ 4.2603759765625,
+ 15.958770751953125,
+ 0
+ ],
+ "rotation": [
+ -5.364745216052604e-7,
+ 3.424797608447095e-7,
+ 1.9941304874941974
+ ],
+ "scale": [
+ 1,
+ 1,
+ 1
+ ],
+ "visible": true,
+ "customComponents": []
+ },
+ "model-imported-model_mto6v5w6_09-fox-16": {
+ "name": "b_Tail02_013",
+ "nodeName": "b_Tail02_013",
+ "nodePath": "0/0/0/0/0/1/0",
+ "interactionId": "interaction-6c4d3691",
+ "capabilities": {
+ "selectable": true,
+ "transform": true,
+ "visible": true,
+ "material": false,
+ "animatable": true
+ },
+ "position": [
+ 12.411918640136719,
+ 0,
+ 0
+ ],
+ "rotation": [
+ -6.738507958375962e-23,
+ -5.2439168243310865e-23,
+ 0.3128105211171357
+ ],
+ "scale": [
+ 1,
+ 1,
+ 1
+ ],
+ "visible": true,
+ "customComponents": []
+ },
+ "model-imported-model_mto6v5w6_09-fox-17": {
+ "name": "b_Tail03_014",
+ "nodeName": "b_Tail03_014",
+ "nodePath": "0/0/0/0/0/1/0/0",
+ "interactionId": "interaction-e93e191e",
+ "capabilities": {
+ "selectable": true,
+ "transform": true,
+ "visible": true,
+ "material": false,
+ "animatable": true
+ },
+ "position": [
+ 24.24032211303711,
+ 0,
+ 0
+ ],
+ "rotation": [
+ -5.7486339033124146e-24,
+ 1.1669178332583734e-22,
+ -0.012937892408322462
+ ],
+ "scale": [
+ 1,
+ 1,
+ 1
+ ],
+ "visible": true,
+ "customComponents": []
+ },
+ "model-imported-model_mto6v5w6_09-fox-18": {
+ "name": "b_LeftLeg01_015",
+ "nodeName": "b_LeftLeg01_015",
+ "nodePath": "0/0/0/0/0/2",
+ "interactionId": "interaction-643ea143",
+ "capabilities": {
+ "selectable": true,
+ "transform": true,
+ "visible": true,
+ "material": false,
+ "animatable": true
+ },
+ "position": [
+ 4.813770294189453,
+ 5.154018402099609,
+ -6.968006134033203
+ ],
+ "rotation": [
+ 0.1223493637902729,
+ -0.09724770930295147,
+ 2.9630295165414986
+ ],
+ "scale": [
+ 1,
+ 1,
+ 1
+ ],
+ "visible": true,
+ "customComponents": []
+ },
+ "model-imported-model_mto6v5w6_09-fox-19": {
+ "name": "b_LeftLeg02_016",
+ "nodeName": "b_LeftLeg02_016",
+ "nodePath": "0/0/0/0/0/2/0",
+ "interactionId": "interaction-7175f8dc",
+ "capabilities": {
+ "selectable": true,
+ "transform": true,
+ "visible": true,
+ "material": false,
+ "animatable": true
+ },
+ "position": [
+ 18.944175720214844,
+ 0,
+ 0
+ ],
+ "rotation": [
+ 1.2545483692289454e-8,
+ 3.728483680761731e-8,
+ -0.522293355530293
+ ],
+ "scale": [
+ 1,
+ 1,
+ 1
+ ],
+ "visible": true,
+ "customComponents": []
+ },
+ "model-imported-model_mto6v5w6_09-fox-20": {
+ "name": "b_LeftFoot01_017",
+ "nodeName": "b_LeftFoot01_017",
+ "nodePath": "0/0/0/0/0/2/0/0",
+ "interactionId": "interaction-f5ed353b",
+ "capabilities": {
+ "selectable": true,
+ "transform": true,
+ "visible": true,
+ "material": false,
+ "animatable": true
+ },
+ "position": [
+ 17.942811965942383,
+ 0,
+ 0
+ ],
+ "rotation": [
+ -0.03128474386729028,
+ -0.15471927051895154,
+ 0.7774909055508243
+ ],
+ "scale": [
+ 1,
+ 1,
+ 1
+ ],
+ "visible": true,
+ "customComponents": []
+ },
+ "model-imported-model_mto6v5w6_09-fox-21": {
+ "name": "b_LeftFoot02_018",
+ "nodeName": "b_LeftFoot02_018",
+ "nodePath": "0/0/0/0/0/2/0/0/0",
+ "interactionId": "interaction-1bbe01a4",
+ "capabilities": {
+ "selectable": true,
+ "transform": true,
+ "visible": true,
+ "material": false,
+ "animatable": true
+ },
+ "position": [
+ 15.779938697814941,
+ 0,
+ 0
+ ],
+ "rotation": [
+ 0,
+ 0,
+ 1.158241570830627
+ ],
+ "scale": [
+ 1,
+ 1,
+ 1
+ ],
+ "visible": true,
+ "customComponents": []
+ },
+ "model-imported-model_mto6v5w6_09-fox-22": {
+ "name": "b_RightLeg01_019",
+ "nodeName": "b_RightLeg01_019",
+ "nodePath": "0/0/0/0/0/3",
+ "interactionId": "interaction-633e9fb0",
+ "capabilities": {
+ "selectable": true,
+ "transform": true,
+ "visible": true,
+ "material": false,
+ "animatable": true
+ },
+ "position": [
+ 4.813777923583984,
+ 5.154026031494141,
+ 6.967563629150391
+ ],
+ "rotation": [
+ -0.10137208185544387,
+ 0.05534659522395753,
+ -2.3613690068554236
+ ],
+ "scale": [
+ 1,
+ 1,
+ 1
+ ],
+ "visible": true,
+ "customComponents": []
+ },
+ "model-imported-model_mto6v5w6_09-fox-23": {
+ "name": "b_RightLeg02_020",
+ "nodeName": "b_RightLeg02_020",
+ "nodePath": "0/0/0/0/0/3/0",
+ "interactionId": "interaction-95ff89c7",
+ "capabilities": {
+ "selectable": true,
+ "transform": true,
+ "visible": true,
+ "material": false,
+ "animatable": true
+ },
+ "position": [
+ 18.944183349609375,
+ 0,
+ 0
+ ],
+ "rotation": [
+ 2.7890192431350882e-8,
+ 1.4750479199499402e-8,
+ -1.0710323806538369
+ ],
+ "scale": [
+ 1,
+ 1,
+ 1
+ ],
+ "visible": true,
+ "customComponents": []
+ },
+ "model-imported-model_mto6v5w6_09-fox-24": {
+ "name": "b_RightFoot01_021",
+ "nodeName": "b_RightFoot01_021",
+ "nodePath": "0/0/0/0/0/3/0/0",
+ "interactionId": "interaction-a157d498",
+ "capabilities": {
+ "selectable": true,
+ "transform": true,
+ "visible": true,
+ "material": false,
+ "animatable": true
+ },
+ "position": [
+ 17.94281005859375,
+ 0,
+ 0
+ ],
+ "rotation": [
+ -0.14401413968163027,
+ 0.2814735368347316,
+ 1.4591240233677443
+ ],
+ "scale": [
+ 1,
+ 1,
+ 1
+ ],
+ "visible": true,
+ "customComponents": []
+ },
+ "model-imported-model_mto6v5w6_09-fox-25": {
+ "name": "b_RightFoot02_022",
+ "nodeName": "b_RightFoot02_022",
+ "nodePath": "0/0/0/0/0/3/0/0/0",
+ "interactionId": "interaction-ebd38e3f",
+ "capabilities": {
+ "selectable": true,
+ "transform": true,
+ "visible": true,
+ "material": false,
+ "animatable": true
+ },
+ "position": [
+ 15.779935836791992,
+ 0,
+ 0
+ ],
+ "rotation": [
+ 0,
+ 0,
+ 1.158241570830627
+ ],
+ "scale": [
+ 1,
+ 1,
+ 1
+ ],
+ "visible": true,
+ "customComponents": []
+ },
+ "model-imported-model_mto6v5w6_09-fox-26": {
+ "name": "闭环验收-旧版本需人工合并",
+ "nodeName": "fox",
+ "nodePath": "0/1",
+ "interactionId": "interaction-a3d35c26",
+ "capabilities": {
+ "selectable": true,
+ "transform": true,
+ "visible": true,
+ "material": true,
+ "animatable": true
+ },
+ "position": [
+ 0.25,
+ 0,
+ 0
+ ],
+ "rotation": [
+ 0,
+ 0,
+ 0
+ ],
+ "scale": [
+ 1,
+ 1,
+ 1
+ ],
+ "visible": true,
+ "customComponents": [],
+ "materials": [
+ {
+ "color": "ffffff",
+ "emissive": "000000",
+ "emissiveIntensity": 1,
+ "metalness": 0,
+ "roughness": 0.3,
+ "opacity": 1,
+ "transparent": false,
+ "depthWrite": true
+ }
+ ]
+ }
+ },
+ "selectionScope": "part",
+ "nativeAnimation": {
+ "clipIndex": 2,
+ "clipName": "Run"
+ }
+ },
+ "environment": {
+ "mode": "placeholder",
+ "environmentType": "outdoor",
+ "sourceName": "内置野外综合训练环境",
+ "sizeBytes": 0,
+ "assetCode": "",
+ "storageUri": "",
+ "resourceUrl": "",
+ "objects": {
+ "outdoor-scene": {
+ "name": "野外综合训练环境",
+ "position": [
+ 0,
+ 0,
+ 0
+ ],
+ "rotation": [
+ 0,
+ 0,
+ 0
+ ],
+ "scale": [
+ 1,
+ 1,
+ 1
+ ],
+ "visible": true,
+ "customComponents": []
+ },
+ "outdoor-site": {
+ "name": "场地与道路",
+ "position": [
+ 0,
+ 0,
+ 0
+ ],
+ "rotation": [
+ 0,
+ 0,
+ 0
+ ],
+ "scale": [
+ 1,
+ 1,
+ 1
+ ],
+ "visible": true,
+ "customComponents": []
+ },
+ "outdoor-ground": {
+ "name": "室外训练场地",
+ "position": [
+ 0,
+ -0.065,
+ 0
+ ],
+ "rotation": [
+ -1.5707963267948966,
+ 0,
+ 0
+ ],
+ "scale": [
+ 1,
+ 1,
+ 1
+ ],
+ "visible": true,
+ "customComponents": [],
+ "materials": [
+ {
+ "color": "263529",
+ "emissive": "000000",
+ "emissiveIntensity": 1,
+ "metalness": 0,
+ "roughness": 0.98,
+ "opacity": 1,
+ "transparent": false,
+ "depthWrite": true
+ }
+ ]
+ },
+ "outdoor-apron": {
+ "name": "装备操作坪",
+ "position": [
+ 0,
+ -0.045,
+ 0
+ ],
+ "rotation": [
+ -1.5707963267948966,
+ 0,
+ 0
+ ],
+ "scale": [
+ 1,
+ 1,
+ 1
+ ],
+ "visible": true,
+ "customComponents": [],
+ "materials": [
+ {
+ "color": "39444a",
+ "emissive": "000000",
+ "emissiveIntensity": 1,
+ "metalness": 0.03,
+ "roughness": 0.92,
+ "opacity": 1,
+ "transparent": false,
+ "depthWrite": true
+ }
+ ]
+ },
+ "outdoor-road": {
+ "name": "保障通道",
+ "position": [
+ 0,
+ -0.035,
+ 17
+ ],
+ "rotation": [
+ -1.5707963267948966,
+ 0,
+ 0
+ ],
+ "scale": [
+ 1,
+ 1,
+ 1
+ ],
+ "visible": true,
+ "customComponents": [],
+ "materials": [
+ {
+ "color": "202a31",
+ "emissive": "000000",
+ "emissiveIntensity": 1,
+ "metalness": 0.02,
+ "roughness": 0.95,
+ "opacity": 1,
+ "transparent": false,
+ "depthWrite": true
+ }
+ ]
+ },
+ "outdoor-road-line-1": {
+ "name": "道路中心线 1",
+ "position": [
+ -28.699999999999996,
+ 0.01,
+ 17
+ ],
+ "rotation": [
+ 0,
+ 0,
+ 0
+ ],
+ "scale": [
+ 1,
+ 1,
+ 1
+ ],
+ "visible": true,
+ "customComponents": [],
+ "materials": [
+ {
+ "color": "e5c64c",
+ "emissive": "302600",
+ "emissiveIntensity": 0.18,
+ "metalness": 0.08,
+ "roughness": 0.72,
+ "opacity": 1,
+ "transparent": false,
+ "depthWrite": true
+ }
+ ]
+ },
+ "outdoor-road-line-2": {
+ "name": "道路中心线 2",
+ "position": [
+ -24.599999999999998,
+ 0.01,
+ 17
+ ],
+ "rotation": [
+ 0,
+ 0,
+ 0
+ ],
+ "scale": [
+ 1,
+ 1,
+ 1
+ ],
+ "visible": true,
+ "customComponents": [],
+ "materials": [
+ {
+ "color": "e5c64c",
+ "emissive": "302600",
+ "emissiveIntensity": 0.18,
+ "metalness": 0.08,
+ "roughness": 0.72,
+ "opacity": 1,
+ "transparent": false,
+ "depthWrite": true
+ }
+ ]
+ },
+ "outdoor-road-line-3": {
+ "name": "道路中心线 3",
+ "position": [
+ -20.5,
+ 0.01,
+ 17
+ ],
+ "rotation": [
+ 0,
+ 0,
+ 0
+ ],
+ "scale": [
+ 1,
+ 1,
+ 1
+ ],
+ "visible": true,
+ "customComponents": [],
+ "materials": [
+ {
+ "color": "e5c64c",
+ "emissive": "302600",
+ "emissiveIntensity": 0.18,
+ "metalness": 0.08,
+ "roughness": 0.72,
+ "opacity": 1,
+ "transparent": false,
+ "depthWrite": true
+ }
+ ]
+ },
+ "outdoor-road-line-4": {
+ "name": "道路中心线 4",
+ "position": [
+ -16.4,
+ 0.01,
+ 17
+ ],
+ "rotation": [
+ 0,
+ 0,
+ 0
+ ],
+ "scale": [
+ 1,
+ 1,
+ 1
+ ],
+ "visible": true,
+ "customComponents": [],
+ "materials": [
+ {
+ "color": "e5c64c",
+ "emissive": "302600",
+ "emissiveIntensity": 0.18,
+ "metalness": 0.08,
+ "roughness": 0.72,
+ "opacity": 1,
+ "transparent": false,
+ "depthWrite": true
+ }
+ ]
+ },
+ "outdoor-road-line-5": {
+ "name": "道路中心线 5",
+ "position": [
+ -12.299999999999999,
+ 0.01,
+ 17
+ ],
+ "rotation": [
+ 0,
+ 0,
+ 0
+ ],
+ "scale": [
+ 1,
+ 1,
+ 1
+ ],
+ "visible": true,
+ "customComponents": [],
+ "materials": [
+ {
+ "color": "e5c64c",
+ "emissive": "302600",
+ "emissiveIntensity": 0.18,
+ "metalness": 0.08,
+ "roughness": 0.72,
+ "opacity": 1,
+ "transparent": false,
+ "depthWrite": true
+ }
+ ]
+ },
+ "outdoor-road-line-6": {
+ "name": "道路中心线 6",
+ "position": [
+ -8.2,
+ 0.01,
+ 17
+ ],
+ "rotation": [
+ 0,
+ 0,
+ 0
+ ],
+ "scale": [
+ 1,
+ 1,
+ 1
+ ],
+ "visible": true,
+ "customComponents": [],
+ "materials": [
+ {
+ "color": "e5c64c",
+ "emissive": "302600",
+ "emissiveIntensity": 0.18,
+ "metalness": 0.08,
+ "roughness": 0.72,
+ "opacity": 1,
+ "transparent": false,
+ "depthWrite": true
+ }
+ ]
+ },
+ "outdoor-road-line-7": {
+ "name": "道路中心线 7",
+ "position": [
+ -4.1,
+ 0.01,
+ 17
+ ],
+ "rotation": [
+ 0,
+ 0,
+ 0
+ ],
+ "scale": [
+ 1,
+ 1,
+ 1
+ ],
+ "visible": true,
+ "customComponents": [],
+ "materials": [
+ {
+ "color": "e5c64c",
+ "emissive": "302600",
+ "emissiveIntensity": 0.18,
+ "metalness": 0.08,
+ "roughness": 0.72,
+ "opacity": 1,
+ "transparent": false,
+ "depthWrite": true
+ }
+ ]
+ },
+ "outdoor-road-line-8": {
+ "name": "道路中心线 8",
+ "position": [
+ 0,
+ 0.01,
+ 17
+ ],
+ "rotation": [
+ 0,
+ 0,
+ 0
+ ],
+ "scale": [
+ 1,
+ 1,
+ 1
+ ],
+ "visible": true,
+ "customComponents": [],
+ "materials": [
+ {
+ "color": "e5c64c",
+ "emissive": "302600",
+ "emissiveIntensity": 0.18,
+ "metalness": 0.08,
+ "roughness": 0.72,
+ "opacity": 1,
+ "transparent": false,
+ "depthWrite": true
+ }
+ ]
+ },
+ "outdoor-road-line-9": {
+ "name": "道路中心线 9",
+ "position": [
+ 4.1,
+ 0.01,
+ 17
+ ],
+ "rotation": [
+ 0,
+ 0,
+ 0
+ ],
+ "scale": [
+ 1,
+ 1,
+ 1
+ ],
+ "visible": true,
+ "customComponents": [],
+ "materials": [
+ {
+ "color": "e5c64c",
+ "emissive": "302600",
+ "emissiveIntensity": 0.18,
+ "metalness": 0.08,
+ "roughness": 0.72,
+ "opacity": 1,
+ "transparent": false,
+ "depthWrite": true
+ }
+ ]
+ },
+ "outdoor-road-line-10": {
+ "name": "道路中心线 10",
+ "position": [
+ 8.2,
+ 0.01,
+ 17
+ ],
+ "rotation": [
+ 0,
+ 0,
+ 0
+ ],
+ "scale": [
+ 1,
+ 1,
+ 1
+ ],
+ "visible": true,
+ "customComponents": [],
+ "materials": [
+ {
+ "color": "e5c64c",
+ "emissive": "302600",
+ "emissiveIntensity": 0.18,
+ "metalness": 0.08,
+ "roughness": 0.72,
+ "opacity": 1,
+ "transparent": false,
+ "depthWrite": true
+ }
+ ]
+ },
+ "outdoor-road-line-11": {
+ "name": "道路中心线 11",
+ "position": [
+ 12.299999999999999,
+ 0.01,
+ 17
+ ],
+ "rotation": [
+ 0,
+ 0,
+ 0
+ ],
+ "scale": [
+ 1,
+ 1,
+ 1
+ ],
+ "visible": true,
+ "customComponents": [],
+ "materials": [
+ {
+ "color": "e5c64c",
+ "emissive": "302600",
+ "emissiveIntensity": 0.18,
+ "metalness": 0.08,
+ "roughness": 0.72,
+ "opacity": 1,
+ "transparent": false,
+ "depthWrite": true
+ }
+ ]
+ },
+ "outdoor-road-line-12": {
+ "name": "道路中心线 12",
+ "position": [
+ 16.4,
+ 0.01,
+ 17
+ ],
+ "rotation": [
+ 0,
+ 0,
+ 0
+ ],
+ "scale": [
+ 1,
+ 1,
+ 1
+ ],
+ "visible": true,
+ "customComponents": [],
+ "materials": [
+ {
+ "color": "e5c64c",
+ "emissive": "302600",
+ "emissiveIntensity": 0.18,
+ "metalness": 0.08,
+ "roughness": 0.72,
+ "opacity": 1,
+ "transparent": false,
+ "depthWrite": true
+ }
+ ]
+ },
+ "outdoor-road-line-13": {
+ "name": "道路中心线 13",
+ "position": [
+ 20.5,
+ 0.01,
+ 17
+ ],
+ "rotation": [
+ 0,
+ 0,
+ 0
+ ],
+ "scale": [
+ 1,
+ 1,
+ 1
+ ],
+ "visible": true,
+ "customComponents": [],
+ "materials": [
+ {
+ "color": "e5c64c",
+ "emissive": "302600",
+ "emissiveIntensity": 0.18,
+ "metalness": 0.08,
+ "roughness": 0.72,
+ "opacity": 1,
+ "transparent": false,
+ "depthWrite": true
+ }
+ ]
+ },
+ "outdoor-road-line-14": {
+ "name": "道路中心线 14",
+ "position": [
+ 24.599999999999998,
+ 0.01,
+ 17
+ ],
+ "rotation": [
+ 0,
+ 0,
+ 0
+ ],
+ "scale": [
+ 1,
+ 1,
+ 1
+ ],
+ "visible": true,
+ "customComponents": [],
+ "materials": [
+ {
+ "color": "e5c64c",
+ "emissive": "302600",
+ "emissiveIntensity": 0.18,
+ "metalness": 0.08,
+ "roughness": 0.72,
+ "opacity": 1,
+ "transparent": false,
+ "depthWrite": true
+ }
+ ]
+ },
+ "outdoor-road-line-15": {
+ "name": "道路中心线 15",
+ "position": [
+ 28.699999999999996,
+ 0.01,
+ 17
+ ],
+ "rotation": [
+ 0,
+ 0,
+ 0
+ ],
+ "scale": [
+ 1,
+ 1,
+ 1
+ ],
+ "visible": true,
+ "customComponents": [],
+ "materials": [
+ {
+ "color": "e5c64c",
+ "emissive": "302600",
+ "emissiveIntensity": 0.18,
+ "metalness": 0.08,
+ "roughness": 0.72,
+ "opacity": 1,
+ "transparent": false,
+ "depthWrite": true
+ }
+ ]
+ },
+ "outdoor-safety--17.6--11": {
+ "name": "操作区安全标线",
+ "position": [
+ -17.6,
+ 0.012,
+ -11
+ ],
+ "rotation": [
+ 0,
+ 0.5654866776461628,
+ 0
+ ],
+ "scale": [
+ 1,
+ 1,
+ 1
+ ],
+ "visible": true,
+ "customComponents": [],
+ "materials": [
+ {
+ "color": "e5c64c",
+ "emissive": "302600",
+ "emissiveIntensity": 0.18,
+ "metalness": 0.08,
+ "roughness": 0.72,
+ "opacity": 1,
+ "transparent": false,
+ "depthWrite": true
+ }
+ ]
+ },
+ "outdoor-safety--17.6--8.4": {
+ "name": "操作区安全标线",
+ "position": [
+ -17.6,
+ 0.012,
+ -8.4
+ ],
+ "rotation": [
+ 0,
+ 0.5654866776461628,
+ 0
+ ],
+ "scale": [
+ 1,
+ 1,
+ 1
+ ],
+ "visible": true,
+ "customComponents": [],
+ "materials": [
+ {
+ "color": "e5c64c",
+ "emissive": "302600",
+ "emissiveIntensity": 0.18,
+ "metalness": 0.08,
+ "roughness": 0.72,
+ "opacity": 1,
+ "transparent": false,
+ "depthWrite": true
+ }
+ ]
+ },
+ "outdoor-safety--17.6--5.800000000000001": {
+ "name": "操作区安全标线",
+ "position": [
+ -17.6,
+ 0.012,
+ -5.800000000000001
+ ],
+ "rotation": [
+ 0,
+ 0.5654866776461628,
+ 0
+ ],
+ "scale": [
+ 1,
+ 1,
+ 1
+ ],
+ "visible": true,
+ "customComponents": [],
+ "materials": [
+ {
+ "color": "e5c64c",
+ "emissive": "302600",
+ "emissiveIntensity": 0.18,
+ "metalness": 0.08,
+ "roughness": 0.72,
+ "opacity": 1,
+ "transparent": false,
+ "depthWrite": true
+ }
+ ]
+ },
+ "outdoor-safety--17.6--3.2000000000000006": {
+ "name": "操作区安全标线",
+ "position": [
+ -17.6,
+ 0.012,
+ -3.2000000000000006
+ ],
+ "rotation": [
+ 0,
+ 0.5654866776461628,
+ 0
+ ],
+ "scale": [
+ 1,
+ 1,
+ 1
+ ],
+ "visible": true,
+ "customComponents": [],
+ "materials": [
+ {
+ "color": "e5c64c",
+ "emissive": "302600",
+ "emissiveIntensity": 0.18,
+ "metalness": 0.08,
+ "roughness": 0.72,
+ "opacity": 1,
+ "transparent": false,
+ "depthWrite": true
+ }
+ ]
+ },
+ "outdoor-safety--17.6--0.6000000000000005": {
+ "name": "操作区安全标线",
+ "position": [
+ -17.6,
+ 0.012,
+ -0.6000000000000005
+ ],
+ "rotation": [
+ 0,
+ 0.5654866776461628,
+ 0
+ ],
+ "scale": [
+ 1,
+ 1,
+ 1
+ ],
+ "visible": true,
+ "customComponents": [],
+ "materials": [
+ {
+ "color": "e5c64c",
+ "emissive": "302600",
+ "emissiveIntensity": 0.18,
+ "metalness": 0.08,
+ "roughness": 0.72,
+ "opacity": 1,
+ "transparent": false,
+ "depthWrite": true
+ }
+ ]
+ },
+ "outdoor-safety--17.6-1.9999999999999996": {
+ "name": "操作区安全标线",
+ "position": [
+ -17.6,
+ 0.012,
+ 1.9999999999999996
+ ],
+ "rotation": [
+ 0,
+ 0.5654866776461628,
+ 0
+ ],
+ "scale": [
+ 1,
+ 1,
+ 1
+ ],
+ "visible": true,
+ "customComponents": [],
+ "materials": [
+ {
+ "color": "e5c64c",
+ "emissive": "302600",
+ "emissiveIntensity": 0.18,
+ "metalness": 0.08,
+ "roughness": 0.72,
+ "opacity": 1,
+ "transparent": false,
+ "depthWrite": true
+ }
+ ]
+ },
+ "outdoor-safety--17.6-4.6": {
+ "name": "操作区安全标线",
+ "position": [
+ -17.6,
+ 0.012,
+ 4.6
+ ],
+ "rotation": [
+ 0,
+ 0.5654866776461628,
+ 0
+ ],
+ "scale": [
+ 1,
+ 1,
+ 1
+ ],
+ "visible": true,
+ "customComponents": [],
+ "materials": [
+ {
+ "color": "e5c64c",
+ "emissive": "302600",
+ "emissiveIntensity": 0.18,
+ "metalness": 0.08,
+ "roughness": 0.72,
+ "opacity": 1,
+ "transparent": false,
+ "depthWrite": true
+ }
+ ]
+ },
+ "outdoor-safety--17.6-7.199999999999999": {
+ "name": "操作区安全标线",
+ "position": [
+ -17.6,
+ 0.012,
+ 7.199999999999999
+ ],
+ "rotation": [
+ 0,
+ 0.5654866776461628,
+ 0
+ ],
+ "scale": [
+ 1,
+ 1,
+ 1
+ ],
+ "visible": true,
+ "customComponents": [],
+ "materials": [
+ {
+ "color": "e5c64c",
+ "emissive": "302600",
+ "emissiveIntensity": 0.18,
+ "metalness": 0.08,
+ "roughness": 0.72,
+ "opacity": 1,
+ "transparent": false,
+ "depthWrite": true
+ }
+ ]
+ },
+ "outdoor-safety--17.6-9.799999999999999": {
+ "name": "操作区安全标线",
+ "position": [
+ -17.6,
+ 0.012,
+ 9.799999999999999
+ ],
+ "rotation": [
+ 0,
+ 0.5654866776461628,
+ 0
+ ],
+ "scale": [
+ 1,
+ 1,
+ 1
+ ],
+ "visible": true,
+ "customComponents": [],
+ "materials": [
+ {
+ "color": "e5c64c",
+ "emissive": "302600",
+ "emissiveIntensity": 0.18,
+ "metalness": 0.08,
+ "roughness": 0.72,
+ "opacity": 1,
+ "transparent": false,
+ "depthWrite": true
+ }
+ ]
+ },
+ "outdoor-safety-17.6--11": {
+ "name": "操作区安全标线",
+ "position": [
+ 17.6,
+ 0.012,
+ -11
+ ],
+ "rotation": [
+ 0,
+ 0.5654866776461628,
+ 0
+ ],
+ "scale": [
+ 1,
+ 1,
+ 1
+ ],
+ "visible": true,
+ "customComponents": [],
+ "materials": [
+ {
+ "color": "e5c64c",
+ "emissive": "302600",
+ "emissiveIntensity": 0.18,
+ "metalness": 0.08,
+ "roughness": 0.72,
+ "opacity": 1,
+ "transparent": false,
+ "depthWrite": true
+ }
+ ]
+ },
+ "outdoor-safety-17.6--8.4": {
+ "name": "操作区安全标线",
+ "position": [
+ 17.6,
+ 0.012,
+ -8.4
+ ],
+ "rotation": [
+ 0,
+ 0.5654866776461628,
+ 0
+ ],
+ "scale": [
+ 1,
+ 1,
+ 1
+ ],
+ "visible": true,
+ "customComponents": [],
+ "materials": [
+ {
+ "color": "e5c64c",
+ "emissive": "302600",
+ "emissiveIntensity": 0.18,
+ "metalness": 0.08,
+ "roughness": 0.72,
+ "opacity": 1,
+ "transparent": false,
+ "depthWrite": true
+ }
+ ]
+ },
+ "outdoor-safety-17.6--5.800000000000001": {
+ "name": "操作区安全标线",
+ "position": [
+ 17.6,
+ 0.012,
+ -5.800000000000001
+ ],
+ "rotation": [
+ 0,
+ 0.5654866776461628,
+ 0
+ ],
+ "scale": [
+ 1,
+ 1,
+ 1
+ ],
+ "visible": true,
+ "customComponents": [],
+ "materials": [
+ {
+ "color": "e5c64c",
+ "emissive": "302600",
+ "emissiveIntensity": 0.18,
+ "metalness": 0.08,
+ "roughness": 0.72,
+ "opacity": 1,
+ "transparent": false,
+ "depthWrite": true
+ }
+ ]
+ },
+ "outdoor-safety-17.6--3.2000000000000006": {
+ "name": "操作区安全标线",
+ "position": [
+ 17.6,
+ 0.012,
+ -3.2000000000000006
+ ],
+ "rotation": [
+ 0,
+ 0.5654866776461628,
+ 0
+ ],
+ "scale": [
+ 1,
+ 1,
+ 1
+ ],
+ "visible": true,
+ "customComponents": [],
+ "materials": [
+ {
+ "color": "e5c64c",
+ "emissive": "302600",
+ "emissiveIntensity": 0.18,
+ "metalness": 0.08,
+ "roughness": 0.72,
+ "opacity": 1,
+ "transparent": false,
+ "depthWrite": true
+ }
+ ]
+ },
+ "outdoor-safety-17.6--0.6000000000000005": {
+ "name": "操作区安全标线",
+ "position": [
+ 17.6,
+ 0.012,
+ -0.6000000000000005
+ ],
+ "rotation": [
+ 0,
+ 0.5654866776461628,
+ 0
+ ],
+ "scale": [
+ 1,
+ 1,
+ 1
+ ],
+ "visible": true,
+ "customComponents": [],
+ "materials": [
+ {
+ "color": "e5c64c",
+ "emissive": "302600",
+ "emissiveIntensity": 0.18,
+ "metalness": 0.08,
+ "roughness": 0.72,
+ "opacity": 1,
+ "transparent": false,
+ "depthWrite": true
+ }
+ ]
+ },
+ "outdoor-safety-17.6-1.9999999999999996": {
+ "name": "操作区安全标线",
+ "position": [
+ 17.6,
+ 0.012,
+ 1.9999999999999996
+ ],
+ "rotation": [
+ 0,
+ 0.5654866776461628,
+ 0
+ ],
+ "scale": [
+ 1,
+ 1,
+ 1
+ ],
+ "visible": true,
+ "customComponents": [],
+ "materials": [
+ {
+ "color": "e5c64c",
+ "emissive": "302600",
+ "emissiveIntensity": 0.18,
+ "metalness": 0.08,
+ "roughness": 0.72,
+ "opacity": 1,
+ "transparent": false,
+ "depthWrite": true
+ }
+ ]
+ },
+ "outdoor-safety-17.6-4.6": {
+ "name": "操作区安全标线",
+ "position": [
+ 17.6,
+ 0.012,
+ 4.6
+ ],
+ "rotation": [
+ 0,
+ 0.5654866776461628,
+ 0
+ ],
+ "scale": [
+ 1,
+ 1,
+ 1
+ ],
+ "visible": true,
+ "customComponents": [],
+ "materials": [
+ {
+ "color": "e5c64c",
+ "emissive": "302600",
+ "emissiveIntensity": 0.18,
+ "metalness": 0.08,
+ "roughness": 0.72,
+ "opacity": 1,
+ "transparent": false,
+ "depthWrite": true
+ }
+ ]
+ },
+ "outdoor-safety-17.6-7.199999999999999": {
+ "name": "操作区安全标线",
+ "position": [
+ 17.6,
+ 0.012,
+ 7.199999999999999
+ ],
+ "rotation": [
+ 0,
+ 0.5654866776461628,
+ 0
+ ],
+ "scale": [
+ 1,
+ 1,
+ 1
+ ],
+ "visible": true,
+ "customComponents": [],
+ "materials": [
+ {
+ "color": "e5c64c",
+ "emissive": "302600",
+ "emissiveIntensity": 0.18,
+ "metalness": 0.08,
+ "roughness": 0.72,
+ "opacity": 1,
+ "transparent": false,
+ "depthWrite": true
+ }
+ ]
+ },
+ "outdoor-safety-17.6-9.799999999999999": {
+ "name": "操作区安全标线",
+ "position": [
+ 17.6,
+ 0.012,
+ 9.799999999999999
+ ],
+ "rotation": [
+ 0,
+ 0.5654866776461628,
+ 0
+ ],
+ "scale": [
+ 1,
+ 1,
+ 1
+ ],
+ "visible": true,
+ "customComponents": [],
+ "materials": [
+ {
+ "color": "e5c64c",
+ "emissive": "302600",
+ "emissiveIntensity": 0.18,
+ "metalness": 0.08,
+ "roughness": 0.72,
+ "opacity": 1,
+ "transparent": false,
+ "depthWrite": true
+ }
+ ]
+ },
+ "outdoor-workshop": {
+ "name": "维修保障厂房",
+ "position": [
+ 0,
+ 0,
+ 0
+ ],
+ "rotation": [
+ 0,
+ 0,
+ 0
+ ],
+ "scale": [
+ 1,
+ 1,
+ 1
+ ],
+ "visible": true,
+ "customComponents": []
+ },
+ "outdoor-workshop-body": {
+ "name": "维修保障厂房主体",
+ "position": [
+ -10,
+ 3.25,
+ -18
+ ],
+ "rotation": [
+ 0,
+ 0,
+ 0
+ ],
+ "scale": [
+ 1,
+ 1,
+ 1
+ ],
+ "visible": true,
+ "customComponents": [],
+ "materials": [
+ {
+ "color": "536675",
+ "emissive": "000000",
+ "emissiveIntensity": 1,
+ "metalness": 0.18,
+ "roughness": 0.78,
+ "opacity": 1,
+ "transparent": false,
+ "depthWrite": true
+ }
+ ]
+ },
+ "outdoor-workshop-roof": {
+ "name": "厂房金属屋面",
+ "position": [
+ -10,
+ 6.65,
+ -18
+ ],
+ "rotation": [
+ 0,
+ 0,
+ 0
+ ],
+ "scale": [
+ 1,
+ 1,
+ 1
+ ],
+ "visible": true,
+ "customComponents": [],
+ "materials": [
+ {
+ "color": "263846",
+ "emissive": "000000",
+ "emissiveIntensity": 1,
+ "metalness": 0.35,
+ "roughness": 0.62,
+ "opacity": 1,
+ "transparent": false,
+ "depthWrite": true
+ }
+ ]
+ },
+ "outdoor-workshop-window-1": {
+ "name": "厂房观察窗 1",
+ "position": [
+ -17,
+ 3.7,
+ -14.21
+ ],
+ "rotation": [
+ 0,
+ 0,
+ 0
+ ],
+ "scale": [
+ 1,
+ 1,
+ 1
+ ],
+ "visible": true,
+ "customComponents": [],
+ "materials": [
+ {
+ "color": "4a89a2",
+ "emissive": "000000",
+ "emissiveIntensity": 1,
+ "metalness": 0.12,
+ "roughness": 0.25,
+ "opacity": 0.68,
+ "transparent": true,
+ "depthWrite": true
+ }
+ ]
+ },
+ "outdoor-workshop-window-2": {
+ "name": "厂房观察窗 2",
+ "position": [
+ -12.4,
+ 3.7,
+ -14.21
+ ],
+ "rotation": [
+ 0,
+ 0,
+ 0
+ ],
+ "scale": [
+ 1,
+ 1,
+ 1
+ ],
+ "visible": true,
+ "customComponents": [],
+ "materials": [
+ {
+ "color": "4a89a2",
+ "emissive": "000000",
+ "emissiveIntensity": 1,
+ "metalness": 0.12,
+ "roughness": 0.25,
+ "opacity": 0.68,
+ "transparent": true,
+ "depthWrite": true
+ }
+ ]
+ },
+ "outdoor-workshop-window-3": {
+ "name": "厂房观察窗 3",
+ "position": [
+ -7.800000000000001,
+ 3.7,
+ -14.21
+ ],
+ "rotation": [
+ 0,
+ 0,
+ 0
+ ],
+ "scale": [
+ 1,
+ 1,
+ 1
+ ],
+ "visible": true,
+ "customComponents": [],
+ "materials": [
+ {
+ "color": "4a89a2",
+ "emissive": "000000",
+ "emissiveIntensity": 1,
+ "metalness": 0.12,
+ "roughness": 0.25,
+ "opacity": 0.68,
+ "transparent": true,
+ "depthWrite": true
+ }
+ ]
+ },
+ "outdoor-workshop-window-4": {
+ "name": "厂房观察窗 4",
+ "position": [
+ -3.200000000000001,
+ 3.7,
+ -14.21
+ ],
+ "rotation": [
+ 0,
+ 0,
+ 0
+ ],
+ "scale": [
+ 1,
+ 1,
+ 1
+ ],
+ "visible": true,
+ "customComponents": [],
+ "materials": [
+ {
+ "color": "4a89a2",
+ "emissive": "000000",
+ "emissiveIntensity": 1,
+ "metalness": 0.12,
+ "roughness": 0.25,
+ "opacity": 0.68,
+ "transparent": true,
+ "depthWrite": true
+ }
+ ]
+ },
+ "outdoor-workshop-door": {
+ "name": "装备出入门",
+ "position": [
+ -3.6,
+ 2.16,
+ -14.18
+ ],
+ "rotation": [
+ 0,
+ 0,
+ 0
+ ],
+ "scale": [
+ 1,
+ 1,
+ 1
+ ],
+ "visible": true,
+ "customComponents": [],
+ "materials": [
+ {
+ "color": "202a31",
+ "emissive": "000000",
+ "emissiveIntensity": 1,
+ "metalness": 0.02,
+ "roughness": 0.95,
+ "opacity": 1,
+ "transparent": false,
+ "depthWrite": true
+ }
+ ]
+ },
+ "outdoor-canopy": {
+ "name": "室外检修棚",
+ "position": [
+ 0,
+ 0,
+ 0
+ ],
+ "rotation": [
+ 0,
+ 0,
+ 0
+ ],
+ "scale": [
+ 1,
+ 1,
+ 1
+ ],
+ "visible": true,
+ "customComponents": []
+ },
+ "outdoor-canopy-roof": {
+ "name": "检修棚顶",
+ "position": [
+ 18,
+ 5.05,
+ -8
+ ],
+ "rotation": [
+ 0,
+ 0,
+ 0
+ ],
+ "scale": [
+ 1,
+ 1,
+ 1
+ ],
+ "visible": true,
+ "customComponents": [],
+ "materials": [
+ {
+ "color": "263846",
+ "emissive": "000000",
+ "emissiveIntensity": 1,
+ "metalness": 0.35,
+ "roughness": 0.62,
+ "opacity": 1,
+ "transparent": false,
+ "depthWrite": true
+ }
+ ]
+ },
+ "outdoor-canopy-column-1": {
+ "name": "检修棚立柱 1",
+ "position": [
+ 12.7,
+ 2.5,
+ -11
+ ],
+ "rotation": [
+ 0,
+ 0,
+ 0
+ ],
+ "scale": [
+ 1,
+ 1,
+ 1
+ ],
+ "visible": true,
+ "customComponents": [],
+ "materials": [
+ {
+ "color": "66757c",
+ "emissive": "000000",
+ "emissiveIntensity": 1,
+ "metalness": 0.48,
+ "roughness": 0.56,
+ "opacity": 1,
+ "transparent": false,
+ "depthWrite": true
+ }
+ ]
+ },
+ "outdoor-canopy-column-2": {
+ "name": "检修棚立柱 2",
+ "position": [
+ 23.3,
+ 2.5,
+ -11
+ ],
+ "rotation": [
+ 0,
+ 0,
+ 0
+ ],
+ "scale": [
+ 1,
+ 1,
+ 1
+ ],
+ "visible": true,
+ "customComponents": [],
+ "materials": [
+ {
+ "color": "66757c",
+ "emissive": "000000",
+ "emissiveIntensity": 1,
+ "metalness": 0.48,
+ "roughness": 0.56,
+ "opacity": 1,
+ "transparent": false,
+ "depthWrite": true
+ }
+ ]
+ },
+ "outdoor-canopy-column-3": {
+ "name": "检修棚立柱 3",
+ "position": [
+ 12.7,
+ 2.5,
+ -5
+ ],
+ "rotation": [
+ 0,
+ 0,
+ 0
+ ],
+ "scale": [
+ 1,
+ 1,
+ 1
+ ],
+ "visible": true,
+ "customComponents": [],
+ "materials": [
+ {
+ "color": "66757c",
+ "emissive": "000000",
+ "emissiveIntensity": 1,
+ "metalness": 0.48,
+ "roughness": 0.56,
+ "opacity": 1,
+ "transparent": false,
+ "depthWrite": true
+ }
+ ]
+ },
+ "outdoor-canopy-column-4": {
+ "name": "检修棚立柱 4",
+ "position": [
+ 23.3,
+ 2.5,
+ -5
+ ],
+ "rotation": [
+ 0,
+ 0,
+ 0
+ ],
+ "scale": [
+ 1,
+ 1,
+ 1
+ ],
+ "visible": true,
+ "customComponents": [],
+ "materials": [
+ {
+ "color": "66757c",
+ "emissive": "000000",
+ "emissiveIntensity": 1,
+ "metalness": 0.48,
+ "roughness": 0.56,
+ "opacity": 1,
+ "transparent": false,
+ "depthWrite": true
+ }
+ ]
+ },
+ "outdoor-service-box": {
+ "name": "室外工具柜",
+ "position": [
+ 22.2,
+ 1.05,
+ -9.8
+ ],
+ "rotation": [
+ 0,
+ 0,
+ 0
+ ],
+ "scale": [
+ 1,
+ 1,
+ 1
+ ],
+ "visible": true,
+ "customComponents": [],
+ "materials": [
+ {
+ "color": "d98d2b",
+ "emissive": "000000",
+ "emissiveIntensity": 1,
+ "metalness": 0.12,
+ "roughness": 0.7,
+ "opacity": 1,
+ "transparent": false,
+ "depthWrite": true
+ }
+ ]
+ },
+ "outdoor-perimeter": {
+ "name": "场地边界与安全设施",
+ "position": [
+ 0,
+ 0,
+ 0
+ ],
+ "rotation": [
+ 0,
+ 0,
+ 0
+ ],
+ "scale": [
+ 1,
+ 1,
+ 1
+ ],
+ "visible": true,
+ "customComponents": []
+ },
+ "outdoor-fence-back-1": {
+ "name": "后侧围栏立柱 1",
+ "position": [
+ -29,
+ 1.1,
+ -25
+ ],
+ "rotation": [
+ 0,
+ 0,
+ 0
+ ],
+ "scale": [
+ 1,
+ 1,
+ 1
+ ],
+ "visible": true,
+ "customComponents": [],
+ "materials": [
+ {
+ "color": "66757c",
+ "emissive": "000000",
+ "emissiveIntensity": 1,
+ "metalness": 0.48,
+ "roughness": 0.56,
+ "opacity": 1,
+ "transparent": false,
+ "depthWrite": true
+ }
+ ]
+ },
+ "outdoor-fence-back-2": {
+ "name": "后侧围栏立柱 2",
+ "position": [
+ -23.2,
+ 1.1,
+ -25
+ ],
+ "rotation": [
+ 0,
+ 0,
+ 0
+ ],
+ "scale": [
+ 1,
+ 1,
+ 1
+ ],
+ "visible": true,
+ "customComponents": [],
+ "materials": [
+ {
+ "color": "66757c",
+ "emissive": "000000",
+ "emissiveIntensity": 1,
+ "metalness": 0.48,
+ "roughness": 0.56,
+ "opacity": 1,
+ "transparent": false,
+ "depthWrite": true
+ }
+ ]
+ },
+ "outdoor-fence-back-3": {
+ "name": "后侧围栏立柱 3",
+ "position": [
+ -17.4,
+ 1.1,
+ -25
+ ],
+ "rotation": [
+ 0,
+ 0,
+ 0
+ ],
+ "scale": [
+ 1,
+ 1,
+ 1
+ ],
+ "visible": true,
+ "customComponents": [],
+ "materials": [
+ {
+ "color": "66757c",
+ "emissive": "000000",
+ "emissiveIntensity": 1,
+ "metalness": 0.48,
+ "roughness": 0.56,
+ "opacity": 1,
+ "transparent": false,
+ "depthWrite": true
+ }
+ ]
+ },
+ "outdoor-fence-back-4": {
+ "name": "后侧围栏立柱 4",
+ "position": [
+ -11.6,
+ 1.1,
+ -25
+ ],
+ "rotation": [
+ 0,
+ 0,
+ 0
+ ],
+ "scale": [
+ 1,
+ 1,
+ 1
+ ],
+ "visible": true,
+ "customComponents": [],
+ "materials": [
+ {
+ "color": "66757c",
+ "emissive": "000000",
+ "emissiveIntensity": 1,
+ "metalness": 0.48,
+ "roughness": 0.56,
+ "opacity": 1,
+ "transparent": false,
+ "depthWrite": true
+ }
+ ]
+ },
+ "outdoor-fence-back-5": {
+ "name": "后侧围栏立柱 5",
+ "position": [
+ -5.8,
+ 1.1,
+ -25
+ ],
+ "rotation": [
+ 0,
+ 0,
+ 0
+ ],
+ "scale": [
+ 1,
+ 1,
+ 1
+ ],
+ "visible": true,
+ "customComponents": [],
+ "materials": [
+ {
+ "color": "66757c",
+ "emissive": "000000",
+ "emissiveIntensity": 1,
+ "metalness": 0.48,
+ "roughness": 0.56,
+ "opacity": 1,
+ "transparent": false,
+ "depthWrite": true
+ }
+ ]
+ },
+ "outdoor-fence-back-6": {
+ "name": "后侧围栏立柱 6",
+ "position": [
+ 0,
+ 1.1,
+ -25
+ ],
+ "rotation": [
+ 0,
+ 0,
+ 0
+ ],
+ "scale": [
+ 1,
+ 1,
+ 1
+ ],
+ "visible": true,
+ "customComponents": [],
+ "materials": [
+ {
+ "color": "66757c",
+ "emissive": "000000",
+ "emissiveIntensity": 1,
+ "metalness": 0.48,
+ "roughness": 0.56,
+ "opacity": 1,
+ "transparent": false,
+ "depthWrite": true
+ }
+ ]
+ },
+ "outdoor-fence-back-7": {
+ "name": "后侧围栏立柱 7",
+ "position": [
+ 5.8,
+ 1.1,
+ -25
+ ],
+ "rotation": [
+ 0,
+ 0,
+ 0
+ ],
+ "scale": [
+ 1,
+ 1,
+ 1
+ ],
+ "visible": true,
+ "customComponents": [],
+ "materials": [
+ {
+ "color": "66757c",
+ "emissive": "000000",
+ "emissiveIntensity": 1,
+ "metalness": 0.48,
+ "roughness": 0.56,
+ "opacity": 1,
+ "transparent": false,
+ "depthWrite": true
+ }
+ ]
+ },
+ "outdoor-fence-back-8": {
+ "name": "后侧围栏立柱 8",
+ "position": [
+ 11.6,
+ 1.1,
+ -25
+ ],
+ "rotation": [
+ 0,
+ 0,
+ 0
+ ],
+ "scale": [
+ 1,
+ 1,
+ 1
+ ],
+ "visible": true,
+ "customComponents": [],
+ "materials": [
+ {
+ "color": "66757c",
+ "emissive": "000000",
+ "emissiveIntensity": 1,
+ "metalness": 0.48,
+ "roughness": 0.56,
+ "opacity": 1,
+ "transparent": false,
+ "depthWrite": true
+ }
+ ]
+ },
+ "outdoor-fence-back-9": {
+ "name": "后侧围栏立柱 9",
+ "position": [
+ 17.4,
+ 1.1,
+ -25
+ ],
+ "rotation": [
+ 0,
+ 0,
+ 0
+ ],
+ "scale": [
+ 1,
+ 1,
+ 1
+ ],
+ "visible": true,
+ "customComponents": [],
+ "materials": [
+ {
+ "color": "66757c",
+ "emissive": "000000",
+ "emissiveIntensity": 1,
+ "metalness": 0.48,
+ "roughness": 0.56,
+ "opacity": 1,
+ "transparent": false,
+ "depthWrite": true
+ }
+ ]
+ },
+ "outdoor-fence-back-10": {
+ "name": "后侧围栏立柱 10",
+ "position": [
+ 23.2,
+ 1.1,
+ -25
+ ],
+ "rotation": [
+ 0,
+ 0,
+ 0
+ ],
+ "scale": [
+ 1,
+ 1,
+ 1
+ ],
+ "visible": true,
+ "customComponents": [],
+ "materials": [
+ {
+ "color": "66757c",
+ "emissive": "000000",
+ "emissiveIntensity": 1,
+ "metalness": 0.48,
+ "roughness": 0.56,
+ "opacity": 1,
+ "transparent": false,
+ "depthWrite": true
+ }
+ ]
+ },
+ "outdoor-fence-back-11": {
+ "name": "后侧围栏立柱 11",
+ "position": [
+ 29,
+ 1.1,
+ -25
+ ],
+ "rotation": [
+ 0,
+ 0,
+ 0
+ ],
+ "scale": [
+ 1,
+ 1,
+ 1
+ ],
+ "visible": true,
+ "customComponents": [],
+ "materials": [
+ {
+ "color": "66757c",
+ "emissive": "000000",
+ "emissiveIntensity": 1,
+ "metalness": 0.48,
+ "roughness": 0.56,
+ "opacity": 1,
+ "transparent": false,
+ "depthWrite": true
+ }
+ ]
+ },
+ "outdoor-fence-rail-a": {
+ "name": "后侧围栏上横杆",
+ "position": [
+ 0,
+ 1.85,
+ -25
+ ],
+ "rotation": [
+ 0,
+ 0,
+ 0
+ ],
+ "scale": [
+ 1,
+ 1,
+ 1
+ ],
+ "visible": true,
+ "customComponents": [],
+ "materials": [
+ {
+ "color": "66757c",
+ "emissive": "000000",
+ "emissiveIntensity": 1,
+ "metalness": 0.48,
+ "roughness": 0.56,
+ "opacity": 1,
+ "transparent": false,
+ "depthWrite": true
+ }
+ ]
+ },
+ "outdoor-fence-rail-b": {
+ "name": "后侧围栏下横杆",
+ "position": [
+ 0,
+ 0.75,
+ -25
+ ],
+ "rotation": [
+ 0,
+ 0,
+ 0
+ ],
+ "scale": [
+ 1,
+ 1,
+ 1
+ ],
+ "visible": true,
+ "customComponents": [],
+ "materials": [
+ {
+ "color": "66757c",
+ "emissive": "000000",
+ "emissiveIntensity": 1,
+ "metalness": 0.48,
+ "roughness": 0.56,
+ "opacity": 1,
+ "transparent": false,
+ "depthWrite": true
+ }
+ ]
+ },
+ "outdoor-scene-sign": {
+ "name": "室外实训区域标识",
+ "position": [
+ 26,
+ 2.4,
+ 11.5
+ ],
+ "rotation": [
+ 0,
+ 0,
+ 0
+ ],
+ "scale": [
+ 1,
+ 1,
+ 1
+ ],
+ "visible": true,
+ "customComponents": [],
+ "materials": [
+ {
+ "color": "536675",
+ "emissive": "000000",
+ "emissiveIntensity": 1,
+ "metalness": 0.18,
+ "roughness": 0.78,
+ "opacity": 1,
+ "transparent": false,
+ "depthWrite": true
+ }
+ ]
+ },
+ "outdoor-scene-sign-post-a": {
+ "name": "区域标识支柱 A",
+ "position": [
+ 24,
+ 1.4,
+ 11.5
+ ],
+ "rotation": [
+ 0,
+ 0,
+ 0
+ ],
+ "scale": [
+ 1,
+ 1,
+ 1
+ ],
+ "visible": true,
+ "customComponents": [],
+ "materials": [
+ {
+ "color": "66757c",
+ "emissive": "000000",
+ "emissiveIntensity": 1,
+ "metalness": 0.48,
+ "roughness": 0.56,
+ "opacity": 1,
+ "transparent": false,
+ "depthWrite": true
+ }
+ ]
+ },
+ "outdoor-scene-sign-post-b": {
+ "name": "区域标识支柱 B",
+ "position": [
+ 28,
+ 1.4,
+ 11.5
+ ],
+ "rotation": [
+ 0,
+ 0,
+ 0
+ ],
+ "scale": [
+ 1,
+ 1,
+ 1
+ ],
+ "visible": true,
+ "customComponents": [],
+ "materials": [
+ {
+ "color": "66757c",
+ "emissive": "000000",
+ "emissiveIntensity": 1,
+ "metalness": 0.48,
+ "roughness": 0.56,
+ "opacity": 1,
+ "transparent": false,
+ "depthWrite": true
+ }
+ ]
+ }
+ }
+ },
+ "runtime": {
+ "startMode": "guided",
+ "showStepPanel": true,
+ "showProgress": true,
+ "allowReplay": true,
+ "language": "zh-CN"
+ },
+ "publish": {
+ "target": "standalone-html",
+ "deployment": "offline",
+ "includeProjectData": true,
+ "includeViewportPreview": true
+ }
+ }
+}
\ No newline at end of file
diff --git a/reports/model-editor-closure-20260906/downloads/recovery-238-isolated.json b/reports/model-editor-closure-20260906/downloads/recovery-238-isolated.json
new file mode 100644
index 0000000..5694af7
--- /dev/null
+++ b/reports/model-editor-closure-20260906/downloads/recovery-238-isolated.json
@@ -0,0 +1,3422 @@
+{
+ "schema": "unreal-tran.model-api-recovery/v1",
+ "snapshotId": "b9f82f0c-e635-4bba-8c75-583e33d211af",
+ "projectId": "238",
+ "projectVersion": 2,
+ "savedAt": "2026-09-06T08:24:04.316Z",
+ "ownerPageNonce": "19090ca8-db9b-4717-a10a-c193e41cdbda",
+ "document": {
+ "schema": "digital-twin-editor-project",
+ "version": 4,
+ "name": "闭环验收-恢复副本-mtpjp3nr",
+ "modelId": "imported-model_mto6v5w6_09-fox",
+ "modelResource": {
+ "kind": "imported",
+ "id": "imported-model_mto6v5w6_09-fox",
+ "name": "09-Fox",
+ "assetCode": "MODEL_MTO6V5W6_09-FOX",
+ "code": "MODEL_MTO6V5W6_09-FOX",
+ "storageUri": "fsvc://unreal-tran/model/2026/09/05/3fc69ae0d435f869afca1fcd943486d1.glb",
+ "assetKey": "",
+ "resourceUrl": "",
+ "sourceName": "09-Fox.glb",
+ "sizeBytes": 162852,
+ "lastModified": 0,
+ "mimeType": "model/gltf-binary",
+ "thumbnail": "",
+ "vertices": 1728,
+ "triangles": 576,
+ "nodes": 27,
+ "meshes": 1,
+ "materials": 1,
+ "textures": 1,
+ "animations": 3,
+ "animationDurationSeconds": 3.4166667461395264,
+ "modelMetadata": {
+ "nodeCount": 27,
+ "meshCount": 1,
+ "materialCount": 1,
+ "textureCount": 1,
+ "animations": [
+ {
+ "index": 0,
+ "name": "Survey",
+ "start": 0,
+ "end": 3.4166667461395264,
+ "duration": 3.4166667461395264,
+ "trackCount": 21
+ },
+ {
+ "index": 1,
+ "name": "Walk",
+ "start": 0,
+ "end": 0.7083333134651184,
+ "duration": 0.7083333134651184,
+ "trackCount": 21
+ },
+ {
+ "index": 2,
+ "name": "Run",
+ "start": 0,
+ "end": 1.1583333015441895,
+ "duration": 1.1583333015441895,
+ "trackCount": 21
+ }
+ ],
+ "animationDurationSeconds": 3.4166667461395264
+ },
+ "nativeAnimations": [
+ {
+ "index": 0,
+ "name": "Survey",
+ "start": 0,
+ "end": 3.4166667461395264,
+ "duration": 3.4166667461395264,
+ "trackCount": 21
+ },
+ {
+ "index": 1,
+ "name": "Walk",
+ "start": 0,
+ "end": 0.7083333134651184,
+ "duration": 0.7083333134651184,
+ "trackCount": 21
+ },
+ {
+ "index": 2,
+ "name": "Run",
+ "start": 0,
+ "end": 1.1583333015441895,
+ "duration": 1.1583333015441895,
+ "trackCount": 21
+ }
+ ]
+ },
+ "meta": {
+ "description": "用于装备维修实训场景编排、编辑器回归与离线发布验证的系统内置基准工程",
+ "author": "数字孪生项目组",
+ "scene": "车间与野外装备维修实训环境",
+ "tags": [
+ "三维场景",
+ "时间轴动画",
+ "蓝图交互",
+ "实训流程"
+ ],
+ "migratedFrom": "legacy-model-document",
+ "source": "content-api-glb",
+ "sourceName": "09-Fox.glb",
+ "assetCode": "MODEL_MTO6V5W6_09-FOX",
+ "storageUri": "fsvc://unreal-tran/model/2026/09/05/3fc69ae0d435f869afca1fcd943486d1.glb"
+ },
+ "timeline": {
+ "version": 1,
+ "duration": 8,
+ "currentTime": 0,
+ "loop": true,
+ "zoom": 1,
+ "tracks": [
+ {
+ "id": "track-0-1yjfz9t",
+ "name": "模型整体位移",
+ "targetId": "interaction-b9240aaa",
+ "property": "position",
+ "color": "#ffb22e",
+ "muted": false,
+ "keyframes": [
+ {
+ "id": "key-0-0-kmfo820",
+ "time": 0,
+ "value": [
+ 0,
+ 0,
+ 0
+ ],
+ "easing": "easeInOut"
+ },
+ {
+ "id": "key-0-1-snsj2e2",
+ "time": 8,
+ "value": [
+ 0,
+ 0,
+ 0
+ ],
+ "easing": "easeInOut"
+ }
+ ]
+ }
+ ]
+ },
+ "blueprint": {
+ "version": 1,
+ "name": "挖掘机作业实训闭环",
+ "viewport": {
+ "x": 55,
+ "y": 55,
+ "zoom": 0.82
+ },
+ "nodes": [
+ {
+ "id": "bp-start",
+ "type": "trigger-click",
+ "title": "点击挖掘机",
+ "x": 40,
+ "y": 55,
+ "params": {
+ "event": "click",
+ "target": "挖掘机"
+ }
+ },
+ {
+ "id": "bp-highlight",
+ "type": "highlight",
+ "title": "高亮工作装置",
+ "x": 300,
+ "y": 55,
+ "params": {
+ "target": "铲斗",
+ "color": "#38e8ff",
+ "pulse": true
+ }
+ },
+ {
+ "id": "bp-message",
+ "type": "message",
+ "title": "显示操作提示",
+ "x": 560,
+ "y": 55,
+ "params": {
+ "title": "挖掘作业实训",
+ "text": "观察动臂、斗杆和铲斗的协同动作",
+ "level": "info"
+ }
+ },
+ {
+ "id": "bp-play",
+ "type": "play-timeline",
+ "title": "播放作业动画",
+ "x": 820,
+ "y": 55,
+ "params": {
+ "clip": "挖掘机作业实训",
+ "from": 0,
+ "speed": 1,
+ "waitForEnd": true
+ }
+ },
+ {
+ "id": "bp-wait",
+ "type": "wait",
+ "title": "动作稳定",
+ "x": 560,
+ "y": 245,
+ "params": {
+ "duration": 600
+ }
+ },
+ {
+ "id": "bp-check",
+ "type": "condition",
+ "title": "动画是否完成",
+ "x": 300,
+ "y": 245,
+ "params": {
+ "variable": "timelineComplete",
+ "operator": "==",
+ "value": true
+ }
+ },
+ {
+ "id": "bp-retry",
+ "type": "message",
+ "title": "等待动画完成",
+ "x": 820,
+ "y": 245,
+ "params": {
+ "title": "流程校验",
+ "text": "动画尚未完成,请重新执行",
+ "level": "warning"
+ }
+ },
+ {
+ "id": "bp-finish",
+ "type": "finish",
+ "title": "流程完成",
+ "x": 40,
+ "y": 245,
+ "params": {
+ "result": "success",
+ "text": "挖掘机作业实训流程完成"
+ }
+ }
+ ],
+ "edges": [
+ {
+ "id": "bp-e1",
+ "from": {
+ "node": "bp-start",
+ "port": "then"
+ },
+ "to": {
+ "node": "bp-highlight",
+ "port": "in"
+ }
+ },
+ {
+ "id": "bp-e2",
+ "from": {
+ "node": "bp-highlight",
+ "port": "then"
+ },
+ "to": {
+ "node": "bp-message",
+ "port": "in"
+ }
+ },
+ {
+ "id": "bp-e3",
+ "from": {
+ "node": "bp-message",
+ "port": "then"
+ },
+ "to": {
+ "node": "bp-play",
+ "port": "in"
+ }
+ },
+ {
+ "id": "bp-e4",
+ "from": {
+ "node": "bp-play",
+ "port": "then"
+ },
+ "to": {
+ "node": "bp-wait",
+ "port": "in"
+ }
+ },
+ {
+ "id": "bp-e5",
+ "from": {
+ "node": "bp-wait",
+ "port": "then"
+ },
+ "to": {
+ "node": "bp-check",
+ "port": "in"
+ }
+ },
+ {
+ "id": "bp-e6",
+ "from": {
+ "node": "bp-check",
+ "port": "true"
+ },
+ "to": {
+ "node": "bp-finish",
+ "port": "in"
+ }
+ },
+ {
+ "id": "bp-e7",
+ "from": {
+ "node": "bp-check",
+ "port": "false"
+ },
+ "to": {
+ "node": "bp-retry",
+ "port": "in"
+ }
+ },
+ {
+ "id": "bp-e8",
+ "from": {
+ "node": "bp-retry",
+ "port": "then"
+ },
+ "to": {
+ "node": "bp-play",
+ "port": "in"
+ }
+ }
+ ]
+ },
+ "camera": {
+ "position": [
+ 4.643347096844712,
+ 4.616668289605231,
+ 12.832677927580558
+ ],
+ "target": [
+ 3.875009990661482e-8,
+ 2.4006999949620704,
+ 2.2660093712545404e-7
+ ]
+ },
+ "model": {
+ "objects": {
+ "equipment-root": {
+ "name": "09-Fox",
+ "nodeName": "09-Fox",
+ "nodePath": "@root",
+ "interactionId": "interaction-b9240aaa",
+ "capabilities": {
+ "selectable": true,
+ "transform": true,
+ "visible": true,
+ "material": false,
+ "animatable": true
+ },
+ "position": [
+ 0,
+ 0,
+ 0
+ ],
+ "rotation": [
+ 0,
+ 0,
+ 0
+ ],
+ "scale": [
+ 1,
+ 1,
+ 1
+ ],
+ "visible": true,
+ "customComponents": []
+ },
+ "model-imported-model_mto6v5w6_09-fox-0": {
+ "name": "",
+ "nodeName": "Group",
+ "nodePath": "0",
+ "interactionId": "interaction-02351bce",
+ "capabilities": {
+ "selectable": true,
+ "transform": true,
+ "visible": true,
+ "material": false,
+ "animatable": true
+ },
+ "position": [
+ 3.875009990661482e-8,
+ 0.007396563645075124,
+ 0.6522089834776823
+ ],
+ "rotation": [
+ 0,
+ 0,
+ 0
+ ],
+ "scale": [
+ 0.06075496488891343,
+ 0.06075496488891343,
+ 0.06075496488891343
+ ],
+ "visible": true,
+ "customComponents": []
+ },
+ "model-imported-model_mto6v5w6_09-fox-1": {
+ "name": "root",
+ "nodeName": "root",
+ "nodePath": "0/0",
+ "interactionId": "interaction-a4d35db9",
+ "capabilities": {
+ "selectable": true,
+ "transform": true,
+ "visible": true,
+ "material": false,
+ "animatable": true
+ },
+ "position": [
+ 0,
+ 0,
+ 0
+ ],
+ "rotation": [
+ 0,
+ 0,
+ 0
+ ],
+ "scale": [
+ 1,
+ 1,
+ 1
+ ],
+ "visible": true,
+ "customComponents": []
+ },
+ "model-imported-model_mto6v5w6_09-fox-2": {
+ "name": "_rootJoint",
+ "nodeName": "_rootJoint",
+ "nodePath": "0/0/0",
+ "interactionId": "interaction-4476c556",
+ "capabilities": {
+ "selectable": true,
+ "transform": true,
+ "visible": true,
+ "material": false,
+ "animatable": true
+ },
+ "position": [
+ 0,
+ 0,
+ 0
+ ],
+ "rotation": [
+ 0,
+ 0,
+ 0
+ ],
+ "scale": [
+ 1,
+ 1,
+ 1
+ ],
+ "visible": true,
+ "customComponents": []
+ },
+ "model-imported-model_mto6v5w6_09-fox-3": {
+ "name": "b_Root_00",
+ "nodeName": "b_Root_00",
+ "nodePath": "0/0/0/0",
+ "interactionId": "interaction-ffc45d11",
+ "capabilities": {
+ "selectable": true,
+ "transform": true,
+ "visible": true,
+ "material": false,
+ "animatable": true
+ },
+ "position": [
+ 0,
+ 0,
+ 0
+ ],
+ "rotation": [
+ -1.5708000357176293,
+ 0,
+ 0
+ ],
+ "scale": [
+ 1,
+ 1,
+ 1
+ ],
+ "visible": true,
+ "customComponents": []
+ },
+ "model-imported-model_mto6v5w6_09-fox-4": {
+ "name": "b_Hip_01",
+ "nodeName": "b_Hip_01",
+ "nodePath": "0/0/0/0/0",
+ "interactionId": "interaction-7a75e39e",
+ "capabilities": {
+ "selectable": true,
+ "transform": true,
+ "visible": true,
+ "material": false,
+ "animatable": true
+ },
+ "position": [
+ 0.000001241521920852674,
+ 22.061977333358605,
+ 34.62373020761327
+ ],
+ "rotation": [
+ 0.44722050284761794,
+ -1.5707951637404125,
+ 0
+ ],
+ "scale": [
+ 1,
+ 1,
+ 1
+ ],
+ "visible": true,
+ "customComponents": []
+ },
+ "model-imported-model_mto6v5w6_09-fox-5": {
+ "name": "b_Spine01_02",
+ "nodeName": "b_Spine01_02",
+ "nodePath": "0/0/0/0/0/0",
+ "interactionId": "interaction-663ea469",
+ "capabilities": {
+ "selectable": true,
+ "transform": true,
+ "visible": true,
+ "material": false,
+ "animatable": true
+ },
+ "position": [
+ 12.850601196289062,
+ 0,
+ 0
+ ],
+ "rotation": [
+ -3.357310257291308e-7,
+ -3.316574913360067e-8,
+ -1.2102038452161539
+ ],
+ "scale": [
+ 1,
+ 1,
+ 1
+ ],
+ "visible": true,
+ "customComponents": []
+ },
+ "model-imported-model_mto6v5w6_09-fox-6": {
+ "name": "b_Spine02_03",
+ "nodeName": "b_Spine02_03",
+ "nodePath": "0/0/0/0/0/0/0",
+ "interactionId": "interaction-46867126",
+ "capabilities": {
+ "selectable": true,
+ "transform": true,
+ "visible": true,
+ "material": false,
+ "animatable": true
+ },
+ "position": [
+ 21.65575408935547,
+ -0.000118255615234375,
+ 0
+ ],
+ "rotation": [
+ 8.772651057591018e-8,
+ 3.3355428835535634e-8,
+ 0.3633406824515624
+ ],
+ "scale": [
+ 1,
+ 1,
+ 1
+ ],
+ "visible": true,
+ "customComponents": []
+ },
+ "model-imported-model_mto6v5w6_09-fox-7": {
+ "name": "b_Neck_04",
+ "nodeName": "b_Neck_04",
+ "nodePath": "0/0/0/0/0/0/0/0",
+ "interactionId": "interaction-27148d81",
+ "capabilities": {
+ "selectable": true,
+ "transform": true,
+ "visible": true,
+ "material": false,
+ "animatable": true
+ },
+ "position": [
+ 25.64914321899414,
+ 0,
+ 0
+ ],
+ "rotation": [
+ 6.423807629998062e-15,
+ 1.9993472402144555e-16,
+ 0.031113979193075042
+ ],
+ "scale": [
+ 1,
+ 1,
+ 1
+ ],
+ "visible": true,
+ "customComponents": []
+ },
+ "model-imported-model_mto6v5w6_09-fox-8": {
+ "name": "b_Head_05",
+ "nodeName": "b_Head_05",
+ "nodePath": "0/0/0/0/0/0/0/0/0",
+ "interactionId": "interaction-b24eef2e",
+ "capabilities": {
+ "selectable": true,
+ "transform": true,
+ "visible": true,
+ "material": false,
+ "animatable": true
+ },
+ "position": [
+ 13.376960754394531,
+ 0,
+ 0
+ ],
+ "rotation": [
+ 3.253825627358364e-8,
+ -1.577320798309453e-8,
+ -0.451380500897834
+ ],
+ "scale": [
+ 1,
+ 1,
+ 1
+ ],
+ "visible": true,
+ "customComponents": []
+ },
+ "model-imported-model_mto6v5w6_09-fox-9": {
+ "name": "b_RightUpperArm_06",
+ "nodeName": "b_RightUpperArm_06",
+ "nodePath": "0/0/0/0/0/0/0/1",
+ "interactionId": "interaction-26148bee",
+ "capabilities": {
+ "selectable": true,
+ "transform": true,
+ "visible": true,
+ "material": false,
+ "animatable": true
+ },
+ "position": [
+ 18.677913665771484,
+ -4.297340393066406,
+ 6.9675750732421875
+ ],
+ "rotation": [
+ -0.035644184619881884,
+ -0.047001863061712265,
+ -1.2972005346348685
+ ],
+ "scale": [
+ 1,
+ 1,
+ 1
+ ],
+ "visible": true,
+ "customComponents": []
+ },
+ "model-imported-model_mto6v5w6_09-fox-10": {
+ "name": "b_RightForeArm_07",
+ "nodeName": "b_RightForeArm_07",
+ "nodePath": "0/0/0/0/0/0/0/1/0",
+ "interactionId": "interaction-36896559",
+ "capabilities": {
+ "selectable": true,
+ "transform": true,
+ "visible": true,
+ "material": false,
+ "animatable": true
+ },
+ "position": [
+ 23.04512596130371,
+ 0,
+ 0
+ ],
+ "rotation": [
+ 2.1355818997049898e-8,
+ 7.233656206674673e-8,
+ 1.2561843405841544
+ ],
+ "scale": [
+ 1,
+ 1,
+ 1
+ ],
+ "visible": true,
+ "customComponents": []
+ },
+ "model-imported-model_mto6v5w6_09-fox-11": {
+ "name": "b_RightHand_08",
+ "nodeName": "b_RightHand_08",
+ "nodePath": "0/0/0/0/0/0/0/1/0/0",
+ "interactionId": "interaction-57a124f6",
+ "capabilities": {
+ "selectable": true,
+ "transform": true,
+ "visible": true,
+ "material": false,
+ "animatable": true
+ },
+ "position": [
+ 19.350055694580078,
+ -0.14598655700683594,
+ 0
+ ],
+ "rotation": [
+ 0.005245101551986586,
+ 0.05200705984217871,
+ -0.267165337534188
+ ],
+ "scale": [
+ 1,
+ 1,
+ 1
+ ],
+ "visible": true,
+ "customComponents": []
+ },
+ "model-imported-model_mto6v5w6_09-fox-12": {
+ "name": "b_LeftUpperArm_09",
+ "nodeName": "b_LeftUpperArm_09",
+ "nodePath": "0/0/0/0/0/0/0/2",
+ "interactionId": "interaction-25148a5b",
+ "capabilities": {
+ "selectable": true,
+ "transform": true,
+ "visible": true,
+ "material": false,
+ "animatable": true
+ },
+ "position": [
+ 18.67791748046875,
+ -4.297344207763672,
+ -6.967987060546875
+ ],
+ "rotation": [
+ -0.00020118104561539401,
+ 0.0009350561550523671,
+ -1.5148902559141275
+ ],
+ "scale": [
+ 1,
+ 1,
+ 1
+ ],
+ "visible": true,
+ "customComponents": []
+ },
+ "model-imported-model_mto6v5w6_09-fox-13": {
+ "name": "b_LeftForeArm_010",
+ "nodeName": "b_LeftForeArm_010",
+ "nodePath": "0/0/0/0/0/0/0/2/0",
+ "interactionId": "interaction-5c502ac4",
+ "capabilities": {
+ "selectable": true,
+ "transform": true,
+ "visible": true,
+ "material": false,
+ "animatable": true
+ },
+ "position": [
+ 23.045124053955078,
+ 0,
+ 0
+ ],
+ "rotation": [
+ 2.5220861991717696e-8,
+ 7.109903232964989e-8,
+ 1.2307106255258764
+ ],
+ "scale": [
+ 1,
+ 1,
+ 1
+ ],
+ "visible": true,
+ "customComponents": []
+ },
+ "model-imported-model_mto6v5w6_09-fox-14": {
+ "name": "b_LeftHand_011",
+ "nodeName": "b_LeftHand_011",
+ "nodePath": "0/0/0/0/0/0/0/2/0/0",
+ "interactionId": "interaction-192b4cd3",
+ "capabilities": {
+ "selectable": true,
+ "transform": true,
+ "visible": true,
+ "material": false,
+ "animatable": true
+ },
+ "position": [
+ 19.350051879882812,
+ -0.14599037170410156,
+ 0
+ ],
+ "rotation": [
+ 0.004279557751527162,
+ -0.03344276494440009,
+ -1.5985291598689726
+ ],
+ "scale": [
+ 1,
+ 1,
+ 1
+ ],
+ "visible": true,
+ "customComponents": []
+ },
+ "model-imported-model_mto6v5w6_09-fox-15": {
+ "name": "b_Tail01_012",
+ "nodeName": "b_Tail01_012",
+ "nodePath": "0/0/0/0/0/1",
+ "interactionId": "interaction-653ea2d6",
+ "capabilities": {
+ "selectable": true,
+ "transform": true,
+ "visible": true,
+ "material": false,
+ "animatable": true
+ },
+ "position": [
+ 4.2603759765625,
+ 15.958770751953125,
+ 0
+ ],
+ "rotation": [
+ -5.364745216052604e-7,
+ 3.424797608447095e-7,
+ 1.9941304874941974
+ ],
+ "scale": [
+ 1,
+ 1,
+ 1
+ ],
+ "visible": true,
+ "customComponents": []
+ },
+ "model-imported-model_mto6v5w6_09-fox-16": {
+ "name": "b_Tail02_013",
+ "nodeName": "b_Tail02_013",
+ "nodePath": "0/0/0/0/0/1/0",
+ "interactionId": "interaction-6c4d3691",
+ "capabilities": {
+ "selectable": true,
+ "transform": true,
+ "visible": true,
+ "material": false,
+ "animatable": true
+ },
+ "position": [
+ 12.411918640136719,
+ 0,
+ 0
+ ],
+ "rotation": [
+ -6.738507958375962e-23,
+ -5.2439168243310865e-23,
+ 0.3128105211171357
+ ],
+ "scale": [
+ 1,
+ 1,
+ 1
+ ],
+ "visible": true,
+ "customComponents": []
+ },
+ "model-imported-model_mto6v5w6_09-fox-17": {
+ "name": "b_Tail03_014",
+ "nodeName": "b_Tail03_014",
+ "nodePath": "0/0/0/0/0/1/0/0",
+ "interactionId": "interaction-e93e191e",
+ "capabilities": {
+ "selectable": true,
+ "transform": true,
+ "visible": true,
+ "material": false,
+ "animatable": true
+ },
+ "position": [
+ 24.24032211303711,
+ 0,
+ 0
+ ],
+ "rotation": [
+ -5.7486339033124146e-24,
+ 1.1669178332583734e-22,
+ -0.012937892408322462
+ ],
+ "scale": [
+ 1,
+ 1,
+ 1
+ ],
+ "visible": true,
+ "customComponents": []
+ },
+ "model-imported-model_mto6v5w6_09-fox-18": {
+ "name": "b_LeftLeg01_015",
+ "nodeName": "b_LeftLeg01_015",
+ "nodePath": "0/0/0/0/0/2",
+ "interactionId": "interaction-643ea143",
+ "capabilities": {
+ "selectable": true,
+ "transform": true,
+ "visible": true,
+ "material": false,
+ "animatable": true
+ },
+ "position": [
+ 4.813770294189453,
+ 5.154018402099609,
+ -6.968006134033203
+ ],
+ "rotation": [
+ 0.1223493637902729,
+ -0.09724770930295147,
+ 2.9630295165414986
+ ],
+ "scale": [
+ 1,
+ 1,
+ 1
+ ],
+ "visible": true,
+ "customComponents": []
+ },
+ "model-imported-model_mto6v5w6_09-fox-19": {
+ "name": "b_LeftLeg02_016",
+ "nodeName": "b_LeftLeg02_016",
+ "nodePath": "0/0/0/0/0/2/0",
+ "interactionId": "interaction-7175f8dc",
+ "capabilities": {
+ "selectable": true,
+ "transform": true,
+ "visible": true,
+ "material": false,
+ "animatable": true
+ },
+ "position": [
+ 18.944175720214844,
+ 0,
+ 0
+ ],
+ "rotation": [
+ 1.2545483692289454e-8,
+ 3.728483680761731e-8,
+ -0.522293355530293
+ ],
+ "scale": [
+ 1,
+ 1,
+ 1
+ ],
+ "visible": true,
+ "customComponents": []
+ },
+ "model-imported-model_mto6v5w6_09-fox-20": {
+ "name": "b_LeftFoot01_017",
+ "nodeName": "b_LeftFoot01_017",
+ "nodePath": "0/0/0/0/0/2/0/0",
+ "interactionId": "interaction-f5ed353b",
+ "capabilities": {
+ "selectable": true,
+ "transform": true,
+ "visible": true,
+ "material": false,
+ "animatable": true
+ },
+ "position": [
+ 17.942811965942383,
+ 0,
+ 0
+ ],
+ "rotation": [
+ -0.03128474386729028,
+ -0.15471927051895154,
+ 0.7774909055508243
+ ],
+ "scale": [
+ 1,
+ 1,
+ 1
+ ],
+ "visible": true,
+ "customComponents": []
+ },
+ "model-imported-model_mto6v5w6_09-fox-21": {
+ "name": "b_LeftFoot02_018",
+ "nodeName": "b_LeftFoot02_018",
+ "nodePath": "0/0/0/0/0/2/0/0/0",
+ "interactionId": "interaction-1bbe01a4",
+ "capabilities": {
+ "selectable": true,
+ "transform": true,
+ "visible": true,
+ "material": false,
+ "animatable": true
+ },
+ "position": [
+ 15.779938697814941,
+ 0,
+ 0
+ ],
+ "rotation": [
+ 0,
+ 0,
+ 1.158241570830627
+ ],
+ "scale": [
+ 1,
+ 1,
+ 1
+ ],
+ "visible": true,
+ "customComponents": []
+ },
+ "model-imported-model_mto6v5w6_09-fox-22": {
+ "name": "b_RightLeg01_019",
+ "nodeName": "b_RightLeg01_019",
+ "nodePath": "0/0/0/0/0/3",
+ "interactionId": "interaction-633e9fb0",
+ "capabilities": {
+ "selectable": true,
+ "transform": true,
+ "visible": true,
+ "material": false,
+ "animatable": true
+ },
+ "position": [
+ 4.813777923583984,
+ 5.154026031494141,
+ 6.967563629150391
+ ],
+ "rotation": [
+ -0.10137208185544387,
+ 0.05534659522395753,
+ -2.3613690068554236
+ ],
+ "scale": [
+ 1,
+ 1,
+ 1
+ ],
+ "visible": true,
+ "customComponents": []
+ },
+ "model-imported-model_mto6v5w6_09-fox-23": {
+ "name": "b_RightLeg02_020",
+ "nodeName": "b_RightLeg02_020",
+ "nodePath": "0/0/0/0/0/3/0",
+ "interactionId": "interaction-95ff89c7",
+ "capabilities": {
+ "selectable": true,
+ "transform": true,
+ "visible": true,
+ "material": false,
+ "animatable": true
+ },
+ "position": [
+ 18.944183349609375,
+ 0,
+ 0
+ ],
+ "rotation": [
+ 2.7890192431350882e-8,
+ 1.4750479199499402e-8,
+ -1.0710323806538369
+ ],
+ "scale": [
+ 1,
+ 1,
+ 1
+ ],
+ "visible": true,
+ "customComponents": []
+ },
+ "model-imported-model_mto6v5w6_09-fox-24": {
+ "name": "b_RightFoot01_021",
+ "nodeName": "b_RightFoot01_021",
+ "nodePath": "0/0/0/0/0/3/0/0",
+ "interactionId": "interaction-a157d498",
+ "capabilities": {
+ "selectable": true,
+ "transform": true,
+ "visible": true,
+ "material": false,
+ "animatable": true
+ },
+ "position": [
+ 17.94281005859375,
+ 0,
+ 0
+ ],
+ "rotation": [
+ -0.14401413968163027,
+ 0.2814735368347316,
+ 1.4591240233677443
+ ],
+ "scale": [
+ 1,
+ 1,
+ 1
+ ],
+ "visible": true,
+ "customComponents": []
+ },
+ "model-imported-model_mto6v5w6_09-fox-25": {
+ "name": "b_RightFoot02_022",
+ "nodeName": "b_RightFoot02_022",
+ "nodePath": "0/0/0/0/0/3/0/0/0",
+ "interactionId": "interaction-ebd38e3f",
+ "capabilities": {
+ "selectable": true,
+ "transform": true,
+ "visible": true,
+ "material": false,
+ "animatable": true
+ },
+ "position": [
+ 15.779935836791992,
+ 0,
+ 0
+ ],
+ "rotation": [
+ 0,
+ 0,
+ 1.158241570830627
+ ],
+ "scale": [
+ 1,
+ 1,
+ 1
+ ],
+ "visible": true,
+ "customComponents": []
+ },
+ "model-imported-model_mto6v5w6_09-fox-26": {
+ "name": "闭环验收-旧版本需人工合并",
+ "nodeName": "fox",
+ "nodePath": "0/1",
+ "interactionId": "interaction-a3d35c26",
+ "capabilities": {
+ "selectable": true,
+ "transform": true,
+ "visible": true,
+ "material": true,
+ "animatable": true
+ },
+ "position": [
+ 0.25,
+ 0,
+ 0
+ ],
+ "rotation": [
+ 0,
+ 0,
+ 0
+ ],
+ "scale": [
+ 1,
+ 1,
+ 1
+ ],
+ "visible": true,
+ "customComponents": [],
+ "materials": [
+ {
+ "color": "ffffff",
+ "emissive": "000000",
+ "emissiveIntensity": 1,
+ "metalness": 0,
+ "roughness": 0.3,
+ "opacity": 1,
+ "transparent": false,
+ "depthWrite": true
+ }
+ ]
+ }
+ },
+ "selectionScope": "part",
+ "nativeAnimation": {
+ "clipIndex": 2,
+ "clipName": "Run"
+ }
+ },
+ "environment": {
+ "mode": "placeholder",
+ "environmentType": "outdoor",
+ "sourceName": "内置野外综合训练环境",
+ "sizeBytes": 0,
+ "assetCode": "",
+ "storageUri": "",
+ "resourceUrl": "",
+ "objects": {
+ "outdoor-scene": {
+ "name": "野外综合训练环境",
+ "position": [
+ 0,
+ 0,
+ 0
+ ],
+ "rotation": [
+ 0,
+ 0,
+ 0
+ ],
+ "scale": [
+ 1,
+ 1,
+ 1
+ ],
+ "visible": true,
+ "customComponents": []
+ },
+ "outdoor-site": {
+ "name": "场地与道路",
+ "position": [
+ 0,
+ 0,
+ 0
+ ],
+ "rotation": [
+ 0,
+ 0,
+ 0
+ ],
+ "scale": [
+ 1,
+ 1,
+ 1
+ ],
+ "visible": true,
+ "customComponents": []
+ },
+ "outdoor-ground": {
+ "name": "室外训练场地",
+ "position": [
+ 0,
+ -0.065,
+ 0
+ ],
+ "rotation": [
+ -1.5707963267948966,
+ 0,
+ 0
+ ],
+ "scale": [
+ 1,
+ 1,
+ 1
+ ],
+ "visible": true,
+ "customComponents": [],
+ "materials": [
+ {
+ "color": "263529",
+ "emissive": "000000",
+ "emissiveIntensity": 1,
+ "metalness": 0,
+ "roughness": 0.98,
+ "opacity": 1,
+ "transparent": false,
+ "depthWrite": true
+ }
+ ]
+ },
+ "outdoor-apron": {
+ "name": "装备操作坪",
+ "position": [
+ 0,
+ -0.045,
+ 0
+ ],
+ "rotation": [
+ -1.5707963267948966,
+ 0,
+ 0
+ ],
+ "scale": [
+ 1,
+ 1,
+ 1
+ ],
+ "visible": true,
+ "customComponents": [],
+ "materials": [
+ {
+ "color": "39444a",
+ "emissive": "000000",
+ "emissiveIntensity": 1,
+ "metalness": 0.03,
+ "roughness": 0.92,
+ "opacity": 1,
+ "transparent": false,
+ "depthWrite": true
+ }
+ ]
+ },
+ "outdoor-road": {
+ "name": "保障通道",
+ "position": [
+ 0,
+ -0.035,
+ 17
+ ],
+ "rotation": [
+ -1.5707963267948966,
+ 0,
+ 0
+ ],
+ "scale": [
+ 1,
+ 1,
+ 1
+ ],
+ "visible": true,
+ "customComponents": [],
+ "materials": [
+ {
+ "color": "202a31",
+ "emissive": "000000",
+ "emissiveIntensity": 1,
+ "metalness": 0.02,
+ "roughness": 0.95,
+ "opacity": 1,
+ "transparent": false,
+ "depthWrite": true
+ }
+ ]
+ },
+ "outdoor-road-line-1": {
+ "name": "道路中心线 1",
+ "position": [
+ -28.699999999999996,
+ 0.01,
+ 17
+ ],
+ "rotation": [
+ 0,
+ 0,
+ 0
+ ],
+ "scale": [
+ 1,
+ 1,
+ 1
+ ],
+ "visible": true,
+ "customComponents": [],
+ "materials": [
+ {
+ "color": "e5c64c",
+ "emissive": "302600",
+ "emissiveIntensity": 0.18,
+ "metalness": 0.08,
+ "roughness": 0.72,
+ "opacity": 1,
+ "transparent": false,
+ "depthWrite": true
+ }
+ ]
+ },
+ "outdoor-road-line-2": {
+ "name": "道路中心线 2",
+ "position": [
+ -24.599999999999998,
+ 0.01,
+ 17
+ ],
+ "rotation": [
+ 0,
+ 0,
+ 0
+ ],
+ "scale": [
+ 1,
+ 1,
+ 1
+ ],
+ "visible": true,
+ "customComponents": [],
+ "materials": [
+ {
+ "color": "e5c64c",
+ "emissive": "302600",
+ "emissiveIntensity": 0.18,
+ "metalness": 0.08,
+ "roughness": 0.72,
+ "opacity": 1,
+ "transparent": false,
+ "depthWrite": true
+ }
+ ]
+ },
+ "outdoor-road-line-3": {
+ "name": "道路中心线 3",
+ "position": [
+ -20.5,
+ 0.01,
+ 17
+ ],
+ "rotation": [
+ 0,
+ 0,
+ 0
+ ],
+ "scale": [
+ 1,
+ 1,
+ 1
+ ],
+ "visible": true,
+ "customComponents": [],
+ "materials": [
+ {
+ "color": "e5c64c",
+ "emissive": "302600",
+ "emissiveIntensity": 0.18,
+ "metalness": 0.08,
+ "roughness": 0.72,
+ "opacity": 1,
+ "transparent": false,
+ "depthWrite": true
+ }
+ ]
+ },
+ "outdoor-road-line-4": {
+ "name": "道路中心线 4",
+ "position": [
+ -16.4,
+ 0.01,
+ 17
+ ],
+ "rotation": [
+ 0,
+ 0,
+ 0
+ ],
+ "scale": [
+ 1,
+ 1,
+ 1
+ ],
+ "visible": true,
+ "customComponents": [],
+ "materials": [
+ {
+ "color": "e5c64c",
+ "emissive": "302600",
+ "emissiveIntensity": 0.18,
+ "metalness": 0.08,
+ "roughness": 0.72,
+ "opacity": 1,
+ "transparent": false,
+ "depthWrite": true
+ }
+ ]
+ },
+ "outdoor-road-line-5": {
+ "name": "道路中心线 5",
+ "position": [
+ -12.299999999999999,
+ 0.01,
+ 17
+ ],
+ "rotation": [
+ 0,
+ 0,
+ 0
+ ],
+ "scale": [
+ 1,
+ 1,
+ 1
+ ],
+ "visible": true,
+ "customComponents": [],
+ "materials": [
+ {
+ "color": "e5c64c",
+ "emissive": "302600",
+ "emissiveIntensity": 0.18,
+ "metalness": 0.08,
+ "roughness": 0.72,
+ "opacity": 1,
+ "transparent": false,
+ "depthWrite": true
+ }
+ ]
+ },
+ "outdoor-road-line-6": {
+ "name": "道路中心线 6",
+ "position": [
+ -8.2,
+ 0.01,
+ 17
+ ],
+ "rotation": [
+ 0,
+ 0,
+ 0
+ ],
+ "scale": [
+ 1,
+ 1,
+ 1
+ ],
+ "visible": true,
+ "customComponents": [],
+ "materials": [
+ {
+ "color": "e5c64c",
+ "emissive": "302600",
+ "emissiveIntensity": 0.18,
+ "metalness": 0.08,
+ "roughness": 0.72,
+ "opacity": 1,
+ "transparent": false,
+ "depthWrite": true
+ }
+ ]
+ },
+ "outdoor-road-line-7": {
+ "name": "道路中心线 7",
+ "position": [
+ -4.1,
+ 0.01,
+ 17
+ ],
+ "rotation": [
+ 0,
+ 0,
+ 0
+ ],
+ "scale": [
+ 1,
+ 1,
+ 1
+ ],
+ "visible": true,
+ "customComponents": [],
+ "materials": [
+ {
+ "color": "e5c64c",
+ "emissive": "302600",
+ "emissiveIntensity": 0.18,
+ "metalness": 0.08,
+ "roughness": 0.72,
+ "opacity": 1,
+ "transparent": false,
+ "depthWrite": true
+ }
+ ]
+ },
+ "outdoor-road-line-8": {
+ "name": "道路中心线 8",
+ "position": [
+ 0,
+ 0.01,
+ 17
+ ],
+ "rotation": [
+ 0,
+ 0,
+ 0
+ ],
+ "scale": [
+ 1,
+ 1,
+ 1
+ ],
+ "visible": true,
+ "customComponents": [],
+ "materials": [
+ {
+ "color": "e5c64c",
+ "emissive": "302600",
+ "emissiveIntensity": 0.18,
+ "metalness": 0.08,
+ "roughness": 0.72,
+ "opacity": 1,
+ "transparent": false,
+ "depthWrite": true
+ }
+ ]
+ },
+ "outdoor-road-line-9": {
+ "name": "道路中心线 9",
+ "position": [
+ 4.1,
+ 0.01,
+ 17
+ ],
+ "rotation": [
+ 0,
+ 0,
+ 0
+ ],
+ "scale": [
+ 1,
+ 1,
+ 1
+ ],
+ "visible": true,
+ "customComponents": [],
+ "materials": [
+ {
+ "color": "e5c64c",
+ "emissive": "302600",
+ "emissiveIntensity": 0.18,
+ "metalness": 0.08,
+ "roughness": 0.72,
+ "opacity": 1,
+ "transparent": false,
+ "depthWrite": true
+ }
+ ]
+ },
+ "outdoor-road-line-10": {
+ "name": "道路中心线 10",
+ "position": [
+ 8.2,
+ 0.01,
+ 17
+ ],
+ "rotation": [
+ 0,
+ 0,
+ 0
+ ],
+ "scale": [
+ 1,
+ 1,
+ 1
+ ],
+ "visible": true,
+ "customComponents": [],
+ "materials": [
+ {
+ "color": "e5c64c",
+ "emissive": "302600",
+ "emissiveIntensity": 0.18,
+ "metalness": 0.08,
+ "roughness": 0.72,
+ "opacity": 1,
+ "transparent": false,
+ "depthWrite": true
+ }
+ ]
+ },
+ "outdoor-road-line-11": {
+ "name": "道路中心线 11",
+ "position": [
+ 12.299999999999999,
+ 0.01,
+ 17
+ ],
+ "rotation": [
+ 0,
+ 0,
+ 0
+ ],
+ "scale": [
+ 1,
+ 1,
+ 1
+ ],
+ "visible": true,
+ "customComponents": [],
+ "materials": [
+ {
+ "color": "e5c64c",
+ "emissive": "302600",
+ "emissiveIntensity": 0.18,
+ "metalness": 0.08,
+ "roughness": 0.72,
+ "opacity": 1,
+ "transparent": false,
+ "depthWrite": true
+ }
+ ]
+ },
+ "outdoor-road-line-12": {
+ "name": "道路中心线 12",
+ "position": [
+ 16.4,
+ 0.01,
+ 17
+ ],
+ "rotation": [
+ 0,
+ 0,
+ 0
+ ],
+ "scale": [
+ 1,
+ 1,
+ 1
+ ],
+ "visible": true,
+ "customComponents": [],
+ "materials": [
+ {
+ "color": "e5c64c",
+ "emissive": "302600",
+ "emissiveIntensity": 0.18,
+ "metalness": 0.08,
+ "roughness": 0.72,
+ "opacity": 1,
+ "transparent": false,
+ "depthWrite": true
+ }
+ ]
+ },
+ "outdoor-road-line-13": {
+ "name": "道路中心线 13",
+ "position": [
+ 20.5,
+ 0.01,
+ 17
+ ],
+ "rotation": [
+ 0,
+ 0,
+ 0
+ ],
+ "scale": [
+ 1,
+ 1,
+ 1
+ ],
+ "visible": true,
+ "customComponents": [],
+ "materials": [
+ {
+ "color": "e5c64c",
+ "emissive": "302600",
+ "emissiveIntensity": 0.18,
+ "metalness": 0.08,
+ "roughness": 0.72,
+ "opacity": 1,
+ "transparent": false,
+ "depthWrite": true
+ }
+ ]
+ },
+ "outdoor-road-line-14": {
+ "name": "道路中心线 14",
+ "position": [
+ 24.599999999999998,
+ 0.01,
+ 17
+ ],
+ "rotation": [
+ 0,
+ 0,
+ 0
+ ],
+ "scale": [
+ 1,
+ 1,
+ 1
+ ],
+ "visible": true,
+ "customComponents": [],
+ "materials": [
+ {
+ "color": "e5c64c",
+ "emissive": "302600",
+ "emissiveIntensity": 0.18,
+ "metalness": 0.08,
+ "roughness": 0.72,
+ "opacity": 1,
+ "transparent": false,
+ "depthWrite": true
+ }
+ ]
+ },
+ "outdoor-road-line-15": {
+ "name": "道路中心线 15",
+ "position": [
+ 28.699999999999996,
+ 0.01,
+ 17
+ ],
+ "rotation": [
+ 0,
+ 0,
+ 0
+ ],
+ "scale": [
+ 1,
+ 1,
+ 1
+ ],
+ "visible": true,
+ "customComponents": [],
+ "materials": [
+ {
+ "color": "e5c64c",
+ "emissive": "302600",
+ "emissiveIntensity": 0.18,
+ "metalness": 0.08,
+ "roughness": 0.72,
+ "opacity": 1,
+ "transparent": false,
+ "depthWrite": true
+ }
+ ]
+ },
+ "outdoor-safety--17.6--11": {
+ "name": "操作区安全标线",
+ "position": [
+ -17.6,
+ 0.012,
+ -11
+ ],
+ "rotation": [
+ 0,
+ 0.5654866776461628,
+ 0
+ ],
+ "scale": [
+ 1,
+ 1,
+ 1
+ ],
+ "visible": true,
+ "customComponents": [],
+ "materials": [
+ {
+ "color": "e5c64c",
+ "emissive": "302600",
+ "emissiveIntensity": 0.18,
+ "metalness": 0.08,
+ "roughness": 0.72,
+ "opacity": 1,
+ "transparent": false,
+ "depthWrite": true
+ }
+ ]
+ },
+ "outdoor-safety--17.6--8.4": {
+ "name": "操作区安全标线",
+ "position": [
+ -17.6,
+ 0.012,
+ -8.4
+ ],
+ "rotation": [
+ 0,
+ 0.5654866776461628,
+ 0
+ ],
+ "scale": [
+ 1,
+ 1,
+ 1
+ ],
+ "visible": true,
+ "customComponents": [],
+ "materials": [
+ {
+ "color": "e5c64c",
+ "emissive": "302600",
+ "emissiveIntensity": 0.18,
+ "metalness": 0.08,
+ "roughness": 0.72,
+ "opacity": 1,
+ "transparent": false,
+ "depthWrite": true
+ }
+ ]
+ },
+ "outdoor-safety--17.6--5.800000000000001": {
+ "name": "操作区安全标线",
+ "position": [
+ -17.6,
+ 0.012,
+ -5.800000000000001
+ ],
+ "rotation": [
+ 0,
+ 0.5654866776461628,
+ 0
+ ],
+ "scale": [
+ 1,
+ 1,
+ 1
+ ],
+ "visible": true,
+ "customComponents": [],
+ "materials": [
+ {
+ "color": "e5c64c",
+ "emissive": "302600",
+ "emissiveIntensity": 0.18,
+ "metalness": 0.08,
+ "roughness": 0.72,
+ "opacity": 1,
+ "transparent": false,
+ "depthWrite": true
+ }
+ ]
+ },
+ "outdoor-safety--17.6--3.2000000000000006": {
+ "name": "操作区安全标线",
+ "position": [
+ -17.6,
+ 0.012,
+ -3.2000000000000006
+ ],
+ "rotation": [
+ 0,
+ 0.5654866776461628,
+ 0
+ ],
+ "scale": [
+ 1,
+ 1,
+ 1
+ ],
+ "visible": true,
+ "customComponents": [],
+ "materials": [
+ {
+ "color": "e5c64c",
+ "emissive": "302600",
+ "emissiveIntensity": 0.18,
+ "metalness": 0.08,
+ "roughness": 0.72,
+ "opacity": 1,
+ "transparent": false,
+ "depthWrite": true
+ }
+ ]
+ },
+ "outdoor-safety--17.6--0.6000000000000005": {
+ "name": "操作区安全标线",
+ "position": [
+ -17.6,
+ 0.012,
+ -0.6000000000000005
+ ],
+ "rotation": [
+ 0,
+ 0.5654866776461628,
+ 0
+ ],
+ "scale": [
+ 1,
+ 1,
+ 1
+ ],
+ "visible": true,
+ "customComponents": [],
+ "materials": [
+ {
+ "color": "e5c64c",
+ "emissive": "302600",
+ "emissiveIntensity": 0.18,
+ "metalness": 0.08,
+ "roughness": 0.72,
+ "opacity": 1,
+ "transparent": false,
+ "depthWrite": true
+ }
+ ]
+ },
+ "outdoor-safety--17.6-1.9999999999999996": {
+ "name": "操作区安全标线",
+ "position": [
+ -17.6,
+ 0.012,
+ 1.9999999999999996
+ ],
+ "rotation": [
+ 0,
+ 0.5654866776461628,
+ 0
+ ],
+ "scale": [
+ 1,
+ 1,
+ 1
+ ],
+ "visible": true,
+ "customComponents": [],
+ "materials": [
+ {
+ "color": "e5c64c",
+ "emissive": "302600",
+ "emissiveIntensity": 0.18,
+ "metalness": 0.08,
+ "roughness": 0.72,
+ "opacity": 1,
+ "transparent": false,
+ "depthWrite": true
+ }
+ ]
+ },
+ "outdoor-safety--17.6-4.6": {
+ "name": "操作区安全标线",
+ "position": [
+ -17.6,
+ 0.012,
+ 4.6
+ ],
+ "rotation": [
+ 0,
+ 0.5654866776461628,
+ 0
+ ],
+ "scale": [
+ 1,
+ 1,
+ 1
+ ],
+ "visible": true,
+ "customComponents": [],
+ "materials": [
+ {
+ "color": "e5c64c",
+ "emissive": "302600",
+ "emissiveIntensity": 0.18,
+ "metalness": 0.08,
+ "roughness": 0.72,
+ "opacity": 1,
+ "transparent": false,
+ "depthWrite": true
+ }
+ ]
+ },
+ "outdoor-safety--17.6-7.199999999999999": {
+ "name": "操作区安全标线",
+ "position": [
+ -17.6,
+ 0.012,
+ 7.199999999999999
+ ],
+ "rotation": [
+ 0,
+ 0.5654866776461628,
+ 0
+ ],
+ "scale": [
+ 1,
+ 1,
+ 1
+ ],
+ "visible": true,
+ "customComponents": [],
+ "materials": [
+ {
+ "color": "e5c64c",
+ "emissive": "302600",
+ "emissiveIntensity": 0.18,
+ "metalness": 0.08,
+ "roughness": 0.72,
+ "opacity": 1,
+ "transparent": false,
+ "depthWrite": true
+ }
+ ]
+ },
+ "outdoor-safety--17.6-9.799999999999999": {
+ "name": "操作区安全标线",
+ "position": [
+ -17.6,
+ 0.012,
+ 9.799999999999999
+ ],
+ "rotation": [
+ 0,
+ 0.5654866776461628,
+ 0
+ ],
+ "scale": [
+ 1,
+ 1,
+ 1
+ ],
+ "visible": true,
+ "customComponents": [],
+ "materials": [
+ {
+ "color": "e5c64c",
+ "emissive": "302600",
+ "emissiveIntensity": 0.18,
+ "metalness": 0.08,
+ "roughness": 0.72,
+ "opacity": 1,
+ "transparent": false,
+ "depthWrite": true
+ }
+ ]
+ },
+ "outdoor-safety-17.6--11": {
+ "name": "操作区安全标线",
+ "position": [
+ 17.6,
+ 0.012,
+ -11
+ ],
+ "rotation": [
+ 0,
+ 0.5654866776461628,
+ 0
+ ],
+ "scale": [
+ 1,
+ 1,
+ 1
+ ],
+ "visible": true,
+ "customComponents": [],
+ "materials": [
+ {
+ "color": "e5c64c",
+ "emissive": "302600",
+ "emissiveIntensity": 0.18,
+ "metalness": 0.08,
+ "roughness": 0.72,
+ "opacity": 1,
+ "transparent": false,
+ "depthWrite": true
+ }
+ ]
+ },
+ "outdoor-safety-17.6--8.4": {
+ "name": "操作区安全标线",
+ "position": [
+ 17.6,
+ 0.012,
+ -8.4
+ ],
+ "rotation": [
+ 0,
+ 0.5654866776461628,
+ 0
+ ],
+ "scale": [
+ 1,
+ 1,
+ 1
+ ],
+ "visible": true,
+ "customComponents": [],
+ "materials": [
+ {
+ "color": "e5c64c",
+ "emissive": "302600",
+ "emissiveIntensity": 0.18,
+ "metalness": 0.08,
+ "roughness": 0.72,
+ "opacity": 1,
+ "transparent": false,
+ "depthWrite": true
+ }
+ ]
+ },
+ "outdoor-safety-17.6--5.800000000000001": {
+ "name": "操作区安全标线",
+ "position": [
+ 17.6,
+ 0.012,
+ -5.800000000000001
+ ],
+ "rotation": [
+ 0,
+ 0.5654866776461628,
+ 0
+ ],
+ "scale": [
+ 1,
+ 1,
+ 1
+ ],
+ "visible": true,
+ "customComponents": [],
+ "materials": [
+ {
+ "color": "e5c64c",
+ "emissive": "302600",
+ "emissiveIntensity": 0.18,
+ "metalness": 0.08,
+ "roughness": 0.72,
+ "opacity": 1,
+ "transparent": false,
+ "depthWrite": true
+ }
+ ]
+ },
+ "outdoor-safety-17.6--3.2000000000000006": {
+ "name": "操作区安全标线",
+ "position": [
+ 17.6,
+ 0.012,
+ -3.2000000000000006
+ ],
+ "rotation": [
+ 0,
+ 0.5654866776461628,
+ 0
+ ],
+ "scale": [
+ 1,
+ 1,
+ 1
+ ],
+ "visible": true,
+ "customComponents": [],
+ "materials": [
+ {
+ "color": "e5c64c",
+ "emissive": "302600",
+ "emissiveIntensity": 0.18,
+ "metalness": 0.08,
+ "roughness": 0.72,
+ "opacity": 1,
+ "transparent": false,
+ "depthWrite": true
+ }
+ ]
+ },
+ "outdoor-safety-17.6--0.6000000000000005": {
+ "name": "操作区安全标线",
+ "position": [
+ 17.6,
+ 0.012,
+ -0.6000000000000005
+ ],
+ "rotation": [
+ 0,
+ 0.5654866776461628,
+ 0
+ ],
+ "scale": [
+ 1,
+ 1,
+ 1
+ ],
+ "visible": true,
+ "customComponents": [],
+ "materials": [
+ {
+ "color": "e5c64c",
+ "emissive": "302600",
+ "emissiveIntensity": 0.18,
+ "metalness": 0.08,
+ "roughness": 0.72,
+ "opacity": 1,
+ "transparent": false,
+ "depthWrite": true
+ }
+ ]
+ },
+ "outdoor-safety-17.6-1.9999999999999996": {
+ "name": "操作区安全标线",
+ "position": [
+ 17.6,
+ 0.012,
+ 1.9999999999999996
+ ],
+ "rotation": [
+ 0,
+ 0.5654866776461628,
+ 0
+ ],
+ "scale": [
+ 1,
+ 1,
+ 1
+ ],
+ "visible": true,
+ "customComponents": [],
+ "materials": [
+ {
+ "color": "e5c64c",
+ "emissive": "302600",
+ "emissiveIntensity": 0.18,
+ "metalness": 0.08,
+ "roughness": 0.72,
+ "opacity": 1,
+ "transparent": false,
+ "depthWrite": true
+ }
+ ]
+ },
+ "outdoor-safety-17.6-4.6": {
+ "name": "操作区安全标线",
+ "position": [
+ 17.6,
+ 0.012,
+ 4.6
+ ],
+ "rotation": [
+ 0,
+ 0.5654866776461628,
+ 0
+ ],
+ "scale": [
+ 1,
+ 1,
+ 1
+ ],
+ "visible": true,
+ "customComponents": [],
+ "materials": [
+ {
+ "color": "e5c64c",
+ "emissive": "302600",
+ "emissiveIntensity": 0.18,
+ "metalness": 0.08,
+ "roughness": 0.72,
+ "opacity": 1,
+ "transparent": false,
+ "depthWrite": true
+ }
+ ]
+ },
+ "outdoor-safety-17.6-7.199999999999999": {
+ "name": "操作区安全标线",
+ "position": [
+ 17.6,
+ 0.012,
+ 7.199999999999999
+ ],
+ "rotation": [
+ 0,
+ 0.5654866776461628,
+ 0
+ ],
+ "scale": [
+ 1,
+ 1,
+ 1
+ ],
+ "visible": true,
+ "customComponents": [],
+ "materials": [
+ {
+ "color": "e5c64c",
+ "emissive": "302600",
+ "emissiveIntensity": 0.18,
+ "metalness": 0.08,
+ "roughness": 0.72,
+ "opacity": 1,
+ "transparent": false,
+ "depthWrite": true
+ }
+ ]
+ },
+ "outdoor-safety-17.6-9.799999999999999": {
+ "name": "操作区安全标线",
+ "position": [
+ 17.6,
+ 0.012,
+ 9.799999999999999
+ ],
+ "rotation": [
+ 0,
+ 0.5654866776461628,
+ 0
+ ],
+ "scale": [
+ 1,
+ 1,
+ 1
+ ],
+ "visible": true,
+ "customComponents": [],
+ "materials": [
+ {
+ "color": "e5c64c",
+ "emissive": "302600",
+ "emissiveIntensity": 0.18,
+ "metalness": 0.08,
+ "roughness": 0.72,
+ "opacity": 1,
+ "transparent": false,
+ "depthWrite": true
+ }
+ ]
+ },
+ "outdoor-workshop": {
+ "name": "维修保障厂房",
+ "position": [
+ 0,
+ 0,
+ 0
+ ],
+ "rotation": [
+ 0,
+ 0,
+ 0
+ ],
+ "scale": [
+ 1,
+ 1,
+ 1
+ ],
+ "visible": true,
+ "customComponents": []
+ },
+ "outdoor-workshop-body": {
+ "name": "维修保障厂房主体",
+ "position": [
+ -10,
+ 3.25,
+ -18
+ ],
+ "rotation": [
+ 0,
+ 0,
+ 0
+ ],
+ "scale": [
+ 1,
+ 1,
+ 1
+ ],
+ "visible": true,
+ "customComponents": [],
+ "materials": [
+ {
+ "color": "536675",
+ "emissive": "000000",
+ "emissiveIntensity": 1,
+ "metalness": 0.18,
+ "roughness": 0.78,
+ "opacity": 1,
+ "transparent": false,
+ "depthWrite": true
+ }
+ ]
+ },
+ "outdoor-workshop-roof": {
+ "name": "厂房金属屋面",
+ "position": [
+ -10,
+ 6.65,
+ -18
+ ],
+ "rotation": [
+ 0,
+ 0,
+ 0
+ ],
+ "scale": [
+ 1,
+ 1,
+ 1
+ ],
+ "visible": true,
+ "customComponents": [],
+ "materials": [
+ {
+ "color": "263846",
+ "emissive": "000000",
+ "emissiveIntensity": 1,
+ "metalness": 0.35,
+ "roughness": 0.62,
+ "opacity": 1,
+ "transparent": false,
+ "depthWrite": true
+ }
+ ]
+ },
+ "outdoor-workshop-window-1": {
+ "name": "厂房观察窗 1",
+ "position": [
+ -17,
+ 3.7,
+ -14.21
+ ],
+ "rotation": [
+ 0,
+ 0,
+ 0
+ ],
+ "scale": [
+ 1,
+ 1,
+ 1
+ ],
+ "visible": true,
+ "customComponents": [],
+ "materials": [
+ {
+ "color": "4a89a2",
+ "emissive": "000000",
+ "emissiveIntensity": 1,
+ "metalness": 0.12,
+ "roughness": 0.25,
+ "opacity": 0.68,
+ "transparent": true,
+ "depthWrite": true
+ }
+ ]
+ },
+ "outdoor-workshop-window-2": {
+ "name": "厂房观察窗 2",
+ "position": [
+ -12.4,
+ 3.7,
+ -14.21
+ ],
+ "rotation": [
+ 0,
+ 0,
+ 0
+ ],
+ "scale": [
+ 1,
+ 1,
+ 1
+ ],
+ "visible": true,
+ "customComponents": [],
+ "materials": [
+ {
+ "color": "4a89a2",
+ "emissive": "000000",
+ "emissiveIntensity": 1,
+ "metalness": 0.12,
+ "roughness": 0.25,
+ "opacity": 0.68,
+ "transparent": true,
+ "depthWrite": true
+ }
+ ]
+ },
+ "outdoor-workshop-window-3": {
+ "name": "厂房观察窗 3",
+ "position": [
+ -7.800000000000001,
+ 3.7,
+ -14.21
+ ],
+ "rotation": [
+ 0,
+ 0,
+ 0
+ ],
+ "scale": [
+ 1,
+ 1,
+ 1
+ ],
+ "visible": true,
+ "customComponents": [],
+ "materials": [
+ {
+ "color": "4a89a2",
+ "emissive": "000000",
+ "emissiveIntensity": 1,
+ "metalness": 0.12,
+ "roughness": 0.25,
+ "opacity": 0.68,
+ "transparent": true,
+ "depthWrite": true
+ }
+ ]
+ },
+ "outdoor-workshop-window-4": {
+ "name": "厂房观察窗 4",
+ "position": [
+ -3.200000000000001,
+ 3.7,
+ -14.21
+ ],
+ "rotation": [
+ 0,
+ 0,
+ 0
+ ],
+ "scale": [
+ 1,
+ 1,
+ 1
+ ],
+ "visible": true,
+ "customComponents": [],
+ "materials": [
+ {
+ "color": "4a89a2",
+ "emissive": "000000",
+ "emissiveIntensity": 1,
+ "metalness": 0.12,
+ "roughness": 0.25,
+ "opacity": 0.68,
+ "transparent": true,
+ "depthWrite": true
+ }
+ ]
+ },
+ "outdoor-workshop-door": {
+ "name": "装备出入门",
+ "position": [
+ -3.6,
+ 2.16,
+ -14.18
+ ],
+ "rotation": [
+ 0,
+ 0,
+ 0
+ ],
+ "scale": [
+ 1,
+ 1,
+ 1
+ ],
+ "visible": true,
+ "customComponents": [],
+ "materials": [
+ {
+ "color": "202a31",
+ "emissive": "000000",
+ "emissiveIntensity": 1,
+ "metalness": 0.02,
+ "roughness": 0.95,
+ "opacity": 1,
+ "transparent": false,
+ "depthWrite": true
+ }
+ ]
+ },
+ "outdoor-canopy": {
+ "name": "室外检修棚",
+ "position": [
+ 0,
+ 0,
+ 0
+ ],
+ "rotation": [
+ 0,
+ 0,
+ 0
+ ],
+ "scale": [
+ 1,
+ 1,
+ 1
+ ],
+ "visible": true,
+ "customComponents": []
+ },
+ "outdoor-canopy-roof": {
+ "name": "检修棚顶",
+ "position": [
+ 18,
+ 5.05,
+ -8
+ ],
+ "rotation": [
+ 0,
+ 0,
+ 0
+ ],
+ "scale": [
+ 1,
+ 1,
+ 1
+ ],
+ "visible": true,
+ "customComponents": [],
+ "materials": [
+ {
+ "color": "263846",
+ "emissive": "000000",
+ "emissiveIntensity": 1,
+ "metalness": 0.35,
+ "roughness": 0.62,
+ "opacity": 1,
+ "transparent": false,
+ "depthWrite": true
+ }
+ ]
+ },
+ "outdoor-canopy-column-1": {
+ "name": "检修棚立柱 1",
+ "position": [
+ 12.7,
+ 2.5,
+ -11
+ ],
+ "rotation": [
+ 0,
+ 0,
+ 0
+ ],
+ "scale": [
+ 1,
+ 1,
+ 1
+ ],
+ "visible": true,
+ "customComponents": [],
+ "materials": [
+ {
+ "color": "66757c",
+ "emissive": "000000",
+ "emissiveIntensity": 1,
+ "metalness": 0.48,
+ "roughness": 0.56,
+ "opacity": 1,
+ "transparent": false,
+ "depthWrite": true
+ }
+ ]
+ },
+ "outdoor-canopy-column-2": {
+ "name": "检修棚立柱 2",
+ "position": [
+ 23.3,
+ 2.5,
+ -11
+ ],
+ "rotation": [
+ 0,
+ 0,
+ 0
+ ],
+ "scale": [
+ 1,
+ 1,
+ 1
+ ],
+ "visible": true,
+ "customComponents": [],
+ "materials": [
+ {
+ "color": "66757c",
+ "emissive": "000000",
+ "emissiveIntensity": 1,
+ "metalness": 0.48,
+ "roughness": 0.56,
+ "opacity": 1,
+ "transparent": false,
+ "depthWrite": true
+ }
+ ]
+ },
+ "outdoor-canopy-column-3": {
+ "name": "检修棚立柱 3",
+ "position": [
+ 12.7,
+ 2.5,
+ -5
+ ],
+ "rotation": [
+ 0,
+ 0,
+ 0
+ ],
+ "scale": [
+ 1,
+ 1,
+ 1
+ ],
+ "visible": true,
+ "customComponents": [],
+ "materials": [
+ {
+ "color": "66757c",
+ "emissive": "000000",
+ "emissiveIntensity": 1,
+ "metalness": 0.48,
+ "roughness": 0.56,
+ "opacity": 1,
+ "transparent": false,
+ "depthWrite": true
+ }
+ ]
+ },
+ "outdoor-canopy-column-4": {
+ "name": "检修棚立柱 4",
+ "position": [
+ 23.3,
+ 2.5,
+ -5
+ ],
+ "rotation": [
+ 0,
+ 0,
+ 0
+ ],
+ "scale": [
+ 1,
+ 1,
+ 1
+ ],
+ "visible": true,
+ "customComponents": [],
+ "materials": [
+ {
+ "color": "66757c",
+ "emissive": "000000",
+ "emissiveIntensity": 1,
+ "metalness": 0.48,
+ "roughness": 0.56,
+ "opacity": 1,
+ "transparent": false,
+ "depthWrite": true
+ }
+ ]
+ },
+ "outdoor-service-box": {
+ "name": "室外工具柜",
+ "position": [
+ 22.2,
+ 1.05,
+ -9.8
+ ],
+ "rotation": [
+ 0,
+ 0,
+ 0
+ ],
+ "scale": [
+ 1,
+ 1,
+ 1
+ ],
+ "visible": true,
+ "customComponents": [],
+ "materials": [
+ {
+ "color": "d98d2b",
+ "emissive": "000000",
+ "emissiveIntensity": 1,
+ "metalness": 0.12,
+ "roughness": 0.7,
+ "opacity": 1,
+ "transparent": false,
+ "depthWrite": true
+ }
+ ]
+ },
+ "outdoor-perimeter": {
+ "name": "场地边界与安全设施",
+ "position": [
+ 0,
+ 0,
+ 0
+ ],
+ "rotation": [
+ 0,
+ 0,
+ 0
+ ],
+ "scale": [
+ 1,
+ 1,
+ 1
+ ],
+ "visible": true,
+ "customComponents": []
+ },
+ "outdoor-fence-back-1": {
+ "name": "后侧围栏立柱 1",
+ "position": [
+ -29,
+ 1.1,
+ -25
+ ],
+ "rotation": [
+ 0,
+ 0,
+ 0
+ ],
+ "scale": [
+ 1,
+ 1,
+ 1
+ ],
+ "visible": true,
+ "customComponents": [],
+ "materials": [
+ {
+ "color": "66757c",
+ "emissive": "000000",
+ "emissiveIntensity": 1,
+ "metalness": 0.48,
+ "roughness": 0.56,
+ "opacity": 1,
+ "transparent": false,
+ "depthWrite": true
+ }
+ ]
+ },
+ "outdoor-fence-back-2": {
+ "name": "后侧围栏立柱 2",
+ "position": [
+ -23.2,
+ 1.1,
+ -25
+ ],
+ "rotation": [
+ 0,
+ 0,
+ 0
+ ],
+ "scale": [
+ 1,
+ 1,
+ 1
+ ],
+ "visible": true,
+ "customComponents": [],
+ "materials": [
+ {
+ "color": "66757c",
+ "emissive": "000000",
+ "emissiveIntensity": 1,
+ "metalness": 0.48,
+ "roughness": 0.56,
+ "opacity": 1,
+ "transparent": false,
+ "depthWrite": true
+ }
+ ]
+ },
+ "outdoor-fence-back-3": {
+ "name": "后侧围栏立柱 3",
+ "position": [
+ -17.4,
+ 1.1,
+ -25
+ ],
+ "rotation": [
+ 0,
+ 0,
+ 0
+ ],
+ "scale": [
+ 1,
+ 1,
+ 1
+ ],
+ "visible": true,
+ "customComponents": [],
+ "materials": [
+ {
+ "color": "66757c",
+ "emissive": "000000",
+ "emissiveIntensity": 1,
+ "metalness": 0.48,
+ "roughness": 0.56,
+ "opacity": 1,
+ "transparent": false,
+ "depthWrite": true
+ }
+ ]
+ },
+ "outdoor-fence-back-4": {
+ "name": "后侧围栏立柱 4",
+ "position": [
+ -11.6,
+ 1.1,
+ -25
+ ],
+ "rotation": [
+ 0,
+ 0,
+ 0
+ ],
+ "scale": [
+ 1,
+ 1,
+ 1
+ ],
+ "visible": true,
+ "customComponents": [],
+ "materials": [
+ {
+ "color": "66757c",
+ "emissive": "000000",
+ "emissiveIntensity": 1,
+ "metalness": 0.48,
+ "roughness": 0.56,
+ "opacity": 1,
+ "transparent": false,
+ "depthWrite": true
+ }
+ ]
+ },
+ "outdoor-fence-back-5": {
+ "name": "后侧围栏立柱 5",
+ "position": [
+ -5.8,
+ 1.1,
+ -25
+ ],
+ "rotation": [
+ 0,
+ 0,
+ 0
+ ],
+ "scale": [
+ 1,
+ 1,
+ 1
+ ],
+ "visible": true,
+ "customComponents": [],
+ "materials": [
+ {
+ "color": "66757c",
+ "emissive": "000000",
+ "emissiveIntensity": 1,
+ "metalness": 0.48,
+ "roughness": 0.56,
+ "opacity": 1,
+ "transparent": false,
+ "depthWrite": true
+ }
+ ]
+ },
+ "outdoor-fence-back-6": {
+ "name": "后侧围栏立柱 6",
+ "position": [
+ 0,
+ 1.1,
+ -25
+ ],
+ "rotation": [
+ 0,
+ 0,
+ 0
+ ],
+ "scale": [
+ 1,
+ 1,
+ 1
+ ],
+ "visible": true,
+ "customComponents": [],
+ "materials": [
+ {
+ "color": "66757c",
+ "emissive": "000000",
+ "emissiveIntensity": 1,
+ "metalness": 0.48,
+ "roughness": 0.56,
+ "opacity": 1,
+ "transparent": false,
+ "depthWrite": true
+ }
+ ]
+ },
+ "outdoor-fence-back-7": {
+ "name": "后侧围栏立柱 7",
+ "position": [
+ 5.8,
+ 1.1,
+ -25
+ ],
+ "rotation": [
+ 0,
+ 0,
+ 0
+ ],
+ "scale": [
+ 1,
+ 1,
+ 1
+ ],
+ "visible": true,
+ "customComponents": [],
+ "materials": [
+ {
+ "color": "66757c",
+ "emissive": "000000",
+ "emissiveIntensity": 1,
+ "metalness": 0.48,
+ "roughness": 0.56,
+ "opacity": 1,
+ "transparent": false,
+ "depthWrite": true
+ }
+ ]
+ },
+ "outdoor-fence-back-8": {
+ "name": "后侧围栏立柱 8",
+ "position": [
+ 11.6,
+ 1.1,
+ -25
+ ],
+ "rotation": [
+ 0,
+ 0,
+ 0
+ ],
+ "scale": [
+ 1,
+ 1,
+ 1
+ ],
+ "visible": true,
+ "customComponents": [],
+ "materials": [
+ {
+ "color": "66757c",
+ "emissive": "000000",
+ "emissiveIntensity": 1,
+ "metalness": 0.48,
+ "roughness": 0.56,
+ "opacity": 1,
+ "transparent": false,
+ "depthWrite": true
+ }
+ ]
+ },
+ "outdoor-fence-back-9": {
+ "name": "后侧围栏立柱 9",
+ "position": [
+ 17.4,
+ 1.1,
+ -25
+ ],
+ "rotation": [
+ 0,
+ 0,
+ 0
+ ],
+ "scale": [
+ 1,
+ 1,
+ 1
+ ],
+ "visible": true,
+ "customComponents": [],
+ "materials": [
+ {
+ "color": "66757c",
+ "emissive": "000000",
+ "emissiveIntensity": 1,
+ "metalness": 0.48,
+ "roughness": 0.56,
+ "opacity": 1,
+ "transparent": false,
+ "depthWrite": true
+ }
+ ]
+ },
+ "outdoor-fence-back-10": {
+ "name": "后侧围栏立柱 10",
+ "position": [
+ 23.2,
+ 1.1,
+ -25
+ ],
+ "rotation": [
+ 0,
+ 0,
+ 0
+ ],
+ "scale": [
+ 1,
+ 1,
+ 1
+ ],
+ "visible": true,
+ "customComponents": [],
+ "materials": [
+ {
+ "color": "66757c",
+ "emissive": "000000",
+ "emissiveIntensity": 1,
+ "metalness": 0.48,
+ "roughness": 0.56,
+ "opacity": 1,
+ "transparent": false,
+ "depthWrite": true
+ }
+ ]
+ },
+ "outdoor-fence-back-11": {
+ "name": "后侧围栏立柱 11",
+ "position": [
+ 29,
+ 1.1,
+ -25
+ ],
+ "rotation": [
+ 0,
+ 0,
+ 0
+ ],
+ "scale": [
+ 1,
+ 1,
+ 1
+ ],
+ "visible": true,
+ "customComponents": [],
+ "materials": [
+ {
+ "color": "66757c",
+ "emissive": "000000",
+ "emissiveIntensity": 1,
+ "metalness": 0.48,
+ "roughness": 0.56,
+ "opacity": 1,
+ "transparent": false,
+ "depthWrite": true
+ }
+ ]
+ },
+ "outdoor-fence-rail-a": {
+ "name": "后侧围栏上横杆",
+ "position": [
+ 0,
+ 1.85,
+ -25
+ ],
+ "rotation": [
+ 0,
+ 0,
+ 0
+ ],
+ "scale": [
+ 1,
+ 1,
+ 1
+ ],
+ "visible": true,
+ "customComponents": [],
+ "materials": [
+ {
+ "color": "66757c",
+ "emissive": "000000",
+ "emissiveIntensity": 1,
+ "metalness": 0.48,
+ "roughness": 0.56,
+ "opacity": 1,
+ "transparent": false,
+ "depthWrite": true
+ }
+ ]
+ },
+ "outdoor-fence-rail-b": {
+ "name": "后侧围栏下横杆",
+ "position": [
+ 0,
+ 0.75,
+ -25
+ ],
+ "rotation": [
+ 0,
+ 0,
+ 0
+ ],
+ "scale": [
+ 1,
+ 1,
+ 1
+ ],
+ "visible": true,
+ "customComponents": [],
+ "materials": [
+ {
+ "color": "66757c",
+ "emissive": "000000",
+ "emissiveIntensity": 1,
+ "metalness": 0.48,
+ "roughness": 0.56,
+ "opacity": 1,
+ "transparent": false,
+ "depthWrite": true
+ }
+ ]
+ },
+ "outdoor-scene-sign": {
+ "name": "室外实训区域标识",
+ "position": [
+ 26,
+ 2.4,
+ 11.5
+ ],
+ "rotation": [
+ 0,
+ 0,
+ 0
+ ],
+ "scale": [
+ 1,
+ 1,
+ 1
+ ],
+ "visible": true,
+ "customComponents": [],
+ "materials": [
+ {
+ "color": "536675",
+ "emissive": "000000",
+ "emissiveIntensity": 1,
+ "metalness": 0.18,
+ "roughness": 0.78,
+ "opacity": 1,
+ "transparent": false,
+ "depthWrite": true
+ }
+ ]
+ },
+ "outdoor-scene-sign-post-a": {
+ "name": "区域标识支柱 A",
+ "position": [
+ 24,
+ 1.4,
+ 11.5
+ ],
+ "rotation": [
+ 0,
+ 0,
+ 0
+ ],
+ "scale": [
+ 1,
+ 1,
+ 1
+ ],
+ "visible": true,
+ "customComponents": [],
+ "materials": [
+ {
+ "color": "66757c",
+ "emissive": "000000",
+ "emissiveIntensity": 1,
+ "metalness": 0.48,
+ "roughness": 0.56,
+ "opacity": 1,
+ "transparent": false,
+ "depthWrite": true
+ }
+ ]
+ },
+ "outdoor-scene-sign-post-b": {
+ "name": "区域标识支柱 B",
+ "position": [
+ 28,
+ 1.4,
+ 11.5
+ ],
+ "rotation": [
+ 0,
+ 0,
+ 0
+ ],
+ "scale": [
+ 1,
+ 1,
+ 1
+ ],
+ "visible": true,
+ "customComponents": [],
+ "materials": [
+ {
+ "color": "66757c",
+ "emissive": "000000",
+ "emissiveIntensity": 1,
+ "metalness": 0.48,
+ "roughness": 0.56,
+ "opacity": 1,
+ "transparent": false,
+ "depthWrite": true
+ }
+ ]
+ }
+ }
+ },
+ "runtime": {
+ "startMode": "guided",
+ "showStepPanel": true,
+ "showProgress": true,
+ "allowReplay": true,
+ "language": "zh-CN"
+ },
+ "publish": {
+ "target": "standalone-html",
+ "deployment": "offline",
+ "includeProjectData": true,
+ "includeViewportPreview": true
+ }
+ }
+}
\ No newline at end of file
diff --git a/reports/model-editor-closure-20260906/evidence/30-assets-roundtrip.json b/reports/model-editor-closure-20260906/evidence/30-assets-roundtrip.json
new file mode 100644
index 0000000..e367aa4
--- /dev/null
+++ b/reports/model-editor-closure-20260906/evidence/30-assets-roundtrip.json
@@ -0,0 +1,35 @@
+{
+ "time": "2026-09-06T08:20:27.495Z",
+ "data": {
+ "status": "PASS",
+ "kind": "e2e+api",
+ "testTitle": "受控模型与外部环境 GLB 上传、JSON 往返和刷新重开",
+ "featureIds": [
+ "project.import-glb",
+ "environment.external",
+ "project.import-json",
+ "project.export-json",
+ "project.save"
+ ],
+ "projectId": "226",
+ "mainCode": "MODEL_MTPJKFPW_09-FOX",
+ "environmentCode": "ENV_MODEL_MTPJKH7H_01-BOX",
+ "mainUri": "fsvc://unreal-tran/model/2026/09/06/b85bd2722c4e133e61469c9f413380f0.glb",
+ "environmentUri": "fsvc://unreal-tran/model/2026/09/06/9bd7351f3ee917431354690f44a449b5.glb",
+ "modelMeshes": 1,
+ "environmentMeshes": 1,
+ "nativeClips": 3,
+ "environmentPosition": [
+ 6,
+ 0,
+ -7
+ ],
+ "environmentScale": [
+ 0.15,
+ 0.15,
+ 0.15
+ ],
+ "result": "PASS",
+ "scope": "真实上传、注册、保存、JSON重导入、刷新后的实际网格和原生动画目录;未断言下游场景执行动画。"
+ }
+}
\ No newline at end of file
diff --git a/reports/model-editor-closure-20260906/evidence/31-invalid-import.json b/reports/model-editor-closure-20260906/evidence/31-invalid-import.json
new file mode 100644
index 0000000..241b347
--- /dev/null
+++ b/reports/model-editor-closure-20260906/evidence/31-invalid-import.json
@@ -0,0 +1,18 @@
+{
+ "time": "2026-09-06T08:20:33.535Z",
+ "data": {
+ "status": "PASS",
+ "kind": "e2e+api",
+ "testTitle": "非法 JSON 与未登记 blob 引用导入失败时保留当前文档",
+ "featureIds": [
+ "project.import-json"
+ ],
+ "projectId": "227",
+ "result": "PASS",
+ "cases": [
+ "JSON语法错误",
+ "无受控资源编号/地址的跨页面blob引用"
+ ],
+ "retainedVersion": 1
+ }
+}
\ No newline at end of file
diff --git a/reports/model-editor-closure-20260906/evidence/32-save-failure-recovery.json b/reports/model-editor-closure-20260906/evidence/32-save-failure-recovery.json
new file mode 100644
index 0000000..feb3a41
--- /dev/null
+++ b/reports/model-editor-closure-20260906/evidence/32-save-failure-recovery.json
@@ -0,0 +1,28 @@
+{
+ "time": "2026-09-06T08:20:43.214Z",
+ "data": {
+ "status": "PASS",
+ "kind": "e2e+api",
+ "testTitle": "保存请求超时和 409 保留恢复副本,解除故障后可重试",
+ "featureIds": [
+ "project.conflict",
+ "project.save",
+ "feedback.toast"
+ ],
+ "projectId": "228",
+ "result": "PASS",
+ "intercepted": [
+ {
+ "mode": "timedout",
+ "method": "PUT",
+ "projectId": "228"
+ },
+ {
+ "mode": "409",
+ "method": "PUT",
+ "projectId": "228"
+ }
+ ],
+ "scope": "真实页面保存及真实服务端重试;失败由浏览器定向注入 timedout/409,非宣称自然发生的服务故障。"
+ }
+}
\ No newline at end of file
diff --git a/reports/model-editor-closure-20260906/evidence/34-publish-fixed-version.json b/reports/model-editor-closure-20260906/evidence/34-publish-fixed-version.json
new file mode 100644
index 0000000..bff8b21
--- /dev/null
+++ b/reports/model-editor-closure-20260906/evidence/34-publish-fixed-version.json
@@ -0,0 +1,21 @@
+{
+ "time": "2026-09-06T08:20:51.047Z",
+ "data": {
+ "status": "PASS",
+ "kind": "e2e+api",
+ "testTitle": "直接发布同步截图封面、固定版本目录与真实离线步骤 HTML",
+ "featureIds": [
+ "publish.api",
+ "publish.cover",
+ "publish.html",
+ "publish.downstream"
+ ],
+ "projectId": "229",
+ "result": "PASS",
+ "versionId": "283",
+ "coverUri": "fsvc://unreal-tran/image/2026/09/06/14ab7b4e1e7a9d21f13f3504cb8b89ae.jpg",
+ "priorCoverUri": "fsvc://unreal-tran/image/2026/09/05/c09a49efd4423dcf8ccbc46b46d1e209.jpg",
+ "mainCode": "MODEL_MTO6V5W6_09-FOX",
+ "scope": "目录可取得固定发布版本及同版封面;HTML已真实打开并确认演示边界,未声明下游动画执行或完整离线三维运行。"
+ }
+}
\ No newline at end of file
diff --git a/reports/model-editor-closure-20260906/evidence/41-resource-search-switch.json b/reports/model-editor-closure-20260906/evidence/41-resource-search-switch.json
new file mode 100644
index 0000000..5e1b088
--- /dev/null
+++ b/reports/model-editor-closure-20260906/evidence/41-resource-search-switch.json
@@ -0,0 +1,25 @@
+{
+ "time": "2026-09-06T08:22:56.960Z",
+ "data": {
+ "status": "PASS",
+ "kind": "e2e+api",
+ "testTitle": "补充资源库:搜索清空、真实卡片切换与刷新保留",
+ "featureIds": [
+ "resource.search",
+ "resource.catalog"
+ ],
+ "projectId": "235",
+ "original": {
+ "id": "imported-model_mto6v5w6_09-fox",
+ "name": "09-Fox",
+ "code": "MODEL_MTO6V5W6_09-FOX"
+ },
+ "boxId": "imported-model_mtpjnqqk_01-box",
+ "restored": {
+ "id": "imported-model_mto6v5w6_09-fox",
+ "code": "MODEL_MTO6V5W6_09-FOX",
+ "meshes": 1
+ },
+ "scope": "真实搜索与资源卡片点击、服务端保存、刷新后原模型网格;副本独立登记待清理。"
+ }
+}
\ No newline at end of file
diff --git a/reports/model-editor-closure-20260906/evidence/43-builtin-environments-tree.json b/reports/model-editor-closure-20260906/evidence/43-builtin-environments-tree.json
new file mode 100644
index 0000000..7f710a0
--- /dev/null
+++ b/reports/model-editor-closure-20260906/evidence/43-builtin-environments-tree.json
@@ -0,0 +1,52 @@
+{
+ "time": "2026-09-06T08:23:19.759Z",
+ "data": {
+ "status": "PASS",
+ "kind": "e2e+api",
+ "testTitle": "补充环境与树:内置三环境保存重开、节点计数与折叠展开",
+ "featureIds": [
+ "environment.builtin",
+ "tree.browse",
+ "tree.expand",
+ "project.leave"
+ ],
+ "projectId": "236",
+ "snapshots": [
+ {
+ "type": "workshop",
+ "mode": "placeholder",
+ "sourceKind": "placeholder",
+ "nodes": 48,
+ "meshes": 45,
+ "id": "ee302132-152c-4575-a2ec-0a56b4820079",
+ "shownRows": 76,
+ "totalNodes": 76
+ },
+ {
+ "type": "outdoor",
+ "mode": "placeholder",
+ "sourceKind": "placeholder",
+ "nodes": 70,
+ "meshes": 65,
+ "id": "bf88db35-635d-4012-8df6-dd41382e624f",
+ "shownRows": 98,
+ "totalNodes": 98
+ },
+ {
+ "type": "depot",
+ "mode": "placeholder",
+ "sourceKind": "builtin-gltf",
+ "nodes": 41,
+ "meshes": 32,
+ "id": "cebab0bb-6910-4dff-b846-9978adcb3450",
+ "shownRows": 69,
+ "totalNodes": 69
+ }
+ ],
+ "depotSavedLeave": {
+ "promptedForUnsavedChanges": false,
+ "version": 5
+ },
+ "scope": "真实切换、保存和刷新三种环境;树计数与展开折叠;装备车库保存后返回列表不误报未保存。树220行上限仍属已说明边界。"
+ }
+}
\ No newline at end of file
diff --git a/reports/model-editor-closure-20260906/evidence/46-navigation-panels-autosave.json b/reports/model-editor-closure-20260906/evidence/46-navigation-panels-autosave.json
new file mode 100644
index 0000000..fb86d12
--- /dev/null
+++ b/reports/model-editor-closure-20260906/evidence/46-navigation-panels-autosave.json
@@ -0,0 +1,80 @@
+{
+ "time": "2026-09-06T08:23:54.656Z",
+ "data": {
+ "status": "PASS",
+ "kind": "e2e+api",
+ "testTitle": "补充视口与工作区:鼠标镜头、聚焦、面板、预览导航、日志与自动保存",
+ "featureIds": [
+ "project.name",
+ "project.autosave",
+ "view.orbit",
+ "view.focus",
+ "view.panels",
+ "view.preview-step",
+ "feedback.log",
+ "feedback.stats",
+ "property.identity",
+ "property.binding",
+ "selection.scope",
+ "component.inspect"
+ ],
+ "projectId": "237",
+ "focused": {
+ "position": [
+ 11.153569378931522,
+ 8.526809324389628,
+ 13.922975975299929
+ ],
+ "target": [
+ 0.01518877997232826,
+ 2.4006999949620704,
+ 2.266009371254541e-7
+ ],
+ "distance": 18.853169271563605
+ },
+ "rotated": {
+ "position": [
+ 1.7511663652214757,
+ 6.181727848275404,
+ 18.38837160033089
+ ],
+ "target": [
+ 0.01518877997232826,
+ 2.4006999949620704,
+ 2.266009371254541e-7
+ ],
+ "distance": 18.853169271563594
+ },
+ "panned": {
+ "position": [
+ -5.273341581654527,
+ 5.082450786888863,
+ 17.990688935160133
+ ],
+ "target": [
+ -0.5866856170824521,
+ 2.73698783970429,
+ -0.11942014831462
+ ],
+ "distance": 18.85316927156359
+ },
+ "focusTargetScreen": [
+ -5.177525136316571e-19,
+ -1.227265217497261e-16,
+ 0.9980999049952497
+ ],
+ "panelModes": [
+ "timeline",
+ "blueprint",
+ "curve",
+ "console"
+ ],
+ "logCount": 8,
+ "stats": {
+ "objects": "98 节点 / 66 网格",
+ "triangles": "1,438",
+ "fps": "54"
+ },
+ "scope": "真实鼠标镜头操作;聚焦只验证相机目标;真实30秒自动保存,无测试加速定时器;统计内存估值未当作性能结论。"
+ }
+}
\ No newline at end of file
diff --git a/reports/model-editor-closure-20260906/evidence/51-gizmos.json b/reports/model-editor-closure-20260906/evidence/51-gizmos.json
new file mode 100644
index 0000000..68e7815
--- /dev/null
+++ b/reports/model-editor-closure-20260906/evidence/51-gizmos.json
@@ -0,0 +1,72 @@
+{
+ "time": "2026-09-06T08:22:38.815Z",
+ "data": {
+ "status": "PASS",
+ "kind": "e2e+runtime-readback",
+ "testTitle": "真实三维移动、旋转、缩放手柄拖动及撤销重做保存重开",
+ "featureIds": [
+ "view.move",
+ "view.rotate",
+ "view.scale"
+ ],
+ "projectId": "233",
+ "changes": [
+ {
+ "mode": "translate",
+ "before": [
+ 0,
+ 0,
+ 0
+ ],
+ "after": [
+ 0.7257266613731312,
+ 0,
+ 0
+ ]
+ },
+ {
+ "mode": "rotate",
+ "before": [
+ 0,
+ 0,
+ 0
+ ],
+ "after": [
+ -1.194547194225429,
+ 0,
+ 0
+ ]
+ },
+ {
+ "mode": "scale",
+ "before": [
+ 1,
+ 1,
+ 1
+ ],
+ "after": [
+ 0.30167729043770014,
+ 1,
+ 1
+ ]
+ }
+ ],
+ "reopened": {
+ "position": [
+ 0.7257266613731312,
+ 0,
+ 0
+ ],
+ "rotation": [
+ -1.194547194225429,
+ 0,
+ 0
+ ],
+ "scale": [
+ 0.30167729043770014,
+ 1,
+ 1
+ ]
+ }
+ }
+}
\ No newline at end of file
diff --git a/reports/model-editor-closure-20260906/evidence/52-leave.json b/reports/model-editor-closure-20260906/evidence/52-leave.json
new file mode 100644
index 0000000..b0225ef
--- /dev/null
+++ b/reports/model-editor-closure-20260906/evidence/52-leave.json
@@ -0,0 +1,15 @@
+{
+ "time": "2026-09-06T08:22:48.155Z",
+ "data": {
+ "status": "PASS",
+ "kind": "e2e+api",
+ "testTitle": "未保存离开:关闭确认保留工作、保存失败留在原页、重试保存后离开",
+ "featureIds": [
+ "project.leave"
+ ],
+ "projectId": "234",
+ "closeKeptDraft": true,
+ "failureKeptDraft": true,
+ "retriedSavePersisted": true
+ }
+}
\ No newline at end of file
diff --git a/reports/model-editor-closure-20260906/evidence/60-project-recovery.json b/reports/model-editor-closure-20260906/evidence/60-project-recovery.json
new file mode 100644
index 0000000..3eeb90a
--- /dev/null
+++ b/reports/model-editor-closure-20260906/evidence/60-project-recovery.json
@@ -0,0 +1,17 @@
+{
+ "time": "2026-09-06T08:24:07.184Z",
+ "data": {
+ "status": "PASS",
+ "kind": "e2e+api",
+ "testTitle": "真实未保存稿同版本恢复并显式保存;旧版本恢复稿下载隔离保留",
+ "featureIds": [
+ "project.recovery"
+ ],
+ "projectId": "238",
+ "sameVersion": 1,
+ "explicitSaveVersion": 2,
+ "concurrentVersion": 3,
+ "retainedFilename": "recovery-238-isolated.json",
+ "scope": "通过真实beforeunload创建未保存稿;同版恢复后仅显式保存落库;旧版只下载隔离,不自动覆盖服务端。丢弃/跨标签竞态另由既有恢复合同测试覆盖。"
+ }
+}
\ No newline at end of file
diff --git a/reports/model-editor-closure-20260906/evidence/63-loading-publish-preflight.json b/reports/model-editor-closure-20260906/evidence/63-loading-publish-preflight.json
new file mode 100644
index 0000000..076fe6b
--- /dev/null
+++ b/reports/model-editor-closure-20260906/evidence/63-loading-publish-preflight.json
@@ -0,0 +1,16 @@
+{
+ "time": "2026-09-06T08:24:17.025Z",
+ "data": {
+ "status": "PASS",
+ "kind": "e2e+api",
+ "testTitle": "受控 GLB 下载失败阻止编辑,重试成功;发布预检开关无写入且拒绝错误资源 JSON",
+ "featureIds": [
+ "resource.loading",
+ "publish.preflight"
+ ],
+ "projectId": "239",
+ "failedDownloads": 1,
+ "retainedVersion": 1,
+ "scope": "真实受控下载定向503→中性失败与禁止编辑→解除故障后UI重试成功;预检面板开关不写项目,错误资源JSON在替换当前模型之前被拒绝。服务端发布时code/URI一致性拒绝由ContentProjectServiceTest合同回归覆盖,未把面板绿标代替服务端校验。"
+ }
+}
\ No newline at end of file
diff --git a/reports/model-editor-closure-20260906/evidence/new-project.json b/reports/model-editor-closure-20260906/evidence/new-project.json
new file mode 100644
index 0000000..cedfe63
--- /dev/null
+++ b/reports/model-editor-closure-20260906/evidence/new-project.json
@@ -0,0 +1,17 @@
+{
+ "time": "2026-09-06T08:22:24.723Z",
+ "data": {
+ "status": "PASS",
+ "kind": "e2e+api",
+ "testTitle": "新建工程取消及失败保留输入;成功创建新ID且不覆盖当前模型",
+ "featureIds": [
+ "project.new"
+ ],
+ "oldId": "168",
+ "newId": "232",
+ "cancelPreserved": true,
+ "failureRetainedName": true,
+ "originalVersion": 17,
+ "sourceUnchanged": true
+ }
+}
\ No newline at end of file
diff --git a/reports/model-editor-closure-20260906/evidence/permissions.json b/reports/model-editor-closure-20260906/evidence/permissions.json
new file mode 100644
index 0000000..46d60b9
--- /dev/null
+++ b/reports/model-editor-closure-20260906/evidence/permissions.json
@@ -0,0 +1,18 @@
+{
+ "time": "2026-09-06T08:22:29.489Z",
+ "data": {
+ "status": "PASS",
+ "kind": "e2e+api",
+ "testTitle": "管理员只读权限:编辑、新建、导入、保存发布均不可越权",
+ "featureIds": [
+ "project.permissions"
+ ],
+ "scope": "管理员只读UI、真实键盘搜索、原生片段切换与Enter播放/暂停、预览无内容写请求及创建403;纯publisher无需update的发布流程由独立权限合同回归补充。",
+ "readonlyUi": true,
+ "readonlySearch": true,
+ "nativeClipChanged": true,
+ "keyboardPreview": true,
+ "previewWriteRequests": 0,
+ "createStatus": 403
+ }
+}
\ No newline at end of file
diff --git a/reports/model-editor-closure-20260906/evidence/properties.json b/reports/model-editor-closure-20260906/evidence/properties.json
new file mode 100644
index 0000000..8e0446b
--- /dev/null
+++ b/reports/model-editor-closure-20260906/evidence/properties.json
@@ -0,0 +1,56 @@
+{
+ "time": "2026-09-06T08:21:58.738Z",
+ "data": {
+ "status": "PASS",
+ "kind": "e2e+api",
+ "testTitle": "对象名称、变换、PBR、属性与扩展标记保存重开;撤销重做",
+ "featureIds": [
+ "property.name",
+ "property.position",
+ "property.rotation",
+ "property.scale",
+ "property.color",
+ "property.metalness",
+ "property.roughness",
+ "property.opacity",
+ "property.code",
+ "property.status",
+ "component.tag",
+ "project.undo",
+ "project.redo",
+ "project.save"
+ ],
+ "scope": "真实属性输入/材质参数/标签、保存与刷新;变换只验数值输入,不代替三维 gizmo 拖动。",
+ "state": {
+ "id": "model-imported-model_mto6v5w6_09-fox-26",
+ "position": [
+ 1.25,
+ 0,
+ 0
+ ],
+ "rotation": [
+ 0,
+ 0.2617993877991494,
+ 0
+ ],
+ "scale": [
+ 1,
+ 1,
+ 1.1
+ ],
+ "customProperties": {
+ "resourceCode": "CLOSURE-FOX-001",
+ "operatingStatus": "SERVICE"
+ },
+ "customComponents": [
+ "扩展标记 1",
+ "扩展标记 2",
+ "扩展标记 3",
+ "扩展标记 4",
+ "扩展标记 5"
+ ]
+ },
+ "serverVersion": 15,
+ "persisted": true
+ }
+}
\ No newline at end of file
diff --git a/reports/model-editor-closure-20260906/evidence/truck-blueprint-events.json b/reports/model-editor-closure-20260906/evidence/truck-blueprint-events.json
new file mode 100644
index 0000000..927b3a2
--- /dev/null
+++ b/reports/model-editor-closure-20260906/evidence/truck-blueprint-events.json
@@ -0,0 +1,327 @@
+{
+ "time": "2026-09-06T08:21:50.227Z",
+ "data": {
+ "status": "PASS",
+ "kind": "e2e",
+ "testTitle": "已保存蓝图:条件出口与目标匹配 runtime 检查,真实模型单击/双击/进入预览自动触发",
+ "featureIds": [
+ "blueprint.click",
+ "blueprint.double-click",
+ "blueprint.auto",
+ "view.preview"
+ ],
+ "projectId": "169",
+ "runtimeSupplement": {
+ "status": "PASS",
+ "kind": "runtime",
+ "featureIds": [
+ "blueprint.condition"
+ ],
+ "branches": {
+ "falseBranch": {
+ "result": true,
+ "visitedFinish": false,
+ "logs": [
+ {
+ "time": "16:21:41",
+ "message": "开始执行 · 闭环-条件判断",
+ "level": "run",
+ "nodeId": "node-mtpjlz9t-p124m"
+ },
+ {
+ "time": "16:21:41",
+ "message": "执行节点:闭环-条件判断",
+ "level": "run",
+ "nodeId": "node-mtpjlz9t-p124m"
+ },
+ {
+ "time": "16:21:41",
+ "message": "条件结果:否 / FALSE",
+ "level": "warning",
+ "nodeId": "node-mtpjlz9t-p124m"
+ },
+ {
+ "time": "16:21:41",
+ "message": "节点“闭环-条件判断”没有后续连线,流程结束",
+ "level": "warning",
+ "nodeId": "node-mtpjlz9t-p124m"
+ },
+ {
+ "time": "16:21:41",
+ "message": "蓝图执行完成 · 1 个节点",
+ "level": "success",
+ "nodeId": null
+ }
+ ]
+ },
+ "trueBranch": {
+ "result": true,
+ "visitedFinish": true,
+ "logs": [
+ {
+ "time": "16:21:41",
+ "message": "开始执行 · 闭环-条件判断",
+ "level": "run",
+ "nodeId": "node-mtpjlz9t-p124m"
+ },
+ {
+ "time": "16:21:41",
+ "message": "执行节点:闭环-条件判断",
+ "level": "run",
+ "nodeId": "node-mtpjlz9t-p124m"
+ },
+ {
+ "time": "16:21:41",
+ "message": "条件结果:是 / TRUE",
+ "level": "success",
+ "nodeId": "node-mtpjlz9t-p124m"
+ },
+ {
+ "time": "16:21:41",
+ "message": "执行节点:闭环-流程完成",
+ "level": "run",
+ "nodeId": "node-mtpjlznf-cxlzk"
+ },
+ {
+ "time": "16:21:41",
+ "message": "蓝图执行完成 · 2 个节点",
+ "level": "success",
+ "nodeId": null
+ }
+ ]
+ }
+ },
+ "mismatch": {
+ "result": false,
+ "started": false
+ }
+ },
+ "browserEvents": [
+ {
+ "event": "click",
+ "starts": 1,
+ "finished": true,
+ "source": "真实 renderer canvas 指针事件",
+ "logs": [
+ {
+ "time": "16:21:42",
+ "message": "运行状态已重置",
+ "level": "system",
+ "nodeId": null
+ },
+ {
+ "time": "16:21:42",
+ "message": "开始执行 · 闭环-点击触发",
+ "level": "run",
+ "nodeId": "node-mtpjlx7c-sgcyx"
+ },
+ {
+ "time": "16:21:42",
+ "message": "执行节点:闭环-点击触发",
+ "level": "run",
+ "nodeId": "node-mtpjlx7c-sgcyx"
+ },
+ {
+ "time": "16:21:42",
+ "message": "执行节点:闭环-播放时间轴",
+ "level": "run",
+ "nodeId": "node-mtpjlxj9-y3g3v"
+ },
+ {
+ "time": "16:21:42",
+ "message": "执行节点:闭环-等待",
+ "level": "run",
+ "nodeId": "node-mtpjlxxe-ond97"
+ },
+ {
+ "time": "16:21:42",
+ "message": "执行节点:闭环-高亮部件",
+ "level": "run",
+ "nodeId": "node-mtpjlyci-4c5oi"
+ },
+ {
+ "time": "16:21:42",
+ "message": "执行节点:闭环-操作提示",
+ "level": "run",
+ "nodeId": "node-mtpjlyuw-164xj"
+ },
+ {
+ "time": "16:21:43",
+ "message": "执行节点:闭环-条件判断",
+ "level": "run",
+ "nodeId": "node-mtpjlz9t-p124m"
+ },
+ {
+ "time": "16:21:43",
+ "message": "条件结果:是 / TRUE",
+ "level": "success",
+ "nodeId": "node-mtpjlz9t-p124m"
+ },
+ {
+ "time": "16:21:43",
+ "message": "执行节点:闭环-流程完成",
+ "level": "run",
+ "nodeId": "node-mtpjlznf-cxlzk"
+ },
+ {
+ "time": "16:21:43",
+ "message": "蓝图执行完成 · 7 个节点",
+ "level": "success",
+ "nodeId": null
+ }
+ ]
+ },
+ {
+ "event": "double-click",
+ "starts": 1,
+ "finished": true,
+ "source": "真实 renderer canvas 指针事件",
+ "logs": [
+ {
+ "time": "16:21:43",
+ "message": "运行状态已重置",
+ "level": "system",
+ "nodeId": null
+ },
+ {
+ "time": "16:21:44",
+ "message": "开始执行 · 闭环-点击触发",
+ "level": "run",
+ "nodeId": "node-mtpjlx7c-sgcyx"
+ },
+ {
+ "time": "16:21:44",
+ "message": "执行节点:闭环-点击触发",
+ "level": "run",
+ "nodeId": "node-mtpjlx7c-sgcyx"
+ },
+ {
+ "time": "16:21:44",
+ "message": "执行节点:闭环-播放时间轴",
+ "level": "run",
+ "nodeId": "node-mtpjlxj9-y3g3v"
+ },
+ {
+ "time": "16:21:44",
+ "message": "执行节点:闭环-等待",
+ "level": "run",
+ "nodeId": "node-mtpjlxxe-ond97"
+ },
+ {
+ "time": "16:21:44",
+ "message": "执行节点:闭环-高亮部件",
+ "level": "run",
+ "nodeId": "node-mtpjlyci-4c5oi"
+ },
+ {
+ "time": "16:21:44",
+ "message": "执行节点:闭环-操作提示",
+ "level": "run",
+ "nodeId": "node-mtpjlyuw-164xj"
+ },
+ {
+ "time": "16:21:45",
+ "message": "执行节点:闭环-条件判断",
+ "level": "run",
+ "nodeId": "node-mtpjlz9t-p124m"
+ },
+ {
+ "time": "16:21:45",
+ "message": "条件结果:是 / TRUE",
+ "level": "success",
+ "nodeId": "node-mtpjlz9t-p124m"
+ },
+ {
+ "time": "16:21:45",
+ "message": "执行节点:闭环-流程完成",
+ "level": "run",
+ "nodeId": "node-mtpjlznf-cxlzk"
+ },
+ {
+ "time": "16:21:45",
+ "message": "蓝图执行完成 · 7 个节点",
+ "level": "success",
+ "nodeId": null
+ }
+ ]
+ },
+ {
+ "event": "auto",
+ "starts": 1,
+ "finished": true,
+ "source": "真实点击进入预览",
+ "logs": [
+ {
+ "time": "16:21:45",
+ "message": "运行状态已重置",
+ "level": "system",
+ "nodeId": null
+ },
+ {
+ "time": "16:21:45",
+ "message": "开始执行 · 闭环-点击触发",
+ "level": "run",
+ "nodeId": "node-mtpjlx7c-sgcyx"
+ },
+ {
+ "time": "16:21:45",
+ "message": "执行节点:闭环-点击触发",
+ "level": "run",
+ "nodeId": "node-mtpjlx7c-sgcyx"
+ },
+ {
+ "time": "16:21:45",
+ "message": "执行节点:闭环-播放时间轴",
+ "level": "run",
+ "nodeId": "node-mtpjlxj9-y3g3v"
+ },
+ {
+ "time": "16:21:45",
+ "message": "执行节点:闭环-等待",
+ "level": "run",
+ "nodeId": "node-mtpjlxxe-ond97"
+ },
+ {
+ "time": "16:21:45",
+ "message": "执行节点:闭环-高亮部件",
+ "level": "run",
+ "nodeId": "node-mtpjlyci-4c5oi"
+ },
+ {
+ "time": "16:21:46",
+ "message": "执行节点:闭环-操作提示",
+ "level": "run",
+ "nodeId": "node-mtpjlyuw-164xj"
+ },
+ {
+ "time": "16:21:46",
+ "message": "执行节点:闭环-条件判断",
+ "level": "run",
+ "nodeId": "node-mtpjlz9t-p124m"
+ },
+ {
+ "time": "16:21:46",
+ "message": "条件结果:是 / TRUE",
+ "level": "success",
+ "nodeId": "node-mtpjlz9t-p124m"
+ },
+ {
+ "time": "16:21:46",
+ "message": "执行节点:闭环-流程完成",
+ "level": "run",
+ "nodeId": "node-mtpjlznf-cxlzk"
+ },
+ {
+ "time": "16:21:46",
+ "message": "蓝图执行完成 · 7 个节点",
+ "level": "success",
+ "nodeId": null
+ }
+ ]
+ }
+ ],
+ "persistedNodeCount": 7,
+ "persistedEdgeCount": 6,
+ "scope": "执行使用正式模型编辑器的真实模型/时间轴/高亮/提示 handler;不等同于学员端任务下发。"
+ }
+}
\ No newline at end of file
diff --git a/reports/model-editor-closure-20260906/evidence/truck-blueprint-tool-supplement.json b/reports/model-editor-closure-20260906/evidence/truck-blueprint-tool-supplement.json
new file mode 100644
index 0000000..0b10bd9
--- /dev/null
+++ b/reports/model-editor-closure-20260906/evidence/truck-blueprint-tool-supplement.json
@@ -0,0 +1,697 @@
+{
+ "time": "2026-09-06T08:21:23.003Z",
+ "data": {
+ "status": "PASS",
+ "kind": "e2e+runtime-read",
+ "testTitle": "补充时间轴与蓝图工具:刻度/按键/记录/分量、基准匹配、节点搜索拖入与画布平移",
+ "featureIds": [
+ "timeline.seek",
+ "timeline.record",
+ "timeline.baseline",
+ "curve.axis",
+ "blueprint.palette",
+ "blueprint.add",
+ "blueprint.canvas"
+ ],
+ "projectIds": [
+ "230",
+ "231"
+ ],
+ "pose": {
+ "time": 0.9666666666666666,
+ "value": [
+ 2.4166666666666665,
+ -0.9666666666666666,
+ 0
+ ],
+ "actual": [
+ 2.4166666666666665,
+ -0.9666666666666666,
+ 0
+ ],
+ "base": [
+ 0,
+ 0,
+ 0
+ ]
+ },
+ "recorded": {
+ "id": "key-u60x1lh",
+ "time": 0.9666666666666666,
+ "value": [
+ 2.4166666666666665,
+ -0.9666666666666666,
+ 0
+ ],
+ "easing": "easeInOut"
+ },
+ "curveAxis": {
+ "xAndYPathsDiffer": true,
+ "values": [
+ 5,
+ -2,
+ 0
+ ]
+ },
+ "noMatchPreserved": true,
+ "baseline": {
+ "name": "闭环工具补充-基准匹配-mtpjlmiy",
+ "timeline": {
+ "version": 1,
+ "duration": 12,
+ "currentTime": 0,
+ "loop": true,
+ "zoom": 1,
+ "tracks": [
+ {
+ "id": "bridge-front-outrigger-drop",
+ "name": "前支腿下落支撑",
+ "targetId": "bridge-front-outrigger",
+ "property": "position",
+ "color": "#ffd166",
+ "muted": false,
+ "keyframes": [
+ {
+ "id": "bfo-k0",
+ "time": 0,
+ "value": [
+ 0,
+ 0,
+ 0
+ ],
+ "easing": "easeInOut"
+ },
+ {
+ "id": "bfo-k1",
+ "time": 2.4,
+ "value": [
+ 0,
+ -0.45,
+ 0
+ ],
+ "easing": "easeInOut"
+ },
+ {
+ "id": "bfo-k2",
+ "time": 10,
+ "value": [
+ 0,
+ -0.45,
+ 0
+ ],
+ "easing": "linear"
+ },
+ {
+ "id": "bfo-k3",
+ "time": 12,
+ "value": [
+ 0,
+ 0,
+ 0
+ ],
+ "easing": "easeInOut"
+ }
+ ]
+ },
+ {
+ "id": "bridge-rear-outrigger-drop",
+ "name": "后支腿下落支撑",
+ "targetId": "bridge-rear-outrigger",
+ "property": "position",
+ "color": "#f4a261",
+ "muted": false,
+ "keyframes": [
+ {
+ "id": "bro-k0",
+ "time": 0,
+ "value": [
+ 0,
+ 0,
+ 0
+ ],
+ "easing": "easeInOut"
+ },
+ {
+ "id": "bro-k1",
+ "time": 2.2,
+ "value": [
+ 0,
+ -0.19,
+ 0
+ ],
+ "easing": "easeInOut"
+ },
+ {
+ "id": "bro-k2",
+ "time": 10,
+ "value": [
+ 0,
+ -0.19,
+ 0
+ ],
+ "easing": "linear"
+ },
+ {
+ "id": "bro-k3",
+ "time": 12,
+ "value": [
+ 0,
+ 0,
+ 0
+ ],
+ "easing": "easeInOut"
+ }
+ ]
+ },
+ {
+ "id": "bridge-rear-support-drop",
+ "name": "后支座下落支撑",
+ "targetId": "bridge-rear-support",
+ "property": "position",
+ "color": "#e76f51",
+ "muted": false,
+ "keyframes": [
+ {
+ "id": "brs-k0",
+ "time": 0,
+ "value": [
+ 0,
+ 0,
+ 0
+ ],
+ "easing": "easeInOut"
+ },
+ {
+ "id": "brs-k1",
+ "time": 0.4,
+ "value": [
+ 0,
+ 0,
+ 0
+ ],
+ "easing": "easeInOut"
+ },
+ {
+ "id": "brs-k2",
+ "time": 2.6,
+ "value": [
+ 0,
+ -0.25,
+ 0
+ ],
+ "easing": "easeInOut"
+ },
+ {
+ "id": "brs-k3",
+ "time": 10.2,
+ "value": [
+ 0,
+ -0.25,
+ 0
+ ],
+ "easing": "linear"
+ },
+ {
+ "id": "brs-k4",
+ "time": 12,
+ "value": [
+ 0,
+ 0,
+ 0
+ ],
+ "easing": "easeInOut"
+ }
+ ]
+ },
+ {
+ "id": "bridge-gantry-slew",
+ "name": "回转门架回转",
+ "targetId": "bridge-gantry",
+ "property": "rotation.y",
+ "color": "#2a9d8f",
+ "muted": false,
+ "keyframes": [
+ {
+ "id": "bgs-k0",
+ "time": 0,
+ "value": 0,
+ "easing": "easeInOut"
+ },
+ {
+ "id": "bgs-k1",
+ "time": 2.2,
+ "value": 0,
+ "easing": "easeInOut"
+ },
+ {
+ "id": "bgs-k2",
+ "time": 4.8,
+ "value": 0.24,
+ "easing": "easeInOut"
+ },
+ {
+ "id": "bgs-k3",
+ "time": 8.6,
+ "value": 0.24,
+ "easing": "linear"
+ },
+ {
+ "id": "bgs-k4",
+ "time": 11.4,
+ "value": 0,
+ "easing": "easeInOut"
+ },
+ {
+ "id": "bgs-k5",
+ "time": 12,
+ "value": 0,
+ "easing": "easeInOut"
+ }
+ ]
+ },
+ {
+ "id": "bridge-gantry-upper-luffing",
+ "name": "门架上臂变幅",
+ "targetId": "bridge-gantry-upper",
+ "property": "rotation.x",
+ "color": "#8a68e8",
+ "muted": false,
+ "keyframes": [
+ {
+ "id": "bgu-k0",
+ "time": 0,
+ "value": 0,
+ "easing": "easeInOut"
+ },
+ {
+ "id": "bgu-k1",
+ "time": 3,
+ "value": 0,
+ "easing": "easeInOut"
+ },
+ {
+ "id": "bgu-k2",
+ "time": 5.4,
+ "value": -0.22,
+ "easing": "easeInOut"
+ },
+ {
+ "id": "bgu-k3",
+ "time": 8.8,
+ "value": -0.22,
+ "easing": "linear"
+ },
+ {
+ "id": "bgu-k4",
+ "time": 11.2,
+ "value": 0,
+ "easing": "easeInOut"
+ },
+ {
+ "id": "bgu-k5",
+ "time": 12,
+ "value": 0,
+ "easing": "easeInOut"
+ }
+ ]
+ },
+ {
+ "id": "bridge-erection-frame-luffing",
+ "name": "后架设架起落(抬头 → 落桥)",
+ "targetId": "bridge-erection-frame",
+ "property": "rotation.x",
+ "color": "#4cc9f0",
+ "muted": false,
+ "keyframes": [
+ {
+ "id": "bef-k0",
+ "time": 0,
+ "value": 0,
+ "easing": "easeInOut"
+ },
+ {
+ "id": "bef-k1",
+ "time": 3.6,
+ "value": 0,
+ "easing": "easeInOut"
+ },
+ {
+ "id": "bef-k2",
+ "time": 5,
+ "value": -0.07,
+ "easing": "easeInOut"
+ },
+ {
+ "id": "bef-k3",
+ "time": 7.8,
+ "value": 0.2,
+ "easing": "easeInOut"
+ },
+ {
+ "id": "bef-k4",
+ "time": 9.8,
+ "value": 0.2,
+ "easing": "linear"
+ },
+ {
+ "id": "bef-k5",
+ "time": 11.6,
+ "value": 0,
+ "easing": "easeInOut"
+ },
+ {
+ "id": "bef-k6",
+ "time": 12,
+ "value": 0,
+ "easing": "easeInOut"
+ }
+ ]
+ },
+ {
+ "id": "bridge-luffing-cylinder-follow",
+ "name": "变幅缸随动",
+ "targetId": "bridge-luffing-cylinder",
+ "property": "rotation.x",
+ "color": "#90be6d",
+ "muted": false,
+ "keyframes": [
+ {
+ "id": "blc-k0",
+ "time": 0,
+ "value": 0,
+ "easing": "easeInOut"
+ },
+ {
+ "id": "blc-k1",
+ "time": 3.6,
+ "value": 0,
+ "easing": "easeInOut"
+ },
+ {
+ "id": "blc-k2",
+ "time": 5,
+ "value": -0.03,
+ "easing": "easeInOut"
+ },
+ {
+ "id": "blc-k3",
+ "time": 7.8,
+ "value": 0.09,
+ "easing": "easeInOut"
+ },
+ {
+ "id": "blc-k4",
+ "time": 9.8,
+ "value": 0.09,
+ "easing": "linear"
+ },
+ {
+ "id": "blc-k5",
+ "time": 11.6,
+ "value": 0,
+ "easing": "easeInOut"
+ },
+ {
+ "id": "blc-k6",
+ "time": 12,
+ "value": 0,
+ "easing": "easeInOut"
+ }
+ ]
+ },
+ {
+ "id": "bridge-carriage-travel",
+ "name": "移动架沿滑道后行",
+ "targetId": "bridge-carriage",
+ "property": "position",
+ "color": "#32b8e6",
+ "muted": false,
+ "keyframes": [
+ {
+ "id": "bct-k0",
+ "time": 0,
+ "value": [
+ 0,
+ 0,
+ 0
+ ],
+ "easing": "easeInOut"
+ },
+ {
+ "id": "bct-k1",
+ "time": 5.4,
+ "value": [
+ 0,
+ 0,
+ 0
+ ],
+ "easing": "easeInOut"
+ },
+ {
+ "id": "bct-k2",
+ "time": 8.4,
+ "value": [
+ 0,
+ 0,
+ -0.42
+ ],
+ "easing": "easeInOut"
+ },
+ {
+ "id": "bct-k3",
+ "time": 10,
+ "value": [
+ 0,
+ 0,
+ -0.42
+ ],
+ "easing": "linear"
+ },
+ {
+ "id": "bct-k4",
+ "time": 12,
+ "value": [
+ 0,
+ 0,
+ 0
+ ],
+ "easing": "easeInOut"
+ }
+ ]
+ },
+ {
+ "id": "bridge-launch-pinion-drive",
+ "name": "导梁销齿轮驱动",
+ "targetId": "bridge-launch-pinion",
+ "property": "rotation.x",
+ "color": "#f9c74f",
+ "muted": false,
+ "keyframes": [
+ {
+ "id": "blp-k0",
+ "time": 0,
+ "value": 0,
+ "easing": "linear"
+ },
+ {
+ "id": "blp-k1",
+ "time": 5.4,
+ "value": 0,
+ "easing": "linear"
+ },
+ {
+ "id": "blp-k2",
+ "time": 8.4,
+ "value": 5.2,
+ "easing": "linear"
+ },
+ {
+ "id": "blp-k3",
+ "time": 10,
+ "value": 5.2,
+ "easing": "linear"
+ },
+ {
+ "id": "blp-k4",
+ "time": 12,
+ "value": 0,
+ "easing": "linear"
+ }
+ ]
+ },
+ {
+ "id": "bridge-front-cradle-luffing",
+ "name": "前托架抬头",
+ "targetId": "bridge-front-cradle",
+ "property": "rotation.x",
+ "color": "#ff9f2f",
+ "muted": false,
+ "keyframes": [
+ {
+ "id": "bfc-k0",
+ "time": 0,
+ "value": 0,
+ "easing": "easeInOut"
+ },
+ {
+ "id": "bfc-k1",
+ "time": 6.8,
+ "value": 0,
+ "easing": "easeInOut"
+ },
+ {
+ "id": "bfc-k2",
+ "time": 9,
+ "value": 0.12,
+ "easing": "easeInOut"
+ },
+ {
+ "id": "bfc-k3",
+ "time": 10.4,
+ "value": 0.12,
+ "easing": "linear"
+ },
+ {
+ "id": "bfc-k4",
+ "time": 11.8,
+ "value": 0,
+ "easing": "easeInOut"
+ },
+ {
+ "id": "bfc-k5",
+ "time": 12,
+ "value": 0,
+ "easing": "easeInOut"
+ }
+ ]
+ },
+ {
+ "id": "bridge-front-pusher-extend",
+ "name": "前推送装置送桥",
+ "targetId": "bridge-front-pusher",
+ "property": "position",
+ "color": "#ef476f",
+ "muted": false,
+ "keyframes": [
+ {
+ "id": "bfp-k0",
+ "time": 0,
+ "value": [
+ 0,
+ 0,
+ 0
+ ],
+ "easing": "easeInOut"
+ },
+ {
+ "id": "bfp-k1",
+ "time": 7.2,
+ "value": [
+ 0,
+ 0,
+ 0
+ ],
+ "easing": "easeInOut"
+ },
+ {
+ "id": "bfp-k2",
+ "time": 9.4,
+ "value": [
+ 0,
+ 0,
+ 0.34
+ ],
+ "easing": "easeInOut"
+ },
+ {
+ "id": "bfp-k3",
+ "time": 10.6,
+ "value": [
+ 0,
+ 0,
+ 0.34
+ ],
+ "easing": "linear"
+ },
+ {
+ "id": "bfp-k4",
+ "time": 12,
+ "value": [
+ 0,
+ 0,
+ 0
+ ],
+ "easing": "easeInOut"
+ }
+ ]
+ },
+ {
+ "id": "bridge-feed-pinion-drive",
+ "name": "前推送销齿轮驱动",
+ "targetId": "bridge-feed-pinion",
+ "property": "rotation.x",
+ "color": "#c77dff",
+ "muted": false,
+ "keyframes": [
+ {
+ "id": "bfd-k0",
+ "time": 0,
+ "value": 0,
+ "easing": "linear"
+ },
+ {
+ "id": "bfd-k1",
+ "time": 7.2,
+ "value": 0,
+ "easing": "linear"
+ },
+ {
+ "id": "bfd-k2",
+ "time": 9.4,
+ "value": 4,
+ "easing": "linear"
+ },
+ {
+ "id": "bfd-k3",
+ "time": 10.6,
+ "value": 4,
+ "easing": "linear"
+ },
+ {
+ "id": "bfd-k4",
+ "time": 12,
+ "value": 0,
+ "easing": "linear"
+ }
+ ]
+ }
+ ]
+ },
+ "resolved": true
+ },
+ "addedNode": {
+ "id": "node-mtpjlj3c-n13gc",
+ "type": "message",
+ "title": "操作提示",
+ "x": 440,
+ "y": 160,
+ "params": {
+ "title": "操作提示",
+ "text": "请观察铲斗与动臂的联动过程",
+ "level": "info"
+ }
+ },
+ "draggedNode": {
+ "id": "node-mtpjlj9e-sko2s",
+ "type": "wait",
+ "title": "等待",
+ "x": -40,
+ "y": -170,
+ "params": {
+ "duration": 600
+ }
+ },
+ "canvasPreservedGraph": true,
+ "scope": "真实模型编辑参数/基准轨道生成验收,不构成装备操作或维修指导。桥梁原工程94仅GET和copy。"
+ }
+}
\ No newline at end of file
diff --git a/reports/model-editor-closure-20260906/evidence/truck-blueprint-ui.json b/reports/model-editor-closure-20260906/evidence/truck-blueprint-ui.json
new file mode 100644
index 0000000..e0c0575
--- /dev/null
+++ b/reports/model-editor-closure-20260906/evidence/truck-blueprint-ui.json
@@ -0,0 +1,259 @@
+{
+ "time": "2026-09-06T08:21:37.144Z",
+ "data": {
+ "status": "PASS",
+ "kind": "e2e+runtime-read",
+ "testTitle": "蓝图真实节点库与端口拖线:七种参数、内部边复制/剪切、撤销重做及保存",
+ "featureIds": [
+ "blueprint.parameters",
+ "blueprint.move",
+ "blueprint.connect",
+ "blueprint.delete",
+ "blueprint.copy",
+ "blueprint.run",
+ "blueprint.stop",
+ "blueprint.reset",
+ "blueprint.timeline",
+ "blueprint.wait",
+ "blueprint.highlight",
+ "blueprint.message",
+ "blueprint.finish",
+ "project.undo",
+ "project.redo"
+ ],
+ "scope": "节点库点选添加及实际拖动/接线/执行;不把点选当成节点拖入,不把适应画布当成鼠标平移缩放全部已验。",
+ "projectId": "169",
+ "graph": {
+ "nodes": [
+ {
+ "id": "node-mtpjlx7c-sgcyx",
+ "type": "trigger-click",
+ "title": "闭环-点击触发",
+ "x": 160,
+ "y": -120,
+ "params": {
+ "event": "click",
+ "target": "model-imported-model_mto6wi6k_08-cesiummilktruck-3"
+ }
+ },
+ {
+ "id": "node-mtpjlxj9-y3g3v",
+ "type": "play-timeline",
+ "title": "闭环-播放时间轴",
+ "x": 420,
+ "y": -120,
+ "params": {
+ "clip": "牛奶卡车曲线联动",
+ "from": 8,
+ "speed": 2,
+ "waitForEnd": true
+ }
+ },
+ {
+ "id": "node-mtpjlxxe-ond97",
+ "type": "wait",
+ "title": "闭环-等待",
+ "x": 680,
+ "y": -120,
+ "params": {
+ "duration": 100
+ }
+ },
+ {
+ "id": "node-mtpjlyci-4c5oi",
+ "type": "highlight",
+ "title": "闭环-高亮部件",
+ "x": 160,
+ "y": 70,
+ "params": {
+ "target": "model-imported-model_mto6wi6k_08-cesiummilktruck-3",
+ "color": "#ff8800",
+ "pulse": false
+ }
+ },
+ {
+ "id": "node-mtpjlyuw-164xj",
+ "type": "message",
+ "title": "闭环-操作提示",
+ "x": 420,
+ "y": 70,
+ "params": {
+ "title": "卡车模型闭环提示",
+ "text": "检查真实目标高亮、时间轴完成和条件出口。",
+ "level": "success"
+ }
+ },
+ {
+ "id": "node-mtpjlz9t-p124m",
+ "type": "condition",
+ "title": "闭环-条件判断",
+ "x": 680,
+ "y": 70,
+ "params": {
+ "variable": "timelineComplete",
+ "operator": "==",
+ "value": true
+ }
+ },
+ {
+ "id": "node-mtpjlznf-cxlzk",
+ "type": "finish",
+ "title": "闭环-流程完成",
+ "x": 420,
+ "y": 260,
+ "params": {
+ "result": "success",
+ "text": "牛奶卡车蓝图验证完成"
+ }
+ }
+ ],
+ "edges": [
+ {
+ "id": "edge-mtpjm04u-tt0qb",
+ "from": {
+ "node": "node-mtpjlx7c-sgcyx",
+ "port": "then"
+ },
+ "to": {
+ "node": "node-mtpjlxj9-y3g3v",
+ "port": "in"
+ }
+ },
+ {
+ "id": "edge-mtpjm0bc-mnf7c",
+ "from": {
+ "node": "node-mtpjlxj9-y3g3v",
+ "port": "then"
+ },
+ "to": {
+ "node": "node-mtpjlxxe-ond97",
+ "port": "in"
+ }
+ },
+ {
+ "id": "edge-mtpjm0gx-mu6ys",
+ "from": {
+ "node": "node-mtpjlxxe-ond97",
+ "port": "then"
+ },
+ "to": {
+ "node": "node-mtpjlyci-4c5oi",
+ "port": "in"
+ }
+ },
+ {
+ "id": "edge-mtpjm0nr-y06n7",
+ "from": {
+ "node": "node-mtpjlyci-4c5oi",
+ "port": "then"
+ },
+ "to": {
+ "node": "node-mtpjlyuw-164xj",
+ "port": "in"
+ }
+ },
+ {
+ "id": "edge-mtpjm0ts-px732",
+ "from": {
+ "node": "node-mtpjlyuw-164xj",
+ "port": "then"
+ },
+ "to": {
+ "node": "node-mtpjlz9t-p124m",
+ "port": "in"
+ }
+ },
+ {
+ "id": "edge-mtpjm0zf-sa3h2",
+ "from": {
+ "node": "node-mtpjlz9t-p124m",
+ "port": "true"
+ },
+ "to": {
+ "node": "node-mtpjlznf-cxlzk",
+ "port": "in"
+ }
+ }
+ ],
+ "running": false,
+ "logs": [
+ {
+ "time": "16:21:35",
+ "message": "运行状态已重置",
+ "level": "system",
+ "nodeId": null
+ }
+ ]
+ },
+ "completedLog": [
+ {
+ "time": "16:21:25",
+ "message": "蓝图编辑器就绪 · 7 类节点已加载",
+ "level": "system",
+ "nodeId": null
+ },
+ {
+ "time": "16:21:33",
+ "message": "开始执行 · 闭环-点击触发",
+ "level": "run",
+ "nodeId": "node-mtpjlx7c-sgcyx"
+ },
+ {
+ "time": "16:21:33",
+ "message": "执行节点:闭环-点击触发",
+ "level": "run",
+ "nodeId": "node-mtpjlx7c-sgcyx"
+ },
+ {
+ "time": "16:21:33",
+ "message": "执行节点:闭环-播放时间轴",
+ "level": "run",
+ "nodeId": "node-mtpjlxj9-y3g3v"
+ },
+ {
+ "time": "16:21:33",
+ "message": "执行节点:闭环-等待",
+ "level": "run",
+ "nodeId": "node-mtpjlxxe-ond97"
+ },
+ {
+ "time": "16:21:33",
+ "message": "执行节点:闭环-高亮部件",
+ "level": "run",
+ "nodeId": "node-mtpjlyci-4c5oi"
+ },
+ {
+ "time": "16:21:34",
+ "message": "执行节点:闭环-操作提示",
+ "level": "run",
+ "nodeId": "node-mtpjlyuw-164xj"
+ },
+ {
+ "time": "16:21:35",
+ "message": "执行节点:闭环-条件判断",
+ "level": "run",
+ "nodeId": "node-mtpjlz9t-p124m"
+ },
+ {
+ "time": "16:21:35",
+ "message": "条件结果:是 / TRUE",
+ "level": "success",
+ "nodeId": "node-mtpjlz9t-p124m"
+ },
+ {
+ "time": "16:21:35",
+ "message": "执行节点:闭环-流程完成",
+ "level": "run",
+ "nodeId": "node-mtpjlznf-cxlzk"
+ },
+ {
+ "time": "16:21:35",
+ "message": "蓝图执行完成 · 7 个节点",
+ "level": "success",
+ "nodeId": null
+ }
+ ],
+ "actions": "真实节点库/参数控件/端口拖线/复制剪切粘贴/删除/撤销重做/运行停止重置/保存",
+ "boundaries": "200 步循环保护由独立 Node 回归覆盖;本浏览器流程不注入假图或替换执行 handler。"
+ }
+}
\ No newline at end of file
diff --git a/reports/model-editor-closure-20260906/evidence/truck-timeline-curve.json b/reports/model-editor-closure-20260906/evidence/truck-timeline-curve.json
new file mode 100644
index 0000000..3618fb6
--- /dev/null
+++ b/reports/model-editor-closure-20260906/evidence/truck-timeline-curve.json
@@ -0,0 +1,81 @@
+{
+ "time": "2026-09-06T08:21:02.898Z",
+ "data": {
+ "status": "PASS",
+ "kind": "e2e+runtime-read",
+ "testTitle": "时间轴真实控件:轨道/关键帧/播放与曲线 5 插值,保存后重新加载一致",
+ "featureIds": [
+ "timeline.play",
+ "timeline.stop",
+ "timeline.loop",
+ "timeline.key-nav",
+ "timeline.add-track",
+ "timeline.remove-track",
+ "timeline.target",
+ "timeline.property",
+ "timeline.mute",
+ "timeline.add-key",
+ "timeline.delete-key",
+ "timeline.drag-key",
+ "timeline.zoom",
+ "timeline.curve",
+ "curve.time",
+ "curve.value",
+ "curve.easing"
+ ],
+ "scope": "真实控件和指针操作,运行时读取数值校验;curve.axis仅切换无数值独立断言,timeline.seek播放头与Alt吸附未在本例覆盖。",
+ "projectId": "169",
+ "actions": "真实 DOM 轨道/帧增删、关键帧指针拖移、播放控件、曲线输入和保存重开",
+ "restored": {
+ "id": "track-0-che4ljm",
+ "name": "新轨道 6",
+ "targetId": "interaction-5f7e4bee",
+ "property": "position",
+ "color": "#ffb22e",
+ "muted": false,
+ "keyframes": [
+ {
+ "id": "key-0-0-mi1ztwf",
+ "time": 0,
+ "value": [
+ 0,
+ 0,
+ 0
+ ],
+ "easing": "easeInOut"
+ },
+ {
+ "id": "key-zi8i3f7",
+ "time": 2,
+ "value": [
+ 5,
+ 0,
+ 0
+ ],
+ "easing": "easeInOut"
+ },
+ {
+ "id": "key-0-1-2lnk2wi",
+ "time": 8,
+ "value": [
+ 0,
+ 0,
+ 0
+ ],
+ "easing": "easeInOut"
+ }
+ ]
+ },
+ "interpolationSamples": {
+ "linear": 1.25,
+ "easeInOut": 0.625,
+ "easeIn": 0.3125,
+ "easeOut": 2.1875,
+ "step": 0
+ },
+ "excluded": [
+ "未新增 solo 能力",
+ "未将原生 GLB 动画片段当成多条编辑时间轴"
+ ]
+ }
+}
\ No newline at end of file
diff --git a/reports/model-editor-closure-20260906/evidence/viewport.json b/reports/model-editor-closure-20260906/evidence/viewport.json
new file mode 100644
index 0000000..181d6c1
--- /dev/null
+++ b/reports/model-editor-closure-20260906/evidence/viewport.json
@@ -0,0 +1,26 @@
+{
+ "time": "2026-09-06T08:22:14.169Z",
+ "data": {
+ "status": "PASS",
+ "kind": "e2e",
+ "testTitle": "场景树搜索、可见性、选择、视口工具、原生动画与截图",
+ "featureIds": [
+ "tree.search",
+ "tree.visibility",
+ "selection.sync",
+ "view.grid",
+ "view.pbr",
+ "view.wireframe",
+ "view.outline",
+ "view.perspective",
+ "view.preview",
+ "view.snapshot",
+ "timeline.native"
+ ],
+ "scope": "树搜索、对象隐藏刷新、轮廓及视图开关、原生动画和PNG下载。移动/旋转/缩放只切换模式,真实gizmo拖动仍需补验;聚焦未做几何断言。",
+ "clips": 3,
+ "nativeClipPersisted": true,
+ "hiddenObjectPersisted": true,
+ "snapshot": "装备模型编辑工具_1788682931740.png"
+ }
+}
\ No newline at end of file
diff --git a/reports/model-editor-closure-20260906/feature-catalog.json b/reports/model-editor-closure-20260906/feature-catalog.json
new file mode 100644
index 0000000..6d1b226
--- /dev/null
+++ b/reports/model-editor-closure-20260906/feature-catalog.json
@@ -0,0 +1,1447 @@
+{
+ "schemaVersion": 1,
+ "features": [
+ {
+ "id": "project.new",
+ "group": "工程与数据",
+ "name": "新建工程",
+ "source": {
+ "file": "../../../unreal_tran_web/src/views/content/LegacyModelEditorView.vue",
+ "line": 118,
+ "symbol": "createNewProject"
+ },
+ "implementation": "本轮修复",
+ "boundary": "宿主创建新项目 ID 并跳转;新建不是清空并覆盖当前工程。",
+ "fix": "新建复用正式 create API;创建失败或取消保留原工程。",
+ "acceptance": "新建前后 ID 不同,原工程内容/版本不被覆盖;取消不发创建请求。"
+ },
+ {
+ "id": "project.import-glb",
+ "group": "工程与数据",
+ "name": "导入装备 GLB",
+ "source": {
+ "file": "../../../unreal_tran/src/main.js",
+ "line": 1264,
+ "symbol": "importModelFile"
+ },
+ "implementation": "已有实现",
+ "boundary": "只支持单文件 GLB;上传→登记→解析→最终保存。",
+ "fix": "延续导入 busy/失败阻断及稳定资产校验。",
+ "acceptance": "选择轻量 GLB,真实模型可见;刷新仍加载同一资产。"
+ },
+ {
+ "id": "project.import-json",
+ "group": "工程与数据",
+ "name": "导入工程 JSON",
+ "source": {
+ "file": "../../../unreal_tran/src/main.js",
+ "line": 2863,
+ "symbol": "importProjectFile"
+ },
+ "implementation": "本轮修复",
+ "boundary": "导入到当前工程,不创建副本;受控引用必须属于当前可用资产。JSON 不含 GLB 字节。",
+ "fix": "导入前 materialize 稳定资产引用。",
+ "acceptance": "同项目导出后再导入,材质/动画/环境不丢;无效或跨项目缺失引用报错。"
+ },
+ {
+ "id": "project.save",
+ "group": "工程与数据",
+ "name": "手动保存 / Ctrl+S",
+ "source": {
+ "file": "../../../unreal_tran/src/main.js",
+ "line": 2748,
+ "symbol": "saveProject"
+ },
+ "implementation": "已有实现",
+ "boundary": "同一工程保存内容和当前视口封面;成功以 API 响应为准。",
+ "fix": "保留队列、版本保护和封面失败回退。",
+ "acceptance": "保存真实 PUT 成功,刷新字段不丢;重复点击不交错。"
+ },
+ {
+ "id": "project.autosave",
+ "group": "工程与数据",
+ "name": "30 秒自动保存",
+ "source": {
+ "file": "../../../unreal_tran/src/main.js",
+ "line": 2122,
+ "symbol": "bindShellEvents"
+ },
+ "implementation": "已有实现",
+ "boundary": "自动保存工程内容;不反复截图更新封面,恢复稿待确认期间暂停。",
+ "fix": "导入/封面保存期间阻断重入。",
+ "acceptance": "修改后等待自动保存并刷新;忙碌时不提交半成品。"
+ },
+ {
+ "id": "project.name",
+ "group": "工程与数据",
+ "name": "修改工程名",
+ "source": {
+ "file": "../../../unreal_tran/src/main.js",
+ "line": 457,
+ "symbol": "projectNameInput"
+ },
+ "implementation": "已有实现",
+ "boundary": "输入→失焦/Enter→保存;当前模型工程名。",
+ "fix": "无本轮专门改动。",
+ "acceptance": "改名保存后列表和重新打开一致。"
+ },
+ {
+ "id": "project.export-json",
+ "group": "工程与数据",
+ "name": "另存项目 JSON",
+ "source": {
+ "file": "../../../unreal_tran/src/main.js",
+ "line": 2883,
+ "symbol": "saveProjectAs"
+ },
+ "implementation": "已有实现",
+ "boundary": "下载工程 JSON;不是另建服务端项目,也不内嵌 GLB 文件。",
+ "fix": "稳定资产引用保留,临时 blob 地址清除。",
+ "acceptance": "下载文件可解析,包含轨道/蓝图/自定义字段,配套资源可重新解析。"
+ },
+ {
+ "id": "project.undo",
+ "group": "工程与数据",
+ "name": "撤销 / Ctrl+Z",
+ "source": {
+ "file": "../../../unreal_tran/src/main.js",
+ "line": 160,
+ "symbol": "class HistoryStack"
+ },
+ "implementation": "本轮修复",
+ "boundary": "最多 60 个历史快照;不是数据库版本回滚。",
+ "fix": "异步互斥,失败不移动索引,托管 GLB 快照重新解析。",
+ "acceptance": "属性/曲线修改撤销;连续点击不并发;下载失败索引与修改保留。"
+ },
+ {
+ "id": "project.redo",
+ "group": "工程与数据",
+ "name": "重做 / Ctrl+Y",
+ "source": {
+ "file": "../../../unreal_tran/src/main.js",
+ "line": 160,
+ "symbol": "class HistoryStack"
+ },
+ "implementation": "本轮修复",
+ "boundary": "撤销后重做;产生新编辑时清掉旧 redo 分支。",
+ "fix": "与撤销共用异步恢复保护。",
+ "acceptance": "重做恢复相同字段及姿态,不回放无效分支。"
+ },
+ {
+ "id": "project.recovery",
+ "group": "工程与数据",
+ "name": "异常恢复副本",
+ "source": {
+ "file": "../../../unreal_tran/src/main.js",
+ "line": 2554,
+ "symbol": "reconcileApiRecovery"
+ },
+ "implementation": "本轮修复",
+ "boundary": "同版本恢复稿需用户确认;旧版本隔离/下载,不静默覆盖服务器。",
+ "fix": "恢复稿重新解析受控资源;指纹忽略对象键顺序,避免已保存页面刷新误生恢复稿。",
+ "acceptance": "同版本恢复可见;旧版本保留/下载;主动保存前不自动 PUT。"
+ },
+ {
+ "id": "project.conflict",
+ "group": "工程与数据",
+ "name": "保存/发布版本冲突",
+ "source": {
+ "file": "../../../unreal_tran/src/model-content-api.js",
+ "line": 1025,
+ "symbol": "queueSave"
+ },
+ "implementation": "已有实现",
+ "boundary": "409 阻断;恢复稿保留。",
+ "fix": "发布封面队列同样遵守冲突保护。",
+ "acceptance": "定向模拟 409,无后续发布请求,不误报成功。"
+ },
+ {
+ "id": "project.permissions",
+ "group": "工程与数据",
+ "name": "查看/编辑/新增/发布权限",
+ "source": {
+ "file": "../../../unreal_tran_web/src/features/editors/model/legacy-iframe-permissions.ts",
+ "line": 32,
+ "symbol": "installLegacyModelPermissions"
+ },
+ "implementation": "本轮修复",
+ "boundary": "按 content.model.* 权限,不按 teacher/admin 身份推断写权限;只读仍能搜索、选择原生动画和键盘激活预览。",
+ "fix": "新建接真实 create 能力;只发布可发布已保存版本;修复查看控件键盘事件误拦截。",
+ "acceptance": "只读查看操作可用且无内容写请求;不可编辑/保存;publisher 不上传/PUT,正常发布已有版本。"
+ },
+ {
+ "id": "project.leave",
+ "group": "工程与数据",
+ "name": "离开/切角色未保存确认",
+ "source": {
+ "file": "../../../unreal_tran_web/src/views/content/LegacyModelEditorView.vue",
+ "line": 147,
+ "symbol": "confirmLeave"
+ },
+ "implementation": "已有实现",
+ "boundary": "取消留在当前页;保存失败不得离开;上传/保存/发布忙时保留状态。",
+ "fix": "新建也复用现有未保存确认。",
+ "acceptance": "取消、放弃、保存失败与 busy 分别验证,没有误清 dirty。"
+ },
+ {
+ "id": "resource.catalog",
+ "group": "资源库与场景树",
+ "name": "资源库浏览 / 选择装备",
+ "source": {
+ "file": "../../../unreal_tran/src/main.js",
+ "line": 1383,
+ "symbol": "renderAssets"
+ },
+ "implementation": "本轮修复",
+ "boundary": "切换装备可能应用其基准时间轴/蓝图;不是把第二个主装备追加进当前模型。",
+ "fix": "修复单节点选择器误用导致启动报错;忙碌期间禁止切换资源。",
+ "acceptance": "完整资源库 DOM 初始化、卡片事件、空目录启动正常;切换实际几何可见,保存后资源一致。"
+ },
+ {
+ "id": "resource.search",
+ "group": "资源库与场景树",
+ "name": "资源搜索",
+ "source": {
+ "file": "../../../unreal_tran/src/main.js",
+ "line": 1467,
+ "symbol": "applyAssetSearch"
+ },
+ "implementation": "已有实现",
+ "boundary": "按资源名称/关键词过滤现有目录,不搜索服务器全站。",
+ "fix": "无本轮专门改动。",
+ "acceptance": "关键字命中、空结果与清空恢复。"
+ },
+ {
+ "id": "environment.builtin",
+ "group": "资源库与场景树",
+ "name": "内置车间 / 野外 / 装备车库",
+ "source": {
+ "file": "../../../unreal_tran/src/main.js",
+ "line": 789,
+ "symbol": "resetEnvironmentPlaceholder"
+ },
+ "implementation": "已有实现",
+ "boundary": "车间/野外为程序化环境;车库为随包 GLB。",
+ "fix": "保留真实 GLB 和占位环境来源区别。",
+ "acceptance": "三种切换与重新打开可重现相应环境。"
+ },
+ {
+ "id": "environment.external",
+ "group": "资源库与场景树",
+ "name": "导入外部车间 / 野外 GLB",
+ "source": {
+ "file": "../../../unreal_tran/src/main.js",
+ "line": 1028,
+ "symbol": "loadEnvironmentSceneFile"
+ },
+ "implementation": "本轮修复",
+ "boundary": "一个环境根;受控 MODEL_FILE 资产 metadata.usage=environment,与主装备独立。",
+ "fix": "上传、最终保存、JSON 与刷新恢复稳定引用;失败阻断半提交。",
+ "acceptance": "小 GLB 上传后主装备不被替换;环境刷新仍可见且资产引用一致。"
+ },
+ {
+ "id": "resource.loading",
+ "group": "资源库与场景树",
+ "name": "模型加载进度 / 失败 / 重试",
+ "source": {
+ "file": "../../../unreal_tran_web/src/views/content/LegacyModelEditorView.vue",
+ "line": 33,
+ "symbol": "installLegacyModelIframeLoadingScope"
+ },
+ "implementation": "已有实现",
+ "boundary": "局部加载状态;错误短提示,中性失败状态可重试。",
+ "fix": "复用前轮加载范围与错误消息修复。",
+ "acceptance": "资源失败不无限转圈;重试恢复;无凭据泄露。"
+ },
+ {
+ "id": "tree.browse",
+ "group": "资源库与场景树",
+ "name": "场景树节点与计数",
+ "source": {
+ "file": "../../../unreal_tran/src/main.js",
+ "line": 1925,
+ "symbol": "buildSceneTree"
+ },
+ "implementation": "现有边界",
+ "boundary": "树行上限 220;统计节点总数不等于当前展开行数。",
+ "fix": "无本轮容量重构。",
+ "acceptance": "模型/环境节点可达,计数说明不当作全部节点已经逐项验收。"
+ },
+ {
+ "id": "tree.expand",
+ "group": "资源库与场景树",
+ "name": "节点展开 / 折叠",
+ "source": {
+ "file": "../../../unreal_tran/src/main.js",
+ "line": 1925,
+ "symbol": "buildSceneTree"
+ },
+ "implementation": "已有实现",
+ "boundary": "当前界面浏览状态;无须持久化展开布局。",
+ "fix": "无本轮专门改动。",
+ "acceptance": "展开和折叠子级不修改模型对象。"
+ },
+ {
+ "id": "tree.search",
+ "group": "资源库与场景树",
+ "name": "场景树搜索",
+ "source": {
+ "file": "../../../unreal_tran/src/main.js",
+ "line": 1925,
+ "symbol": "buildSceneTree"
+ },
+ "implementation": "已有实现",
+ "boundary": "搜索当前载入对象,不新增/删除对象。",
+ "fix": "无本轮专门改动。",
+ "acceptance": "搜索节点后选中正确对象,清空恢复树。"
+ },
+ {
+ "id": "tree.visibility",
+ "group": "资源库与场景树",
+ "name": "显示 / 隐藏对象",
+ "source": {
+ "file": "../../../unreal_tran/src/main.js",
+ "line": 1925,
+ "symbol": "buildSceneTree"
+ },
+ "implementation": "本轮修复",
+ "boundary": "对象 visible 保存;隐藏祖先的子对象不接受射线拾取。",
+ "fix": "拾取与高亮统一检查祖先可见性。",
+ "acceptance": "隐藏父级后不能点到子级,保存重开仍隐藏。"
+ },
+ {
+ "id": "selection.scope",
+ "group": "资源库与场景树",
+ "name": "整体 / 部件级选择",
+ "source": {
+ "file": "../../../unreal_tran/src/main.js",
+ "line": 1938,
+ "symbol": "setSelectionScope"
+ },
+ "implementation": "已有实现",
+ "boundary": "整体作用主装备根;部件作用实际节点。",
+ "fix": "保留当前选择层级合同。",
+ "acceptance": "点击可见 mesh,整体/部件切换选择目标不同。"
+ },
+ {
+ "id": "selection.sync",
+ "group": "资源库与场景树",
+ "name": "树 / 视口 / 属性选择联动",
+ "source": {
+ "file": "../../../unreal_tran/src/main.js",
+ "line": 1947,
+ "symbol": "selectObject"
+ },
+ "implementation": "本轮修复",
+ "boundary": "显示实际对象并更新辅助框;环境也是可编辑对象。",
+ "fix": "关闭轮廓后重选不恢复辅助框。",
+ "acceptance": "三处名称/对象 ID 一致,重选后轮廓状态保持。"
+ },
+ {
+ "id": "view.orbit",
+ "group": "三维视图与操作",
+ "name": "鼠标旋转 / 平移 / 缩放视角",
+ "source": {
+ "file": "../../../unreal_tran/src/main.js",
+ "line": 2,
+ "symbol": "OrbitControls"
+ },
+ "implementation": "已有实现",
+ "boundary": "当前三维相机;不是模型变换。",
+ "fix": "无本轮专门改动。",
+ "acceptance": "鼠标操作改变镜头但不改变对象局部坐标。"
+ },
+ {
+ "id": "view.focus",
+ "group": "三维视图与操作",
+ "name": "聚焦选中 / F",
+ "source": {
+ "file": "../../../unreal_tran/src/main.js",
+ "line": 2272,
+ "symbol": "fitCamera"
+ },
+ "implementation": "已有实现",
+ "boundary": "按当前对象包围盒定位相机。",
+ "fix": "无本轮专门改动。",
+ "acceptance": "小模型/部件聚焦后仍可见,不被裁剪。"
+ },
+ {
+ "id": "view.move",
+ "group": "三维视图与操作",
+ "name": "移动工具 / W",
+ "source": {
+ "file": "../../../unreal_tran/src/main.js",
+ "line": 2234,
+ "symbol": "setTransformMode"
+ },
+ "implementation": "本轮修复",
+ "boundary": "拖拽编辑对象局部位置,保存后重开一致。",
+ "fix": "动画姿态下更新基值只扣实际偏移。",
+ "acceptance": "移动后保存、刷新及动画再播放不漂移。"
+ },
+ {
+ "id": "view.rotate",
+ "group": "三维视图与操作",
+ "name": "旋转工具 / E",
+ "source": {
+ "file": "../../../unreal_tran/src/main.js",
+ "line": 2234,
+ "symbol": "setTransformMode"
+ },
+ "implementation": "本轮修复",
+ "boundary": "对象旋转;与当前时间轴旋转叠加需要一致。",
+ "fix": "统一动画基值处理。",
+ "acceptance": "旋转后保存/撤销/重做姿态一致。"
+ },
+ {
+ "id": "view.scale",
+ "group": "三维视图与操作",
+ "name": "缩放工具 / R",
+ "source": {
+ "file": "../../../unreal_tran/src/main.js",
+ "line": 2234,
+ "symbol": "setTransformMode"
+ },
+ "implementation": "已有实现",
+ "boundary": "对象缩放持久化;时间轴没有缩放轨道 UI。",
+ "fix": "无新增缩放动画能力。",
+ "acceptance": "对象缩放保存和重开正确。"
+ },
+ {
+ "id": "view.grid",
+ "group": "三维视图与操作",
+ "name": "网格开关",
+ "source": {
+ "file": "../../../unreal_tran/src/main.js",
+ "line": 2283,
+ "symbol": "toggleGrid"
+ },
+ "implementation": "已有实现",
+ "boundary": "临时视图选项,不要求写入工程。",
+ "fix": "非持久化不计保存缺陷。",
+ "acceptance": "开关只影响辅助网格,不改变模型。"
+ },
+ {
+ "id": "view.pbr",
+ "group": "三维视图与操作",
+ "name": "PBR 光照开关",
+ "source": {
+ "file": "../../../unreal_tran/src/main.js",
+ "line": 2236,
+ "symbol": "handleView"
+ },
+ "implementation": "已有实现",
+ "boundary": "当前照明预览选项;不是批量材质编辑。",
+ "fix": "非持久化不计保存缺陷。",
+ "acceptance": "开关改变显示,物体材质字段不被覆盖。"
+ },
+ {
+ "id": "view.wireframe",
+ "group": "三维视图与操作",
+ "name": "线框开关",
+ "source": {
+ "file": "../../../unreal_tran/src/main.js",
+ "line": 2236,
+ "symbol": "handleView"
+ },
+ "implementation": "已有实现",
+ "boundary": "当前视图选项;不承诺保存到工程。",
+ "fix": "非持久化不计保存缺陷。",
+ "acceptance": "线框开关恢复正常着色。"
+ },
+ {
+ "id": "view.outline",
+ "group": "三维视图与操作",
+ "name": "选中轮廓开关",
+ "source": {
+ "file": "../../../unreal_tran/src/main.js",
+ "line": 2236,
+ "symbol": "handleView"
+ },
+ "implementation": "本轮修复",
+ "boundary": "当前会话偏好,进出预览后保持;不要求跨重载存储。",
+ "fix": "重选时不重新泄露轮廓。",
+ "acceptance": "关闭→重选→预览→退出,轮廓仍关闭。"
+ },
+ {
+ "id": "view.perspective",
+ "group": "三维视图与操作",
+ "name": "透视视角按钮",
+ "source": {
+ "file": "../../../unreal_tran/src/main.js",
+ "line": 2236,
+ "symbol": "handleView"
+ },
+ "implementation": "现有边界",
+ "boundary": "重置透视观察角度;当前没有正交视角菜单。",
+ "fix": "不把临时视图状态当作保存缺陷。",
+ "acceptance": "点击后相机和聚焦目标正常。"
+ },
+ {
+ "id": "view.snapshot",
+ "group": "三维视图与操作",
+ "name": "下载视口截图",
+ "source": {
+ "file": "../../../unreal_tran/src/main.js",
+ "line": 3092,
+ "symbol": "downloadSnapshot"
+ },
+ "implementation": "已有实现",
+ "boundary": "当前 canvas PNG,可包含编辑辅助元素;保存封面是独立截图流程。",
+ "fix": "无本轮专门改动。",
+ "acceptance": "PNG 下载可打开,内容与当前视口对应。"
+ },
+ {
+ "id": "view.panels",
+ "group": "三维视图与操作",
+ "name": "底部页签 / Tab / 面板高度",
+ "source": {
+ "file": "../../../unreal_tran/src/main.js",
+ "line": 2215,
+ "symbol": "switchBottomMode"
+ },
+ "implementation": "已有实现",
+ "boundary": "时间轴、蓝图、曲线、日志切换;拖分割条只调整当前工作区。",
+ "fix": "曲线入口接真实数据。",
+ "acceptance": "各页签显示正确内容,调整高度后画布正确 resize。"
+ },
+ {
+ "id": "view.preview",
+ "group": "三维视图与操作",
+ "name": "进入 / 退出运行预览",
+ "source": {
+ "file": "../../../unreal_tran/src/main.js",
+ "line": 2284,
+ "symbol": "togglePreview"
+ },
+ "implementation": "本轮修复",
+ "boundary": "真实当前三维场景;隐藏辅助工具,退出不丢编辑状态。",
+ "fix": "进入显式派发 auto;退出停止运行并恢复轮廓策略。",
+ "acceptance": "进入/退出与 Esc,三维仍可见,无辅助框泄漏。"
+ },
+ {
+ "id": "view.preview-step",
+ "group": "三维视图与操作",
+ "name": "预览播放 / 前后导航",
+ "source": {
+ "file": "../../../unreal_tran/src/main.js",
+ "line": 2122,
+ "symbol": "bindShellEvents"
+ },
+ "implementation": "现有边界",
+ "boundary": "播放运行蓝图;前后按钮当前按时间轴 ±2 秒,不是按业务工序跳转。",
+ "fix": "无新增业务工序导航。",
+ "acceptance": "播放触发真实流程;前后跳时与时间轴一致。"
+ },
+ {
+ "id": "property.name",
+ "group": "对象属性与扩展标记",
+ "name": "对象名称",
+ "source": {
+ "file": "../../../unreal_tran/src/main.js",
+ "line": 1996,
+ "symbol": "bindInspectorEvents"
+ },
+ "implementation": "已有实现",
+ "boundary": "修改当前节点名,标识保持稳定。",
+ "fix": "无本轮专门改动。",
+ "acceptance": "树和属性同步,保存重开名称保留。"
+ },
+ {
+ "id": "property.identity",
+ "group": "对象属性与扩展标记",
+ "name": "对象标识 / 状态说明",
+ "source": {
+ "file": "../../../unreal_tran/src/main.js",
+ "line": 1958,
+ "symbol": "renderInspector"
+ },
+ "implementation": "本轮修复",
+ "boundary": "标识只读;可见性含祖先状态,阴影取实际castShadow;编辑能力由宿主权限注入。",
+ "fix": "不再固定显示“可见/投射阴影”,补宿主可编辑状态钩子。",
+ "acceptance": "标识不可编辑;选中对象、隐藏状态、阴影及只读权限标签一致。"
+ },
+ {
+ "id": "property.position",
+ "group": "对象属性与扩展标记",
+ "name": "位置 XYZ 数值",
+ "source": {
+ "file": "../../../unreal_tran/src/main.js",
+ "line": 1996,
+ "symbol": "bindInspectorEvents"
+ },
+ "implementation": "本轮修复",
+ "boundary": "局部坐标;动画当前帧下编辑也能生成正确基值。",
+ "fix": "中性姿态与实际应用偏移统一。",
+ "acceptance": "三个轴编辑、保存重开、动画回放一致。"
+ },
+ {
+ "id": "property.rotation",
+ "group": "对象属性与扩展标记",
+ "name": "旋转 XYZ 数值",
+ "source": {
+ "file": "../../../unreal_tran/src/main.js",
+ "line": 1996,
+ "symbol": "bindInspectorEvents"
+ },
+ "implementation": "本轮修复",
+ "boundary": "输入角度转弧度存储,显示角度。",
+ "fix": "多轨旋转基值不重复扣除。",
+ "acceptance": "度数显示与存储/播放的弧度一致。"
+ },
+ {
+ "id": "property.scale",
+ "group": "对象属性与扩展标记",
+ "name": "缩放 XYZ 数值",
+ "source": {
+ "file": "../../../unreal_tran/src/main.js",
+ "line": 1996,
+ "symbol": "bindInspectorEvents"
+ },
+ "implementation": "已有实现",
+ "boundary": "对象当前缩放,不添加新的缩放轨道。",
+ "fix": "无本轮专门改动。",
+ "acceptance": "非均匀缩放可保存和重开。"
+ },
+ {
+ "id": "property.color",
+ "group": "对象属性与扩展标记",
+ "name": "材质基础色",
+ "source": {
+ "file": "../../../unreal_tran/src/main.js",
+ "line": 1996,
+ "symbol": "bindInspectorEvents"
+ },
+ "implementation": "现有边界",
+ "boundary": "只编辑当前对象第一个材质槽;共享材质可能影响共享该实例的网格。",
+ "fix": "界面明确材质槽 1/N。",
+ "acceptance": "颜色修改、保存重开对应当前槽;不宣称所有槽可编辑。"
+ },
+ {
+ "id": "property.metalness",
+ "group": "对象属性与扩展标记",
+ "name": "材质金属度",
+ "source": {
+ "file": "../../../unreal_tran/src/main.js",
+ "line": 1996,
+ "symbol": "bindInspectorEvents"
+ },
+ "implementation": "现有边界",
+ "boundary": "当前首槽 PBR 参数 0~1。",
+ "fix": "标明材质槽范围。",
+ "acceptance": "拖动参数有效,保存重开值一致。"
+ },
+ {
+ "id": "property.roughness",
+ "group": "对象属性与扩展标记",
+ "name": "材质粗糙度",
+ "source": {
+ "file": "../../../unreal_tran/src/main.js",
+ "line": 1996,
+ "symbol": "bindInspectorEvents"
+ },
+ "implementation": "现有边界",
+ "boundary": "当前首槽 PBR 参数 0~1。",
+ "fix": "标明材质槽范围。",
+ "acceptance": "参数与当前槽持久化一致。"
+ },
+ {
+ "id": "property.opacity",
+ "group": "对象属性与扩展标记",
+ "name": "材质透明度",
+ "source": {
+ "file": "../../../unreal_tran/src/main.js",
+ "line": 1996,
+ "symbol": "bindInspectorEvents"
+ },
+ "implementation": "现有边界",
+ "boundary": "当前首槽;联动 transparent/depthWrite。",
+ "fix": "保留实际透明度保存字段。",
+ "acceptance": "透明效果与重开结果一致。"
+ },
+ {
+ "id": "property.code",
+ "group": "对象属性与扩展标记",
+ "name": "自定义资源编码",
+ "source": {
+ "file": "../../../unreal_tran/src/main.js",
+ "line": 1996,
+ "symbol": "bindInspectorEvents"
+ },
+ "implementation": "本轮修复",
+ "boundary": "对象 customProperties.resourceCode,不是服务器资产主键。",
+ "fix": "输入真正回写、保存、恢复及撤销。",
+ "acceptance": "编辑、清空、重选、JSON 往返、刷新均一致。"
+ },
+ {
+ "id": "property.status",
+ "group": "对象属性与扩展标记",
+ "name": "自定义运行状态",
+ "source": {
+ "file": "../../../unreal_tran/src/main.js",
+ "line": 1996,
+ "symbol": "bindInspectorEvents"
+ },
+ "implementation": "本轮修复",
+ "boundary": "NORMAL/SERVICE 标签,不会发起真实设备控制。",
+ "fix": "真正写回对象,首次变更可撤销。",
+ "acceptance": "两状态切换及刷新保留。"
+ },
+ {
+ "id": "property.binding",
+ "group": "对象属性与扩展标记",
+ "name": "流程绑定说明",
+ "source": {
+ "file": "../../../unreal_tran/src/main.js",
+ "line": 1958,
+ "symbol": "renderInspector"
+ },
+ "implementation": "现有边界",
+ "boundary": "只读显示模板/时间轴名称,不是教学任务的设备或物联网绑定。",
+ "fix": "无本轮业务绑定改造。",
+ "acceptance": "只读,不将其当作任务绑定操作验收。"
+ },
+ {
+ "id": "component.inspect",
+ "group": "对象属性与扩展标记",
+ "name": "组件页内置能力说明",
+ "source": {
+ "file": "../../../unreal_tran/src/main.js",
+ "line": 1975,
+ "symbol": "renderComponents"
+ },
+ "implementation": "现有边界",
+ "boundary": "变换、场景节点、渲染等派生说明,没有通用组件启停系统。",
+ "fix": "区分内置说明与自定义标记。",
+ "acceptance": "显示与对象类型一致,不虚报可执行组件配置。"
+ },
+ {
+ "id": "component.tag",
+ "group": "对象属性与扩展标记",
+ "name": "添加扩展标记",
+ "source": {
+ "file": "../../../unreal_tran/src/main.js",
+ "line": 1975,
+ "symbol": "renderComponents"
+ },
+ "implementation": "本轮澄清",
+ "boundary": "只保存名称标签;没有脚本、启停、运行时行为。",
+ "fix": "从“添加组件”改为“扩展标记,仅保存标签”。",
+ "acceptance": "新增标签保存/重开;不得声称已启用新行为。"
+ },
+ {
+ "id": "timeline.play",
+ "group": "时间轴与曲线",
+ "name": "播放 / 暂停 / Space",
+ "source": {
+ "file": "../../../unreal_tran/src/timeline.js",
+ "line": 393,
+ "symbol": "play()"
+ },
+ "implementation": "已有实现",
+ "boundary": "播放当前编辑时间轴;原生 GLB 动画单独控制。",
+ "fix": "停止清理与时间同步由本轮补强。",
+ "acceptance": "播放进度/真实对象姿态变化,暂停停在当前帧。"
+ },
+ {
+ "id": "timeline.stop",
+ "group": "时间轴与曲线",
+ "name": "停止",
+ "source": {
+ "file": "../../../unreal_tran/src/timeline.js",
+ "line": 429,
+ "symbol": "stop()"
+ },
+ "implementation": "已有实现",
+ "boundary": "停止并回到起始时间,按起始帧应用动画。",
+ "fix": "无本轮语义扩展。",
+ "acceptance": "按钮停止后不再自行前进。"
+ },
+ {
+ "id": "timeline.loop",
+ "group": "时间轴与曲线",
+ "name": "循环开关",
+ "source": {
+ "file": "../../../unreal_tran/src/timeline.js",
+ "line": 198,
+ "symbol": "#bindEvents"
+ },
+ "implementation": "本轮修复",
+ "boundary": "循环设置随 timeline 保存;不循环到达终点只完成一次。",
+ "fix": "循环变更进入历史/dirty。",
+ "acceptance": "开关、保存重开、到末尾/循环边界正确。"
+ },
+ {
+ "id": "timeline.key-nav",
+ "group": "时间轴与曲线",
+ "name": "上一 / 下一关键帧",
+ "source": {
+ "file": "../../../unreal_tran/src/timeline.js",
+ "line": 232,
+ "symbol": "#onClick"
+ },
+ "implementation": "已有实现",
+ "boundary": "按时间轴关键帧时间定位。",
+ "fix": "无本轮专门改动。",
+ "acceptance": "多个轨道时跳到正确相邻关键时刻。"
+ },
+ {
+ "id": "timeline.seek",
+ "group": "时间轴与曲线",
+ "name": "拖播放头 / 点击刻度",
+ "source": {
+ "file": "../../../unreal_tran/src/timeline.js",
+ "line": 263,
+ "symbol": "#onPointerDown"
+ },
+ "implementation": "本轮修复",
+ "boundary": "实时求值当前帧;键盘左右移动一帧,Shift 移动一秒。",
+ "fix": "拖动补发 timechange,解绑指针监听。",
+ "acceptance": "时间码、曲线和视口姿态同步。"
+ },
+ {
+ "id": "timeline.add-track",
+ "group": "时间轴与曲线",
+ "name": "添加轨道",
+ "source": {
+ "file": "../../../unreal_tran/src/timeline.js",
+ "line": 471,
+ "symbol": "addTrack("
+ },
+ "implementation": "已有实现",
+ "boundary": "当前主装备节点的变换轨道。",
+ "fix": "无新增轨道类型。",
+ "acceptance": "增加后可绑定节点并保存。"
+ },
+ {
+ "id": "timeline.remove-track",
+ "group": "时间轴与曲线",
+ "name": "删除轨道",
+ "source": {
+ "file": "../../../unreal_tran/src/timeline.js",
+ "line": 495,
+ "symbol": "removeTrack("
+ },
+ "implementation": "本轮修复",
+ "boundary": "删除后不再保留该轨道施加的旧姿态。",
+ "fix": "宿主恢复此前作用的属性基值。",
+ "acceptance": "删除即时复原,再保存/重开一致。"
+ },
+ {
+ "id": "timeline.target",
+ "group": "时间轴与曲线",
+ "name": "修改轨道目标节点",
+ "source": {
+ "file": "../../../unreal_tran/src/timeline.js",
+ "line": 506,
+ "symbol": "updateSelectedTrack"
+ },
+ "implementation": "本轮修复",
+ "boundary": "用稳定对象标识绑定,不靠同名第一项。",
+ "fix": "重绑清理旧目标的应用姿态。",
+ "acceptance": "从 A 改为 B,A 复原,B 播放。"
+ },
+ {
+ "id": "timeline.property",
+ "group": "时间轴与曲线",
+ "name": "选择位置 / 旋转属性",
+ "source": {
+ "file": "../../../unreal_tran/src/timeline.js",
+ "line": 506,
+ "symbol": "updateSelectedTrack"
+ },
+ "implementation": "已有实现",
+ "boundary": "位置 XYZ 向量、旋转 X/Y/Z;没有缩放轨道 UI。",
+ "fix": "保留当前实际能力。",
+ "acceptance": "属性切换后关键帧类型和播放一致。"
+ },
+ {
+ "id": "timeline.mute",
+ "group": "时间轴与曲线",
+ "name": "轨道静音",
+ "source": {
+ "file": "../../../unreal_tran/src/timeline.js",
+ "line": 232,
+ "symbol": "#onClick"
+ },
+ "implementation": "本轮修复",
+ "boundary": "仅忽略当前轨道,不关闭其他轨道。",
+ "fix": "不再停留旧姿态或保存时重复扣值。",
+ "acceptance": "静音/恢复后实际姿态与重开一致。"
+ },
+ {
+ "id": "timeline.record",
+ "group": "时间轴与曲线",
+ "name": "记录当前姿态关键帧",
+ "source": {
+ "file": "../../../unreal_tran/src/main.js",
+ "line": 2276,
+ "symbol": "addKeyframeFromSelection"
+ },
+ "implementation": "本轮修复",
+ "boundary": "记录相对对象基值的当前值。",
+ "fix": "与多轨实际应用/保存采用同一基值。",
+ "acceptance": "操作对象后记录,回放命中相同姿态。"
+ },
+ {
+ "id": "timeline.add-key",
+ "group": "时间轴与曲线",
+ "name": "双击新增关键帧",
+ "source": {
+ "file": "../../../unreal_tran/src/timeline.js",
+ "line": 255,
+ "symbol": "#onDoubleClick"
+ },
+ "implementation": "已有实现",
+ "boundary": "在轨道对应时刻创建/更新关键帧。",
+ "fix": "时间碰撞保留当前编辑帧。",
+ "acceptance": "新增后节点可选,时间/数值可保存。"
+ },
+ {
+ "id": "timeline.delete-key",
+ "group": "时间轴与曲线",
+ "name": "删除关键帧 / Delete",
+ "source": {
+ "file": "../../../unreal_tran/src/timeline.js",
+ "line": 584,
+ "symbol": "deleteSelected"
+ },
+ "implementation": "已有实现",
+ "boundary": "删除选中的关键帧;输入框编辑时不截获删除键。",
+ "fix": "删除和监听清理回归。",
+ "acceptance": "删除后曲线和播放变化,撤销可恢复。"
+ },
+ {
+ "id": "timeline.drag-key",
+ "group": "时间轴与曲线",
+ "name": "关键帧拖动 / Alt 取消吸附",
+ "source": {
+ "file": "../../../unreal_tran/src/timeline.js",
+ "line": 263,
+ "symbol": "#onPointerDown"
+ },
+ "implementation": "本轮修复",
+ "boundary": "默认 1/30 秒吸附;重叠时合并,保留正在编辑帧。",
+ "fix": "拖动时间同步,碰撞处理确定。",
+ "acceptance": "拖动/重叠/Alt 行为和保存后时间正确。"
+ },
+ {
+ "id": "timeline.zoom",
+ "group": "时间轴与曲线",
+ "name": "时间轴缩放",
+ "source": {
+ "file": "../../../unreal_tran/src/timeline.js",
+ "line": 451,
+ "symbol": "setZoom"
+ },
+ "implementation": "已有实现",
+ "boundary": "显示缩放随 timeline 记录;不改变时长/关键帧数值。",
+ "fix": "无本轮专门改动。",
+ "acceptance": "缩放不改变动作内容。"
+ },
+ {
+ "id": "timeline.multitrack",
+ "group": "时间轴与曲线",
+ "name": "多轨组合与保存基值",
+ "source": {
+ "file": "../../../unreal_tran/src/model-editor-state.js",
+ "line": 12,
+ "symbol": "applyEditorTimelinePose"
+ },
+ "implementation": "本轮修复",
+ "boundary": "不同属性组合;同对象同属性最后有效轨道生效。",
+ "fix": "求值、手工编辑基值、序列化一致。",
+ "acceptance": "两轨同属性、静音、删轨、重绑、重开无漂移。"
+ },
+ {
+ "id": "timeline.baseline",
+ "group": "时间轴与曲线",
+ "name": "载入装备基准动作",
+ "source": {
+ "file": "../../../unreal_tran/src/main.js",
+ "line": 3058,
+ "symbol": "loadBaselineAnimation"
+ },
+ "implementation": "现有边界",
+ "boundary": "匹配部件后直接替换当前时间轴/蓝图;当前没有单独确认框,不是任意维修流程自动生成。",
+ "fix": "保持当前装备限定;无匹配只提示,不改工程。",
+ "acceptance": "匹配时载入/保存/重开;无匹配时保留原数据并给出提示。"
+ },
+ {
+ "id": "timeline.native",
+ "group": "时间轴与曲线",
+ "name": "GLB 原生动画选择 / 播放",
+ "source": {
+ "file": "../../../unreal_tran/src/main.js",
+ "line": 1715,
+ "symbol": "selectNativeAnimation"
+ },
+ "implementation": "已有实现",
+ "boundary": "真实 GLB 自带片段;保存选择索引,不是蓝图 clip 字段。",
+ "fix": "无片段时禁用。",
+ "acceptance": "狐狸实际动画播放/暂停,选择保存后重开一致。"
+ },
+ {
+ "id": "timeline.curve",
+ "group": "时间轴与曲线",
+ "name": "曲线选择轨道 / 关键帧",
+ "source": {
+ "file": "../../../unreal_tran/src/model-curve-editor.js",
+ "line": 5,
+ "symbol": "class ModelCurveEditor"
+ },
+ "implementation": "本轮修复",
+ "boundary": "曲线与时间轴共用一份轨道和关键帧数据。",
+ "fix": "移除固定 SVG 假数据。",
+ "acceptance": "实际轨道名和点数匹配时间轴,点击同步当前帧。"
+ },
+ {
+ "id": "curve.time",
+ "group": "时间轴与曲线",
+ "name": "曲线编辑时间",
+ "source": {
+ "file": "../../../unreal_tran/src/model-curve-editor.js",
+ "line": 30,
+ "symbol": "change(input)"
+ },
+ "implementation": "本轮修复",
+ "boundary": "更新实际 key.time,约束在时间轴时长内。",
+ "fix": "调用 timeline.updateKeyframe 并保持编辑目标。",
+ "acceptance": "改时间后重排、撤销、JSON 往返一致。"
+ },
+ {
+ "id": "curve.value",
+ "group": "时间轴与曲线",
+ "name": "曲线编辑标量 / XYZ 数值",
+ "source": {
+ "file": "../../../unreal_tran/src/model-curve-editor.js",
+ "line": 30,
+ "symbol": "change(input)"
+ },
+ "implementation": "本轮修复",
+ "boundary": "修改实际关键帧值,向量编辑保留其他分量。",
+ "fix": "无效空值拒绝提交。",
+ "acceptance": "标量和向量都驱动真实姿态并保存。"
+ },
+ {
+ "id": "curve.easing",
+ "group": "时间轴与曲线",
+ "name": "曲线插值",
+ "source": {
+ "file": "../../../unreal_tran/src/model-curve-editor.js",
+ "line": 30,
+ "symbol": "change(input)"
+ },
+ "implementation": "本轮修复",
+ "boundary": "支持线性、平滑、缓入、缓出、阶梯;没有 Bezier 切线 UI。",
+ "fix": "阶梯插值与已有下游时间轴求值一致。",
+ "acceptance": "修改插值改变中间帧,重开保留。"
+ },
+ {
+ "id": "curve.axis",
+ "group": "时间轴与曲线",
+ "name": "曲线 XYZ 分量显示",
+ "source": {
+ "file": "../../../unreal_tran/src/model-curve-editor.js",
+ "line": 30,
+ "symbol": "change(input)"
+ },
+ "implementation": "已有实现",
+ "boundary": "改变图上显示分量,不改另外两轴的数据。",
+ "fix": "当前实际向量轨道才出现。",
+ "acceptance": "切分量显示不同曲线,数值不被改写。"
+ },
+ {
+ "id": "blueprint.palette",
+ "group": "蓝图与交互反馈",
+ "name": "节点库搜索 / 拖入节点",
+ "source": {
+ "file": "../../../unreal_tran/src/blueprint.js",
+ "line": 287,
+ "symbol": "_renderPalette"
+ },
+ "implementation": "已有实现",
+ "boundary": "当前七类节点:触发、时间轴、等待、高亮、提示、条件、完成。",
+ "fix": "不新增任意脚本节点。",
+ "acceptance": "搜索、拖入后节点类型和参数可保存。"
+ },
+ {
+ "id": "blueprint.add",
+ "group": "蓝图与交互反馈",
+ "name": "添加节点按钮",
+ "source": {
+ "file": "../../../unreal_tran/src/blueprint.js",
+ "line": 257,
+ "symbol": "_bind()"
+ },
+ "implementation": "已有实现",
+ "boundary": "工具栏默认添加提示节点;其他类型来自节点库。",
+ "fix": "无本轮专门改动。",
+ "acceptance": "新增进入画布、历史与 JSON。"
+ },
+ {
+ "id": "blueprint.parameters",
+ "group": "蓝图与交互反馈",
+ "name": "节点标题 / 参数编辑",
+ "source": {
+ "file": "../../../unreal_tran/src/blueprint.js",
+ "line": 431,
+ "symbol": "_renderInspector"
+ },
+ "implementation": "已有实现",
+ "boundary": "按节点类型提供数字、文字、枚举、复选框。",
+ "fix": "保留数值边界与真实数据写回。",
+ "acceptance": "改参数与标题后重开保留。"
+ },
+ {
+ "id": "blueprint.move",
+ "group": "蓝图与交互反馈",
+ "name": "节点拖动 / 多选框选",
+ "source": {
+ "file": "../../../unreal_tran/src/blueprint.js",
+ "line": 566,
+ "symbol": "_onViewportPointerDown"
+ },
+ "implementation": "已有实现",
+ "boundary": "节点布局随蓝图 JSON 保存。",
+ "fix": "无本轮专门改动。",
+ "acceptance": "多选移动位置正确,撤销恢复。"
+ },
+ {
+ "id": "blueprint.connect",
+ "group": "蓝图与交互反馈",
+ "name": "连接 / 替换 / 删除执行线",
+ "source": {
+ "file": "../../../unreal_tran/src/blueprint.js",
+ "line": 767,
+ "symbol": "connect("
+ },
+ "implementation": "本轮修复",
+ "boundary": "单输出重连替换;允许多个来源汇聚同一输入。",
+ "fix": "不再清掉同一输入的其他合法来源。",
+ "acceptance": "true/false 汇聚保留两条,替换输出只影响该线。"
+ },
+ {
+ "id": "blueprint.delete",
+ "group": "蓝图与交互反馈",
+ "name": "删除节点 / 连线",
+ "source": {
+ "file": "../../../unreal_tran/src/blueprint.js",
+ "line": 786,
+ "symbol": "deleteSelection"
+ },
+ "implementation": "已有实现",
+ "boundary": "删除节点一并清理相关边。",
+ "fix": "复制/剪切使用同一图数据合同。",
+ "acceptance": "无悬空边;撤销恢复完整图。"
+ },
+ {
+ "id": "blueprint.copy",
+ "group": "蓝图与交互反馈",
+ "name": "复制 / 剪切 / 粘贴",
+ "source": {
+ "file": "../../../unreal_tran/src/blueprint.js",
+ "line": 714,
+ "symbol": "copySelection"
+ },
+ "implementation": "本轮修复",
+ "boundary": "Ctrl/Cmd+C/X/V;复制选区内部边,不复制指向外部的边。",
+ "fix": "新节点 ID 和内部连线同步生成,阻止快捷键冒泡。",
+ "acceptance": "副本内部连接正确,不误删外部对象。"
+ },
+ {
+ "id": "blueprint.canvas",
+ "group": "蓝图与交互反馈",
+ "name": "平移 / 缩放 / 适应画布",
+ "source": {
+ "file": "../../../unreal_tran/src/blueprint.js",
+ "line": 801,
+ "symbol": "frameAll"
+ },
+ "implementation": "已有实现",
+ "boundary": "蓝图画布浏览;不是三维相机。",
+ "fix": "无本轮专门改动。",
+ "acceptance": "滚轮、Space 拖动和适应可见全部节点。"
+ },
+ {
+ "id": "blueprint.run",
+ "group": "蓝图与交互反馈",
+ "name": "运行 / F5",
+ "source": {
+ "file": "../../../unreal_tran/src/blueprint.js",
+ "line": 871,
+ "symbol": "async run("
+ },
+ "implementation": "本轮修复",
+ "boundary": "在当前模型编辑器驱动真实场景;有 200 步循环上限。",
+ "fix": "条件与等待流程回归。",
+ "acceptance": "执行高亮、动作、反馈;循环图可终止不挂死。"
+ },
+ {
+ "id": "blueprint.stop",
+ "group": "蓝图与交互反馈",
+ "name": "停止运行",
+ "source": {
+ "file": "../../../unreal_tran/src/blueprint.js",
+ "line": 997,
+ "symbol": "stop("
+ },
+ "implementation": "本轮修复",
+ "boundary": "停止中断挂起等待,旧运行不覆盖新运行状态。",
+ "fix": "AbortSignal 贯穿宿主动作与高亮清理。",
+ "acceptance": "等待/动画中停止及时退出,无迟到完成提示。"
+ },
+ {
+ "id": "blueprint.reset",
+ "group": "蓝图与交互反馈",
+ "name": "重置执行",
+ "source": {
+ "file": "../../../unreal_tran/src/blueprint.js",
+ "line": 1014,
+ "symbol": "resetExecution"
+ },
+ "implementation": "已有实现",
+ "boundary": "复位执行状态、时间轴和高亮;保留蓝图编辑内容。",
+ "fix": "高亮恢复原材质。",
+ "acceptance": "运行后重置,进度归零且节点图不丢。"
+ },
+ {
+ "id": "blueprint.click",
+ "group": "蓝图与交互反馈",
+ "name": "点击触发目标",
+ "source": {
+ "file": "../../../unreal_tran/src/blueprint.js",
+ "line": 933,
+ "symbol": "async trigger("
+ },
+ "implementation": "本轮修复",
+ "boundary": "匹配事件和目标 ID/名称/祖先别名;空目标可通配。",
+ "fix": "不再点击 B 触发 A。",
+ "acceptance": "两个不同目标分别点击,只运行匹配分支。"
+ },
+ {
+ "id": "blueprint.double-click",
+ "group": "蓝图与交互反馈",
+ "name": "双击触发目标",
+ "source": {
+ "file": "../../../unreal_tran/src/main.js",
+ "line": 2256,
+ "symbol": "pickObject"
+ },
+ "implementation": "本轮修复",
+ "boundary": "双击独立派发 double-click,与单击区分。",
+ "fix": "增加真实视口双击派发并抑制待发单击。",
+ "acceptance": "双击只触发目标的双击分支。"
+ },
+ {
+ "id": "blueprint.auto",
+ "group": "蓝图与交互反馈",
+ "name": "自动触发",
+ "source": {
+ "file": "../../../unreal_tran/src/main.js",
+ "line": 2284,
+ "symbol": "togglePreview"
+ },
+ "implementation": "本轮修复",
+ "boundary": "仅进入预览显式派发 auto,不把每次单击当 auto。",
+ "fix": "事件匹配与进入预览联动。",
+ "acceptance": "进入预览触发一次,普通点击不重放自动分支。"
+ },
+ {
+ "id": "blueprint.timeline",
+ "group": "蓝图与交互反馈",
+ "name": "播放时间轴节点",
+ "source": {
+ "file": "../../../unreal_tran/src/main.js",
+ "line": 2024,
+ "symbol": "playTimelineForBlueprint"
+ },
+ "implementation": "本轮修复",
+ "boundary": "播放当前唯一时间轴,动作名称仅描述;支持起始时间、速度、等待。",
+ "fix": "起始末帧不反跳零,终止可取消。",
+ "acceptance": "起始时间和速度实际生效,等待与不等待分支正确。"
+ },
+ {
+ "id": "blueprint.wait",
+ "group": "蓝图与交互反馈",
+ "name": "等待节点",
+ "source": {
+ "file": "../../../unreal_tran/src/blueprint.js",
+ "line": 1,
+ "symbol": "NODE_TYPES"
+ },
+ "implementation": "已有实现",
+ "boundary": "指定毫秒等待;可被停止取消。",
+ "fix": "异步停止及时结束。",
+ "acceptance": "等待期间停止不会继续执行后续节点。"
+ },
+ {
+ "id": "blueprint.highlight",
+ "group": "蓝图与交互反馈",
+ "name": "高亮节点 / 呼吸效果",
+ "source": {
+ "file": "../../../unreal_tran/src/main.js",
+ "line": 2052,
+ "symbol": "highlightPart"
+ },
+ "implementation": "本轮修复",
+ "boundary": "准确命中可见对象;同名歧义要求标识;没有可高亮材质则报错。",
+ "fix": "拒绝错误整机回退,真实呼吸效果,保存忽略临时高亮。",
+ "acceptance": "目标正确、找不到报错、结束恢复、保存不永久染色。"
+ },
+ {
+ "id": "blueprint.message",
+ "group": "蓝图与交互反馈",
+ "name": "操作提示节点",
+ "source": {
+ "file": "../../../unreal_tran/src/main.js",
+ "line": 2099,
+ "symbol": "showTeachingMessage"
+ },
+ "implementation": "现有边界",
+ "boundary": "显示标题/文本,可点击关闭或约 1.8/2.5 秒自动继续;不是强制人员确认。",
+ "fix": "不将其当作业务审核/签名。",
+ "acceptance": "文本出现、关闭和停止不留弹层。"
+ },
+ {
+ "id": "blueprint.condition",
+ "group": "蓝图与交互反馈",
+ "name": "条件判断节点",
+ "source": {
+ "file": "../../../unreal_tran/src/main.js",
+ "line": 2095,
+ "symbol": "evaluateBlueprintCondition"
+ },
+ "implementation": "本轮修复",
+ "boundary": "当前运行变量、嵌套字段、truthy/布尔/数字比较。",
+ "fix": "false 没有出口不走 true,字符串布尔正确解析。",
+ "acceptance": "真假分支分别验证,缺出口不误执行。"
+ },
+ {
+ "id": "blueprint.finish",
+ "group": "蓝图与交互反馈",
+ "name": "流程完成节点",
+ "source": {
+ "file": "../../../unreal_tran/src/main.js",
+ "line": 1075,
+ "symbol": "initEditors"
+ },
+ "implementation": "已有实现",
+ "boundary": "本次预览结果/进度提示,不写教学任务成绩。",
+ "fix": "保持本次运行反馈边界。",
+ "acceptance": "成功/完成提示与实际执行结果一致。"
+ },
+ {
+ "id": "publish.preflight",
+ "group": "发布、交付与运行信息",
+ "name": "生成 / 发布检查面板",
+ "source": {
+ "file": "../../../unreal_tran/src/main.js",
+ "line": 2978,
+ "symbol": "togglePublishPanel"
+ },
+ "implementation": "现有边界",
+ "boundary": "本地主要检查资源引用存在及轨道/节点数量,不执行完整蓝图可达性、终止性或业务校验;服务端发布校验最终生效。",
+ "fix": "发布目标与离线交付能力明确。",
+ "acceptance": "可打开关闭;不完整工程不能凭界面绿标绕过服务端。"
+ },
+ {
+ "id": "publish.api",
+ "group": "发布、交付与运行信息",
+ "name": "发布到场景资源库",
+ "source": {
+ "file": "../../../unreal_tran/src/main.js",
+ "line": 2897,
+ "symbol": "publishToSceneLibrary"
+ },
+ "implementation": "本轮修复",
+ "boundary": "先保存当前工程再发布固定版本;纯 publisher 发布已保存版本。",
+ "fix": "整个保存/发布队列互斥,失败不发后续发布。",
+ "acceptance": "发布版本/模型资产/列表状态一致,保存失败不发布。"
+ },
+ {
+ "id": "publish.cover",
+ "group": "发布、交付与运行信息",
+ "name": "发布时当前三维截图封面",
+ "source": {
+ "file": "../../../unreal_tran/src/main.js",
+ "line": 2897,
+ "symbol": "publishToSceneLibrary"
+ },
+ "implementation": "本轮修复",
+ "boundary": "JPEG 当前视口;截图或上传失败可保留旧封面并明确提示。",
+ "fix": "与手动保存共用封面链路,409 阻断。",
+ "acceptance": "真实封面图对应当前模型;上传失败提示一次,旧图保留。"
+ },
+ {
+ "id": "publish.html",
+ "group": "发布、交付与运行信息",
+ "name": "导出步骤演示 HTML",
+ "source": {
+ "file": "../../../unreal_tran/src/main.js",
+ "line": 3011,
+ "symbol": "exportStandaloneHtml"
+ },
+ "implementation": "本轮澄清",
+ "boundary": "工程数据、静态截图和步骤演示;不包含 GLB、不执行真实三维交互。",
+ "fix": "生成面板和按钮移除完整离线运行的误导。",
+ "acceptance": "下载可打开、步骤导航可用,醒目标明不含三维模型。"
+ },
+ {
+ "id": "publish.downstream",
+ "group": "发布、交付与运行信息",
+ "name": "下游读取模型固定版本",
+ "source": {
+ "file": "../../../unreal_tran/src/model-content-api.js",
+ "line": 1159,
+ "symbol": "publishDocument"
+ },
+ "implementation": "现有边界",
+ "boundary": "已发布模型作为资源引用;下游自动执行模型 timeline/blueprint 尚无统一运行桥接。",
+ "fix": "本轮不将资源加载成功等同动作自动运行成功。",
+ "acceptance": "固定引用/可见性单独测;自动动作执行列为待确认范围。"
+ },
+ {
+ "id": "feedback.log",
+ "group": "发布、交付与运行信息",
+ "name": "运行日志",
+ "source": {
+ "file": "../../../unreal_tran/src/main.js",
+ "line": 3116,
+ "symbol": "log(message"
+ },
+ "implementation": "已有实现",
+ "boundary": "本次内存运行日志,最多 160 条;不是服务端审计日志。",
+ "fix": "错误信息转义,保留清晰运行结果。",
+ "acceptance": "选中/运行/保存产生相应日志,不要求刷新保留。"
+ },
+ {
+ "id": "feedback.stats",
+ "group": "发布、交付与运行信息",
+ "name": "节点 / 三角面 / FPS 等状态",
+ "source": {
+ "file": "../../../unreal_tran/src/main.js",
+ "line": 3094,
+ "symbol": "updateStats"
+ },
+ "implementation": "现有边界",
+ "boundary": "节点与三角面统计、当前 FPS;内存为估算值,不是精确浏览器内存剖析。",
+ "fix": "无本轮性能仪表重构。",
+ "acceptance": "载入模型后统计有合理变化,状态不作为性能验收数据。"
+ },
+ {
+ "id": "feedback.toast",
+ "group": "发布、交付与运行信息",
+ "name": "成功 / 错误短消息",
+ "source": {
+ "file": "../../../unreal_tran/src/main.js",
+ "line": 3117,
+ "symbol": "toast(message"
+ },
+ "implementation": "已有实现",
+ "boundary": "瞬时反馈;保存/发布成功以接口完成为准。",
+ "fix": "封面失败不伪报全部成功。",
+ "acceptance": "失败出现一次明确提示,自动消失,不形成重复常驻错误条。"
+ }
+ ]
+}
\ No newline at end of file
diff --git a/reports/model-editor-closure-20260906/index.html b/reports/model-editor-closure-20260906/index.html
new file mode 100644
index 0000000..a25292e
--- /dev/null
+++ b/reports/model-editor-closure-20260906/index.html
@@ -0,0 +1,2624 @@
+装备模型编辑器 · 全功能闭环验收 MODEL AUTHORING · CLOSURE REVIEW · 2026.09.06
装备模型编辑器 全功能闭环审查与验收 沿资料 → 产品原型 → 设计 → 开发 → 测试核查每一个可达动作。用狐狸、牛奶卡车等轻量模型验证通用编排能力,保留现有业务流程与版本数据。
+验收概况 01 资料 02 原型 03 设计 04 开发 05 测试 待确认范围 截图证据
+已列功能的记录验证通过 已验证 · 报告生成:2026/9/6 16:34:35(北京时间)。源码已接通、合同测试和真实浏览器验证分别记录;没有断言关联的功能仍为待验证。
功能清单 103 全部可达动作逐项整理
逐项证据 103 / 103 0 项仍待验证
浏览器 E2E 19 / 19 0 失败 · 最终轮已标记
截图 / 待确认 41 / 3 截图按实际文件读取
“已实现/本轮修复”是源码状态;绿色“已验证”要求独立证据。过程截图与测试总数不能自动把 103 个功能全部变成通过。
+01 / 资料
从参考资料提取通用能力 原始两份 PDF 共 11 页已由主代理完整渲染并视觉阅读。原始诉求集中在部件选择、操作步骤、交互反馈和结果追踪。本轮以狐狸、牛奶卡车等轻量模型验证部件选择、流程编排、交互反馈和结果保存。
维修训练内容设计.pdf · 电器控制系统故障维修脚本.pdf 。报告仅链接原文,不复制全文或整页图片。
+02 / 产品原型
确认用户实际使用的执行入口 内容模型列表 选中正式项目,进入模型编辑路由。
Vue 宿主 传 projectId / api=1,注入权限与未保存确认。
同源 iframe 承载 legacy-model-editor/editor.html。
真实原型源码 main.js + timeline.js + blueprint.js,经 stage 构建。
内容 API 受控资产、项目版本、保存和发布。
+03 / 设计
明确保存、运行与验证的边界 工程数据闭环 对象变换/材质/自定义字段、时间轴/蓝图和环境稳定引用必须能保存、重开、撤销及 JSON 往返。临时 blob 只用于渲染,服务端资产标识用于持久化。
导入、截图、保存与发布期间保留互斥和乐观锁;失败不得用成功消息掩盖,也不得发布半绑定资源。
界面与业务范围 网格、线框、PBR、透视和展开布局属于当前视图选项,未跨刷新保存不作为缺陷。运行日志属于本次运行记录,不等同审计和教学成绩。
现有组件新增是名称标记;现有离线 HTML 是步骤演示;下游自动执行模型动作须另行确认。
+04 / 开发
全功能覆盖矩阵 每行列出实际执行入口、实现状态、边界、对应修复及独立验证要求。静态功能清单 · 带证据的矩阵 JSON · 修复前基线与实施记录 。
全部验证状态 待验证 已验证 失败 需复核 103 项
+只读模型的搜索、原生动画选择与键盘预览 权限桥误把查看控件的键盘交互当成内容编辑,导致只读用户不能在搜索框输入、切换原生动画或按 Enter 进入预览。
按实际查看控件放行键盘输入/选择/激活;模型编辑、内容上传/保存/发布仍依对应权限阻断。
真实 DOM 权限桥 14/14 通过;最终完整19条E2E全部通过,其中管理员用例验证真实键盘搜索、原生动画切换与Enter播放/暂停,期间没有内容写请求,直接创建接口返回403。
源码 · 聚焦测试 Auth / Tran 连接有效性检查增加时限与空闲保活 数据库连接健康检查可能在驱动网络读取上长时间阻塞,拖住借用连接的请求;不能据此把每个历史503都逐条归因为此缺陷。
两个模块统一使用有时限的 SELECT 1 健康检查,默认临时网络时限 2 秒;检查结束恢复原连接超时,开启空闲检查和保活。已有普通业务 SQL 超时策略不变。
实际 Kingbase 驱动隔离探针:正常检查 12ms,原0ms和原7000ms网络超时均恢复;隔离网络黑洞约2010ms抛出 SocketTimeoutException 并关闭连接。探针不是整个平台可用性测试,后端打包和全量E2E结果另记。
源码 · 另一模块源码 · 验证证据 曲线轨道与分量控件的可读性 曲线轨道改为按钮后遗漏背景和边框样式,显示为灰底灰字;分量下拉沿用浏览器默认样式并与数值标签重叠。
补齐整宽暗色轨道按钮、主副文字与选中/悬停/键盘焦点样式;分量下拉使用暗色样式并定位右上。仅修改曲线面板CSS。
最终浏览器轮次重新操作轨道、关键帧、五种插值和分量选择并截图;可视效果以最终22与28号截图核对。
源码 · 验证证据
+工程与数据 资源库与场景树 三维视图与操作 对象属性与扩展标记 时间轴与曲线 蓝图与交互反馈 发布、交付与运行信息
+05 / 测试
执行结果与证据分层 浏览器 E2E 19/19 项通过 · 已验证 · Playwright 原始结果
通过浏览器定向注入的 409/503 等异常仅验证失败处理,不代表开发服务真实发生该故障。UI 点击、脚本调用运行时和纯 API 检查按各用例证据说明区分。
+保留首轮失败与定向重跑事实 仅保留安全的次数、名称、时间及源文件哈希;不复制请求正文、会话、错误上下文或私有运行日志。历史轮次不替代当前 results.json,定向2/2不等同全量19/19。 安全迭代记录
已修复 Auth/Tran 数据库连接健康检查可能无界阻塞的问题,并完成实际 Kingbase 驱动隔离黑洞探针(约2010ms释放关闭);最终双模块构建和完整E2E另行记录。该探针不能证明历史每一次503均已逐条与该原因建立因果关联。
完成修复后另跑完整19条,使用新的完整results.json、对应时段证据/附件、最终validation及artifact检查;旧失败保留在本历史记录。
+真实 API / 运行时断言 逐份读取 evidence/*.json。文件存在本身不代表通过;每份证据须注明断言结果和覆盖的 featureIds。
evidence/30-assets-roundtrip.json 已验证 {
+ "time": "2026-09-06T08:20:27.495Z",
+ "data": {
+ "status": "PASS",
+ "kind": "e2e+api",
+ "testTitle": "受控模型与外部环境 GLB 上传、JSON 往返和刷新重开",
+ "featureIds": [
+ "project.import-glb",
+ "environment.external",
+ "project.import-json",
+ "project.export-json",
+ "project.save"
+ ],
+ "projectId": "226",
+ "mainCode": "MODEL_MTPJKFPW_09-FOX",
+ "environmentCode": "ENV_MODEL_MTPJKH7H_01-BOX",
+ "mainUri": "fsvc://unreal-tran/model/2026/09/06/b85bd2722c4e133e61469c9f413380f0.glb",
+ "environmentUri": "fsvc://unreal-tran/model/2026/09/06/9bd7351f3ee917431354690f44a449b5.glb",
+ "modelMeshes": 1,
+ "environmentMeshes": 1,
+ "nativeClips": 3,
+ "environmentPosition": [
+ 6,
+ 0,
+ -7
+ ],
+ "environmentScale": [
+ 0.15,
+ 0.15,
+ 0.15
+ ],
+ "result": "PASS",
+ "scope": "真实上传、注册、保存、JSON重导入、刷新后的实际网格和原生动画目录;未断言下游场景执行动画。"
+ }
+} evidence/31-invalid-import.json 已验证 {
+ "time": "2026-09-06T08:20:33.535Z",
+ "data": {
+ "status": "PASS",
+ "kind": "e2e+api",
+ "testTitle": "非法 JSON 与未登记 blob 引用导入失败时保留当前文档",
+ "featureIds": [
+ "project.import-json"
+ ],
+ "projectId": "227",
+ "result": "PASS",
+ "cases": [
+ "JSON语法错误",
+ "无受控资源编号/地址的跨页面blob引用"
+ ],
+ "retainedVersion": 1
+ }
+} evidence/32-save-failure-recovery.json 已验证 {
+ "time": "2026-09-06T08:20:43.214Z",
+ "data": {
+ "status": "PASS",
+ "kind": "e2e+api",
+ "testTitle": "保存请求超时和 409 保留恢复副本,解除故障后可重试",
+ "featureIds": [
+ "project.conflict",
+ "project.save",
+ "feedback.toast"
+ ],
+ "projectId": "228",
+ "result": "PASS",
+ "intercepted": [
+ {
+ "mode": "timedout",
+ "method": "PUT",
+ "projectId": "228"
+ },
+ {
+ "mode": "409",
+ "method": "PUT",
+ "projectId": "228"
+ }
+ ],
+ "scope": "真实页面保存及真实服务端重试;失败由浏览器定向注入 timedout/409,非宣称自然发生的服务故障。"
+ }
+} evidence/34-publish-fixed-version.json 已验证 {
+ "time": "2026-09-06T08:20:51.047Z",
+ "data": {
+ "status": "PASS",
+ "kind": "e2e+api",
+ "testTitle": "直接发布同步截图封面、固定版本目录与真实离线步骤 HTML",
+ "featureIds": [
+ "publish.api",
+ "publish.cover",
+ "publish.html",
+ "publish.downstream"
+ ],
+ "projectId": "229",
+ "result": "PASS",
+ "versionId": "283",
+ "coverUri": "fsvc://unreal-tran/image/2026/09/06/14ab7b4e1e7a9d21f13f3504cb8b89ae.jpg",
+ "priorCoverUri": "fsvc://unreal-tran/image/2026/09/05/c09a49efd4423dcf8ccbc46b46d1e209.jpg",
+ "mainCode": "MODEL_MTO6V5W6_09-FOX",
+ "scope": "目录可取得固定发布版本及同版封面;HTML已真实打开并确认演示边界,未声明下游动画执行或完整离线三维运行。"
+ }
+} evidence/41-resource-search-switch.json 已验证 {
+ "time": "2026-09-06T08:22:56.960Z",
+ "data": {
+ "status": "PASS",
+ "kind": "e2e+api",
+ "testTitle": "补充资源库:搜索清空、真实卡片切换与刷新保留",
+ "featureIds": [
+ "resource.search",
+ "resource.catalog"
+ ],
+ "projectId": "235",
+ "original": {
+ "id": "imported-model_mto6v5w6_09-fox",
+ "name": "09-Fox",
+ "code": "MODEL_MTO6V5W6_09-FOX"
+ },
+ "boxId": "imported-model_mtpjnqqk_01-box",
+ "restored": {
+ "id": "imported-model_mto6v5w6_09-fox",
+ "code": "MODEL_MTO6V5W6_09-FOX",
+ "meshes": 1
+ },
+ "scope": "真实搜索与资源卡片点击、服务端保存、刷新后原模型网格;副本独立登记待清理。"
+ }
+} evidence/43-builtin-environments-tree.json 已验证 {
+ "time": "2026-09-06T08:23:19.759Z",
+ "data": {
+ "status": "PASS",
+ "kind": "e2e+api",
+ "testTitle": "补充环境与树:内置三环境保存重开、节点计数与折叠展开",
+ "featureIds": [
+ "environment.builtin",
+ "tree.browse",
+ "tree.expand",
+ "project.leave"
+ ],
+ "projectId": "236",
+ "snapshots": [
+ {
+ "type": "workshop",
+ "mode": "placeholder",
+ "sourceKind": "placeholder",
+ "nodes": 48,
+ "meshes": 45,
+ "id": "ee302132-152c-4575-a2ec-0a56b4820079",
+ "shownRows": 76,
+ "totalNodes": 76
+ },
+ {
+ "type": "outdoor",
+ "mode": "placeholder",
+ "sourceKind": "placeholder",
+ "nodes": 70,
+ "meshes": 65,
+ "id": "bf88db35-635d-4012-8df6-dd41382e624f",
+ "shownRows": 98,
+ "totalNodes": 98
+ },
+ {
+ "type": "depot",
+ "mode": "placeholder",
+ "sourceKind": "builtin-gltf",
+ "nodes": 41,
+ "meshes": 32,
+ "id": "cebab0bb-6910-4dff-b846-9978adcb3450",
+ "shownRows": 69,
+ "totalNodes": 69
+ }
+ ],
+ "depotSavedLeave": {
+ "promptedForUnsavedChanges": false,
+ "version": 5
+ },
+ "scope": "真实切换、保存和刷新三种环境;树计数与展开折叠;装备车库保存后返回列表不误报未保存。树220行上限仍属已说明边界。"
+ }
+} evidence/46-navigation-panels-autosave.json 已验证 {
+ "time": "2026-09-06T08:23:54.656Z",
+ "data": {
+ "status": "PASS",
+ "kind": "e2e+api",
+ "testTitle": "补充视口与工作区:鼠标镜头、聚焦、面板、预览导航、日志与自动保存",
+ "featureIds": [
+ "project.name",
+ "project.autosave",
+ "view.orbit",
+ "view.focus",
+ "view.panels",
+ "view.preview-step",
+ "feedback.log",
+ "feedback.stats",
+ "property.identity",
+ "property.binding",
+ "selection.scope",
+ "component.inspect"
+ ],
+ "projectId": "237",
+ "focused": {
+ "position": [
+ 11.153569378931522,
+ 8.526809324389628,
+ 13.922975975299929
+ ],
+ "target": [
+ 0.01518877997232826,
+ 2.4006999949620704,
+ 2.266009371254541e-7
+ ],
+ "distance": 18.853169271563605
+ },
+ "rotated": {
+ "position": [
+ 1.7511663652214757,
+ 6.181727848275404,
+ 18.38837160033089
+ ],
+ "target": [
+ 0.01518877997232826,
+ 2.4006999949620704,
+ 2.266009371254541e-7
+ ],
+ "distance": 18.853169271563594
+ },
+ "panned": {
+ "position": [
+ -5.273341581654527,
+ 5.082450786888863,
+ 17.990688935160133
+ ],
+ "target": [
+ -0.5866856170824521,
+ 2.73698783970429,
+ -0.11942014831462
+ ],
+ "distance": 18.85316927156359
+ },
+ "focusTargetScreen": [
+ -5.177525136316571e-19,
+ -1.227265217497261e-16,
+ 0.9980999049952497
+ ],
+ "panelModes": [
+ "timeline",
+ "blueprint",
+ "curve",
+ "console"
+ ],
+ "logCount": 8,
+ "stats": {
+ "objects": "98 节点 / 66 网格",
+ "triangles": "1,438",
+ "fps": "54"
+ },
+ "scope": "真实鼠标镜头操作;聚焦只验证相机目标;真实30秒自动保存,无测试加速定时器;统计内存估值未当作性能结论。"
+ }
+} evidence/51-gizmos.json 已验证 {
+ "time": "2026-09-06T08:22:38.815Z",
+ "data": {
+ "status": "PASS",
+ "kind": "e2e+runtime-readback",
+ "testTitle": "真实三维移动、旋转、缩放手柄拖动及撤销重做保存重开",
+ "featureIds": [
+ "view.move",
+ "view.rotate",
+ "view.scale"
+ ],
+ "projectId": "233",
+ "changes": [
+ {
+ "mode": "translate",
+ "before": [
+ 0,
+ 0,
+ 0
+ ],
+ "after": [
+ 0.7257266613731312,
+ 0,
+ 0
+ ]
+ },
+ {
+ "mode": "rotate",
+ "before": [
+ 0,
+ 0,
+ 0
+ ],
+ "after": [
+ -1.194547194225429,
+ 0,
+ 0
+ ]
+ },
+ {
+ "mode": "scale",
+ "before": [
+ 1,
+ 1,
+ 1
+ ],
+ "after": [
+ 0.30167729043770014,
+ 1,
+ 1
+ ]
+ }
+ ],
+ "reopened": {
+ "position": [
+ 0.7257266613731312,
+ 0,
+ 0
+ ],
+ "rotation": [
+ -1.194547194225429,
+ 0,
+ 0
+ ],
+ "scale": [
+ 0.30167729043770014,
+ 1,
+ 1
+ ]
+ }
+ }
+} evidence/52-leave.json 已验证 {
+ "time": "2026-09-06T08:22:48.155Z",
+ "data": {
+ "status": "PASS",
+ "kind": "e2e+api",
+ "testTitle": "未保存离开:关闭确认保留工作、保存失败留在原页、重试保存后离开",
+ "featureIds": [
+ "project.leave"
+ ],
+ "projectId": "234",
+ "closeKeptDraft": true,
+ "failureKeptDraft": true,
+ "retriedSavePersisted": true
+ }
+} evidence/60-project-recovery.json 已验证 {
+ "time": "2026-09-06T08:24:07.184Z",
+ "data": {
+ "status": "PASS",
+ "kind": "e2e+api",
+ "testTitle": "真实未保存稿同版本恢复并显式保存;旧版本恢复稿下载隔离保留",
+ "featureIds": [
+ "project.recovery"
+ ],
+ "projectId": "238",
+ "sameVersion": 1,
+ "explicitSaveVersion": 2,
+ "concurrentVersion": 3,
+ "retainedFilename": "recovery-238-isolated.json",
+ "scope": "通过真实beforeunload创建未保存稿;同版恢复后仅显式保存落库;旧版只下载隔离,不自动覆盖服务端。丢弃/跨标签竞态另由既有恢复合同测试覆盖。"
+ }
+} evidence/63-loading-publish-preflight.json 已验证 {
+ "time": "2026-09-06T08:24:17.025Z",
+ "data": {
+ "status": "PASS",
+ "kind": "e2e+api",
+ "testTitle": "受控 GLB 下载失败阻止编辑,重试成功;发布预检开关无写入且拒绝错误资源 JSON",
+ "featureIds": [
+ "resource.loading",
+ "publish.preflight"
+ ],
+ "projectId": "239",
+ "failedDownloads": 1,
+ "retainedVersion": 1,
+ "scope": "真实受控下载定向503→中性失败与禁止编辑→解除故障后UI重试成功;预检面板开关不写项目,错误资源JSON在替换当前模型之前被拒绝。服务端发布时code/URI一致性拒绝由ContentProjectServiceTest合同回归覆盖,未把面板绿标代替服务端校验。"
+ }
+} evidence/new-project.json 已验证 {
+ "time": "2026-09-06T08:22:24.723Z",
+ "data": {
+ "status": "PASS",
+ "kind": "e2e+api",
+ "testTitle": "新建工程取消及失败保留输入;成功创建新ID且不覆盖当前模型",
+ "featureIds": [
+ "project.new"
+ ],
+ "oldId": "168",
+ "newId": "232",
+ "cancelPreserved": true,
+ "failureRetainedName": true,
+ "originalVersion": 17,
+ "sourceUnchanged": true
+ }
+} evidence/permissions.json 已验证 {
+ "time": "2026-09-06T08:22:29.489Z",
+ "data": {
+ "status": "PASS",
+ "kind": "e2e+api",
+ "testTitle": "管理员只读权限:编辑、新建、导入、保存发布均不可越权",
+ "featureIds": [
+ "project.permissions"
+ ],
+ "scope": "管理员只读UI、真实键盘搜索、原生片段切换与Enter播放/暂停、预览无内容写请求及创建403;纯publisher无需update的发布流程由独立权限合同回归补充。",
+ "readonlyUi": true,
+ "readonlySearch": true,
+ "nativeClipChanged": true,
+ "keyboardPreview": true,
+ "previewWriteRequests": 0,
+ "createStatus": 403
+ }
+} evidence/properties.json 已验证 {
+ "time": "2026-09-06T08:21:58.738Z",
+ "data": {
+ "status": "PASS",
+ "kind": "e2e+api",
+ "testTitle": "对象名称、变换、PBR、属性与扩展标记保存重开;撤销重做",
+ "featureIds": [
+ "property.name",
+ "property.position",
+ "property.rotation",
+ "property.scale",
+ "property.color",
+ "property.metalness",
+ "property.roughness",
+ "property.opacity",
+ "property.code",
+ "property.status",
+ "component.tag",
+ "project.undo",
+ "project.redo",
+ "project.save"
+ ],
+ "scope": "真实属性输入/材质参数/标签、保存与刷新;变换只验数值输入,不代替三维 gizmo 拖动。",
+ "state": {
+ "id": "model-imported-model_mto6v5w6_09-fox-26",
+ "position": [
+ 1.25,
+ 0,
+ 0
+ ],
+ "rotation": [
+ 0,
+ 0.2617993877991494,
+ 0
+ ],
+ "scale": [
+ 1,
+ 1,
+ 1.1
+ ],
+ "customProperties": {
+ "resourceCode": "CLOSURE-FOX-001",
+ "operatingStatus": "SERVICE"
+ },
+ "customComponents": [
+ "扩展标记 1",
+ "扩展标记 2",
+ "扩展标记 3",
+ "扩展标记 4",
+ "扩展标记 5"
+ ]
+ },
+ "serverVersion": 15,
+ "persisted": true
+ }
+} evidence/truck-blueprint-events.json 已验证 {
+ "time": "2026-09-06T08:21:50.227Z",
+ "data": {
+ "status": "PASS",
+ "kind": "e2e",
+ "testTitle": "已保存蓝图:条件出口与目标匹配 runtime 检查,真实模型单击/双击/进入预览自动触发",
+ "featureIds": [
+ "blueprint.click",
+ "blueprint.double-click",
+ "blueprint.auto",
+ "view.preview"
+ ],
+ "projectId": "169",
+ "runtimeSupplement": {
+ "status": "PASS",
+ "kind": "runtime",
+ "featureIds": [
+ "blueprint.condition"
+ ],
+ "branches": {
+ "falseBranch": {
+ "result": true,
+ "visitedFinish": false,
+ "logs": [
+ {
+ "time": "16:21:41",
+ "message": "开始执行 · 闭环-条件判断",
+ "level": "run",
+ "nodeId": "node-mtpjlz9t-p124m"
+ },
+ {
+ "time": "16:21:41",
+ "message": "执行节点:闭环-条件判断",
+ "level": "run",
+ "nodeId": "node-mtpjlz9t-p124m"
+ },
+ {
+ "time": "16:21:41",
+ "message": "条件结果:否 / FALSE",
+ "level": "warning",
+ "nodeId": "node-mtpjlz9t-p124m"
+ },
+ {
+ "time": "16:21:41",
+ "message": "节点“闭环-条件判断”没有后续连线,流程结束",
+ "level": "warning",
+ "nodeId": "node-mtpjlz9t-p124m"
+ },
+ {
+ "time": "16:21:41",
+ "message": "蓝图执行完成 · 1 个节点",
+ "level": "success",
+ "nodeId": null
+ }
+ ]
+ },
+ "trueBranch": {
+ "result": true,
+ "visitedFinish": true,
+ "logs": [
+ {
+ "time": "16:21:41",
+ "message": "开始执行 · 闭环-条件判断",
+ "level": "run",
+ "nodeId": "node-mtpjlz9t-p124m"
+ },
+ {
+ "time": "16:21:41",
+ "message": "执行节点:闭环-条件判断",
+ "level": "run",
+ "nodeId": "node-mtpjlz9t-p124m"
+ },
+ {
+ "time": "16:21:41",
+ "message": "条件结果:是 / TRUE",
+ "level": "success",
+ "nodeId": "node-mtpjlz9t-p124m"
+ },
+ {
+ "time": "16:21:41",
+ "message": "执行节点:闭环-流程完成",
+ "level": "run",
+ "nodeId": "node-mtpjlznf-cxlzk"
+ },
+ {
+ "time": "16:21:41",
+ "message": "蓝图执行完成 · 2 个节点",
+ "level": "success",
+ "nodeId": null
+ }
+ ]
+ }
+ },
+ "mismatch": {
+ "result": false,
+ "started": false
+ }
+ },
+ "browserEvents": [
+ {
+ "event": "click",
+ "starts": 1,
+ "finished": true,
+ "source": "真实 renderer canvas 指针事件",
+ "logs": [
+ {
+ "time": "16:21:42",
+ "message": "运行状态已重置",
+ "level": "system",
+ "nodeId": null
+ },
+ {
+ "time": "16:21:42",
+ "message": "开始执行 · 闭环-点击触发",
+ "level": "run",
+ "nodeId": "node-mtpjlx7c-sgcyx"
+ },
+ {
+ "time": "16:21:42",
+ "message": "执行节点:闭环-点击触发",
+ "level": "run",
+ "nodeId": "node-mtpjlx7c-sgcyx"
+ },
+ {
+ "time": "16:21:42",
+ "message": "执行节点:闭环-播放时间轴",
+ "level": "run",
+ "nodeId": "node-mtpjlxj9-y3g3v"
+ },
+ {
+ "time": "16:21:42",
+ "message": "执行节点:闭环-等待",
+ "level": "run",
+ "nodeId": "node-mtpjlxxe-ond97"
+ },
+ {
+ "time": "16:21:42",
+ "message": "执行节点:闭环-高亮部件",
+ "level": "run",
+ "nodeId": "node-mtpjlyci-4c5oi"
+ },
+ {
+ "time": "16:21:42",
+ "message": "执行节点:闭环-操作提示",
+ "level": "run",
+ "nodeId": "node-mtpjlyuw-164xj"
+ },
+ {
+ "time": "16:21:43",
+ "message": "执行节点:闭环-条件判断",
+ "level": "run",
+ "nodeId": "node-mtpjlz9t-p124m"
+ },
+ {
+ "time": "16:21:43",
+ "message": "条件结果:是 / TRUE",
+ "level": "success",
+ "nodeId": "node-mtpjlz9t-p124m"
+ },
+ {
+ "time": "16:21:43",
+ "message": "执行节点:闭环-流程完成",
+ "level": "run",
+ "nodeId": "node-mtpjlznf-cxlzk"
+ },
+ {
+ "time": "16:21:43",
+ "message": "蓝图执行完成 · 7 个节点",
+ "level": "success",
+ "nodeId": null
+ }
+ ]
+ },
+ {
+ "event": "double-click",
+ "starts": 1,
+ "finished": true,
+ "source": "真实 renderer canvas 指针事件",
+ "logs": [
+ {
+ "time": "16:21:43",
+ "message": "运行状态已重置",
+ "level": "system",
+ "nodeId": null
+ },
+ {
+ "time": "16:21:44",
+ "message": "开始执行 · 闭环-点击触发",
+ "level": "run",
+ "nodeId": "node-mtpjlx7c-sgcyx"
+ },
+ {
+ "time": "16:21:44",
+ "message": "执行节点:闭环-点击触发",
+ "level": "run",
+ "nodeId": "node-mtpjlx7c-sgcyx"
+ },
+ {
+ "time": "16:21:44",
+ "message": "执行节点:闭环-播放时间轴",
+ "level": "run",
+ "nodeId": "node-mtpjlxj9-y3g3v"
+ },
+ {
+ "time": "16:21:44",
+ "message": "执行节点:闭环-等待",
+ "level": "run",
+ "nodeId": "node-mtpjlxxe-ond97"
+ },
+ {
+ "time": "16:21:44",
+ "message": "执行节点:闭环-高亮部件",
+ "level": "run",
+ "nodeId": "node-mtpjlyci-4c5oi"
+ },
+ {
+ "time": "16:21:44",
+ "message": "执行节点:闭环-操作提示",
+ "level": "run",
+ "nodeId": "node-mtpjlyuw-164xj"
+ },
+ {
+ "time": "16:21:45",
+ "message": "执行节点:闭环-条件判断",
+ "level": "run",
+ "nodeId": "node-mtpjlz9t-p124m"
+ },
+ {
+ "time": "16:21:45",
+ "message": "条件结果:是 / TRUE",
+ "level": "success",
+ "nodeId": "node-mtpjlz9t-p124m"
+ },
+ {
+ "time": "16:21:45",
+ "message": "执行节点:闭环-流程完成",
+ "level": "run",
+ "nodeId": "node-mtpjlznf-cxlzk"
+ },
+ {
+ "time": "16:21:45",
+ "message": "蓝图执行完成 · 7 个节点",
+ "level": "success",
+ "nodeId": null
+ }
+ ]
+ },
+ {
+ "event": "auto",
+ "starts": 1,
+ "finished": true,
+ "source": "真实点击进入预览",
+ "logs": [
+ {
+ "time": "16:21:45",
+ "message": "运行状态已重置",
+ "level": "system",
+ "nodeId": null
+ },
+ {
+ "time": "16:21:45",
+ "message": "开始执行 · 闭环-点击触发",
+ "level": "run",
+ "nodeId": "node-mtpjlx7c-sgcyx"
+ },
+ {
+ "time": "16:21:45",
+ "message": "执行节点:闭环-点击触发",
+ "level": "run",
+ "nodeId": "node-mtpjlx7c-sgcyx"
+ },
+ {
+ "time": "16:21:45",
+ "message": "执行节点:闭环-播放时间轴",
+ "level": "run",
+ "nodeId": "node-mtpjlxj9-y3g3v"
+ },
+ {
+ "time": "16:21:45",
+ "message": "执行节点:闭环-等待",
+ "level": "run",
+ "nodeId": "node-mtpjlxxe-ond97"
+ },
+ {
+ "time": "16:21:45",
+ "message": "执行节点:闭环-高亮部件",
+ "level": "run",
+ "nodeId": "node-mtpjlyci-4c5oi"
+ },
+ {
+ "time": "16:21:46",
+ "message": "执行节点:闭环-操作提示",
+ "level": "run",
+ "nodeId": "node-mtpjlyuw-164xj"
+ },
+ {
+ "time": "16:21:46",
+ "message": "执行节点:闭环-条件判断",
+ "level": "run",
+ "nodeId": "node-mtpjlz9t-p124m"
+ },
+ {
+ "time": "16:21:46",
+ "message": "条件结果:是 / TRUE",
+ "level": "success",
+ "nodeId": "node-mtpjlz9t-p124m"
+ },
+ {
+ "time": "16:21:46",
+ "message": "执行节点:闭环-流程完成",
+ "level": "run",
+ "nodeId": "node-mtpjlznf-cxlzk"
+ },
+ {
+ "time": "16:21:46",
+ "message": "蓝图执行完成 · 7 个节点",
+ "level": "success",
+ "nodeId": null
+ }
+ ]
+ }
+ ],
+ "persistedNodeCount": 7,
+ "persistedEdgeCount": 6,
+ "scope": "执行使用正式模型编辑器的真实模型/时间轴/高亮/提示 handler;不等同于学员端任务下发。"
+ }
+} evidence/truck-blueprint-tool-supplement.json 已验证 {
+ "time": "2026-09-06T08:21:23.003Z",
+ "data": {
+ "status": "PASS",
+ "kind": "e2e+runtime-read",
+ "testTitle": "补充时间轴与蓝图工具:刻度/按键/记录/分量、基准匹配、节点搜索拖入与画布平移",
+ "featureIds": [
+ "timeline.seek",
+ "timeline.record",
+ "timeline.baseline",
+ "curve.axis",
+ "blueprint.palette",
+ "blueprint.add",
+ "blueprint.canvas"
+ ],
+ "projectIds": [
+ "230",
+ "231"
+ ],
+ "pose": {
+ "time": 0.9666666666666666,
+ "value": [
+ 2.4166666666666665,
+ -0.9666666666666666,
+ 0
+ ],
+ "actual": [
+ 2.4166666666666665,
+ -0.9666666666666666,
+ 0
+ ],
+ "base": [
+ 0,
+ 0,
+ 0
+ ]
+ },
+ "recorded": {
+ "id": "key-u60x1lh",
+ "time": 0.9666666666666666,
+ "value": [
+ 2.4166666666666665,
+ -0.9666666666666666,
+ 0
+ ],
+ "easing": "easeInOut"
+ },
+ "curveAxis": {
+ "xAndYPathsDiffer": true,
+ "values": [
+ 5,
+ -2,
+ 0
+ ]
+ },
+ "noMatchPreserved": true,
+ "baseline": {
+ "name": "闭环工具补充-基准匹配-mtpjlmiy",
+ "timeline": {
+ "version": 1,
+ "duration": 12,
+ "currentTime": 0,
+ "loop": true,
+ "zoom": 1,
+ "tracks": [
+ {
+ "id": "bridge-front-outrigger-drop",
+ "name": "前支腿下落支撑",
+ "targetId": "bridge-front-outrigger",
+ "property": "position",
+ "color": "#ffd166",
+ "muted": false,
+ "keyframes": [
+ {
+ "id": "bfo-k0",
+ "time": 0,
+ "value": [
+ 0,
+ 0,
+ 0
+ ],
+ "easing": "easeInOut"
+ },
+ {
+ "id": "bfo-k1",
+ "time": 2.4,
+ "value": [
+ 0,
+ -0.45,
+ 0
+ ],
+ "easing": "easeInOut"
+ },
+ {
+ "id": "bfo-k2",
+ "time": 10,
+ "value": [
+ 0,
+ -0.45,
+ 0
+ ],
+ "easing": "linear"
+ },
+ {
+ "id": "bfo-k3",
+ "time": 12,
+ "value": [
+ 0,
+ 0,
+ 0
+ ],
+ "easing": "easeInOut"
+ }
+ ]
+ },
+ {
+ "id": "bridge-rear-outrigger-drop",
+ "name": "后支腿下落支撑",
+ "targetId": "bridge-rear-outrigger",
+ "property": "position",
+ "color": "#f4a261",
+ "muted": false,
+ "keyframes": [
+ {
+ "id": "bro-k0",
+ "time": 0,
+ "value": [
+ 0,
+ 0,
+ 0
+ ],
+ "easing": "easeInOut"
+ },
+ {
+ "id": "bro-k1",
+ "time": 2.2,
+ "value": [
+ 0,
+ -0.19,
+ 0
+ ],
+ "easing": "easeInOut"
+ },
+ {
+ "id": "bro-k2",
+ "time": 10,
+ "value": [
+ 0,
+ -0.19,
+ 0
+ ],
+ "easing": "linear"
+ },
+ {
+ "id": "bro-k3",
+ "time": 12,
+ "value": [
+ 0,
+ 0,
+ 0
+ ],
+ "easing": "easeInOut"
+ }
+ ]
+ },
+ {
+ "id": "bridge-rear-support-drop",
+ "name": "后支座下落支撑",
+ "targetId": "bridge-rear-support",
+ "property": "position",
+ "color": "#e76f51",
+ "muted": false,
+ "keyframes": [
+ {
+ "id": "brs-k0",
+ "time": 0,
+ "value": [
+ 0,
+ 0,
+ 0
+ ],
+ "easing": "easeInOut"
+ },
+ {
+ "id": "brs-k1",
+ "time": 0.4,
+ "value": [
+ 0,
+ 0,
+ 0
+ ],
+ "easing": "easeInOut"
+ },
+ {
+ "id": "brs-k2",
+ "time": 2.6,
+ "value": [
+ 0,
+ -0.25,
+ 0
+ ],
+ "easing": "easeInOut"
+ },
+ {
+ "id": "brs-k3",
+ "time": 10.2,
+ "value": [
+ 0,
+ -0.25,
+ 0
+ ],
+ "easing": "linear"
+ },
+ {
+ "id": "brs-k4",
+ "time": 12,
+ "value": [
+ 0,
+ 0,
+ 0
+ ],
+ "easing": "easeInOut"
+ }
+ ]
+ },
+ {
+ "id": "bridge-gantry-slew",
+ "name": "回转门架回转",
+ "targetId": "bridge-gantry",
+ "property": "rotation.y",
+ "color": "#2a9d8f",
+ "muted": false,
+ "keyframes": [
+ {
+ "id": "bgs-k0",
+ "time": 0,
+ "value": 0,
+ "easing": "easeInOut"
+ },
+ {
+ "id": "bgs-k1",
+ "time": 2.2,
+ "value": 0,
+ "easing": "easeInOut"
+ },
+ {
+ "id": "bgs-k2",
+ "time": 4.8,
+ "value": 0.24,
+ "easing": "easeInOut"
+ },
+ {
+ "id": "bgs-k3",
+ "time": 8.6,
+ "value": 0.24,
+ "easing": "linear"
+ },
+ {
+ "id": "bgs-k4",
+ "time": 11.4,
+ "value": 0,
+ "easing": "easeInOut"
+ },
+ {
+ "id": "bgs-k5",
+ "time": 12,
+ "value": 0,
+ "easing": "easeInOut"
+ }
+ ]
+ },
+ {
+ "id": "bridge-gantry-upper-luffing",
+ "name": "门架上臂变幅",
+ "targetId": "bridge-gantry-upper",
+ "property": "rotation.x",
+ "color": "#8a68e8",
+ "muted": false,
+ "keyframes": [
+ {
+ "id": "bgu-k0",
+ "time": 0,
+ "value": 0,
+ "easing": "easeInOut"
+ },
+ {
+ "id": "bgu-k1",
+ "time": 3,
+ "value": 0,
+ "easing": "easeInOut"
+ },
+ {
+ "id": "bgu-k2",
+ "time": 5.4,
+ "value": -0.22,
+ "easing": "easeInOut"
+ },
+ {
+ "id": "bgu-k3",
+ "time": 8.8,
+ "value": -0.22,
+ "easing": "linear"
+ },
+ {
+ "id": "bgu-k4",
+ "time": 11.2,
+ "value": 0,
+ "easing": "easeInOut"
+ },
+ {
+ "id": "bgu-k5",
+ "time": 12,
+ "value": 0,
+ "easing": "easeInOut"
+ }
+ ]
+ },
+ {
+ "id": "bridge-erection-frame-luffing",
+ "name": "后架设架起落(抬头 → 落桥)",
+ "targetId": "bridge-erection-frame",
+ "property": "rotation.x",
+ "color": "#4cc9f0",
+ "muted": false,
+ "keyframes": [
+ {
+ "id": "bef-k0",
+ "time": 0,
+ "value": 0,
+ "easing": "easeInOut"
+ },
+ {
+ "id": "bef-k1",
+ "time": 3.6,
+ "value": 0,
+ "easing": "easeInOut"
+ },
+ {
+ "id": "bef-k2",
+ "time": 5,
+ "value": -0.07,
+ "easing": "easeInOut"
+ },
+ {
+ "id": "bef-k3",
+ "time": 7.8,
+ "value": 0.2,
+ "easing": "easeInOut"
+ },
+ {
+ "id": "bef-k4",
+ "time": 9.8,
+ "value": 0.2,
+ "easing": "linear"
+ },
+ {
+ "id": "bef-k5",
+ "time": 11.6,
+ "value": 0,
+ "easing": "easeInOut"
+ },
+ {
+ "id": "bef-k6",
+ "time": 12,
+ "value": 0,
+ "easing": "easeInOut"
+ }
+ ]
+ },
+ {
+ "id": "bridge-luffing-cylinder-follow",
+ "name": "变幅缸随动",
+ "targetId": "bridge-luffing-cylinder",
+ "property": "rotation.x",
+ "color": "#90be6d",
+ "muted": false,
+ "keyframes": [
+ {
+ "id": "blc-k0",
+ "time": 0,
+ "value": 0,
+ "easing": "easeInOut"
+ },
+ {
+ "id": "blc-k1",
+ "time": 3.6,
+ "value": 0,
+ "easing": "easeInOut"
+ },
+ {
+ "id": "blc-k2",
+ "time": 5,
+ "value": -0.03,
+ "easing": "easeInOut"
+ },
+ {
+ "id": "blc-k3",
+ "time": 7.8,
+ "value": 0.09,
+ "easing": "easeInOut"
+ },
+ {
+ "id": "blc-k4",
+ "time": 9.8,
+ "value": 0.09,
+ "easing": "linear"
+ },
+ {
+ "id": "blc-k5",
+ "time": 11.6,
+ "value": 0,
+ "easing": "easeInOut"
+ },
+ {
+ "id": "blc-k6",
+ "time": 12,
+ "value": 0,
+ "easing": "easeInOut"
+ }
+ ]
+ },
+ {
+ "id": "bridge-carriage-travel",
+ "name": "移动架沿滑道后行",
+ "targetId": "bridge-carriage",
+ "property": "position",
+ "color": "#32b8e6",
+ "muted": false,
+ "keyframes": [
+ {
+ "id": "bct-k0",
+ "time": 0,
+ "value": [
+ 0,
+ 0,
+ 0
+ ],
+ "easing": "easeInOut"
+ },
+ {
+ "id": "bct-k1",
+ "time": 5.4,
+ "value": [
+ 0,
+ 0,
+ 0
+ ],
+ "easing": "easeInOut"
+ },
+ {
+ "id": "bct-k2",
+ "time": 8.4,
+ "value": [
+ 0,
+ 0,
+ -0.42
+ ],
+ "easing": "easeInOut"
+ },
+ {
+ "id": "bct-k3",
+ "time": 10,
+ "value": [
+ 0,
+ 0,
+ -0.42
+ ],
+ "easing": "linear"
+ },
+ {
+ "id": "bct-k4",
+ "time": 12,
+ "value": [
+ 0,
+ 0,
+ 0
+ ],
+ "easing": "easeInOut"
+ }
+ ]
+ },
+ {
+ "id": "bridge-launch-pinion-drive",
+ "name": "导梁销齿轮驱动",
+ "targetId": "bridge-launch-pinion",
+ "property": "rotation.x",
+ "color": "#f9c74f",
+ "muted": false,
+ "keyframes": [
+ {
+ "id": "blp-k0",
+ "time": 0,
+ "value": 0,
+ "easing": "linear"
+ },
+ {
+ "id": "blp-k1",
+ "time": 5.4,
+ "value": 0,
+ "easing": "linear"
+ },
+ {
+ "id": "blp-k2",
+ "time": 8.4,
+ "value": 5.2,
+ "easing": "linear"
+ },
+ {
+ "id": "blp-k3",
+ "time": 10,
+ "value": 5.2,
+ "easing": "linear"
+ },
+ {
+ "id": "blp-k4",
+ "time": 12,
+ "value": 0,
+ "easing": "linear"
+ }
+ ]
+ },
+ {
+ "id": "bridge-front-cradle-luffing",
+ "name": "前托架抬头",
+ "targetId": "bridge-front-cradle",
+ "property": "rotation.x",
+ "color": "#ff9f2f",
+ "muted": false,
+ "keyframes": [
+ {
+ "id": "bfc-k0",
+ "time": 0,
+ "value": 0,
+ "easing": "easeInOut"
+ },
+ {
+ "id": "bfc-k1",
+ "time": 6.8,
+ "value": 0,
+ "easing": "easeInOut"
+ },
+ {
+ "id": "bfc-k2",
+ "time": 9,
+ "value": 0.12,
+ "easing": "easeInOut"
+ },
+ {
+ "id": "bfc-k3",
+ "time": 10.4,
+ "value": 0.12,
+ "easing": "linear"
+ },
+ {
+ "id": "bfc-k4",
+ "time": 11.8,
+ "value": 0,
+ "easing": "easeInOut"
+ },
+ {
+ "id": "bfc-k5",
+ "time": 12,
+ "value": 0,
+ "easing": "easeInOut"
+ }
+ ]
+ },
+ {
+ "id": "bridge-front-pusher-extend",
+ "name": "前推送装置送桥",
+ "targetId": "bridge-front-pusher",
+ "property": "position",
+ "color": "#ef476f",
+ "muted": false,
+ "keyframes": [
+ {
+ "id": "bfp-k0",
+ "time": 0,
+ "value": [
+ 0,
+ 0,
+ 0
+ ],
+ "easing": "easeInOut"
+ },
+ {
+ "id": "bfp-k1",
+ "time": 7.2,
+ "value": [
+ 0,
+ 0,
+ 0
+ ],
+ "easing": "easeInOut"
+ },
+ {
+ "id": "bfp-k2",
+ "time": 9.4,
+ "value": [
+ 0,
+ 0,
+ 0.34
+ ],
+ "easing": "easeInOut"
+ },
+ {
+ "id": "bfp-k3",
+ "time": 10.6,
+ "value": [
+ 0,
+ 0,
+ 0.34
+ ],
+ "easing": "linear"
+ },
+ {
+ "id": "bfp-k4",
+ "time": 12,
+ "value": [
+ 0,
+ 0,
+ 0
+ ],
+ "easing": "easeInOut"
+ }
+ ]
+ },
+ {
+ "id": "bridge-feed-pinion-drive",
+ "name": "前推送销齿轮驱动",
+ "targetId": "bridge-feed-pinion",
+ "property": "rotation.x",
+ "color": "#c77dff",
+ "muted": false,
+ "keyframes": [
+ {
+ "id": "bfd-k0",
+ "time": 0,
+ "value": 0,
+ "easing": "linear"
+ },
+ {
+ "id": "bfd-k1",
+ "time": 7.2,
+ "value": 0,
+ "easing": "linear"
+ },
+ {
+ "id": "bfd-k2",
+ "time": 9.4,
+ "value": 4,
+ "easing": "linear"
+ },
+ {
+ "id": "bfd-k3",
+ "time": 10.6,
+ "value": 4,
+ "easing": "linear"
+ },
+ {
+ "id": "bfd-k4",
+ "time": 12,
+ "value": 0,
+ "easing": "linear"
+ }
+ ]
+ }
+ ]
+ },
+ "resolved": true
+ },
+ "addedNode": {
+ "id": "node-mtpjlj3c-n13gc",
+ "type": "message",
+ "title": "操作提示",
+ "x": 440,
+ "y": 160,
+ "params": {
+ "title": "操作提示",
+ "text": "请观察铲斗与动臂的联动过程",
+ "level": "info"
+ }
+ },
+ "draggedNode": {
+ "id": "node-mtpjlj9e-sko2s",
+ "type": "wait",
+ "title": "等待",
+ "x": -40,
+ "y": -170,
+ "params": {
+ "duration": 600
+ }
+ },
+ "canvasPreservedGraph": true,
+ "scope": "真实模型编辑参数/基准轨道生成验收,不构成装备操作或维修指导。桥梁原工程94仅GET和copy。"
+ }
+} evidence/truck-blueprint-ui.json 已验证 {
+ "time": "2026-09-06T08:21:37.144Z",
+ "data": {
+ "status": "PASS",
+ "kind": "e2e+runtime-read",
+ "testTitle": "蓝图真实节点库与端口拖线:七种参数、内部边复制/剪切、撤销重做及保存",
+ "featureIds": [
+ "blueprint.parameters",
+ "blueprint.move",
+ "blueprint.connect",
+ "blueprint.delete",
+ "blueprint.copy",
+ "blueprint.run",
+ "blueprint.stop",
+ "blueprint.reset",
+ "blueprint.timeline",
+ "blueprint.wait",
+ "blueprint.highlight",
+ "blueprint.message",
+ "blueprint.finish",
+ "project.undo",
+ "project.redo"
+ ],
+ "scope": "节点库点选添加及实际拖动/接线/执行;不把点选当成节点拖入,不把适应画布当成鼠标平移缩放全部已验。",
+ "projectId": "169",
+ "graph": {
+ "nodes": [
+ {
+ "id": "node-mtpjlx7c-sgcyx",
+ "type": "trigger-click",
+ "title": "闭环-点击触发",
+ "x": 160,
+ "y": -120,
+ "params": {
+ "event": "click",
+ "target": "model-imported-model_mto6wi6k_08-cesiummilktruck-3"
+ }
+ },
+ {
+ "id": "node-mtpjlxj9-y3g3v",
+ "type": "play-timeline",
+ "title": "闭环-播放时间轴",
+ "x": 420,
+ "y": -120,
+ "params": {
+ "clip": "牛奶卡车曲线联动",
+ "from": 8,
+ "speed": 2,
+ "waitForEnd": true
+ }
+ },
+ {
+ "id": "node-mtpjlxxe-ond97",
+ "type": "wait",
+ "title": "闭环-等待",
+ "x": 680,
+ "y": -120,
+ "params": {
+ "duration": 100
+ }
+ },
+ {
+ "id": "node-mtpjlyci-4c5oi",
+ "type": "highlight",
+ "title": "闭环-高亮部件",
+ "x": 160,
+ "y": 70,
+ "params": {
+ "target": "model-imported-model_mto6wi6k_08-cesiummilktruck-3",
+ "color": "#ff8800",
+ "pulse": false
+ }
+ },
+ {
+ "id": "node-mtpjlyuw-164xj",
+ "type": "message",
+ "title": "闭环-操作提示",
+ "x": 420,
+ "y": 70,
+ "params": {
+ "title": "卡车模型闭环提示",
+ "text": "检查真实目标高亮、时间轴完成和条件出口。",
+ "level": "success"
+ }
+ },
+ {
+ "id": "node-mtpjlz9t-p124m",
+ "type": "condition",
+ "title": "闭环-条件判断",
+ "x": 680,
+ "y": 70,
+ "params": {
+ "variable": "timelineComplete",
+ "operator": "==",
+ "value": true
+ }
+ },
+ {
+ "id": "node-mtpjlznf-cxlzk",
+ "type": "finish",
+ "title": "闭环-流程完成",
+ "x": 420,
+ "y": 260,
+ "params": {
+ "result": "success",
+ "text": "牛奶卡车蓝图验证完成"
+ }
+ }
+ ],
+ "edges": [
+ {
+ "id": "edge-mtpjm04u-tt0qb",
+ "from": {
+ "node": "node-mtpjlx7c-sgcyx",
+ "port": "then"
+ },
+ "to": {
+ "node": "node-mtpjlxj9-y3g3v",
+ "port": "in"
+ }
+ },
+ {
+ "id": "edge-mtpjm0bc-mnf7c",
+ "from": {
+ "node": "node-mtpjlxj9-y3g3v",
+ "port": "then"
+ },
+ "to": {
+ "node": "node-mtpjlxxe-ond97",
+ "port": "in"
+ }
+ },
+ {
+ "id": "edge-mtpjm0gx-mu6ys",
+ "from": {
+ "node": "node-mtpjlxxe-ond97",
+ "port": "then"
+ },
+ "to": {
+ "node": "node-mtpjlyci-4c5oi",
+ "port": "in"
+ }
+ },
+ {
+ "id": "edge-mtpjm0nr-y06n7",
+ "from": {
+ "node": "node-mtpjlyci-4c5oi",
+ "port": "then"
+ },
+ "to": {
+ "node": "node-mtpjlyuw-164xj",
+ "port": "in"
+ }
+ },
+ {
+ "id": "edge-mtpjm0ts-px732",
+ "from": {
+ "node": "node-mtpjlyuw-164xj",
+ "port": "then"
+ },
+ "to": {
+ "node": "node-mtpjlz9t-p124m",
+ "port": "in"
+ }
+ },
+ {
+ "id": "edge-mtpjm0zf-sa3h2",
+ "from": {
+ "node": "node-mtpjlz9t-p124m",
+ "port": "true"
+ },
+ "to": {
+ "node": "node-mtpjlznf-cxlzk",
+ "port": "in"
+ }
+ }
+ ],
+ "running": false,
+ "logs": [
+ {
+ "time": "16:21:35",
+ "message": "运行状态已重置",
+ "level": "system",
+ "nodeId": null
+ }
+ ]
+ },
+ "completedLog": [
+ {
+ "time": "16:21:25",
+ "message": "蓝图编辑器就绪 · 7 类节点已加载",
+ "level": "system",
+ "nodeId": null
+ },
+ {
+ "time": "16:21:33",
+ "message": "开始执行 · 闭环-点击触发",
+ "level": "run",
+ "nodeId": "node-mtpjlx7c-sgcyx"
+ },
+ {
+ "time": "16:21:33",
+ "message": "执行节点:闭环-点击触发",
+ "level": "run",
+ "nodeId": "node-mtpjlx7c-sgcyx"
+ },
+ {
+ "time": "16:21:33",
+ "message": "执行节点:闭环-播放时间轴",
+ "level": "run",
+ "nodeId": "node-mtpjlxj9-y3g3v"
+ },
+ {
+ "time": "16:21:33",
+ "message": "执行节点:闭环-等待",
+ "level": "run",
+ "nodeId": "node-mtpjlxxe-ond97"
+ },
+ {
+ "time": "16:21:33",
+ "message": "执行节点:闭环-高亮部件",
+ "level": "run",
+ "nodeId": "node-mtpjlyci-4c5oi"
+ },
+ {
+ "time": "16:21:34",
+ "message": "执行节点:闭环-操作提示",
+ "level": "run",
+ "nodeId": "node-mtpjlyuw-164xj"
+ },
+ {
+ "time": "16:21:35",
+ "message": "执行节点:闭环-条件判断",
+ "level": "run",
+ "nodeId": "node-mtpjlz9t-p124m"
+ },
+ {
+ "time": "16:21:35",
+ "message": "条件结果:是 / TRUE",
+ "level": "success",
+ "nodeId": "node-mtpjlz9t-p124m"
+ },
+ {
+ "time": "16:21:35",
+ "message": "执行节点:闭环-流程完成",
+ "level": "run",
+ "nodeId": "node-mtpjlznf-cxlzk"
+ },
+ {
+ "time": "16:21:35",
+ "message": "蓝图执行完成 · 7 个节点",
+ "level": "success",
+ "nodeId": null
+ }
+ ],
+ "actions": "真实节点库/参数控件/端口拖线/复制剪切粘贴/删除/撤销重做/运行停止重置/保存",
+ "boundaries": "200 步循环保护由独立 Node 回归覆盖;本浏览器流程不注入假图或替换执行 handler。"
+ }
+} evidence/truck-timeline-curve.json 已验证 {
+ "time": "2026-09-06T08:21:02.898Z",
+ "data": {
+ "status": "PASS",
+ "kind": "e2e+runtime-read",
+ "testTitle": "时间轴真实控件:轨道/关键帧/播放与曲线 5 插值,保存后重新加载一致",
+ "featureIds": [
+ "timeline.play",
+ "timeline.stop",
+ "timeline.loop",
+ "timeline.key-nav",
+ "timeline.add-track",
+ "timeline.remove-track",
+ "timeline.target",
+ "timeline.property",
+ "timeline.mute",
+ "timeline.add-key",
+ "timeline.delete-key",
+ "timeline.drag-key",
+ "timeline.zoom",
+ "timeline.curve",
+ "curve.time",
+ "curve.value",
+ "curve.easing"
+ ],
+ "scope": "真实控件和指针操作,运行时读取数值校验;curve.axis仅切换无数值独立断言,timeline.seek播放头与Alt吸附未在本例覆盖。",
+ "projectId": "169",
+ "actions": "真实 DOM 轨道/帧增删、关键帧指针拖移、播放控件、曲线输入和保存重开",
+ "restored": {
+ "id": "track-0-che4ljm",
+ "name": "新轨道 6",
+ "targetId": "interaction-5f7e4bee",
+ "property": "position",
+ "color": "#ffb22e",
+ "muted": false,
+ "keyframes": [
+ {
+ "id": "key-0-0-mi1ztwf",
+ "time": 0,
+ "value": [
+ 0,
+ 0,
+ 0
+ ],
+ "easing": "easeInOut"
+ },
+ {
+ "id": "key-zi8i3f7",
+ "time": 2,
+ "value": [
+ 5,
+ 0,
+ 0
+ ],
+ "easing": "easeInOut"
+ },
+ {
+ "id": "key-0-1-2lnk2wi",
+ "time": 8,
+ "value": [
+ 0,
+ 0,
+ 0
+ ],
+ "easing": "easeInOut"
+ }
+ ]
+ },
+ "interpolationSamples": {
+ "linear": 1.25,
+ "easeInOut": 0.625,
+ "easeIn": 0.3125,
+ "easeOut": 2.1875,
+ "step": 0
+ },
+ "excluded": [
+ "未新增 solo 能力",
+ "未将原生 GLB 动画片段当成多条编辑时间轴"
+ ]
+ }
+} evidence/viewport.json 已验证 {
+ "time": "2026-09-06T08:22:14.169Z",
+ "data": {
+ "status": "PASS",
+ "kind": "e2e",
+ "testTitle": "场景树搜索、可见性、选择、视口工具、原生动画与截图",
+ "featureIds": [
+ "tree.search",
+ "tree.visibility",
+ "selection.sync",
+ "view.grid",
+ "view.pbr",
+ "view.wireframe",
+ "view.outline",
+ "view.perspective",
+ "view.preview",
+ "view.snapshot",
+ "timeline.native"
+ ],
+ "scope": "树搜索、对象隐藏刷新、轮廓及视图开关、原生动画和PNG下载。移动/旋转/缩放只切换模式,真实gizmo拖动仍需补验;聚焦未做几何断言。",
+ "clips": 3,
+ "nativeClipPersisted": true,
+ "hiddenObjectPersisted": true,
+ "snapshot": "装备模型编辑工具_1788682931740.png"
+ }
+}
+构建、聚焦单测与版本检查 完整验证记录 。单测/合同测试不并入真实 E2E 数量。
{
+ "time": "2026-09-06T08:28:23.891Z",
+ "status": "PASS",
+ "e2eComplete": true,
+ "checks": [
+ {
+ "name": "装备模型编辑回归",
+ "command": "node --test (8 model editor test files)",
+ "tests": 54,
+ "status": "PASS",
+ "failures": 0,
+ "exitCode": 0
+ },
+ {
+ "name": "权限桥新建、脏状态和只读键盘回归",
+ "command": "node --test tools/content-permissions.test.mjs",
+ "tests": 14,
+ "status": "PASS",
+ "failures": 0,
+ "exitCode": 0
+ },
+ {
+ "name": "报告证据归属与附件校验回归",
+ "command": "node --test tools/model-editor-report-evidence.test.mjs",
+ "tests": 7,
+ "status": "PASS",
+ "failures": 0,
+ "exitCode": 0
+ },
+ {
+ "name": "实际 Kingbase 驱动正常检查与隔离网络无响应验证",
+ "command": "KingbaseValidationProbe (isolated loopback TCP proxy, SELECT 1 only)",
+ "evidence": "data/kingbase-validation-probe-result.json",
+ "tests": 3,
+ "status": "PASS",
+ "failures": 0,
+ "exitCode": 0
+ },
+ {
+ "name": "Auth / Tran 完整构建与测试",
+ "command": "mvn -pl ut_auth,ut_tran -am package",
+ "tests": 476,
+ "moduleTests": [
+ 120,
+ 356
+ ],
+ "status": "PASS",
+ "failures": 0,
+ "exitCode": 0
+ },
+ {
+ "name": "Web 构建和 iframe 产物一致性",
+ "command": "pnpm run build",
+ "warning": "既有大包体积提示;构建及10项public/dist哈希校验通过",
+ "status": "PASS",
+ "failures": 0,
+ "exitCode": 0
+ },
+ {
+ "name": "6 E2E specs strict TypeScript",
+ "command": "tsc --noEmit --strict (closure specs, helper, config)",
+ "status": "PASS",
+ "failures": 0,
+ "exitCode": 0
+ }
+ ],
+ "coverage": [
+ {
+ "title": "resource catalog startup renders all model/environment cards and binds every action with a real DOM",
+ "featureIds": [
+ "resource.catalog"
+ ],
+ "scope": "真实 DOM 渲染、卡片绑定、空目录及 busy 保护;不包含 GLB 网络加载。",
+ "status": "PASS",
+ "kind": "unit",
+ "testFile": "unreal_tran/tests/model-main-closure.test.mjs"
+ },
+ {
+ "title": "curve edits real scalar key values, time and interpolation, persisting through document reload",
+ "featureIds": [
+ "timeline.curve",
+ "curve.time",
+ "curve.value",
+ "curve.easing"
+ ],
+ "scope": "真实曲线控件与 TimelineEditor,标量编辑与文档往返。",
+ "status": "PASS",
+ "kind": "unit",
+ "testFile": "unreal_tran/tests/model-main-closure.test.mjs"
+ },
+ {
+ "title": "curve vector editing preserves other components and selected track; invalid values do not mutate",
+ "featureIds": [
+ "curve.value"
+ ],
+ "scope": "向量其它分量保留,当前轨道、非法值保护;不包含显示轴切换或可视像素验收。",
+ "status": "PASS",
+ "kind": "unit",
+ "testFile": "unreal_tran/tests/model-main-closure.test.mjs"
+ },
+ {
+ "title": "multiple tracks on one property use last effective value; capture subtracts it only once",
+ "featureIds": [
+ "timeline.multitrack"
+ ],
+ "scope": "调用实际 apply/capture/update 基值方法;验证同属性最后有效轨道、静音/删除后中性姿态,偏移只扣一次。",
+ "status": "PASS",
+ "kind": "unit",
+ "testFile": "unreal_tran/tests/model-main-closure.test.mjs"
+ },
+ {
+ "title": "changing a binding resets the former target and keeps manual transform edits as base",
+ "featureIds": [
+ "timeline.multitrack",
+ "timeline.target"
+ ],
+ "scope": "轨道重绑后旧目标复位,手动变换成为新基值;不假称鼠标拖动已验。",
+ "status": "PASS",
+ "kind": "unit",
+ "testFile": "unreal_tran/tests/model-main-closure.test.mjs"
+ },
+ {
+ "title": "custom properties and extension labels survive actual capture, project JSON and object restore",
+ "featureIds": [
+ "property.code",
+ "property.status",
+ "component.tag"
+ ],
+ "scope": "真实对象 capture、normalization、JSON 与 restore 往返;清空和撤销到未存在字段状态。",
+ "status": "PASS",
+ "kind": "unit",
+ "testFile": "unreal_tran/tests/model-main-closure.test.mjs"
+ },
+ {
+ "title": "boolean, numeric and nested blueprint conditions select their true/false branches",
+ "featureIds": [
+ "blueprint.condition"
+ ],
+ "scope": "布尔、数字、truthy 与嵌套路径值解析。",
+ "status": "PASS",
+ "kind": "unit",
+ "testFile": "unreal_tran/tests/model-main-closure.test.mjs"
+ },
+ {
+ "title": "visible child under a hidden ancestor cannot be picked or highlighted; trigger aliases include ancestors",
+ "featureIds": [
+ "tree.visibility",
+ "blueprint.highlight"
+ ],
+ "scope": "共用可见性和祖先别名函数;鼠标事件另由 E2E 验证。",
+ "status": "PASS",
+ "kind": "unit",
+ "testFile": "unreal_tran/tests/model-main-closure.test.mjs"
+ },
+ {
+ "title": "inspector state tags reflect ancestor visibility and actual shadow flags, with a host editability hook",
+ "featureIds": [
+ "property.identity"
+ ],
+ "scope": "真实属性 DOM 显示隐藏/阴影状态,并提供宿主权限标签 hook;不伪称此单测完成会话鉴权。",
+ "status": "PASS",
+ "kind": "unit",
+ "testFile": "unreal_tran/tests/model-main-closure.test.mjs"
+ },
+ {
+ "title": "history retains 60 states, truncates redo after edits and ignores duplicate snapshots",
+ "featureIds": [
+ "project.undo",
+ "project.redo"
+ ],
+ "scope": "60 个快照上限、重复状态忽略及新编辑截断 redo 分支。",
+ "status": "PASS",
+ "kind": "unit",
+ "testFile": "unreal_tran/tests/model-main-closure.test.mjs"
+ },
+ {
+ "title": "history serializes async restores and leaves index unchanged when restoration fails",
+ "featureIds": [
+ "project.undo",
+ "project.redo"
+ ],
+ "scope": "异步互斥、下载失败不移动索引,解除锁后可继续操作。",
+ "status": "PASS",
+ "kind": "unit",
+ "testFile": "unreal_tran/tests/model-main-closure.test.mjs"
+ },
+ {
+ "title": "managed-GLB history downloads the snapshot stable reference before restoring, without rewriting it",
+ "featureIds": [
+ "project.undo",
+ "project.redo"
+ ],
+ "scope": "受控 GLB 快照先物化再恢复,不把临时 URL 写回快照。",
+ "status": "PASS",
+ "kind": "unit",
+ "testFile": "unreal_tran/tests/model-main-closure.test.mjs"
+ },
+ {
+ "title": "beforeunload recovery does not create a draft for key order alone, but preserves real unsaved changes",
+ "featureIds": [
+ "project.recovery"
+ ],
+ "scope": "同语义对象键顺序不产生假恢复稿;真正修改仍保留。不包含全部恢复/隔离/下载 UI 分支。",
+ "status": "PASS",
+ "kind": "unit",
+ "testFile": "unreal_tran/tests/model-main-closure.test.mjs"
+ },
+ {
+ "title": "highlight rejects missing/hidden targets and transient colors never enter a saved project",
+ "featureIds": [
+ "blueprint.highlight"
+ ],
+ "scope": "目标不存在/隐藏报错,执行时暂态发光颜色不进入保存文档,结束恢复原材质。",
+ "status": "PASS",
+ "kind": "unit",
+ "testFile": "unreal_tran/tests/model-main-closure.test.mjs"
+ },
+ {
+ "title": "API publish supplies current cover, blocks duplicate publication and reports preserved old cover once",
+ "featureIds": [
+ "publish.api",
+ "publish.cover",
+ "feedback.toast"
+ ],
+ "scope": "实际发布宿主方法传封面、重入阻断和上传失败只提示一次;模拟 API,不是实际服务响应。",
+ "status": "PASS",
+ "kind": "unit",
+ "testFile": "unreal_tran/tests/model-main-closure.test.mjs"
+ },
+ {
+ "title": "API conflict leaves publication failed and releases the cover guard",
+ "featureIds": [
+ "project.conflict",
+ "publish.api",
+ "publish.cover"
+ ],
+ "scope": "409 分支不误报已发布并释放 busy 标记;模拟 API。",
+ "status": "PASS",
+ "kind": "unit",
+ "testFile": "unreal_tran/tests/model-main-closure.test.mjs"
+ }
+ ],
+ "runtime": {
+ "authSha256": "95b8487ca6a1d970387214b027ee9c1d34ae0d1d5c988574c024805d8bb61d9b",
+ "tranSha256": "e9528d739d6d817ebf7c5b9a34302195156b1543663066f5484c86c594c6e729",
+ "builtEqualsRunning": true,
+ "fileService": "开发进程撤销旧17180转发覆盖,恢复已配置远端文件服务"
+ },
+ "previousWorkPushed": {
+ "api": "71df00dffa39a8f49a4f0654969a3014c2f78f67",
+ "web": "3f39e2c94df9cdadf3545ae14bf0df41f2c37bf1",
+ "e2e": "b3d4ce8ffd8144090c18c8a28f5ca824170042c8"
+ }
+}
+测试数据保留与清理 清理结果
{
+ "time": "2026-09-06T08:27:13.581Z",
+ "status": "PASS",
+ "originals": [
+ {
+ "id": "157",
+ "name": "实测模型-狐狸动画-已验证-20260905",
+ "version": 15,
+ "unchanged": true
+ },
+ {
+ "id": "156",
+ "name": "实测模型-牛奶卡车-已验证-20260905",
+ "version": 20,
+ "unchanged": true
+ },
+ {
+ "id": "94",
+ "name": "桥梁架设装备整机模型",
+ "version": 253,
+ "unchanged": true
+ }
+ ],
+ "projects": [
+ {
+ "id": "168",
+ "alreadyAbsent": true
+ },
+ {
+ "id": "169",
+ "alreadyAbsent": true
+ },
+ {
+ "id": "170",
+ "alreadyAbsent": true
+ },
+ {
+ "id": "171",
+ "alreadyAbsent": true
+ },
+ {
+ "id": "172",
+ "alreadyAbsent": true
+ },
+ {
+ "id": "173",
+ "alreadyAbsent": true
+ },
+ {
+ "id": "174",
+ "alreadyAbsent": true
+ },
+ {
+ "id": "175",
+ "alreadyAbsent": true
+ },
+ {
+ "id": "176",
+ "alreadyAbsent": true
+ },
+ {
+ "id": "177",
+ "alreadyAbsent": true
+ },
+ {
+ "id": "178",
+ "alreadyAbsent": true
+ },
+ {
+ "id": "179",
+ "alreadyAbsent": true
+ },
+ {
+ "id": "180",
+ "alreadyAbsent": true
+ },
+ {
+ "id": "181",
+ "alreadyAbsent": true
+ },
+ {
+ "id": "182",
+ "alreadyAbsent": true
+ },
+ {
+ "id": "183",
+ "name": "闭环导航名称-183",
+ "withdrawn": false,
+ "removed": true
+ },
+ {
+ "id": "184",
+ "name": "闭环验收-资源往返-mtph19ll",
+ "withdrawn": false,
+ "removed": true
+ },
+ {
+ "id": "185",
+ "name": "闭环验收-三维手柄-1788678747289",
+ "withdrawn": false,
+ "removed": true
+ },
+ {
+ "id": "186",
+ "name": "闭环验收-离开保护-1788678780620",
+ "withdrawn": false,
+ "removed": true
+ },
+ {
+ "id": "187",
+ "name": "闭环验收-恢复副本-mtph6hw8",
+ "withdrawn": false,
+ "removed": true
+ },
+ {
+ "id": "188",
+ "name": "闭环验收-加载与预检-mtph6ubj",
+ "withdrawn": false,
+ "removed": true
+ },
+ {
+ "id": "189",
+ "name": "闭环验收-三维手柄-1788678838243",
+ "withdrawn": false,
+ "removed": true
+ },
+ {
+ "id": "190",
+ "name": "闭环验收-离开保护-1788678850196",
+ "withdrawn": false,
+ "removed": true
+ },
+ {
+ "id": "191",
+ "name": "闭环工具补充-卡车控件-mtpha049",
+ "withdrawn": false,
+ "removed": true
+ },
+ {
+ "id": "192",
+ "name": "闭环工具补充-卡车控件-mtphbg8t",
+ "withdrawn": false,
+ "removed": true
+ },
+ {
+ "id": "193",
+ "name": "闭环工具补充-基准匹配-mtphbpqq",
+ "withdrawn": false,
+ "removed": true
+ },
+ {
+ "id": "194",
+ "name": "闭环工具补充-卡车控件-mtphfk1m",
+ "withdrawn": false,
+ "removed": true
+ },
+ {
+ "id": "195",
+ "name": "闭环工具补充-基准匹配-mtphfu32",
+ "withdrawn": false,
+ "removed": true
+ },
+ {
+ "id": "196",
+ "name": "闭环验收-资源往返-mtphjhoi",
+ "withdrawn": false,
+ "removed": true
+ },
+ {
+ "id": "197",
+ "name": "闭环验收-拒绝非法资源-mtphjv5x",
+ "withdrawn": false,
+ "removed": true
+ },
+ {
+ "id": "198",
+ "name": "闭环验收-保存失败恢复-mtphk0kk",
+ "withdrawn": false,
+ "removed": true
+ },
+ {
+ "id": "199",
+ "name": "闭环验收-发布封面版本-mtphk960",
+ "withdrawn": true,
+ "removed": true
+ },
+ {
+ "id": "200",
+ "name": "闭环工具补充-卡车控件-mtphkrbz",
+ "withdrawn": false,
+ "removed": true
+ },
+ {
+ "id": "201",
+ "name": "闭环工具补充-基准匹配-mtphkzlj",
+ "withdrawn": false,
+ "removed": true
+ },
+ {
+ "id": "202",
+ "name": "闭环新建-1788679561949",
+ "withdrawn": false,
+ "removed": true
+ },
+ {
+ "id": "203",
+ "name": "闭环验收-三维手柄-1788679613072",
+ "withdrawn": false,
+ "removed": true
+ },
+ {
+ "id": "204",
+ "name": "闭环验收-离开保护-1788679623529",
+ "withdrawn": false,
+ "removed": true
+ },
+ {
+ "id": "205",
+ "name": "闭环补充-资源库-mtphobxb",
+ "withdrawn": false,
+ "removed": true
+ },
+ {
+ "id": "206",
+ "name": "闭环补充-内置环境-mtphomov",
+ "withdrawn": false,
+ "removed": true
+ },
+ {
+ "id": "207",
+ "name": "闭环导航名称-207",
+ "withdrawn": false,
+ "removed": true
+ },
+ {
+ "id": "208",
+ "name": "闭环验收-恢复副本-mtphpz7v",
+ "withdrawn": false,
+ "removed": true
+ },
+ {
+ "id": "209",
+ "name": "闭环验收-加载与预检-mtphq9y1",
+ "withdrawn": false,
+ "removed": true
+ },
+ {
+ "id": "210",
+ "name": "闭环验收-离开保护-1788681347476",
+ "withdrawn": false,
+ "removed": true
+ },
+ {
+ "id": "211",
+ "name": "闭环补充-内置环境-mtpip60q",
+ "withdrawn": false,
+ "removed": true
+ },
+ {
+ "id": "212",
+ "name": "闭环验收-资源往返-mtpjbnl2",
+ "withdrawn": false,
+ "removed": true
+ },
+ {
+ "id": "213",
+ "name": "闭环验收-拒绝非法资源-mtpjbyqo",
+ "withdrawn": false,
+ "removed": true
+ },
+ {
+ "id": "214",
+ "name": "闭环验收-保存失败恢复-mtpjc2vu",
+ "withdrawn": false,
+ "removed": true
+ },
+ {
+ "id": "215",
+ "name": "闭环验收-发布封面版本-mtpjcafv",
+ "withdrawn": true,
+ "removed": true
+ },
+ {
+ "id": "216",
+ "name": "闭环工具补充-卡车控件-mtpjcof8",
+ "withdrawn": false,
+ "removed": true
+ },
+ {
+ "id": "217",
+ "name": "闭环工具补充-基准匹配-mtpjcvxe",
+ "withdrawn": false,
+ "removed": true
+ },
+ {
+ "id": "218",
+ "name": "闭环新建-1788682528069",
+ "withdrawn": false,
+ "removed": true
+ },
+ {
+ "id": "219",
+ "name": "闭环验收-三维手柄-1788682536810",
+ "withdrawn": false,
+ "removed": true
+ },
+ {
+ "id": "220",
+ "name": "闭环验收-离开保护-1788682545004",
+ "withdrawn": false,
+ "removed": true
+ },
+ {
+ "id": "221",
+ "name": "闭环补充-资源库-mtpjesgj",
+ "withdrawn": false,
+ "removed": true
+ },
+ {
+ "id": "222",
+ "name": "闭环补充-内置环境-mtpjezfi",
+ "withdrawn": false,
+ "removed": true
+ },
+ {
+ "id": "223",
+ "name": "闭环导航名称-223",
+ "withdrawn": false,
+ "removed": true
+ },
+ {
+ "id": "224",
+ "name": "闭环验收-恢复副本-mtpjg6y2",
+ "withdrawn": false,
+ "removed": true
+ },
+ {
+ "id": "225",
+ "name": "闭环验收-加载与预检-mtpjgfnd",
+ "withdrawn": false,
+ "removed": true
+ },
+ {
+ "id": "226",
+ "name": "闭环验收-资源往返-mtpjkcpq",
+ "withdrawn": false,
+ "removed": true
+ },
+ {
+ "id": "227",
+ "name": "闭环验收-拒绝非法资源-mtpjko7g",
+ "withdrawn": false,
+ "removed": true
+ },
+ {
+ "id": "228",
+ "name": "闭环验收-保存失败恢复-mtpjkshn",
+ "withdrawn": false,
+ "removed": true
+ },
+ {
+ "id": "229",
+ "name": "闭环验收-发布封面版本-mtpjkzt1",
+ "withdrawn": true,
+ "removed": true
+ },
+ {
+ "id": "230",
+ "name": "闭环工具补充-卡车控件-mtpjlf69",
+ "withdrawn": false,
+ "removed": true
+ },
+ {
+ "id": "231",
+ "name": "闭环工具补充-基准匹配-mtpjlmiy",
+ "withdrawn": false,
+ "removed": true
+ },
+ {
+ "id": "232",
+ "name": "闭环新建-1788682938712",
+ "withdrawn": false,
+ "removed": true
+ },
+ {
+ "id": "233",
+ "name": "闭环验收-三维手柄-1788682951454",
+ "withdrawn": false,
+ "removed": true
+ },
+ {
+ "id": "234",
+ "name": "闭环验收-离开保护-1788682960426",
+ "withdrawn": false,
+ "removed": true
+ },
+ {
+ "id": "235",
+ "name": "闭环补充-资源库-mtpjnoh9",
+ "withdrawn": false,
+ "removed": true
+ },
+ {
+ "id": "236",
+ "name": "闭环补充-内置环境-mtpjnv0t",
+ "withdrawn": false,
+ "removed": true
+ },
+ {
+ "id": "237",
+ "name": "闭环导航名称-237",
+ "withdrawn": false,
+ "removed": true
+ },
+ {
+ "id": "238",
+ "name": "闭环验收-恢复副本-mtpjp3nr",
+ "withdrawn": false,
+ "removed": true
+ },
+ {
+ "id": "239",
+ "name": "闭环验收-加载与预检-mtpjpdaj",
+ "withdrawn": false,
+ "removed": true
+ }
+ ]
+}
+交付物凭据与附件检查 最终扫描通过 · 脱敏扫描结果
四个仓库相对 HEAD 的变动/新增文件与本轮完整报告;真实本地账号密码精确匹配、可解码 JWT、ZIP/Playwright 内嵌报告逐项解压扫描及 PNG 文本元数据。输出不包含任何命中值或附近正文。
扫描 104 份文本、122 张图片的文本元数据及 1 个归档;图片正文不以文本扫描代替视觉 QA。
检查范围、历史常量排除与限制 {
+ "time": "2026-09-06T08:33:57.163137+00:00",
+ "status": "PASS",
+ "secretScan": "PASS",
+ "phase": "final",
+ "finalArtifactScan": true,
+ "scope": "四个仓库相对 HEAD 的变动/新增文件与本轮完整报告;真实本地账号密码精确匹配、可解码 JWT、ZIP/Playwright 内嵌报告逐项解压扫描及 PNG 文本元数据。输出不包含任何命中值或附近正文。",
+ "baselinePolicy": "只排除 HEAD 已存在且邻近至少64字符完全一致的普通源码/固定库字符串(统一CRLF/LF);新哈希模型bundle另允许至少25个邻近词元在仅归一化压缩变量名后完全一致,且命中须为更长固定常量的子串、不能是完整密码值。唯一经本轮复核的配置例外是 ut_auth/src/main/resources/application.yml 的 bootstrap-admin-password 环境变量默认项:文件、键、整行须与 HEAD 完全相同,且新旧文件各只出现一次;改值、改变量名、复制到其他文件或新增同一行均仍失败。其他真实凭据赋值与ZIP内元数据不作排除。Playwright静态库对比HEAD上一轮报告壳。",
+ "limitations": [
+ "图片仅扫描可读元数据,不执行OCR;图像正文由浏览器报告视觉QA核对。",
+ "只扫描本轮交付范围,不声称已审计整个历史仓库。",
+ "初查通过不代表最终结果文件已完成,最终需 --final 再执行。"
+ ],
+ "selfChecks": {
+ "status": "PASS",
+ "tests": 15
+ },
+ "repositoryHeads": {
+ "unreal_tran": "d16184acf1686bb4a6ad44949d0587c5373ffbde",
+ "unreal_tran_web": "3f39e2c94df9cdadf3545ae14bf0df41f2c37bf1",
+ "unreal_tran_api": "71df00dffa39a8f49a4f0654969a3014c2f78f67",
+ "ute2e": "b3d4ce8ffd8144090c18c8a28f5ca824170042c8"
+ },
+ "changedFileCounts": {
+ "unreal_tran": 14,
+ "unreal_tran_web": 8,
+ "unreal_tran_api": 15,
+ "ute2e": 184
+ },
+ "deletedFileCounts": {
+ "unreal_tran": 0,
+ "unreal_tran_web": 2,
+ "unreal_tran_api": 0,
+ "ute2e": 0
+ },
+ "textArtifactsScanned": 104,
+ "pngMetadataFilesScanned": 122,
+ "archives": [
+ {
+ "file": "ute2e/reports/model-editor-closure-20260906/playwright/index.html!embedded-report-1.zip",
+ "entries": 7,
+ "textEntries": 7
+ }
+ ],
+ "finalInputs": {
+ "results.json": true,
+ "validation.json": true,
+ "cleanup.json": true,
+ "playwright/index.html": true
+ },
+ "finalInputSha256": {
+ "results.json": "f37013658c8c38a5ec5993e636a30ad57eee9d3bcc119650c929952de8d556e9",
+ "validation.json": "6e0e87d0df73061bc8549d3405f03731fecf077d7425f52eeed45008f7d09959",
+ "cleanup.json": "ab2776afa143d9937651daa8fa18b531a3fb84c703f5f5b0918930e78c76eb26",
+ "playwright/index.html": "92dcca3fe570530bde973b9e09199c064fc2b343bb6f8a21b67fb3c72b8949ed"
+ },
+ "unchangedBaselineExclusions": [
+ {
+ "file": "unreal_tran_api/ut_auth/src/main/resources/application.yml",
+ "baseline": "unreal_tran_api/HEAD:ut_auth/src/main/resources/application.yml",
+ "occurrences": 1
+ },
+ {
+ "file": "unreal_tran_api/wiki/11-内容制作模块.md",
+ "baseline": "unreal_tran_api/HEAD:wiki/11-内容制作模块.md",
+ "occurrences": 17
+ },
+ {
+ "file": "unreal_tran_web/public/legacy-model-editor/assets/editor-BAqfQ3VC.js",
+ "baseline": "unreal_tran_web/HEAD:public/legacy-model-editor/assets/editor-CunHwJSa.js",
+ "occurrences": 1
+ },
+ {
+ "file": "ute2e/reports/model-editor-closure-20260906/playwright/index.html",
+ "baseline": "ute2e/HEAD:reports/role-permissions-20260906/playwright/index.html",
+ "occurrences": 1
+ }
+ ],
+ "unscannedBinaryFiles": [],
+ "findings": []
+}
+需要产品确认 / 3 项
较大调整的具体范围 以下能力超出现有小范围修复。本轮保留入口并明确现状;确认范围后才能据此另行实现和验收。
D1 真正的组件运行系统 当前行为 “组件”页目前是内置能力说明;新增项只保存扩展标记名称,没有启停、参数执行或生命周期。 拟实现的可观察行为 用户新增一种行为组件,配置参数后能在当前模型和约定下游环境执行;停用后立即停止,重开后按保存状态恢复。 建议确认的实施范围 先定义有限组件目录、参数 schema、权限与版本;实现初始化/更新/销毁及错误隔离。不要直接开放任意脚本。 验收条件 每种批准组件可添加、编辑、启停、删除、保存重开;重复载入不重复绑定事件;失败组件不阻断其他对象,运行结果可定位。 D2 完整三维离线运行包 当前行为 现有 HTML 是嵌入静态截图与工程数据的步骤演示,可离线查看;没有 GLB 字节和完整三维运行时。 拟实现的可观察行为 断网电脑打开交付包,真实模型/材质/原生动画/编辑时间轴与交互节点可以运行,且不依赖服务端 blob URL。 建议确认的实施范围 明确交付形式(目录/ZIP或单文件)、体积约束、资产授权和三维依赖;打包全部受控资产、运行时和相对资源清单。 验收条件 清缓存断网运行;模型和环境完整可见,动作/分支/高亮可交互;资源缺失明确报错;包内清单/哈希与发布版本一致。 D3 下游场景 / 训练自动执行模型动画与蓝图 当前行为 发布模型可供下游按固定版本引用;不同下游已有局部动画能力,但没有统一的模型 timeline/blueprint 自动执行桥接。加载成功不等于动作已自动执行。 拟实现的可观察行为 在场景或训练放入模型实例后,按明确触发策略启动该发布版本的时间轴/蓝图;多个实例互不干扰。 建议确认的实施范围 统一实例目标命名空间、事件总线、生命周期和停止规则;明确自动播放、用户触发与训练步骤触发优先级,以及错误/完成结果回传。 验收条件 两个同模型实例分别触发不串台;锁定发布版本;停止/切步骤/卸载无迟到动作;真实对象姿态、分支和结果回传均有 E2E 证据。
+可视证据
41 张截图 · 点击放大 截图来自实际目录;首次加载、过程画面和最终结果按图注说明,不把空场景或加载遮罩当作模型可见证据。
01 对象属性与扩展标记保存 本轮附件一致 通过真实属性控件编辑并保存;新增内容是可持久化的名称标记,不代表组件运行行为已经实现。
01-components-saved.png 02 刷新后的模型属性 本轮附件一致 重新加载服务端工程后核对对象名称、变换、首个材质槽、自定义字段和扩展标记;不是仅核对保存提示。
02-properties-reopened.png 03 狐狸模型运行预览 本轮附件一致 真实狐狸模型在编辑器预览视口显示。临时网格、线框、透视等视图选项与模型数据保存分开验收。
03-fox-preview.png 04 新建失败后的重试界面 本轮附件一致 定向注入创建请求失败,核对原工程保留及新建表单可重试;该画面不表示开发服务自然发生故障。
04-new-retry-dialog.png 05 新建独立模型工程 本轮附件一致 新建走真实创建接口并进入新项目 ID,原模型工程没有被清空覆盖。
05-new-separate-project.png 06 只读模型入口 本轮附件一致 使用本轮实际权限会话检查只读控件及接口拒绝;平台角色名称本身不授予内容写权限。
06-readonly-permissions.png 07 卡车时间轴与部件轨道 本轮附件一致 真实轨道增删、目标绑定、播放停止、静音和关键帧操作。当前截图停在零秒;它不表示原生 GLB 动画已经变成多条可编辑时间轴。
21-truck-timeline-controls.png 08 曲线编辑:部件 X=5 的测试姿态 本轮附件一致 将卡车车身部件关键帧 X 改为 5 验证曲线与真实视口联动,因此车身与轮子暂时分离;这是测试姿态,不是模型加载丢失。
22-truck-curve-value-five.png 09 七种蓝图节点与真实端口连线 本轮附件一致 节点通过节点库创建,参数使用实际控件编辑,端口通过指针拖线连接;蓝图图数据随后保存重开核对。
23-truck-blueprint-seven-nodes.png 10 蓝图执行到操作提示 本轮附件一致 编辑器真实 handler 驱动时间轴、等待、高亮与提示节点;不是替换 handler 的演示截图。
24-truck-blueprint-real-message.png 11 蓝图完成与本次运行日志 本轮附件一致 用例检查节点顺序、条件出口和完成事件;右侧日志只记录当前运行,不等同服务端审计或教学成绩。
25-truck-blueprint-completed-log.png 12 条件真假出口的运行时补充检查 本轮附件一致 对已由 UI 创建的图调用运行时接口检查 true/false 与不匹配目标;这张图标记为运行时证据,不能当成纯鼠标 UI 覆盖。
26-truck-condition-branches-runtime.png 13 进入预览自动触发蓝图 本轮附件一致 真实点击进入预览后派发 auto,校验只启动一次;不代表下游场景或训练会自动运行模型蓝图。
27-truck-preview-auto.png 14 真实单击卡车触发蓝图 本轮附件一致 在真实可见网格像素上单击,校验只启动一次且流程完成;没有伪造 trigger 事件。
27-truck-preview-click.png 15 真实双击卡车触发蓝图 本轮附件一致 通过 renderer canvas 双击命中真实模型,校验事件匹配与单次执行;避免单击误启动双击/自动触发器。
27-truck-preview-double-click.png 16 曲线分量切换与负值 本轮附件一致 在独立卡车副本中设置关键帧 X=5、Y=-2,切换曲线显示分量并比较曲线路径,确认切换视图不修改关键帧数据。车身偏移是特意设置的测试姿态。
28-curve-axis-y-negative.png 17 真实刻度定位、方向键与关键帧记录 本轮附件一致 通过时间刻度点击/拖动、左右与 Shift 按键定位时间,再用实际关键帧按钮记录当前偏移,核对时间码、关键帧和持久化内容。
29-timeline-seek-record.png 18 主狐狸与外部方盒环境同时重开 本轮附件一致 狐狸是主模型,旁边方盒是轻量环境 GLB;两者使用独立受控资产引用,刷新后均有真实网格。当前镜头较远,资产身份以随附 API 断言核对。
30-assets-environment-reopened.png 19 蓝图库搜索、真实拖入与画布操作 本轮附件一致 搜索并清空节点库,通过 HTML5 拖放添加节点,核对工具栏默认添加、画布滚轮缩放/平移/适应操作没有意外改变节点图。
30-blueprint-palette-canvas.png 20 匹配模型的基准时间轴保存重开 本轮附件一致 在独立匹配装备副本载入基准动作,核对轨道目标存在、项目名称保留、保存刷新后时间轴一致;不匹配卡车分支另外验证原时间轴未被覆盖。仅验证通用编辑数据,不制作实际维修流程。
31-bridge-baseline-reopened.png 21 无效工程导入保留原模型 本轮附件一致 非法 JSON 或无有效受控引用的导入被拒绝;核对原模型与工程内容仍在,没有以默认模型静默替换。
31-invalid-import-keeps-model.png 22 版本冲突阻断保存并保留恢复稿 本轮附件一致 通过定向注入 409 验证保存失败分支及恢复数据,不将失败提示本身当成成功保存。
32-save-conflict-keeps-recovery.png 23 保存失败后再次保存成功 本轮附件一致 恢复正常请求后点击真实保存。右上红色 409 提示来自上一故障注入步骤,截图时尚在自动消退周期内;本次重试成功由当前 PUT 响应和保存后的刷新断言确认。失败后的旧上传票据不得被误复用。
33-save-retry-success.png 24 发布版本与当前视口封面 本轮附件一致 发布接口、保存版本和受控封面资产一起核对;封面来自当前三维视口。下载后的下游资源验证不等于下游自动执行蓝图。
34-published-current-cover.png 25 实际下载的步骤演示 HTML 本轮附件一致 打开真实导出文件检查步骤演示内容。此文件不含完整三维模型和资源,完整三维离线包列为待确认范围。
35-offline-step-demo-actual-file.png 26 资源搜索清空后恢复卡片 本轮附件一致 输入命中关键字、无匹配关键字,再清空搜索,断言卡片数量与空结果状态;仅搜索当前目录。
41-resource-search-restored.png 27 真实资源卡片切换后刷新 本轮附件一致 在独立副本导入小方盒,再点回原狐狸卡片;保存刷新后核对原资产 ID、code 与真实网格,不改原模型数据。
42-resource-card-switch-reopened.png 28 内置车间环境保存重开 本轮附件一致 真实切换内置车间、保存和刷新后核对环境类型与网格;树计数、折叠和展开同时验证。
43-builtin-workshop-reopened.png 29 内置野外环境保存重开 本轮附件一致 真实切换内置野外、保存和刷新后核对环境类型与网格;程序化环境不冒称外部 GLB。
44-builtin-outdoor-reopened.png 30 内置装备车库保存重开 本轮附件一致 真实加载随包车库 GLB,保存刷新后检查 builtin-gltf 来源与网格;树总数与当前展开行数分开统计。
45-builtin-depot-reopened.png 31 预览前后步与退出 本轮附件一致 通过真实按钮验证下一步推进两秒、上一步回零,并用 Escape 返回编辑;预览步进不是创建新的业务步骤。
46-preview-navigation.png 32 等待自动保存后刷新核对字段 本轮附件一致 自定义编码修改后没有点击保存,等待实际 30 秒自动 PUT;刷新确认名称与编码保留。下方日志为本次加载运行日志,FPS 与显存只作状态展示。
47-navigation-autosave-log.png 33 真实手柄操作的旋转与单轴缩放测试姿态 本轮附件一致 为了验证真实平移、旋转与缩放手柄,特意把狐狸旋转至侧立并缩小 X 轴;画面中的侧立和压扁属于测试姿态,不是加载变形。定位只读投影,没有脚本写姿态;随后撤销/重做、保存刷新核对。
51-transform-gizmos.png 34 未保存离开遇到保存失败 本轮附件一致 关闭确认框保留修改;定向注入保存 503 后仍留在原页,解除异常后重试保存才离开,最终 API 核对数据。
52-leave-save-failure.png 35 真实未保存修改触发同版本恢复 本轮附件一致 先在实际属性控件修改,再刷新触发 beforeunload 恢复稿;没有预先注入 localStorage 假快照。此时服务端版本未变化。
60-same-version-real-recovery.png 36 恢复稿经显式保存后落库 本轮附件一致 点击恢复后编辑器保留需显式保存状态;实际点击保存才更新服务端版本,恢复名称与原受控模型一致。
61-recovered-draft-explicitly-saved.png 37 旧版本恢复稿只下载与隔离 本轮附件一致 在独立副本模拟另一个编辑器保存;旧稿不显示覆盖恢复按钮,只下载并隔离保留,服务端较新内容未被覆盖。
62-stale-version-recovery-protection.png 38 受控模型下载失败时禁止编辑 本轮附件一致 定向注入资源下载 503,中性加载失败界面保留重试入口,保存与发布均禁用;空白视口不作为模型加载成功。
63-controlled-glb-download-failed.png 39 解除资源故障后实际重试成功 本轮附件一致 解除下载故障后点击宿主重试,iframe 重新载入并出现真实模型网格;没有以默认占位模型绕过失败。
64-controlled-glb-retry-loaded.png 40 发布预检清单与只读开关行为 本轮附件一致 打开和关闭预检面板不会写入项目;错误受控资产引用的 JSON 在替换模型前被拒绝。面板绿标不能替代服务端发布校验。
65-publish-preflight-readonly.png 41 车库保存后正常返回列表 本轮附件一致 车库环境保存后使用正式宿主导航返回列表,确认不会因旧版规范化器把 depot 误改成 outdoor 而产生虚假的未保存提示;实际修改仍受离开保护。
66-depot-saved-return-list.png
+生成命令:node tools/build-model-editor-closure-report.mjs。功能实现状态与验证状态独立;缺失、失败或未确认的最终轮结果绝不自动改为 PASS。报告不读取私有账号文件、会话配置或数据库备份。
+
\ No newline at end of file
diff --git a/reports/model-editor-closure-20260906/playwright/data/0502bf59e8c3b5ebd19ab93192d75f0e766438ac.png b/reports/model-editor-closure-20260906/playwright/data/0502bf59e8c3b5ebd19ab93192d75f0e766438ac.png
new file mode 100644
index 0000000..8347f96
Binary files /dev/null and b/reports/model-editor-closure-20260906/playwright/data/0502bf59e8c3b5ebd19ab93192d75f0e766438ac.png differ
diff --git a/reports/model-editor-closure-20260906/playwright/data/110218f749ca04531152d784fd50e78a42c0221f.png b/reports/model-editor-closure-20260906/playwright/data/110218f749ca04531152d784fd50e78a42c0221f.png
new file mode 100644
index 0000000..ef8ae04
Binary files /dev/null and b/reports/model-editor-closure-20260906/playwright/data/110218f749ca04531152d784fd50e78a42c0221f.png differ
diff --git a/reports/model-editor-closure-20260906/playwright/data/11f3c9dd3e15d2b45094e75a804238a72a7f54c4.png b/reports/model-editor-closure-20260906/playwright/data/11f3c9dd3e15d2b45094e75a804238a72a7f54c4.png
new file mode 100644
index 0000000..3cb5df7
Binary files /dev/null and b/reports/model-editor-closure-20260906/playwright/data/11f3c9dd3e15d2b45094e75a804238a72a7f54c4.png differ
diff --git a/reports/model-editor-closure-20260906/playwright/data/2810a42d580fe020a188ce7d5e935eb6fe212a36.png b/reports/model-editor-closure-20260906/playwright/data/2810a42d580fe020a188ce7d5e935eb6fe212a36.png
new file mode 100644
index 0000000..8bb4a69
Binary files /dev/null and b/reports/model-editor-closure-20260906/playwright/data/2810a42d580fe020a188ce7d5e935eb6fe212a36.png differ
diff --git a/reports/model-editor-closure-20260906/playwright/data/28dabf44f567babf2f9c25a0736706b0d4619db0.png b/reports/model-editor-closure-20260906/playwright/data/28dabf44f567babf2f9c25a0736706b0d4619db0.png
new file mode 100644
index 0000000..e836f37
Binary files /dev/null and b/reports/model-editor-closure-20260906/playwright/data/28dabf44f567babf2f9c25a0736706b0d4619db0.png differ
diff --git a/reports/model-editor-closure-20260906/playwright/data/3224eb17a1d5f077cc68acfeef132664ac7a09f4.png b/reports/model-editor-closure-20260906/playwright/data/3224eb17a1d5f077cc68acfeef132664ac7a09f4.png
new file mode 100644
index 0000000..83e2291
Binary files /dev/null and b/reports/model-editor-closure-20260906/playwright/data/3224eb17a1d5f077cc68acfeef132664ac7a09f4.png differ
diff --git a/reports/model-editor-closure-20260906/playwright/data/402af0979bb564a936743fb2df608dc1dc09e2cf.png b/reports/model-editor-closure-20260906/playwright/data/402af0979bb564a936743fb2df608dc1dc09e2cf.png
new file mode 100644
index 0000000..774e368
Binary files /dev/null and b/reports/model-editor-closure-20260906/playwright/data/402af0979bb564a936743fb2df608dc1dc09e2cf.png differ
diff --git a/reports/model-editor-closure-20260906/playwright/data/452fd6550161c5c706e493af1933b7bdd85ffc35.png b/reports/model-editor-closure-20260906/playwright/data/452fd6550161c5c706e493af1933b7bdd85ffc35.png
new file mode 100644
index 0000000..23b9d50
Binary files /dev/null and b/reports/model-editor-closure-20260906/playwright/data/452fd6550161c5c706e493af1933b7bdd85ffc35.png differ
diff --git a/reports/model-editor-closure-20260906/playwright/data/47022e58239b0760d7e644cf16e17a7a1828ba84.png b/reports/model-editor-closure-20260906/playwright/data/47022e58239b0760d7e644cf16e17a7a1828ba84.png
new file mode 100644
index 0000000..8eb0ca2
Binary files /dev/null and b/reports/model-editor-closure-20260906/playwright/data/47022e58239b0760d7e644cf16e17a7a1828ba84.png differ
diff --git a/reports/model-editor-closure-20260906/playwright/data/48616317a42276265bbbeb4acf0ee03b8dc85809.png b/reports/model-editor-closure-20260906/playwright/data/48616317a42276265bbbeb4acf0ee03b8dc85809.png
new file mode 100644
index 0000000..8639f50
Binary files /dev/null and b/reports/model-editor-closure-20260906/playwright/data/48616317a42276265bbbeb4acf0ee03b8dc85809.png differ
diff --git a/reports/model-editor-closure-20260906/playwright/data/5c4a084d0265b0d38165092e20ee17e9fe03893a.png b/reports/model-editor-closure-20260906/playwright/data/5c4a084d0265b0d38165092e20ee17e9fe03893a.png
new file mode 100644
index 0000000..e43cdb8
Binary files /dev/null and b/reports/model-editor-closure-20260906/playwright/data/5c4a084d0265b0d38165092e20ee17e9fe03893a.png differ
diff --git a/reports/model-editor-closure-20260906/playwright/data/622317f3de0afe6153ab2381b90f53af77f13e09.png b/reports/model-editor-closure-20260906/playwright/data/622317f3de0afe6153ab2381b90f53af77f13e09.png
new file mode 100644
index 0000000..19b556f
Binary files /dev/null and b/reports/model-editor-closure-20260906/playwright/data/622317f3de0afe6153ab2381b90f53af77f13e09.png differ
diff --git a/reports/model-editor-closure-20260906/playwright/data/682734e577a8a5ac9f4a0ec32e25bd0a7e9541ea.png b/reports/model-editor-closure-20260906/playwright/data/682734e577a8a5ac9f4a0ec32e25bd0a7e9541ea.png
new file mode 100644
index 0000000..3d3f334
Binary files /dev/null and b/reports/model-editor-closure-20260906/playwright/data/682734e577a8a5ac9f4a0ec32e25bd0a7e9541ea.png differ
diff --git a/reports/model-editor-closure-20260906/playwright/data/6895bf00610acdab6e262d6fc3aa254f70ae7f52.png b/reports/model-editor-closure-20260906/playwright/data/6895bf00610acdab6e262d6fc3aa254f70ae7f52.png
new file mode 100644
index 0000000..5297bfd
Binary files /dev/null and b/reports/model-editor-closure-20260906/playwright/data/6895bf00610acdab6e262d6fc3aa254f70ae7f52.png differ
diff --git a/reports/model-editor-closure-20260906/playwright/data/6e2a075550fbf1502cd0c17c77a3616da9493a0c.png b/reports/model-editor-closure-20260906/playwright/data/6e2a075550fbf1502cd0c17c77a3616da9493a0c.png
new file mode 100644
index 0000000..0235ce7
Binary files /dev/null and b/reports/model-editor-closure-20260906/playwright/data/6e2a075550fbf1502cd0c17c77a3616da9493a0c.png differ
diff --git a/reports/model-editor-closure-20260906/playwright/data/6e546699c2dc057e71ef99728c4f9963118a4bed.png b/reports/model-editor-closure-20260906/playwright/data/6e546699c2dc057e71ef99728c4f9963118a4bed.png
new file mode 100644
index 0000000..72d318f
Binary files /dev/null and b/reports/model-editor-closure-20260906/playwright/data/6e546699c2dc057e71ef99728c4f9963118a4bed.png differ
diff --git a/reports/model-editor-closure-20260906/playwright/data/6fbadd02a9a3e229098b8149294beba85930ad68.png b/reports/model-editor-closure-20260906/playwright/data/6fbadd02a9a3e229098b8149294beba85930ad68.png
new file mode 100644
index 0000000..8a4025c
Binary files /dev/null and b/reports/model-editor-closure-20260906/playwright/data/6fbadd02a9a3e229098b8149294beba85930ad68.png differ
diff --git a/reports/model-editor-closure-20260906/playwright/data/7514b94ed0279009d97e6f0faa47d9569b455720.png b/reports/model-editor-closure-20260906/playwright/data/7514b94ed0279009d97e6f0faa47d9569b455720.png
new file mode 100644
index 0000000..3433be0
Binary files /dev/null and b/reports/model-editor-closure-20260906/playwright/data/7514b94ed0279009d97e6f0faa47d9569b455720.png differ
diff --git a/reports/model-editor-closure-20260906/playwright/data/796f8a702bb9ecb0cfc5257024265f4aad2eddcc.png b/reports/model-editor-closure-20260906/playwright/data/796f8a702bb9ecb0cfc5257024265f4aad2eddcc.png
new file mode 100644
index 0000000..26103fa
Binary files /dev/null and b/reports/model-editor-closure-20260906/playwright/data/796f8a702bb9ecb0cfc5257024265f4aad2eddcc.png differ
diff --git a/reports/model-editor-closure-20260906/playwright/data/81f0cf128bdd981c01424c4720c85817ac9ec1c2.png b/reports/model-editor-closure-20260906/playwright/data/81f0cf128bdd981c01424c4720c85817ac9ec1c2.png
new file mode 100644
index 0000000..d3deb8e
Binary files /dev/null and b/reports/model-editor-closure-20260906/playwright/data/81f0cf128bdd981c01424c4720c85817ac9ec1c2.png differ
diff --git a/reports/model-editor-closure-20260906/playwright/data/8445d4f7a2d32f16962a3e2d027a755ec5471b1f.png b/reports/model-editor-closure-20260906/playwright/data/8445d4f7a2d32f16962a3e2d027a755ec5471b1f.png
new file mode 100644
index 0000000..0f0d72f
Binary files /dev/null and b/reports/model-editor-closure-20260906/playwright/data/8445d4f7a2d32f16962a3e2d027a755ec5471b1f.png differ
diff --git a/reports/model-editor-closure-20260906/playwright/data/87f75a0546df552fc1b2693851b9a76b40777174.png b/reports/model-editor-closure-20260906/playwright/data/87f75a0546df552fc1b2693851b9a76b40777174.png
new file mode 100644
index 0000000..4d04560
Binary files /dev/null and b/reports/model-editor-closure-20260906/playwright/data/87f75a0546df552fc1b2693851b9a76b40777174.png differ
diff --git a/reports/model-editor-closure-20260906/playwright/data/89faa07c1239219892ab5d718d543415c15d4703.png b/reports/model-editor-closure-20260906/playwright/data/89faa07c1239219892ab5d718d543415c15d4703.png
new file mode 100644
index 0000000..97271e7
Binary files /dev/null and b/reports/model-editor-closure-20260906/playwright/data/89faa07c1239219892ab5d718d543415c15d4703.png differ
diff --git a/reports/model-editor-closure-20260906/playwright/data/9229045d4e10cefe626fb59471d932ea73318a88.png b/reports/model-editor-closure-20260906/playwright/data/9229045d4e10cefe626fb59471d932ea73318a88.png
new file mode 100644
index 0000000..5d4b2e3
Binary files /dev/null and b/reports/model-editor-closure-20260906/playwright/data/9229045d4e10cefe626fb59471d932ea73318a88.png differ
diff --git a/reports/model-editor-closure-20260906/playwright/data/96138050bac40d403093f3e1f0d8452e2e4ed2be.png b/reports/model-editor-closure-20260906/playwright/data/96138050bac40d403093f3e1f0d8452e2e4ed2be.png
new file mode 100644
index 0000000..61e9450
Binary files /dev/null and b/reports/model-editor-closure-20260906/playwright/data/96138050bac40d403093f3e1f0d8452e2e4ed2be.png differ
diff --git a/reports/model-editor-closure-20260906/playwright/data/9d0b54f719769044f2d40c2d968e06d681d3a91d.png b/reports/model-editor-closure-20260906/playwright/data/9d0b54f719769044f2d40c2d968e06d681d3a91d.png
new file mode 100644
index 0000000..d545fb5
Binary files /dev/null and b/reports/model-editor-closure-20260906/playwright/data/9d0b54f719769044f2d40c2d968e06d681d3a91d.png differ
diff --git a/reports/model-editor-closure-20260906/playwright/data/9dc827ea5841248a200736bb29716647f9791c46.png b/reports/model-editor-closure-20260906/playwright/data/9dc827ea5841248a200736bb29716647f9791c46.png
new file mode 100644
index 0000000..48c5040
Binary files /dev/null and b/reports/model-editor-closure-20260906/playwright/data/9dc827ea5841248a200736bb29716647f9791c46.png differ
diff --git a/reports/model-editor-closure-20260906/playwright/data/a51348e953f84cdbc744719e7340a1054ceed8c8.png b/reports/model-editor-closure-20260906/playwright/data/a51348e953f84cdbc744719e7340a1054ceed8c8.png
new file mode 100644
index 0000000..4894b9c
Binary files /dev/null and b/reports/model-editor-closure-20260906/playwright/data/a51348e953f84cdbc744719e7340a1054ceed8c8.png differ
diff --git a/reports/model-editor-closure-20260906/playwright/data/a6fc5911956fe3a4a4954cd956f5789cff0cff6f.png b/reports/model-editor-closure-20260906/playwright/data/a6fc5911956fe3a4a4954cd956f5789cff0cff6f.png
new file mode 100644
index 0000000..1645e8e
Binary files /dev/null and b/reports/model-editor-closure-20260906/playwright/data/a6fc5911956fe3a4a4954cd956f5789cff0cff6f.png differ
diff --git a/reports/model-editor-closure-20260906/playwright/data/bf7191a5fbb250bd515e3f41492aeb23df5ae359.png b/reports/model-editor-closure-20260906/playwright/data/bf7191a5fbb250bd515e3f41492aeb23df5ae359.png
new file mode 100644
index 0000000..65386df
Binary files /dev/null and b/reports/model-editor-closure-20260906/playwright/data/bf7191a5fbb250bd515e3f41492aeb23df5ae359.png differ
diff --git a/reports/model-editor-closure-20260906/playwright/data/bfe3be979894e97e7ec8e89afaae7479271cf2c5.png b/reports/model-editor-closure-20260906/playwright/data/bfe3be979894e97e7ec8e89afaae7479271cf2c5.png
new file mode 100644
index 0000000..e3e57b0
Binary files /dev/null and b/reports/model-editor-closure-20260906/playwright/data/bfe3be979894e97e7ec8e89afaae7479271cf2c5.png differ
diff --git a/reports/model-editor-closure-20260906/playwright/data/c1a8cac198625dfc01dfc5d83d8353368d5fd614.png b/reports/model-editor-closure-20260906/playwright/data/c1a8cac198625dfc01dfc5d83d8353368d5fd614.png
new file mode 100644
index 0000000..3011233
Binary files /dev/null and b/reports/model-editor-closure-20260906/playwright/data/c1a8cac198625dfc01dfc5d83d8353368d5fd614.png differ
diff --git a/reports/model-editor-closure-20260906/playwright/data/c8d16fffb06b4a2c45c493325c9c0c1f3de8c8eb.png b/reports/model-editor-closure-20260906/playwright/data/c8d16fffb06b4a2c45c493325c9c0c1f3de8c8eb.png
new file mode 100644
index 0000000..d0d21cd
Binary files /dev/null and b/reports/model-editor-closure-20260906/playwright/data/c8d16fffb06b4a2c45c493325c9c0c1f3de8c8eb.png differ
diff --git a/reports/model-editor-closure-20260906/playwright/data/d82dbdb1c6db88a715fab45ff23c616188bdf462.png b/reports/model-editor-closure-20260906/playwright/data/d82dbdb1c6db88a715fab45ff23c616188bdf462.png
new file mode 100644
index 0000000..0d8466c
Binary files /dev/null and b/reports/model-editor-closure-20260906/playwright/data/d82dbdb1c6db88a715fab45ff23c616188bdf462.png differ
diff --git a/reports/model-editor-closure-20260906/playwright/data/dbc87e263567cb8ff8bc29c46afcc7ddb1646707.png b/reports/model-editor-closure-20260906/playwright/data/dbc87e263567cb8ff8bc29c46afcc7ddb1646707.png
new file mode 100644
index 0000000..32d188c
Binary files /dev/null and b/reports/model-editor-closure-20260906/playwright/data/dbc87e263567cb8ff8bc29c46afcc7ddb1646707.png differ
diff --git a/reports/model-editor-closure-20260906/playwright/data/e0d2e197c83d3d2a80e936963ccfb353e60bb468.png b/reports/model-editor-closure-20260906/playwright/data/e0d2e197c83d3d2a80e936963ccfb353e60bb468.png
new file mode 100644
index 0000000..66719fe
Binary files /dev/null and b/reports/model-editor-closure-20260906/playwright/data/e0d2e197c83d3d2a80e936963ccfb353e60bb468.png differ
diff --git a/reports/model-editor-closure-20260906/playwright/data/f9835bd2505af78ac1446e317dfcff1389a321ad.png b/reports/model-editor-closure-20260906/playwright/data/f9835bd2505af78ac1446e317dfcff1389a321ad.png
new file mode 100644
index 0000000..d482dcb
Binary files /dev/null and b/reports/model-editor-closure-20260906/playwright/data/f9835bd2505af78ac1446e317dfcff1389a321ad.png differ
diff --git a/reports/model-editor-closure-20260906/playwright/data/fc64bff3e671bf3b5a4829fb6d3d4767217b0a28.png b/reports/model-editor-closure-20260906/playwright/data/fc64bff3e671bf3b5a4829fb6d3d4767217b0a28.png
new file mode 100644
index 0000000..2554c8d
Binary files /dev/null and b/reports/model-editor-closure-20260906/playwright/data/fc64bff3e671bf3b5a4829fb6d3d4767217b0a28.png differ
diff --git a/reports/model-editor-closure-20260906/playwright/data/fcb7b57c5e9ad1b6c4362ed873d6a9782d27a573.png b/reports/model-editor-closure-20260906/playwright/data/fcb7b57c5e9ad1b6c4362ed873d6a9782d27a573.png
new file mode 100644
index 0000000..5ec56aa
Binary files /dev/null and b/reports/model-editor-closure-20260906/playwright/data/fcb7b57c5e9ad1b6c4362ed873d6a9782d27a573.png differ
diff --git a/reports/model-editor-closure-20260906/playwright/data/fcf34a55eee9071e263002610e6f96e164700a57.png b/reports/model-editor-closure-20260906/playwright/data/fcf34a55eee9071e263002610e6f96e164700a57.png
new file mode 100644
index 0000000..cf86b7e
Binary files /dev/null and b/reports/model-editor-closure-20260906/playwright/data/fcf34a55eee9071e263002610e6f96e164700a57.png differ
diff --git a/reports/model-editor-closure-20260906/playwright/index.html b/reports/model-editor-closure-20260906/playwright/index.html
new file mode 100644
index 0000000..58302a6
--- /dev/null
+++ b/reports/model-editor-closure-20260906/playwright/index.html
@@ -0,0 +1,76 @@
+
+
+
+
+
+
+
+
+ Playwright Test Report
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/reports/model-editor-closure-20260906/results.json b/reports/model-editor-closure-20260906/results.json
new file mode 100644
index 0000000..792a8ac
--- /dev/null
+++ b/reports/model-editor-closure-20260906/results.json
@@ -0,0 +1,1473 @@
+{
+ "config": {
+ "configFile": "E:\\work\\edit_3d\\unreal_tran\\ute2e\\model-editor-closure.config.ts",
+ "rootDir": "E:/work/edit_3d/unreal_tran/ute2e/tests",
+ "forbidOnly": false,
+ "fullyParallel": false,
+ "globalSetup": null,
+ "globalTeardown": null,
+ "globalTimeout": 0,
+ "grep": {},
+ "grepInvert": null,
+ "maxFailures": 0,
+ "metadata": {
+ "actualWorkers": 1
+ },
+ "preserveOutput": "always",
+ "reporter": [
+ [
+ "list",
+ null
+ ],
+ [
+ "html",
+ {
+ "outputFolder": "reports/model-editor-closure-20260906/playwright",
+ "open": "never"
+ }
+ ],
+ [
+ "json",
+ {
+ "outputFile": "reports/model-editor-closure-20260906/results.json"
+ }
+ ]
+ ],
+ "reportSlowTests": {
+ "max": 5,
+ "threshold": 300000
+ },
+ "quiet": false,
+ "projects": [
+ {
+ "outputDir": "E:/work/edit_3d/unreal_tran/ute2e/reports/model-editor-closure-20260906/test-results",
+ "repeatEach": 1,
+ "retries": 0,
+ "metadata": {
+ "actualWorkers": 1
+ },
+ "id": "",
+ "name": "",
+ "testDir": "E:/work/edit_3d/unreal_tran/ute2e/tests",
+ "testIgnore": [],
+ "testMatch": [
+ "model-editor-*closure.spec.ts"
+ ],
+ "timeout": 180000
+ }
+ ],
+ "shard": null,
+ "updateSnapshots": "missing",
+ "updateSourceMethod": "patch",
+ "version": "1.55.0",
+ "workers": 1,
+ "webServer": null
+ },
+ "suites": [
+ {
+ "title": "model-editor-assets-closure.spec.ts",
+ "file": "model-editor-assets-closure.spec.ts",
+ "column": 0,
+ "line": 0,
+ "specs": [
+ {
+ "title": "受控模型与外部环境 GLB 上传、JSON 往返和刷新重开",
+ "ok": true,
+ "tags": [],
+ "tests": [
+ {
+ "timeout": 240000,
+ "annotations": [],
+ "expectedStatus": "passed",
+ "projectId": "",
+ "projectName": "",
+ "results": [
+ {
+ "workerIndex": 0,
+ "parallelIndex": 0,
+ "status": "passed",
+ "duration": 14587,
+ "errors": [],
+ "stdout": [],
+ "stderr": [],
+ "retry": 0,
+ "steps": [
+ {
+ "title": "Expect \"poll toBe\"",
+ "duration": 865
+ },
+ {
+ "title": "Expect \"poll toBe\"",
+ "duration": 892
+ },
+ {
+ "title": "Expect \"poll toBe\"",
+ "duration": 858
+ }
+ ],
+ "startTime": "2026-09-06T08:20:12.862Z",
+ "annotations": [],
+ "attachments": [
+ {
+ "name": "30-assets-environment-reopened",
+ "contentType": "image/png",
+ "path": "E:\\work\\edit_3d\\unreal_tran\\ute2e\\reports\\model-editor-closure-20260906\\test-results\\model-editor-assets-closure-受控模型与外部环境-GLB-上传、JSON-往返和刷新重开\\attachments\\30-assets-environment-reopened-0b217450c575c27e43124bef07a78ad9994b5655.png"
+ },
+ {
+ "name": "asset-closure-request-failures",
+ "contentType": "application/json",
+ "body": "ewogICJzdGF0dXMiOiAicGFzc2VkIiwKICAiZmFpbHVyZXMiOiBbXQp9"
+ }
+ ]
+ }
+ ],
+ "status": "expected"
+ }
+ ],
+ "id": "cb4f1f36c25897770766-c32cd9aa8e762c665bb1",
+ "file": "model-editor-assets-closure.spec.ts",
+ "line": 96,
+ "column": 1
+ },
+ {
+ "title": "非法 JSON 与未登记 blob 引用导入失败时保留当前文档",
+ "ok": true,
+ "tags": [],
+ "tests": [
+ {
+ "timeout": 240000,
+ "annotations": [],
+ "expectedStatus": "passed",
+ "projectId": "",
+ "projectName": "",
+ "results": [
+ {
+ "workerIndex": 0,
+ "parallelIndex": 0,
+ "status": "passed",
+ "duration": 5983,
+ "errors": [],
+ "stdout": [],
+ "stderr": [],
+ "retry": 0,
+ "steps": [
+ {
+ "title": "Expect \"poll toBe\"",
+ "duration": 855
+ }
+ ],
+ "startTime": "2026-09-06T08:20:28.057Z",
+ "annotations": [],
+ "attachments": [
+ {
+ "name": "31-invalid-import-keeps-model",
+ "contentType": "image/png",
+ "path": "E:\\work\\edit_3d\\unreal_tran\\ute2e\\reports\\model-editor-closure-20260906\\test-results\\model-editor-assets-closure-非法-JSON-与未登记-blob-引用导入失败时保留当前文档\\attachments\\31-invalid-import-keeps-model-5904d280d3b8ecdbe4d76a8ac27b6e7a40a4aad8.png"
+ },
+ {
+ "name": "asset-closure-request-failures",
+ "contentType": "application/json",
+ "body": "ewogICJzdGF0dXMiOiAicGFzc2VkIiwKICAiZmFpbHVyZXMiOiBbXQp9"
+ }
+ ]
+ }
+ ],
+ "status": "expected"
+ }
+ ],
+ "id": "cb4f1f36c25897770766-7a4927774126e60694fc",
+ "file": "model-editor-assets-closure.spec.ts",
+ "line": 173,
+ "column": 1
+ },
+ {
+ "title": "保存请求超时和 409 保留恢复副本,解除故障后可重试",
+ "ok": true,
+ "tags": [],
+ "tests": [
+ {
+ "timeout": 240000,
+ "annotations": [],
+ "expectedStatus": "passed",
+ "projectId": "",
+ "projectName": "",
+ "results": [
+ {
+ "workerIndex": 0,
+ "parallelIndex": 0,
+ "status": "passed",
+ "duration": 9659,
+ "errors": [],
+ "stdout": [],
+ "stderr": [],
+ "retry": 0,
+ "steps": [
+ {
+ "title": "Expect \"poll toBe\"",
+ "duration": 856
+ },
+ {
+ "title": "Expect \"poll toBe\"",
+ "duration": 881
+ },
+ {
+ "title": "Expect \"poll toBe\"",
+ "duration": 869
+ }
+ ],
+ "startTime": "2026-09-06T08:20:34.059Z",
+ "annotations": [],
+ "attachments": [
+ {
+ "name": "32-save-conflict-keeps-recovery",
+ "contentType": "image/png",
+ "path": "E:\\work\\edit_3d\\unreal_tran\\ute2e\\reports\\model-editor-closure-20260906\\test-results\\model-editor-assets-closure-保存请求超时和-409-保留恢复副本,解除故障后可重试\\attachments\\32-save-conflict-keeps-recovery-bdbd9d2f5f21f32934d13ba7c960ef621322e580.png"
+ },
+ {
+ "name": "33-save-retry-success",
+ "contentType": "image/png",
+ "path": "E:\\work\\edit_3d\\unreal_tran\\ute2e\\reports\\model-editor-closure-20260906\\test-results\\model-editor-assets-closure-保存请求超时和-409-保留恢复副本,解除故障后可重试\\attachments\\33-save-retry-success-cd8188c4d4d9e62319c0d19d3065adf4239f054c.png"
+ },
+ {
+ "name": "asset-closure-request-failures",
+ "contentType": "application/json",
+ "body": "ewogICJzdGF0dXMiOiAicGFzc2VkIiwKICAiZmFpbHVyZXMiOiBbCiAgICB7CiAgICAgICJwYXRoIjogIi9hcGkvdHJhbi92MS9jb250ZW50L3Byb2plY3RzLzIyOCIsCiAgICAgICJtZXRob2QiOiAiUFVUIiwKICAgICAgImVycm9yIjogIm5ldDo6RVJSX1RJTUVEX09VVCIKICAgIH0sCiAgICB7CiAgICAgICJwYXRoIjogIi9hcGkvdHJhbi92MS9jb250ZW50L3Byb2plY3RzLzIyOCIsCiAgICAgICJtZXRob2QiOiAiUFVUIiwKICAgICAgInN0YXR1cyI6IDQwOQogICAgfQogIF0KfQ=="
+ }
+ ]
+ }
+ ],
+ "status": "expected"
+ }
+ ],
+ "id": "cb4f1f36c25897770766-aa6175a8b5542b6e12f4",
+ "file": "model-editor-assets-closure.spec.ts",
+ "line": 200,
+ "column": 1
+ },
+ {
+ "title": "直接发布同步截图封面、固定版本目录与真实离线步骤 HTML",
+ "ok": true,
+ "tags": [],
+ "tests": [
+ {
+ "timeout": 240000,
+ "annotations": [],
+ "expectedStatus": "passed",
+ "projectId": "",
+ "projectName": "",
+ "results": [
+ {
+ "workerIndex": 0,
+ "parallelIndex": 0,
+ "status": "passed",
+ "duration": 7862,
+ "errors": [],
+ "stdout": [],
+ "stderr": [],
+ "retry": 0,
+ "steps": [
+ {
+ "title": "Expect \"poll toBe\"",
+ "duration": 858
+ }
+ ],
+ "startTime": "2026-09-06T08:20:43.736Z",
+ "annotations": [],
+ "attachments": [
+ {
+ "name": "34-published-current-cover",
+ "contentType": "image/png",
+ "path": "E:\\work\\edit_3d\\unreal_tran\\ute2e\\reports\\model-editor-closure-20260906\\test-results\\model-editor-assets-closure-直接发布同步截图封面、固定版本目录与真实离线步骤-HTML\\attachments\\34-published-current-cover-3b1ac74b6702d51cdf6323c312e32faf73f8ee2d.png"
+ },
+ {
+ "name": "35-offline-step-demo-actual-file",
+ "contentType": "image/png",
+ "path": "E:\\work\\edit_3d\\unreal_tran\\ute2e\\reports\\model-editor-closure-20260906\\test-results\\model-editor-assets-closure-直接发布同步截图封面、固定版本目录与真实离线步骤-HTML\\attachments\\35-offline-step-demo-actual-file-58b5bf5a8274950c465b689222a28f39946ed7a7.png"
+ },
+ {
+ "name": "asset-closure-request-failures",
+ "contentType": "application/json",
+ "body": "ewogICJzdGF0dXMiOiAicGFzc2VkIiwKICAiZmFpbHVyZXMiOiBbXQp9"
+ }
+ ]
+ }
+ ],
+ "status": "expected"
+ }
+ ],
+ "id": "cb4f1f36c25897770766-1848a4db0bd530391615",
+ "file": "model-editor-assets-closure.spec.ts",
+ "line": 255,
+ "column": 1
+ }
+ ]
+ },
+ {
+ "title": "model-editor-blueprint-closure.spec.ts",
+ "file": "model-editor-blueprint-closure.spec.ts",
+ "column": 0,
+ "line": 0,
+ "specs": [
+ {
+ "title": "时间轴真实控件:轨道/关键帧/播放与曲线 5 插值,保存后重新加载一致",
+ "ok": true,
+ "tags": [],
+ "tests": [
+ {
+ "timeout": 240000,
+ "annotations": [],
+ "expectedStatus": "passed",
+ "projectId": "",
+ "projectName": "",
+ "results": [
+ {
+ "workerIndex": 0,
+ "parallelIndex": 0,
+ "status": "passed",
+ "duration": 11778,
+ "errors": [],
+ "stdout": [],
+ "stderr": [],
+ "retry": 0,
+ "steps": [
+ {
+ "title": "Expect \"poll toBe\"",
+ "duration": 857
+ },
+ {
+ "title": "Expect \"poll toBeGreaterThan\"",
+ "duration": 112
+ },
+ {
+ "title": "Expect \"poll toBe\"",
+ "duration": 887
+ },
+ {
+ "title": "Expect \"poll toBe\"",
+ "duration": 9
+ },
+ {
+ "title": "Expect \"poll toBe\"",
+ "duration": 857
+ }
+ ],
+ "startTime": "2026-09-06T08:20:51.641Z",
+ "annotations": [],
+ "attachments": [
+ {
+ "name": "21-truck-timeline-controls",
+ "contentType": "image/png",
+ "path": "E:\\work\\edit_3d\\unreal_tran\\ute2e\\reports\\model-editor-closure-20260906\\test-results\\model-editor-blueprint-clo-6be9e-轨道-关键帧-播放与曲线-5-插值,保存后重新加载一致\\attachments\\21-truck-timeline-controls-2a17373c89d979c104760948140a377d958f2c9e.png"
+ },
+ {
+ "name": "22-truck-curve-value-five",
+ "contentType": "image/png",
+ "path": "E:\\work\\edit_3d\\unreal_tran\\ute2e\\reports\\model-editor-closure-20260906\\test-results\\model-editor-blueprint-clo-6be9e-轨道-关键帧-播放与曲线-5-插值,保存后重新加载一致\\attachments\\22-truck-curve-value-five-6bc3e10cded19e78397b06fc2c4763e4f66add5b.png"
+ }
+ ]
+ }
+ ],
+ "status": "expected"
+ }
+ ],
+ "id": "7877967975cc19becd23-271d6a09e0cc2942bf5d",
+ "file": "model-editor-blueprint-closure.spec.ts",
+ "line": 111,
+ "column": 1
+ },
+ {
+ "title": "补充时间轴与蓝图工具:刻度/按键/记录/分量、基准匹配、节点搜索拖入与画布平移",
+ "ok": true,
+ "tags": [],
+ "tests": [
+ {
+ "timeout": 480000,
+ "annotations": [],
+ "expectedStatus": "passed",
+ "projectId": "",
+ "projectName": "",
+ "results": [
+ {
+ "workerIndex": 0,
+ "parallelIndex": 0,
+ "status": "passed",
+ "duration": 20110,
+ "errors": [],
+ "stdout": [],
+ "stderr": [],
+ "retry": 0,
+ "steps": [
+ {
+ "title": "Expect \"poll toBe\"",
+ "duration": 853
+ },
+ {
+ "title": "Expect \"poll toBeGreaterThan\"",
+ "duration": 6
+ },
+ {
+ "title": "Expect \"poll toBe\"",
+ "duration": 869
+ },
+ {
+ "title": "Expect \"poll toBe\"",
+ "duration": 10
+ },
+ {
+ "title": "Expect \"poll toBe\"",
+ "duration": 856
+ },
+ {
+ "title": "Expect \"poll toBe\"",
+ "duration": 854
+ },
+ {
+ "title": "Expect \"poll toBe\"",
+ "duration": 1894
+ },
+ {
+ "title": "Expect \"poll toBe\"",
+ "duration": 4
+ },
+ {
+ "title": "Expect \"poll toBe\"",
+ "duration": 25
+ },
+ {
+ "title": "Expect \"poll toBe\"",
+ "duration": 857
+ }
+ ],
+ "startTime": "2026-09-06T08:21:03.434Z",
+ "annotations": [],
+ "attachments": [
+ {
+ "name": "28-curve-axis-y-negative",
+ "contentType": "image/png",
+ "path": "E:\\work\\edit_3d\\unreal_tran\\ute2e\\reports\\model-editor-closure-20260906\\test-results\\model-editor-blueprint-clo-00943-度-按键-记录-分量、基准匹配、节点搜索拖入与画布平移\\attachments\\28-curve-axis-y-negative-64c22e4d86b85537d460ad7d7cdcdce6d25f9181.png"
+ },
+ {
+ "name": "29-timeline-seek-record",
+ "contentType": "image/png",
+ "path": "E:\\work\\edit_3d\\unreal_tran\\ute2e\\reports\\model-editor-closure-20260906\\test-results\\model-editor-blueprint-clo-00943-度-按键-记录-分量、基准匹配、节点搜索拖入与画布平移\\attachments\\29-timeline-seek-record-822f173d22a8dc7042aa141ead0ab475cb16905b.png"
+ },
+ {
+ "name": "30-blueprint-palette-canvas",
+ "contentType": "image/png",
+ "path": "E:\\work\\edit_3d\\unreal_tran\\ute2e\\reports\\model-editor-closure-20260906\\test-results\\model-editor-blueprint-clo-00943-度-按键-记录-分量、基准匹配、节点搜索拖入与画布平移\\attachments\\30-blueprint-palette-canvas-d404defdf7e7032c5f0ee25a2de998c94e73e7ce.png"
+ },
+ {
+ "name": "31-bridge-baseline-reopened",
+ "contentType": "image/png",
+ "path": "E:\\work\\edit_3d\\unreal_tran\\ute2e\\reports\\model-editor-closure-20260906\\test-results\\model-editor-blueprint-clo-00943-度-按键-记录-分量、基准匹配、节点搜索拖入与画布平移\\attachments\\31-bridge-baseline-reopened-98df622fb0096d1491b00525e0175e8242002375.png"
+ }
+ ]
+ }
+ ],
+ "status": "expected"
+ }
+ ],
+ "id": "7877967975cc19becd23-894018d5f244ed4b3c23",
+ "file": "model-editor-blueprint-closure.spec.ts",
+ "line": 220,
+ "column": 1
+ },
+ {
+ "title": "蓝图真实节点库与端口拖线:七种参数、内部边复制/剪切、撤销重做及保存",
+ "ok": true,
+ "tags": [],
+ "tests": [
+ {
+ "timeout": 300000,
+ "annotations": [],
+ "expectedStatus": "passed",
+ "projectId": "",
+ "projectName": "",
+ "results": [
+ {
+ "workerIndex": 0,
+ "parallelIndex": 0,
+ "status": "passed",
+ "duration": 14140,
+ "errors": [],
+ "stdout": [],
+ "stderr": [],
+ "retry": 0,
+ "steps": [
+ {
+ "title": "Expect \"poll toBe\"",
+ "duration": 858
+ },
+ {
+ "title": "Expect \"poll toBe\"",
+ "duration": 4
+ },
+ {
+ "title": "Expect \"poll toBe\"",
+ "duration": 10
+ },
+ {
+ "title": "Expect \"poll toBe\"",
+ "duration": 7
+ },
+ {
+ "title": "Expect \"poll toBe\"",
+ "duration": 8
+ },
+ {
+ "title": "Expect \"poll toBe\"",
+ "duration": 7
+ },
+ {
+ "title": "Expect \"poll toBe\"",
+ "duration": 9
+ },
+ {
+ "title": "Expect \"poll toBe\"",
+ "duration": 12
+ },
+ {
+ "title": "Expect \"poll toEqual\"",
+ "duration": 8
+ },
+ {
+ "title": "Expect \"poll toEqual\"",
+ "duration": 10
+ },
+ {
+ "title": "Expect \"poll toBe\"",
+ "duration": 10
+ },
+ {
+ "title": "Expect \"poll toBe\"",
+ "duration": 2
+ },
+ {
+ "title": "Expect \"poll toBe\"",
+ "duration": 3
+ },
+ {
+ "title": "Expect \"poll toBe\"",
+ "duration": 878
+ },
+ {
+ "title": "Expect \"poll toBe\"",
+ "duration": 4
+ }
+ ],
+ "startTime": "2026-09-06T08:21:23.563Z",
+ "annotations": [],
+ "attachments": [
+ {
+ "name": "23-truck-blueprint-seven-nodes",
+ "contentType": "image/png",
+ "path": "E:\\work\\edit_3d\\unreal_tran\\ute2e\\reports\\model-editor-closure-20260906\\test-results\\model-editor-blueprint-clo-a1167-与端口拖线:七种参数、内部边复制-剪切、撤销重做及保存\\attachments\\23-truck-blueprint-seven-nodes-ecc767fc459b2952be7e19e13c0110bd1a2dee33.png"
+ },
+ {
+ "name": "24-truck-blueprint-real-message",
+ "contentType": "image/png",
+ "path": "E:\\work\\edit_3d\\unreal_tran\\ute2e\\reports\\model-editor-closure-20260906\\test-results\\model-editor-blueprint-clo-a1167-与端口拖线:七种参数、内部边复制-剪切、撤销重做及保存\\attachments\\24-truck-blueprint-real-message-38c2313f1adb8f4ed6447bc7f8d522c4b83aa41a.png"
+ },
+ {
+ "name": "25-truck-blueprint-completed-log",
+ "contentType": "image/png",
+ "path": "E:\\work\\edit_3d\\unreal_tran\\ute2e\\reports\\model-editor-closure-20260906\\test-results\\model-editor-blueprint-clo-a1167-与端口拖线:七种参数、内部边复制-剪切、撤销重做及保存\\attachments\\25-truck-blueprint-completed-log-c0df93a4b0b5662b8a4136bf134dc15e82d0ce03.png"
+ }
+ ]
+ }
+ ],
+ "status": "expected"
+ }
+ ],
+ "id": "7877967975cc19becd23-d21d03795b85962de781",
+ "file": "model-editor-blueprint-closure.spec.ts",
+ "line": 401,
+ "column": 1
+ },
+ {
+ "title": "已保存蓝图:条件出口与目标匹配 runtime 检查,真实模型单击/双击/进入预览自动触发",
+ "ok": true,
+ "tags": [],
+ "tests": [
+ {
+ "timeout": 240000,
+ "annotations": [],
+ "expectedStatus": "passed",
+ "projectId": "",
+ "projectName": "",
+ "results": [
+ {
+ "workerIndex": 0,
+ "parallelIndex": 0,
+ "status": "passed",
+ "duration": 13050,
+ "errors": [],
+ "stdout": [],
+ "stderr": [],
+ "retry": 0,
+ "steps": [
+ {
+ "title": "Expect \"poll toBe\"",
+ "duration": 858
+ },
+ {
+ "title": "Expect \"poll toBe\"",
+ "duration": 7
+ },
+ {
+ "title": "Expect \"poll toBe\"",
+ "duration": 12
+ },
+ {
+ "title": "Expect \"poll toBe\"",
+ "duration": 15
+ },
+ {
+ "title": "Expect \"poll toBe\"",
+ "duration": 7
+ },
+ {
+ "title": "Expect \"poll toBe\"",
+ "duration": 9
+ },
+ {
+ "title": "Expect \"poll toBe\"",
+ "duration": 2
+ },
+ {
+ "title": "Expect \"poll toBe\"",
+ "duration": 17
+ },
+ {
+ "title": "Expect \"poll toBe\"",
+ "duration": 5
+ },
+ {
+ "title": "Expect \"poll toBe\"",
+ "duration": 11
+ },
+ {
+ "title": "Expect \"poll toBe\"",
+ "duration": 2
+ },
+ {
+ "title": "Expect \"poll toBe\"",
+ "duration": 3
+ },
+ {
+ "title": "Expect \"poll toBe\"",
+ "duration": 11
+ },
+ {
+ "title": "Expect \"poll toBe\"",
+ "duration": 7
+ },
+ {
+ "title": "Expect \"poll toBe\"",
+ "duration": 6
+ },
+ {
+ "title": "Expect \"poll toBe\"",
+ "duration": 878
+ },
+ {
+ "title": "Expect \"poll toBe\"",
+ "duration": 2
+ },
+ {
+ "title": "Expect \"poll toBe\"",
+ "duration": 854
+ }
+ ],
+ "startTime": "2026-09-06T08:21:37.716Z",
+ "annotations": [],
+ "attachments": [
+ {
+ "name": "26-truck-condition-branches-runtime",
+ "contentType": "image/png",
+ "path": "E:\\work\\edit_3d\\unreal_tran\\ute2e\\reports\\model-editor-closure-20260906\\test-results\\model-editor-blueprint-clo-bd382-ntime-检查,真实模型单击-双击-进入预览自动触发\\attachments\\26-truck-condition-branches-runtime-9128d681c549a4fa3148bd07b3357db657a40d6c.png"
+ },
+ {
+ "name": "27-truck-preview-click",
+ "contentType": "image/png",
+ "path": "E:\\work\\edit_3d\\unreal_tran\\ute2e\\reports\\model-editor-closure-20260906\\test-results\\model-editor-blueprint-clo-bd382-ntime-检查,真实模型单击-双击-进入预览自动触发\\attachments\\27-truck-preview-click-6d9291281fc9b367812df6555e82464d6dada33a.png"
+ },
+ {
+ "name": "27-truck-preview-double-click",
+ "contentType": "image/png",
+ "path": "E:\\work\\edit_3d\\unreal_tran\\ute2e\\reports\\model-editor-closure-20260906\\test-results\\model-editor-blueprint-clo-bd382-ntime-检查,真实模型单击-双击-进入预览自动触发\\attachments\\27-truck-preview-double-click-127eaa96a52803ae0386ecbfa9ac1303679c5b14.png"
+ },
+ {
+ "name": "27-truck-preview-auto",
+ "contentType": "image/png",
+ "path": "E:\\work\\edit_3d\\unreal_tran\\ute2e\\reports\\model-editor-closure-20260906\\test-results\\model-editor-blueprint-clo-bd382-ntime-检查,真实模型单击-双击-进入预览自动触发\\attachments\\27-truck-preview-auto-2f7b6d23546285f001cce25ea2530bb42557031b.png"
+ }
+ ]
+ }
+ ],
+ "status": "expected"
+ }
+ ],
+ "id": "7877967975cc19becd23-179d4c75cc4d6ebe8031",
+ "file": "model-editor-blueprint-closure.spec.ts",
+ "line": 530,
+ "column": 1
+ }
+ ]
+ },
+ {
+ "title": "model-editor-closure.spec.ts",
+ "file": "model-editor-closure.spec.ts",
+ "column": 0,
+ "line": 0,
+ "specs": [
+ {
+ "title": "对象名称、变换、PBR、属性与扩展标记保存重开;撤销重做",
+ "ok": true,
+ "tags": [],
+ "tests": [
+ {
+ "timeout": 180000,
+ "annotations": [],
+ "expectedStatus": "passed",
+ "projectId": "",
+ "projectName": "",
+ "results": [
+ {
+ "workerIndex": 0,
+ "parallelIndex": 0,
+ "status": "passed",
+ "duration": 8429,
+ "errors": [],
+ "stdout": [],
+ "stderr": [],
+ "retry": 0,
+ "steps": [
+ {
+ "title": "Expect \"poll toBe\"",
+ "duration": 856
+ },
+ {
+ "title": "Expect \"poll toBe\"",
+ "duration": 880
+ },
+ {
+ "title": "Expect \"poll toBe\"",
+ "duration": 857
+ }
+ ],
+ "startTime": "2026-09-06T08:21:50.801Z",
+ "annotations": [],
+ "attachments": [
+ {
+ "name": "01-components-saved",
+ "contentType": "image/png",
+ "path": "E:\\work\\edit_3d\\unreal_tran\\ute2e\\reports\\model-editor-closure-20260906\\test-results\\model-editor-closure-对象名称、变换、PBR、属性与扩展标记保存重开;撤销重做\\attachments\\01-components-saved-ae39fa21d0fc2a897ce7de3b0f88ccfc3e2f856e.png"
+ },
+ {
+ "name": "02-properties-reopened",
+ "contentType": "image/png",
+ "path": "E:\\work\\edit_3d\\unreal_tran\\ute2e\\reports\\model-editor-closure-20260906\\test-results\\model-editor-closure-对象名称、变换、PBR、属性与扩展标记保存重开;撤销重做\\attachments\\02-properties-reopened-a57241261681ccf5003bc3518934bc6a405ac8da.png"
+ }
+ ]
+ }
+ ],
+ "status": "expected"
+ }
+ ],
+ "id": "ee29163d1ffaab76499c-aacae52d788da5146fa0",
+ "file": "model-editor-closure.spec.ts",
+ "line": 21,
+ "column": 1
+ },
+ {
+ "title": "场景树搜索、可见性、选择、视口工具、原生动画与截图",
+ "ok": true,
+ "tags": [],
+ "tests": [
+ {
+ "timeout": 180000,
+ "annotations": [],
+ "expectedStatus": "passed",
+ "projectId": "",
+ "projectName": "",
+ "results": [
+ {
+ "workerIndex": 0,
+ "parallelIndex": 0,
+ "status": "passed",
+ "duration": 15440,
+ "errors": [],
+ "stdout": [],
+ "stderr": [],
+ "retry": 0,
+ "steps": [
+ {
+ "title": "Expect \"poll toBe\"",
+ "duration": 855
+ },
+ {
+ "title": "Expect \"poll toBe\"",
+ "duration": 1880
+ },
+ {
+ "title": "Expect \"poll toBe\"",
+ "duration": 354
+ },
+ {
+ "title": "Expect \"poll toBeGreaterThan\"",
+ "duration": 7
+ },
+ {
+ "title": "Expect \"poll toBe\"",
+ "duration": 887
+ },
+ {
+ "title": "Expect \"poll toBe\"",
+ "duration": 855
+ }
+ ],
+ "startTime": "2026-09-06T08:21:59.244Z",
+ "annotations": [],
+ "attachments": [
+ {
+ "name": "03-fox-preview",
+ "contentType": "image/png",
+ "path": "E:\\work\\edit_3d\\unreal_tran\\ute2e\\reports\\model-editor-closure-20260906\\test-results\\model-editor-closure-场景树搜索、可见性、选择、视口工具、原生动画与截图\\attachments\\03-fox-preview-6838bb394189ee194653cdf1ce1b443e70df1a8b.png"
+ }
+ ]
+ }
+ ],
+ "status": "expected"
+ }
+ ],
+ "id": "ee29163d1ffaab76499c-a2e7d404b95cc8622dda",
+ "file": "model-editor-closure.spec.ts",
+ "line": 76,
+ "column": 1
+ },
+ {
+ "title": "新建工程取消及失败保留输入;成功创建新ID且不覆盖当前模型",
+ "ok": true,
+ "tags": [],
+ "tests": [
+ {
+ "timeout": 180000,
+ "annotations": [],
+ "expectedStatus": "passed",
+ "projectId": "",
+ "projectName": "",
+ "results": [
+ {
+ "workerIndex": 0,
+ "parallelIndex": 0,
+ "status": "passed",
+ "duration": 10526,
+ "errors": [],
+ "stdout": [],
+ "stderr": [],
+ "retry": 0,
+ "steps": [
+ {
+ "title": "Expect \"poll toBe\"",
+ "duration": 856
+ },
+ {
+ "title": "Expect \"poll toBe\"",
+ "duration": 0
+ }
+ ],
+ "startTime": "2026-09-06T08:22:14.701Z",
+ "annotations": [],
+ "attachments": [
+ {
+ "name": "04-new-retry-dialog",
+ "contentType": "image/png",
+ "path": "E:\\work\\edit_3d\\unreal_tran\\ute2e\\reports\\model-editor-closure-20260906\\test-results\\model-editor-closure-新建工程取消及失败保留输入;成功创建新ID且不覆盖当前模型\\attachments\\04-new-retry-dialog-afe979ffafbc07e6d7990008d5f175f563e9c01e.png"
+ },
+ {
+ "name": "05-new-separate-project",
+ "contentType": "image/png",
+ "path": "E:\\work\\edit_3d\\unreal_tran\\ute2e\\reports\\model-editor-closure-20260906\\test-results\\model-editor-closure-新建工程取消及失败保留输入;成功创建新ID且不覆盖当前模型\\attachments\\05-new-separate-project-64ded6f37c342a5b2e22a8016537d8b284dc7a58.png"
+ }
+ ]
+ }
+ ],
+ "status": "expected"
+ }
+ ],
+ "id": "ee29163d1ffaab76499c-5a027e2b322a568805c4",
+ "file": "model-editor-closure.spec.ts",
+ "line": 143,
+ "column": 1
+ },
+ {
+ "title": "管理员只读权限:编辑、新建、导入、保存发布均不可越权",
+ "ok": true,
+ "tags": [],
+ "tests": [
+ {
+ "timeout": 180000,
+ "annotations": [],
+ "expectedStatus": "passed",
+ "projectId": "",
+ "projectName": "",
+ "results": [
+ {
+ "workerIndex": 0,
+ "parallelIndex": 0,
+ "status": "passed",
+ "duration": 4731,
+ "errors": [],
+ "stdout": [],
+ "stderr": [],
+ "retry": 0,
+ "steps": [
+ {
+ "title": "Expect \"poll toBe\"",
+ "duration": 856
+ },
+ {
+ "title": "Expect \"poll toBe\"",
+ "duration": 8
+ },
+ {
+ "title": "Expect \"poll toBe\"",
+ "duration": 2
+ },
+ {
+ "title": "Expect \"poll toBe\"",
+ "duration": 3
+ }
+ ],
+ "startTime": "2026-09-06T08:22:25.248Z",
+ "annotations": [],
+ "attachments": [
+ {
+ "name": "06-readonly-permissions",
+ "contentType": "image/png",
+ "path": "E:\\work\\edit_3d\\unreal_tran\\ute2e\\reports\\model-editor-closure-20260906\\test-results\\model-editor-closure-管理员只读权限:编辑、新建、导入、保存发布均不可越权\\attachments\\06-readonly-permissions-8658be816abe5235cf7912998e213d81202b0928.png"
+ }
+ ]
+ }
+ ],
+ "status": "expected"
+ }
+ ],
+ "id": "ee29163d1ffaab76499c-cf68684abf52fefaa3fb",
+ "file": "model-editor-closure.spec.ts",
+ "line": 182,
+ "column": 1
+ }
+ ]
+ },
+ {
+ "title": "model-editor-interaction-closure.spec.ts",
+ "file": "model-editor-interaction-closure.spec.ts",
+ "column": 0,
+ "line": 0,
+ "specs": [
+ {
+ "title": "真实三维移动、旋转、缩放手柄拖动及撤销重做保存重开",
+ "ok": true,
+ "tags": [],
+ "tests": [
+ {
+ "timeout": 180000,
+ "annotations": [],
+ "expectedStatus": "passed",
+ "projectId": "",
+ "projectName": "",
+ "results": [
+ {
+ "workerIndex": 0,
+ "parallelIndex": 0,
+ "status": "passed",
+ "duration": 9359,
+ "errors": [],
+ "stdout": [],
+ "stderr": [],
+ "retry": 0,
+ "steps": [
+ {
+ "title": "Expect \"poll toBe\"",
+ "duration": 855
+ },
+ {
+ "title": "Expect \"poll not toEqual\"",
+ "duration": 6
+ },
+ {
+ "title": "Expect \"poll toEqual\"",
+ "duration": 5
+ },
+ {
+ "title": "Expect \"poll toBe\"",
+ "duration": 868
+ },
+ {
+ "title": "Expect \"poll toBe\"",
+ "duration": 858
+ }
+ ],
+ "startTime": "2026-09-06T08:22:30.002Z",
+ "annotations": [],
+ "attachments": [
+ {
+ "name": "51-transform-gizmos",
+ "contentType": "image/png",
+ "path": "E:\\work\\edit_3d\\unreal_tran\\ute2e\\reports\\model-editor-closure-20260906\\test-results\\model-editor-interaction-closure-真实三维移动、旋转、缩放手柄拖动及撤销重做保存重开\\attachments\\51-transform-gizmos-0879ae115c902aae87005110b9920fb2dee858e8.png"
+ }
+ ]
+ }
+ ],
+ "status": "expected"
+ }
+ ],
+ "id": "986249bbcf0ca9ffdf3b-6b3263dcbc38adfe102c",
+ "file": "model-editor-interaction-closure.spec.ts",
+ "line": 57,
+ "column": 1
+ },
+ {
+ "title": "未保存离开:关闭确认保留工作、保存失败留在原页、重试保存后离开",
+ "ok": true,
+ "tags": [],
+ "tests": [
+ {
+ "timeout": 180000,
+ "annotations": [],
+ "expectedStatus": "passed",
+ "projectId": "",
+ "projectName": "",
+ "results": [
+ {
+ "workerIndex": 0,
+ "parallelIndex": 0,
+ "status": "passed",
+ "duration": 9506,
+ "errors": [],
+ "stdout": [],
+ "stderr": [],
+ "retry": 0,
+ "steps": [
+ {
+ "title": "Expect \"poll toBe\"",
+ "duration": 857
+ },
+ {
+ "title": "Expect \"poll toBe\"",
+ "duration": 884
+ }
+ ],
+ "startTime": "2026-09-06T08:22:39.374Z",
+ "annotations": [],
+ "attachments": [
+ {
+ "name": "52-leave-save-failure",
+ "contentType": "image/png",
+ "path": "E:\\work\\edit_3d\\unreal_tran\\ute2e\\reports\\model-editor-closure-20260906\\test-results\\model-editor-interaction-c-1d68c-开:关闭确认保留工作、保存失败留在原页、重试保存后离开\\attachments\\52-leave-save-failure-30b1a23cb07d8b2055eb222a027356eb4e56af68.png"
+ }
+ ]
+ }
+ ],
+ "status": "expected"
+ }
+ ],
+ "id": "986249bbcf0ca9ffdf3b-318d7c3e35387cc044e5",
+ "file": "model-editor-interaction-closure.spec.ts",
+ "line": 85,
+ "column": 1
+ }
+ ]
+ },
+ {
+ "title": "model-editor-navigation-closure.spec.ts",
+ "file": "model-editor-navigation-closure.spec.ts",
+ "column": 0,
+ "line": 0,
+ "specs": [
+ {
+ "title": "补充资源库:搜索清空、真实卡片切换与刷新保留",
+ "ok": true,
+ "tags": [],
+ "tests": [
+ {
+ "timeout": 360000,
+ "annotations": [],
+ "expectedStatus": "passed",
+ "projectId": "",
+ "projectName": "",
+ "results": [
+ {
+ "workerIndex": 0,
+ "parallelIndex": 0,
+ "status": "passed",
+ "duration": 8527,
+ "errors": [],
+ "stdout": [],
+ "stderr": [],
+ "retry": 0,
+ "steps": [
+ {
+ "title": "Expect \"poll toBe\"",
+ "duration": 858
+ },
+ {
+ "title": "Expect \"poll toEqual\"",
+ "duration": 12
+ },
+ {
+ "title": "Expect \"poll toBe\"",
+ "duration": 858
+ }
+ ],
+ "startTime": "2026-09-06T08:22:48.914Z",
+ "annotations": [],
+ "attachments": [
+ {
+ "name": "41-resource-search-restored",
+ "contentType": "image/png",
+ "path": "E:\\work\\edit_3d\\unreal_tran\\ute2e\\reports\\model-editor-closure-20260906\\test-results\\model-editor-navigation-closure-补充资源库:搜索清空、真实卡片切换与刷新保留\\attachments\\41-resource-search-restored-62898b900698f2ddcb8150f9fb249cc2009b0a76.png"
+ },
+ {
+ "name": "42-resource-card-switch-reopened",
+ "contentType": "image/png",
+ "path": "E:\\work\\edit_3d\\unreal_tran\\ute2e\\reports\\model-editor-closure-20260906\\test-results\\model-editor-navigation-closure-补充资源库:搜索清空、真实卡片切换与刷新保留\\attachments\\42-resource-card-switch-reopened-d6612f2d7a4310fd29bf300a5474e8de9dbeb5c9.png"
+ }
+ ]
+ }
+ ],
+ "status": "expected"
+ }
+ ],
+ "id": "3416cd0ceb7fe78cc3e9-322ded20ed2e8453279b",
+ "file": "model-editor-navigation-closure.spec.ts",
+ "line": 42,
+ "column": 1
+ },
+ {
+ "title": "补充环境与树:内置三环境保存重开、节点计数与折叠展开",
+ "ok": true,
+ "tags": [],
+ "tests": [
+ {
+ "timeout": 360000,
+ "annotations": [],
+ "expectedStatus": "passed",
+ "projectId": "",
+ "projectName": "",
+ "results": [
+ {
+ "workerIndex": 0,
+ "parallelIndex": 0,
+ "status": "passed",
+ "duration": 22755,
+ "errors": [],
+ "stdout": [],
+ "stderr": [],
+ "retry": 0,
+ "steps": [
+ {
+ "title": "Expect \"poll toBe\"",
+ "duration": 855
+ },
+ {
+ "title": "Expect \"poll toBe\"",
+ "duration": 869
+ },
+ {
+ "title": "Expect \"poll toBe\"",
+ "duration": 857
+ },
+ {
+ "title": "Expect \"poll toBe\"",
+ "duration": 875
+ },
+ {
+ "title": "Expect \"poll toBe\"",
+ "duration": 355
+ },
+ {
+ "title": "Expect \"poll toBe\"",
+ "duration": 891
+ },
+ {
+ "title": "Expect \"poll toBe\"",
+ "duration": 356
+ },
+ {
+ "title": "Expect \"poll toBe\"",
+ "duration": 1884
+ }
+ ],
+ "startTime": "2026-09-06T08:22:57.459Z",
+ "annotations": [],
+ "attachments": [
+ {
+ "name": "43-builtin-workshop-reopened",
+ "contentType": "image/png",
+ "path": "E:\\work\\edit_3d\\unreal_tran\\ute2e\\reports\\model-editor-closure-20260906\\test-results\\model-editor-navigation-closure-补充环境与树:内置三环境保存重开、节点计数与折叠展开\\attachments\\43-builtin-workshop-reopened-3a9569a421c4b2a54350c03fc4f9d27aa4508cc0.png"
+ },
+ {
+ "name": "44-builtin-outdoor-reopened",
+ "contentType": "image/png",
+ "path": "E:\\work\\edit_3d\\unreal_tran\\ute2e\\reports\\model-editor-closure-20260906\\test-results\\model-editor-navigation-closure-补充环境与树:内置三环境保存重开、节点计数与折叠展开\\attachments\\44-builtin-outdoor-reopened-1fc5ca99f556a6dceb804ecfaf565ad3a47b0456.png"
+ },
+ {
+ "name": "45-builtin-depot-reopened",
+ "contentType": "image/png",
+ "path": "E:\\work\\edit_3d\\unreal_tran\\ute2e\\reports\\model-editor-closure-20260906\\test-results\\model-editor-navigation-closure-补充环境与树:内置三环境保存重开、节点计数与折叠展开\\attachments\\45-builtin-depot-reopened-f8abc2100587ac8320fc411d6807cd52c11bdf62.png"
+ },
+ {
+ "name": "66-depot-saved-return-list",
+ "contentType": "image/png",
+ "path": "E:\\work\\edit_3d\\unreal_tran\\ute2e\\reports\\model-editor-closure-20260906\\test-results\\model-editor-navigation-closure-补充环境与树:内置三环境保存重开、节点计数与折叠展开\\attachments\\66-depot-saved-return-list-799dae9d08248509e596a99158db667f031fa21a.png"
+ }
+ ]
+ }
+ ],
+ "status": "expected"
+ }
+ ],
+ "id": "3416cd0ceb7fe78cc3e9-20ed1751b964d5d1dd89",
+ "file": "model-editor-navigation-closure.spec.ts",
+ "line": 93,
+ "column": 1
+ },
+ {
+ "title": "补充视口与工作区:鼠标镜头、聚焦、面板、预览导航、日志与自动保存",
+ "ok": true,
+ "tags": [],
+ "tests": [
+ {
+ "timeout": 360000,
+ "annotations": [],
+ "expectedStatus": "passed",
+ "projectId": "",
+ "projectName": "",
+ "results": [
+ {
+ "workerIndex": 0,
+ "parallelIndex": 0,
+ "status": "passed",
+ "duration": 34856,
+ "errors": [],
+ "stdout": [],
+ "stderr": [],
+ "retry": 0,
+ "steps": [
+ {
+ "title": "Expect \"poll toBe\"",
+ "duration": 854
+ },
+ {
+ "title": "Expect \"poll not toBe\"",
+ "duration": 4
+ },
+ {
+ "title": "Expect \"poll not toBe\"",
+ "duration": 2
+ },
+ {
+ "title": "Expect \"poll toBeLessThan\"",
+ "duration": 3
+ },
+ {
+ "title": "Expect \"poll toBe\"",
+ "duration": 16
+ },
+ {
+ "title": "Expect \"poll toBe\"",
+ "duration": 5
+ },
+ {
+ "title": "Expect \"poll toBe\"",
+ "duration": 353
+ },
+ {
+ "title": "Expect \"poll toMatch\"",
+ "duration": 4
+ }
+ ],
+ "startTime": "2026-09-06T08:23:20.225Z",
+ "annotations": [],
+ "attachments": [
+ {
+ "name": "46-preview-navigation",
+ "contentType": "image/png",
+ "path": "E:\\work\\edit_3d\\unreal_tran\\ute2e\\reports\\model-editor-closure-20260906\\test-results\\model-editor-navigation-cl-eb142-工作区:鼠标镜头、聚焦、面板、预览导航、日志与自动保存\\attachments\\46-preview-navigation-79c779579b09e8604281e58630286df53a836b46.png"
+ },
+ {
+ "name": "47-navigation-autosave-log",
+ "contentType": "image/png",
+ "path": "E:\\work\\edit_3d\\unreal_tran\\ute2e\\reports\\model-editor-closure-20260906\\test-results\\model-editor-navigation-cl-eb142-工作区:鼠标镜头、聚焦、面板、预览导航、日志与自动保存\\attachments\\47-navigation-autosave-log-ac9ff200bfc61b9c7f06756d0337eb231e97c172.png"
+ }
+ ]
+ }
+ ],
+ "status": "expected"
+ }
+ ],
+ "id": "3416cd0ceb7fe78cc3e9-06e06fa6f5f97869928e",
+ "file": "model-editor-navigation-closure.spec.ts",
+ "line": 152,
+ "column": 1
+ }
+ ]
+ },
+ {
+ "title": "model-editor-recovery-closure.spec.ts",
+ "file": "model-editor-recovery-closure.spec.ts",
+ "column": 0,
+ "line": 0,
+ "specs": [
+ {
+ "title": "真实未保存稿同版本恢复并显式保存;旧版本恢复稿下载隔离保留",
+ "ok": true,
+ "tags": [],
+ "tests": [
+ {
+ "timeout": 180000,
+ "annotations": [],
+ "expectedStatus": "passed",
+ "projectId": "",
+ "projectName": "",
+ "results": [
+ {
+ "workerIndex": 0,
+ "parallelIndex": 0,
+ "status": "passed",
+ "duration": 12525,
+ "errors": [],
+ "stdout": [],
+ "stderr": [],
+ "retry": 0,
+ "steps": [
+ {
+ "title": "Expect \"poll toBe\"",
+ "duration": 853
+ },
+ {
+ "title": "Expect \"poll toBe\"",
+ "duration": 862
+ },
+ {
+ "title": "Expect \"poll toBe\"",
+ "duration": 1
+ },
+ {
+ "title": "Expect \"poll toBe\"",
+ "duration": 868
+ },
+ {
+ "title": "Expect \"poll toBe\"",
+ "duration": 355
+ },
+ {
+ "title": "Expect \"poll toBe\"",
+ "duration": 0
+ }
+ ],
+ "startTime": "2026-09-06T08:23:55.126Z",
+ "annotations": [],
+ "attachments": [
+ {
+ "name": "60-same-version-real-recovery",
+ "contentType": "image/png",
+ "path": "E:\\work\\edit_3d\\unreal_tran\\ute2e\\reports\\model-editor-closure-20260906\\test-results\\model-editor-recovery-closure-真实未保存稿同版本恢复并显式保存;旧版本恢复稿下载隔离保留\\attachments\\60-same-version-real-recovery-d2301e7ae7ff7cc63db5cac6677b53f042d262ee.png"
+ },
+ {
+ "name": "61-recovered-draft-explicitly-saved",
+ "contentType": "image/png",
+ "path": "E:\\work\\edit_3d\\unreal_tran\\ute2e\\reports\\model-editor-closure-20260906\\test-results\\model-editor-recovery-closure-真实未保存稿同版本恢复并显式保存;旧版本恢复稿下载隔离保留\\attachments\\61-recovered-draft-explicitly-saved-6e2daca0e6690289171370e74711c92c81e533b5.png"
+ },
+ {
+ "name": "62-stale-version-recovery-protection",
+ "contentType": "image/png",
+ "path": "E:\\work\\edit_3d\\unreal_tran\\ute2e\\reports\\model-editor-closure-20260906\\test-results\\model-editor-recovery-closure-真实未保存稿同版本恢复并显式保存;旧版本恢复稿下载隔离保留\\attachments\\62-stale-version-recovery-protection-3568bd8f9a1edd173ceb683a07ce142bb99e78ec.png"
+ }
+ ]
+ }
+ ],
+ "status": "expected"
+ }
+ ],
+ "id": "c025f1fb3c3358087016-30fef0c4a9087ce6c3ff",
+ "file": "model-editor-recovery-closure.spec.ts",
+ "line": 64,
+ "column": 1
+ },
+ {
+ "title": "受控 GLB 下载失败阻止编辑,重试成功;发布预检开关无写入且拒绝错误资源 JSON",
+ "ok": true,
+ "tags": [],
+ "tests": [
+ {
+ "timeout": 180000,
+ "annotations": [],
+ "expectedStatus": "passed",
+ "projectId": "",
+ "projectName": "",
+ "results": [
+ {
+ "workerIndex": 0,
+ "parallelIndex": 0,
+ "status": "passed",
+ "duration": 9843,
+ "errors": [],
+ "stdout": [],
+ "stderr": [],
+ "retry": 0,
+ "steps": [
+ {
+ "title": "Expect \"poll toBe\"",
+ "duration": 858
+ },
+ {
+ "title": "Expect \"poll toBe\"",
+ "duration": 103
+ }
+ ],
+ "startTime": "2026-09-06T08:24:07.671Z",
+ "annotations": [],
+ "attachments": [
+ {
+ "name": "63-controlled-glb-download-failed",
+ "contentType": "image/png",
+ "path": "E:\\work\\edit_3d\\unreal_tran\\ute2e\\reports\\model-editor-closure-20260906\\test-results\\model-editor-recovery-clos-6e0ee-,重试成功;发布预检开关无写入且拒绝错误资源-JSON\\attachments\\63-controlled-glb-download-failed-73fceec6371a6105b03cd8ea12f77f39723dcf95.png"
+ },
+ {
+ "name": "64-controlled-glb-retry-loaded",
+ "contentType": "image/png",
+ "path": "E:\\work\\edit_3d\\unreal_tran\\ute2e\\reports\\model-editor-closure-20260906\\test-results\\model-editor-recovery-clos-6e0ee-,重试成功;发布预检开关无写入且拒绝错误资源-JSON\\attachments\\64-controlled-glb-retry-loaded-858424017a5aaf28a86e96f347246671ff3081fb.png"
+ },
+ {
+ "name": "65-publish-preflight-readonly",
+ "contentType": "image/png",
+ "path": "E:\\work\\edit_3d\\unreal_tran\\ute2e\\reports\\model-editor-closure-20260906\\test-results\\model-editor-recovery-clos-6e0ee-,重试成功;发布预检开关无写入且拒绝错误资源-JSON\\attachments\\65-publish-preflight-readonly-cf0ed010f8339fb135edd70ae0023ce93f71419f.png"
+ }
+ ]
+ }
+ ],
+ "status": "expected"
+ }
+ ],
+ "id": "c025f1fb3c3358087016-e9e08080f670bc826303",
+ "file": "model-editor-recovery-closure.spec.ts",
+ "line": 127,
+ "column": 1
+ }
+ ]
+ }
+ ],
+ "errors": [],
+ "stats": {
+ "startTime": "2026-09-06T08:20:11.525Z",
+ "duration": 246742.013,
+ "expected": 19,
+ "skipped": 0,
+ "unexpected": 0,
+ "flaky": 0
+ }
+}
\ No newline at end of file
diff --git a/reports/model-editor-closure-20260906/run-history.json b/reports/model-editor-closure-20260906/run-history.json
new file mode 100644
index 0000000..7dbae2f
--- /dev/null
+++ b/reports/model-editor-closure-20260906/run-history.json
@@ -0,0 +1,100 @@
+{
+ "time": "2026-09-06T08:13:36.256Z",
+ "kind": "historical-runs",
+ "scope": "仅保留安全的次数、名称、时间及源文件哈希;不复制请求正文、会话、错误上下文或私有运行日志。历史轮次不替代当前 results.json,定向2/2不等同全量19/19。",
+ "runs": [
+ {
+ "name": "全量首轮",
+ "outcome": "FAIL",
+ "startedAt": "2026-09-06T07:23:31.420Z",
+ "tests": 19,
+ "passed": 17,
+ "failed": 2,
+ "skipped": 0,
+ "sourceSha256": "21e9e8a824ba988682ee2eb5edcbb35f2b3a97be4f86ad63353c535922aac22b",
+ "sourceDescription": "私有归档的首轮 Playwright JSON",
+ "cases": [
+ {
+ "title": "未保存离开:关闭确认保留工作、保存失败留在原页、重试保存后离开",
+ "outcome": "FAIL",
+ "startTime": "2026-09-06T07:27:01.902Z",
+ "httpStatus": 503,
+ "sourceLocation": {
+ "file": "tests/model-editor-interaction-closure.spec.ts",
+ "line": 114,
+ "column": 41
+ },
+ "request": {
+ "method": "GET",
+ "route": "/api/tran/v1/content/projects/{id}"
+ },
+ "stage": "保存完成并返回模型列表后,由测试辅助函数读取项目详情核验",
+ "note": "界面已返回列表;失败发生于末尾GET详情的状态码断言,不是进入用例登录失败,也不是测试有意注入的保存503。"
+ },
+ {
+ "title": "补充环境与树:内置三环境保存重开、节点计数与折叠展开",
+ "outcome": "FAIL",
+ "startTime": "2026-09-06T07:27:31.096Z",
+ "httpStatus": 503,
+ "sourceLocation": {
+ "file": "tests/model-editor-navigation-closure.spec.ts",
+ "line": 142,
+ "column": 49
+ },
+ "request": {
+ "method": "GET",
+ "route": "/api/tran/v1/content/projects/{id}"
+ },
+ "stage": "保存完成并返回模型列表后,由测试辅助函数读取项目详情核验",
+ "note": "界面已返回列表;失败发生于末尾GET详情的状态码断言,不是进入用例登录失败,也不是测试有意注入的保存503。"
+ }
+ ],
+ "note": "两例均已完成保存并返回列表,末尾测试辅助GET项目详情返回503;分别定位 interaction:114 与 navigation:142。"
+ },
+ {
+ "name": "首次鉴权诊断定向重跑",
+ "outcome": "FAIL",
+ "tests": 2,
+ "passed": 0,
+ "failed": 2,
+ "sourceSha256": "2c66d00995fa4c69ad12b7fb2e3f367b9d7216eb4a2f283e883c560ac38fc573",
+ "sourceDescription": "私有定向运行日志,仅提取计数及调用阶段",
+ "stage": "测试signIn→login请求达到30秒超时",
+ "note": "此轮才在登录阶段超时,业务操作未开始;不与首轮末尾GET详情503混为同一发生阶段。"
+ },
+ {
+ "name": "后续定向重跑",
+ "outcome": "PASS",
+ "tests": 2,
+ "passed": 2,
+ "failed": 0,
+ "sourceSha256": "58711f5c450de4fd85845ec7e2a698fbb5faf01ad3258654ca74964b9e0462b1",
+ "sourceDescription": "私有定向运行日志,匹配原两个失败标题",
+ "cases": [
+ {
+ "title": "未保存离开:关闭确认保留工作、保存失败留在原页、重试保存后离开",
+ "outcome": "PASS"
+ },
+ {
+ "title": "补充环境与树:内置三环境保存重开、节点计数与折叠展开",
+ "outcome": "PASS"
+ }
+ ],
+ "note": "只证明这两条在此次运行通过,不能把首轮17/19改写为19/19。"
+ },
+ {
+ "name": "修复与曲线配色更新后的最终完整轮",
+ "outcome": "PASS",
+ "startedAt": "2026-09-06T08:20:11.525Z",
+ "tests": 19,
+ "passed": 19,
+ "failed": 0,
+ "skipped": 0,
+ "durationMs": 246742.013,
+ "sourceSha256": "f37013658c8c38a5ec5993e636a30ad57eee9d3bcc119650c929952de8d556e9",
+ "sourceDescription": "当前完整 Playwright JSON;重试次数0,未拼接定向结果"
+ }
+ ],
+ "investigation": "已修复 Auth/Tran 数据库连接健康检查可能无界阻塞的问题,并完成实际 Kingbase 驱动隔离黑洞探针(约2010ms释放关闭);最终双模块构建和完整E2E另行记录。该探针不能证明历史每一次503均已逐条与该原因建立因果关联。",
+ "finalAcceptance": "完成修复后另跑完整19条,使用新的完整results.json、对应时段证据/附件、最终validation及artifact检查;旧失败保留在本历史记录。"
+}
\ No newline at end of file
diff --git a/reports/model-editor-closure-20260906/screenshot-captions.json b/reports/model-editor-closure-20260906/screenshot-captions.json
new file mode 100644
index 0000000..242e247
--- /dev/null
+++ b/reports/model-editor-closure-20260906/screenshot-captions.json
@@ -0,0 +1,43 @@
+{
+ "01-components-saved": { "title": "对象属性与扩展标记保存", "description": "通过真实属性控件编辑并保存;新增内容是可持久化的名称标记,不代表组件运行行为已经实现。" },
+ "02-properties-reopened": { "title": "刷新后的模型属性", "description": "重新加载服务端工程后核对对象名称、变换、首个材质槽、自定义字段和扩展标记;不是仅核对保存提示。" },
+ "03-fox-preview": { "title": "狐狸模型运行预览", "description": "真实狐狸模型在编辑器预览视口显示。临时网格、线框、透视等视图选项与模型数据保存分开验收。" },
+ "04-new-retry-dialog": { "title": "新建失败后的重试界面", "description": "定向注入创建请求失败,核对原工程保留及新建表单可重试;该画面不表示开发服务自然发生故障。" },
+ "05-new-separate-project": { "title": "新建独立模型工程", "description": "新建走真实创建接口并进入新项目 ID,原模型工程没有被清空覆盖。" },
+ "06-readonly-permissions": { "title": "只读模型入口", "description": "使用本轮实际权限会话检查只读控件及接口拒绝;平台角色名称本身不授予内容写权限。" },
+ "21-truck-timeline-controls": { "title": "卡车时间轴与部件轨道", "description": "真实轨道增删、目标绑定、播放停止、静音和关键帧操作。当前截图停在零秒;它不表示原生 GLB 动画已经变成多条可编辑时间轴。" },
+ "22-truck-curve-value-five": { "title": "曲线编辑:部件 X=5 的测试姿态", "description": "将卡车车身部件关键帧 X 改为 5 验证曲线与真实视口联动,因此车身与轮子暂时分离;这是测试姿态,不是模型加载丢失。" },
+ "23-truck-blueprint-seven-nodes": { "title": "七种蓝图节点与真实端口连线", "description": "节点通过节点库创建,参数使用实际控件编辑,端口通过指针拖线连接;蓝图图数据随后保存重开核对。" },
+ "24-truck-blueprint-real-message": { "title": "蓝图执行到操作提示", "description": "编辑器真实 handler 驱动时间轴、等待、高亮与提示节点;不是替换 handler 的演示截图。" },
+ "25-truck-blueprint-completed-log": { "title": "蓝图完成与本次运行日志", "description": "用例检查节点顺序、条件出口和完成事件;右侧日志只记录当前运行,不等同服务端审计或教学成绩。" },
+ "26-truck-condition-branches-runtime": { "title": "条件真假出口的运行时补充检查", "description": "对已由 UI 创建的图调用运行时接口检查 true/false 与不匹配目标;这张图标记为运行时证据,不能当成纯鼠标 UI 覆盖。" },
+ "27-truck-preview-click": { "title": "真实单击卡车触发蓝图", "description": "在真实可见网格像素上单击,校验只启动一次且流程完成;没有伪造 trigger 事件。" },
+ "27-truck-preview-double-click": { "title": "真实双击卡车触发蓝图", "description": "通过 renderer canvas 双击命中真实模型,校验事件匹配与单次执行;避免单击误启动双击/自动触发器。" },
+ "27-truck-preview-auto": { "title": "进入预览自动触发蓝图", "description": "真实点击进入预览后派发 auto,校验只启动一次;不代表下游场景或训练会自动运行模型蓝图。" },
+ "28-curve-axis-y-negative": { "title": "曲线分量切换与负值", "description": "在独立卡车副本中设置关键帧 X=5、Y=-2,切换曲线显示分量并比较曲线路径,确认切换视图不修改关键帧数据。车身偏移是特意设置的测试姿态。" },
+ "29-timeline-seek-record": { "title": "真实刻度定位、方向键与关键帧记录", "description": "通过时间刻度点击/拖动、左右与 Shift 按键定位时间,再用实际关键帧按钮记录当前偏移,核对时间码、关键帧和持久化内容。" },
+ "30-blueprint-palette-canvas": { "title": "蓝图库搜索、真实拖入与画布操作", "description": "搜索并清空节点库,通过 HTML5 拖放添加节点,核对工具栏默认添加、画布滚轮缩放/平移/适应操作没有意外改变节点图。" },
+ "31-bridge-baseline-reopened": { "title": "匹配模型的基准时间轴保存重开", "description": "在独立匹配装备副本载入基准动作,核对轨道目标存在、项目名称保留、保存刷新后时间轴一致;不匹配卡车分支另外验证原时间轴未被覆盖。仅验证通用编辑数据,不制作实际维修流程。" },
+ "30-assets-environment-reopened": { "title": "主狐狸与外部方盒环境同时重开", "description": "狐狸是主模型,旁边方盒是轻量环境 GLB;两者使用独立受控资产引用,刷新后均有真实网格。当前镜头较远,资产身份以随附 API 断言核对。" },
+ "31-invalid-import-keeps-model": { "title": "无效工程导入保留原模型", "description": "非法 JSON 或无有效受控引用的导入被拒绝;核对原模型与工程内容仍在,没有以默认模型静默替换。" },
+ "32-save-conflict-keeps-recovery": { "title": "版本冲突阻断保存并保留恢复稿", "description": "通过定向注入 409 验证保存失败分支及恢复数据,不将失败提示本身当成成功保存。" },
+ "33-save-retry-success": { "title": "保存失败后再次保存成功", "description": "恢复正常请求后点击真实保存。右上红色 409 提示来自上一故障注入步骤,截图时尚在自动消退周期内;本次重试成功由当前 PUT 响应和保存后的刷新断言确认。失败后的旧上传票据不得被误复用。" },
+ "34-published-current-cover": { "title": "发布版本与当前视口封面", "description": "发布接口、保存版本和受控封面资产一起核对;封面来自当前三维视口。下载后的下游资源验证不等于下游自动执行蓝图。" },
+ "35-offline-step-demo-actual-file": { "title": "实际下载的步骤演示 HTML", "description": "打开真实导出文件检查步骤演示内容。此文件不含完整三维模型和资源,完整三维离线包列为待确认范围。" },
+ "41-resource-search-restored": { "title": "资源搜索清空后恢复卡片", "description": "输入命中关键字、无匹配关键字,再清空搜索,断言卡片数量与空结果状态;仅搜索当前目录。" },
+ "42-resource-card-switch-reopened": { "title": "真实资源卡片切换后刷新", "description": "在独立副本导入小方盒,再点回原狐狸卡片;保存刷新后核对原资产 ID、code 与真实网格,不改原模型数据。" },
+ "43-builtin-workshop-reopened": { "title": "内置车间环境保存重开", "description": "真实切换内置车间、保存和刷新后核对环境类型与网格;树计数、折叠和展开同时验证。" },
+ "44-builtin-outdoor-reopened": { "title": "内置野外环境保存重开", "description": "真实切换内置野外、保存和刷新后核对环境类型与网格;程序化环境不冒称外部 GLB。" },
+ "45-builtin-depot-reopened": { "title": "内置装备车库保存重开", "description": "真实加载随包车库 GLB,保存刷新后检查 builtin-gltf 来源与网格;树总数与当前展开行数分开统计。" },
+ "46-preview-navigation": { "title": "预览前后步与退出", "description": "通过真实按钮验证下一步推进两秒、上一步回零,并用 Escape 返回编辑;预览步进不是创建新的业务步骤。" },
+ "47-navigation-autosave-log": { "title": "等待自动保存后刷新核对字段", "description": "自定义编码修改后没有点击保存,等待实际 30 秒自动 PUT;刷新确认名称与编码保留。下方日志为本次加载运行日志,FPS 与显存只作状态展示。" },
+ "51-transform-gizmos": { "title": "真实手柄操作的旋转与单轴缩放测试姿态", "description": "为了验证真实平移、旋转与缩放手柄,特意把狐狸旋转至侧立并缩小 X 轴;画面中的侧立和压扁属于测试姿态,不是加载变形。定位只读投影,没有脚本写姿态;随后撤销/重做、保存刷新核对。" },
+ "52-leave-save-failure": { "title": "未保存离开遇到保存失败", "description": "关闭确认框保留修改;定向注入保存 503 后仍留在原页,解除异常后重试保存才离开,最终 API 核对数据。" },
+ "60-same-version-real-recovery": { "title": "真实未保存修改触发同版本恢复", "description": "先在实际属性控件修改,再刷新触发 beforeunload 恢复稿;没有预先注入 localStorage 假快照。此时服务端版本未变化。" },
+ "61-recovered-draft-explicitly-saved": { "title": "恢复稿经显式保存后落库", "description": "点击恢复后编辑器保留需显式保存状态;实际点击保存才更新服务端版本,恢复名称与原受控模型一致。" },
+ "62-stale-version-recovery-protection": { "title": "旧版本恢复稿只下载与隔离", "description": "在独立副本模拟另一个编辑器保存;旧稿不显示覆盖恢复按钮,只下载并隔离保留,服务端较新内容未被覆盖。" },
+ "63-controlled-glb-download-failed": { "title": "受控模型下载失败时禁止编辑", "description": "定向注入资源下载 503,中性加载失败界面保留重试入口,保存与发布均禁用;空白视口不作为模型加载成功。" },
+ "64-controlled-glb-retry-loaded": { "title": "解除资源故障后实际重试成功", "description": "解除下载故障后点击宿主重试,iframe 重新载入并出现真实模型网格;没有以默认占位模型绕过失败。" },
+ "65-publish-preflight-readonly": { "title": "发布预检清单与只读开关行为", "description": "打开和关闭预检面板不会写入项目;错误受控资产引用的 JSON 在替换模型前被拒绝。面板绿标不能替代服务端发布校验。" },
+ "66-depot-saved-return-list": { "title": "车库保存后正常返回列表", "description": "车库环境保存后使用正式宿主导航返回列表,确认不会因旧版规范化器把 depot 误改成 outdoor 而产生虚假的未保存提示;实际修改仍受离开保护。" }
+}
diff --git a/reports/model-editor-closure-20260906/screenshot-verification.json b/reports/model-editor-closure-20260906/screenshot-verification.json
new file mode 100644
index 0000000..1724a07
--- /dev/null
+++ b/reports/model-editor-closure-20260906/screenshot-verification.json
@@ -0,0 +1,498 @@
+{
+ "time": "2026-09-06T08:34:35.457Z",
+ "scope": "截图与当前 results.json 的附件逐字节 SHA256 一致性;同名预跑或重拍不自动计本轮通过。",
+ "screenshots": [
+ {
+ "file": "screenshots/01-components-saved.png",
+ "title": "对象属性与扩展标记保存",
+ "status": "PASS",
+ "associations": [
+ {
+ "title": "对象名称、变换、PBR、属性与扩展标记保存重开;撤销重做",
+ "status": "PASS",
+ "hashMatches": true
+ }
+ ]
+ },
+ {
+ "file": "screenshots/02-properties-reopened.png",
+ "title": "刷新后的模型属性",
+ "status": "PASS",
+ "associations": [
+ {
+ "title": "对象名称、变换、PBR、属性与扩展标记保存重开;撤销重做",
+ "status": "PASS",
+ "hashMatches": true
+ }
+ ]
+ },
+ {
+ "file": "screenshots/03-fox-preview.png",
+ "title": "狐狸模型运行预览",
+ "status": "PASS",
+ "associations": [
+ {
+ "title": "场景树搜索、可见性、选择、视口工具、原生动画与截图",
+ "status": "PASS",
+ "hashMatches": true
+ }
+ ]
+ },
+ {
+ "file": "screenshots/04-new-retry-dialog.png",
+ "title": "新建失败后的重试界面",
+ "status": "PASS",
+ "associations": [
+ {
+ "title": "新建工程取消及失败保留输入;成功创建新ID且不覆盖当前模型",
+ "status": "PASS",
+ "hashMatches": true
+ }
+ ]
+ },
+ {
+ "file": "screenshots/05-new-separate-project.png",
+ "title": "新建独立模型工程",
+ "status": "PASS",
+ "associations": [
+ {
+ "title": "新建工程取消及失败保留输入;成功创建新ID且不覆盖当前模型",
+ "status": "PASS",
+ "hashMatches": true
+ }
+ ]
+ },
+ {
+ "file": "screenshots/06-readonly-permissions.png",
+ "title": "只读模型入口",
+ "status": "PASS",
+ "associations": [
+ {
+ "title": "管理员只读权限:编辑、新建、导入、保存发布均不可越权",
+ "status": "PASS",
+ "hashMatches": true
+ }
+ ]
+ },
+ {
+ "file": "screenshots/21-truck-timeline-controls.png",
+ "title": "卡车时间轴与部件轨道",
+ "status": "PASS",
+ "associations": [
+ {
+ "title": "时间轴真实控件:轨道/关键帧/播放与曲线 5 插值,保存后重新加载一致",
+ "status": "PASS",
+ "hashMatches": true
+ }
+ ]
+ },
+ {
+ "file": "screenshots/22-truck-curve-value-five.png",
+ "title": "曲线编辑:部件 X=5 的测试姿态",
+ "status": "PASS",
+ "associations": [
+ {
+ "title": "时间轴真实控件:轨道/关键帧/播放与曲线 5 插值,保存后重新加载一致",
+ "status": "PASS",
+ "hashMatches": true
+ }
+ ]
+ },
+ {
+ "file": "screenshots/23-truck-blueprint-seven-nodes.png",
+ "title": "七种蓝图节点与真实端口连线",
+ "status": "PASS",
+ "associations": [
+ {
+ "title": "蓝图真实节点库与端口拖线:七种参数、内部边复制/剪切、撤销重做及保存",
+ "status": "PASS",
+ "hashMatches": true
+ }
+ ]
+ },
+ {
+ "file": "screenshots/24-truck-blueprint-real-message.png",
+ "title": "蓝图执行到操作提示",
+ "status": "PASS",
+ "associations": [
+ {
+ "title": "蓝图真实节点库与端口拖线:七种参数、内部边复制/剪切、撤销重做及保存",
+ "status": "PASS",
+ "hashMatches": true
+ }
+ ]
+ },
+ {
+ "file": "screenshots/25-truck-blueprint-completed-log.png",
+ "title": "蓝图完成与本次运行日志",
+ "status": "PASS",
+ "associations": [
+ {
+ "title": "蓝图真实节点库与端口拖线:七种参数、内部边复制/剪切、撤销重做及保存",
+ "status": "PASS",
+ "hashMatches": true
+ }
+ ]
+ },
+ {
+ "file": "screenshots/26-truck-condition-branches-runtime.png",
+ "title": "条件真假出口的运行时补充检查",
+ "status": "PASS",
+ "associations": [
+ {
+ "title": "已保存蓝图:条件出口与目标匹配 runtime 检查,真实模型单击/双击/进入预览自动触发",
+ "status": "PASS",
+ "hashMatches": true
+ }
+ ]
+ },
+ {
+ "file": "screenshots/27-truck-preview-auto.png",
+ "title": "进入预览自动触发蓝图",
+ "status": "PASS",
+ "associations": [
+ {
+ "title": "已保存蓝图:条件出口与目标匹配 runtime 检查,真实模型单击/双击/进入预览自动触发",
+ "status": "PASS",
+ "hashMatches": true
+ }
+ ]
+ },
+ {
+ "file": "screenshots/27-truck-preview-click.png",
+ "title": "真实单击卡车触发蓝图",
+ "status": "PASS",
+ "associations": [
+ {
+ "title": "已保存蓝图:条件出口与目标匹配 runtime 检查,真实模型单击/双击/进入预览自动触发",
+ "status": "PASS",
+ "hashMatches": true
+ }
+ ]
+ },
+ {
+ "file": "screenshots/27-truck-preview-double-click.png",
+ "title": "真实双击卡车触发蓝图",
+ "status": "PASS",
+ "associations": [
+ {
+ "title": "已保存蓝图:条件出口与目标匹配 runtime 检查,真实模型单击/双击/进入预览自动触发",
+ "status": "PASS",
+ "hashMatches": true
+ }
+ ]
+ },
+ {
+ "file": "screenshots/28-curve-axis-y-negative.png",
+ "title": "曲线分量切换与负值",
+ "status": "PASS",
+ "associations": [
+ {
+ "title": "补充时间轴与蓝图工具:刻度/按键/记录/分量、基准匹配、节点搜索拖入与画布平移",
+ "status": "PASS",
+ "hashMatches": true
+ }
+ ]
+ },
+ {
+ "file": "screenshots/29-timeline-seek-record.png",
+ "title": "真实刻度定位、方向键与关键帧记录",
+ "status": "PASS",
+ "associations": [
+ {
+ "title": "补充时间轴与蓝图工具:刻度/按键/记录/分量、基准匹配、节点搜索拖入与画布平移",
+ "status": "PASS",
+ "hashMatches": true
+ }
+ ]
+ },
+ {
+ "file": "screenshots/30-assets-environment-reopened.png",
+ "title": "主狐狸与外部方盒环境同时重开",
+ "status": "PASS",
+ "associations": [
+ {
+ "title": "受控模型与外部环境 GLB 上传、JSON 往返和刷新重开",
+ "status": "PASS",
+ "hashMatches": true
+ }
+ ]
+ },
+ {
+ "file": "screenshots/30-blueprint-palette-canvas.png",
+ "title": "蓝图库搜索、真实拖入与画布操作",
+ "status": "PASS",
+ "associations": [
+ {
+ "title": "补充时间轴与蓝图工具:刻度/按键/记录/分量、基准匹配、节点搜索拖入与画布平移",
+ "status": "PASS",
+ "hashMatches": true
+ }
+ ]
+ },
+ {
+ "file": "screenshots/31-bridge-baseline-reopened.png",
+ "title": "匹配模型的基准时间轴保存重开",
+ "status": "PASS",
+ "associations": [
+ {
+ "title": "补充时间轴与蓝图工具:刻度/按键/记录/分量、基准匹配、节点搜索拖入与画布平移",
+ "status": "PASS",
+ "hashMatches": true
+ }
+ ]
+ },
+ {
+ "file": "screenshots/31-invalid-import-keeps-model.png",
+ "title": "无效工程导入保留原模型",
+ "status": "PASS",
+ "associations": [
+ {
+ "title": "非法 JSON 与未登记 blob 引用导入失败时保留当前文档",
+ "status": "PASS",
+ "hashMatches": true
+ }
+ ]
+ },
+ {
+ "file": "screenshots/32-save-conflict-keeps-recovery.png",
+ "title": "版本冲突阻断保存并保留恢复稿",
+ "status": "PASS",
+ "associations": [
+ {
+ "title": "保存请求超时和 409 保留恢复副本,解除故障后可重试",
+ "status": "PASS",
+ "hashMatches": true
+ }
+ ]
+ },
+ {
+ "file": "screenshots/33-save-retry-success.png",
+ "title": "保存失败后再次保存成功",
+ "status": "PASS",
+ "associations": [
+ {
+ "title": "保存请求超时和 409 保留恢复副本,解除故障后可重试",
+ "status": "PASS",
+ "hashMatches": true
+ }
+ ]
+ },
+ {
+ "file": "screenshots/34-published-current-cover.png",
+ "title": "发布版本与当前视口封面",
+ "status": "PASS",
+ "associations": [
+ {
+ "title": "直接发布同步截图封面、固定版本目录与真实离线步骤 HTML",
+ "status": "PASS",
+ "hashMatches": true
+ }
+ ]
+ },
+ {
+ "file": "screenshots/35-offline-step-demo-actual-file.png",
+ "title": "实际下载的步骤演示 HTML",
+ "status": "PASS",
+ "associations": [
+ {
+ "title": "直接发布同步截图封面、固定版本目录与真实离线步骤 HTML",
+ "status": "PASS",
+ "hashMatches": true
+ }
+ ]
+ },
+ {
+ "file": "screenshots/41-resource-search-restored.png",
+ "title": "资源搜索清空后恢复卡片",
+ "status": "PASS",
+ "associations": [
+ {
+ "title": "补充资源库:搜索清空、真实卡片切换与刷新保留",
+ "status": "PASS",
+ "hashMatches": true
+ }
+ ]
+ },
+ {
+ "file": "screenshots/42-resource-card-switch-reopened.png",
+ "title": "真实资源卡片切换后刷新",
+ "status": "PASS",
+ "associations": [
+ {
+ "title": "补充资源库:搜索清空、真实卡片切换与刷新保留",
+ "status": "PASS",
+ "hashMatches": true
+ }
+ ]
+ },
+ {
+ "file": "screenshots/43-builtin-workshop-reopened.png",
+ "title": "内置车间环境保存重开",
+ "status": "PASS",
+ "associations": [
+ {
+ "title": "补充环境与树:内置三环境保存重开、节点计数与折叠展开",
+ "status": "PASS",
+ "hashMatches": true
+ }
+ ]
+ },
+ {
+ "file": "screenshots/44-builtin-outdoor-reopened.png",
+ "title": "内置野外环境保存重开",
+ "status": "PASS",
+ "associations": [
+ {
+ "title": "补充环境与树:内置三环境保存重开、节点计数与折叠展开",
+ "status": "PASS",
+ "hashMatches": true
+ }
+ ]
+ },
+ {
+ "file": "screenshots/45-builtin-depot-reopened.png",
+ "title": "内置装备车库保存重开",
+ "status": "PASS",
+ "associations": [
+ {
+ "title": "补充环境与树:内置三环境保存重开、节点计数与折叠展开",
+ "status": "PASS",
+ "hashMatches": true
+ }
+ ]
+ },
+ {
+ "file": "screenshots/46-preview-navigation.png",
+ "title": "预览前后步与退出",
+ "status": "PASS",
+ "associations": [
+ {
+ "title": "补充视口与工作区:鼠标镜头、聚焦、面板、预览导航、日志与自动保存",
+ "status": "PASS",
+ "hashMatches": true
+ }
+ ]
+ },
+ {
+ "file": "screenshots/47-navigation-autosave-log.png",
+ "title": "等待自动保存后刷新核对字段",
+ "status": "PASS",
+ "associations": [
+ {
+ "title": "补充视口与工作区:鼠标镜头、聚焦、面板、预览导航、日志与自动保存",
+ "status": "PASS",
+ "hashMatches": true
+ }
+ ]
+ },
+ {
+ "file": "screenshots/51-transform-gizmos.png",
+ "title": "真实手柄操作的旋转与单轴缩放测试姿态",
+ "status": "PASS",
+ "associations": [
+ {
+ "title": "真实三维移动、旋转、缩放手柄拖动及撤销重做保存重开",
+ "status": "PASS",
+ "hashMatches": true
+ }
+ ]
+ },
+ {
+ "file": "screenshots/52-leave-save-failure.png",
+ "title": "未保存离开遇到保存失败",
+ "status": "PASS",
+ "associations": [
+ {
+ "title": "未保存离开:关闭确认保留工作、保存失败留在原页、重试保存后离开",
+ "status": "PASS",
+ "hashMatches": true
+ }
+ ]
+ },
+ {
+ "file": "screenshots/60-same-version-real-recovery.png",
+ "title": "真实未保存修改触发同版本恢复",
+ "status": "PASS",
+ "associations": [
+ {
+ "title": "真实未保存稿同版本恢复并显式保存;旧版本恢复稿下载隔离保留",
+ "status": "PASS",
+ "hashMatches": true
+ }
+ ]
+ },
+ {
+ "file": "screenshots/61-recovered-draft-explicitly-saved.png",
+ "title": "恢复稿经显式保存后落库",
+ "status": "PASS",
+ "associations": [
+ {
+ "title": "真实未保存稿同版本恢复并显式保存;旧版本恢复稿下载隔离保留",
+ "status": "PASS",
+ "hashMatches": true
+ }
+ ]
+ },
+ {
+ "file": "screenshots/62-stale-version-recovery-protection.png",
+ "title": "旧版本恢复稿只下载与隔离",
+ "status": "PASS",
+ "associations": [
+ {
+ "title": "真实未保存稿同版本恢复并显式保存;旧版本恢复稿下载隔离保留",
+ "status": "PASS",
+ "hashMatches": true
+ }
+ ]
+ },
+ {
+ "file": "screenshots/63-controlled-glb-download-failed.png",
+ "title": "受控模型下载失败时禁止编辑",
+ "status": "PASS",
+ "associations": [
+ {
+ "title": "受控 GLB 下载失败阻止编辑,重试成功;发布预检开关无写入且拒绝错误资源 JSON",
+ "status": "PASS",
+ "hashMatches": true
+ }
+ ]
+ },
+ {
+ "file": "screenshots/64-controlled-glb-retry-loaded.png",
+ "title": "解除资源故障后实际重试成功",
+ "status": "PASS",
+ "associations": [
+ {
+ "title": "受控 GLB 下载失败阻止编辑,重试成功;发布预检开关无写入且拒绝错误资源 JSON",
+ "status": "PASS",
+ "hashMatches": true
+ }
+ ]
+ },
+ {
+ "file": "screenshots/65-publish-preflight-readonly.png",
+ "title": "发布预检清单与只读开关行为",
+ "status": "PASS",
+ "associations": [
+ {
+ "title": "受控 GLB 下载失败阻止编辑,重试成功;发布预检开关无写入且拒绝错误资源 JSON",
+ "status": "PASS",
+ "hashMatches": true
+ }
+ ]
+ },
+ {
+ "file": "screenshots/66-depot-saved-return-list.png",
+ "title": "车库保存后正常返回列表",
+ "status": "PASS",
+ "associations": [
+ {
+ "title": "补充环境与树:内置三环境保存重开、节点计数与折叠展开",
+ "status": "PASS",
+ "hashMatches": true
+ }
+ ]
+ }
+ ]
+}
\ No newline at end of file
diff --git a/reports/model-editor-closure-20260906/screenshots/01-components-saved.png b/reports/model-editor-closure-20260906/screenshots/01-components-saved.png
new file mode 100644
index 0000000..61e9450
Binary files /dev/null and b/reports/model-editor-closure-20260906/screenshots/01-components-saved.png differ
diff --git a/reports/model-editor-closure-20260906/screenshots/02-properties-reopened.png b/reports/model-editor-closure-20260906/screenshots/02-properties-reopened.png
new file mode 100644
index 0000000..8a4025c
Binary files /dev/null and b/reports/model-editor-closure-20260906/screenshots/02-properties-reopened.png differ
diff --git a/reports/model-editor-closure-20260906/screenshots/03-fox-preview.png b/reports/model-editor-closure-20260906/screenshots/03-fox-preview.png
new file mode 100644
index 0000000..23b9d50
Binary files /dev/null and b/reports/model-editor-closure-20260906/screenshots/03-fox-preview.png differ
diff --git a/reports/model-editor-closure-20260906/screenshots/04-new-retry-dialog.png b/reports/model-editor-closure-20260906/screenshots/04-new-retry-dialog.png
new file mode 100644
index 0000000..d0d21cd
Binary files /dev/null and b/reports/model-editor-closure-20260906/screenshots/04-new-retry-dialog.png differ
diff --git a/reports/model-editor-closure-20260906/screenshots/05-new-separate-project.png b/reports/model-editor-closure-20260906/screenshots/05-new-separate-project.png
new file mode 100644
index 0000000..32d188c
Binary files /dev/null and b/reports/model-editor-closure-20260906/screenshots/05-new-separate-project.png differ
diff --git a/reports/model-editor-closure-20260906/screenshots/06-readonly-permissions.png b/reports/model-editor-closure-20260906/screenshots/06-readonly-permissions.png
new file mode 100644
index 0000000..4894b9c
Binary files /dev/null and b/reports/model-editor-closure-20260906/screenshots/06-readonly-permissions.png differ
diff --git a/reports/model-editor-closure-20260906/screenshots/21-truck-timeline-controls.png b/reports/model-editor-closure-20260906/screenshots/21-truck-timeline-controls.png
new file mode 100644
index 0000000..e3e57b0
Binary files /dev/null and b/reports/model-editor-closure-20260906/screenshots/21-truck-timeline-controls.png differ
diff --git a/reports/model-editor-closure-20260906/screenshots/22-truck-curve-value-five.png b/reports/model-editor-closure-20260906/screenshots/22-truck-curve-value-five.png
new file mode 100644
index 0000000..5297bfd
Binary files /dev/null and b/reports/model-editor-closure-20260906/screenshots/22-truck-curve-value-five.png differ
diff --git a/reports/model-editor-closure-20260906/screenshots/23-truck-blueprint-seven-nodes.png b/reports/model-editor-closure-20260906/screenshots/23-truck-blueprint-seven-nodes.png
new file mode 100644
index 0000000..8bb4a69
Binary files /dev/null and b/reports/model-editor-closure-20260906/screenshots/23-truck-blueprint-seven-nodes.png differ
diff --git a/reports/model-editor-closure-20260906/screenshots/24-truck-blueprint-real-message.png b/reports/model-editor-closure-20260906/screenshots/24-truck-blueprint-real-message.png
new file mode 100644
index 0000000..83e2291
Binary files /dev/null and b/reports/model-editor-closure-20260906/screenshots/24-truck-blueprint-real-message.png differ
diff --git a/reports/model-editor-closure-20260906/screenshots/25-truck-blueprint-completed-log.png b/reports/model-editor-closure-20260906/screenshots/25-truck-blueprint-completed-log.png
new file mode 100644
index 0000000..0d8466c
Binary files /dev/null and b/reports/model-editor-closure-20260906/screenshots/25-truck-blueprint-completed-log.png differ
diff --git a/reports/model-editor-closure-20260906/screenshots/26-truck-condition-branches-runtime.png b/reports/model-editor-closure-20260906/screenshots/26-truck-condition-branches-runtime.png
new file mode 100644
index 0000000..3011233
Binary files /dev/null and b/reports/model-editor-closure-20260906/screenshots/26-truck-condition-branches-runtime.png differ
diff --git a/reports/model-editor-closure-20260906/screenshots/27-truck-preview-auto.png b/reports/model-editor-closure-20260906/screenshots/27-truck-preview-auto.png
new file mode 100644
index 0000000..3cb5df7
Binary files /dev/null and b/reports/model-editor-closure-20260906/screenshots/27-truck-preview-auto.png differ
diff --git a/reports/model-editor-closure-20260906/screenshots/27-truck-preview-click.png b/reports/model-editor-closure-20260906/screenshots/27-truck-preview-click.png
new file mode 100644
index 0000000..4d04560
Binary files /dev/null and b/reports/model-editor-closure-20260906/screenshots/27-truck-preview-click.png differ
diff --git a/reports/model-editor-closure-20260906/screenshots/27-truck-preview-double-click.png b/reports/model-editor-closure-20260906/screenshots/27-truck-preview-double-click.png
new file mode 100644
index 0000000..3cb5df7
Binary files /dev/null and b/reports/model-editor-closure-20260906/screenshots/27-truck-preview-double-click.png differ
diff --git a/reports/model-editor-closure-20260906/screenshots/28-curve-axis-y-negative.png b/reports/model-editor-closure-20260906/screenshots/28-curve-axis-y-negative.png
new file mode 100644
index 0000000..2554c8d
Binary files /dev/null and b/reports/model-editor-closure-20260906/screenshots/28-curve-axis-y-negative.png differ
diff --git a/reports/model-editor-closure-20260906/screenshots/29-timeline-seek-record.png b/reports/model-editor-closure-20260906/screenshots/29-timeline-seek-record.png
new file mode 100644
index 0000000..8eb0ca2
Binary files /dev/null and b/reports/model-editor-closure-20260906/screenshots/29-timeline-seek-record.png differ
diff --git a/reports/model-editor-closure-20260906/screenshots/30-assets-environment-reopened.png b/reports/model-editor-closure-20260906/screenshots/30-assets-environment-reopened.png
new file mode 100644
index 0000000..5ec56aa
Binary files /dev/null and b/reports/model-editor-closure-20260906/screenshots/30-assets-environment-reopened.png differ
diff --git a/reports/model-editor-closure-20260906/screenshots/30-blueprint-palette-canvas.png b/reports/model-editor-closure-20260906/screenshots/30-blueprint-palette-canvas.png
new file mode 100644
index 0000000..19b556f
Binary files /dev/null and b/reports/model-editor-closure-20260906/screenshots/30-blueprint-palette-canvas.png differ
diff --git a/reports/model-editor-closure-20260906/screenshots/31-bridge-baseline-reopened.png b/reports/model-editor-closure-20260906/screenshots/31-bridge-baseline-reopened.png
new file mode 100644
index 0000000..cf86b7e
Binary files /dev/null and b/reports/model-editor-closure-20260906/screenshots/31-bridge-baseline-reopened.png differ
diff --git a/reports/model-editor-closure-20260906/screenshots/31-invalid-import-keeps-model.png b/reports/model-editor-closure-20260906/screenshots/31-invalid-import-keeps-model.png
new file mode 100644
index 0000000..66719fe
Binary files /dev/null and b/reports/model-editor-closure-20260906/screenshots/31-invalid-import-keeps-model.png differ
diff --git a/reports/model-editor-closure-20260906/screenshots/32-save-conflict-keeps-recovery.png b/reports/model-editor-closure-20260906/screenshots/32-save-conflict-keeps-recovery.png
new file mode 100644
index 0000000..26103fa
Binary files /dev/null and b/reports/model-editor-closure-20260906/screenshots/32-save-conflict-keeps-recovery.png differ
diff --git a/reports/model-editor-closure-20260906/screenshots/33-save-retry-success.png b/reports/model-editor-closure-20260906/screenshots/33-save-retry-success.png
new file mode 100644
index 0000000..d3deb8e
Binary files /dev/null and b/reports/model-editor-closure-20260906/screenshots/33-save-retry-success.png differ
diff --git a/reports/model-editor-closure-20260906/screenshots/34-published-current-cover.png b/reports/model-editor-closure-20260906/screenshots/34-published-current-cover.png
new file mode 100644
index 0000000..0235ce7
Binary files /dev/null and b/reports/model-editor-closure-20260906/screenshots/34-published-current-cover.png differ
diff --git a/reports/model-editor-closure-20260906/screenshots/35-offline-step-demo-actual-file.png b/reports/model-editor-closure-20260906/screenshots/35-offline-step-demo-actual-file.png
new file mode 100644
index 0000000..65386df
Binary files /dev/null and b/reports/model-editor-closure-20260906/screenshots/35-offline-step-demo-actual-file.png differ
diff --git a/reports/model-editor-closure-20260906/screenshots/41-resource-search-restored.png b/reports/model-editor-closure-20260906/screenshots/41-resource-search-restored.png
new file mode 100644
index 0000000..774e368
Binary files /dev/null and b/reports/model-editor-closure-20260906/screenshots/41-resource-search-restored.png differ
diff --git a/reports/model-editor-closure-20260906/screenshots/42-resource-card-switch-reopened.png b/reports/model-editor-closure-20260906/screenshots/42-resource-card-switch-reopened.png
new file mode 100644
index 0000000..d545fb5
Binary files /dev/null and b/reports/model-editor-closure-20260906/screenshots/42-resource-card-switch-reopened.png differ
diff --git a/reports/model-editor-closure-20260906/screenshots/43-builtin-workshop-reopened.png b/reports/model-editor-closure-20260906/screenshots/43-builtin-workshop-reopened.png
new file mode 100644
index 0000000..8639f50
Binary files /dev/null and b/reports/model-editor-closure-20260906/screenshots/43-builtin-workshop-reopened.png differ
diff --git a/reports/model-editor-closure-20260906/screenshots/44-builtin-outdoor-reopened.png b/reports/model-editor-closure-20260906/screenshots/44-builtin-outdoor-reopened.png
new file mode 100644
index 0000000..0f0d72f
Binary files /dev/null and b/reports/model-editor-closure-20260906/screenshots/44-builtin-outdoor-reopened.png differ
diff --git a/reports/model-editor-closure-20260906/screenshots/45-builtin-depot-reopened.png b/reports/model-editor-closure-20260906/screenshots/45-builtin-depot-reopened.png
new file mode 100644
index 0000000..ef8ae04
Binary files /dev/null and b/reports/model-editor-closure-20260906/screenshots/45-builtin-depot-reopened.png differ
diff --git a/reports/model-editor-closure-20260906/screenshots/46-preview-navigation.png b/reports/model-editor-closure-20260906/screenshots/46-preview-navigation.png
new file mode 100644
index 0000000..e43cdb8
Binary files /dev/null and b/reports/model-editor-closure-20260906/screenshots/46-preview-navigation.png differ
diff --git a/reports/model-editor-closure-20260906/screenshots/47-navigation-autosave-log.png b/reports/model-editor-closure-20260906/screenshots/47-navigation-autosave-log.png
new file mode 100644
index 0000000..97271e7
Binary files /dev/null and b/reports/model-editor-closure-20260906/screenshots/47-navigation-autosave-log.png differ
diff --git a/reports/model-editor-closure-20260906/screenshots/51-transform-gizmos.png b/reports/model-editor-closure-20260906/screenshots/51-transform-gizmos.png
new file mode 100644
index 0000000..d482dcb
Binary files /dev/null and b/reports/model-editor-closure-20260906/screenshots/51-transform-gizmos.png differ
diff --git a/reports/model-editor-closure-20260906/screenshots/52-leave-save-failure.png b/reports/model-editor-closure-20260906/screenshots/52-leave-save-failure.png
new file mode 100644
index 0000000..5d4b2e3
Binary files /dev/null and b/reports/model-editor-closure-20260906/screenshots/52-leave-save-failure.png differ
diff --git a/reports/model-editor-closure-20260906/screenshots/60-same-version-real-recovery.png b/reports/model-editor-closure-20260906/screenshots/60-same-version-real-recovery.png
new file mode 100644
index 0000000..3433be0
Binary files /dev/null and b/reports/model-editor-closure-20260906/screenshots/60-same-version-real-recovery.png differ
diff --git a/reports/model-editor-closure-20260906/screenshots/61-recovered-draft-explicitly-saved.png b/reports/model-editor-closure-20260906/screenshots/61-recovered-draft-explicitly-saved.png
new file mode 100644
index 0000000..1645e8e
Binary files /dev/null and b/reports/model-editor-closure-20260906/screenshots/61-recovered-draft-explicitly-saved.png differ
diff --git a/reports/model-editor-closure-20260906/screenshots/62-stale-version-recovery-protection.png b/reports/model-editor-closure-20260906/screenshots/62-stale-version-recovery-protection.png
new file mode 100644
index 0000000..e836f37
Binary files /dev/null and b/reports/model-editor-closure-20260906/screenshots/62-stale-version-recovery-protection.png differ
diff --git a/reports/model-editor-closure-20260906/screenshots/63-controlled-glb-download-failed.png b/reports/model-editor-closure-20260906/screenshots/63-controlled-glb-download-failed.png
new file mode 100644
index 0000000..48c5040
Binary files /dev/null and b/reports/model-editor-closure-20260906/screenshots/63-controlled-glb-download-failed.png differ
diff --git a/reports/model-editor-closure-20260906/screenshots/64-controlled-glb-retry-loaded.png b/reports/model-editor-closure-20260906/screenshots/64-controlled-glb-retry-loaded.png
new file mode 100644
index 0000000..3d3f334
Binary files /dev/null and b/reports/model-editor-closure-20260906/screenshots/64-controlled-glb-retry-loaded.png differ
diff --git a/reports/model-editor-closure-20260906/screenshots/65-publish-preflight-readonly.png b/reports/model-editor-closure-20260906/screenshots/65-publish-preflight-readonly.png
new file mode 100644
index 0000000..72d318f
Binary files /dev/null and b/reports/model-editor-closure-20260906/screenshots/65-publish-preflight-readonly.png differ
diff --git a/reports/model-editor-closure-20260906/screenshots/66-depot-saved-return-list.png b/reports/model-editor-closure-20260906/screenshots/66-depot-saved-return-list.png
new file mode 100644
index 0000000..8347f96
Binary files /dev/null and b/reports/model-editor-closure-20260906/screenshots/66-depot-saved-return-list.png differ
diff --git a/reports/model-editor-closure-20260906/test-results/model-editor-assets-closure-保存请求超时和-409-保留恢复副本,解除故障后可重试/attachments/32-save-conflict-keeps-recovery-bdbd9d2f5f21f32934d13ba7c960ef621322e580.png b/reports/model-editor-closure-20260906/test-results/model-editor-assets-closure-保存请求超时和-409-保留恢复副本,解除故障后可重试/attachments/32-save-conflict-keeps-recovery-bdbd9d2f5f21f32934d13ba7c960ef621322e580.png
new file mode 100644
index 0000000..26103fa
Binary files /dev/null and b/reports/model-editor-closure-20260906/test-results/model-editor-assets-closure-保存请求超时和-409-保留恢复副本,解除故障后可重试/attachments/32-save-conflict-keeps-recovery-bdbd9d2f5f21f32934d13ba7c960ef621322e580.png differ
diff --git a/reports/model-editor-closure-20260906/test-results/model-editor-assets-closure-保存请求超时和-409-保留恢复副本,解除故障后可重试/attachments/33-save-retry-success-cd8188c4d4d9e62319c0d19d3065adf4239f054c.png b/reports/model-editor-closure-20260906/test-results/model-editor-assets-closure-保存请求超时和-409-保留恢复副本,解除故障后可重试/attachments/33-save-retry-success-cd8188c4d4d9e62319c0d19d3065adf4239f054c.png
new file mode 100644
index 0000000..d3deb8e
Binary files /dev/null and b/reports/model-editor-closure-20260906/test-results/model-editor-assets-closure-保存请求超时和-409-保留恢复副本,解除故障后可重试/attachments/33-save-retry-success-cd8188c4d4d9e62319c0d19d3065adf4239f054c.png differ
diff --git a/reports/model-editor-closure-20260906/test-results/model-editor-assets-closure-受控模型与外部环境-GLB-上传、JSON-往返和刷新重开/attachments/30-assets-environment-reopened-0b217450c575c27e43124bef07a78ad9994b5655.png b/reports/model-editor-closure-20260906/test-results/model-editor-assets-closure-受控模型与外部环境-GLB-上传、JSON-往返和刷新重开/attachments/30-assets-environment-reopened-0b217450c575c27e43124bef07a78ad9994b5655.png
new file mode 100644
index 0000000..5ec56aa
Binary files /dev/null and b/reports/model-editor-closure-20260906/test-results/model-editor-assets-closure-受控模型与外部环境-GLB-上传、JSON-往返和刷新重开/attachments/30-assets-environment-reopened-0b217450c575c27e43124bef07a78ad9994b5655.png differ
diff --git a/reports/model-editor-closure-20260906/test-results/model-editor-assets-closure-直接发布同步截图封面、固定版本目录与真实离线步骤-HTML/attachments/34-published-current-cover-3b1ac74b6702d51cdf6323c312e32faf73f8ee2d.png b/reports/model-editor-closure-20260906/test-results/model-editor-assets-closure-直接发布同步截图封面、固定版本目录与真实离线步骤-HTML/attachments/34-published-current-cover-3b1ac74b6702d51cdf6323c312e32faf73f8ee2d.png
new file mode 100644
index 0000000..0235ce7
Binary files /dev/null and b/reports/model-editor-closure-20260906/test-results/model-editor-assets-closure-直接发布同步截图封面、固定版本目录与真实离线步骤-HTML/attachments/34-published-current-cover-3b1ac74b6702d51cdf6323c312e32faf73f8ee2d.png differ
diff --git a/reports/model-editor-closure-20260906/test-results/model-editor-assets-closure-直接发布同步截图封面、固定版本目录与真实离线步骤-HTML/attachments/35-offline-step-demo-actual-file-58b5bf5a8274950c465b689222a28f39946ed7a7.png b/reports/model-editor-closure-20260906/test-results/model-editor-assets-closure-直接发布同步截图封面、固定版本目录与真实离线步骤-HTML/attachments/35-offline-step-demo-actual-file-58b5bf5a8274950c465b689222a28f39946ed7a7.png
new file mode 100644
index 0000000..65386df
Binary files /dev/null and b/reports/model-editor-closure-20260906/test-results/model-editor-assets-closure-直接发布同步截图封面、固定版本目录与真实离线步骤-HTML/attachments/35-offline-step-demo-actual-file-58b5bf5a8274950c465b689222a28f39946ed7a7.png differ
diff --git a/reports/model-editor-closure-20260906/test-results/model-editor-assets-closure-非法-JSON-与未登记-blob-引用导入失败时保留当前文档/attachments/31-invalid-import-keeps-model-5904d280d3b8ecdbe4d76a8ac27b6e7a40a4aad8.png b/reports/model-editor-closure-20260906/test-results/model-editor-assets-closure-非法-JSON-与未登记-blob-引用导入失败时保留当前文档/attachments/31-invalid-import-keeps-model-5904d280d3b8ecdbe4d76a8ac27b6e7a40a4aad8.png
new file mode 100644
index 0000000..66719fe
Binary files /dev/null and b/reports/model-editor-closure-20260906/test-results/model-editor-assets-closure-非法-JSON-与未登记-blob-引用导入失败时保留当前文档/attachments/31-invalid-import-keeps-model-5904d280d3b8ecdbe4d76a8ac27b6e7a40a4aad8.png differ
diff --git a/reports/model-editor-closure-20260906/test-results/model-editor-blueprint-clo-00943-度-按键-记录-分量、基准匹配、节点搜索拖入与画布平移/attachments/28-curve-axis-y-negative-64c22e4d86b85537d460ad7d7cdcdce6d25f9181.png b/reports/model-editor-closure-20260906/test-results/model-editor-blueprint-clo-00943-度-按键-记录-分量、基准匹配、节点搜索拖入与画布平移/attachments/28-curve-axis-y-negative-64c22e4d86b85537d460ad7d7cdcdce6d25f9181.png
new file mode 100644
index 0000000..2554c8d
Binary files /dev/null and b/reports/model-editor-closure-20260906/test-results/model-editor-blueprint-clo-00943-度-按键-记录-分量、基准匹配、节点搜索拖入与画布平移/attachments/28-curve-axis-y-negative-64c22e4d86b85537d460ad7d7cdcdce6d25f9181.png differ
diff --git a/reports/model-editor-closure-20260906/test-results/model-editor-blueprint-clo-00943-度-按键-记录-分量、基准匹配、节点搜索拖入与画布平移/attachments/29-timeline-seek-record-822f173d22a8dc7042aa141ead0ab475cb16905b.png b/reports/model-editor-closure-20260906/test-results/model-editor-blueprint-clo-00943-度-按键-记录-分量、基准匹配、节点搜索拖入与画布平移/attachments/29-timeline-seek-record-822f173d22a8dc7042aa141ead0ab475cb16905b.png
new file mode 100644
index 0000000..8eb0ca2
Binary files /dev/null and b/reports/model-editor-closure-20260906/test-results/model-editor-blueprint-clo-00943-度-按键-记录-分量、基准匹配、节点搜索拖入与画布平移/attachments/29-timeline-seek-record-822f173d22a8dc7042aa141ead0ab475cb16905b.png differ
diff --git a/reports/model-editor-closure-20260906/test-results/model-editor-blueprint-clo-00943-度-按键-记录-分量、基准匹配、节点搜索拖入与画布平移/attachments/30-blueprint-palette-canvas-d404defdf7e7032c5f0ee25a2de998c94e73e7ce.png b/reports/model-editor-closure-20260906/test-results/model-editor-blueprint-clo-00943-度-按键-记录-分量、基准匹配、节点搜索拖入与画布平移/attachments/30-blueprint-palette-canvas-d404defdf7e7032c5f0ee25a2de998c94e73e7ce.png
new file mode 100644
index 0000000..19b556f
Binary files /dev/null and b/reports/model-editor-closure-20260906/test-results/model-editor-blueprint-clo-00943-度-按键-记录-分量、基准匹配、节点搜索拖入与画布平移/attachments/30-blueprint-palette-canvas-d404defdf7e7032c5f0ee25a2de998c94e73e7ce.png differ
diff --git a/reports/model-editor-closure-20260906/test-results/model-editor-blueprint-clo-00943-度-按键-记录-分量、基准匹配、节点搜索拖入与画布平移/attachments/31-bridge-baseline-reopened-98df622fb0096d1491b00525e0175e8242002375.png b/reports/model-editor-closure-20260906/test-results/model-editor-blueprint-clo-00943-度-按键-记录-分量、基准匹配、节点搜索拖入与画布平移/attachments/31-bridge-baseline-reopened-98df622fb0096d1491b00525e0175e8242002375.png
new file mode 100644
index 0000000..cf86b7e
Binary files /dev/null and b/reports/model-editor-closure-20260906/test-results/model-editor-blueprint-clo-00943-度-按键-记录-分量、基准匹配、节点搜索拖入与画布平移/attachments/31-bridge-baseline-reopened-98df622fb0096d1491b00525e0175e8242002375.png differ
diff --git a/reports/model-editor-closure-20260906/test-results/model-editor-blueprint-clo-6be9e-轨道-关键帧-播放与曲线-5-插值,保存后重新加载一致/attachments/21-truck-timeline-controls-2a17373c89d979c104760948140a377d958f2c9e.png b/reports/model-editor-closure-20260906/test-results/model-editor-blueprint-clo-6be9e-轨道-关键帧-播放与曲线-5-插值,保存后重新加载一致/attachments/21-truck-timeline-controls-2a17373c89d979c104760948140a377d958f2c9e.png
new file mode 100644
index 0000000..e3e57b0
Binary files /dev/null and b/reports/model-editor-closure-20260906/test-results/model-editor-blueprint-clo-6be9e-轨道-关键帧-播放与曲线-5-插值,保存后重新加载一致/attachments/21-truck-timeline-controls-2a17373c89d979c104760948140a377d958f2c9e.png differ
diff --git a/reports/model-editor-closure-20260906/test-results/model-editor-blueprint-clo-6be9e-轨道-关键帧-播放与曲线-5-插值,保存后重新加载一致/attachments/22-truck-curve-value-five-6bc3e10cded19e78397b06fc2c4763e4f66add5b.png b/reports/model-editor-closure-20260906/test-results/model-editor-blueprint-clo-6be9e-轨道-关键帧-播放与曲线-5-插值,保存后重新加载一致/attachments/22-truck-curve-value-five-6bc3e10cded19e78397b06fc2c4763e4f66add5b.png
new file mode 100644
index 0000000..5297bfd
Binary files /dev/null and b/reports/model-editor-closure-20260906/test-results/model-editor-blueprint-clo-6be9e-轨道-关键帧-播放与曲线-5-插值,保存后重新加载一致/attachments/22-truck-curve-value-five-6bc3e10cded19e78397b06fc2c4763e4f66add5b.png differ
diff --git a/reports/model-editor-closure-20260906/test-results/model-editor-blueprint-clo-a1167-与端口拖线:七种参数、内部边复制-剪切、撤销重做及保存/attachments/23-truck-blueprint-seven-nodes-ecc767fc459b2952be7e19e13c0110bd1a2dee33.png b/reports/model-editor-closure-20260906/test-results/model-editor-blueprint-clo-a1167-与端口拖线:七种参数、内部边复制-剪切、撤销重做及保存/attachments/23-truck-blueprint-seven-nodes-ecc767fc459b2952be7e19e13c0110bd1a2dee33.png
new file mode 100644
index 0000000..8bb4a69
Binary files /dev/null and b/reports/model-editor-closure-20260906/test-results/model-editor-blueprint-clo-a1167-与端口拖线:七种参数、内部边复制-剪切、撤销重做及保存/attachments/23-truck-blueprint-seven-nodes-ecc767fc459b2952be7e19e13c0110bd1a2dee33.png differ
diff --git a/reports/model-editor-closure-20260906/test-results/model-editor-blueprint-clo-a1167-与端口拖线:七种参数、内部边复制-剪切、撤销重做及保存/attachments/24-truck-blueprint-real-message-38c2313f1adb8f4ed6447bc7f8d522c4b83aa41a.png b/reports/model-editor-closure-20260906/test-results/model-editor-blueprint-clo-a1167-与端口拖线:七种参数、内部边复制-剪切、撤销重做及保存/attachments/24-truck-blueprint-real-message-38c2313f1adb8f4ed6447bc7f8d522c4b83aa41a.png
new file mode 100644
index 0000000..83e2291
Binary files /dev/null and b/reports/model-editor-closure-20260906/test-results/model-editor-blueprint-clo-a1167-与端口拖线:七种参数、内部边复制-剪切、撤销重做及保存/attachments/24-truck-blueprint-real-message-38c2313f1adb8f4ed6447bc7f8d522c4b83aa41a.png differ
diff --git a/reports/model-editor-closure-20260906/test-results/model-editor-blueprint-clo-a1167-与端口拖线:七种参数、内部边复制-剪切、撤销重做及保存/attachments/25-truck-blueprint-completed-log-c0df93a4b0b5662b8a4136bf134dc15e82d0ce03.png b/reports/model-editor-closure-20260906/test-results/model-editor-blueprint-clo-a1167-与端口拖线:七种参数、内部边复制-剪切、撤销重做及保存/attachments/25-truck-blueprint-completed-log-c0df93a4b0b5662b8a4136bf134dc15e82d0ce03.png
new file mode 100644
index 0000000..0d8466c
Binary files /dev/null and b/reports/model-editor-closure-20260906/test-results/model-editor-blueprint-clo-a1167-与端口拖线:七种参数、内部边复制-剪切、撤销重做及保存/attachments/25-truck-blueprint-completed-log-c0df93a4b0b5662b8a4136bf134dc15e82d0ce03.png differ
diff --git a/reports/model-editor-closure-20260906/test-results/model-editor-blueprint-clo-bd382-ntime-检查,真实模型单击-双击-进入预览自动触发/attachments/26-truck-condition-branches-runtime-9128d681c549a4fa3148bd07b3357db657a40d6c.png b/reports/model-editor-closure-20260906/test-results/model-editor-blueprint-clo-bd382-ntime-检查,真实模型单击-双击-进入预览自动触发/attachments/26-truck-condition-branches-runtime-9128d681c549a4fa3148bd07b3357db657a40d6c.png
new file mode 100644
index 0000000..3011233
Binary files /dev/null and b/reports/model-editor-closure-20260906/test-results/model-editor-blueprint-clo-bd382-ntime-检查,真实模型单击-双击-进入预览自动触发/attachments/26-truck-condition-branches-runtime-9128d681c549a4fa3148bd07b3357db657a40d6c.png differ
diff --git a/reports/model-editor-closure-20260906/test-results/model-editor-blueprint-clo-bd382-ntime-检查,真实模型单击-双击-进入预览自动触发/attachments/27-truck-preview-auto-2f7b6d23546285f001cce25ea2530bb42557031b.png b/reports/model-editor-closure-20260906/test-results/model-editor-blueprint-clo-bd382-ntime-检查,真实模型单击-双击-进入预览自动触发/attachments/27-truck-preview-auto-2f7b6d23546285f001cce25ea2530bb42557031b.png
new file mode 100644
index 0000000..3cb5df7
Binary files /dev/null and b/reports/model-editor-closure-20260906/test-results/model-editor-blueprint-clo-bd382-ntime-检查,真实模型单击-双击-进入预览自动触发/attachments/27-truck-preview-auto-2f7b6d23546285f001cce25ea2530bb42557031b.png differ
diff --git a/reports/model-editor-closure-20260906/test-results/model-editor-blueprint-clo-bd382-ntime-检查,真实模型单击-双击-进入预览自动触发/attachments/27-truck-preview-click-6d9291281fc9b367812df6555e82464d6dada33a.png b/reports/model-editor-closure-20260906/test-results/model-editor-blueprint-clo-bd382-ntime-检查,真实模型单击-双击-进入预览自动触发/attachments/27-truck-preview-click-6d9291281fc9b367812df6555e82464d6dada33a.png
new file mode 100644
index 0000000..4d04560
Binary files /dev/null and b/reports/model-editor-closure-20260906/test-results/model-editor-blueprint-clo-bd382-ntime-检查,真实模型单击-双击-进入预览自动触发/attachments/27-truck-preview-click-6d9291281fc9b367812df6555e82464d6dada33a.png differ
diff --git a/reports/model-editor-closure-20260906/test-results/model-editor-blueprint-clo-bd382-ntime-检查,真实模型单击-双击-进入预览自动触发/attachments/27-truck-preview-double-click-127eaa96a52803ae0386ecbfa9ac1303679c5b14.png b/reports/model-editor-closure-20260906/test-results/model-editor-blueprint-clo-bd382-ntime-检查,真实模型单击-双击-进入预览自动触发/attachments/27-truck-preview-double-click-127eaa96a52803ae0386ecbfa9ac1303679c5b14.png
new file mode 100644
index 0000000..3cb5df7
Binary files /dev/null and b/reports/model-editor-closure-20260906/test-results/model-editor-blueprint-clo-bd382-ntime-检查,真实模型单击-双击-进入预览自动触发/attachments/27-truck-preview-double-click-127eaa96a52803ae0386ecbfa9ac1303679c5b14.png differ
diff --git a/reports/model-editor-closure-20260906/test-results/model-editor-closure-场景树搜索、可见性、选择、视口工具、原生动画与截图/attachments/03-fox-preview-6838bb394189ee194653cdf1ce1b443e70df1a8b.png b/reports/model-editor-closure-20260906/test-results/model-editor-closure-场景树搜索、可见性、选择、视口工具、原生动画与截图/attachments/03-fox-preview-6838bb394189ee194653cdf1ce1b443e70df1a8b.png
new file mode 100644
index 0000000..23b9d50
Binary files /dev/null and b/reports/model-editor-closure-20260906/test-results/model-editor-closure-场景树搜索、可见性、选择、视口工具、原生动画与截图/attachments/03-fox-preview-6838bb394189ee194653cdf1ce1b443e70df1a8b.png differ
diff --git a/reports/model-editor-closure-20260906/test-results/model-editor-closure-对象名称、变换、PBR、属性与扩展标记保存重开;撤销重做/attachments/01-components-saved-ae39fa21d0fc2a897ce7de3b0f88ccfc3e2f856e.png b/reports/model-editor-closure-20260906/test-results/model-editor-closure-对象名称、变换、PBR、属性与扩展标记保存重开;撤销重做/attachments/01-components-saved-ae39fa21d0fc2a897ce7de3b0f88ccfc3e2f856e.png
new file mode 100644
index 0000000..61e9450
Binary files /dev/null and b/reports/model-editor-closure-20260906/test-results/model-editor-closure-对象名称、变换、PBR、属性与扩展标记保存重开;撤销重做/attachments/01-components-saved-ae39fa21d0fc2a897ce7de3b0f88ccfc3e2f856e.png differ
diff --git a/reports/model-editor-closure-20260906/test-results/model-editor-closure-对象名称、变换、PBR、属性与扩展标记保存重开;撤销重做/attachments/02-properties-reopened-a57241261681ccf5003bc3518934bc6a405ac8da.png b/reports/model-editor-closure-20260906/test-results/model-editor-closure-对象名称、变换、PBR、属性与扩展标记保存重开;撤销重做/attachments/02-properties-reopened-a57241261681ccf5003bc3518934bc6a405ac8da.png
new file mode 100644
index 0000000..8a4025c
Binary files /dev/null and b/reports/model-editor-closure-20260906/test-results/model-editor-closure-对象名称、变换、PBR、属性与扩展标记保存重开;撤销重做/attachments/02-properties-reopened-a57241261681ccf5003bc3518934bc6a405ac8da.png differ
diff --git a/reports/model-editor-closure-20260906/test-results/model-editor-closure-新建工程取消及失败保留输入;成功创建新ID且不覆盖当前模型/attachments/04-new-retry-dialog-afe979ffafbc07e6d7990008d5f175f563e9c01e.png b/reports/model-editor-closure-20260906/test-results/model-editor-closure-新建工程取消及失败保留输入;成功创建新ID且不覆盖当前模型/attachments/04-new-retry-dialog-afe979ffafbc07e6d7990008d5f175f563e9c01e.png
new file mode 100644
index 0000000..d0d21cd
Binary files /dev/null and b/reports/model-editor-closure-20260906/test-results/model-editor-closure-新建工程取消及失败保留输入;成功创建新ID且不覆盖当前模型/attachments/04-new-retry-dialog-afe979ffafbc07e6d7990008d5f175f563e9c01e.png differ
diff --git a/reports/model-editor-closure-20260906/test-results/model-editor-closure-新建工程取消及失败保留输入;成功创建新ID且不覆盖当前模型/attachments/05-new-separate-project-64ded6f37c342a5b2e22a8016537d8b284dc7a58.png b/reports/model-editor-closure-20260906/test-results/model-editor-closure-新建工程取消及失败保留输入;成功创建新ID且不覆盖当前模型/attachments/05-new-separate-project-64ded6f37c342a5b2e22a8016537d8b284dc7a58.png
new file mode 100644
index 0000000..32d188c
Binary files /dev/null and b/reports/model-editor-closure-20260906/test-results/model-editor-closure-新建工程取消及失败保留输入;成功创建新ID且不覆盖当前模型/attachments/05-new-separate-project-64ded6f37c342a5b2e22a8016537d8b284dc7a58.png differ
diff --git a/reports/model-editor-closure-20260906/test-results/model-editor-closure-管理员只读权限:编辑、新建、导入、保存发布均不可越权/attachments/06-readonly-permissions-8658be816abe5235cf7912998e213d81202b0928.png b/reports/model-editor-closure-20260906/test-results/model-editor-closure-管理员只读权限:编辑、新建、导入、保存发布均不可越权/attachments/06-readonly-permissions-8658be816abe5235cf7912998e213d81202b0928.png
new file mode 100644
index 0000000..4894b9c
Binary files /dev/null and b/reports/model-editor-closure-20260906/test-results/model-editor-closure-管理员只读权限:编辑、新建、导入、保存发布均不可越权/attachments/06-readonly-permissions-8658be816abe5235cf7912998e213d81202b0928.png differ
diff --git a/reports/model-editor-closure-20260906/test-results/model-editor-interaction-c-1d68c-开:关闭确认保留工作、保存失败留在原页、重试保存后离开/attachments/52-leave-save-failure-30b1a23cb07d8b2055eb222a027356eb4e56af68.png b/reports/model-editor-closure-20260906/test-results/model-editor-interaction-c-1d68c-开:关闭确认保留工作、保存失败留在原页、重试保存后离开/attachments/52-leave-save-failure-30b1a23cb07d8b2055eb222a027356eb4e56af68.png
new file mode 100644
index 0000000..5d4b2e3
Binary files /dev/null and b/reports/model-editor-closure-20260906/test-results/model-editor-interaction-c-1d68c-开:关闭确认保留工作、保存失败留在原页、重试保存后离开/attachments/52-leave-save-failure-30b1a23cb07d8b2055eb222a027356eb4e56af68.png differ
diff --git a/reports/model-editor-closure-20260906/test-results/model-editor-interaction-closure-真实三维移动、旋转、缩放手柄拖动及撤销重做保存重开/attachments/51-transform-gizmos-0879ae115c902aae87005110b9920fb2dee858e8.png b/reports/model-editor-closure-20260906/test-results/model-editor-interaction-closure-真实三维移动、旋转、缩放手柄拖动及撤销重做保存重开/attachments/51-transform-gizmos-0879ae115c902aae87005110b9920fb2dee858e8.png
new file mode 100644
index 0000000..d482dcb
Binary files /dev/null and b/reports/model-editor-closure-20260906/test-results/model-editor-interaction-closure-真实三维移动、旋转、缩放手柄拖动及撤销重做保存重开/attachments/51-transform-gizmos-0879ae115c902aae87005110b9920fb2dee858e8.png differ
diff --git a/reports/model-editor-closure-20260906/test-results/model-editor-navigation-cl-eb142-工作区:鼠标镜头、聚焦、面板、预览导航、日志与自动保存/attachments/46-preview-navigation-79c779579b09e8604281e58630286df53a836b46.png b/reports/model-editor-closure-20260906/test-results/model-editor-navigation-cl-eb142-工作区:鼠标镜头、聚焦、面板、预览导航、日志与自动保存/attachments/46-preview-navigation-79c779579b09e8604281e58630286df53a836b46.png
new file mode 100644
index 0000000..e43cdb8
Binary files /dev/null and b/reports/model-editor-closure-20260906/test-results/model-editor-navigation-cl-eb142-工作区:鼠标镜头、聚焦、面板、预览导航、日志与自动保存/attachments/46-preview-navigation-79c779579b09e8604281e58630286df53a836b46.png differ
diff --git a/reports/model-editor-closure-20260906/test-results/model-editor-navigation-cl-eb142-工作区:鼠标镜头、聚焦、面板、预览导航、日志与自动保存/attachments/47-navigation-autosave-log-ac9ff200bfc61b9c7f06756d0337eb231e97c172.png b/reports/model-editor-closure-20260906/test-results/model-editor-navigation-cl-eb142-工作区:鼠标镜头、聚焦、面板、预览导航、日志与自动保存/attachments/47-navigation-autosave-log-ac9ff200bfc61b9c7f06756d0337eb231e97c172.png
new file mode 100644
index 0000000..97271e7
Binary files /dev/null and b/reports/model-editor-closure-20260906/test-results/model-editor-navigation-cl-eb142-工作区:鼠标镜头、聚焦、面板、预览导航、日志与自动保存/attachments/47-navigation-autosave-log-ac9ff200bfc61b9c7f06756d0337eb231e97c172.png differ
diff --git a/reports/model-editor-closure-20260906/test-results/model-editor-navigation-closure-补充环境与树:内置三环境保存重开、节点计数与折叠展开/attachments/43-builtin-workshop-reopened-3a9569a421c4b2a54350c03fc4f9d27aa4508cc0.png b/reports/model-editor-closure-20260906/test-results/model-editor-navigation-closure-补充环境与树:内置三环境保存重开、节点计数与折叠展开/attachments/43-builtin-workshop-reopened-3a9569a421c4b2a54350c03fc4f9d27aa4508cc0.png
new file mode 100644
index 0000000..8639f50
Binary files /dev/null and b/reports/model-editor-closure-20260906/test-results/model-editor-navigation-closure-补充环境与树:内置三环境保存重开、节点计数与折叠展开/attachments/43-builtin-workshop-reopened-3a9569a421c4b2a54350c03fc4f9d27aa4508cc0.png differ
diff --git a/reports/model-editor-closure-20260906/test-results/model-editor-navigation-closure-补充环境与树:内置三环境保存重开、节点计数与折叠展开/attachments/44-builtin-outdoor-reopened-1fc5ca99f556a6dceb804ecfaf565ad3a47b0456.png b/reports/model-editor-closure-20260906/test-results/model-editor-navigation-closure-补充环境与树:内置三环境保存重开、节点计数与折叠展开/attachments/44-builtin-outdoor-reopened-1fc5ca99f556a6dceb804ecfaf565ad3a47b0456.png
new file mode 100644
index 0000000..0f0d72f
Binary files /dev/null and b/reports/model-editor-closure-20260906/test-results/model-editor-navigation-closure-补充环境与树:内置三环境保存重开、节点计数与折叠展开/attachments/44-builtin-outdoor-reopened-1fc5ca99f556a6dceb804ecfaf565ad3a47b0456.png differ
diff --git a/reports/model-editor-closure-20260906/test-results/model-editor-navigation-closure-补充环境与树:内置三环境保存重开、节点计数与折叠展开/attachments/45-builtin-depot-reopened-f8abc2100587ac8320fc411d6807cd52c11bdf62.png b/reports/model-editor-closure-20260906/test-results/model-editor-navigation-closure-补充环境与树:内置三环境保存重开、节点计数与折叠展开/attachments/45-builtin-depot-reopened-f8abc2100587ac8320fc411d6807cd52c11bdf62.png
new file mode 100644
index 0000000..ef8ae04
Binary files /dev/null and b/reports/model-editor-closure-20260906/test-results/model-editor-navigation-closure-补充环境与树:内置三环境保存重开、节点计数与折叠展开/attachments/45-builtin-depot-reopened-f8abc2100587ac8320fc411d6807cd52c11bdf62.png differ
diff --git a/reports/model-editor-closure-20260906/test-results/model-editor-navigation-closure-补充环境与树:内置三环境保存重开、节点计数与折叠展开/attachments/66-depot-saved-return-list-799dae9d08248509e596a99158db667f031fa21a.png b/reports/model-editor-closure-20260906/test-results/model-editor-navigation-closure-补充环境与树:内置三环境保存重开、节点计数与折叠展开/attachments/66-depot-saved-return-list-799dae9d08248509e596a99158db667f031fa21a.png
new file mode 100644
index 0000000..8347f96
Binary files /dev/null and b/reports/model-editor-closure-20260906/test-results/model-editor-navigation-closure-补充环境与树:内置三环境保存重开、节点计数与折叠展开/attachments/66-depot-saved-return-list-799dae9d08248509e596a99158db667f031fa21a.png differ
diff --git a/reports/model-editor-closure-20260906/test-results/model-editor-navigation-closure-补充资源库:搜索清空、真实卡片切换与刷新保留/attachments/41-resource-search-restored-62898b900698f2ddcb8150f9fb249cc2009b0a76.png b/reports/model-editor-closure-20260906/test-results/model-editor-navigation-closure-补充资源库:搜索清空、真实卡片切换与刷新保留/attachments/41-resource-search-restored-62898b900698f2ddcb8150f9fb249cc2009b0a76.png
new file mode 100644
index 0000000..774e368
Binary files /dev/null and b/reports/model-editor-closure-20260906/test-results/model-editor-navigation-closure-补充资源库:搜索清空、真实卡片切换与刷新保留/attachments/41-resource-search-restored-62898b900698f2ddcb8150f9fb249cc2009b0a76.png differ
diff --git a/reports/model-editor-closure-20260906/test-results/model-editor-navigation-closure-补充资源库:搜索清空、真实卡片切换与刷新保留/attachments/42-resource-card-switch-reopened-d6612f2d7a4310fd29bf300a5474e8de9dbeb5c9.png b/reports/model-editor-closure-20260906/test-results/model-editor-navigation-closure-补充资源库:搜索清空、真实卡片切换与刷新保留/attachments/42-resource-card-switch-reopened-d6612f2d7a4310fd29bf300a5474e8de9dbeb5c9.png
new file mode 100644
index 0000000..d545fb5
Binary files /dev/null and b/reports/model-editor-closure-20260906/test-results/model-editor-navigation-closure-补充资源库:搜索清空、真实卡片切换与刷新保留/attachments/42-resource-card-switch-reopened-d6612f2d7a4310fd29bf300a5474e8de9dbeb5c9.png differ
diff --git a/reports/model-editor-closure-20260906/test-results/model-editor-recovery-clos-6e0ee-,重试成功;发布预检开关无写入且拒绝错误资源-JSON/attachments/63-controlled-glb-download-failed-73fceec6371a6105b03cd8ea12f77f39723dcf95.png b/reports/model-editor-closure-20260906/test-results/model-editor-recovery-clos-6e0ee-,重试成功;发布预检开关无写入且拒绝错误资源-JSON/attachments/63-controlled-glb-download-failed-73fceec6371a6105b03cd8ea12f77f39723dcf95.png
new file mode 100644
index 0000000..48c5040
Binary files /dev/null and b/reports/model-editor-closure-20260906/test-results/model-editor-recovery-clos-6e0ee-,重试成功;发布预检开关无写入且拒绝错误资源-JSON/attachments/63-controlled-glb-download-failed-73fceec6371a6105b03cd8ea12f77f39723dcf95.png differ
diff --git a/reports/model-editor-closure-20260906/test-results/model-editor-recovery-clos-6e0ee-,重试成功;发布预检开关无写入且拒绝错误资源-JSON/attachments/64-controlled-glb-retry-loaded-858424017a5aaf28a86e96f347246671ff3081fb.png b/reports/model-editor-closure-20260906/test-results/model-editor-recovery-clos-6e0ee-,重试成功;发布预检开关无写入且拒绝错误资源-JSON/attachments/64-controlled-glb-retry-loaded-858424017a5aaf28a86e96f347246671ff3081fb.png
new file mode 100644
index 0000000..3d3f334
Binary files /dev/null and b/reports/model-editor-closure-20260906/test-results/model-editor-recovery-clos-6e0ee-,重试成功;发布预检开关无写入且拒绝错误资源-JSON/attachments/64-controlled-glb-retry-loaded-858424017a5aaf28a86e96f347246671ff3081fb.png differ
diff --git a/reports/model-editor-closure-20260906/test-results/model-editor-recovery-clos-6e0ee-,重试成功;发布预检开关无写入且拒绝错误资源-JSON/attachments/65-publish-preflight-readonly-cf0ed010f8339fb135edd70ae0023ce93f71419f.png b/reports/model-editor-closure-20260906/test-results/model-editor-recovery-clos-6e0ee-,重试成功;发布预检开关无写入且拒绝错误资源-JSON/attachments/65-publish-preflight-readonly-cf0ed010f8339fb135edd70ae0023ce93f71419f.png
new file mode 100644
index 0000000..72d318f
Binary files /dev/null and b/reports/model-editor-closure-20260906/test-results/model-editor-recovery-clos-6e0ee-,重试成功;发布预检开关无写入且拒绝错误资源-JSON/attachments/65-publish-preflight-readonly-cf0ed010f8339fb135edd70ae0023ce93f71419f.png differ
diff --git a/reports/model-editor-closure-20260906/test-results/model-editor-recovery-closure-真实未保存稿同版本恢复并显式保存;旧版本恢复稿下载隔离保留/attachments/60-same-version-real-recovery-d2301e7ae7ff7cc63db5cac6677b53f042d262ee.png b/reports/model-editor-closure-20260906/test-results/model-editor-recovery-closure-真实未保存稿同版本恢复并显式保存;旧版本恢复稿下载隔离保留/attachments/60-same-version-real-recovery-d2301e7ae7ff7cc63db5cac6677b53f042d262ee.png
new file mode 100644
index 0000000..3433be0
Binary files /dev/null and b/reports/model-editor-closure-20260906/test-results/model-editor-recovery-closure-真实未保存稿同版本恢复并显式保存;旧版本恢复稿下载隔离保留/attachments/60-same-version-real-recovery-d2301e7ae7ff7cc63db5cac6677b53f042d262ee.png differ
diff --git a/reports/model-editor-closure-20260906/test-results/model-editor-recovery-closure-真实未保存稿同版本恢复并显式保存;旧版本恢复稿下载隔离保留/attachments/61-recovered-draft-explicitly-saved-6e2daca0e6690289171370e74711c92c81e533b5.png b/reports/model-editor-closure-20260906/test-results/model-editor-recovery-closure-真实未保存稿同版本恢复并显式保存;旧版本恢复稿下载隔离保留/attachments/61-recovered-draft-explicitly-saved-6e2daca0e6690289171370e74711c92c81e533b5.png
new file mode 100644
index 0000000..1645e8e
Binary files /dev/null and b/reports/model-editor-closure-20260906/test-results/model-editor-recovery-closure-真实未保存稿同版本恢复并显式保存;旧版本恢复稿下载隔离保留/attachments/61-recovered-draft-explicitly-saved-6e2daca0e6690289171370e74711c92c81e533b5.png differ
diff --git a/reports/model-editor-closure-20260906/test-results/model-editor-recovery-closure-真实未保存稿同版本恢复并显式保存;旧版本恢复稿下载隔离保留/attachments/62-stale-version-recovery-protection-3568bd8f9a1edd173ceb683a07ce142bb99e78ec.png b/reports/model-editor-closure-20260906/test-results/model-editor-recovery-closure-真实未保存稿同版本恢复并显式保存;旧版本恢复稿下载隔离保留/attachments/62-stale-version-recovery-protection-3568bd8f9a1edd173ceb683a07ce142bb99e78ec.png
new file mode 100644
index 0000000..e836f37
Binary files /dev/null and b/reports/model-editor-closure-20260906/test-results/model-editor-recovery-closure-真实未保存稿同版本恢复并显式保存;旧版本恢复稿下载隔离保留/attachments/62-stale-version-recovery-protection-3568bd8f9a1edd173ceb683a07ce142bb99e78ec.png differ
diff --git a/reports/model-editor-closure-20260906/validation.json b/reports/model-editor-closure-20260906/validation.json
new file mode 100644
index 0000000..86411fa
--- /dev/null
+++ b/reports/model-editor-closure-20260906/validation.json
@@ -0,0 +1,254 @@
+{
+ "time": "2026-09-06T08:28:23.891Z",
+ "status": "PASS",
+ "e2eComplete": true,
+ "checks": [
+ {
+ "name": "装备模型编辑回归",
+ "command": "node --test (8 model editor test files)",
+ "tests": 54,
+ "status": "PASS",
+ "failures": 0,
+ "exitCode": 0
+ },
+ {
+ "name": "权限桥新建、脏状态和只读键盘回归",
+ "command": "node --test tools/content-permissions.test.mjs",
+ "tests": 14,
+ "status": "PASS",
+ "failures": 0,
+ "exitCode": 0
+ },
+ {
+ "name": "报告证据归属与附件校验回归",
+ "command": "node --test tools/model-editor-report-evidence.test.mjs",
+ "tests": 7,
+ "status": "PASS",
+ "failures": 0,
+ "exitCode": 0
+ },
+ {
+ "name": "实际 Kingbase 驱动正常检查与隔离网络无响应验证",
+ "command": "KingbaseValidationProbe (isolated loopback TCP proxy, SELECT 1 only)",
+ "evidence": "data/kingbase-validation-probe-result.json",
+ "tests": 3,
+ "status": "PASS",
+ "failures": 0,
+ "exitCode": 0
+ },
+ {
+ "name": "Auth / Tran 完整构建与测试",
+ "command": "mvn -pl ut_auth,ut_tran -am package",
+ "tests": 476,
+ "moduleTests": [
+ 120,
+ 356
+ ],
+ "status": "PASS",
+ "failures": 0,
+ "exitCode": 0
+ },
+ {
+ "name": "Web 构建和 iframe 产物一致性",
+ "command": "pnpm run build",
+ "warning": "既有大包体积提示;构建及10项public/dist哈希校验通过",
+ "status": "PASS",
+ "failures": 0,
+ "exitCode": 0
+ },
+ {
+ "name": "6 E2E specs strict TypeScript",
+ "command": "tsc --noEmit --strict (closure specs, helper, config)",
+ "status": "PASS",
+ "failures": 0,
+ "exitCode": 0
+ }
+ ],
+ "coverage": [
+ {
+ "title": "resource catalog startup renders all model/environment cards and binds every action with a real DOM",
+ "featureIds": [
+ "resource.catalog"
+ ],
+ "scope": "真实 DOM 渲染、卡片绑定、空目录及 busy 保护;不包含 GLB 网络加载。",
+ "status": "PASS",
+ "kind": "unit",
+ "testFile": "unreal_tran/tests/model-main-closure.test.mjs"
+ },
+ {
+ "title": "curve edits real scalar key values, time and interpolation, persisting through document reload",
+ "featureIds": [
+ "timeline.curve",
+ "curve.time",
+ "curve.value",
+ "curve.easing"
+ ],
+ "scope": "真实曲线控件与 TimelineEditor,标量编辑与文档往返。",
+ "status": "PASS",
+ "kind": "unit",
+ "testFile": "unreal_tran/tests/model-main-closure.test.mjs"
+ },
+ {
+ "title": "curve vector editing preserves other components and selected track; invalid values do not mutate",
+ "featureIds": [
+ "curve.value"
+ ],
+ "scope": "向量其它分量保留,当前轨道、非法值保护;不包含显示轴切换或可视像素验收。",
+ "status": "PASS",
+ "kind": "unit",
+ "testFile": "unreal_tran/tests/model-main-closure.test.mjs"
+ },
+ {
+ "title": "multiple tracks on one property use last effective value; capture subtracts it only once",
+ "featureIds": [
+ "timeline.multitrack"
+ ],
+ "scope": "调用实际 apply/capture/update 基值方法;验证同属性最后有效轨道、静音/删除后中性姿态,偏移只扣一次。",
+ "status": "PASS",
+ "kind": "unit",
+ "testFile": "unreal_tran/tests/model-main-closure.test.mjs"
+ },
+ {
+ "title": "changing a binding resets the former target and keeps manual transform edits as base",
+ "featureIds": [
+ "timeline.multitrack",
+ "timeline.target"
+ ],
+ "scope": "轨道重绑后旧目标复位,手动变换成为新基值;不假称鼠标拖动已验。",
+ "status": "PASS",
+ "kind": "unit",
+ "testFile": "unreal_tran/tests/model-main-closure.test.mjs"
+ },
+ {
+ "title": "custom properties and extension labels survive actual capture, project JSON and object restore",
+ "featureIds": [
+ "property.code",
+ "property.status",
+ "component.tag"
+ ],
+ "scope": "真实对象 capture、normalization、JSON 与 restore 往返;清空和撤销到未存在字段状态。",
+ "status": "PASS",
+ "kind": "unit",
+ "testFile": "unreal_tran/tests/model-main-closure.test.mjs"
+ },
+ {
+ "title": "boolean, numeric and nested blueprint conditions select their true/false branches",
+ "featureIds": [
+ "blueprint.condition"
+ ],
+ "scope": "布尔、数字、truthy 与嵌套路径值解析。",
+ "status": "PASS",
+ "kind": "unit",
+ "testFile": "unreal_tran/tests/model-main-closure.test.mjs"
+ },
+ {
+ "title": "visible child under a hidden ancestor cannot be picked or highlighted; trigger aliases include ancestors",
+ "featureIds": [
+ "tree.visibility",
+ "blueprint.highlight"
+ ],
+ "scope": "共用可见性和祖先别名函数;鼠标事件另由 E2E 验证。",
+ "status": "PASS",
+ "kind": "unit",
+ "testFile": "unreal_tran/tests/model-main-closure.test.mjs"
+ },
+ {
+ "title": "inspector state tags reflect ancestor visibility and actual shadow flags, with a host editability hook",
+ "featureIds": [
+ "property.identity"
+ ],
+ "scope": "真实属性 DOM 显示隐藏/阴影状态,并提供宿主权限标签 hook;不伪称此单测完成会话鉴权。",
+ "status": "PASS",
+ "kind": "unit",
+ "testFile": "unreal_tran/tests/model-main-closure.test.mjs"
+ },
+ {
+ "title": "history retains 60 states, truncates redo after edits and ignores duplicate snapshots",
+ "featureIds": [
+ "project.undo",
+ "project.redo"
+ ],
+ "scope": "60 个快照上限、重复状态忽略及新编辑截断 redo 分支。",
+ "status": "PASS",
+ "kind": "unit",
+ "testFile": "unreal_tran/tests/model-main-closure.test.mjs"
+ },
+ {
+ "title": "history serializes async restores and leaves index unchanged when restoration fails",
+ "featureIds": [
+ "project.undo",
+ "project.redo"
+ ],
+ "scope": "异步互斥、下载失败不移动索引,解除锁后可继续操作。",
+ "status": "PASS",
+ "kind": "unit",
+ "testFile": "unreal_tran/tests/model-main-closure.test.mjs"
+ },
+ {
+ "title": "managed-GLB history downloads the snapshot stable reference before restoring, without rewriting it",
+ "featureIds": [
+ "project.undo",
+ "project.redo"
+ ],
+ "scope": "受控 GLB 快照先物化再恢复,不把临时 URL 写回快照。",
+ "status": "PASS",
+ "kind": "unit",
+ "testFile": "unreal_tran/tests/model-main-closure.test.mjs"
+ },
+ {
+ "title": "beforeunload recovery does not create a draft for key order alone, but preserves real unsaved changes",
+ "featureIds": [
+ "project.recovery"
+ ],
+ "scope": "同语义对象键顺序不产生假恢复稿;真正修改仍保留。不包含全部恢复/隔离/下载 UI 分支。",
+ "status": "PASS",
+ "kind": "unit",
+ "testFile": "unreal_tran/tests/model-main-closure.test.mjs"
+ },
+ {
+ "title": "highlight rejects missing/hidden targets and transient colors never enter a saved project",
+ "featureIds": [
+ "blueprint.highlight"
+ ],
+ "scope": "目标不存在/隐藏报错,执行时暂态发光颜色不进入保存文档,结束恢复原材质。",
+ "status": "PASS",
+ "kind": "unit",
+ "testFile": "unreal_tran/tests/model-main-closure.test.mjs"
+ },
+ {
+ "title": "API publish supplies current cover, blocks duplicate publication and reports preserved old cover once",
+ "featureIds": [
+ "publish.api",
+ "publish.cover",
+ "feedback.toast"
+ ],
+ "scope": "实际发布宿主方法传封面、重入阻断和上传失败只提示一次;模拟 API,不是实际服务响应。",
+ "status": "PASS",
+ "kind": "unit",
+ "testFile": "unreal_tran/tests/model-main-closure.test.mjs"
+ },
+ {
+ "title": "API conflict leaves publication failed and releases the cover guard",
+ "featureIds": [
+ "project.conflict",
+ "publish.api",
+ "publish.cover"
+ ],
+ "scope": "409 分支不误报已发布并释放 busy 标记;模拟 API。",
+ "status": "PASS",
+ "kind": "unit",
+ "testFile": "unreal_tran/tests/model-main-closure.test.mjs"
+ }
+ ],
+ "runtime": {
+ "authSha256": "95b8487ca6a1d970387214b027ee9c1d34ae0d1d5c988574c024805d8bb61d9b",
+ "tranSha256": "e9528d739d6d817ebf7c5b9a34302195156b1543663066f5484c86c594c6e729",
+ "builtEqualsRunning": true,
+ "fileService": "开发进程撤销旧17180转发覆盖,恢复已配置远端文件服务"
+ },
+ "previousWorkPushed": {
+ "api": "71df00dffa39a8f49a4f0654969a3014c2f78f67",
+ "web": "3f39e2c94df9cdadf3545ae14bf0df41f2c37bf1",
+ "e2e": "b3d4ce8ffd8144090c18c8a28f5ca824170042c8"
+ }
+}
\ No newline at end of file
diff --git a/reports/scene-editor-closure-20260906/README.md b/reports/scene-editor-closure-20260906/README.md
new file mode 100644
index 0000000..90cbc85
--- /dev/null
+++ b/reports/scene-editor-closure-20260906/README.md
@@ -0,0 +1,11 @@
+# 场景编辑器闭环审查
+
+本目录独立记录场景编辑器本轮审查,初始状态为 **PENDING(待验证)**。源码审查和既往场景测试不能替代本轮真实端到端证据。
+
+- [交互报告](index.html)
+- [覆盖约定](coverage-plan.md)
+- [功能清单](feature-catalog.json)
+- [源码基线与边界](data/inventory-scene.md)
+- [待确认的大范围调整](decisions.json)
+
+在 `ute2e` 运行 `node tools/build-scene-editor-closure-report.mjs` 生成报告。生成器只读证据文件,不登录、不调用业务 API、不运行测试。报告不会因存在截图或源码修复记录而自动判定通过。
diff --git a/reports/scene-editor-closure-20260906/artifact-checks.json b/reports/scene-editor-closure-20260906/artifact-checks.json
new file mode 100644
index 0000000..38140b3
--- /dev/null
+++ b/reports/scene-editor-closure-20260906/artifact-checks.json
@@ -0,0 +1,48 @@
+{
+ "time": "2026-09-06T10:38:58.813043+00:00",
+ "status": "PASS",
+ "phase": "final",
+ "finalArtifactScan": true,
+ "scope": "仅 scene-editor-closure-20260906 报告文件;不改应用,不调用 API,不清理数据。",
+ "finalInputs": {
+ "results.json": true,
+ "validation.json": true,
+ "cleanup.json": true,
+ "playwright/index.html": true
+ },
+ "finalInputSha256": {
+ "cleanup.json": "363182ead34695d93861ce6cfb35f835138ec4634efe508c0132f3350af75040",
+ "playwright/index.html": "ab0b6459019918285a0aa00c95c0c9860ddd9c6268ce82c06ff16e462b0656a1",
+ "results.json": "de3541793d020ebd1532f94d8e728b1ef5dbaff3ea192d55491fd8952ba3ce98",
+ "validation.json": "3cc6e98b1859adcf1c8c5df56006ae539c3fed907666af7936e35340d5ae3cd6"
+ },
+ "pendingReasons": [],
+ "selfChecks": {
+ "status": "PASS",
+ "tests": 16
+ },
+ "counts": {
+ "filesScanned": 208,
+ "textArtifactsScanned": 60,
+ "imagesValidated": 157,
+ "localLinksChecked": 524,
+ "archivesDecoded": 1,
+ "archiveEntriesScanned": 8
+ },
+ "findings": [],
+ "exemptions": {
+ "count": 1,
+ "items": [
+ {
+ "file": "playwright/index.html",
+ "rule": "unchanged-playwright-vendor-context",
+ "baseline": "ute2e/HEAD:reports/role-permissions-20260906/playwright/index.html"
+ }
+ ]
+ },
+ "limitations": [
+ "图片解码和文本元数据检查不代替人工视觉核验;未对图片像素进行 OCR。",
+ "只校验 HTML 本地链接文件存在,不访问外部链接;Playwright 路由片段不作文件路径。",
+ "只有当前最终输入完整且全部通过才可 PASS;已解码 ZIP 不适用静态第三方库基线豁免。"
+ ]
+}
\ No newline at end of file
diff --git a/reports/scene-editor-closure-20260906/cleanup.json b/reports/scene-editor-closure-20260906/cleanup.json
new file mode 100644
index 0000000..8c27779
--- /dev/null
+++ b/reports/scene-editor-closure-20260906/cleanup.json
@@ -0,0 +1,1331 @@
+{
+ "time": "2026-09-06T10:33:56.078Z",
+ "status": "PASS",
+ "originals": [
+ {
+ "id": "160",
+ "name": "实测场景-狐狸野外训练区-20260905",
+ "version": 10,
+ "unchanged": true,
+ "scope": "全部创作字段、版本、资产、依赖;引用统计与其触发的更新时间单独核对",
+ "before": {
+ "usageCount": 2,
+ "updateTime": "2026-09-06T00:28:06"
+ },
+ "after": {
+ "usageCount": 2,
+ "updateTime": "2026-09-06T18:24:42"
+ },
+ "fullRecordUnchanged": false,
+ "referenceCountRestored": true
+ },
+ {
+ "id": "159",
+ "name": "实测场景-牛奶卡车检验区-20260905",
+ "version": 7,
+ "unchanged": true,
+ "scope": "全部创作字段、版本、资产、依赖;引用统计与其触发的更新时间单独核对",
+ "before": {
+ "usageCount": 2,
+ "updateTime": "2026-09-05T23:52:35"
+ },
+ "after": {
+ "usageCount": 2,
+ "updateTime": "2026-09-06T18:25:00"
+ },
+ "fullRecordUnchanged": false,
+ "referenceCountRestored": true
+ },
+ {
+ "id": "157",
+ "name": "实测模型-狐狸动画-已验证-20260905",
+ "version": 15,
+ "unchanged": true,
+ "scope": "全部创作字段、版本、资产、依赖;引用统计与其触发的更新时间单独核对",
+ "before": {
+ "usageCount": 1,
+ "updateTime": "2026-09-05T22:52:25"
+ },
+ "after": {
+ "usageCount": 1,
+ "updateTime": "2026-09-06T18:35:18"
+ },
+ "fullRecordUnchanged": false,
+ "referenceCountRestored": true
+ },
+ {
+ "id": "156",
+ "name": "实测模型-牛奶卡车-已验证-20260905",
+ "version": 20,
+ "unchanged": true,
+ "scope": "全部创作字段、版本、资产、依赖;引用统计与其触发的更新时间单独核对",
+ "before": {
+ "usageCount": 1,
+ "updateTime": "2026-09-05T19:32:28"
+ },
+ "after": {
+ "usageCount": 1,
+ "updateTime": "2026-09-06T18:35:18"
+ },
+ "fullRecordUnchanged": false,
+ "referenceCountRestored": true
+ }
+ ],
+ "projects": [
+ {
+ "id": "406",
+ "name": "场景闭环-真正新建工程",
+ "withdrawn": false,
+ "removed": true
+ },
+ {
+ "id": "405",
+ "name": "场景闭环-fox-新建-1788690633695",
+ "withdrawn": false,
+ "removed": true
+ },
+ {
+ "id": "404",
+ "name": "场景闭环-truck-部件历史-1788690615624",
+ "withdrawn": false,
+ "removed": true
+ },
+ {
+ "id": "403",
+ "name": "场景闭环-fox-属性封面-1788690604193",
+ "withdrawn": false,
+ "removed": true
+ },
+ {
+ "id": "402",
+ "name": "场景闭环-fox-缓存关闭-1788690596810",
+ "withdrawn": false,
+ "removed": true
+ },
+ {
+ "id": "401",
+ "name": "场景闭环-truck-变换手柄-1788690585109",
+ "withdrawn": false,
+ "removed": true
+ },
+ {
+ "id": "400",
+ "name": "场景闭环-fox-视口主题-1788690566083",
+ "withdrawn": false,
+ "removed": true
+ },
+ {
+ "id": "399",
+ "name": "场景闭环-truck-published-model-assets-1788690547315",
+ "withdrawn": false,
+ "removed": true
+ },
+ {
+ "id": "398",
+ "name": "场景闭环-fox-venue-assets-1788690532832",
+ "withdrawn": false,
+ "removed": true
+ },
+ {
+ "id": "397",
+ "name": "场景闭环-fox-basic-assets-1788690523002",
+ "withdrawn": false,
+ "removed": true
+ },
+ {
+ "id": "396",
+ "name": "场景闭环-fox-template-confirm-1788690515145",
+ "withdrawn": false,
+ "removed": true
+ },
+ {
+ "id": "395",
+ "name": "场景闭环-fox-outdoor-1-1788690506435",
+ "withdrawn": false,
+ "removed": true
+ },
+ {
+ "id": "394",
+ "name": "场景闭环-fox-workshop-5-1788690497410",
+ "withdrawn": false,
+ "removed": true
+ },
+ {
+ "id": "393",
+ "name": "场景闭环-fox-workshop-4-1788690488727",
+ "withdrawn": false,
+ "removed": true
+ },
+ {
+ "id": "392",
+ "name": "场景闭环-fox-workshop-3-1788690480008",
+ "withdrawn": false,
+ "removed": true
+ },
+ {
+ "id": "391",
+ "name": "场景闭环-fox-workshop-2-1788690471227",
+ "withdrawn": false,
+ "removed": true
+ },
+ {
+ "id": "390",
+ "name": "场景闭环-fox-workshop-real-1788690454290",
+ "withdrawn": false,
+ "removed": true
+ },
+ {
+ "id": "389",
+ "name": "场景闭环-fox-workshop-1-1788690445705",
+ "withdrawn": false,
+ "removed": true
+ },
+ {
+ "id": "388",
+ "name": "场景闭环-fox-terrain-1-1788690435105",
+ "withdrawn": false,
+ "removed": true
+ },
+ {
+ "id": "387",
+ "name": "场景闭环-fox-depot-1-1788690425919",
+ "withdrawn": false,
+ "removed": true
+ },
+ {
+ "id": "386",
+ "name": "场景闭环-truck-发布-1788690410773",
+ "withdrawn": true,
+ "removed": true
+ },
+ {
+ "id": "385",
+ "name": "场景闭环-truck-恢复冲突-1788690394970",
+ "withdrawn": false,
+ "removed": true
+ },
+ {
+ "id": "384",
+ "name": "场景闭环-fox-失败恢复-1788690381118",
+ "withdrawn": false,
+ "removed": true
+ },
+ {
+ "id": "383",
+ "name": "场景闭环-truck-导入导出-1788690357912",
+ "withdrawn": false,
+ "removed": true
+ },
+ {
+ "id": "382",
+ "name": "场景闭环-fox-权限-update-1788690342946",
+ "withdrawn": false,
+ "removed": true
+ },
+ {
+ "id": "381",
+ "name": "场景闭环-fox-权限-update-1788690328470",
+ "withdrawn": false,
+ "removed": true
+ },
+ {
+ "id": "380",
+ "name": "场景闭环-fox-权限-publish-1788690315486",
+ "withdrawn": true,
+ "removed": true
+ },
+ {
+ "id": "379",
+ "name": "场景闭环-fox-权限-update-1788690305191",
+ "withdrawn": false,
+ "removed": true
+ },
+ {
+ "id": "378",
+ "name": "场景闭环-truck-训练引用-1788690290633",
+ "withdrawn": false,
+ "removed": true
+ },
+ {
+ "id": "377",
+ "name": "场景闭环-truck-下游训练-1788690284039",
+ "withdrawn": true,
+ "removed": true
+ },
+ {
+ "id": "376",
+ "name": "场景闭环-fox-训练引用-1788690274474",
+ "withdrawn": false,
+ "removed": true
+ },
+ {
+ "id": "375",
+ "name": "场景闭环-fox-下游训练-1788690268292",
+ "withdrawn": true,
+ "removed": true
+ },
+ {
+ "id": "374",
+ "name": "场景闭环-truck-并发保存-1788690255955",
+ "withdrawn": false,
+ "removed": true
+ },
+ {
+ "id": "373",
+ "name": "场景闭环-fox-加载互斥-1788690247508",
+ "withdrawn": false,
+ "removed": true
+ },
+ {
+ "id": "372",
+ "name": "场景闭环-真正新建工程",
+ "withdrawn": false,
+ "removed": true
+ },
+ {
+ "id": "371",
+ "name": "场景闭环-fox-新建-1788689963096",
+ "withdrawn": false,
+ "removed": true
+ },
+ {
+ "id": "370",
+ "name": "场景闭环-truck-部件历史-1788689939984",
+ "withdrawn": false,
+ "removed": true
+ },
+ {
+ "id": "369",
+ "name": "场景闭环-fox-属性封面-1788689927504",
+ "withdrawn": false,
+ "removed": true
+ },
+ {
+ "id": "368",
+ "name": "场景闭环-fox-缓存关闭-1788689919715",
+ "withdrawn": false,
+ "removed": true
+ },
+ {
+ "id": "367",
+ "name": "场景闭环-truck-变换手柄-1788689907362",
+ "withdrawn": false,
+ "removed": true
+ },
+ {
+ "id": "366",
+ "name": "场景闭环-fox-视口主题-1788689887971",
+ "withdrawn": false,
+ "removed": true
+ },
+ {
+ "id": "365",
+ "name": "场景闭环-truck-published-model-assets-1788689868844",
+ "withdrawn": false,
+ "removed": true
+ },
+ {
+ "id": "364",
+ "name": "场景闭环-fox-venue-assets-1788689851507",
+ "withdrawn": false,
+ "removed": true
+ },
+ {
+ "id": "363",
+ "name": "场景闭环-fox-basic-assets-1788689841257",
+ "withdrawn": false,
+ "removed": true
+ },
+ {
+ "id": "362",
+ "name": "场景闭环-fox-template-confirm-1788689832942",
+ "withdrawn": false,
+ "removed": true
+ },
+ {
+ "id": "361",
+ "name": "场景闭环-fox-outdoor-1-1788689823859",
+ "withdrawn": false,
+ "removed": true
+ },
+ {
+ "id": "360",
+ "name": "场景闭环-fox-workshop-5-1788689814070",
+ "withdrawn": false,
+ "removed": true
+ },
+ {
+ "id": "359",
+ "name": "场景闭环-fox-workshop-4-1788689805324",
+ "withdrawn": false,
+ "removed": true
+ },
+ {
+ "id": "358",
+ "name": "场景闭环-fox-workshop-3-1788689795722",
+ "withdrawn": false,
+ "removed": true
+ },
+ {
+ "id": "357",
+ "name": "场景闭环-fox-workshop-2-1788689786399",
+ "withdrawn": false,
+ "removed": true
+ },
+ {
+ "id": "356",
+ "name": "场景闭环-fox-workshop-real-1788689769744",
+ "withdrawn": false,
+ "removed": true
+ },
+ {
+ "id": "355",
+ "name": "场景闭环-fox-workshop-1-1788689759412",
+ "withdrawn": false,
+ "removed": true
+ },
+ {
+ "id": "354",
+ "name": "场景闭环-fox-terrain-1-1788689748108",
+ "withdrawn": false,
+ "removed": true
+ },
+ {
+ "id": "353",
+ "name": "场景闭环-fox-depot-1-1788689738761",
+ "withdrawn": false,
+ "removed": true
+ },
+ {
+ "id": "352",
+ "name": "场景闭环-truck-发布-1788689724178",
+ "withdrawn": true,
+ "removed": true
+ },
+ {
+ "id": "351",
+ "name": "场景闭环-truck-恢复冲突-1788689708022",
+ "withdrawn": false,
+ "removed": true
+ },
+ {
+ "id": "350",
+ "name": "场景闭环-fox-失败恢复-1788689696598",
+ "withdrawn": false,
+ "removed": true
+ },
+ {
+ "id": "349",
+ "name": "场景闭环-truck-导入导出-1788689673559",
+ "withdrawn": false,
+ "removed": true
+ },
+ {
+ "id": "348",
+ "name": "场景闭环-fox-权限-update-1788689659107",
+ "withdrawn": false,
+ "removed": true
+ },
+ {
+ "id": "347",
+ "name": "场景闭环-fox-权限-update-1788689643227",
+ "withdrawn": false,
+ "removed": true
+ },
+ {
+ "id": "346",
+ "name": "场景闭环-fox-权限-publish-1788689630155",
+ "withdrawn": true,
+ "removed": true
+ },
+ {
+ "id": "345",
+ "name": "场景闭环-fox-权限-update-1788689620039",
+ "withdrawn": false,
+ "removed": true
+ },
+ {
+ "id": "344",
+ "name": "场景闭环-truck-训练引用-1788689606358",
+ "withdrawn": false,
+ "removed": true
+ },
+ {
+ "id": "343",
+ "name": "场景闭环-truck-下游训练-1788689599841",
+ "withdrawn": true,
+ "removed": true
+ },
+ {
+ "id": "342",
+ "name": "场景闭环-fox-训练引用-1788689589414",
+ "withdrawn": false,
+ "removed": true
+ },
+ {
+ "id": "341",
+ "name": "场景闭环-fox-下游训练-1788689583199",
+ "withdrawn": true,
+ "removed": true
+ },
+ {
+ "id": "340",
+ "name": "场景闭环-truck-并发保存-1788689570735",
+ "withdrawn": false,
+ "removed": true
+ },
+ {
+ "id": "339",
+ "name": "场景闭环-fox-加载互斥-1788689562316",
+ "withdrawn": false,
+ "removed": true
+ },
+ {
+ "id": "338",
+ "name": "场景闭环-真正新建工程",
+ "withdrawn": false,
+ "removed": true
+ },
+ {
+ "id": "337",
+ "name": "场景闭环-fox-新建-1788689472391",
+ "withdrawn": false,
+ "removed": true
+ },
+ {
+ "id": "336",
+ "name": "场景闭环-truck-部件历史-1788689451802",
+ "withdrawn": false,
+ "removed": true
+ },
+ {
+ "id": "335",
+ "name": "场景闭环-fox-属性封面-1788689438588",
+ "withdrawn": false,
+ "removed": true
+ },
+ {
+ "id": "334",
+ "name": "场景闭环-fox-缓存关闭-1788689428982",
+ "withdrawn": false,
+ "removed": true
+ },
+ {
+ "id": "333",
+ "name": "场景闭环-truck-变换手柄-1788689416649",
+ "withdrawn": false,
+ "removed": true
+ },
+ {
+ "id": "332",
+ "name": "场景闭环-fox-视口主题-1788689397757",
+ "withdrawn": false,
+ "removed": true
+ },
+ {
+ "id": "331",
+ "name": "场景闭环-truck-published-model-assets-1788689378255",
+ "withdrawn": false,
+ "removed": true
+ },
+ {
+ "id": "330",
+ "name": "场景闭环-fox-venue-assets-1788689363711",
+ "withdrawn": false,
+ "removed": true
+ },
+ {
+ "id": "329",
+ "name": "场景闭环-fox-basic-assets-1788689353936",
+ "withdrawn": false,
+ "removed": true
+ },
+ {
+ "id": "328",
+ "name": "场景闭环-fox-template-confirm-1788689346518",
+ "withdrawn": false,
+ "removed": true
+ },
+ {
+ "id": "327",
+ "name": "场景闭环-fox-outdoor-1-1788689337645",
+ "withdrawn": false,
+ "removed": true
+ },
+ {
+ "id": "326",
+ "name": "场景闭环-fox-workshop-5-1788689328719",
+ "withdrawn": false,
+ "removed": true
+ },
+ {
+ "id": "325",
+ "name": "场景闭环-fox-workshop-4-1788689319174",
+ "withdrawn": false,
+ "removed": true
+ },
+ {
+ "id": "324",
+ "name": "场景闭环-fox-workshop-3-1788689309257",
+ "withdrawn": false,
+ "removed": true
+ },
+ {
+ "id": "323",
+ "name": "场景闭环-fox-workshop-2-1788689300207",
+ "withdrawn": false,
+ "removed": true
+ },
+ {
+ "id": "322",
+ "name": "场景闭环-fox-workshop-real-1788689279687",
+ "withdrawn": false,
+ "removed": true
+ },
+ {
+ "id": "321",
+ "name": "场景闭环-fox-workshop-1-1788689269832",
+ "withdrawn": false,
+ "removed": true
+ },
+ {
+ "id": "320",
+ "name": "场景闭环-fox-terrain-1-1788689259583",
+ "withdrawn": false,
+ "removed": true
+ },
+ {
+ "id": "319",
+ "name": "场景闭环-fox-depot-1-1788689250167",
+ "withdrawn": false,
+ "removed": true
+ },
+ {
+ "id": "318",
+ "name": "场景闭环-truck-发布-1788689235579",
+ "withdrawn": true,
+ "removed": true
+ },
+ {
+ "id": "317",
+ "name": "场景闭环-truck-恢复冲突-1788689218382",
+ "withdrawn": false,
+ "removed": true
+ },
+ {
+ "id": "316",
+ "name": "场景闭环-fox-失败恢复-1788689204839",
+ "withdrawn": false,
+ "removed": true
+ },
+ {
+ "id": "315",
+ "name": "场景闭环-truck-导入导出-1788689180787",
+ "withdrawn": false,
+ "removed": true
+ },
+ {
+ "id": "314",
+ "name": "场景闭环-fox-权限-update-1788689165361",
+ "withdrawn": false,
+ "removed": true
+ },
+ {
+ "id": "313",
+ "name": "场景闭环-fox-权限-update-1788689149194",
+ "withdrawn": false,
+ "removed": true
+ },
+ {
+ "id": "312",
+ "name": "场景闭环-fox-权限-publish-1788689135779",
+ "withdrawn": true,
+ "removed": true
+ },
+ {
+ "id": "311",
+ "name": "场景闭环-fox-权限-update-1788689124828",
+ "withdrawn": false,
+ "removed": true
+ },
+ {
+ "id": "310",
+ "name": "场景闭环-truck-训练引用-1788689111536",
+ "withdrawn": false,
+ "removed": true
+ },
+ {
+ "id": "309",
+ "name": "场景闭环-truck-下游训练-1788689104848",
+ "withdrawn": true,
+ "removed": true
+ },
+ {
+ "id": "308",
+ "name": "场景闭环-fox-训练引用-1788689092685",
+ "withdrawn": false,
+ "removed": true
+ },
+ {
+ "id": "307",
+ "name": "场景闭环-fox-下游训练-1788689086274",
+ "withdrawn": true,
+ "removed": true
+ },
+ {
+ "id": "306",
+ "name": "场景闭环-truck-并发保存-1788689071822",
+ "withdrawn": false,
+ "removed": true
+ },
+ {
+ "id": "305",
+ "name": "场景闭环-fox-加载互斥-1788689063020",
+ "withdrawn": false,
+ "removed": true
+ },
+ {
+ "id": "304",
+ "name": "场景闭环-真正新建工程",
+ "withdrawn": false,
+ "removed": true
+ },
+ {
+ "id": "303",
+ "name": "场景闭环-fox-新建-1788689003592",
+ "withdrawn": false,
+ "removed": true
+ },
+ {
+ "id": "302",
+ "name": "场景闭环-fox-新建-1788688912075",
+ "withdrawn": false,
+ "removed": true
+ },
+ {
+ "id": "301",
+ "name": "场景闭环-truck-部件历史-1788688403240",
+ "withdrawn": false,
+ "removed": true
+ },
+ {
+ "id": "300",
+ "name": "场景闭环-truck-部件历史-1788688047929",
+ "withdrawn": false,
+ "removed": true
+ },
+ {
+ "id": "299",
+ "name": "场景闭环-truck-发布-1788688034614",
+ "withdrawn": true,
+ "removed": true
+ },
+ {
+ "id": "298",
+ "name": "场景闭环-truck-导入导出-1788688009926",
+ "withdrawn": false,
+ "removed": true
+ },
+ {
+ "id": "297",
+ "name": "场景闭环-fox-缓存关闭-1788688001266",
+ "withdrawn": false,
+ "removed": true
+ },
+ {
+ "id": "296",
+ "name": "场景闭环-fox-加载互斥-1788687999188",
+ "withdrawn": false,
+ "removed": true
+ },
+ {
+ "id": "295",
+ "name": "场景闭环-fox-视口主题-1788687979832",
+ "withdrawn": false,
+ "removed": true
+ },
+ {
+ "id": "294",
+ "name": "场景闭环-fox-缓存关闭-1788687896452",
+ "withdrawn": false,
+ "removed": true
+ },
+ {
+ "id": "293",
+ "name": "场景闭环-truck-变换手柄-1788687883118",
+ "withdrawn": false,
+ "removed": true
+ },
+ {
+ "id": "292",
+ "name": "场景闭环-fox-视口主题-1788687863268",
+ "withdrawn": false,
+ "removed": true
+ },
+ {
+ "id": "291",
+ "name": "场景闭环-truck-变换手柄-1788687694671",
+ "withdrawn": false,
+ "removed": true
+ },
+ {
+ "id": "290",
+ "name": "场景闭环-truck-导入导出-1788687671067",
+ "withdrawn": false,
+ "removed": true
+ },
+ {
+ "id": "289",
+ "name": "场景闭环-truck-训练引用-1788687660492",
+ "withdrawn": false,
+ "removed": true
+ },
+ {
+ "id": "288",
+ "name": "场景闭环-truck-下游训练-1788687652038",
+ "withdrawn": true,
+ "removed": true
+ },
+ {
+ "id": "287",
+ "name": "场景闭环-fox-训练引用-1788687639306",
+ "withdrawn": false,
+ "removed": true
+ },
+ {
+ "id": "286",
+ "name": "场景闭环-fox-权限-update-1788687629666",
+ "withdrawn": false,
+ "removed": true
+ },
+ {
+ "id": "285",
+ "name": "场景闭环-fox-下游训练-1788687629513",
+ "withdrawn": true,
+ "removed": true
+ },
+ {
+ "id": "284",
+ "name": "场景闭环-truck-变换手柄-1788687611730",
+ "withdrawn": false,
+ "removed": true
+ },
+ {
+ "id": "283",
+ "name": "场景闭环-真正新建工程",
+ "withdrawn": false,
+ "removed": true
+ },
+ {
+ "id": "282",
+ "name": "场景闭环-fox-新建-1788687557234",
+ "withdrawn": false,
+ "removed": true
+ },
+ {
+ "id": "281",
+ "name": "场景闭环-truck-部件历史-1788687523512",
+ "withdrawn": false,
+ "removed": true
+ },
+ {
+ "id": "280",
+ "name": "场景闭环-fox-缓存关闭-1788687510182",
+ "withdrawn": false,
+ "removed": true
+ },
+ {
+ "id": "279",
+ "name": "场景闭环-fox-属性封面-1788687505338",
+ "withdrawn": false,
+ "removed": true
+ },
+ {
+ "id": "278",
+ "name": "场景闭环-truck-变换手柄-1788687490547",
+ "withdrawn": false,
+ "removed": true
+ },
+ {
+ "id": "277",
+ "name": "场景闭环-truck-发布-1788687488691",
+ "withdrawn": true,
+ "removed": true
+ },
+ {
+ "id": "276",
+ "name": "场景闭环-fox-权限-update-1788687483840",
+ "withdrawn": false,
+ "removed": true
+ },
+ {
+ "id": "275",
+ "name": "场景闭环-fox-视口主题-1788687465156",
+ "withdrawn": false,
+ "removed": true
+ },
+ {
+ "id": "274",
+ "name": "场景闭环-truck-恢复冲突-1788687463987",
+ "withdrawn": false,
+ "removed": true
+ },
+ {
+ "id": "273",
+ "name": "场景闭环-fox-失败恢复-1788687446300",
+ "withdrawn": false,
+ "removed": true
+ },
+ {
+ "id": "272",
+ "name": "场景闭环-fox-权限-update-1788687390660",
+ "withdrawn": false,
+ "removed": true
+ },
+ {
+ "id": "271",
+ "name": "场景闭环-truck-导入导出-1788687373696",
+ "withdrawn": false,
+ "removed": true
+ },
+ {
+ "id": "270",
+ "name": "场景闭环-fox-权限-update-1788687371702",
+ "withdrawn": false,
+ "removed": true
+ },
+ {
+ "id": "269",
+ "name": "场景闭环-truck-并发保存-1788687357870",
+ "withdrawn": false,
+ "removed": true
+ },
+ {
+ "id": "268",
+ "name": "场景闭环-fox-加载互斥-1788687348860",
+ "withdrawn": false,
+ "removed": true
+ },
+ {
+ "id": "267",
+ "name": "场景闭环-truck-published-model-assets-1788687245313",
+ "withdrawn": false,
+ "removed": true
+ },
+ {
+ "id": "266",
+ "name": "场景闭环-truck-published-model-assets-1788687127891",
+ "withdrawn": false,
+ "removed": true
+ },
+ {
+ "id": "265",
+ "name": "场景闭环-fox-venue-assets-1788687111574",
+ "withdrawn": false,
+ "removed": true
+ },
+ {
+ "id": "264",
+ "name": "场景闭环-fox-basic-assets-1788687099010",
+ "withdrawn": false,
+ "removed": true
+ },
+ {
+ "id": "263",
+ "name": "场景闭环-fox-权限-publish-1788687065773",
+ "withdrawn": true,
+ "removed": true
+ },
+ {
+ "id": "262",
+ "name": "场景闭环-fox-权限-update-1788687053632",
+ "withdrawn": false,
+ "removed": true
+ },
+ {
+ "id": "261",
+ "name": "场景闭环-truck-发布-1788687017309",
+ "withdrawn": false,
+ "removed": true
+ },
+ {
+ "id": "260",
+ "name": "场景闭环-truck-恢复冲突-1788686976171",
+ "withdrawn": false,
+ "removed": true
+ },
+ {
+ "id": "259",
+ "name": "场景闭环-fox-失败恢复-1788686960079",
+ "withdrawn": false,
+ "removed": true
+ },
+ {
+ "id": "258",
+ "name": "场景闭环-fox-basic-assets-1788686942828",
+ "withdrawn": false,
+ "removed": true
+ },
+ {
+ "id": "257",
+ "name": "场景闭环-fox-template-confirm-1788686932562",
+ "withdrawn": false,
+ "removed": true
+ },
+ {
+ "id": "256",
+ "name": "场景闭环-truck-导入导出-1788686930172",
+ "withdrawn": false,
+ "removed": true
+ },
+ {
+ "id": "255",
+ "name": "场景闭环-fox-权限-publish-1788686926664",
+ "withdrawn": true,
+ "removed": true
+ },
+ {
+ "id": "254",
+ "name": "场景闭环-fox-outdoor-1-1788686919613",
+ "withdrawn": false,
+ "removed": true
+ },
+ {
+ "id": "253",
+ "name": "场景闭环-fox-workshop-5-1788686910855",
+ "withdrawn": false,
+ "removed": true
+ },
+ {
+ "id": "252",
+ "name": "场景闭环-fox-workshop-4-1788686900385",
+ "withdrawn": false,
+ "removed": true
+ },
+ {
+ "id": "251",
+ "name": "场景闭环-fox-workshop-3-1788686891160",
+ "withdrawn": false,
+ "removed": true
+ },
+ {
+ "id": "250",
+ "name": "场景闭环-fox-workshop-2-1788686882523",
+ "withdrawn": false,
+ "removed": true
+ },
+ {
+ "id": "249",
+ "name": "场景闭环-fox-workshop-real-1788686865124",
+ "withdrawn": false,
+ "removed": true
+ },
+ {
+ "id": "248",
+ "name": "场景闭环-fox-workshop-1-1788686855958",
+ "withdrawn": false,
+ "removed": true
+ },
+ {
+ "id": "247",
+ "name": "场景闭环-fox-terrain-1-1788686845776",
+ "withdrawn": false,
+ "removed": true
+ },
+ {
+ "id": "246",
+ "name": "场景闭环-fox-depot-1-1788686833933",
+ "withdrawn": false,
+ "removed": true
+ },
+ {
+ "id": "245",
+ "name": "场景闭环-fox-权限-update-1788686823269",
+ "withdrawn": false,
+ "removed": true
+ },
+ {
+ "id": "244",
+ "name": "场景闭环-真正新建工程",
+ "withdrawn": false,
+ "removed": true
+ },
+ {
+ "id": "243",
+ "name": "场景闭环-fox-新建-1788686775392",
+ "withdrawn": false,
+ "removed": true
+ },
+ {
+ "id": "242",
+ "name": "场景闭环-fox-workshop-1-1788686765127",
+ "withdrawn": false,
+ "removed": true
+ },
+ {
+ "id": "241",
+ "name": "场景闭环-truck-部件历史-1788686669343",
+ "withdrawn": false,
+ "removed": true
+ },
+ {
+ "id": "240",
+ "name": "场景闭环-fox-属性封面-1788686649089",
+ "withdrawn": false,
+ "removed": true
+ }
+ ],
+ "users": [
+ {
+ "id": "101",
+ "removed": true
+ },
+ {
+ "id": "102",
+ "removed": true
+ },
+ {
+ "id": "103",
+ "removed": true
+ },
+ {
+ "id": "104",
+ "removed": true
+ },
+ {
+ "id": "105",
+ "removed": true
+ },
+ {
+ "id": "106",
+ "removed": true
+ },
+ {
+ "id": "107",
+ "removed": true
+ },
+ {
+ "id": "108",
+ "removed": true
+ },
+ {
+ "id": "109",
+ "removed": true
+ },
+ {
+ "id": "110",
+ "removed": true
+ },
+ {
+ "id": "111",
+ "removed": true
+ },
+ {
+ "id": "112",
+ "removed": true
+ },
+ {
+ "id": "113",
+ "removed": true
+ },
+ {
+ "id": "114",
+ "removed": true
+ },
+ {
+ "id": "115",
+ "removed": true
+ },
+ {
+ "id": "116",
+ "removed": true
+ },
+ {
+ "id": "117",
+ "removed": true
+ },
+ {
+ "id": "118",
+ "removed": true
+ },
+ {
+ "id": "119",
+ "removed": true
+ },
+ {
+ "id": "120",
+ "removed": true
+ },
+ {
+ "id": "121",
+ "removed": true
+ },
+ {
+ "id": "122",
+ "removed": true
+ },
+ {
+ "id": "123",
+ "removed": true
+ },
+ {
+ "id": "124",
+ "removed": true
+ },
+ {
+ "id": "125",
+ "removed": true
+ },
+ {
+ "id": "126",
+ "removed": true
+ },
+ {
+ "id": "127",
+ "removed": true
+ },
+ {
+ "id": "128",
+ "removed": true
+ }
+ ],
+ "roles": [
+ {
+ "id": "22",
+ "removed": true
+ },
+ {
+ "id": "23",
+ "removed": true
+ },
+ {
+ "id": "24",
+ "removed": true
+ },
+ {
+ "id": "25",
+ "removed": true
+ },
+ {
+ "id": "26",
+ "removed": true
+ },
+ {
+ "id": "27",
+ "removed": true
+ },
+ {
+ "id": "28",
+ "removed": true
+ },
+ {
+ "id": "29",
+ "removed": true
+ },
+ {
+ "id": "30",
+ "removed": true
+ },
+ {
+ "id": "31",
+ "removed": true
+ },
+ {
+ "id": "32",
+ "removed": true
+ },
+ {
+ "id": "33",
+ "removed": true
+ },
+ {
+ "id": "34",
+ "removed": true
+ },
+ {
+ "id": "35",
+ "removed": true
+ },
+ {
+ "id": "36",
+ "removed": true
+ },
+ {
+ "id": "37",
+ "removed": true
+ },
+ {
+ "id": "38",
+ "removed": true
+ },
+ {
+ "id": "39",
+ "removed": true
+ },
+ {
+ "id": "40",
+ "removed": true
+ },
+ {
+ "id": "41",
+ "removed": true
+ },
+ {
+ "id": "42",
+ "removed": true
+ },
+ {
+ "id": "43",
+ "removed": true
+ },
+ {
+ "id": "44",
+ "removed": true
+ },
+ {
+ "id": "45",
+ "removed": true
+ },
+ {
+ "id": "46",
+ "removed": true
+ },
+ {
+ "id": "47",
+ "removed": true
+ },
+ {
+ "id": "48",
+ "removed": true
+ },
+ {
+ "id": "49",
+ "removed": true
+ },
+ {
+ "id": "50",
+ "removed": true
+ },
+ {
+ "id": "51",
+ "removed": true
+ },
+ {
+ "id": "52",
+ "removed": true
+ },
+ {
+ "id": "53",
+ "removed": true
+ },
+ {
+ "id": "54",
+ "removed": true
+ },
+ {
+ "id": "55",
+ "removed": true
+ }
+ ],
+ "note": "测试副本增删会刷新原件引用统计及更新时间;创作内容、版本、资产和依赖未改写,引用数量已恢复,未回填或伪造原更新时间。"
+}
\ No newline at end of file
diff --git a/reports/scene-editor-closure-20260906/coverage-plan.md b/reports/scene-editor-closure-20260906/coverage-plan.md
new file mode 100644
index 0000000..cb0c27e
--- /dev/null
+++ b/reports/scene-editor-closure-20260906/coverage-plan.md
@@ -0,0 +1,59 @@
+# 资料 → 产品原型 → 设计 → 开发 → 测试
+
+## 资料
+
+复用前轮模型编辑器审查中已完整视觉阅读的两份 PDF(共 11 页)所提炼的通用交互需求:部件级选择与操作、动作流程、交互反馈、结果追踪。本轮没有重新声称阅读新资料,也不创建具体装备维修流程。
+
+既往 `scene-live-20260905` 的狐狸可见性更正说明保留为经验:保存成功、引用正确、接口通过,不能替代模型实际可见的截图和刷新验证。
+
+## 产品原型
+
+当前场景路由实际挂载 `LegacyScenePageView.vue`,内部运行 `guide-legacy/demo-src/scene-editor.js`。保留的 `SceneEditorView.vue → SceneEditorWorkspace.vue` 没有挂到默认路由;其中额外渲染、组件、动画和布局控件不计入当前可达功能的通过分母。
+
+覆盖清单按 9 组固定 119 个功能 ID:工程入口、文件与导入、资源库、9 个模板、视口与变换、属性与底部面板、历史与预览、封面与发布、权限恢复与生命周期。每行列出源码定位、当前边界、可观察验收要求。
+
+## 设计
+
+服务端项目、当前版本、已发布固定版本和受控资产是业务真值。本地恢复稿用于失败恢复,不代表自动保存成功。临时视图(例如网格和当前吸附设置)不要求伪造为项目持久化字段。
+
+权限按 `content.scene.create/update/delete/publish` 拆分。新建要求 create;编辑、导入、封面与保存要求 update;仅 publish 的用户可以发布已保存版本,不能借发布执行未授权的保存。只读用户仍可浏览和使用非写入视图。
+
+普通保存、JSON 导入、模板替换、上传、发布和关闭守卫需要保持互斥与失败回滚;409 不覆盖新版本。返回场景列表等同切标签并保留缓存,不弹关闭确认;真正关闭场景标签才询问未存修改。模型放置必须固定来源项目、版本和资产编码,不得将错误编码回退成另一个模型。
+
+## 开发
+
+本轮以现有入口的小范围闭环修复为主。`data/baseline-findings.json` 是检查时的风险清单;它不表示最终仍存在缺陷,也不表示修复已经通过。修复记录由 `fixes.json.items` 提供,每项填写 id、summary、featureIds。
+
+较大变化仅记录在 `decisions.json`,不并入本轮已实现/通过数。日志按当前运行的操作记录验收,不称为服务端审计日志;JSON 是工程文档,不称为自包含三维离线包。
+
+## 测试
+
+测试使用独立场景副本,保留源狐狸、牛奶卡车模型及原场景。截图应说明测试姿态、故障注入和遮挡限制,不用绿色成功提示代替可见性验证。
+
+浏览器证据文件建议如下,`testTitle` 必须与 Playwright 的真实标题完全一致,时间必须落在最终对应运行区间内:
+
+```json
+{
+ "time": "测试完成时间(ISO)",
+ "data": {
+ "kind": "e2e",
+ "status": "PASS",
+ "testTitle": "真实测试标题",
+ "featureIds": ["project.load", "project.save"],
+ "scope": "本用例实际断言的行为和限制",
+ "screenshots": ["01-example.png"]
+ }
+}
+```
+
+接口/单元证据必须明确 `kind`,并与 `validation.json.runId` 相同,或由 `validation.json.coverage` 提供。单元通过不得写成真实鼠标操作通过。失败的最终结果会醒目显示失败,旧截图和定向重跑不能覆盖最终全套失败事实。
+
+只有最终结果、构建/单元验证、清理、安全扫描、功能证据和截图关联全部满足约定,报告才能显示 PASS。缺证据、待确认或未运行都保持待验证。
+
+## 当前源码中的断言计划
+
+运行 `node tools/build-scene-editor-coverage-plan.mjs` 可只读解析当前场景 spec 和明确的 unit 计划,输出 [119 项逐行映射](data/coverage-test-plan.json)。这份文件全部为 PENDING:它说明哪段测试打算检查哪项功能,不表示这些测试属于最终运行,更不替代 `results.json`。
+
+浏览器范围包括 9 模板、资源库、属性和历史、JSON/GLB 与错误恢复、发布封面、真实相机/变换手柄、权限和多会话,以及独立训练副本的静态还原。单元范围明确保留异步失败回滚、隐藏祖先射线、队列并发、60 快照边界、资源释放等难以通过单张截图判定的合同。
+
+`tools/scene-editor-evidence-bindings.mjs` 只补证据元数据。实际测试到达证据记录处后写入真实 `test.info().title` 和时间;最终报告仍交叉核对 Playwright 最终状态和该执行区间,不使用映射文件推导 PASS。
diff --git a/reports/scene-editor-closure-20260906/coverage.json b/reports/scene-editor-closure-20260906/coverage.json
new file mode 100644
index 0000000..9942349
--- /dev/null
+++ b/reports/scene-editor-closure-20260906/coverage.json
@@ -0,0 +1,6136 @@
+{
+ "generatedAt": "2026-09-06T10:36:39.541Z",
+ "overall": "PASS",
+ "finalRun": true,
+ "finalArtifactsVerified": true,
+ "featureCount": {
+ "total": 119,
+ "pass": 119,
+ "fail": 0,
+ "pending": 0
+ },
+ "featureKinds": {
+ "browser": 112,
+ "focused": 7
+ },
+ "e2eCount": {
+ "total": 33,
+ "pass": 33,
+ "fail": 0,
+ "pending": 0
+ },
+ "validationTotals": [
+ {
+ "kind": "构建与类型检查",
+ "checks": 1,
+ "passedChecks": 1,
+ "tests": 0,
+ "passedTests": 0
+ },
+ {
+ "kind": "后端构建与测试",
+ "checks": 1,
+ "passedChecks": 1,
+ "tests": 373,
+ "passedTests": 373
+ },
+ {
+ "kind": "Node 合同与单元",
+ "checks": 2,
+ "passedChecks": 2,
+ "tests": 138,
+ "passedTests": 138
+ }
+ ],
+ "cleanupCount": {
+ "projects": 167,
+ "projectsRemoved": 167,
+ "originals": 4,
+ "originalsUnchanged": 4,
+ "users": 28,
+ "usersRemoved": 28,
+ "roles": 34,
+ "rolesRemoved": 34
+ },
+ "unknownIds": [],
+ "missing": [],
+ "malformed": [],
+ "features": [
+ {
+ "id": "project.load",
+ "group": "工程入口与顶部命令",
+ "name": "按真实项目 ID 打开已存场景",
+ "source": {
+ "file": "../../../unreal_tran_web/src/views/content/LegacyScenePageView.vue",
+ "symbol": "async function loadProject",
+ "line": 547
+ },
+ "implementation": "本轮修复已完成验证",
+ "boundary": "服务端 content 与版本是真值;不得以模板种子覆盖有效旧工程。",
+ "acceptance": "独立新会话打开,真实模型可见、固定版本及对象字段一致。",
+ "fix": "新实例部件 sceneId 包含所属根对象身份,不再直接复用来源模型部件 ID;源 interaction/editor 标识保留。旧单实例 ID 保留,旧文档重复 ID 仅按文档顺序迁移后续冲突实例并同步 override。第二完整轮进一步发现 GLB 并发完成顺序会改变根对象顺序;现等待 allSettled 成功后按原文档顺序排列恢复根对象,拆分模板返回数组内部顺序和既有对象均保留,同源两实例不再随网速互换列表位置。新增延迟 30/1/10ms 乱序加载合同,验证序列化仍按文档顺序;最终浏览器验证独立记录。",
+ "fixIds": [
+ "SCENE-F18"
+ ],
+ "verification": "PASS",
+ "evidence": [
+ {
+ "status": "PASS",
+ "testTitle": "狐狸:真实细分权限、全部属性、清空绑定、蒙皮保护、相机与封面保存重开",
+ "recordedAt": "2026-09-06T10:30:14.385Z",
+ "kind": "browser",
+ "ids": [
+ "project.load",
+ "project.title",
+ "project.save",
+ "selection.tree",
+ "property.name",
+ "property.visible",
+ "property.operable",
+ "property.position",
+ "property.rotation",
+ "property.scale",
+ "property.device-id",
+ "property.model-id",
+ "property.semantic",
+ "part.source",
+ "panel.binding",
+ "cover.capture",
+ "cover.atomic"
+ ],
+ "file": "evidence/fox-properties-cover.json",
+ "title": "狐狸:真实细分权限、全部属性、清空绑定、蒙皮保护、相机与封面保存重开",
+ "details": "真实字段输入与清空、六个语义选项、蒙皮保护、源模型可见,正文/资产/封面保存和刷新;不把原件旧模板组合当新模板结果。",
+ "screenshots": []
+ },
+ {
+ "status": "PASS",
+ "testTitle": "successful parallel restores retain document order after delayed GLBs and split-object batches finish out of order",
+ "kind": "unit",
+ "runId": "scene-editor-2026-09-06T10:24:04.121Z",
+ "ids": [
+ "project.load",
+ "selection.tree"
+ ],
+ "file": "validation.json",
+ "title": "successful parallel restores retain document order after delayed GLBs and split-object batches finish out of order",
+ "details": "实际执行真实方法/纯函数合同,未冒称浏览器操作",
+ "screenshots": []
+ }
+ ]
+ },
+ {
+ "id": "project.retry",
+ "group": "工程入口与顶部命令",
+ "name": "项目或挂载加载失败后重新加载",
+ "source": {
+ "file": "../../../unreal_tran_web/src/views/content/LegacyScenePageView.vue",
+ "symbol": "mountError",
+ "line": 76
+ },
+ "implementation": "本轮修复已完成验证",
+ "boundary": "错误详情自动消退,保留中性失败状态与重试按钮。",
+ "acceptance": "注入加载失败;无可写半场景;恢复网络后按钮重试成功。",
+ "fix": "前端目录和资源解析同时匹配指定项目、版本、code、id、URI、READY MODEL_FILE;错误引用不回退首文件或本地缓存。仅含 sourceAssetId/assetId 的本项目资源也必须将 ID 纳入缓存/去重键,防止已缓存有效引用掩盖另一个错误ID;失败请求缓存可重试。复制场景保留旧伪 URI 但只读取当前副本资产。",
+ "fixIds": [
+ "SCENE-F12"
+ ],
+ "verification": "PASS",
+ "evidence": [
+ {
+ "status": "PASS",
+ "testTitle": "加载失败重试与加载互斥:不展示可写半场景、恢复网络后模型完整加载",
+ "recordedAt": "2026-09-06T10:24:14.518Z",
+ "kind": "browser",
+ "ids": [
+ "project.retry",
+ "glb.pending",
+ "leave.busy"
+ ],
+ "file": "evidence/loading-retry.json",
+ "title": "加载失败重试与加载互斥:不展示可写半场景、恢复网络后模型完整加载",
+ "details": "GET503只留中性失败和重试;延迟受控下载时禁用写入/导出/预览并阻止关闭,释放后真实狐狸加载。",
+ "screenshots": []
+ },
+ {
+ "status": "PASS",
+ "testTitle": "a failed fixed-version lookup is evicted and the same reference can be retried",
+ "kind": "unit",
+ "runId": "scene-editor-2026-09-06T10:24:04.121Z",
+ "ids": [
+ "project.retry"
+ ],
+ "file": "validation.json",
+ "title": "a failed fixed-version lookup is evicted and the same reference can be retried",
+ "details": "实际执行真实方法/纯函数合同,未冒称浏览器操作",
+ "screenshots": []
+ }
+ ]
+ },
+ {
+ "id": "project.title",
+ "group": "工程入口与顶部命令",
+ "name": "当前场景名称与项目说明按钮",
+ "source": {
+ "file": "../../../unreal_tran_web/src/features/guide-legacy/demo-src/scene-editor.js",
+ "symbol": "project: () => this.toast",
+ "line": 599
+ },
+ "implementation": "已有实现已完成验证",
+ "boundary": "现有标题显示服务端名称;说明按钮不是重命名输入框。",
+ "acceptance": "长名称完整 title 正确;点击说明反映内容 API 管理。",
+ "fix": "尚未关联本轮修复记录",
+ "fixIds": [],
+ "verification": "PASS",
+ "evidence": [
+ {
+ "status": "PASS",
+ "testTitle": "狐狸:真实细分权限、全部属性、清空绑定、蒙皮保护、相机与封面保存重开",
+ "recordedAt": "2026-09-06T10:30:14.385Z",
+ "kind": "browser",
+ "ids": [
+ "project.load",
+ "project.title",
+ "project.save",
+ "selection.tree",
+ "property.name",
+ "property.visible",
+ "property.operable",
+ "property.position",
+ "property.rotation",
+ "property.scale",
+ "property.device-id",
+ "property.model-id",
+ "property.semantic",
+ "part.source",
+ "panel.binding",
+ "cover.capture",
+ "cover.atomic"
+ ],
+ "file": "evidence/fox-properties-cover.json",
+ "title": "狐狸:真实细分权限、全部属性、清空绑定、蒙皮保护、相机与封面保存重开",
+ "details": "真实字段输入与清空、六个语义选项、蒙皮保护、源模型可见,正文/资产/封面保存和刷新;不把原件旧模板组合当新模板结果。",
+ "screenshots": []
+ }
+ ]
+ },
+ {
+ "id": "project.new",
+ "group": "工程入口与顶部命令",
+ "name": "顶部新建独立工程",
+ "source": {
+ "file": "../../../unreal_tran_web/src/features/guide-legacy/demo-src/scene-editor.js",
+ "symbol": "new: () =>",
+ "line": 575
+ },
+ "implementation": "本轮修复已完成验证",
+ "boundary": "基线代码重置当前模板;本轮由宿主接真实创建 API,必须保留原项目。",
+ "acceptance": "取消/失败不清原工程;成功新 ID;保存重开与列表对应。",
+ "fix": "顶部新建委托宿主命名与创建 API,先确认未保存内容。选择放弃也要等创建及导航成功后才清除原 dirty/恢复稿;创建503仍保留原稿。成功后刷新原缓存页,避免再次打开时将已放弃修改冒称已保存。取消、失败不清原场景,成功导航到新项目 ID,并有创建并发守卫。",
+ "fixIds": [
+ "SCENE-F02"
+ ],
+ "verification": "PASS",
+ "evidence": [
+ {
+ "status": "PASS",
+ "testTitle": "新建独立场景:取消、失败、保护原工程以及成功新 ID",
+ "recordedAt": "2026-09-06T10:30:58.245Z",
+ "kind": "browser",
+ "ids": [
+ "project.new"
+ ],
+ "file": "evidence/new-project.json",
+ "title": "新建独立场景:取消、失败、保护原工程以及成功新 ID",
+ "details": "取消命名、创建503保留原工程、创建成功新ID、保存刷新与原工程内容核对。",
+ "screenshots": []
+ }
+ ]
+ },
+ {
+ "id": "project.back",
+ "group": "工程入口与顶部命令",
+ "name": "返回场景制作列表并保留场景标签",
+ "source": {
+ "file": "../../../unreal_tran_web/src/features/guide-legacy/demo-src/scene-editor.js",
+ "symbol": "back: () =>",
+ "line": 598
+ },
+ "implementation": "已有实现已完成验证",
+ "boundary": "平台约定返回列表等同切换标签,保留场景缓存和未存稿,不弹关闭确认;真正关闭标签才询问。",
+ "acceptance": "未存状态返回列表不写服务器、不丢场景;重新点原标签恢复输入和选择。",
+ "fix": "尚未关联本轮修复记录",
+ "fixIds": [],
+ "verification": "PASS",
+ "evidence": [
+ {
+ "status": "PASS",
+ "testTitle": "发布:前端绑定校验、保存与发布失败阻断、真实固定版本供训练目录使用",
+ "recordedAt": "2026-09-06T10:27:04.782Z",
+ "kind": "browser",
+ "ids": [
+ "validation.complete",
+ "validation.bindings",
+ "panel.validation",
+ "publish.saved",
+ "publish.failure",
+ "publish.fixed-model",
+ "cover.atomic",
+ "project.back"
+ ],
+ "file": "evidence/publish-fixed-version.json",
+ "title": "发布:前端绑定校验、保存与发布失败阻断、真实固定版本供训练目录使用",
+ "details": "实际校验、绑定阻断、POST503不假发布,重试发布当前固定版本;返回列表保留编辑标签,搜索当前项目并核对真实封面图片加载。下游静态渲染另有独立用例。",
+ "screenshots": []
+ }
+ ]
+ },
+ {
+ "id": "project.save",
+ "group": "工程入口与顶部命令",
+ "name": "保存草稿按钮",
+ "source": {
+ "file": "../../../unreal_tran_web/src/features/guide-legacy/demo-src/scene-editor.js",
+ "symbol": "async saveProject()",
+ "line": 1874
+ },
+ "implementation": "本轮修复已完成验证",
+ "boundary": "只有服务器保存完成才显示成功;本地恢复缓冲不是自动入库。",
+ "acceptance": "编辑后真实 PUT,刷新值一致,version 前进一次。",
+ "fix": "完整 PUT 回写原有相同 SCENE_MODEL 三元组时沿用场景授权边界;新增或改版依赖仍要求来源模型可见及范围权限,不扩大模型页面访问权。",
+ "fixIds": [
+ "SCENE-F14"
+ ],
+ "verification": "PASS",
+ "evidence": [
+ {
+ "status": "PASS",
+ "testTitle": "狐狸:真实细分权限、全部属性、清空绑定、蒙皮保护、相机与封面保存重开",
+ "recordedAt": "2026-09-06T10:30:14.385Z",
+ "kind": "browser",
+ "ids": [
+ "project.load",
+ "project.title",
+ "project.save",
+ "selection.tree",
+ "property.name",
+ "property.visible",
+ "property.operable",
+ "property.position",
+ "property.rotation",
+ "property.scale",
+ "property.device-id",
+ "property.model-id",
+ "property.semantic",
+ "part.source",
+ "panel.binding",
+ "cover.capture",
+ "cover.atomic"
+ ],
+ "file": "evidence/fox-properties-cover.json",
+ "title": "狐狸:真实细分权限、全部属性、清空绑定、蒙皮保护、相机与封面保存重开",
+ "details": "真实字段输入与清空、六个语义选项、蒙皮保护、源模型可见,正文/资产/封面保存和刷新;不把原件旧模板组合当新模板结果。",
+ "screenshots": []
+ },
+ {
+ "status": "PASS",
+ "testTitle": "延迟保存快照:重复点击只提交一次,期间新编辑保留,旧恢复稿不会误报冲突",
+ "recordedAt": "2026-09-06T10:24:27.147Z",
+ "kind": "browser",
+ "ids": [
+ "project.concurrent-save",
+ "project.save",
+ "project.save-key",
+ "recovery.autobuffer",
+ "recovery.same-version"
+ ],
+ "file": "evidence/save-concurrency.json",
+ "title": "延迟保存快照:重复点击只提交一次,期间新编辑保留,旧恢复稿不会误报冲突",
+ "details": "延迟真实PUT快照A、重复Ctrl+S不重复请求、期间编辑B仍dirty;再次保存刷新无旧恢复稿误报。",
+ "screenshots": []
+ },
+ {
+ "status": "PASS",
+ "testTitle": "双标签各自修改场景:保存和明确丢弃当前页均保留另一页真实恢复稿",
+ "recordedAt": "2026-09-06T10:25:56.822Z",
+ "kind": "browser",
+ "ids": [
+ "recovery.multi-session",
+ "recovery.autobuffer",
+ "recovery.discard",
+ "project.save"
+ ],
+ "file": "evidence/scene-multi-session.json",
+ "title": "双标签各自修改场景:保存和明确丢弃当前页均保留另一页真实恢复稿",
+ "details": "",
+ "screenshots": []
+ }
+ ]
+ },
+ {
+ "id": "project.save-key",
+ "group": "工程入口与顶部命令",
+ "name": "Ctrl/Meta+S 保存快捷键",
+ "source": {
+ "file": "../../../unreal_tran_web/src/features/guide-legacy/demo-src/scene-editor.js",
+ "symbol": "handleKeyboard(event)",
+ "line": 469
+ },
+ "implementation": "已有实现已完成验证",
+ "boundary": "键盘与按钮走相同保存/权限/忙碌守卫。",
+ "acceptance": "实际按键触发一次保存;表单内与非表单焦点分别核查。",
+ "fix": "尚未关联本轮修复记录",
+ "fixIds": [],
+ "verification": "PASS",
+ "evidence": [
+ {
+ "status": "PASS",
+ "testTitle": "延迟保存快照:重复点击只提交一次,期间新编辑保留,旧恢复稿不会误报冲突",
+ "recordedAt": "2026-09-06T10:24:27.147Z",
+ "kind": "browser",
+ "ids": [
+ "project.concurrent-save",
+ "project.save",
+ "project.save-key",
+ "recovery.autobuffer",
+ "recovery.same-version"
+ ],
+ "file": "evidence/save-concurrency.json",
+ "title": "延迟保存快照:重复点击只提交一次,期间新编辑保留,旧恢复稿不会误报冲突",
+ "details": "延迟真实PUT快照A、重复Ctrl+S不重复请求、期间编辑B仍dirty;再次保存刷新无旧恢复稿误报。",
+ "screenshots": []
+ },
+ {
+ "status": "PASS",
+ "testTitle": "牛奶卡车:刚性部件独立变换、软删除、历史与保存基线一致",
+ "recordedAt": "2026-09-06T10:30:32.596Z",
+ "kind": "browser",
+ "ids": [
+ "project.save-key",
+ "part.transform",
+ "part.soft-delete",
+ "history.undo",
+ "history.redo",
+ "property.name",
+ "view.enclosure-full",
+ "view.enclosure-xray",
+ "view.enclosure-hidden"
+ ],
+ "file": "evidence/truck-part-history.json",
+ "title": "牛奶卡车:刚性部件独立变换、软删除、历史与保存基线一致",
+ "details": "实际部件变换和软删除、按钮/键盘历史、保存基线与输入框自身撤销;双实例隔离及车库围护三种模式真实点击、导出和隐藏后保存刷新。",
+ "screenshots": []
+ }
+ ]
+ },
+ {
+ "id": "project.save-failure",
+ "group": "工程入口与顶部命令",
+ "name": "保存失败保留修改和恢复稿",
+ "source": {
+ "file": "../../../unreal_tran_web/src/features/guide-legacy/demo-src/scene-editor.js",
+ "symbol": "async saveProject()",
+ "line": 1874
+ },
+ "implementation": "已有实现已完成验证",
+ "boundary": "网络失败或业务拒绝不能清 dirty;不弹虚假保存成功。",
+ "acceptance": "定向 503 后对象/字段保留,恢复后重试与刷新成功。",
+ "fix": "尚未关联本轮修复记录",
+ "fixIds": [],
+ "verification": "PASS",
+ "evidence": [
+ {
+ "status": "PASS",
+ "testTitle": "保存失败与封面降级:503 保留草稿,封面上传失败保留旧图,恢复后可重试",
+ "recordedAt": "2026-09-06T10:26:33.388Z",
+ "kind": "browser",
+ "ids": [
+ "project.save-failure",
+ "cover.failure"
+ ],
+ "file": "evidence/save-cover-failure.json",
+ "title": "保存失败与封面降级:503 保留草稿,封面上传失败保留旧图,恢复后可重试",
+ "details": "正文PUT503不清草稿;封面上传503保留旧图但正文可保存;恢复服务后保存刷新。截图异常和409另由聚焦合同验证。",
+ "screenshots": []
+ }
+ ]
+ },
+ {
+ "id": "project.conflict",
+ "group": "工程入口与顶部命令",
+ "name": "409 乐观锁冲突保护",
+ "source": {
+ "file": "../../../unreal_tran_web/src/views/content/LegacyScenePageView.vue",
+ "symbol": "function describeSaveError",
+ "line": 432
+ },
+ "implementation": "已有实现已完成验证",
+ "boundary": "服务器最新版本不覆盖;恢复副本可下载/隔离。",
+ "acceptance": "独立副本制造版本冲突;验证失败提示、旧版本不覆盖与本地稿保留。",
+ "fix": "尚未关联本轮修复记录",
+ "fixIds": [],
+ "verification": "PASS",
+ "evidence": [
+ {
+ "status": "PASS",
+ "testTitle": "同版本恢复与 409:真实未保存草稿重载、服务器新版本保护与隔离下载",
+ "recordedAt": "2026-09-06T10:26:49.585Z",
+ "kind": "browser",
+ "ids": [
+ "project.conflict",
+ "recovery.autobuffer",
+ "recovery.same-version",
+ "recovery.stale-version"
+ ],
+ "file": "evidence/real-recovery-conflict.json",
+ "title": "同版本恢复与 409:真实未保存草稿重载、服务器新版本保护与隔离下载",
+ "details": "真实未存输入产生恢复稿,同版本恢复不自动入库;另一版本先保存后409拒绝覆盖,旧稿隔离下载。",
+ "screenshots": []
+ },
+ {
+ "status": "PASS",
+ "testTitle": "upload conflict propagates without any content PUT",
+ "kind": "unit",
+ "runId": "scene-editor-2026-09-06T10:24:04.121Z",
+ "ids": [
+ "project.conflict",
+ "cover.failure"
+ ],
+ "file": "validation.json",
+ "title": "upload conflict propagates without any content PUT",
+ "details": "实际执行真实方法/纯函数合同,未冒称浏览器操作",
+ "screenshots": []
+ }
+ ]
+ },
+ {
+ "id": "project.concurrent-save",
+ "group": "工程入口与顶部命令",
+ "name": "重复保存与保存中继续编辑",
+ "source": {
+ "file": "../../../unreal_tran_web/src/features/guide-legacy/demo-src/scene-editor.js",
+ "symbol": "const saveRevision = this.changeRevision",
+ "line": 1887
+ },
+ "implementation": "本轮修复已完成验证",
+ "boundary": "提交快照与后续编辑分开;后续修改不能随早先请求完成清掉。",
+ "acceptance": "延迟 PUT、连点保存并继续改值;一次旧提交、新修改仍 dirty。",
+ "fix": "开始保存时取消之前安排的恢复稿定时器,避免它覆盖宿主保存快照 nonce;保存中的后续编辑仍单独保留。",
+ "fixIds": [
+ "SCENE-F08"
+ ],
+ "verification": "PASS",
+ "evidence": [
+ {
+ "status": "PASS",
+ "testTitle": "延迟保存快照:重复点击只提交一次,期间新编辑保留,旧恢复稿不会误报冲突",
+ "recordedAt": "2026-09-06T10:24:27.147Z",
+ "kind": "browser",
+ "ids": [
+ "project.concurrent-save",
+ "project.save",
+ "project.save-key",
+ "recovery.autobuffer",
+ "recovery.same-version"
+ ],
+ "file": "evidence/save-concurrency.json",
+ "title": "延迟保存快照:重复点击只提交一次,期间新编辑保留,旧恢复稿不会误报冲突",
+ "details": "延迟真实PUT快照A、重复Ctrl+S不重复请求、期间编辑B仍dirty;再次保存刷新无旧恢复稿误报。",
+ "screenshots": []
+ }
+ ]
+ },
+ {
+ "id": "json.export",
+ "group": "工程文件与资源导入",
+ "name": "导出当前场景 JSON",
+ "source": {
+ "file": "../../../unreal_tran_web/src/features/guide-legacy/demo-src/scene-editor.js",
+ "symbol": "exportProject()",
+ "line": 578
+ },
+ "implementation": "已有实现已完成验证",
+ "boundary": "导出 edit3dv4.scene、对象/部件覆盖、渲染和稳定资源引用;不冒称自包含 GLB 包。",
+ "acceptance": "打开下载 JSON 核对内容、结构和无临时 blob 依赖。",
+ "fix": "尚未关联本轮修复记录",
+ "fixIds": [],
+ "verification": "PASS",
+ "evidence": [
+ {
+ "status": "PASS",
+ "testTitle": "工程 JSON 与小 GLB:真实上传、损坏拒绝、引用失败回滚、相机参数往返",
+ "recordedAt": "2026-09-06T10:26:19.703Z",
+ "kind": "browser",
+ "ids": [
+ "json.export",
+ "json.import",
+ "json.invalid",
+ "json.missing-resource",
+ "json.rendering",
+ "glb.import",
+ "glb.invalid",
+ "glb.references",
+ "validation.references"
+ ],
+ "file": "evidence/json-glb-roundtrip.json",
+ "title": "工程 JSON 与小 GLB:真实上传、损坏拒绝、引用失败回滚、相机参数往返",
+ "details": "真实JSON/GLB文件输入、错误schema/损坏资源/上传503拒绝、错误assetCode回滚、稳定URI及渲染参数保存重开。",
+ "screenshots": []
+ },
+ {
+ "status": "PASS",
+ "testTitle": "只读管理员:可浏览预览与导出,模板/资源/键盘/接口不能改场景",
+ "recordedAt": "2026-09-06T10:31:05.221Z",
+ "kind": "browser",
+ "ids": [
+ "permission.readonly",
+ "permission.update",
+ "permission.publish",
+ "preview.enter",
+ "preview.exit",
+ "preview.lock",
+ "json.export"
+ ],
+ "file": "evidence/readonly.json",
+ "title": "只读管理员:可浏览预览与导出,模板/资源/键盘/接口不能改场景",
+ "details": "只读按钮/模板/资源禁写、蒙皮浏览、运行预览与快捷键不变更对象,直接PUT拒绝;具体搜索/鼠标覆盖另见视口用例。",
+ "screenshots": []
+ }
+ ]
+ },
+ {
+ "id": "json.import",
+ "group": "工程文件与资源导入",
+ "name": "导入有效场景工程 JSON",
+ "source": {
+ "file": "../../../unreal_tran_web/src/features/guide-legacy/demo-src/scene-editor.js",
+ "symbol": "importProject(event)",
+ "line": 462
+ },
+ "implementation": "本轮修复已完成验证",
+ "boundary": "替换当前编辑文档后仍需保存;不得写入另一个项目身份。",
+ "acceptance": "导出后实际文件导入、保存、重开;对象与渲染参数往返一致。",
+ "fix": "模板按钮使用模板种子,不再次读当前项目旧文档;模板/JSON 替换先保存当前运行对象,等待全部加载结束,失败恢复原环境、对象、选择和 dirty;真实确认取消保留原稿。",
+ "fixIds": [
+ "SCENE-F03"
+ ],
+ "verification": "PASS",
+ "evidence": [
+ {
+ "status": "PASS",
+ "testTitle": "工程 JSON 与小 GLB:真实上传、损坏拒绝、引用失败回滚、相机参数往返",
+ "recordedAt": "2026-09-06T10:26:19.703Z",
+ "kind": "browser",
+ "ids": [
+ "json.export",
+ "json.import",
+ "json.invalid",
+ "json.missing-resource",
+ "json.rendering",
+ "glb.import",
+ "glb.invalid",
+ "glb.references",
+ "validation.references"
+ ],
+ "file": "evidence/json-glb-roundtrip.json",
+ "title": "工程 JSON 与小 GLB:真实上传、损坏拒绝、引用失败回滚、相机参数往返",
+ "details": "真实JSON/GLB文件输入、错误schema/损坏资源/上传503拒绝、错误assetCode回滚、稳定URI及渲染参数保存重开。",
+ "screenshots": []
+ }
+ ]
+ },
+ {
+ "id": "json.invalid",
+ "group": "工程文件与资源导入",
+ "name": "非法 JSON / 错误 schema 拒绝",
+ "source": {
+ "file": "../../../unreal_tran_web/src/features/guide-legacy/demo-src/scene-editor.js",
+ "symbol": "importProject(event)",
+ "line": 462
+ },
+ "implementation": "本轮修复已完成验证",
+ "boundary": "解析或结构错误不得清空当前场景。",
+ "acceptance": "非法 JSON 与非场景文档各一次,原工程和模型仍可编辑。",
+ "fix": "模板按钮使用模板种子,不再次读当前项目旧文档;模板/JSON 替换先保存当前运行对象,等待全部加载结束,失败恢复原环境、对象、选择和 dirty;真实确认取消保留原稿。",
+ "fixIds": [
+ "SCENE-F03"
+ ],
+ "verification": "PASS",
+ "evidence": [
+ {
+ "status": "PASS",
+ "testTitle": "工程 JSON 与小 GLB:真实上传、损坏拒绝、引用失败回滚、相机参数往返",
+ "recordedAt": "2026-09-06T10:26:19.703Z",
+ "kind": "browser",
+ "ids": [
+ "json.export",
+ "json.import",
+ "json.invalid",
+ "json.missing-resource",
+ "json.rendering",
+ "glb.import",
+ "glb.invalid",
+ "glb.references",
+ "validation.references"
+ ],
+ "file": "evidence/json-glb-roundtrip.json",
+ "title": "工程 JSON 与小 GLB:真实上传、损坏拒绝、引用失败回滚、相机参数往返",
+ "details": "真实JSON/GLB文件输入、错误schema/损坏资源/上传503拒绝、错误assetCode回滚、稳定URI及渲染参数保存重开。",
+ "screenshots": []
+ }
+ ]
+ },
+ {
+ "id": "json.missing-resource",
+ "group": "工程文件与资源导入",
+ "name": "JSON 缺失/错误受控引用拒绝与回滚",
+ "source": {
+ "file": "../../../unreal_tran_web/src/features/guide-legacy/demo-src/scene-editor.js",
+ "symbol": "async restoreImportedObject",
+ "line": 2016
+ },
+ "implementation": "本轮修复已完成验证",
+ "boundary": "错误资产编码不能回退为另一个模型;恢复失败不半替换。",
+ "acceptance": "导入显式不存在编码/版本;原对象保留或明确阻断,零错误发布。",
+ "fix": "模板按钮使用模板种子,不再次读当前项目旧文档;模板/JSON 替换先保存当前运行对象,等待全部加载结束,失败恢复原环境、对象、选择和 dirty;真实确认取消保留原稿。;前端目录和资源解析同时匹配指定项目、版本、code、id、URI、READY MODEL_FILE;错误引用不回退首文件或本地缓存。仅含 sourceAssetId/assetId 的本项目资源也必须将 ID 纳入缓存/去重键,防止已缓存有效引用掩盖另一个错误ID;失败请求缓存可重试。复制场景保留旧伪 URI 但只读取当前副本资产。",
+ "fixIds": [
+ "SCENE-F03",
+ "SCENE-F12"
+ ],
+ "verification": "PASS",
+ "evidence": [
+ {
+ "status": "PASS",
+ "testTitle": "工程 JSON 与小 GLB:真实上传、损坏拒绝、引用失败回滚、相机参数往返",
+ "recordedAt": "2026-09-06T10:26:19.703Z",
+ "kind": "browser",
+ "ids": [
+ "json.export",
+ "json.import",
+ "json.invalid",
+ "json.missing-resource",
+ "json.rendering",
+ "glb.import",
+ "glb.invalid",
+ "glb.references",
+ "validation.references"
+ ],
+ "file": "evidence/json-glb-roundtrip.json",
+ "title": "工程 JSON 与小 GLB:真实上传、损坏拒绝、引用失败回滚、相机参数往返",
+ "details": "真实JSON/GLB文件输入、错误schema/损坏资源/上传503拒绝、错误assetCode回滚、稳定URI及渲染参数保存重开。",
+ "screenshots": []
+ },
+ {
+ "status": "PASS",
+ "testTitle": "a failed replacement GLB keeps the previous live scene rather than clearing an unsaved draft",
+ "kind": "unit",
+ "runId": "scene-editor-2026-09-06T10:24:04.121Z",
+ "ids": [
+ "json.missing-resource"
+ ],
+ "file": "validation.json",
+ "title": "a failed replacement GLB keeps the previous live scene rather than clearing an unsaved draft",
+ "details": "实际执行真实方法/纯函数合同,未冒称浏览器操作",
+ "screenshots": []
+ },
+ {
+ "status": "PASS",
+ "testTitle": "an explicit missing code cannot fall back to the first READY asset",
+ "kind": "unit",
+ "runId": "scene-editor-2026-09-06T10:24:04.121Z",
+ "ids": [
+ "validation.references",
+ "json.missing-resource"
+ ],
+ "file": "validation.json",
+ "title": "an explicit missing code cannot fall back to the first READY asset",
+ "details": "实际执行真实方法/纯函数合同,未冒称浏览器操作",
+ "screenshots": []
+ }
+ ]
+ },
+ {
+ "id": "json.rendering",
+ "group": "工程文件与资源导入",
+ "name": "相机、灯光、雾和环境序列化往返",
+ "source": {
+ "file": "../../../unreal_tran_web/src/features/guide-legacy/demo-src/scene-editor.js",
+ "symbol": "serializeRendering()",
+ "line": 857
+ },
+ "implementation": "本轮修复已完成验证",
+ "boundary": "当前默认 UI 不提供全部渲染编辑器;导入/保存仍须保留其文档参数。",
+ "acceptance": "合法带渲染参数文档导入、实际画面比对、保存重开字段一致。",
+ "fix": "透视按钮实际恢复先前观察位置;导入、加载和历史恢复存在已保存相机时不再被车辆自动聚焦覆盖。",
+ "fixIds": [
+ "SCENE-F04"
+ ],
+ "verification": "PASS",
+ "evidence": [
+ {
+ "status": "PASS",
+ "testTitle": "工程 JSON 与小 GLB:真实上传、损坏拒绝、引用失败回滚、相机参数往返",
+ "recordedAt": "2026-09-06T10:26:19.703Z",
+ "kind": "browser",
+ "ids": [
+ "json.export",
+ "json.import",
+ "json.invalid",
+ "json.missing-resource",
+ "json.rendering",
+ "glb.import",
+ "glb.invalid",
+ "glb.references",
+ "validation.references"
+ ],
+ "file": "evidence/json-glb-roundtrip.json",
+ "title": "工程 JSON 与小 GLB:真实上传、损坏拒绝、引用失败回滚、相机参数往返",
+ "details": "真实JSON/GLB文件输入、错误schema/损坏资源/上传503拒绝、错误assetCode回滚、稳定URI及渲染参数保存重开。",
+ "screenshots": []
+ },
+ {
+ "status": "PASS",
+ "testTitle": "loading and history restoration preserve an authored camera rather than auto-focusing a vehicle",
+ "kind": "unit",
+ "runId": "scene-editor-2026-09-06T10:24:04.121Z",
+ "ids": [
+ "json.rendering",
+ "history.undo"
+ ],
+ "file": "validation.json",
+ "title": "loading and history restoration preserve an authored camera rather than auto-focusing a vehicle",
+ "details": "实际执行真实方法/纯函数合同,未冒称浏览器操作",
+ "screenshots": []
+ }
+ ]
+ },
+ {
+ "id": "glb.import",
+ "group": "工程文件与资源导入",
+ "name": "本地 GLB 上传并加入当前场景",
+ "source": {
+ "file": "../../../unreal_tran_web/src/features/guide-legacy/demo-src/scene-editor.js",
+ "symbol": "async importGLB(event)",
+ "line": 2122
+ },
+ "implementation": "本轮修复已完成验证",
+ "boundary": "上传到内容服务,消费票据后解析;对象加入不等于工程已保存。",
+ "acceptance": "真实选择小 GLB,上传/登记/可见/保存重开均完成。",
+ "fix": "恢复显示名、设备和物模型编号使用空值保留规则,不把显式空串补回旧值;新资源序号跳过既有编号,空白编号校验按 trim 处理。",
+ "fixIds": [
+ "SCENE-F06"
+ ],
+ "verification": "PASS",
+ "evidence": [
+ {
+ "status": "PASS",
+ "testTitle": "工程 JSON 与小 GLB:真实上传、损坏拒绝、引用失败回滚、相机参数往返",
+ "recordedAt": "2026-09-06T10:26:19.703Z",
+ "kind": "browser",
+ "ids": [
+ "json.export",
+ "json.import",
+ "json.invalid",
+ "json.missing-resource",
+ "json.rendering",
+ "glb.import",
+ "glb.invalid",
+ "glb.references",
+ "validation.references"
+ ],
+ "file": "evidence/json-glb-roundtrip.json",
+ "title": "工程 JSON 与小 GLB:真实上传、损坏拒绝、引用失败回滚、相机参数往返",
+ "details": "真实JSON/GLB文件输入、错误schema/损坏资源/上传503拒绝、错误assetCode回滚、稳定URI及渲染参数保存重开。",
+ "screenshots": []
+ }
+ ]
+ },
+ {
+ "id": "glb.invalid",
+ "group": "工程文件与资源导入",
+ "name": "非 GLB / 损坏 GLB / 上传失败处理",
+ "source": {
+ "file": "../../../unreal_tran_web/src/features/guide-legacy/demo-src/scene-editor.js",
+ "symbol": "async importGLB(event)",
+ "line": 2122
+ },
+ "implementation": "已有实现已完成验证",
+ "boundary": "上传或解析失败不插入占位对象;原场景应保留,提示可重试。",
+ "acceptance": "错误格式及损坏文件、上传 503;验证对象数/旧资产/后续成功导入。",
+ "fix": "尚未关联本轮修复记录",
+ "fixIds": [],
+ "verification": "PASS",
+ "evidence": [
+ {
+ "status": "PASS",
+ "testTitle": "工程 JSON 与小 GLB:真实上传、损坏拒绝、引用失败回滚、相机参数往返",
+ "recordedAt": "2026-09-06T10:26:19.703Z",
+ "kind": "browser",
+ "ids": [
+ "json.export",
+ "json.import",
+ "json.invalid",
+ "json.missing-resource",
+ "json.rendering",
+ "glb.import",
+ "glb.invalid",
+ "glb.references",
+ "validation.references"
+ ],
+ "file": "evidence/json-glb-roundtrip.json",
+ "title": "工程 JSON 与小 GLB:真实上传、损坏拒绝、引用失败回滚、相机参数往返",
+ "details": "真实JSON/GLB文件输入、错误schema/损坏资源/上传503拒绝、错误assetCode回滚、稳定URI及渲染参数保存重开。",
+ "screenshots": []
+ }
+ ]
+ },
+ {
+ "id": "glb.pending",
+ "group": "工程文件与资源导入",
+ "name": "资源加载/上传期间操作互斥",
+ "source": {
+ "file": "../../../unreal_tran_web/src/features/guide-legacy/demo-src/scene-editor.js",
+ "symbol": "setModelLoading(loading",
+ "line": 1411
+ },
+ "implementation": "已有实现已完成验证",
+ "boundary": "加载中的模型不能被半保存、导出或发布;不能离开造成旧请求污染新项目。",
+ "acceptance": "延迟下载/上传,操作禁用与待完成状态一致,完成后恢复。",
+ "fix": "尚未关联本轮修复记录",
+ "fixIds": [],
+ "verification": "PASS",
+ "evidence": [
+ {
+ "status": "PASS",
+ "testTitle": "加载失败重试与加载互斥:不展示可写半场景、恢复网络后模型完整加载",
+ "recordedAt": "2026-09-06T10:24:14.518Z",
+ "kind": "browser",
+ "ids": [
+ "project.retry",
+ "glb.pending",
+ "leave.busy"
+ ],
+ "file": "evidence/loading-retry.json",
+ "title": "加载失败重试与加载互斥:不展示可写半场景、恢复网络后模型完整加载",
+ "details": "GET503只留中性失败和重试;延迟受控下载时禁用写入/导出/预览并阻止关闭,释放后真实狐狸加载。",
+ "screenshots": []
+ },
+ {
+ "status": "PASS",
+ "testTitle": "replacement failure waits for other started loads before restoring the original scene",
+ "kind": "unit",
+ "runId": "scene-editor-2026-09-06T10:24:04.121Z",
+ "ids": [
+ "history.failure",
+ "glb.pending"
+ ],
+ "file": "validation.json",
+ "title": "replacement failure waits for other started loads before restoring the original scene",
+ "details": "实际执行真实方法/纯函数合同,未冒称浏览器操作",
+ "screenshots": []
+ },
+ {
+ "status": "PASS",
+ "testTitle": "an upload finishing after a project switch cannot register into the new session",
+ "kind": "unit",
+ "runId": "scene-editor-2026-09-06T10:24:04.121Z",
+ "ids": [
+ "glb.pending",
+ "lifecycle.destroy"
+ ],
+ "file": "validation.json",
+ "title": "an upload finishing after a project switch cannot register into the new session",
+ "details": "实际执行真实方法/纯函数合同,未冒称浏览器操作",
+ "screenshots": []
+ }
+ ]
+ },
+ {
+ "id": "glb.references",
+ "group": "工程文件与资源导入",
+ "name": "本项目 GLB 稳定资产引用",
+ "source": {
+ "file": "../../../unreal_tran_web/src/features/scene-legacy/serverAssetStore.ts",
+ "symbol": "async put(blob",
+ "line": 113
+ },
+ "implementation": "本轮修复已完成验证",
+ "boundary": "自导入使用 content://asset/scene/{projectId}/{assetCode} 和 READY 资产;blob 只供运行。",
+ "acceptance": "重新会话受控下载成功,主对象/资产身份一致,无本地缓存依赖。",
+ "fix": "前端目录和资源解析同时匹配指定项目、版本、code、id、URI、READY MODEL_FILE;错误引用不回退首文件或本地缓存。仅含 sourceAssetId/assetId 的本项目资源也必须将 ID 纳入缓存/去重键,防止已缓存有效引用掩盖另一个错误ID;失败请求缓存可重试。复制场景保留旧伪 URI 但只读取当前副本资产。;新增 SceneResourceReferenceValidator 检查 objects 和递归 splitParts;自有/固定版本资源的所有显式引用必须指向同一 READY 模型文件,错误引用在发布/提交前阻断,保留受控的内置资源和旧引用兼容。",
+ "fixIds": [
+ "SCENE-F12",
+ "SCENE-F13"
+ ],
+ "verification": "PASS",
+ "evidence": [
+ {
+ "status": "PASS",
+ "testTitle": "工程 JSON 与小 GLB:真实上传、损坏拒绝、引用失败回滚、相机参数往返",
+ "recordedAt": "2026-09-06T10:26:19.703Z",
+ "kind": "browser",
+ "ids": [
+ "json.export",
+ "json.import",
+ "json.invalid",
+ "json.missing-resource",
+ "json.rendering",
+ "glb.import",
+ "glb.invalid",
+ "glb.references",
+ "validation.references"
+ ],
+ "file": "evidence/json-glb-roundtrip.json",
+ "title": "工程 JSON 与小 GLB:真实上传、损坏拒绝、引用失败回滚、相机参数往返",
+ "details": "真实JSON/GLB文件输入、错误schema/损坏资源/上传503拒绝、错误assetCode回滚、稳定URI及渲染参数保存重开。",
+ "screenshots": []
+ },
+ {
+ "status": "PASS",
+ "testTitle": "copied scene keeps its original pseudo URI but reads its own copied asset by stable code",
+ "kind": "unit",
+ "runId": "scene-editor-2026-09-06T10:24:04.121Z",
+ "ids": [
+ "glb.references"
+ ],
+ "file": "validation.json",
+ "title": "copied scene keeps its original pseudo URI but reads its own copied asset by stable code",
+ "details": "实际执行真实方法/纯函数合同,未冒称浏览器操作",
+ "screenshots": []
+ }
+ ]
+ },
+ {
+ "id": "panel.left-tabs",
+ "group": "左侧目录与资源放置",
+ "name": "场景大纲 / 资源库 / 场景模板切换",
+ "source": {
+ "file": "../../../unreal_tran_web/src/features/guide-legacy/demo-src/scene-editor.js",
+ "symbol": "const leftTab =",
+ "line": 540
+ },
+ "implementation": "已有实现已完成验证",
+ "boundary": "切面板不写业务文档;筛选状态行为需一致。",
+ "acceptance": "三页签均可达,无 DOM 异常或资源目录中断。",
+ "fix": "尚未关联本轮修复记录",
+ "fixIds": [],
+ "verification": "PASS",
+ "evidence": [
+ {
+ "status": "PASS",
+ "testTitle": "模板搜索与未保存切换确认保留和放弃",
+ "recordedAt": "2026-09-06T10:28:42.077Z",
+ "kind": "e2e",
+ "ids": [
+ "panel.left-tabs",
+ "search.templates",
+ "template.switch-cancel"
+ ],
+ "file": "evidence/template-confirm.json",
+ "title": "模板搜索与未保存切换确认保留和放弃",
+ "details": "中文/无匹配/清空搜索;真实取消保留对象,真实确认放弃并切换空白模板后保存。",
+ "screenshots": [
+ "29-template-confirm-empty.png"
+ ]
+ }
+ ]
+ },
+ {
+ "id": "search.tree",
+ "group": "左侧目录与资源放置",
+ "name": "对象/部件名称搜索与清空",
+ "source": {
+ "file": "../../../unreal_tran_web/src/features/guide-legacy/demo-src/scene-editor.js",
+ "symbol": "renderTree()",
+ "line": 627
+ },
+ "implementation": "已有实现已完成验证",
+ "boundary": "根名与部件名可命中;无匹配状态应清楚,非删除对象。",
+ "acceptance": "根名/部件名/无结果/清空分别核对行和真实对象数。",
+ "fix": "尚未关联本轮修复记录",
+ "fixIds": [],
+ "verification": "PASS",
+ "evidence": [
+ {
+ "status": "PASS",
+ "testTitle": "模型成果狐狸卡车双击与真实拖入、树搜索和部件隐藏重开",
+ "recordedAt": "2026-09-06T10:29:25.167Z",
+ "kind": "e2e",
+ "ids": [
+ "assets.catalog",
+ "assets.model-double-click",
+ "assets.model-drop",
+ "search.tree",
+ "part.source",
+ "property.visible"
+ ],
+ "file": "evidence/published-model-assets.json",
+ "title": "模型成果狐狸卡车双击与真实拖入、树搜索和部件隐藏重开",
+ "details": "API 已发布目录,狐狸双击和卡车真实拖入;固定来源版本、树根/部件搜索;卡车刚体部件隐藏保存重开,最后恢复可见。基线树没有折叠按钮,不将搜索伪称折叠。",
+ "screenshots": [
+ "33-fox-placed-visible.png",
+ "34-truck-placed-part-restored.png"
+ ]
+ }
+ ]
+ },
+ {
+ "id": "search.assets",
+ "group": "左侧目录与资源放置",
+ "name": "资源搜索与异步刷新保留筛选",
+ "source": {
+ "file": "../../../unreal_tran_web/src/features/guide-legacy/demo-src/scene-editor.js",
+ "symbol": "applyLeftFilter()",
+ "line": 742
+ },
+ "implementation": "已有实现已完成验证",
+ "boundary": "只筛当前目录;异步重绘不复活不匹配卡片。",
+ "acceptance": "命中、无结果、清空,目录重绘后过滤仍正确。",
+ "fix": "尚未关联本轮修复记录",
+ "fixIds": [],
+ "verification": "PASS",
+ "evidence": [
+ {
+ "status": "PASS",
+ "testTitle": "八种基础资源逐项放置、真实拖放、搜索和保存重开",
+ "recordedAt": "2026-09-06T10:28:51.941Z",
+ "kind": "e2e",
+ "ids": [
+ "asset.diagnostic",
+ "asset.equipment",
+ "asset.workbench",
+ "asset.shelf",
+ "asset.safety-zone",
+ "asset.camera",
+ "asset.worker",
+ "asset.light",
+ "assets.basic-double-click",
+ "assets.basic-drop",
+ "search.assets",
+ "panel.objects"
+ ],
+ "file": "evidence/basic-assets.json",
+ "title": "八种基础资源逐项放置、真实拖放、搜索和保存重开",
+ "details": "8 种基础资源分别双击放置并分开布置;真实指针拖入第 9 个工作台、吸附检查、唯一编号、保存刷新一致。照明为现有资源外观,不冒称连接真实灯控。",
+ "screenshots": [
+ "30-eight-basic-assets-reopened.png"
+ ]
+ }
+ ]
+ },
+ {
+ "id": "search.templates",
+ "group": "左侧目录与资源放置",
+ "name": "模板搜索与清空",
+ "source": {
+ "file": "../../../unreal_tran_web/src/features/guide-legacy/demo-src/scene-editor.js",
+ "symbol": "filterLeft(query)",
+ "line": 2278
+ },
+ "implementation": "已有实现已完成验证",
+ "boundary": "模板卡片与同一搜索框联动。",
+ "acceptance": "中文模板关键字过滤、无结果和清空。",
+ "fix": "尚未关联本轮修复记录",
+ "fixIds": [],
+ "verification": "PASS",
+ "evidence": [
+ {
+ "status": "PASS",
+ "testTitle": "模板搜索与未保存切换确认保留和放弃",
+ "recordedAt": "2026-09-06T10:28:42.077Z",
+ "kind": "e2e",
+ "ids": [
+ "panel.left-tabs",
+ "search.templates",
+ "template.switch-cancel"
+ ],
+ "file": "evidence/template-confirm.json",
+ "title": "模板搜索与未保存切换确认保留和放弃",
+ "details": "中文/无匹配/清空搜索;真实取消保留对象,真实确认放弃并切换空白模板后保存。",
+ "screenshots": [
+ "29-template-confirm-empty.png"
+ ]
+ }
+ ]
+ },
+ {
+ "id": "assets.catalog",
+ "group": "左侧目录与资源放置",
+ "name": "真实已发布模型成果目录",
+ "source": {
+ "file": "../../../unreal_tran_web/src/features/scene-legacy/contentApiClient.ts",
+ "symbol": "async listPublishedModels",
+ "line": 783
+ },
+ "implementation": "本轮修复已完成验证",
+ "boundary": "目录由 API 预加载,当前默认不以 localStorage 发布清单为真值。",
+ "acceptance": "显示发布模型名称/版本,草稿不冒充已发布,空态可操作。",
+ "fix": "移除 runtime 旧 content.update 门槛,独立计算新建、更新和发布;无模型页面权限时不加载新模型目录,但既有固定依赖仍可按场景授权读取。",
+ "fixIds": [
+ "SCENE-F01"
+ ],
+ "verification": "PASS",
+ "evidence": [
+ {
+ "status": "PASS",
+ "testTitle": "模型成果狐狸卡车双击与真实拖入、树搜索和部件隐藏重开",
+ "recordedAt": "2026-09-06T10:29:25.167Z",
+ "kind": "e2e",
+ "ids": [
+ "assets.catalog",
+ "assets.model-double-click",
+ "assets.model-drop",
+ "search.tree",
+ "part.source",
+ "property.visible"
+ ],
+ "file": "evidence/published-model-assets.json",
+ "title": "模型成果狐狸卡车双击与真实拖入、树搜索和部件隐藏重开",
+ "details": "API 已发布目录,狐狸双击和卡车真实拖入;固定来源版本、树根/部件搜索;卡车刚体部件隐藏保存重开,最后恢复可见。基线树没有折叠按钮,不将搜索伪称折叠。",
+ "screenshots": [
+ "33-fox-placed-visible.png",
+ "34-truck-placed-part-restored.png"
+ ]
+ }
+ ]
+ },
+ {
+ "id": "assets.model-double-click",
+ "group": "左侧目录与资源放置",
+ "name": "双击已发布模型放置",
+ "source": {
+ "file": "../../../unreal_tran_web/src/features/guide-legacy/demo-src/scene-editor.js",
+ "symbol": "async placeEditorAsset",
+ "line": 1357
+ },
+ "implementation": "已有实现已完成验证",
+ "boundary": "按固定 project/version/assetCode 导入当前实例,继承发布部件和材质。",
+ "acceptance": "狐狸和卡车分别双击加入、真实可见、保存重开固定版本。",
+ "fix": "尚未关联本轮修复记录",
+ "fixIds": [],
+ "verification": "PASS",
+ "evidence": [
+ {
+ "status": "PASS",
+ "testTitle": "模型成果狐狸卡车双击与真实拖入、树搜索和部件隐藏重开",
+ "recordedAt": "2026-09-06T10:29:25.167Z",
+ "kind": "e2e",
+ "ids": [
+ "assets.catalog",
+ "assets.model-double-click",
+ "assets.model-drop",
+ "search.tree",
+ "part.source",
+ "property.visible"
+ ],
+ "file": "evidence/published-model-assets.json",
+ "title": "模型成果狐狸卡车双击与真实拖入、树搜索和部件隐藏重开",
+ "details": "API 已发布目录,狐狸双击和卡车真实拖入;固定来源版本、树根/部件搜索;卡车刚体部件隐藏保存重开,最后恢复可见。基线树没有折叠按钮,不将搜索伪称折叠。",
+ "screenshots": [
+ "33-fox-placed-visible.png",
+ "34-truck-placed-part-restored.png"
+ ]
+ },
+ {
+ "status": "PASS",
+ "testTitle": "fresh skinned placement reaches the requested size and floor after authored scale/pose replay",
+ "kind": "unit",
+ "runId": "scene-editor-2026-09-06T10:24:04.121Z",
+ "ids": [
+ "assets.model-double-click",
+ "assets.model-drop"
+ ],
+ "file": "validation.json",
+ "title": "fresh skinned placement reaches the requested size and floor after authored scale/pose replay",
+ "details": "实际执行真实方法/纯函数合同,未冒称浏览器操作",
+ "screenshots": []
+ }
+ ]
+ },
+ {
+ "id": "assets.model-drop",
+ "group": "左侧目录与资源放置",
+ "name": "拖入已发布模型放置",
+ "source": {
+ "file": "../../../unreal_tran_web/src/features/guide-legacy/demo-src/scene-editor.js",
+ "symbol": "handleDrop(event)",
+ "line": 461
+ },
+ "implementation": "已有实现已完成验证",
+ "boundary": "拖放实际命中地平面;与双击共用受控资产链路。",
+ "acceptance": "真实指针拖放到不同位置,坐标/实例数/固定引用准确。",
+ "fix": "尚未关联本轮修复记录",
+ "fixIds": [],
+ "verification": "PASS",
+ "evidence": [
+ {
+ "status": "PASS",
+ "testTitle": "模型成果狐狸卡车双击与真实拖入、树搜索和部件隐藏重开",
+ "recordedAt": "2026-09-06T10:29:25.167Z",
+ "kind": "e2e",
+ "ids": [
+ "assets.catalog",
+ "assets.model-double-click",
+ "assets.model-drop",
+ "search.tree",
+ "part.source",
+ "property.visible"
+ ],
+ "file": "evidence/published-model-assets.json",
+ "title": "模型成果狐狸卡车双击与真实拖入、树搜索和部件隐藏重开",
+ "details": "API 已发布目录,狐狸双击和卡车真实拖入;固定来源版本、树根/部件搜索;卡车刚体部件隐藏保存重开,最后恢复可见。基线树没有折叠按钮,不将搜索伪称折叠。",
+ "screenshots": [
+ "33-fox-placed-visible.png",
+ "34-truck-placed-part-restored.png"
+ ]
+ },
+ {
+ "status": "PASS",
+ "testTitle": "fresh skinned placement reaches the requested size and floor after authored scale/pose replay",
+ "kind": "unit",
+ "runId": "scene-editor-2026-09-06T10:24:04.121Z",
+ "ids": [
+ "assets.model-double-click",
+ "assets.model-drop"
+ ],
+ "file": "validation.json",
+ "title": "fresh skinned placement reaches the requested size and floor after authored scale/pose replay",
+ "details": "实际执行真实方法/纯函数合同,未冒称浏览器操作",
+ "screenshots": []
+ }
+ ]
+ },
+ {
+ "id": "assets.basic-double-click",
+ "group": "左侧目录与资源放置",
+ "name": "双击基础资源放置",
+ "source": {
+ "file": "../../../unreal_tran_web/src/features/guide-legacy/demo-src/scene-editor.js",
+ "symbol": "onRootDblClick",
+ "line": 429
+ },
+ "implementation": "本轮修复已完成验证",
+ "boundary": "以当前轨道目标落地;每次生成独立对象身份。",
+ "acceptance": "双击生成对象,变换、保存后仍可见。",
+ "fix": "恢复显示名、设备和物模型编号使用空值保留规则,不把显式空串补回旧值;新资源序号跳过既有编号,空白编号校验按 trim 处理。",
+ "fixIds": [
+ "SCENE-F06"
+ ],
+ "verification": "PASS",
+ "evidence": [
+ {
+ "status": "PASS",
+ "testTitle": "八种基础资源逐项放置、真实拖放、搜索和保存重开",
+ "recordedAt": "2026-09-06T10:28:51.941Z",
+ "kind": "e2e",
+ "ids": [
+ "asset.diagnostic",
+ "asset.equipment",
+ "asset.workbench",
+ "asset.shelf",
+ "asset.safety-zone",
+ "asset.camera",
+ "asset.worker",
+ "asset.light",
+ "assets.basic-double-click",
+ "assets.basic-drop",
+ "search.assets",
+ "panel.objects"
+ ],
+ "file": "evidence/basic-assets.json",
+ "title": "八种基础资源逐项放置、真实拖放、搜索和保存重开",
+ "details": "8 种基础资源分别双击放置并分开布置;真实指针拖入第 9 个工作台、吸附检查、唯一编号、保存刷新一致。照明为现有资源外观,不冒称连接真实灯控。",
+ "screenshots": [
+ "30-eight-basic-assets-reopened.png"
+ ]
+ }
+ ]
+ },
+ {
+ "id": "assets.basic-drop",
+ "group": "左侧目录与资源放置",
+ "name": "基础资源拖入与拖放提示",
+ "source": {
+ "file": "../../../unreal_tran_web/src/features/guide-legacy/demo-src/scene-editor.js",
+ "symbol": "handleDrop(event)",
+ "line": 461
+ },
+ "implementation": "已有实现已完成验证",
+ "boundary": "MIME 和 text/plain 回退协议一致;离开视口清提示。",
+ "acceptance": "真实拖放命中位置与 0.5m 吸附、dragleave/drop 提示复位。",
+ "fix": "尚未关联本轮修复记录",
+ "fixIds": [],
+ "verification": "PASS",
+ "evidence": [
+ {
+ "status": "PASS",
+ "testTitle": "八种基础资源逐项放置、真实拖放、搜索和保存重开",
+ "recordedAt": "2026-09-06T10:28:51.941Z",
+ "kind": "e2e",
+ "ids": [
+ "asset.diagnostic",
+ "asset.equipment",
+ "asset.workbench",
+ "asset.shelf",
+ "asset.safety-zone",
+ "asset.camera",
+ "asset.worker",
+ "asset.light",
+ "assets.basic-double-click",
+ "assets.basic-drop",
+ "search.assets",
+ "panel.objects"
+ ],
+ "file": "evidence/basic-assets.json",
+ "title": "八种基础资源逐项放置、真实拖放、搜索和保存重开",
+ "details": "8 种基础资源分别双击放置并分开布置;真实指针拖入第 9 个工作台、吸附检查、唯一编号、保存刷新一致。照明为现有资源外观,不冒称连接真实灯控。",
+ "screenshots": [
+ "30-eight-basic-assets-reopened.png"
+ ]
+ }
+ ]
+ },
+ {
+ "id": "assets.model-link",
+ "group": "左侧目录与资源放置",
+ "name": "打开/返回模型编辑器入口",
+ "source": {
+ "file": "../../../unreal_tran_web/src/features/guide-legacy/demo-src/scene-editor.js",
+ "symbol": "open-model-editor",
+ "line": 590
+ },
+ "implementation": "本轮修复已完成验证",
+ "boundary": "当前基线只有跳模型列表,来源按钮不得冒称直达正确源项目。",
+ "acceptance": "空资源库和选中模型来源两入口,跳转/未保存保护正确。",
+ "fix": "模型来源按钮有来源权限时导航正确项目;无来源页面权限回到可访问列表,不将模型列表误称为来源工程编辑。",
+ "fixIds": [
+ "SCENE-F11"
+ ],
+ "verification": "PASS",
+ "evidence": [
+ {
+ "status": "PASS",
+ "testTitle": "model source navigation locates the published owner and falls back without source permission",
+ "kind": "unit",
+ "runId": "scene-editor-2026-09-06T10:24:04.121Z",
+ "ids": [
+ "assets.model-link"
+ ],
+ "file": "validation.json",
+ "title": "model source navigation locates the published owner and falls back without source permission",
+ "details": "实际执行真实方法/纯函数合同,未冒称浏览器操作",
+ "screenshots": []
+ }
+ ]
+ },
+ {
+ "id": "asset.diagnostic",
+ "group": "左侧目录与资源放置",
+ "name": "基础资源:液压诊断工位",
+ "source": {
+ "file": "../../../unreal_tran_web/src/features/guide-legacy/demo-src/scene-editor.js",
+ "symbol": "id: 'diagnostic'",
+ "line": 123
+ },
+ "implementation": "已有实现已完成验证",
+ "boundary": "现有程序化场景资源;名称不代表已连接物理设备或真实人员。",
+ "acceptance": "实际放置可见,独立编号、位置和保存重开一致;照明应区分灯具外观与光源效果。",
+ "fix": "尚未关联本轮修复记录",
+ "fixIds": [],
+ "verification": "PASS",
+ "evidence": [
+ {
+ "status": "PASS",
+ "testTitle": "八种基础资源逐项放置、真实拖放、搜索和保存重开",
+ "recordedAt": "2026-09-06T10:28:51.941Z",
+ "kind": "e2e",
+ "ids": [
+ "asset.diagnostic",
+ "asset.equipment",
+ "asset.workbench",
+ "asset.shelf",
+ "asset.safety-zone",
+ "asset.camera",
+ "asset.worker",
+ "asset.light",
+ "assets.basic-double-click",
+ "assets.basic-drop",
+ "search.assets",
+ "panel.objects"
+ ],
+ "file": "evidence/basic-assets.json",
+ "title": "八种基础资源逐项放置、真实拖放、搜索和保存重开",
+ "details": "8 种基础资源分别双击放置并分开布置;真实指针拖入第 9 个工作台、吸附检查、唯一编号、保存刷新一致。照明为现有资源外观,不冒称连接真实灯控。",
+ "screenshots": [
+ "30-eight-basic-assets-reopened.png"
+ ]
+ }
+ ]
+ },
+ {
+ "id": "asset.equipment",
+ "group": "左侧目录与资源放置",
+ "name": "基础资源:维修装备主体",
+ "source": {
+ "file": "../../../unreal_tran_web/src/features/guide-legacy/demo-src/scene-editor.js",
+ "symbol": "id: 'equipment'",
+ "line": 124
+ },
+ "implementation": "已有实现已完成验证",
+ "boundary": "现有程序化场景资源;名称不代表已连接物理设备或真实人员。",
+ "acceptance": "实际放置可见,独立编号、位置和保存重开一致;照明应区分灯具外观与光源效果。",
+ "fix": "尚未关联本轮修复记录",
+ "fixIds": [],
+ "verification": "PASS",
+ "evidence": [
+ {
+ "status": "PASS",
+ "testTitle": "八种基础资源逐项放置、真实拖放、搜索和保存重开",
+ "recordedAt": "2026-09-06T10:28:51.941Z",
+ "kind": "e2e",
+ "ids": [
+ "asset.diagnostic",
+ "asset.equipment",
+ "asset.workbench",
+ "asset.shelf",
+ "asset.safety-zone",
+ "asset.camera",
+ "asset.worker",
+ "asset.light",
+ "assets.basic-double-click",
+ "assets.basic-drop",
+ "search.assets",
+ "panel.objects"
+ ],
+ "file": "evidence/basic-assets.json",
+ "title": "八种基础资源逐项放置、真实拖放、搜索和保存重开",
+ "details": "8 种基础资源分别双击放置并分开布置;真实指针拖入第 9 个工作台、吸附检查、唯一编号、保存刷新一致。照明为现有资源外观,不冒称连接真实灯控。",
+ "screenshots": [
+ "30-eight-basic-assets-reopened.png"
+ ]
+ }
+ ]
+ },
+ {
+ "id": "asset.workbench",
+ "group": "左侧目录与资源放置",
+ "name": "基础资源:检修工作台",
+ "source": {
+ "file": "../../../unreal_tran_web/src/features/guide-legacy/demo-src/scene-editor.js",
+ "symbol": "id: 'workbench'",
+ "line": 125
+ },
+ "implementation": "已有实现已完成验证",
+ "boundary": "现有程序化场景资源;名称不代表已连接物理设备或真实人员。",
+ "acceptance": "实际放置可见,独立编号、位置和保存重开一致;照明应区分灯具外观与光源效果。",
+ "fix": "尚未关联本轮修复记录",
+ "fixIds": [],
+ "verification": "PASS",
+ "evidence": [
+ {
+ "status": "PASS",
+ "testTitle": "八种基础资源逐项放置、真实拖放、搜索和保存重开",
+ "recordedAt": "2026-09-06T10:28:51.941Z",
+ "kind": "e2e",
+ "ids": [
+ "asset.diagnostic",
+ "asset.equipment",
+ "asset.workbench",
+ "asset.shelf",
+ "asset.safety-zone",
+ "asset.camera",
+ "asset.worker",
+ "asset.light",
+ "assets.basic-double-click",
+ "assets.basic-drop",
+ "search.assets",
+ "panel.objects"
+ ],
+ "file": "evidence/basic-assets.json",
+ "title": "八种基础资源逐项放置、真实拖放、搜索和保存重开",
+ "details": "8 种基础资源分别双击放置并分开布置;真实指针拖入第 9 个工作台、吸附检查、唯一编号、保存刷新一致。照明为现有资源外观,不冒称连接真实灯控。",
+ "screenshots": [
+ "30-eight-basic-assets-reopened.png"
+ ]
+ }
+ ]
+ },
+ {
+ "id": "asset.shelf",
+ "group": "左侧目录与资源放置",
+ "name": "基础资源:器材存放架",
+ "source": {
+ "file": "../../../unreal_tran_web/src/features/guide-legacy/demo-src/scene-editor.js",
+ "symbol": "id: 'shelf'",
+ "line": 126
+ },
+ "implementation": "已有实现已完成验证",
+ "boundary": "现有程序化场景资源;名称不代表已连接物理设备或真实人员。",
+ "acceptance": "实际放置可见,独立编号、位置和保存重开一致;照明应区分灯具外观与光源效果。",
+ "fix": "尚未关联本轮修复记录",
+ "fixIds": [],
+ "verification": "PASS",
+ "evidence": [
+ {
+ "status": "PASS",
+ "testTitle": "八种基础资源逐项放置、真实拖放、搜索和保存重开",
+ "recordedAt": "2026-09-06T10:28:51.941Z",
+ "kind": "e2e",
+ "ids": [
+ "asset.diagnostic",
+ "asset.equipment",
+ "asset.workbench",
+ "asset.shelf",
+ "asset.safety-zone",
+ "asset.camera",
+ "asset.worker",
+ "asset.light",
+ "assets.basic-double-click",
+ "assets.basic-drop",
+ "search.assets",
+ "panel.objects"
+ ],
+ "file": "evidence/basic-assets.json",
+ "title": "八种基础资源逐项放置、真实拖放、搜索和保存重开",
+ "details": "8 种基础资源分别双击放置并分开布置;真实指针拖入第 9 个工作台、吸附检查、唯一编号、保存刷新一致。照明为现有资源外观,不冒称连接真实灯控。",
+ "screenshots": [
+ "30-eight-basic-assets-reopened.png"
+ ]
+ }
+ ]
+ },
+ {
+ "id": "asset.safety-zone",
+ "group": "左侧目录与资源放置",
+ "name": "基础资源:安全作业区域",
+ "source": {
+ "file": "../../../unreal_tran_web/src/features/guide-legacy/demo-src/scene-editor.js",
+ "symbol": "id: 'safety-zone'",
+ "line": 127
+ },
+ "implementation": "已有实现已完成验证",
+ "boundary": "现有程序化场景资源;名称不代表已连接物理设备或真实人员。",
+ "acceptance": "实际放置可见,独立编号、位置和保存重开一致;照明应区分灯具外观与光源效果。",
+ "fix": "尚未关联本轮修复记录",
+ "fixIds": [],
+ "verification": "PASS",
+ "evidence": [
+ {
+ "status": "PASS",
+ "testTitle": "八种基础资源逐项放置、真实拖放、搜索和保存重开",
+ "recordedAt": "2026-09-06T10:28:51.941Z",
+ "kind": "e2e",
+ "ids": [
+ "asset.diagnostic",
+ "asset.equipment",
+ "asset.workbench",
+ "asset.shelf",
+ "asset.safety-zone",
+ "asset.camera",
+ "asset.worker",
+ "asset.light",
+ "assets.basic-double-click",
+ "assets.basic-drop",
+ "search.assets",
+ "panel.objects"
+ ],
+ "file": "evidence/basic-assets.json",
+ "title": "八种基础资源逐项放置、真实拖放、搜索和保存重开",
+ "details": "8 种基础资源分别双击放置并分开布置;真实指针拖入第 9 个工作台、吸附检查、唯一编号、保存刷新一致。照明为现有资源外观,不冒称连接真实灯控。",
+ "screenshots": [
+ "30-eight-basic-assets-reopened.png"
+ ]
+ }
+ ]
+ },
+ {
+ "id": "asset.camera",
+ "group": "左侧目录与资源放置",
+ "name": "基础资源:视觉采集设备",
+ "source": {
+ "file": "../../../unreal_tran_web/src/features/guide-legacy/demo-src/scene-editor.js",
+ "symbol": "id: 'camera'",
+ "line": 128
+ },
+ "implementation": "已有实现已完成验证",
+ "boundary": "现有程序化场景资源;名称不代表已连接物理设备或真实人员。",
+ "acceptance": "实际放置可见,独立编号、位置和保存重开一致;照明应区分灯具外观与光源效果。",
+ "fix": "尚未关联本轮修复记录",
+ "fixIds": [],
+ "verification": "PASS",
+ "evidence": [
+ {
+ "status": "PASS",
+ "testTitle": "八种基础资源逐项放置、真实拖放、搜索和保存重开",
+ "recordedAt": "2026-09-06T10:28:51.941Z",
+ "kind": "e2e",
+ "ids": [
+ "asset.diagnostic",
+ "asset.equipment",
+ "asset.workbench",
+ "asset.shelf",
+ "asset.safety-zone",
+ "asset.camera",
+ "asset.worker",
+ "asset.light",
+ "assets.basic-double-click",
+ "assets.basic-drop",
+ "search.assets",
+ "panel.objects"
+ ],
+ "file": "evidence/basic-assets.json",
+ "title": "八种基础资源逐项放置、真实拖放、搜索和保存重开",
+ "details": "8 种基础资源分别双击放置并分开布置;真实指针拖入第 9 个工作台、吸附检查、唯一编号、保存刷新一致。照明为现有资源外观,不冒称连接真实灯控。",
+ "screenshots": [
+ "30-eight-basic-assets-reopened.png"
+ ]
+ }
+ ]
+ },
+ {
+ "id": "asset.worker",
+ "group": "左侧目录与资源放置",
+ "name": "基础资源:训练人员占位",
+ "source": {
+ "file": "../../../unreal_tran_web/src/features/guide-legacy/demo-src/scene-editor.js",
+ "symbol": "id: 'worker'",
+ "line": 129
+ },
+ "implementation": "已有实现已完成验证",
+ "boundary": "现有程序化场景资源;名称不代表已连接物理设备或真实人员。",
+ "acceptance": "实际放置可见,独立编号、位置和保存重开一致;照明应区分灯具外观与光源效果。",
+ "fix": "尚未关联本轮修复记录",
+ "fixIds": [],
+ "verification": "PASS",
+ "evidence": [
+ {
+ "status": "PASS",
+ "testTitle": "八种基础资源逐项放置、真实拖放、搜索和保存重开",
+ "recordedAt": "2026-09-06T10:28:51.941Z",
+ "kind": "e2e",
+ "ids": [
+ "asset.diagnostic",
+ "asset.equipment",
+ "asset.workbench",
+ "asset.shelf",
+ "asset.safety-zone",
+ "asset.camera",
+ "asset.worker",
+ "asset.light",
+ "assets.basic-double-click",
+ "assets.basic-drop",
+ "search.assets",
+ "panel.objects"
+ ],
+ "file": "evidence/basic-assets.json",
+ "title": "八种基础资源逐项放置、真实拖放、搜索和保存重开",
+ "details": "8 种基础资源分别双击放置并分开布置;真实指针拖入第 9 个工作台、吸附检查、唯一编号、保存刷新一致。照明为现有资源外观,不冒称连接真实灯控。",
+ "screenshots": [
+ "30-eight-basic-assets-reopened.png"
+ ]
+ }
+ ]
+ },
+ {
+ "id": "asset.light",
+ "group": "左侧目录与资源放置",
+ "name": "基础资源:区域照明",
+ "source": {
+ "file": "../../../unreal_tran_web/src/features/guide-legacy/demo-src/scene-editor.js",
+ "symbol": "id: 'light'",
+ "line": 130
+ },
+ "implementation": "已有实现已完成验证",
+ "boundary": "现有程序化场景资源;名称不代表已连接物理设备或真实人员。",
+ "acceptance": "实际放置可见,独立编号、位置和保存重开一致;照明应区分灯具外观与光源效果。",
+ "fix": "尚未关联本轮修复记录",
+ "fixIds": [],
+ "verification": "PASS",
+ "evidence": [
+ {
+ "status": "PASS",
+ "testTitle": "八种基础资源逐项放置、真实拖放、搜索和保存重开",
+ "recordedAt": "2026-09-06T10:28:51.941Z",
+ "kind": "e2e",
+ "ids": [
+ "asset.diagnostic",
+ "asset.equipment",
+ "asset.workbench",
+ "asset.shelf",
+ "asset.safety-zone",
+ "asset.camera",
+ "asset.worker",
+ "asset.light",
+ "assets.basic-double-click",
+ "assets.basic-drop",
+ "search.assets",
+ "panel.objects"
+ ],
+ "file": "evidence/basic-assets.json",
+ "title": "八种基础资源逐项放置、真实拖放、搜索和保存重开",
+ "details": "8 种基础资源分别双击放置并分开布置;真实指针拖入第 9 个工作台、吸附检查、唯一编号、保存刷新一致。照明为现有资源外观,不冒称连接真实灯控。",
+ "screenshots": [
+ "30-eight-basic-assets-reopened.png"
+ ]
+ }
+ ]
+ },
+ {
+ "id": "asset.vehicle-depot",
+ "group": "左侧目录与资源放置",
+ "name": "场地资源:真实装备车库",
+ "source": {
+ "file": "../../../unreal_tran_web/src/features/guide-legacy/demo-src/scene-editor.js",
+ "symbol": "id: 'vehicle-depot'",
+ "line": 121
+ },
+ "implementation": "已有实现已完成验证",
+ "boundary": "整体场地 GLB 与维修车间分开;重复放置按既有规则处理。",
+ "acceptance": "资源拖放或双击加载、比例/落地正常、重复放置不产生错误重叠。",
+ "fix": "尚未关联本轮修复记录",
+ "fixIds": [],
+ "verification": "PASS",
+ "evidence": [
+ {
+ "status": "PASS",
+ "testTitle": "两种整体场地加载、重复放置阻止与显式删除后替换",
+ "recordedAt": "2026-09-06T10:29:06.315Z",
+ "kind": "e2e",
+ "ids": [
+ "asset.vehicle-depot",
+ "asset.field-terrain",
+ "object.delete"
+ ],
+ "file": "evidence/venue-assets.json",
+ "title": "两种整体场地加载、重复放置阻止与显式删除后替换",
+ "details": "车库和起伏地形真实加载并分别保存重开;已有整体场地时阻止叠加,显式删除旧场地后才能替换。",
+ "screenshots": [
+ "31-venue-depot-reopened.png",
+ "32-venue-terrain-reopened.png"
+ ]
+ }
+ ]
+ },
+ {
+ "id": "asset.field-terrain",
+ "group": "左侧目录与资源放置",
+ "name": "场地资源:真实野外地形",
+ "source": {
+ "file": "../../../unreal_tran_web/src/features/guide-legacy/demo-src/scene-editor.js",
+ "symbol": "id: 'field-terrain'",
+ "line": 122
+ },
+ "implementation": "已有实现已完成验证",
+ "boundary": "真实地形资源和指定落地基准;不能以程序地板代替。",
+ "acceptance": "加载后真实起伏地形可见,保存重开资源、地面高度一致。",
+ "fix": "尚未关联本轮修复记录",
+ "fixIds": [],
+ "verification": "PASS",
+ "evidence": [
+ {
+ "status": "PASS",
+ "testTitle": "两种整体场地加载、重复放置阻止与显式删除后替换",
+ "recordedAt": "2026-09-06T10:29:06.315Z",
+ "kind": "e2e",
+ "ids": [
+ "asset.vehicle-depot",
+ "asset.field-terrain",
+ "object.delete"
+ ],
+ "file": "evidence/venue-assets.json",
+ "title": "两种整体场地加载、重复放置阻止与显式删除后替换",
+ "details": "车库和起伏地形真实加载并分别保存重开;已有整体场地时阻止叠加,显式删除旧场地后才能替换。",
+ "screenshots": [
+ "31-venue-depot-reopened.png",
+ "32-venue-terrain-reopened.png"
+ ]
+ }
+ ]
+ },
+ {
+ "id": "template.depot-1",
+ "group": "全部可达场景模板",
+ "name": "真实装备车库",
+ "source": {
+ "file": "../../../unreal_tran_web/src/features/guide-legacy/demo-src/scene-editor.js",
+ "symbol": "id: 'depot-1'",
+ "line": 86
+ },
+ "implementation": "本轮修复已完成验证",
+ "boundary": "模板场地与种子布局须有实际执行;切模板不是创建新的业务项目。",
+ "acceptance": "独立副本点击模板、确认/取消未存操作、真实画面、保存重开 presetId/环境/对象一致。",
+ "fix": "模板按钮使用模板种子,不再次读当前项目旧文档;模板/JSON 替换先保存当前运行对象,等待全部加载结束,失败恢复原环境、对象、选择和 dirty;真实确认取消保留原稿。",
+ "fixIds": [
+ "SCENE-F03"
+ ],
+ "verification": "PASS",
+ "evidence": [
+ {
+ "status": "PASS",
+ "testTitle": "场景模板实际加载、保存重开:真实装备车库",
+ "recordedAt": "2026-09-06T10:27:14.151Z",
+ "kind": "e2e",
+ "ids": [
+ "template.depot-1"
+ ],
+ "file": "evidence/template-depot-1.json",
+ "title": "场景模板实际加载、保存重开:真实装备车库",
+ "details": "真实模板按钮、选中标签、环境字段、种子对象、PUT 后刷新重开;截图验证对应模板实际画面。",
+ "screenshots": [
+ "20-template-depot-1.png"
+ ]
+ }
+ ]
+ },
+ {
+ "id": "template.terrain-1",
+ "group": "全部可达场景模板",
+ "name": "真实野外训练场",
+ "source": {
+ "file": "../../../unreal_tran_web/src/features/guide-legacy/demo-src/scene-editor.js",
+ "symbol": "id: 'terrain-1'",
+ "line": 87
+ },
+ "implementation": "本轮修复已完成验证",
+ "boundary": "模板场地与种子布局须有实际执行;切模板不是创建新的业务项目。",
+ "acceptance": "独立副本点击模板、确认/取消未存操作、真实画面、保存重开 presetId/环境/对象一致。",
+ "fix": "模板按钮使用模板种子,不再次读当前项目旧文档;模板/JSON 替换先保存当前运行对象,等待全部加载结束,失败恢复原环境、对象、选择和 dirty;真实确认取消保留原稿。",
+ "fixIds": [
+ "SCENE-F03"
+ ],
+ "verification": "PASS",
+ "evidence": [
+ {
+ "status": "PASS",
+ "testTitle": "场景模板实际加载、保存重开:真实野外训练场",
+ "recordedAt": "2026-09-06T10:27:24.419Z",
+ "kind": "e2e",
+ "ids": [
+ "template.terrain-1"
+ ],
+ "file": "evidence/template-terrain-1.json",
+ "title": "场景模板实际加载、保存重开:真实野外训练场",
+ "details": "真实模板按钮、选中标签、环境字段、种子对象、PUT 后刷新重开;截图验证对应模板实际画面。",
+ "screenshots": [
+ "21-template-terrain-1.png"
+ ]
+ }
+ ]
+ },
+ {
+ "id": "template.workshop-1",
+ "group": "全部可达场景模板",
+ "name": "综合检验车间",
+ "source": {
+ "file": "../../../unreal_tran_web/src/features/guide-legacy/demo-src/scene-editor.js",
+ "symbol": "id: 'workshop-1'",
+ "line": 88
+ },
+ "implementation": "本轮修复已完成验证",
+ "boundary": "模板场地与种子布局须有实际执行;切模板不是创建新的业务项目。",
+ "acceptance": "独立副本点击模板、确认/取消未存操作、真实画面、保存重开 presetId/环境/对象一致。",
+ "fix": "模板按钮使用模板种子,不再次读当前项目旧文档;模板/JSON 替换先保存当前运行对象,等待全部加载结束,失败恢复原环境、对象、选择和 dirty;真实确认取消保留原稿。",
+ "fixIds": [
+ "SCENE-F03"
+ ],
+ "verification": "PASS",
+ "evidence": [
+ {
+ "status": "PASS",
+ "testTitle": "场景模板实际加载、保存重开:综合检验车间",
+ "recordedAt": "2026-09-06T10:27:33.434Z",
+ "kind": "e2e",
+ "ids": [
+ "template.workshop-1"
+ ],
+ "file": "evidence/template-workshop-1.json",
+ "title": "场景模板实际加载、保存重开:综合检验车间",
+ "details": "真实模板按钮、选中标签、环境字段、种子对象、PUT 后刷新重开;截图验证对应模板实际画面。",
+ "screenshots": [
+ "22-template-workshop-1.png"
+ ]
+ }
+ ]
+ },
+ {
+ "id": "template.workshop-real",
+ "group": "全部可达场景模板",
+ "name": "演示维修车间(可拆分)",
+ "source": {
+ "file": "../../../unreal_tran_web/src/features/guide-legacy/demo-src/scene-editor.js",
+ "symbol": "id: 'workshop-real'",
+ "line": 89
+ },
+ "implementation": "本轮修复已完成验证",
+ "boundary": "模板场地与种子布局须有实际执行;切模板不是创建新的业务项目。",
+ "acceptance": "独立副本点击模板、确认/取消未存操作、真实画面、保存重开 presetId/环境/对象一致。",
+ "fix": "模板按钮使用模板种子,不再次读当前项目旧文档;模板/JSON 替换先保存当前运行对象,等待全部加载结束,失败恢复原环境、对象、选择和 dirty;真实确认取消保留原稿。",
+ "fixIds": [
+ "SCENE-F03"
+ ],
+ "verification": "PASS",
+ "evidence": [
+ {
+ "status": "PASS",
+ "testTitle": "场景模板实际加载、保存重开:演示维修车间(可拆分)",
+ "recordedAt": "2026-09-06T10:27:50.202Z",
+ "kind": "e2e",
+ "ids": [
+ "template.workshop-real"
+ ],
+ "file": "evidence/template-workshop-real.json",
+ "title": "场景模板实际加载、保存重开:演示维修车间(可拆分)",
+ "details": "真实模板按钮、选中标签、环境字段、种子对象、PUT 后刷新重开;截图验证对应模板实际画面。",
+ "screenshots": [
+ "23-template-workshop-real.png"
+ ]
+ }
+ ]
+ },
+ {
+ "id": "template.workshop-2",
+ "group": "全部可达场景模板",
+ "name": "总成拆卸车间",
+ "source": {
+ "file": "../../../unreal_tran_web/src/features/guide-legacy/demo-src/scene-editor.js",
+ "symbol": "id: 'workshop-2'",
+ "line": 90
+ },
+ "implementation": "本轮修复已完成验证",
+ "boundary": "模板场地与种子布局须有实际执行;切模板不是创建新的业务项目。",
+ "acceptance": "独立副本点击模板、确认/取消未存操作、真实画面、保存重开 presetId/环境/对象一致。",
+ "fix": "模板按钮使用模板种子,不再次读当前项目旧文档;模板/JSON 替换先保存当前运行对象,等待全部加载结束,失败恢复原环境、对象、选择和 dirty;真实确认取消保留原稿。",
+ "fixIds": [
+ "SCENE-F03"
+ ],
+ "verification": "PASS",
+ "evidence": [
+ {
+ "status": "PASS",
+ "testTitle": "场景模板实际加载、保存重开:总成拆卸车间",
+ "recordedAt": "2026-09-06T10:27:59.059Z",
+ "kind": "e2e",
+ "ids": [
+ "template.workshop-2"
+ ],
+ "file": "evidence/template-workshop-2.json",
+ "title": "场景模板实际加载、保存重开:总成拆卸车间",
+ "details": "真实模板按钮、选中标签、环境字段、种子对象、PUT 后刷新重开;截图验证对应模板实际画面。",
+ "screenshots": [
+ "24-template-workshop-2.png"
+ ]
+ }
+ ]
+ },
+ {
+ "id": "template.workshop-3",
+ "group": "全部可达场景模板",
+ "name": "部件分解车间",
+ "source": {
+ "file": "../../../unreal_tran_web/src/features/guide-legacy/demo-src/scene-editor.js",
+ "symbol": "id: 'workshop-3'",
+ "line": 91
+ },
+ "implementation": "本轮修复已完成验证",
+ "boundary": "模板场地与种子布局须有实际执行;切模板不是创建新的业务项目。",
+ "acceptance": "独立副本点击模板、确认/取消未存操作、真实画面、保存重开 presetId/环境/对象一致。",
+ "fix": "模板按钮使用模板种子,不再次读当前项目旧文档;模板/JSON 替换先保存当前运行对象,等待全部加载结束,失败恢复原环境、对象、选择和 dirty;真实确认取消保留原稿。",
+ "fixIds": [
+ "SCENE-F03"
+ ],
+ "verification": "PASS",
+ "evidence": [
+ {
+ "status": "PASS",
+ "testTitle": "场景模板实际加载、保存重开:部件分解车间",
+ "recordedAt": "2026-09-06T10:28:07.821Z",
+ "kind": "e2e",
+ "ids": [
+ "template.workshop-3"
+ ],
+ "file": "evidence/template-workshop-3.json",
+ "title": "场景模板实际加载、保存重开:部件分解车间",
+ "details": "真实模板按钮、选中标签、环境字段、种子对象、PUT 后刷新重开;截图验证对应模板实际画面。",
+ "screenshots": [
+ "25-template-workshop-3.png"
+ ]
+ }
+ ]
+ },
+ {
+ "id": "template.workshop-4",
+ "group": "全部可达场景模板",
+ "name": "清洗检测车间",
+ "source": {
+ "file": "../../../unreal_tran_web/src/features/guide-legacy/demo-src/scene-editor.js",
+ "symbol": "id: 'workshop-4'",
+ "line": 92
+ },
+ "implementation": "本轮修复已完成验证",
+ "boundary": "模板场地与种子布局须有实际执行;切模板不是创建新的业务项目。",
+ "acceptance": "独立副本点击模板、确认/取消未存操作、真实画面、保存重开 presetId/环境/对象一致。",
+ "fix": "模板按钮使用模板种子,不再次读当前项目旧文档;模板/JSON 替换先保存当前运行对象,等待全部加载结束,失败恢复原环境、对象、选择和 dirty;真实确认取消保留原稿。",
+ "fixIds": [
+ "SCENE-F03"
+ ],
+ "verification": "PASS",
+ "evidence": [
+ {
+ "status": "PASS",
+ "testTitle": "场景模板实际加载、保存重开:清洗检测车间",
+ "recordedAt": "2026-09-06T10:28:16.504Z",
+ "kind": "e2e",
+ "ids": [
+ "template.workshop-4"
+ ],
+ "file": "evidence/template-workshop-4.json",
+ "title": "场景模板实际加载、保存重开:清洗检测车间",
+ "details": "真实模板按钮、选中标签、环境字段、种子对象、PUT 后刷新重开;截图验证对应模板实际画面。",
+ "screenshots": [
+ "26-template-workshop-4.png"
+ ]
+ }
+ ]
+ },
+ {
+ "id": "template.workshop-5",
+ "group": "全部可达场景模板",
+ "name": "复装调试车间",
+ "source": {
+ "file": "../../../unreal_tran_web/src/features/guide-legacy/demo-src/scene-editor.js",
+ "symbol": "id: 'workshop-5'",
+ "line": 93
+ },
+ "implementation": "本轮修复已完成验证",
+ "boundary": "模板场地与种子布局须有实际执行;切模板不是创建新的业务项目。",
+ "acceptance": "独立副本点击模板、确认/取消未存操作、真实画面、保存重开 presetId/环境/对象一致。",
+ "fix": "模板按钮使用模板种子,不再次读当前项目旧文档;模板/JSON 替换先保存当前运行对象,等待全部加载结束,失败恢复原环境、对象、选择和 dirty;真实确认取消保留原稿。",
+ "fixIds": [
+ "SCENE-F03"
+ ],
+ "verification": "PASS",
+ "evidence": [
+ {
+ "status": "PASS",
+ "testTitle": "场景模板实际加载、保存重开:复装调试车间",
+ "recordedAt": "2026-09-06T10:28:25.537Z",
+ "kind": "e2e",
+ "ids": [
+ "template.workshop-5"
+ ],
+ "file": "evidence/template-workshop-5.json",
+ "title": "场景模板实际加载、保存重开:复装调试车间",
+ "details": "真实模板按钮、选中标签、环境字段、种子对象、PUT 后刷新重开;截图验证对应模板实际画面。",
+ "screenshots": [
+ "27-template-workshop-5.png"
+ ]
+ }
+ ]
+ },
+ {
+ "id": "template.outdoor-1",
+ "group": "全部可达场景模板",
+ "name": "室外综合训练场",
+ "source": {
+ "file": "../../../unreal_tran_web/src/features/guide-legacy/demo-src/scene-editor.js",
+ "symbol": "id: 'outdoor-1'",
+ "line": 94
+ },
+ "implementation": "本轮修复已完成验证",
+ "boundary": "模板场地与种子布局须有实际执行;切模板不是创建新的业务项目。",
+ "acceptance": "独立副本点击模板、确认/取消未存操作、真实画面、保存重开 presetId/环境/对象一致。",
+ "fix": "模板按钮使用模板种子,不再次读当前项目旧文档;模板/JSON 替换先保存当前运行对象,等待全部加载结束,失败恢复原环境、对象、选择和 dirty;真实确认取消保留原稿。",
+ "fixIds": [
+ "SCENE-F03"
+ ],
+ "verification": "PASS",
+ "evidence": [
+ {
+ "status": "PASS",
+ "testTitle": "场景模板实际加载、保存重开:室外综合训练场",
+ "recordedAt": "2026-09-06T10:28:34.148Z",
+ "kind": "e2e",
+ "ids": [
+ "template.outdoor-1"
+ ],
+ "file": "evidence/template-outdoor-1.json",
+ "title": "场景模板实际加载、保存重开:室外综合训练场",
+ "details": "真实模板按钮、选中标签、环境字段、种子对象、PUT 后刷新重开;截图验证对应模板实际画面。",
+ "screenshots": [
+ "28-template-outdoor-1.png"
+ ]
+ }
+ ]
+ },
+ {
+ "id": "template.switch-cancel",
+ "group": "全部可达场景模板",
+ "name": "未保存模板切换取消",
+ "source": {
+ "file": "../../../unreal_tran_web/src/features/guide-legacy/demo-src/scene-editor.js",
+ "symbol": "async loadPreset",
+ "line": 765
+ },
+ "implementation": "本轮修复已完成验证",
+ "boundary": "取消保留原场景、选择与 dirty;请求中不能切换半场景。",
+ "acceptance": "有未存修改时取消/确认分别验证,取消零对象和服务端变更。",
+ "fix": "模板按钮使用模板种子,不再次读当前项目旧文档;模板/JSON 替换先保存当前运行对象,等待全部加载结束,失败恢复原环境、对象、选择和 dirty;真实确认取消保留原稿。",
+ "fixIds": [
+ "SCENE-F03"
+ ],
+ "verification": "PASS",
+ "evidence": [
+ {
+ "status": "PASS",
+ "testTitle": "模板搜索与未保存切换确认保留和放弃",
+ "recordedAt": "2026-09-06T10:28:42.077Z",
+ "kind": "e2e",
+ "ids": [
+ "panel.left-tabs",
+ "search.templates",
+ "template.switch-cancel"
+ ],
+ "file": "evidence/template-confirm.json",
+ "title": "模板搜索与未保存切换确认保留和放弃",
+ "details": "中文/无匹配/清空搜索;真实取消保留对象,真实确认放弃并切换空白模板后保存。",
+ "screenshots": [
+ "29-template-confirm-empty.png"
+ ]
+ }
+ ]
+ },
+ {
+ "id": "view.orbit",
+ "group": "三维视口、选择与变换",
+ "name": "鼠标旋转相机",
+ "source": {
+ "file": "../../../unreal_tran_web/src/features/guide-legacy/demo-src/scene-editor.js",
+ "symbol": "new OrbitControls",
+ "line": 379
+ },
+ "implementation": "已有实现已完成验证",
+ "boundary": "相机轨道操作不应误选、拖动物体。",
+ "acceptance": "实际鼠标拖动,相机变化且对象姿态不变。",
+ "fix": "尚未关联本轮修复记录",
+ "fixIds": [],
+ "verification": "PASS",
+ "evidence": [
+ {
+ "status": "PASS",
+ "testTitle": "真实相机操控、视口拾取、预览禁写和两尺寸主题可读性",
+ "recordedAt": "2026-09-06T10:29:44.118Z",
+ "kind": "真实UI+只读投影",
+ "ids": [
+ "view.orbit",
+ "view.pan",
+ "view.zoom",
+ "view.top",
+ "view.perspective",
+ "view.grid",
+ "view.focus",
+ "selection.viewport",
+ "selection.highlight",
+ "preview.enter",
+ "preview.exit",
+ "preview.lock",
+ "lifecycle.resize-theme"
+ ],
+ "file": "evidence/viewport-camera-theme.json",
+ "title": "真实相机操控、视口拾取、预览禁写和两尺寸主题可读性",
+ "details": "",
+ "screenshots": [
+ "40-viewport-dark-1920",
+ "41-viewport-light-1440",
+ "42-fox-preview-original-material"
+ ]
+ }
+ ]
+ },
+ {
+ "id": "view.pan",
+ "group": "三维视口、选择与变换",
+ "name": "鼠标平移相机",
+ "source": {
+ "file": "../../../unreal_tran_web/src/features/guide-legacy/demo-src/scene-editor.js",
+ "symbol": "new OrbitControls",
+ "line": 379
+ },
+ "implementation": "已有实现已完成验证",
+ "boundary": "轨道目标平移与物体位置分开。",
+ "acceptance": "实际右键/约定鼠标平移,目标变化且对象数据不变。",
+ "fix": "尚未关联本轮修复记录",
+ "fixIds": [],
+ "verification": "PASS",
+ "evidence": [
+ {
+ "status": "PASS",
+ "testTitle": "真实相机操控、视口拾取、预览禁写和两尺寸主题可读性",
+ "recordedAt": "2026-09-06T10:29:44.118Z",
+ "kind": "真实UI+只读投影",
+ "ids": [
+ "view.orbit",
+ "view.pan",
+ "view.zoom",
+ "view.top",
+ "view.perspective",
+ "view.grid",
+ "view.focus",
+ "selection.viewport",
+ "selection.highlight",
+ "preview.enter",
+ "preview.exit",
+ "preview.lock",
+ "lifecycle.resize-theme"
+ ],
+ "file": "evidence/viewport-camera-theme.json",
+ "title": "真实相机操控、视口拾取、预览禁写和两尺寸主题可读性",
+ "details": "",
+ "screenshots": [
+ "40-viewport-dark-1920",
+ "41-viewport-light-1440",
+ "42-fox-preview-original-material"
+ ]
+ }
+ ]
+ },
+ {
+ "id": "view.zoom",
+ "group": "三维视口、选择与变换",
+ "name": "滚轮缩放相机",
+ "source": {
+ "file": "../../../unreal_tran_web/src/features/guide-legacy/demo-src/scene-editor.js",
+ "symbol": "new OrbitControls",
+ "line": 379
+ },
+ "implementation": "已有实现已完成验证",
+ "boundary": "改变观察距离,不改对象 scale。",
+ "acceptance": "滚轮前后模型可见且对象 scale 不变。",
+ "fix": "尚未关联本轮修复记录",
+ "fixIds": [],
+ "verification": "PASS",
+ "evidence": [
+ {
+ "status": "PASS",
+ "testTitle": "真实相机操控、视口拾取、预览禁写和两尺寸主题可读性",
+ "recordedAt": "2026-09-06T10:29:44.118Z",
+ "kind": "真实UI+只读投影",
+ "ids": [
+ "view.orbit",
+ "view.pan",
+ "view.zoom",
+ "view.top",
+ "view.perspective",
+ "view.grid",
+ "view.focus",
+ "selection.viewport",
+ "selection.highlight",
+ "preview.enter",
+ "preview.exit",
+ "preview.lock",
+ "lifecycle.resize-theme"
+ ],
+ "file": "evidence/viewport-camera-theme.json",
+ "title": "真实相机操控、视口拾取、预览禁写和两尺寸主题可读性",
+ "details": "",
+ "screenshots": [
+ "40-viewport-dark-1920",
+ "41-viewport-light-1440",
+ "42-fox-preview-original-material"
+ ]
+ }
+ ]
+ },
+ {
+ "id": "view.perspective",
+ "group": "三维视口、选择与变换",
+ "name": "透视按钮",
+ "source": {
+ "file": "../../../unreal_tran_web/src/features/guide-legacy/demo-src/scene-editor.js",
+ "symbol": "data-camera=\"perspective\"",
+ "line": 259
+ },
+ "implementation": "本轮修复已完成验证",
+ "boundary": "基线按钮无处理分支;需明确恢复相机视角,不静默无效。",
+ "acceptance": "顶视切回透视,实际相机位置与激活样式变化。",
+ "fix": "透视按钮实际恢复先前观察位置;导入、加载和历史恢复存在已保存相机时不再被车辆自动聚焦覆盖。",
+ "fixIds": [
+ "SCENE-F04"
+ ],
+ "verification": "PASS",
+ "evidence": [
+ {
+ "status": "PASS",
+ "testTitle": "真实相机操控、视口拾取、预览禁写和两尺寸主题可读性",
+ "recordedAt": "2026-09-06T10:29:44.118Z",
+ "kind": "真实UI+只读投影",
+ "ids": [
+ "view.orbit",
+ "view.pan",
+ "view.zoom",
+ "view.top",
+ "view.perspective",
+ "view.grid",
+ "view.focus",
+ "selection.viewport",
+ "selection.highlight",
+ "preview.enter",
+ "preview.exit",
+ "preview.lock",
+ "lifecycle.resize-theme"
+ ],
+ "file": "evidence/viewport-camera-theme.json",
+ "title": "真实相机操控、视口拾取、预览禁写和两尺寸主题可读性",
+ "details": "",
+ "screenshots": [
+ "40-viewport-dark-1920",
+ "41-viewport-light-1440",
+ "42-fox-preview-original-material"
+ ]
+ }
+ ]
+ },
+ {
+ "id": "view.top",
+ "group": "三维视口、选择与变换",
+ "name": "顶视图按钮",
+ "source": {
+ "file": "../../../unreal_tran_web/src/features/guide-legacy/demo-src/scene-editor.js",
+ "symbol": "setTopView()",
+ "line": 585
+ },
+ "implementation": "本轮修复已完成验证",
+ "boundary": "仍为透视相机顶视位置,不冒称正交投影。",
+ "acceptance": "实际点击后顶视方向与目标一致。",
+ "fix": "透视按钮实际恢复先前观察位置;导入、加载和历史恢复存在已保存相机时不再被车辆自动聚焦覆盖。",
+ "fixIds": [
+ "SCENE-F04"
+ ],
+ "verification": "PASS",
+ "evidence": [
+ {
+ "status": "PASS",
+ "testTitle": "真实相机操控、视口拾取、预览禁写和两尺寸主题可读性",
+ "recordedAt": "2026-09-06T10:29:44.118Z",
+ "kind": "真实UI+只读投影",
+ "ids": [
+ "view.orbit",
+ "view.pan",
+ "view.zoom",
+ "view.top",
+ "view.perspective",
+ "view.grid",
+ "view.focus",
+ "selection.viewport",
+ "selection.highlight",
+ "preview.enter",
+ "preview.exit",
+ "preview.lock",
+ "lifecycle.resize-theme"
+ ],
+ "file": "evidence/viewport-camera-theme.json",
+ "title": "真实相机操控、视口拾取、预览禁写和两尺寸主题可读性",
+ "details": "",
+ "screenshots": [
+ "40-viewport-dark-1920",
+ "41-viewport-light-1440",
+ "42-fox-preview-original-material"
+ ]
+ }
+ ]
+ },
+ {
+ "id": "view.focus",
+ "group": "三维视口、选择与变换",
+ "name": "聚焦选中对象",
+ "source": {
+ "file": "../../../unreal_tran_web/src/features/guide-legacy/demo-src/scene-editor.js",
+ "symbol": "focusSelected()",
+ "line": 586
+ },
+ "implementation": "已有实现已完成验证",
+ "boundary": "按对象真实 bounds 调整相机;无选中应提示。",
+ "acceptance": "狐狸/卡车/环境分别聚焦可见;无选中提示不异常。",
+ "fix": "尚未关联本轮修复记录",
+ "fixIds": [],
+ "verification": "PASS",
+ "evidence": [
+ {
+ "status": "PASS",
+ "testTitle": "真实相机操控、视口拾取、预览禁写和两尺寸主题可读性",
+ "recordedAt": "2026-09-06T10:29:44.118Z",
+ "kind": "真实UI+只读投影",
+ "ids": [
+ "view.orbit",
+ "view.pan",
+ "view.zoom",
+ "view.top",
+ "view.perspective",
+ "view.grid",
+ "view.focus",
+ "selection.viewport",
+ "selection.highlight",
+ "preview.enter",
+ "preview.exit",
+ "preview.lock",
+ "lifecycle.resize-theme"
+ ],
+ "file": "evidence/viewport-camera-theme.json",
+ "title": "真实相机操控、视口拾取、预览禁写和两尺寸主题可读性",
+ "details": "",
+ "screenshots": [
+ "40-viewport-dark-1920",
+ "41-viewport-light-1440",
+ "42-fox-preview-original-material"
+ ]
+ }
+ ]
+ },
+ {
+ "id": "view.grid",
+ "group": "三维视口、选择与变换",
+ "name": "网格显隐",
+ "source": {
+ "file": "../../../unreal_tran_web/src/features/guide-legacy/demo-src/scene-editor.js",
+ "symbol": "toggleGrid(button)",
+ "line": 1643
+ },
+ "implementation": "已有实现已完成验证",
+ "boundary": "当前视图辅助选项;不默认要求跨重开持久化。",
+ "acceptance": "真实网格出现/消失、状态同步、截图封面不含网格。",
+ "fix": "尚未关联本轮修复记录",
+ "fixIds": [],
+ "verification": "PASS",
+ "evidence": [
+ {
+ "status": "PASS",
+ "testTitle": "真实相机操控、视口拾取、预览禁写和两尺寸主题可读性",
+ "recordedAt": "2026-09-06T10:29:44.118Z",
+ "kind": "真实UI+只读投影",
+ "ids": [
+ "view.orbit",
+ "view.pan",
+ "view.zoom",
+ "view.top",
+ "view.perspective",
+ "view.grid",
+ "view.focus",
+ "selection.viewport",
+ "selection.highlight",
+ "preview.enter",
+ "preview.exit",
+ "preview.lock",
+ "lifecycle.resize-theme"
+ ],
+ "file": "evidence/viewport-camera-theme.json",
+ "title": "真实相机操控、视口拾取、预览禁写和两尺寸主题可读性",
+ "details": "",
+ "screenshots": [
+ "40-viewport-dark-1920",
+ "41-viewport-light-1440",
+ "42-fox-preview-original-material"
+ ]
+ },
+ {
+ "status": "PASS",
+ "testTitle": "copies synchronously without bounds, gizmo, grid or selection glow, then restores before JPEG encoding",
+ "kind": "unit",
+ "runId": "scene-editor-2026-09-06T10:24:04.121Z",
+ "ids": [
+ "cover.capture",
+ "selection.highlight",
+ "view.grid"
+ ],
+ "file": "validation.json",
+ "title": "copies synchronously without bounds, gizmo, grid or selection glow, then restores before JPEG encoding",
+ "details": "实际执行真实方法/纯函数合同,未冒称浏览器操作",
+ "screenshots": []
+ }
+ ]
+ },
+ {
+ "id": "view.enclosure-full",
+ "group": "三维视口、选择与变换",
+ "name": "车库外壳完整显示",
+ "source": {
+ "file": "../../../unreal_tran_web/src/features/guide-legacy/demo-src/garage-enclosure.js",
+ "symbol": "createGarageEnclosureControls",
+ "line": 377
+ },
+ "implementation": "已有实现已完成验证",
+ "boundary": "完整显示屋顶/墙体/玻璃;仅车库资源存在时出现控制。",
+ "acceptance": "实际点击、对象材料和显示恢复,状态与画面一致。",
+ "fix": "尚未关联本轮修复记录",
+ "fixIds": [],
+ "verification": "PASS",
+ "evidence": [
+ {
+ "status": "PASS",
+ "testTitle": "牛奶卡车:刚性部件独立变换、软删除、历史与保存基线一致",
+ "recordedAt": "2026-09-06T10:30:32.596Z",
+ "kind": "browser",
+ "ids": [
+ "project.save-key",
+ "part.transform",
+ "part.soft-delete",
+ "history.undo",
+ "history.redo",
+ "property.name",
+ "view.enclosure-full",
+ "view.enclosure-xray",
+ "view.enclosure-hidden"
+ ],
+ "file": "evidence/truck-part-history.json",
+ "title": "牛奶卡车:刚性部件独立变换、软删除、历史与保存基线一致",
+ "details": "实际部件变换和软删除、按钮/键盘历史、保存基线与输入框自身撤销;双实例隔离及车库围护三种模式真实点击、导出和隐藏后保存刷新。",
+ "screenshots": []
+ }
+ ]
+ },
+ {
+ "id": "view.enclosure-xray",
+ "group": "三维视口、选择与变换",
+ "name": "车库外壳透明查看",
+ "source": {
+ "file": "../../../unreal_tran_web/src/features/guide-legacy/demo-src/garage-enclosure.js",
+ "symbol": "createGarageEnclosureControls",
+ "line": 377
+ },
+ "implementation": "已有实现已完成验证",
+ "boundary": "车库多种围护材料联动;不等于普通相机透视。",
+ "acceptance": "实际外壳透明且内部模型可辨,材质无串改。",
+ "fix": "尚未关联本轮修复记录",
+ "fixIds": [],
+ "verification": "PASS",
+ "evidence": [
+ {
+ "status": "PASS",
+ "testTitle": "牛奶卡车:刚性部件独立变换、软删除、历史与保存基线一致",
+ "recordedAt": "2026-09-06T10:30:32.596Z",
+ "kind": "browser",
+ "ids": [
+ "project.save-key",
+ "part.transform",
+ "part.soft-delete",
+ "history.undo",
+ "history.redo",
+ "property.name",
+ "view.enclosure-full",
+ "view.enclosure-xray",
+ "view.enclosure-hidden"
+ ],
+ "file": "evidence/truck-part-history.json",
+ "title": "牛奶卡车:刚性部件独立变换、软删除、历史与保存基线一致",
+ "details": "实际部件变换和软删除、按钮/键盘历史、保存基线与输入框自身撤销;双实例隔离及车库围护三种模式真实点击、导出和隐藏后保存刷新。",
+ "screenshots": []
+ }
+ ]
+ },
+ {
+ "id": "view.enclosure-hidden",
+ "group": "三维视口、选择与变换",
+ "name": "隐藏车库外壳",
+ "source": {
+ "file": "../../../unreal_tran_web/src/features/guide-legacy/demo-src/garage-enclosure.js",
+ "symbol": "createGarageEnclosureControls",
+ "line": 377
+ },
+ "implementation": "已有实现已完成验证",
+ "boundary": "隐藏围护后保留内部及地面;序列化 enclosureMode 与 dirty 契约须核对。",
+ "acceptance": "隐藏/还原、保存重开和只读浏览行为准确。",
+ "fix": "尚未关联本轮修复记录",
+ "fixIds": [],
+ "verification": "PASS",
+ "evidence": [
+ {
+ "status": "PASS",
+ "testTitle": "牛奶卡车:刚性部件独立变换、软删除、历史与保存基线一致",
+ "recordedAt": "2026-09-06T10:30:32.596Z",
+ "kind": "browser",
+ "ids": [
+ "project.save-key",
+ "part.transform",
+ "part.soft-delete",
+ "history.undo",
+ "history.redo",
+ "property.name",
+ "view.enclosure-full",
+ "view.enclosure-xray",
+ "view.enclosure-hidden"
+ ],
+ "file": "evidence/truck-part-history.json",
+ "title": "牛奶卡车:刚性部件独立变换、软删除、历史与保存基线一致",
+ "details": "实际部件变换和软删除、按钮/键盘历史、保存基线与输入框自身撤销;双实例隔离及车库围护三种模式真实点击、导出和隐藏后保存刷新。",
+ "screenshots": []
+ }
+ ]
+ },
+ {
+ "id": "selection.viewport",
+ "group": "三维视口、选择与变换",
+ "name": "点击视口选中根对象/部件",
+ "source": {
+ "file": "../../../unreal_tran_web/src/features/guide-legacy/demo-src/scene-editor.js",
+ "symbol": "selectFromViewport(event)",
+ "line": 456
+ },
+ "implementation": "本轮修复已完成验证",
+ "boundary": "只命中真实可选择可见对象;拖相机超过阈值不误选。",
+ "acceptance": "真实像素点击目标、树/属性/高亮一致,点击空白清选择。",
+ "fix": "新实例部件 sceneId 包含所属根对象身份,不再直接复用来源模型部件 ID;源 interaction/editor 标识保留。旧单实例 ID 保留,旧文档重复 ID 仅按文档顺序迁移后续冲突实例并同步 override。第二完整轮进一步发现 GLB 并发完成顺序会改变根对象顺序;现等待 allSettled 成功后按原文档顺序排列恢复根对象,拆分模板返回数组内部顺序和既有对象均保留,同源两实例不再随网速互换列表位置。新增延迟 30/1/10ms 乱序加载合同,验证序列化仍按文档顺序;最终浏览器验证独立记录。",
+ "fixIds": [
+ "SCENE-F18"
+ ],
+ "verification": "PASS",
+ "evidence": [
+ {
+ "status": "PASS",
+ "testTitle": "真实相机操控、视口拾取、预览禁写和两尺寸主题可读性",
+ "recordedAt": "2026-09-06T10:29:44.118Z",
+ "kind": "真实UI+只读投影",
+ "ids": [
+ "view.orbit",
+ "view.pan",
+ "view.zoom",
+ "view.top",
+ "view.perspective",
+ "view.grid",
+ "view.focus",
+ "selection.viewport",
+ "selection.highlight",
+ "preview.enter",
+ "preview.exit",
+ "preview.lock",
+ "lifecycle.resize-theme"
+ ],
+ "file": "evidence/viewport-camera-theme.json",
+ "title": "真实相机操控、视口拾取、预览禁写和两尺寸主题可读性",
+ "details": "",
+ "screenshots": [
+ "40-viewport-dark-1920",
+ "41-viewport-light-1440",
+ "42-fox-preview-original-material"
+ ]
+ }
+ ]
+ },
+ {
+ "id": "selection.tree",
+ "group": "三维视口、选择与变换",
+ "name": "大纲根对象与部件选中",
+ "source": {
+ "file": "../../../unreal_tran_web/src/features/guide-legacy/demo-src/scene-editor.js",
+ "symbol": "const treeItem =",
+ "line": 546
+ },
+ "implementation": "本轮修复已完成验证",
+ "boundary": "大纲显示所有可编辑实例和部件;环境锁定提示不是折叠控件。",
+ "acceptance": "树选中与视口高亮/属性联动、筛选后仍可选。",
+ "fix": "新实例部件 sceneId 包含所属根对象身份,不再直接复用来源模型部件 ID;源 interaction/editor 标识保留。旧单实例 ID 保留,旧文档重复 ID 仅按文档顺序迁移后续冲突实例并同步 override。第二完整轮进一步发现 GLB 并发完成顺序会改变根对象顺序;现等待 allSettled 成功后按原文档顺序排列恢复根对象,拆分模板返回数组内部顺序和既有对象均保留,同源两实例不再随网速互换列表位置。新增延迟 30/1/10ms 乱序加载合同,验证序列化仍按文档顺序;最终浏览器验证独立记录。",
+ "fixIds": [
+ "SCENE-F18"
+ ],
+ "verification": "PASS",
+ "evidence": [
+ {
+ "status": "PASS",
+ "testTitle": "狐狸:真实细分权限、全部属性、清空绑定、蒙皮保护、相机与封面保存重开",
+ "recordedAt": "2026-09-06T10:30:14.385Z",
+ "kind": "browser",
+ "ids": [
+ "project.load",
+ "project.title",
+ "project.save",
+ "selection.tree",
+ "property.name",
+ "property.visible",
+ "property.operable",
+ "property.position",
+ "property.rotation",
+ "property.scale",
+ "property.device-id",
+ "property.model-id",
+ "property.semantic",
+ "part.source",
+ "panel.binding",
+ "cover.capture",
+ "cover.atomic"
+ ],
+ "file": "evidence/fox-properties-cover.json",
+ "title": "狐狸:真实细分权限、全部属性、清空绑定、蒙皮保护、相机与封面保存重开",
+ "details": "真实字段输入与清空、六个语义选项、蒙皮保护、源模型可见,正文/资产/封面保存和刷新;不把原件旧模板组合当新模板结果。",
+ "screenshots": []
+ },
+ {
+ "status": "PASS",
+ "testTitle": "two published instances have unique tree part IDs and editing the second persists independently after reopen",
+ "kind": "unit",
+ "runId": "scene-editor-2026-09-06T10:24:04.121Z",
+ "ids": [
+ "part.transform",
+ "selection.tree"
+ ],
+ "file": "validation.json",
+ "title": "two published instances have unique tree part IDs and editing the second persists independently after reopen",
+ "details": "实际执行真实方法/纯函数合同,未冒称浏览器操作",
+ "screenshots": []
+ },
+ {
+ "status": "PASS",
+ "testTitle": "successful parallel restores retain document order after delayed GLBs and split-object batches finish out of order",
+ "kind": "unit",
+ "runId": "scene-editor-2026-09-06T10:24:04.121Z",
+ "ids": [
+ "project.load",
+ "selection.tree"
+ ],
+ "file": "validation.json",
+ "title": "successful parallel restores retain document order after delayed GLBs and split-object batches finish out of order",
+ "details": "实际执行真实方法/纯函数合同,未冒称浏览器操作",
+ "screenshots": []
+ }
+ ]
+ },
+ {
+ "id": "selection.hidden",
+ "group": "三维视口、选择与变换",
+ "name": "隐藏祖先与软删除部件不可误拾取",
+ "source": {
+ "file": "../../../unreal_tran_web/src/features/guide-legacy/demo-src/scene-selection.js",
+ "symbol": "chooseSceneSelection",
+ "line": 41
+ },
+ "implementation": "本轮修复已完成验证",
+ "boundary": "父隐藏/子隐藏/软删除状态不能被射线重新命中。",
+ "acceptance": "用重叠模型/部件实测,命中应落可见有效对象。",
+ "fix": "射线候选逐级检查父级可见性和软删除;删除根对象先恢复高亮材质再释放几何/材质,避免错误拾取及 GPU 资源残留。",
+ "fixIds": [
+ "SCENE-F09"
+ ],
+ "verification": "PASS",
+ "evidence": [
+ {
+ "status": "PASS",
+ "testTitle": "real Three ray hits on hidden descendants are excluded before selecting a root or part",
+ "kind": "unit",
+ "runId": "scene-editor-2026-09-06T10:24:04.121Z",
+ "ids": [
+ "selection.hidden"
+ ],
+ "file": "validation.json",
+ "title": "real Three ray hits on hidden descendants are excluded before selecting a root or part",
+ "details": "实际执行真实方法/纯函数合同,未冒称浏览器操作",
+ "screenshots": []
+ }
+ ]
+ },
+ {
+ "id": "selection.highlight",
+ "group": "三维视口、选择与变换",
+ "name": "选中高亮与恢复原材质",
+ "source": {
+ "file": "../../../unreal_tran_web/src/features/guide-legacy/demo-src/scene-editor.js",
+ "symbol": "updateSelectionHighlight()",
+ "line": 627
+ },
+ "implementation": "本轮修复已完成验证",
+ "boundary": "高亮是暂态,不写入模型/场景材质,不进入封面。",
+ "acceptance": "切换对象/清选择/预览/保存截图后材质原值恢复。",
+ "fix": "射线候选逐级检查父级可见性和软删除;删除根对象先恢复高亮材质再释放几何/材质,避免错误拾取及 GPU 资源残留。",
+ "fixIds": [
+ "SCENE-F09"
+ ],
+ "verification": "PASS",
+ "evidence": [
+ {
+ "status": "PASS",
+ "testTitle": "真实相机操控、视口拾取、预览禁写和两尺寸主题可读性",
+ "recordedAt": "2026-09-06T10:29:44.118Z",
+ "kind": "真实UI+只读投影",
+ "ids": [
+ "view.orbit",
+ "view.pan",
+ "view.zoom",
+ "view.top",
+ "view.perspective",
+ "view.grid",
+ "view.focus",
+ "selection.viewport",
+ "selection.highlight",
+ "preview.enter",
+ "preview.exit",
+ "preview.lock",
+ "lifecycle.resize-theme"
+ ],
+ "file": "evidence/viewport-camera-theme.json",
+ "title": "真实相机操控、视口拾取、预览禁写和两尺寸主题可读性",
+ "details": "",
+ "screenshots": [
+ "40-viewport-dark-1920",
+ "41-viewport-light-1440",
+ "42-fox-preview-original-material"
+ ]
+ },
+ {
+ "status": "PASS",
+ "testTitle": "selection highlight cleanup restores source materials and releases its own GPU helper",
+ "kind": "unit",
+ "runId": "scene-editor-2026-09-06T10:24:04.121Z",
+ "ids": [
+ "selection.highlight",
+ "lifecycle.destroy"
+ ],
+ "file": "validation.json",
+ "title": "selection highlight cleanup restores source materials and releases its own GPU helper",
+ "details": "实际执行真实方法/纯函数合同,未冒称浏览器操作",
+ "screenshots": []
+ },
+ {
+ "status": "PASS",
+ "testTitle": "copies synchronously without bounds, gizmo, grid or selection glow, then restores before JPEG encoding",
+ "kind": "unit",
+ "runId": "scene-editor-2026-09-06T10:24:04.121Z",
+ "ids": [
+ "cover.capture",
+ "selection.highlight",
+ "view.grid"
+ ],
+ "file": "validation.json",
+ "title": "copies synchronously without bounds, gizmo, grid or selection glow, then restores before JPEG encoding",
+ "details": "实际执行真实方法/纯函数合同,未冒称浏览器操作",
+ "screenshots": []
+ }
+ ]
+ },
+ {
+ "id": "transform.translate",
+ "group": "三维视口、选择与变换",
+ "name": "移动工具及真实 XYZ 手柄",
+ "source": {
+ "file": "../../../unreal_tran_web/src/features/guide-legacy/demo-src/scene-editor.js",
+ "symbol": "setTransform(mode)",
+ "line": 1627
+ },
+ "implementation": "已有实现已完成验证",
+ "boundary": "对象实例位置;部件按发布能力限制。",
+ "acceptance": "真实拖动平移手柄,属性联动、撤销重做、保存重开一致。",
+ "fix": "尚未关联本轮修复记录",
+ "fixIds": [],
+ "verification": "PASS",
+ "evidence": [
+ {
+ "status": "PASS",
+ "testTitle": "真实变换手柄移动旋转缩放、吸附与保存重开",
+ "recordedAt": "2026-09-06T10:29:55.869Z",
+ "kind": "真实UI手柄+只读几何投影定位",
+ "ids": [
+ "transform.translate",
+ "transform.rotate",
+ "transform.scale",
+ "transform.keys",
+ "transform.snap"
+ ],
+ "file": "evidence/viewport-transform.json",
+ "title": "真实变换手柄移动旋转缩放、吸附与保存重开",
+ "details": "",
+ "screenshots": [
+ "43-real-transform-gizmo"
+ ]
+ }
+ ]
+ },
+ {
+ "id": "transform.rotate",
+ "group": "三维视口、选择与变换",
+ "name": "旋转工具及真实旋转手柄",
+ "source": {
+ "file": "../../../unreal_tran_web/src/features/guide-legacy/demo-src/scene-editor.js",
+ "symbol": "setTransform(mode)",
+ "line": 1627
+ },
+ "implementation": "已有实现已完成验证",
+ "boundary": "UI 度数与保存弧度转换;不能仅用脚本改姿态代替拖动。",
+ "acceptance": "实际旋转手柄改变姿态并重开一致。",
+ "fix": "尚未关联本轮修复记录",
+ "fixIds": [],
+ "verification": "PASS",
+ "evidence": [
+ {
+ "status": "PASS",
+ "testTitle": "真实变换手柄移动旋转缩放、吸附与保存重开",
+ "recordedAt": "2026-09-06T10:29:55.869Z",
+ "kind": "真实UI手柄+只读几何投影定位",
+ "ids": [
+ "transform.translate",
+ "transform.rotate",
+ "transform.scale",
+ "transform.keys",
+ "transform.snap"
+ ],
+ "file": "evidence/viewport-transform.json",
+ "title": "真实变换手柄移动旋转缩放、吸附与保存重开",
+ "details": "",
+ "screenshots": [
+ "43-real-transform-gizmo"
+ ]
+ }
+ ]
+ },
+ {
+ "id": "transform.scale",
+ "group": "三维视口、选择与变换",
+ "name": "缩放工具及真实缩放手柄",
+ "source": {
+ "file": "../../../unreal_tran_web/src/features/guide-legacy/demo-src/scene-editor.js",
+ "symbol": "setTransform(mode)",
+ "line": 1627
+ },
+ "implementation": "本轮修复已完成验证",
+ "boundary": "归一化模型已有 scale 必须保留,负/零/小值合同明确。",
+ "acceptance": "真实缩放与属性数值一致;狐狸不突然放成超大或消失。",
+ "fix": "实际手柄跨越枢轴时按既有严格正尺度合同限制,避免产生负或零尺度导致模型翻转或消失;未新增镜像建模功能。",
+ "fixIds": [
+ "SCENE-F17"
+ ],
+ "verification": "PASS",
+ "evidence": [
+ {
+ "status": "PASS",
+ "testTitle": "真实变换手柄移动旋转缩放、吸附与保存重开",
+ "recordedAt": "2026-09-06T10:29:55.869Z",
+ "kind": "真实UI手柄+只读几何投影定位",
+ "ids": [
+ "transform.translate",
+ "transform.rotate",
+ "transform.scale",
+ "transform.keys",
+ "transform.snap"
+ ],
+ "file": "evidence/viewport-transform.json",
+ "title": "真实变换手柄移动旋转缩放、吸附与保存重开",
+ "details": "",
+ "screenshots": [
+ "43-real-transform-gizmo"
+ ]
+ },
+ {
+ "status": "PASS",
+ "testTitle": "gizmo scaling across its pivot follows the same strictly positive scale contract as inspector edits",
+ "kind": "unit",
+ "runId": "scene-editor-2026-09-06T10:24:04.121Z",
+ "ids": [
+ "transform.scale",
+ "property.scale"
+ ],
+ "file": "validation.json",
+ "title": "gizmo scaling across its pivot follows the same strictly positive scale contract as inspector edits",
+ "details": "实际执行真实方法/纯函数合同,未冒称浏览器操作",
+ "screenshots": []
+ }
+ ]
+ },
+ {
+ "id": "transform.keys",
+ "group": "三维视口、选择与变换",
+ "name": "W/E/R 快捷键",
+ "source": {
+ "file": "../../../unreal_tran_web/src/features/guide-legacy/demo-src/scene-editor.js",
+ "symbol": "handleKeyboard(event)",
+ "line": 469
+ },
+ "implementation": "本轮修复已完成验证",
+ "boundary": "键盘焦点在表单时不切工具,失活缓存页不能抢键。",
+ "acceptance": "视口按键切工具;名称输入包含 w/e/r 正常;只读不改。",
+ "fix": "预览状态同步全部操作禁用,Delete/W/E/R 和表单输入遵守预览/权限;失活缓存页忽略全局快捷键,输入框撤销保持文本语义。",
+ "fixIds": [
+ "SCENE-F05"
+ ],
+ "verification": "PASS",
+ "evidence": [
+ {
+ "status": "PASS",
+ "testTitle": "真实变换手柄移动旋转缩放、吸附与保存重开",
+ "recordedAt": "2026-09-06T10:29:55.869Z",
+ "kind": "真实UI手柄+只读几何投影定位",
+ "ids": [
+ "transform.translate",
+ "transform.rotate",
+ "transform.scale",
+ "transform.keys",
+ "transform.snap"
+ ],
+ "file": "evidence/viewport-transform.json",
+ "title": "真实变换手柄移动旋转缩放、吸附与保存重开",
+ "details": "",
+ "screenshots": [
+ "43-real-transform-gizmo"
+ ]
+ },
+ {
+ "status": "PASS",
+ "testTitle": "typing undo/redo stays inside text controls and inactive cached editors ignore global shortcuts",
+ "kind": "unit",
+ "runId": "scene-editor-2026-09-06T10:24:04.121Z",
+ "ids": [
+ "history.undo",
+ "history.redo",
+ "transform.keys",
+ "lifecycle.cache"
+ ],
+ "file": "validation.json",
+ "title": "typing undo/redo stays inside text controls and inactive cached editors ignore global shortcuts",
+ "details": "实际执行真实方法/纯函数合同,未冒称浏览器操作",
+ "screenshots": []
+ }
+ ]
+ },
+ {
+ "id": "transform.snap",
+ "group": "三维视口、选择与变换",
+ "name": "吸附 0.5m 与旋转 15° 开关",
+ "source": {
+ "file": "../../../unreal_tran_web/src/features/guide-legacy/demo-src/scene-editor.js",
+ "symbol": "toggleSnap()",
+ "line": 581
+ },
+ "implementation": "已有实现已完成验证",
+ "boundary": "开关同时控制拖放/变换吸附;当前视图工具参数与业务值区分。",
+ "acceptance": "开/关实际拖动位置、旋转角度,按钮/页脚状态一致。",
+ "fix": "尚未关联本轮修复记录",
+ "fixIds": [],
+ "verification": "PASS",
+ "evidence": [
+ {
+ "status": "PASS",
+ "testTitle": "真实变换手柄移动旋转缩放、吸附与保存重开",
+ "recordedAt": "2026-09-06T10:29:55.869Z",
+ "kind": "真实UI手柄+只读几何投影定位",
+ "ids": [
+ "transform.translate",
+ "transform.rotate",
+ "transform.scale",
+ "transform.keys",
+ "transform.snap"
+ ],
+ "file": "evidence/viewport-transform.json",
+ "title": "真实变换手柄移动旋转缩放、吸附与保存重开",
+ "details": "",
+ "screenshots": [
+ "43-real-transform-gizmo"
+ ]
+ }
+ ]
+ },
+ {
+ "id": "property.name",
+ "group": "对象属性、部件与底部面板",
+ "name": "显示名称",
+ "source": {
+ "file": "../../../unreal_tran_web/src/features/guide-legacy/demo-src/scene-editor.js",
+ "symbol": "handleInput(event)",
+ "line": 437
+ },
+ "implementation": "本轮修复已完成验证",
+ "boundary": "对象/部件 displayName 与项目名称分开。",
+ "acceptance": "修改名称,树/对象条/属性同步,保存重开保留。",
+ "fix": "恢复显示名、设备和物模型编号使用空值保留规则,不把显式空串补回旧值;新资源序号跳过既有编号,空白编号校验按 trim 处理。",
+ "fixIds": [
+ "SCENE-F06"
+ ],
+ "verification": "PASS",
+ "evidence": [
+ {
+ "status": "PASS",
+ "testTitle": "狐狸:真实细分权限、全部属性、清空绑定、蒙皮保护、相机与封面保存重开",
+ "recordedAt": "2026-09-06T10:30:14.385Z",
+ "kind": "browser",
+ "ids": [
+ "project.load",
+ "project.title",
+ "project.save",
+ "selection.tree",
+ "property.name",
+ "property.visible",
+ "property.operable",
+ "property.position",
+ "property.rotation",
+ "property.scale",
+ "property.device-id",
+ "property.model-id",
+ "property.semantic",
+ "part.source",
+ "panel.binding",
+ "cover.capture",
+ "cover.atomic"
+ ],
+ "file": "evidence/fox-properties-cover.json",
+ "title": "狐狸:真实细分权限、全部属性、清空绑定、蒙皮保护、相机与封面保存重开",
+ "details": "真实字段输入与清空、六个语义选项、蒙皮保护、源模型可见,正文/资产/封面保存和刷新;不把原件旧模板组合当新模板结果。",
+ "screenshots": []
+ },
+ {
+ "status": "PASS",
+ "testTitle": "牛奶卡车:刚性部件独立变换、软删除、历史与保存基线一致",
+ "recordedAt": "2026-09-06T10:30:32.596Z",
+ "kind": "browser",
+ "ids": [
+ "project.save-key",
+ "part.transform",
+ "part.soft-delete",
+ "history.undo",
+ "history.redo",
+ "property.name",
+ "view.enclosure-full",
+ "view.enclosure-xray",
+ "view.enclosure-hidden"
+ ],
+ "file": "evidence/truck-part-history.json",
+ "title": "牛奶卡车:刚性部件独立变换、软删除、历史与保存基线一致",
+ "details": "实际部件变换和软删除、按钮/键盘历史、保存基线与输入框自身撤销;双实例隔离及车库围护三种模式真实点击、导出和隐藏后保存刷新。",
+ "screenshots": []
+ }
+ ]
+ },
+ {
+ "id": "property.visible",
+ "group": "对象属性、部件与底部面板",
+ "name": "在场景中可见",
+ "source": {
+ "file": "../../../unreal_tran_web/src/features/guide-legacy/demo-src/scene-editor.js",
+ "symbol": "handleChange(event)",
+ "line": 438
+ },
+ "implementation": "已有实现已完成验证",
+ "boundary": "实例显隐可保存;受保护蒙皮部件不得独立隐藏。",
+ "acceptance": "开/关可见性、恢复、保存重开,视口与树状态准确。",
+ "fix": "尚未关联本轮修复记录",
+ "fixIds": [],
+ "verification": "PASS",
+ "evidence": [
+ {
+ "status": "PASS",
+ "testTitle": "狐狸:真实细分权限、全部属性、清空绑定、蒙皮保护、相机与封面保存重开",
+ "recordedAt": "2026-09-06T10:30:14.385Z",
+ "kind": "browser",
+ "ids": [
+ "project.load",
+ "project.title",
+ "project.save",
+ "selection.tree",
+ "property.name",
+ "property.visible",
+ "property.operable",
+ "property.position",
+ "property.rotation",
+ "property.scale",
+ "property.device-id",
+ "property.model-id",
+ "property.semantic",
+ "part.source",
+ "panel.binding",
+ "cover.capture",
+ "cover.atomic"
+ ],
+ "file": "evidence/fox-properties-cover.json",
+ "title": "狐狸:真实细分权限、全部属性、清空绑定、蒙皮保护、相机与封面保存重开",
+ "details": "真实字段输入与清空、六个语义选项、蒙皮保护、源模型可见,正文/资产/封面保存和刷新;不把原件旧模板组合当新模板结果。",
+ "screenshots": []
+ },
+ {
+ "status": "PASS",
+ "testTitle": "模型成果狐狸卡车双击与真实拖入、树搜索和部件隐藏重开",
+ "recordedAt": "2026-09-06T10:29:25.167Z",
+ "kind": "e2e",
+ "ids": [
+ "assets.catalog",
+ "assets.model-double-click",
+ "assets.model-drop",
+ "search.tree",
+ "part.source",
+ "property.visible"
+ ],
+ "file": "evidence/published-model-assets.json",
+ "title": "模型成果狐狸卡车双击与真实拖入、树搜索和部件隐藏重开",
+ "details": "API 已发布目录,狐狸双击和卡车真实拖入;固定来源版本、树根/部件搜索;卡车刚体部件隐藏保存重开,最后恢复可见。基线树没有折叠按钮,不将搜索伪称折叠。",
+ "screenshots": [
+ "33-fox-placed-visible.png",
+ "34-truck-placed-part-restored.png"
+ ]
+ }
+ ]
+ },
+ {
+ "id": "property.operable",
+ "group": "对象属性、部件与底部面板",
+ "name": "可作为实训目标",
+ "source": {
+ "file": "../../../unreal_tran_web/src/features/guide-legacy/demo-src/scene-editor.js",
+ "symbol": "handleChange(event)",
+ "line": 438
+ },
+ "implementation": "已有实现已完成验证",
+ "boundary": "operable/capabilities 进入场景定义;不能扩大平台操作权限。",
+ "acceptance": "开/关并保存重开,发布数据的目标能力一致。",
+ "fix": "尚未关联本轮修复记录",
+ "fixIds": [],
+ "verification": "PASS",
+ "evidence": [
+ {
+ "status": "PASS",
+ "testTitle": "狐狸:真实细分权限、全部属性、清空绑定、蒙皮保护、相机与封面保存重开",
+ "recordedAt": "2026-09-06T10:30:14.385Z",
+ "kind": "browser",
+ "ids": [
+ "project.load",
+ "project.title",
+ "project.save",
+ "selection.tree",
+ "property.name",
+ "property.visible",
+ "property.operable",
+ "property.position",
+ "property.rotation",
+ "property.scale",
+ "property.device-id",
+ "property.model-id",
+ "property.semantic",
+ "part.source",
+ "panel.binding",
+ "cover.capture",
+ "cover.atomic"
+ ],
+ "file": "evidence/fox-properties-cover.json",
+ "title": "狐狸:真实细分权限、全部属性、清空绑定、蒙皮保护、相机与封面保存重开",
+ "details": "真实字段输入与清空、六个语义选项、蒙皮保护、源模型可见,正文/资产/封面保存和刷新;不把原件旧模板组合当新模板结果。",
+ "screenshots": []
+ }
+ ]
+ },
+ {
+ "id": "property.position",
+ "group": "对象属性、部件与底部面板",
+ "name": "位置 X/Y/Z 数值",
+ "source": {
+ "file": "../../../unreal_tran_web/src/features/guide-legacy/demo-src/scene-editor.js",
+ "symbol": "handleInput(event)",
+ "line": 437
+ },
+ "implementation": "已有实现已完成验证",
+ "boundary": "三个输入回写局部 position;不使用纯展示假字段。",
+ "acceptance": "三个轴独立输入与手柄联动、失焦/history/重开准确。",
+ "fix": "尚未关联本轮修复记录",
+ "fixIds": [],
+ "verification": "PASS",
+ "evidence": [
+ {
+ "status": "PASS",
+ "testTitle": "狐狸:真实细分权限、全部属性、清空绑定、蒙皮保护、相机与封面保存重开",
+ "recordedAt": "2026-09-06T10:30:14.385Z",
+ "kind": "browser",
+ "ids": [
+ "project.load",
+ "project.title",
+ "project.save",
+ "selection.tree",
+ "property.name",
+ "property.visible",
+ "property.operable",
+ "property.position",
+ "property.rotation",
+ "property.scale",
+ "property.device-id",
+ "property.model-id",
+ "property.semantic",
+ "part.source",
+ "panel.binding",
+ "cover.capture",
+ "cover.atomic"
+ ],
+ "file": "evidence/fox-properties-cover.json",
+ "title": "狐狸:真实细分权限、全部属性、清空绑定、蒙皮保护、相机与封面保存重开",
+ "details": "真实字段输入与清空、六个语义选项、蒙皮保护、源模型可见,正文/资产/封面保存和刷新;不把原件旧模板组合当新模板结果。",
+ "screenshots": []
+ }
+ ]
+ },
+ {
+ "id": "property.rotation",
+ "group": "对象属性、部件与底部面板",
+ "name": "旋转 X/Y/Z 数值",
+ "source": {
+ "file": "../../../unreal_tran_web/src/features/guide-legacy/demo-src/scene-editor.js",
+ "symbol": "handleInput(event)",
+ "line": 437
+ },
+ "implementation": "已有实现已完成验证",
+ "boundary": "UI 为角度,文档为弧度。",
+ "acceptance": "非整角/负角输入、实际姿态与 JSON/重开一致。",
+ "fix": "尚未关联本轮修复记录",
+ "fixIds": [],
+ "verification": "PASS",
+ "evidence": [
+ {
+ "status": "PASS",
+ "testTitle": "狐狸:真实细分权限、全部属性、清空绑定、蒙皮保护、相机与封面保存重开",
+ "recordedAt": "2026-09-06T10:30:14.385Z",
+ "kind": "browser",
+ "ids": [
+ "project.load",
+ "project.title",
+ "project.save",
+ "selection.tree",
+ "property.name",
+ "property.visible",
+ "property.operable",
+ "property.position",
+ "property.rotation",
+ "property.scale",
+ "property.device-id",
+ "property.model-id",
+ "property.semantic",
+ "part.source",
+ "panel.binding",
+ "cover.capture",
+ "cover.atomic"
+ ],
+ "file": "evidence/fox-properties-cover.json",
+ "title": "狐狸:真实细分权限、全部属性、清空绑定、蒙皮保护、相机与封面保存重开",
+ "details": "真实字段输入与清空、六个语义选项、蒙皮保护、源模型可见,正文/资产/封面保存和刷新;不把原件旧模板组合当新模板结果。",
+ "screenshots": []
+ }
+ ]
+ },
+ {
+ "id": "property.scale",
+ "group": "对象属性、部件与底部面板",
+ "name": "缩放 X/Y/Z 数值及有效值",
+ "source": {
+ "file": "../../../unreal_tran_web/src/features/scene-legacy/modelPlacement.ts",
+ "symbol": "parseSceneScale",
+ "line": 36
+ },
+ "implementation": "本轮修复已完成验证",
+ "boundary": "支持小数归一化 scale,避免小值格式化成零;非法值不得破坏模型。",
+ "acceptance": "小正数、负数/零/清空按合同处理,三轴保存往返正常。",
+ "fix": "实际手柄跨越枢轴时按既有严格正尺度合同限制,避免产生负或零尺度导致模型翻转或消失;未新增镜像建模功能。",
+ "fixIds": [
+ "SCENE-F17"
+ ],
+ "verification": "PASS",
+ "evidence": [
+ {
+ "status": "PASS",
+ "testTitle": "狐狸:真实细分权限、全部属性、清空绑定、蒙皮保护、相机与封面保存重开",
+ "recordedAt": "2026-09-06T10:30:14.385Z",
+ "kind": "browser",
+ "ids": [
+ "project.load",
+ "project.title",
+ "project.save",
+ "selection.tree",
+ "property.name",
+ "property.visible",
+ "property.operable",
+ "property.position",
+ "property.rotation",
+ "property.scale",
+ "property.device-id",
+ "property.model-id",
+ "property.semantic",
+ "part.source",
+ "panel.binding",
+ "cover.capture",
+ "cover.atomic"
+ ],
+ "file": "evidence/fox-properties-cover.json",
+ "title": "狐狸:真实细分权限、全部属性、清空绑定、蒙皮保护、相机与封面保存重开",
+ "details": "真实字段输入与清空、六个语义选项、蒙皮保护、源模型可见,正文/资产/封面保存和刷新;不把原件旧模板组合当新模板结果。",
+ "screenshots": []
+ },
+ {
+ "status": "PASS",
+ "testTitle": "gizmo scaling across its pivot follows the same strictly positive scale contract as inspector edits",
+ "kind": "unit",
+ "runId": "scene-editor-2026-09-06T10:24:04.121Z",
+ "ids": [
+ "transform.scale",
+ "property.scale"
+ ],
+ "file": "validation.json",
+ "title": "gizmo scaling across its pivot follows the same strictly positive scale contract as inspector edits",
+ "details": "实际执行真实方法/纯函数合同,未冒称浏览器操作",
+ "screenshots": []
+ },
+ {
+ "status": "PASS",
+ "testTitle": "scale input accepts real GLTF normalization factors below 0.05",
+ "kind": "unit",
+ "runId": "scene-editor-2026-09-06T10:24:04.121Z",
+ "ids": [
+ "property.scale"
+ ],
+ "file": "validation.json",
+ "title": "scale input accepts real GLTF normalization factors below 0.05",
+ "details": "实际执行真实方法/纯函数合同,未冒称浏览器操作",
+ "screenshots": []
+ }
+ ]
+ },
+ {
+ "id": "property.device-id",
+ "group": "对象属性、部件与底部面板",
+ "name": "设备编号",
+ "source": {
+ "file": "../../../unreal_tran_web/src/features/guide-legacy/demo-src/scene-editor.js",
+ "symbol": "handleInput(event)",
+ "line": 437
+ },
+ "implementation": "本轮修复已完成验证",
+ "boundary": "根对象绑定字符串;重复/空值由预检与后端核查。",
+ "acceptance": "修改后绑定表同步、保存重开,重复值能被阻断。",
+ "fix": "恢复显示名、设备和物模型编号使用空值保留规则,不把显式空串补回旧值;新资源序号跳过既有编号,空白编号校验按 trim 处理。",
+ "fixIds": [
+ "SCENE-F06"
+ ],
+ "verification": "PASS",
+ "evidence": [
+ {
+ "status": "PASS",
+ "testTitle": "狐狸:真实细分权限、全部属性、清空绑定、蒙皮保护、相机与封面保存重开",
+ "recordedAt": "2026-09-06T10:30:14.385Z",
+ "kind": "browser",
+ "ids": [
+ "project.load",
+ "project.title",
+ "project.save",
+ "selection.tree",
+ "property.name",
+ "property.visible",
+ "property.operable",
+ "property.position",
+ "property.rotation",
+ "property.scale",
+ "property.device-id",
+ "property.model-id",
+ "property.semantic",
+ "part.source",
+ "panel.binding",
+ "cover.capture",
+ "cover.atomic"
+ ],
+ "file": "evidence/fox-properties-cover.json",
+ "title": "狐狸:真实细分权限、全部属性、清空绑定、蒙皮保护、相机与封面保存重开",
+ "details": "真实字段输入与清空、六个语义选项、蒙皮保护、源模型可见,正文/资产/封面保存和刷新;不把原件旧模板组合当新模板结果。",
+ "screenshots": []
+ }
+ ]
+ },
+ {
+ "id": "property.model-id",
+ "group": "对象属性、部件与底部面板",
+ "name": "物模型编号",
+ "source": {
+ "file": "../../../unreal_tran_web/src/features/guide-legacy/demo-src/scene-editor.js",
+ "symbol": "handleInput(event)",
+ "line": 437
+ },
+ "implementation": "本轮修复已完成验证",
+ "boundary": "与受控 assetCode、来源项目 ID 分开。",
+ "acceptance": "修改后只改业务编号,源 GLB 仍加载正确。",
+ "fix": "恢复显示名、设备和物模型编号使用空值保留规则,不把显式空串补回旧值;新资源序号跳过既有编号,空白编号校验按 trim 处理。",
+ "fixIds": [
+ "SCENE-F06"
+ ],
+ "verification": "PASS",
+ "evidence": [
+ {
+ "status": "PASS",
+ "testTitle": "狐狸:真实细分权限、全部属性、清空绑定、蒙皮保护、相机与封面保存重开",
+ "recordedAt": "2026-09-06T10:30:14.385Z",
+ "kind": "browser",
+ "ids": [
+ "project.load",
+ "project.title",
+ "project.save",
+ "selection.tree",
+ "property.name",
+ "property.visible",
+ "property.operable",
+ "property.position",
+ "property.rotation",
+ "property.scale",
+ "property.device-id",
+ "property.model-id",
+ "property.semantic",
+ "part.source",
+ "panel.binding",
+ "cover.capture",
+ "cover.atomic"
+ ],
+ "file": "evidence/fox-properties-cover.json",
+ "title": "狐狸:真实细分权限、全部属性、清空绑定、蒙皮保护、相机与封面保存重开",
+ "details": "真实字段输入与清空、六个语义选项、蒙皮保护、源模型可见,正文/资产/封面保存和刷新;不把原件旧模板组合当新模板结果。",
+ "screenshots": []
+ }
+ ]
+ },
+ {
+ "id": "property.semantic",
+ "group": "对象属性、部件与底部面板",
+ "name": "语义类型六个选项",
+ "source": {
+ "file": "../../../unreal_tran_web/src/features/guide-legacy/demo-src/scene-editor.js",
+ "symbol": "handleChange(event)",
+ "line": 438
+ },
+ "implementation": "已有实现已完成验证",
+ "boundary": "实训装备/工位设施/安全区域/采集设备/训练人员/环境设施。",
+ "acceptance": "逐项可选且保存重开保留;绑定表显示实际类型。",
+ "fix": "尚未关联本轮修复记录",
+ "fixIds": [],
+ "verification": "PASS",
+ "evidence": [
+ {
+ "status": "PASS",
+ "testTitle": "狐狸:真实细分权限、全部属性、清空绑定、蒙皮保护、相机与封面保存重开",
+ "recordedAt": "2026-09-06T10:30:14.385Z",
+ "kind": "browser",
+ "ids": [
+ "project.load",
+ "project.title",
+ "project.save",
+ "selection.tree",
+ "property.name",
+ "property.visible",
+ "property.operable",
+ "property.position",
+ "property.rotation",
+ "property.scale",
+ "property.device-id",
+ "property.model-id",
+ "property.semantic",
+ "part.source",
+ "panel.binding",
+ "cover.capture",
+ "cover.atomic"
+ ],
+ "file": "evidence/fox-properties-cover.json",
+ "title": "狐狸:真实细分权限、全部属性、清空绑定、蒙皮保护、相机与封面保存重开",
+ "details": "真实字段输入与清空、六个语义选项、蒙皮保护、源模型可见,正文/资产/封面保存和刷新;不把原件旧模板组合当新模板结果。",
+ "screenshots": []
+ }
+ ]
+ },
+ {
+ "id": "part.source",
+ "group": "对象属性、部件与底部面板",
+ "name": "模型来源与部件权限说明",
+ "source": {
+ "file": "../../../unreal_tran_web/src/features/guide-legacy/demo-src/scene-editor.js",
+ "symbol": "renderInspector()",
+ "line": 1520
+ },
+ "implementation": "本轮修复已完成验证",
+ "boundary": "固定模型版本/源节点;蒙皮仅查看高亮,非骨骼独立变换。",
+ "acceptance": "狐狸蒙皮与卡车刚性部件说明/禁用态准确。",
+ "fix": "模型来源按钮有来源权限时导航正确项目;无来源页面权限回到可访问列表,不将模型列表误称为来源工程编辑。",
+ "fixIds": [
+ "SCENE-F11"
+ ],
+ "verification": "PASS",
+ "evidence": [
+ {
+ "status": "PASS",
+ "testTitle": "狐狸:真实细分权限、全部属性、清空绑定、蒙皮保护、相机与封面保存重开",
+ "recordedAt": "2026-09-06T10:30:14.385Z",
+ "kind": "browser",
+ "ids": [
+ "project.load",
+ "project.title",
+ "project.save",
+ "selection.tree",
+ "property.name",
+ "property.visible",
+ "property.operable",
+ "property.position",
+ "property.rotation",
+ "property.scale",
+ "property.device-id",
+ "property.model-id",
+ "property.semantic",
+ "part.source",
+ "panel.binding",
+ "cover.capture",
+ "cover.atomic"
+ ],
+ "file": "evidence/fox-properties-cover.json",
+ "title": "狐狸:真实细分权限、全部属性、清空绑定、蒙皮保护、相机与封面保存重开",
+ "details": "真实字段输入与清空、六个语义选项、蒙皮保护、源模型可见,正文/资产/封面保存和刷新;不把原件旧模板组合当新模板结果。",
+ "screenshots": []
+ },
+ {
+ "status": "PASS",
+ "testTitle": "模型成果狐狸卡车双击与真实拖入、树搜索和部件隐藏重开",
+ "recordedAt": "2026-09-06T10:29:25.167Z",
+ "kind": "e2e",
+ "ids": [
+ "assets.catalog",
+ "assets.model-double-click",
+ "assets.model-drop",
+ "search.tree",
+ "part.source",
+ "property.visible"
+ ],
+ "file": "evidence/published-model-assets.json",
+ "title": "模型成果狐狸卡车双击与真实拖入、树搜索和部件隐藏重开",
+ "details": "API 已发布目录,狐狸双击和卡车真实拖入;固定来源版本、树根/部件搜索;卡车刚体部件隐藏保存重开,最后恢复可见。基线树没有折叠按钮,不将搜索伪称折叠。",
+ "screenshots": [
+ "33-fox-placed-visible.png",
+ "34-truck-placed-part-restored.png"
+ ]
+ }
+ ]
+ },
+ {
+ "id": "part.transform",
+ "group": "对象属性、部件与底部面板",
+ "name": "刚性部件独立变换并保存覆盖",
+ "source": {
+ "file": "../../../unreal_tran_web/src/features/guide-legacy/demo-src/scene-editor.js",
+ "symbol": "captureModelPartOverrides(root)",
+ "line": 1123
+ },
+ "implementation": "本轮修复已完成验证",
+ "boundary": "partOverrides 与稳定 interactionId;多个实例互不串改。",
+ "acceptance": "两卡车实例改单部件,保存重开另一实例不受影响。",
+ "fix": "新实例部件 sceneId 包含所属根对象身份,不再直接复用来源模型部件 ID;源 interaction/editor 标识保留。旧单实例 ID 保留,旧文档重复 ID 仅按文档顺序迁移后续冲突实例并同步 override。第二完整轮进一步发现 GLB 并发完成顺序会改变根对象顺序;现等待 allSettled 成功后按原文档顺序排列恢复根对象,拆分模板返回数组内部顺序和既有对象均保留,同源两实例不再随网速互换列表位置。新增延迟 30/1/10ms 乱序加载合同,验证序列化仍按文档顺序;最终浏览器验证独立记录。",
+ "fixIds": [
+ "SCENE-F18"
+ ],
+ "verification": "PASS",
+ "evidence": [
+ {
+ "status": "PASS",
+ "testTitle": "牛奶卡车:刚性部件独立变换、软删除、历史与保存基线一致",
+ "recordedAt": "2026-09-06T10:30:32.596Z",
+ "kind": "browser",
+ "ids": [
+ "project.save-key",
+ "part.transform",
+ "part.soft-delete",
+ "history.undo",
+ "history.redo",
+ "property.name",
+ "view.enclosure-full",
+ "view.enclosure-xray",
+ "view.enclosure-hidden"
+ ],
+ "file": "evidence/truck-part-history.json",
+ "title": "牛奶卡车:刚性部件独立变换、软删除、历史与保存基线一致",
+ "details": "实际部件变换和软删除、按钮/键盘历史、保存基线与输入框自身撤销;双实例隔离及车库围护三种模式真实点击、导出和隐藏后保存刷新。",
+ "screenshots": []
+ },
+ {
+ "status": "PASS",
+ "testTitle": "two published instances have unique tree part IDs and editing the second persists independently after reopen",
+ "kind": "unit",
+ "runId": "scene-editor-2026-09-06T10:24:04.121Z",
+ "ids": [
+ "part.transform",
+ "selection.tree"
+ ],
+ "file": "validation.json",
+ "title": "two published instances have unique tree part IDs and editing the second persists independently after reopen",
+ "details": "实际执行真实方法/纯函数合同,未冒称浏览器操作",
+ "screenshots": []
+ },
+ {
+ "status": "PASS",
+ "testTitle": "legacy duplicate instance IDs migrate only later owners in document order despite reversed GLB completion",
+ "kind": "unit",
+ "runId": "scene-editor-2026-09-06T10:24:04.121Z",
+ "ids": [
+ "part.transform",
+ "publish.fixed-model"
+ ],
+ "file": "validation.json",
+ "title": "legacy duplicate instance IDs migrate only later owners in document order despite reversed GLB completion",
+ "details": "实际执行真实方法/纯函数合同,未冒称浏览器操作",
+ "screenshots": []
+ }
+ ]
+ },
+ {
+ "id": "part.soft-delete",
+ "group": "对象属性、部件与底部面板",
+ "name": "部件软删除与恢复",
+ "source": {
+ "file": "../../../unreal_tran_web/src/features/guide-legacy/demo-src/scene-editor.js",
+ "symbol": "deleteSelected()",
+ "line": 588
+ },
+ "implementation": "本轮修复已完成验证",
+ "boundary": "被允许的部件 sceneDeleted/visible 持久化;可撤销恢复。",
+ "acceptance": "实际删除、撤销、保存重开均一致;蒙皮不允许破坏骨架。",
+ "fix": "新实例部件 sceneId 包含所属根对象身份,不再直接复用来源模型部件 ID;源 interaction/editor 标识保留。旧单实例 ID 保留,旧文档重复 ID 仅按文档顺序迁移后续冲突实例并同步 override。第二完整轮进一步发现 GLB 并发完成顺序会改变根对象顺序;现等待 allSettled 成功后按原文档顺序排列恢复根对象,拆分模板返回数组内部顺序和既有对象均保留,同源两实例不再随网速互换列表位置。新增延迟 30/1/10ms 乱序加载合同,验证序列化仍按文档顺序;最终浏览器验证独立记录。",
+ "fixIds": [
+ "SCENE-F18"
+ ],
+ "verification": "PASS",
+ "evidence": [
+ {
+ "status": "PASS",
+ "testTitle": "牛奶卡车:刚性部件独立变换、软删除、历史与保存基线一致",
+ "recordedAt": "2026-09-06T10:30:32.596Z",
+ "kind": "browser",
+ "ids": [
+ "project.save-key",
+ "part.transform",
+ "part.soft-delete",
+ "history.undo",
+ "history.redo",
+ "property.name",
+ "view.enclosure-full",
+ "view.enclosure-xray",
+ "view.enclosure-hidden"
+ ],
+ "file": "evidence/truck-part-history.json",
+ "title": "牛奶卡车:刚性部件独立变换、软删除、历史与保存基线一致",
+ "details": "实际部件变换和软删除、按钮/键盘历史、保存基线与输入框自身撤销;双实例隔离及车库围护三种模式真实点击、导出和隐藏后保存刷新。",
+ "screenshots": []
+ }
+ ]
+ },
+ {
+ "id": "object.delete",
+ "group": "对象属性、部件与底部面板",
+ "name": "删除根对象按钮 / Delete",
+ "source": {
+ "file": "../../../unreal_tran_web/src/features/guide-legacy/demo-src/scene-editor.js",
+ "symbol": "deleteSelected()",
+ "line": 588
+ },
+ "implementation": "本轮修复已完成验证",
+ "boundary": "移除当前场景实例,不删除源模型项目/固定发布版本。",
+ "acceptance": "按钮和键盘各验证;源项目不变,撤销和重开正确。",
+ "fix": "射线候选逐级检查父级可见性和软删除;删除根对象先恢复高亮材质再释放几何/材质,避免错误拾取及 GPU 资源残留。",
+ "fixIds": [
+ "SCENE-F09"
+ ],
+ "verification": "PASS",
+ "evidence": [
+ {
+ "status": "PASS",
+ "testTitle": "两种整体场地加载、重复放置阻止与显式删除后替换",
+ "recordedAt": "2026-09-06T10:29:06.315Z",
+ "kind": "e2e",
+ "ids": [
+ "asset.vehicle-depot",
+ "asset.field-terrain",
+ "object.delete"
+ ],
+ "file": "evidence/venue-assets.json",
+ "title": "两种整体场地加载、重复放置阻止与显式删除后替换",
+ "details": "车库和起伏地形真实加载并分别保存重开;已有整体场地时阻止叠加,显式删除旧场地后才能替换。",
+ "screenshots": [
+ "31-venue-depot-reopened.png",
+ "32-venue-terrain-reopened.png"
+ ]
+ },
+ {
+ "status": "PASS",
+ "testTitle": "deleting a root disposes its geometry and materials after clearing selection",
+ "kind": "unit",
+ "runId": "scene-editor-2026-09-06T10:24:04.121Z",
+ "ids": [
+ "object.delete",
+ "lifecycle.destroy"
+ ],
+ "file": "validation.json",
+ "title": "deleting a root disposes its geometry and materials after clearing selection",
+ "details": "实际执行真实方法/纯函数合同,未冒称浏览器操作",
+ "screenshots": []
+ }
+ ]
+ },
+ {
+ "id": "panel.objects",
+ "group": "对象属性、部件与底部面板",
+ "name": "底部对象列表与选中",
+ "source": {
+ "file": "../../../unreal_tran_web/src/features/guide-legacy/demo-src/scene-editor.js",
+ "symbol": "renderBottom(mode",
+ "line": 1619
+ },
+ "implementation": "本轮修复已完成验证",
+ "boundary": "基线显示前 6 个摘要,完整对象仍在大纲;需明确截断。",
+ "acceptance": "大于 6 个对象时列表说明准确,摘要点击选择正确。",
+ "fix": "设备绑定显示真实语义与未设置;校验按钮进入当前检查面板并随绑定变更刷新;日志记录本次运行实际操作,错误详情保持瞬时提示;摘要超过六项明确完整列表位置。",
+ "fixIds": [
+ "SCENE-F10"
+ ],
+ "verification": "PASS",
+ "evidence": [
+ {
+ "status": "PASS",
+ "testTitle": "八种基础资源逐项放置、真实拖放、搜索和保存重开",
+ "recordedAt": "2026-09-06T10:28:51.941Z",
+ "kind": "e2e",
+ "ids": [
+ "asset.diagnostic",
+ "asset.equipment",
+ "asset.workbench",
+ "asset.shelf",
+ "asset.safety-zone",
+ "asset.camera",
+ "asset.worker",
+ "asset.light",
+ "assets.basic-double-click",
+ "assets.basic-drop",
+ "search.assets",
+ "panel.objects"
+ ],
+ "file": "evidence/basic-assets.json",
+ "title": "八种基础资源逐项放置、真实拖放、搜索和保存重开",
+ "details": "8 种基础资源分别双击放置并分开布置;真实指针拖入第 9 个工作台、吸附检查、唯一编号、保存刷新一致。照明为现有资源外观,不冒称连接真实灯控。",
+ "screenshots": [
+ "30-eight-basic-assets-reopened.png"
+ ]
+ }
+ ]
+ },
+ {
+ "id": "panel.binding",
+ "group": "对象属性、部件与底部面板",
+ "name": "设备绑定总表",
+ "source": {
+ "file": "../../../unreal_tran_web/src/features/guide-legacy/demo-src/scene-editor.js",
+ "symbol": "renderBottom(mode",
+ "line": 1619
+ },
+ "implementation": "本轮修复已完成验证",
+ "boundary": "基线语义列恒写已绑定;需要真实类型/缺失/冲突反馈。",
+ "acceptance": "编辑字段即时同步,空/重复数据不得假标已绑定。",
+ "fix": "设备绑定显示真实语义与未设置;校验按钮进入当前检查面板并随绑定变更刷新;日志记录本次运行实际操作,错误详情保持瞬时提示;摘要超过六项明确完整列表位置。",
+ "fixIds": [
+ "SCENE-F10"
+ ],
+ "verification": "PASS",
+ "evidence": [
+ {
+ "status": "PASS",
+ "testTitle": "狐狸:真实细分权限、全部属性、清空绑定、蒙皮保护、相机与封面保存重开",
+ "recordedAt": "2026-09-06T10:30:14.385Z",
+ "kind": "browser",
+ "ids": [
+ "project.load",
+ "project.title",
+ "project.save",
+ "selection.tree",
+ "property.name",
+ "property.visible",
+ "property.operable",
+ "property.position",
+ "property.rotation",
+ "property.scale",
+ "property.device-id",
+ "property.model-id",
+ "property.semantic",
+ "part.source",
+ "panel.binding",
+ "cover.capture",
+ "cover.atomic"
+ ],
+ "file": "evidence/fox-properties-cover.json",
+ "title": "狐狸:真实细分权限、全部属性、清空绑定、蒙皮保护、相机与封面保存重开",
+ "details": "真实字段输入与清空、六个语义选项、蒙皮保护、源模型可见,正文/资产/封面保存和刷新;不把原件旧模板组合当新模板结果。",
+ "screenshots": []
+ }
+ ]
+ },
+ {
+ "id": "panel.validation",
+ "group": "对象属性、部件与底部面板",
+ "name": "底部发布检查面板",
+ "source": {
+ "file": "../../../unreal_tran_web/src/features/guide-legacy/demo-src/scene-editor.js",
+ "symbol": "renderBottom(mode",
+ "line": 1619
+ },
+ "implementation": "本轮修复已完成验证",
+ "boundary": "显示当前逐项检查,不等于已发布。",
+ "acceptance": "顶部校验/发布失败可定位检查面板,内容随修改更新。",
+ "fix": "设备绑定显示真实语义与未设置;校验按钮进入当前检查面板并随绑定变更刷新;日志记录本次运行实际操作,错误详情保持瞬时提示;摘要超过六项明确完整列表位置。",
+ "fixIds": [
+ "SCENE-F10"
+ ],
+ "verification": "PASS",
+ "evidence": [
+ {
+ "status": "PASS",
+ "testTitle": "发布:前端绑定校验、保存与发布失败阻断、真实固定版本供训练目录使用",
+ "recordedAt": "2026-09-06T10:27:04.782Z",
+ "kind": "browser",
+ "ids": [
+ "validation.complete",
+ "validation.bindings",
+ "panel.validation",
+ "publish.saved",
+ "publish.failure",
+ "publish.fixed-model",
+ "cover.atomic",
+ "project.back"
+ ],
+ "file": "evidence/publish-fixed-version.json",
+ "title": "发布:前端绑定校验、保存与发布失败阻断、真实固定版本供训练目录使用",
+ "details": "实际校验、绑定阻断、POST503不假发布,重试发布当前固定版本;返回列表保留编辑标签,搜索当前项目并核对真实封面图片加载。下游静态渲染另有独立用例。",
+ "screenshots": []
+ },
+ {
+ "status": "PASS",
+ "testTitle": "verify opens live validation and whitespace binding edits immediately invalidate the active tab",
+ "kind": "unit",
+ "runId": "scene-editor-2026-09-06T10:24:04.121Z",
+ "ids": [
+ "panel.validation",
+ "validation.bindings"
+ ],
+ "file": "validation.json",
+ "title": "verify opens live validation and whitespace binding edits immediately invalidate the active tab",
+ "details": "实际执行真实方法/纯函数合同,未冒称浏览器操作",
+ "screenshots": []
+ }
+ ]
+ },
+ {
+ "id": "panel.logs",
+ "group": "对象属性、部件与底部面板",
+ "name": "操作日志",
+ "source": {
+ "file": "../../../unreal_tran_web/src/features/guide-legacy/demo-src/scene-editor.js",
+ "symbol": "renderBottom(mode",
+ "line": 1619
+ },
+ "implementation": "本轮修复已完成验证",
+ "boundary": "基线为静态示例且误称本地存储;本轮只需真实本次运行日志,不扩展服务端审计。",
+ "acceptance": "实际操作产生对应时间/结果;API 模式不显示本地存储为真值。",
+ "fix": "设备绑定显示真实语义与未设置;校验按钮进入当前检查面板并随绑定变更刷新;日志记录本次运行实际操作,错误详情保持瞬时提示;摘要超过六项明确完整列表位置。",
+ "fixIds": [
+ "SCENE-F10"
+ ],
+ "verification": "PASS",
+ "evidence": [
+ {
+ "status": "PASS",
+ "testTitle": "activity log reflects actual session events and keeps API error details transient",
+ "kind": "unit",
+ "runId": "scene-editor-2026-09-06T10:24:04.121Z",
+ "ids": [
+ "panel.logs"
+ ],
+ "file": "validation.json",
+ "title": "activity log reflects actual session events and keeps API error details transient",
+ "details": "实际执行真实方法/纯函数合同,未冒称浏览器操作",
+ "screenshots": []
+ }
+ ]
+ },
+ {
+ "id": "history.undo",
+ "group": "历史与预览",
+ "name": "回退按钮 / Ctrl+Z",
+ "source": {
+ "file": "../../../unreal_tran_web/src/features/guide-legacy/demo-src/scene-editor.js",
+ "symbol": "async undo()",
+ "line": 2417
+ },
+ "implementation": "本轮修复已完成验证",
+ "boundary": "输入连续修改合并成合理历史步;恢复对象/模板/选择。",
+ "acceptance": "属性、添加、删除、模板分别撤销,真实画面与文档一致。",
+ "fix": "透视按钮实际恢复先前观察位置;导入、加载和历史恢复存在已保存相机时不再被车辆自动聚焦覆盖。;预览状态同步全部操作禁用,Delete/W/E/R 和表单输入遵守预览/权限;失活缓存页忽略全局快捷键,输入框撤销保持文本语义。;文档签名忽略更新时间和对象键顺序;保存后的 undo/redo 根据真实已保存文档判断 dirty;异步恢复失败保留现场并回滚历史索引。",
+ "fixIds": [
+ "SCENE-F04",
+ "SCENE-F05",
+ "SCENE-F07"
+ ],
+ "verification": "PASS",
+ "evidence": [
+ {
+ "status": "PASS",
+ "testTitle": "牛奶卡车:刚性部件独立变换、软删除、历史与保存基线一致",
+ "recordedAt": "2026-09-06T10:30:32.596Z",
+ "kind": "browser",
+ "ids": [
+ "project.save-key",
+ "part.transform",
+ "part.soft-delete",
+ "history.undo",
+ "history.redo",
+ "property.name",
+ "view.enclosure-full",
+ "view.enclosure-xray",
+ "view.enclosure-hidden"
+ ],
+ "file": "evidence/truck-part-history.json",
+ "title": "牛奶卡车:刚性部件独立变换、软删除、历史与保存基线一致",
+ "details": "实际部件变换和软删除、按钮/键盘历史、保存基线与输入框自身撤销;双实例隔离及车库围护三种模式真实点击、导出和隐藏后保存刷新。",
+ "screenshots": []
+ },
+ {
+ "status": "PASS",
+ "testTitle": "typing undo/redo stays inside text controls and inactive cached editors ignore global shortcuts",
+ "kind": "unit",
+ "runId": "scene-editor-2026-09-06T10:24:04.121Z",
+ "ids": [
+ "history.undo",
+ "history.redo",
+ "transform.keys",
+ "lifecycle.cache"
+ ],
+ "file": "validation.json",
+ "title": "typing undo/redo stays inside text controls and inactive cached editors ignore global shortcuts",
+ "details": "实际执行真实方法/纯函数合同,未冒称浏览器操作",
+ "screenshots": []
+ },
+ {
+ "status": "PASS",
+ "testTitle": "loading and history restoration preserve an authored camera rather than auto-focusing a vehicle",
+ "kind": "unit",
+ "runId": "scene-editor-2026-09-06T10:24:04.121Z",
+ "ids": [
+ "json.rendering",
+ "history.undo"
+ ],
+ "file": "validation.json",
+ "title": "loading and history restoration preserve an authored camera rather than auto-focusing a vehicle",
+ "details": "实际执行真实方法/纯函数合同,未冒称浏览器操作",
+ "screenshots": []
+ },
+ {
+ "status": "PASS",
+ "testTitle": "save B then undo A stays dirty, and redo B returns to the actual saved baseline",
+ "kind": "unit",
+ "runId": "scene-editor-2026-09-06T10:24:04.121Z",
+ "ids": [
+ "history.undo",
+ "history.redo"
+ ],
+ "file": "validation.json",
+ "title": "save B then undo A stays dirty, and redo B returns to the actual saved baseline",
+ "details": "实际执行真实方法/纯函数合同,未冒称浏览器操作",
+ "screenshots": []
+ },
+ {
+ "status": "PASS",
+ "testTitle": "default history caps at 60 snapshots, stops at either edge and discards redo only after a new branch",
+ "kind": "unit",
+ "runId": "scene-editor-2026-09-06T10:24:04.121Z",
+ "ids": [
+ "history.capacity",
+ "history.undo",
+ "history.redo"
+ ],
+ "file": "validation.json",
+ "title": "default history caps at 60 snapshots, stops at either edge and discards redo only after a new branch",
+ "details": "实际执行真实方法/纯函数合同,未冒称浏览器操作",
+ "screenshots": []
+ }
+ ]
+ },
+ {
+ "id": "history.redo",
+ "group": "历史与预览",
+ "name": "前进按钮 / Ctrl+Y / Ctrl+Shift+Z",
+ "source": {
+ "file": "../../../unreal_tran_web/src/features/guide-legacy/demo-src/scene-editor.js",
+ "symbol": "async redo()",
+ "line": 2437
+ },
+ "implementation": "本轮修复已完成验证",
+ "boundary": "撤销后重做,分叉编辑清 redo。",
+ "acceptance": "按钮与两种快捷键各执行;新编辑后旧 redo 不可用。",
+ "fix": "预览状态同步全部操作禁用,Delete/W/E/R 和表单输入遵守预览/权限;失活缓存页忽略全局快捷键,输入框撤销保持文本语义。;文档签名忽略更新时间和对象键顺序;保存后的 undo/redo 根据真实已保存文档判断 dirty;异步恢复失败保留现场并回滚历史索引。",
+ "fixIds": [
+ "SCENE-F05",
+ "SCENE-F07"
+ ],
+ "verification": "PASS",
+ "evidence": [
+ {
+ "status": "PASS",
+ "testTitle": "牛奶卡车:刚性部件独立变换、软删除、历史与保存基线一致",
+ "recordedAt": "2026-09-06T10:30:32.596Z",
+ "kind": "browser",
+ "ids": [
+ "project.save-key",
+ "part.transform",
+ "part.soft-delete",
+ "history.undo",
+ "history.redo",
+ "property.name",
+ "view.enclosure-full",
+ "view.enclosure-xray",
+ "view.enclosure-hidden"
+ ],
+ "file": "evidence/truck-part-history.json",
+ "title": "牛奶卡车:刚性部件独立变换、软删除、历史与保存基线一致",
+ "details": "实际部件变换和软删除、按钮/键盘历史、保存基线与输入框自身撤销;双实例隔离及车库围护三种模式真实点击、导出和隐藏后保存刷新。",
+ "screenshots": []
+ },
+ {
+ "status": "PASS",
+ "testTitle": "typing undo/redo stays inside text controls and inactive cached editors ignore global shortcuts",
+ "kind": "unit",
+ "runId": "scene-editor-2026-09-06T10:24:04.121Z",
+ "ids": [
+ "history.undo",
+ "history.redo",
+ "transform.keys",
+ "lifecycle.cache"
+ ],
+ "file": "validation.json",
+ "title": "typing undo/redo stays inside text controls and inactive cached editors ignore global shortcuts",
+ "details": "实际执行真实方法/纯函数合同,未冒称浏览器操作",
+ "screenshots": []
+ },
+ {
+ "status": "PASS",
+ "testTitle": "save B then undo A stays dirty, and redo B returns to the actual saved baseline",
+ "kind": "unit",
+ "runId": "scene-editor-2026-09-06T10:24:04.121Z",
+ "ids": [
+ "history.undo",
+ "history.redo"
+ ],
+ "file": "validation.json",
+ "title": "save B then undo A stays dirty, and redo B returns to the actual saved baseline",
+ "details": "实际执行真实方法/纯函数合同,未冒称浏览器操作",
+ "screenshots": []
+ },
+ {
+ "status": "PASS",
+ "testTitle": "default history caps at 60 snapshots, stops at either edge and discards redo only after a new branch",
+ "kind": "unit",
+ "runId": "scene-editor-2026-09-06T10:24:04.121Z",
+ "ids": [
+ "history.capacity",
+ "history.undo",
+ "history.redo"
+ ],
+ "file": "validation.json",
+ "title": "default history caps at 60 snapshots, stops at either edge and discards redo only after a new branch",
+ "details": "实际执行真实方法/纯函数合同,未冒称浏览器操作",
+ "screenshots": []
+ }
+ ]
+ },
+ {
+ "id": "history.capacity",
+ "group": "历史与预览",
+ "name": "60 步历史上限与边界",
+ "source": {
+ "file": "../../../unreal_tran_web/src/features/guide-legacy/demo-src/scene-editor.js",
+ "symbol": "new SceneHistory(60)",
+ "line": 180
+ },
+ "implementation": "已有实现已完成验证",
+ "boundary": "历史属于本次编辑会话;不宣称服务端版本历史。",
+ "acceptance": "聚焦测试验证 60 步边界、空 undo/redo 与失焦快照。",
+ "fix": "尚未关联本轮修复记录",
+ "fixIds": [],
+ "verification": "PASS",
+ "evidence": [
+ {
+ "status": "PASS",
+ "testTitle": "default history caps at 60 snapshots, stops at either edge and discards redo only after a new branch",
+ "kind": "unit",
+ "runId": "scene-editor-2026-09-06T10:24:04.121Z",
+ "ids": [
+ "history.capacity",
+ "history.undo",
+ "history.redo"
+ ],
+ "file": "validation.json",
+ "title": "default history caps at 60 snapshots, stops at either edge and discards redo only after a new branch",
+ "details": "实际执行真实方法/纯函数合同,未冒称浏览器操作",
+ "screenshots": []
+ }
+ ]
+ },
+ {
+ "id": "history.failure",
+ "group": "历史与预览",
+ "name": "异步历史恢复失败/互斥",
+ "source": {
+ "file": "../../../unreal_tran_web/src/features/guide-legacy/demo-src/scene-editor.js",
+ "symbol": "async restoreHistoryState",
+ "line": 2328
+ },
+ "implementation": "本轮修复已完成验证",
+ "boundary": "失败不能推进索引或丢当前有效场景;忙碌时写操作需一致。",
+ "acceptance": "GLB 恢复失败、并发 undo/redo、保存混入均不半提交。",
+ "fix": "文档签名忽略更新时间和对象键顺序;保存后的 undo/redo 根据真实已保存文档判断 dirty;异步恢复失败保留现场并回滚历史索引。",
+ "fixIds": [
+ "SCENE-F07"
+ ],
+ "verification": "PASS",
+ "evidence": [
+ {
+ "status": "PASS",
+ "testTitle": "failed undo restoration retains live objects and keeps the undo target available for retry",
+ "kind": "unit",
+ "runId": "scene-editor-2026-09-06T10:24:04.121Z",
+ "ids": [
+ "history.failure"
+ ],
+ "file": "validation.json",
+ "title": "failed undo restoration retains live objects and keeps the undo target available for retry",
+ "details": "实际执行真实方法/纯函数合同,未冒称浏览器操作",
+ "screenshots": []
+ },
+ {
+ "status": "PASS",
+ "testTitle": "replacement failure waits for other started loads before restoring the original scene",
+ "kind": "unit",
+ "runId": "scene-editor-2026-09-06T10:24:04.121Z",
+ "ids": [
+ "history.failure",
+ "glb.pending"
+ ],
+ "file": "validation.json",
+ "title": "replacement failure waits for other started loads before restoring the original scene",
+ "details": "实际执行真实方法/纯函数合同,未冒称浏览器操作",
+ "screenshots": []
+ }
+ ]
+ },
+ {
+ "id": "preview.enter",
+ "group": "历史与预览",
+ "name": "进入运行预览",
+ "source": {
+ "file": "../../../unreal_tran_web/src/features/guide-legacy/demo-src/scene-editor.js",
+ "symbol": "togglePreview()",
+ "line": 583
+ },
+ "implementation": "本轮修复已完成验证",
+ "boundary": "当前默认预览只展示场景,未接模型 timeline/blueprint 自动执行。",
+ "acceptance": "实际进入,辅助选择/手柄状态正常,模型可见。",
+ "fix": "预览状态同步全部操作禁用,Delete/W/E/R 和表单输入遵守预览/权限;失活缓存页忽略全局快捷键,输入框撤销保持文本语义。",
+ "fixIds": [
+ "SCENE-F05"
+ ],
+ "verification": "PASS",
+ "evidence": [
+ {
+ "status": "PASS",
+ "testTitle": "只读管理员:可浏览预览与导出,模板/资源/键盘/接口不能改场景",
+ "recordedAt": "2026-09-06T10:31:05.221Z",
+ "kind": "browser",
+ "ids": [
+ "permission.readonly",
+ "permission.update",
+ "permission.publish",
+ "preview.enter",
+ "preview.exit",
+ "preview.lock",
+ "json.export"
+ ],
+ "file": "evidence/readonly.json",
+ "title": "只读管理员:可浏览预览与导出,模板/资源/键盘/接口不能改场景",
+ "details": "只读按钮/模板/资源禁写、蒙皮浏览、运行预览与快捷键不变更对象,直接PUT拒绝;具体搜索/鼠标覆盖另见视口用例。",
+ "screenshots": []
+ },
+ {
+ "status": "PASS",
+ "testTitle": "真实相机操控、视口拾取、预览禁写和两尺寸主题可读性",
+ "recordedAt": "2026-09-06T10:29:44.118Z",
+ "kind": "真实UI+只读投影",
+ "ids": [
+ "view.orbit",
+ "view.pan",
+ "view.zoom",
+ "view.top",
+ "view.perspective",
+ "view.grid",
+ "view.focus",
+ "selection.viewport",
+ "selection.highlight",
+ "preview.enter",
+ "preview.exit",
+ "preview.lock",
+ "lifecycle.resize-theme"
+ ],
+ "file": "evidence/viewport-camera-theme.json",
+ "title": "真实相机操控、视口拾取、预览禁写和两尺寸主题可读性",
+ "details": "",
+ "screenshots": [
+ "40-viewport-dark-1920",
+ "41-viewport-light-1440",
+ "42-fox-preview-original-material"
+ ]
+ }
+ ]
+ },
+ {
+ "id": "preview.exit",
+ "group": "历史与预览",
+ "name": "按钮 / Escape 退出预览",
+ "source": {
+ "file": "../../../unreal_tran_web/src/features/guide-legacy/demo-src/scene-editor.js",
+ "symbol": "handleKeyboard(event)",
+ "line": 469
+ },
+ "implementation": "本轮修复已完成验证",
+ "boundary": "退出后恢复编辑选择与状态;不得丢未存变换。",
+ "acceptance": "两种退出方式均有效,输入焦点与缓存激活行为正确。",
+ "fix": "预览状态同步全部操作禁用,Delete/W/E/R 和表单输入遵守预览/权限;失活缓存页忽略全局快捷键,输入框撤销保持文本语义。",
+ "fixIds": [
+ "SCENE-F05"
+ ],
+ "verification": "PASS",
+ "evidence": [
+ {
+ "status": "PASS",
+ "testTitle": "只读管理员:可浏览预览与导出,模板/资源/键盘/接口不能改场景",
+ "recordedAt": "2026-09-06T10:31:05.221Z",
+ "kind": "browser",
+ "ids": [
+ "permission.readonly",
+ "permission.update",
+ "permission.publish",
+ "preview.enter",
+ "preview.exit",
+ "preview.lock",
+ "json.export"
+ ],
+ "file": "evidence/readonly.json",
+ "title": "只读管理员:可浏览预览与导出,模板/资源/键盘/接口不能改场景",
+ "details": "只读按钮/模板/资源禁写、蒙皮浏览、运行预览与快捷键不变更对象,直接PUT拒绝;具体搜索/鼠标覆盖另见视口用例。",
+ "screenshots": []
+ },
+ {
+ "status": "PASS",
+ "testTitle": "真实相机操控、视口拾取、预览禁写和两尺寸主题可读性",
+ "recordedAt": "2026-09-06T10:29:44.118Z",
+ "kind": "真实UI+只读投影",
+ "ids": [
+ "view.orbit",
+ "view.pan",
+ "view.zoom",
+ "view.top",
+ "view.perspective",
+ "view.grid",
+ "view.focus",
+ "selection.viewport",
+ "selection.highlight",
+ "preview.enter",
+ "preview.exit",
+ "preview.lock",
+ "lifecycle.resize-theme"
+ ],
+ "file": "evidence/viewport-camera-theme.json",
+ "title": "真实相机操控、视口拾取、预览禁写和两尺寸主题可读性",
+ "details": "",
+ "screenshots": [
+ "40-viewport-dark-1920",
+ "41-viewport-light-1440",
+ "42-fox-preview-original-material"
+ ]
+ },
+ {
+ "status": "PASS",
+ "testTitle": "preview locks mutation controls and Delete while Escape restores editing",
+ "kind": "unit",
+ "runId": "scene-editor-2026-09-06T10:24:04.121Z",
+ "ids": [
+ "preview.lock",
+ "preview.exit"
+ ],
+ "file": "validation.json",
+ "title": "preview locks mutation controls and Delete while Escape restores editing",
+ "details": "实际执行真实方法/纯函数合同,未冒称浏览器操作",
+ "screenshots": []
+ }
+ ]
+ },
+ {
+ "id": "preview.lock",
+ "group": "历史与预览",
+ "name": "预览中禁止编辑",
+ "source": {
+ "file": "../../../unreal_tran_web/src/features/guide-legacy/demo-src/scene-editor.js",
+ "symbol": "togglePreview()",
+ "line": 583
+ },
+ "implementation": "本轮修复已完成验证",
+ "boundary": "显示“场景编辑锁定”必须覆盖属性、删除、模板、导入和快捷键。",
+ "acceptance": "尝试所有写入口,预览时文档不改变;退出后正常。",
+ "fix": "预览状态同步全部操作禁用,Delete/W/E/R 和表单输入遵守预览/权限;失活缓存页忽略全局快捷键,输入框撤销保持文本语义。",
+ "fixIds": [
+ "SCENE-F05"
+ ],
+ "verification": "PASS",
+ "evidence": [
+ {
+ "status": "PASS",
+ "testTitle": "只读管理员:可浏览预览与导出,模板/资源/键盘/接口不能改场景",
+ "recordedAt": "2026-09-06T10:31:05.221Z",
+ "kind": "browser",
+ "ids": [
+ "permission.readonly",
+ "permission.update",
+ "permission.publish",
+ "preview.enter",
+ "preview.exit",
+ "preview.lock",
+ "json.export"
+ ],
+ "file": "evidence/readonly.json",
+ "title": "只读管理员:可浏览预览与导出,模板/资源/键盘/接口不能改场景",
+ "details": "只读按钮/模板/资源禁写、蒙皮浏览、运行预览与快捷键不变更对象,直接PUT拒绝;具体搜索/鼠标覆盖另见视口用例。",
+ "screenshots": []
+ },
+ {
+ "status": "PASS",
+ "testTitle": "真实相机操控、视口拾取、预览禁写和两尺寸主题可读性",
+ "recordedAt": "2026-09-06T10:29:44.118Z",
+ "kind": "真实UI+只读投影",
+ "ids": [
+ "view.orbit",
+ "view.pan",
+ "view.zoom",
+ "view.top",
+ "view.perspective",
+ "view.grid",
+ "view.focus",
+ "selection.viewport",
+ "selection.highlight",
+ "preview.enter",
+ "preview.exit",
+ "preview.lock",
+ "lifecycle.resize-theme"
+ ],
+ "file": "evidence/viewport-camera-theme.json",
+ "title": "真实相机操控、视口拾取、预览禁写和两尺寸主题可读性",
+ "details": "",
+ "screenshots": [
+ "40-viewport-dark-1920",
+ "41-viewport-light-1440",
+ "42-fox-preview-original-material"
+ ]
+ },
+ {
+ "status": "PASS",
+ "testTitle": "preview locks mutation controls and Delete while Escape restores editing",
+ "kind": "unit",
+ "runId": "scene-editor-2026-09-06T10:24:04.121Z",
+ "ids": [
+ "preview.lock",
+ "preview.exit"
+ ],
+ "file": "validation.json",
+ "title": "preview locks mutation controls and Delete while Escape restores editing",
+ "details": "实际执行真实方法/纯函数合同,未冒称浏览器操作",
+ "screenshots": []
+ }
+ ]
+ },
+ {
+ "id": "cover.capture",
+ "group": "保存封面、发布与依赖",
+ "name": "保存时截取当前三维场景封面",
+ "source": {
+ "file": "../../../unreal_tran_web/src/features/scene-legacy/sceneCoverCapture.ts",
+ "symbol": "export async function captureSceneCover",
+ "line": 26
+ },
+ "implementation": "已有实现已完成验证",
+ "boundary": "JPEG 最大边 640、当前相机;隐藏手柄/网格/高亮且恢复现场。",
+ "acceptance": "截图与服务端图片可辨一致,像素实际有模型,UI 状态不被破坏。",
+ "fix": "尚未关联本轮修复记录",
+ "fixIds": [],
+ "verification": "PASS",
+ "evidence": [
+ {
+ "status": "PASS",
+ "testTitle": "狐狸:真实细分权限、全部属性、清空绑定、蒙皮保护、相机与封面保存重开",
+ "recordedAt": "2026-09-06T10:30:14.385Z",
+ "kind": "browser",
+ "ids": [
+ "project.load",
+ "project.title",
+ "project.save",
+ "selection.tree",
+ "property.name",
+ "property.visible",
+ "property.operable",
+ "property.position",
+ "property.rotation",
+ "property.scale",
+ "property.device-id",
+ "property.model-id",
+ "property.semantic",
+ "part.source",
+ "panel.binding",
+ "cover.capture",
+ "cover.atomic"
+ ],
+ "file": "evidence/fox-properties-cover.json",
+ "title": "狐狸:真实细分权限、全部属性、清空绑定、蒙皮保护、相机与封面保存重开",
+ "details": "真实字段输入与清空、六个语义选项、蒙皮保护、源模型可见,正文/资产/封面保存和刷新;不把原件旧模板组合当新模板结果。",
+ "screenshots": []
+ },
+ {
+ "status": "PASS",
+ "testTitle": "copies synchronously without bounds, gizmo, grid or selection glow, then restores before JPEG encoding",
+ "kind": "unit",
+ "runId": "scene-editor-2026-09-06T10:24:04.121Z",
+ "ids": [
+ "cover.capture",
+ "selection.highlight",
+ "view.grid"
+ ],
+ "file": "validation.json",
+ "title": "copies synchronously without bounds, gizmo, grid or selection glow, then restores before JPEG encoding",
+ "details": "实际执行真实方法/纯函数合同,未冒称浏览器操作",
+ "screenshots": []
+ }
+ ]
+ },
+ {
+ "id": "cover.atomic",
+ "group": "保存封面、发布与依赖",
+ "name": "封面资产与文档同次保存",
+ "source": {
+ "file": "../../../unreal_tran_web/src/views/content/LegacyScenePageView.vue",
+ "symbol": "saveSceneDocumentWithCover",
+ "line": 47
+ },
+ "implementation": "已有实现已完成验证",
+ "boundary": "封面 upload ticket 与 version 同一次 PUT 消费,不另存过期文档。",
+ "acceptance": "检查资产/封面字段/当前版本相同,并真实列表加载图片。",
+ "fix": "尚未关联本轮修复记录",
+ "fixIds": [],
+ "verification": "PASS",
+ "evidence": [
+ {
+ "status": "PASS",
+ "testTitle": "狐狸:真实细分权限、全部属性、清空绑定、蒙皮保护、相机与封面保存重开",
+ "recordedAt": "2026-09-06T10:30:14.385Z",
+ "kind": "browser",
+ "ids": [
+ "project.load",
+ "project.title",
+ "project.save",
+ "selection.tree",
+ "property.name",
+ "property.visible",
+ "property.operable",
+ "property.position",
+ "property.rotation",
+ "property.scale",
+ "property.device-id",
+ "property.model-id",
+ "property.semantic",
+ "part.source",
+ "panel.binding",
+ "cover.capture",
+ "cover.atomic"
+ ],
+ "file": "evidence/fox-properties-cover.json",
+ "title": "狐狸:真实细分权限、全部属性、清空绑定、蒙皮保护、相机与封面保存重开",
+ "details": "真实字段输入与清空、六个语义选项、蒙皮保护、源模型可见,正文/资产/封面保存和刷新;不把原件旧模板组合当新模板结果。",
+ "screenshots": []
+ },
+ {
+ "status": "PASS",
+ "testTitle": "发布:前端绑定校验、保存与发布失败阻断、真实固定版本供训练目录使用",
+ "recordedAt": "2026-09-06T10:27:04.782Z",
+ "kind": "browser",
+ "ids": [
+ "validation.complete",
+ "validation.bindings",
+ "panel.validation",
+ "publish.saved",
+ "publish.failure",
+ "publish.fixed-model",
+ "cover.atomic",
+ "project.back"
+ ],
+ "file": "evidence/publish-fixed-version.json",
+ "title": "发布:前端绑定校验、保存与发布失败阻断、真实固定版本供训练目录使用",
+ "details": "实际校验、绑定阻断、POST503不假发布,重试发布当前固定版本;返回列表保留编辑标签,搜索当前项目并核对真实封面图片加载。下游静态渲染另有独立用例。",
+ "screenshots": []
+ },
+ {
+ "status": "PASS",
+ "testTitle": "scene image ticket, fixed asset replacement, dependencies and coverUri commit in one versioned PUT",
+ "kind": "unit",
+ "runId": "scene-editor-2026-09-06T10:24:04.121Z",
+ "ids": [
+ "cover.atomic"
+ ],
+ "file": "validation.json",
+ "title": "scene image ticket, fixed asset replacement, dependencies and coverUri commit in one versioned PUT",
+ "details": "实际执行真实方法/纯函数合同,未冒称浏览器操作",
+ "screenshots": []
+ }
+ ]
+ },
+ {
+ "id": "cover.failure",
+ "group": "保存封面、发布与依赖",
+ "name": "截图/封面上传失败保留原封面",
+ "source": {
+ "file": "../../../unreal_tran_web/src/views/content/LegacyScenePageView.vue",
+ "symbol": "coverFailed",
+ "line": 390
+ },
+ "implementation": "已有实现已完成验证",
+ "boundary": "正文可保存时给真实 warning;409 必须阻断且不得覆盖。",
+ "acceptance": "截图失败、上传503、PUT409各自核对保留/提示/零错误发布。",
+ "fix": "尚未关联本轮修复记录",
+ "fixIds": [],
+ "verification": "PASS",
+ "evidence": [
+ {
+ "status": "PASS",
+ "testTitle": "保存失败与封面降级:503 保留草稿,封面上传失败保留旧图,恢复后可重试",
+ "recordedAt": "2026-09-06T10:26:33.388Z",
+ "kind": "browser",
+ "ids": [
+ "project.save-failure",
+ "cover.failure"
+ ],
+ "file": "evidence/save-cover-failure.json",
+ "title": "保存失败与封面降级:503 保留草稿,封面上传失败保留旧图,恢复后可重试",
+ "details": "正文PUT503不清草稿;封面上传503保留旧图但正文可保存;恢复服务后保存刷新。截图异常和409另由聚焦合同验证。",
+ "screenshots": []
+ },
+ {
+ "status": "PASS",
+ "testTitle": "capture failure saves content with the previous cover and reports a warning result",
+ "kind": "unit",
+ "runId": "scene-editor-2026-09-06T10:24:04.121Z",
+ "ids": [
+ "cover.failure"
+ ],
+ "file": "validation.json",
+ "title": "capture failure saves content with the previous cover and reports a warning result",
+ "details": "实际执行真实方法/纯函数合同,未冒称浏览器操作",
+ "screenshots": []
+ },
+ {
+ "status": "PASS",
+ "testTitle": "upload conflict propagates without any content PUT",
+ "kind": "unit",
+ "runId": "scene-editor-2026-09-06T10:24:04.121Z",
+ "ids": [
+ "project.conflict",
+ "cover.failure"
+ ],
+ "file": "validation.json",
+ "title": "upload conflict propagates without any content PUT",
+ "details": "实际执行真实方法/纯函数合同,未冒称浏览器操作",
+ "screenshots": []
+ }
+ ]
+ },
+ {
+ "id": "validation.complete",
+ "group": "保存封面、发布与依赖",
+ "name": "环境、对象用途、模型加载检查",
+ "source": {
+ "file": "../../../unreal_tran_web/src/features/guide-legacy/demo-src/scene-editor.js",
+ "symbol": "verifyScene(showToast",
+ "line": 1715
+ },
+ "implementation": "已有实现已完成验证",
+ "boundary": "纯环境允许;模型未完成解析必须阻断。",
+ "acceptance": "空地板/纯环境/带模型和加载中分别核对检查结果。",
+ "fix": "尚未关联本轮修复记录",
+ "fixIds": [],
+ "verification": "PASS",
+ "evidence": [
+ {
+ "status": "PASS",
+ "testTitle": "发布:前端绑定校验、保存与发布失败阻断、真实固定版本供训练目录使用",
+ "recordedAt": "2026-09-06T10:27:04.782Z",
+ "kind": "browser",
+ "ids": [
+ "validation.complete",
+ "validation.bindings",
+ "panel.validation",
+ "publish.saved",
+ "publish.failure",
+ "publish.fixed-model",
+ "cover.atomic",
+ "project.back"
+ ],
+ "file": "evidence/publish-fixed-version.json",
+ "title": "发布:前端绑定校验、保存与发布失败阻断、真实固定版本供训练目录使用",
+ "details": "实际校验、绑定阻断、POST503不假发布,重试发布当前固定版本;返回列表保留编辑标签,搜索当前项目并核对真实封面图片加载。下游静态渲染另有独立用例。",
+ "screenshots": []
+ }
+ ]
+ },
+ {
+ "id": "validation.bindings",
+ "group": "保存封面、发布与依赖",
+ "name": "设备/物模型编号完整与唯一",
+ "source": {
+ "file": "../../../unreal_tran_web/src/features/guide-legacy/demo-src/scene-editor.js",
+ "symbol": "verifyScene(showToast",
+ "line": 1715
+ },
+ "implementation": "本轮修复已完成验证",
+ "boundary": "空白、重复编号按前后端合同处理,不自动替用户分配新业务身份。",
+ "acceptance": "空、空格、重复与合法编号;错误项可见,发布被阻断。",
+ "fix": "恢复显示名、设备和物模型编号使用空值保留规则,不把显式空串补回旧值;新资源序号跳过既有编号,空白编号校验按 trim 处理。;设备绑定显示真实语义与未设置;校验按钮进入当前检查面板并随绑定变更刷新;日志记录本次运行实际操作,错误详情保持瞬时提示;摘要超过六项明确完整列表位置。",
+ "fixIds": [
+ "SCENE-F06",
+ "SCENE-F10"
+ ],
+ "verification": "PASS",
+ "evidence": [
+ {
+ "status": "PASS",
+ "testTitle": "发布:前端绑定校验、保存与发布失败阻断、真实固定版本供训练目录使用",
+ "recordedAt": "2026-09-06T10:27:04.782Z",
+ "kind": "browser",
+ "ids": [
+ "validation.complete",
+ "validation.bindings",
+ "panel.validation",
+ "publish.saved",
+ "publish.failure",
+ "publish.fixed-model",
+ "cover.atomic",
+ "project.back"
+ ],
+ "file": "evidence/publish-fixed-version.json",
+ "title": "发布:前端绑定校验、保存与发布失败阻断、真实固定版本供训练目录使用",
+ "details": "实际校验、绑定阻断、POST503不假发布,重试发布当前固定版本;返回列表保留编辑标签,搜索当前项目并核对真实封面图片加载。下游静态渲染另有独立用例。",
+ "screenshots": []
+ },
+ {
+ "status": "PASS",
+ "testTitle": "new resource bindings do not reuse a saved serial after earlier objects were deleted",
+ "kind": "unit",
+ "runId": "scene-editor-2026-09-06T10:24:04.121Z",
+ "ids": [
+ "validation.bindings"
+ ],
+ "file": "validation.json",
+ "title": "new resource bindings do not reuse a saved serial after earlier objects were deleted",
+ "details": "实际执行真实方法/纯函数合同,未冒称浏览器操作",
+ "screenshots": []
+ },
+ {
+ "status": "PASS",
+ "testTitle": "verify opens live validation and whitespace binding edits immediately invalidate the active tab",
+ "kind": "unit",
+ "runId": "scene-editor-2026-09-06T10:24:04.121Z",
+ "ids": [
+ "panel.validation",
+ "validation.bindings"
+ ],
+ "file": "validation.json",
+ "title": "verify opens live validation and whitespace binding edits immediately invalidate the active tab",
+ "details": "实际执行真实方法/纯函数合同,未冒称浏览器操作",
+ "screenshots": []
+ }
+ ]
+ },
+ {
+ "id": "validation.references",
+ "group": "保存封面、发布与依赖",
+ "name": "受控资源引用完整性",
+ "source": {
+ "file": "../../../unreal_tran_web/src/features/scene-legacy/serverAssetStore.ts",
+ "symbol": "hasReferenceSync",
+ "line": 18
+ },
+ "implementation": "本轮修复已完成验证",
+ "boundary": "project/version/assetCode/sourceAssetId 必须指向实际固定资产,不能只测有字段。",
+ "acceptance": "不存在编码/错误资产类型/跨项目引用显式拒绝;正确依赖通过。",
+ "fix": "前端目录和资源解析同时匹配指定项目、版本、code、id、URI、READY MODEL_FILE;错误引用不回退首文件或本地缓存。仅含 sourceAssetId/assetId 的本项目资源也必须将 ID 纳入缓存/去重键,防止已缓存有效引用掩盖另一个错误ID;失败请求缓存可重试。复制场景保留旧伪 URI 但只读取当前副本资产。;新增 SceneResourceReferenceValidator 检查 objects 和递归 splitParts;自有/固定版本资源的所有显式引用必须指向同一 READY 模型文件,错误引用在发布/提交前阻断,保留受控的内置资源和旧引用兼容。",
+ "fixIds": [
+ "SCENE-F12",
+ "SCENE-F13"
+ ],
+ "verification": "PASS",
+ "evidence": [
+ {
+ "status": "PASS",
+ "testTitle": "工程 JSON 与小 GLB:真实上传、损坏拒绝、引用失败回滚、相机参数往返",
+ "recordedAt": "2026-09-06T10:26:19.703Z",
+ "kind": "browser",
+ "ids": [
+ "json.export",
+ "json.import",
+ "json.invalid",
+ "json.missing-resource",
+ "json.rendering",
+ "glb.import",
+ "glb.invalid",
+ "glb.references",
+ "validation.references"
+ ],
+ "file": "evidence/json-glb-roundtrip.json",
+ "title": "工程 JSON 与小 GLB:真实上传、损坏拒绝、引用失败回滚、相机参数往返",
+ "details": "真实JSON/GLB文件输入、错误schema/损坏资源/上传503拒绝、错误assetCode回滚、稳定URI及渲染参数保存重开。",
+ "screenshots": []
+ },
+ {
+ "status": "PASS",
+ "testTitle": "an explicit missing code cannot fall back to the first READY asset",
+ "kind": "unit",
+ "runId": "scene-editor-2026-09-06T10:24:04.121Z",
+ "ids": [
+ "validation.references",
+ "json.missing-resource"
+ ],
+ "file": "validation.json",
+ "title": "an explicit missing code cannot fall back to the first READY asset",
+ "details": "实际执行真实方法/纯函数合同,未冒称浏览器操作",
+ "screenshots": []
+ }
+ ]
+ },
+ {
+ "id": "publish.saved",
+ "group": "保存封面、发布与依赖",
+ "name": "发布场景按钮的真实 API 闭环",
+ "source": {
+ "file": "../../../unreal_tran_web/src/features/guide-legacy/demo-src/scene-editor.js",
+ "symbol": "async publishScene()",
+ "line": 2213
+ },
+ "implementation": "已有实现已完成验证",
+ "boundary": "有编辑权时先保存含封面再发布;两步均成功才显示已发布。",
+ "acceptance": "POST 真正发布当前版本,列表状态与下游目录一致。",
+ "fix": "尚未关联本轮修复记录",
+ "fixIds": [],
+ "verification": "PASS",
+ "evidence": [
+ {
+ "status": "PASS",
+ "testTitle": "发布:前端绑定校验、保存与发布失败阻断、真实固定版本供训练目录使用",
+ "recordedAt": "2026-09-06T10:27:04.782Z",
+ "kind": "browser",
+ "ids": [
+ "validation.complete",
+ "validation.bindings",
+ "panel.validation",
+ "publish.saved",
+ "publish.failure",
+ "publish.fixed-model",
+ "cover.atomic",
+ "project.back"
+ ],
+ "file": "evidence/publish-fixed-version.json",
+ "title": "发布:前端绑定校验、保存与发布失败阻断、真实固定版本供训练目录使用",
+ "details": "实际校验、绑定阻断、POST503不假发布,重试发布当前固定版本;返回列表保留编辑标签,搜索当前项目并核对真实封面图片加载。下游静态渲染另有独立用例。",
+ "screenshots": []
+ }
+ ]
+ },
+ {
+ "id": "publish.only",
+ "group": "保存封面、发布与依赖",
+ "name": "只有发布权限时发布已存版本",
+ "source": {
+ "file": "../../../unreal_tran_web/src/features/guide-legacy/demo-src/scene-editor.js",
+ "symbol": "if (this.readOnly)",
+ "line": 335
+ },
+ "implementation": "已有实现已完成验证",
+ "boundary": "publish-only 不先 PUT、不上传封面、不要求 update;服务器校验真值。",
+ "acceptance": "隔离权限会话发布已存有效版本,零 update/upload 请求。",
+ "fix": "尚未关联本轮修复记录",
+ "fixIds": [],
+ "verification": "PASS",
+ "evidence": [
+ {
+ "status": "PASS",
+ "testTitle": "仅场景发布权限:浏览器发布已存副本,不发送保存或上传请求",
+ "recordedAt": "2026-09-06T10:25:19.519Z",
+ "kind": "browser",
+ "ids": [
+ "permission.publish",
+ "publish.only",
+ "publish.fixed-model"
+ ],
+ "file": "evidence/scene-publish-only.json",
+ "title": "仅场景发布权限:浏览器发布已存副本,不发送保存或上传请求",
+ "details": "",
+ "screenshots": []
+ }
+ ]
+ },
+ {
+ "id": "publish.failure",
+ "group": "保存封面、发布与依赖",
+ "name": "预检/保存/发布失败阻断",
+ "source": {
+ "file": "../../../unreal_tran_web/src/features/guide-legacy/demo-src/scene-editor.js",
+ "symbol": "async publishScene()",
+ "line": 2213
+ },
+ "implementation": "本轮修复已完成验证",
+ "boundary": "保存失败不 POST 发布;发布失败不假标已发布;保持错误恢复能力。",
+ "acceptance": "预检错误、PUT409/503、POST失败及重试,版本/状态准确。",
+ "fix": "新增 SceneResourceReferenceValidator 检查 objects 和递归 splitParts;自有/固定版本资源的所有显式引用必须指向同一 READY 模型文件,错误引用在发布/提交前阻断,保留受控的内置资源和旧引用兼容。",
+ "fixIds": [
+ "SCENE-F13"
+ ],
+ "verification": "PASS",
+ "evidence": [
+ {
+ "status": "PASS",
+ "testTitle": "发布:前端绑定校验、保存与发布失败阻断、真实固定版本供训练目录使用",
+ "recordedAt": "2026-09-06T10:27:04.782Z",
+ "kind": "browser",
+ "ids": [
+ "validation.complete",
+ "validation.bindings",
+ "panel.validation",
+ "publish.saved",
+ "publish.failure",
+ "publish.fixed-model",
+ "cover.atomic",
+ "project.back"
+ ],
+ "file": "evidence/publish-fixed-version.json",
+ "title": "发布:前端绑定校验、保存与发布失败阻断、真实固定版本供训练目录使用",
+ "details": "实际校验、绑定阻断、POST503不假发布,重试发布当前固定版本;返回列表保留编辑标签,搜索当前项目并核对真实封面图片加载。下游静态渲染另有独立用例。",
+ "screenshots": []
+ }
+ ]
+ },
+ {
+ "id": "publish.concurrent",
+ "group": "保存封面、发布与依赖",
+ "name": "重复发布与上传/保存队列",
+ "source": {
+ "file": "../../../unreal_tran_web/src/views/content/LegacyScenePageView.vue",
+ "symbol": "publishDocument:",
+ "line": 509
+ },
+ "implementation": "已有实现已完成验证",
+ "boundary": "整条 POST 完成前仍占 FIFO;重复操作不串版本。",
+ "acceptance": "延迟上传/保存/发布并触发竞争,服务端只有完整合法版本。",
+ "fix": "尚未关联本轮修复记录",
+ "fixIds": [],
+ "verification": "PASS",
+ "evidence": [
+ {
+ "status": "PASS",
+ "testTitle": "role switch waits through validation, save, and the later queued publish after dirty clears",
+ "kind": "unit",
+ "runId": "scene-editor-2026-09-06T10:24:04.121Z",
+ "ids": [
+ "publish.concurrent",
+ "leave.busy",
+ "leave.role-switch"
+ ],
+ "file": "validation.json",
+ "title": "role switch waits through validation, save, and the later queued publish after dirty clears",
+ "details": "实际执行真实方法/纯函数合同,未冒称浏览器操作",
+ "screenshots": []
+ },
+ {
+ "status": "PASS",
+ "testTitle": "project operation queue serializes upload, save and publish including failures",
+ "kind": "unit",
+ "runId": "scene-editor-2026-09-06T10:24:04.121Z",
+ "ids": [
+ "publish.concurrent"
+ ],
+ "file": "validation.json",
+ "title": "project operation queue serializes upload, save and publish including failures",
+ "details": "实际执行真实方法/纯函数合同,未冒称浏览器操作",
+ "screenshots": []
+ }
+ ]
+ },
+ {
+ "id": "publish.fixed-model",
+ "group": "保存封面、发布与依赖",
+ "name": "固定 SCENE_MODEL 版本与多个实例",
+ "source": {
+ "file": "../../../unreal_tran_web/src/features/scene-legacy/contentApiClient.ts",
+ "symbol": "collectSceneModelDependencies",
+ "line": 938
+ },
+ "implementation": "本轮修复已完成验证",
+ "boundary": "去重精确模型版本,不能随模型新发布静默换源版本。",
+ "acceptance": "同模型多实例及另模型新版本,场景重开仍使用所存固定版本。",
+ "fix": "前端目录和资源解析同时匹配指定项目、版本、code、id、URI、READY MODEL_FILE;错误引用不回退首文件或本地缓存。仅含 sourceAssetId/assetId 的本项目资源也必须将 ID 纳入缓存/去重键,防止已缓存有效引用掩盖另一个错误ID;失败请求缓存可重试。复制场景保留旧伪 URI 但只读取当前副本资产。;新增 SceneResourceReferenceValidator 检查 objects 和递归 splitParts;自有/固定版本资源的所有显式引用必须指向同一 READY 模型文件,错误引用在发布/提交前阻断,保留受控的内置资源和旧引用兼容。;完整 PUT 回写原有相同 SCENE_MODEL 三元组时沿用场景授权边界;新增或改版依赖仍要求来源模型可见及范围权限,不扩大模型页面访问权。;新实例部件 sceneId 包含所属根对象身份,不再直接复用来源模型部件 ID;源 interaction/editor 标识保留。旧单实例 ID 保留,旧文档重复 ID 仅按文档顺序迁移后续冲突实例并同步 override。第二完整轮进一步发现 GLB 并发完成顺序会改变根对象顺序;现等待 allSettled 成功后按原文档顺序排列恢复根对象,拆分模板返回数组内部顺序和既有对象均保留,同源两实例不再随网速互换列表位置。新增延迟 30/1/10ms 乱序加载合同,验证序列化仍按文档顺序;最终浏览器验证独立记录。",
+ "fixIds": [
+ "SCENE-F12",
+ "SCENE-F13",
+ "SCENE-F14",
+ "SCENE-F18"
+ ],
+ "verification": "PASS",
+ "evidence": [
+ {
+ "status": "PASS",
+ "testTitle": "发布:前端绑定校验、保存与发布失败阻断、真实固定版本供训练目录使用",
+ "recordedAt": "2026-09-06T10:27:04.782Z",
+ "kind": "browser",
+ "ids": [
+ "validation.complete",
+ "validation.bindings",
+ "panel.validation",
+ "publish.saved",
+ "publish.failure",
+ "publish.fixed-model",
+ "cover.atomic",
+ "project.back"
+ ],
+ "file": "evidence/publish-fixed-version.json",
+ "title": "发布:前端绑定校验、保存与发布失败阻断、真实固定版本供训练目录使用",
+ "details": "实际校验、绑定阻断、POST503不假发布,重试发布当前固定版本;返回列表保留编辑标签,搜索当前项目并核对真实封面图片加载。下游静态渲染另有独立用例。",
+ "screenshots": []
+ },
+ {
+ "status": "PASS",
+ "testTitle": "仅场景发布权限:浏览器发布已存副本,不发送保存或上传请求",
+ "recordedAt": "2026-09-06T10:25:19.519Z",
+ "kind": "browser",
+ "ids": [
+ "permission.publish",
+ "publish.only",
+ "publish.fixed-model"
+ ],
+ "file": "evidence/scene-publish-only.json",
+ "title": "仅场景发布权限:浏览器发布已存副本,不发送保存或上传请求",
+ "details": "",
+ "screenshots": []
+ },
+ {
+ "status": "PASS",
+ "testTitle": "仅场景编辑权限:固定模型可加载和保存,不能发布或访问模型页面",
+ "recordedAt": "2026-09-06T10:25:11.741Z",
+ "kind": "browser",
+ "ids": [
+ "permission.update",
+ "permission.publish",
+ "publish.fixed-model"
+ ],
+ "file": "evidence/scene-update-only.json",
+ "title": "仅场景编辑权限:固定模型可加载和保存,不能发布或访问模型页面",
+ "details": "",
+ "screenshots": []
+ },
+ {
+ "status": "PASS",
+ "testTitle": "legacy duplicate instance IDs migrate only later owners in document order despite reversed GLB completion",
+ "kind": "unit",
+ "runId": "scene-editor-2026-09-06T10:24:04.121Z",
+ "ids": [
+ "part.transform",
+ "publish.fixed-model"
+ ],
+ "file": "validation.json",
+ "title": "legacy duplicate instance IDs migrate only later owners in document order despite reversed GLB completion",
+ "details": "实际执行真实方法/纯函数合同,未冒称浏览器操作",
+ "screenshots": []
+ },
+ {
+ "status": "PASS",
+ "testTitle": "fixed model reads the exact version and downloads through its visible scene dependency",
+ "kind": "unit",
+ "runId": "scene-editor-2026-09-06T10:24:04.121Z",
+ "ids": [
+ "publish.fixed-model",
+ "permission.update"
+ ],
+ "file": "validation.json",
+ "title": "fixed model reads the exact version and downloads through its visible scene dependency",
+ "details": "实际执行真实方法/纯函数合同,未冒称浏览器操作",
+ "screenshots": []
+ },
+ {
+ "status": "PASS",
+ "testTitle": "fixed model dependencies are recursively collected, exact and de-duplicated",
+ "kind": "unit",
+ "runId": "scene-editor-2026-09-06T10:24:04.121Z",
+ "ids": [
+ "publish.fixed-model"
+ ],
+ "file": "validation.json",
+ "title": "fixed model dependencies are recursively collected, exact and de-duplicated",
+ "details": "实际执行真实方法/纯函数合同,未冒称浏览器操作",
+ "screenshots": []
+ }
+ ]
+ },
+ {
+ "id": "publish.downstream",
+ "group": "保存封面、发布与依赖",
+ "name": "发布场景供训练选择和静态还原",
+ "source": {
+ "file": "../../../unreal_tran_web/src/features/guide-legacy/demo-src/scene-editor.js",
+ "symbol": "场景已发布,训练编排可以引用",
+ "line": 2227
+ },
+ "implementation": "已有实现已完成验证",
+ "boundary": "本轮只验证版本/目标/静态可见性;不承诺模型蓝图或原生动画自动执行。",
+ "acceptance": "新场景出现在正式训练目录,绑定后狐狸/卡车可见且目标身份一致。",
+ "fix": "尚未关联本轮修复记录",
+ "fixIds": [],
+ "verification": "PASS",
+ "evidence": [
+ {
+ "status": "PASS",
+ "testTitle": "fox:场景发布后在真实训练编排选择、保存并重开静态模型",
+ "recordedAt": "2026-09-06T10:24:43.055Z",
+ "kind": "browser",
+ "ids": [
+ "publish.downstream"
+ ],
+ "file": "evidence/downstream-fox.json",
+ "title": "fox:场景发布后在真实训练编排选择、保存并重开静态模型",
+ "details": "",
+ "screenshots": []
+ },
+ {
+ "status": "PASS",
+ "testTitle": "truck:场景发布后在真实训练编排选择、保存并重开静态模型",
+ "recordedAt": "2026-09-06T10:25:01.457Z",
+ "kind": "browser",
+ "ids": [
+ "publish.downstream"
+ ],
+ "file": "evidence/downstream-truck.json",
+ "title": "truck:场景发布后在真实训练编排选择、保存并重开静态模型",
+ "details": "",
+ "screenshots": []
+ }
+ ]
+ },
+ {
+ "id": "permission.update",
+ "group": "权限、缓存与未保存恢复",
+ "name": "场景细分编辑权限",
+ "source": {
+ "file": "../../../unreal_tran_web/src/features/guide-legacy/demo-src/scene-editor.js",
+ "symbol": "this.readOnly =",
+ "line": 149
+ },
+ "implementation": "本轮修复已完成验证",
+ "boundary": "只按 content.scene.update;基线残留 content.update 需修,不由角色名推断。",
+ "acceptance": "具更新权会话可编辑;无更新权的写入口和接口全部拒绝。",
+ "fix": "移除 runtime 旧 content.update 门槛,独立计算新建、更新和发布;无模型页面权限时不加载新模型目录,但既有固定依赖仍可按场景授权读取。;完整 PUT 回写原有相同 SCENE_MODEL 三元组时沿用场景授权边界;新增或改版依赖仍要求来源模型可见及范围权限,不扩大模型页面访问权。",
+ "fixIds": [
+ "SCENE-F01",
+ "SCENE-F14"
+ ],
+ "verification": "PASS",
+ "evidence": [
+ {
+ "status": "PASS",
+ "testTitle": "只读管理员:可浏览预览与导出,模板/资源/键盘/接口不能改场景",
+ "recordedAt": "2026-09-06T10:31:05.221Z",
+ "kind": "browser",
+ "ids": [
+ "permission.readonly",
+ "permission.update",
+ "permission.publish",
+ "preview.enter",
+ "preview.exit",
+ "preview.lock",
+ "json.export"
+ ],
+ "file": "evidence/readonly.json",
+ "title": "只读管理员:可浏览预览与导出,模板/资源/键盘/接口不能改场景",
+ "details": "只读按钮/模板/资源禁写、蒙皮浏览、运行预览与快捷键不变更对象,直接PUT拒绝;具体搜索/鼠标覆盖另见视口用例。",
+ "screenshots": []
+ },
+ {
+ "status": "PASS",
+ "testTitle": "仅场景编辑权限:固定模型可加载和保存,不能发布或访问模型页面",
+ "recordedAt": "2026-09-06T10:25:11.741Z",
+ "kind": "browser",
+ "ids": [
+ "permission.update",
+ "permission.publish",
+ "publish.fixed-model"
+ ],
+ "file": "evidence/scene-update-only.json",
+ "title": "仅场景编辑权限:固定模型可加载和保存,不能发布或访问模型页面",
+ "details": "",
+ "screenshots": []
+ },
+ {
+ "status": "PASS",
+ "testTitle": "scene granular update permission enables editing without the retired shared code",
+ "kind": "unit",
+ "runId": "scene-editor-2026-09-06T10:24:04.121Z",
+ "ids": [
+ "permission.update"
+ ],
+ "file": "validation.json",
+ "title": "scene granular update permission enables editing without the retired shared code",
+ "details": "实际执行真实方法/纯函数合同,未冒称浏览器操作",
+ "screenshots": []
+ },
+ {
+ "status": "PASS",
+ "testTitle": "fixed model reads the exact version and downloads through its visible scene dependency",
+ "kind": "unit",
+ "runId": "scene-editor-2026-09-06T10:24:04.121Z",
+ "ids": [
+ "publish.fixed-model",
+ "permission.update"
+ ],
+ "file": "validation.json",
+ "title": "fixed model reads the exact version and downloads through its visible scene dependency",
+ "details": "实际执行真实方法/纯函数合同,未冒称浏览器操作",
+ "screenshots": []
+ }
+ ]
+ },
+ {
+ "id": "permission.publish",
+ "group": "权限、缓存与未保存恢复",
+ "name": "场景细分发布权限",
+ "source": {
+ "file": "../../../unreal_tran_web/src/features/guide-legacy/demo-src/scene-editor.js",
+ "symbol": "this.canPublish =",
+ "line": 152
+ },
+ "implementation": "本轮修复已完成验证",
+ "boundary": "页面 content.scene 与 publish 动作分开;更新权不隐含发布。",
+ "acceptance": "只更新不能发布;仅发布可发布已存版本;无页面权不可访问。",
+ "fix": "移除 runtime 旧 content.update 门槛,独立计算新建、更新和发布;无模型页面权限时不加载新模型目录,但既有固定依赖仍可按场景授权读取。",
+ "fixIds": [
+ "SCENE-F01"
+ ],
+ "verification": "PASS",
+ "evidence": [
+ {
+ "status": "PASS",
+ "testTitle": "只读管理员:可浏览预览与导出,模板/资源/键盘/接口不能改场景",
+ "recordedAt": "2026-09-06T10:31:05.221Z",
+ "kind": "browser",
+ "ids": [
+ "permission.readonly",
+ "permission.update",
+ "permission.publish",
+ "preview.enter",
+ "preview.exit",
+ "preview.lock",
+ "json.export"
+ ],
+ "file": "evidence/readonly.json",
+ "title": "只读管理员:可浏览预览与导出,模板/资源/键盘/接口不能改场景",
+ "details": "只读按钮/模板/资源禁写、蒙皮浏览、运行预览与快捷键不变更对象,直接PUT拒绝;具体搜索/鼠标覆盖另见视口用例。",
+ "screenshots": []
+ },
+ {
+ "status": "PASS",
+ "testTitle": "无场景页面权限:编辑路由和直接内容接口均拒绝访问",
+ "recordedAt": "2026-09-06T10:25:24.402Z",
+ "kind": "browser",
+ "ids": [
+ "permission.publish"
+ ],
+ "file": "evidence/scene-no-page-permission.json",
+ "title": "无场景页面权限:编辑路由和直接内容接口均拒绝访问",
+ "details": "",
+ "screenshots": []
+ },
+ {
+ "status": "PASS",
+ "testTitle": "仅场景发布权限:浏览器发布已存副本,不发送保存或上传请求",
+ "recordedAt": "2026-09-06T10:25:19.519Z",
+ "kind": "browser",
+ "ids": [
+ "permission.publish",
+ "publish.only",
+ "publish.fixed-model"
+ ],
+ "file": "evidence/scene-publish-only.json",
+ "title": "仅场景发布权限:浏览器发布已存副本,不发送保存或上传请求",
+ "details": "",
+ "screenshots": []
+ },
+ {
+ "status": "PASS",
+ "testTitle": "仅场景编辑权限:固定模型可加载和保存,不能发布或访问模型页面",
+ "recordedAt": "2026-09-06T10:25:11.741Z",
+ "kind": "browser",
+ "ids": [
+ "permission.update",
+ "permission.publish",
+ "publish.fixed-model"
+ ],
+ "file": "evidence/scene-update-only.json",
+ "title": "仅场景编辑权限:固定模型可加载和保存,不能发布或访问模型页面",
+ "details": "",
+ "screenshots": []
+ }
+ ]
+ },
+ {
+ "id": "permission.readonly",
+ "group": "权限、缓存与未保存恢复",
+ "name": "只读仍可查看、搜索、选中和预览",
+ "source": {
+ "file": "../../../unreal_tran_web/src/features/guide-legacy/demo-src/scene-editor.js",
+ "symbol": "applyAccessMode()",
+ "line": 198
+ },
+ "implementation": "本轮修复已完成验证",
+ "boundary": "禁写不应误禁观察控件;临时视图不保存到服务器。",
+ "acceptance": "真实键盘搜索、选择、相机、预览,零内容写请求。",
+ "fix": "移除 runtime 旧 content.update 门槛,独立计算新建、更新和发布;无模型页面权限时不加载新模型目录,但既有固定依赖仍可按场景授权读取。",
+ "fixIds": [
+ "SCENE-F01"
+ ],
+ "verification": "PASS",
+ "evidence": [
+ {
+ "status": "PASS",
+ "testTitle": "只读管理员:可浏览预览与导出,模板/资源/键盘/接口不能改场景",
+ "recordedAt": "2026-09-06T10:31:05.221Z",
+ "kind": "browser",
+ "ids": [
+ "permission.readonly",
+ "permission.update",
+ "permission.publish",
+ "preview.enter",
+ "preview.exit",
+ "preview.lock",
+ "json.export"
+ ],
+ "file": "evidence/readonly.json",
+ "title": "只读管理员:可浏览预览与导出,模板/资源/键盘/接口不能改场景",
+ "details": "只读按钮/模板/资源禁写、蒙皮浏览、运行预览与快捷键不变更对象,直接PUT拒绝;具体搜索/鼠标覆盖另见视口用例。",
+ "screenshots": []
+ },
+ {
+ "status": "PASS",
+ "testTitle": "后台场景未保存时切换角色:关闭确认保留草稿,保存切换后重建只读权限",
+ "recordedAt": "2026-09-06T10:25:39.438Z",
+ "kind": "browser",
+ "ids": [
+ "leave.role-switch",
+ "leave.cancel",
+ "leave.save",
+ "permission.readonly"
+ ],
+ "file": "evidence/scene-role-switch.json",
+ "title": "后台场景未保存时切换角色:关闭确认保留草稿,保存切换后重建只读权限",
+ "details": "",
+ "screenshots": []
+ }
+ ]
+ },
+ {
+ "id": "recovery.autobuffer",
+ "group": "权限、缓存与未保存恢复",
+ "name": "800ms 未保存恢复缓冲",
+ "source": {
+ "file": "../../../unreal_tran_web/src/features/guide-legacy/demo-src/scene-editor.js",
+ "symbol": "markDirty(label",
+ "line": 654
+ },
+ "implementation": "本轮修复已完成验证",
+ "boundary": "仅写隔离浏览器恢复稿,不伪称自动入库。",
+ "acceptance": "实际修改后等待,恢复稿存在但无自动 PUT;手动保存后入库。",
+ "fix": "顶部新建委托宿主命名与创建 API,先确认未保存内容。选择放弃也要等创建及导航成功后才清除原 dirty/恢复稿;创建503仍保留原稿。成功后刷新原缓存页,避免再次打开时将已放弃修改冒称已保存。取消、失败不清原场景,成功导航到新项目 ID,并有创建并发守卫。;开始保存时取消之前安排的恢复稿定时器,避免它覆盖宿主保存快照 nonce;保存中的后续编辑仍单独保留。",
+ "fixIds": [
+ "SCENE-F02",
+ "SCENE-F08"
+ ],
+ "verification": "PASS",
+ "evidence": [
+ {
+ "status": "PASS",
+ "testTitle": "同版本恢复与 409:真实未保存草稿重载、服务器新版本保护与隔离下载",
+ "recordedAt": "2026-09-06T10:26:49.585Z",
+ "kind": "browser",
+ "ids": [
+ "project.conflict",
+ "recovery.autobuffer",
+ "recovery.same-version",
+ "recovery.stale-version"
+ ],
+ "file": "evidence/real-recovery-conflict.json",
+ "title": "同版本恢复与 409:真实未保存草稿重载、服务器新版本保护与隔离下载",
+ "details": "真实未存输入产生恢复稿,同版本恢复不自动入库;另一版本先保存后409拒绝覆盖,旧稿隔离下载。",
+ "screenshots": []
+ },
+ {
+ "status": "PASS",
+ "testTitle": "延迟保存快照:重复点击只提交一次,期间新编辑保留,旧恢复稿不会误报冲突",
+ "recordedAt": "2026-09-06T10:24:27.147Z",
+ "kind": "browser",
+ "ids": [
+ "project.concurrent-save",
+ "project.save",
+ "project.save-key",
+ "recovery.autobuffer",
+ "recovery.same-version"
+ ],
+ "file": "evidence/save-concurrency.json",
+ "title": "延迟保存快照:重复点击只提交一次,期间新编辑保留,旧恢复稿不会误报冲突",
+ "details": "延迟真实PUT快照A、重复Ctrl+S不重复请求、期间编辑B仍dirty;再次保存刷新无旧恢复稿误报。",
+ "screenshots": []
+ },
+ {
+ "status": "PASS",
+ "testTitle": "双标签各自修改场景:保存和明确丢弃当前页均保留另一页真实恢复稿",
+ "recordedAt": "2026-09-06T10:25:56.822Z",
+ "kind": "browser",
+ "ids": [
+ "recovery.multi-session",
+ "recovery.autobuffer",
+ "recovery.discard",
+ "project.save"
+ ],
+ "file": "evidence/scene-multi-session.json",
+ "title": "双标签各自修改场景:保存和明确丢弃当前页均保留另一页真实恢复稿",
+ "details": "",
+ "screenshots": []
+ }
+ ]
+ },
+ {
+ "id": "recovery.same-version",
+ "group": "权限、缓存与未保存恢复",
+ "name": "同版本真实未保存恢复",
+ "source": {
+ "file": "../../../unreal_tran_web/src/views/content/LegacyScenePageView.vue",
+ "symbol": "sceneRecoveryMatchesProjectVersion",
+ "line": 46
+ },
+ "implementation": "本轮修复已完成验证",
+ "boundary": "先回到视口,须显式保存才入库;相同文档不应产生假恢复。",
+ "acceptance": "真实修改后刷新,恢复/保存/再刷新;无注入假 localStorage 代替。",
+ "fix": "文档签名忽略更新时间和对象键顺序;保存后的 undo/redo 根据真实已保存文档判断 dirty;异步恢复失败保留现场并回滚历史索引。;开始保存时取消之前安排的恢复稿定时器,避免它覆盖宿主保存快照 nonce;保存中的后续编辑仍单独保留。",
+ "fixIds": [
+ "SCENE-F07",
+ "SCENE-F08"
+ ],
+ "verification": "PASS",
+ "evidence": [
+ {
+ "status": "PASS",
+ "testTitle": "同版本恢复与 409:真实未保存草稿重载、服务器新版本保护与隔离下载",
+ "recordedAt": "2026-09-06T10:26:49.585Z",
+ "kind": "browser",
+ "ids": [
+ "project.conflict",
+ "recovery.autobuffer",
+ "recovery.same-version",
+ "recovery.stale-version"
+ ],
+ "file": "evidence/real-recovery-conflict.json",
+ "title": "同版本恢复与 409:真实未保存草稿重载、服务器新版本保护与隔离下载",
+ "details": "真实未存输入产生恢复稿,同版本恢复不自动入库;另一版本先保存后409拒绝覆盖,旧稿隔离下载。",
+ "screenshots": []
+ },
+ {
+ "status": "PASS",
+ "testTitle": "延迟保存快照:重复点击只提交一次,期间新编辑保留,旧恢复稿不会误报冲突",
+ "recordedAt": "2026-09-06T10:24:27.147Z",
+ "kind": "browser",
+ "ids": [
+ "project.concurrent-save",
+ "project.save",
+ "project.save-key",
+ "recovery.autobuffer",
+ "recovery.same-version"
+ ],
+ "file": "evidence/save-concurrency.json",
+ "title": "延迟保存快照:重复点击只提交一次,期间新编辑保留,旧恢复稿不会误报冲突",
+ "details": "延迟真实PUT快照A、重复Ctrl+S不重复请求、期间编辑B仍dirty;再次保存刷新无旧恢复稿误报。",
+ "screenshots": []
+ }
+ ]
+ },
+ {
+ "id": "recovery.stale-version",
+ "group": "权限、缓存与未保存恢复",
+ "name": "不同版本恢复稿隔离",
+ "source": {
+ "file": "../../../unreal_tran_web/src/views/content/LegacyScenePageView.vue",
+ "symbol": "sceneRecoveryMatchesProjectVersion",
+ "line": 46
+ },
+ "implementation": "已有实现已完成验证",
+ "boundary": "旧稿不能自动覆盖新服务器版本;下载供人工合并。",
+ "acceptance": "另一会话先保存,新会话面对旧稿只能下载/隔离/明确丢弃。",
+ "fix": "尚未关联本轮修复记录",
+ "fixIds": [],
+ "verification": "PASS",
+ "evidence": [
+ {
+ "status": "PASS",
+ "testTitle": "同版本恢复与 409:真实未保存草稿重载、服务器新版本保护与隔离下载",
+ "recordedAt": "2026-09-06T10:26:49.585Z",
+ "kind": "browser",
+ "ids": [
+ "project.conflict",
+ "recovery.autobuffer",
+ "recovery.same-version",
+ "recovery.stale-version"
+ ],
+ "file": "evidence/real-recovery-conflict.json",
+ "title": "同版本恢复与 409:真实未保存草稿重载、服务器新版本保护与隔离下载",
+ "details": "真实未存输入产生恢复稿,同版本恢复不自动入库;另一版本先保存后409拒绝覆盖,旧稿隔离下载。",
+ "screenshots": []
+ }
+ ]
+ },
+ {
+ "id": "recovery.discard",
+ "group": "权限、缓存与未保存恢复",
+ "name": "下载、保留、丢弃恢复稿",
+ "source": {
+ "file": "../../../unreal_tran_web/src/views/content/LegacyScenePageView.vue",
+ "symbol": "async function quarantineRecovery",
+ "line": 223
+ },
+ "implementation": "已有实现已完成验证",
+ "boundary": "关闭/Escape 不算明确丢弃;他会话新写入稿不得被误删。",
+ "acceptance": "按钮逐项核对本地隔离键、下载文件与服务器无意外写入。",
+ "fix": "尚未关联本轮修复记录",
+ "fixIds": [],
+ "verification": "PASS",
+ "evidence": [
+ {
+ "status": "PASS",
+ "testTitle": "双标签各自修改场景:保存和明确丢弃当前页均保留另一页真实恢复稿",
+ "recordedAt": "2026-09-06T10:25:56.822Z",
+ "kind": "browser",
+ "ids": [
+ "recovery.multi-session",
+ "recovery.autobuffer",
+ "recovery.discard",
+ "project.save"
+ ],
+ "file": "evidence/scene-multi-session.json",
+ "title": "双标签各自修改场景:保存和明确丢弃当前页均保留另一页真实恢复稿",
+ "details": "",
+ "screenshots": []
+ },
+ {
+ "status": "PASS",
+ "testTitle": "recovery conflict defaults to preserve and only explicit discard may remove it",
+ "kind": "unit",
+ "runId": "scene-editor-2026-09-06T10:24:04.121Z",
+ "ids": [
+ "recovery.discard"
+ ],
+ "file": "validation.json",
+ "title": "recovery conflict defaults to preserve and only explicit discard may remove it",
+ "details": "实际执行真实方法/纯函数合同,未冒称浏览器操作",
+ "screenshots": []
+ }
+ ]
+ },
+ {
+ "id": "recovery.multi-session",
+ "group": "权限、缓存与未保存恢复",
+ "name": "多标签/多会话恢复所有权",
+ "source": {
+ "file": "../../../unreal_tran_web/src/views/content/LegacyScenePageView.vue",
+ "symbol": "recoveryStoragePrefix",
+ "line": 104
+ },
+ "implementation": "已有实现已完成验证",
+ "boundary": "会话键/nonce/锁避免覆盖别的未存稿;本地缓冲不冒充权限。",
+ "acceptance": "两个编辑会话产生不同草稿,保存/丢弃一份不删另一份。",
+ "fix": "尚未关联本轮修复记录",
+ "fixIds": [],
+ "verification": "PASS",
+ "evidence": [
+ {
+ "status": "PASS",
+ "testTitle": "双标签各自修改场景:保存和明确丢弃当前页均保留另一页真实恢复稿",
+ "recordedAt": "2026-09-06T10:25:56.822Z",
+ "kind": "browser",
+ "ids": [
+ "recovery.multi-session",
+ "recovery.autobuffer",
+ "recovery.discard",
+ "project.save"
+ ],
+ "file": "evidence/scene-multi-session.json",
+ "title": "双标签各自修改场景:保存和明确丢弃当前页均保留另一页真实恢复稿",
+ "details": "",
+ "screenshots": []
+ },
+ {
+ "status": "PASS",
+ "testTitle": "per-page recovery keys prevent copied tabs from deleting each other checkpoints",
+ "kind": "unit",
+ "runId": "scene-editor-2026-09-06T10:24:04.121Z",
+ "ids": [
+ "recovery.multi-session"
+ ],
+ "file": "validation.json",
+ "title": "per-page recovery keys prevent copied tabs from deleting each other checkpoints",
+ "details": "实际执行真实方法/纯函数合同,未冒称浏览器操作",
+ "screenshots": []
+ }
+ ]
+ },
+ {
+ "id": "leave.cancel",
+ "group": "权限、缓存与未保存恢复",
+ "name": "关闭场景标签时取消离开",
+ "source": {
+ "file": "../../../unreal_tran_web/src/views/content/LegacyScenePageView.vue",
+ "symbol": "async function confirmLeave",
+ "line": 704
+ },
+ "implementation": "本轮修复已完成验证",
+ "boundary": "关闭确认保留当前 dirty 与恢复副本;返回列表或切普通标签不应提示。",
+ "acceptance": "真实关闭确认的关闭/Escape、继续编辑,数据未丢;不把返回列表当关闭。",
+ "fix": "关闭单个或批量场景标签先按目标调用缓存守卫;取消或后续保存失败不提交此前放弃决定,全部确认后才关闭并清理。返回列表仍保留缓存,不扩展为强制关闭。",
+ "fixIds": [
+ "SCENE-F16"
+ ],
+ "verification": "PASS",
+ "evidence": [
+ {
+ "status": "PASS",
+ "testTitle": "后台场景未保存时切换角色:关闭确认保留草稿,保存切换后重建只读权限",
+ "recordedAt": "2026-09-06T10:25:39.438Z",
+ "kind": "browser",
+ "ids": [
+ "leave.role-switch",
+ "leave.cancel",
+ "leave.save",
+ "permission.readonly"
+ ],
+ "file": "evidence/scene-role-switch.json",
+ "title": "后台场景未保存时切换角色:关闭确认保留草稿,保存切换后重建只读权限",
+ "details": "",
+ "screenshots": []
+ },
+ {
+ "status": "PASS",
+ "testTitle": "closing one selected scene checks its cached guard, leaves unrelated workspaces alone",
+ "kind": "unit",
+ "runId": "scene-editor-2026-09-06T10:24:04.121Z",
+ "ids": [
+ "leave.cancel",
+ "lifecycle.cache"
+ ],
+ "file": "validation.json",
+ "title": "closing one selected scene checks its cached guard, leaves unrelated workspaces alone",
+ "details": "实际执行真实方法/纯函数合同,未冒称浏览器操作",
+ "screenshots": []
+ },
+ {
+ "status": "PASS",
+ "testTitle": "cancel or failed save in a later tab never commits earlier draft discard",
+ "kind": "unit",
+ "runId": "scene-editor-2026-09-06T10:24:04.121Z",
+ "ids": [
+ "leave.cancel",
+ "leave.save",
+ "leave.role-switch"
+ ],
+ "file": "validation.json",
+ "title": "cancel or failed save in a later tab never commits earlier draft discard",
+ "details": "实际执行真实方法/纯函数合同,未冒称浏览器操作",
+ "screenshots": []
+ }
+ ]
+ },
+ {
+ "id": "leave.save",
+ "group": "权限、缓存与未保存恢复",
+ "name": "保存并离开 / 放弃修改",
+ "source": {
+ "file": "../../../unreal_tran_web/src/views/content/LegacyScenePageView.vue",
+ "symbol": "async function confirmLeave",
+ "line": 704
+ },
+ "implementation": "本轮修复已完成验证",
+ "boundary": "等待保存完成并确认最终 dirty;明确放弃只影响当前工程会话。",
+ "acceptance": "保存成功才离开;503/409留原页;放弃按确认执行。",
+ "fix": "关闭单个或批量场景标签先按目标调用缓存守卫;取消或后续保存失败不提交此前放弃决定,全部确认后才关闭并清理。返回列表仍保留缓存,不扩展为强制关闭。",
+ "fixIds": [
+ "SCENE-F16"
+ ],
+ "verification": "PASS",
+ "evidence": [
+ {
+ "status": "PASS",
+ "testTitle": "后台场景未保存时切换角色:关闭确认保留草稿,保存切换后重建只读权限",
+ "recordedAt": "2026-09-06T10:25:39.438Z",
+ "kind": "browser",
+ "ids": [
+ "leave.role-switch",
+ "leave.cancel",
+ "leave.save",
+ "permission.readonly"
+ ],
+ "file": "evidence/scene-role-switch.json",
+ "title": "后台场景未保存时切换角色:关闭确认保留草稿,保存切换后重建只读权限",
+ "details": "",
+ "screenshots": []
+ },
+ {
+ "status": "PASS",
+ "testTitle": "cancel or failed save in a later tab never commits earlier draft discard",
+ "kind": "unit",
+ "runId": "scene-editor-2026-09-06T10:24:04.121Z",
+ "ids": [
+ "leave.cancel",
+ "leave.save",
+ "leave.role-switch"
+ ],
+ "file": "validation.json",
+ "title": "cancel or failed save in a later tab never commits earlier draft discard",
+ "details": "实际执行真实方法/纯函数合同,未冒称浏览器操作",
+ "screenshots": []
+ },
+ {
+ "status": "PASS",
+ "testTitle": "all successful confirmations commit together and unmounted guards are removed",
+ "kind": "unit",
+ "runId": "scene-editor-2026-09-06T10:24:04.121Z",
+ "ids": [
+ "leave.save",
+ "lifecycle.destroy"
+ ],
+ "file": "validation.json",
+ "title": "all successful confirmations commit together and unmounted guards are removed",
+ "details": "实际执行真实方法/纯函数合同,未冒称浏览器操作",
+ "screenshots": []
+ }
+ ]
+ },
+ {
+ "id": "leave.busy",
+ "group": "权限、缓存与未保存恢复",
+ "name": "加载/上传/发布途中离开",
+ "source": {
+ "file": "../../../unreal_tran_web/src/views/content/LegacyScenePageView.vue",
+ "symbol": "const resourcesBusy =",
+ "line": 708
+ },
+ "implementation": "已有实现已完成验证",
+ "boundary": "资源未就绪阻止离开;进行中的保存/发布须等待最终结果。",
+ "acceptance": "延迟资源与提交各验证取消/等待,旧请求不污染新项目。",
+ "fix": "尚未关联本轮修复记录",
+ "fixIds": [],
+ "verification": "PASS",
+ "evidence": [
+ {
+ "status": "PASS",
+ "testTitle": "加载失败重试与加载互斥:不展示可写半场景、恢复网络后模型完整加载",
+ "recordedAt": "2026-09-06T10:24:14.518Z",
+ "kind": "browser",
+ "ids": [
+ "project.retry",
+ "glb.pending",
+ "leave.busy"
+ ],
+ "file": "evidence/loading-retry.json",
+ "title": "加载失败重试与加载互斥:不展示可写半场景、恢复网络后模型完整加载",
+ "details": "GET503只留中性失败和重试;延迟受控下载时禁用写入/导出/预览并阻止关闭,释放后真实狐狸加载。",
+ "screenshots": []
+ },
+ {
+ "status": "PASS",
+ "testTitle": "role switch waits through validation, save, and the later queued publish after dirty clears",
+ "kind": "unit",
+ "runId": "scene-editor-2026-09-06T10:24:04.121Z",
+ "ids": [
+ "publish.concurrent",
+ "leave.busy",
+ "leave.role-switch"
+ ],
+ "file": "validation.json",
+ "title": "role switch waits through validation, save, and the later queued publish after dirty clears",
+ "details": "实际执行真实方法/纯函数合同,未冒称浏览器操作",
+ "screenshots": []
+ },
+ {
+ "status": "PASS",
+ "testTitle": "role switch rechecks resource uploads started while the save was finishing",
+ "kind": "unit",
+ "runId": "scene-editor-2026-09-06T10:24:04.121Z",
+ "ids": [
+ "leave.busy"
+ ],
+ "file": "validation.json",
+ "title": "role switch rechecks resource uploads started while the save was finishing",
+ "details": "实际执行真实方法/纯函数合同,未冒称浏览器操作",
+ "screenshots": []
+ }
+ ]
+ },
+ {
+ "id": "leave.role-switch",
+ "group": "权限、缓存与未保存恢复",
+ "name": "角色切换覆盖后台场景未存稿",
+ "source": {
+ "file": "../../../unreal_tran_web/src/views/content/LegacyScenePageView.vue",
+ "symbol": "registerWorkspaceLeaveGuard",
+ "line": 2
+ },
+ "implementation": "本轮修复已完成验证",
+ "boundary": "全体守卫确认后才切角色;后一页取消不能抹掉前页 dirty。",
+ "acceptance": "含后台场景的切角色确认取消/保存失败/成功后权限重建。",
+ "fix": "关闭单个或批量场景标签先按目标调用缓存守卫;取消或后续保存失败不提交此前放弃决定,全部确认后才关闭并清理。返回列表仍保留缓存,不扩展为强制关闭。",
+ "fixIds": [
+ "SCENE-F16"
+ ],
+ "verification": "PASS",
+ "evidence": [
+ {
+ "status": "PASS",
+ "testTitle": "后台场景未保存时切换角色:关闭确认保留草稿,保存切换后重建只读权限",
+ "recordedAt": "2026-09-06T10:25:39.438Z",
+ "kind": "browser",
+ "ids": [
+ "leave.role-switch",
+ "leave.cancel",
+ "leave.save",
+ "permission.readonly"
+ ],
+ "file": "evidence/scene-role-switch.json",
+ "title": "后台场景未保存时切换角色:关闭确认保留草稿,保存切换后重建只读权限",
+ "details": "",
+ "screenshots": []
+ },
+ {
+ "status": "PASS",
+ "testTitle": "cancel or failed save in a later tab never commits earlier draft discard",
+ "kind": "unit",
+ "runId": "scene-editor-2026-09-06T10:24:04.121Z",
+ "ids": [
+ "leave.cancel",
+ "leave.save",
+ "leave.role-switch"
+ ],
+ "file": "validation.json",
+ "title": "cancel or failed save in a later tab never commits earlier draft discard",
+ "details": "实际执行真实方法/纯函数合同,未冒称浏览器操作",
+ "screenshots": []
+ },
+ {
+ "status": "PASS",
+ "testTitle": "role switch waits through validation, save, and the later queued publish after dirty clears",
+ "kind": "unit",
+ "runId": "scene-editor-2026-09-06T10:24:04.121Z",
+ "ids": [
+ "publish.concurrent",
+ "leave.busy",
+ "leave.role-switch"
+ ],
+ "file": "validation.json",
+ "title": "role switch waits through validation, save, and the later queued publish after dirty clears",
+ "details": "实际执行真实方法/纯函数合同,未冒称浏览器操作",
+ "screenshots": []
+ }
+ ]
+ },
+ {
+ "id": "lifecycle.cache",
+ "group": "权限、缓存与未保存恢复",
+ "name": "标签失活 pause / 激活 resume",
+ "source": {
+ "file": "../../../unreal_tran_web/src/features/guide-legacy/demo-src/scene-editor.js",
+ "symbol": "pause()",
+ "line": 523
+ },
+ "implementation": "本轮修复已完成验证",
+ "boundary": "停止后台 RAF、恢复时重算视口;后台实例不能抢当前页键盘。",
+ "acceptance": "两场景标签切换、选择和未存值保留、后台无写/快捷键误动作。",
+ "fix": "顶部新建委托宿主命名与创建 API,先确认未保存内容。选择放弃也要等创建及导航成功后才清除原 dirty/恢复稿;创建503仍保留原稿。成功后刷新原缓存页,避免再次打开时将已放弃修改冒称已保存。取消、失败不清原场景,成功导航到新项目 ID,并有创建并发守卫。;预览状态同步全部操作禁用,Delete/W/E/R 和表单输入遵守预览/权限;失活缓存页忽略全局快捷键,输入框撤销保持文本语义。;关闭单个或批量场景标签先按目标调用缓存守卫;取消或后续保存失败不提交此前放弃决定,全部确认后才关闭并清理。返回列表仍保留缓存,不扩展为强制关闭。",
+ "fixIds": [
+ "SCENE-F02",
+ "SCENE-F05",
+ "SCENE-F16"
+ ],
+ "verification": "PASS",
+ "evidence": [
+ {
+ "status": "PASS",
+ "testTitle": "缓存标签暂停键盘、草稿恢复以及关闭取消和保存失败留页",
+ "recordedAt": "2026-09-06T10:30:03.089Z",
+ "kind": "真实UI缓存/关闭+浏览器模拟503拒绝写入",
+ "ids": [
+ "lifecycle.cache"
+ ],
+ "file": "evidence/viewport-cache-close.json",
+ "title": "缓存标签暂停键盘、草稿恢复以及关闭取消和保存失败留页",
+ "details": "",
+ "screenshots": []
+ },
+ {
+ "status": "PASS",
+ "testTitle": "typing undo/redo stays inside text controls and inactive cached editors ignore global shortcuts",
+ "kind": "unit",
+ "runId": "scene-editor-2026-09-06T10:24:04.121Z",
+ "ids": [
+ "history.undo",
+ "history.redo",
+ "transform.keys",
+ "lifecycle.cache"
+ ],
+ "file": "validation.json",
+ "title": "typing undo/redo stays inside text controls and inactive cached editors ignore global shortcuts",
+ "details": "实际执行真实方法/纯函数合同,未冒称浏览器操作",
+ "screenshots": []
+ },
+ {
+ "status": "PASS",
+ "testTitle": "closing one selected scene checks its cached guard, leaves unrelated workspaces alone",
+ "kind": "unit",
+ "runId": "scene-editor-2026-09-06T10:24:04.121Z",
+ "ids": [
+ "leave.cancel",
+ "lifecycle.cache"
+ ],
+ "file": "validation.json",
+ "title": "closing one selected scene checks its cached guard, leaves unrelated workspaces alone",
+ "details": "实际执行真实方法/纯函数合同,未冒称浏览器操作",
+ "screenshots": []
+ }
+ ]
+ },
+ {
+ "id": "lifecycle.destroy",
+ "group": "权限、缓存与未保存恢复",
+ "name": "关闭释放 WebGL/监听/资源",
+ "source": {
+ "file": "../../../unreal_tran_web/src/features/guide-legacy/demo-src/scene-editor.js",
+ "symbol": "destroy()",
+ "line": 421
+ },
+ "implementation": "本轮修复已完成验证",
+ "boundary": "销毁后的异步回调无 DOM 写入,无残留全局事件和资产 URL。",
+ "acceptance": "反复打开/关闭、加载中关闭边界,控制台无异常/泄漏事件。",
+ "fix": "射线候选逐级检查父级可见性和软删除;删除根对象先恢复高亮材质再释放几何/材质,避免错误拾取及 GPU 资源残留。;关闭单个或批量场景标签先按目标调用缓存守卫;取消或后续保存失败不提交此前放弃决定,全部确认后才关闭并清理。返回列表仍保留缓存,不扩展为强制关闭。",
+ "fixIds": [
+ "SCENE-F09",
+ "SCENE-F16"
+ ],
+ "verification": "PASS",
+ "evidence": [
+ {
+ "status": "PASS",
+ "testTitle": "deleting a root disposes its geometry and materials after clearing selection",
+ "kind": "unit",
+ "runId": "scene-editor-2026-09-06T10:24:04.121Z",
+ "ids": [
+ "object.delete",
+ "lifecycle.destroy"
+ ],
+ "file": "validation.json",
+ "title": "deleting a root disposes its geometry and materials after clearing selection",
+ "details": "实际执行真实方法/纯函数合同,未冒称浏览器操作",
+ "screenshots": []
+ },
+ {
+ "status": "PASS",
+ "testTitle": "selection highlight cleanup restores source materials and releases its own GPU helper",
+ "kind": "unit",
+ "runId": "scene-editor-2026-09-06T10:24:04.121Z",
+ "ids": [
+ "selection.highlight",
+ "lifecycle.destroy"
+ ],
+ "file": "validation.json",
+ "title": "selection highlight cleanup restores source materials and releases its own GPU helper",
+ "details": "实际执行真实方法/纯函数合同,未冒称浏览器操作",
+ "screenshots": []
+ },
+ {
+ "status": "PASS",
+ "testTitle": "all successful confirmations commit together and unmounted guards are removed",
+ "kind": "unit",
+ "runId": "scene-editor-2026-09-06T10:24:04.121Z",
+ "ids": [
+ "leave.save",
+ "lifecycle.destroy"
+ ],
+ "file": "validation.json",
+ "title": "all successful confirmations commit together and unmounted guards are removed",
+ "details": "实际执行真实方法/纯函数合同,未冒称浏览器操作",
+ "screenshots": []
+ },
+ {
+ "status": "PASS",
+ "testTitle": "an upload finishing after a project switch cannot register into the new session",
+ "kind": "unit",
+ "runId": "scene-editor-2026-09-06T10:24:04.121Z",
+ "ids": [
+ "glb.pending",
+ "lifecycle.destroy"
+ ],
+ "file": "validation.json",
+ "title": "an upload finishing after a project switch cannot register into the new session",
+ "details": "实际执行真实方法/纯函数合同,未冒称浏览器操作",
+ "screenshots": []
+ }
+ ]
+ },
+ {
+ "id": "lifecycle.resize-theme",
+ "group": "权限、缓存与未保存恢复",
+ "name": "桌面尺寸与明亮/暗黑可读性",
+ "source": {
+ "file": "../../../unreal_tran_web/src/features/guide-legacy/demo-src/scene-editor.js",
+ "symbol": "resize()",
+ "line": 414
+ },
+ "implementation": "本轮修复已完成验证",
+ "boundary": "继承正式主题;所有可达控件可读、无截断遮挡。",
+ "acceptance": "1920/1440 下工具栏、属性、模板与弹窗截图,主题切换无低对比。",
+ "fix": "原型 CSS 全局重置收敛到场景作用域,降低按钮重置优先级;侧栏、输入、模板、属性和摘要使用正式主题变量,修复白字白底;修复 1440 宽时长工具栏/画布撑开网格覆盖属性栏。",
+ "fixIds": [
+ "SCENE-F15"
+ ],
+ "verification": "PASS",
+ "evidence": [
+ {
+ "status": "PASS",
+ "testTitle": "真实相机操控、视口拾取、预览禁写和两尺寸主题可读性",
+ "recordedAt": "2026-09-06T10:29:44.118Z",
+ "kind": "真实UI+只读投影",
+ "ids": [
+ "view.orbit",
+ "view.pan",
+ "view.zoom",
+ "view.top",
+ "view.perspective",
+ "view.grid",
+ "view.focus",
+ "selection.viewport",
+ "selection.highlight",
+ "preview.enter",
+ "preview.exit",
+ "preview.lock",
+ "lifecycle.resize-theme"
+ ],
+ "file": "evidence/viewport-camera-theme.json",
+ "title": "真实相机操控、视口拾取、预览禁写和两尺寸主题可读性",
+ "details": "",
+ "screenshots": [
+ "40-viewport-dark-1920",
+ "41-viewport-light-1440",
+ "42-fox-preview-original-material"
+ ]
+ }
+ ]
+ }
+ ]
+}
\ No newline at end of file
diff --git a/reports/scene-editor-closure-20260906/covers/fox.jpg b/reports/scene-editor-closure-20260906/covers/fox.jpg
new file mode 100644
index 0000000..5b1c635
Binary files /dev/null and b/reports/scene-editor-closure-20260906/covers/fox.jpg differ
diff --git a/reports/scene-editor-closure-20260906/data/baseline-findings.json b/reports/scene-editor-closure-20260906/data/baseline-findings.json
new file mode 100644
index 0000000..c26d19e
--- /dev/null
+++ b/reports/scene-editor-closure-20260906/data/baseline-findings.json
@@ -0,0 +1,18 @@
+{
+ "status": "PENDING",
+ "scope": "本轮开始时只读源码发现;不是最终缺陷清单,也不是测试结果",
+ "items": [
+ {"id":"SCENE-B01","title":"细分权限与旧 runtime 权限码不一致","observation":"宿主按 content.scene.update,runtime 基线仍含 content.update。","featureIds":["permission.update","permission.readonly"]},
+ {"id":"SCENE-B02","title":"新建重置当前工程身份","observation":"基线新建加载当前模板并 reset;需要真实创建 API 与未保存确认。","featureIds":["project.new"]},
+ {"id":"SCENE-B03","title":"透视与预览写入守卫不完整","observation":"透视按钮缺实际分支;预览需要同时约束按钮和快捷键。","featureIds":["view.perspective","preview.lock"]},
+ {"id":"SCENE-B04","title":"异步导入和历史恢复的失败回滚","observation":"缺失受控资源可能在清空原对象后失败;历史索引需要和真实恢复结果一致。","featureIds":["json.missing-resource","history.failure"]},
+ {"id":"SCENE-B05","title":"显式资产编码不应静默回退","observation":"错误 assetCode 回退为首个 READY_MODEL_FILE 可能加载错误模型。","featureIds":["validation.references","publish.fixed-model"]},
+ {"id":"SCENE-B06","title":"底部日志和绑定状态存在固定文本","observation":"静态日志不能称实际记录;恒定已绑定不能替代设备字段校验。","featureIds":["panel.binding","panel.logs"]},
+ {"id":"SCENE-B07","title":"缓存暂停后键盘监听需实际隔离","observation":"只暂停 RAF 不保证后台编辑器不响应全局键盘。","featureIds":["lifecycle.cache"]},
+ {"id":"SCENE-B08","title":"原场景 160 的既有模板与对象组合不一致","observation":"主代理只读核实原件 presetId=workshop-real,objects 仍包含 terrain;这是既往切模板后持久化的旧组合。未推断用户意图改写原件。本轮仅在独立副本通过真实模板替换验证新保存的环境和对象一致。","featureIds":["project.load","template.switch-cancel","template.workshop-real","template.terrain-1"]},
+ {"id":"SCENE-B09","title":"来源部件 ID 被多实例共享导致点选串对象","observation":"本轮双实例验收发现第二实例部件可能复用第一实例 sceneId,选择和覆盖误指向第一实例;修复需按所属根对象隔离身份,同时保留旧单实例兼容与稳定来源标识。","featureIds":["selection.tree","selection.viewport","part.transform","part.soft-delete"]},
+ {"id":"SCENE-B10","title":"新建选择放弃后创建失败提前丢失 dirty","observation":"最终复核发现放弃旧稿不能在新项目创建前提交,否则POST503会留下旧页面但失去未保存标记和恢复保护。本轮补为创建且导航成功后才提交放弃,并重新加载原缓存内容。","featureIds":["project.new","recovery.autobuffer","lifecycle.cache"]},
+ {"id":"SCENE-B11","title":"仅以资产 ID 引用时缓存身份不完整","observation":"最终复核发现省略code和URI、仅提供sourceAssetId/assetId的场景资产可能共用不完整缓存键,导致先前有效结果绕过另一引用校验。补修同时覆盖下载缓存与预检去重。","featureIds":["validation.references","glb.references"]},
+ {"id":"SCENE-B12","title":"并行 GLB 恢复按完成先后改变对象顺序","observation":"第二完整轮在保存卡车双实例后重开时复现:各 GLB 加载结束后直接追加根对象,导致同源实例在对象列表中的先后随完成时间变化。修复后等待全部恢复完成,再按原文档顺序提交,拆分数组内部顺序保持;30/1/10ms 延迟合同验证实际完成顺序不同仍能稳定序列化。","featureIds":["project.load","selection.tree"]}
+ ]
+}
diff --git a/reports/scene-editor-closure-20260906/data/coverage-test-plan.json b/reports/scene-editor-closure-20260906/data/coverage-test-plan.json
new file mode 100644
index 0000000..219518d
--- /dev/null
+++ b/reports/scene-editor-closure-20260906/data/coverage-test-plan.json
@@ -0,0 +1,4074 @@
+{
+ "generatedAt": "2026-09-06T10:30:32.992Z",
+ "status": "PENDING",
+ "scope": "只读解析当前spec与明确unit计划;不执行测试,不推断最终PASS。",
+ "count": {
+ "total": 119,
+ "withBrowserPlan": 112,
+ "onlyUnitPlan": 7,
+ "unmapped": 0
+ },
+ "unknownIds": [],
+ "gaps": [],
+ "features": [
+ {
+ "id": "project.load",
+ "name": "按真实项目 ID 打开已存场景",
+ "group": "工程入口与顶部命令",
+ "verification": "PENDING",
+ "plannedEvidence": [
+ {
+ "file": "tests/scene-editor-workflow-closure.spec.ts",
+ "line": 64,
+ "evidenceName": "fox-properties-cover",
+ "testTitle": "狐狸:真实细分权限、全部属性、清空绑定、蒙皮保护、相机与封面保存重开",
+ "kind": "browser",
+ "featureIds": [
+ "project.load",
+ "project.title",
+ "project.save",
+ "selection.tree",
+ "property.name",
+ "property.visible",
+ "property.operable",
+ "property.position",
+ "property.rotation",
+ "property.scale",
+ "property.device-id",
+ "property.model-id",
+ "property.semantic",
+ "part.source",
+ "panel.binding",
+ "cover.capture",
+ "cover.atomic"
+ ],
+ "scope": "真实字段输入与清空、六个语义选项、蒙皮保护、源模型可见,正文/资产/封面保存和刷新;不把原件旧模板组合当新模板结果。"
+ },
+ {
+ "file": "tools/scene-closure-engine.test.mjs",
+ "testTitle": "successful parallel restores retain document order after delayed GLBs and split-object batches finish out of order",
+ "featureIds": [
+ "project.load",
+ "selection.tree"
+ ],
+ "kind": "unit",
+ "scope": "聚焦合同/真实DOM/Three对象计算;不称为浏览器鼠标交互。"
+ }
+ ]
+ },
+ {
+ "id": "project.retry",
+ "name": "项目或挂载加载失败后重新加载",
+ "group": "工程入口与顶部命令",
+ "verification": "PENDING",
+ "plannedEvidence": [
+ {
+ "file": "tests/scene-editor-async-closure.spec.ts",
+ "line": 27,
+ "evidenceName": "loading-retry",
+ "testTitle": "加载失败重试与加载互斥:不展示可写半场景、恢复网络后模型完整加载",
+ "kind": "browser",
+ "featureIds": [
+ "project.retry",
+ "glb.pending",
+ "leave.busy"
+ ],
+ "scope": "GET503只留中性失败和重试;延迟受控下载时禁用写入/导出/预览并阻止关闭,释放后真实狐狸加载。"
+ },
+ {
+ "file": "tools/scene-closure-assets.test.mjs",
+ "testTitle": "a failed fixed-version lookup is evicted and the same reference can be retried",
+ "featureIds": [
+ "project.retry"
+ ],
+ "kind": "unit",
+ "scope": "聚焦合同/真实DOM/Three对象计算;不称为浏览器鼠标交互。"
+ }
+ ]
+ },
+ {
+ "id": "project.title",
+ "name": "当前场景名称与项目说明按钮",
+ "group": "工程入口与顶部命令",
+ "verification": "PENDING",
+ "plannedEvidence": [
+ {
+ "file": "tests/scene-editor-workflow-closure.spec.ts",
+ "line": 64,
+ "evidenceName": "fox-properties-cover",
+ "testTitle": "狐狸:真实细分权限、全部属性、清空绑定、蒙皮保护、相机与封面保存重开",
+ "kind": "browser",
+ "featureIds": [
+ "project.load",
+ "project.title",
+ "project.save",
+ "selection.tree",
+ "property.name",
+ "property.visible",
+ "property.operable",
+ "property.position",
+ "property.rotation",
+ "property.scale",
+ "property.device-id",
+ "property.model-id",
+ "property.semantic",
+ "part.source",
+ "panel.binding",
+ "cover.capture",
+ "cover.atomic"
+ ],
+ "scope": "真实字段输入与清空、六个语义选项、蒙皮保护、源模型可见,正文/资产/封面保存和刷新;不把原件旧模板组合当新模板结果。"
+ }
+ ]
+ },
+ {
+ "id": "project.new",
+ "name": "顶部新建独立工程",
+ "group": "工程入口与顶部命令",
+ "verification": "PENDING",
+ "plannedEvidence": [
+ {
+ "file": "tests/scene-editor-workflow-closure.spec.ts",
+ "line": 166,
+ "evidenceName": "new-project",
+ "testTitle": "新建独立场景:取消、失败、保护原工程以及成功新 ID",
+ "kind": "browser",
+ "featureIds": [
+ "project.new"
+ ],
+ "scope": "取消命名、创建503保留原工程、创建成功新ID、保存刷新与原工程内容核对。"
+ }
+ ]
+ },
+ {
+ "id": "project.back",
+ "name": "返回场景制作列表并保留场景标签",
+ "group": "工程入口与顶部命令",
+ "verification": "PENDING",
+ "plannedEvidence": [
+ {
+ "file": "tests/scene-editor-persistence-closure.spec.ts",
+ "line": 153,
+ "evidenceName": "publish-fixed-version",
+ "testTitle": "发布:前端绑定校验、保存与发布失败阻断、真实固定版本供训练目录使用",
+ "kind": "browser",
+ "featureIds": [
+ "validation.complete",
+ "validation.bindings",
+ "panel.validation",
+ "publish.saved",
+ "publish.failure",
+ "publish.fixed-model",
+ "cover.atomic",
+ "project.back"
+ ],
+ "scope": "实际校验、绑定阻断、POST503不假发布,重试发布当前固定版本;返回列表保留编辑标签,搜索当前项目并核对真实封面图片加载。下游静态渲染另有独立用例。"
+ }
+ ]
+ },
+ {
+ "id": "project.save",
+ "name": "保存草稿按钮",
+ "group": "工程入口与顶部命令",
+ "verification": "PENDING",
+ "plannedEvidence": [
+ {
+ "file": "tests/scene-editor-async-closure.spec.ts",
+ "line": 50,
+ "evidenceName": "save-concurrency",
+ "testTitle": "延迟保存快照:重复点击只提交一次,期间新编辑保留,旧恢复稿不会误报冲突",
+ "kind": "browser",
+ "featureIds": [
+ "project.concurrent-save",
+ "project.save",
+ "project.save-key",
+ "recovery.autobuffer",
+ "recovery.same-version"
+ ],
+ "scope": "延迟真实PUT快照A、重复Ctrl+S不重复请求、期间编辑B仍dirty;再次保存刷新无旧恢复稿误报。"
+ },
+ {
+ "file": "tests/scene-editor-permissions-closure.spec.ts",
+ "line": 305,
+ "evidenceName": "scene-multi-session",
+ "testTitle": "双标签各自修改场景:保存和明确丢弃当前页均保留另一页真实恢复稿",
+ "kind": "browser",
+ "featureIds": [
+ "recovery.multi-session",
+ "recovery.autobuffer",
+ "recovery.discard",
+ "project.save"
+ ],
+ "scope": "以源测试实际断言为准;计划关联不代表最终通过。"
+ },
+ {
+ "file": "tests/scene-editor-workflow-closure.spec.ts",
+ "line": 64,
+ "evidenceName": "fox-properties-cover",
+ "testTitle": "狐狸:真实细分权限、全部属性、清空绑定、蒙皮保护、相机与封面保存重开",
+ "kind": "browser",
+ "featureIds": [
+ "project.load",
+ "project.title",
+ "project.save",
+ "selection.tree",
+ "property.name",
+ "property.visible",
+ "property.operable",
+ "property.position",
+ "property.rotation",
+ "property.scale",
+ "property.device-id",
+ "property.model-id",
+ "property.semantic",
+ "part.source",
+ "panel.binding",
+ "cover.capture",
+ "cover.atomic"
+ ],
+ "scope": "真实字段输入与清空、六个语义选项、蒙皮保护、源模型可见,正文/资产/封面保存和刷新;不把原件旧模板组合当新模板结果。"
+ }
+ ]
+ },
+ {
+ "id": "project.save-key",
+ "name": "Ctrl/Meta+S 保存快捷键",
+ "group": "工程入口与顶部命令",
+ "verification": "PENDING",
+ "plannedEvidence": [
+ {
+ "file": "tests/scene-editor-async-closure.spec.ts",
+ "line": 50,
+ "evidenceName": "save-concurrency",
+ "testTitle": "延迟保存快照:重复点击只提交一次,期间新编辑保留,旧恢复稿不会误报冲突",
+ "kind": "browser",
+ "featureIds": [
+ "project.concurrent-save",
+ "project.save",
+ "project.save-key",
+ "recovery.autobuffer",
+ "recovery.same-version"
+ ],
+ "scope": "延迟真实PUT快照A、重复Ctrl+S不重复请求、期间编辑B仍dirty;再次保存刷新无旧恢复稿误报。"
+ },
+ {
+ "file": "tests/scene-editor-workflow-closure.spec.ts",
+ "line": 121,
+ "evidenceName": "truck-part-history",
+ "testTitle": "牛奶卡车:刚性部件独立变换、软删除、历史与保存基线一致",
+ "kind": "browser",
+ "featureIds": [
+ "project.save-key",
+ "part.transform",
+ "part.soft-delete",
+ "history.undo",
+ "history.redo",
+ "property.name",
+ "view.enclosure-full",
+ "view.enclosure-xray",
+ "view.enclosure-hidden"
+ ],
+ "scope": "实际部件变换和软删除、按钮/键盘历史、保存基线与输入框自身撤销;双实例隔离及车库围护三种模式真实点击、导出和隐藏后保存刷新。"
+ }
+ ]
+ },
+ {
+ "id": "project.save-failure",
+ "name": "保存失败保留修改和恢复稿",
+ "group": "工程入口与顶部命令",
+ "verification": "PENDING",
+ "plannedEvidence": [
+ {
+ "file": "tests/scene-editor-persistence-closure.spec.ts",
+ "line": 85,
+ "evidenceName": "save-cover-failure",
+ "testTitle": "保存失败与封面降级:503 保留草稿,封面上传失败保留旧图,恢复后可重试",
+ "kind": "browser",
+ "featureIds": [
+ "project.save-failure",
+ "cover.failure"
+ ],
+ "scope": "正文PUT503不清草稿;封面上传503保留旧图但正文可保存;恢复服务后保存刷新。截图异常和409另由聚焦合同验证。"
+ }
+ ]
+ },
+ {
+ "id": "project.conflict",
+ "name": "409 乐观锁冲突保护",
+ "group": "工程入口与顶部命令",
+ "verification": "PENDING",
+ "plannedEvidence": [
+ {
+ "file": "tests/scene-editor-persistence-closure.spec.ts",
+ "line": 117,
+ "evidenceName": "real-recovery-conflict",
+ "testTitle": "同版本恢复与 409:真实未保存草稿重载、服务器新版本保护与隔离下载",
+ "kind": "browser",
+ "featureIds": [
+ "project.conflict",
+ "recovery.autobuffer",
+ "recovery.same-version",
+ "recovery.stale-version"
+ ],
+ "scope": "真实未存输入产生恢复稿,同版本恢复不自动入库;另一版本先保存后409拒绝覆盖,旧稿隔离下载。"
+ },
+ {
+ "file": "tools/scene-cover-api.test.mjs",
+ "testTitle": "upload conflict propagates without any content PUT",
+ "featureIds": [
+ "project.conflict",
+ "cover.failure"
+ ],
+ "kind": "unit",
+ "scope": "聚焦合同/真实DOM/Three对象计算;不称为浏览器鼠标交互。"
+ }
+ ]
+ },
+ {
+ "id": "project.concurrent-save",
+ "name": "重复保存与保存中继续编辑",
+ "group": "工程入口与顶部命令",
+ "verification": "PENDING",
+ "plannedEvidence": [
+ {
+ "file": "tests/scene-editor-async-closure.spec.ts",
+ "line": 50,
+ "evidenceName": "save-concurrency",
+ "testTitle": "延迟保存快照:重复点击只提交一次,期间新编辑保留,旧恢复稿不会误报冲突",
+ "kind": "browser",
+ "featureIds": [
+ "project.concurrent-save",
+ "project.save",
+ "project.save-key",
+ "recovery.autobuffer",
+ "recovery.same-version"
+ ],
+ "scope": "延迟真实PUT快照A、重复Ctrl+S不重复请求、期间编辑B仍dirty;再次保存刷新无旧恢复稿误报。"
+ }
+ ]
+ },
+ {
+ "id": "json.export",
+ "name": "导出当前场景 JSON",
+ "group": "工程文件与资源导入",
+ "verification": "PENDING",
+ "plannedEvidence": [
+ {
+ "file": "tests/scene-editor-persistence-closure.spec.ts",
+ "line": 61,
+ "evidenceName": "json-glb-roundtrip",
+ "testTitle": "工程 JSON 与小 GLB:真实上传、损坏拒绝、引用失败回滚、相机参数往返",
+ "kind": "browser",
+ "featureIds": [
+ "json.export",
+ "json.import",
+ "json.invalid",
+ "json.missing-resource",
+ "json.rendering",
+ "glb.import",
+ "glb.invalid",
+ "glb.references",
+ "validation.references"
+ ],
+ "scope": "真实JSON/GLB文件输入、错误schema/损坏资源/上传503拒绝、错误assetCode回滚、稳定URI及渲染参数保存重开。"
+ },
+ {
+ "file": "tests/scene-editor-workflow-closure.spec.ts",
+ "line": 190,
+ "evidenceName": "readonly",
+ "testTitle": "只读管理员:可浏览预览与导出,模板/资源/键盘/接口不能改场景",
+ "kind": "browser",
+ "featureIds": [
+ "permission.readonly",
+ "permission.update",
+ "permission.publish",
+ "preview.enter",
+ "preview.exit",
+ "preview.lock",
+ "json.export"
+ ],
+ "scope": "只读按钮/模板/资源禁写、蒙皮浏览、运行预览与快捷键不变更对象,直接PUT拒绝;具体搜索/鼠标覆盖另见视口用例。"
+ }
+ ]
+ },
+ {
+ "id": "json.import",
+ "name": "导入有效场景工程 JSON",
+ "group": "工程文件与资源导入",
+ "verification": "PENDING",
+ "plannedEvidence": [
+ {
+ "file": "tests/scene-editor-persistence-closure.spec.ts",
+ "line": 61,
+ "evidenceName": "json-glb-roundtrip",
+ "testTitle": "工程 JSON 与小 GLB:真实上传、损坏拒绝、引用失败回滚、相机参数往返",
+ "kind": "browser",
+ "featureIds": [
+ "json.export",
+ "json.import",
+ "json.invalid",
+ "json.missing-resource",
+ "json.rendering",
+ "glb.import",
+ "glb.invalid",
+ "glb.references",
+ "validation.references"
+ ],
+ "scope": "真实JSON/GLB文件输入、错误schema/损坏资源/上传503拒绝、错误assetCode回滚、稳定URI及渲染参数保存重开。"
+ }
+ ]
+ },
+ {
+ "id": "json.invalid",
+ "name": "非法 JSON / 错误 schema 拒绝",
+ "group": "工程文件与资源导入",
+ "verification": "PENDING",
+ "plannedEvidence": [
+ {
+ "file": "tests/scene-editor-persistence-closure.spec.ts",
+ "line": 61,
+ "evidenceName": "json-glb-roundtrip",
+ "testTitle": "工程 JSON 与小 GLB:真实上传、损坏拒绝、引用失败回滚、相机参数往返",
+ "kind": "browser",
+ "featureIds": [
+ "json.export",
+ "json.import",
+ "json.invalid",
+ "json.missing-resource",
+ "json.rendering",
+ "glb.import",
+ "glb.invalid",
+ "glb.references",
+ "validation.references"
+ ],
+ "scope": "真实JSON/GLB文件输入、错误schema/损坏资源/上传503拒绝、错误assetCode回滚、稳定URI及渲染参数保存重开。"
+ }
+ ]
+ },
+ {
+ "id": "json.missing-resource",
+ "name": "JSON 缺失/错误受控引用拒绝与回滚",
+ "group": "工程文件与资源导入",
+ "verification": "PENDING",
+ "plannedEvidence": [
+ {
+ "file": "tests/scene-editor-persistence-closure.spec.ts",
+ "line": 61,
+ "evidenceName": "json-glb-roundtrip",
+ "testTitle": "工程 JSON 与小 GLB:真实上传、损坏拒绝、引用失败回滚、相机参数往返",
+ "kind": "browser",
+ "featureIds": [
+ "json.export",
+ "json.import",
+ "json.invalid",
+ "json.missing-resource",
+ "json.rendering",
+ "glb.import",
+ "glb.invalid",
+ "glb.references",
+ "validation.references"
+ ],
+ "scope": "真实JSON/GLB文件输入、错误schema/损坏资源/上传503拒绝、错误assetCode回滚、稳定URI及渲染参数保存重开。"
+ },
+ {
+ "file": "tools/scene-closure-engine.test.mjs",
+ "testTitle": "a failed replacement GLB keeps the previous live scene rather than clearing an unsaved draft",
+ "featureIds": [
+ "json.missing-resource"
+ ],
+ "kind": "unit",
+ "scope": "聚焦合同/真实DOM/Three对象计算;不称为浏览器鼠标交互。"
+ },
+ {
+ "file": "tools/scene-closure-assets.test.mjs",
+ "testTitle": "an explicit missing code cannot fall back to the first READY asset",
+ "featureIds": [
+ "validation.references",
+ "json.missing-resource"
+ ],
+ "kind": "unit",
+ "scope": "聚焦合同/真实DOM/Three对象计算;不称为浏览器鼠标交互。"
+ }
+ ]
+ },
+ {
+ "id": "json.rendering",
+ "name": "相机、灯光、雾和环境序列化往返",
+ "group": "工程文件与资源导入",
+ "verification": "PENDING",
+ "plannedEvidence": [
+ {
+ "file": "tests/scene-editor-persistence-closure.spec.ts",
+ "line": 61,
+ "evidenceName": "json-glb-roundtrip",
+ "testTitle": "工程 JSON 与小 GLB:真实上传、损坏拒绝、引用失败回滚、相机参数往返",
+ "kind": "browser",
+ "featureIds": [
+ "json.export",
+ "json.import",
+ "json.invalid",
+ "json.missing-resource",
+ "json.rendering",
+ "glb.import",
+ "glb.invalid",
+ "glb.references",
+ "validation.references"
+ ],
+ "scope": "真实JSON/GLB文件输入、错误schema/损坏资源/上传503拒绝、错误assetCode回滚、稳定URI及渲染参数保存重开。"
+ },
+ {
+ "file": "tools/scene-closure-engine.test.mjs",
+ "testTitle": "loading and history restoration preserve an authored camera rather than auto-focusing a vehicle",
+ "featureIds": [
+ "json.rendering",
+ "history.undo"
+ ],
+ "kind": "unit",
+ "scope": "聚焦合同/真实DOM/Three对象计算;不称为浏览器鼠标交互。"
+ }
+ ]
+ },
+ {
+ "id": "glb.import",
+ "name": "本地 GLB 上传并加入当前场景",
+ "group": "工程文件与资源导入",
+ "verification": "PENDING",
+ "plannedEvidence": [
+ {
+ "file": "tests/scene-editor-persistence-closure.spec.ts",
+ "line": 61,
+ "evidenceName": "json-glb-roundtrip",
+ "testTitle": "工程 JSON 与小 GLB:真实上传、损坏拒绝、引用失败回滚、相机参数往返",
+ "kind": "browser",
+ "featureIds": [
+ "json.export",
+ "json.import",
+ "json.invalid",
+ "json.missing-resource",
+ "json.rendering",
+ "glb.import",
+ "glb.invalid",
+ "glb.references",
+ "validation.references"
+ ],
+ "scope": "真实JSON/GLB文件输入、错误schema/损坏资源/上传503拒绝、错误assetCode回滚、稳定URI及渲染参数保存重开。"
+ }
+ ]
+ },
+ {
+ "id": "glb.invalid",
+ "name": "非 GLB / 损坏 GLB / 上传失败处理",
+ "group": "工程文件与资源导入",
+ "verification": "PENDING",
+ "plannedEvidence": [
+ {
+ "file": "tests/scene-editor-persistence-closure.spec.ts",
+ "line": 61,
+ "evidenceName": "json-glb-roundtrip",
+ "testTitle": "工程 JSON 与小 GLB:真实上传、损坏拒绝、引用失败回滚、相机参数往返",
+ "kind": "browser",
+ "featureIds": [
+ "json.export",
+ "json.import",
+ "json.invalid",
+ "json.missing-resource",
+ "json.rendering",
+ "glb.import",
+ "glb.invalid",
+ "glb.references",
+ "validation.references"
+ ],
+ "scope": "真实JSON/GLB文件输入、错误schema/损坏资源/上传503拒绝、错误assetCode回滚、稳定URI及渲染参数保存重开。"
+ }
+ ]
+ },
+ {
+ "id": "glb.pending",
+ "name": "资源加载/上传期间操作互斥",
+ "group": "工程文件与资源导入",
+ "verification": "PENDING",
+ "plannedEvidence": [
+ {
+ "file": "tests/scene-editor-async-closure.spec.ts",
+ "line": 27,
+ "evidenceName": "loading-retry",
+ "testTitle": "加载失败重试与加载互斥:不展示可写半场景、恢复网络后模型完整加载",
+ "kind": "browser",
+ "featureIds": [
+ "project.retry",
+ "glb.pending",
+ "leave.busy"
+ ],
+ "scope": "GET503只留中性失败和重试;延迟受控下载时禁用写入/导出/预览并阻止关闭,释放后真实狐狸加载。"
+ },
+ {
+ "file": "tools/scene-closure-engine.test.mjs",
+ "testTitle": "replacement failure waits for other started loads before restoring the original scene",
+ "featureIds": [
+ "history.failure",
+ "glb.pending"
+ ],
+ "kind": "unit",
+ "scope": "聚焦合同/真实DOM/Three对象计算;不称为浏览器鼠标交互。"
+ },
+ {
+ "file": "../unreal_tran_web/src/features/scene-legacy/contentApiClient.test.mjs",
+ "testTitle": "an upload finishing after a project switch cannot register into the new session",
+ "featureIds": [
+ "glb.pending",
+ "lifecycle.destroy"
+ ],
+ "kind": "unit",
+ "scope": "聚焦合同/真实DOM/Three对象计算;不称为浏览器鼠标交互。"
+ }
+ ]
+ },
+ {
+ "id": "glb.references",
+ "name": "本项目 GLB 稳定资产引用",
+ "group": "工程文件与资源导入",
+ "verification": "PENDING",
+ "plannedEvidence": [
+ {
+ "file": "tests/scene-editor-persistence-closure.spec.ts",
+ "line": 61,
+ "evidenceName": "json-glb-roundtrip",
+ "testTitle": "工程 JSON 与小 GLB:真实上传、损坏拒绝、引用失败回滚、相机参数往返",
+ "kind": "browser",
+ "featureIds": [
+ "json.export",
+ "json.import",
+ "json.invalid",
+ "json.missing-resource",
+ "json.rendering",
+ "glb.import",
+ "glb.invalid",
+ "glb.references",
+ "validation.references"
+ ],
+ "scope": "真实JSON/GLB文件输入、错误schema/损坏资源/上传503拒绝、错误assetCode回滚、稳定URI及渲染参数保存重开。"
+ },
+ {
+ "file": "tools/scene-closure-assets.test.mjs",
+ "testTitle": "copied scene keeps its original pseudo URI but reads its own copied asset by stable code",
+ "featureIds": [
+ "glb.references"
+ ],
+ "kind": "unit",
+ "scope": "聚焦合同/真实DOM/Three对象计算;不称为浏览器鼠标交互。"
+ }
+ ]
+ },
+ {
+ "id": "panel.left-tabs",
+ "name": "场景大纲 / 资源库 / 场景模板切换",
+ "group": "左侧目录与资源放置",
+ "verification": "PENDING",
+ "plannedEvidence": [
+ {
+ "file": "tests/scene-editor-templates-closure.spec.ts",
+ "line": 118,
+ "evidenceName": "template-confirm",
+ "testTitle": "模板搜索与未保存切换确认保留和放弃",
+ "kind": "browser",
+ "featureIds": [
+ "panel.left-tabs",
+ "search.templates",
+ "template.switch-cancel"
+ ],
+ "scope": "中文/无匹配/清空搜索;真实取消保留对象,真实确认放弃并切换空白模板后保存。"
+ }
+ ]
+ },
+ {
+ "id": "search.tree",
+ "name": "对象/部件名称搜索与清空",
+ "group": "左侧目录与资源放置",
+ "verification": "PENDING",
+ "plannedEvidence": [
+ {
+ "file": "tests/scene-editor-templates-closure.spec.ts",
+ "line": 246,
+ "evidenceName": "published-model-assets",
+ "testTitle": "模型成果狐狸卡车双击与真实拖入、树搜索和部件隐藏重开",
+ "kind": "browser",
+ "featureIds": [
+ "assets.catalog",
+ "assets.model-double-click",
+ "assets.model-drop",
+ "search.tree",
+ "part.source",
+ "property.visible"
+ ],
+ "scope": "API 已发布目录,狐狸双击和卡车真实拖入;固定来源版本、树根/部件搜索;卡车刚体部件隐藏保存重开,最后恢复可见。基线树没有折叠按钮,不将搜索伪称折叠。"
+ }
+ ]
+ },
+ {
+ "id": "search.assets",
+ "name": "资源搜索与异步刷新保留筛选",
+ "group": "左侧目录与资源放置",
+ "verification": "PENDING",
+ "plannedEvidence": [
+ {
+ "file": "tests/scene-editor-templates-closure.spec.ts",
+ "line": 161,
+ "evidenceName": "basic-assets",
+ "testTitle": "八种基础资源逐项放置、真实拖放、搜索和保存重开",
+ "kind": "browser",
+ "featureIds": [
+ "assets.basic-double-click",
+ "assets.basic-drop",
+ "search.assets",
+ "panel.objects",
+ "asset.diagnostic",
+ "asset.equipment",
+ "asset.workbench",
+ "asset.shelf",
+ "asset.safety-zone",
+ "asset.camera",
+ "asset.worker",
+ "asset.light"
+ ],
+ "scope": "8 种基础资源分别双击放置并分开布置;真实指针拖入第 9 个工作台、吸附检查、唯一编号、保存刷新一致。照明为现有资源外观,不冒称连接真实灯控。"
+ }
+ ]
+ },
+ {
+ "id": "search.templates",
+ "name": "模板搜索与清空",
+ "group": "左侧目录与资源放置",
+ "verification": "PENDING",
+ "plannedEvidence": [
+ {
+ "file": "tests/scene-editor-templates-closure.spec.ts",
+ "line": 118,
+ "evidenceName": "template-confirm",
+ "testTitle": "模板搜索与未保存切换确认保留和放弃",
+ "kind": "browser",
+ "featureIds": [
+ "panel.left-tabs",
+ "search.templates",
+ "template.switch-cancel"
+ ],
+ "scope": "中文/无匹配/清空搜索;真实取消保留对象,真实确认放弃并切换空白模板后保存。"
+ }
+ ]
+ },
+ {
+ "id": "assets.catalog",
+ "name": "真实已发布模型成果目录",
+ "group": "左侧目录与资源放置",
+ "verification": "PENDING",
+ "plannedEvidence": [
+ {
+ "file": "tests/scene-editor-templates-closure.spec.ts",
+ "line": 246,
+ "evidenceName": "published-model-assets",
+ "testTitle": "模型成果狐狸卡车双击与真实拖入、树搜索和部件隐藏重开",
+ "kind": "browser",
+ "featureIds": [
+ "assets.catalog",
+ "assets.model-double-click",
+ "assets.model-drop",
+ "search.tree",
+ "part.source",
+ "property.visible"
+ ],
+ "scope": "API 已发布目录,狐狸双击和卡车真实拖入;固定来源版本、树根/部件搜索;卡车刚体部件隐藏保存重开,最后恢复可见。基线树没有折叠按钮,不将搜索伪称折叠。"
+ }
+ ]
+ },
+ {
+ "id": "assets.model-double-click",
+ "name": "双击已发布模型放置",
+ "group": "左侧目录与资源放置",
+ "verification": "PENDING",
+ "plannedEvidence": [
+ {
+ "file": "tests/scene-editor-templates-closure.spec.ts",
+ "line": 246,
+ "evidenceName": "published-model-assets",
+ "testTitle": "模型成果狐狸卡车双击与真实拖入、树搜索和部件隐藏重开",
+ "kind": "browser",
+ "featureIds": [
+ "assets.catalog",
+ "assets.model-double-click",
+ "assets.model-drop",
+ "search.tree",
+ "part.source",
+ "property.visible"
+ ],
+ "scope": "API 已发布目录,狐狸双击和卡车真实拖入;固定来源版本、树根/部件搜索;卡车刚体部件隐藏保存重开,最后恢复可见。基线树没有折叠按钮,不将搜索伪称折叠。"
+ },
+ {
+ "file": "tools/scene-model-placement.test.mjs",
+ "testTitle": "fresh skinned placement reaches the requested size and floor after authored scale/pose replay",
+ "featureIds": [
+ "assets.model-double-click",
+ "assets.model-drop"
+ ],
+ "kind": "unit",
+ "scope": "聚焦合同/真实DOM/Three对象计算;不称为浏览器鼠标交互。"
+ }
+ ]
+ },
+ {
+ "id": "assets.model-drop",
+ "name": "拖入已发布模型放置",
+ "group": "左侧目录与资源放置",
+ "verification": "PENDING",
+ "plannedEvidence": [
+ {
+ "file": "tests/scene-editor-templates-closure.spec.ts",
+ "line": 246,
+ "evidenceName": "published-model-assets",
+ "testTitle": "模型成果狐狸卡车双击与真实拖入、树搜索和部件隐藏重开",
+ "kind": "browser",
+ "featureIds": [
+ "assets.catalog",
+ "assets.model-double-click",
+ "assets.model-drop",
+ "search.tree",
+ "part.source",
+ "property.visible"
+ ],
+ "scope": "API 已发布目录,狐狸双击和卡车真实拖入;固定来源版本、树根/部件搜索;卡车刚体部件隐藏保存重开,最后恢复可见。基线树没有折叠按钮,不将搜索伪称折叠。"
+ },
+ {
+ "file": "tools/scene-model-placement.test.mjs",
+ "testTitle": "fresh skinned placement reaches the requested size and floor after authored scale/pose replay",
+ "featureIds": [
+ "assets.model-double-click",
+ "assets.model-drop"
+ ],
+ "kind": "unit",
+ "scope": "聚焦合同/真实DOM/Three对象计算;不称为浏览器鼠标交互。"
+ }
+ ]
+ },
+ {
+ "id": "assets.basic-double-click",
+ "name": "双击基础资源放置",
+ "group": "左侧目录与资源放置",
+ "verification": "PENDING",
+ "plannedEvidence": [
+ {
+ "file": "tests/scene-editor-templates-closure.spec.ts",
+ "line": 161,
+ "evidenceName": "basic-assets",
+ "testTitle": "八种基础资源逐项放置、真实拖放、搜索和保存重开",
+ "kind": "browser",
+ "featureIds": [
+ "assets.basic-double-click",
+ "assets.basic-drop",
+ "search.assets",
+ "panel.objects",
+ "asset.diagnostic",
+ "asset.equipment",
+ "asset.workbench",
+ "asset.shelf",
+ "asset.safety-zone",
+ "asset.camera",
+ "asset.worker",
+ "asset.light"
+ ],
+ "scope": "8 种基础资源分别双击放置并分开布置;真实指针拖入第 9 个工作台、吸附检查、唯一编号、保存刷新一致。照明为现有资源外观,不冒称连接真实灯控。"
+ }
+ ]
+ },
+ {
+ "id": "assets.basic-drop",
+ "name": "基础资源拖入与拖放提示",
+ "group": "左侧目录与资源放置",
+ "verification": "PENDING",
+ "plannedEvidence": [
+ {
+ "file": "tests/scene-editor-templates-closure.spec.ts",
+ "line": 161,
+ "evidenceName": "basic-assets",
+ "testTitle": "八种基础资源逐项放置、真实拖放、搜索和保存重开",
+ "kind": "browser",
+ "featureIds": [
+ "assets.basic-double-click",
+ "assets.basic-drop",
+ "search.assets",
+ "panel.objects",
+ "asset.diagnostic",
+ "asset.equipment",
+ "asset.workbench",
+ "asset.shelf",
+ "asset.safety-zone",
+ "asset.camera",
+ "asset.worker",
+ "asset.light"
+ ],
+ "scope": "8 种基础资源分别双击放置并分开布置;真实指针拖入第 9 个工作台、吸附检查、唯一编号、保存刷新一致。照明为现有资源外观,不冒称连接真实灯控。"
+ }
+ ]
+ },
+ {
+ "id": "assets.model-link",
+ "name": "打开/返回模型编辑器入口",
+ "group": "左侧目录与资源放置",
+ "verification": "PENDING",
+ "plannedEvidence": [
+ {
+ "file": "tools/scene-closure-engine.test.mjs",
+ "testTitle": "model source navigation locates the published owner and falls back without source permission",
+ "featureIds": [
+ "assets.model-link"
+ ],
+ "kind": "unit",
+ "scope": "聚焦合同/真实DOM/Three对象计算;不称为浏览器鼠标交互。"
+ }
+ ]
+ },
+ {
+ "id": "asset.diagnostic",
+ "name": "基础资源:液压诊断工位",
+ "group": "左侧目录与资源放置",
+ "verification": "PENDING",
+ "plannedEvidence": [
+ {
+ "file": "tests/scene-editor-templates-closure.spec.ts",
+ "line": 161,
+ "evidenceName": "basic-assets",
+ "testTitle": "八种基础资源逐项放置、真实拖放、搜索和保存重开",
+ "kind": "browser",
+ "featureIds": [
+ "assets.basic-double-click",
+ "assets.basic-drop",
+ "search.assets",
+ "panel.objects",
+ "asset.diagnostic",
+ "asset.equipment",
+ "asset.workbench",
+ "asset.shelf",
+ "asset.safety-zone",
+ "asset.camera",
+ "asset.worker",
+ "asset.light"
+ ],
+ "scope": "8 种基础资源分别双击放置并分开布置;真实指针拖入第 9 个工作台、吸附检查、唯一编号、保存刷新一致。照明为现有资源外观,不冒称连接真实灯控。"
+ }
+ ]
+ },
+ {
+ "id": "asset.equipment",
+ "name": "基础资源:维修装备主体",
+ "group": "左侧目录与资源放置",
+ "verification": "PENDING",
+ "plannedEvidence": [
+ {
+ "file": "tests/scene-editor-templates-closure.spec.ts",
+ "line": 161,
+ "evidenceName": "basic-assets",
+ "testTitle": "八种基础资源逐项放置、真实拖放、搜索和保存重开",
+ "kind": "browser",
+ "featureIds": [
+ "assets.basic-double-click",
+ "assets.basic-drop",
+ "search.assets",
+ "panel.objects",
+ "asset.diagnostic",
+ "asset.equipment",
+ "asset.workbench",
+ "asset.shelf",
+ "asset.safety-zone",
+ "asset.camera",
+ "asset.worker",
+ "asset.light"
+ ],
+ "scope": "8 种基础资源分别双击放置并分开布置;真实指针拖入第 9 个工作台、吸附检查、唯一编号、保存刷新一致。照明为现有资源外观,不冒称连接真实灯控。"
+ }
+ ]
+ },
+ {
+ "id": "asset.workbench",
+ "name": "基础资源:检修工作台",
+ "group": "左侧目录与资源放置",
+ "verification": "PENDING",
+ "plannedEvidence": [
+ {
+ "file": "tests/scene-editor-templates-closure.spec.ts",
+ "line": 161,
+ "evidenceName": "basic-assets",
+ "testTitle": "八种基础资源逐项放置、真实拖放、搜索和保存重开",
+ "kind": "browser",
+ "featureIds": [
+ "assets.basic-double-click",
+ "assets.basic-drop",
+ "search.assets",
+ "panel.objects",
+ "asset.diagnostic",
+ "asset.equipment",
+ "asset.workbench",
+ "asset.shelf",
+ "asset.safety-zone",
+ "asset.camera",
+ "asset.worker",
+ "asset.light"
+ ],
+ "scope": "8 种基础资源分别双击放置并分开布置;真实指针拖入第 9 个工作台、吸附检查、唯一编号、保存刷新一致。照明为现有资源外观,不冒称连接真实灯控。"
+ }
+ ]
+ },
+ {
+ "id": "asset.shelf",
+ "name": "基础资源:器材存放架",
+ "group": "左侧目录与资源放置",
+ "verification": "PENDING",
+ "plannedEvidence": [
+ {
+ "file": "tests/scene-editor-templates-closure.spec.ts",
+ "line": 161,
+ "evidenceName": "basic-assets",
+ "testTitle": "八种基础资源逐项放置、真实拖放、搜索和保存重开",
+ "kind": "browser",
+ "featureIds": [
+ "assets.basic-double-click",
+ "assets.basic-drop",
+ "search.assets",
+ "panel.objects",
+ "asset.diagnostic",
+ "asset.equipment",
+ "asset.workbench",
+ "asset.shelf",
+ "asset.safety-zone",
+ "asset.camera",
+ "asset.worker",
+ "asset.light"
+ ],
+ "scope": "8 种基础资源分别双击放置并分开布置;真实指针拖入第 9 个工作台、吸附检查、唯一编号、保存刷新一致。照明为现有资源外观,不冒称连接真实灯控。"
+ }
+ ]
+ },
+ {
+ "id": "asset.safety-zone",
+ "name": "基础资源:安全作业区域",
+ "group": "左侧目录与资源放置",
+ "verification": "PENDING",
+ "plannedEvidence": [
+ {
+ "file": "tests/scene-editor-templates-closure.spec.ts",
+ "line": 161,
+ "evidenceName": "basic-assets",
+ "testTitle": "八种基础资源逐项放置、真实拖放、搜索和保存重开",
+ "kind": "browser",
+ "featureIds": [
+ "assets.basic-double-click",
+ "assets.basic-drop",
+ "search.assets",
+ "panel.objects",
+ "asset.diagnostic",
+ "asset.equipment",
+ "asset.workbench",
+ "asset.shelf",
+ "asset.safety-zone",
+ "asset.camera",
+ "asset.worker",
+ "asset.light"
+ ],
+ "scope": "8 种基础资源分别双击放置并分开布置;真实指针拖入第 9 个工作台、吸附检查、唯一编号、保存刷新一致。照明为现有资源外观,不冒称连接真实灯控。"
+ }
+ ]
+ },
+ {
+ "id": "asset.camera",
+ "name": "基础资源:视觉采集设备",
+ "group": "左侧目录与资源放置",
+ "verification": "PENDING",
+ "plannedEvidence": [
+ {
+ "file": "tests/scene-editor-templates-closure.spec.ts",
+ "line": 161,
+ "evidenceName": "basic-assets",
+ "testTitle": "八种基础资源逐项放置、真实拖放、搜索和保存重开",
+ "kind": "browser",
+ "featureIds": [
+ "assets.basic-double-click",
+ "assets.basic-drop",
+ "search.assets",
+ "panel.objects",
+ "asset.diagnostic",
+ "asset.equipment",
+ "asset.workbench",
+ "asset.shelf",
+ "asset.safety-zone",
+ "asset.camera",
+ "asset.worker",
+ "asset.light"
+ ],
+ "scope": "8 种基础资源分别双击放置并分开布置;真实指针拖入第 9 个工作台、吸附检查、唯一编号、保存刷新一致。照明为现有资源外观,不冒称连接真实灯控。"
+ }
+ ]
+ },
+ {
+ "id": "asset.worker",
+ "name": "基础资源:训练人员占位",
+ "group": "左侧目录与资源放置",
+ "verification": "PENDING",
+ "plannedEvidence": [
+ {
+ "file": "tests/scene-editor-templates-closure.spec.ts",
+ "line": 161,
+ "evidenceName": "basic-assets",
+ "testTitle": "八种基础资源逐项放置、真实拖放、搜索和保存重开",
+ "kind": "browser",
+ "featureIds": [
+ "assets.basic-double-click",
+ "assets.basic-drop",
+ "search.assets",
+ "panel.objects",
+ "asset.diagnostic",
+ "asset.equipment",
+ "asset.workbench",
+ "asset.shelf",
+ "asset.safety-zone",
+ "asset.camera",
+ "asset.worker",
+ "asset.light"
+ ],
+ "scope": "8 种基础资源分别双击放置并分开布置;真实指针拖入第 9 个工作台、吸附检查、唯一编号、保存刷新一致。照明为现有资源外观,不冒称连接真实灯控。"
+ }
+ ]
+ },
+ {
+ "id": "asset.light",
+ "name": "基础资源:区域照明",
+ "group": "左侧目录与资源放置",
+ "verification": "PENDING",
+ "plannedEvidence": [
+ {
+ "file": "tests/scene-editor-templates-closure.spec.ts",
+ "line": 161,
+ "evidenceName": "basic-assets",
+ "testTitle": "八种基础资源逐项放置、真实拖放、搜索和保存重开",
+ "kind": "browser",
+ "featureIds": [
+ "assets.basic-double-click",
+ "assets.basic-drop",
+ "search.assets",
+ "panel.objects",
+ "asset.diagnostic",
+ "asset.equipment",
+ "asset.workbench",
+ "asset.shelf",
+ "asset.safety-zone",
+ "asset.camera",
+ "asset.worker",
+ "asset.light"
+ ],
+ "scope": "8 种基础资源分别双击放置并分开布置;真实指针拖入第 9 个工作台、吸附检查、唯一编号、保存刷新一致。照明为现有资源外观,不冒称连接真实灯控。"
+ }
+ ]
+ },
+ {
+ "id": "asset.vehicle-depot",
+ "name": "场地资源:真实装备车库",
+ "group": "左侧目录与资源放置",
+ "verification": "PENDING",
+ "plannedEvidence": [
+ {
+ "file": "tests/scene-editor-templates-closure.spec.ts",
+ "line": 193,
+ "evidenceName": "venue-assets",
+ "testTitle": "两种整体场地加载、重复放置阻止与显式删除后替换",
+ "kind": "browser",
+ "featureIds": [
+ "asset.vehicle-depot",
+ "asset.field-terrain",
+ "object.delete"
+ ],
+ "scope": "车库和起伏地形真实加载并分别保存重开;已有整体场地时阻止叠加,显式删除旧场地后才能替换。"
+ }
+ ]
+ },
+ {
+ "id": "asset.field-terrain",
+ "name": "场地资源:真实野外地形",
+ "group": "左侧目录与资源放置",
+ "verification": "PENDING",
+ "plannedEvidence": [
+ {
+ "file": "tests/scene-editor-templates-closure.spec.ts",
+ "line": 193,
+ "evidenceName": "venue-assets",
+ "testTitle": "两种整体场地加载、重复放置阻止与显式删除后替换",
+ "kind": "browser",
+ "featureIds": [
+ "asset.vehicle-depot",
+ "asset.field-terrain",
+ "object.delete"
+ ],
+ "scope": "车库和起伏地形真实加载并分别保存重开;已有整体场地时阻止叠加,显式删除旧场地后才能替换。"
+ }
+ ]
+ },
+ {
+ "id": "template.depot-1",
+ "name": "真实装备车库",
+ "group": "全部可达场景模板",
+ "verification": "PENDING",
+ "plannedEvidence": [
+ {
+ "file": "tests/scene-editor-templates-closure.spec.ts",
+ "line": 85,
+ "evidenceName": "template-depot-1",
+ "testTitle": "场景模板实际加载、保存重开:真实装备车库",
+ "kind": "browser",
+ "featureIds": [
+ "template.depot-1"
+ ],
+ "scope": "循环参数化模板用例,每种独立副本、真实点击、保存、刷新与截图。"
+ }
+ ]
+ },
+ {
+ "id": "template.terrain-1",
+ "name": "真实野外训练场",
+ "group": "全部可达场景模板",
+ "verification": "PENDING",
+ "plannedEvidence": [
+ {
+ "file": "tests/scene-editor-templates-closure.spec.ts",
+ "line": 85,
+ "evidenceName": "template-terrain-1",
+ "testTitle": "场景模板实际加载、保存重开:真实野外训练场",
+ "kind": "browser",
+ "featureIds": [
+ "template.terrain-1"
+ ],
+ "scope": "循环参数化模板用例,每种独立副本、真实点击、保存、刷新与截图。"
+ }
+ ]
+ },
+ {
+ "id": "template.workshop-1",
+ "name": "综合检验车间",
+ "group": "全部可达场景模板",
+ "verification": "PENDING",
+ "plannedEvidence": [
+ {
+ "file": "tests/scene-editor-templates-closure.spec.ts",
+ "line": 85,
+ "evidenceName": "template-workshop-1",
+ "testTitle": "场景模板实际加载、保存重开:综合检验车间",
+ "kind": "browser",
+ "featureIds": [
+ "template.workshop-1"
+ ],
+ "scope": "循环参数化模板用例,每种独立副本、真实点击、保存、刷新与截图。"
+ }
+ ]
+ },
+ {
+ "id": "template.workshop-real",
+ "name": "演示维修车间(可拆分)",
+ "group": "全部可达场景模板",
+ "verification": "PENDING",
+ "plannedEvidence": [
+ {
+ "file": "tests/scene-editor-templates-closure.spec.ts",
+ "line": 85,
+ "evidenceName": "template-workshop-real",
+ "testTitle": "场景模板实际加载、保存重开:演示维修车间(可拆分)",
+ "kind": "browser",
+ "featureIds": [
+ "template.workshop-real"
+ ],
+ "scope": "循环参数化模板用例,每种独立副本、真实点击、保存、刷新与截图。"
+ }
+ ]
+ },
+ {
+ "id": "template.workshop-2",
+ "name": "总成拆卸车间",
+ "group": "全部可达场景模板",
+ "verification": "PENDING",
+ "plannedEvidence": [
+ {
+ "file": "tests/scene-editor-templates-closure.spec.ts",
+ "line": 85,
+ "evidenceName": "template-workshop-2",
+ "testTitle": "场景模板实际加载、保存重开:总成拆卸车间",
+ "kind": "browser",
+ "featureIds": [
+ "template.workshop-2"
+ ],
+ "scope": "循环参数化模板用例,每种独立副本、真实点击、保存、刷新与截图。"
+ }
+ ]
+ },
+ {
+ "id": "template.workshop-3",
+ "name": "部件分解车间",
+ "group": "全部可达场景模板",
+ "verification": "PENDING",
+ "plannedEvidence": [
+ {
+ "file": "tests/scene-editor-templates-closure.spec.ts",
+ "line": 85,
+ "evidenceName": "template-workshop-3",
+ "testTitle": "场景模板实际加载、保存重开:部件分解车间",
+ "kind": "browser",
+ "featureIds": [
+ "template.workshop-3"
+ ],
+ "scope": "循环参数化模板用例,每种独立副本、真实点击、保存、刷新与截图。"
+ }
+ ]
+ },
+ {
+ "id": "template.workshop-4",
+ "name": "清洗检测车间",
+ "group": "全部可达场景模板",
+ "verification": "PENDING",
+ "plannedEvidence": [
+ {
+ "file": "tests/scene-editor-templates-closure.spec.ts",
+ "line": 85,
+ "evidenceName": "template-workshop-4",
+ "testTitle": "场景模板实际加载、保存重开:清洗检测车间",
+ "kind": "browser",
+ "featureIds": [
+ "template.workshop-4"
+ ],
+ "scope": "循环参数化模板用例,每种独立副本、真实点击、保存、刷新与截图。"
+ }
+ ]
+ },
+ {
+ "id": "template.workshop-5",
+ "name": "复装调试车间",
+ "group": "全部可达场景模板",
+ "verification": "PENDING",
+ "plannedEvidence": [
+ {
+ "file": "tests/scene-editor-templates-closure.spec.ts",
+ "line": 85,
+ "evidenceName": "template-workshop-5",
+ "testTitle": "场景模板实际加载、保存重开:复装调试车间",
+ "kind": "browser",
+ "featureIds": [
+ "template.workshop-5"
+ ],
+ "scope": "循环参数化模板用例,每种独立副本、真实点击、保存、刷新与截图。"
+ }
+ ]
+ },
+ {
+ "id": "template.outdoor-1",
+ "name": "室外综合训练场",
+ "group": "全部可达场景模板",
+ "verification": "PENDING",
+ "plannedEvidence": [
+ {
+ "file": "tests/scene-editor-templates-closure.spec.ts",
+ "line": 85,
+ "evidenceName": "template-outdoor-1",
+ "testTitle": "场景模板实际加载、保存重开:室外综合训练场",
+ "kind": "browser",
+ "featureIds": [
+ "template.outdoor-1"
+ ],
+ "scope": "循环参数化模板用例,每种独立副本、真实点击、保存、刷新与截图。"
+ }
+ ]
+ },
+ {
+ "id": "template.switch-cancel",
+ "name": "未保存模板切换取消",
+ "group": "全部可达场景模板",
+ "verification": "PENDING",
+ "plannedEvidence": [
+ {
+ "file": "tests/scene-editor-templates-closure.spec.ts",
+ "line": 118,
+ "evidenceName": "template-confirm",
+ "testTitle": "模板搜索与未保存切换确认保留和放弃",
+ "kind": "browser",
+ "featureIds": [
+ "panel.left-tabs",
+ "search.templates",
+ "template.switch-cancel"
+ ],
+ "scope": "中文/无匹配/清空搜索;真实取消保留对象,真实确认放弃并切换空白模板后保存。"
+ }
+ ]
+ },
+ {
+ "id": "view.orbit",
+ "name": "鼠标旋转相机",
+ "group": "三维视口、选择与变换",
+ "verification": "PENDING",
+ "plannedEvidence": [
+ {
+ "file": "tests/scene-editor-viewport-closure.spec.ts",
+ "line": 117,
+ "evidenceName": "viewport-camera-theme",
+ "testTitle": "真实相机操控、视口拾取、预览禁写和两尺寸主题可读性",
+ "kind": "browser",
+ "featureIds": [
+ "view.orbit",
+ "view.pan",
+ "view.zoom",
+ "view.top",
+ "view.perspective",
+ "view.grid",
+ "view.focus",
+ "selection.viewport",
+ "selection.highlight",
+ "preview.enter",
+ "preview.exit",
+ "preview.lock",
+ "lifecycle.resize-theme"
+ ],
+ "scope": "以源测试实际断言为准;计划关联不代表最终通过。"
+ }
+ ]
+ },
+ {
+ "id": "view.pan",
+ "name": "鼠标平移相机",
+ "group": "三维视口、选择与变换",
+ "verification": "PENDING",
+ "plannedEvidence": [
+ {
+ "file": "tests/scene-editor-viewport-closure.spec.ts",
+ "line": 117,
+ "evidenceName": "viewport-camera-theme",
+ "testTitle": "真实相机操控、视口拾取、预览禁写和两尺寸主题可读性",
+ "kind": "browser",
+ "featureIds": [
+ "view.orbit",
+ "view.pan",
+ "view.zoom",
+ "view.top",
+ "view.perspective",
+ "view.grid",
+ "view.focus",
+ "selection.viewport",
+ "selection.highlight",
+ "preview.enter",
+ "preview.exit",
+ "preview.lock",
+ "lifecycle.resize-theme"
+ ],
+ "scope": "以源测试实际断言为准;计划关联不代表最终通过。"
+ }
+ ]
+ },
+ {
+ "id": "view.zoom",
+ "name": "滚轮缩放相机",
+ "group": "三维视口、选择与变换",
+ "verification": "PENDING",
+ "plannedEvidence": [
+ {
+ "file": "tests/scene-editor-viewport-closure.spec.ts",
+ "line": 117,
+ "evidenceName": "viewport-camera-theme",
+ "testTitle": "真实相机操控、视口拾取、预览禁写和两尺寸主题可读性",
+ "kind": "browser",
+ "featureIds": [
+ "view.orbit",
+ "view.pan",
+ "view.zoom",
+ "view.top",
+ "view.perspective",
+ "view.grid",
+ "view.focus",
+ "selection.viewport",
+ "selection.highlight",
+ "preview.enter",
+ "preview.exit",
+ "preview.lock",
+ "lifecycle.resize-theme"
+ ],
+ "scope": "以源测试实际断言为准;计划关联不代表最终通过。"
+ }
+ ]
+ },
+ {
+ "id": "view.perspective",
+ "name": "透视按钮",
+ "group": "三维视口、选择与变换",
+ "verification": "PENDING",
+ "plannedEvidence": [
+ {
+ "file": "tests/scene-editor-viewport-closure.spec.ts",
+ "line": 117,
+ "evidenceName": "viewport-camera-theme",
+ "testTitle": "真实相机操控、视口拾取、预览禁写和两尺寸主题可读性",
+ "kind": "browser",
+ "featureIds": [
+ "view.orbit",
+ "view.pan",
+ "view.zoom",
+ "view.top",
+ "view.perspective",
+ "view.grid",
+ "view.focus",
+ "selection.viewport",
+ "selection.highlight",
+ "preview.enter",
+ "preview.exit",
+ "preview.lock",
+ "lifecycle.resize-theme"
+ ],
+ "scope": "以源测试实际断言为准;计划关联不代表最终通过。"
+ }
+ ]
+ },
+ {
+ "id": "view.top",
+ "name": "顶视图按钮",
+ "group": "三维视口、选择与变换",
+ "verification": "PENDING",
+ "plannedEvidence": [
+ {
+ "file": "tests/scene-editor-viewport-closure.spec.ts",
+ "line": 117,
+ "evidenceName": "viewport-camera-theme",
+ "testTitle": "真实相机操控、视口拾取、预览禁写和两尺寸主题可读性",
+ "kind": "browser",
+ "featureIds": [
+ "view.orbit",
+ "view.pan",
+ "view.zoom",
+ "view.top",
+ "view.perspective",
+ "view.grid",
+ "view.focus",
+ "selection.viewport",
+ "selection.highlight",
+ "preview.enter",
+ "preview.exit",
+ "preview.lock",
+ "lifecycle.resize-theme"
+ ],
+ "scope": "以源测试实际断言为准;计划关联不代表最终通过。"
+ }
+ ]
+ },
+ {
+ "id": "view.focus",
+ "name": "聚焦选中对象",
+ "group": "三维视口、选择与变换",
+ "verification": "PENDING",
+ "plannedEvidence": [
+ {
+ "file": "tests/scene-editor-viewport-closure.spec.ts",
+ "line": 117,
+ "evidenceName": "viewport-camera-theme",
+ "testTitle": "真实相机操控、视口拾取、预览禁写和两尺寸主题可读性",
+ "kind": "browser",
+ "featureIds": [
+ "view.orbit",
+ "view.pan",
+ "view.zoom",
+ "view.top",
+ "view.perspective",
+ "view.grid",
+ "view.focus",
+ "selection.viewport",
+ "selection.highlight",
+ "preview.enter",
+ "preview.exit",
+ "preview.lock",
+ "lifecycle.resize-theme"
+ ],
+ "scope": "以源测试实际断言为准;计划关联不代表最终通过。"
+ }
+ ]
+ },
+ {
+ "id": "view.grid",
+ "name": "网格显隐",
+ "group": "三维视口、选择与变换",
+ "verification": "PENDING",
+ "plannedEvidence": [
+ {
+ "file": "tests/scene-editor-viewport-closure.spec.ts",
+ "line": 117,
+ "evidenceName": "viewport-camera-theme",
+ "testTitle": "真实相机操控、视口拾取、预览禁写和两尺寸主题可读性",
+ "kind": "browser",
+ "featureIds": [
+ "view.orbit",
+ "view.pan",
+ "view.zoom",
+ "view.top",
+ "view.perspective",
+ "view.grid",
+ "view.focus",
+ "selection.viewport",
+ "selection.highlight",
+ "preview.enter",
+ "preview.exit",
+ "preview.lock",
+ "lifecycle.resize-theme"
+ ],
+ "scope": "以源测试实际断言为准;计划关联不代表最终通过。"
+ },
+ {
+ "file": "tools/scene-cover-capture.test.mjs",
+ "testTitle": "copies synchronously without bounds, gizmo, grid or selection glow, then restores before JPEG encoding",
+ "featureIds": [
+ "cover.capture",
+ "selection.highlight",
+ "view.grid"
+ ],
+ "kind": "unit",
+ "scope": "聚焦合同/真实DOM/Three对象计算;不称为浏览器鼠标交互。"
+ }
+ ]
+ },
+ {
+ "id": "view.enclosure-full",
+ "name": "车库外壳完整显示",
+ "group": "三维视口、选择与变换",
+ "verification": "PENDING",
+ "plannedEvidence": [
+ {
+ "file": "tests/scene-editor-workflow-closure.spec.ts",
+ "line": 121,
+ "evidenceName": "truck-part-history",
+ "testTitle": "牛奶卡车:刚性部件独立变换、软删除、历史与保存基线一致",
+ "kind": "browser",
+ "featureIds": [
+ "project.save-key",
+ "part.transform",
+ "part.soft-delete",
+ "history.undo",
+ "history.redo",
+ "property.name",
+ "view.enclosure-full",
+ "view.enclosure-xray",
+ "view.enclosure-hidden"
+ ],
+ "scope": "实际部件变换和软删除、按钮/键盘历史、保存基线与输入框自身撤销;双实例隔离及车库围护三种模式真实点击、导出和隐藏后保存刷新。"
+ }
+ ]
+ },
+ {
+ "id": "view.enclosure-xray",
+ "name": "车库外壳透明查看",
+ "group": "三维视口、选择与变换",
+ "verification": "PENDING",
+ "plannedEvidence": [
+ {
+ "file": "tests/scene-editor-workflow-closure.spec.ts",
+ "line": 121,
+ "evidenceName": "truck-part-history",
+ "testTitle": "牛奶卡车:刚性部件独立变换、软删除、历史与保存基线一致",
+ "kind": "browser",
+ "featureIds": [
+ "project.save-key",
+ "part.transform",
+ "part.soft-delete",
+ "history.undo",
+ "history.redo",
+ "property.name",
+ "view.enclosure-full",
+ "view.enclosure-xray",
+ "view.enclosure-hidden"
+ ],
+ "scope": "实际部件变换和软删除、按钮/键盘历史、保存基线与输入框自身撤销;双实例隔离及车库围护三种模式真实点击、导出和隐藏后保存刷新。"
+ }
+ ]
+ },
+ {
+ "id": "view.enclosure-hidden",
+ "name": "隐藏车库外壳",
+ "group": "三维视口、选择与变换",
+ "verification": "PENDING",
+ "plannedEvidence": [
+ {
+ "file": "tests/scene-editor-workflow-closure.spec.ts",
+ "line": 121,
+ "evidenceName": "truck-part-history",
+ "testTitle": "牛奶卡车:刚性部件独立变换、软删除、历史与保存基线一致",
+ "kind": "browser",
+ "featureIds": [
+ "project.save-key",
+ "part.transform",
+ "part.soft-delete",
+ "history.undo",
+ "history.redo",
+ "property.name",
+ "view.enclosure-full",
+ "view.enclosure-xray",
+ "view.enclosure-hidden"
+ ],
+ "scope": "实际部件变换和软删除、按钮/键盘历史、保存基线与输入框自身撤销;双实例隔离及车库围护三种模式真实点击、导出和隐藏后保存刷新。"
+ }
+ ]
+ },
+ {
+ "id": "selection.viewport",
+ "name": "点击视口选中根对象/部件",
+ "group": "三维视口、选择与变换",
+ "verification": "PENDING",
+ "plannedEvidence": [
+ {
+ "file": "tests/scene-editor-viewport-closure.spec.ts",
+ "line": 117,
+ "evidenceName": "viewport-camera-theme",
+ "testTitle": "真实相机操控、视口拾取、预览禁写和两尺寸主题可读性",
+ "kind": "browser",
+ "featureIds": [
+ "view.orbit",
+ "view.pan",
+ "view.zoom",
+ "view.top",
+ "view.perspective",
+ "view.grid",
+ "view.focus",
+ "selection.viewport",
+ "selection.highlight",
+ "preview.enter",
+ "preview.exit",
+ "preview.lock",
+ "lifecycle.resize-theme"
+ ],
+ "scope": "以源测试实际断言为准;计划关联不代表最终通过。"
+ }
+ ]
+ },
+ {
+ "id": "selection.tree",
+ "name": "大纲根对象与部件选中",
+ "group": "三维视口、选择与变换",
+ "verification": "PENDING",
+ "plannedEvidence": [
+ {
+ "file": "tests/scene-editor-workflow-closure.spec.ts",
+ "line": 64,
+ "evidenceName": "fox-properties-cover",
+ "testTitle": "狐狸:真实细分权限、全部属性、清空绑定、蒙皮保护、相机与封面保存重开",
+ "kind": "browser",
+ "featureIds": [
+ "project.load",
+ "project.title",
+ "project.save",
+ "selection.tree",
+ "property.name",
+ "property.visible",
+ "property.operable",
+ "property.position",
+ "property.rotation",
+ "property.scale",
+ "property.device-id",
+ "property.model-id",
+ "property.semantic",
+ "part.source",
+ "panel.binding",
+ "cover.capture",
+ "cover.atomic"
+ ],
+ "scope": "真实字段输入与清空、六个语义选项、蒙皮保护、源模型可见,正文/资产/封面保存和刷新;不把原件旧模板组合当新模板结果。"
+ },
+ {
+ "file": "tools/scene-closure-engine.test.mjs",
+ "testTitle": "two published instances have unique tree part IDs and editing the second persists independently after reopen",
+ "featureIds": [
+ "part.transform",
+ "selection.tree"
+ ],
+ "kind": "unit",
+ "scope": "聚焦合同/真实DOM/Three对象计算;不称为浏览器鼠标交互。"
+ },
+ {
+ "file": "tools/scene-closure-engine.test.mjs",
+ "testTitle": "successful parallel restores retain document order after delayed GLBs and split-object batches finish out of order",
+ "featureIds": [
+ "project.load",
+ "selection.tree"
+ ],
+ "kind": "unit",
+ "scope": "聚焦合同/真实DOM/Three对象计算;不称为浏览器鼠标交互。"
+ }
+ ]
+ },
+ {
+ "id": "selection.hidden",
+ "name": "隐藏祖先与软删除部件不可误拾取",
+ "group": "三维视口、选择与变换",
+ "verification": "PENDING",
+ "plannedEvidence": [
+ {
+ "file": "tools/scene-closure-engine.test.mjs",
+ "testTitle": "real Three ray hits on hidden descendants are excluded before selecting a root or part",
+ "featureIds": [
+ "selection.hidden"
+ ],
+ "kind": "unit",
+ "scope": "聚焦合同/真实DOM/Three对象计算;不称为浏览器鼠标交互。"
+ }
+ ]
+ },
+ {
+ "id": "selection.highlight",
+ "name": "选中高亮与恢复原材质",
+ "group": "三维视口、选择与变换",
+ "verification": "PENDING",
+ "plannedEvidence": [
+ {
+ "file": "tests/scene-editor-viewport-closure.spec.ts",
+ "line": 117,
+ "evidenceName": "viewport-camera-theme",
+ "testTitle": "真实相机操控、视口拾取、预览禁写和两尺寸主题可读性",
+ "kind": "browser",
+ "featureIds": [
+ "view.orbit",
+ "view.pan",
+ "view.zoom",
+ "view.top",
+ "view.perspective",
+ "view.grid",
+ "view.focus",
+ "selection.viewport",
+ "selection.highlight",
+ "preview.enter",
+ "preview.exit",
+ "preview.lock",
+ "lifecycle.resize-theme"
+ ],
+ "scope": "以源测试实际断言为准;计划关联不代表最终通过。"
+ },
+ {
+ "file": "tools/scene-closure-engine.test.mjs",
+ "testTitle": "selection highlight cleanup restores source materials and releases its own GPU helper",
+ "featureIds": [
+ "selection.highlight",
+ "lifecycle.destroy"
+ ],
+ "kind": "unit",
+ "scope": "聚焦合同/真实DOM/Three对象计算;不称为浏览器鼠标交互。"
+ },
+ {
+ "file": "tools/scene-cover-capture.test.mjs",
+ "testTitle": "copies synchronously without bounds, gizmo, grid or selection glow, then restores before JPEG encoding",
+ "featureIds": [
+ "cover.capture",
+ "selection.highlight",
+ "view.grid"
+ ],
+ "kind": "unit",
+ "scope": "聚焦合同/真实DOM/Three对象计算;不称为浏览器鼠标交互。"
+ }
+ ]
+ },
+ {
+ "id": "transform.translate",
+ "name": "移动工具及真实 XYZ 手柄",
+ "group": "三维视口、选择与变换",
+ "verification": "PENDING",
+ "plannedEvidence": [
+ {
+ "file": "tests/scene-editor-viewport-closure.spec.ts",
+ "line": 148,
+ "evidenceName": "viewport-transform",
+ "testTitle": "真实变换手柄移动旋转缩放、吸附与保存重开",
+ "kind": "browser",
+ "featureIds": [
+ "transform.translate",
+ "transform.rotate",
+ "transform.scale",
+ "transform.keys",
+ "transform.snap"
+ ],
+ "scope": "以源测试实际断言为准;计划关联不代表最终通过。"
+ }
+ ]
+ },
+ {
+ "id": "transform.rotate",
+ "name": "旋转工具及真实旋转手柄",
+ "group": "三维视口、选择与变换",
+ "verification": "PENDING",
+ "plannedEvidence": [
+ {
+ "file": "tests/scene-editor-viewport-closure.spec.ts",
+ "line": 148,
+ "evidenceName": "viewport-transform",
+ "testTitle": "真实变换手柄移动旋转缩放、吸附与保存重开",
+ "kind": "browser",
+ "featureIds": [
+ "transform.translate",
+ "transform.rotate",
+ "transform.scale",
+ "transform.keys",
+ "transform.snap"
+ ],
+ "scope": "以源测试实际断言为准;计划关联不代表最终通过。"
+ }
+ ]
+ },
+ {
+ "id": "transform.scale",
+ "name": "缩放工具及真实缩放手柄",
+ "group": "三维视口、选择与变换",
+ "verification": "PENDING",
+ "plannedEvidence": [
+ {
+ "file": "tests/scene-editor-viewport-closure.spec.ts",
+ "line": 148,
+ "evidenceName": "viewport-transform",
+ "testTitle": "真实变换手柄移动旋转缩放、吸附与保存重开",
+ "kind": "browser",
+ "featureIds": [
+ "transform.translate",
+ "transform.rotate",
+ "transform.scale",
+ "transform.keys",
+ "transform.snap"
+ ],
+ "scope": "以源测试实际断言为准;计划关联不代表最终通过。"
+ },
+ {
+ "file": "tools/scene-closure-engine.test.mjs",
+ "testTitle": "gizmo scaling across its pivot follows the same strictly positive scale contract as inspector edits",
+ "featureIds": [
+ "transform.scale",
+ "property.scale"
+ ],
+ "kind": "unit",
+ "scope": "聚焦合同/真实DOM/Three对象计算;不称为浏览器鼠标交互。"
+ }
+ ]
+ },
+ {
+ "id": "transform.keys",
+ "name": "W/E/R 快捷键",
+ "group": "三维视口、选择与变换",
+ "verification": "PENDING",
+ "plannedEvidence": [
+ {
+ "file": "tests/scene-editor-viewport-closure.spec.ts",
+ "line": 148,
+ "evidenceName": "viewport-transform",
+ "testTitle": "真实变换手柄移动旋转缩放、吸附与保存重开",
+ "kind": "browser",
+ "featureIds": [
+ "transform.translate",
+ "transform.rotate",
+ "transform.scale",
+ "transform.keys",
+ "transform.snap"
+ ],
+ "scope": "以源测试实际断言为准;计划关联不代表最终通过。"
+ },
+ {
+ "file": "tools/scene-closure-engine.test.mjs",
+ "testTitle": "typing undo/redo stays inside text controls and inactive cached editors ignore global shortcuts",
+ "featureIds": [
+ "history.undo",
+ "history.redo",
+ "transform.keys",
+ "lifecycle.cache"
+ ],
+ "kind": "unit",
+ "scope": "聚焦合同/真实DOM/Three对象计算;不称为浏览器鼠标交互。"
+ }
+ ]
+ },
+ {
+ "id": "transform.snap",
+ "name": "吸附 0.5m 与旋转 15° 开关",
+ "group": "三维视口、选择与变换",
+ "verification": "PENDING",
+ "plannedEvidence": [
+ {
+ "file": "tests/scene-editor-viewport-closure.spec.ts",
+ "line": 148,
+ "evidenceName": "viewport-transform",
+ "testTitle": "真实变换手柄移动旋转缩放、吸附与保存重开",
+ "kind": "browser",
+ "featureIds": [
+ "transform.translate",
+ "transform.rotate",
+ "transform.scale",
+ "transform.keys",
+ "transform.snap"
+ ],
+ "scope": "以源测试实际断言为准;计划关联不代表最终通过。"
+ }
+ ]
+ },
+ {
+ "id": "property.name",
+ "name": "显示名称",
+ "group": "对象属性、部件与底部面板",
+ "verification": "PENDING",
+ "plannedEvidence": [
+ {
+ "file": "tests/scene-editor-workflow-closure.spec.ts",
+ "line": 64,
+ "evidenceName": "fox-properties-cover",
+ "testTitle": "狐狸:真实细分权限、全部属性、清空绑定、蒙皮保护、相机与封面保存重开",
+ "kind": "browser",
+ "featureIds": [
+ "project.load",
+ "project.title",
+ "project.save",
+ "selection.tree",
+ "property.name",
+ "property.visible",
+ "property.operable",
+ "property.position",
+ "property.rotation",
+ "property.scale",
+ "property.device-id",
+ "property.model-id",
+ "property.semantic",
+ "part.source",
+ "panel.binding",
+ "cover.capture",
+ "cover.atomic"
+ ],
+ "scope": "真实字段输入与清空、六个语义选项、蒙皮保护、源模型可见,正文/资产/封面保存和刷新;不把原件旧模板组合当新模板结果。"
+ },
+ {
+ "file": "tests/scene-editor-workflow-closure.spec.ts",
+ "line": 121,
+ "evidenceName": "truck-part-history",
+ "testTitle": "牛奶卡车:刚性部件独立变换、软删除、历史与保存基线一致",
+ "kind": "browser",
+ "featureIds": [
+ "project.save-key",
+ "part.transform",
+ "part.soft-delete",
+ "history.undo",
+ "history.redo",
+ "property.name",
+ "view.enclosure-full",
+ "view.enclosure-xray",
+ "view.enclosure-hidden"
+ ],
+ "scope": "实际部件变换和软删除、按钮/键盘历史、保存基线与输入框自身撤销;双实例隔离及车库围护三种模式真实点击、导出和隐藏后保存刷新。"
+ }
+ ]
+ },
+ {
+ "id": "property.visible",
+ "name": "在场景中可见",
+ "group": "对象属性、部件与底部面板",
+ "verification": "PENDING",
+ "plannedEvidence": [
+ {
+ "file": "tests/scene-editor-templates-closure.spec.ts",
+ "line": 246,
+ "evidenceName": "published-model-assets",
+ "testTitle": "模型成果狐狸卡车双击与真实拖入、树搜索和部件隐藏重开",
+ "kind": "browser",
+ "featureIds": [
+ "assets.catalog",
+ "assets.model-double-click",
+ "assets.model-drop",
+ "search.tree",
+ "part.source",
+ "property.visible"
+ ],
+ "scope": "API 已发布目录,狐狸双击和卡车真实拖入;固定来源版本、树根/部件搜索;卡车刚体部件隐藏保存重开,最后恢复可见。基线树没有折叠按钮,不将搜索伪称折叠。"
+ },
+ {
+ "file": "tests/scene-editor-workflow-closure.spec.ts",
+ "line": 64,
+ "evidenceName": "fox-properties-cover",
+ "testTitle": "狐狸:真实细分权限、全部属性、清空绑定、蒙皮保护、相机与封面保存重开",
+ "kind": "browser",
+ "featureIds": [
+ "project.load",
+ "project.title",
+ "project.save",
+ "selection.tree",
+ "property.name",
+ "property.visible",
+ "property.operable",
+ "property.position",
+ "property.rotation",
+ "property.scale",
+ "property.device-id",
+ "property.model-id",
+ "property.semantic",
+ "part.source",
+ "panel.binding",
+ "cover.capture",
+ "cover.atomic"
+ ],
+ "scope": "真实字段输入与清空、六个语义选项、蒙皮保护、源模型可见,正文/资产/封面保存和刷新;不把原件旧模板组合当新模板结果。"
+ }
+ ]
+ },
+ {
+ "id": "property.operable",
+ "name": "可作为实训目标",
+ "group": "对象属性、部件与底部面板",
+ "verification": "PENDING",
+ "plannedEvidence": [
+ {
+ "file": "tests/scene-editor-workflow-closure.spec.ts",
+ "line": 64,
+ "evidenceName": "fox-properties-cover",
+ "testTitle": "狐狸:真实细分权限、全部属性、清空绑定、蒙皮保护、相机与封面保存重开",
+ "kind": "browser",
+ "featureIds": [
+ "project.load",
+ "project.title",
+ "project.save",
+ "selection.tree",
+ "property.name",
+ "property.visible",
+ "property.operable",
+ "property.position",
+ "property.rotation",
+ "property.scale",
+ "property.device-id",
+ "property.model-id",
+ "property.semantic",
+ "part.source",
+ "panel.binding",
+ "cover.capture",
+ "cover.atomic"
+ ],
+ "scope": "真实字段输入与清空、六个语义选项、蒙皮保护、源模型可见,正文/资产/封面保存和刷新;不把原件旧模板组合当新模板结果。"
+ }
+ ]
+ },
+ {
+ "id": "property.position",
+ "name": "位置 X/Y/Z 数值",
+ "group": "对象属性、部件与底部面板",
+ "verification": "PENDING",
+ "plannedEvidence": [
+ {
+ "file": "tests/scene-editor-workflow-closure.spec.ts",
+ "line": 64,
+ "evidenceName": "fox-properties-cover",
+ "testTitle": "狐狸:真实细分权限、全部属性、清空绑定、蒙皮保护、相机与封面保存重开",
+ "kind": "browser",
+ "featureIds": [
+ "project.load",
+ "project.title",
+ "project.save",
+ "selection.tree",
+ "property.name",
+ "property.visible",
+ "property.operable",
+ "property.position",
+ "property.rotation",
+ "property.scale",
+ "property.device-id",
+ "property.model-id",
+ "property.semantic",
+ "part.source",
+ "panel.binding",
+ "cover.capture",
+ "cover.atomic"
+ ],
+ "scope": "真实字段输入与清空、六个语义选项、蒙皮保护、源模型可见,正文/资产/封面保存和刷新;不把原件旧模板组合当新模板结果。"
+ }
+ ]
+ },
+ {
+ "id": "property.rotation",
+ "name": "旋转 X/Y/Z 数值",
+ "group": "对象属性、部件与底部面板",
+ "verification": "PENDING",
+ "plannedEvidence": [
+ {
+ "file": "tests/scene-editor-workflow-closure.spec.ts",
+ "line": 64,
+ "evidenceName": "fox-properties-cover",
+ "testTitle": "狐狸:真实细分权限、全部属性、清空绑定、蒙皮保护、相机与封面保存重开",
+ "kind": "browser",
+ "featureIds": [
+ "project.load",
+ "project.title",
+ "project.save",
+ "selection.tree",
+ "property.name",
+ "property.visible",
+ "property.operable",
+ "property.position",
+ "property.rotation",
+ "property.scale",
+ "property.device-id",
+ "property.model-id",
+ "property.semantic",
+ "part.source",
+ "panel.binding",
+ "cover.capture",
+ "cover.atomic"
+ ],
+ "scope": "真实字段输入与清空、六个语义选项、蒙皮保护、源模型可见,正文/资产/封面保存和刷新;不把原件旧模板组合当新模板结果。"
+ }
+ ]
+ },
+ {
+ "id": "property.scale",
+ "name": "缩放 X/Y/Z 数值及有效值",
+ "group": "对象属性、部件与底部面板",
+ "verification": "PENDING",
+ "plannedEvidence": [
+ {
+ "file": "tests/scene-editor-workflow-closure.spec.ts",
+ "line": 64,
+ "evidenceName": "fox-properties-cover",
+ "testTitle": "狐狸:真实细分权限、全部属性、清空绑定、蒙皮保护、相机与封面保存重开",
+ "kind": "browser",
+ "featureIds": [
+ "project.load",
+ "project.title",
+ "project.save",
+ "selection.tree",
+ "property.name",
+ "property.visible",
+ "property.operable",
+ "property.position",
+ "property.rotation",
+ "property.scale",
+ "property.device-id",
+ "property.model-id",
+ "property.semantic",
+ "part.source",
+ "panel.binding",
+ "cover.capture",
+ "cover.atomic"
+ ],
+ "scope": "真实字段输入与清空、六个语义选项、蒙皮保护、源模型可见,正文/资产/封面保存和刷新;不把原件旧模板组合当新模板结果。"
+ },
+ {
+ "file": "tools/scene-closure-engine.test.mjs",
+ "testTitle": "gizmo scaling across its pivot follows the same strictly positive scale contract as inspector edits",
+ "featureIds": [
+ "transform.scale",
+ "property.scale"
+ ],
+ "kind": "unit",
+ "scope": "聚焦合同/真实DOM/Three对象计算;不称为浏览器鼠标交互。"
+ },
+ {
+ "file": "tools/scene-model-placement.test.mjs",
+ "testTitle": "scale input accepts real GLTF normalization factors below 0.05",
+ "featureIds": [
+ "property.scale"
+ ],
+ "kind": "unit",
+ "scope": "聚焦合同/真实DOM/Three对象计算;不称为浏览器鼠标交互。"
+ }
+ ]
+ },
+ {
+ "id": "property.device-id",
+ "name": "设备编号",
+ "group": "对象属性、部件与底部面板",
+ "verification": "PENDING",
+ "plannedEvidence": [
+ {
+ "file": "tests/scene-editor-workflow-closure.spec.ts",
+ "line": 64,
+ "evidenceName": "fox-properties-cover",
+ "testTitle": "狐狸:真实细分权限、全部属性、清空绑定、蒙皮保护、相机与封面保存重开",
+ "kind": "browser",
+ "featureIds": [
+ "project.load",
+ "project.title",
+ "project.save",
+ "selection.tree",
+ "property.name",
+ "property.visible",
+ "property.operable",
+ "property.position",
+ "property.rotation",
+ "property.scale",
+ "property.device-id",
+ "property.model-id",
+ "property.semantic",
+ "part.source",
+ "panel.binding",
+ "cover.capture",
+ "cover.atomic"
+ ],
+ "scope": "真实字段输入与清空、六个语义选项、蒙皮保护、源模型可见,正文/资产/封面保存和刷新;不把原件旧模板组合当新模板结果。"
+ }
+ ]
+ },
+ {
+ "id": "property.model-id",
+ "name": "物模型编号",
+ "group": "对象属性、部件与底部面板",
+ "verification": "PENDING",
+ "plannedEvidence": [
+ {
+ "file": "tests/scene-editor-workflow-closure.spec.ts",
+ "line": 64,
+ "evidenceName": "fox-properties-cover",
+ "testTitle": "狐狸:真实细分权限、全部属性、清空绑定、蒙皮保护、相机与封面保存重开",
+ "kind": "browser",
+ "featureIds": [
+ "project.load",
+ "project.title",
+ "project.save",
+ "selection.tree",
+ "property.name",
+ "property.visible",
+ "property.operable",
+ "property.position",
+ "property.rotation",
+ "property.scale",
+ "property.device-id",
+ "property.model-id",
+ "property.semantic",
+ "part.source",
+ "panel.binding",
+ "cover.capture",
+ "cover.atomic"
+ ],
+ "scope": "真实字段输入与清空、六个语义选项、蒙皮保护、源模型可见,正文/资产/封面保存和刷新;不把原件旧模板组合当新模板结果。"
+ }
+ ]
+ },
+ {
+ "id": "property.semantic",
+ "name": "语义类型六个选项",
+ "group": "对象属性、部件与底部面板",
+ "verification": "PENDING",
+ "plannedEvidence": [
+ {
+ "file": "tests/scene-editor-workflow-closure.spec.ts",
+ "line": 64,
+ "evidenceName": "fox-properties-cover",
+ "testTitle": "狐狸:真实细分权限、全部属性、清空绑定、蒙皮保护、相机与封面保存重开",
+ "kind": "browser",
+ "featureIds": [
+ "project.load",
+ "project.title",
+ "project.save",
+ "selection.tree",
+ "property.name",
+ "property.visible",
+ "property.operable",
+ "property.position",
+ "property.rotation",
+ "property.scale",
+ "property.device-id",
+ "property.model-id",
+ "property.semantic",
+ "part.source",
+ "panel.binding",
+ "cover.capture",
+ "cover.atomic"
+ ],
+ "scope": "真实字段输入与清空、六个语义选项、蒙皮保护、源模型可见,正文/资产/封面保存和刷新;不把原件旧模板组合当新模板结果。"
+ }
+ ]
+ },
+ {
+ "id": "part.source",
+ "name": "模型来源与部件权限说明",
+ "group": "对象属性、部件与底部面板",
+ "verification": "PENDING",
+ "plannedEvidence": [
+ {
+ "file": "tests/scene-editor-templates-closure.spec.ts",
+ "line": 246,
+ "evidenceName": "published-model-assets",
+ "testTitle": "模型成果狐狸卡车双击与真实拖入、树搜索和部件隐藏重开",
+ "kind": "browser",
+ "featureIds": [
+ "assets.catalog",
+ "assets.model-double-click",
+ "assets.model-drop",
+ "search.tree",
+ "part.source",
+ "property.visible"
+ ],
+ "scope": "API 已发布目录,狐狸双击和卡车真实拖入;固定来源版本、树根/部件搜索;卡车刚体部件隐藏保存重开,最后恢复可见。基线树没有折叠按钮,不将搜索伪称折叠。"
+ },
+ {
+ "file": "tests/scene-editor-workflow-closure.spec.ts",
+ "line": 64,
+ "evidenceName": "fox-properties-cover",
+ "testTitle": "狐狸:真实细分权限、全部属性、清空绑定、蒙皮保护、相机与封面保存重开",
+ "kind": "browser",
+ "featureIds": [
+ "project.load",
+ "project.title",
+ "project.save",
+ "selection.tree",
+ "property.name",
+ "property.visible",
+ "property.operable",
+ "property.position",
+ "property.rotation",
+ "property.scale",
+ "property.device-id",
+ "property.model-id",
+ "property.semantic",
+ "part.source",
+ "panel.binding",
+ "cover.capture",
+ "cover.atomic"
+ ],
+ "scope": "真实字段输入与清空、六个语义选项、蒙皮保护、源模型可见,正文/资产/封面保存和刷新;不把原件旧模板组合当新模板结果。"
+ }
+ ]
+ },
+ {
+ "id": "part.transform",
+ "name": "刚性部件独立变换并保存覆盖",
+ "group": "对象属性、部件与底部面板",
+ "verification": "PENDING",
+ "plannedEvidence": [
+ {
+ "file": "tests/scene-editor-workflow-closure.spec.ts",
+ "line": 121,
+ "evidenceName": "truck-part-history",
+ "testTitle": "牛奶卡车:刚性部件独立变换、软删除、历史与保存基线一致",
+ "kind": "browser",
+ "featureIds": [
+ "project.save-key",
+ "part.transform",
+ "part.soft-delete",
+ "history.undo",
+ "history.redo",
+ "property.name",
+ "view.enclosure-full",
+ "view.enclosure-xray",
+ "view.enclosure-hidden"
+ ],
+ "scope": "实际部件变换和软删除、按钮/键盘历史、保存基线与输入框自身撤销;双实例隔离及车库围护三种模式真实点击、导出和隐藏后保存刷新。"
+ },
+ {
+ "file": "tools/scene-closure-engine.test.mjs",
+ "testTitle": "two published instances have unique tree part IDs and editing the second persists independently after reopen",
+ "featureIds": [
+ "part.transform",
+ "selection.tree"
+ ],
+ "kind": "unit",
+ "scope": "聚焦合同/真实DOM/Three对象计算;不称为浏览器鼠标交互。"
+ },
+ {
+ "file": "tools/scene-closure-engine.test.mjs",
+ "testTitle": "legacy duplicate instance IDs migrate only later owners in document order despite reversed GLB completion",
+ "featureIds": [
+ "part.transform",
+ "publish.fixed-model"
+ ],
+ "kind": "unit",
+ "scope": "聚焦合同/真实DOM/Three对象计算;不称为浏览器鼠标交互。"
+ }
+ ]
+ },
+ {
+ "id": "part.soft-delete",
+ "name": "部件软删除与恢复",
+ "group": "对象属性、部件与底部面板",
+ "verification": "PENDING",
+ "plannedEvidence": [
+ {
+ "file": "tests/scene-editor-workflow-closure.spec.ts",
+ "line": 121,
+ "evidenceName": "truck-part-history",
+ "testTitle": "牛奶卡车:刚性部件独立变换、软删除、历史与保存基线一致",
+ "kind": "browser",
+ "featureIds": [
+ "project.save-key",
+ "part.transform",
+ "part.soft-delete",
+ "history.undo",
+ "history.redo",
+ "property.name",
+ "view.enclosure-full",
+ "view.enclosure-xray",
+ "view.enclosure-hidden"
+ ],
+ "scope": "实际部件变换和软删除、按钮/键盘历史、保存基线与输入框自身撤销;双实例隔离及车库围护三种模式真实点击、导出和隐藏后保存刷新。"
+ }
+ ]
+ },
+ {
+ "id": "object.delete",
+ "name": "删除根对象按钮 / Delete",
+ "group": "对象属性、部件与底部面板",
+ "verification": "PENDING",
+ "plannedEvidence": [
+ {
+ "file": "tests/scene-editor-templates-closure.spec.ts",
+ "line": 193,
+ "evidenceName": "venue-assets",
+ "testTitle": "两种整体场地加载、重复放置阻止与显式删除后替换",
+ "kind": "browser",
+ "featureIds": [
+ "asset.vehicle-depot",
+ "asset.field-terrain",
+ "object.delete"
+ ],
+ "scope": "车库和起伏地形真实加载并分别保存重开;已有整体场地时阻止叠加,显式删除旧场地后才能替换。"
+ },
+ {
+ "file": "tools/scene-closure-engine.test.mjs",
+ "testTitle": "deleting a root disposes its geometry and materials after clearing selection",
+ "featureIds": [
+ "object.delete",
+ "lifecycle.destroy"
+ ],
+ "kind": "unit",
+ "scope": "聚焦合同/真实DOM/Three对象计算;不称为浏览器鼠标交互。"
+ }
+ ]
+ },
+ {
+ "id": "panel.objects",
+ "name": "底部对象列表与选中",
+ "group": "对象属性、部件与底部面板",
+ "verification": "PENDING",
+ "plannedEvidence": [
+ {
+ "file": "tests/scene-editor-templates-closure.spec.ts",
+ "line": 161,
+ "evidenceName": "basic-assets",
+ "testTitle": "八种基础资源逐项放置、真实拖放、搜索和保存重开",
+ "kind": "browser",
+ "featureIds": [
+ "assets.basic-double-click",
+ "assets.basic-drop",
+ "search.assets",
+ "panel.objects",
+ "asset.diagnostic",
+ "asset.equipment",
+ "asset.workbench",
+ "asset.shelf",
+ "asset.safety-zone",
+ "asset.camera",
+ "asset.worker",
+ "asset.light"
+ ],
+ "scope": "8 种基础资源分别双击放置并分开布置;真实指针拖入第 9 个工作台、吸附检查、唯一编号、保存刷新一致。照明为现有资源外观,不冒称连接真实灯控。"
+ }
+ ]
+ },
+ {
+ "id": "panel.binding",
+ "name": "设备绑定总表",
+ "group": "对象属性、部件与底部面板",
+ "verification": "PENDING",
+ "plannedEvidence": [
+ {
+ "file": "tests/scene-editor-workflow-closure.spec.ts",
+ "line": 64,
+ "evidenceName": "fox-properties-cover",
+ "testTitle": "狐狸:真实细分权限、全部属性、清空绑定、蒙皮保护、相机与封面保存重开",
+ "kind": "browser",
+ "featureIds": [
+ "project.load",
+ "project.title",
+ "project.save",
+ "selection.tree",
+ "property.name",
+ "property.visible",
+ "property.operable",
+ "property.position",
+ "property.rotation",
+ "property.scale",
+ "property.device-id",
+ "property.model-id",
+ "property.semantic",
+ "part.source",
+ "panel.binding",
+ "cover.capture",
+ "cover.atomic"
+ ],
+ "scope": "真实字段输入与清空、六个语义选项、蒙皮保护、源模型可见,正文/资产/封面保存和刷新;不把原件旧模板组合当新模板结果。"
+ }
+ ]
+ },
+ {
+ "id": "panel.validation",
+ "name": "底部发布检查面板",
+ "group": "对象属性、部件与底部面板",
+ "verification": "PENDING",
+ "plannedEvidence": [
+ {
+ "file": "tests/scene-editor-persistence-closure.spec.ts",
+ "line": 153,
+ "evidenceName": "publish-fixed-version",
+ "testTitle": "发布:前端绑定校验、保存与发布失败阻断、真实固定版本供训练目录使用",
+ "kind": "browser",
+ "featureIds": [
+ "validation.complete",
+ "validation.bindings",
+ "panel.validation",
+ "publish.saved",
+ "publish.failure",
+ "publish.fixed-model",
+ "cover.atomic",
+ "project.back"
+ ],
+ "scope": "实际校验、绑定阻断、POST503不假发布,重试发布当前固定版本;返回列表保留编辑标签,搜索当前项目并核对真实封面图片加载。下游静态渲染另有独立用例。"
+ },
+ {
+ "file": "tools/scene-closure-engine.test.mjs",
+ "testTitle": "verify opens live validation and whitespace binding edits immediately invalidate the active tab",
+ "featureIds": [
+ "panel.validation",
+ "validation.bindings"
+ ],
+ "kind": "unit",
+ "scope": "聚焦合同/真实DOM/Three对象计算;不称为浏览器鼠标交互。"
+ }
+ ]
+ },
+ {
+ "id": "panel.logs",
+ "name": "操作日志",
+ "group": "对象属性、部件与底部面板",
+ "verification": "PENDING",
+ "plannedEvidence": [
+ {
+ "file": "tools/scene-closure-engine.test.mjs",
+ "testTitle": "activity log reflects actual session events and keeps API error details transient",
+ "featureIds": [
+ "panel.logs"
+ ],
+ "kind": "unit",
+ "scope": "聚焦合同/真实DOM/Three对象计算;不称为浏览器鼠标交互。"
+ }
+ ]
+ },
+ {
+ "id": "history.undo",
+ "name": "回退按钮 / Ctrl+Z",
+ "group": "历史与预览",
+ "verification": "PENDING",
+ "plannedEvidence": [
+ {
+ "file": "tests/scene-editor-workflow-closure.spec.ts",
+ "line": 121,
+ "evidenceName": "truck-part-history",
+ "testTitle": "牛奶卡车:刚性部件独立变换、软删除、历史与保存基线一致",
+ "kind": "browser",
+ "featureIds": [
+ "project.save-key",
+ "part.transform",
+ "part.soft-delete",
+ "history.undo",
+ "history.redo",
+ "property.name",
+ "view.enclosure-full",
+ "view.enclosure-xray",
+ "view.enclosure-hidden"
+ ],
+ "scope": "实际部件变换和软删除、按钮/键盘历史、保存基线与输入框自身撤销;双实例隔离及车库围护三种模式真实点击、导出和隐藏后保存刷新。"
+ },
+ {
+ "file": "tools/scene-closure-engine.test.mjs",
+ "testTitle": "typing undo/redo stays inside text controls and inactive cached editors ignore global shortcuts",
+ "featureIds": [
+ "history.undo",
+ "history.redo",
+ "transform.keys",
+ "lifecycle.cache"
+ ],
+ "kind": "unit",
+ "scope": "聚焦合同/真实DOM/Three对象计算;不称为浏览器鼠标交互。"
+ },
+ {
+ "file": "tools/scene-closure-engine.test.mjs",
+ "testTitle": "loading and history restoration preserve an authored camera rather than auto-focusing a vehicle",
+ "featureIds": [
+ "json.rendering",
+ "history.undo"
+ ],
+ "kind": "unit",
+ "scope": "聚焦合同/真实DOM/Three对象计算;不称为浏览器鼠标交互。"
+ },
+ {
+ "file": "tools/scene-closure-engine.test.mjs",
+ "testTitle": "save B then undo A stays dirty, and redo B returns to the actual saved baseline",
+ "featureIds": [
+ "history.undo",
+ "history.redo"
+ ],
+ "kind": "unit",
+ "scope": "聚焦合同/真实DOM/Three对象计算;不称为浏览器鼠标交互。"
+ },
+ {
+ "file": "tools/scene-closure-engine.test.mjs",
+ "testTitle": "default history caps at 60 snapshots, stops at either edge and discards redo only after a new branch",
+ "featureIds": [
+ "history.capacity",
+ "history.undo",
+ "history.redo"
+ ],
+ "kind": "unit",
+ "scope": "聚焦合同/真实DOM/Three对象计算;不称为浏览器鼠标交互。"
+ }
+ ]
+ },
+ {
+ "id": "history.redo",
+ "name": "前进按钮 / Ctrl+Y / Ctrl+Shift+Z",
+ "group": "历史与预览",
+ "verification": "PENDING",
+ "plannedEvidence": [
+ {
+ "file": "tests/scene-editor-workflow-closure.spec.ts",
+ "line": 121,
+ "evidenceName": "truck-part-history",
+ "testTitle": "牛奶卡车:刚性部件独立变换、软删除、历史与保存基线一致",
+ "kind": "browser",
+ "featureIds": [
+ "project.save-key",
+ "part.transform",
+ "part.soft-delete",
+ "history.undo",
+ "history.redo",
+ "property.name",
+ "view.enclosure-full",
+ "view.enclosure-xray",
+ "view.enclosure-hidden"
+ ],
+ "scope": "实际部件变换和软删除、按钮/键盘历史、保存基线与输入框自身撤销;双实例隔离及车库围护三种模式真实点击、导出和隐藏后保存刷新。"
+ },
+ {
+ "file": "tools/scene-closure-engine.test.mjs",
+ "testTitle": "typing undo/redo stays inside text controls and inactive cached editors ignore global shortcuts",
+ "featureIds": [
+ "history.undo",
+ "history.redo",
+ "transform.keys",
+ "lifecycle.cache"
+ ],
+ "kind": "unit",
+ "scope": "聚焦合同/真实DOM/Three对象计算;不称为浏览器鼠标交互。"
+ },
+ {
+ "file": "tools/scene-closure-engine.test.mjs",
+ "testTitle": "save B then undo A stays dirty, and redo B returns to the actual saved baseline",
+ "featureIds": [
+ "history.undo",
+ "history.redo"
+ ],
+ "kind": "unit",
+ "scope": "聚焦合同/真实DOM/Three对象计算;不称为浏览器鼠标交互。"
+ },
+ {
+ "file": "tools/scene-closure-engine.test.mjs",
+ "testTitle": "default history caps at 60 snapshots, stops at either edge and discards redo only after a new branch",
+ "featureIds": [
+ "history.capacity",
+ "history.undo",
+ "history.redo"
+ ],
+ "kind": "unit",
+ "scope": "聚焦合同/真实DOM/Three对象计算;不称为浏览器鼠标交互。"
+ }
+ ]
+ },
+ {
+ "id": "history.capacity",
+ "name": "60 步历史上限与边界",
+ "group": "历史与预览",
+ "verification": "PENDING",
+ "plannedEvidence": [
+ {
+ "file": "tools/scene-closure-engine.test.mjs",
+ "testTitle": "default history caps at 60 snapshots, stops at either edge and discards redo only after a new branch",
+ "featureIds": [
+ "history.capacity",
+ "history.undo",
+ "history.redo"
+ ],
+ "kind": "unit",
+ "scope": "聚焦合同/真实DOM/Three对象计算;不称为浏览器鼠标交互。"
+ }
+ ]
+ },
+ {
+ "id": "history.failure",
+ "name": "异步历史恢复失败/互斥",
+ "group": "历史与预览",
+ "verification": "PENDING",
+ "plannedEvidence": [
+ {
+ "file": "tools/scene-closure-engine.test.mjs",
+ "testTitle": "failed undo restoration retains live objects and keeps the undo target available for retry",
+ "featureIds": [
+ "history.failure"
+ ],
+ "kind": "unit",
+ "scope": "聚焦合同/真实DOM/Three对象计算;不称为浏览器鼠标交互。"
+ },
+ {
+ "file": "tools/scene-closure-engine.test.mjs",
+ "testTitle": "replacement failure waits for other started loads before restoring the original scene",
+ "featureIds": [
+ "history.failure",
+ "glb.pending"
+ ],
+ "kind": "unit",
+ "scope": "聚焦合同/真实DOM/Three对象计算;不称为浏览器鼠标交互。"
+ }
+ ]
+ },
+ {
+ "id": "preview.enter",
+ "name": "进入运行预览",
+ "group": "历史与预览",
+ "verification": "PENDING",
+ "plannedEvidence": [
+ {
+ "file": "tests/scene-editor-viewport-closure.spec.ts",
+ "line": 117,
+ "evidenceName": "viewport-camera-theme",
+ "testTitle": "真实相机操控、视口拾取、预览禁写和两尺寸主题可读性",
+ "kind": "browser",
+ "featureIds": [
+ "view.orbit",
+ "view.pan",
+ "view.zoom",
+ "view.top",
+ "view.perspective",
+ "view.grid",
+ "view.focus",
+ "selection.viewport",
+ "selection.highlight",
+ "preview.enter",
+ "preview.exit",
+ "preview.lock",
+ "lifecycle.resize-theme"
+ ],
+ "scope": "以源测试实际断言为准;计划关联不代表最终通过。"
+ },
+ {
+ "file": "tests/scene-editor-workflow-closure.spec.ts",
+ "line": 190,
+ "evidenceName": "readonly",
+ "testTitle": "只读管理员:可浏览预览与导出,模板/资源/键盘/接口不能改场景",
+ "kind": "browser",
+ "featureIds": [
+ "permission.readonly",
+ "permission.update",
+ "permission.publish",
+ "preview.enter",
+ "preview.exit",
+ "preview.lock",
+ "json.export"
+ ],
+ "scope": "只读按钮/模板/资源禁写、蒙皮浏览、运行预览与快捷键不变更对象,直接PUT拒绝;具体搜索/鼠标覆盖另见视口用例。"
+ }
+ ]
+ },
+ {
+ "id": "preview.exit",
+ "name": "按钮 / Escape 退出预览",
+ "group": "历史与预览",
+ "verification": "PENDING",
+ "plannedEvidence": [
+ {
+ "file": "tests/scene-editor-viewport-closure.spec.ts",
+ "line": 117,
+ "evidenceName": "viewport-camera-theme",
+ "testTitle": "真实相机操控、视口拾取、预览禁写和两尺寸主题可读性",
+ "kind": "browser",
+ "featureIds": [
+ "view.orbit",
+ "view.pan",
+ "view.zoom",
+ "view.top",
+ "view.perspective",
+ "view.grid",
+ "view.focus",
+ "selection.viewport",
+ "selection.highlight",
+ "preview.enter",
+ "preview.exit",
+ "preview.lock",
+ "lifecycle.resize-theme"
+ ],
+ "scope": "以源测试实际断言为准;计划关联不代表最终通过。"
+ },
+ {
+ "file": "tests/scene-editor-workflow-closure.spec.ts",
+ "line": 190,
+ "evidenceName": "readonly",
+ "testTitle": "只读管理员:可浏览预览与导出,模板/资源/键盘/接口不能改场景",
+ "kind": "browser",
+ "featureIds": [
+ "permission.readonly",
+ "permission.update",
+ "permission.publish",
+ "preview.enter",
+ "preview.exit",
+ "preview.lock",
+ "json.export"
+ ],
+ "scope": "只读按钮/模板/资源禁写、蒙皮浏览、运行预览与快捷键不变更对象,直接PUT拒绝;具体搜索/鼠标覆盖另见视口用例。"
+ },
+ {
+ "file": "tools/scene-closure-engine.test.mjs",
+ "testTitle": "preview locks mutation controls and Delete while Escape restores editing",
+ "featureIds": [
+ "preview.lock",
+ "preview.exit"
+ ],
+ "kind": "unit",
+ "scope": "聚焦合同/真实DOM/Three对象计算;不称为浏览器鼠标交互。"
+ }
+ ]
+ },
+ {
+ "id": "preview.lock",
+ "name": "预览中禁止编辑",
+ "group": "历史与预览",
+ "verification": "PENDING",
+ "plannedEvidence": [
+ {
+ "file": "tests/scene-editor-viewport-closure.spec.ts",
+ "line": 117,
+ "evidenceName": "viewport-camera-theme",
+ "testTitle": "真实相机操控、视口拾取、预览禁写和两尺寸主题可读性",
+ "kind": "browser",
+ "featureIds": [
+ "view.orbit",
+ "view.pan",
+ "view.zoom",
+ "view.top",
+ "view.perspective",
+ "view.grid",
+ "view.focus",
+ "selection.viewport",
+ "selection.highlight",
+ "preview.enter",
+ "preview.exit",
+ "preview.lock",
+ "lifecycle.resize-theme"
+ ],
+ "scope": "以源测试实际断言为准;计划关联不代表最终通过。"
+ },
+ {
+ "file": "tests/scene-editor-workflow-closure.spec.ts",
+ "line": 190,
+ "evidenceName": "readonly",
+ "testTitle": "只读管理员:可浏览预览与导出,模板/资源/键盘/接口不能改场景",
+ "kind": "browser",
+ "featureIds": [
+ "permission.readonly",
+ "permission.update",
+ "permission.publish",
+ "preview.enter",
+ "preview.exit",
+ "preview.lock",
+ "json.export"
+ ],
+ "scope": "只读按钮/模板/资源禁写、蒙皮浏览、运行预览与快捷键不变更对象,直接PUT拒绝;具体搜索/鼠标覆盖另见视口用例。"
+ },
+ {
+ "file": "tools/scene-closure-engine.test.mjs",
+ "testTitle": "preview locks mutation controls and Delete while Escape restores editing",
+ "featureIds": [
+ "preview.lock",
+ "preview.exit"
+ ],
+ "kind": "unit",
+ "scope": "聚焦合同/真实DOM/Three对象计算;不称为浏览器鼠标交互。"
+ }
+ ]
+ },
+ {
+ "id": "cover.capture",
+ "name": "保存时截取当前三维场景封面",
+ "group": "保存封面、发布与依赖",
+ "verification": "PENDING",
+ "plannedEvidence": [
+ {
+ "file": "tests/scene-editor-workflow-closure.spec.ts",
+ "line": 64,
+ "evidenceName": "fox-properties-cover",
+ "testTitle": "狐狸:真实细分权限、全部属性、清空绑定、蒙皮保护、相机与封面保存重开",
+ "kind": "browser",
+ "featureIds": [
+ "project.load",
+ "project.title",
+ "project.save",
+ "selection.tree",
+ "property.name",
+ "property.visible",
+ "property.operable",
+ "property.position",
+ "property.rotation",
+ "property.scale",
+ "property.device-id",
+ "property.model-id",
+ "property.semantic",
+ "part.source",
+ "panel.binding",
+ "cover.capture",
+ "cover.atomic"
+ ],
+ "scope": "真实字段输入与清空、六个语义选项、蒙皮保护、源模型可见,正文/资产/封面保存和刷新;不把原件旧模板组合当新模板结果。"
+ },
+ {
+ "file": "tools/scene-cover-capture.test.mjs",
+ "testTitle": "copies synchronously without bounds, gizmo, grid or selection glow, then restores before JPEG encoding",
+ "featureIds": [
+ "cover.capture",
+ "selection.highlight",
+ "view.grid"
+ ],
+ "kind": "unit",
+ "scope": "聚焦合同/真实DOM/Three对象计算;不称为浏览器鼠标交互。"
+ }
+ ]
+ },
+ {
+ "id": "cover.atomic",
+ "name": "封面资产与文档同次保存",
+ "group": "保存封面、发布与依赖",
+ "verification": "PENDING",
+ "plannedEvidence": [
+ {
+ "file": "tests/scene-editor-persistence-closure.spec.ts",
+ "line": 153,
+ "evidenceName": "publish-fixed-version",
+ "testTitle": "发布:前端绑定校验、保存与发布失败阻断、真实固定版本供训练目录使用",
+ "kind": "browser",
+ "featureIds": [
+ "validation.complete",
+ "validation.bindings",
+ "panel.validation",
+ "publish.saved",
+ "publish.failure",
+ "publish.fixed-model",
+ "cover.atomic",
+ "project.back"
+ ],
+ "scope": "实际校验、绑定阻断、POST503不假发布,重试发布当前固定版本;返回列表保留编辑标签,搜索当前项目并核对真实封面图片加载。下游静态渲染另有独立用例。"
+ },
+ {
+ "file": "tests/scene-editor-workflow-closure.spec.ts",
+ "line": 64,
+ "evidenceName": "fox-properties-cover",
+ "testTitle": "狐狸:真实细分权限、全部属性、清空绑定、蒙皮保护、相机与封面保存重开",
+ "kind": "browser",
+ "featureIds": [
+ "project.load",
+ "project.title",
+ "project.save",
+ "selection.tree",
+ "property.name",
+ "property.visible",
+ "property.operable",
+ "property.position",
+ "property.rotation",
+ "property.scale",
+ "property.device-id",
+ "property.model-id",
+ "property.semantic",
+ "part.source",
+ "panel.binding",
+ "cover.capture",
+ "cover.atomic"
+ ],
+ "scope": "真实字段输入与清空、六个语义选项、蒙皮保护、源模型可见,正文/资产/封面保存和刷新;不把原件旧模板组合当新模板结果。"
+ },
+ {
+ "file": "tools/scene-cover-api.test.mjs",
+ "testTitle": "scene image ticket, fixed asset replacement, dependencies and coverUri commit in one versioned PUT",
+ "featureIds": [
+ "cover.atomic"
+ ],
+ "kind": "unit",
+ "scope": "聚焦合同/真实DOM/Three对象计算;不称为浏览器鼠标交互。"
+ }
+ ]
+ },
+ {
+ "id": "cover.failure",
+ "name": "截图/封面上传失败保留原封面",
+ "group": "保存封面、发布与依赖",
+ "verification": "PENDING",
+ "plannedEvidence": [
+ {
+ "file": "tests/scene-editor-persistence-closure.spec.ts",
+ "line": 85,
+ "evidenceName": "save-cover-failure",
+ "testTitle": "保存失败与封面降级:503 保留草稿,封面上传失败保留旧图,恢复后可重试",
+ "kind": "browser",
+ "featureIds": [
+ "project.save-failure",
+ "cover.failure"
+ ],
+ "scope": "正文PUT503不清草稿;封面上传503保留旧图但正文可保存;恢复服务后保存刷新。截图异常和409另由聚焦合同验证。"
+ },
+ {
+ "file": "tools/scene-cover-api.test.mjs",
+ "testTitle": "capture failure saves content with the previous cover and reports a warning result",
+ "featureIds": [
+ "cover.failure"
+ ],
+ "kind": "unit",
+ "scope": "聚焦合同/真实DOM/Three对象计算;不称为浏览器鼠标交互。"
+ },
+ {
+ "file": "tools/scene-cover-api.test.mjs",
+ "testTitle": "upload conflict propagates without any content PUT",
+ "featureIds": [
+ "project.conflict",
+ "cover.failure"
+ ],
+ "kind": "unit",
+ "scope": "聚焦合同/真实DOM/Three对象计算;不称为浏览器鼠标交互。"
+ }
+ ]
+ },
+ {
+ "id": "validation.complete",
+ "name": "环境、对象用途、模型加载检查",
+ "group": "保存封面、发布与依赖",
+ "verification": "PENDING",
+ "plannedEvidence": [
+ {
+ "file": "tests/scene-editor-persistence-closure.spec.ts",
+ "line": 153,
+ "evidenceName": "publish-fixed-version",
+ "testTitle": "发布:前端绑定校验、保存与发布失败阻断、真实固定版本供训练目录使用",
+ "kind": "browser",
+ "featureIds": [
+ "validation.complete",
+ "validation.bindings",
+ "panel.validation",
+ "publish.saved",
+ "publish.failure",
+ "publish.fixed-model",
+ "cover.atomic",
+ "project.back"
+ ],
+ "scope": "实际校验、绑定阻断、POST503不假发布,重试发布当前固定版本;返回列表保留编辑标签,搜索当前项目并核对真实封面图片加载。下游静态渲染另有独立用例。"
+ }
+ ]
+ },
+ {
+ "id": "validation.bindings",
+ "name": "设备/物模型编号完整与唯一",
+ "group": "保存封面、发布与依赖",
+ "verification": "PENDING",
+ "plannedEvidence": [
+ {
+ "file": "tests/scene-editor-persistence-closure.spec.ts",
+ "line": 153,
+ "evidenceName": "publish-fixed-version",
+ "testTitle": "发布:前端绑定校验、保存与发布失败阻断、真实固定版本供训练目录使用",
+ "kind": "browser",
+ "featureIds": [
+ "validation.complete",
+ "validation.bindings",
+ "panel.validation",
+ "publish.saved",
+ "publish.failure",
+ "publish.fixed-model",
+ "cover.atomic",
+ "project.back"
+ ],
+ "scope": "实际校验、绑定阻断、POST503不假发布,重试发布当前固定版本;返回列表保留编辑标签,搜索当前项目并核对真实封面图片加载。下游静态渲染另有独立用例。"
+ },
+ {
+ "file": "tools/scene-closure-engine.test.mjs",
+ "testTitle": "new resource bindings do not reuse a saved serial after earlier objects were deleted",
+ "featureIds": [
+ "validation.bindings"
+ ],
+ "kind": "unit",
+ "scope": "聚焦合同/真实DOM/Three对象计算;不称为浏览器鼠标交互。"
+ },
+ {
+ "file": "tools/scene-closure-engine.test.mjs",
+ "testTitle": "verify opens live validation and whitespace binding edits immediately invalidate the active tab",
+ "featureIds": [
+ "panel.validation",
+ "validation.bindings"
+ ],
+ "kind": "unit",
+ "scope": "聚焦合同/真实DOM/Three对象计算;不称为浏览器鼠标交互。"
+ }
+ ]
+ },
+ {
+ "id": "validation.references",
+ "name": "受控资源引用完整性",
+ "group": "保存封面、发布与依赖",
+ "verification": "PENDING",
+ "plannedEvidence": [
+ {
+ "file": "tests/scene-editor-persistence-closure.spec.ts",
+ "line": 61,
+ "evidenceName": "json-glb-roundtrip",
+ "testTitle": "工程 JSON 与小 GLB:真实上传、损坏拒绝、引用失败回滚、相机参数往返",
+ "kind": "browser",
+ "featureIds": [
+ "json.export",
+ "json.import",
+ "json.invalid",
+ "json.missing-resource",
+ "json.rendering",
+ "glb.import",
+ "glb.invalid",
+ "glb.references",
+ "validation.references"
+ ],
+ "scope": "真实JSON/GLB文件输入、错误schema/损坏资源/上传503拒绝、错误assetCode回滚、稳定URI及渲染参数保存重开。"
+ },
+ {
+ "file": "tools/scene-closure-assets.test.mjs",
+ "testTitle": "an explicit missing code cannot fall back to the first READY asset",
+ "featureIds": [
+ "validation.references",
+ "json.missing-resource"
+ ],
+ "kind": "unit",
+ "scope": "聚焦合同/真实DOM/Three对象计算;不称为浏览器鼠标交互。"
+ }
+ ]
+ },
+ {
+ "id": "publish.saved",
+ "name": "发布场景按钮的真实 API 闭环",
+ "group": "保存封面、发布与依赖",
+ "verification": "PENDING",
+ "plannedEvidence": [
+ {
+ "file": "tests/scene-editor-persistence-closure.spec.ts",
+ "line": 153,
+ "evidenceName": "publish-fixed-version",
+ "testTitle": "发布:前端绑定校验、保存与发布失败阻断、真实固定版本供训练目录使用",
+ "kind": "browser",
+ "featureIds": [
+ "validation.complete",
+ "validation.bindings",
+ "panel.validation",
+ "publish.saved",
+ "publish.failure",
+ "publish.fixed-model",
+ "cover.atomic",
+ "project.back"
+ ],
+ "scope": "实际校验、绑定阻断、POST503不假发布,重试发布当前固定版本;返回列表保留编辑标签,搜索当前项目并核对真实封面图片加载。下游静态渲染另有独立用例。"
+ }
+ ]
+ },
+ {
+ "id": "publish.only",
+ "name": "只有发布权限时发布已存版本",
+ "group": "保存封面、发布与依赖",
+ "verification": "PENDING",
+ "plannedEvidence": [
+ {
+ "file": "tests/scene-editor-permissions-closure.spec.ts",
+ "line": 171,
+ "evidenceName": "scene-publish-only",
+ "testTitle": "仅场景发布权限:浏览器发布已存副本,不发送保存或上传请求",
+ "kind": "browser",
+ "featureIds": [
+ "permission.publish",
+ "publish.only",
+ "publish.fixed-model"
+ ],
+ "scope": "以源测试实际断言为准;计划关联不代表最终通过。"
+ }
+ ]
+ },
+ {
+ "id": "publish.failure",
+ "name": "预检/保存/发布失败阻断",
+ "group": "保存封面、发布与依赖",
+ "verification": "PENDING",
+ "plannedEvidence": [
+ {
+ "file": "tests/scene-editor-persistence-closure.spec.ts",
+ "line": 153,
+ "evidenceName": "publish-fixed-version",
+ "testTitle": "发布:前端绑定校验、保存与发布失败阻断、真实固定版本供训练目录使用",
+ "kind": "browser",
+ "featureIds": [
+ "validation.complete",
+ "validation.bindings",
+ "panel.validation",
+ "publish.saved",
+ "publish.failure",
+ "publish.fixed-model",
+ "cover.atomic",
+ "project.back"
+ ],
+ "scope": "实际校验、绑定阻断、POST503不假发布,重试发布当前固定版本;返回列表保留编辑标签,搜索当前项目并核对真实封面图片加载。下游静态渲染另有独立用例。"
+ }
+ ]
+ },
+ {
+ "id": "publish.concurrent",
+ "name": "重复发布与上传/保存队列",
+ "group": "保存封面、发布与依赖",
+ "verification": "PENDING",
+ "plannedEvidence": [
+ {
+ "file": "tools/scene-cover-api.test.mjs",
+ "testTitle": "role switch waits through validation, save, and the later queued publish after dirty clears",
+ "featureIds": [
+ "publish.concurrent",
+ "leave.busy",
+ "leave.role-switch"
+ ],
+ "kind": "unit",
+ "scope": "聚焦合同/真实DOM/Three对象计算;不称为浏览器鼠标交互。"
+ },
+ {
+ "file": "../unreal_tran_web/src/features/scene-legacy/contentApiClient.test.mjs",
+ "testTitle": "project operation queue serializes upload, save and publish including failures",
+ "featureIds": [
+ "publish.concurrent"
+ ],
+ "kind": "unit",
+ "scope": "聚焦合同/真实DOM/Three对象计算;不称为浏览器鼠标交互。"
+ }
+ ]
+ },
+ {
+ "id": "publish.fixed-model",
+ "name": "固定 SCENE_MODEL 版本与多个实例",
+ "group": "保存封面、发布与依赖",
+ "verification": "PENDING",
+ "plannedEvidence": [
+ {
+ "file": "tests/scene-editor-permissions-closure.spec.ts",
+ "line": 142,
+ "evidenceName": "scene-update-only",
+ "testTitle": "仅场景编辑权限:固定模型可加载和保存,不能发布或访问模型页面",
+ "kind": "browser",
+ "featureIds": [
+ "permission.update",
+ "permission.publish",
+ "publish.fixed-model"
+ ],
+ "scope": "以源测试实际断言为准;计划关联不代表最终通过。"
+ },
+ {
+ "file": "tests/scene-editor-permissions-closure.spec.ts",
+ "line": 171,
+ "evidenceName": "scene-publish-only",
+ "testTitle": "仅场景发布权限:浏览器发布已存副本,不发送保存或上传请求",
+ "kind": "browser",
+ "featureIds": [
+ "permission.publish",
+ "publish.only",
+ "publish.fixed-model"
+ ],
+ "scope": "以源测试实际断言为准;计划关联不代表最终通过。"
+ },
+ {
+ "file": "tests/scene-editor-persistence-closure.spec.ts",
+ "line": 153,
+ "evidenceName": "publish-fixed-version",
+ "testTitle": "发布:前端绑定校验、保存与发布失败阻断、真实固定版本供训练目录使用",
+ "kind": "browser",
+ "featureIds": [
+ "validation.complete",
+ "validation.bindings",
+ "panel.validation",
+ "publish.saved",
+ "publish.failure",
+ "publish.fixed-model",
+ "cover.atomic",
+ "project.back"
+ ],
+ "scope": "实际校验、绑定阻断、POST503不假发布,重试发布当前固定版本;返回列表保留编辑标签,搜索当前项目并核对真实封面图片加载。下游静态渲染另有独立用例。"
+ },
+ {
+ "file": "tools/scene-closure-engine.test.mjs",
+ "testTitle": "legacy duplicate instance IDs migrate only later owners in document order despite reversed GLB completion",
+ "featureIds": [
+ "part.transform",
+ "publish.fixed-model"
+ ],
+ "kind": "unit",
+ "scope": "聚焦合同/真实DOM/Three对象计算;不称为浏览器鼠标交互。"
+ },
+ {
+ "file": "tools/scene-closure-assets.test.mjs",
+ "testTitle": "fixed model reads the exact version and downloads through its visible scene dependency",
+ "featureIds": [
+ "publish.fixed-model",
+ "permission.update"
+ ],
+ "kind": "unit",
+ "scope": "聚焦合同/真实DOM/Three对象计算;不称为浏览器鼠标交互。"
+ },
+ {
+ "file": "../unreal_tran_web/src/features/scene-legacy/contentApiClient.test.mjs",
+ "testTitle": "fixed model dependencies are recursively collected, exact and de-duplicated",
+ "featureIds": [
+ "publish.fixed-model"
+ ],
+ "kind": "unit",
+ "scope": "聚焦合同/真实DOM/Three对象计算;不称为浏览器鼠标交互。"
+ }
+ ]
+ },
+ {
+ "id": "publish.downstream",
+ "name": "发布场景供训练选择和静态还原",
+ "group": "保存封面、发布与依赖",
+ "verification": "PENDING",
+ "plannedEvidence": [
+ {
+ "file": "tests/scene-editor-downstream-closure.spec.ts",
+ "line": 28,
+ "evidenceName": "downstream-fox",
+ "testTitle": "fox:场景发布后在真实训练编排选择、保存并重开静态模型",
+ "kind": "browser",
+ "featureIds": [
+ "publish.downstream"
+ ],
+ "scope": "真实训练编排静态还原;不计来源模型动画/蓝图自动执行。"
+ },
+ {
+ "file": "tests/scene-editor-downstream-closure.spec.ts",
+ "line": 28,
+ "evidenceName": "downstream-truck",
+ "testTitle": "truck:场景发布后在真实训练编排选择、保存并重开静态模型",
+ "kind": "browser",
+ "featureIds": [
+ "publish.downstream"
+ ],
+ "scope": "真实训练编排静态还原;不计来源模型动画/蓝图自动执行。"
+ }
+ ]
+ },
+ {
+ "id": "permission.update",
+ "name": "场景细分编辑权限",
+ "group": "权限、缓存与未保存恢复",
+ "verification": "PENDING",
+ "plannedEvidence": [
+ {
+ "file": "tests/scene-editor-permissions-closure.spec.ts",
+ "line": 142,
+ "evidenceName": "scene-update-only",
+ "testTitle": "仅场景编辑权限:固定模型可加载和保存,不能发布或访问模型页面",
+ "kind": "browser",
+ "featureIds": [
+ "permission.update",
+ "permission.publish",
+ "publish.fixed-model"
+ ],
+ "scope": "以源测试实际断言为准;计划关联不代表最终通过。"
+ },
+ {
+ "file": "tests/scene-editor-workflow-closure.spec.ts",
+ "line": 190,
+ "evidenceName": "readonly",
+ "testTitle": "只读管理员:可浏览预览与导出,模板/资源/键盘/接口不能改场景",
+ "kind": "browser",
+ "featureIds": [
+ "permission.readonly",
+ "permission.update",
+ "permission.publish",
+ "preview.enter",
+ "preview.exit",
+ "preview.lock",
+ "json.export"
+ ],
+ "scope": "只读按钮/模板/资源禁写、蒙皮浏览、运行预览与快捷键不变更对象,直接PUT拒绝;具体搜索/鼠标覆盖另见视口用例。"
+ },
+ {
+ "file": "tools/scene-closure-engine.test.mjs",
+ "testTitle": "scene granular update permission enables editing without the retired shared code",
+ "featureIds": [
+ "permission.update"
+ ],
+ "kind": "unit",
+ "scope": "聚焦合同/真实DOM/Three对象计算;不称为浏览器鼠标交互。"
+ },
+ {
+ "file": "tools/scene-closure-assets.test.mjs",
+ "testTitle": "fixed model reads the exact version and downloads through its visible scene dependency",
+ "featureIds": [
+ "publish.fixed-model",
+ "permission.update"
+ ],
+ "kind": "unit",
+ "scope": "聚焦合同/真实DOM/Three对象计算;不称为浏览器鼠标交互。"
+ }
+ ]
+ },
+ {
+ "id": "permission.publish",
+ "name": "场景细分发布权限",
+ "group": "权限、缓存与未保存恢复",
+ "verification": "PENDING",
+ "plannedEvidence": [
+ {
+ "file": "tests/scene-editor-permissions-closure.spec.ts",
+ "line": 142,
+ "evidenceName": "scene-update-only",
+ "testTitle": "仅场景编辑权限:固定模型可加载和保存,不能发布或访问模型页面",
+ "kind": "browser",
+ "featureIds": [
+ "permission.update",
+ "permission.publish",
+ "publish.fixed-model"
+ ],
+ "scope": "以源测试实际断言为准;计划关联不代表最终通过。"
+ },
+ {
+ "file": "tests/scene-editor-permissions-closure.spec.ts",
+ "line": 171,
+ "evidenceName": "scene-publish-only",
+ "testTitle": "仅场景发布权限:浏览器发布已存副本,不发送保存或上传请求",
+ "kind": "browser",
+ "featureIds": [
+ "permission.publish",
+ "publish.only",
+ "publish.fixed-model"
+ ],
+ "scope": "以源测试实际断言为准;计划关联不代表最终通过。"
+ },
+ {
+ "file": "tests/scene-editor-permissions-closure.spec.ts",
+ "line": 183,
+ "evidenceName": "scene-no-page-permission",
+ "testTitle": "无场景页面权限:编辑路由和直接内容接口均拒绝访问",
+ "kind": "browser",
+ "featureIds": [
+ "permission.publish"
+ ],
+ "scope": "以源测试实际断言为准;计划关联不代表最终通过。"
+ },
+ {
+ "file": "tests/scene-editor-workflow-closure.spec.ts",
+ "line": 190,
+ "evidenceName": "readonly",
+ "testTitle": "只读管理员:可浏览预览与导出,模板/资源/键盘/接口不能改场景",
+ "kind": "browser",
+ "featureIds": [
+ "permission.readonly",
+ "permission.update",
+ "permission.publish",
+ "preview.enter",
+ "preview.exit",
+ "preview.lock",
+ "json.export"
+ ],
+ "scope": "只读按钮/模板/资源禁写、蒙皮浏览、运行预览与快捷键不变更对象,直接PUT拒绝;具体搜索/鼠标覆盖另见视口用例。"
+ }
+ ]
+ },
+ {
+ "id": "permission.readonly",
+ "name": "只读仍可查看、搜索、选中和预览",
+ "group": "权限、缓存与未保存恢复",
+ "verification": "PENDING",
+ "plannedEvidence": [
+ {
+ "file": "tests/scene-editor-permissions-closure.spec.ts",
+ "line": 253,
+ "evidenceName": "scene-role-switch",
+ "testTitle": "后台场景未保存时切换角色:关闭确认保留草稿,保存切换后重建只读权限",
+ "kind": "browser",
+ "featureIds": [
+ "leave.role-switch",
+ "leave.cancel",
+ "leave.save",
+ "permission.readonly"
+ ],
+ "scope": "以源测试实际断言为准;计划关联不代表最终通过。"
+ },
+ {
+ "file": "tests/scene-editor-workflow-closure.spec.ts",
+ "line": 190,
+ "evidenceName": "readonly",
+ "testTitle": "只读管理员:可浏览预览与导出,模板/资源/键盘/接口不能改场景",
+ "kind": "browser",
+ "featureIds": [
+ "permission.readonly",
+ "permission.update",
+ "permission.publish",
+ "preview.enter",
+ "preview.exit",
+ "preview.lock",
+ "json.export"
+ ],
+ "scope": "只读按钮/模板/资源禁写、蒙皮浏览、运行预览与快捷键不变更对象,直接PUT拒绝;具体搜索/鼠标覆盖另见视口用例。"
+ }
+ ]
+ },
+ {
+ "id": "recovery.autobuffer",
+ "name": "800ms 未保存恢复缓冲",
+ "group": "权限、缓存与未保存恢复",
+ "verification": "PENDING",
+ "plannedEvidence": [
+ {
+ "file": "tests/scene-editor-async-closure.spec.ts",
+ "line": 50,
+ "evidenceName": "save-concurrency",
+ "testTitle": "延迟保存快照:重复点击只提交一次,期间新编辑保留,旧恢复稿不会误报冲突",
+ "kind": "browser",
+ "featureIds": [
+ "project.concurrent-save",
+ "project.save",
+ "project.save-key",
+ "recovery.autobuffer",
+ "recovery.same-version"
+ ],
+ "scope": "延迟真实PUT快照A、重复Ctrl+S不重复请求、期间编辑B仍dirty;再次保存刷新无旧恢复稿误报。"
+ },
+ {
+ "file": "tests/scene-editor-permissions-closure.spec.ts",
+ "line": 305,
+ "evidenceName": "scene-multi-session",
+ "testTitle": "双标签各自修改场景:保存和明确丢弃当前页均保留另一页真实恢复稿",
+ "kind": "browser",
+ "featureIds": [
+ "recovery.multi-session",
+ "recovery.autobuffer",
+ "recovery.discard",
+ "project.save"
+ ],
+ "scope": "以源测试实际断言为准;计划关联不代表最终通过。"
+ },
+ {
+ "file": "tests/scene-editor-persistence-closure.spec.ts",
+ "line": 117,
+ "evidenceName": "real-recovery-conflict",
+ "testTitle": "同版本恢复与 409:真实未保存草稿重载、服务器新版本保护与隔离下载",
+ "kind": "browser",
+ "featureIds": [
+ "project.conflict",
+ "recovery.autobuffer",
+ "recovery.same-version",
+ "recovery.stale-version"
+ ],
+ "scope": "真实未存输入产生恢复稿,同版本恢复不自动入库;另一版本先保存后409拒绝覆盖,旧稿隔离下载。"
+ }
+ ]
+ },
+ {
+ "id": "recovery.same-version",
+ "name": "同版本真实未保存恢复",
+ "group": "权限、缓存与未保存恢复",
+ "verification": "PENDING",
+ "plannedEvidence": [
+ {
+ "file": "tests/scene-editor-async-closure.spec.ts",
+ "line": 50,
+ "evidenceName": "save-concurrency",
+ "testTitle": "延迟保存快照:重复点击只提交一次,期间新编辑保留,旧恢复稿不会误报冲突",
+ "kind": "browser",
+ "featureIds": [
+ "project.concurrent-save",
+ "project.save",
+ "project.save-key",
+ "recovery.autobuffer",
+ "recovery.same-version"
+ ],
+ "scope": "延迟真实PUT快照A、重复Ctrl+S不重复请求、期间编辑B仍dirty;再次保存刷新无旧恢复稿误报。"
+ },
+ {
+ "file": "tests/scene-editor-persistence-closure.spec.ts",
+ "line": 117,
+ "evidenceName": "real-recovery-conflict",
+ "testTitle": "同版本恢复与 409:真实未保存草稿重载、服务器新版本保护与隔离下载",
+ "kind": "browser",
+ "featureIds": [
+ "project.conflict",
+ "recovery.autobuffer",
+ "recovery.same-version",
+ "recovery.stale-version"
+ ],
+ "scope": "真实未存输入产生恢复稿,同版本恢复不自动入库;另一版本先保存后409拒绝覆盖,旧稿隔离下载。"
+ }
+ ]
+ },
+ {
+ "id": "recovery.stale-version",
+ "name": "不同版本恢复稿隔离",
+ "group": "权限、缓存与未保存恢复",
+ "verification": "PENDING",
+ "plannedEvidence": [
+ {
+ "file": "tests/scene-editor-persistence-closure.spec.ts",
+ "line": 117,
+ "evidenceName": "real-recovery-conflict",
+ "testTitle": "同版本恢复与 409:真实未保存草稿重载、服务器新版本保护与隔离下载",
+ "kind": "browser",
+ "featureIds": [
+ "project.conflict",
+ "recovery.autobuffer",
+ "recovery.same-version",
+ "recovery.stale-version"
+ ],
+ "scope": "真实未存输入产生恢复稿,同版本恢复不自动入库;另一版本先保存后409拒绝覆盖,旧稿隔离下载。"
+ }
+ ]
+ },
+ {
+ "id": "recovery.discard",
+ "name": "下载、保留、丢弃恢复稿",
+ "group": "权限、缓存与未保存恢复",
+ "verification": "PENDING",
+ "plannedEvidence": [
+ {
+ "file": "tests/scene-editor-permissions-closure.spec.ts",
+ "line": 305,
+ "evidenceName": "scene-multi-session",
+ "testTitle": "双标签各自修改场景:保存和明确丢弃当前页均保留另一页真实恢复稿",
+ "kind": "browser",
+ "featureIds": [
+ "recovery.multi-session",
+ "recovery.autobuffer",
+ "recovery.discard",
+ "project.save"
+ ],
+ "scope": "以源测试实际断言为准;计划关联不代表最终通过。"
+ },
+ {
+ "file": "../unreal_tran_web/src/features/scene-legacy/contentApiClient.test.mjs",
+ "testTitle": "recovery conflict defaults to preserve and only explicit discard may remove it",
+ "featureIds": [
+ "recovery.discard"
+ ],
+ "kind": "unit",
+ "scope": "聚焦合同/真实DOM/Three对象计算;不称为浏览器鼠标交互。"
+ }
+ ]
+ },
+ {
+ "id": "recovery.multi-session",
+ "name": "多标签/多会话恢复所有权",
+ "group": "权限、缓存与未保存恢复",
+ "verification": "PENDING",
+ "plannedEvidence": [
+ {
+ "file": "tests/scene-editor-permissions-closure.spec.ts",
+ "line": 305,
+ "evidenceName": "scene-multi-session",
+ "testTitle": "双标签各自修改场景:保存和明确丢弃当前页均保留另一页真实恢复稿",
+ "kind": "browser",
+ "featureIds": [
+ "recovery.multi-session",
+ "recovery.autobuffer",
+ "recovery.discard",
+ "project.save"
+ ],
+ "scope": "以源测试实际断言为准;计划关联不代表最终通过。"
+ },
+ {
+ "file": "../unreal_tran_web/src/features/scene-legacy/contentApiClient.test.mjs",
+ "testTitle": "per-page recovery keys prevent copied tabs from deleting each other checkpoints",
+ "featureIds": [
+ "recovery.multi-session"
+ ],
+ "kind": "unit",
+ "scope": "聚焦合同/真实DOM/Three对象计算;不称为浏览器鼠标交互。"
+ }
+ ]
+ },
+ {
+ "id": "leave.cancel",
+ "name": "关闭场景标签时取消离开",
+ "group": "权限、缓存与未保存恢复",
+ "verification": "PENDING",
+ "plannedEvidence": [
+ {
+ "file": "tests/scene-editor-permissions-closure.spec.ts",
+ "line": 253,
+ "evidenceName": "scene-role-switch",
+ "testTitle": "后台场景未保存时切换角色:关闭确认保留草稿,保存切换后重建只读权限",
+ "kind": "browser",
+ "featureIds": [
+ "leave.role-switch",
+ "leave.cancel",
+ "leave.save",
+ "permission.readonly"
+ ],
+ "scope": "以源测试实际断言为准;计划关联不代表最终通过。"
+ },
+ {
+ "file": "tools/scene-tab-close.test.mjs",
+ "testTitle": "closing one selected scene checks its cached guard, leaves unrelated workspaces alone",
+ "featureIds": [
+ "leave.cancel",
+ "lifecycle.cache"
+ ],
+ "kind": "unit",
+ "scope": "聚焦合同/真实DOM/Three对象计算;不称为浏览器鼠标交互。"
+ },
+ {
+ "file": "tools/scene-tab-close.test.mjs",
+ "testTitle": "cancel or failed save in a later tab never commits earlier draft discard",
+ "featureIds": [
+ "leave.cancel",
+ "leave.save",
+ "leave.role-switch"
+ ],
+ "kind": "unit",
+ "scope": "聚焦合同/真实DOM/Three对象计算;不称为浏览器鼠标交互。"
+ }
+ ]
+ },
+ {
+ "id": "leave.save",
+ "name": "保存并离开 / 放弃修改",
+ "group": "权限、缓存与未保存恢复",
+ "verification": "PENDING",
+ "plannedEvidence": [
+ {
+ "file": "tests/scene-editor-permissions-closure.spec.ts",
+ "line": 253,
+ "evidenceName": "scene-role-switch",
+ "testTitle": "后台场景未保存时切换角色:关闭确认保留草稿,保存切换后重建只读权限",
+ "kind": "browser",
+ "featureIds": [
+ "leave.role-switch",
+ "leave.cancel",
+ "leave.save",
+ "permission.readonly"
+ ],
+ "scope": "以源测试实际断言为准;计划关联不代表最终通过。"
+ },
+ {
+ "file": "tools/scene-tab-close.test.mjs",
+ "testTitle": "cancel or failed save in a later tab never commits earlier draft discard",
+ "featureIds": [
+ "leave.cancel",
+ "leave.save",
+ "leave.role-switch"
+ ],
+ "kind": "unit",
+ "scope": "聚焦合同/真实DOM/Three对象计算;不称为浏览器鼠标交互。"
+ },
+ {
+ "file": "tools/scene-tab-close.test.mjs",
+ "testTitle": "all successful confirmations commit together and unmounted guards are removed",
+ "featureIds": [
+ "leave.save",
+ "lifecycle.destroy"
+ ],
+ "kind": "unit",
+ "scope": "聚焦合同/真实DOM/Three对象计算;不称为浏览器鼠标交互。"
+ }
+ ]
+ },
+ {
+ "id": "leave.busy",
+ "name": "加载/上传/发布途中离开",
+ "group": "权限、缓存与未保存恢复",
+ "verification": "PENDING",
+ "plannedEvidence": [
+ {
+ "file": "tests/scene-editor-async-closure.spec.ts",
+ "line": 27,
+ "evidenceName": "loading-retry",
+ "testTitle": "加载失败重试与加载互斥:不展示可写半场景、恢复网络后模型完整加载",
+ "kind": "browser",
+ "featureIds": [
+ "project.retry",
+ "glb.pending",
+ "leave.busy"
+ ],
+ "scope": "GET503只留中性失败和重试;延迟受控下载时禁用写入/导出/预览并阻止关闭,释放后真实狐狸加载。"
+ },
+ {
+ "file": "tools/scene-cover-api.test.mjs",
+ "testTitle": "role switch waits through validation, save, and the later queued publish after dirty clears",
+ "featureIds": [
+ "publish.concurrent",
+ "leave.busy",
+ "leave.role-switch"
+ ],
+ "kind": "unit",
+ "scope": "聚焦合同/真实DOM/Three对象计算;不称为浏览器鼠标交互。"
+ },
+ {
+ "file": "tools/scene-cover-api.test.mjs",
+ "testTitle": "role switch rechecks resource uploads started while the save was finishing",
+ "featureIds": [
+ "leave.busy"
+ ],
+ "kind": "unit",
+ "scope": "聚焦合同/真实DOM/Three对象计算;不称为浏览器鼠标交互。"
+ }
+ ]
+ },
+ {
+ "id": "leave.role-switch",
+ "name": "角色切换覆盖后台场景未存稿",
+ "group": "权限、缓存与未保存恢复",
+ "verification": "PENDING",
+ "plannedEvidence": [
+ {
+ "file": "tests/scene-editor-permissions-closure.spec.ts",
+ "line": 253,
+ "evidenceName": "scene-role-switch",
+ "testTitle": "后台场景未保存时切换角色:关闭确认保留草稿,保存切换后重建只读权限",
+ "kind": "browser",
+ "featureIds": [
+ "leave.role-switch",
+ "leave.cancel",
+ "leave.save",
+ "permission.readonly"
+ ],
+ "scope": "以源测试实际断言为准;计划关联不代表最终通过。"
+ },
+ {
+ "file": "tools/scene-tab-close.test.mjs",
+ "testTitle": "cancel or failed save in a later tab never commits earlier draft discard",
+ "featureIds": [
+ "leave.cancel",
+ "leave.save",
+ "leave.role-switch"
+ ],
+ "kind": "unit",
+ "scope": "聚焦合同/真实DOM/Three对象计算;不称为浏览器鼠标交互。"
+ },
+ {
+ "file": "tools/scene-cover-api.test.mjs",
+ "testTitle": "role switch waits through validation, save, and the later queued publish after dirty clears",
+ "featureIds": [
+ "publish.concurrent",
+ "leave.busy",
+ "leave.role-switch"
+ ],
+ "kind": "unit",
+ "scope": "聚焦合同/真实DOM/Three对象计算;不称为浏览器鼠标交互。"
+ }
+ ]
+ },
+ {
+ "id": "lifecycle.cache",
+ "name": "标签失活 pause / 激活 resume",
+ "group": "权限、缓存与未保存恢复",
+ "verification": "PENDING",
+ "plannedEvidence": [
+ {
+ "file": "tests/scene-editor-viewport-closure.spec.ts",
+ "line": 178,
+ "evidenceName": "viewport-cache-close",
+ "testTitle": "缓存标签暂停键盘、草稿恢复以及关闭取消和保存失败留页",
+ "kind": "browser",
+ "featureIds": [
+ "lifecycle.cache"
+ ],
+ "scope": "以源测试实际断言为准;计划关联不代表最终通过。"
+ },
+ {
+ "file": "tools/scene-closure-engine.test.mjs",
+ "testTitle": "typing undo/redo stays inside text controls and inactive cached editors ignore global shortcuts",
+ "featureIds": [
+ "history.undo",
+ "history.redo",
+ "transform.keys",
+ "lifecycle.cache"
+ ],
+ "kind": "unit",
+ "scope": "聚焦合同/真实DOM/Three对象计算;不称为浏览器鼠标交互。"
+ },
+ {
+ "file": "tools/scene-tab-close.test.mjs",
+ "testTitle": "closing one selected scene checks its cached guard, leaves unrelated workspaces alone",
+ "featureIds": [
+ "leave.cancel",
+ "lifecycle.cache"
+ ],
+ "kind": "unit",
+ "scope": "聚焦合同/真实DOM/Three对象计算;不称为浏览器鼠标交互。"
+ }
+ ]
+ },
+ {
+ "id": "lifecycle.destroy",
+ "name": "关闭释放 WebGL/监听/资源",
+ "group": "权限、缓存与未保存恢复",
+ "verification": "PENDING",
+ "plannedEvidence": [
+ {
+ "file": "tools/scene-closure-engine.test.mjs",
+ "testTitle": "deleting a root disposes its geometry and materials after clearing selection",
+ "featureIds": [
+ "object.delete",
+ "lifecycle.destroy"
+ ],
+ "kind": "unit",
+ "scope": "聚焦合同/真实DOM/Three对象计算;不称为浏览器鼠标交互。"
+ },
+ {
+ "file": "tools/scene-closure-engine.test.mjs",
+ "testTitle": "selection highlight cleanup restores source materials and releases its own GPU helper",
+ "featureIds": [
+ "selection.highlight",
+ "lifecycle.destroy"
+ ],
+ "kind": "unit",
+ "scope": "聚焦合同/真实DOM/Three对象计算;不称为浏览器鼠标交互。"
+ },
+ {
+ "file": "tools/scene-tab-close.test.mjs",
+ "testTitle": "all successful confirmations commit together and unmounted guards are removed",
+ "featureIds": [
+ "leave.save",
+ "lifecycle.destroy"
+ ],
+ "kind": "unit",
+ "scope": "聚焦合同/真实DOM/Three对象计算;不称为浏览器鼠标交互。"
+ },
+ {
+ "file": "../unreal_tran_web/src/features/scene-legacy/contentApiClient.test.mjs",
+ "testTitle": "an upload finishing after a project switch cannot register into the new session",
+ "featureIds": [
+ "glb.pending",
+ "lifecycle.destroy"
+ ],
+ "kind": "unit",
+ "scope": "聚焦合同/真实DOM/Three对象计算;不称为浏览器鼠标交互。"
+ }
+ ]
+ },
+ {
+ "id": "lifecycle.resize-theme",
+ "name": "桌面尺寸与明亮/暗黑可读性",
+ "group": "权限、缓存与未保存恢复",
+ "verification": "PENDING",
+ "plannedEvidence": [
+ {
+ "file": "tests/scene-editor-viewport-closure.spec.ts",
+ "line": 117,
+ "evidenceName": "viewport-camera-theme",
+ "testTitle": "真实相机操控、视口拾取、预览禁写和两尺寸主题可读性",
+ "kind": "browser",
+ "featureIds": [
+ "view.orbit",
+ "view.pan",
+ "view.zoom",
+ "view.top",
+ "view.perspective",
+ "view.grid",
+ "view.focus",
+ "selection.viewport",
+ "selection.highlight",
+ "preview.enter",
+ "preview.exit",
+ "preview.lock",
+ "lifecycle.resize-theme"
+ ],
+ "scope": "以源测试实际断言为准;计划关联不代表最终通过。"
+ }
+ ]
+ }
+ ]
+}
\ No newline at end of file
diff --git a/reports/scene-editor-closure-20260906/data/inventory-scene.md b/reports/scene-editor-closure-20260906/data/inventory-scene.md
new file mode 100644
index 0000000..72982f4
--- /dev/null
+++ b/reports/scene-editor-closure-20260906/data/inventory-scene.md
@@ -0,0 +1,37 @@
+# 场景编辑器源码基线
+
+本清单是本轮开始时的静态审查,尚不构成测试通过。完整逐项矩阵见 [119 项功能清单](../feature-catalog.json),源码行号由报告工具按当前符号重新定位。
+
+## 实际执行路径
+
+`router/index.ts` 的 `/content/scenes/:id/edit` 挂载 `LegacyScenePageView.vue`;宿主加载当前项目、已发布模型目录和受控资产,挂载 `scene-legacy/runtime.ts`,最终执行 `guide-legacy/demo-src/scene-editor.js`。`SceneEditorView.vue` 和 `SceneEditorWorkspace.vue` 为保留但未挂载的正式实现。
+
+顶部可达:返回、当前项目说明、新建、导入工程、保存草稿、导出 JSON、撤销、重做、移动、旋转、缩放、0.5m 吸附、校验、运行预览、发布。返回列表保留场景标签缓存,等同普通切页;真正关闭标签才执行未保存确认。快捷键覆盖 W/E/R、Delete、Ctrl/Meta+S/Z/Y。左侧为场景大纲、资源库、模板,搜索共用输入框。视口有透视、顶视图、聚焦选中、网格,真实车库另有完整/透视/隐藏围护模式。
+
+资源库包含 API 已发布模型,8 种基础资源,以及真实装备车库和真实野外地形两种整体场地。模板共 9 个,各自需要独立验证环境、模板选中标签、种子对象及保存重开;不能只验证一个模板后将其余标为通过。
+
+属性区包含名称、可见、可操作、位置、旋转、缩放、设备编号、物模型和语义类型。已发布模型的部件选中、受限变换、隐藏和软删除需遵守源部件能力,实例删除不能删除来源资产。底部为对象列表、设备绑定、发布检查、操作日志。
+
+## 数据与权限边界
+
+宿主 API 控制工程内容、版本、封面和发布。保存截图为 JPEG,最长边 640,捕获后恢复辅助对象与材质;上传和主文档保存必须属于同次受控保存。截图失败允许保留旧封面并真实告警,409 必须阻断。
+
+来源模型序列化包含 targetProjectId、targetVersionId、sourceAssetId、assetCode、modelEditorAssetId 和受控 resourceUrl。场景自行上传资源使用 `content://asset/scene/{projectId}/{assetCode}`;来源模型可使用 `content://asset/model/{projectId}/{versionId}/{assetId}`。临时 blob 仅供运行,不能作为可交付持久引用。
+
+恢复稿、跨会话租约、同版本恢复提示、陈旧版本隔离、离开前保存、角色切换和后台标签缓存都是必须单独验证的闭环。部分缓存动作只暂停 RAF 不代表键盘监听已经失活,需实际切换标签测试。
+
+## 基线风险(等待修复和证据)
+
+- runtime 仍检查旧 `content.update`,可能与宿主细分权限不一致。
+- 透视按钮有标记但基线处理路径缺失;预览不能仅改变样式后仍允许快捷键写入。
+- 新建在基线中重置当前 ID;应由宿主真实创建新工程并执行未存确认。
+- JSON 缺失资源恢复和异步撤销需要原子回滚,避免半替换内容或错误历史索引。
+- 显式错误 assetCode 不能回退到首个 READY 模型资产。
+- 底部基线日志固定三行,设备绑定状态固定文本;需要避免误导为真实操作记录或已校验绑定。
+- 包含相机/灯光/雾的工程字段需要往返,当前没有 UI 的编辑器不应假装已做 UI 验证。
+
+## 明确边界
+
+运行预览主要用于当前三维场景检查。基线渲染循环没有自动执行来源模型时间轴、蓝图的机制。这个大范围运行合同与前轮模型报告的待确认项一致,不能以场景静态可见宣称动作流程已在下游运行。
+
+当前树头是展示行,没有可折叠控件的基线证据;若本轮修复增加折叠,需要补真实 UI 证据和矩阵记录,不能通过程序直接隐藏 DOM 充当折叠测试。
diff --git a/reports/scene-editor-closure-20260906/decisions.json b/reports/scene-editor-closure-20260906/decisions.json
new file mode 100644
index 0000000..dcd0ea7
--- /dev/null
+++ b/reports/scene-editor-closure-20260906/decisions.json
@@ -0,0 +1,22 @@
+{
+ "items": [
+ {
+ "id":"SCENE-D01",
+ "title":"保留的正式工作区额外控件是否迁入默认入口",
+ "status":"PENDING",
+ "current":"默认入口为 legacy 场景编辑器。另一份未挂载工作区的额外渲染、组件、动画和布局控件不能计入当前 UI 已闭环。",
+ "behavior":"若希望使用这些功能,需要明确采用哪一个入口和哪些现有控件,避免两个编辑器对同一工程写出不同合同。",
+ "scope":"单独确认所需控件清单和入口迁移范围,优先保留已有场景文档、权限、封面、固定版本与恢复机制。",
+ "acceptance":"所选入口只有一条业务真值链路;既有场景往返不丢字段,权限、导入、保存、发布和截图均完成独立端到端回归。"
+ },
+ {
+ "id":"SCENE-D02",
+ "title":"下游自动执行来源模型时间轴和蓝图",
+ "status":"PENDING",
+ "current":"场景当前导入模型几何、部件状态和材质;静态预览不能证明来源模型动作或蓝图在场景和训练中执行。",
+ "behavior":"例如模型中的点击蓝图或时间轴动作,应在场景运行和训练任务的哪个时点触发、怎样停止和重置,目前没有完整下游合同。",
+ "scope":"沿用前轮模型审查的大范围待确认项,单独定义固定版本的运行状态、触发事件、权限、生命周期与训练结果追踪。",
+ "acceptance":"用明确的动作样例验证导入、发布、场景预览、训练运行、停止重置和结果记录;不能只以模型可见或接口引用通过验收。"
+ }
+ ]
+}
diff --git a/reports/scene-editor-closure-20260906/evidence/basic-assets.json b/reports/scene-editor-closure-20260906/evidence/basic-assets.json
new file mode 100644
index 0000000..521c716
--- /dev/null
+++ b/reports/scene-editor-closure-20260906/evidence/basic-assets.json
@@ -0,0 +1,35 @@
+{
+ "time": "2026-09-06T10:28:51.941Z",
+ "data": {
+ "kind": "e2e",
+ "status": "PASS",
+ "testTitle": "八种基础资源逐项放置、真实拖放、搜索和保存重开",
+ "featureIds": [
+ "asset.diagnostic",
+ "asset.equipment",
+ "asset.workbench",
+ "asset.shelf",
+ "asset.safety-zone",
+ "asset.camera",
+ "asset.worker",
+ "asset.light",
+ "assets.basic-double-click",
+ "assets.basic-drop",
+ "search.assets",
+ "panel.objects"
+ ],
+ "projectId": "397",
+ "dragged": {
+ "position": [
+ 7,
+ 0,
+ -1
+ ],
+ "type": "workbench"
+ },
+ "scope": "8 种基础资源分别双击放置并分开布置;真实指针拖入第 9 个工作台、吸附检查、唯一编号、保存刷新一致。照明为现有资源外观,不冒称连接真实灯控。",
+ "screenshots": [
+ "30-eight-basic-assets-reopened.png"
+ ]
+ }
+}
\ No newline at end of file
diff --git a/reports/scene-editor-closure-20260906/evidence/downstream-fox.json b/reports/scene-editor-closure-20260906/evidence/downstream-fox.json
new file mode 100644
index 0000000..c43fce6
--- /dev/null
+++ b/reports/scene-editor-closure-20260906/evidence/downstream-fox.json
@@ -0,0 +1,23 @@
+{
+ "time": "2026-09-06T10:24:43.055Z",
+ "data": {
+ "kind": "browser",
+ "status": "PASS",
+ "testTitle": "fox:场景发布后在真实训练编排选择、保存并重开静态模型",
+ "featureIds": [
+ "publish.downstream"
+ ],
+ "projectId": "375",
+ "sceneVersionId": "429",
+ "trainingProjectId": "376",
+ "dependency": {
+ "id": "397",
+ "targetProjectId": "375",
+ "targetVersionId": "429",
+ "relationType": "TRAINING_SCENE",
+ "required": true,
+ "sortOrder": 0
+ },
+ "staticOnly": true
+ }
+}
\ No newline at end of file
diff --git a/reports/scene-editor-closure-20260906/evidence/downstream-truck.json b/reports/scene-editor-closure-20260906/evidence/downstream-truck.json
new file mode 100644
index 0000000..2bc3312
--- /dev/null
+++ b/reports/scene-editor-closure-20260906/evidence/downstream-truck.json
@@ -0,0 +1,23 @@
+{
+ "time": "2026-09-06T10:25:01.457Z",
+ "data": {
+ "kind": "browser",
+ "status": "PASS",
+ "testTitle": "truck:场景发布后在真实训练编排选择、保存并重开静态模型",
+ "featureIds": [
+ "publish.downstream"
+ ],
+ "projectId": "377",
+ "sceneVersionId": "431",
+ "trainingProjectId": "378",
+ "dependency": {
+ "id": "401",
+ "targetProjectId": "377",
+ "targetVersionId": "431",
+ "relationType": "TRAINING_SCENE",
+ "required": true,
+ "sortOrder": 0
+ },
+ "staticOnly": true
+ }
+}
\ No newline at end of file
diff --git a/reports/scene-editor-closure-20260906/evidence/fox-properties-cover.json b/reports/scene-editor-closure-20260906/evidence/fox-properties-cover.json
new file mode 100644
index 0000000..866a99a
--- /dev/null
+++ b/reports/scene-editor-closure-20260906/evidence/fox-properties-cover.json
@@ -0,0 +1,1798 @@
+{
+ "time": "2026-09-06T10:30:14.385Z",
+ "data": {
+ "kind": "browser",
+ "status": "PASS",
+ "testTitle": "狐狸:真实细分权限、全部属性、清空绑定、蒙皮保护、相机与封面保存重开",
+ "featureIds": [
+ "project.load",
+ "project.title",
+ "project.save",
+ "selection.tree",
+ "property.name",
+ "property.visible",
+ "property.operable",
+ "property.position",
+ "property.rotation",
+ "property.scale",
+ "property.device-id",
+ "property.model-id",
+ "property.semantic",
+ "part.source",
+ "panel.binding",
+ "cover.capture",
+ "cover.atomic"
+ ],
+ "scope": "真实字段输入与清空、六个语义选项、蒙皮保护、源模型可见,正文/资产/封面保存和刷新;不把原件旧模板组合当新模板结果。",
+ "projectId": "403",
+ "original": {
+ "mounted": true,
+ "hasEnvironment": true,
+ "hasEnvironmentTarget": true,
+ "toneMappingExposure": 1.05,
+ "background": "a8cbe1",
+ "meshCount": 15,
+ "materials": [
+ {
+ "name": "(未命名)",
+ "color": "445e2d",
+ "metalness": 0.35,
+ "roughness": 0.55,
+ "envMapIntensity": 1,
+ "hasMap": false,
+ "preset": null,
+ "rule": null,
+ "authored": null
+ },
+ {
+ "name": "(未命名)",
+ "color": "182c36",
+ "metalness": 0.55,
+ "roughness": 0.34,
+ "envMapIntensity": 1,
+ "hasMap": false,
+ "preset": null,
+ "rule": null,
+ "authored": null
+ },
+ {
+ "name": "(未命名)",
+ "color": "1e4f69",
+ "metalness": 0.45,
+ "roughness": 0.42,
+ "envMapIntensity": 1,
+ "hasMap": false,
+ "preset": null,
+ "rule": null,
+ "authored": null
+ },
+ {
+ "name": "(未命名)",
+ "color": "36b37e",
+ "metalness": 0,
+ "roughness": 1,
+ "envMapIntensity": 1,
+ "hasMap": false,
+ "preset": null,
+ "rule": null,
+ "authored": null
+ },
+ {
+ "name": "(未命名)",
+ "color": "182c36",
+ "metalness": 0.55,
+ "roughness": 0.34,
+ "envMapIntensity": 1,
+ "hasMap": false,
+ "preset": null,
+ "rule": null,
+ "authored": null
+ },
+ {
+ "name": "(未命名)",
+ "color": "e4a62b",
+ "metalness": 0.25,
+ "roughness": 0.48,
+ "envMapIntensity": 1,
+ "hasMap": false,
+ "preset": null,
+ "rule": null,
+ "authored": null
+ },
+ {
+ "name": "M_3dfreehub_mat_2",
+ "color": "ffffff",
+ "metalness": 0,
+ "roughness": 0.5,
+ "envMapIntensity": 1,
+ "hasMap": true,
+ "preset": null,
+ "rule": null,
+ "authored": null
+ },
+ {
+ "name": "M_3dfreehub_mat_3",
+ "color": "ffffff",
+ "metalness": 0,
+ "roughness": 0.5,
+ "envMapIntensity": 1,
+ "hasMap": true,
+ "preset": null,
+ "rule": null,
+ "authored": null
+ },
+ {
+ "name": "M_3dfreehub_mat_4",
+ "color": "ffffff",
+ "metalness": 0,
+ "roughness": 0.5,
+ "envMapIntensity": 1,
+ "hasMap": true,
+ "preset": null,
+ "rule": null,
+ "authored": null
+ },
+ {
+ "name": "M_3dfreehub_mat_5",
+ "color": "ffffff",
+ "metalness": 0,
+ "roughness": 0.5,
+ "envMapIntensity": 1,
+ "hasMap": true,
+ "preset": null,
+ "rule": null,
+ "authored": null
+ },
+ {
+ "name": "Material__85",
+ "color": "695c5b",
+ "metalness": 0,
+ "roughness": 0.5,
+ "envMapIntensity": 1,
+ "hasMap": false,
+ "preset": null,
+ "rule": null,
+ "authored": null
+ },
+ {
+ "name": "fox_material",
+ "color": "ffffff",
+ "metalness": 0,
+ "roughness": 0.3,
+ "envMapIntensity": 1,
+ "hasMap": true,
+ "preset": null,
+ "rule": null,
+ "authored": null
+ }
+ ],
+ "objectCount": 5,
+ "contentBounds": {
+ "min": [
+ -41,
+ -5.239,
+ -34.028
+ ],
+ "max": [
+ 41,
+ 11.358,
+ 34.028
+ ],
+ "size": [
+ 82,
+ 16.596,
+ 68.057
+ ]
+ },
+ "importedObjects": [
+ {
+ "name": "狐狸野外巡检目标",
+ "modelEditorModelId": "imported-model_mto6v5w6_09-fox",
+ "modelEditorAssetId": "content-model-157-201",
+ "deviceId": "SCENE-FOX-001",
+ "modelId": "MODEL-IMPORT-06",
+ "assetCode": "MODEL_MTO6V5W6_09-FOX",
+ "targetProjectId": "157",
+ "targetVersionId": "201"
+ }
+ ],
+ "apiControlled": true,
+ "readOnly": false,
+ "ready": true,
+ "readySettled": true,
+ "dirty": false,
+ "saving": false,
+ "publishing": false,
+ "pendingLoads": 0,
+ "paused": false,
+ "previewing": false,
+ "currentPreset": {
+ "id": "workshop-real",
+ "name": "演示维修车间(可拆分)",
+ "type": "车间"
+ },
+ "cameraPosition": [
+ -0.04179225073787585,
+ 6.099634646023559,
+ 17.355036987378313
+ ],
+ "cameraTarget": [
+ -7.512232860974557,
+ 1.4059499759307126,
+ -2.2204460492503126e-16
+ ],
+ "gridVisible": true,
+ "transformMode": "translate",
+ "selectedSceneId": "",
+ "canUndo": false,
+ "canRedo": false,
+ "objects": [
+ {
+ "sceneId": "OBJ-1788605873329-2",
+ "type": "equipment",
+ "name": "维修装备主体 02",
+ "visible": true,
+ "position": [
+ 0,
+ 0,
+ 0
+ ],
+ "rotation": [
+ 0,
+ 0,
+ 0
+ ],
+ "scale": [
+ 1,
+ 1,
+ 1
+ ],
+ "deviceId": "EQ-002",
+ "modelId": "MODEL-EQUIPMENT-02",
+ "semantic": "实训装备",
+ "operable": true,
+ "capabilities": [
+ "select",
+ "inspect",
+ "transform"
+ ],
+ "assetCode": "",
+ "sourceAssetId": "",
+ "targetProjectId": "",
+ "targetVersionId": "",
+ "modelEditorAssetId": "",
+ "parts": []
+ },
+ {
+ "sceneId": "OBJ-1788605873331-3",
+ "type": "safety-zone",
+ "name": "安全作业区域 03",
+ "visible": true,
+ "position": [
+ 0,
+ 0.02,
+ 0
+ ],
+ "rotation": [
+ 0,
+ 0,
+ 0
+ ],
+ "scale": [
+ 1,
+ 1,
+ 1
+ ],
+ "deviceId": "EQ-003",
+ "modelId": "MODEL-SAFETY-ZONE-03",
+ "semantic": "安全区域",
+ "operable": false,
+ "capabilities": [
+ "select"
+ ],
+ "assetCode": "",
+ "sourceAssetId": "",
+ "targetProjectId": "",
+ "targetVersionId": "",
+ "modelEditorAssetId": "",
+ "parts": []
+ },
+ {
+ "sceneId": "OBJ-1788605873333-4",
+ "type": "light",
+ "name": "区域照明 04",
+ "visible": true,
+ "position": [
+ -12,
+ 0,
+ 8
+ ],
+ "rotation": [
+ 0,
+ 0,
+ 0
+ ],
+ "scale": [
+ 1,
+ 1,
+ 1
+ ],
+ "deviceId": "EQ-004",
+ "modelId": "MODEL-LIGHT-04",
+ "semantic": "实训装备",
+ "operable": false,
+ "capabilities": [
+ "select"
+ ],
+ "assetCode": "",
+ "sourceAssetId": "",
+ "targetProjectId": "",
+ "targetVersionId": "",
+ "modelEditorAssetId": "",
+ "parts": []
+ },
+ {
+ "sceneId": "OBJ-1788605916565-6",
+ "type": "imported",
+ "name": "狐狸野外巡检目标",
+ "visible": true,
+ "position": [
+ -7.5,
+ 0.10930174852949677,
+ 0.3440109341705351
+ ],
+ "rotation": [
+ 0,
+ 0,
+ 0
+ ],
+ "scale": [
+ 0.03616375932570189,
+ 0.03616375932570189,
+ 0.03616375932570189
+ ],
+ "deviceId": "SCENE-FOX-001",
+ "modelId": "MODEL-IMPORT-06",
+ "semantic": "实训装备",
+ "operable": true,
+ "capabilities": [
+ "select",
+ "inspect",
+ "transform"
+ ],
+ "assetCode": "MODEL_MTO6V5W6_09-FOX",
+ "sourceAssetId": "2030",
+ "targetProjectId": "157",
+ "targetVersionId": "201",
+ "modelEditorAssetId": "content-model-157-201",
+ "parts": [
+ {
+ "sceneId": "interaction-a3d35c26",
+ "name": "fox",
+ "visible": true,
+ "sceneDeleted": false,
+ "position": [
+ 0.25,
+ 0,
+ 0
+ ],
+ "rotation": [
+ 0,
+ 0,
+ 0
+ ],
+ "scale": [
+ 1,
+ 1,
+ 1
+ ],
+ "capabilities": [
+ "select",
+ "inspect"
+ ]
+ }
+ ]
+ },
+ {
+ "sceneId": "OBJ-1788605874053-5",
+ "type": "imported",
+ "name": "地形",
+ "visible": true,
+ "position": [
+ 3.069303533692093,
+ 4.802826448856971,
+ -3.074293867354786
+ ],
+ "rotation": [
+ 0,
+ 0,
+ 0
+ ],
+ "scale": [
+ 1.0050406774865042,
+ 1.0050406774865042,
+ 1.0050406774865042
+ ],
+ "deviceId": "EQ-005",
+ "modelId": "MODEL-IMPORT-05",
+ "semantic": "环境设施",
+ "operable": false,
+ "capabilities": [
+ "select"
+ ],
+ "assetCode": "",
+ "sourceAssetId": "",
+ "targetProjectId": "",
+ "targetVersionId": "",
+ "modelEditorAssetId": "",
+ "parts": []
+ }
+ ]
+ },
+ "savedObject": {
+ "sceneId": "OBJ-1788605916565-6",
+ "type": "imported",
+ "name": "闭环验收狐狸目标",
+ "deviceId": "",
+ "modelId": "",
+ "semantic": "训练人员",
+ "assetRole": "",
+ "builtinAssetId": "content-model-157-201",
+ "modelEditorAssetId": "content-model-157-201",
+ "modelEditorModelId": "imported-model_mto6v5w6_09-fox",
+ "modelEditorRevision": 2,
+ "modelOverrides": {
+ "equipment-root": {
+ "name": "09-Fox",
+ "nodeName": "09-Fox",
+ "nodePath": "@root",
+ "interactionId": "interaction-b9240aaa",
+ "capabilities": {
+ "selectable": true,
+ "transform": true,
+ "visible": true,
+ "material": false,
+ "animatable": true
+ },
+ "position": [
+ 0,
+ 0,
+ 0
+ ],
+ "rotation": [
+ 0,
+ 0,
+ 0
+ ],
+ "scale": [
+ 1,
+ 1,
+ 1
+ ],
+ "visible": true,
+ "customComponents": []
+ },
+ "model-imported-model_mto6v5w6_09-fox-0": {
+ "name": "",
+ "nodeName": "Group",
+ "nodePath": "0",
+ "interactionId": "interaction-02351bce",
+ "capabilities": {
+ "selectable": true,
+ "transform": true,
+ "visible": true,
+ "material": false,
+ "animatable": true
+ },
+ "position": [
+ 3.875009990661482e-8,
+ 0.007396563645075124,
+ 0.6522089834776823
+ ],
+ "rotation": [
+ 0,
+ 0,
+ 0
+ ],
+ "scale": [
+ 0.06075496488891343,
+ 0.06075496488891343,
+ 0.06075496488891343
+ ],
+ "visible": true,
+ "customComponents": []
+ },
+ "model-imported-model_mto6v5w6_09-fox-1": {
+ "name": "root",
+ "nodeName": "root",
+ "nodePath": "0/0",
+ "interactionId": "interaction-a4d35db9",
+ "capabilities": {
+ "selectable": true,
+ "transform": true,
+ "visible": true,
+ "material": false,
+ "animatable": true
+ },
+ "position": [
+ 0,
+ 0,
+ 0
+ ],
+ "rotation": [
+ 0,
+ 0,
+ 0
+ ],
+ "scale": [
+ 1,
+ 1,
+ 1
+ ],
+ "visible": true,
+ "customComponents": []
+ },
+ "model-imported-model_mto6v5w6_09-fox-2": {
+ "name": "_rootJoint",
+ "nodeName": "_rootJoint",
+ "nodePath": "0/0/0",
+ "interactionId": "interaction-4476c556",
+ "capabilities": {
+ "selectable": true,
+ "transform": true,
+ "visible": true,
+ "material": false,
+ "animatable": true
+ },
+ "position": [
+ 0,
+ 0,
+ 0
+ ],
+ "rotation": [
+ 0,
+ 0,
+ 0
+ ],
+ "scale": [
+ 1,
+ 1,
+ 1
+ ],
+ "visible": true,
+ "customComponents": []
+ },
+ "model-imported-model_mto6v5w6_09-fox-3": {
+ "name": "b_Root_00",
+ "nodeName": "b_Root_00",
+ "nodePath": "0/0/0/0",
+ "interactionId": "interaction-ffc45d11",
+ "capabilities": {
+ "selectable": true,
+ "transform": true,
+ "visible": true,
+ "material": false,
+ "animatable": true
+ },
+ "position": [
+ 0,
+ 0,
+ 0
+ ],
+ "rotation": [
+ -1.5708000357176293,
+ 0,
+ 0
+ ],
+ "scale": [
+ 1,
+ 1,
+ 1
+ ],
+ "visible": true,
+ "customComponents": []
+ },
+ "model-imported-model_mto6v5w6_09-fox-4": {
+ "name": "b_Hip_01",
+ "nodeName": "b_Hip_01",
+ "nodePath": "0/0/0/0/0",
+ "interactionId": "interaction-7a75e39e",
+ "capabilities": {
+ "selectable": true,
+ "transform": true,
+ "visible": true,
+ "material": false,
+ "animatable": true
+ },
+ "position": [
+ 0.000001241521920852674,
+ 22.061977333358605,
+ 34.62373020761327
+ ],
+ "rotation": [
+ 0.44722050284761794,
+ -1.5707951637404125,
+ 0
+ ],
+ "scale": [
+ 1,
+ 1,
+ 1
+ ],
+ "visible": true,
+ "customComponents": []
+ },
+ "model-imported-model_mto6v5w6_09-fox-5": {
+ "name": "b_Spine01_02",
+ "nodeName": "b_Spine01_02",
+ "nodePath": "0/0/0/0/0/0",
+ "interactionId": "interaction-663ea469",
+ "capabilities": {
+ "selectable": true,
+ "transform": true,
+ "visible": true,
+ "material": false,
+ "animatable": true
+ },
+ "position": [
+ 12.850601196289062,
+ 0,
+ 0
+ ],
+ "rotation": [
+ -3.357310257291308e-7,
+ -3.316574913360067e-8,
+ -1.2102038452161539
+ ],
+ "scale": [
+ 1,
+ 1,
+ 1
+ ],
+ "visible": true,
+ "customComponents": []
+ },
+ "model-imported-model_mto6v5w6_09-fox-6": {
+ "name": "b_Spine02_03",
+ "nodeName": "b_Spine02_03",
+ "nodePath": "0/0/0/0/0/0/0",
+ "interactionId": "interaction-46867126",
+ "capabilities": {
+ "selectable": true,
+ "transform": true,
+ "visible": true,
+ "material": false,
+ "animatable": true
+ },
+ "position": [
+ 21.65575408935547,
+ -0.000118255615234375,
+ 0
+ ],
+ "rotation": [
+ 8.772651057591018e-8,
+ 3.3355428835535634e-8,
+ 0.3633406824515624
+ ],
+ "scale": [
+ 1,
+ 1,
+ 1
+ ],
+ "visible": true,
+ "customComponents": []
+ },
+ "model-imported-model_mto6v5w6_09-fox-7": {
+ "name": "b_Neck_04",
+ "nodeName": "b_Neck_04",
+ "nodePath": "0/0/0/0/0/0/0/0",
+ "interactionId": "interaction-27148d81",
+ "capabilities": {
+ "selectable": true,
+ "transform": true,
+ "visible": true,
+ "material": false,
+ "animatable": true
+ },
+ "position": [
+ 25.64914321899414,
+ 0,
+ 0
+ ],
+ "rotation": [
+ 6.423807629998062e-15,
+ 1.9993472402144555e-16,
+ 0.031113979193075042
+ ],
+ "scale": [
+ 1,
+ 1,
+ 1
+ ],
+ "visible": true,
+ "customComponents": []
+ },
+ "model-imported-model_mto6v5w6_09-fox-8": {
+ "name": "b_Head_05",
+ "nodeName": "b_Head_05",
+ "nodePath": "0/0/0/0/0/0/0/0/0",
+ "interactionId": "interaction-b24eef2e",
+ "capabilities": {
+ "selectable": true,
+ "transform": true,
+ "visible": true,
+ "material": false,
+ "animatable": true
+ },
+ "position": [
+ 13.376960754394531,
+ 0,
+ 0
+ ],
+ "rotation": [
+ 3.253825627358364e-8,
+ -1.577320798309453e-8,
+ -0.451380500897834
+ ],
+ "scale": [
+ 1,
+ 1,
+ 1
+ ],
+ "visible": true,
+ "customComponents": []
+ },
+ "model-imported-model_mto6v5w6_09-fox-9": {
+ "name": "b_RightUpperArm_06",
+ "nodeName": "b_RightUpperArm_06",
+ "nodePath": "0/0/0/0/0/0/0/1",
+ "interactionId": "interaction-26148bee",
+ "capabilities": {
+ "selectable": true,
+ "transform": true,
+ "visible": true,
+ "material": false,
+ "animatable": true
+ },
+ "position": [
+ 18.677913665771484,
+ -4.297340393066406,
+ 6.9675750732421875
+ ],
+ "rotation": [
+ -0.035644184619881884,
+ -0.047001863061712265,
+ -1.2972005346348685
+ ],
+ "scale": [
+ 1,
+ 1,
+ 1
+ ],
+ "visible": true,
+ "customComponents": []
+ },
+ "model-imported-model_mto6v5w6_09-fox-10": {
+ "name": "b_RightForeArm_07",
+ "nodeName": "b_RightForeArm_07",
+ "nodePath": "0/0/0/0/0/0/0/1/0",
+ "interactionId": "interaction-36896559",
+ "capabilities": {
+ "selectable": true,
+ "transform": true,
+ "visible": true,
+ "material": false,
+ "animatable": true
+ },
+ "position": [
+ 23.04512596130371,
+ 0,
+ 0
+ ],
+ "rotation": [
+ 2.1355818997049898e-8,
+ 7.233656206674673e-8,
+ 1.2561843405841544
+ ],
+ "scale": [
+ 1,
+ 1,
+ 1
+ ],
+ "visible": true,
+ "customComponents": []
+ },
+ "model-imported-model_mto6v5w6_09-fox-11": {
+ "name": "b_RightHand_08",
+ "nodeName": "b_RightHand_08",
+ "nodePath": "0/0/0/0/0/0/0/1/0/0",
+ "interactionId": "interaction-57a124f6",
+ "capabilities": {
+ "selectable": true,
+ "transform": true,
+ "visible": true,
+ "material": false,
+ "animatable": true
+ },
+ "position": [
+ 19.350055694580078,
+ -0.14598655700683594,
+ 0
+ ],
+ "rotation": [
+ 0.005245101551986586,
+ 0.05200705984217871,
+ -0.267165337534188
+ ],
+ "scale": [
+ 1,
+ 1,
+ 1
+ ],
+ "visible": true,
+ "customComponents": []
+ },
+ "model-imported-model_mto6v5w6_09-fox-12": {
+ "name": "b_LeftUpperArm_09",
+ "nodeName": "b_LeftUpperArm_09",
+ "nodePath": "0/0/0/0/0/0/0/2",
+ "interactionId": "interaction-25148a5b",
+ "capabilities": {
+ "selectable": true,
+ "transform": true,
+ "visible": true,
+ "material": false,
+ "animatable": true
+ },
+ "position": [
+ 18.67791748046875,
+ -4.297344207763672,
+ -6.967987060546875
+ ],
+ "rotation": [
+ -0.00020118104561539401,
+ 0.0009350561550523671,
+ -1.5148902559141275
+ ],
+ "scale": [
+ 1,
+ 1,
+ 1
+ ],
+ "visible": true,
+ "customComponents": []
+ },
+ "model-imported-model_mto6v5w6_09-fox-13": {
+ "name": "b_LeftForeArm_010",
+ "nodeName": "b_LeftForeArm_010",
+ "nodePath": "0/0/0/0/0/0/0/2/0",
+ "interactionId": "interaction-5c502ac4",
+ "capabilities": {
+ "selectable": true,
+ "transform": true,
+ "visible": true,
+ "material": false,
+ "animatable": true
+ },
+ "position": [
+ 23.045124053955078,
+ 0,
+ 0
+ ],
+ "rotation": [
+ 2.5220861991717696e-8,
+ 7.109903232964989e-8,
+ 1.2307106255258764
+ ],
+ "scale": [
+ 1,
+ 1,
+ 1
+ ],
+ "visible": true,
+ "customComponents": []
+ },
+ "model-imported-model_mto6v5w6_09-fox-14": {
+ "name": "b_LeftHand_011",
+ "nodeName": "b_LeftHand_011",
+ "nodePath": "0/0/0/0/0/0/0/2/0/0",
+ "interactionId": "interaction-192b4cd3",
+ "capabilities": {
+ "selectable": true,
+ "transform": true,
+ "visible": true,
+ "material": false,
+ "animatable": true
+ },
+ "position": [
+ 19.350051879882812,
+ -0.14599037170410156,
+ 0
+ ],
+ "rotation": [
+ 0.004279557751527162,
+ -0.03344276494440009,
+ -1.5985291598689726
+ ],
+ "scale": [
+ 1,
+ 1,
+ 1
+ ],
+ "visible": true,
+ "customComponents": []
+ },
+ "model-imported-model_mto6v5w6_09-fox-15": {
+ "name": "b_Tail01_012",
+ "nodeName": "b_Tail01_012",
+ "nodePath": "0/0/0/0/0/1",
+ "interactionId": "interaction-653ea2d6",
+ "capabilities": {
+ "selectable": true,
+ "transform": true,
+ "visible": true,
+ "material": false,
+ "animatable": true
+ },
+ "position": [
+ 4.2603759765625,
+ 15.958770751953125,
+ 0
+ ],
+ "rotation": [
+ -5.364745216052604e-7,
+ 3.424797608447095e-7,
+ 1.9941304874941974
+ ],
+ "scale": [
+ 1,
+ 1,
+ 1
+ ],
+ "visible": true,
+ "customComponents": []
+ },
+ "model-imported-model_mto6v5w6_09-fox-16": {
+ "name": "b_Tail02_013",
+ "nodeName": "b_Tail02_013",
+ "nodePath": "0/0/0/0/0/1/0",
+ "interactionId": "interaction-6c4d3691",
+ "capabilities": {
+ "selectable": true,
+ "transform": true,
+ "visible": true,
+ "material": false,
+ "animatable": true
+ },
+ "position": [
+ 12.411918640136719,
+ 0,
+ 0
+ ],
+ "rotation": [
+ -6.738507958375962e-23,
+ -5.2439168243310865e-23,
+ 0.3128105211171357
+ ],
+ "scale": [
+ 1,
+ 1,
+ 1
+ ],
+ "visible": true,
+ "customComponents": []
+ },
+ "model-imported-model_mto6v5w6_09-fox-17": {
+ "name": "b_Tail03_014",
+ "nodeName": "b_Tail03_014",
+ "nodePath": "0/0/0/0/0/1/0/0",
+ "interactionId": "interaction-e93e191e",
+ "capabilities": {
+ "selectable": true,
+ "transform": true,
+ "visible": true,
+ "material": false,
+ "animatable": true
+ },
+ "position": [
+ 24.24032211303711,
+ 0,
+ 0
+ ],
+ "rotation": [
+ -5.7486339033124146e-24,
+ 1.1669178332583734e-22,
+ -0.012937892408322462
+ ],
+ "scale": [
+ 1,
+ 1,
+ 1
+ ],
+ "visible": true,
+ "customComponents": []
+ },
+ "model-imported-model_mto6v5w6_09-fox-18": {
+ "name": "b_LeftLeg01_015",
+ "nodeName": "b_LeftLeg01_015",
+ "nodePath": "0/0/0/0/0/2",
+ "interactionId": "interaction-643ea143",
+ "capabilities": {
+ "selectable": true,
+ "transform": true,
+ "visible": true,
+ "material": false,
+ "animatable": true
+ },
+ "position": [
+ 4.813770294189453,
+ 5.154018402099609,
+ -6.968006134033203
+ ],
+ "rotation": [
+ 0.1223493637902729,
+ -0.09724770930295147,
+ 2.9630295165414986
+ ],
+ "scale": [
+ 1,
+ 1,
+ 1
+ ],
+ "visible": true,
+ "customComponents": []
+ },
+ "model-imported-model_mto6v5w6_09-fox-19": {
+ "name": "b_LeftLeg02_016",
+ "nodeName": "b_LeftLeg02_016",
+ "nodePath": "0/0/0/0/0/2/0",
+ "interactionId": "interaction-7175f8dc",
+ "capabilities": {
+ "selectable": true,
+ "transform": true,
+ "visible": true,
+ "material": false,
+ "animatable": true
+ },
+ "position": [
+ 18.944175720214844,
+ 0,
+ 0
+ ],
+ "rotation": [
+ 1.2545483692289454e-8,
+ 3.728483680761731e-8,
+ -0.522293355530293
+ ],
+ "scale": [
+ 1,
+ 1,
+ 1
+ ],
+ "visible": true,
+ "customComponents": []
+ },
+ "model-imported-model_mto6v5w6_09-fox-20": {
+ "name": "b_LeftFoot01_017",
+ "nodeName": "b_LeftFoot01_017",
+ "nodePath": "0/0/0/0/0/2/0/0",
+ "interactionId": "interaction-f5ed353b",
+ "capabilities": {
+ "selectable": true,
+ "transform": true,
+ "visible": true,
+ "material": false,
+ "animatable": true
+ },
+ "position": [
+ 17.942811965942383,
+ 0,
+ 0
+ ],
+ "rotation": [
+ -0.03128474386729028,
+ -0.15471927051895154,
+ 0.7774909055508243
+ ],
+ "scale": [
+ 1,
+ 1,
+ 1
+ ],
+ "visible": true,
+ "customComponents": []
+ },
+ "model-imported-model_mto6v5w6_09-fox-21": {
+ "name": "b_LeftFoot02_018",
+ "nodeName": "b_LeftFoot02_018",
+ "nodePath": "0/0/0/0/0/2/0/0/0",
+ "interactionId": "interaction-1bbe01a4",
+ "capabilities": {
+ "selectable": true,
+ "transform": true,
+ "visible": true,
+ "material": false,
+ "animatable": true
+ },
+ "position": [
+ 15.779938697814941,
+ 0,
+ 0
+ ],
+ "rotation": [
+ 0,
+ 0,
+ 1.158241570830627
+ ],
+ "scale": [
+ 1,
+ 1,
+ 1
+ ],
+ "visible": true,
+ "customComponents": []
+ },
+ "model-imported-model_mto6v5w6_09-fox-22": {
+ "name": "b_RightLeg01_019",
+ "nodeName": "b_RightLeg01_019",
+ "nodePath": "0/0/0/0/0/3",
+ "interactionId": "interaction-633e9fb0",
+ "capabilities": {
+ "selectable": true,
+ "transform": true,
+ "visible": true,
+ "material": false,
+ "animatable": true
+ },
+ "position": [
+ 4.813777923583984,
+ 5.154026031494141,
+ 6.967563629150391
+ ],
+ "rotation": [
+ -0.10137208185544387,
+ 0.05534659522395753,
+ -2.3613690068554236
+ ],
+ "scale": [
+ 1,
+ 1,
+ 1
+ ],
+ "visible": true,
+ "customComponents": []
+ },
+ "model-imported-model_mto6v5w6_09-fox-23": {
+ "name": "b_RightLeg02_020",
+ "nodeName": "b_RightLeg02_020",
+ "nodePath": "0/0/0/0/0/3/0",
+ "interactionId": "interaction-95ff89c7",
+ "capabilities": {
+ "selectable": true,
+ "transform": true,
+ "visible": true,
+ "material": false,
+ "animatable": true
+ },
+ "position": [
+ 18.944183349609375,
+ 0,
+ 0
+ ],
+ "rotation": [
+ 2.7890192431350882e-8,
+ 1.4750479199499402e-8,
+ -1.0710323806538369
+ ],
+ "scale": [
+ 1,
+ 1,
+ 1
+ ],
+ "visible": true,
+ "customComponents": []
+ },
+ "model-imported-model_mto6v5w6_09-fox-24": {
+ "name": "b_RightFoot01_021",
+ "nodeName": "b_RightFoot01_021",
+ "nodePath": "0/0/0/0/0/3/0/0",
+ "interactionId": "interaction-a157d498",
+ "capabilities": {
+ "selectable": true,
+ "transform": true,
+ "visible": true,
+ "material": false,
+ "animatable": true
+ },
+ "position": [
+ 17.94281005859375,
+ 0,
+ 0
+ ],
+ "rotation": [
+ -0.14401413968163027,
+ 0.2814735368347316,
+ 1.4591240233677443
+ ],
+ "scale": [
+ 1,
+ 1,
+ 1
+ ],
+ "visible": true,
+ "customComponents": []
+ },
+ "model-imported-model_mto6v5w6_09-fox-25": {
+ "name": "b_RightFoot02_022",
+ "nodeName": "b_RightFoot02_022",
+ "nodePath": "0/0/0/0/0/3/0/0/0",
+ "interactionId": "interaction-ebd38e3f",
+ "capabilities": {
+ "selectable": true,
+ "transform": true,
+ "visible": true,
+ "material": false,
+ "animatable": true
+ },
+ "position": [
+ 15.779935836791992,
+ 0,
+ 0
+ ],
+ "rotation": [
+ 0,
+ 0,
+ 1.158241570830627
+ ],
+ "scale": [
+ 1,
+ 1,
+ 1
+ ],
+ "visible": true,
+ "customComponents": []
+ },
+ "model-imported-model_mto6v5w6_09-fox-26": {
+ "name": "fox",
+ "nodeName": "fox",
+ "nodePath": "0/1",
+ "interactionId": "interaction-a3d35c26",
+ "capabilities": {
+ "selectable": true,
+ "transform": true,
+ "visible": true,
+ "material": true,
+ "animatable": true
+ },
+ "position": [
+ 0.25,
+ 0,
+ 0
+ ],
+ "rotation": [
+ 0,
+ 0,
+ 0
+ ],
+ "scale": [
+ 1,
+ 1,
+ 1
+ ],
+ "visible": true,
+ "customComponents": [],
+ "materials": [
+ {
+ "color": "ffffff",
+ "emissive": "000000",
+ "emissiveIntensity": 1,
+ "metalness": 0,
+ "roughness": 0.3,
+ "opacity": 1,
+ "transparent": false,
+ "depthWrite": true
+ }
+ ]
+ }
+ },
+ "materialPreset": null,
+ "modelPartTargets": [
+ {
+ "sceneId": "interaction-a3d35c26",
+ "interactionId": "interaction-a3d35c26",
+ "sourceInteractionId": "interaction-a3d35c26",
+ "editorId": "model-imported-model_mto6v5w6_09-fox-26",
+ "nodePath": "0/1",
+ "sourceNodeName": "fox",
+ "name": "fox",
+ "operable": true,
+ "capabilities": [
+ "select",
+ "inspect"
+ ],
+ "visible": true,
+ "sceneDeleted": false
+ }
+ ],
+ "partOverrides": {
+ "interaction-a3d35c26": {
+ "sceneId": "interaction-a3d35c26",
+ "interactionId": "interaction-a3d35c26",
+ "sourceInteractionId": "interaction-a3d35c26",
+ "editorId": "model-imported-model_mto6v5w6_09-fox-26",
+ "nodePath": "0/1",
+ "sourceNodeName": "fox",
+ "name": "fox",
+ "position": [
+ 0.25,
+ 0,
+ 0
+ ],
+ "rotation": [
+ 0,
+ 0,
+ 0
+ ],
+ "scale": [
+ 1,
+ 1,
+ 1
+ ],
+ "visible": true,
+ "sceneDeleted": false,
+ "operable": true,
+ "capabilities": [
+ "select",
+ "inspect"
+ ]
+ }
+ },
+ "interactionId": "scene:OBJ-1788605916565-6",
+ "operable": true,
+ "capabilities": [
+ "select",
+ "inspect",
+ "transform",
+ "measure"
+ ],
+ "sourceNodeName": "Group",
+ "splitGroupId": "",
+ "visible": true,
+ "position": [
+ -6.3,
+ 0.25,
+ 1.2
+ ],
+ "rotation": [
+ 0.09599310885968812,
+ -0.4276056667386107,
+ 0.05585053606381855
+ ],
+ "scale": [
+ 0.036,
+ 0.04,
+ 0.038
+ ],
+ "assetKey": "",
+ "resourceUrl": "content://asset/model/157/201/2030",
+ "sourceName": "09-Fox.glb",
+ "sizeBytes": 162852,
+ "mimeType": "model/gltf-binary",
+ "enclosureMode": "",
+ "assetCode": "MODEL_MTO6V5W6_09-FOX",
+ "sourceAssetId": "2030",
+ "targetProjectId": "157",
+ "targetVersionId": "201"
+ },
+ "cover": {
+ "id": "3222",
+ "storageUri": "fsvc://unreal-tran/image/2026/09/06/288a231e0c536022ac65174accd3feaf.jpg",
+ "sizeBytes": 43691
+ },
+ "reopened": {
+ "mounted": true,
+ "hasEnvironment": true,
+ "hasEnvironmentTarget": true,
+ "toneMappingExposure": 1.05,
+ "background": "a8cbe1",
+ "meshCount": 15,
+ "materials": [
+ {
+ "name": "(未命名)",
+ "color": "445e2d",
+ "metalness": 0.35,
+ "roughness": 0.55,
+ "envMapIntensity": 1,
+ "hasMap": false,
+ "preset": null,
+ "rule": null,
+ "authored": null
+ },
+ {
+ "name": "(未命名)",
+ "color": "182c36",
+ "metalness": 0.55,
+ "roughness": 0.34,
+ "envMapIntensity": 1,
+ "hasMap": false,
+ "preset": null,
+ "rule": null,
+ "authored": null
+ },
+ {
+ "name": "(未命名)",
+ "color": "1e4f69",
+ "metalness": 0.45,
+ "roughness": 0.42,
+ "envMapIntensity": 1,
+ "hasMap": false,
+ "preset": null,
+ "rule": null,
+ "authored": null
+ },
+ {
+ "name": "(未命名)",
+ "color": "36b37e",
+ "metalness": 0,
+ "roughness": 1,
+ "envMapIntensity": 1,
+ "hasMap": false,
+ "preset": null,
+ "rule": null,
+ "authored": null
+ },
+ {
+ "name": "(未命名)",
+ "color": "182c36",
+ "metalness": 0.55,
+ "roughness": 0.34,
+ "envMapIntensity": 1,
+ "hasMap": false,
+ "preset": null,
+ "rule": null,
+ "authored": null
+ },
+ {
+ "name": "(未命名)",
+ "color": "e4a62b",
+ "metalness": 0.25,
+ "roughness": 0.48,
+ "envMapIntensity": 1,
+ "hasMap": false,
+ "preset": null,
+ "rule": null,
+ "authored": null
+ },
+ {
+ "name": "fox_material",
+ "color": "ffffff",
+ "metalness": 0,
+ "roughness": 0.3,
+ "envMapIntensity": 1,
+ "hasMap": true,
+ "preset": null,
+ "rule": null,
+ "authored": null
+ },
+ {
+ "name": "M_3dfreehub_mat_2",
+ "color": "ffffff",
+ "metalness": 0,
+ "roughness": 0.5,
+ "envMapIntensity": 1,
+ "hasMap": true,
+ "preset": null,
+ "rule": null,
+ "authored": null
+ },
+ {
+ "name": "M_3dfreehub_mat_3",
+ "color": "ffffff",
+ "metalness": 0,
+ "roughness": 0.5,
+ "envMapIntensity": 1,
+ "hasMap": true,
+ "preset": null,
+ "rule": null,
+ "authored": null
+ },
+ {
+ "name": "M_3dfreehub_mat_4",
+ "color": "ffffff",
+ "metalness": 0,
+ "roughness": 0.5,
+ "envMapIntensity": 1,
+ "hasMap": true,
+ "preset": null,
+ "rule": null,
+ "authored": null
+ },
+ {
+ "name": "M_3dfreehub_mat_5",
+ "color": "ffffff",
+ "metalness": 0,
+ "roughness": 0.5,
+ "envMapIntensity": 1,
+ "hasMap": true,
+ "preset": null,
+ "rule": null,
+ "authored": null
+ },
+ {
+ "name": "Material__85",
+ "color": "695c5b",
+ "metalness": 0,
+ "roughness": 0.5,
+ "envMapIntensity": 1,
+ "hasMap": false,
+ "preset": null,
+ "rule": null,
+ "authored": null
+ }
+ ],
+ "objectCount": 5,
+ "contentBounds": {
+ "min": [
+ -41,
+ -5.239,
+ -34.028
+ ],
+ "max": [
+ 41,
+ 11.358,
+ 34.028
+ ],
+ "size": [
+ 82,
+ 16.596,
+ 68.057
+ ]
+ },
+ "importedObjects": [
+ {
+ "name": "闭环验收狐狸目标",
+ "modelEditorModelId": "imported-model_mto6v5w6_09-fox",
+ "modelEditorAssetId": "content-model-157-201",
+ "deviceId": "",
+ "modelId": "",
+ "assetCode": "MODEL_MTO6V5W6_09-FOX",
+ "targetProjectId": "157",
+ "targetVersionId": "201"
+ }
+ ],
+ "apiControlled": true,
+ "readOnly": false,
+ "ready": true,
+ "readySettled": true,
+ "dirty": false,
+ "saving": false,
+ "publishing": false,
+ "pendingLoads": 0,
+ "paused": false,
+ "previewing": false,
+ "currentPreset": {
+ "id": "workshop-real",
+ "name": "演示维修车间(可拆分)",
+ "type": "车间"
+ },
+ "cameraPosition": [
+ -4.276260993586593,
+ 3.4391140607904136,
+ 7.517683946469992
+ ],
+ "cameraTarget": [
+ -7.512232860974557,
+ 1.4059499759307126,
+ -2.2204460492503126e-16
+ ],
+ "gridVisible": true,
+ "transformMode": "translate",
+ "selectedSceneId": "",
+ "canUndo": false,
+ "canRedo": false,
+ "objects": [
+ {
+ "sceneId": "OBJ-1788605873329-2",
+ "type": "equipment",
+ "name": "维修装备主体 02",
+ "visible": true,
+ "position": [
+ 0,
+ 0,
+ 0
+ ],
+ "rotation": [
+ 0,
+ 0,
+ 0
+ ],
+ "scale": [
+ 1,
+ 1,
+ 1
+ ],
+ "deviceId": "EQ-002",
+ "modelId": "MODEL-EQUIPMENT-02",
+ "semantic": "实训装备",
+ "operable": true,
+ "capabilities": [
+ "select",
+ "inspect",
+ "transform"
+ ],
+ "assetCode": "",
+ "sourceAssetId": "",
+ "targetProjectId": "",
+ "targetVersionId": "",
+ "modelEditorAssetId": "",
+ "parts": []
+ },
+ {
+ "sceneId": "OBJ-1788605873331-3",
+ "type": "safety-zone",
+ "name": "安全作业区域 03",
+ "visible": true,
+ "position": [
+ 0,
+ 0.02,
+ 0
+ ],
+ "rotation": [
+ 0,
+ 0,
+ 0
+ ],
+ "scale": [
+ 1,
+ 1,
+ 1
+ ],
+ "deviceId": "EQ-003",
+ "modelId": "MODEL-SAFETY-ZONE-03",
+ "semantic": "安全区域",
+ "operable": false,
+ "capabilities": [
+ "select"
+ ],
+ "assetCode": "",
+ "sourceAssetId": "",
+ "targetProjectId": "",
+ "targetVersionId": "",
+ "modelEditorAssetId": "",
+ "parts": []
+ },
+ {
+ "sceneId": "OBJ-1788605873333-4",
+ "type": "light",
+ "name": "区域照明 04",
+ "visible": true,
+ "position": [
+ -12,
+ 0,
+ 8
+ ],
+ "rotation": [
+ 0,
+ 0,
+ 0
+ ],
+ "scale": [
+ 1,
+ 1,
+ 1
+ ],
+ "deviceId": "EQ-004",
+ "modelId": "MODEL-LIGHT-04",
+ "semantic": "实训装备",
+ "operable": false,
+ "capabilities": [
+ "select"
+ ],
+ "assetCode": "",
+ "sourceAssetId": "",
+ "targetProjectId": "",
+ "targetVersionId": "",
+ "modelEditorAssetId": "",
+ "parts": []
+ },
+ {
+ "sceneId": "OBJ-1788605916565-6",
+ "type": "imported",
+ "name": "闭环验收狐狸目标",
+ "visible": true,
+ "position": [
+ -6.3,
+ 0.25,
+ 1.2
+ ],
+ "rotation": [
+ 0.09599310885968812,
+ -0.4276056667386107,
+ 0.05585053606381855
+ ],
+ "scale": [
+ 0.036,
+ 0.04,
+ 0.038
+ ],
+ "deviceId": "",
+ "modelId": "",
+ "semantic": "训练人员",
+ "operable": true,
+ "capabilities": [
+ "select",
+ "inspect",
+ "transform",
+ "measure"
+ ],
+ "assetCode": "MODEL_MTO6V5W6_09-FOX",
+ "sourceAssetId": "2030",
+ "targetProjectId": "157",
+ "targetVersionId": "201",
+ "modelEditorAssetId": "content-model-157-201",
+ "parts": [
+ {
+ "sceneId": "interaction-a3d35c26",
+ "name": "fox",
+ "visible": true,
+ "sceneDeleted": false,
+ "position": [
+ 0.25,
+ 0,
+ 0
+ ],
+ "rotation": [
+ 0,
+ 0,
+ 0
+ ],
+ "scale": [
+ 1,
+ 1,
+ 1
+ ],
+ "capabilities": [
+ "select",
+ "inspect"
+ ]
+ }
+ ]
+ },
+ {
+ "sceneId": "OBJ-1788605874053-5",
+ "type": "imported",
+ "name": "地形",
+ "visible": true,
+ "position": [
+ 3.069303533692093,
+ 4.802826448856971,
+ -3.074293867354786
+ ],
+ "rotation": [
+ 0,
+ 0,
+ 0
+ ],
+ "scale": [
+ 1.0050406774865042,
+ 1.0050406774865042,
+ 1.0050406774865042
+ ],
+ "deviceId": "EQ-005",
+ "modelId": "MODEL-IMPORT-05",
+ "semantic": "环境设施",
+ "operable": false,
+ "capabilities": [
+ "select"
+ ],
+ "assetCode": "",
+ "sourceAssetId": "",
+ "targetProjectId": "",
+ "targetVersionId": "",
+ "modelEditorAssetId": "",
+ "parts": []
+ }
+ ]
+ }
+ }
+}
\ No newline at end of file
diff --git a/reports/scene-editor-closure-20260906/evidence/json-glb-roundtrip.json b/reports/scene-editor-closure-20260906/evidence/json-glb-roundtrip.json
new file mode 100644
index 0000000..c3307b4
--- /dev/null
+++ b/reports/scene-editor-closure-20260906/evidence/json-glb-roundtrip.json
@@ -0,0 +1,1437 @@
+{
+ "time": "2026-09-06T10:26:19.703Z",
+ "data": {
+ "kind": "browser",
+ "status": "PASS",
+ "testTitle": "工程 JSON 与小 GLB:真实上传、损坏拒绝、引用失败回滚、相机参数往返",
+ "featureIds": [
+ "json.export",
+ "json.import",
+ "json.invalid",
+ "json.missing-resource",
+ "json.rendering",
+ "glb.import",
+ "glb.invalid",
+ "glb.references",
+ "validation.references"
+ ],
+ "scope": "真实JSON/GLB文件输入、错误schema/损坏资源/上传503拒绝、错误assetCode回滚、稳定URI及渲染参数保存重开。",
+ "projectId": "383",
+ "initial": {
+ "mounted": true,
+ "hasEnvironment": true,
+ "hasEnvironmentTarget": true,
+ "toneMappingExposure": 1.05,
+ "background": "b9d5e6",
+ "meshCount": 37,
+ "materials": [
+ {
+ "name": "BaiSeNeiQiang",
+ "color": "eaeaea",
+ "metalness": 0,
+ "roughness": 0.5,
+ "envMapIntensity": 1,
+ "hasMap": false,
+ "preset": null,
+ "rule": null,
+ "authored": null
+ },
+ {
+ "name": "BoLi",
+ "color": "87c8fb",
+ "metalness": 0,
+ "roughness": 0.5,
+ "envMapIntensity": 1,
+ "hasMap": false,
+ "preset": null,
+ "rule": null,
+ "authored": null
+ },
+ {
+ "name": "HuiSe3",
+ "color": "8a8a8a",
+ "metalness": 0.43,
+ "roughness": 0.4,
+ "envMapIntensity": 1,
+ "hasMap": false,
+ "preset": null,
+ "rule": null,
+ "authored": null
+ },
+ {
+ "name": "HuiSe4",
+ "color": "c0c0c0",
+ "metalness": 0,
+ "roughness": 0.5,
+ "envMapIntensity": 1,
+ "hasMap": false,
+ "preset": null,
+ "rule": null,
+ "authored": null
+ },
+ {
+ "name": "JinShu",
+ "color": "555555",
+ "metalness": 0.44,
+ "roughness": 0.5,
+ "envMapIntensity": 1,
+ "hasMap": false,
+ "preset": null,
+ "rule": null,
+ "authored": null
+ },
+ {
+ "name": "MenPai",
+ "color": "ffffff",
+ "metalness": 0.68,
+ "roughness": 0.5,
+ "envMapIntensity": 1,
+ "hasMap": true,
+ "preset": null,
+ "rule": null,
+ "authored": null
+ },
+ {
+ "name": "WaiQiang",
+ "color": "efefef",
+ "metalness": 0,
+ "roughness": 0.5,
+ "envMapIntensity": 1,
+ "hasMap": true,
+ "preset": null,
+ "rule": null,
+ "authored": null
+ },
+ {
+ "name": "baiSe",
+ "color": "eaeaea",
+ "metalness": 0,
+ "roughness": 0.5,
+ "envMapIntensity": 1,
+ "hasMap": false,
+ "preset": null,
+ "rule": null,
+ "authored": null
+ },
+ {
+ "name": "HZhuan",
+ "color": "ffffff",
+ "metalness": 0,
+ "roughness": 0.5,
+ "envMapIntensity": 1,
+ "hasMap": true,
+ "preset": null,
+ "rule": null,
+ "authored": null
+ },
+ {
+ "name": "DiMian",
+ "color": "d2d2d2",
+ "metalness": 0.6,
+ "roughness": 0.2,
+ "envMapIntensity": 1,
+ "hasMap": true,
+ "preset": null,
+ "rule": null,
+ "authored": null
+ },
+ {
+ "name": "HuangSe",
+ "color": "d0af42",
+ "metalness": 0,
+ "roughness": 0.5,
+ "envMapIntensity": 1,
+ "hasMap": false,
+ "preset": null,
+ "rule": null,
+ "authored": null
+ },
+ {
+ "name": "HuangSe2",
+ "color": "b29b87",
+ "metalness": 0,
+ "roughness": 0.5,
+ "envMapIntensity": 1,
+ "hasMap": false,
+ "preset": null,
+ "rule": null,
+ "authored": null
+ },
+ {
+ "name": "HuangSe3",
+ "color": "aea791",
+ "metalness": 0,
+ "roughness": 0.5,
+ "envMapIntensity": 1,
+ "hasMap": false,
+ "preset": null,
+ "rule": null,
+ "authored": null
+ },
+ {
+ "name": "HuiSe",
+ "color": "555555",
+ "metalness": 0.82,
+ "roughness": 0.15,
+ "envMapIntensity": 1,
+ "hasMap": false,
+ "preset": null,
+ "rule": null,
+ "authored": null
+ },
+ {
+ "name": "HuiSe2",
+ "color": "9a9ca4",
+ "metalness": 0,
+ "roughness": 1,
+ "envMapIntensity": 1,
+ "hasMap": false,
+ "preset": null,
+ "rule": null,
+ "authored": null
+ },
+ {
+ "name": "NeiQiangBaiSe",
+ "color": "bfbfbf",
+ "metalness": 0,
+ "roughness": 0.5,
+ "envMapIntensity": 1,
+ "hasMap": false,
+ "preset": null,
+ "rule": null,
+ "authored": null
+ },
+ {
+ "name": "adskMatHuiSe",
+ "color": "797979",
+ "metalness": 0.43,
+ "roughness": 0.4,
+ "envMapIntensity": 1,
+ "hasMap": false,
+ "preset": null,
+ "rule": null,
+ "authored": null
+ },
+ {
+ "name": "truck",
+ "color": "ffffff",
+ "metalness": 0,
+ "roughness": 0.3,
+ "envMapIntensity": 1,
+ "hasMap": true,
+ "preset": null,
+ "rule": null,
+ "authored": null
+ },
+ {
+ "name": "glass",
+ "color": "003928",
+ "metalness": 0,
+ "roughness": 1,
+ "envMapIntensity": 1,
+ "hasMap": false,
+ "preset": null,
+ "rule": null,
+ "authored": null
+ },
+ {
+ "name": "window_trim",
+ "color": "484848",
+ "metalness": 0,
+ "roughness": 1,
+ "envMapIntensity": 1,
+ "hasMap": false,
+ "preset": null,
+ "rule": null,
+ "authored": null
+ },
+ {
+ "name": "wheels",
+ "color": "ffffff",
+ "metalness": 0,
+ "roughness": 1,
+ "envMapIntensity": 1,
+ "hasMap": true,
+ "preset": null,
+ "rule": null,
+ "authored": null
+ }
+ ],
+ "objectCount": 2,
+ "contentBounds": {
+ "min": [
+ -30,
+ 0,
+ -17.156
+ ],
+ "max": [
+ 30,
+ 14.579,
+ 17.156
+ ],
+ "size": [
+ 60,
+ 14.579,
+ 34.311
+ ]
+ },
+ "importedObjects": [
+ {
+ "name": "牛奶卡车检验主装备",
+ "modelEditorModelId": "imported-model_mto6wi6k_08-cesiummilktruck",
+ "modelEditorAssetId": "content-model-156-204",
+ "deviceId": "SCENE-TRUCK-001",
+ "modelId": "MODEL-IMPORT-02",
+ "assetCode": "MODEL_MTO6WI6K_08-CESIUMMILKTRUCK",
+ "targetProjectId": "156",
+ "targetVersionId": "204"
+ }
+ ],
+ "apiControlled": true,
+ "readOnly": false,
+ "ready": true,
+ "readySettled": true,
+ "dirty": false,
+ "saving": false,
+ "publishing": false,
+ "pendingLoads": 0,
+ "paused": false,
+ "previewing": false,
+ "currentPreset": {
+ "id": "depot-1",
+ "name": "真实装备车库",
+ "type": "车间"
+ },
+ "cameraPosition": [
+ 10.565439077211714,
+ 5.0456876852867705,
+ 7.37050840519251
+ ],
+ "cameraTarget": [
+ 4.2,
+ 1.695456592017448,
+ 0
+ ],
+ "gridVisible": true,
+ "transformMode": "translate",
+ "selectedSceneId": "",
+ "canUndo": false,
+ "canRedo": false,
+ "objects": [
+ {
+ "sceneId": "OBJ-1788605149879-1",
+ "type": "imported",
+ "name": "车库",
+ "visible": true,
+ "position": [
+ 0,
+ 0.01461010548216679,
+ -4.4135855858939976e-8
+ ],
+ "rotation": [
+ 0,
+ 0,
+ 0
+ ],
+ "scale": [
+ 1.009710078119415,
+ 1.009710078119415,
+ 1.009710078119415
+ ],
+ "deviceId": "EQ-001",
+ "modelId": "MODEL-IMPORT-01",
+ "semantic": "环境设施",
+ "operable": false,
+ "capabilities": [
+ "select"
+ ],
+ "assetCode": "",
+ "sourceAssetId": "",
+ "targetProjectId": "",
+ "targetVersionId": "",
+ "modelEditorAssetId": "",
+ "parts": []
+ },
+ {
+ "sceneId": "OBJ-1788605199171-2",
+ "type": "imported",
+ "name": "牛奶卡车检验主装备",
+ "visible": true,
+ "position": [
+ 4.2,
+ 0.20617168998977797,
+ -0.4356763541543951
+ ],
+ "rotation": [
+ 0,
+ 0,
+ 0
+ ],
+ "scale": [
+ 1.2323086149245381,
+ 1.2323086149245381,
+ 1.2323086149245381
+ ],
+ "deviceId": "SCENE-TRUCK-001",
+ "modelId": "MODEL-IMPORT-02",
+ "semantic": "实训装备",
+ "operable": true,
+ "capabilities": [
+ "select",
+ "inspect",
+ "transform"
+ ],
+ "assetCode": "MODEL_MTO6WI6K_08-CESIUMMILKTRUCK",
+ "sourceAssetId": "2069",
+ "targetProjectId": "156",
+ "targetVersionId": "204",
+ "modelEditorAssetId": "content-model-156-204",
+ "parts": [
+ {
+ "sceneId": "interaction-1ba581e6",
+ "name": "Yup2Zup",
+ "visible": true,
+ "sceneDeleted": false,
+ "position": [
+ 0,
+ 0,
+ 0
+ ],
+ "rotation": [
+ 1.57079626719025,
+ 0,
+ 1.5707963267948966
+ ],
+ "scale": [
+ 1,
+ 1,
+ 1
+ ],
+ "capabilities": [
+ "select",
+ "inspect",
+ "transform",
+ "animate"
+ ]
+ },
+ {
+ "sceneId": "interaction-004dec41",
+ "name": "Cesium_Milk_Truck",
+ "visible": true,
+ "sceneDeleted": false,
+ "position": [
+ 0.35,
+ 0,
+ 0
+ ],
+ "rotation": [
+ 0,
+ 0,
+ 0
+ ],
+ "scale": [
+ 1,
+ 1,
+ 1
+ ],
+ "capabilities": [
+ "select",
+ "inspect",
+ "transform",
+ "animate"
+ ]
+ },
+ {
+ "sceneId": "interaction-5f7e4bee",
+ "name": "Cesium_Milk_Truck_1",
+ "visible": true,
+ "sceneDeleted": false,
+ "position": [
+ 0,
+ 0,
+ 0
+ ],
+ "rotation": [
+ 0,
+ 0,
+ 0
+ ],
+ "scale": [
+ 1,
+ 1,
+ 1
+ ],
+ "capabilities": [
+ "select",
+ "inspect",
+ "transform",
+ "animate"
+ ]
+ },
+ {
+ "sceneId": "interaction-607e4d81",
+ "name": "Cesium_Milk_Truck_2",
+ "visible": true,
+ "sceneDeleted": false,
+ "position": [
+ 0,
+ 0,
+ 0
+ ],
+ "rotation": [
+ 0,
+ 0,
+ 0
+ ],
+ "scale": [
+ 1,
+ 1,
+ 1
+ ],
+ "capabilities": [
+ "select",
+ "inspect",
+ "transform",
+ "animate"
+ ]
+ },
+ {
+ "sceneId": "interaction-5d7e48c8",
+ "name": "Cesium_Milk_Truck_3",
+ "visible": true,
+ "sceneDeleted": false,
+ "position": [
+ 0,
+ 0,
+ 0
+ ],
+ "rotation": [
+ 0,
+ 0,
+ 0
+ ],
+ "scale": [
+ 1,
+ 1,
+ 1
+ ],
+ "capabilities": [
+ "select",
+ "inspect",
+ "transform",
+ "animate"
+ ]
+ },
+ {
+ "sceneId": "interaction-5e7e4a5b",
+ "name": "Node",
+ "visible": true,
+ "sceneDeleted": false,
+ "position": [
+ 1.432669997215271,
+ 0,
+ -0.4277220070362091
+ ],
+ "rotation": [
+ 0,
+ 0,
+ 0
+ ],
+ "scale": [
+ 1,
+ 1,
+ 1
+ ],
+ "capabilities": [
+ "select",
+ "inspect",
+ "transform",
+ "animate"
+ ]
+ },
+ {
+ "sceneId": "interaction-ce2feac4",
+ "name": "Wheels",
+ "visible": true,
+ "sceneDeleted": false,
+ "position": [
+ 0,
+ 0,
+ 0
+ ],
+ "rotation": [
+ -3.141592653589793,
+ -0.603571861900947,
+ -3.141592653589793
+ ],
+ "scale": [
+ 1,
+ 1,
+ 1
+ ],
+ "capabilities": [
+ "select",
+ "inspect",
+ "transform",
+ "animate"
+ ]
+ },
+ {
+ "sceneId": "interaction-637e523a",
+ "name": "Node001",
+ "visible": true,
+ "sceneDeleted": false,
+ "position": [
+ -1.352329969406128,
+ 0,
+ -0.4277220070362091
+ ],
+ "rotation": [
+ 0,
+ 0,
+ 0
+ ],
+ "scale": [
+ 1,
+ 1,
+ 1
+ ],
+ "capabilities": [
+ "select",
+ "inspect",
+ "transform",
+ "animate"
+ ]
+ },
+ {
+ "sceneId": "interaction-f417f82d",
+ "name": "Wheels001",
+ "visible": true,
+ "sceneDeleted": false,
+ "position": [
+ 0,
+ 0,
+ 0
+ ],
+ "rotation": [
+ -3.141592653589793,
+ -0.603571861900947,
+ -3.141592653589793
+ ],
+ "scale": [
+ 1,
+ 1,
+ 1
+ ],
+ "capabilities": [
+ "select",
+ "inspect",
+ "transform",
+ "animate"
+ ]
+ }
+ ]
+ }
+ ]
+ },
+ "added": {
+ "sceneId": "OBJ-1788690372805-4",
+ "type": "imported",
+ "name": "01-Box",
+ "visible": true,
+ "position": [
+ 0,
+ 2.5,
+ 0
+ ],
+ "rotation": [
+ 0,
+ 0,
+ 0
+ ],
+ "scale": [
+ 4.999999999999999,
+ 4.999999999999999,
+ 4.999999999999999
+ ],
+ "deviceId": "EQ-004",
+ "modelId": "MODEL-IMPORT-04",
+ "semantic": "实训装备",
+ "operable": true,
+ "capabilities": [
+ "select",
+ "inspect",
+ "transform"
+ ],
+ "assetCode": "SCENE-GLB-MTPO2C8Q-1",
+ "sourceAssetId": "",
+ "targetProjectId": "",
+ "targetVersionId": "",
+ "modelEditorAssetId": "",
+ "parts": []
+ },
+ "savedObject": {
+ "sceneId": "OBJ-1788690372805-4",
+ "type": "imported",
+ "name": "01-Box",
+ "deviceId": "EQ-004",
+ "modelId": "MODEL-IMPORT-04",
+ "semantic": "实训装备",
+ "assetRole": "",
+ "builtinAssetId": "",
+ "modelEditorAssetId": "",
+ "modelEditorModelId": "",
+ "modelEditorRevision": 0,
+ "modelOverrides": {},
+ "materialPreset": null,
+ "modelPartTargets": [],
+ "partOverrides": {},
+ "interactionId": "scene:OBJ-1788690372805-4",
+ "operable": true,
+ "capabilities": [
+ "select",
+ "inspect",
+ "transform"
+ ],
+ "sourceNodeName": "",
+ "splitGroupId": "",
+ "visible": true,
+ "position": [
+ 0,
+ 2.5,
+ 0
+ ],
+ "rotation": [
+ 0,
+ 0,
+ 0
+ ],
+ "scale": [
+ 4.999999999999999,
+ 4.999999999999999,
+ 4.999999999999999
+ ],
+ "assetKey": "",
+ "resourceUrl": "content://asset/scene/383/SCENE-GLB-MTPO2C8Q-1",
+ "sourceName": "01-Box.glb",
+ "sizeBytes": 1664,
+ "mimeType": "model/gltf-binary",
+ "enclosureMode": "",
+ "assetCode": "SCENE-GLB-MTPO2C8Q-1",
+ "sourceAssetId": ""
+ },
+ "reopened": {
+ "mounted": true,
+ "hasEnvironment": true,
+ "hasEnvironmentTarget": true,
+ "toneMappingExposure": 0.9,
+ "background": "b9d5e6",
+ "meshCount": 38,
+ "materials": [
+ {
+ "name": "BaiSeNeiQiang",
+ "color": "eaeaea",
+ "metalness": 0,
+ "roughness": 0.5,
+ "envMapIntensity": 1,
+ "hasMap": false,
+ "preset": null,
+ "rule": null,
+ "authored": null
+ },
+ {
+ "name": "BoLi",
+ "color": "87c8fb",
+ "metalness": 0,
+ "roughness": 0.5,
+ "envMapIntensity": 1,
+ "hasMap": false,
+ "preset": null,
+ "rule": null,
+ "authored": null
+ },
+ {
+ "name": "HuiSe3",
+ "color": "8a8a8a",
+ "metalness": 0.43,
+ "roughness": 0.4,
+ "envMapIntensity": 1,
+ "hasMap": false,
+ "preset": null,
+ "rule": null,
+ "authored": null
+ },
+ {
+ "name": "HuiSe4",
+ "color": "c0c0c0",
+ "metalness": 0,
+ "roughness": 0.5,
+ "envMapIntensity": 1,
+ "hasMap": false,
+ "preset": null,
+ "rule": null,
+ "authored": null
+ },
+ {
+ "name": "JinShu",
+ "color": "555555",
+ "metalness": 0.44,
+ "roughness": 0.5,
+ "envMapIntensity": 1,
+ "hasMap": false,
+ "preset": null,
+ "rule": null,
+ "authored": null
+ },
+ {
+ "name": "MenPai",
+ "color": "ffffff",
+ "metalness": 0.68,
+ "roughness": 0.5,
+ "envMapIntensity": 1,
+ "hasMap": true,
+ "preset": null,
+ "rule": null,
+ "authored": null
+ },
+ {
+ "name": "WaiQiang",
+ "color": "efefef",
+ "metalness": 0,
+ "roughness": 0.5,
+ "envMapIntensity": 1,
+ "hasMap": true,
+ "preset": null,
+ "rule": null,
+ "authored": null
+ },
+ {
+ "name": "baiSe",
+ "color": "eaeaea",
+ "metalness": 0,
+ "roughness": 0.5,
+ "envMapIntensity": 1,
+ "hasMap": false,
+ "preset": null,
+ "rule": null,
+ "authored": null
+ },
+ {
+ "name": "HZhuan",
+ "color": "ffffff",
+ "metalness": 0,
+ "roughness": 0.5,
+ "envMapIntensity": 1,
+ "hasMap": true,
+ "preset": null,
+ "rule": null,
+ "authored": null
+ },
+ {
+ "name": "DiMian",
+ "color": "d2d2d2",
+ "metalness": 0.6,
+ "roughness": 0.2,
+ "envMapIntensity": 1,
+ "hasMap": true,
+ "preset": null,
+ "rule": null,
+ "authored": null
+ },
+ {
+ "name": "HuangSe",
+ "color": "d0af42",
+ "metalness": 0,
+ "roughness": 0.5,
+ "envMapIntensity": 1,
+ "hasMap": false,
+ "preset": null,
+ "rule": null,
+ "authored": null
+ },
+ {
+ "name": "HuangSe2",
+ "color": "b29b87",
+ "metalness": 0,
+ "roughness": 0.5,
+ "envMapIntensity": 1,
+ "hasMap": false,
+ "preset": null,
+ "rule": null,
+ "authored": null
+ },
+ {
+ "name": "HuangSe3",
+ "color": "aea791",
+ "metalness": 0,
+ "roughness": 0.5,
+ "envMapIntensity": 1,
+ "hasMap": false,
+ "preset": null,
+ "rule": null,
+ "authored": null
+ },
+ {
+ "name": "HuiSe",
+ "color": "555555",
+ "metalness": 0.82,
+ "roughness": 0.15,
+ "envMapIntensity": 1,
+ "hasMap": false,
+ "preset": null,
+ "rule": null,
+ "authored": null
+ },
+ {
+ "name": "HuiSe2",
+ "color": "9a9ca4",
+ "metalness": 0,
+ "roughness": 1,
+ "envMapIntensity": 1,
+ "hasMap": false,
+ "preset": null,
+ "rule": null,
+ "authored": null
+ },
+ {
+ "name": "NeiQiangBaiSe",
+ "color": "bfbfbf",
+ "metalness": 0,
+ "roughness": 0.5,
+ "envMapIntensity": 1,
+ "hasMap": false,
+ "preset": null,
+ "rule": null,
+ "authored": null
+ },
+ {
+ "name": "adskMatHuiSe",
+ "color": "797979",
+ "metalness": 0.43,
+ "roughness": 0.4,
+ "envMapIntensity": 1,
+ "hasMap": false,
+ "preset": null,
+ "rule": null,
+ "authored": null
+ },
+ {
+ "name": "Red",
+ "color": "e70000",
+ "metalness": 0,
+ "roughness": 1,
+ "envMapIntensity": 1,
+ "hasMap": false,
+ "preset": null,
+ "rule": null,
+ "authored": null
+ },
+ {
+ "name": "truck",
+ "color": "ffffff",
+ "metalness": 0,
+ "roughness": 0.3,
+ "envMapIntensity": 1,
+ "hasMap": true,
+ "preset": null,
+ "rule": null,
+ "authored": null
+ },
+ {
+ "name": "glass",
+ "color": "003928",
+ "metalness": 0,
+ "roughness": 1,
+ "envMapIntensity": 1,
+ "hasMap": false,
+ "preset": null,
+ "rule": null,
+ "authored": null
+ },
+ {
+ "name": "window_trim",
+ "color": "484848",
+ "metalness": 0,
+ "roughness": 1,
+ "envMapIntensity": 1,
+ "hasMap": false,
+ "preset": null,
+ "rule": null,
+ "authored": null
+ },
+ {
+ "name": "wheels",
+ "color": "ffffff",
+ "metalness": 0,
+ "roughness": 1,
+ "envMapIntensity": 1,
+ "hasMap": true,
+ "preset": null,
+ "rule": null,
+ "authored": null
+ }
+ ],
+ "objectCount": 3,
+ "contentBounds": {
+ "min": [
+ -30,
+ 0,
+ -17.156
+ ],
+ "max": [
+ 30,
+ 14.579,
+ 17.156
+ ],
+ "size": [
+ 60,
+ 14.579,
+ 34.311
+ ]
+ },
+ "importedObjects": [
+ {
+ "name": "牛奶卡车检验主装备",
+ "modelEditorModelId": "imported-model_mto6wi6k_08-cesiummilktruck",
+ "modelEditorAssetId": "content-model-156-204",
+ "deviceId": "SCENE-TRUCK-001",
+ "modelId": "MODEL-IMPORT-02",
+ "assetCode": "MODEL_MTO6WI6K_08-CESIUMMILKTRUCK",
+ "targetProjectId": "156",
+ "targetVersionId": "204"
+ }
+ ],
+ "apiControlled": true,
+ "readOnly": false,
+ "ready": true,
+ "readySettled": true,
+ "dirty": false,
+ "saving": false,
+ "publishing": false,
+ "pendingLoads": 0,
+ "paused": false,
+ "previewing": false,
+ "currentPreset": {
+ "id": "depot-1",
+ "name": "真实装备车库",
+ "type": "车间"
+ },
+ "cameraPosition": [
+ 12,
+ 10.000000000000002,
+ 16
+ ],
+ "cameraTarget": [
+ 0,
+ 1,
+ 0
+ ],
+ "gridVisible": true,
+ "transformMode": "translate",
+ "selectedSceneId": "",
+ "canUndo": false,
+ "canRedo": false,
+ "objects": [
+ {
+ "sceneId": "OBJ-1788605149879-1",
+ "type": "imported",
+ "name": "车库",
+ "visible": true,
+ "position": [
+ 0,
+ 0.01461010548216679,
+ -4.4135855858939976e-8
+ ],
+ "rotation": [
+ 0,
+ 0,
+ 0
+ ],
+ "scale": [
+ 1.009710078119415,
+ 1.009710078119415,
+ 1.009710078119415
+ ],
+ "deviceId": "EQ-001",
+ "modelId": "MODEL-IMPORT-01",
+ "semantic": "环境设施",
+ "operable": false,
+ "capabilities": [
+ "select"
+ ],
+ "assetCode": "",
+ "sourceAssetId": "",
+ "targetProjectId": "",
+ "targetVersionId": "",
+ "modelEditorAssetId": "",
+ "parts": []
+ },
+ {
+ "sceneId": "OBJ-1788605199171-2",
+ "type": "imported",
+ "name": "牛奶卡车检验主装备",
+ "visible": true,
+ "position": [
+ 4.2,
+ 0.20617168998977797,
+ -0.4356763541543951
+ ],
+ "rotation": [
+ 0,
+ 0,
+ 0
+ ],
+ "scale": [
+ 1.2323086149245381,
+ 1.2323086149245381,
+ 1.2323086149245381
+ ],
+ "deviceId": "SCENE-TRUCK-001",
+ "modelId": "MODEL-IMPORT-02",
+ "semantic": "实训装备",
+ "operable": true,
+ "capabilities": [
+ "select",
+ "inspect",
+ "transform"
+ ],
+ "assetCode": "MODEL_MTO6WI6K_08-CESIUMMILKTRUCK",
+ "sourceAssetId": "2069",
+ "targetProjectId": "156",
+ "targetVersionId": "204",
+ "modelEditorAssetId": "content-model-156-204",
+ "parts": [
+ {
+ "sceneId": "interaction-1ba581e6",
+ "name": "Yup2Zup",
+ "visible": true,
+ "sceneDeleted": false,
+ "position": [
+ 0,
+ 0,
+ 0
+ ],
+ "rotation": [
+ 1.57079626719025,
+ 0,
+ 1.5707963267948966
+ ],
+ "scale": [
+ 1,
+ 1,
+ 1
+ ],
+ "capabilities": [
+ "select",
+ "inspect",
+ "transform",
+ "animate"
+ ]
+ },
+ {
+ "sceneId": "interaction-004dec41",
+ "name": "Cesium_Milk_Truck",
+ "visible": true,
+ "sceneDeleted": false,
+ "position": [
+ 0.35,
+ 0,
+ 0
+ ],
+ "rotation": [
+ 0,
+ 0,
+ 0
+ ],
+ "scale": [
+ 1,
+ 1,
+ 1
+ ],
+ "capabilities": [
+ "select",
+ "inspect",
+ "transform",
+ "animate"
+ ]
+ },
+ {
+ "sceneId": "interaction-5f7e4bee",
+ "name": "Cesium_Milk_Truck_1",
+ "visible": true,
+ "sceneDeleted": false,
+ "position": [
+ 0,
+ 0,
+ 0
+ ],
+ "rotation": [
+ 0,
+ 0,
+ 0
+ ],
+ "scale": [
+ 1,
+ 1,
+ 1
+ ],
+ "capabilities": [
+ "select",
+ "inspect",
+ "transform",
+ "animate"
+ ]
+ },
+ {
+ "sceneId": "interaction-607e4d81",
+ "name": "Cesium_Milk_Truck_2",
+ "visible": true,
+ "sceneDeleted": false,
+ "position": [
+ 0,
+ 0,
+ 0
+ ],
+ "rotation": [
+ 0,
+ 0,
+ 0
+ ],
+ "scale": [
+ 1,
+ 1,
+ 1
+ ],
+ "capabilities": [
+ "select",
+ "inspect",
+ "transform",
+ "animate"
+ ]
+ },
+ {
+ "sceneId": "interaction-5d7e48c8",
+ "name": "Cesium_Milk_Truck_3",
+ "visible": true,
+ "sceneDeleted": false,
+ "position": [
+ 0,
+ 0,
+ 0
+ ],
+ "rotation": [
+ 0,
+ 0,
+ 0
+ ],
+ "scale": [
+ 1,
+ 1,
+ 1
+ ],
+ "capabilities": [
+ "select",
+ "inspect",
+ "transform",
+ "animate"
+ ]
+ },
+ {
+ "sceneId": "interaction-5e7e4a5b",
+ "name": "Node",
+ "visible": true,
+ "sceneDeleted": false,
+ "position": [
+ 1.432669997215271,
+ 0,
+ -0.4277220070362091
+ ],
+ "rotation": [
+ 0,
+ 0,
+ 0
+ ],
+ "scale": [
+ 1,
+ 1,
+ 1
+ ],
+ "capabilities": [
+ "select",
+ "inspect",
+ "transform",
+ "animate"
+ ]
+ },
+ {
+ "sceneId": "interaction-ce2feac4",
+ "name": "Wheels",
+ "visible": true,
+ "sceneDeleted": false,
+ "position": [
+ 0,
+ 0,
+ 0
+ ],
+ "rotation": [
+ -3.141592653589793,
+ -0.603571861900947,
+ -3.141592653589793
+ ],
+ "scale": [
+ 1,
+ 1,
+ 1
+ ],
+ "capabilities": [
+ "select",
+ "inspect",
+ "transform",
+ "animate"
+ ]
+ },
+ {
+ "sceneId": "interaction-637e523a",
+ "name": "Node001",
+ "visible": true,
+ "sceneDeleted": false,
+ "position": [
+ -1.352329969406128,
+ 0,
+ -0.4277220070362091
+ ],
+ "rotation": [
+ 0,
+ 0,
+ 0
+ ],
+ "scale": [
+ 1,
+ 1,
+ 1
+ ],
+ "capabilities": [
+ "select",
+ "inspect",
+ "transform",
+ "animate"
+ ]
+ },
+ {
+ "sceneId": "interaction-f417f82d",
+ "name": "Wheels001",
+ "visible": true,
+ "sceneDeleted": false,
+ "position": [
+ 0,
+ 0,
+ 0
+ ],
+ "rotation": [
+ -3.141592653589793,
+ -0.603571861900947,
+ -3.141592653589793
+ ],
+ "scale": [
+ 1,
+ 1,
+ 1
+ ],
+ "capabilities": [
+ "select",
+ "inspect",
+ "transform",
+ "animate"
+ ]
+ }
+ ]
+ },
+ {
+ "sceneId": "OBJ-1788690372805-4",
+ "type": "imported",
+ "name": "01-Box",
+ "visible": true,
+ "position": [
+ 0,
+ 2.5,
+ 0
+ ],
+ "rotation": [
+ 0,
+ 0,
+ 0
+ ],
+ "scale": [
+ 4.999999999999999,
+ 4.999999999999999,
+ 4.999999999999999
+ ],
+ "deviceId": "EQ-004",
+ "modelId": "MODEL-IMPORT-04",
+ "semantic": "实训装备",
+ "operable": true,
+ "capabilities": [
+ "select",
+ "inspect",
+ "transform"
+ ],
+ "assetCode": "SCENE-GLB-MTPO2C8Q-1",
+ "sourceAssetId": "",
+ "targetProjectId": "",
+ "targetVersionId": "",
+ "modelEditorAssetId": "",
+ "parts": []
+ }
+ ]
+ },
+ "rendering": {
+ "schema": "edit3dv4.scene-rendering",
+ "version": "1.0",
+ "background": 12178918,
+ "fog": {
+ "color": 11189196,
+ "near": 60,
+ "far": 240
+ },
+ "toneMappingExposure": 0.9,
+ "environment": {
+ "type": "room",
+ "sigma": 0.04
+ },
+ "camera": {
+ "fov": 45,
+ "near": 0.5308325806671546,
+ "far": 300,
+ "position": [
+ 12,
+ 10.000000000000002,
+ 16
+ ],
+ "target": [
+ 0,
+ 1,
+ 0
+ ]
+ },
+ "lights": {
+ "hemisphere": {
+ "skyColor": 14216959,
+ "groundColor": 6386534,
+ "intensity": 1.4
+ },
+ "directional": {
+ "color": 16777215,
+ "intensity": 2.9,
+ "position": [
+ 24,
+ 40,
+ 20
+ ],
+ "castShadow": true,
+ "shadowMapSize": 2048,
+ "shadowBounds": 60
+ }
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/reports/scene-editor-closure-20260906/evidence/loading-retry.json b/reports/scene-editor-closure-20260906/evidence/loading-retry.json
new file mode 100644
index 0000000..d17cf97
--- /dev/null
+++ b/reports/scene-editor-closure-20260906/evidence/loading-retry.json
@@ -0,0 +1,419 @@
+{
+ "time": "2026-09-06T10:24:14.518Z",
+ "data": {
+ "kind": "browser",
+ "status": "PASS",
+ "testTitle": "加载失败重试与加载互斥:不展示可写半场景、恢复网络后模型完整加载",
+ "featureIds": [
+ "project.retry",
+ "glb.pending",
+ "leave.busy"
+ ],
+ "scope": "GET503只留中性失败和重试;延迟受控下载时禁用写入/导出/预览并阻止关闭,释放后真实狐狸加载。",
+ "projectId": "373",
+ "ready": {
+ "mounted": true,
+ "hasEnvironment": true,
+ "hasEnvironmentTarget": true,
+ "toneMappingExposure": 1.05,
+ "background": "a8cbe1",
+ "meshCount": 15,
+ "materials": [
+ {
+ "name": "(未命名)",
+ "color": "445e2d",
+ "metalness": 0.35,
+ "roughness": 0.55,
+ "envMapIntensity": 1,
+ "hasMap": false,
+ "preset": null,
+ "rule": null,
+ "authored": null
+ },
+ {
+ "name": "(未命名)",
+ "color": "182c36",
+ "metalness": 0.55,
+ "roughness": 0.34,
+ "envMapIntensity": 1,
+ "hasMap": false,
+ "preset": null,
+ "rule": null,
+ "authored": null
+ },
+ {
+ "name": "(未命名)",
+ "color": "1e4f69",
+ "metalness": 0.45,
+ "roughness": 0.42,
+ "envMapIntensity": 1,
+ "hasMap": false,
+ "preset": null,
+ "rule": null,
+ "authored": null
+ },
+ {
+ "name": "(未命名)",
+ "color": "36b37e",
+ "metalness": 0,
+ "roughness": 1,
+ "envMapIntensity": 1,
+ "hasMap": false,
+ "preset": null,
+ "rule": null,
+ "authored": null
+ },
+ {
+ "name": "(未命名)",
+ "color": "182c36",
+ "metalness": 0.55,
+ "roughness": 0.34,
+ "envMapIntensity": 1,
+ "hasMap": false,
+ "preset": null,
+ "rule": null,
+ "authored": null
+ },
+ {
+ "name": "(未命名)",
+ "color": "e4a62b",
+ "metalness": 0.25,
+ "roughness": 0.48,
+ "envMapIntensity": 1,
+ "hasMap": false,
+ "preset": null,
+ "rule": null,
+ "authored": null
+ },
+ {
+ "name": "M_3dfreehub_mat_2",
+ "color": "ffffff",
+ "metalness": 0,
+ "roughness": 0.5,
+ "envMapIntensity": 1,
+ "hasMap": true,
+ "preset": null,
+ "rule": null,
+ "authored": null
+ },
+ {
+ "name": "M_3dfreehub_mat_3",
+ "color": "ffffff",
+ "metalness": 0,
+ "roughness": 0.5,
+ "envMapIntensity": 1,
+ "hasMap": true,
+ "preset": null,
+ "rule": null,
+ "authored": null
+ },
+ {
+ "name": "M_3dfreehub_mat_4",
+ "color": "ffffff",
+ "metalness": 0,
+ "roughness": 0.5,
+ "envMapIntensity": 1,
+ "hasMap": true,
+ "preset": null,
+ "rule": null,
+ "authored": null
+ },
+ {
+ "name": "M_3dfreehub_mat_5",
+ "color": "ffffff",
+ "metalness": 0,
+ "roughness": 0.5,
+ "envMapIntensity": 1,
+ "hasMap": true,
+ "preset": null,
+ "rule": null,
+ "authored": null
+ },
+ {
+ "name": "Material__85",
+ "color": "695c5b",
+ "metalness": 0,
+ "roughness": 0.5,
+ "envMapIntensity": 1,
+ "hasMap": false,
+ "preset": null,
+ "rule": null,
+ "authored": null
+ },
+ {
+ "name": "fox_material",
+ "color": "ffffff",
+ "metalness": 0,
+ "roughness": 0.3,
+ "envMapIntensity": 1,
+ "hasMap": true,
+ "preset": null,
+ "rule": null,
+ "authored": null
+ }
+ ],
+ "objectCount": 5,
+ "contentBounds": {
+ "min": [
+ -41,
+ -5.239,
+ -34.028
+ ],
+ "max": [
+ 41,
+ 11.358,
+ 34.028
+ ],
+ "size": [
+ 82,
+ 16.596,
+ 68.057
+ ]
+ },
+ "importedObjects": [
+ {
+ "name": "狐狸野外巡检目标",
+ "modelEditorModelId": "imported-model_mto6v5w6_09-fox",
+ "modelEditorAssetId": "content-model-157-201",
+ "deviceId": "SCENE-FOX-001",
+ "modelId": "MODEL-IMPORT-06",
+ "assetCode": "MODEL_MTO6V5W6_09-FOX",
+ "targetProjectId": "157",
+ "targetVersionId": "201"
+ }
+ ],
+ "apiControlled": true,
+ "readOnly": false,
+ "ready": true,
+ "readySettled": true,
+ "dirty": false,
+ "saving": false,
+ "publishing": false,
+ "pendingLoads": 0,
+ "paused": false,
+ "previewing": false,
+ "currentPreset": {
+ "id": "workshop-real",
+ "name": "演示维修车间(可拆分)",
+ "type": "车间"
+ },
+ "cameraPosition": [
+ -0.04179225073787585,
+ 6.099634646023559,
+ 17.355036987378313
+ ],
+ "cameraTarget": [
+ -7.512232860974557,
+ 1.4059499759307126,
+ -2.2204460492503126e-16
+ ],
+ "gridVisible": true,
+ "transformMode": "translate",
+ "selectedSceneId": "",
+ "canUndo": false,
+ "canRedo": false,
+ "objects": [
+ {
+ "sceneId": "OBJ-1788605873329-2",
+ "type": "equipment",
+ "name": "维修装备主体 02",
+ "visible": true,
+ "position": [
+ 0,
+ 0,
+ 0
+ ],
+ "rotation": [
+ 0,
+ 0,
+ 0
+ ],
+ "scale": [
+ 1,
+ 1,
+ 1
+ ],
+ "deviceId": "EQ-002",
+ "modelId": "MODEL-EQUIPMENT-02",
+ "semantic": "实训装备",
+ "operable": true,
+ "capabilities": [
+ "select",
+ "inspect",
+ "transform"
+ ],
+ "assetCode": "",
+ "sourceAssetId": "",
+ "targetProjectId": "",
+ "targetVersionId": "",
+ "modelEditorAssetId": "",
+ "parts": []
+ },
+ {
+ "sceneId": "OBJ-1788605873331-3",
+ "type": "safety-zone",
+ "name": "安全作业区域 03",
+ "visible": true,
+ "position": [
+ 0,
+ 0.02,
+ 0
+ ],
+ "rotation": [
+ 0,
+ 0,
+ 0
+ ],
+ "scale": [
+ 1,
+ 1,
+ 1
+ ],
+ "deviceId": "EQ-003",
+ "modelId": "MODEL-SAFETY-ZONE-03",
+ "semantic": "安全区域",
+ "operable": false,
+ "capabilities": [
+ "select"
+ ],
+ "assetCode": "",
+ "sourceAssetId": "",
+ "targetProjectId": "",
+ "targetVersionId": "",
+ "modelEditorAssetId": "",
+ "parts": []
+ },
+ {
+ "sceneId": "OBJ-1788605873333-4",
+ "type": "light",
+ "name": "区域照明 04",
+ "visible": true,
+ "position": [
+ -12,
+ 0,
+ 8
+ ],
+ "rotation": [
+ 0,
+ 0,
+ 0
+ ],
+ "scale": [
+ 1,
+ 1,
+ 1
+ ],
+ "deviceId": "EQ-004",
+ "modelId": "MODEL-LIGHT-04",
+ "semantic": "实训装备",
+ "operable": false,
+ "capabilities": [
+ "select"
+ ],
+ "assetCode": "",
+ "sourceAssetId": "",
+ "targetProjectId": "",
+ "targetVersionId": "",
+ "modelEditorAssetId": "",
+ "parts": []
+ },
+ {
+ "sceneId": "OBJ-1788605916565-6",
+ "type": "imported",
+ "name": "狐狸野外巡检目标",
+ "visible": true,
+ "position": [
+ -7.5,
+ 0.10930174852949677,
+ 0.3440109341705351
+ ],
+ "rotation": [
+ 0,
+ 0,
+ 0
+ ],
+ "scale": [
+ 0.03616375932570189,
+ 0.03616375932570189,
+ 0.03616375932570189
+ ],
+ "deviceId": "SCENE-FOX-001",
+ "modelId": "MODEL-IMPORT-06",
+ "semantic": "实训装备",
+ "operable": true,
+ "capabilities": [
+ "select",
+ "inspect",
+ "transform"
+ ],
+ "assetCode": "MODEL_MTO6V5W6_09-FOX",
+ "sourceAssetId": "2030",
+ "targetProjectId": "157",
+ "targetVersionId": "201",
+ "modelEditorAssetId": "content-model-157-201",
+ "parts": [
+ {
+ "sceneId": "interaction-a3d35c26",
+ "name": "fox",
+ "visible": true,
+ "sceneDeleted": false,
+ "position": [
+ 0.25,
+ 0,
+ 0
+ ],
+ "rotation": [
+ 0,
+ 0,
+ 0
+ ],
+ "scale": [
+ 1,
+ 1,
+ 1
+ ],
+ "capabilities": [
+ "select",
+ "inspect"
+ ]
+ }
+ ]
+ },
+ {
+ "sceneId": "OBJ-1788605874053-5",
+ "type": "imported",
+ "name": "地形",
+ "visible": true,
+ "position": [
+ 3.069303533692093,
+ 4.802826448856971,
+ -3.074293867354786
+ ],
+ "rotation": [
+ 0,
+ 0,
+ 0
+ ],
+ "scale": [
+ 1.0050406774865042,
+ 1.0050406774865042,
+ 1.0050406774865042
+ ],
+ "deviceId": "EQ-005",
+ "modelId": "MODEL-IMPORT-05",
+ "semantic": "环境设施",
+ "operable": false,
+ "capabilities": [
+ "select"
+ ],
+ "assetCode": "",
+ "sourceAssetId": "",
+ "targetProjectId": "",
+ "targetVersionId": "",
+ "modelEditorAssetId": "",
+ "parts": []
+ }
+ ]
+ }
+ }
+}
\ No newline at end of file
diff --git a/reports/scene-editor-closure-20260906/evidence/new-project.json b/reports/scene-editor-closure-20260906/evidence/new-project.json
new file mode 100644
index 0000000..3d9ffaf
--- /dev/null
+++ b/reports/scene-editor-closure-20260906/evidence/new-project.json
@@ -0,0 +1,58 @@
+{
+ "time": "2026-09-06T10:30:58.245Z",
+ "data": {
+ "kind": "browser",
+ "status": "PASS",
+ "testTitle": "新建独立场景:取消、失败、保护原工程以及成功新 ID",
+ "featureIds": [
+ "project.new"
+ ],
+ "scope": "取消命名、创建503保留原工程、创建成功新ID、保存刷新与原工程内容核对。",
+ "sourceId": "405",
+ "createdId": "406",
+ "sourceName": "场景闭环-fox-新建-1788690633695",
+ "current": {
+ "mounted": true,
+ "hasEnvironment": true,
+ "hasEnvironmentTarget": true,
+ "toneMappingExposure": 1.05,
+ "background": "b9d5e6",
+ "meshCount": 0,
+ "materials": [],
+ "objectCount": 0,
+ "contentBounds": null,
+ "importedObjects": [],
+ "apiControlled": true,
+ "readOnly": false,
+ "ready": true,
+ "readySettled": true,
+ "dirty": false,
+ "saving": false,
+ "publishing": false,
+ "pendingLoads": 0,
+ "paused": false,
+ "previewing": false,
+ "currentPreset": {
+ "id": "workshop-1",
+ "name": "综合检验车间",
+ "type": "车间"
+ },
+ "cameraPosition": [
+ 15,
+ 12,
+ 19
+ ],
+ "cameraTarget": [
+ 0,
+ 1.5,
+ 0
+ ],
+ "gridVisible": true,
+ "transformMode": "translate",
+ "selectedSceneId": "",
+ "canUndo": false,
+ "canRedo": false,
+ "objects": []
+ }
+ }
+}
\ No newline at end of file
diff --git a/reports/scene-editor-closure-20260906/evidence/publish-fixed-version.json b/reports/scene-editor-closure-20260906/evidence/publish-fixed-version.json
new file mode 100644
index 0000000..9615f96
--- /dev/null
+++ b/reports/scene-editor-closure-20260906/evidence/publish-fixed-version.json
@@ -0,0 +1,50 @@
+{
+ "time": "2026-09-06T10:27:04.782Z",
+ "data": {
+ "kind": "browser",
+ "status": "PASS",
+ "testTitle": "发布:前端绑定校验、保存与发布失败阻断、真实固定版本供训练目录使用",
+ "featureIds": [
+ "validation.complete",
+ "validation.bindings",
+ "panel.validation",
+ "publish.saved",
+ "publish.failure",
+ "publish.fixed-model",
+ "cover.atomic",
+ "project.back"
+ ],
+ "scope": "实际校验、绑定阻断、POST503不假发布,重试发布当前固定版本;返回列表保留编辑标签,搜索当前项目并核对真实封面图片加载。下游静态渲染另有独立用例。",
+ "projectId": "386",
+ "versionId": "440",
+ "dependency": {
+ "id": "421",
+ "targetProjectId": "156",
+ "targetVersionId": "204",
+ "relationType": "SCENE_MODEL",
+ "required": true,
+ "sortOrder": 0
+ },
+ "catalog": {
+ "projectId": "386",
+ "versionId": "440",
+ "type": "SCENE",
+ "code": "SCENE-56BD7C4325D74958",
+ "name": "场景闭环-truck-发布-1788690410773",
+ "categoryCode": "WORKSHOP",
+ "trainingMode": "",
+ "coverUri": "fsvc://unreal-tran/image/2026/09/06/dd7bf4562b6ff0ce60b7019d6c94c54d.jpg",
+ "versionNo": 1,
+ "versionCode": "V1.0.0",
+ "versionStatus": "PUBLISHED",
+ "publishedTime": 1788690417,
+ "contentSummary": {
+ "schema": "edit3dv4.scene",
+ "sceneType": "WORKSHOP",
+ "environmentName": "正式车库建筑体量 · 整机停放与进出场",
+ "objectCount": 2
+ }
+ },
+ "coverUri": "fsvc://unreal-tran/image/2026/09/06/dd7bf4562b6ff0ce60b7019d6c94c54d.jpg"
+ }
+}
\ No newline at end of file
diff --git a/reports/scene-editor-closure-20260906/evidence/published-model-assets.json b/reports/scene-editor-closure-20260906/evidence/published-model-assets.json
new file mode 100644
index 0000000..68e8d7f
--- /dev/null
+++ b/reports/scene-editor-closure-20260906/evidence/published-model-assets.json
@@ -0,0 +1,32 @@
+{
+ "time": "2026-09-06T10:29:25.167Z",
+ "data": {
+ "kind": "e2e",
+ "status": "PASS",
+ "testTitle": "模型成果狐狸卡车双击与真实拖入、树搜索和部件隐藏重开",
+ "featureIds": [
+ "assets.catalog",
+ "assets.model-double-click",
+ "assets.model-drop",
+ "search.tree",
+ "part.source",
+ "property.visible"
+ ],
+ "projectId": "399",
+ "sources": [
+ {
+ "projectId": "157",
+ "versionId": "201"
+ },
+ {
+ "projectId": "156",
+ "versionId": "204"
+ }
+ ],
+ "scope": "API 已发布目录,狐狸双击和卡车真实拖入;固定来源版本、树根/部件搜索;卡车刚体部件隐藏保存重开,最后恢复可见。基线树没有折叠按钮,不将搜索伪称折叠。",
+ "screenshots": [
+ "33-fox-placed-visible.png",
+ "34-truck-placed-part-restored.png"
+ ]
+ }
+}
\ No newline at end of file
diff --git a/reports/scene-editor-closure-20260906/evidence/readonly.json b/reports/scene-editor-closure-20260906/evidence/readonly.json
new file mode 100644
index 0000000..7221cb5
--- /dev/null
+++ b/reports/scene-editor-closure-20260906/evidence/readonly.json
@@ -0,0 +1,827 @@
+{
+ "time": "2026-09-06T10:31:05.221Z",
+ "data": {
+ "kind": "browser",
+ "status": "PASS",
+ "testTitle": "只读管理员:可浏览预览与导出,模板/资源/键盘/接口不能改场景",
+ "featureIds": [
+ "permission.readonly",
+ "permission.update",
+ "permission.publish",
+ "preview.enter",
+ "preview.exit",
+ "preview.lock",
+ "json.export"
+ ],
+ "scope": "只读按钮/模板/资源禁写、蒙皮浏览、运行预览与快捷键不变更对象,直接PUT拒绝;具体搜索/鼠标覆盖另见视口用例。",
+ "before": {
+ "mounted": true,
+ "hasEnvironment": true,
+ "hasEnvironmentTarget": true,
+ "toneMappingExposure": 1.05,
+ "background": "a8cbe1",
+ "meshCount": 15,
+ "materials": [
+ {
+ "name": "(未命名)",
+ "color": "445e2d",
+ "metalness": 0.35,
+ "roughness": 0.55,
+ "envMapIntensity": 1,
+ "hasMap": false,
+ "preset": null,
+ "rule": null,
+ "authored": null
+ },
+ {
+ "name": "(未命名)",
+ "color": "182c36",
+ "metalness": 0.55,
+ "roughness": 0.34,
+ "envMapIntensity": 1,
+ "hasMap": false,
+ "preset": null,
+ "rule": null,
+ "authored": null
+ },
+ {
+ "name": "(未命名)",
+ "color": "1e4f69",
+ "metalness": 0.45,
+ "roughness": 0.42,
+ "envMapIntensity": 1,
+ "hasMap": false,
+ "preset": null,
+ "rule": null,
+ "authored": null
+ },
+ {
+ "name": "(未命名)",
+ "color": "36b37e",
+ "metalness": 0,
+ "roughness": 1,
+ "envMapIntensity": 1,
+ "hasMap": false,
+ "preset": null,
+ "rule": null,
+ "authored": null
+ },
+ {
+ "name": "(未命名)",
+ "color": "182c36",
+ "metalness": 0.55,
+ "roughness": 0.34,
+ "envMapIntensity": 1,
+ "hasMap": false,
+ "preset": null,
+ "rule": null,
+ "authored": null
+ },
+ {
+ "name": "(未命名)",
+ "color": "e4a62b",
+ "metalness": 0.25,
+ "roughness": 0.48,
+ "envMapIntensity": 1,
+ "hasMap": false,
+ "preset": null,
+ "rule": null,
+ "authored": null
+ },
+ {
+ "name": "M_3dfreehub_mat_2",
+ "color": "ffffff",
+ "metalness": 0,
+ "roughness": 0.5,
+ "envMapIntensity": 1,
+ "hasMap": true,
+ "preset": null,
+ "rule": null,
+ "authored": null
+ },
+ {
+ "name": "M_3dfreehub_mat_3",
+ "color": "ffffff",
+ "metalness": 0,
+ "roughness": 0.5,
+ "envMapIntensity": 1,
+ "hasMap": true,
+ "preset": null,
+ "rule": null,
+ "authored": null
+ },
+ {
+ "name": "M_3dfreehub_mat_4",
+ "color": "ffffff",
+ "metalness": 0,
+ "roughness": 0.5,
+ "envMapIntensity": 1,
+ "hasMap": true,
+ "preset": null,
+ "rule": null,
+ "authored": null
+ },
+ {
+ "name": "M_3dfreehub_mat_5",
+ "color": "ffffff",
+ "metalness": 0,
+ "roughness": 0.5,
+ "envMapIntensity": 1,
+ "hasMap": true,
+ "preset": null,
+ "rule": null,
+ "authored": null
+ },
+ {
+ "name": "Material__85",
+ "color": "695c5b",
+ "metalness": 0,
+ "roughness": 0.5,
+ "envMapIntensity": 1,
+ "hasMap": false,
+ "preset": null,
+ "rule": null,
+ "authored": null
+ },
+ {
+ "name": "fox_material",
+ "color": "ffffff",
+ "metalness": 0,
+ "roughness": 0.3,
+ "envMapIntensity": 1,
+ "hasMap": true,
+ "preset": null,
+ "rule": null,
+ "authored": null
+ }
+ ],
+ "objectCount": 5,
+ "contentBounds": {
+ "min": [
+ -41,
+ -5.239,
+ -34.028
+ ],
+ "max": [
+ 41,
+ 11.358,
+ 34.028
+ ],
+ "size": [
+ 82,
+ 16.596,
+ 68.057
+ ]
+ },
+ "importedObjects": [
+ {
+ "name": "狐狸野外巡检目标",
+ "modelEditorModelId": "imported-model_mto6v5w6_09-fox",
+ "modelEditorAssetId": "content-model-157-201",
+ "deviceId": "SCENE-FOX-001",
+ "modelId": "MODEL-IMPORT-06",
+ "assetCode": "MODEL_MTO6V5W6_09-FOX",
+ "targetProjectId": "157",
+ "targetVersionId": "201"
+ }
+ ],
+ "apiControlled": true,
+ "readOnly": true,
+ "ready": true,
+ "readySettled": true,
+ "dirty": false,
+ "saving": false,
+ "publishing": false,
+ "pendingLoads": 0,
+ "paused": false,
+ "previewing": false,
+ "currentPreset": {
+ "id": "workshop-real",
+ "name": "演示维修车间(可拆分)",
+ "type": "车间"
+ },
+ "cameraPosition": [
+ -0.04179225073787585,
+ 6.099634646023559,
+ 17.355036987378313
+ ],
+ "cameraTarget": [
+ -7.512232860974557,
+ 1.4059499759307126,
+ -2.2204460492503126e-16
+ ],
+ "gridVisible": true,
+ "transformMode": "translate",
+ "selectedSceneId": "",
+ "canUndo": false,
+ "canRedo": false,
+ "objects": [
+ {
+ "sceneId": "OBJ-1788605873329-2",
+ "type": "equipment",
+ "name": "维修装备主体 02",
+ "visible": true,
+ "position": [
+ 0,
+ 0,
+ 0
+ ],
+ "rotation": [
+ 0,
+ 0,
+ 0
+ ],
+ "scale": [
+ 1,
+ 1,
+ 1
+ ],
+ "deviceId": "EQ-002",
+ "modelId": "MODEL-EQUIPMENT-02",
+ "semantic": "实训装备",
+ "operable": true,
+ "capabilities": [
+ "select",
+ "inspect",
+ "transform"
+ ],
+ "assetCode": "",
+ "sourceAssetId": "",
+ "targetProjectId": "",
+ "targetVersionId": "",
+ "modelEditorAssetId": "",
+ "parts": []
+ },
+ {
+ "sceneId": "OBJ-1788605873331-3",
+ "type": "safety-zone",
+ "name": "安全作业区域 03",
+ "visible": true,
+ "position": [
+ 0,
+ 0.02,
+ 0
+ ],
+ "rotation": [
+ 0,
+ 0,
+ 0
+ ],
+ "scale": [
+ 1,
+ 1,
+ 1
+ ],
+ "deviceId": "EQ-003",
+ "modelId": "MODEL-SAFETY-ZONE-03",
+ "semantic": "安全区域",
+ "operable": false,
+ "capabilities": [
+ "select"
+ ],
+ "assetCode": "",
+ "sourceAssetId": "",
+ "targetProjectId": "",
+ "targetVersionId": "",
+ "modelEditorAssetId": "",
+ "parts": []
+ },
+ {
+ "sceneId": "OBJ-1788605873333-4",
+ "type": "light",
+ "name": "区域照明 04",
+ "visible": true,
+ "position": [
+ -12,
+ 0,
+ 8
+ ],
+ "rotation": [
+ 0,
+ 0,
+ 0
+ ],
+ "scale": [
+ 1,
+ 1,
+ 1
+ ],
+ "deviceId": "EQ-004",
+ "modelId": "MODEL-LIGHT-04",
+ "semantic": "实训装备",
+ "operable": false,
+ "capabilities": [
+ "select"
+ ],
+ "assetCode": "",
+ "sourceAssetId": "",
+ "targetProjectId": "",
+ "targetVersionId": "",
+ "modelEditorAssetId": "",
+ "parts": []
+ },
+ {
+ "sceneId": "OBJ-1788605916565-6",
+ "type": "imported",
+ "name": "狐狸野外巡检目标",
+ "visible": true,
+ "position": [
+ -7.5,
+ 0.10930174852949677,
+ 0.3440109341705351
+ ],
+ "rotation": [
+ 0,
+ 0,
+ 0
+ ],
+ "scale": [
+ 0.03616375932570189,
+ 0.03616375932570189,
+ 0.03616375932570189
+ ],
+ "deviceId": "SCENE-FOX-001",
+ "modelId": "MODEL-IMPORT-06",
+ "semantic": "实训装备",
+ "operable": true,
+ "capabilities": [
+ "select",
+ "inspect",
+ "transform"
+ ],
+ "assetCode": "MODEL_MTO6V5W6_09-FOX",
+ "sourceAssetId": "2030",
+ "targetProjectId": "157",
+ "targetVersionId": "201",
+ "modelEditorAssetId": "content-model-157-201",
+ "parts": [
+ {
+ "sceneId": "interaction-a3d35c26",
+ "name": "fox",
+ "visible": true,
+ "sceneDeleted": false,
+ "position": [
+ 0.25,
+ 0,
+ 0
+ ],
+ "rotation": [
+ 0,
+ 0,
+ 0
+ ],
+ "scale": [
+ 1,
+ 1,
+ 1
+ ],
+ "capabilities": [
+ "select",
+ "inspect"
+ ]
+ }
+ ]
+ },
+ {
+ "sceneId": "OBJ-1788605874053-5",
+ "type": "imported",
+ "name": "地形",
+ "visible": true,
+ "position": [
+ 3.069303533692093,
+ 4.802826448856971,
+ -3.074293867354786
+ ],
+ "rotation": [
+ 0,
+ 0,
+ 0
+ ],
+ "scale": [
+ 1.0050406774865042,
+ 1.0050406774865042,
+ 1.0050406774865042
+ ],
+ "deviceId": "EQ-005",
+ "modelId": "MODEL-IMPORT-05",
+ "semantic": "环境设施",
+ "operable": false,
+ "capabilities": [
+ "select"
+ ],
+ "assetCode": "",
+ "sourceAssetId": "",
+ "targetProjectId": "",
+ "targetVersionId": "",
+ "modelEditorAssetId": "",
+ "parts": []
+ }
+ ]
+ },
+ "after": {
+ "mounted": true,
+ "hasEnvironment": true,
+ "hasEnvironmentTarget": true,
+ "toneMappingExposure": 1.05,
+ "background": "a8cbe1",
+ "meshCount": 15,
+ "materials": [
+ {
+ "name": "(未命名)",
+ "color": "445e2d",
+ "metalness": 0.35,
+ "roughness": 0.55,
+ "envMapIntensity": 1,
+ "hasMap": false,
+ "preset": null,
+ "rule": null,
+ "authored": null
+ },
+ {
+ "name": "(未命名)",
+ "color": "182c36",
+ "metalness": 0.55,
+ "roughness": 0.34,
+ "envMapIntensity": 1,
+ "hasMap": false,
+ "preset": null,
+ "rule": null,
+ "authored": null
+ },
+ {
+ "name": "(未命名)",
+ "color": "1e4f69",
+ "metalness": 0.45,
+ "roughness": 0.42,
+ "envMapIntensity": 1,
+ "hasMap": false,
+ "preset": null,
+ "rule": null,
+ "authored": null
+ },
+ {
+ "name": "(未命名)",
+ "color": "36b37e",
+ "metalness": 0,
+ "roughness": 1,
+ "envMapIntensity": 1,
+ "hasMap": false,
+ "preset": null,
+ "rule": null,
+ "authored": null
+ },
+ {
+ "name": "(未命名)",
+ "color": "182c36",
+ "metalness": 0.55,
+ "roughness": 0.34,
+ "envMapIntensity": 1,
+ "hasMap": false,
+ "preset": null,
+ "rule": null,
+ "authored": null
+ },
+ {
+ "name": "(未命名)",
+ "color": "e4a62b",
+ "metalness": 0.25,
+ "roughness": 0.48,
+ "envMapIntensity": 1,
+ "hasMap": false,
+ "preset": null,
+ "rule": null,
+ "authored": null
+ },
+ {
+ "name": "M_3dfreehub_mat_2",
+ "color": "ffffff",
+ "metalness": 0,
+ "roughness": 0.5,
+ "envMapIntensity": 1,
+ "hasMap": true,
+ "preset": null,
+ "rule": null,
+ "authored": null
+ },
+ {
+ "name": "M_3dfreehub_mat_3",
+ "color": "ffffff",
+ "metalness": 0,
+ "roughness": 0.5,
+ "envMapIntensity": 1,
+ "hasMap": true,
+ "preset": null,
+ "rule": null,
+ "authored": null
+ },
+ {
+ "name": "M_3dfreehub_mat_4",
+ "color": "ffffff",
+ "metalness": 0,
+ "roughness": 0.5,
+ "envMapIntensity": 1,
+ "hasMap": true,
+ "preset": null,
+ "rule": null,
+ "authored": null
+ },
+ {
+ "name": "M_3dfreehub_mat_5",
+ "color": "ffffff",
+ "metalness": 0,
+ "roughness": 0.5,
+ "envMapIntensity": 1,
+ "hasMap": true,
+ "preset": null,
+ "rule": null,
+ "authored": null
+ },
+ {
+ "name": "Material__85",
+ "color": "695c5b",
+ "metalness": 0,
+ "roughness": 0.5,
+ "envMapIntensity": 1,
+ "hasMap": false,
+ "preset": null,
+ "rule": null,
+ "authored": null
+ },
+ {
+ "name": "fox_material",
+ "color": "ffffff",
+ "metalness": 0,
+ "roughness": 0.3,
+ "envMapIntensity": 1,
+ "hasMap": true,
+ "preset": null,
+ "rule": null,
+ "authored": null
+ }
+ ],
+ "objectCount": 5,
+ "contentBounds": {
+ "min": [
+ -41,
+ -5.239,
+ -34.028
+ ],
+ "max": [
+ 41,
+ 11.358,
+ 34.028
+ ],
+ "size": [
+ 82,
+ 16.596,
+ 68.057
+ ]
+ },
+ "importedObjects": [
+ {
+ "name": "狐狸野外巡检目标",
+ "modelEditorModelId": "imported-model_mto6v5w6_09-fox",
+ "modelEditorAssetId": "content-model-157-201",
+ "deviceId": "SCENE-FOX-001",
+ "modelId": "MODEL-IMPORT-06",
+ "assetCode": "MODEL_MTO6V5W6_09-FOX",
+ "targetProjectId": "157",
+ "targetVersionId": "201"
+ }
+ ],
+ "apiControlled": true,
+ "readOnly": true,
+ "ready": true,
+ "readySettled": true,
+ "dirty": false,
+ "saving": false,
+ "publishing": false,
+ "pendingLoads": 0,
+ "paused": false,
+ "previewing": false,
+ "currentPreset": {
+ "id": "workshop-real",
+ "name": "演示维修车间(可拆分)",
+ "type": "车间"
+ },
+ "cameraPosition": [
+ -4.276260993586593,
+ 3.4391140607904136,
+ 7.517683946469992
+ ],
+ "cameraTarget": [
+ -7.512232860974557,
+ 1.4059499759307126,
+ -2.2204460492503126e-16
+ ],
+ "gridVisible": true,
+ "transformMode": "translate",
+ "selectedSceneId": "OBJ-1788605916565-6",
+ "canUndo": false,
+ "canRedo": false,
+ "objects": [
+ {
+ "sceneId": "OBJ-1788605873329-2",
+ "type": "equipment",
+ "name": "维修装备主体 02",
+ "visible": true,
+ "position": [
+ 0,
+ 0,
+ 0
+ ],
+ "rotation": [
+ 0,
+ 0,
+ 0
+ ],
+ "scale": [
+ 1,
+ 1,
+ 1
+ ],
+ "deviceId": "EQ-002",
+ "modelId": "MODEL-EQUIPMENT-02",
+ "semantic": "实训装备",
+ "operable": true,
+ "capabilities": [
+ "select",
+ "inspect",
+ "transform"
+ ],
+ "assetCode": "",
+ "sourceAssetId": "",
+ "targetProjectId": "",
+ "targetVersionId": "",
+ "modelEditorAssetId": "",
+ "parts": []
+ },
+ {
+ "sceneId": "OBJ-1788605873331-3",
+ "type": "safety-zone",
+ "name": "安全作业区域 03",
+ "visible": true,
+ "position": [
+ 0,
+ 0.02,
+ 0
+ ],
+ "rotation": [
+ 0,
+ 0,
+ 0
+ ],
+ "scale": [
+ 1,
+ 1,
+ 1
+ ],
+ "deviceId": "EQ-003",
+ "modelId": "MODEL-SAFETY-ZONE-03",
+ "semantic": "安全区域",
+ "operable": false,
+ "capabilities": [
+ "select"
+ ],
+ "assetCode": "",
+ "sourceAssetId": "",
+ "targetProjectId": "",
+ "targetVersionId": "",
+ "modelEditorAssetId": "",
+ "parts": []
+ },
+ {
+ "sceneId": "OBJ-1788605873333-4",
+ "type": "light",
+ "name": "区域照明 04",
+ "visible": true,
+ "position": [
+ -12,
+ 0,
+ 8
+ ],
+ "rotation": [
+ 0,
+ 0,
+ 0
+ ],
+ "scale": [
+ 1,
+ 1,
+ 1
+ ],
+ "deviceId": "EQ-004",
+ "modelId": "MODEL-LIGHT-04",
+ "semantic": "实训装备",
+ "operable": false,
+ "capabilities": [
+ "select"
+ ],
+ "assetCode": "",
+ "sourceAssetId": "",
+ "targetProjectId": "",
+ "targetVersionId": "",
+ "modelEditorAssetId": "",
+ "parts": []
+ },
+ {
+ "sceneId": "OBJ-1788605916565-6",
+ "type": "imported",
+ "name": "狐狸野外巡检目标",
+ "visible": true,
+ "position": [
+ -7.5,
+ 0.10930174852949677,
+ 0.3440109341705351
+ ],
+ "rotation": [
+ 0,
+ 0,
+ 0
+ ],
+ "scale": [
+ 0.03616375932570189,
+ 0.03616375932570189,
+ 0.03616375932570189
+ ],
+ "deviceId": "SCENE-FOX-001",
+ "modelId": "MODEL-IMPORT-06",
+ "semantic": "实训装备",
+ "operable": true,
+ "capabilities": [
+ "select",
+ "inspect",
+ "transform"
+ ],
+ "assetCode": "MODEL_MTO6V5W6_09-FOX",
+ "sourceAssetId": "2030",
+ "targetProjectId": "157",
+ "targetVersionId": "201",
+ "modelEditorAssetId": "content-model-157-201",
+ "parts": [
+ {
+ "sceneId": "interaction-a3d35c26",
+ "name": "fox",
+ "visible": true,
+ "sceneDeleted": false,
+ "position": [
+ 0.25,
+ 0,
+ 0
+ ],
+ "rotation": [
+ 0,
+ 0,
+ 0
+ ],
+ "scale": [
+ 1,
+ 1,
+ 1
+ ],
+ "capabilities": [
+ "select",
+ "inspect"
+ ]
+ }
+ ]
+ },
+ {
+ "sceneId": "OBJ-1788605874053-5",
+ "type": "imported",
+ "name": "地形",
+ "visible": true,
+ "position": [
+ 3.069303533692093,
+ 4.802826448856971,
+ -3.074293867354786
+ ],
+ "rotation": [
+ 0,
+ 0,
+ 0
+ ],
+ "scale": [
+ 1.0050406774865042,
+ 1.0050406774865042,
+ 1.0050406774865042
+ ],
+ "deviceId": "EQ-005",
+ "modelId": "MODEL-IMPORT-05",
+ "semantic": "环境设施",
+ "operable": false,
+ "capabilities": [
+ "select"
+ ],
+ "assetCode": "",
+ "sourceAssetId": "",
+ "targetProjectId": "",
+ "targetVersionId": "",
+ "modelEditorAssetId": "",
+ "parts": []
+ }
+ ]
+ },
+ "directWriteStatus": 403
+ }
+}
\ No newline at end of file
diff --git a/reports/scene-editor-closure-20260906/evidence/real-recovery-conflict.json b/reports/scene-editor-closure-20260906/evidence/real-recovery-conflict.json
new file mode 100644
index 0000000..2a86d32
--- /dev/null
+++ b/reports/scene-editor-closure-20260906/evidence/real-recovery-conflict.json
@@ -0,0 +1,636 @@
+{
+ "time": "2026-09-06T10:26:49.585Z",
+ "data": {
+ "kind": "browser",
+ "status": "PASS",
+ "testTitle": "同版本恢复与 409:真实未保存草稿重载、服务器新版本保护与隔离下载",
+ "featureIds": [
+ "project.conflict",
+ "recovery.autobuffer",
+ "recovery.same-version",
+ "recovery.stale-version"
+ ],
+ "scope": "真实未存输入产生恢复稿,同版本恢复不自动入库;另一版本先保存后409拒绝覆盖,旧稿隔离下载。",
+ "projectId": "385",
+ "prompt": "发现版本冲突的恢复副本\n\n这个未提交副本基于旧的服务端版本,不能直接恢复或覆盖最新内容。可下载 JSON 后在场景编辑器中人工合并;关闭窗口会继续保留副本。\n\n删除副本并使用服务端\n下载冲突副本(保留)",
+ "originalVersion": 1,
+ "newVersion": 3,
+ "recovered": {
+ "mounted": true,
+ "hasEnvironment": true,
+ "hasEnvironmentTarget": true,
+ "toneMappingExposure": 1.05,
+ "background": "b9d5e6",
+ "meshCount": 37,
+ "materials": [
+ {
+ "name": "BaiSeNeiQiang",
+ "color": "eaeaea",
+ "metalness": 0,
+ "roughness": 0.5,
+ "envMapIntensity": 1,
+ "hasMap": false,
+ "preset": null,
+ "rule": null,
+ "authored": null
+ },
+ {
+ "name": "BoLi",
+ "color": "87c8fb",
+ "metalness": 0,
+ "roughness": 0.5,
+ "envMapIntensity": 1,
+ "hasMap": false,
+ "preset": null,
+ "rule": null,
+ "authored": null
+ },
+ {
+ "name": "HuiSe3",
+ "color": "8a8a8a",
+ "metalness": 0.43,
+ "roughness": 0.4,
+ "envMapIntensity": 1,
+ "hasMap": false,
+ "preset": null,
+ "rule": null,
+ "authored": null
+ },
+ {
+ "name": "HuiSe4",
+ "color": "c0c0c0",
+ "metalness": 0,
+ "roughness": 0.5,
+ "envMapIntensity": 1,
+ "hasMap": false,
+ "preset": null,
+ "rule": null,
+ "authored": null
+ },
+ {
+ "name": "JinShu",
+ "color": "555555",
+ "metalness": 0.44,
+ "roughness": 0.5,
+ "envMapIntensity": 1,
+ "hasMap": false,
+ "preset": null,
+ "rule": null,
+ "authored": null
+ },
+ {
+ "name": "MenPai",
+ "color": "ffffff",
+ "metalness": 0.68,
+ "roughness": 0.5,
+ "envMapIntensity": 1,
+ "hasMap": true,
+ "preset": null,
+ "rule": null,
+ "authored": null
+ },
+ {
+ "name": "WaiQiang",
+ "color": "efefef",
+ "metalness": 0,
+ "roughness": 0.5,
+ "envMapIntensity": 1,
+ "hasMap": true,
+ "preset": null,
+ "rule": null,
+ "authored": null
+ },
+ {
+ "name": "baiSe",
+ "color": "eaeaea",
+ "metalness": 0,
+ "roughness": 0.5,
+ "envMapIntensity": 1,
+ "hasMap": false,
+ "preset": null,
+ "rule": null,
+ "authored": null
+ },
+ {
+ "name": "HZhuan",
+ "color": "ffffff",
+ "metalness": 0,
+ "roughness": 0.5,
+ "envMapIntensity": 1,
+ "hasMap": true,
+ "preset": null,
+ "rule": null,
+ "authored": null
+ },
+ {
+ "name": "DiMian",
+ "color": "d2d2d2",
+ "metalness": 0.6,
+ "roughness": 0.2,
+ "envMapIntensity": 1,
+ "hasMap": true,
+ "preset": null,
+ "rule": null,
+ "authored": null
+ },
+ {
+ "name": "HuangSe",
+ "color": "d0af42",
+ "metalness": 0,
+ "roughness": 0.5,
+ "envMapIntensity": 1,
+ "hasMap": false,
+ "preset": null,
+ "rule": null,
+ "authored": null
+ },
+ {
+ "name": "HuangSe2",
+ "color": "b29b87",
+ "metalness": 0,
+ "roughness": 0.5,
+ "envMapIntensity": 1,
+ "hasMap": false,
+ "preset": null,
+ "rule": null,
+ "authored": null
+ },
+ {
+ "name": "HuangSe3",
+ "color": "aea791",
+ "metalness": 0,
+ "roughness": 0.5,
+ "envMapIntensity": 1,
+ "hasMap": false,
+ "preset": null,
+ "rule": null,
+ "authored": null
+ },
+ {
+ "name": "HuiSe",
+ "color": "555555",
+ "metalness": 0.82,
+ "roughness": 0.15,
+ "envMapIntensity": 1,
+ "hasMap": false,
+ "preset": null,
+ "rule": null,
+ "authored": null
+ },
+ {
+ "name": "HuiSe2",
+ "color": "9a9ca4",
+ "metalness": 0,
+ "roughness": 1,
+ "envMapIntensity": 1,
+ "hasMap": false,
+ "preset": null,
+ "rule": null,
+ "authored": null
+ },
+ {
+ "name": "NeiQiangBaiSe",
+ "color": "bfbfbf",
+ "metalness": 0,
+ "roughness": 0.5,
+ "envMapIntensity": 1,
+ "hasMap": false,
+ "preset": null,
+ "rule": null,
+ "authored": null
+ },
+ {
+ "name": "adskMatHuiSe",
+ "color": "797979",
+ "metalness": 0.43,
+ "roughness": 0.4,
+ "envMapIntensity": 1,
+ "hasMap": false,
+ "preset": null,
+ "rule": null,
+ "authored": null
+ },
+ {
+ "name": "truck",
+ "color": "ffffff",
+ "metalness": 0,
+ "roughness": 0.3,
+ "envMapIntensity": 1,
+ "hasMap": true,
+ "preset": null,
+ "rule": null,
+ "authored": null
+ },
+ {
+ "name": "glass",
+ "color": "003928",
+ "metalness": 0,
+ "roughness": 1,
+ "envMapIntensity": 1,
+ "hasMap": false,
+ "preset": null,
+ "rule": null,
+ "authored": null
+ },
+ {
+ "name": "window_trim",
+ "color": "484848",
+ "metalness": 0,
+ "roughness": 1,
+ "envMapIntensity": 1,
+ "hasMap": false,
+ "preset": null,
+ "rule": null,
+ "authored": null
+ },
+ {
+ "name": "wheels",
+ "color": "ffffff",
+ "metalness": 0,
+ "roughness": 1,
+ "envMapIntensity": 1,
+ "hasMap": true,
+ "preset": null,
+ "rule": null,
+ "authored": null
+ }
+ ],
+ "objectCount": 2,
+ "contentBounds": {
+ "min": [
+ -30,
+ 0,
+ -17.156
+ ],
+ "max": [
+ 30,
+ 14.579,
+ 17.156
+ ],
+ "size": [
+ 60,
+ 14.579,
+ 34.311
+ ]
+ },
+ "importedObjects": [
+ {
+ "name": "真实未保存卡车恢复",
+ "modelEditorModelId": "imported-model_mto6wi6k_08-cesiummilktruck",
+ "modelEditorAssetId": "content-model-156-204",
+ "deviceId": "SCENE-TRUCK-001",
+ "modelId": "MODEL-IMPORT-02",
+ "assetCode": "MODEL_MTO6WI6K_08-CESIUMMILKTRUCK",
+ "targetProjectId": "156",
+ "targetVersionId": "204"
+ }
+ ],
+ "apiControlled": true,
+ "readOnly": false,
+ "ready": true,
+ "readySettled": true,
+ "dirty": false,
+ "saving": false,
+ "publishing": false,
+ "pendingLoads": 0,
+ "paused": false,
+ "previewing": false,
+ "currentPreset": {
+ "id": "depot-1",
+ "name": "真实装备车库",
+ "type": "车间"
+ },
+ "cameraPosition": [
+ 10.565439077211714,
+ 5.0456876852867705,
+ 7.37050840519251
+ ],
+ "cameraTarget": [
+ 4.2,
+ 1.695456592017448,
+ 0
+ ],
+ "gridVisible": true,
+ "transformMode": "translate",
+ "selectedSceneId": "",
+ "canUndo": false,
+ "canRedo": false,
+ "objects": [
+ {
+ "sceneId": "OBJ-1788605149879-1",
+ "type": "imported",
+ "name": "车库",
+ "visible": true,
+ "position": [
+ 0,
+ 0.01461010548216679,
+ -4.4135855858939976e-8
+ ],
+ "rotation": [
+ 0,
+ 0,
+ 0
+ ],
+ "scale": [
+ 1.009710078119415,
+ 1.009710078119415,
+ 1.009710078119415
+ ],
+ "deviceId": "EQ-001",
+ "modelId": "MODEL-IMPORT-01",
+ "semantic": "环境设施",
+ "operable": false,
+ "capabilities": [
+ "select"
+ ],
+ "assetCode": "",
+ "sourceAssetId": "",
+ "targetProjectId": "",
+ "targetVersionId": "",
+ "modelEditorAssetId": "",
+ "parts": []
+ },
+ {
+ "sceneId": "OBJ-1788605199171-2",
+ "type": "imported",
+ "name": "真实未保存卡车恢复",
+ "visible": true,
+ "position": [
+ 4.2,
+ 0.20617168998977797,
+ -0.4356763541543951
+ ],
+ "rotation": [
+ 0,
+ 0,
+ 0
+ ],
+ "scale": [
+ 1.2323086149245381,
+ 1.2323086149245381,
+ 1.2323086149245381
+ ],
+ "deviceId": "SCENE-TRUCK-001",
+ "modelId": "MODEL-IMPORT-02",
+ "semantic": "实训装备",
+ "operable": true,
+ "capabilities": [
+ "select",
+ "inspect",
+ "transform"
+ ],
+ "assetCode": "MODEL_MTO6WI6K_08-CESIUMMILKTRUCK",
+ "sourceAssetId": "2069",
+ "targetProjectId": "156",
+ "targetVersionId": "204",
+ "modelEditorAssetId": "content-model-156-204",
+ "parts": [
+ {
+ "sceneId": "interaction-1ba581e6",
+ "name": "Yup2Zup",
+ "visible": true,
+ "sceneDeleted": false,
+ "position": [
+ 0,
+ 0,
+ 0
+ ],
+ "rotation": [
+ 1.57079626719025,
+ 0,
+ 1.5707963267948966
+ ],
+ "scale": [
+ 1,
+ 1,
+ 1
+ ],
+ "capabilities": [
+ "select",
+ "inspect",
+ "transform",
+ "animate"
+ ]
+ },
+ {
+ "sceneId": "interaction-004dec41",
+ "name": "Cesium_Milk_Truck",
+ "visible": true,
+ "sceneDeleted": false,
+ "position": [
+ 0.35,
+ 0,
+ 0
+ ],
+ "rotation": [
+ 0,
+ 0,
+ 0
+ ],
+ "scale": [
+ 1,
+ 1,
+ 1
+ ],
+ "capabilities": [
+ "select",
+ "inspect",
+ "transform",
+ "animate"
+ ]
+ },
+ {
+ "sceneId": "interaction-5f7e4bee",
+ "name": "Cesium_Milk_Truck_1",
+ "visible": true,
+ "sceneDeleted": false,
+ "position": [
+ 0,
+ 0,
+ 0
+ ],
+ "rotation": [
+ 0,
+ 0,
+ 0
+ ],
+ "scale": [
+ 1,
+ 1,
+ 1
+ ],
+ "capabilities": [
+ "select",
+ "inspect",
+ "transform",
+ "animate"
+ ]
+ },
+ {
+ "sceneId": "interaction-607e4d81",
+ "name": "Cesium_Milk_Truck_2",
+ "visible": true,
+ "sceneDeleted": false,
+ "position": [
+ 0,
+ 0,
+ 0
+ ],
+ "rotation": [
+ 0,
+ 0,
+ 0
+ ],
+ "scale": [
+ 1,
+ 1,
+ 1
+ ],
+ "capabilities": [
+ "select",
+ "inspect",
+ "transform",
+ "animate"
+ ]
+ },
+ {
+ "sceneId": "interaction-5d7e48c8",
+ "name": "Cesium_Milk_Truck_3",
+ "visible": true,
+ "sceneDeleted": false,
+ "position": [
+ 0,
+ 0,
+ 0
+ ],
+ "rotation": [
+ 0,
+ 0,
+ 0
+ ],
+ "scale": [
+ 1,
+ 1,
+ 1
+ ],
+ "capabilities": [
+ "select",
+ "inspect",
+ "transform",
+ "animate"
+ ]
+ },
+ {
+ "sceneId": "interaction-5e7e4a5b",
+ "name": "Node",
+ "visible": true,
+ "sceneDeleted": false,
+ "position": [
+ 1.432669997215271,
+ 0,
+ -0.4277220070362091
+ ],
+ "rotation": [
+ 0,
+ 0,
+ 0
+ ],
+ "scale": [
+ 1,
+ 1,
+ 1
+ ],
+ "capabilities": [
+ "select",
+ "inspect",
+ "transform",
+ "animate"
+ ]
+ },
+ {
+ "sceneId": "interaction-ce2feac4",
+ "name": "Wheels",
+ "visible": true,
+ "sceneDeleted": false,
+ "position": [
+ 0,
+ 0,
+ 0
+ ],
+ "rotation": [
+ -3.141592653589793,
+ -0.603571861900947,
+ -3.141592653589793
+ ],
+ "scale": [
+ 1,
+ 1,
+ 1
+ ],
+ "capabilities": [
+ "select",
+ "inspect",
+ "transform",
+ "animate"
+ ]
+ },
+ {
+ "sceneId": "interaction-637e523a",
+ "name": "Node001",
+ "visible": true,
+ "sceneDeleted": false,
+ "position": [
+ -1.352329969406128,
+ 0,
+ -0.4277220070362091
+ ],
+ "rotation": [
+ 0,
+ 0,
+ 0
+ ],
+ "scale": [
+ 1,
+ 1,
+ 1
+ ],
+ "capabilities": [
+ "select",
+ "inspect",
+ "transform",
+ "animate"
+ ]
+ },
+ {
+ "sceneId": "interaction-f417f82d",
+ "name": "Wheels001",
+ "visible": true,
+ "sceneDeleted": false,
+ "position": [
+ 0,
+ 0,
+ 0
+ ],
+ "rotation": [
+ -3.141592653589793,
+ -0.603571861900947,
+ -3.141592653589793
+ ],
+ "scale": [
+ 1,
+ 1,
+ 1
+ ],
+ "capabilities": [
+ "select",
+ "inspect",
+ "transform",
+ "animate"
+ ]
+ }
+ ]
+ }
+ ]
+ }
+ }
+}
\ No newline at end of file
diff --git a/reports/scene-editor-closure-20260906/evidence/save-concurrency.json b/reports/scene-editor-closure-20260906/evidence/save-concurrency.json
new file mode 100644
index 0000000..407901b
--- /dev/null
+++ b/reports/scene-editor-closure-20260906/evidence/save-concurrency.json
@@ -0,0 +1,636 @@
+{
+ "time": "2026-09-06T10:24:27.147Z",
+ "data": {
+ "kind": "browser",
+ "status": "PASS",
+ "testTitle": "延迟保存快照:重复点击只提交一次,期间新编辑保留,旧恢复稿不会误报冲突",
+ "featureIds": [
+ "project.concurrent-save",
+ "project.save",
+ "project.save-key",
+ "recovery.autobuffer",
+ "recovery.same-version"
+ ],
+ "scope": "延迟真实PUT快照A、重复Ctrl+S不重复请求、期间编辑B仍dirty;再次保存刷新无旧恢复稿误报。",
+ "projectId": "374",
+ "firstVersion": 2,
+ "submissionsBeforeRetry": 1,
+ "reopened": {
+ "mounted": true,
+ "hasEnvironment": true,
+ "hasEnvironmentTarget": true,
+ "toneMappingExposure": 1.05,
+ "background": "b9d5e6",
+ "meshCount": 37,
+ "materials": [
+ {
+ "name": "BaiSeNeiQiang",
+ "color": "eaeaea",
+ "metalness": 0,
+ "roughness": 0.5,
+ "envMapIntensity": 1,
+ "hasMap": false,
+ "preset": null,
+ "rule": null,
+ "authored": null
+ },
+ {
+ "name": "BoLi",
+ "color": "87c8fb",
+ "metalness": 0,
+ "roughness": 0.5,
+ "envMapIntensity": 1,
+ "hasMap": false,
+ "preset": null,
+ "rule": null,
+ "authored": null
+ },
+ {
+ "name": "HuiSe3",
+ "color": "8a8a8a",
+ "metalness": 0.43,
+ "roughness": 0.4,
+ "envMapIntensity": 1,
+ "hasMap": false,
+ "preset": null,
+ "rule": null,
+ "authored": null
+ },
+ {
+ "name": "HuiSe4",
+ "color": "c0c0c0",
+ "metalness": 0,
+ "roughness": 0.5,
+ "envMapIntensity": 1,
+ "hasMap": false,
+ "preset": null,
+ "rule": null,
+ "authored": null
+ },
+ {
+ "name": "JinShu",
+ "color": "555555",
+ "metalness": 0.44,
+ "roughness": 0.5,
+ "envMapIntensity": 1,
+ "hasMap": false,
+ "preset": null,
+ "rule": null,
+ "authored": null
+ },
+ {
+ "name": "MenPai",
+ "color": "ffffff",
+ "metalness": 0.68,
+ "roughness": 0.5,
+ "envMapIntensity": 1,
+ "hasMap": true,
+ "preset": null,
+ "rule": null,
+ "authored": null
+ },
+ {
+ "name": "WaiQiang",
+ "color": "efefef",
+ "metalness": 0,
+ "roughness": 0.5,
+ "envMapIntensity": 1,
+ "hasMap": true,
+ "preset": null,
+ "rule": null,
+ "authored": null
+ },
+ {
+ "name": "baiSe",
+ "color": "eaeaea",
+ "metalness": 0,
+ "roughness": 0.5,
+ "envMapIntensity": 1,
+ "hasMap": false,
+ "preset": null,
+ "rule": null,
+ "authored": null
+ },
+ {
+ "name": "HZhuan",
+ "color": "ffffff",
+ "metalness": 0,
+ "roughness": 0.5,
+ "envMapIntensity": 1,
+ "hasMap": true,
+ "preset": null,
+ "rule": null,
+ "authored": null
+ },
+ {
+ "name": "DiMian",
+ "color": "d2d2d2",
+ "metalness": 0.6,
+ "roughness": 0.2,
+ "envMapIntensity": 1,
+ "hasMap": true,
+ "preset": null,
+ "rule": null,
+ "authored": null
+ },
+ {
+ "name": "HuangSe",
+ "color": "d0af42",
+ "metalness": 0,
+ "roughness": 0.5,
+ "envMapIntensity": 1,
+ "hasMap": false,
+ "preset": null,
+ "rule": null,
+ "authored": null
+ },
+ {
+ "name": "HuangSe2",
+ "color": "b29b87",
+ "metalness": 0,
+ "roughness": 0.5,
+ "envMapIntensity": 1,
+ "hasMap": false,
+ "preset": null,
+ "rule": null,
+ "authored": null
+ },
+ {
+ "name": "HuangSe3",
+ "color": "aea791",
+ "metalness": 0,
+ "roughness": 0.5,
+ "envMapIntensity": 1,
+ "hasMap": false,
+ "preset": null,
+ "rule": null,
+ "authored": null
+ },
+ {
+ "name": "HuiSe",
+ "color": "555555",
+ "metalness": 0.82,
+ "roughness": 0.15,
+ "envMapIntensity": 1,
+ "hasMap": false,
+ "preset": null,
+ "rule": null,
+ "authored": null
+ },
+ {
+ "name": "HuiSe2",
+ "color": "9a9ca4",
+ "metalness": 0,
+ "roughness": 1,
+ "envMapIntensity": 1,
+ "hasMap": false,
+ "preset": null,
+ "rule": null,
+ "authored": null
+ },
+ {
+ "name": "NeiQiangBaiSe",
+ "color": "bfbfbf",
+ "metalness": 0,
+ "roughness": 0.5,
+ "envMapIntensity": 1,
+ "hasMap": false,
+ "preset": null,
+ "rule": null,
+ "authored": null
+ },
+ {
+ "name": "adskMatHuiSe",
+ "color": "797979",
+ "metalness": 0.43,
+ "roughness": 0.4,
+ "envMapIntensity": 1,
+ "hasMap": false,
+ "preset": null,
+ "rule": null,
+ "authored": null
+ },
+ {
+ "name": "truck",
+ "color": "ffffff",
+ "metalness": 0,
+ "roughness": 0.3,
+ "envMapIntensity": 1,
+ "hasMap": true,
+ "preset": null,
+ "rule": null,
+ "authored": null
+ },
+ {
+ "name": "glass",
+ "color": "003928",
+ "metalness": 0,
+ "roughness": 1,
+ "envMapIntensity": 1,
+ "hasMap": false,
+ "preset": null,
+ "rule": null,
+ "authored": null
+ },
+ {
+ "name": "window_trim",
+ "color": "484848",
+ "metalness": 0,
+ "roughness": 1,
+ "envMapIntensity": 1,
+ "hasMap": false,
+ "preset": null,
+ "rule": null,
+ "authored": null
+ },
+ {
+ "name": "wheels",
+ "color": "ffffff",
+ "metalness": 0,
+ "roughness": 1,
+ "envMapIntensity": 1,
+ "hasMap": true,
+ "preset": null,
+ "rule": null,
+ "authored": null
+ }
+ ],
+ "objectCount": 2,
+ "contentBounds": {
+ "min": [
+ -30,
+ 0,
+ -17.156
+ ],
+ "max": [
+ 30,
+ 14.579,
+ 17.156
+ ],
+ "size": [
+ 60,
+ 14.579,
+ 34.311
+ ]
+ },
+ "importedObjects": [
+ {
+ "name": "提交之后的新修改B",
+ "modelEditorModelId": "imported-model_mto6wi6k_08-cesiummilktruck",
+ "modelEditorAssetId": "content-model-156-204",
+ "deviceId": "SCENE-TRUCK-001",
+ "modelId": "MODEL-IMPORT-02",
+ "assetCode": "MODEL_MTO6WI6K_08-CESIUMMILKTRUCK",
+ "targetProjectId": "156",
+ "targetVersionId": "204"
+ }
+ ],
+ "apiControlled": true,
+ "readOnly": false,
+ "ready": true,
+ "readySettled": true,
+ "dirty": false,
+ "saving": false,
+ "publishing": false,
+ "pendingLoads": 0,
+ "paused": false,
+ "previewing": false,
+ "currentPreset": {
+ "id": "depot-1",
+ "name": "真实装备车库",
+ "type": "车间"
+ },
+ "cameraPosition": [
+ 10.565439077211714,
+ 5.0456876852867705,
+ 7.37050840519251
+ ],
+ "cameraTarget": [
+ 4.2,
+ 1.695456592017448,
+ 0
+ ],
+ "gridVisible": true,
+ "transformMode": "translate",
+ "selectedSceneId": "",
+ "canUndo": false,
+ "canRedo": false,
+ "objects": [
+ {
+ "sceneId": "OBJ-1788605149879-1",
+ "type": "imported",
+ "name": "车库",
+ "visible": true,
+ "position": [
+ 0,
+ 0.01461010548216679,
+ -4.4135855858939976e-8
+ ],
+ "rotation": [
+ 0,
+ 0,
+ 0
+ ],
+ "scale": [
+ 1.009710078119415,
+ 1.009710078119415,
+ 1.009710078119415
+ ],
+ "deviceId": "EQ-001",
+ "modelId": "MODEL-IMPORT-01",
+ "semantic": "环境设施",
+ "operable": false,
+ "capabilities": [
+ "select"
+ ],
+ "assetCode": "",
+ "sourceAssetId": "",
+ "targetProjectId": "",
+ "targetVersionId": "",
+ "modelEditorAssetId": "",
+ "parts": []
+ },
+ {
+ "sceneId": "OBJ-1788605199171-2",
+ "type": "imported",
+ "name": "提交之后的新修改B",
+ "visible": true,
+ "position": [
+ 4.2,
+ 0.20617168998977797,
+ -0.4356763541543951
+ ],
+ "rotation": [
+ 0,
+ 0,
+ 0
+ ],
+ "scale": [
+ 1.2323086149245381,
+ 1.2323086149245381,
+ 1.2323086149245381
+ ],
+ "deviceId": "SCENE-TRUCK-001",
+ "modelId": "MODEL-IMPORT-02",
+ "semantic": "实训装备",
+ "operable": true,
+ "capabilities": [
+ "select",
+ "inspect",
+ "transform"
+ ],
+ "assetCode": "MODEL_MTO6WI6K_08-CESIUMMILKTRUCK",
+ "sourceAssetId": "2069",
+ "targetProjectId": "156",
+ "targetVersionId": "204",
+ "modelEditorAssetId": "content-model-156-204",
+ "parts": [
+ {
+ "sceneId": "interaction-1ba581e6",
+ "name": "Yup2Zup",
+ "visible": true,
+ "sceneDeleted": false,
+ "position": [
+ 0,
+ 0,
+ 0
+ ],
+ "rotation": [
+ 1.57079626719025,
+ 0,
+ 1.5707963267948966
+ ],
+ "scale": [
+ 1,
+ 1,
+ 1
+ ],
+ "capabilities": [
+ "select",
+ "inspect",
+ "transform",
+ "animate"
+ ]
+ },
+ {
+ "sceneId": "interaction-004dec41",
+ "name": "Cesium_Milk_Truck",
+ "visible": true,
+ "sceneDeleted": false,
+ "position": [
+ 0.35,
+ 0,
+ 0
+ ],
+ "rotation": [
+ 0,
+ 0,
+ 0
+ ],
+ "scale": [
+ 1,
+ 1,
+ 1
+ ],
+ "capabilities": [
+ "select",
+ "inspect",
+ "transform",
+ "animate"
+ ]
+ },
+ {
+ "sceneId": "interaction-5f7e4bee",
+ "name": "Cesium_Milk_Truck_1",
+ "visible": true,
+ "sceneDeleted": false,
+ "position": [
+ 0,
+ 0,
+ 0
+ ],
+ "rotation": [
+ 0,
+ 0,
+ 0
+ ],
+ "scale": [
+ 1,
+ 1,
+ 1
+ ],
+ "capabilities": [
+ "select",
+ "inspect",
+ "transform",
+ "animate"
+ ]
+ },
+ {
+ "sceneId": "interaction-607e4d81",
+ "name": "Cesium_Milk_Truck_2",
+ "visible": true,
+ "sceneDeleted": false,
+ "position": [
+ 0,
+ 0,
+ 0
+ ],
+ "rotation": [
+ 0,
+ 0,
+ 0
+ ],
+ "scale": [
+ 1,
+ 1,
+ 1
+ ],
+ "capabilities": [
+ "select",
+ "inspect",
+ "transform",
+ "animate"
+ ]
+ },
+ {
+ "sceneId": "interaction-5d7e48c8",
+ "name": "Cesium_Milk_Truck_3",
+ "visible": true,
+ "sceneDeleted": false,
+ "position": [
+ 0,
+ 0,
+ 0
+ ],
+ "rotation": [
+ 0,
+ 0,
+ 0
+ ],
+ "scale": [
+ 1,
+ 1,
+ 1
+ ],
+ "capabilities": [
+ "select",
+ "inspect",
+ "transform",
+ "animate"
+ ]
+ },
+ {
+ "sceneId": "interaction-5e7e4a5b",
+ "name": "Node",
+ "visible": true,
+ "sceneDeleted": false,
+ "position": [
+ 1.432669997215271,
+ 0,
+ -0.4277220070362091
+ ],
+ "rotation": [
+ 0,
+ 0,
+ 0
+ ],
+ "scale": [
+ 1,
+ 1,
+ 1
+ ],
+ "capabilities": [
+ "select",
+ "inspect",
+ "transform",
+ "animate"
+ ]
+ },
+ {
+ "sceneId": "interaction-ce2feac4",
+ "name": "Wheels",
+ "visible": true,
+ "sceneDeleted": false,
+ "position": [
+ 0,
+ 0,
+ 0
+ ],
+ "rotation": [
+ -3.141592653589793,
+ -0.603571861900947,
+ -3.141592653589793
+ ],
+ "scale": [
+ 1,
+ 1,
+ 1
+ ],
+ "capabilities": [
+ "select",
+ "inspect",
+ "transform",
+ "animate"
+ ]
+ },
+ {
+ "sceneId": "interaction-637e523a",
+ "name": "Node001",
+ "visible": true,
+ "sceneDeleted": false,
+ "position": [
+ -1.352329969406128,
+ 0,
+ -0.4277220070362091
+ ],
+ "rotation": [
+ 0,
+ 0,
+ 0
+ ],
+ "scale": [
+ 1,
+ 1,
+ 1
+ ],
+ "capabilities": [
+ "select",
+ "inspect",
+ "transform",
+ "animate"
+ ]
+ },
+ {
+ "sceneId": "interaction-f417f82d",
+ "name": "Wheels001",
+ "visible": true,
+ "sceneDeleted": false,
+ "position": [
+ 0,
+ 0,
+ 0
+ ],
+ "rotation": [
+ -3.141592653589793,
+ -0.603571861900947,
+ -3.141592653589793
+ ],
+ "scale": [
+ 1,
+ 1,
+ 1
+ ],
+ "capabilities": [
+ "select",
+ "inspect",
+ "transform",
+ "animate"
+ ]
+ }
+ ]
+ }
+ ]
+ }
+ }
+}
\ No newline at end of file
diff --git a/reports/scene-editor-closure-20260906/evidence/save-cover-failure.json b/reports/scene-editor-closure-20260906/evidence/save-cover-failure.json
new file mode 100644
index 0000000..2b4478e
--- /dev/null
+++ b/reports/scene-editor-closure-20260906/evidence/save-cover-failure.json
@@ -0,0 +1,421 @@
+{
+ "time": "2026-09-06T10:26:33.388Z",
+ "data": {
+ "kind": "browser",
+ "status": "PASS",
+ "testTitle": "保存失败与封面降级:503 保留草稿,封面上传失败保留旧图,恢复后可重试",
+ "featureIds": [
+ "project.save-failure",
+ "cover.failure"
+ ],
+ "scope": "正文PUT503不清草稿;封面上传503保留旧图但正文可保存;恢复服务后保存刷新。截图异常和409另由聚焦合同验证。",
+ "projectId": "384",
+ "originalVersion": 1,
+ "degradedVersion": 2,
+ "coverPreserved": true,
+ "recovered": {
+ "mounted": true,
+ "hasEnvironment": true,
+ "hasEnvironmentTarget": true,
+ "toneMappingExposure": 1.05,
+ "background": "a8cbe1",
+ "meshCount": 15,
+ "materials": [
+ {
+ "name": "(未命名)",
+ "color": "445e2d",
+ "metalness": 0.35,
+ "roughness": 0.55,
+ "envMapIntensity": 1,
+ "hasMap": false,
+ "preset": null,
+ "rule": null,
+ "authored": null
+ },
+ {
+ "name": "(未命名)",
+ "color": "182c36",
+ "metalness": 0.55,
+ "roughness": 0.34,
+ "envMapIntensity": 1,
+ "hasMap": false,
+ "preset": null,
+ "rule": null,
+ "authored": null
+ },
+ {
+ "name": "(未命名)",
+ "color": "1e4f69",
+ "metalness": 0.45,
+ "roughness": 0.42,
+ "envMapIntensity": 1,
+ "hasMap": false,
+ "preset": null,
+ "rule": null,
+ "authored": null
+ },
+ {
+ "name": "(未命名)",
+ "color": "36b37e",
+ "metalness": 0,
+ "roughness": 1,
+ "envMapIntensity": 1,
+ "hasMap": false,
+ "preset": null,
+ "rule": null,
+ "authored": null
+ },
+ {
+ "name": "(未命名)",
+ "color": "182c36",
+ "metalness": 0.55,
+ "roughness": 0.34,
+ "envMapIntensity": 1,
+ "hasMap": false,
+ "preset": null,
+ "rule": null,
+ "authored": null
+ },
+ {
+ "name": "(未命名)",
+ "color": "e4a62b",
+ "metalness": 0.25,
+ "roughness": 0.48,
+ "envMapIntensity": 1,
+ "hasMap": false,
+ "preset": null,
+ "rule": null,
+ "authored": null
+ },
+ {
+ "name": "fox_material",
+ "color": "ffffff",
+ "metalness": 0,
+ "roughness": 0.3,
+ "envMapIntensity": 1,
+ "hasMap": true,
+ "preset": null,
+ "rule": null,
+ "authored": null
+ },
+ {
+ "name": "M_3dfreehub_mat_2",
+ "color": "ffffff",
+ "metalness": 0,
+ "roughness": 0.5,
+ "envMapIntensity": 1,
+ "hasMap": true,
+ "preset": null,
+ "rule": null,
+ "authored": null
+ },
+ {
+ "name": "M_3dfreehub_mat_3",
+ "color": "ffffff",
+ "metalness": 0,
+ "roughness": 0.5,
+ "envMapIntensity": 1,
+ "hasMap": true,
+ "preset": null,
+ "rule": null,
+ "authored": null
+ },
+ {
+ "name": "M_3dfreehub_mat_4",
+ "color": "ffffff",
+ "metalness": 0,
+ "roughness": 0.5,
+ "envMapIntensity": 1,
+ "hasMap": true,
+ "preset": null,
+ "rule": null,
+ "authored": null
+ },
+ {
+ "name": "M_3dfreehub_mat_5",
+ "color": "ffffff",
+ "metalness": 0,
+ "roughness": 0.5,
+ "envMapIntensity": 1,
+ "hasMap": true,
+ "preset": null,
+ "rule": null,
+ "authored": null
+ },
+ {
+ "name": "Material__85",
+ "color": "695c5b",
+ "metalness": 0,
+ "roughness": 0.5,
+ "envMapIntensity": 1,
+ "hasMap": false,
+ "preset": null,
+ "rule": null,
+ "authored": null
+ }
+ ],
+ "objectCount": 5,
+ "contentBounds": {
+ "min": [
+ -41,
+ -5.239,
+ -34.028
+ ],
+ "max": [
+ 41,
+ 11.358,
+ 34.028
+ ],
+ "size": [
+ 82,
+ 16.596,
+ 68.057
+ ]
+ },
+ "importedObjects": [
+ {
+ "name": "网络恢复后的狐狸",
+ "modelEditorModelId": "imported-model_mto6v5w6_09-fox",
+ "modelEditorAssetId": "content-model-157-201",
+ "deviceId": "SCENE-FOX-001",
+ "modelId": "MODEL-IMPORT-06",
+ "assetCode": "MODEL_MTO6V5W6_09-FOX",
+ "targetProjectId": "157",
+ "targetVersionId": "201"
+ }
+ ],
+ "apiControlled": true,
+ "readOnly": false,
+ "ready": true,
+ "readySettled": true,
+ "dirty": false,
+ "saving": false,
+ "publishing": false,
+ "pendingLoads": 0,
+ "paused": false,
+ "previewing": false,
+ "currentPreset": {
+ "id": "workshop-real",
+ "name": "演示维修车间(可拆分)",
+ "type": "车间"
+ },
+ "cameraPosition": [
+ -0.04179225073787585,
+ 6.099634646023559,
+ 17.355036987378313
+ ],
+ "cameraTarget": [
+ -7.512232860974557,
+ 1.4059499759307126,
+ -2.2204460492503126e-16
+ ],
+ "gridVisible": true,
+ "transformMode": "translate",
+ "selectedSceneId": "",
+ "canUndo": false,
+ "canRedo": false,
+ "objects": [
+ {
+ "sceneId": "OBJ-1788605873329-2",
+ "type": "equipment",
+ "name": "维修装备主体 02",
+ "visible": true,
+ "position": [
+ 0,
+ 0,
+ 0
+ ],
+ "rotation": [
+ 0,
+ 0,
+ 0
+ ],
+ "scale": [
+ 1,
+ 1,
+ 1
+ ],
+ "deviceId": "EQ-002",
+ "modelId": "MODEL-EQUIPMENT-02",
+ "semantic": "实训装备",
+ "operable": true,
+ "capabilities": [
+ "select",
+ "inspect",
+ "transform"
+ ],
+ "assetCode": "",
+ "sourceAssetId": "",
+ "targetProjectId": "",
+ "targetVersionId": "",
+ "modelEditorAssetId": "",
+ "parts": []
+ },
+ {
+ "sceneId": "OBJ-1788605873331-3",
+ "type": "safety-zone",
+ "name": "安全作业区域 03",
+ "visible": true,
+ "position": [
+ 0,
+ 0.02,
+ 0
+ ],
+ "rotation": [
+ 0,
+ 0,
+ 0
+ ],
+ "scale": [
+ 1,
+ 1,
+ 1
+ ],
+ "deviceId": "EQ-003",
+ "modelId": "MODEL-SAFETY-ZONE-03",
+ "semantic": "安全区域",
+ "operable": false,
+ "capabilities": [
+ "select"
+ ],
+ "assetCode": "",
+ "sourceAssetId": "",
+ "targetProjectId": "",
+ "targetVersionId": "",
+ "modelEditorAssetId": "",
+ "parts": []
+ },
+ {
+ "sceneId": "OBJ-1788605873333-4",
+ "type": "light",
+ "name": "区域照明 04",
+ "visible": true,
+ "position": [
+ -12,
+ 0,
+ 8
+ ],
+ "rotation": [
+ 0,
+ 0,
+ 0
+ ],
+ "scale": [
+ 1,
+ 1,
+ 1
+ ],
+ "deviceId": "EQ-004",
+ "modelId": "MODEL-LIGHT-04",
+ "semantic": "实训装备",
+ "operable": false,
+ "capabilities": [
+ "select"
+ ],
+ "assetCode": "",
+ "sourceAssetId": "",
+ "targetProjectId": "",
+ "targetVersionId": "",
+ "modelEditorAssetId": "",
+ "parts": []
+ },
+ {
+ "sceneId": "OBJ-1788605916565-6",
+ "type": "imported",
+ "name": "网络恢复后的狐狸",
+ "visible": true,
+ "position": [
+ -7.5,
+ 0.10930174852949677,
+ 0.3440109341705351
+ ],
+ "rotation": [
+ 0,
+ 0,
+ 0
+ ],
+ "scale": [
+ 0.03616375932570189,
+ 0.03616375932570189,
+ 0.03616375932570189
+ ],
+ "deviceId": "SCENE-FOX-001",
+ "modelId": "MODEL-IMPORT-06",
+ "semantic": "实训装备",
+ "operable": true,
+ "capabilities": [
+ "select",
+ "inspect",
+ "transform"
+ ],
+ "assetCode": "MODEL_MTO6V5W6_09-FOX",
+ "sourceAssetId": "2030",
+ "targetProjectId": "157",
+ "targetVersionId": "201",
+ "modelEditorAssetId": "content-model-157-201",
+ "parts": [
+ {
+ "sceneId": "interaction-a3d35c26",
+ "name": "fox",
+ "visible": true,
+ "sceneDeleted": false,
+ "position": [
+ 0.25,
+ 0,
+ 0
+ ],
+ "rotation": [
+ 0,
+ 0,
+ 0
+ ],
+ "scale": [
+ 1,
+ 1,
+ 1
+ ],
+ "capabilities": [
+ "select",
+ "inspect"
+ ]
+ }
+ ]
+ },
+ {
+ "sceneId": "OBJ-1788605874053-5",
+ "type": "imported",
+ "name": "地形",
+ "visible": true,
+ "position": [
+ 3.069303533692093,
+ 4.802826448856971,
+ -3.074293867354786
+ ],
+ "rotation": [
+ 0,
+ 0,
+ 0
+ ],
+ "scale": [
+ 1.0050406774865042,
+ 1.0050406774865042,
+ 1.0050406774865042
+ ],
+ "deviceId": "EQ-005",
+ "modelId": "MODEL-IMPORT-05",
+ "semantic": "环境设施",
+ "operable": false,
+ "capabilities": [
+ "select"
+ ],
+ "assetCode": "",
+ "sourceAssetId": "",
+ "targetProjectId": "",
+ "targetVersionId": "",
+ "modelEditorAssetId": "",
+ "parts": []
+ }
+ ]
+ }
+ }
+}
\ No newline at end of file
diff --git a/reports/scene-editor-closure-20260906/evidence/scene-multi-session.json b/reports/scene-editor-closure-20260906/evidence/scene-multi-session.json
new file mode 100644
index 0000000..0034057
--- /dev/null
+++ b/reports/scene-editor-closure-20260906/evidence/scene-multi-session.json
@@ -0,0 +1,20 @@
+{
+ "time": "2026-09-06T10:25:56.822Z",
+ "data": {
+ "kind": "browser",
+ "status": "PASS",
+ "testTitle": "双标签各自修改场景:保存和明确丢弃当前页均保留另一页真实恢复稿",
+ "featureIds": [
+ "recovery.multi-session",
+ "recovery.autobuffer",
+ "recovery.discard",
+ "project.save"
+ ],
+ "projectId": "382",
+ "savedName": "标签A已保存-382",
+ "otherDraftName": "标签B保持未保存-382",
+ "otherDraftVersion": 1,
+ "remainingRecoveryCount": 1,
+ "serverVersion": 2
+ }
+}
\ No newline at end of file
diff --git a/reports/scene-editor-closure-20260906/evidence/scene-no-page-permission.json b/reports/scene-editor-closure-20260906/evidence/scene-no-page-permission.json
new file mode 100644
index 0000000..eccfdc1
--- /dev/null
+++ b/reports/scene-editor-closure-20260906/evidence/scene-no-page-permission.json
@@ -0,0 +1,16 @@
+{
+ "time": "2026-09-06T10:25:24.402Z",
+ "data": {
+ "kind": "browser",
+ "status": "PASS",
+ "testTitle": "无场景页面权限:编辑路由和直接内容接口均拒绝访问",
+ "featureIds": [
+ "permission.publish"
+ ],
+ "permissionCodes": [
+ "dashboard.view"
+ ],
+ "route": "/dashboard",
+ "directReadStatus": 403
+ }
+}
\ No newline at end of file
diff --git a/reports/scene-editor-closure-20260906/evidence/scene-publish-only.json b/reports/scene-editor-closure-20260906/evidence/scene-publish-only.json
new file mode 100644
index 0000000..7cbf8c7
--- /dev/null
+++ b/reports/scene-editor-closure-20260906/evidence/scene-publish-only.json
@@ -0,0 +1,26 @@
+{
+ "time": "2026-09-06T10:25:19.519Z",
+ "data": {
+ "kind": "browser",
+ "status": "PASS",
+ "testTitle": "仅场景发布权限:浏览器发布已存副本,不发送保存或上传请求",
+ "featureIds": [
+ "permission.publish",
+ "publish.only",
+ "publish.fixed-model"
+ ],
+ "projectId": "380",
+ "permissionCodes": [
+ "dashboard.view",
+ "content.scene",
+ "content.scene.publish"
+ ],
+ "versionId": "434",
+ "writes": [
+ {
+ "method": "POST",
+ "path": "/api/tran/v1/content/projects/380/publish"
+ }
+ ]
+ }
+}
\ No newline at end of file
diff --git a/reports/scene-editor-closure-20260906/evidence/scene-role-switch.json b/reports/scene-editor-closure-20260906/evidence/scene-role-switch.json
new file mode 100644
index 0000000..ffeeed0
--- /dev/null
+++ b/reports/scene-editor-closure-20260906/evidence/scene-role-switch.json
@@ -0,0 +1,19 @@
+{
+ "time": "2026-09-06T10:25:39.438Z",
+ "data": {
+ "kind": "browser",
+ "status": "PASS",
+ "testTitle": "后台场景未保存时切换角色:关闭确认保留草稿,保存切换后重建只读权限",
+ "featureIds": [
+ "leave.role-switch",
+ "leave.cancel",
+ "leave.save",
+ "permission.readonly"
+ ],
+ "projectId": "381",
+ "savedName": "后台场景保存后切换-381",
+ "roleChangeRequests": 1,
+ "savedVersion": 2,
+ "readOnly": true
+ }
+}
\ No newline at end of file
diff --git a/reports/scene-editor-closure-20260906/evidence/scene-update-only.json b/reports/scene-editor-closure-20260906/evidence/scene-update-only.json
new file mode 100644
index 0000000..e595d06
--- /dev/null
+++ b/reports/scene-editor-closure-20260906/evidence/scene-update-only.json
@@ -0,0 +1,30 @@
+{
+ "time": "2026-09-06T10:25:11.741Z",
+ "data": {
+ "kind": "browser",
+ "status": "PASS",
+ "testTitle": "仅场景编辑权限:固定模型可加载和保存,不能发布或访问模型页面",
+ "featureIds": [
+ "permission.update",
+ "permission.publish",
+ "publish.fixed-model"
+ ],
+ "projectId": "379",
+ "permissionCodes": [
+ "dashboard.view",
+ "content.scene",
+ "content.scene.update"
+ ],
+ "dependencies": [
+ [
+ "157",
+ "201",
+ "SCENE_MODEL",
+ true
+ ]
+ ],
+ "savedName": "仅场景编辑权限保存-379",
+ "directModelReadStatus": 403,
+ "directPublishStatus": 403
+ }
+}
\ No newline at end of file
diff --git a/reports/scene-editor-closure-20260906/evidence/template-confirm.json b/reports/scene-editor-closure-20260906/evidence/template-confirm.json
new file mode 100644
index 0000000..8b20284
--- /dev/null
+++ b/reports/scene-editor-closure-20260906/evidence/template-confirm.json
@@ -0,0 +1,18 @@
+{
+ "time": "2026-09-06T10:28:42.077Z",
+ "data": {
+ "kind": "e2e",
+ "status": "PASS",
+ "testTitle": "模板搜索与未保存切换确认保留和放弃",
+ "featureIds": [
+ "panel.left-tabs",
+ "search.templates",
+ "template.switch-cancel"
+ ],
+ "projectId": "396",
+ "scope": "中文/无匹配/清空搜索;真实取消保留对象,真实确认放弃并切换空白模板后保存。",
+ "screenshots": [
+ "29-template-confirm-empty.png"
+ ]
+ }
+}
\ No newline at end of file
diff --git a/reports/scene-editor-closure-20260906/evidence/template-depot-1.json b/reports/scene-editor-closure-20260906/evidence/template-depot-1.json
new file mode 100644
index 0000000..ebbd896
--- /dev/null
+++ b/reports/scene-editor-closure-20260906/evidence/template-depot-1.json
@@ -0,0 +1,35 @@
+{
+ "time": "2026-09-06T10:27:14.151Z",
+ "data": {
+ "kind": "e2e",
+ "status": "PASS",
+ "testTitle": "场景模板实际加载、保存重开:真实装备车库",
+ "featureIds": [
+ "template.depot-1"
+ ],
+ "projectId": "387",
+ "presetId": "depot-1",
+ "objectCount": 1,
+ "bounds": {
+ "min": [
+ -30,
+ 0,
+ -17.156
+ ],
+ "max": [
+ 30,
+ 14.579,
+ 17.156
+ ],
+ "size": [
+ 60,
+ 14.579,
+ 34.311
+ ]
+ },
+ "scope": "真实模板按钮、选中标签、环境字段、种子对象、PUT 后刷新重开;截图验证对应模板实际画面。",
+ "screenshots": [
+ "20-template-depot-1.png"
+ ]
+ }
+}
\ No newline at end of file
diff --git a/reports/scene-editor-closure-20260906/evidence/template-outdoor-1.json b/reports/scene-editor-closure-20260906/evidence/template-outdoor-1.json
new file mode 100644
index 0000000..eeb6924
--- /dev/null
+++ b/reports/scene-editor-closure-20260906/evidence/template-outdoor-1.json
@@ -0,0 +1,35 @@
+{
+ "time": "2026-09-06T10:28:34.148Z",
+ "data": {
+ "kind": "e2e",
+ "status": "PASS",
+ "testTitle": "场景模板实际加载、保存重开:室外综合训练场",
+ "featureIds": [
+ "template.outdoor-1"
+ ],
+ "projectId": "395",
+ "presetId": "outdoor-1",
+ "objectCount": 3,
+ "bounds": {
+ "min": [
+ -8.2,
+ -0.07,
+ -2.5
+ ],
+ "max": [
+ 6.6,
+ 5.31,
+ 3.35
+ ],
+ "size": [
+ 14.8,
+ 5.38,
+ 5.85
+ ]
+ },
+ "scope": "真实模板按钮、选中标签、环境字段、种子对象、PUT 后刷新重开;截图验证对应模板实际画面。",
+ "screenshots": [
+ "28-template-outdoor-1.png"
+ ]
+ }
+}
\ No newline at end of file
diff --git a/reports/scene-editor-closure-20260906/evidence/template-terrain-1.json b/reports/scene-editor-closure-20260906/evidence/template-terrain-1.json
new file mode 100644
index 0000000..e3428b7
--- /dev/null
+++ b/reports/scene-editor-closure-20260906/evidence/template-terrain-1.json
@@ -0,0 +1,35 @@
+{
+ "time": "2026-09-06T10:27:24.419Z",
+ "data": {
+ "kind": "e2e",
+ "status": "PASS",
+ "testTitle": "场景模板实际加载、保存重开:真实野外训练场",
+ "featureIds": [
+ "template.terrain-1"
+ ],
+ "projectId": "388",
+ "presetId": "terrain-1",
+ "objectCount": 4,
+ "bounds": {
+ "min": [
+ -41,
+ -5.239,
+ -34.028
+ ],
+ "max": [
+ 41,
+ 11.358,
+ 34.028
+ ],
+ "size": [
+ 82,
+ 16.596,
+ 68.057
+ ]
+ },
+ "scope": "真实模板按钮、选中标签、环境字段、种子对象、PUT 后刷新重开;截图验证对应模板实际画面。",
+ "screenshots": [
+ "21-template-terrain-1.png"
+ ]
+ }
+}
\ No newline at end of file
diff --git a/reports/scene-editor-closure-20260906/evidence/template-workshop-1.json b/reports/scene-editor-closure-20260906/evidence/template-workshop-1.json
new file mode 100644
index 0000000..ad9b143
--- /dev/null
+++ b/reports/scene-editor-closure-20260906/evidence/template-workshop-1.json
@@ -0,0 +1,19 @@
+{
+ "time": "2026-09-06T10:27:33.434Z",
+ "data": {
+ "kind": "e2e",
+ "status": "PASS",
+ "testTitle": "场景模板实际加载、保存重开:综合检验车间",
+ "featureIds": [
+ "template.workshop-1"
+ ],
+ "projectId": "389",
+ "presetId": "workshop-1",
+ "objectCount": 0,
+ "bounds": null,
+ "scope": "真实模板按钮、选中标签、环境字段、种子对象、PUT 后刷新重开;截图验证对应模板实际画面。",
+ "screenshots": [
+ "22-template-workshop-1.png"
+ ]
+ }
+}
\ No newline at end of file
diff --git a/reports/scene-editor-closure-20260906/evidence/template-workshop-2.json b/reports/scene-editor-closure-20260906/evidence/template-workshop-2.json
new file mode 100644
index 0000000..20cf006
--- /dev/null
+++ b/reports/scene-editor-closure-20260906/evidence/template-workshop-2.json
@@ -0,0 +1,35 @@
+{
+ "time": "2026-09-06T10:27:59.059Z",
+ "data": {
+ "kind": "e2e",
+ "status": "PASS",
+ "testTitle": "场景模板实际加载、保存重开:总成拆卸车间",
+ "featureIds": [
+ "template.workshop-2"
+ ],
+ "projectId": "391",
+ "presetId": "workshop-2",
+ "objectCount": 3,
+ "bounds": {
+ "min": [
+ -8.9,
+ -0.07,
+ -5.625
+ ],
+ "max": [
+ 9.6,
+ 3.15,
+ 2.9
+ ],
+ "size": [
+ 18.5,
+ 3.22,
+ 8.525
+ ]
+ },
+ "scope": "真实模板按钮、选中标签、环境字段、种子对象、PUT 后刷新重开;截图验证对应模板实际画面。",
+ "screenshots": [
+ "24-template-workshop-2.png"
+ ]
+ }
+}
\ No newline at end of file
diff --git a/reports/scene-editor-closure-20260906/evidence/template-workshop-3.json b/reports/scene-editor-closure-20260906/evidence/template-workshop-3.json
new file mode 100644
index 0000000..26c61e3
--- /dev/null
+++ b/reports/scene-editor-closure-20260906/evidence/template-workshop-3.json
@@ -0,0 +1,35 @@
+{
+ "time": "2026-09-06T10:28:07.821Z",
+ "data": {
+ "kind": "e2e",
+ "status": "PASS",
+ "testTitle": "场景模板实际加载、保存重开:部件分解车间",
+ "featureIds": [
+ "template.workshop-3"
+ ],
+ "projectId": "392",
+ "presetId": "workshop-3",
+ "objectCount": 3,
+ "bounds": {
+ "min": [
+ -7.1,
+ 0,
+ -5.625
+ ],
+ "max": [
+ 8.6,
+ 4.575,
+ 0.9
+ ],
+ "size": [
+ 15.7,
+ 4.575,
+ 6.525
+ ]
+ },
+ "scope": "真实模板按钮、选中标签、环境字段、种子对象、PUT 后刷新重开;截图验证对应模板实际画面。",
+ "screenshots": [
+ "25-template-workshop-3.png"
+ ]
+ }
+}
\ No newline at end of file
diff --git a/reports/scene-editor-closure-20260906/evidence/template-workshop-4.json b/reports/scene-editor-closure-20260906/evidence/template-workshop-4.json
new file mode 100644
index 0000000..6c53046
--- /dev/null
+++ b/reports/scene-editor-closure-20260906/evidence/template-workshop-4.json
@@ -0,0 +1,35 @@
+{
+ "time": "2026-09-06T10:28:16.504Z",
+ "data": {
+ "kind": "e2e",
+ "status": "PASS",
+ "testTitle": "场景模板实际加载、保存重开:清洗检测车间",
+ "featureIds": [
+ "template.workshop-4"
+ ],
+ "projectId": "393",
+ "presetId": "workshop-4",
+ "objectCount": 4,
+ "bounds": {
+ "min": [
+ -7.9,
+ 0,
+ -5.275
+ ],
+ "max": [
+ 8.055,
+ 4.575,
+ 2.9
+ ],
+ "size": [
+ 15.955,
+ 4.575,
+ 8.175
+ ]
+ },
+ "scope": "真实模板按钮、选中标签、环境字段、种子对象、PUT 后刷新重开;截图验证对应模板实际画面。",
+ "screenshots": [
+ "26-template-workshop-4.png"
+ ]
+ }
+}
\ No newline at end of file
diff --git a/reports/scene-editor-closure-20260906/evidence/template-workshop-5.json b/reports/scene-editor-closure-20260906/evidence/template-workshop-5.json
new file mode 100644
index 0000000..1cec049
--- /dev/null
+++ b/reports/scene-editor-closure-20260906/evidence/template-workshop-5.json
@@ -0,0 +1,35 @@
+{
+ "time": "2026-09-06T10:28:25.537Z",
+ "data": {
+ "kind": "e2e",
+ "status": "PASS",
+ "testTitle": "场景模板实际加载、保存重开:复装调试车间",
+ "featureIds": [
+ "template.workshop-5"
+ ],
+ "projectId": "394",
+ "presetId": "workshop-5",
+ "objectCount": 4,
+ "bounds": {
+ "min": [
+ -8.9,
+ 0,
+ -4.35
+ ],
+ "max": [
+ 8.3,
+ 5.31,
+ 3.9
+ ],
+ "size": [
+ 17.2,
+ 5.31,
+ 8.25
+ ]
+ },
+ "scope": "真实模板按钮、选中标签、环境字段、种子对象、PUT 后刷新重开;截图验证对应模板实际画面。",
+ "screenshots": [
+ "27-template-workshop-5.png"
+ ]
+ }
+}
\ No newline at end of file
diff --git a/reports/scene-editor-closure-20260906/evidence/template-workshop-real.json b/reports/scene-editor-closure-20260906/evidence/template-workshop-real.json
new file mode 100644
index 0000000..1bb1df0
--- /dev/null
+++ b/reports/scene-editor-closure-20260906/evidence/template-workshop-real.json
@@ -0,0 +1,35 @@
+{
+ "time": "2026-09-06T10:27:50.202Z",
+ "data": {
+ "kind": "e2e",
+ "status": "PASS",
+ "testTitle": "场景模板实际加载、保存重开:演示维修车间(可拆分)",
+ "featureIds": [
+ "template.workshop-real"
+ ],
+ "projectId": "390",
+ "presetId": "workshop-real",
+ "objectCount": 74,
+ "bounds": {
+ "min": [
+ -9.424,
+ 0,
+ -14
+ ],
+ "max": [
+ 9.424,
+ 6.32,
+ 14
+ ],
+ "size": [
+ 18.848,
+ 6.32,
+ 28
+ ]
+ },
+ "scope": "真实模板按钮、选中标签、环境字段、种子对象、PUT 后刷新重开;截图验证对应模板实际画面。",
+ "screenshots": [
+ "23-template-workshop-real.png"
+ ]
+ }
+}
\ No newline at end of file
diff --git a/reports/scene-editor-closure-20260906/evidence/truck-part-history.json b/reports/scene-editor-closure-20260906/evidence/truck-part-history.json
new file mode 100644
index 0000000..005894f
--- /dev/null
+++ b/reports/scene-editor-closure-20260906/evidence/truck-part-history.json
@@ -0,0 +1,2743 @@
+{
+ "time": "2026-09-06T10:30:32.596Z",
+ "data": {
+ "kind": "browser",
+ "status": "PASS",
+ "testTitle": "牛奶卡车:刚性部件独立变换、软删除、历史与保存基线一致",
+ "featureIds": [
+ "project.save-key",
+ "part.transform",
+ "part.soft-delete",
+ "history.undo",
+ "history.redo",
+ "property.name",
+ "view.enclosure-full",
+ "view.enclosure-xray",
+ "view.enclosure-hidden"
+ ],
+ "scope": "实际部件变换和软删除、按钮/键盘历史、保存基线与输入框自身撤销;双实例隔离及车库围护三种模式真实点击、导出和隐藏后保存刷新。",
+ "projectId": "404",
+ "partId": "interaction-1ba581e6",
+ "initial": {
+ "mounted": true,
+ "hasEnvironment": true,
+ "hasEnvironmentTarget": true,
+ "toneMappingExposure": 1.05,
+ "background": "b9d5e6",
+ "meshCount": 42,
+ "materials": [
+ {
+ "name": "BaiSeNeiQiang",
+ "color": "eaeaea",
+ "metalness": 0,
+ "roughness": 0.5,
+ "envMapIntensity": 1,
+ "hasMap": false,
+ "preset": null,
+ "rule": null,
+ "authored": null
+ },
+ {
+ "name": "BoLi",
+ "color": "87c8fb",
+ "metalness": 0,
+ "roughness": 0.5,
+ "envMapIntensity": 1,
+ "hasMap": false,
+ "preset": null,
+ "rule": null,
+ "authored": null
+ },
+ {
+ "name": "HuiSe3",
+ "color": "8a8a8a",
+ "metalness": 0.43,
+ "roughness": 0.4,
+ "envMapIntensity": 1,
+ "hasMap": false,
+ "preset": null,
+ "rule": null,
+ "authored": null
+ },
+ {
+ "name": "HuiSe4",
+ "color": "c0c0c0",
+ "metalness": 0,
+ "roughness": 0.5,
+ "envMapIntensity": 1,
+ "hasMap": false,
+ "preset": null,
+ "rule": null,
+ "authored": null
+ },
+ {
+ "name": "JinShu",
+ "color": "555555",
+ "metalness": 0.44,
+ "roughness": 0.5,
+ "envMapIntensity": 1,
+ "hasMap": false,
+ "preset": null,
+ "rule": null,
+ "authored": null
+ },
+ {
+ "name": "MenPai",
+ "color": "ffffff",
+ "metalness": 0.68,
+ "roughness": 0.5,
+ "envMapIntensity": 1,
+ "hasMap": true,
+ "preset": null,
+ "rule": null,
+ "authored": null
+ },
+ {
+ "name": "WaiQiang",
+ "color": "efefef",
+ "metalness": 0,
+ "roughness": 0.5,
+ "envMapIntensity": 1,
+ "hasMap": true,
+ "preset": null,
+ "rule": null,
+ "authored": null
+ },
+ {
+ "name": "baiSe",
+ "color": "eaeaea",
+ "metalness": 0,
+ "roughness": 0.5,
+ "envMapIntensity": 1,
+ "hasMap": false,
+ "preset": null,
+ "rule": null,
+ "authored": null
+ },
+ {
+ "name": "HZhuan",
+ "color": "ffffff",
+ "metalness": 0,
+ "roughness": 0.5,
+ "envMapIntensity": 1,
+ "hasMap": true,
+ "preset": null,
+ "rule": null,
+ "authored": null
+ },
+ {
+ "name": "DiMian",
+ "color": "d2d2d2",
+ "metalness": 0.6,
+ "roughness": 0.2,
+ "envMapIntensity": 1,
+ "hasMap": true,
+ "preset": null,
+ "rule": null,
+ "authored": null
+ },
+ {
+ "name": "HuangSe",
+ "color": "d0af42",
+ "metalness": 0,
+ "roughness": 0.5,
+ "envMapIntensity": 1,
+ "hasMap": false,
+ "preset": null,
+ "rule": null,
+ "authored": null
+ },
+ {
+ "name": "HuangSe2",
+ "color": "b29b87",
+ "metalness": 0,
+ "roughness": 0.5,
+ "envMapIntensity": 1,
+ "hasMap": false,
+ "preset": null,
+ "rule": null,
+ "authored": null
+ },
+ {
+ "name": "HuangSe3",
+ "color": "aea791",
+ "metalness": 0,
+ "roughness": 0.5,
+ "envMapIntensity": 1,
+ "hasMap": false,
+ "preset": null,
+ "rule": null,
+ "authored": null
+ },
+ {
+ "name": "HuiSe",
+ "color": "555555",
+ "metalness": 0.82,
+ "roughness": 0.15,
+ "envMapIntensity": 1,
+ "hasMap": false,
+ "preset": null,
+ "rule": null,
+ "authored": null
+ },
+ {
+ "name": "HuiSe2",
+ "color": "9a9ca4",
+ "metalness": 0,
+ "roughness": 1,
+ "envMapIntensity": 1,
+ "hasMap": false,
+ "preset": null,
+ "rule": null,
+ "authored": null
+ },
+ {
+ "name": "NeiQiangBaiSe",
+ "color": "bfbfbf",
+ "metalness": 0,
+ "roughness": 0.5,
+ "envMapIntensity": 1,
+ "hasMap": false,
+ "preset": null,
+ "rule": null,
+ "authored": null
+ },
+ {
+ "name": "adskMatHuiSe",
+ "color": "797979",
+ "metalness": 0.43,
+ "roughness": 0.4,
+ "envMapIntensity": 1,
+ "hasMap": false,
+ "preset": null,
+ "rule": null,
+ "authored": null
+ },
+ {
+ "name": "truck",
+ "color": "ffffff",
+ "metalness": 0,
+ "roughness": 0.3,
+ "envMapIntensity": 1,
+ "hasMap": true,
+ "preset": null,
+ "rule": null,
+ "authored": null
+ },
+ {
+ "name": "glass",
+ "color": "003928",
+ "metalness": 0,
+ "roughness": 1,
+ "envMapIntensity": 1,
+ "hasMap": false,
+ "preset": null,
+ "rule": null,
+ "authored": null
+ },
+ {
+ "name": "window_trim",
+ "color": "484848",
+ "metalness": 0,
+ "roughness": 1,
+ "envMapIntensity": 1,
+ "hasMap": false,
+ "preset": null,
+ "rule": null,
+ "authored": null
+ },
+ {
+ "name": "wheels",
+ "color": "ffffff",
+ "metalness": 0,
+ "roughness": 1,
+ "envMapIntensity": 1,
+ "hasMap": true,
+ "preset": null,
+ "rule": null,
+ "authored": null
+ }
+ ],
+ "objectCount": 3,
+ "contentBounds": {
+ "min": [
+ -30,
+ 0,
+ -17.156
+ ],
+ "max": [
+ 30,
+ 14.579,
+ 17.156
+ ],
+ "size": [
+ 60,
+ 14.579,
+ 34.311
+ ]
+ },
+ "importedObjects": [
+ {
+ "name": "牛奶卡车检验主装备",
+ "modelEditorModelId": "imported-model_mto6wi6k_08-cesiummilktruck",
+ "modelEditorAssetId": "content-model-156-204",
+ "deviceId": "SCENE-TRUCK-001",
+ "modelId": "MODEL-IMPORT-02",
+ "assetCode": "MODEL_MTO6WI6K_08-CESIUMMILKTRUCK",
+ "targetProjectId": "156",
+ "targetVersionId": "204"
+ },
+ {
+ "name": "实测模型-牛奶卡车-已验证-20260905",
+ "modelEditorModelId": "imported-model_mto6wi6k_08-cesiummilktruck",
+ "modelEditorAssetId": "content-model-156-204",
+ "deviceId": "EQ-003",
+ "modelId": "MODEL-IMPORT-03",
+ "assetCode": "MODEL_MTO6WI6K_08-CESIUMMILKTRUCK",
+ "targetProjectId": "156",
+ "targetVersionId": "204"
+ }
+ ],
+ "apiControlled": true,
+ "readOnly": false,
+ "ready": true,
+ "readySettled": true,
+ "dirty": true,
+ "saving": false,
+ "publishing": false,
+ "pendingLoads": 0,
+ "paused": false,
+ "previewing": false,
+ "currentPreset": {
+ "id": "depot-1",
+ "name": "真实装备车库",
+ "type": "车间"
+ },
+ "cameraPosition": [
+ 10.565439077211714,
+ 5.0456876852867705,
+ 7.37050840519251
+ ],
+ "cameraTarget": [
+ 4.2,
+ 1.695456592017448,
+ 0
+ ],
+ "gridVisible": true,
+ "transformMode": "translate",
+ "selectedSceneId": "OBJ-1788690625404-3",
+ "canUndo": true,
+ "canRedo": false,
+ "objects": [
+ {
+ "sceneId": "OBJ-1788605149879-1",
+ "type": "imported",
+ "name": "车库",
+ "visible": true,
+ "position": [
+ 0,
+ 0.01461010548216679,
+ -4.4135855858939976e-8
+ ],
+ "rotation": [
+ 0,
+ 0,
+ 0
+ ],
+ "scale": [
+ 1.009710078119415,
+ 1.009710078119415,
+ 1.009710078119415
+ ],
+ "deviceId": "EQ-001",
+ "modelId": "MODEL-IMPORT-01",
+ "semantic": "环境设施",
+ "operable": false,
+ "capabilities": [
+ "select"
+ ],
+ "assetCode": "",
+ "sourceAssetId": "",
+ "targetProjectId": "",
+ "targetVersionId": "",
+ "modelEditorAssetId": "",
+ "parts": []
+ },
+ {
+ "sceneId": "OBJ-1788605199171-2",
+ "type": "imported",
+ "name": "牛奶卡车检验主装备",
+ "visible": true,
+ "position": [
+ 4.2,
+ 0.20617168998977797,
+ -0.4356763541543951
+ ],
+ "rotation": [
+ 0,
+ 0,
+ 0
+ ],
+ "scale": [
+ 1.2323086149245381,
+ 1.2323086149245381,
+ 1.2323086149245381
+ ],
+ "deviceId": "SCENE-TRUCK-001",
+ "modelId": "MODEL-IMPORT-02",
+ "semantic": "实训装备",
+ "operable": true,
+ "capabilities": [
+ "select",
+ "inspect",
+ "transform"
+ ],
+ "assetCode": "MODEL_MTO6WI6K_08-CESIUMMILKTRUCK",
+ "sourceAssetId": "2069",
+ "targetProjectId": "156",
+ "targetVersionId": "204",
+ "modelEditorAssetId": "content-model-156-204",
+ "parts": [
+ {
+ "sceneId": "interaction-1ba581e6",
+ "name": "Yup2Zup",
+ "visible": true,
+ "sceneDeleted": false,
+ "position": [
+ 0,
+ 0,
+ 0
+ ],
+ "rotation": [
+ 1.57079626719025,
+ 0,
+ 1.5707963267948966
+ ],
+ "scale": [
+ 1,
+ 1,
+ 1
+ ],
+ "capabilities": [
+ "select",
+ "inspect",
+ "transform",
+ "animate"
+ ]
+ },
+ {
+ "sceneId": "interaction-004dec41",
+ "name": "Cesium_Milk_Truck",
+ "visible": true,
+ "sceneDeleted": false,
+ "position": [
+ 0.35,
+ 0,
+ 0
+ ],
+ "rotation": [
+ 0,
+ 0,
+ 0
+ ],
+ "scale": [
+ 1,
+ 1,
+ 1
+ ],
+ "capabilities": [
+ "select",
+ "inspect",
+ "transform",
+ "animate"
+ ]
+ },
+ {
+ "sceneId": "interaction-5f7e4bee",
+ "name": "Cesium_Milk_Truck_1",
+ "visible": true,
+ "sceneDeleted": false,
+ "position": [
+ 0,
+ 0,
+ 0
+ ],
+ "rotation": [
+ 0,
+ 0,
+ 0
+ ],
+ "scale": [
+ 1,
+ 1,
+ 1
+ ],
+ "capabilities": [
+ "select",
+ "inspect",
+ "transform",
+ "animate"
+ ]
+ },
+ {
+ "sceneId": "interaction-607e4d81",
+ "name": "Cesium_Milk_Truck_2",
+ "visible": true,
+ "sceneDeleted": false,
+ "position": [
+ 0,
+ 0,
+ 0
+ ],
+ "rotation": [
+ 0,
+ 0,
+ 0
+ ],
+ "scale": [
+ 1,
+ 1,
+ 1
+ ],
+ "capabilities": [
+ "select",
+ "inspect",
+ "transform",
+ "animate"
+ ]
+ },
+ {
+ "sceneId": "interaction-5d7e48c8",
+ "name": "Cesium_Milk_Truck_3",
+ "visible": true,
+ "sceneDeleted": false,
+ "position": [
+ 0,
+ 0,
+ 0
+ ],
+ "rotation": [
+ 0,
+ 0,
+ 0
+ ],
+ "scale": [
+ 1,
+ 1,
+ 1
+ ],
+ "capabilities": [
+ "select",
+ "inspect",
+ "transform",
+ "animate"
+ ]
+ },
+ {
+ "sceneId": "interaction-5e7e4a5b",
+ "name": "Node",
+ "visible": true,
+ "sceneDeleted": false,
+ "position": [
+ 1.432669997215271,
+ 0,
+ -0.4277220070362091
+ ],
+ "rotation": [
+ 0,
+ 0,
+ 0
+ ],
+ "scale": [
+ 1,
+ 1,
+ 1
+ ],
+ "capabilities": [
+ "select",
+ "inspect",
+ "transform",
+ "animate"
+ ]
+ },
+ {
+ "sceneId": "interaction-ce2feac4",
+ "name": "Wheels",
+ "visible": true,
+ "sceneDeleted": false,
+ "position": [
+ 0,
+ 0,
+ 0
+ ],
+ "rotation": [
+ -3.141592653589793,
+ -0.603571861900947,
+ -3.141592653589793
+ ],
+ "scale": [
+ 1,
+ 1,
+ 1
+ ],
+ "capabilities": [
+ "select",
+ "inspect",
+ "transform",
+ "animate"
+ ]
+ },
+ {
+ "sceneId": "interaction-637e523a",
+ "name": "Node001",
+ "visible": true,
+ "sceneDeleted": false,
+ "position": [
+ -1.352329969406128,
+ 0,
+ -0.4277220070362091
+ ],
+ "rotation": [
+ 0,
+ 0,
+ 0
+ ],
+ "scale": [
+ 1,
+ 1,
+ 1
+ ],
+ "capabilities": [
+ "select",
+ "inspect",
+ "transform",
+ "animate"
+ ]
+ },
+ {
+ "sceneId": "interaction-f417f82d",
+ "name": "Wheels001",
+ "visible": true,
+ "sceneDeleted": false,
+ "position": [
+ 0,
+ 0,
+ 0
+ ],
+ "rotation": [
+ -3.141592653589793,
+ -0.603571861900947,
+ -3.141592653589793
+ ],
+ "scale": [
+ 1,
+ 1,
+ 1
+ ],
+ "capabilities": [
+ "select",
+ "inspect",
+ "transform",
+ "animate"
+ ]
+ }
+ ]
+ },
+ {
+ "sceneId": "OBJ-1788690625404-3",
+ "type": "imported",
+ "name": "实测模型-牛奶卡车-已验证-20260905",
+ "visible": true,
+ "position": [
+ 4.2,
+ 0.20617168998977797,
+ -0.4356763541543951
+ ],
+ "rotation": [
+ 0,
+ 0,
+ 0
+ ],
+ "scale": [
+ 1.2323086149245381,
+ 1.2323086149245381,
+ 1.2323086149245381
+ ],
+ "deviceId": "EQ-003",
+ "modelId": "MODEL-IMPORT-03",
+ "semantic": "实训装备",
+ "operable": true,
+ "capabilities": [
+ "select",
+ "inspect",
+ "transform"
+ ],
+ "assetCode": "MODEL_MTO6WI6K_08-CESIUMMILKTRUCK",
+ "sourceAssetId": "2069",
+ "targetProjectId": "156",
+ "targetVersionId": "204",
+ "modelEditorAssetId": "content-model-156-204",
+ "parts": [
+ {
+ "sceneId": "scene-part:OBJ-1788690625404-3:interaction-1ba581e6",
+ "name": "Yup2Zup",
+ "visible": true,
+ "sceneDeleted": false,
+ "position": [
+ 0,
+ 0,
+ 0
+ ],
+ "rotation": [
+ 1.57079626719025,
+ 0,
+ 1.5707963267948966
+ ],
+ "scale": [
+ 1,
+ 1,
+ 1
+ ],
+ "capabilities": [
+ "select",
+ "inspect",
+ "transform",
+ "animate"
+ ]
+ },
+ {
+ "sceneId": "scene-part:OBJ-1788690625404-3:interaction-004dec41",
+ "name": "Cesium_Milk_Truck",
+ "visible": true,
+ "sceneDeleted": false,
+ "position": [
+ 0.35,
+ 0,
+ 0
+ ],
+ "rotation": [
+ 0,
+ 0,
+ 0
+ ],
+ "scale": [
+ 1,
+ 1,
+ 1
+ ],
+ "capabilities": [
+ "select",
+ "inspect",
+ "transform",
+ "animate"
+ ]
+ },
+ {
+ "sceneId": "scene-part:OBJ-1788690625404-3:interaction-5f7e4bee",
+ "name": "Cesium_Milk_Truck_1",
+ "visible": true,
+ "sceneDeleted": false,
+ "position": [
+ 0,
+ 0,
+ 0
+ ],
+ "rotation": [
+ 0,
+ 0,
+ 0
+ ],
+ "scale": [
+ 1,
+ 1,
+ 1
+ ],
+ "capabilities": [
+ "select",
+ "inspect",
+ "transform",
+ "animate"
+ ]
+ },
+ {
+ "sceneId": "scene-part:OBJ-1788690625404-3:interaction-607e4d81",
+ "name": "Cesium_Milk_Truck_2",
+ "visible": true,
+ "sceneDeleted": false,
+ "position": [
+ 0,
+ 0,
+ 0
+ ],
+ "rotation": [
+ 0,
+ 0,
+ 0
+ ],
+ "scale": [
+ 1,
+ 1,
+ 1
+ ],
+ "capabilities": [
+ "select",
+ "inspect",
+ "transform",
+ "animate"
+ ]
+ },
+ {
+ "sceneId": "scene-part:OBJ-1788690625404-3:interaction-5d7e48c8",
+ "name": "Cesium_Milk_Truck_3",
+ "visible": true,
+ "sceneDeleted": false,
+ "position": [
+ 0,
+ 0,
+ 0
+ ],
+ "rotation": [
+ 0,
+ 0,
+ 0
+ ],
+ "scale": [
+ 1,
+ 1,
+ 1
+ ],
+ "capabilities": [
+ "select",
+ "inspect",
+ "transform",
+ "animate"
+ ]
+ },
+ {
+ "sceneId": "scene-part:OBJ-1788690625404-3:interaction-5e7e4a5b",
+ "name": "Node",
+ "visible": true,
+ "sceneDeleted": false,
+ "position": [
+ 1.432669997215271,
+ 0,
+ -0.4277220070362091
+ ],
+ "rotation": [
+ 0,
+ 0,
+ 0
+ ],
+ "scale": [
+ 1,
+ 1,
+ 1
+ ],
+ "capabilities": [
+ "select",
+ "inspect",
+ "transform",
+ "animate"
+ ]
+ },
+ {
+ "sceneId": "scene-part:OBJ-1788690625404-3:interaction-ce2feac4",
+ "name": "Wheels",
+ "visible": true,
+ "sceneDeleted": false,
+ "position": [
+ 0,
+ 0,
+ 0
+ ],
+ "rotation": [
+ -3.141592653589793,
+ -0.603571861900947,
+ -3.141592653589793
+ ],
+ "scale": [
+ 1,
+ 1,
+ 1
+ ],
+ "capabilities": [
+ "select",
+ "inspect",
+ "transform",
+ "animate"
+ ]
+ },
+ {
+ "sceneId": "scene-part:OBJ-1788690625404-3:interaction-637e523a",
+ "name": "Node001",
+ "visible": true,
+ "sceneDeleted": false,
+ "position": [
+ -1.352329969406128,
+ 0,
+ -0.4277220070362091
+ ],
+ "rotation": [
+ 0,
+ 0,
+ 0
+ ],
+ "scale": [
+ 1,
+ 1,
+ 1
+ ],
+ "capabilities": [
+ "select",
+ "inspect",
+ "transform",
+ "animate"
+ ]
+ },
+ {
+ "sceneId": "scene-part:OBJ-1788690625404-3:interaction-f417f82d",
+ "name": "Wheels001",
+ "visible": true,
+ "sceneDeleted": false,
+ "position": [
+ 0,
+ 0,
+ 0
+ ],
+ "rotation": [
+ -3.141592653589793,
+ -0.603571861900947,
+ -3.141592653589793
+ ],
+ "scale": [
+ 1,
+ 1,
+ 1
+ ],
+ "capabilities": [
+ "select",
+ "inspect",
+ "transform",
+ "animate"
+ ]
+ }
+ ]
+ }
+ ]
+ },
+ "changed": {
+ "mounted": true,
+ "hasEnvironment": true,
+ "hasEnvironmentTarget": true,
+ "toneMappingExposure": 1.05,
+ "background": "b9d5e6",
+ "meshCount": 42,
+ "materials": [
+ {
+ "name": "BaiSeNeiQiang",
+ "color": "eaeaea",
+ "metalness": 0,
+ "roughness": 0.5,
+ "envMapIntensity": 1,
+ "hasMap": false,
+ "preset": null,
+ "rule": null,
+ "authored": null
+ },
+ {
+ "name": "BoLi",
+ "color": "87c8fb",
+ "metalness": 0,
+ "roughness": 0.5,
+ "envMapIntensity": 1,
+ "hasMap": false,
+ "preset": null,
+ "rule": null,
+ "authored": null
+ },
+ {
+ "name": "HuiSe3",
+ "color": "8a8a8a",
+ "metalness": 0.43,
+ "roughness": 0.4,
+ "envMapIntensity": 1,
+ "hasMap": false,
+ "preset": null,
+ "rule": null,
+ "authored": null
+ },
+ {
+ "name": "HuiSe4",
+ "color": "c0c0c0",
+ "metalness": 0,
+ "roughness": 0.5,
+ "envMapIntensity": 1,
+ "hasMap": false,
+ "preset": null,
+ "rule": null,
+ "authored": null
+ },
+ {
+ "name": "JinShu",
+ "color": "555555",
+ "metalness": 0.44,
+ "roughness": 0.5,
+ "envMapIntensity": 1,
+ "hasMap": false,
+ "preset": null,
+ "rule": null,
+ "authored": null
+ },
+ {
+ "name": "MenPai",
+ "color": "ffffff",
+ "metalness": 0.68,
+ "roughness": 0.5,
+ "envMapIntensity": 1,
+ "hasMap": true,
+ "preset": null,
+ "rule": null,
+ "authored": null
+ },
+ {
+ "name": "WaiQiang",
+ "color": "efefef",
+ "metalness": 0,
+ "roughness": 0.5,
+ "envMapIntensity": 1,
+ "hasMap": true,
+ "preset": null,
+ "rule": null,
+ "authored": null
+ },
+ {
+ "name": "baiSe",
+ "color": "eaeaea",
+ "metalness": 0,
+ "roughness": 0.5,
+ "envMapIntensity": 1,
+ "hasMap": false,
+ "preset": null,
+ "rule": null,
+ "authored": null
+ },
+ {
+ "name": "HZhuan",
+ "color": "ffffff",
+ "metalness": 0,
+ "roughness": 0.5,
+ "envMapIntensity": 1,
+ "hasMap": true,
+ "preset": null,
+ "rule": null,
+ "authored": null
+ },
+ {
+ "name": "DiMian",
+ "color": "d2d2d2",
+ "metalness": 0.6,
+ "roughness": 0.2,
+ "envMapIntensity": 1,
+ "hasMap": true,
+ "preset": null,
+ "rule": null,
+ "authored": null
+ },
+ {
+ "name": "HuangSe",
+ "color": "d0af42",
+ "metalness": 0,
+ "roughness": 0.5,
+ "envMapIntensity": 1,
+ "hasMap": false,
+ "preset": null,
+ "rule": null,
+ "authored": null
+ },
+ {
+ "name": "HuangSe2",
+ "color": "b29b87",
+ "metalness": 0,
+ "roughness": 0.5,
+ "envMapIntensity": 1,
+ "hasMap": false,
+ "preset": null,
+ "rule": null,
+ "authored": null
+ },
+ {
+ "name": "HuangSe3",
+ "color": "aea791",
+ "metalness": 0,
+ "roughness": 0.5,
+ "envMapIntensity": 1,
+ "hasMap": false,
+ "preset": null,
+ "rule": null,
+ "authored": null
+ },
+ {
+ "name": "HuiSe",
+ "color": "555555",
+ "metalness": 0.82,
+ "roughness": 0.15,
+ "envMapIntensity": 1,
+ "hasMap": false,
+ "preset": null,
+ "rule": null,
+ "authored": null
+ },
+ {
+ "name": "HuiSe2",
+ "color": "9a9ca4",
+ "metalness": 0,
+ "roughness": 1,
+ "envMapIntensity": 1,
+ "hasMap": false,
+ "preset": null,
+ "rule": null,
+ "authored": null
+ },
+ {
+ "name": "NeiQiangBaiSe",
+ "color": "bfbfbf",
+ "metalness": 0,
+ "roughness": 0.5,
+ "envMapIntensity": 1,
+ "hasMap": false,
+ "preset": null,
+ "rule": null,
+ "authored": null
+ },
+ {
+ "name": "adskMatHuiSe",
+ "color": "797979",
+ "metalness": 0.43,
+ "roughness": 0.4,
+ "envMapIntensity": 1,
+ "hasMap": false,
+ "preset": null,
+ "rule": null,
+ "authored": null
+ },
+ {
+ "name": "truck",
+ "color": "ffffff",
+ "metalness": 0,
+ "roughness": 0.3,
+ "envMapIntensity": 1,
+ "hasMap": true,
+ "preset": null,
+ "rule": null,
+ "authored": null
+ },
+ {
+ "name": "glass",
+ "color": "003928",
+ "metalness": 0,
+ "roughness": 1,
+ "envMapIntensity": 1,
+ "hasMap": false,
+ "preset": null,
+ "rule": null,
+ "authored": null
+ },
+ {
+ "name": "window_trim",
+ "color": "484848",
+ "metalness": 0,
+ "roughness": 1,
+ "envMapIntensity": 1,
+ "hasMap": false,
+ "preset": null,
+ "rule": null,
+ "authored": null
+ },
+ {
+ "name": "wheels",
+ "color": "ffffff",
+ "metalness": 0,
+ "roughness": 1,
+ "envMapIntensity": 1,
+ "hasMap": true,
+ "preset": null,
+ "rule": null,
+ "authored": null
+ }
+ ],
+ "objectCount": 3,
+ "contentBounds": {
+ "min": [
+ -30,
+ 0,
+ -17.156
+ ],
+ "max": [
+ 30,
+ 14.579,
+ 17.156
+ ],
+ "size": [
+ 60,
+ 14.579,
+ 34.311
+ ]
+ },
+ "importedObjects": [
+ {
+ "name": "牛奶卡车检验主装备",
+ "modelEditorModelId": "imported-model_mto6wi6k_08-cesiummilktruck",
+ "modelEditorAssetId": "content-model-156-204",
+ "deviceId": "SCENE-TRUCK-001",
+ "modelId": "MODEL-IMPORT-02",
+ "assetCode": "MODEL_MTO6WI6K_08-CESIUMMILKTRUCK",
+ "targetProjectId": "156",
+ "targetVersionId": "204"
+ },
+ {
+ "name": "实测模型-牛奶卡车-已验证-20260905",
+ "modelEditorModelId": "imported-model_mto6wi6k_08-cesiummilktruck",
+ "modelEditorAssetId": "content-model-156-204",
+ "deviceId": "EQ-003",
+ "modelId": "MODEL-IMPORT-03",
+ "assetCode": "MODEL_MTO6WI6K_08-CESIUMMILKTRUCK",
+ "targetProjectId": "156",
+ "targetVersionId": "204"
+ }
+ ],
+ "apiControlled": true,
+ "readOnly": false,
+ "ready": true,
+ "readySettled": true,
+ "dirty": true,
+ "saving": false,
+ "publishing": false,
+ "pendingLoads": 0,
+ "paused": false,
+ "previewing": false,
+ "currentPreset": {
+ "id": "depot-1",
+ "name": "真实装备车库",
+ "type": "车间"
+ },
+ "cameraPosition": [
+ 10.565439077211714,
+ 5.0456876852867705,
+ 7.37050840519251
+ ],
+ "cameraTarget": [
+ 4.2,
+ 1.695456592017448,
+ 0
+ ],
+ "gridVisible": true,
+ "transformMode": "translate",
+ "selectedSceneId": "interaction-1ba581e6",
+ "canUndo": true,
+ "canRedo": false,
+ "objects": [
+ {
+ "sceneId": "OBJ-1788605149879-1",
+ "type": "imported",
+ "name": "车库",
+ "visible": true,
+ "position": [
+ 0,
+ 0.01461010548216679,
+ -4.4135855858939976e-8
+ ],
+ "rotation": [
+ 0,
+ 0,
+ 0
+ ],
+ "scale": [
+ 1.009710078119415,
+ 1.009710078119415,
+ 1.009710078119415
+ ],
+ "deviceId": "EQ-001",
+ "modelId": "MODEL-IMPORT-01",
+ "semantic": "环境设施",
+ "operable": false,
+ "capabilities": [
+ "select"
+ ],
+ "assetCode": "",
+ "sourceAssetId": "",
+ "targetProjectId": "",
+ "targetVersionId": "",
+ "modelEditorAssetId": "",
+ "parts": []
+ },
+ {
+ "sceneId": "OBJ-1788605199171-2",
+ "type": "imported",
+ "name": "牛奶卡车检验主装备",
+ "visible": true,
+ "position": [
+ 4.2,
+ 0.20617168998977797,
+ -0.4356763541543951
+ ],
+ "rotation": [
+ 0,
+ 0,
+ 0
+ ],
+ "scale": [
+ 1.2323086149245381,
+ 1.2323086149245381,
+ 1.2323086149245381
+ ],
+ "deviceId": "SCENE-TRUCK-001",
+ "modelId": "MODEL-IMPORT-02",
+ "semantic": "实训装备",
+ "operable": true,
+ "capabilities": [
+ "select",
+ "inspect",
+ "transform"
+ ],
+ "assetCode": "MODEL_MTO6WI6K_08-CESIUMMILKTRUCK",
+ "sourceAssetId": "2069",
+ "targetProjectId": "156",
+ "targetVersionId": "204",
+ "modelEditorAssetId": "content-model-156-204",
+ "parts": [
+ {
+ "sceneId": "interaction-1ba581e6",
+ "name": "独立部件验收",
+ "visible": true,
+ "sceneDeleted": false,
+ "position": [
+ 0.3,
+ 0,
+ 0
+ ],
+ "rotation": [
+ 1.57079626719025,
+ 0,
+ 1.5707963267948966
+ ],
+ "scale": [
+ 1,
+ 1,
+ 1
+ ],
+ "capabilities": [
+ "select",
+ "inspect",
+ "transform",
+ "animate"
+ ]
+ },
+ {
+ "sceneId": "interaction-004dec41",
+ "name": "Cesium_Milk_Truck",
+ "visible": true,
+ "sceneDeleted": false,
+ "position": [
+ 0.35,
+ 0,
+ 0
+ ],
+ "rotation": [
+ 0,
+ 0,
+ 0
+ ],
+ "scale": [
+ 1,
+ 1,
+ 1
+ ],
+ "capabilities": [
+ "select",
+ "inspect",
+ "transform",
+ "animate"
+ ]
+ },
+ {
+ "sceneId": "interaction-5f7e4bee",
+ "name": "Cesium_Milk_Truck_1",
+ "visible": true,
+ "sceneDeleted": false,
+ "position": [
+ 0,
+ 0,
+ 0
+ ],
+ "rotation": [
+ 0,
+ 0,
+ 0
+ ],
+ "scale": [
+ 1,
+ 1,
+ 1
+ ],
+ "capabilities": [
+ "select",
+ "inspect",
+ "transform",
+ "animate"
+ ]
+ },
+ {
+ "sceneId": "interaction-607e4d81",
+ "name": "Cesium_Milk_Truck_2",
+ "visible": true,
+ "sceneDeleted": false,
+ "position": [
+ 0,
+ 0,
+ 0
+ ],
+ "rotation": [
+ 0,
+ 0,
+ 0
+ ],
+ "scale": [
+ 1,
+ 1,
+ 1
+ ],
+ "capabilities": [
+ "select",
+ "inspect",
+ "transform",
+ "animate"
+ ]
+ },
+ {
+ "sceneId": "interaction-5d7e48c8",
+ "name": "Cesium_Milk_Truck_3",
+ "visible": true,
+ "sceneDeleted": false,
+ "position": [
+ 0,
+ 0,
+ 0
+ ],
+ "rotation": [
+ 0,
+ 0,
+ 0
+ ],
+ "scale": [
+ 1,
+ 1,
+ 1
+ ],
+ "capabilities": [
+ "select",
+ "inspect",
+ "transform",
+ "animate"
+ ]
+ },
+ {
+ "sceneId": "interaction-5e7e4a5b",
+ "name": "Node",
+ "visible": true,
+ "sceneDeleted": false,
+ "position": [
+ 1.432669997215271,
+ 0,
+ -0.4277220070362091
+ ],
+ "rotation": [
+ 0,
+ 0,
+ 0
+ ],
+ "scale": [
+ 1,
+ 1,
+ 1
+ ],
+ "capabilities": [
+ "select",
+ "inspect",
+ "transform",
+ "animate"
+ ]
+ },
+ {
+ "sceneId": "interaction-ce2feac4",
+ "name": "Wheels",
+ "visible": true,
+ "sceneDeleted": false,
+ "position": [
+ 0,
+ 0,
+ 0
+ ],
+ "rotation": [
+ -3.141592653589793,
+ -0.603571861900947,
+ -3.141592653589793
+ ],
+ "scale": [
+ 1,
+ 1,
+ 1
+ ],
+ "capabilities": [
+ "select",
+ "inspect",
+ "transform",
+ "animate"
+ ]
+ },
+ {
+ "sceneId": "interaction-637e523a",
+ "name": "Node001",
+ "visible": true,
+ "sceneDeleted": false,
+ "position": [
+ -1.352329969406128,
+ 0,
+ -0.4277220070362091
+ ],
+ "rotation": [
+ 0,
+ 0,
+ 0
+ ],
+ "scale": [
+ 1,
+ 1,
+ 1
+ ],
+ "capabilities": [
+ "select",
+ "inspect",
+ "transform",
+ "animate"
+ ]
+ },
+ {
+ "sceneId": "interaction-f417f82d",
+ "name": "Wheels001",
+ "visible": true,
+ "sceneDeleted": false,
+ "position": [
+ 0,
+ 0,
+ 0
+ ],
+ "rotation": [
+ -3.141592653589793,
+ -0.603571861900947,
+ -3.141592653589793
+ ],
+ "scale": [
+ 1,
+ 1,
+ 1
+ ],
+ "capabilities": [
+ "select",
+ "inspect",
+ "transform",
+ "animate"
+ ]
+ }
+ ]
+ },
+ {
+ "sceneId": "OBJ-1788690625404-3",
+ "type": "imported",
+ "name": "实测模型-牛奶卡车-已验证-20260905",
+ "visible": true,
+ "position": [
+ 4.2,
+ 0.20617168998977797,
+ -0.4356763541543951
+ ],
+ "rotation": [
+ 0,
+ 0,
+ 0
+ ],
+ "scale": [
+ 1.2323086149245381,
+ 1.2323086149245381,
+ 1.2323086149245381
+ ],
+ "deviceId": "EQ-003",
+ "modelId": "MODEL-IMPORT-03",
+ "semantic": "实训装备",
+ "operable": true,
+ "capabilities": [
+ "select",
+ "inspect",
+ "transform"
+ ],
+ "assetCode": "MODEL_MTO6WI6K_08-CESIUMMILKTRUCK",
+ "sourceAssetId": "2069",
+ "targetProjectId": "156",
+ "targetVersionId": "204",
+ "modelEditorAssetId": "content-model-156-204",
+ "parts": [
+ {
+ "sceneId": "scene-part:OBJ-1788690625404-3:interaction-1ba581e6",
+ "name": "Yup2Zup",
+ "visible": true,
+ "sceneDeleted": false,
+ "position": [
+ 0,
+ 0,
+ 0
+ ],
+ "rotation": [
+ 1.57079626719025,
+ 0,
+ 1.5707963267948966
+ ],
+ "scale": [
+ 1,
+ 1,
+ 1
+ ],
+ "capabilities": [
+ "select",
+ "inspect",
+ "transform",
+ "animate"
+ ]
+ },
+ {
+ "sceneId": "scene-part:OBJ-1788690625404-3:interaction-004dec41",
+ "name": "Cesium_Milk_Truck",
+ "visible": true,
+ "sceneDeleted": false,
+ "position": [
+ 0.35,
+ 0,
+ 0
+ ],
+ "rotation": [
+ 0,
+ 0,
+ 0
+ ],
+ "scale": [
+ 1,
+ 1,
+ 1
+ ],
+ "capabilities": [
+ "select",
+ "inspect",
+ "transform",
+ "animate"
+ ]
+ },
+ {
+ "sceneId": "scene-part:OBJ-1788690625404-3:interaction-5f7e4bee",
+ "name": "Cesium_Milk_Truck_1",
+ "visible": true,
+ "sceneDeleted": false,
+ "position": [
+ 0,
+ 0,
+ 0
+ ],
+ "rotation": [
+ 0,
+ 0,
+ 0
+ ],
+ "scale": [
+ 1,
+ 1,
+ 1
+ ],
+ "capabilities": [
+ "select",
+ "inspect",
+ "transform",
+ "animate"
+ ]
+ },
+ {
+ "sceneId": "scene-part:OBJ-1788690625404-3:interaction-607e4d81",
+ "name": "Cesium_Milk_Truck_2",
+ "visible": true,
+ "sceneDeleted": false,
+ "position": [
+ 0,
+ 0,
+ 0
+ ],
+ "rotation": [
+ 0,
+ 0,
+ 0
+ ],
+ "scale": [
+ 1,
+ 1,
+ 1
+ ],
+ "capabilities": [
+ "select",
+ "inspect",
+ "transform",
+ "animate"
+ ]
+ },
+ {
+ "sceneId": "scene-part:OBJ-1788690625404-3:interaction-5d7e48c8",
+ "name": "Cesium_Milk_Truck_3",
+ "visible": true,
+ "sceneDeleted": false,
+ "position": [
+ 0,
+ 0,
+ 0
+ ],
+ "rotation": [
+ 0,
+ 0,
+ 0
+ ],
+ "scale": [
+ 1,
+ 1,
+ 1
+ ],
+ "capabilities": [
+ "select",
+ "inspect",
+ "transform",
+ "animate"
+ ]
+ },
+ {
+ "sceneId": "scene-part:OBJ-1788690625404-3:interaction-5e7e4a5b",
+ "name": "Node",
+ "visible": true,
+ "sceneDeleted": false,
+ "position": [
+ 1.432669997215271,
+ 0,
+ -0.4277220070362091
+ ],
+ "rotation": [
+ 0,
+ 0,
+ 0
+ ],
+ "scale": [
+ 1,
+ 1,
+ 1
+ ],
+ "capabilities": [
+ "select",
+ "inspect",
+ "transform",
+ "animate"
+ ]
+ },
+ {
+ "sceneId": "scene-part:OBJ-1788690625404-3:interaction-ce2feac4",
+ "name": "Wheels",
+ "visible": true,
+ "sceneDeleted": false,
+ "position": [
+ 0,
+ 0,
+ 0
+ ],
+ "rotation": [
+ -3.141592653589793,
+ -0.603571861900947,
+ -3.141592653589793
+ ],
+ "scale": [
+ 1,
+ 1,
+ 1
+ ],
+ "capabilities": [
+ "select",
+ "inspect",
+ "transform",
+ "animate"
+ ]
+ },
+ {
+ "sceneId": "scene-part:OBJ-1788690625404-3:interaction-637e523a",
+ "name": "Node001",
+ "visible": true,
+ "sceneDeleted": false,
+ "position": [
+ -1.352329969406128,
+ 0,
+ -0.4277220070362091
+ ],
+ "rotation": [
+ 0,
+ 0,
+ 0
+ ],
+ "scale": [
+ 1,
+ 1,
+ 1
+ ],
+ "capabilities": [
+ "select",
+ "inspect",
+ "transform",
+ "animate"
+ ]
+ },
+ {
+ "sceneId": "scene-part:OBJ-1788690625404-3:interaction-f417f82d",
+ "name": "Wheels001",
+ "visible": true,
+ "sceneDeleted": false,
+ "position": [
+ 0,
+ 0,
+ 0
+ ],
+ "rotation": [
+ -3.141592653589793,
+ -0.603571861900947,
+ -3.141592653589793
+ ],
+ "scale": [
+ 1,
+ 1,
+ 1
+ ],
+ "capabilities": [
+ "select",
+ "inspect",
+ "transform",
+ "animate"
+ ]
+ }
+ ]
+ }
+ ]
+ },
+ "reopened": {
+ "mounted": true,
+ "hasEnvironment": true,
+ "hasEnvironmentTarget": true,
+ "toneMappingExposure": 1.05,
+ "background": "b9d5e6",
+ "meshCount": 42,
+ "materials": [
+ {
+ "name": "BaiSeNeiQiang",
+ "color": "eaeaea",
+ "metalness": 0,
+ "roughness": 0.5,
+ "envMapIntensity": 1,
+ "hasMap": false,
+ "preset": null,
+ "rule": null,
+ "authored": null
+ },
+ {
+ "name": "BoLi",
+ "color": "87c8fb",
+ "metalness": 0,
+ "roughness": 0.5,
+ "envMapIntensity": 1,
+ "hasMap": false,
+ "preset": null,
+ "rule": null,
+ "authored": null
+ },
+ {
+ "name": "HuiSe3",
+ "color": "8a8a8a",
+ "metalness": 0.43,
+ "roughness": 0.4,
+ "envMapIntensity": 1,
+ "hasMap": false,
+ "preset": null,
+ "rule": null,
+ "authored": null
+ },
+ {
+ "name": "HuiSe4",
+ "color": "c0c0c0",
+ "metalness": 0,
+ "roughness": 0.5,
+ "envMapIntensity": 1,
+ "hasMap": false,
+ "preset": null,
+ "rule": null,
+ "authored": null
+ },
+ {
+ "name": "JinShu",
+ "color": "555555",
+ "metalness": 0.44,
+ "roughness": 0.5,
+ "envMapIntensity": 1,
+ "hasMap": false,
+ "preset": null,
+ "rule": null,
+ "authored": null
+ },
+ {
+ "name": "MenPai",
+ "color": "ffffff",
+ "metalness": 0.68,
+ "roughness": 0.5,
+ "envMapIntensity": 1,
+ "hasMap": true,
+ "preset": null,
+ "rule": null,
+ "authored": null
+ },
+ {
+ "name": "WaiQiang",
+ "color": "efefef",
+ "metalness": 0,
+ "roughness": 0.5,
+ "envMapIntensity": 1,
+ "hasMap": true,
+ "preset": null,
+ "rule": null,
+ "authored": null
+ },
+ {
+ "name": "baiSe",
+ "color": "eaeaea",
+ "metalness": 0,
+ "roughness": 0.5,
+ "envMapIntensity": 1,
+ "hasMap": false,
+ "preset": null,
+ "rule": null,
+ "authored": null
+ },
+ {
+ "name": "HZhuan",
+ "color": "ffffff",
+ "metalness": 0,
+ "roughness": 0.5,
+ "envMapIntensity": 1,
+ "hasMap": true,
+ "preset": null,
+ "rule": null,
+ "authored": null
+ },
+ {
+ "name": "DiMian",
+ "color": "d2d2d2",
+ "metalness": 0.6,
+ "roughness": 0.2,
+ "envMapIntensity": 1,
+ "hasMap": true,
+ "preset": null,
+ "rule": null,
+ "authored": null
+ },
+ {
+ "name": "HuangSe",
+ "color": "d0af42",
+ "metalness": 0,
+ "roughness": 0.5,
+ "envMapIntensity": 1,
+ "hasMap": false,
+ "preset": null,
+ "rule": null,
+ "authored": null
+ },
+ {
+ "name": "HuangSe2",
+ "color": "b29b87",
+ "metalness": 0,
+ "roughness": 0.5,
+ "envMapIntensity": 1,
+ "hasMap": false,
+ "preset": null,
+ "rule": null,
+ "authored": null
+ },
+ {
+ "name": "HuangSe3",
+ "color": "aea791",
+ "metalness": 0,
+ "roughness": 0.5,
+ "envMapIntensity": 1,
+ "hasMap": false,
+ "preset": null,
+ "rule": null,
+ "authored": null
+ },
+ {
+ "name": "HuiSe",
+ "color": "555555",
+ "metalness": 0.82,
+ "roughness": 0.15,
+ "envMapIntensity": 1,
+ "hasMap": false,
+ "preset": null,
+ "rule": null,
+ "authored": null
+ },
+ {
+ "name": "HuiSe2",
+ "color": "9a9ca4",
+ "metalness": 0,
+ "roughness": 1,
+ "envMapIntensity": 1,
+ "hasMap": false,
+ "preset": null,
+ "rule": null,
+ "authored": null
+ },
+ {
+ "name": "NeiQiangBaiSe",
+ "color": "bfbfbf",
+ "metalness": 0,
+ "roughness": 0.5,
+ "envMapIntensity": 1,
+ "hasMap": false,
+ "preset": null,
+ "rule": null,
+ "authored": null
+ },
+ {
+ "name": "adskMatHuiSe",
+ "color": "797979",
+ "metalness": 0.43,
+ "roughness": 0.4,
+ "envMapIntensity": 1,
+ "hasMap": false,
+ "preset": null,
+ "rule": null,
+ "authored": null
+ },
+ {
+ "name": "truck",
+ "color": "ffffff",
+ "metalness": 0,
+ "roughness": 0.3,
+ "envMapIntensity": 1,
+ "hasMap": true,
+ "preset": null,
+ "rule": null,
+ "authored": null
+ },
+ {
+ "name": "glass",
+ "color": "003928",
+ "metalness": 0,
+ "roughness": 1,
+ "envMapIntensity": 1,
+ "hasMap": false,
+ "preset": null,
+ "rule": null,
+ "authored": null
+ },
+ {
+ "name": "window_trim",
+ "color": "484848",
+ "metalness": 0,
+ "roughness": 1,
+ "envMapIntensity": 1,
+ "hasMap": false,
+ "preset": null,
+ "rule": null,
+ "authored": null
+ },
+ {
+ "name": "wheels",
+ "color": "ffffff",
+ "metalness": 0,
+ "roughness": 1,
+ "envMapIntensity": 1,
+ "hasMap": true,
+ "preset": null,
+ "rule": null,
+ "authored": null
+ }
+ ],
+ "objectCount": 3,
+ "contentBounds": {
+ "min": [
+ -30,
+ 0,
+ -17.156
+ ],
+ "max": [
+ 30,
+ 14.579,
+ 17.156
+ ],
+ "size": [
+ 60,
+ 14.579,
+ 34.311
+ ]
+ },
+ "importedObjects": [
+ {
+ "name": "卡车部件闭环完成",
+ "modelEditorModelId": "imported-model_mto6wi6k_08-cesiummilktruck",
+ "modelEditorAssetId": "content-model-156-204",
+ "deviceId": "SCENE-TRUCK-001",
+ "modelId": "MODEL-IMPORT-02",
+ "assetCode": "MODEL_MTO6WI6K_08-CESIUMMILKTRUCK",
+ "targetProjectId": "156",
+ "targetVersionId": "204"
+ },
+ {
+ "name": "实测模型-牛奶卡车-已验证-20260905",
+ "modelEditorModelId": "imported-model_mto6wi6k_08-cesiummilktruck",
+ "modelEditorAssetId": "content-model-156-204",
+ "deviceId": "EQ-003",
+ "modelId": "MODEL-IMPORT-03",
+ "assetCode": "MODEL_MTO6WI6K_08-CESIUMMILKTRUCK",
+ "targetProjectId": "156",
+ "targetVersionId": "204"
+ }
+ ],
+ "apiControlled": true,
+ "readOnly": false,
+ "ready": true,
+ "readySettled": true,
+ "dirty": false,
+ "saving": false,
+ "publishing": false,
+ "pendingLoads": 0,
+ "paused": false,
+ "previewing": false,
+ "currentPreset": {
+ "id": "depot-1",
+ "name": "真实装备车库",
+ "type": "车间"
+ },
+ "cameraPosition": [
+ 10.565439077211714,
+ 5.0456876852867705,
+ 7.37050840519251
+ ],
+ "cameraTarget": [
+ 4.2,
+ 1.695456592017448,
+ 0
+ ],
+ "gridVisible": true,
+ "transformMode": "translate",
+ "selectedSceneId": "",
+ "canUndo": false,
+ "canRedo": false,
+ "objects": [
+ {
+ "sceneId": "OBJ-1788605149879-1",
+ "type": "imported",
+ "name": "车库",
+ "visible": true,
+ "position": [
+ 0,
+ 0.01461010548216679,
+ -4.4135855858939976e-8
+ ],
+ "rotation": [
+ 0,
+ 0,
+ 0
+ ],
+ "scale": [
+ 1.009710078119415,
+ 1.009710078119415,
+ 1.009710078119415
+ ],
+ "deviceId": "EQ-001",
+ "modelId": "MODEL-IMPORT-01",
+ "semantic": "环境设施",
+ "operable": false,
+ "capabilities": [
+ "select"
+ ],
+ "assetCode": "",
+ "sourceAssetId": "",
+ "targetProjectId": "",
+ "targetVersionId": "",
+ "modelEditorAssetId": "",
+ "parts": []
+ },
+ {
+ "sceneId": "OBJ-1788605199171-2",
+ "type": "imported",
+ "name": "卡车部件闭环完成",
+ "visible": true,
+ "position": [
+ 4.2,
+ 0.20617168998977797,
+ -0.4356763541543951
+ ],
+ "rotation": [
+ 0,
+ 0,
+ 0
+ ],
+ "scale": [
+ 1.2323086149245381,
+ 1.2323086149245381,
+ 1.2323086149245381
+ ],
+ "deviceId": "SCENE-TRUCK-001",
+ "modelId": "MODEL-IMPORT-02",
+ "semantic": "实训装备",
+ "operable": true,
+ "capabilities": [
+ "select",
+ "inspect",
+ "transform"
+ ],
+ "assetCode": "MODEL_MTO6WI6K_08-CESIUMMILKTRUCK",
+ "sourceAssetId": "2069",
+ "targetProjectId": "156",
+ "targetVersionId": "204",
+ "modelEditorAssetId": "content-model-156-204",
+ "parts": [
+ {
+ "sceneId": "interaction-1ba581e6",
+ "name": "独立部件验收",
+ "visible": true,
+ "sceneDeleted": false,
+ "position": [
+ 0.3,
+ 0,
+ 0
+ ],
+ "rotation": [
+ 1.57079626719025,
+ 0,
+ 1.5707963267948966
+ ],
+ "scale": [
+ 1,
+ 1,
+ 1
+ ],
+ "capabilities": [
+ "select",
+ "inspect",
+ "transform",
+ "animate"
+ ]
+ },
+ {
+ "sceneId": "interaction-004dec41",
+ "name": "Cesium_Milk_Truck",
+ "visible": true,
+ "sceneDeleted": false,
+ "position": [
+ 0.35,
+ 0,
+ 0
+ ],
+ "rotation": [
+ 0,
+ 0,
+ 0
+ ],
+ "scale": [
+ 1,
+ 1,
+ 1
+ ],
+ "capabilities": [
+ "select",
+ "inspect",
+ "transform",
+ "animate"
+ ]
+ },
+ {
+ "sceneId": "interaction-5f7e4bee",
+ "name": "Cesium_Milk_Truck_1",
+ "visible": true,
+ "sceneDeleted": false,
+ "position": [
+ 0,
+ 0,
+ 0
+ ],
+ "rotation": [
+ 0,
+ 0,
+ 0
+ ],
+ "scale": [
+ 1,
+ 1,
+ 1
+ ],
+ "capabilities": [
+ "select",
+ "inspect",
+ "transform",
+ "animate"
+ ]
+ },
+ {
+ "sceneId": "interaction-607e4d81",
+ "name": "Cesium_Milk_Truck_2",
+ "visible": true,
+ "sceneDeleted": false,
+ "position": [
+ 0,
+ 0,
+ 0
+ ],
+ "rotation": [
+ 0,
+ 0,
+ 0
+ ],
+ "scale": [
+ 1,
+ 1,
+ 1
+ ],
+ "capabilities": [
+ "select",
+ "inspect",
+ "transform",
+ "animate"
+ ]
+ },
+ {
+ "sceneId": "interaction-5d7e48c8",
+ "name": "Cesium_Milk_Truck_3",
+ "visible": true,
+ "sceneDeleted": false,
+ "position": [
+ 0,
+ 0,
+ 0
+ ],
+ "rotation": [
+ 0,
+ 0,
+ 0
+ ],
+ "scale": [
+ 1,
+ 1,
+ 1
+ ],
+ "capabilities": [
+ "select",
+ "inspect",
+ "transform",
+ "animate"
+ ]
+ },
+ {
+ "sceneId": "interaction-5e7e4a5b",
+ "name": "Node",
+ "visible": true,
+ "sceneDeleted": false,
+ "position": [
+ 1.432669997215271,
+ 0,
+ -0.4277220070362091
+ ],
+ "rotation": [
+ 0,
+ 0,
+ 0
+ ],
+ "scale": [
+ 1,
+ 1,
+ 1
+ ],
+ "capabilities": [
+ "select",
+ "inspect",
+ "transform",
+ "animate"
+ ]
+ },
+ {
+ "sceneId": "interaction-ce2feac4",
+ "name": "Wheels",
+ "visible": true,
+ "sceneDeleted": false,
+ "position": [
+ 0,
+ 0,
+ 0
+ ],
+ "rotation": [
+ -3.141592653589793,
+ -0.603571861900947,
+ -3.141592653589793
+ ],
+ "scale": [
+ 1,
+ 1,
+ 1
+ ],
+ "capabilities": [
+ "select",
+ "inspect",
+ "transform",
+ "animate"
+ ]
+ },
+ {
+ "sceneId": "interaction-637e523a",
+ "name": "Node001",
+ "visible": true,
+ "sceneDeleted": false,
+ "position": [
+ -1.352329969406128,
+ 0,
+ -0.4277220070362091
+ ],
+ "rotation": [
+ 0,
+ 0,
+ 0
+ ],
+ "scale": [
+ 1,
+ 1,
+ 1
+ ],
+ "capabilities": [
+ "select",
+ "inspect",
+ "transform",
+ "animate"
+ ]
+ },
+ {
+ "sceneId": "interaction-f417f82d",
+ "name": "Wheels001",
+ "visible": true,
+ "sceneDeleted": false,
+ "position": [
+ 0,
+ 0,
+ 0
+ ],
+ "rotation": [
+ -3.141592653589793,
+ -0.603571861900947,
+ -3.141592653589793
+ ],
+ "scale": [
+ 1,
+ 1,
+ 1
+ ],
+ "capabilities": [
+ "select",
+ "inspect",
+ "transform",
+ "animate"
+ ]
+ }
+ ]
+ },
+ {
+ "sceneId": "OBJ-1788690625404-3",
+ "type": "imported",
+ "name": "实测模型-牛奶卡车-已验证-20260905",
+ "visible": true,
+ "position": [
+ 4.2,
+ 0.20617168998977797,
+ -0.4356763541543951
+ ],
+ "rotation": [
+ 0,
+ 0,
+ 0
+ ],
+ "scale": [
+ 1.2323086149245381,
+ 1.2323086149245381,
+ 1.2323086149245381
+ ],
+ "deviceId": "EQ-003",
+ "modelId": "MODEL-IMPORT-03",
+ "semantic": "实训装备",
+ "operable": true,
+ "capabilities": [
+ "select",
+ "inspect",
+ "transform"
+ ],
+ "assetCode": "MODEL_MTO6WI6K_08-CESIUMMILKTRUCK",
+ "sourceAssetId": "2069",
+ "targetProjectId": "156",
+ "targetVersionId": "204",
+ "modelEditorAssetId": "content-model-156-204",
+ "parts": [
+ {
+ "sceneId": "scene-part:OBJ-1788690625404-3:interaction-1ba581e6",
+ "name": "Yup2Zup",
+ "visible": true,
+ "sceneDeleted": false,
+ "position": [
+ 0,
+ 0,
+ 0
+ ],
+ "rotation": [
+ 1.57079626719025,
+ 0,
+ 1.5707963267948966
+ ],
+ "scale": [
+ 1,
+ 1,
+ 1
+ ],
+ "capabilities": [
+ "select",
+ "inspect",
+ "transform",
+ "animate"
+ ]
+ },
+ {
+ "sceneId": "scene-part:OBJ-1788690625404-3:interaction-004dec41",
+ "name": "Cesium_Milk_Truck",
+ "visible": true,
+ "sceneDeleted": false,
+ "position": [
+ 0.35,
+ 0,
+ 0
+ ],
+ "rotation": [
+ 0,
+ 0,
+ 0
+ ],
+ "scale": [
+ 1,
+ 1,
+ 1
+ ],
+ "capabilities": [
+ "select",
+ "inspect",
+ "transform",
+ "animate"
+ ]
+ },
+ {
+ "sceneId": "scene-part:OBJ-1788690625404-3:interaction-5f7e4bee",
+ "name": "Cesium_Milk_Truck_1",
+ "visible": true,
+ "sceneDeleted": false,
+ "position": [
+ 0,
+ 0,
+ 0
+ ],
+ "rotation": [
+ 0,
+ 0,
+ 0
+ ],
+ "scale": [
+ 1,
+ 1,
+ 1
+ ],
+ "capabilities": [
+ "select",
+ "inspect",
+ "transform",
+ "animate"
+ ]
+ },
+ {
+ "sceneId": "scene-part:OBJ-1788690625404-3:interaction-607e4d81",
+ "name": "Cesium_Milk_Truck_2",
+ "visible": true,
+ "sceneDeleted": false,
+ "position": [
+ 0,
+ 0,
+ 0
+ ],
+ "rotation": [
+ 0,
+ 0,
+ 0
+ ],
+ "scale": [
+ 1,
+ 1,
+ 1
+ ],
+ "capabilities": [
+ "select",
+ "inspect",
+ "transform",
+ "animate"
+ ]
+ },
+ {
+ "sceneId": "scene-part:OBJ-1788690625404-3:interaction-5d7e48c8",
+ "name": "Cesium_Milk_Truck_3",
+ "visible": true,
+ "sceneDeleted": false,
+ "position": [
+ 0,
+ 0,
+ 0
+ ],
+ "rotation": [
+ 0,
+ 0,
+ 0
+ ],
+ "scale": [
+ 1,
+ 1,
+ 1
+ ],
+ "capabilities": [
+ "select",
+ "inspect",
+ "transform",
+ "animate"
+ ]
+ },
+ {
+ "sceneId": "scene-part:OBJ-1788690625404-3:interaction-5e7e4a5b",
+ "name": "Node",
+ "visible": true,
+ "sceneDeleted": false,
+ "position": [
+ 1.432669997215271,
+ 0,
+ -0.4277220070362091
+ ],
+ "rotation": [
+ 0,
+ 0,
+ 0
+ ],
+ "scale": [
+ 1,
+ 1,
+ 1
+ ],
+ "capabilities": [
+ "select",
+ "inspect",
+ "transform",
+ "animate"
+ ]
+ },
+ {
+ "sceneId": "scene-part:OBJ-1788690625404-3:interaction-ce2feac4",
+ "name": "Wheels",
+ "visible": true,
+ "sceneDeleted": false,
+ "position": [
+ 0,
+ 0,
+ 0
+ ],
+ "rotation": [
+ -3.141592653589793,
+ -0.603571861900947,
+ -3.141592653589793
+ ],
+ "scale": [
+ 1,
+ 1,
+ 1
+ ],
+ "capabilities": [
+ "select",
+ "inspect",
+ "transform",
+ "animate"
+ ]
+ },
+ {
+ "sceneId": "scene-part:OBJ-1788690625404-3:interaction-637e523a",
+ "name": "Node001",
+ "visible": true,
+ "sceneDeleted": false,
+ "position": [
+ -1.352329969406128,
+ 0,
+ -0.4277220070362091
+ ],
+ "rotation": [
+ 0,
+ 0,
+ 0
+ ],
+ "scale": [
+ 1,
+ 1,
+ 1
+ ],
+ "capabilities": [
+ "select",
+ "inspect",
+ "transform",
+ "animate"
+ ]
+ },
+ {
+ "sceneId": "scene-part:OBJ-1788690625404-3:interaction-f417f82d",
+ "name": "Wheels001",
+ "visible": true,
+ "sceneDeleted": false,
+ "position": [
+ 0,
+ 0,
+ 0
+ ],
+ "rotation": [
+ -3.141592653589793,
+ -0.603571861900947,
+ -3.141592653589793
+ ],
+ "scale": [
+ 1,
+ 1,
+ 1
+ ],
+ "capabilities": [
+ "select",
+ "inspect",
+ "transform",
+ "animate"
+ ]
+ }
+ ]
+ }
+ ]
+ }
+ }
+}
\ No newline at end of file
diff --git a/reports/scene-editor-closure-20260906/evidence/venue-assets.json b/reports/scene-editor-closure-20260906/evidence/venue-assets.json
new file mode 100644
index 0000000..e016a49
--- /dev/null
+++ b/reports/scene-editor-closure-20260906/evidence/venue-assets.json
@@ -0,0 +1,19 @@
+{
+ "time": "2026-09-06T10:29:06.315Z",
+ "data": {
+ "kind": "e2e",
+ "status": "PASS",
+ "testTitle": "两种整体场地加载、重复放置阻止与显式删除后替换",
+ "featureIds": [
+ "asset.vehicle-depot",
+ "asset.field-terrain",
+ "object.delete"
+ ],
+ "projectId": "398",
+ "scope": "车库和起伏地形真实加载并分别保存重开;已有整体场地时阻止叠加,显式删除旧场地后才能替换。",
+ "screenshots": [
+ "31-venue-depot-reopened.png",
+ "32-venue-terrain-reopened.png"
+ ]
+ }
+}
\ No newline at end of file
diff --git a/reports/scene-editor-closure-20260906/evidence/viewport-cache-close.json b/reports/scene-editor-closure-20260906/evidence/viewport-cache-close.json
new file mode 100644
index 0000000..224a462
--- /dev/null
+++ b/reports/scene-editor-closure-20260906/evidence/viewport-cache-close.json
@@ -0,0 +1,14 @@
+{
+ "time": "2026-09-06T10:30:03.089Z",
+ "data": {
+ "kind": "真实UI缓存/关闭+浏览器模拟503拒绝写入",
+ "status": "PASS",
+ "testTitle": "缓存标签暂停键盘、草稿恢复以及关闭取消和保存失败留页",
+ "projectId": "402",
+ "featureIds": [
+ "lifecycle.cache"
+ ],
+ "screenshot": "44-cache-close-save-failure",
+ "originalDraftPreserved": true
+ }
+}
\ No newline at end of file
diff --git a/reports/scene-editor-closure-20260906/evidence/viewport-camera-theme.json b/reports/scene-editor-closure-20260906/evidence/viewport-camera-theme.json
new file mode 100644
index 0000000..9795f90
--- /dev/null
+++ b/reports/scene-editor-closure-20260906/evidence/viewport-camera-theme.json
@@ -0,0 +1,33 @@
+{
+ "time": "2026-09-06T10:29:44.118Z",
+ "data": {
+ "kind": "真实UI+只读投影",
+ "status": "PASS",
+ "testTitle": "真实相机操控、视口拾取、预览禁写和两尺寸主题可读性",
+ "projectId": "400",
+ "featureIds": [
+ "view.orbit",
+ "view.pan",
+ "view.zoom",
+ "view.top",
+ "view.perspective",
+ "view.grid",
+ "view.focus",
+ "selection.viewport",
+ "selection.highlight",
+ "preview.enter",
+ "preview.exit",
+ "preview.lock",
+ "lifecycle.resize-theme"
+ ],
+ "colors": {
+ "text": "rgb(104, 131, 125)",
+ "background": "rgb(237, 244, 241)"
+ },
+ "screenshots": [
+ "40-viewport-dark-1920",
+ "41-viewport-light-1440",
+ "42-fox-preview-original-material"
+ ]
+ }
+}
\ No newline at end of file
diff --git a/reports/scene-editor-closure-20260906/evidence/viewport-transform.json b/reports/scene-editor-closure-20260906/evidence/viewport-transform.json
new file mode 100644
index 0000000..1df2cb0
--- /dev/null
+++ b/reports/scene-editor-closure-20260906/evidence/viewport-transform.json
@@ -0,0 +1,91 @@
+{
+ "time": "2026-09-06T10:29:55.869Z",
+ "data": {
+ "kind": "真实UI手柄+只读几何投影定位",
+ "status": "PASS",
+ "testTitle": "真实变换手柄移动旋转缩放、吸附与保存重开",
+ "projectId": "401",
+ "featureIds": [
+ "transform.translate",
+ "transform.rotate",
+ "transform.scale",
+ "transform.keys",
+ "transform.snap"
+ ],
+ "before": {
+ "sceneId": "OBJ-1788690589583-3",
+ "type": "equipment",
+ "name": "维修装备主体 03",
+ "visible": true,
+ "position": [
+ 0,
+ 0,
+ 0
+ ],
+ "rotation": [
+ 0,
+ 0,
+ 0
+ ],
+ "scale": [
+ 1,
+ 1,
+ 1
+ ],
+ "deviceId": "EQ-003",
+ "modelId": "MODEL-EQUIPMENT-03",
+ "semantic": "实训装备",
+ "operable": true,
+ "capabilities": [
+ "select",
+ "inspect",
+ "transform"
+ ],
+ "assetCode": "",
+ "sourceAssetId": "",
+ "targetProjectId": "",
+ "targetVersionId": "",
+ "modelEditorAssetId": "",
+ "parts": []
+ },
+ "after": {
+ "sceneId": "OBJ-1788690589583-3",
+ "type": "equipment",
+ "name": "维修装备主体 03",
+ "visible": true,
+ "position": [
+ 1.5,
+ 0,
+ 0
+ ],
+ "rotation": [
+ 0,
+ 1.5707963267948966,
+ 0
+ ],
+ "scale": [
+ 1.8626076910667746,
+ 1,
+ 1
+ ],
+ "deviceId": "EQ-003",
+ "modelId": "MODEL-EQUIPMENT-03",
+ "semantic": "实训装备",
+ "operable": true,
+ "capabilities": [
+ "select",
+ "inspect",
+ "transform"
+ ],
+ "assetCode": "",
+ "sourceAssetId": "",
+ "targetProjectId": "",
+ "targetVersionId": "",
+ "modelEditorAssetId": "",
+ "parts": []
+ },
+ "screenshots": [
+ "43-real-transform-gizmo"
+ ]
+ }
+}
\ No newline at end of file
diff --git a/reports/scene-editor-closure-20260906/feature-catalog.json b/reports/scene-editor-closure-20260906/feature-catalog.json
new file mode 100644
index 0000000..6c196c8
--- /dev/null
+++ b/reports/scene-editor-closure-20260906/feature-catalog.json
@@ -0,0 +1,2052 @@
+[
+ {
+ "id": "project.load",
+ "group": "工程入口与顶部命令",
+ "name": "按真实项目 ID 打开已存场景",
+ "source": {
+ "file": "../../../unreal_tran_web/src/views/content/LegacyScenePageView.vue",
+ "symbol": "async function loadProject",
+ "line": 547
+ },
+ "implementation": "本轮修复已完成验证",
+ "boundary": "服务端 content 与版本是真值;不得以模板种子覆盖有效旧工程。",
+ "acceptance": "独立新会话打开,真实模型可见、固定版本及对象字段一致。",
+ "fix": "新实例部件 sceneId 包含所属根对象身份,不再直接复用来源模型部件 ID;源 interaction/editor 标识保留。旧单实例 ID 保留,旧文档重复 ID 仅按文档顺序迁移后续冲突实例并同步 override。第二完整轮进一步发现 GLB 并发完成顺序会改变根对象顺序;现等待 allSettled 成功后按原文档顺序排列恢复根对象,拆分模板返回数组内部顺序和既有对象均保留,同源两实例不再随网速互换列表位置。新增延迟 30/1/10ms 乱序加载合同,验证序列化仍按文档顺序;最终浏览器验证独立记录。",
+ "fixIds": [
+ "SCENE-F18"
+ ],
+ "verification": "PASS"
+ },
+ {
+ "id": "project.retry",
+ "group": "工程入口与顶部命令",
+ "name": "项目或挂载加载失败后重新加载",
+ "source": {
+ "file": "../../../unreal_tran_web/src/views/content/LegacyScenePageView.vue",
+ "symbol": "mountError",
+ "line": 76
+ },
+ "implementation": "本轮修复已完成验证",
+ "boundary": "错误详情自动消退,保留中性失败状态与重试按钮。",
+ "acceptance": "注入加载失败;无可写半场景;恢复网络后按钮重试成功。",
+ "fix": "前端目录和资源解析同时匹配指定项目、版本、code、id、URI、READY MODEL_FILE;错误引用不回退首文件或本地缓存。仅含 sourceAssetId/assetId 的本项目资源也必须将 ID 纳入缓存/去重键,防止已缓存有效引用掩盖另一个错误ID;失败请求缓存可重试。复制场景保留旧伪 URI 但只读取当前副本资产。",
+ "fixIds": [
+ "SCENE-F12"
+ ],
+ "verification": "PASS"
+ },
+ {
+ "id": "project.title",
+ "group": "工程入口与顶部命令",
+ "name": "当前场景名称与项目说明按钮",
+ "source": {
+ "file": "../../../unreal_tran_web/src/features/guide-legacy/demo-src/scene-editor.js",
+ "symbol": "project: () => this.toast",
+ "line": 599
+ },
+ "implementation": "已有实现已完成验证",
+ "boundary": "现有标题显示服务端名称;说明按钮不是重命名输入框。",
+ "acceptance": "长名称完整 title 正确;点击说明反映内容 API 管理。",
+ "fix": "尚未关联本轮修复记录",
+ "fixIds": [],
+ "verification": "PASS"
+ },
+ {
+ "id": "project.new",
+ "group": "工程入口与顶部命令",
+ "name": "顶部新建独立工程",
+ "source": {
+ "file": "../../../unreal_tran_web/src/features/guide-legacy/demo-src/scene-editor.js",
+ "symbol": "new: () =>",
+ "line": 575
+ },
+ "implementation": "本轮修复已完成验证",
+ "boundary": "基线代码重置当前模板;本轮由宿主接真实创建 API,必须保留原项目。",
+ "acceptance": "取消/失败不清原工程;成功新 ID;保存重开与列表对应。",
+ "fix": "顶部新建委托宿主命名与创建 API,先确认未保存内容。选择放弃也要等创建及导航成功后才清除原 dirty/恢复稿;创建503仍保留原稿。成功后刷新原缓存页,避免再次打开时将已放弃修改冒称已保存。取消、失败不清原场景,成功导航到新项目 ID,并有创建并发守卫。",
+ "fixIds": [
+ "SCENE-F02"
+ ],
+ "verification": "PASS"
+ },
+ {
+ "id": "project.back",
+ "group": "工程入口与顶部命令",
+ "name": "返回场景制作列表并保留场景标签",
+ "source": {
+ "file": "../../../unreal_tran_web/src/features/guide-legacy/demo-src/scene-editor.js",
+ "symbol": "back: () =>",
+ "line": 598
+ },
+ "implementation": "已有实现已完成验证",
+ "boundary": "平台约定返回列表等同切换标签,保留场景缓存和未存稿,不弹关闭确认;真正关闭标签才询问。",
+ "acceptance": "未存状态返回列表不写服务器、不丢场景;重新点原标签恢复输入和选择。",
+ "fix": "尚未关联本轮修复记录",
+ "fixIds": [],
+ "verification": "PASS"
+ },
+ {
+ "id": "project.save",
+ "group": "工程入口与顶部命令",
+ "name": "保存草稿按钮",
+ "source": {
+ "file": "../../../unreal_tran_web/src/features/guide-legacy/demo-src/scene-editor.js",
+ "symbol": "async saveProject()",
+ "line": 1874
+ },
+ "implementation": "本轮修复已完成验证",
+ "boundary": "只有服务器保存完成才显示成功;本地恢复缓冲不是自动入库。",
+ "acceptance": "编辑后真实 PUT,刷新值一致,version 前进一次。",
+ "fix": "完整 PUT 回写原有相同 SCENE_MODEL 三元组时沿用场景授权边界;新增或改版依赖仍要求来源模型可见及范围权限,不扩大模型页面访问权。",
+ "fixIds": [
+ "SCENE-F14"
+ ],
+ "verification": "PASS"
+ },
+ {
+ "id": "project.save-key",
+ "group": "工程入口与顶部命令",
+ "name": "Ctrl/Meta+S 保存快捷键",
+ "source": {
+ "file": "../../../unreal_tran_web/src/features/guide-legacy/demo-src/scene-editor.js",
+ "symbol": "handleKeyboard(event)",
+ "line": 469
+ },
+ "implementation": "已有实现已完成验证",
+ "boundary": "键盘与按钮走相同保存/权限/忙碌守卫。",
+ "acceptance": "实际按键触发一次保存;表单内与非表单焦点分别核查。",
+ "fix": "尚未关联本轮修复记录",
+ "fixIds": [],
+ "verification": "PASS"
+ },
+ {
+ "id": "project.save-failure",
+ "group": "工程入口与顶部命令",
+ "name": "保存失败保留修改和恢复稿",
+ "source": {
+ "file": "../../../unreal_tran_web/src/features/guide-legacy/demo-src/scene-editor.js",
+ "symbol": "async saveProject()",
+ "line": 1874
+ },
+ "implementation": "已有实现已完成验证",
+ "boundary": "网络失败或业务拒绝不能清 dirty;不弹虚假保存成功。",
+ "acceptance": "定向 503 后对象/字段保留,恢复后重试与刷新成功。",
+ "fix": "尚未关联本轮修复记录",
+ "fixIds": [],
+ "verification": "PASS"
+ },
+ {
+ "id": "project.conflict",
+ "group": "工程入口与顶部命令",
+ "name": "409 乐观锁冲突保护",
+ "source": {
+ "file": "../../../unreal_tran_web/src/views/content/LegacyScenePageView.vue",
+ "symbol": "function describeSaveError",
+ "line": 432
+ },
+ "implementation": "已有实现已完成验证",
+ "boundary": "服务器最新版本不覆盖;恢复副本可下载/隔离。",
+ "acceptance": "独立副本制造版本冲突;验证失败提示、旧版本不覆盖与本地稿保留。",
+ "fix": "尚未关联本轮修复记录",
+ "fixIds": [],
+ "verification": "PASS"
+ },
+ {
+ "id": "project.concurrent-save",
+ "group": "工程入口与顶部命令",
+ "name": "重复保存与保存中继续编辑",
+ "source": {
+ "file": "../../../unreal_tran_web/src/features/guide-legacy/demo-src/scene-editor.js",
+ "symbol": "const saveRevision = this.changeRevision",
+ "line": 1887
+ },
+ "implementation": "本轮修复已完成验证",
+ "boundary": "提交快照与后续编辑分开;后续修改不能随早先请求完成清掉。",
+ "acceptance": "延迟 PUT、连点保存并继续改值;一次旧提交、新修改仍 dirty。",
+ "fix": "开始保存时取消之前安排的恢复稿定时器,避免它覆盖宿主保存快照 nonce;保存中的后续编辑仍单独保留。",
+ "fixIds": [
+ "SCENE-F08"
+ ],
+ "verification": "PASS"
+ },
+ {
+ "id": "json.export",
+ "group": "工程文件与资源导入",
+ "name": "导出当前场景 JSON",
+ "source": {
+ "file": "../../../unreal_tran_web/src/features/guide-legacy/demo-src/scene-editor.js",
+ "symbol": "exportProject()",
+ "line": 578
+ },
+ "implementation": "已有实现已完成验证",
+ "boundary": "导出 edit3dv4.scene、对象/部件覆盖、渲染和稳定资源引用;不冒称自包含 GLB 包。",
+ "acceptance": "打开下载 JSON 核对内容、结构和无临时 blob 依赖。",
+ "fix": "尚未关联本轮修复记录",
+ "fixIds": [],
+ "verification": "PASS"
+ },
+ {
+ "id": "json.import",
+ "group": "工程文件与资源导入",
+ "name": "导入有效场景工程 JSON",
+ "source": {
+ "file": "../../../unreal_tran_web/src/features/guide-legacy/demo-src/scene-editor.js",
+ "symbol": "importProject(event)",
+ "line": 462
+ },
+ "implementation": "本轮修复已完成验证",
+ "boundary": "替换当前编辑文档后仍需保存;不得写入另一个项目身份。",
+ "acceptance": "导出后实际文件导入、保存、重开;对象与渲染参数往返一致。",
+ "fix": "模板按钮使用模板种子,不再次读当前项目旧文档;模板/JSON 替换先保存当前运行对象,等待全部加载结束,失败恢复原环境、对象、选择和 dirty;真实确认取消保留原稿。",
+ "fixIds": [
+ "SCENE-F03"
+ ],
+ "verification": "PASS"
+ },
+ {
+ "id": "json.invalid",
+ "group": "工程文件与资源导入",
+ "name": "非法 JSON / 错误 schema 拒绝",
+ "source": {
+ "file": "../../../unreal_tran_web/src/features/guide-legacy/demo-src/scene-editor.js",
+ "symbol": "importProject(event)",
+ "line": 462
+ },
+ "implementation": "本轮修复已完成验证",
+ "boundary": "解析或结构错误不得清空当前场景。",
+ "acceptance": "非法 JSON 与非场景文档各一次,原工程和模型仍可编辑。",
+ "fix": "模板按钮使用模板种子,不再次读当前项目旧文档;模板/JSON 替换先保存当前运行对象,等待全部加载结束,失败恢复原环境、对象、选择和 dirty;真实确认取消保留原稿。",
+ "fixIds": [
+ "SCENE-F03"
+ ],
+ "verification": "PASS"
+ },
+ {
+ "id": "json.missing-resource",
+ "group": "工程文件与资源导入",
+ "name": "JSON 缺失/错误受控引用拒绝与回滚",
+ "source": {
+ "file": "../../../unreal_tran_web/src/features/guide-legacy/demo-src/scene-editor.js",
+ "symbol": "async restoreImportedObject",
+ "line": 2016
+ },
+ "implementation": "本轮修复已完成验证",
+ "boundary": "错误资产编码不能回退为另一个模型;恢复失败不半替换。",
+ "acceptance": "导入显式不存在编码/版本;原对象保留或明确阻断,零错误发布。",
+ "fix": "模板按钮使用模板种子,不再次读当前项目旧文档;模板/JSON 替换先保存当前运行对象,等待全部加载结束,失败恢复原环境、对象、选择和 dirty;真实确认取消保留原稿。;前端目录和资源解析同时匹配指定项目、版本、code、id、URI、READY MODEL_FILE;错误引用不回退首文件或本地缓存。仅含 sourceAssetId/assetId 的本项目资源也必须将 ID 纳入缓存/去重键,防止已缓存有效引用掩盖另一个错误ID;失败请求缓存可重试。复制场景保留旧伪 URI 但只读取当前副本资产。",
+ "fixIds": [
+ "SCENE-F03",
+ "SCENE-F12"
+ ],
+ "verification": "PASS"
+ },
+ {
+ "id": "json.rendering",
+ "group": "工程文件与资源导入",
+ "name": "相机、灯光、雾和环境序列化往返",
+ "source": {
+ "file": "../../../unreal_tran_web/src/features/guide-legacy/demo-src/scene-editor.js",
+ "symbol": "serializeRendering()",
+ "line": 857
+ },
+ "implementation": "本轮修复已完成验证",
+ "boundary": "当前默认 UI 不提供全部渲染编辑器;导入/保存仍须保留其文档参数。",
+ "acceptance": "合法带渲染参数文档导入、实际画面比对、保存重开字段一致。",
+ "fix": "透视按钮实际恢复先前观察位置;导入、加载和历史恢复存在已保存相机时不再被车辆自动聚焦覆盖。",
+ "fixIds": [
+ "SCENE-F04"
+ ],
+ "verification": "PASS"
+ },
+ {
+ "id": "glb.import",
+ "group": "工程文件与资源导入",
+ "name": "本地 GLB 上传并加入当前场景",
+ "source": {
+ "file": "../../../unreal_tran_web/src/features/guide-legacy/demo-src/scene-editor.js",
+ "symbol": "async importGLB(event)",
+ "line": 2122
+ },
+ "implementation": "本轮修复已完成验证",
+ "boundary": "上传到内容服务,消费票据后解析;对象加入不等于工程已保存。",
+ "acceptance": "真实选择小 GLB,上传/登记/可见/保存重开均完成。",
+ "fix": "恢复显示名、设备和物模型编号使用空值保留规则,不把显式空串补回旧值;新资源序号跳过既有编号,空白编号校验按 trim 处理。",
+ "fixIds": [
+ "SCENE-F06"
+ ],
+ "verification": "PASS"
+ },
+ {
+ "id": "glb.invalid",
+ "group": "工程文件与资源导入",
+ "name": "非 GLB / 损坏 GLB / 上传失败处理",
+ "source": {
+ "file": "../../../unreal_tran_web/src/features/guide-legacy/demo-src/scene-editor.js",
+ "symbol": "async importGLB(event)",
+ "line": 2122
+ },
+ "implementation": "已有实现已完成验证",
+ "boundary": "上传或解析失败不插入占位对象;原场景应保留,提示可重试。",
+ "acceptance": "错误格式及损坏文件、上传 503;验证对象数/旧资产/后续成功导入。",
+ "fix": "尚未关联本轮修复记录",
+ "fixIds": [],
+ "verification": "PASS"
+ },
+ {
+ "id": "glb.pending",
+ "group": "工程文件与资源导入",
+ "name": "资源加载/上传期间操作互斥",
+ "source": {
+ "file": "../../../unreal_tran_web/src/features/guide-legacy/demo-src/scene-editor.js",
+ "symbol": "setModelLoading(loading",
+ "line": 1411
+ },
+ "implementation": "已有实现已完成验证",
+ "boundary": "加载中的模型不能被半保存、导出或发布;不能离开造成旧请求污染新项目。",
+ "acceptance": "延迟下载/上传,操作禁用与待完成状态一致,完成后恢复。",
+ "fix": "尚未关联本轮修复记录",
+ "fixIds": [],
+ "verification": "PASS"
+ },
+ {
+ "id": "glb.references",
+ "group": "工程文件与资源导入",
+ "name": "本项目 GLB 稳定资产引用",
+ "source": {
+ "file": "../../../unreal_tran_web/src/features/scene-legacy/serverAssetStore.ts",
+ "symbol": "async put(blob",
+ "line": 113
+ },
+ "implementation": "本轮修复已完成验证",
+ "boundary": "自导入使用 content://asset/scene/{projectId}/{assetCode} 和 READY 资产;blob 只供运行。",
+ "acceptance": "重新会话受控下载成功,主对象/资产身份一致,无本地缓存依赖。",
+ "fix": "前端目录和资源解析同时匹配指定项目、版本、code、id、URI、READY MODEL_FILE;错误引用不回退首文件或本地缓存。仅含 sourceAssetId/assetId 的本项目资源也必须将 ID 纳入缓存/去重键,防止已缓存有效引用掩盖另一个错误ID;失败请求缓存可重试。复制场景保留旧伪 URI 但只读取当前副本资产。;新增 SceneResourceReferenceValidator 检查 objects 和递归 splitParts;自有/固定版本资源的所有显式引用必须指向同一 READY 模型文件,错误引用在发布/提交前阻断,保留受控的内置资源和旧引用兼容。",
+ "fixIds": [
+ "SCENE-F12",
+ "SCENE-F13"
+ ],
+ "verification": "PASS"
+ },
+ {
+ "id": "panel.left-tabs",
+ "group": "左侧目录与资源放置",
+ "name": "场景大纲 / 资源库 / 场景模板切换",
+ "source": {
+ "file": "../../../unreal_tran_web/src/features/guide-legacy/demo-src/scene-editor.js",
+ "symbol": "const leftTab =",
+ "line": 540
+ },
+ "implementation": "已有实现已完成验证",
+ "boundary": "切面板不写业务文档;筛选状态行为需一致。",
+ "acceptance": "三页签均可达,无 DOM 异常或资源目录中断。",
+ "fix": "尚未关联本轮修复记录",
+ "fixIds": [],
+ "verification": "PASS"
+ },
+ {
+ "id": "search.tree",
+ "group": "左侧目录与资源放置",
+ "name": "对象/部件名称搜索与清空",
+ "source": {
+ "file": "../../../unreal_tran_web/src/features/guide-legacy/demo-src/scene-editor.js",
+ "symbol": "renderTree()",
+ "line": 627
+ },
+ "implementation": "已有实现已完成验证",
+ "boundary": "根名与部件名可命中;无匹配状态应清楚,非删除对象。",
+ "acceptance": "根名/部件名/无结果/清空分别核对行和真实对象数。",
+ "fix": "尚未关联本轮修复记录",
+ "fixIds": [],
+ "verification": "PASS"
+ },
+ {
+ "id": "search.assets",
+ "group": "左侧目录与资源放置",
+ "name": "资源搜索与异步刷新保留筛选",
+ "source": {
+ "file": "../../../unreal_tran_web/src/features/guide-legacy/demo-src/scene-editor.js",
+ "symbol": "applyLeftFilter()",
+ "line": 742
+ },
+ "implementation": "已有实现已完成验证",
+ "boundary": "只筛当前目录;异步重绘不复活不匹配卡片。",
+ "acceptance": "命中、无结果、清空,目录重绘后过滤仍正确。",
+ "fix": "尚未关联本轮修复记录",
+ "fixIds": [],
+ "verification": "PASS"
+ },
+ {
+ "id": "search.templates",
+ "group": "左侧目录与资源放置",
+ "name": "模板搜索与清空",
+ "source": {
+ "file": "../../../unreal_tran_web/src/features/guide-legacy/demo-src/scene-editor.js",
+ "symbol": "filterLeft(query)",
+ "line": 2278
+ },
+ "implementation": "已有实现已完成验证",
+ "boundary": "模板卡片与同一搜索框联动。",
+ "acceptance": "中文模板关键字过滤、无结果和清空。",
+ "fix": "尚未关联本轮修复记录",
+ "fixIds": [],
+ "verification": "PASS"
+ },
+ {
+ "id": "assets.catalog",
+ "group": "左侧目录与资源放置",
+ "name": "真实已发布模型成果目录",
+ "source": {
+ "file": "../../../unreal_tran_web/src/features/scene-legacy/contentApiClient.ts",
+ "symbol": "async listPublishedModels",
+ "line": 783
+ },
+ "implementation": "本轮修复已完成验证",
+ "boundary": "目录由 API 预加载,当前默认不以 localStorage 发布清单为真值。",
+ "acceptance": "显示发布模型名称/版本,草稿不冒充已发布,空态可操作。",
+ "fix": "移除 runtime 旧 content.update 门槛,独立计算新建、更新和发布;无模型页面权限时不加载新模型目录,但既有固定依赖仍可按场景授权读取。",
+ "fixIds": [
+ "SCENE-F01"
+ ],
+ "verification": "PASS"
+ },
+ {
+ "id": "assets.model-double-click",
+ "group": "左侧目录与资源放置",
+ "name": "双击已发布模型放置",
+ "source": {
+ "file": "../../../unreal_tran_web/src/features/guide-legacy/demo-src/scene-editor.js",
+ "symbol": "async placeEditorAsset",
+ "line": 1357
+ },
+ "implementation": "已有实现已完成验证",
+ "boundary": "按固定 project/version/assetCode 导入当前实例,继承发布部件和材质。",
+ "acceptance": "狐狸和卡车分别双击加入、真实可见、保存重开固定版本。",
+ "fix": "尚未关联本轮修复记录",
+ "fixIds": [],
+ "verification": "PASS"
+ },
+ {
+ "id": "assets.model-drop",
+ "group": "左侧目录与资源放置",
+ "name": "拖入已发布模型放置",
+ "source": {
+ "file": "../../../unreal_tran_web/src/features/guide-legacy/demo-src/scene-editor.js",
+ "symbol": "handleDrop(event)",
+ "line": 461
+ },
+ "implementation": "已有实现已完成验证",
+ "boundary": "拖放实际命中地平面;与双击共用受控资产链路。",
+ "acceptance": "真实指针拖放到不同位置,坐标/实例数/固定引用准确。",
+ "fix": "尚未关联本轮修复记录",
+ "fixIds": [],
+ "verification": "PASS"
+ },
+ {
+ "id": "assets.basic-double-click",
+ "group": "左侧目录与资源放置",
+ "name": "双击基础资源放置",
+ "source": {
+ "file": "../../../unreal_tran_web/src/features/guide-legacy/demo-src/scene-editor.js",
+ "symbol": "onRootDblClick",
+ "line": 429
+ },
+ "implementation": "本轮修复已完成验证",
+ "boundary": "以当前轨道目标落地;每次生成独立对象身份。",
+ "acceptance": "双击生成对象,变换、保存后仍可见。",
+ "fix": "恢复显示名、设备和物模型编号使用空值保留规则,不把显式空串补回旧值;新资源序号跳过既有编号,空白编号校验按 trim 处理。",
+ "fixIds": [
+ "SCENE-F06"
+ ],
+ "verification": "PASS"
+ },
+ {
+ "id": "assets.basic-drop",
+ "group": "左侧目录与资源放置",
+ "name": "基础资源拖入与拖放提示",
+ "source": {
+ "file": "../../../unreal_tran_web/src/features/guide-legacy/demo-src/scene-editor.js",
+ "symbol": "handleDrop(event)",
+ "line": 461
+ },
+ "implementation": "已有实现已完成验证",
+ "boundary": "MIME 和 text/plain 回退协议一致;离开视口清提示。",
+ "acceptance": "真实拖放命中位置与 0.5m 吸附、dragleave/drop 提示复位。",
+ "fix": "尚未关联本轮修复记录",
+ "fixIds": [],
+ "verification": "PASS"
+ },
+ {
+ "id": "assets.model-link",
+ "group": "左侧目录与资源放置",
+ "name": "打开/返回模型编辑器入口",
+ "source": {
+ "file": "../../../unreal_tran_web/src/features/guide-legacy/demo-src/scene-editor.js",
+ "symbol": "open-model-editor",
+ "line": 590
+ },
+ "implementation": "本轮修复已完成验证",
+ "boundary": "当前基线只有跳模型列表,来源按钮不得冒称直达正确源项目。",
+ "acceptance": "空资源库和选中模型来源两入口,跳转/未保存保护正确。",
+ "fix": "模型来源按钮有来源权限时导航正确项目;无来源页面权限回到可访问列表,不将模型列表误称为来源工程编辑。",
+ "fixIds": [
+ "SCENE-F11"
+ ],
+ "verification": "PASS"
+ },
+ {
+ "id": "asset.diagnostic",
+ "group": "左侧目录与资源放置",
+ "name": "基础资源:液压诊断工位",
+ "source": {
+ "file": "../../../unreal_tran_web/src/features/guide-legacy/demo-src/scene-editor.js",
+ "symbol": "id: 'diagnostic'",
+ "line": 123
+ },
+ "implementation": "已有实现已完成验证",
+ "boundary": "现有程序化场景资源;名称不代表已连接物理设备或真实人员。",
+ "acceptance": "实际放置可见,独立编号、位置和保存重开一致;照明应区分灯具外观与光源效果。",
+ "fix": "尚未关联本轮修复记录",
+ "fixIds": [],
+ "verification": "PASS"
+ },
+ {
+ "id": "asset.equipment",
+ "group": "左侧目录与资源放置",
+ "name": "基础资源:维修装备主体",
+ "source": {
+ "file": "../../../unreal_tran_web/src/features/guide-legacy/demo-src/scene-editor.js",
+ "symbol": "id: 'equipment'",
+ "line": 124
+ },
+ "implementation": "已有实现已完成验证",
+ "boundary": "现有程序化场景资源;名称不代表已连接物理设备或真实人员。",
+ "acceptance": "实际放置可见,独立编号、位置和保存重开一致;照明应区分灯具外观与光源效果。",
+ "fix": "尚未关联本轮修复记录",
+ "fixIds": [],
+ "verification": "PASS"
+ },
+ {
+ "id": "asset.workbench",
+ "group": "左侧目录与资源放置",
+ "name": "基础资源:检修工作台",
+ "source": {
+ "file": "../../../unreal_tran_web/src/features/guide-legacy/demo-src/scene-editor.js",
+ "symbol": "id: 'workbench'",
+ "line": 125
+ },
+ "implementation": "已有实现已完成验证",
+ "boundary": "现有程序化场景资源;名称不代表已连接物理设备或真实人员。",
+ "acceptance": "实际放置可见,独立编号、位置和保存重开一致;照明应区分灯具外观与光源效果。",
+ "fix": "尚未关联本轮修复记录",
+ "fixIds": [],
+ "verification": "PASS"
+ },
+ {
+ "id": "asset.shelf",
+ "group": "左侧目录与资源放置",
+ "name": "基础资源:器材存放架",
+ "source": {
+ "file": "../../../unreal_tran_web/src/features/guide-legacy/demo-src/scene-editor.js",
+ "symbol": "id: 'shelf'",
+ "line": 126
+ },
+ "implementation": "已有实现已完成验证",
+ "boundary": "现有程序化场景资源;名称不代表已连接物理设备或真实人员。",
+ "acceptance": "实际放置可见,独立编号、位置和保存重开一致;照明应区分灯具外观与光源效果。",
+ "fix": "尚未关联本轮修复记录",
+ "fixIds": [],
+ "verification": "PASS"
+ },
+ {
+ "id": "asset.safety-zone",
+ "group": "左侧目录与资源放置",
+ "name": "基础资源:安全作业区域",
+ "source": {
+ "file": "../../../unreal_tran_web/src/features/guide-legacy/demo-src/scene-editor.js",
+ "symbol": "id: 'safety-zone'",
+ "line": 127
+ },
+ "implementation": "已有实现已完成验证",
+ "boundary": "现有程序化场景资源;名称不代表已连接物理设备或真实人员。",
+ "acceptance": "实际放置可见,独立编号、位置和保存重开一致;照明应区分灯具外观与光源效果。",
+ "fix": "尚未关联本轮修复记录",
+ "fixIds": [],
+ "verification": "PASS"
+ },
+ {
+ "id": "asset.camera",
+ "group": "左侧目录与资源放置",
+ "name": "基础资源:视觉采集设备",
+ "source": {
+ "file": "../../../unreal_tran_web/src/features/guide-legacy/demo-src/scene-editor.js",
+ "symbol": "id: 'camera'",
+ "line": 128
+ },
+ "implementation": "已有实现已完成验证",
+ "boundary": "现有程序化场景资源;名称不代表已连接物理设备或真实人员。",
+ "acceptance": "实际放置可见,独立编号、位置和保存重开一致;照明应区分灯具外观与光源效果。",
+ "fix": "尚未关联本轮修复记录",
+ "fixIds": [],
+ "verification": "PASS"
+ },
+ {
+ "id": "asset.worker",
+ "group": "左侧目录与资源放置",
+ "name": "基础资源:训练人员占位",
+ "source": {
+ "file": "../../../unreal_tran_web/src/features/guide-legacy/demo-src/scene-editor.js",
+ "symbol": "id: 'worker'",
+ "line": 129
+ },
+ "implementation": "已有实现已完成验证",
+ "boundary": "现有程序化场景资源;名称不代表已连接物理设备或真实人员。",
+ "acceptance": "实际放置可见,独立编号、位置和保存重开一致;照明应区分灯具外观与光源效果。",
+ "fix": "尚未关联本轮修复记录",
+ "fixIds": [],
+ "verification": "PASS"
+ },
+ {
+ "id": "asset.light",
+ "group": "左侧目录与资源放置",
+ "name": "基础资源:区域照明",
+ "source": {
+ "file": "../../../unreal_tran_web/src/features/guide-legacy/demo-src/scene-editor.js",
+ "symbol": "id: 'light'",
+ "line": 130
+ },
+ "implementation": "已有实现已完成验证",
+ "boundary": "现有程序化场景资源;名称不代表已连接物理设备或真实人员。",
+ "acceptance": "实际放置可见,独立编号、位置和保存重开一致;照明应区分灯具外观与光源效果。",
+ "fix": "尚未关联本轮修复记录",
+ "fixIds": [],
+ "verification": "PASS"
+ },
+ {
+ "id": "asset.vehicle-depot",
+ "group": "左侧目录与资源放置",
+ "name": "场地资源:真实装备车库",
+ "source": {
+ "file": "../../../unreal_tran_web/src/features/guide-legacy/demo-src/scene-editor.js",
+ "symbol": "id: 'vehicle-depot'",
+ "line": 121
+ },
+ "implementation": "已有实现已完成验证",
+ "boundary": "整体场地 GLB 与维修车间分开;重复放置按既有规则处理。",
+ "acceptance": "资源拖放或双击加载、比例/落地正常、重复放置不产生错误重叠。",
+ "fix": "尚未关联本轮修复记录",
+ "fixIds": [],
+ "verification": "PASS"
+ },
+ {
+ "id": "asset.field-terrain",
+ "group": "左侧目录与资源放置",
+ "name": "场地资源:真实野外地形",
+ "source": {
+ "file": "../../../unreal_tran_web/src/features/guide-legacy/demo-src/scene-editor.js",
+ "symbol": "id: 'field-terrain'",
+ "line": 122
+ },
+ "implementation": "已有实现已完成验证",
+ "boundary": "真实地形资源和指定落地基准;不能以程序地板代替。",
+ "acceptance": "加载后真实起伏地形可见,保存重开资源、地面高度一致。",
+ "fix": "尚未关联本轮修复记录",
+ "fixIds": [],
+ "verification": "PASS"
+ },
+ {
+ "id": "template.depot-1",
+ "group": "全部可达场景模板",
+ "name": "真实装备车库",
+ "source": {
+ "file": "../../../unreal_tran_web/src/features/guide-legacy/demo-src/scene-editor.js",
+ "symbol": "id: 'depot-1'",
+ "line": 86
+ },
+ "implementation": "本轮修复已完成验证",
+ "boundary": "模板场地与种子布局须有实际执行;切模板不是创建新的业务项目。",
+ "acceptance": "独立副本点击模板、确认/取消未存操作、真实画面、保存重开 presetId/环境/对象一致。",
+ "fix": "模板按钮使用模板种子,不再次读当前项目旧文档;模板/JSON 替换先保存当前运行对象,等待全部加载结束,失败恢复原环境、对象、选择和 dirty;真实确认取消保留原稿。",
+ "fixIds": [
+ "SCENE-F03"
+ ],
+ "verification": "PASS"
+ },
+ {
+ "id": "template.terrain-1",
+ "group": "全部可达场景模板",
+ "name": "真实野外训练场",
+ "source": {
+ "file": "../../../unreal_tran_web/src/features/guide-legacy/demo-src/scene-editor.js",
+ "symbol": "id: 'terrain-1'",
+ "line": 87
+ },
+ "implementation": "本轮修复已完成验证",
+ "boundary": "模板场地与种子布局须有实际执行;切模板不是创建新的业务项目。",
+ "acceptance": "独立副本点击模板、确认/取消未存操作、真实画面、保存重开 presetId/环境/对象一致。",
+ "fix": "模板按钮使用模板种子,不再次读当前项目旧文档;模板/JSON 替换先保存当前运行对象,等待全部加载结束,失败恢复原环境、对象、选择和 dirty;真实确认取消保留原稿。",
+ "fixIds": [
+ "SCENE-F03"
+ ],
+ "verification": "PASS"
+ },
+ {
+ "id": "template.workshop-1",
+ "group": "全部可达场景模板",
+ "name": "综合检验车间",
+ "source": {
+ "file": "../../../unreal_tran_web/src/features/guide-legacy/demo-src/scene-editor.js",
+ "symbol": "id: 'workshop-1'",
+ "line": 88
+ },
+ "implementation": "本轮修复已完成验证",
+ "boundary": "模板场地与种子布局须有实际执行;切模板不是创建新的业务项目。",
+ "acceptance": "独立副本点击模板、确认/取消未存操作、真实画面、保存重开 presetId/环境/对象一致。",
+ "fix": "模板按钮使用模板种子,不再次读当前项目旧文档;模板/JSON 替换先保存当前运行对象,等待全部加载结束,失败恢复原环境、对象、选择和 dirty;真实确认取消保留原稿。",
+ "fixIds": [
+ "SCENE-F03"
+ ],
+ "verification": "PASS"
+ },
+ {
+ "id": "template.workshop-real",
+ "group": "全部可达场景模板",
+ "name": "演示维修车间(可拆分)",
+ "source": {
+ "file": "../../../unreal_tran_web/src/features/guide-legacy/demo-src/scene-editor.js",
+ "symbol": "id: 'workshop-real'",
+ "line": 89
+ },
+ "implementation": "本轮修复已完成验证",
+ "boundary": "模板场地与种子布局须有实际执行;切模板不是创建新的业务项目。",
+ "acceptance": "独立副本点击模板、确认/取消未存操作、真实画面、保存重开 presetId/环境/对象一致。",
+ "fix": "模板按钮使用模板种子,不再次读当前项目旧文档;模板/JSON 替换先保存当前运行对象,等待全部加载结束,失败恢复原环境、对象、选择和 dirty;真实确认取消保留原稿。",
+ "fixIds": [
+ "SCENE-F03"
+ ],
+ "verification": "PASS"
+ },
+ {
+ "id": "template.workshop-2",
+ "group": "全部可达场景模板",
+ "name": "总成拆卸车间",
+ "source": {
+ "file": "../../../unreal_tran_web/src/features/guide-legacy/demo-src/scene-editor.js",
+ "symbol": "id: 'workshop-2'",
+ "line": 90
+ },
+ "implementation": "本轮修复已完成验证",
+ "boundary": "模板场地与种子布局须有实际执行;切模板不是创建新的业务项目。",
+ "acceptance": "独立副本点击模板、确认/取消未存操作、真实画面、保存重开 presetId/环境/对象一致。",
+ "fix": "模板按钮使用模板种子,不再次读当前项目旧文档;模板/JSON 替换先保存当前运行对象,等待全部加载结束,失败恢复原环境、对象、选择和 dirty;真实确认取消保留原稿。",
+ "fixIds": [
+ "SCENE-F03"
+ ],
+ "verification": "PASS"
+ },
+ {
+ "id": "template.workshop-3",
+ "group": "全部可达场景模板",
+ "name": "部件分解车间",
+ "source": {
+ "file": "../../../unreal_tran_web/src/features/guide-legacy/demo-src/scene-editor.js",
+ "symbol": "id: 'workshop-3'",
+ "line": 91
+ },
+ "implementation": "本轮修复已完成验证",
+ "boundary": "模板场地与种子布局须有实际执行;切模板不是创建新的业务项目。",
+ "acceptance": "独立副本点击模板、确认/取消未存操作、真实画面、保存重开 presetId/环境/对象一致。",
+ "fix": "模板按钮使用模板种子,不再次读当前项目旧文档;模板/JSON 替换先保存当前运行对象,等待全部加载结束,失败恢复原环境、对象、选择和 dirty;真实确认取消保留原稿。",
+ "fixIds": [
+ "SCENE-F03"
+ ],
+ "verification": "PASS"
+ },
+ {
+ "id": "template.workshop-4",
+ "group": "全部可达场景模板",
+ "name": "清洗检测车间",
+ "source": {
+ "file": "../../../unreal_tran_web/src/features/guide-legacy/demo-src/scene-editor.js",
+ "symbol": "id: 'workshop-4'",
+ "line": 92
+ },
+ "implementation": "本轮修复已完成验证",
+ "boundary": "模板场地与种子布局须有实际执行;切模板不是创建新的业务项目。",
+ "acceptance": "独立副本点击模板、确认/取消未存操作、真实画面、保存重开 presetId/环境/对象一致。",
+ "fix": "模板按钮使用模板种子,不再次读当前项目旧文档;模板/JSON 替换先保存当前运行对象,等待全部加载结束,失败恢复原环境、对象、选择和 dirty;真实确认取消保留原稿。",
+ "fixIds": [
+ "SCENE-F03"
+ ],
+ "verification": "PASS"
+ },
+ {
+ "id": "template.workshop-5",
+ "group": "全部可达场景模板",
+ "name": "复装调试车间",
+ "source": {
+ "file": "../../../unreal_tran_web/src/features/guide-legacy/demo-src/scene-editor.js",
+ "symbol": "id: 'workshop-5'",
+ "line": 93
+ },
+ "implementation": "本轮修复已完成验证",
+ "boundary": "模板场地与种子布局须有实际执行;切模板不是创建新的业务项目。",
+ "acceptance": "独立副本点击模板、确认/取消未存操作、真实画面、保存重开 presetId/环境/对象一致。",
+ "fix": "模板按钮使用模板种子,不再次读当前项目旧文档;模板/JSON 替换先保存当前运行对象,等待全部加载结束,失败恢复原环境、对象、选择和 dirty;真实确认取消保留原稿。",
+ "fixIds": [
+ "SCENE-F03"
+ ],
+ "verification": "PASS"
+ },
+ {
+ "id": "template.outdoor-1",
+ "group": "全部可达场景模板",
+ "name": "室外综合训练场",
+ "source": {
+ "file": "../../../unreal_tran_web/src/features/guide-legacy/demo-src/scene-editor.js",
+ "symbol": "id: 'outdoor-1'",
+ "line": 94
+ },
+ "implementation": "本轮修复已完成验证",
+ "boundary": "模板场地与种子布局须有实际执行;切模板不是创建新的业务项目。",
+ "acceptance": "独立副本点击模板、确认/取消未存操作、真实画面、保存重开 presetId/环境/对象一致。",
+ "fix": "模板按钮使用模板种子,不再次读当前项目旧文档;模板/JSON 替换先保存当前运行对象,等待全部加载结束,失败恢复原环境、对象、选择和 dirty;真实确认取消保留原稿。",
+ "fixIds": [
+ "SCENE-F03"
+ ],
+ "verification": "PASS"
+ },
+ {
+ "id": "template.switch-cancel",
+ "group": "全部可达场景模板",
+ "name": "未保存模板切换取消",
+ "source": {
+ "file": "../../../unreal_tran_web/src/features/guide-legacy/demo-src/scene-editor.js",
+ "symbol": "async loadPreset",
+ "line": 765
+ },
+ "implementation": "本轮修复已完成验证",
+ "boundary": "取消保留原场景、选择与 dirty;请求中不能切换半场景。",
+ "acceptance": "有未存修改时取消/确认分别验证,取消零对象和服务端变更。",
+ "fix": "模板按钮使用模板种子,不再次读当前项目旧文档;模板/JSON 替换先保存当前运行对象,等待全部加载结束,失败恢复原环境、对象、选择和 dirty;真实确认取消保留原稿。",
+ "fixIds": [
+ "SCENE-F03"
+ ],
+ "verification": "PASS"
+ },
+ {
+ "id": "view.orbit",
+ "group": "三维视口、选择与变换",
+ "name": "鼠标旋转相机",
+ "source": {
+ "file": "../../../unreal_tran_web/src/features/guide-legacy/demo-src/scene-editor.js",
+ "symbol": "new OrbitControls",
+ "line": 379
+ },
+ "implementation": "已有实现已完成验证",
+ "boundary": "相机轨道操作不应误选、拖动物体。",
+ "acceptance": "实际鼠标拖动,相机变化且对象姿态不变。",
+ "fix": "尚未关联本轮修复记录",
+ "fixIds": [],
+ "verification": "PASS"
+ },
+ {
+ "id": "view.pan",
+ "group": "三维视口、选择与变换",
+ "name": "鼠标平移相机",
+ "source": {
+ "file": "../../../unreal_tran_web/src/features/guide-legacy/demo-src/scene-editor.js",
+ "symbol": "new OrbitControls",
+ "line": 379
+ },
+ "implementation": "已有实现已完成验证",
+ "boundary": "轨道目标平移与物体位置分开。",
+ "acceptance": "实际右键/约定鼠标平移,目标变化且对象数据不变。",
+ "fix": "尚未关联本轮修复记录",
+ "fixIds": [],
+ "verification": "PASS"
+ },
+ {
+ "id": "view.zoom",
+ "group": "三维视口、选择与变换",
+ "name": "滚轮缩放相机",
+ "source": {
+ "file": "../../../unreal_tran_web/src/features/guide-legacy/demo-src/scene-editor.js",
+ "symbol": "new OrbitControls",
+ "line": 379
+ },
+ "implementation": "已有实现已完成验证",
+ "boundary": "改变观察距离,不改对象 scale。",
+ "acceptance": "滚轮前后模型可见且对象 scale 不变。",
+ "fix": "尚未关联本轮修复记录",
+ "fixIds": [],
+ "verification": "PASS"
+ },
+ {
+ "id": "view.perspective",
+ "group": "三维视口、选择与变换",
+ "name": "透视按钮",
+ "source": {
+ "file": "../../../unreal_tran_web/src/features/guide-legacy/demo-src/scene-editor.js",
+ "symbol": "data-camera=\"perspective\"",
+ "line": 259
+ },
+ "implementation": "本轮修复已完成验证",
+ "boundary": "基线按钮无处理分支;需明确恢复相机视角,不静默无效。",
+ "acceptance": "顶视切回透视,实际相机位置与激活样式变化。",
+ "fix": "透视按钮实际恢复先前观察位置;导入、加载和历史恢复存在已保存相机时不再被车辆自动聚焦覆盖。",
+ "fixIds": [
+ "SCENE-F04"
+ ],
+ "verification": "PASS"
+ },
+ {
+ "id": "view.top",
+ "group": "三维视口、选择与变换",
+ "name": "顶视图按钮",
+ "source": {
+ "file": "../../../unreal_tran_web/src/features/guide-legacy/demo-src/scene-editor.js",
+ "symbol": "setTopView()",
+ "line": 585
+ },
+ "implementation": "本轮修复已完成验证",
+ "boundary": "仍为透视相机顶视位置,不冒称正交投影。",
+ "acceptance": "实际点击后顶视方向与目标一致。",
+ "fix": "透视按钮实际恢复先前观察位置;导入、加载和历史恢复存在已保存相机时不再被车辆自动聚焦覆盖。",
+ "fixIds": [
+ "SCENE-F04"
+ ],
+ "verification": "PASS"
+ },
+ {
+ "id": "view.focus",
+ "group": "三维视口、选择与变换",
+ "name": "聚焦选中对象",
+ "source": {
+ "file": "../../../unreal_tran_web/src/features/guide-legacy/demo-src/scene-editor.js",
+ "symbol": "focusSelected()",
+ "line": 586
+ },
+ "implementation": "已有实现已完成验证",
+ "boundary": "按对象真实 bounds 调整相机;无选中应提示。",
+ "acceptance": "狐狸/卡车/环境分别聚焦可见;无选中提示不异常。",
+ "fix": "尚未关联本轮修复记录",
+ "fixIds": [],
+ "verification": "PASS"
+ },
+ {
+ "id": "view.grid",
+ "group": "三维视口、选择与变换",
+ "name": "网格显隐",
+ "source": {
+ "file": "../../../unreal_tran_web/src/features/guide-legacy/demo-src/scene-editor.js",
+ "symbol": "toggleGrid(button)",
+ "line": 1643
+ },
+ "implementation": "已有实现已完成验证",
+ "boundary": "当前视图辅助选项;不默认要求跨重开持久化。",
+ "acceptance": "真实网格出现/消失、状态同步、截图封面不含网格。",
+ "fix": "尚未关联本轮修复记录",
+ "fixIds": [],
+ "verification": "PASS"
+ },
+ {
+ "id": "view.enclosure-full",
+ "group": "三维视口、选择与变换",
+ "name": "车库外壳完整显示",
+ "source": {
+ "file": "../../../unreal_tran_web/src/features/guide-legacy/demo-src/garage-enclosure.js",
+ "symbol": "createGarageEnclosureControls",
+ "line": 377
+ },
+ "implementation": "已有实现已完成验证",
+ "boundary": "完整显示屋顶/墙体/玻璃;仅车库资源存在时出现控制。",
+ "acceptance": "实际点击、对象材料和显示恢复,状态与画面一致。",
+ "fix": "尚未关联本轮修复记录",
+ "fixIds": [],
+ "verification": "PASS"
+ },
+ {
+ "id": "view.enclosure-xray",
+ "group": "三维视口、选择与变换",
+ "name": "车库外壳透明查看",
+ "source": {
+ "file": "../../../unreal_tran_web/src/features/guide-legacy/demo-src/garage-enclosure.js",
+ "symbol": "createGarageEnclosureControls",
+ "line": 377
+ },
+ "implementation": "已有实现已完成验证",
+ "boundary": "车库多种围护材料联动;不等于普通相机透视。",
+ "acceptance": "实际外壳透明且内部模型可辨,材质无串改。",
+ "fix": "尚未关联本轮修复记录",
+ "fixIds": [],
+ "verification": "PASS"
+ },
+ {
+ "id": "view.enclosure-hidden",
+ "group": "三维视口、选择与变换",
+ "name": "隐藏车库外壳",
+ "source": {
+ "file": "../../../unreal_tran_web/src/features/guide-legacy/demo-src/garage-enclosure.js",
+ "symbol": "createGarageEnclosureControls",
+ "line": 377
+ },
+ "implementation": "已有实现已完成验证",
+ "boundary": "隐藏围护后保留内部及地面;序列化 enclosureMode 与 dirty 契约须核对。",
+ "acceptance": "隐藏/还原、保存重开和只读浏览行为准确。",
+ "fix": "尚未关联本轮修复记录",
+ "fixIds": [],
+ "verification": "PASS"
+ },
+ {
+ "id": "selection.viewport",
+ "group": "三维视口、选择与变换",
+ "name": "点击视口选中根对象/部件",
+ "source": {
+ "file": "../../../unreal_tran_web/src/features/guide-legacy/demo-src/scene-editor.js",
+ "symbol": "selectFromViewport(event)",
+ "line": 456
+ },
+ "implementation": "本轮修复已完成验证",
+ "boundary": "只命中真实可选择可见对象;拖相机超过阈值不误选。",
+ "acceptance": "真实像素点击目标、树/属性/高亮一致,点击空白清选择。",
+ "fix": "新实例部件 sceneId 包含所属根对象身份,不再直接复用来源模型部件 ID;源 interaction/editor 标识保留。旧单实例 ID 保留,旧文档重复 ID 仅按文档顺序迁移后续冲突实例并同步 override。第二完整轮进一步发现 GLB 并发完成顺序会改变根对象顺序;现等待 allSettled 成功后按原文档顺序排列恢复根对象,拆分模板返回数组内部顺序和既有对象均保留,同源两实例不再随网速互换列表位置。新增延迟 30/1/10ms 乱序加载合同,验证序列化仍按文档顺序;最终浏览器验证独立记录。",
+ "fixIds": [
+ "SCENE-F18"
+ ],
+ "verification": "PASS"
+ },
+ {
+ "id": "selection.tree",
+ "group": "三维视口、选择与变换",
+ "name": "大纲根对象与部件选中",
+ "source": {
+ "file": "../../../unreal_tran_web/src/features/guide-legacy/demo-src/scene-editor.js",
+ "symbol": "const treeItem =",
+ "line": 546
+ },
+ "implementation": "本轮修复已完成验证",
+ "boundary": "大纲显示所有可编辑实例和部件;环境锁定提示不是折叠控件。",
+ "acceptance": "树选中与视口高亮/属性联动、筛选后仍可选。",
+ "fix": "新实例部件 sceneId 包含所属根对象身份,不再直接复用来源模型部件 ID;源 interaction/editor 标识保留。旧单实例 ID 保留,旧文档重复 ID 仅按文档顺序迁移后续冲突实例并同步 override。第二完整轮进一步发现 GLB 并发完成顺序会改变根对象顺序;现等待 allSettled 成功后按原文档顺序排列恢复根对象,拆分模板返回数组内部顺序和既有对象均保留,同源两实例不再随网速互换列表位置。新增延迟 30/1/10ms 乱序加载合同,验证序列化仍按文档顺序;最终浏览器验证独立记录。",
+ "fixIds": [
+ "SCENE-F18"
+ ],
+ "verification": "PASS"
+ },
+ {
+ "id": "selection.hidden",
+ "group": "三维视口、选择与变换",
+ "name": "隐藏祖先与软删除部件不可误拾取",
+ "source": {
+ "file": "../../../unreal_tran_web/src/features/guide-legacy/demo-src/scene-selection.js",
+ "symbol": "chooseSceneSelection",
+ "line": 41
+ },
+ "implementation": "本轮修复已完成验证",
+ "boundary": "父隐藏/子隐藏/软删除状态不能被射线重新命中。",
+ "acceptance": "用重叠模型/部件实测,命中应落可见有效对象。",
+ "fix": "射线候选逐级检查父级可见性和软删除;删除根对象先恢复高亮材质再释放几何/材质,避免错误拾取及 GPU 资源残留。",
+ "fixIds": [
+ "SCENE-F09"
+ ],
+ "verification": "PASS"
+ },
+ {
+ "id": "selection.highlight",
+ "group": "三维视口、选择与变换",
+ "name": "选中高亮与恢复原材质",
+ "source": {
+ "file": "../../../unreal_tran_web/src/features/guide-legacy/demo-src/scene-editor.js",
+ "symbol": "updateSelectionHighlight()",
+ "line": 627
+ },
+ "implementation": "本轮修复已完成验证",
+ "boundary": "高亮是暂态,不写入模型/场景材质,不进入封面。",
+ "acceptance": "切换对象/清选择/预览/保存截图后材质原值恢复。",
+ "fix": "射线候选逐级检查父级可见性和软删除;删除根对象先恢复高亮材质再释放几何/材质,避免错误拾取及 GPU 资源残留。",
+ "fixIds": [
+ "SCENE-F09"
+ ],
+ "verification": "PASS"
+ },
+ {
+ "id": "transform.translate",
+ "group": "三维视口、选择与变换",
+ "name": "移动工具及真实 XYZ 手柄",
+ "source": {
+ "file": "../../../unreal_tran_web/src/features/guide-legacy/demo-src/scene-editor.js",
+ "symbol": "setTransform(mode)",
+ "line": 1627
+ },
+ "implementation": "已有实现已完成验证",
+ "boundary": "对象实例位置;部件按发布能力限制。",
+ "acceptance": "真实拖动平移手柄,属性联动、撤销重做、保存重开一致。",
+ "fix": "尚未关联本轮修复记录",
+ "fixIds": [],
+ "verification": "PASS"
+ },
+ {
+ "id": "transform.rotate",
+ "group": "三维视口、选择与变换",
+ "name": "旋转工具及真实旋转手柄",
+ "source": {
+ "file": "../../../unreal_tran_web/src/features/guide-legacy/demo-src/scene-editor.js",
+ "symbol": "setTransform(mode)",
+ "line": 1627
+ },
+ "implementation": "已有实现已完成验证",
+ "boundary": "UI 度数与保存弧度转换;不能仅用脚本改姿态代替拖动。",
+ "acceptance": "实际旋转手柄改变姿态并重开一致。",
+ "fix": "尚未关联本轮修复记录",
+ "fixIds": [],
+ "verification": "PASS"
+ },
+ {
+ "id": "transform.scale",
+ "group": "三维视口、选择与变换",
+ "name": "缩放工具及真实缩放手柄",
+ "source": {
+ "file": "../../../unreal_tran_web/src/features/guide-legacy/demo-src/scene-editor.js",
+ "symbol": "setTransform(mode)",
+ "line": 1627
+ },
+ "implementation": "本轮修复已完成验证",
+ "boundary": "归一化模型已有 scale 必须保留,负/零/小值合同明确。",
+ "acceptance": "真实缩放与属性数值一致;狐狸不突然放成超大或消失。",
+ "fix": "实际手柄跨越枢轴时按既有严格正尺度合同限制,避免产生负或零尺度导致模型翻转或消失;未新增镜像建模功能。",
+ "fixIds": [
+ "SCENE-F17"
+ ],
+ "verification": "PASS"
+ },
+ {
+ "id": "transform.keys",
+ "group": "三维视口、选择与变换",
+ "name": "W/E/R 快捷键",
+ "source": {
+ "file": "../../../unreal_tran_web/src/features/guide-legacy/demo-src/scene-editor.js",
+ "symbol": "handleKeyboard(event)",
+ "line": 469
+ },
+ "implementation": "本轮修复已完成验证",
+ "boundary": "键盘焦点在表单时不切工具,失活缓存页不能抢键。",
+ "acceptance": "视口按键切工具;名称输入包含 w/e/r 正常;只读不改。",
+ "fix": "预览状态同步全部操作禁用,Delete/W/E/R 和表单输入遵守预览/权限;失活缓存页忽略全局快捷键,输入框撤销保持文本语义。",
+ "fixIds": [
+ "SCENE-F05"
+ ],
+ "verification": "PASS"
+ },
+ {
+ "id": "transform.snap",
+ "group": "三维视口、选择与变换",
+ "name": "吸附 0.5m 与旋转 15° 开关",
+ "source": {
+ "file": "../../../unreal_tran_web/src/features/guide-legacy/demo-src/scene-editor.js",
+ "symbol": "toggleSnap()",
+ "line": 581
+ },
+ "implementation": "已有实现已完成验证",
+ "boundary": "开关同时控制拖放/变换吸附;当前视图工具参数与业务值区分。",
+ "acceptance": "开/关实际拖动位置、旋转角度,按钮/页脚状态一致。",
+ "fix": "尚未关联本轮修复记录",
+ "fixIds": [],
+ "verification": "PASS"
+ },
+ {
+ "id": "property.name",
+ "group": "对象属性、部件与底部面板",
+ "name": "显示名称",
+ "source": {
+ "file": "../../../unreal_tran_web/src/features/guide-legacy/demo-src/scene-editor.js",
+ "symbol": "handleInput(event)",
+ "line": 437
+ },
+ "implementation": "本轮修复已完成验证",
+ "boundary": "对象/部件 displayName 与项目名称分开。",
+ "acceptance": "修改名称,树/对象条/属性同步,保存重开保留。",
+ "fix": "恢复显示名、设备和物模型编号使用空值保留规则,不把显式空串补回旧值;新资源序号跳过既有编号,空白编号校验按 trim 处理。",
+ "fixIds": [
+ "SCENE-F06"
+ ],
+ "verification": "PASS"
+ },
+ {
+ "id": "property.visible",
+ "group": "对象属性、部件与底部面板",
+ "name": "在场景中可见",
+ "source": {
+ "file": "../../../unreal_tran_web/src/features/guide-legacy/demo-src/scene-editor.js",
+ "symbol": "handleChange(event)",
+ "line": 438
+ },
+ "implementation": "已有实现已完成验证",
+ "boundary": "实例显隐可保存;受保护蒙皮部件不得独立隐藏。",
+ "acceptance": "开/关可见性、恢复、保存重开,视口与树状态准确。",
+ "fix": "尚未关联本轮修复记录",
+ "fixIds": [],
+ "verification": "PASS"
+ },
+ {
+ "id": "property.operable",
+ "group": "对象属性、部件与底部面板",
+ "name": "可作为实训目标",
+ "source": {
+ "file": "../../../unreal_tran_web/src/features/guide-legacy/demo-src/scene-editor.js",
+ "symbol": "handleChange(event)",
+ "line": 438
+ },
+ "implementation": "已有实现已完成验证",
+ "boundary": "operable/capabilities 进入场景定义;不能扩大平台操作权限。",
+ "acceptance": "开/关并保存重开,发布数据的目标能力一致。",
+ "fix": "尚未关联本轮修复记录",
+ "fixIds": [],
+ "verification": "PASS"
+ },
+ {
+ "id": "property.position",
+ "group": "对象属性、部件与底部面板",
+ "name": "位置 X/Y/Z 数值",
+ "source": {
+ "file": "../../../unreal_tran_web/src/features/guide-legacy/demo-src/scene-editor.js",
+ "symbol": "handleInput(event)",
+ "line": 437
+ },
+ "implementation": "已有实现已完成验证",
+ "boundary": "三个输入回写局部 position;不使用纯展示假字段。",
+ "acceptance": "三个轴独立输入与手柄联动、失焦/history/重开准确。",
+ "fix": "尚未关联本轮修复记录",
+ "fixIds": [],
+ "verification": "PASS"
+ },
+ {
+ "id": "property.rotation",
+ "group": "对象属性、部件与底部面板",
+ "name": "旋转 X/Y/Z 数值",
+ "source": {
+ "file": "../../../unreal_tran_web/src/features/guide-legacy/demo-src/scene-editor.js",
+ "symbol": "handleInput(event)",
+ "line": 437
+ },
+ "implementation": "已有实现已完成验证",
+ "boundary": "UI 为角度,文档为弧度。",
+ "acceptance": "非整角/负角输入、实际姿态与 JSON/重开一致。",
+ "fix": "尚未关联本轮修复记录",
+ "fixIds": [],
+ "verification": "PASS"
+ },
+ {
+ "id": "property.scale",
+ "group": "对象属性、部件与底部面板",
+ "name": "缩放 X/Y/Z 数值及有效值",
+ "source": {
+ "file": "../../../unreal_tran_web/src/features/scene-legacy/modelPlacement.ts",
+ "symbol": "parseSceneScale",
+ "line": 36
+ },
+ "implementation": "本轮修复已完成验证",
+ "boundary": "支持小数归一化 scale,避免小值格式化成零;非法值不得破坏模型。",
+ "acceptance": "小正数、负数/零/清空按合同处理,三轴保存往返正常。",
+ "fix": "实际手柄跨越枢轴时按既有严格正尺度合同限制,避免产生负或零尺度导致模型翻转或消失;未新增镜像建模功能。",
+ "fixIds": [
+ "SCENE-F17"
+ ],
+ "verification": "PASS"
+ },
+ {
+ "id": "property.device-id",
+ "group": "对象属性、部件与底部面板",
+ "name": "设备编号",
+ "source": {
+ "file": "../../../unreal_tran_web/src/features/guide-legacy/demo-src/scene-editor.js",
+ "symbol": "handleInput(event)",
+ "line": 437
+ },
+ "implementation": "本轮修复已完成验证",
+ "boundary": "根对象绑定字符串;重复/空值由预检与后端核查。",
+ "acceptance": "修改后绑定表同步、保存重开,重复值能被阻断。",
+ "fix": "恢复显示名、设备和物模型编号使用空值保留规则,不把显式空串补回旧值;新资源序号跳过既有编号,空白编号校验按 trim 处理。",
+ "fixIds": [
+ "SCENE-F06"
+ ],
+ "verification": "PASS"
+ },
+ {
+ "id": "property.model-id",
+ "group": "对象属性、部件与底部面板",
+ "name": "物模型编号",
+ "source": {
+ "file": "../../../unreal_tran_web/src/features/guide-legacy/demo-src/scene-editor.js",
+ "symbol": "handleInput(event)",
+ "line": 437
+ },
+ "implementation": "本轮修复已完成验证",
+ "boundary": "与受控 assetCode、来源项目 ID 分开。",
+ "acceptance": "修改后只改业务编号,源 GLB 仍加载正确。",
+ "fix": "恢复显示名、设备和物模型编号使用空值保留规则,不把显式空串补回旧值;新资源序号跳过既有编号,空白编号校验按 trim 处理。",
+ "fixIds": [
+ "SCENE-F06"
+ ],
+ "verification": "PASS"
+ },
+ {
+ "id": "property.semantic",
+ "group": "对象属性、部件与底部面板",
+ "name": "语义类型六个选项",
+ "source": {
+ "file": "../../../unreal_tran_web/src/features/guide-legacy/demo-src/scene-editor.js",
+ "symbol": "handleChange(event)",
+ "line": 438
+ },
+ "implementation": "已有实现已完成验证",
+ "boundary": "实训装备/工位设施/安全区域/采集设备/训练人员/环境设施。",
+ "acceptance": "逐项可选且保存重开保留;绑定表显示实际类型。",
+ "fix": "尚未关联本轮修复记录",
+ "fixIds": [],
+ "verification": "PASS"
+ },
+ {
+ "id": "part.source",
+ "group": "对象属性、部件与底部面板",
+ "name": "模型来源与部件权限说明",
+ "source": {
+ "file": "../../../unreal_tran_web/src/features/guide-legacy/demo-src/scene-editor.js",
+ "symbol": "renderInspector()",
+ "line": 1520
+ },
+ "implementation": "本轮修复已完成验证",
+ "boundary": "固定模型版本/源节点;蒙皮仅查看高亮,非骨骼独立变换。",
+ "acceptance": "狐狸蒙皮与卡车刚性部件说明/禁用态准确。",
+ "fix": "模型来源按钮有来源权限时导航正确项目;无来源页面权限回到可访问列表,不将模型列表误称为来源工程编辑。",
+ "fixIds": [
+ "SCENE-F11"
+ ],
+ "verification": "PASS"
+ },
+ {
+ "id": "part.transform",
+ "group": "对象属性、部件与底部面板",
+ "name": "刚性部件独立变换并保存覆盖",
+ "source": {
+ "file": "../../../unreal_tran_web/src/features/guide-legacy/demo-src/scene-editor.js",
+ "symbol": "captureModelPartOverrides(root)",
+ "line": 1123
+ },
+ "implementation": "本轮修复已完成验证",
+ "boundary": "partOverrides 与稳定 interactionId;多个实例互不串改。",
+ "acceptance": "两卡车实例改单部件,保存重开另一实例不受影响。",
+ "fix": "新实例部件 sceneId 包含所属根对象身份,不再直接复用来源模型部件 ID;源 interaction/editor 标识保留。旧单实例 ID 保留,旧文档重复 ID 仅按文档顺序迁移后续冲突实例并同步 override。第二完整轮进一步发现 GLB 并发完成顺序会改变根对象顺序;现等待 allSettled 成功后按原文档顺序排列恢复根对象,拆分模板返回数组内部顺序和既有对象均保留,同源两实例不再随网速互换列表位置。新增延迟 30/1/10ms 乱序加载合同,验证序列化仍按文档顺序;最终浏览器验证独立记录。",
+ "fixIds": [
+ "SCENE-F18"
+ ],
+ "verification": "PASS"
+ },
+ {
+ "id": "part.soft-delete",
+ "group": "对象属性、部件与底部面板",
+ "name": "部件软删除与恢复",
+ "source": {
+ "file": "../../../unreal_tran_web/src/features/guide-legacy/demo-src/scene-editor.js",
+ "symbol": "deleteSelected()",
+ "line": 588
+ },
+ "implementation": "本轮修复已完成验证",
+ "boundary": "被允许的部件 sceneDeleted/visible 持久化;可撤销恢复。",
+ "acceptance": "实际删除、撤销、保存重开均一致;蒙皮不允许破坏骨架。",
+ "fix": "新实例部件 sceneId 包含所属根对象身份,不再直接复用来源模型部件 ID;源 interaction/editor 标识保留。旧单实例 ID 保留,旧文档重复 ID 仅按文档顺序迁移后续冲突实例并同步 override。第二完整轮进一步发现 GLB 并发完成顺序会改变根对象顺序;现等待 allSettled 成功后按原文档顺序排列恢复根对象,拆分模板返回数组内部顺序和既有对象均保留,同源两实例不再随网速互换列表位置。新增延迟 30/1/10ms 乱序加载合同,验证序列化仍按文档顺序;最终浏览器验证独立记录。",
+ "fixIds": [
+ "SCENE-F18"
+ ],
+ "verification": "PASS"
+ },
+ {
+ "id": "object.delete",
+ "group": "对象属性、部件与底部面板",
+ "name": "删除根对象按钮 / Delete",
+ "source": {
+ "file": "../../../unreal_tran_web/src/features/guide-legacy/demo-src/scene-editor.js",
+ "symbol": "deleteSelected()",
+ "line": 588
+ },
+ "implementation": "本轮修复已完成验证",
+ "boundary": "移除当前场景实例,不删除源模型项目/固定发布版本。",
+ "acceptance": "按钮和键盘各验证;源项目不变,撤销和重开正确。",
+ "fix": "射线候选逐级检查父级可见性和软删除;删除根对象先恢复高亮材质再释放几何/材质,避免错误拾取及 GPU 资源残留。",
+ "fixIds": [
+ "SCENE-F09"
+ ],
+ "verification": "PASS"
+ },
+ {
+ "id": "panel.objects",
+ "group": "对象属性、部件与底部面板",
+ "name": "底部对象列表与选中",
+ "source": {
+ "file": "../../../unreal_tran_web/src/features/guide-legacy/demo-src/scene-editor.js",
+ "symbol": "renderBottom(mode",
+ "line": 1619
+ },
+ "implementation": "本轮修复已完成验证",
+ "boundary": "基线显示前 6 个摘要,完整对象仍在大纲;需明确截断。",
+ "acceptance": "大于 6 个对象时列表说明准确,摘要点击选择正确。",
+ "fix": "设备绑定显示真实语义与未设置;校验按钮进入当前检查面板并随绑定变更刷新;日志记录本次运行实际操作,错误详情保持瞬时提示;摘要超过六项明确完整列表位置。",
+ "fixIds": [
+ "SCENE-F10"
+ ],
+ "verification": "PASS"
+ },
+ {
+ "id": "panel.binding",
+ "group": "对象属性、部件与底部面板",
+ "name": "设备绑定总表",
+ "source": {
+ "file": "../../../unreal_tran_web/src/features/guide-legacy/demo-src/scene-editor.js",
+ "symbol": "renderBottom(mode",
+ "line": 1619
+ },
+ "implementation": "本轮修复已完成验证",
+ "boundary": "基线语义列恒写已绑定;需要真实类型/缺失/冲突反馈。",
+ "acceptance": "编辑字段即时同步,空/重复数据不得假标已绑定。",
+ "fix": "设备绑定显示真实语义与未设置;校验按钮进入当前检查面板并随绑定变更刷新;日志记录本次运行实际操作,错误详情保持瞬时提示;摘要超过六项明确完整列表位置。",
+ "fixIds": [
+ "SCENE-F10"
+ ],
+ "verification": "PASS"
+ },
+ {
+ "id": "panel.validation",
+ "group": "对象属性、部件与底部面板",
+ "name": "底部发布检查面板",
+ "source": {
+ "file": "../../../unreal_tran_web/src/features/guide-legacy/demo-src/scene-editor.js",
+ "symbol": "renderBottom(mode",
+ "line": 1619
+ },
+ "implementation": "本轮修复已完成验证",
+ "boundary": "显示当前逐项检查,不等于已发布。",
+ "acceptance": "顶部校验/发布失败可定位检查面板,内容随修改更新。",
+ "fix": "设备绑定显示真实语义与未设置;校验按钮进入当前检查面板并随绑定变更刷新;日志记录本次运行实际操作,错误详情保持瞬时提示;摘要超过六项明确完整列表位置。",
+ "fixIds": [
+ "SCENE-F10"
+ ],
+ "verification": "PASS"
+ },
+ {
+ "id": "panel.logs",
+ "group": "对象属性、部件与底部面板",
+ "name": "操作日志",
+ "source": {
+ "file": "../../../unreal_tran_web/src/features/guide-legacy/demo-src/scene-editor.js",
+ "symbol": "renderBottom(mode",
+ "line": 1619
+ },
+ "implementation": "本轮修复已完成验证",
+ "boundary": "基线为静态示例且误称本地存储;本轮只需真实本次运行日志,不扩展服务端审计。",
+ "acceptance": "实际操作产生对应时间/结果;API 模式不显示本地存储为真值。",
+ "fix": "设备绑定显示真实语义与未设置;校验按钮进入当前检查面板并随绑定变更刷新;日志记录本次运行实际操作,错误详情保持瞬时提示;摘要超过六项明确完整列表位置。",
+ "fixIds": [
+ "SCENE-F10"
+ ],
+ "verification": "PASS"
+ },
+ {
+ "id": "history.undo",
+ "group": "历史与预览",
+ "name": "回退按钮 / Ctrl+Z",
+ "source": {
+ "file": "../../../unreal_tran_web/src/features/guide-legacy/demo-src/scene-editor.js",
+ "symbol": "async undo()",
+ "line": 2417
+ },
+ "implementation": "本轮修复已完成验证",
+ "boundary": "输入连续修改合并成合理历史步;恢复对象/模板/选择。",
+ "acceptance": "属性、添加、删除、模板分别撤销,真实画面与文档一致。",
+ "fix": "透视按钮实际恢复先前观察位置;导入、加载和历史恢复存在已保存相机时不再被车辆自动聚焦覆盖。;预览状态同步全部操作禁用,Delete/W/E/R 和表单输入遵守预览/权限;失活缓存页忽略全局快捷键,输入框撤销保持文本语义。;文档签名忽略更新时间和对象键顺序;保存后的 undo/redo 根据真实已保存文档判断 dirty;异步恢复失败保留现场并回滚历史索引。",
+ "fixIds": [
+ "SCENE-F04",
+ "SCENE-F05",
+ "SCENE-F07"
+ ],
+ "verification": "PASS"
+ },
+ {
+ "id": "history.redo",
+ "group": "历史与预览",
+ "name": "前进按钮 / Ctrl+Y / Ctrl+Shift+Z",
+ "source": {
+ "file": "../../../unreal_tran_web/src/features/guide-legacy/demo-src/scene-editor.js",
+ "symbol": "async redo()",
+ "line": 2437
+ },
+ "implementation": "本轮修复已完成验证",
+ "boundary": "撤销后重做,分叉编辑清 redo。",
+ "acceptance": "按钮与两种快捷键各执行;新编辑后旧 redo 不可用。",
+ "fix": "预览状态同步全部操作禁用,Delete/W/E/R 和表单输入遵守预览/权限;失活缓存页忽略全局快捷键,输入框撤销保持文本语义。;文档签名忽略更新时间和对象键顺序;保存后的 undo/redo 根据真实已保存文档判断 dirty;异步恢复失败保留现场并回滚历史索引。",
+ "fixIds": [
+ "SCENE-F05",
+ "SCENE-F07"
+ ],
+ "verification": "PASS"
+ },
+ {
+ "id": "history.capacity",
+ "group": "历史与预览",
+ "name": "60 步历史上限与边界",
+ "source": {
+ "file": "../../../unreal_tran_web/src/features/guide-legacy/demo-src/scene-editor.js",
+ "symbol": "new SceneHistory(60)",
+ "line": 180
+ },
+ "implementation": "已有实现已完成验证",
+ "boundary": "历史属于本次编辑会话;不宣称服务端版本历史。",
+ "acceptance": "聚焦测试验证 60 步边界、空 undo/redo 与失焦快照。",
+ "fix": "尚未关联本轮修复记录",
+ "fixIds": [],
+ "verification": "PASS"
+ },
+ {
+ "id": "history.failure",
+ "group": "历史与预览",
+ "name": "异步历史恢复失败/互斥",
+ "source": {
+ "file": "../../../unreal_tran_web/src/features/guide-legacy/demo-src/scene-editor.js",
+ "symbol": "async restoreHistoryState",
+ "line": 2328
+ },
+ "implementation": "本轮修复已完成验证",
+ "boundary": "失败不能推进索引或丢当前有效场景;忙碌时写操作需一致。",
+ "acceptance": "GLB 恢复失败、并发 undo/redo、保存混入均不半提交。",
+ "fix": "文档签名忽略更新时间和对象键顺序;保存后的 undo/redo 根据真实已保存文档判断 dirty;异步恢复失败保留现场并回滚历史索引。",
+ "fixIds": [
+ "SCENE-F07"
+ ],
+ "verification": "PASS"
+ },
+ {
+ "id": "preview.enter",
+ "group": "历史与预览",
+ "name": "进入运行预览",
+ "source": {
+ "file": "../../../unreal_tran_web/src/features/guide-legacy/demo-src/scene-editor.js",
+ "symbol": "togglePreview()",
+ "line": 583
+ },
+ "implementation": "本轮修复已完成验证",
+ "boundary": "当前默认预览只展示场景,未接模型 timeline/blueprint 自动执行。",
+ "acceptance": "实际进入,辅助选择/手柄状态正常,模型可见。",
+ "fix": "预览状态同步全部操作禁用,Delete/W/E/R 和表单输入遵守预览/权限;失活缓存页忽略全局快捷键,输入框撤销保持文本语义。",
+ "fixIds": [
+ "SCENE-F05"
+ ],
+ "verification": "PASS"
+ },
+ {
+ "id": "preview.exit",
+ "group": "历史与预览",
+ "name": "按钮 / Escape 退出预览",
+ "source": {
+ "file": "../../../unreal_tran_web/src/features/guide-legacy/demo-src/scene-editor.js",
+ "symbol": "handleKeyboard(event)",
+ "line": 469
+ },
+ "implementation": "本轮修复已完成验证",
+ "boundary": "退出后恢复编辑选择与状态;不得丢未存变换。",
+ "acceptance": "两种退出方式均有效,输入焦点与缓存激活行为正确。",
+ "fix": "预览状态同步全部操作禁用,Delete/W/E/R 和表单输入遵守预览/权限;失活缓存页忽略全局快捷键,输入框撤销保持文本语义。",
+ "fixIds": [
+ "SCENE-F05"
+ ],
+ "verification": "PASS"
+ },
+ {
+ "id": "preview.lock",
+ "group": "历史与预览",
+ "name": "预览中禁止编辑",
+ "source": {
+ "file": "../../../unreal_tran_web/src/features/guide-legacy/demo-src/scene-editor.js",
+ "symbol": "togglePreview()",
+ "line": 583
+ },
+ "implementation": "本轮修复已完成验证",
+ "boundary": "显示“场景编辑锁定”必须覆盖属性、删除、模板、导入和快捷键。",
+ "acceptance": "尝试所有写入口,预览时文档不改变;退出后正常。",
+ "fix": "预览状态同步全部操作禁用,Delete/W/E/R 和表单输入遵守预览/权限;失活缓存页忽略全局快捷键,输入框撤销保持文本语义。",
+ "fixIds": [
+ "SCENE-F05"
+ ],
+ "verification": "PASS"
+ },
+ {
+ "id": "cover.capture",
+ "group": "保存封面、发布与依赖",
+ "name": "保存时截取当前三维场景封面",
+ "source": {
+ "file": "../../../unreal_tran_web/src/features/scene-legacy/sceneCoverCapture.ts",
+ "symbol": "export async function captureSceneCover",
+ "line": 26
+ },
+ "implementation": "已有实现已完成验证",
+ "boundary": "JPEG 最大边 640、当前相机;隐藏手柄/网格/高亮且恢复现场。",
+ "acceptance": "截图与服务端图片可辨一致,像素实际有模型,UI 状态不被破坏。",
+ "fix": "尚未关联本轮修复记录",
+ "fixIds": [],
+ "verification": "PASS"
+ },
+ {
+ "id": "cover.atomic",
+ "group": "保存封面、发布与依赖",
+ "name": "封面资产与文档同次保存",
+ "source": {
+ "file": "../../../unreal_tran_web/src/views/content/LegacyScenePageView.vue",
+ "symbol": "saveSceneDocumentWithCover",
+ "line": 47
+ },
+ "implementation": "已有实现已完成验证",
+ "boundary": "封面 upload ticket 与 version 同一次 PUT 消费,不另存过期文档。",
+ "acceptance": "检查资产/封面字段/当前版本相同,并真实列表加载图片。",
+ "fix": "尚未关联本轮修复记录",
+ "fixIds": [],
+ "verification": "PASS"
+ },
+ {
+ "id": "cover.failure",
+ "group": "保存封面、发布与依赖",
+ "name": "截图/封面上传失败保留原封面",
+ "source": {
+ "file": "../../../unreal_tran_web/src/views/content/LegacyScenePageView.vue",
+ "symbol": "coverFailed",
+ "line": 390
+ },
+ "implementation": "已有实现已完成验证",
+ "boundary": "正文可保存时给真实 warning;409 必须阻断且不得覆盖。",
+ "acceptance": "截图失败、上传503、PUT409各自核对保留/提示/零错误发布。",
+ "fix": "尚未关联本轮修复记录",
+ "fixIds": [],
+ "verification": "PASS"
+ },
+ {
+ "id": "validation.complete",
+ "group": "保存封面、发布与依赖",
+ "name": "环境、对象用途、模型加载检查",
+ "source": {
+ "file": "../../../unreal_tran_web/src/features/guide-legacy/demo-src/scene-editor.js",
+ "symbol": "verifyScene(showToast",
+ "line": 1715
+ },
+ "implementation": "已有实现已完成验证",
+ "boundary": "纯环境允许;模型未完成解析必须阻断。",
+ "acceptance": "空地板/纯环境/带模型和加载中分别核对检查结果。",
+ "fix": "尚未关联本轮修复记录",
+ "fixIds": [],
+ "verification": "PASS"
+ },
+ {
+ "id": "validation.bindings",
+ "group": "保存封面、发布与依赖",
+ "name": "设备/物模型编号完整与唯一",
+ "source": {
+ "file": "../../../unreal_tran_web/src/features/guide-legacy/demo-src/scene-editor.js",
+ "symbol": "verifyScene(showToast",
+ "line": 1715
+ },
+ "implementation": "本轮修复已完成验证",
+ "boundary": "空白、重复编号按前后端合同处理,不自动替用户分配新业务身份。",
+ "acceptance": "空、空格、重复与合法编号;错误项可见,发布被阻断。",
+ "fix": "恢复显示名、设备和物模型编号使用空值保留规则,不把显式空串补回旧值;新资源序号跳过既有编号,空白编号校验按 trim 处理。;设备绑定显示真实语义与未设置;校验按钮进入当前检查面板并随绑定变更刷新;日志记录本次运行实际操作,错误详情保持瞬时提示;摘要超过六项明确完整列表位置。",
+ "fixIds": [
+ "SCENE-F06",
+ "SCENE-F10"
+ ],
+ "verification": "PASS"
+ },
+ {
+ "id": "validation.references",
+ "group": "保存封面、发布与依赖",
+ "name": "受控资源引用完整性",
+ "source": {
+ "file": "../../../unreal_tran_web/src/features/scene-legacy/serverAssetStore.ts",
+ "symbol": "hasReferenceSync",
+ "line": 18
+ },
+ "implementation": "本轮修复已完成验证",
+ "boundary": "project/version/assetCode/sourceAssetId 必须指向实际固定资产,不能只测有字段。",
+ "acceptance": "不存在编码/错误资产类型/跨项目引用显式拒绝;正确依赖通过。",
+ "fix": "前端目录和资源解析同时匹配指定项目、版本、code、id、URI、READY MODEL_FILE;错误引用不回退首文件或本地缓存。仅含 sourceAssetId/assetId 的本项目资源也必须将 ID 纳入缓存/去重键,防止已缓存有效引用掩盖另一个错误ID;失败请求缓存可重试。复制场景保留旧伪 URI 但只读取当前副本资产。;新增 SceneResourceReferenceValidator 检查 objects 和递归 splitParts;自有/固定版本资源的所有显式引用必须指向同一 READY 模型文件,错误引用在发布/提交前阻断,保留受控的内置资源和旧引用兼容。",
+ "fixIds": [
+ "SCENE-F12",
+ "SCENE-F13"
+ ],
+ "verification": "PASS"
+ },
+ {
+ "id": "publish.saved",
+ "group": "保存封面、发布与依赖",
+ "name": "发布场景按钮的真实 API 闭环",
+ "source": {
+ "file": "../../../unreal_tran_web/src/features/guide-legacy/demo-src/scene-editor.js",
+ "symbol": "async publishScene()",
+ "line": 2213
+ },
+ "implementation": "已有实现已完成验证",
+ "boundary": "有编辑权时先保存含封面再发布;两步均成功才显示已发布。",
+ "acceptance": "POST 真正发布当前版本,列表状态与下游目录一致。",
+ "fix": "尚未关联本轮修复记录",
+ "fixIds": [],
+ "verification": "PASS"
+ },
+ {
+ "id": "publish.only",
+ "group": "保存封面、发布与依赖",
+ "name": "只有发布权限时发布已存版本",
+ "source": {
+ "file": "../../../unreal_tran_web/src/features/guide-legacy/demo-src/scene-editor.js",
+ "symbol": "if (this.readOnly)",
+ "line": 335
+ },
+ "implementation": "已有实现已完成验证",
+ "boundary": "publish-only 不先 PUT、不上传封面、不要求 update;服务器校验真值。",
+ "acceptance": "隔离权限会话发布已存有效版本,零 update/upload 请求。",
+ "fix": "尚未关联本轮修复记录",
+ "fixIds": [],
+ "verification": "PASS"
+ },
+ {
+ "id": "publish.failure",
+ "group": "保存封面、发布与依赖",
+ "name": "预检/保存/发布失败阻断",
+ "source": {
+ "file": "../../../unreal_tran_web/src/features/guide-legacy/demo-src/scene-editor.js",
+ "symbol": "async publishScene()",
+ "line": 2213
+ },
+ "implementation": "本轮修复已完成验证",
+ "boundary": "保存失败不 POST 发布;发布失败不假标已发布;保持错误恢复能力。",
+ "acceptance": "预检错误、PUT409/503、POST失败及重试,版本/状态准确。",
+ "fix": "新增 SceneResourceReferenceValidator 检查 objects 和递归 splitParts;自有/固定版本资源的所有显式引用必须指向同一 READY 模型文件,错误引用在发布/提交前阻断,保留受控的内置资源和旧引用兼容。",
+ "fixIds": [
+ "SCENE-F13"
+ ],
+ "verification": "PASS"
+ },
+ {
+ "id": "publish.concurrent",
+ "group": "保存封面、发布与依赖",
+ "name": "重复发布与上传/保存队列",
+ "source": {
+ "file": "../../../unreal_tran_web/src/views/content/LegacyScenePageView.vue",
+ "symbol": "publishDocument:",
+ "line": 509
+ },
+ "implementation": "已有实现已完成验证",
+ "boundary": "整条 POST 完成前仍占 FIFO;重复操作不串版本。",
+ "acceptance": "延迟上传/保存/发布并触发竞争,服务端只有完整合法版本。",
+ "fix": "尚未关联本轮修复记录",
+ "fixIds": [],
+ "verification": "PASS"
+ },
+ {
+ "id": "publish.fixed-model",
+ "group": "保存封面、发布与依赖",
+ "name": "固定 SCENE_MODEL 版本与多个实例",
+ "source": {
+ "file": "../../../unreal_tran_web/src/features/scene-legacy/contentApiClient.ts",
+ "symbol": "collectSceneModelDependencies",
+ "line": 938
+ },
+ "implementation": "本轮修复已完成验证",
+ "boundary": "去重精确模型版本,不能随模型新发布静默换源版本。",
+ "acceptance": "同模型多实例及另模型新版本,场景重开仍使用所存固定版本。",
+ "fix": "前端目录和资源解析同时匹配指定项目、版本、code、id、URI、READY MODEL_FILE;错误引用不回退首文件或本地缓存。仅含 sourceAssetId/assetId 的本项目资源也必须将 ID 纳入缓存/去重键,防止已缓存有效引用掩盖另一个错误ID;失败请求缓存可重试。复制场景保留旧伪 URI 但只读取当前副本资产。;新增 SceneResourceReferenceValidator 检查 objects 和递归 splitParts;自有/固定版本资源的所有显式引用必须指向同一 READY 模型文件,错误引用在发布/提交前阻断,保留受控的内置资源和旧引用兼容。;完整 PUT 回写原有相同 SCENE_MODEL 三元组时沿用场景授权边界;新增或改版依赖仍要求来源模型可见及范围权限,不扩大模型页面访问权。;新实例部件 sceneId 包含所属根对象身份,不再直接复用来源模型部件 ID;源 interaction/editor 标识保留。旧单实例 ID 保留,旧文档重复 ID 仅按文档顺序迁移后续冲突实例并同步 override。第二完整轮进一步发现 GLB 并发完成顺序会改变根对象顺序;现等待 allSettled 成功后按原文档顺序排列恢复根对象,拆分模板返回数组内部顺序和既有对象均保留,同源两实例不再随网速互换列表位置。新增延迟 30/1/10ms 乱序加载合同,验证序列化仍按文档顺序;最终浏览器验证独立记录。",
+ "fixIds": [
+ "SCENE-F12",
+ "SCENE-F13",
+ "SCENE-F14",
+ "SCENE-F18"
+ ],
+ "verification": "PASS"
+ },
+ {
+ "id": "publish.downstream",
+ "group": "保存封面、发布与依赖",
+ "name": "发布场景供训练选择和静态还原",
+ "source": {
+ "file": "../../../unreal_tran_web/src/features/guide-legacy/demo-src/scene-editor.js",
+ "symbol": "场景已发布,训练编排可以引用",
+ "line": 2227
+ },
+ "implementation": "已有实现已完成验证",
+ "boundary": "本轮只验证版本/目标/静态可见性;不承诺模型蓝图或原生动画自动执行。",
+ "acceptance": "新场景出现在正式训练目录,绑定后狐狸/卡车可见且目标身份一致。",
+ "fix": "尚未关联本轮修复记录",
+ "fixIds": [],
+ "verification": "PASS"
+ },
+ {
+ "id": "permission.update",
+ "group": "权限、缓存与未保存恢复",
+ "name": "场景细分编辑权限",
+ "source": {
+ "file": "../../../unreal_tran_web/src/features/guide-legacy/demo-src/scene-editor.js",
+ "symbol": "this.readOnly =",
+ "line": 149
+ },
+ "implementation": "本轮修复已完成验证",
+ "boundary": "只按 content.scene.update;基线残留 content.update 需修,不由角色名推断。",
+ "acceptance": "具更新权会话可编辑;无更新权的写入口和接口全部拒绝。",
+ "fix": "移除 runtime 旧 content.update 门槛,独立计算新建、更新和发布;无模型页面权限时不加载新模型目录,但既有固定依赖仍可按场景授权读取。;完整 PUT 回写原有相同 SCENE_MODEL 三元组时沿用场景授权边界;新增或改版依赖仍要求来源模型可见及范围权限,不扩大模型页面访问权。",
+ "fixIds": [
+ "SCENE-F01",
+ "SCENE-F14"
+ ],
+ "verification": "PASS"
+ },
+ {
+ "id": "permission.publish",
+ "group": "权限、缓存与未保存恢复",
+ "name": "场景细分发布权限",
+ "source": {
+ "file": "../../../unreal_tran_web/src/features/guide-legacy/demo-src/scene-editor.js",
+ "symbol": "this.canPublish =",
+ "line": 152
+ },
+ "implementation": "本轮修复已完成验证",
+ "boundary": "页面 content.scene 与 publish 动作分开;更新权不隐含发布。",
+ "acceptance": "只更新不能发布;仅发布可发布已存版本;无页面权不可访问。",
+ "fix": "移除 runtime 旧 content.update 门槛,独立计算新建、更新和发布;无模型页面权限时不加载新模型目录,但既有固定依赖仍可按场景授权读取。",
+ "fixIds": [
+ "SCENE-F01"
+ ],
+ "verification": "PASS"
+ },
+ {
+ "id": "permission.readonly",
+ "group": "权限、缓存与未保存恢复",
+ "name": "只读仍可查看、搜索、选中和预览",
+ "source": {
+ "file": "../../../unreal_tran_web/src/features/guide-legacy/demo-src/scene-editor.js",
+ "symbol": "applyAccessMode()",
+ "line": 198
+ },
+ "implementation": "本轮修复已完成验证",
+ "boundary": "禁写不应误禁观察控件;临时视图不保存到服务器。",
+ "acceptance": "真实键盘搜索、选择、相机、预览,零内容写请求。",
+ "fix": "移除 runtime 旧 content.update 门槛,独立计算新建、更新和发布;无模型页面权限时不加载新模型目录,但既有固定依赖仍可按场景授权读取。",
+ "fixIds": [
+ "SCENE-F01"
+ ],
+ "verification": "PASS"
+ },
+ {
+ "id": "recovery.autobuffer",
+ "group": "权限、缓存与未保存恢复",
+ "name": "800ms 未保存恢复缓冲",
+ "source": {
+ "file": "../../../unreal_tran_web/src/features/guide-legacy/demo-src/scene-editor.js",
+ "symbol": "markDirty(label",
+ "line": 654
+ },
+ "implementation": "本轮修复已完成验证",
+ "boundary": "仅写隔离浏览器恢复稿,不伪称自动入库。",
+ "acceptance": "实际修改后等待,恢复稿存在但无自动 PUT;手动保存后入库。",
+ "fix": "顶部新建委托宿主命名与创建 API,先确认未保存内容。选择放弃也要等创建及导航成功后才清除原 dirty/恢复稿;创建503仍保留原稿。成功后刷新原缓存页,避免再次打开时将已放弃修改冒称已保存。取消、失败不清原场景,成功导航到新项目 ID,并有创建并发守卫。;开始保存时取消之前安排的恢复稿定时器,避免它覆盖宿主保存快照 nonce;保存中的后续编辑仍单独保留。",
+ "fixIds": [
+ "SCENE-F02",
+ "SCENE-F08"
+ ],
+ "verification": "PASS"
+ },
+ {
+ "id": "recovery.same-version",
+ "group": "权限、缓存与未保存恢复",
+ "name": "同版本真实未保存恢复",
+ "source": {
+ "file": "../../../unreal_tran_web/src/views/content/LegacyScenePageView.vue",
+ "symbol": "sceneRecoveryMatchesProjectVersion",
+ "line": 46
+ },
+ "implementation": "本轮修复已完成验证",
+ "boundary": "先回到视口,须显式保存才入库;相同文档不应产生假恢复。",
+ "acceptance": "真实修改后刷新,恢复/保存/再刷新;无注入假 localStorage 代替。",
+ "fix": "文档签名忽略更新时间和对象键顺序;保存后的 undo/redo 根据真实已保存文档判断 dirty;异步恢复失败保留现场并回滚历史索引。;开始保存时取消之前安排的恢复稿定时器,避免它覆盖宿主保存快照 nonce;保存中的后续编辑仍单独保留。",
+ "fixIds": [
+ "SCENE-F07",
+ "SCENE-F08"
+ ],
+ "verification": "PASS"
+ },
+ {
+ "id": "recovery.stale-version",
+ "group": "权限、缓存与未保存恢复",
+ "name": "不同版本恢复稿隔离",
+ "source": {
+ "file": "../../../unreal_tran_web/src/views/content/LegacyScenePageView.vue",
+ "symbol": "sceneRecoveryMatchesProjectVersion",
+ "line": 46
+ },
+ "implementation": "已有实现已完成验证",
+ "boundary": "旧稿不能自动覆盖新服务器版本;下载供人工合并。",
+ "acceptance": "另一会话先保存,新会话面对旧稿只能下载/隔离/明确丢弃。",
+ "fix": "尚未关联本轮修复记录",
+ "fixIds": [],
+ "verification": "PASS"
+ },
+ {
+ "id": "recovery.discard",
+ "group": "权限、缓存与未保存恢复",
+ "name": "下载、保留、丢弃恢复稿",
+ "source": {
+ "file": "../../../unreal_tran_web/src/views/content/LegacyScenePageView.vue",
+ "symbol": "async function quarantineRecovery",
+ "line": 223
+ },
+ "implementation": "已有实现已完成验证",
+ "boundary": "关闭/Escape 不算明确丢弃;他会话新写入稿不得被误删。",
+ "acceptance": "按钮逐项核对本地隔离键、下载文件与服务器无意外写入。",
+ "fix": "尚未关联本轮修复记录",
+ "fixIds": [],
+ "verification": "PASS"
+ },
+ {
+ "id": "recovery.multi-session",
+ "group": "权限、缓存与未保存恢复",
+ "name": "多标签/多会话恢复所有权",
+ "source": {
+ "file": "../../../unreal_tran_web/src/views/content/LegacyScenePageView.vue",
+ "symbol": "recoveryStoragePrefix",
+ "line": 104
+ },
+ "implementation": "已有实现已完成验证",
+ "boundary": "会话键/nonce/锁避免覆盖别的未存稿;本地缓冲不冒充权限。",
+ "acceptance": "两个编辑会话产生不同草稿,保存/丢弃一份不删另一份。",
+ "fix": "尚未关联本轮修复记录",
+ "fixIds": [],
+ "verification": "PASS"
+ },
+ {
+ "id": "leave.cancel",
+ "group": "权限、缓存与未保存恢复",
+ "name": "关闭场景标签时取消离开",
+ "source": {
+ "file": "../../../unreal_tran_web/src/views/content/LegacyScenePageView.vue",
+ "symbol": "async function confirmLeave",
+ "line": 704
+ },
+ "implementation": "本轮修复已完成验证",
+ "boundary": "关闭确认保留当前 dirty 与恢复副本;返回列表或切普通标签不应提示。",
+ "acceptance": "真实关闭确认的关闭/Escape、继续编辑,数据未丢;不把返回列表当关闭。",
+ "fix": "关闭单个或批量场景标签先按目标调用缓存守卫;取消或后续保存失败不提交此前放弃决定,全部确认后才关闭并清理。返回列表仍保留缓存,不扩展为强制关闭。",
+ "fixIds": [
+ "SCENE-F16"
+ ],
+ "verification": "PASS"
+ },
+ {
+ "id": "leave.save",
+ "group": "权限、缓存与未保存恢复",
+ "name": "保存并离开 / 放弃修改",
+ "source": {
+ "file": "../../../unreal_tran_web/src/views/content/LegacyScenePageView.vue",
+ "symbol": "async function confirmLeave",
+ "line": 704
+ },
+ "implementation": "本轮修复已完成验证",
+ "boundary": "等待保存完成并确认最终 dirty;明确放弃只影响当前工程会话。",
+ "acceptance": "保存成功才离开;503/409留原页;放弃按确认执行。",
+ "fix": "关闭单个或批量场景标签先按目标调用缓存守卫;取消或后续保存失败不提交此前放弃决定,全部确认后才关闭并清理。返回列表仍保留缓存,不扩展为强制关闭。",
+ "fixIds": [
+ "SCENE-F16"
+ ],
+ "verification": "PASS"
+ },
+ {
+ "id": "leave.busy",
+ "group": "权限、缓存与未保存恢复",
+ "name": "加载/上传/发布途中离开",
+ "source": {
+ "file": "../../../unreal_tran_web/src/views/content/LegacyScenePageView.vue",
+ "symbol": "const resourcesBusy =",
+ "line": 708
+ },
+ "implementation": "已有实现已完成验证",
+ "boundary": "资源未就绪阻止离开;进行中的保存/发布须等待最终结果。",
+ "acceptance": "延迟资源与提交各验证取消/等待,旧请求不污染新项目。",
+ "fix": "尚未关联本轮修复记录",
+ "fixIds": [],
+ "verification": "PASS"
+ },
+ {
+ "id": "leave.role-switch",
+ "group": "权限、缓存与未保存恢复",
+ "name": "角色切换覆盖后台场景未存稿",
+ "source": {
+ "file": "../../../unreal_tran_web/src/views/content/LegacyScenePageView.vue",
+ "symbol": "registerWorkspaceLeaveGuard",
+ "line": 2
+ },
+ "implementation": "本轮修复已完成验证",
+ "boundary": "全体守卫确认后才切角色;后一页取消不能抹掉前页 dirty。",
+ "acceptance": "含后台场景的切角色确认取消/保存失败/成功后权限重建。",
+ "fix": "关闭单个或批量场景标签先按目标调用缓存守卫;取消或后续保存失败不提交此前放弃决定,全部确认后才关闭并清理。返回列表仍保留缓存,不扩展为强制关闭。",
+ "fixIds": [
+ "SCENE-F16"
+ ],
+ "verification": "PASS"
+ },
+ {
+ "id": "lifecycle.cache",
+ "group": "权限、缓存与未保存恢复",
+ "name": "标签失活 pause / 激活 resume",
+ "source": {
+ "file": "../../../unreal_tran_web/src/features/guide-legacy/demo-src/scene-editor.js",
+ "symbol": "pause()",
+ "line": 523
+ },
+ "implementation": "本轮修复已完成验证",
+ "boundary": "停止后台 RAF、恢复时重算视口;后台实例不能抢当前页键盘。",
+ "acceptance": "两场景标签切换、选择和未存值保留、后台无写/快捷键误动作。",
+ "fix": "顶部新建委托宿主命名与创建 API,先确认未保存内容。选择放弃也要等创建及导航成功后才清除原 dirty/恢复稿;创建503仍保留原稿。成功后刷新原缓存页,避免再次打开时将已放弃修改冒称已保存。取消、失败不清原场景,成功导航到新项目 ID,并有创建并发守卫。;预览状态同步全部操作禁用,Delete/W/E/R 和表单输入遵守预览/权限;失活缓存页忽略全局快捷键,输入框撤销保持文本语义。;关闭单个或批量场景标签先按目标调用缓存守卫;取消或后续保存失败不提交此前放弃决定,全部确认后才关闭并清理。返回列表仍保留缓存,不扩展为强制关闭。",
+ "fixIds": [
+ "SCENE-F02",
+ "SCENE-F05",
+ "SCENE-F16"
+ ],
+ "verification": "PASS"
+ },
+ {
+ "id": "lifecycle.destroy",
+ "group": "权限、缓存与未保存恢复",
+ "name": "关闭释放 WebGL/监听/资源",
+ "source": {
+ "file": "../../../unreal_tran_web/src/features/guide-legacy/demo-src/scene-editor.js",
+ "symbol": "destroy()",
+ "line": 421
+ },
+ "implementation": "本轮修复已完成验证",
+ "boundary": "销毁后的异步回调无 DOM 写入,无残留全局事件和资产 URL。",
+ "acceptance": "反复打开/关闭、加载中关闭边界,控制台无异常/泄漏事件。",
+ "fix": "射线候选逐级检查父级可见性和软删除;删除根对象先恢复高亮材质再释放几何/材质,避免错误拾取及 GPU 资源残留。;关闭单个或批量场景标签先按目标调用缓存守卫;取消或后续保存失败不提交此前放弃决定,全部确认后才关闭并清理。返回列表仍保留缓存,不扩展为强制关闭。",
+ "fixIds": [
+ "SCENE-F09",
+ "SCENE-F16"
+ ],
+ "verification": "PASS"
+ },
+ {
+ "id": "lifecycle.resize-theme",
+ "group": "权限、缓存与未保存恢复",
+ "name": "桌面尺寸与明亮/暗黑可读性",
+ "source": {
+ "file": "../../../unreal_tran_web/src/features/guide-legacy/demo-src/scene-editor.js",
+ "symbol": "resize()",
+ "line": 414
+ },
+ "implementation": "本轮修复已完成验证",
+ "boundary": "继承正式主题;所有可达控件可读、无截断遮挡。",
+ "acceptance": "1920/1440 下工具栏、属性、模板与弹窗截图,主题切换无低对比。",
+ "fix": "原型 CSS 全局重置收敛到场景作用域,降低按钮重置优先级;侧栏、输入、模板、属性和摘要使用正式主题变量,修复白字白底;修复 1440 宽时长工具栏/画布撑开网格覆盖属性栏。",
+ "fixIds": [
+ "SCENE-F15"
+ ],
+ "verification": "PASS"
+ }
+]
\ No newline at end of file
diff --git a/reports/scene-editor-closure-20260906/fixes.json b/reports/scene-editor-closure-20260906/fixes.json
new file mode 100644
index 0000000..6235352
--- /dev/null
+++ b/reports/scene-editor-closure-20260906/fixes.json
@@ -0,0 +1,24 @@
+{
+ "status":"PENDING",
+ "scope":"已核对本轮源码 diff 的修复清单;实际通过以最终测试和逐项证据为准。",
+ "items":[
+ {"id":"SCENE-F01","title":"默认入口按细分权限控制操作","summary":"移除 runtime 旧 content.update 门槛,独立计算新建、更新和发布;无模型页面权限时不加载新模型目录,但既有固定依赖仍可按场景授权读取。","featureIds":["permission.update","permission.publish","permission.readonly","assets.catalog"]},
+ {"id":"SCENE-F02","title":"新建产生独立服务端项目,失败保留原稿","summary":"顶部新建委托宿主命名与创建 API,先确认未保存内容。选择放弃也要等创建及导航成功后才清除原 dirty/恢复稿;创建503仍保留原稿。成功后刷新原缓存页,避免再次打开时将已放弃修改冒称已保存。取消、失败不清原场景,成功导航到新项目 ID,并有创建并发守卫。","featureIds":["project.new","recovery.autobuffer","lifecycle.cache"]},
+ {"id":"SCENE-F03","title":"模板切换与导入原子替换","summary":"模板按钮使用模板种子,不再次读当前项目旧文档;模板/JSON 替换先保存当前运行对象,等待全部加载结束,失败恢复原环境、对象、选择和 dirty;真实确认取消保留原稿。","featureIds":["template.switch-cancel","json.import","json.invalid","json.missing-resource","template.depot-1","template.terrain-1","template.workshop-1","template.workshop-real","template.workshop-2","template.workshop-3","template.workshop-4","template.workshop-5","template.outdoor-1"]},
+ {"id":"SCENE-F04","title":"透视返回与已保存相机保留","summary":"透视按钮实际恢复先前观察位置;导入、加载和历史恢复存在已保存相机时不再被车辆自动聚焦覆盖。","featureIds":["view.perspective","view.top","json.rendering","history.undo"]},
+ {"id":"SCENE-F05","title":"预览与后台标签写入守卫","summary":"预览状态同步全部操作禁用,Delete/W/E/R 和表单输入遵守预览/权限;失活缓存页忽略全局快捷键,输入框撤销保持文本语义。","featureIds":["preview.enter","preview.exit","preview.lock","transform.keys","lifecycle.cache","history.undo","history.redo"]},
+ {"id":"SCENE-F06","title":"清空属性真实往返和唯一编号","summary":"恢复显示名、设备和物模型编号使用空值保留规则,不把显式空串补回旧值;新资源序号跳过既有编号,空白编号校验按 trim 处理。","featureIds":["property.name","property.device-id","property.model-id","validation.bindings","assets.basic-double-click","glb.import"]},
+ {"id":"SCENE-F07","title":"历史恢复失败回滚与保存基线","summary":"文档签名忽略更新时间和对象键顺序;保存后的 undo/redo 根据真实已保存文档判断 dirty;异步恢复失败保留现场并回滚历史索引。","featureIds":["history.undo","history.redo","history.failure","recovery.same-version"]},
+ {"id":"SCENE-F08","title":"取消旧恢复定时器避免保存后假冲突","summary":"开始保存时取消之前安排的恢复稿定时器,避免它覆盖宿主保存快照 nonce;保存中的后续编辑仍单独保留。","featureIds":["project.concurrent-save","recovery.autobuffer","recovery.same-version"]},
+ {"id":"SCENE-F09","title":"隐藏对象拾取与删除资源释放","summary":"射线候选逐级检查父级可见性和软删除;删除根对象先恢复高亮材质再释放几何/材质,避免错误拾取及 GPU 资源残留。","featureIds":["selection.hidden","selection.highlight","object.delete","lifecycle.destroy"]},
+ {"id":"SCENE-F10","title":"底部面板显示实际状态","summary":"设备绑定显示真实语义与未设置;校验按钮进入当前检查面板并随绑定变更刷新;日志记录本次运行实际操作,错误详情保持瞬时提示;摘要超过六项明确完整列表位置。","featureIds":["panel.objects","panel.binding","panel.validation","panel.logs","validation.bindings"]},
+ {"id":"SCENE-F11","title":"来源模型导航和作用域清楚","summary":"模型来源按钮有来源权限时导航正确项目;无来源页面权限回到可访问列表,不将模型列表误称为来源工程编辑。","featureIds":["assets.model-link","part.source"]},
+ {"id":"SCENE-F12","title":"受控资产引用与缓存身份严格匹配","summary":"前端目录和资源解析同时匹配指定项目、版本、code、id、URI、READY MODEL_FILE;错误引用不回退首文件或本地缓存。仅含 sourceAssetId/assetId 的本项目资源也必须将 ID 纳入缓存/去重键,防止已缓存有效引用掩盖另一个错误ID;失败请求缓存可重试。复制场景保留旧伪 URI 但只读取当前副本资产。","featureIds":["validation.references","json.missing-resource","glb.references","publish.fixed-model","project.retry"]},
+ {"id":"SCENE-F13","title":"服务端发布验证真实资源身份","summary":"新增 SceneResourceReferenceValidator 检查 objects 和递归 splitParts;自有/固定版本资源的所有显式引用必须指向同一 READY 模型文件,错误引用在发布/提交前阻断,保留受控的内置资源和旧引用兼容。","featureIds":["validation.references","publish.failure","publish.fixed-model","glb.references"]},
+ {"id":"SCENE-F14","title":"仅场景编辑者可保存既有固定依赖","summary":"完整 PUT 回写原有相同 SCENE_MODEL 三元组时沿用场景授权边界;新增或改版依赖仍要求来源模型可见及范围权限,不扩大模型页面访问权。","featureIds":["permission.update","publish.fixed-model","project.save"]},
+ {"id":"SCENE-F15","title":"主题继承与文字可读性","summary":"原型 CSS 全局重置收敛到场景作用域,降低按钮重置优先级;侧栏、输入、模板、属性和摘要使用正式主题变量,修复白字白底;修复 1440 宽时长工具栏/画布撑开网格覆盖属性栏。","featureIds":["lifecycle.resize-theme"]},
+ {"id":"SCENE-F16","title":"真正关闭场景标签先执行缓存守卫","summary":"关闭单个或批量场景标签先按目标调用缓存守卫;取消或后续保存失败不提交此前放弃决定,全部确认后才关闭并清理。返回列表仍保留缓存,不扩展为强制关闭。","featureIds":["leave.cancel","leave.save","leave.role-switch","lifecycle.cache","lifecycle.destroy"]},
+ {"id":"SCENE-F17","title":"缩放手柄与属性正尺度规则一致","summary":"实际手柄跨越枢轴时按既有严格正尺度合同限制,避免产生负或零尺度导致模型翻转或消失;未新增镜像建模功能。","featureIds":["transform.scale","property.scale"]},
+ {"id":"SCENE-F18","title":"同源多实例独立选择,保存重开保持对象顺序","summary":"新实例部件 sceneId 包含所属根对象身份,不再直接复用来源模型部件 ID;源 interaction/editor 标识保留。旧单实例 ID 保留,旧文档重复 ID 仅按文档顺序迁移后续冲突实例并同步 override。第二完整轮进一步发现 GLB 并发完成顺序会改变根对象顺序;现等待 allSettled 成功后按原文档顺序排列恢复根对象,拆分模板返回数组内部顺序和既有对象均保留,同源两实例不再随网速互换列表位置。新增延迟 30/1/10ms 乱序加载合同,验证序列化仍按文档顺序;最终浏览器验证独立记录。","featureIds":["project.load","selection.tree","selection.viewport","part.transform","part.soft-delete","publish.fixed-model"]}
+ ]
+}
diff --git a/reports/scene-editor-closure-20260906/index.html b/reports/scene-editor-closure-20260906/index.html
new file mode 100644
index 0000000..5da0a4d
--- /dev/null
+++ b/reports/scene-editor-closure-20260906/index.html
@@ -0,0 +1,13 @@
+场景编辑器 · 全功能闭环审查 验收概况 01 资料 02 原型 03 设计 04 开发 05 测试 待确认范围 截图
+当前列明功能已完成记录验证 已验证 · 2026/9/6 18:36:39(北京时间)
默认入口功能 119 未挂载 Vue 工作区不纳入分母
逐项证据匹配 119 / 119 失败 0 · 待验证 0
当前真实 E2E 33 / 33 构建、API 和单测不计入 E2E
原始截图 52 需图注与本轮附件哈希一致
112 项功能由本轮浏览器证据验证,7 项由聚焦单元 / 合同证据验证;未验证 0 项。
构建与类型检查:1 / 1 项检查;后端构建与测试:373 / 373 项测试;Node 合同与单元:138 / 138 项测试。
数据清理已验证;临时项目 167 / 167、账号 28 / 28、角色 34 / 34 已清理;原件创作内容、版本、资产与依赖 4 / 4 保持不变。
+
+02 / 产品原型
以当前路由挂载的场景编辑器为准 场景列表 ContentProjectsView 的 SCENE 入口。
真实路由 /content/scenes/:id/edit。
Vue 宿主 LegacyScenePageView 管理权限、项目、恢复和 FIFO。
原型运行时 demo-src/scene-editor.js 真实 DOM + Three.js。
内容 API 项目版本、封面、受控资产与发布依赖。
+03 / 设计
先约定保存、权限与实例边界 数据闭环 edit3dv4.scene 文档保存对象变换、语义、模型部件覆盖和 rendering;模型以 SCENE_MODEL 固定项目/版本依赖,场景自导入 GLB 使用本项目资产编码。blob 是临时渲染地址。
手动保存同时生成当前视口 JPEG 封面;截图失败保留旧封面并提示,409 不覆盖。800ms 本地恢复缓冲不等于自动写服务器。
权限与生命周期 content.scene 控制页面,create/update/delete/publish 控制实际动作。发布只有权限者可以发布已存版本;编辑后发布必须先完成受 update 保护的保存。
切标签保留编辑状态并暂停渲染;真正离开/关闭/切角色确认未存修改。资源上传、保存和发布均须完整结束或明确阻止离开。
基线风险记录 以下是本轮修复前源码发现,不能代替实际失败复现。已修复时须关联 fixes.json 与新测试,历史风险描述保留追溯。
+04 / 开发
默认入口全功能覆盖矩阵 稳定功能 ID · 逐项证据 · 测试分层约定 。每项分别说明实现状态、闭环边界与实际证据;各模板与资源单独列明。
全部状态 待验证 已验证 失败 需复核 119 项
工程入口与顶部命令 工程文件与资源导入 左侧目录与资源放置 全部可达场景模板 三维视口、选择与变换 对象属性、部件与底部面板 历史与预览 保存封面、发布与依赖 权限、缓存与未保存恢复
+05 / 测试
真实浏览器、API、单测分别记录 最终整轮 E2E 首轮记录:32 项通过,1 项失败。关闭保存失败用例的提示定位同时匹配成功和失败消息,已限定目标错误;另补列表搜索遮罩消失后截图的等待条件。此轮记录保留在验证文件中,不计入下列最终整轮结果。
第二轮记录:32 项通过,1 项失败。并行加载同源模型时恢复顺序不稳定;已保留文档对象顺序,并以实例编号验证部件隔离和重开。此轮记录保留在验证文件中,不计入下列最终整轮结果。
Playwright JSON · Playwright HTML 。同名旧用例或定向重跑不会覆盖当前完整轮失败。
构建、后端与聚焦单元测试 构建与类型检查:1 / 1 项检查;后端构建与测试:373 / 373 项测试;Node 合同与单元:138 / 138 项测试。上述数量分别来自 validation.json,不计入 E2E 数。
数据保留与清理 数据清理已验证;临时项目 167 / 167、账号 28 / 28、角色 34 / 34 已清理;原件创作内容、版本、资产与依赖 4 / 4 保持不变。
测试副本增删会刷新原件引用统计及更新时间;创作内容、版本、资产和依赖未改写,引用数量已恢复,未回填或伪造原更新时间。
完整清理与保留证据 · 最终交付物扫描 。
逐项证据索引 构建与最终验证记录 · 截图附件匹配结果 。
最终整轮确认:已完成;最终交付物哈希扫描:通过。
+范围与产品确认
较大调整单列,不纳入本轮通过数 SCENE-D01 保留的正式工作区额外控件是否迁入默认入口 当前行为 默认入口为 legacy 场景编辑器。另一份未挂载工作区的额外渲染、组件、动画和布局控件不能计入当前 UI 已闭环。 建议确认的范围 单独确认所需控件清单和入口迁移范围,优先保留已有场景文档、权限、封面、固定版本与恢复机制。 可观察验收 所选入口只有一条业务真值链路;既有场景往返不丢字段,权限、导入、保存、发布和截图均完成独立端到端回归。 SCENE-D02 下游自动执行来源模型时间轴和蓝图 当前行为 场景当前导入模型几何、部件状态和材质;静态预览不能证明来源模型动作或蓝图在场景和训练中执行。 建议确认的范围 沿用前轮模型审查的大范围待确认项,单独定义固定版本的运行状态、触发事件、权限、生命周期与训练结果追踪。 可观察验收 用明确的动作样例验证导入、发布、场景预览、训练运行、停止重置和结果记录;不能只以模型可见或接口引用通过验收。
+实际截图
52 张原图 · 点击放大 失败注入、加载遮罩、环境全景和实际模型可见证据分别写明。故意移动/缩放部件的测试姿态不能误判为加载丢失。
1. 原场景副本中实际加载的狐狸 已验证 狐狸来源固定模型版本,聚焦后核对真实几何。原场景已有模板标识与地形对象不一致,未擅自推断并改写原件。
01-fox-actual-scene.png 2. 狐狸属性保存重开与蒙皮部件保护 已验证 位置、旋转、小数缩放和空绑定保存重开;狐狸蒙皮部件显示只可查看与高亮,独立变换/隐藏禁止。姿态为属性测试值。
02-fox-reopened-skin-protection.png 3. 卡车部件编辑和历史恢复后的场景 已验证 刚性部件做过独立位移、软删除与撤销恢复;保留的测试姿态不应误判为模型加载损坏,具体值以文档和刷新断言为准。
03-truck-part-history-reopened.png 4. 新建失败时原场景和恢复保护仍保留 已验证 定向注入创建 POST 503,失败提示为预期分支。用例分别检查保存与选择放弃两种路径;选择放弃也不会在创建失败时清除原 dirty/恢复稿。最终成功创建后,重新打开旧缓存页应显示服务端内容。具体分支以最终断言为准。
04-new-failure-original-preserved.png 5. 真正创建独立场景并保存重开 已验证 新项目具有独立 ID,初始空白模板正常;原项目内容另通过 API 核对保留。
05-new-separate-scene.png 6. 只读管理员浏览场景 已验证 更新、导入、新建、发布等写入口禁用;查看、预览和导出仍可用,直接内容 PUT 返回 403。
06-readonly-scene-permissions.png 7. 车库围护完整显示 已验证 实际点击完整模式并核对导出字段;建筑墙体、屋顶与支柱可能遮住内部模型,属于本模式显示范围。
07-garage-full.png 8. 车库围护隐藏 已验证 实际点击隐藏外壳,导出后保存并刷新验证模式保留;内部对象不会因为围护隐藏而删除。
07-garage-hidden.png 9. 车库围护透明查看 已验证 实际点击透明模式,导出 enclosureMode 同步;透明围护与保留的结构柱区别于模型未加载。
07-garage-xray.png 10. 错误资源编码被拒绝并保留场景 已验证 JSON 明确引用不存在的 assetCode,预期报错;原场景对象仍保留,未回退为其他模型文件。
10-invalid-reference-rolled-back.png 11. 小 GLB 真实上传进入当前场景 已验证 通过文件输入上传测试 Box.glb,受控资产登记后加入场景;与之前故意损坏文件/上传503分支分开验收。
11-small-glb-real-upload.png 12. JSON 与受控 GLB 保存刷新往返 已验证 有效工程导入后的对象、受控 URI 和渲染参数重开保持。画面相机为测试导入值,不强行套用默认视角。
12-json-glb-reopened.png 13. 保存 PUT 503 时保留未存稿 已验证 注入保存失败,版本未前进且 dirty 保留;红色/警告提示为故障恢复验收的预期画面。
13-save-failure-keeps-draft.png 14. 封面上传失败时保留旧图 已验证 封面上传被注入503,正文仍可成功保存,真实提示封面降级;不把此步骤记为新封面生成成功。
14-cover-failure-keeps-old-image.png 15. 恢复服务后重新保存成功 已验证 恢复请求后再次修改并保存、刷新核对内容;先前故障提示若仍处正常消退周期,不改变当前响应和刷新断言。
15-save-retry-success.png 16. 真实未保存输入恢复到编辑器 已验证 由实际输入产生本地恢复稿,刷新后明确恢复;恢复仅回到当前编辑视口,未自动写入服务器。
16-real-unsaved-recovery.png 17. 409 版本冲突不覆盖最新内容 已验证 另一请求先保存新版本,当前旧会话再保存被拒绝;未存修改保留,不能显示保存成功。
17-conflict-does-not-overwrite.png 18. 旧版本恢复稿隔离与下载决策 已验证 旧稿不能直接覆盖最新服务端版本,需明确下载/保留或丢弃;此图用于验证保护提示。
18-stale-recovery-explicit-decision.png 19. 发布接口故障仍保持草稿 已验证 定向注入发布 POST 503;保存和发布为独立结果,项目状态保持 DRAFT,后续恢复才真正发布。
19-publish-failure-stays-draft.png 20. 场景发布完成并固定模型依赖 已验证 发布 POST 成功、当前版本与 publishedVersionId 一致;模型来源固定到既有发布版本。
20-scene-published-fixed-version.png 21. 真实装备车库模板保存重开 已验证 独立副本通过模板按钮加载真实车库,保存后刷新;左侧模板选中状态和画面环境同时核对。
20-template-depot-1.png 22. 返回列表查看实际场景封面 已验证 返回列表保留编辑器缓存标签;搜索当前项目并验证图片 naturalWidth 大于零,封面来自服务端受控资产。
21-published-scene-cover-in-list.png 23. 真实野外训练场模板保存重开 已验证 真实起伏地形和默认装备、区域、照明种子对象;不把默认装备占位模型误称为狐狸。
21-template-terrain-1.png 24. 综合检验车间的空白场地 已验证 此模板设计为锁定地板和零业务对象,空白是正常模板结果;保存刷新后仍为零对象。
22-template-workshop-1.png 25. 演示维修车间的 74 个独立部件 已验证 通过可拆分模板加载,74 个场景对象保存重开;与整体装备车库为不同资源。
23-template-workshop-real.png 26. 总成拆卸车间模板 已验证 装备主体、工作台和存放架三种种子资源,模板选择、类型和保存刷新一致。
24-template-workshop-2.png 27. 部件分解车间模板 已验证 工作台、存放架和视觉采集设备三种种子资源保存重开。
25-template-workshop-3.png 28. 清洗检测车间模板 已验证 诊断工位、工作台、采集设备和安全区域四种种子资源保存重开。
26-template-workshop-4.png 29. 复装调试车间模板 已验证 诊断工位、工作台、照明和人员占位四种种子资源保存重开;占位不代表真实设备接入。
27-template-workshop-5.png 30. 室外综合训练场模板 已验证 程序化室外场地及三种种子资源,区别于正式起伏地形模板。
28-template-outdoor-1.png 31. 模板切换确认与空白模板保存 已验证 先取消切换确认保留未存对象,再明确放弃并切换;图中空场地是确认后的正常结果。
29-template-confirm-empty.png 32. 八种基础资源逐项放置并刷新 已验证 8 种资源分别双击并分开摆放,第 9 个工作台通过真实鼠标拖入;保存后核对各对象位置和唯一编号。
30-eight-basic-assets-reopened.png 33. 资源库车库作为整体场地 已验证 从空白模板的资源库加载车库并保存重开;已有整体场地时阻止重复叠加。
31-venue-depot-reopened.png 34. 显式删除旧场地后加载真实地形 已验证 先删除车库,再加入地形;保存刷新确认新场地资产引用与起伏高度,未静默覆盖原场地。
32-venue-terrain-reopened.png 35. API 已发布狐狸模型双击放置 已验证 真实资源卡片双击导入当前实例,移动到 X=-3 并聚焦;保留来源项目和已发布固定版本。
33-fox-placed-visible.png 36. 真实拖入卡车并完成部件隐藏往返 已验证 卡车通过鼠标拖入;刚体部件隐藏保存刷新后再恢复可见,取消高亮后展示完整材质。聚焦卡车导致左侧狐狸部分出画,不是狐狸加载丢失。
34-truck-placed-part-restored.png 37. 1920 暗色主题的真实视口操作 已验证 实际轨道旋转、右键平移、滚轮和点击拾取后检查侧栏、属性和视口;定位钩子只读几何,不修改场景。
40-viewport-dark-1920.png 38. 1440 军工绿主题的完整编辑布局 已验证 真实切换主题和浏览器尺寸,检查长工具栏、画布与右侧属性栏不会互相遮挡。
41-viewport-light-1440.png 39. 狐狸运行预览与原材质 已验证 进入静态场景预览,隐藏选中高亮和手柄并禁止写入;黄色狐狸可见不代表原生动画或蓝图已执行。
42-fox-preview-original-material.png 40. 真实鼠标拖动三种变换手柄 已验证 移动、旋转和单轴缩放后的故意测试姿态;通过真实鼠标命中手柄,只读投影仅用于定位,保存重开再核对数值。
43-real-transform-gizmo.png 41. 后台缓存和关闭保存失败保留编辑器 已验证 切标签保留未存内容、后台快捷键不修改对象;关闭时保存503为预期故障注入,当前场景和未存稿保留。
44-cache-close-save-failure.png 42. 只有场景更新权限仍可保存固定模型 已验证 已有固定依赖通过场景授权读取并保存刷新;无模型页面权限,直接来源项目访问和发布接口仍拒绝。
51-scene-update-only-saved.png 43. 只有发布权限发布已保存版本 已验证 真实浏览器仅发送一个发布 POST,没有内容 PUT 或封面上传;发布权限不扩大为编辑权限。
52-scene-publish-only-no-save.png 44. 没有场景页面权限时拒绝入口 已验证 编辑路由被拦截且直接内容接口返回403;画面不是场景加载成功的证据。
53-scene-route-access-denied.png 45. 取消切角色保留后台场景草稿 已验证 关闭未存确认后没有角色 PUT、没有保存请求;返回场景标签仍保留编辑名称和 dirty。
54-scene-role-switch-cancel-keeps-draft.png 46. 保存后切换角色并重建只读权限 已验证 明确保存后才切换角色,重新启动拉取新权限;场景名称保存成功,更新和发布入口按新角色禁用。
55-scene-role-switch-saved-readonly.png 47. 另一浏览器页的真实恢复稿仍保留 已验证 A 页保存并明确删除自己的恢复稿,B 页实际输入的未存稿及版本仍保留;未通过伪造 localStorage 构造该分支。
56-scene-other-tab-recovery-preserved.png 48. 加载失败保留中性状态和重试入口 已验证 项目 GET 503 注入后不展示可写半场景;错误详情由短消息提供,页面仍能重新加载。
60-scene-load-failure-retry.png 49. 模型下载未完成时禁止半提交 已验证 真实延迟受控资源下载,视口显示加载状态,保存/导出/发布等操作禁用,关闭被阻止;释放请求后再验证完整加载。
61-model-loading-blocks-partial-save.png 50. 保存快照 A 期间编辑 B 仍保留 已验证 延迟PUT后继续修改名称,先完成的请求只保存A;新修改B保持dirty,重复快捷键不重复提交,随后保存刷新核对B。
62-new-edits-preserved-during-save.png 51. 新发布狐狸场景在训练编排中重开 已验证 独立训练副本真实选择新发布场景,保存 TRAINING_SCENE 固定版本并刷新,橙黄色狐狸实际可见;仅验证静态还原。
70-fox-training-scene-reopened.png 52. 新发布卡车场景在训练编排中重开 已验证 独立训练副本绑定新场景固定版本后保存刷新,卡车实际可见;默认车库支柱局部遮挡车身,未将遮挡误认为模型丢失。
70-truck-training-scene-reopened.png 生成命令:node tools/build-scene-editor-closure-report.mjs。仅处理此场景报告;原模型报告不改动。没有当前轮关联证据的功能保持 PENDING。
\ No newline at end of file
diff --git a/reports/scene-editor-closure-20260906/playwright/data/0600b74a150d9c138b9ac7b43c0329da5f5aeb1d.png b/reports/scene-editor-closure-20260906/playwright/data/0600b74a150d9c138b9ac7b43c0329da5f5aeb1d.png
new file mode 100644
index 0000000..661d0f2
Binary files /dev/null and b/reports/scene-editor-closure-20260906/playwright/data/0600b74a150d9c138b9ac7b43c0329da5f5aeb1d.png differ
diff --git a/reports/scene-editor-closure-20260906/playwright/data/09d444c1de8bea55886e8937afc3a1f3647ab2e9.png b/reports/scene-editor-closure-20260906/playwright/data/09d444c1de8bea55886e8937afc3a1f3647ab2e9.png
new file mode 100644
index 0000000..650dbde
Binary files /dev/null and b/reports/scene-editor-closure-20260906/playwright/data/09d444c1de8bea55886e8937afc3a1f3647ab2e9.png differ
diff --git a/reports/scene-editor-closure-20260906/playwright/data/0fe3274ae310ac6e1fd86e58e8c415023285ad8a.png b/reports/scene-editor-closure-20260906/playwright/data/0fe3274ae310ac6e1fd86e58e8c415023285ad8a.png
new file mode 100644
index 0000000..ecce71c
Binary files /dev/null and b/reports/scene-editor-closure-20260906/playwright/data/0fe3274ae310ac6e1fd86e58e8c415023285ad8a.png differ
diff --git a/reports/scene-editor-closure-20260906/playwright/data/10f441aa8b5ba3a568ebf339e243590a1f4f04d8.png b/reports/scene-editor-closure-20260906/playwright/data/10f441aa8b5ba3a568ebf339e243590a1f4f04d8.png
new file mode 100644
index 0000000..adf8ab8
Binary files /dev/null and b/reports/scene-editor-closure-20260906/playwright/data/10f441aa8b5ba3a568ebf339e243590a1f4f04d8.png differ
diff --git a/reports/scene-editor-closure-20260906/playwright/data/191da95dbd8505ad1e367c57bea289446c73ffb4.png b/reports/scene-editor-closure-20260906/playwright/data/191da95dbd8505ad1e367c57bea289446c73ffb4.png
new file mode 100644
index 0000000..9aeda0a
Binary files /dev/null and b/reports/scene-editor-closure-20260906/playwright/data/191da95dbd8505ad1e367c57bea289446c73ffb4.png differ
diff --git a/reports/scene-editor-closure-20260906/playwright/data/1be528d3aa1044126829f262c1c0645bda679cb4.png b/reports/scene-editor-closure-20260906/playwright/data/1be528d3aa1044126829f262c1c0645bda679cb4.png
new file mode 100644
index 0000000..31a1a38
Binary files /dev/null and b/reports/scene-editor-closure-20260906/playwright/data/1be528d3aa1044126829f262c1c0645bda679cb4.png differ
diff --git a/reports/scene-editor-closure-20260906/playwright/data/21ddc85923942108107f535f0438877bf22ea05e.png b/reports/scene-editor-closure-20260906/playwright/data/21ddc85923942108107f535f0438877bf22ea05e.png
new file mode 100644
index 0000000..d288cc9
Binary files /dev/null and b/reports/scene-editor-closure-20260906/playwright/data/21ddc85923942108107f535f0438877bf22ea05e.png differ
diff --git a/reports/scene-editor-closure-20260906/playwright/data/27576fcedb8b74f784a74707504bc5ceea3e2bdb.png b/reports/scene-editor-closure-20260906/playwright/data/27576fcedb8b74f784a74707504bc5ceea3e2bdb.png
new file mode 100644
index 0000000..fd02ed8
Binary files /dev/null and b/reports/scene-editor-closure-20260906/playwright/data/27576fcedb8b74f784a74707504bc5ceea3e2bdb.png differ
diff --git a/reports/scene-editor-closure-20260906/playwright/data/284f13a2762b3ac242748ad6abe2762ec0201818.png b/reports/scene-editor-closure-20260906/playwright/data/284f13a2762b3ac242748ad6abe2762ec0201818.png
new file mode 100644
index 0000000..4e9458b
Binary files /dev/null and b/reports/scene-editor-closure-20260906/playwright/data/284f13a2762b3ac242748ad6abe2762ec0201818.png differ
diff --git a/reports/scene-editor-closure-20260906/playwright/data/2f22c8d27db3e5c91634b7d97cd8a37226481b58.png b/reports/scene-editor-closure-20260906/playwright/data/2f22c8d27db3e5c91634b7d97cd8a37226481b58.png
new file mode 100644
index 0000000..65d99d9
Binary files /dev/null and b/reports/scene-editor-closure-20260906/playwright/data/2f22c8d27db3e5c91634b7d97cd8a37226481b58.png differ
diff --git a/reports/scene-editor-closure-20260906/playwright/data/2f4cfa4e7a77c8196ec233682b49d8b22cf75f33.png b/reports/scene-editor-closure-20260906/playwright/data/2f4cfa4e7a77c8196ec233682b49d8b22cf75f33.png
new file mode 100644
index 0000000..8a818d1
Binary files /dev/null and b/reports/scene-editor-closure-20260906/playwright/data/2f4cfa4e7a77c8196ec233682b49d8b22cf75f33.png differ
diff --git a/reports/scene-editor-closure-20260906/playwright/data/30200a746c9b48e1825f439cb8ae5201f1012a4b.png b/reports/scene-editor-closure-20260906/playwright/data/30200a746c9b48e1825f439cb8ae5201f1012a4b.png
new file mode 100644
index 0000000..dfe3110
Binary files /dev/null and b/reports/scene-editor-closure-20260906/playwright/data/30200a746c9b48e1825f439cb8ae5201f1012a4b.png differ
diff --git a/reports/scene-editor-closure-20260906/playwright/data/3dac939ce13eb088bbc7363407d1af7dd8b6eec0.png b/reports/scene-editor-closure-20260906/playwright/data/3dac939ce13eb088bbc7363407d1af7dd8b6eec0.png
new file mode 100644
index 0000000..f1b3f52
Binary files /dev/null and b/reports/scene-editor-closure-20260906/playwright/data/3dac939ce13eb088bbc7363407d1af7dd8b6eec0.png differ
diff --git a/reports/scene-editor-closure-20260906/playwright/data/4efe7522b7b2cc8ff7393566ea7e8eb75a7fb8a6.png b/reports/scene-editor-closure-20260906/playwright/data/4efe7522b7b2cc8ff7393566ea7e8eb75a7fb8a6.png
new file mode 100644
index 0000000..4566b4d
Binary files /dev/null and b/reports/scene-editor-closure-20260906/playwright/data/4efe7522b7b2cc8ff7393566ea7e8eb75a7fb8a6.png differ
diff --git a/reports/scene-editor-closure-20260906/playwright/data/521ea365ba2cd78c012a583eeb1fe433e68908dd.png b/reports/scene-editor-closure-20260906/playwright/data/521ea365ba2cd78c012a583eeb1fe433e68908dd.png
new file mode 100644
index 0000000..3d3f751
Binary files /dev/null and b/reports/scene-editor-closure-20260906/playwright/data/521ea365ba2cd78c012a583eeb1fe433e68908dd.png differ
diff --git a/reports/scene-editor-closure-20260906/playwright/data/53016dddb3ff31c908c9eb246203ec1bea7818fb.png b/reports/scene-editor-closure-20260906/playwright/data/53016dddb3ff31c908c9eb246203ec1bea7818fb.png
new file mode 100644
index 0000000..e858285
Binary files /dev/null and b/reports/scene-editor-closure-20260906/playwright/data/53016dddb3ff31c908c9eb246203ec1bea7818fb.png differ
diff --git a/reports/scene-editor-closure-20260906/playwright/data/53cc8492bfd39454e6f962aa17d2bbcb45ba2675.png b/reports/scene-editor-closure-20260906/playwright/data/53cc8492bfd39454e6f962aa17d2bbcb45ba2675.png
new file mode 100644
index 0000000..38e4fae
Binary files /dev/null and b/reports/scene-editor-closure-20260906/playwright/data/53cc8492bfd39454e6f962aa17d2bbcb45ba2675.png differ
diff --git a/reports/scene-editor-closure-20260906/playwright/data/59288cf618fcbe855931350f11814577202064ee.png b/reports/scene-editor-closure-20260906/playwright/data/59288cf618fcbe855931350f11814577202064ee.png
new file mode 100644
index 0000000..03cdd3d
Binary files /dev/null and b/reports/scene-editor-closure-20260906/playwright/data/59288cf618fcbe855931350f11814577202064ee.png differ
diff --git a/reports/scene-editor-closure-20260906/playwright/data/60f753349a378219830ee74f9cbc74d613838222.png b/reports/scene-editor-closure-20260906/playwright/data/60f753349a378219830ee74f9cbc74d613838222.png
new file mode 100644
index 0000000..f428540
Binary files /dev/null and b/reports/scene-editor-closure-20260906/playwright/data/60f753349a378219830ee74f9cbc74d613838222.png differ
diff --git a/reports/scene-editor-closure-20260906/playwright/data/63c6d88fabb02aa24f55d93eff9d88d1316263c8.png b/reports/scene-editor-closure-20260906/playwright/data/63c6d88fabb02aa24f55d93eff9d88d1316263c8.png
new file mode 100644
index 0000000..18abe74
Binary files /dev/null and b/reports/scene-editor-closure-20260906/playwright/data/63c6d88fabb02aa24f55d93eff9d88d1316263c8.png differ
diff --git a/reports/scene-editor-closure-20260906/playwright/data/71c301ab0a83dd9183a65004ba3213816941fa68.png b/reports/scene-editor-closure-20260906/playwright/data/71c301ab0a83dd9183a65004ba3213816941fa68.png
new file mode 100644
index 0000000..8f57499
Binary files /dev/null and b/reports/scene-editor-closure-20260906/playwright/data/71c301ab0a83dd9183a65004ba3213816941fa68.png differ
diff --git a/reports/scene-editor-closure-20260906/playwright/data/7516a92e97a7881d67fe522c38e88d3e62ff27f1.png b/reports/scene-editor-closure-20260906/playwright/data/7516a92e97a7881d67fe522c38e88d3e62ff27f1.png
new file mode 100644
index 0000000..4bb4a48
Binary files /dev/null and b/reports/scene-editor-closure-20260906/playwright/data/7516a92e97a7881d67fe522c38e88d3e62ff27f1.png differ
diff --git a/reports/scene-editor-closure-20260906/playwright/data/76302a1aea14682fa9c65da97f1089c58fa642ea.png b/reports/scene-editor-closure-20260906/playwright/data/76302a1aea14682fa9c65da97f1089c58fa642ea.png
new file mode 100644
index 0000000..3ac91c9
Binary files /dev/null and b/reports/scene-editor-closure-20260906/playwright/data/76302a1aea14682fa9c65da97f1089c58fa642ea.png differ
diff --git a/reports/scene-editor-closure-20260906/playwright/data/7df3b141c189232802abda65a084176d904cc487.png b/reports/scene-editor-closure-20260906/playwright/data/7df3b141c189232802abda65a084176d904cc487.png
new file mode 100644
index 0000000..3c31983
Binary files /dev/null and b/reports/scene-editor-closure-20260906/playwright/data/7df3b141c189232802abda65a084176d904cc487.png differ
diff --git a/reports/scene-editor-closure-20260906/playwright/data/7e3c4fd13e3679a2950917f6ffa099ab6970e37e.png b/reports/scene-editor-closure-20260906/playwright/data/7e3c4fd13e3679a2950917f6ffa099ab6970e37e.png
new file mode 100644
index 0000000..071b135
Binary files /dev/null and b/reports/scene-editor-closure-20260906/playwright/data/7e3c4fd13e3679a2950917f6ffa099ab6970e37e.png differ
diff --git a/reports/scene-editor-closure-20260906/playwright/data/7eb107a9492b54302f152aaba4d8d5aaf169275e.png b/reports/scene-editor-closure-20260906/playwright/data/7eb107a9492b54302f152aaba4d8d5aaf169275e.png
new file mode 100644
index 0000000..608e56a
Binary files /dev/null and b/reports/scene-editor-closure-20260906/playwright/data/7eb107a9492b54302f152aaba4d8d5aaf169275e.png differ
diff --git a/reports/scene-editor-closure-20260906/playwright/data/803ee8221e4ddd9d8175fde51b82038790e4704a.png b/reports/scene-editor-closure-20260906/playwright/data/803ee8221e4ddd9d8175fde51b82038790e4704a.png
new file mode 100644
index 0000000..4939000
Binary files /dev/null and b/reports/scene-editor-closure-20260906/playwright/data/803ee8221e4ddd9d8175fde51b82038790e4704a.png differ
diff --git a/reports/scene-editor-closure-20260906/playwright/data/969cbad78b3c1af7e2c275105d66ec4823cd04aa.png b/reports/scene-editor-closure-20260906/playwright/data/969cbad78b3c1af7e2c275105d66ec4823cd04aa.png
new file mode 100644
index 0000000..806eb21
Binary files /dev/null and b/reports/scene-editor-closure-20260906/playwright/data/969cbad78b3c1af7e2c275105d66ec4823cd04aa.png differ
diff --git a/reports/scene-editor-closure-20260906/playwright/data/96fefe531e25f809c68b6c7c5083524ed564f53a.png b/reports/scene-editor-closure-20260906/playwright/data/96fefe531e25f809c68b6c7c5083524ed564f53a.png
new file mode 100644
index 0000000..4993345
Binary files /dev/null and b/reports/scene-editor-closure-20260906/playwright/data/96fefe531e25f809c68b6c7c5083524ed564f53a.png differ
diff --git a/reports/scene-editor-closure-20260906/playwright/data/a15cb2915faa71efe7ca4b91150471f5d74a5319.png b/reports/scene-editor-closure-20260906/playwright/data/a15cb2915faa71efe7ca4b91150471f5d74a5319.png
new file mode 100644
index 0000000..23e7cce
Binary files /dev/null and b/reports/scene-editor-closure-20260906/playwright/data/a15cb2915faa71efe7ca4b91150471f5d74a5319.png differ
diff --git a/reports/scene-editor-closure-20260906/playwright/data/a2f9d8b3792001f185f33748aa6a59b130af77f2.png b/reports/scene-editor-closure-20260906/playwright/data/a2f9d8b3792001f185f33748aa6a59b130af77f2.png
new file mode 100644
index 0000000..d264faf
Binary files /dev/null and b/reports/scene-editor-closure-20260906/playwright/data/a2f9d8b3792001f185f33748aa6a59b130af77f2.png differ
diff --git a/reports/scene-editor-closure-20260906/playwright/data/a3ebb565b82c2acd58e60bca68cdf6b29da31fe5.png b/reports/scene-editor-closure-20260906/playwright/data/a3ebb565b82c2acd58e60bca68cdf6b29da31fe5.png
new file mode 100644
index 0000000..732e95f
Binary files /dev/null and b/reports/scene-editor-closure-20260906/playwright/data/a3ebb565b82c2acd58e60bca68cdf6b29da31fe5.png differ
diff --git a/reports/scene-editor-closure-20260906/playwright/data/afcf12dd2beb2203a3090c219a598d5534ba8f84.png b/reports/scene-editor-closure-20260906/playwright/data/afcf12dd2beb2203a3090c219a598d5534ba8f84.png
new file mode 100644
index 0000000..57d18b5
Binary files /dev/null and b/reports/scene-editor-closure-20260906/playwright/data/afcf12dd2beb2203a3090c219a598d5534ba8f84.png differ
diff --git a/reports/scene-editor-closure-20260906/playwright/data/b1521173206879ee78c52314283d7f14b44d2f1b.png b/reports/scene-editor-closure-20260906/playwright/data/b1521173206879ee78c52314283d7f14b44d2f1b.png
new file mode 100644
index 0000000..f4984c1
Binary files /dev/null and b/reports/scene-editor-closure-20260906/playwright/data/b1521173206879ee78c52314283d7f14b44d2f1b.png differ
diff --git a/reports/scene-editor-closure-20260906/playwright/data/bdc8c12890d078bbbbbeaef6931d71ca9e69010d.png b/reports/scene-editor-closure-20260906/playwright/data/bdc8c12890d078bbbbbeaef6931d71ca9e69010d.png
new file mode 100644
index 0000000..ece741f
Binary files /dev/null and b/reports/scene-editor-closure-20260906/playwright/data/bdc8c12890d078bbbbbeaef6931d71ca9e69010d.png differ
diff --git a/reports/scene-editor-closure-20260906/playwright/data/c09bdfa7e8f110527a83450f78a1e7adba99011e.png b/reports/scene-editor-closure-20260906/playwright/data/c09bdfa7e8f110527a83450f78a1e7adba99011e.png
new file mode 100644
index 0000000..8b6a48d
Binary files /dev/null and b/reports/scene-editor-closure-20260906/playwright/data/c09bdfa7e8f110527a83450f78a1e7adba99011e.png differ
diff --git a/reports/scene-editor-closure-20260906/playwright/data/c0e189aa84bcbaca447a7513f84017d7d4f9a0e9.png b/reports/scene-editor-closure-20260906/playwright/data/c0e189aa84bcbaca447a7513f84017d7d4f9a0e9.png
new file mode 100644
index 0000000..f5a1f51
Binary files /dev/null and b/reports/scene-editor-closure-20260906/playwright/data/c0e189aa84bcbaca447a7513f84017d7d4f9a0e9.png differ
diff --git a/reports/scene-editor-closure-20260906/playwright/data/c35b6a1812b7794de1165afe2d5929ae01e29394.png b/reports/scene-editor-closure-20260906/playwright/data/c35b6a1812b7794de1165afe2d5929ae01e29394.png
new file mode 100644
index 0000000..bf3ca8f
Binary files /dev/null and b/reports/scene-editor-closure-20260906/playwright/data/c35b6a1812b7794de1165afe2d5929ae01e29394.png differ
diff --git a/reports/scene-editor-closure-20260906/playwright/data/c45ab4c6ec9891e2b20cab183b8c1f4407f4acc1.png b/reports/scene-editor-closure-20260906/playwright/data/c45ab4c6ec9891e2b20cab183b8c1f4407f4acc1.png
new file mode 100644
index 0000000..33029aa
Binary files /dev/null and b/reports/scene-editor-closure-20260906/playwright/data/c45ab4c6ec9891e2b20cab183b8c1f4407f4acc1.png differ
diff --git a/reports/scene-editor-closure-20260906/playwright/data/c8209275fa305069ddbed46697b1884cc366a404.png b/reports/scene-editor-closure-20260906/playwright/data/c8209275fa305069ddbed46697b1884cc366a404.png
new file mode 100644
index 0000000..9b91ff9
Binary files /dev/null and b/reports/scene-editor-closure-20260906/playwright/data/c8209275fa305069ddbed46697b1884cc366a404.png differ
diff --git a/reports/scene-editor-closure-20260906/playwright/data/d3619c4a493d0e629e4b50a8e175888cdd031b7c.png b/reports/scene-editor-closure-20260906/playwright/data/d3619c4a493d0e629e4b50a8e175888cdd031b7c.png
new file mode 100644
index 0000000..34b8628
Binary files /dev/null and b/reports/scene-editor-closure-20260906/playwright/data/d3619c4a493d0e629e4b50a8e175888cdd031b7c.png differ
diff --git a/reports/scene-editor-closure-20260906/playwright/data/dfdb0e046b1b596d21ac0dbca72f1357532e984d.png b/reports/scene-editor-closure-20260906/playwright/data/dfdb0e046b1b596d21ac0dbca72f1357532e984d.png
new file mode 100644
index 0000000..c6ef4b0
Binary files /dev/null and b/reports/scene-editor-closure-20260906/playwright/data/dfdb0e046b1b596d21ac0dbca72f1357532e984d.png differ
diff --git a/reports/scene-editor-closure-20260906/playwright/data/e29eb680042e5801176de0247e3aae5b212ef14e.png b/reports/scene-editor-closure-20260906/playwright/data/e29eb680042e5801176de0247e3aae5b212ef14e.png
new file mode 100644
index 0000000..b7a1449
Binary files /dev/null and b/reports/scene-editor-closure-20260906/playwright/data/e29eb680042e5801176de0247e3aae5b212ef14e.png differ
diff --git a/reports/scene-editor-closure-20260906/playwright/data/e5a28577ef831163a93230f8414cf0b85cb14f93.png b/reports/scene-editor-closure-20260906/playwright/data/e5a28577ef831163a93230f8414cf0b85cb14f93.png
new file mode 100644
index 0000000..6ba8f56
Binary files /dev/null and b/reports/scene-editor-closure-20260906/playwright/data/e5a28577ef831163a93230f8414cf0b85cb14f93.png differ
diff --git a/reports/scene-editor-closure-20260906/playwright/data/e959dc3f4a7d7bd9270bd7773e801ddf5538b430.png b/reports/scene-editor-closure-20260906/playwright/data/e959dc3f4a7d7bd9270bd7773e801ddf5538b430.png
new file mode 100644
index 0000000..a7f9fa6
Binary files /dev/null and b/reports/scene-editor-closure-20260906/playwright/data/e959dc3f4a7d7bd9270bd7773e801ddf5538b430.png differ
diff --git a/reports/scene-editor-closure-20260906/playwright/data/e9a0e1474dd095dd62bbc2d4fc5781e4ef46631f.png b/reports/scene-editor-closure-20260906/playwright/data/e9a0e1474dd095dd62bbc2d4fc5781e4ef46631f.png
new file mode 100644
index 0000000..d23c79d
Binary files /dev/null and b/reports/scene-editor-closure-20260906/playwright/data/e9a0e1474dd095dd62bbc2d4fc5781e4ef46631f.png differ
diff --git a/reports/scene-editor-closure-20260906/playwright/data/ecb646423ed56eae343a4e37de400997276c5583.png b/reports/scene-editor-closure-20260906/playwright/data/ecb646423ed56eae343a4e37de400997276c5583.png
new file mode 100644
index 0000000..6290d0e
Binary files /dev/null and b/reports/scene-editor-closure-20260906/playwright/data/ecb646423ed56eae343a4e37de400997276c5583.png differ
diff --git a/reports/scene-editor-closure-20260906/playwright/data/f140944b06a5e2fcf371192272226a693475e803.png b/reports/scene-editor-closure-20260906/playwright/data/f140944b06a5e2fcf371192272226a693475e803.png
new file mode 100644
index 0000000..ed86ee3
Binary files /dev/null and b/reports/scene-editor-closure-20260906/playwright/data/f140944b06a5e2fcf371192272226a693475e803.png differ
diff --git a/reports/scene-editor-closure-20260906/playwright/data/f1db5f35eff3814bcf7f873980a5ea8faaaca457.png b/reports/scene-editor-closure-20260906/playwright/data/f1db5f35eff3814bcf7f873980a5ea8faaaca457.png
new file mode 100644
index 0000000..60b02da
Binary files /dev/null and b/reports/scene-editor-closure-20260906/playwright/data/f1db5f35eff3814bcf7f873980a5ea8faaaca457.png differ
diff --git a/reports/scene-editor-closure-20260906/playwright/data/f708e23baca70e7f82074a22b5022914460d5fa2.png b/reports/scene-editor-closure-20260906/playwright/data/f708e23baca70e7f82074a22b5022914460d5fa2.png
new file mode 100644
index 0000000..b3ca427
Binary files /dev/null and b/reports/scene-editor-closure-20260906/playwright/data/f708e23baca70e7f82074a22b5022914460d5fa2.png differ
diff --git a/reports/scene-editor-closure-20260906/playwright/data/f976d2f4b1cb8534a7cd8aebd77cb0686ca4c3df.png b/reports/scene-editor-closure-20260906/playwright/data/f976d2f4b1cb8534a7cd8aebd77cb0686ca4c3df.png
new file mode 100644
index 0000000..d95445d
Binary files /dev/null and b/reports/scene-editor-closure-20260906/playwright/data/f976d2f4b1cb8534a7cd8aebd77cb0686ca4c3df.png differ
diff --git a/reports/scene-editor-closure-20260906/playwright/data/fb228831d8db6db28545ee09044d007335bd922e.png b/reports/scene-editor-closure-20260906/playwright/data/fb228831d8db6db28545ee09044d007335bd922e.png
new file mode 100644
index 0000000..d708c06
Binary files /dev/null and b/reports/scene-editor-closure-20260906/playwright/data/fb228831d8db6db28545ee09044d007335bd922e.png differ
diff --git a/reports/scene-editor-closure-20260906/playwright/index.html b/reports/scene-editor-closure-20260906/playwright/index.html
new file mode 100644
index 0000000..183af17
--- /dev/null
+++ b/reports/scene-editor-closure-20260906/playwright/index.html
@@ -0,0 +1,76 @@
+
+
+
+
+
+
+
+
+ Playwright Test Report
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/reports/scene-editor-closure-20260906/results.json b/reports/scene-editor-closure-20260906/results.json
new file mode 100644
index 0000000..68895b4
--- /dev/null
+++ b/reports/scene-editor-closure-20260906/results.json
@@ -0,0 +1,2264 @@
+{
+ "config": {
+ "configFile": "E:\\work\\edit_3d\\unreal_tran\\ute2e\\scene-editor-closure.config.ts",
+ "rootDir": "E:/work/edit_3d/unreal_tran/ute2e/tests",
+ "forbidOnly": false,
+ "fullyParallel": false,
+ "globalSetup": null,
+ "globalTeardown": null,
+ "globalTimeout": 0,
+ "grep": {},
+ "grepInvert": null,
+ "maxFailures": 0,
+ "metadata": {
+ "actualWorkers": 1
+ },
+ "preserveOutput": "always",
+ "reporter": [
+ [
+ "list",
+ null
+ ],
+ [
+ "html",
+ {
+ "outputFolder": "reports/scene-editor-closure-20260906/playwright",
+ "open": "never"
+ }
+ ],
+ [
+ "json",
+ {
+ "outputFile": "reports/scene-editor-closure-20260906/results.json"
+ }
+ ]
+ ],
+ "reportSlowTests": {
+ "max": 5,
+ "threshold": 300000
+ },
+ "quiet": false,
+ "projects": [
+ {
+ "outputDir": "E:/work/edit_3d/unreal_tran/ute2e/reports/scene-editor-closure-20260906/test-results",
+ "repeatEach": 1,
+ "retries": 0,
+ "metadata": {
+ "actualWorkers": 1
+ },
+ "id": "",
+ "name": "",
+ "testDir": "E:/work/edit_3d/unreal_tran/ute2e/tests",
+ "testIgnore": [],
+ "testMatch": [
+ "scene-editor-*closure.spec.ts"
+ ],
+ "timeout": 180000
+ }
+ ],
+ "shard": null,
+ "updateSnapshots": "missing",
+ "updateSourceMethod": "patch",
+ "version": "1.55.0",
+ "workers": 1,
+ "webServer": null
+ },
+ "suites": [
+ {
+ "title": "scene-editor-async-closure.spec.ts",
+ "file": "scene-editor-async-closure.spec.ts",
+ "column": 0,
+ "line": 0,
+ "specs": [
+ {
+ "title": "加载失败重试与加载互斥:不展示可写半场景、恢复网络后模型完整加载",
+ "ok": true,
+ "tags": [],
+ "tests": [
+ {
+ "timeout": 180000,
+ "annotations": [],
+ "expectedStatus": "passed",
+ "projectId": "",
+ "projectName": "",
+ "results": [
+ {
+ "workerIndex": 0,
+ "parallelIndex": 0,
+ "status": "passed",
+ "duration": 8661,
+ "errors": [],
+ "stdout": [],
+ "stderr": [],
+ "retry": 0,
+ "steps": [
+ {
+ "title": "Expect \"poll toBe\"",
+ "duration": 47
+ },
+ {
+ "title": "Expect \"poll toBe\"",
+ "duration": 884
+ }
+ ],
+ "startTime": "2026-09-06T10:24:05.760Z",
+ "annotations": [],
+ "attachments": [
+ {
+ "name": "60-scene-load-failure-retry",
+ "contentType": "image/png",
+ "path": "E:\\work\\edit_3d\\unreal_tran\\ute2e\\reports\\scene-editor-closure-20260906\\test-results\\scene-editor-async-closure-加载失败重试与加载互斥:不展示可写半场景、恢复网络后模型完整加载\\attachments\\60-scene-load-failure-retry-bcafe64eeb8ce6f7d80edb15916770893d2b09a0.png"
+ },
+ {
+ "name": "61-model-loading-blocks-partial-save",
+ "contentType": "image/png",
+ "path": "E:\\work\\edit_3d\\unreal_tran\\ute2e\\reports\\scene-editor-closure-20260906\\test-results\\scene-editor-async-closure-加载失败重试与加载互斥:不展示可写半场景、恢复网络后模型完整加载\\attachments\\61-model-loading-blocks-partial-save-1722bedde84490e939186cce6826d9a159dc093e.png"
+ }
+ ]
+ }
+ ],
+ "status": "expected"
+ }
+ ],
+ "id": "575a4480be47befcf397-8f7fdcc948f9a668f012",
+ "file": "scene-editor-async-closure.spec.ts",
+ "line": 6,
+ "column": 1
+ },
+ {
+ "title": "延迟保存快照:重复点击只提交一次,期间新编辑保留,旧恢复稿不会误报冲突",
+ "ok": true,
+ "tags": [],
+ "tests": [
+ {
+ "timeout": 180000,
+ "annotations": [],
+ "expectedStatus": "passed",
+ "projectId": "",
+ "projectName": "",
+ "results": [
+ {
+ "workerIndex": 0,
+ "parallelIndex": 0,
+ "status": "passed",
+ "duration": 12526,
+ "errors": [],
+ "stdout": [],
+ "stderr": [],
+ "retry": 0,
+ "steps": [
+ {
+ "title": "Expect \"poll toBe\"",
+ "duration": 1252
+ },
+ {
+ "title": "Expect \"poll toBe\"",
+ "duration": 856
+ },
+ {
+ "title": "Expect \"poll toBe\"",
+ "duration": 3
+ },
+ {
+ "title": "Expect \"poll toBe\"",
+ "duration": 382
+ },
+ {
+ "title": "Expect \"poll toBe\"",
+ "duration": 896
+ },
+ {
+ "title": "Expect \"poll toBe\"",
+ "duration": 173
+ }
+ ],
+ "startTime": "2026-09-06T10:24:15.181Z",
+ "annotations": [],
+ "attachments": [
+ {
+ "name": "62-new-edits-preserved-during-save",
+ "contentType": "image/png",
+ "path": "E:\\work\\edit_3d\\unreal_tran\\ute2e\\reports\\scene-editor-closure-20260906\\test-results\\scene-editor-async-closure-41b99-复点击只提交一次,期间新编辑保留,旧恢复稿不会误报冲突\\attachments\\62-new-edits-preserved-during-save-5fb848bba12f1b18dcb8ef7a85f10f71eabacbe3.png"
+ }
+ ]
+ }
+ ],
+ "status": "expected"
+ }
+ ],
+ "id": "575a4480be47befcf397-a7e3f1f5c6376b9fbf17",
+ "file": "scene-editor-async-closure.spec.ts",
+ "line": 30,
+ "column": 1
+ }
+ ]
+ },
+ {
+ "title": "scene-editor-downstream-closure.spec.ts",
+ "file": "scene-editor-downstream-closure.spec.ts",
+ "column": 0,
+ "line": 0,
+ "specs": [
+ {
+ "title": "fox:场景发布后在真实训练编排选择、保存并重开静态模型",
+ "ok": true,
+ "tags": [],
+ "tests": [
+ {
+ "timeout": 180000,
+ "annotations": [],
+ "expectedStatus": "passed",
+ "projectId": "",
+ "projectName": "",
+ "results": [
+ {
+ "workerIndex": 0,
+ "parallelIndex": 0,
+ "status": "passed",
+ "duration": 15813,
+ "errors": [],
+ "stdout": [],
+ "stderr": [],
+ "retry": 0,
+ "steps": [
+ {
+ "title": "Expect \"poll toBe\"",
+ "duration": 16
+ }
+ ],
+ "startTime": "2026-09-06T10:24:27.746Z",
+ "annotations": [],
+ "attachments": [
+ {
+ "name": "70-fox-training-scene-reopened",
+ "contentType": "image/png",
+ "path": "E:\\work\\edit_3d\\unreal_tran\\ute2e\\reports\\scene-editor-closure-20260906\\test-results\\scene-editor-downstream-closure-fox:场景发布后在真实训练编排选择、保存并重开静态模型\\attachments\\70-fox-training-scene-reopened-1639ae91c8ff70bde8b0be5a0d015d028d44e6de.png"
+ }
+ ]
+ }
+ ],
+ "status": "expected"
+ }
+ ],
+ "id": "2ea1a7ae4af2a736b944-1d2dff7fe46411020c0b",
+ "file": "scene-editor-downstream-closure.spec.ts",
+ "line": 4,
+ "column": 63
+ },
+ {
+ "title": "truck:场景发布后在真实训练编排选择、保存并重开静态模型",
+ "ok": true,
+ "tags": [],
+ "tests": [
+ {
+ "timeout": 180000,
+ "annotations": [],
+ "expectedStatus": "passed",
+ "projectId": "",
+ "projectName": "",
+ "results": [
+ {
+ "workerIndex": 0,
+ "parallelIndex": 0,
+ "status": "passed",
+ "duration": 18404,
+ "errors": [],
+ "stdout": [],
+ "stderr": [],
+ "retry": 0,
+ "steps": [
+ {
+ "title": "Expect \"poll toBe\"",
+ "duration": 235
+ }
+ ],
+ "startTime": "2026-09-06T10:24:43.573Z",
+ "annotations": [],
+ "attachments": [
+ {
+ "name": "70-truck-training-scene-reopened",
+ "contentType": "image/png",
+ "path": "E:\\work\\edit_3d\\unreal_tran\\ute2e\\reports\\scene-editor-closure-20260906\\test-results\\scene-editor-downstream-cl-ee237-ck:场景发布后在真实训练编排选择、保存并重开静态模型\\attachments\\70-truck-training-scene-reopened-c071233858019110dc3102c5a59268c8098cfa59.png"
+ }
+ ]
+ }
+ ],
+ "status": "expected"
+ }
+ ],
+ "id": "2ea1a7ae4af2a736b944-87d0cb76029294c20442",
+ "file": "scene-editor-downstream-closure.spec.ts",
+ "line": 4,
+ "column": 63
+ }
+ ]
+ },
+ {
+ "title": "scene-editor-permissions-closure.spec.ts",
+ "file": "scene-editor-permissions-closure.spec.ts",
+ "column": 0,
+ "line": 0,
+ "specs": [
+ {
+ "title": "仅场景编辑权限:固定模型可加载和保存,不能发布或访问模型页面",
+ "ok": true,
+ "tags": [],
+ "tests": [
+ {
+ "timeout": 180000,
+ "annotations": [],
+ "expectedStatus": "passed",
+ "projectId": "",
+ "projectName": "",
+ "results": [
+ {
+ "workerIndex": 0,
+ "parallelIndex": 0,
+ "status": "passed",
+ "duration": 10287,
+ "errors": [],
+ "stdout": [],
+ "stderr": [],
+ "retry": 0,
+ "steps": [
+ {
+ "title": "Expect \"poll toBe\"",
+ "duration": 1043
+ },
+ {
+ "title": "Expect \"poll toBe\"",
+ "duration": 880
+ },
+ {
+ "title": "Expect \"poll toBe\"",
+ "duration": 270
+ }
+ ],
+ "startTime": "2026-09-06T10:25:02.012Z",
+ "annotations": [],
+ "attachments": [
+ {
+ "name": "51-scene-update-only-saved",
+ "contentType": "image/png",
+ "path": "E:\\work\\edit_3d\\unreal_tran\\ute2e\\reports\\scene-editor-closure-20260906\\test-results\\scene-editor-permissions-c-f3b5c-编辑权限:固定模型可加载和保存,不能发布或访问模型页面\\attachments\\51-scene-update-only-saved-f7388e35a457ab515e375a3e20e2793b681d4b0d.png"
+ }
+ ]
+ }
+ ],
+ "status": "expected"
+ }
+ ],
+ "id": "d0c0880734f11c9888fc-ff42090ec92fbe8beb5b",
+ "file": "scene-editor-permissions-closure.spec.ts",
+ "line": 110,
+ "column": 1
+ },
+ {
+ "title": "仅场景发布权限:浏览器发布已存副本,不发送保存或上传请求",
+ "ok": true,
+ "tags": [],
+ "tests": [
+ {
+ "timeout": 180000,
+ "annotations": [],
+ "expectedStatus": "passed",
+ "projectId": "",
+ "projectName": "",
+ "results": [
+ {
+ "workerIndex": 0,
+ "parallelIndex": 0,
+ "status": "passed",
+ "duration": 7702,
+ "errors": [],
+ "stdout": [],
+ "stderr": [],
+ "retry": 0,
+ "steps": [
+ {
+ "title": "Expect \"poll toBe\"",
+ "duration": 1070
+ },
+ {
+ "title": "Expect \"poll toBe\"",
+ "duration": 9
+ }
+ ],
+ "startTime": "2026-09-06T10:25:12.316Z",
+ "annotations": [],
+ "attachments": [
+ {
+ "name": "52-scene-publish-only-no-save",
+ "contentType": "image/png",
+ "path": "E:\\work\\edit_3d\\unreal_tran\\ute2e\\reports\\scene-editor-closure-20260906\\test-results\\scene-editor-permissions-c-54924-场景发布权限:浏览器发布已存副本,不发送保存或上传请求\\attachments\\52-scene-publish-only-no-save-28f0d4ba0206564466b1322f8ea078a9f03a1d93.png"
+ }
+ ]
+ }
+ ],
+ "status": "expected"
+ }
+ ],
+ "id": "d0c0880734f11c9888fc-58d9cbce5aaad65214ed",
+ "file": "scene-editor-permissions-closure.spec.ts",
+ "line": 147,
+ "column": 1
+ },
+ {
+ "title": "无场景页面权限:编辑路由和直接内容接口均拒绝访问",
+ "ok": true,
+ "tags": [],
+ "tests": [
+ {
+ "timeout": 180000,
+ "annotations": [],
+ "expectedStatus": "passed",
+ "projectId": "",
+ "projectName": "",
+ "results": [
+ {
+ "workerIndex": 0,
+ "parallelIndex": 0,
+ "status": "passed",
+ "duration": 4834,
+ "errors": [],
+ "stdout": [],
+ "stderr": [],
+ "retry": 0,
+ "startTime": "2026-09-06T10:25:20.036Z",
+ "annotations": [],
+ "attachments": [
+ {
+ "name": "53-scene-route-access-denied",
+ "contentType": "image/png",
+ "path": "E:\\work\\edit_3d\\unreal_tran\\ute2e\\reports\\scene-editor-closure-20260906\\test-results\\scene-editor-permissions-closure-无场景页面权限:编辑路由和直接内容接口均拒绝访问\\attachments\\53-scene-route-access-denied-194f1508efe48603c64ef6b9ac921315610dcee4.png"
+ }
+ ]
+ }
+ ],
+ "status": "expected"
+ }
+ ],
+ "id": "d0c0880734f11c9888fc-072ebbeb590c211d8e85",
+ "file": "scene-editor-permissions-closure.spec.ts",
+ "line": 175,
+ "column": 1
+ },
+ {
+ "title": "后台场景未保存时切换角色:关闭确认保留草稿,保存切换后重建只读权限",
+ "ok": true,
+ "tags": [],
+ "tests": [
+ {
+ "timeout": 180000,
+ "annotations": [],
+ "expectedStatus": "passed",
+ "projectId": "",
+ "projectName": "",
+ "results": [
+ {
+ "workerIndex": 0,
+ "parallelIndex": 0,
+ "status": "passed",
+ "duration": 15055,
+ "errors": [],
+ "stdout": [],
+ "stderr": [],
+ "retry": 0,
+ "steps": [
+ {
+ "title": "Expect \"poll toBe\"",
+ "duration": 916
+ },
+ {
+ "title": "Expect \"poll toBe\"",
+ "duration": 7
+ },
+ {
+ "title": "Expect \"poll toBe\"",
+ "duration": 192
+ }
+ ],
+ "startTime": "2026-09-06T10:25:24.880Z",
+ "annotations": [],
+ "attachments": [
+ {
+ "name": "54-scene-role-switch-cancel-keeps-draft",
+ "contentType": "image/png",
+ "path": "E:\\work\\edit_3d\\unreal_tran\\ute2e\\reports\\scene-editor-closure-20260906\\test-results\\scene-editor-permissions-c-4c783-存时切换角色:关闭确认保留草稿,保存切换后重建只读权限\\attachments\\54-scene-role-switch-cancel-keeps-draft-14e2bc4f9d547e122b4ae74afcee0fcae168b10e.png"
+ },
+ {
+ "name": "55-scene-role-switch-saved-readonly",
+ "contentType": "image/png",
+ "path": "E:\\work\\edit_3d\\unreal_tran\\ute2e\\reports\\scene-editor-closure-20260906\\test-results\\scene-editor-permissions-c-4c783-存时切换角色:关闭确认保留草稿,保存切换后重建只读权限\\attachments\\55-scene-role-switch-saved-readonly-df4aead6c1e902b5730ec128b01f3f145967a855.png"
+ }
+ ]
+ }
+ ],
+ "status": "expected"
+ }
+ ],
+ "id": "d0c0880734f11c9888fc-604216b87aefdf788044",
+ "file": "scene-editor-permissions-closure.spec.ts",
+ "line": 204,
+ "column": 1
+ },
+ {
+ "title": "双标签各自修改场景:保存和明确丢弃当前页均保留另一页真实恢复稿",
+ "ok": true,
+ "tags": [],
+ "tests": [
+ {
+ "timeout": 180000,
+ "annotations": [],
+ "expectedStatus": "passed",
+ "projectId": "",
+ "projectName": "",
+ "results": [
+ {
+ "workerIndex": 0,
+ "parallelIndex": 0,
+ "status": "passed",
+ "duration": 17430,
+ "errors": [],
+ "stdout": [],
+ "stderr": [],
+ "retry": 0,
+ "steps": [
+ {
+ "title": "Expect \"poll toBe\"",
+ "duration": 890
+ },
+ {
+ "title": "Expect \"poll toBe\"",
+ "duration": 451
+ },
+ {
+ "title": "Expect \"poll toEqual\"",
+ "duration": 888
+ },
+ {
+ "title": "Expect \"poll toBe\"",
+ "duration": 881
+ },
+ {
+ "title": "Expect \"poll toEqual\"",
+ "duration": 2
+ },
+ {
+ "title": "Expect \"poll toBe\"",
+ "duration": 869
+ },
+ {
+ "title": "Expect \"poll toBe\"",
+ "duration": 1084
+ },
+ {
+ "title": "Expect \"poll toEqual\"",
+ "duration": 5
+ }
+ ],
+ "startTime": "2026-09-06T10:25:39.947Z",
+ "annotations": [],
+ "attachments": [
+ {
+ "name": "56-scene-other-tab-recovery-preserved",
+ "contentType": "image/png",
+ "path": "E:\\work\\edit_3d\\unreal_tran\\ute2e\\reports\\scene-editor-closure-20260906\\test-results\\scene-editor-permissions-c-868b3-自修改场景:保存和明确丢弃当前页均保留另一页真实恢复稿\\attachments\\56-scene-other-tab-recovery-preserved-47a3200c2fce92a8e02f7e195382a692a7f48850.png"
+ }
+ ]
+ }
+ ],
+ "status": "expected"
+ }
+ ],
+ "id": "d0c0880734f11c9888fc-507d0182aa032fd9e24a",
+ "file": "scene-editor-permissions-closure.spec.ts",
+ "line": 268,
+ "column": 1
+ }
+ ]
+ },
+ {
+ "title": "scene-editor-persistence-closure.spec.ts",
+ "file": "scene-editor-persistence-closure.spec.ts",
+ "column": 0,
+ "line": 0,
+ "specs": [
+ {
+ "title": "工程 JSON 与小 GLB:真实上传、损坏拒绝、引用失败回滚、相机参数往返",
+ "ok": true,
+ "tags": [],
+ "tests": [
+ {
+ "timeout": 180000,
+ "annotations": [],
+ "expectedStatus": "passed",
+ "projectId": "",
+ "projectName": "",
+ "results": [
+ {
+ "workerIndex": 0,
+ "parallelIndex": 0,
+ "status": "passed",
+ "duration": 22988,
+ "errors": [],
+ "stdout": [],
+ "stderr": [],
+ "retry": 0,
+ "steps": [
+ {
+ "title": "Expect \"poll toBe\"",
+ "duration": 182
+ },
+ {
+ "title": "Expect \"poll toBe\"",
+ "duration": 8
+ },
+ {
+ "title": "Expect \"poll toBe\"",
+ "duration": 9
+ },
+ {
+ "title": "Expect \"poll toBe\"",
+ "duration": 7
+ },
+ {
+ "title": "Expect \"poll toBe\"",
+ "duration": 880
+ },
+ {
+ "title": "Expect \"poll toBe\"",
+ "duration": 7
+ },
+ {
+ "title": "Expect \"poll toBe\"",
+ "duration": 883
+ },
+ {
+ "title": "Expect \"poll toBe\"",
+ "duration": 200
+ },
+ {
+ "title": "Expect \"poll toBe\"",
+ "duration": 14
+ },
+ {
+ "title": "Expect \"poll toBe\"",
+ "duration": 886
+ },
+ {
+ "title": "Expect \"poll toBe\"",
+ "duration": 9
+ }
+ ],
+ "startTime": "2026-09-06T10:25:57.403Z",
+ "annotations": [],
+ "attachments": [
+ {
+ "name": "10-invalid-reference-rolled-back",
+ "contentType": "image/png",
+ "path": "E:\\work\\edit_3d\\unreal_tran\\ute2e\\reports\\scene-editor-closure-20260906\\test-results\\scene-editor-persistence-c-9277e-GLB:真实上传、损坏拒绝、引用失败回滚、相机参数往返\\attachments\\10-invalid-reference-rolled-back-05f07581c2912fa8b34b6a60bf1dc2dd010aa862.png"
+ },
+ {
+ "name": "11-small-glb-real-upload",
+ "contentType": "image/png",
+ "path": "E:\\work\\edit_3d\\unreal_tran\\ute2e\\reports\\scene-editor-closure-20260906\\test-results\\scene-editor-persistence-c-9277e-GLB:真实上传、损坏拒绝、引用失败回滚、相机参数往返\\attachments\\11-small-glb-real-upload-f95ba088694823bd5f5a71d70d90326fead99212.png"
+ },
+ {
+ "name": "12-json-glb-reopened",
+ "contentType": "image/png",
+ "path": "E:\\work\\edit_3d\\unreal_tran\\ute2e\\reports\\scene-editor-closure-20260906\\test-results\\scene-editor-persistence-c-9277e-GLB:真实上传、损坏拒绝、引用失败回滚、相机参数往返\\attachments\\12-json-glb-reopened-5af43bf69f6e95e35c4d42ea94835a599fab9ef4.png"
+ }
+ ]
+ }
+ ],
+ "status": "expected"
+ }
+ ],
+ "id": "b6d4258f35bda29d9ea1-6cc96fb401af62790187",
+ "file": "scene-editor-persistence-closure.spec.ts",
+ "line": 10,
+ "column": 1
+ },
+ {
+ "title": "保存失败与封面降级:503 保留草稿,封面上传失败保留旧图,恢复后可重试",
+ "ok": true,
+ "tags": [],
+ "tests": [
+ {
+ "timeout": 180000,
+ "annotations": [],
+ "expectedStatus": "passed",
+ "projectId": "",
+ "projectName": "",
+ "results": [
+ {
+ "workerIndex": 0,
+ "parallelIndex": 0,
+ "status": "passed",
+ "duration": 13793,
+ "errors": [],
+ "stdout": [],
+ "stderr": [],
+ "retry": 0,
+ "steps": [
+ {
+ "title": "Expect \"poll toBe\"",
+ "duration": 390
+ },
+ {
+ "title": "Expect \"poll toBe\"",
+ "duration": 9
+ },
+ {
+ "title": "Expect \"poll toBe\"",
+ "duration": 914
+ },
+ {
+ "title": "Expect \"poll toBe\"",
+ "duration": 610
+ }
+ ],
+ "startTime": "2026-09-06T10:26:20.402Z",
+ "annotations": [],
+ "attachments": [
+ {
+ "name": "13-save-failure-keeps-draft",
+ "contentType": "image/png",
+ "path": "E:\\work\\edit_3d\\unreal_tran\\ute2e\\reports\\scene-editor-closure-20260906\\test-results\\scene-editor-persistence-c-76357-:503-保留草稿,封面上传失败保留旧图,恢复后可重试\\attachments\\13-save-failure-keeps-draft-5eb981845121d792227751835c6fb6280e2c74ba.png"
+ },
+ {
+ "name": "14-cover-failure-keeps-old-image",
+ "contentType": "image/png",
+ "path": "E:\\work\\edit_3d\\unreal_tran\\ute2e\\reports\\scene-editor-closure-20260906\\test-results\\scene-editor-persistence-c-76357-:503-保留草稿,封面上传失败保留旧图,恢复后可重试\\attachments\\14-cover-failure-keeps-old-image-b6f09855431366456d99ce13fe930dae908d48aa.png"
+ },
+ {
+ "name": "15-save-retry-success",
+ "contentType": "image/png",
+ "path": "E:\\work\\edit_3d\\unreal_tran\\ute2e\\reports\\scene-editor-closure-20260906\\test-results\\scene-editor-persistence-c-76357-:503-保留草稿,封面上传失败保留旧图,恢复后可重试\\attachments\\15-save-retry-success-5e4541dd400beff4e856c5b33499a09d74a5fb12.png"
+ }
+ ]
+ }
+ ],
+ "status": "expected"
+ }
+ ],
+ "id": "b6d4258f35bda29d9ea1-de5526c41750602bc87b",
+ "file": "scene-editor-persistence-closure.spec.ts",
+ "line": 64,
+ "column": 1
+ },
+ {
+ "title": "同版本恢复与 409:真实未保存草稿重载、服务器新版本保护与隔离下载",
+ "ok": true,
+ "tags": [],
+ "tests": [
+ {
+ "timeout": 180000,
+ "annotations": [],
+ "expectedStatus": "passed",
+ "projectId": "",
+ "projectName": "",
+ "results": [
+ {
+ "workerIndex": 0,
+ "parallelIndex": 0,
+ "status": "passed",
+ "duration": 15945,
+ "errors": [],
+ "stdout": [],
+ "stderr": [],
+ "retry": 0,
+ "steps": [
+ {
+ "title": "Expect \"poll toBe\"",
+ "duration": 387
+ },
+ {
+ "title": "Expect \"poll toBeGreaterThan\"",
+ "duration": 868
+ },
+ {
+ "title": "Expect \"poll toBe\"",
+ "duration": 9
+ },
+ {
+ "title": "Expect \"poll toBe\"",
+ "duration": 894
+ },
+ {
+ "title": "Expect \"poll toBe\"",
+ "duration": 469
+ }
+ ],
+ "startTime": "2026-09-06T10:26:34.209Z",
+ "annotations": [],
+ "attachments": [
+ {
+ "name": "16-real-unsaved-recovery",
+ "contentType": "image/png",
+ "path": "E:\\work\\edit_3d\\unreal_tran\\ute2e\\reports\\scene-editor-closure-20260906\\test-results\\scene-editor-persistence-c-65cd5-409:真实未保存草稿重载、服务器新版本保护与隔离下载\\attachments\\16-real-unsaved-recovery-41c248cd04d1f0203ab044bbbbe65bdd4b349c51.png"
+ },
+ {
+ "name": "17-conflict-does-not-overwrite",
+ "contentType": "image/png",
+ "path": "E:\\work\\edit_3d\\unreal_tran\\ute2e\\reports\\scene-editor-closure-20260906\\test-results\\scene-editor-persistence-c-65cd5-409:真实未保存草稿重载、服务器新版本保护与隔离下载\\attachments\\17-conflict-does-not-overwrite-2a666b3204c597f2999ec44b69184ba1c9ad43f6.png"
+ },
+ {
+ "name": "18-stale-recovery-explicit-decision",
+ "contentType": "image/png",
+ "path": "E:\\work\\edit_3d\\unreal_tran\\ute2e\\reports\\scene-editor-closure-20260906\\test-results\\scene-editor-persistence-c-65cd5-409:真实未保存草稿重载、服务器新版本保护与隔离下载\\attachments\\18-stale-recovery-explicit-decision-e3f73496423fb14f7101245ea8289af9a30adf8c.png"
+ }
+ ]
+ }
+ ],
+ "status": "expected"
+ }
+ ],
+ "id": "b6d4258f35bda29d9ea1-07fb95896e51aac724cd",
+ "file": "scene-editor-persistence-closure.spec.ts",
+ "line": 88,
+ "column": 1
+ },
+ {
+ "title": "发布:前端绑定校验、保存与发布失败阻断、真实固定版本供训练目录使用",
+ "ok": true,
+ "tags": [],
+ "tests": [
+ {
+ "timeout": 180000,
+ "annotations": [],
+ "expectedStatus": "passed",
+ "projectId": "",
+ "projectName": "",
+ "results": [
+ {
+ "workerIndex": 0,
+ "parallelIndex": 0,
+ "status": "passed",
+ "duration": 14921,
+ "errors": [],
+ "stdout": [],
+ "stderr": [],
+ "retry": 0,
+ "steps": [
+ {
+ "title": "Expect \"poll toBe\"",
+ "duration": 193
+ },
+ {
+ "title": "Expect \"poll toBe\"",
+ "duration": 17
+ }
+ ],
+ "startTime": "2026-09-06T10:26:50.166Z",
+ "annotations": [],
+ "attachments": [
+ {
+ "name": "19-publish-failure-stays-draft",
+ "contentType": "image/png",
+ "path": "E:\\work\\edit_3d\\unreal_tran\\ute2e\\reports\\scene-editor-closure-20260906\\test-results\\scene-editor-persistence-c-f3c53-定校验、保存与发布失败阻断、真实固定版本供训练目录使用\\attachments\\19-publish-failure-stays-draft-55033546226558dac6a51ac98602d0843511876e.png"
+ },
+ {
+ "name": "20-scene-published-fixed-version",
+ "contentType": "image/png",
+ "path": "E:\\work\\edit_3d\\unreal_tran\\ute2e\\reports\\scene-editor-closure-20260906\\test-results\\scene-editor-persistence-c-f3c53-定校验、保存与发布失败阻断、真实固定版本供训练目录使用\\attachments\\20-scene-published-fixed-version-2fbecdebdab60144c77746719660eaaf060932f9.png"
+ },
+ {
+ "name": "21-published-scene-cover-in-list",
+ "contentType": "image/png",
+ "path": "E:\\work\\edit_3d\\unreal_tran\\ute2e\\reports\\scene-editor-closure-20260906\\test-results\\scene-editor-persistence-c-f3c53-定校验、保存与发布失败阻断、真实固定版本供训练目录使用\\attachments\\21-published-scene-cover-in-list-30bd8194718756cb25ed82589ae7c81bd2363e6e.png"
+ }
+ ]
+ }
+ ],
+ "status": "expected"
+ }
+ ],
+ "id": "b6d4258f35bda29d9ea1-46054e87eca4e87660d2",
+ "file": "scene-editor-persistence-closure.spec.ts",
+ "line": 120,
+ "column": 1
+ }
+ ]
+ },
+ {
+ "title": "scene-editor-templates-closure.spec.ts",
+ "file": "scene-editor-templates-closure.spec.ts",
+ "column": 0,
+ "line": 0,
+ "specs": [
+ {
+ "title": "场景模板实际加载、保存重开:真实装备车库",
+ "ok": true,
+ "tags": [],
+ "tests": [
+ {
+ "timeout": 180000,
+ "annotations": [],
+ "expectedStatus": "passed",
+ "projectId": "",
+ "projectName": "",
+ "results": [
+ {
+ "workerIndex": 0,
+ "parallelIndex": 0,
+ "status": "passed",
+ "duration": 9485,
+ "errors": [],
+ "stdout": [],
+ "stderr": [],
+ "retry": 0,
+ "steps": [
+ {
+ "title": "Expect \"poll toBe\"",
+ "duration": 117
+ },
+ {
+ "title": "Expect \"poll toBe\"",
+ "duration": 193
+ },
+ {
+ "title": "Expect \"poll toBe\"",
+ "duration": 878
+ },
+ {
+ "title": "Expect \"poll toBe\"",
+ "duration": 16
+ }
+ ],
+ "startTime": "2026-09-06T10:27:05.118Z",
+ "annotations": [],
+ "attachments": [
+ {
+ "name": "20-template-depot-1",
+ "contentType": "image/png",
+ "path": "E:\\work\\edit_3d\\unreal_tran\\ute2e\\reports\\scene-editor-closure-20260906\\test-results\\scene-editor-templates-closure-场景模板实际加载、保存重开:真实装备车库\\attachments\\20-template-depot-1-b94f06050cef74a4aeb174ebde7595a73ad6a4c9.png"
+ }
+ ]
+ }
+ ],
+ "status": "expected"
+ }
+ ],
+ "id": "dcad141e22d6fb4f278b-c16bc463ee09b41aeec3",
+ "file": "scene-editor-templates-closure.spec.ts",
+ "line": 60,
+ "column": 3
+ },
+ {
+ "title": "场景模板实际加载、保存重开:真实野外训练场",
+ "ok": true,
+ "tags": [],
+ "tests": [
+ {
+ "timeout": 180000,
+ "annotations": [],
+ "expectedStatus": "passed",
+ "projectId": "",
+ "projectName": "",
+ "results": [
+ {
+ "workerIndex": 0,
+ "parallelIndex": 0,
+ "status": "passed",
+ "duration": 10356,
+ "errors": [],
+ "stdout": [],
+ "stderr": [],
+ "retry": 0,
+ "steps": [
+ {
+ "title": "Expect \"poll toBe\"",
+ "duration": 932
+ },
+ {
+ "title": "Expect \"poll toBe\"",
+ "duration": 891
+ },
+ {
+ "title": "Expect \"poll toBe\"",
+ "duration": 878
+ },
+ {
+ "title": "Expect \"poll toBe\"",
+ "duration": 399
+ }
+ ],
+ "startTime": "2026-09-06T10:27:14.614Z",
+ "annotations": [],
+ "attachments": [
+ {
+ "name": "21-template-terrain-1",
+ "contentType": "image/png",
+ "path": "E:\\work\\edit_3d\\unreal_tran\\ute2e\\reports\\scene-editor-closure-20260906\\test-results\\scene-editor-templates-closure-场景模板实际加载、保存重开:真实野外训练场\\attachments\\21-template-terrain-1-e0372c0f4b0de556c73c13a3f39d2678e400d30b.png"
+ }
+ ]
+ }
+ ],
+ "status": "expected"
+ }
+ ],
+ "id": "dcad141e22d6fb4f278b-6945cab62e38da9c17db",
+ "file": "scene-editor-templates-closure.spec.ts",
+ "line": 60,
+ "column": 3
+ },
+ {
+ "title": "场景模板实际加载、保存重开:综合检验车间",
+ "ok": true,
+ "tags": [],
+ "tests": [
+ {
+ "timeout": 180000,
+ "annotations": [],
+ "expectedStatus": "passed",
+ "projectId": "",
+ "projectName": "",
+ "results": [
+ {
+ "workerIndex": 0,
+ "parallelIndex": 0,
+ "status": "passed",
+ "duration": 8873,
+ "errors": [],
+ "stdout": [],
+ "stderr": [],
+ "retry": 0,
+ "steps": [
+ {
+ "title": "Expect \"poll toBe\"",
+ "duration": 950
+ },
+ {
+ "title": "Expect \"poll toBe\"",
+ "duration": 3
+ },
+ {
+ "title": "Expect \"poll toBe\"",
+ "duration": 880
+ },
+ {
+ "title": "Expect \"poll toBe\"",
+ "duration": 5
+ }
+ ],
+ "startTime": "2026-09-06T10:27:24.980Z",
+ "annotations": [],
+ "attachments": [
+ {
+ "name": "22-template-workshop-1",
+ "contentType": "image/png",
+ "path": "E:\\work\\edit_3d\\unreal_tran\\ute2e\\reports\\scene-editor-closure-20260906\\test-results\\scene-editor-templates-closure-场景模板实际加载、保存重开:综合检验车间\\attachments\\22-template-workshop-1-630e13e0c8646d0da4d0c8bdeb1db31b483f65ac.png"
+ }
+ ]
+ }
+ ],
+ "status": "expected"
+ }
+ ],
+ "id": "dcad141e22d6fb4f278b-8fec87f708b178499b22",
+ "file": "scene-editor-templates-closure.spec.ts",
+ "line": 60,
+ "column": 3
+ },
+ {
+ "title": "场景模板实际加载、保存重开:演示维修车间(可拆分)",
+ "ok": true,
+ "tags": [],
+ "tests": [
+ {
+ "timeout": 180000,
+ "annotations": [],
+ "expectedStatus": "passed",
+ "projectId": "",
+ "projectName": "",
+ "results": [
+ {
+ "workerIndex": 0,
+ "parallelIndex": 0,
+ "status": "passed",
+ "duration": 16827,
+ "errors": [],
+ "stdout": [],
+ "stderr": [],
+ "retry": 0,
+ "steps": [
+ {
+ "title": "Expect \"poll toBe\"",
+ "duration": 6
+ },
+ {
+ "title": "Expect \"poll toBe\"",
+ "duration": 2352
+ },
+ {
+ "title": "Expect \"poll toBe\"",
+ "duration": 970
+ },
+ {
+ "title": "Expect \"poll toBe\"",
+ "duration": 2391
+ }
+ ],
+ "startTime": "2026-09-06T10:27:33.863Z",
+ "annotations": [],
+ "attachments": [
+ {
+ "name": "23-template-workshop-real",
+ "contentType": "image/png",
+ "path": "E:\\work\\edit_3d\\unreal_tran\\ute2e\\reports\\scene-editor-closure-20260906\\test-results\\scene-editor-templates-closure-场景模板实际加载、保存重开:演示维修车间(可拆分)\\attachments\\23-template-workshop-real-af23224a3356e092fbca276a7e9c890a9ea98b6d.png"
+ }
+ ]
+ }
+ ],
+ "status": "expected"
+ }
+ ],
+ "id": "dcad141e22d6fb4f278b-52a128e64a6a44f01b1b",
+ "file": "scene-editor-templates-closure.spec.ts",
+ "line": 60,
+ "column": 3
+ },
+ {
+ "title": "场景模板实际加载、保存重开:总成拆卸车间",
+ "ok": true,
+ "tags": [],
+ "tests": [
+ {
+ "timeout": 180000,
+ "annotations": [],
+ "expectedStatus": "passed",
+ "projectId": "",
+ "projectName": "",
+ "results": [
+ {
+ "workerIndex": 0,
+ "parallelIndex": 0,
+ "status": "passed",
+ "duration": 8767,
+ "errors": [],
+ "stdout": [],
+ "stderr": [],
+ "retry": 0,
+ "steps": [
+ {
+ "title": "Expect \"poll toBe\"",
+ "duration": 917
+ },
+ {
+ "title": "Expect \"poll toBe\"",
+ "duration": 6
+ },
+ {
+ "title": "Expect \"poll toBe\"",
+ "duration": 881
+ },
+ {
+ "title": "Expect \"poll toBe\"",
+ "duration": 5
+ }
+ ],
+ "startTime": "2026-09-06T10:27:50.701Z",
+ "annotations": [],
+ "attachments": [
+ {
+ "name": "24-template-workshop-2",
+ "contentType": "image/png",
+ "path": "E:\\work\\edit_3d\\unreal_tran\\ute2e\\reports\\scene-editor-closure-20260906\\test-results\\scene-editor-templates-closure-场景模板实际加载、保存重开:总成拆卸车间\\attachments\\24-template-workshop-2-1efef10a7936083fcf6564010a115032f05d4056.png"
+ }
+ ]
+ }
+ ],
+ "status": "expected"
+ }
+ ],
+ "id": "dcad141e22d6fb4f278b-ad1527532544b5ca558d",
+ "file": "scene-editor-templates-closure.spec.ts",
+ "line": 60,
+ "column": 3
+ },
+ {
+ "title": "场景模板实际加载、保存重开:部件分解车间",
+ "ok": true,
+ "tags": [],
+ "tests": [
+ {
+ "timeout": 180000,
+ "annotations": [],
+ "expectedStatus": "passed",
+ "projectId": "",
+ "projectName": "",
+ "results": [
+ {
+ "workerIndex": 0,
+ "parallelIndex": 0,
+ "status": "passed",
+ "duration": 8779,
+ "errors": [],
+ "stdout": [],
+ "stderr": [],
+ "retry": 0,
+ "steps": [
+ {
+ "title": "Expect \"poll toBe\"",
+ "duration": 11
+ },
+ {
+ "title": "Expect \"poll toBe\"",
+ "duration": 4
+ },
+ {
+ "title": "Expect \"poll toBe\"",
+ "duration": 883
+ },
+ {
+ "title": "Expect \"poll toBe\"",
+ "duration": 8
+ }
+ ],
+ "startTime": "2026-09-06T10:27:59.479Z",
+ "annotations": [],
+ "attachments": [
+ {
+ "name": "25-template-workshop-3",
+ "contentType": "image/png",
+ "path": "E:\\work\\edit_3d\\unreal_tran\\ute2e\\reports\\scene-editor-closure-20260906\\test-results\\scene-editor-templates-closure-场景模板实际加载、保存重开:部件分解车间\\attachments\\25-template-workshop-3-0ae893985dd2a81428ef5ec0b0be20e102630da6.png"
+ }
+ ]
+ }
+ ],
+ "status": "expected"
+ }
+ ],
+ "id": "dcad141e22d6fb4f278b-aa4dc579893eb90e5a2c",
+ "file": "scene-editor-templates-closure.spec.ts",
+ "line": 60,
+ "column": 3
+ },
+ {
+ "title": "场景模板实际加载、保存重开:清洗检测车间",
+ "ok": true,
+ "tags": [],
+ "tests": [
+ {
+ "timeout": 180000,
+ "annotations": [],
+ "expectedStatus": "passed",
+ "projectId": "",
+ "projectName": "",
+ "results": [
+ {
+ "workerIndex": 0,
+ "parallelIndex": 0,
+ "status": "passed",
+ "duration": 8633,
+ "errors": [],
+ "stdout": [],
+ "stderr": [],
+ "retry": 0,
+ "steps": [
+ {
+ "title": "Expect \"poll toBe\"",
+ "duration": 15
+ },
+ {
+ "title": "Expect \"poll toBe\"",
+ "duration": 4
+ },
+ {
+ "title": "Expect \"poll toBe\"",
+ "duration": 879
+ },
+ {
+ "title": "Expect \"poll toBe\"",
+ "duration": 5
+ }
+ ],
+ "startTime": "2026-09-06T10:28:08.269Z",
+ "annotations": [],
+ "attachments": [
+ {
+ "name": "26-template-workshop-4",
+ "contentType": "image/png",
+ "path": "E:\\work\\edit_3d\\unreal_tran\\ute2e\\reports\\scene-editor-closure-20260906\\test-results\\scene-editor-templates-closure-场景模板实际加载、保存重开:清洗检测车间\\attachments\\26-template-workshop-4-18603ff60a745210c089d577791039692a6c53e4.png"
+ }
+ ]
+ }
+ ],
+ "status": "expected"
+ }
+ ],
+ "id": "dcad141e22d6fb4f278b-384fc3ca8bf72fa1c66a",
+ "file": "scene-editor-templates-closure.spec.ts",
+ "line": 60,
+ "column": 3
+ },
+ {
+ "title": "场景模板实际加载、保存重开:复装调试车间",
+ "ok": true,
+ "tags": [],
+ "tests": [
+ {
+ "timeout": 180000,
+ "annotations": [],
+ "expectedStatus": "passed",
+ "projectId": "",
+ "projectName": "",
+ "results": [
+ {
+ "workerIndex": 0,
+ "parallelIndex": 0,
+ "status": "passed",
+ "duration": 9034,
+ "errors": [],
+ "stdout": [],
+ "stderr": [],
+ "retry": 0,
+ "steps": [
+ {
+ "title": "Expect \"poll toBe\"",
+ "duration": 16
+ },
+ {
+ "title": "Expect \"poll toBe\"",
+ "duration": 5
+ },
+ {
+ "title": "Expect \"poll toBe\"",
+ "duration": 880
+ },
+ {
+ "title": "Expect \"poll toBe\"",
+ "duration": 18
+ }
+ ],
+ "startTime": "2026-09-06T10:28:16.917Z",
+ "annotations": [],
+ "attachments": [
+ {
+ "name": "27-template-workshop-5",
+ "contentType": "image/png",
+ "path": "E:\\work\\edit_3d\\unreal_tran\\ute2e\\reports\\scene-editor-closure-20260906\\test-results\\scene-editor-templates-closure-场景模板实际加载、保存重开:复装调试车间\\attachments\\27-template-workshop-5-b4abd6b6e7bdc106f54e61c26f139abb586b3b25.png"
+ }
+ ]
+ }
+ ],
+ "status": "expected"
+ }
+ ],
+ "id": "dcad141e22d6fb4f278b-d16adc7b3fa864b54758",
+ "file": "scene-editor-templates-closure.spec.ts",
+ "line": 60,
+ "column": 3
+ },
+ {
+ "title": "场景模板实际加载、保存重开:室外综合训练场",
+ "ok": true,
+ "tags": [],
+ "tests": [
+ {
+ "timeout": 180000,
+ "annotations": [],
+ "expectedStatus": "passed",
+ "projectId": "",
+ "projectName": "",
+ "results": [
+ {
+ "workerIndex": 0,
+ "parallelIndex": 0,
+ "status": "passed",
+ "duration": 8601,
+ "errors": [],
+ "stdout": [],
+ "stderr": [],
+ "retry": 0,
+ "steps": [
+ {
+ "title": "Expect \"poll toBe\"",
+ "duration": 31
+ },
+ {
+ "title": "Expect \"poll toBe\"",
+ "duration": 5
+ },
+ {
+ "title": "Expect \"poll toBe\"",
+ "duration": 874
+ },
+ {
+ "title": "Expect \"poll toBe\"",
+ "duration": 19
+ }
+ ],
+ "startTime": "2026-09-06T10:28:25.967Z",
+ "annotations": [],
+ "attachments": [
+ {
+ "name": "28-template-outdoor-1",
+ "contentType": "image/png",
+ "path": "E:\\work\\edit_3d\\unreal_tran\\ute2e\\reports\\scene-editor-closure-20260906\\test-results\\scene-editor-templates-closure-场景模板实际加载、保存重开:室外综合训练场\\attachments\\28-template-outdoor-1-252598baacd8ac3e60b8050ccbf5fa5e81cb875f.png"
+ }
+ ]
+ }
+ ],
+ "status": "expected"
+ }
+ ],
+ "id": "dcad141e22d6fb4f278b-a8fce07c8d37aab25759",
+ "file": "scene-editor-templates-closure.spec.ts",
+ "line": 60,
+ "column": 3
+ },
+ {
+ "title": "模板搜索与未保存切换确认保留和放弃",
+ "ok": true,
+ "tags": [],
+ "tests": [
+ {
+ "timeout": 180000,
+ "annotations": [],
+ "expectedStatus": "passed",
+ "projectId": "",
+ "projectName": "",
+ "results": [
+ {
+ "workerIndex": 0,
+ "parallelIndex": 0,
+ "status": "passed",
+ "duration": 7905,
+ "errors": [],
+ "stdout": [],
+ "stderr": [],
+ "retry": 0,
+ "steps": [
+ {
+ "title": "Expect \"poll toBe\"",
+ "duration": 480
+ },
+ {
+ "title": "Expect \"poll toBe\"",
+ "duration": 9
+ },
+ {
+ "title": "Expect \"poll toBe\"",
+ "duration": 867
+ }
+ ],
+ "startTime": "2026-09-06T10:28:34.583Z",
+ "annotations": [],
+ "attachments": [
+ {
+ "name": "29-template-confirm-empty",
+ "contentType": "image/png",
+ "path": "E:\\work\\edit_3d\\unreal_tran\\ute2e\\reports\\scene-editor-closure-20260906\\test-results\\scene-editor-templates-closure-模板搜索与未保存切换确认保留和放弃\\attachments\\29-template-confirm-empty-c3412d63dd2b64db245f43b7884bd18078c4603c.png"
+ }
+ ]
+ }
+ ],
+ "status": "expected"
+ }
+ ],
+ "id": "dcad141e22d6fb4f278b-795b90f9f94f80df7bfd",
+ "file": "scene-editor-templates-closure.spec.ts",
+ "line": 92,
+ "column": 1
+ },
+ {
+ "title": "八种基础资源逐项放置、真实拖放、搜索和保存重开",
+ "ok": true,
+ "tags": [],
+ "tests": [
+ {
+ "timeout": 180000,
+ "annotations": [],
+ "expectedStatus": "passed",
+ "projectId": "",
+ "projectName": "",
+ "results": [
+ {
+ "workerIndex": 0,
+ "parallelIndex": 0,
+ "status": "passed",
+ "duration": 9861,
+ "errors": [],
+ "stdout": [],
+ "stderr": [],
+ "retry": 0,
+ "steps": [
+ {
+ "title": "Expect \"poll toBe\"",
+ "duration": 7
+ },
+ {
+ "title": "Expect \"poll toBe\"",
+ "duration": 3
+ },
+ {
+ "title": "Expect \"poll toBe\"",
+ "duration": 5
+ },
+ {
+ "title": "Expect \"poll toBe\"",
+ "duration": 5
+ },
+ {
+ "title": "Expect \"poll toBe\"",
+ "duration": 6
+ },
+ {
+ "title": "Expect \"poll toBe\"",
+ "duration": 9
+ },
+ {
+ "title": "Expect \"poll toBe\"",
+ "duration": 37
+ },
+ {
+ "title": "Expect \"poll toBe\"",
+ "duration": 35
+ },
+ {
+ "title": "Expect \"poll toBe\"",
+ "duration": 11
+ },
+ {
+ "title": "Expect \"poll toBe\"",
+ "duration": 7
+ },
+ {
+ "title": "Expect \"poll toBe\"",
+ "duration": 9
+ },
+ {
+ "title": "Expect \"poll toBe\"",
+ "duration": 891
+ },
+ {
+ "title": "Expect \"poll toBe\"",
+ "duration": 6
+ }
+ ],
+ "startTime": "2026-09-06T10:28:42.501Z",
+ "annotations": [],
+ "attachments": [
+ {
+ "name": "30-eight-basic-assets-reopened",
+ "contentType": "image/png",
+ "path": "E:\\work\\edit_3d\\unreal_tran\\ute2e\\reports\\scene-editor-closure-20260906\\test-results\\scene-editor-templates-closure-八种基础资源逐项放置、真实拖放、搜索和保存重开\\attachments\\30-eight-basic-assets-reopened-db9d240a62964056c9443d3f4b6041716d28d76b.png"
+ }
+ ]
+ }
+ ],
+ "status": "expected"
+ }
+ ],
+ "id": "dcad141e22d6fb4f278b-90de9edbd4d7fd9e6d8a",
+ "file": "scene-editor-templates-closure.spec.ts",
+ "line": 123,
+ "column": 1
+ },
+ {
+ "title": "两种整体场地加载、重复放置阻止与显式删除后替换",
+ "ok": true,
+ "tags": [],
+ "tests": [
+ {
+ "timeout": 180000,
+ "annotations": [],
+ "expectedStatus": "passed",
+ "projectId": "",
+ "projectName": "",
+ "results": [
+ {
+ "workerIndex": 0,
+ "parallelIndex": 0,
+ "status": "passed",
+ "duration": 14433,
+ "errors": [],
+ "stdout": [],
+ "stderr": [],
+ "retry": 0,
+ "steps": [
+ {
+ "title": "Expect \"poll toBe\"",
+ "duration": 20
+ },
+ {
+ "title": "Expect \"poll toBe\"",
+ "duration": 2
+ },
+ {
+ "title": "Expect \"poll toBe\"",
+ "duration": 266
+ },
+ {
+ "title": "Expect \"poll toBe\"",
+ "duration": 893
+ },
+ {
+ "title": "Expect \"poll toBe\"",
+ "duration": 10
+ },
+ {
+ "title": "Expect \"poll toBe\"",
+ "duration": 4
+ },
+ {
+ "title": "Expect \"poll toBe\"",
+ "duration": 552
+ },
+ {
+ "title": "Expect \"poll toBe\"",
+ "duration": 880
+ },
+ {
+ "title": "Expect \"poll toBe\"",
+ "duration": 333
+ }
+ ],
+ "startTime": "2026-09-06T10:28:52.372Z",
+ "annotations": [],
+ "attachments": [
+ {
+ "name": "31-venue-depot-reopened",
+ "contentType": "image/png",
+ "path": "E:\\work\\edit_3d\\unreal_tran\\ute2e\\reports\\scene-editor-closure-20260906\\test-results\\scene-editor-templates-closure-两种整体场地加载、重复放置阻止与显式删除后替换\\attachments\\31-venue-depot-reopened-7f145261822d801517e00cbd9b9c3aaf01efa6ec.png"
+ },
+ {
+ "name": "32-venue-terrain-reopened",
+ "contentType": "image/png",
+ "path": "E:\\work\\edit_3d\\unreal_tran\\ute2e\\reports\\scene-editor-closure-20260906\\test-results\\scene-editor-templates-closure-两种整体场地加载、重复放置阻止与显式删除后替换\\attachments\\32-venue-terrain-reopened-46b72440b98f0cbb6efd3b2f54fa7438e7bc906e.png"
+ }
+ ]
+ }
+ ],
+ "status": "expected"
+ }
+ ],
+ "id": "dcad141e22d6fb4f278b-a154681324f8436d76e2",
+ "file": "scene-editor-templates-closure.spec.ts",
+ "line": 167,
+ "column": 1
+ },
+ {
+ "title": "模型成果狐狸卡车双击与真实拖入、树搜索和部件隐藏重开",
+ "ok": true,
+ "tags": [],
+ "tests": [
+ {
+ "timeout": 180000,
+ "annotations": [],
+ "expectedStatus": "passed",
+ "projectId": "",
+ "projectName": "",
+ "results": [
+ {
+ "workerIndex": 0,
+ "parallelIndex": 0,
+ "status": "passed",
+ "duration": 18765,
+ "errors": [],
+ "stdout": [],
+ "stderr": [],
+ "retry": 0,
+ "steps": [
+ {
+ "title": "Expect \"poll toBe\"",
+ "duration": 375
+ },
+ {
+ "title": "Expect \"poll toBe\"",
+ "duration": 2
+ },
+ {
+ "title": "Expect \"poll toBe\"",
+ "duration": 870
+ },
+ {
+ "title": "Expect \"poll toBe\"",
+ "duration": 4
+ },
+ {
+ "title": "Expect \"poll toBe\"",
+ "duration": 113
+ },
+ {
+ "title": "Expect \"poll toBe\"",
+ "duration": 8
+ },
+ {
+ "title": "Expect \"poll toBe\"",
+ "duration": 880
+ },
+ {
+ "title": "Expect \"poll toBe\"",
+ "duration": 874
+ },
+ {
+ "title": "Expect \"poll toBe\"",
+ "duration": 885
+ },
+ {
+ "title": "Expect \"poll toBe\"",
+ "duration": 11
+ }
+ ],
+ "startTime": "2026-09-06T10:29:06.817Z",
+ "annotations": [],
+ "attachments": [
+ {
+ "name": "33-fox-placed-visible",
+ "contentType": "image/png",
+ "path": "E:\\work\\edit_3d\\unreal_tran\\ute2e\\reports\\scene-editor-closure-20260906\\test-results\\scene-editor-templates-closure-模型成果狐狸卡车双击与真实拖入、树搜索和部件隐藏重开\\attachments\\33-fox-placed-visible-fa2377e8659c4f45630894cf655e6309273deae5.png"
+ },
+ {
+ "name": "34-truck-placed-part-restored",
+ "contentType": "image/png",
+ "path": "E:\\work\\edit_3d\\unreal_tran\\ute2e\\reports\\scene-editor-closure-20260906\\test-results\\scene-editor-templates-closure-模型成果狐狸卡车双击与真实拖入、树搜索和部件隐藏重开\\attachments\\34-truck-placed-part-restored-8af7f3874fcde860cdfaf3b1075526b4dd84e572.png"
+ }
+ ]
+ }
+ ],
+ "status": "expected"
+ }
+ ],
+ "id": "dcad141e22d6fb4f278b-47b93ecb3dad3096dda4",
+ "file": "scene-editor-templates-closure.spec.ts",
+ "line": 198,
+ "column": 1
+ }
+ ]
+ },
+ {
+ "title": "scene-editor-viewport-closure.spec.ts",
+ "file": "scene-editor-viewport-closure.spec.ts",
+ "column": 0,
+ "line": 0,
+ "specs": [
+ {
+ "title": "真实相机操控、视口拾取、预览禁写和两尺寸主题可读性",
+ "ok": true,
+ "tags": [],
+ "tests": [
+ {
+ "timeout": 180000,
+ "annotations": [],
+ "expectedStatus": "passed",
+ "projectId": "",
+ "projectName": "",
+ "results": [
+ {
+ "workerIndex": 0,
+ "parallelIndex": 0,
+ "status": "passed",
+ "duration": 18992,
+ "errors": [],
+ "stdout": [],
+ "stderr": [],
+ "retry": 0,
+ "steps": [
+ {
+ "title": "Expect \"poll toBe\"",
+ "duration": 172
+ },
+ {
+ "title": "Expect \"poll toBe\"",
+ "duration": 126
+ },
+ {
+ "title": "Expect \"poll toBe\"",
+ "duration": 4916
+ },
+ {
+ "title": "Expect \"poll toBe\"",
+ "duration": 3894
+ },
+ {
+ "title": "Expect \"poll toBe\"",
+ "duration": 110
+ },
+ {
+ "title": "Expect \"poll toBe\"",
+ "duration": 112
+ },
+ {
+ "title": "Expect \"poll toBe\"",
+ "duration": 116
+ },
+ {
+ "title": "Expect \"poll toBe\"",
+ "duration": 112
+ },
+ {
+ "title": "Expect \"poll toBe\"",
+ "duration": 9
+ }
+ ],
+ "startTime": "2026-09-06T10:29:25.609Z",
+ "annotations": [],
+ "attachments": [
+ {
+ "name": "40-viewport-dark-1920",
+ "contentType": "image/png",
+ "path": "E:\\work\\edit_3d\\unreal_tran\\ute2e\\reports\\scene-editor-closure-20260906\\test-results\\scene-editor-viewport-closure-真实相机操控、视口拾取、预览禁写和两尺寸主题可读性\\attachments\\40-viewport-dark-1920-6f73c71d463ef8d27e1da0b2e3a7ee470cce0995.png"
+ },
+ {
+ "name": "41-viewport-light-1440",
+ "contentType": "image/png",
+ "path": "E:\\work\\edit_3d\\unreal_tran\\ute2e\\reports\\scene-editor-closure-20260906\\test-results\\scene-editor-viewport-closure-真实相机操控、视口拾取、预览禁写和两尺寸主题可读性\\attachments\\41-viewport-light-1440-dc92e9151d424a939ea25d65d5db32e301d4bdcd.png"
+ },
+ {
+ "name": "42-fox-preview-original-material",
+ "contentType": "image/png",
+ "path": "E:\\work\\edit_3d\\unreal_tran\\ute2e\\reports\\scene-editor-closure-20260906\\test-results\\scene-editor-viewport-closure-真实相机操控、视口拾取、预览禁写和两尺寸主题可读性\\attachments\\42-fox-preview-original-material-1f3084811239e15fa31ff9b11641c1a758c2e0a7.png"
+ }
+ ]
+ }
+ ],
+ "status": "expected"
+ }
+ ],
+ "id": "6ef420bb3894a11c27b3-673d73e06ee8cb25a67e",
+ "file": "scene-editor-viewport-closure.spec.ts",
+ "line": 76,
+ "column": 1
+ },
+ {
+ "title": "真实变换手柄移动旋转缩放、吸附与保存重开",
+ "ok": true,
+ "tags": [],
+ "tests": [
+ {
+ "timeout": 180000,
+ "annotations": [],
+ "expectedStatus": "passed",
+ "projectId": "",
+ "projectName": "",
+ "results": [
+ {
+ "workerIndex": 0,
+ "parallelIndex": 0,
+ "status": "passed",
+ "duration": 11654,
+ "errors": [],
+ "stdout": [],
+ "stderr": [],
+ "retry": 0,
+ "steps": [
+ {
+ "title": "Expect \"poll toBe\"",
+ "duration": 418
+ },
+ {
+ "title": "Expect \"poll toBe\"",
+ "duration": 2
+ },
+ {
+ "title": "Expect \"poll toBe\"",
+ "duration": 114
+ },
+ {
+ "title": "Expect \"poll toEqual\"",
+ "duration": 5
+ },
+ {
+ "title": "Expect \"poll toBe\"",
+ "duration": 881
+ },
+ {
+ "title": "Expect \"poll toBe\"",
+ "duration": 11
+ }
+ ],
+ "startTime": "2026-09-06T10:29:44.613Z",
+ "annotations": [],
+ "attachments": [
+ {
+ "name": "43-real-transform-gizmo",
+ "contentType": "image/png",
+ "path": "E:\\work\\edit_3d\\unreal_tran\\ute2e\\reports\\scene-editor-closure-20260906\\test-results\\scene-editor-viewport-closure-真实变换手柄移动旋转缩放、吸附与保存重开\\attachments\\43-real-transform-gizmo-b8e33ddb28e729d479a3c7d69b44c9e2690cbc4c.png"
+ }
+ ]
+ }
+ ],
+ "status": "expected"
+ }
+ ],
+ "id": "6ef420bb3894a11c27b3-6bedbd20eae431b8626d",
+ "file": "scene-editor-viewport-closure.spec.ts",
+ "line": 120,
+ "column": 1
+ },
+ {
+ "title": "缓存标签暂停键盘、草稿恢复以及关闭取消和保存失败留页",
+ "ok": true,
+ "tags": [],
+ "tests": [
+ {
+ "timeout": 180000,
+ "annotations": [],
+ "expectedStatus": "passed",
+ "projectId": "",
+ "projectName": "",
+ "results": [
+ {
+ "workerIndex": 0,
+ "parallelIndex": 0,
+ "status": "passed",
+ "duration": 7369,
+ "errors": [],
+ "stdout": [],
+ "stderr": [],
+ "retry": 0,
+ "steps": [
+ {
+ "title": "Expect \"poll toBe\"",
+ "duration": 22
+ },
+ {
+ "title": "Expect \"poll toBe\"",
+ "duration": 112
+ },
+ {
+ "title": "Expect \"poll toBe\"",
+ "duration": 10
+ }
+ ],
+ "startTime": "2026-09-06T10:29:56.280Z",
+ "annotations": [],
+ "attachments": [
+ {
+ "name": "44-cache-close-save-failure",
+ "contentType": "image/png",
+ "path": "E:\\work\\edit_3d\\unreal_tran\\ute2e\\reports\\scene-editor-closure-20260906\\test-results\\scene-editor-viewport-closure-缓存标签暂停键盘、草稿恢复以及关闭取消和保存失败留页\\attachments\\44-cache-close-save-failure-ebbf3ab612f78f5af1af442d083ce3fdf538576f.png"
+ }
+ ]
+ }
+ ],
+ "status": "expected"
+ }
+ ],
+ "id": "6ef420bb3894a11c27b3-07d6378e52141ea1d6de",
+ "file": "scene-editor-viewport-closure.spec.ts",
+ "line": 151,
+ "column": 1
+ }
+ ]
+ },
+ {
+ "title": "scene-editor-workflow-closure.spec.ts",
+ "file": "scene-editor-workflow-closure.spec.ts",
+ "column": 0,
+ "line": 0,
+ "specs": [
+ {
+ "title": "狐狸:真实细分权限、全部属性、清空绑定、蒙皮保护、相机与封面保存重开",
+ "ok": true,
+ "tags": [],
+ "tests": [
+ {
+ "timeout": 180000,
+ "annotations": [],
+ "expectedStatus": "passed",
+ "projectId": "",
+ "projectName": "",
+ "results": [
+ {
+ "workerIndex": 0,
+ "parallelIndex": 0,
+ "status": "passed",
+ "duration": 11283,
+ "errors": [],
+ "stdout": [],
+ "stderr": [],
+ "retry": 0,
+ "steps": [
+ {
+ "title": "Expect \"poll toBe\"",
+ "duration": 24
+ },
+ {
+ "title": "Expect \"poll toBe\"",
+ "duration": 886
+ },
+ {
+ "title": "Expect \"poll toBe\"",
+ "duration": 476
+ }
+ ],
+ "startTime": "2026-09-06T10:30:03.679Z",
+ "annotations": [],
+ "attachments": [
+ {
+ "name": "01-fox-actual-scene",
+ "contentType": "image/png",
+ "path": "E:\\work\\edit_3d\\unreal_tran\\ute2e\\reports\\scene-editor-closure-20260906\\test-results\\scene-editor-workflow-clos-1cfb2-权限、全部属性、清空绑定、蒙皮保护、相机与封面保存重开\\attachments\\01-fox-actual-scene-f8ceace857505589c4ff000436a8f300253b8663.png"
+ },
+ {
+ "name": "02-fox-reopened-skin-protection",
+ "contentType": "image/png",
+ "path": "E:\\work\\edit_3d\\unreal_tran\\ute2e\\reports\\scene-editor-closure-20260906\\test-results\\scene-editor-workflow-clos-1cfb2-权限、全部属性、清空绑定、蒙皮保护、相机与封面保存重开\\attachments\\02-fox-reopened-skin-protection-1a05d737459c22e1308f422fd83510e30b1cac30.png"
+ }
+ ]
+ }
+ ],
+ "status": "expected"
+ }
+ ],
+ "id": "89128dc7deef11d952e1-4cfe42a5927f1dd90267",
+ "file": "scene-editor-workflow-closure.spec.ts",
+ "line": 13,
+ "column": 1
+ },
+ {
+ "title": "牛奶卡车:刚性部件独立变换、软删除、历史与保存基线一致",
+ "ok": true,
+ "tags": [],
+ "tests": [
+ {
+ "timeout": 180000,
+ "annotations": [],
+ "expectedStatus": "passed",
+ "projectId": "",
+ "projectName": "",
+ "results": [
+ {
+ "workerIndex": 0,
+ "parallelIndex": 0,
+ "status": "passed",
+ "duration": 18215,
+ "errors": [],
+ "stdout": [],
+ "stderr": [],
+ "retry": 0,
+ "steps": [
+ {
+ "title": "Expect \"poll toBe\"",
+ "duration": 389
+ },
+ {
+ "title": "Expect \"poll toBe\"",
+ "duration": 884
+ },
+ {
+ "title": "Expect \"poll toBe\"",
+ "duration": 152
+ },
+ {
+ "title": "Expect \"poll toBe\"",
+ "duration": 143
+ },
+ {
+ "title": "Expect \"poll toBe\"",
+ "duration": 12
+ },
+ {
+ "title": "Expect \"poll toBe\"",
+ "duration": 885
+ },
+ {
+ "title": "Expect \"poll toBe\"",
+ "duration": 9
+ },
+ {
+ "title": "Expect \"poll toBe\"",
+ "duration": 9
+ },
+ {
+ "title": "Expect \"poll toBe\"",
+ "duration": 11
+ },
+ {
+ "title": "Expect \"poll toBe\"",
+ "duration": 9
+ },
+ {
+ "title": "Expect \"poll toBe\"",
+ "duration": 896
+ },
+ {
+ "title": "Expect \"poll toBe\"",
+ "duration": 12
+ },
+ {
+ "title": "Expect \"poll toBe\"",
+ "duration": 17
+ },
+ {
+ "title": "Expect \"poll toBe\"",
+ "duration": 916
+ },
+ {
+ "title": "Expect \"poll toBe\"",
+ "duration": 483
+ }
+ ],
+ "startTime": "2026-09-06T10:30:14.977Z",
+ "annotations": [],
+ "attachments": [
+ {
+ "name": "07-garage-full",
+ "contentType": "image/png",
+ "path": "E:\\work\\edit_3d\\unreal_tran\\ute2e\\reports\\scene-editor-closure-20260906\\test-results\\scene-editor-workflow-closure-牛奶卡车:刚性部件独立变换、软删除、历史与保存基线一致\\attachments\\07-garage-full-f72eec4133d7a368ffd3b648288dd30fef75161e.png"
+ },
+ {
+ "name": "07-garage-xray",
+ "contentType": "image/png",
+ "path": "E:\\work\\edit_3d\\unreal_tran\\ute2e\\reports\\scene-editor-closure-20260906\\test-results\\scene-editor-workflow-closure-牛奶卡车:刚性部件独立变换、软删除、历史与保存基线一致\\attachments\\07-garage-xray-35ccddc218c0d72103caabee8a6cb31d52f0bbed.png"
+ },
+ {
+ "name": "07-garage-hidden",
+ "contentType": "image/png",
+ "path": "E:\\work\\edit_3d\\unreal_tran\\ute2e\\reports\\scene-editor-closure-20260906\\test-results\\scene-editor-workflow-closure-牛奶卡车:刚性部件独立变换、软删除、历史与保存基线一致\\attachments\\07-garage-hidden-9c3abe15ddc69936a78c85fa8778365ab0d515dd.png"
+ },
+ {
+ "name": "03-truck-part-history-reopened",
+ "contentType": "image/png",
+ "path": "E:\\work\\edit_3d\\unreal_tran\\ute2e\\reports\\scene-editor-closure-20260906\\test-results\\scene-editor-workflow-closure-牛奶卡车:刚性部件独立变换、软删除、历史与保存基线一致\\attachments\\03-truck-part-history-reopened-2a3b2f13fb63ded2894505c93f7cffa332cedf99.png"
+ }
+ ]
+ }
+ ],
+ "status": "expected"
+ }
+ ],
+ "id": "89128dc7deef11d952e1-116cd3f83090b53d1ed4",
+ "file": "scene-editor-workflow-closure.spec.ts",
+ "line": 67,
+ "column": 1
+ },
+ {
+ "title": "新建独立场景:取消、失败、保护原工程以及成功新 ID",
+ "ok": true,
+ "tags": [],
+ "tests": [
+ {
+ "timeout": 180000,
+ "annotations": [],
+ "expectedStatus": "passed",
+ "projectId": "",
+ "projectName": "",
+ "results": [
+ {
+ "workerIndex": 0,
+ "parallelIndex": 0,
+ "status": "passed",
+ "duration": 25457,
+ "errors": [],
+ "stdout": [],
+ "stderr": [],
+ "retry": 0,
+ "steps": [
+ {
+ "title": "Expect \"poll toBe\"",
+ "duration": 7
+ },
+ {
+ "title": "Expect \"poll toBeGreaterThan\"",
+ "duration": 871
+ },
+ {
+ "title": "Expect \"poll toBe\"",
+ "duration": 4
+ },
+ {
+ "title": "Expect \"poll toBe\"",
+ "duration": 864
+ },
+ {
+ "title": "Expect \"poll toBe\"",
+ "duration": 6
+ },
+ {
+ "title": "Expect \"poll toBe\"",
+ "duration": 644
+ },
+ {
+ "title": "Expect \"poll toBe\"",
+ "duration": 4
+ }
+ ],
+ "startTime": "2026-09-06T10:30:33.202Z",
+ "annotations": [],
+ "attachments": [
+ {
+ "name": "04-new-failure-original-preserved",
+ "contentType": "image/png",
+ "path": "E:\\work\\edit_3d\\unreal_tran\\ute2e\\reports\\scene-editor-closure-20260906\\test-results\\scene-editor-workflow-closure-新建独立场景:取消、失败、保护原工程以及成功新-ID\\attachments\\04-new-failure-original-preserved-fe807bceb8705d176e7c0360db656ee145405d34.png"
+ },
+ {
+ "name": "05-new-separate-scene",
+ "contentType": "image/png",
+ "path": "E:\\work\\edit_3d\\unreal_tran\\ute2e\\reports\\scene-editor-closure-20260906\\test-results\\scene-editor-workflow-closure-新建独立场景:取消、失败、保护原工程以及成功新-ID\\attachments\\05-new-separate-scene-5de74911a31d6d4cc92c75f9ae16f6fa4d772149.png"
+ }
+ ]
+ }
+ ],
+ "status": "expected"
+ }
+ ],
+ "id": "89128dc7deef11d952e1-649f0b657201c1a9ec8a",
+ "file": "scene-editor-workflow-closure.spec.ts",
+ "line": 124,
+ "column": 1
+ },
+ {
+ "title": "只读管理员:可浏览预览与导出,模板/资源/键盘/接口不能改场景",
+ "ok": true,
+ "tags": [],
+ "tests": [
+ {
+ "timeout": 180000,
+ "annotations": [],
+ "expectedStatus": "passed",
+ "projectId": "",
+ "projectName": "",
+ "results": [
+ {
+ "workerIndex": 0,
+ "parallelIndex": 0,
+ "status": "passed",
+ "duration": 7136,
+ "errors": [],
+ "stdout": [],
+ "stderr": [],
+ "retry": 0,
+ "steps": [
+ {
+ "title": "Expect \"poll toBe\"",
+ "duration": 1052
+ }
+ ],
+ "startTime": "2026-09-06T10:30:58.675Z",
+ "annotations": [],
+ "attachments": [
+ {
+ "name": "06-readonly-scene-permissions",
+ "contentType": "image/png",
+ "path": "E:\\work\\edit_3d\\unreal_tran\\ute2e\\reports\\scene-editor-closure-20260906\\test-results\\scene-editor-workflow-clos-22aa1-员:可浏览预览与导出,模板-资源-键盘-接口不能改场景\\attachments\\06-readonly-scene-permissions-349245dac8e17c0f8f046075a554dca688143e55.png"
+ }
+ ]
+ }
+ ],
+ "status": "expected"
+ }
+ ],
+ "id": "89128dc7deef11d952e1-0bd930512f42c077d430",
+ "file": "scene-editor-workflow-closure.spec.ts",
+ "line": 169,
+ "column": 1
+ }
+ ]
+ }
+ ],
+ "errors": [],
+ "stats": {
+ "startTime": "2026-09-06T10:24:04.121Z",
+ "duration": 422286.673,
+ "expected": 33,
+ "skipped": 0,
+ "unexpected": 0,
+ "flaky": 0
+ }
+}
\ No newline at end of file
diff --git a/reports/scene-editor-closure-20260906/screenshot-captions.json b/reports/scene-editor-closure-20260906/screenshot-captions.json
new file mode 100644
index 0000000..ad06b1c
--- /dev/null
+++ b/reports/scene-editor-closure-20260906/screenshot-captions.json
@@ -0,0 +1,210 @@
+{
+ "01-fox-actual-scene.png": {
+ "title": "原场景副本中实际加载的狐狸",
+ "description": "狐狸来源固定模型版本,聚焦后核对真实几何。原场景已有模板标识与地形对象不一致,未擅自推断并改写原件。"
+ },
+ "02-fox-reopened-skin-protection.png": {
+ "title": "狐狸属性保存重开与蒙皮部件保护",
+ "description": "位置、旋转、小数缩放和空绑定保存重开;狐狸蒙皮部件显示只可查看与高亮,独立变换/隐藏禁止。姿态为属性测试值。"
+ },
+ "03-truck-part-history-reopened.png": {
+ "title": "卡车部件编辑和历史恢复后的场景",
+ "description": "刚性部件做过独立位移、软删除与撤销恢复;保留的测试姿态不应误判为模型加载损坏,具体值以文档和刷新断言为准。"
+ },
+ "04-new-failure-original-preserved.png": {
+ "title": "新建失败时原场景和恢复保护仍保留",
+ "description": "定向注入创建 POST 503,失败提示为预期分支。用例分别检查保存与选择放弃两种路径;选择放弃也不会在创建失败时清除原 dirty/恢复稿。最终成功创建后,重新打开旧缓存页应显示服务端内容。具体分支以最终断言为准。"
+ },
+ "05-new-separate-scene.png": {
+ "title": "真正创建独立场景并保存重开",
+ "description": "新项目具有独立 ID,初始空白模板正常;原项目内容另通过 API 核对保留。"
+ },
+ "06-readonly-scene-permissions.png": {
+ "title": "只读管理员浏览场景",
+ "description": "更新、导入、新建、发布等写入口禁用;查看、预览和导出仍可用,直接内容 PUT 返回 403。"
+ },
+ "07-garage-full.png": {
+ "title": "车库围护完整显示",
+ "description": "实际点击完整模式并核对导出字段;建筑墙体、屋顶与支柱可能遮住内部模型,属于本模式显示范围。"
+ },
+ "07-garage-xray.png": {
+ "title": "车库围护透明查看",
+ "description": "实际点击透明模式,导出 enclosureMode 同步;透明围护与保留的结构柱区别于模型未加载。"
+ },
+ "07-garage-hidden.png": {
+ "title": "车库围护隐藏",
+ "description": "实际点击隐藏外壳,导出后保存并刷新验证模式保留;内部对象不会因为围护隐藏而删除。"
+ },
+ "10-invalid-reference-rolled-back.png": {
+ "title": "错误资源编码被拒绝并保留场景",
+ "description": "JSON 明确引用不存在的 assetCode,预期报错;原场景对象仍保留,未回退为其他模型文件。"
+ },
+ "11-small-glb-real-upload.png": {
+ "title": "小 GLB 真实上传进入当前场景",
+ "description": "通过文件输入上传测试 Box.glb,受控资产登记后加入场景;与之前故意损坏文件/上传503分支分开验收。"
+ },
+ "12-json-glb-reopened.png": {
+ "title": "JSON 与受控 GLB 保存刷新往返",
+ "description": "有效工程导入后的对象、受控 URI 和渲染参数重开保持。画面相机为测试导入值,不强行套用默认视角。"
+ },
+ "13-save-failure-keeps-draft.png": {
+ "title": "保存 PUT 503 时保留未存稿",
+ "description": "注入保存失败,版本未前进且 dirty 保留;红色/警告提示为故障恢复验收的预期画面。"
+ },
+ "14-cover-failure-keeps-old-image.png": {
+ "title": "封面上传失败时保留旧图",
+ "description": "封面上传被注入503,正文仍可成功保存,真实提示封面降级;不把此步骤记为新封面生成成功。"
+ },
+ "15-save-retry-success.png": {
+ "title": "恢复服务后重新保存成功",
+ "description": "恢复请求后再次修改并保存、刷新核对内容;先前故障提示若仍处正常消退周期,不改变当前响应和刷新断言。"
+ },
+ "16-real-unsaved-recovery.png": {
+ "title": "真实未保存输入恢复到编辑器",
+ "description": "由实际输入产生本地恢复稿,刷新后明确恢复;恢复仅回到当前编辑视口,未自动写入服务器。"
+ },
+ "17-conflict-does-not-overwrite.png": {
+ "title": "409 版本冲突不覆盖最新内容",
+ "description": "另一请求先保存新版本,当前旧会话再保存被拒绝;未存修改保留,不能显示保存成功。"
+ },
+ "18-stale-recovery-explicit-decision.png": {
+ "title": "旧版本恢复稿隔离与下载决策",
+ "description": "旧稿不能直接覆盖最新服务端版本,需明确下载/保留或丢弃;此图用于验证保护提示。"
+ },
+ "19-publish-failure-stays-draft.png": {
+ "title": "发布接口故障仍保持草稿",
+ "description": "定向注入发布 POST 503;保存和发布为独立结果,项目状态保持 DRAFT,后续恢复才真正发布。"
+ },
+ "20-scene-published-fixed-version.png": {
+ "title": "场景发布完成并固定模型依赖",
+ "description": "发布 POST 成功、当前版本与 publishedVersionId 一致;模型来源固定到既有发布版本。"
+ },
+ "21-published-scene-cover-in-list.png": {
+ "title": "返回列表查看实际场景封面",
+ "description": "返回列表保留编辑器缓存标签;搜索当前项目并验证图片 naturalWidth 大于零,封面来自服务端受控资产。"
+ },
+ "20-template-depot-1.png": {
+ "title": "真实装备车库模板保存重开",
+ "description": "独立副本通过模板按钮加载真实车库,保存后刷新;左侧模板选中状态和画面环境同时核对。"
+ },
+ "21-template-terrain-1.png": {
+ "title": "真实野外训练场模板保存重开",
+ "description": "真实起伏地形和默认装备、区域、照明种子对象;不把默认装备占位模型误称为狐狸。"
+ },
+ "22-template-workshop-1.png": {
+ "title": "综合检验车间的空白场地",
+ "description": "此模板设计为锁定地板和零业务对象,空白是正常模板结果;保存刷新后仍为零对象。"
+ },
+ "23-template-workshop-real.png": {
+ "title": "演示维修车间的 74 个独立部件",
+ "description": "通过可拆分模板加载,74 个场景对象保存重开;与整体装备车库为不同资源。"
+ },
+ "24-template-workshop-2.png": {
+ "title": "总成拆卸车间模板",
+ "description": "装备主体、工作台和存放架三种种子资源,模板选择、类型和保存刷新一致。"
+ },
+ "25-template-workshop-3.png": {
+ "title": "部件分解车间模板",
+ "description": "工作台、存放架和视觉采集设备三种种子资源保存重开。"
+ },
+ "26-template-workshop-4.png": {
+ "title": "清洗检测车间模板",
+ "description": "诊断工位、工作台、采集设备和安全区域四种种子资源保存重开。"
+ },
+ "27-template-workshop-5.png": {
+ "title": "复装调试车间模板",
+ "description": "诊断工位、工作台、照明和人员占位四种种子资源保存重开;占位不代表真实设备接入。"
+ },
+ "28-template-outdoor-1.png": {
+ "title": "室外综合训练场模板",
+ "description": "程序化室外场地及三种种子资源,区别于正式起伏地形模板。"
+ },
+ "29-template-confirm-empty.png": {
+ "title": "模板切换确认与空白模板保存",
+ "description": "先取消切换确认保留未存对象,再明确放弃并切换;图中空场地是确认后的正常结果。"
+ },
+ "30-eight-basic-assets-reopened.png": {
+ "title": "八种基础资源逐项放置并刷新",
+ "description": "8 种资源分别双击并分开摆放,第 9 个工作台通过真实鼠标拖入;保存后核对各对象位置和唯一编号。"
+ },
+ "31-venue-depot-reopened.png": {
+ "title": "资源库车库作为整体场地",
+ "description": "从空白模板的资源库加载车库并保存重开;已有整体场地时阻止重复叠加。"
+ },
+ "32-venue-terrain-reopened.png": {
+ "title": "显式删除旧场地后加载真实地形",
+ "description": "先删除车库,再加入地形;保存刷新确认新场地资产引用与起伏高度,未静默覆盖原场地。"
+ },
+ "33-fox-placed-visible.png": {
+ "title": "API 已发布狐狸模型双击放置",
+ "description": "真实资源卡片双击导入当前实例,移动到 X=-3 并聚焦;保留来源项目和已发布固定版本。"
+ },
+ "34-truck-placed-part-restored.png": {
+ "title": "真实拖入卡车并完成部件隐藏往返",
+ "description": "卡车通过鼠标拖入;刚体部件隐藏保存刷新后再恢复可见,取消高亮后展示完整材质。聚焦卡车导致左侧狐狸部分出画,不是狐狸加载丢失。"
+ },
+ "40-viewport-dark-1920.png": {
+ "title": "1920 暗色主题的真实视口操作",
+ "description": "实际轨道旋转、右键平移、滚轮和点击拾取后检查侧栏、属性和视口;定位钩子只读几何,不修改场景。"
+ },
+ "41-viewport-light-1440.png": {
+ "title": "1440 军工绿主题的完整编辑布局",
+ "description": "真实切换主题和浏览器尺寸,检查长工具栏、画布与右侧属性栏不会互相遮挡。"
+ },
+ "42-fox-preview-original-material.png": {
+ "title": "狐狸运行预览与原材质",
+ "description": "进入静态场景预览,隐藏选中高亮和手柄并禁止写入;黄色狐狸可见不代表原生动画或蓝图已执行。"
+ },
+ "43-real-transform-gizmo.png": {
+ "title": "真实鼠标拖动三种变换手柄",
+ "description": "移动、旋转和单轴缩放后的故意测试姿态;通过真实鼠标命中手柄,只读投影仅用于定位,保存重开再核对数值。"
+ },
+ "44-cache-close-save-failure.png": {
+ "title": "后台缓存和关闭保存失败保留编辑器",
+ "description": "切标签保留未存内容、后台快捷键不修改对象;关闭时保存503为预期故障注入,当前场景和未存稿保留。"
+ },
+ "51-scene-update-only-saved.png": {
+ "title": "只有场景更新权限仍可保存固定模型",
+ "description": "已有固定依赖通过场景授权读取并保存刷新;无模型页面权限,直接来源项目访问和发布接口仍拒绝。"
+ },
+ "52-scene-publish-only-no-save.png": {
+ "title": "只有发布权限发布已保存版本",
+ "description": "真实浏览器仅发送一个发布 POST,没有内容 PUT 或封面上传;发布权限不扩大为编辑权限。"
+ },
+ "53-scene-route-access-denied.png": {
+ "title": "没有场景页面权限时拒绝入口",
+ "description": "编辑路由被拦截且直接内容接口返回403;画面不是场景加载成功的证据。"
+ },
+ "54-scene-role-switch-cancel-keeps-draft.png": {
+ "title": "取消切角色保留后台场景草稿",
+ "description": "关闭未存确认后没有角色 PUT、没有保存请求;返回场景标签仍保留编辑名称和 dirty。"
+ },
+ "55-scene-role-switch-saved-readonly.png": {
+ "title": "保存后切换角色并重建只读权限",
+ "description": "明确保存后才切换角色,重新启动拉取新权限;场景名称保存成功,更新和发布入口按新角色禁用。"
+ },
+ "56-scene-other-tab-recovery-preserved.png": {
+ "title": "另一浏览器页的真实恢复稿仍保留",
+ "description": "A 页保存并明确删除自己的恢复稿,B 页实际输入的未存稿及版本仍保留;未通过伪造 localStorage 构造该分支。"
+ },
+ "60-scene-load-failure-retry.png": {
+ "title": "加载失败保留中性状态和重试入口",
+ "description": "项目 GET 503 注入后不展示可写半场景;错误详情由短消息提供,页面仍能重新加载。"
+ },
+ "61-model-loading-blocks-partial-save.png": {
+ "title": "模型下载未完成时禁止半提交",
+ "description": "真实延迟受控资源下载,视口显示加载状态,保存/导出/发布等操作禁用,关闭被阻止;释放请求后再验证完整加载。"
+ },
+ "62-new-edits-preserved-during-save.png": {
+ "title": "保存快照 A 期间编辑 B 仍保留",
+ "description": "延迟PUT后继续修改名称,先完成的请求只保存A;新修改B保持dirty,重复快捷键不重复提交,随后保存刷新核对B。"
+ },
+ "70-fox-training-scene-reopened.png": {
+ "title": "新发布狐狸场景在训练编排中重开",
+ "description": "独立训练副本真实选择新发布场景,保存 TRAINING_SCENE 固定版本并刷新,橙黄色狐狸实际可见;仅验证静态还原。"
+ },
+ "70-truck-training-scene-reopened.png": {
+ "title": "新发布卡车场景在训练编排中重开",
+ "description": "独立训练副本绑定新场景固定版本后保存刷新,卡车实际可见;默认车库支柱局部遮挡车身,未将遮挡误认为模型丢失。"
+ }
+}
\ No newline at end of file
diff --git a/reports/scene-editor-closure-20260906/screenshot-verification.json b/reports/scene-editor-closure-20260906/screenshot-verification.json
new file mode 100644
index 0000000..0e2f64a
--- /dev/null
+++ b/reports/scene-editor-closure-20260906/screenshot-verification.json
@@ -0,0 +1,626 @@
+[
+ {
+ "file": "01-fox-actual-scene.png",
+ "status": "PASS",
+ "captionPresent": true,
+ "associations": [
+ {
+ "testTitle": "狐狸:真实细分权限、全部属性、清空绑定、蒙皮保护、相机与封面保存重开",
+ "status": "PASS",
+ "hashMatches": true
+ }
+ ]
+ },
+ {
+ "file": "02-fox-reopened-skin-protection.png",
+ "status": "PASS",
+ "captionPresent": true,
+ "associations": [
+ {
+ "testTitle": "狐狸:真实细分权限、全部属性、清空绑定、蒙皮保护、相机与封面保存重开",
+ "status": "PASS",
+ "hashMatches": true
+ }
+ ]
+ },
+ {
+ "file": "03-truck-part-history-reopened.png",
+ "status": "PASS",
+ "captionPresent": true,
+ "associations": [
+ {
+ "testTitle": "牛奶卡车:刚性部件独立变换、软删除、历史与保存基线一致",
+ "status": "PASS",
+ "hashMatches": true
+ }
+ ]
+ },
+ {
+ "file": "04-new-failure-original-preserved.png",
+ "status": "PASS",
+ "captionPresent": true,
+ "associations": [
+ {
+ "testTitle": "新建独立场景:取消、失败、保护原工程以及成功新 ID",
+ "status": "PASS",
+ "hashMatches": true
+ }
+ ]
+ },
+ {
+ "file": "05-new-separate-scene.png",
+ "status": "PASS",
+ "captionPresent": true,
+ "associations": [
+ {
+ "testTitle": "新建独立场景:取消、失败、保护原工程以及成功新 ID",
+ "status": "PASS",
+ "hashMatches": true
+ }
+ ]
+ },
+ {
+ "file": "06-readonly-scene-permissions.png",
+ "status": "PASS",
+ "captionPresent": true,
+ "associations": [
+ {
+ "testTitle": "只读管理员:可浏览预览与导出,模板/资源/键盘/接口不能改场景",
+ "status": "PASS",
+ "hashMatches": true
+ }
+ ]
+ },
+ {
+ "file": "07-garage-full.png",
+ "status": "PASS",
+ "captionPresent": true,
+ "associations": [
+ {
+ "testTitle": "牛奶卡车:刚性部件独立变换、软删除、历史与保存基线一致",
+ "status": "PASS",
+ "hashMatches": true
+ }
+ ]
+ },
+ {
+ "file": "07-garage-hidden.png",
+ "status": "PASS",
+ "captionPresent": true,
+ "associations": [
+ {
+ "testTitle": "牛奶卡车:刚性部件独立变换、软删除、历史与保存基线一致",
+ "status": "PASS",
+ "hashMatches": true
+ }
+ ]
+ },
+ {
+ "file": "07-garage-xray.png",
+ "status": "PASS",
+ "captionPresent": true,
+ "associations": [
+ {
+ "testTitle": "牛奶卡车:刚性部件独立变换、软删除、历史与保存基线一致",
+ "status": "PASS",
+ "hashMatches": true
+ }
+ ]
+ },
+ {
+ "file": "10-invalid-reference-rolled-back.png",
+ "status": "PASS",
+ "captionPresent": true,
+ "associations": [
+ {
+ "testTitle": "工程 JSON 与小 GLB:真实上传、损坏拒绝、引用失败回滚、相机参数往返",
+ "status": "PASS",
+ "hashMatches": true
+ }
+ ]
+ },
+ {
+ "file": "11-small-glb-real-upload.png",
+ "status": "PASS",
+ "captionPresent": true,
+ "associations": [
+ {
+ "testTitle": "工程 JSON 与小 GLB:真实上传、损坏拒绝、引用失败回滚、相机参数往返",
+ "status": "PASS",
+ "hashMatches": true
+ }
+ ]
+ },
+ {
+ "file": "12-json-glb-reopened.png",
+ "status": "PASS",
+ "captionPresent": true,
+ "associations": [
+ {
+ "testTitle": "工程 JSON 与小 GLB:真实上传、损坏拒绝、引用失败回滚、相机参数往返",
+ "status": "PASS",
+ "hashMatches": true
+ }
+ ]
+ },
+ {
+ "file": "13-save-failure-keeps-draft.png",
+ "status": "PASS",
+ "captionPresent": true,
+ "associations": [
+ {
+ "testTitle": "保存失败与封面降级:503 保留草稿,封面上传失败保留旧图,恢复后可重试",
+ "status": "PASS",
+ "hashMatches": true
+ }
+ ]
+ },
+ {
+ "file": "14-cover-failure-keeps-old-image.png",
+ "status": "PASS",
+ "captionPresent": true,
+ "associations": [
+ {
+ "testTitle": "保存失败与封面降级:503 保留草稿,封面上传失败保留旧图,恢复后可重试",
+ "status": "PASS",
+ "hashMatches": true
+ }
+ ]
+ },
+ {
+ "file": "15-save-retry-success.png",
+ "status": "PASS",
+ "captionPresent": true,
+ "associations": [
+ {
+ "testTitle": "保存失败与封面降级:503 保留草稿,封面上传失败保留旧图,恢复后可重试",
+ "status": "PASS",
+ "hashMatches": true
+ }
+ ]
+ },
+ {
+ "file": "16-real-unsaved-recovery.png",
+ "status": "PASS",
+ "captionPresent": true,
+ "associations": [
+ {
+ "testTitle": "同版本恢复与 409:真实未保存草稿重载、服务器新版本保护与隔离下载",
+ "status": "PASS",
+ "hashMatches": true
+ }
+ ]
+ },
+ {
+ "file": "17-conflict-does-not-overwrite.png",
+ "status": "PASS",
+ "captionPresent": true,
+ "associations": [
+ {
+ "testTitle": "同版本恢复与 409:真实未保存草稿重载、服务器新版本保护与隔离下载",
+ "status": "PASS",
+ "hashMatches": true
+ }
+ ]
+ },
+ {
+ "file": "18-stale-recovery-explicit-decision.png",
+ "status": "PASS",
+ "captionPresent": true,
+ "associations": [
+ {
+ "testTitle": "同版本恢复与 409:真实未保存草稿重载、服务器新版本保护与隔离下载",
+ "status": "PASS",
+ "hashMatches": true
+ }
+ ]
+ },
+ {
+ "file": "19-publish-failure-stays-draft.png",
+ "status": "PASS",
+ "captionPresent": true,
+ "associations": [
+ {
+ "testTitle": "发布:前端绑定校验、保存与发布失败阻断、真实固定版本供训练目录使用",
+ "status": "PASS",
+ "hashMatches": true
+ }
+ ]
+ },
+ {
+ "file": "20-scene-published-fixed-version.png",
+ "status": "PASS",
+ "captionPresent": true,
+ "associations": [
+ {
+ "testTitle": "发布:前端绑定校验、保存与发布失败阻断、真实固定版本供训练目录使用",
+ "status": "PASS",
+ "hashMatches": true
+ }
+ ]
+ },
+ {
+ "file": "20-template-depot-1.png",
+ "status": "PASS",
+ "captionPresent": true,
+ "associations": [
+ {
+ "testTitle": "场景模板实际加载、保存重开:真实装备车库",
+ "status": "PASS",
+ "hashMatches": true
+ }
+ ]
+ },
+ {
+ "file": "21-published-scene-cover-in-list.png",
+ "status": "PASS",
+ "captionPresent": true,
+ "associations": [
+ {
+ "testTitle": "发布:前端绑定校验、保存与发布失败阻断、真实固定版本供训练目录使用",
+ "status": "PASS",
+ "hashMatches": true
+ }
+ ]
+ },
+ {
+ "file": "21-template-terrain-1.png",
+ "status": "PASS",
+ "captionPresent": true,
+ "associations": [
+ {
+ "testTitle": "场景模板实际加载、保存重开:真实野外训练场",
+ "status": "PASS",
+ "hashMatches": true
+ }
+ ]
+ },
+ {
+ "file": "22-template-workshop-1.png",
+ "status": "PASS",
+ "captionPresent": true,
+ "associations": [
+ {
+ "testTitle": "场景模板实际加载、保存重开:综合检验车间",
+ "status": "PASS",
+ "hashMatches": true
+ }
+ ]
+ },
+ {
+ "file": "23-template-workshop-real.png",
+ "status": "PASS",
+ "captionPresent": true,
+ "associations": [
+ {
+ "testTitle": "场景模板实际加载、保存重开:演示维修车间(可拆分)",
+ "status": "PASS",
+ "hashMatches": true
+ }
+ ]
+ },
+ {
+ "file": "24-template-workshop-2.png",
+ "status": "PASS",
+ "captionPresent": true,
+ "associations": [
+ {
+ "testTitle": "场景模板实际加载、保存重开:总成拆卸车间",
+ "status": "PASS",
+ "hashMatches": true
+ }
+ ]
+ },
+ {
+ "file": "25-template-workshop-3.png",
+ "status": "PASS",
+ "captionPresent": true,
+ "associations": [
+ {
+ "testTitle": "场景模板实际加载、保存重开:部件分解车间",
+ "status": "PASS",
+ "hashMatches": true
+ }
+ ]
+ },
+ {
+ "file": "26-template-workshop-4.png",
+ "status": "PASS",
+ "captionPresent": true,
+ "associations": [
+ {
+ "testTitle": "场景模板实际加载、保存重开:清洗检测车间",
+ "status": "PASS",
+ "hashMatches": true
+ }
+ ]
+ },
+ {
+ "file": "27-template-workshop-5.png",
+ "status": "PASS",
+ "captionPresent": true,
+ "associations": [
+ {
+ "testTitle": "场景模板实际加载、保存重开:复装调试车间",
+ "status": "PASS",
+ "hashMatches": true
+ }
+ ]
+ },
+ {
+ "file": "28-template-outdoor-1.png",
+ "status": "PASS",
+ "captionPresent": true,
+ "associations": [
+ {
+ "testTitle": "场景模板实际加载、保存重开:室外综合训练场",
+ "status": "PASS",
+ "hashMatches": true
+ }
+ ]
+ },
+ {
+ "file": "29-template-confirm-empty.png",
+ "status": "PASS",
+ "captionPresent": true,
+ "associations": [
+ {
+ "testTitle": "模板搜索与未保存切换确认保留和放弃",
+ "status": "PASS",
+ "hashMatches": true
+ }
+ ]
+ },
+ {
+ "file": "30-eight-basic-assets-reopened.png",
+ "status": "PASS",
+ "captionPresent": true,
+ "associations": [
+ {
+ "testTitle": "八种基础资源逐项放置、真实拖放、搜索和保存重开",
+ "status": "PASS",
+ "hashMatches": true
+ }
+ ]
+ },
+ {
+ "file": "31-venue-depot-reopened.png",
+ "status": "PASS",
+ "captionPresent": true,
+ "associations": [
+ {
+ "testTitle": "两种整体场地加载、重复放置阻止与显式删除后替换",
+ "status": "PASS",
+ "hashMatches": true
+ }
+ ]
+ },
+ {
+ "file": "32-venue-terrain-reopened.png",
+ "status": "PASS",
+ "captionPresent": true,
+ "associations": [
+ {
+ "testTitle": "两种整体场地加载、重复放置阻止与显式删除后替换",
+ "status": "PASS",
+ "hashMatches": true
+ }
+ ]
+ },
+ {
+ "file": "33-fox-placed-visible.png",
+ "status": "PASS",
+ "captionPresent": true,
+ "associations": [
+ {
+ "testTitle": "模型成果狐狸卡车双击与真实拖入、树搜索和部件隐藏重开",
+ "status": "PASS",
+ "hashMatches": true
+ }
+ ]
+ },
+ {
+ "file": "34-truck-placed-part-restored.png",
+ "status": "PASS",
+ "captionPresent": true,
+ "associations": [
+ {
+ "testTitle": "模型成果狐狸卡车双击与真实拖入、树搜索和部件隐藏重开",
+ "status": "PASS",
+ "hashMatches": true
+ }
+ ]
+ },
+ {
+ "file": "40-viewport-dark-1920.png",
+ "status": "PASS",
+ "captionPresent": true,
+ "associations": [
+ {
+ "testTitle": "真实相机操控、视口拾取、预览禁写和两尺寸主题可读性",
+ "status": "PASS",
+ "hashMatches": true
+ }
+ ]
+ },
+ {
+ "file": "41-viewport-light-1440.png",
+ "status": "PASS",
+ "captionPresent": true,
+ "associations": [
+ {
+ "testTitle": "真实相机操控、视口拾取、预览禁写和两尺寸主题可读性",
+ "status": "PASS",
+ "hashMatches": true
+ }
+ ]
+ },
+ {
+ "file": "42-fox-preview-original-material.png",
+ "status": "PASS",
+ "captionPresent": true,
+ "associations": [
+ {
+ "testTitle": "真实相机操控、视口拾取、预览禁写和两尺寸主题可读性",
+ "status": "PASS",
+ "hashMatches": true
+ }
+ ]
+ },
+ {
+ "file": "43-real-transform-gizmo.png",
+ "status": "PASS",
+ "captionPresent": true,
+ "associations": [
+ {
+ "testTitle": "真实变换手柄移动旋转缩放、吸附与保存重开",
+ "status": "PASS",
+ "hashMatches": true
+ }
+ ]
+ },
+ {
+ "file": "44-cache-close-save-failure.png",
+ "status": "PASS",
+ "captionPresent": true,
+ "associations": [
+ {
+ "testTitle": "缓存标签暂停键盘、草稿恢复以及关闭取消和保存失败留页",
+ "status": "PASS",
+ "hashMatches": true
+ }
+ ]
+ },
+ {
+ "file": "51-scene-update-only-saved.png",
+ "status": "PASS",
+ "captionPresent": true,
+ "associations": [
+ {
+ "testTitle": "仅场景编辑权限:固定模型可加载和保存,不能发布或访问模型页面",
+ "status": "PASS",
+ "hashMatches": true
+ }
+ ]
+ },
+ {
+ "file": "52-scene-publish-only-no-save.png",
+ "status": "PASS",
+ "captionPresent": true,
+ "associations": [
+ {
+ "testTitle": "仅场景发布权限:浏览器发布已存副本,不发送保存或上传请求",
+ "status": "PASS",
+ "hashMatches": true
+ }
+ ]
+ },
+ {
+ "file": "53-scene-route-access-denied.png",
+ "status": "PASS",
+ "captionPresent": true,
+ "associations": [
+ {
+ "testTitle": "无场景页面权限:编辑路由和直接内容接口均拒绝访问",
+ "status": "PASS",
+ "hashMatches": true
+ }
+ ]
+ },
+ {
+ "file": "54-scene-role-switch-cancel-keeps-draft.png",
+ "status": "PASS",
+ "captionPresent": true,
+ "associations": [
+ {
+ "testTitle": "后台场景未保存时切换角色:关闭确认保留草稿,保存切换后重建只读权限",
+ "status": "PASS",
+ "hashMatches": true
+ }
+ ]
+ },
+ {
+ "file": "55-scene-role-switch-saved-readonly.png",
+ "status": "PASS",
+ "captionPresent": true,
+ "associations": [
+ {
+ "testTitle": "后台场景未保存时切换角色:关闭确认保留草稿,保存切换后重建只读权限",
+ "status": "PASS",
+ "hashMatches": true
+ }
+ ]
+ },
+ {
+ "file": "56-scene-other-tab-recovery-preserved.png",
+ "status": "PASS",
+ "captionPresent": true,
+ "associations": [
+ {
+ "testTitle": "双标签各自修改场景:保存和明确丢弃当前页均保留另一页真实恢复稿",
+ "status": "PASS",
+ "hashMatches": true
+ }
+ ]
+ },
+ {
+ "file": "60-scene-load-failure-retry.png",
+ "status": "PASS",
+ "captionPresent": true,
+ "associations": [
+ {
+ "testTitle": "加载失败重试与加载互斥:不展示可写半场景、恢复网络后模型完整加载",
+ "status": "PASS",
+ "hashMatches": true
+ }
+ ]
+ },
+ {
+ "file": "61-model-loading-blocks-partial-save.png",
+ "status": "PASS",
+ "captionPresent": true,
+ "associations": [
+ {
+ "testTitle": "加载失败重试与加载互斥:不展示可写半场景、恢复网络后模型完整加载",
+ "status": "PASS",
+ "hashMatches": true
+ }
+ ]
+ },
+ {
+ "file": "62-new-edits-preserved-during-save.png",
+ "status": "PASS",
+ "captionPresent": true,
+ "associations": [
+ {
+ "testTitle": "延迟保存快照:重复点击只提交一次,期间新编辑保留,旧恢复稿不会误报冲突",
+ "status": "PASS",
+ "hashMatches": true
+ }
+ ]
+ },
+ {
+ "file": "70-fox-training-scene-reopened.png",
+ "status": "PASS",
+ "captionPresent": true,
+ "associations": [
+ {
+ "testTitle": "fox:场景发布后在真实训练编排选择、保存并重开静态模型",
+ "status": "PASS",
+ "hashMatches": true
+ }
+ ]
+ },
+ {
+ "file": "70-truck-training-scene-reopened.png",
+ "status": "PASS",
+ "captionPresent": true,
+ "associations": [
+ {
+ "testTitle": "truck:场景发布后在真实训练编排选择、保存并重开静态模型",
+ "status": "PASS",
+ "hashMatches": true
+ }
+ ]
+ }
+]
\ No newline at end of file
diff --git a/reports/scene-editor-closure-20260906/screenshots/01-fox-actual-scene.png b/reports/scene-editor-closure-20260906/screenshots/01-fox-actual-scene.png
new file mode 100644
index 0000000..806eb21
Binary files /dev/null and b/reports/scene-editor-closure-20260906/screenshots/01-fox-actual-scene.png differ
diff --git a/reports/scene-editor-closure-20260906/screenshots/02-fox-reopened-skin-protection.png b/reports/scene-editor-closure-20260906/screenshots/02-fox-reopened-skin-protection.png
new file mode 100644
index 0000000..4e9458b
Binary files /dev/null and b/reports/scene-editor-closure-20260906/screenshots/02-fox-reopened-skin-protection.png differ
diff --git a/reports/scene-editor-closure-20260906/screenshots/03-truck-part-history-reopened.png b/reports/scene-editor-closure-20260906/screenshots/03-truck-part-history-reopened.png
new file mode 100644
index 0000000..f5a1f51
Binary files /dev/null and b/reports/scene-editor-closure-20260906/screenshots/03-truck-part-history-reopened.png differ
diff --git a/reports/scene-editor-closure-20260906/screenshots/04-new-failure-original-preserved.png b/reports/scene-editor-closure-20260906/screenshots/04-new-failure-original-preserved.png
new file mode 100644
index 0000000..38e4fae
Binary files /dev/null and b/reports/scene-editor-closure-20260906/screenshots/04-new-failure-original-preserved.png differ
diff --git a/reports/scene-editor-closure-20260906/screenshots/05-new-separate-scene.png b/reports/scene-editor-closure-20260906/screenshots/05-new-separate-scene.png
new file mode 100644
index 0000000..60b02da
Binary files /dev/null and b/reports/scene-editor-closure-20260906/screenshots/05-new-separate-scene.png differ
diff --git a/reports/scene-editor-closure-20260906/screenshots/06-readonly-scene-permissions.png b/reports/scene-editor-closure-20260906/screenshots/06-readonly-scene-permissions.png
new file mode 100644
index 0000000..6290d0e
Binary files /dev/null and b/reports/scene-editor-closure-20260906/screenshots/06-readonly-scene-permissions.png differ
diff --git a/reports/scene-editor-closure-20260906/screenshots/07-garage-full.png b/reports/scene-editor-closure-20260906/screenshots/07-garage-full.png
new file mode 100644
index 0000000..d708c06
Binary files /dev/null and b/reports/scene-editor-closure-20260906/screenshots/07-garage-full.png differ
diff --git a/reports/scene-editor-closure-20260906/screenshots/07-garage-hidden.png b/reports/scene-editor-closure-20260906/screenshots/07-garage-hidden.png
new file mode 100644
index 0000000..3ac91c9
Binary files /dev/null and b/reports/scene-editor-closure-20260906/screenshots/07-garage-hidden.png differ
diff --git a/reports/scene-editor-closure-20260906/screenshots/07-garage-xray.png b/reports/scene-editor-closure-20260906/screenshots/07-garage-xray.png
new file mode 100644
index 0000000..8f57499
Binary files /dev/null and b/reports/scene-editor-closure-20260906/screenshots/07-garage-xray.png differ
diff --git a/reports/scene-editor-closure-20260906/screenshots/10-invalid-reference-rolled-back.png b/reports/scene-editor-closure-20260906/screenshots/10-invalid-reference-rolled-back.png
new file mode 100644
index 0000000..f428540
Binary files /dev/null and b/reports/scene-editor-closure-20260906/screenshots/10-invalid-reference-rolled-back.png differ
diff --git a/reports/scene-editor-closure-20260906/screenshots/11-small-glb-real-upload.png b/reports/scene-editor-closure-20260906/screenshots/11-small-glb-real-upload.png
new file mode 100644
index 0000000..3c31983
Binary files /dev/null and b/reports/scene-editor-closure-20260906/screenshots/11-small-glb-real-upload.png differ
diff --git a/reports/scene-editor-closure-20260906/screenshots/12-json-glb-reopened.png b/reports/scene-editor-closure-20260906/screenshots/12-json-glb-reopened.png
new file mode 100644
index 0000000..b3ca427
Binary files /dev/null and b/reports/scene-editor-closure-20260906/screenshots/12-json-glb-reopened.png differ
diff --git a/reports/scene-editor-closure-20260906/screenshots/13-save-failure-keeps-draft.png b/reports/scene-editor-closure-20260906/screenshots/13-save-failure-keeps-draft.png
new file mode 100644
index 0000000..071b135
Binary files /dev/null and b/reports/scene-editor-closure-20260906/screenshots/13-save-failure-keeps-draft.png differ
diff --git a/reports/scene-editor-closure-20260906/screenshots/14-cover-failure-keeps-old-image.png b/reports/scene-editor-closure-20260906/screenshots/14-cover-failure-keeps-old-image.png
new file mode 100644
index 0000000..f4984c1
Binary files /dev/null and b/reports/scene-editor-closure-20260906/screenshots/14-cover-failure-keeps-old-image.png differ
diff --git a/reports/scene-editor-closure-20260906/screenshots/15-save-retry-success.png b/reports/scene-editor-closure-20260906/screenshots/15-save-retry-success.png
new file mode 100644
index 0000000..b7a1449
Binary files /dev/null and b/reports/scene-editor-closure-20260906/screenshots/15-save-retry-success.png differ
diff --git a/reports/scene-editor-closure-20260906/screenshots/16-real-unsaved-recovery.png b/reports/scene-editor-closure-20260906/screenshots/16-real-unsaved-recovery.png
new file mode 100644
index 0000000..9b91ff9
Binary files /dev/null and b/reports/scene-editor-closure-20260906/screenshots/16-real-unsaved-recovery.png differ
diff --git a/reports/scene-editor-closure-20260906/screenshots/17-conflict-does-not-overwrite.png b/reports/scene-editor-closure-20260906/screenshots/17-conflict-does-not-overwrite.png
new file mode 100644
index 0000000..a7f9fa6
Binary files /dev/null and b/reports/scene-editor-closure-20260906/screenshots/17-conflict-does-not-overwrite.png differ
diff --git a/reports/scene-editor-closure-20260906/screenshots/18-stale-recovery-explicit-decision.png b/reports/scene-editor-closure-20260906/screenshots/18-stale-recovery-explicit-decision.png
new file mode 100644
index 0000000..34b8628
Binary files /dev/null and b/reports/scene-editor-closure-20260906/screenshots/18-stale-recovery-explicit-decision.png differ
diff --git a/reports/scene-editor-closure-20260906/screenshots/19-publish-failure-stays-draft.png b/reports/scene-editor-closure-20260906/screenshots/19-publish-failure-stays-draft.png
new file mode 100644
index 0000000..d264faf
Binary files /dev/null and b/reports/scene-editor-closure-20260906/screenshots/19-publish-failure-stays-draft.png differ
diff --git a/reports/scene-editor-closure-20260906/screenshots/20-scene-published-fixed-version.png b/reports/scene-editor-closure-20260906/screenshots/20-scene-published-fixed-version.png
new file mode 100644
index 0000000..6ba8f56
Binary files /dev/null and b/reports/scene-editor-closure-20260906/screenshots/20-scene-published-fixed-version.png differ
diff --git a/reports/scene-editor-closure-20260906/screenshots/20-template-depot-1.png b/reports/scene-editor-closure-20260906/screenshots/20-template-depot-1.png
new file mode 100644
index 0000000..650dbde
Binary files /dev/null and b/reports/scene-editor-closure-20260906/screenshots/20-template-depot-1.png differ
diff --git a/reports/scene-editor-closure-20260906/screenshots/21-published-scene-cover-in-list.png b/reports/scene-editor-closure-20260906/screenshots/21-published-scene-cover-in-list.png
new file mode 100644
index 0000000..23e7cce
Binary files /dev/null and b/reports/scene-editor-closure-20260906/screenshots/21-published-scene-cover-in-list.png differ
diff --git a/reports/scene-editor-closure-20260906/screenshots/21-template-terrain-1.png b/reports/scene-editor-closure-20260906/screenshots/21-template-terrain-1.png
new file mode 100644
index 0000000..d23c79d
Binary files /dev/null and b/reports/scene-editor-closure-20260906/screenshots/21-template-terrain-1.png differ
diff --git a/reports/scene-editor-closure-20260906/screenshots/22-template-workshop-1.png b/reports/scene-editor-closure-20260906/screenshots/22-template-workshop-1.png
new file mode 100644
index 0000000..adf8ab8
Binary files /dev/null and b/reports/scene-editor-closure-20260906/screenshots/22-template-workshop-1.png differ
diff --git a/reports/scene-editor-closure-20260906/screenshots/23-template-workshop-real.png b/reports/scene-editor-closure-20260906/screenshots/23-template-workshop-real.png
new file mode 100644
index 0000000..9aeda0a
Binary files /dev/null and b/reports/scene-editor-closure-20260906/screenshots/23-template-workshop-real.png differ
diff --git a/reports/scene-editor-closure-20260906/screenshots/24-template-workshop-2.png b/reports/scene-editor-closure-20260906/screenshots/24-template-workshop-2.png
new file mode 100644
index 0000000..ed86ee3
Binary files /dev/null and b/reports/scene-editor-closure-20260906/screenshots/24-template-workshop-2.png differ
diff --git a/reports/scene-editor-closure-20260906/screenshots/25-template-workshop-3.png b/reports/scene-editor-closure-20260906/screenshots/25-template-workshop-3.png
new file mode 100644
index 0000000..4939000
Binary files /dev/null and b/reports/scene-editor-closure-20260906/screenshots/25-template-workshop-3.png differ
diff --git a/reports/scene-editor-closure-20260906/screenshots/26-template-workshop-4.png b/reports/scene-editor-closure-20260906/screenshots/26-template-workshop-4.png
new file mode 100644
index 0000000..661d0f2
Binary files /dev/null and b/reports/scene-editor-closure-20260906/screenshots/26-template-workshop-4.png differ
diff --git a/reports/scene-editor-closure-20260906/screenshots/27-template-workshop-5.png b/reports/scene-editor-closure-20260906/screenshots/27-template-workshop-5.png
new file mode 100644
index 0000000..c6ef4b0
Binary files /dev/null and b/reports/scene-editor-closure-20260906/screenshots/27-template-workshop-5.png differ
diff --git a/reports/scene-editor-closure-20260906/screenshots/28-template-outdoor-1.png b/reports/scene-editor-closure-20260906/screenshots/28-template-outdoor-1.png
new file mode 100644
index 0000000..03cdd3d
Binary files /dev/null and b/reports/scene-editor-closure-20260906/screenshots/28-template-outdoor-1.png differ
diff --git a/reports/scene-editor-closure-20260906/screenshots/29-template-confirm-empty.png b/reports/scene-editor-closure-20260906/screenshots/29-template-confirm-empty.png
new file mode 100644
index 0000000..d288cc9
Binary files /dev/null and b/reports/scene-editor-closure-20260906/screenshots/29-template-confirm-empty.png differ
diff --git a/reports/scene-editor-closure-20260906/screenshots/30-eight-basic-assets-reopened.png b/reports/scene-editor-closure-20260906/screenshots/30-eight-basic-assets-reopened.png
new file mode 100644
index 0000000..dfe3110
Binary files /dev/null and b/reports/scene-editor-closure-20260906/screenshots/30-eight-basic-assets-reopened.png differ
diff --git a/reports/scene-editor-closure-20260906/screenshots/31-venue-depot-reopened.png b/reports/scene-editor-closure-20260906/screenshots/31-venue-depot-reopened.png
new file mode 100644
index 0000000..4566b4d
Binary files /dev/null and b/reports/scene-editor-closure-20260906/screenshots/31-venue-depot-reopened.png differ
diff --git a/reports/scene-editor-closure-20260906/screenshots/32-venue-terrain-reopened.png b/reports/scene-editor-closure-20260906/screenshots/32-venue-terrain-reopened.png
new file mode 100644
index 0000000..4993345
Binary files /dev/null and b/reports/scene-editor-closure-20260906/screenshots/32-venue-terrain-reopened.png differ
diff --git a/reports/scene-editor-closure-20260906/screenshots/33-fox-placed-visible.png b/reports/scene-editor-closure-20260906/screenshots/33-fox-placed-visible.png
new file mode 100644
index 0000000..3d3f751
Binary files /dev/null and b/reports/scene-editor-closure-20260906/screenshots/33-fox-placed-visible.png differ
diff --git a/reports/scene-editor-closure-20260906/screenshots/34-truck-placed-part-restored.png b/reports/scene-editor-closure-20260906/screenshots/34-truck-placed-part-restored.png
new file mode 100644
index 0000000..f1b3f52
Binary files /dev/null and b/reports/scene-editor-closure-20260906/screenshots/34-truck-placed-part-restored.png differ
diff --git a/reports/scene-editor-closure-20260906/screenshots/40-viewport-dark-1920.png b/reports/scene-editor-closure-20260906/screenshots/40-viewport-dark-1920.png
new file mode 100644
index 0000000..d95445d
Binary files /dev/null and b/reports/scene-editor-closure-20260906/screenshots/40-viewport-dark-1920.png differ
diff --git a/reports/scene-editor-closure-20260906/screenshots/41-viewport-light-1440.png b/reports/scene-editor-closure-20260906/screenshots/41-viewport-light-1440.png
new file mode 100644
index 0000000..608e56a
Binary files /dev/null and b/reports/scene-editor-closure-20260906/screenshots/41-viewport-light-1440.png differ
diff --git a/reports/scene-editor-closure-20260906/screenshots/42-fox-preview-original-material.png b/reports/scene-editor-closure-20260906/screenshots/42-fox-preview-original-material.png
new file mode 100644
index 0000000..ecce71c
Binary files /dev/null and b/reports/scene-editor-closure-20260906/screenshots/42-fox-preview-original-material.png differ
diff --git a/reports/scene-editor-closure-20260906/screenshots/43-real-transform-gizmo.png b/reports/scene-editor-closure-20260906/screenshots/43-real-transform-gizmo.png
new file mode 100644
index 0000000..bf3ca8f
Binary files /dev/null and b/reports/scene-editor-closure-20260906/screenshots/43-real-transform-gizmo.png differ
diff --git a/reports/scene-editor-closure-20260906/screenshots/44-cache-close-save-failure.png b/reports/scene-editor-closure-20260906/screenshots/44-cache-close-save-failure.png
new file mode 100644
index 0000000..8a818d1
Binary files /dev/null and b/reports/scene-editor-closure-20260906/screenshots/44-cache-close-save-failure.png differ
diff --git a/reports/scene-editor-closure-20260906/screenshots/51-scene-update-only-saved.png b/reports/scene-editor-closure-20260906/screenshots/51-scene-update-only-saved.png
new file mode 100644
index 0000000..18abe74
Binary files /dev/null and b/reports/scene-editor-closure-20260906/screenshots/51-scene-update-only-saved.png differ
diff --git a/reports/scene-editor-closure-20260906/screenshots/52-scene-publish-only-no-save.png b/reports/scene-editor-closure-20260906/screenshots/52-scene-publish-only-no-save.png
new file mode 100644
index 0000000..31a1a38
Binary files /dev/null and b/reports/scene-editor-closure-20260906/screenshots/52-scene-publish-only-no-save.png differ
diff --git a/reports/scene-editor-closure-20260906/screenshots/53-scene-route-access-denied.png b/reports/scene-editor-closure-20260906/screenshots/53-scene-route-access-denied.png
new file mode 100644
index 0000000..57d18b5
Binary files /dev/null and b/reports/scene-editor-closure-20260906/screenshots/53-scene-route-access-denied.png differ
diff --git a/reports/scene-editor-closure-20260906/screenshots/54-scene-role-switch-cancel-keeps-draft.png b/reports/scene-editor-closure-20260906/screenshots/54-scene-role-switch-cancel-keeps-draft.png
new file mode 100644
index 0000000..33029aa
Binary files /dev/null and b/reports/scene-editor-closure-20260906/screenshots/54-scene-role-switch-cancel-keeps-draft.png differ
diff --git a/reports/scene-editor-closure-20260906/screenshots/55-scene-role-switch-saved-readonly.png b/reports/scene-editor-closure-20260906/screenshots/55-scene-role-switch-saved-readonly.png
new file mode 100644
index 0000000..8b6a48d
Binary files /dev/null and b/reports/scene-editor-closure-20260906/screenshots/55-scene-role-switch-saved-readonly.png differ
diff --git a/reports/scene-editor-closure-20260906/screenshots/56-scene-other-tab-recovery-preserved.png b/reports/scene-editor-closure-20260906/screenshots/56-scene-other-tab-recovery-preserved.png
new file mode 100644
index 0000000..65d99d9
Binary files /dev/null and b/reports/scene-editor-closure-20260906/screenshots/56-scene-other-tab-recovery-preserved.png differ
diff --git a/reports/scene-editor-closure-20260906/screenshots/60-scene-load-failure-retry.png b/reports/scene-editor-closure-20260906/screenshots/60-scene-load-failure-retry.png
new file mode 100644
index 0000000..732e95f
Binary files /dev/null and b/reports/scene-editor-closure-20260906/screenshots/60-scene-load-failure-retry.png differ
diff --git a/reports/scene-editor-closure-20260906/screenshots/61-model-loading-blocks-partial-save.png b/reports/scene-editor-closure-20260906/screenshots/61-model-loading-blocks-partial-save.png
new file mode 100644
index 0000000..fd02ed8
Binary files /dev/null and b/reports/scene-editor-closure-20260906/screenshots/61-model-loading-blocks-partial-save.png differ
diff --git a/reports/scene-editor-closure-20260906/screenshots/62-new-edits-preserved-during-save.png b/reports/scene-editor-closure-20260906/screenshots/62-new-edits-preserved-during-save.png
new file mode 100644
index 0000000..e858285
Binary files /dev/null and b/reports/scene-editor-closure-20260906/screenshots/62-new-edits-preserved-during-save.png differ
diff --git a/reports/scene-editor-closure-20260906/screenshots/70-fox-training-scene-reopened.png b/reports/scene-editor-closure-20260906/screenshots/70-fox-training-scene-reopened.png
new file mode 100644
index 0000000..ece741f
Binary files /dev/null and b/reports/scene-editor-closure-20260906/screenshots/70-fox-training-scene-reopened.png differ
diff --git a/reports/scene-editor-closure-20260906/screenshots/70-truck-training-scene-reopened.png b/reports/scene-editor-closure-20260906/screenshots/70-truck-training-scene-reopened.png
new file mode 100644
index 0000000..4bb4a48
Binary files /dev/null and b/reports/scene-editor-closure-20260906/screenshots/70-truck-training-scene-reopened.png differ
diff --git a/reports/scene-editor-closure-20260906/test-results/scene-editor-async-closure-41b99-复点击只提交一次,期间新编辑保留,旧恢复稿不会误报冲突/attachments/62-new-edits-preserved-during-save-5fb848bba12f1b18dcb8ef7a85f10f71eabacbe3.png b/reports/scene-editor-closure-20260906/test-results/scene-editor-async-closure-41b99-复点击只提交一次,期间新编辑保留,旧恢复稿不会误报冲突/attachments/62-new-edits-preserved-during-save-5fb848bba12f1b18dcb8ef7a85f10f71eabacbe3.png
new file mode 100644
index 0000000..e858285
Binary files /dev/null and b/reports/scene-editor-closure-20260906/test-results/scene-editor-async-closure-41b99-复点击只提交一次,期间新编辑保留,旧恢复稿不会误报冲突/attachments/62-new-edits-preserved-during-save-5fb848bba12f1b18dcb8ef7a85f10f71eabacbe3.png differ
diff --git a/reports/scene-editor-closure-20260906/test-results/scene-editor-async-closure-加载失败重试与加载互斥:不展示可写半场景、恢复网络后模型完整加载/attachments/60-scene-load-failure-retry-bcafe64eeb8ce6f7d80edb15916770893d2b09a0.png b/reports/scene-editor-closure-20260906/test-results/scene-editor-async-closure-加载失败重试与加载互斥:不展示可写半场景、恢复网络后模型完整加载/attachments/60-scene-load-failure-retry-bcafe64eeb8ce6f7d80edb15916770893d2b09a0.png
new file mode 100644
index 0000000..732e95f
Binary files /dev/null and b/reports/scene-editor-closure-20260906/test-results/scene-editor-async-closure-加载失败重试与加载互斥:不展示可写半场景、恢复网络后模型完整加载/attachments/60-scene-load-failure-retry-bcafe64eeb8ce6f7d80edb15916770893d2b09a0.png differ
diff --git a/reports/scene-editor-closure-20260906/test-results/scene-editor-async-closure-加载失败重试与加载互斥:不展示可写半场景、恢复网络后模型完整加载/attachments/61-model-loading-blocks-partial-save-1722bedde84490e939186cce6826d9a159dc093e.png b/reports/scene-editor-closure-20260906/test-results/scene-editor-async-closure-加载失败重试与加载互斥:不展示可写半场景、恢复网络后模型完整加载/attachments/61-model-loading-blocks-partial-save-1722bedde84490e939186cce6826d9a159dc093e.png
new file mode 100644
index 0000000..fd02ed8
Binary files /dev/null and b/reports/scene-editor-closure-20260906/test-results/scene-editor-async-closure-加载失败重试与加载互斥:不展示可写半场景、恢复网络后模型完整加载/attachments/61-model-loading-blocks-partial-save-1722bedde84490e939186cce6826d9a159dc093e.png differ
diff --git a/reports/scene-editor-closure-20260906/test-results/scene-editor-downstream-cl-ee237-ck:场景发布后在真实训练编排选择、保存并重开静态模型/attachments/70-truck-training-scene-reopened-c071233858019110dc3102c5a59268c8098cfa59.png b/reports/scene-editor-closure-20260906/test-results/scene-editor-downstream-cl-ee237-ck:场景发布后在真实训练编排选择、保存并重开静态模型/attachments/70-truck-training-scene-reopened-c071233858019110dc3102c5a59268c8098cfa59.png
new file mode 100644
index 0000000..4bb4a48
Binary files /dev/null and b/reports/scene-editor-closure-20260906/test-results/scene-editor-downstream-cl-ee237-ck:场景发布后在真实训练编排选择、保存并重开静态模型/attachments/70-truck-training-scene-reopened-c071233858019110dc3102c5a59268c8098cfa59.png differ
diff --git a/reports/scene-editor-closure-20260906/test-results/scene-editor-downstream-closure-fox:场景发布后在真实训练编排选择、保存并重开静态模型/attachments/70-fox-training-scene-reopened-1639ae91c8ff70bde8b0be5a0d015d028d44e6de.png b/reports/scene-editor-closure-20260906/test-results/scene-editor-downstream-closure-fox:场景发布后在真实训练编排选择、保存并重开静态模型/attachments/70-fox-training-scene-reopened-1639ae91c8ff70bde8b0be5a0d015d028d44e6de.png
new file mode 100644
index 0000000..ece741f
Binary files /dev/null and b/reports/scene-editor-closure-20260906/test-results/scene-editor-downstream-closure-fox:场景发布后在真实训练编排选择、保存并重开静态模型/attachments/70-fox-training-scene-reopened-1639ae91c8ff70bde8b0be5a0d015d028d44e6de.png differ
diff --git a/reports/scene-editor-closure-20260906/test-results/scene-editor-permissions-c-4c783-存时切换角色:关闭确认保留草稿,保存切换后重建只读权限/attachments/54-scene-role-switch-cancel-keeps-draft-14e2bc4f9d547e122b4ae74afcee0fcae168b10e.png b/reports/scene-editor-closure-20260906/test-results/scene-editor-permissions-c-4c783-存时切换角色:关闭确认保留草稿,保存切换后重建只读权限/attachments/54-scene-role-switch-cancel-keeps-draft-14e2bc4f9d547e122b4ae74afcee0fcae168b10e.png
new file mode 100644
index 0000000..33029aa
Binary files /dev/null and b/reports/scene-editor-closure-20260906/test-results/scene-editor-permissions-c-4c783-存时切换角色:关闭确认保留草稿,保存切换后重建只读权限/attachments/54-scene-role-switch-cancel-keeps-draft-14e2bc4f9d547e122b4ae74afcee0fcae168b10e.png differ
diff --git a/reports/scene-editor-closure-20260906/test-results/scene-editor-permissions-c-4c783-存时切换角色:关闭确认保留草稿,保存切换后重建只读权限/attachments/55-scene-role-switch-saved-readonly-df4aead6c1e902b5730ec128b01f3f145967a855.png b/reports/scene-editor-closure-20260906/test-results/scene-editor-permissions-c-4c783-存时切换角色:关闭确认保留草稿,保存切换后重建只读权限/attachments/55-scene-role-switch-saved-readonly-df4aead6c1e902b5730ec128b01f3f145967a855.png
new file mode 100644
index 0000000..8b6a48d
Binary files /dev/null and b/reports/scene-editor-closure-20260906/test-results/scene-editor-permissions-c-4c783-存时切换角色:关闭确认保留草稿,保存切换后重建只读权限/attachments/55-scene-role-switch-saved-readonly-df4aead6c1e902b5730ec128b01f3f145967a855.png differ
diff --git a/reports/scene-editor-closure-20260906/test-results/scene-editor-permissions-c-54924-场景发布权限:浏览器发布已存副本,不发送保存或上传请求/attachments/52-scene-publish-only-no-save-28f0d4ba0206564466b1322f8ea078a9f03a1d93.png b/reports/scene-editor-closure-20260906/test-results/scene-editor-permissions-c-54924-场景发布权限:浏览器发布已存副本,不发送保存或上传请求/attachments/52-scene-publish-only-no-save-28f0d4ba0206564466b1322f8ea078a9f03a1d93.png
new file mode 100644
index 0000000..31a1a38
Binary files /dev/null and b/reports/scene-editor-closure-20260906/test-results/scene-editor-permissions-c-54924-场景发布权限:浏览器发布已存副本,不发送保存或上传请求/attachments/52-scene-publish-only-no-save-28f0d4ba0206564466b1322f8ea078a9f03a1d93.png differ
diff --git a/reports/scene-editor-closure-20260906/test-results/scene-editor-permissions-c-868b3-自修改场景:保存和明确丢弃当前页均保留另一页真实恢复稿/attachments/56-scene-other-tab-recovery-preserved-47a3200c2fce92a8e02f7e195382a692a7f48850.png b/reports/scene-editor-closure-20260906/test-results/scene-editor-permissions-c-868b3-自修改场景:保存和明确丢弃当前页均保留另一页真实恢复稿/attachments/56-scene-other-tab-recovery-preserved-47a3200c2fce92a8e02f7e195382a692a7f48850.png
new file mode 100644
index 0000000..65d99d9
Binary files /dev/null and b/reports/scene-editor-closure-20260906/test-results/scene-editor-permissions-c-868b3-自修改场景:保存和明确丢弃当前页均保留另一页真实恢复稿/attachments/56-scene-other-tab-recovery-preserved-47a3200c2fce92a8e02f7e195382a692a7f48850.png differ
diff --git a/reports/scene-editor-closure-20260906/test-results/scene-editor-permissions-c-f3b5c-编辑权限:固定模型可加载和保存,不能发布或访问模型页面/attachments/51-scene-update-only-saved-f7388e35a457ab515e375a3e20e2793b681d4b0d.png b/reports/scene-editor-closure-20260906/test-results/scene-editor-permissions-c-f3b5c-编辑权限:固定模型可加载和保存,不能发布或访问模型页面/attachments/51-scene-update-only-saved-f7388e35a457ab515e375a3e20e2793b681d4b0d.png
new file mode 100644
index 0000000..18abe74
Binary files /dev/null and b/reports/scene-editor-closure-20260906/test-results/scene-editor-permissions-c-f3b5c-编辑权限:固定模型可加载和保存,不能发布或访问模型页面/attachments/51-scene-update-only-saved-f7388e35a457ab515e375a3e20e2793b681d4b0d.png differ
diff --git a/reports/scene-editor-closure-20260906/test-results/scene-editor-permissions-closure-无场景页面权限:编辑路由和直接内容接口均拒绝访问/attachments/53-scene-route-access-denied-194f1508efe48603c64ef6b9ac921315610dcee4.png b/reports/scene-editor-closure-20260906/test-results/scene-editor-permissions-closure-无场景页面权限:编辑路由和直接内容接口均拒绝访问/attachments/53-scene-route-access-denied-194f1508efe48603c64ef6b9ac921315610dcee4.png
new file mode 100644
index 0000000..57d18b5
Binary files /dev/null and b/reports/scene-editor-closure-20260906/test-results/scene-editor-permissions-closure-无场景页面权限:编辑路由和直接内容接口均拒绝访问/attachments/53-scene-route-access-denied-194f1508efe48603c64ef6b9ac921315610dcee4.png differ
diff --git a/reports/scene-editor-closure-20260906/test-results/scene-editor-persistence-c-65cd5-409:真实未保存草稿重载、服务器新版本保护与隔离下载/attachments/16-real-unsaved-recovery-41c248cd04d1f0203ab044bbbbe65bdd4b349c51.png b/reports/scene-editor-closure-20260906/test-results/scene-editor-persistence-c-65cd5-409:真实未保存草稿重载、服务器新版本保护与隔离下载/attachments/16-real-unsaved-recovery-41c248cd04d1f0203ab044bbbbe65bdd4b349c51.png
new file mode 100644
index 0000000..9b91ff9
Binary files /dev/null and b/reports/scene-editor-closure-20260906/test-results/scene-editor-persistence-c-65cd5-409:真实未保存草稿重载、服务器新版本保护与隔离下载/attachments/16-real-unsaved-recovery-41c248cd04d1f0203ab044bbbbe65bdd4b349c51.png differ
diff --git a/reports/scene-editor-closure-20260906/test-results/scene-editor-persistence-c-65cd5-409:真实未保存草稿重载、服务器新版本保护与隔离下载/attachments/17-conflict-does-not-overwrite-2a666b3204c597f2999ec44b69184ba1c9ad43f6.png b/reports/scene-editor-closure-20260906/test-results/scene-editor-persistence-c-65cd5-409:真实未保存草稿重载、服务器新版本保护与隔离下载/attachments/17-conflict-does-not-overwrite-2a666b3204c597f2999ec44b69184ba1c9ad43f6.png
new file mode 100644
index 0000000..a7f9fa6
Binary files /dev/null and b/reports/scene-editor-closure-20260906/test-results/scene-editor-persistence-c-65cd5-409:真实未保存草稿重载、服务器新版本保护与隔离下载/attachments/17-conflict-does-not-overwrite-2a666b3204c597f2999ec44b69184ba1c9ad43f6.png differ
diff --git a/reports/scene-editor-closure-20260906/test-results/scene-editor-persistence-c-65cd5-409:真实未保存草稿重载、服务器新版本保护与隔离下载/attachments/18-stale-recovery-explicit-decision-e3f73496423fb14f7101245ea8289af9a30adf8c.png b/reports/scene-editor-closure-20260906/test-results/scene-editor-persistence-c-65cd5-409:真实未保存草稿重载、服务器新版本保护与隔离下载/attachments/18-stale-recovery-explicit-decision-e3f73496423fb14f7101245ea8289af9a30adf8c.png
new file mode 100644
index 0000000..34b8628
Binary files /dev/null and b/reports/scene-editor-closure-20260906/test-results/scene-editor-persistence-c-65cd5-409:真实未保存草稿重载、服务器新版本保护与隔离下载/attachments/18-stale-recovery-explicit-decision-e3f73496423fb14f7101245ea8289af9a30adf8c.png differ
diff --git a/reports/scene-editor-closure-20260906/test-results/scene-editor-persistence-c-76357-:503-保留草稿,封面上传失败保留旧图,恢复后可重试/attachments/13-save-failure-keeps-draft-5eb981845121d792227751835c6fb6280e2c74ba.png b/reports/scene-editor-closure-20260906/test-results/scene-editor-persistence-c-76357-:503-保留草稿,封面上传失败保留旧图,恢复后可重试/attachments/13-save-failure-keeps-draft-5eb981845121d792227751835c6fb6280e2c74ba.png
new file mode 100644
index 0000000..071b135
Binary files /dev/null and b/reports/scene-editor-closure-20260906/test-results/scene-editor-persistence-c-76357-:503-保留草稿,封面上传失败保留旧图,恢复后可重试/attachments/13-save-failure-keeps-draft-5eb981845121d792227751835c6fb6280e2c74ba.png differ
diff --git a/reports/scene-editor-closure-20260906/test-results/scene-editor-persistence-c-76357-:503-保留草稿,封面上传失败保留旧图,恢复后可重试/attachments/14-cover-failure-keeps-old-image-b6f09855431366456d99ce13fe930dae908d48aa.png b/reports/scene-editor-closure-20260906/test-results/scene-editor-persistence-c-76357-:503-保留草稿,封面上传失败保留旧图,恢复后可重试/attachments/14-cover-failure-keeps-old-image-b6f09855431366456d99ce13fe930dae908d48aa.png
new file mode 100644
index 0000000..f4984c1
Binary files /dev/null and b/reports/scene-editor-closure-20260906/test-results/scene-editor-persistence-c-76357-:503-保留草稿,封面上传失败保留旧图,恢复后可重试/attachments/14-cover-failure-keeps-old-image-b6f09855431366456d99ce13fe930dae908d48aa.png differ
diff --git a/reports/scene-editor-closure-20260906/test-results/scene-editor-persistence-c-76357-:503-保留草稿,封面上传失败保留旧图,恢复后可重试/attachments/15-save-retry-success-5e4541dd400beff4e856c5b33499a09d74a5fb12.png b/reports/scene-editor-closure-20260906/test-results/scene-editor-persistence-c-76357-:503-保留草稿,封面上传失败保留旧图,恢复后可重试/attachments/15-save-retry-success-5e4541dd400beff4e856c5b33499a09d74a5fb12.png
new file mode 100644
index 0000000..b7a1449
Binary files /dev/null and b/reports/scene-editor-closure-20260906/test-results/scene-editor-persistence-c-76357-:503-保留草稿,封面上传失败保留旧图,恢复后可重试/attachments/15-save-retry-success-5e4541dd400beff4e856c5b33499a09d74a5fb12.png differ
diff --git a/reports/scene-editor-closure-20260906/test-results/scene-editor-persistence-c-9277e-GLB:真实上传、损坏拒绝、引用失败回滚、相机参数往返/attachments/10-invalid-reference-rolled-back-05f07581c2912fa8b34b6a60bf1dc2dd010aa862.png b/reports/scene-editor-closure-20260906/test-results/scene-editor-persistence-c-9277e-GLB:真实上传、损坏拒绝、引用失败回滚、相机参数往返/attachments/10-invalid-reference-rolled-back-05f07581c2912fa8b34b6a60bf1dc2dd010aa862.png
new file mode 100644
index 0000000..f428540
Binary files /dev/null and b/reports/scene-editor-closure-20260906/test-results/scene-editor-persistence-c-9277e-GLB:真实上传、损坏拒绝、引用失败回滚、相机参数往返/attachments/10-invalid-reference-rolled-back-05f07581c2912fa8b34b6a60bf1dc2dd010aa862.png differ
diff --git a/reports/scene-editor-closure-20260906/test-results/scene-editor-persistence-c-9277e-GLB:真实上传、损坏拒绝、引用失败回滚、相机参数往返/attachments/11-small-glb-real-upload-f95ba088694823bd5f5a71d70d90326fead99212.png b/reports/scene-editor-closure-20260906/test-results/scene-editor-persistence-c-9277e-GLB:真实上传、损坏拒绝、引用失败回滚、相机参数往返/attachments/11-small-glb-real-upload-f95ba088694823bd5f5a71d70d90326fead99212.png
new file mode 100644
index 0000000..3c31983
Binary files /dev/null and b/reports/scene-editor-closure-20260906/test-results/scene-editor-persistence-c-9277e-GLB:真实上传、损坏拒绝、引用失败回滚、相机参数往返/attachments/11-small-glb-real-upload-f95ba088694823bd5f5a71d70d90326fead99212.png differ
diff --git a/reports/scene-editor-closure-20260906/test-results/scene-editor-persistence-c-9277e-GLB:真实上传、损坏拒绝、引用失败回滚、相机参数往返/attachments/12-json-glb-reopened-5af43bf69f6e95e35c4d42ea94835a599fab9ef4.png b/reports/scene-editor-closure-20260906/test-results/scene-editor-persistence-c-9277e-GLB:真实上传、损坏拒绝、引用失败回滚、相机参数往返/attachments/12-json-glb-reopened-5af43bf69f6e95e35c4d42ea94835a599fab9ef4.png
new file mode 100644
index 0000000..b3ca427
Binary files /dev/null and b/reports/scene-editor-closure-20260906/test-results/scene-editor-persistence-c-9277e-GLB:真实上传、损坏拒绝、引用失败回滚、相机参数往返/attachments/12-json-glb-reopened-5af43bf69f6e95e35c4d42ea94835a599fab9ef4.png differ
diff --git a/reports/scene-editor-closure-20260906/test-results/scene-editor-persistence-c-f3c53-定校验、保存与发布失败阻断、真实固定版本供训练目录使用/attachments/19-publish-failure-stays-draft-55033546226558dac6a51ac98602d0843511876e.png b/reports/scene-editor-closure-20260906/test-results/scene-editor-persistence-c-f3c53-定校验、保存与发布失败阻断、真实固定版本供训练目录使用/attachments/19-publish-failure-stays-draft-55033546226558dac6a51ac98602d0843511876e.png
new file mode 100644
index 0000000..d264faf
Binary files /dev/null and b/reports/scene-editor-closure-20260906/test-results/scene-editor-persistence-c-f3c53-定校验、保存与发布失败阻断、真实固定版本供训练目录使用/attachments/19-publish-failure-stays-draft-55033546226558dac6a51ac98602d0843511876e.png differ
diff --git a/reports/scene-editor-closure-20260906/test-results/scene-editor-persistence-c-f3c53-定校验、保存与发布失败阻断、真实固定版本供训练目录使用/attachments/20-scene-published-fixed-version-2fbecdebdab60144c77746719660eaaf060932f9.png b/reports/scene-editor-closure-20260906/test-results/scene-editor-persistence-c-f3c53-定校验、保存与发布失败阻断、真实固定版本供训练目录使用/attachments/20-scene-published-fixed-version-2fbecdebdab60144c77746719660eaaf060932f9.png
new file mode 100644
index 0000000..6ba8f56
Binary files /dev/null and b/reports/scene-editor-closure-20260906/test-results/scene-editor-persistence-c-f3c53-定校验、保存与发布失败阻断、真实固定版本供训练目录使用/attachments/20-scene-published-fixed-version-2fbecdebdab60144c77746719660eaaf060932f9.png differ
diff --git a/reports/scene-editor-closure-20260906/test-results/scene-editor-persistence-c-f3c53-定校验、保存与发布失败阻断、真实固定版本供训练目录使用/attachments/21-published-scene-cover-in-list-30bd8194718756cb25ed82589ae7c81bd2363e6e.png b/reports/scene-editor-closure-20260906/test-results/scene-editor-persistence-c-f3c53-定校验、保存与发布失败阻断、真实固定版本供训练目录使用/attachments/21-published-scene-cover-in-list-30bd8194718756cb25ed82589ae7c81bd2363e6e.png
new file mode 100644
index 0000000..23e7cce
Binary files /dev/null and b/reports/scene-editor-closure-20260906/test-results/scene-editor-persistence-c-f3c53-定校验、保存与发布失败阻断、真实固定版本供训练目录使用/attachments/21-published-scene-cover-in-list-30bd8194718756cb25ed82589ae7c81bd2363e6e.png differ
diff --git a/reports/scene-editor-closure-20260906/test-results/scene-editor-templates-closure-两种整体场地加载、重复放置阻止与显式删除后替换/attachments/31-venue-depot-reopened-7f145261822d801517e00cbd9b9c3aaf01efa6ec.png b/reports/scene-editor-closure-20260906/test-results/scene-editor-templates-closure-两种整体场地加载、重复放置阻止与显式删除后替换/attachments/31-venue-depot-reopened-7f145261822d801517e00cbd9b9c3aaf01efa6ec.png
new file mode 100644
index 0000000..4566b4d
Binary files /dev/null and b/reports/scene-editor-closure-20260906/test-results/scene-editor-templates-closure-两种整体场地加载、重复放置阻止与显式删除后替换/attachments/31-venue-depot-reopened-7f145261822d801517e00cbd9b9c3aaf01efa6ec.png differ
diff --git a/reports/scene-editor-closure-20260906/test-results/scene-editor-templates-closure-两种整体场地加载、重复放置阻止与显式删除后替换/attachments/32-venue-terrain-reopened-46b72440b98f0cbb6efd3b2f54fa7438e7bc906e.png b/reports/scene-editor-closure-20260906/test-results/scene-editor-templates-closure-两种整体场地加载、重复放置阻止与显式删除后替换/attachments/32-venue-terrain-reopened-46b72440b98f0cbb6efd3b2f54fa7438e7bc906e.png
new file mode 100644
index 0000000..4993345
Binary files /dev/null and b/reports/scene-editor-closure-20260906/test-results/scene-editor-templates-closure-两种整体场地加载、重复放置阻止与显式删除后替换/attachments/32-venue-terrain-reopened-46b72440b98f0cbb6efd3b2f54fa7438e7bc906e.png differ
diff --git a/reports/scene-editor-closure-20260906/test-results/scene-editor-templates-closure-八种基础资源逐项放置、真实拖放、搜索和保存重开/attachments/30-eight-basic-assets-reopened-db9d240a62964056c9443d3f4b6041716d28d76b.png b/reports/scene-editor-closure-20260906/test-results/scene-editor-templates-closure-八种基础资源逐项放置、真实拖放、搜索和保存重开/attachments/30-eight-basic-assets-reopened-db9d240a62964056c9443d3f4b6041716d28d76b.png
new file mode 100644
index 0000000..dfe3110
Binary files /dev/null and b/reports/scene-editor-closure-20260906/test-results/scene-editor-templates-closure-八种基础资源逐项放置、真实拖放、搜索和保存重开/attachments/30-eight-basic-assets-reopened-db9d240a62964056c9443d3f4b6041716d28d76b.png differ
diff --git a/reports/scene-editor-closure-20260906/test-results/scene-editor-templates-closure-场景模板实际加载、保存重开:复装调试车间/attachments/27-template-workshop-5-b4abd6b6e7bdc106f54e61c26f139abb586b3b25.png b/reports/scene-editor-closure-20260906/test-results/scene-editor-templates-closure-场景模板实际加载、保存重开:复装调试车间/attachments/27-template-workshop-5-b4abd6b6e7bdc106f54e61c26f139abb586b3b25.png
new file mode 100644
index 0000000..c6ef4b0
Binary files /dev/null and b/reports/scene-editor-closure-20260906/test-results/scene-editor-templates-closure-场景模板实际加载、保存重开:复装调试车间/attachments/27-template-workshop-5-b4abd6b6e7bdc106f54e61c26f139abb586b3b25.png differ
diff --git a/reports/scene-editor-closure-20260906/test-results/scene-editor-templates-closure-场景模板实际加载、保存重开:室外综合训练场/attachments/28-template-outdoor-1-252598baacd8ac3e60b8050ccbf5fa5e81cb875f.png b/reports/scene-editor-closure-20260906/test-results/scene-editor-templates-closure-场景模板实际加载、保存重开:室外综合训练场/attachments/28-template-outdoor-1-252598baacd8ac3e60b8050ccbf5fa5e81cb875f.png
new file mode 100644
index 0000000..03cdd3d
Binary files /dev/null and b/reports/scene-editor-closure-20260906/test-results/scene-editor-templates-closure-场景模板实际加载、保存重开:室外综合训练场/attachments/28-template-outdoor-1-252598baacd8ac3e60b8050ccbf5fa5e81cb875f.png differ
diff --git a/reports/scene-editor-closure-20260906/test-results/scene-editor-templates-closure-场景模板实际加载、保存重开:总成拆卸车间/attachments/24-template-workshop-2-1efef10a7936083fcf6564010a115032f05d4056.png b/reports/scene-editor-closure-20260906/test-results/scene-editor-templates-closure-场景模板实际加载、保存重开:总成拆卸车间/attachments/24-template-workshop-2-1efef10a7936083fcf6564010a115032f05d4056.png
new file mode 100644
index 0000000..ed86ee3
Binary files /dev/null and b/reports/scene-editor-closure-20260906/test-results/scene-editor-templates-closure-场景模板实际加载、保存重开:总成拆卸车间/attachments/24-template-workshop-2-1efef10a7936083fcf6564010a115032f05d4056.png differ
diff --git a/reports/scene-editor-closure-20260906/test-results/scene-editor-templates-closure-场景模板实际加载、保存重开:清洗检测车间/attachments/26-template-workshop-4-18603ff60a745210c089d577791039692a6c53e4.png b/reports/scene-editor-closure-20260906/test-results/scene-editor-templates-closure-场景模板实际加载、保存重开:清洗检测车间/attachments/26-template-workshop-4-18603ff60a745210c089d577791039692a6c53e4.png
new file mode 100644
index 0000000..661d0f2
Binary files /dev/null and b/reports/scene-editor-closure-20260906/test-results/scene-editor-templates-closure-场景模板实际加载、保存重开:清洗检测车间/attachments/26-template-workshop-4-18603ff60a745210c089d577791039692a6c53e4.png differ
diff --git a/reports/scene-editor-closure-20260906/test-results/scene-editor-templates-closure-场景模板实际加载、保存重开:演示维修车间(可拆分)/attachments/23-template-workshop-real-af23224a3356e092fbca276a7e9c890a9ea98b6d.png b/reports/scene-editor-closure-20260906/test-results/scene-editor-templates-closure-场景模板实际加载、保存重开:演示维修车间(可拆分)/attachments/23-template-workshop-real-af23224a3356e092fbca276a7e9c890a9ea98b6d.png
new file mode 100644
index 0000000..9aeda0a
Binary files /dev/null and b/reports/scene-editor-closure-20260906/test-results/scene-editor-templates-closure-场景模板实际加载、保存重开:演示维修车间(可拆分)/attachments/23-template-workshop-real-af23224a3356e092fbca276a7e9c890a9ea98b6d.png differ
diff --git a/reports/scene-editor-closure-20260906/test-results/scene-editor-templates-closure-场景模板实际加载、保存重开:真实装备车库/attachments/20-template-depot-1-b94f06050cef74a4aeb174ebde7595a73ad6a4c9.png b/reports/scene-editor-closure-20260906/test-results/scene-editor-templates-closure-场景模板实际加载、保存重开:真实装备车库/attachments/20-template-depot-1-b94f06050cef74a4aeb174ebde7595a73ad6a4c9.png
new file mode 100644
index 0000000..650dbde
Binary files /dev/null and b/reports/scene-editor-closure-20260906/test-results/scene-editor-templates-closure-场景模板实际加载、保存重开:真实装备车库/attachments/20-template-depot-1-b94f06050cef74a4aeb174ebde7595a73ad6a4c9.png differ
diff --git a/reports/scene-editor-closure-20260906/test-results/scene-editor-templates-closure-场景模板实际加载、保存重开:真实野外训练场/attachments/21-template-terrain-1-e0372c0f4b0de556c73c13a3f39d2678e400d30b.png b/reports/scene-editor-closure-20260906/test-results/scene-editor-templates-closure-场景模板实际加载、保存重开:真实野外训练场/attachments/21-template-terrain-1-e0372c0f4b0de556c73c13a3f39d2678e400d30b.png
new file mode 100644
index 0000000..d23c79d
Binary files /dev/null and b/reports/scene-editor-closure-20260906/test-results/scene-editor-templates-closure-场景模板实际加载、保存重开:真实野外训练场/attachments/21-template-terrain-1-e0372c0f4b0de556c73c13a3f39d2678e400d30b.png differ
diff --git a/reports/scene-editor-closure-20260906/test-results/scene-editor-templates-closure-场景模板实际加载、保存重开:综合检验车间/attachments/22-template-workshop-1-630e13e0c8646d0da4d0c8bdeb1db31b483f65ac.png b/reports/scene-editor-closure-20260906/test-results/scene-editor-templates-closure-场景模板实际加载、保存重开:综合检验车间/attachments/22-template-workshop-1-630e13e0c8646d0da4d0c8bdeb1db31b483f65ac.png
new file mode 100644
index 0000000..adf8ab8
Binary files /dev/null and b/reports/scene-editor-closure-20260906/test-results/scene-editor-templates-closure-场景模板实际加载、保存重开:综合检验车间/attachments/22-template-workshop-1-630e13e0c8646d0da4d0c8bdeb1db31b483f65ac.png differ
diff --git a/reports/scene-editor-closure-20260906/test-results/scene-editor-templates-closure-场景模板实际加载、保存重开:部件分解车间/attachments/25-template-workshop-3-0ae893985dd2a81428ef5ec0b0be20e102630da6.png b/reports/scene-editor-closure-20260906/test-results/scene-editor-templates-closure-场景模板实际加载、保存重开:部件分解车间/attachments/25-template-workshop-3-0ae893985dd2a81428ef5ec0b0be20e102630da6.png
new file mode 100644
index 0000000..4939000
Binary files /dev/null and b/reports/scene-editor-closure-20260906/test-results/scene-editor-templates-closure-场景模板实际加载、保存重开:部件分解车间/attachments/25-template-workshop-3-0ae893985dd2a81428ef5ec0b0be20e102630da6.png differ
diff --git a/reports/scene-editor-closure-20260906/test-results/scene-editor-templates-closure-模型成果狐狸卡车双击与真实拖入、树搜索和部件隐藏重开/attachments/33-fox-placed-visible-fa2377e8659c4f45630894cf655e6309273deae5.png b/reports/scene-editor-closure-20260906/test-results/scene-editor-templates-closure-模型成果狐狸卡车双击与真实拖入、树搜索和部件隐藏重开/attachments/33-fox-placed-visible-fa2377e8659c4f45630894cf655e6309273deae5.png
new file mode 100644
index 0000000..3d3f751
Binary files /dev/null and b/reports/scene-editor-closure-20260906/test-results/scene-editor-templates-closure-模型成果狐狸卡车双击与真实拖入、树搜索和部件隐藏重开/attachments/33-fox-placed-visible-fa2377e8659c4f45630894cf655e6309273deae5.png differ
diff --git a/reports/scene-editor-closure-20260906/test-results/scene-editor-templates-closure-模型成果狐狸卡车双击与真实拖入、树搜索和部件隐藏重开/attachments/34-truck-placed-part-restored-8af7f3874fcde860cdfaf3b1075526b4dd84e572.png b/reports/scene-editor-closure-20260906/test-results/scene-editor-templates-closure-模型成果狐狸卡车双击与真实拖入、树搜索和部件隐藏重开/attachments/34-truck-placed-part-restored-8af7f3874fcde860cdfaf3b1075526b4dd84e572.png
new file mode 100644
index 0000000..f1b3f52
Binary files /dev/null and b/reports/scene-editor-closure-20260906/test-results/scene-editor-templates-closure-模型成果狐狸卡车双击与真实拖入、树搜索和部件隐藏重开/attachments/34-truck-placed-part-restored-8af7f3874fcde860cdfaf3b1075526b4dd84e572.png differ
diff --git a/reports/scene-editor-closure-20260906/test-results/scene-editor-templates-closure-模板搜索与未保存切换确认保留和放弃/attachments/29-template-confirm-empty-c3412d63dd2b64db245f43b7884bd18078c4603c.png b/reports/scene-editor-closure-20260906/test-results/scene-editor-templates-closure-模板搜索与未保存切换确认保留和放弃/attachments/29-template-confirm-empty-c3412d63dd2b64db245f43b7884bd18078c4603c.png
new file mode 100644
index 0000000..d288cc9
Binary files /dev/null and b/reports/scene-editor-closure-20260906/test-results/scene-editor-templates-closure-模板搜索与未保存切换确认保留和放弃/attachments/29-template-confirm-empty-c3412d63dd2b64db245f43b7884bd18078c4603c.png differ
diff --git a/reports/scene-editor-closure-20260906/test-results/scene-editor-viewport-closure-真实变换手柄移动旋转缩放、吸附与保存重开/attachments/43-real-transform-gizmo-b8e33ddb28e729d479a3c7d69b44c9e2690cbc4c.png b/reports/scene-editor-closure-20260906/test-results/scene-editor-viewport-closure-真实变换手柄移动旋转缩放、吸附与保存重开/attachments/43-real-transform-gizmo-b8e33ddb28e729d479a3c7d69b44c9e2690cbc4c.png
new file mode 100644
index 0000000..bf3ca8f
Binary files /dev/null and b/reports/scene-editor-closure-20260906/test-results/scene-editor-viewport-closure-真实变换手柄移动旋转缩放、吸附与保存重开/attachments/43-real-transform-gizmo-b8e33ddb28e729d479a3c7d69b44c9e2690cbc4c.png differ
diff --git a/reports/scene-editor-closure-20260906/test-results/scene-editor-viewport-closure-真实相机操控、视口拾取、预览禁写和两尺寸主题可读性/attachments/40-viewport-dark-1920-6f73c71d463ef8d27e1da0b2e3a7ee470cce0995.png b/reports/scene-editor-closure-20260906/test-results/scene-editor-viewport-closure-真实相机操控、视口拾取、预览禁写和两尺寸主题可读性/attachments/40-viewport-dark-1920-6f73c71d463ef8d27e1da0b2e3a7ee470cce0995.png
new file mode 100644
index 0000000..d95445d
Binary files /dev/null and b/reports/scene-editor-closure-20260906/test-results/scene-editor-viewport-closure-真实相机操控、视口拾取、预览禁写和两尺寸主题可读性/attachments/40-viewport-dark-1920-6f73c71d463ef8d27e1da0b2e3a7ee470cce0995.png differ
diff --git a/reports/scene-editor-closure-20260906/test-results/scene-editor-viewport-closure-真实相机操控、视口拾取、预览禁写和两尺寸主题可读性/attachments/41-viewport-light-1440-dc92e9151d424a939ea25d65d5db32e301d4bdcd.png b/reports/scene-editor-closure-20260906/test-results/scene-editor-viewport-closure-真实相机操控、视口拾取、预览禁写和两尺寸主题可读性/attachments/41-viewport-light-1440-dc92e9151d424a939ea25d65d5db32e301d4bdcd.png
new file mode 100644
index 0000000..608e56a
Binary files /dev/null and b/reports/scene-editor-closure-20260906/test-results/scene-editor-viewport-closure-真实相机操控、视口拾取、预览禁写和两尺寸主题可读性/attachments/41-viewport-light-1440-dc92e9151d424a939ea25d65d5db32e301d4bdcd.png differ
diff --git a/reports/scene-editor-closure-20260906/test-results/scene-editor-viewport-closure-真实相机操控、视口拾取、预览禁写和两尺寸主题可读性/attachments/42-fox-preview-original-material-1f3084811239e15fa31ff9b11641c1a758c2e0a7.png b/reports/scene-editor-closure-20260906/test-results/scene-editor-viewport-closure-真实相机操控、视口拾取、预览禁写和两尺寸主题可读性/attachments/42-fox-preview-original-material-1f3084811239e15fa31ff9b11641c1a758c2e0a7.png
new file mode 100644
index 0000000..ecce71c
Binary files /dev/null and b/reports/scene-editor-closure-20260906/test-results/scene-editor-viewport-closure-真实相机操控、视口拾取、预览禁写和两尺寸主题可读性/attachments/42-fox-preview-original-material-1f3084811239e15fa31ff9b11641c1a758c2e0a7.png differ
diff --git a/reports/scene-editor-closure-20260906/test-results/scene-editor-viewport-closure-缓存标签暂停键盘、草稿恢复以及关闭取消和保存失败留页/attachments/44-cache-close-save-failure-ebbf3ab612f78f5af1af442d083ce3fdf538576f.png b/reports/scene-editor-closure-20260906/test-results/scene-editor-viewport-closure-缓存标签暂停键盘、草稿恢复以及关闭取消和保存失败留页/attachments/44-cache-close-save-failure-ebbf3ab612f78f5af1af442d083ce3fdf538576f.png
new file mode 100644
index 0000000..8a818d1
Binary files /dev/null and b/reports/scene-editor-closure-20260906/test-results/scene-editor-viewport-closure-缓存标签暂停键盘、草稿恢复以及关闭取消和保存失败留页/attachments/44-cache-close-save-failure-ebbf3ab612f78f5af1af442d083ce3fdf538576f.png differ
diff --git a/reports/scene-editor-closure-20260906/test-results/scene-editor-workflow-clos-1cfb2-权限、全部属性、清空绑定、蒙皮保护、相机与封面保存重开/attachments/01-fox-actual-scene-f8ceace857505589c4ff000436a8f300253b8663.png b/reports/scene-editor-closure-20260906/test-results/scene-editor-workflow-clos-1cfb2-权限、全部属性、清空绑定、蒙皮保护、相机与封面保存重开/attachments/01-fox-actual-scene-f8ceace857505589c4ff000436a8f300253b8663.png
new file mode 100644
index 0000000..806eb21
Binary files /dev/null and b/reports/scene-editor-closure-20260906/test-results/scene-editor-workflow-clos-1cfb2-权限、全部属性、清空绑定、蒙皮保护、相机与封面保存重开/attachments/01-fox-actual-scene-f8ceace857505589c4ff000436a8f300253b8663.png differ
diff --git a/reports/scene-editor-closure-20260906/test-results/scene-editor-workflow-clos-1cfb2-权限、全部属性、清空绑定、蒙皮保护、相机与封面保存重开/attachments/02-fox-reopened-skin-protection-1a05d737459c22e1308f422fd83510e30b1cac30.png b/reports/scene-editor-closure-20260906/test-results/scene-editor-workflow-clos-1cfb2-权限、全部属性、清空绑定、蒙皮保护、相机与封面保存重开/attachments/02-fox-reopened-skin-protection-1a05d737459c22e1308f422fd83510e30b1cac30.png
new file mode 100644
index 0000000..4e9458b
Binary files /dev/null and b/reports/scene-editor-closure-20260906/test-results/scene-editor-workflow-clos-1cfb2-权限、全部属性、清空绑定、蒙皮保护、相机与封面保存重开/attachments/02-fox-reopened-skin-protection-1a05d737459c22e1308f422fd83510e30b1cac30.png differ
diff --git a/reports/scene-editor-closure-20260906/test-results/scene-editor-workflow-clos-22aa1-员:可浏览预览与导出,模板-资源-键盘-接口不能改场景/attachments/06-readonly-scene-permissions-349245dac8e17c0f8f046075a554dca688143e55.png b/reports/scene-editor-closure-20260906/test-results/scene-editor-workflow-clos-22aa1-员:可浏览预览与导出,模板-资源-键盘-接口不能改场景/attachments/06-readonly-scene-permissions-349245dac8e17c0f8f046075a554dca688143e55.png
new file mode 100644
index 0000000..6290d0e
Binary files /dev/null and b/reports/scene-editor-closure-20260906/test-results/scene-editor-workflow-clos-22aa1-员:可浏览预览与导出,模板-资源-键盘-接口不能改场景/attachments/06-readonly-scene-permissions-349245dac8e17c0f8f046075a554dca688143e55.png differ
diff --git a/reports/scene-editor-closure-20260906/test-results/scene-editor-workflow-closure-新建独立场景:取消、失败、保护原工程以及成功新-ID/attachments/04-new-failure-original-preserved-fe807bceb8705d176e7c0360db656ee145405d34.png b/reports/scene-editor-closure-20260906/test-results/scene-editor-workflow-closure-新建独立场景:取消、失败、保护原工程以及成功新-ID/attachments/04-new-failure-original-preserved-fe807bceb8705d176e7c0360db656ee145405d34.png
new file mode 100644
index 0000000..38e4fae
Binary files /dev/null and b/reports/scene-editor-closure-20260906/test-results/scene-editor-workflow-closure-新建独立场景:取消、失败、保护原工程以及成功新-ID/attachments/04-new-failure-original-preserved-fe807bceb8705d176e7c0360db656ee145405d34.png differ
diff --git a/reports/scene-editor-closure-20260906/test-results/scene-editor-workflow-closure-新建独立场景:取消、失败、保护原工程以及成功新-ID/attachments/05-new-separate-scene-5de74911a31d6d4cc92c75f9ae16f6fa4d772149.png b/reports/scene-editor-closure-20260906/test-results/scene-editor-workflow-closure-新建独立场景:取消、失败、保护原工程以及成功新-ID/attachments/05-new-separate-scene-5de74911a31d6d4cc92c75f9ae16f6fa4d772149.png
new file mode 100644
index 0000000..60b02da
Binary files /dev/null and b/reports/scene-editor-closure-20260906/test-results/scene-editor-workflow-closure-新建独立场景:取消、失败、保护原工程以及成功新-ID/attachments/05-new-separate-scene-5de74911a31d6d4cc92c75f9ae16f6fa4d772149.png differ
diff --git a/reports/scene-editor-closure-20260906/test-results/scene-editor-workflow-closure-牛奶卡车:刚性部件独立变换、软删除、历史与保存基线一致/attachments/03-truck-part-history-reopened-2a3b2f13fb63ded2894505c93f7cffa332cedf99.png b/reports/scene-editor-closure-20260906/test-results/scene-editor-workflow-closure-牛奶卡车:刚性部件独立变换、软删除、历史与保存基线一致/attachments/03-truck-part-history-reopened-2a3b2f13fb63ded2894505c93f7cffa332cedf99.png
new file mode 100644
index 0000000..f5a1f51
Binary files /dev/null and b/reports/scene-editor-closure-20260906/test-results/scene-editor-workflow-closure-牛奶卡车:刚性部件独立变换、软删除、历史与保存基线一致/attachments/03-truck-part-history-reopened-2a3b2f13fb63ded2894505c93f7cffa332cedf99.png differ
diff --git a/reports/scene-editor-closure-20260906/test-results/scene-editor-workflow-closure-牛奶卡车:刚性部件独立变换、软删除、历史与保存基线一致/attachments/07-garage-full-f72eec4133d7a368ffd3b648288dd30fef75161e.png b/reports/scene-editor-closure-20260906/test-results/scene-editor-workflow-closure-牛奶卡车:刚性部件独立变换、软删除、历史与保存基线一致/attachments/07-garage-full-f72eec4133d7a368ffd3b648288dd30fef75161e.png
new file mode 100644
index 0000000..d708c06
Binary files /dev/null and b/reports/scene-editor-closure-20260906/test-results/scene-editor-workflow-closure-牛奶卡车:刚性部件独立变换、软删除、历史与保存基线一致/attachments/07-garage-full-f72eec4133d7a368ffd3b648288dd30fef75161e.png differ
diff --git a/reports/scene-editor-closure-20260906/test-results/scene-editor-workflow-closure-牛奶卡车:刚性部件独立变换、软删除、历史与保存基线一致/attachments/07-garage-hidden-9c3abe15ddc69936a78c85fa8778365ab0d515dd.png b/reports/scene-editor-closure-20260906/test-results/scene-editor-workflow-closure-牛奶卡车:刚性部件独立变换、软删除、历史与保存基线一致/attachments/07-garage-hidden-9c3abe15ddc69936a78c85fa8778365ab0d515dd.png
new file mode 100644
index 0000000..3ac91c9
Binary files /dev/null and b/reports/scene-editor-closure-20260906/test-results/scene-editor-workflow-closure-牛奶卡车:刚性部件独立变换、软删除、历史与保存基线一致/attachments/07-garage-hidden-9c3abe15ddc69936a78c85fa8778365ab0d515dd.png differ
diff --git a/reports/scene-editor-closure-20260906/test-results/scene-editor-workflow-closure-牛奶卡车:刚性部件独立变换、软删除、历史与保存基线一致/attachments/07-garage-xray-35ccddc218c0d72103caabee8a6cb31d52f0bbed.png b/reports/scene-editor-closure-20260906/test-results/scene-editor-workflow-closure-牛奶卡车:刚性部件独立变换、软删除、历史与保存基线一致/attachments/07-garage-xray-35ccddc218c0d72103caabee8a6cb31d52f0bbed.png
new file mode 100644
index 0000000..8f57499
Binary files /dev/null and b/reports/scene-editor-closure-20260906/test-results/scene-editor-workflow-closure-牛奶卡车:刚性部件独立变换、软删除、历史与保存基线一致/attachments/07-garage-xray-35ccddc218c0d72103caabee8a6cb31d52f0bbed.png differ
diff --git a/reports/scene-editor-closure-20260906/validation.json b/reports/scene-editor-closure-20260906/validation.json
new file mode 100644
index 0000000..befd482
--- /dev/null
+++ b/reports/scene-editor-closure-20260906/validation.json
@@ -0,0 +1,539 @@
+{
+ "time": "2026-09-06T10:31:52.558Z",
+ "runId": "scene-editor-2026-09-06T10:24:04.121Z",
+ "overallStatus": "PASS",
+ "finalRun": true,
+ "scope": "默认路由的119项功能;浏览器证据与聚焦单元合同分别计数。较大功能扩展未实施。",
+ "e2e": {
+ "tests": 33,
+ "pass": 33,
+ "fail": 0,
+ "retries": 0,
+ "startTime": "2026-09-06T10:24:04.121Z",
+ "durationMs": 422286.673,
+ "complete": true
+ },
+ "priorFullRun": {
+ "pass": 32,
+ "fail": 1,
+ "reason": "关闭保存失败用例的提示定位同时匹配成功和失败消息,已限定目标错误;另补列表搜索遮罩消失后截图的等待条件。",
+ "privateArchived": true,
+ "countedAsFinal": false
+ },
+ "secondFullRun": {
+ "pass": 32,
+ "fail": 1,
+ "reason": "并行加载同源模型时恢复顺序不稳定;已保留文档对象顺序,并以实例编号验证部件隔离和重开。",
+ "privateArchived": true,
+ "countedAsFinal": false
+ },
+ "frontend": {
+ "status": "PASS",
+ "command": "pnpm run build",
+ "legacyModelPayloadUnchanged": true
+ },
+ "backend": {
+ "status": "PASS",
+ "command": "mvn -pl ut_tran -am test package",
+ "tests": 373,
+ "failures": 0,
+ "errors": 0,
+ "skipped": 0,
+ "suites": 54
+ },
+ "runtime": {
+ "status": "PASS",
+ "tests": 106,
+ "pass": 106,
+ "fail": 0,
+ "skipped": 0
+ },
+ "client": {
+ "status": "PASS",
+ "tests": 32,
+ "pass": 32,
+ "fail": 0,
+ "skipped": 0
+ },
+ "coverage": [
+ {
+ "kind": "unit",
+ "status": "PASS",
+ "runId": "scene-editor-2026-09-06T10:24:04.121Z",
+ "testTitle": "scene granular update permission enables editing without the retired shared code",
+ "featureIds": [
+ "permission.update"
+ ],
+ "source": "tools/scene-closure-engine.test.mjs",
+ "note": "实际执行真实方法/纯函数合同,未冒称浏览器操作"
+ },
+ {
+ "kind": "unit",
+ "status": "PASS",
+ "runId": "scene-editor-2026-09-06T10:24:04.121Z",
+ "testTitle": "typing undo/redo stays inside text controls and inactive cached editors ignore global shortcuts",
+ "featureIds": [
+ "history.undo",
+ "history.redo",
+ "transform.keys",
+ "lifecycle.cache"
+ ],
+ "source": "tools/scene-closure-engine.test.mjs",
+ "note": "实际执行真实方法/纯函数合同,未冒称浏览器操作"
+ },
+ {
+ "kind": "unit",
+ "status": "PASS",
+ "runId": "scene-editor-2026-09-06T10:24:04.121Z",
+ "testTitle": "preview locks mutation controls and Delete while Escape restores editing",
+ "featureIds": [
+ "preview.lock",
+ "preview.exit"
+ ],
+ "source": "tools/scene-closure-engine.test.mjs",
+ "note": "实际执行真实方法/纯函数合同,未冒称浏览器操作"
+ },
+ {
+ "kind": "unit",
+ "status": "PASS",
+ "runId": "scene-editor-2026-09-06T10:24:04.121Z",
+ "testTitle": "real Three ray hits on hidden descendants are excluded before selecting a root or part",
+ "featureIds": [
+ "selection.hidden"
+ ],
+ "source": "tools/scene-closure-engine.test.mjs",
+ "note": "实际执行真实方法/纯函数合同,未冒称浏览器操作"
+ },
+ {
+ "kind": "unit",
+ "status": "PASS",
+ "runId": "scene-editor-2026-09-06T10:24:04.121Z",
+ "testTitle": "deleting a root disposes its geometry and materials after clearing selection",
+ "featureIds": [
+ "object.delete",
+ "lifecycle.destroy"
+ ],
+ "source": "tools/scene-closure-engine.test.mjs",
+ "note": "实际执行真实方法/纯函数合同,未冒称浏览器操作"
+ },
+ {
+ "kind": "unit",
+ "status": "PASS",
+ "runId": "scene-editor-2026-09-06T10:24:04.121Z",
+ "testTitle": "loading and history restoration preserve an authored camera rather than auto-focusing a vehicle",
+ "featureIds": [
+ "json.rendering",
+ "history.undo"
+ ],
+ "source": "tools/scene-closure-engine.test.mjs",
+ "note": "实际执行真实方法/纯函数合同,未冒称浏览器操作"
+ },
+ {
+ "kind": "unit",
+ "status": "PASS",
+ "runId": "scene-editor-2026-09-06T10:24:04.121Z",
+ "testTitle": "save B then undo A stays dirty, and redo B returns to the actual saved baseline",
+ "featureIds": [
+ "history.undo",
+ "history.redo"
+ ],
+ "source": "tools/scene-closure-engine.test.mjs",
+ "note": "实际执行真实方法/纯函数合同,未冒称浏览器操作"
+ },
+ {
+ "kind": "unit",
+ "status": "PASS",
+ "runId": "scene-editor-2026-09-06T10:24:04.121Z",
+ "testTitle": "new resource bindings do not reuse a saved serial after earlier objects were deleted",
+ "featureIds": [
+ "validation.bindings"
+ ],
+ "source": "tools/scene-closure-engine.test.mjs",
+ "note": "实际执行真实方法/纯函数合同,未冒称浏览器操作"
+ },
+ {
+ "kind": "unit",
+ "status": "PASS",
+ "runId": "scene-editor-2026-09-06T10:24:04.121Z",
+ "testTitle": "a failed replacement GLB keeps the previous live scene rather than clearing an unsaved draft",
+ "featureIds": [
+ "json.missing-resource"
+ ],
+ "source": "tools/scene-closure-engine.test.mjs",
+ "note": "实际执行真实方法/纯函数合同,未冒称浏览器操作"
+ },
+ {
+ "kind": "unit",
+ "status": "PASS",
+ "runId": "scene-editor-2026-09-06T10:24:04.121Z",
+ "testTitle": "failed undo restoration retains live objects and keeps the undo target available for retry",
+ "featureIds": [
+ "history.failure"
+ ],
+ "source": "tools/scene-closure-engine.test.mjs",
+ "note": "实际执行真实方法/纯函数合同,未冒称浏览器操作"
+ },
+ {
+ "kind": "unit",
+ "status": "PASS",
+ "runId": "scene-editor-2026-09-06T10:24:04.121Z",
+ "testTitle": "selection highlight cleanup restores source materials and releases its own GPU helper",
+ "featureIds": [
+ "selection.highlight",
+ "lifecycle.destroy"
+ ],
+ "source": "tools/scene-closure-engine.test.mjs",
+ "note": "实际执行真实方法/纯函数合同,未冒称浏览器操作"
+ },
+ {
+ "kind": "unit",
+ "status": "PASS",
+ "runId": "scene-editor-2026-09-06T10:24:04.121Z",
+ "testTitle": "replacement failure waits for other started loads before restoring the original scene",
+ "featureIds": [
+ "history.failure",
+ "glb.pending"
+ ],
+ "source": "tools/scene-closure-engine.test.mjs",
+ "note": "实际执行真实方法/纯函数合同,未冒称浏览器操作"
+ },
+ {
+ "kind": "unit",
+ "status": "PASS",
+ "runId": "scene-editor-2026-09-06T10:24:04.121Z",
+ "testTitle": "activity log reflects actual session events and keeps API error details transient",
+ "featureIds": [
+ "panel.logs"
+ ],
+ "source": "tools/scene-closure-engine.test.mjs",
+ "note": "实际执行真实方法/纯函数合同,未冒称浏览器操作"
+ },
+ {
+ "kind": "unit",
+ "status": "PASS",
+ "runId": "scene-editor-2026-09-06T10:24:04.121Z",
+ "testTitle": "verify opens live validation and whitespace binding edits immediately invalidate the active tab",
+ "featureIds": [
+ "panel.validation",
+ "validation.bindings"
+ ],
+ "source": "tools/scene-closure-engine.test.mjs",
+ "note": "实际执行真实方法/纯函数合同,未冒称浏览器操作"
+ },
+ {
+ "kind": "unit",
+ "status": "PASS",
+ "runId": "scene-editor-2026-09-06T10:24:04.121Z",
+ "testTitle": "model source navigation locates the published owner and falls back without source permission",
+ "featureIds": [
+ "assets.model-link"
+ ],
+ "source": "tools/scene-closure-engine.test.mjs",
+ "note": "实际执行真实方法/纯函数合同,未冒称浏览器操作"
+ },
+ {
+ "kind": "unit",
+ "status": "PASS",
+ "runId": "scene-editor-2026-09-06T10:24:04.121Z",
+ "testTitle": "gizmo scaling across its pivot follows the same strictly positive scale contract as inspector edits",
+ "featureIds": [
+ "transform.scale",
+ "property.scale"
+ ],
+ "source": "tools/scene-closure-engine.test.mjs",
+ "note": "实际执行真实方法/纯函数合同,未冒称浏览器操作"
+ },
+ {
+ "kind": "unit",
+ "status": "PASS",
+ "runId": "scene-editor-2026-09-06T10:24:04.121Z",
+ "testTitle": "default history caps at 60 snapshots, stops at either edge and discards redo only after a new branch",
+ "featureIds": [
+ "history.capacity",
+ "history.undo",
+ "history.redo"
+ ],
+ "source": "tools/scene-closure-engine.test.mjs",
+ "note": "实际执行真实方法/纯函数合同,未冒称浏览器操作"
+ },
+ {
+ "kind": "unit",
+ "status": "PASS",
+ "runId": "scene-editor-2026-09-06T10:24:04.121Z",
+ "testTitle": "two published instances have unique tree part IDs and editing the second persists independently after reopen",
+ "featureIds": [
+ "part.transform",
+ "selection.tree"
+ ],
+ "source": "tools/scene-closure-engine.test.mjs",
+ "note": "实际执行真实方法/纯函数合同,未冒称浏览器操作"
+ },
+ {
+ "kind": "unit",
+ "status": "PASS",
+ "runId": "scene-editor-2026-09-06T10:24:04.121Z",
+ "testTitle": "legacy duplicate instance IDs migrate only later owners in document order despite reversed GLB completion",
+ "featureIds": [
+ "part.transform",
+ "publish.fixed-model"
+ ],
+ "source": "tools/scene-closure-engine.test.mjs",
+ "note": "实际执行真实方法/纯函数合同,未冒称浏览器操作"
+ },
+ {
+ "kind": "unit",
+ "status": "PASS",
+ "runId": "scene-editor-2026-09-06T10:24:04.121Z",
+ "testTitle": "successful parallel restores retain document order after delayed GLBs and split-object batches finish out of order",
+ "featureIds": [
+ "project.load",
+ "selection.tree"
+ ],
+ "source": "tools/scene-closure-engine.test.mjs",
+ "note": "实际执行真实方法/纯函数合同,未冒称浏览器操作"
+ },
+ {
+ "kind": "unit",
+ "status": "PASS",
+ "runId": "scene-editor-2026-09-06T10:24:04.121Z",
+ "testTitle": "closing one selected scene checks its cached guard, leaves unrelated workspaces alone",
+ "featureIds": [
+ "leave.cancel",
+ "lifecycle.cache"
+ ],
+ "source": "tools/scene-tab-close.test.mjs",
+ "note": "实际执行真实方法/纯函数合同,未冒称浏览器操作"
+ },
+ {
+ "kind": "unit",
+ "status": "PASS",
+ "runId": "scene-editor-2026-09-06T10:24:04.121Z",
+ "testTitle": "cancel or failed save in a later tab never commits earlier draft discard",
+ "featureIds": [
+ "leave.cancel",
+ "leave.save",
+ "leave.role-switch"
+ ],
+ "source": "tools/scene-tab-close.test.mjs",
+ "note": "实际执行真实方法/纯函数合同,未冒称浏览器操作"
+ },
+ {
+ "kind": "unit",
+ "status": "PASS",
+ "runId": "scene-editor-2026-09-06T10:24:04.121Z",
+ "testTitle": "all successful confirmations commit together and unmounted guards are removed",
+ "featureIds": [
+ "leave.save",
+ "lifecycle.destroy"
+ ],
+ "source": "tools/scene-tab-close.test.mjs",
+ "note": "实际执行真实方法/纯函数合同,未冒称浏览器操作"
+ },
+ {
+ "kind": "unit",
+ "status": "PASS",
+ "runId": "scene-editor-2026-09-06T10:24:04.121Z",
+ "testTitle": "fixed model reads the exact version and downloads through its visible scene dependency",
+ "featureIds": [
+ "publish.fixed-model",
+ "permission.update"
+ ],
+ "source": "tools/scene-closure-assets.test.mjs",
+ "note": "实际执行真实方法/纯函数合同,未冒称浏览器操作"
+ },
+ {
+ "kind": "unit",
+ "status": "PASS",
+ "runId": "scene-editor-2026-09-06T10:24:04.121Z",
+ "testTitle": "an explicit missing code cannot fall back to the first READY asset",
+ "featureIds": [
+ "validation.references",
+ "json.missing-resource"
+ ],
+ "source": "tools/scene-closure-assets.test.mjs",
+ "note": "实际执行真实方法/纯函数合同,未冒称浏览器操作"
+ },
+ {
+ "kind": "unit",
+ "status": "PASS",
+ "runId": "scene-editor-2026-09-06T10:24:04.121Z",
+ "testTitle": "copied scene keeps its original pseudo URI but reads its own copied asset by stable code",
+ "featureIds": [
+ "glb.references"
+ ],
+ "source": "tools/scene-closure-assets.test.mjs",
+ "note": "实际执行真实方法/纯函数合同,未冒称浏览器操作"
+ },
+ {
+ "kind": "unit",
+ "status": "PASS",
+ "runId": "scene-editor-2026-09-06T10:24:04.121Z",
+ "testTitle": "a failed fixed-version lookup is evicted and the same reference can be retried",
+ "featureIds": [
+ "project.retry"
+ ],
+ "source": "tools/scene-closure-assets.test.mjs",
+ "note": "实际执行真实方法/纯函数合同,未冒称浏览器操作"
+ },
+ {
+ "kind": "unit",
+ "status": "PASS",
+ "runId": "scene-editor-2026-09-06T10:24:04.121Z",
+ "testTitle": "scene image ticket, fixed asset replacement, dependencies and coverUri commit in one versioned PUT",
+ "featureIds": [
+ "cover.atomic"
+ ],
+ "source": "tools/scene-cover-api.test.mjs",
+ "note": "实际执行真实方法/纯函数合同,未冒称浏览器操作"
+ },
+ {
+ "kind": "unit",
+ "status": "PASS",
+ "runId": "scene-editor-2026-09-06T10:24:04.121Z",
+ "testTitle": "capture failure saves content with the previous cover and reports a warning result",
+ "featureIds": [
+ "cover.failure"
+ ],
+ "source": "tools/scene-cover-api.test.mjs",
+ "note": "实际执行真实方法/纯函数合同,未冒称浏览器操作"
+ },
+ {
+ "kind": "unit",
+ "status": "PASS",
+ "runId": "scene-editor-2026-09-06T10:24:04.121Z",
+ "testTitle": "upload conflict propagates without any content PUT",
+ "featureIds": [
+ "project.conflict",
+ "cover.failure"
+ ],
+ "source": "tools/scene-cover-api.test.mjs",
+ "note": "实际执行真实方法/纯函数合同,未冒称浏览器操作"
+ },
+ {
+ "kind": "unit",
+ "status": "PASS",
+ "runId": "scene-editor-2026-09-06T10:24:04.121Z",
+ "testTitle": "role switch waits through validation, save, and the later queued publish after dirty clears",
+ "featureIds": [
+ "publish.concurrent",
+ "leave.busy",
+ "leave.role-switch"
+ ],
+ "source": "tools/scene-cover-api.test.mjs",
+ "note": "实际执行真实方法/纯函数合同,未冒称浏览器操作"
+ },
+ {
+ "kind": "unit",
+ "status": "PASS",
+ "runId": "scene-editor-2026-09-06T10:24:04.121Z",
+ "testTitle": "role switch rechecks resource uploads started while the save was finishing",
+ "featureIds": [
+ "leave.busy"
+ ],
+ "source": "tools/scene-cover-api.test.mjs",
+ "note": "实际执行真实方法/纯函数合同,未冒称浏览器操作"
+ },
+ {
+ "kind": "unit",
+ "status": "PASS",
+ "runId": "scene-editor-2026-09-06T10:24:04.121Z",
+ "testTitle": "copies synchronously without bounds, gizmo, grid or selection glow, then restores before JPEG encoding",
+ "featureIds": [
+ "cover.capture",
+ "selection.highlight",
+ "view.grid"
+ ],
+ "source": "tools/scene-cover-capture.test.mjs",
+ "note": "实际执行真实方法/纯函数合同,未冒称浏览器操作"
+ },
+ {
+ "kind": "unit",
+ "status": "PASS",
+ "runId": "scene-editor-2026-09-06T10:24:04.121Z",
+ "testTitle": "fresh skinned placement reaches the requested size and floor after authored scale/pose replay",
+ "featureIds": [
+ "assets.model-double-click",
+ "assets.model-drop"
+ ],
+ "source": "tools/scene-model-placement.test.mjs",
+ "note": "实际执行真实方法/纯函数合同,未冒称浏览器操作"
+ },
+ {
+ "kind": "unit",
+ "status": "PASS",
+ "runId": "scene-editor-2026-09-06T10:24:04.121Z",
+ "testTitle": "scale input accepts real GLTF normalization factors below 0.05",
+ "featureIds": [
+ "property.scale"
+ ],
+ "source": "tools/scene-model-placement.test.mjs",
+ "note": "实际执行真实方法/纯函数合同,未冒称浏览器操作"
+ },
+ {
+ "kind": "unit",
+ "status": "PASS",
+ "runId": "scene-editor-2026-09-06T10:24:04.121Z",
+ "testTitle": "fixed model dependencies are recursively collected, exact and de-duplicated",
+ "featureIds": [
+ "publish.fixed-model"
+ ],
+ "source": "../unreal_tran_web/src/features/scene-legacy/contentApiClient.test.mjs",
+ "note": "实际执行真实方法/纯函数合同,未冒称浏览器操作"
+ },
+ {
+ "kind": "unit",
+ "status": "PASS",
+ "runId": "scene-editor-2026-09-06T10:24:04.121Z",
+ "testTitle": "recovery conflict defaults to preserve and only explicit discard may remove it",
+ "featureIds": [
+ "recovery.discard"
+ ],
+ "source": "../unreal_tran_web/src/features/scene-legacy/contentApiClient.test.mjs",
+ "note": "实际执行真实方法/纯函数合同,未冒称浏览器操作"
+ },
+ {
+ "kind": "unit",
+ "status": "PASS",
+ "runId": "scene-editor-2026-09-06T10:24:04.121Z",
+ "testTitle": "per-page recovery keys prevent copied tabs from deleting each other checkpoints",
+ "featureIds": [
+ "recovery.multi-session"
+ ],
+ "source": "../unreal_tran_web/src/features/scene-legacy/contentApiClient.test.mjs",
+ "note": "实际执行真实方法/纯函数合同,未冒称浏览器操作"
+ },
+ {
+ "kind": "unit",
+ "status": "PASS",
+ "runId": "scene-editor-2026-09-06T10:24:04.121Z",
+ "testTitle": "project operation queue serializes upload, save and publish including failures",
+ "featureIds": [
+ "publish.concurrent"
+ ],
+ "source": "../unreal_tran_web/src/features/scene-legacy/contentApiClient.test.mjs",
+ "note": "实际执行真实方法/纯函数合同,未冒称浏览器操作"
+ },
+ {
+ "kind": "unit",
+ "status": "PASS",
+ "runId": "scene-editor-2026-09-06T10:24:04.121Z",
+ "testTitle": "an upload finishing after a project switch cannot register into the new session",
+ "featureIds": [
+ "glb.pending",
+ "lifecycle.destroy"
+ ],
+ "source": "../unreal_tran_web/src/features/scene-legacy/contentApiClient.test.mjs",
+ "note": "实际执行真实方法/纯函数合同,未冒称浏览器操作"
+ }
+ ],
+ "database": {
+ "ddlChanged": false,
+ "reason": "未新增或修改表结构;仅注册并清理测试副本、账号和角色。"
+ },
+ "logSha256": {
+ "web-build-final.log": "60909f032950078c3fc1e979de879a63626335d271a343f1eec3e9181c79a2bb",
+ "api-build.log": "b7b7dfccd66ffba22116a7f2d1e034b0ec608b8a33d9b57226ff91c330b6abcf",
+ "node-tests-final.log": "22f353c2a1c09f31c604fcaf7a4759cc2d652900e7090875b036b8aacc7ea56c",
+ "scene-api-tests-final.log": "e37ac6f5e235ae4699e9b26601f72717927decbda5d7df616f680d23e2945897"
+ }
+}
\ No newline at end of file
diff --git a/scene-editor-closure.config.ts b/scene-editor-closure.config.ts
new file mode 100644
index 0000000..5b4a237
--- /dev/null
+++ b/scene-editor-closure.config.ts
@@ -0,0 +1,11 @@
+import { defineConfig } from '@playwright/test'
+export default defineConfig({
+ testDir: './tests', testMatch: 'scene-editor-*closure.spec.ts',
+ outputDir: './reports/scene-editor-closure-20260906/test-results', workers: 1, fullyParallel: false,
+ retries: 0, timeout: 180_000, expect: { timeout: 25_000 },
+ reporter: [['list'], ['html', { outputFolder: 'reports/scene-editor-closure-20260906/playwright', open: 'never' }],
+ ['json', { outputFile: 'reports/scene-editor-closure-20260906/results.json' }]],
+ use: { baseURL: 'http://127.0.0.1:6180', browserName: 'chromium', channel: 'msedge', headless: true,
+ locale: 'zh-CN', timezoneId: 'Asia/Shanghai', viewport: { width: 1920, height: 1080 },
+ navigationTimeout: 60_000, actionTimeout: 25_000, screenshot: 'only-on-failure', trace: 'off', video: 'off' },
+})
diff --git a/tests/model-editor-assets-closure.spec.ts b/tests/model-editor-assets-closure.spec.ts
new file mode 100644
index 0000000..020156b
--- /dev/null
+++ b/tests/model-editor-assets-closure.spec.ts
@@ -0,0 +1,317 @@
+import fs from 'node:fs'
+import path from 'node:path'
+import { pathToFileURL } from 'node:url'
+import { expect, test, type Frame, type Page, type Route } from '@playwright/test'
+import {
+ call, closeSession, evidence, fixture, openEditor, readyFrame, report,
+ saveEditor, selectMesh, setRange, shot, signIn, work, writeFixture,
+} from './model-editor-closure-helpers'
+
+// Real copies, real uploads and real persistence. Only the two explicitly named
+// PUT failure cases intercept requests; originals 156/157 are never mutated.
+test.describe.configure({ mode: 'default' })
+test.setTimeout(240_000)
+const materials = path.resolve(work, '../../文档/素材')
+const downloads = path.join(report, 'downloads')
+const projectEndpoint = (id: string) => `tran/v1/content/projects/${id}`
+const controlledUri = /^(?:content:\/\/sha256\/[0-9a-f]{64}$|fsvc:\/\/[^/\s]+\/(?:model|image)\/)/
+const requestFailures = new WeakMap>>()
+
+test.beforeEach(async ({ page }) => {
+ const failures: Array> = []
+ requestFailures.set(page, failures)
+ page.on('response', response => {
+ if (response.status() >= 400) failures.push({ path: new URL(response.url()).pathname,
+ method: response.request().method(), status: response.status() })
+ })
+ page.on('requestfailed', request => failures.push({ path: new URL(request.url()).pathname,
+ method: request.method(), error: request.failure()?.errorText }))
+})
+
+test.afterEach(async ({ page }, info) => {
+ // Paths/statuses only: do not persist authorization headers, tokens or login bodies.
+ await info.attach('asset-closure-request-failures', { contentType: 'application/json',
+ body: Buffer.from(JSON.stringify({ status: info.status, failures: requestFailures.get(page) || [] }, null, 2)) })
+})
+
+async function readProject(token: string, id: string) {
+ const result = await call(projectEndpoint(id), token)
+ expect(result.status, result.message).toBe(200)
+ return { ...result.data, content: result.data.currentVersion.content, assets: result.data.currentVersion.assets }
+}
+
+async function isolatedProject(token: string, label: string) {
+ const data = fixture()
+ const sourceId = String(data.originals.fox.id)
+ const source = await readProject(token, sourceId)
+ const name = `闭环验收-${label}-${Date.now().toString(36)}`
+ const copied = await call(`${projectEndpoint(sourceId)}/copy`, token, { name, version: source.project.version })
+ expect(copied.status, copied.message).toBe(200)
+ const id = String(copied.data.project.id)
+ expect([sourceId, '156', '157']).not.toContain(id)
+ // Read again immediately before writing so another completed fixture is retained.
+ const latest = fixture()
+ latest.extraProjectIds = [...new Set([...(latest.extraProjectIds || []), id])]
+ latest.assetClosureModels = { ...(latest.assetClosureModels || {}), [label]: { id, name } }
+ writeFixture(latest)
+ return { id, name }
+}
+
+async function settle(frame: Frame) {
+ await frame.waitForFunction(() => {
+ const e = (window as any).editorApp
+ return e?.apiEditorHydrated && e.apiState === 'ready' && !e.apiImportBusy && !e.apiCoverSavePending
+ }, undefined, { timeout: 90_000 })
+}
+
+async function saveAndRead(page: Page, frame: Frame, token: string, id: string) {
+ const savedResponse = page.waitForResponse(response =>
+ response.request().method() === 'PUT' && new URL(response.url()).pathname.endsWith(`/content/projects/${id}`),
+ { timeout: 40_000 })
+ await saveEditor(frame)
+ expect((await savedResponse).status()).toBe(200)
+ await settle(frame)
+ return readProject(token, id)
+}
+
+async function snapshot(frame: Frame) {
+ return frame.evaluate(() => {
+ const d = (window as any).editorApp.serializeProject()
+ // Ignore camera damping between frames; compare all edited payload sections.
+ return { name: d.name, modelId: d.modelId, modelResource: d.modelResource, model: d.model,
+ environment: d.environment, timeline: d.timeline, blueprint: d.blueprint }
+ })
+}
+
+async function exportedJson(page: Page, frame: Frame, filename: string) {
+ fs.mkdirSync(downloads, { recursive: true })
+ const waiting = page.waitForEvent('download')
+ await frame.locator('[data-action="save-as"]').first().click()
+ const downloaded = await waiting
+ const target = path.join(downloads, filename)
+ await downloaded.saveAs(target)
+ return { target, document: JSON.parse(fs.readFileSync(target, 'utf8')) }
+}
+
+test('受控模型与外部环境 GLB 上传、JSON 往返和刷新重开', async ({ page }, info) => {
+ const token = await signIn(page)
+ try {
+ const model = await isolatedProject(token, '资源往返')
+ let frame = await openEditor(page, model.id)
+ await frame.locator('#model-file-input').setInputFiles(path.join(materials, '09-Fox.glb'))
+ await frame.waitForFunction(() => {
+ const e = (window as any).editorApp
+ return e.activeDescriptor?.sourceName === '09-Fox.glb' && e.apiState === 'ready'
+ && !e.apiImportBusy && !e.apiCoverSavePending
+ }, undefined, { timeout: 90_000 })
+ const main = await readProject(token, model.id)
+ const mainCode = main.content.modelResource.assetCode
+ expect(main.content.modelResource.storageUri).toMatch(controlledUri)
+ expect(main.assets.find((a: any) => a.assetCode === mainCode || a.code === mainCode)?.status).toBe('READY')
+
+ await frame.locator('#outdoor-file-input').setInputFiles(path.join(materials, '01-Box.glb'))
+ await frame.waitForFunction(() => {
+ const e = (window as any).editorApp
+ return e.outdoorSceneMeta?.sourceName === '01-Box.glb' && e.outdoorSceneMeta?.assetCode
+ && e.apiState === 'ready' && !e.apiImportBusy && !e.apiCoverSavePending
+ }, undefined, { timeout: 90_000 })
+ // This tiny sample is a solid cube, not a walkable environment. Position and
+ // scale its root with the UI so the screenshot can show both loaded assets.
+ const environmentId = await frame.evaluate(() => (window as any).editorApp.outdoorRoot.uuid)
+ await frame.locator(`[data-object-id="${environmentId}"]`).click()
+ for (const [vector, axis, value] of [['position', 'x', '6'], ['position', 'z', '-7'],
+ ['scale', 'x', '0.15'], ['scale', 'y', '0.15'], ['scale', 'z', '0.15']]) {
+ const input = frame.locator(`[data-vector="${vector}"][data-axis="${axis}"]`)
+ await input.fill(value)
+ await input.press('Tab')
+ }
+ await selectMesh(frame)
+ await frame.locator('[data-action="focus"]').first().click()
+ const saved = await saveAndRead(page, frame, token, model.id)
+ expect(saved.content.environment.mode).toBe('external')
+ expect(saved.content.environment.assetCode).toMatch(/^ENV_MODEL_/)
+ expect(saved.content.environment.storageUri).toMatch(controlledUri)
+ expect(saved.content.modelResource.assetCode).toBe(mainCode)
+ expect(saved.content.environment.resourceUrl).toBeUndefined()
+
+ const exported = await exportedJson(page, frame, 'assets-roundtrip.json')
+ expect(exported.document.environment.assetCode).toBe(saved.content.environment.assetCode)
+ expect(exported.document.modelResource.assetCode).toBe(mainCode)
+ await frame.locator('#project-file-input').setInputFiles(exported.target)
+ await expect(frame.locator('#toast-stack .toast.success').filter({ hasText: /项目.*导入成功/ }).last()).toBeVisible()
+ await settle(frame)
+ await page.reload()
+ frame = await readyFrame(page)
+ const restored = await frame.evaluate(() => {
+ const e = (window as any).editorApp
+ let modelMeshes = 0; let environmentMeshes = 0
+ e.modelPivot.traverse((o: any) => { if (o.isMesh) modelMeshes += 1 })
+ e.outdoorRoot.traverse((o: any) => { if (o.isMesh) environmentMeshes += 1 })
+ return { mainCode: e.activeDescriptor.assetCode, environment: e.outdoorSceneMeta,
+ environmentPosition: e.outdoorRoot.position.toArray(), environmentScale: e.outdoorRoot.scale.toArray(),
+ modelMeshes, environmentMeshes, nativeClips: e.nativeClips.length }
+ })
+ expect(restored.mainCode).toBe(mainCode)
+ expect(restored.environment.assetCode).toBe(saved.content.environment.assetCode)
+ expect(restored.environment.resourceUrl).toMatch(/^blob:/)
+ expect(restored.environmentPosition).toEqual([6, 0, -7])
+ expect(restored.environmentScale).toEqual([0.15, 0.15, 0.15])
+ expect(restored.modelMeshes).toBeGreaterThan(0)
+ expect(restored.environmentMeshes).toBeGreaterThan(0)
+ expect(restored.nativeClips).toBeGreaterThan(0)
+ await shot(page, info, '30-assets-environment-reopened')
+ evidence('30-assets-roundtrip', { status: 'PASS', kind: 'e2e+api', testTitle: info.title,
+ featureIds: ['project.import-glb', 'environment.external', 'project.import-json', 'project.export-json', 'project.save'], projectId: model.id, mainCode,
+ environmentCode: restored.environment.assetCode, mainUri: saved.content.modelResource.storageUri,
+ environmentUri: saved.content.environment.storageUri, modelMeshes: restored.modelMeshes,
+ environmentMeshes: restored.environmentMeshes, nativeClips: restored.nativeClips,
+ environmentPosition: restored.environmentPosition, environmentScale: restored.environmentScale,
+ result: 'PASS', scope: '真实上传、注册、保存、JSON重导入、刷新后的实际网格和原生动画目录;未断言下游场景执行动画。' })
+ } finally { await closeSession(page) }
+})
+
+test('非法 JSON 与未登记 blob 引用导入失败时保留当前文档', async ({ page }, info) => {
+ const token = await signIn(page)
+ try {
+ const model = await isolatedProject(token, '拒绝非法资源')
+ const frame = await openEditor(page, model.id)
+ const original = await snapshot(frame)
+ const server = await readProject(token, model.id)
+ const exported = await exportedJson(page, frame, 'invalid-import-baseline.json')
+ const invalid = { ...exported.document, modelId: 'unregistered-foreign-glb',
+ modelResource: { kind: 'imported', id: 'unregistered-foreign-glb', assetCode: '', code: '',
+ storageUri: '', assetKey: '', resourceUrl: 'blob:unregistered-foreign-page' } }
+ for (const input of [{ name: 'invalid-syntax.json', value: '{invalid-json' },
+ { name: 'unregistered-blob.json', value: JSON.stringify(invalid) }]) {
+ await frame.locator('#project-file-input').setInputFiles({ name: input.name,
+ mimeType: 'application/json', buffer: Buffer.from(input.value) })
+ await expect(frame.locator('#toast-stack .toast.error').filter({ hasText: '导入失败' }).last()).toBeVisible()
+ expect(await snapshot(frame)).toEqual(original)
+ const after = await readProject(token, model.id)
+ expect(after.project.version).toBe(server.project.version)
+ expect(after.content).toEqual(server.content)
+ }
+ await shot(page, info, '31-invalid-import-keeps-model')
+ evidence('31-invalid-import', { status: 'PASS', kind: 'e2e+api', testTitle: info.title, featureIds: ['project.import-json'], projectId: model.id, result: 'PASS',
+ cases: ['JSON语法错误', '无受控资源编号/地址的跨页面blob引用'], retainedVersion: server.project.version })
+ } finally { await closeSession(page) }
+})
+
+test('保存请求超时和 409 保留恢复副本,解除故障后可重试', async ({ page }, info) => {
+ const token = await signIn(page)
+ const intercepted: Array<{ mode: string; method: string; projectId: string }> = []
+ let handler: ((route: Route) => Promise) | null = null
+ let url = ''
+ try {
+ const model = await isolatedProject(token, '保存失败恢复')
+ const frame = await openEditor(page, model.id)
+ await selectMesh(frame)
+ url = `**/api/tran/v1/content/projects/${model.id}`
+ for (const mode of ['timedout', '409']) {
+ const baseline = await readProject(token, model.id)
+ handler = async route => {
+ if (route.request().method() !== 'PUT') return route.continue()
+ intercepted.push({ mode, method: 'PUT', projectId: model.id })
+ if (mode === 'timedout') await route.abort('timedout')
+ else await route.fulfill({ status: 409, contentType: 'application/json', body: JSON.stringify({
+ code: 409, message: '闭环验收:模拟乐观锁冲突,未执行写入', requestId: 'model-closure-fixture-conflict', data: null,
+ }) })
+ }
+ await page.route(url, handler)
+ await frame.locator('#object-name-input').fill(`闭环验收-保留编辑-${mode}`)
+ await frame.locator('#object-name-input').press('Tab')
+ const changed = await snapshot(frame)
+ await frame.locator('[data-action="save"]').first().click()
+ await frame.waitForFunction(expected => {
+ const e = (window as any).editorApp
+ return e.apiState === expected && !e.apiCoverSavePending
+ }, mode === '409' ? 'conflict' : 'error')
+ expect(await snapshot(frame)).toEqual(changed)
+ const recovery = await frame.evaluate(() => {
+ const e = (window as any).editorApp
+ const value = JSON.parse(localStorage.getItem(e.apiRecoveryKey) || 'null')
+ return { exists: !!value, version: value?.projectVersion, model: value?.document?.model }
+ })
+ expect(recovery.exists).toBe(true)
+ expect(recovery.model).toEqual(changed.model)
+ const afterFailure = await readProject(token, model.id)
+ expect(afterFailure.project.version).toBe(baseline.project.version)
+ expect(afterFailure.content).toEqual(baseline.content)
+ if (mode === '409') await shot(page, info, '32-save-conflict-keeps-recovery')
+ await page.unroute(url, handler); handler = null
+ const retried = await saveAndRead(page, frame, token, model.id)
+ expect(retried.project.version).toBeGreaterThan(baseline.project.version)
+ expect(retried.content.model).toEqual(changed.model)
+ }
+ await shot(page, info, '33-save-retry-success')
+ evidence('32-save-failure-recovery', { status: 'PASS', kind: 'e2e+api', testTitle: info.title, featureIds: ['project.conflict', 'project.save', 'feedback.toast'], projectId: model.id, result: 'PASS', intercepted,
+ scope: '真实页面保存及真实服务端重试;失败由浏览器定向注入 timedout/409,非宣称自然发生的服务故障。' })
+ } finally {
+ if (handler) await page.unroute(url, handler)
+ await closeSession(page)
+ }
+})
+
+test('直接发布同步截图封面、固定版本目录与真实离线步骤 HTML', async ({ page, context }, info) => {
+ const token = await signIn(page)
+ try {
+ const model = await isolatedProject(token, '发布封面版本')
+ const frame = await openEditor(page, model.id)
+ const baseline = await readProject(token, model.id)
+ const selected = await selectMesh(frame)
+ // The source may contain a deliberately hidden mesh. Make this independent
+ // copy visible through its actual tree control for the published-cover check.
+ if (!await frame.evaluate(() => (window as any).editorApp.selected.visible)) {
+ await frame.locator(`[data-object-id="${selected.uuid}"] .tree-eye`).click()
+ }
+ expect(await frame.evaluate(() => (window as any).editorApp.selected.visible)).toBe(true)
+ await setRange(frame, '#material-color', '#eb348c')
+ await frame.locator('[data-action="publish"]').first().click()
+ const publishing = page.waitForResponse(response => response.request().method() === 'POST'
+ && new URL(response.url()).pathname.endsWith(`/content/projects/${model.id}/publish`), { timeout: 60_000 })
+ await frame.locator('[data-action="publish-model-asset"]').click()
+ const response = await publishing
+ expect(response.status(), (await response.json()).message).toBe(200)
+ await settle(frame)
+ const published = await readProject(token, model.id)
+ expect(published.project.status).toBe('PUBLISHED')
+ expect(published.project.coverUri).toMatch(controlledUri)
+ expect(published.project.coverUri).not.toBe(baseline.project.coverUri)
+ const versionId = String(published.project.currentVersionId)
+ const catalog = await call(`tran/v1/content/catalog?relationType=SCENE_MODEL&size=100&keyword=${encodeURIComponent(published.project.name)}`, token)
+ expect(catalog.status, catalog.message).toBe(200)
+ const entry = catalog.data.records.find((item: any) => String(item.projectId) === model.id)
+ expect(entry).toBeTruthy()
+ expect(String(entry.versionId)).toBe(versionId)
+ const fixed = await call(`${projectEndpoint(model.id)}/versions/${versionId}`, token)
+ expect(fixed.status, fixed.message).toBe(200)
+ expect(fixed.data.content.modelResource.assetCode).toBe(published.content.modelResource.assetCode)
+ expect(fixed.data.assets.some((a: any) => (a.code || a.assetCode) === 'MODEL_COVER'
+ && a.storageUri === published.project.coverUri && a.status === 'READY')).toBe(true)
+ await shot(page, info, '34-published-current-cover')
+
+ fs.mkdirSync(downloads, { recursive: true })
+ const waiting = page.waitForEvent('download')
+ await frame.locator('[data-action="export-html"]').click()
+ const downloaded = await waiting
+ const file = path.join(downloads, 'model-offline-steps.html')
+ await downloaded.saveAs(file)
+ expect(fs.readFileSync(file, 'utf8')).toContain('本文件不运行三维模型、时间轴动画或蓝图分支')
+ const offline = await context.newPage()
+ try {
+ await offline.goto(pathToFileURL(file).href)
+ await expect(offline.locator('body')).toContainText('离线步骤演示')
+ await expect(offline.locator('body')).toContainText('不含三维模型和真实交互执行')
+ await expect(offline.locator('#play')).toContainText('播放步骤')
+ await offline.locator('#play').click()
+ await expect(offline.locator('#play')).toContainText('暂停步骤')
+ await offline.locator('#play').click()
+ await shot(offline, info, '35-offline-step-demo-actual-file')
+ } finally { await offline.close() }
+ evidence('34-publish-fixed-version', { status: 'PASS', kind: 'e2e+api', testTitle: info.title,
+ featureIds: ['publish.api', 'publish.cover', 'publish.html', 'publish.downstream'], projectId: model.id, result: 'PASS', versionId,
+ coverUri: published.project.coverUri, priorCoverUri: baseline.project.coverUri,
+ mainCode: published.content.modelResource.assetCode,
+ scope: '目录可取得固定发布版本及同版封面;HTML已真实打开并确认演示边界,未声明下游动画执行或完整离线三维运行。' })
+ } finally { await closeSession(page) }
+})
diff --git a/tests/model-editor-blueprint-closure.spec.ts b/tests/model-editor-blueprint-closure.spec.ts
new file mode 100644
index 0000000..3fda19f
--- /dev/null
+++ b/tests/model-editor-blueprint-closure.spec.ts
@@ -0,0 +1,636 @@
+import { test, expect, type Frame, type Locator, type Page } from '@playwright/test'
+import fs from 'node:fs'
+import path from 'node:path'
+import crypto from 'node:crypto'
+import { signIn, openEditor, readyFrame, selectMesh, setRange, saveEditor, shot, closeSession, fixture, evidence, call, work, writeFixture } from './model-editor-closure-helpers'
+
+// Real backend + real editor. This file owns only the copied truck fixture.
+// evaluate() reads editor state, except explicitly labelled runtime branch checks.
+// Auth state stays in the helper/browser; no trace or storageState is exported.
+test.describe.configure({ mode: 'serial' })
+
+type NodeIds = Record<'trigger' | 'play' | 'wait' | 'highlight' | 'message' | 'condition' | 'finish', string>
+let authoredGraph: NodeIds
+let targetAlias: string
+let authoredTrack: { id: string; middleId: string; time: number; targetId: string }
+
+const timeline = (frame: Frame) => frame.locator('#timeline-root')
+const blueprint = (frame: Frame) => frame.locator('#blueprint-root')
+const node = (frame: Frame, id: string) => blueprint(frame).locator(`.bp-node[data-node-id="${id}"]`)
+
+async function fillChanged(input: Locator, value: string) {
+ await input.fill(value)
+ await input.press('Tab')
+}
+
+async function activate(frame: Frame, name: string) {
+ await frame.locator(`[data-bottom-tab="${name}"]`).click()
+ await expect(frame.locator(`.editor-mode[data-mode="${name}"]`)).toHaveClass(/active/)
+}
+
+async function trackState(frame: Frame, id: string) {
+ return frame.evaluate(id => (window as any).editorApp.timeline.getTrack(id), id)
+}
+
+async function saveTruck(page: Page, frame: Frame, id = fixture().models.truck.id) {
+ expect([94, 156, 157]).not.toContain(Number(id))
+ const response = page.waitForResponse(response => response.request().method() === 'PUT' &&
+ new URL(response.url()).pathname.endsWith(`/content/projects/${id}`), { timeout: 90_000 })
+ await saveEditor(frame)
+ expect((await response).status()).toBe(200)
+ await expect.poll(() => frame.evaluate(() => {
+ const e = (window as any).editorApp
+ return e.apiState === 'ready' && !e.apiSession.savePending && !e.apiCoverSavePending
+ }), { timeout: 90_000 }).toBe(true)
+}
+
+async function bpField(frame: Frame, label: RegExp) {
+ return blueprint(frame).locator('.bp-field').filter({
+ has: frame.locator('.bp-field-label').filter({ hasText: label }),
+ }).locator('input,select,textarea')
+}
+
+async function fieldText(frame: Frame, label: RegExp, value: string) {
+ await fillChanged(await bpField(frame, label), value)
+}
+
+async function selectNode(frame: Frame, id: string) {
+ await node(frame, id).locator('.bp-node-header').click()
+ await expect(node(frame, id)).toHaveClass(/bp-selected/)
+}
+
+async function addNode(page: Page, frame: Frame, title: string, type: string, column: number, row: number) {
+ await blueprint(frame).locator('.bp-palette-item').filter({ hasText: title }).click()
+ const id = await frame.evaluate(() => [...(window as any).editorApp.blueprint.selection][0] as string)
+ expect(await frame.evaluate(id => (window as any).editorApp.blueprint.nodes.find((n: any) => n.id === id).type, id)).toBe(type)
+ const header = node(frame, id).locator('.bp-node-header')
+ const box = await header.boundingBox()
+ const viewport = await blueprint(frame).locator('.bp-viewport').boundingBox()
+ expect(box).not.toBeNull(); expect(viewport).not.toBeNull()
+ // Actual pointer drag. Arrange headers in a 3 x 3 grid, then use the editor's
+ // fit button before wiring. No synthetic graph coordinates are injected.
+ await page.mouse.move(box!.x + box!.width / 2, box!.y + box!.height / 2)
+ await page.mouse.down()
+ await page.mouse.move(viewport!.x + viewport!.width * (0.15 + column * 0.34),
+ viewport!.y + viewport!.height * (0.08 + row * 0.31), { steps: 10 })
+ await page.mouse.up()
+ await fieldText(frame, /^节点名称$/, `闭环-${title}`)
+ return id
+}
+
+async function connect(frame: Frame, from: string, port: string, to: string) {
+ const root = blueprint(frame)
+ await root.locator(`.bp-port-dot[data-node-id="${from}"][data-port-id="${port}"][data-direction="out"]`)
+ .dragTo(root.locator(`.bp-port-dot[data-node-id="${to}"][data-port-id="in"][data-direction="in"]`))
+ await expect.poll(() => frame.evaluate(({ from, port, to }) => (window as any).editorApp.blueprint.edges.some((e: any) =>
+ e.from.node === from && e.from.port === port && e.to.node === to), { from, port, to })).toBe(true)
+}
+
+async function graphState(frame: Frame) {
+ return frame.evaluate(() => {
+ const b = (window as any).editorApp.blueprint
+ return { nodes: b.nodes, edges: b.edges, running: b.running, logs: b.logs }
+ })
+}
+
+async function resetBlueprint(frame: Frame) {
+ await blueprint(frame).locator('[data-command="reset"]').click()
+ await expect.poll(() => frame.evaluate(() => (window as any).editorApp.blueprint.running)).toBe(false)
+}
+
+async function waitForFinished(frame: Frame) {
+ await expect.poll(async () => {
+ const state = await graphState(frame)
+ return !state.running && state.logs.some((entry: any) => entry.message.startsWith('蓝图执行完成'))
+ }, { timeout: 30_000 }).toBe(true)
+ expect((await graphState(frame)).logs.filter((entry: any) => entry.level === 'error')).toEqual([])
+}
+
+test.afterEach(async ({ page }) => { await closeSession(page) })
+
+test('时间轴真实控件:轨道/关键帧/播放与曲线 5 插值,保存后重新加载一致', async ({ page }, info) => {
+ test.setTimeout(240_000)
+ await signIn(page)
+ let frame = await openEditor(page, 'truck')
+ const mesh = await selectMesh(frame)
+ await activate(frame, 'timeline')
+ const root = timeline(frame)
+ const count = await frame.evaluate(() => (window as any).editorApp.timeline.tracks.length)
+ await root.locator('[data-action="add-track"]').click()
+ const disposable = await frame.evaluate(() => (window as any).editorApp.timeline.selectedTrackId)
+ await root.locator('[data-action="remove-track"]').click()
+ expect(await trackState(frame, disposable)).toBeNull()
+ expect(await frame.evaluate(() => (window as any).editorApp.timeline.tracks.length)).toBe(count)
+
+ await root.locator('[data-action="add-track"]').click()
+ const id = await frame.evaluate(() => (window as any).editorApp.timeline.selectedTrackId)
+ const options = await root.locator('[data-role="track-target"] option').evaluateAll(options => options.map(o => ({ value: (o as HTMLOptionElement).value, text: o.textContent })))
+ const targetId = options.find(o => o.value === mesh.id)?.value || options.find(o => o.text?.includes(mesh.name))?.value
+ expect(targetId, 'Selected real mesh must be offered by the target selector').toBeTruthy()
+ await root.locator('[data-role="track-target"]').selectOption(targetId!)
+ for (const property of ['rotation.x', 'rotation.y', 'rotation.z', 'position']) {
+ await root.locator('[data-role="track-property"]').selectOption(property)
+ expect((await trackState(frame, id)).property).toBe(property)
+ }
+ await root.locator('[data-role="loop"]').uncheck()
+ expect(await frame.evaluate(() => (window as any).editorApp.timeline.loop)).toBe(false)
+ await root.locator('[data-role="loop"]').check()
+ await setRange(frame, '#timeline-root [data-role="zoom"]', '0.5')
+ expect(await frame.evaluate(() => (window as any).editorApp.timeline.zoom)).toBe(0.5)
+
+ const lane = root.locator(`.timeline-lane[data-track-id="${id}"]`)
+ await lane.scrollIntoViewIfNeeded()
+ const laneBox = await lane.boundingBox()
+ expect(laneBox).not.toBeNull()
+ await lane.dblclick({ position: { x: laneBox!.width * 0.28, y: laneBox!.height / 2 } })
+ let state = await trackState(frame, id)
+ expect(state.keyframes).toHaveLength(3)
+ const middleId = state.keyframes[1].id
+ const beforeDrag = state.keyframes[1].time
+ const key = root.locator(`.timeline-keyframe[data-key-id="${middleId}"]`)
+ const keyBox = await key.boundingBox()
+ await page.mouse.move(keyBox!.x + keyBox!.width / 2, keyBox!.y + keyBox!.height / 2)
+ await page.mouse.down()
+ await page.mouse.move(laneBox!.x + laneBox!.width * 0.43, laneBox!.y + laneBox!.height / 2, { steps: 12 })
+ await page.mouse.up()
+ state = await trackState(frame, id)
+ expect(state.keyframes.find((k: any) => k.id === middleId).time).toBeGreaterThan(beforeDrag)
+
+ // A second temporary key exercises the actual delete button without deleting
+ // the authored key whose value/curve will be verified after a network reload.
+ await lane.dblclick({ position: { x: laneBox!.width * 0.65, y: laneBox!.height / 2 } })
+ expect((await trackState(frame, id)).keyframes).toHaveLength(4)
+ await root.locator('[data-action="delete"]').click()
+ expect((await trackState(frame, id)).keyframes).toHaveLength(3)
+ await root.locator('[data-action="stop"]').click()
+ await root.locator('[data-action="next"]').click()
+ expect(await frame.evaluate(() => (window as any).editorApp.timeline.currentTime)).toBeGreaterThan(0)
+ await root.locator('[data-action="previous"]').click()
+ expect(await frame.evaluate(() => (window as any).editorApp.timeline.currentTime)).toBe(0)
+ await root.locator('[data-action="play"]').click()
+ await expect.poll(() => frame.evaluate(() => (window as any).editorApp.timeline.currentTime)).toBeGreaterThan(0.1)
+ await root.locator('[data-action="pause"]').click()
+ expect(await frame.evaluate(() => (window as any).editorApp.timeline.playing)).toBe(false)
+ await root.locator(`[data-track-id="${id}"] [data-action="toggle-mute"]`).click()
+ expect((await trackState(frame, id)).muted).toBe(true)
+ expect(await frame.evaluate(id => Object.hasOwn((window as any).editorApp.timeline.evaluate(), id), id)).toBe(false)
+ await root.locator(`[data-track-id="${id}"] [data-action="toggle-mute"]`).click()
+ await root.locator('[data-action="stop"]').click()
+ expect(await frame.evaluate(() => (window as any).editorApp.timeline.currentTime)).toBe(0)
+ await shot(page, info, '21-truck-timeline-controls')
+
+ await activate(frame, 'curve')
+ await frame.locator(`[data-curve-track="${id}"]`).click()
+ await frame.locator(`[data-curve-key="${middleId}"]`).click()
+ const duration = await frame.evaluate(() => (window as any).editorApp.timeline.duration)
+ const time = Math.min(2, duration / 2)
+ await fillChanged(frame.locator('[data-curve-field="time"]'), String(time))
+ await fillChanged(frame.locator('[data-curve-field="value"][data-curve-component="0"]'), '5')
+ const samples: Record = {}
+ for (const easing of ['linear', 'easeInOut', 'easeIn', 'easeOut', 'step']) {
+ await frame.locator('[data-curve-field="easing"]').selectOption(easing)
+ expect((await trackState(frame, id)).keyframes.find((k: any) => k.id === middleId).easing).toBe(easing)
+ samples[easing] = await frame.evaluate(({ id, time }) => (window as any).editorApp.timeline.evaluateTrack(id, time / 4)[0], { id, time })
+ }
+ expect(samples.linear).toBeCloseTo(1.25)
+ expect(samples.easeIn).toBeLessThan(samples.linear)
+ expect(samples.easeOut).toBeGreaterThan(samples.linear)
+ expect(samples.step).toBe(0)
+ await frame.locator('[data-curve-field="easing"]').selectOption('easeInOut')
+ await frame.locator('[data-curve-axis]').selectOption('1')
+ await frame.locator('[data-curve-axis]').selectOption('0')
+ await shot(page, info, '22-truck-curve-value-five')
+ await saveTruck(page, frame)
+ await page.reload()
+ frame = await readyFrame(page)
+ const restored = await trackState(frame, id)
+ expect(restored.targetId).toBe(targetId)
+ expect(restored.property).toBe('position')
+ expect(restored.muted).toBe(false)
+ expect(restored.keyframes).toHaveLength(3)
+ expect(restored.keyframes.find((k: any) => k.id === middleId)).toMatchObject({ time, value: [5, 0, 0], easing: 'easeInOut' })
+ expect(await frame.evaluate(() => (window as any).editorApp.timeline.loop)).toBe(true)
+ expect(await frame.evaluate(() => (window as any).editorApp.timeline.zoom)).toBe(0.5)
+ authoredTrack = { id, middleId, time, targetId: targetId! }
+ evidence('truck-timeline-curve', { status: 'PASS', kind: 'e2e+runtime-read', testTitle: info.title,
+ featureIds: ['timeline.play', 'timeline.stop', 'timeline.loop', 'timeline.key-nav', 'timeline.add-track', 'timeline.remove-track', 'timeline.target', 'timeline.property', 'timeline.mute', 'timeline.add-key', 'timeline.delete-key', 'timeline.drag-key', 'timeline.zoom', 'timeline.curve', 'curve.time', 'curve.value', 'curve.easing'],
+ scope: '真实控件和指针操作,运行时读取数值校验;curve.axis仅切换无数值独立断言,timeline.seek播放头与Alt吸附未在本例覆盖。', projectId: fixture().models.truck.id, actions: '真实 DOM 轨道/帧增删、关键帧指针拖移、播放控件、曲线输入和保存重开', restored, interpolationSamples: samples, excluded: ['未新增 solo 能力', '未将原生 GLB 动画片段当成多条编辑时间轴'] })
+})
+
+test('补充时间轴与蓝图工具:刻度/按键/记录/分量、基准匹配、节点搜索拖入与画布平移', async ({ page }, info) => {
+ test.setTimeout(480_000)
+ const token = await signIn(page)
+ const copy = async (sourceId: string, label: string) => {
+ const original = await call(`tran/v1/content/projects/${sourceId}`, token)
+ expect(original.status).toBe(200)
+ if (sourceId === '94' && !fixture().originals.bridge) {
+ fs.writeFileSync(path.join(work, 'original-bridge.json'), JSON.stringify(original.data, null, 2))
+ const data = fixture()
+ data.originals.bridge = { id: sourceId, name: original.data.project.name, version: original.data.project.version,
+ hash: crypto.createHash('sha256').update(JSON.stringify(original.data)).digest('hex') }
+ writeFixture(data)
+ }
+ const result = await call(`tran/v1/content/projects/${sourceId}/copy`, token, {
+ name: `闭环工具补充-${label}-${Date.now().toString(36)}`, version: original.data.project.version,
+ })
+ expect(result.status, result.message).toBe(200)
+ const id = String(result.data.project.id)
+ expect(['94', '156', '157']).not.toContain(id)
+ const data = fixture()
+ data.extraProjectIds = [...new Set([...(data.extraProjectIds || []), id])]
+ data.blueprintToolCopies = { ...(data.blueprintToolCopies || {}), [label]: id }
+ writeFixture(data)
+ return id
+ }
+ const id = await copy('156', '卡车控件')
+ let frame = await openEditor(page, id)
+ const mesh = await selectMesh(frame)
+ await activate(frame, 'timeline')
+ await timeline(frame).locator('[data-action="stop"]').click()
+ await timeline(frame).locator('[data-action="add-track"]').click()
+ const trackId = await frame.evaluate(() => (window as any).editorApp.timeline.selectedTrackId)
+ const targetOptions = await timeline(frame).locator('[data-role="track-target"] option').evaluateAll(options => options.map(o => ({ value: (o as HTMLOptionElement).value, text: o.textContent })))
+ const targetOption = targetOptions.find(o => o.value === mesh.id) || targetOptions.find(o => o.text?.includes(mesh.name))
+ expect(targetOption).toBeTruthy()
+ await timeline(frame).locator('[data-role="track-target"]').selectOption(targetOption!.value)
+ await timeline(frame).locator('[data-role="track-property"]').selectOption('position')
+ await setRange(frame, '#timeline-root [data-role="zoom"]', '0.5')
+ const lane = timeline(frame).locator(`.timeline-lane[data-track-id="${trackId}"]`)
+ const laneBox = await lane.boundingBox()
+ await lane.dblclick({ position: { x: laneBox!.width / 4, y: laneBox!.height / 2 } })
+ const middle = (await trackState(frame, trackId)).keyframes[1]
+ await activate(frame, 'curve')
+ await frame.locator(`[data-curve-track="${trackId}"]`).click()
+ await frame.locator(`[data-curve-key="${middle.id}"]`).click()
+ await fillChanged(frame.locator('[data-curve-field="time"]'), '2')
+ await fillChanged(frame.locator('[data-curve-field="value"][data-curve-component="0"]'), '5')
+ await fillChanged(frame.locator('[data-curve-field="value"][data-curve-component="1"]'), '-2')
+ await frame.locator('[data-curve-field="easing"]').selectOption('linear')
+ const xPath = await frame.locator('.curve-path').getAttribute('points')
+ await frame.locator('[data-curve-axis]').selectOption('1')
+ const yPath = await frame.locator('.curve-path').getAttribute('points')
+ expect(yPath).not.toEqual(xPath)
+ expect((await trackState(frame, trackId)).keyframes.find((k: any) => k.id === middle.id).value).toEqual([5, -2, 0])
+ await expect(frame.locator('.curve-label')).toContainText('-2.000')
+ await shot(page, info, '28-curve-axis-y-negative')
+ await frame.locator('[data-curve-axis]').selectOption('0')
+ expect(await frame.locator('.curve-path').getAttribute('points')).toBe(xPath)
+
+ await activate(frame, 'timeline')
+ const ruler = timeline(frame).locator('[data-role="ruler"]')
+ const rulerBox = await ruler.boundingBox()
+ await ruler.click({ position: { x: rulerBox!.width / 8, y: rulerBox!.height / 2 } })
+ const initialTime = await frame.evaluate(() => (window as any).editorApp.timeline.currentTime)
+ expect(initialTime).toBeCloseTo(1, 1)
+ await timeline(frame).locator('[data-action="pause"]').focus()
+ await page.keyboard.press('Shift+ArrowRight')
+ expect(await frame.evaluate(() => (window as any).editorApp.timeline.currentTime)).toBeCloseTo(initialTime + 1, 5)
+ await page.keyboard.press('ArrowLeft')
+ expect(await frame.evaluate(() => (window as any).editorApp.timeline.currentTime)).toBeCloseTo(initialTime + 1 - 1 / 30, 5)
+ await page.keyboard.press('Shift+ArrowLeft')
+ const pose = await frame.evaluate(trackId => {
+ const e = (window as any).editorApp, track = e.timeline.getTrack(trackId), target = e.modelTargets.get(track.targetId)
+ return { time: e.timeline.currentTime, value: e.timeline.evaluateTrack(trackId), actual: target.position.toArray(), base: target.userData.timelineBase.position }
+ }, trackId)
+ expect(pose.time).toBeCloseTo(initialTime - 1 / 30, 5)
+ pose.actual.forEach((value: number, i: number) => expect(value - pose.base[i]).toBeCloseTo(pose.value[i], 5))
+ await expect(timeline(frame).locator('[data-role="timecode"]')).not.toContainText('00:00.000 /')
+ await frame.locator('[data-action="add-key"]').click()
+ const recorded = (await trackState(frame, trackId)).keyframes.find((k: any) => Math.abs(k.time - pose.time) < 0.00001)
+ expect(recorded).toBeTruthy()
+ recorded.value.forEach((value: number, i: number) => expect(value).toBeCloseTo(pose.value[i], 5))
+ expect((await trackState(frame, trackId)).keyframes).toHaveLength(4)
+ await shot(page, info, '29-timeline-seek-record')
+
+ const beforeNoMatch = await frame.evaluate(() => ({ timeline: (window as any).editorApp.timeline.toJSON(), blueprint: (window as any).editorApp.blueprint.toJSON() }))
+ await frame.locator('[data-action="load-baseline-animation"]').click()
+ await expect(frame.locator('.toast').filter({ hasText: '没有可自动匹配的基准动作' })).toBeVisible()
+ expect(await frame.evaluate(() => ({ timeline: (window as any).editorApp.timeline.toJSON(), blueprint: (window as any).editorApp.blueprint.toJSON() }))).toEqual(beforeNoMatch)
+
+ await activate(frame, 'blueprint')
+ const bp = blueprint(frame)
+ await bp.locator('.bp-search-input').fill('条件判断')
+ await expect(bp.locator('.bp-palette-item')).toHaveCount(1)
+ await expect(bp.locator('.bp-palette-item')).toContainText('条件判断')
+ await bp.locator('.bp-search-input').fill('')
+ await expect(bp.locator('.bp-palette-item')).toHaveCount(7)
+ const beforeAdd = await graphState(frame)
+ const historyIndex = await frame.evaluate(() => (window as any).editorApp.history.index)
+ await bp.locator('[data-command="add"]').click()
+ const added = (await graphState(frame)).nodes.filter((n: any) => !beforeAdd.nodes.some((old: any) => old.id === n.id))
+ expect(added).toHaveLength(1)
+ expect(added[0].type).toBe('message')
+ expect(await frame.evaluate(() => (window as any).editorApp.history.index)).toBeGreaterThan(historyIndex)
+ const viewport = bp.locator('.bp-viewport'), viewportBox = await viewport.boundingBox()
+ await bp.locator('.bp-palette-item').filter({ hasText: /^◷等待/ }).dragTo(viewport, {
+ targetPosition: { x: viewportBox!.width * 0.1, y: viewportBox!.height * 0.15 },
+ })
+ const dragged = (await graphState(frame)).nodes.filter((n: any) => !beforeAdd.nodes.some((old: any) => old.id === n.id) && n.id !== added[0].id)
+ expect(dragged).toHaveLength(1)
+ expect(dragged[0].type).toBe('wait')
+ const graphBeforeCanvas = await frame.evaluate(() => {
+ const b = (window as any).editorApp.blueprint
+ return { nodes: b.nodes, edges: b.edges, viewport: b.viewport }
+ })
+ const blank = await viewport.evaluate(element => {
+ const rect = element.getBoundingClientRect()
+ for (const x of [0.9, 0.1, 0.5]) for (const y of [0.85, 0.65, 0.25]) {
+ const hit = document.elementFromPoint(rect.left + rect.width * x, rect.top + rect.height * y)
+ if (hit && !hit.closest('.bp-node,.bp-edge')) return { x: rect.width * x, y: rect.height * y }
+ }
+ return null
+ })
+ expect(blank).not.toBeNull()
+ await bp.focus()
+ await page.keyboard.down('Space')
+ await page.mouse.move(viewportBox!.x + blank!.x, viewportBox!.y + blank!.y)
+ await page.mouse.down()
+ await page.mouse.move(viewportBox!.x + blank!.x - 45, viewportBox!.y + blank!.y - 25, { steps: 8 })
+ await page.mouse.up()
+ await page.keyboard.up('Space')
+ const afterPan = await frame.evaluate(() => (window as any).editorApp.blueprint.viewport)
+ expect(afterPan.x).toBeCloseTo(graphBeforeCanvas.viewport.x - 45, 1)
+ expect(afterPan.y).toBeCloseTo(graphBeforeCanvas.viewport.y - 25, 1)
+ await page.mouse.wheel(0, -120)
+ await expect.poll(() => frame.evaluate(() => (window as any).editorApp.blueprint.viewport.zoom)).toBeGreaterThan(afterPan.zoom)
+ await bp.locator('[data-command="frame"]').click()
+ const graphAfterCanvas = await graphState(frame)
+ expect(graphAfterCanvas.nodes).toEqual(graphBeforeCanvas.nodes)
+ expect(graphAfterCanvas.edges).toEqual(graphBeforeCanvas.edges)
+ await shot(page, info, '30-blueprint-palette-canvas')
+ await saveTruck(page, frame, id)
+ await page.reload(); frame = await readyFrame(page)
+ expect((await trackState(frame, trackId)).keyframes.find((k: any) => k.id === recorded.id)).toEqual(recorded)
+ expect((await graphState(frame)).nodes.some((n: any) => n.id === added[0].id)).toBe(true)
+ expect((await graphState(frame)).nodes.some((n: any) => n.id === dragged[0].id)).toBe(true)
+
+ const bridgeId = await copy('94', '基准匹配')
+ frame = await openEditor(page, bridgeId)
+ const identity = await frame.evaluate(() => ({ name: (window as any).editorApp.projectName, model: (window as any).editorApp.activeDescriptor.id }))
+ await frame.locator('[data-action="focus"]').first().click()
+ await activate(frame, 'timeline')
+ // Source 94 already contains the generated baseline. Persist a real extra
+ // track first, so reloading the baseline exercises a changed server draft
+ // instead of correctly taking the unchanged-save fast path.
+ await timeline(frame).locator('[data-action="add-track"]').click()
+ const replacedTrack = await frame.evaluate(() => (window as any).editorApp.timeline.selectedTrackId)
+ await saveTruck(page, frame, bridgeId)
+ const savedBaseline = page.waitForResponse(response => response.request().method() === 'PUT'
+ && new URL(response.url()).pathname.endsWith(`/content/projects/${bridgeId}`), { timeout: 120_000 })
+ await frame.locator('[data-action="load-baseline-animation"]').click()
+ expect((await savedBaseline).status()).toBe(200)
+ await expect.poll(() => frame.evaluate(() => !(window as any).editorApp.apiCoverSavePending)).toBe(true)
+ const baseline = await frame.evaluate(() => {
+ const e = (window as any).editorApp
+ return { name: e.projectName, timeline: e.timeline.toJSON(), resolved: e.timeline.tracks.every((t: any) => e.modelTargets.has(t.targetId)) }
+ })
+ expect(baseline.name).toBe(identity.name)
+ expect(baseline.timeline.tracks.length).toBeGreaterThan(1)
+ expect(baseline.timeline.tracks.some((t: any) => t.id === replacedTrack)).toBe(false)
+ expect(baseline.resolved).toBe(true)
+ await page.reload(); frame = await readyFrame(page)
+ expect(await frame.evaluate(() => (window as any).editorApp.timeline.toJSON())).toEqual(baseline.timeline)
+ await shot(page, info, '31-bridge-baseline-reopened')
+ evidence('truck-blueprint-tool-supplement', { status: 'PASS', kind: 'e2e+runtime-read', testTitle: info.title,
+ featureIds: ['timeline.seek', 'timeline.record', 'timeline.baseline', 'curve.axis', 'blueprint.palette', 'blueprint.add', 'blueprint.canvas'],
+ projectIds: [id, bridgeId], pose, recorded, curveAxis: { xAndYPathsDiffer: xPath !== yPath, values: [5, -2, 0] },
+ noMatchPreserved: true, baseline, addedNode: added[0], draggedNode: dragged[0], canvasPreservedGraph: true,
+ scope: '真实模型编辑参数/基准轨道生成验收,不构成装备操作或维修指导。桥梁原工程94仅GET和copy。' })
+})
+
+test('蓝图真实节点库与端口拖线:七种参数、内部边复制/剪切、撤销重做及保存', async ({ page }, info) => {
+ test.setTimeout(300_000)
+ await signIn(page)
+ const frame = await openEditor(page, 'truck')
+ const mesh = await selectMesh(frame)
+ targetAlias = mesh.id || mesh.name
+ expect(targetAlias).toBeTruthy()
+ await activate(frame, 'timeline')
+ await timeline(frame).locator('[data-action="stop"]').click()
+ await activate(frame, 'blueprint')
+ const root = blueprint(frame)
+ await root.focus()
+ await page.keyboard.press('Control+a')
+ await page.keyboard.press('Delete')
+ expect((await graphState(frame)).nodes).toHaveLength(0)
+ await expect(root.locator('.bp-empty-hint')).toBeVisible()
+ const previousZoom = await frame.evaluate(() => (window as any).editorApp.blueprint.viewport.zoom)
+ await root.locator('.bp-viewport').hover()
+ // 80% leaves a visible gap between the three 210px nodes in this viewport.
+ await page.mouse.wheel(0, Math.log(previousZoom / 0.8) / 0.0012)
+ await expect.poll(() => frame.evaluate(() => Math.round((window as any).editorApp.blueprint.viewport.zoom * 1000))).toBe(800)
+ const ids = {} as NodeIds
+ ids.trigger = await addNode(page, frame, '点击触发', 'trigger-click', 0, 0)
+ await (await bpField(frame, /^事件$/)).selectOption('click')
+ await fieldText(frame, /^目标对象$/, targetAlias)
+ ids.play = await addNode(page, frame, '播放时间轴', 'play-timeline', 1, 0)
+ await fieldText(frame, /^(动作名称|动画片段)/, '牛奶卡车曲线联动')
+ const duration = await frame.evaluate(() => (window as any).editorApp.timeline.duration)
+ await fieldText(frame, /^起始时间$/, String(duration))
+ await fieldText(frame, /^播放速度$/, '2')
+ await (await bpField(frame, /^等待播完$/)).check()
+ ids.wait = await addNode(page, frame, '等待', 'wait', 2, 0)
+ await fieldText(frame, /^时长/, '100')
+ ids.highlight = await addNode(page, frame, '高亮部件', 'highlight', 0, 1)
+ await fieldText(frame, /^目标对象$/, targetAlias)
+ await (await bpField(frame, /^高亮颜色$/)).evaluate(input => {
+ (input as HTMLInputElement).value = '#ff8800'
+ input.dispatchEvent(new Event('input', { bubbles: true }))
+ input.dispatchEvent(new Event('change', { bubbles: true }))
+ })
+ await (await bpField(frame, /^呼吸效果$/)).uncheck()
+ ids.message = await addNode(page, frame, '操作提示', 'message', 1, 1)
+ await fieldText(frame, /^标题$/, '卡车模型闭环提示')
+ await fieldText(frame, /^提示内容$/, '检查真实目标高亮、时间轴完成和条件出口。')
+ await (await bpField(frame, /^级别$/)).selectOption('success')
+ ids.condition = await addNode(page, frame, '条件判断', 'condition', 2, 1)
+ await fieldText(frame, /^变量$/, 'timelineComplete')
+ await (await bpField(frame, /^运算符$/)).selectOption('==')
+ await fieldText(frame, /^比较值$/, 'true')
+ ids.finish = await addNode(page, frame, '流程完成', 'finish', 1, 2)
+ await (await bpField(frame, /^结果$/)).selectOption('success')
+ await fieldText(frame, /^完成提示$/, '牛奶卡车蓝图验证完成')
+ await root.locator('[data-command="frame"]').click()
+ await connect(frame, ids.trigger, 'then', ids.play)
+ await connect(frame, ids.play, 'then', ids.wait)
+ await connect(frame, ids.wait, 'then', ids.highlight)
+ await connect(frame, ids.highlight, 'then', ids.message)
+ await connect(frame, ids.message, 'then', ids.condition)
+ await connect(frame, ids.condition, 'true', ids.finish)
+ expect((await graphState(frame)).edges).toHaveLength(6)
+ await expect(root.locator('.bp-empty-hint')).toBeHidden()
+ expect(await root.locator('.bp-empty-hint').evaluate(element => getComputedStyle(element).display)).toBe('none')
+ const parameters = (await graphState(frame)).nodes
+ const params = (id: string) => parameters.find((n: any) => n.id === id).params
+ expect(params(ids.trigger)).toMatchObject({ event: 'click', target: targetAlias })
+ expect(params(ids.play)).toMatchObject({ clip: '牛奶卡车曲线联动', from: duration, speed: 2, waitForEnd: true })
+ expect(params(ids.wait)).toMatchObject({ duration: 100 })
+ expect(params(ids.highlight)).toMatchObject({ target: targetAlias, color: '#ff8800', pulse: false })
+ expect(params(ids.message)).toMatchObject({ title: '卡车模型闭环提示', text: '检查真实目标高亮、时间轴完成和条件出口。', level: 'success' })
+ expect(params(ids.condition)).toMatchObject({ variable: 'timelineComplete', operator: '==', value: true })
+ expect(params(ids.finish)).toMatchObject({ result: 'success', text: '牛奶卡车蓝图验证完成' })
+ await shot(page, info, '23-truck-blueprint-seven-nodes')
+
+ // Clipboard is driven by real shortcuts scoped to the blueprint. All seven
+ // copied nodes must keep their six internal edges, with fresh endpoint IDs.
+ await root.focus()
+ await page.keyboard.press('Control+a')
+ await page.keyboard.press('Control+c')
+ await page.keyboard.press('Control+v')
+ const copied = await graphState(frame)
+ expect(copied.nodes).toHaveLength(14)
+ expect(copied.edges).toHaveLength(12)
+ const copyIds = copied.nodes.filter((n: any) => !Object.values(ids).includes(n.id)).map((n: any) => n.id)
+ expect(copied.edges.filter((e: any) => copyIds.includes(e.from.node) && copyIds.includes(e.to.node))).toHaveLength(6)
+ await page.keyboard.press('Control+x')
+ expect((await graphState(frame)).nodes).toHaveLength(7)
+ await page.keyboard.press('Control+v')
+ expect((await graphState(frame)).edges).toHaveLength(12)
+ await page.keyboard.press('Delete')
+ expect((await graphState(frame)).nodes).toHaveLength(7)
+ expect((await graphState(frame)).edges).toHaveLength(6)
+ await frame.locator('[data-action="undo"]').first().click()
+ await expect.poll(() => frame.evaluate(() => ({ count: (window as any).editorApp.blueprint.nodes.length, locked: (window as any).editorApp.history.locked })), { timeout: 60_000 }).toEqual({ count: 14, locked: false })
+ await frame.locator('[data-action="redo"]').first().click()
+ await expect.poll(() => frame.evaluate(() => ({ count: (window as any).editorApp.blueprint.nodes.length, locked: (window as any).editorApp.history.locked })), { timeout: 60_000 }).toEqual({ count: 7, locked: false })
+ await root.locator('[data-command="frame"]').click()
+
+ await root.locator('[data-command="run"]').click()
+ await expect(frame.locator('[data-modal-close]')).toBeVisible()
+ await shot(page, info, '24-truck-blueprint-real-message')
+ await frame.locator('[data-modal-close]').click()
+ await waitForFinished(frame)
+ const completed = await graphState(frame)
+ for (const id of Object.values(ids)) expect(completed.logs.some((l: any) => l.nodeId === id && l.message.startsWith('执行节点'))).toBe(true)
+ expect(completed.logs.some((l: any) => l.message.includes('TRUE'))).toBe(true)
+ await shot(page, info, '25-truck-blueprint-completed-log')
+
+ // A long real wait allows a deterministic toolbar cancellation assertion.
+ await selectNode(frame, ids.wait)
+ await fieldText(frame, /^时长/, '30000')
+ await root.locator('[data-command="run"]').click()
+ await expect.poll(() => frame.evaluate(() => (window as any).editorApp.blueprint.activeNodeId)).toBe(ids.wait)
+ await root.locator('[data-command="stop"]').click()
+ expect((await graphState(frame)).running).toBe(false)
+ expect((await graphState(frame)).logs.some((l: any) => l.message.includes('已停止'))).toBe(true)
+ await resetBlueprint(frame)
+ expect(await frame.evaluate(() => (window as any).editorApp.timeline.currentTime)).toBe(0)
+ expect(await frame.evaluate(() => (window as any).editorApp.activeHighlightMaterials.size)).toBe(0)
+ await selectNode(frame, ids.wait)
+ await fieldText(frame, /^时长/, '100')
+ await saveTruck(page, frame)
+ authoredGraph = ids
+ evidence('truck-blueprint-ui', { status: 'PASS', kind: 'e2e+runtime-read', testTitle: info.title,
+ featureIds: ['blueprint.parameters', 'blueprint.move', 'blueprint.connect', 'blueprint.delete', 'blueprint.copy', 'blueprint.run', 'blueprint.stop', 'blueprint.reset', 'blueprint.timeline', 'blueprint.wait', 'blueprint.highlight', 'blueprint.message', 'blueprint.finish', 'project.undo', 'project.redo'],
+ scope: '节点库点选添加及实际拖动/接线/执行;不把点选当成节点拖入,不把适应画布当成鼠标平移缩放全部已验。', projectId: fixture().models.truck.id, graph: await graphState(frame), completedLog: completed.logs,
+ actions: '真实节点库/参数控件/端口拖线/复制剪切粘贴/删除/撤销重做/运行停止重置/保存',
+ boundaries: '200 步循环保护由独立 Node 回归覆盖;本浏览器流程不注入假图或替换执行 handler。' })
+})
+
+test('已保存蓝图:条件出口与目标匹配 runtime 检查,真实模型单击/双击/进入预览自动触发', async ({ page }, info) => {
+ test.setTimeout(240_000)
+ await signIn(page)
+ const frame = await openEditor(page, 'truck')
+ const ids = authoredGraph
+ expect(ids).toBeTruthy()
+ const restored = await graphState(frame)
+ expect(restored.nodes.map((n: any) => n.id).sort()).toEqual(Object.values(ids).sort())
+ expect(restored.edges).toHaveLength(6)
+ if (authoredTrack) expect((await trackState(frame, authoredTrack.id)).keyframes.find((k: any) => k.id === authoredTrack.middleId).value[0]).toBe(5)
+ await activate(frame, 'blueprint')
+ const root = blueprint(frame)
+ await root.locator('[data-command="frame"]').click()
+ await resetBlueprint(frame)
+
+ // Explicit runtime-only assertions on the graph authored with UI in test 2.
+ // They supplement, rather than stand in for, the canvas actions below.
+ const branches = await frame.evaluate(async ({ condition, finish }) => {
+ const b = (window as any).editorApp.blueprint
+ const one = async (value: boolean) => {
+ const start = b.logs.length
+ const result = await b.run({ startNodeId: condition, variables: { timelineComplete: value } })
+ const logs = b.logs.slice(start)
+ return { result, visitedFinish: logs.some((l: any) => l.nodeId === finish && l.message.startsWith('执行节点')), logs }
+ }
+ return { falseBranch: await one(false), trueBranch: await one(true) }
+ }, { condition: ids.condition, finish: ids.finish })
+ expect(branches.falseBranch.result).toBe(true)
+ expect(branches.falseBranch.visitedFinish).toBe(false)
+ expect(branches.trueBranch.visitedFinish).toBe(true)
+ const mismatch = await frame.evaluate(async () => {
+ const b = (window as any).editorApp.blueprint, count = b.logs.length
+ const result = await b.trigger('click', { object: '__different_real_target__' })
+ return { result, started: b.logs.slice(count).some((l: any) => l.message.startsWith('开始执行')) }
+ })
+ expect(mismatch.started).toBe(false)
+ await shot(page, info, '26-truck-condition-branches-runtime')
+
+ // Bind to the actual model root alias so any visible child pixel can trigger
+ // it. The pointer locations are found by read-only raycasting; the real
+ // renderer's pointer handlers produce the payload (no fake trigger dispatch).
+ const rootAlias = await frame.evaluate(() => {
+ const model = (window as any).editorApp.modelPivot
+ return model.userData.editorId || model.name
+ })
+ expect(rootAlias).toBeTruthy()
+ await selectNode(frame, ids.trigger)
+ await fieldText(frame, /^目标对象$/, rootAlias)
+ const events: unknown[] = []
+ for (const event of ['click', 'double-click', 'auto']) {
+ await selectNode(frame, ids.trigger)
+ await (await bpField(frame, /^事件$/)).selectOption(event)
+ await resetBlueprint(frame)
+ await frame.locator('[data-action="preview"]').first().click()
+ await expect.poll(() => frame.evaluate(() => (window as any).editorApp.preview)).toBe(true)
+ if (event !== 'auto') {
+ const point = await frame.evaluate(() => {
+ const e = (window as any).editorApp
+ const rect = e.renderer.domElement.getBoundingClientRect()
+ const ray = new e.raycaster.constructor()
+ const pointer = e.pointer.clone()
+ for (let iy = 2; iy < 19; iy++) for (let ix = 2; ix < 19; ix++) {
+ const x = ix / 20, y = iy / 20
+ if (document.elementFromPoint(rect.left + rect.width * x, rect.top + rect.height * y) !== e.renderer.domElement) continue
+ pointer.set(x * 2 - 1, -(y * 2 - 1))
+ ray.setFromCamera(pointer, e.camera)
+ const hit = ray.intersectObjects([e.modelPivot], true).find((hit: any) => {
+ for (let o = hit.object; o; o = o.parent) if (!o.visible) return false
+ return true
+ })
+ if (hit) return { x: x * rect.width, y: y * rect.height, mesh: hit.object.name }
+ }
+ return null
+ })
+ expect(point, 'A visible, unobstructed real truck mesh pixel must be raycastable').not.toBeNull()
+ const canvas = frame.locator('#render-host canvas')
+ if (event === 'click') await canvas.click({ position: point! })
+ else await canvas.dblclick({ position: point!, delay: 70 })
+ }
+ await expect(frame.locator('[data-modal-close]')).toBeVisible()
+ await frame.locator('[data-modal-close]').click()
+ await waitForFinished(frame)
+ const state = await graphState(frame)
+ expect(state.logs.filter((l: any) => l.message.startsWith('开始执行'))).toHaveLength(1)
+ expect(state.logs.some((l: any) => l.nodeId === ids.finish && l.message.startsWith('执行节点'))).toBe(true)
+ events.push({ event, starts: 1, finished: true, source: event === 'auto' ? '真实点击进入预览' : '真实 renderer canvas 指针事件', logs: state.logs })
+ await shot(page, info, `27-truck-preview-${event}`)
+ await frame.locator('.preview-exit[data-action="preview"]').click()
+ await expect.poll(() => frame.evaluate(() => (window as any).editorApp.preview)).toBe(false)
+ }
+ // Keep a normal click trigger in the saved copy; no test-only handler/data is
+ // stored. Reopen verifies graph parameters survived the actual backend save.
+ await selectNode(frame, ids.trigger)
+ await (await bpField(frame, /^事件$/)).selectOption('click')
+ await resetBlueprint(frame)
+ await saveTruck(page, frame)
+ await page.reload()
+ const reopened = await readyFrame(page)
+ const finalGraph = await graphState(reopened)
+ expect(finalGraph.nodes.find((n: any) => n.id === ids.trigger).params).toMatchObject({ event: 'click', target: rootAlias })
+ expect(finalGraph.edges).toHaveLength(6)
+ evidence('truck-blueprint-events', { status: 'PASS', kind: 'e2e', testTitle: info.title,
+ featureIds: ['blueprint.click', 'blueprint.double-click', 'blueprint.auto', 'view.preview'], projectId: fixture().models.truck.id,
+ runtimeSupplement: { status: 'PASS', kind: 'runtime', featureIds: ['blueprint.condition'], branches, mismatch }, browserEvents: events,
+ persistedNodeCount: finalGraph.nodes.length, persistedEdgeCount: finalGraph.edges.length,
+ scope: '执行使用正式模型编辑器的真实模型/时间轴/高亮/提示 handler;不等同于学员端任务下发。' })
+})
diff --git a/tests/model-editor-closure-helpers.ts b/tests/model-editor-closure-helpers.ts
new file mode 100644
index 0000000..cf67c1b
--- /dev/null
+++ b/tests/model-editor-closure-helpers.ts
@@ -0,0 +1,83 @@
+import fs from 'node:fs'
+import path from 'node:path'
+import { expect, type Frame, type Page, type TestInfo } from '@playwright/test'
+// @ts-ignore Repository helper reads credentials privately; no trace/storageState is emitted.
+import { call, login, fixture, writeFixture, report, work } from '../tools/model-editor-closure-live.mjs'
+export { call, login, fixture, writeFixture, report, work }
+
+export async function signIn(page: Page, account = 'root') {
+ const session = await login(account)
+ await page.addInitScript(({ accessToken, refreshToken }) => {
+ if (!sessionStorage.getItem('model-closure-initialized')) {
+ sessionStorage.setItem('unreal-tran:web:access-token:v1', accessToken)
+ sessionStorage.setItem('unreal-tran:web:refresh-token:v1', refreshToken || '')
+ sessionStorage.setItem('model-closure-initialized', '1')
+ }
+ }, { accessToken: session.accessToken, refreshToken: session.refreshToken })
+ return session.accessToken as string
+}
+
+export async function openEditor(page: Page, key: 'fox' | 'truck' | string = 'fox') {
+ const id = fixture().models[key]?.id || key
+ await page.goto(`/content/models/${id}/edit`)
+ return readyFrame(page)
+}
+
+export async function readyFrame(page: Page): Promise {
+ await expect.poll(() => page.frames().some(frame => frame.url().includes('/legacy-model-editor/editor.html')), { timeout: 60_000 }).toBe(true)
+ const frame = page.frames().find(frame => frame.url().includes('/legacy-model-editor/editor.html'))!
+ await frame.waitForFunction(() => {
+ const editor = (window as any).editorApp
+ return editor?.apiEditorHydrated && editor?.modelPivot && editor?.apiState === 'ready' && !editor.apiImportBusy && !editor.apiCoverSavePending
+ }, undefined, { timeout: 90_000 })
+ await expect(frame.locator('.editor-shell')).toHaveAttribute('data-host-readonly', /true|false/)
+ return frame
+}
+
+export async function saveEditor(frame: Frame) {
+ await frame.locator('[data-action="save"]').first().click()
+ await expect.poll(() => frame.evaluate(() => {
+ const e = (window as any).editorApp
+ return e.apiState === 'ready' && !e.apiCoverSavePending && !e.apiSession.savePending
+ })).toBe(true)
+}
+
+export async function selectMesh(frame: Frame) {
+ await frame.locator('[data-selection-scope="part"]').click()
+ const mesh = await frame.evaluate(() => {
+ const e = (window as any).editorApp
+ let selected: any
+ e.modelPivot.traverse((o: any) => { if (!selected && o.isMesh) selected = o })
+ return { uuid: selected.uuid, id: selected.userData.editorId, name: selected.name }
+ })
+ await frame.locator(`[data-object-id="${mesh.uuid}"]`).click()
+ return mesh
+}
+
+export async function setRange(frame: Frame, selector: string, value: string) {
+ await frame.locator(selector).evaluate((element, next) => {
+ (element as HTMLInputElement).value = next
+ element.dispatchEvent(new Event('input', { bubbles: true }))
+ element.dispatchEvent(new Event('change', { bubbles: true }))
+ }, value)
+}
+
+export async function shot(page: Page, info: TestInfo, name: string) {
+ const folder = path.join(report, 'screenshots')
+ fs.mkdirSync(folder, { recursive: true })
+ await expect(page.locator('.el-loading-mask:visible')).toHaveCount(0)
+ const file = path.join(folder, `${name}.png`)
+ await page.screenshot({ path: file, fullPage: true, animations: 'disabled' })
+ await info.attach(name, { path: file, contentType: 'image/png' })
+}
+
+export async function closeSession(page: Page) {
+ const token = await page.evaluate(() => sessionStorage.getItem('unreal-tran:web:access-token:v1')).catch(() => null)
+ if (token) await call('auth/v1/auth/logout', token, {}).catch(() => {})
+}
+
+export function evidence(name: string, data: unknown) {
+ const folder = path.join(report, 'evidence')
+ fs.mkdirSync(folder, { recursive: true })
+ fs.writeFileSync(path.join(folder, `${name}.json`), JSON.stringify({ time: new Date().toISOString(), data }, null, 2))
+}
diff --git a/tests/model-editor-closure.spec.ts b/tests/model-editor-closure.spec.ts
new file mode 100644
index 0000000..f2df865
--- /dev/null
+++ b/tests/model-editor-closure.spec.ts
@@ -0,0 +1,222 @@
+import { test, expect, type Frame } from '@playwright/test'
+import { signIn, openEditor, readyFrame, saveEditor, selectMesh, setRange, shot, closeSession, evidence, call, fixture, writeFixture } from './model-editor-closure-helpers'
+
+test.afterEach(async ({ page }) => { await closeSession(page) })
+async function change(frame: Frame, selector: string, value: string) {
+ await frame.locator(selector).fill(value)
+ await frame.locator(selector).dispatchEvent('change')
+}
+async function selectId(frame: Frame, id: string) {
+ const uuid = await frame.evaluate(id => {
+ let found: any
+ const e = (window as any).editorApp
+ e.modelPivot.traverse((o: any) => { if (o.userData.editorId === id) found = o })
+ return found?.uuid
+ }, id)
+ expect(uuid).toBeTruthy()
+ await frame.locator('[data-selection-scope="part"]').click()
+ await frame.locator(`[data-object-id="${uuid}"]`).click()
+}
+
+test('对象名称、变换、PBR、属性与扩展标记保存重开;撤销重做', async ({ page }, info) => {
+ const token = await signIn(page)
+ let frame = await openEditor(page)
+ const mesh = await selectMesh(frame)
+ await frame.locator('[data-inspector-tab="properties"]').click()
+ await change(frame, '#object-name-input', '闭环狐狸可编辑部件')
+ await change(frame, '[data-vector="position"][data-axis="x"]', '1.25')
+ await change(frame, '[data-vector="rotation"][data-axis="y"]', '15')
+ await change(frame, '[data-vector="scale"][data-axis="z"]', '1.1')
+ await setRange(frame, '#material-color', '#cc9955')
+ await setRange(frame, '#material-metalness', '0.25')
+ await setRange(frame, '#material-roughness', '0.7')
+ await setRange(frame, '#material-opacity', '0.85')
+ await change(frame, '#custom-resource-code', 'CLOSURE-FOX-001')
+ await frame.locator('#custom-operating-status').selectOption('NORMAL')
+ await frame.locator('#custom-operating-status').selectOption('SERVICE')
+ await frame.locator('[data-action="undo"]').first().click()
+ await expect(frame.locator('#custom-operating-status')).toHaveValue('NORMAL')
+ await frame.locator('[data-action="redo"]').first().click()
+ await expect(frame.locator('#custom-operating-status')).toHaveValue('SERVICE')
+ await frame.locator('[data-inspector-tab="components"]').click()
+ const count = await frame.locator('.component-card').count()
+ await frame.locator('.component-add').click()
+ await expect(frame.locator('.component-card')).toHaveCount(count + 1)
+ await expect(frame.locator('.component-card').last()).toContainText('仅保存标签')
+ await saveEditor(frame)
+ await shot(page, info, '01-components-saved')
+ await page.reload()
+ frame = await readyFrame(page)
+ await selectId(frame, mesh.id)
+ await expect(frame.locator('#object-name-input')).toHaveValue('闭环狐狸可编辑部件')
+ await expect(frame.locator('#custom-resource-code')).toHaveValue('CLOSURE-FOX-001')
+ await expect(frame.locator('#custom-operating-status')).toHaveValue('SERVICE')
+ await expect(frame.locator('#material-color')).toHaveValue('#cc9955')
+ await expect(frame.locator('#material-metalness')).toHaveValue('0.25')
+ await expect(frame.locator('#material-roughness')).toHaveValue('0.7')
+ await expect(frame.locator('#material-opacity')).toHaveValue('0.85')
+ const state = await frame.evaluate(() => {
+ const o = (window as any).editorApp.selected
+ return { id: o.userData.editorId, position: o.position.toArray(), rotation: o.rotation.toArray().slice(0, 3), scale: o.scale.toArray(), customProperties: o.userData.customProperties, customComponents: o.userData.customComponents }
+ })
+ expect(state.position[0]).toBeCloseTo(1.25)
+ expect(state.rotation[1]).toBeCloseTo(Math.PI / 12)
+ expect(state.scale[2]).toBeCloseTo(1.1)
+ expect(state.customComponents.length).toBeGreaterThan(0)
+ const saved = await call(`tran/v1/content/projects/${fixture().models.fox.id}`, token)
+ expect(saved.status).toBe(200)
+ expect(JSON.stringify(saved.data.currentVersion.content)).toContain('CLOSURE-FOX-001')
+ await frame.locator('[data-action="focus"]').first().click()
+ await shot(page, info, '02-properties-reopened')
+ evidence('properties', { status: 'PASS', kind: 'e2e+api', testTitle: info.title,
+ featureIds: ['property.name', 'property.position', 'property.rotation', 'property.scale', 'property.color', 'property.metalness', 'property.roughness', 'property.opacity', 'property.code', 'property.status', 'component.tag', 'project.undo', 'project.redo', 'project.save'],
+ scope: '真实属性输入/材质参数/标签、保存与刷新;变换只验数值输入,不代替三维 gizmo 拖动。', state, serverVersion: saved.data.project.version, persisted: true })
+})
+
+test('场景树搜索、可见性、选择、视口工具、原生动画与截图', async ({ page }, info) => {
+ await signIn(page)
+ let frame = await openEditor(page)
+ const mesh = await selectMesh(frame)
+ if (!await frame.evaluate(() => (window as any).editorApp.selected.visible)) await frame.locator(`[data-object-id="${mesh.uuid}"] .tree-eye`).click()
+ await frame.locator('#left-search').fill('闭环狐狸可编辑部件')
+ await expect(frame.locator(`[data-object-id="${mesh.uuid}"]`)).toBeVisible()
+ await frame.locator('#left-search').fill('不存在的节点验收字符串')
+ await expect(frame.locator('.tree-row')).toHaveCount(0)
+ await frame.locator('#left-search').fill('')
+ await frame.locator(`[data-object-id="${mesh.uuid}"] .tree-eye`).click()
+ expect(await frame.evaluate(() => (window as any).editorApp.selected.visible)).toBe(false)
+ await saveEditor(frame)
+ await page.reload(); frame = await readyFrame(page)
+ await selectId(frame, mesh.id)
+ expect(await frame.evaluate(() => (window as any).editorApp.selected.visible)).toBe(false)
+ await expect(frame.locator('.tag-row')).toContainText('已隐藏')
+ const uuid = await frame.evaluate(() => (window as any).editorApp.selected.uuid)
+ await frame.locator(`[data-object-id="${uuid}"] .tree-eye`).click()
+ await frame.locator('[data-view="outline"]').click()
+ await frame.locator('[data-selection-scope="whole"]').click()
+ expect(await frame.evaluate(() => (window as any).editorApp.selectionHelper.visible)).toBe(false)
+ await frame.locator('[data-selection-scope="part"]').click()
+ await frame.locator('[data-view="outline"]').click()
+ for (const mode of ['translate', 'rotate', 'scale']) {
+ await frame.locator(`[data-transform="${mode}"]`).click()
+ expect(await frame.evaluate(() => (window as any).editorApp.transform.mode)).toBe(mode)
+ }
+ await frame.locator('[data-view="wireframe"]').click()
+ expect(await frame.evaluate(() => {
+ let wire = false; (window as any).editorApp.modelPivot.traverse((o: any) => { if (o.isMesh) wire = (Array.isArray(o.material) ? o.material[0] : o.material).wireframe }); return wire
+ })).toBe(true)
+ await frame.locator('[data-view="wireframe"]').click()
+ await frame.locator('[data-view="lit"]').click()
+ expect(await frame.evaluate(() => (window as any).editorApp.scene.environment === null)).toBe(true)
+ await frame.locator('[data-view="lit"]').click()
+ await frame.locator('[data-action="grid"]').first().click()
+ expect(await frame.evaluate(() => (window as any).editorApp.grid.visible)).toBe(false)
+ await frame.locator('[data-action="grid"]').first().click()
+ await frame.locator('[data-view="perspective"]').click()
+ expect(await frame.evaluate(() => (window as any).editorApp.camera.fov)).toBe(42)
+ const clips = await frame.locator('#native-clip-select option').count()
+ expect(clips).toBeGreaterThan(1)
+ await frame.locator('#native-clip-select').selectOption('1')
+ await frame.locator('[data-action="toggle-native-clip"]').click()
+ await expect.poll(() => frame.evaluate(() => (window as any).editorApp.nativeAction.time)).toBeGreaterThan(0)
+ await frame.locator('[data-action="toggle-native-clip"]').click()
+ expect(await frame.evaluate(() => (window as any).editorApp.nativeAnimationPlaying)).toBe(false)
+ await frame.locator('[data-action="preview"]').first().click()
+ await expect(frame.locator('.editor-shell')).toHaveClass(/preview-mode/)
+ expect(await frame.evaluate(() => (window as any).editorApp.transform.enabled)).toBe(false)
+ await expect(frame.locator('#toast-stack .toast')).toHaveCount(0)
+ await shot(page, info, '03-fox-preview')
+ await frame.getByRole('button', { name: '退出预览 Esc', exact: true }).click()
+ await saveEditor(frame)
+ const downloadPromise = page.waitForEvent('download')
+ await frame.locator('[data-action="snapshot"]').first().click()
+ const download = await downloadPromise
+ expect(download.suggestedFilename()).toMatch(/\.png$/i)
+ expect(await download.failure()).toBeNull()
+ await page.reload(); frame = await readyFrame(page)
+ await expect(frame.locator('#native-clip-select')).toHaveValue('1')
+ evidence('viewport', { status: 'PASS', kind: 'e2e', testTitle: info.title,
+ featureIds: ['tree.search', 'tree.visibility', 'selection.sync', 'view.grid', 'view.pbr', 'view.wireframe', 'view.outline', 'view.perspective', 'view.preview', 'view.snapshot', 'timeline.native'],
+ scope: '树搜索、对象隐藏刷新、轮廓及视图开关、原生动画和PNG下载。移动/旋转/缩放只切换模式,真实gizmo拖动仍需补验;聚焦未做几何断言。', clips, nativeClipPersisted: true, hiddenObjectPersisted: true, snapshot: download.suggestedFilename() })
+})
+
+test('新建工程取消及失败保留输入;成功创建新ID且不覆盖当前模型', async ({ page }, info) => {
+ const token = await signIn(page)
+ let frame = await openEditor(page)
+ const id = fixture().models.fox.id
+ const before = await call(`tran/v1/content/projects/${id}`, token)
+ await frame.locator('[data-action="new"]').click()
+ const dialog = page.getByRole('dialog', { name: '新建装备模型' })
+ await expect(dialog).toBeVisible()
+ await dialog.getByRole('button', { name: '取消', exact: true }).click()
+ await expect(dialog).toBeHidden()
+ expect(page.url()).toContain(`/models/${id}/edit`)
+ await frame.locator('[data-action="new"]').click()
+ const name = `闭环新建-${Date.now()}`
+ await dialog.getByRole('textbox', { name: '新模型名称' }).fill(name)
+ const endpoint = '**/api/tran/v1/content/projects'
+ await page.route(endpoint, async route => route.request().method() === 'POST'
+ ? route.fulfill({ status: 503, contentType: 'application/json', body: JSON.stringify({ code: 503, message: '验收模拟新建暂时失败' }) }) : route.continue())
+ await dialog.getByRole('button', { name: '创建', exact: true }).click()
+ await expect(page.locator('.el-message')).toContainText('验收模拟新建暂时失败')
+ await expect(dialog.getByRole('textbox', { name: '新模型名称' })).toHaveValue(name)
+ await shot(page, info, '04-new-retry-dialog')
+ await page.unroute(endpoint)
+ const created = page.waitForResponse(response => response.url().endsWith('/api/tran/v1/content/projects') && response.request().method() === 'POST' && response.ok())
+ await dialog.getByRole('button', { name: '创建', exact: true }).click()
+ const response = await (await created).json()
+ const newId = response.data.project?.id || response.data.id
+ expect(newId).toBeTruthy()
+ const data = fixture(); data.extraProjectIds.push(String(newId)); writeFixture(data)
+ await expect(page).toHaveURL(new RegExp(`/models/${newId}/edit`))
+ frame = await readyFrame(page)
+ await expect(frame.locator('#project-name')).toHaveValue(name)
+ const after = await call(`tran/v1/content/projects/${id}`, token)
+ expect(after.data.project.version).toBe(before.data.project.version)
+ expect(after.data.currentVersion.content).toEqual(before.data.currentVersion.content)
+ await expect(page.locator('.el-message').filter({ hasText: '验收模拟新建暂时失败' })).toHaveCount(0)
+ await shot(page, info, '05-new-separate-project')
+ evidence('new-project', { status: 'PASS', kind: 'e2e+api', testTitle: info.title, featureIds: ['project.new'], oldId: id, newId, cancelPreserved: true, failureRetainedName: true, originalVersion: after.data.project.version, sourceUnchanged: true })
+})
+
+test('管理员只读权限:编辑、新建、导入、保存发布均不可越权', async ({ page }, info) => {
+ const token = await signIn(page, 'admin')
+ const frame = await openEditor(page)
+ await expect(frame.locator('.editor-shell')).toHaveAttribute('data-host-readonly', 'true')
+ await selectMesh(frame)
+ await expect(frame.locator('[data-model-editability]')).toHaveText('只读')
+ for (const action of ['new', 'import-model', 'open', 'save', 'undo', 'redo', 'publish']) {
+ const button = frame.locator(`[data-action="${action}"]`).first()
+ await expect(button).toBeDisabled()
+ }
+ const unexpectedWrites: string[] = []
+ page.on('request', request => {
+ if (/\/api\/tran\/v1\/content\//.test(request.url()) && ['POST', 'PUT', 'PATCH', 'DELETE'].includes(request.method())) {
+ unexpectedWrites.push(request.method())
+ }
+ })
+ const search = frame.locator('#left-search')
+ await search.pressSequentially('fox')
+ await expect(search).toHaveValue('fox')
+ await expect(frame.locator('#scene-tree .tree-row')).not.toHaveCount(0)
+ await search.press('Control+A')
+ await search.press('Backspace')
+ await expect(search).toHaveValue('')
+ const clips = frame.locator('#native-clip-select')
+ const options = await clips.locator('option').count()
+ expect(options).toBeGreaterThan(1)
+ const nextClip = String((Number(await clips.inputValue()) + 1) % options)
+ await clips.selectOption(nextClip)
+ await expect.poll(() => frame.evaluate(() => (window as any).editorApp.nativeClipIndex)).toBe(Number(nextClip))
+ const nativeToggle = frame.locator('#native-clip-toggle')
+ await nativeToggle.focus()
+ await nativeToggle.press('Enter')
+ await expect.poll(() => frame.evaluate(() => (window as any).editorApp.nativeAnimationPlaying)).toBe(true)
+ await nativeToggle.press('Enter')
+ await expect.poll(() => frame.evaluate(() => (window as any).editorApp.nativeAnimationPlaying)).toBe(false)
+ expect(unexpectedWrites).toEqual([])
+ const denial = await call('tran/v1/content/projects', token, { type: 'MODEL', name: '不得创建', version: 0, content: {} })
+ expect(denial.status).toBe(403)
+ await shot(page, info, '06-readonly-permissions')
+ evidence('permissions', { status: 'PASS', kind: 'e2e+api', testTitle: info.title, featureIds: ['project.permissions'], scope: '管理员只读UI、真实键盘搜索、原生片段切换与Enter播放/暂停、预览无内容写请求及创建403;纯publisher无需update的发布流程由独立权限合同回归补充。', readonlyUi: true, readonlySearch: true, nativeClipChanged: true, keyboardPreview: true, previewWriteRequests: unexpectedWrites.length, createStatus: denial.status })
+})
diff --git a/tests/model-editor-interaction-closure.spec.ts b/tests/model-editor-interaction-closure.spec.ts
new file mode 100644
index 0000000..7ec42f4
--- /dev/null
+++ b/tests/model-editor-interaction-closure.spec.ts
@@ -0,0 +1,117 @@
+import { test, expect, type Frame, type Page } from '@playwright/test'
+import { signIn, openEditor, readyFrame, saveEditor, selectMesh, shot, closeSession, evidence, fixture, writeFixture, call } from './model-editor-closure-helpers'
+
+test.afterEach(async ({ page }) => { await closeSession(page) })
+async function copy(token: string, label: string) {
+ const source = fixture().originals.fox.id
+ const original = await call(`tran/v1/content/projects/${source}`, token)
+ const result = await call(`tran/v1/content/projects/${source}/copy`, token, { name: `闭环验收-${label}-${Date.now()}`, version: original.data.project.version })
+ expect(result.status).toBe(200)
+ const id = String(result.data.project.id)
+ const data = fixture(); data.extraProjectIds.push(id); writeFixture(data)
+ return id
+}
+async function pose(frame: Frame) {
+ return frame.evaluate(() => {
+ const o = (window as any).editorApp.modelPivot
+ return { position: o.position.toArray(), rotation: o.rotation.toArray().slice(0, 3), scale: o.scale.toArray() }
+ })
+}
+async function dragGizmo(page: Page, frame: Frame, mode: string) {
+ await frame.locator(`[data-transform="${mode}"]`).click()
+ const box = await frame.locator('#render-host canvas').first().boundingBox()
+ expect(box).toBeTruthy()
+ const points = await frame.evaluate(mode => {
+ const e = (window as any).editorApp
+ const handles = e.transform._gizmo.gizmo[mode].children.filter((o: any) => o.visible && o.name === 'X')
+ const points: { x: number; y: number }[] = []
+ for (const handle of handles) {
+ const attr = handle.geometry?.attributes?.position
+ if (!attr) continue
+ handle.geometry.computeBoundingBox()
+ const center = handle.geometry.boundingBox.getCenter(e.camera.position.clone())
+ const candidates = [center]
+ if (mode === 'rotate') for (let i = 0; i < attr.count; i += Math.max(1, Math.floor(attr.count / 24))) candidates.push(e.camera.position.clone().fromBufferAttribute(attr, i))
+ for (const v of candidates) {
+ v.applyMatrix4(handle.matrixWorld).project(e.camera)
+ points.push({ x: (v.x + 1) / 2, y: (1 - v.y) / 2 })
+ }
+ }
+ return points
+ }, mode)
+ let hit: { x: number; y: number } | undefined
+ for (const point of points) {
+ if (point.x < .05 || point.x > .95 || point.y < .05 || point.y > .95) continue
+ const p = { x: box!.x + point.x * box!.width, y: box!.y + point.y * box!.height }
+ await page.mouse.move(p.x, p.y)
+ if (await frame.evaluate(() => (window as any).editorApp.transform.axis === 'X')) { hit = p; break }
+ }
+ expect(hit, `Real ${mode} X handle can be hovered`).toBeTruthy()
+ await page.mouse.down()
+ expect(await frame.evaluate(() => (window as any).editorApp.transform.dragging)).toBe(true)
+ await page.mouse.move(hit!.x + 46, hit!.y - 31, { steps: 12 })
+ await page.mouse.up()
+ expect(await frame.evaluate(() => (window as any).editorApp.transform.dragging)).toBe(false)
+}
+
+test('真实三维移动、旋转、缩放手柄拖动及撤销重做保存重开', async ({ page }, info) => {
+ const token = await signIn(page)
+ const id = await copy(token, '三维手柄')
+ let frame = await openEditor(page, id)
+ await selectMesh(frame)
+ await frame.locator('[data-selection-scope="whole"]').click()
+ await frame.locator('[data-action="focus"]').first().click()
+ let previous = await pose(frame)
+ const changes: unknown[] = []
+ for (const [mode, key] of [['translate', 'position'], ['rotate', 'rotation'], ['scale', 'scale']] as const) {
+ await dragGizmo(page, frame, mode)
+ const next = await pose(frame)
+ expect(next[key]).not.toEqual(previous[key])
+ changes.push({ mode, before: previous[key], after: next[key] })
+ previous = next
+ }
+ await frame.locator('[data-action="undo"]').first().click()
+ await expect.poll(async () => (await pose(frame)).scale).not.toEqual(previous.scale)
+ await frame.locator('[data-action="redo"]').first().click()
+ await expect.poll(async () => (await pose(frame)).scale).toEqual(previous.scale)
+ await saveEditor(frame)
+ await shot(page, info, '51-transform-gizmos')
+ await page.reload(); frame = await readyFrame(page)
+ const reopened = await pose(frame)
+ for (const key of ['position', 'rotation', 'scale'] as const) for (let i = 0; i < 3; i++) expect(reopened[key][i]).toBeCloseTo(previous[key][i], 5)
+ evidence('51-gizmos', { status: 'PASS', kind: 'e2e+runtime-readback', testTitle: info.title, featureIds: ['view.move', 'view.rotate', 'view.scale'], projectId: id, changes, reopened })
+})
+
+test('未保存离开:关闭确认保留工作、保存失败留在原页、重试保存后离开', async ({ page }, info) => {
+ const token = await signIn(page)
+ const id = await copy(token, '离开保护')
+ const frame = await openEditor(page, id)
+ await selectMesh(frame)
+ await frame.locator('#custom-resource-code').fill('UNSAVED-LEAVE-CLOSURE')
+ await frame.locator('#custom-resource-code').dispatchEvent('change')
+ const list = page.getByRole('link', { name: '模型制作', exact: true })
+ await list.click()
+ const dialog = page.locator('.el-message-box').filter({ hasText: '当前模型有未保存修改' })
+ await expect(dialog).toBeVisible()
+ await dialog.locator('.el-message-box__headerbtn').click()
+ await expect(dialog).toBeHidden()
+ await expect(page).toHaveURL(new RegExp(`/models/${id}/edit`))
+ await expect(frame.locator('#custom-resource-code')).toHaveValue('UNSAVED-LEAVE-CLOSURE')
+ const endpoint = `**/api/tran/v1/content/projects/${id}`
+ await page.route(endpoint, async route => route.request().method() === 'PUT'
+ ? route.fulfill({ status: 503, contentType: 'application/json', body: JSON.stringify({ code: 503, message: '验收模拟保存失败' }) }) : route.continue())
+ await list.click()
+ await dialog.getByRole('button', { name: '保存并离开', exact: true }).click()
+ await expect.poll(() => frame.evaluate(() => (window as any).editorApp.apiState)).toBe('error')
+ await expect(page).toHaveURL(new RegExp(`/models/${id}/edit`))
+ await expect(frame.locator('#custom-resource-code')).toHaveValue('UNSAVED-LEAVE-CLOSURE')
+ await shot(page, info, '52-leave-save-failure')
+ await page.unroute(endpoint)
+ await list.click()
+ await dialog.getByRole('button', { name: '保存并离开', exact: true }).click()
+ await expect(page).toHaveURL(/\/content\/models$/)
+ const stored = await call(`tran/v1/content/projects/${id}`, token)
+ expect(stored.status, stored.message).toBe(200)
+ expect(JSON.stringify(stored.data.currentVersion.content)).toContain('UNSAVED-LEAVE-CLOSURE')
+ evidence('52-leave', { status: 'PASS', kind: 'e2e+api', testTitle: info.title, featureIds: ['project.leave'], projectId: id, closeKeptDraft: true, failureKeptDraft: true, retriedSavePersisted: true })
+})
diff --git a/tests/model-editor-navigation-closure.spec.ts b/tests/model-editor-navigation-closure.spec.ts
new file mode 100644
index 0000000..26fac99
--- /dev/null
+++ b/tests/model-editor-navigation-closure.spec.ts
@@ -0,0 +1,268 @@
+import path from 'node:path'
+import { expect, test, type Frame, type Page } from '@playwright/test'
+import { call, closeSession, evidence, fixture, openEditor, readyFrame, saveEditor, selectMesh, shot, signIn, work, writeFixture } from './model-editor-closure-helpers'
+
+// These tests own independent server copies. No shared fox/truck draft is edited.
+// All editor changes use real DOM inputs/buttons/pointers; evaluate() only reads state.
+test.setTimeout(360_000)
+test.afterEach(async ({ page }) => { await closeSession(page) })
+
+async function copyFor(token: string, name: string) {
+ const sourceId = String(fixture().originals.fox.id)
+ const source = await call(`tran/v1/content/projects/${sourceId}`, token)
+ expect(source.status, source.message).toBe(200)
+ const copied = await call(`tran/v1/content/projects/${sourceId}/copy`, token, {
+ name: `闭环补充-${name}-${Date.now().toString(36)}`, version: source.data.project.version,
+ })
+ expect(copied.status, copied.message).toBe(200)
+ const id = String(copied.data.project.id)
+ expect([sourceId, '156', '157']).not.toContain(id)
+ const data = fixture()
+ data.extraProjectIds = [...new Set([...(data.extraProjectIds || []), id])]
+ data.navigationCopies = { ...(data.navigationCopies || {}), [name]: id }
+ writeFixture(data)
+ return id
+}
+
+async function persisted(page: Page, frame: Frame, token: string, id: string) {
+ const response = page.waitForResponse(response => response.request().method() === 'PUT'
+ && new URL(response.url()).pathname.endsWith(`/content/projects/${id}`), { timeout: 60_000 })
+ await saveEditor(frame)
+ expect((await response).status()).toBe(200)
+ const saved = await call(`tran/v1/content/projects/${id}`, token)
+ expect(saved.status, saved.message).toBe(200)
+ return saved.data
+}
+
+async function activate(frame: Frame, tab: string) {
+ await frame.locator(`[data-bottom-tab="${tab}"]`).click()
+ await expect(frame.locator(`[data-mode="${tab}"]`)).toHaveClass(/active/)
+}
+
+test('补充资源库:搜索清空、真实卡片切换与刷新保留', async ({ page }, info) => {
+ const token = await signIn(page), id = await copyFor(token, '资源库')
+ let frame = await openEditor(page, id)
+ const original = await frame.evaluate(() => {
+ const e = (window as any).editorApp
+ return { id: e.activeDescriptor.id, name: e.activeDescriptor.name, code: e.activeDescriptor.assetCode }
+ })
+ await frame.locator('[data-left-tab="assets"]').click()
+ const search = frame.locator('#left-search')
+ await search.fill(original.name)
+ await expect(frame.locator(`[data-model="${original.id}"]`)).toBeVisible()
+ await search.fill('NO-MATCH-资源搜索-193746')
+ await expect(frame.locator('.asset-search-empty')).toBeVisible()
+ await expect(frame.locator('.asset-card:visible')).toHaveCount(0)
+ await search.fill('')
+ await expect(frame.locator('.asset-search-empty')).toBeHidden()
+ expect(await frame.locator('.asset-card:visible').count()).toBeGreaterThan(3)
+ await shot(page, info, '41-resource-search-restored')
+
+ // A small controlled model gives the real catalog a second registered asset.
+ await frame.locator('#model-file-input').setInputFiles(path.resolve(work, '../../文档/素材/01-Box.glb'))
+ await frame.waitForFunction(() => {
+ const e = (window as any).editorApp
+ return e.activeDescriptor?.sourceName === '01-Box.glb' && e.apiState === 'ready' && !e.apiImportBusy && !e.apiCoverSavePending
+ }, undefined, { timeout: 90_000 })
+ const boxId = await frame.evaluate(() => (window as any).editorApp.activeDescriptor.id)
+ expect(boxId).not.toBe(original.id)
+ await expect(frame.locator(`[data-model="${original.id}"]`)).toBeVisible()
+ const switched = page.waitForResponse(response => response.request().method() === 'PUT'
+ && new URL(response.url()).pathname.endsWith(`/content/projects/${id}`), { timeout: 60_000 })
+ await frame.locator(`[data-model="${original.id}"]`).click()
+ expect((await switched).status()).toBe(200)
+ await expect.poll(() => frame.evaluate(() => {
+ const e = (window as any).editorApp
+ return { id: e.activeDescriptor.id, ready: e.apiState === 'ready' && !e.apiSession.savePending }
+ })).toEqual({ id: original.id, ready: true })
+ await page.reload(); frame = await readyFrame(page)
+ const restored = await frame.evaluate(() => {
+ const e = (window as any).editorApp; let meshes = 0
+ e.modelPivot.traverse((o: any) => { if (o.isMesh) meshes++ })
+ return { id: e.activeDescriptor.id, code: e.activeDescriptor.assetCode, meshes }
+ })
+ expect(restored.id).toBe(original.id)
+ expect(restored.code).toBe(original.code)
+ expect(restored.meshes).toBeGreaterThan(0)
+ await shot(page, info, '42-resource-card-switch-reopened')
+ evidence('41-resource-search-switch', { status: 'PASS', kind: 'e2e+api', testTitle: info.title,
+ featureIds: ['resource.search', 'resource.catalog'], projectId: id, original, boxId, restored,
+ scope: '真实搜索与资源卡片点击、服务端保存、刷新后原模型网格;副本独立登记待清理。' })
+})
+
+test('补充环境与树:内置三环境保存重开、节点计数与折叠展开', async ({ page }, info) => {
+ const token = await signIn(page), id = await copyFor(token, '内置环境')
+ let frame = await openEditor(page, id)
+ const snapshots: unknown[] = []
+ for (const [index, type] of ['workshop', 'outdoor', 'depot'].entries()) {
+ await frame.locator('[data-left-tab="assets"]').click()
+ await frame.locator('#left-search').fill('')
+ await frame.locator(`[data-environment-action="builtin"][data-environment-type="${type}"]`).click()
+ await frame.waitForFunction(type => {
+ const e = (window as any).editorApp
+ return e.outdoorSceneMeta.environmentType === type && e.outdoorSceneMeta.mode === 'placeholder'
+ && e.loading.classList.contains('hidden')
+ }, type, { timeout: 90_000 })
+ const saved = await persisted(page, frame, token, id)
+ expect(saved.currentVersion.content.environment.environmentType).toBe(type)
+ await page.reload(); frame = await readyFrame(page)
+ const environment = await frame.evaluate(() => {
+ const e = (window as any).editorApp; let nodes = 0, meshes = 0
+ e.outdoorRoot.traverse((o: any) => { nodes++; if (o.isMesh) meshes++ })
+ return { type: e.outdoorSceneMeta.environmentType, mode: e.outdoorSceneMeta.mode,
+ sourceKind: e.outdoorRoot.userData.sourceKind, nodes, meshes, id: e.outdoorRoot.uuid }
+ })
+ expect(environment.type).toBe(type)
+ expect(environment.mode).toBe('placeholder')
+ expect(environment.meshes).toBeGreaterThan(0)
+ if (type === 'depot') expect(environment.sourceKind).toBe('builtin-gltf')
+ await frame.locator('[data-left-tab="scene"]').click()
+ await frame.locator('#left-search').fill('')
+ const beforeCount = await frame.locator('.tree-row').count()
+ const rootRow = frame.locator(`[data-object-id="${environment.id}"]`)
+ await rootRow.locator('.tree-caret').click()
+ expect(await frame.locator('.tree-row').count()).toBeLessThan(beforeCount)
+ await rootRow.locator('.tree-caret').click()
+ await expect(frame.locator('.tree-row')).toHaveCount(beforeCount)
+ const actualCount = await frame.evaluate(() => {
+ const e = (window as any).editorApp; let count = 0
+ e.getEditableRoots().forEach((root: any) => root.traverse(() => count++))
+ return count
+ })
+ await expect(frame.locator('#scene-count')).toHaveText(`${actualCount} 对象`)
+ await shot(page, info, `${43 + index}-builtin-${type}-reopened`)
+ snapshots.push({ ...environment, shownRows: beforeCount, totalNodes: actualCount })
+ }
+ const beforeLeave = await persisted(page, frame, token, id)
+ expect(beforeLeave.currentVersion.content.environment.environmentType).toBe('depot')
+ await page.getByRole('link', { name: '模型制作', exact: true }).click()
+ await expect(page).toHaveURL(/\/content\/models$/)
+ await expect(page.locator('.el-message-box').filter({ hasText: '当前模型有未保存修改' })).toBeHidden()
+ const afterLeave = await call(`tran/v1/content/projects/${id}`, token)
+ expect(afterLeave.status, afterLeave.message).toBe(200)
+ expect(afterLeave.data.project.version).toBe(beforeLeave.project.version)
+ expect(afterLeave.data.currentVersion.content.environment.environmentType).toBe('depot')
+ await shot(page, info, '66-depot-saved-return-list')
+ evidence('43-builtin-environments-tree', { status: 'PASS', kind: 'e2e+api', testTitle: info.title,
+ featureIds: ['environment.builtin', 'tree.browse', 'tree.expand', 'project.leave'], projectId: id, snapshots,
+ depotSavedLeave: { promptedForUnsavedChanges: false, version: afterLeave.data.project.version },
+ scope: '真实切换、保存和刷新三种环境;树计数与展开折叠;装备车库保存后返回列表不误报未保存。树220行上限仍属已说明边界。' })
+})
+
+test('补充视口与工作区:鼠标镜头、聚焦、面板、预览导航、日志与自动保存', async ({ page }, info) => {
+ const token = await signIn(page), id = await copyFor(token, '视口导航')
+ let frame = await openEditor(page, id)
+ const renamed = `闭环导航名称-${id}`
+ const renamedResponse = page.waitForResponse(response => response.request().method() === 'PUT'
+ && new URL(response.url()).pathname.endsWith(`/content/projects/${id}`))
+ await frame.locator('#project-name').fill(renamed)
+ await frame.locator('#project-name').press('Enter')
+ expect((await renamedResponse).status()).toBe(200)
+ const mesh = await selectMesh(frame)
+ const identity = await frame.evaluate(() => {
+ const e = (window as any).editorApp
+ return { selectedId: e.selected.userData.editorId, localPosition: e.selected.position.toArray() }
+ })
+ expect(identity.selectedId).toBe(mesh.id)
+ await frame.locator('[data-selection-scope="whole"]').click()
+ expect(await frame.evaluate(() => {
+ const e = (window as any).editorApp
+ return e.selectionScope === 'whole' && e.selected === e.modelPivot
+ })).toBe(true)
+ await selectMesh(frame)
+ expect(await frame.evaluate(() => (window as any).editorApp.selectionScope)).toBe('part')
+ await expect(frame.locator('.property-row').filter({ hasText: '标识' }).locator('input')).toHaveAttribute('readonly', '')
+ await expect(frame.locator('.property-row').filter({ hasText: '流程绑定' }).locator('input')).toHaveAttribute('readonly', '')
+ await frame.locator('[data-inspector-tab="components"]').click()
+ await expect(frame.locator('.component-card').filter({ hasText: 'Transform' })).toBeVisible()
+ await expect(frame.locator('.component-card').filter({ hasText: 'MeshRenderer' })).toBeVisible()
+ await expect(frame.locator('.component-card').filter({ hasText: 'Shadow' })).toBeVisible()
+ expect(await frame.evaluate(() => (window as any).editorApp.selected.isMesh)).toBe(true)
+ await frame.locator('[data-inspector-tab="properties"]').click()
+ await frame.locator('[data-action="focus"]').first().click()
+ const camera = () => frame.evaluate(() => {
+ const e = (window as any).editorApp
+ return { position: e.camera.position.toArray(), target: e.orbit.target.toArray(), distance: e.camera.position.distanceTo(e.orbit.target) }
+ })
+ const focused = await camera()
+ expect(focused.distance).toBeGreaterThan(0)
+ // Focus target is the object's bounds center, which may differ from its origin.
+ // The orbit target itself must be centered and inside the camera frustum.
+ const targetScreen = await frame.evaluate(() => {
+ const e = (window as any).editorApp
+ return e.orbit.target.clone().project(e.camera).toArray()
+ })
+ expect(Math.abs(targetScreen[0])).toBeLessThan(0.01)
+ expect(Math.abs(targetScreen[1])).toBeLessThan(0.01)
+ const canvas = frame.locator('#render-host canvas'), box = await canvas.boundingBox()
+ expect(box).not.toBeNull()
+ const x = box!.x + box!.width * 0.16, y = box!.y + box!.height * 0.74
+ await page.mouse.move(x, y); await page.mouse.down()
+ await page.mouse.move(x + 90, y - 20, { steps: 12 }); await page.mouse.up()
+ await expect.poll(async () => JSON.stringify((await camera()).position)).not.toBe(JSON.stringify(focused.position))
+ const rotated = await camera()
+ await page.mouse.move(x, y); await page.mouse.down({ button: 'right' })
+ await page.mouse.move(x + 45, y + 25, { steps: 10 }); await page.mouse.up({ button: 'right' })
+ await expect.poll(async () => JSON.stringify((await camera()).target)).not.toBe(JSON.stringify(rotated.target))
+ const panned = await camera()
+ await page.mouse.move(x, y); await page.mouse.wheel(0, -220)
+ await expect.poll(async () => (await camera()).distance).toBeLessThan(panned.distance)
+ expect(await frame.evaluate(() => (window as any).editorApp.selected.position.toArray())).toEqual(identity.localPosition)
+
+ const panelModes = []
+ for (const mode of ['timeline', 'blueprint', 'curve', 'console']) {
+ await activate(frame, mode)
+ panelModes.push(await frame.evaluate(() => (window as any).editorApp.bottomMode))
+ }
+ expect(panelModes).toEqual(['timeline', 'blueprint', 'curve', 'console'])
+ await expect(frame.locator('#console-root .log-row').first()).toBeVisible()
+ const logCount = await frame.evaluate(() => (window as any).editorApp.consoleRows.length)
+ expect(logCount).toBeGreaterThan(0)
+ await expect(frame.locator('#log-badge')).toHaveText(String(logCount))
+ const splitter = await frame.locator('#splitter-h').boundingBox()
+ const beforeHeight = await frame.evaluate(() => getComputedStyle(document.documentElement).getPropertyValue('--bottom-h'))
+ await page.mouse.move(splitter!.x + splitter!.width / 2, splitter!.y + splitter!.height / 2)
+ await page.mouse.down(); await page.mouse.move(splitter!.x + splitter!.width / 2, splitter!.y - 35, { steps: 8 }); await page.mouse.up()
+ expect(await frame.evaluate(() => getComputedStyle(document.documentElement).getPropertyValue('--bottom-h'))).not.toBe(beforeHeight)
+ await activate(frame, 'timeline')
+ await canvas.click({ position: { x: 15, y: 120 } })
+ await page.keyboard.press('Tab')
+ await expect(frame.locator('[data-mode="blueprint"]')).toHaveClass(/active/)
+ await activate(frame, 'timeline')
+ await frame.locator('#timeline-root [data-action="stop"]').click()
+ const duration = await frame.evaluate(() => (window as any).editorApp.timeline.duration)
+ await frame.locator('[data-action="preview"]').first().click()
+ await frame.locator('[data-preview-action="next"]').click()
+ expect(await frame.evaluate(() => (window as any).editorApp.timeline.currentTime)).toBe(Math.min(duration, 2))
+ await frame.locator('[data-preview-action="previous"]').click()
+ expect(await frame.evaluate(() => (window as any).editorApp.timeline.currentTime)).toBe(0)
+ await shot(page, info, '46-preview-navigation')
+ await page.keyboard.press('Escape')
+ await expect.poll(() => frame.evaluate(() => (window as any).editorApp.preview)).toBe(false)
+
+ await selectMesh(frame)
+ const autoValue = `AUTO-SAVE-${id}`
+ const automaticResponse = page.waitForResponse(response => response.request().method() === 'PUT'
+ && new URL(response.url()).pathname.endsWith(`/content/projects/${id}`)
+ && String(response.request().postData() || '').includes(autoValue), { timeout: 55_000 })
+ await frame.locator('#custom-resource-code').fill(autoValue)
+ await frame.locator('#custom-resource-code').press('Tab')
+ // Do not click save: this explicitly waits for the real 30-second timer.
+ expect((await automaticResponse).status()).toBe(200)
+ await expect.poll(() => frame.evaluate(() => !(window as any).editorApp.apiSession.savePending)).toBe(true)
+ await page.reload(); frame = await readyFrame(page)
+ await selectMesh(frame)
+ await expect(frame.locator('#project-name')).toHaveValue(renamed)
+ await expect(frame.locator('#custom-resource-code')).toHaveValue(autoValue)
+ await expect.poll(() => frame.locator('#fps-status').innerText()).toMatch(/^[1-9]\d*$/)
+ const stats = await frame.evaluate(() => ({ objects: document.querySelector('#object-status')?.textContent,
+ triangles: document.querySelector('#triangle-status')?.textContent, fps: document.querySelector('#fps-status')?.textContent }))
+ expect(stats.objects).toMatch(/\d+ 节点 \/ \d+ 网格/)
+ expect(Number(stats.triangles?.replaceAll(',', ''))).toBeGreaterThan(0)
+ await activate(frame, 'console')
+ await shot(page, info, '47-navigation-autosave-log')
+ evidence('46-navigation-panels-autosave', { status: 'PASS', kind: 'e2e+api', testTitle: info.title,
+ featureIds: ['project.name', 'project.autosave', 'view.orbit', 'view.focus', 'view.panels', 'view.preview-step', 'feedback.log', 'feedback.stats', 'property.identity', 'property.binding', 'selection.scope', 'component.inspect'],
+ projectId: id, focused, rotated, panned, focusTargetScreen: targetScreen, panelModes, logCount, stats,
+ scope: '真实鼠标镜头操作;聚焦只验证相机目标;真实30秒自动保存,无测试加速定时器;统计内存估值未当作性能结论。' })
+})
diff --git a/tests/model-editor-recovery-closure.spec.ts b/tests/model-editor-recovery-closure.spec.ts
new file mode 100644
index 0000000..d4587f0
--- /dev/null
+++ b/tests/model-editor-recovery-closure.spec.ts
@@ -0,0 +1,195 @@
+import fs from 'node:fs'
+import path from 'node:path'
+import { expect, test, type Frame, type Page } from '@playwright/test'
+import {
+ call, closeSession, evidence, fixture, openEditor, readyFrame, report,
+ saveEditor, selectMesh, shot, signIn, writeFixture,
+} from './model-editor-closure-helpers'
+
+test.setTimeout(180_000)
+test.afterEach(async ({ page }) => { await closeSession(page) })
+const endpoint = (id: string) => `tran/v1/content/projects/${id}`
+
+async function read(token: string, id: string) {
+ const response = await call(endpoint(id), token)
+ expect(response.status, response.message).toBe(200)
+ return response.data
+}
+
+async function copyModel(token: string, label: string) {
+ const sourceId = String(fixture().originals.fox.id)
+ const source = await read(token, sourceId)
+ const name = `闭环验收-${label}-${Date.now().toString(36)}`
+ const copied = await call(`${endpoint(sourceId)}/copy`, token, { name, version: source.project.version })
+ expect(copied.status, copied.message).toBe(200)
+ const id = String(copied.data.project.id)
+ expect(['156', '157', String(fixture().models.fox.id), String(fixture().models.truck.id)]).not.toContain(id)
+ const latest = fixture()
+ latest.extraProjectIds = [...new Set([...(latest.extraProjectIds || []), id])]
+ latest.recoveryClosureModels = { ...(latest.recoveryClosureModels || {}), [label]: { id, name } }
+ writeFixture(latest)
+ return { id, name }
+}
+
+async function currentFrame(page: Page) {
+ await expect.poll(() => page.frames().some(f => f.url().includes('/legacy-model-editor/editor.html'))).toBe(true)
+ return page.frames().find(f => f.url().includes('/legacy-model-editor/editor.html'))!
+}
+
+async function editMeshName(frame: Frame, name: string) {
+ await selectMesh(frame)
+ await frame.locator('#object-name-input').fill(name)
+ await frame.locator('#object-name-input').press('Tab')
+ expect(await frame.evaluate(() => (window as any).editorApp.selected.name)).toBe(name)
+}
+
+function projectCommand(detail: any, description: string) {
+ const p = detail.project
+ return {
+ type: p.type, code: p.code, name: p.name, description, categoryCode: p.categoryCode,
+ coverUri: p.coverUri, content: detail.currentVersion.content, version: p.version,
+ dependencies: detail.currentVersion.dependencies.map((item: any) => ({
+ targetProjectId: item.targetProjectId, targetVersionId: item.targetVersionId,
+ relationType: item.relationType, required: item.required,
+ })),
+ assets: detail.currentVersion.assets.map((item: any) => ({
+ code: item.code, name: item.name, type: item.type, storageUri: item.storageUri,
+ mimeType: item.mimeType, sizeBytes: item.sizeBytes, sha256: item.sha256,
+ metadata: item.metadata, status: item.status, sortOrder: item.sortOrder,
+ })),
+ changeSummary: '闭环验收:模拟另一编辑器在隔离测试项目中保存元数据',
+ }
+}
+
+test('真实未保存稿同版本恢复并显式保存;旧版本恢复稿下载隔离保留', async ({ page }, info) => {
+ const token = await signIn(page)
+ const model = await copyModel(token, '恢复副本')
+ let frame = await openEditor(page, model.id)
+ const baseline = await read(token, model.id)
+ const restoredName = '闭环验收-真实未保存恢复'
+ await editMeshName(frame, restoredName)
+ // Reload exercises the actual beforeunload checkpoint. No storage state is seeded.
+ await page.reload()
+ frame = await currentFrame(page)
+ await expect(frame.locator('#modal-title')).toHaveText('发现同版本未保存恢复副本')
+ expect((await read(token, model.id)).project.version).toBe(baseline.project.version)
+ await shot(page, info, '60-same-version-real-recovery')
+ await frame.locator('[data-model-recovery-action="restore"]').click()
+ frame = await readyFrame(page)
+ expect(await frame.evaluate(() => (window as any).editorApp.apiRecoveryAwaitingExplicitSave)).toBe(true)
+ await selectMesh(frame)
+ await expect(frame.locator('#object-name-input')).toHaveValue(restoredName)
+ expect((await read(token, model.id)).project.version).toBe(baseline.project.version)
+ await saveEditor(frame)
+ const restored = await read(token, model.id)
+ expect(restored.project.version).toBeGreaterThan(baseline.project.version)
+ expect(Object.values(restored.currentVersion.content.model.objects).some((o: any) => o.name === restoredName)).toBe(true)
+ expect(await frame.evaluate(() => (window as any).editorApp.apiRecoveryAwaitingExplicitSave)).toBe(false)
+ await shot(page, info, '61-recovered-draft-explicitly-saved')
+
+ const staleName = '闭环验收-旧版本需人工合并'
+ await editMeshName(frame, staleName)
+ const concurrent = await call(endpoint(model.id), token,
+ projectCommand(restored, '隔离项目的并发元数据更新,保留已保存模型内容'), 'PUT')
+ expect(concurrent.status, concurrent.message).toBe(200)
+ expect(concurrent.data.project.version).toBeGreaterThan(restored.project.version)
+ await page.reload()
+ frame = await currentFrame(page)
+ await expect(frame.locator('#modal-title')).toHaveText('发现不同版本恢复副本')
+ await expect(frame.locator('[data-model-recovery-action="restore"]')).toHaveCount(0)
+ await shot(page, info, '62-stale-version-recovery-protection')
+ const downloading = page.waitForEvent('download', { timeout: 30_000 })
+ await frame.locator('[data-model-recovery-action="keep"]').click()
+ const downloaded = await downloading
+ const folder = path.join(report, 'downloads')
+ fs.mkdirSync(folder, { recursive: true })
+ const file = path.join(folder, `recovery-${model.id}-isolated.json`)
+ await downloaded.saveAs(file)
+ const retained = JSON.parse(fs.readFileSync(file, 'utf8'))
+ expect(retained.projectId).toBe(model.id)
+ expect(retained.projectVersion).toBe(restored.project.version)
+ expect(Object.values(retained.document.model.objects).some((o: any) => o.name === staleName)).toBe(true)
+ frame = await readyFrame(page)
+ const after = await read(token, model.id)
+ expect(after.project.version).toBe(concurrent.data.project.version)
+ expect(after.currentVersion.content).toEqual(concurrent.data.currentVersion.content)
+ const quarantined = await frame.evaluate(id => Object.keys(localStorage)
+ .filter(key => key.startsWith(`dt-editor-project-api-quarantine-v1:${id}:`))
+ .some(key => JSON.parse(localStorage.getItem(key) || 'null')?.document?.model), model.id)
+ expect(quarantined).toBe(true)
+ evidence('60-project-recovery', { status: 'PASS', kind: 'e2e+api', testTitle: info.title,
+ featureIds: ['project.recovery'], projectId: model.id,
+ sameVersion: baseline.project.version, explicitSaveVersion: restored.project.version,
+ concurrentVersion: concurrent.data.project.version, retainedFilename: path.basename(file),
+ scope: '通过真实beforeunload创建未保存稿;同版恢复后仅显式保存落库;旧版只下载隔离,不自动覆盖服务端。丢弃/跨标签竞态另由既有恢复合同测试覆盖。' })
+})
+
+test('受控 GLB 下载失败阻止编辑,重试成功;发布预检开关无写入且拒绝错误资源 JSON', async ({ page }, info) => {
+ const token = await signIn(page)
+ const model = await copyModel(token, '加载与预检')
+ const baseline = await read(token, model.id)
+ const assetUrl = `**/api/tran/v1/content/projects/${model.id}/assets/*/content`
+ let failedDownloads = 0
+ await page.route(assetUrl, async route => {
+ failedDownloads += 1
+ await route.fulfill({ status: 503, contentType: 'application/json', body: JSON.stringify({
+ code: 503, message: '闭环验收:模拟受控GLB暂时不可用', data: null, requestId: 'closure-glb-unavailable',
+ }) })
+ })
+ try {
+ await page.goto(`/content/models/${model.id}/edit`)
+ let frame = await currentFrame(page)
+ await expect(frame.locator('#loading-overlay')).toContainText('模型工程加载失败')
+ await expect(frame.locator('[data-action="save"]').first()).toBeDisabled()
+ await expect(frame.locator('[data-action="publish"]').first()).toBeDisabled()
+ expect(await frame.evaluate(() => (window as any).editorApp.apiEditorHydrated)).toBe(false)
+ expect(failedDownloads).toBeGreaterThan(0)
+ expect((await read(token, model.id)).project.version).toBe(baseline.project.version)
+ await shot(page, info, '63-controlled-glb-download-failed')
+ await page.unroute(assetUrl)
+ const previousFrame = frame
+ const detached = page.waitForEvent('framedetached', { predicate: candidate => candidate === previousFrame })
+ await frame.locator('[data-host-model-load-retry]').click()
+ await detached
+ frame = await readyFrame(page)
+ expect(await frame.evaluate(() => {
+ let count = 0
+ ;(window as any).editorApp.modelPivot.traverse((o: any) => { if (o.isMesh) count += 1 })
+ return count
+ })).toBeGreaterThan(0)
+ await frame.locator('[data-action="focus"]').first().click()
+ await shot(page, info, '64-controlled-glb-retry-loaded')
+
+ const beforePanel = await read(token, model.id)
+ await frame.locator('[data-action="publish"]').first().click()
+ await expect(frame.locator('#publish-modal')).toHaveAttribute('aria-hidden', 'false')
+ expect(await frame.locator('.publish-check').count()).toBeGreaterThanOrEqual(3)
+ await expect(frame.locator('#publish-checks')).toContainText('模型资源已绑定')
+ await shot(page, info, '65-publish-preflight-readonly')
+ await frame.locator('#publish-modal [data-publish-close]').first().click()
+ await expect(frame.locator('#publish-modal')).toHaveAttribute('aria-hidden', 'true')
+ const afterPanel = await read(token, model.id)
+ expect(afterPanel.project.version).toBe(beforePanel.project.version)
+ expect(afterPanel.currentVersion.content).toEqual(beforePanel.currentVersion.content)
+
+ const exportPromise = page.waitForEvent('download', { timeout: 30_000 })
+ await frame.locator('[data-action="save-as"]').first().click()
+ const exported = await exportPromise
+ const temporary = path.join(report, 'downloads', `preflight-${model.id}.json`)
+ fs.mkdirSync(path.dirname(temporary), { recursive: true })
+ await exported.saveAs(temporary)
+ const invalid = JSON.parse(fs.readFileSync(temporary, 'utf8'))
+ invalid.modelResource.assetCode = 'CLOSURE_NONEXISTENT_CONTROLLED_MODEL'
+ invalid.modelResource.code = 'CLOSURE_NONEXISTENT_CONTROLLED_MODEL'
+ const originalCode = afterPanel.currentVersion.content.modelResource.assetCode
+ await frame.locator('#project-file-input').setInputFiles({ name: 'missing-controlled-model.json',
+ mimeType: 'application/json', buffer: Buffer.from(JSON.stringify(invalid)) })
+ await expect(frame.locator('.toast.error').filter({ hasText: '导入失败' }).last()).toBeVisible()
+ expect(await frame.evaluate(() => (window as any).editorApp.activeDescriptor.assetCode)).toBe(originalCode)
+ expect((await read(token, model.id)).project.version).toBe(beforePanel.project.version)
+ evidence('63-loading-publish-preflight', { status: 'PASS', kind: 'e2e+api', testTitle: info.title,
+ featureIds: ['resource.loading', 'publish.preflight'], projectId: model.id, failedDownloads,
+ retainedVersion: beforePanel.project.version,
+ scope: '真实受控下载定向503→中性失败与禁止编辑→解除故障后UI重试成功;预检面板开关不写项目,错误资源JSON在替换当前模型之前被拒绝。服务端发布时code/URI一致性拒绝由ContentProjectServiceTest合同回归覆盖,未把面板绿标代替服务端校验。' })
+ } finally { await page.unroute(assetUrl) }
+})
diff --git a/tests/scene-editor-async-closure.spec.ts b/tests/scene-editor-async-closure.spec.ts
new file mode 100644
index 0000000..10289c1
--- /dev/null
+++ b/tests/scene-editor-async-closure.spec.ts
@@ -0,0 +1,51 @@
+import { test, expect } from '@playwright/test'
+import { signIn, freshScene, openEditor, ready, diagnostics, action, save, shot, evidence, closeSession, call } from './scene-editor-closure-helpers'
+test.afterEach(async ({page})=>closeSession(page))
+const setName=async(page:any,name:string)=>{const input=page.locator('[data-inspector-field="name"]');await input.fill(name);await input.press('Tab')}
+
+test('加载失败重试与加载互斥:不展示可写半场景、恢复网络后模型完整加载',async({page},info)=>{
+ const token=await signIn(page),copy=await freshScene(token,'fox','加载互斥'),id=copy.project.id
+ const endpoint=`**/api/tran/v1/content/projects/${id}`
+ await page.route(endpoint,route=>route.fulfill({status:503,contentType:'application/json',body:JSON.stringify({code:503,message:'验收模拟场景加载失败'})}))
+ await page.goto(`/content/scenes/${id}/edit`)
+ await expect(page.getByText('场景工程加载失败',{exact:true})).toBeVisible()
+ await expect(page.locator('.se-shell')).toHaveCount(0)
+ await shot(page,info,'60-scene-load-failure-retry');await page.unroute(endpoint)
+ let release!:()=>void;const gate=new Promise(resolve=>release=resolve)
+ await page.route('**/api/tran/v1/content/projects/*/assets/*/content',async route=>{await gate;await route.continue()})
+ await page.getByRole('button',{name:'重新加载',exact:true}).click()
+ await expect(page.locator('.se-shell')).toBeVisible()
+ await expect.poll(async()=>Boolean((await diagnostics(page))?.pendingLoads)).toBe(true)
+ for(const name of ['new','open','save','export','verify','publish','preview'])await expect(action(page,name)).toBeDisabled()
+ await page.getByRole('button',{name:'关闭数字场景编辑',exact:true}).click()
+ await page.getByRole('button',{name:'关闭标签',exact:true}).click()
+ await expect(page.locator('.el-message-box')).toHaveCount(0)
+ await expect(page.locator('.se-shell')).toBeVisible()
+ await shot(page,info,'61-model-loading-blocks-partial-save')
+ release();await ready(page);await page.unroute('**/api/tran/v1/content/projects/*/assets/*/content')
+ expect((await diagnostics(page)).objects.some((o:any)=>o.targetProjectId==='157')).toBe(true)
+ evidence('loading-retry',{projectId:id,ready:await diagnostics(page)})
+})
+
+test('延迟保存快照:重复点击只提交一次,期间新编辑保留,旧恢复稿不会误报冲突',async({page},info)=>{
+ const token=await signIn(page),copy=await freshScene(token,'truck','并发保存'),id=copy.project.id
+ await openEditor(page,id);const truck=(await diagnostics(page)).objects.find((o:any)=>o.targetProjectId==='156')
+ await page.locator(`#se-tree [data-object-id="${truck.sceneId}"]`).click();await setName(page,'提交中的快照A')
+ let release!:()=>void;const gate=new Promise(resolve=>release=resolve),submitted:any[]=[]
+ const endpoint=`**/api/tran/v1/content/projects/${id}`
+ await page.route(endpoint,async route=>{if(route.request().method()!=='PUT')return route.continue();submitted.push(route.request().postDataJSON());await gate;await route.continue()})
+ await action(page,'save').click();await expect.poll(()=>submitted.length).toBe(1)
+ await expect(action(page,'save')).toBeDisabled()
+ await page.keyboard.press('Control+s');expect(submitted.length).toBe(1)
+ await setName(page,'提交之后的新修改B')
+ await expect.poll(()=>page.evaluate(()=>Object.keys(localStorage).some(k=>k.includes('scene-api-recovery')))).toBe(true)
+ release();await expect.poll(async()=>(await diagnostics(page)).saving).toBe(false)
+ expect((await diagnostics(page)).dirty).toBe(true);expect(submitted.length).toBe(1)
+ const first=(await call(`tran/v1/content/projects/${id}`,token)).data
+ expect(first.currentVersion.content.objects.find((o:any)=>o.sceneId===truck.sceneId).name).toBe('提交中的快照A')
+ await shot(page,info,'62-new-edits-preserved-during-save');await page.unroute(endpoint);await save(page)
+ await page.reload();await ready(page)
+ expect((await diagnostics(page)).objects.find((o:any)=>o.sceneId===truck.sceneId).name).toBe('提交之后的新修改B')
+ await expect(page.locator('.el-message-box')).toHaveCount(0)
+ evidence('save-concurrency',{projectId:id,firstVersion:first.project.version,submissionsBeforeRetry:submitted.length,reopened:await diagnostics(page)})
+})
diff --git a/tests/scene-editor-closure-helpers.ts b/tests/scene-editor-closure-helpers.ts
new file mode 100644
index 0000000..6e609bb
--- /dev/null
+++ b/tests/scene-editor-closure-helpers.ts
@@ -0,0 +1,53 @@
+import fs from 'node:fs'
+import path from 'node:path'
+import { test, expect, type Page, type TestInfo } from '@playwright/test'
+// @ts-ignore Report metadata does not perform application actions.
+import { sceneEvidenceBindings } from '../tools/scene-editor-evidence-bindings.mjs'
+// @ts-ignore Private credentials stay outside the report and repository.
+import { call, login, teacherLogin, freshScene, registerProject, report, work, fixture } from '../tools/scene-editor-closure-live.mjs'
+export { call, freshScene, registerProject, report, work, fixture }
+export async function signIn(page: Page, account = 'teacher') {
+ const session = account === 'teacher' ? await teacherLogin() : await login(account)
+ await page.addInitScript(({ accessToken, refreshToken }) => {
+ if (!sessionStorage.getItem('scene-closure-initialized')) {
+ sessionStorage.setItem('unreal-tran:web:access-token:v1', accessToken)
+ sessionStorage.setItem('unreal-tran:web:refresh-token:v1', refreshToken || '')
+ sessionStorage.setItem('scene-closure-initialized', '1')
+ }
+ }, { accessToken: session.accessToken, refreshToken: session.refreshToken })
+ return session.accessToken as string
+}
+export const diagnostics = (page: Page) => page.evaluate(() => (window as any).__sceneEditorDiagnostics?.())
+export async function ready(page: Page) {
+ await expect(page.locator('.se-shell')).toBeVisible({ timeout: 90000 })
+ await expect.poll(async () => { const d = await diagnostics(page); return Boolean(d?.readySettled && d.pendingLoads === 0) }, { timeout: 90000 }).toBe(true)
+}
+export async function openEditor(page: Page, id: string) { await page.goto(`/content/scenes/${id}/edit`); await ready(page) }
+export const action = (page: Page, name: string) => page.locator(`.se-shell [data-action="${name}"]`).first()
+export async function save(page: Page) {
+ await action(page, 'save').click()
+ await expect.poll(async () => { const d = await diagnostics(page); return !d.saving && !d.dirty }).toBe(true)
+}
+export async function exportDocument(page: Page) {
+ const waiting = page.waitForEvent('download')
+ await action(page, 'export').click()
+ const download = await waiting
+ const stream = await download.createReadStream()
+ const chunks: Buffer[] = []; for await (const chunk of stream!) chunks.push(chunk)
+ return JSON.parse(Buffer.concat(chunks).toString('utf8'))
+}
+export async function shot(page: Page, info: TestInfo, name: string) {
+ const folder = path.join(report, 'screenshots'); fs.mkdirSync(folder, { recursive: true })
+ const file = path.join(folder, `${name}.png`)
+ await page.screenshot({ path: file, fullPage: true, animations: 'disabled' })
+ await info.attach(name, { path: file, contentType: 'image/png' })
+}
+export function evidence(name: string, data: unknown) {
+ const folder = path.join(report, 'evidence'); fs.mkdirSync(folder, { recursive: true })
+ const metadata = { kind: 'browser', status: 'PASS', testTitle: test.info().title, ...(sceneEvidenceBindings[name] || {}) }
+ fs.writeFileSync(path.join(folder, `${name}.json`), JSON.stringify({ time: new Date().toISOString(), data: { ...metadata, ...(data as object) } }, null, 2))
+}
+export async function closeSession(page: Page) {
+ const token = await page.evaluate(() => sessionStorage.getItem('unreal-tran:web:access-token:v1')).catch(() => null)
+ if (token) await call('auth/v1/auth/logout', token, {}).catch(() => {})
+}
diff --git a/tests/scene-editor-downstream-closure.spec.ts b/tests/scene-editor-downstream-closure.spec.ts
new file mode 100644
index 0000000..e17111f
--- /dev/null
+++ b/tests/scene-editor-downstream-closure.spec.ts
@@ -0,0 +1,29 @@
+import {test,expect} from '@playwright/test'
+import {signIn,freshScene,openEditor,action,shot,evidence,closeSession,call,registerProject} from './scene-editor-closure-helpers'
+test.afterEach(async({page})=>closeSession(page))
+for(const [key,trainingId] of [['fox','161'],['truck','162']])test(`${key}:场景发布后在真实训练编排选择、保存并重开静态模型`,async({page},info)=>{
+ const token=await signIn(page),scene=await freshScene(token,key,'下游训练'),id=scene.project.id
+ await openEditor(page,id);await action(page,'publish').click()
+ await expect(page.locator('#se-save-state')).toHaveText('已发布',{timeout:60000})
+ const published=(await call(`tran/v1/content/projects/${id}`,token)).data
+ const trainingSource=(await call(`tran/v1/content/projects/${trainingId}`,token)).data
+ const copied=await call(`tran/v1/content/projects/${trainingId}/copy`,token,{name:`场景闭环-${key}-训练引用-${Date.now()}`,version:trainingSource.project.version})
+ expect(copied.status).toBe(200);registerProject(copied.data.project)
+ const targetId=copied.data.project.id
+ await page.goto(`/content/training-projects/${targetId}/legacy`)
+ await expect(page.locator('.te-shell')).toBeVisible({timeout:90000})
+ await expect(page.locator('[data-role="scene-viewport"]')).toHaveAttribute('data-state','ready',{timeout:90000})
+ await page.locator('.te-shell [data-action="open-published-scenes"]').click()
+ const option=page.locator('[data-action="choose-published-scene"]').filter({hasText:scene.project.name})
+ await expect(option).toHaveCount(1);await option.click()
+ await expect(page.locator('[data-role="scene-viewport"]')).toHaveAttribute('data-state','ready',{timeout:90000})
+ const response=page.waitForResponse(r=>r.url().endsWith(`/content/projects/${targetId}`)&&r.request().method()==='PUT')
+ await page.locator('.te-shell [data-action="save"]').first().click();expect((await response).status()).toBe(200)
+ const saved=(await call(`tran/v1/content/projects/${targetId}`,token)).data
+ const dependency=saved.currentVersion.dependencies.find((d:any)=>d.relationType==='TRAINING_SCENE')
+ expect(dependency.targetProjectId).toBe(id);expect(dependency.targetVersionId).toBe(published.currentVersion.id)
+ await page.reload();await expect(page.locator('[data-role="scene-viewport"]')).toHaveAttribute('data-state','ready',{timeout:90000})
+ await expect(page.locator('.te-scene-canvas')).toBeVisible()
+ await shot(page,info,`70-${key}-training-scene-reopened`)
+ evidence(`downstream-${key}`,{featureIds:['publish.downstream'],projectId:id,sceneVersionId:published.currentVersion.id,trainingProjectId:targetId,dependency,staticOnly:true})
+})
diff --git a/tests/scene-editor-permissions-closure.spec.ts b/tests/scene-editor-permissions-closure.spec.ts
new file mode 100644
index 0000000..e953e75
--- /dev/null
+++ b/tests/scene-editor-permissions-closure.spec.ts
@@ -0,0 +1,310 @@
+import { test, expect, type Page } from '@playwright/test'
+import crypto from 'node:crypto'
+import { action, call, closeSession, diagnostics, evidence, freshScene, openEditor, ready, save, shot } from './scene-editor-closure-helpers'
+// @ts-ignore Credentials and temporary-user passwords remain in the private fixture only.
+import { fixture, writeFixture, login } from '../tools/scene-editor-closure-live.mjs'
+
+type Kind = 'update' | 'publish' | 'no-scene'
+const grants: Record = {
+ update: ['dashboard.view', 'content.scene', 'content.scene.update'],
+ publish: ['dashboard.view', 'content.scene', 'content.scene.publish'],
+ 'no-scene': ['dashboard.view'],
+}
+
+// Each mutation reloads the current fixture synchronously, so parallel independent
+// test authors do not overwrite project registrations made while an API call waits.
+function registerPrivateUser(user: any) {
+ const current = fixture()
+ current.extraUsers = [...(current.extraUsers || []), user]
+ writeFixture(current)
+}
+function registerPrivateRole(id: string) {
+ const current = fixture()
+ current.roleIds = [...(current.roleIds || []), id]
+ writeFixture(current)
+}
+
+async function prepareActor(page: Page, kind: Kind, includeReadOnlyRole = false) {
+ const maintenance = await login('root')
+ const rootToken = maintenance.accessToken
+ let issuedToken: string | null = null
+ let prepared = false
+ try {
+ const me = await call('auth/v1/auth/me', rootToken)
+ expect(me.status).toBe(200)
+ const roles = await call('auth/v1/roles/all', rootToken)
+ expect(roles.status).toBe(200)
+ const teacher = (roles.data.records || roles.data).find((role: any) => role.code === 'teacher')
+ expect(teacher?.id).toBeTruthy()
+ const suffix = crypto.randomBytes(4).toString('hex')
+ const role = await call('auth/v1/roles', rootToken, {
+ code: `scene_${kind.replace('-', '_')}_${suffix}`,
+ name: `场景验收·${kind === 'update' ? '仅编辑' : kind === 'publish' ? '仅发布' : '无场景权限'}-${suffix}`,
+ shortName: '测', description: 'UTE2E-SCENE-CLOSURE-20260906',
+ dataScope: 'ALL', enabled: true, sortOrder: 95, departmentScopeIds: [],
+ })
+ expect(role.status, 'Create isolated role').toBe(200)
+ registerPrivateRole(role.data.id)
+ const permissionSave = await call(`auth/v1/permissions/roles/${role.data.id}`, rootToken, {
+ permissionCodes: grants[kind], version: role.data.dataVersion ?? role.data.version,
+ }, 'PUT')
+ expect(permissionSave.status, 'Save isolated action permissions').toBe(200)
+ let readOnlyRole: any = null
+ if (includeReadOnlyRole) {
+ const extra = await call('auth/v1/roles', rootToken, {
+ code: `scene_read_${suffix}`, name: `场景验收·仅查看-${suffix}`, shortName: '测',
+ description: 'UTE2E-SCENE-CLOSURE-20260906', dataScope: 'ALL', enabled: true,
+ sortOrder: 96, departmentScopeIds: [],
+ })
+ expect(extra.status).toBe(200)
+ readOnlyRole = extra.data
+ registerPrivateRole(readOnlyRole.id)
+ const saved = await call(`auth/v1/permissions/roles/${readOnlyRole.id}`, rootToken, {
+ permissionCodes: ['dashboard.view', 'content.scene'], version: readOnlyRole.dataVersion ?? readOnlyRole.version,
+ }, 'PUT')
+ expect(saved.status).toBe(200)
+ }
+ const password = `Sc!${crypto.randomBytes(7).toString('hex')}`
+ const created = await call('auth/v1/users', rootToken, {
+ username: `scene_perm_${suffix}`, displayName: `场景权限验收${suffix.slice(0, 4)}`,
+ departmentId: me.data.departmentId, roleIds: [teacher.id, role.data.id, ...(readOnlyRole ? [readOnlyRole.id] : [])], defaultRoleId: teacher.id,
+ enabled: true, password, remark: 'UTE2E-SCENE-CLOSURE-20260906',
+ })
+ expect(created.status, 'Create isolated multi-role account').toBe(200)
+ registerPrivateUser({ ...created.data, password })
+ const signedIn = await call('auth/v1/auth/login', null, {
+ userId: created.data.id, password, roleCode: 'teacher', departmentId: me.data.departmentId, rememberMe: false,
+ })
+ expect(signedIn.status, 'Sign in through the existing teacher entry').toBe(200)
+ let session = signedIn.data
+ issuedToken = session.accessToken
+ const switched = await call('auth/v1/auth/active-role', session.accessToken, { roleId: role.data.id }, 'PUT')
+ expect(switched.status, 'Activate the isolated limited role').toBe(200)
+ if (switched.data?.accessToken) session = switched.data
+ issuedToken = session.accessToken
+ const actor = await call('auth/v1/auth/me', session.accessToken)
+ expect(actor.status).toBe(200)
+ expect(actor.data.authorizationMode).toBe('SINGLE_ACTIVE')
+ expect(String(actor.data.activeRoleId)).toBe(String(role.data.id))
+ expect(actor.data.permissions).not.toContain('content.model')
+ expect(actor.data.permissions).not.toContain('content.model.update')
+ expect(actor.data.permissions).not.toContain(kind === 'update' ? 'content.scene.publish' : 'content.scene.update')
+ await page.addInitScript(({ accessToken, refreshToken }) => {
+ if (!sessionStorage.getItem('scene-permission-closure-initialized')) {
+ sessionStorage.setItem('unreal-tran:web:access-token:v1', accessToken)
+ sessionStorage.setItem('unreal-tran:web:refresh-token:v1', refreshToken || '')
+ sessionStorage.setItem('scene-permission-closure-initialized', '1')
+ }
+ }, { accessToken: session.accessToken, refreshToken: session.refreshToken })
+ const copy = kind === 'no-scene' ? null : await freshScene(rootToken, 'fox', `权限-${kind}`)
+ prepared = true
+ return { token: session.accessToken as string, copy, permissionCodes: grants[kind], roleId: role.data.id, readOnlyRole }
+ } finally {
+ if (!prepared && issuedToken) await call('auth/v1/auth/logout', issuedToken, {}).catch(() => {})
+ await call('auth/v1/auth/logout', rootToken, {}).catch(() => {})
+ }
+}
+
+test.afterEach(async ({ page }) => closeSession(page))
+
+test('仅场景编辑权限:固定模型可加载和保存,不能发布或访问模型页面', async ({ page }, info) => {
+ const actor = await prepareActor(page, 'update'), id = actor.copy.project.id
+ await openEditor(page, id)
+ const before = await diagnostics(page)
+ expect(before.readOnly).toBe(false)
+ const model = before.objects.find((object: any) => object.targetProjectId === '157')
+ expect(model).toBeTruthy()
+ expect(model.parts.length).toBeGreaterThan(0)
+ await expect(action(page, 'save')).toBeEnabled()
+ await expect(action(page, 'publish')).toBeDisabled()
+ await expect(action(page, 'new')).toBeDisabled()
+ await page.locator(`#se-tree [data-object-id="${model.sceneId}"]`).click()
+ await action(page, 'focus').click()
+ const name = `仅场景编辑权限保存-${id}`
+ const input = page.locator('[data-inspector-field="name"]')
+ await input.fill(name)
+ await input.press('Tab')
+ await save(page)
+ const saved = await call(`tran/v1/content/projects/${id}`, actor.token)
+ expect(saved.status).toBe(200)
+ const dependencies = (values: any[]) => values.map(value => [value.targetProjectId, value.targetVersionId, value.relationType, value.required])
+ expect(dependencies(saved.data.currentVersion.dependencies)).toEqual(dependencies(actor.copy.currentVersion.dependencies))
+ expect(saved.data.currentVersion.content.objects.find((object: any) => object.sceneId === model.sceneId).name).toBe(name)
+ await page.reload(); await ready(page)
+ expect((await diagnostics(page)).objects.find((object: any) => object.sceneId === model.sceneId).name).toBe(name)
+ await page.locator(`#se-tree [data-object-id="${model.sceneId}"]`).click()
+ await action(page, 'focus').click()
+ const forbiddenModel = await call('tran/v1/content/projects/157', actor.token)
+ expect(forbiddenModel.status).toBe(403)
+ const forbiddenPublish = await call(`tran/v1/content/projects/${id}/publish`, actor.token, { version: saved.data.project.version })
+ expect(forbiddenPublish.status).toBe(403)
+ await shot(page, info, '51-scene-update-only-saved')
+ evidence('scene-update-only', { featureIds: ['permission.update', 'permission.publish', 'publish.fixed-model'], status: 'PASS',
+ kind: 'browser', testTitle: info.title, projectId: id, permissionCodes: actor.permissionCodes, dependencies: dependencies(saved.data.currentVersion.dependencies),
+ savedName: name, directModelReadStatus: forbiddenModel.status, directPublishStatus: forbiddenPublish.status })
+})
+
+test('仅场景发布权限:浏览器发布已存副本,不发送保存或上传请求', async ({ page }, info) => {
+ const actor = await prepareActor(page, 'publish'), id = actor.copy.project.id
+ const writes: Array<{ method: string; path: string }> = []
+ page.on('request', request => {
+ const pathname = new URL(request.url()).pathname
+ if (pathname.startsWith(`/api/tran/v1/content/projects/${id}`) && !['GET', 'HEAD'].includes(request.method())) {
+ writes.push({ method: request.method(), path: pathname })
+ }
+ })
+ await openEditor(page, id)
+ expect((await diagnostics(page)).readOnly).toBe(true)
+ await expect(action(page, 'save')).toBeDisabled()
+ await expect(action(page, 'publish')).toBeEnabled()
+ const response = page.waitForResponse(value => value.url().endsWith(`/content/projects/${id}/publish`) && value.request().method() === 'POST')
+ await action(page, 'publish').click()
+ expect((await response).status()).toBe(200)
+ await expect.poll(async () => (await diagnostics(page)).publishing).toBe(false)
+ await expect(action(page, 'save')).toBeDisabled()
+ const published = await call(`tran/v1/content/projects/${id}`, actor.token)
+ expect(published.status).toBe(200)
+ expect(published.data.project.status).toBe('PUBLISHED')
+ expect(published.data.currentVersion.content).toEqual(actor.copy.currentVersion.content)
+ expect(writes).toEqual([{ method: 'POST', path: `/api/tran/v1/content/projects/${id}/publish` }])
+ await shot(page, info, '52-scene-publish-only-no-save')
+ evidence('scene-publish-only', { featureIds: ['permission.publish', 'publish.only', 'publish.fixed-model'], status: 'PASS', kind: 'browser', testTitle: info.title,
+ projectId: id, permissionCodes: actor.permissionCodes, versionId: published.data.project.publishedVersionId, writes })
+})
+
+test('无场景页面权限:编辑路由和直接内容接口均拒绝访问', async ({ page }, info) => {
+ const actor = await prepareActor(page, 'no-scene'), id = fixture().originals.fox.id
+ await page.goto(`/content/scenes/${id}/edit`)
+ await expect(page).not.toHaveURL(new RegExp(`/content/scenes/${id}/edit`))
+ await expect(page.locator('.se-shell')).toHaveCount(0)
+ const denied = await call(`tran/v1/content/projects/${id}`, actor.token)
+ expect(denied.status).toBe(403)
+ await shot(page, info, '53-scene-route-access-denied')
+ evidence('scene-no-page-permission', { featureIds: ['permission.publish'], status: 'PASS', kind: 'browser', testTitle: info.title,
+ permissionCodes: actor.permissionCodes, route: new URL(page.url()).pathname, directReadStatus: denied.status })
+})
+
+async function editFoxName(page: Page, name: string) {
+ const model = (await diagnostics(page)).objects.find((object: any) => object.targetProjectId === '157')
+ expect(model).toBeTruthy()
+ await page.locator(`#se-tree [data-object-id="${model.sceneId}"]`).click()
+ const input = page.locator('[data-inspector-field="name"]')
+ await input.fill(name); await input.press('Tab')
+ return model.sceneId as string
+}
+
+async function openRoleDialog(page: Page) {
+ await page.getByRole('button', { name: /^账号菜单:/ }).click()
+ await page.getByRole('menuitem', { name: '切换角色', exact: true }).click()
+ const dialog = page.getByRole('dialog', { name: '切换角色', exact: true })
+ await expect(dialog).toBeVisible()
+ return dialog
+}
+
+test('后台场景未保存时切换角色:关闭确认保留草稿,保存切换后重建只读权限', async ({ page }, info) => {
+ const actor = await prepareActor(page, 'update', true), id = actor.copy.project.id
+ const roleRequests: string[] = []
+ page.on('request', request => {
+ if (new URL(request.url()).pathname === '/api/auth/v1/auth/active-role' && request.method() === 'PUT') roleRequests.push('PUT')
+ })
+ await openEditor(page, id)
+ const name = `后台场景保存后切换-${id}`
+ const objectId = await editFoxName(page, name)
+ await page.getByRole('tab', { name: '工作总览', exact: true }).click()
+ let switcher = await openRoleDialog(page)
+ await switcher.getByRole('button', { name: new RegExp(actor.readOnlyRole.name) }).click()
+ const leave = page.getByRole('dialog', { name: '离开场景编辑器', exact: true })
+ await expect(leave).toBeVisible()
+ await leave.locator('.el-message-box__headerbtn').click()
+ await expect(leave).not.toBeVisible()
+ expect(roleRequests).toEqual([])
+ const unchangedRole = await call('auth/v1/auth/me', actor.token)
+ expect(String(unchangedRole.data.activeRoleId)).toBe(String(actor.roleId))
+ const unchangedProject = await call(`tran/v1/content/projects/${id}`, actor.token)
+ expect(unchangedProject.data.project.version).toBe(actor.copy.project.version)
+ expect(unchangedProject.data.currentVersion.content).toEqual(actor.copy.currentVersion.content)
+ await switcher.getByRole('button', { name: '关闭', exact: true }).click()
+ await page.getByRole('tab', { name: '数字场景编辑', exact: true }).click()
+ await ready(page)
+ expect((await diagnostics(page)).dirty).toBe(true)
+ expect((await diagnostics(page)).objects.find((object: any) => object.sceneId === objectId).name).toBe(name)
+ await shot(page, info, '54-scene-role-switch-cancel-keeps-draft')
+ await page.getByRole('tab', { name: '工作总览', exact: true }).click()
+ switcher = await openRoleDialog(page)
+ await switcher.getByRole('button', { name: new RegExp(actor.readOnlyRole.name) }).click()
+ await expect(leave).toBeVisible()
+ await leave.getByRole('button', { name: '保存并离开', exact: true }).click()
+ await expect(page).toHaveURL(/\/dashboard$/)
+ await expect(page.getByRole('button', { name: /^账号菜单:/ })).toContainText(actor.readOnlyRole.name)
+ expect(roleRequests).toEqual(['PUT'])
+ const token = await page.evaluate(() => sessionStorage.getItem('unreal-tran:web:access-token:v1'))
+ const switched = await call('auth/v1/auth/me', token)
+ expect(String(switched.data.activeRoleId)).toBe(String(actor.readOnlyRole.id))
+ expect(switched.data.permissions).not.toContain('content.scene.update')
+ await openEditor(page, id)
+ expect((await diagnostics(page)).readOnly).toBe(true)
+ expect((await diagnostics(page)).objects.find((object: any) => object.sceneId === objectId).name).toBe(name)
+ await expect(action(page, 'save')).toBeDisabled()
+ await expect(action(page, 'publish')).toBeDisabled()
+ const saved = await call(`tran/v1/content/projects/${id}`, token)
+ expect(saved.data.project.version).toBe(actor.copy.project.version + 1)
+ expect(saved.data.currentVersion.content.objects.find((object: any) => object.sceneId === objectId).name).toBe(name)
+ await shot(page, info, '55-scene-role-switch-saved-readonly')
+ evidence('scene-role-switch', { featureIds: ['leave.role-switch', 'leave.cancel', 'leave.save', 'permission.readonly'],
+ status: 'PASS', kind: 'browser', testTitle: info.title, projectId: id, savedName: name,
+ roleChangeRequests: roleRequests.length, savedVersion: saved.data.project.version, readOnly: true })
+})
+
+async function recoveries(page: Page, id: string) {
+ return page.evaluate(projectId => Object.keys(localStorage)
+ .filter(key => key.includes(`edit3dv4:scene-api-recovery:${projectId}:session:`))
+ .map(key => {
+ const value = JSON.parse(localStorage.getItem(key) || '{}')
+ return { key, nonce: value.nonce, version: value.projectVersion,
+ name: value.document?.objects?.find((object: any) => object.targetProjectId === '157')?.name }
+ }), id)
+}
+
+test('双标签各自修改场景:保存和明确丢弃当前页均保留另一页真实恢复稿', async ({ page, context }, info) => {
+ const actor = await prepareActor(page, 'update'), id = actor.copy.project.id
+ const other = await context.newPage()
+ try {
+ await other.addInitScript(token => {
+ sessionStorage.setItem('unreal-tran:web:access-token:v1', token)
+ }, actor.token)
+ await openEditor(page, id)
+ await openEditor(other, id)
+ const nameA = `标签A已保存-${id}`, nameB = `标签B保持未保存-${id}`
+ await editFoxName(page, nameA)
+ await editFoxName(other, nameB)
+ await expect.poll(async () => (await recoveries(page, id)).map(value => value.name).sort()).toEqual([nameA, nameB].sort())
+ const originalB = (await recoveries(page, id)).find(value => value.name === nameB)!
+ const before = await call(`tran/v1/content/projects/${id}`, actor.token)
+ expect(before.data.project.version).toBe(actor.copy.project.version)
+ expect(before.data.currentVersion.content).toEqual(actor.copy.currentVersion.content)
+ await save(page)
+ await expect.poll(async () => recoveries(page, id)).toEqual([originalB])
+ expect((await diagnostics(other)).dirty).toBe(true)
+ expect((await diagnostics(other)).objects.find((object: any) => object.targetProjectId === '157').name).toBe(nameB)
+ const saved = await call(`tran/v1/content/projects/${id}`, actor.token)
+ expect(saved.data.project.version).toBe(actor.copy.project.version + 1)
+ expect(saved.data.currentVersion.content.objects.find((object: any) => object.targetProjectId === '157').name).toBe(nameA)
+ await editFoxName(page, `标签A明确丢弃-${id}`)
+ await expect.poll(async () => (await recoveries(page, id)).length).toBe(2)
+ await page.reload()
+ const recovery = page.getByRole('dialog', { name: '恢复未提交场景', exact: true })
+ await expect(recovery).toBeVisible()
+ await recovery.getByRole('button', { name: '删除副本并使用服务端', exact: true }).click()
+ await ready(page)
+ await expect.poll(async () => recoveries(other, id)).toEqual([originalB])
+ const final = await call(`tran/v1/content/projects/${id}`, actor.token)
+ expect(final.data.project.version).toBe(saved.data.project.version)
+ expect(final.data.currentVersion.content).toEqual(saved.data.currentVersion.content)
+ expect((await diagnostics(other)).dirty).toBe(true)
+ await shot(other, info, '56-scene-other-tab-recovery-preserved')
+ evidence('scene-multi-session', { featureIds: ['recovery.multi-session', 'recovery.autobuffer', 'recovery.discard', 'project.save'],
+ status: 'PASS', kind: 'browser', testTitle: info.title, projectId: id, savedName: nameA,
+ otherDraftName: originalB.name, otherDraftVersion: originalB.version, remainingRecoveryCount: 1,
+ serverVersion: final.data.project.version })
+ } finally { await other.close() }
+})
diff --git a/tests/scene-editor-persistence-closure.spec.ts b/tests/scene-editor-persistence-closure.spec.ts
new file mode 100644
index 0000000..efbb8b9
--- /dev/null
+++ b/tests/scene-editor-persistence-closure.spec.ts
@@ -0,0 +1,154 @@
+import { test, expect } from '@playwright/test'
+import path from 'node:path'
+import { signIn, freshScene, openEditor, ready, diagnostics, action, save, exportDocument, shot, evidence, closeSession, call, work } from './scene-editor-closure-helpers'
+
+test.afterEach(async ({ page }) => closeSession(page))
+const choose = async (page:any,id:string) => page.locator(`#se-tree [data-object-id="${id}"]`).click()
+const rename = async (page:any,value:string) => { await page.locator('[data-inspector-field="name"]').fill(value); await page.locator('[data-inspector-field="name"]').press('Tab') }
+const uploadJSON = (page:any,content:unknown) => page.locator('#se-project-file').setInputFiles({name:'scene-review.json',mimeType:'application/json',buffer:Buffer.from(typeof content==='string'?content:JSON.stringify(content))})
+
+test('工程 JSON 与小 GLB:真实上传、损坏拒绝、引用失败回滚、相机参数往返', async ({ page },info) => {
+ const token=await signIn(page), copy=await freshScene(token,'truck','导入导出'), id=copy.project.id
+ await openEditor(page,id); const initial=await diagnostics(page), exported=await exportDocument(page)
+ expect(exported.schema).toBe('edit3dv4.scene'); expect(JSON.stringify(exported)).not.toContain('blob:')
+ for(const invalid of ['{invalid', {schema:'another.kind',objects:[]}]) {
+ await uploadJSON(page,invalid); await expect(page.locator('.se-toast.warn').last()).toBeVisible()
+ expect((await diagnostics(page)).objects).toEqual(initial.objects)
+ }
+ const missing=structuredClone(exported), model=missing.objects.find((o:any)=>o.targetProjectId==='156')
+ model.assetCode='SCENE-REVIEW-NOT-EXIST'
+ await uploadJSON(page,missing)
+ await expect(page.locator('.se-toast.warn').last()).toContainText(/失败/)
+ await ready(page); expect((await diagnostics(page)).objects).toEqual(initial.objects)
+ await shot(page,info,'10-invalid-reference-rolled-back')
+ await expect(page.locator('.se-toast.warn')).toHaveCount(0)
+ await page.locator('#se-model-file').setInputFiles({name:'broken.glb',mimeType:'model/gltf-binary',buffer:Buffer.from('broken')})
+ await expect(page.locator('.se-toast.warn').last()).toContainText('模型导入失败'); await ready(page)
+ expect((await diagnostics(page)).objectCount).toBe(initial.objectCount)
+ await expect(page.locator('.se-toast.warn')).toHaveCount(0)
+ const uploadEndpoint=`**/api/tran/v1/content/projects/${id}/assets/upload`
+ await page.route(uploadEndpoint,route=>route.fulfill({status:503,contentType:'application/json',body:JSON.stringify({code:503,message:'验收模拟资源上传失败'})}))
+ const asset=path.resolve(work,'../../文档/素材/01-Box.glb')
+ await page.locator('#se-model-file').setInputFiles(asset)
+ await expect(page.locator('.se-toast.warn').last()).toContainText(/模型导入失败.*503/); await ready(page)
+ expect((await diagnostics(page)).objectCount).toBe(initial.objectCount); await page.unroute(uploadEndpoint)
+ await expect(page.locator('.se-toast.warn')).toHaveCount(0)
+ await page.locator('#se-model-file').setInputFiles(asset)
+ await expect.poll(async()=> (await diagnostics(page)).objectCount,{timeout:60000}).toBe(initial.objectCount+1)
+ await ready(page)
+ const loaded=await diagnostics(page), added=loaded.objects.find((o:any)=>!initial.objects.some((b:any)=>b.sceneId===o.sceneId))
+ expect(added.assetCode).toBeTruthy(); await choose(page,added.sceneId); await action(page,'focus').click()
+ await save(page); await shot(page,info,'11-small-glb-real-upload')
+ const document=await exportDocument(page)
+ document.rendering.camera.position=[12,10,16]; document.rendering.camera.target=[0,1,0]
+ document.rendering.toneMappingExposure=0.9;document.rendering.fog={color:0xaabbcc,near:60,far:240}
+ document.rendering.lights.hemisphere.intensity=1.4;document.rendering.lights.directional.intensity=2.9
+ await uploadJSON(page,document); await ready(page)
+ await expect.poll(async()=> (await diagnostics(page)).dirty).toBe(true)
+ await save(page); await page.reload(); await ready(page)
+ const reopened=await diagnostics(page)
+ expect(reopened.objectCount).toBe(initial.objectCount+1)
+ for(let i=0;i<3;i++) expect(reopened.cameraPosition[i]).toBeCloseTo(document.rendering.camera.position[i],3)
+ const after=(await call(`tran/v1/content/projects/${id}`,token)).data
+ expect(after.currentVersion.content.rendering.toneMappingExposure).toBe(0.9)
+ expect(after.currentVersion.content.rendering.fog).toEqual(document.rendering.fog)
+ expect(after.currentVersion.content.rendering.lights.hemisphere.intensity).toBe(1.4)
+ const savedAsset=after.currentVersion.assets.find((a:any)=>a.code===added.assetCode)
+ expect(savedAsset.type).toBe('MODEL_FILE'); expect(savedAsset.status).toBe('READY')
+ const savedObject=after.currentVersion.content.objects.find((o:any)=>o.sceneId===added.sceneId)
+ expect(savedObject.resourceUrl).toMatch(/^content:\/\/asset\/scene\//)
+ await shot(page,info,'12-json-glb-reopened')
+ evidence('json-glb-roundtrip',{projectId:id,initial,added,savedObject,reopened,rendering:after.currentVersion.content.rendering})
+})
+
+test('保存失败与封面降级:503 保留草稿,封面上传失败保留旧图,恢复后可重试', async ({ page },info) => {
+ const token=await signIn(page), copy=await freshScene(token,'fox','失败恢复'), id=copy.project.id
+ await openEditor(page,id); const initial=await diagnostics(page), fox=initial.objects.find((o:any)=>o.targetProjectId==='157')
+ await choose(page,fox.sceneId); await rename(page,'失败后继续保存的狐狸')
+ const putEndpoint=`**/api/tran/v1/content/projects/${id}`
+ await page.route(putEndpoint,route=>route.request().method()==='PUT'?route.fulfill({status:503,contentType:'application/json',body:JSON.stringify({code:503,message:'验收模拟保存服务不可用'})}):route.continue())
+ await action(page,'save').click(); await expect(page.locator('.se-toast.warn').last()).toContainText(/保存失败/)
+ expect((await diagnostics(page)).dirty).toBe(true)
+ expect((await call(`tran/v1/content/projects/${id}`,token)).data.project.version).toBe(copy.project.version)
+ await shot(page,info,'13-save-failure-keeps-draft'); await page.unroute(putEndpoint)
+ const uploadEndpoint=`**/api/tran/v1/content/projects/${id}/assets/upload`
+ await page.route(uploadEndpoint,route=>route.fulfill({status:503,contentType:'application/json',body:JSON.stringify({code:503,message:'验收模拟封面上传失败'})}))
+ await action(page,'save').click(); await expect(page.locator('.se-toast.warn').last()).toContainText(/封面/)
+ await expect.poll(async()=> (await diagnostics(page)).dirty).toBe(false)
+ const degraded=(await call(`tran/v1/content/projects/${id}`,token)).data
+ expect(degraded.project.coverUri).toBe(copy.project.coverUri)
+ expect(degraded.currentVersion.content.objects.find((o:any)=>o.sceneId===fox.sceneId).name).toBe('失败后继续保存的狐狸')
+ await shot(page,info,'14-cover-failure-keeps-old-image'); await page.unroute(uploadEndpoint)
+ await rename(page,'网络恢复后的狐狸'); await save(page); await page.reload(); await ready(page)
+ const recovered=await diagnostics(page); expect(recovered.objects.find((o:any)=>o.sceneId===fox.sceneId).name).toBe('网络恢复后的狐狸')
+ await shot(page,info,'15-save-retry-success')
+ evidence('save-cover-failure',{projectId:id,originalVersion:copy.project.version,degradedVersion:degraded.project.version,coverPreserved:true,recovered})
+})
+
+test('同版本恢复与 409:真实未保存草稿重载、服务器新版本保护与隔离下载', async ({ page },info) => {
+ const token=await signIn(page), copy=await freshScene(token,'truck','恢复冲突'), id=copy.project.id
+ await openEditor(page,id); const first=await diagnostics(page), truck=first.objects.find((o:any)=>o.targetProjectId==='156')
+ await choose(page,truck.sceneId); await rename(page,'真实未保存卡车恢复')
+ await expect.poll(()=>page.evaluate(()=>Object.keys(localStorage).filter(k=>k.includes('scene')&&k.includes('recovery')).length)).toBeGreaterThan(0)
+ await page.reload()
+ await expect(page.getByRole('button',{name:/恢复.*(副本|草稿|修改)|使用恢复/})).toBeVisible({timeout:60000})
+ await page.getByRole('button',{name:/恢复.*(副本|草稿|修改)|使用恢复/}).click(); await ready(page)
+ expect((await diagnostics(page)).dirty).toBe(true)
+ expect((await call(`tran/v1/content/projects/${id}`,token)).data.project.version).toBe(copy.project.version)
+ await shot(page,info,'16-real-unsaved-recovery'); await save(page)
+ const current=(await call(`tran/v1/content/projects/${id}`,token)).data
+ await choose(page,truck.sceneId); await rename(page,'旧会话冲突中的卡车')
+ const external=await call(`tran/v1/content/projects/${id}`,token,{
+ ...current.project, name:current.project.name, content:current.currentVersion.content,
+ assets:current.currentVersion.assets, dependencies:current.currentVersion.dependencies, version:current.project.version,
+ },'PUT')
+ expect(external.status).toBe(200)
+ await action(page,'save').click(); await expect(page.locator('.se-toast.warn').last()).toContainText(/其他人|版本|冲突/)
+ expect((await diagnostics(page)).dirty).toBe(true)
+ await shot(page,info,'17-conflict-does-not-overwrite')
+ await page.reload(); await expect(page.locator('.el-message-box')).toBeVisible({timeout:60000})
+ const prompt=await page.locator('.el-message-box').innerText()
+ await shot(page,info,'18-stale-recovery-explicit-decision')
+ const downloadPromise=page.waitForEvent('download')
+ await page.getByRole('button',{name:/下载/}).last().click(); const download=await downloadPromise
+ const file=path.join(work,`scene-conflict-${id}.json`); await download.saveAs(file)
+ await ready(page)
+ expect((await diagnostics(page)).objects.find((o:any)=>o.sceneId===truck.sceneId).name).toBe('真实未保存卡车恢复')
+ evidence('real-recovery-conflict',{projectId:id,prompt,originalVersion:copy.project.version,newVersion:external.data.project.version,recovered:await diagnostics(page)})
+})
+
+test('发布:前端绑定校验、保存与发布失败阻断、真实固定版本供训练目录使用', async ({ page },info) => {
+ const token=await signIn(page), copy=await freshScene(token,'truck','发布'), id=copy.project.id
+ await openEditor(page,id); const initial=await diagnostics(page), truck=initial.objects.find((o:any)=>o.targetProjectId==='156')
+ await choose(page,truck.sceneId); const input=page.locator('[data-inspector-field="deviceId"]')
+ await input.fill(''); await input.press('Tab'); await action(page,'verify').click()
+ await expect(page.locator('#se-bottom-content')).toContainText(/设备/)
+ await action(page,'publish').click(); await expect(page.locator('.se-toast.warn').last()).toContainText(/检查未通过/)
+ expect((await call(`tran/v1/content/projects/${id}`,token)).data.project.status).toBe('DRAFT')
+ await input.fill('SCENE-TRUCK-CLOSURE'); await input.press('Tab')
+ const endpoint=`**/api/tran/v1/content/projects/${id}/publish`
+ await page.route(endpoint,route=>route.fulfill({status:503,contentType:'application/json',body:JSON.stringify({code:503,message:'验收模拟发布失败'})}))
+ await action(page,'publish').click(); await expect(page.locator('.se-toast.warn').last()).toContainText(/发布失败/)
+ expect((await call(`tran/v1/content/projects/${id}`,token)).data.project.status).toBe('DRAFT')
+ await shot(page,info,'19-publish-failure-stays-draft'); await page.unroute(endpoint)
+ await action(page,'publish').click(); await expect(page.locator('#se-save-state')).toHaveText('已发布',{timeout:60000})
+ const published=(await call(`tran/v1/content/projects/${id}`,token)).data
+ expect(published.project.status).toBe('PUBLISHED'); expect(published.project.publishedVersionId).toBe(published.currentVersion.id)
+ const modelDependency=published.currentVersion.dependencies.find((d:any)=>d.targetProjectId==='156')
+ expect(modelDependency.targetVersionId).toBe(truck.targetVersionId); expect(modelDependency.required).toBe(true)
+ const catalog=await call('tran/v1/content/catalog?type=SCENE&size=100',token)
+ expect(catalog.status).toBe(200)
+ const row=(catalog.data.records||catalog.data).find((r:any)=>r.projectId===id)
+ expect(row).toBeTruthy()
+ await shot(page,info,'20-scene-published-fixed-version')
+ await action(page,'back').click();await expect(page).toHaveURL(/\/content\/scenes(?:\?|$)/)
+ await page.getByPlaceholder('搜索场景名称或编码').fill(copy.project.name)
+ await page.getByRole('button',{name:'搜索',exact:true}).click()
+ await expect(page.locator('.el-loading-mask:visible')).toHaveCount(0)
+ await expect(page.locator('.asset-row-list > article')).toHaveCount(1)
+ const card=page.locator('.asset-row-list > article').filter({hasText:copy.project.name})
+ await expect(card).toHaveCount(1);await expect(card).toContainText('已发布')
+ await expect.poll(()=>card.locator('img').evaluate((img:any)=>img.complete&&img.naturalWidth>0)).toBe(true)
+ await shot(page,info,'21-published-scene-cover-in-list')
+ evidence('publish-fixed-version',{projectId:id,versionId:published.currentVersion.id,dependency:modelDependency,catalog:row,coverUri:published.project.coverUri})
+})
diff --git a/tests/scene-editor-templates-closure.spec.ts b/tests/scene-editor-templates-closure.spec.ts
new file mode 100644
index 0000000..7d489b2
--- /dev/null
+++ b/tests/scene-editor-templates-closure.spec.ts
@@ -0,0 +1,250 @@
+import { test, expect, type Page } from '@playwright/test'
+import {
+ action, call, closeSession, diagnostics, evidence, exportDocument,
+ freshScene, openEditor, ready, save, shot, signIn,
+} from './scene-editor-closure-helpers'
+
+const templates = [
+ { id: 'depot-1', name: '真实装备车库', type: '车间', count: 1, builtIn: 'vehicle-depot' },
+ { id: 'terrain-1', name: '真实野外训练场', type: '室外', count: 4, builtIn: 'field-terrain' },
+ { id: 'workshop-1', name: '综合检验车间', type: '车间', count: 0 },
+ { id: 'workshop-real', name: '演示维修车间(可拆分)', type: '车间', count: 74, builtIn: 'maintenance-workshop' },
+ { id: 'workshop-2', name: '总成拆卸车间', type: '车间', count: 3, types: ['equipment', 'workbench', 'shelf'] },
+ { id: 'workshop-3', name: '部件分解车间', type: '车间', count: 3, types: ['workbench', 'shelf', 'camera'] },
+ { id: 'workshop-4', name: '清洗检测车间', type: '车间', count: 4, types: ['diagnostic', 'workbench', 'camera', 'safety-zone'] },
+ { id: 'workshop-5', name: '复装调试车间', type: '车间', count: 4, types: ['diagnostic', 'workbench', 'light', 'worker'] },
+ { id: 'outdoor-1', name: '室外综合训练场', type: '室外', count: 3, types: ['equipment', 'safety-zone', 'light'] },
+]
+const basics = ['diagnostic', 'equipment', 'workbench', 'shelf', 'safety-zone', 'camera', 'worker', 'light']
+const tab = (page: Page, name: string) => page.locator(`[data-left-tab="${name}"]`)
+const card = (page: Page, id: string) => page.locator(`[data-preset-id="${id}"]`)
+const field = (page: Page, name: string) => page.locator(`[data-inspector-field="${name}"]`)
+async function selectTemplate(page: Page, id: string) {
+ await tab(page, 'scenes').click()
+ await page.locator('#se-search').fill('')
+ await card(page, id).click()
+ const dialog = page.getByRole('dialog', { name: '切换场景模板' })
+ if (await dialog.isVisible()) await dialog.getByRole('button', { name: '放弃修改并切换' }).click()
+ await expect.poll(async () => {
+ const d = await diagnostics(page)
+ return d?.currentPreset?.id === id && d.pendingLoads === 0
+ }, { timeout: 120_000 }).toBe(true)
+ await expect(card(page, id)).toHaveClass(/active/)
+}
+async function getSaved(token: string, id: string) {
+ const response = await call(`tran/v1/content/projects/${id}`, token)
+ expect(response.status).toBe(200)
+ expect(response.data.currentVersion).toBeTruthy()
+ return response.data.currentVersion
+}
+async function realDrag(page: Page, source: ReturnType, x = 0.48, y = 0.57) {
+ await source.scrollIntoViewIfNeeded()
+ const start = await source.boundingBox(), target = await page.locator('#se-viewport canvas').boundingBox()
+ expect(start).not.toBeNull(); expect(target).not.toBeNull()
+ await page.mouse.move(start!.x + start!.width / 2, start!.y + start!.height / 2)
+ await page.mouse.down()
+ await page.mouse.move(start!.x + start!.width / 2 + 12, start!.y + start!.height / 2, { steps: 3 })
+ await page.mouse.move(target!.x + target!.width * x, target!.y + target!.height * y, { steps: 12 })
+ await page.mouse.up()
+}
+async function clearSelectionForScreenshot(page: Page) {
+ const canvas = page.locator('#se-viewport canvas'), box = await canvas.boundingBox()
+ expect(box).not.toBeNull()
+ await canvas.click({ position: { x: box!.width - 28, y: 95 } })
+ await expect.poll(async () => (await diagnostics(page)).selectedSceneId).toBe('')
+ await expect(page.locator('.se-toast, .el-message')).toHaveCount(0)
+}
+test.afterEach(async ({ page }) => { await closeSession(page) })
+
+for (const [index, preset] of templates.entries()) {
+ test(`场景模板实际加载、保存重开:${preset.name}`, async ({ page }, info) => {
+ const token = await signIn(page), created = await freshScene(token, 'fox', preset.id)
+ const id = created.project.id
+ await openEditor(page, id)
+ await selectTemplate(page, preset.id)
+ const loaded = await diagnostics(page)
+ expect(loaded.currentPreset).toEqual({ id: preset.id, name: preset.name, type: preset.type })
+ expect(loaded.objectCount).toBe(preset.count)
+ if (preset.types) expect(loaded.objects.map((o: any) => o.type).sort()).toEqual([...preset.types].sort())
+ await expect(page.locator('#se-preset-badge')).toContainText(`${preset.type}环境`)
+ await save(page)
+ const saved = await getSaved(token, id)
+ expect(saved.content.presetId).toBe(preset.id)
+ expect(saved.content.environment.presetId).toBe(preset.id)
+ if (preset.builtIn) expect(saved.content.objects.some((o: any) => o.builtinAssetId === preset.builtIn)).toBe(true)
+ await page.reload(); await ready(page)
+ const reopened = await diagnostics(page)
+ expect(reopened.currentPreset.id).toBe(preset.id)
+ expect(reopened.objectCount).toBe(preset.count)
+ expect(reopened.dirty).toBe(false)
+ await tab(page, 'scenes').click()
+ await expect(card(page, preset.id)).toHaveClass(/active/)
+ await expect(page.locator('#se-preset-badge')).toContainText(`${preset.type}环境`)
+ const name = `${20 + index}-template-${preset.id}`
+ await shot(page, info, name)
+ evidence(`template-${preset.id}`, { kind: 'e2e', status: 'PASS', testTitle: info.title,
+ featureIds: [`template.${preset.id}`], projectId: id, presetId: preset.id,
+ objectCount: reopened.objectCount, bounds: reopened.contentBounds,
+ scope: '真实模板按钮、选中标签、环境字段、种子对象、PUT 后刷新重开;截图验证对应模板实际画面。', screenshots: [`${name}.png`] })
+ })
+}
+
+test('模板搜索与未保存切换确认保留和放弃', async ({ page }, info) => {
+ const token = await signIn(page), created = await freshScene(token, 'fox', 'template-confirm')
+ await openEditor(page, created.project.id)
+ const original = await diagnostics(page), root = original.objects.find((o: any) => o.targetProjectId)
+ await page.locator(`[data-object-id="${root.sceneId}"]`).first().click()
+ await field(page, 'name').fill('模板确认保留对象'); await field(page, 'name').press('Tab')
+ await tab(page, 'scenes').click()
+ await page.locator('#se-search').fill('清洗')
+ await expect(page.locator('.se-scene-card:visible')).toHaveCount(1)
+ await expect(card(page, 'workshop-4')).toBeVisible()
+ await page.locator('#se-search').fill('无匹配模板-closure')
+ await expect(page.locator('.se-scene-card:visible')).toHaveCount(0)
+ await page.locator('#se-search').fill(''); await expect(page.locator('.se-scene-card:visible')).toHaveCount(9)
+ await card(page, 'workshop-1').click()
+ const dialog = page.getByRole('dialog', { name: '切换场景模板' })
+ await expect(dialog).toBeVisible()
+ await dialog.getByRole('button', { name: '继续编辑' }).click()
+ expect((await diagnostics(page)).currentPreset.id).toBe(original.currentPreset.id)
+ expect((await diagnostics(page)).objects.some((o: any) => o.name === '模板确认保留对象')).toBe(true)
+ await card(page, 'workshop-1').click(); await dialog.getByRole('button', { name: '放弃修改并切换' }).click()
+ await expect.poll(async () => (await diagnostics(page)).objectCount).toBe(0)
+ await expect(card(page, 'workshop-1')).toHaveClass(/active/)
+ await save(page)
+ const saved = await getSaved(token, created.project.id)
+ expect(saved.content.presetId).toBe('workshop-1')
+ await shot(page, info, '29-template-confirm-empty')
+ evidence('template-confirm', { kind: 'e2e', status: 'PASS', testTitle: info.title,
+ featureIds: ['panel.left-tabs', 'search.templates', 'template.switch-cancel'], projectId: created.project.id,
+ scope: '中文/无匹配/清空搜索;真实取消保留对象,真实确认放弃并切换空白模板后保存。', screenshots: ['29-template-confirm-empty.png'] })
+})
+
+test('八种基础资源逐项放置、真实拖放、搜索和保存重开', async ({ page }, info) => {
+ const token = await signIn(page), created = await freshScene(token, 'fox', 'basic-assets')
+ await openEditor(page, created.project.id); await selectTemplate(page, 'workshop-1')
+ await tab(page, 'assets').click()
+ await page.locator('#se-search').fill('检修工作台')
+ await expect(page.locator('.se-asset:visible')).toHaveCount(1)
+ await expect(page.locator('[data-asset-id="workbench"]')).toBeVisible()
+ await page.locator('#se-search').fill('无匹配资源-closure')
+ await expect(page.locator('.se-asset:visible')).toHaveCount(0)
+ await page.locator('#se-search').fill('')
+ for (const [index, id] of basics.entries()) {
+ await page.locator(`[data-asset-id="${id}"]`).dblclick()
+ await expect.poll(async () => (await diagnostics(page)).objectCount).toBe(index + 1)
+ await field(page, 'x').fill(String((index % 4) * 4 - 6)); await field(page, 'x').press('Tab')
+ await field(page, 'z').fill(String(Math.floor(index / 4) * 5 - 2)); await field(page, 'z').press('Tab')
+ }
+ await realDrag(page, page.locator('[data-asset-id="workbench"]'), 0.7, 0.65)
+ await expect.poll(async () => (await diagnostics(page)).objectCount).toBe(9)
+ const placed = await diagnostics(page), dragged = placed.objects.at(-1)
+ expect(dragged.type).toBe('workbench')
+ expect(dragged.position.some((v: number) => Math.abs(v) > 0.1)).toBe(true)
+ for (const axis of [0, 2]) expect(dragged.position[axis] * 2).toBeCloseTo(Math.round(dragged.position[axis] * 2), 5)
+ await expect(page.locator('#se-drop-hint')).not.toHaveClass(/show/)
+ expect(new Set(placed.objects.map((o: any) => o.sceneId)).size).toBe(9)
+ expect(new Set(placed.objects.map((o: any) => o.deviceId)).size).toBe(9)
+ await save(page); await page.reload(); await ready(page)
+ const reopened = await diagnostics(page)
+ expect(reopened.objects.map((o: any) => ({ type: o.type, position: o.position, deviceId: o.deviceId })))
+ .toEqual(placed.objects.map((o: any) => ({ type: o.type, position: o.position, deviceId: o.deviceId })))
+ await tab(page, 'outline').click()
+ await expect(page.locator('.se-object-summary button')).toHaveCount(6)
+ await expect(page.locator('.se-object-summary-note')).toContainText('共 9 个对象,此处显示前 6 个')
+ const firstSummary = page.locator('.se-object-summary button').first()
+ const firstSummaryId = await firstSummary.getAttribute('data-object-id')
+ await firstSummary.click()
+ expect((await diagnostics(page)).selectedSceneId).toBe(firstSummaryId)
+ await expect(field(page, 'name')).toHaveValue(reopened.objects.find((o: any) => o.sceneId === firstSummaryId).name)
+ await shot(page, info, '30-eight-basic-assets-reopened')
+ evidence('basic-assets', { kind: 'e2e', status: 'PASS', testTitle: info.title,
+ featureIds: [...basics.map(id => `asset.${id}`), 'assets.basic-double-click', 'assets.basic-drop', 'search.assets', 'panel.objects'],
+ projectId: created.project.id, dragged: { position: dragged.position, type: dragged.type },
+ scope: '8 种基础资源分别双击放置并分开布置;真实指针拖入第 9 个工作台、吸附检查、唯一编号、保存刷新一致。照明为现有资源外观,不冒称连接真实灯控。', screenshots: ['30-eight-basic-assets-reopened.png'] })
+})
+
+test('两种整体场地加载、重复放置阻止与显式删除后替换', async ({ page }, info) => {
+ const token = await signIn(page), created = await freshScene(token, 'fox', 'venue-assets')
+ await openEditor(page, created.project.id); await selectTemplate(page, 'workshop-1')
+ await tab(page, 'assets').click()
+ await page.locator('[data-asset-id="vehicle-depot"]').dblclick()
+ await expect.poll(async () => { const d = await diagnostics(page); return d.objectCount === 1 && d.pendingLoads === 0 }, { timeout: 120000 }).toBe(true)
+ const depot = await exportDocument(page)
+ expect(depot.objects.some((o: any) => o.builtinAssetId === 'vehicle-depot')).toBe(true)
+ await page.locator('[data-asset-id="field-terrain"]').dblclick()
+ expect((await diagnostics(page)).objectCount).toBe(1)
+ expect((await exportDocument(page)).objects.some((o: any) => o.builtinAssetId === 'field-terrain')).toBe(false)
+ await save(page); await page.reload(); await ready(page)
+ await shot(page, info, '31-venue-depot-reopened')
+ const root = (await diagnostics(page)).objects[0]
+ await page.locator(`[data-object-id="${root.sceneId}"]`).first().click()
+ await action(page, 'delete').click()
+ await expect.poll(async () => (await diagnostics(page)).objectCount).toBe(0)
+ await tab(page, 'assets').click(); await page.locator('[data-asset-id="field-terrain"]').dblclick()
+ await expect.poll(async () => { const d = await diagnostics(page); return d.objectCount === 1 && d.pendingLoads === 0 }, { timeout: 120000 }).toBe(true)
+ await save(page); await page.reload(); await ready(page)
+ const saved = await getSaved(token, created.project.id)
+ expect(saved.content.objects.some((o: any) => o.builtinAssetId === 'field-terrain')).toBe(true)
+ expect(saved.content.objects.some((o: any) => o.builtinAssetId === 'vehicle-depot')).toBe(false)
+ const terrain = await diagnostics(page)
+ expect(terrain.contentBounds.size[1]).toBeGreaterThan(1)
+ await shot(page, info, '32-venue-terrain-reopened')
+ evidence('venue-assets', { kind: 'e2e', status: 'PASS', testTitle: info.title,
+ featureIds: ['asset.vehicle-depot', 'asset.field-terrain', 'object.delete'], projectId: created.project.id,
+ scope: '车库和起伏地形真实加载并分别保存重开;已有整体场地时阻止叠加,显式删除旧场地后才能替换。', screenshots: ['31-venue-depot-reopened.png', '32-venue-terrain-reopened.png'] })
+})
+
+test('模型成果狐狸卡车双击与真实拖入、树搜索和部件隐藏重开', async ({ page }, info) => {
+ const token = await signIn(page), created = await freshScene(token, 'truck', 'published-model-assets')
+ await openEditor(page, created.project.id); await selectTemplate(page, 'workshop-1')
+ await tab(page, 'assets').click()
+ const fox = page.locator('.se-editor-asset').filter({ hasText: /狐狸/ }).first()
+ const truck = page.locator('.se-editor-asset').filter({ hasText: /牛奶卡车/ }).first()
+ await expect(fox).toContainText('已发布'); await expect(truck).toContainText('已发布')
+ const foxProjectId = await fox.getAttribute('data-target-project-id'), foxVersionId = await fox.getAttribute('data-target-version-id')
+ const truckProjectId = await truck.getAttribute('data-target-project-id'), truckVersionId = await truck.getAttribute('data-target-version-id')
+ await fox.dblclick()
+ await expect.poll(async () => { const d = await diagnostics(page); return d.objectCount === 1 && d.pendingLoads === 0 }).toBe(true)
+ await field(page, 'x').fill('-3'); await field(page, 'x').press('Tab')
+ await action(page, 'focus').click()
+ await clearSelectionForScreenshot(page)
+ await shot(page, info, '33-fox-placed-visible')
+ await realDrag(page, truck, 0.7, 0.6)
+ await expect.poll(async () => { const d = await diagnostics(page); return d.objectCount === 2 && d.pendingLoads === 0 }).toBe(true)
+ await field(page, 'x').fill('3'); await field(page, 'x').press('Tab')
+ await action(page, 'focus').click()
+ const placed = await diagnostics(page), foxRoot = placed.objects.find((o: any) => String(o.targetProjectId) === foxProjectId)
+ const truckRoot = placed.objects.find((o: any) => String(o.targetProjectId) === truckProjectId)
+ expect(String(foxRoot.targetVersionId)).toBe(foxVersionId); expect(String(truckRoot.targetVersionId)).toBe(truckVersionId)
+ expect(foxRoot.parts.length).toBeGreaterThan(0); expect(truckRoot.parts.length).toBeGreaterThan(0)
+ await tab(page, 'outline').click()
+ await page.locator('#se-search').fill(truckRoot.name)
+ await expect(page.locator(`.se-tree-row[data-object-id="${truckRoot.sceneId}"]`)).toBeVisible()
+ await expect(page.locator(`.se-tree-row[data-object-id="${foxRoot.sceneId}"]`)).toHaveCount(0)
+ const part = truckRoot.parts.find((p: any) => p.capabilities.includes('transform'))
+ expect(part).toBeTruthy()
+ await page.locator('#se-search').fill(part.name)
+ await expect(page.locator(`.se-tree-row[data-object-id="${part.sceneId}"]`)).toBeVisible()
+ await page.locator('#se-search').fill('无匹配对象-closure'); await expect(page.locator('.se-tree-row')).toHaveCount(0)
+ await page.locator('#se-search').fill('')
+ await page.locator(`.se-tree-row[data-object-id="${part.sceneId}"]`).click()
+ await expect(field(page, 'visible')).toBeEnabled(); await field(page, 'visible').uncheck()
+ await expect.poll(async () => (await diagnostics(page)).objects.find((o: any) => o.sceneId === truckRoot.sceneId).parts.find((p: any) => p.sceneId === part.sceneId).visible).toBe(false)
+ await save(page); await page.reload(); await ready(page)
+ const reopened = await diagnostics(page), savedTruck = reopened.objects.find((o: any) => o.sceneId === truckRoot.sceneId)
+ expect(savedTruck.parts.find((p: any) => p.sceneId === part.sceneId).visible).toBe(false)
+ await page.locator(`.se-tree-row[data-object-id="${part.sceneId}"]`).click()
+ await expect(field(page, 'visible')).not.toBeChecked()
+ // Restore the deliberately hidden body part so the retained screenshot is a complete model.
+ await field(page, 'visible').check(); await save(page)
+ await page.locator(`.se-tree-row[data-object-id="${truckRoot.sceneId}"]`).click(); await action(page, 'focus').click()
+ await clearSelectionForScreenshot(page)
+ await shot(page, info, '34-truck-placed-part-restored')
+ const saved = await getSaved(token, created.project.id)
+ expect(saved.dependencies.filter((d: any) => d.kind === 'SCENE_MODEL' || d.relationType === 'SCENE_MODEL').length).toBe(2)
+ evidence('published-model-assets', { kind: 'e2e', status: 'PASS', testTitle: info.title,
+ featureIds: ['assets.catalog', 'assets.model-double-click', 'assets.model-drop', 'search.tree', 'part.source', 'property.visible'],
+ projectId: created.project.id, sources: [{ projectId: foxProjectId, versionId: foxVersionId }, { projectId: truckProjectId, versionId: truckVersionId }],
+ scope: 'API 已发布目录,狐狸双击和卡车真实拖入;固定来源版本、树根/部件搜索;卡车刚体部件隐藏保存重开,最后恢复可见。基线树没有折叠按钮,不将搜索伪称折叠。', screenshots: ['33-fox-placed-visible.png', '34-truck-placed-part-restored.png'] })
+})
diff --git a/tests/scene-editor-viewport-closure.spec.ts b/tests/scene-editor-viewport-closure.spec.ts
new file mode 100644
index 0000000..7a9fc33
--- /dev/null
+++ b/tests/scene-editor-viewport-closure.spec.ts
@@ -0,0 +1,179 @@
+import { test, expect, type Page } from '@playwright/test'
+import { signIn, freshScene, openEditor, ready, diagnostics, action, save, shot, evidence, closeSession } from './scene-editor-closure-helpers'
+
+test.afterEach(async ({ page }) => closeSession(page))
+const field = (page: Page, name: string) => page.locator(`[data-inspector-field="${name}"]`)
+const object = (d: any, id: string) => d.objects.find((o: any) => o.sceneId === id)
+const distance = (a: number[], b: number[]) => Math.hypot(...a.map((v, i) => v - b[i]!))
+async function settle(page: Page) {
+ let previous = ''
+ await expect.poll(async () => {
+ const d = await diagnostics(page)
+ const current = [...d.cameraPosition, ...d.cameraTarget].map((n: number) => n.toFixed(3)).join(',')
+ const same = previous === current; previous = current; return same
+ }).toBe(true)
+}
+
+// Only project existing scene/gizmo geometry to pixels. All mutations below use
+// browser pointer/keyboard actions; this hook exposes no editor or write methods.
+async function installProjection(page: Page) {
+ await page.route('**/src/features/guide-legacy/demo-src/scene-editor.js*', async route => {
+ const response = await route.fetch()
+ const body = await response.text()
+ await route.fulfill({ response, body: body + `\n
+globalThis.__scenePointerRead = (kind, wanted) => {
+ const e = activeSceneEditor; if (!e) return null;
+ if(kind === 'axis') return e.transform.axis;
+ const rect = e.renderer.domElement.getBoundingClientRect();
+ const project = v => { const p = v.clone().project(e.camera); return {x:rect.x+(p.x+1)*rect.width/2,y:rect.y+(1-p.y)*rect.height/2,z:p.z}; };
+ const visible = n => { for(let p=n;p;p=p.parent) if(!p.visible)return false; return true; };
+ const candidates=[];
+ if(kind==='gizmo') {
+ const group=e.transform._gizmo.picker[e.transform.mode];
+ group.traverse(n=>{
+ if(n.name!==wanted || !n.geometry?.attributes?.position || !n.visible)return;
+ const a=n.geometry.attributes.position, box=new THREE.Box3();
+ for(let i=0;in.userData.sceneId===wanted);
+ root?.traverse(n=>{
+ if(!n.isMesh || !visible(n) || !n.geometry?.attributes?.position)return;
+ const a=n.geometry.attributes.position,box=new THREE.Box3();
+ for(let i=0;in.visible),true),e.objects);
+ if(picked===root || picked?.userData.sceneOwnerId===wanted)candidates.push({...p,sceneId:picked.userData.sceneId});
+ });
+ }
+ return candidates.filter(p=>p.z>-1&&p.z<1&&p.x>rect.x+15&&p.xrect.y+65&&p.y new Promise(resolve => requestAnimationFrame(() => requestAnimationFrame(resolve))))
+ const candidates = await page.evaluate(axis => (window as any).__scenePointerRead('gizmo', axis), axis)
+ let hit: any
+ for (const point of candidates) {
+ await page.mouse.move(point.x, point.y)
+ if (await page.evaluate(() => (window as any).__scenePointerRead('axis')) === axis) { hit = point; break }
+ }
+ expect(hit, `visible ${axis} gizmo can be picked by a real mouse`).toBeTruthy()
+ await page.mouse.down(); await page.mouse.move(hit.x + dx, hit.y + dy, { steps: 18 }); await page.mouse.up()
+}
+async function theme(page: Page, label: string) {
+ await page.getByRole('button', { name: '界面与导航设置' }).click()
+ const drawer = page.locator('.el-drawer').filter({ hasText: '界面与导航设置' })
+ await drawer.locator('.theme-options button').filter({ hasText: label }).click()
+ await drawer.locator('.el-drawer__close-btn').click()
+ await expect(drawer).toBeHidden()
+}
+
+test('真实相机操控、视口拾取、预览禁写和两尺寸主题可读性', async ({ page }, info) => {
+ await installProjection(page)
+ const token = await signIn(page), copy = await freshScene(token, 'fox', '视口主题')
+ await openEditor(page, copy.project.id)
+ const initial = await diagnostics(page), fox = initial.objects.find((o: any) => o.targetProjectId === '157')
+ await page.locator(`#se-tree [data-object-id="${fox.sceneId}"]`).click(); await action(page, 'focus').click(); await settle(page)
+ const canvas = page.locator('#se-viewport > canvas'), box = (await canvas.boundingBox())!
+ const start = { x: box.x + box.width * .38, y: box.y + box.height * .66 }
+ const beforeOrbit = await diagnostics(page)
+ await page.mouse.move(start.x, start.y); await page.mouse.down(); await page.mouse.move(start.x + 95, start.y - 38, { steps: 18 }); await page.mouse.up(); await settle(page)
+ const orbited = await diagnostics(page); expect(distance(orbited.cameraPosition, beforeOrbit.cameraPosition)).toBeGreaterThan(.1)
+ await page.mouse.move(start.x, start.y); await page.mouse.down({ button: 'right' }); await page.mouse.move(start.x + 70, start.y - 30, { steps: 18 }); await page.mouse.up({ button: 'right' }); await settle(page)
+ const panned = await diagnostics(page); expect(distance(panned.cameraTarget, orbited.cameraTarget)).toBeGreaterThan(.1)
+ await page.mouse.wheel(0, -250); await settle(page)
+ const zoomed = await diagnostics(page); expect(distance(zoomed.cameraPosition, zoomed.cameraTarget)).toBeLessThan(distance(panned.cameraPosition, panned.cameraTarget))
+ await action(page, 'top').click(); await settle(page)
+ expect((await diagnostics(page)).cameraPosition).not.toEqual(zoomed.cameraPosition)
+ await page.locator('[data-camera="perspective"]').click(); await settle(page)
+ expect(distance((await diagnostics(page)).cameraPosition, zoomed.cameraPosition)).toBeLessThan(.02)
+ const grid = (await diagnostics(page)).gridVisible; await action(page, 'grid').click(); expect((await diagnostics(page)).gridVisible).toBe(!grid); await action(page, 'grid').click()
+ await page.locator(`#se-tree [data-object-id="${fox.sceneId}"]`).click(); await action(page, 'focus').click(); await settle(page)
+ const points = await page.evaluate(id => (window as any).__scenePointerRead('mesh', id), fox.sceneId)
+ expect(points.length).toBeGreaterThan(0); await page.mouse.click(points[0].x, points[0].y)
+ expect((await diagnostics(page)).selectedSceneId).toBe(points[0].sceneId)
+ await theme(page, '暗色')
+ await shot(page, info, '40-viewport-dark-1920')
+ await page.setViewportSize({ width: 1440, height: 1000 }); await theme(page, '军工绿')
+ const inspectorBounds = (await page.locator('.se-right').boundingBox())!
+ expect(inspectorBounds.x + inspectorBounds.width).toBeLessThanOrEqual(1440)
+ await expect.poll(async () => { const b = (await canvas.boundingBox())!; return b.x + b.width <= inspectorBounds.x }).toBe(true)
+ const colors = await page.locator('[data-inspector-field="x"]').evaluate(el => ({ text: getComputedStyle(el).color, background: getComputedStyle(el).backgroundColor }))
+ expect(colors.text).not.toBe(colors.background)
+ await shot(page, info, '41-viewport-light-1440')
+ await page.setViewportSize({ width: 1920, height: 1080 })
+ const beforePreview = await diagnostics(page)
+ await action(page, 'preview').click(); expect((await diagnostics(page)).previewing).toBe(true)
+ await expect(action(page, 'save')).toBeDisabled(); await expect(field(page, 'name')).toBeDisabled()
+ await page.keyboard.press('Delete'); await page.keyboard.press('w'); await page.keyboard.press('e'); await page.keyboard.press('r')
+ expect((await diagnostics(page)).objects).toEqual(beforePreview.objects)
+ await shot(page, info, '42-fox-preview-original-material')
+ await page.keyboard.press('Escape'); expect((await diagnostics(page)).previewing).toBe(false); await expect(action(page, 'save')).toBeEnabled()
+ evidence('viewport-camera-theme', { status: 'PASS', kind: '真实UI+只读投影', projectId: copy.project.id, featureIds: ['view.orbit','view.pan','view.zoom','view.top','view.perspective','view.grid','view.focus','selection.viewport','selection.highlight','preview.enter','preview.exit','preview.lock','lifecycle.resize-theme'], colors, screenshots: ['40-viewport-dark-1920','41-viewport-light-1440','42-fox-preview-original-material'] })
+})
+
+test('真实变换手柄移动旋转缩放、吸附与保存重开', async ({ page }, info) => {
+ await installProjection(page)
+ const token = await signIn(page), copy = await freshScene(token, 'truck', '变换手柄')
+ await openEditor(page, copy.project.id)
+ await page.locator('[data-left-tab="scenes"]').click(); await page.locator('[data-preset-id="workshop-1"]').click(); await ready(page)
+ await page.locator('[data-left-tab="assets"]').click(); await page.locator('[data-asset-id="equipment"]').dblclick()
+ const added = (await diagnostics(page)).objects[0]; expect(added).toBeTruthy()
+ await action(page, 'focus').click(); await settle(page)
+ await page.keyboard.press('w'); expect((await diagnostics(page)).transformMode).toBe('translate')
+ await dragGizmo(page, 'X', 85, -12)
+ const moved = object(await diagnostics(page), added.sceneId); expect(moved.position).not.toEqual(added.position); expect(moved.position[0] * 2).toBeCloseTo(Math.round(moved.position[0] * 2), 4)
+ await page.keyboard.press('e'); expect((await diagnostics(page)).transformMode).toBe('rotate')
+ await dragGizmo(page, 'Y', 70, 45)
+ const rotated = object(await diagnostics(page), added.sceneId); expect(rotated.rotation).not.toEqual(moved.rotation); expect(rotated.rotation[1] / (Math.PI / 12)).toBeCloseTo(Math.round(rotated.rotation[1] / (Math.PI / 12)), 4)
+ await action(page, 'snap').click(); await expect(page.locator('#se-status-snap')).toHaveText('关闭')
+ await page.keyboard.press('r'); expect((await diagnostics(page)).transformMode).toBe('scale')
+ await dragGizmo(page, 'X', 70, -20)
+ expect(object(await diagnostics(page), added.sceneId).scale[0]).toBe(.000001)
+ await action(page, 'undo').click()
+ await expect.poll(async () => object(await diagnostics(page), added.sceneId).scale).toEqual(rotated.scale)
+ await dragGizmo(page, 'X', -30, 10)
+ const scaled = object(await diagnostics(page), added.sceneId); expect(scaled.scale).not.toEqual(rotated.scale); expect(scaled.scale.every((v: number) => v > 0)).toBe(true)
+ await action(page, 'snap').click(); await expect(page.locator('#se-status-snap')).toHaveText('0.5m')
+ await shot(page, info, '43-real-transform-gizmo')
+ await save(page); await page.reload(); await ready(page)
+ const reopened = object(await diagnostics(page), added.sceneId)
+ expect(reopened.position).toEqual(scaled.position); expect(reopened.rotation).toEqual(scaled.rotation); expect(reopened.scale).toEqual(scaled.scale)
+ await expect(page.getByText('旧版本恢复稿', { exact: false })).toBeHidden()
+ evidence('viewport-transform', { status: 'PASS', kind: '真实UI手柄+只读几何投影定位', projectId: copy.project.id, featureIds: ['transform.translate','transform.rotate','transform.scale','transform.keys','transform.snap'], before: added, after: reopened, screenshots: ['43-real-transform-gizmo'] })
+})
+
+test('缓存标签暂停键盘、草稿恢复以及关闭取消和保存失败留页', async ({ page }, info) => {
+ const token = await signIn(page), copy = await freshScene(token, 'fox', '缓存关闭')
+ await openEditor(page, copy.project.id)
+ const fox = (await diagnostics(page)).objects.find((o: any) => o.targetProjectId === '157')
+ await page.locator(`#se-tree [data-object-id="${fox.sceneId}"]`).click()
+ await field(page, 'name').fill('缓存未保存狐狸'); await field(page, 'name').press('Tab')
+ const dirty = await diagnostics(page); expect(dirty.dirty).toBe(true)
+ await page.locator('.page-tab-link').filter({ hasText: '工作总览' }).click()
+ await expect.poll(async () => (await diagnostics(page))?.paused).toBe(true)
+ await page.keyboard.press('Delete'); await page.keyboard.press('w'); await page.keyboard.press('e'); await page.keyboard.press('r'); await page.keyboard.press('Control+z')
+ expect((await diagnostics(page)).objects).toEqual(dirty.objects)
+ await page.locator('.page-tab-link').filter({ hasText: '数字场景编辑' }).click(); await ready(page)
+ expect((await diagnostics(page)).paused).toBe(false); expect((await diagnostics(page)).dirty).toBe(true); await expect(field(page, 'name')).toHaveValue('缓存未保存狐狸')
+ await page.locator('.page-tab.active .page-tab-close').click()
+ await expect(page.getByRole('dialog')).toBeVisible(); await page.keyboard.press('Escape')
+ await expect(page.locator('.se-shell')).toBeVisible(); expect((await diagnostics(page)).dirty).toBe(true)
+ await page.route(`**/api/tran/v1/content/projects/${copy.project.id}`, async route => {
+ if (route.request().method() === 'PUT') return route.fulfill({ status: 503, contentType: 'application/json', body: JSON.stringify({ code: 'SERVICE_UNAVAILABLE', message: '视口关闭回归:模拟保存失败', data: null }) })
+ return route.continue()
+ })
+ await page.locator('.page-tab.active .page-tab-close').click()
+ await page.getByRole('button', { name: '关闭标签', exact: true }).click()
+ await page.getByRole('button', { name: '保存并离开', exact: true }).click()
+ await expect(page.locator('.se-toast.warn').filter({ hasText: '模拟保存失败' })).toBeVisible()
+ await expect(page.locator('.se-shell')).toBeVisible(); expect((await diagnostics(page)).dirty).toBe(true)
+ await expect(page.locator('.page-tab-link').filter({ hasText: '数字场景编辑' })).toBeVisible()
+ await shot(page, info, '44-cache-close-save-failure')
+ evidence('viewport-cache-close', { status: 'PASS', kind: '真实UI缓存/关闭+浏览器模拟503拒绝写入', projectId: copy.project.id, featureIds: ['lifecycle.cache'], screenshot: '44-cache-close-save-failure', originalDraftPreserved: true })
+})
diff --git a/tests/scene-editor-workflow-closure.spec.ts b/tests/scene-editor-workflow-closure.spec.ts
new file mode 100644
index 0000000..8d92706
--- /dev/null
+++ b/tests/scene-editor-workflow-closure.spec.ts
@@ -0,0 +1,191 @@
+import { test, expect } from '@playwright/test'
+import fs from 'node:fs'
+import path from 'node:path'
+import { signIn, freshScene, openEditor, ready, diagnostics, action, save, exportDocument, shot, evidence, closeSession, call, report, registerProject } from './scene-editor-closure-helpers'
+
+test.afterEach(async ({ page }) => closeSession(page))
+const field = (page: any, name: string) => page.locator(`[data-inspector-field="${name}"]`)
+const select = async (page: any, id: string) => page.locator(`#se-tree [data-object-id="${id}"]`).click()
+const rootModel = (d: any, id: string) => d.objects.find((o: any) => o.targetProjectId === id)
+const byId = (d: any, id: string) => d.objects.find((o: any) => o.sceneId === id)
+const setField = async (page: any, name: string, value: string) => { await field(page, name).fill(value); await field(page, name).press('Tab') }
+
+test('狐狸:真实细分权限、全部属性、清空绑定、蒙皮保护、相机与封面保存重开', async ({ page }, info) => {
+ const token = await signIn(page), copy = await freshScene(token, 'fox', '属性封面'), id = copy.project.id
+ await openEditor(page, id)
+ const before = await diagnostics(page), fox = rootModel(before, '157')
+ expect(before.readOnly).toBe(false); expect(fox.parts.length).toBeGreaterThan(0)
+ await expect(action(page, 'new')).toBeEnabled(); await expect(action(page, 'save')).toBeEnabled()
+ await expect(page.locator('.se-project')).toHaveAttribute('title', `当前场景:${copy.project.name}`)
+ await action(page, 'project').click()
+ await select(page, fox.sceneId); await action(page, 'focus').click()
+ await shot(page, info, '01-fox-actual-scene')
+ await setField(page, 'name', '闭环验收狐狸目标')
+ for (const [name, value] of [['x','-6.3'],['y','0.25'],['z','1.2'],['rx','5.5'],['ry','-24.5'],['rz','3.2']]) await setField(page, name, value)
+ for (const [name, value] of [['sx','0.036'],['sy','0.04'],['sz','0.038']]) await setField(page, name, value)
+ await setField(page, 'sx', '0'); expect(byId(await diagnostics(page), fox.sceneId).scale[0]).toBeGreaterThan(0)
+ await setField(page, 'sx', '-1'); expect(byId(await diagnostics(page), fox.sceneId).scale[0]).toBeGreaterThan(0)
+ await setField(page, 'sx', '0.036')
+ await field(page, 'visible').uncheck(); expect(byId(await diagnostics(page), fox.sceneId).visible).toBe(false)
+ await field(page, 'visible').check()
+ await field(page, 'operable').uncheck(); expect(byId(await diagnostics(page), fox.sceneId).operable).toBe(false)
+ await field(page, 'operable').check()
+ for (const semantic of ['实训装备','工位设施','安全区域','采集设备','训练人员','环境设施']) {
+ await field(page, 'semantic').selectOption(semantic)
+ expect(byId(await diagnostics(page), fox.sceneId).semantic).toBe(semantic)
+ }
+ await field(page, 'semantic').selectOption('训练人员')
+ await setField(page, 'deviceId', ''); await setField(page, 'modelId', '')
+ await page.locator('[data-bottom-tab="binding"]').click()
+ await expect(page.locator('#se-bottom-content')).toContainText('训练人员')
+ await expect(page.locator('#se-bottom-content')).toContainText('未设置')
+ await save(page)
+ const saved = (await call(`tran/v1/content/projects/${id}`, token)).data
+ const sourceObject = saved.currentVersion.content.objects.find((o: any) => o.sceneId === fox.sceneId)
+ expect(sourceObject.deviceId).toBe(''); expect(sourceObject.modelId).toBe('')
+ expect(saved.project.coverUri).toMatch(/^(content|fsvc):/)
+ const cover = saved.currentVersion.assets.find((a: any) => a.code === 'SCENE_COVER')
+ expect(cover.storageUri).toBe(saved.project.coverUri)
+ const image = await page.request.get(`/api/tran/v1/content/projects/${id}/assets/${cover.id}/content`, { headers: { Authorization: `Bearer ${token}` } })
+ expect(image.ok()).toBe(true)
+ fs.mkdirSync(path.join(report, 'covers'), { recursive: true }); fs.writeFileSync(path.join(report, 'covers/fox.jpg'), await image.body())
+ const storedCamera = saved.currentVersion.content.rendering.camera
+ await page.reload(); await ready(page)
+ const reopened = await diagnostics(page), restored = byId(reopened, fox.sceneId)
+ expect(restored.position).toEqual(sourceObject.position); expect(restored.scale).toEqual(sourceObject.scale)
+ expect(restored.rotation).toEqual(sourceObject.rotation); expect(restored.deviceId).toBe(''); expect(restored.modelId).toBe('')
+ for (let i=0;i<3;i++) expect(reopened.cameraPosition[i]).toBeCloseTo(storedCamera.position[i], 4)
+ await select(page, fox.sceneId)
+ const part = restored.parts[0]; await select(page, part.sceneId)
+ await expect(field(page, 'x')).toBeDisabled(); await expect(field(page, 'visible')).toBeDisabled()
+ await expect(page.locator('#se-inspector')).toContainText('蒙皮部件仅支持查看与高亮')
+ await action(page, 'delete').click(); expect(byId(await diagnostics(page), fox.sceneId).parts[0].sceneDeleted).toBe(false)
+ await shot(page, info, '02-fox-reopened-skin-protection')
+ evidence('fox-properties-cover', { projectId:id, original:before, savedObject:sourceObject, cover:{ id:cover.id,storageUri:cover.storageUri,sizeBytes:cover.sizeBytes }, reopened })
+})
+
+test('牛奶卡车:刚性部件独立变换、软删除、历史与保存基线一致', async ({ page }, info) => {
+ const token = await signIn(page), copy = await freshScene(token, 'truck', '部件历史'), id = copy.project.id
+ await openEditor(page, id)
+ for(const mode of ['full','xray','hidden']) {
+ const control=page.locator(`[data-garage-enclosure-mode="${mode}"]`)
+ await control.click();await expect(control).toHaveAttribute('aria-pressed','true')
+ const document=await exportDocument(page)
+ expect(document.objects.find((o:any)=>o.name==='车库').enclosureMode).toBe(mode)
+ await shot(page,info,`07-garage-${mode}`)
+ }
+ await save(page);await page.reload();await ready(page)
+ await expect(page.locator('[data-garage-enclosure-mode="hidden"]')).toHaveAttribute('aria-pressed','true')
+ const originalCount=(await diagnostics(page)).objectCount
+ await page.locator('[data-left-tab="assets"]').click()
+ await page.locator('[data-editor-asset-id][data-target-project-id="156"]').dblclick()
+ await expect.poll(async()=>(await diagnostics(page)).objectCount).toBe(originalCount+1);await ready(page)
+ await page.locator('[data-left-tab="outline"]').click()
+ const initial = await diagnostics(page), truck = rootModel(initial, '156')
+ const secondTruck=initial.objects.filter((o:any)=>o.targetProjectId==='156')[1]
+ expect(secondTruck.sceneId).not.toBe(truck.sceneId)
+ await select(page, truck.sceneId); await action(page, 'focus').click()
+ const part = truck.parts.find((p: any) => p.capabilities.includes('transform'))
+ expect(part).toBeTruthy(); await select(page, part.sceneId)
+ await expect(field(page, 'x')).toBeEnabled()
+ await setField(page, 'name', '独立部件验收')
+ await setField(page, 'x', String(Number((part.position[0]+0.3).toFixed(3))))
+ const changed = await diagnostics(page)
+ expect(byId(changed,truck.sceneId).position).toEqual(truck.position)
+ await save(page)
+ await action(page, 'delete').click()
+ expect(byId(await diagnostics(page),truck.sceneId).parts.find((p:any)=>p.sceneId===part.sceneId).sceneDeleted).toBe(true)
+ await action(page, 'undo').click(); await ready(page)
+ await expect.poll(async()=>byId(await diagnostics(page),truck.sceneId).parts.find((p:any)=>p.sceneId===part.sceneId).sceneDeleted).toBe(false)
+ await page.locator('.se-project').focus(); await page.keyboard.press('Control+Shift+Z')
+ await expect.poll(async()=>byId(await diagnostics(page),truck.sceneId).parts.find((p:any)=>p.sceneId===part.sceneId).sceneDeleted).toBe(true)
+ await page.keyboard.press('Control+z')
+ await expect.poll(async()=>byId(await diagnostics(page),truck.sceneId).parts.find((p:any)=>p.sceneId===part.sceneId).sceneDeleted).toBe(false)
+ await select(page, truck.sceneId); await setField(page,'name','保存后的卡车B'); await save(page)
+ await action(page,'undo').click()
+ await expect.poll(async()=> (await diagnostics(page)).dirty).toBe(true)
+ await action(page,'redo').click(); await expect.poll(async()=> (await diagnostics(page)).dirty).toBe(false)
+ await select(page, truck.sceneId)
+ await field(page,'name').fill('输入框自身撤销'); await field(page,'name').press('Control+z')
+ expect((await diagnostics(page)).objectCount).toBe(initial.objectCount)
+ await field(page,'name').press('Tab'); await setField(page,'name','卡车部件闭环完成')
+ await field(page,'name').press('Control+s'); await expect.poll(async()=> (await diagnostics(page)).dirty).toBe(false)
+ await page.reload(); await ready(page)
+ const reopened = await diagnostics(page), savedPart=byId(reopened,truck.sceneId).parts.find((p:any)=>p.sceneId===part.sceneId)
+ expect(reopened.objects.map((o:any)=>o.sceneId)).toEqual(initial.objects.map((o:any)=>o.sceneId))
+ expect(byId(reopened,secondTruck.sceneId).parts).toEqual(secondTruck.parts)
+ expect(savedPart.position).toEqual(byId(changed,truck.sceneId).parts.find((p:any)=>p.sceneId===part.sceneId).position)
+ expect(savedPart.name).toBe('独立部件验收')
+ await select(page, truck.sceneId); await action(page,'focus').click()
+ await shot(page,info,'03-truck-part-history-reopened')
+ evidence('truck-part-history',{projectId:id,partId:part.sceneId,initial,changed,reopened})
+})
+
+test('新建独立场景:取消、失败、保护原工程以及成功新 ID', async ({ page },info) => {
+ const token=await signIn(page), copy=await freshScene(token,'fox','新建'), id=copy.project.id
+ await openEditor(page,id); const initial=await diagnostics(page), fox=rootModel(initial,'157')
+ await select(page,fox.sceneId); await setField(page,'name','新建前未保存狐狸')
+ await action(page,'new').click(); await page.getByRole('button',{name:'取消',exact:true}).click()
+ expect(page.url()).toContain(`/${id}/edit`); expect(byId(await diagnostics(page),fox.sceneId).name).toBe('新建前未保存狐狸')
+ const endpoint='**/api/tran/v1/content/projects'
+ await page.route(endpoint,route=>route.request().method()==='POST'?route.fulfill({status:503,contentType:'application/json',body:JSON.stringify({code:503,message:'验收模拟创建暂不可用'})}):route.continue())
+ await action(page,'new').click(); await page.locator('.el-message-box__input input').fill('场景闭环-新建失败演练')
+ await page.getByRole('button',{name:'创建并编辑',exact:true}).click()
+ await page.getByRole('button',{name:'保存并离开',exact:true}).click()
+ await expect(page.locator('.el-message--error')).toContainText('创建暂不可用')
+ expect(page.url()).toContain(`/${id}/edit`); expect(byId(await diagnostics(page),fox.sceneId).name).toBe('新建前未保存狐狸')
+ await expect(page.locator('.el-message--error')).toHaveCount(0)
+ await setField(page,'name','新建失败仍可恢复的狐狸')
+ const recovery = () => page.evaluate(projectId => Object.entries(localStorage)
+ .filter(([key]) => key.includes(`edit3dv4:scene-api-recovery:${projectId}:session:`))
+ .map(([,value]) => JSON.parse(value)), id)
+ await expect.poll(async()=>(await recovery()).length).toBeGreaterThan(0)
+ await action(page,'new').click(); await page.locator('.el-message-box__input input').fill('场景闭环-放弃后创建失败演练')
+ await page.getByRole('button',{name:'创建并编辑',exact:true}).click()
+ await page.getByRole('button',{name:'放弃修改',exact:true}).click()
+ await expect(page.locator('.el-message--error')).toContainText('创建暂不可用')
+ expect((await diagnostics(page)).dirty).toBe(true)
+ expect((await recovery())[0].document.objects.find((o:any)=>o.sceneId===fox.sceneId).name).toBe('新建失败仍可恢复的狐狸')
+ expect((await call(`tran/v1/content/projects/${id}`,token)).data.currentVersion.content.objects.find((o:any)=>o.sceneId===fox.sceneId).name).toBe('新建前未保存狐狸')
+ await shot(page,info,'04-new-failure-original-preserved'); await page.unroute(endpoint)
+ await action(page,'new').click(); await page.locator('.el-message-box__input input').fill('场景闭环-真正新建工程')
+ const response=page.waitForResponse(r=>r.url().endsWith('/api/tran/v1/content/projects')&&r.request().method()==='POST')
+ await page.getByRole('button',{name:'创建并编辑',exact:true}).click()
+ await page.getByRole('button',{name:'放弃修改',exact:true}).click()
+ const result=await(await response).json(); registerProject(result.data.project)
+ const newId=result.data.project.id; expect(newId).not.toBe(id)
+ await expect(page).toHaveURL(new RegExp(`/content/scenes/${newId}/edit`)); await ready(page); await save(page)
+ expect(await recovery()).toEqual([])
+ await page.reload(); await ready(page); await shot(page,info,'05-new-separate-scene')
+ await openEditor(page,id)
+ expect(byId(await diagnostics(page),fox.sceneId).name).toBe('新建前未保存狐狸')
+ expect((await diagnostics(page)).dirty).toBe(false)
+ await openEditor(page,newId)
+ const previous=(await call(`tran/v1/content/projects/${id}`,token)).data
+ expect(previous.currentVersion.content.objects.find((o:any)=>o.sceneId===fox.sceneId).name).toBe('新建前未保存狐狸')
+ evidence('new-project',{sourceId:id,createdId:newId,sourceName:previous.project.name,current:await diagnostics(page)})
+})
+
+test('只读管理员:可浏览预览与导出,模板/资源/键盘/接口不能改场景', async ({ page },info) => {
+ const token=await signIn(page,'admin')
+ await openEditor(page,'160'); const before=await diagnostics(page)
+ expect(before.readOnly).toBe(true)
+ for(const name of ['new','open','save','undo','redo','snap','delete','publish']) await expect(action(page,name)).toBeDisabled()
+ await page.locator('[data-left-tab="scenes"]').click()
+ await expect(page.locator('[data-preset-id]').first()).toBeDisabled()
+ await page.locator('[data-left-tab="assets"]').click()
+ await expect(page.locator('[data-import-model]')).toBeDisabled()
+ expect(await page.locator('[data-editor-asset-id]').first().getAttribute('draggable')).toBe('false')
+ await page.locator('[data-left-tab="outline"]').click(); const fox=rootModel(before,'157'); await select(page,fox.sceneId)
+ await expect(field(page,'name')).toBeDisabled(); await action(page,'focus').click()
+ await action(page,'preview').click(); expect((await diagnostics(page)).previewing).toBe(true)
+ await page.keyboard.press('Delete'); await page.keyboard.press('w'); await page.keyboard.press('Control+z')
+ expect((await diagnostics(page)).objects).toEqual(before.objects)
+ await page.keyboard.press('Escape'); expect((await diagnostics(page)).previewing).toBe(false)
+ const exported=await exportDocument(page); expect(exported.objects.length).toBe(before.objectCount)
+ const original=(await call('tran/v1/content/projects/160',token)).data
+ const denied=await call('tran/v1/content/projects/160',token,{...original.project,content:original.currentVersion.content,
+ dependencies:original.currentVersion.dependencies,assets:original.currentVersion.assets},'PUT'); expect(denied.status).toBe(403)
+ await shot(page,info,'06-readonly-scene-permissions')
+ evidence('readonly',{before,after:await diagnostics(page),directWriteStatus:denied.status})
+})
diff --git a/tools/build-model-editor-closure-report.mjs b/tools/build-model-editor-closure-report.mjs
new file mode 100644
index 0000000..30b69c8
--- /dev/null
+++ b/tools/build-model-editor-closure-report.mjs
@@ -0,0 +1,300 @@
+import fs from 'node:fs'
+import path from 'node:path'
+import crypto from 'node:crypto'
+import { fileURLToPath } from 'node:url'
+import { resolveBrowserProof, screenshotProofStatus } from './model-editor-report-evidence.mjs'
+
+// This tool renders evidence. It neither exercises the application nor reads credentials.
+const toolDir = path.dirname(fileURLToPath(import.meta.url))
+const reportDir = path.resolve(toolDir, '../reports/model-editor-closure-20260906')
+const workspace = path.resolve(toolDir, '../../..')
+fs.mkdirSync(path.join(reportDir, 'data'), { recursive: true })
+const baseline = path.join(workspace, '.codex-tmp/model-closure-20260906/inventory-main.md')
+if (fs.existsSync(baseline)) fs.copyFileSync(baseline, path.join(reportDir, 'data/inventory-main.md'))
+const esc = value => String(value ?? '').replace(/[&<>"']/g, c => ({ '&': '&', '<': '<', '>': '>', '"': '"', "'": ''' })[c])
+const arr = value => Array.isArray(value) ? value : []
+const encode = value => value.replaceAll('\\', '/').split('/').map(encodeURIComponent).join('/')
+const clean = value => typeof value === 'string' ? value.replace(/\bBearer\s+[^\s"'<>]+/gi, 'Bearer [已隐藏]').replace(/\beyJ[\w-]+\.[\w-]+\.[\w-]+\b/g, '[已隐藏]')
+ : Array.isArray(value) ? value.map(clean)
+ : value && typeof value === 'object' ? Object.fromEntries(Object.entries(value).filter(([key]) => !/^(password|secret|accessToken|refreshToken|token|authorization|cookie|uploadTicket|credentials)$/i.test(key)).map(([key, child]) => [key, clean(child)])) : value
+const missing = [], malformed = []
+function read(name, required = false) {
+ const file = path.join(reportDir, name)
+ if (!fs.existsSync(file)) { if (required) missing.push(name); return null }
+ try { return clean(JSON.parse(fs.readFileSync(file, 'utf8').replace(/^\uFEFF/, ''))) }
+ catch (error) { malformed.push({ file: name, error: error.message }); return null }
+}
+function state(value) {
+ if (value === true) return 'PASS'
+ if (value === false) return 'FAIL'
+ const text = String(value ?? '').toUpperCase()
+ if (['PASS', 'PASSED', 'SUCCESS', 'SUCCEEDED', 'EXPECTED'].includes(text)) return 'PASS'
+ if (['FAIL', 'FAILED', 'ERROR', 'UNEXPECTED', 'TIMEDOUT', 'INTERRUPTED'].includes(text)) return 'FAIL'
+ if (['FLAKY', 'WARN', 'WARNING'].includes(text)) return 'WARN'
+ if (['SKIP', 'SKIPPED'].includes(text)) return 'SKIP'
+ return 'PENDING'
+}
+const labels = { PASS: '已验证', FAIL: '失败', WARN: '需复核', PENDING: '待验证', SKIP: '未执行' }
+const badge = (value, label) => `${esc(label || labels[value] || value)} `
+const link = (file, label = file) => fs.existsSync(path.resolve(reportDir, file)) ? `${esc(label)} ` : `${esc(label)} · 待提供 `
+const pretty = value => `${esc(JSON.stringify(value, null, 2))} `
+const sourceFiles = { main: '../../../unreal_tran/src/main.js', timeline: '../../../unreal_tran/src/timeline.js', blueprint: '../../../unreal_tran/src/blueprint.js', curve: '../../../unreal_tran/src/model-curve-editor.js', state: '../../../unreal_tran/src/model-editor-state.js', api: '../../../unreal_tran/src/model-content-api.js', io: '../../../unreal_tran/src/editor-project-io.js', host: '../../../unreal_tran_web/src/views/content/LegacyModelEditorView.vue', bridge: '../../../unreal_tran_web/src/features/editors/model/legacy-iframe-permissions.ts' }
+
+// Implementation state and verification state are separate. No row is verified by source review alone.
+// [id, name, source locator, implementation, boundary, fix, planned acceptance]
+const groups = [
+ ['工程与数据', [
+ ['project.new', '新建工程', 'host:createNewProject', '本轮修复', '宿主创建新项目 ID 并跳转;新建不是清空并覆盖当前工程。', '新建复用正式 create API;创建失败或取消保留原工程。', '新建前后 ID 不同,原工程内容/版本不被覆盖;取消不发创建请求。'],
+ ['project.import-glb', '导入装备 GLB', 'main:importModelFile', '已有实现', '只支持单文件 GLB;上传→登记→解析→最终保存。', '延续导入 busy/失败阻断及稳定资产校验。', '选择轻量 GLB,真实模型可见;刷新仍加载同一资产。'],
+ ['project.import-json', '导入工程 JSON', 'main:importProjectFile', '本轮修复', '导入到当前工程,不创建副本;受控引用必须属于当前可用资产。JSON 不含 GLB 字节。', '导入前 materialize 稳定资产引用。', '同项目导出后再导入,材质/动画/环境不丢;无效或跨项目缺失引用报错。'],
+ ['project.save', '手动保存 / Ctrl+S', 'main:saveProject', '已有实现', '同一工程保存内容和当前视口封面;成功以 API 响应为准。', '保留队列、版本保护和封面失败回退。', '保存真实 PUT 成功,刷新字段不丢;重复点击不交错。'],
+ ['project.autosave', '30 秒自动保存', 'main:bindShellEvents', '已有实现', '自动保存工程内容;不反复截图更新封面,恢复稿待确认期间暂停。', '导入/封面保存期间阻断重入。', '修改后等待自动保存并刷新;忙碌时不提交半成品。'],
+ ['project.name', '修改工程名', 'main:projectNameInput', '已有实现', '输入→失焦/Enter→保存;当前模型工程名。', '无本轮专门改动。', '改名保存后列表和重新打开一致。'],
+ ['project.export-json', '另存项目 JSON', 'main:saveProjectAs', '已有实现', '下载工程 JSON;不是另建服务端项目,也不内嵌 GLB 文件。', '稳定资产引用保留,临时 blob 地址清除。', '下载文件可解析,包含轨道/蓝图/自定义字段,配套资源可重新解析。'],
+ ['project.undo', '撤销 / Ctrl+Z', 'main:class HistoryStack', '本轮修复', '最多 60 个历史快照;不是数据库版本回滚。', '异步互斥,失败不移动索引,托管 GLB 快照重新解析。', '属性/曲线修改撤销;连续点击不并发;下载失败索引与修改保留。'],
+ ['project.redo', '重做 / Ctrl+Y', 'main:class HistoryStack', '本轮修复', '撤销后重做;产生新编辑时清掉旧 redo 分支。', '与撤销共用异步恢复保护。', '重做恢复相同字段及姿态,不回放无效分支。'],
+ ['project.recovery', '异常恢复副本', 'main:reconcileApiRecovery', '本轮修复', '同版本恢复稿需用户确认;旧版本隔离/下载,不静默覆盖服务器。', '恢复稿重新解析受控资源;指纹忽略对象键顺序,避免已保存页面刷新误生恢复稿。', '同版本恢复可见;旧版本保留/下载;主动保存前不自动 PUT。'],
+ ['project.conflict', '保存/发布版本冲突', 'api:queueSave', '已有实现', '409 阻断;恢复稿保留。', '发布封面队列同样遵守冲突保护。', '定向模拟 409,无后续发布请求,不误报成功。'],
+ ['project.permissions', '查看/编辑/新增/发布权限', 'bridge:installLegacyModelPermissions', '本轮修复', '按 content.model.* 权限,不按 teacher/admin 身份推断写权限;只读仍能搜索、选择原生动画和键盘激活预览。', '新建接真实 create 能力;只发布可发布已保存版本;修复查看控件键盘事件误拦截。', '只读查看操作可用且无内容写请求;不可编辑/保存;publisher 不上传/PUT,正常发布已有版本。'],
+ ['project.leave', '离开/切角色未保存确认', 'host:confirmLeave', '已有实现', '取消留在当前页;保存失败不得离开;上传/保存/发布忙时保留状态。', '新建也复用现有未保存确认。', '取消、放弃、保存失败与 busy 分别验证,没有误清 dirty。'],
+ ]],
+ ['资源库与场景树', [
+ ['resource.catalog', '资源库浏览 / 选择装备', 'main:renderAssets', '本轮修复', '切换装备可能应用其基准时间轴/蓝图;不是把第二个主装备追加进当前模型。', '修复单节点选择器误用导致启动报错;忙碌期间禁止切换资源。', '完整资源库 DOM 初始化、卡片事件、空目录启动正常;切换实际几何可见,保存后资源一致。'],
+ ['resource.search', '资源搜索', 'main:applyAssetSearch', '已有实现', '按资源名称/关键词过滤现有目录,不搜索服务器全站。', '无本轮专门改动。', '关键字命中、空结果与清空恢复。'],
+ ['environment.builtin', '内置车间 / 野外 / 装备车库', 'main:resetEnvironmentPlaceholder', '已有实现', '车间/野外为程序化环境;车库为随包 GLB。', '保留真实 GLB 和占位环境来源区别。', '三种切换与重新打开可重现相应环境。'],
+ ['environment.external', '导入外部车间 / 野外 GLB', 'main:loadEnvironmentSceneFile', '本轮修复', '一个环境根;受控 MODEL_FILE 资产 metadata.usage=environment,与主装备独立。', '上传、最终保存、JSON 与刷新恢复稳定引用;失败阻断半提交。', '小 GLB 上传后主装备不被替换;环境刷新仍可见且资产引用一致。'],
+ ['resource.loading', '模型加载进度 / 失败 / 重试', 'host:installLegacyModelIframeLoadingScope', '已有实现', '局部加载状态;错误短提示,中性失败状态可重试。', '复用前轮加载范围与错误消息修复。', '资源失败不无限转圈;重试恢复;无凭据泄露。'],
+ ['tree.browse', '场景树节点与计数', 'main:buildSceneTree', '现有边界', '树行上限 220;统计节点总数不等于当前展开行数。', '无本轮容量重构。', '模型/环境节点可达,计数说明不当作全部节点已经逐项验收。'],
+ ['tree.expand', '节点展开 / 折叠', 'main:buildSceneTree', '已有实现', '当前界面浏览状态;无须持久化展开布局。', '无本轮专门改动。', '展开和折叠子级不修改模型对象。'],
+ ['tree.search', '场景树搜索', 'main:buildSceneTree', '已有实现', '搜索当前载入对象,不新增/删除对象。', '无本轮专门改动。', '搜索节点后选中正确对象,清空恢复树。'],
+ ['tree.visibility', '显示 / 隐藏对象', 'main:buildSceneTree', '本轮修复', '对象 visible 保存;隐藏祖先的子对象不接受射线拾取。', '拾取与高亮统一检查祖先可见性。', '隐藏父级后不能点到子级,保存重开仍隐藏。'],
+ ['selection.scope', '整体 / 部件级选择', 'main:setSelectionScope', '已有实现', '整体作用主装备根;部件作用实际节点。', '保留当前选择层级合同。', '点击可见 mesh,整体/部件切换选择目标不同。'],
+ ['selection.sync', '树 / 视口 / 属性选择联动', 'main:selectObject', '本轮修复', '显示实际对象并更新辅助框;环境也是可编辑对象。', '关闭轮廓后重选不恢复辅助框。', '三处名称/对象 ID 一致,重选后轮廓状态保持。'],
+ ]],
+ ['三维视图与操作', [
+ ['view.orbit', '鼠标旋转 / 平移 / 缩放视角', 'main:OrbitControls', '已有实现', '当前三维相机;不是模型变换。', '无本轮专门改动。', '鼠标操作改变镜头但不改变对象局部坐标。'],
+ ['view.focus', '聚焦选中 / F', 'main:fitCamera', '已有实现', '按当前对象包围盒定位相机。', '无本轮专门改动。', '小模型/部件聚焦后仍可见,不被裁剪。'],
+ ['view.move', '移动工具 / W', 'main:setTransformMode', '本轮修复', '拖拽编辑对象局部位置,保存后重开一致。', '动画姿态下更新基值只扣实际偏移。', '移动后保存、刷新及动画再播放不漂移。'],
+ ['view.rotate', '旋转工具 / E', 'main:setTransformMode', '本轮修复', '对象旋转;与当前时间轴旋转叠加需要一致。', '统一动画基值处理。', '旋转后保存/撤销/重做姿态一致。'],
+ ['view.scale', '缩放工具 / R', 'main:setTransformMode', '已有实现', '对象缩放持久化;时间轴没有缩放轨道 UI。', '无新增缩放动画能力。', '对象缩放保存和重开正确。'],
+ ['view.grid', '网格开关', 'main:toggleGrid', '已有实现', '临时视图选项,不要求写入工程。', '非持久化不计保存缺陷。', '开关只影响辅助网格,不改变模型。'],
+ ['view.pbr', 'PBR 光照开关', 'main:handleView', '已有实现', '当前照明预览选项;不是批量材质编辑。', '非持久化不计保存缺陷。', '开关改变显示,物体材质字段不被覆盖。'],
+ ['view.wireframe', '线框开关', 'main:handleView', '已有实现', '当前视图选项;不承诺保存到工程。', '非持久化不计保存缺陷。', '线框开关恢复正常着色。'],
+ ['view.outline', '选中轮廓开关', 'main:handleView', '本轮修复', '当前会话偏好,进出预览后保持;不要求跨重载存储。', '重选时不重新泄露轮廓。', '关闭→重选→预览→退出,轮廓仍关闭。'],
+ ['view.perspective', '透视视角按钮', 'main:handleView', '现有边界', '重置透视观察角度;当前没有正交视角菜单。', '不把临时视图状态当作保存缺陷。', '点击后相机和聚焦目标正常。'],
+ ['view.snapshot', '下载视口截图', 'main:downloadSnapshot', '已有实现', '当前 canvas PNG,可包含编辑辅助元素;保存封面是独立截图流程。', '无本轮专门改动。', 'PNG 下载可打开,内容与当前视口对应。'],
+ ['view.panels', '底部页签 / Tab / 面板高度', 'main:switchBottomMode', '已有实现', '时间轴、蓝图、曲线、日志切换;拖分割条只调整当前工作区。', '曲线入口接真实数据。', '各页签显示正确内容,调整高度后画布正确 resize。'],
+ ['view.preview', '进入 / 退出运行预览', 'main:togglePreview', '本轮修复', '真实当前三维场景;隐藏辅助工具,退出不丢编辑状态。', '进入显式派发 auto;退出停止运行并恢复轮廓策略。', '进入/退出与 Esc,三维仍可见,无辅助框泄漏。'],
+ ['view.preview-step', '预览播放 / 前后导航', 'main:bindShellEvents', '现有边界', '播放运行蓝图;前后按钮当前按时间轴 ±2 秒,不是按业务工序跳转。', '无新增业务工序导航。', '播放触发真实流程;前后跳时与时间轴一致。'],
+ ]],
+ ['对象属性与扩展标记', [
+ ['property.name', '对象名称', 'main:bindInspectorEvents', '已有实现', '修改当前节点名,标识保持稳定。', '无本轮专门改动。', '树和属性同步,保存重开名称保留。'],
+ ['property.identity', '对象标识 / 状态说明', 'main:renderInspector', '本轮修复', '标识只读;可见性含祖先状态,阴影取实际castShadow;编辑能力由宿主权限注入。', '不再固定显示“可见/投射阴影”,补宿主可编辑状态钩子。', '标识不可编辑;选中对象、隐藏状态、阴影及只读权限标签一致。'],
+ ['property.position', '位置 XYZ 数值', 'main:bindInspectorEvents', '本轮修复', '局部坐标;动画当前帧下编辑也能生成正确基值。', '中性姿态与实际应用偏移统一。', '三个轴编辑、保存重开、动画回放一致。'],
+ ['property.rotation', '旋转 XYZ 数值', 'main:bindInspectorEvents', '本轮修复', '输入角度转弧度存储,显示角度。', '多轨旋转基值不重复扣除。', '度数显示与存储/播放的弧度一致。'],
+ ['property.scale', '缩放 XYZ 数值', 'main:bindInspectorEvents', '已有实现', '对象当前缩放,不添加新的缩放轨道。', '无本轮专门改动。', '非均匀缩放可保存和重开。'],
+ ['property.color', '材质基础色', 'main:bindInspectorEvents', '现有边界', '只编辑当前对象第一个材质槽;共享材质可能影响共享该实例的网格。', '界面明确材质槽 1/N。', '颜色修改、保存重开对应当前槽;不宣称所有槽可编辑。'],
+ ['property.metalness', '材质金属度', 'main:bindInspectorEvents', '现有边界', '当前首槽 PBR 参数 0~1。', '标明材质槽范围。', '拖动参数有效,保存重开值一致。'],
+ ['property.roughness', '材质粗糙度', 'main:bindInspectorEvents', '现有边界', '当前首槽 PBR 参数 0~1。', '标明材质槽范围。', '参数与当前槽持久化一致。'],
+ ['property.opacity', '材质透明度', 'main:bindInspectorEvents', '现有边界', '当前首槽;联动 transparent/depthWrite。', '保留实际透明度保存字段。', '透明效果与重开结果一致。'],
+ ['property.code', '自定义资源编码', 'main:bindInspectorEvents', '本轮修复', '对象 customProperties.resourceCode,不是服务器资产主键。', '输入真正回写、保存、恢复及撤销。', '编辑、清空、重选、JSON 往返、刷新均一致。'],
+ ['property.status', '自定义运行状态', 'main:bindInspectorEvents', '本轮修复', 'NORMAL/SERVICE 标签,不会发起真实设备控制。', '真正写回对象,首次变更可撤销。', '两状态切换及刷新保留。'],
+ ['property.binding', '流程绑定说明', 'main:renderInspector', '现有边界', '只读显示模板/时间轴名称,不是教学任务的设备或物联网绑定。', '无本轮业务绑定改造。', '只读,不将其当作任务绑定操作验收。'],
+ ['component.inspect', '组件页内置能力说明', 'main:renderComponents', '现有边界', '变换、场景节点、渲染等派生说明,没有通用组件启停系统。', '区分内置说明与自定义标记。', '显示与对象类型一致,不虚报可执行组件配置。'],
+ ['component.tag', '添加扩展标记', 'main:renderComponents', '本轮澄清', '只保存名称标签;没有脚本、启停、运行时行为。', '从“添加组件”改为“扩展标记,仅保存标签”。', '新增标签保存/重开;不得声称已启用新行为。'],
+ ]],
+ ['时间轴与曲线', [
+ ['timeline.play', '播放 / 暂停 / Space', 'timeline:play()', '已有实现', '播放当前编辑时间轴;原生 GLB 动画单独控制。', '停止清理与时间同步由本轮补强。', '播放进度/真实对象姿态变化,暂停停在当前帧。'],
+ ['timeline.stop', '停止', 'timeline:stop()', '已有实现', '停止并回到起始时间,按起始帧应用动画。', '无本轮语义扩展。', '按钮停止后不再自行前进。'],
+ ['timeline.loop', '循环开关', 'timeline:#bindEvents', '本轮修复', '循环设置随 timeline 保存;不循环到达终点只完成一次。', '循环变更进入历史/dirty。', '开关、保存重开、到末尾/循环边界正确。'],
+ ['timeline.key-nav', '上一 / 下一关键帧', 'timeline:#onClick', '已有实现', '按时间轴关键帧时间定位。', '无本轮专门改动。', '多个轨道时跳到正确相邻关键时刻。'],
+ ['timeline.seek', '拖播放头 / 点击刻度', 'timeline:#onPointerDown', '本轮修复', '实时求值当前帧;键盘左右移动一帧,Shift 移动一秒。', '拖动补发 timechange,解绑指针监听。', '时间码、曲线和视口姿态同步。'],
+ ['timeline.add-track', '添加轨道', 'timeline:addTrack(', '已有实现', '当前主装备节点的变换轨道。', '无新增轨道类型。', '增加后可绑定节点并保存。'],
+ ['timeline.remove-track', '删除轨道', 'timeline:removeTrack(', '本轮修复', '删除后不再保留该轨道施加的旧姿态。', '宿主恢复此前作用的属性基值。', '删除即时复原,再保存/重开一致。'],
+ ['timeline.target', '修改轨道目标节点', 'timeline:updateSelectedTrack', '本轮修复', '用稳定对象标识绑定,不靠同名第一项。', '重绑清理旧目标的应用姿态。', '从 A 改为 B,A 复原,B 播放。'],
+ ['timeline.property', '选择位置 / 旋转属性', 'timeline:updateSelectedTrack', '已有实现', '位置 XYZ 向量、旋转 X/Y/Z;没有缩放轨道 UI。', '保留当前实际能力。', '属性切换后关键帧类型和播放一致。'],
+ ['timeline.mute', '轨道静音', 'timeline:#onClick', '本轮修复', '仅忽略当前轨道,不关闭其他轨道。', '不再停留旧姿态或保存时重复扣值。', '静音/恢复后实际姿态与重开一致。'],
+ ['timeline.record', '记录当前姿态关键帧', 'main:addKeyframeFromSelection', '本轮修复', '记录相对对象基值的当前值。', '与多轨实际应用/保存采用同一基值。', '操作对象后记录,回放命中相同姿态。'],
+ ['timeline.add-key', '双击新增关键帧', 'timeline:#onDoubleClick', '已有实现', '在轨道对应时刻创建/更新关键帧。', '时间碰撞保留当前编辑帧。', '新增后节点可选,时间/数值可保存。'],
+ ['timeline.delete-key', '删除关键帧 / Delete', 'timeline:deleteSelected', '已有实现', '删除选中的关键帧;输入框编辑时不截获删除键。', '删除和监听清理回归。', '删除后曲线和播放变化,撤销可恢复。'],
+ ['timeline.drag-key', '关键帧拖动 / Alt 取消吸附', 'timeline:#onPointerDown', '本轮修复', '默认 1/30 秒吸附;重叠时合并,保留正在编辑帧。', '拖动时间同步,碰撞处理确定。', '拖动/重叠/Alt 行为和保存后时间正确。'],
+ ['timeline.zoom', '时间轴缩放', 'timeline:setZoom', '已有实现', '显示缩放随 timeline 记录;不改变时长/关键帧数值。', '无本轮专门改动。', '缩放不改变动作内容。'],
+ ['timeline.multitrack', '多轨组合与保存基值', 'state:applyEditorTimelinePose', '本轮修复', '不同属性组合;同对象同属性最后有效轨道生效。', '求值、手工编辑基值、序列化一致。', '两轨同属性、静音、删轨、重绑、重开无漂移。'],
+ ['timeline.baseline', '载入装备基准动作', 'main:loadBaselineAnimation', '现有边界', '匹配部件后直接替换当前时间轴/蓝图;当前没有单独确认框,不是任意维修流程自动生成。', '保持当前装备限定;无匹配只提示,不改工程。', '匹配时载入/保存/重开;无匹配时保留原数据并给出提示。'],
+ ['timeline.native', 'GLB 原生动画选择 / 播放', 'main:selectNativeAnimation', '已有实现', '真实 GLB 自带片段;保存选择索引,不是蓝图 clip 字段。', '无片段时禁用。', '狐狸实际动画播放/暂停,选择保存后重开一致。'],
+ ['timeline.curve', '曲线选择轨道 / 关键帧', 'curve:class ModelCurveEditor', '本轮修复', '曲线与时间轴共用一份轨道和关键帧数据。', '移除固定 SVG 假数据。', '实际轨道名和点数匹配时间轴,点击同步当前帧。'],
+ ['curve.time', '曲线编辑时间', 'curve:change(input)', '本轮修复', '更新实际 key.time,约束在时间轴时长内。', '调用 timeline.updateKeyframe 并保持编辑目标。', '改时间后重排、撤销、JSON 往返一致。'],
+ ['curve.value', '曲线编辑标量 / XYZ 数值', 'curve:change(input)', '本轮修复', '修改实际关键帧值,向量编辑保留其他分量。', '无效空值拒绝提交。', '标量和向量都驱动真实姿态并保存。'],
+ ['curve.easing', '曲线插值', 'curve:change(input)', '本轮修复', '支持线性、平滑、缓入、缓出、阶梯;没有 Bezier 切线 UI。', '阶梯插值与已有下游时间轴求值一致。', '修改插值改变中间帧,重开保留。'],
+ ['curve.axis', '曲线 XYZ 分量显示', 'curve:change(input)', '已有实现', '改变图上显示分量,不改另外两轴的数据。', '当前实际向量轨道才出现。', '切分量显示不同曲线,数值不被改写。'],
+ ]],
+ ['蓝图与交互反馈', [
+ ['blueprint.palette', '节点库搜索 / 拖入节点', 'blueprint:_renderPalette', '已有实现', '当前七类节点:触发、时间轴、等待、高亮、提示、条件、完成。', '不新增任意脚本节点。', '搜索、拖入后节点类型和参数可保存。'],
+ ['blueprint.add', '添加节点按钮', 'blueprint:_bind()', '已有实现', '工具栏默认添加提示节点;其他类型来自节点库。', '无本轮专门改动。', '新增进入画布、历史与 JSON。'],
+ ['blueprint.parameters', '节点标题 / 参数编辑', 'blueprint:_renderInspector', '已有实现', '按节点类型提供数字、文字、枚举、复选框。', '保留数值边界与真实数据写回。', '改参数与标题后重开保留。'],
+ ['blueprint.move', '节点拖动 / 多选框选', 'blueprint:_onViewportPointerDown', '已有实现', '节点布局随蓝图 JSON 保存。', '无本轮专门改动。', '多选移动位置正确,撤销恢复。'],
+ ['blueprint.connect', '连接 / 替换 / 删除执行线', 'blueprint:connect(', '本轮修复', '单输出重连替换;允许多个来源汇聚同一输入。', '不再清掉同一输入的其他合法来源。', 'true/false 汇聚保留两条,替换输出只影响该线。'],
+ ['blueprint.delete', '删除节点 / 连线', 'blueprint:deleteSelection', '已有实现', '删除节点一并清理相关边。', '复制/剪切使用同一图数据合同。', '无悬空边;撤销恢复完整图。'],
+ ['blueprint.copy', '复制 / 剪切 / 粘贴', 'blueprint:copySelection', '本轮修复', 'Ctrl/Cmd+C/X/V;复制选区内部边,不复制指向外部的边。', '新节点 ID 和内部连线同步生成,阻止快捷键冒泡。', '副本内部连接正确,不误删外部对象。'],
+ ['blueprint.canvas', '平移 / 缩放 / 适应画布', 'blueprint:frameAll', '已有实现', '蓝图画布浏览;不是三维相机。', '无本轮专门改动。', '滚轮、Space 拖动和适应可见全部节点。'],
+ ['blueprint.run', '运行 / F5', 'blueprint:async run(', '本轮修复', '在当前模型编辑器驱动真实场景;有 200 步循环上限。', '条件与等待流程回归。', '执行高亮、动作、反馈;循环图可终止不挂死。'],
+ ['blueprint.stop', '停止运行', 'blueprint:stop(', '本轮修复', '停止中断挂起等待,旧运行不覆盖新运行状态。', 'AbortSignal 贯穿宿主动作与高亮清理。', '等待/动画中停止及时退出,无迟到完成提示。'],
+ ['blueprint.reset', '重置执行', 'blueprint:resetExecution', '已有实现', '复位执行状态、时间轴和高亮;保留蓝图编辑内容。', '高亮恢复原材质。', '运行后重置,进度归零且节点图不丢。'],
+ ['blueprint.click', '点击触发目标', 'blueprint:async trigger(', '本轮修复', '匹配事件和目标 ID/名称/祖先别名;空目标可通配。', '不再点击 B 触发 A。', '两个不同目标分别点击,只运行匹配分支。'],
+ ['blueprint.double-click', '双击触发目标', 'main:pickObject', '本轮修复', '双击独立派发 double-click,与单击区分。', '增加真实视口双击派发并抑制待发单击。', '双击只触发目标的双击分支。'],
+ ['blueprint.auto', '自动触发', 'main:togglePreview', '本轮修复', '仅进入预览显式派发 auto,不把每次单击当 auto。', '事件匹配与进入预览联动。', '进入预览触发一次,普通点击不重放自动分支。'],
+ ['blueprint.timeline', '播放时间轴节点', 'main:playTimelineForBlueprint', '本轮修复', '播放当前唯一时间轴,动作名称仅描述;支持起始时间、速度、等待。', '起始末帧不反跳零,终止可取消。', '起始时间和速度实际生效,等待与不等待分支正确。'],
+ ['blueprint.wait', '等待节点', 'blueprint:NODE_TYPES', '已有实现', '指定毫秒等待;可被停止取消。', '异步停止及时结束。', '等待期间停止不会继续执行后续节点。'],
+ ['blueprint.highlight', '高亮节点 / 呼吸效果', 'main:highlightPart', '本轮修复', '准确命中可见对象;同名歧义要求标识;没有可高亮材质则报错。', '拒绝错误整机回退,真实呼吸效果,保存忽略临时高亮。', '目标正确、找不到报错、结束恢复、保存不永久染色。'],
+ ['blueprint.message', '操作提示节点', 'main:showTeachingMessage', '现有边界', '显示标题/文本,可点击关闭或约 1.8/2.5 秒自动继续;不是强制人员确认。', '不将其当作业务审核/签名。', '文本出现、关闭和停止不留弹层。'],
+ ['blueprint.condition', '条件判断节点', 'main:evaluateBlueprintCondition', '本轮修复', '当前运行变量、嵌套字段、truthy/布尔/数字比较。', 'false 没有出口不走 true,字符串布尔正确解析。', '真假分支分别验证,缺出口不误执行。'],
+ ['blueprint.finish', '流程完成节点', 'main:initEditors', '已有实现', '本次预览结果/进度提示,不写教学任务成绩。', '保持本次运行反馈边界。', '成功/完成提示与实际执行结果一致。'],
+ ]],
+ ['发布、交付与运行信息', [
+ ['publish.preflight', '生成 / 发布检查面板', 'main:togglePublishPanel', '现有边界', '本地主要检查资源引用存在及轨道/节点数量,不执行完整蓝图可达性、终止性或业务校验;服务端发布校验最终生效。', '发布目标与离线交付能力明确。', '可打开关闭;不完整工程不能凭界面绿标绕过服务端。'],
+ ['publish.api', '发布到场景资源库', 'main:publishToSceneLibrary', '本轮修复', '先保存当前工程再发布固定版本;纯 publisher 发布已保存版本。', '整个保存/发布队列互斥,失败不发后续发布。', '发布版本/模型资产/列表状态一致,保存失败不发布。'],
+ ['publish.cover', '发布时当前三维截图封面', 'main:publishToSceneLibrary', '本轮修复', 'JPEG 当前视口;截图或上传失败可保留旧封面并明确提示。', '与手动保存共用封面链路,409 阻断。', '真实封面图对应当前模型;上传失败提示一次,旧图保留。'],
+ ['publish.html', '导出步骤演示 HTML', 'main:exportStandaloneHtml', '本轮澄清', '工程数据、静态截图和步骤演示;不包含 GLB、不执行真实三维交互。', '生成面板和按钮移除完整离线运行的误导。', '下载可打开、步骤导航可用,醒目标明不含三维模型。'],
+ ['publish.downstream', '下游读取模型固定版本', 'api:publishDocument', '现有边界', '已发布模型作为资源引用;下游自动执行模型 timeline/blueprint 尚无统一运行桥接。', '本轮不将资源加载成功等同动作自动运行成功。', '固定引用/可见性单独测;自动动作执行列为待确认范围。'],
+ ['feedback.log', '运行日志', 'main:log(message', '已有实现', '本次内存运行日志,最多 160 条;不是服务端审计日志。', '错误信息转义,保留清晰运行结果。', '选中/运行/保存产生相应日志,不要求刷新保留。'],
+ ['feedback.stats', '节点 / 三角面 / FPS 等状态', 'main:updateStats', '现有边界', '节点与三角面统计、当前 FPS;内存为估算值,不是精确浏览器内存剖析。', '无本轮性能仪表重构。', '载入模型后统计有合理变化,状态不作为性能验收数据。'],
+ ['feedback.toast', '成功 / 错误短消息', 'main:toast(message', '已有实现', '瞬时反馈;保存/发布成功以接口完成为准。', '封面失败不伪报全部成功。', '失败出现一次明确提示,自动消失,不形成重复常驻错误条。'],
+ ]],
+]
+
+const sourceCache = new Map()
+function locate(locator) {
+ const colon = locator.indexOf(':'), key = locator.slice(0, colon), needle = locator.slice(colon + 1), file = sourceFiles[key]
+ if (!sourceCache.has(file)) sourceCache.set(file, fs.existsSync(path.resolve(reportDir, file)) ? fs.readFileSync(path.resolve(reportDir, file), 'utf8') : '')
+ const lines = sourceCache.get(file).split(/\r?\n/)
+ const name = needle.replace(/^async /, '').replace(/\(.*/, '')
+ const declaration = new RegExp(`^\\s*(?:export\\s+)?(?:async\\s+)?(?:function\\s+)?${name.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')}\\s*\\(`)
+ const declared = lines.findIndex(line => declaration.test(line))
+ const index = declared >= 0 ? declared : lines.findIndex(line => line.includes(needle))
+ return { file, line: index < 0 ? null : index + 1, symbol: needle }
+}
+const catalog = groups.flatMap(([group, rows]) => rows.map(([id, name, locator, implementation, boundary, fix, acceptance]) => ({ id, group, name, source: locate(locator), implementation, boundary, fix, acceptance })))
+fs.writeFileSync(path.join(reportDir, 'feature-catalog.json'), JSON.stringify({ schemaVersion: 1, features: catalog }, null, 2))
+
+const results = read('results.json', true), validation = read('validation.json', true), cleanup = read('cleanup.json'), artifactChecks = read('artifact-checks.json'), runHistory = read('run-history.json'), finalCorrections = read('data/final-corrections.json')
+const proofFiles = fs.existsSync(path.join(reportDir, 'evidence')) ? fs.readdirSync(path.join(reportDir, 'evidence')).filter(name => name.endsWith('.json')).sort().map(name => `evidence/${name}`) : []
+const proofData = proofFiles.map(file => ({ file, data: read(file) }))
+function e2eRows(suites, parents = []) {
+ return arr(suites).flatMap(suite => [
+ ...arr(suite.specs).flatMap(spec => arr(spec.tests).map(item => {
+ const last = arr(item.results).at(-1)
+ return { title: [...parents, spec.title].filter(Boolean).join(' · '), rawTitle: spec.title, status: item.status === 'unexpected' ? 'FAIL' : item.status === 'flaky' ? 'WARN' : state(last?.status), startTime: last?.startTime, duration: last?.duration, attachments: arr(last?.attachments).map(a => ({ name: a.name, path: a.path, contentType: a.contentType })), errors: arr(last?.errors).map(error => error.message || String(error)) }
+ })), ...e2eRows(suite.suites, suite.file ? parents : [...parents, suite.title].filter(Boolean)),
+ ])
+}
+const browserRows = e2eRows(results?.suites)
+const proofs = []
+function collect(value, file, inherited = {}) {
+ if (!value || typeof value !== 'object') return
+ const ownStatus = state(value.status ?? value.result ?? value.overallStatus), status = ownStatus === 'PENDING' ? inherited.status : ownStatus
+ const testTitle = value.testTitle || value.test || inherited.testTitle
+ const recordedAt = value.recordedAt || value.time || inherited.recordedAt
+ const kind = value.kind || value.type || inherited.kind || 'evidence'
+ const ids = [...arr(value.featureIds), ...arr(value.features), ...(typeof value.featureId === 'string' ? [value.featureId] : [])].filter(item => typeof item === 'string')
+ if (ids.length) proofs.push({ ids, file, status: status || 'PENDING', testTitle, recordedAt, kind, title: value.title || value.name || testTitle || file, screenshots: arr(value.screenshots), details: value.scope || value.note || value.details || '' })
+ for (const [key, child] of Object.entries(value)) if (child && typeof child === 'object' && !['features', 'featureIds', 'screenshots', 'attachments'].includes(key)) {
+ if (Array.isArray(child)) child.forEach(item => collect(item, file, { status, testTitle, recordedAt, kind }))
+ else collect(child, file, { status, testTitle, recordedAt, kind })
+ }
+}
+proofData.forEach(({ file, data }) => collect(data, file))
+collect(validation?.coverage, 'validation.json')
+for (let index = 0; index < proofs.length; index++) proofs[index] = resolveBrowserProof(proofs[index], browserRows)
+const coverage = catalog.map(feature => {
+ const evidence = proofs.filter(item => item.ids.includes(feature.id)), statuses = evidence.map(item => item.status)
+ const status = statuses.includes('FAIL') ? 'FAIL' : statuses.includes('WARN') ? 'WARN' : statuses.includes('PASS') ? 'PASS' : 'PENDING'
+ return { ...feature, verification: status, evidence }
+})
+const count = rows => ({ total: rows.length, pass: rows.filter(row => row.status === 'PASS').length, fail: rows.filter(row => row.status === 'FAIL').length, pending: rows.filter(row => !['PASS', 'FAIL'].includes(row.status)).length })
+const browserCount = count(browserRows), featureCount = count(coverage.map(row => ({ status: row.verification })))
+// A runtime result:false can be the expected result of an unmatched trigger.
+// Only explicit check outcomes / failed commands are failures, not arbitrary booleans in snapshots.
+const anyFailure = value => value && typeof value === 'object' && (state(value.overallStatus ?? value.status ?? (typeof value.result === 'string' ? value.result : undefined)) === 'FAIL' || (Number.isInteger(value.exitCode) && value.exitCode !== 0) || Object.values(value).some(child => child && typeof child === 'object' && (Array.isArray(child) ? child.some(anyFailure) : anyFailure(child))))
+const failed = browserCount.fail || featureCount.fail || arr(results?.errors).length || Number(results?.stats?.unexpected || 0) || anyFailure(validation) || anyFailure(cleanup) || anyFailure(artifactChecks) || malformed.length || proofData.some(item => anyFailure(item.data))
+const finalRun = validation?.e2eComplete === true || validation?.finalRun === true || validation?.e2e?.complete === true
+const runVerified = results && validation && browserCount.total > 0 && browserCount.pending === 0 && finalRun && state(validation.overallStatus ?? validation.status) === 'PASS'
+const finalArtifactsVerified = artifactChecks?.finalArtifactScan === true && state(artifactChecks.status ?? artifactChecks.secretScan) === 'PASS'
+ && ['results.json', 'validation.json', 'cleanup.json', 'playwright/index.html'].every(file => {
+ const target = path.join(reportDir, file)
+ return fs.existsSync(target) && artifactChecks.finalInputSha256?.[file] === crypto.createHash('sha256').update(fs.readFileSync(target)).digest('hex')
+ })
+const overall = failed ? 'FAIL' : runVerified && featureCount.pending === 0 && finalArtifactsVerified ? 'PASS' : 'PENDING'
+const overallTitle = failed ? '存在失败,当前验收未通过' : overall === 'PASS' ? '已列功能的记录验证通过' : runVerified && featureCount.pending === 0 ? '功能验收已通过,最终交付物扫描待完成' : runVerified ? '已执行验收通过,部分功能尚未逐项验证' : '实现已整理,最终验收证据待完成'
+fs.writeFileSync(path.join(reportDir, 'coverage.json'), JSON.stringify({ generatedAt: new Date().toISOString(), overall, featureCount, browserCount, finalRun, finalArtifactsVerified, features: coverage }, null, 2))
+
+const screenshotDir = path.join(reportDir, 'screenshots')
+const screenshots = fs.existsSync(screenshotDir) ? fs.readdirSync(screenshotDir).filter(name => /\.(png|jpe?g|webp)$/i.test(name)).sort() : []
+const captions = read('screenshot-captions.json') || {}
+const requirements = [
+ ['部件级选择与操作', '整体与部件区分;树、视口和属性联动;操作后可保存重开。', 'selection.scope / selection.sync / property.*'],
+ ['动作流程', '时间轴关键帧、曲线插值、蓝图顺序和真假分支有实际执行结果。', 'timeline.* / curve.* / blueprint.*'],
+ ['交互反馈', '点击/双击/自动事件、高亮、文字提示、停止和错误反馈与对象匹配。', 'blueprint.click / double-click / auto / highlight / message'],
+ ['结果追踪', '模型版本、封面、引用和本次运行结果有证据;明确运行日志不是教学成绩或审计。', 'publish.* / feedback.* / project.conflict'],
+]
+const decisions = [
+ { id: 'D1', title: '真正的组件运行系统', current: '“组件”页目前是内置能力说明;新增项只保存扩展标记名称,没有启停、参数执行或生命周期。', behavior: '用户新增一种行为组件,配置参数后能在当前模型和约定下游环境执行;停用后立即停止,重开后按保存状态恢复。', scope: '先定义有限组件目录、参数 schema、权限与版本;实现初始化/更新/销毁及错误隔离。不要直接开放任意脚本。', acceptance: '每种批准组件可添加、编辑、启停、删除、保存重开;重复载入不重复绑定事件;失败组件不阻断其他对象,运行结果可定位。' },
+ { id: 'D2', title: '完整三维离线运行包', current: '现有 HTML 是嵌入静态截图与工程数据的步骤演示,可离线查看;没有 GLB 字节和完整三维运行时。', behavior: '断网电脑打开交付包,真实模型/材质/原生动画/编辑时间轴与交互节点可以运行,且不依赖服务端 blob URL。', scope: '明确交付形式(目录/ZIP或单文件)、体积约束、资产授权和三维依赖;打包全部受控资产、运行时和相对资源清单。', acceptance: '清缓存断网运行;模型和环境完整可见,动作/分支/高亮可交互;资源缺失明确报错;包内清单/哈希与发布版本一致。' },
+ { id: 'D3', title: '下游场景 / 训练自动执行模型动画与蓝图', current: '发布模型可供下游按固定版本引用;不同下游已有局部动画能力,但没有统一的模型 timeline/blueprint 自动执行桥接。加载成功不等于动作已自动执行。', behavior: '在场景或训练放入模型实例后,按明确触发策略启动该发布版本的时间轴/蓝图;多个实例互不干扰。', scope: '统一实例目标命名空间、事件总线、生命周期和停止规则;明确自动播放、用户触发与训练步骤触发优先级,以及错误/完成结果回传。', acceptance: '两个同模型实例分别触发不串台;锁定发布版本;停止/切步骤/卸载无迟到动作;真实对象姿态、分支和结果回传均有 E2E 证据。' },
+]
+const table = (heads, rows) => ``
+const sourceLink = source => link(source.file, `${path.basename(source.file)}${source.line ? `:${source.line}` : ''} · ${source.symbol}`)
+const evidenceLinks = ['results.json', 'run-history.json', 'validation.json', 'cleanup.json', 'artifact-checks.json', 'coverage.json', 'feature-catalog.json', 'coverage-plan.md', 'data/inventory-main.md', 'data/unit-coverage-plan.json', 'data/final-corrections.json', 'data/kingbase-validation-probe-result.json', 'data/visual-qa.json', 'screenshot-captions.json', 'screenshot-verification.json', ...proofFiles]
+const fileHashes = new Map()
+const fileHash = file => {
+ if (!file || !fs.existsSync(file)) return null
+ if (!fileHashes.has(file)) fileHashes.set(file, crypto.createHash('sha256').update(fs.readFileSync(file)).digest('hex'))
+ return fileHashes.get(file)
+}
+function attachmentFile(attachment) {
+ const normalized = String(attachment.path || '').replaceAll('\\', '/')
+ const marker = normalized.lastIndexOf('/test-results/')
+ // Remap Playwright absolute paths to this checked-out report directory.
+ const candidate = path.resolve(reportDir, marker >= 0 ? normalized.slice(marker + 1) : normalized)
+ const relative = path.relative(reportDir, candidate)
+ return relative && !relative.startsWith('..') && !path.isAbsolute(relative) ? candidate : null
+}
+const screenshotEvidence = []
+const gallery = screenshots.map((filename, index) => {
+ const base = filename.replace(/\.[^.]+$/, ''), caption = captions[filename] || captions[base]
+ const title = typeof caption === 'string' ? caption : caption?.title || base.replace(/^\d+[a-z]?[-_]?/, '').replaceAll('-', ' ')
+ const detail = caption?.description || caption?.detail || '过程画面;只有关联断言通过时才作为相应功能的验证证据。'
+ const associated = browserRows.flatMap(row => row.attachments.filter(attachment => attachment.name === base || attachment.name === filename).map(attachment => ({
+ title: row.title, status: row.status, hashMatches: fileHash(attachmentFile(attachment)) === fileHash(path.join(screenshotDir, filename)),
+ })))
+ const verification = screenshotProofStatus(associated)
+ screenshotEvidence.push({ file: `screenshots/${filename}`, title, status: verification, associations: associated })
+ const note = verification === 'PASS' ? '本轮附件一致' : verification === 'FAIL' ? '本轮用例失败' : verification === 'WARN' ? '用例需复核' : associated.length ? '图片与本轮附件待核对' : '过程截图'
+ return `${String(index + 1).padStart(2, '0')} ${esc(title)} ${badge(verification, note)}${esc(detail)}
${esc(filename)} `
+}).join('')
+fs.writeFileSync(path.join(reportDir, 'screenshot-verification.json'), JSON.stringify({ time: new Date().toISOString(), scope: '截图与当前 results.json 的附件逐字节 SHA256 一致性;同名预跑或重拍不自动计本轮通过。', screenshots: screenshotEvidence }, null, 2))
+const statusCountCards = `功能清单 ${catalog.length} 全部可达动作逐项整理
逐项证据 ${featureCount.pass} / ${featureCount.total} ${featureCount.pending} 项仍待验证
浏览器 E2E ${browserCount.pass} / ${browserCount.total} ${browserCount.fail} 失败 · ${finalRun ? '最终轮已标记' : '最终轮未确认'}
截图 / 待确认 ${screenshots.length} / 3 截图按实际文件读取
`
+const document = `装备模型编辑器 · 全功能闭环验收 MODEL AUTHORING · CLOSURE REVIEW · 2026.09.06
装备模型编辑器 全功能闭环审查与验收 沿资料 → 产品原型 → 设计 → 开发 → 测试核查每一个可达动作。用狐狸、牛奶卡车等轻量模型验证通用编排能力,保留现有业务流程与版本数据。
+验收概况 01 资料 02 原型 03 设计 04 开发 05 测试 待确认范围 截图证据
+${esc(overallTitle)} ${badge(overall)} · 报告生成:${esc(new Date().toLocaleString('zh-CN', { timeZone: 'Asia/Shanghai', hour12: false }))}(北京时间)。源码已接通、合同测试和真实浏览器验证分别记录;没有断言关联的功能仍为待验证。
${missing.length ? `
待提供:${missing.map(esc).join('、')}。
` : ''}${malformed.length ? `
证据读取失败:${esc(JSON.stringify(malformed))}
` : ''}
${statusCountCards}“已实现/本轮修复”是源码状态;绿色“已验证”要求独立证据。过程截图与测试总数不能自动把 ${catalog.length} 个功能全部变成通过。
+01 / 资料
从参考资料提取通用能力 原始两份 PDF 共 11 页已由主代理完整渲染并视觉阅读。原始诉求集中在部件选择、操作步骤、交互反馈和结果追踪。本轮以狐狸、牛奶卡车等轻量模型验证部件选择、流程编排、交互反馈和结果保存。
${link('../../../../文档/维修训练内容设计.pdf', '维修训练内容设计.pdf')} · ${link('../../../../文档/电器控制系统故障维修脚本.pdf', '电器控制系统故障维修脚本.pdf')}。报告仅链接原文,不复制全文或整页图片。
${table(['资料能力', '本轮可观察结果', '矩阵追踪'], requirements.map(row => row.map(esc)))}
+02 / 产品原型
确认用户实际使用的执行入口 内容模型列表 选中正式项目,进入模型编辑路由。
Vue 宿主 传 projectId / api=1,注入权限与未保存确认。
同源 iframe 承载 legacy-model-editor/editor.html。
真实原型源码 main.js + timeline.js + blueprint.js,经 stage 构建。
内容 API 受控资产、项目版本、保存和发布。
当前默认入口由 ${link('../../../unreal_tran_web/src/config/content.ts', 'content.ts')} 和 ${link(sourceFiles.host, 'LegacyModelEditorView.vue')} 确定,${link('../../../unreal_tran_web/scripts/stage-legacy-model-editor.mjs', 'stage-legacy-model-editor.mjs')} 将真实原型源码打入静态资源。备用 DigitalTwinEditor.js 不是本轮默认入口。
覆盖顶部工具、场景树/资源库、三维视口、对象属性/组件说明、时间轴、曲线、蓝图、发布面板、运行日志和快捷键。代码中保留但没有可达按钮的“训练场景 JSON 导入”不计为用户可达功能。
+03 / 设计
明确保存、运行与验证的边界 工程数据闭环 对象变换/材质/自定义字段、时间轴/蓝图和环境稳定引用必须能保存、重开、撤销及 JSON 往返。临时 blob 只用于渲染,服务端资产标识用于持久化。
导入、截图、保存与发布期间保留互斥和乐观锁;失败不得用成功消息掩盖,也不得发布半绑定资源。
界面与业务范围 网格、线框、PBR、透视和展开布局属于当前视图选项,未跨刷新保存不作为缺陷。运行日志属于本次运行记录,不等同审计和教学成绩。
现有组件新增是名称标记;现有离线 HTML 是步骤演示;下游自动执行模型动作须另行确认。
+04 / 开发
全功能覆盖矩阵 每行列出实际执行入口、实现状态、边界、对应修复及独立验证要求。${link('feature-catalog.json', '静态功能清单')} · ${link('coverage.json', '带证据的矩阵 JSON')} · ${link('data/inventory-main.md', '修复前基线与实施记录')}。
全部验证状态 待验证 已验证 失败 需复核 ${catalog.length} 项
+${finalCorrections ? `${arr(finalCorrections.items).map(item => `
${esc(item.title)} ${esc(item.problem)}
${esc(item.change)}
${esc(item.validation)}
${link(item.source, '源码')}${item.secondSource ? ` · ${link(item.secondSource, '另一模块源码')}` : ''}${item.testSource ? ` · ${link(item.testSource, '聚焦测试')}` : ''}${item.evidence ? ` · ${link(item.evidence, '验证证据')}` : ''} `).join('')}
` : ''}
+${groups.map(([group]) => `${esc(group)} `).join('')}
+05 / 测试
执行结果与证据分层 浏览器 E2E ${results ? `${browserCount.pass}/${browserCount.total} 项通过 · ${badge(browserCount.fail ? 'FAIL' : browserCount.pending || !browserCount.total ? 'PENDING' : 'PASS')} · ${link('results.json', 'Playwright 原始结果')}
${table(['用例', '结果', '耗时 / 失败原因'], browserRows.map(row => [esc(row.title), badge(row.status), `${row.duration == null ? '未记录' : `${(row.duration / 1000).toFixed(1)} 秒`}${row.errors.length ? `失败信息 ${pretty(row.errors)} ` : ''} `]))}` : '尚未提供 results.json,不计任何 E2E 通过。
'}通过浏览器定向注入的 409/503 等异常仅验证失败处理,不代表开发服务真实发生该故障。UI 点击、脚本调用运行时和纯 API 检查按各用例证据说明区分。
+${runHistory ? `保留首轮失败与定向重跑事实 ${esc(runHistory.scope)} ${link('run-history.json', '安全迭代记录')}
${table(['历史轮次', '原始结果', '解释范围'], arr(runHistory.runs).map(run => [esc(run.name), `${Number(run.passed)}/${Number(run.tests)} 通过 · ${badge(state(run.outcome), state(run.outcome) === 'PASS' ? '该轮通过' : '该轮失败')}`, esc(run.note || '保留原始失败,不以随后同名证据覆盖。')]))}${esc(runHistory.investigation)}
${esc(runHistory.finalAcceptance)}
` : ''}
+真实 API / 运行时断言 逐份读取 evidence/*.json。文件存在本身不代表通过;每份证据须注明断言结果和覆盖的 featureIds。
${proofData.length ? proofData.map(({ file, data }) => `${link(file)} ${badge(anyFailure(data) ? 'FAIL' : state(data?.status ?? data?.data?.status ?? data?.result))} ${pretty(data)} `).join('') : '尚无接口或运行时证据文件。
'}
+构建、聚焦单测与版本检查 ${validation ? `${link('validation.json', '完整验证记录')}。单测/合同测试不并入真实 E2E 数量。
${pretty(validation)}` : 'validation.json 待提供。已由子任务运行的聚焦测试需最终记录确认;报告不硬编码历史通过数。
'}
+测试数据保留与清理 ${cleanup ? `${link('cleanup.json', '清理结果')}
${pretty(cleanup)}` : '清理或保留结果待记录;不声明测试数据已全部清理。原始业务工程与其发布版本应以最终证据核对。
'}
+交付物凭据与附件检查 ${artifactChecks ? `${badge(finalArtifactsVerified ? 'PASS' : anyFailure(artifactChecks) ? 'FAIL' : 'PENDING', finalArtifactsVerified ? '最终扫描通过' : artifactChecks.phase === 'preliminary' ? '初查完成,待最终扫描' : '待核验')} · ${link('artifact-checks.json', '脱敏扫描结果')}
${esc(artifactChecks.scope)}
扫描 ${Number(artifactChecks.textArtifactsScanned || 0)} 份文本、${Number(artifactChecks.pngMetadataFilesScanned || 0)} 张图片的文本元数据及 ${arr(artifactChecks.archives).length} 个归档;图片正文不以文本扫描代替视觉 QA。
检查范围、历史常量排除与限制 ${pretty(artifactChecks)} ` : '交付物扫描结果待提供;不声明报告已完成最终凭据检查。
'}
+需要产品确认 / 3 项
较大调整的具体范围 以下能力超出现有小范围修复。本轮保留入口并明确现状;确认范围后才能据此另行实现和验收。
${decisions.map(item => `${item.id} ${esc(item.title)} 当前行为 ${esc(item.current)} 拟实现的可观察行为 ${esc(item.behavior)} 建议确认的实施范围 ${esc(item.scope)} 验收条件 ${esc(item.acceptance)} `).join('')}
+可视证据
${screenshots.length} 张截图 · 点击放大 截图来自实际目录;首次加载、过程画面和最终结果按图注说明,不把空场景或加载遮罩当作模型可见证据。
${gallery || '
尚未生成截图。不会补画或伪造测试截图。
'}
+交付文件
证据与复现入口 ${evidenceLinks.map(file => link(file)).join('')}
生成命令:node tools/build-model-editor-closure-report.mjs。功能实现状态与验证状态独立;缺失、失败或未确认的最终轮结果绝不自动改为 PASS。报告不读取私有账号文件、会话配置或数据库备份。
+ `
+fs.writeFileSync(path.join(reportDir, 'index.html'), document, 'utf8')
+console.log(JSON.stringify({ report: path.join(reportDir, 'index.html'), overall, features: featureCount, e2e: browserCount, finalRun, screenshots: screenshots.length, evidenceFiles: proofFiles.length, missing, malformed }, null, 2))
diff --git a/tools/build-scene-editor-closure-report.mjs b/tools/build-scene-editor-closure-report.mjs
new file mode 100644
index 0000000..3c9bce5
--- /dev/null
+++ b/tools/build-scene-editor-closure-report.mjs
@@ -0,0 +1,213 @@
+import fs from 'node:fs'
+import path from 'node:path'
+import crypto from 'node:crypto'
+import { fileURLToPath } from 'node:url'
+import { sceneGroups, sceneSources, unmountedWorkspace } from './scene-editor-feature-catalog.mjs'
+import { resolveBrowserProof, screenshotProofStatus } from './model-editor-report-evidence.mjs'
+import { matchesSceneScreenshotAttachment, isSceneUnitProof } from './scene-editor-report-evidence.mjs'
+
+// Report generation only: no login, credential files, application requests or tests.
+const here = path.dirname(fileURLToPath(import.meta.url))
+const report = path.resolve(here, '../reports/scene-editor-closure-20260906')
+fs.mkdirSync(report, { recursive: true })
+const arr = value => Array.isArray(value) ? value : []
+const esc = value => String(value ?? '').replace(/[&<>"']/g, c => ({ '&': '&', '<': '<', '>': '>', '"': '"', "'": ''' })[c])
+const encode = value => value.replaceAll('\\', '/').split('/').map(encodeURIComponent).join('/')
+const missing = [], malformed = []
+function read(file, required = false) {
+ const target = path.resolve(report, file)
+ if (!fs.existsSync(target)) { if (required) missing.push(file); return null }
+ try { return JSON.parse(fs.readFileSync(target, 'utf8').replace(/^\uFEFF/, '')) }
+ catch { malformed.push(file); return null }
+}
+function state(value) {
+ if (value === true) return 'PASS'
+ if (value === false) return 'FAIL'
+ const normalized = String(value ?? '').toUpperCase()
+ if (['PASS', 'PASSED', 'SUCCESS'].includes(normalized)) return 'PASS'
+ if (['FAIL', 'FAILED', 'TIMEDOUT', 'INTERRUPTED', 'ERROR', 'UNEXPECTED'].includes(normalized)) return 'FAIL'
+ if (['WARN', 'FLAKY'].includes(normalized)) return 'WARN'
+ return 'PENDING'
+}
+const labels = { PASS: '已验证', FAIL: '失败', WARN: '需复核', PENDING: '待验证' }
+const badge = (status, label) => `${esc(label || labels[status])} `
+const link = (file, label = file) => fs.existsSync(path.resolve(report, file))
+ ? `${esc(label)} ` : `${esc(label)}(待提供) `
+const sourceCache = new Map()
+function locate(locator) {
+ const colon = locator.indexOf(':'), key = locator.slice(0, colon), symbol = locator.slice(colon + 1)
+ const file = sceneSources[key]
+ if (!file) return { file: '', symbol, line: null }
+ if (!sourceCache.has(file)) {
+ const target = path.resolve(report, file)
+ sourceCache.set(file, fs.existsSync(target) ? fs.readFileSync(target, 'utf8').split(/\r?\n/) : [])
+ }
+ const index = sourceCache.get(file).findIndex(line => line.includes(symbol))
+ return { file, symbol, line: index < 0 ? null : index + 1 }
+}
+const fixes = read('fixes.json') || { items: [] }
+const catalog = sceneGroups.flatMap(([group, rows]) => rows.map(([id, name, locator, boundary, acceptance]) => {
+ const related = arr(fixes.items).filter(item => arr(item.featureIds).includes(id))
+ return { id, group, name, source: locate(locator), implementation: related.length ? '本轮修复记录已提供,仍需验证' : '源码路径已审查,待本轮实测', boundary, acceptance,
+ fix: related.length ? related.map(item => item.summary || item.title).join(';') : '尚未关联本轮修复记录', fixIds: related.map(item => item.id) }
+}))
+const allIds = new Set(catalog.map(row => row.id))
+if (allIds.size !== catalog.length) throw new Error('Duplicate scene feature IDs')
+const results = read('results.json', true), validation = read('validation.json', true), cleanup = read('cleanup.json'), artifacts = read('artifact-checks.json')
+const baseline = read('data/baseline-findings.json') || { items: [] }, decisions = read('decisions.json') || { items: [] }
+const captions = read('screenshot-captions.json') || {}
+function browserRows(suites, parents = []) {
+ return arr(suites).flatMap(suite => [
+ ...arr(suite.specs).flatMap(spec => arr(spec.tests).map(test => {
+ const last = arr(test.results).at(-1)
+ return { title: [...parents, spec.title].filter(Boolean).join(' · '), rawTitle: spec.title,
+ status: test.status === 'unexpected' ? 'FAIL' : test.status === 'flaky' ? 'WARN' : state(last?.status),
+ startTime: last?.startTime, duration: last?.duration,
+ attachments: arr(last?.attachments).map(item => ({ name: item.name, path: item.path, contentType: item.contentType })),
+ // Never paste request/response bodies or full errors into the HTML.
+ errorCount: arr(last?.errors).length,
+ }
+ })), ...browserRows(suite.suites, suite.file ? parents : [...parents, suite.title].filter(Boolean)),
+ ])
+}
+const tests = browserRows(results?.suites)
+const files = fs.existsSync(path.join(report, 'evidence')) ? fs.readdirSync(path.join(report, 'evidence')).filter(file => file.endsWith('.json')).sort().map(file => `evidence/${file}`) : []
+const documents = files.map(file => ({ file, data: read(file) }))
+const rawProofs = []
+function collect(value, file, parent = {}) {
+ if (!value || typeof value !== 'object') return
+ const ownState = state(value.status ?? value.overallStatus ?? (typeof value.result === 'string' ? value.result : undefined))
+ const metadata = {
+ status: ownState === 'PENDING' ? parent.status || 'PENDING' : ownState,
+ testTitle: value.testTitle || parent.testTitle,
+ recordedAt: value.recordedAt || value.time || parent.recordedAt,
+ kind: value.kind || parent.kind || '',
+ runId: value.runId || parent.runId,
+ }
+ const ids = [...arr(value.featureIds), ...arr(value.features), ...(value.featureId ? [value.featureId] : [])].filter(id => typeof id === 'string')
+ if (ids.length) rawProofs.push({ ...metadata, ids, file, title: value.title || value.name || metadata.testTitle || file, details: value.scope || value.note || '', screenshots: arr(value.screenshots) })
+ for (const [key, child] of Object.entries(value)) if (child && typeof child === 'object' && !['featureIds', 'features', 'screenshots', 'attachments'].includes(key)) {
+ if (Array.isArray(child)) child.forEach(item => collect(item, file, metadata))
+ else collect(child, file, metadata)
+ }
+}
+documents.forEach(item => collect(item.data, item.file))
+collect(validation?.coverage, 'validation.json')
+const unknownIds = [...new Set(rawProofs.flatMap(proof => proof.ids).filter(id => !allIds.has(id)))]
+const proofs = rawProofs.map(proof => {
+ if (proof.testTitle && !isSceneUnitProof(proof)) return resolveBrowserProof(proof, tests)
+ // Independent API/unit proof is admissible only in the confirmed current run,
+ // or under validation.coverage. A stale unlinked PASS file is not sufficient.
+ const validKind = ['api', 'unit', 'contract', 'integration', 'manual-visual'].includes(String(proof.kind).toLowerCase())
+ const associated = proof.file === 'validation.json' || (validation?.runId && proof.runId === validation.runId)
+ if (!validKind || !associated) return { ...proof, status: proof.status === 'FAIL' ? 'FAIL' : 'PENDING', verificationNote: '缺少当前轮 testTitle 或 runId/验证类型关联;旧证据不计通过。' }
+ return proof
+})
+const coverage = catalog.map(feature => {
+ const evidence = proofs.filter(proof => proof.ids.includes(feature.id)), values = evidence.map(proof => proof.status)
+ const verification = values.includes('FAIL') ? 'FAIL' : values.includes('WARN') ? 'WARN' : values.includes('PASS') ? 'PASS' : 'PENDING'
+ const implementation = verification === 'PASS' ? feature.fixIds.length ? '本轮修复已完成验证' : '已有实现已完成验证'
+ : verification === 'FAIL' ? '当前验证失败,尚未完成验收'
+ : verification === 'WARN' ? '当前证据需复核,尚未完成验收' : feature.implementation
+ return { ...feature, implementation, verification, evidence }
+})
+fs.writeFileSync(path.join(report, 'feature-catalog.json'), JSON.stringify(coverage.map(({ evidence, ...feature }) => feature), null, 2))
+const count = rows => ({ total: rows.length, pass: rows.filter(row => row.status === 'PASS').length, fail: rows.filter(row => row.status === 'FAIL').length, pending: rows.filter(row => !['PASS', 'FAIL'].includes(row.status)).length })
+const featureCount = count(coverage.map(row => ({ status: row.verification }))), e2eCount = count(tests)
+const hasBrowserProof = row => row.evidence.some(proof => proof.status === 'PASS' && proof.testTitle && !isSceneUnitProof(proof))
+const featureKinds = {
+ browser: coverage.filter(row => row.verification === 'PASS' && hasBrowserProof(row)).length,
+ focused: coverage.filter(row => row.verification === 'PASS' && !hasBrowserProof(row) && row.evidence.some(proof => proof.status === 'PASS' && isSceneUnitProof(proof))).length,
+}
+const namedValidationChecks = [
+ ['frontend', 'Web 构建与既有模型产物校验', '构建与类型检查'],
+ ['backend', 'Tran 后端完整构建与测试', '后端构建与测试'],
+ ['runtime', '场景运行时、资源、关闭守卫与封面合同', 'Node 合同与单元'],
+ ['client', '场景内容 API 客户端合同', 'Node 合同与单元'],
+ ['reportTools', '报告证据与附件匹配', '报告工具'],
+].filter(([key]) => validation?.[key]).map(([key, name, kind]) => ({ name, kind, ...validation[key] }))
+const validationChecks = (arr(validation?.checks).length ? validation.checks : namedValidationChecks).map(check => {
+ const descriptor = `${check.name || ''} ${check.command || ''}`
+ const kind = check.kind || (/node\s+--test/.test(descriptor) ? /报告/.test(descriptor) ? '报告工具' : 'Node 合同与单元'
+ : /\bmvn\b|后端|Tran|Auth/.test(descriptor) ? '后端构建与测试'
+ : Number.isFinite(check.tests) ? '专项验证' : '构建与类型检查')
+ return { ...check, kind, status: state(check.status) }
+})
+const validationTotals = [...new Set(validationChecks.map(check => check.kind))].map(kind => {
+ const checks = validationChecks.filter(check => check.kind === kind)
+ return { kind, checks: checks.length, passedChecks: checks.filter(check => check.status === 'PASS').length,
+ tests: checks.reduce((sum, check) => sum + (Number.isFinite(check.tests) ? check.tests : 0), 0),
+ passedTests: checks.reduce((sum, check) => sum + (check.status === 'PASS' && Number.isFinite(check.tests) ? check.tests : 0), 0) }
+})
+const cleanupProjects = arr(cleanup?.projects), cleanupOriginals = arr(cleanup?.originals)
+const wasRemoved = item => item.removed === true || item.alreadyAbsent === true || item.absent === true || item.status === 404 || item.statusCode === 404
+const cleanupCount = {
+ projects: cleanupProjects.length,
+ projectsRemoved: cleanupProjects.filter(wasRemoved).length,
+ originals: cleanupOriginals.length,
+ originalsUnchanged: cleanupOriginals.filter(item => item.unchanged === true).length,
+ users: arr(cleanup?.users).length,
+ usersRemoved: arr(cleanup?.users).filter(wasRemoved).length,
+ roles: arr(cleanup?.roles).length,
+ rolesRemoved: arr(cleanup?.roles).filter(wasRemoved).length,
+}
+const digest = file => fs.existsSync(file) ? crypto.createHash('sha256').update(fs.readFileSync(file)).digest('hex') : null
+function attachmentPath(item) {
+ const normalized = String(item.path || '').replaceAll('\\', '/')
+ if (!normalized) return null
+ const marker = '/test-results/'
+ const start = normalized.lastIndexOf(marker)
+ if (start >= 0) return path.join(report, 'test-results', normalized.slice(start + marker.length))
+ return path.isAbsolute(item.path) ? item.path : path.resolve(report, item.path)
+}
+const imageDir = path.join(report, 'screenshots')
+const images = fs.existsSync(imageDir) ? fs.readdirSync(imageDir).filter(file => /\.(png|jpg|jpeg|webp)$/i.test(file)).sort() : []
+const imageProof = images.map(file => {
+ const hash = digest(path.join(imageDir, file))
+ const matches = tests.flatMap(test => test.attachments.filter(item => matchesSceneScreenshotAttachment(file, item))
+ .map(item => ({ testTitle: test.title, status: test.status, hashMatches: digest(attachmentPath(item) || '') === hash })))
+ return { file, status: screenshotProofStatus(matches), captionPresent: Boolean(captions[file] || captions[file.replace(/\.[^.]+$/, '')]), associations: matches }
+})
+fs.writeFileSync(path.join(report, 'screenshot-verification.json'), JSON.stringify(imageProof, null, 2))
+const anyFailure = value => value && typeof value === 'object' && (state(value.status ?? value.overallStatus) === 'FAIL' || (Number.isInteger(value.exitCode) && value.exitCode !== 0) || Object.values(value).some(child => child && typeof child === 'object' && (Array.isArray(child) ? child.some(anyFailure) : anyFailure(child))))
+const finalRun = validation?.finalRun === true || validation?.e2eComplete === true || validation?.e2e?.complete === true
+const requiredFinalFiles = ['results.json', 'validation.json', 'cleanup.json', 'playwright/index.html']
+const finalArtifactsVerified = artifacts?.finalArtifactScan === true && state(artifacts.status) === 'PASS'
+ && requiredFinalFiles.every(file => artifacts.finalInputSha256?.[file] && artifacts.finalInputSha256[file] === digest(path.join(report, file)))
+const failed = e2eCount.fail || featureCount.fail || malformed.length || unknownIds.length || arr(results?.errors).length || Number(results?.stats?.unexpected || 0)
+ || anyFailure(validation) || anyFailure(cleanup) || anyFailure(artifacts) || documents.some(item => anyFailure(item.data)) || imageProof.some(item => item.status === 'FAIL')
+const finished = finalRun && state(validation?.overallStatus ?? validation?.status) === 'PASS' && e2eCount.total > 0 && !e2eCount.pending
+ && !featureCount.pending && finalArtifactsVerified && images.length > 0 && imageProof.every(item => item.status === 'PASS' && item.captionPresent)
+const overall = failed ? 'FAIL' : finished ? 'PASS' : 'PENDING'
+const snapshot = { generatedAt: new Date().toISOString(), overall, finalRun, finalArtifactsVerified, featureCount, featureKinds, e2eCount, validationTotals, cleanupCount, unknownIds, missing, malformed, features: coverage }
+fs.writeFileSync(path.join(report, 'coverage.json'), JSON.stringify(snapshot, null, 2))
+const sourceLink = source => link(source.file, `${path.basename(source.file)}${source.line ? `:${source.line}` : ''} · ${source.symbol}`)
+const table = (heads, rows) => ``
+const validationSummary = validationTotals.length ? validationTotals.map(item => `${item.kind}:${item.tests ? `${item.passedTests} / ${item.tests} 项测试` : `${item.passedChecks} / ${item.checks} 项检查`}`).join(';') : '构建与单测统计待最终 validation.json 提供'
+const cleanupSummary = cleanup ? `数据清理${labels[state(cleanup.status)]};临时项目 ${cleanupCount.projectsRemoved} / ${cleanupCount.projects}、账号 ${cleanupCount.usersRemoved} / ${cleanupCount.users}、角色 ${cleanupCount.rolesRemoved} / ${cleanupCount.roles} 已清理;原件创作内容、版本、资产与依赖 ${cleanupCount.originalsUnchanged} / ${cleanupCount.originals} 保持不变` : '数据清理记录待提供'
+const cleanupOriginalTable = cleanupOriginals.length ? table(['原始数据 / 版本', '创作数据比对范围', '引用数量:测试前 → 清理后', '更新时间:测试前 → 清理后', '完整记录'], cleanupOriginals.map(item => [
+ `${esc(item.name || item.id)} · 版本 ${esc(item.version ?? '未记录')} `,
+ `${badge(item.unchanged === true ? 'PASS' : item.unchanged === false ? 'FAIL' : 'PENDING', item.unchanged === true ? '创作数据保持不变' : '待核验')}${esc(item.scope || '以清理证据声明的字段范围为准')}
`,
+ `${esc(item.before?.usageCount ?? '未记录')} → ${esc(item.after?.usageCount ?? '未记录')}${item.referenceCountRestored === true ? `${badge('PASS', '引用数量已恢复')}
` : ''}`,
+ `${esc(item.before?.updateTime ?? '未记录')} → ${esc(item.after?.updateTime ?? '未记录')}`,
+ item.fullRecordUnchanged === true ? '完整记录相同' : item.fullRecordUnchanged === false ? '存在派生字段差异;详见前后统计' : '未声明整条记录完全一致',
+])) : ''
+const historicalRuns = [['首轮', validation?.priorFullRun || validation?.firstRun], ['第二轮', validation?.secondFullRun]].filter(([, run]) => run)
+const firstRunNotice = historicalRuns.map(([label, run]) => `${esc(label)}记录:${esc(run.passed ?? run.pass ?? run.passedTests ?? '未记录')} 项通过,${esc(run.fail ?? run.failed ?? run.testLocatorFailure ?? run.testLocatorFailures ?? '未记录')} 项失败。${esc(run.reason || '')}${run.additionalScreenshotWait ? '后续补充截图等待,确保列表加载遮罩消失后取证。' : ''}此轮记录保留在验证文件中,不计入下列最终整轮结果。
`).join('')
+const notice = overall === 'FAIL' ? '存在失败,当前验收未通过' : overall === 'PASS' ? '当前列明功能已完成记录验证' : '全功能清单已建立,最终验收待完成'
+const links = ['feature-catalog.json', 'coverage.json', 'coverage-plan.md', 'data/coverage-test-plan.json', 'data/inventory-scene.md', 'data/baseline-findings.json', 'decisions.json', 'fixes.json', 'results.json', 'validation.json', 'cleanup.json', 'artifact-checks.json', 'screenshot-verification.json', 'screenshot-captions.json', 'covers/fox.jpg', 'playwright/index.html', ...files]
+const html = `场景编辑器 · 全功能闭环审查 验收概况 01 资料 02 原型 03 设计 04 开发 05 测试 待确认范围 截图
+${esc(notice)} ${badge(overall)} · ${esc(new Date().toLocaleString('zh-CN', { timeZone: 'Asia/Shanghai', hour12: false }))}(北京时间)
${missing.length ? `
待提供:${missing.map(esc).join('、')}。
` : ''}${malformed.length || unknownIds.length ? `
证据结构问题:${esc([...malformed, ...unknownIds].join('、'))}。
` : ''}
默认入口功能 ${catalog.length} 未挂载 Vue 工作区不纳入分母
逐项证据匹配 ${featureCount.pass} / ${featureCount.total} 失败 ${featureCount.fail} · 待验证 ${featureCount.pending}
当前真实 E2E ${e2eCount.pass} / ${e2eCount.total} 构建、API 和单测不计入 E2E
原始截图 ${images.length} 需图注与本轮附件哈希一致
${featureKinds.browser} 项功能由本轮浏览器证据验证,${featureKinds.focused} 项由聚焦单元 / 合同证据验证;未验证 ${featureCount.pending} 项。
${esc(validationSummary)}。
${esc(cleanupSummary)}。
+01 / 资料
参考资料中的通用交互要求 沿用前一轮已核对的两份 PDF(共 11 页)。资料中的部件选择、操作步骤、交互反馈和结果追踪,在本轮映射为场景对象摆放、部件能力、稳定资源引用、保存发布及后续训练选用。
${link('../../../../文档/维修训练内容设计.pdf', '维修训练内容设计.pdf')} · ${link('../../../../文档/电器控制系统故障维修脚本.pdf', '电器控制系统故障维修脚本.pdf')} · ${link('../scene-live-20260905/index.html', '前轮场景实测报告(历史参考)')}
前轮曾把对象树/接口成功误认为狐狸可见,已撤回旧完整通过结论并补视觉验证。本轮要求截图中实际辨认模型,不能仅凭树节点、依赖字段或成功提示判断加载通过。
+02 / 产品原型
以当前路由挂载的场景编辑器为准 场景列表 ContentProjectsView 的 SCENE 入口。
真实路由 /content/scenes/:id/edit。
Vue 宿主 LegacyScenePageView 管理权限、项目、恢复和 FIFO。
原型运行时 demo-src/scene-editor.js 真实 DOM + Three.js。
内容 API 项目版本、封面、受控资产与发布依赖。
${sourceLink(locate('router:场景制作:列表走真实内容制作 API'))} · ${sourceLink(locate('host:async function mountLegacy'))} · ${sourceLink(locate('storage:export function legacySceneRuntimeContext'))}
SceneEditorView → SceneEditorWorkspace 的正式实现仍在源码中,但当前场景路由未挂载。它的额外控件列在“待确认范围”中,不能作为本轮默认入口的已实现/已验证功能。
+03 / 设计
先约定保存、权限与实例边界 数据闭环 edit3dv4.scene 文档保存对象变换、语义、模型部件覆盖和 rendering;模型以 SCENE_MODEL 固定项目/版本依赖,场景自导入 GLB 使用本项目资产编码。blob 是临时渲染地址。
手动保存同时生成当前视口 JPEG 封面;截图失败保留旧封面并提示,409 不覆盖。800ms 本地恢复缓冲不等于自动写服务器。
权限与生命周期 content.scene 控制页面,create/update/delete/publish 控制实际动作。发布只有权限者可以发布已存版本;编辑后发布必须先完成受 update 保护的保存。
切标签保留编辑状态并暂停渲染;真正离开/关闭/切角色确认未存修改。资源上传、保存和发布均须完整结束或明确阻止离开。
基线风险记录 以下是本轮修复前源码发现,不能代替实际失败复现。已修复时须关联 fixes.json 与新测试,历史风险描述保留追溯。
${table(['风险', '基线观察', '受影响功能'], arr(baseline.items).map(item => [esc(item.title), esc(item.observation), arr(item.featureIds).map(esc).join('、')]))}
+04 / 开发
默认入口全功能覆盖矩阵 ${link('feature-catalog.json', '稳定功能 ID')} · ${link('coverage.json', '逐项证据')} · ${link('coverage-plan.md', '测试分层约定')}。每项分别说明实现状态、闭环边界与实际证据;各模板与资源单独列明。
全部状态 待验证 已验证 失败 需复核 ${catalog.length} 项
${sceneGroups.map(([group]) => `${esc(group)} `).join('')}
+05 / 测试
真实浏览器、API、单测分别记录 最终整轮 E2E ${firstRunNotice}${results ? table(['用例', '结果', '耗时 / 证据'], tests.map(test => [esc(test.title), badge(test.status), `${test.duration == null ? '未记录' : `${(test.duration / 1000).toFixed(1)} 秒`} · ${test.attachments.length} 个附件${test.errorCount ? ` · ${test.errorCount} 条失败信息,详见安全处理后的原始报告` : ''}`])) : '尚无 results.json,未执行项目不计通过。
'}${link('results.json', 'Playwright JSON')} · ${link('playwright/index.html', 'Playwright HTML')}。同名旧用例或定向重跑不会覆盖当前完整轮失败。
构建、后端与聚焦单元测试 ${validationChecks.length ? table(['分类 / 检查', '结果', '实际数量', '执行命令 / 说明'], validationChecks.map(check => [esc(`${check.kind} · ${check.name || '未命名检查'}`), badge(check.status), Number.isFinite(check.tests) ? `${esc(check.tests)} 项测试${Number.isFinite(check.failures) ? ` · ${esc(check.failures)} 失败` : ''}` : '独立检查,不计入测试总数', `${esc(check.command || '未记录命令')}${check.warning ? ` · ${esc(check.warning)} ` : ''}${check.evidence ? `${link(check.evidence, '原始证据')}
` : ''}`])) : '最终构建和测试统计待提供。
'}${esc(validationSummary)}。上述数量分别来自 validation.json,不计入 E2E 数。
数据保留与清理 ${esc(cleanupSummary)}。
${cleanup?.note ? `${esc(cleanup.note)}
` : ''}${cleanupOriginalTable}${link('cleanup.json', '完整清理与保留证据')} · ${link('artifact-checks.json', '最终交付物扫描')}。
逐项证据索引 ${files.length ? table(['证据文件', '关联类型 / 结果'], documents.map(item => [link(item.file), esc([...new Set(proofs.filter(proof => proof.file === item.file).map(proof => `${proof.kind}: ${proof.status}`))].join(';') || '未声明 featureIds,未计入矩阵')])) : '本轮逐项证据待提供。
'}${link('validation.json', '构建与最终验证记录')} · ${link('screenshot-verification.json', '截图附件匹配结果')}。
最终整轮确认:${finalRun ? '已完成' : '待提供'};最终交付物哈希扫描:${finalArtifactsVerified ? '通过' : '待完成'}。
+范围与产品确认
较大调整单列,不纳入本轮通过数 未挂载正式工作区的能力差异 ${table(['能力组', '当前边界'], unmountedWorkspace.map(row => row.map(esc)))}
${link(sceneSources.formal, 'SceneEditorWorkspace.vue 源码参考')}。没有可达 UI 的功能不臆造测试、不自动迁入默认入口。
${arr(decisions.items).map(item => `${esc(item.id)} ${esc(item.title)} 当前行为 ${esc(item.current)} 建议确认的范围 ${esc(item.scope)} 可观察验收 ${esc(item.acceptance)} `).join('')}
+实际截图
${images.length} 张原图 · 点击放大 失败注入、加载遮罩、环境全景和实际模型可见证据分别写明。故意移动/缩放部件的测试姿态不能误判为加载丢失。
${images.length ? images.map((file, index) => { const caption = captions[file] || captions[file.replace(/\.[^.]+$/, '')] || {}; const verify = imageProof.find(item => item.file === file); return `
${index + 1}. ${esc(caption.title || file)} ${badge(verify.status)}${esc(caption.description || '图注尚未提供;不推断画面已验收。')}
${esc(file)} ` }).join('') : '
尚未提供截图,不生成或补画测试画面。
'}
${links.map(file => link(file)).join('')}
生成命令:node tools/build-scene-editor-closure-report.mjs。仅处理此场景报告;原模型报告不改动。没有当前轮关联证据的功能保持 PENDING。 `
+fs.writeFileSync(path.join(report, 'index.html'), html)
+console.log(JSON.stringify({ report: path.join(report, 'index.html'), overall, featureCount, e2eCount, screenshots: images.length, unresolvedSources: catalog.filter(row => !row.source.line).map(row => row.id), unknownIds, missing, malformed }, null, 2))
diff --git a/tools/build-scene-editor-coverage-plan.mjs b/tools/build-scene-editor-coverage-plan.mjs
new file mode 100644
index 0000000..befc7fb
--- /dev/null
+++ b/tools/build-scene-editor-coverage-plan.mjs
@@ -0,0 +1,56 @@
+import fs from 'node:fs'
+import path from 'node:path'
+import { fileURLToPath } from 'node:url'
+import ts from 'typescript'
+import { sceneGroups } from './scene-editor-feature-catalog.mjs'
+import { sceneEvidenceBindings, sceneUnitPlan } from './scene-editor-evidence-bindings.mjs'
+
+// Static test-plan extraction, never executes a business test or accesses API.
+const root = path.resolve(path.dirname(fileURLToPath(import.meta.url)), '..')
+const report = path.join(root, 'reports/scene-editor-closure-20260906')
+const catalog = sceneGroups.flatMap(([group, rows]) => rows.map(([id, name]) => ({ id, name, group })))
+const browser = []
+const literal = node => node && (ts.isStringLiteral(node) || ts.isNoSubstitutionTemplateLiteral(node)) ? node.text : null
+function property(node, key) {
+ return node && ts.isObjectLiteralExpression(node)
+ ? node.properties.find(p => ts.isPropertyAssignment(p) && p.name.getText().replace(/['"]/g, '') === key)?.initializer : null
+}
+for (const basename of fs.readdirSync(path.join(root, 'tests')).filter(f => /^scene-editor-.*closure\.spec\.ts$/.test(f))) {
+ const file = `tests/${basename}`, text = fs.readFileSync(path.join(root, file), 'utf8')
+ const source = ts.createSourceFile(file, text, ts.ScriptTarget.Latest, true)
+ function visit(node, title = '') {
+ if (ts.isCallExpression(node) && node.expression.getText(source) === 'test') title = literal(node.arguments[0]) || node.arguments[0].getText(source)
+ if (ts.isCallExpression(node) && node.expression.getText(source) === 'evidence') {
+ const name = literal(node.arguments[0]), data = node.arguments[1]
+ const idsNode = property(data, 'featureIds')
+ const explicitIds = idsNode && ts.isArrayLiteralExpression(idsNode) ? idsNode.elements.map(literal).filter(Boolean) : []
+ const binding = name && sceneEvidenceBindings[name]
+ const ids = explicitIds.length ? explicitIds : binding?.featureIds || []
+ const line = source.getLineAndCharacterOfPosition(node.getStart()).line + 1
+ if (name) {
+ const featureIds = name === 'basic-assets'
+ ? [...ids, ...['diagnostic','equipment','workbench','shelf','safety-zone','camera','worker','light'].map(v => `asset.${v}`)] : ids
+ browser.push({ file, line, evidenceName: name, testTitle: title, kind: 'browser', featureIds: [...new Set(featureIds)], scope: literal(property(data, 'scope')) || binding?.scope || '以源测试实际断言为准;计划关联不代表最终通过。' })
+ } else if (basename === 'scene-editor-templates-closure.spec.ts') {
+ for (const item of catalog.filter(item => item.id.startsWith('template.') && item.id !== 'template.switch-cancel')) {
+ browser.push({ file, line, evidenceName: item.id.replace('template.', 'template-'), testTitle: `场景模板实际加载、保存重开:${item.name}`, kind: 'browser', featureIds: [item.id], scope: '循环参数化模板用例,每种独立副本、真实点击、保存、刷新与截图。' })
+ }
+ } else if (basename === 'scene-editor-downstream-closure.spec.ts') {
+ for (const key of ['fox','truck']) browser.push({ file, line, evidenceName: `downstream-${key}`, testTitle: `${key}:场景发布后在真实训练编排选择、保存并重开静态模型`, kind: 'browser', featureIds: ids, scope: '真实训练编排静态还原;不计来源模型动画/蓝图自动执行。' })
+ }
+ }
+ ts.forEachChild(node, child => visit(child, title))
+ }
+ visit(source)
+}
+const unit = sceneUnitPlan.map(([file, testTitle, featureIds]) => ({ file, testTitle, featureIds, kind: 'unit', scope: '聚焦合同/真实DOM/Three对象计算;不称为浏览器鼠标交互。' }))
+const rows = catalog.map(item => ({ ...item, verification: 'PENDING', plannedEvidence: [...browser, ...unit].filter(proof => proof.featureIds.includes(item.id)) }))
+const unknownIds = [...new Set([...browser, ...unit].flatMap(p => p.featureIds).filter(id => !catalog.some(row => row.id === id)))]
+const data = { generatedAt: new Date().toISOString(), status: 'PENDING', scope: '只读解析当前spec与明确unit计划;不执行测试,不推断最终PASS。',
+ count: { total: rows.length, withBrowserPlan: rows.filter(r => r.plannedEvidence.some(p => p.kind === 'browser')).length,
+ onlyUnitPlan: rows.filter(r => r.plannedEvidence.length && r.plannedEvidence.every(p => p.kind === 'unit')).length,
+ unmapped: rows.filter(r => !r.plannedEvidence.length).length },
+ unknownIds, gaps: rows.filter(r => !r.plannedEvidence.length).map(({ id, name }) => ({ id, name })), features: rows }
+fs.mkdirSync(path.join(report, 'data'), { recursive: true })
+fs.writeFileSync(path.join(report, 'data/coverage-test-plan.json'), JSON.stringify(data, null, 2))
+console.log(JSON.stringify({ count: data.count, gaps: data.gaps, unknownIds }, null, 2))
diff --git a/tools/check-scene-review-originals.mjs b/tools/check-scene-review-originals.mjs
new file mode 100644
index 0000000..c0979e9
--- /dev/null
+++ b/tools/check-scene-review-originals.mjs
@@ -0,0 +1,17 @@
+import fs from 'node:fs';
+import path from 'node:path';
+import { call, login, fixture, work } from './scene-editor-closure-live.mjs';
+const session = await login('root');
+function diff(a,b,p='') {
+ if(JSON.stringify(a)===JSON.stringify(b))return [];
+ if(a && b && typeof a==='object'&&typeof b==='object')return [...new Set([...Object.keys(a),...Object.keys(b)])].flatMap(k=>diff(a[k],b[k],`${p}/${k}`));
+ return [{path:p,before:a,after:b}];
+}
+try {
+ for(const [key,original] of Object.entries(fixture().originals)) {
+ const before=JSON.parse(fs.readFileSync(path.join(work,`original-${key}.json`),'utf8'));
+ const current=await call(`tran/v1/content/projects/${original.id}`,session.accessToken);
+ const differences=diff(before,current.data);
+ console.log(JSON.stringify({key,id:original.id,differences}));
+ }
+} finally {await call('auth/v1/auth/logout',session.accessToken,{}).catch(()=>{});}
diff --git a/tools/cleanup-model-editor-closure.mjs b/tools/cleanup-model-editor-closure.mjs
new file mode 100644
index 0000000..07bcfa3
--- /dev/null
+++ b/tools/cleanup-model-editor-closure.mjs
@@ -0,0 +1,54 @@
+import fs from 'node:fs';
+import path from 'node:path';
+import crypto from 'node:crypto';
+import assert from 'node:assert/strict';
+import { call, login, fixture, writeFixture, report } from './model-editor-closure-live.mjs';
+
+const data = fixture();
+const session = await login('root');
+const result = { time: new Date().toISOString(), status: 'PENDING', originals: [], projects: [] };
+try {
+ const me = await call('auth/v1/auth/me', session.accessToken);
+ assert.equal(me.status, 200);
+ for (const original of Object.values(data.originals)) {
+ const current = await call(`tran/v1/content/projects/${original.id}`, session.accessToken);
+ assert.equal(current.status, 200);
+ const hash = crypto.createHash('sha256').update(JSON.stringify(current.data)).digest('hex');
+ assert.equal(hash, original.hash, `Original ${original.id} full document must be unchanged`);
+ result.originals.push({ id: original.id, name: original.name, version: current.data.project.version, unchanged: true });
+ }
+ const originals = new Set(Object.values(data.originals).map(item => String(item.id)));
+ const ids = [...new Set([...Object.values(data.models).map(item => item.id), ...data.extraProjectIds].map(String))];
+ for (const id of ids) {
+ assert.ok(!originals.has(id), 'Never delete original reference projects');
+ const current = await call(`tran/v1/content/projects/${id}`, session.accessToken);
+ if (current.status === 404) { result.projects.push({ id, alreadyAbsent: true }); continue; }
+ assert.equal(current.status, 200);
+ let p = current.data.project;
+ assert.ok(/^闭环(?:验收|新建|补充|工具补充)-/.test(p.name) || p.name === `闭环导航名称-${id}`,
+ 'Only names created or renamed by this review may be removed');
+ assert.equal(String(p.ownerUserId), String(me.data.id));
+ assert.ok(p.addTime >= Math.floor(Date.parse(data.createdAt) / 1000));
+ let withdrawn = false;
+ if (p.status === 'PUBLISHED' || p.statusCode === 'PUBLISHED') {
+ const withdrawal = await call(`tran/v1/content/projects/${id}/review`, session.accessToken, {
+ action: 'REVOKE', version: p.version, comment: '装备模型编辑闭环验收结束,清理本次隔离测试副本',
+ });
+ assert.equal(withdrawal.status, 200, `Withdraw isolated project ${id}`);
+ p = withdrawal.data.project;
+ withdrawn = true;
+ }
+ const removed = await call(`tran/v1/content/projects/${id}?version=${p.version}`, session.accessToken, undefined, 'DELETE');
+ assert.equal(removed.status, 200, `Remove isolated project ${id}: ${removed.message}`);
+ result.projects.push({ id, name: p.name, withdrawn, removed: true });
+ }
+ result.status = 'PASS';
+ data.cleanedAt = result.time; writeFixture(data);
+ console.log(JSON.stringify({ status: result.status, unchangedOriginals: result.originals.length, cleanedProjects: result.projects.length }));
+} catch (error) {
+ result.status = 'FAIL'; result.error = error.message; process.exitCode = 1;
+ console.error(error.message);
+} finally {
+ fs.writeFileSync(path.join(report, 'cleanup.json'), JSON.stringify(result, null, 2));
+ await call('auth/v1/auth/logout', session.accessToken, {}).catch(() => {});
+}
diff --git a/tools/cleanup-scene-editor-closure.mjs b/tools/cleanup-scene-editor-closure.mjs
new file mode 100644
index 0000000..237a57d
--- /dev/null
+++ b/tools/cleanup-scene-editor-closure.mjs
@@ -0,0 +1,75 @@
+import fs from 'node:fs';
+import path from 'node:path';
+import assert from 'node:assert/strict';
+import { call, login, fixture, writeFixture, report, work, hash } from './scene-editor-closure-live.mjs';
+const data=fixture(), session=await login('root'), token=session.accessToken;
+const result={time:new Date().toISOString(),status:'PENDING',originals:[],projects:[],users:[],roles:[]};
+const originalSnapshots=new Map();
+function authoredSnapshot(detail) {
+ const project={...detail.project};
+ // Dependency creation/removal refreshes these two derived bookkeeping fields.
+ // Keep every authored field, version, asset and dependency in the comparison.
+ delete project.usageCount;delete project.updateTime;
+ return {...detail,project};
+}
+try {
+ const me=(await call('auth/v1/auth/me',token)).data;
+ const originals=new Set(Object.values(data.originals).map(o=>String(o.id)));
+ const owners=new Set([String(me.id),String(data.user?.id),...(data.extraUsers||[]).map(u=>String(u.id))]);
+ for(const [key,original] of Object.entries(data.originals)) {
+ const baseline=JSON.parse(fs.readFileSync(path.join(work,`original-${key}.json`),'utf8'));
+ assert.equal(hash(baseline),original.hash,`Original snapshot integrity ${original.id}`);
+ originalSnapshots.set(String(original.id),baseline);
+ const current=await call(`tran/v1/content/projects/${original.id}`,token);
+ assert.equal(current.status,200);assert.equal(hash(authoredSnapshot(current.data)),hash(authoredSnapshot(baseline)),`Original ${original.id} authored data unchanged`);
+ result.originals.push({id:original.id,name:original.name,version:original.version,unchanged:true,scope:'全部创作字段、版本、资产、依赖;引用统计与其触发的更新时间单独核对',
+ before:{usageCount:baseline.project.usageCount,updateTime:baseline.project.updateTime}});
+ }
+ // Training copies can depend on scene copies; remove dependents first.
+ const registrations=[...data.projects].sort((a,b)=>Number(b.id)-Number(a.id));
+ for(const registered of registrations) {
+ const id=String(registered.id);assert.ok(!originals.has(id));
+ const found=await call(`tran/v1/content/projects/${id}`,token);
+ if(found.status===404){result.projects.push({id,alreadyAbsent:true});continue;}
+ assert.equal(found.status,200);let p=found.data.project;
+ assert.equal(p.code,registered.code,`Registered project identity ${id}`);
+ assert.ok(p.name.startsWith('场景闭环-'),`Review-owned name ${id}`);
+ assert.ok(owners.has(String(p.ownerUserId)),`Review-owned actor ${id}`);
+ assert.ok(p.addTime>=Math.floor(Date.parse(data.createdAt)/1000),`Review creation time ${id}`);
+ let withdrawn=false;
+ if(p.status==='PUBLISHED'){
+ const revoked=await call(`tran/v1/content/projects/${id}/review`,token,{action:'REVOKE',version:p.version,comment:'场景编辑器闭环验收完成,清理独立测试副本'});
+ assert.equal(revoked.status,200,`Revoke isolated ${id}: ${revoked.message}`);p=revoked.data.project;withdrawn=true;
+ }
+ const removed=await call(`tran/v1/content/projects/${id}?version=${p.version}`,token,undefined,'DELETE');
+ assert.equal(removed.status,200,`Remove isolated ${id}: ${removed.message}`);
+ result.projects.push({id,name:p.name,withdrawn,removed:true});
+ }
+ for(const user of [data.user,...(data.extraUsers||[])].filter(Boolean)){
+ assert.ok(/^(rv_scene_|scene_perm_)/.test(user.username),'Only registered review users');
+ const current=await call(`auth/v1/users/${user.id}`,token);
+ if(current.status===404){result.users.push({id:user.id,alreadyAbsent:true});continue;}
+ assert.equal(current.status,200);assert.equal(current.data.username,user.username);assert.equal(current.data.isProtected,false);
+ const removed=await call(`auth/v1/users/${user.id}`,token,{version:current.data.dataVersion??current.data.version},'DELETE');
+ assert.equal(removed.status,200,`Remove isolated user: ${removed.message}`);result.users.push({id:user.id,removed:true});
+ }
+ for(const id of data.roleIds||[]){
+ const current=await call(`auth/v1/roles/${id}`,token);
+ if(current.status===404){result.roles.push({id,alreadyAbsent:true});continue;}
+ assert.equal(current.status,200);assert.ok(String(current.data.code).startsWith('scene_'));
+ const removed=await call(`auth/v1/roles/${id}`,token,{version:current.data.dataVersion??current.data.version},'DELETE');
+ assert.equal(removed.status,200,`Remove isolated role: ${removed.message}`);result.roles.push({id,removed:true});
+ }
+ for(const original of result.originals){
+ const baseline=originalSnapshots.get(String(original.id)),current=await call(`tran/v1/content/projects/${original.id}`,token);
+ assert.equal(current.status,200);assert.equal(hash(authoredSnapshot(current.data)),hash(authoredSnapshot(baseline)));
+ assert.equal(current.data.project.usageCount,baseline.project.usageCount,`Original ${original.id} reference count restored`);
+ original.after={usageCount:current.data.project.usageCount,updateTime:current.data.project.updateTime};
+ original.fullRecordUnchanged=hash(current.data)===hash(baseline);
+ original.referenceCountRestored=true;
+ }
+ result.note='测试副本增删会刷新原件引用统计及更新时间;创作内容、版本、资产和依赖未改写,引用数量已恢复,未回填或伪造原更新时间。';
+ result.status='PASS';data.cleanedAt=result.time;writeFixture(data);
+ console.log(JSON.stringify({status:'PASS',originals:result.originals.length,projects:result.projects.length,users:result.users.length,roles:result.roles.length}));
+}catch(error){result.status='FAIL';result.error=error.message;process.exitCode=1;console.error(error.message);}
+finally{fs.writeFileSync(path.join(report,'cleanup.json'),JSON.stringify(result,null,2));await call('auth/v1/auth/logout',token,{}).catch(()=>{});}
diff --git a/tools/content-permissions.test.mjs b/tools/content-permissions.test.mjs
index 6a23ec5..b5654fc 100644
--- a/tools/content-permissions.test.mjs
+++ b/tools/content-permissions.test.mjs
@@ -3,6 +3,8 @@ import test from 'node:test'
import { readFile } from 'node:fs/promises'
import { createRequire } from 'node:module'
const requireWeb = createRequire(new URL('../../unreal_tran_web/package.json', import.meta.url))
+const requirePrototype = createRequire(new URL('../../unreal_tran/package.json', import.meta.url))
+const { Window } = requirePrototype('happy-dom')
const ts = requireWeb('typescript')
const web = new URL('../../unreal_tran_web/src/', import.meta.url)
const dataModule = source => `data:text/javascript;base64,${Buffer.from(source).toString('base64')}`
@@ -64,24 +66,25 @@ test('OFD lifecycle does not treat admin/teacher identity as a publish grant', (
assert.equal(canPerformOFDAction(state, 'publish', { role: 'custom', permissions: ['content.ofd.update'] }), false)
})
-const { installLegacyModelPermissions } = await import(await compile('features/editors/model/legacy-iframe-permissions.ts', {
- '../common/editor-project-io.js': new URL('features/editors/common/editor-project-io.js', web).href,
-}))
-function modelFixture({ update = false, publish = false } = {}) {
+const { installLegacyModelPermissions } = await import(await compile('features/editors/model/legacy-iframe-permissions.ts'))
+function modelFixture({ update = false, publish = false, create = false, createNew, document } = {}) {
const calls = []
const editor = {
apiEditorHydrated: true, apiState: 'ready', apiSession: { project: { id: 'test', version: 7 }, lastSavedFingerprint: '' },
saveProject: async () => { calls.push('save'); return {} },
+ newProject: async () => { calls.push('local-new'); return true },
publishToSceneLibrary: async () => { calls.push('save-and-publish'); return {} },
saveRecoveryCopy: () => calls.push('recovery'),
serializeProject: () => ({}), toast: message => calls.push(message),
transform: { enabled: true, detach() {}, attach() { calls.push('transform') } },
}
- const doc = { defaultView: { editorApp: editor }, body: {}, querySelector: () => null, querySelectorAll: () => [], addEventListener() {}, removeEventListener() {} }
+ const doc = document || { defaultView: { editorApp: editor }, body: {}, querySelector: () => null, querySelectorAll: () => [], addEventListener() {}, removeEventListener() {} }
+ doc.defaultView.editorApp = editor
const previousObserver = globalThis.MutationObserver
globalThis.MutationObserver = class { observe() {} disconnect() {} }
const bridge = installLegacyModelPermissions(doc, {
- canCreate: () => false, canUpdate: () => update, canPublish: () => publish,
+ canCreate: () => create, canUpdate: () => update, canPublish: () => publish,
+ createNew: createNew || (async () => { calls.push('server-new'); return true }),
publishSaved: async version => { calls.push(['publish-saved', version]); return { id: 'test', version: 8 } },
})
globalThis.MutationObserver = previousObserver
@@ -105,6 +108,27 @@ test('model iframe publisher-only uses the loaded optimistic-lock version withou
assert.equal(calls.includes('save-and-publish'), false)
bridge.dispose()
})
+
+test('read-only model search, native clip selection and preview keyboard activation remain usable without enabling edits', t => {
+ const window = new Window({ url: 'http://localhost/editor.html' })
+ t.after(() => window.happyDOM.cancelAsync())
+ const doc = window.document
+ doc.body.innerHTML = ''
+ const { bridge } = modelFixture({ document: doc })
+ t.after(() => bridge.dispose())
+ const dispatch = (selector, type, key = '') => doc.querySelector(selector).dispatchEvent(type === 'keydown'
+ ? new window.KeyboardEvent(type, { key, bubbles: true, cancelable: true })
+ : new window.Event(type, { bubbles: true, cancelable: true }))
+ for (const key of ['x', 'Backspace', 'Enter']) assert.equal(dispatch('#left-search', 'keydown', key), true, `search ${key}`)
+ assert.equal(dispatch('#left-search', 'input'), true)
+ assert.equal(dispatch('#native-clip-select', 'change'), true, 'a clip choice changes preview playback, not the saved project')
+ assert.equal(dispatch('#left-search', 'drop'), false, 'read-only control exceptions must not enable resource drops')
+ for (const key of ['Enter', ' ']) assert.equal(dispatch('[data-action="preview"]', 'keydown', key), true, `preview activation ${key}`)
+ assert.equal(dispatch('#project-name', 'input'), false)
+ assert.equal(dispatch('#position-x', 'change'), false)
+ assert.equal(dispatch('#model-file-input', 'change'), false)
+ assert.equal(dispatch('[data-action="preview"]', 'keydown', 'Delete'), false)
+})
test('committed role switch prevents iframe unload recovery from writing discarded changes', async () => {
const { editor, calls, bridge } = modelFixture({ update: true, publish: true })
bridge.roleSwitchCommitted()
@@ -124,6 +148,75 @@ test('read-only model viewing skips automatic migration and recovery without mod
assert.equal(editor.apiDocumentNeedsMigrationSave, true, 'the host must restore the original runtime state on disposal')
})
+const { ModelContentProjectSession } = await import(new URL('../../unreal_tran/src/model-content-api.js', import.meta.url))
+const { createBuiltInTestProject: createModelDocument, normalizeProjectDocument: normalizeModelDocument } = await import(new URL('../../unreal_tran/src/editor-project-io.js', import.meta.url))
+
+test('model host dirty check uses the complete saved iframe document, including depot environments', async () => {
+ for (const environmentType of ['outdoor', 'workshop', 'depot']) {
+ const document = normalizeModelDocument(createModelDocument())
+ document.environment.environmentType = environmentType
+ document.environment.objects = { 'environment-fixture': { position: [1, 2, 3], visible: false } }
+ document.camera.position = [7, 8, 9]
+ const session = new ModelContentProjectSession({ projectId: '7', client: {
+ async request(_path, { json }) {
+ return { ...session.project, ...structuredClone(json), id: '7', version: session.project.version + 1 }
+ },
+ } })
+ session.project = { id: '7', type: 'MODEL', name: document.name, version: 1, coverUri: '', assets: [], dependencies: [] }
+ await session.queueSave(document)
+ const { editor, bridge } = modelFixture({ update: true })
+ editor.apiSession = session
+ editor.serializeProject = () => structuredClone(document)
+ assert.equal(bridge.isDirty(), false, `${environmentType}: successful save must be clean`)
+ document.model = { id: 'fixture-part', visible: false }
+ assert.equal(bridge.isDirty(), true, `${environmentType}: real model edits must remain dirty`)
+ await session.queueSave(document)
+ assert.equal(bridge.isDirty(), false, `${environmentType}: saving the real edit clears dirty state`)
+ document.environment.objects['environment-fixture'].position[0] = 10
+ assert.equal(bridge.isDirty(), true, `${environmentType}: environment edits must remain dirty`)
+ bridge.dispose()
+ const readonly = modelFixture()
+ readonly.editor.apiSession = session
+ readonly.editor.serializeProject = () => structuredClone(document)
+ assert.equal(readonly.bridge.isDirty(), false, 'read-only viewing must not prompt to save')
+ readonly.bridge.dispose()
+ }
+})
+
+test('model create-only permission uses host creation without resetting or saving the loaded project', async () => {
+ const { editor, bridge, calls } = modelFixture({ create: true })
+ assert.equal(await editor.newProject(), true)
+ assert.deepEqual(calls, ['server-new'])
+ assert.equal(await editor.saveProject(), null)
+ bridge.dispose()
+ assert.equal(await editor.newProject(), true)
+ assert.deepEqual(calls, ['server-new', 'local-new'], 'disposing restores the original runtime method')
+})
+
+test('model new prevents concurrent creation and releases its guard after cancellation or failure', async () => {
+ let settle, attempts = 0
+ const { editor, bridge } = modelFixture({ create: true, createNew: () => {
+ attempts += 1
+ return new Promise((resolve, reject) => { settle = { resolve, reject } })
+ } })
+ const first = editor.newProject()
+ assert.equal(await editor.newProject(), false)
+ assert.equal(attempts, 1)
+ settle.resolve(false)
+ assert.equal(await first, false)
+ const second = editor.newProject()
+ settle.reject(new Error('fixture creation failed'))
+ await assert.rejects(second, /fixture creation failed/)
+ const third = editor.newProject()
+ assert.equal(attempts, 3, 'cancellation and rejection must both permit a retry')
+ settle.resolve(true)
+ assert.equal(await third, true)
+ bridge.roleSwitchCommitted()
+ assert.equal(await editor.newProject(), false)
+ assert.equal(attempts, 3, 'a committed role switch must prevent late creation')
+ bridge.dispose()
+})
+
const policy = await import(await compile('features/guide/publishDeliveriesPolicy.ts'))
const publishSource = await readFile(new URL('features/guide/publishDeliveries.ts', web), 'utf8')
const publishFunction = ts.transpileModule(publishSource.slice(publishSource.indexOf('export async function publishGuideWithDeliveries(')), {
diff --git a/tools/finalize-scene-editor-closure.mjs b/tools/finalize-scene-editor-closure.mjs
new file mode 100644
index 0000000..3f022e6
--- /dev/null
+++ b/tools/finalize-scene-editor-closure.mjs
@@ -0,0 +1,49 @@
+import fs from 'node:fs';
+import path from 'node:path';
+import crypto from 'node:crypto';
+import assert from 'node:assert/strict';
+import {report,work} from './scene-editor-closure-live.mjs';
+import {sceneUnitPlan} from './scene-editor-evidence-bindings.mjs';
+const readLog=name=>{const b=fs.readFileSync(path.join(work,name));return b.subarray(0,2).equals(Buffer.from([255,254]))?b.toString('utf16le').slice(1):b.toString('utf8').replace(/^\uFEFF/,'')};
+const read=name=>JSON.parse(fs.readFileSync(path.join(report,name),'utf8'));
+const sha=file=>crypto.createHash('sha256').update(fs.readFileSync(file)).digest('hex');
+const write=(name,value)=>fs.writeFileSync(path.join(report,name),JSON.stringify(value,null,2));
+const results=read('results.json');
+function tests(suites){return suites.flatMap(s=>[...(s.specs||[]).flatMap(p=>p.tests.map(t=>({title:p.title,status:t.status,result:t.results.at(-1)}))),...tests(s.suites||[])])}
+const cases=tests(results.suites);
+assert.ok(cases.length>0);assert.equal(results.errors.length,0);assert.ok(cases.every(t=>t.status==='expected'&&t.result.status==='passed'&&t.result.retry===0),'One full no-retry browser run must pass');
+const runtimeLog=readLog('node-tests-final.log'),apiLog=readLog('scene-api-tests-final.log');
+const totals=text=>Object.fromEntries(['tests','pass','fail','skipped'].map(key=>[key,Number(new RegExp(`^# ${key} (\\d+)`,'m').exec(text)?.[1])]));
+const runtime=totals(runtimeLog),client=totals(apiLog);
+for(const group of [runtime,client]){assert.ok(group.tests>0);assert.equal(group.pass,group.tests);assert.equal(group.fail,0);assert.equal(group.skipped,0)}
+const build=readLog('web-build-final.log');assert.match(build,/built in/);assert.match(build,/legacy model iframe verified/);
+const apiBuild=readLog('api-build.log');assert.match(apiBuild,/BUILD SUCCESS/);
+const apiReports=path.resolve(report,'../../../unreal_tran_api/ut_tran/target/surefire-reports');
+const backend={tests:0,failures:0,errors:0,skipped:0,suites:0};
+for(const file of fs.readdirSync(apiReports).filter(f=>/^TEST-.*\.xml$/.test(f))){
+ const head=fs.readFileSync(path.join(apiReports,file),'utf8').match(/]*>/)?.[0];assert.ok(head);
+ backend.suites++;for(const key of ['tests','failures','errors','skipped'])backend[key]+=Number(new RegExp(`\\b${key}="(\\d+)"`).exec(head)?.[1]||0);
+}
+assert.ok(backend.tests>0);assert.equal(backend.failures+backend.errors+backend.skipped,0);
+const runId=`scene-editor-${results.stats.startTime}`;
+const coverage=sceneUnitPlan.map(([file,title,featureIds])=>{
+ assert.ok(`${runtimeLog}\n${apiLog}`.includes(`- ${title}\n`)||`${runtimeLog}\n${apiLog}`.includes(`- ${title}\r\n`),`Executed unit proof: ${title}`);
+ return{kind:'unit',status:'PASS',runId,testTitle:title,featureIds,source:file,note:'实际执行真实方法/纯函数合同,未冒称浏览器操作'};
+});
+write('validation.json',{time:new Date().toISOString(),runId,overallStatus:'PASS',finalRun:true,
+ scope:'默认路由的119项功能;浏览器证据与聚焦单元合同分别计数。较大功能扩展未实施。',
+ e2e:{tests:cases.length,pass:cases.length,fail:0,retries:0,startTime:results.stats.startTime,durationMs:results.stats.duration,complete:true},
+ priorFullRun:{pass:32,fail:1,reason:'关闭保存失败用例的提示定位同时匹配成功和失败消息,已限定目标错误;另补列表搜索遮罩消失后截图的等待条件。',privateArchived:true,countedAsFinal:false},
+ secondFullRun:{pass:32,fail:1,reason:'并行加载同源模型时恢复顺序不稳定;已保留文档对象顺序,并以实例编号验证部件隔离和重开。',privateArchived:true,countedAsFinal:false},
+ frontend:{status:'PASS',command:'pnpm run build',legacyModelPayloadUnchanged:true},
+ backend:{status:'PASS',command:'mvn -pl ut_tran -am test package',...backend},
+ runtime:{status:'PASS',...runtime},client:{status:'PASS',...client},coverage,
+ database:{ddlChanged:false,reason:'未新增或修改表结构;仅注册并清理测试副本、账号和角色。'},
+ logSha256:Object.fromEntries(['web-build-final.log','api-build.log','node-tests-final.log','scene-api-tests-final.log'].map(file=>[file,sha(path.join(work,file))]))});
+const captions=read('screenshot-captions.json');
+const titles=cases.flatMap(t=>(t.result.attachments||[]).map(a=>({name:a.name,title:t.title})));
+for(const file of fs.readdirSync(path.join(report,'screenshots'))){
+ if(!captions[file]){const base=file.replace(/\.[^.]+$/,'');const found=titles.find(a=>a.name===base||a.name===file);assert.ok(found,`Current screenshot attachment ${file}`);captions[file]=`${found.title}:${base}`;}
+}
+write('screenshot-captions.json',captions);
+console.log(JSON.stringify({e2e:cases.length,backend:backend.tests,node:runtime.tests+client.tests,unitProofs:coverage.length,runId}));
diff --git a/tools/model-editor-closure-live.mjs b/tools/model-editor-closure-live.mjs
new file mode 100644
index 0000000..0def492
--- /dev/null
+++ b/tools/model-editor-closure-live.mjs
@@ -0,0 +1,48 @@
+import fs from 'node:fs';
+import path from 'node:path';
+import crypto from 'node:crypto';
+import assert from 'node:assert/strict';
+import { fileURLToPath } from 'node:url';
+import { login, root } from './role-permissions-live.mjs';
+
+export { login };
+export async function call(endpoint, token, body, method = body === undefined ? 'GET' : 'POST') {
+ const response = await fetch(`http://127.0.0.1:6180/api/${endpoint}`, {
+ method, headers: { 'Content-Type': 'application/json', ...(token ? { Authorization: `Bearer ${token}` } : {}) },
+ body: body === undefined ? undefined : JSON.stringify(body), signal: AbortSignal.timeout(120000),
+ });
+ const value = await response.json();
+ return { status: response.status, code: value.code, data: value.data, message: value.message };
+}
+export const report = path.join(root, 'ute2e/reports/model-editor-closure-20260906');
+export const work = path.resolve(root, '../.codex-tmp/model-closure-20260906');
+export const fixturePath = path.join(work, 'fixtures.json');
+fs.mkdirSync(report, { recursive: true }); fs.mkdirSync(work, { recursive: true });
+export const fixture = () => JSON.parse(fs.readFileSync(fixturePath, 'utf8'));
+export const writeFixture = value => fs.writeFileSync(fixturePath, JSON.stringify(value, null, 2));
+const hash = value => crypto.createHash('sha256').update(JSON.stringify(value)).digest('hex');
+
+async function prepare() {
+ if (fs.existsSync(fixturePath) && !fixture().cleanedAt && !process.argv.includes('--resume')) throw new Error('This run already has fixtures; reuse them or clean up first');
+ const session = await login('root');
+ const data = process.argv.includes('--resume') ? fixture() : { createdAt: new Date().toISOString(), models: {}, extraProjectIds: [], originals: {} };
+ writeFixture(data);
+ try {
+ for (const [key, id, name] of [['fox', '157', '闭环验收-狐狸-20260906'], ['truck', '156', '闭环验收-牛奶卡车-20260906']]) {
+ if (data.models[key]) continue;
+ const original = await call(`tran/v1/content/projects/${id}`, session.accessToken);
+ assert.equal(original.status, 200);
+ data.originals[key] = { id, name: original.data.project.name, version: original.data.project.version, hash: hash(original.data) };
+ fs.writeFileSync(path.join(work, `original-${key}.json`), JSON.stringify(original.data, null, 2));
+ writeFixture(data);
+ const copy = await call(`tran/v1/content/projects/${id}/copy`, session.accessToken, { name, version: original.data.project.version });
+ assert.equal(copy.status, 200, `Copy ${key}: ${copy.message}`);
+ assert.ok(copy.data.project.id);
+ data.models[key] = { id: copy.data.project.id, name, code: copy.data.project.code };
+ writeFixture(data);
+ }
+ console.log(JSON.stringify({ fixturesReady: true, models: data.models }));
+ } finally { await call('auth/v1/auth/logout', session.accessToken, {}).catch(() => {}); }
+}
+
+if (process.argv[1] === fileURLToPath(import.meta.url)) prepare().catch(error => { console.error(error.message); process.exitCode = 1; });
diff --git a/tools/model-editor-report-evidence.mjs b/tools/model-editor-report-evidence.mjs
new file mode 100644
index 0000000..e3ed113
--- /dev/null
+++ b/tools/model-editor-report-evidence.mjs
@@ -0,0 +1,22 @@
+// Report-only evidence rules. These functions do not run the application.
+export function resolveBrowserProof(proof, rows) {
+ if (!proof.testTitle) return { ...proof }
+ const candidates = rows.filter(row => row.title === proof.testTitle || row.rawTitle === proof.testTitle)
+ if (candidates.length !== 1) return { ...proof, status: 'PENDING', verificationNote: candidates.length ? '同名测试不唯一,不能推断关联。' : '当前 results.json 没有此测试,旧证据不计入。' }
+ const test = candidates[0]
+ if (test.status !== 'PASS') return { ...proof, status: test.status, verificationNote: '当前 results.json 对应用例未通过;定向重跑不能覆盖此轮失败。' }
+ const recorded = Date.parse(proof.recordedAt), started = Date.parse(test.startTime)
+ const duration = Number(test.duration)
+ if (!Number.isFinite(recorded) || !Number.isFinite(started) || !Number.isFinite(duration)
+ || recorded < started - 2000 || recorded > started + duration + 2000) {
+ return { ...proof, status: 'PENDING', verificationNote: '证据时间不在当前测试的执行范围内,需核对是否属于预跑/重跑。' }
+ }
+ return { ...proof }
+}
+
+export function screenshotProofStatus(associations) {
+ if (!associations.length) return 'PENDING'
+ if (associations.some(item => item.status === 'FAIL')) return 'FAIL'
+ if (associations.some(item => item.status === 'WARN')) return 'WARN'
+ return associations.every(item => item.status === 'PASS' && item.hashMatches === true) ? 'PASS' : 'PENDING'
+}
diff --git a/tools/model-editor-report-evidence.test.mjs b/tools/model-editor-report-evidence.test.mjs
new file mode 100644
index 0000000..65600ea
--- /dev/null
+++ b/tools/model-editor-report-evidence.test.mjs
@@ -0,0 +1,33 @@
+import assert from 'node:assert/strict'
+import test from 'node:test'
+import { resolveBrowserProof, screenshotProofStatus } from './model-editor-report-evidence.mjs'
+
+const row = { title: '已保存蓝图', rawTitle: '已保存蓝图', status: 'PASS', startTime: '2026-09-06T07:00:00Z', duration: 10000 }
+const proof = { testTitle: row.title, status: 'PASS', recordedAt: '2026-09-06T07:00:05Z', kind: 'e2e' }
+test('本轮同标题且时间落在实际运行内的证据可关联', () => {
+ assert.equal(resolveBrowserProof(proof, [row]).status, 'PASS')
+})
+test('定向重跑成功不得覆盖全量结果中仍然失败的同名用例', () => {
+ assert.equal(resolveBrowserProof({ ...proof, recordedAt: '2026-09-06T08:00:05Z' }, [{ ...row, status: 'FAIL' }]).status, 'FAIL')
+})
+test('同名旧证据、未来重跑证据和没有时间的证据不计本轮通过', () => {
+ for (const recordedAt of ['2026-09-06T06:00:05Z', '2026-09-06T08:00:05Z', undefined])
+ assert.equal(resolveBrowserProof({ ...proof, recordedAt }, [row]).status, 'PENDING')
+})
+test('不以标题子串或重复同名跨配置结果推定关联', () => {
+ assert.equal(resolveBrowserProof({ ...proof, testTitle: '保存' }, [row]).status, 'PENDING')
+ assert.equal(resolveBrowserProof(proof, [row, { ...row }]).status, 'PENDING')
+})
+test('没有浏览器 testTitle 的单元证据保留自身状态', () => {
+ const unit = { title: 'multi-track unit', status: 'PASS', kind: 'unit' }
+ assert.deepEqual(resolveBrowserProof(unit, []), unit)
+})
+test('截图必须与当前通过用例的正式附件字节一致', () => {
+ assert.equal(screenshotProofStatus([{ status: 'PASS', hashMatches: true }]), 'PASS')
+ assert.equal(screenshotProofStatus([{ status: 'PASS', hashMatches: false }]), 'PENDING')
+ assert.equal(screenshotProofStatus([]), 'PENDING')
+})
+test('失败或不稳定用例截图不因同名重拍而标通过', () => {
+ assert.equal(screenshotProofStatus([{ status: 'FAIL', hashMatches: true }]), 'FAIL')
+ assert.equal(screenshotProofStatus([{ status: 'PASS', hashMatches: true }, { status: 'WARN', hashMatches: true }]), 'WARN')
+})
diff --git a/tools/scene-closure-assets.test.mjs b/tools/scene-closure-assets.test.mjs
new file mode 100644
index 0000000..68ccf0d
--- /dev/null
+++ b/tools/scene-closure-assets.test.mjs
@@ -0,0 +1,281 @@
+import assert from 'node:assert/strict'
+import test from 'node:test'
+import { readFileSync } from 'node:fs'
+import { createRequire } from 'node:module'
+
+const requireWeb = createRequire(new URL('../../unreal_tran_web/package.json', import.meta.url))
+const ts = requireWeb('typescript')
+const sourceRoot = new URL('../../unreal_tran_web/src/features/scene-legacy/', import.meta.url)
+const dataUrl = source => `data:text/javascript;base64,${Buffer.from(source).toString('base64')}`
+const compile = file => ts.transpileModule(readFileSync(new URL(file, sourceRoot), 'utf8'), {
+ compilerOptions: { target: ts.ScriptTarget.ES2022, module: ts.ModuleKind.ESNext },
+}).outputText
+const clientUrl = dataUrl(compile('contentApiClient.ts'))
+const { LegacySceneOperationQueue, toPublishedModelAsset } = await import(clientUrl)
+const storeSource = compile('serverAssetStore.ts').replace(/from ['"]\.\/contentApiClient['"]/g, `from '${clientUrl}'`)
+const { SceneServerAssetStore } = await import(dataUrl(storeSource))
+
+const asset = (id, code, overrides = {}) => ({
+ id, code, versionId: '201', name: `${code}.glb`, type: 'MODEL_FILE', status: 'READY',
+ storageUri: `fsvc://test/${code}.glb`, mimeType: 'model/gltf-binary', sizeBytes: 12,
+ sha256: 'a'.repeat(64), metadata: {}, sortOrder: 0, ...overrides,
+})
+const foxReference = (patch = {}) => ({ targetProjectId: '157', targetVersionId: '201',
+ sourceAssetId: '2030', assetCode: 'FOX', resourceUrl: 'content://asset/model/157/201/2030', ...patch })
+
+function fixture(options = {}) {
+ const calls = { versions: [], downloads: [], fallback: [], revoked: [], blobs: [] }
+ const version = { id: '201', projectId: '157', versionNumber: 2, status: 'PUBLISHED',
+ content: { modelResource: { assetCode: 'FOX' } },
+ assets: [asset('2030', 'FOX'), asset('2031', 'BOX')], ...options.version }
+ const project = { id: '10', version: 1, assets: [asset('81', 'LOCAL', { versionId: '11' })],
+ dependencies: [{ relationType: 'SCENE_MODEL', targetProjectId: '157', targetVersionId: '201', required: true }],
+ ...options.project }
+ const client = {
+ projectId: project.id,
+ loadVersion: async (...args) => {
+ calls.versions.push(args)
+ return options.loadVersion ? options.loadVersion(...args) : structuredClone(version)
+ },
+ downloadAsset: async (...args) => {
+ calls.downloads.push(args)
+ return options.downloadAsset ? options.downloadAsset(...args) : new Blob([args.join('/')])
+ },
+ }
+ const fallback = {
+ put: async () => { throw new Error('Unexpected local upload') },
+ hasReferenceSync: value => { calls.fallback.push(['has', value]); return true },
+ acquireObjectUrl: async value => { calls.fallback.push(['acquire', value]); return 'blob:stale-local-cache' },
+ releaseObjectUrl: value => calls.fallback.push(['release', value]),
+ validateReferences: async value => { calls.fallback.push(['validate', value]); return { valid: true, missing: [] } },
+ }
+ const store = new SceneServerAssetStore({ client, project: () => project, fallback,
+ operationQueue: new LegacySceneOperationQueue(), commitUploadedAsset: async () => {},
+ urlApi: {
+ createObjectURL: blob => { calls.blobs.push(blob); return `blob:server-${calls.blobs.length}` },
+ revokeObjectURL: url => calls.revoked.push(url),
+ },
+ })
+ return { store, project, version, calls }
+}
+
+test('fixed model reads the exact version and downloads through its visible scene dependency', async () => {
+ const f = fixture()
+ const reference = foxReference()
+ const url = await f.store.acquireObjectUrl(reference)
+ assert.equal(url, 'blob:server-1')
+ assert.deepEqual(f.calls.versions, [['10', '201']])
+ assert.deepEqual(f.calls.downloads, [['10', '2030']])
+ assert.equal(await f.calls.blobs[0].text(), '10/2030')
+ assert.deepEqual(f.calls.fallback, [])
+ f.store.releaseObjectUrl(reference)
+ assert.deepEqual(f.calls.revoked, [url])
+})
+
+test('an explicit missing code cannot fall back to the first READY asset', async () => {
+ const f = fixture()
+ await assert.rejects(f.store.acquireObjectUrl(foxReference({ assetCode: 'MISSING', sourceAssetId: '', resourceUrl: '' })))
+ assert.deepEqual(f.calls.downloads, [])
+ assert.deepEqual(f.calls.fallback, [])
+})
+
+test('asset code and id must refer to the same model file', async () => {
+ const f = fixture()
+ await assert.rejects(f.store.acquireObjectUrl(foxReference({ assetCode: 'BOX' })))
+ assert.deepEqual(f.calls.downloads, [])
+ assert.deepEqual(f.calls.fallback, [])
+})
+
+test('model URI project, version and asset id must agree with the descriptor', async () => {
+ for (const resourceUrl of ['content://asset/model/158/201/2030',
+ 'content://asset/model/157/202/2030', 'content://asset/model/157/201/2031']) {
+ const f = fixture()
+ await assert.rejects(f.store.acquireObjectUrl(foxReference({ resourceUrl })), undefined, resourceUrl)
+ assert.deepEqual(f.calls.downloads, [])
+ assert.deepEqual(f.calls.fallback, [])
+ }
+})
+
+test('a non-pseudo resource URI must still match the selected fixed model storage URI', async () => {
+ const f = fixture()
+ await assert.rejects(f.store.acquireObjectUrl(foxReference({ resourceUrl: 'fsvc://test/OTHER.glb' })))
+ assert.deepEqual(f.calls.downloads, [])
+ assert.deepEqual(f.calls.fallback, [])
+})
+
+test('fixed model references cannot bypass identity checks with an unrelated built-in path', async () => {
+ const f = fixture()
+ await assert.rejects(f.store.acquireObjectUrl(foxReference({ resourceUrl: '/models/other.glb' })))
+ assert.deepEqual(f.calls.fallback, [])
+})
+
+test('a valid fixed built-in file is verified against its version before using static delivery', async () => {
+ const f = fixture({ version: { assets: [asset('2030', 'FOX', { storageUri: '/models/fox.glb' })] } })
+ const reference = foxReference({ resourceUrl: '/models/fox.glb' })
+ assert.equal((await f.store.validateReferences([reference])).valid, true)
+ await f.store.acquireObjectUrl(reference)
+ assert.deepEqual(f.calls.versions, [['10', '201']])
+ assert.deepEqual(f.calls.downloads, [], 'Built-in files do not use the controlled-file endpoint')
+ assert.equal(f.calls.fallback.filter(call => call[0] === 'acquire').length, 1)
+})
+
+test('a matching controlled storage URI is accepted for the fixed model file', async () => {
+ const f = fixture()
+ assert.equal(await f.store.acquireObjectUrl(foxReference({ resourceUrl: 'fsvc://test/FOX.glb' })), 'blob:server-1')
+ assert.deepEqual(f.calls.downloads, [['10', '2030']])
+})
+
+test('non-READY files and READY images are not accepted as model files', async () => {
+ for (const patch of [{ status: 'UPLOADING' }, { type: 'IMAGE' }]) {
+ const f = fixture({ version: { assets: [asset('2030', 'FOX', patch)] } })
+ await assert.rejects(f.store.acquireObjectUrl(foxReference()))
+ assert.deepEqual(f.calls.downloads, [])
+ assert.deepEqual(f.calls.fallback, [])
+ }
+})
+
+test('a version response from a different project or version cannot supply a matching asset id', async () => {
+ for (const patch of [{ id: '202' }, { projectId: '158' }]) {
+ const f = fixture({ version: patch })
+ await assert.rejects(f.store.acquireObjectUrl(foxReference()))
+ assert.deepEqual(f.calls.downloads, [])
+ }
+})
+
+test('a failed fixed-version lookup is evicted and the same reference can be retried', async () => {
+ let attempt = 0
+ const f = fixture({ loadVersion: async () => {
+ if (++attempt === 1) throw new Error('Injected version timeout')
+ return structuredClone(f.version)
+ } })
+ await assert.rejects(f.store.acquireObjectUrl(foxReference()), /Injected version timeout/)
+ assert.equal(await f.store.acquireObjectUrl(foxReference()), 'blob:server-1')
+ assert.equal(f.calls.versions.length, 2)
+ assert.equal(f.calls.downloads.length, 1)
+ assert.deepEqual(f.calls.fallback, [])
+})
+
+test('a controlled download failure does not return local cached bytes and can retry', async () => {
+ let attempt = 0
+ const f = fixture({ downloadAsset: async () => {
+ if (++attempt === 1) throw new Error('Injected controlled download failure')
+ return new Blob(['fresh controlled GLB'])
+ } })
+ await assert.rejects(f.store.acquireObjectUrl(foxReference({ assetKey: 'old-cached-fox' })), /controlled download failure/)
+ assert.equal(await f.store.acquireObjectUrl(foxReference({ assetKey: 'old-cached-fox' })), 'blob:server-1')
+ assert.equal(await f.calls.blobs[0].text(), 'fresh controlled GLB')
+ assert.deepEqual(f.calls.fallback, [])
+})
+
+test('scene-owned code resolves only against the current scene assets', async () => {
+ const f = fixture()
+ assert.equal(await f.store.acquireObjectUrl({ assetCode: 'LOCAL', resourceUrl: 'content://asset/scene/10/LOCAL' }), 'blob:server-1')
+ assert.deepEqual(f.calls.versions, [])
+ assert.deepEqual(f.calls.downloads, [['10', '81']])
+})
+
+test('scene-owned code does not override a conflicting controlled storage URI', async () => {
+ const f = fixture()
+ await assert.rejects(f.store.acquireObjectUrl({ assetCode: 'LOCAL', resourceUrl: 'fsvc://test/OTHER.glb' }))
+ assert.deepEqual(f.calls.downloads, [])
+ assert.deepEqual(f.calls.fallback, [])
+})
+
+test('copied scene keeps its original pseudo URI but reads its own copied asset by stable code', async () => {
+ const f = fixture()
+ assert.equal(await f.store.acquireObjectUrl({ assetCode: 'LOCAL', resourceUrl: 'content://asset/scene/9/LOCAL' }), 'blob:server-1')
+ assert.deepEqual(f.calls.downloads, [['10', '81']])
+ assert.deepEqual(f.calls.fallback, [])
+})
+
+test('string content URIs are resolved for fixed models and scene-owned uploads', async () => {
+ const f = fixture()
+ await f.store.acquireObjectUrl('content://asset/model/157/201/2030')
+ await f.store.acquireObjectUrl('content://asset/scene/10/LOCAL')
+ assert.deepEqual(f.calls.downloads, [['10', '2030'], ['10', '81']])
+ assert.deepEqual(f.calls.fallback, [])
+})
+
+test('a pooled fixed model URL does not validate another descriptor with a conflicting URI', async () => {
+ const f = fixture()
+ await f.store.acquireObjectUrl(foxReference())
+ await assert.rejects(f.store.acquireObjectUrl(foxReference({ resourceUrl: 'content://asset/model/157/202/2030' })))
+ assert.equal(f.calls.downloads.length, 1)
+})
+
+test('a pooled scene-owned code does not validate another descriptor with a conflicting asset id', async () => {
+ const f = fixture()
+ await f.store.acquireObjectUrl({ assetCode: 'LOCAL', sourceAssetId: '81' })
+ await assert.rejects(f.store.acquireObjectUrl({ assetCode: 'LOCAL', sourceAssetId: '999' }))
+ assert.equal(f.calls.downloads.length, 1)
+})
+
+test('scene-owned ID-only references isolate cached files and reject a missing ID', async () => {
+ for (const idField of ['sourceAssetId', 'assetId']) {
+ const f = fixture({ project: { assets: [asset('81', 'LOCAL'), asset('82', 'OTHER')] } })
+ const first = { [idField]: '81' }, second = { [idField]: '82' }
+ assert.equal(await f.store.acquireObjectUrl(first), 'blob:server-1')
+ await assert.rejects(f.store.acquireObjectUrl({ [idField]: '999' }), /服务端模型资源不存在/)
+ assert.equal(await f.store.acquireObjectUrl(second), 'blob:server-2')
+ assert.deepEqual(f.calls.downloads, [['10', '81'], ['10', '82']])
+ assert.deepEqual(f.calls.fallback, [])
+ f.store.releaseObjectUrl(first)
+ assert.deepEqual(f.calls.revoked, ['blob:server-1'])
+ assert.equal(await f.store.acquireObjectUrl(second), 'blob:server-2')
+ assert.equal(f.calls.downloads.length, 2)
+ f.store.dispose()
+ }
+})
+
+test('ID-only validation retains a missing reference when a valid reference follows it', async () => {
+ for (const idField of ['sourceAssetId', 'assetId']) {
+ const f = fixture()
+ const result = await f.store.validateReferences([
+ { [idField]: '999', sourceName: 'missing-file' },
+ { [idField]: '81', sourceName: 'valid-file' },
+ ])
+ assert.equal(result.valid, false)
+ assert.deepEqual(result.missing, ['missing-file'])
+ assert.deepEqual(f.calls.fallback, [])
+ assert.deepEqual(f.calls.downloads, [])
+ f.store.dispose()
+ }
+})
+
+test('validation does not deduplicate conflicting scene-owned descriptors into a valid one', async () => {
+ const f = fixture()
+ const result = await f.store.validateReferences([{ assetCode: 'LOCAL', sourceAssetId: '999' },
+ { assetCode: 'LOCAL', sourceAssetId: '81' }])
+ assert.equal(result.valid, false)
+ assert.equal(result.missing.length, 1)
+ assert.deepEqual(f.calls.fallback, [])
+})
+
+test('server-managed invalid references cannot pass using a populated fallback cache', async () => {
+ const f = fixture()
+ const result = await f.store.validateReferences([{ assetCode: 'MISSING', assetKey: 'old-cached-fox' }])
+ assert.equal(result.valid, false)
+ assert.deepEqual(f.calls.fallback, [])
+})
+
+test('plain local legacy references retain their fallback behavior', async () => {
+ const f = fixture()
+ assert.equal(await f.store.acquireObjectUrl({ assetKey: 'local-only-legacy' }), 'blob:stale-local-cache')
+ assert.equal(f.calls.fallback.length, 1)
+ assert.deepEqual(f.calls.downloads, [])
+})
+
+test('published model catalogue refuses an explicit missing code instead of choosing the first GLB', () => {
+ const f = fixture({ version: { content: { modelResource: { assetCode: 'MISSING' } } } })
+ assert.equal(toPublishedModelAsset({ projectId: '157', versionId: '201' }, f.version), null)
+})
+
+test('published model catalogue selects only a READY MODEL_FILE and preserves its fixed identity', () => {
+ const f = fixture({ version: { assets: [asset('99', 'FOX', { type: 'IMAGE' }),
+ asset('100', 'FOX', { status: 'UPLOADING' }), asset('2030', 'FOX')] } })
+ const result = toPublishedModelAsset({ projectId: '157', versionId: '201' }, f.version)
+ assert.equal(result.assetCode, 'FOX')
+ assert.equal(result.sourceAssetId, '2030')
+ assert.equal(result.targetVersionId, '201')
+ assert.equal(result.resourceUrl, 'content://asset/model/157/201/2030')
+})
diff --git a/tools/scene-closure-engine.test.mjs b/tools/scene-closure-engine.test.mjs
new file mode 100644
index 0000000..3ca2a0e
--- /dev/null
+++ b/tools/scene-closure-engine.test.mjs
@@ -0,0 +1,534 @@
+import assert from 'node:assert/strict'
+import test from 'node:test'
+import { readFileSync } from 'node:fs'
+import { createRequire } from 'node:module'
+import * as historyModule from '../../unreal_tran_web/src/features/guide-legacy/demo-src/scene-history.js'
+import { chooseSceneSelection } from '../../unreal_tran_web/src/features/guide-legacy/demo-src/scene-selection.js'
+import { findDuplicateSceneBindingIds } from '../../unreal_tran_web/src/features/guide-legacy/demo-src/scene-validation.js'
+import * as rendering from '../../unreal_tran_web/src/features/guide-legacy/demo-src/scene-rendering-profile.js'
+import * as environment from '../../unreal_tran_web/src/features/guide-legacy/demo-src/scene-environment.js'
+import { SceneSelectionHighlight } from '../../unreal_tran_web/src/features/guide-legacy/demo-src/scene-selection-highlight.js'
+
+const requireWeb = createRequire(new URL('../../unreal_tran_web/package.json', import.meta.url))
+const requirePrototype = createRequire(new URL('../../unreal_tran/package.json', import.meta.url))
+const THREE = requireWeb('three')
+const { Window } = requirePrototype('happy-dom')
+const { SceneHistory } = historyModule
+const source = readFileSync(new URL('../../unreal_tran_web/src/features/guide-legacy/demo-src/scene-editor.js', import.meta.url), 'utf8')
+// Execute the shipped class methods with real Three.js objects and a DOM. Only
+// WebGL construction / external GLB transport are replaced in these Node tests.
+const body = source.slice(source.indexOf('const STORAGE_KEY'), source.indexOf('/** 当前挂载的实例'))
+
+function fixture() {
+ const window = new Window()
+ const document = window.document
+ const bindings = {
+ THREE, window, document, ...historyModule, chooseSceneSelection,
+ ...rendering, ...environment, findDuplicateSceneBindingIds,
+ modelUrl: value => value, listPublishedModelAssets: () => [],
+ refreshSceneModelBounds: root => { root.updateMatrixWorld(true); return new THREE.Box3().setFromObject(root) },
+ parseSceneScale: (value, current) => Number(value) > 0 ? Number(value) : current,
+ formatSceneScale: value => String(value),
+ applyModelEditorOverrides: () => ({}),
+ isBridgeErectorVehicleReference: () => false,
+ createGarageEnclosureController: () => ({}),
+ createGarageEnclosureControls: () => ({ attach() {} }),
+ assetStore: {}, setTimeout, clearTimeout,
+ }
+ const { WorkshopSceneEditor, SCENE_PRESETS } = new Function(...Object.keys(bindings), `${body}; return {WorkshopSceneEditor, SCENE_PRESETS}`)(...Object.values(bindings))
+ const editor = Object.create(WorkshopSceneEditor.prototype)
+ const root = document.createElement('div')
+ document.body.append(root)
+ Object.assign(editor, {
+ root, context: { remoteControlled: true }, currentPreset: SCENE_PRESETS.find(p => p.id === 'workshop-1'),
+ readySettled: true, readOnly: false, canPublish: true, pendingLoads: 0, loadGeneration: 0,
+ objects: [], selected: null, assetSerial: 0, snap: true, dirty: false, changeRevision: 0,
+ history: new SceneHistory(), historyInitialized: false, historyBusy: false,
+ scene: new THREE.Scene(), camera: new THREE.PerspectiveCamera(),
+ contentRoot: new THREE.Group(), environmentRoot: new THREE.Group(),
+ controls: { target: new THREE.Vector3(), update() {} },
+ transform: { attach() {}, detach() {}, setMode() {} },
+ renderer: { toneMappingExposure: 1 },
+ ambient: new THREE.HemisphereLight(), sun: new THREE.DirectionalLight(),
+ grid: { visible: true }, selectionHighlight: { setObject() {}, clear() {} },
+ assetStore: { hasReferenceSync: () => true },
+ garageEnclosure: { setMode() {}, unregisterTree() {} },
+ messages: [], toast(message) { this.messages.push(message) },
+ })
+ editor.renderShell()
+ editor.cacheElements()
+ return { editor, window, document, WorkshopSceneEditor, SCENE_PRESETS }
+}
+
+function keyboard(target, key, options = {}) {
+ return { target, key, ctrlKey: false, metaKey: false, altKey: false, shiftKey: false,
+ ...options, prevented: false, preventDefault() { this.prevented = true } }
+}
+
+test('scene granular update permission enables editing without the retired shared code', async () => {
+ const f = fixture()
+ class WithoutWebGL extends f.WorkshopSceneEditor {
+ renderShell() {} cacheElements() {} applyAccessMode() {} initThree() {} bindEvents() {} animate() {} renderAssets() {}
+ async loadPreset() {}
+ }
+ const editor = new WithoutWebGL(f.editor.root, { storage: f.window.localStorage, can: code => code === 'content.scene.update' })
+ await editor.ready
+ assert.equal(editor.readOnly, false)
+ assert.equal(editor.canPublish, false)
+})
+
+test('typing undo/redo stays inside text controls and inactive cached editors ignore global shortcuts', () => {
+ const { editor, document } = fixture()
+ const input = document.createElement('input')
+ const calls = []
+ editor.undo = () => calls.push('undo'); editor.redo = () => calls.push('redo')
+ editor.deleteSelected = () => calls.push('delete'); editor.setTransform = mode => calls.push(mode)
+ for (const key of ['z', 'y']) editor.handleKeyboard(keyboard(input, key, { ctrlKey: true }))
+ assert.deepEqual(calls, [])
+ editor.saveProject = () => calls.push('save')
+ const save = keyboard(input, 's', { ctrlKey: true })
+ editor.handleKeyboard(save)
+ assert.deepEqual(calls, ['save'])
+ assert.equal(save.prevented, true)
+ calls.length = 0
+ editor.paused = true
+ for (const key of ['Delete', 'w', 'e', 'r', 'z']) editor.handleKeyboard(keyboard(document.body, key, { ctrlKey: key === 'z' }))
+ assert.deepEqual(calls, [])
+})
+
+test('preview locks mutation controls and Delete while Escape restores editing', () => {
+ const { editor, document } = fixture()
+ let deletes = 0
+ editor.deleteSelected = () => deletes++
+ editor.togglePreview()
+ editor.handleKeyboard(keyboard(document.body, 'Delete'))
+ assert.equal(deletes, 0)
+ assert.equal(editor.writeUnavailable(), true)
+ assert.equal(editor.root.querySelector('[data-action="open"]').disabled, true)
+ editor.handleKeyboard(keyboard(document.body, 'Escape'))
+ assert.equal(editor.previewing, false)
+ assert.equal(editor.writeUnavailable(), false)
+})
+
+test('readonly templates have disabled affordances and cannot replace the scene', () => {
+ const { editor } = fixture()
+ editor.readOnly = true
+ editor.renderPresets()
+ editor.applyAccessMode()
+ const card = editor.root.querySelector('[data-preset-id="terrain-1"]')
+ let changed = false
+ editor.loadPreset = () => { changed = true }
+ editor.handleClick({ target: card })
+ assert.equal(changed, false)
+ assert.equal(card.disabled, true)
+})
+
+test('top view returns to the previous perspective through the visible perspective button', () => {
+ const { editor } = fixture()
+ editor.camera.position.set(12, 9, 18)
+ editor.controls.target.set(2, 1, 3)
+ editor.setTopView()
+ editor.setTopView()
+ editor.handleClick({ target: editor.root.querySelector('[data-camera="perspective"]') })
+ assert.deepEqual(editor.camera.position.toArray(), [12, 9, 18])
+ assert.deepEqual(editor.controls.target.toArray(), [2, 1, 3])
+})
+
+test('explicitly cleared bindings and display name survive serialized restore', () => {
+ const { editor } = fixture()
+ const object = new THREE.Group()
+ object.userData = { displayName: 'old name', deviceId: 'OLD-DEVICE', modelId: 'OLD-MODEL' }
+ editor.applySerializedObject(object, { name: '', deviceId: '', modelId: '' })
+ assert.equal(object.userData.displayName, '')
+ assert.equal(object.userData.deviceId, '')
+ assert.equal(object.userData.modelId, '')
+})
+
+test('binding table reports the actual semantic value without claiming missing bindings are complete', () => {
+ const { editor } = fixture()
+ const object = new THREE.Group()
+ object.userData = { displayName: '区域', deviceId: '', modelId: '', semantic: '安全区域' }
+ editor.objects = [object]
+ editor.renderBottom('binding')
+ assert.match(editor.bottomContent.textContent, /安全区域/)
+ assert.doesNotMatch(editor.bottomContent.textContent, /已绑定/)
+ editor.renderBottom('logs')
+ assert.doesNotMatch(editor.bottomContent.textContent, /本地存储/)
+})
+
+test('real Three ray hits on hidden descendants are excluded before selecting a root or part', () => {
+ const root = new THREE.Group()
+ root.userData.assetRoot = true
+ const hidden = new THREE.Group()
+ hidden.visible = false
+ const mesh = new THREE.Mesh(new THREE.BoxGeometry(), new THREE.MeshBasicMaterial())
+ hidden.add(mesh); root.add(hidden)
+ root.updateMatrixWorld(true)
+ const ray = new THREE.Raycaster(new THREE.Vector3(0, 0, 3), new THREE.Vector3(0, 0, -1))
+ const hits = ray.intersectObjects([root], true)
+ assert.ok(hits.length > 0, 'Three returns intersections even when an ancestor is hidden')
+ assert.equal(chooseSceneSelection(hits, [root]) === null, true)
+ hidden.visible = true
+ assert.equal(chooseSceneSelection(hits, [root]), root)
+ mesh.userData.sceneDeleted = true
+ assert.equal(chooseSceneSelection(hits, [root]) === null, true)
+})
+
+test('deleting a root disposes its geometry and materials after clearing selection', () => {
+ const { editor } = fixture()
+ const mesh = new THREE.Mesh(new THREE.BoxGeometry(), new THREE.MeshBasicMaterial())
+ mesh.userData = { sceneId: 'mesh', displayName: 'mesh' }
+ editor.objects = [mesh]; editor.contentRoot.add(mesh); editor.selected = mesh
+ let geometry = 0, material = 0
+ mesh.geometry.addEventListener('dispose', () => geometry++)
+ mesh.material.addEventListener('dispose', () => material++)
+ editor.deleteSelected()
+ assert.equal(editor.objects.length, 0)
+ assert.equal(geometry, 1)
+ assert.equal(material, 1)
+})
+
+test('loading and history restoration preserve an authored camera rather than auto-focusing a vehicle', async () => {
+ const { editor } = fixture()
+ const project = { presetId: 'workshop-1', objects: [], rendering: { camera: { position: [7, 8, 9], target: [1, 2, 3] } } }
+ editor.context.readDocument = () => project
+ editor.focusPublishedVehicle = () => editor.camera.position.set(99, 99, 99)
+ await editor.loadPreset('workshop-1', { initial: true })
+ assert.deepEqual(editor.camera.position.toArray(), [7, 8, 9])
+ editor.camera.position.set(11, 11, 11)
+ await editor.restoreHistoryState({ project, selectedSceneId: '', dirty: false })
+ assert.deepEqual(editor.camera.position.toArray(), [7, 8, 9])
+})
+
+test('save B then undo A stays dirty, and redo B returns to the actual saved baseline', async () => {
+ const { editor } = fixture()
+ const object = editor.addAsset('equipment', new THREE.Vector3(), true)
+ editor.historyInitialized = true
+ editor.history.reset(editor.captureHistoryState())
+ object.position.x = 4
+ editor.markDirty('move to B')
+ const writes = []
+ editor.context.saveDocument = async doc => { writes.push(structuredClone(doc)) }
+ await editor.saveProject()
+ assert.equal(editor.dirty, false)
+ await editor.undo()
+ assert.equal(object.position.x, 0)
+ assert.equal(editor.dirty, true, 'A is no longer the server version after B was saved')
+ assert.equal(editor.root.querySelector('#se-save-state').textContent, '未保存')
+ await editor.redo()
+ assert.equal(object.position.x, 4)
+ assert.equal(editor.dirty, false)
+ assert.equal(writes.length, 1, 'history navigation never saves by itself')
+})
+
+test('new resource bindings do not reuse a saved serial after earlier objects were deleted', () => {
+ const { editor } = fixture()
+ editor.restoreObject({ type: 'equipment', name: 'kept', deviceId: 'EQ-002', modelId: 'MODEL-EQUIPMENT-02' })
+ const added = editor.addAsset('equipment', new THREE.Vector3(), true)
+ assert.notEqual(added.userData.deviceId, 'EQ-002')
+ assert.notEqual(added.userData.modelId, 'MODEL-EQUIPMENT-02')
+})
+
+test('formal new delegates to the host even for create-only sessions and leaves this document intact', async () => {
+ const { editor } = fixture()
+ editor.readOnly = true; editor.canCreate = true
+ let created = 0
+ editor.context.onNew = async () => { created++ }
+ editor.loadPreset = () => { throw new Error('must not reset the current document') }
+ editor.applyAccessMode()
+ assert.equal(editor.root.querySelector('[data-action="new"]').disabled, false)
+ await editor.newProject()
+ assert.equal(created, 1)
+ editor.pendingLoads = 1
+ await editor.newProject()
+ assert.equal(created, 1)
+})
+
+test('cancelled async template replacement retains the existing scene and history', async () => {
+ const { editor } = fixture()
+ editor.dirty = true
+ let prompts = 0, clears = 0
+ editor.context.confirmDiscard = async () => { prompts++; return false }
+ editor.clearContent = () => { clears++ }
+ await editor.loadPreset('terrain-1')
+ assert.equal(prompts, 1)
+ assert.equal(clears, 0)
+ assert.equal(editor.currentPreset.id, 'workshop-1')
+})
+
+test('a failed replacement GLB keeps the previous live scene rather than clearing an unsaved draft', async () => {
+ const { editor } = fixture()
+ const kept = editor.addAsset('equipment', new THREE.Vector3(3, 0, 1), true)
+ editor.dirty = true
+ editor.context.confirmDiscard = async () => true
+ editor.context.readDocument = () => null
+ editor.placeAsset = async () => { throw new Error('fixture model unavailable') }
+ try { await editor.loadPreset('terrain-1') } catch {}
+ assert.equal(editor.currentPreset.id, 'workshop-1')
+ assert.equal(editor.objects.length, 1)
+ assert.equal(editor.objects[0] === kept, true)
+ assert.equal(kept.parent === editor.contentRoot, true)
+ assert.equal(editor.dirty, true)
+})
+
+test('failed undo restoration retains live objects and keeps the undo target available for retry', async () => {
+ const { editor } = fixture()
+ const kept = editor.addAsset('equipment', new THREE.Vector3(), true)
+ editor.historyInitialized = true
+ const earlier = editor.captureHistoryState()
+ earlier.project.objects.push({ sceneId: 'missing', type: 'imported', name: 'missing.glb' })
+ editor.history.reset(earlier)
+ editor.markDirty('delete missing asset')
+ editor.restoreImportedObject = async () => { throw new Error('fixture model unavailable') }
+ await editor.undo()
+ assert.equal(editor.objects[0] === kept, true)
+ assert.equal(editor.history.canUndo, true)
+ assert.equal(editor.history.canRedo, false)
+})
+
+test('validation text and imported object IDs cannot create executable DOM markup', () => {
+ const { editor } = fixture()
+ editor.verifyScene = () => [{ ok: false, label: 'duplicate', detail: ' ' }]
+ editor.renderBottom('validation')
+ assert.equal(editor.bottomContent.querySelector('img') === null, true)
+ assert.match(editor.bottomContent.textContent, / item.label === '对象设备编号完整').ok, false)
+ assert.ok(editor.bottomContent.querySelector('.warn'))
+ editor.renderBottom('binding')
+ const name = editor.root.querySelector('[data-inspector-field="name"]')
+ name.value = 'Live edited name'
+ editor.handleInput({ target: name })
+ assert.match(editor.bottomContent.textContent, /Live edited name/)
+ assert.equal(editor.root.querySelector('[data-bottom-tab="binding"]').classList.contains('active'), true)
+})
+
+test('model source navigation locates the published owner and falls back without source permission', () => {
+ const { editor } = fixture()
+ const root = editor.addAsset('equipment', new THREE.Vector3(), true)
+ root.userData.targetProjectId = '157'
+ const part = new THREE.Group()
+ part.userData = { modelPartTarget: true, sceneOwnerId: root.userData.sceneId }
+ editor.selected = part
+ const requests = []
+ editor.context.navigate = (...args) => requests.push(args)
+ const button = editor.root.querySelector('[data-action="open-model-editor"]')
+ editor.handleClick({ target: button })
+ assert.deepEqual(requests.pop(), ['editor', '?edit=157'])
+ editor.context.can = () => false
+ editor.handleClick({ target: button })
+ assert.deepEqual(requests.pop(), ['editor', ''])
+})
+
+test('gizmo scaling across its pivot follows the same strictly positive scale contract as inspector edits', () => {
+ const { editor } = fixture()
+ const model = editor.addAsset('equipment', new THREE.Vector3(), true)
+ editor.selectObject(model)
+ editor.transform.mode = 'scale'
+ model.scale.set(-1.25, 0, 0.034)
+ editor.handleTransformChange()
+ assert.deepEqual(model.scale.toArray(), [0.000001, 0.000001, 0.034])
+ assert.equal(editor.root.querySelector('[data-inspector-field="sx"]').value, '0.000001')
+ assert.equal(editor.dirty, true)
+ assert.equal(editor.transformChanged, true)
+})
+
+test('default history caps at 60 snapshots, stops at either edge and discards redo only after a new branch', () => {
+ const history = new SceneHistory()
+ assert.equal(history.undo(), null)
+ assert.equal(history.redo(), null)
+ history.reset({ revision: 0 })
+ for (let revision = 1; revision <= 75; revision++) history.push({ revision }, `edit ${revision}`)
+ assert.equal(history.past.length, 60)
+ let oldest
+ for (let i = 0; i < 59; i++) oldest = history.undo()
+ assert.equal(oldest.state.revision, 16)
+ assert.equal(history.undo(), null)
+ assert.equal(history.canUndo, false)
+ for (let i = 0; i < 59; i++) assert.ok(history.redo())
+ assert.equal(history.redo(), null)
+ assert.equal(history.past.at(-1).state.revision, 75)
+ const previous = history.undo()
+ previous.state.revision = -1
+ assert.equal(history.past.at(-1).state.revision, 74)
+ assert.equal(history.push({ revision: 74 }), false)
+ assert.equal(history.canRedo, true)
+ history.push({ revision: 100 }, 'new branch')
+ assert.equal(history.canRedo, false)
+ assert.equal(history.redo(), null)
+})
+
+const truckPartDefinitions = [{ interactionId: 'interaction-source-wheel', editorId: 'wheel-editor-id', nodeName: 'Wheel' }]
+function addTruckInstance(editor, id) {
+ const root = new THREE.Group()
+ root.userData = { sceneId: id, assetType: 'imported', modelEditorAssetId: 'truck-published', displayName: id }
+ const part = new THREE.Mesh(new THREE.BoxGeometry(), new THREE.MeshStandardMaterial())
+ part.name = 'Wheel'; part.userData = { interactionId: 'interaction-source-wheel', editorId: 'wheel-editor-id' }
+ root.add(part); editor.contentRoot.add(root); editor.objects.push(root)
+ return { root, part }
+}
+
+test('two published instances have unique tree part IDs and editing the second persists independently after reopen', () => {
+ const { editor } = fixture()
+ const first = addTruckInstance(editor, 'TRUCK-A'), second = addTruckInstance(editor, 'TRUCK-B')
+ editor.registerModelPartTargets(first.root, truckPartDefinitions)
+ editor.registerModelPartTargets(second.root, truckPartDefinitions)
+ assert.equal(first.part.userData.sceneId, 'scene-part:TRUCK-A:interaction-source-wheel')
+ assert.equal(second.part.userData.sceneId, 'scene-part:TRUCK-B:interaction-source-wheel')
+ assert.equal(second.part.userData.interactionId, first.part.userData.interactionId)
+ assert.equal(second.part.userData.editorId, first.part.userData.editorId)
+ editor.renderTree()
+ const secondRow = editor.root.querySelector(`[data-object-id="${second.part.userData.sceneId}"]`)
+ editor.handleClick({ target: secondRow })
+ assert.equal(editor.selected === second.part, true)
+ const input = editor.root.querySelector('[data-inspector-field="x"]')
+ input.value = '3.75'; editor.handleInput({ target: input }); editor.handleChange({ target: input })
+ assert.equal(second.part.position.x, 3.75)
+ assert.equal(first.part.position.x, 0)
+ const saved = [first, second].map(instance => editor.serializeObject(instance.root))
+ const reopened = fixture().editor
+ reopened.publishedModelAsset = () => ({ parts: truckPartDefinitions })
+ for (const item of saved) reopened.applySerializedObject(addTruckInstance(reopened, item.sceneId).root, item)
+ assert.equal(reopened.findSceneSelectionById(first.part.userData.sceneId).position.x, 0)
+ assert.equal(reopened.findSceneSelectionById(second.part.userData.sceneId).position.x, 3.75)
+ assert.deepEqual(reopened.objects.map(root => reopened.captureModelPartTargets(root)), saved.map(item => item.modelPartTargets))
+})
+
+test('legacy duplicate instance IDs migrate only later owners in document order despite reversed GLB completion', async () => {
+ const makeSaved = (id, x) => ({ sceneId: id, type: 'imported', name: id, modelEditorAssetId: 'truck-published',
+ modelPartTargets: [{ sceneId: 'legacy-wheel', interactionId: 'interaction-source-wheel', sourceInteractionId: 'interaction-source-wheel', editorId: 'wheel-editor-id', sourceNodeName: 'Wheel' }],
+ partOverrides: { 'interaction-source-wheel': { sceneId: 'legacy-wheel', sourceInteractionId: 'interaction-source-wheel', position: [x, 0, 0] } } })
+ const original = [makeSaved('TRUCK-A', 1), makeSaved('TRUCK-B', 2)]
+ const { editor } = fixture()
+ editor.publishedModelAsset = () => ({ parts: truckPartDefinitions })
+ editor.restoreObject = async item => {
+ await new Promise(resolve => setTimeout(resolve, item.sceneId === 'TRUCK-A' ? 20 : 1))
+ const instance = addTruckInstance(editor, item.sceneId)
+ editor.applySerializedObject(instance.root, item)
+ return instance.root
+ }
+ await editor.restoreSceneObjects(original, editor.loadGeneration)
+ assert.equal(editor.findSceneSelectionById('legacy-wheel').userData.sceneOwnerId, 'TRUCK-A')
+ assert.equal(editor.findSceneSelectionById('scene-part:TRUCK-B:interaction-source-wheel').position.x, 2)
+ assert.equal(original[1].modelPartTargets[0].sceneId, 'legacy-wheel', 'the source document is not modified in place')
+ const persisted = editor.objects.map(root => editor.serializeObject(root))
+ assert.deepEqual(editor.normalizeModelPartSceneIds(persisted), persisted, 'migration is stable after saving in completion order')
+ const single = fixture().editor
+ assert.deepEqual(single.normalizeModelPartSceneIds([original[0]]), [original[0]], 'legacy single-instance references remain unchanged')
+})
+
+test('successful parallel restores retain document order after delayed GLBs and split-object batches finish out of order', async () => {
+ const { editor } = fixture()
+ const kept = editor.addAsset('equipment', new THREE.Vector3(), true)
+ const completed = []
+ editor.restoreObject = async item => {
+ await new Promise(resolve => setTimeout(resolve, item.delay))
+ completed.push(item.id)
+ const roots = item.parts.map(id => {
+ const root = new THREE.Group(); root.userData = { sceneId: id, assetType: 'imported' }
+ editor.contentRoot.add(root); editor.objects.push(root); return root
+ })
+ return item.parts.length === 1 ? roots[0] : roots
+ }
+ await editor.restoreSceneObjects([
+ { id: 'slow-first', parts: ['TRUCK-A'], delay: 30 },
+ { id: 'fast-second', parts: ['TRUCK-B'], delay: 1 },
+ { id: 'split-third', parts: ['SPLIT-1', 'SPLIT-2'], delay: 10 },
+ ], editor.loadGeneration)
+ assert.deepEqual(completed, ['fast-second', 'split-third', 'slow-first'])
+ assert.deepEqual(editor.objects.map(root => root.userData.sceneId), [kept.userData.sceneId, 'TRUCK-A', 'TRUCK-B', 'SPLIT-1', 'SPLIT-2'])
+ assert.deepEqual(editor.serialize().objects.filter(item => !environment.isSceneEnvironmentSnapshotObject(item)).map(item => item.sceneId).filter(Boolean), editor.objects.map(root => root.userData.sceneId))
+})
diff --git a/tools/scene-cover-api.test.mjs b/tools/scene-cover-api.test.mjs
index b1113f6..83f1244 100644
--- a/tools/scene-cover-api.test.mjs
+++ b/tools/scene-cover-api.test.mjs
@@ -2,6 +2,7 @@ import assert from 'node:assert/strict'
import test from 'node:test'
import { readFileSync } from 'node:fs'
import { createRequire } from 'node:module'
+import { sceneDocumentSignature } from '../../unreal_tran_web/src/features/guide-legacy/demo-src/scene-history.js'
const requireWeb = createRequire(new URL('../../unreal_tran_web/package.json', import.meta.url))
const ts = requireWeb('typescript')
@@ -163,7 +164,7 @@ test('the shared operation queue serializes cover upload and final PUT before th
const editorSource = readFileSync(new URL('../../unreal_tran_web/src/features/guide-legacy/demo-src/scene-editor.js', import.meta.url), 'utf8').replace(/\r\n?/g, '\n')
const saveMethod = editorSource.slice(editorSource.indexOf(' async saveProject() {'), editorSource.indexOf(' /**\n * 读取已存场景')).trim()
-const { saveProject } = new Function(`return ({ ${saveMethod} })`)()
+const { saveProject } = new Function('sceneDocumentSignature', `return ({ ${saveMethod} })`)(sceneDocumentSignature)
function editorFixture(saveDocument) {
return {
readySettled: true, pendingLoads: 0, readOnly: false, destroyed: false, saving: false, publishing: false, dirty: false, changeRevision: 0,
@@ -227,7 +228,7 @@ function leaveEditor(editor, {
operations = new LegacySceneOperationQueue(), hostLoading = false,
confirm = async () => undefined, preserveOnDiscard = false,
} = {}) {
- return new Function('editorHandle', 'ElMessageBox', 'ElMessage', 'operationQueue', 'loading', 'preserveOnDiscard', `${leaveCompiled}\nreturn confirmLeave({ preserveOnDiscard })`)(
+ return new Function('editorHandle', 'ElMessageBox', 'ElMessage', 'operationQueue', 'loading', 'preserveOnDiscard', `const creationNavigation = false, creatingProject = false;\n${leaveCompiled}\nreturn confirmLeave({ preserveOnDiscard })`)(
editor,
{ confirm },
{ error: message => editor.toast(message, 'error'), info: message => editor.toast(message, 'info') },
diff --git a/tools/scene-editor-closure-live.mjs b/tools/scene-editor-closure-live.mjs
new file mode 100644
index 0000000..d862ccd
--- /dev/null
+++ b/tools/scene-editor-closure-live.mjs
@@ -0,0 +1,85 @@
+import fs from 'node:fs';
+import path from 'node:path';
+import crypto from 'node:crypto';
+import assert from 'node:assert/strict';
+import { fileURLToPath } from 'node:url';
+import { login, root } from './role-permissions-live.mjs';
+export { login };
+export const work = path.resolve(root, '../.codex-tmp/scene-closure-20260906');
+export const report = path.join(root, 'ute2e/reports/scene-editor-closure-20260906');
+export const fixturePath = path.join(work, 'fixtures.json');
+fs.mkdirSync(work, { recursive: true }); fs.mkdirSync(report, { recursive: true });
+export const fixture = () => JSON.parse(fs.readFileSync(fixturePath, 'utf8'));
+export function writeFixture(value) {
+ const lock = `${fixturePath}.lock`;
+ let handle;
+ for (let attempt=0; attempt<100; attempt++) {
+ try { handle=fs.openSync(lock,'wx'); break; }
+ catch (error) { if(error.code!=='EEXIST')throw error; Atomics.wait(new Int32Array(new SharedArrayBuffer(4)),0,0,20); }
+ }
+ if(handle===undefined)throw new Error('Scene fixture registry is busy');
+ try {
+ const existing=fs.existsSync(fixturePath)?fixture():{};
+ const merged={...existing,...value};
+ for(const key of ['projects','extraUsers','roleIds']) {
+ const entries=[...(existing[key]||[]),...(value[key]||[])];
+ if(entries.length)merged[key]=[...new Map(entries.map(item=>[typeof item==='object'?String(item.id):String(item),item])).values()];
+ }
+ fs.writeFileSync(fixturePath,JSON.stringify(merged,null,2));
+ } finally { fs.closeSync(handle); fs.unlinkSync(lock); }
+}
+export const hash = value => crypto.createHash('sha256').update(JSON.stringify(value)).digest('hex');
+export async function call(endpoint, token, body, method = body === undefined ? 'GET' : 'POST') {
+ const response = await fetch(`http://127.0.0.1:6180/api/${endpoint}`, {
+ method, headers: { 'Content-Type': 'application/json', ...(token ? { Authorization: `Bearer ${token}` } : {}) },
+ body: body === undefined ? undefined : JSON.stringify(body), signal: AbortSignal.timeout(120000),
+ });
+ const value = await response.json();
+ return { status: response.status, code: value.code, data: value.data, message: value.message };
+}
+export async function teacherLogin() {
+ const user = fixture().user;
+ const response = await call('auth/v1/auth/login', null, { userId: user.id, password: user.password, roleCode: 'teacher', departmentId: user.departmentId, rememberMe: false });
+ assert.equal(response.status, 200, 'Scene review teacher signs in');
+ return response.data;
+}
+export function registerProject(project) {
+ const f = fixture();
+ if (!f.projects.some(p => p.id === project.id)) f.projects.push({ id: project.id, name: project.name, code: project.code, ownerId: project.ownerId });
+ writeFixture(f);
+}
+export async function freshScene(token, key = 'fox', suffix = '') {
+ const f = fixture(), source = f.originals[key];
+ const result = await call(`tran/v1/content/projects/${source.id}/copy`, token, { name: `场景闭环-${key}-${suffix}-${Date.now()}`, version: source.version });
+ assert.equal(result.status, 200, `Copy scene: ${result.message}`);
+ registerProject(result.data.project);
+ return result.data;
+}
+async function prepare() {
+ if (fs.existsSync(fixturePath) && !fixture().cleanedAt && fixture().user) throw new Error('Scene fixtures already active; reuse existing fixtures');
+ const session = await login('root'), token = session.accessToken;
+ const f = { createdAt: new Date().toISOString(), originals: {}, projects: [], user: null };
+ writeFixture(f);
+ try {
+ for (const [key, id] of [['fox', '160'], ['truck', '159'], ['foxModel', '157'], ['truckModel', '156']]) {
+ const original = await call(`tran/v1/content/projects/${id}`, token);
+ assert.equal(original.status, 200);
+ f.originals[key] = { id, name: original.data.project.name, version: original.data.project.version, hash: hash(original.data) };
+ fs.writeFileSync(path.join(work, `original-${key}.json`), JSON.stringify(original.data, null, 2));
+ writeFixture(f);
+ }
+ const me = (await call('auth/v1/auth/me', token)).data;
+ const roles = (await call('auth/v1/roles/all', token)).data;
+ const teacher = (roles.records || roles).find(r => r.code === 'teacher');
+ const password = `Sc!${crypto.randomBytes(7).toString('hex')}`;
+ const made = await call('auth/v1/users', token, {
+ username: `rv_scene_${crypto.randomBytes(4).toString('hex')}`, displayName: '场景编辑器闭环验收',
+ departmentId: me.departmentId, roleIds: [teacher.id], defaultRoleId: teacher.id,
+ enabled: true, password, remark: 'UTE2E-SCENE-CLOSURE-20260906',
+ });
+ assert.equal(made.status, 200, `Temporary teacher: ${made.message}`);
+ f.user = { ...made.data, password }; writeFixture(f);
+ console.log(JSON.stringify({ prepared: true, originals: Object.fromEntries(Object.entries(f.originals).map(([k,v])=>[k,v.id])) }));
+ } finally { await call('auth/v1/auth/logout', token, {}).catch(()=>{}); }
+}
+if (process.argv[1] === fileURLToPath(import.meta.url)) prepare().catch(e => { console.error(e.message); process.exitCode = 1; });
diff --git a/tools/scene-editor-evidence-bindings.mjs b/tools/scene-editor-evidence-bindings.mjs
new file mode 100644
index 0000000..b13bacd
--- /dev/null
+++ b/tools/scene-editor-evidence-bindings.mjs
@@ -0,0 +1,57 @@
+// Evidence metadata only. Tests still perform every assertion and record their
+// actual runtime title/time; this file does not turn planned coverage into PASS.
+export const sceneEvidenceBindings = {
+ 'fox-properties-cover': { featureIds: ['project.load','project.title','project.save','selection.tree','property.name','property.visible','property.operable','property.position','property.rotation','property.scale','property.device-id','property.model-id','property.semantic','part.source','panel.binding','cover.capture','cover.atomic'], scope: '真实字段输入与清空、六个语义选项、蒙皮保护、源模型可见,正文/资产/封面保存和刷新;不把原件旧模板组合当新模板结果。' },
+ 'truck-part-history': { featureIds: ['project.save-key','part.transform','part.soft-delete','history.undo','history.redo','property.name','view.enclosure-full','view.enclosure-xray','view.enclosure-hidden'], scope: '实际部件变换和软删除、按钮/键盘历史、保存基线与输入框自身撤销;双实例隔离及车库围护三种模式真实点击、导出和隐藏后保存刷新。' },
+ 'new-project': { featureIds: ['project.new'], scope: '取消命名、创建503保留原工程、创建成功新ID、保存刷新与原工程内容核对。' },
+ readonly: { featureIds: ['permission.readonly','permission.update','permission.publish','preview.enter','preview.exit','preview.lock','json.export'], scope: '只读按钮/模板/资源禁写、蒙皮浏览、运行预览与快捷键不变更对象,直接PUT拒绝;具体搜索/鼠标覆盖另见视口用例。' },
+ 'json-glb-roundtrip': { featureIds: ['json.export','json.import','json.invalid','json.missing-resource','json.rendering','glb.import','glb.invalid','glb.references','validation.references'], scope: '真实JSON/GLB文件输入、错误schema/损坏资源/上传503拒绝、错误assetCode回滚、稳定URI及渲染参数保存重开。' },
+ 'save-cover-failure': { featureIds: ['project.save-failure','cover.failure'], scope: '正文PUT503不清草稿;封面上传503保留旧图但正文可保存;恢复服务后保存刷新。截图异常和409另由聚焦合同验证。' },
+ 'real-recovery-conflict': { featureIds: ['project.conflict','recovery.autobuffer','recovery.same-version','recovery.stale-version'], scope: '真实未存输入产生恢复稿,同版本恢复不自动入库;另一版本先保存后409拒绝覆盖,旧稿隔离下载。' },
+ 'publish-fixed-version': { featureIds: ['validation.complete','validation.bindings','panel.validation','publish.saved','publish.failure','publish.fixed-model','cover.atomic','project.back'], scope: '实际校验、绑定阻断、POST503不假发布,重试发布当前固定版本;返回列表保留编辑标签,搜索当前项目并核对真实封面图片加载。下游静态渲染另有独立用例。' },
+ 'loading-retry': { featureIds: ['project.retry','glb.pending','leave.busy'], scope: 'GET503只留中性失败和重试;延迟受控下载时禁用写入/导出/预览并阻止关闭,释放后真实狐狸加载。' },
+ 'save-concurrency': { featureIds: ['project.concurrent-save','project.save','project.save-key','recovery.autobuffer','recovery.same-version'], scope: '延迟真实PUT快照A、重复Ctrl+S不重复请求、期间编辑B仍dirty;再次保存刷新无旧恢复稿误报。' },
+}
+
+export const sceneUnitPlan = [
+ ['tools/scene-closure-engine.test.mjs','scene granular update permission enables editing without the retired shared code',['permission.update']],
+ ['tools/scene-closure-engine.test.mjs','typing undo/redo stays inside text controls and inactive cached editors ignore global shortcuts',['history.undo','history.redo','transform.keys','lifecycle.cache']],
+ ['tools/scene-closure-engine.test.mjs','preview locks mutation controls and Delete while Escape restores editing',['preview.lock','preview.exit']],
+ ['tools/scene-closure-engine.test.mjs','real Three ray hits on hidden descendants are excluded before selecting a root or part',['selection.hidden']],
+ ['tools/scene-closure-engine.test.mjs','deleting a root disposes its geometry and materials after clearing selection',['object.delete','lifecycle.destroy']],
+ ['tools/scene-closure-engine.test.mjs','loading and history restoration preserve an authored camera rather than auto-focusing a vehicle',['json.rendering','history.undo']],
+ ['tools/scene-closure-engine.test.mjs','save B then undo A stays dirty, and redo B returns to the actual saved baseline',['history.undo','history.redo']],
+ ['tools/scene-closure-engine.test.mjs','new resource bindings do not reuse a saved serial after earlier objects were deleted',['validation.bindings']],
+ ['tools/scene-closure-engine.test.mjs','a failed replacement GLB keeps the previous live scene rather than clearing an unsaved draft',['json.missing-resource']],
+ ['tools/scene-closure-engine.test.mjs','failed undo restoration retains live objects and keeps the undo target available for retry',['history.failure']],
+ ['tools/scene-closure-engine.test.mjs','selection highlight cleanup restores source materials and releases its own GPU helper',['selection.highlight','lifecycle.destroy']],
+ ['tools/scene-closure-engine.test.mjs','replacement failure waits for other started loads before restoring the original scene',['history.failure','glb.pending']],
+ ['tools/scene-closure-engine.test.mjs','activity log reflects actual session events and keeps API error details transient',['panel.logs']],
+ ['tools/scene-closure-engine.test.mjs','verify opens live validation and whitespace binding edits immediately invalidate the active tab',['panel.validation','validation.bindings']],
+ ['tools/scene-closure-engine.test.mjs','model source navigation locates the published owner and falls back without source permission',['assets.model-link']],
+ ['tools/scene-closure-engine.test.mjs','gizmo scaling across its pivot follows the same strictly positive scale contract as inspector edits',['transform.scale','property.scale']],
+ ['tools/scene-closure-engine.test.mjs','default history caps at 60 snapshots, stops at either edge and discards redo only after a new branch',['history.capacity','history.undo','history.redo']],
+ ['tools/scene-closure-engine.test.mjs','two published instances have unique tree part IDs and editing the second persists independently after reopen',['part.transform','selection.tree']],
+ ['tools/scene-closure-engine.test.mjs','legacy duplicate instance IDs migrate only later owners in document order despite reversed GLB completion',['part.transform','publish.fixed-model']],
+ ['tools/scene-closure-engine.test.mjs','successful parallel restores retain document order after delayed GLBs and split-object batches finish out of order',['project.load','selection.tree']],
+ ['tools/scene-tab-close.test.mjs','closing one selected scene checks its cached guard, leaves unrelated workspaces alone',['leave.cancel','lifecycle.cache']],
+ ['tools/scene-tab-close.test.mjs','cancel or failed save in a later tab never commits earlier draft discard',['leave.cancel','leave.save','leave.role-switch']],
+ ['tools/scene-tab-close.test.mjs','all successful confirmations commit together and unmounted guards are removed',['leave.save','lifecycle.destroy']],
+ ['tools/scene-closure-assets.test.mjs','fixed model reads the exact version and downloads through its visible scene dependency',['publish.fixed-model','permission.update']],
+ ['tools/scene-closure-assets.test.mjs','an explicit missing code cannot fall back to the first READY asset',['validation.references','json.missing-resource']],
+ ['tools/scene-closure-assets.test.mjs','copied scene keeps its original pseudo URI but reads its own copied asset by stable code',['glb.references']],
+ ['tools/scene-closure-assets.test.mjs','a failed fixed-version lookup is evicted and the same reference can be retried',['project.retry']],
+ ['tools/scene-cover-api.test.mjs','scene image ticket, fixed asset replacement, dependencies and coverUri commit in one versioned PUT',['cover.atomic']],
+ ['tools/scene-cover-api.test.mjs','capture failure saves content with the previous cover and reports a warning result',['cover.failure']],
+ ['tools/scene-cover-api.test.mjs','upload conflict propagates without any content PUT',['project.conflict','cover.failure']],
+ ['tools/scene-cover-api.test.mjs','role switch waits through validation, save, and the later queued publish after dirty clears',['publish.concurrent','leave.busy','leave.role-switch']],
+ ['tools/scene-cover-api.test.mjs','role switch rechecks resource uploads started while the save was finishing',['leave.busy']],
+ ['tools/scene-cover-capture.test.mjs','copies synchronously without bounds, gizmo, grid or selection glow, then restores before JPEG encoding',['cover.capture','selection.highlight','view.grid']],
+ ['tools/scene-model-placement.test.mjs','fresh skinned placement reaches the requested size and floor after authored scale/pose replay',['assets.model-double-click','assets.model-drop']],
+ ['tools/scene-model-placement.test.mjs','scale input accepts real GLTF normalization factors below 0.05',['property.scale']],
+ ['../unreal_tran_web/src/features/scene-legacy/contentApiClient.test.mjs','fixed model dependencies are recursively collected, exact and de-duplicated',['publish.fixed-model']],
+ ['../unreal_tran_web/src/features/scene-legacy/contentApiClient.test.mjs','recovery conflict defaults to preserve and only explicit discard may remove it',['recovery.discard']],
+ ['../unreal_tran_web/src/features/scene-legacy/contentApiClient.test.mjs','per-page recovery keys prevent copied tabs from deleting each other checkpoints',['recovery.multi-session']],
+ ['../unreal_tran_web/src/features/scene-legacy/contentApiClient.test.mjs','project operation queue serializes upload, save and publish including failures',['publish.concurrent']],
+ ['../unreal_tran_web/src/features/scene-legacy/contentApiClient.test.mjs','an upload finishing after a project switch cannot register into the new session',['glb.pending','lifecycle.destroy']],
+]
diff --git a/tools/scene-editor-feature-catalog.mjs b/tools/scene-editor-feature-catalog.mjs
new file mode 100644
index 0000000..ae21ec2
--- /dev/null
+++ b/tools/scene-editor-feature-catalog.mjs
@@ -0,0 +1,160 @@
+// Default-route controls only. The unmounted Vue workspace is documented separately.
+export const sceneSources = {
+ router: '../../../unreal_tran_web/src/router/index.ts',
+ runtime: '../../../unreal_tran_web/src/features/guide-legacy/demo-src/scene-editor.js',
+ host: '../../../unreal_tran_web/src/views/content/LegacyScenePageView.vue',
+ api: '../../../unreal_tran_web/src/features/scene-legacy/contentApiClient.ts',
+ assets: '../../../unreal_tran_web/src/features/scene-legacy/serverAssetStore.ts',
+ storage: '../../../unreal_tran_web/src/features/scene-legacy/runtime.ts',
+ cover: '../../../unreal_tran_web/src/features/scene-legacy/sceneCoverCapture.ts',
+ placement: '../../../unreal_tran_web/src/features/scene-legacy/modelPlacement.ts',
+ enclosure: '../../../unreal_tran_web/src/features/guide-legacy/demo-src/garage-enclosure.js',
+ selection: '../../../unreal_tran_web/src/features/guide-legacy/demo-src/scene-selection.js',
+ history: '../../../unreal_tran_web/src/features/guide-legacy/demo-src/scene-history.js',
+ formal: '../../../unreal_tran_web/src/features/editors/scene/SceneEditorWorkspace.vue',
+ list: '../../../unreal_tran_web/src/views/content/ContentProjectsView.vue',
+}
+
+// [stable ID, name, source key:search text, scope/boundary, observable acceptance]
+export const sceneGroups = [
+ ['工程入口与顶部命令', [
+ ['project.load', '按真实项目 ID 打开已存场景', 'host:async function loadProject', '服务端 content 与版本是真值;不得以模板种子覆盖有效旧工程。', '独立新会话打开,真实模型可见、固定版本及对象字段一致。'],
+ ['project.retry', '项目或挂载加载失败后重新加载', 'host:mountError', '错误详情自动消退,保留中性失败状态与重试按钮。', '注入加载失败;无可写半场景;恢复网络后按钮重试成功。'],
+ ['project.title', '当前场景名称与项目说明按钮', 'runtime:project: () => this.toast', '现有标题显示服务端名称;说明按钮不是重命名输入框。', '长名称完整 title 正确;点击说明反映内容 API 管理。'],
+ ['project.new', '顶部新建独立工程', 'runtime:new: () =>', '基线代码重置当前模板;本轮由宿主接真实创建 API,必须保留原项目。', '取消/失败不清原工程;成功新 ID;保存重开与列表对应。'],
+ ['project.back', '返回场景制作列表并保留场景标签', 'runtime:back: () =>', '平台约定返回列表等同切换标签,保留场景缓存和未存稿,不弹关闭确认;真正关闭标签才询问。', '未存状态返回列表不写服务器、不丢场景;重新点原标签恢复输入和选择。'],
+ ['project.save', '保存草稿按钮', 'runtime:async saveProject()', '只有服务器保存完成才显示成功;本地恢复缓冲不是自动入库。', '编辑后真实 PUT,刷新值一致,version 前进一次。'],
+ ['project.save-key', 'Ctrl/Meta+S 保存快捷键', 'runtime:handleKeyboard(event)', '键盘与按钮走相同保存/权限/忙碌守卫。', '实际按键触发一次保存;表单内与非表单焦点分别核查。'],
+ ['project.save-failure', '保存失败保留修改和恢复稿', 'runtime:async saveProject()', '网络失败或业务拒绝不能清 dirty;不弹虚假保存成功。', '定向 503 后对象/字段保留,恢复后重试与刷新成功。'],
+ ['project.conflict', '409 乐观锁冲突保护', 'host:function describeSaveError', '服务器最新版本不覆盖;恢复副本可下载/隔离。', '独立副本制造版本冲突;验证失败提示、旧版本不覆盖与本地稿保留。'],
+ ['project.concurrent-save', '重复保存与保存中继续编辑', 'runtime:const saveRevision = this.changeRevision', '提交快照与后续编辑分开;后续修改不能随早先请求完成清掉。', '延迟 PUT、连点保存并继续改值;一次旧提交、新修改仍 dirty。'],
+ ]],
+ ['工程文件与资源导入', [
+ ['json.export', '导出当前场景 JSON', 'runtime:exportProject()', '导出 edit3dv4.scene、对象/部件覆盖、渲染和稳定资源引用;不冒称自包含 GLB 包。', '打开下载 JSON 核对内容、结构和无临时 blob 依赖。'],
+ ['json.import', '导入有效场景工程 JSON', 'runtime:importProject(event)', '替换当前编辑文档后仍需保存;不得写入另一个项目身份。', '导出后实际文件导入、保存、重开;对象与渲染参数往返一致。'],
+ ['json.invalid', '非法 JSON / 错误 schema 拒绝', 'runtime:importProject(event)', '解析或结构错误不得清空当前场景。', '非法 JSON 与非场景文档各一次,原工程和模型仍可编辑。'],
+ ['json.missing-resource', 'JSON 缺失/错误受控引用拒绝与回滚', 'runtime:async restoreImportedObject', '错误资产编码不能回退为另一个模型;恢复失败不半替换。', '导入显式不存在编码/版本;原对象保留或明确阻断,零错误发布。'],
+ ['json.rendering', '相机、灯光、雾和环境序列化往返', 'runtime:serializeRendering()', '当前默认 UI 不提供全部渲染编辑器;导入/保存仍须保留其文档参数。', '合法带渲染参数文档导入、实际画面比对、保存重开字段一致。'],
+ ['glb.import', '本地 GLB 上传并加入当前场景', 'runtime:async importGLB(event)', '上传到内容服务,消费票据后解析;对象加入不等于工程已保存。', '真实选择小 GLB,上传/登记/可见/保存重开均完成。'],
+ ['glb.invalid', '非 GLB / 损坏 GLB / 上传失败处理', 'runtime:async importGLB(event)', '上传或解析失败不插入占位对象;原场景应保留,提示可重试。', '错误格式及损坏文件、上传 503;验证对象数/旧资产/后续成功导入。'],
+ ['glb.pending', '资源加载/上传期间操作互斥', 'runtime:setModelLoading(loading', '加载中的模型不能被半保存、导出或发布;不能离开造成旧请求污染新项目。', '延迟下载/上传,操作禁用与待完成状态一致,完成后恢复。'],
+ ['glb.references', '本项目 GLB 稳定资产引用', 'assets:async put(blob', '自导入使用 content://asset/scene/{projectId}/{assetCode} 和 READY 资产;blob 只供运行。', '重新会话受控下载成功,主对象/资产身份一致,无本地缓存依赖。'],
+ ]],
+ ['左侧目录与资源放置', [
+ ['panel.left-tabs', '场景大纲 / 资源库 / 场景模板切换', 'runtime:const leftTab =', '切面板不写业务文档;筛选状态行为需一致。', '三页签均可达,无 DOM 异常或资源目录中断。'],
+ ['search.tree', '对象/部件名称搜索与清空', 'runtime:renderTree()', '根名与部件名可命中;无匹配状态应清楚,非删除对象。', '根名/部件名/无结果/清空分别核对行和真实对象数。'],
+ ['search.assets', '资源搜索与异步刷新保留筛选', 'runtime:applyLeftFilter()', '只筛当前目录;异步重绘不复活不匹配卡片。', '命中、无结果、清空,目录重绘后过滤仍正确。'],
+ ['search.templates', '模板搜索与清空', 'runtime:filterLeft(query)', '模板卡片与同一搜索框联动。', '中文模板关键字过滤、无结果和清空。'],
+ ['assets.catalog', '真实已发布模型成果目录', 'api:async listPublishedModels', '目录由 API 预加载,当前默认不以 localStorage 发布清单为真值。', '显示发布模型名称/版本,草稿不冒充已发布,空态可操作。'],
+ ['assets.model-double-click', '双击已发布模型放置', 'runtime:async placeEditorAsset', '按固定 project/version/assetCode 导入当前实例,继承发布部件和材质。', '狐狸和卡车分别双击加入、真实可见、保存重开固定版本。'],
+ ['assets.model-drop', '拖入已发布模型放置', 'runtime:handleDrop(event)', '拖放实际命中地平面;与双击共用受控资产链路。', '真实指针拖放到不同位置,坐标/实例数/固定引用准确。'],
+ ['assets.basic-double-click', '双击基础资源放置', 'runtime:onRootDblClick', '以当前轨道目标落地;每次生成独立对象身份。', '双击生成对象,变换、保存后仍可见。'],
+ ['assets.basic-drop', '基础资源拖入与拖放提示', 'runtime:handleDrop(event)', 'MIME 和 text/plain 回退协议一致;离开视口清提示。', '真实拖放命中位置与 0.5m 吸附、dragleave/drop 提示复位。'],
+ ['assets.model-link', '打开/返回模型编辑器入口', 'runtime:open-model-editor', '当前基线只有跳模型列表,来源按钮不得冒称直达正确源项目。', '空资源库和选中模型来源两入口,跳转/未保存保护正确。'],
+ ...[
+ ['diagnostic', '液压诊断工位'], ['equipment', '维修装备主体'], ['workbench', '检修工作台'], ['shelf', '器材存放架'],
+ ['safety-zone', '安全作业区域'], ['camera', '视觉采集设备'], ['worker', '训练人员占位'], ['light', '区域照明'],
+ ].map(([id, name]) => [`asset.${id}`, `基础资源:${name}`, `runtime:id: '${id}'`, '现有程序化场景资源;名称不代表已连接物理设备或真实人员。', '实际放置可见,独立编号、位置和保存重开一致;照明应区分灯具外观与光源效果。']),
+ ['asset.vehicle-depot', '场地资源:真实装备车库', "runtime:id: 'vehicle-depot'", '整体场地 GLB 与维修车间分开;重复放置按既有规则处理。', '资源拖放或双击加载、比例/落地正常、重复放置不产生错误重叠。'],
+ ['asset.field-terrain', '场地资源:真实野外地形', "runtime:id: 'field-terrain'", '真实地形资源和指定落地基准;不能以程序地板代替。', '加载后真实起伏地形可见,保存重开资源、地面高度一致。'],
+ ]],
+ ['全部可达场景模板', [
+ ...[
+ ['depot-1', '真实装备车库'], ['terrain-1', '真实野外训练场'], ['workshop-1', '综合检验车间'], ['workshop-real', '演示维修车间(可拆分)'],
+ ['workshop-2', '总成拆卸车间'], ['workshop-3', '部件分解车间'], ['workshop-4', '清洗检测车间'], ['workshop-5', '复装调试车间'], ['outdoor-1', '室外综合训练场'],
+ ].map(([id, name]) => [`template.${id}`, name, `runtime:id: '${id}'`, '模板场地与种子布局须有实际执行;切模板不是创建新的业务项目。', '独立副本点击模板、确认/取消未存操作、真实画面、保存重开 presetId/环境/对象一致。']),
+ ['template.switch-cancel', '未保存模板切换取消', 'runtime:async loadPreset', '取消保留原场景、选择与 dirty;请求中不能切换半场景。', '有未存修改时取消/确认分别验证,取消零对象和服务端变更。'],
+ ]],
+ ['三维视口、选择与变换', [
+ ['view.orbit', '鼠标旋转相机', 'runtime:new OrbitControls', '相机轨道操作不应误选、拖动物体。', '实际鼠标拖动,相机变化且对象姿态不变。'],
+ ['view.pan', '鼠标平移相机', 'runtime:new OrbitControls', '轨道目标平移与物体位置分开。', '实际右键/约定鼠标平移,目标变化且对象数据不变。'],
+ ['view.zoom', '滚轮缩放相机', 'runtime:new OrbitControls', '改变观察距离,不改对象 scale。', '滚轮前后模型可见且对象 scale 不变。'],
+ ['view.perspective', '透视按钮', 'runtime:data-camera="perspective"', '基线按钮无处理分支;需明确恢复相机视角,不静默无效。', '顶视切回透视,实际相机位置与激活样式变化。'],
+ ['view.top', '顶视图按钮', 'runtime:setTopView()', '仍为透视相机顶视位置,不冒称正交投影。', '实际点击后顶视方向与目标一致。'],
+ ['view.focus', '聚焦选中对象', 'runtime:focusSelected()', '按对象真实 bounds 调整相机;无选中应提示。', '狐狸/卡车/环境分别聚焦可见;无选中提示不异常。'],
+ ['view.grid', '网格显隐', 'runtime:toggleGrid(button)', '当前视图辅助选项;不默认要求跨重开持久化。', '真实网格出现/消失、状态同步、截图封面不含网格。'],
+ ['view.enclosure-full', '车库外壳完整显示', 'enclosure:createGarageEnclosureControls', '完整显示屋顶/墙体/玻璃;仅车库资源存在时出现控制。', '实际点击、对象材料和显示恢复,状态与画面一致。'],
+ ['view.enclosure-xray', '车库外壳透明查看', 'enclosure:createGarageEnclosureControls', '车库多种围护材料联动;不等于普通相机透视。', '实际外壳透明且内部模型可辨,材质无串改。'],
+ ['view.enclosure-hidden', '隐藏车库外壳', 'enclosure:createGarageEnclosureControls', '隐藏围护后保留内部及地面;序列化 enclosureMode 与 dirty 契约须核对。', '隐藏/还原、保存重开和只读浏览行为准确。'],
+ ['selection.viewport', '点击视口选中根对象/部件', 'runtime:selectFromViewport(event)', '只命中真实可选择可见对象;拖相机超过阈值不误选。', '真实像素点击目标、树/属性/高亮一致,点击空白清选择。'],
+ ['selection.tree', '大纲根对象与部件选中', 'runtime:const treeItem =', '大纲显示所有可编辑实例和部件;环境锁定提示不是折叠控件。', '树选中与视口高亮/属性联动、筛选后仍可选。'],
+ ['selection.hidden', '隐藏祖先与软删除部件不可误拾取', 'selection:chooseSceneSelection', '父隐藏/子隐藏/软删除状态不能被射线重新命中。', '用重叠模型/部件实测,命中应落可见有效对象。'],
+ ['selection.highlight', '选中高亮与恢复原材质', 'runtime:updateSelectionHighlight()', '高亮是暂态,不写入模型/场景材质,不进入封面。', '切换对象/清选择/预览/保存截图后材质原值恢复。'],
+ ['transform.translate', '移动工具及真实 XYZ 手柄', 'runtime:setTransform(mode)', '对象实例位置;部件按发布能力限制。', '真实拖动平移手柄,属性联动、撤销重做、保存重开一致。'],
+ ['transform.rotate', '旋转工具及真实旋转手柄', 'runtime:setTransform(mode)', 'UI 度数与保存弧度转换;不能仅用脚本改姿态代替拖动。', '实际旋转手柄改变姿态并重开一致。'],
+ ['transform.scale', '缩放工具及真实缩放手柄', 'runtime:setTransform(mode)', '归一化模型已有 scale 必须保留,负/零/小值合同明确。', '真实缩放与属性数值一致;狐狸不突然放成超大或消失。'],
+ ['transform.keys', 'W/E/R 快捷键', 'runtime:handleKeyboard(event)', '键盘焦点在表单时不切工具,失活缓存页不能抢键。', '视口按键切工具;名称输入包含 w/e/r 正常;只读不改。'],
+ ['transform.snap', '吸附 0.5m 与旋转 15° 开关', 'runtime:toggleSnap()', '开关同时控制拖放/变换吸附;当前视图工具参数与业务值区分。', '开/关实际拖动位置、旋转角度,按钮/页脚状态一致。'],
+ ]],
+ ['对象属性、部件与底部面板', [
+ ['property.name', '显示名称', 'runtime:handleInput(event)', '对象/部件 displayName 与项目名称分开。', '修改名称,树/对象条/属性同步,保存重开保留。'],
+ ['property.visible', '在场景中可见', 'runtime:handleChange(event)', '实例显隐可保存;受保护蒙皮部件不得独立隐藏。', '开/关可见性、恢复、保存重开,视口与树状态准确。'],
+ ['property.operable', '可作为实训目标', 'runtime:handleChange(event)', 'operable/capabilities 进入场景定义;不能扩大平台操作权限。', '开/关并保存重开,发布数据的目标能力一致。'],
+ ['property.position', '位置 X/Y/Z 数值', 'runtime:handleInput(event)', '三个输入回写局部 position;不使用纯展示假字段。', '三个轴独立输入与手柄联动、失焦/history/重开准确。'],
+ ['property.rotation', '旋转 X/Y/Z 数值', 'runtime:handleInput(event)', 'UI 为角度,文档为弧度。', '非整角/负角输入、实际姿态与 JSON/重开一致。'],
+ ['property.scale', '缩放 X/Y/Z 数值及有效值', 'placement:parseSceneScale', '支持小数归一化 scale,避免小值格式化成零;非法值不得破坏模型。', '小正数、负数/零/清空按合同处理,三轴保存往返正常。'],
+ ['property.device-id', '设备编号', 'runtime:handleInput(event)', '根对象绑定字符串;重复/空值由预检与后端核查。', '修改后绑定表同步、保存重开,重复值能被阻断。'],
+ ['property.model-id', '物模型编号', 'runtime:handleInput(event)', '与受控 assetCode、来源项目 ID 分开。', '修改后只改业务编号,源 GLB 仍加载正确。'],
+ ['property.semantic', '语义类型六个选项', 'runtime:handleChange(event)', '实训装备/工位设施/安全区域/采集设备/训练人员/环境设施。', '逐项可选且保存重开保留;绑定表显示实际类型。'],
+ ['part.source', '模型来源与部件权限说明', 'runtime:renderInspector()', '固定模型版本/源节点;蒙皮仅查看高亮,非骨骼独立变换。', '狐狸蒙皮与卡车刚性部件说明/禁用态准确。'],
+ ['part.transform', '刚性部件独立变换并保存覆盖', 'runtime:captureModelPartOverrides(root)', 'partOverrides 与稳定 interactionId;多个实例互不串改。', '两卡车实例改单部件,保存重开另一实例不受影响。'],
+ ['part.soft-delete', '部件软删除与恢复', 'runtime:deleteSelected()', '被允许的部件 sceneDeleted/visible 持久化;可撤销恢复。', '实际删除、撤销、保存重开均一致;蒙皮不允许破坏骨架。'],
+ ['object.delete', '删除根对象按钮 / Delete', 'runtime:deleteSelected()', '移除当前场景实例,不删除源模型项目/固定发布版本。', '按钮和键盘各验证;源项目不变,撤销和重开正确。'],
+ ['panel.objects', '底部对象列表与选中', 'runtime:renderBottom(mode', '基线显示前 6 个摘要,完整对象仍在大纲;需明确截断。', '大于 6 个对象时列表说明准确,摘要点击选择正确。'],
+ ['panel.binding', '设备绑定总表', 'runtime:renderBottom(mode', '基线语义列恒写已绑定;需要真实类型/缺失/冲突反馈。', '编辑字段即时同步,空/重复数据不得假标已绑定。'],
+ ['panel.validation', '底部发布检查面板', 'runtime:renderBottom(mode', '显示当前逐项检查,不等于已发布。', '顶部校验/发布失败可定位检查面板,内容随修改更新。'],
+ ['panel.logs', '操作日志', 'runtime:renderBottom(mode', '基线为静态示例且误称本地存储;本轮只需真实本次运行日志,不扩展服务端审计。', '实际操作产生对应时间/结果;API 模式不显示本地存储为真值。'],
+ ]],
+ ['历史与预览', [
+ ['history.undo', '回退按钮 / Ctrl+Z', 'runtime:async undo()', '输入连续修改合并成合理历史步;恢复对象/模板/选择。', '属性、添加、删除、模板分别撤销,真实画面与文档一致。'],
+ ['history.redo', '前进按钮 / Ctrl+Y / Ctrl+Shift+Z', 'runtime:async redo()', '撤销后重做,分叉编辑清 redo。', '按钮与两种快捷键各执行;新编辑后旧 redo 不可用。'],
+ ['history.capacity', '60 步历史上限与边界', 'runtime:new SceneHistory(60)', '历史属于本次编辑会话;不宣称服务端版本历史。', '聚焦测试验证 60 步边界、空 undo/redo 与失焦快照。'],
+ ['history.failure', '异步历史恢复失败/互斥', 'runtime:async restoreHistoryState', '失败不能推进索引或丢当前有效场景;忙碌时写操作需一致。', 'GLB 恢复失败、并发 undo/redo、保存混入均不半提交。'],
+ ['preview.enter', '进入运行预览', 'runtime:togglePreview()', '当前默认预览只展示场景,未接模型 timeline/blueprint 自动执行。', '实际进入,辅助选择/手柄状态正常,模型可见。'],
+ ['preview.exit', '按钮 / Escape 退出预览', 'runtime:handleKeyboard(event)', '退出后恢复编辑选择与状态;不得丢未存变换。', '两种退出方式均有效,输入焦点与缓存激活行为正确。'],
+ ['preview.lock', '预览中禁止编辑', 'runtime:togglePreview()', '显示“场景编辑锁定”必须覆盖属性、删除、模板、导入和快捷键。', '尝试所有写入口,预览时文档不改变;退出后正常。'],
+ ]],
+ ['保存封面、发布与依赖', [
+ ['cover.capture', '保存时截取当前三维场景封面', 'cover:export async function captureSceneCover', 'JPEG 最大边 640、当前相机;隐藏手柄/网格/高亮且恢复现场。', '截图与服务端图片可辨一致,像素实际有模型,UI 状态不被破坏。'],
+ ['cover.atomic', '封面资产与文档同次保存', 'host:saveSceneDocumentWithCover', '封面 upload ticket 与 version 同一次 PUT 消费,不另存过期文档。', '检查资产/封面字段/当前版本相同,并真实列表加载图片。'],
+ ['cover.failure', '截图/封面上传失败保留原封面', 'host:coverFailed', '正文可保存时给真实 warning;409 必须阻断且不得覆盖。', '截图失败、上传503、PUT409各自核对保留/提示/零错误发布。'],
+ ['validation.complete', '环境、对象用途、模型加载检查', 'runtime:verifyScene(showToast', '纯环境允许;模型未完成解析必须阻断。', '空地板/纯环境/带模型和加载中分别核对检查结果。'],
+ ['validation.bindings', '设备/物模型编号完整与唯一', 'runtime:verifyScene(showToast', '空白、重复编号按前后端合同处理,不自动替用户分配新业务身份。', '空、空格、重复与合法编号;错误项可见,发布被阻断。'],
+ ['validation.references', '受控资源引用完整性', 'assets:hasReferenceSync', 'project/version/assetCode/sourceAssetId 必须指向实际固定资产,不能只测有字段。', '不存在编码/错误资产类型/跨项目引用显式拒绝;正确依赖通过。'],
+ ['publish.saved', '发布场景按钮的真实 API 闭环', 'runtime:async publishScene()', '有编辑权时先保存含封面再发布;两步均成功才显示已发布。', 'POST 真正发布当前版本,列表状态与下游目录一致。'],
+ ['publish.only', '只有发布权限时发布已存版本', 'runtime:if (this.readOnly)', 'publish-only 不先 PUT、不上传封面、不要求 update;服务器校验真值。', '隔离权限会话发布已存有效版本,零 update/upload 请求。'],
+ ['publish.failure', '预检/保存/发布失败阻断', 'runtime:async publishScene()', '保存失败不 POST 发布;发布失败不假标已发布;保持错误恢复能力。', '预检错误、PUT409/503、POST失败及重试,版本/状态准确。'],
+ ['publish.concurrent', '重复发布与上传/保存队列', 'host:publishDocument:', '整条 POST 完成前仍占 FIFO;重复操作不串版本。', '延迟上传/保存/发布并触发竞争,服务端只有完整合法版本。'],
+ ['publish.fixed-model', '固定 SCENE_MODEL 版本与多个实例', 'api:collectSceneModelDependencies', '去重精确模型版本,不能随模型新发布静默换源版本。', '同模型多实例及另模型新版本,场景重开仍使用所存固定版本。'],
+ ['publish.downstream', '发布场景供训练选择和静态还原', 'runtime:场景已发布,训练编排可以引用', '本轮只验证版本/目标/静态可见性;不承诺模型蓝图或原生动画自动执行。', '新场景出现在正式训练目录,绑定后狐狸/卡车可见且目标身份一致。'],
+ ]],
+ ['权限、缓存与未保存恢复', [
+ ['permission.update', '场景细分编辑权限', 'runtime:this.readOnly =', '只按 content.scene.update;基线残留 content.update 需修,不由角色名推断。', '具更新权会话可编辑;无更新权的写入口和接口全部拒绝。'],
+ ['permission.publish', '场景细分发布权限', 'runtime:this.canPublish =', '页面 content.scene 与 publish 动作分开;更新权不隐含发布。', '只更新不能发布;仅发布可发布已存版本;无页面权不可访问。'],
+ ['permission.readonly', '只读仍可查看、搜索、选中和预览', 'runtime:applyAccessMode()', '禁写不应误禁观察控件;临时视图不保存到服务器。', '真实键盘搜索、选择、相机、预览,零内容写请求。'],
+ ['recovery.autobuffer', '800ms 未保存恢复缓冲', 'runtime:markDirty(label', '仅写隔离浏览器恢复稿,不伪称自动入库。', '实际修改后等待,恢复稿存在但无自动 PUT;手动保存后入库。'],
+ ['recovery.same-version', '同版本真实未保存恢复', 'host:sceneRecoveryMatchesProjectVersion', '先回到视口,须显式保存才入库;相同文档不应产生假恢复。', '真实修改后刷新,恢复/保存/再刷新;无注入假 localStorage 代替。'],
+ ['recovery.stale-version', '不同版本恢复稿隔离', 'host:sceneRecoveryMatchesProjectVersion', '旧稿不能自动覆盖新服务器版本;下载供人工合并。', '另一会话先保存,新会话面对旧稿只能下载/隔离/明确丢弃。'],
+ ['recovery.discard', '下载、保留、丢弃恢复稿', 'host:async function quarantineRecovery', '关闭/Escape 不算明确丢弃;他会话新写入稿不得被误删。', '按钮逐项核对本地隔离键、下载文件与服务器无意外写入。'],
+ ['recovery.multi-session', '多标签/多会话恢复所有权', 'host:recoveryStoragePrefix', '会话键/nonce/锁避免覆盖别的未存稿;本地缓冲不冒充权限。', '两个编辑会话产生不同草稿,保存/丢弃一份不删另一份。'],
+ ['leave.cancel', '关闭场景标签时取消离开', 'host:async function confirmLeave', '关闭确认保留当前 dirty 与恢复副本;返回列表或切普通标签不应提示。', '真实关闭确认的关闭/Escape、继续编辑,数据未丢;不把返回列表当关闭。'],
+ ['leave.save', '保存并离开 / 放弃修改', 'host:async function confirmLeave', '等待保存完成并确认最终 dirty;明确放弃只影响当前工程会话。', '保存成功才离开;503/409留原页;放弃按确认执行。'],
+ ['leave.busy', '加载/上传/发布途中离开', 'host:const resourcesBusy =', '资源未就绪阻止离开;进行中的保存/发布须等待最终结果。', '延迟资源与提交各验证取消/等待,旧请求不污染新项目。'],
+ ['leave.role-switch', '角色切换覆盖后台场景未存稿', 'host:registerWorkspaceLeaveGuard', '全体守卫确认后才切角色;后一页取消不能抹掉前页 dirty。', '含后台场景的切角色确认取消/保存失败/成功后权限重建。'],
+ ['lifecycle.cache', '标签失活 pause / 激活 resume', 'runtime:pause()', '停止后台 RAF、恢复时重算视口;后台实例不能抢当前页键盘。', '两场景标签切换、选择和未存值保留、后台无写/快捷键误动作。'],
+ ['lifecycle.destroy', '关闭释放 WebGL/监听/资源', 'runtime:destroy()', '销毁后的异步回调无 DOM 写入,无残留全局事件和资产 URL。', '反复打开/关闭、加载中关闭边界,控制台无异常/泄漏事件。'],
+ ['lifecycle.resize-theme', '桌面尺寸与明亮/暗黑可读性', 'runtime:resize()', '继承正式主题;所有可达控件可读、无截断遮挡。', '1920/1440 下工具栏、属性、模板与弹窗截图,主题切换无低对比。'],
+ ]],
+]
+
+export const unmountedWorkspace = [
+ ['正式工作区入口', 'SceneEditorView.vue → SceneEditorWorkspace.vue 存在实现;当前场景路由不引用,不能纳入默认入口已通过数。'],
+ ['对象管理', '正式工作区有层级折叠、锁定、复制、软删除恢复按钮;默认 legacy 大纲没有对应全部控件。'],
+ ['变换和相机', '正式工作区有局部/世界空间、自定义移动吸附、前视/右视、双击与 F 聚焦;默认入口只提供既有 W/E/R、固定吸附、顶视/透视/聚焦。'],
+ ['材质和阴影', '正式工作区提供颜色、透明度、投射/接收阴影;默认场景属性没有这些编辑控件,只还原模型发布数据。'],
+ ['原生动画', '正式工作区有片段、速度、自动播放、循环和播放按钮;默认 legacy 场景没有这些控件,也未在 animate 中播放模型原生动画。'],
+ ['运行组件', '正式工作区有持续旋转/往复/呼吸缩放/热点组件及启用、名称、轴、速度、幅度;默认入口未显示组件页签。'],
+ ['语义扩展', '正式工作区有交互标识、标签、能力清单与资源引用详情;默认只有显示名、可见、实训目标、设备/物模型编号和语义类型。'],
+ ['环境渲染编辑', '正式工作区有背景/透明背景/曝光/环境强度、半球光/主光、雾范围、车间剖切;默认入口只保存/恢复文档中的渲染参数。'],
+ ['底部面板', '正式工作区有真实日志、绑定状态、问题定位、底部折叠;默认入口对应基线有静态信息和六项对象摘要。'],
+]
diff --git a/tools/scene-editor-report-evidence.mjs b/tools/scene-editor-report-evidence.mjs
new file mode 100644
index 0000000..4c84d2b
--- /dev/null
+++ b/tools/scene-editor-report-evidence.mjs
@@ -0,0 +1,11 @@
+import path from 'node:path'
+
+export function matchesSceneScreenshotAttachment(file, attachment) {
+ const stem = file.replace(/\.[^.]+$/, '')
+ const attachmentFile = path.basename(String(attachment.path || '').replaceAll('\\', '/'))
+ return attachment.name === file || attachment.name === stem || attachmentFile === file
+}
+
+export function isSceneUnitProof(proof) {
+ return ['api', 'unit', 'contract', 'integration', 'manual-visual'].includes(proof.kind)
+}
diff --git a/tools/scene-editor-report-evidence.test.mjs b/tools/scene-editor-report-evidence.test.mjs
new file mode 100644
index 0000000..9032f7b
--- /dev/null
+++ b/tools/scene-editor-report-evidence.test.mjs
@@ -0,0 +1,20 @@
+import assert from 'node:assert/strict'
+import test from 'node:test'
+import { matchesSceneScreenshotAttachment, isSceneUnitProof } from './scene-editor-report-evidence.mjs'
+test('extensionless Playwright attachment name associates with the original PNG', () => {
+ assert.equal(matchesSceneScreenshotAttachment('33-fox.png', { name: '33-fox', path: 'attachments/33-fox-.png' }), true)
+})
+test('exact filename and portable Windows attachment path are recognized', () => {
+ assert.equal(matchesSceneScreenshotAttachment('33-fox.png', { name: '33-fox.png' }), true)
+ assert.equal(matchesSceneScreenshotAttachment('33-fox.png', { name: 'screenshot', path: 'E:\\report\\screenshots\\33-fox.png' }), true)
+})
+test('neighboring names never become a screenshot association', () => {
+ assert.equal(matchesSceneScreenshotAttachment('33-fox.png', { name: '33-fox-failure', path: 'attachments/failure.png' }), false)
+ assert.equal(matchesSceneScreenshotAttachment('33-fox.png', { name: '34-truck' }), false)
+})
+test('unit titles remain unit evidence and browser titles use the final Playwright run', () => {
+ assert.equal(isSceneUnitProof({ kind: 'unit', testTitle: 'unit case' }), true)
+ assert.equal(isSceneUnitProof({ kind: 'api', testTitle: 'API contract' }), true)
+ assert.equal(isSceneUnitProof({ kind: 'browser', testTitle: 'UI case' }), false)
+ assert.equal(isSceneUnitProof({ kind: '真实UI+只读投影', testTitle: 'UI case' }), false)
+})
diff --git a/tools/scene-tab-close.test.mjs b/tools/scene-tab-close.test.mjs
new file mode 100644
index 0000000..beeec8f
--- /dev/null
+++ b/tools/scene-tab-close.test.mjs
@@ -0,0 +1,22 @@
+import test from 'node:test';
+import assert from 'node:assert/strict';
+import fs from 'node:fs';
+import {createRequire} from 'node:module';
+const requireWeb=createRequire(new URL('../../unreal_tran_web/package.json',import.meta.url));
+const ts=requireWeb('typescript');
+const source=fs.readFileSync(new URL('../../unreal_tran_web/src/utils/workspaceTabCloseGuards.ts',import.meta.url),'utf8');
+const compiled=ts.transpileModule(source,{compilerOptions:{target:ts.ScriptTarget.ES2022,module:ts.ModuleKind.ESNext}}).outputText;
+const {registerWorkspaceTabCloseGuard:register,prepareWorkspaceTabsClose:prepare}=await import(`data:text/javascript;base64,${Buffer.from(compiled).toString('base64')}`);
+test('closing one selected scene checks its cached guard, leaves unrelated workspaces alone',async()=>{
+ const seen=[];const a=register({key:()=>'/scene/a',prepare:()=>{seen.push('a');return true}}),b=register({key:()=>'/scene/b',prepare:()=>{seen.push('b');return true}});
+ try {const commit=await prepare(['/scene/b']);assert.equal(typeof commit,'function');commit();assert.deepEqual(seen,['b']);}finally{a();b();}
+});
+test('cancel or failed save in a later tab never commits earlier draft discard',async()=>{
+ let discarded=false;const a=register({key:()=>'/a',prepare:()=>()=>{discarded=true}}),b=register({key:()=>'/b',prepare:()=>false});
+ try{assert.equal(await prepare(['/a','/b']),false);assert.equal(discarded,false);}finally{a();b();}
+});
+test('all successful confirmations commit together and unmounted guards are removed',async()=>{
+ let commits=0;const a=register({key:()=>'/a',prepare:()=>()=>{commits++}}),b=register({key:()=>'/b',prepare:()=>()=>{commits++}});
+ b();try{const commit=await prepare(['/a','/b']);assert.equal(commits,0);commit();assert.equal(commits,1);}finally{a();}
+ assert.equal(typeof await prepare(['/a','/b']),'function');
+});
diff --git a/tools/verify-scene-artifacts.py b/tools/verify-scene-artifacts.py
new file mode 100644
index 0000000..2719936
--- /dev/null
+++ b/tools/verify-scene-artifacts.py
@@ -0,0 +1,359 @@
+"""Scan this scene report only. Never print matched credentials or request data."""
+from pathlib import Path
+from collections import Counter
+from html.parser import HTMLParser
+from urllib.parse import unquote, urlsplit
+import argparse
+import base64
+import datetime
+import hashlib
+import html
+import io
+import json
+import re
+import subprocess
+import zipfile
+
+from PIL import Image
+
+WORKSPACE = next(parent for parent in Path(__file__).resolve().parents if (parent / 'unreal_tran/ute2e').is_dir())
+ROOT = WORKSPACE / 'unreal_tran'
+REPORT = ROOT / 'ute2e/reports/scene-editor-closure-20260906'
+OUTPUT = REPORT / 'artifact-checks.json'
+FINAL_FILES = ('results.json', 'validation.json', 'cleanup.json', 'playwright/index.html')
+TEXT_SUFFIXES = {'.ts', '.js', '.mjs', '.cjs', '.py', '.md', '.json', '.html', '.css', '.txt', '.log', '.trace', '.network', '.svg', '.xml', '.yml', '.yaml', '.csv', '.vue', '.sql'}
+IMAGES = {'.png', '.jpg', '.jpeg', '.webp'}
+JWT = re.compile(r'(?= 64:
+ for name, previous in baselines:
+ key = (name, context)
+ if context in previous and used[key] < previous.count(context) * context.count(needle):
+ accepted = name
+ used[key] += 1
+ break
+ if accepted:
+ exemptions.append({'rule': 'unchanged-playwright-vendor-context', 'baseline': accepted})
+ else:
+ findings.append({'rule': 'known-credential', 'line': text.count('\n', 0, offset) + 1})
+ findings.extend({'rule': 'decodable-jwt', 'line': text.count('\n', 0, match.start()) + 1} for match in decoded_jwts(text))
+ return findings, exemptions
+
+
+def status_pass(value):
+ return str(value or '').upper() in ('PASS', 'PASSED', 'SUCCESS')
+
+
+def final_input_state(documents, requested):
+ reasons = []
+ if not requested:
+ reasons.append('final-scan-not-requested')
+ for name in FINAL_FILES:
+ if name not in documents:
+ reasons.append('missing-final-input:' + name)
+ validation = documents.get('validation.json', {})
+ if not (validation.get('finalRun') is True or validation.get('e2eComplete') is True or validation.get('e2e', {}).get('complete') is True):
+ reasons.append('final-run-not-confirmed')
+ if not status_pass(validation.get('overallStatus', validation.get('status'))):
+ reasons.append('validation-not-pass')
+ if not status_pass(documents.get('cleanup.json', {}).get('status')):
+ reasons.append('cleanup-not-pass')
+ tests = []
+ def visit(suites):
+ for suite in suites:
+ for spec in suite.get('specs', []):
+ tests.extend(spec.get('tests', []))
+ visit(suite.get('suites', []))
+ results = documents.get('results.json', {})
+ visit(results.get('suites', []))
+ if not tests or any(test.get('status') in ('unexpected', 'flaky', 'skipped') or not test.get('results') or test['results'][-1].get('status') != 'passed' for test in tests):
+ reasons.append('current-e2e-not-all-pass')
+ if results.get('errors') or any(results.get('stats', {}).get(key, 0) for key in ('unexpected', 'flaky', 'skipped')):
+ reasons.append('current-e2e-has-errors')
+ return reasons
+
+
+class LocalLinks(HTMLParser):
+ def __init__(self):
+ super().__init__(convert_charrefs=True)
+ self.links = []
+ def handle_starttag(self, tag, attrs):
+ self.links.extend(value for key, value in attrs if key in ('href', 'src', 'poster') and value)
+
+
+def resolve_local_link(source, value, root):
+ parsed = urlsplit(value)
+ if parsed.scheme in ('http', 'https', 'data', 'mailto', 'tel', 'javascript', 'blob') or parsed.netloc or not parsed.path:
+ return None
+ if parsed.scheme:
+ raise ValueError('Unsupported local URL scheme')
+ target = (source.parent / unquote(parsed.path)).resolve()
+ if not target.is_relative_to(root):
+ raise ValueError('Local URL outside workspace')
+ return target
+
+
+def image_metadata(payload):
+ with Image.open(io.BytesIO(payload)) as img:
+ img.verify()
+ with Image.open(io.BytesIO(payload)) as img:
+ img.load()
+ if min(img.size) <= 0:
+ raise ValueError('Empty image')
+ values = list(img.info.values()) + list(img.getexif().values())
+ metadata = '\n'.join(decode(value) if isinstance(value, bytes) else str(value) for value in values if isinstance(value, (str, bytes)))
+ return img.format, img.size, metadata
+
+
+def self_checks():
+ value = 'test-only-scene-scanner-value'
+ vendor = 'const alphabet="ABCDEFGHIJKLMNOPQRSTUVWXYZ-prefix-' + value + '-suffix-abcdefghijklmnopqrstuvwxyz-0123456789";'
+ assert not text_matches(vendor, [value], [('HEAD:vendor', vendor)])[0]
+ assert text_matches('password="' + value + '"', [value], [('HEAD:vendor', 'password="' + value + '"')])[0]
+ assert text_matches(vendor, [value])[0]
+ enc = lambda obj: base64.urlsafe_b64encode(json.dumps(obj).encode()).decode().rstrip('=')
+ token = enc({'alg': 'HS256'}) + '.' + enc({'sub': 'scanner-test'}) + '.testsigsample'
+ assert text_matches(token, [])[0]
+ assert not text_matches('Bearer ${token}; eyJnot-a-token', [])[0]
+ archive = io.BytesIO()
+ with zipfile.ZipFile(archive, 'w') as item:
+ item.writestr('report.json', json.dumps({'accessToken': token}))
+ embedded = 'data:application/zip;base64,' + base64.b64encode(archive.getvalue()).decode()
+ with zipfile.ZipFile(io.BytesIO(base64.b64decode(ZIP_DATA.search(embedded)[1]))) as item:
+ assert text_matches(decode(item.read('report.json')), [])[0]
+ png = io.BytesIO()
+ Image.new('RGB', (2, 3)).save(png, format='PNG')
+ assert image_metadata(png.getvalue())[:2] == ('PNG', (2, 3))
+ try:
+ image_metadata(png.getvalue()[:20])
+ except Exception:
+ pass
+ else:
+ raise AssertionError('Truncated image was accepted')
+ docs = {'results.json': {'suites': [{'specs': [{'tests': [{'status': 'expected', 'results': [{'status': 'passed'}]}]}]}]}, 'validation.json': {'status': 'PASS', 'e2eComplete': True}, 'cleanup.json': {'status': 'PASS'}, 'playwright/index.html': True}
+ assert not final_input_state(docs, True)
+ assert final_input_state(docs, False)
+ assert final_input_state({}, True)
+ docs['results.json']['suites'][0]['specs'][0]['tests'][0]['status'] = 'flaky'
+ assert final_input_state(docs, True)
+ source = WORKSPACE / 'example/index.html'
+ assert resolve_local_link(source, '../%E6%96%87%E6%A1%A3/sample.pdf#x', WORKSPACE) == WORKSPACE / '文档/sample.pdf'
+ assert resolve_local_link(source, 'https://example.test/image.png', WORKSPACE) is None
+ try:
+ resolve_local_link(source, '../../../outside.txt', WORKSPACE)
+ except ValueError:
+ pass
+ else:
+ raise AssertionError('Escaping local link was accepted')
+ parser = LocalLinks()
+ parser.feed('x ')
+ assert len(parser.links) == 2
+ return 16
+
+
+def scan(final=False):
+ checks = self_checks()
+ values = credentials()
+ findings, exemptions, pending = [], [], []
+ counts = Counter()
+ documents, hashes = {}, {}
+ baselines = []
+ for source in ('reports/role-permissions-20260906/playwright/index.html', 'reports/model-editor-closure-20260906/playwright/index.html'):
+ result = subprocess.run(['git', 'show', 'HEAD:' + source], cwd=ROOT / 'ute2e', stdout=subprocess.PIPE, stderr=subprocess.DEVNULL, check=False)
+ if result.returncode == 0:
+ baselines.append(('ute2e/HEAD:' + source, ZIP_DATA.sub('data:application/zip;base64,[decoded separately]', decode(result.stdout))))
+
+ def safe(label):
+ for value in values:
+ label = label.replace(value, '[redacted]')
+ return JWT.sub('[redacted-jwt]', label)
+
+ def record(label, rule, **extra):
+ findings.append({'file': safe(label), 'rule': rule, **extra})
+
+ def scan_text(label, content, vendor=False, depth=0):
+ counts['textArtifactsScanned'] += 1
+ encoded = list(ZIP_DATA.finditer(content))
+ plain = ZIP_DATA.sub('data:application/zip;base64,[decoded separately]', content)
+ hits, waived = text_matches(plain, values, baselines if vendor else ())
+ findings.extend({'file': safe(label), **hit} for hit in hits)
+ exemptions.extend({'file': safe(label), **item} for item in waived)
+ if 'playwrightReportBase64' in content and not encoded:
+ record(label, 'playwright-embedded-metadata-missing')
+ for index, match in enumerate(encoded):
+ try:
+ scan_zip(label + '!embedded-' + str(index + 1) + '.zip', base64.b64decode(match[1]), depth + 1)
+ except Exception:
+ record(label, 'embedded-archive-invalid')
+
+ def scan_image(label, payload):
+ try:
+ _, _, metadata = image_metadata(payload)
+ counts['imagesValidated'] += 1
+ if metadata:
+ scan_text(label + '!image-metadata', metadata)
+ except Exception:
+ record(label, 'image-validation-failed')
+
+ def scan_zip(label, payload, depth=0):
+ if depth > 3:
+ record(label, 'archive-depth-exceeded')
+ return
+ try:
+ with zipfile.ZipFile(io.BytesIO(payload)) as archive:
+ entries = [entry for entry in archive.infolist() if not entry.is_dir()]
+ if sum(entry.file_size for entry in entries) > 256 * 1024 * 1024:
+ record(label, 'archive-size-limit-exceeded')
+ return
+ counts['archivesDecoded'] += 1
+ for entry in entries:
+ child = label + '!' + entry.filename
+ if PRIVATE_PATH.search(entry.filename.replace('\\', '/')):
+ record(child, 'private-artifact-in-archive')
+ data = archive.read(entry)
+ counts['archiveEntriesScanned'] += 1
+ suffix = Path(entry.filename).suffix.lower()
+ if suffix == '.zip' or data[:4] == b'PK\x03\x04':
+ scan_zip(child, data, depth + 1)
+ elif suffix in IMAGES:
+ scan_image(child, data)
+ elif suffix in TEXT_SUFFIXES or b'\x00' not in data[:4096]:
+ scan_text(child, decode(data), depth=depth)
+ except Exception:
+ record(label, 'archive-read-failed')
+
+ for file in sorted(REPORT.rglob('*')):
+ if not file.is_file():
+ continue
+ label = file.relative_to(REPORT).as_posix()
+ if file.is_symlink() or not file.resolve().is_relative_to(REPORT.resolve()):
+ record(label, 'report-link-outside-scan-root')
+ continue
+ if PRIVATE_PATH.search(label):
+ record(label, 'private-artifact-in-report')
+ data = file.read_bytes()
+ counts['filesScanned'] += 1
+ if label in FINAL_FILES:
+ hashes[label] = hashlib.sha256(data).hexdigest()
+ try:
+ documents[label] = json.loads(decode(data)) if label.endswith('.json') else True
+ except Exception:
+ record(label, 'final-input-invalid-json')
+ suffix = file.suffix.lower()
+ if suffix in IMAGES:
+ scan_image(label, data)
+ elif suffix == '.zip' or data[:4] == b'PK\x03\x04':
+ scan_zip(label, data)
+ elif suffix in TEXT_SUFFIXES or b'\x00' not in data[:4096]:
+ content = decode(data)
+ scan_text(label, content, vendor=label == 'playwright/index.html')
+ if suffix == '.html':
+ parser = LocalLinks()
+ try:
+ parser.feed(content)
+ for value in parser.links:
+ target = resolve_local_link(file, value, WORKSPACE)
+ if target is not None:
+ counts['localLinksChecked'] += 1
+ if not target.exists():
+ record(label, 'missing-local-link', target=safe(str(target.relative_to(WORKSPACE)).replace('\\', '/')))
+ except Exception:
+ record(label, 'invalid-html-local-link')
+ else:
+ counts['nontextBinaryFiles'] += 1
+ pending.extend(final_input_state(documents, final))
+ if not counts['filesScanned'] or not counts['imagesValidated']:
+ pending.append('report-or-images-missing')
+ status = 'FAIL' if findings else 'PENDING' if pending else 'PASS'
+ output = {
+ 'time': datetime.datetime.now(datetime.timezone.utc).isoformat(),
+ 'status': status,
+ 'phase': 'final' if final else 'preliminary',
+ 'finalArtifactScan': final and not pending,
+ 'scope': '仅 scene-editor-closure-20260906 报告文件;不改应用,不调用 API,不清理数据。',
+ 'finalInputs': {name: name in documents for name in FINAL_FILES},
+ 'finalInputSha256': hashes,
+ 'pendingReasons': pending,
+ 'selfChecks': {'status': 'PASS', 'tests': checks},
+ 'counts': dict(counts),
+ 'findings': findings,
+ 'exemptions': {'count': len(exemptions), 'items': exemptions},
+ 'limitations': ['图片解码和文本元数据检查不代替人工视觉核验;未对图片像素进行 OCR。', '只校验 HTML 本地链接文件存在,不访问外部链接;Playwright 路由片段不作文件路径。', '只有当前最终输入完整且全部通过才可 PASS;已解码 ZIP 不适用静态第三方库基线豁免。']
+ }
+ OUTPUT.parent.mkdir(parents=True, exist_ok=True)
+ OUTPUT.write_text(json.dumps(output, ensure_ascii=False, indent=2), encoding='utf-8')
+ print(json.dumps({'status': status, 'finalArtifactScan': output['finalArtifactScan'], 'findings': len(findings), 'pending': pending, 'counts': dict(counts)}, ensure_ascii=False))
+ return 0 if status == 'PASS' else 1 if status == 'FAIL' else 2
+
+
+if __name__ == '__main__':
+ parser = argparse.ArgumentParser()
+ parser.add_argument('--final', action='store_true')
+ parser.add_argument('--self-test', action='store_true')
+ args = parser.parse_args()
+ if args.self_test:
+ print(json.dumps({'status': 'PASS', 'tests': self_checks()}))
+ else:
+ try:
+ raise SystemExit(scan(args.final))
+ except Exception as error:
+ # Do not include exception messages: they may contain source values.
+ failure = {'time': datetime.datetime.now(datetime.timezone.utc).isoformat(), 'status': 'FAIL', 'finalArtifactScan': False, 'finalInputSha256': {}, 'findings': [{'file': 'scanner', 'rule': 'scan-incomplete', 'errorType': type(error).__name__}]}
+ OUTPUT.parent.mkdir(parents=True, exist_ok=True)
+ OUTPUT.write_text(json.dumps(failure, indent=2), encoding='utf-8')
+ print(json.dumps({'status': 'FAIL', 'errorType': type(error).__name__}))
+ raise SystemExit(1)
diff --git a/tools/verify-scene-report-ui.mjs b/tools/verify-scene-report-ui.mjs
new file mode 100644
index 0000000..4c62d58
--- /dev/null
+++ b/tools/verify-scene-report-ui.mjs
@@ -0,0 +1,29 @@
+import { chromium } from '@playwright/test';
+import { pathToFileURL } from 'node:url';
+import assert from 'node:assert/strict';
+import path from 'node:path';
+import { report, work } from './scene-editor-closure-live.mjs';
+const browser = await chromium.launch({ channel:'msedge', headless:true });
+try {
+ const page = await browser.newPage({ viewport:{width:1600,height:1000} });
+ const errors=[];
+ page.on('pageerror', error => errors.push(error.message));
+ await page.goto(pathToFileURL(path.join(report,'index.html')).href);
+ assert.equal(await page.locator('.matrix tbody tr').count(),119);
+ await page.screenshot({path:path.join(work,'report-overview.png')});
+ await page.locator('#search').fill('模型');
+ assert.ok(await page.locator('.matrix tbody tr:not(.hidden)').count()>0);
+ await page.locator('#search').fill('');
+ await page.locator('#state').selectOption('PENDING');
+ assert.equal(await page.locator('.matrix tbody tr:not(.hidden)').count(),0);
+ await page.locator('#state').selectOption('all');
+ await page.locator('a.shot').filter({has:page.locator('img[src*="42-fox"]')}).click();
+ await page.locator('#viewer').waitFor({state:'visible'});
+ await page.waitForFunction(() => {
+ const img=document.querySelector('#viewer img');return img.complete && img.naturalWidth>0;
+ });
+ await page.screenshot({path:path.join(work,'report-image-viewer.png')});
+ await page.locator('#close').click();
+ assert.equal(errors.length,0);
+ console.log(JSON.stringify({reportUi:'PASS',features:119,filter:true,viewer:true,pageErrors:0}));
+} finally {await browser.close();}