API

The game list's data as JSON, for building a server browser, a bot or your own stats. These endpoints are Delta's parts on this site: Delta List (live games, listing), Delta Stats (finished games, players) and Delta Link (connecting a game).

Basics

  • Base URL: https://halo.milenko.org. Every endpoint is under /v1/.
  • No account or key needed to read anything.
  • Limit: 60 calls a minute per IP address. Past that you get 429 slow down.
  • No CORS headers, so a web page on another site can't call it from the browser. Call it from a server, a bot or a game.
  • Errors are plain text with a status code, such as 404 not found.
  • GET /v1/health answers ok while the server is up.

Game types

Games and reports give the game type as a number in engine:

engineGame type
1Capture the Flag
2Slayer
3Oddball
4King of the Hill
5Race

Map names

map is either a short name (bloodgulch) or the full tag path (levels\test\bloodgulch\bloodgulch). Take the part after the last backslash to get the short name.

A Halo PC (Custom Edition) map, which Chupathingy plays from the player's maps_ce folder, ends in @ce (bloodgulch@ce), a different map from the Xbox map of the same name. Its players need maps_ce/bloodgulch.map to join.

Co-op games

A game on a campaign level is a co-op game (OpenCE's networked co-op): a10 The Pillar of Autumn, a30 Halo, a50 The Truth and Reconciliation, b30 The Silent Cartographer, b40 Assault on the Control Room, c10 343 Guilty Spark, c20 The Library, c40 Two Betrayals, d20 Keyes and d40 The Maw. Live games say so in mode. A co-op game's engine and gametype are whatever its host's menus last had for multiplayer, not what is played: show it as co-op. Co-op games aren't recorded in the multiplayer stats, so they have no carnage reports.

Live games Delta List

GET/v1/games

The games being hosted right now, most players first; co-op games after the multiplayer games.

{"games": [{
  "invite": "54d85a1abd0d9a48…",   // 64 hex digits (44 from older builds)
  "name": "Walter937",
  "map": "bloodgulch",
  "engine": 1,
  "players": 25,
  "maximum_players": 128,
  "open": 1,                    // 1 if it takes new players
  "version": 10,                // the host's network version
  "score_limit": 0,
  "teams": 1,
  "roster": [{"team": 0, "name": "Walter937"}, …],
  "region": "NA-US",            // where the host is: continent and country ("" if unknown)
  "verified": true,             // false: the list couldn't reach it lately
  "counted": true,              // false: left out of the totals (below)
  "age": 57,                    // seconds since the host last announced it
  "listed_by": "walter937",     // only for games listed from the site
  "source": "announced",        // where the listing came from (below)
  "mode": "multiplayer",        // or "coop": a campaign level (above)
  "platform": "pc_linux",       // the host's platform ("" if it didn't say; below)
  "hosting": "player",          // "official", "dedicated", "player" or ""
  "protocol": "delta",          // "delta", "opence" or ""
  "family": "xbox",             // the map's: "xbox", "ce", "pc" or "md"
  "machines": {"pc_linux": 2, "xbox": 1},   // a Delta host's machines by platform, else {}
  "arch": "x64",                // a ChupathingyCE host's build, else ""
  "build": "0.7.1b"
}]}

source is announced (the host's copy announces it here), broker (an OpenCE public game, below), relayed (shared on Discord and listed by a relay), hosted (listed by a player from the site) or unverified (a relay's invite the list couldn't reach yet). OpenCE public games also have gametype (the game variant's name) and in_progress (1 once the match has started; open then says whether it still takes players).

roster is missing from games listed through the site, and from older builds.

Hosts and kinds of game

A ChupathingyCE host (0.7.1b and later) says what it runs on when it announces its game (below). The rest is worked out from where the listing came from. Each field is empty when unknown, as it is for older builds and for games listed by their invite.

  • platform: the host machine's platform, from its Delta platform key: pc_windows, pc_macos, pc_linux, android, steam_deck, xbox, xbox360, wiiu, switch or unknown (a platform this list doesn't know yet).
  • hosting: official (one of ChupathingyCE's own dedicated servers), dedicated (anyone's dedicated server), player (a player hosting from their game).
  • protocol: delta (hosted with Delta: OpenCE players join it too) or opence (OpenCE's protocol alone: every OpenCE public game, and a ChupathingyCE host with Delta off). The site shows these as "OpenCE". Nothing about an OpenCE host is known beyond its listing.
  • family: the map's family, from its name: an Xbox map, or <file>@ce (Halo PC Custom Edition), <file>@pc (Halo PC retail) or <file>@md (HaloMD).
  • machines: how many machines in the game are on each platform, as the host's Delta Peer knows them (legacy is a machine without Delta, unknown one that doesn't share its platform). Counts only, with no player named. A dedicated server leaves itself out.

