翻译层
翻译层是框架的唯一输入侧:把某个特定输入源的剧本、逻辑与资源,转换为框架内部的通用协议。框架本身不识别任何外部格式;所有格式知识都集中在翻译规则、翻译器和翻译产物里。
相关页面:剧本 IR、分发包与包结构、隔离运行、Translate 大插件。
三件必须分离
| 组成 | 单元归属 | 内容 | 可替换性 |
|---|---|---|---|
| 翻译规则 | 数据包 | 声明式映射:格式识别特征、入口、字符集、字段映射、指令映射表 | 改规则不用改代码 |
| 翻译器 | 插件 | 执行翻译的 codec;只新增、无写边 | 同一格式可有多个实现 |
| 翻译产物 | 数据包 + 资源包 | 转换后的剧本、数值与表现资源 | 可独立加载、缓存、分发 |
分离不是文件组织偏好,而是三条边界:
- 规则是数据。 格式名、识别路径、字段与指令映射都来自规则条目,不写死在框架代码里。规则只接受 JSON。
- 翻译器是代码。 它执行转换,但不得借此改写框架内部流程;确需产生写边时,必须升为 Mod 并按 Mod 的审计口径处理。
- 产物是普通包。 产物落成标准数据包与资源包,走普通装载入口。框架不为翻译产物保留特权通道,Asset 管线也不认任何特例入口。
翻译边界
| 内容 | 是否翻译 | 输出去向 |
|---|---|---|
| 叙事:剧本、对话、分支、场景编排 | 是 | 内部剧本 IR |
| 表现资源:立绘、背景、CG、音频、UI 皮肤 | 是 | 资源包 |
| 界面文本:i18n 表 | 是 | 资源包的 lang 文本表 |
| 玩法数值:战斗数值、技能表、养成消耗 | 是 | 数据包条目 |
| 玩法规则:战斗循环、连携、伤害公式 | 否 | 由框架的玩法大插件实现 |
玩法规则不翻译。 把外部规则逐条搬进数据包,会让框架被某个输入源的规则牵着走,也会让「替换某套玩法机制」无法成立。框架内置的默认玩法实现,必须以实现位的一个默认条目存在,不能硬编码进机制。
二进制解析不由 JS 代劳。 二进制容器与编码解析一律由 Rust 实现,网页与 Worker 使用同源编译的 WASM;隔离 Worker 内的 JS 只做文本与 JSON 映射。资源原样复制不算解码。缺少对应解析能力时,明确报「不支持」,不得用近似实现冒充。
接口必须中立
翻译接口按通用形状设计,签名中不得出现任何特定输入源的概念。诸如某类专有机制名、某类素材分类名之类的名词,只能出现在数据包条目的内容里,不能进入接口名、字段名或错误语义。
| 必须 | 禁止 |
|---|---|
| 以格式标识、规则、候选实现、预算等通用概念描述作业 | 在服务或 codec 接口上写死某个输入源的专有名词 |
| 为多输入源留出选择与分派位置 | 让新增输入源必须修改框架核心 |
| 规则与实现分离,按格式声明能力 | 让翻译器假定只有一种格式存在 |
| 内建版本协商与命名空间 | 照搬外部 API 的形态作为内部协议 |
第一版只落地一个输入源,但接口形状必须为后续输入源留位。这是「协议中立」的具体含义,不是口号。
两种模式
| 模式 | 行为 | 适用场景 |
|---|---|---|
| 离线翻译 | 一次性转换为内部格式的包,产物入库 | 默认模式;可提前准备、可分发、可复核 |
| 在线翻译 | 运行期实时转换,产物不入库 | 尚未提前适配的输入;必须隔离运行 |
离线模式让转换结果变成可检查、可缓存、可重复使用的普通包;在线模式只解决「先跑起来」,不承诺产物持久化。
离线翻译的完整流程
- 选择输入:用户选择目录或归档。归档先展开为目录来源;ZIP 只支持存储与 deflate,ZIP64、分卷与加密条目明确报不支持。
- 识别格式:按规则声明的识别路径计算命中率,置信度 = 命中数 ÷ 总数;零命中不列出。低于 100% 时必须让用户再确认,不能替用户拍板。
- 选择翻译器:只在清单声明支持该格式的候选中选择;翻译器须已获准读取本次输入所需的能力。
- 隔离执行:主线程代翻译器读取输入,翻译器代码只在隔离 Worker 中运行。墙钟、输入、输出三项预算同时生效。
- 逐包写出:每个包单独提交,不存在跨包事务。写出内容按标准目录树整理,并生成清单。
- 用户逐个装载:写出完成后不自动装载。用户选择某个产物后,它仍走普通准入、签名核验与未签名批准流程。
第 6 步是硬规则:写出不授予信任。翻译成功只说明格式转换完成,不说明产物可信。
翻译器如何被运行
翻译器以 codec 形式登记,登记落点为 translate:codec@1:
- 只能由装载器以代理条目登记,不能由其他单元在运行期直接顶替。
- 单元清单必须声明该 codec 支持的
formats;运行时导出的formats必须与清单一致,不一致即以invalid失败。 - 代码单元的入口字节只作为 Worker 源码,主 realm 不执行。
- Worker 内装载 ESM,创建受限的
read(path)桥,并调用decode(bytes, options)。 - 主线程按调用时的获准目录快照检查路径、实际长度与累计输入预算;翻译器不能自行扩大读取范围。
- 已获准的能力只随代理条目记录,不向 Worker 提供能力对象。
翻译器同时至多发出 64 个读请求;每次读取至少按 4 KiB 计入输入预算,零字节文件也不能被无限次读取。归档来源的等待队列同样最多 64 项。
隔离与预算
| 项目 | 默认值 | 可调上限 |
|---|---|---|
| 墙钟 | 30 分钟 | 240 分钟 |
| 输入 | 1 GiB | 8 GiB |
| 输出 | 1 GiB | 8 GiB |
- 隔离环境由不允许同源访问的沙箱 iframe 启动专用 Worker;主页面只经消息通道传消息与字节。
- 预算超限即中止,销毁本次隔离环境并告警;不会把半成品交给主页面。
- 归档另有单包限制:中央目录最多 64 MiB,单条目的压缩数据与解压结果各最多 256 MiB,解压比最多 100:1;输入预算按实际读入量与解出量取较大者计费。读取逐个进行,同一时刻只有一个条目的字节在途,等待队列最多 64 项。
- 取消后不再读取下一份来源,不回传迟到结果。
- 输入输出预算不是浏览器进程的内存硬限额;Worker 内已分配的内存不受框架限制,这是已知限制。
完整机制、能力门禁与产物核验见隔离运行。
产物写出与核验
产物不是「翻译器说是什么就是什么」:
| 核验项 | 做法 |
|---|---|
| 文件来源 | copyFrom 必须经同一获准读取通道取得原始字节,并计入输入预算 |
| 文件指纹 | 按实际字节生成 SHA-256,覆盖翻译器自报的文件表 |
| 清单 | 由框架生成标准 pack.json,不采用翻译器自报的清单 |
| 分类 | 拒绝混入代码入口、能力声明或写边的数据包与资源包 |
| 输出预算 | 最终清单字节也计入累计输出预算 |
| 单包预算 | 单文件、文件数与总量三项分别设限,交出结果前先核对 |
超限时结果不转交主页面,错误原因只认固定类别,不展示翻译器自拟的说明文字。
失败、重试与取消
- 进度按识别、解码、复制、写出四个阶段报告;界面可以显示完成量与总量。
- 失败事件带阶段、原因、错误码、用量与预算;超预算时使用固定错误码,并给出三项预算的对照。
- 超预算后允许用户调高预算重试;写出失败可以只重试写出,不必重跑整个翻译。
- 写出途中取消即停,不再写下一个包;写出与重试同一时刻只允许一个在进行。
- 取消不强制中止来源适配器已经发出的外部读取,但不会交付迟到结果。
当前实现
翻译服务、规则注册、装载代理、隔离运行、写出与归档解压已完成;契约用例在 Node 与浏览器环境通过,隔离运行与解压只在浏览器中执行。外壳的离线翻译看板已接入目录或 ZIP 选择、识别确认、工序进度、产物逐个装载与预算调高。网络获取能力 caps:net-fetch 当前不可用,在线翻译的网络获取路径仍受限。
实现范围与阶段证据见当前状态与测试;接口细节见Translate 大插件。