@volter/project 0.5.211 → 0.5.212

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 (52) hide show
  1. package/NOTICE +2 -2
  2. package/README.md +8 -15
  3. package/package.json +5 -26
  4. package/schemas/volter-project.schema.json +33 -145
  5. package/schemas/volter-settings.schema.json +11 -49
  6. package/src/adapter/adapter-module.ts +204 -465
  7. package/src/adapter/adapter-surface.ts +4 -32
  8. package/src/adapter/authoring-seam-contract.ts +7 -24
  9. package/src/adapter/authoring.ts +126 -265
  10. package/src/adapter/editor-looks.ts +96 -127
  11. package/src/adapter/finders/registry.ts +6 -21
  12. package/src/adapter/host-context.ts +17 -72
  13. package/src/adapter/index.ts +4 -77
  14. package/src/adapter/manifest-interpreter.ts +7 -50
  15. package/src/adapter/physics-adapter-2d.ts +4 -21
  16. package/src/adapter/root-adapter.ts +37 -111
  17. package/src/adapter/root-seam-contract.ts +3 -6
  18. package/src/adapter/root-stacking.ts +4 -46
  19. package/src/adapter/seam-evidence.ts +0 -16
  20. package/src/adapter/system-adapter.ts +24 -656
  21. package/src/adapter/system-seam-contract.ts +1 -91
  22. package/src/adapter/transform.ts +4 -3
  23. package/src/adapter/workspace-arrangement.ts +29 -49
  24. package/src/manifest/editor-port.ts +10 -36
  25. package/src/manifest/filename.ts +3 -42
  26. package/src/manifest/load-file.ts +9 -15
  27. package/src/manifest/load.ts +44 -179
  28. package/src/manifest/locate.ts +6 -29
  29. package/src/manifest/schema.ts +155 -427
  30. package/src/semver.ts +16 -0
  31. package/src/settings/keys.ts +9 -11
  32. package/src/settings/schema.ts +55 -44
  33. package/schemas/engine-api.json +0 -57
  34. package/schemas/engine-api.md +0 -22
  35. package/src/adapter/binding.ts +0 -445
  36. package/src/adapter/debug-registry-ref.ts +0 -19
  37. package/src/adapter/frame-capture.ts +0 -80
  38. package/src/adapter/ingest/contract-system-slots.ts +0 -28
  39. package/src/adapter/ingest/game-contract-seams.ts +0 -86
  40. package/src/adapter/ingest/game-contract.ts +0 -506
  41. package/src/adapter/ingest/mount-readiness.ts +0 -140
  42. package/src/adapter/loop-gate-report.ts +0 -53
  43. package/src/adapter/native-entry-surface.ts +0 -100
  44. package/src/adapter/render-audio.ts +0 -20
  45. package/src/adapter/render-memory.ts +0 -34
  46. package/src/adapter/renderer-config.ts +0 -84
  47. package/src/adapter/system-slot.ts +0 -301
  48. package/src/core/system-phase.ts +0 -52
  49. package/src/inspection.ts +0 -281
  50. package/src/manifest/configuration-kinds.ts +0 -241
  51. package/src/manifest/kind-modules.ts +0 -58
  52. package/src/manifest/runtime-environment.ts +0 -48
@@ -4,25 +4,11 @@
4
4
 
5
5
  import type { PhysicsAdapter2D } from './physics-adapter-2d';
6
6
  import { defineSeamShape, type SeamShape } from './seam-evidence';
7
- import type {
8
- AudioAdapter,
9
- CameraAdapter,
10
- DebugAdapter,
11
- NavigationAdapter,
12
- NetworkingAdapter,
13
- PhysicsAdapter,
14
- RenderDebugAdapter,
15
- SystemAdapters,
16
- } from './system-adapter';
7
+ import type { CameraAdapter, PhysicsAdapter, SystemAdapters } from './system-adapter';
17
8
 
18
9
  export const SYSTEM_ADAPTERS_SHAPE = defineSeamShape<SystemAdapters>()({
19
10
  physics: { optional: true, kind: 'value', required: 'effect' },
20
- networking: { optional: true, kind: 'value', required: 'operation' },
21
- navigation: { optional: true, kind: 'value', required: 'operation' },
22
- audio: { optional: true, kind: 'value', required: 'effect' },
23
11
  camera: { optional: true, kind: 'value', required: 'effect' },
24
- debug: { optional: true, kind: 'value', required: 'operation' },
25
- renderDebug: { optional: true, kind: 'value', required: 'effect' },
26
12
  });
