gamedev 0.0.2-alpha.0 → 0.0.4-alpha.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -22,7 +22,7 @@ export class WorldManifest {
22
22
  if (parsed.formatVersion !== 2) {
23
23
  throw new Error(
24
24
  'Invalid world.json format version. Expected formatVersion 2.\n' +
25
- 'Run "hyperfy world export" to regenerate world.json (formatVersion 2).'
25
+ 'Run "gamedev world export" to regenerate world.json (formatVersion 2).'
26
26
  )
27
27
  }
28
28
 
package/app-server/cli.js CHANGED
@@ -17,7 +17,7 @@ async function main() {
17
17
  console.error(`❌ ${err?.message || err}`)
18
18
  process.exit(1)
19
19
  }
20
- const exitCode = await runAppCommand({ command, args, helpPrefix: 'hyperfy' })
20
+ const exitCode = await runAppCommand({ command, args, helpPrefix: 'gamedev apps' })
21
21
  process.exit(exitCode)
22
22
  }
23
23
 
@@ -246,7 +246,7 @@ export class HyperfyCLI {
246
246
  const blueprints = listLocalBlueprints(this.appsDir)
247
247
  if (blueprints.length === 0) {
248
248
  console.log(`📝 No local blueprints found in ${this.appsDir}`)
249
- console.log(`💡 Run "hyperfy world export" to pull blueprints from the world.`)
249
+ console.log(`💡 Run "gamedev world export" to pull blueprints from the world.`)
250
250
  console.log(` Use --include-built-scripts if you need script code locally.`)
251
251
  return
252
252
  }
@@ -399,7 +399,7 @@ app.on("update", (delta) => {
399
399
  console.log(`✅ Successfully created app in world: ${appName}`)
400
400
  console.log(` • Blueprint: ${blueprintId}`)
401
401
  console.log(` • Entity: ${entityId}`)
402
- console.log(`💡 Run "hyperfy world export" to sync into ${this.appsDir}.`)
402
+ console.log(`💡 Run "gamedev world export" to sync into ${this.appsDir}.`)
403
403
  console.log(` Use --include-built-scripts if you need script code locally.`)
404
404
  } catch (error) {
405
405
  console.error(`❌ Error creating app:`, error?.message || error)
@@ -563,7 +563,7 @@ app.on("update", (delta) => {
563
563
  console.log(`✅ Script validation passed for ${appName}`)
564
564
  console.log(`🔗 Hash: ${localHash}`)
565
565
  } else {
566
- console.log(`💡 Run 'hyperfy apps deploy ${appName}' (or save the file with app-server running)`)
566
+ console.log(`💡 Run 'gamedev apps deploy ${appName}' (or save the file with app-server running)`)
567
567
  }
568
568
  } catch (error) {
569
569
  console.error(`❌ Error validating app:`, error?.message || error)
@@ -677,9 +677,9 @@ app.on("update", (delta) => {
677
677
  }
678
678
  }
679
679
 
680
- showHelp({ commandPrefix = 'hyperfy apps' } = {}) {
680
+ showHelp({ commandPrefix = 'gamedev apps' } = {}) {
681
681
  console.log(`
682
- 🚀 Hyperfy CLI (direct /admin mode)
682
+ 🚀 Gamedev CLI (direct /admin mode)
683
683
 
684
684
  Usage:
685
685
  ${commandPrefix} <command> [options]
@@ -697,7 +697,7 @@ Commands:
697
697
  reset [--force] Delete local apps/assets/world.json
698
698
  status Show /admin snapshot summary
699
699
  help Show this help
700
- --target <name> Use .hyperfy/targets.json entry for WORLD_URL/WORLD_ID/ADMIN_CODE/DEPLOY_CODE
700
+ --target <name> Use .lobby/targets.json entry for WORLD_URL/WORLD_ID/ADMIN_CODE/DEPLOY_CODE
701
701
 
702
702
  Options:
703
703
  --dry-run, -n Show deploy plan without applying changes
@@ -739,7 +739,7 @@ export async function runAppCommand({ command, args = [], rootDir = process.cwd(
739
739
  }
740
740
  }
741
741
  const cli = new HyperfyCLI({ rootDir })
742
- const commandPrefix = helpPrefix || 'hyperfy apps'
742
+ const commandPrefix = helpPrefix || 'gamedev apps'
743
743
  let exitCode = 0
744
744
 
745
745
  switch (command) {
@@ -558,7 +558,7 @@ export class DirectAppServer {
558
558
  } else if (!hasWorldFile && hasApps) {
559
559
  throw new Error(
560
560
  'world.json missing; cannot safely apply exact world layout. ' +
561
- 'Run "hyperfy world export" to generate it from the world, or create world.json to seed a new world.'
561
+ 'Run "gamedev world export" to generate it from the world, or create world.json to seed a new world.'
562
562
  )
563
563
  } else {
564
564
  const manifest = this.manifest.read()
@@ -586,7 +586,7 @@ export class DirectAppServer {
586
586
  const err = new Error(
587
587
  'Local project is empty and this world already has content. ' +
588
588
  'Script code is not downloaded by default. ' +
589
- 'Run "hyperfy world export --include-built-scripts" to scaffold from the world.'
589
+ 'Run "gamedev world export --include-built-scripts" to scaffold from the world.'
590
590
  )
591
591
  err.code = 'empty_project_requires_export'
592
592
  throw err
@@ -657,7 +657,7 @@ export class DirectAppServer {
657
657
  async importWorldFromDisk() {
658
658
  const manifest = this.manifest.read()
659
659
  if (!manifest) {
660
- throw new Error('world.json missing. Run "hyperfy world export" to generate it first.')
660
+ throw new Error('world.json missing. Run "gamedev world export" to generate it first.')
661
661
  }
662
662
  const errors = this.manifest.validate(manifest)
663
663
  if (errors.length) {
@@ -29,9 +29,9 @@ dist/
29
29
  !.env.example
30
30
 
31
31
  # Local world state
32
- .hyperfy/*
33
- !.hyperfy/
34
- !.hyperfy/targets.example.json
32
+ .lobby/*
33
+ !.lobby/
34
+ !.lobby/targets.example.json
35
35
 
36
36
  # Claude local settings
37
37
  .claude/settings.local.json
@@ -56,7 +56,7 @@ const DEFAULT_TSCONFIG = {
56
56
 
57
57
  function normalizePackageName(name, fallback) {
58
58
  const raw = (name || '').trim() || (fallback || '').trim()
59
- const base = raw || 'hyperfy-world'
59
+ const base = raw || 'lobby-world'
60
60
  const scopeMatch = base.startsWith('@') ? base.split('/') : null
61
61
  if (scopeMatch && scopeMatch.length === 2) {
62
62
  const scope = scopeMatch[0].toLowerCase().replace(/[^a-z0-9._-]+/g, '')
@@ -64,7 +64,7 @@ function normalizePackageName(name, fallback) {
64
64
  if (scope && pkg) return `${scope}/${pkg}`
65
65
  }
66
66
  const normalized = base.toLowerCase().replace(/[^a-z0-9._-]+/g, '-').replace(/^-+|-+$/g, '')
67
- return normalized || 'hyperfy-world'
67
+ return normalized || 'lobby-world'
68
68
  }
69
69
 
70
70
  function buildPackageJson({ packageName, sdkName, sdkVersion }) {
@@ -79,8 +79,8 @@ function buildPackageJson({ packageName, sdkName, sdkVersion }) {
79
79
  private: true,
80
80
  type: 'module',
81
81
  scripts: {
82
- dev: 'hyperfy dev',
83
- build: 'hyperfy apps build --all',
82
+ dev: 'gamedev dev',
83
+ build: 'gamedev apps build --all',
84
84
  typecheck: 'tsc --noEmit',
85
85
  },
86
86
  devDependencies,
@@ -101,7 +101,7 @@ function resolveSdkVersion() {
101
101
 
102
102
  function buildEnvExample() {
103
103
  return `# Hyperfy project environment (example)
104
- # Run "hyperfy dev" to generate a local .env automatically.
104
+ # Run "gamedev dev" to generate a local .env automatically.
105
105
  WORLD_URL=http://localhost:5000
106
106
  WORLD_ID=local-your-world-id
107
107
  ADMIN_CODE=your-admin-code
@@ -250,7 +250,7 @@ export function scaffoldBaseProject({
250
250
  report,
251
251
  })
252
252
 
253
- writeFileWithPolicy(path.join(rootDir, '.hyperfy', 'targets.example.json'), buildTargetsExample(), {
253
+ writeFileWithPolicy(path.join(rootDir, '.lobby', 'targets.example.json'), buildTargetsExample(), {
254
254
  force,
255
255
  writeFile,
256
256
  report,
@@ -1,7 +1,7 @@
1
1
  import fs from 'fs'
2
2
  import path from 'path'
3
3
 
4
- const TARGETS_FILE = path.join('.hyperfy', 'targets.json')
4
+ const TARGETS_FILE = path.join('.lobby', 'targets.json')
5
5
 
6
6
  export function parseTargetArgs(args = []) {
7
7
  let target = null
@@ -16,7 +16,7 @@ description: Hyperfy world-project app scripting workflow and constraints. Use w
16
16
  1. Identify the target app(s) and update the entry script at `apps/<AppName>/index.ts`.
17
17
  2. Update blueprint defaults in `apps/<AppName>/*.json` (model, props, flags).
18
18
  3. Keep per-instance overrides in `world.json`.
19
- 4. Use `hyperfy dev` for continuous sync; use `hyperfy apps build --all` and `hyperfy apps deploy <app>` for explicit deploys.
19
+ 4. Use `gamedev dev` for continuous sync; use `gamedev apps build --all` and `gamedev apps deploy <app>` for explicit deploys.
20
20
 
21
21
  ## Runtime Constraints
22
22
 
@@ -38,4 +38,4 @@ description: Hyperfy world-project app scripting workflow and constraints. Use w
38
38
  ## Guardrails
39
39
 
40
40
  - Do not edit `dist/` (build output).
41
- - Do not edit `.hyperfy/*` except `targets.json` (local only).
41
+ - Do not edit `.lobby/*` except `targets.json` (local only).
@@ -7,7 +7,7 @@ import { fileURLToPath } from 'url'
7
7
  import { spawn } from 'child_process'
8
8
  import { customAlphabet } from 'nanoid'
9
9
 
10
- import { HyperfyCLI, runAppCommand } from '../app-server/commands.js'
10
+ import { runAppCommand } from '../app-server/commands.js'
11
11
  import { DirectAppServer } from '../app-server/direct.js'
12
12
  import { scaffoldBaseProject, scaffoldBuiltins, writeManifest } from '../app-server/scaffold.js'
13
13
  import { applyTargetEnv, parseTargetArgs, resolveTarget, readTargets } from '../app-server/targets.js'
@@ -59,7 +59,7 @@ function writeDotEnv(filePath, content) {
59
59
  fs.writeFileSync(filePath, content.endsWith('\n') ? content : `${content}\n`, 'utf8')
60
60
  }
61
61
 
62
- const TARGETS_FILE = path.join('.hyperfy', 'targets.json')
62
+ const TARGETS_FILE = path.join('.lobby', 'targets.json')
63
63
 
64
64
  function writeTargetsFile(targets, rootDir = projectDir) {
65
65
  const filePath = path.join(rootDir, TARGETS_FILE)
@@ -68,13 +68,6 @@ function writeTargetsFile(targets, rootDir = projectDir) {
68
68
  return filePath
69
69
  }
70
70
 
71
- function isProjectEmpty(dirPath) {
72
- const ignore = new Set(['.git', '.DS_Store'])
73
- if (!fs.existsSync(dirPath)) return true
74
- const entries = fs.readdirSync(dirPath, { withFileTypes: true })
75
- return entries.every(entry => ignore.has(entry.name))
76
- }
77
-
78
71
  function generateAdminCode() {
79
72
  return crypto.randomBytes(16).toString('base64url')
80
73
  }
@@ -161,7 +154,7 @@ function isLocalWorld({ worldUrl }) {
161
154
  }
162
155
 
163
156
  function getWorldDir(worldId) {
164
- return path.join(projectDir, '.hyperfy', worldId)
157
+ return path.join(projectDir, '.lobby', worldId)
165
158
  }
166
159
 
167
160
  function hasKey(env, key) {
@@ -282,15 +275,6 @@ function validateLocalEnv(env, derived) {
282
275
  return issues
283
276
  }
284
277
 
285
- async function confirmAction(prompt) {
286
- if (!process.stdin.isTTY) return false
287
- const rl = readline.createInterface({ input: process.stdin, output: process.stdout })
288
- const answer = await new Promise(resolve => rl.question(prompt, resolve))
289
- rl.close()
290
- const normalized = (answer || '').trim().toLowerCase()
291
- return normalized === 'y' || normalized === 'yes'
292
- }
293
-
294
278
  async function promptValue(prompt) {
295
279
  if (!process.stdin.isTTY) return null
296
280
  const rl = readline.createInterface({ input: process.stdin, output: process.stdout })
@@ -302,12 +286,6 @@ async function promptValue(prompt) {
302
286
 
303
287
  function ensureEnvForStart() {
304
288
  if (!fs.existsSync(envPath)) {
305
- if (!isProjectEmpty(projectDir)) {
306
- console.error('Error: Missing .env in a non-empty project.')
307
- console.error('Hint: Create a .env with WORLD_URL, WORLD_ID, ADMIN_CODE, and world server settings.')
308
- return { ok: false }
309
- }
310
-
311
289
  const worldId = `local-${uuid()}`
312
290
  const adminCode = generateAdminCode()
313
291
  const jwtSecret = generateJwtSecret()
@@ -469,10 +447,10 @@ async function startCommand(args = []) {
469
447
 
470
448
  function printInitHelp() {
471
449
  console.log(`
472
- Hyperfy Init
450
+ Gamedev Init
473
451
 
474
452
  Usage:
475
- hyperfy init [options]
453
+ gamedev init [options]
476
454
 
477
455
  Options:
478
456
  --name <package> Package name (defaults to folder name)
@@ -568,7 +546,7 @@ async function initCommand(args = []) {
568
546
 
569
547
  async function appsCommand(args) {
570
548
  if (!args.length || ['help', '--help', '-h'].includes(args[0])) {
571
- await runAppCommand({ command: 'help', args: [], rootDir: projectDir, helpPrefix: 'hyperfy apps' })
549
+ await runAppCommand({ command: 'help', args: [], rootDir: projectDir, helpPrefix: 'gamedev apps' })
572
550
  return 0
573
551
  }
574
552
 
@@ -589,7 +567,7 @@ async function appsCommand(args) {
589
567
  const env = readDotEnv(envPath)
590
568
  if (env) applyEnvToProcess(env)
591
569
 
592
- return runAppCommand({ command, args: commandArgs, rootDir: projectDir, helpPrefix: 'hyperfy apps' })
570
+ return runAppCommand({ command, args: commandArgs, rootDir: projectDir, helpPrefix: 'gamedev apps' })
593
571
  }
594
572
 
595
573
  async function connectAdminServer({ worldUrl, adminCode, rootDir }) {
@@ -610,53 +588,6 @@ async function connectAdminServer({ worldUrl, adminCode, rootDir }) {
610
588
  }
611
589
  }
612
590
 
613
- async function projectCommand(args) {
614
- if (!args.length || ['help', '--help', '-h'].includes(args[0])) {
615
- printHelp()
616
- return 0
617
- }
618
-
619
- if (args[0] !== 'reset') {
620
- console.error(`Error: Unknown project command: ${args[0]}`)
621
- printHelp()
622
- return 1
623
- }
624
-
625
- const force = args.includes('--force') || args.includes('-f')
626
- const cli = new HyperfyCLI({ rootDir: projectDir })
627
- await cli.reset({ force })
628
- return 0
629
- }
630
-
631
- async function worldsCommand(args) {
632
- if (!args.length || ['help', '--help', '-h'].includes(args[0]) || args[0] === 'list') {
633
- const root = path.join(projectDir, '.hyperfy')
634
- if (!fs.existsSync(root)) {
635
- console.log('Worlds: No local worlds found.')
636
- return 0
637
- }
638
- const entries = fs
639
- .readdirSync(root, { withFileTypes: true })
640
- .filter(entry => entry.isDirectory())
641
- .map(entry => entry.name)
642
-
643
- if (!entries.length) {
644
- console.log('Worlds: No local worlds found.')
645
- return 0
646
- }
647
-
648
- console.log('Worlds: Local worlds:')
649
- for (const entry of entries) {
650
- console.log(` - ${entry}`)
651
- }
652
- return 0
653
- }
654
-
655
- console.error(`Error: Unknown worlds command: ${args[0]}`)
656
- printHelp()
657
- return 1
658
- }
659
-
660
591
  async function worldCommand(args) {
661
592
  if (!args.length || ['help', '--help', '-h'].includes(args[0])) {
662
593
  printHelp()
@@ -665,7 +596,7 @@ async function worldCommand(args) {
665
596
 
666
597
  const action = args[0]
667
598
 
668
- if (action === 'export' || action === 'import' || action === 'wipe') {
599
+ if (action === 'export' || action === 'import') {
669
600
  const env = readDotEnv(envPath)
670
601
  if (!env) {
671
602
  console.error('Error: Missing .env in this project.')
@@ -680,37 +611,6 @@ async function worldCommand(args) {
680
611
  return 1
681
612
  }
682
613
 
683
- if (action === 'wipe') {
684
- if (!isLocalWorld({ worldUrl })) {
685
- console.error('Error: WORLD_URL does not indicate a local world. Refusing to wipe.')
686
- return 1
687
- }
688
-
689
- const worldDir = getWorldDir(worldId)
690
- if (!fs.existsSync(worldDir)) {
691
- console.log(`Worlds: No local world found at ${worldDir}`)
692
- return 0
693
- }
694
-
695
- const force = args.includes('--force') || args.includes('-f')
696
- if (!force) {
697
- const ok = await confirmAction(`Delete local world data at ${worldDir}? (y/N): `)
698
- if (!ok) {
699
- console.log('World wipe cancelled')
700
- return 1
701
- }
702
- }
703
-
704
- try {
705
- fs.rmSync(worldDir, { recursive: true, force: true })
706
- console.log(`Deleted ${worldDir}`)
707
- return 0
708
- } catch (error) {
709
- console.error(`Error: Failed to delete ${worldDir}`, error?.message || error)
710
- return 1
711
- }
712
- }
713
-
714
614
  let server
715
615
  try {
716
616
  server = await connectAdminServer({ worldUrl, adminCode: env.ADMIN_CODE, rootDir: projectDir })
@@ -740,10 +640,10 @@ async function worldCommand(args) {
740
640
 
741
641
  function printFlyHelp() {
742
642
  console.log(`
743
- Hyperfy Fly
643
+ Gamedev Fly
744
644
 
745
645
  Usage:
746
- hyperfy fly <command> [options]
646
+ gamedev fly <command> [options]
747
647
 
748
648
  Commands:
749
649
  init Generate fly.toml and optional GitHub workflow
@@ -755,11 +655,11 @@ Init options:
755
655
  --region <code> Fly region (default: ams)
756
656
  --persist Enable volume mount + SAVE_INTERVAL
757
657
  --world-id <id> WORLD_ID override (default: fly-<app>)
758
- --target <name> Update .hyperfy/targets.json with worldUrl/worldId
658
+ --target <name> Update .lobby/targets.json with worldUrl/worldId
759
659
  --force, -f Overwrite existing fly.toml / workflow
760
660
 
761
661
  Secrets options:
762
- --target <name> Update .hyperfy/targets.json with generated codes
662
+ --target <name> Update .lobby/targets.json with generated codes
763
663
  --no-deploy-code Skip DEPLOY_CODE generation
764
664
  --force, -f Overwrite existing target codes
765
665
  `)
@@ -1067,26 +967,22 @@ async function flyCommand(args) {
1067
967
 
1068
968
  function printHelp() {
1069
969
  console.log(`
1070
- Hyperfy CLI
970
+ Gamedev CLI
1071
971
 
1072
972
  Usage:
1073
- hyperfy <command> [options]
973
+ gamedev <command> [options]
1074
974
 
1075
975
  Commands:
1076
976
  init Scaffold a new world project in the current folder
1077
- start Start the world (local or remote) + app-server sync
1078
- dev Alias for start
977
+ dev Start the world (local or remote) + app-server sync
1079
978
  apps <command> Manage apps (create, list, build, clean, deploy, update, validate, status)
1080
979
  fly <command> Fly.io deployment helpers
1081
- project reset [--force] Delete local apps/assets/world.json in this project
1082
980
  world export Export world.json + apps/assets from the world (use --include-built-scripts for scripts)
1083
981
  world import Import local apps + world.json into the world
1084
- world wipe [--force] Delete the local world runtime directory for this project
1085
- worlds list List local world directories in ./.hyperfy
1086
982
  help Show this help
1087
983
 
1088
984
  Options:
1089
- --target <name> Use .hyperfy/targets.json entry (applies to start/dev/apps)
985
+ --target <name> Use .lobby/targets.json entry (applies to dev/apps)
1090
986
  `)
1091
987
  }
1092
988
 
@@ -1096,17 +992,12 @@ async function main() {
1096
992
  switch (command) {
1097
993
  case 'init':
1098
994
  return initCommand(args)
1099
- case 'start':
1100
995
  case 'dev':
1101
996
  return startCommand(args)
1102
997
  case 'apps':
1103
998
  return appsCommand(args)
1104
- case 'project':
1105
- return projectCommand(args)
1106
999
  case 'world':
1107
1000
  return worldCommand(args)
1108
- case 'worlds':
1109
- return worldsCommand(args)
1110
1001
  case 'fly':
1111
1002
  return flyCommand(args)
1112
1003
  case 'help':
@@ -25,14 +25,14 @@
25
25
  <meta name="apple-mobile-web-app-capable" content="yes" />
26
26
  <meta name="mobile-web-app-capable" content="yes" />
27
27
  <link rel="preload" href="/rubik.woff2" as="font" type="font/woff2" crossorigin />
28
- <link rel="stylesheet" type="text/css" href="/index.css?v=1768869532415" />
28
+ <link rel="stylesheet" type="text/css" href="/index.css?v=1768873891919" />
29
29
  <script>
30
30
  window.PARTICLES_PATH = '/particles-4YQR4CFO.js'
31
31
  </script>
32
32
  </head>
33
33
  <body>
34
34
  <div id="root"></div>
35
- <script src="/env.js?v=1768869532415"></script>
35
+ <script src="/env.js?v=1768873891919"></script>
36
36
  <script src="/admin-EJMJPJ7M.js" type="module"></script>
37
37
  </body>
38
38
  </html>
@@ -26,14 +26,14 @@
26
26
  <meta name="mobile-web-app-capable" content="yes" />
27
27
  <!-- <link rel='icon' href='/favicon.ico' /> -->
28
28
  <link rel="preload" href="/rubik.woff2" as="font" type="font/woff2" crossorigin />
29
- <link rel="stylesheet" type="text/css" href="/index.css?v=1768869532414" />
29
+ <link rel="stylesheet" type="text/css" href="/index.css?v=1768873891919" />
30
30
  <script>
31
31
  window.PARTICLES_PATH = '/particles-4YQR4CFO.js'
32
32
  </script>
33
33
  </head>
34
34
  <body>
35
35
  <div id="root"></div>
36
- <script src="/env.js?v=1768869532414"></script>
36
+ <script src="/env.js?v=1768873891919"></script>
37
37
  <script src="/index-RJRKEY6V.js" type="module"></script>
38
38
  </body>
39
39
  </html>
package/package.json CHANGED
@@ -1,12 +1,13 @@
1
1
  {
2
2
  "name": "gamedev",
3
- "version": "0.0.2-alpha.0",
3
+ "version": "0.0.4-alpha.1",
4
4
  "type": "module",
5
5
  "main": "index.node.js",
6
6
  "types": "index.d.ts",
7
7
  "files": [
8
8
  "build/**",
9
9
  "app-server/**",
10
+ "src/core/packets.js",
10
11
  "index.node.js",
11
12
  "index.node-client.js",
12
13
  "index.d.ts",
@@ -65,7 +66,7 @@
65
66
  "test:integration": "node --test \"test/integration/**/*.test.js\""
66
67
  },
67
68
  "bin": {
68
- "@drama.haus/hyperfy": "bin/hyperfy.mjs"
69
+ "gamedev": "bin/gamedev.mjs"
69
70
  },
70
71
  "dependencies": {
71
72
  "@aws-sdk/client-s3": "^3.859.0",
@@ -0,0 +1,78 @@
1
+ import { Packr } from 'msgpackr'
2
+
3
+ const packr = new Packr({ structuredClone: true })
4
+
5
+ // prettier-ignore
6
+ const names = [
7
+ 'snapshot',
8
+ 'command',
9
+ 'chatAdded',
10
+ 'chatCleared',
11
+ 'blueprintAdded',
12
+ 'blueprintModified',
13
+ 'entityAdded',
14
+ 'entityModified',
15
+ 'entityEvent',
16
+ 'entityRemoved',
17
+ 'playerTeleport',
18
+ 'playerPush',
19
+ 'playerSessionAvatar',
20
+ 'liveKitLevel',
21
+ 'mute',
22
+ 'settingsModified',
23
+ 'spawnModified',
24
+ 'modifyRank',
25
+ 'kick',
26
+ 'ping',
27
+ 'pong',
28
+ 'blueprintRemoved',
29
+ // admin protocol
30
+ 'adminAuth',
31
+ 'adminAuthOk',
32
+ 'adminAuthError',
33
+ 'adminCommand',
34
+ 'adminResult',
35
+ // admin player streaming
36
+ 'playerJoined',
37
+ 'playerUpdated',
38
+ 'playerLeft',
39
+ ]
40
+
41
+ const byName = {}
42
+ const byId = {}
43
+
44
+ let ids = -1
45
+
46
+ for (const name of names) {
47
+ const id = ++ids
48
+ const info = {
49
+ id,
50
+ name,
51
+ method: `on${capitalize(name)}`, // eg 'connect' -> 'onConnect'
52
+ }
53
+ byName[name] = info
54
+ byId[id] = info
55
+ }
56
+
57
+ function capitalize(str) {
58
+ return str.charAt(0).toUpperCase() + str.slice(1)
59
+ }
60
+
61
+ export function writePacket(name, data) {
62
+ const info = byName[name]
63
+ if (!info) throw new Error(`writePacket failed: ${name} (name not found)`)
64
+ const packet = packr.pack([info.id, data])
65
+ return packet
66
+ }
67
+
68
+ export function readPacket(packet) {
69
+ try {
70
+ const [id, data] = packr.unpack(packet)
71
+ const info = byId[id]
72
+ if (!info) throw new Error(`readPacket failed: ${id} (id not found)`)
73
+ return [info.method, data]
74
+ } catch (err) {
75
+ console.error(err)
76
+ return []
77
+ }
78
+ }