1 chat presence and social
forgejo-actions edited this page 2026-07-06 16:45:10 +00:00

Chat, Presence, And Social Runtime

Chat and social runtime currently combines active in-memory delivery with reserved persistence schemas for longer-lived social features. Treat active session delivery as the online authority and the SQL social tables as the durable contract being prepared around it.

Active Chat Channels

Runtime chat uses ChatChannel:

  • Zone: delivered to active sessions in the speaker's current zone.
  • Party: delivered to active party members, even across zones.
  • Whisper: delivered to the speaker and one resolved online recipient.
  • System: delivered by server/admin paths only.

Messages carry speaker entity id, speaker name, channel, body, optional target, optional emote reference, and sent_at. Emotes are accepted only when the atlas/index pair is valid.

Runtime normalizes chat text by collapsing whitespace and keeping the first 200 characters. Target text is normalized to the first whitespace-separated token and capped at 64 characters.

Players cannot send system chat directly. If a player attempts the system channel, the server sends a private system notice explaining that system messages are admin-only.

Target Resolution

The active session directory indexes chat candidates by character id, normalized character name, zone, and session token.

Whispers and party invites resolve a target in this order:

  1. Parse the target as a character UUID and look up that active character.
  2. Otherwise, match active character names case-insensitively.
  3. Reject missing targets with a private system notice.
  4. Reject ambiguous names with a private system notice that asks for the character id.

Delivery skips sessions that are pending zone transfer. Zone and party target sets are also filtered through chat privacy so ignored speakers do not deliver to recipients that block them.

Party Runtime

The party registry is in memory inside the game-server session registry. It owns party ids, membership, leader choice, invites, and reverse invite indexes.

Party commands are sent through the party chat channel:

  • invite or inv <name>: invite an online target.
  • accept or join [inviter]: accept a pending invite, optionally from a named inviter.
  • leave: leave the current party.
  • disband: disband the party when the speaker is the leader.

Party invites expire after 10 minutes. Party chat without a party returns a private system notice. Party membership is used by trigger authority and encounter logic through server-side party context; clients should not be trusted to supply party ids for authority decisions.

Presence

Active chat routing comes from the game server's active session directory. Online service presence is coordinated separately by the session lifecycle and control-plane Redis presence records described in Session Handoff And Transfer.

The 0020_account_social_preferences_and_chat_history.sql migration also creates social_presence with offline, online, away, and busy labels. That table is the durable social-presence shape, not the live routing source for current chat packets.

When debugging presence, check which layer is stale:

  • active session directory for live chat routing;
  • Redis presence for fleet/control-plane visibility;
  • SQL social_presence for longer-lived social state once that path is enabled.

Ignores, Mutes, And Moderation

The runtime privacy registry has two concepts:

  • per-recipient ignored speakers, used to block delivery from one speaker to one recipient;
  • muted speakers, used to stop a character from sending chat.

Mute and unmute are active admin actions. A muted speaker receives a private system notice and their chat is not broadcast. The ignore registry and recent-history registry are present but marked as reserved for the chat service rollout; production UI and durable ignore syncing should be verified before relying on them.

The SQL migration reserves durable social edges with relationships friend, ignored, muted, and blocked. Do not assume those rows automatically affect live chat delivery until the sync path is explicitly wired.

History And Replay

The active runtime records recent delivered messages per character in memory, capped to the recent-history limit. That is useful for runtime tests and replay-style service rollout work, but it is not a durable chat archive by itself.

The SQL chat_history_messages table is the durable history contract. It stores recipient account, speaker account/character, speaker name, channel, optional target account/name, body, sent/delivered timestamps, and transfer scope (session, zone_transfer, or reconnect). Current runtime delivery should be treated as online delivery first; durable history persistence is a separate responsibility.

System Chat

System chat is sent through admin/control-panel paths. It can target all active sessions or a specific zone. The game server reports the delivered session count, and the control panel exposes system chat send fields for operators.

The internal game-server route is POST /v1/internal/chat/system, protected by internal service authentication. Admin actions also expose system chat, mute, and unmute through the admin authorization/audit path.

Use system chat for operational notices, not for authored quest/dialogue content. Authored content should use the dialogue, quest, NPC service, or trigger systems so it remains replayable and validated as content.

Boundaries

Active today:

  • zone chat delivery;
  • whisper delivery to online, unambiguous targets;
  • party invite/accept/leave/disband and party chat;
  • admin system chat;
  • admin mute/unmute;
  • in-memory recent delivery records.

Reserved or partial:

  • durable friend/guild/social invite workflows;
  • durable ignored/muted/blocked edge synchronization into runtime privacy;
  • durable chat history persistence and replay;
  • SQL social presence as live routing authority.

When implementing new social features, wire the durable SQL contract, runtime registry, admin/moderation surface, and replay/audit behavior together. Leaving one layer disconnected creates a feature that appears present in schema but does not affect online play.