@volter/editor-threejs 0.5.67 → 0.5.69

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 (43) hide show
  1. package/NOTICE +3 -3
  2. package/contributions/animation-mixers.service.ts +1 -1
  3. package/contributions/animation-timeline.utility.tsx +1 -1
  4. package/dist-node/serving.mjs +10 -10
  5. package/package.json +4 -4
  6. package/serving/animation-live-module.ts +6 -6
  7. package/serving/animation-stamp.ts +5 -5
  8. package/serving/index.ts +1 -1
  9. package/src/adapter/hierarchy-marks.ts +5 -5
  10. package/src/adapter/ingest/scene-capture.ts +7 -7
  11. package/src/adapter/ingest/structural-ids.ts +1 -1
  12. package/src/adapter/ingest/visible-capture-window.ts +3 -3
  13. package/src/asset-loaders.ts +1 -1
  14. package/src/ecs/object-marks.ts +4 -4
  15. package/src/ecs/user-data.ts +3 -3
  16. package/src/kit/asset-compare.ts +2 -2
  17. package/src/kit/asset-preview-command.ts +2 -2
  18. package/src/kit/asset-preview.ts +1 -1
  19. package/src/kit/authoring/component-instance-root.ts +2 -2
  20. package/src/kit/authoring/object3d-document-session.ts +1 -1
  21. package/src/kit/authoring/three-projection-core.ts +1 -1
  22. package/src/kit/authoring/viewport-pick-context.ts +1 -1
  23. package/src/kit/authoring/viewport-raycast.ts +1 -1
  24. package/src/kit/components/CameraInfo.tsx +1 -1
  25. package/src/kit/components/InspectorObjectPreview.tsx +1 -1
  26. package/src/kit/components/Object3DDocumentToolbar.tsx +10 -10
  27. package/src/kit/components/StageHost.tsx +14 -14
  28. package/src/kit/components/StatsOverlay.tsx +4 -4
  29. package/src/kit/components/ViewportFurniture.tsx +22 -22
  30. package/src/kit/components/ViewportOverlay.tsx +4 -4
  31. package/src/kit/components/ViewportShadingMenu.tsx +9 -9
  32. package/src/kit/components/ViewportViewMenu.tsx +1 -1
  33. package/src/kit/components/stage-presence-markers.ts +2 -2
  34. package/src/kit/components/standard-viewport-dressing.ts +18 -18
  35. package/src/kit/editor-shell-store.ts +1 -1
  36. package/src/kit/editor-viewport.ts +17 -17
  37. package/src/kit/live-module-source.ts +1 -1
  38. package/src/kit/projection/three.ts +3 -3
  39. package/src/kit/three-viewport/selection-outline.ts +9 -9
  40. package/src/kit/three-viewport/skeleton-helper.ts +1 -1
  41. package/src/kit/three-viewport/studio-environment.ts +1 -1
  42. package/src/kit/viewport-hotkeys.ts +2 -2
  43. package/src/viewport/content-bounds.ts +1 -1
