@diffenderfer-games/hub 2.0.0-next.8 → 2.0.0-next.9

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.
Files changed (69) hide show
  1. package/CHANGELOG.md +32 -13
  2. package/dist/auto.d.ts +2 -2
  3. package/dist/avatar-pixi.d.ts +19 -1
  4. package/dist/avatar-pixi.d.ts.map +1 -1
  5. package/dist/avatar-pixi.js +37 -18
  6. package/dist/avatar-pixi.js.map +1 -1
  7. package/dist/avatar-svg.d.ts +1 -1
  8. package/dist/avatar-svg.js +1 -1
  9. package/dist/avatar-three.d.ts +1 -1
  10. package/dist/avatar.d.ts +3 -3
  11. package/dist/avatar.js +20 -7
  12. package/dist/avatar.js.map +1 -1
  13. package/dist/doctor/cli.mjs +58 -4
  14. package/dist/doctor/cli.mjs.map +1 -1
  15. package/dist/index.d.ts +2 -2
  16. package/dist/shared/animator.d.ts +1 -1
  17. package/dist/shared/art.js +1 -1
  18. package/dist/shared/atlas.d.ts +5 -1
  19. package/dist/shared/atlas.d.ts.map +1 -1
  20. package/dist/shared/avatar.d.ts +10 -4
  21. package/dist/shared/avatar.d.ts.map +1 -1
  22. package/dist/shared/bridge.js +2 -2
  23. package/dist/shared/bridge.js.map +1 -1
  24. package/dist/shared/client-version.gen.js +1 -1
  25. package/dist/shared/daily.js +1 -1
  26. package/dist/shared/describe.d.ts +1 -1
  27. package/dist/shared/describe.d.ts.map +1 -1
  28. package/dist/shared/describe.js +1 -1
  29. package/dist/shared/index.d.ts +2 -2
  30. package/dist/shared/live-canvas.js +1 -1
  31. package/dist/shared/overlays.js +1 -1
  32. package/dist/shared/overlays.js.map +1 -1
  33. package/dist/shared/pose-key.js +2 -2
  34. package/dist/shared/pose-key.js.map +1 -1
  35. package/dist/shared/social.d.ts +19 -4
  36. package/dist/shared/social.d.ts.map +1 -1
  37. package/dist/shared/src.js +6 -0
  38. package/dist/shared/src.js.map +1 -1
  39. package/dist/shared/svg-markup.js +430 -21
  40. package/dist/shared/svg-markup.js.map +1 -1
  41. package/dist/shared/svg.js +2 -2
  42. package/dist/shared/svg.js.map +1 -1
  43. package/dist/shared/types.d.ts +9 -1
  44. package/dist/shared/types.d.ts.map +1 -1
  45. package/dist/social.d.ts +2 -2
  46. package/package.json +3 -2
  47. package/src/avatar/adapters/pixi-crowd.ts +45 -18
  48. package/src/avatar/atlas.ts +16 -6
  49. package/src/avatar/layout.ts +1 -1
  50. package/src/avatar/outputs/canvas.ts +2 -2
  51. package/src/avatar/outputs/svg.ts +2 -2
  52. package/src/avatar/render/body.ts +2 -2
  53. package/src/avatar/render/compose.ts +25 -8
  54. package/src/avatar/render/frame.ts +15 -2
  55. package/src/avatar/render/types.ts +9 -0
  56. package/src/avatar/render/walker-parts.ts +121 -0
  57. package/src/avatar/render/walker.ts +199 -0
  58. package/src/avatar.ts +2 -1
  59. package/src/core/rest/routes.gen.ts +1 -0
  60. package/src/core/turns/your-turn-buzz.ts +69 -69
  61. package/src/core/version/client-version.gen.ts +1 -1
  62. package/src/doctor/checks.ts +2 -2
  63. package/src/doctor/cli.ts +5 -2
  64. package/src/doctor/world-check.ts +61 -0
  65. package/src/index.ts +68 -68
  66. package/src/realtime/runtime-api.ts +5 -2
  67. package/src/realtime/types.ts +1 -0
  68. package/src/realtime/world-types.ts +20 -0
  69. package/src/turns-rules.ts +12 -12
