diff --git a/docs/index.md b/docs/index.md
index a8a14be..7b6c162 100644
--- a/docs/index.md
+++ b/docs/index.md
@@ -65,27 +65,74 @@ on the **CPU**: ten clients at once, 30 of 30 episodes successful, at
Stepping is 10x slower in software, and most of that hides behind the queue
of clients waiting on one policy.
+It is lighter than the other systems that train through their channel, too.
+`plugrl-env-client` installs 31 packages, 222 MB, with no torch, and the C++
+client below is an 86 KB binary. RLlib's external-env client and LeRobot's
+HIL-SERL actor each install about 6.1 GB, torch and 15 CUDA packages among
+it, because both run the policy on the environment side. A thin environment
+side alone is not new: dm_env_rpc's and openpi's clients are smaller still,
+but neither trains through its channel
+([E45](https://github.com/PlugRL/plugrl-server/tree/main/experiments/e45-env-side-footprint)).
+
It does not have to be Python either. [The protocol](protocol/index.md) is
written down, with a conformance checker. A C++ program with nothing beyond
the standard library - no msgpack or WebSocket library - steps its own copy of
Pendulum and trains a policy through it, as well as the Python env client does
([E44](https://github.com/PlugRL/plugrl-server/tree/main/experiments/e44-cpp-pendulum)).
+## Other trainers train through it
+
+The protocol is not tied to PlugRL's own server. Small adapters present it as
+the environment interface a trainer already has (`plugrl-bridges`, not public
+yet). Through them, three trainers that were not written for PlugRL train
+PlugRL env clients, and every seed of every pairing learns:
+- RLinf's PPO, on the C++ Pendulum client;
+- Stable-Baselines3's PPO, on the C++ Pendulum client and on HalfCheetah;
+- CleanRL's PPO, on HalfCheetah
+ ([E49](https://github.com/PlugRL/plugrl-server/tree/main/experiments/e49-multi-trainer)).
+
+
+
+Each line is one seed's mean return over its 16 env clients, from the
+bridge's own episode log. RLinf learns Pendulum later than Stable-Baselines3
+at the same settings; E49 records that and does not explain it.
+
+RLinf also trained 16 HalfCheetah env clients that ran on a laptop with no
+torch, Ray or RLinf, in a 276 MB environment
+([E48](https://github.com/PlugRL/plugrl-server/tree/main/experiments/e48-rlinf-bridge)).
+
+Behind the protocol, training sees nothing different. Stable-Baselines3 with
+its 16 environments in another process ends on the same weights, byte for
+byte, as with them in its own process: on Pendulum and HalfCheetah
+([E50](https://github.com/PlugRL/plugrl-server/tree/main/experiments/e50-boundary-transparency)),
+on Atari frames with a CNN
+([E53](https://github.com/PlugRL/plugrl-server/tree/main/experiments/e53-image-observations)),
+and with every byte between the two sides held up to 25 ms each way
+([E52](https://github.com/PlugRL/plugrl-server/tree/main/experiments/e52-latency-sweep)).
+Only the time changes: 0.4-0.6 ms per step of 16 environments on one machine,
+2.6-3.0 ms when each step carries 16 frames, and about two one-way delays per
+step behind a slow link.
+
## What the split costs
On one machine, almost nothing: an exchange takes 0.1 ms with states only and
under 1 ms with a 588 KiB observation. Between two machines it becomes two
terms:
-- **A fixed latency.** About 3 ms between a laptop on campus Wi-Fi and a wired
+- **A fixed latency.** About 3 ms between a laptop on Wi-Fi and a
workstation, over Tailscale.
-- **Twice the observation's bytes over the link's bandwidth.** Each exchange
- carries the observation twice, once asking for the action and once in the
- feedback.
-
-On that link, at 18 MB/s, a state-only task pays about 3 ms per step, and a
-184 KiB camera observation about 21 ms
-([E43](https://github.com/PlugRL/plugrl-server/tree/main/experiments/e43-cross-machine-training)).
-A faster link shrinks the second term in proportion.
+- **The observation's bytes over the link's bandwidth, once.** The feedback
+ that ends a chunk carries the observation, and with `reuse-feedback-obs`, a
+ feature the server offers and the env client uses by default, the next
+ request for actions leaves it out.
+
+On a 23.7 MB/s link, a state-only task pays about 3 ms per step, and a
+184 KiB camera observation 11.6 ms, against 18.5 ms when each observation
+crossed twice. Training ends on the same weights, byte for byte, either way
+([E46](https://github.com/PlugRL/plugrl-server/tree/main/experiments/e46-reuse-feedback-obs);
+[E43](https://github.com/PlugRL/plugrl-server/tree/main/experiments/e43-cross-machine-training)
+measured the first version). A faster link shrinks the second term in
+proportion.
## A real VLA, end to end
@@ -121,16 +168,17 @@ cd plugrl-server && uv sync && cd ..
cd plugrl-env-client && uv sync --extra mujoco && cd ..
```
-Then, in two terminals:
+Then, in two terminals, each inside its own repository (`uv run` uses that
+repository's `.venv`):
```bash
-# Terminal 1 - the training server
-plugrl-run-server fpo-policy default fpo default \
+# Terminal 1, in plugrl-server - the training server
+uv run plugrl-run-server fpo-policy default fpo default \
--port 8000 --policy.device cpu \
--algo.global-steps 500000 --algo.buffer-size 4096
-# Terminal 2 - the environment
-plugrl-run-env-client mujoco-v1 \
+# Terminal 2, in plugrl-env-client - the environment
+uv run plugrl-run-env-client mujoco-v1 \
--server-host 127.0.0.1 --server-port 8000 \
--num-envs 1 --num-episodes 600 --runner.replan-steps 1 --runner.seed 0
```
diff --git a/docs/index.zh.md b/docs/index.zh.md
index d19dcdf..491ee15 100644
--- a/docs/index.zh.md
+++ b/docs/index.zh.md
@@ -48,20 +48,61 @@ HalfCheetah,训练端在一台 Linux 工作站上。两边各六个种子落
[E13](https://github.com/PlugRL/plugrl-server/tree/main/experiments/e13-gpu-free-rendering))。
软件渲染的单步慢 10 倍,其中大部分被"多个客户端排队等同一个策略"的等待掩盖了。
+和其他"通过通道训练"的系统比,它也更轻。`plugrl-env-client` 装 31 个包、222 MB,
+不带 torch;下面那个 C++ 客户端是一个 86 KB 的可执行文件。RLlib 的 external env 客户端
+和 LeRobot 的 HIL-SERL actor 各装约 6.1 GB,其中有 torch 和 15 个 CUDA 包,因为它们都在
+环境端跑策略。光是环境端轻并不新鲜:dm_env_rpc 和 openpi 的客户端更小,但两者都不通过
+自己的通道训练([E45](https://github.com/PlugRL/plugrl-server/tree/main/experiments/e45-env-side-footprint))。
+
环境端也不必是 Python。[协议](protocol/index.zh.md)是写下来的,附带一致性检查器。一个
除了标准库之外什么都不用的 C++ 程序(没有 msgpack 库,也没有 WebSocket 库),自己实现了
Pendulum,通过 PlugRL 训练出了策略,学得和 Python 环境端一样好
([E44](https://github.com/PlugRL/plugrl-server/tree/main/experiments/e44-cpp-pendulum))。
+## 别的训练框架也能通过它训练
+
+协议不绑定 PlugRL 自己的训练端。几个小适配器把它包装成训练框架本来就认的环境接口
+(`plugrl-bridges`,暂未公开)。通过它们,三个不是为 PlugRL 写的训练框架训练 PlugRL
+的环境端,每种组合的每个种子都学会了:
+- RLinf 的 PPO,训练 C++ Pendulum 客户端;
+- Stable-Baselines3 的 PPO,训练 C++ Pendulum 客户端和 HalfCheetah;
+- CleanRL 的 PPO,训练 HalfCheetah
+ ([E49](https://github.com/PlugRL/plugrl-server/tree/main/experiments/e49-multi-trainer))。
+
+
+
+每条线是一个种子在 16 个环境端上的平均回报,数据来自 bridge 自己记的回合日志。同样的
+设置下,RLinf 学会 Pendulum 比 Stable-Baselines3 晚;E49 记录了这一点,但没有解释原因。
+
+RLinf 还训练了跑在一台笔记本上的 16 个 HalfCheetah 环境端,那台笔记本没装 torch、Ray
+和 RLinf,环境端只占 276 MB
+([E48](https://github.com/PlugRL/plugrl-server/tree/main/experiments/e48-rlinf-bridge))。
+
+隔着协议,训练看到的东西没有任何不同。Stable-Baselines3 把 16 个环境放到另一个进程里,
+最后的权重和环境放在自己进程里时逐字节相同:Pendulum 和 HalfCheetah 上如此
+([E50](https://github.com/PlugRL/plugrl-server/tree/main/experiments/e50-boundary-transparency)),
+换成 Atari 画面加 CNN 也如此
+([E53](https://github.com/PlugRL/plugrl-server/tree/main/experiments/e53-image-observations)),
+两端之间每个字节每个方向都压住 25 毫秒还是如此
+([E52](https://github.com/PlugRL/plugrl-server/tree/main/experiments/e52-latency-sweep))。
+变的只有时间:同一台机器上 16 个环境每步多 0.4 到 0.6 毫秒,每步带 16 帧画面时多 2.6 到
+3.0 毫秒,链路慢时每步大约多两个单程延迟。
+
## 拆开的代价
同一台机器上几乎没有代价:只传状态时一次交换 0.1 毫秒,观测有 588 KiB 也不到 1 毫秒。
跨两台机器时,代价分成两项:
-- **固定延迟**:校园 Wi-Fi 上的笔记本经 Tailscale 连有线工作站,大约 3 毫秒。
-- **两倍的观测数据量除以链路带宽**:每次交换里观测要传两次,一次在请求动作时,一次在反馈里。
+- **固定延迟**:Wi-Fi 上的笔记本经 Tailscale 连工作站,大约 3 毫秒。
+- **观测数据量除以链路带宽,只算一次**:结束一段动作的反馈里带着观测;有了
+ `reuse-feedback-obs`(训练端提供、环境端默认使用的协议特性),下一次请求动作时不再
+ 重复发送。
-在这条链路上(18 MB/s),只传状态的任务每步多约 3 毫秒,一个 184 KiB 的相机观测每步多约
-21 毫秒([E43](https://github.com/PlugRL/plugrl-server/tree/main/experiments/e43-cross-machine-training))。链路越快,第二项按比例越小。
+在一条 23.7 MB/s 的链路上,只传状态的任务每步多约 3 毫秒,一个 184 KiB 的相机观测每步多
+11.6 毫秒,观测传两次时是 18.5 毫秒。两种情况下训练出的权重逐字节相同
+([E46](https://github.com/PlugRL/plugrl-server/tree/main/experiments/e46-reuse-feedback-obs);
+第一版的测量见 [E43](https://github.com/PlugRL/plugrl-server/tree/main/experiments/e43-cross-machine-training))。
+链路越快,第二项按比例越小。
## 真实 VLA,端到端
@@ -93,16 +134,16 @@ cd plugrl-server && uv sync && cd ..
cd plugrl-env-client && uv sync --extra mujoco && cd ..
```
-然后开两个终端:
+然后开两个终端,各自进到对应的仓库里运行(`uv run` 用的是那个仓库自己的 `.venv`):
```bash
-# 终端 1 —— 训练端
-plugrl-run-server fpo-policy default fpo default \
+# 终端 1,在 plugrl-server 里 —— 训练端
+uv run plugrl-run-server fpo-policy default fpo default \
--port 8000 --policy.device cpu \
--algo.global-steps 500000 --algo.buffer-size 4096
-# 终端 2 —— 环境端
-plugrl-run-env-client mujoco-v1 \
+# 终端 2,在 plugrl-env-client 里 —— 环境端
+uv run plugrl-run-env-client mujoco-v1 \
--server-host 127.0.0.1 --server-port 8000 \
--num-envs 1 --num-episodes 600 --runner.replan-steps 1 --runner.seed 0
```
diff --git a/docs/media/other-trainers.png b/docs/media/other-trainers.png
new file mode 100644
index 0000000..093db26
Binary files /dev/null and b/docs/media/other-trainers.png differ