@virjay/playscope-cocos 0.0.0-stage → 0.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (92) hide show
  1. package/CHANGELOG.md +30 -0
  2. package/README.md +314 -2
  3. package/USAGE.md +372 -0
  4. package/VALIDATION.md +93 -0
  5. package/dist/adapters/levelplay/index.d.ts +15 -0
  6. package/dist/adapters/levelplay/index.js +14 -0
  7. package/dist/cocos/Storage.d.ts +16 -0
  8. package/dist/cocos/Storage.js +16 -0
  9. package/dist/cocos/XhrJsonTransport.d.ts +8 -0
  10. package/dist/cocos/XhrJsonTransport.js +65 -0
  11. package/dist/cocos/index.d.ts +48 -0
  12. package/dist/cocos/index.js +134 -0
  13. package/dist/core/ClientOwnership.d.ts +6 -0
  14. package/dist/core/ClientOwnership.js +39 -0
  15. package/dist/core/ClientScope.d.ts +5 -0
  16. package/dist/core/ClientScope.js +34 -0
  17. package/dist/core/ConfigurationSync.d.ts +18 -0
  18. package/dist/core/ConfigurationSync.js +66 -0
  19. package/dist/core/FactEncoding.d.ts +10 -0
  20. package/dist/core/FactEncoding.js +29 -0
  21. package/dist/core/HttpError.d.ts +10 -0
  22. package/dist/core/HttpError.js +17 -0
  23. package/dist/core/OutboxStore.d.ts +31 -0
  24. package/dist/core/OutboxStore.js +228 -0
  25. package/dist/core/PersistentState.d.ts +33 -0
  26. package/dist/core/PersistentState.js +92 -0
  27. package/dist/core/PlayScopeClient.d.ts +65 -0
  28. package/dist/core/PlayScopeClient.js +422 -0
  29. package/dist/core/QueueAdmission.d.ts +4 -0
  30. package/dist/core/QueueAdmission.js +29 -0
  31. package/dist/core/index.d.ts +7 -0
  32. package/dist/core/index.js +27 -0
  33. package/dist/core/revenue.d.ts +33 -0
  34. package/dist/core/revenue.js +23 -0
  35. package/dist/core/types.d.ts +155 -0
  36. package/dist/core/types.js +2 -0
  37. package/dist/core/uuid.d.ts +1 -0
  38. package/dist/core/uuid.js +9 -0
  39. package/dist/esm/adapters/levelplay/index.js +11 -0
  40. package/dist/esm/cocos/Storage.js +12 -0
  41. package/dist/esm/cocos/XhrJsonTransport.js +61 -0
  42. package/dist/esm/cocos/index.js +115 -0
  43. package/dist/esm/core/ClientOwnership.js +34 -0
  44. package/dist/esm/core/ClientScope.js +30 -0
  45. package/dist/esm/core/ConfigurationSync.js +62 -0
  46. package/dist/esm/core/FactEncoding.js +24 -0
  47. package/dist/esm/core/HttpError.js +12 -0
  48. package/dist/esm/core/OutboxStore.js +224 -0
  49. package/dist/esm/core/PersistentState.js +85 -0
  50. package/dist/esm/core/PlayScopeClient.js +415 -0
  51. package/dist/esm/core/QueueAdmission.js +25 -0
  52. package/dist/esm/core/index.js +7 -0
  53. package/dist/esm/core/revenue.js +20 -0
  54. package/dist/esm/core/types.js +1 -0
  55. package/dist/esm/core/uuid.js +6 -0
  56. package/dist/esm/package.json +1 -0
  57. package/dist/esm/protocols/game-v1/index.js +21 -0
  58. package/dist/protocols/game-v1/index.d.ts +8 -0
  59. package/dist/protocols/game-v1/index.js +25 -0
  60. package/doc/README.md +4 -0
  61. package/doc//344/275/277/347/224/250/346/214/207/345/215/227/README.md +585 -0
  62. package/doc//344/275/277/347/224/250/346/214/207/345/215/227//345/217/202/350/200/203/350/265/204/346/226/231/0.1.1-types/adapters/levelplay/index.d.ts +15 -0
  63. package/doc//344/275/277/347/224/250/346/214/207/345/215/227//345/217/202/350/200/203/350/265/204/346/226/231/0.1.1-types/cocos/Storage.d.ts +13 -0
  64. package/doc//344/275/277/347/224/250/346/214/207/345/215/227//345/217/202/350/200/203/350/265/204/346/226/231/0.1.1-types/cocos/XhrJsonTransport.d.ts +8 -0
  65. package/doc//344/275/277/347/224/250/346/214/207/345/215/227//345/217/202/350/200/203/350/265/204/346/226/231/0.1.1-types/cocos/index.d.ts +38 -0
  66. package/doc//344/275/277/347/224/250/346/214/207/345/215/227//345/217/202/350/200/203/350/265/204/346/226/231/0.1.1-types/core/PlayScopeClient.d.ts +46 -0
  67. package/doc//344/275/277/347/224/250/346/214/207/345/215/227//345/217/202/350/200/203/350/265/204/346/226/231/0.1.1-types/core/index.d.ts +3 -0
  68. package/doc//344/275/277/347/224/250/346/214/207/345/215/227//345/217/202/350/200/203/350/265/204/346/226/231/0.1.1-types/core/revenue.d.ts +25 -0
  69. package/doc//344/275/277/347/224/250/346/214/207/345/215/227//345/217/202/350/200/203/350/265/204/346/226/231/0.1.1-types/core/types.d.ts +81 -0
  70. package/doc//344/275/277/347/224/250/346/214/207/345/215/227//345/217/202/350/200/203/350/265/204/346/226/231/exports.json +89 -0
  71. package/doc//344/275/277/347/224/250/346/214/207/345/215/227//345/217/202/350/200/203/350/265/204/346/226/231/public-api.d.ts +281 -0
  72. package/doc//344/275/277/347/224/250/346/214/207/345/215/227//345/217/202/350/200/203/350/265/204/346/226/231//346/234/215/345/212/241/347/253/257/client-context.schema.json +103 -0
  73. package/doc//344/275/277/347/224/250/346/214/207/345/215/227//345/217/202/350/200/203/350/265/204/346/226/231//346/234/215/345/212/241/347/253/257/game-ad-properties.json +765 -0
  74. package/doc//344/275/277/347/224/250/346/214/207/345/215/227//345/217/202/350/200/203/350/265/204/346/226/231//346/234/215/345/212/241/347/253/257/game-builtin-properties.json +479 -0
  75. package/doc//344/275/277/347/224/250/346/214/207/345/215/227//345/217/202/350/200/203/350/265/204/346/226/231//346/234/215/345/212/241/347/253/257/game-event.json +278 -0
  76. package/doc//344/275/277/347/224/250/346/214/207/345/215/227//345/217/202/350/200/203/350/265/204/346/226/231//346/234/215/345/212/241/347/253/257/product-events.json +2878 -0
  77. package/doc//344/275/277/347/224/250/346/214/207/345/215/227//345/217/202/350/200/203/350/265/204/346/226/231//346/234/215/345/212/241/347/253/257/standard-contract.json +183 -0
  78. package/doc//344/275/277/347/224/250/346/214/207/345/215/227//345/217/202/350/200/203/350/265/204/346/226/231//346/234/215/345/212/241/347/253/257//344/272/247/345/223/201/344/272/213/344/273/266/347/264/242/345/274/225.md +87 -0
  79. package/doc//344/275/277/347/224/250/346/214/207/345/215/227//345/217/202/350/200/203/350/265/204/346/226/231//346/235/245/346/272/220/344/270/216/347/211/210/346/234/254.json +35 -0
  80. package/doc//344/275/277/347/224/250/346/214/207/345/215/227//347/244/272/344/276/213/CoreIntegration.ts +13 -0
  81. package/doc//344/275/277/347/224/250/346/214/207/345/215/227//347/244/272/344/276/213/LevelPlayTelemetry.ts +119 -0
  82. package/doc//344/275/277/347/224/250/346/214/207/345/215/227//347/244/272/344/276/213/PlayScopeExample.ts +46 -0
  83. package/doc//344/275/277/347/224/250/346/214/207/345/215/227//347/244/272/344/276/213/QuestGarden.ts +129 -0
  84. package/doc//344/275/277/347/224/250/346/214/207/345/215/227//347/244/272/344/276/213/README.md +23 -0
  85. package/doc//344/275/277/347/224/250/346/214/207/345/215/227//347/244/272/344/276/213/project-fixture.json +188 -0
  86. package/doc//344/275/277/347/224/250/346/214/207/345/215/227//347/244/272/344/276/213/tsconfig.json +20 -0
  87. package/examples/cocos-minimal/PlayScopeExample.ts +27 -0
  88. package/examples/quest-garden/QuestGarden.ts +129 -0
  89. package/examples/quest-garden/README.md +180 -0
  90. package/examples/quest-garden/project-fixture.json +188 -0
  91. package/examples/quest-garden/tsconfig.json +12 -0
  92. package/package.json +73 -4
