diff --git a/.env.example b/.env.example deleted file mode 100644 index 6300625..0000000 --- a/.env.example +++ /dev/null @@ -1,7 +0,0 @@ -# Opcjonalne nadpisanie konfiguracji Supabase dla Vite. -# Domyślne wartości (projekt `rejson-widgets`) są w src/online.ts. -# Używaj WYŁĄCZNIE klucza publishable / anon — nigdy service_role ani sb_secret. -# VITE_* trafia do bundla, więc nie wolno tu wpisywać żadnych sekretów. -# -# VITE_SUPABASE_URL=https://hzbqzfimtroixnldzjeq.supabase.co -# VITE_SUPABASE_PUBLISHABLE_KEY=sb_publishable_twoj_klucz_anon diff --git a/.github/workflows/deploy-pages.yml b/.github/workflows/deploy-pages.yml index 0e9b764..53c4095 100644 --- a/.github/workflows/deploy-pages.yml +++ b/.github/workflows/deploy-pages.yml @@ -1,11 +1,8 @@ -name: RejPlay · GitHub Pages +name: verify + deploy on: push: branches: [main] - # v0.7.1: wcześniej workflow reagował wyłącznie na jeden, historyczny branch - # roboczy, więc pull requesty nie dostawały ŻADNEGO checka. Teraz każdy PR - # przechodzi pełną weryfikację, a wdrożenie nadal jest zarezerwowane dla main. pull_request: workflow_dispatch: @@ -20,35 +17,36 @@ concurrency: jobs: verify: - name: Typecheck · testy · build runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 - uses: actions/setup-node@v4 with: - node-version: '22' + node-version: 22 cache: npm - run: npm ci - run: npm run typecheck - run: npm test - run: npm run build - # Artefakt Pages tworzymy tylko z main — PR ma udowodnić, że build - # przechodzi, a nie publikować swoją wersję strony. - - if: github.ref == 'refs/heads/main' && github.event_name != 'pull_request' - uses: actions/configure-pages@v5 - - if: github.ref == 'refs/heads/main' && github.event_name != 'pull_request' - uses: actions/upload-pages-artifact@v3 - with: - path: dist deploy: - needs: verify - # Repozytorium pozwala wdrażać do środowiska github-pages tylko z main. if: github.ref == 'refs/heads/main' && github.event_name != 'pull_request' + needs: verify runs-on: ubuntu-latest environment: name: github-pages url: ${{ steps.deployment.outputs.page_url }} steps: + - uses: actions/checkout@v4 + - uses: actions/setup-node@v4 + with: + node-version: 22 + cache: npm + - run: npm ci + - run: npm run build + - uses: actions/configure-pages@v5 + - uses: actions/upload-pages-artifact@v3 + with: + path: dist - id: deployment uses: actions/deploy-pages@v4 diff --git a/README.md b/README.md index b9bdc24..6e41693 100644 --- a/README.md +++ b/README.md @@ -1,712 +1,104 @@ -# RejPlay - -Przeglądarkowa platforma 3D przygotowana na **wiele gier**: pierwszą z nich -jest autorska *Demonofobia*, a wspólnym punktem zbiórki jest park **Lobby** -(dokładnie taka nazwa — bez dopisków i bez „3D”). Frontend jest w pełni -statyczny (Vite + Three.js, jeden kontekst WebGL) i działa na **GitHub Pages**. -Multiplayer, czat i profile korzystają z istniejącego projektu -**Supabase `rejson-widgets`** — nie mamy własnego serwera. **Nie ma trybu -offline**: bez połączenia klient ponawia próby, a czat i Presence czekają na -serwer (nie udajemy lokalnych graczy ani wiadomości „tylko u Ciebie"). -**Zagranie wymaga konta.** - -Nie ma zapowiedzi gier ani zmyślonych metryk: katalog pokazuje tylko to, co -naprawdę działa. W `public/art/` jest jeden plik: `house.jpg` (kafelek gry). - -**v0.9 — pewny STOP, kalibracja punktowa i czyste przejścia:** sterowanie bez -klawiatury nie włącza się już samo (`enabled` nie jest zapisywane, stary wpis z -`localStorage` jest ignorowany), znika stały dryf głowy, a brak twarzy daje -natychmiastowe zero we wszystkich kanałach — także `onControl(0)` w silniku. -Jeden, zawsze dostępny hamulec: przycisk **„Zatrzymaj"** i klawisz **C**. -Dodano kreator **9 punktów na ekranie**: dopasowanie afiniczne spojrzenia -(`src/gaze-calibration.ts`), punkt neutralny głowy i automatyczne wykrywanie -odwróconych osi dłoni (lewo/prawo, góra/dół, przód/tył), z ręcznymi -przełącznikami w laboratorium. W Demonofobii W/dotyk mają pierwszeństwo przed -kanałem z kamerki (`combineMovement()`), ruch próbkuje cały odcinek zamiast -tylko celu, collidery mają widoczne rekwizyty podwórka, a skrzydło drzwi -zostało odsunięte od osi spawnu. Usunięto tryb offline, zmieniono tekst hero, -a lampy w Lobby są normalizowane do rzeczywistego bbox GLB (Khronos -`Lantern.glb` miał 25,66 m × `scale 2.2` = 56 m w parku o promieniu 12 m). -Testy: **82/82** (`npm test`). - -**v0.8 — chodzenie, park bez brył i realniejsze dłonie:** naprawiony błąd, -po którym w Demonofobii nie dało się ruszyć z miejsca (gracz rodził się -**wewnątrz** collidera blatu monitoringu, a `yaw = π` przy `yawTarget = 0` -dorzucał obrót kamery o 180° na starcie). Czysta geometria kolizji trafiła do -`src/movement.ts` z testami drogi od samochodu do frontowych drzwi, start -sam wybiera wolne miejsce, a focus w polu tekstowym nie blokuje już WASD. -Park **Lobby** nie ma ani jednej prymitywnej bryły: altana ze stożka, klomby z -kulek, oczko z torusa, tablica z prostopadłościanu i mosiężny zegar zostały -zastąpione gotowymi GLB (fontanna, wiata, billboard „Lobby", ławki, donice, -głazy, roślina PBR, latarnie, stół piknikowy). `park.ts` odpowiada dziś -wyłącznie za teksturowany teren, niebo i światła; portale gier też są -billboardami GLB. Katalog urósł do **120 darmowych modeli w 9 kategoriach** — -dodano 16 modeli GridFall (budynki, pojazdy, most) i 3 fotorealistyczne PBR -z Khronos, z notami licencyjnymi i testem integralności w CI. Dłonie dostały -anatomiczne minima stawów, zależność ścięgnistą nadgarstka (tenodesis), -kaskadę czasową palców oraz stożkowate paliczki z kostkami i opuszkami -w widoku awaryjnym. **50 testów** w `node --test`. - -**v0.7.1 — odporność i jakość śledzenia:** cztery realne usterki w torze -kamerki zostały usunięte. Po zniknięciu twarzy `CameraControl` odejmował -neutralną kalibrację od zera, więc postać jechała sama; teraz brak sygnału -jest czymś innym niż sygnał zerowy. Pojedynczy `NaN` z MediaPipe zatruwał -sprężyny `HandAnimator` na zawsze — każda klatka przechodzi teraz przez -`src/tracking-guard.ts`, a integrator sam odtwarza stan. Dłonie i twarz mają -watchdogi, które przy braku odczytu świadomie wracają do spoczynku zamiast -zamarzać; odłączenie kamerki wykrywa tor `ended` i wznawia strumień (max 2 -próby). `faceFrameFromLandmarks` wydzielono do czystej, testowalnej -`src/face-math.ts` z adaptacyjną kalibracją mrugnięcia (próg dopasowuje się -do oka użytkownika), a „Kalibruj pozycję neutralną" naprawiono tak, by nie -dokładać skoku ±π do następnej klatki. Start kamerki sprząta strumień i iframe -po błędzie, liczniki diagnostyczne resetują się co sesję, a raport „Kopiuj -logi" ma wersję v0.7.1 i wiek odczytów. Dodano **29 testów jednostkowych** -(`tests/`, wbudowany `node --test`, zero nowych zależności) i wreszcie -**checki na pull requestach**: poprzedni workflow reagował tylko na jeden -historyczny branch, więc PR-y nie miały żadnej weryfikacji. Zapis do czatu, -profili i zdarzeń przeszedł przez RPC z walidacją tokenu (migracja -`20261105000000_rejplay_write_auth.sql`, zastosowana i sprawdzona na bazie: -polityki `SELECT`, bezpośredni `INSERT` jako `anon` odrzucany). Presence ma -jawny numer protokołu, żeby przyszłe zmiany nie rozdzielały klientów. - -**v0.7 — park, assety, nowy home i brama konta:** Lobby stało się jasnym -parkiem w świetle dnia (trawnik, okrągła alejka, altana, klomby, oczko wodne, -tablica „Lobby”, brama gry) zamiast nocnego betonowego placu. Biblioteka -darmowych, realistycznych assetów urosła z 25 do **101 modeli w 9 kategoriach** -(meble, postacie, zwierzęta, pojazdy, budynki, roślinność, ulica, przedmioty, -park) — Demonofobia jest tylko jedną z gier, które mają z niej korzystać. -Strona główna została przebudowana: mniej tekstu, 25 ikon SVG, brak -nakładania się sekcji na 390 / 1024 / 1440 px. Dodano **ładny ekran -logowania i rejestracji** (`src/auth-gate.ts`) oraz twardą blokadę: bez konta -nie da się wejść do Lobby ani uruchomić gry. Zero botów — lista graczy to -wyłącznie realne Presence. Przy okazji naprawiono `CommunityAccount.refresh()`: -pierwsze odświeżenie bez sesji zostawiało resolved promise w `pending`, więc -późniejsze logowanie nigdy nie walidowało tokenu. - -**v0.5:** pełna pozycja dłoni w 3D (x / y / głębokość) zamiast tylko palców -i drobnych ruchów nadgarstka, first-person w Lobby, przechwycenie kursora -z pełnym obrotem 360° dla wielu monitorów, adaptacyjne wygładzanie i czułość -ruchu do ustawienia na żywo. - -**v0.6.1 — naprawa kamerki i nadgarstka:** FaceMesh działa w osobnym, -same-origin iframe (`public/mediapipe/face/bridge.html`), bo oba bundle -MediaPipe nadpisywały sobie globalne zmienne Emscripten w jednym dokumencie — -to było źródłem `Błąd odczytu klatki: TypeError: Cannot read properties of -undefined (reading 'buffer')` oraz braku wykrywania głowy i oczu. first-person -`src/view-hands.ts` dostał wreszcie geometryczne wyznaczanie osi zgięcia -(poprzednia poprawka trafiła tylko do awatara third-person) i prawdziwy, -ciągły obrót nadgarstka na kości `Hand`; stary wzór `atan2(palm.x, -palm.y)` -zawsze dawał po clampie stałą `1`, więc nadgarstek nie reagował. Słabsze -kamerki dostają drabinę `getUserMedia` (640×480 → 320×240 → `video: true`), -niższe progi pewności, mniejszy kadr dla FaceMesh i czytelny status wykrywania. -Dodano **„Kopiuj logi” / „Pokaż logi” / „Restart kamerki”** — raport trafia do -schowka, nie zawiera obrazu, konta, tokenów ani kodu drużyny. - -**v0.6 — „VR bez VR”:** jedna kamerka zasila trzy niezależne tory: MediaPipe -Hands (dłonie 1:1) **oraz** FaceMesh z `refineLandmarks` (głowa + źrenice). -**Laboratorium kamery** na starcie robi kalibrację neutralnej pozycji i trzy -testy (dłonie, głowa, oczy); po nich lekki przechył głowy w przód porusza -postacią z prędkością wprost proporcjonalną do kąta, przechył w tył cofa, -przechył na bok strafuje, a źrenice sterują kamerą. Palce chwytają, odkładają -i używają narzędzi z **pasu na biodrach**. Klawiatura, mysz i dotyk zostają -wyłącznie jako zapasowe sterowanie. Do tego: konta współdzielone z -`rejson59.github.io`, drużyny z kodem zaproszenia, zero botów demo, samochód-baza -i 25 darmowych assetów CC0/MIT, 6 narzędzi, 6 śladów, 10 autorskich zjaw. +# RejPlay · Gabinet Osobliwości + +Cztery kompletne, autorskie gry przeglądarkowe w jednym gabinecie. Nie ma tu ani jednego pliku grafiki, modelu, tekstury, nagrania ani fontu z sieci: cała geometria, światło, faktury, dźwięki i drony są liczone w przeglądarce (Canvas 2D, SVG, CSS, WebAudio). Nie ma też serwera, konta ani zależności runtime — postęp (rekordy i liczba partii) zostaje w `localStorage` tego urządzenia. + +Wejdź na półkę, wybierz pozycję, przeczytaj wstęp pod znakiem `?` w prawym górnym rogu. `Esc` zawsze wraca na półkę. --- -## Co działa teraz +## I · Centrala -### Strona główna (v0.7) +**Analogowy thriller szpiegowski · jedno pomieszczenie · dedukcja** -Uporządkowany ekran platformy: więcej ikon, mniej tekstu, żadnych -nachodzących na siebie bloków. Jasny motyw jest domyślny, ciemny nadpisuje -zmienne kolorów. Klimat horroru pojawia się wyłącznie w kafelku gry. +Noc, lata pięćdziesiąte, górski kurort odcięty przez lawinę. Siedzisz na poddaszu hotelu Alpenrose przed tablicą z dwunastoma gniazdami i nie wychodzisz z pokoju przez całą partię. W hotelu ktoś zabił gościa; wśród gości są szpieg, konsul, politycy i przestępcy. Jedynym oknem na świat jest centrala. -- górny pasek: wyszukiwarka (`/` focus), ikony dłoni / motywu / jakości, - przycisk konta, awatar i **Lobby**; na wąskich ekranach etykiety znikają, - więc pasek nigdy nie wypycha strony w bok, -- lewa nawigacja z 8 ikonami (Start, Światy, Assety, Postać, Edytor, - Samouczek, Kamerka, Kopiuj logi), panel **„Na żywo”** i lista graczy — - wyłącznie prawdziwe Presence, -- hero z podglądem awatara 3D i trzema chipami: konto wymagane / dłonie 1:1 / - zero botów, -- pasek liczb: **120 assetów · 9 kategorii · 1 grywalny świat · 0 botów**, -- baner *Demonofobia*, „Ostatnio”, szeroki kafelek gry i kafelek parku Lobby, -- **Biblioteka assetów**: 9 kafli kategorii + modal z przeszukiwalną listą - 120 modeli, ich licencją i rozmiarem, -- panel postaci (nick, akcent, edytor) i cztery ikonowe kafle „Na start”, -- „Trzy kroki do gry” zamiast ściany tekstu. +* **Przepinaj kable.** Kliknij dwa gniazda albo przeciągnij między nimi wtyczkę. Dzwoniący abonent ma pulsującą lampkę. Możesz też **wymusić rozmowę między dowolną parą** — np. połączyć sprawcę z posterunkiem albo z kolejną ofiarą. Taki mostek tworzy nową rozmowę, dodaje trop i zmienia bieg nocy, ale zostawia słyszalny klik. +* **Podsłuchuj ostrożnie.** Dźwignia podsłuchu odsłania kolejne zdania i zapisuje tropy w notesie. Każde przełączenie może wygenerować klik: szpieg słyszy go najlepiej. Podejrzenia rosną do 100% — wtedy goście odcinają centralę. +* **Nagrywaj i montuj.** Aktywną linię nagrywasz na szpulę, dwie szpule tniesz i sklejasz w nową taśmę, a potem puszczasz ją wybranemu gościowi. Policja dostaje patrol, sprawca słyszy własny głos, szpieg przestaje ufać wspólnikowi, świadek dopisuje zeznanie. +* **Zarządzaj hotelem.** Winda, prąd na III piętrze, światła w holu, kocioł. Zablokowanie windy lub odcięcie prądu ratuje drugą ofiarę; wyłączenie kotła wyostrza słuch wszystkich gości. +* **Złóż raport.** Wskaż sprawcę, motyw i dowód rzeczowy. Wynik zależy od tropów, uratowanej ofiary, podejrzeń i tego, czy udało Ci się zdemaskować sprawcę taśmą. -Wybór motywu zapisuje się w `localStorage` (`rejplay:theme`). Skrypt motywu i -krytyczny CSS są w ``, więc nie ma FOUC; ekran startowy zasłania scenę do -momentu załadowania modeli. +Noc trwa 120 sekund (1 s = 2 minuty gry) w trzech aktach; raport możesz złożyć wcześniej. -### Samouczek +## II · Pasożyt -Ośmiokrokowy modal (strona główna, lewy pasek, baner, start gry). Nawigacja: -`Dalej` / `Wstecz`, kropki, strzałki, `Esc`. Ilustracje to czysty CSS i tekst. +**Biologiczny city-builder · żywa mapa · balans chciwości** -Kroki: ruch z przechwyconym kursorem (pełny obrót 360°) → sześć narzędzi na -pasie → skanowanie `E` → foto-dowód `P` (bez pliku) → nalot → notatnik `J` → -raport i wyjście. +Twoja kolonia rośnie wewnątrz stworzenia wielkości kontynentu, które wędruje przez jałowy świat. Nie ma stali, betonu ani prądu: wapń pochodzi z kości, tlen i hormony z krwi i narządów, biomasa z tkanki. Mapa nie jest terenem — oddycha, ma tętno, zapalenie, gorączkę i może umrzeć. -### Edytor postaci +* **Osiem struktur**, każda z kosztem, utrzymaniem i kosztem biologicznym: kopalnia wapnia tylko na kości, pompa na naczyniu, gruczoł na narządzie, farma na tkance, łata na ranie, węzeł termoregulacji na węźle nerwowym, gniazdo i wieża immunosupresyjna. +* **Zapalenie rozlewa się** po sąsiednich polach, a jego średnia podnosi gorączkę. Powyżej 39 °C gospodarz traci zdrowie co cykl. +* **Układ odpornościowy** wysyła leukocyty, które idą po najbliższy budynek i niszczą go jak infekcję. Wieże rozpylają immunosupresant, wyciszają zapalenie i zabijają krwinki w promieniu dwóch pól. +* **Zdarzenia co 22 cykle**: drapieżnik z zewnątrz rozrywa ścianę ciała, skok odporności, gorączka nocna. Decyzja zamraża symulację — nie da się jej przeczekać. +* **Cel:** 240 cykli symbiozy. Śmierć gospodarza kończy grę, więc agresywna rozbudowa jest najkrótszą drogą do przegranej. -Zmiany widać natychmiast w podglądzie, lobby **i w dłoniach first-person**; -zapisują się w `localStorage` (`rejplay:appearance`). Przeciąganie obraca samą -postać — kamera i światła stoją nieruchomo. +Sterowanie: paleta → klik na polu, prawy przycisk rozwiązuje strukturę, `⏸` / `1×` / `2×` / `4×` zmieniają tempo. -| Grupa | Opcje | -| --- | --- | -| Sylwetka | **13 postaci Rocketbox: 9 męskich + 4 żeńskie** (Marek…Gustaw, Alicja, Ewa, Nina, Iza) | -| Odcień skóry | 6 ciepłych tonów; **domyślnie „Średnia”** dla wszystkich postaci i dłoni w grze | -| Kolor włosów | czarny, ciemny brąz, brąz, blond, rudy, siwy | -| Ubranie | **8 gotowych strojów**, każdy w wariancie męskim i damskim | - -**Naprawione błędy wszystkich postaci żeńskich.** Damskie i męskie korpusy -Rocketbox mają **różny układ UV** (u kobiet ręce są u góry atlasu, nogi na dole). -Wcześniej każdy strój nakładał teksturę męską, co dawało „pończochy z rękawów”, -skórzane łaty na łydkach i czarne plamy na łokciach. Teraz `OutfitDef` ma -`bodyFemale`, a `src/human.ts` wybiera wariant dla płci modelu. Poprawiono też -maskę fryzury: jasny blond był wcześniej klasyfikowany jako skóra, więc głowa -wyglądała na łysą. Maska rozróżnia odcień czerwono-różowy (skóra) od żółtego -(włosy) i ogranicza przebarwienie strefami UV, zostawiając oczy, usta i kartę -oka. - -**Kolor skóry obejmuje dłonie.** Odkryta skóra jest w teksturze *body*, nie -tylko *head*. `bodyTexture()` przelicza ją pod wybrany odcień z płynnym -przejściem (bez „opasek” na przedramieniu), zachowując cieniowanie i paznokcie. -Ubrania nie są przebarwiane — środek atlasu jest wyłączony z maski. - -**Ubrania nie są rzeźbione ręcznie.** Podmieniamy wyłącznie oryginalne tekstury -korpusu (`*_body_color.jpg` + `*_body_normal.jpg`) z innych wariantów Rocketbox. -Szkielet, UV, głowa i fryzura zostają nietknięte. Wcześniejsza próba dorabiania -geometrii (`src/garments.ts`) została usunięta. - -### Śledzenie dłoni 1:1 (kamerka, nie tryb kamery w grze) - -`Dłonie 1:1` uruchamia **`getUserMedia` + MediaPipe Hands** i rysuje szkielet -obu dłoni na `` nad podglądem. - -**To nie jest rozpoznawanie gestów ani odtwarzanie animacji.** Model zwraca 21 -punktów 3D na dłoń; `src/hand-pose.ts` liczy z nich **kąty zgięcia 15 stawów** -(5 palców × 3 ogniwa), rozstawienie, roll, pitch, yaw oraz **pełną pozycję dłoni -w 3D**. `src/human.ts` odwzorowuje kąty na kości `Bip01_*_Finger*`, a pozycję -na `UpperArm` / `Forearm` awatara; `src/view-hands.ts` prowadzi dłonie -first-person. Poruszenie jednym palcem przed kamerą porusza tym samym palcem -awatara — z wygładzaniem, bez cooldownu, bez „wykrytego gestu”. - -**Pozycja 3D (v0.5).** Kadr kamerki jest mapowany liniowo na przestrzeń przed -kamerą: `x` steruje poziomem w całym kadrze (dłoń w prawo → dłoń w prawo, -bez odwrócenia lustra), `y` wysokością, a `z` głębokością wyliczoną z rozmiaru -dłoni (odległość nadgarstek → nasada palca środkowego; MediaPipe `lm.z[0]` jest -względne i nie nadaje się do tego celu). Dłonie pierwszoosobowe wędrują po -całym ekranie, a `presence` wygładza powrót do spoczynku, gdy dłoń zniknie. -W pionie zakres jest celowo ograniczony do dolnej połowy kadru: przy pełnym 1:1 -do góry wjeżdżałoby całe przedramię awatara i zasłaniało widok. - -Wygładzanie pozycji jest **adaptacyjne**: szybki ruch skraca czas tłumienia -(brak „gumy”), a bezruch wydłuża go i wycina szum detekcji. Suwak -**„Odpowiedź pozycji 3D”** w panelu kamerki pozwala wybrać między -`Płynnie` a `Bardzo 1:1` (`rejplay:hand-responsiveness`). Panel pokazuje też -na żywo wartości `x / y / z` obu dłoni. - -**Dłonie first-person to oryginalne dłonie awatara.** Nie ma już osobnego -proceduralnego viewmodelu z kapsułek. `AvatarViewHand` klonuje rigowany -`SkinnedMesh` wybranej postaci, wycina z niego trójkąty przypisane do kości -przedramienia / nadgarstka / palców, ustawia go przed kamerą i steruje tymi -samymi 15 stawami. Skóra, paznokcie, rękawy i proporcje palców są identyczne z -postacią w lobby i zmieniają się razem z edytorem. Jeśli GLB nie wczyta się -wcale, działa lekki `FallbackViewHand`. - -MediaPipe: `maxNumHands: 2`, `modelComplexity: 1` (0 na słabym sprzęcie), pliki WASM/dane leżą lokalnie -w `public/mediapipe/` (zero CDN, zero danych opuszcza przeglądarkę). W grze panel -kamerki się chowa, ale **tracking działa dalej**. To **nie** jest wirtualny -aparat / tryb kamery (`C` usunięty); `P` zapisuje foto-dowód wyłącznie w -pamięci / notatniku — **bez PNG, bez pobierania, bez ukrytego downloadu**. - -**Realizm ruchu dłoni (v0.4).** Surowe kąty z 21 punktów przechodzą przez -wiązadłowy model palca: DIP jest częściowo sprzężony z PIP, sąsiednie palce -lekko pociągają się przy zaciskaniu, a kciuk ma własne osie i limity. Do tego -`HandAnimator` używa **SmoothDamp** (stabilny nawet przy 2–3 klatkach na -sekundę), mikro-drżenia przy bezruchu, opóźnienia nadgarstka i chwytu -spoczynkowego, gdy dłoń znika z kadru — nie ma już zamrożonej ani nagle -otwartej dłoni. Narzędzie i dłonie reagują z opóźnieniem na obrót głowy, skan -i bieg. - -### Płynność, telefony i tablety (v0.4) - -`src/performance.ts` mierzy średni czas klatki (EMA, z histerezą i ignorowaniem -przerw > 250 ms po powrocie z innej karty) i dobiera profil: `oszczędna` -(DPR ≤ 1, bez cieni, bez kurzu/ziarna, limit 45 fps, bez MSAA), `wysoka` i -`auto`. W górnym pasku **„Jakość”** można wymusić profil; wybór zapisuje się w -`localStorage` (`rejplay:quality`). Słabe urządzenia startują bez antyaliasu i z -lżejszą kamerą MediaPipe (480×360 / 24 fps / `modelComplexity: 0`). - -Żeby nie było przycięć: - -- brak alokacji w pętli (wspólne wektory i kwaterniony, stany animacji bez - tworzenia obiektów na klatkę), -- `preserveDrawingBuffer: false` — żaden kadr nie jest zrzucany do pliku, -- `IntersectionObserver` pomija render home, gdy scena jest poza ekranem, -- kurz aktualizuje się najwyżej 30×/s, migotanie świateł tylko przy zmianie - stanu, 8 rekwizytów CC0 ładuje się równolegle jednym `Promise.all()`, -- bufor, cienie i materiały zmieniają się wyłącznie przy zmianie profilu. - -**Dotyk:** `body.has-touch` chowa pasek narzędzi dla klawiatury (dubluje -przyciski), a sześć przycisków akcji ma min. 44×44 px (tablety 52 px) i obsługuje -przytrzymanie (`Bieg`, `Skanuj`). Uwzględniamy `env(safe-area-inset-*)`, -`100dvh` i `touch-action: none`; osobne breakpointy dla pionu, poziomu -≤ 560 px i tabletu w pionie trzymają HUD w górnych rogach, a joystick i przyciski -na dole. Viewmodel dostosowuje FOV, rozstaw i skalę dłoni do proporcji ekranu — -na wąskim telefonie dłonie nie znikają poza kadrem ani nie zasłaniają połowy -ekranu. Hybrydowe wykrywanie dotyku obsługuje też tablety z myszą. - -### Lobby first-person i dłonie przed oczami (v0.5) - -Lobby ma teraz **widok z oczu** jako domyślny: kamera na wysokości 1,62 m, -własna sylwetka ukryta (dla innych graczy w Presence jest widoczna normalnie), -a przed oczami widać **te same rigowane dłonie awatara** co w grze — bez -narzędzia, z rozluźnionym chwytem. `src/first-person-hands.ts` trzyma osobną, -lekką scenę viewmodelu (własne światło, czysty Z-buffer), rysowaną przez ten -sam renderer po scenie lobby. Kamerka prowadzi tu dłonie w całym kadrze, -więc wrażenie jest bliskie VR, ale bez gogli. - -`V` lub przycisk **„Widok: z oczu / 3D”** wraca do dawnej kamery za plecami -(gdzie widać całą postać i ruchy ramion). Wybór zapisuje się w -`rejplay:lobby-view`. Zdjęcie `P` w FP składa scenę lobby i viewmodel w jedno -PNG. Portal działa w obu widokach: przy wolnym kursorze klika się go myszą, -a przy przechwyconym — celownikiem z samego środka ekranu (podświetla się, -gdy trafia w portal). - -### Mysz, dwa monitory i pełny obrót 360° (v0.5) - -Bez przechwycenia kursora ruch myszy zatrzymywał się na krawędzi okna: na dwóch -monitorach kursor uciekał na drugi ekran, przeglądarka przestawała wysyłać -`movementX/Y` i nie dało się obrócić o pełny kąt. Teraz po starcie rundy gra -(prosząc o `unadjustedMovement`, z fallbackiem na zwykły lock) przejmuje kursor, -a delty są nieograniczone krawędzią ekranu — działa też w Lobby FP po jednym -kliku w widok 3D. - -- pierwsze 1–2 zdarzenia `mousemove` po locku są ignorowane (skok - „wyśrodkowania” kursora potrafił obrócić kamerę o pół obrotu), -- `Esc` tylko zwalnia kursor, dzięki czemu HUD, narzędzia i karty zjaw w - notatniku są klikalne; ponowne `Esc` wychodzi z gry, -- `J` / `Tab` otwierając notatnik zwalnia kursor, zamykając — przejmuje go - z powrotem; ekran wyniku i wyjście zawsze zwalniają blokadę, -- suwak w intro oraz `[` / `]` zmieniają czułość myszy w każdej chwili - (`rejplay:look-sensitivity`, 0,6×–2×), -- gdy przeglądarka odmówi locka (np. osadzona ramka bez `allow="pointer-lock"`), - podpowiedź tłumaczy fallback: przytrzymanie lewego przycisku i trzymanie - kursora przy krawędzi okna daje **ciągły** obrót oparty o zegar ścienny, - więc nawet przy 2 FPS nie zwalnia razem z animacją. - -### Lobby i multiplayer - -**Lobby to park, nie betonowy plac.** Jasne niebo z gradientem, słońce z cieniami, -trawnik, okrągła alejka z czterema dojściami, altana z zegarem słonecznym, -osiem klombów, oczko wodne, drewniana tablica „Lobby” i brama z okładką gry. -Gotowe GLB dokładają drzewa, żywopłoty, ławki, latarnie, stojak na rowery, -drogowskaz i fontannę. W altanie stoi **galeria trzech nowych assetów** -(aparat, boombox, sofa) na postumentach — podgląd biblioteki w silniku gry. -Żadnych postaci udających graczy: ludzkie modele są w katalogu, nie w parku. -`Demonofobia · graj` uruchamia dochodzenie. Lobby nie jest osobną grą. - -- **prawdziwi gracze**: pozycja, obrót, nick, kolor i wygląd awatara przez - Supabase **Realtime Presence**; obcy awatary są rigowanymi postaciami 3D, - wygładzanymi interpolacją, -- **wspólny czat** zapisywany w `rejplay_chat` i dostarczany przez - `postgres_changes` (historia 12 ostatnich wiadomości), -- **zero botów demonstracyjnych** — lista graczy to wyłącznie Presence; bez - połączenia z serwerem nie pokazujemy nawet siebie jako „online” i ponawiamy - łączenie (nie ma trybu offline), -- **drużyny (party)**: utwórz kod, wyślij link, czat i Presence zawężają się - do `rejplay:party:`, a „Rozpocznij razem” wysyła broadcast do całej ekipy, -- `WASD` / strzałki, `Spacja`, przeciąganie myszą lub pointer lock, dotyk, -- `V` — pierwszoosobowy widok z oczu ↔ kamera trzecioosobowa, -- `E` / **Machaj** — emote bez VR, wysyłany jako broadcast `wave` na kanał - Lobby/drużyny (limit 1,2 s); u odbiorców animuje awatara nadawcy i pokazuje - powiadomienie. Odrzucamy zdarzenia własne, puste, nieprawidłowe i starsze - niż 15 s. Kamerka steruje palcami **i pozycją 3D**. - -### Supabase `rejson-widgets` - -Projekt `hzbqzfimtroixnldzjeq` (eu-central-1). W frontendzie używamy -**wyłącznie klucza publishable/anon** — `service_role` i sekrety nigdy nie -trafiają do kodu ani do bundla. Konfigurację można nadpisać zmiennymi -`VITE_SUPABASE_URL` / `VITE_SUPABASE_PUBLISHABLE_KEY` (patrz `.env.example`). - -| Tabela | Zawartość | -| --- | --- | -| `rejplay_profiles` | nick, kolor, wygląd JSON, `client_id` z localStorage | -| `rejplay_chat` | wiadomości Lobby (max 180 znaków) | -| `rejplay_events` | `game_open`, `game_start`, `game_win`, `game_fail`, `photo`, `lobby_enter` | -| `rejplay_parties` | kod, nazwa, lider (UUID konta głównej strony), wygaśnięcie | -| `rejplay_party_members` | `client_id`, `user_id`, nick, kolor, wygląd, `last_seen_at` | - -**Konta i brama logowania (v0.7):** RejPlay nie ma własnej bazy haseł, ale ma -własny, ładny ekran **logowania i rejestracji** (`#auth`, `src/auth-gate.ts`). -Używa tych samych RPC co `/logowanie` na głównej stronie: -`community_login(p_username, p_pin)` / `community_register(...)` / -`community_whoami(p_token)` i tego samego klucza -`rejson_community_session_v1` w `localStorage`. Dzięki temu jedno konto działa -na całym `rejson59.github.io`, a formularz działa także w dev na localhost. -Bez zalogowania lub rejestracji **nie można zagrać** — `enterLobby()` i -`openGame()` wołają `AuthGate.require()`, więc żaden przycisk, skrót ani portal -3D nie obejdzie blokady. Po udanym logowaniu brama wznawia klikniętą akcję. -RPC `community_*` używają dokładnie tego publicznego klucza `anon` co -`/js/logowanie.js` (`COMMUNITY_ANON_KEY`); Presence, czat i zdarzenia używają -klucza publishable z `src/online.ts`. W `` wciąż ładujemy -`/js/rejson-account.js?v=1` tylko na wspólnym originie — wtedy sesję -potwierdza gotowy bridge zamiast bezpośredniego RPC. Każda funkcja drużyny dodatkowo waliduje token po stronie serwera -przez `community_session_user(p_token)`. Token nigdy nie trafia do URL, czatu, -Presence, logów ani `postMessage`. Tabele drużyn mają włączone RLS i **brak -polityk dla anon** — dostęp wyłącznie przez funkcje `SECURITY DEFINER`. - -Migracje (RLS, indeksy, publikacja `supabase_realtime`, funkcje RPC) leżą w -`supabase/migrations/20261102190000_rejplay_multiplayer.sql`, -`20261104190000_rejplay_parties.sql` oraz -`20261104191000_rejplay_party_auth_fix.sql` i zostały zastosowane w projekcie. **Jeśli baza jest niedostępna**, wskaźnik pokazuje -„Ponawiam połączenie”, a gra nie udaje lokalnego multiplayera: czat nie -przyjmuje wiadomości bez serwera (zamiast „tylko u Ciebie" dostaje komunikat -systemowy), Presence nie pokazuje fikcyjnych graczy, a podgląd postaci -i katalog assetów wciąż działają. Rozpoczęcie gry wymaga potwierdzonego konta -(`community_whoami`), więc przy niedostępnej bazie nie można zweryfikować -sesji — zgodnie z założeniem „bez konta nie gramy". Istniejąca, ważna sesja -jest odczytywana ponownie po powrocie połączenia. - -### Demonofobia — jedyna grywalna gra - -**Oryginalna, autorska gra dochodzeniowa.** Nie używamy nazwy *Phasmophobia*, -jej duchów, lore'u, interfejsu, dźwięków ani assetów Kinetic Games. Wszystkie -nazwy, opisy zjaw, dowody, dźwięki (WebAudio) i geometria domu są napisane od -zera. Podobieństwo dotyczy **mechaniki gatunku**: first-person, ręczne zbieranie -dowodów, dedukcja typu zjawy, notatnik, narastające zagrożenie, ucieczka. - -Pierwszoosobowe dochodzenie przy Wrzosowej 12: cztery pokoje, korytarz, -podwórko z **samochodem-bazą** (start, monitoring, bezpieczna strefa, regeneracja -nerwów, przerwanie nalotu), latarka, **sześć narzędzi**, **sześć śladów**, -**dziesięć zjaw** i 16 miejsc z poszlakami. - -**Wygląd i realizm:** - -- proceduralne tekstury z canvas (deski ze słojami, tapeta z zaciekami, - tkanina, glazura, dywan) z mapami wypukłości, -- meble z zaokrąglonych krawędzi i toczenia: stół, krzesła, regał z książkami, - fotel, łóżko, szafki, zlew, czajnik, wieszak, lustro, zegar, obrazy, kurz, -- **25 gotowych, fotorealistycznych assetów CC0 / MIT** w `public/assets/props/`: - oryginalne 8 rekwizytów Poly Haven plus 17 modeli z GridFall (drzewa, ławki, - latarnie, fontanna, donice, hydrant, bariera, kontener, klimatyzator, - `van_white.glb` jako baza). Tekstury 1K są osadzone w GLB — bez CDN, bez - połączenia. Noty: `LICENSE-polyhaven-cc0.txt`, `LICENSE-gridfall-MIT.txt`, - -- statyczna geometria łączona wg materiału (`mergeGeometries`), -- latarka z cieniami, księżyc, migotanie, mgła, winyeta i ziarno, -- **oryginalne dłonie awatara** first-person na osobnej scenie z własnym - światłem + narzędzie z żywym ekranem LCD, sondą/anteną/mikrofonem, -- ruch: bezwładność, wygładzone rozglądanie, kołysanie głowy, przechył, - kroki zsynchronizowane z krokiem, FOV przy biegu, podniesienie narzędzia przy - skanie; na dotyku czułość rozglądania jest wyższa, bo palec pokonuje krótszą - drogę niż mysz. - -| Element | Opis | -| --- | --- | -| Pas na biodrach | 6 slotów 3D przypiętych do kamery + te same sloty w HUD; weź `1`–`6`, odłóż `Q`, wskaż palcem lub spojrzeniem | -| Narzędzia | `1` Termometr · `2` Miernik pola · `3` Rejestrator · `4` Latarka UV · `5` Czujnik ruchu · `6` Kamera nocna | -| Gesty | zaciśnięcie palców = chwyć / użyj, dłoń otwarta ~0,9 s = odłóż, palec wskazujący = wybierz slot | -| Skan | przytrzymaj `E` (albo zaciśnij palce) przez ~1,2 s — `wykryto` albo `brak śladu` | -| Dowody | Zimny punkt, Zakłócenia pola, Szepty, Odciski dłoni, Kroki bez źródła, Cienie w kadrze | -| Zjawy | Mara, Iskra, Szron, Cisza, Popiół, Nić, Odcień, Kurz, Kompas, Głębia | -| Foto-dowód | `P` zaznacza dowód w notatniku; **nie zapisuje PNG ani nie pobiera pliku** | -| Nalot | co ~30–48 s; wyjście frontowymi drzwiami, `P` lub przeczekanie kończy go | -| Nerwy | maleją z czasem, szybciej po ciemku, podczas zjawisk i nalotu | -| Raport | `J` / `Tab` → wybierz zjawę; pomyłka kosztuje 24 nerwy | - -Sterowanie: `WASD` + mysz z **pointer lockiem** (nieograniczone `movementX/Y`, -pełny obrót 360°), `Shift` bieg, `F` latarka, `Esc` najpierw zwalnia kursor, -ponowne `Esc` / `← Wyjdź` wychodzi z gry. Na dotyku: joystick + `Skanuj`, `Bieg`, -`Foto`, `Narzędzie`, `Notatnik`, `Latarka` (przyciski trzymane palcem mają -wizualny stan wciśnięcia). - -### Laboratorium kamery — konfiguracja wstępna - -Dostęp z lewego paska, ekranu startowego gry i przycisku w górnym pasku. -Jeden element `