@lagless/create 0.0.33

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 (67) hide show
  1. package/dist/index.d.ts +3 -0
  2. package/dist/index.d.ts.map +1 -0
  3. package/dist/index.js +123 -0
  4. package/dist/index.js.map +1 -0
  5. package/dist/tsconfig.lib.tsbuildinfo +1 -0
  6. package/package.json +59 -0
  7. package/templates/.nxignore +1 -0
  8. package/templates/pixi-react/AGENTS.md +41 -0
  9. package/templates/pixi-react/CLAUDE.md +79 -0
  10. package/templates/pixi-react/README.md +151 -0
  11. package/templates/pixi-react/__packageName__-backend/bunfig.toml +4 -0
  12. package/templates/pixi-react/__packageName__-backend/package.json +19 -0
  13. package/templates/pixi-react/__packageName__-backend/src/game-hooks.ts +47 -0
  14. package/templates/pixi-react/__packageName__-backend/src/main.ts +28 -0
  15. package/templates/pixi-react/__packageName__-backend/tsconfig.json +19 -0
  16. package/templates/pixi-react/__packageName__-frontend/index.html +23 -0
  17. package/templates/pixi-react/__packageName__-frontend/package.json +33 -0
  18. package/templates/pixi-react/__packageName__-frontend/src/app/app.tsx +7 -0
  19. package/templates/pixi-react/__packageName__-frontend/src/app/components/debug-panel.tsx +18 -0
  20. package/templates/pixi-react/__packageName__-frontend/src/app/game-view/game-scene.tsx +14 -0
  21. package/templates/pixi-react/__packageName__-frontend/src/app/game-view/game-view.tsx +46 -0
  22. package/templates/pixi-react/__packageName__-frontend/src/app/game-view/grid-background.tsx +33 -0
  23. package/templates/pixi-react/__packageName__-frontend/src/app/game-view/player-view.tsx +59 -0
  24. package/templates/pixi-react/__packageName__-frontend/src/app/game-view/runner-provider.tsx +180 -0
  25. package/templates/pixi-react/__packageName__-frontend/src/app/hooks/use-start-match.ts +53 -0
  26. package/templates/pixi-react/__packageName__-frontend/src/app/hooks/use-start-multiplayer-match.ts +138 -0
  27. package/templates/pixi-react/__packageName__-frontend/src/app/router.tsx +25 -0
  28. package/templates/pixi-react/__packageName__-frontend/src/app/screens/game.screen.tsx +6 -0
  29. package/templates/pixi-react/__packageName__-frontend/src/app/screens/title.screen.tsx +104 -0
  30. package/templates/pixi-react/__packageName__-frontend/src/main.tsx +7 -0
  31. package/templates/pixi-react/__packageName__-frontend/src/styles.css +24 -0
  32. package/templates/pixi-react/__packageName__-frontend/tsconfig.json +22 -0
  33. package/templates/pixi-react/__packageName__-frontend/vite.config.ts +37 -0
  34. package/templates/pixi-react/__packageName__-simulation/.swcrc +27 -0
  35. package/templates/pixi-react/__packageName__-simulation/package.json +23 -0
  36. package/templates/pixi-react/__packageName__-simulation/src/index.ts +4 -0
  37. package/templates/pixi-react/__packageName__-simulation/src/lib/arena.ts +8 -0
  38. package/templates/pixi-react/__packageName__-simulation/src/lib/schema/code-gen/GameState.ts +29 -0
  39. package/templates/pixi-react/__packageName__-simulation/src/lib/schema/code-gen/MoveInput.ts +23 -0
  40. package/templates/pixi-react/__packageName__-simulation/src/lib/schema/code-gen/MovingFilter.ts +15 -0
  41. package/templates/pixi-react/__packageName__-simulation/src/lib/schema/code-gen/PlayerBody.ts +55 -0
  42. package/templates/pixi-react/__packageName__-simulation/src/lib/schema/code-gen/PlayerFilter.ts +15 -0
  43. package/templates/pixi-react/__packageName__-simulation/src/lib/schema/code-gen/PlayerJoined.ts +24 -0
  44. package/templates/pixi-react/__packageName__-simulation/src/lib/schema/code-gen/PlayerLeft.ts +23 -0
  45. package/templates/pixi-react/__packageName__-simulation/src/lib/schema/code-gen/PlayerResource.ts +83 -0
  46. package/templates/pixi-react/__packageName__-simulation/src/lib/schema/code-gen/ReportHash.ts +23 -0
  47. package/templates/pixi-react/__packageName__-simulation/src/lib/schema/code-gen/Transform2d.ts +65 -0
  48. package/templates/pixi-react/__packageName__-simulation/src/lib/schema/code-gen/Velocity2d.ts +55 -0
  49. package/templates/pixi-react/__packageName__-simulation/src/lib/schema/code-gen/__ProjectName__.core.ts +22 -0
  50. package/templates/pixi-react/__packageName__-simulation/src/lib/schema/code-gen/__ProjectName__.runner.ts +16 -0
  51. package/templates/pixi-react/__packageName__-simulation/src/lib/schema/code-gen/__ProjectName__InputRegistry.ts +8 -0
  52. package/templates/pixi-react/__packageName__-simulation/src/lib/schema/code-gen/index.ts +16 -0
  53. package/templates/pixi-react/__packageName__-simulation/src/lib/schema/ecs.yaml +52 -0
  54. package/templates/pixi-react/__packageName__-simulation/src/lib/signals/index.ts +5 -0
  55. package/templates/pixi-react/__packageName__-simulation/src/lib/systems/apply-move-input.system.ts +23 -0
  56. package/templates/pixi-react/__packageName__-simulation/src/lib/systems/boundary.system.ts +34 -0
  57. package/templates/pixi-react/__packageName__-simulation/src/lib/systems/damping.system.ts +18 -0
  58. package/templates/pixi-react/__packageName__-simulation/src/lib/systems/hash-verification.system.ts +17 -0
  59. package/templates/pixi-react/__packageName__-simulation/src/lib/systems/index.ts +20 -0
  60. package/templates/pixi-react/__packageName__-simulation/src/lib/systems/integrate.system.ts +18 -0
  61. package/templates/pixi-react/__packageName__-simulation/src/lib/systems/player-connection.system.ts +47 -0
  62. package/templates/pixi-react/__packageName__-simulation/src/lib/systems/player-leave.system.ts +21 -0
  63. package/templates/pixi-react/__packageName__-simulation/src/lib/systems/save-prev-transform.system.ts +17 -0
  64. package/templates/pixi-react/__packageName__-simulation/tsconfig.json +22 -0
  65. package/templates/pixi-react/package.json +8 -0
  66. package/templates/pixi-react/pnpm-workspace.yaml +4 -0
  67. package/templates/pixi-react/tsconfig.base.json +21 -0
