Wróć do doświadczenia
ENPL

Case study · produkt, design i engineering

Rolling Dices: od asystenta RPG do virtual tabletop w czasie rzeczywistym.

Od października 2022 do kwietnia 2024 prowadziłem produkt, badania, UX i architekturę — oraz współtworzyłem — platformę RPG czasu rzeczywistego. Trzech inżynierów, tylko jeden na pełny etat, zbudowało grywalny VTT, przetestowało go z ponad 100 osobami i zamknęło, gdy adopcja nie uzasadniała dalszej inwestycji.

8 min czytania

Okres
10.2022 — 04.2024
Rola
Founder & Lead Engineer
Badania
25+ sesji · 100+ użytkowników
Rezultat
Grywalne MVP · świadomie zamknięte
Build early access z marca 2024, uruchomiony z oryginalnego kodu na reprezentatywnych danych sesji.

01 · Punkt wyjścia

Zacząć obok stołu, a potem zasłużyć na miejsce w jego centrum.

Grupa RPG śledzi karty postaci, kości, zasady, ekwipunek, rany, notatki i pozycję. Większość virtual tabletopów zaczynała od mapy. My pytaliśmy, co aplikacja może przejąć, nie odbierając kontroli graczom ani prowadzącemu.

Zaczęliśmy od telefonu wspierającego grę przy fizycznym stole, ale ta sama sesja musiała działać zdalnie. Powstało jedno założenie: responsywna aplikacja użyteczna zarówno obok stołu, jak i zamiast niego.

Dwa produkty rozwiązujące ten sam problem z przeciwnych stron

VTT od mapy

Przenieś fizyczny stół na ekran; zasady niech każda drużyna przyniesie własne.

  1. Mapa
  2. Pionki
  3. Czat
  4. Kości

Zasady zostają w głowach graczy; oprogramowanie zostaje uniwersalne.

Rolling Dices

Najpierw scyfryzuj drużynę i zasady; po stół sięgnij, gdy aplikacja rozumie, czym jest tura.

  1. Postacie
  2. Sesje
  3. Kości
  4. Zasady
  5. Świat
  6. Plansza

Mapa pojawiła się na końcu — wtedy aplikacja rozumiała już toczącą się na niej grę.

Rolling Dices rozwijało się odwrotnie niż VTT zaczynające od mapy: najpierw cyfryzowało zasady i sesję, a stół na końcu.

Moja rola

  • Założyłem produkt i zdefiniowałem propozycję dla początkujących.
  • Prowadziłem 25+ sesji badawczych i testów z ponad 100 użytkownikami.
  • Projektowałem kluczowe flow i system interfejsu w Figmie.
  • Prowadziłem architekturę i współtworzyłem aplikację w React i Supabase; odpowiadałem za zakres i kolejność prac trzyosobowego zespołu inżynierskiego.
  • Podjąłem decyzję o zamknięciu, gdy dane nie uzasadniały dalszej inwestycji.

02 · Co badania przesunęły do centrum

Produkt zmieniał się razem z naszym rozumieniem gry.

Pierwsze wydanie organizowało konta, postacie i zaproszenia, ale ledwie dotykało gry. Testy ujawniły ważniejsze pytania: co widzi każda rola, kto rozpoczyna akcję i które elementy sesji muszą być trwałe?

Wspólna sesja zastąpiła konto w centrum produktu. Jej dziennik połączył rozmowę, rzuty, zdarzenia i zmiany stanu; każda wydana pętla ujawniała kolejną odpowiedzialność — od kontekstu postaci po zasoby i wspólną przestrzeń.

  • 25+sesji badawczych i usability
  • 100+zaangażowanych graczy i prowadzących
  • 2konwenty i kilkanaście mniejszych wydarzeń

Cztery sposoby patrzenia na ten sam stół.

Jak badaliśmy
  • Serwer Discord · głos + aplikacja

    Moderowane playtesty na Discordzie

    Co zmieniłoGrupy wracały do dziennika, żeby rozstrzygać spory — dlatego dziennik sesji stał się kręgosłupem produktu, a nie oknem czatu.

  • Bykon · Copernicon · puby, kluby, imprezy

    Demo dla przechodniów na konwentach i spotkaniach

    Co zmieniłoPierwsze dwie minuty rozstrzygały wszystko. Tworzenie postaci zamieniło rozdawanie punktów na wybór kart historii, bo nikt przy stoisku nie chciał liczyć.

  • Jeden na jeden, udostępniony ekran

    Testy użyteczności na zadaniach

    Co zmieniłoRzut wywołany musiał zaczynać prowadzący. Gracze prosili o możliwość rzucania samodzielnie, a potem nie wiedzieli po co — prośba o rzut stała się akcją MG z dołączonym kontekstem.

  • Po sesjach · kanały Discorda

    Wywiady z MG i ankiety społeczności

    Co zmieniłoMG nie chcieli automatyzacji fikcji — chcieli pozbyć się księgowości. Ta granica pojawia się w sekcji o ewolucji i nigdy się nie przesunęła.

