# Phase 1 feasibility report

验证日期：2026-07-22。目标环境：macOS 26.5、arm64 Apple M5 Pro、Homebrew、Ghostty、Pi Coding Agent 0.81.1。

## 结论

优先架构可行，最终实现采用 Rust sidecar。当前 Homebrew libmpv 可以用 `MPV_RENDER_API_TYPE_SW` 直接生成目标尺寸 `rgb0` framebuffer，并由同一个 mpv 实例通过 CoreAudio 播放音频。不需要 `mpv --vo=tct`、ANSI 捕获、独立 pane 或第二个媒体时钟。

## 环境检查

- mpv：Homebrew `mpv 0.41.0_6`，播放器版本 `0.41.0`。
- libmpv：`/opt/homebrew/opt/mpv/lib/libmpv.2.dylib`，pkg-config 版本 `2.5.0`。
- headers：`client.h`、`render.h`、`render_gl.h`。
- Software Render API：`render.h` 包含 `MPV_RENDER_API_TYPE_SW` 及 `SW_SIZE=17`、`SW_FORMAT=18`、`SW_STRIDE=19`、`SW_POINTER=20`。
- Rust：Homebrew `rustc 1.97.1`、`cargo 1.97.1`。
- Node.js：`v25.9.0`。
- 视频：H.264 1280×720、30 FPS；AAC 44.1 kHz 双声道；时长 31.2 秒。

注意：最初用 `find /opt/homebrew/opt/mpv` 未跟随符号链接，曾得到错误的“没有 libmpv”结果。Cellar manifest、`pkg-config mpv`、`otool` 和导出符号共同确认 libmpv 完整存在。

## Software Render 验证

以下数字记录最初的 exact-surface 可行性探针。当前自适应实现把 `80×40` 解释为 render box；16:9 素材实际输出 `71×40`。当前证据见 [`verification.md`](verification.md)。

执行：

```bash
npm run build:native
npm run probe -- "/absolute/path/to/video.mp4"
```

真实探针使用以下参数：

```text
80×40 pixels, rgb0, 64-byte aligned stride, 15 FPS, 45 output frames
```

结果：

- sidecar 退出码：0。
- PIV1 输出：577,024 字节，全部消息按长度精确解析，无尾部残留。
- frame 数：45。
- 每帧：80×40，stride 320，payload 12,800 字节。
- 像素通道范围：0–255；非黑通道 383,362，确认 framebuffer 非空且内容变化。
- 进程 CPU：0.19 秒 user + 0.06 秒 system / 3.65 秒 wall，约占单核 7%。
- 最大常驻内存：约 130 MiB。

## 音频验证

在同一次探针、同一个 mpv handle 中，`AUDIO_RECONFIG`/`FILE_LOADED` 后报告：

```text
video=h264 1280x720; audio=aac 44100Hz stereo; ao=coreaudio; device=auto
```

这证明音轨已选择并由 macOS CoreAudio 初始化。音频时间与视频 render queue 都由 libmpv 驱动；TypeScript 不建立独立音频定时器。

## 帧率与同步策略

源视频为 30 FPS，TUI 目标为 15 FPS。`FILE_LOADED` 后 sidecar 读取 libmpv 的 `container-fps`，使用帧预算均匀抽样；30→15 时严格执行一次 emit、一次 skip。这个 gate 不读取 wall clock，也不是第二个媒体定时器。

authoritative media geometry 建立前，sidecar 保留已排队的 render update，不提前消费首帧。geometry 建立后，每个带 `PRESENT` 的 `MPV_RENDER_UPDATE_FRAME` 都调用一次 `mpv_render_context_render()`：

- 首个 `PRESENT`：渲染到 `rgb0` surface，并发送 READY 与首帧。
- 后续 `New`：进入 frame-rate gate；emit 时发送最新帧，skip 时使用 `MPV_RENDER_PARAM_SKIP_RENDERING=1`。
- `REDRAW`/`REPEAT`：直接 `SKIP_RENDERING`，不占媒体帧预算。
- `NEXT_FRAME_INFO` 暂无 `PRESENT`：等待下一次 update，不调用 render。

因此不会积压旧帧，且默认 `BLOCK_FOR_TARGET_TIME` 行为仍由 mpv 对齐音频时钟。45 个输出帧在 3.65 秒内完成，扣除初始化后约为目标 15 FPS。

## Node 直连评估

Node 直接 FFI 理论上可行，但不作为最终路径：render update callback 可从任意 native thread 调用，生命周期和线程约束不适合通过通用 JS FFI 暴露；常见 `ffi-napi` 包也明显早于当前 Node 25。Rust sidecar 已在本机验证，无 Node ABI 耦合，并把 unsafe 范围限制在一个小模块内。

## Gate 结果

| 验证项 | 结果 |
| --- | --- |
| Homebrew mpv 包含 libmpv | 通过 |
| Software Render API 可用 | 通过 |
| `rgb0` framebuffer 在 80×40 box 内稳定输出 | 通过 |
| 同实例系统音频播放 | 通过，CoreAudio 44.1 kHz stereo |
| Node 直接调用是否优先 | 否，采用已验证的 Rust sidecar |
| CPU 阈值低于 80% | 通过；sidecar 探针约 7%，240×70 大面积压力测试的 Pi/Node + native 峰值和 31.6% |
