A lexicon for game activity sharing

I’d like to hear everyone’s thoughts on a lexicon that games can use to broadcast the current activity of the player. Recently, I implemented music activity sharing for my project, https://colibri.social:

I think that, similar to the music activity records that apps like teal.fm and rocksky.app provide, it could be cool to have a lexicon that games can use for similar things! Apps like Colibri could then use them in a way similar to Discord’s Rich Presence system.

This is not meant to fully go into the detail of what such a lexicon should contain, though i think it should at least be the name, a sub-title, maybe a current state, an image, an optionally a link to something.

In any case, what are you thoughts? Did you maybe think about something like this already, or would you be interested in making this a thing? I’d be happy to hear from you :smiley:

4 Likes

I like this idea! I can immediately see a mini-app that crossposts your Steam activity to your atproto account…

@trezy.codes , do any of your games.gamesgamesgamesgames lexicons cover activity like this?

I’m still meaning to write up my proposal for a “playthrough” lexicon, which could hold this information, but I think it’s useful to split them — perhaps this “activity” record would point to the playthrough its recording to (if being recorded).

@lou.gg do you havee a feel for what data you’d want it to hold? (Given you have an active use-case!)

  • The games being played? (a g.gggg.game, I reckon)
  • When the play started?
  • When the play ended (thereby marking it as no longer active)?
  • An updated timestamp (if that is more than X minutes old, and there’s no end stamp, then it’s an abandoned record, not a continuing one)?
  • The platform/location? (eg. Steam, or Clun's Cannon — rpg.actor )

For reference, here’s the app.bsky.actor.status record (schema) written by stream.place:

{
  "$type": "app.bsky.actor.status",
  "embed": {
    "$type": "app.bsky.embed.external",
    "external": {
      "uri": "https://stream.place/byjp.me",
      "thumb": {
        "ref": {
          "$link": "bafkreietxkmxmuqecf3mlwgldghrpz2ifp2qcysxoqjndiopmq3kcwassa"
        },
        "size": 18115,
        "$type": "blob",
        "mimeType": "image/jpeg"
      },
      "title": "30 seconds",
      "description": "@byjp.me is 🔴LIVE on stream.place"
    }
  },
  "status": "app.bsky.actor.status#live",
  "createdAt": "2026-08-28T14:27:58Z",
  "durationMinutes": 10
}
2 Likes

This is something we’d happily display on the atmosphere.games profile pages and gamer cards. Would do well to showcase what users are currently playing and help game outreach.

Inclined to lean on the actor.status that @byjp.me points out because it’s already in use and could easily be expanded to highlight other things. All we need for (web)games is just the link, and that could serve to lobbies, etc.

Is there any reason we should reinvent this wheel?

3 Likes

@byjp.me I’d probably include an image for the game, a title, a sub-title that explains what the player is doing, then a creation date (createdAt) so we can derive playtime and an expiresAt so we can infer when a record goes stale. The platform should keep extending the expiresAt record if the player is still active, and if they go offline in a way the game can detect (no force-quit, graceful exit) it should delete the record entirely. The platform could be a nice thing to add, I like that.

Have this poorly-made ASCII art of what I think it might look like in Colibri, and how I’d use all that info:

|-----------------------------------------|
| PLAYING ON ATMOSPHERE.GAMES             |
| |---------|                             |
| |         |  Awesome RPG Simulator      |
| |   🖼️    |  Raiding a dungeon          |
| |         |  🎮 1h 14m 20s              |
| |---------|                             |
|-----------------------------------------|

The top is the platform that is supplied, then you’ve got the image, “Awesome RPG Simulator“ is the title, the sub-title is “Raiding a dungeon“, and the playtime is inferred from the creation date.

@rpg.actor I agree with the actor.status idea in general, but I think reusing the record might lead to fights between apps that both try to access this. Imagine, for example, a user streaming their game. I’d argue that we should be able to tell both that they are live and that they are playing a game!

2 Likes

(As a reply to @rpg.actor :slight_smile: )

The structure is useful, but I don’t think we can re-use it verbatim, as app.bsky.actor.status has its rkey set as literal:self — ie. you can only have exactly one record. And I think gaming is exactly the case where you might both be playing a game and streaming it on stream.place.

There are a few useful bits missing too’; if I try to put my latest Steam play into the .actor.status structure:

{
  "$type": "app.bsky.actor.status",
  "embed": {
    "$type": "app.bsky.embed.external",
    "external": {
      "uri": "https://cartridge.dev/game/valheim", # This has to be a URI, but I think a pointer to the g.gggg.game at-uri is useful
      "thumb": {
        "ref": {
          "$link": "bafk…"
        },
        "size": …,
        "$type": "blob",
        "mimeType": "image/jpeg"
      },
      "title": "Valheim",
      "description": "@byjp.me playing Valheim on Steam"
    }
  },
  "status": "app.bsky.actor.status#live",
  "createdAt": "2026-08-28T14:43:41Z",
  "durationMinutes": 10 # Equivalent to the updatedTime that I suggested
  
  # No "endedAt" timestamp, so is inherently transient, like app.bsky.actor.status, rather than teal.fm/rocksky.app streams (which result in a history of your plays) — though g.gggg.actor.stats can cover that.
}

We could copy this lexicon over to g.gggg.actor.status, for example, but I’d make some tweaks if we did.

2 Likes

Looks like we both replied at the same time! :smiley:

Okay, so a very rough early draft might be, for the transient version, this with an rkey of self:

{
  "$type": "games.gamesgamesgamesgames.actor.status",

  # New: The game being played (at-uri)
  "game": "at://did:web:gamesgamesgamesgames.games/games.gamesgamesgamesgames.game/3mghqukbd4c2d",
  # New: the platform being used (steam, web, gog, etc)
  "platform": "steam",
  # New: Where to join in or watch (free text; invite code, url, uri; game-dependent. Suggest URI)
  "lobby": "ABC123"

  "embed": {
    "$type": "app.bsky.embed.external",
    "external": {
      "uri": "https://cartridge.dev/game/valheim",
      "colorRGB": { "r": 173, "g": 28, "b": 2},
      "thumb": {
        "ref": {
          "$link": "bafk…"
        },
        "size": …,
        "$type": "blob",
        "mimeType": "image/jpeg"
      },
      "title": "Valheim",
      "description": "Dying on the plains"
    }
  },
  # Different knownValues:
  "status": "games.gamesgamesgamesgames.actor.status#live",
  "createdAt": "2026-08-28T14:43:41Z",
  "durationMinutes": 10
}

I guess, if conforming to the bsky status, we’d use our updates to bump the duration up to (now - createdAt) + 5 mins every update.

It’s very similar!

3 Likes

I think I’d still include the platform (like Steam, Epic Games, GOG, …) in here.

1 Like

Good plan; I’ve added it above :slight_smile:

3 Likes

Might be worthwhile adding a session (lobby) reference field?

There’s a different outlink needs for wanting to join their session, vs visiting the game page.

3 Likes

Great shout; which coud be a stream URL, or an invite to join in-game (or perhaps something else!) — if I’m understanding correctly?

I’ll add above!

2 Likes

It’s likely to be highly variable as different games / platforms have their own session-join methodology and we don’t want to dictate that just for a status lexicon.

Might be a url. Might be a uri. Might be a room code. Who knows?!

At this stage, feels better to stay loose and let devs decide what works for them.

3 Likes

There’s not anything in the g5 lexicons currently, but rich presence is absolutely something I wanted to add so this fits in nicely. A g5.actor.status lexicon makes a lot of sense to me.

I’m inclined to agree with @rpg.actor about the lobby field. Let’s start with basic rich presence, then we can add things like click-to-join later.

I’d be happy to prototype this in Trezy’s Pets, too. It’d be a real small lift to add these records.

One thing I didn’t see mentioned: what should cleanup look like? With app.bsky.actor.status, there can only ever be one record, so cleanup isn’t necessary because there will only ever be 1 record. With game statuses, should we expect a game to clean up old records? Does it just update the same record forever? Does it create 1 record per session and keep it updated, or does it create a new record every 10 minutes (or whatever the duration is)?

My suggestion would be:

  • Create a record when the player starts a game session
    • Include identical createdAt and updatedAt fields
    • Set the duration (e.g. 10 minutes)
  • Every so often (i.e. before the duration expires, maybe recommend 5 minutes), update the updatedAt field

That gives a couple things:

  • Display limit is updatedAt + duration, not createdAt + duration
  • Limits records in the PDS to sessions per game, not 1 record every few minutes of a session
  • Allows us to build a history for the player
    • Steam activity
    • User has played game for X hours

I think it also makes sense to at an endedAt field that only gets populated when the game session ends. If it never gets set (like if the game crashes, players internet cuts out, game dev doesn’t care, etc) we always have createdAt - (updatedAt + duration) for session length. If it does get set, we can derive session length from createdAt - endedAt.

2 Likes

The cleanup question kinda makes .self the reasonable approach again.

We don’t need multiple records for this, and can’t guarantee apps will properly clean up when sessions are done (nevermind interrupted sessions preventing it). In any use case, we’re really only needing one status to display the most recent anyway. The design proposed for colibri only has one in showcase. Most traditional systems overwrite the existing status with whatever is the latest, even when multiple games open (see Steam). Discord likewise overwrites the current activity with the game, or the stream (whatever latest), so .self still feels right.

Kinda reconsidering the bsky.actor.status use, and to find an individual workaround (some record extension?) for the edge-case of declaring both “I am playing X” and “I am streaming X” as there’s very little value in overlapping .status records for multiple simultaneous games.

We can also verify if they are streaming independent from the .status by checking the stream.place records, right?

1 Like

Thanks for weighing in @trezy.codes !

I was wondering how you’d see this overlapping with g.gggg.actor.stats (slightly unfortunate similarity there, I’ve just realised!) — given that already tracks play time for a game.

I’m leaning towards using a solitary self record, particularly for @lou.gg‘s use case (some chrome around a profile saying what game you’re playing, like Steam or Discord) — is it ever useful to know that I played Time Flies on Monday, Thursday and Saturday, rather than I played it for 4 hours?

(Also, let’s not out folks who are playing a cheeky round of Slay the Spire 2 in between meetings at work eh? :winking_face_with_tongue:)

I really like the idea of updatedAt + duration as the catch for “I forgot to update and close the session off” (it’s a little easier for everyone than the logic I was suggesting, of incrementing duration from createdAt to match your session) — even if we’re only keeping one self record per account.

1 Like

I think the current design around app.bsky.actor.status us the chrome on Bluesky saying “Live”, and that doesn’t quite fit with just playing a game (but not streaming it) — I think it’s worth keeping them distinct, even if their semantics are super similar.

1 Like

Yes, for sure. I think it’d need to be a URL, though, otherwise different services would need to implement multiple ways of immediately joining a game. The services should provide a URL, then handle the rest themselves once they’re in an environment the game dev controls.

+1 on this, feels like it’s own thing.

I also really like the suggestion by @trezy.codes about how the records should be structured! History building is neat, expiresAt is what I’d argue for as well.

On the whole .self discourse: I’d actually like to support multiple things at once. Think about, for example, an AFK game, or an overlay-style game like bongo cat on Steam. You’d still want your presence for this. Technically, you could be playing multiple games at once, and I’d be happy to display multiple activities at the same time in Colibri as well. Especially if we’re going to use this for playtime tracking, we should make sure to keep them separate.

1 Like

Thinking more like that “Live” ring gets expanded to include more use types.

No reason why bsky couldn’t link out to games with viable sessions, to join.

Could differentiate the #live from a #game in the status field very easily.

Would be a real benefit to overall exposure and reach, attractive for devs.

2 Likes

Good call, actually… that may make sense.

If they’re willing to, that’d be awesome!

Though I think that leans on there being an action of some kind associated. For the streams it’s “watch”, for games it doesn’t always make sense.

How would you feel about both?

A g.gggg.actor.status for a game you’re playing, and also request that Bluesky extend app.bsky.actor.status for “I’m playing a game and you should join me, here’s a URL to do that”.

1 Like

At this stage, our priorities are keeping things as simple as possible. Every developer we have spoken with has felt overwhelmed learning the conventions and there’s very little advantage to the labour (at this stage).

Whatever the course is, it should be as easy as possible for maximum benefit.

If we can make a great case for bsky to adopt some #games recognition in the existing .status or a new lexicon just for it, then it would genuinely improve the visibility for titles leveraging atproto and be a tremendous win in terms of convincing other game developers to try it out.

Currently see good reasons why bsky would want that, so interests are aligned.

2 Likes