Created
October 4, 2026 03:42
-
-
Save goldengrape/1e17f461906337612eb2b379286ef09f to your computer and use it in GitHub Desktop.
Insta360 Offline Capture:交给 AI 的完整重建提示词.md
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| # Insta360 Offline Capture:交给 AI 的完整重建提示词 | |
| 下面正文可以直接作为另一个 AI 的任务。它不要求对方拥有原项目、历史聊天、已下载模型或我的电脑环境。目标是重建功能与行为,不要求源码逐字相同。 | |
| --- | |
| 你是负责实现和测试的工程助手。请在当前工作目录重建一个 Chrome 扩展项目。 | |
| 请直接完成源代码、必要文档、测试和安装包,不要只给方案、伪代码或界面样稿。本任务已经给出技术栈、范围和验收标准;一般实现选择由你判断,不需要反复确认。遇到真实环境限制时,完成可以独立完成的工作,准确记录未验证项,不要虚构测试结果。 | |
| 当前任务仅要求本地交付,不要求发布商店、部署网站、推送远程仓库或联系任何人。不要修改用户的日常浏览器资料。如果目录已有文件,先查看并保留用户改动;不要盲目删除、重置或覆盖。 | |
| ## 1. 用户需求和范围 | |
| 用户在 Insta360 3D Space 分享页面查看一个 3D Gaussian Splat 场景,希望通过插件下载一个完整 ZIP 包。解压后有 HTML、原始 SOG、相机数据、查看器代码和说明。用户双击 HTML 就能离线查看,能够旋转、移动和缩放,不必安装 Node.js、不必启动服务器、不必重新访问 Insta360。 | |
| 主流程是: | |
| 1. 用户打开正常显示的 Insta360 场景页面。 | |
| 2. 打开插件,点击“扫描并重新加载页面”。 | |
| 3. 等待模型加载,可移动视角触发延迟资源。 | |
| 4. 点击“停止扫描”。 | |
| 5. 点击“下载离线场景 ZIP”。 | |
| 6. 插件打开独立打包进度页,下载原始资源,生成 ZIP,并通过 Chrome 保存。 | |
| 7. 用户解压,双击 `viewer-offline.html`。 | |
| 同时保留原始资源选择、分别下载和单独导出 `manifest.json` 的功能。提供开发者用的离线 HTML 构建命令,支持 SOG,其他受转换器支持的格式作为辅助路径。 | |
| 明确范围: | |
| - 运行于 Windows 的 Chrome;扩展使用 Manifest V3,最低 Chrome 125。 | |
| - ZIP 导出只承诺支持一个 ZIP 格式的 `.sog` 模型,不做多模型合并。 | |
| - 可以识别其它模型或分块元数据,并提供原始资源下载;不能把“发现了若干分块”说成“已完整导出整个场景”。 | |
| - 不复刻 Insta360 的网页、社交功能、评论或账号系统。 | |
| - 不增加后端、云上传、遥测、登录、数据库或付费功能。 | |
| - 不猜测其它场景 ID,不绕过访问限制,不导出 Cookie,不绕过 401/403。 | |
| - 保留现成查看器的 WebXR VR/AR 能力,但没有头显时不能宣称真实 XR 会话测试通过。 | |
| ## 2. 编程与沟通要求 | |
| 遵循公理设计、契约式设计、函数式编程、数据导向编程和奥卡姆剃刀原则,把原则落实到模块和接口: | |
| - URL 识别、评分、资源合并、路径生成、模型与相机匹配、相机变换、HTML 填充采用纯函数。 | |
| - Chrome API、网络、DOM、文件读写分别放在有明确职责的模块。 | |
| - 对消息来源、标签页、URL 协议、HTTP 结果、输入格式、大小、相机矩阵和 ZIP 路径建立明确契约。 | |
| - 使用普通对象、数组和 Map 保存数据;不为此项目增加大型框架或复杂类继承。 | |
| - 不复制大段相机计算逻辑,插件和 CLI 共用同一实现。 | |
| - 避免无必要的抽象。文档只解释实际需求、重要取舍和可验证行为。 | |
| 使用平实、直接、克制的技术中文。禁止隐喻、大厂黑话和故作高深的汇报句式。发现问题先说明具体事实,再说明处理方式。 | |
| ## 3. 技术栈和依赖 | |
| - 扩展:原生 JavaScript ES Modules、HTML、CSS。 | |
| - 开发与测试:Node.js 22 或更新版本、npm、`node:test`、`node:assert/strict`。 | |
| - package.json 设置 `"type":"module"`、`"private":true`、`"engines":{"node":">=22"}`,让 `.js` 纯函数模块可被 Node 测试直接导入。 | |
| - 查看器:固定 `@playcanvas/supersplat-viewer` 为 `1.37.0`。 | |
| - CLI 转换器:固定 `@playcanvas/splat-transform` 为 `3.9.0`。 | |
| - 原项目解析到的 PlayCanvas peer dependency 为 `2.23.0`。在重建项目中显式固定或通过锁文件保持这个版本,避免未来安装得到不同引擎。 | |
| - 生成并保留 `package-lock.json`。安装依赖后检查真实导出接口,不凭记忆编造 API。 | |
| - 正常使用插件不需要 npm 或 Node.js;查看器代码必须已包含在安装包里。 | |
| 推荐 npm 脚本: | |
| ```json | |
| { | |
| "test": "node --test tests/*.test.mjs", | |
| "check": "node tools/check-extension.mjs", | |
| "build:viewer": "node tools/build-extension-viewer.mjs", | |
| "build-viewer": "node tools/build-viewer.mjs" | |
| } | |
| ``` | |
| 建议再提供 `package` 命令,能够可靠地生成安装 ZIP 和源码 ZIP。打包工具采用最少依赖即可;Windows PowerShell 的 `Compress-Archive` 或经验证的 Node 打包方案均可。 | |
| ## 4. 文件结构与职责 | |
| ```text | |
| insta360-offline-chrome/ | |
| package.json | |
| package-lock.json | |
| .gitignore | |
| README.md | |
| SECURITY.md | |
| extension/ | |
| manifest.json | |
| background.js | |
| shared.js | |
| popup.html | |
| popup.js | |
| popup.css | |
| archive.js | |
| viewer-settings.js | |
| export.html | |
| export.js | |
| export.css | |
| icons/ | |
| icon16.png | |
| icon32.png | |
| icon48.png | |
| icon128.png | |
| viewer/ | |
| template.html | |
| settings.json | |
| viewer.js | |
| viewer.css | |
| LICENSE.txt | |
| tools/ | |
| build-extension-viewer.mjs | |
| build-viewer.mjs | |
| check-extension.mjs | |
| package-extension.mjs # 或同等可靠打包工具 | |
| tests/ | |
| shared.test.mjs | |
| background.test.mjs | |
| archive.test.mjs | |
| viewer-settings.test.mjs | |
| docs/ | |
| URD.md | |
| ALGORITHM.md | |
| TEST-REPORT.md | |
| output/ # 生成文件,不属于源码安装依赖 | |
| ``` | |
| `.gitignore` 至少忽略 `node_modules/`、`output/`、测试浏览器资料、Playwright 临时产物和生成的 ZIP。若有现成 Git 仓库,在重要实现阶段记录本地检查点;没有远程时不创建推送或合并要求。不要为了文档数量增加无用文件。 | |
| ## 5. Manifest 和权限 | |
| 使用模块 Service Worker:`background.js`;默认弹窗为 `popup.html`。扩展和 package 版本保持一致,图标引用必须真实存在。 | |
| 所需权限: | |
| ```json | |
| { | |
| "permissions": ["activeTab", "debugger", "downloads", "scripting", "storage", "tabs"], | |
| "host_permissions": [ | |
| "https://app.insta360.com/*", | |
| "https://app-x-va-prod.s3.amazonaws.com/*" | |
| ], | |
| "optional_host_permissions": ["https://*/*"] | |
| } | |
| ``` | |
| 不要在安装时要求读取所有网站。打包遇到其它实际观察到的 HTTPS 资源源站时,只请求选中模型和匹配相机数据的 origin 权限。 | |
| `chrome.permissions.request({origins})` 必须从用户点击下载按钮的事件处理器发起,避免异步流程丢失 user gesture。拒绝权限时显示中文错误;不能把拒绝处理成打包成功。进度页再次用 `permissions.contains` 检查。 | |
| 所有资源获取只使用当前扫描观察到的地址。扩展消息要求 sender 的扩展 ID 等于自身 ID,sender URL 属于自身 `chrome-extension://`,tabId 是有效整数。外部页面不能通过消息命令让插件下载任意 URL。 | |
| 遵循 MV3 CSP:插件的运行脚本用本地模块文件,不启用 `unsafe-eval`,不执行远程 JS。查看器模板中的内联代码只作为文本用于生成下载文件,不在扩展页面中执行。 | |
| ## 6. 资源识别:shared.js | |
| 提供以下接口或语义等价接口: | |
| ```text | |
| sceneIdFromUrl(url) -> sceneId | null | |
| extensionOf(url) -> 格式/精确元数据文件名 | 空字符串 | |
| classifyResource({url,mimeType,type,size}) -> {score,kind,reasons,ext} | |
| extractUrls(value, baseUrl) -> 去重 HTTP(S) URL 数组 | |
| safeSegment(text, fallback) -> 安全文件名片段 | |
| downloadPathFor(url, sceneId, index) -> Chrome 相对下载路径 | |
| dedupeResources(resources) -> 按完整 URL 合并的资源数组 | |
| canDownloadResource(resource) -> boolean | |
| ``` | |
| 页面校验必须检查:协议是 `https:`、hostname 恰为 `app.insta360.com`、pathname 完整匹配 `/3dspace/detail/<scene-id>`,可有末尾斜线。不接受其它域名、相似域名或仅路径里碰巧出现字符串的页面。 | |
| 识别模型扩展名:`.sog`、`.ply`、`.compressed.ply`、`.splat`、`.ksplat`、`.spz`、`.lcc`、`.lcc2`。优先识别 `.compressed.ply` 等完整后缀。 | |
| 按 basename 精确识别 `meta.json`、`lod-meta.json`、`cameras.json`,不能让 `lod-meta.json` 被后缀判断误判为 `meta.json`,不能把 `notmeta.json` 当模型入口。 | |
| 参考评分规则,用于复现现有候选列表: | |
| | 条件 | 分数 | | |
| |---|---:| | |
| | 已知模型后缀 | +100 | | |
| | meta.json / lod-meta.json | +90 | | |
| | cameras.json | +70 | | |
| | 普通 JSON 路径含模型关键词 | +45 | | |
| | pathname 含 gaussian、splat、sog、pointcloud、point-cloud、3dgs、lod-meta、compressed.ply、model、scene 等关键词 | +35 | | |
| | octet-stream、application/zip、model/* MIME | +20 | | |
| | Fetch/XHR 大于 2,000,000 字节 | +15 | | |
| | WEBP 路径含 sh、means、quat、scale | +35 | | |
| | 普通 WEBP | -15 | | |
| | 路径含 thumbnail、cover、avatar、poster、preview、logo、icon | -45 | | |
| | JS/CSS/HTML 文件或 MIME | 分数最高为 0 | | |
| | 由模型入口 JSON 明确引用的资源 | 额外 +45 | | |
| 关键词只检查 URL pathname,不检查 query,防止 `script.js?model=x.sog` 误判。`kind` 可以是 model、metadata、payload、candidate、other。 | |
| 递归提取 JSON 中的 URL,但只接受绝对 HTTP(S) URL 或看起来是文件路径的字符串。标题、UUID、枚举、普通文本不能自动变成相对资源。恢复 `\\u0026` 和转义斜线;页面 HTML 补扫还需要恢复 `&`。签名 URL 必须保留完整 query,不能自行删除、重排签名参数或猜测过期后的新签名。 | |
| 资源合并必须保留已知 HTTP 状态、MIME、请求类型、最大已知大小和原始 firstSeenAt。页面补扫发现同一 URL 时不能把这些事实覆盖为 0 或空值。评分用最大值,不能把真实 0 分用 `||` 当成缺失。sources 和 reasons 去重;新的网络成功响应可以清除该 URL 的失败状态。 | |
| 可下载条件:HTTP(S)、请求方法未知或 GET、状态未知或 2xx,并且没有明确 loadingFailed。状态未知只说明待下载验证,不代表成功。 | |
| 路径要求:保留 host 和原始路径层级,去掉 query 对文件名的影响,过滤 Windows 不允许字符、控制字符、路径穿越、末尾点/空格和保留名 CON/PRN/AUX/NUL/COM*/LPT*。损坏的百分号编码不能导致整次操作崩溃。同一次下载出现路径冲突时加编号。 | |
| ## 7. 扫描与状态:background.js | |
| 按 tabId 管理扫描状态,使用 `chrome.storage.session` 保存小型状态,Service Worker 重启后可以恢复。不要把模型 Blob、Base64 或整个离线 HTML 放进 session storage。 | |
| 状态最少包含: | |
| ```js | |
| { | |
| tabId, pageUrl, sceneId, | |
| attached: false, scanning: false, | |
| startedAt: null, stoppedAt: null, | |
| resources: [], requests: {}, errors: [], downloads: [], message: '' | |
| } | |
| ``` | |
| 资源保存 url、ext、kind、score、reasons、sources、requestId、method、status、mimeType、type、size、failed、failure、firstSeenAt。requests 保存请求方法、原地址、响应地址、状态、类型、Content-Length 和有限的认证头。 | |
| 所有影响同一标签页状态的操作放入按 tabId 的 Promise 队列,包括 UI 命令、调试事件、存储定时器和下载状态更新。单个操作失败不能导致后续队列永远拒绝。持久化可以约 500 ms 防抖,但定时器也必须进入同一队列,避免旧状态覆盖新状态。 | |
| 开始扫描: | |
| 1. 校验页面;重复开始同一正在扫描的标签页不重复 attach。 | |
| 2. `chrome.debugger.attach({tabId}, '1.3')`。 | |
| 3. `Network.enable`,总缓冲约 20 MiB,单资源缓冲约 5 MiB。 | |
| 4. `Page.enable`。 | |
| 5. `Target.setAutoAttach({autoAttach:true, waitForDebuggerOnStart:true, flatten:true})`。 | |
| 6. 标记并保存状态,再 `chrome.tabs.reload(tabId, {bypassCache:true})`。 | |
| 7. attach、Network.enable 或 reload 失败时解除已建立连接,恢复 scanning/attached 为 false,记录可读错误。 | |
| 必须捕获 Worker/子调试会话。收到 `Target.attachedToTarget` 后,对 `{tabId,sessionId}` 开启 Network,并继续设置 autoAttach;在 finally 中调用 `Runtime.runIfWaitingForDebugger`,不能把网页 Worker 永久暂停。 | |
| 请求唯一键用 `${sessionId || 'root'}:${requestId}`,避免不同会话中相同 requestId 相撞。对子会话调用 `Network.getResponseBody` 时,必须使用正确 Debuggee sessionId 和 CDP 原 requestId,而不是合成键。 | |
| 处理这些事件:requestWillBeSent、responseReceived、loadingFinished、loadingFailed、Page.loadEventFired、debugger.onDetach、tabs.onRemoved。 | |
| - 请求事件保存方法和认证头;只允许 authorization、x-auth-token、x-api-key,不读取/导出 Cookie。 | |
| - 响应事件保存实际 HTTP 状态、MIME、大小、类型;Content-Length 不区分头名称大小写。 | |
| - 完成事件补充 encodedDataLength,并重新计算大资源评分。 | |
| - 成功的小型 JSON 可以读取 body;上限 5 MiB,同时检查 Content-Length、encodedDataLength 和解码后的实际大小。 | |
| - `getResponseBody` 失败或 body 被回收不应停止扫描;继续依赖 URL 和页面补扫。 | |
| - 模型入口 meta.json/lod-meta.json 的引用加权;其它 JSON 不自动获得同样的模型依赖加权。 | |
| - loadingFailed 标记资源失败,禁止将其当作可下载成功资源。 | |
| - 页面加载约 1.8 秒后补扫;执行时重新判断当前扫描状态,不污染已停止或新开始的会话。 | |
| 页面补扫用 `chrome.scripting.executeScript` 遍历可访问 frame,收集 performance resource entries、DOM 的 src/href、HTML 内嵌的 HTTP(S) URL。适当限制返回数量,例如 5000 条;不得向页面注入远程脚本。 | |
| 停止扫描先补扫再 detach,更新停止时间。标签页关闭后清除 timer、内存状态和 session storage,忽略关闭标签页的迟到事件。 | |
| 消息接口:START_SCAN、STOP_SCAN、GET_STATE、DOWNLOAD_RESOURCES、DOWNLOAD_MANIFEST。统一返回 `{ok:true,...}` 或 `{ok:false,error}`。异步 onMessage 保持消息通道打开,并保证回应一次。 | |
| ## 8. 原始资源下载与弹窗 | |
| 弹窗宽约 440 px,使用原生 DOM 和 textContent,不能把抓到的 URL 或标题作为 HTML 执行。 | |
| 必须有:场景标识、扫描状态、扫描按钮、停止按钮、主按钮“下载离线场景 ZIP”、提示信息、资源列表、选择高置信度、清空、下载选中资源、下载 manifest.json、实际下载结果。 | |
| 资源显示文件名、格式、评分、大小、HTTP 状态和失败原因。URL 过长时截断显示,完整地址可在 tooltip 查看。默认列表可显示 score >=20;“选择高置信度”选 score >=60 且可下载的资源。 | |
| 约 1.2 秒轮询 GET_STATE;用户操作期间避免重复命令;popup 关闭后扫描不丢失。支持 `popup.html?tabId=<id>`,便于隔离浏览器测试指定场景页。正常弹窗使用当前活动标签页。 | |
| 普通资源用 `chrome.downloads.download` 保存到: | |
| ```text | |
| Insta360-Offline/<scene-id>/assets/<host>/<原始路径> | |
| ``` | |
| 使用 `conflictAction:'uniquify'`。download API 返回 id 仅表示任务创建;必须用 `downloads.onChanged` 和 `downloads.search` 跟踪 in_progress、complete、interrupted、bytesReceived、totalBytes 和 error。 | |
| 清单包含观察到的资源、实际下载状态和本地相对路径。若 Chrome 改了重名文件名,以真实下载 filename 为准,不只输出预先计划的路径。单独原始资源清单可以记录观察到的完整资源 URL,但明确签名可能过期;任何归档不得写出认证头。 | |
| 修正按钮 hover 的 CSS 优先级:主按钮保持深色背景与白色文字,不能 hover 后变成浅灰底白字。 | |
| ## 9. 查看器构建和 HTML 填充 | |
| 安装 npm 依赖后,在开发阶段生成 `extension/viewer/`,不能让最终用户执行此步骤。 | |
| 使用库的公开接口: | |
| ```js | |
| import { renderViewerHtml, js, css } from '@playcanvas/supersplat-viewer'; | |
| import { defaultSettings } from '@playcanvas/supersplat-viewer/settings'; | |
| const template = renderViewerHtml({ | |
| bootstrap: { | |
| settings: '__INSTA360_SETTINGS__', | |
| contentUrl: '__INSTA360_MODEL__', | |
| contentFilename: 'scene.sog' | |
| }, | |
| inlineCss: true, | |
| inlineJs: true | |
| }); | |
| ``` | |
| 把 template 写入 template.html;把 `defaultSettings('environment')` 写入 settings.json;js/css 导出字符串分别写入 viewer.js/viewer.css;保留该 npm 包 LICENSE,并按实际分发内容保留其它必要许可证。 | |
| 运行时读取静态模板文本,用实际 settings JSON 替换 `"__INSTA360_SETTINGS__"`,用 `data:application/octet-stream;base64,<原始SOG>` 替换模型 token。 | |
| 两个 token 必须各出现一次;缺失或重复要报模板错误。模型数据必须是预期 Base64 data URL。JSON 替换文本里的 `<` 转成 `\\u003c`,防止字符串提前结束 script 标签。不要靠删除或改写库私有压缩代码来构造查看器。 | |
| 最终 HTML 必须内嵌查看器 JS、CSS、初始视角和模型。因此它可以直接从 `file://` 运行,不通过 fetch('./assets/3DGS.sog') 读取旁边的模型,不依赖远程字体、CDN、原始场景接口或签名地址。 | |
| ## 10. 相机转换:必须准确实现 | |
| SOG 直接放进通用 Model Viewer 后看起来混乱,不足以证明文件损坏。默认相机可能贴在墙面或离开采集范围;此项目必须优先恢复相机数据中的初始视角,而不是用模型包围盒中心代替。 | |
| `cameras.json` 是数组。以数组第一项为初始相机,验证后读取 position、rotation、height、fy;第一项无效时按下述失败策略处理,不静默跳到其它相机。rotation 是 3×3 camera-to-world 矩阵。当前 SuperSplat 对 splat 应用绕 Z 轴 180° 的变换,需要同时转换相机位置与朝向。 | |
| 复现计算如下: | |
| ```js | |
| const position = [-camera.position[0], -camera.position[1], camera.position[2]]; | |
| const direction = [ | |
| -camera.rotation[0][2], | |
| -camera.rotation[1][2], | |
| camera.rotation[2][2] | |
| ]; | |
| const target = position.map((value, index) => value + direction[index]); | |
| const fov = 2 * Math.atan(camera.height / (2 * camera.fy)) * 180 / Math.PI; | |
| settings.cameras = [{ initial: { position, target, fov } }]; | |
| ``` | |
| 纯函数 `initialCameraFromCameras(cameras)` 返回 `{position,target,fov}`。 | |
| 检查 position 恰为 3 个有限数值,rotation 恰为 3×3 有限数值,height/fy 是正有限数,direction 长度非零,结果不会产生 NaN/Infinity。 | |
| 相机文件必须从模型同一 origin 和同一目录匹配 basename `cameras.json`,两者可以有不同签名 query。不能取另一个执行目录或另一个模型的 cameras。 | |
| 如果扫描结果没发现相机文件,弹窗和包内说明明确提示默认视角;允许用查看器默认相机打包。如果已找到相机 URL,但下载失败、JSON 无效或矩阵不合法,则本次 ZIP 打包失败,不能静默退回默认相机并宣称完整导出。 | |
| 原始 cameras.json 保留原始字节,viewer/settings.json 保存转换后的设置;两者含义不同,不相互覆盖。 | |
| ## 11. 独立打包页和 ZIP | |
| 不要在 popup 中执行整个大文件打包,因为 popup 关闭会中断任务;不要在 Service Worker 中存大模型或创建需要 DOM 的 FileReader 流程。 | |
| 点击主按钮时: | |
| 1. 从当前扫描结果选择 SOG。 | |
| 2. 有一个 SOG 时自动选择,不必勾选相机。 | |
| 3. 有多个 SOG 时要求用户只勾选一个;多个勾选或没有明确选择时禁用并说明原因。 | |
| 4. 请求实际资源 origin 的必要权限。 | |
| 5. 打开 `export.html?tabId=...&model=...`,或使用等价的仅插件内部任务标识。 | |
| 打包页显示准备、资源下载、封装、保存、完成或失败状态,提供取消与失败重试。说明“下载完成前请保留此页面”。重复点击或重试要避免重入。 | |
| 打包页从 background 获取扫描状态,再检查模型确实仍在观察结果里。fetch 时使用捕获并过滤的认证头;注意 background 保存 `{name,value}` 数组,而 fetch 的 HeadersInit 需要 `[name,value]` 对或合法对象,不能直接混用这两种结构。 | |
| 下载使用 AbortController、`credentials:'omit'`、`cache:'no-store'`。流式读取时报告已接收字节并检查大小: | |
| - 模型上限 128 MiB。 | |
| - 相机上限 8 MiB。 | |
| - 既检查 Content-Length,也检查实际累计字节,不能只相信响应头。 | |
| - 非 2xx、网络失败、空文件、超限明确报错。 | |
| - SOG 开头必须为 ZIP local file header `0x04034b50`,即字节 `50 4b 03 04`。此检查只是格式初筛,不宣称已完成内部语义验证。 | |
| 模型原样封装,不重新量化、不做 GPU 转码、不改变 SH 数据。Blob 使用 application/octet-stream MIME,FileReader 转 Base64 data URL。 | |
| 场景 ZIP 名称:`Insta360-Offline/<scene-id>-offline.zip`,冲突自动改名。ZIP 内部根目录如下: | |
| ```text | |
| viewer-offline.html | |
| assets/3DGS.sog | |
| assets/cameras.json # 原场景有相机数据时包含 | |
| viewer/viewer.js | |
| viewer/viewer.css | |
| viewer/settings.json | |
| viewer/LICENSE.txt | |
| manifest.json | |
| README.txt | |
| ``` | |
| HTML 内嵌模型,assets 另保留一份原始模型;这会增加包大小,是为了同时满足双击查看和保留原始文件。viewer.js/css 是独立代码备份,HTML 运行并不依赖相邻文件。 | |
| ZIP 清单记录 schema/version、sceneId、来源页面、生成时间、查看器名称和固定版本、入口 HTML、原始文件路径/大小、初始相机来源和缺少相机的警告。不需要把带签名的 CDN URL 或认证头写入这个完整包的清单。 | |
| ZIP 实现可以使用原生、无外部依赖的 stored ZIP: | |
| - 每个条目 local header + 原始 Blob,central directory,EOCD。 | |
| - 压缩方式 0;CRC32 用标准多项式 `0xedb88320`。 | |
| - 正确填写小端字段、原始大小、UTF-8 文件名长度、local header offset、中央目录大小和起点。 | |
| - UTF-8 flag 0x800,合法 DOS 日期,例如 1980-01-01。 | |
| - 拒绝绝对路径、路径穿越、反斜线导致的歧义、重复文件名、ZIP32 超限和过多条目。 | |
| - 用 CRC 标准向量 `123456789 -> 0xcbf43926` 和独立 ZIP 阅读器验证,不能只用自己写的解码逻辑证明自己正确。 | |
| 最终用 Blob URL 和 `chrome.downloads.download` 保存,跟踪实际 complete/interrupted。创建成功时只显示“正在保存”,不能提前显示“下载完成”。下载完成或中断后释放 Blob URL;错误与取消也清理资源。 | |
| ## 12. 辅助 CLI | |
| 提供 `npm run build-viewer -- <模型文件> [输出html]`。 | |
| - 输入必须存在且是文件;输出必须为不同的 `.html` 路径,不能覆盖输入。 | |
| - `.sog` 且没有额外转换选项时,检查 ZIP 文件头,原样读取;寻找同目录 cameras.json,使用同一个相机纯函数;通过 renderViewerHtml 生成内嵌数据的 HTML。 | |
| - CLI 可明确警告无效相机并使用默认设置;这与主 ZIP 流程“已发现相机但无效则失败”的严格策略要在文档中说明,不能混淆。 | |
| - 其它支持格式或明确额外转换选项走 splat-transform CLI。 | |
| - Windows 上不要直接依赖 spawnSync 执行 `.cmd`。解析 `node_modules/@playcanvas/splat-transform/bin/cli.mjs`,用 `process.execPath` 启动。 | |
| - 转换默认 `--gpu cpu`,允许用户显式覆盖。已有真实测试遇到过 Windows D3D12 GPU 压缩驱动挂起,直接 SOG 封装不应走这条路径。 | |
| - 透传错误与退出码,不把进程启动成功当作转换成功。 | |
| ## 13. WebXR:保留能力,准确报告 | |
| 现成 SuperSplat Viewer 1.37.0 包含 `immersive-vr` 和 `immersive-ar` 检测、XR 控制和启动入口。支持的浏览器/设备上显示相应按钮,不支持时隐藏或正确提示,不能模拟按钮成功。 | |
| 模板默认倾向 WebGPU,支持 `?webgl`。该版本在当前后端不能直接启动 XR、但检测到 WebGL 可用时,会提示切换并重新加载。保留这个行为,不要擅自把普通离线页面改成只能 WebGL。 | |
| 普通 `file://` 打开测试不等于 WebXR 会话验证。XR 还依赖设备、浏览器、安全上下文和用户授权。一般使用 HTTPS 或同一设备的 localhost;头显访问电脑的 `http://192.168...` 不自动享有 localhost 特例。没有真实硬件时只记录代码能力和未验证项,不保证 Quest、Vision Pro 或其它设备都可用。本版不要求新增 HTTPS 服务器、证书管理或 XR 启动脚本。 | |
| ## 14. 测试计划与判断标准 | |
| 使用有明确正确结果的测试,不用“有一个 canvas”代替“模型渲染正确”。至少覆盖以下行为,测试数量不必与原项目相同,但不能丢失关键契约。 | |
| ### A. 纯函数 | |
| - 严格场景域名/协议/路由识别,拒绝相似域名和其它路径。 | |
| - 精确元数据文件名及 compressed.ply 后缀。 | |
| - query 中的模型关键词不会给页面脚本高分。 | |
| - JSON 提取绝对/相对文件引用,排除普通文本和危险协议。 | |
| - 转义签名地址恢复完整。 | |
| - 损坏的百分号编码与 Windows 保留文件名处理。 | |
| - 页面补扫合并保留网络事实和零分语义。 | |
| - HTTP 403、loadingFailed 和非 GET 不可下载。 | |
| - 同目录相机匹配,不跨执行目录;多 SOG 选择必须明确。 | |
| - HTML token 缺失/重复失败,settings 中 `</script>` 不会破坏脚本边界。 | |
| - CRC 标准向量、ZIP offset、UTF-8 名称、原始字节及路径拒绝。 | |
| - 相机 position、rotation 第三列、target、fov 的具体数值;非法相机不能输出 NaN。 | |
| ### B. 模拟 Chrome API 的 background 契约测试 | |
| - 外部消息拒绝,tabId 校验,异步响应。 | |
| - attach/enable/reload 失败时 detach 和 stopped 状态。 | |
| - 密集网络事件与 timer 顺序不会丢状态。 | |
| - 主页面与 Worker 相同 requestId 不相撞。 | |
| - 子会话失败也要恢复 Worker 运行。 | |
| - body 被回收时仍继续扫描。 | |
| - HTTP 失败资源不能建立普通下载任务。 | |
| - 下载 complete/interrupted 正确显示,单纯取得 downloadId 不算完成。 | |
| - 导出清单记录 Chrome 实际改名路径,不包含认证头。 | |
| ### C. 真实浏览器验收 | |
| 用隔离的浏览器资料加载实际扩展,通过真实 popup 按钮完成扫描和 ZIP 导出。使用已有可用浏览器自动化能力;Windows 上若普通 Chrome 不接受命令行加载扩展,使用支持该测试方式的 Chromium,不要修改用户的主浏览器。扩展更新后确保测试实际运行最新文件,必要时使用新测试 profile。 | |
| 实际验收必须完成: | |
| 1. 扩展可加载,manifest 可读,Service Worker 没有启动错误。 | |
| 2. 打开场景,通过按钮扫描,再停止;至少识别 SOG 和该目录的 cameras.json。 | |
| 3. 点击 ZIP 按钮,看到打包进度,确认 Chrome 最终 complete,并保存实际生成的文件。 | |
| 4. 用独立 ZIP 阅读器检查全部条目和 CRC;Python `zipfile.testzip()` 可作为判断工具,但不是最终用户依赖。 | |
| 5. ZIP 原始 SOG 与实际下载响应或已有可靠原文件逐字节一致;从 HTML Bootstrap 提取 Base64 后与 assets/3DGS.sog 一致。 | |
| 6. 检查原始相机没有被改写,viewer/settings.json 与 HTML 内嵌设置一致。 | |
| 7. 解压后先启用浏览器离线模式,再从 `file://` 打开或重新加载 HTML;等待资源真正完成,截图检查正确初始视角。 | |
| 8. 拖动操作确实改变模型画面;脚本错误为 0,网络记录中没有远程资源请求。保存操作前后的截图。 | |
| 9. 在隔离测试页拦截模型响应为 403:明确失败,未创建新 ZIP 下载。 | |
| 10. 拦截相机响应为无效数组:明确失败,未静默生成默认视角 ZIP。 | |
| 11. 如可行,测试取消、大小限制和下载保存中断;没测到的项目准确标为未验证。 | |
| 不要把“扫描资源状态为 0”直接当失败:页面可能从缓存或 IndexedDB 恢复;但最终 fetch/下载必须验证实际响应。若参考页面不可用,记录真实原因,完成本地与模拟测试,保留真实样本验收为未完成;不能用伪造场景宣称参考页面测试成功。 | |
| ## 15. 静态检查、安装包与交付 | |
| `npm run check` 至少检查:MV3、manifest/package 版本一致、JS 模块语法、popup/export 页面和 CSS 存在、图标存在、全部查看器静态文件存在、两个模板 token 各一次。 | |
| 最终交付: | |
| ```text | |
| output/insta360-offline-chrome-0.3.0.zip | |
| output/insta360-offline-chrome-0.3.0-source.zip | |
| output/insta360-offline-chrome-0.3.0/ # 可直接加载的解压目录 | |
| ``` | |
| 安装 ZIP 根目录必须直接有 manifest.json、background.js、popup、export、共享模块、icons/ 和 viewer/。不能仅在根目录放 extension/、docs/ 等文件,然后让用户选错误层级。这个错误会导致 “Manifest file is missing or unreadable”。 | |
| 安装 ZIP 不包含 node_modules、开发文档、浏览器 profile 或样例模型;源码 ZIP 包含源码、开发工具、测试、文档、package.json/lock,排除 node_modules、output 和敏感测试产物。 | |
| 独立验证安装 ZIP 的 CRC、manifest 版本、被引用文件存在、与被测 extension/ 文件字节一致。README 清楚说明 Chrome 开发者模式、加载包含 manifest.json 的目录、升级后重新加载、主流程、包内重复保存模型的原因、128 MiB 限制、相机策略、可选权限和 XR 验证范围。 | |
| `docs/URD.md` 写确认需求和范围;`docs/ALGORITHM.md` 写模块职责、数据流、接口契约与关键取舍;`docs/TEST-REPORT.md` 写本次真实测试、命令、结果、产物位置及未验证项。用一张简短表把核心需求对应到模块和验收测试,避免仅罗列术语。 | |
| ## 16. 推荐执行顺序 | |
| 1. 检查目录和工具环境,建立最小文件结构及简短需求/检查计划。 | |
| 2. 实现 URL/评分/合并/路径和相机纯函数,运行相关契约测试。 | |
| 3. 实现 background 状态队列、捕获、Worker 会话、失败清理和普通下载。 | |
| 4. 实现 popup,通过真实页面验证扫描与资源发现。 | |
| 5. 安装固定依赖,通过公开 API 生成查看器静态文件。 | |
| 6. 实现 HTML 填充和 ZIP,验证 CRC、文件内容和字节一致性。 | |
| 7. 实现独立打包页、权限请求、取消、错误和真实保存状态。 | |
| 8. 完成真实 ZIP 下载、解压、断网打开、初始视角和交互验收。 | |
| 9. 生成安装包与源码包,检查安装根目录及完整性,更新文档。 | |
| 10. 最终报告已完成行为、测试证据、交付文件和实际限制,不只报告“代码已写完”。 | |
| 不要仅因 Node 单元测试通过就结束,也不要为追求测试数量反复测试没有变化的部分。修复真实失败后重测受影响流程;无法验证真实设备或资源时如实说明。 | |
| ## 17. 技术参考 | |
| 以下用于核对接口和平台约束,不允许在最终离线页面运行时依赖这些站点: | |
| - Chrome debugger:https://developer.chrome.com/docs/extensions/reference/api/debugger | |
| - Chrome downloads:https://developer.chrome.com/docs/extensions/reference/api/downloads | |
| - Chrome permissions:https://developer.chrome.com/docs/extensions/reference/api/permissions | |
| - Chrome storage:https://developer.chrome.com/docs/extensions/reference/api/storage | |
| - SuperSplat Viewer:https://developer.playcanvas.com/user-manual/supersplat/viewer/ | |
| - Viewer 自托管:https://developer.playcanvas.com/user-manual/supersplat/viewer/self-hosting/ | |
| - Viewer 嵌入:https://developer.playcanvas.com/user-manual/supersplat/viewer/embedding/ | |
| - SOG 格式:https://developer.playcanvas.com/user-manual/gaussian-splatting/formats/sog/ | |
| - splat-transform CLI:https://developer.playcanvas.com/user-manual/splat-transform/cli-reference/ | |
| - WebXR 环境要求:https://developer.mozilla.org/en-US/docs/Web/API/WebXR_Device_API/Startup_and_shutdown | |
| 如果网页文档与固定 npm 版本的实际接口不一致,以该版本实际代码和接口测试为准,并记录差异。不要悄悄升级依赖来规避需要理解的问题。 | |
| 请现在开始实现,持续完成到上述交付和验收结束。 |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment