@genex-ai/cli-demo 0.34.0 → 0.35.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/package.json
CHANGED
|
@@ -85,17 +85,71 @@ owns the queue, roles, winner-stays, forfeit, timeout, and the win condition.
|
|
|
85
85
|
```jsonc
|
|
86
86
|
"genex": {
|
|
87
87
|
"matchmaking": {
|
|
88
|
-
"preset": "arena", // duel | arena | teams | private
|
|
88
|
+
"preset": "arena", // open | duel | arena | teams | private
|
|
89
89
|
"winCondition": "firstToScore", // lastStanding | firstToScore | highScoreInTime | firstToFinish
|
|
90
90
|
"config": { "scoreTarget": 20, "maxPlayers": 8 } // numeric knobs, optional
|
|
91
91
|
}
|
|
92
92
|
}
|
|
93
93
|
```
|
|
94
94
|
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
**
|
|
98
|
-
**
|
|
95
|
+
**Choosing a preset — your own rules → `open`; a ready-made match loop → `duel`/`arena`/`teams`.**
|
|
96
|
+
Presets: **open** (a room of N players — the server owns only seating/capacity/refill, you write
|
|
97
|
+
everything else), **duel** (1v1 winner-stays), **arena** (N-player FFA, join-anytime), **teams**
|
|
98
|
+
(balanced N-v-N), **private** (invite-code lobby). Win conditions (batteries-included presets only):
|
|
99
|
+
**lastStanding** (last one alive), **firstToScore** (first to the score target), **highScoreInTime**
|
|
100
|
+
(top score at the time cap), **firstToFinish** (first to finish). A round that hits the time cap
|
|
101
|
+
undecided is a draw. (`open` has no win condition — it never ends a round for you.)
|
|
102
|
+
|
|
103
|
+
### `open` — the default building block (bring your own rules)
|
|
104
|
+
|
|
105
|
+
Reach for **`open`** first: a room of up to `maxPlayers` that the queue seats and (optionally)
|
|
106
|
+
refills, and NOTHING else — no rounds, no scores, no win condition, no winner-stays eviction. You
|
|
107
|
+
build teams/rounds/scoring/win-logic in game code on the primitives you already have (host election,
|
|
108
|
+
`shared`, per-player state, the `players` list). Config knobs (all numbers):
|
|
109
|
+
|
|
110
|
+
```jsonc
|
|
111
|
+
"genex": { "matchmaking": { "preset": "open", "config": {
|
|
112
|
+
"maxPlayers": 10, // seat cap (clamped 2..64)
|
|
113
|
+
"minPlayers": 2, // quorum to flip waiting→playing (default 1)
|
|
114
|
+
"fill": 1 // 1 = keep full: grow to max + refill freed seats (default); 0 = lock for good once simultaneously full (no substitutes)
|
|
115
|
+
} } } }
|
|
116
|
+
```
|
|
117
|
+
|
|
118
|
+
With `open`, `mm.matchmaking.status` only goes `searching`→`waiting`→`playing` (never `countdown`/
|
|
119
|
+
`ended`), `players` is the live roster, and `scores`/`winnerId` stay empty — watch `players` and
|
|
120
|
+
`status`, not `matchStart`/`matchEnded` (they never fire). Nobody is ever evicted. Build the rest:
|
|
121
|
+
|
|
122
|
+
| Want… | Do it in game code on top of `open` |
|
|
123
|
+
| --- | --- |
|
|
124
|
+
| Room formation & refill | Nothing — the server owns it via `minPlayers`/`maxPlayers`/`fill`. |
|
|
125
|
+
| Duel (1v1) | `maxPlayers: 2, minPlayers: 2`, then start when `mm.matchmaking.players.length === 2`. |
|
|
126
|
+
| Team assignment | The **host** splits `players` deterministically (sort ids, round-robin) and writes the map to `shared` (`session.shared.set('teams', …)`); everyone renders from it. Survives host migration because it lives in shared state. |
|
|
127
|
+
| Rounds / countdown | Host writes `{ phase, deadline }` into `shared`; clients render the countdown from the timestamp (no server clock — approximate fairness is fine at this trust tier). |
|
|
128
|
+
| Scores | A host-owned entry in `shared` (or per-player state); you define what a point means. |
|
|
129
|
+
| Win condition | Your code checks its own condition (you already compute the signals) and the host writes the result to `shared`. |
|
|
130
|
+
| Winner-stays / rotation | The losing **client** leaves voluntarily — `mm.cancel()` then `matchmake()` again; the freed seat refills (`fill: 1`). There is NO forced kick (a server-only power, deliberately not exposed) — a modified client can squat its seat, so if you need *enforced* rotation use `duel`/`arena`/`teams` instead. |
|
|
131
|
+
| Forfeit on disconnect | React to the `players` list shrinking (the SDK surfaces leaves after the reconnect grace). |
|
|
132
|
+
| Spectators | A game-level role: keep a "dead"/observing player seated and just render them as a watcher. There is no server spectator concept — everyone in a room is a player. |
|
|
133
|
+
|
|
134
|
+
#### Waiting room / lobby — two patterns
|
|
135
|
+
|
|
136
|
+
`open` seats you into a LIVE shared room the moment you're matched (`session` goes live, players sync)
|
|
137
|
+
but doesn't "start" anything — so the pre-game lobby is simply **your room before it's grown to the
|
|
138
|
+
size you want**. Set `minPlayers` to your target (so `status` stays `waiting` until enough arrive) and
|
|
139
|
+
let the **host** own the "go" moment by writing a start signal to `shared` (it survives host migration).
|
|
140
|
+
The lobby and the game are ONE `open` room — never spin up a second room for it. Two ways to present it:
|
|
141
|
+
|
|
142
|
+
- **A) UI lobby (Dota-style).** While waiting, render an OVERLAY from `mm.matchmaking` instead of the
|
|
143
|
+
game: the roster + count (`players.length` / target), each player's name, and optionally a per-player
|
|
144
|
+
"ready" toggle (store it in per-player state or a `shared` map). The host begins when all are ready
|
|
145
|
+
(or `players.length >= N`) by writing e.g. `session.shared.set('phase', { started: true, at: <ts> })`;
|
|
146
|
+
every client sees it and swaps the overlay for the game.
|
|
147
|
+
- **B) Physical lobby (Roblox-style).** The waiting area IS a 3D scene in the SAME room: render a lobby
|
|
148
|
+
and let players walk their avatars around, syncing position with `me.set` on the tick exactly like
|
|
149
|
+
in-game. Show a "N / target — starting soon" sign driven by `players.length`. A "ready pad" is a nice
|
|
150
|
+
affordance: players stand on it, the host counts how many are on it (from their synced positions) and
|
|
151
|
+
writes a `shared` countdown; when it elapses everyone moves their camera/scene into the match — no
|
|
152
|
+
re-matchmaking, they're already together.
|
|
99
153
|
|
|
100
154
|
**Private lobbies** (for `preset: 'private'`) don't use `matchmake()` — a host makes an invite code
|
|
101
155
|
and friends join it; the lobby is persistent (rounds replay, nobody is evicted):
|