@@ -0,0 +1,3 @@
1
+ #!/usr/bin/env node
2
+ export {};
3
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":""}
package/dist/index.js ADDED
@@ -0,0 +1,123 @@
1
+ #!/usr/bin/env node
2
+ import { program } from 'commander';
3
+ import * as fs from 'node:fs';
4
+ import * as path from 'node:path';
5
+ import * as ejs from 'ejs';
6
+ import { fileURLToPath } from 'node:url';
7
+ const __filename = fileURLToPath(import.meta.url);
8
+ const __dirname = path.dirname(__filename);
9
+ function toPascalCase(kebab) {
10
+ return kebab.split('-').map((s)=>s.charAt(0).toUpperCase() + s.slice(1)).join('');
11
+ }
12
+ function getTemplatesDir() {
13
+ // In development (source), templates are sibling to src/
14
+ // In dist, templates are at package root (copied by files field)
15
+ const devPath = path.resolve(__dirname, '..', 'templates');
16
+ if (fs.existsSync(devPath)) return devPath;
17
+ // Fallback for npm install
18
+ const distPath = path.resolve(__dirname, '..', '..', 'templates');
19
+ if (fs.existsSync(distPath)) return distPath;
20
+ throw new Error('Templates directory not found');
21
+ }
22
+ function walkDir(dir) {
23
+ const results = [];
24
+ for (const entry of fs.readdirSync(dir, {
25
+ withFileTypes: true
26
+ })){
27
+ const full = path.join(dir, entry.name);
28
+ if (entry.isDirectory()) {
29
+ results.push(...walkDir(full));
30
+ } else {
31
+ results.push(full);
32
+ }
33
+ }
34
+ return results;
35
+ }
36
+ function processPath(filePath, vars) {
37
+ let result = filePath;
38
+ result = result.replace(/__packageName__/g, vars.packageName);
39
+ result = result.replace(/__ProjectName__/g, vars.projectName);
40
+ return result;
41
+ }
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)=>{
43
+ const targetDir = path.resolve(process.cwd(), projectArg);
44
+ const packageName = path.basename(targetDir).toLowerCase();
45
+ const pascalName = toPascalCase(packageName);
46
+ if (fs.existsSync(targetDir)) {
47
+ console.error(`Error: Directory "${targetDir}" already exists.`);
48
+ process.exit(1);
49
+ }
50
+ const templatesDir = getTemplatesDir();
51
+ const presetDir = path.join(templatesDir, options.preset);
52
+ if (!fs.existsSync(presetDir)) {
53
+ console.error(`Error: Preset "${options.preset}" not found. Available: ${fs.readdirSync(templatesDir).join(', ')}`);
54
+ process.exit(1);
55
+ }
56
+ // Read package.json from this package to get current lagless version
57
+ const pkgJsonPath = path.resolve(__dirname, '..', 'package.json');
58
+ let laglessVersion = '0.0.30';
59
+ try {
60
+ const pkg = JSON.parse(fs.readFileSync(pkgJsonPath, 'utf-8'));
61
+ laglessVersion = pkg.version || laglessVersion;
62
+ } catch (e) {
63
+ // fallback
64
+ }
65
+ const vars = {
66
+ projectName: pascalName,
67
+ packageName,
68
+ frontendPort: options.port,
69
+ serverPort: options.serverPort,
70
+ laglessVersion
71
+ };
72
+ console.log(`\nCreating Lagless project "${packageName}"...`);
73
+ console.log(` Preset: ${options.preset}`);
74
+ console.log(` Frontend port: ${options.port}`);
75
+ console.log(` Server port: ${options.serverPort}`);
76
+ console.log(` Target: ${targetDir}\n`);
77
+ const templateFiles = walkDir(presetDir);
78
+ for (const templateFile of templateFiles){
79
+ const relativePath = path.relative(presetDir, templateFile);
80
+ const outputRelative = processPath(relativePath, vars);
81
+ const outputPath = path.join(targetDir, outputRelative);
82
+ const outputDir = path.dirname(outputPath);
83
+ if (!fs.existsSync(outputDir)) {
84
+ fs.mkdirSync(outputDir, {
85
+ recursive: true
86
+ });
87
+ }
88
+ const content = fs.readFileSync(templateFile, 'utf-8');
89
+ // Only process .ejs files or text files that might contain EJS tags
90
+ const ext = path.extname(templateFile);
91
+ const textExts = [
92
+ '.ts',
93
+ '.tsx',
94
+ '.json',
95
+ '.yaml',
96
+ '.yml',
97
+ '.html',
98
+ '.css',
99
+ '.md',
100
+ '.toml',
101
+ '.gitignore'
102
+ ];
103
+ if (textExts.includes(ext) || ext === '.ejs') {
104
+ const rendered = ejs.render(content, vars, {
105
+ filename: templateFile
106
+ });
107
+ const finalPath = ext === '.ejs' ? outputPath.replace(/\.ejs$/, '') : outputPath;
108
+ fs.writeFileSync(finalPath, rendered, 'utf-8');
109
+ } else {
110
+ // Binary or unknown — copy as-is
111
+ fs.copyFileSync(templateFile, outputPath);
112
+ }
113
+ }
114
+ console.log('Project created successfully!\n');
115
+ console.log('Next steps:');
116
+ console.log(` cd ${packageName}`);
117
+ console.log(' pnpm install');
118
+ console.log(' pnpm dev:backend # Start game server');
119
+ console.log(' pnpm dev:frontend # Start frontend dev server\n');
120
+ });
121
+ program.parse();
122
+
123
+ //# sourceMappingURL=index.js.map
@@ -0,0 +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 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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"}
@@ -0,0 +1 @@
1
+ {"version":"5.9.3"}
package/package.json ADDED
@@ -0,0 +1,59 @@
1
+ {
2
+ "name": "@lagless/create",
3
+ "version": "0.0.33",
4
+ "license": "MIT",
5
+ "repository": {
6
+ "type": "git",
7
+ "url": "https://github.com/ppauel/lagless",
8
+ "directory": "tools/create"
9
+ },
10
+ "type": "module",
11
+ "main": "./dist/index.js",
12
+ "types": "./dist/index.d.ts",
13
+ "bin": {
14
+ "create-lagless": "./dist/index.js"
15
+ },
16
+ "exports": {
17
+ "./package.json": "./package.json",
18
+ ".": {
19
+ "@lagless/source": "./src/index.ts",
20
+ "types": "./dist/index.d.ts",
21
+ "import": "./dist/index.js",
22
+ "default": "./dist/index.js"
23
+ }
24
+ },
25
+ "files": [
26
+ "dist",
27
+ "templates",
28
+ "README.md"
29
+ ],
30
+ "nx": {
31
+ "sourceRoot": "tools/create/src",
32
+ "targets": {
33
+ "build": {
34
+ "executor": "@nx/js:swc",
35
+ "outputs": [
36
+ "{options.outputPath}"
37
+ ],
38
+ "options": {
39
+ "outputPath": "tools/create/dist",
40
+ "main": "tools/create/src/index.ts",
41
+ "tsConfig": "tools/create/tsconfig.lib.json",
42
+ "skipTypeCheck": true,
43
+ "stripLeadingPaths": true
44
+ }
45
+ }
46
+ }
47
+ },
48
+ "dependencies": {
49
+ "commander": "^14.0.1",
50
+ "ejs": "^3.1.10",
51
+ "@swc/helpers": "~0.5.11"
52
+ },
53
+ "devDependencies": {
54
+ "@types/ejs": "^3.1.5"
55
+ },
56
+ "publishConfig": {
57
+ "access": "public"
58
+ }
59
+ }
@@ -0,0 +1 @@
1
+ *
@@ -0,0 +1,41 @@
1
+ # AGENTS.md — Multi-Agent Development Guide
2
+
3
+ ## Project Overview
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
9
+
10
+ ## Task Decomposition
11
+
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
18
+
19
+ ### Debugging Determinism Issues
20
+
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
25
+
26
+ ### Testing Strategy
27
+
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
31
+
32
+ ## Key Files
33
+
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 |
@@ -0,0 +1,79 @@
1
+ # CLAUDE.md
2
+
3
+ ## What This Is
4
+
5
+ <%= projectName %> is a multiplayer browser game built with **Lagless**, a deterministic ECS framework. TypeScript, simulate/rollback netcode, all simulation state in a single ArrayBuffer.
6
+
7
+ ## Commands
8
+
9
+ ```bash
10
+ # Install
11
+ pnpm install
12
+
13
+ # Start game server (Bun)
14
+ pnpm dev:backend
15
+
16
+ # Start frontend dev server (Vite)
17
+ pnpm dev:frontend
18
+
19
+ # Regenerate ECS code after schema changes
20
+ pnpm codegen
21
+ ```
22
+
23
+ ## Architecture
24
+
25
+ ### Three Packages
26
+
27
+ - **<%= packageName %>-simulation** — Shared deterministic game logic (ECS systems, components, signals)
28
+ - **<%= packageName %>-frontend** — React + Pixi.js game client
29
+ - **<%= packageName %>-backend** — Bun game server (relay model, no simulation)
30
+
31
+ ### ECS Memory Model
32
+
33
+ All state lives in one contiguous ArrayBuffer with Structure-of-Arrays layout. Snapshot = `ArrayBuffer.slice(0)`. Rollback = `Uint8Array.set()` from snapshot.
34
+
35
+ ### Simulation Loop
36
+
37
+ ```
38
+ 1. clock.update(dt)
39
+ 2. targetTick = floor(accTime / frameLength)
40
+ 3. checkAndRollback() — if remote inputs arrived
41
+ 4. simulationTicks: for each tick → run systems in order → process signals
42
+ 5. inputProvider.update() — drain inputs, send to server
43
+ 6. interpolationFactor = leftover / frameLength
44
+ ```
45
+
46
+ ### Input System
47
+
48
+ RPCs are deterministically ordered by `(playerSlot, ordinal, seq)`. Local inputs are scheduled `currentTick + inputDelay` ticks ahead. Server relays inputs to all clients.
49
+
50
+ ### Key Conventions
51
+
52
+ - **Determinism is paramount**: Same inputs + same seed = identical state on every client
53
+ - All deterministic math must use `MathOps` (WASM-backed), never `Math.*` trig functions
54
+ - `MathOps.init()` must be called before simulation starts
55
+ - When spawning entities: always set `prevPositionX/Y = positionX/Y` to avoid interpolation jumps
56
+ - `@abraham/reflection` must be imported before any decorated class
57
+ - Generated code in `code-gen/` directories — never edit manually, regenerate from YAML
58
+ - Systems array order = execution order (deterministic)
59
+
60
+ ### Schema & Codegen
61
+
62
+ ECS schema is defined in `<%= packageName %>-simulation/src/lib/schema/ecs.yaml`. Run `pnpm codegen` after changes.
63
+
64
+ Supported field types: `uint8`, `uint16`, `uint32`, `int8`, `int16`, `int32`, `float32`, `float64`, `uint8[N]` (fixed arrays).
65
+
66
+ ### Adding Components/Systems
67
+
68
+ 1. Add to `ecs.yaml`, run `pnpm codegen`
69
+ 2. Create system file in `systems/` with `@ECSSystem()` decorator
70
+ 3. Add to systems array in `systems/index.ts` (order matters)
71
+ 4. Systems get dependencies via constructor injection (DI container)
72
+
73
+ ### Multiplayer
74
+
75
+ - Server never runs simulation — relay model
76
+ - `RelayInputProvider` handles prediction + rollback
77
+ - `RelayConnection` manages WebSocket to relay server
78
+ - State transfer for late-join via `StateRequest`/`StateResponse`
79
+ - `RoomHooks` in backend define game lifecycle (join, leave, reconnect)
@@ -0,0 +1,151 @@
1
+ # <%= projectName %>
2
+
3
+ A multiplayer game built with [Lagless](https://github.com/ppauel/lagless) — a deterministic ECS framework for real-time multiplayer browser games.
4
+
5
+ ## Prerequisites
6
+
7
+ - [Node.js](https://nodejs.org/) 20+
8
+ - [pnpm](https://pnpm.io/) 9+
9
+ - [Bun](https://bun.sh/) (for the game server)
10
+
11
+ ## Getting Started
12
+
13
+ ```bash
14
+ pnpm install
15
+
16
+ # Terminal 1 — Start the game server
17
+ pnpm dev:backend
18
+
19
+ # Terminal 2 — Start the frontend dev server
20
+ pnpm dev:frontend
21
+ ```
22
+
23
+ Open http://localhost:<%= frontendPort %> in your browser. Click "Play Local" for single-player or "Play Online" for multiplayer.
24
+
25
+ Press **F3** to toggle the debug panel (shows network stats, tick info, hash verification).
26
+
27
+ ## Project Structure
28
+
29
+ ```
30
+ <%= packageName %>/
31
+ ├── <%= packageName %>-simulation/ # Shared deterministic game logic (ECS)
32
+ │ └── src/lib/
33
+ │ ├── schema/ecs.yaml # ECS schema definition
34
+ │ ├── schema/code-gen/ # Generated code (don't edit manually)
35
+ │ ├── systems/ # ECS systems (game logic)
36
+ │ ├── signals/ # Rollback-aware events
37
+ │ └── arena.ts # Game constants
38
+ ├── <%= packageName %>-frontend/ # React + Pixi.js game client
39
+ │ └── src/app/
40
+ │ ├── screens/ # Title screen, game screen
41
+ │ ├── hooks/ # Match start hooks
42
+ │ └── game-view/ # Pixi.js rendering
43
+ └── <%= packageName %>-backend/ # Bun game server
44
+ └── src/
45
+ ├── main.ts # Server entry point
46
+ └── game-hooks.ts # Room lifecycle hooks
47
+ ```
48
+
49
+ ## ECS Schema
50
+
51
+ The game's data model is defined in `<%= packageName %>-simulation/src/lib/schema/ecs.yaml`. After modifying the schema, regenerate code:
52
+
53
+ ```bash
54
+ pnpm codegen
55
+ ```
56
+
57
+ ### Schema Reference
58
+
59
+ **Components** — Per-entity data stored in Structure-of-Arrays layout:
60
+ ```yaml
61
+ components:
62
+ MyComponent:
63
+ fieldName: float32 # Supported: uint8, uint16, uint32, int8, int16, int32, float32, float64
64
+ ```
65
+
66
+ **Singletons** — Global game state (single instance):
67
+ ```yaml
68
+ singletons:
69
+ GameState:
70
+ gamePhase: uint8
71
+ ```
72
+
73
+ **Player Resources** — Per-player data (indexed by player slot):
74
+ ```yaml
75
+ playerResources:
76
+ PlayerResource:
77
+ score: uint32
78
+ id: uint8[16] # Fixed-size arrays supported
79
+ ```
80
+
81
+ **Inputs** — RPCs sent by clients and server:
82
+ ```yaml
83
+ inputs:
84
+ MoveInput:
85
+ directionX: float32
86
+ directionY: float32
87
+ ```
88
+
89
+ **Filters** — Cached entity queries:
90
+ ```yaml
91
+ filters:
92
+ PlayerFilter:
93
+ include: [Transform2d, PlayerBody]
94
+ # exclude: [Dead] # Optional
95
+ ```
96
+
97
+ ## Adding a New System
98
+
99
+ 1. Create `<%= packageName %>-simulation/src/lib/systems/my-system.system.ts`:
100
+
101
+ ```typescript
102
+ import { ECSSystem, IECSSystem } from '@lagless/core';
103
+ import { Transform2d, PlayerFilter } from '../schema/code-gen/index.js';
104
+
105
+ @ECSSystem()
106
+ export class MySystem implements IECSSystem {
107
+ constructor(
108
+ private readonly _PlayerFilter: PlayerFilter,
109
+ private readonly _Transform2d: Transform2d,
110
+ ) {}
111
+
112
+ public update(tick: number): void {
113
+ for (const entity of this._PlayerFilter) {
114
+ // Your logic here
115
+ }
116
+ }
117
+ }
118
+ ```
119
+
120
+ 2. Add it to the systems array in `systems/index.ts` (order matters — systems run sequentially).
121
+
122
+ ## Key Conventions
123
+
124
+ - **Determinism**: All simulation math must use `MathOps` for trig functions (WASM-backed). Never use `Math.sin/cos/atan2/sqrt` in systems.
125
+ - **Interpolation**: Always set `prevPositionX/Y` equal to `positionX/Y` when spawning entities to avoid one-frame jumps.
126
+ - **Vector math**: Use `Vector2.addToRef()`/`.addInPlace()` in hot paths to avoid allocations.
127
+ - **Decorators**: `@abraham/reflection` must be imported before any decorated class (done in `main.tsx`).
128
+
129
+ ## Architecture
130
+
131
+ - **Server never runs simulation** — it relays inputs between clients
132
+ - **Clients are authoritative on determinism** — same inputs + same seed = identical state
133
+ - **Rollback netcode** — local inputs are predicted, remote inputs trigger rollback when they arrive
134
+ - **State transfer** — late-joining players receive a state snapshot from existing clients
135
+
136
+ ## @lagless Packages
137
+
138
+ | Package | Description |
139
+ |---------|-------------|
140
+ | `@lagless/core` | ECS engine, simulation loop, input system, signals |
141
+ | `@lagless/binary` | Memory tracking, typed array utilities |
142
+ | `@lagless/math` | Deterministic math (WASM), Vector2 |
143
+ | `@lagless/misc` | Logging, UUID, VisualSmoother2d |
144
+ | `@lagless/net-wire` | Binary network protocol messages |
145
+ | `@lagless/relay-client` | Client-side relay connection, RelayInputProvider |
146
+ | `@lagless/relay-server` | Server-side relay room, RoomHooks |
147
+ | `@lagless/relay-game-server` | Full game server with matchmaking |
148
+ | `@lagless/matchmaking` | Matchmaking queue service |
149
+ | `@lagless/react` | React debug panel, auth utilities |
150
+ | `@lagless/pixi-react` | FilterViews, VFX container, virtual joystick |
151
+ | `@lagless/codegen` | ECS code generation from YAML |
@@ -0,0 +1,4 @@
1
+ # Bun configuration for game-server
2
+ # @lagless/source condition resolves workspace packages to TypeScript source
3
+ [resolve]
4
+ conditions = ["@lagless/source"]
@@ -0,0 +1,19 @@
1
+ {
2
+ "name": "<%= packageName %>-backend",
3
+ "version": "0.0.1",
4
+ "private": true,
5
+ "type": "module",
6
+ "scripts": {
7
+ "dev": "bun --watch run src/main.ts",
8
+ "start": "bun run src/main.ts"
9
+ },
10
+ "dependencies": {
11
+ "<%= packageName %>-simulation": "workspace:*",
12
+ "@lagless/relay-game-server": "^<%= laglessVersion %>",
13
+ "@lagless/misc": "^<%= laglessVersion %>",
14
+ "reflect-metadata": "^0.2.0"
15
+ },
16
+ "devDependencies": {
17
+ "@types/bun": "latest"
18
+ }
19
+ }
@@ -0,0 +1,47 @@
1
+ import type { RoomHooks, RoomContext, PlayerInfo } from '@lagless/relay-server';
2
+ import { LeaveReason } from '@lagless/relay-server';
3
+ import { createLogger, UUID } from '@lagless/misc';
4
+ import { PlayerJoined, PlayerLeft } from '<%= packageName %>-simulation';
5
+
6
+ const log = createLogger('<%= projectName %>Hooks');
7
+
8
+ export const gameHooks: RoomHooks = {
9
+ onRoomCreated(ctx: RoomContext) {
10
+ log.info(`[${ctx.matchId}] Room created, ${ctx.getPlayers().length} players`);
11
+ },
12
+
13
+ onPlayerJoin(ctx: RoomContext, player: PlayerInfo) {
14
+ log.info(`[${ctx.matchId}] Player joined: slot=${player.slot} bot=${player.isBot} id=${player.playerId.slice(0, 8)}`);
15
+
16
+ ctx.emitServerEvent(PlayerJoined.id, {
17
+ playerId: player.isBot
18
+ ? UUID.generateMasked().asUint8()
19
+ : UUID.fromString(player.playerId).asUint8(),
20
+ slot: player.slot,
21
+ } satisfies PlayerJoined['schema']);
22
+ },
23
+
24
+ onPlayerReconnect(ctx: RoomContext, player: PlayerInfo) {
25
+ log.info(`[${ctx.matchId}] Player reconnected: slot=${player.slot} id=${player.playerId.slice(0, 8)}`);
26
+ },
27
+
28
+ onPlayerLeave(ctx: RoomContext, player: PlayerInfo, reason: LeaveReason) {
29
+ log.info(`[${ctx.matchId}] Player left: slot=${player.slot} reason=${LeaveReason[reason]}`);
30
+ ctx.emitServerEvent(PlayerLeft.id, {
31
+ slot: player.slot,
32
+ reason,
33
+ } satisfies PlayerLeft['schema']);
34
+ },
35
+
36
+ shouldAcceptLateJoin() {
37
+ return true;
38
+ },
39
+
40
+ shouldAcceptReconnect() {
41
+ return true;
42
+ },
43
+
44
+ onRoomDisposed(ctx: RoomContext) {
45
+ log.info(`[${ctx.matchId}] Room disposed`);
46
+ },
47
+ };
@@ -0,0 +1,28 @@
1
+ import 'reflect-metadata';
2
+ import { RelayGameServer } from '@lagless/relay-game-server';
3
+ import { <%= projectName %>InputRegistry } from '<%= packageName %>-simulation';
4
+ import { gameHooks } from './game-hooks.js';
5
+
6
+ const server = new RelayGameServer({
7
+ port: Number(process.env.PORT ?? <%= serverPort %>),
8
+ loggerName: '<%= projectName %>Server',
9
+ roomType: {
10
+ name: '<%= packageName %>',
11
+ config: {
12
+ maxPlayers: 4,
13
+ tickRateHz: 60,
14
+ maxFutureTicks: 20,
15
+ lateJoinEnabled: true,
16
+ reconnectTimeoutMs: 30_000,
17
+ stateTransferTimeoutMs: 5_000,
18
+ },
19
+ hooks: gameHooks,
20
+ inputRegistry: <%= projectName %>InputRegistry,
21
+ },
22
+ matchmaking: {
23
+ scope: '<%= packageName %>',
24
+ config: { minPlayersToStart: 1, maxPlayers: 4, waitTimeoutMs: 2_000 },
25
+ },
26
+ });
27
+
28
+ server.start();
@@ -0,0 +1,19 @@
1
+ {
2
+ "compilerOptions": {
3
+ "target": "ES2022",
4
+ "module": "ESNext",
5
+ "moduleResolution": "bundler",
6
+ "lib": ["ES2022"],
7
+ "strict": true,
8
+ "esModuleInterop": true,
9
+ "skipLibCheck": true,
10
+ "forceConsistentCasingInFileNames": true,
11
+ "experimentalDecorators": true,
12
+ "emitDecoratorMetadata": true,
13
+ "resolveJsonModule": true,
14
+ "isolatedModules": true,
15
+ "noEmit": true,
16
+ "types": ["bun-types"]
17
+ },
18
+ "include": ["src/**/*.ts"]
19
+ }
@@ -0,0 +1,23 @@
1
+ <!DOCTYPE html>
2
+ <html lang="en">
3
+ <head>
4
+ <meta charset="utf-8" />
5
+ <title><%= projectName %></title>
6
+ <base href="/" />
7
+ <meta name="viewport" content="width=device-width, initial-scale=1" />
8
+ <link rel="stylesheet" href="/src/styles.css" />
9
+ <style>
10
+ html, body {
11
+ margin: 0;
12
+ padding: 0;
13
+ background: #0a0a1a;
14
+ font-family: 'Courier New', Courier, monospace;
15
+ color: #e0e0e0;
16
+ }
17
+ </style>
18
+ </head>
19
+ <body>
20
+ <div id="root"></div>
21
+ <script type="module" src="/src/main.tsx"></script>
22
+ </body>
23
+ </html>