Hoppa till innehåll

Fallstudie

CarmaClouds: En plattformsoberoende programvaran för karaktärssynkronisering inom virtuella tävlingsmiljöer

Roll: Solo DeveloperVaraktighet: 2024-nuLive: MIT (open source)Status: Aktiv - används i pågående kampanjer
TypeScriptJavaScriptSupabaseChrome Ext. (MV3)Firefox Ext. (MV2)Discord.jsMeteor DDPESBuild

Skala

175,000+

Totalt antal rader kod

3

VTT-plattformar

Chrome + Firefox

Webbläsarmål

20+

Discord-kommandon

50+

Gränsfall i D&D 5e

Problemet

Spelare som skapar D&D 5e-karaktärer i DiceCloud V2 står inför ett besvärligt arbetsflöde: plattformen erbjuder omfattande karaktärshantering, men när det är dags att spela i Roll20, Foundry VTT eller Owlbear Rodeo måste varje egenskap, besvärjelse och förmåga matas in manuellt på nytt. Över 100 datapunkter, som skrivs in för hand, med inmatningsfel som ackumuleras under sessionerna.

Det fanns inget verktyg som kunde koppla samman DiceCloud med någon VTT-plattform. Ekosystemet saknade interoperabilitet.

Viktiga funktioner

RollCloud

Webbläsartillägget DiceCloud till Roll20 (Chrome + Firefox)

85-90 % färdig

Analyserar karaktärsdata från DiceCloud och fyller i karaktärsblad i Roll20 via Roll20:s DOM. Använder Manifest V3 (Chrome) och Manifest V2 (Firefox) som har kompilerats från delad källkod med hjälp av ESBuild.

OwlCloud

DiceCloud till Owlbear Rodeo - inbyggd tilläggsfunktion

95 % färdig

Översätter data från DiceCloud till Owlbear Rodeos egna tilläggs-API. Den mest fullständiga integrationen - Owlbears tilläggsmodell är väl dokumenterad och stabil.

FoundCloud

DiceCloud till Foundry VTT-modul

70 % klart

Foundry kräver inbäddade föremål istället för vanliga attribut, vilket gör översättningen mer komplicerad än i Roll20. Föremål för D&D 5e-klasser mappas via systemets föremålskatalog.

CoyoteCloud

Tvåvägssynkronisering med DiceCloud i Coyotes and Candles VTT

Aktiv

Den komponent som används mest i produktionsmiljöer. CoyoteCloud integrerar DiceClouds skrivåterföring med Coyotes och Candles VTT - karaktärsändringar som görs i VTT-verktyget överförs tillbaka till DiceCloud i realtid, och tvärtom. Används varje vecka i livekampanjer.

Pip2 Discord Bot

Karaktärsdata via Discord-slashkommandon

Fristående drift

Över 20 slash-kommandon för att hämta karaktärsdata, kasta tärningar med fullständig modifieringskontext och hantera kampanjstatus från Discord.

Tekniska höjdpunkter

DiceCloud använder Meteor DDP, inte REST

DiceClouds API bygger på Meteor DDP-protokollet - ett WebSocket-baserat pub/sub-system utan REST-gränssnitt. Jag har implementerat en anpassad DDP-klient med köhantering för förfrågningar för att förhindra överanvändning av API:et.

Plattformarnas datamodeller är i grunden oförenliga

Roll20 använder platta karaktärsattribut. Foundry kräver inbäddade objekt med ett specifikt schema. Owlbear har sin egen modell. Adaptermönstret - ett gemensamt parsningssteg som matar plattformsspecifika adaptrar - gjorde det relativt enkelt att lägga till nya plattformar efter de två första.

Regelsystem för D&D 5e med över 50 specialfall

Att kopiera generiska statistikvärden är enkelt. Det är den exakta matematiken - specialförmågor, vapenidentifiering, utökade kritiska träffintervall och beräkningar av trollformelsplatser för multiklasskaraktärer - som utgör själva produkten. Varje specialfall hanteras av en modulär mönsterjämförelsefunktion i regelmotorn.

API:erna för tillägg till Chrome och Firefox skiljer sig åt i grunden

Lösningen blev att skriva till en gemensam källkod och köra en ESBuild-pipeline som genererar båda utgångarna. Automatisk skapande av ZIP-filer vid kompilering förhindrar förvirring vid distributionen.

Konsolidering av monorepo

RollCloud, OwlCloud och FoundCloud var ursprungligen separata repositorier. Genom att slå samman dem till ett enda monorepo med gemensam kärnlogik undveks avvikelser och gjorde det möjligt att tillämpa ändringarna i DiceClouds parsning överallt samtidigt.

Skärmdumpar

Vad jag lärde mig

Ett synkroniseringsverktyg ägnar hela sin livstid åt att stämma av andras datamodeller, och nästan alla buggar som är värda att minnas har uppstått genom att man litat på ett antagande som någon av dem inte delade.

En position i en lista är inte en identitet

Tillägget fortsatte att synkronisera fel tecken. En kodväg hämtade tecken[0], det äldsta som någonsin lagts till; en annan, i samma popup-fönster, hämtade separat det sista elementet och skuggade det. Arrayen är ordnad efter infogningsordning och uppdateras på plats, så ingen av dem följde det ark du faktiskt hade öppet, och fliken fastnade på ett tecken oavsett vilket du tittade på. En spegelbild av samma bugg dök sedan upp i en andra adapter. Genom att lösa utifrån id:t i den öppna fliken, med fallback till det senast synkroniserade, åtgärdades båda, och lösaren finns nu på ett ställe så att de inte kan glida isär igen.

Delad lagring kräver skrivningar med begränsad räckvidd

När en karaktär skickades till Owlbear rensades allas data. Push-hanteraren raderade hela lagringsnyckeln vid lyckat utförande, och den nyckeln delas med flikarna Roll20, Foundry samt Coyotes and Candles. Den tar nu endast bort den karaktär som skickades. Allt som skriver till lagringsutrymmet - som även läses av andra funktioner - måste tänka i rader, inte i nycklar.

Identiteten i raden är inte identiteten i token

Molnsynkroniseringen började misslyckas med felmeddelandet ”ny rad bryter mot säkerhetspolicyn på radnivå”. Raden innehöll ett giltigt användar-ID, men det JWT som anlände till PostgREST visade sig hänvisa till ett annat ID, eller till ”anon”, vilket ledde till att en WITH CHECK-sats som jämförde auth.uid() med raden avvisade den. Uppgifterna var korrekta, men autentiseringsuppgifterna var felaktiga. Med RLS är den enda identiteten som räknas den som token bevisar, vilket gör det värt att kontrollera vad token pekar på snarare än vad nyttolasten anger.

Länkar