Starship Horizons
Log In
Knowledge BaseClient Architecture

Client Architecture

Horizons has no client application. Every screen in the game — a bridge console, the Main Viewer, the Game Master station, the editor — is a web page served by the game server and drawn in a browser using WebGL. The server runs the simulation and holds all of the state; the browser draws and sends input.

The two connections

Every console makes two, and both must work:

  • HTTP on port 1864 delivers the page and its assets. Port 1866 is the TLS equivalent.
  • A WebSocket on port 1865 carries the live game. Port 1867 is the TLS equivalent.

A page that loads but never comes alive has the first and not the second. That is worth knowing before you debug anything else — see Hosting Over The Internet.

Screens

A screen is a registered page. Screens are declared in XML, one registration per station:

<screen name="Flight" icon="roles/badge-flight.webp" base="console.htm"
        url="flight" file="flight.htm" role="Flight"
        mission-summary="true" packets="" tags="role-primary,flight" />
AttributeMeans
nameThe display title. This is what the GM console lists.
urlThe address slug. Not the same as the name, which catches people out.
baseWhich base page wraps it.
fileThe screen content itself.
roleWhich vessel role this station binds as.
packetsServer packets this screen wants from the outset.
visibleWhether it appears on the station selection page. Invisible screens are still reachable at their address.
assign-vesselWhether opening it binds to a vessel. False for admin stations.

Screen registrations are contributed by modules and merged after the base game loads, so a module can add stations or replace existing ones. See Modules.

Base pages

A console page is a base page with the screen's content injected into it. There are four bases, and the choice matters:

  • console.htm — a player console. Chrome, alert bar, role indicators, and the waiting / offline / spawning states.
  • console-admin.htm — an admin station.
  • console-child.htm — a page hosted inside another, used by the editor for its document tabs.
  • dialog.htm — a small standalone panel.

The base page loads hydra.js (the whole client runtime, as a classic script exposing globals) and hydra-module.js (the ES module entry point, which brings in the widget controller and the three.js import map). The screen's own content is placed where the base says [Screen.Content].

The .msp extension

Pages are requested with a .msp suffix — flight.msp, gamemaster.msp. The server strips it, looks the screen up by its url slug, renders the base page around the screen file and substitutes tokens. A request without .msp gets a static file, not a rendered screen.

Server-side tokens

Bracketed tokens in a page are replaced by the server before it is sent:

TokenValue
[Screen.Title], [Screen.Name], [Screen.Url]This screen's identity
[Screen.Content]The screen file, injected into the base
[Screen.Roles], [Screen.MissionSummary]Screen configuration, as metadata
[Server.LocalVersion], [Server.Version]Build version
[Server.IP], [Server.Port]Where this server is
[Game.AvailableScreens], [Game.ActiveScreens]Station lists
[Vessel.Alert], [Vessel.Name], [Vessel.ID]The bound vessel

Two of these do more than they look:

[Server.LocalVersion] is the cache‑buster. Every script and stylesheet is requested as panel.css?[Server.LocalVersion]. It changes when the build version changes — which means editing a stylesheet does not invalidate it. Already‑running clients keep the old sheet until they are reloaded. Freshly requested .msp pages are always current.

[Vessel.Alert] selects a stylesheet. The page links alert[Vessel.Alert].css, so the alert condition physically swaps a stylesheet rather than toggling classes. That is how a whole console restyles itself at Red.

Console states

A player console is always in one of several states, each a panel in the base page that is shown or hidden: offline (no connection), waiting (connected, no vessel assigned), spawning, and online. The waiting state is the one players report as "my console does nothing" — it means no vessel, which is a Game Master matter, not a client one.

What the client holds

hydra.js maintains a set of globals that page code and widgets read directly:

  • game — the session: players, missions, variables, events, and the broadcast bus.
  • thisvessel — the vessel this console is bound to. Almost everything a widget renders comes from here.
  • workbench — the page itself, including workbench.Widgets, the widget controller.
  • socket — the live connection.

These are globals on purpose: widgets are loaded dynamically and independently, and this is the shared surface they agree on. See Widgets and Packets And Topics.

Layout and 3D

Consoles are laid out with flexbox and a set of panel classes (panel-area, panel-container, panel-header, panel-full) rather than absolute positioning, so a console adapts to whatever screen it is on.

The 3D views — viewport, main viewer, radar — are three.js scenes drawn into canvases, loaded through an import map so widgets can import "three" directly. Several of them can be on one page at once: a role console typically has a viewport, a 3D radar, and two smaller relative radars, each with its own render loop.

Performance, briefly

Because a console page can hold nearly a dozen independent animation loops, the useful measurement is milliseconds of JavaScript per frame, attributed to the loop that spent it — not frame rate. Frame rate is capped by the display's refresh and hides all available headroom, so a console can look perfectly smooth while one widget is using most of the budget. Radar widgets have historically been the surprise.

Last updated 5 September 2026