# IGP Unity Mirror Transport

这是 `IGP Unity Multiplayer` 包内给已使用 Mirror 项目提供的可选集成。

它的职责很单一：

- 提供一个可以直接挂到 Mirror `Transport` 槽位里的 `IGPMirrorTransport`
- 把 Mirror 的连接意图、收发和断开动作桥接到 `IGPMultiplayerRuntime`

Mirror 只使用 Multiplayer 根据显式 RoomNode descriptor 建立的 Game lane。
它不解析 Room/Lobby control payload，不执行房间命令，也不维护 room snapshot；Mirror 不
创建独立的 KCP/TCP 连接。

Mirror 不是 Multiplayer 的必需依赖；没有 Mirror 时，Realtime、KCP/TCP/UDP
Game data plane 仍可正常编译和使用。

要用它，你仍然需要先接入：

- `cn.indiegp.sdk.unity`
- Mirror

## 当前包内容

- 包名：`cn.indiegp.sdk.unity.multiplayer`
- 运行时程序集：`IGP.Multiplayer.Mirror`
- 组件：`IGP.Multiplayer.Mirror.IGPMirrorTransport`

## 安装

先保证项目里已经有：

- `cn.indiegp.sdk.unity`
- Mirror

Mirror 版本要求：

- 最低兼容 `v89.0.0`
- 推荐使用 `v90.0.0` 或更高版本

默认接法会兼容 `v89.0.0` 之后的 Mirror，不需要额外宏定义。`IGP_MIRROR_HAS_SERVER_CONNECTED_WITH_ADDRESS` 只是备用开关，只有在你确认项目里的 Mirror 已经包含新版连接回调，并且明确要让本包直接调用它时才需要添加。

然后再把这个包加进 Unity 工程。

本仓库本地源码接法：

```json
{
  "dependencies": {
    "cn.indiegp.sdk.unity": "file:../../../../adapters/unity/Runtime/IGP.UnitySDK",
    "cn.indiegp.sdk.unity.multiplayer": "file:../../../../adapters/unity/Runtime/IGP.UnitySDK.Multiplayer"
  }
}
```

## 用法

1. 在同一个 runtime root 上放好 `IGPRuntimeManager` 和 `IGPMultiplayerRuntime`
2. 再把 `IGPMirrorTransport` 挂到网络对象上，并显式绑定 `IGPMultiplayerRuntime`
3. 在 Mirror 的 `NetworkManager.transport` 上指向它
4. host / client 启动后，Mirror 的消息会通过 IGP 房间数据面转发

如果你想直接照着接，优先看：

- `Documentation~/MIRROR-QUICKSTART.md`

## Reliable application frame

`IGPMirrorTransport` 会先按 peer 保存 reliable Mirror payload，再按 `Reliable Send Rate` 构建 `IGMF v1` application frame。发送频率默认 `30 Hz`，允许范围为 `1..60 Hz`。每个 peer 每 Tick 最多提交一个 frame，Unity 卡帧后不会补发多个历史 Tick。

可以在 Inspector 修改 `Reliable Send Rate`，也可以在代码里设置；越界值会被限制到有效范围：

```csharp
transport.ReliableSendRate = 60;
```

`GetBatchThreshold(Channels.Reliable)` 固定返回 `1024`，只控制 Mirror 的小消息预合批。旧 `ReliableBatchThresholdBytes` 属性仅保留源码兼容，不再改变行为。单条 reliable 大消息仍可按 `GetMaxPacketSize()` 的上限进入 SDK 可靠分片链路。

## Unreliable UDP 通道

Mirror 的 `channelId` 是逻辑通道，KCP/TCP/UDP 是实际物理通道，两者不要混用：

| Mirror 逻辑通道 | 物理通道 | 不可用行为 | 对端收到的逻辑通道 |
| --- | --- | --- | --- |
| `Channels.Reliable` | 当前 KCP 或 TCP | 无 | `Channels.Reliable` |
| `Channels.Unreliable` | UDP | 返回 unavailable 并丢弃，不回退 | `Channels.Unreliable` |

`IGPMirrorTransport` 默认开启 `Use UDP Unreliable Lane`。目标实现由调用方先把
RoomNode descriptor 交给 `IGPMultiplayerRuntime`；Mirror 的 `ClientConnect` /
`ServerStart` 只记录逻辑连接意图。UDP descriptor 缺失不影响 reliable Ready；
实际发送 `Channels.Unreliable` 时，如果 UDP 未连接，transport 会输出 Warning、
返回 unavailable 并丢弃该包，不会转换到 KCP 或 TCP。
`GetMaxPacketSize(Channels.Unreliable)` 使用服务端协商的 UDP payload 上限，
默认 `1200` 字节。

