Apache-2.0 spec: 1.0-draft posy/1 channel

Stream avatar poses over WebRTC, not a mocap suit's worth of bandwidth.

Posy is an open, compact protocol for streaming humanoid body, finger and facial pose between many users in real time — ~116 bytes per frame, roughly 70× smaller than VMC, purpose-built for WebRTC data channels.

Channel label avatar-pose · protocol string posy/1

Standard-Sync frame 116 bytes
header + bone_mask16 B
13× quaternions (smallest-three)52 B
root position6 B
fingers (both hands)24 B
expressions + gaze18 B
30 HzFULL tier
~40 kbit/son wire
~70×smaller than VMC
Is this for me?

Three questions, sixty seconds

What is this, does it work, and how do I use it. Everything below exists to answer one of those — nothing here is here to look impressive.

Good fit

  • You build multi-user VTuber / avatar apps in the browser or over WebRTC
  • You need many participants over normal, non-datacenter internet connections
  • You want lightweight full-body + finger + face sync, not lossless studio capture
  • You just want to hang out with a few friends — one of you can host the room from home

Not a fit (yet)

  • You need lossless studio mocap recording — use VMC or BVH instead
  • You need a fully serverless peer-to-peer mesh — Posy routes through one peer, though that peer can simply be you
  • You want v2 features today — delta encoding, field stripping, props & scenes are deferred

But why not just use VMC?

VMC Protocol is great, but it's fundamentally a local-machine protocol — it assumes one sender and one receiver on the same LAN, at a size that gets expensive fast once you multiply it by dozens of participants. Posy exists for the case VMC never targeted: many people, real internet connections, one avatar's worth of pose data squeezed into a single UDP-sized datagram. The closed-source alternatives that already solve this just don't publish a spec — so here we are.

How it works

One peer routes — and that peer can be you

Posy needs exactly one participant to act as the router: it owns the session clock, room membership, playspace and per-subscriber rate control. That's a role, not a product you have to rent. Voice rides on ordinary WebRTC Opus tracks; pose rides on an unreliable, unordered data channel — because a late pose frame is a useless one.

Sendercaptures & encodes
Routing peeryour PC, a VPS, or a hosted SFU
Receiversslerp, interpolate, render

The routing peer MUST NOT modify a forwarded frame's byte content in v1 — that's what makes rate-tiering a pure frame-drop operation, and what makes a lightweight self-hosted router realistic.

Just want to hang out with friends? Host it yourself.

You don't need infrastructure to use Posy. One person in the group runs the routing role on their own machine and everyone else connects to them — the same shape as hosting a game server for your friends. Because the router only forwards bytes it never rewrites, the job is light: a handful of peers is very comfortable on an ordinary home connection.

One friend hosts~2–8 people

Whoever has the best upload runs the routing role alongside their own client. Typical home fibre handles a small group at NORMAL without noticing.

A cheap VPS~10–40 people

Pose routing is bytes-in-bytes-out with no transcoding, so the smallest tier of box goes a long way. Bandwidth is the cost, not CPU.

A hosted SFU40+ or public rooms

Any SFU supporting per-producer data-channel forwarding works — mediasoup does natively. Posy never depends on a specific one.

If your upload is the tight spot, you have real levers before giving up: drop everyone to NORMAL or MINIMAL, let non-performers join as receive-only viewers, or hand the routing role to whoever in the group has the best upstream. Play with the numbers in the bandwidth calculator — small rooms are cheaper than most people expect.

1. Sender captures pose

A client resolves its tracker/avatar into VRM 1.0 normalized humanoid bones — identity rotation in T-pose, each quaternion a local parent-relative delta.

2. A routing peer forwards, never rewrites

Frames travel sender → router → receivers. That router can be a hosted SFU, a VPS, or just one of your own machines — it only drops and forwards frames, it never touches their bytes.

3. Receiver applies directly

A receiver applies incoming quaternions straight to its own normalized rig. It never needs to know the sender's skeleton, avatar type or tracker.

Coordinate system (§3, normative)

  • Right-handed, Y-up, model faces +Z, metres.
  • Every humanoid bone has identity rotation in the T-pose.
  • Each quaternion is the bone's local, parent-relative delta from T-pose.
  • The hips bone additionally carries the root position block.

Conversion is always the sender's job. VRM 1.0 needs none; VRM 0.x reads the already-resolved normalized rig; MMD precomputes inverse(q_rest) · q_current once at load.