Jeden cykl badawczy, mniej więcej co dwa tygodnie
  1. 01

    Przeprowadź sesję

    Przeszkolony MG gra, jedno z nas patrzy i zapisuje wyłącznie to, co się wydarzyło.

    notatki z czasem + nagranie
  2. 02

    Zapytaj, jak to było

    Kwadrans ze stołem, dłuższa rozmowa z prowadzącym.

    notatki z wywiadów
  3. 03

    Uporządkuj dowody

    Momenty grupowane według tego, co mówią o produkcie, nie kto je zgłosił.

    tablica wniosków
  4. 04

    Wybierz jedną odpowiedzialność

    Jedna kolejna rzecz, którą aplikacja ma przejąć, skonfrontowana z granicą produktu.

    flow w Figmie + notatka o zakresie
  5. 05

    Wydaj i umów kolejny stół

    Build na tyle mały, żeby przetestować go z tymi samymi MG w ciągu dwóch tygodni.

    wydanie + następna sesja

Trzech inżynierów, jeden na pełny etat — i ludzie wokół stołu.

Zespół techniczny był mały i częściowo dorywczy. Produkt działał dzięki drugiemu kręgowi — współzałożycielowi z branży wydawniczej, zaprzyjaźnionym mistrzom gry i wolontariuszom ze społeczności — który dawał to, czego trzech inżynierów nie mogło: prawdziwe stoły do obserwacji i ręce do ich prowadzenia.

Zespół techniczny

  • Lead — produkt, badania, UX, architekturapełny etat

    Ja. Prowadziłem badania, projektowałem flow i współtworzyłem aplikację od początku do końca.

  • Fullstack, nacisk na frontend20–30 h / mies.

    Prace nad interfejsem i funkcjami, w wymiarze zależnym od bieżącego obciążenia.

  • Fullstack, nacisk na backendodszedł w 2023

    Odpowiadał za pierwotny własny serwer. Zrezygnował w 2023 roku, co przebudowało architekturę.

Zespół wspierający

  • Współzałożyciel — podręczniki i społecznośćwydawnictwo

    Redagował i przygotowywał do druku podręczniki TTRPG w polskim wydawnictwie; napisał nasz skrót zasad, prowadził media społecznościowe i organizował każdy playtest z lokalnymi społecznościami.

  • Zaprzyjaźnieni mistrzowie grykilkoro

    Przeszkoleni z aplikacji; prowadzili sesje testowe na konwentach, w pubach, klubach, online i przy fizycznych stołach.

  • Wolontariusze ze społecznościdoraźnie

    Grafik, autorzy treści i ambasadorzy społeczności, którzy pomagali przy ulotkach, lore, postach w mediach społecznościowych i przyprowadzali własne grupy na playtesty.

  • Społeczność na Discordziegracze

    Serwer playtestów: wracające drużyny, zgłoszenia błędów, ankiety o tym, co budować dalej.

Gdy w 2023 roku odszedł backend developer, nie zastąpiliśmy własnego serwera. Iterowanie wyłącznie na froncie, z Supabase RPC jako prowizorycznym backendem, utrzymało rytm badań — i stało się zakładem architektonicznym opisanym dalej.

Jak badania wyglądały z zewnątrz.

Testowaliśmy tam, gdzie gracze już się spotykali: online, na konwentach i w lokalnych klubach.

Testy z przechodniami na Bykonie w Bydgoszczy.
Opowiadanie o produkcie lokalnemu radiu studenckiemu między sesjami z przechodniami.
Ulotka rozdawana na konwentach. W środku — dwustronicowy skrót zasad napisany przez naszego współzałożyciela.
Moderowany playtest zdalny: Discord obok sesji na żywo.
Skrót zasad: czym jest gra fabularna, jak stworzyć postać i jak rzut akcji kończy się sukcesem, twistem albo porażką.

03 · Rozszerzająca się granica

Każde wydanie przesuwało granicę tego, co liczyło się jako gra.

Produkt przeszedł od przechowywania informacji przez koordynację ludzi, wspólny stan i zasady aż po reprezentację przestrzeni. Kolejność wyznaczały badania, nie sztywny roadmap.

Pięć wydań i udział księgowości stołu, którą każde z nich przejęło
  1. Koniec 2022

    Asystent RPG

    Przechowywała informacje

    • konta
    • postacie
    • sesje
    • znajomi

    Konta, postacie i launcher organizowały grupę przed grą. Najważniejszym obiektem wciąż było konto.

  2. Wiosna 2023

    Wspólna sesja

    Koordynowała ludzi

    • drużyna
    • rzuty wywołane
    • dziennik sesji
    • zaproszenia

    Dziennik sesji, drużyna, fabuła i rzuty wywołane stworzyły pierwszą pełną pętlę multiplayer.

  3. Maj — sie 2023

    Asystent realtime

    Pamiętała wspólny stan

    • obecność
    • zdrowie
    • ekwipunek
    • lokacje
    • zapisy

    Migracja do Supabase zmieniła sesje we wspólne obiekty live z oczekującymi akcjami, obecnością i ekwipunkiem.

  4. Wrz — gru 2023

    Silnik zasad i świat

    Liczyła zasady

    • atrybuty
    • statystyki pochodne
    • trudność
    • energia i XP
    • ekonomia drużyny

    Atrybuty, umiejętności, wyposażenie i zasoby zmieniły karty postaci w aktywne obiekty gry; Story stało się Exploration.

  5. Sty — lut 2024

    Virtual tabletop

    Reprezentowała przestrzeń

    • siatka
    • inicjatywa
    • wysokość
    • widoczność
    • skradanie

    Siatka walki, jednostki, inicjatywa, teren, wysokość i widzenie domknęły ewolucję w taktyczny workspace.

