vimp-engine 0.39.0 → 0.41.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (62) hide show
  1. package/README.md +2 -2
  2. package/bin/vimp-sim.js +2 -2
  3. package/core/Cargo.toml +2 -2
  4. package/package.json +3 -4
  5. package/src/client/boot.js +11 -87
  6. package/src/client/components/controller/Vote.js +26 -0
  7. package/src/client/components/model/Controls.js +9 -1
  8. package/src/client/components/model/RoomMenu.js +1 -1
  9. package/src/client/components/model/Vote.js +202 -0
  10. package/src/client/components/view/Auth.js +3 -3
  11. package/src/client/components/view/Panel.js +176 -6
  12. package/src/client/components/view/Voice.js +6 -1
  13. package/src/client/components/view/Vote.js +71 -2
  14. package/src/client/lib/formBuilder.js +7 -6
  15. package/src/client/lib/hostVoteCommand.js +2 -2
  16. package/src/client/lib/pickActiveGame.js +3 -12
  17. package/src/client/lib/retryOnNetworkError.js +0 -9
  18. package/src/client/main.js +47 -90
  19. package/src/client/network/SessionSupervisor.js +14 -14
  20. package/src/client/network/WebRtcManager.js +1 -2
  21. package/src/client/network/policyClose.js +10 -77
  22. package/src/client/style.css +85 -10
  23. package/src/client/views/gameShell.js +2 -4
  24. package/src/client/views/includes/auth.pug +1 -4
  25. package/src/client/views/includes/lobby.pug +3 -6
  26. package/src/client/voice/MicCapture.js +3 -1
  27. package/src/config/abiOps.js +42 -0
  28. package/src/config/closeCodes.js +7 -9
  29. package/src/config/gameCodes.js +1 -1
  30. package/src/config/hostDefaults.js +2 -2
  31. package/src/config/lobby.js +3 -4
  32. package/src/config/menuName.js +3 -0
  33. package/src/config/wsports.js +1 -1
  34. package/src/devtools/ScenarioRunner.js +1 -1
  35. package/src/devtools/builtinScenario.js +2 -2
  36. package/src/devtools/contract/rules/a2-package-scripts.js +1 -1
  37. package/src/devtools/contract/rules/a4-vite-config.js +1 -1
  38. package/src/devtools/contract/rules/c4-component-dependencies.js +1 -1
  39. package/src/devtools/invariants.js +1 -1
  40. package/src/devtools/pluginLoader.js +2 -2
  41. package/src/host/GameCoreAdapter.js +83 -4
  42. package/src/host/HostGame.js +93 -17
  43. package/src/host/PortMachine.js +4 -21
  44. package/src/host/commandFailure.js +2 -2
  45. package/src/host/identity.js +3 -3
  46. package/src/host/meta/core/CommandProcessor.js +2 -2
  47. package/src/host/meta/core/RoundManager.js +8 -1
  48. package/src/host/meta/core/VoteCoordinator.js +56 -1
  49. package/src/host/meta/modules/Accolades.js +1 -1
  50. package/src/host/meta/modules/TimerManager.js +16 -2
  51. package/src/host/meta/modules/Vote.js +65 -0
  52. package/src/host/meta/player/ParticipantManager.js +19 -3
  53. package/src/lib/capabilities.js +17 -0
  54. package/src/lib/createHostRuntime.js +1 -1
  55. package/src/lib/gameConfigView.js +7 -0
  56. package/src/lib/loadGamePackage.js +7 -9
  57. package/src/lib/offlinePlayerData.js +1 -1
  58. package/src/lib/validators.js +2 -2
  59. package/src/lib/voicePacket.js +1 -1
  60. package/src/standalone/index.js +3 -2
  61. package/src/client/lib/footerLink.js +0 -30
  62. package/src/client/network/WebSocketTransport.js +0 -78
@@ -35,11 +35,10 @@ import {
35
35
  bindLiveErrors,
36
36
  } from './lib/formBuilder.js';
37
37
  import { normalizeAuthParams } from './lib/authParams.js';
38
- import { renderDocsLink } from './lib/footerLink.js';
39
38
  import { createGameActivator } from './lib/gameActivator.js';