package/CHANGELOG.md ADDED
@@ -0,0 +1,30 @@
1
+ # Changelog
2
+
3
+ ## 0.2.0 — 2026-11-12
4
+
5
+ 首次以 @virjay/playscope-cocos 分发。旧 @playscope/cocos 0.1.1 tarball 不包含下列变更。升级时需更换包名并保留原存储键;无 scope 历史状态需要明确来源声明。
6
+
7
+ - 读取故障、损坏、未知版本拒绝初始化并保留原状态;旧无版本状态显式迁移,保留身份、序号及事件原文。同一运行时拒绝相同存储 scope/key 的重复 writer。
8
+ - 持久状态绑定服务器、项目和环境;冲突在宿主回调、写入及网络前拒绝。**兼容变化:无 scope 历史状态必须显式提供匹配的 legacyScope 来源声明。** 元数据在构造时复制。
9
+ - 增加默认 4 MiB 待传/隔离事实预算及可选分段持久化:v2 索引、不可变段、写前清理日志和恢复。默认保留快照;v2 不能直接降级。超大历史事件和单事件 413 保留原文隔离。
10
+ - 队列保留策略交给宿主;收入事实与 game-v1 编码分离,新增 protocols/game-v1 子路径,保留兼容 helper 及 Cocos 自定义编码器。
11
+ - 配置同步独立调度、合并请求和退避;新增 trackWithResult、flushWithResult、refreshConfiguration、retryUploads 及 status,保留旧入口。交付与配置共享鉴权,ACK 检查点失败停止继续交付。
12
+ - 销毁及迟到响应不能覆写替代实例。Cocos 部分监听注册失败会清理资源;取消监听抛错仍尝试其余清理并释放 writer;XHR 初始化、响应读取或 abort 失败仍释放请求并结束等待。
13
+ - 新增只导入公共入口的 Quest Garden 源码宿主、独立事件/收入/配置 fixture;不包含 Creator 场景或游戏存档,自定义收入不自动进入标准收入看板。
14
+ - 提供不生成 dist 的源码测试、示例 noEmit 和 Outbox 基准;修复 SMB 路径别名下的示例入口映射。已有服务二进制验证工具覆盖真实入库、进程恢复、分段提交及合成旧状态迁移中断,共用 worker 管理和文件存储。
15
+
16
+ - 增加 doc/使用指南 完整交接资料:公开接口全集、旧包声明、事件契约、完整广告映射、配置示例与验收清单,随包分发。
17
+
18
+ 当前结果及发布边界统一见 [VALIDATION.md](VALIDATION.md),验证工具不替代新包入口或设备验收。
19
+
20
+ ## 0.1.1
21
+
22
+ - 发布独立 ESM 入口和 module 字段,修复 Creator 3.8 的 CommonJS 转发导出识别失败;保留 CommonJS 入口。
23
+ - 对两种模块入口增加实际导入及持久队列验证。
24
+
25
+ ## 0.1.0
26
+
27
+ - 抽取 API v1 安装身份、持久队列、鉴权、ACK 和重试,兼容 BlockDrop 旧状态与队列原文。
28
+ - 提供 Cocos 存储、XHR、生命周期和采集开关,业务语义由宿主维护。
29
+ - 提供可选 LevelPlay 收入归一与持久去重入口。
30
+ - 加入独立测试、最小 Cocos 接入示例和本地隔离服务验收脚本。
package/README.md CHANGED
@@ -1,3 +1,315 @@
1
- # Temporary Holding Version
1
+ # PlayScope Cocos SDK
2
2
 
