@vgai/editor 0.4.0 → 0.4.1
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/dist/assets/{axe-BJjvBeOZ.js → axe-N4cNyzfs.js} +1 -1
- package/dist/assets/{browserAll-Dn0pgD87.js → browserAll-BIBT0Z2H.js} +1 -1
- package/dist/assets/{create-runtime-BF9OD-U8.js → create-runtime-DxLhdk4U.js} +1 -1
- package/dist/assets/{game-u6c2Patu.js → game-4NbNRROm.js} +1 -1
- package/dist/assets/{game-umrHIqFB.js → game-B3UH5LjS.js} +1 -1
- package/dist/assets/{game-BR0GBNgF.js → game-ByxRLJi1.js} +1 -1
- package/dist/assets/{game-DeTGdgYt.js → game-CJ5BqOYG.js} +1 -1
- package/dist/assets/{game-DQxhCYsZ.js → game-CSJm1kwB.js} +1 -1
- package/dist/assets/{game-CLNm2g0y.js → game-D9Mo4L3Q.js} +1 -1
- package/dist/assets/{game-B346oj_f.js → game-DJmU9BQF.js} +1 -1
- package/dist/assets/{game-BCNN63uV.js → game-ZqGLgxRj.js} +1 -1
- package/dist/assets/{game-loop-t5W_oqxz.js → game-loop-9YP76JB1.js} +1 -1
- package/dist/assets/{game-source-DzE9taML.js → game-source-DLKG9pmj.js} +1 -1
- package/dist/assets/{index-DbQzGLxv.js → index-8A4JLNIp.js} +902 -892
- package/dist/assets/{index-CgcH1RpF.js → index-Brzr17q0.js} +1 -1
- package/dist/assets/{index-D3tcNB2T.js → index-CnKtvrru.js} +1 -1
- package/dist/assets/{index-Fc8Ezr20.js → index-D-WR11Jw.js} +1 -1
- package/dist/assets/index-DiUacMXR.css +1 -0
- package/dist/assets/{ingest-siblings-CW_xbvrF.js → ingest-siblings-Cmx39OBK.js} +1 -1
- package/dist/assets/{mount-manifest-DgUscyaD.js → mount-manifest-kzdZgkkc.js} +1 -1
- package/dist/assets/{react-18-C4tVCii_.js → react-18-DvWsdwa_.js} +1 -1
- package/dist/assets/webworkerAll-C9GR-KrR.js +1 -0
- package/dist/assets/{world-documents-BSaA5YIF.js → world-documents-DM_aIMAW.js} +1 -1
- package/dist/index.html +2 -2
- package/dist-server/packaged.mjs +27125 -1075
- package/package.json +2 -2
- package/server/asset-history-snapshots.ts +189 -0
- package/server/asset-library-routes.ts +404 -53
- package/server/catalog-import-recipe.ts +97 -0
- package/server/catalog-reimport.ts +141 -0
- package/server/cloud-asset-catalog.ts +71 -5
- package/server/dev.ts +1 -1
- package/server/editor-server.ts +106 -51
- package/server/editor-sse.ts +5 -0
- package/server/generative-execution-context.ts +45 -0
- package/server/model-import-conversion.ts +345 -0
- package/server/packaged.ts +1 -1
- package/server/project-hmr-files.ts +3 -5
- package/server/project-output-writer.ts +440 -36
- package/server/project-tools.ts +529 -0
- package/server/server-utils.ts +14 -11
- package/src/EditorContext.tsx +4 -4
- package/src/action-registry.ts +12 -0
- package/src/asset-editor-context.tsx +9 -0
- package/src/asset-events.ts +3 -15
- package/src/asset-selection.ts +3 -0
- package/src/asset-workflow/asset-capabilities.ts +40 -0
- package/src/asset-workflow/asset-import-jobs.ts +108 -0
- package/src/asset-workflow/asset-library-query.ts +8 -1
- package/src/asset-workflow/asset-workflow-quality.ts +2 -0
- package/src/asset-workflow/import-contract.ts +2 -0
- package/src/asset-workflow/model-inspection.ts +92 -1
- package/src/asset-workflow/preview-resource-lifetime.ts +44 -0
- package/src/asset-workflow/project-asset-commands.ts +1 -0
- package/src/asset-workflow/project-asset-health.ts +3 -6
- package/src/asset-workflow/project-asset-operations.ts +119 -4
- package/src/command-listener.ts +37 -9
- package/src/components/ApplicationMenus.tsx +13 -15
- package/src/components/AssetBrowser.tsx +301 -26
- package/src/components/AssetImportDetails.tsx +207 -92
- package/src/components/AssetImportJobStatus.tsx +55 -0
- package/src/components/CenterDocuments.tsx +28 -1
- package/src/components/CommandPalette.tsx +2 -2
- package/src/components/DefaultEditorLayout.tsx +4 -0
- package/src/components/InspectorToolSection.tsx +12 -10
- package/src/components/OnlineAssetBrowser.tsx +197 -11
- package/src/components/ToolHost.tsx +8 -8
- package/src/components/ViewportOverlay.tsx +13 -26
- package/src/components/ViewportPanel.tsx +19 -14
- package/src/components/ViewportShadingMenu.tsx +103 -0
- package/src/components/WorkspaceDock.tsx +103 -17
- package/src/components/asset-documents.tsx +38 -11
- package/src/components/asset-editor-persistence.ts +300 -5
- package/src/components/asset-selection-section.tsx +83 -9
- package/src/components/asset-viewers/AudioViewer.tsx +4 -7
- package/src/components/asset-viewers/ModelViewer.tsx +168 -5
- package/src/components/asset-viewers/OnlineAssetDetail.tsx +52 -12
- package/src/components/asset-viewers/SourceAssetViewer.tsx +7 -26
- package/src/components/asset-workflow.css +30 -3
- package/src/components/primitives/editor-icons.ts +1 -0
- package/src/components/project-tool-documents.tsx +298 -0
- package/src/components/status-contributions.tsx +38 -0
- package/src/components/tool-documents.tsx +20 -17
- package/src/components/tool-schema-form.tsx +258 -0
- package/src/components/workspace-dock.css +16 -0
- package/src/editor-api.ts +159 -22
- package/src/editor-store.ts +6 -5
- package/src/editor-viewport.ts +12 -7
- package/src/game-viewport-state.ts +28 -0
- package/src/model-thumbnail.ts +97 -9
- package/src/play-mode.ts +30 -3
- package/src/project-tool-discovery.ts +5 -5
- package/src/{project-operations.ts → project-tools.ts} +39 -26
- package/src/scene-sync.ts +0 -67
- package/src/storage/handle-storage.ts +5 -1
- package/src/storage/http-storage.ts +7 -2
- package/src/storage/mem-storage.ts +7 -1
- package/src/storage/types.ts +2 -0
- package/src/theme-css.ts +17 -7
- package/src/tool-loader.ts +152 -241
- package/src/viewport-shading-boundary.ts +15 -0
- package/src/widgets/index.ts +2 -1
- package/src/workspace-dock-controller.ts +8 -0
- package/src/workspace-document-registry.ts +4 -4
- package/src/workspace-state-persistence.ts +36 -10
- package/template/.claude/skills/editor/SKILL.md +13 -3
- package/template/AGENTS.md +100 -83
- package/template/package.json +16 -0
- package/template/public/scenes/default.vscn.json +1 -2
- package/template/scripts/playtest.ts +7 -37
- package/template/server/rooms/game-room.ts +2 -2
- package/template/src/data/README.md +3 -2
- package/template/src/data/tuning.data.json +1 -3
- package/template/src/data/tuning.schema.json +0 -12
- package/template/src/data/tuning.schema.ts +2 -9
- package/template/src/data/tuning.ts +1 -1
- package/template/src/scripts/registry.ts +0 -3
- package/template/src/tools/README.md +151 -16
- package/template/src/tools/autoplay.tool.ts +31 -0
- package/template/src/tools/autoplay.ts +15 -0
- package/template/src/tools/tuning.document.tsx +107 -0
- package/template/src/tools/tuning.tool.ts +24 -0
- package/template/src/tools/tuning.ts +9 -0
- package/template/src/ui/Button.stories.tsx +1 -1
- package/template/tests/acceptance/example.spec.ts +4 -59
- package/template/vgai.game.json +1 -1
- package/dist/assets/index-bAGhEnnO.css +0 -1
- package/dist/assets/webworkerAll-BA4aq7xq.js +0 -1
- package/dist/project-scripts.bundle.js +0 -3917
- package/server/project-operations.ts +0 -343
- package/src/components/project-operation-documents.tsx +0 -226
- package/template/src/operations/README.md +0 -88
- package/template/src/scripts/components/data-spinner.ts +0 -86
- package/template/src/scripts/components/spin-lap-math.ts +0 -27
- package/template/src/tools/example.tool.tsx +0 -97
- package/template/src/tools/spin.tool.tsx +0 -101
- package/template/tests/logic/example.test.ts +0 -31
|
@@ -1,16 +1,151 @@
|
|
|
1
|
-
# src/tools — project
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
`tool = defineTool(
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
`
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
`
|
|
1
|
+
# src/tools — registered project callables
|
|
2
|
+
|
|
3
|
+
A tool is an ordinary JavaScript/TypeScript function explicitly registered in
|
|
4
|
+
`package.json#vgai.tools`. The callable module exports `tool = defineTool(...)`
|
|
5
|
+
from `@vgai/sdk/tools`; keep reusable behavior in an ordinary sibling module so
|
|
6
|
+
game code and npm scripts can call it directly.
|
|
7
|
+
|
|
8
|
+
Every registered tool is discoverable and callable through:
|
|
9
|
+
|
|
10
|
+
- `import { tools } from '@vgai/live'` → `tools.list()` / `tools.call(...)`
|
|
11
|
+
- `vgai run --list` / `vgai run <name>`
|
|
12
|
+
- the editor's Project Tools catalog and schema-driven fallback form
|
|
13
|
+
|
|
14
|
+
A registration may also list editor `contributions`. Each one names an ordinary
|
|
15
|
+
React module, a stable contribution point, and a title. Its default component
|
|
16
|
+
receives `{ tool, client }` and calls `client.runProjectTool(tool.name, ...)`;
|
|
17
|
+
it must not contain a second copy of the tool's behavior. The supported points
|
|
18
|
+
are `workspace.document`, `workspace.utility`, and `selection.inspector` (the
|
|
19
|
+
last also exports `match(node, adapter)`). Prefer a focused custom contribution
|
|
20
|
+
for a substantial workflow; the generated form and Advanced JSON remain the
|
|
21
|
+
universal fallback.
|
|
22
|
+
|
|
23
|
+
Contribution UI is editor chrome. Use ordinary React for layout and state, and
|
|
24
|
+
import shared interactive controls from `@editor/widgets` so buttons, fields,
|
|
25
|
+
menus, focus rings, disabled states, density, and theme switching match the
|
|
26
|
+
rest of the editor. Inline styles are appropriate for layout and data-driven
|
|
27
|
+
values, not for forking the editor's control paint.
|
|
28
|
+
|
|
29
|
+
Match the contribution point's native shape: documents can use a bounded,
|
|
30
|
+
scrollable workspace; inspector sections should be dense and one-column;
|
|
31
|
+
utilities should be compact rows or small status blocks rather than document
|
|
32
|
+
forms dropped into the bottom dock. Keep badges to short statuses or counts.
|
|
33
|
+
Buttons and badges are intrinsically sized; set `width: '100%'` only when a
|
|
34
|
+
full-width action is intentional.
|
|
35
|
+
|
|
36
|
+
```tsx
|
|
37
|
+
import { Button, TextInput } from '@editor/widgets';
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
```json
|
|
41
|
+
{
|
|
42
|
+
"entry": "./src/tools/tuning.tool.ts",
|
|
43
|
+
"contributions": [
|
|
44
|
+
{
|
|
45
|
+
"id": "tuning",
|
|
46
|
+
"point": "workspace.document",
|
|
47
|
+
"title": "Tuning",
|
|
48
|
+
"entry": "./src/tools/tuning.document.tsx"
|
|
49
|
+
}
|
|
50
|
+
]
|
|
51
|
+
}
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
The starter registers `project.tuning` and `project.autoplay`. Both callable
|
|
55
|
+
implementations deliberately report `TOOL_NOT_CONFIGURED` until replaced with
|
|
56
|
+
game-specific behavior. `npm run playtest` calls the same `runAutoplay`
|
|
57
|
+
function as the registered autoplay tool.
|
|
58
|
+
|
|
59
|
+
Installed libraries can advertise helper tools through their own
|
|
60
|
+
`package.json#vgai.tools`; the humanoid bake is one example. Registration adds
|
|
61
|
+
addressability only—there is no extension base class, tool lifecycle, scripting
|
|
62
|
+
language, or domain switch in the host.
|
|
63
|
+
|
|
64
|
+
## Provider-native generated asset
|
|
65
|
+
|
|
66
|
+
Install Fal only in a project that authors Fal-backed assets:
|
|
67
|
+
|
|
68
|
+
```sh
|
|
69
|
+
npm install --save-dev @fal-ai/client@1.10.1 @vgai/fal-client-compat
|
|
70
|
+
```
|
|
71
|
+
|
|
72
|
+
Keep the useful implementation as ordinary code. This mock-first example uses
|
|
73
|
+
the upstream Fal call unchanged, validates the PNG, and hands the accepted file
|
|
74
|
+
to the host once. Direct and managed execution replace only the client Fetch
|
|
75
|
+
configuration; they do not change `fal.run(...)` or add provenance bookkeeping.
|
|
76
|
+
|
|
77
|
+
```ts
|
|
78
|
+
// src/tools/generate-icon.ts
|
|
79
|
+
import { createFalClient } from '@fal-ai/client';
|
|
80
|
+
import { createMockFalFetch } from '@vgai/fal-client-compat/mock';
|
|
81
|
+
import type { ToolContext } from '@vgai/sdk/tools';
|
|
82
|
+
|
|
83
|
+
export interface GenerateIconInput {
|
|
84
|
+
prompt: string;
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
export async function generateIcon(input: GenerateIconInput, ctx: ToolContext) {
|
|
88
|
+
if (!ctx.projectOutputs) throw new Error('The project-output writer is unavailable.');
|
|
89
|
+
const fal = createFalClient({
|
|
90
|
+
credentials: 'vgai-mock',
|
|
91
|
+
fetch: createMockFalFetch(),
|
|
92
|
+
});
|
|
93
|
+
const generated = await fal.run('fal-ai/nano-banana', {
|
|
94
|
+
input: { prompt: input.prompt, output_format: 'png' },
|
|
95
|
+
});
|
|
96
|
+
const file = generated.data.images[0];
|
|
97
|
+
if (!file) throw new Error('Fal returned no image.');
|
|
98
|
+
const response = await fetch(file.url);
|
|
99
|
+
if (!response.ok) throw new Error(`Fal image download failed: HTTP ${response.status}.`);
|
|
100
|
+
const bytes = new Uint8Array(await response.arrayBuffer());
|
|
101
|
+
const png = [137, 80, 78, 71, 13, 10, 26, 10];
|
|
102
|
+
if (!png.every((byte, index) => bytes[index] === byte)) {
|
|
103
|
+
throw new Error('Fal output is not a PNG.');
|
|
104
|
+
}
|
|
105
|
+
const path = 'public/generated/icon.png';
|
|
106
|
+
const committed = await ctx.projectOutputs.write([
|
|
107
|
+
{ path, content: bytes, mediaType: 'image/png', role: 'asset' },
|
|
108
|
+
]);
|
|
109
|
+
return {
|
|
110
|
+
path,
|
|
111
|
+
bytes: bytes.byteLength,
|
|
112
|
+
provenanceOperationId: committed.provenanceOperationId!,
|
|
113
|
+
};
|
|
114
|
+
}
|
|
115
|
+
```
|
|
116
|
+
|
|
117
|
+
The externally callable definition is deliberately thin:
|
|
118
|
+
|
|
119
|
+
```ts
|
|
120
|
+
// src/tools/generate-icon.tool.ts
|
|
121
|
+
import { defineTool } from '@vgai/sdk/tools';
|
|
122
|
+
import { z } from 'zod';
|
|
123
|
+
import { generateIcon } from './generate-icon';
|
|
124
|
+
|
|
125
|
+
export const tool = defineTool({
|
|
126
|
+
name: 'project.icon.generate',
|
|
127
|
+
summary: 'Generate a project icon with Fal.',
|
|
128
|
+
description: 'Runs the project-owned native Fal call and commits one validated PNG.',
|
|
129
|
+
input: z.object({ prompt: z.string().min(1) }),
|
|
130
|
+
result: z.object({
|
|
131
|
+
path: z.string(),
|
|
132
|
+
bytes: z.number().int().positive(),
|
|
133
|
+
provenanceOperationId: z.string(),
|
|
134
|
+
}),
|
|
135
|
+
errors: [],
|
|
136
|
+
requires: { project: true },
|
|
137
|
+
host: 'node',
|
|
138
|
+
mutates: true,
|
|
139
|
+
supportsDryRun: false,
|
|
140
|
+
longRunning: true,
|
|
141
|
+
permission: { risk: 'write', summary: 'Writes one generated PNG into this project.' },
|
|
142
|
+
impl: generateIcon,
|
|
143
|
+
});
|
|
144
|
+
```
|
|
145
|
+
|
|
146
|
+
Register `"./src/tools/generate-icon.tool.ts"` in `package.json#vgai.tools`,
|
|
147
|
+
then call it with a normal JSON input file:
|
|
148
|
+
|
|
149
|
+
```sh
|
|
150
|
+
npm run vgai -- run project.icon.generate --input generate-icon.input.json --yes
|
|
151
|
+
```
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import { defineTool } from '@vgai/sdk/tools';
|
|
2
|
+
import { z } from 'zod';
|
|
3
|
+
import { runAutoplay } from './autoplay';
|
|
4
|
+
|
|
5
|
+
export const tool = defineTool({
|
|
6
|
+
name: 'project.autoplay',
|
|
7
|
+
summary: 'Autoplay this game.',
|
|
8
|
+
description:
|
|
9
|
+
'Runs the game-authored @vgai/live interaction and acceptance loop. The starter implementation deliberately fails until the game has an honest playable path.',
|
|
10
|
+
input: z.object({}),
|
|
11
|
+
result: z.object({
|
|
12
|
+
passed: z.boolean(),
|
|
13
|
+
summary: z.string(),
|
|
14
|
+
artifacts: z.array(z.string()),
|
|
15
|
+
}),
|
|
16
|
+
errors: [
|
|
17
|
+
{
|
|
18
|
+
code: 'TOOL_NOT_CONFIGURED',
|
|
19
|
+
summary: 'The starter autoplay tool has not been implemented for this game.',
|
|
20
|
+
},
|
|
21
|
+
],
|
|
22
|
+
requires: { project: true, editor: true, play: true },
|
|
23
|
+
host: 'node',
|
|
24
|
+
mutates: false,
|
|
25
|
+
supportsDryRun: false,
|
|
26
|
+
longRunning: true,
|
|
27
|
+
permission: { risk: 'read', summary: 'Drives and observes the running game.' },
|
|
28
|
+
async impl() {
|
|
29
|
+
return runAutoplay();
|
|
30
|
+
},
|
|
31
|
+
});
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import { ToolError } from '@vgai/sdk/tools';
|
|
2
|
+
|
|
3
|
+
export interface AutoplayResult {
|
|
4
|
+
passed: boolean;
|
|
5
|
+
summary: string;
|
|
6
|
+
artifacts: string[];
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
/** Replace this stub with ordinary `@vgai/live` calls that drive and verify honest input. */
|
|
10
|
+
export async function runAutoplay(): Promise<AutoplayResult> {
|
|
11
|
+
throw new ToolError(
|
|
12
|
+
'TOOL_NOT_CONFIGURED',
|
|
13
|
+
'project.autoplay is a deliberate starter stub. Implement the first honest player interaction with @vgai/live as soon as the game becomes playable.',
|
|
14
|
+
);
|
|
15
|
+
}
|
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
import { Button } from '@editor/widgets';
|
|
2
|
+
import type { ToolContributionProps } from '@vgai/editor-sdk/contributions';
|
|
3
|
+
import { useState } from 'react';
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Starter document for the game-specific tuning tool. Keep this UI small and
|
|
7
|
+
* intentional: its only job is to collect useful inputs and call the tool.
|
|
8
|
+
*/
|
|
9
|
+
export default function TuningDocument({ tool, client }: ToolContributionProps) {
|
|
10
|
+
const [result, setResult] = useState<unknown>(null);
|
|
11
|
+
const [running, setRunning] = useState(false);
|
|
12
|
+
|
|
13
|
+
const verify = async () => {
|
|
14
|
+
setRunning(true);
|
|
15
|
+
try {
|
|
16
|
+
setResult(await client.runProjectTool(tool.name, {}, { confirm: true }));
|
|
17
|
+
} finally {
|
|
18
|
+
setRunning(false);
|
|
19
|
+
}
|
|
20
|
+
};
|
|
21
|
+
|
|
22
|
+
return (
|
|
23
|
+
<main
|
|
24
|
+
style={{
|
|
25
|
+
padding: 24,
|
|
26
|
+
fontFamily: 'var(--vgai-font-sans)',
|
|
27
|
+
maxWidth: 760,
|
|
28
|
+
margin: '0 auto',
|
|
29
|
+
color: 'inherit',
|
|
30
|
+
}}
|
|
31
|
+
data-testid="tuning-starter"
|
|
32
|
+
>
|
|
33
|
+
<section
|
|
34
|
+
style={{
|
|
35
|
+
overflow: 'hidden',
|
|
36
|
+
border: '1px solid var(--vgai-boundary-default)',
|
|
37
|
+
borderRadius: 'var(--vgai-radius-md)',
|
|
38
|
+
background: 'var(--vgai-surface-panel)',
|
|
39
|
+
}}
|
|
40
|
+
>
|
|
41
|
+
<div style={{ padding: '20px 22px' }}>
|
|
42
|
+
<div
|
|
43
|
+
style={{
|
|
44
|
+
color: 'var(--vgai-content-dim)',
|
|
45
|
+
fontSize: 11,
|
|
46
|
+
fontWeight: 700,
|
|
47
|
+
letterSpacing: '0.08em',
|
|
48
|
+
textTransform: 'uppercase',
|
|
49
|
+
}}
|
|
50
|
+
>
|
|
51
|
+
Starter contribution · not configured
|
|
52
|
+
</div>
|
|
53
|
+
<h2 style={{ margin: '7px 0 6px', fontSize: 20 }}>Game tuning</h2>
|
|
54
|
+
<p
|
|
55
|
+
style={{
|
|
56
|
+
margin: 0,
|
|
57
|
+
maxWidth: 620,
|
|
58
|
+
color: 'var(--vgai-content-muted)',
|
|
59
|
+
lineHeight: 1.55,
|
|
60
|
+
}}
|
|
61
|
+
>
|
|
62
|
+
Replace the schema in <code>tuning.tool.ts</code>, implement the data changes in{' '}
|
|
63
|
+
<code>tuning.ts</code>, then replace this starter with focused controls for the values
|
|
64
|
+
designers should change. Keep the behavior in the registered tool.
|
|
65
|
+
</p>
|
|
66
|
+
</div>
|
|
67
|
+
<footer
|
|
68
|
+
style={{
|
|
69
|
+
display: 'flex',
|
|
70
|
+
alignItems: 'center',
|
|
71
|
+
justifyContent: 'space-between',
|
|
72
|
+
gap: 16,
|
|
73
|
+
padding: '12px 22px',
|
|
74
|
+
borderTop: '1px solid var(--vgai-boundary-default)',
|
|
75
|
+
background: 'var(--vgai-surface-chrome)',
|
|
76
|
+
}}
|
|
77
|
+
>
|
|
78
|
+
<span style={{ color: 'var(--vgai-content-dim)', fontSize: 12 }}>
|
|
79
|
+
Calls <code>{tool.name}</code>
|
|
80
|
+
</span>
|
|
81
|
+
<Button
|
|
82
|
+
variant="solid"
|
|
83
|
+
disabled={running}
|
|
84
|
+
aria-busy={running}
|
|
85
|
+
onClick={() => void verify()}
|
|
86
|
+
>
|
|
87
|
+
{running ? 'Checking…' : 'Check callable stub'}
|
|
88
|
+
</Button>
|
|
89
|
+
</footer>
|
|
90
|
+
</section>
|
|
91
|
+
{result !== null && (
|
|
92
|
+
<pre
|
|
93
|
+
style={{
|
|
94
|
+
marginTop: 12,
|
|
95
|
+
padding: 12,
|
|
96
|
+
overflow: 'auto',
|
|
97
|
+
border: '1px solid var(--vgai-boundary-default)',
|
|
98
|
+
borderRadius: 'var(--vgai-radius-sm)',
|
|
99
|
+
background: 'var(--vgai-surface-inset)',
|
|
100
|
+
}}
|
|
101
|
+
>
|
|
102
|
+
{JSON.stringify(result, null, 2)}
|
|
103
|
+
</pre>
|
|
104
|
+
)}
|
|
105
|
+
</main>
|
|
106
|
+
);
|
|
107
|
+
}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import { defineTool } from '@vgai/sdk/tools';
|
|
2
|
+
import { z } from 'zod';
|
|
3
|
+
import { runTuning } from './tuning';
|
|
4
|
+
|
|
5
|
+
export const tool = defineTool({
|
|
6
|
+
name: 'project.tuning',
|
|
7
|
+
summary: 'Tune this game.',
|
|
8
|
+
description:
|
|
9
|
+
'Starter contract for the game-specific tuning tool. Replace its schema and ordinary implementation before use.',
|
|
10
|
+
input: z.object({}),
|
|
11
|
+
result: z.object({ updated: z.array(z.string()) }),
|
|
12
|
+
errors: [
|
|
13
|
+
{
|
|
14
|
+
code: 'TOOL_NOT_CONFIGURED',
|
|
15
|
+
summary: 'The starter tuning tool has not been customized for this game.',
|
|
16
|
+
},
|
|
17
|
+
],
|
|
18
|
+
requires: { project: true },
|
|
19
|
+
host: 'node',
|
|
20
|
+
mutates: true,
|
|
21
|
+
supportsDryRun: false,
|
|
22
|
+
permission: { risk: 'write', summary: "Updates this project's game-specific tuning data." },
|
|
23
|
+
impl: runTuning,
|
|
24
|
+
});
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import { ToolError } from '@vgai/sdk/tools';
|
|
2
|
+
|
|
3
|
+
/** Replace this stub with ordinary project code that updates the game's real tuning data. */
|
|
4
|
+
export async function runTuning(_input: Record<string, never>): Promise<{ updated: string[] }> {
|
|
5
|
+
throw new ToolError(
|
|
6
|
+
'TOOL_NOT_CONFIGURED',
|
|
7
|
+
'project.tuning is a deliberate starter stub. Define the game-specific input schema, update its real tuning data, and make the game consume those values before using this tool.',
|
|
8
|
+
);
|
|
9
|
+
}
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
* Worked CSF example — C3 (spec §9, docs/AI-NATIVE-AUTHORING-IMPLEMENTATION-SPEC.md
|
|
3
3
|
* "Add CSF Discovery and Portable Story Mounting"). A conventional
|
|
4
4
|
* `*.stories.tsx` file next to the component it documents, same "the file is
|
|
5
|
-
* the document" doctrine `src/tools/
|
|
5
|
+
* the document" doctrine `src/tools/tuning.document.tsx` and `src/data/`'s
|
|
6
6
|
* trio already follow in this project. In a React-root project, add
|
|
7
7
|
* `parameters: { vgai: { root: '<root-id>' } }` to associate a CSF document;
|
|
8
8
|
* the editor then shows it beneath that root in the main hierarchy and mounts
|
|
@@ -1,64 +1,9 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* L2 — acceptance layer (`docs/ACCEPTANCE-DRIVER-SPEC.md` §2.3): the canonical
|
|
3
|
-
* worked example. Run with `npm run test:acceptance` (`vgai probe`) — never
|
|
4
|
-
* `npx playwright test` (see `@vgai/probe`'s PROBE_ENV_MISSING error). Drives
|
|
5
|
-
* the template's shipped example component, `DataSpinner`
|
|
6
|
-
* (`src/scripts/components/data-spinner.ts`).
|
|
7
|
-
*/
|
|
8
|
-
|
|
1
|
+
/** Replace this bridge smoke test with game-specific honest-input acceptance specs. */
|
|
9
2
|
import { expect, test } from '@vgai/probe';
|
|
10
3
|
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
// rather than one test per case copy-pasted.
|
|
14
|
-
const BUILT_IN_PROVIDERS = ['time', 'input.actions'];
|
|
15
|
-
|
|
16
|
-
test('the debug bridge is alive before any game-specific state is asserted on', async ({
|
|
17
|
-
game,
|
|
18
|
-
}) => {
|
|
19
|
-
// proves the whole pipe (page → bridge → fixture) before any game code —
|
|
20
|
-
// a one-shot read races cold boot (sim may not have ticked yet under load),
|
|
21
|
-
// so this is a waitFor like every other time-dependent read in this file.
|
|
22
|
-
await game.waitFor((s) => (s('time') as { tick: number }).tick > 0, { simSeconds: 10 });
|
|
23
|
-
});
|
|
24
|
-
|
|
25
|
-
for (const name of BUILT_IN_PROVIDERS) {
|
|
26
|
-
test(`built-in provider "${name}" is enumerated`, async ({ game }) => {
|
|
27
|
-
const providers = await game.providers();
|
|
28
|
-
expect(providers.map((p) => p.name)).toContain(name);
|
|
29
|
-
});
|
|
30
|
-
}
|
|
31
|
-
|
|
32
|
-
test('spinner: fixture reset + honest-input boost + observable state + events + screenshot', async ({
|
|
33
|
-
game,
|
|
34
|
-
}) => {
|
|
35
|
-
// Self-degrading (gem-runner friction, #146 ledger): this test proves the
|
|
36
|
-
// TEMPLATE's shipped example component. The moment you replace the default
|
|
37
|
-
// scene with your own game — no Box, no DataSpinner — it skips with this
|
|
38
|
-
// message instead of failing your suite red. Replace it with specs for
|
|
39
|
-
// YOUR game's providers (and delete this one) as your first real spec.
|
|
40
|
-
const providerNames = (await game.providers()).map((p) => p.name);
|
|
41
|
-
test.skip(
|
|
42
|
-
!providerNames.includes('spinner'),
|
|
43
|
-
'the default scene\'s DataSpinner is gone (you replaced the scene — good); ' +
|
|
44
|
-
'replace this example spec with specs for your own game state',
|
|
45
|
-
);
|
|
46
|
-
|
|
47
|
-
// Fixture (declared cheat, D16): jump to a known lapsCompleted before
|
|
48
|
-
// proving anything — never a substitute for the honest-input path below.
|
|
49
|
-
await game.command('spinner.reset');
|
|
50
|
-
|
|
51
|
-
// Proof: drive the SAME action a human would (see
|
|
52
|
-
// public/inputmaps/default.inputmap.json's 'attack' binding) — DataSpinner
|
|
53
|
-
// reads it via ctx.input.isPressed, not a synthetic backdoor.
|
|
54
|
-
await game.input.hold('attack', { simSeconds: 4 });
|
|
55
|
-
|
|
56
|
-
await game.waitFor((s) => (s('spinner') as { lapsCompleted: number }).lapsCompleted >= 1, {
|
|
4
|
+
test('the debug bridge is alive', async ({ game }) => {
|
|
5
|
+
await game.waitFor((state) => (state('time') as { tick: number }).tick > 0, {
|
|
57
6
|
simSeconds: 10,
|
|
58
7
|
});
|
|
59
|
-
|
|
60
|
-
const spinner = (await game.state('spinner')) as { lapsCompleted: number };
|
|
61
|
-
expect(spinner.lapsCompleted).toBeGreaterThanOrEqual(1);
|
|
62
|
-
await game.events.expect(['spin-lap']);
|
|
63
|
-
await game.screenshot('after-boosted-spin');
|
|
8
|
+
expect((await game.providers()).map((provider) => provider.name)).toContain('input.actions');
|
|
64
9
|
});
|