Tandem
Last edited by · ·
Tandem
Two cogs, one couch, no channel.
Two cogs are rigidly gripped to the two handles of one couch and have to walk it through a procedurally generated warehouse obstacle course — room by room, doorway by doorway — with no way to say anything to each other. The couch obeys the sum of their forces and turns on the difference, so every misread of the partner shows up as a wall scrape or a drop. The only signal about what your partner intends is what you feel through the handle. The last doorway is 1.05 m wide; the couch is 0.90 m.
A policy is just a prompt. Both champions are LLM prompt policies; both fillers are scripted baselines; all four run the same image, switched by an environment variable.
- Rules, physics and scoring:
docs/RULES.md - Wire protocol, replay format and the reply schema:
docs/PROTOCOL.md - How to write a tandem prompt:
docs/CARRYING.md - Design note:
docs/plans/2026-08-23-tandem-design.md - Working in this repo (determinism contract, frozen files, tests):
AGENTS.md - Tuning the scripted baselines:
docs/BASELINE-TUNING.md
Scoring
Fully cooperative — both seats receive the identical number.
delivered: score = 0.30 + 0.35 * speed + 0.35 * condition
not delivered: score = 0.25 * progress * condition
speed = clamp(2 − t/par, 0, 1), condition = (1000 − damage)/1000. Any
delivery beats every non-delivery; a wrecked couch scores 0.000. The league
ranks by the seat's mean score across its episodes (its cross-play mean), not by
Elo: with two identical scores every episode is a draw.
Running it
The whole game is one image with two entrypoints.
docker build --platform=linux/amd64 -t coworld-tandem:ci .
tools/ci/docker_smoke.sh coworld-tandem:ci # one real episode, raw docker
Seat a policy of your own by reusing the image and setting a prompt:
coworld upload-policy coworld-tandem:latest --name my-tandem \
--run /bin/tandem-player --secret-env PLAYER_PROMPT="<your strategy>"
or run a scripted seat: PLAYER_SCRIPTED=porter (the strain-arbitrated
reference carrier, and the fallback for every failure mode) or
PLAYER_SCRIPTED=mule (never yields, never braces, scrapes constantly).
Repo layout
| Path | What |
|---|---|
src/tandem/{sim,course,control,trig}.nim | the integer-only determinism core |
src/tandem/{orders,baselines,llm,decide}.nim | the order schema, the two baselines, the LLM client and the turn engine |
src/tandem/{server,roster,replays,replay_runtime,broadcast,global,rig_art}.nim | the episode server, the replay codec and the renderer |
client/ | the broadcast chrome, inherited from Metta-AI/coworld-ctf |
replay-viewer/ | the static wasm replay bundle |
tests/ | the determinism gate and the rest of the suite |
Determinism
Replays are re-simulated in the browser by the emscripten/wasm32 build of
the same Nim module the native amd64 server ran, and their per-tick
gameHash chain must match bit for bit. So the sim, the course generator, the
control layer and the trigonometry are integer-only — no floats, no libm, one
committed sine table, one integer square root, one integer atan2 — and every
product is taken in int64 and narrowed with an explicit truncating div.
Unusually for this lineage the control layer is inside the determinism boundary: the replay carries 100 order records instead of 4800 action records, and the viewer re-derives every per-tick force from them.