w rękach ludzi przy stole w aplikacji

Jedno nie przesunęło się nigdy: fikcja i prawo prowadzącego do rozstrzygania o niej. Każdy szczebel zdejmował ze stołu księgowość — nigdy decyzję należącą do ludzi wokół niego.

Więcej zasad usuwało więcej księgowości, ale odbierało produktowi uniwersalność. Odpowiedzią był konfigurowalny system motywów — najambitniejszy zakład projektu.

04 · Co aplikacja rozumiała

Nie plansza z tokenami. VTT, które rozumiało grę.

W early access Rolling Dices było połączonym zestawem systemów rozgrywki. Wartość nie leżała w jednym ekranie, lecz w tym, że reguły postaci, zasoby drużyny, postęp fabuły i walka przestrzenna zmieniały ten sam stan sesji.

LOG

Pamięć sesji

Dopisywana historia, w której dialog, rzuty, przedmioty, zdrowie, zasoby i lokacje dzielą jedną oś czasu.

  • wiadomości
  • zapisy rzutów
  • zdarzenia fabularne
  • zmiany stanu
CHAR

Silnik postaci

Atrybuty i umiejętności zasilały statystyki pochodne; ekwipunek, perki i efekty pasywne zmieniały możliwości postaci.

  • zdrowie i energia
  • XP i poziomy
  • udźwig / skok / rzut
  • ruch i widzenie
ACTN

Akcje świadome zasad

Rzut wywołany niósł aktora, atrybut, trudność i kontekst ataku lub obrony; silnik budował kości i liczył konsekwencje.

  • difficulty_roll
  • action_roll
  • premia za RP
  • efekty pasywne
PARTY

Ekonomia kampanii

Pieniądze, prowiant, materiały i lekarstwa należały do drużyny, więc odpoczynek, leczenie i naprawa czerpały z jednej puli.

  • odpoczynek
  • leczenie ran
  • naprawa
  • level-up
WORLD

Eksploracja

Model Location → Area → Place ze story cards i położeniem graczy, dający prowadzącemu inny widok niż drużynie.

  • hierarchia lokacji
  • whereabouts
  • story cards
  • widok GM / gracza
BOARD

Walka taktyczna

Wspólna siatka z jednostkami, inicjatywą i rundami, na której teren, wysokość, ściany i powierzchnie nadawały pozycji znaczenie.

  • kolejka inicjatywy
  • rundy i tury
  • dystans
  • wysokość

Jeden cykl sesji

Gra narracyjna i taktyczna były jedną pętlą: eksploracja wywoływała zdarzenia, zdarzenia prowadziły do spotkań i akcji, akcje zmieniały postacie i zasoby, a zapisana historia wpływała na dalszy bieg gry.

Jeden cykl sesji i stan zapisywany w każdej fazie
  1. 01

    Wejście do świata

    GM aktywuje lokację; każdy gracz zajmuje obszar i miejsce.

    db_Session + whereabouts
  2. 02

    Ujawnienie zdarzenia

    Karta powiązana z lokacją wprowadza spotkanie, zagrożenie lub skarb.

    instance_StoryCard
  3. 03

    Budowa spotkania

    GM tworzy jednostki, umieszcza je na planszy i ustala inicjatywę.

    instance_Entity + Board
  4. 04

    Rozstrzyganie akcji

    Tury i rzuty wywołane łączą zasady postaci, pozycję i modyfikatory.

    turn_order + PendingAction
  5. 05

    Zapis konsekwencji

    Zdrowie, energia, sprzęt, zasoby i historia odzwierciedlają wynik.

    SessionPlayer + SessionRecord
  6. 06

    Przygotowanie od nowa

    Odpoczynek, lekarstwa i materiały zamieniają zapasy w gotowość.

    db_Session resources

Każdy desktopowy zrzut na tej stronie to aplikacja React z marca 2024 uruchomiona z oryginalnego kodu na reprezentatywnych danych sesji. Nic nie zostało przerysowane.

01 Silnik postaci

Karta postaci była wejściem do silnika zasad.

