@genex-ai/cli-demo 0.15.0 → 0.16.0

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/index.js CHANGED
@@ -1110,6 +1110,24 @@ async function detectMultiplayer(cwd = process.cwd()) {
1110
1110
  return false;
1111
1111
  }
1112
1112
  }
1113
+ async function detectMatchmaking(log, cwd = process.cwd()) {
1114
+ let pkg;
1115
+ try {
1116
+ pkg = JSON.parse(await fs8.readFile(path9.join(cwd, "package.json"), "utf8"));
1117
+ } catch (err) {
1118
+ log.dim(` (skipping matchmaking \u2014 couldn't read package.json: ${String(err)})`);
1119
+ return null;
1120
+ }
1121
+ const mm = pkg.genex?.matchmaking;
1122
+ if (mm && typeof mm.preset === "string" && mm.preset) {
1123
+ return {
1124
+ preset: mm.preset,
1125
+ ...mm.winCondition ? { winCondition: mm.winCondition } : {},
1126
+ ...mm.config ? { config: mm.config } : {}
1127
+ };
1128
+ }
1129
+ return null;
1130
+ }
1113
1131
  async function runPublish(opts) {
1114
1132
  const log = createLogger({ quiet: opts.quiet });
1115
1133
  log.plain(c.bold("genex publish"));
@@ -1151,6 +1169,8 @@ async function runPublish(opts) {
1151
1169
  const embedSdkVersion = await detectEmbedSdkVersion();
1152
1170
  if (embedSdkVersion) body.embedSdkVersion = embedSdkVersion;
1153
1171
  body.multiplayer = await detectMultiplayer();
1172
+ const matchmaking = await detectMatchmaking(log);
1173
+ if (matchmaking) body.matchmaking = matchmaking;
1154
1174
  res = await fetch(`${apiUrl}/api/projects/${meta.id}/publish`, {
1155
1175
  method: "POST",
1156
1176
  headers: { "Content-Type": "application/json", Authorization: `Bearer ${token}` },
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@genex-ai/cli-demo",
3
- "version": "0.15.0",
3
+ "version": "0.16.0",
4
4
  "description": "Set up your ~/.claude workspace, authorize, create a game project, generate AI assets, and publish (genex CLI).",
5
5
  "type": "module",
6
6
  "bin": {
@@ -37,7 +37,62 @@ example, the shared-object/ball code, rotation, and host usage. Read
37
37
  npm i @genex-ai/multiplayer
38
38
  ```
39
39
 
40
- `objects` and `host` need `@genex-ai/multiplayer` **≥ 0.4.0**.
40
+ This skill targets `@genex-ai/multiplayer` **≥ 0.7.0** (`objects`/`host` since 0.4; `matchmake()` since 0.5; presets + `score()`/`finish()` since 0.6; `createPrivate()`/`joinPrivate()` since 0.7).
41
+
42
+ ## Matchmaking (competitive presets — server-owned)
43
+
44
+ When players should be **matched into separate capped rooms** rather than share one big room (a 1v1
45
+ duel, an FFA arena, N-v-N teams, an invite lobby), use `matchmake()` instead of `connect()`. It
46
+ returns a handle whose **`session` is `null` while searching** — render your OWN "finding a match…"
47
+ HUD from `mm.matchmaking` (its `status`, `queue.position`, `players`/`opponents`, `teams`, `scores`,
48
+ `winCondition`), and switch to the game once `session` goes live:
49
+
50
+ ```ts
51
+ // Pass auth as a FUNCTION — one matchmake() handle re-joins the queue/match many times (re-search,
52
+ // requeue) over a session, and embed tokens rotate (~10 min). A function is read fresh each (re)join;
53
+ // a static object goes stale and gets rejected mid-session.
54
+ const mm = await matchmake<MyState>({ url, room: slug, auth: () => getColyseusAuth() });
55
+ mm.on('matched', () => {/* session is live — start the game */});
56
+ // each frame: if (mm.session) renderGame(mm.session); else renderSearchingHud(mm.matchmaking);
57
+ mm.on('matchEnded', ({ winnerId, scores, draw }) => {/* result screen */});
58
+ mm.on('error', (e) => {/* a (re)join failed, e.g. auth — usually transient; the handle keeps searching */});
59
+
60
+ // Report ONLY your own outcome — the server adjudicates. Which call fits depends on the win condition:
61
+ mm.eliminated(); // I'm out (lastStanding)
62
+ mm.score(1); // I scored (firstToScore / highScoreInTime)
63
+ mm.finish(); // I finished the race (firstToFinish)
64
+ ```
65
+
66
+ Everything is **server-owned** — set once in `package.json` under `genex.matchmaking`, reported at
67
+ publish; the client declares nothing. You never run matchmaking logic: the server owns the queue,
68
+ roles, winner-stays, forfeit, timeout, and the win condition.
69
+
70
+ ```jsonc
71
+ "genex": {
72
+ "matchmaking": {
73
+ "preset": "arena", // duel | arena | teams | private
74
+ "winCondition": "firstToScore", // lastStanding | firstToScore | highScoreInTime | firstToFinish
75
+ "config": { "scoreTarget": 20, "maxPlayers": 8 } // numeric knobs, optional
76
+ }
77
+ }
78
+ ```
79
+
80
+ Presets: **duel** (1v1 winner-stays), **arena** (N-player FFA, join-anytime), **teams** (balanced
81
+ N-v-N), **private** (invite-code lobby). Win conditions: **lastStanding** (last one alive),
82
+ **firstToScore** (first to the score target), **highScoreInTime** (top score at the time cap),
83
+ **firstToFinish** (first to finish). A round that hits the time cap undecided is a draw.
84
+
85
+ **Private lobbies** (for `preset: 'private'`) don't use `matchmake()` — a host makes an invite code
86
+ and friends join it; the lobby is persistent (rounds replay, nobody is evicted):
87
+
88
+ ```ts
89
+ import { createPrivate, joinPrivate } from "@genex-ai/multiplayer";
90
+ const lobby = await createPrivate<MyState>({ url, room: slug, auth: () => getColyseusAuth() }); // live NOW
91
+ showCode(lobby.code); // share this
92
+ // a friend, elsewhere:
93
+ const lobby = await joinPrivate<MyState>(code, { url, room: slug, auth: () => getColyseusAuth() });
94
+ // same handle API as matchmake(): lobby.session, lobby.matchmaking, eliminated()/score()/finish(), cancel()
95
+ ```
41
96
 
42
97
  ## Connect
43
98