Explicitly out of scope for v1

  • Delta / keyframe encoding
  • Server-side field stripping
  • Physics or props
  • Scene state

Listed on purpose so v1 doesn't accidentally block them later — see §11 of the spec.

Packet Lab

Build a frame, byte by byte

Everything here runs the real formulas from Posy-1.0.md §5 — toggle flags and bones and watch the frame size, bit layout and bandwidth respond live.

Loading the packet builder… (needs JavaScript)

§5.3 smallest-three, live

one quaternion → 4 bytes

Rotate a bone below. The demo runs the exact encode/decode algorithm from the spec's Appendix B in your browser — drop the largest component, sign-fix, quantise the other three to 10 bits each, pack into one u32.

Yaw35°
Pitch-15°
Roll10°

One humanoid bone, rotating in its parent's frame — exactly what a single 4-byte quaternion on the wire represents.

Normalized quaternion (x, y, z, w)–

Red component (?) is the largest — it's dropped and reconstructed on decode, never sent over the wire.

Packed u32 (wire bytes)–
Bit layout–
■ index (2b)■ a (10b)■ b (10b)■ c (10b)
Round-trip angular error – worst case ≈ 0.14° (try 0 / 0 / 0 — the identity sits exactly on a rounding tie)
Bandwidth

Per-person cost is trivial. The router's uplink is the budget.

Nobody's individual connection ever struggles with Posy — a pose stream is smaller than the voice call it rides alongside. What scales is the total the routing peer has to push back out, which is exactly why viewers are so cheap to add. Numbers use the §6 methodology and reproduce the spec's worked examples.

Scenarios
FULL senders1

30 Hz — active speakers, close-up faces, fast hands

NORMAL senders0

15 Hz — the sensible default for everyone else

MINIMAL senders0

5 Hz — thumbnails, background peers, idle/AFK avatars

Viewers (receive only)49

Send no pose data at all — they only subscribe. Cheap to add, and they never need an upload budget.

Room size
50 people (1 sending, 49 watching)
Viewers are a pure subscription: no capture, no encoder, no upload. They cost the router one outbound copy of the sender pool each, and they cost themselves almost nothing — which is why an audience of hundreds is a routing problem, never a client problem.
Viewer downstream
–
–
Sender downstream
–
Everyone else's pose, minus their own
Routing peer egress
–
–
Routing peer ingress–

Ingress stays tiny no matter the room size — only senders upload, and each uploads one small stream.

–FULL kbit/s /stream
–NORMAL kbit/s /stream
–MINIMAL kbit/s /stream
Every peer's own upload is one stream — – even at FULL. That's the number that matters if you're the one hosting from home.
Spec explorer

The normative core, made browsable

The full text lives in spec/Posy-1.0.md — this is a navigable view of the tables you'll actually reference while implementing.

64-bit mask, bit n set ⇒ quaternion for bone n present, appearing in the payload in ascending bit order. v1 constraint: bits 25–54 (fingers) MUST be 0.

Repo layout

What each piece is for

This is the intended shape of the repository end-to-end. Green is already there; amber is planned and ordered by the task list below — nothing here is clutter, each node answers one specific question.

Roadmap

The critical path is Phases 1–2

A spec plus passing test vectors is already a usable open standard. Everything past that is comfort — ordered so each phase unblocks the next.

Two anti-clutter rules, enforced from day one: (1) one normative source — if spec, reference code and test vectors ever disagree, the spec wins and the others get fixed, unless the spec itself is flat-out wrong. (2) Issues are for the protocol; Discussions are for everything else — "how do I do rooms in mediasoup" gets redirected, not answered in an issue thread.
FAQ

Rationale, kept out of the spec

Keeps the normative document lean while still answering the 'why' questions somewhere.

Get started

Try it in two minutes

shell
git clone https://github.com/Ron81/posy.git
cd posy/examples/loopback-demo
npm install && npm start

1. Read the normative core

Start with §3 (coordinates), §4 (bones) and §5 (packets) of Posy-1.0.md — that's the part every conformant implementation must agree on.

2. Try the loopback demo (planned)

Zero infrastructure: encode → decode → render locally, no server or signup required. Once published it becomes the README's demo GIF.

3. Validate against test vectors (planned)

Decode each .bin fixture, diff against its .json, reject the ones marked expect: reject. That's conformance.

4. Grab the reference encoder/decoder (planned)

reference/js will ship a small, dependency-free TS implementation — read it, fork it, or just use it as a spec cross-check.