@@ -1,69 +1,69 @@
1
- /**
2
- * "Your turn" buzz: a short vibration when this player becomes due in an
3
- * online game whose page they have open and visible (a phone held while
4
- * waiting). Every online game gets it from `TurnGame`, with no game code.
5
- *
6
- * It buzzes only when the player **becomes** due on a live update: not for
7
- * the next step of their own turn, not for a move they may make out of turn
8
- * (`mayAct`), not for a game they just opened or a catch-up after being
9
- * away, and not in a game they muted. It stays quiet when the player turned
10
- * it off (the menu's Settings, stored on this device), when the page asks
11
- * for less motion (the hub's `data-hub-motion="reduce"` or the system's
12
- * reduced motion), and where `navigator.vibrate` doesn't exist (iOS Safari).
13
- * A page can turn it off for itself (`hub.turns.setYourTurnBuzz(false)`) or
14
- * for one game (`TurnGame.setYourTurnBuzz(false)`).
15
- */
16
- import type { TurnSummary } from '@diffenderfer-games/hub-contract';
17
-
18
- /** The pattern: two short pulses, ms. */
19
- export const YOUR_TURN_BUZZ: readonly number[] = [60, 40, 60];
20
-
21
- /** Where this device keeps the player's choice (`off` turns it off). */
22
- export const YOUR_TURN_BUZZ_KEY = 'dg:your-turn-buzz';
23
-
24
- /** Whether going from `previous` to `next` makes this player newly due (the moment to buzz). */
25
- export function isNewlyDue(previous: Pick<TurnSummary, 'phase' | 'isMyTurn'>, next: Pick<TurnSummary, 'phase' | 'isMyTurn' | 'isMuted'>): boolean {
26
- const wasDue = previous.phase === 'playing' && previous.isMyTurn;
27
- return next.phase === 'playing' && next.isMyTurn && !wasDue && !next.isMuted;
28
- }
29
-
30
- /** The player's own switch on this device (on unless they turned it off). */
31
- export function readYourTurnBuzz(): boolean {
32
- try {
33
- return localStorage.getItem(YOUR_TURN_BUZZ_KEY) !== 'off';
34
- } catch {
35
- return true;
36
- }
37
- }
38
-
39
- /** Save the player's switch on this device. */
40
- export function saveYourTurnBuzz(isOn: boolean): void {
41
- try {
42
- localStorage.setItem(YOUR_TURN_BUZZ_KEY, isOn ? 'on' : 'off');
43
- } catch {
44
- // No storage: it stays on.
45
- }
46
- }
47
-
48
- /** Whether the page asks for less motion (the hub's setting, else the system's). */
49
- function prefersLessMotion(): boolean {
50
- if (typeof document !== 'undefined' && document.documentElement.dataset.hubMotion === 'reduce') {
51
- return true;
52
- }
53
- return typeof matchMedia === 'function' && matchMedia('(prefers-reduced-motion: reduce)').matches;
54
- }
55
-
56
- /** Buzz now, when this page is visible and nothing says not to; fails silently. Answers whether it buzzed. */
57
- export function buzzYourTurn(): boolean {
58
- if (typeof document === 'undefined' || document.visibilityState !== 'visible' || !readYourTurnBuzz() || prefersLessMotion()) {
59
- return false;
60
- }
61
- if (typeof navigator === 'undefined' || typeof navigator.vibrate !== 'function') {
62
- return false;
63
- }
64
- try {
65
- return navigator.vibrate([...YOUR_TURN_BUZZ]);
66
- } catch {
67
- return false;
68
- }
69
- }
1
+ /**
2
+ * "Your turn" buzz: a short vibration when this player becomes due in an
3
+ * online game whose page they have open and visible (a phone held while
4
+ * waiting). Every online game gets it from `TurnGame`, with no game code.
5
+ *
6
+ * It buzzes only when the player **becomes** due on a live update: not for
7
+ * the next step of their own turn, not for a move they may make out of turn
8
+ * (`mayAct`), not for a game they just opened or a catch-up after being
9
+ * away, and not in a game they muted. It stays quiet when the player turned
10
+ * it off (the menu's Settings, stored on this device), when the page asks
11
+ * for less motion (the hub's `data-hub-motion="reduce"` or the system's
12
+ * reduced motion), and where `navigator.vibrate` doesn't exist (iOS Safari).
13
+ * A page can turn it off for itself (`hub.turns.setYourTurnBuzz(false)`) or
14
+ * for one game (`TurnGame.setYourTurnBuzz(false)`).
15
+ */
16
+ import type { TurnSummary } from '@diffenderfer-games/hub-contract';
17
+
18
+ /** The pattern: two short pulses, ms. */
19
+ export const YOUR_TURN_BUZZ: readonly number[] = [60, 40, 60];
20
+
21
+ /** Where this device keeps the player's choice (`off` turns it off). */
22
+ export const YOUR_TURN_BUZZ_KEY = 'dg:your-turn-buzz';
23
+
24
+ /** Whether going from `previous` to `next` makes this player newly due (the moment to buzz). */
25
+ export function isNewlyDue(previous: Pick<TurnSummary, 'phase' | 'isMyTurn'>, next: Pick<TurnSummary, 'phase' | 'isMyTurn' | 'isMuted'>): boolean {
26
+ const wasDue = previous.phase === 'playing' && previous.isMyTurn;
27
+ return next.phase === 'playing' && next.isMyTurn && !wasDue && !next.isMuted;
28
+ }
29
+
30
+ /** The player's own switch on this device (on unless they turned it off). */
31
+ export function readYourTurnBuzz(): boolean {
32
+ try {
33
+ return localStorage.getItem(YOUR_TURN_BUZZ_KEY) !== 'off';
34
+ } catch {
35
+ return true;
36
+ }
37
+ }
38
+
39
+ /** Save the player's switch on this device. */
40
+ export function saveYourTurnBuzz(isOn: boolean): void {
41
+ try {
42
+ localStorage.setItem(YOUR_TURN_BUZZ_KEY, isOn ? 'on' : 'off');
43
+ } catch {
44
+ // No storage: it stays on.
45
+ }
46
+ }
47
+
48
+ /** Whether the page asks for less motion (the hub's setting, else the system's). */
49
+ function prefersLessMotion(): boolean {
50
+ if (typeof document !== 'undefined' && document.documentElement.dataset.hubMotion === 'reduce') {
51
+ return true;
52
+ }
53
+ return typeof matchMedia === 'function' && matchMedia('(prefers-reduced-motion: reduce)').matches;
54
+ }
55
+
56
+ /** Buzz now, when this page is visible and nothing says not to; fails silently. Answers whether it buzzed. */
57
+ export function buzzYourTurn(): boolean {
58
+ if (typeof document === 'undefined' || document.visibilityState !== 'visible' || !readYourTurnBuzz() || prefersLessMotion()) {
59
+ return false;
60
+ }
61
+ if (typeof navigator === 'undefined' || typeof navigator.vibrate !== 'function') {
62
+ return false;
63
+ }
64
+ try {
65
+ return navigator.vibrate([...YOUR_TURN_BUZZ]);
66
+ } catch {
67
+ return false;
68
+ }
69
+ }
@@ -1,3 +1,3 @@
1
1
  // Generated by tools/hub-client/generate.ts. Do not edit; run `npm run gen:hub-client`.
