@lagless/create 0.0.38 → 0.0.39

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (46) hide show
  1. package/LICENSE +26 -0
  2. package/dist/index.js +96 -16
  3. package/dist/index.js.map +1 -1
  4. package/package.json +5 -4
  5. package/templates/pixi-react/AGENTS.md +57 -27
  6. package/templates/pixi-react/CLAUDE.md +225 -49
  7. package/templates/pixi-react/README.md +16 -6
  8. package/templates/pixi-react/__packageName__-backend/package.json +1 -0
  9. package/templates/pixi-react/__packageName__-backend/src/main.ts +2 -0
  10. package/templates/pixi-react/__packageName__-frontend/package.json +8 -0
  11. package/templates/pixi-react/__packageName__-frontend/src/app/game-view/grid-background.tsx +4 -0
  12. package/templates/pixi-react/__packageName__-frontend/src/app/game-view/player-view.tsx +68 -0
  13. package/templates/pixi-react/__packageName__-frontend/src/app/game-view/runner-provider.tsx +57 -0
  14. package/templates/pixi-react/__packageName__-frontend/src/app/hooks/use-start-multiplayer-match.ts +5 -5
  15. package/templates/pixi-react/__packageName__-frontend/src/app/screens/title.screen.tsx +18 -1
  16. package/templates/pixi-react/__packageName__-simulation/package.json +7 -0
  17. package/templates/pixi-react/__packageName__-simulation/src/lib/arena.ts +12 -0
  18. package/templates/pixi-react/__packageName__-simulation/src/lib/schema/ecs.yaml +90 -6
  19. package/templates/pixi-react/__packageName__-simulation/src/lib/systems/apply-move-input.system.ts +73 -0
  20. package/templates/pixi-react/__packageName__-simulation/src/lib/systems/boundary.system.ts +2 -0
  21. package/templates/pixi-react/__packageName__-simulation/src/lib/systems/damping.system.ts +2 -0
  22. package/templates/pixi-react/__packageName__-simulation/src/lib/systems/index.ts +8 -0
  23. package/templates/pixi-react/__packageName__-simulation/src/lib/systems/integrate.system.ts +2 -0
  24. package/templates/pixi-react/__packageName__-simulation/src/lib/systems/physics-step.system.ts +65 -0
  25. package/templates/pixi-react/__packageName__-simulation/src/lib/systems/player-connection.system.ts +158 -0
  26. package/templates/pixi-react/__packageName__-simulation/src/lib/systems/player-leave.system.ts +70 -0
  27. package/templates/pixi-react/__packageName__-simulation/src/lib/systems/save-prev-transform.system.ts +46 -0
  28. package/templates/pixi-react/docs/01-schema-and-codegen.md +244 -0
  29. package/templates/pixi-react/docs/02-ecs-systems.md +293 -0
  30. package/templates/pixi-react/docs/03-determinism.md +204 -0
  31. package/templates/pixi-react/docs/04-input-system.md +255 -0
  32. package/templates/pixi-react/docs/05-signals.md +175 -0
  33. package/templates/pixi-react/docs/06-rendering.md +256 -0
  34. package/templates/pixi-react/docs/07-multiplayer.md +277 -0
  35. package/templates/pixi-react/docs/08-physics2d.md +266 -0
  36. package/templates/pixi-react/docs/08-physics3d.md +312 -0
  37. package/templates/pixi-react/docs/09-recipes.md +362 -0
  38. package/templates/pixi-react/docs/10-common-mistakes.md +224 -0
  39. package/templates/pixi-react/docs/api-quick-reference.md +254 -0
  40. package/templates/pixi-react/package.json +6 -0
  41. /package/templates/pixi-react/__packageName__-backend/{tsconfig.json → tsconfig.json.ejs} +0 -0
  42. /package/templates/pixi-react/__packageName__-frontend/{tsconfig.json → tsconfig.json.ejs} +0 -0
  43. /package/templates/pixi-react/__packageName__-frontend/{vite.config.ts → vite.config.ts.ejs} +0 -0
  44. /package/templates/pixi-react/__packageName__-simulation/{.swcrc → .swcrc.ejs} +0 -0
  45. /package/templates/pixi-react/__packageName__-simulation/{tsconfig.json → tsconfig.json.ejs} +0 -0
  46. /package/templates/pixi-react/{tsconfig.base.json → tsconfig.base.json.ejs} +0 -0
