Skip to content

Phone ​

The phone (Game design document §7.5, §5.5): the satire's main channel and how missions talk to the player. Contacts text and call, apps are screens of short text with buttons, the map app drops a waypoint, and the world's terminals, keypads and people open the same panel. It rests on the items and is written in the mission language.

The phone is an item; its entries are the missions' ​

  • The item: phone is a common item (packages/sim/src/items.ts), tagged phone. Every character starts with one in the inventory's last slot (so whatever is picked up goes in the first seven) when the room runs a game whose rules.phone is true, the default; free roam, with no missions, has none (T there takes out a phone with only the radio on it: Sound and the radio). It is never dropped nor given; a mission may take the tag phone and give it back.
  • The entries: its contacts, messages (with their replies), installed apps, call log and waypoint are entries of the phone, not items in a container. They are data the mission interpreter keeps per player (packages/missions/src/phone.ts, part of the runtime's state, so its determinism covers them) and sends to that player alone. A party's mission (Social) sends its messages, calls, contacts and apps to every member's phone; each member's own phone takes what they ask of it. An item in a slot is a thing a crate or a trunk can hold; a message is not, so the inventory stays a list of things and the phone a list of entries.
  • Without the item the entries stay but nothing opens: T shows nothing, a call is missed. What the world opens (a terminal's screen, a keypad, a conversation face to face) does not need the phone.
  • Whose: contacts, dialogues and apps are a mission's (its contacts, dialogues, apps). A conversation, an installed app and an open screen belong to the mission run that opened them and go when it ends; contacts, messages, the call log and the waypoint stay for the room (a reply to a message of a run that ended is not offered any more).

Using it ​

KeyDoes
Ttakes the phone out, or puts it away (an app's screen closes with it)
Escback a page (an app's screen closes); from home, puts it away
1–9press the page's numbered buttons, instead of selecting a slot, while the phone is out
mouseclicks the buttons and the map, never a shot; over the panel it does not aim; a click on the game still aims and fires
everything elsestill the game's: W/A/S/D, Space, Shift, E. The world does not pause; the car keeps rolling under the phone

The panel is part of the game's picture, drawn in the scene with Babylon's GUI (apps/client/src/gui/phone-blocks.ts builds it from the logic in ui/phone-view.ts; the client UI explains the layer), on the right above the minimap; a page taller than the room left scrolls inside it with the wheel. Home lists Messages (with the unread count), Contacts, Calls, Map, Camera, Jobs (in the city: below) and the installed apps. The messages, the contacts and the calls are listed newest first: the panel stands on its bottom edge, so what arrives goes on top and the button under the pointer stays put; a click is a press and a release on the same button, wherever the panel has it, so a message arriving mid-click never loses it. The buttons never take the keyboard: the keys stay the game's; an app form's fields take it while typed in (a click on the field; digits only; Enter presses the screen's first button with what the fields hold, Esc gives the keys back to the game). A conversation shows over any page. What the world opens, and a call ringing, take the phone out by themselves; when they end, a phone the world took out is put away again.

Messages ​

message { from, text, choices? } puts a message from one of the mission's contacts in the phone (a notice on screen too, the unread count on the Messages tile). Up to three replies, each { id, text, if?, do?, failText? }: picked, the reply's if is checked; when it holds the reply is sent (shown as "You: …") and its do runs, once; when it does not, failText answers under the message and nothing is sent. The older sendMessage { from, text } (a name, no contact) lands in the phone too, without replies. The Messages page marks them read; a message with an id (message { id, from, text }, sendMessage { id, from, text }) is heard by the mission's rules once read (message:<id>.read), or replied to.

Calls and conversations ​

A dialogue is nodes of short lines and choices, from its first node:

json
{
  "id": "onboarding_call",
  "nodes": [
    {
      "id": "terms",
      "line": "Do you accept the Terms, the Terms of the Terms, and the vibes?",
      "choices": [
        {
          "id": "accept",
          "text": "I accept.",
          "do": [{ "setFlag": "accepted" }],
          "next": "rating"
        },
        { "id": "read", "text": "Can I read them first?", "next": "pages" }
      ]
    },
    { "id": "pages", "line": "They're 900 pages and a podcast.", "next": "rating" },
    { "id": "rating", "line": "Your starting rating is 4.1. Nobody knows why." }
  ]
}
  • A node's speaker is one of the mission's contacts (the one the player talks with when absent). Its choices each check their if when picked (failText answers and the line stays), run their do, and go to next or end the conversation; a node without choices goes on to its next (the panel's "…"), or ends it ("Bye"). Every node must be reachable from the first.
  • Calls in: call { from, dialogue } rings the phone for 20 s: answer or decline. Without a phone, or with a conversation already on, the call is missed.
  • Calls out: a contact added with addContact { contact, dialogue } answers with that dialogue while the mission runs; a talk objective naming a contact makes that contact answer with its dialogue; anyone else does not answer.
  • Face to face: an npc actor of the contact role stands at a place; the interaction key says Talk within 1.5 m and runs its dialogue in the same panel (a talk objective naming the actor plays its own). Walking 4 m away or knocking them down ends it.
  • Events: dialogue:<id>.choice:<choice>, dialogue:<id>.ended (reached an end) and dialogue:<id>.declined (declined, rung out, hung up, walked away from). The talk objective is done once the dialogue has ended in the mission (or, with choice, once that choice was picked). The call log keeps answered, made, missed, declined and unanswered calls.
  • The help line: a mission's helpline (one of its contacts) is in the phone from the mission's start and always answers: a call that no dialogue of theirs answers plays one line, the hint of the objective under way (else what the tracker says), so a player who is stuck calls them ("Stuck? T phone, call Dana" on the tracker; see the Missions guide).

Apps ​

An app is screens of up to 400 characters, each with a short form (up to three fields: a code or a number) and up to four buttons { id, text, if?, do?, go?, failText? }. A button checks its if when pressed, with the form's fields (formIs { field, value }, the value { form }): when it holds, its do runs, rules hear app:<app>.<button>, and go moves to another screen; when it does not, failText (else "Nothing happens.") shows on the screen. The game only checks; "hacking" is choices, timers and items, never code. A button can be pressed again, so one that gives items needs a counter in its if.

RentaCar, the example (packages/missions/content/test-phone.json): the rental car stands locked; Unlock works within 10 m of a car tagged rental and hands over that car's key, an item tagged key:vehicle:rental_car, which is what opens it (E Unlock at its door; see the Vehicles guide):

json
{
  "id": "unlock",
  "text": "Unlock",
  "if": {
    "all": [{ "near": { "tag": "rental", "within": 10 } }, { "lt": [{ "counter": "keys" }, 1] }]
  },
  "do": [{ "give": { "item": "rental_key" } }, { "add": { "counter": "keys", "value": 1 } }],
  "go": "unlocked",
  "failText": "No RentaCar within 10 m. The app can't walk for you."
}

installApp puts one of the mission's apps on the phone (it goes when the mission ends), removeApp takes it off, hasApp tells whether it is there.

Jobs ​

In the city (a hub's room, never a run's) the phone has a Jobs app (GAME_DESIGN §13: finding play without leaving the street), the client's own: its data comes from the platform through the page (apps/client/src/renderer/jobs.ts asks GET /playlists, GET /missions?level= and GET /lobbies/mine when its page is shown and every 5 s while it stays; ui/jobs-view.ts makes the page's lists), never from the game server. Its page, top to bottom:

  • Invitations: the lobbies friends invited the player to (phone-job-invite-<lobby>);
  • Lobbies needing players: the public lobbies of the playlists still gathering with a place left, on any level (phone-job-lobby-<lobby>: its mission, level and players);
  • Missions: those of the level the player is on, then the other levels' multiplayer missions (Any's rotation), each with how it is played (phone-job-mission-<game>-<mission>);
  • Quick play: Co-op, Versus, Races, Anything (phone-job-quick-<playlist>).

Picking a lobby or an invitation opens the lobby's page (?lobby=); Quick play goes to the lobby it placed the player in, or straight into the run under way it seated them in; a solo mission opens its run at once; a multiplayer one its fullest public lobby gathering, or a new one. The panel stands on its bottom edge, so the lists that change are on top and what arrives goes at the top of its list (onTop: what was shown keeps its order), Quick play and Back at the bottom: an invitation or a lobby that comes moves nothing under the pointer; and on this page the digit keys count from the bottom (keysOf: 1 its last button, Back, 2 the one above it), so a key pressed as an entry arrives on top still picks what it showed; each list shows a few at most. A page whose platform does not answer (a server played on its own, the local development's) says that jobs need the platform (phone-jobs-state); a choice the platform refused says why (phone-jobs-error). window.__poc().phone.jobs lists what the page shows (null in a run's room).

Terminals and keypads ​

  • A terminal actor (kind: terminal, keypad, atm, vending; all work alike) is a post at a place. The interaction key says Use (or Hack, its prompt) within 1.2 m and opens one of the mission's apps on its first screen (or screen) in the phone's panel, from the world; no phone is needed. Walking 4 m away closes it. use names it; rules hear terminal:<id>.used.
  • A keypad door is a door actor with a code (3 to 8 digits): locked as the mission starts, the key says Enter code and opens a keypad; a wrong code says so, the right one unlocks the door and swings it open away from the player. Rules hear door:<id>.unlocked (as for a door unlocked by its key or an unlock), and the unlock objective is done.

The map and the waypoint ​

The Map page draws the level north up; a click drops the waypoint there, Clear removes it. A mission sets one with waypoint (a place) and clears it with clearWaypoint. The minimap shows it as a cyan diamond with an arrow on its edge pointing the way from the player; it clears itself once the player is within 8 m.

The camera ​

Every phone has a Camera (its tile on home, no mission installs it). Its page says how many photos it took and what the last one got; its shutter is its first button (1), and while the page is out the minimap draws its frame, a cyan cone along the character's aim. A photo is taken along the aim (move the mouse; over the panel it does not aim, so the aim holds while a button is clicked): the server frames it (@foi/ai's inFrame: within 60 m, about 23° either way of the aim, nothing of the level or the world in between but the subject) and the missions read what was in it, the actors and the game's places. A photograph objective is done by a photo of its subject taken from its zone; the camera says so ("Got it."), or that the subject was not in the frame, or that the photo was not taken from the spot.

On the wire and on the server ​

  • The server sends each player a phone text frame (@foi/protocol's PhoneMessage) when their phone changed; the client sends phone commands (open, close, press, reply, answer, decline, hang up, choose, call, read, waypoint, clear waypoint, photo), inputs the interpreter checks like any other: the phone held, the app installed, the button on the screen open, the reply still open. The interaction key at a contact, a terminal or a keypad is an interact event of the simulation the mission host turns into the same kind of input.
  • Snapshots carry the terminals (a box the client twins, so the prompt and the predicted character see it), a character's role (a contact, drawn cyan) and a keypad's lock (amber); PROTOCOL_VERSION 6.
  • apps/server/src/mission-host.ts spawns the people (a character of the contact role, standing) and the terminals, binds the keypad doors, queues the phone commands (16 a player between two steps, 8 taken a step) and sends the phones. Texts of contacts, messages and replies, dialogues and apps are moderated at publishing like the game's other texts (apps/api/src/moderation.ts).

Mini level and tests ​

  • test-phone (built from apps/level-editor/assets/kits/test-phone.recipe.json): an office with one door north of the spawn, open ground to Gary from Facilities (north-west), the Smart Kiosk (north-east) and the RentaCar bay (20 m west). Its game, Day One: Mo texts with two replies, "I'm human." brings GigLoop Onboarding's call whose choices set a flag, Gary texts the keypad's code and talks in person, the RentaCar app unlocks the car within 10 m, the kiosk sets a waypoint. E2E-140 to E2E-149 (tests/e2e/specs/levels/test-phone/).
  • test-mission-verbs: the camera's photo of a mural from its spot (E2E-374), a message read heard by a mission (E2E-371).
  • tests/e2e/specs/platform/continuous.spec.ts: the Jobs app lists a stranger's public lobby on another level, opens it, and the invitation sent from it reaches a friend's Jobs app on top (E2E-682).
  • Unit tests: packages/missions/src/runtime-phone.test.ts, validate-phone.test.ts, runtime-verbs.test.ts (the camera's notes, a message read); packages/sim/src/interactables.test.ts; packages/net-client/src/mirror-items.test.ts; apps/server/src/phone.test.ts; apps/client/src/ui/phone-view.test.ts, ui/jobs-view.test.ts, renderer/jobs.test.ts, apps/client/src/gui/blocks.test.ts (the Jobs page's buttons where they were when an entry arrives).

What comes next ​

The fake web (pages of marked-up text with links, the satire's best channel), conversations voiced by an AI ([LATER]), roles beyond contact (guards, shopkeepers), and terminals placed by the level pipeline rather than by a mission.