Filters take comma-separated values (a game matches any value in a filter, and must match every filter): ?platform= (a platform, or pc, console, handheld, dedicated, or empty for "not reported"), ?family=, ?mode=, ?hosting=, ?protocol=. For example /v1/games?platform=xbox,steam_deck&protocol=delta. An unknown value returns 400.

region is the host's continent (AF, AN, AS, EU, NA, OC, SA) and country (ISO 3166-1), joined by a dash. It comes from the address the host announces from, or the one the list's probe reached it at; addresses themselves are never shown. It is empty when the list can't tell, for example for a game shared on Discord that the probe can't reach. Regions: IP Geolocation by DB-IP, licensed under CC BY 4.0.

counted is false for a listing the list hasn't heard from lately (a game listed from the site that the probe hasn't reached in the last few minutes; it has probably ended, and is removed once the probe has missed it three times), and for a host's older listing once it lists its game again under a new invite. Add up the players of the counted games only, as the site does for players online and Activity.

GET/v1/games.txt

The same list as plain text: one game a line, fields split by tabs, in this order. Easier to read from C than JSON.

invite  name  map  engine  players  maximum_players  open  version  age  score_limit  teams  roster

The roster is team:name pairs joined by |, with team -1 in games without teams.

Newer fields are included only when you ask for them with ?fields= and the number of fields you read, because the game's own browser treats anything after the twelfth field as part of the roster. /v1/games.txt?fields=13 adds region (as in the JSON) after the roster; ?fields=17 adds platform, hosting, protocol and machines after it, the machines as pc_linux:2,xbox:1.

Joining a game

The join link is halo://join/<invite>. Opening it starts the game and joins. A client can also hand the invite straight to its own join code.

