hosting plan
docs/hosting-plan.md
Jump to: What Martin decided (2026-10-18) · Why this exists · What exists today that this builds on · The design · Phases (each stops for Martin's approval) · Open questions for Martin (to be asked one at a time, before the phase that needs them)
Written by Claude, 2026-10-18, from Martin's answers the same day. Private (operator-only; listed in publish.sh PRIVATE). Built one phase at a time, stopping for Martin's approval after every phase. Nothing here is started until Martin approves this plan.
What Martin decided (2026-10-18)
| Question | Decision |
|---|---|
| Who pays | The game builder, a monthly subscription that keeps their game's server online. |
| What it buys | Up to a GB limit per month, and the limit is set by each node operator. |
| What can be bought | Two products: the current game server (realtime rooms, as ASHVALE uses today) and the WebRTC fast lane. |
| Latency | Nodes advertise their ping, so a builder who needs a fast server does not buy a slow one. |
| Payment | The operator decides which coins or tokens it accepts, with any price in each. |
| Who gets paid | Every node that serves the game, in proportion to the game's players' usage on it. Mesh rooms span nodes, so one subscription pays many operators. |
| Game-agnostic | As always: any game uses it; nothing knows about a particular game. |
Why this exists
The realtime rooms are free and slow on purpose: 5 messages a second per player, bursts of 10, 512 bytes (arcade/mesh/node.py RATE, BURST, MAX_PAYLOAD), carried as an HTTP POST up and server-sent events down, over TCP, often through Cloudflare. A request through the public site measured 170–300 ms on 2026-10-18. That is right for an RPG, cards or chess and wrong for a shooter. Paid hosting buys more for one game (higher rates, a fast lane), and makes the operators who carry the traffic get paid for it.
What exists today that this builds on
- Mesh rooms (
arcade/mesh/node.py,service.py,web/realtime.py). A room isgame/room. Each member joins through one node (its origin), and messages are forwarded between nodes. The origin node enforces the rate limit and forwarders drop senders far over it, so a node cannot invent more traffic than its real players can send. Usage counting relies on this. - Node identity keys. Mesh links are signed with each node's key, so receipts can be signed with the same key.
- Mesh offers with a payee. Shared Cores already advertise per-chain offers with a payee address "p" (
service.pyshared(),coreshare.pypayee()), and_sharer_cutalready pays a tenth to a sharer. Hosting offers follow the same pattern. - Two-key pools. Prize pools with a referee hold coins, tokens or pieces at a two-branch script address, with the owner's own key able to close it (
docs/referee.md,prize-pools.md). The lease pool is the same shape. - Token transfers checked by index, as the Exchange does, and coin payments checked from each chain's index (
utxos.py), including borrowed chains. - The WebSocket mesh link (
mesh/wsock.py) and the TURN relay planned for voice (voice-plan.md). The fast lane and voice should share one relay.
The design
1. Hosting offers (what a node sells)
In Admin, under Hosting, the operator sets, for each product (Rooms, Fast lane):
- on or off;
- monthly GB limit for each game, and an overall GB limit for the node;
- players online at once for each game;
- accepted payment: a list of
{asset, price per month}, where the asset is any coin the node runs or borrows, or any arcade token (for example 500 PEP, or 2,000 ARCADE, or 0.5 LTC). The arcade does no currency conversion and shows no exchange rates; - what happens at the limit: slow down (the default: back to free-room rates) or stop, and an optional price for extra GB.
Paid rooms get higher limits than free ones. These are suggested defaults, and the operator can change them: Rooms 20 msg/s, burst 40, 1 KB; Fast lane 60 msg/s, 1 KB, unordered.
The node advertises the offer on the mesh next to its Core offers: {"host": {"rooms": {...}, "fast": {...}, "pay": [{asset, price, payee}], "free": {gb, players}, "region"}}. payee is the node's address for each asset: payout_address for coins, and the operator account's address for tokens.
2. Ping (so nobody buys a slow server)
A browser cannot send ICMP pings, so there are two measured numbers:
1. Live, from the builder's browser. The Host your game page times repeated requests to each node: kept-alive HTTPS to /realtime/hello for Rooms, and STUN binding round trips for the fast lane (the real UDP path). It takes the median of several and sorts nodes by it. A node cannot fake this, because the builder measures it. Over ~80 ms the fast-lane offer is marked "too slow for a fast game." 2. Advertised, from the mesh. Each node publishes its measured round trips to the nodes it links to, which the mesh already times. That gives "~40 ms to most US nodes" for offers the builder's browser has not tested yet.
Players get ping-based routing too: a player of a leased game joins through the serving node with the lowest ping from their browser.
3. The lease and the lease pool (how the builder pays)
- A lease is
{game, builder address, home node, products, asset, budget per month, price ceiling, started, paid until}, inscribed as a small JSON like a pool, so any node can read it. - Which game: an inscription id is unambiguous. A short game name (
my-game) is not owned by anyone, so a lease on a short name binds it to the builder's address, and nodes serve the paid tier only for that game id under that lease. Open question 1. - The builder funds a lease pool, a two-branch script address:
- settle branch: the home node's settler key signs payouts according to the agreed usage (below);
- refund branch: the builder's own key, always, so unspent money is never stuck, even if every node disappears.
- The builder picks the home node on the Host page, usually the one with the best ping. The home node handles signaling for the fast lane, keeps the lease record, gathers receipts and signs settlements. It gets a small fixed slice, suggested at 5%. Open question 2.
- Coin payments count from the mempool. Token payments count after one confirmation, because tokens are only seen confirmed.
- Renewal: three days before
paid until, the builder's arcade shows a notice and a one-tap top-up in the same asset. If the operators drop that asset, the notice says to pick another one. Accounts sign in the browser, so no node can top up for the builder. A builder who wants no gaps funds several months at once; the pool just holds more. - Unpaid: when the pool cannot cover the next settlement, the game drops to free rooms. Nothing breaks, and players of a fast-lane game see "this game's server lease has ended." There are no hard failures.
4. Usage counting and receipts (who served what)
Each node counts, for each leased game, for its own members only (those whose origin it is):
- bytes up (what its members sent);
- bytes down (what it delivered to its members);
- player-minutes;
- for the fast lane, relay bytes it carried as TURN.
Forwarded traffic is never counted by the forwarder, so nothing is counted twice.
Every hour each node signs a receipt with its node key, {game, node, hour, up, down, minutes, relay}, and sends it to the lease's home node over the mesh. Checks:
- Up is checked by the nodes that received it: each node also reports what it got from each other node for that game. Where the two disagree, the lower number counts.
- Down cannot exceed the room's total traffic × that node's members, so the home node caps it.
- Rates cannot exceed the rate limit × members, because the origin node enforces it.
- Relay is capped by the players who reported needing relay (a candidate type in the WebRTC setup).
Fake players (decided by Martin 2026-10-18: the builder's cap, plus a majority ban). The rate limit stops a node inventing traffic for real players, not inventing players. So:
- Builder's cap. Each node's share of a week's usage is capped at its share of the distinct players the builder's own (home) node also saw in that game's rooms. Players the room never really had pay nothing.
- Majority ban, per game pool. Any serving node may propose banning another from one game's pool, with its evidence (receipts that do not add up, players nobody else saw). If more than half of that pool's serving nodes vote yes within a week, the node is removed from that game's pool only: no more payouts from it, and its players route through other nodes. Votes are signed with node keys and kept with the receipts, so every node can recount them. A node removed from one game is untouched elsewhere.
- Who votes. Only nodes that were paid by that pool in the last four weeks, one vote each. A node that was never paid cannot vote, so a cheater cannot start fake nodes to win the vote. Open question 7 (one vote per node, or votes weighted by each node's agreed usage).
- The builder can also remove a node from their own pool at any time, with no vote.
Receipts are kept for 90 days, and any node can ask the home node for the month's set to check it.
5. Settlement (how many nodes get paid)
- The home node settles weekly. Weekly rather than monthly means less is at stake if the home node misbehaves. Each serving node gets: (this week's budget − home slice) × its share of the agreed usage.
- Usage weight: one GB of fast-lane relay weighs the same as one GB of rooms; player-minutes are a tie breaker only. Open question 3 (weigh by GB only, or GB and minutes).
- Prices differ by node. A builder sets a price ceiling per GB. A node whose price (from its offer) is above the ceiling does not take new players for that game; they route to other nodes. A node at its monthly GB limit does the same. Each node's payout is capped at its own price × its GB, and anything left over stays in the pool for next week.
- Borrowed chains: for a coin on a chain the node borrows, the existing
_sharer_cuttakes the sharer's tenth out of that node's part. For tokens it is off by default, with an Admin switch to pass a tenth on. - One transaction per settlement, paying every node at once, with outputs under dust rolled into the next week.
- Trust (decided by Martin 2026-10-18: vote it out): the settle branch trusts the home node, limited by weekly settlements, public transactions every node checks against its own receipts, and the builder's refund branch. A home node that settles wrongly can be removed by the same majority vote as any node (section 4). The next-busiest serving node becomes home and settles from then on. The pool cannot be re-keyed, so the builder's arcade moves the remainder to a new pool for the new home node at the builder's next visit, and payouts wait until then. The builder can also switch home node by hand. Open question 4 (whether a removed node should also be marked publicly on the mesh, beyond that game's pool).
6. The WebRTC fast lane
- What it is: browser-to-browser data channels over UDP, set to unordered with no resends, so a lost position update is skipped instead of holding up the rest. Encryption (DTLS) is built in.
- Signaling: the offer/answer and network addresses go through the game's existing realtime room (the home node), so there is nothing new for a game to set up.
- STUN: every node offers one. It is tiny and free.
- TURN: every node runs a relay for the ~10–20% of players who cannot connect directly. Credentials are short-lived and tied to the lease (
username = expiry:game:member, HMAC with the node's secret), so the relay knows which game each player is in. No free relay (Martin 2026-10-18, replacing the earlier small free relay: "the game designer should pay the fee if they want a relay hosted"). Direct connections, signaling and STUN are free for every game. The relay is only for a game whose builder holds a lease. In a game without one, a player who cannot connect directly (carrier NAT on IPv4, like Starlink) plays that link over the normal rooms instead, slower but playable. Two things soften this: most carrier-NAT connections (Starlink included) also have IPv6, and WebRTC tries IPv6 first, so many connect directly anyway; and an operator can lease to their own games at price 0. Relay bytes are metered as in section 4. Shared with voice (voice-plan.md): one relay implementation, two uses. - Shape: full mesh (every player connected to every other) up to ~8 players. Larger games choose a host, one player's browser acting as server, in this version. A node-run authoritative server (aiortc/pion, judging hits so nobody can claim them) is a later phase, sold per hour under the same lease.
- Page API:
arcade.realtime.join(name, {game, fast: true})returns the same room object plusroom.fast.send(data)androom.on('fast', ...). A game without a fast lease getsroom.fast = nulland keeps working over normal rooms. - Install: the relay must run on a plain
install.pynode without sudo. Open question 5 (our own small Python TURN on asyncio UDP, or a bundled coturn/pion binary).
6a. The Cloudflare relay that exists now, and its subscription fee (2026-10-23)
- Built (2026-10-23): the first relay is not a node-run TURN but Cloudflare TURN on the operator's own key (Martin chose "Cloudflare TURN" after a two-home video call failed: Starlink's shared IPv4). Admin -> Network -> Call relay: the key, which apps may use it ("Apps I choose, any account"; none chosen: every app), and a usage meter against a monthly limit (Cloudflare bills egress; at the limit the relay is no longer handed out). Signed-in players only. The operator pays Cloudflare.
- To add (Martin 2026-10-23): a subscription fee for this relay -- the apps (or their players) that use an operator's relay pay that operator for it, so the relay's bill is not the operator's alone. Not designed yet: who subscribes (an app's builder, as the lease above, or each player), per month or per GB, the assets accepted, and how a lapsed subscription falls back to direct links. It should reuse section 3's lease and section 4's metering where it can. Admin's Call relay card says it is coming.
7. The Host your game page
Under My arcade → Host your game, for builders:
- the list of offers on the mesh with the builder's own live ping, price in each accepted asset, GB limit, free capacity and region; filters: product, asset, max ping, max price;
- pick a home node, product(s), asset, monthly budget and price ceiling, then fund the pool;
- a dashboard: paid until, GB used this month per node, settlements (with links to the transactions), top-up, move home node, close lease (refund branch).
For operators, Admin → Hosting shows the offer settings, leased games served, GB and players per game, weekly earnings by asset, and receipts.
Phases (each stops for Martin's approval)
1. Usage counting and receipts, with no payments. Count bytes, player-minutes and peer reports for every game. Admin shows the numbers. Ship it and watch ASHVALE's real monthly usage before any price is set. Harmless: no behaviour changes. Tests: three-node regtest mesh; members on each; counts add up, forwarded bytes counted once, receipts verify, mismatches resolve to the lower number. 2. Offers, leases, the lease pool and settlement, on Rooms. Admin Hosting settings, mesh offers, lease inscription, pool script (settle + refund), weekly settlement across nodes, paid-tier rates, slow down at the limit, coins and tokens, the sharer's tenth. Tests: regtest Pepecoin and Litecoin; a builder funds a pool in a coin and in a token; three nodes serve; settlement pays each by usage; the refund branch works with every node gone; an overcharging node routes no new players. 3. The WebRTC fast lane. Built 2026-10-18 without the relay (branch claude/fast-lane): STUN on every node, signaling over rooms, room.fast, the room fallback. The relay and its metering come with leases. STUN on every node, TURN on paid nodes, signaling over rooms, room.fast, relay metering. Tests: two headless Chromium browsers connect directly and through the relay; lost-packet behaviour; relay refused without a lease. 4. Ping and the Host your game page. Live ping, mesh-advertised ping, ping-based routing for players, the builder dashboard. Tests: browser tests with a slow node simulated (delay injected), which is sorted last and flagged. 5. The first fast-lane game: a 4 v 4 first-person shooter in three.js (Martin, 2026-10-18; replaces the tank shooter). A completely new game, in its own repository and inscribed like RACE CONDITION, using only the public page API, so it proves the fast lane is game-agnostic.
- Map: inspired by a small suburban test-town arena, but original. Two two-storey houses facing each other across a cul-de-sac, with yards, fences, parked vehicles and a bus as cover, and three lanes. It is mirror-symmetric so both teams get the same map, with a desert backdrop. All geometry is our own, built in code. It does not use the name of, models from, or an exact copy of any commercial game's map, because those belong to their publisher (the same rule as ASHVALE's public text).
- Engine: three.js, starting from its own first-person example (
games_fps: octree collisions, capsule player; MIT licence, credited). Three ways to play, all at once (Martin 2026-10-18): - keyboard and mouse (mouse lock, WASD, Tab for the scoreboard);
- on-screen controls on phones and tablets: a left stick to move, drag the right side to aim, and buttons for fire, aim down sights, jump, crouch, reload, switch weapon and scoreboard, sized for thumbs, with an option for left-handed players;
- gamepad (browser Gamepad API: Xbox, PlayStation and generic controllers, plugged in or Bluetooth): sticks to move and look, triggers to aim and fire, the usual face buttons, adjustable look sensitivity and aim assist (light, gamepad and touch only). The on-screen controls hide when a gamepad or mouse is used and come back on touch.
- Mode: Team Deathmatch, 4 v 4 (8 players at most), first team to 50 kills or 10 minutes. Teams are balanced automatically, a late joiner fills an empty slot, and a ninth player is told the match is full and offered a new one.
- Network: direct browser to browser (full mesh), with no host and no node server; a player who cannot connect directly uses the builder's leased relay, or the normal rooms for that link if there is none. Positions go over
room.fast(unordered, 30 Hz) and are drawn about 100 ms behind with interpolation. Shots are hitscan: the shooter's browser checks the hit against what it saw (lag compensation) and sends it. The victim's browser applies the damage after sanity checks (range, line of sight, fire rate). Deaths and kills go over the reliable room. The match timer, teams and score are kept by the player with the lowest member id, and the next lowest takes over if that player leaves. - Scoreboard: team score plus kills, deaths and K/D for each player, held on Tab (desktop) or a button (phone), and shown at match end.
- Weapons: a rifle and a pistol to start; grenades later.
- Release: the games release gate applies (Martin playtests a private build first).
- Tests: two, four and eight headless browsers in one match, every pair connected directly; kills, deaths and score agree on every screen; the keeper leaving hands over cleanly; a ninth is refused. same lease, and judging hits through the referee's judge format.
6. Later: node-run authoritative servers for big or wagered fast-lane matches, sold per hour under the
Open questions for Martin (to be asked one at a time, before the phase that needs them)
1. Short game names: should a lease bind a name to its builder (first lease wins), or should paid hosting require an inscription id? 2. Home node's slice: 5%, another number, or none? 3. Settlement weight: GB only, or GB plus player-minutes? 4. A home node caught settling wrongly: just switchable, or also marked publicly on the mesh? 5. TURN relay: our own small Python relay (no sudo, more work) or a bundled binary (coturn/pion)? 6. Free rooms: stay at today's rates forever, so small games can exist without paying? 7. Ban votes: one vote per paid node, or weighted by agreed usage?