# Achievements Contract

## 目标

定义跨引擎一致的成就能力语义。

这个 contract 只描述：

- 游戏侧应该传什么
- 平台应该返回什么
- 幂等和去重如何理解

它不描述：

- Unity / Unreal 的具体调用方式
- desktop bridge 的 transport 细节

## 当前稳定边界

当前稳定语义还包括：

- 成就能力不要求 room context
- 成就能力不要求先 attach hosted session
- adapter 可以通过 desktop session、其他本地桥或后端路径承载这组能力

换句话说，`Achievements Contract` 描述的是“能力本身”，不是“必须走哪条通道”。

## 能力

### 1. Unlock Achievement

用途：

- 在某个离散事件发生后，直接解锁成就

请求字段：

| 字段 | 类型 | 必填 | 说明 |
| --- | --- | --- | --- |
| `achievementKey` | `string` | 是 | 成就唯一标识 |
| `eventId` | `string` | 否 | 幂等去重键。SDK 缺省生成；同一事件重试时必须复用同一个值 |
| `occurredAtUnixMs` | `int64` | 否 | 业务事件发生时间；缺省时可由平台补当前时间 |
| `appId` | `int32` | 否 | 多应用场景下显式指定应用 |

返回字段：

| 字段 | 类型 | 说明 |
| --- | --- | --- |
| `success` | `bool` | 请求是否成功处理 |
| `duplicated` | `bool` | 是否因幂等去重而识别为重复 |
| `message` | `string` | 面向调试和日志的说明 |

### 2. Report Achievement Progress

用途：

- 上报可累计的进度型成就

请求字段：

| 字段 | 类型 | 必填 | 说明 |
| --- | --- | --- | --- |
| `achievementKey` | `string` | 是 | 成就唯一标识 |
| `progressValue` | `double` | 是 | 进度值。默认表示当前累计值 |
| `progressValueMode` | `"SET" \| "INCREMENT"` | 否 | `SET` 表示当前累计值，`INCREMENT` 表示本次增量；未传时按 `SET` 处理 |
| `progressSourceKey` | `string` | 否 | 进度来源键，用于统计与排障 |
| `eventId` | `string` | 否 | 幂等去重键。SDK 缺省生成；同一事件重试时必须复用同一个值 |
| `occurredAtUnixMs` | `int64` | 否 | 业务事件发生时间 |
| `appId` | `int32` | 否 | 多应用场景下显式指定应用 |

进度上报默认使用 `SET` 语义，也就是 `progressValue` 是游戏当前已累计到的值。只有游戏侧只能拿到“本次增加了多少”时，才显式传 `progressValueMode = "INCREMENT"`；同一增量事件重试时必须复用同一个 `eventId`，避免重复累加。

返回字段：

与 `Unlock Achievement` 相同：

- `success`
- `duplicated`
- `message`

### 3. Clear Achievements

用途：

- 清除当前游戏下玩家已获得的全部成就

请求字段：

| 字段 | 类型 | 必填 | 说明 |
| --- | --- | --- | --- |
| `appId` | `int32` | 否 | 多应用场景下显式指定应用 |

返回字段：

| 字段 | 类型 | 说明 |
| --- | --- | --- |
| `success` | `bool` | 请求是否成功处理 |
| `deletedCount` | `int32` | 本次清除的成就数量 |
| `message` | `string` | 面向调试和日志的说明 |

## 幂等规则

### eventId

- 如果调用方提供 `eventId`，平台必须把它当成幂等键
- 同一 `(appId, achievementKey, eventId)` 重复提交，不应重复记账
- 重复提交时，返回应为：
  - `success = true`
  - `duplicated = true`

### 没有 eventId

- 如果调用方没有提供 `eventId`，平台可以按“至少一次”语义处理
- 这意味着调用方自己要承担重复上报风险

## 错误语义

典型错误：

- `INVALID_INPUT`
- `UNAUTHORIZED`
- `FORBIDDEN`
- `NOT_FOUND`
- `DESKTOP_SESSION_REQUIRED`
- `DESKTOP_USER_CONTEXT_REQUIRED`
- `DESKTOP_SESSION_CAPABILITY_MISSING`
- `INTERNAL_ERROR`

错误的统一语义见：

- [Error Model](./error-model.md)

## 当前 Unity 映射

当前 Unity adapter 已映射到：

- `IGPSDK.UnlockAchievementAsync(...)`
- `IGPSDK.ReportAchievementProgressAsync(...)`
- `IGPSDK.ClearAchievementsAsync(...)`
- `IGPRuntimeManager.UnlockAchievementAsync(...)`
- `IGPRuntimeManager.ReportAchievementProgressAsync(...)`
- `IGPRuntimeManager.ClearAchievementsAsync(...)`

但这些是 adapter 形态，不是 core contract 本身。
