Концепції, цінність і типові сценарії
Концепції, цінність і типові сценарії
Preparing Ring content
Preparing Ring content
Preparing Ring content
Використовуйте вкладки Founder / Developer на бічній панелі документації, щоб фільтрувати цю сторінку. Видимість у бічній панелі налаштовується в lib/docs/audience-curated-docs.ts.
Ring постачає мініігри для друзів, які повторно використовують fan-out Tunnel, набір інтерактивних повідомлень і IA публічного профілю — без GameConductor, паралельного стека повідомлень чи ставок.
| Поверхня | Шлях |
|---|---|
| Маркетплейс | /games |
| Мініапка | /games/[slug]?session= |
| Публічна доступність | /{username}/games |
| Керування власника | /profile/games (Member) |
| Віджет чату | тип повідомлення / metadata.kind = game_request |
| Вхідний банер | MessagesShell і layout /games |
Доступні назви: хрестики-нулики · шахи · шашки. Тексти каталогу локалізуються через locales/*/modules/games.json (modules.games); англійські рядки в catalog.ts залишаються резервним варіантом для FCM/історії.
Учасники швидше зближуються, коли можуть грати, не залишаючи ваш Ring. Виклики надходять як віджети чату та як глобальний IncomingGameBanner у Messages і /games — тож запрошення не губляться за межами гілки месенджера.
/{username}/games/profile/games і опублікувати доступність (шахи / шашки / хрестики-нулики)./games у кожній локалі — назви каталогу мають локалізуватися (EN/UK/RU/ES/DE).Залежить від: game_request живе в наборі інтерактивних повідомлень і прямих розмовах.
Той самий процес: спільний м’ютекс дзвінка/гри; маршрут ICE повторно використовується для підказок ходів DataChannel.
Залежить від: ACL conversation:*, game:{sessionId} і fan-out games:incoming.
Передумова: привілеї Member керують /profile/games і створенням викликів.
Використовуйте вкладки Founder / Developer на бічній панелі документації, щоб фільтрувати цю сторінку. Видимість у бічній панелі налаштовується в lib/docs/audience-curated-docs.ts.
Ring постачає мініігри для друзів, які повторно використовують fan-out Tunnel, набір інтерактивних повідомлень і IA публічного профілю — без GameConductor, паралельного стека повідомлень чи ставок.
| Поверхня | Шлях |
|---|---|
| Маркетплейс | /games |
| Мініапка | /games/[slug]?session= |
| Публічна доступність | /{username}/games |
| Керування власника | /profile/games (Member) |
| Віджет чату | тип повідомлення / metadata.kind = game_request |
| Вхідний банер | MessagesShell і layout /games |
Доступні назви: хрестики-нулики · шахи · шашки. Тексти каталогу локалізуються через locales/*/modules/games.json (modules.games); англійські рядки в catalog.ts залишаються резервним варіантом для FCM/історії.
Учасники швидше зближуються, коли можуть грати, не залишаючи ваш Ring. Виклики надходять як віджети чату та як глобальний IncomingGameBanner у Messages і /games — тож запрошення не губляться за межами гілки месенджера.
/{username}/games/profile/games і опублікувати доступність (шахи / шашки / хрестики-нулики)./games у кожній локалі — назви каталогу мають локалізуватися (EN/UK/RU/ES/DE).Залежить від: game_request живе в наборі інтерактивних повідомлень і прямих розмовах.
Той самий процес: спільний м’ютекс дзвінка/гри; маршрут ICE повторно використовується для підказок ходів DataChannel.
Залежить від: ACL conversation:*, game:{sessionId} і fan-out games:incoming.
Передумова: привілеї Member керують /profile/games і створенням викликів.
Використовуйте вкладки Founder / Developer на бічній панелі документації, щоб фільтрувати цю сторінку. Видимість у бічній панелі налаштовується в lib/docs/audience-curated-docs.ts.
Ring постачає мініігри для друзів, які повторно використовують fan-out Tunnel, набір інтерактивних повідомлень і IA публічного профілю — без GameConductor, паралельного стека повідомлень чи ставок.
| Поверхня | Шлях |
|---|---|
| Маркетплейс | /games |
| Мініапка | /games/[slug]?session= |
| Публічна доступність | /{username}/games |
| Керування власника | /profile/games (Member) |
| Віджет чату | тип повідомлення / metadata.kind = game_request |
| Вхідний банер | MessagesShell і layout /games |
Доступні назви: хрестики-нулики · шахи · шашки. Тексти каталогу локалізуються через locales/*/modules/games.json (modules.games); англійські рядки в catalog.ts залишаються резервним варіантом для FCM/історії.
Учасники швидше зближуються, коли можуть грати, не залишаючи ваш Ring. Виклики надходять як віджети чату та як глобальний IncomingGameBanner у Messages і /games — тож запрошення не губляться за межами гілки месенджера.
/{username}/games/profile/games і опублікувати доступність (шахи / шашки / хрестики-нулики)./games у кожній локалі — назви каталогу мають локалізуватися (EN/UK/RU/ES/DE).Залежить від: game_request живе в наборі інтерактивних повідомлень і прямих розмовах.
Той самий процес: спільний м’ютекс дзвінка/гри; маршрут ICE повторно використовується для підказок ходів DataChannel.
Залежить від: ACL conversation:*, game:{sessionId} і fan-out games:incoming.
Передумова: привілеї Member керують /profile/games і створенням викликів.
Розмови та інтерактивні віджети (game_request у спільному наборі).
features/peer-games/hooks/use-peer-game-datachannel.ts | Оптимістичні підказки ходів (ring-peer-game-moves) |
app/_actions/peer-games.ts | Серверні дії |
app/api/conversations/[id]/game-invite/route.ts | Тонкий HTTP-аналог → createInvite |
app/api/cron/peer-game-session-expiry/route.ts | Конвеєр ProcessConductor |
app/api/tunnel/subscribe/route.ts | ACL учасників HTTP game:* |
lib/tunnel/native-ws/attach.ts | ACL учасників WS game:* (віддзеркалює HTTP) |
IncomingGameBanner | Очищення життєвого циклу games:incoming + game:{id} |
Міграція data/migrations/043_peer_games.sql:
peer_game_sessions — індекси за conversation / challenger / peer / status / slug / messageId / updated_atuser_peer_games — enabledSlugs для кожного власника (унікальне поле ownerId)| Канал | Події |
|---|---|
conversation:{id} | game:invite, game:accept, game:decline, game:resign, game:move, game:expire |
game:{sessionId} | game:accept, game:decline, game:resign, game:move, game:expire, game:dc-signal |
| інбокс користувача | games:incoming (invite, accepted, terminal) |
ACL підписки: для game:{sessionId} за замовчуванням доступ заборонено і через HTTP, і через native WS — лише учасники з БД (getSessionForParticipant). Спостереження відкладено.
Очищення банера: game:expire / game:decline / game:accept на каналі сесії, а також games:incoming із terminal або accepted (охоплює гонку підписки після запрошення).
NotificationType.GAME_REQUEST / GAME_UPDATED. Офлайн-push: після !deliveredLive зачекати приблизно 500 мс і повторно перевірити getTunnelHub().isUserConnected(peer) перед createNotification + PUSH dual-stack (FCM Admin і RFC web-push). На банері — лише м’який сигнал Web Audio; ніколи не викликайте ringtone / setPeerCallBusy. На пристроях, якими керує Chrome FCM, push_subscriptions порожній (RFC — no-op). Див. Push-сповіщення (FCM).
isUserConnected хаба локальний для процесу. Хибний офлайн FCM у multi-pod — відома залишкова проблема; спільна presence ще в беклозі.
setPeerCallBusy / setPeerGameBusy з BroadcastChannel (+ опційно navigator.locks)SET NX PX усередині createInvite (спершу claim; у разі помилки — releaseNx) — Server Action + HTTP + Play-with-me спільно використовують SSOTkey={fen}); невалідні ходи повертають falseКонвеєр peer-game-session-expiry:
| Випадок | TTL | Результат |
|---|---|---|
Осиротіла неприйнята сесія (без messageId) | ~2m | Повернення |
| Неприйняте запрошення | 15m | Відхилено + updateMessageLocked |
| Активна бездіяльність | 2h | Здано/завершено + сповіщення Tunnel |
Планувальники: Vercel */5 (vercel.json) · k3s-or CronJobs infrastructure/k3s-or/ring-platform-org/cronjob-peer-game-session-expiry.yaml (+ сусідній cronjob-close-expired-polls.yaml, /bin/sh, щоб розгортати CRON_SECRET). Застосуйте в кластері під час переведення k8s prod.
RTCDataChannel із міткою ring-peer-game-moves (ordered) + fetchIceServers. Конверти peer — це підказки; validateMove / Tunnel game:move / БД залишаються SSOT. Відхилення → відкат до останнього авторитетного hydrate. Перезапуск ICE у нестабільних мережах — у беклозі; шлях Tunnel залишається резервним.
createGameRequest · acceptGameRequest · declineGameRequest · resignPeerGameAction · submitPeerGameMoveAction · getPeerGameSessionAction · updateEnabledGamesAction · challengeUserToGameAction
Структурна перевірка: npx tsx scripts/smoke-peer-games-soak.cts. Модульні тести плагінів у __tests__/features/peer-games/plugins.test.ts. Чекліст live E2E для Member: AI-CONTEXT/concepts/peer-games-live-e2e-soak-2026-07-24.json (поширення empire Reggie заблоковане до погодження).
Наступний крок: публічний /{username}/games поруч із Player і Gallery.
createGameRequest / POST …/game-invite / challengeUserToGameAction
→ PeerGameService.createInvite (SSOT)
→ Redis SET NX claim peer-game:invite:{from}:{to}:{slug} (Map fallback)
→ create session (peer_game_sessions) FIRST
→ send game_request message → link messageId
→ on failure: delete session + releaseNx
→ publishToChannel(conversation:…, game:invite)
→ publishToUserTunnel(peer, games:incoming)
→ if !deliveredLive: sleep ~500ms; FCM GAME_REQUEST only if still !isUserConnected
accept / decline / resign / submitMove
→ participant ACL + plugin validateMove/applyMove
→ updateMessageLocked on linked widget metadata
→ game:* on game:{sessionId} AND conversation:{id}
→ decline/expire also publish terminal on games:incoming (banner clear)
client mini-app
→ hydrate DB SSOT → useTunnelChannel(game:{id})
→ optional RTCDataChannel optimistic hints (fetchIceServers); server remains SSOTРозмови та інтерактивні віджети (game_request у спільному наборі).
features/peer-games/hooks/use-peer-game-datachannel.ts | Оптимістичні підказки ходів (ring-peer-game-moves) |
app/_actions/peer-games.ts | Серверні дії |
app/api/conversations/[id]/game-invite/route.ts | Тонкий HTTP-аналог → createInvite |
app/api/cron/peer-game-session-expiry/route.ts | Конвеєр ProcessConductor |
app/api/tunnel/subscribe/route.ts | ACL учасників HTTP game:* |
lib/tunnel/native-ws/attach.ts | ACL учасників WS game:* (віддзеркалює HTTP) |
IncomingGameBanner | Очищення життєвого циклу games:incoming + game:{id} |
Міграція data/migrations/043_peer_games.sql:
peer_game_sessions — індекси за conversation / challenger / peer / status / slug / messageId / updated_atuser_peer_games — enabledSlugs для кожного власника (унікальне поле ownerId)| Канал | Події |
|---|---|
conversation:{id} | game:invite, game:accept, game:decline, game:resign, game:move, game:expire |
game:{sessionId} | game:accept, game:decline, game:resign, game:move, game:expire, game:dc-signal |
| інбокс користувача | games:incoming (invite, accepted, terminal) |
ACL підписки: для game:{sessionId} за замовчуванням доступ заборонено і через HTTP, і через native WS — лише учасники з БД (getSessionForParticipant). Спостереження відкладено.
Очищення банера: game:expire / game:decline / game:accept на каналі сесії, а також games:incoming із terminal або accepted (охоплює гонку підписки після запрошення).
NotificationType.GAME_REQUEST / GAME_UPDATED. Офлайн-push: після !deliveredLive зачекати приблизно 500 мс і повторно перевірити getTunnelHub().isUserConnected(peer) перед createNotification + PUSH dual-stack (FCM Admin і RFC web-push). На банері — лише м’який сигнал Web Audio; ніколи не викликайте ringtone / setPeerCallBusy. На пристроях, якими керує Chrome FCM, push_subscriptions порожній (RFC — no-op). Див. Push-сповіщення (FCM).
isUserConnected хаба локальний для процесу. Хибний офлайн FCM у multi-pod — відома залишкова проблема; спільна presence ще в беклозі.
setPeerCallBusy / setPeerGameBusy з BroadcastChannel (+ опційно navigator.locks)SET NX PX усередині createInvite (спершу claim; у разі помилки — releaseNx) — Server Action + HTTP + Play-with-me спільно використовують SSOTkey={fen}); невалідні ходи повертають falseКонвеєр peer-game-session-expiry:
| Випадок | TTL | Результат |
|---|---|---|
Осиротіла неприйнята сесія (без messageId) | ~2m | Повернення |
| Неприйняте запрошення | 15m | Відхилено + updateMessageLocked |
| Активна бездіяльність | 2h | Здано/завершено + сповіщення Tunnel |
Планувальники: Vercel */5 (vercel.json) · k3s-or CronJobs infrastructure/k3s-or/ring-platform-org/cronjob-peer-game-session-expiry.yaml (+ сусідній cronjob-close-expired-polls.yaml, /bin/sh, щоб розгортати CRON_SECRET). Застосуйте в кластері під час переведення k8s prod.
RTCDataChannel із міткою ring-peer-game-moves (ordered) + fetchIceServers. Конверти peer — це підказки; validateMove / Tunnel game:move / БД залишаються SSOT. Відхилення → відкат до останнього авторитетного hydrate. Перезапуск ICE у нестабільних мережах — у беклозі; шлях Tunnel залишається резервним.
createGameRequest · acceptGameRequest · declineGameRequest · resignPeerGameAction · submitPeerGameMoveAction · getPeerGameSessionAction · updateEnabledGamesAction · challengeUserToGameAction
Структурна перевірка: npx tsx scripts/smoke-peer-games-soak.cts. Модульні тести плагінів у __tests__/features/peer-games/plugins.test.ts. Чекліст live E2E для Member: AI-CONTEXT/concepts/peer-games-live-e2e-soak-2026-07-24.json (поширення empire Reggie заблоковане до погодження).
Наступний крок: публічний /{username}/games поруч із Player і Gallery.
createGameRequest / POST …/game-invite / challengeUserToGameAction
→ PeerGameService.createInvite (SSOT)
→ Redis SET NX claim peer-game:invite:{from}:{to}:{slug} (Map fallback)
→ create session (peer_game_sessions) FIRST
→ send game_request message → link messageId
→ on failure: delete session + releaseNx
→ publishToChannel(conversation:…, game:invite)
→ publishToUserTunnel(peer, games:incoming)
→ if !deliveredLive: sleep ~500ms; FCM GAME_REQUEST only if still !isUserConnected
accept / decline / resign / submitMove
→ participant ACL + plugin validateMove/applyMove
→ updateMessageLocked on linked widget metadata
→ game:* on game:{sessionId} AND conversation:{id}
→ decline/expire also publish terminal on games:incoming (banner clear)
client mini-app
→ hydrate DB SSOT → useTunnelChannel(game:{id})
→ optional RTCDataChannel optimistic hints (fetchIceServers); server remains SSOTРозмови та інтерактивні віджети (game_request у спільному наборі).
features/peer-games/hooks/use-peer-game-datachannel.ts | Оптимістичні підказки ходів (ring-peer-game-moves) |
app/_actions/peer-games.ts | Серверні дії |
app/api/conversations/[id]/game-invite/route.ts | Тонкий HTTP-аналог → createInvite |
app/api/cron/peer-game-session-expiry/route.ts | Конвеєр ProcessConductor |
app/api/tunnel/subscribe/route.ts | ACL учасників HTTP game:* |
lib/tunnel/native-ws/attach.ts | ACL учасників WS game:* (віддзеркалює HTTP) |
IncomingGameBanner | Очищення життєвого циклу games:incoming + game:{id} |
Міграція data/migrations/043_peer_games.sql:
peer_game_sessions — індекси за conversation / challenger / peer / status / slug / messageId / updated_atuser_peer_games — enabledSlugs для кожного власника (унікальне поле ownerId)| Канал | Події |
|---|---|
conversation:{id} | game:invite, game:accept, game:decline, game:resign, game:move, game:expire |
game:{sessionId} | game:accept, game:decline, game:resign, game:move, game:expire, game:dc-signal |
| інбокс користувача | games:incoming (invite, accepted, terminal) |
ACL підписки: для game:{sessionId} за замовчуванням доступ заборонено і через HTTP, і через native WS — лише учасники з БД (getSessionForParticipant). Спостереження відкладено.
Очищення банера: game:expire / game:decline / game:accept на каналі сесії, а також games:incoming із terminal або accepted (охоплює гонку підписки після запрошення).
NotificationType.GAME_REQUEST / GAME_UPDATED. Офлайн-push: після !deliveredLive зачекати приблизно 500 мс і повторно перевірити getTunnelHub().isUserConnected(peer) перед createNotification + PUSH dual-stack (FCM Admin і RFC web-push). На банері — лише м’який сигнал Web Audio; ніколи не викликайте ringtone / setPeerCallBusy. На пристроях, якими керує Chrome FCM, push_subscriptions порожній (RFC — no-op). Див. Push-сповіщення (FCM).
isUserConnected хаба локальний для процесу. Хибний офлайн FCM у multi-pod — відома залишкова проблема; спільна presence ще в беклозі.
setPeerCallBusy / setPeerGameBusy з BroadcastChannel (+ опційно navigator.locks)SET NX PX усередині createInvite (спершу claim; у разі помилки — releaseNx) — Server Action + HTTP + Play-with-me спільно використовують SSOTkey={fen}); невалідні ходи повертають falseКонвеєр peer-game-session-expiry:
| Випадок | TTL | Результат |
|---|---|---|
Осиротіла неприйнята сесія (без messageId) | ~2m | Повернення |
| Неприйняте запрошення | 15m | Відхилено + updateMessageLocked |
| Активна бездіяльність | 2h | Здано/завершено + сповіщення Tunnel |
Планувальники: Vercel */5 (vercel.json) · k3s-or CronJobs infrastructure/k3s-or/ring-platform-org/cronjob-peer-game-session-expiry.yaml (+ сусідній cronjob-close-expired-polls.yaml, /bin/sh, щоб розгортати CRON_SECRET). Застосуйте в кластері під час переведення k8s prod.
RTCDataChannel із міткою ring-peer-game-moves (ordered) + fetchIceServers. Конверти peer — це підказки; validateMove / Tunnel game:move / БД залишаються SSOT. Відхилення → відкат до останнього авторитетного hydrate. Перезапуск ICE у нестабільних мережах — у беклозі; шлях Tunnel залишається резервним.
createGameRequest · acceptGameRequest · declineGameRequest · resignPeerGameAction · submitPeerGameMoveAction · getPeerGameSessionAction · updateEnabledGamesAction · challengeUserToGameAction
Структурна перевірка: npx tsx scripts/smoke-peer-games-soak.cts. Модульні тести плагінів у __tests__/features/peer-games/plugins.test.ts. Чекліст live E2E для Member: AI-CONTEXT/concepts/peer-games-live-e2e-soak-2026-07-24.json (поширення empire Reggie заблоковане до погодження).
Наступний крок: публічний /{username}/games поруч із Player і Gallery.
createGameRequest / POST …/game-invite / challengeUserToGameAction
→ PeerGameService.createInvite (SSOT)
→ Redis SET NX claim peer-game:invite:{from}:{to}:{slug} (Map fallback)
→ create session (peer_game_sessions) FIRST
→ send game_request message → link messageId
→ on failure: delete session + releaseNx
→ publishToChannel(conversation:…, game:invite)
→ publishToUserTunnel(peer, games:incoming)
→ if !deliveredLive: sleep ~500ms; FCM GAME_REQUEST only if still !isUserConnected
accept / decline / resign / submitMove
→ participant ACL + plugin validateMove/applyMove
→ updateMessageLocked on linked widget metadata
→ game:* on game:{sessionId} AND conversation:{id}
→ decline/expire also publish terminal on games:incoming (banner clear)
client mini-app
→ hydrate DB SSOT → useTunnelChannel(game:{id})
→ optional RTCDataChannel optimistic hints (fetchIceServers); server remains SSOT