# 两种发送策略

`send(message,{delivery:'reliable'|'latest'})` 返回明确结果。省略 options 默认 reliable；旧 boolean true/false 临时分别映射可靠/最新值。传错策略、空对象或未知字段明确 invalid_delivery，不静默选默认通道。

成功是 `{accepted:true,status:'sent'|'queued',messageId?,bytes?}`；可靠项附messageId/字节数，ID仅在本sender内有效。失败是 `{accepted:false,status:'rejected',reason}`。完整reason：invalid_delivery、invalid_message、not_open、message_too_large、backpressure、expired、closed、send_failed、cancelled。

`sent` 仅表示 `RTCDataChannel.send` 接受，不代表对端收到或执行。`queued` 是本地接受待发。关键操作应有应用 ID、ACK 和重复处理；没拿到ACK时不能凭“reliable”判断已成功。循环结构/BigInt/无可序列化顶层/抛错toJSON明确拒绝；消息按JSON编码后的 UTF-8 字节计量。

| 限制 | reliable | latest |
| --- | --- | --- |
| 单消息 | 64 KiB，且不超过协商的有限正 SCTP 上限 | 同左 |
| 本地待发 | 最多64条、总256 KiB，保持FIFO | 仅最新1条 |
| 原生缓冲 | 下一条加入后不超过256 KiB | 小包加入后不超过 512 B；单个更大消息只在缓冲空时发送 |
| 待发age | 最老项满5秒使未发前缀失败 | 旧待发值超过150ms丢弃 |
| 队列满 | 明确拒绝新消息，绝不覆盖已接受项 | 更新替换旧待发值 |

两个lane独立，可靠队头不会阻塞SDK的latest槽；底层共享网络仍会受实际带宽约束。小包预算允许浏览器尚未将少量已交付字节的计数归零时继续发送，避免每包等待清零。512 B 是本地有效负载预算，不是时延保证；较大消息仍可能占用网络，实际吞吐应单独测量。5秒仅限本地未发队列，不是已经进入原生缓冲的交付TTL。clearStateQueue只清latest，不撤销可靠业务。

可靠项接受后失败逐条通知观察回调：

```js
{delivery:'reliable',messageId,reason:'expired'|'closed'|'send_failed'|'cancelled',
 bytes,ageMs,generation,errorName?}
```

最新值的原生异常通知 `{delivery:'latest',reason:'send_failed',generation,errorName}`；替换/age过期是正常有损策略。errorName为有限枚举，无原始异常message或消息正文。

`onSendError` 只观察。SDK把当前连接的可靠非cancelled失败统一交 `onDisconnect`；不要在两个回调重复关闭/结算。同步 rejected 由调用方处理，先核对当前连接/代际，因为即时原生异常可能已经结束该会话。主动 leave/detach 或替换旧channel会报告 cancelled，但不制造第二次断线。关闭时清timer/listener，旧sender不能结束新连接。

适合reliable：回合/投票、选择/准备、开局/结束确认、checksum。适合latest：可覆盖的位置/光标/快照。一次性按键不能直接当latest丢弃；游戏若要用latest承载输入，需带原始输入历史、序号及确认水位并单独测试丢包恢复。回滚、预测、反作弊与终局权威都属于游戏协议。

[有序事件示例](../examples/delivery.mjs)的序号作用域是整条 transport 代际：同一连接再战继续使用同一实例，不从 1 重置。若游戏另建按局实例，必须在 event 与 ACK 外层同时绑定并校验局编号；只校验 event 内的 roundId 无法阻止旧 ACK 确认新一局的同序号事件。此示例只负责有序事件确认，回合权限、规则和恢复由游戏实现。
