Vibecheck is a mobile turn-based RPG game that turns any casual encounters into your in-game ally.
Vibecheck is a Spotify, GPS-powered turn-based mobile RPG.
The concept is very simple: sign-in with Spotify, turn your geolocation on, create your character and let the app work its magic !
When your geolocation is activated, every 30 seconds Vibecheck starts sharing with other app users in a 30 meters radius the last song you listened to on Spotify. If another app user crossed your path, you instantly exchange your last Spotify song along with your in-game character.
This data exchange is triggered instantly and in a completely anonymous manner: no one knows who is who, who received what and where/at what time they crossed path. This ensures the privacy and safety of all app users.
There's an in-game consequence to crossing path with the same user multiple time: the more you cross this user, the more their character will become in your game - and vice versa !
Concerning the game aspect, Vibecheck is a turn-based role-playing game: during combat phases, each players (typically the user and the AI) decide what actions to perform during the round, which is then executed based on each characters' statistics. Those statistics influence how well a character performs in various aspects of combat, and include like attack, defense, strength, stamina, stealth, intelligence, speed, wisdom, reputation, speechcraft, and luck.
During a round, a character can perform an different type of moves: attack, defend and use an object. Those moves can or cannot be executed based on the character's equipped weapon, their position in the battle formation. Furthermore, the action's rate of success is defined by the statistics mentioned below : an attack will use the attack statistics along with a modifier like strength, intelligence, stealth... depending on the character's class. The outcomes of these basic moves are also influenced by factors like luck, the party's "vibe" (harmony), the character's remaining health point, the number of rounds in the battle and the character's stamina (a character with less stamina will perform more badly the more turns it takes ; or an attack will be more likely to either fail or succeed if the user has low health points).
For more potent and unique actions, characters can employ special moves. Special moves leverage the character's special stat, and, like basic moves, their efficacy is augmented by modifiers relevant to their class.
In addition to individual character attributes, the party's collective strength is significant. Parties have a leader and an inventory of items. They also accumulate Vibe (the harmony within a party) and Artefact points. Vibe impacts the likelihood of a successful vibe attack and the effectiveness of using items or skills on fellow party members (for instance, in party with a negative vibe stats, a healing action on a fellow party member might fail due to low harmony). Artefact points grant the party access to powerful, unchangeable buffs (if they have the artefact in their inventory and enough points to equip it) that can influence the outcome of battles. Artefacts cannot be changed during a battle, but can be switched anytime outside of battles.
Vibecheck was thought to be a way to promote the use of public transports: a lot of users either listen to something or play something on their phone in public transport. Vibecheck can insert itself perfectly there, as there's an in-game incentive in crossing path with many other users to get more powerful in-game characters.
Vibecheck is built on top of multiple services and apps :
Upon their first sign-in in the app, the user is invited to sign-in using their Spotify account. Once signed in, a new wallet is created for them via Cometh. We associate the user (identified with a random ID) and their wallet in Pocketbase, which we use as our self-hosted authentication service. Absolutely no identifiable information/sensitive information (Spotify username, email, Spotify ID, phone number...) is stored to guarantee and ensure anonymity, security and privacy. Only the user can update their data, and only the admin has a READ access to it.
The user's last listened song is instantly fetched from the Spotify web API and stored as the user's status in Pocketbase. Every 30 seconds, the webapp will check if the user listened to something new, and if so, update the status in Pocketbase.
The mobile app acts like a podometer of sorts ; if your geolocation is enabled and if you grant permission to the app, every 30 seconds, it will save your geocoordinates in a database until you disable your geolocation ;
Those geocoordinates are stored in Supabase, a PostgreSQL database. Every time a new geocoordinate is added to the database ("passersby" table), it instantly verifies if another person that uses the app crossed path with them (the 2 users are within 30 meters of each others in a 20 seconds time frame). This is possible thanks to a TRIGGER on the table that on every INSERT operation calls a function that performs this verification. Those geoqueries are possible thanks to the PostGIS extension.
The passersby table is scheduled to be emptied every minute via a CRON (the passersby data will be deleted if they were created more than 31 seconds agon), and the users are only referenced by their wallet address. In addition, only the admin is allowed to read and write this database to ensure maximum privacy.
If indeed users crossed path, a new encounter is created in the PostgreSQL database (encounters
table).
Whenever a new encounter is created in the PostgreSQL database, a webhook is setup to call the /vibechecks
endpoint of our REST API. This endpoint is in charge of creating an anonymous vibecheck in Tableland. This anonymous vibecheck contains the status (stored in Pocketbase) from the 2nd user. This message is encrypted via Lit Protocol, and can only be read by the first user in the webapp.
Under the hood, only 1 wallet is authorized to decrypt those messages: the reader wallet. The webapp (which is server-rendered), by verifying the active user session (from Pocketbase and Spotify), can determine if the current user is allowed to decrypt the message (if the wallet_public_address
field from the current Pocketbase session is equal to the receiver
field from the stored row in Tableland.) If so, the user can decrypt the vibecheck and see what song they came by.
When the user first signs up, their last 30 songs from Spotify are used to generate their character sheet using OpenAI.
A character sheet has the following immutable attributes: id (automatically generated, unique), a name, a bio, an avatar and a class. The following attributes are statistics, which can evolve: health, special, attack, defense, speed, strength, stamina, stealth, agility, intelligence, wisdom, reputation, speechcraft and luck. The character sheet is then stored in Tableland.
In addition to the character sheet, after signing in and creating their character sheet, the user will have created for them automatically their actual character, their party and their party inventory, which all have their tables in Tableland.
A character extends a character sheet, and has the following additional attributes: level and experience.
A party has a leader (the user's character) and 2 different statistic points: artefact points and vibe.
In addition to the leader, a party can have other characters associated to it. Those are party members, and are a reference to characters. Those characters are actually a local copy of the character sheet created by other users that the current user encountered.
A party also has an inventory, which is stored in its own table on Tableland.
To sum up, the game relies on the following tables:
Upon their first sign-up, the user gets their character_reference
, and a character
that uses that reference, along with a party and a party inventory ; whenever they cross path with someone, if they didn't cross path with that person before, they get issued a character that uses the character_reference
of that user, and this character is referenced in their party as a party_member
; if they already crossed pass with that user before, then the character
will see their stats boosted.
Depending on how the user reacted to the vibecheck, the vibe
of their party increase or decrease, which has an impact on how combats will unfold.
Ultimately, I came out with this stack and flow mostly on compromises.
I initially planned to build a single progressive web application but the encounter detection can't be build with the current state of PWA. The first version I envisioned would have been built using BLE (bluetooth low-energy) to exchange data between users instantly (like those COVID-19 tracker apps) ; however, it's not possible yet to build this with the web.
I then looked into React Native, but it seems this flow (instant permissionless data exchange via bluetooth) is not possible to implement there yet either. This meant writing a native app, which I don't have the skills for.
I compromised with using geolocation instead but faced another issue: background geolocation is not working on PWA either. So I decided to build with Expo/React Native.
I then faced another issue: some of the sponsors' tech I wanted to use (Push Protocol, XMTP, Cometh) was either not compatible with React Native, or I needed to build my app (with expo prebuild
) ... which was not working on my machine. After some investigations, I discovered that my Java version (21) is not supported by Gradle, which in short meant I could only use Expo Go to continue building my app - or had to deal with reinstalling my environment, which could be very time consuming.
With these problems in mind, I had 2 possible solutions :
I decided to go with option n°2 and started to rebuild everything during week 2. I still faced some difficulty with sponsors' tech. Initially, I wanted the user to see their vibecheck as a message in their wallet, but I couldn't quite figure out how/if I could have Cometh wallet work with XMTP and Push Chat, so I came up with a different implementation : encrypt the message content with Lit Protocol and store this encrypted message on Tableland.
I was a bit surprised at how difficult it was to use Lit Protocol in NodeJS, and I couldn't get the Cometh wallet to decrypt the message, so I had to instead have a dedicated reader externally owned wallet, which would, based on the current user session, decrypt the content.