vimp-engine 0.38.2 → 0.38.3
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.
- package/core/Cargo.toml +1 -1
- package/package.json +3 -1
- package/src/client/boot.js +30 -7
- package/src/client/components/controller/ClientReports.js +1 -1
- package/src/client/components/controller/Games.js +1 -1
- package/src/client/components/model/ClientReports.js +1 -1
- package/src/client/components/model/Games.js +24 -0
- package/src/client/components/view/ClientReports.js +1 -1
- package/src/client/components/view/Games.js +19 -1
- package/src/client/components/view/Lobby.js +1 -7
- package/src/client/debug.js +1 -1
- package/src/client/lib/authApi.js +1 -1
- package/src/client/lib/diagnostics.js +19 -4
- package/src/client/lib/engineVersion.js +0 -6
- package/src/client/lib/footerLink.js +15 -14
- package/src/client/lib/retryOnNetworkError.js +60 -0
- package/src/client/main.js +35 -18
- package/src/client/network/HiddenHostHealthLog.js +40 -3
- package/src/client/network/HostController.js +4 -4
- package/src/client/network/HostHealthPolicy.js +41 -0
- package/src/client/network/InlineHostBridge.js +8 -1
- package/src/client/session/HandoffFlow.js +5 -0
- package/src/client/session/HostRole.js +5 -2
- package/src/client/style.css +5 -6
- package/src/client/views/gameShell.js +3 -3
- package/src/client/views/includes/auth.pug +4 -4
- package/src/client/views/includes/lobby.pug +4 -4
- package/src/client/views/includes/reports.pug +1 -1
- package/src/config/authClient.js +3 -3
- package/src/config/lobby.js +4 -5
- package/src/config/opcodes.js +2 -1
- package/src/devtools/contract/loadContext.js +2 -2
- package/src/devtools/contract/rules/a7-package-repository.js +7 -8
- package/src/devtools/contract/rules/index.js +1 -1
- package/src/devtools/invariants.js +1 -1
- package/src/host/DebugRecorder.js +1 -1
- package/src/host/HostGame.js +35 -15
- package/src/host/commandFailure.js +140 -0
- package/src/host/host.worker.js +8 -2
- package/src/host/meta/SocketManager.js +2 -2
- package/src/host/meta/core/CommandProcessor.js +37 -7
- package/src/lib/applyRoomOverrides.js +1 -1
- package/src/lib/capabilities.js +1 -1
- package/src/lib/checkpointCodec.js +1 -2
- package/src/lib/coreConfig.js +1 -1
- package/src/lib/createHostRuntime.js +1 -1
- package/src/lib/gameId.js +1 -1
- package/src/lib/gamePlugin.js +6 -2
- package/src/lib/jwt.js +1 -1
- package/src/lib/packageLink.js +18 -61
- package/src/lib/validators.js +3 -3
- package/src/standalone/index.js +1 -1
- package/tests/fixtures/generations/gen-api3/README.md +2 -2
- package/tests/fixtures/generations/gen-api4/README.md +2 -2
- package/src/devtools/surface/abiParse.js +0 -175
- package/src/devtools/surface/collect.js +0 -413
|
@@ -56,7 +56,7 @@ export default class HostController {
|
|
|
56
56
|
* @param {Function} [opts.onLobbyInfoChange] - смена строки карточки
|
|
57
57
|
* комнаты в лобби (string|null) — для актуализации info у мастера.
|
|
58
58
|
* @param {Object} [opts.diagnostics] - журнал клиентских ошибок
|
|
59
|
-
* (lib/diagnostics.js
|
|
59
|
+
* (lib/diagnostics.js): туда уходят ошибки Worker'а.
|
|
60
60
|
* @param {Uint8Array} [opts.checkpoint] - сжатая контрольная точка
|
|
61
61
|
* (host-migration этап 5): Worker поднимает матч из неё на паузе, старт —
|
|
62
62
|
* startAfterRestore(). Буфер передаётся списком переноса — после
|
|
@@ -114,7 +114,7 @@ export default class HostController {
|
|
|
114
114
|
// подписчики метрик здоровья хоста (host-migration этап 9a)
|
|
115
115
|
this._healthListeners = new Set();
|
|
116
116
|
|
|
117
|
-
// отладочные запросы в Worker
|
|
117
|
+
// отладочные запросы в Worker:
|
|
118
118
|
// requestId → { resolve, reject }
|
|
119
119
|
this._debugRequests = new Map();
|
|
120
120
|
this._debugRequestId = 0;
|
|
@@ -149,7 +149,7 @@ export default class HostController {
|
|
|
149
149
|
});
|
|
150
150
|
}
|
|
151
151
|
|
|
152
|
-
// журнал клиентских
|
|
152
|
+
// журнал клиентских ошибок: неперехваченная ошибка
|
|
153
153
|
// Worker'а приходит сюда. preventDefault не зовётся — консольный вывод
|
|
154
154
|
// браузера остаётся как есть
|
|
155
155
|
_watchWorkerErrors(worker) {
|
|
@@ -456,7 +456,7 @@ export default class HostController {
|
|
|
456
456
|
}
|
|
457
457
|
|
|
458
458
|
/**
|
|
459
|
-
* Отладочный
|
|
459
|
+
* Отладочный контур: начинает запись живого
|
|
460
460
|
* матча в формат сценария headless-runner'а.
|
|
461
461
|
* @returns {Promise<boolean>} false — dev-режим в комнате выключен.
|
|
462
462
|
*/
|
|
@@ -56,6 +56,7 @@ export default class HostHealthPolicy {
|
|
|
56
56
|
this._successor = false;
|
|
57
57
|
this._hiddenSince = null;
|
|
58
58
|
this._hiddenTimer = null;
|
|
59
|
+
this._hiddenOutcome = null;
|
|
59
60
|
this._lastAutoAt = null;
|
|
60
61
|
this._cooldownBefore = null;
|
|
61
62
|
this._samples = [];
|
|
@@ -80,6 +81,12 @@ export default class HostHealthPolicy {
|
|
|
80
81
|
this._successor = false;
|
|
81
82
|
this._resetSamples();
|
|
82
83
|
|
|
84
|
+
// исход прошлой роли уже прочитан её журналом (flush при потере роли);
|
|
85
|
+
// новой роли он не принадлежит
|
|
86
|
+
if (isHost) {
|
|
87
|
+
this._hiddenOutcome = null;
|
|
88
|
+
}
|
|
89
|
+
|
|
83
90
|
// роль получена скрытой вкладкой: скрытие отсчитывается от получения
|
|
84
91
|
if (isHost && this._hiddenSince !== null) {
|
|
85
92
|
this._hiddenSince = this._hostSince;
|
|
@@ -98,6 +105,7 @@ export default class HostHealthPolicy {
|
|
|
98
105
|
setHidden(hidden) {
|
|
99
106
|
if (hidden && this._hiddenSince === null) {
|
|
100
107
|
this._hiddenSince = this._now();
|
|
108
|
+
this._hiddenOutcome = null;
|
|
101
109
|
this._armHiddenTimer();
|
|
102
110
|
} else if (!hidden) {
|
|
103
111
|
this._hiddenSince = null;
|
|
@@ -105,6 +113,19 @@ export default class HostHealthPolicy {
|
|
|
105
113
|
}
|
|
106
114
|
}
|
|
107
115
|
|
|
116
|
+
/**
|
|
117
|
+
* Исход передачи для последнего эпизода скрытия (живёт до следующего
|
|
118
|
+
* скрытия или новой роли хоста, переживает возврат видимости и потерю
|
|
119
|
+
* роли — его читает журнал закрываемого эпизода): null — порог
|
|
120
|
+
* hiddenHandoffMs не достигнут; 'started' — передача началась;
|
|
121
|
+
* 'disabled' | 'no-successor' | 'blocked' (срок роли или кулдаун) —
|
|
122
|
+
* ожидаемый отказ; 'unexplained' — всё разрешено, а передача не началась.
|
|
123
|
+
* @returns {string|null}
|
|
124
|
+
*/
|
|
125
|
+
hiddenHandoff() {
|
|
126
|
+
return this._hiddenOutcome;
|
|
127
|
+
}
|
|
128
|
+
|
|
108
129
|
// сэмпл здоровья Worker'а
|
|
109
130
|
addHealth(health) {
|
|
110
131
|
if (this._hostSince === null || !health) {
|
|
@@ -182,6 +203,8 @@ export default class HostHealthPolicy {
|
|
|
182
203
|
}
|
|
183
204
|
|
|
184
205
|
if (this._handOverNow('hidden')) {
|
|
206
|
+
this._hiddenOutcome = 'started';
|
|
207
|
+
|
|
185
208
|
return true;
|
|
186
209
|
}
|
|
187
210
|
|
|
@@ -191,9 +214,27 @@ export default class HostHealthPolicy {
|
|
|
191
214
|
this._armHiddenTimer(waitMs);
|
|
192
215
|
}
|
|
193
216
|
|
|
217
|
+
// начатая передача исход не меняет: следующие проверки её уже не стартуют
|
|
218
|
+
if (this._hiddenOutcome !== 'started') {
|
|
219
|
+
this._hiddenOutcome = this._refusal(waitMs);
|
|
220
|
+
}
|
|
221
|
+
|
|
194
222
|
return false;
|
|
195
223
|
}
|
|
196
224
|
|
|
225
|
+
// почему передача со скрытой вкладки не началась
|
|
226
|
+
_refusal(waitMs) {
|
|
227
|
+
if (!this._enabled()) {
|
|
228
|
+
return 'disabled';
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
if (!this._successor) {
|
|
232
|
+
return 'no-successor';
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
return waitMs > 0 ? 'blocked' : 'unexplained';
|
|
236
|
+
}
|
|
237
|
+
|
|
197
238
|
// немедленная передача: ждущую границы — ускорить, иначе начать
|
|
198
239
|
_handOverNow(reason) {
|
|
199
240
|
if (!this._enabled() || !this._successor) {
|
|
@@ -11,7 +11,7 @@ import { createGuestIdentity } from '../../host/identity.js';
|
|
|
11
11
|
// Почему inline, а не Worker: HostPlugin принципиально непередаваем в Worker
|
|
12
12
|
// (postMessage не несёт функции), а standalone SDK получает от репозитория
|
|
13
13
|
// игры именно живой объект плагина. Прод-путь (хост в Worker'е) не меняется —
|
|
14
|
-
// расхождение dev/prod
|
|
14
|
+
// расхождение dev/prod осознанное.
|
|
15
15
|
//
|
|
16
16
|
// Мастера в этом контуре нет: личность гостевая (ник из формы, identity.js),
|
|
17
17
|
// профиль offline-заглушкой (lib/offlinePlayerData.js).
|
|
@@ -102,6 +102,13 @@ export default class InlineHostBridge {
|
|
|
102
102
|
|
|
103
103
|
this._runtime = runtime;
|
|
104
104
|
|
|
105
|
+
// сбой чат-команды игры виден в консоли вкладки вместе с кадрами обработчика
|
|
106
|
+
runtime.host.setDiagnosticSink(report =>
|
|
107
|
+
console.error(
|
|
108
|
+
`[vimp] ${report.message}${report.stack ? `\n${report.stack}` : ''}`,
|
|
109
|
+
),
|
|
110
|
+
);
|
|
111
|
+
|
|
105
112
|
this._portMachine = new PortMachine({
|
|
106
113
|
host: runtime.host,
|
|
107
114
|
socketManager: runtime.socketManager,
|
|
@@ -261,6 +261,11 @@ export default class HandoffFlow {
|
|
|
261
261
|
this._policy?.addHealth(health);
|
|
262
262
|
}
|
|
263
263
|
|
|
264
|
+
// исход передачи для последнего эпизода скрытия (HostHealthPolicy)
|
|
265
|
+
hiddenHandoff() {
|
|
266
|
+
return this._policy?.hiddenHandoff() ?? null;
|
|
267
|
+
}
|
|
268
|
+
|
|
264
269
|
// «Leave server» из меню комнаты (этап 8.3). Хост при других людях отдаёт
|
|
265
270
|
// роль бете без отката и уходит; один в комнате — закрывает её
|
|
266
271
|
// (host_leaving: людей нет — мастер закрывает комнату сразу). Гость
|
|
@@ -321,11 +321,14 @@ export default class HostRole {
|
|
|
321
321
|
onStats: this._debug ? stats => this._debug('standby sent', stats) : null,
|
|
322
322
|
});
|
|
323
323
|
|
|
324
|
-
// метрики здоровья Worker'
|
|
325
|
-
//
|
|
324
|
+
// метрики здоровья Worker'а: эпизод скрытия, в котором матч просел,
|
|
325
|
+
// задел удалённых игроков, а автопередача не началась без видимой
|
|
326
|
+
// причины, уходит в журнал одной сводкой (HiddenHostHealthLog)
|
|
326
327
|
this._hiddenLog = this._factories.hiddenLog({
|
|
327
328
|
warn: (code, summary) =>
|
|
328
329
|
this._diagnostics.warn(code, summary, { source: 'client' }),
|
|
330
|
+
minHealthyTickRate: migration.auto.overloadTickRate,
|
|
331
|
+
handoffOutcome: () => this._getHandoff().hiddenHandoff(),
|
|
329
332
|
hidden: this._ui.isHidden(),
|
|
330
333
|
});
|
|
331
334
|
this._healthReporter = this._isLobbyMode
|
package/src/client/style.css
CHANGED
|
@@ -1,10 +1,9 @@
|
|
|
1
1
|
@charset "UTF-8";
|
|
2
2
|
|
|
3
|
-
/* База страницы:
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
встраивающей странице. */
|
|
3
|
+
/* База страницы: типографика и раскладка интерфейса движка живут здесь, а
|
|
4
|
+
не в index.html, потому что в репозитории игры такого файла нет.
|
|
5
|
+
Standalone SDK импортирует этот файл целиком, поэтому всё, что здесь
|
|
6
|
+
есть, применяется и к встраивающей странице. */
|
|
8
7
|
html,
|
|
9
8
|
body {
|
|
10
9
|
width: 100%;
|
|
@@ -1329,7 +1328,7 @@ body.hide-cursor {
|
|
|
1329
1328
|
color: #d66;
|
|
1330
1329
|
}
|
|
1331
1330
|
|
|
1332
|
-
/* журнал клиентских
|
|
1331
|
+
/* журнал клиентских ошибок: тот же каркас и
|
|
1333
1332
|
палитра, что у панели реестра игр; графы — .games-filters/.games-filter-btn */
|
|
1334
1333
|
#reports-panel {
|
|
1335
1334
|
background-color: #222;
|
|
@@ -55,9 +55,9 @@ const SHELL = [
|
|
|
55
55
|
tag: 'div',
|
|
56
56
|
id: 'auth-link',
|
|
57
57
|
children: [
|
|
58
|
-
//
|
|
59
|
-
//
|
|
60
|
-
{ tag: 'p', children: [{ tag: 'a', id: 'auth-
|
|
58
|
+
// версия пакета ИГРЫ (из манифеста) и ссылка на документацию
|
|
59
|
+
// (только в dev-сборке) — заполняет main.js по AUTH_DATA
|
|
60
|
+
{ tag: 'p', children: [{ tag: 'a', id: 'auth-docs-link' }] },
|
|
61
61
|
{ tag: 'p', id: 'auth-version' },
|
|
62
62
|
{ tag: 'p', text: `© ${new Date().getFullYear()} VIMP` },
|
|
63
63
|
],
|
|
@@ -15,10 +15,10 @@ div#auth
|
|
|
15
15
|
div#auth-informs
|
|
16
16
|
|
|
17
17
|
div#auth-link
|
|
18
|
-
//-
|
|
19
|
-
//-
|
|
20
|
-
//- ячейки остаются пустыми
|
|
18
|
+
//- версия пакета ИГРЫ (мастер кладёт её в манифест, читая package.json
|
|
19
|
+
//- пакета) и ссылка на документацию (только в dev-сборке) —
|
|
20
|
+
//- проставляет main.js по AUTH_DATA; без данных ячейки остаются пустыми
|
|
21
21
|
p
|
|
22
|
-
a#auth-
|
|
22
|
+
a#auth-docs-link
|
|
23
23
|
p#auth-version
|
|
24
24
|
p © #{new Date().getFullYear()} VIMP
|
|
@@ -21,7 +21,7 @@ div#lobby(style='display:none')
|
|
|
21
21
|
input#profile-open(type='button', value='Profile')
|
|
22
22
|
input#games-open-mine(type='button', value='My games')
|
|
23
23
|
input#games-open-moderation(type='button', value='Moderation', style='display:none')
|
|
24
|
-
//- журнал клиентских
|
|
24
|
+
//- журнал клиентских ошибок: только админ
|
|
25
25
|
input#reports-open(type='button', value='Errors', style='display:none')
|
|
26
26
|
hr.lobby-divider
|
|
27
27
|
h3.lobby-section-title= 'HOST A GAME'
|
|
@@ -67,8 +67,8 @@ div#lobby(style='display:none')
|
|
|
67
67
|
span#leaderboard-total
|
|
68
68
|
ol#lobby-leaderboard-list
|
|
69
69
|
p#lobby-my-placement
|
|
70
|
-
//- футер:
|
|
71
|
-
//- (engineVersion.js
|
|
70
|
+
//- футер: ссылка на документацию статична, версию пакета движка
|
|
71
|
+
//- проставляет LobbyView (engineVersion.js)
|
|
72
72
|
div#lobby-footer
|
|
73
73
|
p
|
|
74
74
|
a#lobby-terms-link(target='_blank', rel='noopener') Terms of Service
|
|
@@ -76,6 +76,6 @@ div#lobby(style='display:none')
|
|
|
76
76
|
|
|
|
77
77
|
a#lobby-privacy-link(target='_blank', rel='noopener') Privacy Policy
|
|
78
78
|
p
|
|
79
|
-
a#lobby-link
|
|
79
|
+
a#lobby-link(href='https://docs.vimp.lgick.dev', target='_blank', rel='noopener') docs
|
|
80
80
|
p#lobby-version
|
|
81
81
|
p © #{new Date().getFullYear()} VIMP
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
//- Журнал клиентских
|
|
1
|
+
//- Журнал клиентских ошибок: только админ.
|
|
2
2
|
//- Панель скрыта по умолчанию — открывается кнопкой «Errors» бейджа
|
|
3
3
|
//- пользователя (#lobby-user в lobby.pug); id элементов перечислены в
|
|
4
4
|
//- config/lobby.js. Подписи — по-английски, как и остальное лобби
|
package/src/config/authClient.js
CHANGED
|
@@ -2,8 +2,8 @@
|
|
|
2
2
|
// config/lobby.js — auth-сервис живёт на отдельном домене/порте, поэтому
|
|
3
3
|
// serviceUrl подставляется под конкретный деплой при сборке.
|
|
4
4
|
export default {
|
|
5
|
-
// базовый URL центрального auth
|
|
6
|
-
// из
|
|
5
|
+
// базовый URL центрального auth-сервиса. Дефолт — dev-порт
|
|
6
|
+
// из конфига auth-сервиса; в проде подставляется Vite'ом из
|
|
7
7
|
// VITE_AUTH_SERVICE_URL (см. Dockerfile — тот же VIMP_AUTH_SERVICE_URL,
|
|
8
8
|
// что и серверный master/main.js). Этот модуль импортируется и серверным
|
|
9
9
|
// master/main.js под чистым Node, где import.meta.env не определён —
|
|
@@ -14,7 +14,7 @@ export default {
|
|
|
14
14
|
'http://localhost:3010',
|
|
15
15
|
|
|
16
16
|
// issuer, которым central auth-сервис подписывает identity-токен; должен
|
|
17
|
-
// совпадать с
|
|
17
|
+
// совпадать с jwt.issuer из конфига auth-сервиса — хост сверяет
|
|
18
18
|
// claim iss токена с этим значением при проверке подписи по /jwks
|
|
19
19
|
issuer: 'vimp-auth',
|
|
20
20
|
|
package/src/config/lobby.js
CHANGED
|
@@ -300,11 +300,11 @@ export default {
|
|
|
300
300
|
joinRetryWindowMs: 45000,
|
|
301
301
|
},
|
|
302
302
|
|
|
303
|
-
// приёмник выгрузок отладочного
|
|
303
|
+
// приёмник выгрузок отладочного контура:
|
|
304
304
|
// маршрут поднимается мастером только в dev, в проде вернёт 404
|
|
305
305
|
debugReportUrl: '/debug/report',
|
|
306
306
|
|
|
307
|
-
// журнал клиентских
|
|
307
|
+
// журнал клиентских ошибок: приём на том же боксе,
|
|
308
308
|
// что раздал страницу, — лобби-мастер или dedicated
|
|
309
309
|
clientReportUrl: '/client-reports',
|
|
310
310
|
|
|
@@ -350,9 +350,8 @@ export default {
|
|
|
350
350
|
myPlacementId: 'lobby-my-placement',
|
|
351
351
|
|
|
352
352
|
// футер: версия npm-пакета движка, запечённая в бандл при сборке
|
|
353
|
-
// (client/lib/engineVersion.js)
|
|
353
|
+
// (client/lib/engineVersion.js)
|
|
354
354
|
versionId: 'lobby-version',
|
|
355
|
-
linkId: 'lobby-link',
|
|
356
355
|
},
|
|
357
356
|
|
|
358
357
|
// реестр игр (master-game-registry, этап 4): заявка разработчика и панель
|
|
@@ -489,7 +488,7 @@ export default {
|
|
|
489
488
|
},
|
|
490
489
|
},
|
|
491
490
|
|
|
492
|
-
// журнал клиентских
|
|
491
|
+
// журнал клиентских ошибок: только админ.
|
|
493
492
|
// URL и id элементов — здесь, как у панели реестра игр (reports.pug)
|
|
494
493
|
clientReports: {
|
|
495
494
|
urls: {
|
package/src/config/opcodes.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
// Разметка бинарного snapshot-протокола (порт SHOT_DATA).
|
|
2
2
|
// Единый источник для хоста (упаковка) и клиента (распаковка) — обе стороны
|
|
3
|
-
// живут в Rust-ядре:
|
|
3
|
+
// живут в Rust-ядре: vimp-engine-core/src/snapshot.rs (pack) и
|
|
4
|
+
// vimp-engine-core/src/client/unpack.rs.
|
|
4
5
|
|
|
5
6
|
// версия контракта движок ↔ игра-плагин (GameManifest, HostPlugin,
|
|
6
7
|
// ClientPlugin, Wasm Host ABI — docs/{en,ru}/plugin-api.md).
|
|
@@ -14,7 +14,7 @@ import { createGameConfigView } from '../../lib/gameConfigView.js';
|
|
|
14
14
|
// модули; исходники читаются только там, где проверяется буква кода
|
|
15
15
|
// (engineApi литералом или импортом).
|
|
16
16
|
|
|
17
|
-
// каталог движка
|
|
17
|
+
// каталог пакета движка — из него берётся версия крейта, с
|
|
18
18
|
// которой сверяется пин игры. core/Cargo.toml числится в "files"
|
|
19
19
|
// package.json именно ради этого: иначе в установке из npm версии крейта
|
|
20
20
|
// нет, и правило A5 проверяло бы пин только внутри монорепозитория
|
|
@@ -170,7 +170,7 @@ async function readEngineCoreVersion() {
|
|
|
170
170
|
// версия пакета в секции [package] — первый `version = "…"` до следующей
|
|
171
171
|
// секции; TOML-парсера в зависимостях движка нет, а формат здесь наш.
|
|
172
172
|
//
|
|
173
|
-
// Копия живёт в
|
|
173
|
+
// Копия живёт в create-vimp-game (src/versions.js): скаффолдер
|
|
174
174
|
// ставится через `npm create` и обязан работать без движка в зависимостях.
|
|
175
175
|
// Разбор обязан совпадать — правьте обе или ни одной (см. тест
|
|
176
176
|
// tests/scaffold/versions.test.js).
|
|
@@ -1,17 +1,16 @@
|
|
|
1
1
|
import { WARN, skip, verdict } from '../result.js';
|
|
2
2
|
import { resolveProjectUrl } from '../../../lib/packageLink.js';
|
|
3
3
|
|
|
4
|
-
// Игра без объявленного
|
|
5
|
-
// отказ. Но
|
|
6
|
-
//
|
|
7
|
-
//
|
|
4
|
+
// Игра без объявленного repository собирается и играется — это не
|
|
5
|
+
// отказ. Но реестр игр в лобби показывает ссылку на проект, и берёт он её
|
|
6
|
+
// только из него (фолбэка нет, см. lib/packageLink.js): без них строка
|
|
7
|
+
// игры останется без ссылки, а заметить это без подсказки негде.
|
|
8
8
|
// Отсюда warn — под --strict он становится ошибкой.
|
|
9
9
|
export default {
|
|
10
10
|
id: 'A7',
|
|
11
11
|
name: 'packageRepository',
|
|
12
12
|
level: WARN,
|
|
13
|
-
title:
|
|
14
|
-
'package.json declares homepage or repository (the #auth footer links to it)',
|
|
13
|
+
title: 'package.json declares repository (the games registry links to it)',
|
|
15
14
|
|
|
16
15
|
check(ctx) {
|
|
17
16
|
if (!ctx.pkg) {
|
|
@@ -23,8 +22,8 @@ export default {
|
|
|
23
22
|
}
|
|
24
23
|
|
|
25
24
|
return verdict([
|
|
26
|
-
'
|
|
27
|
-
"the game's entry
|
|
25
|
+
'"repository" does not resolve to a project URL — ' +
|
|
26
|
+
"the game's registry entry will show no project link",
|
|
28
27
|
]);
|
|
29
28
|
},
|
|
30
29
|
};
|
|
@@ -38,7 +38,7 @@ import e6 from './e6-sound-spatial.js';
|
|
|
38
38
|
import e7 from './e7-map-game-field.js';
|
|
39
39
|
|
|
40
40
|
// Порядок групп — порядок отчёта: A пакет и сборка, B host, C client,
|
|
41
|
-
// D снапшот, E
|
|
41
|
+
// D снапшот, E ассеты.
|
|
42
42
|
export const rules = [
|
|
43
43
|
a1,
|
|
44
44
|
a2,
|
|
@@ -19,7 +19,7 @@ export const SKIP = 'skip';
|
|
|
19
19
|
// не берётся судить покрытие схемы» (встроенный смоук на чужой игре)
|
|
20
20
|
export const ALL_KEYS_UNAUDITED = '*';
|
|
21
21
|
|
|
22
|
-
// список проверок
|
|
22
|
+
// список проверок
|
|
23
23
|
const CHECKS = [
|
|
24
24
|
[1, 'finiteValues', 'no NaN/Infinity in decoded fields and hot buffer'],
|
|
25
25
|
[2, 'snapshotKeysUsed', 'every snapshot key produced at least one row'],
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import clock from '../lib/clock.js';
|
|
2
2
|
|
|
3
|
-
// Рекордер живого
|
|
3
|
+
// Рекордер живого матча: пишет то, что headless
|
|
4
4
|
// не воспроизводит — реальный WebRTC, PixiJS и живой ввод человека — ровно в
|
|
5
5
|
// формат сценария headless-runner'а (devtools/ScenarioRunner.js). Баг,
|
|
6
6
|
// пойманный человеком в браузере, догоняется потом `npm run sim:replay`
|
package/src/host/HostGame.js
CHANGED
|
@@ -16,6 +16,7 @@ import closeCodes from '../config/closeCodes.js';
|
|
|
16
16
|
import clock from '../lib/clock.js';
|
|
17
17
|
import { createResumeKey } from '../lib/resumeKey.js';
|
|
18
18
|
import GameCoreAdapter from './GameCoreAdapter.js';
|
|
19
|
+
import { commandFailureReport } from './commandFailure.js';
|
|
19
20
|
import DebugRecorder from './DebugRecorder.js';
|
|
20
21
|
import enginePkg from '../../package.json' with { type: 'json' };
|
|
21
22
|
|
|
@@ -252,7 +253,7 @@ export default class HostGame {
|
|
|
252
253
|
this._networkSendRate = data.timers.networkSendRate;
|
|
253
254
|
this._snapshotManager = new SnapshotThrottle(this._networkSendRate);
|
|
254
255
|
|
|
255
|
-
// рекордер живого матча
|
|
256
|
+
// рекордер живого матча — только в dev-режиме;
|
|
256
257
|
// в проде null, и все точки записи ниже вырождаются в ?.
|
|
257
258
|
this._recorder = this._isDevMode ? new DebugRecorder() : null;
|
|
258
259
|
|
|
@@ -317,19 +318,22 @@ export default class HostGame {
|
|
|
317
318
|
currentMap: data.currentMap,
|
|
318
319
|
});
|
|
319
320
|
|
|
320
|
-
this._commandProcessor = new CommandProcessor(
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
321
|
+
this._commandProcessor = new CommandProcessor(
|
|
322
|
+
{
|
|
323
|
+
participants: this._participants,
|
|
324
|
+
chat: this._chat,
|
|
325
|
+
scripted: this._scripted,
|
|
326
|
+
roundManager: this._roundManager,
|
|
327
|
+
voteCoordinator: this._voteCoordinator,
|
|
328
|
+
timerManager: this._timerManager,
|
|
329
|
+
playerDataSync: this._playerDataSync,
|
|
330
|
+
teams: this._teams,
|
|
331
|
+
spectatorTeam: this._spectatorTeam,
|
|
332
|
+
spectatorId: this._spectatorId,
|
|
333
|
+
isDevMode: this._isDevMode,
|
|
334
|
+
},
|
|
335
|
+
{ onError: (command, error) => this._reportCommandError(command, error) },
|
|
336
|
+
);
|
|
333
337
|
|
|
334
338
|
// игровые чат-команды и коды системных сообщений из HostPlugin
|
|
335
339
|
for (const command of hostPlugin.chatCommands) {
|
|
@@ -349,6 +353,7 @@ export default class HostGame {
|
|
|
349
353
|
this._checkpointSink = null; // ({ meta, core, final }) => void
|
|
350
354
|
// здоровье хоста (host-migration этап 9a): раз в окно цикла
|
|
351
355
|
this._healthSink = null; // (health) => void
|
|
356
|
+
this._diagnosticSink = null; // ({ kind, message, stack }) => void
|
|
352
357
|
this._checkpointIntervalMs = 0; // 0 — периодических точек нет
|
|
353
358
|
this._lastCheckpointAt = 0;
|
|
354
359
|
this._checkpointRequest = null; // { final } — точка на ближайшем кадре
|
|
@@ -738,7 +743,7 @@ export default class HostGame {
|
|
|
738
743
|
this._mapList.push(...Object.keys(this._maps));
|
|
739
744
|
}
|
|
740
745
|
|
|
741
|
-
// ***** отладочный контур
|
|
746
|
+
// ***** отладочный контур ***** //
|
|
742
747
|
// Всё ниже живёт под флагом isDevMode: в проде рекордера нет, а дамп
|
|
743
748
|
// отдаёт только то, что и так знает мета.
|
|
744
749
|
|
|
@@ -1042,6 +1047,21 @@ export default class HostGame {
|
|
|
1042
1047
|
|
|
1043
1048
|
// ***** здоровье хоста (host-migration этап 9a) ***** //
|
|
1044
1049
|
|
|
1050
|
+
/**
|
|
1051
|
+
* Приёмник сбоев кода игры, пойманных хостом: { kind, message, stack }.
|
|
1052
|
+
* @param {Function|null} sink
|
|
1053
|
+
*/
|
|
1054
|
+
setDiagnosticSink(sink) {
|
|
1055
|
+
this._diagnosticSink = typeof sink === 'function' ? sink : null;
|
|
1056
|
+
}
|
|
1057
|
+
|
|
1058
|
+
// сбой чат-команды игры: отчёт собирает commandFailure.js (одна строка
|
|
1059
|
+
// сообщения, стек без текста ошибки); бросок приёмника гасит
|
|
1060
|
+
// CommandProcessor._fail
|
|
1061
|
+
_reportCommandError(command, error) {
|
|
1062
|
+
this._diagnosticSink?.(commandFailureReport(command, error));
|
|
1063
|
+
}
|
|
1064
|
+
|
|
1045
1065
|
/**
|
|
1046
1066
|
* Приёмник метрик здоровья: раз в окно игрового цикла (~1 с) —
|
|
1047
1067
|
* { tickRate, maxGapMs, lostMs, windowMs, peerRttMedian, peerCount }.
|