Karta postaci stająca się wykonywalnym kontekstem
  1. THEME

    Zdefiniuj język

    Atrybuty, umiejętności i wzory żyją w jednej konfiguracji.

    engine/themesConfig
  2. BUILD

    Zbuduj postać

    Rozdziel atrybuty i umiejętności w ramach motywu.

    character_schema
  3. DERIVE

    Wylicz możliwości

    Wartości bazowe i ważone umiejętności dają ruch, widzenie i więcej.

    engine/derivedStatistics
  4. EQUIP

    Zastosuj wyposażenie

    Waga, trwałość i modyfikatory walki wchodzą do kontekstu.

    instance_PlayerItem
  5. ACT

    Rozstrzygnij w kontekście

    Wywołana akcja używa postaci, sprzętu i bieżącego stanu.

    action_roll

02 Ekonomia drużyny

Regeneracja zmieniała umiejętność postaci we wspólną decyzję ekonomiczną.

Jedna akcja wydająca trzy zakresy stanu
  1. TARGET

    Wybierz odbiorcę

    Lecz siebie albo innego członka drużyny.

    target SessionPlayer
  2. CALCULATE

    Wyceń akcję

    Mental i Medicine modyfikują koszt za punkt zdrowia.

    calculateTreatWounds()
  3. PREVIEW

    Pokaż konsekwencję

    Wyświetl zdrowie po leczeniu i pozostałe lekarstwa.

    derived client state
  4. COMMIT A

    Przywróć zdrowie

    Zapisz nowy current_state wybranego gracza.

    instance_SessionPlayer
  5. COMMIT B

    Wydaj lekarstwa

    Odejmij koszt od wspólnych zapasów.

    db_Session.meds
Kontrola zasobów GM-a. Monety, prowiant, lekarstwa i materiały należą do sesji, nie do postaci.
Treat Wounds pokazuje wejścia reguł, wynik zdrowia i koszt wspólnego zasobu przed zatwierdzeniem.

03 Model świata

Sesja poruszała się po świecie, nie po drzewie folderów.

Opracowana treść stająca się pamięcią sesji
  1. AUTHOR

    Spakuj setting

    Location packs grupują lokacje i ich pule kart.

    resource_LocationPack
  2. STRUCTURE

    Zbuduj miejsce

    Lokacje zawierają typowane obszary, a obszary — miejsca.

    resource_Location + Area
  3. LOCATE

    Przenieś drużynę

    Każdy gracz ma jawną lokację, obszar i miejsce.

    SessionPlayer.whereabouts
  4. ACTIVATE

    Wprowadź do gry

    Karta zasobu staje się kartą należącą do sesji.

    instance_StoryCard
  5. REMEMBER

    Rozstrzygnij i zapisz

    Przejściowe zdarzenie znika, konsekwencja zostaje.

    db_SessionRecord
Exploration / Areas. Prowadzący widzi hierarchię lokacji, odkryte miejsca i położenie każdej postaci.
Exploration / Aktywne karty. Opracowane spotkania i zagrożenia jako żywe obiekty sesji.

04 Spotkania i walka

Spotkanie przechodziło z talii treści do stanu taktycznego.

Szablon stający się pozycją na planszy
  1. SELECT

    Wybierz szablon

    Przeglądaj przeciwników i NPC przypisanych do lokacji.

    resource_Entity
  2. SPAWN

    Utwórz stan runtime

    Nadaj jednostce własność sesji i własne HP.

    instance_Entity
  3. PLACE

    Zapisz pozycję

    Przeciągnij instancję z puli na współrzędną.

    board_state[x:y]
  4. ORDER

    Ustal inicjatywę

    Połącz graczy i jednostki w kolejkę tur.

    turn_order + current_round
  5. RESOLVE

    Prowadź walkę

    Ruch, zasięg, widzenie i wyniki zmieniają wspólny stan.

    Board + Player + Entity
Budowa spotkania. GM wybiera szablony według roli taktycznej, zanim utworzy żywych uczestników walki.
Walka w toku. Trzecia runda i kolejka inicjatywy pozostają widoczne obok stanu postaci i sesji.

Mechaniki w szczegółach

Cztery miejsca, w których aplikacja zdjęła zasady z głów graczy.

01 Plansza jako stan świata

Każde pole było stosem faktów o przestrzeni.

Adres
wiersz.kolumna → „6.8”
Wysokość
15 poziomów · −14 m do +14 m
Pole
siatka 2 m · 4 m² na pole
Trwałość
board_state JSON w instance_Board
Nakładka wysokości. Ta sama siatka pokazuje obszary +4 m, 0 m i −2 m, zachowując figurki i przeszkody.
Zarządzanie polami. Cztery zaznaczone pola mogą otrzymać obiekt semantyczny, wysokość, stan wyłączenia albo reset.
Anatomia jednej zapisanej współrzędnej planszy
  1. KEY6.8

    Stały adres wiersz.kolumna w board_state.

  2. SPACEelevation: 7

    Poziom neutralny; mapuje się na 0 m.

  3. CONTENTobject_type

    Opcjonalna powierzchnia, gaz, ściana lub obiekt eksploracji.

  4. ACTORentity_id

    Opcjonalny gracz albo jednostka zajmująca pole.

  5. RULEfield_disabled

    Blokuje umieszczanie; oznacza teren jako niedostępny.