package/LICENSE ADDED
@@ -0,0 +1,26 @@
1
+ Creative Commons Attribution-NonCommercial 4.0 International
2
+
3
+ Copyright (c) 2025 Lagless
4
+
5
+ This work is licensed under the Creative Commons
6
+ Attribution-NonCommercial 4.0 International License.
7
+
8
+ You are free to:
9
+
10
+ Share — copy and redistribute the material in any medium or format
11
+ Adapt — remix, transform, and build upon the material
12
+
13
+ Under the following terms:
14
+
15
+ Attribution — You must give appropriate credit, provide a link to
16
+ the license, and indicate if changes were made. You may do so in
17
+ any reasonable manner, but not in any way that suggests the licensor
18
+ endorses you or your use.
19
+
20
+ NonCommercial — You may not use the material for commercial purposes.
21
+
22
+ No additional restrictions — You may not apply legal terms or
23
+ technological measures that legally restrict others from doing
24
+ anything the license permits.
25
+
26
+ Full license text: https://creativecommons.org/licenses/by-nc/4.0/legalcode
package/dist/index.js CHANGED
@@ -3,18 +3,17 @@ import { program } from 'commander';
3
3
  import * as fs from 'node:fs';
4
4
  import * as path from 'node:path';
5
5
  import * as ejs from 'ejs';
6
+ import { execSync } from 'node:child_process';
6
7
  import { fileURLToPath } from 'node:url';
8
+ import { select } from '@inquirer/prompts';
7
9
  const __filename = fileURLToPath(import.meta.url);
8
10
  const __dirname = path.dirname(__filename);
9
11
  function toPascalCase(kebab) {
10
12
  return kebab.split('-').map((s)=>s.charAt(0).toUpperCase() + s.slice(1)).join('');
11
13
  }