2
2
  /** This client's package version, sent as `x-hub-client` on REST calls and `client` in `hello`. */
3
- export const HUB_CLIENT_VERSION = '2.0.0-next.8';
3
+ export const HUB_CLIENT_VERSION = '2.0.0-next.9';
@@ -15,8 +15,8 @@ export type FindingLevel = 'error' | 'warning' | 'info';
15
15
  export interface Finding {
16
16
  /** How serious it is. */
17
17
  readonly level: FindingLevel;
18
- /** Which check found it: `version`, `vendored` or `manifest`. */
19
- readonly check: 'version' | 'vendored' | 'manifest';
18
+ /** Which check found it: `version`, `vendored`, `manifest` or `world`. */
19
+ readonly check: 'version' | 'vendored' | 'manifest' | 'world';
20
20
  /** What to tell the developer, with the fix. */
21
21
  readonly message: string;
22
22
  }
package/src/doctor/cli.ts CHANGED
@@ -11,7 +11,8 @@
11
11
  * `/_api/version` (`--hub`, default `https://diffenderfer.games`): outside
12
12
  * `supported` fails, `deprecated` warns (`--offline` skips this check);
13
13
  * - a stale vendored `src/hub/` copy or `scripts/sync-hub.mjs` warns;
14
- * - the `package.json` `game.*` block must match the contract's schema.
14
+ * - the `package.json` `game.*` block must match the contract's schema;
15
+ * - a `game.world.map` file must exist and be a valid `dg-world@1` map within the size limit.
15
16
  *
