@kernel.chat/kbot 3.1.1 → 3.1.3
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 +68 -35
- 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/install.sh +64 -9
- 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,19 +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() {
|
|
80
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
|
+
}
|
|
81
102
|
// ── Tool 1: scaffold_game ──────────────────────────────────────────
|
|
82
103
|
/** Per-engine scaffold file generators. Each returns an array of [relativePath, content] */
|
|
83
104
|
const scaffoldFiles = {
|
|
84
105
|
godot(name, tpl) {
|
|
85
106
|
const is3d = tpl === '3d';
|
|
86
107
|
const mainScene = is3d
|
|
87
|
-
? `[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`
|
|
88
|
-
: `[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`;
|
|
89
110
|
return [
|
|
90
|
-
['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`],
|
|
91
112
|
['main.tscn', mainScene],
|
|
92
113
|
['.gitignore', '.godot/\n*.import\nexport_presets.cfg\n'],
|
|
93
114
|
];
|
|
@@ -96,7 +117,7 @@ export function registerGamedevTools() {
|
|
|
96
117
|
const is3d = tpl === '3d';
|
|
97
118
|
return [
|
|
98
119
|
['Assets/.gitkeep', ''],
|
|
99
|
-
['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`],
|
|
100
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`],
|
|
101
122
|
['.gitignore', '[Ll]ibrary/\n[Tt]emp/\n[Oo]bj/\n[Bb]uild/\n*.csproj\n*.sln\n*.pidb\n*.userprefs\n'],
|
|
102
123
|
];
|
|
@@ -128,7 +149,7 @@ export function registerGamedevTools() {
|
|
|
128
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)],
|
|
129
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`],
|
|
130
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`],
|
|
131
|
-
['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`],
|
|
132
153
|
['tsconfig.json', JSON.stringify({ compilerOptions: { target: 'ES2020', module: 'ESNext', moduleResolution: 'bundler', strict: true, esModuleInterop: true }, include: ['src'] }, null, 2)],
|
|
133
154
|
['.gitignore', 'node_modules/\ndist/\n'],
|
|
134
155
|
];
|
|
@@ -155,10 +176,10 @@ export function registerGamedevTools() {
|
|
|
155
176
|
defold(name, tpl) {
|
|
156
177
|
const is3d = tpl === '3d';
|
|
157
178
|
return [
|
|
158
|
-
['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`],
|
|
159
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`],
|
|
160
181
|
['main/game.go', `components {\n id: "script"\n component: "/main/game.script"\n}\n`],
|
|
161
|
-
['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`],
|
|
162
183
|
['.gitignore', 'build/\n.internal/\n'],
|
|
163
184
|
];
|
|
164
185
|
},
|
|
@@ -177,7 +198,7 @@ export function registerGamedevTools() {
|
|
|
177
198
|
const engine = String(args.engine).toLowerCase();
|
|
178
199
|
const name = String(args.name);
|
|
179
200
|
const template = String(args.template || 'blank').toLowerCase();
|
|
180
|
-
const outputDir = String(args.output_dir || `./${name}`);
|
|
201
|
+
const outputDir = safePath(String(args.output_dir || `./${name}`));
|
|
181
202
|
if (!scaffoldFiles[engine]) {
|
|
182
203
|
return `Error: Unknown engine "${engine}". Supported: ${Object.keys(scaffoldFiles).join(', ')}`;
|
|
183
204
|
}
|
|
@@ -201,11 +222,11 @@ export function registerGamedevTools() {
|
|
|
201
222
|
project: (s) => {
|
|
202
223
|
const lines = ['[application]'];
|
|
203
224
|
if (s.name)
|
|
204
|
-
lines.push(`config/name="${s.name}"`);
|
|
225
|
+
lines.push(`config/name="${codeSafe(String(s.name), 'ini')}"`);
|
|
205
226
|
if (s.main_scene)
|
|
206
|
-
lines.push(`run/main_scene="${s.main_scene}"`);
|
|
227
|
+
lines.push(`run/main_scene="${codeSafe(String(s.main_scene), 'ini')}"`);
|
|
207
228
|
lines.push('', '[rendering]');
|
|
208
|
-
lines.push(`renderer/rendering_method="${s.renderer || 'forward_plus'}"`);
|
|
229
|
+
lines.push(`renderer/rendering_method="${codeSafe(String(s.renderer || 'forward_plus'), 'ini')}"`);
|
|
209
230
|
if (s.vsync !== undefined)
|
|
210
231
|
lines.push(`[display]\nwindow/vsync/vsync_mode=${s.vsync ? 1 : 0}`);
|
|
211
232
|
if (s.width)
|
|
@@ -237,14 +258,14 @@ export function registerGamedevTools() {
|
|
|
237
258
|
rendering: (s) => {
|
|
238
259
|
const lines = ['[rendering]'];
|
|
239
260
|
if (s.renderer)
|
|
240
|
-
lines.push(`renderer/rendering_method="${s.renderer}"`);
|
|
261
|
+
lines.push(`renderer/rendering_method="${codeSafe(String(s.renderer), 'ini')}"`);
|
|
241
262
|
if (s.msaa)
|
|
242
263
|
lines.push(`anti_aliasing/quality/msaa_${s.msaa_type || '3d'}=${s.msaa}`);
|
|
243
264
|
if (s.shadows !== undefined)
|
|
244
265
|
lines.push(`lights_and_shadows/directional_shadow/size=${s.shadow_size || 4096}`);
|
|
245
266
|
return lines.join('\n') + '\n';
|
|
246
267
|
},
|
|
247
|
-
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`,
|
|
248
269
|
audio: (s) => {
|
|
249
270
|
const lines = ['[audio]'];
|
|
250
271
|
if (s.bus_count)
|
|
@@ -272,7 +293,7 @@ export function registerGamedevTools() {
|
|
|
272
293
|
bevy: {
|
|
273
294
|
project: (s) => {
|
|
274
295
|
const name = String(s.name || 'game').toLowerCase().replace(/[^a-z0-9-]/g, '-');
|
|
275
|
-
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`;
|
|
276
297
|
},
|
|
277
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`,
|
|
278
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`,
|
|
@@ -284,7 +305,7 @@ export function registerGamedevTools() {
|
|
|
284
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),
|
|
285
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`,
|
|
286
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`,
|
|
287
|
-
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`,
|
|
288
309
|
input: (s) => `// Input key mapping\nexport const KEYS = ${JSON.stringify(s, null, 2)} as const\n`,
|
|
289
310
|
audio: (s) => `// Audio config\nexport const audioConfig = {\n disableWebAudio: ${s.disable_web_audio ?? false},\n noAudio: ${s.no_audio ?? false},\n}\n`,
|
|
290
311
|
},
|
|
@@ -292,7 +313,7 @@ export function registerGamedevTools() {
|
|
|
292
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),
|
|
293
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`,
|
|
294
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`,
|
|
295
|
-
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`,
|
|
296
317
|
input: (s) => `// Input mapping\nexport const INPUT_MAP = ${JSON.stringify(s, null, 2)} as const\n`,
|
|
297
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`,
|
|
298
319
|
},
|
|
@@ -305,7 +326,7 @@ export function registerGamedevTools() {
|
|
|
305
326
|
input: (s) => {
|
|
306
327
|
const lines = ['[/Script/Engine.InputSettings]'];
|
|
307
328
|
for (const [name, key] of Object.entries(s)) {
|
|
308
|
-
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})`);
|
|
309
330
|
}
|
|
310
331
|
return lines.join('\n') + '\n';
|
|
311
332
|
},
|
|
@@ -317,12 +338,12 @@ export function registerGamedevTools() {
|
|
|
317
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),
|
|
318
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`,
|
|
319
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`,
|
|
320
|
-
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`,
|
|
321
342
|
input: (s) => `// Input config\nexport const INPUT_MAP = ${JSON.stringify(s, null, 2)} as const\n`,
|
|
322
|
-
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`,
|
|
323
344
|
},
|
|
324
345
|
defold: {
|
|
325
|
-
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`,
|
|
326
347
|
physics: (s) => `[physics]\ntype = ${s.physics_type || '2D'}\ngravity_y = ${s.gravity ?? -10}\nscale = ${s.scale ?? 0.02}\n`,
|
|
327
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`,
|
|
328
349
|
build: (s) => `[native_extension]\napp_manifest = ${s.manifest || ''}\n`,
|
|
@@ -429,8 +450,11 @@ export function registerGamedevTools() {
|
|
|
429
450
|
let language = String(args.language).toLowerCase();
|
|
430
451
|
const target = String(args.target || 'desktop').toLowerCase();
|
|
431
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'];
|
|
432
454
|
if (source.length < 300 && !source.includes('\n') && existsSync(source)) {
|
|
433
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'}`;
|
|
434
458
|
source = readFileSync(source, 'utf-8');
|
|
435
459
|
if (!args.language) {
|
|
436
460
|
const extMap = { '.glsl': 'glsl', '.vert': 'glsl', '.frag': 'glsl', '.hlsl': 'hlsl', '.wgsl': 'wgsl', '.shader': 'hlsl', '.cg': 'hlsl' };
|
|
@@ -893,7 +917,7 @@ void fragment() {
|
|
|
893
917
|
async execute(args) {
|
|
894
918
|
const materialType = String(args.material_type).toLowerCase();
|
|
895
919
|
const engine = String(args.engine).toLowerCase();
|
|
896
|
-
const outputPath = args.output_path ? String(args.output_path) : null;
|
|
920
|
+
const outputPath = args.output_path ? safePath(String(args.output_path)) : null;
|
|
897
921
|
let params = {};
|
|
898
922
|
if (args.params) {
|
|
899
923
|
try {
|
|
@@ -1249,7 +1273,7 @@ void fragment() {
|
|
|
1249
1273
|
tier: 'free',
|
|
1250
1274
|
async execute(args) {
|
|
1251
1275
|
const shape = String(args.shape).toLowerCase();
|
|
1252
|
-
const outputPath = String(args.output_path);
|
|
1276
|
+
const outputPath = safePath(String(args.output_path));
|
|
1253
1277
|
let params = {};
|
|
1254
1278
|
if (args.params) {
|
|
1255
1279
|
try {
|
|
@@ -1288,8 +1312,8 @@ void fragment() {
|
|
|
1288
1312
|
timeout: 120_000,
|
|
1289
1313
|
async execute(args) {
|
|
1290
1314
|
const inputDir = String(args.input_dir);
|
|
1291
|
-
const outputImage = String(args.output_image);
|
|
1292
|
-
const outputData = String(args.output_data);
|
|
1315
|
+
const outputImage = safePath(String(args.output_image));
|
|
1316
|
+
const outputData = safePath(String(args.output_data));
|
|
1293
1317
|
const algorithm = String(args.algorithm || 'maxrects');
|
|
1294
1318
|
const maxSize = typeof args.max_size === 'number' ? args.max_size : 2048;
|
|
1295
1319
|
const padding = typeof args.padding === 'number' ? args.padding : 2;
|
|
@@ -1604,7 +1628,7 @@ void fragment() {
|
|
|
1604
1628
|
catch {
|
|
1605
1629
|
return 'Error: params must be valid JSON';
|
|
1606
1630
|
}
|
|
1607
|
-
const outputPath = String(args.output_path);
|
|
1631
|
+
const outputPath = safePath(String(args.output_path));
|
|
1608
1632
|
const validTypes = ['rigidbody', 'softbody', 'ragdoll', 'vehicle', 'cloth', 'joints'];
|
|
1609
1633
|
const validEngines = ['godot', 'unity', 'unreal', 'bevy', 'cannon', 'rapier', 'matter'];
|
|
1610
1634
|
if (!validTypes.includes(type))
|
|
@@ -2889,7 +2913,7 @@ const JOINT_CONFIG = ${JSON.stringify({
|
|
|
2889
2913
|
async execute(args) {
|
|
2890
2914
|
const effect = String(args.effect).toLowerCase();
|
|
2891
2915
|
const engine = String(args.engine || 'three').toLowerCase();
|
|
2892
|
-
const outputPath = String(args.output_path);
|
|
2916
|
+
const outputPath = safePath(String(args.output_path));
|
|
2893
2917
|
let overrides = {};
|
|
2894
2918
|
try {
|
|
2895
2919
|
overrides = args.params ? JSON.parse(String(args.params)) : {};
|
|
@@ -3604,7 +3628,7 @@ ${effect === 'fire' || effect === 'smoke' ? ` // Grow over lifetime
|
|
|
3604
3628
|
const width = Math.min(typeof args.width === 'number' ? args.width : 40, 1000);
|
|
3605
3629
|
const height = Math.min(typeof args.height === 'number' ? args.height : 30, 1000);
|
|
3606
3630
|
const seedVal = typeof args.seed === 'number' ? args.seed : Date.now();
|
|
3607
|
-
const outputPath = String(args.output_path);
|
|
3631
|
+
const outputPath = safePath(String(args.output_path));
|
|
3608
3632
|
const format = String(args.format || 'json');
|
|
3609
3633
|
let params = {};
|
|
3610
3634
|
try {
|
|
@@ -4161,7 +4185,7 @@ ${effect === 'fire' || effect === 'smoke' ? ` // Grow over lifetime
|
|
|
4161
4185
|
async execute(args) {
|
|
4162
4186
|
const tilesetType = String(args.tileset_type).toLowerCase();
|
|
4163
4187
|
const terrain = String(args.terrain).toLowerCase();
|
|
4164
|
-
const outputPath = String(args.output_path);
|
|
4188
|
+
const outputPath = safePath(String(args.output_path));
|
|
4165
4189
|
const format = String(args.format || 'json');
|
|
4166
4190
|
const validTypes = ['blob_47', 'wang_16', 'simple_4'];
|
|
4167
4191
|
const validTerrains = ['grass', 'stone', 'water', 'sand', 'snow', 'lava'];
|
|
@@ -4540,7 +4564,7 @@ tile_${i}/terrain_peering/left = ${cardW ? 0 : -1}`;
|
|
|
4540
4564
|
async execute(args) {
|
|
4541
4565
|
const engine = String(args.engine || 'recast').toLowerCase();
|
|
4542
4566
|
const agentType = String(args.agent_type || 'humanoid').toLowerCase();
|
|
4543
|
-
const outputPath = String(args.output_path);
|
|
4567
|
+
const outputPath = safePath(String(args.output_path));
|
|
4544
4568
|
let overrides = {};
|
|
4545
4569
|
try {
|
|
4546
4570
|
overrides = args.params ? JSON.parse(String(args.params)) : {};
|
|
@@ -5649,7 +5673,7 @@ export class NavigationSystem {
|
|
|
5649
5673
|
async execute(args) {
|
|
5650
5674
|
const system = String(args.system).toLowerCase();
|
|
5651
5675
|
const engine = String(args.engine || 'web').toLowerCase();
|
|
5652
|
-
const outputPath = String(args.output_path);
|
|
5676
|
+
const outputPath = safePath(String(args.output_path));
|
|
5653
5677
|
let params = {};
|
|
5654
5678
|
try {
|
|
5655
5679
|
params = args.params ? JSON.parse(String(args.params)) : {};
|
|
@@ -5657,6 +5681,9 @@ export class NavigationSystem {
|
|
|
5657
5681
|
catch {
|
|
5658
5682
|
return 'Error: params must be valid JSON';
|
|
5659
5683
|
}
|
|
5684
|
+
const validEngines = ['godot', 'unity', 'unreal', 'web', 'bevy'];
|
|
5685
|
+
if (!validEngines.includes(engine))
|
|
5686
|
+
return `Error: engine must be one of: ${validEngines.join(', ')}`;
|
|
5660
5687
|
const validSystems = ['spatial', 'music_layers', 'sound_bank', 'howler', 'web_audio'];
|
|
5661
5688
|
if (!validSystems.includes(system)) {
|
|
5662
5689
|
return `Error: Unknown audio system "${system}". Valid: ${validSystems.join(', ')}`;
|
|
@@ -7108,7 +7135,7 @@ export class AudioEngine {
|
|
|
7108
7135
|
const architecture = String(args.architecture).toLowerCase();
|
|
7109
7136
|
const transport = String(args.transport || 'websocket').toLowerCase();
|
|
7110
7137
|
const framework = String(args.framework || 'raw').toLowerCase();
|
|
7111
|
-
const outputDir = String(args.output_dir);
|
|
7138
|
+
const outputDir = safePath(String(args.output_dir));
|
|
7112
7139
|
let features = ['lobby', 'state_sync'];
|
|
7113
7140
|
try {
|
|
7114
7141
|
features = args.features ? JSON.parse(String(args.features)) : ['lobby', 'state_sync'];
|
|
@@ -7116,6 +7143,12 @@ export class AudioEngine {
|
|
|
7116
7143
|
catch {
|
|
7117
7144
|
return 'Error: features must be valid JSON';
|
|
7118
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(', ')}`;
|
|
7119
7152
|
const validArch = ['client_server', 'peer_to_peer', 'relay'];
|
|
7120
7153
|
if (!validArch.includes(architecture)) {
|
|
7121
7154
|
return `Error: Unknown architecture "${architecture}". Valid: ${validArch.join(', ')}`;
|
|
@@ -8520,7 +8553,7 @@ ${hasReconnect ? ' if (this.reconnectTimer) clearTimeout(this.reconnectTimer)
|
|
|
8520
8553
|
async execute(args) {
|
|
8521
8554
|
const engine = String(args.engine || 'web').toLowerCase();
|
|
8522
8555
|
const platforms = String(args.platforms).split(',').map(p => p.trim().toLowerCase());
|
|
8523
|
-
const outputDir = String(args.output_dir);
|
|
8556
|
+
const outputDir = safePath(String(args.output_dir));
|
|
8524
8557
|
const ci = String(args.ci || 'github_actions').toLowerCase();
|
|
8525
8558
|
const validPlatforms = ['steam', 'itch', 'web', 'ios', 'android'];
|
|
8526
8559
|
const invalid = platforms.filter(p => !validPlatforms.includes(p));
|
|
@@ -9022,7 +9055,7 @@ export default defineConfig({
|
|
|
9022
9055
|
async execute(args) {
|
|
9023
9056
|
const testType = String(args.test_type).toLowerCase();
|
|
9024
9057
|
const engine = String(args.engine || 'web').toLowerCase();
|
|
9025
|
-
const outputPath = String(args.output_path);
|
|
9058
|
+
const outputPath = safePath(String(args.output_path));
|
|
9026
9059
|
let params = {};
|
|
9027
9060
|
try {
|
|
9028
9061
|
params = args.params ? JSON.parse(String(args.params)) : {};
|
|
@@ -10336,7 +10369,7 @@ export class PerformanceBudget {
|
|
|
10336
10369
|
catch {
|
|
10337
10370
|
return 'Error: entities must be valid JSON';
|
|
10338
10371
|
}
|
|
10339
|
-
const outputDir = String(args.output_dir);
|
|
10372
|
+
const outputDir = safePath(String(args.output_dir));
|
|
10340
10373
|
let systems = [];
|
|
10341
10374
|
try {
|
|
10342
10375
|
systems = args.systems ? JSON.parse(String(args.systems)) : [];
|