@crowdedkingdoms/crowdyjs 17.13.0-dev.1 → 18.0.0-dev.1
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/MIGRATION.md +163 -1
- package/README.md +142 -146
- package/dist/crowdy-client.d.ts +12 -31
- package/dist/crowdy-client.d.ts.map +1 -1
- package/dist/crowdy-client.js +15 -37
- package/dist/crowdy-dsh/bridge.d.ts.map +1 -1
- package/dist/crowdy-dsh/bridge.js +3 -6
- package/dist/crowdy-studio/controller.d.ts +59 -57
- package/dist/crowdy-studio/controller.d.ts.map +1 -1
- package/dist/crowdy-studio/controller.js +268 -206
- package/dist/crowdy-studio/diagnostics.d.ts +3 -12
- package/dist/crowdy-studio/diagnostics.d.ts.map +1 -1
- package/dist/crowdy-studio/dom-shell.d.ts +2 -4
- package/dist/crowdy-studio/dom-shell.d.ts.map +1 -1
- package/dist/crowdy-studio/dom-shell.js +16 -47
- package/dist/crowdy-studio/embed/hud-layer.d.ts +1 -1
- package/dist/crowdy-studio/embed/panel.d.ts +3 -12
- package/dist/crowdy-studio/embed/panel.d.ts.map +1 -1
- package/dist/crowdy-studio/embed/panel.js +3 -6
- package/dist/crowdy-studio/index.d.ts +1 -3
- package/dist/crowdy-studio/index.d.ts.map +1 -1
- package/dist/crowdy-studio/index.js +0 -2
- package/dist/crowdy-studio/models.d.ts +3 -4
- package/dist/crowdy-studio/models.d.ts.map +1 -1
- package/dist/crowdy-studio/starter-projects.d.ts +8 -4
- package/dist/crowdy-studio/starter-projects.d.ts.map +1 -1
- package/dist/crowdy-studio/starter-projects.js +83 -100
- package/dist/domains/appAccess.d.ts +14 -1
- package/dist/domains/appAccess.d.ts.map +1 -1
- package/dist/domains/appAccess.js +37 -1
- package/dist/domains/exec.d.ts +162 -15
- package/dist/domains/exec.d.ts.map +1 -1
- package/dist/domains/exec.js +180 -18
- package/dist/domains/host.d.ts +3 -3
- package/dist/domains/host.js +3 -3
- package/dist/domains/marketplace.d.ts +9 -58
- package/dist/domains/marketplace.d.ts.map +1 -1
- package/dist/domains/marketplace.js +9 -106
- package/dist/domains/playerWallet.d.ts +6 -12
- package/dist/domains/playerWallet.d.ts.map +1 -1
- package/dist/domains/playerWallet.js +6 -21
- package/dist/errors.d.ts +7 -13
- package/dist/errors.d.ts.map +1 -1
- package/dist/errors.js +6 -10
- package/dist/generated/graphql.d.ts +1384 -7977
- package/dist/generated/graphql.d.ts.map +1 -1
- package/dist/generated/graphql.js +22 -321
- package/dist/grid-mods/exec-client-halves.d.ts +156 -0
- package/dist/grid-mods/exec-client-halves.d.ts.map +1 -0
- package/dist/grid-mods/exec-client-halves.js +356 -0
- package/dist/grid-mods/grid-host-calls.d.ts +29 -15
- package/dist/grid-mods/grid-host-calls.d.ts.map +1 -1
- package/dist/grid-mods/grid-host-calls.js +40 -44
- package/dist/grid-mods/index.d.ts +1 -0
- package/dist/grid-mods/index.d.ts.map +1 -1
- package/dist/grid-mods/index.js +1 -0
- package/dist/grid-mods/mod-runtime.d.ts +21 -6
- package/dist/grid-mods/mod-runtime.d.ts.map +1 -1
- package/dist/grid-mods/mod-runtime.js +6 -2
- package/dist/grid-scope.d.ts +0 -188
- package/dist/grid-scope.d.ts.map +1 -1
- package/dist/grid-scope.js +0 -69
- package/dist/index.d.ts +12 -18
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +11 -16
- package/dist/kit/actions.d.ts +5 -9
- package/dist/kit/actions.d.ts.map +1 -1
- package/dist/kit/actions.js +5 -9
- package/dist/kit/index.d.ts +2 -33
- package/dist/kit/index.d.ts.map +1 -1
- package/dist/kit/index.js +1 -32
- package/dist/kit/kit.d.ts +8 -208
- package/dist/kit/kit.d.ts.map +1 -1
- package/dist/kit/kit.js +5 -163
- package/dist/kit/social.d.ts +1 -3
- package/dist/kit/social.d.ts.map +1 -1
- package/dist/kit/social.js +1 -3
- package/dist/kit/wire.d.ts +16 -5
- package/dist/kit/wire.d.ts.map +1 -1
- package/dist/kit/wire.js +19 -5
- package/dist/live-coding/assets/browser-authoring-index.json +1 -1
- package/dist/live-coding/assets/manifest.json +1 -1
- package/dist/live-coding/browser-authoring-index.generated.d.ts.map +1 -1
- package/dist/live-coding/browser-authoring-index.generated.js +1496 -4386
- package/dist/live-coding/rust-analysis.d.ts.map +1 -1
- package/dist/live-coding/rust-analysis.js +31 -23
- package/dist/player-runtime/client-host-calls.d.ts +8 -0
- package/dist/player-runtime/client-host-calls.d.ts.map +1 -0
- package/dist/player-runtime/client-host-calls.js +28 -0
- package/dist/player-runtime/glue-runtime.d.ts +27 -10
- package/dist/player-runtime/glue-runtime.d.ts.map +1 -1
- package/dist/player-runtime/glue-runtime.js +40 -45
- package/dist/player-runtime/glue-sab.d.ts +2 -2
- package/dist/player-runtime/glue-sab.js +2 -2
- package/dist/player-runtime/player-code-broker.d.ts +80 -24
- package/dist/player-runtime/player-code-broker.d.ts.map +1 -1
- package/dist/player-runtime/player-code-broker.js +169 -41
- package/dist/player-runtime/player-glue-worker.d.ts +8 -8
- package/dist/player-runtime/player-glue-worker.d.ts.map +1 -1
- package/dist/player-runtime/player-glue-worker.js +26 -9
- package/dist/stores/actors.d.ts +12 -2
- package/dist/stores/actors.d.ts.map +1 -1
- package/dist/stores/actors.js +43 -18
- package/dist/stores/chunks.d.ts +12 -0
- package/dist/stores/chunks.d.ts.map +1 -1
- package/dist/stores/chunks.js +112 -39
- package/dist/stores/durable.d.ts.map +1 -1
- package/dist/stores/durable.js +2 -1
- package/dist/stores/inbox.d.ts +1 -1
- package/dist/stores/inbox.d.ts.map +1 -1
- package/dist/stores/index.d.ts +0 -11
- package/dist/stores/index.d.ts.map +1 -1
- package/dist/stores/index.js +0 -4
- package/dist/stores/session.d.ts +0 -2
- package/dist/stores/session.d.ts.map +1 -1
- package/package.json +1 -1
- package/dist/crowdy-studio/model-lint-log.d.ts +0 -76
- package/dist/crowdy-studio/model-lint-log.d.ts.map +0 -1
- package/dist/crowdy-studio/model-lint-log.js +0 -93
- package/dist/crowdy-studio/model-lint.d.ts +0 -61
- package/dist/crowdy-studio/model-lint.d.ts.map +0 -1
- package/dist/crowdy-studio/model-lint.js +0 -45
- package/dist/domains/compute.d.ts +0 -208
- package/dist/domains/compute.d.ts.map +0 -1
- package/dist/domains/compute.js +0 -274
- package/dist/domains/controlPlane.d.ts +0 -38
- package/dist/domains/controlPlane.d.ts.map +0 -1
- package/dist/domains/controlPlane.js +0 -43
- package/dist/domains/gameModel.d.ts +0 -1174
- package/dist/domains/gameModel.d.ts.map +0 -1
- package/dist/domains/gameModel.js +0 -1333
- package/dist/domains/playerCompute.d.ts +0 -91
- package/dist/domains/playerCompute.d.ts.map +0 -1
- package/dist/domains/playerCompute.js +0 -136
- package/dist/domains/playerModel.d.ts +0 -20
- package/dist/domains/playerModel.d.ts.map +0 -1
- package/dist/domains/playerModel.js +0 -54
- package/dist/kit/abilities.d.ts +0 -66
- package/dist/kit/abilities.d.ts.map +0 -1
- package/dist/kit/abilities.js +0 -71
- package/dist/kit/blueprints/combat.d.ts +0 -81
- package/dist/kit/blueprints/combat.d.ts.map +0 -1
- package/dist/kit/blueprints/combat.js +0 -357
- package/dist/kit/blueprints/core.d.ts +0 -222
- package/dist/kit/blueprints/core.d.ts.map +0 -1
- package/dist/kit/blueprints/core.js +0 -185
- package/dist/kit/blueprints/decks.d.ts +0 -50
- package/dist/kit/blueprints/decks.d.ts.map +0 -1
- package/dist/kit/blueprints/decks.js +0 -163
- package/dist/kit/blueprints/economy.d.ts +0 -88
- package/dist/kit/blueprints/economy.d.ts.map +0 -1
- package/dist/kit/blueprints/economy.js +0 -533
- package/dist/kit/blueprints/guild.d.ts +0 -51
- package/dist/kit/blueprints/guild.d.ts.map +0 -1
- package/dist/kit/blueprints/guild.js +0 -50
- package/dist/kit/blueprints/index.d.ts +0 -23
- package/dist/kit/blueprints/index.d.ts.map +0 -1
- package/dist/kit/blueprints/index.js +0 -22
- package/dist/kit/blueprints/inventory.d.ts +0 -84
- package/dist/kit/blueprints/inventory.d.ts.map +0 -1
- package/dist/kit/blueprints/inventory.js +0 -257
- package/dist/kit/blueprints/leaderboards.d.ts +0 -49
- package/dist/kit/blueprints/leaderboards.d.ts.map +0 -1
- package/dist/kit/blueprints/leaderboards.js +0 -126
- package/dist/kit/blueprints/liveops.d.ts +0 -42
- package/dist/kit/blueprints/liveops.d.ts.map +0 -1
- package/dist/kit/blueprints/liveops.js +0 -170
- package/dist/kit/blueprints/locks.d.ts +0 -82
- package/dist/kit/blueprints/locks.d.ts.map +0 -1
- package/dist/kit/blueprints/locks.js +0 -155
- package/dist/kit/blueprints/loot.d.ts +0 -90
- package/dist/kit/blueprints/loot.d.ts.map +0 -1
- package/dist/kit/blueprints/loot.js +0 -229
- package/dist/kit/blueprints/matches.d.ts +0 -84
- package/dist/kit/blueprints/matches.d.ts.map +0 -1
- package/dist/kit/blueprints/matches.js +0 -349
- package/dist/kit/blueprints/moderation.d.ts +0 -32
- package/dist/kit/blueprints/moderation.d.ts.map +0 -1
- package/dist/kit/blueprints/moderation.js +0 -140
- package/dist/kit/blueprints/npcs.d.ts +0 -78
- package/dist/kit/blueprints/npcs.d.ts.map +0 -1
- package/dist/kit/blueprints/npcs.js +0 -125
- package/dist/kit/blueprints/plots.d.ts +0 -63
- package/dist/kit/blueprints/plots.d.ts.map +0 -1
- package/dist/kit/blueprints/plots.js +0 -151
- package/dist/kit/blueprints/progression.d.ts +0 -68
- package/dist/kit/blueprints/progression.d.ts.map +0 -1
- package/dist/kit/blueprints/progression.js +0 -324
- package/dist/kit/blueprints/quests.d.ts +0 -72
- package/dist/kit/blueprints/quests.d.ts.map +0 -1
- package/dist/kit/blueprints/quests.js +0 -285
- package/dist/kit/blueprints/worldsim.d.ts +0 -91
- package/dist/kit/blueprints/worldsim.d.ts.map +0 -1
- package/dist/kit/blueprints/worldsim.js +0 -468
- package/dist/kit/combat.d.ts +0 -141
- package/dist/kit/combat.d.ts.map +0 -1
- package/dist/kit/combat.js +0 -196
- package/dist/kit/decks.d.ts +0 -157
- package/dist/kit/decks.d.ts.map +0 -1
- package/dist/kit/decks.js +0 -186
- package/dist/kit/director.d.ts +0 -88
- package/dist/kit/director.d.ts.map +0 -1
- package/dist/kit/director.js +0 -86
- package/dist/kit/economy.d.ts +0 -289
- package/dist/kit/economy.d.ts.map +0 -1
- package/dist/kit/economy.js +0 -427
- package/dist/kit/engine.d.ts +0 -48
- package/dist/kit/engine.d.ts.map +0 -1
- package/dist/kit/engine.js +0 -113
- package/dist/kit/features.d.ts +0 -60
- package/dist/kit/features.d.ts.map +0 -1
- package/dist/kit/features.js +0 -65
- package/dist/kit/instances.d.ts +0 -52
- package/dist/kit/instances.d.ts.map +0 -1
- package/dist/kit/instances.js +0 -60
- package/dist/kit/inventory.d.ts +0 -129
- package/dist/kit/inventory.d.ts.map +0 -1
- package/dist/kit/inventory.js +0 -200
- package/dist/kit/leaderboards.d.ts +0 -86
- package/dist/kit/leaderboards.d.ts.map +0 -1
- package/dist/kit/leaderboards.js +0 -133
- package/dist/kit/liveops.d.ts +0 -112
- package/dist/kit/liveops.d.ts.map +0 -1
- package/dist/kit/liveops.js +0 -183
- package/dist/kit/loot.d.ts +0 -128
- package/dist/kit/loot.d.ts.map +0 -1
- package/dist/kit/loot.js +0 -160
- package/dist/kit/matches.d.ts +0 -301
- package/dist/kit/matches.d.ts.map +0 -1
- package/dist/kit/matches.js +0 -458
- package/dist/kit/matchmaking.d.ts +0 -57
- package/dist/kit/matchmaking.d.ts.map +0 -1
- package/dist/kit/matchmaking.js +0 -59
- package/dist/kit/minigames.d.ts +0 -30
- package/dist/kit/minigames.d.ts.map +0 -1
- package/dist/kit/minigames.js +0 -38
- package/dist/kit/mobs.d.ts +0 -90
- package/dist/kit/mobs.d.ts.map +0 -1
- package/dist/kit/mobs.js +0 -98
- package/dist/kit/moderation.d.ts +0 -73
- package/dist/kit/moderation.d.ts.map +0 -1
- package/dist/kit/moderation.js +0 -103
- package/dist/kit/movement.d.ts +0 -78
- package/dist/kit/movement.d.ts.map +0 -1
- package/dist/kit/movement.js +0 -103
- package/dist/kit/notifications.d.ts +0 -45
- package/dist/kit/notifications.d.ts.map +0 -1
- package/dist/kit/notifications.js +0 -67
- package/dist/kit/npcs.d.ts +0 -226
- package/dist/kit/npcs.d.ts.map +0 -1
- package/dist/kit/npcs.js +0 -141
- package/dist/kit/objects.d.ts +0 -116
- package/dist/kit/objects.d.ts.map +0 -1
- package/dist/kit/objects.js +0 -119
- package/dist/kit/pets.d.ts +0 -85
- package/dist/kit/pets.d.ts.map +0 -1
- package/dist/kit/pets.js +0 -96
- package/dist/kit/plots.d.ts +0 -85
- package/dist/kit/plots.d.ts.map +0 -1
- package/dist/kit/plots.js +0 -113
- package/dist/kit/progression.d.ts +0 -181
- package/dist/kit/progression.d.ts.map +0 -1
- package/dist/kit/progression.js +0 -265
- package/dist/kit/quests.d.ts +0 -171
- package/dist/kit/quests.d.ts.map +0 -1
- package/dist/kit/quests.js +0 -237
- package/dist/kit/racing.d.ts +0 -85
- package/dist/kit/racing.d.ts.map +0 -1
- package/dist/kit/racing.js +0 -99
- package/dist/kit/shared.d.ts +0 -73
- package/dist/kit/shared.d.ts.map +0 -1
- package/dist/kit/shared.js +0 -101
- package/dist/kit/telemetry.d.ts +0 -66
- package/dist/kit/telemetry.d.ts.map +0 -1
- package/dist/kit/telemetry.js +0 -133
- package/dist/kit/territory.d.ts +0 -97
- package/dist/kit/territory.d.ts.map +0 -1
- package/dist/kit/territory.js +0 -114
- package/dist/kit/worldsim.d.ts +0 -263
- package/dist/kit/worldsim.d.ts.map +0 -1
- package/dist/kit/worldsim.js +0 -268
- package/dist/stores/model.d.ts +0 -81
- package/dist/stores/model.d.ts.map +0 -1
- package/dist/stores/model.js +0 -163
|
@@ -0,0 +1,156 @@
|
|
|
1
|
+
import type { ExecAPI, ExecClientCapabilitySummary, ExecGridClientMod } from '../domains/exec.js';
|
|
2
|
+
import { type PlayerCodeBrokerOptions, type PlayerCodeGridBounds, type PlayerCodeHostCall, type PlayerCodeLogLine, type PlayerCodePresentation } from '../player-runtime/player-code-broker.js';
|
|
3
|
+
/** The grid a player stands in: its id and chunk box. */
|
|
4
|
+
export type ExecClientHalvesGrid = PlayerCodeGridBounds & {
|
|
5
|
+
gridId: string;
|
|
6
|
+
};
|
|
7
|
+
/**
|
|
8
|
+
* One question for the player. `author`: trust every CLIENT half of `authorId` on the grid, at
|
|
9
|
+
* the hash of their union (`exec.trustAuthor`). `mod`: consent to one CLIENT half at its hash
|
|
10
|
+
* (`exec.consentClientMod`).
|
|
11
|
+
*/
|
|
12
|
+
export interface ExecClientHalfPrompt {
|
|
13
|
+
kind: 'author' | 'mod';
|
|
14
|
+
gridId: string;
|
|
15
|
+
authorId: string;
|
|
16
|
+
/** What a yes consents to: the author's `authorCapabilityHash`, or the mod's `capabilityHash`. */
|
|
17
|
+
capabilityHash: string;
|
|
18
|
+
/** The summary to show: the author's union, or the one CLIENT half's. */
|
|
19
|
+
capabilitySummaryJson: string;
|
|
20
|
+
capabilitySummary: ExecClientCapabilitySummary | null;
|
|
21
|
+
/** The CLIENT halves a yes covers now. */
|
|
22
|
+
mods: readonly ExecGridClientMod[];
|
|
23
|
+
}
|
|
24
|
+
/**
|
|
25
|
+
* Why a CLIENT half stopped: no longer listed (`removed`), a new digest, capability hash or tick
|
|
26
|
+
* interval (`changed`), listed without the player's consent or trust (`unconsented`), refused by
|
|
27
|
+
* `filter` (`filtered`), the runner left the grid (`left-grid`) or was stopped (`stopped`), or
|
|
28
|
+
* the broker's circuit breaker tripped (`circuit-open`).
|
|
29
|
+
*/
|
|
30
|
+
export type ExecClientHalfStopReason = 'removed' | 'changed' | 'unconsented' | 'filtered' | 'left-grid' | 'stopped' | 'circuit-open';
|
|
31
|
+
/** A consent, fetch or start that failed, and when that CLIENT half is tried again. */
|
|
32
|
+
export interface ExecClientHalfError {
|
|
33
|
+
mod: ExecGridClientMod;
|
|
34
|
+
stage: 'consent' | 'fetch' | 'start';
|
|
35
|
+
/**
|
|
36
|
+
* `not-found`: not served to the player now (every refusal of the artifact, and a trust while
|
|
37
|
+
* not standing in the grid); `rate-limited`: 12 fetches a minute per player and mod;
|
|
38
|
+
* `conflict`: the hash moved on; `refused`: the served module was, for bytes that differ from
|
|
39
|
+
* its digest, an unknown CLIENT ABI or a capability summary that does not parse; `failed`:
|
|
40
|
+
* anything else.
|
|
41
|
+
*/
|
|
42
|
+
reason: 'not-found' | 'rate-limited' | 'conflict' | 'refused' | 'failed';
|
|
43
|
+
error: unknown;
|
|
44
|
+
/** When the runner fetches or starts it again (epoch ms); absent for a consent. */
|
|
45
|
+
retryAt?: number;
|
|
46
|
+
}
|
|
47
|
+
/** The part of `PlayerCodeBroker` the runner drives. */
|
|
48
|
+
export interface ExecClientHalfBroker {
|
|
49
|
+
start(artifact: ArrayBuffer): Promise<void>;
|
|
50
|
+
stop(): void;
|
|
51
|
+
/** `PlayerCodeBroker.invoke`: the module's `handle_invoke`. */
|
|
52
|
+
invoke?(payload: Uint8Array, options?: {
|
|
53
|
+
timeoutMs?: number;
|
|
54
|
+
}): Promise<Uint8Array>;
|
|
55
|
+
}
|
|
56
|
+
export interface ExecClientHalvesOptions {
|
|
57
|
+
exec: Pick<ExecAPI, 'gridClientMods' | 'consentClientMod' | 'trustAuthor' | 'modClientArtifactBytes'>;
|
|
58
|
+
appId: string;
|
|
59
|
+
/** The platform glue worker (`@crowdedkingdoms/crowdyjs/player-glue-worker`), same origin. */
|
|
60
|
+
workerUrl: string | URL;
|
|
61
|
+
/**
|
|
62
|
+
* Answers the host calls a CLIENT half makes that the broker does not answer itself, for
|
|
63
|
+
* example with `createGridHostCalls` over the grid's scope.
|
|
64
|
+
*/
|
|
65
|
+
onHostCall: (call: PlayerCodeHostCall, mod: ExecGridClientMod) => Promise<unknown>;
|
|
66
|
+
onPresentation?: (presentation: PlayerCodePresentation, mod: ExecGridClientMod) => void;
|
|
67
|
+
/**
|
|
68
|
+
* Each CLIENT half's `crowdy::log` lines, bounded by the broker (see
|
|
69
|
+
* `PlayerCodeBrokerOptions.onLog`). The text is the mod author's: render it as text.
|
|
70
|
+
*/
|
|
71
|
+
onLog?: (line: PlayerCodeLogLine, mod: ExecGridClientMod) => void;
|
|
72
|
+
/**
|
|
73
|
+
* Asks the player about CLIENT halves they neither consented to nor trust the author of; true
|
|
74
|
+
* consents. Without it, only what the player already agreed to runs. A declined question is
|
|
75
|
+
* not asked again on this grid until its hash changes.
|
|
76
|
+
*/
|
|
77
|
+
confirm?: (prompt: ExecClientHalfPrompt) => boolean | Promise<boolean>;
|
|
78
|
+
/** `author` (the default): one question per author; `mod`: one per CLIENT half. */
|
|
79
|
+
ask?: 'author' | 'mod';
|
|
80
|
+
/** Leaves out the CLIENT halves it returns false for, e.g. the one Crowdy Studio previews. */
|
|
81
|
+
filter?: (mod: ExecGridClientMod) => boolean;
|
|
82
|
+
onStarted?: (mod: ExecGridClientMod) => void;
|
|
83
|
+
onStopped?: (mod: ExecGridClientMod, reason: ExecClientHalfStopReason) => void;
|
|
84
|
+
onError?: (error: ExecClientHalfError) => void;
|
|
85
|
+
/** How long a CLIENT half waits after `NOT_FOUND` before it is fetched again. Default 15 s. */
|
|
86
|
+
notFoundRetryMs?: number;
|
|
87
|
+
/** How long after `RATE_LIMITED`. Default 60 s, the limit's window. */
|
|
88
|
+
rateLimitedRetryMs?: number;
|
|
89
|
+
/** How long after any other failure, refused bytes included. Default 60 s. */
|
|
90
|
+
failedRetryMs?: number;
|
|
91
|
+
/** Modules kept by digest, across grids. Default 16. */
|
|
92
|
+
cacheSize?: number;
|
|
93
|
+
now?: () => number;
|
|
94
|
+
brokerFactory?: (options: PlayerCodeBrokerOptions) => ExecClientHalfBroker;
|
|
95
|
+
}
|
|
96
|
+
/**
|
|
97
|
+
* Runs the CLIENT halves of ck-exec mods that the grid a player stands in serves, the way a game
|
|
98
|
+
* should: tell it the grid ({@link enterGrid}) and call {@link refresh} on a cadence (every 10 s
|
|
99
|
+
* or so). Each refresh lists the grid's CLIENT halves (`exec.gridClientMods`), stops the workers
|
|
100
|
+
* whose mod is gone or whose `modId`, `digest`, `capabilityHash` or tick interval changed, asks
|
|
101
|
+
* the player once per author (or per CLIENT half) about the rest, fetches each consented one
|
|
102
|
+
* (`exec.modClientArtifactBytes`, which refuses bytes that differ from their digest) and runs it
|
|
103
|
+
* in a `PlayerCodeBroker` with `engine: 'ck-exec'`, its fuel budget and tick interval, bounded
|
|
104
|
+
* to the host calls of the capability summary the player consented to.
|
|
105
|
+
*
|
|
106
|
+
* Modules are cached by digest, since a digest never changes its bytes and the artifact query
|
|
107
|
+
* allows 12 fetches a minute per player and mod. A cached module starts without a fetch, so the
|
|
108
|
+
* serve checks that query makes (`run_client_code`, standing in the grid) are those of the fetch
|
|
109
|
+
* that filled the cache; consent and trust are the listing's, on every refresh.
|
|
110
|
+
*
|
|
111
|
+
* `NOT_FOUND` (not served to this player now) and `RATE_LIMITED` hold that CLIENT half back for
|
|
112
|
+
* a while instead of fetching on every refresh.
|
|
113
|
+
*/
|
|
114
|
+
export declare class ExecClientHalves {
|
|
115
|
+
private readonly options;
|
|
116
|
+
private grid;
|
|
117
|
+
private generation;
|
|
118
|
+
private readonly workers;
|
|
119
|
+
private readonly cache;
|
|
120
|
+
private readonly retryAt;
|
|
121
|
+
private readonly declined;
|
|
122
|
+
private readonly approved;
|
|
123
|
+
private inFlight;
|
|
124
|
+
constructor(options: ExecClientHalvesOptions);
|
|
125
|
+
/** The grid the player stands in, or null. Any change stops every CLIENT half of the old one. */
|
|
126
|
+
enterGrid(grid: ExecClientHalvesGrid | null): void;
|
|
127
|
+
/** The CLIENT halves running now. */
|
|
128
|
+
get running(): readonly ExecGridClientMod[];
|
|
129
|
+
/**
|
|
130
|
+
* One reconcile of the current grid; a call while one runs for the same grid joins it. Rejects
|
|
131
|
+
* when the grid's CLIENT halves cannot be listed, and what runs keeps running.
|
|
132
|
+
*/
|
|
133
|
+
refresh(): Promise<void>;
|
|
134
|
+
/**
|
|
135
|
+
* Calls the `handle_invoke` export of the running CLIENT half of mod `modId` with `payload`
|
|
136
|
+
* and resolves with its reply bytes (see `PlayerCodeBroker.invoke`). Rejects when that CLIENT
|
|
137
|
+
* half is not running.
|
|
138
|
+
*/
|
|
139
|
+
invoke(modId: string, payload: Uint8Array, options?: {
|
|
140
|
+
timeoutMs?: number;
|
|
141
|
+
}): Promise<Uint8Array>;
|
|
142
|
+
/** Stops every CLIENT half and leaves the grid; the module cache stays. */
|
|
143
|
+
stop(): void;
|
|
144
|
+
private reconcile;
|
|
145
|
+
private stopStale;
|
|
146
|
+
/** Asks about what the player has not agreed to; true when a consent or trust went through. */
|
|
147
|
+
private askAbout;
|
|
148
|
+
private start;
|
|
149
|
+
private fail;
|
|
150
|
+
private remember;
|
|
151
|
+
private wanted;
|
|
152
|
+
private stopWorker;
|
|
153
|
+
private stopAll;
|
|
154
|
+
private now;
|
|
155
|
+
}
|
|
156
|
+
//# sourceMappingURL=exec-client-halves.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"exec-client-halves.d.ts","sourceRoot":"","sources":["../../src/grid-mods/exec-client-halves.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,356 @@
|
|
|
1
|
+
import { CrowdyGraphQLError, CrowdyProtocolError } from '../errors.js';
|
|
2
|
+
import { PlayerCodeBroker, } from '../player-runtime/player-code-broker.js';
|
|
3
|
+
const identity = (m) => `${m.modId}\u0000${m.digest}\u0000${m.capabilityHash}\u0000${m.tickIntervalMs}`;
|
|
4
|
+
function refusal(error) {
|
|
5
|
+
if (error instanceof CrowdyProtocolError)
|
|
6
|
+
return 'refused';
|
|
7
|
+
if (error instanceof CrowdyGraphQLError) {
|
|
8
|
+
if (error.code === 'NOT_FOUND')
|
|
9
|
+
return 'not-found';
|
|
10
|
+
if (error.code === 'RATE_LIMITED')
|
|
11
|
+
return 'rate-limited';
|
|
12
|
+
if (error.code === 'CONFLICT')
|
|
13
|
+
return 'conflict';
|
|
14
|
+
}
|
|
15
|
+
return 'failed';
|
|
16
|
+
}
|
|
17
|
+
/**
|
|
18
|
+
* Runs the CLIENT halves of ck-exec mods that the grid a player stands in serves, the way a game
|
|
19
|
+
* should: tell it the grid ({@link enterGrid}) and call {@link refresh} on a cadence (every 10 s
|
|
20
|
+
* or so). Each refresh lists the grid's CLIENT halves (`exec.gridClientMods`), stops the workers
|
|
21
|
+
* whose mod is gone or whose `modId`, `digest`, `capabilityHash` or tick interval changed, asks
|
|
22
|
+
* the player once per author (or per CLIENT half) about the rest, fetches each consented one
|
|
23
|
+
* (`exec.modClientArtifactBytes`, which refuses bytes that differ from their digest) and runs it
|
|
24
|
+
* in a `PlayerCodeBroker` with `engine: 'ck-exec'`, its fuel budget and tick interval, bounded
|
|
25
|
+
* to the host calls of the capability summary the player consented to.
|
|
26
|
+
*
|
|
27
|
+
* Modules are cached by digest, since a digest never changes its bytes and the artifact query
|
|
28
|
+
* allows 12 fetches a minute per player and mod. A cached module starts without a fetch, so the
|
|
29
|
+
* serve checks that query makes (`run_client_code`, standing in the grid) are those of the fetch
|
|
30
|
+
* that filled the cache; consent and trust are the listing's, on every refresh.
|
|
31
|
+
*
|
|
32
|
+
* `NOT_FOUND` (not served to this player now) and `RATE_LIMITED` hold that CLIENT half back for
|
|
33
|
+
* a while instead of fetching on every refresh.
|
|
34
|
+
*/
|
|
35
|
+
export class ExecClientHalves {
|
|
36
|
+
constructor(options) {
|
|
37
|
+
this.options = options;
|
|
38
|
+
this.grid = null;
|
|
39
|
+
this.generation = 0;
|
|
40
|
+
this.workers = new Map();
|
|
41
|
+
this.cache = new Map();
|
|
42
|
+
this.retryAt = new Map();
|
|
43
|
+
this.declined = new Set();
|
|
44
|
+
this.approved = new Set();
|
|
45
|
+
this.inFlight = null;
|
|
46
|
+
}
|
|
47
|
+
/** The grid the player stands in, or null. Any change stops every CLIENT half of the old one. */
|
|
48
|
+
enterGrid(grid) {
|
|
49
|
+
if (sameGrid(this.grid, grid))
|
|
50
|
+
return;
|
|
51
|
+
this.stopAll('left-grid');
|
|
52
|
+
this.grid = grid ? { ...grid } : null;
|
|
53
|
+
this.generation++;
|
|
54
|
+
this.declined.clear();
|
|
55
|
+
this.approved.clear();
|
|
56
|
+
}
|
|
57
|
+
/** The CLIENT halves running now. */
|
|
58
|
+
get running() {
|
|
59
|
+
return [...this.workers.values()].filter((w) => w.started).map((w) => w.mod);
|
|
60
|
+
}
|
|
61
|
+
/**
|
|
62
|
+
* One reconcile of the current grid; a call while one runs for the same grid joins it. Rejects
|
|
63
|
+
* when the grid's CLIENT halves cannot be listed, and what runs keeps running.
|
|
64
|
+
*/
|
|
65
|
+
refresh() {
|
|
66
|
+
if (this.inFlight?.generation === this.generation)
|
|
67
|
+
return this.inFlight.done;
|
|
68
|
+
const generation = this.generation;
|
|
69
|
+
const done = this.reconcile(generation).finally(() => {
|
|
70
|
+
if (this.inFlight?.done === done)
|
|
71
|
+
this.inFlight = null;
|
|
72
|
+
});
|
|
73
|
+
this.inFlight = { generation, done };
|
|
74
|
+
return done;
|
|
75
|
+
}
|
|
76
|
+
/**
|
|
77
|
+
* Calls the `handle_invoke` export of the running CLIENT half of mod `modId` with `payload`
|
|
78
|
+
* and resolves with its reply bytes (see `PlayerCodeBroker.invoke`). Rejects when that CLIENT
|
|
79
|
+
* half is not running.
|
|
80
|
+
*/
|
|
81
|
+
invoke(modId, payload, options) {
|
|
82
|
+
const worker = this.workers.get(modId);
|
|
83
|
+
if (!worker?.started) {
|
|
84
|
+
return Promise.reject(new Error(`the CLIENT half of mod ${modId} is not running`));
|
|
85
|
+
}
|
|
86
|
+
if (!worker.broker.invoke) {
|
|
87
|
+
return Promise.reject(new Error('this broker cannot invoke its module'));
|
|
88
|
+
}
|
|
89
|
+
return worker.broker.invoke(payload, options);
|
|
90
|
+
}
|
|
91
|
+
/** Stops every CLIENT half and leaves the grid; the module cache stays. */
|
|
92
|
+
stop() {
|
|
93
|
+
this.stopAll('stopped');
|
|
94
|
+
this.grid = null;
|
|
95
|
+
this.generation++;
|
|
96
|
+
this.declined.clear();
|
|
97
|
+
this.approved.clear();
|
|
98
|
+
}
|
|
99
|
+
async reconcile(generation) {
|
|
100
|
+
const grid = this.grid;
|
|
101
|
+
if (!grid)
|
|
102
|
+
return;
|
|
103
|
+
const { exec, appId } = this.options;
|
|
104
|
+
const current = () => generation === this.generation;
|
|
105
|
+
let listed = await exec.gridClientMods(appId, grid.gridId);
|
|
106
|
+
if (!current())
|
|
107
|
+
return;
|
|
108
|
+
this.stopStale(listed);
|
|
109
|
+
if (await this.askAbout(listed, grid, current)) {
|
|
110
|
+
if (!current())
|
|
111
|
+
return;
|
|
112
|
+
listed = await exec.gridClientMods(appId, grid.gridId);
|
|
113
|
+
if (!current())
|
|
114
|
+
return;
|
|
115
|
+
this.stopStale(listed);
|
|
116
|
+
}
|
|
117
|
+
for (const mod of listed) {
|
|
118
|
+
if (!current())
|
|
119
|
+
return;
|
|
120
|
+
if (!this.wanted(mod) || !(mod.callerConsented || mod.callerTrustsAuthor))
|
|
121
|
+
continue;
|
|
122
|
+
if (this.workers.has(mod.modId))
|
|
123
|
+
continue;
|
|
124
|
+
if ((this.retryAt.get(identity(mod)) ?? 0) > this.now())
|
|
125
|
+
continue;
|
|
126
|
+
await this.start(mod, grid, current);
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
stopStale(listed) {
|
|
130
|
+
const byId = new Map(listed.map((m) => [m.modId, m]));
|
|
131
|
+
for (const [modId, worker] of [...this.workers]) {
|
|
132
|
+
const next = byId.get(modId);
|
|
133
|
+
const reason = !next
|
|
134
|
+
? 'removed'
|
|
135
|
+
: identity(next) !== identity(worker.mod)
|
|
136
|
+
? 'changed'
|
|
137
|
+
: !this.wanted(next)
|
|
138
|
+
? 'filtered'
|
|
139
|
+
: !(next.callerConsented || next.callerTrustsAuthor)
|
|
140
|
+
? 'unconsented'
|
|
141
|
+
: null;
|
|
142
|
+
if (reason)
|
|
143
|
+
this.stopWorker(modId, reason);
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
/** Asks about what the player has not agreed to; true when a consent or trust went through. */
|
|
147
|
+
async askAbout(listed, grid, current) {
|
|
148
|
+
const pending = listed.filter((m) => this.wanted(m) && !m.callerConsented && !m.callerTrustsAuthor);
|
|
149
|
+
if (pending.length === 0)
|
|
150
|
+
return false;
|
|
151
|
+
const prompts = [];
|
|
152
|
+
if ((this.options.ask ?? 'author') === 'author') {
|
|
153
|
+
for (const authorId of new Set(pending.map((m) => m.authorId))) {
|
|
154
|
+
const theirs = listed.filter((m) => m.authorId === authorId);
|
|
155
|
+
const [first] = theirs;
|
|
156
|
+
prompts.push({
|
|
157
|
+
key: `author\u0000${authorId}\u0000${first.authorCapabilityHash}`,
|
|
158
|
+
prompt: {
|
|
159
|
+
kind: 'author',
|
|
160
|
+
gridId: grid.gridId,
|
|
161
|
+
authorId,
|
|
162
|
+
capabilityHash: first.authorCapabilityHash,
|
|
163
|
+
capabilitySummaryJson: first.authorCapabilitySummaryJson,
|
|
164
|
+
capabilitySummary: first.authorCapabilitySummary,
|
|
165
|
+
mods: theirs,
|
|
166
|
+
},
|
|
167
|
+
});
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
else {
|
|
171
|
+
for (const mod of pending) {
|
|
172
|
+
prompts.push({
|
|
173
|
+
key: `mod\u0000${mod.modId}\u0000${mod.capabilityHash}`,
|
|
174
|
+
prompt: {
|
|
175
|
+
kind: 'mod',
|
|
176
|
+
gridId: grid.gridId,
|
|
177
|
+
authorId: mod.authorId,
|
|
178
|
+
capabilityHash: mod.capabilityHash,
|
|
179
|
+
capabilitySummaryJson: mod.capabilitySummaryJson,
|
|
180
|
+
capabilitySummary: mod.capabilitySummary,
|
|
181
|
+
mods: [mod],
|
|
182
|
+
},
|
|
183
|
+
});
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
let agreed = false;
|
|
187
|
+
for (const { key, prompt } of prompts) {
|
|
188
|
+
if (!current())
|
|
189
|
+
return agreed;
|
|
190
|
+
if (this.declined.has(key))
|
|
191
|
+
continue;
|
|
192
|
+
if (!this.approved.has(key)) {
|
|
193
|
+
if (!this.options.confirm)
|
|
194
|
+
continue;
|
|
195
|
+
const yes = await this.options.confirm(prompt);
|
|
196
|
+
if (!current())
|
|
197
|
+
return agreed;
|
|
198
|
+
if (!yes) {
|
|
199
|
+
this.declined.add(key);
|
|
200
|
+
continue;
|
|
201
|
+
}
|
|
202
|
+
// Kept until it goes through: a trust refused while the player's presence is not yet
|
|
203
|
+
// registered is retried on the next refresh without asking again.
|
|
204
|
+
this.approved.add(key);
|
|
205
|
+
}
|
|
206
|
+
try {
|
|
207
|
+
const { exec, appId } = this.options;
|
|
208
|
+
if (prompt.kind === 'author') {
|
|
209
|
+
await exec.trustAuthor(appId, grid.gridId, prompt.authorId, prompt.capabilityHash);
|
|
210
|
+
}
|
|
211
|
+
else {
|
|
212
|
+
await exec.consentClientMod(appId, prompt.mods[0].modId, prompt.capabilityHash);
|
|
213
|
+
}
|
|
214
|
+
this.approved.delete(key);
|
|
215
|
+
agreed = true;
|
|
216
|
+
}
|
|
217
|
+
catch (error) {
|
|
218
|
+
const reason = refusal(error);
|
|
219
|
+
// The hash moved on: the next listing brings the new one, and a new question.
|
|
220
|
+
if (reason === 'conflict')
|
|
221
|
+
this.approved.delete(key);
|
|
222
|
+
this.options.onError?.({ mod: prompt.mods[0], stage: 'consent', reason, error });
|
|
223
|
+
}
|
|
224
|
+
}
|
|
225
|
+
return agreed;
|
|
226
|
+
}
|
|
227
|
+
async start(mod, grid, current) {
|
|
228
|
+
// What the player consented to, and what the served artifact itself lists, bound the
|
|
229
|
+
// module's host calls in the broker.
|
|
230
|
+
const consented = mod.capabilitySummary?.hostFunctions;
|
|
231
|
+
if (!Array.isArray(consented)) {
|
|
232
|
+
this.fail(mod, 'start', new CrowdyProtocolError({
|
|
233
|
+
message: `CLIENT half of mod ${mod.modId}: its capability summary does not parse`,
|
|
234
|
+
}));
|
|
235
|
+
return;
|
|
236
|
+
}
|
|
237
|
+
let module = this.cache.get(mod.digest);
|
|
238
|
+
if (module) {
|
|
239
|
+
this.cache.delete(mod.digest);
|
|
240
|
+
this.cache.set(mod.digest, module);
|
|
241
|
+
}
|
|
242
|
+
else {
|
|
243
|
+
try {
|
|
244
|
+
const a = await this.options.exec.modClientArtifactBytes(this.options.appId, mod.modId);
|
|
245
|
+
module = {
|
|
246
|
+
bytes: a.bytes,
|
|
247
|
+
fuelPerDispatch: a.fuelPerDispatch,
|
|
248
|
+
hostFunctions: a.capabilitySummary.hostFunctions,
|
|
249
|
+
};
|
|
250
|
+
this.remember(a.digest, module);
|
|
251
|
+
if (!current())
|
|
252
|
+
return;
|
|
253
|
+
// Changed between the listing and the fetch: the next listing starts the new one.
|
|
254
|
+
if (a.digest !== mod.digest ||
|
|
255
|
+
a.capabilityHash !== mod.capabilityHash ||
|
|
256
|
+
a.tickIntervalMs !== mod.tickIntervalMs) {
|
|
257
|
+
return;
|
|
258
|
+
}
|
|
259
|
+
}
|
|
260
|
+
catch (error) {
|
|
261
|
+
if (!current())
|
|
262
|
+
return;
|
|
263
|
+
this.fail(mod, 'fetch', error);
|
|
264
|
+
return;
|
|
265
|
+
}
|
|
266
|
+
}
|
|
267
|
+
const served = module.hostFunctions;
|
|
268
|
+
const broker = (this.options.brokerFactory ?? ((o) => new PlayerCodeBroker(o)))({
|
|
269
|
+
engine: 'ck-exec',
|
|
270
|
+
workerUrl: this.options.workerUrl,
|
|
271
|
+
grid: { low: grid.low, high: grid.high, gridId: grid.gridId },
|
|
272
|
+
moduleName: mod.name,
|
|
273
|
+
artifactHash: mod.digest,
|
|
274
|
+
fuelPerDispatch: module.fuelPerDispatch,
|
|
275
|
+
tickIntervalMs: mod.tickIntervalMs,
|
|
276
|
+
consentedHostCalls: consented.filter((fn) => served.includes(fn)),
|
|
277
|
+
onHostCall: (call) => this.options.onHostCall(call, mod),
|
|
278
|
+
onPresentation: (presentation) => this.options.onPresentation?.(presentation, mod),
|
|
279
|
+
...(this.options.onLog ? { onLog: (line) => this.options.onLog?.(line, mod) } : {}),
|
|
280
|
+
onCircuitOpen: () => {
|
|
281
|
+
if (this.workers.get(mod.modId)?.broker !== broker)
|
|
282
|
+
return;
|
|
283
|
+
this.retryAt.set(identity(mod), this.now() + (this.options.failedRetryMs ?? 60000));
|
|
284
|
+
this.stopWorker(mod.modId, 'circuit-open');
|
|
285
|
+
},
|
|
286
|
+
});
|
|
287
|
+
const worker = { mod, broker, started: false };
|
|
288
|
+
this.workers.set(mod.modId, worker);
|
|
289
|
+
try {
|
|
290
|
+
await broker.start(module.bytes);
|
|
291
|
+
}
|
|
292
|
+
catch (error) {
|
|
293
|
+
if (this.workers.get(mod.modId) !== worker)
|
|
294
|
+
return;
|
|
295
|
+
this.workers.delete(mod.modId);
|
|
296
|
+
broker.stop();
|
|
297
|
+
if (current())
|
|
298
|
+
this.fail(mod, 'start', error);
|
|
299
|
+
return;
|
|
300
|
+
}
|
|
301
|
+
if (this.workers.get(mod.modId) !== worker)
|
|
302
|
+
return;
|
|
303
|
+
worker.started = true;
|
|
304
|
+
this.options.onStarted?.(mod);
|
|
305
|
+
}
|
|
306
|
+
fail(mod, stage, error) {
|
|
307
|
+
const reason = refusal(error);
|
|
308
|
+
const wait = reason === 'not-found'
|
|
309
|
+
? (this.options.notFoundRetryMs ?? 15000)
|
|
310
|
+
: reason === 'rate-limited'
|
|
311
|
+
? (this.options.rateLimitedRetryMs ?? 60000)
|
|
312
|
+
: (this.options.failedRetryMs ?? 60000);
|
|
313
|
+
const retryAt = this.now() + wait;
|
|
314
|
+
this.retryAt.set(identity(mod), retryAt);
|
|
315
|
+
this.options.onError?.({ mod, stage, reason, error, retryAt });
|
|
316
|
+
}
|
|
317
|
+
remember(digest, module) {
|
|
318
|
+
this.cache.set(digest, module);
|
|
319
|
+
const limit = Math.max(1, this.options.cacheSize ?? 16);
|
|
320
|
+
for (const oldest of this.cache.keys()) {
|
|
321
|
+
if (this.cache.size <= limit)
|
|
322
|
+
break;
|
|
323
|
+
this.cache.delete(oldest);
|
|
324
|
+
}
|
|
325
|
+
}
|
|
326
|
+
wanted(mod) {
|
|
327
|
+
return this.options.filter ? this.options.filter(mod) : true;
|
|
328
|
+
}
|
|
329
|
+
stopWorker(modId, reason) {
|
|
330
|
+
const worker = this.workers.get(modId);
|
|
331
|
+
if (!worker)
|
|
332
|
+
return;
|
|
333
|
+
this.workers.delete(modId);
|
|
334
|
+
worker.broker.stop();
|
|
335
|
+
if (worker.started)
|
|
336
|
+
this.options.onStopped?.(worker.mod, reason);
|
|
337
|
+
}
|
|
338
|
+
stopAll(reason) {
|
|
339
|
+
for (const modId of [...this.workers.keys()])
|
|
340
|
+
this.stopWorker(modId, reason);
|
|
341
|
+
}
|
|
342
|
+
now() {
|
|
343
|
+
return (this.options.now ?? Date.now)();
|
|
344
|
+
}
|
|
345
|
+
}
|
|
346
|
+
function sameGrid(a, b) {
|
|
347
|
+
if (!a || !b)
|
|
348
|
+
return a === b;
|
|
349
|
+
return (a.gridId === b.gridId &&
|
|
350
|
+
a.low.x === b.low.x &&
|
|
351
|
+
a.low.y === b.low.y &&
|
|
352
|
+
a.low.z === b.low.z &&
|
|
353
|
+
a.high.x === b.high.x &&
|
|
354
|
+
a.high.y === b.high.y &&
|
|
355
|
+
a.high.z === b.high.z);
|
|
356
|
+
}
|
|
@@ -1,19 +1,32 @@
|
|
|
1
|
-
import type { GridScope } from '../grid-scope.js';
|
|
1
|
+
import type { GridChunk, GridScope } from '../grid-scope.js';
|
|
2
2
|
import type { PlayerCodeHostCall } from '../player-runtime/player-code-broker.js';
|
|
3
|
+
import type { AvatarsAPI } from '../domains/avatars.js';
|
|
3
4
|
import type { ChunksAPI } from '../domains/chunks.js';
|
|
4
5
|
import type { VoxelsAPI } from '../domains/voxels.js';
|
|
5
6
|
import type { StateAPI } from '../domains/state.js';
|
|
6
|
-
import type { GameModelAPI } from '../domains/gameModel.js';
|
|
7
7
|
/**
|
|
8
|
-
* Game-local fast paths. A game that already holds the world in memory (a
|
|
9
|
-
*
|
|
10
|
-
*
|
|
8
|
+
* Game-local fast paths and knowledge. A game that already holds the world in memory (a chunk
|
|
9
|
+
* store, an actor store) answers reads from there instead of a round trip; anything it leaves
|
|
10
|
+
* out falls through to the server path. The calls only the game can answer (`actors_list*`,
|
|
11
|
+
* `avatar_state_get`, `grid_permission_check`, `pointer_clicks`) are refused without their
|
|
12
|
+
* hook.
|
|
11
13
|
*/
|
|
12
14
|
export interface GridHostLocal {
|
|
13
15
|
chunkVoxels?(x: bigint, y: bigint, z: bigint): {
|
|
14
16
|
voxelsBase64: string | null;
|
|
15
17
|
} | null;
|
|
16
18
|
actorsInChunk?(x: bigint, y: bigint, z: bigint): Array<Record<string, unknown>>;
|
|
19
|
+
/**
|
|
20
|
+
* The chunk of the avatar's live actor, from the game's actor store, or null when the game
|
|
21
|
+
* sees none. `avatar_state_get` answers only for an avatar whose actor is inside the grid.
|
|
22
|
+
*/
|
|
23
|
+
avatarChunk?(avatarId: string): GridChunk | null | undefined | Promise<GridChunk | null | undefined>;
|
|
24
|
+
/**
|
|
25
|
+
* The runtime permission keys the visiting player holds on this grid, as the game knows them
|
|
26
|
+
* (where Crowdy Studio's `targetPermissions` come from). `grid_permission_check` answers from
|
|
27
|
+
* these, for that player and this grid only; the server still enforces the permissions.
|
|
28
|
+
*/
|
|
29
|
+
gridPermissionKeys?(): Iterable<string> | Promise<Iterable<string>>;
|
|
17
30
|
setVoxel?(input: {
|
|
18
31
|
chunk: {
|
|
19
32
|
x: number;
|
|
@@ -36,15 +49,15 @@ export interface GridHostCallsOptions {
|
|
|
36
49
|
chunks: ChunksAPI;
|
|
37
50
|
voxels: VoxelsAPI;
|
|
38
51
|
state: StateAPI;
|
|
39
|
-
|
|
52
|
+
/** For `avatar_state_get`, a public read (`avatarAppState`). */
|
|
53
|
+
avatars?: Pick<AvatarsAPI, 'appState'>;
|
|
40
54
|
};
|
|
41
55
|
local?: GridHostLocal;
|
|
42
56
|
/**
|
|
43
|
-
*
|
|
44
|
-
*
|
|
45
|
-
* functions are meant to be scriptable by visitors' mods.
|
|
57
|
+
* The visiting player's user id. `grid_permission_check` answers only about them, so without
|
|
58
|
+
* it the call is refused.
|
|
46
59
|
*/
|
|
47
|
-
|
|
60
|
+
userId?: string;
|
|
48
61
|
/**
|
|
49
62
|
* Channels a mod may post to. Default: the grid's own channels only (the
|
|
50
63
|
* server rule for grid code). A game may widen it to channels the player
|
|
@@ -60,11 +73,12 @@ export declare class GridHostCallRefused extends Error {
|
|
|
60
73
|
constructor(fn: string, reason?: string);
|
|
61
74
|
}
|
|
62
75
|
/**
|
|
63
|
-
* The page-side answer to
|
|
64
|
-
* through ordinary CrowdyJS confined to one grid
|
|
65
|
-
* to `PlayerCodeBroker({ onHostCall })`; the broker
|
|
66
|
-
* allowlist, rate caps and chunk
|
|
67
|
-
* `hud_set` and `overlay_draw`
|
|
76
|
+
* The page-side answer to a CLIENT half's host calls (`EXEC_CLIENT_HOST_CALLS`,
|
|
77
|
+
* what crowdy-client-sdk wraps), through ordinary CrowdyJS confined to one grid
|
|
78
|
+
* (DN-10 §4). Hand the result to `PlayerCodeBroker({ onHostCall })`; the broker
|
|
79
|
+
* has already applied the allowlist, the player's consent, rate caps and chunk
|
|
80
|
+
* clamps, and answers `grid_info`, `emit_event`, `hud_set` and `overlay_draw`
|
|
81
|
+
* itself.
|
|
68
82
|
*/
|
|
69
83
|
export declare function createGridHostCalls(options: GridHostCallsOptions): (call: PlayerCodeHostCall) => Promise<unknown>;
|
|
70
84
|
//# sourceMappingURL=grid-host-calls.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"grid-host-calls.d.ts","sourceRoot":"","sources":["../../src/grid-mods/grid-host-calls.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,kBAAkB,CAAC;
|
|
1
|
+
{"version":3,"file":"grid-host-calls.d.ts","sourceRoot":"","sources":["../../src/grid-mods/grid-host-calls.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,SAAS,EAAE,SAAS,EAAE,MAAM,kBAAkB,CAAC;AAC7D,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,yCAAyC,CAAC;AAClF,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,uBAAuB,CAAC;AACxD,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,sBAAsB,CAAC;AACtD,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,sBAAsB,CAAC;AACtD,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,qBAAqB,CAAC;AAEpD;;;;;;GAMG;AACH,MAAM,WAAW,aAAa;IAC5B,WAAW,CAAC,CAAC,CAAC,EAAE,MAAM,EAAE,CAAC,EAAE,MAAM,EAAE,CAAC,EAAE,MAAM,GAAG;QAAE,YAAY,EAAE,MAAM,GAAG,IAAI,CAAA;KAAE,GAAG,IAAI,CAAC;IACtF,aAAa,CAAC,CAAC,CAAC,EAAE,MAAM,EAAE,CAAC,EAAE,MAAM,EAAE,CAAC,EAAE,MAAM,GAAG,KAAK,CAAC,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC,CAAC;IAChF;;;OAGG;IACH,WAAW,CAAC,CAAC,QAAQ,EAAE,MAAM,GAAG,SAAS,GAAG,IAAI,GAAG,SAAS,GAAG,OAAO,CAAC,SAAS,GAAG,IAAI,GAAG,SAAS,CAAC,CAAC;IACrG;;;;OAIG;IACH,kBAAkB,CAAC,IAAI,QAAQ,CAAC,MAAM,CAAC,GAAG,OAAO,CAAC,QAAQ,CAAC,MAAM,CAAC,CAAC,CAAC;IACpE,QAAQ,CAAC,CAAC,KAAK,EAAE;QACf,KAAK,EAAE;YAAE,CAAC,EAAE,MAAM,CAAC;YAAC,CAAC,EAAE,MAAM,CAAC;YAAC,CAAC,EAAE,MAAM,CAAA;SAAE,CAAC;QAC3C,CAAC,EAAE,MAAM,CAAC;QACV,CAAC,EAAE,MAAM,CAAC;QACV,CAAC,EAAE,MAAM,CAAC;QACV,SAAS,EAAE,MAAM,CAAC;QAClB,KAAK,CAAC,EAAE,MAAM,CAAC;KAChB,GAAG,OAAO,CAAC,OAAO,CAAC,CAAC;IACrB,kBAAkB,CAAC,IAAI,OAAO,CAAC;CAChC;AAED,MAAM,WAAW,oBAAoB;IACnC,yEAAyE;IACzE,KAAK,EAAE,SAAS,CAAC;IACjB,wEAAwE;IACxE,MAAM,EAAE;QACN,MAAM,EAAE,SAAS,CAAC;QAClB,MAAM,EAAE,SAAS,CAAC;QAClB,KAAK,EAAE,QAAQ,CAAC;QAChB,gEAAgE;QAChE,OAAO,CAAC,EAAE,IAAI,CAAC,UAAU,EAAE,UAAU,CAAC,CAAC;KACxC,CAAC;IACF,KAAK,CAAC,EAAE,aAAa,CAAC;IACtB;;;OAGG;IACH,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB;;;;OAIG;IACH,aAAa,CAAC,EAAE,CAAC,SAAS,EAAE,MAAM,KAAK,OAAO,GAAG,OAAO,CAAC,OAAO,CAAC,CAAC;IAClE,0EAA0E;IAC1E,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB;AAED,sEAAsE;AACtE,qBAAa,mBAAoB,SAAQ,KAAK;IAChC,QAAQ,CAAC,EAAE,EAAE,MAAM;gBAAV,EAAE,EAAE,MAAM,EAAE,MAAM,SAA+B;CAIvE;AAID;;;;;;;GAOG;AACH,wBAAgB,mBAAmB,CACjC,OAAO,EAAE,oBAAoB,GAC5B,CAAC,IAAI,EAAE,kBAAkB,KAAK,OAAO,CAAC,OAAO,CAAC,CA6JhD"}
|