@@ -1327,17 +1327,17 @@ export function Object3DDocumentViewport({
1327
1327
  }
1328
1328
  // AN OBJECT DRAWN BY A HELPER is picked through it: a document that draws an object's
1329
1329
  // overlay itself (a Blender camera's wire, a light's icon) marks each part with the
1330
- // object it stands for (`userData.vgaiPicksAs`), and the nearer hit wins, as Blender's
1330
+ // object it stands for (`userData.volterPicksAs`), and the nearer hit wins, as Blender's
1331
1331
  // pick over its whole drawing does.
1332
- // Only helpers that say they can be picked (`userData.vgaiPickable`) are asked, and a
1332
+ // Only helpers that say they can be picked (`userData.volterPickable`) are asked, and a
1333
1333
  // part is hit only where it is shown (three's raycast reads layers, not `visible`).
1334
- const pickable = viewport.visibleHelpers().filter((helper) => helper.userData['vgaiPickable'] === true);
1334
+ const pickable = viewport.visibleHelpers().filter((helper) => helper.userData['volterPickable'] === true);
1335
1335
  for (const hit of raycaster.intersectObjects(pickable, true)) {
1336
1336
  let proxy: THREE.Object3D | null = null;
1337
1337
  let shown = true;
1338
1338
  for (let object: THREE.Object3D | null = hit.object; object; object = object.parent) {
1339
1339
  if (!object.visible) shown = false;
1340
- proxy ??= (object.userData['vgaiPicksAs'] as THREE.Object3D | undefined) ?? null;
1340
+ proxy ??= (object.userData['volterPicksAs'] as THREE.Object3D | undefined) ?? null;
1341
1341
  }
1342
1342
  if (!shown) continue;
1343
1343
  const id = proxy ? idOf(proxy) : null;
@@ -1402,7 +1402,7 @@ export function Object3DDocumentViewport({
1402
1402
  // Under a group of the stage's own, which the view's presentation shows only while it
1403
1403
  // lights by the document's studio; the document keeps its say over what is inside.
1404
1404
  const viewLockedHolder = new THREE.Group();
1405
- viewLockedHolder.name = 'vgai:document-studio';
1405
+ viewLockedHolder.name = 'volter:document-studio';
1406
1406
  viewLockedHolder.add(dressingViewLocked);
1407
1407
  camera.add(viewLockedHolder);
1408
1408
  host.documentStudio = viewLockedHolder;
@@ -2373,8 +2373,8 @@ export function Object3DDocumentViewport({
2373
2373
  const viewport = (
2374
2374
  <div
2375
2375
  data-testid="tool-object3d-authoring"
2376
- data-vgai-chromeless={chromeless || undefined}
2377
- className={studioStage ? 'vgai-object3d-studio-stage' : undefined}
2376
+ data-volter-chromeless={chromeless || undefined}
2377
+ className={studioStage ? 'volter-object3d-studio-stage' : undefined}
2378
2378
  style={{
2379
2379
  position: chromeless || hasShell ? 'relative' : 'absolute',
2380
2380
  // Inside the Asset Editor shell the stage fills its own box exactly;
@@ -2446,20 +2446,20 @@ export function Object3DDocumentViewport({
2446
2446
  top: 0,
2447
2447
  display: 'flex',
2448
2448
  justifyContent: 'center',
2449
- padding: 'var(--vgai-space-3)',
2449
+ padding: 'var(--volter-space-3)',
2450
2450
  pointerEvents: 'none',
2451
2451
  }}
2452
2452
  >
2453
2453
  <div
2454
- className="vgai-chrome-island"
2454
+ className="volter-chrome-island"
2455
2455
  style={{
2456
2456
  display: 'flex',
2457
2457
  alignItems: 'center',
2458
- gap: 'var(--vgai-space-3)',
2458
+ gap: 'var(--volter-space-3)',
2459
2459
  maxWidth: '80%',
2460
- padding: 'var(--vgai-space-2) var(--vgai-space-3)',
2461
- borderRadius: 'var(--vgai-radius-md)',
2462
- border: `var(--vgai-stroke-resting) solid ${themeVars.semantic.danger}`,
2460
+ padding: 'var(--volter-space-2) var(--volter-space-3)',
2461
+ borderRadius: 'var(--volter-radius-md)',
2462
+ border: `var(--volter-stroke-resting) solid ${themeVars.semantic.danger}`,
2463
2463
  background: themeVars.surface.raised,
2464
2464
  color: themeVars.semantic.danger,
2465
2465
  pointerEvents: 'auto',
@@ -2530,7 +2530,7 @@ export function Object3DDocumentViewport({
2530
2530
  selection={readSelection}
2531
2531
  fill
2532
2532
  >
2533
- <div className="vgai-object3d-asset-workspace">
2533
+ <div className="volter-object3d-asset-workspace">
2534
2534
  {viewport}
2535
2535
  {/* Where the deleted preview strip was. It draws nothing until this
2536
2536
  stage's transport has a subject, so a document with nothing to show
@@ -25,11 +25,11 @@ export function StatsOverlay() {
25
25
  // same corner. Given the same border/radius treatment as its siblings so
26
26
  // the whole corner reads as one family.
27
27
  // P6-U6 (owner taste decision 3: HUD overlays → real islands): the paint
28
- // moved to `.vgai-hud-overlay` so islands chrome can promote it to the
28
+ // moved to `.volter-hud-overlay` so islands chrome can promote it to the
29
29
  // shared glass-island material (compact tier); bars chrome paints the
30
30
  // exact pre-U6 overlay-whisper recipe from the same class.
31
31
  <div
32
- className="vgai-hud-overlay vgai-chrome-island vgai-glass-island"
32
+ className="volter-hud-overlay volter-chrome-island volter-glass-island"
33
33
  data-island-scale="compact"
34
34
  data-hud-border="strong"
35
35
  style={{
@@ -39,11 +39,11 @@ export function StatsOverlay() {
39
39
  // floating header-island clearance, scoped in theme.css); the bare
40
40
  // fallback is the pre-glass 48px so treatment-less chrome ("bars":
41
41
  // graphite/classic/lite/reduced) stays pixel-identical to main.
42
- top: 'var(--vgai-viewport-overlay-hud-top, 48px)',
42
+ top: 'var(--volter-viewport-overlay-hud-top, 48px)',
43
43
  right: 8,
44
44
  padding: '6px 10px',
45
45
  pointerEvents: 'auto',
46
- fontSize: 'var(--vgai-font-base)',
46
+ fontSize: 'var(--volter-font-base)',
47
47
  color: themeVars.content.primary,
48
48
  fontFamily: themeVars.typography.mono,
49
49
  lineHeight: '16px',
@@ -369,17 +369,17 @@ export function ViewportFurniture({
369
369
  aria-hidden="true"
370
370
  style={{
371
371
  position: 'absolute',
372
- top: 'var(--vgai-space-3)',
372
+ top: 'var(--volter-space-3)',
373
373
  // Clear of the tool rail at the stage's left edge (Blender's text
374
374
  // starts past its toolbar): the rail is one control wide plus its
375
375
  // own inset.
376
- left: 'calc(var(--vgai-space-4) + var(--vgai-control-comfortable-height) * 2 + var(--vgai-space-4))',
376
+ left: 'calc(var(--volter-space-4) + var(--volter-control-comfortable-height) * 2 + var(--volter-space-4))',
377
377
  display: 'flex',
378
378
  flexDirection: 'column',
379
- gap: 'var(--vgai-space-1)',
380
- fontSize: 'var(--vgai-font-sm)',
379
+ gap: 'var(--volter-space-1)',
380
+ fontSize: 'var(--volter-font-sm)',
381
381
  color: overlayInk,
382
- textShadow: 'var(--vgai-content-text-shadow, none)',
382
+ textShadow: 'var(--volter-content-text-shadow, none)',
383
383
  pointerEvents: 'none',
384
384
  userSelect: 'none',
385
385
  }}
@@ -396,7 +396,7 @@ export function ViewportFurniture({
396
396
  // The column's own rows keep the block's line step; the block
397
397
  // itself stands off the lines above it. See
398
398
  // `STATISTICS_BLOCK_OFFSET`.
399
- rowGap: 'var(--vgai-space-1)',
399
+ rowGap: 'var(--volter-space-1)',
400
400
  marginTop: spaceVar[STATISTICS_BLOCK_OFFSET],
401
401
  }}
402
402
  >
@@ -412,15 +412,15 @@ export function ViewportFurniture({
412
412
  ) : null}
413
413
  {chrome.viewName === 'menu' ? (
414
414
  <div
415
- className="vgai-viewport-view-pill"
415
+ className="volter-viewport-view-pill"
416
416
  style={{
417
417
  position: 'absolute',
418
- top: 'var(--vgai-viewport-overlay-top, var(--vgai-space-4))',
418
+ top: 'var(--volter-viewport-overlay-top, var(--volter-space-4))',
419
419
  // Past the shelf rail when it draws anything, as the view text is; at the edge when
420
- // it is empty (`data-vgai-stage-rail`, `workspace-surfaces.css`).
420
+ // it is empty (`data-volter-stage-rail`, `workspace-surfaces.css`).
421
421
  left:
422
- 'var(--vgai-stage-name-left, calc(var(--vgai-space-4) + var(--vgai-control-comfortable-height) * 2 + var(--vgai-space-4)))',
423
- zIndex: 'calc(var(--vgai-z-dropdown, 1000) - 1)',
422
+ 'var(--volter-stage-name-left, calc(var(--volter-space-4) + var(--volter-control-comfortable-height) * 2 + var(--volter-space-4)))',
423
+ zIndex: 'calc(var(--volter-z-dropdown, 1000) - 1)',
424
424
  pointerEvents: 'auto',
425
425
  }}
426
426
  >
@@ -433,7 +433,7 @@ export function ViewportFurniture({
433
433
  <button
434
434
  type="button"
435
435
  data-testid="viewport-view-name"
436
- className="vgai-viewport-gizmo-label"
436
+ className="volter-viewport-gizmo-label"
437
437
  aria-label={drawn === 'perspective' ? 'Switch to orthographic' : 'Switch to perspective'}
438
438
  onClick={() => {
439
439
  const next = drawn === 'perspective' ? 'orthographic' : 'perspective';
@@ -447,13 +447,13 @@ export function ViewportFurniture({
447
447
  transform: 'translateX(50%)',
448
448
  display: 'inline-flex',
449
449
  alignItems: 'center',
450
- gap: 'var(--vgai-space-1)',
450
+ gap: 'var(--volter-space-1)',
451
451
  padding: 0,
452
452
  border: 0,
453
453
  background: 'none',
454
- fontSize: 'var(--vgai-font-sm)',
454
+ fontSize: 'var(--volter-font-sm)',
455
455
  color: overlayInk,
456
- textShadow: 'var(--vgai-content-text-shadow, none)',
456
+ textShadow: 'var(--volter-content-text-shadow, none)',
457
457
  cursor: 'pointer',
458
458
  pointerEvents: 'auto',
459
459
  }}
@@ -475,7 +475,7 @@ export function ViewportFurniture({
475
475
  role="toolbar"
476
476
  aria-label="Navigation"
477
477
  aria-orientation="vertical"
478
- className="vgai-chrome-island vgai-viewport-navigation"
478
+ className="volter-chrome-island volter-viewport-navigation"
479
479
  style={{
480
480
  position: 'absolute',
481
481
  top: CLUSTER_TOP,
@@ -487,17 +487,17 @@ export function ViewportFurniture({
487
487
  // 2836); at 12 it is 9.0.
488
488
  // UNGATED, like every other number this cluster carries: its 28 and
489
489
  // 16 are Blender's for every skin already (`theme.css`'s
490
- // `.vgai-viewport-navigation` block, no palette selector), and the
490
+ // `.volter-viewport-navigation` block, no palette selector), and the
491
491
  // estate's `css-style-identity-selector` gate forbids one anyway.
492
- right: 'var(--vgai-space-6)',
492
+ right: 'var(--volter-space-6)',
493
493
  display: 'flex',
494
494
  flexDirection: 'column',
495
- gap: 'var(--vgai-space-1)',
495
+ gap: 'var(--volter-space-1)',
496
496
  // Blender's capsule is exactly as wide as its buttons: it insets
497
497
  // only along the column.
498
- padding: 'var(--vgai-space-1) 0',
499
- borderRadius: 'var(--vgai-radius-full)',
500
- background: 'var(--vgai-island-surface, var(--vgai-surface-overlay))',
498
+ padding: 'var(--volter-space-1) 0',
499
+ borderRadius: 'var(--volter-radius-full)',
500
+ background: 'var(--volter-island-surface, var(--volter-surface-overlay))',
501
501
  pointerEvents: 'auto',
502
502
  }}
503
503
  >
@@ -41,7 +41,7 @@ import { stageViewName } from './stage-view-name';
41
41
  * GRID is the stage's VIEW's switch (`overlays.grid.visible` in
42
42
  * `kit/viewport-presentation`, keyed by `documentId`): the one flag this
43
43
  * button, the document's header, the `toggle.grid` action, `set-grid` and
44
- * `vgai status`'s `showGrid` all read and write.
44
+ * the editor's `status` command's `showGrid` all read and write.
45
45
  */
46
46
  export function ViewportOverlay({
47
47
  store,
@@ -71,8 +71,8 @@ export function ViewportOverlay({
71
71
  const [anchor, setAnchor] = useState<HTMLSpanElement | null>(null);
72
72
  const findSlot = (): HTMLElement | null =>
73
73
  anchor
74
- ?.closest('.vgai-dock-document-content')
75
- ?.querySelector<HTMLElement>(':scope > .vgai-stage-bar [data-stage-bar-slot="display"]') ?? null;
74
+ ?.closest('.volter-dock-document-content')
75
+ ?.querySelector<HTMLElement>(':scope > .volter-stage-bar [data-stage-bar-slot="display"]') ?? null;
76
76
  // ONE OVERLAY PER SLOT: a document hosting two stages has one bar, so the first overlay to
77
77
  // claim its slot draws there and any other keeps its own place.
78
78
  const owner = useId();
@@ -96,7 +96,7 @@ export function ViewportOverlay({
96
96
  const toolbar = (
97
97
  <FloatingToolbar
98
98
  label="Viewport display"
99
- className="vgai-viewport-toolbar vgai-viewport-toolbar-right vgai-stage-display"
99
+ className="volter-viewport-toolbar volter-viewport-toolbar-right volter-stage-display"
100
100
  >
101
101
  {chrome.viewName === 'bar' && session ? (
102
102
  <ViewportViewMenu
@@ -130,7 +130,7 @@ function ViewportShadingGlyph({
130
130
  return (
131
131
  <svg
132
132
  viewBox="0 0 16 16"
133
- className="vgai-viewport-shading-glyph"
133
+ className="volter-viewport-shading-glyph"
134
134
  aria-hidden="true"
135
135
  focusable="false"
136
136
  >
@@ -148,7 +148,7 @@ function ViewportShadingGlyph({
148
148
  ) : null}
149
149
  {glyph === 'rendered' ? (
150
150
  // Blender's Rendered mark is a lit ball: the filled disc with a highlight up and left.
151
- <circle cx="5.6" cy="5.6" r="2.3" fill="var(--vgai-surface-overlay, #ffffff)" fillOpacity="0.7" />
151
+ <circle cx="5.6" cy="5.6" r="2.3" fill="var(--volter-surface-overlay, #ffffff)" fillOpacity="0.7" />
152
152
  ) : null}
153
153
  {glyph === 'preview' ? (
154
154
  <path
@@ -185,7 +185,7 @@ export function ViewportDisplayModeMenu<T extends string>({
185
185
  const menu =
186
186
  open && !disabled ? (
187
187
  <AnchoredMenu
188
- id="vgai-viewport-shading-menu"
188
+ id="volter-viewport-shading-menu"
189
189
  anchorRef={ref}
190
190
  aria-label="Viewport shading modes"
191
191
  onDismiss={() => setOpen(false)}
@@ -224,7 +224,7 @@ export function ViewportDisplayModeMenu<T extends string>({
224
224
  {children ? (
225
225
  <>
226
226
  <MenuSeparator />
227
- <div className="vgai-viewport-shading-options">{children}</div>
227
+ <div className="volter-viewport-shading-options">{children}</div>
228
228
  </>
229
229
  ) : null}
230
230
  </AnchoredMenu>
@@ -237,8 +237,8 @@ export function ViewportDisplayModeMenu<T extends string>({
237
237
  // corners and a shared border — so the five read as one widget with one
238
238
  // cell lit, and `aria-pressed` is the only thing that paints the lit one.
239
239
  return (
240
- <div className="vgai-viewport-popover-anchor">
241
- <span className="vgai-viewport-shading-segments" role="group" aria-label="Viewport shading">
240
+ <div className="volter-viewport-popover-anchor">
241
+ <span className="volter-viewport-shading-segments" role="group" aria-label="Viewport shading">
242
242
  {segments.map((segment) => (
243
243
  <Tooltip key={segment.mode} text={`Viewport shading: ${segment.label}`}>
244
244
  <IconButton
@@ -263,7 +263,7 @@ export function ViewportDisplayModeMenu<T extends string>({
263
263
  aria-label="Shading options"
264
264
  aria-haspopup="menu"
265
265
  aria-expanded={open}
266
- aria-controls={open ? 'vgai-viewport-shading-menu' : undefined}
266
+ aria-controls={open ? 'volter-viewport-shading-menu' : undefined}
267
267
  disabled={disabled}
268
268
  onClick={() => setOpen((value) => !value)}
269
269
  >
@@ -286,7 +286,7 @@ export function ViewportDisplayModeMenu<T extends string>({
286
286
  a bordered well: measured in `modeling-object-none.png`, `Object
287
287
  Mode v` is #272727 inside a 1px #3c3c3c border, 20px tall, on a
288
288
  #343434 header. `secondary` is this estate's select-like trigger
289
- already (FontPicker's `.vgai-field-trigger`), so no new variant. */}
289
+ already (FontPicker's `.volter-field-trigger`), so no new variant. */}
290
290
  <Button
291
291
  ref={ref}
292
292
  type="button"
@@ -295,7 +295,7 @@ export function ViewportDisplayModeMenu<T extends string>({
295
295
  aria-label={`Viewport shading: ${active.label}`}
296
296
  aria-haspopup="menu"
297
297
  aria-expanded={open}
298
- aria-controls={open ? 'vgai-viewport-shading-menu' : undefined}
298
+ aria-controls={open ? 'volter-viewport-shading-menu' : undefined}
299
299
  disabled={disabled}
300
300
  onClick={() => setOpen((value) => !value)}
301
301
  style={{ display: 'inline-flex', alignItems: 'center', gap: 5, minWidth: 66 }}
@@ -86,7 +86,7 @@ export function ViewportViewMenu({
86
86
  };
87
87
 
88
88
  return (
89
- <div ref={anchor} className="vgai-viewport-popover-anchor">
89
+ <div ref={anchor} className="volter-viewport-popover-anchor">
90
90
  {label === undefined ? (
91
91
  <Tooltip text={projection === 'perspective' ? 'Perspective' : 'Orthographic'}>
92
92
  <IconButton
@@ -57,7 +57,7 @@ interface RemoteRayMarker {
57
57
 
58
58
  /**
59
59
  * Remote-camera labels paint into a DETACHED canvas, and a 2D context cannot
60
- * resolve `var(--vgai-…)` — so the theme's value has to be read off the live
60
+ * resolve `var(--volter-…)` — so the theme's value has to be read off the live
61
61
  * theme root before it reaches `fillStyle`. Before a theme is installed (unit
62
62
  * environments), fall back to the default theme's own value rather than a raw
63
63
  * literal, so the label never depends on a color the theme contract doesn't own.
@@ -65,7 +65,7 @@ interface RemoteRayMarker {
65
65
  function contentOnAccentColor(): string {
66
66
  const root = document.querySelector(`.${EDITOR_THEME_CLASS}`);
67
67
  const resolved = root
68
- ? getComputedStyle(root).getPropertyValue('--vgai-content-on-accent').trim()
68
+ ? getComputedStyle(root).getPropertyValue('--volter-content-on-accent').trim()
69
69
  : '';
70
70
  return resolved || graphiteDarkEditorTheme.color.content.onAccent;
71
71
  }
@@ -78,7 +78,7 @@ export interface BackdropStops {
78
78
  */
79
79
  export function paletteBackdropStops(): BackdropStops {
80
80
  const root =
81
- typeof document === 'undefined' ? null : document.querySelector('[data-vgai-palette]');
81
+ typeof document === 'undefined' ? null : document.querySelector('[data-volter-palette]');
82
82
  if (!root) return { bottom: GRADIENT_BOTTOM.clone(), top: GRADIENT_TOP.clone() };
83
83
  const style = getComputedStyle(root);
84
84
  const canvas = document.createElement('canvas');
@@ -91,15 +91,15 @@ export function paletteBackdropStops(): BackdropStops {
91
91
  // their CSS colors over the shell; Three.Color has no alpha channel.
92
92
  context.fillStyle = fallback.getStyle();
93
93
  context.fillRect(0, 0, 1, 1);
94
- context.fillStyle = style.getPropertyValue('--vgai-surface-shell').trim();
94
+ context.fillStyle = style.getPropertyValue('--volter-surface-shell').trim();
95
95
  context.fillRect(0, 0, 1, 1);
96
96
  context.fillStyle = value;
97
97
  context.fillRect(0, 0, 1, 1);
98
98
  const [r, g, b] = context.getImageData(0, 0, 1, 1).data;
99
99
  return new THREE.Color().setRGB(r! / 255, g! / 255, b! / 255, THREE.SRGBColorSpace);
100
100
  };
101
- const panel = read('--vgai-surface-panel', GRADIENT_BOTTOM);
102
- const raised = read('--vgai-surface-raised', GRADIENT_TOP);
101
+ const panel = read('--volter-surface-panel', GRADIENT_BOTTOM);
102
+ const raised = read('--volter-surface-raised', GRADIENT_TOP);
103
103
  // A background texture is not tone-mapped, so what is authored here is
104
104
  // what the screen shows. The small lift keeps the ground a step above the
105
105
  // panel it sits beside (Blender's viewport is lighter than its editors).
@@ -112,14 +112,14 @@ export function paletteBackdropStops(): BackdropStops {
112
112
 
113
113
  /**
114
114
  * Fires when the palette the backdrop follows changes. The theme installer
115
- * stamps its root with `data-vgai-palette` / `data-vgai-material` on every
115
+ * stamps its root with `data-volter-palette` / `data-volter-material` on every
116
116
  * switch, so the DOM is the contract here — a document stage never imports
117
117
  * the shell's preference store (its closure is pinned,
118
118
  * `scripts/validate-editor-closure.mjs`). Returns the unsubscribe.
119
119
  */
120
120
  export function watchPaletteBackdrop(onChange: () => void): () => void {
121
121
  if (typeof document === 'undefined' || typeof MutationObserver === 'undefined') return () => {};
122
- const root = document.querySelector('[data-vgai-palette]');
122
+ const root = document.querySelector('[data-volter-palette]');
123
123
  if (!root) return () => {};
124
124
  const observer = new MutationObserver(() => {
125
125
  onChange();
@@ -127,7 +127,7 @@ export function watchPaletteBackdrop(onChange: () => void): () => void {
127
127
  });
128
128
  observer.observe(root, {
129
129
  attributes: true,
130
- attributeFilter: ['data-vgai-palette', 'data-vgai-material'],
130
+ attributeFilter: ['data-volter-palette', 'data-volter-material'],
131
131
  });
132
132
  return () => observer.disconnect();
133
133
  }
@@ -248,7 +248,7 @@ export function applyGridDistanceFade(material: THREE.Material, gridSize: number
248
248
  );
249
249
  };
250
250
  // Distinct fade bands must not share one cached program.
251
- material.customProgramCacheKey = () => `vgai-grid-fade:${fadeStart}:${fadeEnd}`;
251
+ material.customProgramCacheKey = () => `volter-grid-fade:${fadeStart}:${fadeEnd}`;
252
252
  }
253
253
 
254
254
  export interface StandardViewportDressingOptions {
@@ -302,7 +302,7 @@ export function applyStandardViewportDressing(
302
302
  // document stage is lit by its view's presentation instead (`StagePresentationRig`, and
303
303
  // `StageHost` turns this key off); a stage without a presentation still gets this one.
304
304
  const key = new THREE.DirectionalLight(0xfff3dd, 1.9);
305
- key.name = 'vgai:standard-dressing-key';
305
+ key.name = 'volter:standard-dressing-key';
306
306
  key.position.set(6, 10, -4);
307
307
  key.castShadow = true;
308
308
  // `userData.editorHelper` keeps it out of hierarchy walks and picks;
@@ -336,7 +336,7 @@ export function applyStandardViewportDressing(
336
336
  gridMaterial.opacity = 0.35;
337
337
  gridMaterial.depthWrite = false;
338
338
  applyGridDistanceFade(gridMaterial, size);
339
- helper.name = 'vgai:standard-dressing-grid';
339
+ helper.name = 'volter:standard-dressing-grid';
340
340
  // Just below the ground line so content standing exactly on y=0 never
341
341
  // z-fights the grid lines; centred on the content's own footprint.
342
342
  helper.position.set(centre[0], -0.02, centre[1]);
@@ -402,7 +402,7 @@ const LIGHT_DISTANCE = 12;
402
402
  * else on the page fills.
403
403
  */
404
404
  const GODOT_FILMIC = /* glsl */ `
405
- vec3 vgaiHable( vec3 x ) {
405
+ vec3 volterHable( vec3 x ) {
406
406
  const float A = 0.22 * 4.0;
407
407
  const float B = 0.30 * 2.0;
408
408
  const float C = 0.10;
@@ -413,7 +413,7 @@ vec3 vgaiHable( vec3 x ) {
413
413
  }
414
414
  vec3 CustomToneMapping( vec3 color ) {
415
415
  color *= toneMappingExposure;
416
- return clamp( vgaiHable( max( vec3( 0.0 ), color ) ) / vgaiHable( vec3( 1.0 ) ), 0.0, 1.0 );
416
+ return clamp( volterHable( max( vec3( 0.0 ), color ) ) / volterHable( vec3( 1.0 ) ), 0.0, 1.0 );
417
417
  }`;
418
418
  {
419
419
  const chunk = THREE.ShaderChunk.tonemapping_pars_fragment;
@@ -548,7 +548,7 @@ function fadingFloorMaterial(): THREE.MeshStandardMaterial {
548
548
  `#include <dithering_fragment>\ngl_FragColor.a *= 1.0 - smoothstep(${(half * 0.3).toFixed(1)}, ${(half * 0.95).toFixed(1)}, length(vFloorPlane));`,
549
549
  );
550
550
  };
551
- material.customProgramCacheKey = () => 'vgai-fading-floor';
551
+ material.customProgramCacheKey = () => 'volter-fading-floor';
552
552
  return material;
553
553
  }
554
554
 
@@ -595,19 +595,19 @@ export class StagePresentationRig {
595
595
 
596
596
  /** `onReady`: an environment image arrived after the apply that asked for it; draw again. */
597
597
  constructor(scene: THREE.Scene, private readonly onReady?: () => void) {
598
- this.group.name = 'vgai:stage-presentation-rig';
598
+ this.group.name = 'volter:stage-presentation-rig';
599
599
  // Kept out of hierarchy walks and picks, like every editor helper.
600
600
  this.group.userData['editorHelper'] = true;
601
601
  this.group.add(this.ambient);
602
602
  scene.add(this.group);
603
- this.previewGroup.name = 'vgai:stage-preview-rig';
603
+ this.previewGroup.name = 'volter:stage-preview-rig';
604
604
  this.previewGroup.userData['editorHelper'] = true;
605
- this.sun.name = 'vgai:preview-sun';
605
+ this.sun.name = 'volter:preview-sun';
606
606
  this.sun.userData['editorHelper'] = true;
607
607
  this.previewGroup.add(this.sun, this.sun.target);
608
608
  this.previewGroup.visible = false;
609
609
  scene.add(this.previewGroup);
610
- this.floor.name = 'vgai:preview-floor';
610
+ this.floor.name = 'volter:preview-floor';
611
611
  this.floor.userData['editorHelper'] = true;
612
612
  this.floor.receiveShadow = true;
613
613
  // Transparent now (its fade), so it is sorted with the grid: drawn first, under it.
@@ -1013,7 +1013,7 @@ export class StagePresentationRig {
1013
1013
  this.ambient.intensity = preset.ambient.intensity;
1014
1014
  for (const spec of preset.lights) {
1015
1015
  const light = new THREE.DirectionalLight(spec.color, spec.intensity);
1016
- light.name = `vgai:studio-light:${preset.id}`;
1016
+ light.name = `volter:studio-light:${preset.id}`;
1017
1017
  light.castShadow = spec.castShadow === true;
1018
1018
  light.userData['editorHelper'] = true;
1019
1019
  this.group.add(light);
@@ -612,7 +612,7 @@ export class EditorShellStore {
612
612
  renderer.setRenderTarget(sceneTarget);
613
613
  // The same view policy the on-screen draw applies (`scene-view-fog.ts`).
614
614
  // This capture is the ONLY look anyone gets at the Edit viewport — the
615
- // `vgai screenshot` evidence and every project thumbnail — so a capture
615
+ // the editor's `screenshot` command evidence and every project thumbnail — so a capture
616
616
  // that renders the game's fog while the viewport does not would make the
617
617
  // one door onto the image disagree with the image.
618
618
  withSceneFogNeutralized(this._scene, () =>
@@ -611,7 +611,7 @@ export const COMPASS_CLUSTER_TOP_PX =
611
611
  * at the same pixels.
612
612
  */
613
613
  export const GRAZING_FADE_GLSL = /* glsl */ `
614
- float vgaiGrazingFade(vec3 worldPosition, vec3 eye, vec3 planeNormal, float amount) {
614
+ float volterGrazingFade(vec3 worldPosition, vec3 eye, vec3 planeNormal, float amount) {
615
615
  vec3 toEye = eye - worldPosition;
616
616
  float toEyeLength = max(length(toEye), 1e-6);
617
617
  float graze = 1.0 - abs(dot(toEye / toEyeLength, planeNormal));
@@ -686,10 +686,10 @@ function applyAxisGrazingFade(
686
686
  )
687
687
  .replace(
688
688
  'gl_FragColor = vec4( diffuseColor.rgb, alpha );',
689
- 'gl_FragColor = vec4( diffuseColor.rgb, alpha * vgaiGrazingFade( vAxisWorld, cameraPosition, uPlaneNormal, uGrazingFade ) );',
689
+ 'gl_FragColor = vec4( diffuseColor.rgb, alpha * volterGrazingFade( vAxisWorld, cameraPosition, uPlaneNormal, uGrazingFade ) );',
690
690
  );
691
691
  };
692
- material.customProgramCacheKey = () => 'vgai-axis-grazing-fade';
692
+ material.customProgramCacheKey = () => 'volter-axis-grazing-fade';
693
693
  }
694
694
 
695
695
  const _gridView = new THREE.Vector3();
@@ -824,7 +824,7 @@ function createFloorGrid(extent: number): THREE.Mesh<THREE.PlaneGeometry, THREE.
824
824
  // Blender's own grazing-angle profile, transcribed from its frames
825
825
  // (see GRAZING_FADE_GLSL above). Without it this lattice reads the
826
826
  // same ink at the front of the frame and at the horizon.
827
- fade *= vgaiGrazingFade(vWorld, cameraPosition, uPlaneNormal, uGrazingFade);
827
+ fade *= volterGrazingFade(vWorld, cameraPosition, uPlaneNormal, uGrazingFade);
828
828
  float alpha = line * fade * uOpacity;
829
829
  if (alpha <= 0.002) discard;
830
830
  // WHICH level a fragment belongs to is the major's SHARE of the
@@ -1731,7 +1731,7 @@ export class EditorViewport {
1731
1731
  this._vertexSnapIndicator,
1732
1732
  ];
1733
1733
 
1734
- // D12 (B4) — publish the camera/canvas seam `VgaiSceneAuthoringAdapter.pickable`
1734
+ // D12 (B4) — publish the camera/canvas seam `VolterSceneAuthoringAdapter.pickable`
1735
1735
  // needs (it has no other way to reach them). Cleared in `dispose()`.
1736
1736
  if (this._publishPickContext) {
1737
1737
  this._installPickContext(this.camera);
@@ -2363,8 +2363,8 @@ export class EditorViewport {
2363
2363
  // two competing selection languages and obscures the draggable handle.
2364
2364
  if ([...this._constraintHelpers.values()].some((helper) => helper.presents(obj))) continue;
2365
2365
  // Nor over an object whose document draws its overlay itself, selection colour included
2366
- // (`userData.vgaiOwnOverlay`: a Blender camera, light or empty).
2367
- if (obj.userData['vgaiOwnOverlay']) continue;
2366
+ // (`userData.volterOwnOverlay`: a Blender camera, light or empty).
2367
+ if (obj.userData['volterOwnOverlay']) continue;
2368
2368
  add(
2369
2369
  `selection:${id}`,
2370
2370
  new SelectionBrackets(obj, {
@@ -2759,9 +2759,9 @@ export class EditorViewport {
2759
2759
  }
2760
2760
 
2761
2761
  /** A light the editor draws a helper for: not one whose document draws its own overlay
2762
- * (`userData.vgaiOwnOverlay`, the way `vgaiOwnMaterial` keeps a material its owner's). */
2762
+ * (`userData.volterOwnOverlay`, the way `volterOwnMaterial` keeps a material its owner's). */
2763
2763
  private _nativeLight(object: THREE.Object3D): THREE.Light | null {
2764
- if (object.userData['vgaiOwnOverlay']) return null;
2764
+ if (object.userData['volterOwnOverlay']) return null;
2765
2765
  return (object as THREE.Light).isLight ? (object as THREE.Light) : null;
2766
2766
  }
2767
2767
 
@@ -2939,7 +2939,7 @@ export class EditorViewport {
2939
2939
  }
2940
2940
 
2941
2941
  private _nativeCamera(object: THREE.Object3D): THREE.Camera | null {
2942
- if (object.userData['vgaiOwnOverlay']) return null;
2942
+ if (object.userData['volterOwnOverlay']) return null;
2943
2943
  if ((object as THREE.Camera).isCamera) return object as THREE.Camera;
2944
2944
  const owned = getUserData(object, '_camera');
2945
2945
  return owned?.isCamera ? owned : null;
@@ -3733,7 +3733,7 @@ export class EditorViewport {
3733
3733
  }
3734
3734
 
3735
3735
  /** The helper groups on screen now (`setHelper`), for a pick that lets a helper stand for the
3736
- * object it draws (`userData.vgaiPicksAs`). */
3736
+ * object it draws (`userData.volterPicksAs`). */
3737
3737
  visibleHelpers(): THREE.Object3D[] {
3738
3738
  return [...this._helpers.values()].filter((helper) => helper.visible && helper.parent !== null);
3739
3739
  }
@@ -5577,10 +5577,10 @@ export class EditorViewport {
5577
5577
  ownMaterial &&
5578
5578
  mesh.material &&
5579
5579
  !Array.isArray(mesh.material) &&
5580
- !mesh.userData['vgaiOwnMaterial']
5580
+ !mesh.userData['volterOwnMaterial']
5581
5581
  ) {
5582
5582
  mesh.material = (mesh.material as THREE.Material).clone();
5583
- mesh.userData['vgaiOwnMaterial'] = true;
5583
+ mesh.userData['volterOwnMaterial'] = true;
5584
5584
  }
5585
5585
  const material = mesh.material as
5586
5586
  | (THREE.MeshBasicMaterial & { _color?: THREE.Color | undefined })
@@ -5593,7 +5593,7 @@ export class EditorViewport {
5593
5593
  // translucent); three caches it like the colour, so the cache is dropped too.
5594
5594
  // Handles only: the drag's axis lines (the `helper` family) keep three's own.
5595
5595
  if (handles) {
5596
- const base = (material.userData['vgaiBaseOpacity'] ??= material.opacity) as number;
5596
+ const base = (material.userData['volterBaseOpacity'] ??= material.opacity) as number;
5597
5597
  material.opacity = base * (this._gizmoLook.opacity ?? 1);
5598
5598
  (material as { _opacity?: number | undefined })._opacity = undefined;
5599
5599
  }
@@ -6159,7 +6159,7 @@ export class EditorViewport {
6159
6159
  pointerEvents: 'none',
6160
6160
  zIndex: zIndex.sticky,
6161
6161
  });
6162
- // INSIDE the theme scope, never document.body: the `--vgai-*` tokens
6162
+ // INSIDE the theme scope, never document.body: the `--volter-*` tokens
6163
6163
  // live on `#editor-chrome-root` (deliberately not `:root`), so a
6164
6164
  // body-parented div resolves both colors to nothing and the rubber
6165
6165
  // band is INVISIBLE — three human passes marqueed blind, reading the
@@ -6338,7 +6338,7 @@ export class EditorViewport {
6338
6338
 
6339
6339
  // D12 (B4) — topmost-first layered pick: the composite's non-threejs
6340
6340
  // children (react/pixi design-time layers) are tried topmost-first before
6341
- // falling through to the three raycast (now `VgaiSceneAuthoringAdapter
6341
+ // falling through to the three raycast (now `VolterSceneAuthoringAdapter
6342
6342
  // .pickable`) — see `pickTopmost`'s doc comment. Note: an INTERACTIVE
6343
6343
  // layer (session `interactive` toggle, B1) is mutually exclusive with this
6344
6344
  // walk running at all, by construction — its `pointer-events:auto` DOM
@@ -6489,7 +6489,7 @@ export class EditorViewport {
6489
6489
  background: themeVars.accent.muted,
6490
6490
  zIndex: zIndex.base,
6491
6491
  });
6492
- // Same class as the marquee band: `--vgai-*` resolves only under
6492
+ // Same class as the marquee band: `--volter-*` resolves only under
6493
6493
  // `#editor-chrome-root`, so a body-parented overlay draws with NO border
6494
6494
  // or fill — the drag-to-viewport highlight has been invisible.
6495
6495
  (document.getElementById('editor-chrome-root') ?? document.body).appendChild(overlay);
@@ -4,7 +4,7 @@
4
4
  * (`catalog/project-source/src/tools/module-source.ts`), and deliberately the
5
5
  * SAME contract: a project TypeScript module whose export builds a native
6
6
  * `THREE.Object3D`. `project.bake.module` / `project.bake.preview` /
7
- * `vgai screenshot <module>` already define that contract; the live modeling
7
+ * the editor's `screenshot <module>` command already define that contract; the live modeling
8
8
  * document opens the same thing, so a module that bakes opens, and a module
9
9
  * that opens bakes (docs/BLENDER-PARITY.md §The model file).
10
10
  *