Eksploracja
  • curio
  • poi
  • passage
  • door
  • treasure
Powierzchnie
  • fire
  • ice
  • oil
  • water
  • poison
Gazy
  • smoke
  • steam
  • poison_gas
  • explosive_gas
Przeszkody
  • wall_small
  • wall_medium
  • wall_big
  • disabled

Jedno pole, pięć niezależnych wymiarów. UI składało je wizualnie; dokument planszy synchronizował je jako jeden snapshot.

02 Ruch i pomiar

Ruch negocjowało się jako trasę, zamiast zgadywać go z pikseli.

Skala
2 m na krawędź siatki
Przekątna
√2 × 2 m
Trasa
uporządkowany start + waypointy
Wejście
drag myszą · drag dotykiem
Tryb linijki. Uporządkowane punkty tworzą opisane odcinki i bieżącą sumę; obiekty planszy pozostają widoczne jako ograniczenia.
Pomiar ruchu przez trzy punkty
  1. STARTpole 6.4

    Wybierz pole poruszanej figurki.

  2. LEG 018,00 m

    Cztery poziome krawędzie do pierwszego waypointu.

  3. VIApole 6.8

    Skręć wokół terenu bez zastępowania początku.

  4. LEG 024,00 m

    Dwie pionowe krawędzie do celu.

  5. TOTAL12,00 m

    Porównaj trasę z ruchem walczącej postaci.

Możliwość postaci
  • Physical
  • Dexterity
  • combatMovement
Możliwość jednostki
  • base_movement_range
  • initiative
Blokady upuszczenia
  • occupied
  • wall
  • disabled field

Linijka mierzyła geometrię, silnik dawał możliwości, GM zachowywał ostateczną decyzję.

03 Zdrowie w strefach ciała

Figurka czyniła miejsce obrażenia i stan pancerza czytelnym.

Strefy
głowa · tułów · ramiona · nogi
Miejsce trafienia
D12
Absorpcja
najpierw trwałość pancerza strefy
Konsekwencja
reszta → globalne HP
Podgląd obrażeń. Trafienie w tułów najpierw zużywa trwałość pasującego pancerza, potem zdrowie postaci.
Rozstrzyganie obrażeń strefy ciała
  1. HITD12 → torso

    Kontekst obrony wskazuje jedną z czterech stref.

  2. LOOKUParmour: torso

    Znajdź założony przedmiot z tym samym subtype.

  3. ABSORBmin(9, 11) = 9

    Odejmij pochłonięte obrażenia od trwałości.

  4. APPLY9 − 9 = 0 HP

    Tylko niepochłonięte obrażenia zmieniają zdrowie.

  5. EFFECThealth multiplier

    Pozostałe zdrowie modyfikuje następne rzuty.

Wcześniejszy koncept
  • zone HP
  • minor / moderate / critical
  • overall condition
Early access
  • global health
  • zone target
  • zoned armour
  • durability
Kod figurki
  • czerwony = wybór
  • jasny = mocny pancerz
  • ciemny = zużyty
  • czarny = zniszczony

Strefy ciała jako kontekst taktyczny wokół prostszego rdzenia zdrowia.

04 Ekwipunek jako wejście reguł

Przedmiot był jednocześnie treścią, stanem runtime i kontekstem akcji.

Definicja
resource_Item
Własność
instance_PlayerItem
Ograniczenia
waga · sloty · strefa ciała
Cykl życia
załóż · użyj · uszkodź · napraw · zapisz
Party / Inventory. Jedna karta odsłania sens dla gracza oraz wartości konsumowane przez silnik zasad.
Dane ekwipunku i cykl gameplayowy
  1. DEFINEresource_Item

    Typ, waga, sloty, bazowa trwałość, modyfikatory.

  2. OWNinstance_PlayerItem

    Gracz, ilość, flaga założenia, bieżąca trwałość.

  3. EQUIPloadout guards

    Limit dwóch slotów; unikalna strefa pancerza.

  4. USEaction context

    Zastosuj atak lub obronę; zużyj trwałość.

  5. REPAIRrepair + record

    Wydaj materiały; dopisz zmianę do historii.

Definicja wspólna
  • type
  • subtype
  • rarity
  • weight
  • price
  • base_durability
Instancja gracza
  • quantity
  • is_equipped
  • durability
  • session_player_id
Konsumenci reguł
  • carry capacity
  • attack
  • defence
  • body armour
  • repair

Rozdział resource/instance zachowywał treść do ponownego użycia, a każda sesja uszkadzała i zakładała własną kopię.

05 · Jedna akcja od początku do końca

Mechaniką stała się akcja, nie sama kość.

Rzut wywołany mieści naraz pomysł produktowy i architekturę. Prowadzący wybierał gracza, atrybut i trudność; silnik wyliczał kości i utrwalał oczekującą akcję; gracz rzucał, a zdrowie, energia i wyposażenie kształtowały wynik, który trafiał do wspólnego dziennika. Kości pozostawały widocznym momentem rozstrzygnięcia, ale aplikacja rozumiała dość kontekstu, by ocenić akcję i zachować jej konsekwencje.