27
13
 
28
14
  export const PHYSICS_ADAPTER_SHAPE = defineSeamShape<PhysicsAdapter>()({
@@ -48,91 +34,15 @@ export const PHYSICS_2D_ADAPTER_SHAPE = defineSeamShape<PhysicsAdapter2D>()({
48
34
  unfreeze: { optional: false, kind: 'function', required: 'effect' },
49
35
  });
50
36
 
51
- export const NETWORKING_ADAPTER_SHAPE = defineSeamShape<NetworkingAdapter>()({
52
- peers: { optional: false, kind: 'function', required: 'operation' },
53
- networkId: { optional: false, kind: 'function', required: 'operation' },
54
- authority: { optional: false, kind: 'function', required: 'operation' },
55
- editable: { optional: false, kind: 'function', required: 'operation' },
56
- getConnectionState: { optional: true, kind: 'function', required: 'operation' },
57
- getRoomInfo: { optional: true, kind: 'function', required: 'operation' },
58
- getReplicationStats: { optional: true, kind: 'function', required: 'operation' },
59
- subscribe: { optional: false, kind: 'function', required: 'effect' },
60
- getStateSnapshot: { optional: true, kind: 'function', required: 'operation' },
61
- messageEvents: { optional: true, kind: 'function', required: 'operation' },
62
- getRates: { optional: true, kind: 'function', required: 'operation' },
63
- getConditioning: { optional: true, kind: 'function', required: 'operation' },
64
- setConditioning: { optional: true, kind: 'function', required: 'effect' },
65
- getServerConfig: { optional: true, kind: 'function', required: 'operation' },
66
- getPlayerIdentity: { optional: true, kind: 'function', required: 'operation' },
67
- setPlayerIdentity: { optional: true, kind: 'function', required: 'effect' },
68
- getTrafficByType: { optional: true, kind: 'function', required: 'operation' },
69
- sendMessage: { optional: true, kind: 'function', required: 'effect' },
70
- getConditioningLimits: { optional: true, kind: 'function', required: 'operation' },
71
- ping: { optional: true, kind: 'function', required: 'effect' },
72
- inspectServer: { optional: true, kind: 'function', required: 'operation' },
73
- disconnectClient: { optional: true, kind: 'function', required: 'effect' },
74
- editServerState: { optional: true, kind: 'function', required: 'effect' },
75
- deleteServerState: { optional: true, kind: 'function', required: 'effect' },
76
- sendToClient: { optional: true, kind: 'function', required: 'effect' },
77
- broadcast: { optional: true, kind: 'function', required: 'effect' },
78
- disposeRoom: { optional: true, kind: 'function', required: 'effect' },
79
- stateFieldType: { optional: true, kind: 'function', required: 'operation' },
80
- clearTraffic: { optional: true, kind: 'function', required: 'effect' },
81
- getTrafficByEntity: { optional: true, kind: 'function', required: 'operation' },
82
- isRecording: { optional: true, kind: 'function', required: 'operation' },
83
- setRecording: { optional: true, kind: 'function', required: 'effect' },
84
- });
85
-
86
- export const NAVIGATION_ADAPTER_SHAPE = defineSeamShape<NavigationAdapter>()({
87
- hasNavMesh: { optional: false, kind: 'function', required: 'operation' },
88
- findPath: { optional: false, kind: 'function', required: 'effect' },
89
- debugMesh: { optional: false, kind: 'function', required: 'operation' },
90
- bake: { optional: true, kind: 'function', required: 'effect' },
91
- exportData: { optional: true, kind: 'function', required: 'operation' },
92
- clear: { optional: true, kind: 'function', required: 'effect' },
93
- crowdAgents: { optional: true, kind: 'function', required: 'operation' },
94
- });
95
-
96
- export const AUDIO_ADAPTER_SHAPE = defineSeamShape<AudioAdapter>()({
97
- resume: { optional: true, kind: 'function', required: 'effect' },
98
- setMuted: { optional: false, kind: 'function', required: 'effect' },
99
- isMuted: { optional: false, kind: 'function', required: 'operation' },
100
- graphSnapshot: { optional: true, kind: 'function', required: 'operation' },
101
- transportState: { optional: true, kind: 'function', required: 'operation' },
102
- acquireMeters: { optional: true, kind: 'function', required: 'effect' },
103
- acquireRecordingStream: { optional: true, kind: 'function', required: 'effect' },
104
- renderOffline: { optional: true, kind: 'function', required: 'operation' },
105
- audioEvents: { optional: true, kind: 'function', required: 'operation' },
106
- });
107
-
108
37
  export const CAMERA_ADAPTER_SHAPE = defineSeamShape<CameraAdapter>()({
109
38
  snapshot: { optional: false, kind: 'function', required: 'operation' },
110
39
  subscribe: { optional: false, kind: 'function', required: 'effect' },
111
40
  });
112
41
 
113
- export const DEBUG_ADAPTER_SHAPE = defineSeamShape<DebugAdapter>()({
114
- providers: { optional: false, kind: 'function', required: 'operation' },
115
- state: { optional: false, kind: 'function', required: 'operation' },
116
- stateAll: { optional: false, kind: 'function', required: 'operation' },
117
- commands: { optional: false, kind: 'function', required: 'operation' },
118
- invoke: { optional: false, kind: 'function', required: 'effect' },
119
- events: { optional: false, kind: 'function', required: 'operation' },
120
- });
121
-
122
- export const RENDER_DEBUG_ADAPTER_SHAPE = defineSeamShape<RenderDebugAdapter>()({
123
- captureFrame: { optional: false, kind: 'function', required: 'effect' },
124
- memorySnapshot: { optional: true, kind: 'function', required: 'operation' },
125
- });
126
-
127
42
  type NonPhysicsSystemSlot = Exclude<keyof SystemAdapters, 'physics'>;
128
43
 
129
44
  export const SYSTEM_PROVIDER_SHAPES: {
130
45
  readonly [K in NonPhysicsSystemSlot]: SeamShape<NonNullable<SystemAdapters[K]>>;
131
46
  } = {
132
- networking: NETWORKING_ADAPTER_SHAPE,
133
- navigation: NAVIGATION_ADAPTER_SHAPE,
134
- audio: AUDIO_ADAPTER_SHAPE,
135
47
  camera: CAMERA_ADAPTER_SHAPE,
136
- debug: DEBUG_ADAPTER_SHAPE,
137
- renderDebug: RENDER_DEBUG_ADAPTER_SHAPE,
138
48
  };
@@ -1,7 +1,8 @@
1
1
  /**
2
2
  * The neutral transform value used across adapter interfaces. Position + scale
3
- * are xyz; rotation is a quaternion (xyzw) — matching `Object3D.quaternion`, so
4
- * adapters never have to agree on an Euler convention.
3
+ * are xyz; rotation is a quaternion (xyzw) — matching, e.g., the 3D
4
+ * integration's scene-object quaternion, so adapters never have to agree on an
5
+ * Euler convention.
5
6
  */
6
7
  export interface Transform {
7
8
  position: [number, number, number];
@@ -21,7 +22,7 @@ export interface Transform {
21
22
  * `'unresolved'` is the answer for a node THIS ADAPTER DOES NOT KNOW, and it is
22
23
  * a distinct answer on purpose: every other member names a driver, so returning
23
24
  * one of them for an unknown id is a positive claim about a node the
24
- * implementer has never seen. A game-scoped physics adapter is asked about
25
+ * implementer has never seen. A project-scoped physics adapter is asked about
25
26
  * every node the editor touches, including nodes belonging to another root —
26
27
  * "not mine" has to be sayable.
27
28
  */
@@ -1,52 +1,37 @@
1
- /** The editor's OWN captured dock arrangements (Animate, Look), shared data used by
2
- * imported layout components. A package's workspace ships its own beside its
3
- * layout contribution (`@volter/editor-blender`'s `model-arrangement.json`). */
1
+ /**
2
+ * Dock arrangement data shared by layout contributions. Each package keeps
3
+ * its workspace arrangement beside the owning layout contribution.
4
+ */
4
5
 
5
6
  /**
6
- * ONE EDITOR AREA of a workspace, and the document that fills it.
7
- *
8
- * **A Blender editor AREA is an editor group; the drawer holds utilities**
9
- * (orchestrator ruling 2026-09-19, WORK.md I5). Blender's UV Editing is a
10
- * side-by-side split of the centre (786/785 measured on the engine), Shading
11
- * and Animation a top/bottom split, and both the dock and the frame have
12
- * exactly that shape in editor groups — a second group beside or below the
13
- * document, with the workspace's own split ratio. So the UV editor, the node
14
- * editor and the image view are DOCUMENTS a workspace opens
15
- * into editor groups, never drawer views; the drawer keeps the utilities that
16
- * are not Blender areas (Console, Profiler, Network).
17
- *
18
- * THIS AMENDS "a workspace positions chrome and dock groups only; it never
19
- * opens documents" (ARCHITECTURE-CORE §Editor chrome, 2026-08-30), and the
20
- * amendment is narrow: a workspace opens the documents ITS OWN REFERENCE
21
- * FRAME'S SCREEN HAS, named here, and never a session's content. That is why
22
- * `normalizePresetLayout` still strips every `kind: 'document'` panel out of a
23
- * captured arrangement — the capture carries the GROUP and its settled size,
24
- * this list carries which document fills it, and the two never overlap.
7
+ * A workspace editor area is an editor group filled by a declared document.
8
+ * Areas specify their position and initial split ratio; the drawer hosts
9
+ * utilities. Captured arrangements retain group geometry, while this list
10
+ * names each area’s document contribution. normalizePresetLayout removes
11
+ * captured document panels so the two declarations do not overlap.
25
12
  */
26
13
  export interface WorkspaceAreaContribution {
27
- /** Stable area id, scoped to the workspace. BOTH hosts key an editor group by
28
- * it: the dock's group id is `volter:area:<id>`, and under the Code-OSS frame
29
- * it is the key of `volterDocuments.ts`'s `Map<areaId, IEditorGroup>` — the
30
- * group `IEditorGroupsService.addGroup` created from this area's `place`
31
- * (WORK.md §THE TIMELINE item 1, landed 2026-09-20). */
14
+ /**
15
+ * Stable workspace-scoped area id. The dock keys its group as
16
+ * volter:area:<id>; the native frame maps the same id to its editor group.
17
+ */
32
18
  readonly id: string;
33
- /** The `workspace.document` CONTRIBUTION id this area opens (the
34
- * contribution's file-derived id, e.g. `blender-uv-editor.document`), not a
35
- * document id: the document is minted per open subject. */
19
+ /** The declared `workspace.document` contribution id this area opens.
20
+ * The document instance id is minted per open subject. */
36
21
  readonly document: string;
37
22
  /** Where the group sits relative to the centre document's group. */
38
23
  readonly place: 'left' | 'right' | 'above' | 'below';
39
- /** The area's share of that split, 0..1 — Blender's own measured ratio,
40
- * cited by the workspace that declares it. It is the BOOTSTRAP size only:
24
+ /** The area's share of that split, 0..1, with its source cited by the
25
+ * workspace that declares it. It is the BOOTSTRAP size only:
41
26
  * once a capture of this workspace exists, the captured leaf's own size
42
27
  * wins, exactly as the drawer's captured height wins over its default.
43
28
  * Under the frame the same rule is `setSize` applied ONCE at group
44
29
  * creation, against the two groups' combined extent — a person's drag on
45
30
  * the sash is never overwritten, and a group they closed is re-created at
46
- * this ratio on the next reconcile, because a Blender area is resized or
47
- * joined, never closed. The one other stand-up is the area's document
31
+ * this ratio on the next reconcile, because an area is resized or joined,
32
+ * never closed. The one other stand-up is the area's document
48
33
  * changing its job and asking for that job's size
49
- * (`@volter/sdk/kit/workspace-areas`' `setWorkspaceAreaRatio`). */
34
+ * (editor-core's `src/workspace-areas.ts`' `setWorkspaceAreaRatio`). */
50
35
  readonly ratio: number;
51
36
  }
52
37
 
@@ -55,35 +40,30 @@ export interface WorkspaceArrangement {
55
40
  readonly id: string;
56
41
  readonly title: string;
57
42
  readonly regions?: {
58
- /** The WORKSPACE tab strip in the top bar (Blender's); a click on a tab
43
+ /** The WORKSPACE tab strip in the top bar; a click on a tab
59
44
  * is the explicit act that switches. Absent means hidden. */
60
45
  readonly workspaceTabs?: 'shown' | 'hidden';
61
46
  readonly header?: 'shown' | 'hidden';
62
47
  readonly shelf?: 'shown' | 'hidden';
63
48
  readonly inspector?: 'column' | 'properties';
64
49
  readonly drawer?: 'shown' | 'hidden';
65
- /** The top bar's runtime TELEMETRY cluster — the frame-rate readout, its
66
- * sparkline and the audio meter. `hidden` leaves the corner to the quiet
67
- * product controls (Invite, Account); the Profiler utility remains the
68
- * door to the same numbers. Absent means shown. */
69
- readonly telemetry?: 'shown' | 'hidden';
70
50
  /** The `·TypeName` a component-instance row prints after its name in the
71
- * hierarchy. `hidden` leaves the row its NAME alone, the way Blender's
72
- * Outliner does — there the type is the glyph. The type stays reachable
51
+ * hierarchy. `hidden` leaves the row its NAME alone, for a look whose
52
+ * type is the row's glyph. The type stays reachable
73
53
  * through the row's own tooltip and the inspector. Absent means shown. */
74
54
  readonly hierarchyTypeSuffix?: 'shown' | 'hidden';
75
55
  /** The dotted RULE a component-instance row draws under its name in the
76
- * hierarchy. `hidden` leaves the name unmarked, the way Blender's Outliner
77
- * does — it marks an instance nowhere in that panel, in any selection
78
- * state. The fact stays reachable through the row's own tooltip, its Go to
56
+ * hierarchy. `hidden` leaves the name unmarked, for a look that marks an
57
+ * instance nowhere in that panel, in any selection state. The fact stays reachable through the row's own tooltip, its Go to
79
58
  * Callsite / Open Component Source actions and the inspector. Absent means
80
59
  * shown. */
81
60
  readonly hierarchyInstanceRule?: 'shown' | 'hidden';
82
61
  /** Which restriction columns a hierarchy row carries at its right edge:
83
62
  * this editor's selection lock beside the eye (`select+viewport`), or
84
- * Blender's default filter (`viewport+render`) — the eye in its own
85
- * column with the render column reserved and blank, three.js having one
86
- * visibility flag for viewport and render alike. Absent means the pair. */
63
+ * the viewport and render pair (`viewport+render`) — the eye in its own
64
+ * column with the render column reserved and blank, since e.g. the 3D
65
+ * integration's scene objects have one visibility flag for viewport and
66
+ * render alike. Absent means the pair. */
87
67
  readonly hierarchyRestrictions?: 'select+viewport' | 'viewport+render';
88
68
  };
89
69
  /** The workspace's EDITOR AREAS beside the centre document — see
@@ -1,21 +1,7 @@
1
1
  /**
2
- * The editor's built-in port fallback, and the paired HMR-socket derivation.
3
- *
4
- * ONE owner for the number, because it is spelled in a launcher (the editor's `edit` command),
5
- * three servers (dev / prod / packaged), and a client default — a duplicated
6
- * literal across those drifts silently.
7
- *
8
- * **Why not Vite's 5173.** It used to be exactly that, and 5173 is the default
9
- * of every Vite app on the machine. On a box running any other Vite dev server
10
- * the engine-repo editor and that app fight over one port, and — worse —
11
- * `http://…:5173` can resolve to a FOREIGN application. Measured 2026-08-07:
12
- * an unrelated process on 5173 was what the editor's tab self-heal opened.
13
- *
14
- * **The band.** 20000-20199 is Volter's own reserved band: fixed, hand-picked
15
- * ports that must never be minted for a worktree session. Machine-local
16
- * allocations start at 20200, so one can never land on this default by chance.
17
- * 20173 keeps Vite's memorable `173` tail while sitting in a range nothing
18
- * else claims.
2
+ * The editor’s default port and paired HMR-socket port. Launchers, servers and
3
+ * clients share this constant. The reserved 20000–20199 band is excluded from
4
+ * machine-local session allocation, which starts at 20200.
19
5
  */
20
6
  export const DEFAULT_EDITOR_PORT = 20_173;
21
7
 
@@ -26,9 +12,8 @@ export const DEFAULT_EDITOR_PORT = 20_173;
26
12
  * worktree sessions cannot collide merely because their editor ports differ
27
13
  * by 100. Anything outside that band keeps the simple +100 offset.
28
14
  *
29
- * `DEFAULT_EDITOR_PORT` derives 30173 through the first arm — no special case,
30
- * which is the point: the old expression carried a hardcoded `5173 -> 24678`
31
- * exception precisely because the default sat outside the band it designed.
15
+ * `DEFAULT_EDITOR_PORT` derives 30173 through the first arm, with no special
16
+ * case.
32
17
  */
33
18
  export function editorHmrPort(editorPort: number): number {
34
19
  return editorPort >= 20_000 && editorPort < 30_100 ? editorPort + 10_000 : editorPort + 100;
@@ -44,23 +29,12 @@ function urlAuthorityHost(host: string): string {
44
29
  }
45
30
 
46
31
  /**
47
- * The ONE composer for an editor origin — every URL volter PRINTS, OPENS,
48
- * SELF-HEALS with, records in `.volter/session.json`, or dials.
49
- *
50
- * **It never says `localhost`.** A bare-`localhost` URL is a different address
51
- * from the one the server is listening on: on a dual-stack box `localhost`
52
- * resolves `::1` first while the editor binds `127.0.0.1`, so the URL reaches
53
- * whatever else happens to hold that port on IPv6. Measured 2026-08-07 — the
54
- * tab bijection's self-heal opened an UNRELATED application in the owner's
55
- * browser because it composed its reopen URL from `localhost` instead of the
56
- * host the server had actually bound.
32
+ * Compose an editor origin from its actual bind host. Callers that only know
33
+ * a port use IPv4 loopback. Wildcard binds also use loopback for a visitable
34
+ * local URL, including WSL2 loopback forwarding.
57
35
  *
58
- * `bindHost` is the host the server bound (`resolveBindHost`); callers that
59
- * only know a port — the CLI and SDK clients dialing a local session — omit it
60
- * and get the IPv4 loopback literal. A wildcard bind (`0.0.0.0` / `::`, which
61
- * is what WSL gets) is not an address a browser can visit, so it degrades to
62
- * that same literal: reachable from the same box, and reachable from Windows
63
- * through WSL2's loopback forwarding.
36
+ * A `localhost` URL can resolve to IPv6 while the server binds IPv4, reaching
37
+ * a different listener; emitted origins therefore use an explicit address.
64
38
  */
65
39
  export function editorOrigin(port: number, bindHost?: string): string {
66
40
  const host = (bindHost ?? '').trim();
@@ -1,45 +1,6 @@
1
- // The project manifest's FILENAME. There is exactly ONE.
2
- //
3
- // The manifest is `volter.project.json`, and NOTHING reads a second spelling
4
- // (the legacy-removal doctrine, `docs/ARCHITECTURE-CORE.md` §Vocabulary). Two
5
- // accepted names is the defect, not the convenience: both get written, and
6
- // copies of the "try both" probe spread into modules that agree with the real
7
- // resolver only by coincidence.
8
- //
9
- // The removed name REJECTS LOUDLY, naming the exact one-line fix — never
10
- // silently ignored, never redirected. A directory
11
- // carrying only `volter.game.json` is an error with a `git mv` in the message,
12
- // not a project that quietly fails to open.
13
- //
14
- // This module is the ONE place either literal is spelled out, and it is
15
- // dependency-free (no `node:fs`) so browser bundles can import it. The
16
- // filesystem side lives in `./locate` (`resolveManifestPath`, `hasManifest`,
17
- // `assertNoRemovedManifestFilename`).
1
+ // The project manifest's FILENAME. There is exactly ONE, and this module is the one place it is
2
+ // spelled out. It is dependency-free (no `node:fs`) so browser bundles can import it; the
3
+ // filesystem side lives in `./locate` (`resolveManifestPath`, `hasManifest`).
18
4
 
19
5
  /** The manifest filename. Every reader reads it; every writer writes it. */
20
6
  export const MANIFEST_FILENAME = 'volter.project.json';
21
-
22
- /** The removed filename. Read by nothing — its only job is the error message. */
23
- export const REMOVED_MANIFEST_FILENAME = 'volter.game.json';
24
-
25
- /** True for the manifest filename (basename comparison). */
26
- export function isManifestFilename(name: string): boolean {
27
- return name === MANIFEST_FILENAME;
28
- }
29
-
30
- /**
31
- * The verbatim rejection an on-disk `volter.game.json` earns — the one-line fix
32
- * spelled out, per the removed-format contract. Shared by the Node
33
- * (`./locate`) and browser (storage-backend) halves so a project author sees
34
- * the same sentence wherever the stale name is found.
35
- */
36
- export function removedManifestFilenameMessage(where: string): string {
37
- return (
38
- `${where}: found \`${REMOVED_MANIFEST_FILENAME}\` and no \`${MANIFEST_FILENAME}\`. ` +
39
- `\`${REMOVED_MANIFEST_FILENAME}\` was REMOVED (the legacy-removal doctrine, ` +
40
- 'docs/ARCHITECTURE-CORE.md §Vocabulary) — nothing reads it any more, and it is ' +
41
- 'deliberately NOT read as a fallback, because a second accepted name is the defect.\n' +
42
- `Fix: rename it — \`git mv ${REMOVED_MANIFEST_FILENAME} ${MANIFEST_FILENAME}\`. ` +
43
- 'The contents are unchanged; only the filename moved.'
44
- );
45
- }
@@ -1,13 +1,13 @@
1
- // T3.1 slice 1 — Node-only path wrapper around `loadGameManifest` (§5).
1
+ // Node-only path wrapper around `loadProjectManifest`.
2
2
  //
3
3
  // Kept in its own module (rather than folded into `load.ts`) so that
4
- // `loadGameManifest` itself stays free of `node:fs` — anything that only
4
+ // `loadProjectManifest` itself stays free of `node:fs` — anything that only
5
5
  // needs the pure parse/resolve function (e.g. a future browser/hosted-editor
6
6
  // caller) can import `./load` without pulling a Node-only module into its
7
7
  // bundle.
8
8
 
9
9
  import { readFileSync } from 'node:fs';
10
- import { type LoadGameManifestOptions, loadGameManifest, type ResolvedGameManifest } from './load';
10
+ import { loadProjectManifest, type ResolvedProjectManifest } from './load';
11
11
  import { resolveManifestPath } from './locate';
12
12
 
13
13
  // The dual-name filesystem lookup lives in `./locate` (Zod-free on purpose);
@@ -15,19 +15,13 @@ import { resolveManifestPath } from './locate';
15
15
  // module.
16
16
  export { hasManifest, manifestWritePath, resolveManifestPath } from './locate';
17
17
 
18
- /** Read + JSON.parse `path`, then resolve it via `loadGameManifest`. Node-only. */
19
- export function loadGameManifestFile(
20
- path: string,
21
- options: LoadGameManifestOptions = {},
22
- ): ResolvedGameManifest {
18
+ /** Read + JSON.parse `path`, then resolve it via `loadProjectManifest`. Node-only. */
19
+ export function loadProjectManifestFile(path: string): ResolvedProjectManifest {
23
20
  const raw: unknown = JSON.parse(readFileSync(path, 'utf-8'));
24
- return loadGameManifest(raw, options);
21
+ return loadProjectManifest(raw);
25
22
  }
26
23
 
27
- /** {@link loadGameManifestFile} against `dir`'s `volter.project.json`. */
28
- export function loadGameManifestDir(
29
- dir: string,
30
- options: LoadGameManifestOptions = {},
31
- ): ResolvedGameManifest {
32
- return loadGameManifestFile(resolveManifestPath(dir), options);
24
+ /** {@link loadProjectManifestFile} against `dir`'s `volter.project.json`. */
25
+ export function loadProjectManifestDir(dir: string): ResolvedProjectManifest {
26
+ return loadProjectManifestFile(resolveManifestPath(dir));
33
27
  }