playerthree/protocol
PlayerThree message conventions — transport-agnostic.
PlayerThree does not own the game's netcode. A game carries these messages over whatever it already uses
(the playerthree/colyseus interop maps them onto Colyseus room messages; a custom WebSocket game can do the same).
Agents normally connect through MCP instead (see mcp/), which needs no game transport at all.
Inbound messages are untrusted: size-capped, strictly shaped, rate-limited. Outbound game data always passes the closed-vocabulary guard first.
Interfaces
ObserverAgentCard
Defined in: packages/playerthree/src/protocol.ts:31
Public series-scoped identity only; never a private commander or owner identifier.
Properties
| Property | Type | Defined in |
|---|---|---|
card | EntrantCard | packages/playerthree/src/protocol.ts:34 |
role | string | packages/playerthree/src/protocol.ts:33 |
team | string | packages/playerthree/src/protocol.ts:32 |
ObserverCommentary
Defined in: packages/playerthree/src/protocol.ts:22
Only voluntarily disclosed agent-emitted game chat or summaries, never owner messages or harness logs.
Properties
| Property | Type | Defined in |
|---|---|---|
category | "summary" | "chat" | packages/playerthree/src/protocol.ts:27 |
id | string | packages/playerthree/src/protocol.ts:23 |
role | string | packages/playerthree/src/protocol.ts:25 |
team | string | packages/playerthree/src/protocol.ts:24 |
text | string | packages/playerthree/src/protocol.ts:28 |
tick | number | packages/playerthree/src/protocol.ts:26 |
ObserverFrame
Defined in: packages/playerthree/src/protocol.ts:42
One released observer frame. A frame carries exactly one of world (a keyframe: the full projection, sent on join,
perspective change, resume beyond the batch bound or resynchronization) or delta (playerthree/delta from frame
sequence - 1 of the same perspective, sent only directly after it). Frames are shared by every observer at the
same cursor and are immutable.
Properties
| Property | Type | Defined in |
|---|---|---|
agentCards? | readonly ObserverAgentCard[] | packages/playerthree/src/protocol.ts:52 |
commentary? | readonly ObserverCommentary[] | packages/playerthree/src/protocol.ts:51 |
delta? | JsonDelta | packages/playerthree/src/protocol.ts:49 |
outcomes? | Record<string, Outcome> | packages/playerthree/src/protocol.ts:50 |
perspective | string | packages/playerthree/src/protocol.ts:47 |
recordedAt | number | packages/playerthree/src/protocol.ts:45 |
releaseAt | number | packages/playerthree/src/protocol.ts:46 |
sequence | number | packages/playerthree/src/protocol.ts:43 |
tick | number | packages/playerthree/src/protocol.ts:44 |
world? | unknown | packages/playerthree/src/protocol.ts:48 |
ObserverRequest
Defined in: packages/playerthree/src/protocol.ts:63
Observer transport request; the server, never the client, chooses the projection and release watermark.
Properties
| Property | Type | Defined in |
|---|---|---|
after? | number | packages/playerthree/src/protocol.ts:65 |
perspective | string | packages/playerthree/src/protocol.ts:64 |
ViewState
Defined in: packages/playerthree/src/protocol.ts:159
A reader's state of one team's view stream.
Properties
| Property | Type | Defined in |
|---|---|---|
version | number | packages/playerthree/src/protocol.ts:160 |
view | unknown | packages/playerthree/src/protocol.ts:161 |
WatchMsg
Defined in: packages/playerthree/src/protocol.ts:128
Transport-level spectator message (not routed to PlayerThreeMatch.handleMessage).
Properties
| Property | Type | Defined in |
|---|---|---|
op | "watch" | packages/playerthree/src/protocol.ts:129 |
teams | string[] | packages/playerthree/src/protocol.ts:130 |
Type Aliases
ClientMsg
type ClientMsg =
| {
args?: unknown;
method: ControlMethod;
op: "call";
role: string;
seq: number;
}
| {
detail?: DetailRequest;
everyTicks: number;
op: "stream";
role: string;
seq: number;
}
| {
mandate: unknown;
op: "mandate";
role: string;
seq: number;
}
| {
op: "scopes";
role: string;
scopes: Scope[];
seq: number;
}
| {
approve: boolean;
id: string;
op: "resolveApproval";
role: string;
seq: number;
}
| {
controller: Controller;
op: "controller";
role: string;
seq: number;
}
| {
op: "mint";
role: string;
seq: number;
}
| {
op: "tell";
ping?: unknown;
roles: string[] | "all";
seq: number;
text: string;
};Defined in: packages/playerthree/src/protocol.ts:108
Union Members
Type Literal
{
args?: unknown;
method: ControlMethod;
op: "call";
role: string;
seq: number;
}Control-plane RPC for a seat (agents: their seat; owners: any seat of their team). Omitted args means {}.
Type Literal
{
detail?: DetailRequest;
everyTicks: number;
op: "stream";
role: string;
seq: number;
}detail: observation sections pushed in full besides the role's defaults, or 'all' (unknown → invalid).
Type Literal
{
mandate: unknown;
op: "mandate";
role: string;
seq: number;
}Owner only.
Type Literal
{
op: "scopes";
role: string;
scopes: Scope[];
seq: number;
}Owner only: replace this live seat's agent permissions.
Type Literal
{
approve: boolean;
id: string;
op: "resolveApproval";
role: string;
seq: number;
}Owner only: resolve a supervised proposal.
Type Literal
{
controller: Controller;
op: "controller";
role: string;
seq: number;
}Owner only.
Type Literal
{
op: "mint";
role: string;
seq: number;
}Owner only: mint a seat token for an agent.
Type Literal
{
op: "tell";
ping?: unknown;
roles: string[] | "all";
seq: number;
text: string;
}Owner only (chat mode): message own agents — some roles of the owner's team, or all.
ObserverGrant
type ObserverGrant =
| {
delayMs: number;
kind: "team";
team: string;
}
| {
delayMs: number;
kind: "omniscient";
teams: readonly string[];
};Defined in: packages/playerthree/src/protocol.ts:18
ObserverMessage
type ObserverMessage = object;Defined in: packages/playerthree/src/protocol.ts:54
Properties
frame?
frame?: ObserverFrame;Defined in: packages/playerthree/src/protocol.ts:59
grant?
grant?: ObserverGrant;Defined in: packages/playerthree/src/protocol.ts:58
Admission metadata only: never contains a live tick or completion signal.
op
op: "observer";Defined in: packages/playerthree/src/protocol.ts:55
state
state: "buffering" | "ready" | "closed";Defined in: packages/playerthree/src/protocol.ts:56
Principal
type Principal =
| {
kind: "team";
match: string;
team: string;
}
| {
kind: "human";
match: string;
role: string;
team: string;
}
| {
kind: "seat";
match: string;
role: string;
team: string;
}
| {
kind: "spectator";
match: string;
team?: string;
}
| {
grant: ObserverGrant;
kind: "observer";
match: string;
};Defined in: packages/playerthree/src/protocol.ts:70
A token encodes exactly one principal. Presented once when joining (e.g. Colyseus join options).
Union Members
Type Literal
{
kind: "team";
match: string;
team: string;
}Trusted private team-root capability (every seat of the team); never issue to a public-lobby participant.
Type Literal
{
kind: "human";
match: string;
role: string;
team: string;
}Human owner capability for exactly one seat, with own-side fogged viewing.
Type Literal
{
kind: "seat";
match: string;
role: string;
team: string;
}Agent bound to exactly one seat. Cannot change controllers or mint tokens.
Type Literal
{
kind: "spectator";
match: string;
team?: string;
}Read-only; receives per-team fogged views.
Type Literal
{
grant: ObserverGrant;
kind: "observer";
match: string;
}Observer admission is independent of player, agent and operator authority.
ServerErrorCode
type ServerErrorCode =
| "bad_message"
| "bad_token"
| "forbidden"
| "out_of_scope"
| "approval_not_found"
| "approval_pending"
| "approval_failed"
| "mode_unavailable"
| "rate_limited"
| "invalid"
| "busy"
| "internal"
| "not_in_match"
| "paused";Defined in: packages/playerthree/src/protocol.ts:133
ServerMsg
type ServerMsg =
| ObserverMessage
| {
data: unknown;
ok: true;
op: "result";
seq: number;
}
| {
code: ServerErrorCode;
ok: false;
op: "result";
seq: number;
}
| ViewMessage
| {
data: unknown;
op: "obs";
role: string;
}
| {
op: "seats";
seats: SeatInfo[];
}
| {
cards: PublicSeatCard[];
op: "cards";
}
| {
data: unknown;
op: "report";
team: string;
}
| {
op: "said";
role: string;
text: string;
tick: number;
};Defined in: packages/playerthree/src/protocol.ts:175
Union Members
Type Literal
{
data: unknown;
ok: true;
op: "result";
seq: number;
}Type Literal
{
code: ServerErrorCode;
ok: false;
op: "result";
seq: number;
}Pushed to team owners/spectators at the view rate.
Type Literal
{
data: unknown;
op: "obs";
role: string;
}Stream mode push.
Type Literal
{
op: "seats";
seats: SeatInfo[];
}Seats of the recipient's team (owners) or of the watched teams (spectators).
Type Literal
{
cards: PublicSeatCard[];
op: "cards";
}Type Literal
{
data: unknown;
op: "report";
team: string;
}Guarded after-action report for team (manifest.report).
Type Literal
{
op: "said";
role: string;
text: string;
tick: number;
}An agent's reply, to that team's owners only. Agent-authored: plain text.
ViewMessage
type ViewMessage =
| {
op: "view";
team: string;
version: number;
view: unknown;
}
| {
delta: JsonDelta;
op: "view";
team: string;
version: number;
};Defined in: packages/playerthree/src/protocol.ts:154
One step of a team's view stream: a keyframe (view, the full guarded view at version) or a playerthree/delta
structural delta from version - 1 of the same team's stream. Built once, shared by every reader of the team, frozen.
Variables
MAX_MESSAGE_BYTES
const MAX_MESSAGE_BYTES: number;Defined in: packages/playerthree/src/protocol.ts:194
Inbound message cap. Policy uploads are the largest legitimate message.
MSG
const MSG: object;Defined in: packages/playerthree/src/protocol.ts:83
Message type names on the game transport.
Type Declaration
| Name | Type | Default value | Description | Defined in |
|---|---|---|---|---|
call | "playerthree:call" | 'playerthree:call' | - | packages/playerthree/src/protocol.ts:84 |
cards | "playerthree:cards" | 'playerthree:cards' | - | packages/playerthree/src/protocol.ts:97 |
controller | "playerthree:controller" | 'playerthree:controller' | - | packages/playerthree/src/protocol.ts:90 |
mandate | "playerthree:mandate" | 'playerthree:mandate' | - | packages/playerthree/src/protocol.ts:87 |
mint | "playerthree:mint" | 'playerthree:mint' | - | packages/playerthree/src/protocol.ts:91 |
obs | "playerthree:obs" | 'playerthree:obs' | - | packages/playerthree/src/protocol.ts:94 |
observer | "playerthree:observer" | 'playerthree:observer' | - | packages/playerthree/src/protocol.ts:85 |
report | "playerthree:report" | 'playerthree:report' | Server → owners/spectators: a team's after-action report once its outcome is decided. | packages/playerthree/src/protocol.ts:99 |
resolveApproval | "playerthree:resolveApproval" | 'playerthree:resolveApproval' | - | packages/playerthree/src/protocol.ts:89 |
result | "playerthree:result" | 'playerthree:result' | - | packages/playerthree/src/protocol.ts:92 |
said | "playerthree:said" | 'playerthree:said' | Server → that team's owners only: an agent's reply (Said). | packages/playerthree/src/protocol.ts:105 |
scopes | "playerthree:scopes" | 'playerthree:scopes' | - | packages/playerthree/src/protocol.ts:88 |
seats | "playerthree:seats" | 'playerthree:seats' | Server → owners/spectators: seat list (controllers, liveness, policy versions) whenever it changes. | packages/playerthree/src/protocol.ts:96 |
stream | "playerthree:stream" | 'playerthree:stream' | - | packages/playerthree/src/protocol.ts:86 |
tell | "playerthree:tell" | 'playerthree:tell' | Owner → own agents (chat mode). | packages/playerthree/src/protocol.ts:103 |
view | "playerthree:view" | 'playerthree:view' | - | packages/playerthree/src/protocol.ts:93 |
watch | "playerthree:watch" | 'playerthree:watch' | Spectator → transport: choose which teams' views to receive (WatchMsg). Handled by the transport binding. | packages/playerthree/src/protocol.ts:101 |
PROTOCOL_VERSION
const PROTOCOL_VERSION: 1 = 1;Defined in: packages/playerthree/src/protocol.ts:17
TOKEN_RE
const TOKEN_RE: RegExp;Defined in: packages/playerthree/src/protocol.ts:192
Tokens: 32 chars base64url (24 random bytes). Validated as an opaque format before lookup.
Functions
applyView()
function applyView(state, message): ViewState | undefined;Defined in: packages/playerthree/src/protocol.ts:169
Apply one playerthree:view message: a keyframe replaces the state, a delta applies only to exactly the previous version.
Returns undefined for a delta without its base version (a protocol error: rejoin for a keyframe). state is not
mutated and unchanged subtrees are shared with it, so a reader MUST treat views as immutable.
Parameters
| Parameter | Type |
|---|---|
state | ViewState | undefined |
message | ViewMessage |
Returns
ViewState | undefined