16
17
  * Exits 1 when any check fails.
17
18
  */
@@ -21,6 +22,7 @@ import { parseArgs } from 'node:util';
21
22
  import { HUB_CLIENT_VERSION } from '../core/version/client-version.gen';
22
23
  import { checkClientVersion, checkManifest, checkVendoredCopies, type Finding } from './checks';
23
24
  import { DEFAULT_HUB_TIMEOUT_MS, fetchHubVersion } from './hub-version-fetch';
25
+ import { checkWorld } from './world-check';
24
26
 
25
27
  const DEFAULT_HUB_URL = 'https://diffenderfer.games';
26
28
 
@@ -79,7 +81,8 @@ async function runDoctor(options: DoctorOptions): Promise<Finding[]> {
79
81
  }
80
82
  findings.push(...checkVendoredCopies(options.gameDir));
81
83
  try {
82
- findings.push(...checkManifest(readJsonFile(join(options.gameDir, 'package.json'))));
84
+ const packageJson = readJsonFile(join(options.gameDir, 'package.json'));
85
+ findings.push(...checkManifest(packageJson), ...checkWorld(options.gameDir, packageJson));
83
86
  } catch (failure) {
84
87
  findings.push({ level: 'error', check: 'manifest', message: `could not read package.json: ${failure instanceof Error ? failure.message : String(failure)}` });
85
88
  }
