@volter/play 0.5.213 → 0.5.215

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.
@@ -480,7 +480,7 @@ function stdDevPlugin(services) {
480
480
  }
481
481
 
482
482
  // serving/std-scene.ts
483
- import { existsSync as existsSync3, readFileSync as readFileSync2 } from "node:fs";
483
+ import { existsSync as existsSync3, readdirSync, readFileSync as readFileSync2 } from "node:fs";
484
484
  import { dirname as dirname3, join as join3, relative, sep } from "node:path";
485
485
  import { fileURLToPath } from "node:url";
486
486
 
@@ -589,6 +589,15 @@ function codeSceneModule(root, file, read) {
589
589
  ""
590
590
  ].join("\n");
591
591
  }
592
+ function levelModules(root) {
593
+ let found;
594
+ try {
595
+ found = readdirSync(join3(root, "levels"), { recursive: true, encoding: "utf8" });
596
+ } catch {
597
+ return [];
598
+ }
599
+ return found.filter((file) => /\.tsx?$/.test(file) && !file.split(sep).some((segment) => segment.startsWith(".") || segment === "node_modules")).map((file) => `levels/${file.split(sep).join("/")}`).sort();
600
+ }
592
601
  function gameFile(root, file) {
593
602
  if (file === null) return false;
594
603
  const at = relative(root, file);
@@ -619,12 +628,14 @@ function stdScenePlugin(services) {
619
628
  enforce: "pre",
620
629
  // (a std game's level documents and its levels' modules prebundled as the session starts: found on the view's first open,
621
630
  // Vite would optimize them again then, and the editor's page, which does not reload for it, would hold two copies of
622
- // React. The levels' files are crawled, and the runtime's own packages are included where the game has them)
631
+ // React. The levels' files are crawled, and the runtime's own packages are included where the game has them. The
632
+ // files are listed, never a pattern: one entry that is a pattern makes Vite glob the session's whole list, which
633
+ // skips every `node_modules`, and so every installed package's crawl entry, `globEntries` in its dependency scan)
623
634
  config() {
624
635
  const root = stdGame2();
625
636
  if (root === null) return void 0;
626
637
  const include = RUNTIME_PACKAGES.filter((name) => installedFolder(root, name) !== null);
627
- return { optimizeDeps: { entries: ["levels/**/*.{ts,tsx}"], include } };
638
+ return { optimizeDeps: { entries: levelModules(root), include } };
628
639
  },
629
640
  async resolveId(source, importer) {
630
641
  const ours = importer?.startsWith(VIRTUAL) === true || importer === RUNTIME_ID;
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@volter/play",
3
3
  "publisher": "volter",
4
- "version": "0.5.213",
4
+ "version": "0.5.215",
5
5
  "engines": {
6
6
  "vscode": "^1.104.0"
7
7
  },
@@ -261,16 +261,16 @@
261
261
  "react": "^19.0.0",
262
262
  "react-dom": "^19.0.0",
263
263
  "three": ">=0.186.0 <0.187.0",
264
- "@volter/sdk": "0.5.213",
265
- "@volter/project": "0.5.213",
264
+ "@volter/sdk": "0.5.215",
265
+ "@volter/project": "0.5.215",
266
266
  "vite": "^6.4.3 || ^7.0.0 || ^8.0.0",
267
- "@volter/live": "0.5.213",
268
- "@volter/editor-blender": "0.1.152",
269
- "@volter/editor-threejs": "0.5.213",
267
+ "@volter/live": "0.5.215",
268
+ "@volter/editor-blender": "0.1.154",
269
+ "@volter/editor-threejs": "0.5.215",
270
270
  "@react-three/fiber": "^9.8.2",
271
271
  "@react-three/rapier": "^2.2.0",
272
272
  "postprocessing": "^6.39.5",
273
- "@volter-ai/std": "^0.8.0"
273
+ "@volter-ai/std": "^0.8.0-0"
274
274
  },
275
275
  "scripts": {
276
276
  "typecheck": "tsc -p tsconfig.json",
@@ -281,10 +281,10 @@
281
281
  "access": "public"
282
282
  },
283
283
  "devDependencies": {
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",
284
+ "@volter/editor-blender": "0.1.154",
285
+ "@volter/editor-threejs": "0.5.215",
286
+ "@volter/live": "0.5.215",
287
+ "@volter/sdk": "0.5.215",
288
288
  "esbuild": "0.25.12",
289
289
  "typescript": "5.9.3"
290
290
  },
@@ -22,7 +22,7 @@
22
22
  * cache. A URL the game's code passes as a plain string, and a glTF's external files, keep their URL: they show a
23
23
  * change on a page reload (`docs/adr/std-scene-view.md`, decision 5).
24
24
  */
25
- import { existsSync, readFileSync } from 'node:fs';
25
+ import { existsSync, readdirSync, readFileSync } from 'node:fs';
26
26
  import { dirname, join, relative, sep } from 'node:path';
27
27
  import { fileURLToPath } from 'node:url';
28
28
  import { installedPackageDir } from '@volter/live/installed-package';
@@ -131,6 +131,20 @@ function codeSceneModule(root: string, file: string, read: Set<string>): string
131
131
  ].join('\n');
132
132
  }
