vimp-engine 0.35.6 → 0.37.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.
- package/bin/vimp-sim.js +19 -1
- package/core/Cargo.toml +1 -1
- package/package.json +1 -1
- package/src/client/components/controller/Controls.js +5 -0
- package/src/client/components/controller/Lobby.js +4 -4
- package/src/client/components/controller/RoomMenu.js +43 -0
- package/src/client/components/controller/Vote.js +18 -2
- package/src/client/components/model/Chat.js +19 -2
- package/src/client/components/model/ClientReports.js +6 -2
- package/src/client/components/model/Controls.js +27 -0
- package/src/client/components/model/Games.js +5 -1
- package/src/client/components/model/Lobby.js +32 -24
- package/src/client/components/model/LobbyAuth.js +20 -4
- package/src/client/components/model/RoomMenu.js +121 -0
- package/src/client/components/model/Stat.js +2 -1
- package/src/client/components/model/Vote.js +122 -4
- package/src/client/components/view/Auth.js +20 -10
- package/src/client/components/view/ClientReports.js +16 -5
- package/src/client/components/view/Games.js +39 -14
- package/src/client/components/view/Lobby.js +90 -43
- package/src/client/components/view/LobbyAuth.js +13 -7
- package/src/client/components/view/Panel.js +1 -2
- package/src/client/components/view/RoomMenu.js +69 -0
- package/src/client/debug.js +24 -1
- package/src/client/lib/FpsMeter.js +46 -0
- package/src/client/lib/JoinRetry.js +73 -0
- package/src/client/lib/diagnostics.js +28 -5
- package/src/client/lib/hostCaps.js +82 -0
- package/src/client/lib/hostUnloadGuard.js +108 -0
- package/src/client/lib/hostVoteCommand.js +92 -0
- package/src/client/lib/pageReload.js +29 -0
- package/src/client/lib/roomLink.js +257 -0
- package/src/client/lib/signalingErrors.js +74 -0
- package/src/client/main.js +676 -451
- package/src/client/network/HiddenHostHealthLog.js +76 -0
- package/src/client/network/HostConnectionManager.js +62 -8
- package/src/client/network/HostController.js +376 -23
- package/src/client/network/HostHealthPolicy.js +307 -0
- package/src/client/network/HostHealthReporter.js +54 -0
- package/src/client/network/HostPrewarm.js +235 -0
- package/src/client/network/LoopbackTransport.js +16 -1
- package/src/client/network/PlannedHandoff.js +366 -0
- package/src/client/network/Promotion.js +420 -0
- package/src/client/network/RoomPeersReporter.js +81 -0
- package/src/client/network/SessionSupervisor.js +730 -0
- package/src/client/network/SignalingClient.js +183 -30
- package/src/client/network/StandbyReceiver.js +287 -0
- package/src/client/network/StandbySender.js +319 -0
- package/src/client/network/TokenHandoffTimer.js +81 -0
- package/src/client/network/WebRtcManager.js +338 -11
- package/src/client/network/policyClose.js +32 -2
- package/src/client/network/standbyChunks.js +185 -0
- package/src/client/session/GuestSession.js +428 -0
- package/src/client/session/HandoffFlow.js +399 -0
- package/src/client/session/HostRole.js +708 -0
- package/src/client/session/Membership.js +179 -0
- package/src/client/session/PromotionFlow.js +325 -0
- package/src/client/session/RouteBoot.js +432 -0
- package/src/client/session/StandbyRole.js +195 -0
- package/src/client/session/hostRoomPrep.js +152 -0
- package/src/client/session/roomContext.js +32 -0
- package/src/client/style.css +83 -22
- package/src/client/views/gameShell.js +7 -0
- package/src/client/views/includes/informer.pug +4 -0
- package/src/client/views/includes/lobby.pug +0 -3
- package/src/client/views/includes/roomMenu.pug +9 -0
- package/src/client/views/index.pug +1 -0
- package/src/config/chatMessages.js +45 -0
- package/src/config/closeCodes.js +1 -1
- package/src/config/env.js +37 -0
- package/src/config/hostDefaults.js +24 -0
- package/src/config/lobby.js +154 -3
- package/src/config/master.js +104 -21
- package/src/config/wsports.js +8 -0
- package/src/devtools/RecordingSocketManager.js +3 -2
- package/src/devtools/ScenarioRunner.js +123 -8
- package/src/devtools/VirtualClient.js +20 -0
- package/src/devtools/contract/format.js +4 -1
- package/src/devtools/contract/index.js +1 -2
- package/src/devtools/contract/rules/a2-package-scripts.js +1 -3
- package/src/devtools/contract/rules/b8-system-messages.js +2 -2
- package/src/devtools/contract/rules/b9-vote-names.js +25 -1
- package/src/devtools/contract/rules/c11-stat-mode.js +4 -1
- package/src/devtools/contract/rules/c6-stat-columns.js +1 -1
- package/src/devtools/contract/rules/c7-key-sets.js +8 -4
- package/src/devtools/contract/rules/d3-snapshot-interp.js +2 -1
- package/src/devtools/contract/rules/e1-sound-pairs.js +3 -1
- package/src/devtools/contract/rules/e4-map-layers.js +13 -2
- package/src/devtools/contract/rules/e5-map-radar-walls.js +7 -1
- package/src/devtools/contract/rules/index.js +38 -5
- package/src/devtools/invariants.js +10 -4
- package/src/devtools/pluginLoader.js +1 -4
- package/src/devtools/report.js +17 -1
- package/src/host/DebugRecorder.js +6 -1
- package/src/host/GameCoreAdapter.js +45 -0
- package/src/host/HostGame.js +928 -59
- package/src/host/PortMachine.js +268 -20
- package/src/host/host.worker.js +222 -16
- package/src/host/meta/SocketManager.js +20 -0
- package/src/host/meta/core/RoundManager.js +89 -14
- package/src/host/meta/modules/Accolades.js +4 -1
- package/src/host/meta/modules/Panel.js +30 -0
- package/src/host/meta/modules/PlayerDataSync.js +255 -46
- package/src/host/meta/modules/RTTManager.js +34 -0
- package/src/host/meta/modules/TimerManager.js +215 -18
- package/src/host/meta/modules/chat/systemMessages.js +29 -3
- package/src/host/meta/player/HumanParticipant.js +10 -0
- package/src/host/meta/player/ParticipantManager.js +98 -5
- package/src/lib/AbstractTimer.js +63 -1
- package/src/lib/Publisher.js +26 -0
- package/src/lib/capabilities.js +5 -0
- package/src/lib/checkpointCodec.js +125 -0
- package/src/lib/clientIp.js +9 -2
- package/src/lib/createHostRuntime.js +95 -6
- package/src/lib/formOptions.js +3 -1
- package/src/lib/gameConfigView.js +10 -0
- package/src/lib/gameId.js +13 -0
- package/src/lib/jwt.js +3 -1
- package/src/lib/loadGamePackage.js +2 -2
- package/src/lib/resumeKey.js +38 -0
- package/src/lib/roomId.js +39 -0
- package/src/lib/roomSettings.js +59 -0
- package/src/lib/validators.js +7 -0
- package/tests/fixtures/generations/gen-api3/host/ScriptedManager.js +2 -3
- package/tests/fixtures/generations/gen-api3/host/fakeCore.js +31 -5
- package/tests/fixtures/generations/gen-api3/manifest.json +1 -3
- package/tests/fixtures/generations/gen-api4/host/ScriptedManager.js +2 -3
- package/tests/fixtures/generations/gen-api4/host/fakeCore.js +31 -5
- package/tests/fixtures/generations/gen-api4/manifest.json +1 -3
- package/tests/fixtures/miniGame/config/client.js +0 -20
- package/tests/fixtures/miniGame/host/ScriptedManager.js +2 -3
- package/tests/fixtures/miniGame/host/fakeCore.js +31 -5
|
@@ -29,8 +29,6 @@ export default {
|
|
|
29
29
|
const scripts = ctx.pkg.scripts ?? {};
|
|
30
30
|
const missing = REQUIRED.filter(name => !scripts[name]);
|
|
31
31
|
|
|
32
|
-
return verdict(
|
|
33
|
-
missing.map(name => `script "${name}" is missing`),
|
|
34
|
-
);
|
|
32
|
+
return verdict(missing.map(name => `script "${name}" is missing`));
|
|
35
33
|
},
|
|
36
34
|
};
|
|
@@ -3,8 +3,8 @@ import { ERROR, skip, verdict } from '../result.js';
|
|
|
3
3
|
// Регистрация игровых кодов — слепой Object.assign поверх движкового
|
|
4
4
|
// реестра: код в зарезервированном диапазоне затирает движковое сообщение
|
|
5
5
|
// без предупреждения, и вместо «Vote passed» игрок видит игровой текст.
|
|
6
|
-
// Диапазоны — длины движковых групп (
|
|
7
|
-
const RESERVED = { s:
|
|
6
|
+
// Диапазоны — длины движковых групп (config/chatMessages.js).
|
|
7
|
+
export const RESERVED = { s: 11, v: 15, m: 1, c: 1, n: 1 };
|
|
8
8
|
|
|
9
9
|
export default {
|
|
10
10
|
id: 'B8',
|
|
@@ -8,11 +8,19 @@ import { ERROR, skip, verdict } from '../result.js';
|
|
|
8
8
|
const ENGINE_MENU = ['teamChange', 'mapChange'];
|
|
9
9
|
const ENGINE_TEMPLATES = ['teamChange', 'mapChangeBySystem', 'mapChangeByUser'];
|
|
10
10
|
|
|
11
|
+
// Имена с '@' — голосования самого движка (host-migration этап 10:
|
|
12
|
+
// '@changeHost' — окно «Change host?» от мастера). Ответ на голосование с
|
|
13
|
+
// таким именем клиент перехватывает до хоста — игровое голосование под ним
|
|
14
|
+
// не дошло бы до игры никогда
|
|
15
|
+
const ENGINE_PREFIX = '@';
|
|
16
|
+
const isEngineName = name =>
|
|
17
|
+
typeof name === 'string' && name.startsWith(ENGINE_PREFIX);
|
|
18
|
+
|
|
11
19
|
export default {
|
|
12
20
|
id: 'B9',
|
|
13
21
|
name: 'voteNames',
|
|
14
22
|
level: ERROR,
|
|
15
|
-
title: 'custom votes do not reuse the reserved vote names',
|
|
23
|
+
title: 'custom votes do not reuse the reserved vote names or the @ prefix',
|
|
16
24
|
|
|
17
25
|
check(ctx) {
|
|
18
26
|
const vote = ctx.clientConfig?.modules?.vote?.params;
|
|
@@ -25,6 +33,14 @@ export default {
|
|
|
25
33
|
const violations = [];
|
|
26
34
|
|
|
27
35
|
for (const name of templates) {
|
|
36
|
+
if (isEngineName(name)) {
|
|
37
|
+
violations.push(
|
|
38
|
+
`vote template "${name}" starts with "${ENGINE_PREFIX}" — names ` +
|
|
39
|
+
`with this prefix are reserved for engine votes`,
|
|
40
|
+
);
|
|
41
|
+
continue;
|
|
42
|
+
}
|
|
43
|
+
|
|
28
44
|
if (ENGINE_MENU.includes(name) && !ENGINE_TEMPLATES.includes(name)) {
|
|
29
45
|
violations.push(
|
|
30
46
|
`vote template "${name}" reuses a reserved vote name — the engine ` +
|
|
@@ -36,6 +52,14 @@ export default {
|
|
|
36
52
|
for (const entry of vote.menu ?? []) {
|
|
37
53
|
const name = Array.isArray(entry) ? entry[0] : entry;
|
|
38
54
|
|
|
55
|
+
if (isEngineName(name)) {
|
|
56
|
+
violations.push(
|
|
57
|
+
`vote menu entry "${name}" starts with "${ENGINE_PREFIX}" — names ` +
|
|
58
|
+
`with this prefix are reserved for engine votes`,
|
|
59
|
+
);
|
|
60
|
+
continue;
|
|
61
|
+
}
|
|
62
|
+
|
|
39
63
|
if (!ENGINE_MENU.includes(name) && !templates.includes(name)) {
|
|
40
64
|
violations.push(
|
|
41
65
|
`vote menu entry "${name}" has no template — the vote renders empty`,
|
|
@@ -33,7 +33,10 @@ export default {
|
|
|
33
33
|
const host = ctx.gameConfig.statMode ?? null;
|
|
34
34
|
|
|
35
35
|
if (client === host) {
|
|
36
|
-
return verdict(
|
|
36
|
+
return verdict(
|
|
37
|
+
[],
|
|
38
|
+
client ? `both declare '${client}'` : 'the room table',
|
|
39
|
+
);
|
|
37
40
|
}
|
|
38
41
|
|
|
39
42
|
if (client && !host) {
|
|
@@ -100,7 +100,7 @@ export default {
|
|
|
100
100
|
// ли игра свои стили под этот список
|
|
101
101
|
if (params.mode === 'leaderboard') {
|
|
102
102
|
return styledLeaderboard(ctx.clientPlugin?.styles)
|
|
103
|
-
? verdict([],
|
|
103
|
+
? verdict([], "leaderboard list, laid out by the plugin's own styles")
|
|
104
104
|
: verdict([
|
|
105
105
|
"stat declares mode 'leaderboard', but ClientPlugin.styles says " +
|
|
106
106
|
'nothing about #stat .stat-leaderboard: the engine CSS gives ' +
|
|
@@ -4,7 +4,13 @@ import { ERROR, skip, verdict } from '../result.js';
|
|
|
4
4
|
// prevPlayer наблюдатель заперт на одной камере; занятый движковый код
|
|
5
5
|
// (чат, голосование, статистика, escape, enter) до игры не доходит;
|
|
6
6
|
// расхождение имён с playerKeys даёт клавишу, которая ничего не шлёт.
|
|
7
|
-
const ENGINE_CODES = {
|
|
7
|
+
const ENGINE_CODES = {
|
|
8
|
+
9: 'stat',
|
|
9
|
+
13: 'enter',
|
|
10
|
+
27: 'escape',
|
|
11
|
+
67: 'chat',
|
|
12
|
+
77: 'vote',
|
|
13
|
+
};
|
|
8
14
|
const SPECTATOR_ACTIONS = ['nextPlayer', 'prevPlayer'];
|
|
9
15
|
|
|
10
16
|
export default {
|
|
@@ -51,9 +57,7 @@ export default {
|
|
|
51
57
|
|
|
52
58
|
for (const action of declared) {
|
|
53
59
|
if (!bound.has(action)) {
|
|
54
|
-
violations.push(
|
|
55
|
-
`playerKeys."${action}" has no key in keySetList[1]`,
|
|
56
|
-
);
|
|
60
|
+
violations.push(`playerKeys."${action}" has no key in keySetList[1]`);
|
|
57
61
|
}
|
|
58
62
|
}
|
|
59
63
|
|
|
@@ -12,7 +12,8 @@ export default {
|
|
|
12
12
|
id: 'D3',
|
|
13
13
|
name: 'snapshotInterp',
|
|
14
14
|
level: ERROR,
|
|
15
|
-
title:
|
|
15
|
+
title:
|
|
16
|
+
"interp only on f32 fields of class 'hot' blocks; role 'state' is a u8 right after the row head",
|
|
16
17
|
|
|
17
18
|
check(ctx) {
|
|
18
19
|
const snapshot = ctx.gameConfig?.snapshot;
|
|
@@ -30,7 +30,9 @@ export default {
|
|
|
30
30
|
'mp3',
|
|
31
31
|
]) {
|
|
32
32
|
if (!ctx.distFiles.has(`sounds/${file}.${codec}`)) {
|
|
33
|
-
violations.push(
|
|
33
|
+
violations.push(
|
|
34
|
+
`sound "${name}": dist/sounds/${file}.${codec} is missing`,
|
|
35
|
+
);
|
|
34
36
|
}
|
|
35
37
|
}
|
|
36
38
|
}
|
|
@@ -133,7 +133,13 @@ function checkMap(name, map, violations) {
|
|
|
133
133
|
}
|
|
134
134
|
|
|
135
135
|
// 8. тайл рендер-слоя обязан существовать в тайл-листе по индексу
|
|
136
|
-
checkLayerFrames(
|
|
136
|
+
checkLayerFrames(
|
|
137
|
+
at,
|
|
138
|
+
`level ${key}`,
|
|
139
|
+
level?.layers,
|
|
140
|
+
map.spriteSheet,
|
|
141
|
+
violations,
|
|
142
|
+
);
|
|
137
143
|
|
|
138
144
|
// 11. высоты объёмов уровня
|
|
139
145
|
checkVolumes(at, key, level?.layers, level?.volumes, violations);
|
|
@@ -325,7 +331,12 @@ function rampRunExits(grid, tile, dir) {
|
|
|
325
331
|
return out;
|
|
326
332
|
}
|
|
327
333
|
|
|
328
|
-
const NEIGHBOURS = [
|
|
334
|
+
const NEIGHBOURS = [
|
|
335
|
+
[1, 0],
|
|
336
|
+
[-1, 0],
|
|
337
|
+
[0, 1],
|
|
338
|
+
[0, -1],
|
|
339
|
+
];
|
|
329
340
|
|
|
330
341
|
function checkLevelEdges(at, map, violations) {
|
|
331
342
|
for (const [key, level] of Object.entries(map.levels ?? {})) {
|
|
@@ -43,7 +43,13 @@ export default {
|
|
|
43
43
|
function checkMap(at, map, violations) {
|
|
44
44
|
let checked = 0;
|
|
45
45
|
|
|
46
|
-
checked += checkGrid(
|
|
46
|
+
checked += checkGrid(
|
|
47
|
+
at,
|
|
48
|
+
'level 0',
|
|
49
|
+
map?.layers,
|
|
50
|
+
map?.physicsStatic,
|
|
51
|
+
violations,
|
|
52
|
+
);
|
|
47
53
|
|
|
48
54
|
for (const [key, level] of Object.entries(map?.levels ?? {})) {
|
|
49
55
|
checked += checkGrid(
|
|
@@ -40,11 +40,44 @@ import e7 from './e7-map-game-field.js';
|
|
|
40
40
|
// Порядок групп — порядок отчёта: A пакет и сборка, B host, C client,
|
|
41
41
|
// D снапшот, E ассеты (plan/create-vimp-game/stage_1.md).
|
|
42
42
|
export const rules = [
|
|
43
|
-
a1,
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
43
|
+
a1,
|
|
44
|
+
a2,
|
|
45
|
+
a3,
|
|
46
|
+
a4,
|
|
47
|
+
a5,
|
|
48
|
+
a6,
|
|
49
|
+
a7,
|
|
50
|
+
b1,
|
|
51
|
+
b2,
|
|
52
|
+
b3,
|
|
53
|
+
b4,
|
|
54
|
+
b5,
|
|
55
|
+
b6,
|
|
56
|
+
b7,
|
|
57
|
+
b8,
|
|
58
|
+
b9,
|
|
59
|
+
b10,
|
|
60
|
+
c1,
|
|
61
|
+
c2,
|
|
62
|
+
c3,
|
|
63
|
+
c4,
|
|
64
|
+
c5,
|
|
65
|
+
c6,
|
|
66
|
+
c7,
|
|
67
|
+
c8,
|
|
68
|
+
c9,
|
|
69
|
+
c10,
|
|
70
|
+
c11,
|
|
71
|
+
d1,
|
|
72
|
+
d2,
|
|
73
|
+
d3,
|
|
74
|
+
e1,
|
|
75
|
+
e2,
|
|
76
|
+
e3,
|
|
77
|
+
e4,
|
|
78
|
+
e5,
|
|
79
|
+
e6,
|
|
80
|
+
e7,
|
|
48
81
|
];
|
|
49
82
|
|
|
50
83
|
export default rules;
|
|
@@ -35,7 +35,9 @@ const CHECKS = [
|
|
|
35
35
|
[12, 'determinism', 'two runs produce a byte-identical frame stream'],
|
|
36
36
|
];
|
|
37
37
|
|
|
38
|
-
const TITLES = new Map(
|
|
38
|
+
const TITLES = new Map(
|
|
39
|
+
CHECKS.map(([id, name, title]) => [name, { id, title }]),
|
|
40
|
+
);
|
|
39
41
|
|
|
40
42
|
/**
|
|
41
43
|
* Выполняет проверки 1–11 по итогам прогона (12 — только по требованию,
|
|
@@ -223,7 +225,9 @@ function frameFormat({ clients, socketManager }) {
|
|
|
223
225
|
|
|
224
226
|
for (const client of clients) {
|
|
225
227
|
for (const error of client.decodeErrors) {
|
|
226
|
-
violations.push(
|
|
228
|
+
violations.push(
|
|
229
|
+
`${client.socketId}: push_frame threw — ${error.message}`,
|
|
230
|
+
);
|
|
227
231
|
}
|
|
228
232
|
}
|
|
229
233
|
|
|
@@ -277,7 +281,7 @@ function hotLayout({ clients }) {
|
|
|
277
281
|
item.message
|
|
278
282
|
? `${client.socketId}: ${item.message} (len ${item.len})`
|
|
279
283
|
: `${client.socketId}: hot buffer len ${item.len}, traversal consumed ` +
|
|
280
|
-
|
|
284
|
+
`${item.consumed} — record width or group order drifted`,
|
|
281
285
|
);
|
|
282
286
|
}
|
|
283
287
|
|
|
@@ -463,7 +467,9 @@ function predictionDrift({ clients, scenario }) {
|
|
|
463
467
|
(client.divergenceStats.dropped ?? 0);
|
|
464
468
|
|
|
465
469
|
if (hidden) {
|
|
466
|
-
violations.push(
|
|
470
|
+
violations.push(
|
|
471
|
+
`${client.socketId}: +${hidden} more divergence record(s)`,
|
|
472
|
+
);
|
|
467
473
|
}
|
|
468
474
|
}
|
|
469
475
|
|
|
@@ -16,10 +16,7 @@ import { loadGamePackage } from '../lib/loadGamePackage.js';
|
|
|
16
16
|
// пути в манифесте. Его отсутствие не ошибка — просто игра не прогоняется
|
|
17
17
|
// headless своим настоящим ядром.
|
|
18
18
|
|
|
19
|
-
const FIXTURE_DIR = new URL(
|
|
20
|
-
'../../tests/fixtures/miniGame/',
|
|
21
|
-
import.meta.url,
|
|
22
|
-
);
|
|
19
|
+
const FIXTURE_DIR = new URL('../../tests/fixtures/miniGame/', import.meta.url);
|
|
23
20
|
|
|
24
21
|
// значение поля source у фикстуры: по нему отличается «своя игра, схему
|
|
25
22
|
// которой инструмент знает» от чужого плагина (см. builtinScenario.js)
|
package/src/devtools/report.js
CHANGED
|
@@ -103,6 +103,20 @@ export function formatMarkdown(report) {
|
|
|
103
103
|
}
|
|
104
104
|
}
|
|
105
105
|
|
|
106
|
+
// смены хоста через контрольную точку: 'soft' у игры, которая ждала
|
|
107
|
+
// 'midRound', — первое, что автор должен увидеть
|
|
108
|
+
if (report.checkpointRestores?.length) {
|
|
109
|
+
lines.push('');
|
|
110
|
+
lines.push('## Host checkpoints');
|
|
111
|
+
lines.push('');
|
|
112
|
+
|
|
113
|
+
for (const restore of report.checkpointRestores) {
|
|
114
|
+
lines.push(
|
|
115
|
+
`- tick ${restore.tick}: \`${restore.mode}\`, ${restore.bytes} B`,
|
|
116
|
+
);
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
|
|
106
120
|
lines.push('');
|
|
107
121
|
|
|
108
122
|
return `${lines.join('\n')}\n`;
|
|
@@ -191,7 +205,9 @@ function invariantSection(report) {
|
|
|
191
205
|
const mark = { pass: '✅', fail: '❌', skip: '⏭️' }[check.status];
|
|
192
206
|
const note = check.note ? ` — ${check.note}` : '';
|
|
193
207
|
|
|
194
|
-
lines.push(
|
|
208
|
+
lines.push(
|
|
209
|
+
`- ${mark} ${check.id}. \`${check.name}\`: ${check.title}${note}`,
|
|
210
|
+
);
|
|
195
211
|
|
|
196
212
|
for (const violation of check.violations) {
|
|
197
213
|
lines.push(` - ${violation}`);
|
|
@@ -48,7 +48,12 @@ export default class DebugRecorder {
|
|
|
48
48
|
* @param {Array} [params.participants] - [{ gameId, name, model, socketId,
|
|
49
49
|
* team }] — люди, уже находящиеся в комнате.
|
|
50
50
|
*/
|
|
51
|
-
start({
|
|
51
|
+
start({
|
|
52
|
+
seed = null,
|
|
53
|
+
map = null,
|
|
54
|
+
networkSendRate = null,
|
|
55
|
+
participants = [],
|
|
56
|
+
}) {
|
|
52
57
|
this._reset();
|
|
53
58
|
|
|
54
59
|
this._recording = true;
|
|
@@ -241,6 +241,45 @@ export default class GameCoreAdapter {
|
|
|
241
241
|
);
|
|
242
242
|
}
|
|
243
243
|
|
|
244
|
+
// ***** контрольная точка (host-migration этап 5) ***** //
|
|
245
|
+
|
|
246
|
+
/**
|
|
247
|
+
* Дамп состояния ядра (замороженный ABI `serialize_state`). Звать только
|
|
248
|
+
* на границе кадра — сразу после packBody: накопители снапшота должны
|
|
249
|
+
* быть опустошены (предусловие ядра).
|
|
250
|
+
* @returns {Uint8Array} Собственная копия байтов в JS-куче.
|
|
251
|
+
* @throws {Error} Ядро не смогло сериализовать мир — точка пропускается.
|
|
252
|
+
*/
|
|
253
|
+
serializeState() {
|
|
254
|
+
let bytes;
|
|
255
|
+
|
|
256
|
+
try {
|
|
257
|
+
bytes = this._core.serialize_state();
|
|
258
|
+
} catch (e) {
|
|
259
|
+
throw new Error(`core serialize_state failed: ${errorText(e)}`);
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
if (!(bytes instanceof Uint8Array)) {
|
|
263
|
+
throw new Error('core serialize_state returned no bytes');
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
return bytes;
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
/**
|
|
270
|
+
* Поднимает мир ядра из дампа. Конфиг ядра обязан совпадать с конфигом
|
|
271
|
+
* ядра, снявшего дамп.
|
|
272
|
+
* @param {Uint8Array} bytes
|
|
273
|
+
* @throws {Error} Битый или несовместимый дамп.
|
|
274
|
+
*/
|
|
275
|
+
deserializeState(bytes) {
|
|
276
|
+
try {
|
|
277
|
+
this._core.deserialize_state(bytes);
|
|
278
|
+
} catch (e) {
|
|
279
|
+
throw new Error(`core deserialize_state failed: ${errorText(e)}`);
|
|
280
|
+
}
|
|
281
|
+
}
|
|
282
|
+
|
|
244
283
|
// ***** отладка ***** //
|
|
245
284
|
|
|
246
285
|
/**
|
|
@@ -296,3 +335,9 @@ export default class GameCoreAdapter {
|
|
|
296
335
|
return Boolean(participant && participant.isScripted);
|
|
297
336
|
}
|
|
298
337
|
}
|
|
338
|
+
|
|
339
|
+
// JsError wasm-bindgen и обычный Error — у обоих есть message; строка из
|
|
340
|
+
// throw внутри фейка — тоже ответ
|
|
341
|
+
function errorText(e) {
|
|
342
|
+
return e && e.message ? e.message : String(e);
|
|
343
|
+
}
|