@@ -0,0 +1,61 @@
1
+ /**
2
+ * `hub-doctor`'s world check (docs/plans/world.md §14.6): a `game.world.map`
3
+ * file must exist inside the game, be within the size limit and be a valid
4
+ * `dg-world@1` map (the same `parseWorldMapText` the hub runs at load); a
5
+ * `game.world.generate.type` the hub doesn't know is a warning.
6
+ */
7
+ import { existsSync, readFileSync, statSync } from 'node:fs';
8
+ import { join } from 'node:path';
9
+ import { GamePackageJson } from '@diffenderfer-games/hub-contract';
10
+ import { BUILT_IN_WORLD_TYPES, WORLD_LIMITS, parseWorldMapText } from '@diffenderfer-games/world';
11
+ import type { Finding } from './checks';
12
+
13
+ /** The most map problems listed (the rest are counted). */
14
+ const MAX_LISTED_PROBLEMS = 20;
15
+
16
+ /** Checks the map file and world type `game.world` names. A manifest that doesn't parse is left to `checkManifest`. */
17
+ // eslint-disable-next-line no-restricted-syntax -- trust boundary: the game's package.json, parsed by GamePackageJson next
18
+ export function checkWorld(gameDir: string, packageJson: unknown): Finding[] {
19
+ const parsed = GamePackageJson.safeParse(packageJson);
20
+ const world = parsed.success ? parsed.data.game.world : undefined;
21
+ if (world === undefined) {
22
+ return [];
23
+ }
24
+ const findings: Finding[] = [];
25
+ const type = world.generate?.type;
26
+ if (type !== undefined && !BUILT_IN_WORLD_TYPES.some((known) => known.id === type)) {
27
+ findings.push({ level: 'warning', check: 'world', message: `game.world.generate.type: the hub has no world type "${type}"; use one of ${BUILT_IN_WORLD_TYPES.map((known) => known.id).join(', ')}` });
28
+ }
29
+ return world.map === undefined ? findings : [...findings, ...checkMapFile(gameDir, world.map)];
30
+ }
31
+
32
+ /** One map file's findings. */
33
+ export function checkMapFile(gameDir: string, mapFile: string): Finding[] {
34
+ const path = join(gameDir, mapFile);
35
+ const error = (message: string): Finding => ({ level: 'error', check: 'world', message: `game.world.map (${mapFile}): ${message}` });
36
+ if (!existsSync(path) || !statSync(path).isFile()) {
37
+ return [error('the file does not exist (build or commit it)')];
38
+ }
39
+ const bytes = statSync(path).size;
40
+ if (bytes > WORLD_LIMITS.bytes) {
41
+ return [error(`${String(bytes)} bytes is over the ${String(WORLD_LIMITS.bytes)}-byte limit (save layers as "rle")`)];
42
+ }
43
+ const result = parseWorldMapText(readFileSync(path, 'utf8'));
44
+ const listed = result.problems.slice(0, MAX_LISTED_PROBLEMS).map((problem): Finding => ({
45
+ level: problem.severity === 'error' ? 'error' : 'warning',
46
+ check: 'world',
47
+ message: `game.world.map (${mapFile}): ${problem.message}`,
48
+ }));
49
+ const more = result.problems.length - listed.length;
50
+ if (more > 0) {
51
+ listed.push(error(`and ${String(more)} more problems`));
52
+ }
53
+ if (!result.ok) {
54
+ return listed;
55
+ }
56
+ const { map } = result;
57
+ return [...listed, {
58
+ level: 'info', check: 'world',
59
+ message: `game.world.map (${mapFile}) is a valid dg-world@1 map: ${map.title}, ${String(map.width)} × ${String(map.height)}, ${String(map.objects.length)} objects, ${String(bytes)} bytes`,
60
+ }];
61
+ }
package/src/index.ts CHANGED
@@ -1,68 +1,68 @@
1
- /**
2
- * `@diffenderfer-games/hub`: the hub client for games on diffenderfer.games.
3
- *
4
- * ```ts
5
- * import { hub } from '@diffenderfer-games/hub';
6
- *
7
- * const { user } = await hub.me();
8
- * await hub.putSave('auto', { level: 7, gold: 120 });
9
- * await hub.submitScore('highscore', 9001, { title: 'High Score' });
10
- * ```
11
- *
12
- * In a hosted game the slug and API base are detected from the page; use
13
- * {@link createHub} for others. Every call rejects with a {@link HubError}
14
- * when it fails, except the fire-and-forget analytics (`recordPlay`,
15
- * `heartbeat`, `event`). When the hub can't be reached, reads come from an
16
- * offline cache and writes are queued in an outbox until it is back; games
17
- * queue their own operations with `hub.offline` (docs: "Offline and replay").
18
- *
19
- * The page-wide namespaces hang off `hub` as lazy getters (`hub.input`,
20
- * `hub.daily`, `hub.resume`, `hub.saved`, `hub.social`, `hub.mp`, `hub.rooms`, `hub.race`), and each has
21
- * its own entry (`@diffenderfer-games/hub/input`, ...) for games that want
22
- * only that part.
23
- *
24
- * @packageDocumentation
25
- */
26
- export { createHub, Hub, hub } from './core/hub';
27
- export {
28
- ContentRejectedError,
29
- HubError,
30
- OfflineError,
31
- SAVE_UNKNOWN_CODE,
32
- UNSUPPORTED_BY_HUB,
33
- type HubErrorCode,
34
- type HubErrorDetails,
35
- } from './core/errors';
36
- export type { SyncConflict, SyncResult } from './core/connection';
37
- export type { AuthResponse, ProfileUpdate, StatResult } from './core/hub-progress';
38
- export type { GrantBundle, TradeBundle, TradeId, TradeListOptions, TradeNoteOptions } from './core/hub-economy';
39
- export type { HubMenuControl } from './core/menu-control';
40
- export { HubResume, RESUME_DEBOUNCE_MS, RESUME_MAX_STATE_BYTES } from './core/resume/hub-resume';
41
- export { HubAvatar, type AvatarHost, type AvatarModule } from './core/avatar/hub-avatar';
42
- export type { AvatarChange, AvatarRef, DesignerResult, OpenDesignerOptions, RuntimeAvatarApi } from './core/avatar/avatar-types';
43
- export type { CarriesLook } from './core/avatar/avatar-ref';
44
- export type { ResumeBlocker, ResumeEntry, ResumeLoadOptions, ResumeProgress, ResumeRun, ResumeSaveInput } from './core/resume/resume-types';
45
- export { HubSaved, SAVED_CHANGE_QUIET_MS, type SavedChangeListener, type SavedOpenDetail, type SavedOpenHandler } from './core/saved/hub-saved';
46
- export { SAVED_CHANGE_EVENT, SAVED_KIND_PARAM, SAVED_OPEN_EVENT, SAVED_OPEN_PARAM, savedGameUrl } from './core/saved/saved-open';
47
- export type * from './core/saved/saved-types';
48
- export { HubTurns, TURN_LINK_PARAM, type TurnCreateOptions } from './core/turns/hub-turns';
49
- export { isNewlyDue, readYourTurnBuzz, saveYourTurnBuzz, YOUR_TURN_BUZZ, YOUR_TURN_BUZZ_KEY } from './core/turns/your-turn-buzz';
50
- export { TURN_CHAT_EVENT, TURN_EVENT, TURN_POLL_MS, TURN_QUIET_MS, TURN_RESYNC_EVENT, TurnGame, type TurnGameEvents, type TurnGameOptions, type TurnMoveEvent, type TurnOutcome } from './core/turns/turn-game';
51
- export type { OpenTurnGame, TurnChatLine, TurnDeadline, TurnGameView } from '@diffenderfer-games/hub-contract';
52
- export type { QueueOp, QueueOpInput, QueuedScoreOptions, ProfilePatch } from './core/offline/queue';
53
- export { OfflineApi } from './core/offline/offline-api';
54
- export { OUTBOX_FULL_CODE, type OfflineEventDetails, type OfflineEventName, type OfflineListener, type OfflineOp } from './core/offline/outbox';
55
- export type { OfflineHandler, OfflineKindDefinition, ReplayContext } from './core/offline/outbox-kinds';
56
- export { OUTBOX_MAX_CHARS, OUTBOX_MAX_ENTRIES } from './core/offline/outbox-store';
57
- export type { BufferedEvent } from './core/offline/events';
58
- export type { EndpointFields, EndpointName, EndpointReply } from './core/rest/endpoint-types';
59
- export { CLIENT_VERSION_HEADER } from './core/rest/rest-client';
60
- export type { ClientSupport } from './core/version/hub-version';
61
- export { HUB_CLIENT_VERSION } from './core/version/client-version.gen';
62
- export type * from './core/types';
63
- export * from './daily';
64
- export * from './input';
65
- export type * from './social';
66
- export type * from './mp';
67
- export type * from './race';
68
- export type * from './rooms';
1
+ /**
2
+ * `@diffenderfer-games/hub`: the hub client for games on diffenderfer.games.
3
+ *
4
+ * ```ts
5
+ * import { hub } from '@diffenderfer-games/hub';
6
+ *
7
+ * const { user } = await hub.me();
8
+ * await hub.putSave('auto', { level: 7, gold: 120 });
9
+ * await hub.submitScore('highscore', 9001, { title: 'High Score' });
10
+ * ```
11
+ *
12
+ * In a hosted game the slug and API base are detected from the page; use
13
+ * {@link createHub} for others. Every call rejects with a {@link HubError}
14
+ * when it fails, except the fire-and-forget analytics (`recordPlay`,
15
+ * `heartbeat`, `event`). When the hub can't be reached, reads come from an
16
+ * offline cache and writes are queued in an outbox until it is back; games
17
+ * queue their own operations with `hub.offline` (docs: "Offline and replay").
18
+ *
19
+ * The page-wide namespaces hang off `hub` as lazy getters (`hub.input`,
20
+ * `hub.daily`, `hub.resume`, `hub.saved`, `hub.social`, `hub.mp`, `hub.rooms`, `hub.race`), and each has
21
+ * its own entry (`@diffenderfer-games/hub/input`, ...) for games that want
22
+ * only that part.
23
+ *
24
+ * @packageDocumentation
25
+ */
26
+ export { createHub, Hub, hub } from './core/hub';
27
+ export {
28
+ ContentRejectedError,
29
+ HubError,
30
+ OfflineError,
31
+ SAVE_UNKNOWN_CODE,
32
+ UNSUPPORTED_BY_HUB,
33
+ type HubErrorCode,
34
+ type HubErrorDetails,
35
+ } from './core/errors';
36
+ export type { SyncConflict, SyncResult } from './core/connection';
37
+ export type { AuthResponse, ProfileUpdate, StatResult } from './core/hub-progress';
38
+ export type { GrantBundle, TradeBundle, TradeId, TradeListOptions, TradeNoteOptions } from './core/hub-economy';
39
+ export type { HubMenuControl } from './core/menu-control';
40
+ export { HubResume, RESUME_DEBOUNCE_MS, RESUME_MAX_STATE_BYTES } from './core/resume/hub-resume';
41
+ export { HubAvatar, type AvatarHost, type AvatarModule } from './core/avatar/hub-avatar';
42
+ export type { AvatarChange, AvatarRef, DesignerResult, OpenDesignerOptions, RuntimeAvatarApi } from './core/avatar/avatar-types';
43
+ export type { CarriesLook } from './core/avatar/avatar-ref';
44
+ export type { ResumeBlocker, ResumeEntry, ResumeLoadOptions, ResumeProgress, ResumeRun, ResumeSaveInput } from './core/resume/resume-types';
45
+ export { HubSaved, SAVED_CHANGE_QUIET_MS, type SavedChangeListener, type SavedOpenDetail, type SavedOpenHandler } from './core/saved/hub-saved';
46
+ export { SAVED_CHANGE_EVENT, SAVED_KIND_PARAM, SAVED_OPEN_EVENT, SAVED_OPEN_PARAM, savedGameUrl } from './core/saved/saved-open';
47
+ export type * from './core/saved/saved-types';
48
+ export { HubTurns, TURN_LINK_PARAM, type TurnCreateOptions } from './core/turns/hub-turns';
49
+ export { isNewlyDue, readYourTurnBuzz, saveYourTurnBuzz, YOUR_TURN_BUZZ, YOUR_TURN_BUZZ_KEY } from './core/turns/your-turn-buzz';
50
+ export { TURN_CHAT_EVENT, TURN_EVENT, TURN_POLL_MS, TURN_QUIET_MS, TURN_RESYNC_EVENT, TurnGame, type TurnGameEvents, type TurnGameOptions, type TurnMoveEvent, type TurnOutcome } from './core/turns/turn-game';
51
+ export type { OpenTurnGame, TurnChatLine, TurnDeadline, TurnGameView } from '@diffenderfer-games/hub-contract';
52
+ export type { QueueOp, QueueOpInput, QueuedScoreOptions, ProfilePatch } from './core/offline/queue';
53
+ export { OfflineApi } from './core/offline/offline-api';
54
+ export { OUTBOX_FULL_CODE, type OfflineEventDetails, type OfflineEventName, type OfflineListener, type OfflineOp } from './core/offline/outbox';
55
+ export type { OfflineHandler, OfflineKindDefinition, ReplayContext } from './core/offline/outbox-kinds';
56
+ export { OUTBOX_MAX_CHARS, OUTBOX_MAX_ENTRIES } from './core/offline/outbox-store';
57
+ export type { BufferedEvent } from './core/offline/events';
58
+ export type { EndpointFields, EndpointName, EndpointReply } from './core/rest/endpoint-types';
59
+ export { CLIENT_VERSION_HEADER } from './core/rest/rest-client';
60
+ export type { ClientSupport } from './core/version/hub-version';
61
+ export { HUB_CLIENT_VERSION } from './core/version/client-version.gen';
62
+ export type * from './core/types';
63
+ export * from './daily';
64
+ export * from './input';
65
+ export type * from './social';
66
+ export type * from './mp';
67
+ export type * from './race';
68
+ export type * from './rooms';
@@ -8,14 +8,15 @@ import type { HubMpApi, InviteLinkMethods } from './multiplayer-types';
8
8
  import type { HubRaceApi } from './race-types';
