@volter/play 0.5.212 → 0.5.213
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/README.md +82 -51
- package/contributions/game-panel.tsx +228 -329
- package/contributions/levels.finder.ts +43 -0
- package/contributions/play.page.tsx +56 -174
- package/contributions/play.verbs.ts +74 -0
- package/contributions/{std-scene.document.tsx → std-level.document.tsx} +154 -22
- package/dist-node/serving.mjs +67 -36
- package/package.json +75 -53
- package/serving/index.ts +1 -1
- package/serving/std-dev.ts +86 -53
- package/serving/std-scene-runtime.js +128 -7
- package/serving/std-scene.ts +48 -21
- package/src/editor-blender.ts +4 -19
- package/src/editor-threejs.ts +1 -1
- package/src/game-controls.ts +111 -0
- package/src/game-run.ts +165 -0
- package/src/game-view.tsx +219 -0
- package/src/play-clock.ts +4 -4
- package/src/play-log.ts +57 -73
- package/src/std-controls.ts +7 -7
- package/src/std-export.ts +0 -8
- package/src/std-game-adapter.ts +10 -4
- package/src/std-game.ts +316 -190
- package/src/std-level-files.ts +18 -1
- package/src/std-level.ts +4 -4
- package/src/std-view-bar.tsx +7 -17
- package/contributions/model-play.verbs.ts +0 -44
- package/src/model-play.ts +0 -149
- package/src/std-scene-level.ts +0 -59
package/dist-node/serving.mjs
CHANGED
|
@@ -83,7 +83,7 @@ var blenderHalfOf = (root) => join(stdDev(root), "blender", "blend-to-scene.py")
|
|
|
83
83
|
var isLevelGlb = (path) => (/^levels\/.+\.glb$/.test(path) || /^prefabs\/([^/]+)\/\1\.glb$/.test(path)) && !path.includes("\\") && path.split("/").every((part) => part !== "" && part !== "." && part !== "..");
|
|
84
84
|
function runStdLevel(root, glb) {
|
|
85
85
|
return new Promise((resolve2) => {
|
|
86
|
-
const run = spawn(process.execPath, [join(stdDev(root), "bin", "std.js"), "level", glb
|
|
86
|
+
const run = spawn(process.execPath, [join(stdDev(root), "bin", "std.js"), "level-scene", glb], { cwd: root, windowsHide: true });
|
|
87
87
|
let output = "";
|
|
88
88
|
run.stdout.on("data", (chunk) => {
|
|
89
89
|
output += chunk.toString();
|
|
@@ -257,28 +257,35 @@ async function runningServer(root) {
|
|
|
257
257
|
const record = JSON.parse(text);
|
|
258
258
|
return alive(record.pid) ? record : null;
|
|
259
259
|
}
|
|
260
|
-
var
|
|
260
|
+
var OUTPUT_KEPT = 500;
|
|
261
261
|
var LINE_MOST = 4e3;
|
|
262
262
|
var owned = /* @__PURE__ */ new Map();
|
|
263
|
-
function
|
|
264
|
-
const text = chunk === null ?
|
|
265
|
-
const lines = stripVTControlCharacters(
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
const over =
|
|
263
|
+
function heardOutput(stream, chunk) {
|
|
264
|
+
const text = chunk === null ? stream.decoder.end() : stream.decoder.write(chunk);
|
|
265
|
+
const lines = stripVTControlCharacters(stream.partial + text).split(/\r?\n/);
|
|
266
|
+
stream.partial = chunk === null ? "" : (lines.pop() ?? "").slice(-LINE_MOST);
|
|
267
|
+
stream.lines.push(...lines.filter((line, at) => chunk !== null || at < lines.length - 1 || line !== ""));
|
|
268
|
+
const over = stream.lines.length - OUTPUT_KEPT;
|
|
269
269
|
if (over > 0) {
|
|
270
|
-
|
|
271
|
-
|
|
270
|
+
stream.lines.splice(0, over);
|
|
271
|
+
stream.dropped += over;
|
|
272
272
|
}
|
|
273
273
|
}
|
|
274
|
-
|
|
274
|
+
var lineOf = (asked) => asked === null ? null : Number(asked) || 0;
|
|
275
|
+
function streamFrom(stream, from) {
|
|
276
|
+
const next = stream.dropped + stream.lines.length;
|
|
277
|
+
return { lines: from === null ? [] : stream.lines.slice(Math.max(0, from - stream.dropped)), next };
|
|
278
|
+
}
|
|
279
|
+
function outputFrom(root, server, from) {
|
|
275
280
|
const mine = owned.get(root);
|
|
276
281
|
const pid = mine?.child.pid ?? null;
|
|
277
|
-
if (!mine || pid === null) return { lines: [], next: 0,
|
|
278
|
-
const
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
+
if (!mine || pid === null) return { server: null, stdout: { lines: [], next: 0 }, stderr: { lines: [], next: 0 } };
|
|
283
|
+
const start = (line) => line === null ? null : server === pid ? line : 0;
|
|
284
|
+
return {
|
|
285
|
+
server: pid,
|
|
286
|
+
stdout: streamFrom(mine.streams.stdout, start(from.stdout)),
|
|
287
|
+
stderr: streamFrom(mine.streams.stderr, start(from.stderr))
|
|
288
|
+
};
|
|
282
289
|
}
|
|
283
290
|
function endOwned() {
|
|
284
291
|
for (const { child } of owned.values()) if (child.pid !== void 0 && child.exitCode === null) signalProcessTree(child.pid, "SIGTERM");
|
|
@@ -291,15 +298,18 @@ async function startedServer(root) {
|
|
|
291
298
|
if (!mine || mine.child.exitCode !== null || mine.failed !== null) {
|
|
292
299
|
const npm = npmProcess(["run", "dev", "--", "--no-open"], root);
|
|
293
300
|
const child = spawn(npm.program, npm.args, { cwd: root, detached: process.platform !== "win32", windowsHide: true, stdio: ["ignore", "pipe", "pipe"] });
|
|
294
|
-
|
|
301
|
+
const stream = () => ({ lines: [], dropped: 0, partial: "", decoder: new StringDecoder("utf8") });
|
|
302
|
+
mine = { child, said: "", failed: null, streams: { stdout: stream(), stderr: stream() } };
|
|
295
303
|
const kept = mine;
|
|
296
304
|
const hear = (chunk) => {
|
|
297
305
|
kept.said = (kept.said + chunk.toString("utf8")).slice(-4e3);
|
|
298
306
|
};
|
|
299
307
|
child.stdout?.on("data", hear);
|
|
300
308
|
child.stderr?.on("data", hear);
|
|
301
|
-
|
|
302
|
-
|
|
309
|
+
for (const name of ["stdout", "stderr"]) {
|
|
310
|
+
child[name]?.on("data", (chunk) => heardOutput(kept.streams[name], chunk));
|
|
311
|
+
child[name]?.on("end", () => heardOutput(kept.streams[name], null));
|
|
312
|
+
}
|
|
303
313
|
child.on("error", (error) => {
|
|
304
314
|
kept.failed = error.message;
|
|
305
315
|
});
|
|
@@ -360,8 +370,8 @@ async function traceFrom(root, page, from) {
|
|
|
360
370
|
var PLAY_MS = 15 * 6e4;
|
|
361
371
|
async function playGoal(root, body, response) {
|
|
362
372
|
const asked = body;
|
|
363
|
-
if (typeof asked?.goal !== "string" || asked.goal === "" || asked.bots !== void 0 && !(Number.isInteger(asked.bots) && Number(asked.bots) >= 1))
|
|
364
|
-
return answer(response, 400, { error: `A play is { goal, bots? }: a goal of the game's bot,
|
|
373
|
+
if (typeof asked?.goal !== "string" || asked.goal === "" || asked.bots !== void 0 && !(Number.isInteger(asked.bots) && Number(asked.bots) >= 1) || asked.device !== void 0 && (typeof asked.device !== "string" || asked.device === ""))
|
|
374
|
+
return answer(response, 400, { error: `A play is { goal, bots?, device? }: a goal of the game's bot, a whole number of bots, and one of std's devices by its id; it was given ${JSON.stringify(body)}.` });
|
|
365
375
|
const running = await runningServer(root);
|
|
366
376
|
if (!running) return answer(response, 409, { error: "The game's dev server is not running: press Play." });
|
|
367
377
|
response.statusCode = 200;
|
|
@@ -382,7 +392,7 @@ async function playGoal(root, body, response) {
|
|
|
382
392
|
if (socket.readyState === WebSocket.OPEN || socket.readyState === WebSocket.CONNECTING) socket.close();
|
|
383
393
|
};
|
|
384
394
|
const limit = setTimeout(() => end(false, `Play ended std's play of ${asked.goal}: it ran ${PLAY_MS / 6e4} minutes.`), PLAY_MS);
|
|
385
|
-
socket.addEventListener("open", () => socket.send(JSON.stringify({ goal: asked.goal, bots: asked.bots ?? 1, device: "headless" })));
|
|
395
|
+
socket.addEventListener("open", () => socket.send(JSON.stringify({ goal: asked.goal, bots: asked.bots ?? 1, device: asked.device ?? "headless" })));
|
|
386
396
|
socket.addEventListener("message", (event) => {
|
|
387
397
|
const message = JSON.parse(String(event.data));
|
|
388
398
|
if (message.end !== void 0) end(message.end.ok === true);
|
|
@@ -407,7 +417,9 @@ function answer(response, status, value) {
|
|
|
407
417
|
async function askStd(root, path, init) {
|
|
408
418
|
const running = await runningServer(root);
|
|
409
419
|
if (!running) return { status: 409, body: { error: "The game's dev server is not running: press Play." } };
|
|
410
|
-
const
|
|
420
|
+
const at = new URL(path, running.url);
|
|
421
|
+
at.searchParams.set("token", running.token);
|
|
422
|
+
const response = await fetch(at, init);
|
|
411
423
|
return { status: response.status, body: await response.json() };
|
|
412
424
|
}
|
|
413
425
|
function stdDevPlugin(services) {
|
|
@@ -449,14 +461,14 @@ function stdDevPlugin(services) {
|
|
|
449
461
|
return;
|
|
450
462
|
}
|
|
451
463
|
const control = /^\/devices\/([^/]+)\/control$/.exec(url.pathname)?.[1];
|
|
452
|
-
const asked = url.pathname === "/" && request.method === "GET" ? Promise.resolve({ status: 200, body: stdGame(root) }) : url.pathname === "/start" && request.method === "POST" ? startedServer(root).then(({ url: at }) => ({ status: 200, body: { url: at } })) : url.pathname === "/devices" && request.method === "GET" ? askStd(root, "/__std/devices") : control !== void 0 && request.method === "POST" ? askStd(
|
|
464
|
+
const asked = url.pathname === "/" && request.method === "GET" ? Promise.resolve({ status: 200, body: stdGame(root) }) : url.pathname === "/start" && request.method === "POST" ? startedServer(root).then(({ url: at }) => ({ status: 200, body: { url: at } })) : url.pathname === "/devices" && request.method === "GET" ? askStd(root, "/__std/devices") : url.pathname === "/scene" && request.method === "GET" ? askStd(root, `/__std/scene${url.search}`) : control !== void 0 && request.method === "POST" ? askStd(
|
|
453
465
|
root,
|
|
454
466
|
`/__std/devices/${encodeURIComponent(decodeURIComponent(control))}/control`,
|
|
455
467
|
{ method: "POST", headers: { "content-type": "application/json" }, body: JSON.stringify(bodyOf(request)) }
|
|
456
|
-
) : url.pathname === "/level" && (request.method === "GET" || request.method === "POST") ? levelAnswer(root, request.method, bodyOf(request)) : url.pathname === "/goals" && request.method === "GET" ? botGoals(root) : url.pathname === "/pages" && request.method === "GET" ? pageSlots(root).then((body) => ({ status: 200, body })) : url.pathname === "/trace" && request.method === "GET" && PAGE_ID.test(url.searchParams.get("page") ?? "") ? traceFrom(root, url.searchParams.get("page"), Number(url.searchParams.get("from") ?? 0)).then((body) => ({ status: 200, body })) : url.pathname === "/
|
|
468
|
+
) : url.pathname === "/level" && (request.method === "GET" || request.method === "POST") ? levelAnswer(root, request.method, bodyOf(request)) : url.pathname === "/goals" && request.method === "GET" ? botGoals(root) : url.pathname === "/pages" && request.method === "GET" ? pageSlots(root).then((body) => ({ status: 200, body })) : url.pathname === "/trace" && request.method === "GET" && PAGE_ID.test(url.searchParams.get("page") ?? "") ? traceFrom(root, url.searchParams.get("page"), Number(url.searchParams.get("from") ?? 0)).then((body) => ({ status: 200, body })) : url.pathname === "/output" && request.method === "GET" ? Promise.resolve({ status: 200, body: outputFrom(
|
|
457
469
|
root,
|
|
458
470
|
url.searchParams.has("server") ? Number(url.searchParams.get("server")) : null,
|
|
459
|
-
url.searchParams.
|
|
471
|
+
{ stdout: lineOf(url.searchParams.get("stdout")), stderr: lineOf(url.searchParams.get("stderr")) }
|
|
460
472
|
) }) : Promise.resolve({ status: 404, body: { error: `${STD_ROUTE}${url.pathname} is not a route of Play's` } });
|
|
461
473
|
asked.then(
|
|
462
474
|
({ status, body }) => answer(response, status, body),
|
|
@@ -511,6 +523,8 @@ function levelFiles(blend) {
|
|
|
511
523
|
grading: `${dir}/${base}.grading.tsx`
|
|
512
524
|
};
|
|
513
525
|
}
|
|
526
|
+
var BLEND_LEVEL = /^levels\/.+\.blend$/;
|
|
527
|
+
var CODE_LEVEL = /^levels\/(?:[^/]+\/)*([A-Z][A-Za-z0-9]*Level)\.tsx$/;
|
|
514
528
|
|
|
515
529
|
// serving/std-scene.ts
|
|
516
530
|
var PREFIX = "/@volter-std-scene/";
|
|
@@ -518,11 +532,11 @@ var VIRTUAL = "\0volter-std-scene:";
|
|
|
518
532
|
var RUNTIME = "volter-std-scene-runtime";
|
|
519
533
|
var RUNTIME_ID = "\0volter-std-scene-runtime";
|
|
520
534
|
var RUNTIME_FILE = join3(dirname3(fileURLToPath(import.meta.url)), "..", "serving", "std-scene-runtime.js");
|
|
521
|
-
var LEVEL = /^levels\/.+\.blend$/;
|
|
522
535
|
var MARK = "volter-std-scene";
|
|
523
536
|
var RUNTIME_PACKAGES = ["@react-three/fiber", "@react-three/rapier"];
|
|
524
537
|
var THROUGH = /* @__PURE__ */ new Map([["postprocessing", "@volter-ai/std/level/client"]]);
|
|
525
538
|
var installedFolder = (root, name) => installedPackageDir(root, name.split("/").slice(0, name.startsWith("@") ? 2 : 1).join("/"));
|
|
539
|
+
var refused = (why) => `throw new Error(${JSON.stringify(why)});`;
|
|
526
540
|
var fileUrl = (file, version) => `${fsFileUrl(file)}?${MARK}=${version}`;
|
|
527
541
|
function sceneModule(root, blend, read, version) {
|
|
528
542
|
const files = levelFiles(blend);
|
|
@@ -559,6 +573,22 @@ function sceneModule(root, blend, read, version) {
|
|
|
559
573
|
""
|
|
560
574
|
].join("\n");
|
|
561
575
|
}
|
|
576
|
+
function codeSceneModule(root, file, read) {
|
|
577
|
+
const component = CODE_LEVEL.exec(file)[1];
|
|
578
|
+
read.add(normalizePath(join3(root, file)));
|
|
579
|
+
if (!existsSync3(join3(root, file))) throw new Error(`${file} is not in the game: a level in code is levels/<Name>Level.tsx.`);
|
|
580
|
+
return [
|
|
581
|
+
`import { mountScene as mountLevel } from ${JSON.stringify(RUNTIME)};`,
|
|
582
|
+
`import { ${component} } from ${JSON.stringify(`/${file}`)};`,
|
|
583
|
+
`export const component = ${JSON.stringify(file)};`,
|
|
584
|
+
"export const canvas = { shadows: 'percentage' };",
|
|
585
|
+
"export const gradingExtra = false;",
|
|
586
|
+
`const level = { Level: ${component}, Environment: null, canvas: { shadows: 'percentage' } };`,
|
|
587
|
+
"export const mountScene = (stage) => mountLevel(level, stage);",
|
|
588
|
+
"export const viewTransform = async () => null;",
|
|
589
|
+
""
|
|
590
|
+
].join("\n");
|
|
591
|
+
}
|
|
562
592
|
function gameFile(root, file) {
|
|
563
593
|
if (file === null) return false;
|
|
564
594
|
const at = relative(root, file);
|
|
@@ -587,7 +617,7 @@ function stdScenePlugin(services) {
|
|
|
587
617
|
return {
|
|
588
618
|
name: "volter-play-std-scene",
|
|
589
619
|
enforce: "pre",
|
|
590
|
-
// (a std game's
|
|
620
|
+
// (a std game's level documents and its levels' modules prebundled as the session starts: found on the view's first open,
|
|
591
621
|
// Vite would optimize them again then, and the editor's page, which does not reload for it, would hold two copies of
|
|
592
622
|
// React. The levels' files are crawled, and the runtime's own packages are included where the game has them)
|
|
593
623
|
config() {
|
|
@@ -608,26 +638,27 @@ function stdScenePlugin(services) {
|
|
|
608
638
|
}
|
|
609
639
|
if (!source.startsWith(PREFIX)) return null;
|
|
610
640
|
const [path = "", query = ""] = source.slice(PREFIX.length).split("?");
|
|
611
|
-
const
|
|
612
|
-
if (!
|
|
613
|
-
|
|
641
|
+
const level = decodeURIComponent(path);
|
|
642
|
+
if (!BLEND_LEVEL.test(level) && !CODE_LEVEL.test(level))
|
|
643
|
+
throw new Error(`${level} is not a std level: a level's Blender file (levels/<name>.blend) or a level in code (levels/<Name>Level.tsx).`);
|
|
644
|
+
return `${VIRTUAL}${level}?${query}`;
|
|
614
645
|
},
|
|
615
646
|
load(id) {
|
|
616
647
|
if (id === RUNTIME_ID) return readFileSync2(RUNTIME_FILE, "utf8");
|
|
617
648
|
if (id.startsWith(VIRTUAL)) {
|
|
618
649
|
const root2 = stdGame2();
|
|
619
|
-
if (!root2)
|
|
620
|
-
const
|
|
650
|
+
if (!root2) return refused("The open project is not a std game (no std.json), so it has no level to draw.");
|
|
651
|
+
const level = id.slice(VIRTUAL.length).replace(/\?.*$/, "");
|
|
621
652
|
const read = /* @__PURE__ */ new Set();
|
|
622
|
-
scenes.set(
|
|
623
|
-
return sceneModule(root2,
|
|
653
|
+
scenes.set(level, { id, read });
|
|
654
|
+
return CODE_LEVEL.test(level) ? codeSceneModule(root2, level, read) : sceneModule(root2, level, read, version);
|
|
624
655
|
}
|
|
625
656
|
const [file = id, query] = id.split("?");
|
|
626
657
|
const root = query === "url" ? stdGame2() : null;
|
|
627
658
|
if (!root || !gameFile(root, file)) return null;
|
|
628
659
|
return `export default ${JSON.stringify(fileUrl(file, version))};`;
|
|
629
660
|
},
|
|
630
|
-
// (a
|
|
661
|
+
// (a level's document depends on its module's whole import closure among the game's files, the modules whose import failed
|
|
631
662
|
// to resolve within it and the files its source was written from. Any file of those written, made or removed bumps
|
|
632
663
|
// the content version, invalidates the closure, so each of its modules and URLs is served anew, and is announced on
|
|
633
664
|
// the session's change channel naming the levels it reached)
|
package/package.json
CHANGED
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@volter/play",
|
|
3
3
|
"publisher": "volter",
|
|
4
|
-
"version": "0.5.
|
|
4
|
+
"version": "0.5.213",
|
|
5
5
|
"engines": {
|
|
6
6
|
"vscode": "^1.104.0"
|
|
7
7
|
},
|
|
8
8
|
"author": "Volter AI, Inc.",
|
|
9
9
|
"license": "Apache-2.0",
|
|
10
10
|
"type": "module",
|
|
11
|
-
"description": "Play for a volter-ai/std game in Cyclotron: the game's own dev pages as std devices in the level's stage, the Game panel, a level's export on save and
|
|
11
|
+
"description": "Play for a volter-ai/std game in Cyclotron: the game's own dev pages as std devices in the level's stage, the Game panel, a level's export on save and a level in code's document.",
|
|
12
12
|
"repository": {
|
|
13
13
|
"type": "git",
|
|
14
14
|
"url": "git+https://github.com/volter-ai/editor.git",
|
|
@@ -34,7 +34,8 @@
|
|
|
34
34
|
"contributes": {
|
|
35
35
|
"volter": {
|
|
36
36
|
"activationEvents": [
|
|
37
|
-
"onCustomEditor:volter.blender.model"
|
|
37
|
+
"onCustomEditor:volter.blender.model",
|
|
38
|
+
"onDocument:std-level"
|
|
38
39
|
],
|
|
39
40
|
"serving": [
|
|
40
41
|
"./dist-node/serving.mjs"
|
|
@@ -44,7 +45,8 @@
|
|
|
44
45
|
],
|
|
45
46
|
"editors": [
|
|
46
47
|
{
|
|
47
|
-
"document": "./contributions/std-
|
|
48
|
+
"document": "./contributions/std-level.document.tsx",
|
|
49
|
+
"kind": "std-level"
|
|
48
50
|
}
|
|
49
51
|
],
|
|
50
52
|
"projectAdapters": [
|
|
@@ -52,11 +54,17 @@
|
|
|
52
54
|
"when": "std.json",
|
|
53
55
|
"module": "./src/std-game-adapter.ts"
|
|
54
56
|
}
|
|
57
|
+
],
|
|
58
|
+
"finders": [
|
|
59
|
+
{
|
|
60
|
+
"name": "stdLevelDocuments",
|
|
61
|
+
"module": "./contributions/levels.finder.ts"
|
|
62
|
+
}
|
|
55
63
|
]
|
|
56
64
|
},
|
|
57
65
|
"languageModelTools": [
|
|
58
66
|
{
|
|
59
|
-
"name": "
|
|
67
|
+
"name": "play-log",
|
|
60
68
|
"tags": [
|
|
61
69
|
"volter"
|
|
62
70
|
],
|
|
@@ -65,10 +73,6 @@
|
|
|
65
73
|
"inputSchema": {
|
|
66
74
|
"type": "object",
|
|
67
75
|
"properties": {
|
|
68
|
-
"documentId": {
|
|
69
|
-
"type": "string",
|
|
70
|
-
"description": "The model document; the active Play's when omitted."
|
|
71
|
-
},
|
|
72
76
|
"since": {
|
|
73
77
|
"type": "number",
|
|
74
78
|
"description": "Keep entries at or after this many game seconds."
|
|
@@ -83,12 +87,12 @@
|
|
|
83
87
|
}
|
|
84
88
|
},
|
|
85
89
|
{
|
|
86
|
-
"name": "
|
|
90
|
+
"name": "play",
|
|
87
91
|
"tags": [
|
|
88
92
|
"volter"
|
|
89
93
|
],
|
|
90
94
|
"displayName": "Play control",
|
|
91
|
-
"modelDescription": "Control Play for the
|
|
95
|
+
"modelDescription": "Control Play for the project's std game: state, play, stop, pause, resume or restart. Answers the game's state: play and restart answer once a player's page has joined the game (clock.running), or fail with why it did not start.",
|
|
92
96
|
"inputSchema": {
|
|
93
97
|
"type": "object",
|
|
94
98
|
"properties": {
|
|
@@ -113,91 +117,89 @@
|
|
|
113
117
|
],
|
|
114
118
|
"commands": [
|
|
115
119
|
{
|
|
116
|
-
"command": "volter.
|
|
117
|
-
"title": "
|
|
118
|
-
"category": "Play",
|
|
119
|
-
"volter": {
|
|
120
|
-
"derivedRefresh": "none"
|
|
121
|
-
}
|
|
122
|
-
},
|
|
123
|
-
{
|
|
124
|
-
"command": "volter.model-play-log",
|
|
125
|
-
"title": "model-play-log",
|
|
120
|
+
"command": "volter.play",
|
|
121
|
+
"title": "play",
|
|
126
122
|
"category": "Play",
|
|
127
123
|
"volter": {
|
|
128
|
-
"derivedRefresh": "none"
|
|
124
|
+
"derivedRefresh": "none",
|
|
125
|
+
"timeoutMs": 120000
|
|
129
126
|
}
|
|
130
127
|
},
|
|
131
128
|
{
|
|
132
|
-
"command": "volter.
|
|
133
|
-
"title": "
|
|
129
|
+
"command": "volter.play-log",
|
|
130
|
+
"title": "play-log",
|
|
134
131
|
"category": "Play",
|
|
135
132
|
"volter": {
|
|
136
133
|
"derivedRefresh": "none"
|
|
137
134
|
}
|
|
138
135
|
},
|
|
139
136
|
{
|
|
140
|
-
"command": "volter.
|
|
137
|
+
"command": "volter.play.state",
|
|
141
138
|
"title": "state",
|
|
142
139
|
"category": "Game"
|
|
143
140
|
},
|
|
144
141
|
{
|
|
145
|
-
"command": "volter.
|
|
142
|
+
"command": "volter.play.mode",
|
|
146
143
|
"title": "mode",
|
|
147
144
|
"category": "Game"
|
|
148
145
|
},
|
|
149
146
|
{
|
|
150
|
-
"command": "volter.
|
|
147
|
+
"command": "volter.play.game-mode",
|
|
151
148
|
"title": "Switch to Game Mode",
|
|
152
149
|
"category": "Game"
|
|
153
150
|
},
|
|
154
151
|
{
|
|
155
|
-
"command": "volter.
|
|
152
|
+
"command": "volter.play.animation-mode",
|
|
156
153
|
"title": "Switch to Animation Mode",
|
|
157
154
|
"category": "Game"
|
|
158
155
|
},
|
|
159
156
|
{
|
|
160
|
-
"command": "volter.
|
|
157
|
+
"command": "volter.play.play",
|
|
161
158
|
"title": "Play",
|
|
162
159
|
"category": "Game"
|
|
163
160
|
},
|
|
164
161
|
{
|
|
165
|
-
"command": "volter.
|
|
162
|
+
"command": "volter.play.stop",
|
|
166
163
|
"title": "Stop",
|
|
167
164
|
"category": "Game"
|
|
168
165
|
},
|
|
169
166
|
{
|
|
170
|
-
"command": "volter.
|
|
167
|
+
"command": "volter.play.pause",
|
|
171
168
|
"title": "Pause",
|
|
172
169
|
"category": "Game"
|
|
173
170
|
},
|
|
174
171
|
{
|
|
175
|
-
"command": "volter.
|
|
172
|
+
"command": "volter.play.resume",
|
|
176
173
|
"title": "Resume",
|
|
177
174
|
"category": "Game"
|
|
178
175
|
},
|
|
179
176
|
{
|
|
180
|
-
"command": "volter.
|
|
177
|
+
"command": "volter.play.step",
|
|
181
178
|
"title": "Step One Tick",
|
|
182
179
|
"category": "Game"
|
|
183
180
|
},
|
|
184
181
|
{
|
|
185
|
-
"command": "volter.
|
|
182
|
+
"command": "volter.play.speed",
|
|
186
183
|
"title": "speed",
|
|
187
184
|
"category": "Game"
|
|
188
185
|
},
|
|
189
186
|
{
|
|
190
|
-
"command": "volter.
|
|
187
|
+
"command": "volter.play.players",
|
|
188
|
+
"title": "players",
|
|
189
|
+
"category": "Game"
|
|
190
|
+
},
|
|
191
|
+
{
|
|
192
|
+
"command": "volter.play.autoplay",
|
|
191
193
|
"title": "autoplay",
|
|
192
194
|
"category": "Game"
|
|
193
195
|
},
|
|
194
196
|
{
|
|
195
|
-
"command": "volter.
|
|
197
|
+
"command": "volter.play.restart",
|
|
196
198
|
"title": "Restart",
|
|
197
199
|
"category": "Game"
|
|
198
200
|
},
|
|
199
201
|
{
|
|
200
|
-
"command": "volter.
|
|
202
|
+
"command": "volter.play.export",
|
|
201
203
|
"title": "export",
|
|
202
204
|
"category": "Game"
|
|
203
205
|
}
|
|
@@ -205,46 +207,66 @@
|
|
|
205
207
|
"menus": {
|
|
206
208
|
"commandPalette": [
|
|
207
209
|
{
|
|
208
|
-
"command": "volter.
|
|
210
|
+
"command": "volter.play",
|
|
209
211
|
"when": "false"
|
|
210
212
|
},
|
|
211
213
|
{
|
|
212
|
-
"command": "volter.
|
|
214
|
+
"command": "volter.play-log",
|
|
213
215
|
"when": "false"
|
|
214
216
|
},
|
|
215
217
|
{
|
|
216
|
-
"command": "volter.
|
|
218
|
+
"command": "volter.play.state",
|
|
217
219
|
"when": "false"
|
|
218
220
|
},
|
|
219
221
|
{
|
|
220
|
-
"command": "volter.
|
|
222
|
+
"command": "volter.play.mode",
|
|
221
223
|
"when": "false"
|
|
222
224
|
},
|
|
223
225
|
{
|
|
224
|
-
"command": "volter.
|
|
226
|
+
"command": "volter.play.speed",
|
|
225
227
|
"when": "false"
|
|
226
228
|
},
|
|
227
229
|
{
|
|
228
|
-
"command": "volter.
|
|
230
|
+
"command": "volter.play.players",
|
|
229
231
|
"when": "false"
|
|
230
232
|
},
|
|
231
233
|
{
|
|
232
|
-
"command": "volter.
|
|
234
|
+
"command": "volter.play.autoplay",
|
|
235
|
+
"when": "false"
|
|
236
|
+
},
|
|
237
|
+
{
|
|
238
|
+
"command": "volter.play.export",
|
|
233
239
|
"when": "false"
|
|
234
240
|
}
|
|
235
241
|
]
|
|
242
|
+
},
|
|
243
|
+
"configuration": {
|
|
244
|
+
"title": "Play",
|
|
245
|
+
"properties": {
|
|
246
|
+
"volter.play.players": {
|
|
247
|
+
"type": "integer",
|
|
248
|
+
"default": 1,
|
|
249
|
+
"minimum": 1,
|
|
250
|
+
"maximum": 8,
|
|
251
|
+
"scope": "window",
|
|
252
|
+
"description": "How many players a std game's Play starts: yours, and a bot playing the Game panel's goal for each other. One where unset. The Game panel's Players [-][+] and `play players <n>` write it to the project's settings (.vscode/settings.json), and a running game's players join or leave whenever it changes."
|
|
253
|
+
}
|
|
254
|
+
}
|
|
236
255
|
}
|
|
237
256
|
},
|
|
257
|
+
"dependencies": {
|
|
258
|
+
"zod": "^4.3.6"
|
|
259
|
+
},
|
|
238
260
|
"peerDependencies": {
|
|
239
261
|
"react": "^19.0.0",
|
|
240
262
|
"react-dom": "^19.0.0",
|
|
241
263
|
"three": ">=0.186.0 <0.187.0",
|
|
242
|
-
"@volter/sdk": "0.5.
|
|
243
|
-
"@volter/project": "0.5.
|
|
264
|
+
"@volter/sdk": "0.5.213",
|
|
265
|
+
"@volter/project": "0.5.213",
|
|
244
266
|
"vite": "^6.4.3 || ^7.0.0 || ^8.0.0",
|
|
245
|
-
"@volter/live": "0.5.
|
|
246
|
-
"@volter/editor-blender": "0.1.
|
|
247
|
-
"@volter/editor-threejs": "0.5.
|
|
267
|
+
"@volter/live": "0.5.213",
|
|
268
|
+
"@volter/editor-blender": "0.1.152",
|
|
269
|
+
"@volter/editor-threejs": "0.5.213",
|
|
248
270
|
"@react-three/fiber": "^9.8.2",
|
|
249
271
|
"@react-three/rapier": "^2.2.0",
|
|
250
272
|
"postprocessing": "^6.39.5",
|
|
@@ -259,10 +281,10 @@
|
|
|
259
281
|
"access": "public"
|
|
260
282
|
},
|
|
261
283
|
"devDependencies": {
|
|
262
|
-
"@volter/editor-blender": "0.1.
|
|
263
|
-
"@volter/editor-threejs": "0.5.
|
|
264
|
-
"@volter/live": "0.5.
|
|
265
|
-
"@volter/sdk": "0.5.
|
|
284
|
+
"@volter/editor-blender": "0.1.152",
|
|
285
|
+
"@volter/editor-threejs": "0.5.213",
|
|
286
|
+
"@volter/live": "0.5.213",
|
|
287
|
+
"@volter/sdk": "0.5.213",
|
|
266
288
|
"esbuild": "0.25.12",
|
|
267
289
|
"typescript": "5.9.3"
|
|
268
290
|
},
|
package/serving/index.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* `@volter/play`'s server half (`package.json#contributes.volter.serving`): a std game's dev server as Play reaches it,
|
|
3
3
|
* and its level export's Node half (`std-dev.ts`); a std level's scene as a module of the game's own graph, which the
|
|
4
|
-
*
|
|
4
|
+
* level's document draws (`std-scene.ts`).
|
|
5
5
|
*/
|
|
6
6
|
import type { ProjectServingServices } from '@volter/sdk/session/project-serving';
|
|
7
7
|
import { stdDevPlugin } from './std-dev';
|