@genex-ai/cli-demo 0.92.0-dev.231 → 0.93.0-dev.234
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
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@genex-ai/cli-demo",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.93.0-dev.234",
|
|
4
4
|
"description": "Set up your project's agent workspace (.claude/.codex/.cursor in the game folder), authorize, create a game project, generate AI assets, and publish (genex CLI).",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
@@ -331,6 +331,23 @@ halves of this rule are non-negotiable:
|
|
|
331
331
|
`minPlayers: 2, maxPlayers: 2`: the instant the second player joins). If it doesn't close on
|
|
332
332
|
both, the lobby is broken — fix it; do not ship a waiting room you haven't watched close.
|
|
333
333
|
|
|
334
|
+
**While `status === 'searching'` the roster is EMPTY — count the queue, not `players`.** The
|
|
335
|
+
matchmaker never parks a searcher in a room as a non-player: until it can seat you, you are in the
|
|
336
|
+
search line and in no room at all, so `players`/`connectedPlayers` are `[]`. A pre-match screen that
|
|
337
|
+
counts `players.length` there shows `0 / 2` to a player who is sitting in it, forever. Use the queue
|
|
338
|
+
count — it includes YOU, so it reads `1 / 2` when you are alone — and switch to the roster the moment
|
|
339
|
+
you are seated:
|
|
340
|
+
|
|
341
|
+
```ts
|
|
342
|
+
const v = mm.matchmaking;
|
|
343
|
+
const lobbyCount = v.status === "searching" ? Math.max(v.queue.size, 1) : v.connectedPlayers.length;
|
|
344
|
+
```
|
|
345
|
+
|
|
346
|
+
This bites hardest in the commonest co-op shape: with `minPlayers === maxPlayers` (e.g. `2` and `2`)
|
|
347
|
+
the room can only be formed once BOTH searchers exist, so you go `searching → playing` and **never
|
|
348
|
+
pass through `waiting`** — the screen the player stares at is the SEARCHING screen, and driving it
|
|
349
|
+
off `players.length` pins it at `0 / target` for its entire life.
|
|
350
|
+
|
|
334
351
|
Two ways to present the lobby:
|
|
335
352
|
|
|
336
353
|
- **A) UI lobby (Dota-style).** While `status === 'waiting'`, render an OVERLAY from `mm.matchmaking`
|
|
@@ -552,6 +569,35 @@ fights (many writers). A ball on `objects` glides and has one owner. That's the
|
|
|
552
569
|
nearest `tier.remoteAvatarCap` remotes (`$genex-threejs-adaptive-quality`) — freeze the
|
|
553
570
|
mixer and billboard or hide the rest; a room allows up to 64 players and 64 live avatars
|
|
554
571
|
is a real phone memory kill on its own.
|
|
572
|
+
- **Build each remote's body exactly ONCE — reserve the id BEFORE the first `await`.** You read
|
|
573
|
+
`room.players` every frame, but loading a body is async and slow (seconds). If the only guard is
|
|
574
|
+
the map you fill *after* the await, every frame in that window passes it: you spawn one body per
|
|
575
|
+
frame, each stranded where it spawned — a trail of frozen clones behind the moving player — plus
|
|
576
|
+
dozens of wasted loads. Mark the id synchronously, and drop the body if that player left mid-load:
|
|
577
|
+
|
|
578
|
+
```ts
|
|
579
|
+
const bodies = new Map<string, Body>();
|
|
580
|
+
const loading = new Set<string>(); // the seat reservation
|
|
581
|
+
function ensureBody(p: Player) {
|
|
582
|
+
if (bodies.has(p.id) || loading.has(p.id)) return; // BOTH — checked before any await
|
|
583
|
+
loading.add(p.id);
|
|
584
|
+
void (async () => {
|
|
585
|
+
try {
|
|
586
|
+
const body = await loadVrmClone(p.avatarUrl || "./assets/avatar.vrm");
|
|
587
|
+
if (!room.activePlayers.has(p.id)) return; // left while loading — never add it
|
|
588
|
+
scene.add(body.scene);
|
|
589
|
+
bodies.set(p.id, body);
|
|
590
|
+
} finally { loading.delete(p.id); } // always release, success or throw
|
|
591
|
+
})();
|
|
592
|
+
}
|
|
593
|
+
// Each frame: ensureBody for every remote, then drop bodies whose id is gone.
|
|
594
|
+
for (const [id, body] of bodies) {
|
|
595
|
+
if (!room.activePlayers.has(id)) { scene.remove(body.scene); bodies.delete(id); }
|
|
596
|
+
}
|
|
597
|
+
```
|
|
598
|
+
|
|
599
|
+
The same rule covers anything else you lazily load per player or per object (nameplates, weapon
|
|
600
|
+
models, audio): a check that straddles an `await` is not a guard.
|
|
555
601
|
- `room.activePlayers` — the connected-only subset of `room.players`; use its size for live quorum.
|
|
556
602
|
- `room.objects` — shared objects nobody owns until claimed (a ball, an NPC):
|
|
557
603
|
- `claim(id)` — **legacy** optimistic request. It flips local ownership immediately and is corrected
|