Games of every network version are listed, each with its version (0 when the list hasn't reached it yet). Only show the ones your build can join.

A game only appears here once the list's probe has reached it, and it's checked again every two minutes, so a listed game was really there moments ago.

OpenCE public games

Games hosted as PUBLIC in OpenCE are listed in its in-game server browser through public MQTT brokers, and the list shows them too, with source broker. Each listing is signed by its host's key, and the invite is made from that key, so no one else can list a game under that invite or change its details. The list checks each signature the way the game does, and drops a game as soon as its host ends it, or about 90 seconds after it stops hearing from it. These games have no roster, and usually no region.

Activity Delta List

GET/v1/activity?days=<n>

The list as recorded every minute, over the last n days (1 to 30, default 7). Refreshed once a minute.

{"days": 7, "bucket": 600, "minutes": 10079, "since": 1790390040,
 "samples": [[1790389800, 43, 5], …],      // [time, most players, most games] each 10 minutes
 "now": {"time": 1790994600, "players": 32, "games": 4},
 "peak": {"time": 1790950200, "players": 63},   // the last 24 hours
 "maps": [{"map": "bloodgulch", "minutes": 9800, "player_minutes": 91980}, …],
 "servers": [{"name": "[D] Bloodgulch", "minutes": 9776, "player_minutes": 67680,
              "most_players": 29, "last_seen": 1790994600}, …]}

minutes is how many minutes were recorded in the range, so a server's uptime is its minutes divided by that. Servers are grouped by game name.

Each minute counts the games that were counted then (see Live games): a game that has ended isn't counted while it waits to go off the list, and a host that hosts again is counted once.

Finished games Delta Stats

A game that ends with at least two players, after at least a minute, is kept as a carnage report. Games that crash or get quit early aren't recorded.

Every report has a tier: host (sent by the game's host), verified (the host didn't report it, but two or more players at different addresses sent the same scores of a game the list saw), or player (sent by one player, or by players who disagree). A player game counts only on the line of the player who sent it ("reported": true) and for no leaderboard or record. It appears only on that player's own record (by player ID), in no list of games, and shows only the numbers of its reporters' own lines (the other lines have just name, team, place and "unconfirmed": true).

GET/v1/reports

A page of recent games, newest first, without the players.

QueryMeaning
limitHow many, 1 to 50 (default 10)
beforeOnly games with an ID below this, for the next page
mapOnly this map (as the reports name it)
engineOnly this game type
{"reports": [{"id": 79, "time": 1790993717, "map": "levels\\test\\ratrace\\ratrace",
              "engine": 2, "teams": 0, "host": "[D] Slayer", "winner": "B", "player_count": 6,
              "tier": "host"}],
 "total": 21,           // games matching the filters
 "maps": ["levels\\test\\bloodgulch\\bloodgulch", …]}   // every map played
GET/v1/reports/<id>

One full carnage report.

GET/v1/history?limit=<n>

The last n full reports, newest first (default 100, at most 1000). This is what the leaderboards are built from.

A full report

{"id": 79, "time": 1790993717,          // Unix time it ended
 "map": "levels\\test\\ratrace\\ratrace", "engine": 2, "teams": 0,
 "score_limit": 15, "duration": 492,    // seconds
 "team_scores": [0, 0], "host": "[D] Slayer", "winner": "B",
 "players": [{
   "name": "B", "team": 0, "place": 1, "color": 3,
   "score": 15, "kills": 15, "assists": 4, "deaths": 13,
   "betrayals": 0, "suicides": 0, "shots_fired": 0, "shots_hit": 0, "multikills": 1,
   "flag_grabs": 0, "flag_returns": 0, "flag_scores": 0,
   "ball_time": 0, "ball_carrier_kills": 0, "hill_time": 0, "laps": 0,
   "medals": {"killing_spree": 1},
   "weapons": {"weapons\\assault rifle\\bullet": 9, …},   // kills by the killing damage's tag
   "best_spree": 7, "joined": 0,       // seconds into the game they joined
   "player_id": "0f3c…"                 // only on confirmed lines
 }, …],
 "tier": "host",                        // host, verified or player
 "stats": 1,                            // from builds that record every kill
 "sources": {"statistics": "host", "medals": "host", "weapons": "host", …},
 "departed": [{"name": "C", "kills": 2, "deaths": 4, "left": 210, …}],   // left early
 "reporters": 2}                        // players who sent the same scores

color, the objective stats and medals only appear in reports from newer builds; weapons, best_spree, joined, departed, stats and sources from newer still. sources says how each part was known: host (the host's own numbers), synced (the host's numbers, as every player's game is sent them) or feed (worked out from the kills a player's game saw). Where a player's game didn't see all of someone's kills, that line has "feed_incomplete": true and no medals, weapons or spree. In reports with stats, multikills counts every multikill. Older reports have multikills_chain instead: the game's own counter when the game ended, which is only the multikill under way then, not a count.

Players Delta Stats

A confirmed player has a player_id (32 hex digits) that follows them whatever name they use. Anyone else is only a name, and two people can share one. The leaderboards count confirmed players only.

GET/v1/players/<name or player_id>

The full reports of every game that player played, newest first. A name matches any line with that name, in any case. A player ID matches only that player's confirmed lines, and adds their public profile (or null).

{"reports": [ … ], "profile": {"handle": "milenko", "display": "Milenko", "bio": "…"}}
GET/v1/profiles/<player_id>

A player's public profile, or 404 if they haven't made one, with the badges it shows.

{"handle": "milenko", "display": "Milenko", "bio": "…",
 "badges": [{"id": "developer", "name": "Developer", "description": "…", "icon": "code", "kind": "granted",
             "since": 1791331200}]}
GET/v1/badges

Every badge in use and which profiles show which, to mark names in a list without asking for each profile. kind is granted (given by the site) or automatic (worked out from a player's confirmed games, the platforms their own game says they play on, and when they made their profile). A profile's owner may hide its automatic badges; hidden ones aren't listed.

{"badges": [{"id": "played_xbox", "name": "Played on Xbox", "description": "…", "icon": "xbox", "kind": "automatic"}, …],
 "players": {"0123456789abcdef0123456789abcdef": ["developer", "played_xbox"], …}}

Matches, maps and records Delta Stats events

Hosts that share stats (our dedicated servers, and community servers and ChupathingyCE hosts unless they turn it off) send each game's event log when it ends (ChupathingyCE 0.7.1b and later). These endpoints return what the site keeps from those logs, as JSON cached for a minute. No answer holds an address or a hardware ID. Players who hide their stats are left out of the boards and their own pages answer {"hidden": true}. What we collect.

POST/v1/events

A game's batch, from its host (JSON, gzip or not; schema 1, in the game's docs/delta.md). Our servers send Authorization: Bearer <token>; any other host's game must be listed from the same address. Each server may send one batch every 20 seconds and 60 an hour, at most 2 MiB each.

{"ok": true, "match": 123, "tier": "trusted", "duplicate": false, "ignored": []}
GET/v1/analytics/overview · /v1/scoreboards/players/ID · /v1/verified/leaders

Totals: matches, seconds played, maps, servers, kills, players, matches this week, games under way.

GET/v1/matches?server=&map=&before=&limit=

Recorded matches, newest first (limit up to 50; before a match ID, for the next page).

{"matches": [{"id": 123, "server": 1, "server_name": "[D] Slayer", "tier": "trusted", "started": 1791234567,
  "ended": 1791235467, "duration": 900, "map": "levels\test\bloodgulch\bloodgulch", "map_key": "bloodgulch",
  "engine": 2, "gametype": "Team Slayer", "teams": 1, "team_scores": [50, 41], "players": 12, "ranked": true,
  "final": true, "report": 456, "leader": "Walter", "kills": 91}]}
GET/v1/matches/<id>

One match: the same, with players (each line's score, kills, deaths, assists, accuracy, best spree, medals, weapons by tag with shots, hits, kills and headshots), its timeline (kills as [t, killer, victim, weapon, damage, flags, killer_pos, victim_pos, killer_vehicle], flags 1 headshot, 2 betrayal, 4 suicide, 8 stuck, 16 victim riding; medals, objectives, pickups, rides, sessions, and the game's ping a minute), and its replay (each player's sampled positions in tenths: {"scale": 10, "players": {"0": [[t, x, y, z], …]}}, kept 30 days; null after). Players are their index in players; -1 is no one (the world).

GET/v1/maps · /v1/maps/<map_key>?days=

The maps with recorded matches; one map's numbers over the last days (1 to 365, 90 by default): matches, average length, game types, weapons by kills, accuracy, hot spots, longest kills, top players and its latest matches. A map's key is its file (bloodgulch), ce/<file> for Halo PC maps or md/<file> for HaloMD maps.

GET/v1/heatmap?map=&kind=&days=&weapon=&player=

A map's squares, counted: kind is kills (where killers stood), deaths, positions, spawns or spawn_deaths (spawns their player died within 10 seconds of); weapon a damage tag; player a confirmed player's ID for their own kills or deaths. Each square [gx, gy, count] covers gx * cell to (gx + 1) * cell world units. /static/map_bounds.json has each map's world bounds over its top-down picture, /art/overhead/<map>.png.

{"map": "bloodgulch", "kind": "kills", "days": 30, "cell": 0.5, "matches": 40,
  "cells": [[120, -240, 17], …], "total": 1290, "weapons": [["weapons\sniper rifle\sniper bullet", 310], …]}
GET/v1/analytics/players/<player_id> · /v1/analytics/players/<player_id>/games?before=&limit=

A confirmed player's service record: totals (with accuracy and their longest kill), armor color, game types, playlists, maps, weeks (trend), favorites, weapons, vehicles, medals, pickups, their games in order (series), best sprees, the maps they have a heatmap on, and their profile's badges; and their game history, 25 a page (before: the last page's oldest). A name answers {"name_only": true} (a name alone can be anyone's); a player who hides their record {"hidden": true}.

GET/v1/analytics/search?q= · /v1/analytics/medals?days= · /v1/analytics/overview

Confirmed players whose name has the text; every medal with how many were earned over the last days (0: all time) and its top earners; the site's totals.

GET/v1/playlists · /v1/playlists/<name>?days=

The dedicated servers' playlists; one playlist's games, population by day, maps and game types, top players and latest games.

GET/v1/matches?report=<id>

The recorded match of a carnage report, {"match": n}, or 404.

GET/v1/analytics/leaders?days=&engine=&teams=&map=&playlist= · /v1/analytics/records?days=

Leaderboards (kills, K/D, accuracy, wins, win rate, headshots, sprees, assists, damage, flags, ball and hill time) and single-game records (most kills, longest spree, biggest multikill, best accuracy, longest kill, …) over the last days, 0 for all time; the leaderboards of one game type (engine and teams), map or playlist. Confirmed players only. Ranked matches only: two or more players, no bots, at least a minute long, and not ended by a server stopping. The ratios need minimum_games games, and accuracy needs minimum_shots shots.

GET/v1/servers · /v1/servers/<id>?days=

The servers that send events; one server's activity by day and by hour (UTC), players and health, map rotation, game lengths, top players and latest matches.

POST/v1/session/stats-privacy

Signed in (the page's own request: X-Halo: 1), {} reads your setting and {"hide_stats": true, "hide_heatmap": false} sets it.

Connecting a game Delta Link

Link Profile links a game to a player's profile without a web browser on the game's side (a Steam Deck, Game Mode, a console). The game asks for a code and shows it. The player enters it at /connect, signed in on any device. The game then asks its player to confirm, so a code seen on a stream or in a screenshot can't be used to take over the game. Each step times out after 2 minutes. ChupathingyCE builds do this for you. The game's calls go over HTTPS only, since they carry the player key.

POST/v1/connect/start

JSON {"key": "<player key, 64 hex digits>"}, and optionally "name", the player's name in the game. The answer is one line of text:

ok K7M-2QX 120 3f9c…   // the code, how many seconds it's good for, a poll token (64 hex digits)

The code is 7 characters, XXX-XXX, from 23456789ABCDEFGHJKMNPQRSTUVWXYZ (no 0, O, 1, I or L). It works once. Each key and each address may request 10 codes an hour. Past that, 429. A bad key is 400 bad key.

POST/v1/connect/status

JSON {"token": "<poll token>"}. Ask every 3 seconds or so while the code is on screen. One line:

AnswerMeaning
pendingNobody has typed the code in yet
confirm <profile>Typed in by that profile: ask the player to confirm (2 minutes)
confirm <profile> <previous>The same, and the game's games currently count for previous. Confirming moves them
connected <profile>Confirmed and linked
declinedThe player said no
expiredThe code or the claim lapsed (or the server doesn't know the token)
POST/v1/connect/confirm

JSON {"token": "<poll token>", "accept": true} (or false), once the player chose. One line: connected <profile> (linked now), declined (nothing changed; the code and token are done), or expired (no claim waiting, or it waited more than 2 minutes). A token may try 5 times, and an address 30 times, in 10 minutes. Past that, 429.

POST/v1/connect/claim

The /connect page's own call, signed in: JSON {"code"}, in any case, with the dash or without. It uses up the code and waits for the game, answering 202:

{"waiting": true, "handle": "milenko", "name": "Milenko",
 "previous": null,        // the profile the game's games count for now, if another
 "confirm_seconds": 120}

A session, and an address, may type 10 wrong codes in 10 minutes.

POST/v1/connect/claimed

Also the page's own call: the state of this session's latest claim. The page asks every 2 seconds.

{"state": "connected",   // or "waiting", "declined", "expired"
 "handle": "milenko",   // the profile the game's games count toward
 "name": "Milenko",     // the game's player name, if known
 "games": 12,           // games that game played already
 "same": false,         // true if the game already played as this profile
 "other": null,         // the profile the game had of its own, if any: it stays, its games count here
 "before": null,        // the profile the game was connected to before, if any
 "previous": null, "confirm_seconds": 0}

Finished games don't change. Their lines keep the game's player ID, which every report and the leaderboards show as the profile's.

Listing a game Delta List

ChupathingyCE builds do this for you. This part is for people adding the game list to another build. To list a game by hand, use your profile instead.

POST/v1/announce

Form encoded (application/x-www-form-urlencoded). Send it again at least every 75 seconds while the game runs, or it drops off the list. The reply is ok <seconds until it expires>.

FieldMeaning
inviteThe game's invite, 64 hex digits
nameThe game's name
mapThe map (required)
engineThe game type number
players, maximum_playersPlayer counts
open1 if it takes new players
versionYour build's network version
score_limit, teamsThe game's score limit, and 1 for a team game
rosterteam:name pairs joined by |; send it last

Optional fields about the host (ChupathingyCE 0.7.1b and later). Each is checked on its own: a field the list can't read is dropped, never the whole announcement. Older versions of the list ignore them.

FieldMeaning
deltaThe Delta major the game is hosted with (1), or 0 for a game hosted on OpenCE's protocol alone
platform_keyThe host machine's Delta platform key, 16 hex digits (the 8 bytes of the game's docs/delta.md: platform, key version, flags, limits, memory class)
archx64, x86, arm64 or arm
buildThe build's version, such as 0.7.1b
capabilitiesThe Delta capabilities the host offers, 8 hex digits
machinesThe game's machines by platform, pc_linux:2,xbox:1,legacy:1: counts only

A new game isn't listed right away. The list's probe first checks that the invite leads to a running game, which takes a few seconds. If it can't reach the game, announcements of that invite are refused with 403 for two minutes. Once listed, the game is probed again every two minutes and dropped after three misses in a row.

An address may list 4 games at a time, and an invite can only be announced from the address that first listed it.

POST/v1/withdraw

Form field invite. Takes your game off the list now instead of waiting for it to expire.

POST/v1/report

A finished game's carnage report, as JSON in the shape above plus its invite. It's only accepted from the address that lists that game, at most once a minute. Games with fewer than two players, shorter than a minute, or on a campaign level (co-op), are refused with 422.

POST/v1/client_report

A player's report of a game they joined, sent by ChupathingyCE when the game ends, whoever hosted it: JSON in the shape above, plus invite, key (the player key, which identifies the reporter; only the player ID derived from it is kept, as with confirmed lines), reporter (the reporting player's name, one of the lines), and the game's map, engine, score_limit and host (its name). Reports with the same invite and map that end within a minute and a half of each other, and are about the same length, are treated as one game. A host's report takes precedence. Answers ok <id> <tier>; 422 for a game too short or small, a co-op game, or one with impossible numbers (more kills than deaths, more than a kill a second); 429 for a second report from one player within 30 seconds.

Delta's legacy table Delta List

The signed table that tells ChupathingyCE builds which OpenCE network versions they can announce and join (Delta). Builds from 0.7.0b fetch it at startup and every four hours, and pass it to each other over Delta Peer. CI signs each table and commits it to the game repository's delta-table branch; this site pulls it from there every five minutes, takes only a newer serial with a valid signature, and serves the two files as they are. A reader checks the signature itself, so it doesn't need to trust this site.

GET/v1/delta/legacy

The document: JSON (application/json), at most 16384 bytes. Its exact bytes are what is signed, so keep them as they come.

{
  "delta_legacy": 1,          // the document's format
  "serial": 42,               // higher for each table published
  "issued": 1791331200,       // when it was made (Unix time), optional
  "wires": {                  // one row per wire: the game protocol a build speaks
    "chupa-20a": {"announce": 22, "minimum": 11, "maximum": 22,
                  "follows": "build-145"}   // the OpenCE build the cross-play test proved it with, optional
  },
  "disabled_capabilities": [],  // Delta Peer capabilities switched off
  "platform_policy": {}         // reserved
}
GET/v1/delta/legacy.sig

The document's signature: Ed25519 (RFC 8032) over the document's exact bytes, as 128 hex digits and a line feed (text/plain).

Checking it

  • Check the signature against the public keys in the game's port/linux/src/delta_key.h (also listed in docs/delta.md): a primary and a recovery key. A table signed with either is valid. Check it before reading the document.
  • Take a table only if its serial is higher than the one you have, and read only the row of your own wire. A build takes a row only if it widens its built-in numbers, never if it narrows them.
  • The game's tools/delta_table.py verify legacy.json checks a downloaded pair (with legacy.json.sig beside it) against those keys.

Caching

Both answers have Cache-Control: public, max-age=300. Fetch the two together, and not more than every few hours: the game fetches at startup and every four hours (thirty minutes after a failure).

None yet

Both answer 404 no legacy table until a table is published. Builds then play with the numbers they shipped with. The same two files are also on GitHub, on the delta-table branch of ChupathingyCE/chupathingyce (legacy.json and legacy.json.sig), which the game reads when this site can't be reached or has no valid table.

Building on Delta

Delta is open: anyone may build a client, server or tool that speaks it. The endpoints on this page are its HTTP parts. Delta Peer, the channel between ChupathingyCE machines in a game, is UDP: its wire format (the header, the handshake and every message), the capability registry, platform keys and the legacy table's rules are in docs/delta.md in ChupathingyCE/chupathingyce.