diff --git a/docs/ARCHITECTURE.md b/docs/ARCHITECTURE.md index 5fbd441..796ff5e 100644 --- a/docs/ARCHITECTURE.md +++ b/docs/ARCHITECTURE.md @@ -1251,8 +1251,22 @@ toggle — distinct from the transient gateway-reachability signal — that tint an *Offline* chip and confines play to on-device `vs_ai` games. The **offline lobby lists only those device-local games** (reconstructed by replaying the IndexedDB move journal) and its New-vs-AI entry creates one through the in-browser engine — the same game screen then drives it, the robot replying -locally; online-only affordances (the Stats tab, the friend/random options in New Game) are disabled -or hidden. A `vs_ai` hint (online and offline alike) is unlimited and wallet-free but idle-gated +locally; online-only affordances (the Stats tab, the random-opponent option) are disabled +or hidden, and New Game's *with friends* becomes the entry to **local pass-and-play (hotseat)**. +A hotseat game is a device-local **2-4 human** game built through the same engine (`hotseat` + +per-seat/host PIN locks on the record; the seats carry names but no accounts). A **mandatory host +(referee) PIN** gates the roster at creation and, in-game, the referee overrides — **skip** the +current turn, **exclude** a player (a forced seat resign, `resignSeat`) or **terminate** the game +(deleted, no winner/loser); each seat may also carry its own **optional PIN** that withholds its rack +(the board stays visible — only the tray is gated) until it is entered, so players pass the device +around freely and lock a seat only against a distrusted tablemate. The unlock is **per turn** +(re-locks on advance). PINs are a **social lock, not cryptography** — a salted SHA-256 kept only in +the device record (`lib/pin`); a 4-digit PIN is trivially brute-forceable and the racks live in +client memory regardless, so they defeat a casual glance, not a determined peek. A hotseat game is +**not `vs_ai`**: hints (the freed control becomes the host button) and chat/self-resign are gone, and +its lobby card — active *and* finished — is master-PIN-gated to delete (so the last mover cannot +instantly wipe it); a naturally finished game is saved with its result like any local game. A `vs_ai` +hint (online and offline alike) is unlimited and wallet-free but idle-gated (unlocked ~30 min into a stuck turn), and counts toward no hint statistic. The **source** reports the **seconds left** (`hint_unlock_left_seconds` on the game view) — computed by the **backend from the server clock** online (which also enforces the gate, returning `hint_locked` for an early request), diff --git a/docs/FUNCTIONAL.md b/docs/FUNCTIONAL.md index b8e60ea..bda5ac7 100644 --- a/docs/FUNCTIONAL.md +++ b/docs/FUNCTIONAL.md @@ -269,8 +269,8 @@ statistics. An installed web PWA signed in with a confirmed email can switch to a deliberate **offline mode** (Settings → Play mode). Offline, the app never touches the network: the header turns blue with an *Offline* chip, the lobby lists only the games stored on the device, and online-only surfaces are -disabled or hidden (the Stats tab; the *with friends* and *random opponent* options in New Game). -Only **vs_ai** games are playable — New Game creates a device-local game against the robot, which +disabled or hidden (the Stats tab; the *random opponent* option in New Game). +A New Game against the robot creates a device-local **vs_ai** game — it plays entirely in the browser with no backend. Local games are kept on the device, visible only in offline mode, and never sync to the account. So a game can be created and played with no connection, the app quietly preloads the dictionaries for the player's enabled variants while still online, and @@ -280,6 +280,20 @@ and waits briefly, and if they cannot be readied it stays online with a short *n (the download keeps running in the background, so a later switch is instant). The mode is device-scoped and sticky across launches. +Offline, New Game's **with friends** starts a **local pass-and-play** game for 2-4 people sharing one +device. You first set a **host (leader) password** — a 4-digit PIN entered on a lock-screen-style +keypad; until it is set the player rows stay locked. The app then asks whether you are playing too — +if so you take the first seat with your profile name. You name each player (2 to 4; add or remove +rows, where a removal asks for the leader password) and may give any seat its **own optional PIN**. +During the game the board is always visible, but a PIN-locked seat shows an **Unlock** button in place +of its rack until its owner enters the PIN — so the device passes hand to hand and only a protected +seat hides its letters; the lock returns each turn. The **leader** (the host button in the game) can, +with the leader password, **skip** the current player's turn, **remove** a player, or **end the game +early** — an early end discards the game with no winner or loser. Hints and chat are off in a +pass-and-play game. A game that finishes normally is saved to the offline lobby like any other; +deleting any pass-and-play game — running or finished — from the lobby asks for the leader password, +so no one can wipe a game the moment they have moved. + The app also **enters offline mode on its own** when it cannot reach the network. On a cold launch with no connection it switches to offline for that session; when the device is online but the gateway stays silent for a few seconds it asks first — *No connection. Switch to offline mode?*, with diff --git a/docs/FUNCTIONAL_ru.md b/docs/FUNCTIONAL_ru.md index b91b12e..a2ca461 100644 --- a/docs/FUNCTIONAL_ru.md +++ b/docs/FUNCTIONAL_ru.md @@ -274,8 +274,8 @@ e-mail) либо ввод фразы. Активные игры форфейтя Установленный веб-PWA со входом по подтверждённой почте может переключиться в осознанный **офлайн-режим** (Настройки → Режим игры). В офлайне приложение не обращается к сети: шапка синеет с меткой *Офлайн*, лобби показывает только сохранённые на устройстве игры, а сетевые поверхности -отключены или скрыты (вкладка Статистика; варианты «с другом» и «случайный соперник» в Новой игре). -Играть можно только в **vs_ai** — Новая игра создаёт локальную игру против робота, который ходит +отключены или скрыты (вкладка Статистика; вариант «случайный соперник» в Новой игре). +Новая игра против робота создаёт локальную **vs_ai**-игру — он ходит целиком в браузере без бэкенда. Локальные игры хранятся на устройстве, видны только в офлайн-режиме и никогда не синхронизируются с аккаунтом. Чтобы игру можно было создать и сыграть без связи, приложение заранее, пока ещё онлайн, подгружает словари включённых игроком вариантов и (после установки) @@ -285,6 +285,21 @@ e-mail) либо ввод фразы. Активные игры форфейтя продолжается в фоне, так что следующее переключение мгновенно). Режим привязан к устройству и сохраняется между запусками. +В офлайне «с друзьями» в Новой игре запускает **локальную игру по очереди (hotseat)** на 2–4 человек +за одним устройством. Сначала задаётся **пароль ведущего** — 4-значный PIN на клавиатуре в стиле +экрана блокировки; пока он не задан, строки игроков заблокированы. Затем приложение спрашивает, +играете ли вы сами — если да, вы занимаете первое место под именем из профиля. Вы называете каждого +игрока (от 2 до 4; строки можно добавлять и удалять, удаление спрашивает пароль ведущего) и можете +дать любому месту **свой необязательный PIN**. Во время игры доска видна всегда, но место с PIN +вместо стойки показывает кнопку **«Разблокировать»**, пока владелец не введёт PIN, — так устройство +передаётся из рук в руки, и только защищённое место прячет свои буквы; на каждом ходу замок +возвращается. **Ведущий** (кнопка ведущего в игре) может по паролю ведущего **пропустить** ход +текущего игрока, **исключить** игрока или **завершить игру досрочно** — досрочное завершение стирает +партию без победителей и проигравших. Подсказки и чат в игре по очереди отключены. Нормально +завершённая партия сохраняется в офлайн-лобби как любая другая; удаление любой игры по очереди — +идущей или завершённой — из лобби спрашивает пароль ведущего, чтобы никто не стёр партию сразу после +своего хода. + Приложение также **само включает офлайн-режим**, когда не может достучаться до сети. При холодном запуске без связи оно переходит в офлайн на текущую сессию; когда устройство онлайн, но шлюз молчит несколько секунд, оно сперва спрашивает — *Нет связи. Включить офлайн-режим?*, с выбором **Включить**