@kernel.chat/kbot 3.1.0 → 3.1.2
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/tools/gamedev.d.ts.map +1 -1
- package/dist/tools/gamedev.js +152 -59
- package/dist/tools/gamedev.js.map +1 -1
- package/dist/tools/gamedev.test.d.ts +2 -0
- package/dist/tools/gamedev.test.d.ts.map +1 -0
- package/dist/tools/gamedev.test.js +937 -0
- package/dist/tools/gamedev.test.js.map +1 -0
- package/package.json +1 -1
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"gamedev.d.ts","sourceRoot":"","sources":["../../src/tools/gamedev.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"gamedev.d.ts","sourceRoot":"","sources":["../../src/tools/gamedev.ts"],"names":[],"mappings":"AAoFA,wBAAgB,oBAAoB,IAAI,IAAI,CAgsV3C"}
|
package/dist/tools/gamedev.js
CHANGED
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
// for Godot, Unity, Unreal, Bevy, Phaser, Three.js, PlayCanvas, and Defold.
|
|
4
4
|
import { registerTool } from './index.js';
|
|
5
5
|
import { writeFileSync, readFileSync, existsSync, mkdirSync, readdirSync } from 'node:fs';
|
|
6
|
-
import { dirname, join, basename, extname } from 'node:path';
|
|
6
|
+
import { dirname, join, basename, extname, resolve, relative, isAbsolute } from 'node:path';
|
|
7
7
|
import { execFile } from 'node:child_process';
|
|
8
8
|
// ── Helpers ──────────────────────────────────────────────────────────
|
|
9
9
|
/** Create directory tree if it doesn't exist */
|
|
@@ -75,18 +75,40 @@ function seededRng(seed) {
|
|
|
75
75
|
return (s >>> 0) / 4294967296;
|
|
76
76
|
};
|
|
77
77
|
}
|
|
78
|
+
function safePath(userPath) {
|
|
79
|
+
const resolved = resolve(process.cwd(), userPath);
|
|
80
|
+
const rel = relative(process.cwd(), resolved);
|
|
81
|
+
if (rel.startsWith('..') || isAbsolute(rel)) {
|
|
82
|
+
throw new Error(`Path must be within the working directory: ${userPath}`);
|
|
83
|
+
}
|
|
84
|
+
return resolved;
|
|
85
|
+
}
|
|
78
86
|
// ── Registration ─────────────────────────────────────────────────────
|
|
79
87
|
export function registerGamedevTools() {
|
|
88
|
+
const htmlSafe = (s) => s.replace(/[&<>"']/g, c => ({ '&': '&', '<': '<', '>': '>', '"': '"', "'": ''' }[c] ?? c));
|
|
89
|
+
/** Escape a string for safe interpolation into generated source code */
|
|
90
|
+
function codeSafe(s, lang = 'js') {
|
|
91
|
+
if (lang === 'rust' || lang === 'toml')
|
|
92
|
+
return s.replace(/\\/g, '\\\\').replace(/"/g, '\\"');
|
|
93
|
+
if (lang === 'gdscript' || lang === 'csharp')
|
|
94
|
+
return s.replace(/\\/g, '\\\\').replace(/"/g, '\\"').replace(/\n/g, '\\n');
|
|
95
|
+
if (lang === 'lua')
|
|
96
|
+
return s.replace(/\\/g, '\\\\').replace(/"/g, '\\"').replace(/\n/g, '\\n');
|
|
97
|
+
if (lang === 'ini')
|
|
98
|
+
return s.replace(/[=\n\r]/g, '_');
|
|
99
|
+
// js default
|
|
100
|
+
return s.replace(/\\/g, '\\\\').replace(/'/g, "\\'").replace(/"/g, '\\"').replace(/`/g, '\\`').replace(/\$/g, '\\$').replace(/\n/g, '\\n');
|
|
101
|
+
}
|
|
80
102
|
// ── Tool 1: scaffold_game ──────────────────────────────────────────
|
|
81
103
|
/** Per-engine scaffold file generators. Each returns an array of [relativePath, content] */
|
|
82
104
|
const scaffoldFiles = {
|
|
83
105
|
godot(name, tpl) {
|
|
84
106
|
const is3d = tpl === '3d';
|
|
85
107
|
const mainScene = is3d
|
|
86
|
-
? `[gd_scene load_steps=2 format=3]\n\n[node name="${name}" type="Node3D"]\n\n[node name="Camera3D" type="Camera3D" parent="."]\ntransform = Transform3D(1,0,0,0,1,0,0,0,1,0,2,5)\n\n[node name="DirectionalLight3D" type="DirectionalLight3D" parent="."]\n`
|
|
87
|
-
: `[gd_scene load_steps=2 format=3]\n\n[node name="${name}" type="Node2D"]\n`;
|
|
108
|
+
? `[gd_scene load_steps=2 format=3]\n\n[node name="${codeSafe(name, 'ini')}" type="Node3D"]\n\n[node name="Camera3D" type="Camera3D" parent="."]\ntransform = Transform3D(1,0,0,0,1,0,0,0,1,0,2,5)\n\n[node name="DirectionalLight3D" type="DirectionalLight3D" parent="."]\n`
|
|
109
|
+
: `[gd_scene load_steps=2 format=3]\n\n[node name="${codeSafe(name, 'ini')}" type="Node2D"]\n`;
|
|
88
110
|
return [
|
|
89
|
-
['project.godot', `[application]\nconfig/name="${name}"\nrun/main_scene="res://main.tscn"\nconfig/features=PackedStringArray("4.3")\n\n[rendering]\nrenderer/rendering_method="${is3d ? 'forward_plus' : 'gl_compatibility'}"\n`],
|
|
111
|
+
['project.godot', `[application]\nconfig/name="${codeSafe(name, 'ini')}"\nrun/main_scene="res://main.tscn"\nconfig/features=PackedStringArray("4.3")\n\n[rendering]\nrenderer/rendering_method="${is3d ? 'forward_plus' : 'gl_compatibility'}"\n`],
|
|
90
112
|
['main.tscn', mainScene],
|
|
91
113
|
['.gitignore', '.godot/\n*.import\nexport_presets.cfg\n'],
|
|
92
114
|
];
|
|
@@ -95,7 +117,7 @@ export function registerGamedevTools() {
|
|
|
95
117
|
const is3d = tpl === '3d';
|
|
96
118
|
return [
|
|
97
119
|
['Assets/.gitkeep', ''],
|
|
98
|
-
['Assets/Scripts/GameManager.cs', `using UnityEngine;\n\nnamespace ${name.replace(/[^a-zA-Z0-9]/g, '')}\n{\n public class GameManager : MonoBehaviour\n {\n void Start() { Debug.Log("${name} started"); }\n void Update() { }\n }\n}\n`],
|
|
120
|
+
['Assets/Scripts/GameManager.cs', `using UnityEngine;\n\nnamespace ${name.replace(/[^a-zA-Z0-9]/g, '')}\n{\n public class GameManager : MonoBehaviour\n {\n void Start() { Debug.Log("${codeSafe(name, 'csharp')} started"); }\n void Update() { }\n }\n}\n`],
|
|
99
121
|
['ProjectSettings/ProjectSettings.asset', `%YAML 1.1\n%TAG !u! tag:unity3d.com,2011:\n--- !u!129 &1\nPlayerSettings:\n productName: ${name}\n defaultScreenWidth: 1920\n defaultScreenHeight: 1080\n`],
|
|
100
122
|
['.gitignore', '[Ll]ibrary/\n[Tt]emp/\n[Oo]bj/\n[Bb]uild/\n*.csproj\n*.sln\n*.pidb\n*.userprefs\n'],
|
|
101
123
|
];
|
|
@@ -125,9 +147,9 @@ export function registerGamedevTools() {
|
|
|
125
147
|
const is3d = tpl === '3d';
|
|
126
148
|
return [
|
|
127
149
|
['package.json', JSON.stringify({ name: name.toLowerCase().replace(/[^a-z0-9-]/g, '-'), version: '0.1.0', private: true, scripts: { dev: 'vite', build: 'vite build' }, dependencies: { phaser: '^3.80.0' }, devDependencies: { vite: '^5.0.0', typescript: '^5.4.0' } }, null, 2)],
|
|
128
|
-
['index.html', `<!DOCTYPE html>\n<html><head><title>${name}</title></head>\n<body><script type="module" src="/src/main.ts"></script></body></html>\n`],
|
|
150
|
+
['index.html', `<!DOCTYPE html>\n<html><head><title>${htmlSafe(name)}</title></head>\n<body><script type="module" src="/src/main.ts"></script></body></html>\n`],
|
|
129
151
|
['src/main.ts', `import Phaser from 'phaser'\nimport { MainScene } from './scenes/MainScene'\n\nnew Phaser.Game({\n type: Phaser.AUTO,\n width: 800,\n height: 600,\n physics: { default: 'arcade', arcade: { gravity: { x: 0, y: 300 }, debug: false } },\n scene: [MainScene],\n})\n`],
|
|
130
|
-
['src/scenes/MainScene.ts', `import Phaser from 'phaser'\n\nexport class MainScene extends Phaser.Scene {\n constructor() { super('MainScene') }\n preload() { }\n create() {\n this.add.text(400, 300, '${name}', { fontSize: '32px', color: '#fff' }).setOrigin(0.5)\n }\n}\n`],
|
|
152
|
+
['src/scenes/MainScene.ts', `import Phaser from 'phaser'\n\nexport class MainScene extends Phaser.Scene {\n constructor() { super('MainScene') }\n preload() { }\n create() {\n this.add.text(400, 300, '${codeSafe(name, 'js')}', { fontSize: '32px', color: '#fff' }).setOrigin(0.5)\n }\n}\n`],
|
|
131
153
|
['tsconfig.json', JSON.stringify({ compilerOptions: { target: 'ES2020', module: 'ESNext', moduleResolution: 'bundler', strict: true, esModuleInterop: true }, include: ['src'] }, null, 2)],
|
|
132
154
|
['.gitignore', 'node_modules/\ndist/\n'],
|
|
133
155
|
];
|
|
@@ -136,7 +158,7 @@ export function registerGamedevTools() {
|
|
|
136
158
|
const is3d = true; // Three.js is always 3D
|
|
137
159
|
return [
|
|
138
160
|
['package.json', JSON.stringify({ name: name.toLowerCase().replace(/[^a-z0-9-]/g, '-'), version: '0.1.0', private: true, scripts: { dev: 'vite', build: 'vite build' }, dependencies: { three: '^0.170.0' }, devDependencies: { '@types/three': '^0.170.0', vite: '^5.0.0', typescript: '^5.4.0' } }, null, 2)],
|
|
139
|
-
['index.html', `<!DOCTYPE html>\n<html><head><title>${name}</title><style>body{margin:0;overflow:hidden}canvas{display:block}</style></head>\n<body><script type="module" src="/src/main.ts"></script></body></html>\n`],
|
|
161
|
+
['index.html', `<!DOCTYPE html>\n<html><head><title>${htmlSafe(name)}</title><style>body{margin:0;overflow:hidden}canvas{display:block}</style></head>\n<body><script type="module" src="/src/main.ts"></script></body></html>\n`],
|
|
140
162
|
['src/main.ts', `import * as THREE from 'three'\n\nconst scene = new THREE.Scene()\nconst camera = new THREE.PerspectiveCamera(75, window.innerWidth / window.innerHeight, 0.1, 1000)\nconst renderer = new THREE.WebGLRenderer({ antialias: true })\nrenderer.setSize(window.innerWidth, window.innerHeight)\ndocument.body.appendChild(renderer.domElement)\n\nconst geometry = new THREE.BoxGeometry()\nconst material = new THREE.MeshStandardMaterial({ color: 0x4488ff })\nconst cube = new THREE.Mesh(geometry, material)\nscene.add(cube)\n\nscene.add(new THREE.AmbientLight(0x404040))\nconst light = new THREE.DirectionalLight(0xffffff, 1)\nlight.position.set(5, 5, 5)\nscene.add(light)\n\ncamera.position.z = 5\n\nfunction animate() {\n requestAnimationFrame(animate)\n cube.rotation.x += 0.01\n cube.rotation.y += 0.01\n renderer.render(scene, camera)\n}\nanimate()\n\nwindow.addEventListener('resize', () => {\n camera.aspect = window.innerWidth / window.innerHeight\n camera.updateProjectionMatrix()\n renderer.setSize(window.innerWidth, window.innerHeight)\n})\n`],
|
|
141
163
|
['tsconfig.json', JSON.stringify({ compilerOptions: { target: 'ES2020', module: 'ESNext', moduleResolution: 'bundler', strict: true, esModuleInterop: true }, include: ['src'] }, null, 2)],
|
|
142
164
|
['.gitignore', 'node_modules/\ndist/\n'],
|
|
@@ -145,7 +167,7 @@ export function registerGamedevTools() {
|
|
|
145
167
|
playcanvas(name, tpl) {
|
|
146
168
|
return [
|
|
147
169
|
['package.json', JSON.stringify({ name: name.toLowerCase().replace(/[^a-z0-9-]/g, '-'), version: '0.1.0', private: true, scripts: { dev: 'vite', build: 'vite build' }, dependencies: { playcanvas: '^2.1.0' }, devDependencies: { vite: '^5.0.0', typescript: '^5.4.0' } }, null, 2)],
|
|
148
|
-
['index.html', `<!DOCTYPE html>\n<html><head><title>${name}</title><style>body{margin:0;overflow:hidden}canvas{display:block}</style></head>\n<body><canvas id="app"></canvas><script type="module" src="/src/main.ts"></script></body></html>\n`],
|
|
170
|
+
['index.html', `<!DOCTYPE html>\n<html><head><title>${htmlSafe(name)}</title><style>body{margin:0;overflow:hidden}canvas{display:block}</style></head>\n<body><canvas id="app"></canvas><script type="module" src="/src/main.ts"></script></body></html>\n`],
|
|
149
171
|
['src/main.ts', `import * as pc from 'playcanvas'\n\nconst canvas = document.getElementById('app') as HTMLCanvasElement\nconst app = new pc.Application(canvas, {})\napp.setCanvasFillMode(pc.FILLMODE_FILL_WINDOW)\napp.setCanvasResolution(pc.RESOLUTION_AUTO)\n\nconst camera = new pc.Entity('camera')\ncamera.addComponent('camera', { clearColor: new pc.Color(0.1, 0.1, 0.15) })\ncamera.setPosition(0, 2, 5)\ncamera.lookAt(pc.Vec3.ZERO)\napp.root.addChild(camera)\n\nconst light = new pc.Entity('light')\nlight.addComponent('light')\nlight.setEulerAngles(45, 30, 0)\napp.root.addChild(light)\n\nconst box = new pc.Entity('box')\nbox.addComponent('render', { type: 'box' })\napp.root.addChild(box)\n\napp.on('update', (dt: number) => { box.rotate(10 * dt, 20 * dt, 0) })\napp.start()\n`],
|
|
150
172
|
['tsconfig.json', JSON.stringify({ compilerOptions: { target: 'ES2020', module: 'ESNext', moduleResolution: 'bundler', strict: true, esModuleInterop: true }, include: ['src'] }, null, 2)],
|
|
151
173
|
['.gitignore', 'node_modules/\ndist/\n'],
|
|
@@ -154,10 +176,10 @@ export function registerGamedevTools() {
|
|
|
154
176
|
defold(name, tpl) {
|
|
155
177
|
const is3d = tpl === '3d';
|
|
156
178
|
return [
|
|
157
|
-
['game.project', `[project]\ntitle = ${name}\n\n[display]\nwidth = 960\nheight = 640\n\n[bootstrap]\nmain_collection = /main/main.collectionc\n\n[physics]\ntype = ${is3d ? '3D' : '2D'}\n`],
|
|
179
|
+
['game.project', `[project]\ntitle = ${codeSafe(name, 'ini')}\n\n[display]\nwidth = 960\nheight = 640\n\n[bootstrap]\nmain_collection = /main/main.collectionc\n\n[physics]\ntype = ${is3d ? '3D' : '2D'}\n`],
|
|
158
180
|
['main/main.collection', `name: "main"\ninstances {\n id: "go"\n prototype: "/main/game.go"\n position { x: 0.0 y: 0.0 z: 0.0 }\n}\n`],
|
|
159
181
|
['main/game.go', `components {\n id: "script"\n component: "/main/game.script"\n}\n`],
|
|
160
|
-
['main/game.script', `function init(self)\n msg.post(".", "acquire_input_focus")\n print("${name} started")\nend\n\nfunction update(self, dt)\nend\n\nfunction on_input(self, action_id, action)\nend\n`],
|
|
182
|
+
['main/game.script', `function init(self)\n msg.post(".", "acquire_input_focus")\n print("${codeSafe(name, 'lua')} started")\nend\n\nfunction update(self, dt)\nend\n\nfunction on_input(self, action_id, action)\nend\n`],
|
|
161
183
|
['.gitignore', 'build/\n.internal/\n'],
|
|
162
184
|
];
|
|
163
185
|
},
|
|
@@ -176,7 +198,7 @@ export function registerGamedevTools() {
|
|
|
176
198
|
const engine = String(args.engine).toLowerCase();
|
|
177
199
|
const name = String(args.name);
|
|
178
200
|
const template = String(args.template || 'blank').toLowerCase();
|
|
179
|
-
const outputDir = String(args.output_dir || `./${name}`);
|
|
201
|
+
const outputDir = safePath(String(args.output_dir || `./${name}`));
|
|
180
202
|
if (!scaffoldFiles[engine]) {
|
|
181
203
|
return `Error: Unknown engine "${engine}". Supported: ${Object.keys(scaffoldFiles).join(', ')}`;
|
|
182
204
|
}
|
|
@@ -200,11 +222,11 @@ export function registerGamedevTools() {
|
|
|
200
222
|
project: (s) => {
|
|
201
223
|
const lines = ['[application]'];
|
|
202
224
|
if (s.name)
|
|
203
|
-
lines.push(`config/name="${s.name}"`);
|
|
225
|
+
lines.push(`config/name="${codeSafe(String(s.name), 'ini')}"`);
|
|
204
226
|
if (s.main_scene)
|
|
205
|
-
lines.push(`run/main_scene="${s.main_scene}"`);
|
|
227
|
+
lines.push(`run/main_scene="${codeSafe(String(s.main_scene), 'ini')}"`);
|
|
206
228
|
lines.push('', '[rendering]');
|
|
207
|
-
lines.push(`renderer/rendering_method="${s.renderer || 'forward_plus'}"`);
|
|
229
|
+
lines.push(`renderer/rendering_method="${codeSafe(String(s.renderer || 'forward_plus'), 'ini')}"`);
|
|
208
230
|
if (s.vsync !== undefined)
|
|
209
231
|
lines.push(`[display]\nwindow/vsync/vsync_mode=${s.vsync ? 1 : 0}`);
|
|
210
232
|
if (s.width)
|
|
@@ -236,14 +258,14 @@ export function registerGamedevTools() {
|
|
|
236
258
|
rendering: (s) => {
|
|
237
259
|
const lines = ['[rendering]'];
|
|
238
260
|
if (s.renderer)
|
|
239
|
-
lines.push(`renderer/rendering_method="${s.renderer}"`);
|
|
261
|
+
lines.push(`renderer/rendering_method="${codeSafe(String(s.renderer), 'ini')}"`);
|
|
240
262
|
if (s.msaa)
|
|
241
263
|
lines.push(`anti_aliasing/quality/msaa_${s.msaa_type || '3d'}=${s.msaa}`);
|
|
242
264
|
if (s.shadows !== undefined)
|
|
243
265
|
lines.push(`lights_and_shadows/directional_shadow/size=${s.shadow_size || 4096}`);
|
|
244
266
|
return lines.join('\n') + '\n';
|
|
245
267
|
},
|
|
246
|
-
build: (s) => `[export]\nplatform="${s.platform || 'linux'}"\narch="${s.arch || 'x86_64'}"\n`,
|
|
268
|
+
build: (s) => `[export]\nplatform="${codeSafe(String(s.platform || 'linux'), 'ini')}"\narch="${codeSafe(String(s.arch || 'x86_64'), 'ini')}"\n`,
|
|
247
269
|
audio: (s) => {
|
|
248
270
|
const lines = ['[audio]'];
|
|
249
271
|
if (s.bus_count)
|
|
@@ -271,7 +293,7 @@ export function registerGamedevTools() {
|
|
|
271
293
|
bevy: {
|
|
272
294
|
project: (s) => {
|
|
273
295
|
const name = String(s.name || 'game').toLowerCase().replace(/[^a-z0-9-]/g, '-');
|
|
274
|
-
return `[package]\nname = "${name}"\nversion = "${s.version || '0.1.0'}"\nedition = "2021"\n\n[dependencies]\nbevy = { version = "0.15", features = [${(s.features || []).map((f) => `"${f}"`).join(', ')}] }\n\n[profile.dev]\nopt-level = 1\n[profile.dev.package."*"]\nopt-level = 3\n`;
|
|
296
|
+
return `[package]\nname = "${name}"\nversion = "${codeSafe(String(s.version || '0.1.0'), 'toml')}"\nedition = "2021"\n\n[dependencies]\nbevy = { version = "0.15", features = [${(s.features || []).map((f) => `"${codeSafe(f, 'toml')}"`).join(', ')}] }\n\n[profile.dev]\nopt-level = 1\n[profile.dev.package."*"]\nopt-level = 3\n`;
|
|
275
297
|
},
|
|
276
298
|
build: (s) => `# .cargo/config.toml\n[target.x86_64-unknown-linux-gnu]\nlinker = "clang"\nrustflags = ["-C", "link-arg=-fuse-ld=lld"]\n\n[target.x86_64-pc-windows-msvc]\nrustflags = ["-C", "link-arg=/DEBUG:NONE"]\n`,
|
|
277
299
|
rendering: (s) => `// Rendering plugin configuration\nuse bevy::prelude::*;\n\npub fn rendering_plugin(app: &mut App) {\n app.insert_resource(Msaa::Sample${s.msaa || 4})\n .insert_resource(ClearColor(Color::srgb(${s.clear_r ?? 0.1}, ${s.clear_g ?? 0.1}, ${s.clear_b ?? 0.15})));\n}\n`,
|
|
@@ -283,7 +305,7 @@ export function registerGamedevTools() {
|
|
|
283
305
|
project: (s) => JSON.stringify({ name: String(s.name || 'game').toLowerCase().replace(/[^a-z0-9-]/g, '-'), version: s.version || '0.1.0', private: true, scripts: { dev: 'vite', build: 'vite build' }, dependencies: { phaser: s.phaser_version || '^3.80.0' }, devDependencies: { vite: '^5.0.0', typescript: '^5.4.0' } }, null, 2),
|
|
284
306
|
physics: (s) => `// Phaser physics config\nexport const physicsConfig: Phaser.Types.Physics.ArcadePhysicsConfig = {\n gravity: { x: ${s.gravity_x ?? 0}, y: ${s.gravity_y ?? 300} },\n debug: ${s.debug ?? false},\n}\n`,
|
|
285
307
|
rendering: (s) => `// Phaser rendering config\nexport const renderConfig: Partial<Phaser.Types.Core.GameConfig> = {\n type: Phaser.${s.renderer === 'canvas' ? 'CANVAS' : s.renderer === 'webgl' ? 'WEBGL' : 'AUTO'},\n antialias: ${s.antialias ?? true},\n pixelArt: ${s.pixel_art ?? false},\n roundPixels: ${s.round_pixels ?? false},\n}\n`,
|
|
286
|
-
build: (s) => `// vite.config.ts\nimport { defineConfig } from 'vite'\nexport default defineConfig({\n base: '${s.base || './'}',\n build: { target: '${s.target || 'es2020'}', outDir: '${s.outDir || 'dist'}' },\n})\n`,
|
|
308
|
+
build: (s) => `// vite.config.ts\nimport { defineConfig } from 'vite'\nexport default defineConfig({\n base: '${codeSafe(String(s.base || './'), 'js')}',\n build: { target: '${codeSafe(String(s.target || 'es2020'), 'js')}', outDir: '${codeSafe(String(s.outDir || 'dist'), 'js')}' },\n})\n`,
|
|
287
309
|
input: (s) => `// Input key mapping\nexport const KEYS = ${JSON.stringify(s, null, 2)} as const\n`,
|
|
288
310
|
audio: (s) => `// Audio config\nexport const audioConfig = {\n disableWebAudio: ${s.disable_web_audio ?? false},\n noAudio: ${s.no_audio ?? false},\n}\n`,
|
|
289
311
|
},
|
|
@@ -291,7 +313,7 @@ export function registerGamedevTools() {
|
|
|
291
313
|
project: (s) => JSON.stringify({ name: String(s.name || 'game').toLowerCase().replace(/[^a-z0-9-]/g, '-'), version: s.version || '0.1.0', private: true, scripts: { dev: 'vite', build: 'vite build' }, dependencies: { three: s.three_version || '^0.170.0' }, devDependencies: { '@types/three': s.three_version || '^0.170.0', vite: '^5.0.0', typescript: '^5.4.0' } }, null, 2),
|
|
292
314
|
rendering: (s) => `// Three.js renderer config\nimport * as THREE from 'three'\n\nexport function createRenderer(canvas?: HTMLCanvasElement) {\n const renderer = new THREE.WebGLRenderer({ canvas, antialias: ${s.antialias ?? true}, alpha: ${s.alpha ?? false} })\n renderer.shadowMap.enabled = ${s.shadows ?? true}\n renderer.shadowMap.type = THREE.${s.shadow_type || 'PCFSoftShadowMap'}\n renderer.toneMapping = THREE.${s.tone_mapping || 'ACESFilmicToneMapping'}\n renderer.toneMappingExposure = ${s.exposure ?? 1.0}\n return renderer\n}\n`,
|
|
293
315
|
physics: (s) => `// Physics config (rapier/cannon)\nexport const physicsConfig = {\n gravity: { x: ${s.gravity_x ?? 0}, y: ${s.gravity_y ?? -9.81}, z: ${s.gravity_z ?? 0} },\n timestep: ${s.timestep ?? 1 / 60},\n}\n`,
|
|
294
|
-
build: (s) => `// vite.config.ts\nimport { defineConfig } from 'vite'\nexport default defineConfig({\n base: '${s.base || './'}',\n build: { target: '${s.target || 'es2020'}', outDir: '${s.outDir || 'dist'}' },\n})\n`,
|
|
316
|
+
build: (s) => `// vite.config.ts\nimport { defineConfig } from 'vite'\nexport default defineConfig({\n base: '${codeSafe(String(s.base || './'), 'js')}',\n build: { target: '${codeSafe(String(s.target || 'es2020'), 'js')}', outDir: '${codeSafe(String(s.outDir || 'dist'), 'js')}' },\n})\n`,
|
|
295
317
|
input: (s) => `// Input mapping\nexport const INPUT_MAP = ${JSON.stringify(s, null, 2)} as const\n`,
|
|
296
318
|
audio: (s) => `// Three.js audio config\nimport * as THREE from 'three'\n\nexport function createAudioListener(camera: THREE.Camera) {\n const listener = new THREE.AudioListener()\n camera.add(listener)\n return listener\n}\n`,
|
|
297
319
|
},
|
|
@@ -304,7 +326,7 @@ export function registerGamedevTools() {
|
|
|
304
326
|
input: (s) => {
|
|
305
327
|
const lines = ['[/Script/Engine.InputSettings]'];
|
|
306
328
|
for (const [name, key] of Object.entries(s)) {
|
|
307
|
-
lines.push(`+ActionMappings=(ActionName="${name}",bShift=False,bCtrl=False,bAlt=False,bCmd=False,Key=${key})`);
|
|
329
|
+
lines.push(`+ActionMappings=(ActionName="${codeSafe(String(name), 'ini')}",bShift=False,bCtrl=False,bAlt=False,bCmd=False,Key=${key})`);
|
|
308
330
|
}
|
|
309
331
|
return lines.join('\n') + '\n';
|
|
310
332
|
},
|
|
@@ -316,12 +338,12 @@ export function registerGamedevTools() {
|
|
|
316
338
|
project: (s) => JSON.stringify({ name: s.name || 'game', version: s.version || '0.1.0', scripts: { dev: 'vite', build: 'vite build' }, dependencies: { playcanvas: s.pc_version || '^2.1.0' } }, null, 2),
|
|
317
339
|
rendering: (s) => `// PlayCanvas rendering config\nexport const renderSettings = {\n antialias: ${s.antialias ?? true},\n shadows: ${s.shadows ?? true},\n gammaCorrection: pc.GAMMA_SRGB,\n toneMapping: pc.TONEMAP_ACES,\n}\n`,
|
|
318
340
|
physics: (s) => `// PlayCanvas physics (ammo.js)\nexport const physicsConfig = {\n gravity: [${s.gravity_x ?? 0}, ${s.gravity_y ?? -9.81}, ${s.gravity_z ?? 0}],\n fixedTimeStep: ${s.timestep ?? 1 / 60},\n}\n`,
|
|
319
|
-
build: (s) => `// vite.config.ts\nimport { defineConfig } from 'vite'\nexport default defineConfig({\n base: '${s.base || './'}',\n build: { target: 'es2020', outDir: '${s.outDir || 'dist'}' },\n})\n`,
|
|
341
|
+
build: (s) => `// vite.config.ts\nimport { defineConfig } from 'vite'\nexport default defineConfig({\n base: '${codeSafe(String(s.base || './'), 'js')}',\n build: { target: 'es2020', outDir: '${codeSafe(String(s.outDir || 'dist'), 'js')}' },\n})\n`,
|
|
320
342
|
input: (s) => `// Input config\nexport const INPUT_MAP = ${JSON.stringify(s, null, 2)} as const\n`,
|
|
321
|
-
audio: (s) => `// Audio config\nexport const audioConfig = { volume: ${s.volume ?? 1}, distanceModel: '${s.distance_model || 'inverse'}' }\n`,
|
|
343
|
+
audio: (s) => `// Audio config\nexport const audioConfig = { volume: ${s.volume ?? 1}, distanceModel: '${codeSafe(String(s.distance_model || 'inverse'), 'js')}' }\n`,
|
|
322
344
|
},
|
|
323
345
|
defold: {
|
|
324
|
-
project: (s) => `[project]\ntitle = ${s.name || 'Game'}\n\n[display]\nwidth = ${s.width || 960}\nheight = ${s.height || 640}\n\n[bootstrap]\nmain_collection = ${s.main_collection || '/main/main.collectionc'}\n`,
|
|
346
|
+
project: (s) => `[project]\ntitle = ${codeSafe(String(s.name || 'Game'), 'ini')}\n\n[display]\nwidth = ${s.width || 960}\nheight = ${s.height || 640}\n\n[bootstrap]\nmain_collection = ${codeSafe(String(s.main_collection || '/main/main.collectionc'), 'ini')}\n`,
|
|
325
347
|
physics: (s) => `[physics]\ntype = ${s.physics_type || '2D'}\ngravity_y = ${s.gravity ?? -10}\nscale = ${s.scale ?? 0.02}\n`,
|
|
326
348
|
rendering: (s) => `[graphics]\ndefault_texture_min_filter = ${s.min_filter || 'linear'}\ndefault_texture_mag_filter = ${s.mag_filter || 'linear'}\nmax_draw_calls = ${s.max_draw_calls || 1024}\n`,
|
|
327
349
|
build: (s) => `[native_extension]\napp_manifest = ${s.manifest || ''}\n`,
|
|
@@ -428,8 +450,11 @@ export function registerGamedevTools() {
|
|
|
428
450
|
let language = String(args.language).toLowerCase();
|
|
429
451
|
const target = String(args.target || 'desktop').toLowerCase();
|
|
430
452
|
// If source looks like a file path, try to read it
|
|
453
|
+
const shaderExts = ['.glsl', '.hlsl', '.wgsl', '.metal', '.frag', '.vert', '.comp', '.geom', '.tesc', '.tese', '.gdshader', '.shader', '.cg', '.fx'];
|
|
431
454
|
if (source.length < 300 && !source.includes('\n') && existsSync(source)) {
|
|
432
455
|
const ext = extname(source).toLowerCase();
|
|
456
|
+
if (!shaderExts.includes(ext))
|
|
457
|
+
return `Error: shader_debug only reads shader files (${shaderExts.join(', ')}). Got: ${ext || 'no extension'}`;
|
|
433
458
|
source = readFileSync(source, 'utf-8');
|
|
434
459
|
if (!args.language) {
|
|
435
460
|
const extMap = { '.glsl': 'glsl', '.vert': 'glsl', '.frag': 'glsl', '.hlsl': 'hlsl', '.wgsl': 'wgsl', '.shader': 'hlsl', '.cg': 'hlsl' };
|
|
@@ -892,7 +917,7 @@ void fragment() {
|
|
|
892
917
|
async execute(args) {
|
|
893
918
|
const materialType = String(args.material_type).toLowerCase();
|
|
894
919
|
const engine = String(args.engine).toLowerCase();
|
|
895
|
-
const outputPath = args.output_path ? String(args.output_path) : null;
|
|
920
|
+
const outputPath = args.output_path ? safePath(String(args.output_path)) : null;
|
|
896
921
|
let params = {};
|
|
897
922
|
if (args.params) {
|
|
898
923
|
try {
|
|
@@ -974,8 +999,8 @@ void fragment() {
|
|
|
974
999
|
},
|
|
975
1000
|
sphere(p) {
|
|
976
1001
|
const radius = p.radius ?? 0.5;
|
|
977
|
-
const rings = p.rings ?? 16;
|
|
978
|
-
const segments = p.segments ?? 32;
|
|
1002
|
+
const rings = Math.min(p.rings ?? 16, 256);
|
|
1003
|
+
const segments = Math.min(p.segments ?? 32, 512);
|
|
979
1004
|
const verts = [];
|
|
980
1005
|
const norms = [];
|
|
981
1006
|
const uvs = [];
|
|
@@ -1248,7 +1273,7 @@ void fragment() {
|
|
|
1248
1273
|
tier: 'free',
|
|
1249
1274
|
async execute(args) {
|
|
1250
1275
|
const shape = String(args.shape).toLowerCase();
|
|
1251
|
-
const outputPath = String(args.output_path);
|
|
1276
|
+
const outputPath = safePath(String(args.output_path));
|
|
1252
1277
|
let params = {};
|
|
1253
1278
|
if (args.params) {
|
|
1254
1279
|
try {
|
|
@@ -1287,8 +1312,8 @@ void fragment() {
|
|
|
1287
1312
|
timeout: 120_000,
|
|
1288
1313
|
async execute(args) {
|
|
1289
1314
|
const inputDir = String(args.input_dir);
|
|
1290
|
-
const outputImage = String(args.output_image);
|
|
1291
|
-
const outputData = String(args.output_data);
|
|
1315
|
+
const outputImage = safePath(String(args.output_image));
|
|
1316
|
+
const outputData = safePath(String(args.output_data));
|
|
1292
1317
|
const algorithm = String(args.algorithm || 'maxrects');
|
|
1293
1318
|
const maxSize = typeof args.max_size === 'number' ? args.max_size : 2048;
|
|
1294
1319
|
const padding = typeof args.padding === 'number' ? args.padding : 2;
|
|
@@ -1466,7 +1491,7 @@ void fragment() {
|
|
|
1466
1491
|
const atlasW = nextPow2(atlasSize.width);
|
|
1467
1492
|
const atlasH = nextPow2(atlasSize.height);
|
|
1468
1493
|
// Composite atlas using ImageMagick
|
|
1469
|
-
ensureDir(outputImage);
|
|
1494
|
+
ensureDir(dirname(outputImage));
|
|
1470
1495
|
const compositeArgs = [
|
|
1471
1496
|
'-size', `${atlasW}x${atlasH}`, 'xc:transparent',
|
|
1472
1497
|
];
|
|
@@ -1571,7 +1596,7 @@ void fragment() {
|
|
|
1571
1596
|
};
|
|
1572
1597
|
metadata = JSON.stringify(jsonData, null, 2);
|
|
1573
1598
|
}
|
|
1574
|
-
ensureDir(outputData);
|
|
1599
|
+
ensureDir(dirname(outputData));
|
|
1575
1600
|
writeFileSync(outputData, metadata);
|
|
1576
1601
|
return `Sprite atlas packed successfully:
|
|
1577
1602
|
Atlas: ${outputImage} (${atlasW}x${atlasH})
|
|
@@ -1596,8 +1621,14 @@ void fragment() {
|
|
|
1596
1621
|
async execute(args) {
|
|
1597
1622
|
const type = String(args.type).toLowerCase();
|
|
1598
1623
|
const engine = String(args.engine || 'rapier').toLowerCase();
|
|
1599
|
-
|
|
1600
|
-
|
|
1624
|
+
let params = {};
|
|
1625
|
+
try {
|
|
1626
|
+
params = args.params ? JSON.parse(String(args.params)) : {};
|
|
1627
|
+
}
|
|
1628
|
+
catch {
|
|
1629
|
+
return 'Error: params must be valid JSON';
|
|
1630
|
+
}
|
|
1631
|
+
const outputPath = safePath(String(args.output_path));
|
|
1601
1632
|
const validTypes = ['rigidbody', 'softbody', 'ragdoll', 'vehicle', 'cloth', 'joints'];
|
|
1602
1633
|
const validEngines = ['godot', 'unity', 'unreal', 'bevy', 'cannon', 'rapier', 'matter'];
|
|
1603
1634
|
if (!validTypes.includes(type))
|
|
@@ -2859,7 +2890,7 @@ const JOINT_CONFIG = ${JSON.stringify({
|
|
|
2859
2890
|
fileExt = 'cpp';
|
|
2860
2891
|
else if (fileExt === 'ts' && engine === 'bevy')
|
|
2861
2892
|
fileExt = 'rs';
|
|
2862
|
-
ensureDir(outputPath);
|
|
2893
|
+
ensureDir(dirname(outputPath));
|
|
2863
2894
|
writeFileSync(outputPath, code);
|
|
2864
2895
|
return `Physics setup generated:
|
|
2865
2896
|
Type: ${type}
|
|
@@ -2882,8 +2913,14 @@ const JOINT_CONFIG = ${JSON.stringify({
|
|
|
2882
2913
|
async execute(args) {
|
|
2883
2914
|
const effect = String(args.effect).toLowerCase();
|
|
2884
2915
|
const engine = String(args.engine || 'three').toLowerCase();
|
|
2885
|
-
const outputPath = String(args.output_path);
|
|
2886
|
-
|
|
2916
|
+
const outputPath = safePath(String(args.output_path));
|
|
2917
|
+
let overrides = {};
|
|
2918
|
+
try {
|
|
2919
|
+
overrides = args.params ? JSON.parse(String(args.params)) : {};
|
|
2920
|
+
}
|
|
2921
|
+
catch {
|
|
2922
|
+
return 'Error: params must be valid JSON';
|
|
2923
|
+
}
|
|
2887
2924
|
const validEffects = ['fire', 'smoke', 'rain', 'snow', 'sparks', 'magic', 'explosion', 'dust', 'bubbles', 'leaves', 'confetti'];
|
|
2888
2925
|
const validEngines = ['godot', 'unity', 'unreal', 'three', 'phaser', 'pixi'];
|
|
2889
2926
|
if (!validEffects.includes(effect))
|
|
@@ -3561,7 +3598,7 @@ ${effect === 'fire' || effect === 'smoke' ? ` // Grow over lifetime
|
|
|
3561
3598
|
}
|
|
3562
3599
|
if (!code)
|
|
3563
3600
|
return `Error: No implementation for effect="${effect}" with engine="${engine}"`;
|
|
3564
|
-
ensureDir(outputPath);
|
|
3601
|
+
ensureDir(dirname(outputPath));
|
|
3565
3602
|
writeFileSync(outputPath, code);
|
|
3566
3603
|
return `Particle system generated:
|
|
3567
3604
|
Effect: ${effect}
|
|
@@ -3588,12 +3625,18 @@ ${effect === 'fire' || effect === 'smoke' ? ` // Grow over lifetime
|
|
|
3588
3625
|
tier: 'free',
|
|
3589
3626
|
async execute(args) {
|
|
3590
3627
|
const type = String(args.type).toLowerCase();
|
|
3591
|
-
const width = typeof args.width === 'number' ? args.width : 40;
|
|
3592
|
-
const height = typeof args.height === 'number' ? args.height : 30;
|
|
3628
|
+
const width = Math.min(typeof args.width === 'number' ? args.width : 40, 1000);
|
|
3629
|
+
const height = Math.min(typeof args.height === 'number' ? args.height : 30, 1000);
|
|
3593
3630
|
const seedVal = typeof args.seed === 'number' ? args.seed : Date.now();
|
|
3594
|
-
const outputPath = String(args.output_path);
|
|
3631
|
+
const outputPath = safePath(String(args.output_path));
|
|
3595
3632
|
const format = String(args.format || 'json');
|
|
3596
|
-
|
|
3633
|
+
let params = {};
|
|
3634
|
+
try {
|
|
3635
|
+
params = args.params ? JSON.parse(String(args.params)) : {};
|
|
3636
|
+
}
|
|
3637
|
+
catch {
|
|
3638
|
+
return 'Error: params must be valid JSON';
|
|
3639
|
+
}
|
|
3597
3640
|
const validTypes = ['dungeon', 'platformer', 'overworld', 'maze', 'arena'];
|
|
3598
3641
|
if (!validTypes.includes(type))
|
|
3599
3642
|
return `Error: Invalid type "${type}". Use: ${validTypes.join(', ')}`;
|
|
@@ -4114,7 +4157,7 @@ ${effect === 'fire' || effect === 'smoke' ? ` // Grow over lifetime
|
|
|
4114
4157
|
};
|
|
4115
4158
|
output = JSON.stringify(jsonData, null, 2);
|
|
4116
4159
|
}
|
|
4117
|
-
ensureDir(outputPath);
|
|
4160
|
+
ensureDir(dirname(outputPath));
|
|
4118
4161
|
writeFileSync(outputPath, output);
|
|
4119
4162
|
const floorCount = map.flat().filter(t => t === FLOOR || t === SPAWN || t === EXIT || t === DOOR).length;
|
|
4120
4163
|
return `Level generated:
|
|
@@ -4142,7 +4185,7 @@ ${effect === 'fire' || effect === 'smoke' ? ` // Grow over lifetime
|
|
|
4142
4185
|
async execute(args) {
|
|
4143
4186
|
const tilesetType = String(args.tileset_type).toLowerCase();
|
|
4144
4187
|
const terrain = String(args.terrain).toLowerCase();
|
|
4145
|
-
const outputPath = String(args.output_path);
|
|
4188
|
+
const outputPath = safePath(String(args.output_path));
|
|
4146
4189
|
const format = String(args.format || 'json');
|
|
4147
4190
|
const validTypes = ['blob_47', 'wang_16', 'simple_4'];
|
|
4148
4191
|
const validTerrains = ['grass', 'stone', 'water', 'sand', 'snow', 'lava'];
|
|
@@ -4155,7 +4198,12 @@ ${effect === 'fire' || effect === 'smoke' ? ` // Grow over lifetime
|
|
|
4155
4198
|
// Parse or generate map data
|
|
4156
4199
|
let mapData;
|
|
4157
4200
|
if (args.map_data) {
|
|
4158
|
-
|
|
4201
|
+
try {
|
|
4202
|
+
mapData = JSON.parse(String(args.map_data));
|
|
4203
|
+
}
|
|
4204
|
+
catch {
|
|
4205
|
+
return 'Error: map_data must be valid JSON';
|
|
4206
|
+
}
|
|
4159
4207
|
}
|
|
4160
4208
|
else {
|
|
4161
4209
|
// Auto-generate a sample terrain map
|
|
@@ -4490,7 +4538,7 @@ tile_${i}/terrain_peering/left = ${cardW ? 0 : -1}`;
|
|
|
4490
4538
|
};
|
|
4491
4539
|
output = JSON.stringify(jsonOutput, null, 2);
|
|
4492
4540
|
}
|
|
4493
|
-
ensureDir(outputPath);
|
|
4541
|
+
ensureDir(dirname(outputPath));
|
|
4494
4542
|
writeFileSync(outputPath, output);
|
|
4495
4543
|
const terrainTileCount = tiledMap.flat().filter(t => t >= 0).length;
|
|
4496
4544
|
return `Tilemap generated:
|
|
@@ -4516,8 +4564,14 @@ tile_${i}/terrain_peering/left = ${cardW ? 0 : -1}`;
|
|
|
4516
4564
|
async execute(args) {
|
|
4517
4565
|
const engine = String(args.engine || 'recast').toLowerCase();
|
|
4518
4566
|
const agentType = String(args.agent_type || 'humanoid').toLowerCase();
|
|
4519
|
-
const outputPath = String(args.output_path);
|
|
4520
|
-
|
|
4567
|
+
const outputPath = safePath(String(args.output_path));
|
|
4568
|
+
let overrides = {};
|
|
4569
|
+
try {
|
|
4570
|
+
overrides = args.params ? JSON.parse(String(args.params)) : {};
|
|
4571
|
+
}
|
|
4572
|
+
catch {
|
|
4573
|
+
return 'Error: params must be valid JSON';
|
|
4574
|
+
}
|
|
4521
4575
|
const validEngines = ['godot', 'unity', 'unreal', 'recast', 'three'];
|
|
4522
4576
|
const validAgents = ['humanoid', 'vehicle', 'flying', 'small_creature'];
|
|
4523
4577
|
if (!validEngines.includes(engine))
|
|
@@ -5449,7 +5503,7 @@ export class NavigationSystem {
|
|
|
5449
5503
|
}
|
|
5450
5504
|
if (!code)
|
|
5451
5505
|
return `Error: No implementation for engine="${engine}"`;
|
|
5452
|
-
ensureDir(outputPath);
|
|
5506
|
+
ensureDir(dirname(outputPath));
|
|
5453
5507
|
writeFileSync(outputPath, code);
|
|
5454
5508
|
return `Navigation mesh config generated:
|
|
5455
5509
|
Engine: ${engine}
|
|
@@ -5619,8 +5673,17 @@ export class NavigationSystem {
|
|
|
5619
5673
|
async execute(args) {
|
|
5620
5674
|
const system = String(args.system).toLowerCase();
|
|
5621
5675
|
const engine = String(args.engine || 'web').toLowerCase();
|
|
5622
|
-
const outputPath = String(args.output_path);
|
|
5623
|
-
|
|
5676
|
+
const outputPath = safePath(String(args.output_path));
|
|
5677
|
+
let params = {};
|
|
5678
|
+
try {
|
|
5679
|
+
params = args.params ? JSON.parse(String(args.params)) : {};
|
|
5680
|
+
}
|
|
5681
|
+
catch {
|
|
5682
|
+
return 'Error: params must be valid JSON';
|
|
5683
|
+
}
|
|
5684
|
+
const validEngines = ['godot', 'unity', 'unreal', 'web', 'bevy'];
|
|
5685
|
+
if (!validEngines.includes(engine))
|
|
5686
|
+
return `Error: engine must be one of: ${validEngines.join(', ')}`;
|
|
5624
5687
|
const validSystems = ['spatial', 'music_layers', 'sound_bank', 'howler', 'web_audio'];
|
|
5625
5688
|
if (!validSystems.includes(system)) {
|
|
5626
5689
|
return `Error: Unknown audio system "${system}". Valid: ${validSystems.join(', ')}`;
|
|
@@ -7072,8 +7135,20 @@ export class AudioEngine {
|
|
|
7072
7135
|
const architecture = String(args.architecture).toLowerCase();
|
|
7073
7136
|
const transport = String(args.transport || 'websocket').toLowerCase();
|
|
7074
7137
|
const framework = String(args.framework || 'raw').toLowerCase();
|
|
7075
|
-
const outputDir = String(args.output_dir);
|
|
7076
|
-
|
|
7138
|
+
const outputDir = safePath(String(args.output_dir));
|
|
7139
|
+
let features = ['lobby', 'state_sync'];
|
|
7140
|
+
try {
|
|
7141
|
+
features = args.features ? JSON.parse(String(args.features)) : ['lobby', 'state_sync'];
|
|
7142
|
+
}
|
|
7143
|
+
catch {
|
|
7144
|
+
return 'Error: features must be valid JSON';
|
|
7145
|
+
}
|
|
7146
|
+
const validTransports = ['websocket', 'webrtc'];
|
|
7147
|
+
if (!validTransports.includes(transport))
|
|
7148
|
+
return `Error: transport must be one of: ${validTransports.join(', ')}`;
|
|
7149
|
+
const validFrameworks = ['colyseus', 'socket_io', 'geckos', 'nakama', 'raw'];
|
|
7150
|
+
if (!validFrameworks.includes(framework))
|
|
7151
|
+
return `Error: framework must be one of: ${validFrameworks.join(', ')}`;
|
|
7077
7152
|
const validArch = ['client_server', 'peer_to_peer', 'relay'];
|
|
7078
7153
|
if (!validArch.includes(architecture)) {
|
|
7079
7154
|
return `Error: Unknown architecture "${architecture}". Valid: ${validArch.join(', ')}`;
|
|
@@ -8478,7 +8553,7 @@ ${hasReconnect ? ' if (this.reconnectTimer) clearTimeout(this.reconnectTimer)
|
|
|
8478
8553
|
async execute(args) {
|
|
8479
8554
|
const engine = String(args.engine || 'web').toLowerCase();
|
|
8480
8555
|
const platforms = String(args.platforms).split(',').map(p => p.trim().toLowerCase());
|
|
8481
|
-
const outputDir = String(args.output_dir);
|
|
8556
|
+
const outputDir = safePath(String(args.output_dir));
|
|
8482
8557
|
const ci = String(args.ci || 'github_actions').toLowerCase();
|
|
8483
8558
|
const validPlatforms = ['steam', 'itch', 'web', 'ios', 'android'];
|
|
8484
8559
|
const invalid = platforms.filter(p => !validPlatforms.includes(p));
|
|
@@ -8980,8 +9055,14 @@ export default defineConfig({
|
|
|
8980
9055
|
async execute(args) {
|
|
8981
9056
|
const testType = String(args.test_type).toLowerCase();
|
|
8982
9057
|
const engine = String(args.engine || 'web').toLowerCase();
|
|
8983
|
-
const outputPath = String(args.output_path);
|
|
8984
|
-
|
|
9058
|
+
const outputPath = safePath(String(args.output_path));
|
|
9059
|
+
let params = {};
|
|
9060
|
+
try {
|
|
9061
|
+
params = args.params ? JSON.parse(String(args.params)) : {};
|
|
9062
|
+
}
|
|
9063
|
+
catch {
|
|
9064
|
+
return 'Error: params must be valid JSON';
|
|
9065
|
+
}
|
|
8985
9066
|
const validTypes = ['fps_profiler', 'memory_tracker', 'input_recorder', 'screenshot_test', 'performance_budget'];
|
|
8986
9067
|
if (!validTypes.includes(testType)) {
|
|
8987
9068
|
return `Error: Unknown test type "${testType}". Valid: ${validTypes.join(', ')}`;
|
|
@@ -10281,9 +10362,21 @@ export class PerformanceBudget {
|
|
|
10281
10362
|
tier: 'free',
|
|
10282
10363
|
async execute(args) {
|
|
10283
10364
|
const framework = String(args.framework).toLowerCase();
|
|
10284
|
-
|
|
10285
|
-
|
|
10286
|
-
|
|
10365
|
+
let entities = [];
|
|
10366
|
+
try {
|
|
10367
|
+
entities = JSON.parse(String(args.entities));
|
|
10368
|
+
}
|
|
10369
|
+
catch {
|
|
10370
|
+
return 'Error: entities must be valid JSON';
|
|
10371
|
+
}
|
|
10372
|
+
const outputDir = safePath(String(args.output_dir));
|
|
10373
|
+
let systems = [];
|
|
10374
|
+
try {
|
|
10375
|
+
systems = args.systems ? JSON.parse(String(args.systems)) : [];
|
|
10376
|
+
}
|
|
10377
|
+
catch {
|
|
10378
|
+
return 'Error: systems must be valid JSON';
|
|
10379
|
+
}
|
|
10287
10380
|
const validFrameworks = ['bevy', 'unity_dots', 'bitecs', 'miniplex', 'ecsy'];
|
|
10288
10381
|
if (!validFrameworks.includes(framework)) {
|
|
10289
10382
|
return `Error: Unknown ECS framework "${framework}". Valid: ${validFrameworks.join(', ')}`;
|