State lives on the server. The client is told about it through the WebSocket, and
what arrives is distributed inside the page over a broadcast bus. These are two
separate mechanisms and confusing them is the source of a lot of trouble.
Packets: what the server sends
A packet is a named message from the server —
MAP, VESSEL-WAYPOINTS, ALERT,
DAMAGE-TEAMS, ACTIVE-CONSOLES, and dozens more. Packets are
upper‑case and hyphenated by convention.
The server does not send everything to everyone. A console asks for
the packets it needs:
this.AcceptPacket("DAMAGE-TEAMS");
Two things follow:
- Accepting is refcounted. Several widgets can want the same packet.
The socket tracks owners and only sends a rejection to the server when the last one
releases it. This is why widgets must claim through
this.AcceptPacket rather than the global — see Widgets.
- Accepting delivers immediately. The server answers a new claim by
sending the current value straight away, rather than waiting for the next change. This
is convenient and it is also a race: a widget that mounts while the page is loading
something over REST can have the socket's answer arrive first and win.
A screen can also declare packets it always wants, in its screen registration, which
is how admin stations get message and debug traffic from the outset.
Topics: what the page distributes
When a packet arrives, the client updates its state and then broadcasts one or more
topics — lower‑case, hyphenated. Widgets subscribe to topics,
not to packets:
this.Subscribe("vessel-shields", (m) => this.Render());
The mapping is not one to one. One packet can raise several topics
— the MAP packet updates the current map and raises
map-info, and related packets raise location-current and
planetary-system-detail. And one topic can be raised by several packets, or
by page code with no packet involved at all.
This is exactly why releasing a topic subscription says nothing about whether a
packet is still needed, and why the two are tracked separately.
The topics you will actually use
There are over a hundred. These are the ones nearly every widget wants:
| Topic | Raised when |
ready | The page is up and the socket is connected. Claim packets here, not in the constructor, if you need the socket. |
reset | The session reset. Re‑render, and remember this is not startup. |
resize | The console changed size. |
vessel | The bound vessel's general state changed. |
vessel-id | This console has been given a vessel. |
vessel-initialized | The vessel is fully populated. Prefer this to vessel-id — it is a superset, and a widget that renders on vessel-id often finds half the data missing. |
vessel-components | The component set changed. |
vessel-shields, vessel-damage, vessel-alert | Combat state. |
contacts, contact-updated, contact-removed | The sensor picture. |
target-flight, target-tactical, target-science | The three separate target selections. |
map-info, location-current | Where the ship is. |
mission, game-start, session | Session lifecycle. |
Traps worth knowing in advance
Contact topics arrive in storms. In a busy mission
contact-updated can fire many times per second. A widget that re‑renders
its whole list on every one will dominate the frame. Coalesce, or render on a timer and
let the topic only mark you dirty.
The session state, not the mission state, is authoritative for
whether a mission is actually running. A mission can be loaded without being started,
and code that checks the wrong one acts too early.
Editor pages must guard against live packets. An editor holds one
specific record it loaded over REST. A packet describing whatever the server
currently has loaded is a different thing, and letting it land replaces the record
being edited. The map editor sets a flag for exactly this reason.
Sending state the other way
See Server Commands.