@jgengine/convex 0.49.1 → 0.50.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 +158 -0
- package/dist/createConvexBackend.d.ts +2 -2
- package/dist/createConvexGameTransport.d.ts +10 -10
- package/dist/hostedServer.d.ts +10 -10
- package/dist/resolveConvexMultiplayer.d.ts +1 -1
- package/dist/server.d.ts +103 -102
- package/dist/server.js +18 -16
- package/dist/worldPresence.d.ts +1 -1
- package/dist/worldPresence.js +2 -1
- package/package.json +2 -2
- package/skills/jgengine/SKILL.md +3 -3
- package/skills/jgengine/api.md +5 -7
- package/skills/jgengine-multiplayer/api.md +12 -12
package/CHANGELOG.md
CHANGED
|
@@ -26,6 +26,164 @@ At publish, rename this heading to the new version and mirror the entries into
|
|
|
26
26
|
`packages/core/src/meta/changelog.ts` (the typed `CHANGELOG` export).
|
|
27
27
|
-->
|
|
28
28
|
|
|
29
|
+
## 0.50.0
|
|
30
|
+
|
|
31
|
+
### Migrate
|
|
32
|
+
|
|
33
|
+
- **Bump lockstep SDK packages to `^0.50.0`:** `@jgengine/{core,rapier,react,ws,node,sql,convex,shell,editor,assets,navbake}`. CLI `jgengine` is `0.47.0`; `@jgengine/github` is `0.37.0`.
|
|
34
|
+
- declare the tour's steps in createOnboardingSystem({ steps: [{ id, text, detail, anchor, placement, done: { next: true } }] }) and mount <OnboardingCallout />; a coach-mark condition becomes done: { when } or { event }
|
|
35
|
+
|
|
36
|
+
### Added
|
|
37
|
+
|
|
38
|
+
- An action's bindings take `locked: true`: players can't rebind it, saved overrides for it are ignored, and controls menus show it read-only. `isActionLocked(codes)` reads the flag.
|
|
39
|
+
- `persist.storage` in `defineGame` also takes any `SaveBackend`, such as `createConvexSaveBackend`, so a solo game's whole-world save can go to the cloud.
|
|
40
|
+
- Brightway Park design brief: core loop, session goal, long loop, second 0, the first 10 minutes and a content budget
|
|
41
|
+
- `OnboardingCallout` (`@jgengine/core/onboarding/onboardingCallout.view`): The onboarding's UI callout, for steps about the interface rather than the world: a step with an `anchor` (a CSS selector) gets a card beside that element with a spotlight around it, and a step that finishes on Next with no anchor gets a centred card. Also `OnboardingCalloutCard`, `OnboardingCalloutCardProps`, `OnboardingCalloutProps`, `OnboardingCalloutTheme`.
|
|
42
|
+
- `OnboardingCallout` (`@jgengine/react`): The onboarding's UI callout, for steps about the interface rather than the world: a step with an `anchor` (a CSS selector) gets a card beside that element with a spotlight around it, and a step that finishes on Next with no anchor gets a centred card. Also `OnboardingCalloutCard`, `OnboardingCalloutCardProps`, `OnboardingCalloutProps`, `OnboardingCalloutTheme`.
|
|
43
|
+
- Fernwild's Survivor's path takes a new survivor from the first tree to the Broodmother in 23 steps, with a star on the map at each target.
|
|
44
|
+
- Fernwild taming works from a new game with no cheats: punch a dodo out and feed it berries, club a phiomia and keep it under with narcotics. A club hit now lands its full torpor.
|
|
45
|
+
- Fernwild has 21 explorer notes, a Dossier tab of species met, tamed and notes read, and an always-on map baked from the island's ground with notes, obelisks, caves and tames marked.
|
|
46
|
+
- Fernwild danger rises with distance (wild levels capped at 5 by the beach, the full 30 from 1250 m), and the start beach holds only grazers.
|
|
47
|
+
- createSpoilage in @jgengine/core/inventory/spoilage; Fernwild meat spoils into spoiled meat and the timers survive a save.
|
|
48
|
+
- an onboarding step can set inArea: false so a teaching-area pointer sends the player past the area.
|
|
49
|
+
- Fernwild's PLAN.md design brief compares it with ARK beat by beat, and its from-zero proof plays 19 minutes from a new game; Fernwild is off the depth known-failing list.
|
|
50
|
+
- `createSpoilage` (`@jgengine/core/inventory/spoilage`): Perishables in an inventory, as ARK keeps them: each stack runs one timer, and every time it runs out one unit spoils and the timer starts again, so a big stack lasts longer than a small one. Also `SpoilRule`, `Spoilage`, `SpoiledUnits`, `SpoilingStack`.
|
|
51
|
+
- every game's PLAN.md carries a design brief (core, session and long loops, second 0, the first 10 minutes beat by beat, content budget, systems, onboarding); check games-design fails a game without a complete one
|
|
52
|
+
- games/scripts/depth-known-failing.json lists the 18 games that fail the brief, from-zero and depth rules today, with why; the list only shrinks
|
|
53
|
+
- every game has a from-zero proof (proofs/flows/from-zero.flow.ts) that boots a new game with no prepared state or save and plays its first 10 minutes through real input; it fails a start beyond the brief's second 0, nothing taught or unlocked by minute 2, or new things stopping at the brief's pace
|
|
54
|
+
- check games-depth plays that proof and measures systems touched, time to first unlock, unlocks per 10 minutes, content against the budget, the longest cycle and minutes of content; it blocks landing, and today's games are on the shrink-only known-failing list
|
|
55
|
+
- a game declares its new-game start as data (defineGame newGame: cash, kit, unlocked items, prebuilt pieces, systems that start running). The engine builds every new game from that declaration only, refuses start items handed out anywhere else, and exposes it on the definition for checks to read
|
|
56
|
+
- Fernwild declares its start: empty-handed, knowing only the Stone Pick and Torch engrams
|
|
57
|
+
- `NewGameConfig` (`@jgengine/core/game/newGame`): A game's new-game declaration, passed as `defineGame({ newGame })`: the {@link NewGameState} plus the hooks that apply its game-owned parts. Also `NewGameKit`, `NewGamePiece`, `NewGameState`.
|
|
58
|
+
- onboarding as data (createOnboardingSystem). A game declares its steps; each has text, can point at an entity, the nearest thing of a kind or a spot (on-screen pin or edge arrow, minimap marker, waypoint), completes from a game event, a condition, a measured amount or a Next press, can lock commands until done, and grants items, xp, currency or unlocks. Three styles: a separate tutorial level, a teaching start area, or hints blended into stage one. Skippable, saved per player, shown in the quest tracker and objective banner (OnboardingHud, OnboardingBanner, OnboardingPointer, onboardingLock)
|
|
59
|
+
- ctx.game.commands.gate, one place to refuse commands before they run
|
|
60
|
+
- Fernwild's first day: punch a tree for thatch, gather stone, craft a Stone Pick, tame a dodo, each pointing at the nearest tree, stones or dodo with an arrow and a minimap star; engram learning waits for the pick
|
|
61
|
+
- `Onboarding` (`@jgengine/core/onboarding/onboarding`): One player's run through an {@link OnboardingConfig}. Also `OnboardingConfig`, `OnboardingDone`, `OnboardingPoint`, `OnboardingSnapshot`, `OnboardingStep`, `OnboardingStepView`, `OnboardingStyle`, `OnboardingTarget`.
|
|
62
|
+
- `OnboardingBanner` (`@jgengine/core/onboarding/onboarding.view`): The step-done banner ("Gather stone" / "Next: Craft a Stone Pick") through `ObjectiveBannerHost`, near the top of the screen. Also `OnboardingHud`, `OnboardingHudProps`, `useOnboarding`.
|
|
63
|
+
- `OnboardingPointer` (`@jgengine/core/onboarding/OnboardingPointer.draw`): The onboarding's on-screen pointer, mounted in a game's `WorldOverlay`: a pin over the current step's target while it is in view, an edge arrow pointing the way with the distance while it is not (`WaypointMarkers` over the onboarding's waypoint).
|
|
64
|
+
- `ONBOARDING_MARK` (`@jgengine/core/onboarding/onboardingSystem`): The id of the waypoint and minimap marker the onboarding owns. Also `OnboardingRuntime`, `OnboardingSystemOptions`, `createOnboardingSystem`, `onboardingLock`, `onboardingOf`.
|
|
65
|
+
- repeat targeted tests under concurrent load with complete failure evidence
|
|
66
|
+
- A from-zero proof plays a new Scrap Signal game for ten minutes through real input: Bolt's quests one at a time, the crash chest's pistol, the whelps, the convoy's bruisers and red crate, Old Tusk and his whelps, the road to Breakwater Shelf and Captain Rusk. Scrap Signal is off the depth known-failing list for both from-zero and depth.
|
|
67
|
+
- Camp enemies carry four times their listed health and shield (one dial); bosses and Overdrive crews keep their own numbers; cleared camps refill after four minutes away.
|
|
68
|
+
- Ammo crates that restock, ammo packs sized to a camp fight, and kills that always drop ammo while you are nearly dry.
|
|
69
|
+
- Named targets behind side quests and quest-reward uniques.
|
|
70
|
+
- browser proof flows can move the mouse, look and aim with real pointer input again (flow.moveMouse, flow.look, flow.aimAt), restoring the pointer-lock look and crosshair aim lost when the JSON drive steps were replaced
|
|
71
|
+
- `FLOW_AIM_TOLERANCE_PX` (`@jgengine/core/play/flow`): How close {@link BrowserFlow.aimAt} must bring the target to the crosshair, in CSS pixels.
|
|
72
|
+
- withField(row, key, value) in @jgengine/core/data/withField sets an optional field, or leaves it out when value is undefined, so a cleared field is never stored as key: undefined.
|
|
73
|
+
- `definedOnly` (`@jgengine/core/data/definedOnly`): `value` without its undefined fields, so `{ ...defaults, ...definedOnly(overrides) }` never erases a default with an explicit `undefined`, and a record built from optional inputs can be written where an optional field must be absent rather than `undefined`. Also `DefinedOnly`.
|
|
74
|
+
- `withField` (`@jgengine/core/data/withField`): A copy of `row` with the optional field `key` set to `value`, or left out when `value` is `undefined`. Also `OptionalKey`.
|
|
75
|
+
|
|
76
|
+
### Changed
|
|
77
|
+
|
|
78
|
+
- AGENTS.md settles two rules: an agent's own scratch files go in the session scratchpad (.scratch/ is only tool output), and a wait under 10 minutes runs in the foreground with a timeout while only dev servers and game launches run in the background
|
|
79
|
+
- Brightway Park keeps monthly books: eight-day months from 1 March, a report of every line (gate, rides, shops, awards, borrowing, wages, upkeep, restocking, repairs, research, marketing, interest, building), loan interest at each month's close, two red month-ends in a row close the park, and three marketing campaigns that bring more guests while they run
|
|
80
|
+
- Brightway Park has a design brief in its PLAN.md and a from-zero proof that plays the first 10 minutes of a new game
|
|
81
|
+
- Brightway Park's probe reports taught, unlocks, each content kind's seen and total, and one reading per system
|
|
82
|
+
- Brightway Park's lab opens with four short topics and the first-scenario hints now teach the lab, so the first unlock lands inside two minutes
|
|
83
|
+
- A new Brightway ride or shop that is still shut opens its window, where Connect it waits
|
|
84
|
+
- Brightway Park leaves the depth known-failing list
|
|
85
|
+
- Brightway Park restarts its calendar for every new park or scenario: 09:00 on 1 March, day one, one midnight a day, even when a scenario is started or replayed mid-way through another park
|
|
86
|
+
- Brightway Park's midnight uses the engine clock's cancel handle, so re-arming it after a reset cancels the old timer
|
|
87
|
+
- Brightway Park starts on an empty lot: a gate plaza, the gate shut, no guests until the player builds a path and a ride and opens the gate
|
|
88
|
+
- Guests trickle in and ramp with rating and appeal; the rating follows the park over the day instead of in seconds
|
|
89
|
+
- The open-park and staff proofs play from the empty lot by mouse and keyboard
|
|
90
|
+
- Brightway Park scenarios run for months: a new game starts Brightway Beginnings on the empty lot (120 guests and 400 stars by the end of May, Year 1), Star Attraction runs the whole first season and Full House two; each is won the moment its targets hold, unlocks the next, and the HUD shows days left; time runs at up to 8x
|
|
91
|
+
- Brightway Park's first scenario teaches itself: a card says the next step and rings the button that does it, with an arrow over the spot on the lot (lay a path from the gate, build a ride, connect it, open the gate), and at the first breakdown it says to hire a mechanic from Staff; a new proof plays from the empty lot to a natural breakdown and the hired mechanic fixing it
|
|
92
|
+
- Brightway Park's README states the award range and what the balance bot really measures, and jgengine.json describes the loan the park actually has
|
|
93
|
+
- Brightway's balance bot plays a new game the way a player starts one: the first scenario, the gate opened, a mechanic hired, and it prints how the scenario stands
|
|
94
|
+
- Brightway Park rides stay down until a mechanic fixes them: mechanics are hireable from the start and go straight to broken rides, the ride window shows reliability for the next run, the lab takes five times longer (34 topics over about thirteen park months, Dodgems now a topic), and litter bins catch litter
|
|
95
|
+
- Brightway Park's hint cards now run on the engine's onboarding system, keep up while the game is paused, and have a Hide hints button
|
|
96
|
+
- Brightway Park scenarios run for months: a new game starts Brightway Beginnings on the empty lot (120 guests and 400 stars by the end of May, Year 1), Star Attraction runs the whole first season and Full House two; each is won the moment its targets hold, unlocks the next, and the HUD shows days left; time runs at up to 8x
|
|
97
|
+
- drive's command line and help move to scripts/driveArgs.ts and its lockstep recorder to scripts/driveLockstep.ts, so drive-dev.ts is the run alone (1074 to 684 lines); the move drops four type casts
|
|
98
|
+
- New create-game-jg skill: a new game starts by agreeing its direction with the user (references, how faithful, breadth, look, scale, players, platforms, what done means), mapping every feature with one jgengine has call, and writing PLAN.md before jgengine create
|
|
99
|
+
- bun run drive prints the result, its warnings and failures by default; --verbose adds the progress, reuse and timing lines, and the GPU driver's shader precision warnings move there too
|
|
100
|
+
- drive --step: try flow calls on a live game page, with closest-match failures A --step runs one browser-flow call on a page of the game kept running (paused between commands) in the warm browser, so trying a click, wait or check answers in seconds instead of a full proof run. A failed click names the closest controls and whether one is covered; a failed probe check names missing probe keys and the closest real ones; see() names the closest page text; a misspelt f.call names the closest calls. The page work moves out of drive-dev.ts into drivePage.ts, shared by --flow --browser and --step. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
|
|
101
|
+
- Landing fails fast: lane:close runs bun run preflight (install, gen, type-checks and gates) first and stops at the first failure, before any test runs
|
|
102
|
+
- bun run preflight is that cheap check on its own; preflight --ship is gone
|
|
103
|
+
- verify:fast starts type-checks and gates before tests and stops starting tests after a failure; --cheap runs only the type-checks and gates
|
|
104
|
+
- bun run gen runs independent generators side by side, and gen barrels reads core's surface once instead of five times
|
|
105
|
+
- lane:close starts the queue for its PR in the background and names the one command that prints the outcome
|
|
106
|
+
- Flaky tests fixed: folder deletes retry on Windows (Bun ignores rm's maxRetries), ws grace-window tests use hand-fired timers, the CDP deadline test waits for the server's message, and scrap-signal's audio test restores fetch instead of deleting it
|
|
107
|
+
- game-making skills start from the PLAN.md design brief: milestones are the next 10 minutes of play proven by the from-zero flow, the greenfield contract folds into the brief, and the first tutorial step no longer has to be doable from starting inventory
|
|
108
|
+
- create-game-jg description back under the 15-word skill limit
|
|
109
|
+
- Changelog: locked bindings and SaveBackend storage
|
|
110
|
+
- Faster frames in every game: half-resolution ambient occlusion, a matrix pass that skips what stood still, and a cheaper per-frame scene walk
|
|
111
|
+
- Ambient occlusion and its denoise run on a quarter of the pixels; at 1080p that cut its GPU time from about 2-3.8 ms to 0.6-1.6 ms in every game that uses it, with no visible change in a side-by-side shot.
|
|
112
|
+
- The scene's matrix pass rebuilds only nodes whose position, rotation, scale, parent or world matrix changed; the shader warmup and other traverseVisible callers use a stack walk (visitVisible), 1.4 ms -> 0.12 ms on fernwild.
|
|
113
|
+
- Dev telemetry reads the GPU name once instead of every second; bun run perf names the engine or game function behind each hot three.js function, and a compile hitch names the draw that waited.
|
|
114
|
+
- bun run perf takes several games in one browser and ends with a table per game; a clean-tree run is cached per commit so --compare HEAD measures only the after side; the process exits once results are written.
|
|
115
|
+
- shoot, verify:fast, preflight and lane:close print only results, warnings, failures and summaries by default; --verbose prints progress, reuse, timing and every pass
|
|
116
|
+
- release --wait: wait on npm serving every package, not on the run's 5-minute check 0.49.0's core reached npm 7m12s after upload, 24 s after publish.yml's 300 s check gave up; release --wait reported a failure and a second release followed (~50 min). release --wait now polls the registry for every lockstep package (bounded at 20 minutes) instead of one npm view of core, and a run that failed only at its registry check keeps waiting on npm and then runs the same artifact check locally. publish.yml's check budget goes from 300 s to 900 s because 300 s was measured too short. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
|
|
117
|
+
- The Rustflat Front's crews wake only after Old Tusk is down; Old Tusk has 2000 health, Captain Rusk 1800 plus a 400 shield.
|
|
118
|
+
- Bruiser Bash now ends at the convoy's red crate, and Old Tusk's quest starts by thinning his whelps.
|
|
119
|
+
- Games: the six strict compiler flags (`noUncheckedIndexedAccess`, `exactOptionalPropertyTypes`, `noImplicitOverride`, `noPropertyAccessFromIndexSignature`, `noImplicitReturns`, `noFallthroughCasesInSwitch`) are now on for every in-repo game through `games/tsconfig.base.json`, and in llmville's own tsconfig. Each game's `check-types` (run by CI) now enforces them, so new strict errors cannot come back.
|
|
120
|
+
- ember-command, drift-foundry and brightway-park now pass the strictest type settings. Their catalogs name their ids, so a mistyped unit, building or research id is a type error, and save loading reads old saves without casts.
|
|
121
|
+
- New `@jgengine/core/data/untyped` (`isRecord`, `isFiniteNumber`, `recordOf`) narrows parsed JSON and old saves without casts. New `slotText`/`slotNumber` in `@jgengine/core/save/saveSlots` read a save slot's chips.
|
|
122
|
+
- `ModelConfig.dims`, `ModelBodySource.dims`, `DrivableVehicleOptions.modifiers`, `BuildingSurfaceMaterial.roughness`/`metalness` and the kit objective's `steps`/`deadline` accept an explicit `undefined`.
|
|
123
|
+
- scrap-signal now type-checks clean under the strict flags (unchecked index access, exact optional properties, no index-signature dot access): save reads go through named locals, optional fields are passed only when set, and catalog lookups say when a key can be missing.
|
|
124
|
+
- The shared legacy-save reader reads the envelope by key.
|
|
125
|
+
- harbor-heat now type-checks clean under the strict flags (unchecked index access, exact optional properties, no index-signature dot access).
|
|
126
|
+
- harbor-heat reads scene meta through the engine's typed meta readers, so a wrong value in the scene file fails naming the marker instead of silently falling back to a default.
|
|
127
|
+
- harbor-heat's save reader uses the engine's shared record check instead of its own copy, and drops casts it no longer needs.
|
|
128
|
+
- deepward now type-checks clean under the strict flags (unchecked index access, exact optional properties, no index-signature dot access).
|
|
129
|
+
- deepward's save reader checks stored fields through named raw shapes and the engine's shared record check, instead of reading an open record by dot.
|
|
130
|
+
- deepward's probe now reports its own named readings, so its proof flows are checked against the real probe keys instead of any string.
|
|
131
|
+
- odd-orbit now type-checks clean under the strict flags (unchecked index access, exact optional properties, no index-signature dot access).
|
|
132
|
+
- odd-orbit's save checker names every stored field it reads, so a misspelled field is a type error instead of a silent `undefined`.
|
|
133
|
+
- A career rung with no pay value now fails loudly instead of paying `NaN`.
|
|
134
|
+
- New engine helper `definedFields` (`@jgengine/core/data/definedFields`): passes maybe-missing values on as optional fields, leaving out the undefined ones, so code stays correct under exact optional property types.
|
|
135
|
+
- llmville: first pass toward the strict type flags (517 errors down to 303). Builder session, agent asset requests, dev endpoints, generated assets and rack commands read untyped input through the engine's record check instead of dot access; map decor and procedural props pass optional transforms through `definedFields`.
|
|
136
|
+
- llmville: the game package, Convex backend, dev-server bridges and scripts now pass the strict compiler flags (strict-flag errors 303 → 173; the rest are in `src/`).
|
|
137
|
+
- llmville: command and save readers use bracket reads, optional fields that can be unset say so with `| undefined`, and Convex calls only pass optional fields that are set.
|
|
138
|
+
- llmville: the whole game now passes the strict compiler flags (strict-flag errors 173 → 0).
|
|
139
|
+
- llmville: component props and view-model fields that can be unset say so with `| undefined`; list reads check for a missing entry; fixed lists (avatars, builder materials, room templates) are typed as never empty.
|
|
140
|
+
- llmville: the error-report payload only sends fields that are set, and the toast theme no longer needs a cast.
|
|
141
|
+
- Nine games (apex-nights, beacon-bastion, briarcrown, hold-the-line, orbit-watch, outpost-zero, red-frontier, saltwick, the-sunken-crown) now pass the strictest type settings, ahead of those settings turning on for every game.
|
|
142
|
+
- Games read a placed object's authored settings through `metaNumber`, `metaText` and `requireMetaText` (`@jgengine/core/editor/markerMeta`), which fail loudly on a wrong value instead of casting it.
|
|
143
|
+
- `CompassProps.center`, the kit vitals objective's `id`/`goal`, and kit buttons' `cue` accept an explicit `undefined`.
|
|
144
|
+
- Every engine package (assets, convex, editor, github, jgengine, navbake, node, rapier, react, sql) now type-checks with the strict flags core, shell and ws already had. 400 errors fixed with guards and real types, no new casts.
|
|
145
|
+
- definedOnly in @jgengine/core/data/definedOnly is public, and its result keeps always-defined fields required; only fields that may be undefined become optional.
|
|
146
|
+
- Scripts, the UI registry, the dev, web and desktop apps and the host examples now type-check with the same strict flags as the engine packages. About 1000 errors fixed with guards and real types, no new casts.
|
|
147
|
+
- optional fields on registry slot props (ActionBarSlot, ItemGridSlot, BuffChip), EditorAppProps, the ws host's listOpenServers args and a few core option types accept an explicit undefined.
|
|
148
|
+
|
|
149
|
+
### Fixed
|
|
150
|
+
|
|
151
|
+
- Beacon Bastion's pacing test counts only gold the keep could still have spent: the bounty from the last kill, paid after the final chance to buy, no longer fails a keep that spent everything it could. A keep that stops buying still fails the test.
|
|
152
|
+
- Brightway Park's status card no longer overlaps the goal card at 1600x900; it keeps a steady height as guest thoughts come and go, with mood and cleanliness side by side
|
|
153
|
+
- Brightway's goal hint no longer says to lay a path when the only ride is broken down
|
|
154
|
+
- Fernwild's Harvest Stones prompt no longer shows when you aim at a tree.
|
|
155
|
+
- Fernwild's dehydration test steps whole seconds so it finishes inside CI's test timeout.
|
|
156
|
+
- Fernwild no longer freezes for about a second when you press Play: the island's 33k trees and rocks are placed while the title loads (worst click 940 ms -> 510 ms, menu shows ~0.5 s later)
|
|
157
|
+
- drive --playtest judges a softlock only after the player first acts; a quiet title or idle start no longer counts
|
|
158
|
+
- the capability boot probe presses Play (the game's capture.play) and gives a system 300 frames from there, not from the menu
|
|
159
|
+
- closing a lane again with -m adds the new lines to its change note instead of replacing it
|
|
160
|
+
- the automatic worktree cleanup now removes a lane that never committed once it has sat untouched for an hour with no changed or new files, and its dry run lists only what a real run would take
|
|
161
|
+
- waypoint pins and edge arrows near a screen edge keep their label on screen
|
|
162
|
+
- world-anchored floating text no longer re-renders every frame while nothing floats
|
|
163
|
+
- the worktree cleanup leaves a tree a running process holds whole and reports it in use, instead of half-deleting it and stopping, so the trees after it still go
|
|
164
|
+
- bun run worktree new runs the cleanup from the new tree's own code, not the main checkout's possibly older copy
|
|
165
|
+
- Rockets burst on the first thing they hit instead of flying through it.
|
|
166
|
+
- Chest and crate drops land two metres out on the opener's side, so picking one up no longer puts the camera inside the chest.
|
|
167
|
+
- on Windows a new worktree shares the main checkout's installed packages instead of installing 713 of its own, so it is ready in about 10s instead of 30s to 2 minutes
|
|
168
|
+
- a worktree whose bun.lock moved on gets its own install instead of writing into the shared one
|
|
169
|
+
- the 17 aim and look checks removed with the old drive steps are restored, and the 12 removed drive-step assertion checks now live as browser flow tests (failing expect stops the run, strict value matching, a page error, a timed-out wait)
|
|
170
|
+
- A new game made with `--no-editor` now gets the starter sun angle; it was written in the editor's shape, which `sky()` ignored.
|
|
171
|
+
- the casts and non-null assertions the type-escape baseline was raised to accept (boot sequence, asset prefetch, frame breakdown, perf panel, dev asset plugin, perf scripts) are replaced by real checks.
|
|
172
|
+
- Convex inserts and query results no longer write field: undefined for optional columns.
|
|
173
|
+
- the workspace catalog JSON scanner throws a clear error on truncated JSON instead of reading past the end.
|
|
174
|
+
- script readers of changelog headings, PNG headers and screenshot pixels no longer read past the end of their input.
|
|
175
|
+
|
|
176
|
+
### Removed
|
|
177
|
+
|
|
178
|
+
- createCoachMarkSequence, CoachMark and CoachMarkHost. UI tours are onboarding steps now: give a step an anchor (CSS selector) and placement, or finish it on Next, and OnboardingCallout shows it as a card with a spotlight, Next and Skip, saved with the game
|
|
179
|
+
- `@jgengine/core/ui` no longer exports `CoachMarkPlacement`, `CoachMarkSequence`, `CoachMarkSequenceOptions`, `CoachMarkSnapshot`, `CoachMarkStep`, `CoachMarkView`, `createCoachMarkSequence`.
|
|
180
|
+
- `@jgengine/core/ui/coachMarks` no longer exports `CoachMarkPlacement`, `CoachMarkSequence`, `CoachMarkSequenceOptions`, `CoachMarkSnapshot`, `CoachMarkStep`, `CoachMarkView`, `createCoachMarkSequence`.
|
|
181
|
+
- `@jgengine/core/ui/coachMarks.view` no longer exports `CoachMark`, `CoachMarkHost`, `CoachMarkHostProps`, `CoachMarkProps`, `CoachMarkTheme`, `useCoachMarks`.
|
|
182
|
+
- `@jgengine/react` no longer exports `CoachMark`, `CoachMarkHost`, `CoachMarkHostProps`, `CoachMarkProps`, `CoachMarkTheme`, `useCoachMarks`.
|
|
183
|
+
- the jgengine plan command; the skill replaces it, and jgengine has stays as the lookup
|
|
184
|
+
- the progression-curve seam from games-framework; a game's own level numbers, unlock order and tutorial beats stay in the game at any length (games/AGENTS.md mechanism signal)
|
|
185
|
+
- system checkboxes from the game READMEs' progression sections; progression is written as what the player gets over hours
|
|
186
|
+
|
|
29
187
|
## 0.49.1
|
|
30
188
|
|
|
31
189
|
### Migrate
|
|
@@ -13,8 +13,8 @@ export type ConvexBackendOptions<TRawPresenceRow extends {
|
|
|
13
13
|
client: ConvexReactClient;
|
|
14
14
|
gameId: string;
|
|
15
15
|
userId: string;
|
|
16
|
-
api?: ConvexGameApi;
|
|
17
|
-
poseTuning?: PoseSyncTuning;
|
|
16
|
+
api?: ConvexGameApi | undefined;
|
|
17
|
+
poseTuning?: PoseSyncTuning | undefined;
|
|
18
18
|
presence?: {
|
|
19
19
|
functions: ConvexPresenceFunctions;
|
|
20
20
|
mapRow: (row: TRawPresenceRow) => TPresenceRow;
|
|
@@ -15,23 +15,23 @@ export type ConvexGameApi = {
|
|
|
15
15
|
runtime: {
|
|
16
16
|
joinServer: FunctionReference<"mutation", "public", {
|
|
17
17
|
gameId: string;
|
|
18
|
-
serverId?: string;
|
|
18
|
+
serverId?: string | undefined;
|
|
19
19
|
mode?: string;
|
|
20
20
|
visibility?: "public" | "private";
|
|
21
21
|
joinCode?: string;
|
|
22
|
-
externalId?: string;
|
|
22
|
+
externalId?: string | undefined;
|
|
23
23
|
sessionId?: string;
|
|
24
24
|
}, JoinServerOutcome>;
|
|
25
25
|
leaveServer: FunctionReference<"mutation", "public", {
|
|
26
26
|
serverId: string;
|
|
27
|
-
externalId?: string;
|
|
27
|
+
externalId?: string | undefined;
|
|
28
28
|
sessionId?: string;
|
|
29
29
|
}, null>;
|
|
30
30
|
runCommand: FunctionReference<"mutation", "public", {
|
|
31
31
|
serverId: string;
|
|
32
32
|
command: string;
|
|
33
33
|
input: unknown;
|
|
34
|
-
externalId?: string;
|
|
34
|
+
externalId?: string | undefined;
|
|
35
35
|
}, {
|
|
36
36
|
ok: true;
|
|
37
37
|
} | {
|
|
@@ -42,7 +42,7 @@ export type ConvexGameApi = {
|
|
|
42
42
|
serverId: string;
|
|
43
43
|
action: string;
|
|
44
44
|
entry: unknown;
|
|
45
|
-
externalId?: string;
|
|
45
|
+
externalId?: string | undefined;
|
|
46
46
|
}, null>;
|
|
47
47
|
getServer: FunctionReference<"query", "public", {
|
|
48
48
|
serverId: string;
|
|
@@ -55,13 +55,13 @@ export type ConvexGameApi = {
|
|
|
55
55
|
getFeed: FunctionReference<"query", "public", {
|
|
56
56
|
serverId: string;
|
|
57
57
|
action: string;
|
|
58
|
-
externalId?: string;
|
|
58
|
+
externalId?: string | undefined;
|
|
59
59
|
}, unknown[]>;
|
|
60
60
|
};
|
|
61
61
|
presence: {
|
|
62
62
|
list: FunctionReference<"query", "public", {
|
|
63
63
|
serverId: string;
|
|
64
|
-
externalId?: string;
|
|
64
|
+
externalId?: string | undefined;
|
|
65
65
|
viewerChunkKey?: string;
|
|
66
66
|
}, PresencePoseRow[]>;
|
|
67
67
|
sync: FunctionReference<"mutation", "public", {
|
|
@@ -76,7 +76,7 @@ export type ConvexGameApi = {
|
|
|
76
76
|
sessionId?: string;
|
|
77
77
|
kind?: string;
|
|
78
78
|
label?: string;
|
|
79
|
-
externalId?: string;
|
|
79
|
+
externalId?: string | undefined;
|
|
80
80
|
}, {
|
|
81
81
|
pose: {
|
|
82
82
|
x: number;
|
|
@@ -98,13 +98,13 @@ export type ConvexGameApi = {
|
|
|
98
98
|
messages: FunctionReference<"query", "public", {
|
|
99
99
|
serverId: string;
|
|
100
100
|
channelId: string;
|
|
101
|
-
externalId?: string;
|
|
101
|
+
externalId?: string | undefined;
|
|
102
102
|
}, ChatMessage[]>;
|
|
103
103
|
sendMessage: FunctionReference<"mutation", "public", {
|
|
104
104
|
serverId: string;
|
|
105
105
|
channelId: string;
|
|
106
106
|
body: string;
|
|
107
|
-
externalId?: string;
|
|
107
|
+
externalId?: string | undefined;
|
|
108
108
|
}, ChatSendOutcome>;
|
|
109
109
|
};
|
|
110
110
|
leaderboard: {
|
package/dist/hostedServer.d.ts
CHANGED
|
@@ -53,9 +53,9 @@ export declare function createHostedGameServerFunctions(options: {
|
|
|
53
53
|
tickMs?: number;
|
|
54
54
|
}): {
|
|
55
55
|
getHostedServer: import("convex/server").RegisteredQuery<"public", {
|
|
56
|
-
externalId?: string
|
|
56
|
+
externalId?: string;
|
|
57
57
|
gameId: string;
|
|
58
|
-
serverId?: string
|
|
58
|
+
serverId?: string;
|
|
59
59
|
}, Promise<{
|
|
60
60
|
serverId: string;
|
|
61
61
|
gameId: string;
|
|
@@ -64,24 +64,24 @@ export declare function createHostedGameServerFunctions(options: {
|
|
|
64
64
|
memberUserIds: string[];
|
|
65
65
|
} | null>>;
|
|
66
66
|
joinHostedServer: import("convex/server").RegisteredMutation<"public", {
|
|
67
|
-
externalId?: string
|
|
67
|
+
externalId?: string;
|
|
68
68
|
gameId: string;
|
|
69
|
-
serverId?: string
|
|
69
|
+
serverId?: string;
|
|
70
70
|
}, Promise<{
|
|
71
71
|
serverId: string;
|
|
72
72
|
isNew: boolean;
|
|
73
73
|
}>>;
|
|
74
74
|
leaveHostedServer: import("convex/server").RegisteredMutation<"public", {
|
|
75
|
-
externalId?: string
|
|
75
|
+
externalId?: string;
|
|
76
76
|
gameId: string;
|
|
77
|
-
serverId?: string
|
|
77
|
+
serverId?: string;
|
|
78
78
|
}, Promise<null>>;
|
|
79
79
|
runHostedCommand: import("convex/server").RegisteredMutation<"public", {
|
|
80
80
|
command: string;
|
|
81
|
-
externalId?: string
|
|
81
|
+
externalId?: string;
|
|
82
82
|
gameId: string;
|
|
83
83
|
input: any;
|
|
84
|
-
serverId?: string
|
|
84
|
+
serverId?: string;
|
|
85
85
|
}, Promise<{
|
|
86
86
|
ok: false;
|
|
87
87
|
reason: string;
|
|
@@ -89,10 +89,10 @@ export declare function createHostedGameServerFunctions(options: {
|
|
|
89
89
|
ok: true;
|
|
90
90
|
}>>;
|
|
91
91
|
submitHostedInput: import("convex/server").RegisteredMutation<"public", {
|
|
92
|
-
externalId?: string
|
|
92
|
+
externalId?: string;
|
|
93
93
|
frame: any;
|
|
94
94
|
gameId: string;
|
|
95
|
-
serverId?: string
|
|
95
|
+
serverId?: string;
|
|
96
96
|
}, Promise<null>>;
|
|
97
97
|
tickHostedWorlds: import("convex/server").RegisteredMutation<"internal", {}, Promise<{
|
|
98
98
|
ticked: number;
|
|
@@ -7,7 +7,7 @@ export declare function randomConvexPlayerId(): string;
|
|
|
7
7
|
export declare function resolveConvexMultiplayer(args: {
|
|
8
8
|
game: GameDefinition;
|
|
9
9
|
gameId: string;
|
|
10
|
-
url?: string;
|
|
10
|
+
url?: string | undefined;
|
|
11
11
|
client?: ConvexReactClient;
|
|
12
12
|
api?: ConvexGameApi;
|
|
13
13
|
userId?: string;
|
package/dist/server.d.ts
CHANGED
|
@@ -16,13 +16,13 @@ export { JG_MAX_MEMBERS_PER_SERVER, canJoinPrivateServer, isListablePublicly, va
|
|
|
16
16
|
export declare function jgengineTables(): {
|
|
17
17
|
jgGameServers: import("convex/server").TableDefinition<import("convex/values").VObject<{
|
|
18
18
|
createdAt: number;
|
|
19
|
-
dirtyAt?: number
|
|
19
|
+
dirtyAt?: number;
|
|
20
20
|
gameId: string;
|
|
21
|
-
joinCode?: string
|
|
22
|
-
lastSavedAt?: number
|
|
23
|
-
memberCount?: number
|
|
21
|
+
joinCode?: string;
|
|
22
|
+
lastSavedAt?: number;
|
|
23
|
+
memberCount?: number;
|
|
24
24
|
memberUserIds: string[];
|
|
25
|
-
mode?: string
|
|
25
|
+
mode?: string;
|
|
26
26
|
modeConfig?: any;
|
|
27
27
|
revision: number;
|
|
28
28
|
save: "none" | {
|
|
@@ -34,9 +34,9 @@ export declare function jgengineTables(): {
|
|
|
34
34
|
slotsPerServer: number;
|
|
35
35
|
status: "closed" | "open" | "running";
|
|
36
36
|
tickAnchorMs: number;
|
|
37
|
-
topology?: "rooms" | "shared"
|
|
37
|
+
topology?: "rooms" | "shared";
|
|
38
38
|
updatedAt: number;
|
|
39
|
-
visibility?: "private" | "public"
|
|
39
|
+
visibility?: "private" | "public";
|
|
40
40
|
}, {
|
|
41
41
|
gameId: import("convex/values").VString<string, "required">;
|
|
42
42
|
status: import("convex/values").VUnion<"closed" | "open" | "running", [import("convex/values").VLiteral<"open", "required">, import("convex/values").VLiteral<"running", "required">, import("convex/values").VLiteral<"closed", "required">], "required", never>;
|
|
@@ -75,7 +75,7 @@ export declare function jgengineTables(): {
|
|
|
75
75
|
jgServerCapacity: import("convex/server").TableDefinition<import("convex/values").VObject<{
|
|
76
76
|
gameId: string;
|
|
77
77
|
memberCount: number;
|
|
78
|
-
mode?: string
|
|
78
|
+
mode?: string;
|
|
79
79
|
serverId: GenericId<"jgGameServers">;
|
|
80
80
|
slotsPerServer: number;
|
|
81
81
|
status: "closed" | "open" | "running";
|
|
@@ -92,7 +92,7 @@ export declare function jgengineTables(): {
|
|
|
92
92
|
jgServerMembers: import("convex/server").TableDefinition<import("convex/values").VObject<{
|
|
93
93
|
joinedAt: number;
|
|
94
94
|
serverId: GenericId<"jgGameServers">;
|
|
95
|
-
sessionIds?: string[]
|
|
95
|
+
sessionIds?: string[];
|
|
96
96
|
userId: string;
|
|
97
97
|
}, {
|
|
98
98
|
serverId: import("convex/values").VId<GenericId<"jgGameServers">, "required">;
|
|
@@ -133,11 +133,11 @@ export declare function jgengineTables(): {
|
|
|
133
133
|
}, {}, {}>;
|
|
134
134
|
jgPlayerProfiles: import("convex/server").TableDefinition<import("convex/values").VObject<{
|
|
135
135
|
createdAt: number;
|
|
136
|
-
dirtyAt?: number
|
|
136
|
+
dirtyAt?: number;
|
|
137
137
|
gameId: string;
|
|
138
138
|
playerState: any;
|
|
139
139
|
revision: number;
|
|
140
|
-
sessionServerId?: GenericId<"jgGameServers"
|
|
140
|
+
sessionServerId?: GenericId<"jgGameServers">;
|
|
141
141
|
sessionState?: any;
|
|
142
142
|
updatedAt: number;
|
|
143
143
|
userId: string;
|
|
@@ -156,7 +156,7 @@ export declare function jgengineTables(): {
|
|
|
156
156
|
}, {}, {}>;
|
|
157
157
|
jgWorldChunks: import("convex/server").TableDefinition<import("convex/values").VObject<{
|
|
158
158
|
chunkKey: string;
|
|
159
|
-
dirtyAt?: number
|
|
159
|
+
dirtyAt?: number;
|
|
160
160
|
serverId: GenericId<"jgGameServers">;
|
|
161
161
|
snapshot: any;
|
|
162
162
|
updatedAt: number;
|
|
@@ -173,7 +173,7 @@ export declare function jgengineTables(): {
|
|
|
173
173
|
jgLeaderboardRows: import("convex/server").TableDefinition<import("convex/values").VObject<{
|
|
174
174
|
gameId: string;
|
|
175
175
|
scope: "global" | "profile" | "server";
|
|
176
|
-
serverId?: GenericId<"jgGameServers"
|
|
176
|
+
serverId?: GenericId<"jgGameServers">;
|
|
177
177
|
stat: string;
|
|
178
178
|
updatedAt: number;
|
|
179
179
|
userId: string;
|
|
@@ -204,17 +204,17 @@ export declare function jgengineTables(): {
|
|
|
204
204
|
by_server_and_action: ["serverId", "action", "_creationTime"];
|
|
205
205
|
}, {}, {}>;
|
|
206
206
|
jgPoses: import("convex/server").TableDefinition<import("convex/values").VObject<{
|
|
207
|
-
chunkKey?: string
|
|
208
|
-
homeGameId?: string
|
|
209
|
-
kind?: string
|
|
210
|
-
label?: string
|
|
211
|
-
lastWorldSnapshotAt?: number
|
|
212
|
-
ownerActorId?: string
|
|
213
|
-
revokedAt?: number
|
|
207
|
+
chunkKey?: string;
|
|
208
|
+
homeGameId?: string;
|
|
209
|
+
kind?: string;
|
|
210
|
+
label?: string;
|
|
211
|
+
lastWorldSnapshotAt?: number;
|
|
212
|
+
ownerActorId?: string;
|
|
213
|
+
revokedAt?: number;
|
|
214
214
|
rotationPitch: number;
|
|
215
215
|
rotationY: number;
|
|
216
216
|
serverId: string;
|
|
217
|
-
sessionId?: string
|
|
217
|
+
sessionId?: string;
|
|
218
218
|
updatedAt: number;
|
|
219
219
|
userId: string;
|
|
220
220
|
x: number;
|
|
@@ -255,7 +255,7 @@ export declare function jgengineTables(): {
|
|
|
255
255
|
}, {}, {}>;
|
|
256
256
|
jgChatMessages: import("convex/server").TableDefinition<import("convex/values").VObject<{
|
|
257
257
|
at: number;
|
|
258
|
-
authorName?: string
|
|
258
|
+
authorName?: string;
|
|
259
259
|
body: string;
|
|
260
260
|
channelId: string;
|
|
261
261
|
serverId: string;
|
|
@@ -307,13 +307,13 @@ export declare function jgengineHostedTables(): {
|
|
|
307
307
|
declare const schemaForTypes: import("convex/server").SchemaDefinition<{
|
|
308
308
|
jgGameServers: import("convex/server").TableDefinition<import("convex/values").VObject<{
|
|
309
309
|
createdAt: number;
|
|
310
|
-
dirtyAt?: number
|
|
310
|
+
dirtyAt?: number;
|
|
311
311
|
gameId: string;
|
|
312
|
-
joinCode?: string
|
|
313
|
-
lastSavedAt?: number
|
|
314
|
-
memberCount?: number
|
|
312
|
+
joinCode?: string;
|
|
313
|
+
lastSavedAt?: number;
|
|
314
|
+
memberCount?: number;
|
|
315
315
|
memberUserIds: string[];
|
|
316
|
-
mode?: string
|
|
316
|
+
mode?: string;
|
|
317
317
|
modeConfig?: any;
|
|
318
318
|
revision: number;
|
|
319
319
|
save: "none" | {
|
|
@@ -325,9 +325,9 @@ declare const schemaForTypes: import("convex/server").SchemaDefinition<{
|
|
|
325
325
|
slotsPerServer: number;
|
|
326
326
|
status: "closed" | "open" | "running";
|
|
327
327
|
tickAnchorMs: number;
|
|
328
|
-
topology?: "rooms" | "shared"
|
|
328
|
+
topology?: "rooms" | "shared";
|
|
329
329
|
updatedAt: number;
|
|
330
|
-
visibility?: "private" | "public"
|
|
330
|
+
visibility?: "private" | "public";
|
|
331
331
|
}, {
|
|
332
332
|
gameId: import("convex/values").VString<string, "required">;
|
|
333
333
|
status: import("convex/values").VUnion<"closed" | "open" | "running", [import("convex/values").VLiteral<"open", "required">, import("convex/values").VLiteral<"running", "required">, import("convex/values").VLiteral<"closed", "required">], "required", never>;
|
|
@@ -366,7 +366,7 @@ declare const schemaForTypes: import("convex/server").SchemaDefinition<{
|
|
|
366
366
|
jgServerCapacity: import("convex/server").TableDefinition<import("convex/values").VObject<{
|
|
367
367
|
gameId: string;
|
|
368
368
|
memberCount: number;
|
|
369
|
-
mode?: string
|
|
369
|
+
mode?: string;
|
|
370
370
|
serverId: GenericId<"jgGameServers">;
|
|
371
371
|
slotsPerServer: number;
|
|
372
372
|
status: "closed" | "open" | "running";
|
|
@@ -383,7 +383,7 @@ declare const schemaForTypes: import("convex/server").SchemaDefinition<{
|
|
|
383
383
|
jgServerMembers: import("convex/server").TableDefinition<import("convex/values").VObject<{
|
|
384
384
|
joinedAt: number;
|
|
385
385
|
serverId: GenericId<"jgGameServers">;
|
|
386
|
-
sessionIds?: string[]
|
|
386
|
+
sessionIds?: string[];
|
|
387
387
|
userId: string;
|
|
388
388
|
}, {
|
|
389
389
|
serverId: import("convex/values").VId<GenericId<"jgGameServers">, "required">;
|
|
@@ -424,11 +424,11 @@ declare const schemaForTypes: import("convex/server").SchemaDefinition<{
|
|
|
424
424
|
}, {}, {}>;
|
|
425
425
|
jgPlayerProfiles: import("convex/server").TableDefinition<import("convex/values").VObject<{
|
|
426
426
|
createdAt: number;
|
|
427
|
-
dirtyAt?: number
|
|
427
|
+
dirtyAt?: number;
|
|
428
428
|
gameId: string;
|
|
429
429
|
playerState: any;
|
|
430
430
|
revision: number;
|
|
431
|
-
sessionServerId?: GenericId<"jgGameServers"
|
|
431
|
+
sessionServerId?: GenericId<"jgGameServers">;
|
|
432
432
|
sessionState?: any;
|
|
433
433
|
updatedAt: number;
|
|
434
434
|
userId: string;
|
|
@@ -447,7 +447,7 @@ declare const schemaForTypes: import("convex/server").SchemaDefinition<{
|
|
|
447
447
|
}, {}, {}>;
|
|
448
448
|
jgWorldChunks: import("convex/server").TableDefinition<import("convex/values").VObject<{
|
|
449
449
|
chunkKey: string;
|
|
450
|
-
dirtyAt?: number
|
|
450
|
+
dirtyAt?: number;
|
|
451
451
|
serverId: GenericId<"jgGameServers">;
|
|
452
452
|
snapshot: any;
|
|
453
453
|
updatedAt: number;
|
|
@@ -464,7 +464,7 @@ declare const schemaForTypes: import("convex/server").SchemaDefinition<{
|
|
|
464
464
|
jgLeaderboardRows: import("convex/server").TableDefinition<import("convex/values").VObject<{
|
|
465
465
|
gameId: string;
|
|
466
466
|
scope: "global" | "profile" | "server";
|
|
467
|
-
serverId?: GenericId<"jgGameServers"
|
|
467
|
+
serverId?: GenericId<"jgGameServers">;
|
|
468
468
|
stat: string;
|
|
469
469
|
updatedAt: number;
|
|
470
470
|
userId: string;
|
|
@@ -495,17 +495,17 @@ declare const schemaForTypes: import("convex/server").SchemaDefinition<{
|
|
|
495
495
|
by_server_and_action: ["serverId", "action", "_creationTime"];
|
|
496
496
|
}, {}, {}>;
|
|
497
497
|
jgPoses: import("convex/server").TableDefinition<import("convex/values").VObject<{
|
|
498
|
-
chunkKey?: string
|
|
499
|
-
homeGameId?: string
|
|
500
|
-
kind?: string
|
|
501
|
-
label?: string
|
|
502
|
-
lastWorldSnapshotAt?: number
|
|
503
|
-
ownerActorId?: string
|
|
504
|
-
revokedAt?: number
|
|
498
|
+
chunkKey?: string;
|
|
499
|
+
homeGameId?: string;
|
|
500
|
+
kind?: string;
|
|
501
|
+
label?: string;
|
|
502
|
+
lastWorldSnapshotAt?: number;
|
|
503
|
+
ownerActorId?: string;
|
|
504
|
+
revokedAt?: number;
|
|
505
505
|
rotationPitch: number;
|
|
506
506
|
rotationY: number;
|
|
507
507
|
serverId: string;
|
|
508
|
-
sessionId?: string
|
|
508
|
+
sessionId?: string;
|
|
509
509
|
updatedAt: number;
|
|
510
510
|
userId: string;
|
|
511
511
|
x: number;
|
|
@@ -546,7 +546,7 @@ declare const schemaForTypes: import("convex/server").SchemaDefinition<{
|
|
|
546
546
|
}, {}, {}>;
|
|
547
547
|
jgChatMessages: import("convex/server").TableDefinition<import("convex/values").VObject<{
|
|
548
548
|
at: number;
|
|
549
|
-
authorName?: string
|
|
549
|
+
authorName?: string;
|
|
550
550
|
body: string;
|
|
551
551
|
channelId: string;
|
|
552
552
|
serverId: string;
|
|
@@ -626,7 +626,7 @@ export type RunCommandOutcome = {
|
|
|
626
626
|
};
|
|
627
627
|
/** Arguments of the `runCommand` mutation, and of the equivalent {@link GameServerHelpers.runCommand} helper. */
|
|
628
628
|
export type ConvexRunCommandArgs = RunCommandArgs & {
|
|
629
|
-
externalId?: string;
|
|
629
|
+
externalId?: string | undefined;
|
|
630
630
|
/**
|
|
631
631
|
* How much of the server to hydrate before applying the command. Overrides whatever the
|
|
632
632
|
* {@link CommandDef} declares through its own `scope`; omit both to hydrate the whole world.
|
|
@@ -723,42 +723,42 @@ export declare function createGameServerFunctions(options?: {
|
|
|
723
723
|
allowedFeedActions?: readonly string[];
|
|
724
724
|
}): {
|
|
725
725
|
joinServer: import("convex/server").RegisteredMutation<"public", {
|
|
726
|
-
externalId?: string
|
|
726
|
+
externalId?: string;
|
|
727
727
|
gameId: string;
|
|
728
|
-
joinCode?: string
|
|
729
|
-
mode?: string
|
|
728
|
+
joinCode?: string;
|
|
729
|
+
mode?: string;
|
|
730
730
|
modeConfig?: any;
|
|
731
|
-
serverId?: GenericId<"jgGameServers"
|
|
732
|
-
sessionId?: string
|
|
733
|
-
visibility?: "private" | "public"
|
|
731
|
+
serverId?: GenericId<"jgGameServers">;
|
|
732
|
+
sessionId?: string;
|
|
733
|
+
visibility?: "private" | "public";
|
|
734
734
|
}, Promise<EnsureJoinedOutcome>>;
|
|
735
735
|
leaveServer: import("convex/server").RegisteredMutation<"public", {
|
|
736
|
-
externalId?: string
|
|
736
|
+
externalId?: string;
|
|
737
737
|
serverId: GenericId<"jgGameServers">;
|
|
738
|
-
sessionId?: string
|
|
738
|
+
sessionId?: string;
|
|
739
739
|
}, Promise<null>>;
|
|
740
740
|
runCommand: import("convex/server").RegisteredMutation<"public", {
|
|
741
741
|
command: string;
|
|
742
|
-
externalId?: string
|
|
742
|
+
externalId?: string;
|
|
743
743
|
input: any;
|
|
744
744
|
scope?: {
|
|
745
|
-
chunkKeys?: string[]
|
|
746
|
-
players?: string[]
|
|
747
|
-
}
|
|
745
|
+
chunkKeys?: string[];
|
|
746
|
+
players?: string[];
|
|
747
|
+
};
|
|
748
748
|
serverId: GenericId<"jgGameServers">;
|
|
749
749
|
}, Promise<RunCommandOutcome>>;
|
|
750
750
|
flushSave: import("convex/server").RegisteredMutation<"public", {
|
|
751
|
-
externalId?: string
|
|
751
|
+
externalId?: string;
|
|
752
752
|
serverId: GenericId<"jgGameServers">;
|
|
753
753
|
}, Promise<boolean>>;
|
|
754
754
|
getServer: import("convex/server").RegisteredQuery<"public", {
|
|
755
|
-
externalId?: string
|
|
755
|
+
externalId?: string;
|
|
756
756
|
serverId: GenericId<"jgGameServers">;
|
|
757
757
|
}, Promise<{
|
|
758
758
|
_id: GenericId<"jgGameServers">;
|
|
759
759
|
gameId: string;
|
|
760
760
|
status: "closed" | "open" | "running";
|
|
761
|
-
mode
|
|
761
|
+
mode?: string;
|
|
762
762
|
memberUserIds: string[];
|
|
763
763
|
slotsPerServer: number;
|
|
764
764
|
save: "none" | {
|
|
@@ -773,30 +773,30 @@ export declare function createGameServerFunctions(options?: {
|
|
|
773
773
|
updatedAt: number;
|
|
774
774
|
} | null>>;
|
|
775
775
|
getServerMeta: import("convex/server").RegisteredQuery<"public", {
|
|
776
|
-
externalId?: string
|
|
776
|
+
externalId?: string;
|
|
777
777
|
serverId: GenericId<"jgGameServers">;
|
|
778
|
-
}, Promise<{
|
|
778
|
+
}, Promise<import("@jgengine/core/data/definedOnly").DefinedOnly<{
|
|
779
779
|
_id: GenericId<"jgGameServers">;
|
|
780
780
|
gameId: string;
|
|
781
781
|
status: "closed" | "open" | "running";
|
|
782
782
|
mode: string | undefined;
|
|
783
783
|
memberCount: number;
|
|
784
784
|
slotsPerServer: number;
|
|
785
|
-
} | null>>;
|
|
785
|
+
}> | null>>;
|
|
786
786
|
getServerCapacity: import("convex/server").RegisteredQuery<"public", {
|
|
787
|
-
externalId?: string
|
|
787
|
+
externalId?: string;
|
|
788
788
|
serverId: GenericId<"jgGameServers">;
|
|
789
|
-
}, Promise<{
|
|
789
|
+
}, Promise<import("@jgengine/core/data/definedOnly").DefinedOnly<{
|
|
790
790
|
_id: GenericId<"jgGameServers">;
|
|
791
791
|
gameId: string;
|
|
792
792
|
status: "closed" | "open" | "running";
|
|
793
793
|
mode: string | undefined;
|
|
794
794
|
memberCount: number;
|
|
795
795
|
slotsPerServer: number;
|
|
796
|
-
} | null>>;
|
|
796
|
+
}> | null>>;
|
|
797
797
|
getChunks: import("convex/server").RegisteredQuery<"public", {
|
|
798
798
|
chunkKeys: string[];
|
|
799
|
-
externalId?: string
|
|
799
|
+
externalId?: string;
|
|
800
800
|
serverId: GenericId<"jgGameServers">;
|
|
801
801
|
}, Promise<{
|
|
802
802
|
chunkKey: string;
|
|
@@ -804,7 +804,7 @@ export declare function createGameServerFunctions(options?: {
|
|
|
804
804
|
updatedAt: number;
|
|
805
805
|
}[]>>;
|
|
806
806
|
getPlayerProfile: import("convex/server").RegisteredQuery<"public", {
|
|
807
|
-
externalId?: string
|
|
807
|
+
externalId?: string;
|
|
808
808
|
gameId: string;
|
|
809
809
|
}, Promise<{
|
|
810
810
|
userId: string;
|
|
@@ -815,27 +815,28 @@ export declare function createGameServerFunctions(options?: {
|
|
|
815
815
|
} | null>>;
|
|
816
816
|
getFeed: import("convex/server").RegisteredQuery<"public", {
|
|
817
817
|
action: string;
|
|
818
|
-
externalId?: string
|
|
818
|
+
externalId?: string;
|
|
819
819
|
serverId: GenericId<"jgGameServers">;
|
|
820
820
|
}, Promise<any[]>>;
|
|
821
821
|
pushFeedEntry: import("convex/server").RegisteredMutation<"public", {
|
|
822
822
|
action: string;
|
|
823
823
|
entry: any;
|
|
824
|
-
externalId?: string
|
|
824
|
+
externalId?: string;
|
|
825
825
|
serverId: GenericId<"jgGameServers">;
|
|
826
826
|
}, Promise<null>>;
|
|
827
827
|
listOpenServers: import("convex/server").RegisteredQuery<"public", {
|
|
828
|
-
externalId?: string
|
|
828
|
+
externalId?: string;
|
|
829
829
|
gameId: string;
|
|
830
|
-
limit?: number
|
|
831
|
-
}, Promise<{
|
|
830
|
+
limit?: number;
|
|
831
|
+
}, Promise<({
|
|
832
832
|
_id: GenericId<"jgGameServers">;
|
|
833
833
|
status: "closed" | "open" | "running";
|
|
834
834
|
memberCount: number;
|
|
835
835
|
slotsPerServer: number;
|
|
836
|
-
mode: string | undefined;
|
|
837
836
|
updatedAt: number;
|
|
838
|
-
}
|
|
837
|
+
} & {
|
|
838
|
+
mode?: string;
|
|
839
|
+
})[]>>;
|
|
839
840
|
tickActiveServers: import("convex/server").RegisteredMutation<"internal", {}, Promise<{
|
|
840
841
|
ticked: number;
|
|
841
842
|
saved: number;
|
|
@@ -850,18 +851,18 @@ export declare function createLeaderboardFunctions(options?: {
|
|
|
850
851
|
auth?: JgAuthMode;
|
|
851
852
|
}): {
|
|
852
853
|
getTop: import("convex/server").RegisteredQuery<"public", {
|
|
853
|
-
externalId?: string
|
|
854
|
+
externalId?: string;
|
|
854
855
|
gameId: string;
|
|
855
|
-
limit?: number
|
|
856
|
+
limit?: number;
|
|
856
857
|
scope: "global" | "profile" | "server";
|
|
857
|
-
serverId?: GenericId<"jgGameServers"
|
|
858
|
+
serverId?: GenericId<"jgGameServers">;
|
|
858
859
|
stat: string;
|
|
859
860
|
}, Promise<{
|
|
860
861
|
userId: string;
|
|
861
862
|
value: number;
|
|
862
863
|
}[]>>;
|
|
863
864
|
getProfile: import("convex/server").RegisteredQuery<"public", {
|
|
864
|
-
externalId?: string
|
|
865
|
+
externalId?: string;
|
|
865
866
|
gameId: string;
|
|
866
867
|
userId: string;
|
|
867
868
|
}, Promise<Record<string, number>>>;
|
|
@@ -869,7 +870,7 @@ export declare function createLeaderboardFunctions(options?: {
|
|
|
869
870
|
entries: {
|
|
870
871
|
by: number;
|
|
871
872
|
scope: "global" | "profile" | "server";
|
|
872
|
-
serverId?: GenericId<"jgGameServers"
|
|
873
|
+
serverId?: GenericId<"jgGameServers">;
|
|
873
874
|
stat: string;
|
|
874
875
|
userId: string;
|
|
875
876
|
}[];
|
|
@@ -925,7 +926,7 @@ export declare function createPresenceFunctions(options?: {
|
|
|
925
926
|
resolveSpawn?: (ctx: JGMutationCtx, args: {
|
|
926
927
|
serverId: string;
|
|
927
928
|
userId: string;
|
|
928
|
-
kind?: string;
|
|
929
|
+
kind?: string | undefined;
|
|
929
930
|
}) => Promise<{
|
|
930
931
|
x: number;
|
|
931
932
|
y: number;
|
|
@@ -937,28 +938,28 @@ export declare function createPresenceFunctions(options?: {
|
|
|
937
938
|
} | null;
|
|
938
939
|
}): {
|
|
939
940
|
list: import("convex/server").RegisteredQuery<"public", {
|
|
940
|
-
externalId?: string
|
|
941
|
+
externalId?: string;
|
|
941
942
|
serverId: string;
|
|
942
|
-
viewerChunkKey?: string
|
|
943
|
+
viewerChunkKey?: string;
|
|
943
944
|
}, Promise<PresenceListRow[]>>;
|
|
944
945
|
sync: import("convex/server").RegisteredMutation<"public", {
|
|
945
|
-
externalId?: string
|
|
946
|
-
kind?: string
|
|
947
|
-
label?: string
|
|
946
|
+
externalId?: string;
|
|
947
|
+
kind?: string;
|
|
948
|
+
label?: string;
|
|
948
949
|
pose?: {
|
|
949
950
|
rotationPitch: number;
|
|
950
951
|
rotationY: number;
|
|
951
952
|
x: number;
|
|
952
953
|
y: number;
|
|
953
954
|
z: number;
|
|
954
|
-
}
|
|
955
|
+
};
|
|
955
956
|
serverId: string;
|
|
956
|
-
sessionId?: string
|
|
957
|
+
sessionId?: string;
|
|
957
958
|
}, Promise<PresenceSyncResult | null>>;
|
|
958
959
|
leave: import("convex/server").RegisteredMutation<"public", {
|
|
959
|
-
externalId?: string
|
|
960
|
+
externalId?: string;
|
|
960
961
|
serverId: string;
|
|
961
|
-
sessionId?: string
|
|
962
|
+
sessionId?: string;
|
|
962
963
|
}, Promise<null>>;
|
|
963
964
|
reapIdlePresence: import("convex/server").RegisteredMutation<"internal", {}, Promise<{
|
|
964
965
|
reaped: number;
|
|
@@ -1033,7 +1034,7 @@ export declare function recentChatMessages(ctx: {
|
|
|
1033
1034
|
_creationTime: number;
|
|
1034
1035
|
_id: GenericId<"jgChatMessages">;
|
|
1035
1036
|
at: number;
|
|
1036
|
-
authorName?: string
|
|
1037
|
+
authorName?: string;
|
|
1037
1038
|
body: string;
|
|
1038
1039
|
channelId: string;
|
|
1039
1040
|
serverId: string;
|
|
@@ -1045,19 +1046,19 @@ export declare function recentChatMessages(ctx: {
|
|
|
1045
1046
|
*/
|
|
1046
1047
|
export declare function createClientErrorFunctions(): {
|
|
1047
1048
|
reportClientError: import("convex/server").RegisteredMutation<"public", {
|
|
1048
|
-
agentPrompt?: string
|
|
1049
|
-
componentStack?: string
|
|
1050
|
-
kind?: string
|
|
1049
|
+
agentPrompt?: string;
|
|
1050
|
+
componentStack?: string;
|
|
1051
|
+
kind?: string;
|
|
1051
1052
|
message: string;
|
|
1052
|
-
pathname?: string
|
|
1053
|
-
stack?: string
|
|
1054
|
-
url?: string
|
|
1055
|
-
userAgent?: string
|
|
1053
|
+
pathname?: string;
|
|
1054
|
+
stack?: string;
|
|
1055
|
+
url?: string;
|
|
1056
|
+
userAgent?: string;
|
|
1056
1057
|
}, Promise<{
|
|
1057
1058
|
ok: boolean;
|
|
1058
1059
|
reason: string;
|
|
1059
1060
|
} | {
|
|
1060
|
-
reason?:
|
|
1061
|
+
reason?: never;
|
|
1061
1062
|
ok: boolean;
|
|
1062
1063
|
}>>;
|
|
1063
1064
|
pruneClientErrors: import("convex/server").RegisteredMutation<"internal", {}, Promise<{
|
|
@@ -1078,18 +1079,18 @@ export declare function createChatFunctions(options?: {
|
|
|
1078
1079
|
}): {
|
|
1079
1080
|
messages: import("convex/server").RegisteredQuery<"public", {
|
|
1080
1081
|
channelId: string;
|
|
1081
|
-
externalId?: string
|
|
1082
|
+
externalId?: string;
|
|
1082
1083
|
serverId: string;
|
|
1083
1084
|
}, Promise<ChatMessage[]>>;
|
|
1084
1085
|
recent: import("convex/server").RegisteredQuery<"public", {
|
|
1085
1086
|
channelId: string;
|
|
1086
|
-
externalId?: string
|
|
1087
|
+
externalId?: string;
|
|
1087
1088
|
serverId: string;
|
|
1088
1089
|
}, Promise<ChatMessage[]>>;
|
|
1089
1090
|
sendMessage: import("convex/server").RegisteredMutation<"public", {
|
|
1090
1091
|
body: string;
|
|
1091
1092
|
channelId: string;
|
|
1092
|
-
externalId?: string
|
|
1093
|
+
externalId?: string;
|
|
1093
1094
|
serverId: string;
|
|
1094
1095
|
}, Promise<ChatSendOutcome>>;
|
|
1095
1096
|
pruneChatMessages: import("convex/server").RegisteredMutation<"internal", {}, Promise<{
|
package/dist/server.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { defineSchema, defineTable, internalMutationGeneric, mutationGeneric, queryGeneric, } from "convex/server";
|
|
2
2
|
import { ConvexError, v } from "convex/values";
|
|
3
3
|
import { validateChatMessage } from "@jgengine/core/chat/chat";
|
|
4
|
+
import { definedOnly } from "@jgengine/core/data/definedOnly";
|
|
4
5
|
import { createFeedWriteGate, validateFeedWrite, } from "@jgengine/core/multiplayer/feedWriteGate";
|
|
5
6
|
import { DEFAULT_POSE_SYNC_RULES, decidePoseSync, isPresenceExpired, pickReusablePresence, resolveActivePresence, spawnPresenceState, } from "@jgengine/core/multiplayer/presenceModel";
|
|
6
7
|
import { SERVER_ONLY_COMMAND_REASON, commandScopeEscape, scopeWithActor } from "@jgengine/core/commands/commandRunner";
|
|
@@ -298,10 +299,10 @@ async function ensureServerCapacity(ctx, server) {
|
|
|
298
299
|
const existing = await serverCapacity(ctx, server);
|
|
299
300
|
if (existing)
|
|
300
301
|
return existing;
|
|
301
|
-
const id = await ctx.db.insert("jgServerCapacity", {
|
|
302
|
+
const id = await ctx.db.insert("jgServerCapacity", definedOnly({
|
|
302
303
|
serverId: server._id, gameId: server.gameId, status: server.status, mode: server.mode,
|
|
303
304
|
memberCount: server.memberCount ?? server.memberUserIds.length, slotsPerServer: server.slotsPerServer,
|
|
304
|
-
});
|
|
305
|
+
}));
|
|
305
306
|
return (await ctx.db.get("jgServerCapacity", id));
|
|
306
307
|
}
|
|
307
308
|
/**
|
|
@@ -387,7 +388,7 @@ export async function applyLeaderboardIncrements(ctx, gameId, entries) {
|
|
|
387
388
|
await ctx.db.patch(existing._id, { value: existing.value + entry.by, updatedAt: now });
|
|
388
389
|
}
|
|
389
390
|
else {
|
|
390
|
-
await ctx.db.insert("jgLeaderboardRows", {
|
|
391
|
+
await ctx.db.insert("jgLeaderboardRows", definedOnly({
|
|
391
392
|
gameId,
|
|
392
393
|
stat: entry.stat,
|
|
393
394
|
scope: entry.scope,
|
|
@@ -395,7 +396,7 @@ export async function applyLeaderboardIncrements(ctx, gameId, entries) {
|
|
|
395
396
|
userId: entry.userId,
|
|
396
397
|
value: entry.by,
|
|
397
398
|
updatedAt: now,
|
|
398
|
-
});
|
|
399
|
+
}));
|
|
399
400
|
}
|
|
400
401
|
}
|
|
401
402
|
}
|
|
@@ -475,7 +476,8 @@ export async function persistServerSnapshot(ctx, server, snapshot, save) {
|
|
|
475
476
|
await ctx.db.insert("jgWorldChunks", {
|
|
476
477
|
serverId: server._id,
|
|
477
478
|
chunkKey: chunk.chunkKey,
|
|
478
|
-
|
|
479
|
+
snapshot: chunk.snapshot,
|
|
480
|
+
updatedAt: chunk.updatedAt,
|
|
479
481
|
});
|
|
480
482
|
}
|
|
481
483
|
}
|
|
@@ -582,10 +584,10 @@ export function createGameServerFunctions(options) {
|
|
|
582
584
|
const serverId = await ctx.db.insert("jgGameServers", {
|
|
583
585
|
gameId: args.gameId,
|
|
584
586
|
status: "running",
|
|
585
|
-
mode: args.mode,
|
|
587
|
+
...(args.mode === undefined ? {} : { mode: args.mode }),
|
|
586
588
|
modeConfig: args.modeConfig,
|
|
587
589
|
visibility: args.visibility ?? "public",
|
|
588
|
-
joinCode: args.joinCode,
|
|
590
|
+
...(args.joinCode === undefined ? {} : { joinCode: args.joinCode }),
|
|
589
591
|
topology: shared ? "shared" : "rooms",
|
|
590
592
|
memberCount: 0,
|
|
591
593
|
memberUserIds: [],
|
|
@@ -930,7 +932,7 @@ export function createGameServerFunctions(options) {
|
|
|
930
932
|
_id: server._id,
|
|
931
933
|
gameId: server.gameId,
|
|
932
934
|
status: server.status,
|
|
933
|
-
mode: server.mode,
|
|
935
|
+
...(server.mode === undefined ? {} : { mode: server.mode }),
|
|
934
936
|
memberUserIds: server.memberUserIds,
|
|
935
937
|
slotsPerServer: server.slotsPerServer,
|
|
936
938
|
save: server.save,
|
|
@@ -958,7 +960,7 @@ export function createGameServerFunctions(options) {
|
|
|
958
960
|
memberCount: v.number(),
|
|
959
961
|
slotsPerServer: v.number(),
|
|
960
962
|
}), v.null()),
|
|
961
|
-
handler: (ctx, args) => withMember(ctx, mode, args, null, async (server) => ({
|
|
963
|
+
handler: (ctx, args) => withMember(ctx, mode, args, null, async (server) => definedOnly({
|
|
962
964
|
_id: server._id,
|
|
963
965
|
gameId: server.gameId,
|
|
964
966
|
status: server.status,
|
|
@@ -983,10 +985,10 @@ export function createGameServerFunctions(options) {
|
|
|
983
985
|
const member = await ctx.db.query("jgServerMembers").withIndex("by_server_and_user", q => q.eq("serverId", args.serverId).eq("userId", actor)).unique();
|
|
984
986
|
if (!member)
|
|
985
987
|
return null;
|
|
986
|
-
return { _id: capacity.serverId, gameId: capacity.gameId, status: capacity.status, mode: capacity.mode, memberCount: capacity.memberCount, slotsPerServer: capacity.slotsPerServer };
|
|
988
|
+
return definedOnly({ _id: capacity.serverId, gameId: capacity.gameId, status: capacity.status, mode: capacity.mode, memberCount: capacity.memberCount, slotsPerServer: capacity.slotsPerServer });
|
|
987
989
|
}
|
|
988
990
|
const server = await requireServerMember(ctx, args.serverId, actor);
|
|
989
|
-
return server ? { _id: server._id, gameId: server.gameId, status: server.status, mode: server.mode, memberCount: server.memberUserIds.length, slotsPerServer: server.slotsPerServer } : null;
|
|
991
|
+
return server ? definedOnly({ _id: server._id, gameId: server.gameId, status: server.status, mode: server.mode, memberCount: server.memberUserIds.length, slotsPerServer: server.slotsPerServer }) : null;
|
|
990
992
|
},
|
|
991
993
|
});
|
|
992
994
|
/**
|
|
@@ -1125,7 +1127,7 @@ export function createGameServerFunctions(options) {
|
|
|
1125
1127
|
return Promise.all(rows
|
|
1126
1128
|
.filter((row) => isListablePublicly(row.visibility))
|
|
1127
1129
|
.slice(0, limit)
|
|
1128
|
-
.map(async (row) => ({
|
|
1130
|
+
.map(async (row) => definedOnly({
|
|
1129
1131
|
_id: row._id,
|
|
1130
1132
|
status: row.status,
|
|
1131
1133
|
memberCount: (await serverCapacity(ctx, row))?.memberCount ?? row.memberCount ?? row.memberUserIds.length,
|
|
@@ -1327,7 +1329,7 @@ export function createPresenceFunctions(options) {
|
|
|
1327
1329
|
}
|
|
1328
1330
|
return rows
|
|
1329
1331
|
.filter((row) => row.revokedAt === undefined && row.updatedAt >= threshold)
|
|
1330
|
-
.map((row) => ({
|
|
1332
|
+
.map((row) => definedOnly({
|
|
1331
1333
|
userId: row.userId,
|
|
1332
1334
|
sessionId: row.sessionId,
|
|
1333
1335
|
kind: row.kind,
|
|
@@ -1550,7 +1552,7 @@ export async function sendChatMessage(ctx, args) {
|
|
|
1550
1552
|
const gate = await rateLimit(ctx, { key: JSON.stringify(["chat", args.serverId, args.userId]), windowMs: args.minIntervalMs ?? 2000, max: 1 });
|
|
1551
1553
|
if (!gate.ok)
|
|
1552
1554
|
return { ok: false, reason: "rate_limited" };
|
|
1553
|
-
await ctx.db.insert("jgChatMessages", { serverId: args.serverId, channelId: args.channelId ?? "global", userId: args.userId, body: validation.text, authorName: args.authorName, at: Date.now() });
|
|
1555
|
+
await ctx.db.insert("jgChatMessages", definedOnly({ serverId: args.serverId, channelId: args.channelId ?? "global", userId: args.userId, body: validation.text, authorName: args.authorName, at: Date.now() }));
|
|
1554
1556
|
return { ok: true };
|
|
1555
1557
|
}
|
|
1556
1558
|
/**
|
|
@@ -1664,8 +1666,8 @@ export function createChatFunctions(options) {
|
|
|
1664
1666
|
.withIndex("by_server_channel", (q) => q.eq("serverId", args.serverId).eq("channelId", args.channelId))
|
|
1665
1667
|
.order("desc")
|
|
1666
1668
|
.take(historyLimit * 2 + 16);
|
|
1667
|
-
for (
|
|
1668
|
-
await ctx.db.delete(
|
|
1669
|
+
for (const stale of recent.slice(historyLimit * 2)) {
|
|
1670
|
+
await ctx.db.delete(stale._id);
|
|
1669
1671
|
}
|
|
1670
1672
|
return { ok: true };
|
|
1671
1673
|
},
|
package/dist/worldPresence.d.ts
CHANGED
|
@@ -18,7 +18,7 @@ export interface WorldPresenceRecord extends PresenceResidentRow {
|
|
|
18
18
|
rotationY: number;
|
|
19
19
|
rotationPitch: number;
|
|
20
20
|
lastSeenAt: number;
|
|
21
|
-
lastWorldSnapshotAt?: number;
|
|
21
|
+
lastWorldSnapshotAt?: number | undefined;
|
|
22
22
|
}
|
|
23
23
|
/** World chunk size and host-owned pose, checkpoint and retention policies. */
|
|
24
24
|
export interface WorldPresenceOptions {
|
package/dist/worldPresence.js
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { definedOnly } from "@jgengine/core/data/definedOnly";
|
|
1
2
|
import { DEFAULT_POSE_SYNC_RULES, decidePoseSync, shouldPersistWorldSnapshot } from "@jgengine/core/multiplayer/presenceModel";
|
|
2
3
|
import { chunkKeyAt, chunkKeysAround, parseChunkKey, DEFAULT_CHUNK_SIZE } from "@jgengine/core/runtime/worldChunks";
|
|
3
4
|
function record(row) {
|
|
@@ -47,7 +48,7 @@ export function createWorldPresenceStore(options = {}) {
|
|
|
47
48
|
label: args.label ?? existing?.label, x: args.position.x, y: args.position.y, z: args.position.z,
|
|
48
49
|
rotationY: 0, rotationPitch: 0, chunkKey: chunkKeyAt([args.position.x, args.position.y, args.position.z], size),
|
|
49
50
|
updatedAt: nowMs, revokedAt: undefined };
|
|
50
|
-
const id = existing ? existing._id : await ctx.db.insert("jgPoses", patch);
|
|
51
|
+
const id = existing ? existing._id : await ctx.db.insert("jgPoses", definedOnly(patch));
|
|
51
52
|
if (existing)
|
|
52
53
|
await ctx.db.patch(id, patch);
|
|
53
54
|
return record((await ctx.db.get("jgPoses", id)));
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@jgengine/convex",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.50.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,7 +30,7 @@
|
|
|
30
30
|
"check-types": "tsgo --noEmit -p tsconfig.json"
|
|
31
31
|
},
|
|
32
32
|
"dependencies": {
|
|
33
|
-
"@jgengine/core": "^0.
|
|
33
|
+
"@jgengine/core": "^0.50.0"
|
|
34
34
|
},
|
|
35
35
|
"peerDependencies": {
|
|
36
36
|
"convex": "^1.31.2",
|
package/skills/jgengine/SKILL.md
CHANGED
|
@@ -7,7 +7,7 @@ description: Route game work to the smallest useful JGengine guidance set.
|
|
|
7
7
|
|
|
8
8
|
This skill decides what to load; it is not an engine manual. Package map, commands, layering, and license live in the JGengine README (repo-root [`README.md`](../../../README.md) in the monorepo, [jgengine.com](https://jgengine.com) from a created project); open it when you need one of those facts.
|
|
9
9
|
|
|
10
|
-
A created project installs only `jgengine`, `jgengine-editor`, `jgengine-verify`, `game-design`, and `jgengine-ui`. Run `npx jgengine skills -p --all` before loading any other domain named below. Those projects do not ship `api.md`; read exact signatures from the `.d.ts` files under `node_modules/@jgengine/<pkg>/dist/`.
|
|
10
|
+
A created project installs only `jgengine`, `create-game-jg`, `jgengine-editor`, `jgengine-verify`, `game-design`, and `jgengine-ui`. Run `npx jgengine skills -p --all` before loading any other domain named below. Those projects do not ship `api.md`; read exact signatures from the `.d.ts` files under `node_modules/@jgengine/<pkg>/dist/`.
|
|
11
11
|
|
|
12
12
|
Created projects pin their scaffolding CLI as a local devDependency, separately from SDK versions. Run `npx jgengine doctor` after SDK upgrades to check CLI compatibility. Skill installation preserves newer or differing unversioned copies; update the project CLI first, and use `skills -p --force` only when replacement is intended. Each packaged skill install records its CLI and SDK versions in `.jgengine-version.json`.
|
|
13
13
|
|
|
@@ -27,7 +27,7 @@ finished solution.
|
|
|
27
27
|
|
|
28
28
|
## 1. Establish the target
|
|
29
29
|
|
|
30
|
-
|
|
30
|
+
A new game or a big new feature starts with the `create-game-jg` skill: agree the direction with the user, map every feature with `jgengine has`, write `PLAN.md` with its design brief, then `jgengine create` and build it one milestone per PR, each the next 10 minutes of play. Load it before anything below.
|
|
31
31
|
|
|
32
32
|
Treat the pitch as a unique composition of needs, not a genre to fill in — never reach for a genre kit, preset, archetype, or class template ("default sports car", "default RPG", "default FPS") in the SDK or skills (see [AGENTS.md](../../../AGENTS.md)). Use `game-design` to turn the pitch into a testable player promise, verbs, loops, progression, failure, and completion scenario. Use `level-design` when play depends on authored spaces, routes, encounters, exploration, or spatial pacing. Capture only decisions that change architecture:
|
|
33
33
|
|
|
@@ -38,7 +38,7 @@ 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
|
-
|
|
41
|
+
Start a game only through `create-game-jg` (it picks the template and runs `jgengine create`); never copy `games/*`. The game's `jgengine.json` says what it is and uses (template, editor, `@jgengine` packages, and in-repo its adoption debt and boot probes). 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
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
|
|
package/skills/jgengine/api.md
CHANGED
|
@@ -6,7 +6,7 @@
|
|
|
6
6
|
|
|
7
7
|
- `CHANGELOG` (const): const CHANGELOG: Record<string, ChangelogEntry> — Per-version engine changelog keyed by semver string (e.g. `"0.10.0"`).
|
|
8
8
|
- `ChangelogEntry` (interface): interface ChangelogEntry — One release's migrate steps plus added/changed/removed notes (typed mirror of CHANGELOG.md).
|
|
9
|
-
- `VERSION` (const): const VERSION: "0.
|
|
9
|
+
- `VERSION` (const): const VERSION: "0.50.0" — Installed `@jgengine/core` semver - compare against {@link CHANGELOG} keys when migrating.
|
|
10
10
|
|
|
11
11
|
## @jgengine/core/authoring
|
|
12
12
|
|
|
@@ -277,6 +277,7 @@
|
|
|
277
277
|
## @jgengine/core/devtools/frameBreakdown
|
|
278
278
|
|
|
279
279
|
- `DrawCost` (interface): interface DrawCost — One object or material's share of the draws: totals while gathering, per-frame averages in a summary.
|
|
280
|
+
- `FrameBreakdown` (interface): interface FrameBreakdown — A running breakdown, as `installFrameBreakdown` returns it and `window.__jgFrameBreakdown` holds it.
|
|
280
281
|
- `FrameBreakdownRecord` (interface): interface FrameBreakdownRecord — One frame, as the hooks saw it. GPU times arrive a few frames late; `gpu` is null until they do.
|
|
281
282
|
- `FrameBreakdownSummary` (interface): interface FrameBreakdownSummary — A window of frames, summarized for a person or a diff.
|
|
282
283
|
- `HITCH_MS` (const): const HITCH_MS: 33 — Where each frame goes: the record of one frame (CPU per system, GPU per render pass, draws, shader compiles, uploads, heap) and the summary of a window of them (percentiles, the costliest systems, passes, objects and materials, and every hitch with what caused it). This file is the arithmetic; `frameBreakdown.draw.ts` fills the records from a live renderer.
|
|
@@ -286,9 +287,6 @@
|
|
|
286
287
|
|
|
287
288
|
## @jgengine/core/devtools/frameBreakdown.draw
|
|
288
289
|
|
|
289
|
-
- `BreakdownRenderer` (interface): interface BreakdownRenderer — What the breakdown needs from a three.js WebGLRenderer.
|
|
290
|
-
- `BreakdownStore` (interface): interface BreakdownStore — What it needs from the r3f store: the subscriber list and the subscribe call.
|
|
291
|
-
- `FrameBreakdown` (interface): interface FrameBreakdown — A running breakdown.
|
|
292
290
|
- `measureComposerPasses` (function): function measureComposerPasses(composer: { passes: readonly object[] }): void — Time each post pass of a composer by name, CPU and GPU. Cheap when no breakdown is installed; call it each frame before `composer.render` so passes added later are timed too.
|
|
293
291
|
|
|
294
292
|
## @jgengine/core/devtools/frameSampler.view
|
|
@@ -438,7 +436,7 @@
|
|
|
438
436
|
|
|
439
437
|
- `CHANGELOG` (const): const CHANGELOG: Record<string, ChangelogEntry> — Per-version engine changelog keyed by semver string (e.g. `"0.10.0"`).
|
|
440
438
|
- `ChangelogEntry` (interface): interface ChangelogEntry — One release's migrate steps plus added/changed/removed notes (typed mirror of CHANGELOG.md).
|
|
441
|
-
- `VERSION` (const): const VERSION: "0.
|
|
439
|
+
- `VERSION` (const): const VERSION: "0.50.0" — Installed `@jgengine/core` semver - compare against {@link CHANGELOG} keys when migrating.
|
|
442
440
|
|
|
443
441
|
## @jgengine/core/runtime/adapter
|
|
444
442
|
|
|
@@ -576,7 +574,7 @@
|
|
|
576
574
|
## @jgengine/core/runtime/hostPersistence
|
|
577
575
|
|
|
578
576
|
- `FEED_RING_LIMIT` (const): const FEED_RING_LIMIT: 20 — ⚠ undocumented
|
|
579
|
-
- `GameServerRecord` (type): type GameServerRecord = { serverId: string; gameId: string; status: GameServerStatus; mode?: string; modeConfig?: unknown; label?: string; visibility?: SessionVisibility; joinCode?: string; tags?: string[]; memberUserIds: string[]; slotsPerServer: number; save:
|
|
577
|
+
- `GameServerRecord` (type): type GameServerRecord = { serverId: string; gameId: string; status: GameServerStatus; mode?: string | undefined; modeConfig?: unknown; label?: string; visibility?: SessionVisibility | undefined; joinCode?: string | undefined; tags?: string[]; memberUserIds: string[]; slotsPerServer: number; save: Sa… — ⚠ undocumented
|
|
580
578
|
- `GameServerStatus` (type): type GameServerStatus = "open" | "running" | "closed" — ⚠ undocumented · used by `statusAfterLeave` (@jgengine/core/runtime/hostPolicy): Status after a leave: empty rooms reopen; non-empty rooms keep their current status.
|
|
581
579
|
- `HostPersistence` (type): type HostPersistence = { savePlan?: (plan: ServerPersistPlan) => Promise<void>; resetScenario?: (reset: NormalizedScenarioReset) => Promise<void>; loadServer: (serverId: string) => Promise<GameServerRecord | null>; saveServer: (record: GameServerRecord) => Promise<void>; listServers: (gameId: string… — ⚠ undocumented · used by `memoryPersistence` (@jgengine/node): Creates an in-memory `HostPersistence` implementation, useful for tests and ephemeral hosts.
|
|
582
580
|
- `LEADERBOARD_PENDING_KEY` (const): const LEADERBOARD_PENDING_KEY: "leaderboardPending" — ⚠ undocumented
|
|
@@ -750,7 +748,7 @@
|
|
|
750
748
|
- `GameRuntimeFeeds` (type): type GameRuntimeFeeds = { subscribeServer: ( serverId: string, onChange: (view: GameRuntimeServerView | null) => void, ) => FeedUnsubscribe; subscribePlayer: ( args: { serverId: string }, onChange: (view: GameRuntimePlayerView | null) => void, ) => FeedUnsubscribe; subscribeFeed: ( args: { serverId:… — ⚠ undocumented
|
|
751
749
|
- `GameRuntimePlayerView` (type): type GameRuntimePlayerView = { userId: string; gameId: string; playerState: unknown; updatedAt: number; } — ⚠ undocumented
|
|
752
750
|
- `GameRuntimeServerView` (type): type GameRuntimeServerView = { serverId: string; gameId: string; revision: number; memberUserIds: string[]; serverState: unknown | WorldSyncFrame; updatedAt: number; } — ⚠ undocumented
|
|
753
|
-
- `GameRuntimeTransport` (type): type GameRuntimeTransport = { /** * Join a server. `sessionId` names this client session (one tab, one mounted view) so the host keeps * the user a member while any of their sessions is still live. */ joinServer: (args: { gameId: string; serverId?: string; role?: MultiplayerRole;
|
|
751
|
+
- `GameRuntimeTransport` (type): type GameRuntimeTransport = { /** * Join a server. `sessionId` names this client session (one tab, one mounted view) so the host keeps * the user a member while any of their sessions is still live. */ joinServer: (args: { gameId: string; serverId?: string | undefined; role?: MultiplayerRole; session… — ⚠ undocumented · used by `useServerSession` (@jgengine/core/session/useServerSession.view): Joins a host and exposes a retryable blocking state until membership is confirmed.
|
|
754
752
|
- `JoinServerOutcome` (type): type JoinServerOutcome = | (JoinServerResult & { ok: true }) | { ok: false; reason: "full" | "closed" | "unauthorized" } — Join failures are returned to the caller so the triggering surface can offer retry.
|
|
755
753
|
- `JoinServerResult` (type): type JoinServerResult = { serverId: string; isNew: boolean; resumeTicket?: ResumeTicket; } — ⚠ undocumented
|
|
756
754
|
- `LiveGameBackend` (type): type LiveGameBackend<TPresenceRow = unknown, TPresenceLocation = unknown, TGameId extends string = string> = GameBackend<TPresenceRow, TPresenceLocation, TGameId> & { presenceSync: PresenceSync; pushFeedEntry: (args: { serverId: string; action: string; entry: unknown }) => Promise<void>; chatSyncFor… — ⚠ undocumented
|
|
@@ -5,14 +5,14 @@
|
|
|
5
5
|
## @jgengine/convex
|
|
6
6
|
|
|
7
7
|
- `ConvexBackend` (type): type ConvexBackend<TPresenceRow = unknown, TPresenceLocation = unknown, TGameId extends string = string> = LiveGameBackend<TPresenceRow, TPresenceLocation, TGameId> & { leaderboard: ConvexLeaderboardReads; } — ⚠ undocumented
|
|
8
|
-
- `ConvexBackendOptions` (type): type ConvexBackendOptions<TRawPresenceRow extends { actorExternalId: string }, TPresenceRow> = { client: ConvexReactClient; gameId: string; userId: string; api?: ConvexGameApi; poseTuning?: PoseSyncTuning; presence?: { functions: ConvexPresenceFunctions; mapRow: (row:
|
|
8
|
+
- `ConvexBackendOptions` (type): type ConvexBackendOptions<TRawPresenceRow extends { actorExternalId: string }, TPresenceRow> = { client: ConvexReactClient; gameId: string; userId: string; api?: ConvexGameApi | undefined; poseTuning?: PoseSyncTuning | undefined; presence?: { functions: ConvexPresenceFunctions; mapRow: (row: TRawPre… — ⚠ undocumented
|
|
9
9
|
- `ConvexChatFunctions` (interface): interface ConvexChatFunctions { messages: FunctionReference<"query">; sendMessage: FunctionReference<"mutation"> } — ⚠ undocumented · used by `createConvexChatTransport`: Wires a game's Convex chat functions into the engine's ChatTransport contract: one live query per subscribed channel (the channel's recent h…
|
|
10
|
-
- `ConvexGameApi` (type): type ConvexGameApi = { runtime: { joinServer: FunctionReference< "mutation", "public", { gameId: string; serverId?: string; mode?: string; visibility?: "public" | "private"; joinCode?: string; externalId?: string; sessionId?: string; }, JoinServerOutcome >; leaveServer:
|
|
10
|
+
- `ConvexGameApi` (type): type ConvexGameApi = { runtime: { joinServer: FunctionReference< "mutation", "public", { gameId: string; serverId?: string | undefined; mode?: string; visibility?: "public" | "private"; joinCode?: string; externalId?: string | undefined; sessionId?: string; }, JoinServerOutcome >; leaveServer: Funct… — ⚠ undocumented
|
|
11
11
|
- `ConvexGameClient` (type): type ConvexGameClient = Pick<ConvexReactClient, "mutation" | "watchQuery"> — Structural client seam avoids requiring the engine and game to share a class instance type.
|
|
12
12
|
- `ConvexGameTransportConfig` (type): type ConvexGameTransportConfig = { gameId: string; userId?: string; } — ⚠ undocumented
|
|
13
13
|
- `ConvexLeaderboardReads` (type): type ConvexLeaderboardReads = ReturnType<typeof createConvexLeaderboardReads> — ⚠ undocumented
|
|
14
14
|
- `ConvexPresenceFunctions` (interface): interface ConvexPresenceFunctions — The Convex functions a **game** supplies for a world it owns: presence keyed by the game's own ids, including which residents exist while offline, which is game content and has no engine analogue. Not satisfied by `createPresenceFunctions` (`@jgengine/convex/server`) — that is the pose lane of a hosted `jgGameServers` server, backing `PresenceSync` rather than `PresenceTransport`.
|
|
15
|
-
- `ConvexRunCommandArgs` (type): type ConvexRunCommandArgs = RunCommandArgs & { externalId?: string; /** * How much of the server to hydrate before applying the command. Overrides whatever the * {@link CommandDef} declares through its own `scope`; omit both to hydrate the whole world. */ scope?: LoadSnapshotScope; /**
|
|
15
|
+
- `ConvexRunCommandArgs` (type): type ConvexRunCommandArgs = RunCommandArgs & { externalId?: string | undefined; /** * How much of the server to hydrate before applying the command. Overrides whatever the * {@link CommandDef} declares through its own `scope`; omit both to hydrate the whole world. */ scope?: LoadSnapshotScope; /** *… — Arguments of the `runCommand` mutation, and of the equivalent {@link GameServerHelpers.runCommand} helper.
|
|
16
16
|
- `ConvexSaveBackendOptions` (interface): interface ConvexSaveBackendOptions — Config for {@link createConvexSaveBackend} — the Convex client, optional function refs (defaults to the `saves.*` convention), and an optional key `namespace`.
|
|
17
17
|
- `ConvexSaveFunctions` (interface): interface ConvexSaveFunctions — 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.
|
|
18
18
|
- `DEFAULT_CONVEX_POSE_RULES` (const): const DEFAULT_CONVEX_POSE_RULES: PoseSyncRules — ⚠ undocumented
|
|
@@ -62,7 +62,7 @@
|
|
|
62
62
|
- `randomConvexPlayerId` (function): function randomConvexPlayerId(): string — ⚠ undocumented
|
|
63
63
|
- `rateLimit` (function): function rateLimit(ctx: JGMutationCtx, args: { key: string; windowMs: number; max: number; nowMs?: number }): Promise<{ ok: boolean; retryAfterMs: number }> — Consume one request in a fixed rate window; concurrent callers serialize on the indexed key.
|
|
64
64
|
- `recentChatMessages` (function): function recentChatMessages(ctx: { db: Pick<JGQueryCtx["db"], "query"> }, args: { serverId: string; channelId?: string; limit?: number }): Promise<{ _id: Id<"jgChatMessages">; _creationTime: number; authorName?: string | undefined; serverId: string; userId: string; channelId: string; body: string; a… — Bounded recent chat for a resolved server; authorization belongs to the calling query.
|
|
65
|
-
- `resolveConvexMultiplayer` (function): function resolveConvexMultiplayer(args: { game: GameDefinition; gameId: string; url?: string; client?: ConvexReactClient; api?: ConvexGameApi; userId?: string; force?: boolean; feedActions?: string[]; poseTuning?: PoseSyncTuning; }): MultiplayerSession | null — ⚠ undocumented
|
|
65
|
+
- `resolveConvexMultiplayer` (function): function resolveConvexMultiplayer(args: { game: GameDefinition; gameId: string; url?: string | undefined; client?: ConvexReactClient; api?: ConvexGameApi; userId?: string; force?: boolean; feedActions?: string[]; poseTuning?: PoseSyncTuning; }): MultiplayerSession | null — ⚠ undocumented
|
|
66
66
|
- `sendChatMessage` (function): function sendChatMessage(ctx: JGMutationCtx, args: { serverId: string; userId: string; body: string; channelId?: string; authorName?: string; maxBodyLength?: number; minIntervalMs?: number }): Promise<ChatSendOutcome> — Write validated chat in a host transaction after the caller resolves its actor and world.
|
|
67
67
|
- `validateSlotsPerServer` (function): function validateSlotsPerServer(slotsPerServer: number, topology: "rooms" | "shared" = "rooms"): { ok: true } | { ok: false; reason: string } — Whether a `slotsPerServer` value is a capacity a single hosted server row can actually hold.
|
|
68
68
|
- `watchConvexQuery` (function): function watchConvexQuery<TArgs extends DefaultFunctionArgs, TResult, TView>(client: ConvexGameClient, query: FunctionReference<"query", "public", TArgs, TResult>, args: TArgs, toView: (result: TResult) => TView, onChange: (view: TView) => void): () => void — ⚠ undocumented
|
|
@@ -87,13 +87,13 @@
|
|
|
87
87
|
## @jgengine/convex/createConvexBackend
|
|
88
88
|
|
|
89
89
|
- `ConvexBackend` (type): type ConvexBackend<TPresenceRow = unknown, TPresenceLocation = unknown, TGameId extends string = string> = LiveGameBackend<TPresenceRow, TPresenceLocation, TGameId> & { leaderboard: ConvexLeaderboardReads; } — ⚠ undocumented
|
|
90
|
-
- `ConvexBackendOptions` (type): type ConvexBackendOptions<TRawPresenceRow extends { actorExternalId: string }, TPresenceRow> = { client: ConvexReactClient; gameId: string; userId: string; api?: ConvexGameApi; poseTuning?: PoseSyncTuning; presence?: { functions: ConvexPresenceFunctions; mapRow: (row:
|
|
90
|
+
- `ConvexBackendOptions` (type): type ConvexBackendOptions<TRawPresenceRow extends { actorExternalId: string }, TPresenceRow> = { client: ConvexReactClient; gameId: string; userId: string; api?: ConvexGameApi | undefined; poseTuning?: PoseSyncTuning | undefined; presence?: { functions: ConvexPresenceFunctions; mapRow: (row: TRawPre… — ⚠ undocumented
|
|
91
91
|
- `ConvexLeaderboardReads` (type): type ConvexLeaderboardReads = ReturnType<typeof createConvexLeaderboardReads> — ⚠ undocumented
|
|
92
92
|
- `createConvexBackend` (function): function createConvexBackend<TRawPresenceRow extends { actorExternalId: string } = { actorExternalId: string }, TPresenceRow = unknown, TPresenceLocation = unknown, TGameId extends string = string>(options: ConvexBackendOptions<TRawPresenceRow, TPresenceRow>): ConvexBackend<TPresenceRow, TPresenceLo… — ⚠ undocumented
|
|
93
93
|
|
|
94
94
|
## @jgengine/convex/createConvexGameTransport
|
|
95
95
|
|
|
96
|
-
- `ConvexGameApi` (type): type ConvexGameApi = { runtime: { joinServer: FunctionReference< "mutation", "public", { gameId: string; serverId?: string; mode?: string; visibility?: "public" | "private"; joinCode?: string; externalId?: string; sessionId?: string; }, JoinServerOutcome >; leaveServer:
|
|
96
|
+
- `ConvexGameApi` (type): type ConvexGameApi = { runtime: { joinServer: FunctionReference< "mutation", "public", { gameId: string; serverId?: string | undefined; mode?: string; visibility?: "public" | "private"; joinCode?: string; externalId?: string | undefined; sessionId?: string; }, JoinServerOutcome >; leaveServer: Funct… — ⚠ undocumented
|
|
97
97
|
- `ConvexGameClient` (type): type ConvexGameClient = Pick<ConvexReactClient, "mutation" | "watchQuery"> — Structural client seam avoids requiring the engine and game to share a class instance type.
|
|
98
98
|
- `ConvexGameTransportConfig` (type): type ConvexGameTransportConfig = { gameId: string; userId?: string; } — ⚠ undocumented
|
|
99
99
|
- `createConvexChatSync` (function): function createConvexChatSync(client: ConvexGameClient, api: ConvexGameApi, config: ConvexGameTransportConfig, serverId: string): ChatSync — ⚠ undocumented
|
|
@@ -119,11 +119,11 @@
|
|
|
119
119
|
## @jgengine/convex/resolveConvexMultiplayer
|
|
120
120
|
|
|
121
121
|
- `randomConvexPlayerId` (function): function randomConvexPlayerId(): string — ⚠ undocumented
|
|
122
|
-
- `resolveConvexMultiplayer` (function): function resolveConvexMultiplayer(args: { game: GameDefinition; gameId: string; url?: string; client?: ConvexReactClient; api?: ConvexGameApi; userId?: string; force?: boolean; feedActions?: string[]; poseTuning?: PoseSyncTuning; }): MultiplayerSession | null — ⚠ undocumented
|
|
122
|
+
- `resolveConvexMultiplayer` (function): function resolveConvexMultiplayer(args: { game: GameDefinition; gameId: string; url?: string | undefined; client?: ConvexReactClient; api?: ConvexGameApi; userId?: string; force?: boolean; feedActions?: string[]; poseTuning?: PoseSyncTuning; }): MultiplayerSession | null — ⚠ undocumented
|
|
123
123
|
|
|
124
124
|
## @jgengine/convex/server
|
|
125
125
|
|
|
126
|
-
- `ConvexRunCommandArgs` (type): type ConvexRunCommandArgs = RunCommandArgs & { externalId?: string; /** * How much of the server to hydrate before applying the command. Overrides whatever the * {@link CommandDef} declares through its own `scope`; omit both to hydrate the whole world. */ scope?: LoadSnapshotScope; /**
|
|
126
|
+
- `ConvexRunCommandArgs` (type): type ConvexRunCommandArgs = RunCommandArgs & { externalId?: string | undefined; /** * How much of the server to hydrate before applying the command. Overrides whatever the * {@link CommandDef} declares through its own `scope`; omit both to hydrate the whole world. */ scope?: LoadSnapshotScope; /** *… — Arguments of the `runCommand` mutation, and of the equivalent {@link GameServerHelpers.runCommand} helper.
|
|
127
127
|
- `DEFAULT_CONVEX_POSE_RULES` (const): const DEFAULT_CONVEX_POSE_RULES: PoseSyncRules — ⚠ undocumented
|
|
128
128
|
- `DEFAULT_MAX_SERVERS_PER_TICK` (const): const DEFAULT_MAX_SERVERS_PER_TICK: 32 — How many running servers one `tickActiveServers` transaction may hydrate, tick, and persist.
|
|
129
129
|
- `EnsureJoinedOutcome` (type): type EnsureJoinedOutcome = | { ok: true; serverId: GenericId<"jgGameServers">; isNew: boolean } | { ok: false; reason: "full" | "closed" | "unauthorized" } — Outcome of a join: the server joined and whether the player's profile was created, or why it was refused.
|
|
@@ -200,7 +200,7 @@
|
|
|
200
200
|
- `createFeedWriteGate` (function): function createFeedWriteGate(allowedActions: readonly string[] = []): FeedWriteGate — ⚠ undocumented
|
|
201
201
|
- `createLocalVoiceTransport` (function): function createLocalVoiceTransport(options?: { userId?: string }): { transport: VoiceTransport; participants(channelId: string): readonly VoiceParticipant[]; } — ⚠ undocumented
|
|
202
202
|
- `createPoseSyncGate` (function): function createPoseSyncGate(tuning: PoseSyncTuning = DEFAULT_POSE_SYNC_TUNING): PoseSyncGate — ⚠ undocumented
|
|
203
|
-
- `createPushToTalk` (function): function createPushToTalk(config?: { mode?: PushToTalkMode; onChange?: (transmitting: boolean) => void; }): PushToTalk — ⚠ undocumented
|
|
203
|
+
- `createPushToTalk` (function): function createPushToTalk(config?: { mode?: PushToTalkMode | undefined; onChange?: (transmitting: boolean) => void; }): PushToTalk — ⚠ undocumented
|
|
204
204
|
- `findByJoinCode` (function): function findByJoinCode(listings: readonly SessionListing[], code: string): SessionListing | null — ⚠ undocumented
|
|
205
205
|
- `fromRuntimeObjectRow` (function): function fromRuntimeObjectRow(row: RuntimeObjectRow): SceneObject — Inverse of {@link toRuntimeObjectRow}: rebuild the live placed object a host persisted.
|
|
206
206
|
- `normalizeJoinCode` (function): function normalizeJoinCode(code: string): string — ⚠ undocumented
|
|
@@ -286,7 +286,7 @@
|
|
|
286
286
|
- `DEFAULT_FEED_ACTIONS` (const): const DEFAULT_FEED_ACTIONS: string[] — ⚠ undocumented
|
|
287
287
|
- `PeerShellFactories` (interface): interface PeerShellFactories — Alternate peer factories for hosts that provide their own WebRTC implementation or transport pipe.
|
|
288
288
|
- `ResolvePeerShellMultiplayerArgs` (interface): interface ResolvePeerShellMultiplayerArgs — Peer bootstrap options; passing a playable hosts its authoritative GameContext world.
|
|
289
|
-
- `ResolveShellMultiplayerArgs` (type): type ResolveShellMultiplayerArgs = { game: GameDefinition; gameId: string; url?: string | undefined; userId?: string; force?: boolean; feedActions?: string[]; } — ⚠ undocumented
|
|
289
|
+
- `ResolveShellMultiplayerArgs` (type): type ResolveShellMultiplayerArgs = { game: GameDefinition; gameId: string; url?: string | undefined; userId?: string | undefined; force?: boolean; feedActions?: string[]; } — ⚠ undocumented
|
|
290
290
|
- `ShellMultiplayer` (type): type ShellMultiplayer = MultiplayerSession — ⚠ undocumented · used by `resolvePeerShellMultiplayer`: Opens a peer session.
|
|
291
291
|
- `randomPlayerId` (function): function randomPlayerId(): string — ⚠ undocumented
|
|
292
292
|
- `resolvePeerShellMultiplayer` (function): function resolvePeerShellMultiplayer(args: ResolvePeerShellMultiplayerArgs): Promise<ShellMultiplayer & { close: () => Promise<void> }> — Opens a peer session. With `playable`, one host runs the game loop and guests mirror its shared world. Await `close()` to finish authoritative persistence. Same-origin rooms use BroadcastChannel signaling.
|
|
@@ -319,7 +319,7 @@
|
|
|
319
319
|
- `ActivePresenceResolution` (interface): interface ActivePresenceResolution<T> { active: T | null; extras: T[] } — ⚠ undocumented
|
|
320
320
|
- `DEFAULT_POSE_SYNC_RULES` (const): const DEFAULT_POSE_SYNC_RULES: PoseSyncRules — Canonical client-authoritative pose-sync tuning shared by every host transport (WS, Convex).
|
|
321
321
|
- `DEFAULT_SPAWN_ORIGIN` (const): const DEFAULT_SPAWN_ORIGIN: { readonly x: 0; readonly y: 0; readonly z: 0; } — ⚠ undocumented
|
|
322
|
-
- `IncomingPose` (interface): interface IncomingPose { position: {x: number; z: number; y?: number}; rotationY?: number; rotationPitch?: number; appearance?: Record<string, string> } — ⚠ undocumented
|
|
322
|
+
- `IncomingPose` (interface): interface IncomingPose { position: {x: number; z: number; y?: number}; rotationY?: number; rotationPitch?: number | undefined; appearance?: Record<string, string> } — ⚠ undocumented
|
|
323
323
|
- `PoseSyncDecision` (interface): interface PoseSyncDecision { position: {x: number; y: number; z: number}; rotationY: number; rotationPitch: number; appearance?: Record<string, string> | undefined; changed: boolean; refreshKeepAlive: boolean } — ⚠ undocumented
|
|
324
324
|
- `PoseSyncRules` (interface): interface PoseSyncRules { maxSpeed: number; minIntervalMs?: number; maxVerticalOffset: number; floorY?: number; minElapsedSec: number; maxElapsedSec: number; keepAliveRefreshMs: number } — ⚠ undocumented · used by `DEFAULT_POSE_SYNC_RULES`: Canonical client-authoritative pose-sync tuning shared by every host transport (WS, Convex).
|
|
325
325
|
- `PresencePoseState` (interface): interface PresencePoseState { position: {x: number; y: number; z: number}; rotationY: number; rotationPitch?: number; lastSeenAtMs?: number; appearance?: Record<string, string> | undefined } — ⚠ undocumented
|
|
@@ -354,7 +354,7 @@
|
|
|
354
354
|
- `VoiceRoute` (interface): interface VoiceRoute { fromUserId: string; channelId: string; gain: number } — ⚠ undocumented
|
|
355
355
|
- `VoiceTransport` (interface): interface VoiceTransport — Signaling seam for voice: who is in a channel and which media stream descriptor they published. The media plane (WebRTC, SFU, or anything else that moves audio bytes) stays behind this seam, host-supplied — the engine never touches it. subscribers delivers the channel roster on every change, starting with the current roster.
|
|
356
356
|
- `createLocalVoiceTransport` (function): function createLocalVoiceTransport(options?: { userId?: string }): { transport: VoiceTransport; participants(channelId: string): readonly VoiceParticipant[]; } — ⚠ undocumented
|
|
357
|
-
- `createPushToTalk` (function): function createPushToTalk(config?: { mode?: PushToTalkMode; onChange?: (transmitting: boolean) => void; }): PushToTalk — ⚠ undocumented
|
|
357
|
+
- `createPushToTalk` (function): function createPushToTalk(config?: { mode?: PushToTalkMode | undefined; onChange?: (transmitting: boolean) => void; }): PushToTalk — ⚠ undocumented
|
|
358
358
|
|
|
359
359
|
## @jgengine/core/runtime/worldProjection
|
|
360
360
|
|