12
14
  function getTemplatesDir() {
13
- // In development (source), templates are sibling to src/
14
- // In dist, templates are at package root (copied by files field)
15
15
  const devPath = path.resolve(__dirname, '..', 'templates');
16
16
  if (fs.existsSync(devPath)) return devPath;
17
- // Fallback for npm install
18
17
  const distPath = path.resolve(__dirname, '..', '..', 'templates');
19
18
  if (fs.existsSync(distPath)) return distPath;
20
19
  throw new Error('Templates directory not found');
@@ -39,7 +38,29 @@ function processPath(filePath, vars) {
39
38
  result = result.replace(/__ProjectName__/g, vars.projectName);
40
39
  return result;
41
40
  }
42
- program.name('create-lagless').description('Scaffold a new Lagless multiplayer game project').version('0.0.30').argument('<project-name>', 'Project name in kebab-case (e.g., my-game)').option('--preset <preset>', 'Project preset', 'pixi-react').option('--port <port>', 'Frontend dev server port', '4200').option('--server-port <port>', 'Backend server port', '3333').action(async (projectArg, options)=>{
41
+ async function promptSimulationType() {
42
+ return select({
43
+ message: 'Select simulation type:',
44
+ choices: [
45
+ {
46
+ value: 'raw',
47
+ name: 'Raw ECS',
48
+ description: 'Manual velocity/position management, no physics engine'
49
+ },
50
+ {
51
+ value: 'physics2d',
52
+ name: 'Physics 2D (Rapier)',
53
+ description: 'Rapier 2D rigid body physics with auto-managed transforms'
54
+ },
55
+ {
56
+ value: 'physics3d',
57
+ name: 'Physics 3D (Rapier)',
58
+ description: 'Rapier 3D rigid body physics with top-down 2D rendering'
59
+ }
60
+ ]
61
+ });
62
+ }
63
+ program.name('create-lagless').description('Scaffold a new Lagless multiplayer game project').version('0.0.38').argument('<project-name>', 'Project name in kebab-case (e.g., my-game)').option('--preset <preset>', 'Project preset', 'pixi-react').option('--port <port>', 'Frontend dev server port', '4200').option('--server-port <port>', 'Backend server port', '3333').option('--simulation-type <type>', 'Simulation type: raw, physics2d, or physics3d').action(async (projectArg, options)=>{
43
64
  const targetDir = path.resolve(process.cwd(), projectArg);
44
65
  const packageName = path.basename(targetDir).toLowerCase();
45
66
  const pascalName = toPascalCase(packageName);
@@ -53,9 +74,25 @@ program.name('create-lagless').description('Scaffold a new Lagless multiplayer g
53
74
  console.error(`Error: Preset "${options.preset}" not found. Available: ${fs.readdirSync(templatesDir).join(', ')}`);
54
75
  process.exit(1);
55
76
  }
77
+ // Determine simulation type — from CLI flag or interactive prompt
78
+ let simulationType;
79
+ if (options.simulationType) {
80
+ const valid = [
81
+ 'raw',
82
+ 'physics2d',
83
+ 'physics3d'
84
+ ];
85
+ if (!valid.includes(options.simulationType)) {
86
+ console.error(`Error: Invalid simulation type "${options.simulationType}". Valid: ${valid.join(', ')}`);
87
+ process.exit(1);
88
+ }
89
+ simulationType = options.simulationType;
90
+ } else {
91
+ simulationType = await promptSimulationType();
92
+ }
56
93
  // Read package.json from this package to get current lagless version
57
94
  const pkgJsonPath = path.resolve(__dirname, '..', 'package.json');
58
- let laglessVersion = '0.0.30';
95
+ let laglessVersion = '0.0.38';
59
96
  try {
60
97
  const pkg = JSON.parse(fs.readFileSync(pkgJsonPath, 'utf-8'));
61
98
  laglessVersion = pkg.version || laglessVersion;
@@ -67,10 +104,12 @@ program.name('create-lagless').description('Scaffold a new Lagless multiplayer g
67
104
  packageName,
68
105
  frontendPort: options.port,
69
106
  serverPort: options.serverPort,
70
- laglessVersion
107
+ laglessVersion,
108
+ simulationType
71
109
  };
72
110
  console.log(`\nCreating Lagless project "${packageName}"...`);
73
111
  console.log(` Preset: ${options.preset}`);
112
+ console.log(` Simulation: ${simulationType}`);
74
113
  console.log(` Frontend port: ${options.port}`);
75
114
  console.log(` Server port: ${options.serverPort}`);
76
115
  console.log(` Target: ${targetDir}\n`);
@@ -80,13 +119,8 @@ program.name('create-lagless').description('Scaffold a new Lagless multiplayer g
80
119
  const outputRelative = processPath(relativePath, vars);
81
120
  const outputPath = path.join(targetDir, outputRelative);
82
121
  const outputDir = path.dirname(outputPath);
83
- if (!fs.existsSync(outputDir)) {
84
- fs.mkdirSync(outputDir, {
85
- recursive: true
86
- });
87
- }
88
122
  const content = fs.readFileSync(templateFile, 'utf-8');
89
- // Only process .ejs files or text files that might contain EJS tags
123
+ // Only process text files that might contain EJS tags
90
124
  const ext = path.extname(templateFile);
91
125
  const textExts = [
92
126
  '.ts',
@@ -105,19 +139,65 @@ program.name('create-lagless').description('Scaffold a new Lagless multiplayer g
105
139
  filename: templateFile
106
140
  });
107
141
  const finalPath = ext === '.ejs' ? outputPath.replace(/\.ejs$/, '') : outputPath;
142
+ // Skip writing empty/whitespace-only files (conditional template exclusion)
143
+ if (rendered.trim().length === 0) {
144
+ continue;
145
+ }
146
+ if (!fs.existsSync(outputDir)) {
147
+ fs.mkdirSync(outputDir, {
148
+ recursive: true
149
+ });
150
+ }
108
151
  fs.writeFileSync(finalPath, rendered, 'utf-8');
109
152
  } else {
110
153
  // Binary or unknown — copy as-is
154
+ if (!fs.existsSync(outputDir)) {
155
+ fs.mkdirSync(outputDir, {
156
+ recursive: true
157
+ });
158
+ }
111
159
  fs.copyFileSync(templateFile, outputPath);
112
160
  }
113
161
  }
114
- console.log('Project created successfully!\n');
162
+ // Remove physics docs that don't match the selected simulationType
163
+ const docsDir = path.join(targetDir, 'docs');
164
+ if (simulationType !== 'physics2d') {
165
+ const f = path.join(docsDir, '08-physics2d.md');
166
+ if (fs.existsSync(f)) fs.rmSync(f);
167
+ }
168
+ if (simulationType !== 'physics3d') {
169
+ const f = path.join(docsDir, '08-physics3d.md');
170
+ if (fs.existsSync(f)) fs.rmSync(f);
171
+ }
172
+ // Clone lagless framework source for AI reference
173
+ const sourcesDir = path.join(docsDir, 'sources');
174
+ fs.mkdirSync(sourcesDir, {
175
+ recursive: true
176
+ });
177
+ console.log('Cloning lagless framework source for AI reference...');
178
+ try {
179
+ execSync(`git clone --depth 1 https://github.com/GbGr/lagless.git "${path.join(sourcesDir, 'lagless')}"`, {
180
+ stdio: 'inherit'
181
+ });
182
+ // Remove .git to save space
183
+ fs.rmSync(path.join(sourcesDir, 'lagless', '.git'), {
184
+ recursive: true,
185
+ force: true
186
+ });
187
+ } catch (e) {
188
+ console.warn('Warning: Could not clone lagless source. AI reference will be unavailable.');
189
+ console.warn('You can manually clone later: git clone --depth 1 https://github.com/GbGr/lagless.git docs/sources/lagless');
190
+ }
191
+ console.log('\nProject created successfully!\n');
115
192
  console.log('Next steps:');
116
193
  console.log(` cd ${packageName}`);
117
194
  console.log(' pnpm install');
118
- console.log(' pnpm codegen # Generate ECS code from schema');
119
- console.log(' pnpm dev:backend # Start game server');
120
- console.log(' pnpm dev:frontend # Start frontend dev server\n');
195
+ console.log(' pnpm codegen # Generate ECS code from schema');
196
+ console.log(' pnpm dev # Start backend + frontend + dev-player\n');
197
+ console.log('Or run individually:');
198
+ console.log(' pnpm dev:backend # Game server (Bun, watches for changes)');
199
+ console.log(' pnpm dev:frontend # Frontend (Vite HMR)');
200
+ console.log(' pnpm dev:player # Dev-player (multiplayer testing, port 4210)\n');
121
201
  });
122
202
  program.parse();
123
203
 
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/index.ts"],"sourcesContent":["#!/usr/bin/env node\nimport { program } from 'commander';\nimport * as fs from 'node:fs';\nimport * as path from 'node:path';\nimport * as ejs from 'ejs';\nimport { fileURLToPath } from 'node:url';\n\nconst __filename = fileURLToPath(import.meta.url);\nconst __dirname = path.dirname(__filename);\n\ninterface CreateOptions {\n preset: string;\n port: string;\n serverPort: string;\n}\n\nfunction toPascalCase(kebab: string): string {\n return kebab.split('-').map(s => s.charAt(0).toUpperCase() + s.slice(1)).join('');\n}\n\nfunction getTemplatesDir(): string {\n // In development (source), templates are sibling to src/\n // In dist, templates are at package root (copied by files field)\n const devPath = path.resolve(__dirname, '..', 'templates');\n if (fs.existsSync(devPath)) return devPath;\n // Fallback for npm install\n const distPath = path.resolve(__dirname, '..', '..', 'templates');\n if (fs.existsSync(distPath)) return distPath;\n throw new Error('Templates directory not found');\n}\n\nfunction walkDir(dir: string): string[] {\n const results: string[] = [];\n for (const entry of fs.readdirSync(dir, { withFileTypes: true })) {\n const full = path.join(dir, entry.name);\n if (entry.isDirectory()) {\n results.push(...walkDir(full));\n } else {\n results.push(full);\n }\n }\n return results;\n}\n\nfunction processPath(filePath: string, vars: Record<string, string>): string {\n let result = filePath;\n result = result.replace(/__packageName__/g, vars.packageName);\n result = result.replace(/__ProjectName__/g, vars.projectName);\n return result;\n}\n\nprogram\n .name('create-lagless')\n .description('Scaffold a new Lagless multiplayer game project')\n .version('0.0.30')\n .argument('<project-name>', 'Project name in kebab-case (e.g., my-game)')\n .option('--preset <preset>', 'Project preset', 'pixi-react')\n .option('--port <port>', 'Frontend dev server port', '4200')\n .option('--server-port <port>', 'Backend server port', '3333')\n .action(async (projectArg: string, options: CreateOptions) => {\n const targetDir = path.resolve(process.cwd(), projectArg);\n const packageName = path.basename(targetDir).toLowerCase();\n const pascalName = toPascalCase(packageName);\n\n if (fs.existsSync(targetDir)) {\n console.error(`Error: Directory \"${targetDir}\" already exists.`);\n process.exit(1);\n }\n\n const templatesDir = getTemplatesDir();\n const presetDir = path.join(templatesDir, options.preset);\n\n if (!fs.existsSync(presetDir)) {\n console.error(`Error: Preset \"${options.preset}\" not found. Available: ${fs.readdirSync(templatesDir).join(', ')}`);\n process.exit(1);\n }\n\n // Read package.json from this package to get current lagless version\n const pkgJsonPath = path.resolve(__dirname, '..', 'package.json');\n let laglessVersion = '0.0.30';\n try {\n const pkg = JSON.parse(fs.readFileSync(pkgJsonPath, 'utf-8'));\n laglessVersion = pkg.version || laglessVersion;\n } catch {\n // fallback\n }\n\n const vars = {\n projectName: pascalName,\n packageName,\n frontendPort: options.port,\n serverPort: options.serverPort,\n laglessVersion,\n };\n\n console.log(`\\nCreating Lagless project \"${packageName}\"...`);\n console.log(` Preset: ${options.preset}`);\n console.log(` Frontend port: ${options.port}`);\n console.log(` Server port: ${options.serverPort}`);\n console.log(` Target: ${targetDir}\\n`);\n\n const templateFiles = walkDir(presetDir);\n\n for (const templateFile of templateFiles) {\n const relativePath = path.relative(presetDir, templateFile);\n const outputRelative = processPath(relativePath, vars);\n const outputPath = path.join(targetDir, outputRelative);\n const outputDir = path.dirname(outputPath);\n\n if (!fs.existsSync(outputDir)) {\n fs.mkdirSync(outputDir, { recursive: true });\n }\n\n const content = fs.readFileSync(templateFile, 'utf-8');\n\n // Only process .ejs files or text files that might contain EJS tags\n const ext = path.extname(templateFile);\n const textExts = ['.ts', '.tsx', '.json', '.yaml', '.yml', '.html', '.css', '.md', '.toml', '.gitignore'];\n\n if (textExts.includes(ext) || ext === '.ejs') {\n const rendered = ejs.render(content, vars, { filename: templateFile });\n const finalPath = ext === '.ejs' ? outputPath.replace(/\\.ejs$/, '') : outputPath;\n fs.writeFileSync(finalPath, rendered, 'utf-8');\n } else {\n // Binary or unknown — copy as-is\n fs.copyFileSync(templateFile, outputPath);\n }\n }\n\n console.log('Project created successfully!\\n');\n console.log('Next steps:');\n console.log(` cd ${packageName}`);\n console.log(' pnpm install');\n console.log(' pnpm codegen # Generate ECS code from schema');\n console.log(' pnpm dev:backend # Start game server');\n console.log(' pnpm dev:frontend # Start frontend dev server\\n');\n });\n\nprogram.parse();\n"],"names":["program","fs","path","ejs","fileURLToPath","__filename","url","__dirname","dirname","toPascalCase","kebab","split","map","s","charAt","toUpperCase","slice","join","getTemplatesDir","devPath","resolve","existsSync","distPath","Error","walkDir","dir","results","entry","readdirSync","withFileTypes","full","name","isDirectory","push","processPath","filePath","vars","result","replace","packageName","projectName","description","version","argument","option","action","projectArg","options","targetDir","process","cwd","basename","toLowerCase","pascalName","console","error","exit","templatesDir","presetDir","preset","pkgJsonPath","laglessVersion","pkg","JSON","parse","readFileSync","frontendPort","port","serverPort","log","templateFiles","templateFile","relativePath","relative","outputRelative","outputPath","outputDir","mkdirSync","recursive","content","ext","extname","textExts","includes","rendered","render","filename","finalPath","writeFileSync","copyFileSync"],"rangeMappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;","mappings":"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"}
1
+ {"version":3,"sources":["../src/index.ts"],"sourcesContent":["#!/usr/bin/env node\nimport { program } from 'commander';\nimport * as fs from 'node:fs';\nimport * as path from 'node:path';\nimport * as ejs from 'ejs';\nimport { execSync } from 'node:child_process';\nimport { fileURLToPath } from 'node:url';\nimport { select } from '@inquirer/prompts';\n\nconst __filename = fileURLToPath(import.meta.url);\nconst __dirname = path.dirname(__filename);\n\ntype SimulationType = 'raw' | 'physics2d' | 'physics3d';\n\ninterface CreateOptions {\n preset: string;\n port: string;\n serverPort: string;\n simulationType?: SimulationType;\n}\n\nfunction toPascalCase(kebab: string): string {\n return kebab.split('-').map(s => s.charAt(0).toUpperCase() + s.slice(1)).join('');\n}\n\nfunction getTemplatesDir(): string {\n const devPath = path.resolve(__dirname, '..', 'templates');\n if (fs.existsSync(devPath)) return devPath;\n const distPath = path.resolve(__dirname, '..', '..', 'templates');\n if (fs.existsSync(distPath)) return distPath;\n throw new Error('Templates directory not found');\n}\n\nfunction walkDir(dir: string): string[] {\n const results: string[] = [];\n for (const entry of fs.readdirSync(dir, { withFileTypes: true })) {\n const full = path.join(dir, entry.name);\n if (entry.isDirectory()) {\n results.push(...walkDir(full));\n } else {\n results.push(full);\n }\n }\n return results;\n}\n\nfunction processPath(filePath: string, vars: Record<string, string>): string {\n let result = filePath;\n result = result.replace(/__packageName__/g, vars.packageName);\n result = result.replace(/__ProjectName__/g, vars.projectName);\n return result;\n}\n\nasync function promptSimulationType(): Promise<SimulationType> {\n return select<SimulationType>({\n message: 'Select simulation type:',\n choices: [\n { value: 'raw', name: 'Raw ECS', description: 'Manual velocity/position management, no physics engine' },\n { value: 'physics2d', name: 'Physics 2D (Rapier)', description: 'Rapier 2D rigid body physics with auto-managed transforms' },\n { value: 'physics3d', name: 'Physics 3D (Rapier)', description: 'Rapier 3D rigid body physics with top-down 2D rendering' },\n ],\n });\n}\n\nprogram\n .name('create-lagless')\n .description('Scaffold a new Lagless multiplayer game project')\n .version('0.0.38')\n .argument('<project-name>', 'Project name in kebab-case (e.g., my-game)')\n .option('--preset <preset>', 'Project preset', 'pixi-react')\n .option('--port <port>', 'Frontend dev server port', '4200')\n .option('--server-port <port>', 'Backend server port', '3333')\n .option('--simulation-type <type>', 'Simulation type: raw, physics2d, or physics3d')\n .action(async (projectArg: string, options: CreateOptions) => {\n const targetDir = path.resolve(process.cwd(), projectArg);\n const packageName = path.basename(targetDir).toLowerCase();\n const pascalName = toPascalCase(packageName);\n\n if (fs.existsSync(targetDir)) {\n console.error(`Error: Directory \"${targetDir}\" already exists.`);\n process.exit(1);\n }\n\n const templatesDir = getTemplatesDir();\n const presetDir = path.join(templatesDir, options.preset);\n\n if (!fs.existsSync(presetDir)) {\n console.error(`Error: Preset \"${options.preset}\" not found. Available: ${fs.readdirSync(templatesDir).join(', ')}`);\n process.exit(1);\n }\n\n // Determine simulation type — from CLI flag or interactive prompt\n let simulationType: SimulationType;\n if (options.simulationType) {\n const valid: SimulationType[] = ['raw', 'physics2d', 'physics3d'];\n if (!valid.includes(options.simulationType as SimulationType)) {\n console.error(`Error: Invalid simulation type \"${options.simulationType}\". Valid: ${valid.join(', ')}`);\n process.exit(1);\n }\n simulationType = options.simulationType as SimulationType;\n } else {\n simulationType = await promptSimulationType();\n }\n\n // Read package.json from this package to get current lagless version\n const pkgJsonPath = path.resolve(__dirname, '..', 'package.json');\n let laglessVersion = '0.0.38';\n try {\n const pkg = JSON.parse(fs.readFileSync(pkgJsonPath, 'utf-8'));\n laglessVersion = pkg.version || laglessVersion;\n } catch {\n // fallback\n }\n\n const vars: Record<string, string> = {\n projectName: pascalName,\n packageName,\n frontendPort: options.port,\n serverPort: options.serverPort,\n laglessVersion,\n simulationType,\n };\n\n console.log(`\\nCreating Lagless project \"${packageName}\"...`);\n console.log(` Preset: ${options.preset}`);\n console.log(` Simulation: ${simulationType}`);\n console.log(` Frontend port: ${options.port}`);\n console.log(` Server port: ${options.serverPort}`);\n console.log(` Target: ${targetDir}\\n`);\n\n const templateFiles = walkDir(presetDir);\n\n for (const templateFile of templateFiles) {\n const relativePath = path.relative(presetDir, templateFile);\n const outputRelative = processPath(relativePath, vars);\n const outputPath = path.join(targetDir, outputRelative);\n const outputDir = path.dirname(outputPath);\n\n const content = fs.readFileSync(templateFile, 'utf-8');\n\n // Only process text files that might contain EJS tags\n const ext = path.extname(templateFile);\n const textExts = ['.ts', '.tsx', '.json', '.yaml', '.yml', '.html', '.css', '.md', '.toml', '.gitignore'];\n\n if (textExts.includes(ext) || ext === '.ejs') {\n const rendered = ejs.render(content, vars, { filename: templateFile });\n const finalPath = ext === '.ejs' ? outputPath.replace(/\\.ejs$/, '') : outputPath;\n\n // Skip writing empty/whitespace-only files (conditional template exclusion)\n if (rendered.trim().length === 0) {\n continue;\n }\n\n if (!fs.existsSync(outputDir)) {\n fs.mkdirSync(outputDir, { recursive: true });\n }\n fs.writeFileSync(finalPath, rendered, 'utf-8');\n } else {\n // Binary or unknown — copy as-is\n if (!fs.existsSync(outputDir)) {\n fs.mkdirSync(outputDir, { recursive: true });\n }\n fs.copyFileSync(templateFile, outputPath);\n }\n }\n\n // Remove physics docs that don't match the selected simulationType\n const docsDir = path.join(targetDir, 'docs');\n if (simulationType !== 'physics2d') {\n const f = path.join(docsDir, '08-physics2d.md');\n if (fs.existsSync(f)) fs.rmSync(f);\n }\n if (simulationType !== 'physics3d') {\n const f = path.join(docsDir, '08-physics3d.md');\n if (fs.existsSync(f)) fs.rmSync(f);\n }\n\n // Clone lagless framework source for AI reference\n const sourcesDir = path.join(docsDir, 'sources');\n fs.mkdirSync(sourcesDir, { recursive: true });\n console.log('Cloning lagless framework source for AI reference...');\n try {\n execSync(`git clone --depth 1 https://github.com/GbGr/lagless.git \"${path.join(sourcesDir, 'lagless')}\"`, {\n stdio: 'inherit',\n });\n // Remove .git to save space\n fs.rmSync(path.join(sourcesDir, 'lagless', '.git'), { recursive: true, force: true });\n } catch {\n console.warn('Warning: Could not clone lagless source. AI reference will be unavailable.');\n console.warn('You can manually clone later: git clone --depth 1 https://github.com/GbGr/lagless.git docs/sources/lagless');\n }\n\n console.log('\\nProject created successfully!\\n');\n console.log('Next steps:');\n console.log(` cd ${packageName}`);\n console.log(' pnpm install');\n console.log(' pnpm codegen # Generate ECS code from schema');\n console.log(' pnpm dev # Start backend + frontend + dev-player\\n');\n console.log('Or run individually:');\n console.log(' pnpm dev:backend # Game server (Bun, watches for changes)');\n console.log(' pnpm dev:frontend # Frontend (Vite HMR)');\n console.log(' pnpm dev:player # Dev-player (multiplayer testing, port 4210)\\n');\n });\n\nprogram.parse();\n"],"names":["program","fs","path","ejs","execSync","fileURLToPath","select","__filename","url","__dirname","dirname","toPascalCase","kebab","split","map","s","charAt","toUpperCase","slice","join","getTemplatesDir","devPath","resolve","existsSync","distPath","Error","walkDir","dir","results","entry","readdirSync","withFileTypes","full","name","isDirectory","push","processPath","filePath","vars","result","replace","packageName","projectName","promptSimulationType","message","choices","value","description","version","argument","option","action","projectArg","options","targetDir","process","cwd","basename","toLowerCase","pascalName","console","error","exit","templatesDir","presetDir","preset","simulationType","valid","includes","pkgJsonPath","laglessVersion","pkg","JSON","parse","readFileSync","frontendPort","port","serverPort","log","templateFiles","templateFile","relativePath","relative","outputRelative","outputPath","outputDir","content","ext","extname","textExts","rendered","render","filename","finalPath","trim","length","mkdirSync","recursive","writeFileSync","copyFileSync","docsDir","f","rmSync","sourcesDir","stdio","force","warn"],"rangeMappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;","mappings":"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"}
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@lagless/create",
3
- "version": "0.0.38",
4
- "license": "MIT",
3
+ "version": "0.0.39",
4
+ "license": "CC-BY-NC-4.0",
5
5
  "repository": {
6
6
  "type": "git",
7
7
  "url": "https://github.com/GbGr/lagless",
@@ -45,9 +45,10 @@
45
45
  }
46
46
  },
47
47
  "dependencies": {
48
+ "@inquirer/prompts": "^8.3.0",
49
+ "@swc/helpers": "~0.5.11",
48
50
  "commander": "^14.0.1",
49
- "ejs": "^3.1.10",
50
- "@swc/helpers": "~0.5.11"
51
+ "ejs": "^3.1.10"
51
52
  },
52
53
  "devDependencies": {
53
54
  "@types/ejs": "^3.1.5"
@@ -1,41 +1,71 @@
1
- # AGENTS.md — Multi-Agent Development Guide
1
+ # AGENTS.md — Multi-Agent Guide for <%= projectName %>
2
2
 
3
3
  ## Project Overview
4
4
 
5
- This is a Lagless multiplayer game with three workspace packages:
6
- - `<%= packageName %>-simulation`deterministic ECS logic
7
- - `<%= packageName %>-frontend` — React + Pixi.js client
8
- - `<%= packageName %>-backend` — Bun relay server
5
+ This is a Lagless multiplayer game with three packages:
6
+ - **<%= packageName %>-simulation**Deterministic ECS game logic (shared between all clients)
7
+ - **<%= packageName %>-frontend** — React + Pixi.js rendering client
8
+ - **<%= packageName %>-backend** — Bun relay server (no simulation)
9
9
 
10
10
  ## Task Decomposition
11
11
 
12
12
  ### Adding a New Game Feature
13
-
14
- 1. **Schema**: Add components/inputs/filters to `ecs.yaml`, run `pnpm codegen`
15
- 2. **Systems**: Implement game logic as ECS systems in the simulation package
16
- 3. **Rendering**: Add Pixi.js views in the frontend using `filterView` + `FilterViews`
17
- 4. **Server hooks**: Update `game-hooks.ts` if the feature needs server-side events
13
+ 1. **Schema & Codegen** — Edit `ecs.yaml`, run `pnpm codegen`
14
+ 2. **System** Write `@ECSSystem()` in simulation, add to systems array
15
+ 3. **View** Add Pixi.js component in frontend `game-view/`
16
+ 4. **Input** Wire drainInputs in `runner-provider.tsx` if feature needs player input
17
+ 5. **Test** Verify determinism: open two tabs, play, check F3 hash table matches
18
18
 
19
19
  ### Debugging Determinism Issues
20
+ 1. **Reproduce** — Open dev-player (`pnpm dev:player`), run 2+ instances
21
+ 2. **Identify tick** — F3 debug panel shows hash divergence tick
22
+ 3. **Binary search** — Add hash checks between systems to narrow down which system diverges
23
+ 4. **Check rules** — Review `docs/03-determinism.md` for common violations
24
+ 5. **Common causes** — `Math.sin` instead of `MathOps.sin`, unsorted iteration, missing `prevPosition` init
25
+
26
+ ### Adding Multiplayer Features
27
+ 1. **Server events** — Add to `RoomHooks` in `game-hooks.ts`
28
+ 2. **Server RPCs** — Use `ctx.emitServerEvent()` for server-originated inputs
29
+ 3. **State transfer** — Handled automatically, but test with late-join scenario
30
+ 4. **Reconnect** — Test disconnect/reconnect via F3 debug panel buttons
31
+
32
+ ## File Ownership by Task Type
33
+
34
+ | Task | Primary Files |
35
+ |------|--------------|
36
+ | New component/input | `ecs.yaml` → `pnpm codegen` |
37
+ | Game logic | `*-simulation/src/lib/systems/*.system.ts` |
38
+ | Entity rendering | `*-frontend/src/app/game-view/*.tsx` |
39
+ | UI/screens | `*-frontend/src/app/screens/*.tsx` |
40
+ | Player input | `*-frontend/src/app/game-view/runner-provider.tsx` |
41
+ | Server hooks | `*-backend/src/game-hooks.ts` |
42
+ | Server config | `*-backend/src/main.ts` |
43
+ | Signals | `*-simulation/src/lib/signals/index.ts` |
20
44
 
21
- - Open two browser tabs, both "Play Online"
22
- - Press F3 to show debug panel — check hash verification table
23
- - If hashes diverge: the system running between the two reported hashes has non-deterministic code
24
- - Common causes: using `Math.random()`, `Date.now()`, `Math.sin()` instead of `MathOps`, uninitialized memory
45
+ ## Verification Checklist
25
46
 
26
- ### Testing Strategy
47
+ ### Before Submitting Any Change
48
+ - [ ] `pnpm codegen` runs without errors (if schema changed)
49
+ - [ ] Game starts: `pnpm dev` → no console errors
50
+ - [ ] Single-player works: title screen → game → entities move correctly
51
+ - [ ] Multiplayer works: two browser tabs, both see each other
52
+ - [ ] Determinism holds: F3 panel → hash table → no red (divergence) entries
27
53
 
28
- - **Simulation tests**: Write vitest tests that create a runner, inject RPCs, advance ticks, assert state
29
- - **Determinism tests**: Run same inputs twice, compare final ArrayBuffer hashes
30
- - **Visual tests**: Use local play to verify rendering
54
+ ### For Physics Changes (<%= simulationType !== 'raw' ? 'APPLIES TO THIS PROJECT' : 'if applicable' %>)
55
+ - [ ] Bodies created with correct BodyType
56
+ - [ ] `updateSceneQueries()` called after snapshot restore
57
+ - [ ] ColliderEntityMap rebuilt after state transfer
58
+ - [ ] Collision layers configured correctly
31
59
 
32
- ## Key Files
60
+ ## Key Constraints
61
+ - **Never edit `code-gen/` files** — always edit `ecs.yaml` and run `pnpm codegen`
62
+ - **Never use `Math.sin/cos/atan2/sqrt`** — use `MathOps.*` equivalents
63
+ - **Never use `Math.random()`** — use `PRNG.getFloat()` or `PRNG.getRandomInt()`
64
+ - **Always sanitize RPC inputs** — `Number.isFinite()` before `MathOps.clamp()`
65
+ - **Always set prevPosition = position** when spawning entities with Transform2d
66
+ - **Systems array order = execution order** — it's deterministic and matters
33
67
 
34
- | File | Purpose |
35
- |------|---------|
36
- | `*-simulation/src/lib/schema/ecs.yaml` | ECS data model definition |
37
- | `*-simulation/src/lib/systems/index.ts` | System execution order |
38
- | `*-simulation/src/lib/arena.ts` | Game constants |
39
- | `*-frontend/src/app/game-view/runner-provider.tsx` | Simulation initialization + input draining |
40
- | `*-frontend/src/app/game-view/game-scene.tsx` | Main Pixi.js scene composition |
41
- | `*-backend/src/game-hooks.ts` | Server room lifecycle hooks |
68
+ ## Documentation Reference
69
+ - `CLAUDE.md` — Primary instruction file with code patterns
70
+ - `docs/` Detailed documentation on all framework topics
71
+ - `docs/sources/lagless/` Full framework source code for deep reference