# Folium server protocol

Folium 1.21.11 and 26.2 clients use the same control channel and line format.

## Transport

Send raw UTF-8 bytes on `folium:control`; Minecraft already frames the plugin message. Commands are newline-separated and fields use `|`. Do not use Java `writeUTF` or add a JSON envelope.

Register the channel when the player joins, then send the controls needed for that connection.

Payload limits are 32 KiB, 256 lines, 8 KiB per field and 512 active rules. Invalid commands are ignored. Module and option IDs are case-sensitive; use stable IDs such as `minimap` and `autogg`, rather than translated labels.

## Module controls and events

| Command | Effect |
| --- | --- |
| `disable|minimap` | Turn off and lock a module for this connection. |
| `enable|autogg` | Require a module to stay on. |
| `disable|minimap.entities` | Lock the setting's UI without changing its value. |
| `allow|minimap.entities` | Release a setting lock. |
| `allow|minimap` | Release the module and its settings; restore the player's preference. |
| `reset` | Clear rules, event subscriptions and voice configuration; retain the team roster. |
| `listen|autogg` / `ignore|autogg` | Subscribe or unsubscribe to an event. |
| `trigger|autogg` | Request the built-in Auto GG action, subject to its locks and cooldown. |

The client replies with `ack|<name>|ok` or `ack|<name>|refused|<reason>`. Events arrive as `event|<name>|<payload>`. `enable|voice` is ignored: server permission cannot turn on the player's voice module or microphone.

Rules are client-side controls, not anti-cheat enforcement. Server-imposed values are not saved as player preferences. Disconnect clears server state, including teams.

## Team data

```text
team|<uuid>|<player-name>|<team-id>
team|<uuid>                         # remove assignment, retain position
team                                # clear roster
pos|<uuid>|<x>|<y>|<z>|<health>|<dimension>
pos|<uuid>                          # forget player
```

Health and dimension may be omitted. Coordinates are integer block positions; health must be finite and nonnegative. Positions can be sent before a team assignment.

## Voice on dedicated servers

Send each player their own short-lived ticket and connection configuration:

```text
voice|configure|{"endpoint":"wss://voice.example.net/voice","ticket":"<signed-ticket>","listen":true,"speak":true,"iceServers":[{"urls":["stun:relay.example.net:3478"]},{"urls":["turn:relay.example.net:3478"],"username":"<temporary-user>","credential":"<temporary-password>"}]}
voice|permissions|true|false
voice|off
```

Keep the JSON on one line. Encode a literal pipe in JSON as `\u007c`. Public services need WSS and a valid TLS certificate. Plain WS is accepted only on loopback for local testing. Configuration supports up to eight ICE servers with up to eight URLs each. Production clients require a working TURN/TURNS relay; STUN-only and direct-only configurations do not work. Use short-lived TURN credentials.

`listen` and `speak` permissions apply to the receiving player. Speaking still requires that player to enable their microphone. Neither permission enables the module or mic. Configuration lasts for the game connection: send it on join, refresh tickets before expiry, and resend it after a dimension change. `voice|permissions` updates client behavior; also revoke access on the signaling service so existing tickets cannot continue to grant it.

The reference signaling service uses `Authorization: Bearer <ticket>`. The client sends `voice.join` with its world, talk mode and microphone state. The service derives identity, room and permissions from the verified ticket. It replies with `voice.ready` (member ID, permissions and peers), then sends `voice.peer_joined`, `voice.peer_left`, `voice.signal`, `voice.whisper` and ping/pong keepalives as needed. A signal is an offer or answer with `type` and `sdp`, or a candidate with `type: "candidate"` and nested `candidate`, `sdpMid` and `sdpMLineIndex`. The lower lexical member ID creates the offer. Frames are limited to 16 KiB, 60 messages per second and 16 members per room. Audio uses relay-only WebRTC. TURN hides endpoint addresses from peers, not from the game server or relay operator; it does not provide server-enforced moderation.

## Voice in hosted worlds

Hosted worlds use the `folium:host_voice` channel over their existing authenticated Minecraft connection. The host must enable **Voice chat** when hosting. Each guest must also enable the local Voice module; speaking requires a locally enabled microphone. This transport needs no separate signaling server, STUN or TURN service, but the hosted-world connection itself must be reachable.

Audio is mono Opus at 24 kHz, 24 kbps VBR, in 20 ms frames. Packets carry a session UUID, authenticated sender UUID, sequence, flags, range, gain and encoded audio. The host validates the session and sequence, then forwards audio using current positions, dimensions, permissions and block lists. It applies distance attenuation; whisper range is capped at five blocks. Audio payloads are capped at 1,275 bytes.

Packet kinds are `HELLO=0`, `WELCOME=1`, `STATE=2`, `AUDIO=3`, `OFF=4`, `BLOCK=5` and `STATUS=6`. Flags are `LISTEN=1`, `SPEAK=2` and `WHISPER=4`. `HELLO` discovers support; `WELCOME` returns a connection nonce and host-granted permissions; `STATE` updates local listen, microphone and whisper state; `STATUS` reports current listen state. `BLOCK` uses the sender UUID field for the blocked player; LISTEN set adds the block and unset removes it. Only the authenticated Minecraft sender identity is forwarded. The host rejects invalid sessions, duplicate sequences, malformed Opus and over-budget packets. Limit `HELLO` to one per second, other control messages to 32 per second and audio to 55 packets per second per member. The host can revoke guest listen or speak permission from the hosting screen.

This is Folium voice, not Simple Voice Chat interoperability. The world owner handles forwarded audio and is a trust boundary. Playback recording does not capture this channel. The client validates packet size, rate, session nonce and sequence before forwarding audio.

## Run the reference signaling service

The downloadable [reference service](downloads/voice-reference.zip) is a small server-owned signaling service. It has no Folium account dependency. The [Java ticket helper](downloads/VoiceTickets.java) creates tickets and raw plugin-message payloads for Java server plugins.

Requirements: Node.js 22 or newer, a TLS-terminating reverse proxy, and a working STUN/TURN service for Internet clients.

```powershell
npm ci
$env:VOICE_SECRET = '<random secret of at least 32 bytes>'
$env:VOICE_ADMIN_SECRET = '<different random secret of at least 32 bytes>'
npm start
```

The service binds to `127.0.0.1:24455` by default. Expose `/voice` with WebSocket upgrade enabled; keep `/permissions` private. `PORT` and `BIND` configure the listener. Keep both secrets on the server.

Sign an HS256 JWT with `VOICE_SECRET` for each authenticated player. Claims are `sub` (server-authenticated player UUID), `name`, `room`, `world`, `listen`, `speak` and `exp`. Tickets can last at most five minutes. Evaluate server permissions before issuing them and refresh them before they expire. Never send signing secrets to clients.

To revoke access, call `POST /permissions` with the separate admin bearer secret and a body such as `{"sub":"<uuid>","room":"<room>","listen":false,"speak":false}`. The service disconnects matching sessions and blocks old tickets for their remaining lifetime. Overrides are in memory; rotate the signing secret after a service restart if old tickets must be invalidated.

Run `npm test` to check authentication, signaling, room isolation and revocation. The reference service does not configure a Minecraft server or provision TURN.