9
9
  import type { HubRoomsApi, RoomReclaimMethods } from './room-types';
10
10
  import type { HubNotifyApi, HubPresenceApi, HubSocialApi, PlayOnlineMethods } from './social-types';
11
+ import type { RuntimeWorldApi } from './world-types';
11
12
 
12
13
  /**
13
14
  * The realtime feature version the runtime reports in {@link RealtimeApi.version}.
14
15
  * 2 added races; 3 added invite links and taking back a held seat; 4 added
15
- * "Play online" (tournaments); 5 added avatars (the designer and `ev:avatar`).
16
+ * "Play online" (tournaments); 5 added avatars (the designer and `ev:avatar`); 6 added the world (`world`).
16
17
  * A client feature-detects newer additions with it (or by the optional method).
17
18
  */
18
- export const REALTIME_FEATURE_VERSION = 5;
19
+ export const REALTIME_FEATURE_VERSION = 6;
19
20
 
20
21
  /** `hub.social` as a runtime installs it: version 4's `openPlayOnline` is missing on older runtimes. */
21
22
  export type RuntimeSocialApi = Omit<HubSocialApi, keyof PlayOnlineMethods> & Partial<PlayOnlineMethods>;
@@ -68,4 +69,6 @@ export interface RealtimeApi {
68
69
  race?: HubRaceApi;
69
70
  /** Avatars: the designer and live changes (feature version 5 and later). */
70
71
  avatar?: RuntimeAvatarApi;
72
+ /** The world's ops and events (feature version 6 and later). */
73
+ world?: RuntimeWorldApi;
71
74
  }
