I think leaning on app.bsky.actor.status is the wrong call for this particular use case. It’s live alert, not a rich presence control. I think the clearest argument against it is the multi-app implication. If I’m live on Stream Place, that’s going to publish a status record and it’s going to give users a “Watch” button to click. If I then start a game with a livestreamer focus (let’s say Cult of the Lamb), that’s also going to want to own the status, and it may want to provide both a “Join” and a “Watch” option. How do those applications interact to decide what record wins?
I think it would be a different story if gaming and streaming weren’t so tightly coupled, but there are so many scenarios where we’d have applications fighting over control of the status record that I don’t think it’s worth it.
Besides, if we’re already extending the lexicon, is it much different than creating a new lexicon? I’ve poked Alex Benzer (bsky product team) so hopefully he can come weigh in. Personally, I think the best route would be to create a new lexicon specific to game rich presence. Bluesky would have to make a change to implement it either way.
I totally get the argument here about game dev integrations. I’ve been talking with a handful that share similar sentiments. Unfortunately, I don’t think anything about our architecture on protocol is going to solve that issue.
Rather, I think the solution is to build tools that make it easier for game devs to take advantage of what’s available, like in-engine SDKs and public AppViews. I could be wrong, but in my discussions with game devs, that’s been the most promising avenue of discussion.
I’m aligned with @trezy.codes here, as I’m thinking of potentially using the prospective g5.actor.status lexicon (and other status-shaped lexicons) as a component in a generalized real-time rich presence platform, something that would be difficult to accomplish cleanly if it were folded into app.bsky.actor.status. Extending the Bluesky status lexicon to describe game activity is up to them, and I can see the value in it, but having a separate G5 lexicon is more fitting for long-term internection.
Alright, after a read-through, I think this is on the right track! We should keep the prior art of Discord Rich Presence in mind - at the moment, we still don’t have full feature parity with their displays! Discord has four live-updating fields: details for the main activity, state for a secondary status, party for how many players are in the group/slots are free, and timestamps for match start and/or end. It also has a type field that can be either playing, watching, or listening if we wanna allow atradio/rocksky/plyr to use our lexicon too.
Game invite data is definitely out of scope at the moment since that is thoroughly Permissioned Information and spaces aren’t quite ready for showtime just yet.
I’d honestly be fine with keeping atradio/rocksky/plyr/teal in their own lexicons. They need different info and may want to relay other things, and we can ignore the type field, which makes it less complex for us.
Totally agree on the party field, though! Could be nice to have a third field for multiplayer experiences.
Okay, what do we think of this for a lexicon? (it’s in MLF format for readability).
Changes:
Removed the status field, replacing it with a playing object (present when you’re playing a game, absent when you’re watching a game) which currently only includes the party field @lemmaeof.gay described
This includes a dids field for declaring who else is playing with you (if they have an atproto account)
I’m leaving space in there for lobby or other things as we grow this.
Added details & state as @lemmaeof.gay suggested (maps nicely to Discord)
Moves to createdAt + staleAt timestamps, as—given we’re treating this as a disposable record—I’m not sure what value durationMinutes provided beyond this other than requiring time maths from every client
/// A live "now playing a game" status, one per account.
/// Consider this record abandoned if staleAt >> now.
@key("literal:self")
record status {
/// The game being played (link to a games.gamesgamesgamesgames.game record)
game!: AtUri,
/// The storefront/platform being used
platform: string constrained {
knownValues: [steam, web, gog, epic, itch, playstation, xbox, nintendo, ios, android],
},
/// The kind of session this is (eg. "Daily Challenge", "4-player game")
state: string constrained {
maxGraphemes: 128,
maxLength: 1280,
},
/// The task being handled right now in the game
/// eg. "Level 5-2", "Ranked - Diamond II", "Turn 14 of 20"
details: string constrained {
maxGraphemes: 128,
maxLength: 1280,
},
/// Details only relevant if you're playing.
/// It's presence implies you're playing, not watching.
playing: {
/// Who's playing in this session (only for 'playing' activity)
party: {
/// Count of players currently in the party
current!: integer constrained { minimum: 1 },
/// Who they are, including yourself, may be less than current count
/// eg. if some players don't have atproto accounts.
dids: Did[] constrained { minLength: 1 },
/// Capacity, if there's a hard limit
max: integer constrained { minimum: 1 },
},
// Space for lobby details & other playing-specific metadata
},
/// A card representing the game/session
embed: {}, // app.bsky.embed.external
/// When this session began
createdAt!: Datetime,
/// When this record should be considered abandoned.
/// Active sessions should update this regularly (eg. every 5 mins)
/// to a few minutes in the future.
staleAt!: Datetime,
}
// Storefront/platform the session is running on (open set)
token steam;
token web;
token gog;
token epic;
token itch;
token playstation;
token xbox;
token nintendo;
token ios;
token android;
Looks generally good to me! A few things to nitpick on:
if we’re using tokens for platform then the knownValues should all be full NSIDs+fragments instead of the raw string, if I’m reading the Lexicon spec correctly.
The playing.party.dids array feels like it could maybe have privacy concerns if it’s public, but implementations can (and should) give an option for the status records to not include it on creation/edit.
This is still missing an object for specific timestamps - marking how long a given match has gone on for or how much time it has left. Those are a pretty big part of rich presence, too!
Liking its simplicity. Thinking about how it’ll be professed by the games.
As it’s designed, this write will need to be modified for every build developers provide to each storefront, which is a bit annoying but not outside standard platform compliance.
Though, thinking about cases like itch and GOG where the game is most often completely disconnected from the platform (DRM free). We’re never really “playing on itch” so much.
Suppose as long as it’s not required, it doesn’t pose any hassle for an independent .exe made available directly from the developer either.
Since you mention the privacy aspect, another thought has come to mind.
Game activity is typically a platform-managed expression. If we never want to announce what games we’re playing, then we can disable all activity broadcast from Steam itself.
However, with these records being created from the games, the “covert player” must click a setting in each new title they give write access to. Not a huge deal, but a bit annoying.
Apart from encouraging devs to offer variable OAuth scopes, is there anything we can do?
This is just how the MLF format uses tokens; that renders to lexicon JSON as games.gamesgamesgamesgames.actor.status#steam and similar (Unless @trezy.codes thinks these tokens will be common enough that we could put them in a g5.defs lexicon or similar!)
I fully agree; I’ll add a note there that creators should be wary of privacy concerns.
Oh; and we can state that unless that account has a matching record, apps should consider it as a fake party member.
I’m not too familiar with this part of the gaming ecosystem; what would that look like? A match { startedAt, endedAt } object?
@rpg.actor yeah, I see what you mean; I’d be interested to hear people’s use-cases here — what is platform useful for, for you?
@rpg.actor your point on privacy is a great one too! We can strongly recommend that each platform treats this record as opt-in, and considers what “covert play” looks like for them.
Eg. A steam → this lexicon tool could offer an “Enable” button, and a declaration that “Offline” play isn’t visible to it so can’t be propagated.
This got me thinking — how would we correlate these records? Perhaps an optional playing.id record, which should be the ID of the game being played (with a semantic requirement that it should be the same for all players in a game together).
(Many games have a deterministic ID already, but this could also just be the hash of some deterministic shared state, like the name of the game and the sorted participants’ playerIDs).
If matchmaking is beyond the scope, then suppose platform is valuable for linking to store pages? Discord does this, and it’s a nice exposure feature that shows you where to buy games your friends are playing.
Though, those links are already in the game definition records?
Can’t imagine platforms supporting this natively, and building towards a middleware that players install just for activity expression is not likely going to be worth the effort.
Reality sits the feature inside the games themselves, so unless there’s some way for a user to tell their PDS “never write this kind of record!” (a genuinely worthwhile feature?) then it’ll stay up to developers to respect the user wishes.
Definitely expect users to demand it vocally, and skip titles that don’t handle it well. (Kinda like how folks ignore projects with poorly scoped OAuth). Maybe a social solve!
I think it’d have to be a social solve; a generic solution is rather complex (needing Spaces), cos declaring that you wish to considered offline is, in itself, a signal that you’re being covert right now. (Signal that you don’t want to publish—eg. during working hours—needs to be private to you and/or the publishing application.)
Match making could be in-scope, but I think best not to bite off too much right now!
I like the idea of matching to a store page, I actually don’t see a purchase link on the cartridge.dev pages — @trezy.codes there’s two things for you there I think:
Do you see g5 offering a “where to buy” link?
If you do, and you index it by platform, do you see a g.gggg-based platform tag definition?
While first thought had some middleware software (which nobody would install), the stuff you’ve shown us with airglow operations does make a case for some site that links Steam (etc) to your handle, then reads the public broadcast from the platforms (same way Discord currently does), then writes these records.
Would be a pretty worthwhile feature, and a decent add to atmosphere.games to coincide with its user profile features.
Still feel this is something best expressed by the games themselves, though.
@byjp.me:
…Unless @trezy.codes thinks these tokens will be common enough that we could put them in a g5.defs lexicon or similar!)
We actually have a lexicon for platforms already: games.gamesgamesgamesgames.platform (e.g. Playstation 5. If we make the platform field an open union then it can reference specific platforms with all their metadata from g5, plus platforms that aren’t already in g5 can be referenced as strings.
@rpg.actor:
Game activity is typically a platform-managed expression. If we never want to announce what games we’re playing, then we can disable all activity broadcast from Steam itself.
[…]
Apart from encouraging devs to offer variable OAuth scopes, is there anything we can do?
I think there are multiple stages of supporting covert players:
Social solve - Like you said, players that rely on the functionality will avoid games that don’t respect it.
Build it into the SDKs - Making sure it’s easy for devs to implement makes it much more likely that they’ll implement and respect it.
Build it into the platforms - Will Steam and GOG and Xbox use or respect these status records? Unlikely. But as we build new platforms (like atmosphere.games and the Cartridge desktop launcher), we can make the barrier even lower for devs to implement and leverage this. Further, we can enforce the setting at the platform level (e.g. games launched via Cartridge use the Cartridge AppView, which can check a player’s covert preferences and decide whether the record is allowed to be written).
@byjp.me: Do you see g5 offering a “where to buy” link?
It already does! There’s a Where to Buy section in the right sidebar on Cartridge which is derived from the g5 record.
@byjp.me: If you do, and you index it by platform, do you see a g.gggg-based platform tag definition?