内部 `41014` 表示 reliable `IGMF v1` application frame，`41011` 表示 UDP unreliable payload。`41010`、`41012`、`41013` 只保留接收兼容。Arena 对这些值按 opaque metadata 透明转发，不根据它们选择 KCP 或 UDP。

可以在 Inspector 里关闭 `Use UDP Unreliable Lane`，或在代码里设置：

```csharp
transport.UseRawUdpUnreliableLane = false;
```

关闭后 unreliable 发送会返回 unavailable 并丢弃，不会回退到 reliable transport。

## 自动 frame 压缩

每个完整 application frame 正文都会使用 `Deflate/Fastest` 尝试压缩一次；只有最终 wire size 更小时才采用压缩结果。压缩结果与待发送 frame 一起缓存，底层返回 `WouldBlock` 时不会重复压缩。

压缩不是开关，也没有用户可配置阈值。旧 `EnablePayloadCompression`、`CompressionThresholdBytes` 和 `MinCompressionRatio` 属性只保留源码兼容，不再改变发送策略。双方必须在 MirrorTransport 逻辑连接握手中支持 `ApplicationFrameV1`。

## 统一日志

`IGPMirrorTransport` 默认不单独打印日志。它统一跟随绑定的 `IGPMultiplayerRuntime.LogLevel`：只有共享的 `IGPConfig -> Log Level` 选择非 `Off` 等级时，transport 才会通过 SDK Core 的统一日志入口写入 Unity Console；没有绑定 Multiplayer Runtime 时也不打印日志。连接请求、连接确认、连接建立、断开和服务端启停等低频流程属于 `Info`；普通 payload 收发、队列 drain 和压缩成功不逐条打印，`Warning` / `Error` 仍按对应等级输出。

MirrorTransport 日志统一显示为 `[IGP SDK]`，并带有 `scope=mirror-transport`。MirrorTransport 绑定后通过 Multiplayer 的 internal backend 消费处理后的数据、连接状态和 Peer Activity，不依赖游戏公开事件。`WouldBlock` 时 frame 会保留在队首重试；队列超限、派发停滞和大包等异常仍会通过节流后的 Warning 输出。

payload 发送失败摘要会分别记录 `logicalChannel=reliable/unreliable` 与 `effectiveTransport=kcp/tcp/raw-udp`。UDP 不可用时记录 `unavailable reason=udp_unreliable_not_ready`。

Multiplayer backend 离开 `Ready` 时，`IGPMirrorTransport` 会立即清空待派发的入站消息，并对已建立的客户端和服务端逻辑连接各派发一次 Mirror 断开回调；底层恢复后不会自动重启 `Mirror.NetworkManager`。

合法 reliable payload 的原始大小超过 `16 KiB`，或线上 payload 超过约 `12 KiB` 需要可靠分片时，transport 会记录 `kind=large-payload-send` / `kind=large-payload-receive` Warning。Mirror payload 入站队列硬限制为全局 `4096` 条/`16 MiB`、单玩家 `1024` 条/`4 MiB`；peer lifecycle 队列独立限制为 `256` 条/`1 MiB`。每次 EarlyUpdate 最多派发 `256` 条 payload、`1 MiB` 或 `2 ms`。unreliable 溢出会丢弃并计数，reliable 入站溢出只断开对应逻辑连接。可靠出站队列按 peer 限制为 `1024` 条、`4 MiB`、最老 `15 秒`；`kErrorWouldBlock` 会保留已构建 frame 并在后续 Tick 重试。

## 适用范围

当前这块主要面向：

- Mirror 项目要把传输层接到 IGP
- 已经通过 `IGPMultiplayerRuntime` 接入 descriptor-driven RoomNode Game data plane
- 想直接在 Mirror `Transport` 位置里挂 IGP 传输层

如果你只是先验证 SDK 主链路，仍然先看：

- `adapters/unity/Runtime/IGP.UnitySDK/Documentation~/QUICKSTART.md`

如果你想看给 Mirror 项目的完整示例工程，直接看：

- `samples/unity/2022.3.62f3c1/sample/Examples/MirrorTransportExample.cs.txt`
