@jgengine/convex 0.44.0 → 0.46.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/CHANGELOG.md +1024 -27
- package/README.md +1 -1
- package/dist/convexChatTransport.d.ts +2 -2
- package/dist/convexSaveBackend.d.ts +1 -1
- package/dist/convexSaveBackend.js +1 -1
- package/dist/createConvexGameTransport.d.ts +3 -3
- package/dist/server.d.ts +7 -11
- package/dist/server.js +3 -5
- package/package.json +5 -5
- package/skills/ce-handoff/SKILL.md +1 -1
- package/skills/jgengine/SKILL.md +5 -5
- package/skills/jgengine/api.md +171 -88
- package/skills/jgengine/capabilities.md +30 -10
- package/skills/jgengine/recipes/minimal-game.md +5 -5
- package/skills/jgengine-multiplayer/SKILL.md +2 -2
- package/skills/jgengine-multiplayer/api.md +81 -47
- package/skills/jgengine-multiplayer/capabilities.md +13 -1
- package/skills/jgengine-multiplayer/reference.md +12 -12
- package/skills/jgengine-multiplayer/references/peer-worlds.md +1 -1
package/README.md
CHANGED
|
@@ -19,7 +19,7 @@ const session = resolveConvexMultiplayer({
|
|
|
19
19
|
<GamePlayerShell playable={playable} multiplayer={session} />;
|
|
20
20
|
```
|
|
21
21
|
|
|
22
|
-
`resolveShellMultiplayer` (`@jgengine/
|
|
22
|
+
`resolveShellMultiplayer` (`@jgengine/core/multiplayer/multiplayer.draw`) is the ws counterpart — a dev host can try both in sequence (`resolveConvexMultiplayer(...) ?? resolveShellMultiplayer(...)`, see `apps/dev/src/main.tsx`) and pass whichever resolves to the shell.
|
|
23
23
|
|
|
24
24
|
Once a session resolves, `GamePlayerShell` wires it up with no game code changes: pose presence (`RemotePlayers`), `feedActions` (default `entity.died`) bridged both ways with echo suppression, and `global`-kind chat channels relayed through `chatSyncFor` (`whisper`/`party`/`proximity` stay local). Everything else in `GameContext` stays client-local unless the game also registers a server-side `GameRuntime`.
|
|
25
25
|
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import type { FunctionReference } from "convex/server";
|
|
2
|
-
import type { ChatMessage } from "@jgengine/core/
|
|
3
|
-
import type { ChatTransport } from "@jgengine/core/
|
|
2
|
+
import type { ChatMessage } from "@jgengine/core/chat/chat";
|
|
3
|
+
import type { ChatTransport } from "@jgengine/core/chat/chatContract";
|
|
4
4
|
export interface ConvexChatFunctions {
|
|
5
5
|
messages: FunctionReference<"query">;
|
|
6
6
|
sendMessage: FunctionReference<"mutation">;
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import type { ConvexReactClient } from "convex/react";
|
|
2
2
|
import type { FunctionReference } from "convex/server";
|
|
3
|
-
import { type SaveBackend } from "@jgengine/core/
|
|
3
|
+
import { type SaveBackend } from "@jgengine/core/save/saveStore";
|
|
4
4
|
/** The three Convex functions a save backend calls — a `query` that returns the stored string (or `null`) and two `mutation`s. Point them at your app's own module, or lean on the default `saves.*` convention. */
|
|
5
5
|
export interface ConvexSaveFunctions {
|
|
6
6
|
read: FunctionReference<"query", "public", {
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { anyApi } from "convex/server";
|
|
2
|
-
import { remoteSaveBackend } from "@jgengine/core/
|
|
2
|
+
import { remoteSaveBackend } from "@jgengine/core/save/saveStore";
|
|
3
3
|
/** Default `saves.read` / `saves.write` / `saves.remove` refs for apps that follow the convention — pass your own `functions` to override. */
|
|
4
4
|
export function defaultConvexSaveFunctions() {
|
|
5
5
|
return anyApi.saves;
|
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
import type { ConvexReactClient } from "convex/react";
|
|
2
2
|
import type { DefaultFunctionArgs, FunctionReference } from "convex/server";
|
|
3
3
|
import type { GameRuntimeFeeds, JoinServerOutcome, GameRuntimeTransport, PresencePoseRow, PresenceSync } from "@jgengine/core/runtime/transport";
|
|
4
|
-
import type { ChatMessage } from "@jgengine/core/
|
|
5
|
-
import type { ChatSendOutcome, ChatSync } from "@jgengine/core/
|
|
4
|
+
import type { ChatMessage } from "@jgengine/core/chat/chat";
|
|
5
|
+
import type { ChatSendOutcome, ChatSync } from "@jgengine/core/chat/chatContract";
|
|
6
6
|
import { type PoseSyncTuning } from "@jgengine/core/multiplayer/poseSyncGate";
|
|
7
|
-
import type { LeaderboardScope } from "@jgengine/core/
|
|
7
|
+
import type { LeaderboardScope } from "@jgengine/core/leaderboard/leaderboard";
|
|
8
8
|
/** Structural client seam avoids requiring the engine and game to share a class instance type. */
|
|
9
9
|
export type ConvexGameClient = Pick<ConvexReactClient, "mutation" | "watchQuery">;
|
|
10
10
|
export type ConvexGameTransportConfig = {
|
package/dist/server.d.ts
CHANGED
|
@@ -1,13 +1,14 @@
|
|
|
1
1
|
import type { Auth, FunctionReference, DataModelFromSchemaDefinition, DocumentByName, GenericMutationCtx, GenericQueryCtx } from "convex/server";
|
|
2
2
|
import type { GenericId } from "convex/values";
|
|
3
|
-
import { type ChatMessage } from "@jgengine/core/
|
|
4
|
-
import type { ChatSendOutcome } from "@jgengine/core/
|
|
3
|
+
import { type ChatMessage } from "@jgengine/core/chat/chat";
|
|
4
|
+
import type { ChatSendOutcome } from "@jgengine/core/chat/chatContract";
|
|
5
5
|
import { type PoseSyncRules } from "@jgengine/core/multiplayer/presenceModel";
|
|
6
|
-
import type { CommandScope } from "@jgengine/core/
|
|
6
|
+
import type { CommandScope } from "@jgengine/core/commands/commandRunner";
|
|
7
7
|
import type { GameRuntime } from "@jgengine/core/runtime/gameRuntime";
|
|
8
|
+
import type { RunCommandArgs } from "@jgengine/core/runtime/transport";
|
|
8
9
|
import type { LeaderboardIncrement } from "@jgengine/core/runtime/hostPersistence";
|
|
9
10
|
import { type MatchmakingMode } from "@jgengine/core/runtime/hostPolicy";
|
|
10
|
-
import type { SaveConfig } from "@jgengine/core/
|
|
11
|
+
import type { SaveConfig } from "@jgengine/core/save/saveConfig";
|
|
11
12
|
import type { GameRuntimeSnapshot, RuntimePlayerRow } from "@jgengine/core/runtime/snapshot";
|
|
12
13
|
/** @internal Re-export shared host-policy pure helpers so existing convex imports keep working. */
|
|
13
14
|
export { JG_MAX_MEMBERS_PER_SERVER, canJoinPrivateServer, isListablePublicly, validateSlotsPerServer, type MatchmakingMode, } from "@jgengine/core/runtime/hostPolicy";
|
|
@@ -581,8 +582,6 @@ export declare function resolveActor(ctx: {
|
|
|
581
582
|
}, claimedExternalId: string | undefined, mode: JgAuthMode): Promise<string | null>;
|
|
582
583
|
/** Most live client sessions one shared-topology membership remembers; the oldest drop first. */
|
|
583
584
|
export declare const MAX_MEMBER_SESSIONS = 16;
|
|
584
|
-
/** Longest accepted client `sessionId`. */
|
|
585
|
-
export declare const MAX_SESSION_ID_LENGTH = 128;
|
|
586
585
|
/**
|
|
587
586
|
* How much of a server to hydrate. Both fields default to "everything", which is a document read per
|
|
588
587
|
* member plus one per chunk — the cost that makes a large shared world unaffordable per mutation.
|
|
@@ -626,10 +625,7 @@ export type RunCommandOutcome = {
|
|
|
626
625
|
reason: string;
|
|
627
626
|
};
|
|
628
627
|
/** Arguments of the `runCommand` mutation, and of the equivalent {@link GameServerHelpers.runCommand} helper. */
|
|
629
|
-
export type
|
|
630
|
-
serverId: string;
|
|
631
|
-
command: string;
|
|
632
|
-
input: unknown;
|
|
628
|
+
export type ConvexRunCommandArgs = RunCommandArgs & {
|
|
633
629
|
externalId?: string;
|
|
634
630
|
/**
|
|
635
631
|
* How much of the server to hydrate before applying the command. Overrides whatever the
|
|
@@ -687,7 +683,7 @@ export type GameServerHelpers = {
|
|
|
687
683
|
* Apply a command as the actor. Refuses non-members with "Not a member of this server"; host
|
|
688
684
|
* mutations acting for a user who might have no live session call {@link ensureJoined} first.
|
|
689
685
|
*/
|
|
690
|
-
runCommand: (ctx: JGMutationCtx, args:
|
|
686
|
+
runCommand: (ctx: JGMutationCtx, args: ConvexRunCommandArgs) => Promise<RunCommandOutcome>;
|
|
691
687
|
/**
|
|
692
688
|
* Join a trusted, already-authorized actor through the same path as the `joinServer` mutation.
|
|
693
689
|
* Idempotent: an existing member of `serverId` returns at once. Pins no client session, so it never
|
package/dist/server.js
CHANGED
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
import { defineSchema, defineTable, internalMutationGeneric, mutationGeneric, queryGeneric, } from "convex/server";
|
|
2
2
|
import { ConvexError, v } from "convex/values";
|
|
3
|
-
import { validateChatMessage } from "@jgengine/core/
|
|
3
|
+
import { validateChatMessage } from "@jgengine/core/chat/chat";
|
|
4
4
|
import { createFeedWriteGate, validateFeedWrite, } from "@jgengine/core/multiplayer/feedWriteGate";
|
|
5
5
|
import { DEFAULT_POSE_SYNC_RULES, decidePoseSync, isPresenceExpired, pickReusablePresence, resolveActivePresence, spawnPresenceState, } from "@jgengine/core/multiplayer/presenceModel";
|
|
6
|
-
import { SERVER_ONLY_COMMAND_REASON, commandScopeEscape, scopeWithActor } from "@jgengine/core/
|
|
6
|
+
import { SERVER_ONLY_COMMAND_REASON, commandScopeEscape, scopeWithActor } from "@jgengine/core/commands/commandRunner";
|
|
7
7
|
import { createGameRuntime, initialPlayerState } from "@jgengine/core/runtime/gameRuntime";
|
|
8
8
|
import { buildHydratePlayers, planServerPersist, shouldAutoSave, trimFeedEntries, } from "@jgengine/core/runtime/hostPersistence";
|
|
9
|
-
import { JG_MAX_MEMBERS_PER_SERVER, isListablePublicly, isPrivateJoinBlocked, isServerFull, isServerMember, selectJoinTarget, statusAfterLeave, validateSlotsPerServer, withJoinedMember, withoutMember, } from "@jgengine/core/runtime/hostPolicy";
|
|
9
|
+
import { JG_MAX_MEMBERS_PER_SERVER, isListablePublicly, isPrivateJoinBlocked, isServerFull, isServerMember, selectJoinTarget, statusAfterLeave, validateSlotsPerServer, withJoinedMember, withoutMember, MAX_SESSION_ID_LENGTH, } from "@jgengine/core/runtime/hostPolicy";
|
|
10
10
|
import { createEmptyServerRow } from "@jgengine/core/runtime/snapshot";
|
|
11
11
|
import { chunkKeyAt, parseChunkKey, chunkKeysAround, DEFAULT_CHUNK_SIZE } from "@jgengine/core/runtime/worldChunks";
|
|
12
12
|
import { applyCommandWithOcc, commitIfRevisionMatch } from "./occ.js";
|
|
@@ -203,8 +203,6 @@ async function memberRow(ctx, serverId, userId) {
|
|
|
203
203
|
}
|
|
204
204
|
/** Most live client sessions one shared-topology membership remembers; the oldest drop first. */
|
|
205
205
|
export const MAX_MEMBER_SESSIONS = 16;
|
|
206
|
-
/** Longest accepted client `sessionId`. */
|
|
207
|
-
export const MAX_SESSION_ID_LENGTH = 128;
|
|
208
206
|
function withSession(sessionIds, sessionId) {
|
|
209
207
|
return [...(sessionIds ?? []).filter((id) => id !== sessionId), sessionId].slice(-MAX_MEMBER_SESSIONS);
|
|
210
208
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@jgengine/convex",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.46.0",
|
|
4
4
|
"description": "Convex adapters for JGengine: game transport, presence transport, and backend wiring over @jgengine/core.",
|
|
5
5
|
"license": "Apache-2.0",
|
|
6
6
|
"type": "module",
|
|
@@ -30,16 +30,16 @@
|
|
|
30
30
|
"check-types": "tsgo --noEmit -p tsconfig.json"
|
|
31
31
|
},
|
|
32
32
|
"dependencies": {
|
|
33
|
-
"@jgengine/core": "^0.
|
|
33
|
+
"@jgengine/core": "^0.46.0"
|
|
34
34
|
},
|
|
35
35
|
"peerDependencies": {
|
|
36
36
|
"convex": "^1.31.2",
|
|
37
37
|
"react": "^19.0.0"
|
|
38
38
|
},
|
|
39
39
|
"devDependencies": {
|
|
40
|
-
"convex": "
|
|
41
|
-
"react": "
|
|
42
|
-
"@types/react": "
|
|
40
|
+
"convex": "catalog:",
|
|
41
|
+
"react": "catalog:",
|
|
42
|
+
"@types/react": "catalog:"
|
|
43
43
|
},
|
|
44
44
|
"publishConfig": {
|
|
45
45
|
"access": "public"
|
|
@@ -26,7 +26,7 @@ Create one immutable handoff at the destination the user requested, or use the m
|
|
|
26
26
|
|
|
27
27
|
1. Distill the current objective and the user's latest intent. If a focus was supplied, make it the `resume_focus`.
|
|
28
28
|
2. Inspect only the workspace state needed to explain what exists now. Use JGengine project instructions already in context (`AGENTS.md`, `workflow`, domain skills).
|
|
29
|
-
3. Point to plans, issues, commits, diffs, documentation, and relevant files instead of reproducing their contents. Prefer repository-relative paths (`docs/plans/…`, `packages/…`, `
|
|
29
|
+
3. Point to plans, issues, commits, diffs, documentation, and relevant files instead of reproducing their contents. Prefer repository-relative paths (`docs/plans/…`, `packages/…`, `games/…`).
|
|
30
30
|
4. Redact secrets, credentials, tokens, and unrelated personal information. Preserve operational paths only when the next agent needs them.
|
|
31
31
|
5. Write or publish with existing tools. If the user named another path, folder, format, or publication destination, honor it. Do not also create a managed-store copy unless asked.
|
|
32
32
|
|
package/skills/jgengine/SKILL.md
CHANGED
|
@@ -38,11 +38,11 @@ Treat the pitch as a unique composition of needs, not a genre to fill in — nev
|
|
|
38
38
|
- HUD/menu states and **custom** UI art direction (compose shipped building blocks + HudTheme; no unskinned stock face)
|
|
39
39
|
- one observable completion scenario
|
|
40
40
|
|
|
41
|
-
Pick the dimension first: `create` defaults to a 3D world (terrain, sky, player model, editor scene); a grid, card, tile, or other board game with no 3D scene takes `create --2d` (a `board` world drawn by GameUI over `@jgengine/core/puzzle/cellGrid`).
|
|
41
|
+
Pick the dimension first: `create` defaults to a 3D world (terrain, sky, player model, editor scene); a grid, card, tile, or other board game with no 3D scene takes `create --template 2d` (a `board` world drawn by GameUI over `@jgengine/core/puzzle/cellGrid`). `create --template 3d|2d|builder` is the one way to start a game, in this monorepo too (`bun run jgengine create "<Title>"` lands in `games/`); never copy `games/*`. The game's `jgengine.json` says what it is and uses (template, editor, `@jgengine` packages, and in-repo its capability answers). Change what a game uses by editing it, then run `jgengine sync`, which also brings a game up to the running engine: its `@jgengine` versions and its template's shared files (wiring, page, tooling scripts, AGENTS.md). A shared file the game rewrote goes in its `owns` list so sync leaves it alone.
|
|
42
42
|
|
|
43
|
-
For an existing game asked to pick up a new engine release ("jgengine published a new version, grab it"), run `npx jgengine
|
|
43
|
+
For an existing game asked to pick up a new engine release ("jgengine published a new version, grab it"), run `npx jgengine@latest sync` in the project (or at its workspace root): it moves every `@jgengine` pin and catalog entry to that release, refreshes the template's files, and prints every Migrate step and Adopt-worthy addition in between. Install, migrate oldest-first, then work the Adopt lists instead of reconstructing the changelog by hand.
|
|
44
44
|
|
|
45
|
-
`doctor`
|
|
45
|
+
`doctor` resolves Bun `catalog:` and `catalog:<name>` declarations from the nearest workspace's `workspaces.catalog` or `workspaces.catalogs` and follows hoisted SDK installs; missing or malformed catalog entries fail diagnostics. `sync` at a workspace root moves the catalog pins (preserving per-game `catalog:` declarations, CLI/github versions and a held editor override) and changes declarations only: run `bun install` at the root, then `jgengine doctor --workspace --json` once there.
|
|
46
46
|
|
|
47
47
|
When a linked consumer renders differently despite matching root SDK versions, `doctor` checks direct consumers and SDK dependency/peer paths, including private copies with the same version. Review the reported importer and real paths, align the shared catalog and linked-consumer declarations, then reinstall at the root and rerun `doctor --workspace`; a root install may leave private linked-package installs intact. Restart running games after reinstall. Workspace mode checks installed identities only, reports coverage, and refuses unsupported patterns, escaped symlinks or exhausted traversal budgets. The default project audit stays scoped; neither mode covers custom bundler aliases or indirect non-SDK chains.
|
|
48
48
|
|
|
@@ -70,10 +70,10 @@ World content routes editor-first: any request that adds, moves, restyles, or re
|
|
|
70
70
|
Within each selected domain:
|
|
71
71
|
|
|
72
72
|
0. Fastest start for a common composition: `npx jgengine recipe <name>` prints a vetted, SDK-typechecked snippet (imports + wired code) (`combat-loop`, `loot`, `world-drops`, `click-target`, `ability-bar`, `hitscan-weapon`, `rolled-gear`, `enter-vehicle` and more); `npx jgengine recipe` lists them. Copy that instead of reconning per-file.
|
|
73
|
-
1. Search capabilities by intent; do not read `capabilities.md` top to bottom. `npx jgengine find <intent>` searches every domain's index and every recipe at once,
|
|
73
|
+
1. Search capabilities by intent; do not read `capabilities.md` top to bottom. `npx jgengine find <intent>` names the concept's home folder in each layer (read from the engine source or the installed packages), searches every domain's index and every recipe at once, and prints the primitive and its import (or the recipe to open) and matching assets; agents can use `--json --limit 5` for ranked imports and recipes with total, partial-match, and truncation fields. In the monorepo, `rg -i "<intent>" .claude/skills/*/capabilities.md` does the same search. Search the moment you are tempted to hand-roll a HUD window, inventory grid, paperdoll, hotkey listener, minimap, stat bar, or character motion. A `z-index`, a `keydown` for a panel, a `<div>` inventory, or a limb bob almost certainly has a shipped drop-in (`usePanels`/`PanelHost`, `InventoryGrid`, `CharacterSheet`/`Paperdoll`, `PartMotionRig`, `EntityPreview`); games own the *skin*, not the re-derivation.
|
|
74
74
|
2. Open `api.md` (monorepo) or the package `.d.ts` only when exact signatures are needed; grep it, never read it whole.
|
|
75
75
|
3. Open the linked reference only for a deeper recipe or trap.
|
|
76
|
-
4. For how several primitives wire into a running loop, read the domain's `recipes/` — connected, genre-free walkthroughs organized by composition seam; this skill's own [recipes/minimal-game.md](recipes/minimal-game.md) is the default whole-game walkthrough. Prefer a recipe over reading a game's source; the games under `
|
|
76
|
+
4. For how several primitives wire into a running loop, read the domain's `recipes/` — connected, genre-free walkthroughs organized by composition seam; this skill's own [recipes/minimal-game.md](recipes/minimal-game.md) is the default whole-game walkthrough. Prefer a recipe over reading a game's source; the games under `games/*` are not templates or references, and a hybrid game is just a different composition of the same primitives.
|
|
77
77
|
|
|
78
78
|
If no capability fits, identify the upstream package seam before writing game-local code. In this repository, implement a reusable primitive with its first adopter and a reviewed consumer cleanup; coordinate a differently composed second consumer when applicable. Recipes wire SDK APIs, not copied implementations. If editor-owned content cannot be expressed, file the editor gap first; track any fallback and its removal path.
|
|
79
79
|
|