@@ -51,3 +51,4 @@ export type * from './multiplayer-types';
51
51
  export type * from './room-types';
52
52
  export type * from './race-types';
53
53
  export type { RealtimeApi } from './runtime-api';
54
+ export type { RuntimeWorldApi, WorldEventName, WorldOpName } from './world-types';
@@ -0,0 +1,20 @@
1
+ /**
2
+ * The world over the runtime's socket (docs/plans/world.md §8.3): the
3
+ * `world.*` ops and events, typed from the contract. The hub's world client
4
+ * calls it; games don't need it.
5
+ */
6
+ import type { EventData, EventName, events, OpAck, OpInput, OpName, ops } from '@diffenderfer-games/hub-contract';
7
+
8
+ /** A `world.*` op name. */
9
+ export type WorldOpName = Extract<OpName, `world.${string}`>;
10
+
11
+ /** A `world.*` event name. */
12
+ export type WorldEventName = Extract<EventName, `world.${string}`>;
13
+
14
+ /** `rt.world` (realtime feature version 6 and later). */
15
+ export interface RuntimeWorldApi {
16
+ /** Send a world op on the page's socket; rejects with the hub's refusal. */
17
+ op<Name extends WorldOpName>(name: Name, input: OpInput<(typeof ops)[Name]>): Promise<OpAck<(typeof ops)[Name]>>;
18
+ /** Hear a world event; returns the unsubscribe. */
19
+ on<Name extends WorldEventName>(name: Name, listener: (payload: EventData<(typeof events)[Name]>) => void): () => void;
20
+ }
@@ -1,12 +1,12 @@
1
- /**
2
- * `@diffenderfer-games/hub/turns-rules`: what an online turn game's rules
3
- * module imports (docs/multiplayer.md §16): `defineTurnRules` to type the
4
- * rules object, the rules contract's types, and the seeded RNG the hub
5
- * hands the rules. Nothing else comes with it (no zod, no client), so a
6
- * rules bundle that imports it stays a few KB. The conformance kit for
7
- * tests is `@diffenderfer-games/hub/turns`.
8
- *
9
- * @packageDocumentation
10
- */
11
- export { createTurnRng, defineTurnRules, turnRngStateFromSeed } from '@diffenderfer-games/hub-contract/turns-rules';
12
- export type { TurnApplyResult, TurnOutcome, TurnProgress, TurnRng, TurnRngState, TurnRules, TurnSetupContext } from '@diffenderfer-games/hub-contract/turns-rules';
1
+ /**
2
+ * `@diffenderfer-games/hub/turns-rules`: what an online turn game's rules
3
+ * module imports (docs/multiplayer.md §16): `defineTurnRules` to type the
4
+ * rules object, the rules contract's types, and the seeded RNG the hub
5
+ * hands the rules. Nothing else comes with it (no zod, no client), so a
6
+ * rules bundle that imports it stays a few KB. The conformance kit for
7
+ * tests is `@diffenderfer-games/hub/turns`.
8
+ *
9
+ * @packageDocumentation
10
+ */
11
+ export { createTurnRng, defineTurnRules, turnRngStateFromSeed } from '@diffenderfer-games/hub-contract/turns-rules';
12
+ export type { TurnApplyResult, TurnOutcome, TurnProgress, TurnRng, TurnRngState, TurnRules, TurnSetupContext } from '@diffenderfer-games/hub-contract/turns-rules';