Rzut wywołany, od prowadzącego do wspólnej historii sesji
  1. 01
    Prowadzący

    Wywołanie akcji

    Wybór gracza, kategorii, atrybutu i trudności.

  2. 02
    Silnik w przeglądarce

    Wyliczenie kości

    Dobór schematów z motywu i kontekstu postaci.

    difficulty_roll
  3. 03
    Postgres + Realtime

    Utrwalenie oczekującej akcji

    Żądanie przetrwa opóźnienie i reconnect; Realtime powiadamia gracza.

    instance_PendingAction
  4. 04
    Gracz

    Rzut

    Rozstrzygnięcie akcji kontekstowej, nie dowolnej kości.

    action_roll
  5. 05
    Silnik w przeglądarce

    Wyliczenie konsekwencji

    Modyfikatory, energia, zdrowie, sprzęt i XP.

  6. 06
    Postgres + Realtime

    Zapis i broadcast

    Aktualizacja gracza, dopisanie rekordu, usunięcie oczekującej akcji; każdy klient pobiera świeży stan.

    SessionPlayer + SessionRecord

Plansza reprezentowała stan bieżący; dziennik wyjaśniał, jak drużyna do niego dotarła. Baza była jednocześnie trwałym stanem, transportem multiplayer i lekką kolejką workflow.

Modal wywołania rzutu: odbiorca, atrybut i zestawy kości, kontekst ataku/obrony, premia za role-play i dziesięciostopniowa trudność.

06 · Dwa konteksty, jeden model

Jeden model gry, dwa konteksty użycia.

Na telefonie Rolling Dices działało jak pas z narzędziami: jeden skupiony kontekst naraz, więc telefon wspierał grę przy fizycznym stole, zamiast z nią konkurować. Nasze pierwsze założenie było odwrotne — wczesna wersja blokowała małe ekrany. Potem telefon stał się wymogiem podstawowym, łącznie z dotykowym drag-and-dropem na planszy walki.

Desktop przestał być większym telefonem i stał się przestrzenią pracy: drużyna, dziennik sesji, kości, eksploracja i plansza widoczne razem, a rzadsze operacje otwierane jako zadania modalne. Model domeny nie zmieniał się nigdy; zmieniała się tylko kompozycja.

Jeden model sesji, złożony na dwa sposoby

Desktop · przestrzeń pracy

Cztery powierzchnie otwarte naraz, żeby prowadzący widział drużynę, historię, świat i walkę bez przełączania widoków.

Drużyna

Postacie, zdrowie, energia, ekwipunek

Dziennik sesji + kości

Wspólna historia i trwający rzut

Plansza

Siatka, figurki, inicjatywa, teren

Eksploracja

Lokacja, miejsca, aktywne karty fabuły

Zadania modalne

  • wywołaj rzut
  • zarządzaj zasobami
  • odpoczynek
  • dodaj jednostkę
  • zmień lokację
  • dobierz kartę

Mobile · jeden kontekst

Sześć kontekstów, dokładnie jeden na ekranie naraz, każdy pod kciuk.

  1. Dziennik sesji
  2. Drużyna
  3. Kości
  4. Eksploracja
  5. Plansza
  6. Zasoby

Telefon zmienił się ze zbioru ekranów w system operacyjny sesji.

Koncepcje z 2023 roku wprowadzały osobne narzędzia. W marcu 2024 były one projekcjami jednego modelu multiplayer, z tymi samymi postaciami, akcjami, światem i stanem walki na mobile i desktopie.

Base MVP · Figma, 2023

Niezmodyfikowane projekty pre-alpha z 2023 roku, pokazane osobno od późniejszych zrzutów produktu.

Tworzenie postaci
Wywołanie GM-a
Rzut gracza
Session Log
Fabuła
Mobile, Base MVP 2023 → early access, marzec 2024
  1. Tworzenie postaciStan postaci live i ekwipunek
  2. Osobne formularze rzutuWorkflow zasad GM → gracz
  3. Statyczna lista fabułyStan lokacji, miejsc i zdarzeń
  4. Nawigacja asystentaPas narzędzi całej sesji
  5. Brak warstwy taktycznejJednostki, plansza i tury

Early access · marzec 2024

Widoki mobilne wyrenderowane z oryginalnej aplikacji na tej samej reprezentatywnej kampanii co zrzuty desktopowe.

Drużyna / ekwipunek
Wywołany rzut
Plansza taktyczna
Session Log
Eksploracja

07 · Kształt systemu

VTT oparty na bazie realtime, z silnikiem gry w przeglądarce.

Rolling Dices było aplikacją SPA w React i TypeScript połączoną bezpośrednio z Supabase. Redux trzymał tożsamość i nawigację, React Query — projekcje serwerowe, a interakcje o wysokiej częstotliwości — kości, przeciąganie, zaznaczenia — zostawały lokalne aż do zapisu. Osobna warstwa engine liczyła kości, statystyki pochodne, XP, energię i inicjatywę. Supabase zastąpiło naraz serwer aplikacji, uwierzytelnianie i websockety: wyjątkowo lekka architektura multiplayer jak na trzech inżynierów, z których tylko jeden pracował w pełnym wymiarze.

