review: M2 成果审查后的质量加固

三路+对照审查 M2 全部成果。桥核心(M1,本期未改)与契约骨架零问题;发现集中在新的资源/启动/容器代码,已修:

高危
- ResourceManager.updateGame 原子化:下载→解压暂存→校验 <gamestart>/index.html→才删旧并 rename 换入。
  修复"先删旧 gamehall 再解压,失败即把可用资源砖掉"且 catch『keep local』失效的数据丢失风险
- BridgeGameContainer.setupBridge:先建桥并赋值 this.bridge,能力注册/配置加载整体兜底 try/catch。
  修复"config/registration 抛错→bridge 永远 undefined→H5 所有 callHandler 静默"违反 H5 零改动铁律

中危
- VersionResolver:空 gameid 守卫(大厅不参与 game 树匹配,避免误命中 gamelist 空 id 节点);
  两树 version 平手时取"有下载地址"的一棵(避免选中只有版本号无下载地址的层)。新增 2 单测,覆盖率 83.7%
- Downloader:dataReceive 写盘失败标记并于请求结束后抛出(避免磁盘满致截断文件被当作下载成功)
- FileSystem:rmrf 的 statSync 纳入 try(兑现"不存在/竞态则忽略"承诺);copyRawFileTo 按
  byteOffset/byteLength 切片写入(避免 Uint8Array 为大池子视图时写入多余字节致 zip 损坏);新增 rename
- ConfigManager.fetchSecondary:二级节点本层版本字段也并入(此前只换 channellist/agentlist 会丢节点自身版本/下载地址)

清理/抗回归
- 跨域白名单路径改用 ResourceManager.resourceRoot()(单一来源,避免与领域层路径定义漂移致 V2 回归)
- 前后台订阅移到 setupBridge(桥就位后),避免冷启动 FOREGROUND 早于桥到达被丢
- prepareBuiltin 单次读取内置包(去掉"先整包读探测、再整包读拷贝"双读)
- LocalUploadServer 删死码 isRunning/running
- 文档化延期项:weburl 分支不注入 app_data(同 Android,远程大厅自带)、app_gamename 待 M3 扩展 VersionXml、
  子游戏切换 firstProgressDone 复位 TODO(M3 T-M3-06)

devecocli build 通过;单测全通过;模拟器复测 app_data/V2 fetch/桥双向/特殊字符往返均无回归。
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
lanterngamescn
2026-06-25 19:05:46 +08:00
parent 40b8c2e91c
commit 4590d5bf70
9 changed files with 202 additions and 64 deletions
+28
View File
@@ -111,5 +111,33 @@ export default function versionResolverTest() {
const d: VersionDecision = VersionResolver.resolve(remote, keys);
expect(d.gameDownload).assertEqual('keep');
});
it('empty_gameid_skips_game_tree', 0, () => {
// 大厅 gameid='' 不应误命中 gamelist 中 gameid 为空的节点
const lobbyKeys: MatchKeys = { agentid: 'A1', channelid: 'C1', marketid: 'M1', gameid: '' };
const remote: RemoteConfig = {
gamelist: [{ gameid: '', game_version: '99', game_download: 'should_not_match',
agentlist: [{ agentid: 'A1', game_version: '99', game_download: 'should_not_match' }] }],
agentlist: [{ agentid: 'A1', game_version: '10', game_download: 'agent_only',
channellist: [{ channelid: 'C1',
marketlist: [{ marketid: 'M1',
gamelist: [{ gameid: '', game_version: '88', game_download: 'should_not_match' }] }] }] }],
};
const d: VersionDecision = VersionResolver.resolve(remote, lobbyKeys);
expect(d.gameVersion).assertEqual(10);
expect(d.gameDownload).assertEqual('agent_only');
});
it('tie_prefers_nonempty_download', 0, () => {
// 两树 game_version 相等(20),代理树只有版本号无下载地址 → 应取游戏树的下载地址
const remote: RemoteConfig = {
agentlist: [{ agentid: 'A1', game_version: '20' }],
gamelist: [{ gameid: 'G1',
agentlist: [{ agentid: 'A1', game_version: '20', game_download: 'game_url' }] }],
};
const d: VersionDecision = VersionResolver.resolve(remote, keys);
expect(d.gameVersion).assertEqual(20);
expect(d.gameDownload).assertEqual('game_url');
});
});
}