133
133
 
134
+ /** The game's level modules (`levels/**` TypeScript), each as its path from the game's folder. */
135
+ function levelModules(root: string): string[] {
136
+ let found: string[];
137
+ try {
138
+ found = readdirSync(join(root, 'levels'), { recursive: true, encoding: 'utf8' });
139
+ } catch {
140
+ return [];
141
+ }
142
+ return found
143
+ .filter((file) => /\.tsx?$/.test(file) && !file.split(sep).some((segment) => segment.startsWith('.') || segment === 'node_modules'))
144
+ .map((file) => `levels/${file.split(sep).join('/')}`)
145
+ .sort();
146
+ }
147
+
134
148
  /** Whether a module's file is the game's own (in its folder, not a dependency's). */
135
149
  function gameFile(root: string, file: string | null): file is string {
136
150
  if (file === null) return false;
@@ -168,12 +182,14 @@ export function stdScenePlugin(services: Pick<ProjectServingServices, 'currentPr
168
182
  enforce: 'pre',
169
183
  // (a std game's level documents and its levels' modules prebundled as the session starts: found on the view's first open,
170
184
  // Vite would optimize them again then, and the editor's page, which does not reload for it, would hold two copies of
171
- // React. The levels' files are crawled, and the runtime's own packages are included where the game has them)
185
+ // React. The levels' files are crawled, and the runtime's own packages are included where the game has them. The
186
+ // files are listed, never a pattern: one entry that is a pattern makes Vite glob the session's whole list, which
187
+ // skips every `node_modules`, and so every installed package's crawl entry, `globEntries` in its dependency scan)
172
188
  config() {
173
189
  const root = stdGame();
174
190
  if (root === null) return undefined;
175
191
  const include = RUNTIME_PACKAGES.filter((name) => installedFolder(root, name) !== null);
176
- return { optimizeDeps: { entries: ['levels/**/*.{ts,tsx}'], include } };
192
+ return { optimizeDeps: { entries: levelModules(root), include } };
177
193
  },
178
194
  async resolveId(source, importer) {
179
195
  const ours = importer?.startsWith(VIRTUAL) === true || importer === RUNTIME_ID;
package/src/std-game.ts CHANGED
@@ -515,9 +515,12 @@ async function clockAll(asked: StdClockAction): Promise<StdClock> {
515
515
  if (!current) throw new Error('Nothing is playing; `play` starts it.');
516
516
  const joined = current.players.filter((player) => player.device !== null);
517
517
  if (joined.length === 0) throw new Error('No player\'s page has joined the game yet.');
518
- const answers = await Promise.all(joined.map((player) => stdClock(player.device!, asked).then((clock) => ({ player, clock }))));
519
- // (an answer is its run's: one that comes after Stop or Restart ended that run is no reading of the game now)
520
- if (current.stopped) throw new Error('The run ended (Stop or Restart) before its players\' pages answered.');
518
+ // (an answer is its run's: one that comes after Stop or Restart ended that run is no reading of the game now, and a
519
+ // page that run's end disconnected refuses with std's own "no device" before it can answer)
520
+ const ended = (): Error => new Error('The run ended (Stop or Restart) before its players\' pages answered.');
521
+ const answers = await Promise.all(joined.map((player) => stdClock(player.device!, asked).then((clock) => ({ player, clock }))))
522
+ .catch((error: unknown) => { throw current.stopped ? ended() : error; });
523
+ if (current.stopped) throw ended();
521
524
  const said = answers.find(({ player }) => player === answering(current)) ?? answers[0]!;
522
525
  current.log.append('play', asked.action, { ...asked, ...said.clock });
523
526
  setGameClock(said.clock);