3
- This version is a temporary placeholder for this package. An operational version to replace this has been submitted for review and is awaiting a staged release.
3
+ `@virjay/playscope-cocos` 当前版本为 **0.2.0**,使用 PlayScope API v1,版本独立于服务端。
4
+ 核心不依赖 BlockDrop、Cocos、游戏配置或 Node 运行时,包内没有项目凭据。
5
+
6
+ 首次接入请阅读 [完整使用指南](doc/使用指南/README.md),快速示例见 [USAGE.md](USAGE.md)。
7
+ 完整指南附带全部公开类型、事件字典、配置/广告示例与验收清单。
8
+
9
+ ```sh
10
+ npm install @virjay/playscope-cocos@0.2.0 --save-exact
11
+ ```
12
+
13
+ 包提供 ESM/CommonJS 和类型声明,包含 core、adapters/levelplay、protocols/game-v1 子路径。
14
+ 完整 doc/、examples/ 和验证记录随包分发;维护工具和测试脚本仅在源码工作区中提供。
15
+ SDK 使用 UNLICENSED 元数据,本次未更改许可条款。
16
+
17
+ ## 从旧包迁移
18
+
19
+ BlockDrop 目前仍固定旧 `@playscope/cocos` 0.1.1 tarball,本次发布不自动升级游戏依赖。
20
+ 使用新包时需把导入路径改为 `@virjay/playscope-cocos` 及对应子路径,保留历史 storageKey、身份、队列和配置。
21
+ 新包包含结构化结果、独立配置调度、宿主队列策略、4 MiB 默认事实预算、可选分段存储及 scope 校验。
22
+ 已有无 scope 状态必须先核实历史来源,再提供匹配的 legacyScope。不能清键、重建身份或改写旧队列规避迁移。
23
+ 默认仍使用快照;v2 分段不能直接降级旧包。发布与设备验证边界见 [VALIDATION.md](VALIDATION.md)。
24
+
25
+ ## 最小接入
26
+
27
+ [最小 Cocos 示例](examples/cocos-minimal/PlayScopeExample.ts) 展示初始化、生命周期和释放:
28
+
29
+ ```ts
30
+ import { sys } from 'cc';
31
+ import { init } from '@virjay/playscope-cocos';
32
+
33
+ const telemetry = init({
34
+ baseUrl: 'https://analytics.example.com',
35
+ projectId: '后台项目 UUID',
36
+ projectKey: '项目客户端 Key',
37
+ collectionEnabled: true, // 宿主根据授权状态明确传入
38
+ client: { appVersion: '1.0.0', buildNumber: '1', platform: 'android',
39
+ locale: 'zh-CN', timezoneOffsetMinutes: -new Date().getTimezoneOffset() },
40
+ }, { storage: sys.localStorage });
41
+ ```
42
+
43
+ 客户端只能使用项目 Key,不能填管理 Token。测试与正式用独立项目,Web Origin 需在后台登记。
44
+ `collectionEnabled: false` 时不创建身份、不读写存储、不注册监听、不发请求。
45
+ 授权状态变化时先 dispose,再按新的状态初始化,已有队列和身份保留。
46
+ 同一项目同一存储只允许一个活跃写入实例,由常驻宿主持有。
47
+
48
+ - `init`:恢复身份,登记一次 user_first_open,尝试上传;其他业务事件由宿主按项目契约上报。
49
+ - `track(name, properties, { run, occurredAt })`:冻结事件原文并入队;名称、属性和 run 必须符合该项目冻结契约。
50
+ - `trackAdRevenue(fact, context)`:统一收入字段和重复回调处理。
51
+ - `newSession()`:只更新会话 ID,会话业务边界由宿主决定。
52
+ - `update(dt)`:常驻组件每帧调用,每 5 秒尝试 flush;注入的前后台监听也只触发 flush。
53
+ - `flush()`:异步上传,不阻塞游戏,不保证系统杀进程前已完成请求。
54
+ - `getDiagnostics()`:pending、rejected、droppedEvents、lastError、blocked,用于开发排障。
55
+ - `dispose()`:释放监听、中止网络请求,保留持久状态。源码版本会尝试全部清理,再抛出第一个宿主清理错误;监听注册失败也会释放已取得资源。
56
+
57
+ SDK 不默认采集 ANDROID_ID,不自动把广告弹层当成退出会话,不判断游戏发奖结果。
58
+ 核心从包根入口导出 PlayScopeClient,供已有生命周期和业务层的游戏直接使用。
59
+ BlockDrop 就使用核心及现有宿主调度,不再创建第二个 CocosPlayScope 实例。
60
+
61
+ ## LevelPlay 收入转发
62
+
63
+ 已有原生桥接在唯一的 impression 回调入口调用下面的适配器,不重复覆盖广告监听:
64
+
65
+ ```ts
66
+ import { forwardLevelPlayRevenue } from '@virjay/playscope-cocos/adapters/levelplay';
67
+
68
+ forwardLevelPlayRevenue(telemetry, {
69
+ revenue: impression.revenue, precision: impression.precision,
70
+ adNetwork: impression.adNetwork, mediationAdUnitId: impression.mediationAdUnitId,
71
+ country: impression.country,
72
+ }, {
73
+ exposureId: originalExposure.id,
74
+ placement: 'banner', businessPlacement: 'cover_banner', format: 'banner',
75
+ isTest: true, shownAt: originalExposure.shownAt,
76
+ adConfigRevision: originalExposure.adConfigRevision,
77
+ }, { run: originalExposure.run });
78
+ ```
79
+
80
+ impression 与 originalExposure 由游戏提供。每次横幅刷新必须使用独立曝光 ID;
81
+ 迟到收入仍传原曝光的对局、配置和展示时间,不能绑定当前对局。
82
+ SDK 不安装 Android/iOS 广告库,不负责原生曝光绑定、加载、展示、点击和发奖。
83
+ 其他游戏需将自身原生 impression 回调接到此入口。BlockDrop 仍走已有 GameTelemetry 收入入口,
84
+ 不要再为同一笔收入调用适配器。
85
+
86
+ 仅接受 USD 原值,保留精度;未知值不补零,合法零保留,负数/非有限数值不发。
87
+ 测试、演示和模拟标记由宿主提供。按 provider + exposureId 持久保留最近 2000 个去重键;
88
+ 超出窗口后仍依赖宿主避免重复曝光。SDK 不提供广告平台最终结算对账。
89
+
90
+ ## 配置与兼容迁移
91
+
92
+ 核心可选 restoreCache / resolveConfiguration 由宿主校验其业务 schema,
93
+ ConfigurationContext.request 使用安装 Token,saveCache 保存宿主验证后的缓存。
94
+ 未配置适配器时不请求远程配置;SDK 不解释难度、关卡或广告策略。
95
+
96
+ 新项目键为 playscope:<projectId>。迁移必须显式保留原 storageKey。
97
+ BlockDrop 继续使用 blockdrop:playscope:<projectId>,保留 installId、firstOpenedAt、firstOpenTracked、
98
+ sequence、Token、cache、queue;只转换新事件,绝不改写旧队列,包括旧 MAX 事件。
99
+
100
+ 持久化检查成功后才上传,只删除 ACK 接受或明确永久拒收的事件。
101
+ 断网、丢 ACK、部分确认保留原 eventId 和原文;401 更新 Token,429 遵守 Retry-After,413 缩小批次。
102
+ 明确的协议/授权错误通过 blocked 暂停重试。
103
+ 通用核心默认最多 2000 条;BlockDrop 显式将 batch_generated 阈值设置为 1800。
104
+ 满时拒绝新事件并计入 droppedEvents,不驱逐待确认事实。0.2.0另有下述字节限制。
105
+ track 返回 true 表示已接收入内存队列;存储不可用时诊断 storage_unavailable 并暂停上传,
106
+ 尚未落盘的事件仍可能在系统杀进程时丢失。
107
+
108
+ ## 验收
109
+
110
+ npm test 覆盖旧队列重放、丢 ACK 重建、部分 ACK、401/429/413、存储故障、项目隔离、
111
+ LevelPlay 逆序/重复/零收入、采集关闭和监听释放。
112
+ `node tools/verify-local.mjs` 使用打包产物和临时本地 PlayScope 服务验证真实入库,
113
+ 需先在仓库根目录 make build;不读取 .env,不连接正式服务。
114
+ BlockDrop 另有迁移前客户端生成的状态 fixture 和完整业务回归。
115
+ 真实游戏内新原生收入验收仍受无填充限制,模拟回调通过不能替代真机收入验收。
116
+
117
+ ## 0.2.0 的可靠性增强
118
+
119
+ 源码维护者可用 `npm run test:source` 在内存加载 TypeScript 回归;`npm run check` 仅做 noEmit。包消费者直接使用已构建产物。
120
+
121
+ 存储端口必须让读取故障抛出异常,只有明确没有该键才返回 null。构造失败时抛出
122
+ `PlayScopeInitializationError`,code 为 storage_unavailable / state_corrupt /
123
+ state_version_unsupported / cache_restore_failed / storage_capability_missing /
124
+ scope_mismatch / scope_migration_required;只有 storage_unavailable 的 retryable 为 true。
125
+ 读取恢复后可重新创建客户端;失败的构造不会占用 writer 或覆盖原状态。损坏或未来版本状态
126
+ 需要宿主恢复数据或升级兼容版本,SDK 不自动清空、重置身份或重新上报首次打开。
127
+
128
+ 默认快照使用 schemaVersion 1,旧版无版本状态显式迁移。迁移保留存储键、installId、
129
+ firstOpenedAt、firstOpenTracked、sequence、有效 Token、配置缓存和队列原文。
130
+ 旧 0.1.1 不识别隔离区,直接降级会在下次持久化时丢弃该字段;发生隔离后,回退必须先
131
+ 导出并迁移这些记录。此版本字段提供向前读取保护,不代表旧版本自动获得降级兼容能力。
132
+
133
+ 一个 JS runtime 中,相同存储 scope / key 的第二个实例抛出 PlayScopeOwnershipError。
134
+ 自定义包装器若共享同一底层存储,应提供同一个 ownershipScope 对象;CocosStorage 已提供。
135
+ 这不提供跨标签页 / 跨进程锁,相关宿主仍须保证单 writer。旧实例 dispose 后释放所有权,
136
+ 重复 dispose 和迟到响应不能写入替代实例的状态。
137
+
138
+ track 在宿主 transform 后,按最终事件加批次信封检查 250 KiB 预算;超限返回 false,
139
+ droppedEvents 增加、lastError 为 event_too_large,不消费去重键。
140
+ 历史队列中的超大事件以及服务端明确返回单事件 413 的事实移入持久 quarantined 区;
141
+ 单事件 413 沿用退避,下次 flush 继续后面的事件,不因此设置全局 blocked。
142
+ Token / 配置端点的 413 不会误隔离事件。
143
+
144
+ diagnostics.quarantined / getDiagnostics().quarantined 提供数量;getQuarantinedEvents()
145
+ 返回原事件、原因与时间的副本。隔离不是 ACK 或服务端入库成功,不自动重传或删除;
146
+ 原文与待传队列共用存储容量,0.2.0已增加下述字节预算;隔离区处置 API 尚未实现。
147
+ 输出诊断时不要直接打印包含业务数据的隔离事件原文。
148
+
149
+ ## 0.2.0 的业务解耦
150
+
151
+ 默认队列上限仍为 2000,核心对所有事件名使用同一规则。游戏可显式预留容量:
152
+
153
+ ```ts
154
+ const queuePolicy = { maxEvents: 2000, eventLimits: { ambient_tick: 1800 } };
155
+ // 在 PlayScopeOptions / CocosOptions 中传入 queuePolicy。
156
+ ```
157
+
158
+ eventLimits 按 track 的输入事件名判断,发生在 transform 之前,值是拒绝新事件时的
159
+ 队列长度(不是该类事件自身计数)。0 表示拒绝该输入事件;未配置的名字使用 maxEvents。
160
+ 初始化时复制策略;之后改 options 不会悄悄改变资格。较小策略不会删除恢复的旧队列。
161
+ BlockDrop 接入层显式传入 batch_generated: 1800,保留现有 200 条空间的行为。
162
+
163
+ AdPlacement 现在是宿主字符串;可选 GameV1AdPlacement 在协议模块中保留旧词汇类型。
164
+ 核心 enqueueAdRevenue 只校验收入事实与提供曝光去重,显式编码器负责事件名和字段:
165
+
166
+ ```ts
167
+ import { enqueueAdRevenue } from '@virjay/playscope-cocos/core';
168
+ import { encodeGameV1AdRevenue } from '@virjay/playscope-cocos/protocols/game-v1';
169
+
170
+ enqueueAdRevenue(client, originalExposure, { run: originalRun }, encodeGameV1AdRevenue);
171
+ ```
172
+
173
+ 原 trackAdRevenue helper 仍通过 game-v1 编码,现有调用无需改变;/core 出口保留该兼容重导出。
174
+ CocosOptions.adRevenueEncoder 可替换收入编码,未传时默认 game-v1。编码器只执行在新事实
175
+ 入队前,失败 / 拒绝不会消费曝光去重键。变更编码器不会重编码已持久化事件。
176
+ USD 原值、零收入和精度规则保持原契约,不自动兑换币种。
177
+
178
+ 另一款游戏可以使用 quest_board_bonus 等业务位,并提供自己的字段编码;编码结果仍必须
179
+ 符合目标 PlayScope 项目的事件字典。该配置不安装广告监听、不展示广告,也不决定游戏发奖。
180
+
181
+ ## 0.2.0 的独立调度与结果接口
182
+
183
+ 核心和 Cocos 包装均新增以下接口;既有 track 的 boolean 和 flush 的 Promise<void> 保留:
184
+
185
+ | 接口 | 语义 |
186
+ | --- | --- |
187
+ | trackWithResult | accepted 带 eventId 和 durability(persisted / memory);其他状态为 duplicate、queue_full、queue_bytes_full、event_too_large、invalid_event、disposed,Cocos 关闭采集时为 collection_disabled |
188
+ | flushWithResult | 等待当前交付任务,返回状态、accepted / rejected / quarantined / pending、durable、retryAt 和 error |
189
+ | refreshConfiguration | 等待当前配置任务;遵守下一次请求时间,返回 refreshed / not_due / failed / storage_unavailable / disabled / disposed |
190
+ | retryUploads | 宿主修复配置或权限后解除交付 blocked 并尝试发送;不绕过临时失败的 Retry-After,不重建事件或清空队列 |
191
+ | status | 分别提供 collection、delivery、pendingDurability、retryAt 和 configuration 状态;返回快照,不暴露 Token 或事件原文 |
192
+
193
+ flush / flushWithResult 会独立调度到期配置,并等待当前事件交付轮次;配置完成有单独的
194
+ refreshConfiguration 等待入口。事件上传不等待配置响应。配置回调产生的新事件如果错过
195
+ 本轮上传,由下次 flush 发送;不能再把 await flush 当成配置已生效的保证。
196
+ 并发 flushWithResult 返回同一个在途 Promise,旧 flush 调用也等待该任务。
197
+ 每轮仍至多 10 批,completed 表示这一轮结束;pending 可能大于 0,不代表所有事实都已入库。
198
+ accepted / rejected 只统计本轮服务端明确确认的唯一事件;隔离单独统计,绝不算成功上传。
199
+ durable 表示本地状态通过写入及读回验证。ACK 已成功但本地检查点失败时,结果为
200
+ storage_unavailable、durable=false;重启可能重放旧事实,服务端仍须按原 eventId 幂等。
201
+
202
+ 配置有独立的错误和指数退避(60 秒起,最多 1 小时,并遵守更长的 Retry-After)。
203
+ 成功后按照宿主返回值在 60 秒到 24 小时内刷新,无效值默认 1 小时;由宿主继续调用
204
+ flush 或 refreshConfiguration 推进,没有隐藏定时器。配置 HTTP 400 / 401 / 403 / 404 /
205
+ 413 / 429 / 5xx 不设置交付 blocked。配置请求和上传共享安装鉴权;bootstrap 与刷新
206
+ 合并在途请求。项目 Key / bootstrap 本身失败仍会同时影响两条链路,不能绕过鉴权上传。
207
+
208
+ ConfigurationContext.cache 和 client.cache 返回副本;saveCache 写入失败会让配置结果报告
209
+ storage_unavailable。宿主负责业务 schema 验证和生效,配置 refreshed 仅表示宿主回调完成,
210
+ 不代表 SDK 验证了业务内容。dispose 后旧配置上下文不能发请求或覆盖替代实例的缓存。
211
+
212
+ Cocos 的 collectionEnabled 仍是构造时策略:disabled 不访问存储或网络;修改授权状态
213
+ 需先 dispose 再初始化,已有身份与队列保留。status 不暗示已有动态授权、发送暂停或删除接口。
214
+ 默认 XHR 每次请求有 10 秒超时;自定义 transport 和配置回调仍须自己保证可结束。
215
+ 调用方超时 / 取消和可配置交付预算仍待后续。队列字节预算和分段持久化见下一节。
216
+
217
+ ## 0.2.0 的字节预算与分段存储
218
+
219
+ queuePolicy.maxBytes 默认 4 MiB,按最终 wire 事件 JSON 的 UTF-8 字节数计入待传队列,
220
+ 隔离区计入完整隔离记录的 UTF-8 JSON 字节数。它不包含配置、身份、去重键、索引、
221
+ 段信封、数组逗号或迁移临时副本,不能解释为底层存储的硬配额。
222
+ transform 后检查,超过总预算时 trackWithResult 返回 queue_bytes_full、track 返回 false,
223
+ droppedEvents 增加,去重键不消费。总条数上限仍独立生效。
224
+ 恢复的旧数据即使超限也完整保留;隔离可能因附加原因和时间元数据使总量越限。
225
+ 此时暂停新增事实,待 ACK 释放空间或宿主扩大预算,不自动删除历史事实或隔离记录。
226
+ 每次实例初始化复制预算,修改原 options 不会改变现有实例。
227
+
228
+ ```ts
229
+ const telemetry = new CocosPlayScope({
230
+ ...options,
231
+ collectionEnabled: true,
232
+ persistence: 'segmented',
233
+ queuePolicy: { maxEvents: 2000, maxBytes: 4 * 1024 * 1024 },
234
+ }, { storage: sys.localStorage });
235
+ ```
236
+
237
+ 默认 persistence 为 snapshot,兼容现有只提供 getString / setString / flush 的端口。
238
+ segmented 显式要求 removeString;CocosStorage 在底层提供 removeItem 时暴露该能力。
239
+ 读取已有分段格式时,即使未指定或指定 snapshot,也继续使用分段存储,绝不自动降级。
240
+ 缺少严格删除能力时抛出 storage_capability_missing,保留所有数据。该能力必须让错误抛出,
241
+ 不能静默忽略删除失败。端口必须保证单键替换是完整原子操作,flush 和读回验证必须可用。
242
+
243
+ 分段格式在原 storageKey 保存 schemaVersion 2 的身份 / 配置元数据和索引;事件原文存入
244
+ 该键独占的 :outbox:v2: 子键,每段至多 32 条、目标约 32 KiB。单条大历史记录允许独占更大段。
245
+ 每段有类型、条数、UTF-8 大小及校验和;缺失 / 损坏 / 未支持版本拒绝初始化,原数据保留。
246
+ 校验和用于检测意外损坏,不是安全签名。
247
+
248
+ 每次变更先持久化清理日志,再写入并逐段读回校验新段,最后原子写入及校验索引;
249
+ 此前引用的旧段始终保留到新索引提交成功。索引是唯一提交点,同步持久化保证没有被延迟写替代。
250
+ 只有新的或变化的队尾 / ACK 边界段需要编码和写入,未改变的段复用;隔离区使用同一协议。
251
+ 批次大小按缓存的事实编码长度和信封 / 逗号增量计算,避免每加入一条都重新序列化整批。
252
+ 自定义 transport 获得请求副本,不能修改缓存事实及重放内容。
253
+
254
+ 清理在索引提交后执行,失败保留清理日志。当前事实已确认落盘时仍返回 persisted,
255
+ diagnostics.cleanupPending 为 true;下一次持久化必须先完成清理,失败则保持 memory / 暂停上传,
256
+ 避免每次失败不断生成新孤立段。恢复时以实际索引判断可清理键,包括索引已提交但读回失败的情况。
257
+ 清理键必须属于本存储命名空间,不能删除根索引或其他项目数据。
258
+
259
+ 无版本 / v1 迁移先完整校验旧身份、配置和事实,再按上述协议生成新段;提交前保留旧根快照,
260
+ 提交后保持 installId、首次时间 / 标记、sequence、去重键、缓存和事件字段不变。
261
+ 迁移需要额外空间容纳旧快照与新段;配置预算不是迁移的物理空间预留保证。
262
+ 旧 0.1.1 与引入分段存储前的源码均不支持 v2;启用后不能直接换回旧包或手工只改版本字段。
263
+ 回退需离线将索引和所有引用段校验并还原成旧快照,包含隔离记录,不可只复制根键。
264
+ 当前没有提供自动降级或隔离记录删除 API。
265
+
266
+ diagnostics 新增 pendingBytes、quarantinedBytes、maxQueueBytes、persistence 和 cleanupPending。
267
+ 重复事件仍按已有持久去重窗口处理;ACK 后检查点失败依然可能重放,服务端按 eventId 幂等。
268
+ 核心仍在内存中持有全部恢复事实;字节统计 / 段复用扫描仍随条数增长,元数据仍完整写入,
269
+ 启动校验仍读取全部引用段。此阶段减少的是稳态的大 payload 序列化和写入,不是全链路常量开销。
270
+
271
+ 执行 `node --require ./tools/register-typescript.cjs tools/bench-outbox.cjs` 可复现无 dist 的对照。
272
+ 基准为 Map 存储上的 Node track 调用,计量 UTF-8 读写字节与操作次数;不包含真实磁盘 / JSB / 网络。
273
+ 分段模式用更多读写 / flush / 删除调用换取更少大块数据写入,小队列和真机表现需另行验证。
274
+
275
+ ## 0.2.0 的项目与环境绑定
276
+
277
+ 新状态保存 scope={baseUrl, projectId, environment},v1 快照和 v2 分段索引均保留。
278
+ environment 未提供时按 production 绑定;baseUrl 只去除末尾斜线,不合并主机别名、大小写、
279
+ 默认端口或其他路径写法。项目 Key 不属于 scope,允许轮换。
280
+ 初始化先验证 scope,再调用宿主 restoreCache / onIdentityReady、写入或上传。
281
+ 已绑定状态的服务器、项目或环境冲突抛出 scope_mismatch,保留身份、Token、缓存和事实。
282
+ 不能用 legacyScope 重新绑定已标记状态;应恢复原配置,或为新的独立目标使用新命名空间。
283
+
284
+ **兼容性变化:已有无 scope 状态需要显式迁移声明**,否则抛出 scope_migration_required。
285
+ 这同时适用于无版本、v1 和此前的 v2 状态。宿主确认旧状态的实际来源后传入:
286
+
287
+ ```ts
288
+ legacyScope: {
289
+ baseUrl: 'https://original-analytics.example.com',
290
+ projectId: '原项目 UUID',
291
+ environment: 'production',
292
+ }
293
+ ```
294
+
295
+ 声明必须匹配当前目标。SDK 无法从未标记的历史 Token / 队列证明其来源,不能把当前连接
296
+ 参数当作通用自动迁移依据。BlockDrop 的适配层为其历史命名空间显式声明,升级分发前仍需
297
+ 核对旧版实际连接目标。迁移沿用原键和身份,不重编码历史事件;成功后可移除声明。
298
+ 测试与正式应使用独立项目 ID 和存储键;仅添加 environment 不提供服务端项目隔离。
299
+ 旧 0.1.1 或引入 scope 前的源码可能在重写时丢弃此字段,降级不能保证继续执行该绑定保护。
300
+
301
+ Quest Garden 示例使用不同事件、业务广告位、自定义收入编码与平面配置 schema;
302
+ `npm run check:quest-example` 只做示例 noEmit 检查。源码测试中的本机 HTTP 服务是测试协议
303
+ 实现,并非真实 PlayScope 服务或数据库验收。完整边界见示例 README。
304
+ 另可运行 `npm run verify:quest-local -- /absolute/path/to/existing/playscope`,使用已有真实服务
305
+ 和临时数据库注册示例契约、验证持久入库与重启重放。此入口不构建、不安装依赖、不接受
306
+ 外部目标,结果保存到 artifacts/quest-local-verification.json;与测试协议结果分别记录。
307
+ 增加 `--process-restart` 可验证离线落盘与服务提交后、SDK 处理 ACK 前的两次进程终止,
308
+ 第三个进程恢复身份、首次标记和原事实;报告单独写入 artifacts/quest-process-verification.json。
309
+ 进程被杀验证不代表断电、磁盘故障或 Creator 真机验收。
310
+ 另加 `--storage-crash` 可检查分段写入的提交边界;恢复只读取已提交根、清理孤立段,
311
+ 已提交 ACK 不重传旧事实,随后新事实继续交付。此结果写入独立的
312
+ artifacts/quest-storage-crash-verification.json;仍以测试存储的单键完整替换能力为前提。
313
+ `--migration-crash` 使用已知来源的无版本/v1 合成快照,检查多段迁移中断、原 Token/缓存/
314
+ 序号/去重/隔离记录保留和历史事件原文入库;结果写入 artifacts/quest-migration-crash-verification.json。
315
+ 它不代表实际旧游戏可执行文件、生产历史来源或真实设备升级已经验收。