Model spójności brzmiał: zdarzenie, invalidacja, ponowne pobranie. Przeglądarka zapisywała zmianę w Postgresie, Supabase emitowało zdarzenie, pozostałe klienty unieważniały projekcję i pobierały świeży stan. Realtime nie był dodatkiem — był modelem spójności. Postgres pełnił też rolę backend-for-frontend, składając funkcjami SQL projekcje gotowe dla UI, podczas gdy zapisy nadal trafiały do kilku tabel wprost z przeglądarki.

Warstwy systemu i pętla spójności
  1. Przeglądarka · zainstalowane PWA

    • Widoki React
    • Stan UI w Redux
    • Projekcje React Query
    • Silnik gry w TypeScript
  2. Supabase

    • Auth
    • PostgREST
    • SQL RPC
    • Realtime
    • Storage
  3. PostgreSQL

    • Trwałe rekordy
    • Instancje runtime
    • Treści zasad
    • Funkcje modeli odczytu

Pętla spójności

  1. 01Klient zapisuje do Postgresa
  2. 02Realtime emituje zmianę
  3. 03Klienty unieważniają projekcję
  4. 04Klienty pobierają autorytatywny stan

Czterech właścicieli, cztery rytmy aktualizacji

Dane trafiały tam, gdzie pasował ich czas życia i częstotliwość zmian; ciekawy problem leżał na granicach między właścicielami.

Redux Toolkit

Tożsamość, obudowa sesji, przejściowy kontekst workflow

  • aktualny użytkownik
  • aktywna sesja
  • nawigacja
  • kontekst modala

TanStack Query

Projekcje serwerowe i invalidacja cache’u

  • gracze
  • rekordy
  • lokacje
  • jednostki

Stan komponentów

Interakcje wymagające natychmiastowej reakcji

  • stół do kości
  • pozycja drag
  • wybrane pola

