Call Flow Diagrams
Contents
Status: Design specification Date: 2026-03-26
Overview
Three distinct player scenarios, each with different network topology, data ownership, and communication patterns. This document traces the complete flow for each — from login through gameplay to disconnect.
The Three Cases
Case 1: CITIZEN ON OWN HOST
Player's ship IS the world host.
Everything is local. No Ames. Simplest case.
Case 2: CITIZEN VISITING REMOTE HOST
Player's ship connects to another ship's world.
Ames for game commands. Eyre for web client.
Character data splits between two ships.
Case 3: GUEST ON HOST
No Urbit. Browser connects to host's Eyre.
Everything on the host. Simplest data model.
Case 1: Citizen Playing on Own Host
The player owns the ship that runs the world. They’re playing their own world — building it, testing it, or just adventuring in their own content.
Network Diagram
┌─────────────────────────────────────────────────┐
│ Player's Ship │
│ │
│ ┌──────────┐ ┌─────────────┐ │
│ │ Browser │────▶│ Eyre │ │
│ │ (Web UI) │◀────│ (HTTP/SSE) │ │
│ └──────────┘ └──────┬──────┘ │
│ │ │
│ ┌─────▼──────┐ │
│ │ %mud-world │ │
│ │ │ │
│ │ • Rooms │ │
│ │ • Mobs │ │
│ │ • NPCs │ │
│ │ • Quests │ │
│ │ • Sessions │ │
│ │ • Combat │ │
│ └─────┬──────┘ │
│ │ │
│ ┌─────▼──────────┐ │
│ │ %mud-character │ │
│ │ │ │
│ │ • Stats │ │
│ │ • Inventory │ │
│ │ • World records│ │
│ │ • Home world │ │
│ └────────────────┘ │
│ │
│ ┌──────┐ ┌──────┐ │
│ │ Behn │ │ Ames │ (idle) │
│ │(tick)│ │ │ │
│ └──────┘ └──────┘ │
│ │
│ No network traffic. All local. │
└─────────────────────────────────────────────────┘
Login Flow
1. Player opens browser → https://localhost:8080/mud
│
▼
2. Eyre serves glob (index.html + SolidJS app)
│
▼
3. Player authenticates via Eyre +code
Browser: POST /~/login body: password=<+code>
Eyre: sets session cookie, returns 204
│
▼
4. Web client pokes %mud-world: [%mud-command [%score ~]]
(or any initial command to establish session)
│
▼
5. %mud-world checks: is this our own @p?
Yes → look up citizen-index for existing session
No session exists → create one:
- Load character from %mud-character agent (local poke)
- %mud-character returns character data
- Create player-session in %mud-world state
- Place character in last known room (or spawn-room)
│
▼
6. Web client subscribes: PUT /~/channel/mud-{ts}
Action: subscribe to /game/{session-id}
│
▼
7. %mud-world emits initial updates via subscription:
- [%motd "Welcome back..."]
- [%room-enter room players mobs items]
- [%vitals hp max-hp mana max-mana moves max-moves]
- [%mail-notification count]
- [%online-join ...] for each connected player
│
▼
8. Player is in the game.
Gameplay Flow (Single Command)
Player types "kill wolf"
│
▼
Browser: PUT /~/channel/mud-{ts}
Action: poke %mud-world with %mud-command [%kill "wolf"]
│
▼
Eyre → %mud-world on-poke
│
▼
%mud-world processes:
1. Validate session (cookie → session-id → player-session)
2. Find player's room
3. Match "wolf" against mob-instances in room
4. Initiate combat (set states, add to active-combats)
5. Process first round (damage calc both directions)
6. Emit facts to /game/{session-id}:
- [%combat-start "a shadow wolf"]
- [%combat-round lines]
- [%vitals ...]
7. Emit to other players in room:
- [%chat "say" "" "Wanderer attacks a shadow wolf!"]
│
▼
Eyre SSE stream delivers events to browser
│
▼
SolidJS handler dispatches:
- "combat-start" → update combat state
- "combat-round" → appendLog() for each line
- "vitals" → setHp(), setMana(), etc.
Game Tick Flow
Behn timer fires (every 2 seconds)
│
▼
%mud-world on-arvo [%tick ~]
│
├── Process active combats:
│ For each session in active-combats:
│ Run damage formulas (player → mob, mob → player)
│ Check mob death → award XP, loot, end combat
│ Check player death → trigger death flow
│ Emit [%combat-round] and [%vitals] to player
│
├── Check regen (every 30 sec):
│ For each connected player:
│ Regen HP (CON-based), Mana (WIS-based), Moves (DEX-based)
│ Emit [%vitals] if changed
│
├── Mob AI (every 30 sec):
│ For each mob-instance:
│ Roaming mobs: chance to wander to adjacent room
│ Aggressive mobs: check for players in room, initiate combat
│ Emit [%mob-enters] / [%mob-leaves] to affected rooms
│
├── Area resets (every 60 sec check):
│ For each area past reset-interval:
│ Respawn mobs/items per reset commands
│ Emit [%system-message "You hear movement in the distance..."]
│
├── Quest timers, idle checks
│
└── Re-arm Behn: [%pass /tick %arvo %b %wait next]
Disconnect Flow
Player closes browser tab (or types "quit")
│
▼
Eyre detects SSE connection drop / channel delete
│
▼
%mud-world on-leave fires:
1. Find session by subscription wire
2. If in combat: resolve (mob wins, player "dies" or combat cancels)
3. Remove player from room
4. Emit [%online-leave name] to all subscribers on /online
5. Save character state to local %mud-character agent:
Poke %mud-character: [%save character-snapshot]
6. Clean up session from state
│
▼
Done. All local, no network.
Death Flow (Local)
Player HP reaches 0 (in combat tick)
│
▼
%mud-world processes death:
1. End combat
2. Create corpse in room:
- %none items + 10% gold → lootable by others
- %protected items → owner only
- %soulbound items → NOT on corpse
3. Emit [%you-died ~] to player
4. Emit [%chat "system" "" "Wanderer has been slain!"] to room
│
▼
Since player IS the host, "go home" = go to spawn-room:
5. Move player to spawn-room (this is their home world)
6. Restore HP/mana/moves to full
7. Soulbound items appear in inventory at spawn
8. Emit [%room-enter ...] for spawn room
9. Update corpse-locations on character
│
▼
Player is alive at spawn, corpse is in the dungeon.
Case 2: Citizen Visiting Remote Host
The player owns a ship and is visiting someone else’s world. This is the cross-world case — the most complex flow.
Network Diagram
┌──────────────────────┐ ┌──────────────────────┐
│ Player's Ship │ │ Host's Ship │
│ (~player) │ │ (~host) │
│ │ │ │
│ ┌──────────┐ │ │ ┌───────────┐ │
│ │ Browser │───────────────────────────▶│ Eyre │ │
│ │ (Web UI) │◀──────────────────────────│ (HTTP/SSE)│ │
│ └──────────┘ │ │ └─────┬─────┘ │
│ │ │ │ │
│ ┌────────────────┐ │ Ames │ ┌─────▼─────┐ │
│ │ %mud-character │◀════════════════════▶│%mud-world │ │
│ │ │ │ │ │ │ │
│ │ • Stats (truth)│ │ │ │ • Rooms │ │
│ │ • Inventory │ │ │ │ • Mobs │ │
│ │ • World records│ │ │ │ • Session │ │
│ │ • Home world │ │ │ │ • Combat │ │
│ │ • Corpse locs │ │ │ │ • Record │ │
│ └────────────────┘ │ │ └───────────┘ │
│ │ │ │
│ Player's browser │ │ Host runs the │
│ connects to HOST's │ │ world and game │
│ Eyre, NOT their own │ │ logic │
└──────────────────────┘ └──────────────────────┘
KEY INSIGHT: The browser connects to the HOST's Eyre.
The player's ship communicates with the host via Ames.
These are two separate connections serving different purposes.
Login Flow
1. Player opens browser → https://host-ship.urbit.org/mud
│
▼
2. Host's Eyre serves glob (same web client as all players get)
│
▼
3. Login screen shows:
[Create Guest Character] [Login with Urbit]
│
Player clicks [Login with Urbit]
│
▼
4. Client prompts: "Enter your ship URL"
Player types: https://player-ship.urbit.org
Client stores this as playerShipUrl (persists in localStorage)
│
▼
5. Client opens popup/redirect to player's ship for auth:
Browser: POST https://player-ship.urbit.org/~/login
Player enters their +code for their OWN ship
Player's ship sets auth cookie (scoped to player's domain)
│
▼
6. Client (still in browser) pokes player's ship:
PUT https://player-ship.urbit.org/~/channel/mud-auth-{ts}
Action: poke %mud-character with [%portal-request host=~host-ship]
│
▼
7. Player's %mud-character:
a. Builds character-snapshot + world-records
b. Pokes ~host-ship's %mud-world via Ames:
mark: %mud-portal-enter
{ship: ~player, character: <snapshot>, world-records: <records>}
│
▼
8. Host's %mud-world validates:
- Is ~player banned? → reject
- Character data plausible? → check
- World records signed? → verify signatures
- Trust whitelist → calculate effective level
│
▼
9. Host responds via Ames to player's ship:
mark: %mud-portal-response
[%admitted session-id=0v4d5e... effective-level=85]
│
▼
10. Player's %mud-character receives admission.
Creates an Eyre-accessible session token for the browser:
Stores: {host: ~host-ship, session-id: 0v4d5e..., token: 0v9a8b...}
│
▼
11. Browser polls player's ship: GET /mud/api/portal-status
Response: {"admitted": true, "host": "~host-ship",
"session": "0v4d5e...", "token": "0v9a8b..."}
│
▼
12. Browser now connects to HOST's Eyre with the session token:
PUT https://host-ship.urbit.org/~/channel/mud-{ts}
Action: subscribe to /game/0v4d5e...
Header: X-Mud-Token: 0v9a8b...
(Host validates token against the Ames-admitted session)
│
▼
13. Host's %mud-world emits initial updates via subscription:
- [%motd ...]
- [%room-enter ...]
- [%vitals ...]
- [%mail-notification ...]
│
▼
14. Player is in the host's world.
Browser stores playerShipUrl in localStorage for future sessions.
On return visits, skip steps 4-5 (already authenticated).
Key points about this flow:
- The browser talks to TWO ships: player’s (for auth + portal request) and host’s (for gameplay)
- The player’s ship does the Ames handshake — the browser never touches Ames
- The session token bridges the gap: Ames-authenticated identity → Eyre-accessible session
- Player ship URL is entered once and stored in localStorage
- The
%mud-characteragent needs a/mud/api/portal-statusscry endpoint - The host validates the token against its list of Ames-admitted sessions
Gameplay Flow
Player types "north"
│
▼
Browser: PUT to HOST's /~/channel/mud-{ts}
Action: poke host's %mud-world with %mud-command [%move %north]
│
▼
Host's Eyre → host's %mud-world on-poke
│
▼
Host processes movement:
1. Validate session
2. Check room exits, preconditions
3. Move player between rooms
4. Emit [%room-enter] and [%vitals] to player's subscription
5. Emit [%player-leaves]/[%player-enters] to other players
6. Check for encounters (random spawn chance)
7. Send [%map-room room-summary] for client map cache
│
▼
Host's Eyre SSE delivers to player's browser
│
▼
SolidJS updates (same as Case 1 from here)
NOTE: The player's own ship is NOT involved in routine gameplay.
All commands go Browser → Host Eyre → Host %mud-world.
The player's ship only participates during login, disconnect,
and death (when character data needs to sync).
Disconnect Flow
Player closes browser or types "quit"
│
▼
Host's Eyre detects connection drop
│
▼
Host's %mud-world on-leave:
1. Find session
2. Resolve combat (if any)
3. Remove from room
4. Emit [%online-leave] to others
│
▼
5. Build world-record with everything earned this visit:
- XP earned in this world (cumulative)
- Flags set ("destroyed-shadow-altar")
- Items gained/lost
- Quests completed
- Time played
- Reputation changes
│
▼
6. Sign the world-record (host's cryptographic signature)
│
▼
7. Poke player's ship via Ames:
mark: %mud-portal-exit
{world: ~host, reason: %quit, world-record: <signed>}
│
▼
8. Host RETAINS world-record in citizen-records map
(Player can re-request if the Ames poke failed)
│
▼
Player's %mud-character on-poke:
1. Receive %mud-portal-exit
2. Verify signature
3. Apply world-record to local state:
- Update world-records map
- Store new items from item-snapshots
- Update corpse-locations if died
4. Character is safe on player's own ship
Death Flow (Remote)
Player HP reaches 0 on host's world
│
▼
Host's %mud-world:
1. End combat
2. Create corpse on HOST (in the room where player died):
- %none items + gold → lootable
- %protected items → owner only
- %soulbound items → excluded from corpse
3. Emit [%you-died ~] to player's subscription
4. 3-second pause (death screen on client)
│
▼
5. Host pokes player's ship via Ames:
mark: %mud-portal-exit
{
world: ~host,
reason: %death,
world-record: <updated, signed>,
soulbound-items: [list of item-snapshots],
corpse-location: {world: ~host, room: 1012}
}
│
▼
6. Host removes player from session/room
Host retains:
- Corpse (with %none and %protected items)
- World-record (with updated death count, corpse location)
│
▼
Player's %mud-character on-poke:
1. Receive death event
2. Store soulbound items locally
3. Update corpse-locations: [{world: ~host, room: 1012}]
4. Update world-record
5. Respawn player in HOME WORLD:
- Place in home world spawn room
- Full HP/mana/moves
- Soulbound items in inventory
6. If player's browser is still open:
- Host emits [%you-died ~] BEFORE dropping the subscription
- Client receives death event, shows death screen overlay
- 3-second pause: "You have fallen... returning home..."
- Host drops the subscription (SSE ends)
- Client detects SSE close, checks: was last event %you-died?
Yes → redirect to playerShipUrl (stored in localStorage from login)
No → show "Disconnected. Reconnect?" prompt
- Browser navigates to https://player-ship.urbit.org/mud
- Player's own %mud-world serves the glob, player is home
- Player's %mud-character has already respawned them in home room
│
▼
Player is home. Corpse is on ~host's world.
To recover: portal back to ~host, waypoint, walk to corpse.
Reconnect After Crash
Player's ship was offline when host sent %mud-portal-exit
│
▼
Host's Ames poke fails (nack or timeout)
│
▼
Host retains world-record in citizen-records. No data lost.
│
▼
Later, player's ship comes back online.
Player enters ANY world (or their own):
│
▼
Player's %mud-character checks: do I have pending record requests?
(Knows which worlds it visited but hasn't received records from)
│
▼
Pokes ~host's %mud-world via Ames:
mark: %mud-record-request
{citizen: ~player, world: ~host}
│
▼
Host's %mud-world:
1. Look up citizen-records for ~player
2. Found → send world-record via Ames
3. Not found → respond with "no record" (expired or never visited)
│
▼
Player's %mud-character applies the record.
State is consistent again.
Case 3: Guest Playing on Host
No Urbit ship. Browser-only. Everything lives on the host.
Network Diagram
┌──────────────────┐ ┌──────────────────────────┐
│ Guest's Device │ │ Host's Ship │
│ (any browser) │ │ (~host) │
│ │ │ │
│ ┌──────────┐ │ HTTPS │ ┌───────────┐ │
│ │ Browser │───────────────────────▶│ Eyre │ │
│ │ (Web UI) │◀──────────────────────│ (HTTP/SSE)│ │
│ └──────────┘ │ │ └─────┬─────┘ │
│ │ │ │ │
│ No Urbit. │ │ ┌─────▼──────┐ │
│ No ship. │ │ │ %mud-world │ │
│ No Ames. │ │ │ │ │
│ Just HTTP. │ │ │ • Rooms │ │
│ │ │ │ • Mobs │ │
│ │ │ │ • NPCs │ │
│ │ │ │ • Guest │ │
│ │ │ │ character│ │
│ │ │ │ • Session │ │
│ │ │ │ • Combat │ │
│ │ │ └────────────┘ │
│ │ │ │
│ Cookie: token │ │ Guest character data │
│ (only auth) │ │ lives entirely here │
└──────────────────┘ └──────────────────────────┘
No Ames. No player ship. No %mud-character agent.
All state on host. Cookie is the only identity.
Login Flow
1. Guest opens browser → https://host-ship.urbit.org/mud
│
▼
2. Host's Eyre serves glob (same web client as citizens get)
│
▼
3. Web client detects: no Urbit auth available
Shows login screen with two options:
[Create Guest Character] [Login with Urbit]
│
Guest clicks [Create Guest Character]
│
▼
4. Character creation form:
- Name: [________]
- Race: [dropdown]
- Class: [dropdown]
- Stat allocation: 12 free points
│
▼
5. Browser: POST /mud/api/guest/create
Body: {"name": "Wanderer", "race": "human", "class": "warrior",
"stats": {"str": 16, "int": 10, ...}}
│
▼
6. Host's %mud-world (via Eyre HTTP handler):
a. Validate name (unique among active guests, no banned words)
b. Validate race/class (valid enum values)
c. Validate stats (sum = base + race + class + 12, each ≤ 20)
d. Generate guest token (@uv, cryptographically random)
e. Generate session-id (@uv)
f. Create character with starting stats, gear, position
g. Place in spawn-room (or tutorial room)
h. Store in sessions map and guest-tokens map
│
▼
7. Response: 200 OK
Body: {"ok": true, "token": "0v1a2b...", "session": "0v4d5e..."}
Set-Cookie: mud-guest=0v1a2b...; Path=/mud; HttpOnly; Secure; SameSite=Strict
│
▼
8. Web client stores session-id in memory
Opens Eyre channel: PUT /~/channel/mud-{ts}
Subscribes to /game/{session-id}
│
NOTE: Guests use Eyre channels exactly like citizens do.
The channel is authenticated by the cookie, not by @p.
The %mud-world agent checks the cookie against guest-tokens
to validate the subscription.
│
▼
9. %mud-world emits initial updates:
- [%motd ...]
- [%room-enter ...] (spawn room or tutorial)
- [%vitals ...]
- [%system-message "Welcome, Wanderer. Type 'help' to get started."]
│
▼
10. Guest is in the game.
Gameplay Flow
Identical to Case 1 from the browser's perspective:
Browser → PUT /~/channel (poke with %mud-command)
→ Host Eyre → %mud-world on-poke
→ Process command
→ Emit facts to /game/{session-id}
→ Eyre SSE → Browser
→ SolidJS updates
The web client code is THE SAME for guests and citizens.
The only difference:
- Guest auth: cookie-based (mud-guest cookie)
- Citizen auth: Eyre session (+code login)
- Guest restrictions enforced server-side:
• Level cap checked on XP gain
• Cross-world commands rejected
• Some channels gated (gossip after level 5)
• Clan commands rejected
Gameplay Restrictions (Server-Side Enforcement)
Guest types "gossip hello everyone"
│
▼
%mud-world on-poke [%gossip "hello everyone"]
│
▼
Check: is this a guest session?
Yes → check guest level
Level < 5? → emit [%system-message "You must reach level 5 to use gossip."]
Level ≥ 5? → allow, process normally
│
▼
Guest types "waypoint 3" (trying to use waypoint, which is fine)
→ Allowed. Waypoints work for guests within the host world.
│
▼
Guest somehow triggers cross-world travel:
→ Rejected. [%system-message "Only those with a true name may travel between worlds."]
│
▼
Guest gains enough XP to exceed guest-level-cap:
→ XP banked but level NOT increased
→ [%system-message "You've reached the limit of what a wanderer can achieve..."]
Disconnect Flow
Guest closes browser or types "quit"
│
▼
Host's Eyre detects connection drop
│
▼
Host's %mud-world on-leave:
1. Find session by subscription wire
2. Resolve combat (if any)
3. Remove from room
4. Emit [%online-leave] to others
│
▼
5. CHARACTER IS NOT DELETED.
Session state preserved:
- Character (stats, level, inventory, equipment)
- Position (which room they were in)
- Quest progress
- Mail
- Map exploration cache
Token remains valid in guest-tokens map.
│
▼
6. Guest can return later with same cookie.
Resume Flow
Guest returns (same browser, cookie still set)
│
▼
1. Browser: POST /mud/api/guest/resume
Body: {"token": "0v1a2b..."} (from cookie)
│
▼
2. Host's %mud-world:
a. Look up token in guest-tokens map
b. Found → restore session
c. Not found → "Character not found. Create a new one?"
│
▼
3. Response: 200 OK
Body: {"ok": true, "session": "0v4d5e...",
"character": {name, race, class, level, ...}}
│
▼
4. Web client opens subscription to /game/{session-id}
│
▼
5. %mud-world emits:
- [%system-message "Welcome back, Wanderer."]
- [%room-enter ...] (room where they left off)
- [%vitals ...]
- [%mail-notification count]
│
▼
6. Guest is back where they were.
Death Flow (Guest)
Guest HP reaches 0
│
▼
Host's %mud-world:
1. End combat
2. Create corpse (same rules as citizen):
- %none items + gold → lootable
- %protected items → owner only
- %soulbound items → go directly to guest's inventory at spawn
3. Emit [%you-died ~]
│
▼
4. Guest respawns at world's spawn-room (NOT "home" — they have no home):
- Full HP/mana/moves
- Soulbound items in inventory
- Guest does NOT leave the world (no portal home)
5. Emit [%room-enter ...] for spawn room
6. Update session with corpse location
│
▼
7. [%system-message "The world blurs... you awaken at the crossroads."]
[%system-message "Your remains lie in The Shadow Altar Chamber.
Type 'corpses' to see where your belongings are."]
│
▼
Guest is at spawn. Corpse is in dungeon. Same world. Walk back.
Guest Purge
Guest hasn't connected in N days (configurable, default 30)
│
▼
On area reset tick, %mud-world checks guest sessions:
For each guest where (now - last-input) > purge-threshold:
1. If guest has corpses: items are lost (this is the cost
of not having a ship — no persistent identity, no safety net)
2. Remove session from sessions map
3. Remove token from guest-tokens map
4. Reclaim character name
5. Log: "Guest 'Wanderer' purged after 30 days inactive"
│
▼
If that guest returns with the old cookie:
POST /mud/api/guest/resume → 404 "Character not found"
Offer to create a new character.
Case 4: Guest Upgrades to Citizen
A guest who was playing without an Urbit gets a ship and wants to claim their character.
Flow
1. Guest has a level 28 character on ~host's world
Playing via browser with cookie auth
2. Guest acquires an Urbit ship (~new-citizen)
Installs %mud desk: |install ~sneagan-ship %mud
%mud-character agent starts on their ship
3. Guest logs into ~host's world as usual (cookie)
Plays normally
4. Guest clicks [Claim Character with Urbit] in settings/profile
Client prompts: "Enter your ship URL"
Player enters: https://new-citizen.urbit.org
5. Client authenticates with player's ship:
POST https://new-citizen.urbit.org/~/login (player's +code)
6. Client pokes player's ship:
PUT https://new-citizen.urbit.org/~/channel/mud-claim-{ts}
Action: poke %mud-character with [%claim-guest host=~host token=0v1a2b...]
7. Player's %mud-character pokes host's %mud-world via Ames:
mark: %mud-guest-claim
{citizen: ~new-citizen, guest-token: 0v1a2b...}
8. Host's %mud-world:
a. Look up guest-token → find guest session
b. Verify guest character exists and is not already claimed
c. Build character-snapshot from guest data
d. Build world-record from guest's accumulated play
e. Convert session from %guest to %citizen:
- player-type changes to [%citizen ship=~new-citizen]
- Remove from guest-tokens map
- Add to citizen-index map
f. Send character data + world-record to citizen's ship via Ames:
mark: %mud-guest-claim-response
{character: <full character>, world-record: <signed>}
9. Player's %mud-character:
a. Receive character data
b. Store as local character (now source of truth on player's ship)
c. Store world-record
d. Create home world spawn room
10. Browser session seamlessly transitions:
- Same session-id, same subscription
- Restrictions lift: level cap removed, cross-world unlocked, all channels open
- [%system-message "You have claimed your true name: ~new-citizen.
Your journey is now your own. Welcome home."]
- Banked XP (if any over level cap) immediately applies → level up
11. Player can now:
- Travel to other worlds via portals
- Visit their home world (portal home)
- Keep playing on ~host's world as a citizen
- Their guest cookie is invalidated (no more guest access)
What Transfers
| Data | From Host → Citizen Ship |
|---|---|
| Character (stats, level, XP, skills, proficiencies) | Full copy |
| Inventory (all item instances) | Converted to item-snapshots with origin-world=~host |
| Equipment | Same — item-snapshots |
| Quest progress | Converted to world-record flags |
| Stays on host (world-specific) | |
| Map exploration cache | Stays on host (world-specific) |
| Corpse locations | Transferred to character.corpse-locations |
| Gold, QP | Full copy |
| Discovered waypoints | Converted to world-record |
What Doesn’t Transfer
- Guest token (invalidated)
- Session history / chat logs
- Board posts (stay on the board, author name persists)
Required Marks
:: Guest claiming an @p identity
+$ mud-guest-claim
$: citizen=@p
guest-token=@uv
==
::
:: Host responding with character data
+$ mud-guest-claim-response
$: =character
=world-record
items=(list item-snapshot) :: all items with origin-world set
==
Comparison Matrix
| Aspect | Case 1: Own Host | Case 2: Remote Host | Case 3: Guest |
|---|---|---|---|
| Network | Local only | Ames + Host Eyre | Host Eyre only |
| Browser connects to | Own Eyre | Host’s Eyre | Host’s Eyre |
| Auth method | Own +code | Own +code → Ames handshake | Cookie token |
| Character data lives on | Own ship (%mud-character) | Own ship (synced to host) | Host ship only |
| Game logic runs on | Own %mud-world | Host’s %mud-world | Host’s %mud-world |
| Commands flow | Browser → own Eyre → own agent | Browser → host Eyre → host agent | Browser → host Eyre → host agent |
| Updates flow | Own agent → own Eyre → browser | Host agent → host Eyre → browser | Host agent → host Eyre → browser |
| On death | Respawn at own spawn-room | Respawn at home (own ship) | Respawn at host spawn-room |
| On disconnect | Save to local %mud-character | Host sends world-record via Ames | Character preserved on host |
| Cross-world travel | Yes (portal to other ships) | Yes (already visiting) | No |
| Level cap | None | None | guest-level-cap (default 30) |
| Corpse recovery | Walk there (same world) | Portal back, waypoint, walk | Walk there (same world) |
| Data loss risk | None (own ship) | Low (dual storage of records) | Medium (host purge after inactivity) |
| Latency | Minimal (localhost) | Ames RTT + Eyre | Eyre only (HTTPS) |
Key Boundaries
What Runs Where
PLAYER'S SHIP (citizens only):
%mud-character — source of truth for character
%mud-world — only if hosting their own world
Home world rooms — stored in %mud-character or local %mud-world
World records — accumulated from all visited worlds
HOST'S SHIP:
%mud-world — source of truth for world state
Eyre — serves web client to ALL players (guests + citizens)
Guest characters — stored entirely in %mud-world state
Citizen sessions — temporary, created on entry, cleaned on exit
Citizen records — retained copy of world-record (backup)
Corpses — persistent, on the world where death happened
Boards/mail — stored in %mud-world state
BROWSER (all cases):
SolidJS app — served from host's glob
Map cache — localStorage (citizens), memory (guests)
Session state — session-id in memory, auth in cookie
No game logic — pure display + input
What Crosses the Wire
AMES (citizen ↔ host, ship-to-ship):
%mud-portal-enter citizen → host (character snapshot + records)
%mud-portal-response host → citizen (admitted/rejected + session-id)
%mud-portal-exit host → citizen (world-record on disconnect/death)
%mud-record-request citizen → host (request missed record)
%mud-guest-claim citizen → host (claim guest character)
%mud-guest-claim-resp host → citizen (character data + world-record)
EYRE (browser ↔ host, HTTP):
GET /mud browser → host (serve web client)
POST /mud/api/guest/* browser → host (guest auth)
PUT /~/channel/* browser → host (poke game commands)
GET /~/channel/* host → browser (SSE subscription updates)
EYRE (browser ↔ player's ship, HTTP — Cases 2 & 4 only):
POST /~/login browser → player (authenticate with own +code)
PUT /~/channel/* browser → player (poke %mud-character for portal/claim)
GET /mud/api/portal-status browser → player (poll for admission result)
The browser talks to TWO ships in Case 2: player's (auth) and host's (gameplay).
In Cases 1 and 3, only one ship is involved.