# PIV1 IPC protocol

sidecar 的 stdout 只发送 PIV1 binary messages，stderr 只发送诊断日志。TypeScript 到 sidecar 的控制命令使用 stdin 文本行。两个方向互不混用。

## Binary header

每条 stdout message 由固定 20-byte little-endian header 和可选 payload 组成。

| Offset | Size | Type | Meaning |
| --- | ---: | --- | --- |
| 0 | 4 | ASCII | magic `PIV1` |
| 4 | 1 | u8 | message kind |
| 5 | 1 | u8 | flags，当前为 0 |
| 6 | 2 | u16 LE | READY/FRAME surface width；其他 kind 为 0 |
| 8 | 2 | u16 LE | READY/FRAME surface height；其他 kind 为 0 |
| 10 | 2 | u16 LE | FRAME stride；其他 kind 为 0 |
| 12 | 4 | u32 LE | payload length |
| 16 | 4 | u32 LE | frame sequence |

message kinds：

| Value | Name | Payload |
| ---: | --- | --- |
| 1 | READY | empty；第一帧成功渲染后入队，header 带该 fitted surface 的 width/height |
| 2 | FRAME | `stride × height` bytes 的 `rgb0` surface |
| 3 | END | UTF-8 reason |
| 4 | ERROR | UTF-8 error message |
| 5 | INFO | UTF-8 diagnostics |

FRAME 的每个像素为四字节 `R G B unused`。每一行从 `payload + y × stride` 开始；renderer 只读取前 `width × 4` 字节。stride 和 surface pointer 在 native 端均按 64 字节对齐。

Node stream chunk 与 PIV1 message 没有一一对应关系。`NativeFrameParser` 支持 header/payload 被任意拆分，也支持一个 chunk 内包含多条消息。`NativeClient` 在一个 chunk 出现多帧时只发布最后一帧。

native render thread 不直接写 pipe。独立 output thread 最多持有一个正在写的 frame 和一个可替换的 `latest_frame` slot；pipe 变慢时，新帧覆盖该 slot。因而跨 chunk 也不会形成无界旧帧队列，END 始终排在最后一个 latest frame 之后。

## Control lines

stdin 接受 LF 结尾命令：

```text
resize <width> <height>
stop
```

`width`/`height` 表示最大 render box，不是指定 framebuffer；width 至少为 1 pixel，height 至少为 2 pixels，以容纳一行 half-block。首次 box 来自 Pi `Component.render(width)` 的真实 Widget 宽度，以及终端高度扣除固定 UI 和 estimated editor rows 后按 `maxHeightPercent` 限制的 layout-available rows。终端 resize 或 estimated editor rows 变化后发送新 box；conversation 文本增长本身不会改变 box 或发送 resize。native Module 从稳定的 libmpv `video-out-params/dw/dh` 取得 post-filter display size，在 box 内选择偶数高度的最大 surface；READY 和 FRAME header 返回实际 geometry。重复 box 和不改变实际 surface 的 resize 都会去重。

libmpv 初始保持 paused。event thread 在同一个 handle 上把可观察到的 container-level 90° rotation metadata 实体化为 filter，并在 rotation `COMMAND_REPLY` 成功前丢弃所有 `VIDEO_RECONFIG` geometry；reply 是 180° 等尺寸不变旋转的完成 barrier，90°/270° 还会继续校验 swapped geometry。authoritative media geometry 建立前，render loop 不消费已排队的 render update。native surface 就绪后，先使用 libmpv 明确标注 render-thread-safe 的 `mpv_command_async` 请求 unpause，再用 `MPV_RENDER_PARAM_NEXT_FRAME_INFO` 分类下一帧：第一个 `PRESENT` 发布一次；READY 之后的 `REDRAW`/`REPEAT` 只做 `SKIP_RENDERING`，不进入 frame-rate gate 或 PIV1。load、rotation 和 unpause 的延迟错误通过 `COMMAND_REPLY` 转成 ERROR；property 与 `mpv_wait_event` 仍只在 event thread。

NativeClient 在 resize 时保留缓存帧；Widget 只复用仍位于当前 box 内的 frame，超出时返回 0 行等待新帧。`stop` 使播放循环结束；`MpvPlayer` 依次停止 event thread、释放 render context、销毁 mpv handle，output thread 随后刷出 END 并退出。

## Limits

- TypeScript parser 最大 payload：8 MiB。
- native surface 最大单边：1024 pixels。
- extension 的 render box 可选 cap 最大为 1024 columns、512 rows（即 1024 pixels 高）；实际 surface 由源 display aspect 最大内接。
- 未知 kind、错误 magic、错误 stride 或 geometry mismatch 均为 protocol error，播放立即停止。