engine/*

Czyste transformacje między stanami gry

  • zestawy kości
  • statystyki pochodne
  • XP / energia
  • inicjatywa / widzenie

Nazwa schematu mówiła, w jakim czasie żyje obiekt.

Każdy prefiks sugerował ownership, częstotliwość zmian i sposób konsumowania przez UI.

  1. resource_*

    Definicje zasad i treści

    Stabilne, wielokrotnego użytku, niezależne od sesji

    • resource_CharacterClass
    • resource_Entity
    • resource_Item
    • resource_StoryCard
  2. db_*

    Trwałe rekordy domenowe

    Długo żyjąca tożsamość i historia

    • db_Session
    • db_PlayerCharacter
    • db_SessionRecord
    • db_Profile
  3. instance_*

    Instancje aktywnej sesji

    Zmieniane podczas gry, rozgłaszane przez Realtime

    • instance_SessionPlayer
    • instance_PendingAction
    • instance_Entity
    • instance_Board
  4. engine/*

    Transformacje domenowe

    Bezstanowy TypeScript między wejściem a następnym stanem

    • kości
    • statystyki postaci
    • efekty
    • walka
    • ruch

Postgres jako builder modeli odczytu

RPC get_players_for_session składało gotowy dla UI model gracza z sześciu źródeł, trzymając wiedzę o joinach poza Reactem.

instance_SessionPlayer
↳ db_PlayerCharacter
↳ db_Profile
↳ resource_CharacterClass
↳ get_player_perks()
↳ get_player_items()

Jeden kanał, osiem żywych agregatów

Kanał sesji nasłuchiwał zmian tabel. Większość zdarzeń unieważniała projekcję i pobierała ją ponownie; dopisywane rekordy trafiały wprost do cache’u. Zapytania nie starzały się same — kontraktem odświeżania było Realtime, nie polling.

  • db_SessionRecord
  • db_Session
  • instance_SessionPlayer
  • instance_PlayerItem
  • instance_PendingAction
  • instance_Entity
  • instance_LocationState
  • instance_Board
Pełny model bazy · Beta MVP, sierpień 202319 tabel · 29 relacji między polami

resource_*Zasady i treści

db_*Trwałe rekordy

instance_*Stan aktywnej sesji

db_User

  • iduuidPK
  • display_namevarchar

db_Session

  • idvarchar
  • titlevarchar
  • access_codevarchar[8]
  • owner_iduuidFK
  • theme_idvarcharFK
  • current_location_iduuidFK
  • created_attimestamp
  • updated_attimestamp

db_PlayerCharacter

  • iduuid
  • owner_iduuidFK
  • namevarchar
  • portraitimage
  • classvarcharFK

db_PlayerCharacterDescription

  • iduuid
  • player_character_iduuidFK
  • character_description_iduuidFK

db_PlayerCharacterItem

  • iduuid
  • player_character_iduuidFK
  • character_description_iduuidFK

db_PlayerCharacterSkill

  • player_character_iduuidFK
  • skill_iduuidFK

db_SessionRecord

  • idvarchar
  • authoruuidFK
  • player_characteruuidFK
  • session_iduuidFK
  • record_typerecordTypeEnum
  • contentJSON
  • created_attimestamp

instance_PendingAction

  • idvarchar
  • session_iduuidFK
  • recipientuuidFK
  • typetext
  • contentJSON

instance_SessionPlayer

  • iduuid
  • session_iduuidFK
  • player_character_iduuidFK
  • healthJSON
  • moneynumber

instance_PlayerItem

  • iduuid
  • session_player_iduuidFK
  • item_iduuidFK
  • is_equippedboolean
  • quantitynumber

instance_StoryCard

  • iduuid
  • session_iduuidFK
  • story_card_iduuidFK

resource_Theme

  • idvarchar

resource_CharacterClass

  • idvarchar
  • keyvarchar
  • theme_idvarcharFK
  • portrait_stylestext[]
  • character_schemaJSON
  • base_skillsuuid[]
  • base_itemsuuid[]

resource_CharacterDescription

  • iduuid
  • typeenum
  • keyvarchar
  • class_idvarcharFK
  • contentJSON

resource_LocationPack

  • iduuid
  • tkeytext
  • theme_iduuidFK

resource_Location

  • iduuid
  • tkeytext
  • location_pack_iduuidFK
  • cover_imgtext
  • starting_locationboolean

resource_StoryCard

  • iduuid
  • location_iduuidFK
  • tkeytext
  • typetext
  • contentJSON

resource_Item

  • iduuid
  • theme_idvarcharFK
  • typetext
  • tkeytext
  • contentJSON
  • is_equippableboolean

resource_Skill

  • iduuid
  • theme_idvarcharFK
  • typetext
  • tkeytext
  • contentJSON

08 · Czym zapłaciliśmy

Zapłaciliśmy odpornością, żeby kupić tempo iteracji.

Migracja do Supabase w maju 2023 była kluczowym zakładem, zrobionym z otwartymi oczami. Funkcja szła z interfejsu przez mutację i Postgresa do Realtime bez serwera aplikacyjnego po drodze. Dla częściowo dorywczego zespołu goniącego produkt, który wciąż musiał się obronić, ta dźwignia była warta więcej niż odporna infrastruktura. Rachunek przyszedł w konkretnych miejscach.

  1. 01

    Tempo kontra autorytet

    Liczenie zasad w TypeScript przyspieszało iteracje; oznaczało też, że zmodyfikowany klient mógł wybierać sobie wyniki. RLS chronił wiersze, ale nie potrafił wyrazić reguły gry.

  2. 02

    Proste zapisy kontra transakcje

    Rozstrzygnięcie akcji aktualizowało gracza, tworzyło rekord i usuwało oczekującą akcję w osobnych requestach. Częściowa awaria zostawiała wspólny stan niespójny.

  3. 03

    Jeden dokument planszy kontra współbieżność

    Plansza jako jeden dokument JSON upraszczała hydrację i broadcasting — i pozwalała dwóm równoległym edycjom nadpisywać niezależne pola, last write wins.

09 · Rezultat

Grywalny produkt, świadome zakończenie, trwałe lekcje.

Na początku 2024 roku Rolling Dices miało pełny zarys przeglądarkowego VTT — postacie, wspólne sesje, kontekstowe rzuty, trwały dziennik, ekwipunek, zdrowie, zasoby drużyny, eksplorację, planszę walki z inicjatywą, terenem i widzeniem. Mogło leżeć na telefonach wokół fizycznego stołu albo samo być stołem.

Zakres był realny; walidacja nie. Dowiedzieliśmy się dużo o tym, jak ludzie grają, ale zbyt mało z nich zmieniało utrwalone zachowania w sposób uzasadniający dalszą inwestycję. W kwietniu 2024 zamknąłem produkt, zamiast pomylić tempo techniczne z dowodem rynkowym. Założenie produktu oznacza odpowiedzialność za kryteria zatrzymania tak samo jak za wizję.

Co zabrałem do kolejnych produktów

  1. 01

    Waliduj zachowanie, nie entuzjazm

    Ciepłe rozmowy i angażujące testy to nie powtarzalne użycie, gotowość do zmiany narzędzia ani do płacenia.

  2. 02

    Sekwencjonuj ambicję

    Każdy nowy system zwiększał możliwości produktu — i odsuwał ostrzejszy test głównej propozycji.

  3. 03

    Zatrzymanie projektu to praca produktowa

    Zamknięcie produktu, którego nie bronią dane, oszczędza czas, uwagę i zaufanie dla następnego.

Wczesna aplikacja żyła obok gry. Finalna próbowała zmieścić grę w sobie.

Dalej

Potrzebujesz kogoś, kto weźmie odpowiedzialność za produkt i system pod spodem?

Pracuję od discovery i projektu interfejsu po architekturę aplikacji i wdrożenie — i równie swobodnie podejmuję trudne decyzje produktowe, jak techniczne.