40
39
  import createAutostart from './lib/autostart.js';
41
40
  import { applyCatalogState } from './lib/catalogState.js';
42
- import { getBootConfig, resolveBootConfig } from './boot.js';
41
+ import { resolveBootConfig } from './boot.js';
43
42
  import {
44
43
  ensureGameShell,
45
44
  ensureCanvas,
@@ -67,7 +66,6 @@ import { validateAuth } from '../lib/validators.js';
67
66
  import SoundManager from './SoundManager.js';
68
67
  import SignalingClient from './network/SignalingClient.js';
69
68
  import LoopbackTransport from './network/LoopbackTransport.js';
70
- import WebSocketTransport from './network/WebSocketTransport.js';
71
69
  import InlineHostBridge from './network/InlineHostBridge.js';
72
70
  import SessionSupervisor, {
73
71
  SESSION_STATES,
@@ -83,11 +81,7 @@ import {
83
81
  import { createVerifier, challengeOf } from './lib/pkce.js';
84
82
  import { createTurnstile } from './lib/turnstile.js';
85
83
  import TokenRenewer from './network/TokenRenewer.js';
86
- import {
87
- isKickClose,
88
- POLICY_CLOSE_INFORMS,
89
- shouldReloadAfterClose,
90
- } from './network/policyClose.js';
84
+ import { isKickClose } from './network/policyClose.js';
91
85
  import LobbyModel from './components/model/Lobby.js';
92
86
  import LobbyView from './components/view/Lobby.js';
93
87
  import LobbyCtrl from './components/controller/Lobby.js';
@@ -145,8 +139,8 @@ import runHotTick from './lib/hotTick.js';
145
139
 
146
140
  // Динамическая загрузка игры по каталогу мастера (Этап 6.3): ClientPlugin
147
141
  // (parts, bakers, игровой CSS, хуки ядра) грузится по entries.client манифеста
148
- // — движок не импортирует игру статически. Первой активируется gamesManifest[0]
149
- // (или boot.gameId), но активная игра не заморожена: выбор в #lobby-game и
142
+ // — движок не импортирует игру статически. Первой активируется первая
143
+ // доступная игра каталога, но активная игра не заморожена: выбор в #lobby-game и
150
144
  // вход в чужую комнату переключают её через gameActivator (см. bindActiveGame).
151
145
  // Переключение безопасно ровно до старта матча: всё пер-игровое состояние
152
146
  // (Factory, Pixi-приложения, clientCore, звук) появляется только в CONFIG_DATA,
@@ -179,37 +173,24 @@ function bindActiveGame(manifest, plugin) {
179
173
  }
180
174
 
181
175
  // режим загрузки (Этап 2 плана standalone-sdk): lobby — прод с мастером,
182
- // solo — хост в этой же вкладке (standalone SDK), dedicated — прямой WS к
183
- // Node-серверу. Ветвлений ровно пять: манифест, сигналинг/лобби, транспорт,
184
- // авто-аутентификация и точка монтирования канвасов.
185
- //
186
- // каталог манифестов нужен только лобби-контуру, а его запрос не зависит от
187
- // ответа /config — пускаем оба в полёт разом, иначе старт лобби платит лишний
188
- // последовательный round-trip
189
- const injectedBoot = getBootConfig();
190
- const manifestPromise = injectedBoot
191
- ? null
192
- : fetchGamesManifest(lobbyConfig.gamesManifestUrl, (...args) =>
193
- fetchRetrying(fetch, ...args),
194
- ).catch(err => err);
195
-
196
- let boot;
197
-
198
- try {
199
- boot = injectedBoot ?? (await resolveBootConfig());
200
- } catch (e) {
201
- // сети нет и режим неизвестен: репортёр ещё не создан (ему нужен режим),
202
- // игроку остаётся надпись отказа
203
- showBootFailure(`Failed to load the game: ${e.message}`);
204
- throw e;
205
- }
206
-
176
+ // solo — хост в этой же вкладке (standalone SDK). Ветвлений ровно пять:
177
+ // манифест, сигналинг/лобби, транспорт, авто-аутентификация и точка
178
+ // монтирования канвасов.
179
+ const boot = resolveBootConfig();
207
180
  const bootMode = boot.mode;
208
181
  const isLobbyMode = bootMode === 'lobby';
209
182
 
183
+ // каталог манифестов нужен только лобби-контуру: запрос уходит до создания
184
+ // журнала и каркаса, а ждут его только в загрузке игры ниже
185
+ const manifestPromise = isLobbyMode
186
+ ? fetchGamesManifest(lobbyConfig.gamesManifestUrl, (...args) =>
187
+ fetchRetrying(fetch, ...args),
188
+ ).catch(err => err)
189
+ : null;
190
+
210
191
  // журнал клиентских ошибок: ставится как можно
211
- // раньше, чтобы ловить и сбои самого старта. Лобби и dedicated шлют на
212
- // свой бокс; SDK (solo) — только если встраивающий передал reportUrl
192
+ // раньше, чтобы ловить и сбои самого старта. Лобби шлёт на свой бокс;
193
+ // SDK (solo) — только если встраивающий передал reportUrl
213
194
  const diagnostics = createDiagnostics({
214
195
  url:
215
196
  bootMode === 'solo'
@@ -258,7 +239,7 @@ try {
258
239
 
259
240
  try {
260
241
  // недоступная игра активной быть не может (lib/pickActiveGame.js)
261
- activeGameManifest = pickActiveGame(gamesManifest, boot.gameId);
242
+ activeGameManifest = pickActiveGame(gamesManifest);
262
243
  syncDiagnosticsGame();
263
244
  } catch (e) {
264
245
  // «каталог непустой, но играбельного в нём нет» (движок обновили, все
@@ -266,7 +247,7 @@ try {
266
247
  // лобби это то же самое, что пустой каталог: играть не во что, но
267
248
  // модерация и заявка на месте, и только они это чинят. Терять их из-за
268
249
  // такого нельзя, поэтому причина едет строкой отказа лобби, а не
269
- // терминальным оверлеем. В solo/dedicated отказ остаётся отказом
250
+ // терминальным оверлеем. В solo отказ остаётся отказом
270
251
  if (!isLobbyMode) {
271
252
  throw e;
272
253
  }
@@ -278,7 +259,7 @@ try {
278
259
  // пустой каталог — состояние лобби, а не отказ загрузки: модератор вправе
279
260
  // отключить последнюю игру, реестр вправе ещё ничего не одобрить, и вернуть
280
261
  // каталог к жизни можно только из панели, которая живёт в этой же вкладке.
281
- // В solo/dedicated игры нет — это отказ: там весь смысл вкладки в матче
262
+ // В solo игры нет — это отказ: там весь смысл вкладки в матче
282
263
  if (!activeGameManifest && !isLobbyMode) {
283
264
  throw new Error('master has no games in its catalog');
284
265
  }
@@ -338,7 +319,7 @@ const PC_LEAVE = wsports.client.LEAVE;
338
319
 
339
320
  // сигнальный WebSocket мастера (лобби + установка P2P); игровой трафик идёт
340
321
  // по WebRTC (transport), не через мастер
341
- // (только lobby: solo и dedicated мастера не имеют вовсе)
322
+ // (только lobby: у solo мастера нет вовсе)
342
323
  const wsProtocol = location.protocol === 'https:' ? 'wss:' : 'ws:';
343
324
  const signaling = isLobbyMode
344
325
  ? new SignalingClient(`${wsProtocol}//${location.host}/`)
@@ -346,7 +327,7 @@ const signaling = isLobbyMode
346
327
 
347
328
  // супервизор сессии (host-migration этап 4): держит текущий транспорт к
348
329
  // хосту и переживает его обрыв — переподключается и возвращает место в
349
- // матче. Создаётся при входе в комнату (или в solo/dedicated-матч)
330
+ // матче. Создаётся при входе в комнату (или в solo-матч)
350
331
  let supervisor = null;
351
332
 
352
333
  // оверлей «Reconnecting…» на время переподключения (только лобби-режим)
@@ -406,7 +387,7 @@ const GAME_ROUND_START_CODE = GAME_CODES.roundStart[0];
406
387
  // соединения важнее общего сообщения handleDisconnect
407
388
  let terminalInformShown = false;
408
389
  // ключ последнего терминального tech-кода: по нему лобби-режим отличает кик
409
- // (policyClose.isKickClose) от закрытия комнаты — в P2P кода закрытия нет
390
+ // (policyClose.isKickClose) от закрытия комнаты — у транспортов кода закрытия нет
410
391
  let terminalTechKey = null;
411
392
  // снятие предыдущего animationend-листенера логотипа при повторном
412
393
  // playLogoRoundStart — иначе листенеры копятся при частых стартах раунда
@@ -618,9 +599,9 @@ socketMethods[PS_AUTH_DATA] = data => {
618
599
 
619
600
  document.getElementById('logo').textContent = texts?.title || 'VIMP';
620
601
 
621
- // футер формы входа: версия пакета ИГРЫ и (в dev-сборке) ссылка на
622
- // документацию. Здесь, а не в AuthView: его elems приходят из
623
- // authSchema.elems игрового плагина, и футер каркаса — не его забота.
602
+ // футер формы входа: версия пакета ИГРЫ. Здесь, а не в AuthView: его
603
+ // elems приходят из authSchema.elems игрового плагина, и футер каркаса —
604
+ // не его забота.
624
605
  // Версию кладёт в манифест мастер (GameCatalog читает package.json
625
606
  // пакета); в standalone-манифесте её может не быть — тогда ячейка пустая,
626
607
  // раскладку space-between это не ломает
@@ -630,8 +611,6 @@ socketMethods[PS_AUTH_DATA] = data => {
630
611
  authVersion.textContent = activeGameManifest?.packageVersion ?? '';
631
612
  }
632
613
 
633
- renderDocsLink(document.getElementById('auth-docs-link'), isDevBuild);
634
-
635
614
  // память клиента (localStorage) + принудительное значение поля с
636
615
  // единственным вариантом. Здесь, а не в AuthView: solo-путь ниже отвечает
637
616
  // хосту вообще без формы и обязан прийти к тем же значениям
@@ -987,7 +966,7 @@ socketMethods[PS_GAME_INFORM_DATA] = data => {
987
966
 
988
967
  // проигрывает shimmer-волну по логотипу в начале раунда; логотип живёт вне
989
968
  // PanelView.containerId, поэтому панель им не управляет
990
- // (см. docs.vimp.lgick.dev/en/client-api)
969
+ // (см. VIMP docs: client-api.md)
991
970
  const playLogoRoundStart = () => {
992
971
  const logo = document.getElementById('logo');
993
972
 
@@ -1066,6 +1045,10 @@ socketMethods[PS_CLEAR] = function (setIdList) {
1066
1045
  }
1067
1046
  }
1068
1047
 
1048
+ // личное меню и подсказки прошлого раунда гаснут; ядро игры зажжёт
1049
+ // подсказку заново при спавне
1050
+ modules.vote?.clearPersonal();
1051
+
1069
1052
  clientCore?.reset();
1070
1053
  soundManager.reset();
1071
1054
  };
@@ -1672,10 +1655,8 @@ function handleMessage(data) {
1672
1655
 
1673
1656
  // терминальное закрытие сессии (супервизор: возвращаться некуда — кик,
1674
1657
  // политика, уход хоста, окно переподключения истекло). Останавливаем
1675
- // рендер, показываем заглушку и возвращаемся в лобби перезагрузкой.
1676
- // closeCode приходит только от WebSocket-транспорта (dedicated), остальные
1677
- // транспорты эмитят close без него
1678
- function handleDisconnect(closeCode) {
1658
+ // рендер, показываем заглушку и возвращаемся в лобби перезагрузкой
1659
+ function handleDisconnect() {
1679
1660
  showSessionOverlay(null);
1680
1661
 
1681
1662
  // app.stop() здесь не зовём: при sharedTicker это Ticker.shared.stop()
@@ -1729,31 +1710,18 @@ function handleDisconnect(closeCode) {
1729
1710
  inlineHost?.destroy();
1730
1711
  inlineHost = null;
1731
1712
 
1732
- // запасной текст политического отказа: если сервер прислал причину сам
1733
- // (TECH_INFORM перед закрытием — так приезжает 4006, полная комната), ниже
1734
- // не пишется вообще ничего, и побеждает серверный текст
1735
- const policyInform = POLICY_CLOSE_INFORMS[closeCode];
1736
-
1737
- // терминальную причину закрытия (кик, полная комната) не затираем
1713
+ // терминальную причину закрытия (кик, полная комната — TECH_INFORM перед
1714
+ // закрытием) не затираем
1738
1715
  if (!terminalInformShown) {
1739
1716
  socketMethods[PS_TECH_INFORM_DATA](
1740
- policyInform ??
1741
- (isLobbyMode
1742
- ? 'Host left — the room is closed. Finding another room…'
1743
- : 'The match is over — connection to the host is closed.'),
1717
+ isLobbyMode
1718
+ ? 'Host left — the room is closed. Finding another room…'
1719
+ : 'The match is over — connection to the host is closed.',
1744
1720
  );
1745
1721
  }
1746
1722
 
1747
- // в solo перезагружаться некуда: лобби нет, а матч поднимается с нуля.
1748
- // В dedicated сервер жив — переподключение перезагрузкой уместно, но не
1749
- // тогда, когда он сам нас и отбил по политике (network/policyClose.js)
1750
- if (bootMode === 'solo' || !shouldReloadAfterClose(closeCode)) {
1751
- return;
1752
- }
1753
-
1723
+ // в solo перезагружаться некуда: лобби нет, а матч поднимается с нуля
1754
1724
  if (!isLobbyMode) {
1755
- setTimeout(() => reloadPage(), 3000);
1756
-
1757
1725
  return;
1758
1726
  }
1759
1727
 
@@ -1763,7 +1731,7 @@ function handleDisconnect(closeCode) {
1763
1731
  // — на главную без перезагрузки (причина остаётся в #tech-informer, клик
1764
1732
  // по нему открывает лобби); закрытие комнаты — в быструю игру той же игры
1765
1733
  const exit = decideExitRoute({
1766
- kicked: isKickClose(closeCode, terminalTechKey) || roomCtx.startFailed,
1734
+ kicked: isKickClose(terminalTechKey) || roomCtx.startFailed,
1767
1735
  gameId: activeGameManifest?.id,
1768
1736
  });
1769
1737
 
@@ -1803,10 +1771,9 @@ function startSession(transport, reconnect = null) {
1803
1771
  onResumeRejected: handleResumeRejected,
1804
1772
  onHostLost: reportHostUnreachable,
1805
1773
  onColdRestart: handleColdRestart,
1806
- // кик (TECH_INFORM перед закрытием) и отказы политики не лечатся
1774
+ // кик и полная комната (TECH_INFORM перед закрытием) не лечатся
1807
1775
  // переподключением
1808
- isTerminalClose: closeCode =>
1809
- terminalInformShown || !shouldReloadAfterClose(closeCode),
1776
+ isTerminalClose: () => terminalInformShown,
1810
1777
  reconnect,
1811
1778
  timing: lobbyConfig.session,
1812
1779
  });
@@ -2221,14 +2188,6 @@ async function connectSolo() {
2221
2188
  transport.connect();
2222
2189
  }
2223
2190
 
2224
- // dedicated-режим: прямой WebSocket к Node-серверу игры
2225
- function connectDedicated() {
2226
- const transport = new WebSocketTransport(boot.wsUrl);
2227
-
2228
- startSession(transport);
2229
- transport.connect();
2230
- }
2231
-
2232
2191
  // REST-запрос списка серверов у мастера (поиск игнорирует пагинацию)
2233
2192
  async function fetchServers({ offset, limit, search }) {
2234
2193
  const params = new URLSearchParams();
@@ -2317,7 +2276,7 @@ let roomFormFields = new Map();
2317
2276
  let roomFormDescriptors = [];
2318
2277
 
2319
2278
  // генерирует форму создания комнаты по явной схеме манифеста (roomForm,
2320
- // docs.vimp.lgick.dev/en/plugin-api "Form schema") — движок не выводит контролы
2279
+ // VIMP docs: plugin-api.md § "Form schema") — движок не выводит контролы
2321
2280
  // из типа значения, схема плагина полностью описывает форму
2322
2281
  function populateRoomForm(manifest) {
2323
2282
  const container = document.getElementById(lobbyConfig.elems.fieldsId);
@@ -2705,7 +2664,7 @@ function initLobby() {
2705
2664
  // валидация (pattern/required/min/max) — единственная граница
2706
2665
  // room-формы: она едет клиенту как JSON манифеста, JS-валидаторы
2707
2666
  // (как в auth-форме) туда не сериализуются
2708
- // (docs.vimp.lgick.dev/en/plugin-api "Form schema"); авторитетный клампинг
2667
+ // (VIMP docs: plugin-api.md § "Form schema"); авторитетный клампинг
2709
2668
  // всё равно в applyRoomOverrides.js (вызывается из host.worker.js при
2710
2669
  // создании комнаты). Проверяем до активации игры: неверная форма не должна
2711
2670
  // стоить загрузки плагина. arm() и рисует ошибки, и снимает фильтр «только
@@ -2813,7 +2772,7 @@ function leaveRoomWith(message) {
2813
2772
  }
2814
2773
 
2815
2774
  // точка 2 ветвления: лобби, экран профиля и сигналинг живут только в lobby;
2816
- // solo и dedicated идут сразу к транспорту
2775
+ // solo идёт сразу к транспорту
2817
2776
  if (isLobbyMode) {
2818
2777
  authApi = createAuthApi({ serviceUrl: authClientConfig.serviceUrl });
2819
2778
 
@@ -2956,7 +2915,7 @@ if (isLobbyMode) {
2956
2915
  window.addEventListener('hashchange', () => route.handleHashChange());
2957
2916
 
2958
2917
  signaling.connect();
2959
- } else if (bootMode === 'solo') {
2918
+ } else {
2960
2919
  try {
2961
2920
  await connectSolo();
2962
2921
  } catch (e) {
@@ -2965,6 +2924,4 @@ if (isLobbyMode) {
2965
2924
  );
2966
2925
  throw e;
2967
2926
  }
2968
- } else {
2969
- connectDedicated();
2970
2927
  }
@@ -52,8 +52,8 @@ export default class SessionSupervisor {
52
52
  * @param {Object} opts
53
53
  * @param {Function} opts.onMessage - входящее сообщение текущего
54
54
  * транспорта (строка или ArrayBuffer).
55
- * @param {Function} opts.onTerminal - (closeCode) сессия закрыта
56
- * окончательно: путь прежнего handleDisconnect.
55
+ * @param {Function} opts.onTerminal - сессия закрыта окончательно
56
+ * (handleDisconnect).
57
57
  * @param {Function} [opts.onVoice] - (data) голосовой пакет текущего
58
58
  * транспорта в состоянии inGame (бинарный, без разбора).
59
59
  * @param {Function} [opts.onStateChange] - (state, prev).
@@ -63,10 +63,10 @@ export default class SessionSupervisor {
63
63
  * матче (до переподключения): свидетельство мастеру (host_unreachable).
64
64
  * @param {Function} [opts.onColdRestart] - новый хост поднял матч заново
65
65
  * (host_changed cold): возобновлять нечего, только чистый вход.
66
- * @param {Function} [opts.isTerminalClose] - (closeCode) закрытие по
67
- * политике или кику: возвращаться некуда.
66
+ * @param {Function} [opts.isTerminalClose] - () закрытие после
67
+ * терминальной причины (кик, полная комната): возвращаться некуда.
68
68
  * @param {Object|null} [opts.reconnect] - null — переподключения нет
69
- * (solo, dedicated, вкладка-хост на loopback). Иначе:
69
+ * (solo, вкладка-хост на loopback). Иначе:
70
70
  * { createTransport() → новый транспорт с resume (уже connect'нутый),
71
71
  * getToken() → identity-токен для RESUME_REQUEST }.
72
72
  * @param {Object} [opts.timing] - { reconnectWindowMs, reconnectBaseDelayMs,
@@ -493,7 +493,7 @@ export default class SessionSupervisor {
493
493
  const handlers = {
494
494
  message: data => this._handleMessage(transport, data),
495
495
  voice: data => this._handleVoice(transport, data),
496
- close: closeCode => this._handleClose(transport, closeCode),
496
+ close: () => this._handleClose(transport),
497
497
  open: () => this._handleOpen(transport),
498
498
  };
499
499
 
@@ -573,7 +573,7 @@ export default class SessionSupervisor {
573
573
  }
574
574
  }
575
575
 
576
- _handleClose(transport, closeCode) {
576
+ _handleClose(transport) {
577
577
  if (transport !== this._transport) {
578
578
  return;
579
579
  }
@@ -590,7 +590,7 @@ export default class SessionSupervisor {
590
590
  // транспорт, выданный resumeWith без фабрики попыток: повторять нечем
591
591
  if (this._reconnect === null) {
592
592
  if (!this._failRoleResume('closed')) {
593
- this._terminate(closeCode);
593
+ this._terminate();
594
594
  }
595
595
 
596
596
  return;
@@ -600,23 +600,23 @@ export default class SessionSupervisor {
600
600
  return;
601
601
  }
602
602
 
603
- if (this._canResume(closeCode)) {
603
+ if (this._canResume()) {
604
604
  this._startReconnect();
605
605
  return;
606
606
  }
607
607
 
608
- this._terminate(closeCode);
608
+ this._terminate();
609
609
  }
610
610
 
611
611
  // нетерминальным бывает только обрыв посреди матча, у которого есть место
612
612
  // для возврата и кому возвращаться
613
- _canResume(closeCode) {
613
+ _canResume() {
614
614
  return (
615
615
  this._reconnect !== null &&
616
616
  !this._userLeft &&
617
617
  this._state === S.inGame &&
618
618
  this._session !== null &&
619
- !this._isTerminalClose(closeCode)
619
+ !this._isTerminalClose()
620
620
  );
621
621
  }
622
622
 
@@ -728,7 +728,7 @@ export default class SessionSupervisor {
728
728
 
729
729
  // ***** завершение ***** //
730
730
 
731
- _terminate(closeCode) {
731
+ _terminate() {
732
732
  if (this._state === S.closed) {
733
733
  return;
734
734
  }
@@ -738,7 +738,7 @@ export default class SessionSupervisor {
738
738
  this._clearMigrationTimer();
739
739
  this._onRoleResumeFailed = null;
740
740
  this._setState(S.closed);
741
- this._onTerminal(closeCode);
741
+ this._onTerminal();
742
742
  }
743
743
 
744
744
  _setState(state) {
@@ -493,8 +493,7 @@ export default class WebRtcManager {
493
493
  channel.onmessage = event => this.publisher.emit('voice', event.data);
494
494
  }
495
495
 
496
- // кода закрытия у data channel нет: 'close' эмитится без аргумента, как
497
- // и раньше (код приходит только от WebSocket-транспорта dedicated)
496
+ // кода закрытия у data channel нет: 'close' эмитится без аргумента
498
497
  _emitClose() {
499
498
  if (this._closed) {
500
499
  return;
@@ -1,84 +1,17 @@
1
- // Отказы по политике: коды закрытия, которыми dedicated-сервер отбивает
2
- // соединение (`src/dedicated/main.js` — invalidOrigin, handshakeTimeout,
3
- // tooManyConnections) и которыми порт-машина отбивает полную комнату
4
- // (`src/host/PortMachine.js` — roomFull). Числа берутся из общей карты
5
- // `src/config/closeCodes.js`, чтобы серверные контуры и клиент не разъехались
6
- // молча; тест модуля требует явного решения по каждому коду карты.
7
- //
8
- // Клиенту от кода нужны два разных ответа, и совпадают они не всегда, поэтому
9
- // знание разведено на два экспорта (review-4.md, R4-2):
10
- //
11
- // - перезагружаться ли. Ни на одном из этих кодов перезагрузка не помогает:
12
- // лимит частоты подключений съел бы ей же очередное соединение, таймаут
13
- // хендшейка запустился бы заново, origin страницы не меняется вовсе, а
14
- // слот в полной комнате перезагрузка не освобождает. Брошенная вкладка
15
- // перезагружалась бы вечно, а ждущий слот игрок за 30 витков сам упёрся бы
16
- // в лимит подключений и получил бы чужую причину отказа;
17
- // - какой текст показать. Тексты запасные: `handleDisconnect` берёт их
18
- // только тогда, когда сервер причину не прислал сам (`terminalInformShown`
19
- // — `client/main.js`). Полная комната, например, приезжает с причиной от
20
- // порт-машины (TECH_INFORM перед `close`), и она всегда побеждает —
21
- // запись 4006 ниже отработает, только если тот кадр не доехал. Тексты
22
- // здесь, а не в `techInformList`: список переопределяет игра, и движковый
23
- // индекс дал бы у неё «Unknown error».
24
-
25
- import closeCodes from '../../config/closeCodes.js';
26
-
27
- const {
28
- invalidOrigin,
29
- roomFull,
30
- handshakeTimeout,
31
- tooManyConnections,
32
- kickIdle,
33
- kickForMaxLatency,
34
- kickForMissedPings,
35
- } = closeCodes;
36
-
37
- const NO_RELOAD_CLOSE_CODES = new Set([
38
- invalidOrigin,
39
- roomFull,
40
- handshakeTimeout,
41
- tooManyConnections,
42
- ]);
43
-
44
- export const POLICY_CLOSE_INFORMS = {
45
- [invalidOrigin]:
46
- 'This page is not allowed by the game server. Check the address you opened.',
47
- [roomFull]: 'The room is full. Try again later.',
48
- [handshakeTimeout]:
49
- 'Idle connection closed before the match started. Reload the page when you are ready to play.',
50
- [tooManyConnections]:
51
- 'Too many connections from your address. Wait a minute and reload the page.',
52
- };
53
-
54
- /**
55
- * @param {number} [closeCode] - Код закрытия транспорта. Его отдаёт только
56
- * WebSocket-транспорт (dedicated); в P2P и solo кода нет вовсе.
57
- * @returns {boolean} Уместна ли перезагрузка страницы после разрыва.
58
- */
59
- export function shouldReloadAfterClose(closeCode) {
60
- return !NO_RELOAD_CLOSE_CODES.has(closeCode);
61
- }
62
-
63
- // Кики хоста (host/HostGame.js). В P2P кода закрытия нет вовсе — причина
64
- // приезжает только TECH_INFORM-ключом перед закрытием (индексы
65
- // TECH_CODES из host/meta/SocketManager.js = индексы techInformList);
66
- // в dedicated есть и код. Лобби-режим уводит кикнутого на главную без
67
- // перезагрузки (client/lib/roomLink.js decideExitRoute): быстрая игра по
68
- // hash вернула бы его в ту же комнату
69
- const KICK_CLOSE_CODES = new Set([
70
- kickIdle,
71
- kickForMaxLatency,
72
- kickForMissedPings,
73
- ]);
74
-
1
+ // Кики хоста (host/HostGame.js). Кода закрытия у транспортов клиента нет —
2
+ // причина приезжает только TECH_INFORM-ключом перед закрытием (индексы
3
+ // TECH_CODES из host/meta/SocketManager.js = индексы techInformList).
4
+ // Лобби-режим уводит кикнутого на главную без перезагрузки
5
+ // (client/lib/roomLink.js decideExitRoute): быстрая игра по hash вернула бы
6
+ // его в ту же комнату.
7
+
8
+ // kickIdle, kickForMaxLatency, kickForMissedPings
75
9
  export const KICK_TECH_KEYS = new Set([3, 4, 5]);
76
10
 
77
11
  /**
78
- * @param {number} [closeCode] - Код закрытия транспорта (только WebSocket).
79
12
  * @param {number|null} [techKey] - Последний терминальный TECH_INFORM-ключ.
80
13
  * @returns {boolean} Закрыто ли соединение киком хоста.
81
14
  */
82
- export function isKickClose(closeCode, techKey) {
83
- return KICK_CLOSE_CODES.has(closeCode) || KICK_TECH_KEYS.has(techKey);
15
+ export function isKickClose(techKey) {
16
+ return KICK_TECH_KEYS.has(techKey);
84
17
  }