castle-web-sdk 0.4.42 → 0.4.44
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/README.md +96 -7
- package/dist/castle.d.ts +3 -1
- package/dist/castle.js +1 -0
- package/dist/commands.d.ts +16 -0
- package/dist/display.d.ts +36 -0
- package/dist/display.js +96 -0
- package/dist/runtime.js +35 -17
- package/dist/store.d.ts +17 -0
- package/dist/store.js +10 -0
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -250,20 +250,48 @@ A deck's session server can submit any player's score, which is how a
|
|
|
250
250
|
leaderboard becomes something a player cannot fake. See the multiplayer
|
|
251
251
|
guide.
|
|
252
252
|
|
|
253
|
+
### `Store.usage(): Promise<StoreUsage>`
|
|
254
|
+
|
|
255
|
+
How much the current player and the deck have stored, against the
|
|
256
|
+
limits below, so a deck can warn a player or clear out old saves before a
|
|
257
|
+
write is refused instead of after.
|
|
258
|
+
|
|
259
|
+
```js
|
|
260
|
+
const { player, shared } = await Store.usage();
|
|
261
|
+
if (player.byteCount > player.byteLimit * 0.9) {
|
|
262
|
+
// Nearly full: offer to delete old saves.
|
|
263
|
+
}
|
|
264
|
+
```
|
|
265
|
+
|
|
266
|
+
- `player` is the current player's own allowance in this deck: their
|
|
267
|
+
`Store.me` and `Store.private` keys, their board scores, and what the
|
|
268
|
+
deck's `Characters` keep for them in their playthrough.
|
|
269
|
+
- `shared` is `Store.deck`, one allowance for every player together.
|
|
270
|
+
|
|
271
|
+
Each has `byteCount`, `byteLimit`, `keyCount` and `keyLimit`. A board
|
|
272
|
+
score counts as a key. The answer includes the `set` and `remove` calls
|
|
273
|
+
the page made before calling it, even ones still being batched; await an
|
|
274
|
+
`increment` or a board `submit` first if the answer should include it. A player can only ever see their own usage, not
|
|
275
|
+
another player's.
|
|
276
|
+
|
|
277
|
+
An app too old to know `usage` rejects with a `CastleError` whose `code`
|
|
278
|
+
is `UNKNOWN_COMMAND`.
|
|
279
|
+
|
|
253
280
|
### Limits
|
|
254
281
|
|
|
255
282
|
The platform caps key length, value size, how many keys one read may
|
|
256
|
-
name, and how much
|
|
283
|
+
name, and how much each player may store, and it limits how fast a
|
|
257
284
|
player may read and write. Going over one of these rejects with a
|
|
258
285
|
`CastleError` whose `code` says which, so a deck can tell a player it is
|
|
259
|
-
saving too fast apart from a
|
|
286
|
+
saving too fast apart from a player who is out of space:
|
|
260
287
|
|
|
261
288
|
| limit | value |
|
|
262
289
|
|---|---|
|
|
263
290
|
| key | 128 bytes |
|
|
264
291
|
| value | 256 KB |
|
|
265
292
|
| keys in one `get` | 32 |
|
|
266
|
-
|
|
|
293
|
+
| one player's data in a deck | 50 MB, 50,000 keys |
|
|
294
|
+
| the deck's shared data (`Store.deck`) | 500 MB, 1,000,000 keys |
|
|
267
295
|
| one player's calls | 20 a second, 512 KB a second |
|
|
268
296
|
| every player of a deck together | 500 calls a second, 8 MB a second |
|
|
269
297
|
|
|
@@ -273,6 +301,14 @@ A `list` page holds at most 1,000 entries (a larger `limit` is lowered to
|
|
|
273
301
|
it) and ends early once it reaches 1 MB; take the next page with its
|
|
274
302
|
`cursor`.
|
|
275
303
|
|
|
304
|
+
A player's data is everything stored for them: their `Store.private`
|
|
305
|
+
keys, their `Store.user(id)` keys, their scores on every board, and
|
|
306
|
+
their `Characters` playthrough.
|
|
307
|
+
Each player has their own allowance, so a deck with more
|
|
308
|
+
players can hold more, and one player filling theirs does not stop
|
|
309
|
+
anyone else. A write that shrinks what a player stores always succeeds,
|
|
310
|
+
so a player who is full can always free space.
|
|
311
|
+
|
|
276
312
|
A value is JSON, so binary data such as a picture or a video clip costs
|
|
277
313
|
about a third more once encoded, and a clip of more than a few seconds
|
|
278
314
|
does not fit in one. Ship media with the deck instead.
|
|
@@ -747,9 +783,10 @@ limited to Castle admins for now; every other player gets `'refused'`.
|
|
|
747
783
|
- `tools` — optional function tools, in the OpenAI function-tool schema.
|
|
748
784
|
- `toolChoice` — optional: `'auto'` (the default), `'none'`, `'required'`,
|
|
749
785
|
or `{ type: 'function', function: { name } }` to force one tool.
|
|
750
|
-
- `model` — optional OpenRouter model id
|
|
751
|
-
|
|
752
|
-
|
|
786
|
+
- `model` — optional OpenRouter model id, `'meta/muse-spark-1.3-contributor'`
|
|
787
|
+
by default. Any model OpenRouter lists with a per-token price works; one it
|
|
788
|
+
does not list, or one with a variable price (`'openrouter/auto'`), resolves
|
|
789
|
+
`'error'`.
|
|
753
790
|
- `maxTokens` — optional cap on the reply's length. Defaults to 4096; the
|
|
754
791
|
service caps it at 16384.
|
|
755
792
|
- `reasoning` — optional `{ effort: 'minimal' | 'low' | 'medium' | 'high' }`,
|
|
@@ -817,7 +854,10 @@ for (let round = 0; round < 8; round++) {
|
|
|
817
854
|
`AgentChatResult` has a `status`:
|
|
818
855
|
|
|
819
856
|
- `'ok'` — `message` is the assistant's reply: `{ role, content,
|
|
820
|
-
tool_calls? }`, with `content` null when the reply is
|
|
857
|
+
tool_calls?, reasoning_details? }`, with `content` null when the reply is
|
|
858
|
+
only tool calls. `reasoning_details` holds the model's reasoning blocks
|
|
859
|
+
(some encrypted) when it returned any; append the message unchanged so the
|
|
860
|
+
model can pick its reasoning back up on the next call.
|
|
821
861
|
`reasoning` is the model's reasoning summary when it gave one, for
|
|
822
862
|
display; it is not part of `message`. `finishReason` is `'stop'`,
|
|
823
863
|
`'tool_calls'` or `'length'`. `usage` carries `prompt_tokens`,
|
|
@@ -968,6 +1008,55 @@ Host-specific layout flags such as `CastleEmbed.feed` are deprecated.
|
|
|
968
1008
|
New code should let Castle hosts provide the frame and let the SDK infer
|
|
969
1009
|
whether it is running standalone or embedded.
|
|
970
1010
|
|
|
1011
|
+
### Filling the screen: `<meta name="castle-display">`
|
|
1012
|
+
|
|
1013
|
+
By default a deck is a 5:7 portrait card. A deck that can lay itself out at
|
|
1014
|
+
other shapes opts out of the card in its own `index.html`:
|
|
1015
|
+
|
|
1016
|
+
```html
|
|
1017
|
+
<meta name="castle-display" content="layout=fill">
|
|
1018
|
+
<!-- or narrow the shapes it accepts (width / height, as 16/9, 16:9 or 1.78) -->
|
|
1019
|
+
<meta name="castle-display" content="layout=fill, min-aspect=9/16, max-aspect=16/9">
|
|
1020
|
+
```
|
|
1021
|
+
|
|
1022
|
+
A `fill` deck gets the host's whole frame, edge to edge with no rounded
|
|
1023
|
+
corners, whenever that frame's aspect is inside its range (default 9/21 to
|
|
1024
|
+
21/9); outside it, the deck gets the largest in-range box, centered. Castle's
|
|
1025
|
+
web player gives a fill deck a bigger stage and a fullscreen button. Hosts
|
|
1026
|
+
that don't know the tag still give it a 5:7 frame, so a fill deck should look
|
|
1027
|
+
right at 5:7 as well.
|
|
1028
|
+
|
|
1029
|
+
Lay out against the stage: the element `initCard()` returns, or
|
|
1030
|
+
`--castle-card-w`/`--castle-card-h` in CSS, with a resize listener. Not the
|
|
1031
|
+
window: the stage is smaller than the window when the host frame is outside
|
|
1032
|
+
the deck's range, and inside `castle-web serve`'s own page. `getDisplay()` returns the parsed declaration and `fitStage(w, h,
|
|
1033
|
+
display)` the box the SDK would pick.
|
|
1034
|
+
|
|
1035
|
+
Size HUD, buttons and text in the card unit, not in `vw` or `cqw`:
|
|
1036
|
+
|
|
1037
|
+
```css
|
|
1038
|
+
.score { font-size: calc(5 * var(--castle-unit)); top: calc(3 * var(--castle-unit)); }
|
|
1039
|
+
```
|
|
1040
|
+
|
|
1041
|
+
`--castle-unit` is 1% of the width of the largest 5:7 card that fits in the
|
|
1042
|
+
stage. On a card, or a stage taller than a card, it is 1% of the stage width
|
|
1043
|
+
(the same as `1vw`); on a wide stage it stops growing, so a HUD keeps its card
|
|
1044
|
+
size while the playfield gets the extra room. Anything sized in `vw` grows with
|
|
1045
|
+
the stage instead, and a 16:9 monitor makes it huge. Canvas code gets the same
|
|
1046
|
+
number from `cardUnit(width, height)` (px). The SDK sets the exact value for
|
|
1047
|
+
its own stage; in edit mode, and in a kit's scene editor that draws the stage
|
|
1048
|
+
smaller than the window, it falls back to the window's.
|
|
1049
|
+
|
|
1050
|
+
A 3D camera tuned for the card keeps its framing with `fovForAspect(fov,
|
|
1051
|
+
aspect)`: it returns `fov` at 5:7 and wider (a wide stage shows more to the
|
|
1052
|
+
sides) and widens it on a stage narrower than 5:7, so a tall phone still sees
|
|
1053
|
+
the card's full width.
|
|
1054
|
+
|
|
1055
|
+
```js
|
|
1056
|
+
camera.fov = fovForAspect(50, width / height);
|
|
1057
|
+
camera.updateProjectionMatrix();
|
|
1058
|
+
```
|
|
1059
|
+
|
|
971
1060
|
### `CARD_RATIO`
|
|
972
1061
|
|
|
973
1062
|
The card aspect ratio (`5 / 7`). Use this if you need to size something
|
package/dist/castle.d.ts
CHANGED
|
@@ -29,11 +29,13 @@ export { Portal } from "./portal";
|
|
|
29
29
|
export type { CastlePortalApi, PortalOpenResult, PortalOpenStatus, PortalPrefetchResult, PortalPrefetchStatus, } from "./portal";
|
|
30
30
|
export { CARD_RATIO, deleteFile, fileUrl, initCard, onBeforeRestart, onFilesChanged, onSaveReloadState, openFile, renameFile, requestReload, setup, takeReloadState, writeFileOnce, } from "./runtime";
|
|
31
31
|
export type { FileChange, FilesChangedEvent } from "./runtime";
|
|
32
|
+
export { cardUnit, fitStage, fovForAspect, getDisplay } from "./display";
|
|
33
|
+
export type { DeckDisplay, DeckLayout } from "./display";
|
|
32
34
|
export { flushSaves, hasPendingSave, onSaveState, writeFile, } from "./saveQueue";
|
|
33
35
|
export type { SaveState } from "./saveQueue";
|
|
34
36
|
export { SharedStorage, Storage } from "./storage";
|
|
35
37
|
export { Store } from "./store";
|
|
36
|
-
export type { CastleStoreApi, StoreBoardApi, StoreBoardScore, StoreListEntry, StoreListOptions, StoreListPage, StoreScopeApi, } from "./store";
|
|
38
|
+
export type { CastleStoreApi, StoreBoardApi, StoreBoardScore, StoreListEntry, StoreListOptions, StoreListPage, StoreAllowance, StoreScopeApi, StoreUsage, } from "./store";
|
|
37
39
|
export { Time } from "./time";
|
|
38
40
|
export type { CastleClockZone, CastleDateParts, CastleTimeApi } from "./time";
|
|
39
41
|
export type { Json } from "./types";
|
package/dist/castle.js
CHANGED
|
@@ -15,6 +15,7 @@ export { Pass } from "./passes";
|
|
|
15
15
|
export { Snapshot } from "./snapshots";
|
|
16
16
|
export { Portal } from "./portal";
|
|
17
17
|
export { CARD_RATIO, deleteFile, fileUrl, initCard, onBeforeRestart, onFilesChanged, onSaveReloadState, openFile, renameFile, requestReload, setup, takeReloadState, writeFileOnce, } from "./runtime";
|
|
18
|
+
export { cardUnit, fitStage, fovForAspect, getDisplay } from "./display";
|
|
18
19
|
export { flushSaves, hasPendingSave, onSaveState, writeFile, } from "./saveQueue";
|
|
19
20
|
export { SharedStorage, Storage } from "./storage";
|
|
20
21
|
export { Store } from "./store";
|
package/dist/commands.d.ts
CHANGED
|
@@ -11,6 +11,17 @@ export interface StoreUsage {
|
|
|
11
11
|
rowCount: number;
|
|
12
12
|
byteCount: number;
|
|
13
13
|
}
|
|
14
|
+
/** One storage allowance: what is stored against it, and its limits. */
|
|
15
|
+
export interface StoreAllowance {
|
|
16
|
+
keyCount: number;
|
|
17
|
+
keyLimit: number;
|
|
18
|
+
byteCount: number;
|
|
19
|
+
byteLimit: number;
|
|
20
|
+
}
|
|
21
|
+
export interface StoreAllowances {
|
|
22
|
+
player: StoreAllowance;
|
|
23
|
+
shared: StoreAllowance;
|
|
24
|
+
}
|
|
14
25
|
export interface StoreBoardEntry {
|
|
15
26
|
subject: string;
|
|
16
27
|
score: number;
|
|
@@ -201,6 +212,7 @@ export interface AgentMessage {
|
|
|
201
212
|
tool_calls?: AgentToolCall[];
|
|
202
213
|
tool_call_id?: string;
|
|
203
214
|
name?: string;
|
|
215
|
+
reasoning_details?: Json[];
|
|
204
216
|
}
|
|
205
217
|
export interface AgentTool {
|
|
206
218
|
type: "function";
|
|
@@ -428,6 +440,7 @@ export interface CommandParams {
|
|
|
428
440
|
board: string;
|
|
429
441
|
subject?: string | null;
|
|
430
442
|
};
|
|
443
|
+
"cauldronStorage.usage": Record<string, never>;
|
|
431
444
|
"lifecycle.restoreState": Record<string, never>;
|
|
432
445
|
}
|
|
433
446
|
export interface CommandResult {
|
|
@@ -525,6 +538,9 @@ export interface CommandResult {
|
|
|
525
538
|
"cauldronStorage.boardGet": {
|
|
526
539
|
entry: StoreBoardEntry | null;
|
|
527
540
|
};
|
|
541
|
+
"cauldronStorage.usage": {
|
|
542
|
+
usage: StoreAllowances;
|
|
543
|
+
};
|
|
528
544
|
"lifecycle.restoreState": LifecycleRestoreStateResult;
|
|
529
545
|
}
|
|
530
546
|
export interface UserPayload {
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
export type DeckLayout = "card" | "fill";
|
|
2
|
+
export interface DeckDisplay {
|
|
3
|
+
layout: DeckLayout;
|
|
4
|
+
/** Narrowest width / height the deck accepts. */
|
|
5
|
+
minAspect: number;
|
|
6
|
+
/** Widest width / height the deck accepts. */
|
|
7
|
+
maxAspect: number;
|
|
8
|
+
}
|
|
9
|
+
export declare const CARD_ASPECT: number;
|
|
10
|
+
/** Default fill range: a tall 21:9 phone held upright to a 21:9 monitor. */
|
|
11
|
+
export declare const FILL_MIN_ASPECT: number;
|
|
12
|
+
export declare const FILL_MAX_ASPECT: number;
|
|
13
|
+
export declare const CARD_DISPLAY: DeckDisplay;
|
|
14
|
+
export declare function parseDisplayContent(content: string | null | undefined): DeckDisplay;
|
|
15
|
+
/** The display this deck declared in its index.html (a card when it declared none). */
|
|
16
|
+
export declare function getDisplay(): DeckDisplay;
|
|
17
|
+
/**
|
|
18
|
+
* The card unit, in px, for a stage of `width` x `height`: 1% of the width of
|
|
19
|
+
* the largest 5:7 card that fits in it. On a card it is 1% of the card's width;
|
|
20
|
+
* on a wider stage it stops growing, so HUD and text sized in it stay the size
|
|
21
|
+
* they are on a card while the playfield gets the extra room. CSS gets the same
|
|
22
|
+
* value as `var(--castle-unit)`.
|
|
23
|
+
*/
|
|
24
|
+
export declare function cardUnit(width: number, height: number): number;
|
|
25
|
+
/**
|
|
26
|
+
* A vertical field of view (degrees) for a camera tuned at 5:7. On a stage
|
|
27
|
+
* narrower than 5:7 (a tall phone) it widens so the card's horizontal extent
|
|
28
|
+
* stays in view; at 5:7 and wider it returns `fovDeg` unchanged, so a wide
|
|
29
|
+
* stage just shows more to the sides. Capped at 120 degrees.
|
|
30
|
+
*/
|
|
31
|
+
export declare function fovForAspect(fovDeg: number, aspect: number): number;
|
|
32
|
+
/** The largest box inside `width` x `height` whose aspect is in the display's range. */
|
|
33
|
+
export declare function fitStage(width: number, height: number, display: DeckDisplay): {
|
|
34
|
+
w: number;
|
|
35
|
+
h: number;
|
|
36
|
+
};
|
package/dist/display.js
ADDED
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
// How much of the host's frame a deck may use.
|
|
2
|
+
//
|
|
3
|
+
// A deck declares it in its own index.html, the way a page declares its
|
|
4
|
+
// viewport:
|
|
5
|
+
//
|
|
6
|
+
// <meta name="castle-display" content="layout=fill">
|
|
7
|
+
// <meta name="castle-display" content="layout=fill, min-aspect=9/16, max-aspect=16/9">
|
|
8
|
+
//
|
|
9
|
+
// With no tag the deck is a `card`: the classic 5:7 portrait card, exactly as
|
|
10
|
+
// before. A `fill` deck takes whatever box the host gives it, as long as the
|
|
11
|
+
// box's aspect (width / height) is inside [minAspect, maxAspect]; outside that
|
|
12
|
+
// range it gets the largest box in range, centered. A card is just the range
|
|
13
|
+
// [5/7, 5/7].
|
|
14
|
+
//
|
|
15
|
+
// Hosts read the same tag out of the bundle to size the frame before the deck
|
|
16
|
+
// loads (castle-www common/deckDisplay.ts mirrors this parser). A host that
|
|
17
|
+
// knows nothing about it still gives the deck a 5:7 frame, so a fill deck
|
|
18
|
+
// should render well at 5:7 too; one whose range leaves 5/7 out (landscape
|
|
19
|
+
// only) is letterboxed inside such a frame.
|
|
20
|
+
export const CARD_ASPECT = 5 / 7;
|
|
21
|
+
/** Default fill range: a tall 21:9 phone held upright to a 21:9 monitor. */
|
|
22
|
+
export const FILL_MIN_ASPECT = 9 / 21;
|
|
23
|
+
export const FILL_MAX_ASPECT = 21 / 9;
|
|
24
|
+
export const CARD_DISPLAY = {
|
|
25
|
+
layout: "card",
|
|
26
|
+
minAspect: CARD_ASPECT,
|
|
27
|
+
maxAspect: CARD_ASPECT,
|
|
28
|
+
};
|
|
29
|
+
// "16/9", "16:9" or "1.78"; anything else (or a non-positive value) is null.
|
|
30
|
+
function parseAspect(value) {
|
|
31
|
+
const ratio = value.match(/^\s*(\d+(?:\.\d+)?)\s*[/:]\s*(\d+(?:\.\d+)?)\s*$/);
|
|
32
|
+
const n = ratio ? Number(ratio[1]) / Number(ratio[2]) : Number(value);
|
|
33
|
+
return Number.isFinite(n) && n > 0 ? n : null;
|
|
34
|
+
}
|
|
35
|
+
export function parseDisplayContent(content) {
|
|
36
|
+
if (!content)
|
|
37
|
+
return CARD_DISPLAY;
|
|
38
|
+
const fields = {};
|
|
39
|
+
for (const part of content.split(/[,;]/)) {
|
|
40
|
+
const eq = part.indexOf("=");
|
|
41
|
+
if (eq < 0)
|
|
42
|
+
continue;
|
|
43
|
+
fields[part.slice(0, eq).trim().toLowerCase()] = part.slice(eq + 1).trim();
|
|
44
|
+
}
|
|
45
|
+
if (fields.layout?.toLowerCase() !== "fill")
|
|
46
|
+
return CARD_DISPLAY;
|
|
47
|
+
let minAspect = parseAspect(fields["min-aspect"] ?? "") ?? FILL_MIN_ASPECT;
|
|
48
|
+
let maxAspect = parseAspect(fields["max-aspect"] ?? "") ?? FILL_MAX_ASPECT;
|
|
49
|
+
if (minAspect > maxAspect)
|
|
50
|
+
[minAspect, maxAspect] = [maxAspect, minAspect];
|
|
51
|
+
return { layout: "fill", minAspect, maxAspect };
|
|
52
|
+
}
|
|
53
|
+
let cached = null;
|
|
54
|
+
/** The display this deck declared in its index.html (a card when it declared none). */
|
|
55
|
+
export function getDisplay() {
|
|
56
|
+
if (cached)
|
|
57
|
+
return cached;
|
|
58
|
+
if (typeof document === "undefined")
|
|
59
|
+
return CARD_DISPLAY;
|
|
60
|
+
const meta = document.querySelector('meta[name="castle-display"]');
|
|
61
|
+
cached = parseDisplayContent(meta?.content);
|
|
62
|
+
return cached;
|
|
63
|
+
}
|
|
64
|
+
/**
|
|
65
|
+
* The card unit, in px, for a stage of `width` x `height`: 1% of the width of
|
|
66
|
+
* the largest 5:7 card that fits in it. On a card it is 1% of the card's width;
|
|
67
|
+
* on a wider stage it stops growing, so HUD and text sized in it stay the size
|
|
68
|
+
* they are on a card while the playfield gets the extra room. CSS gets the same
|
|
69
|
+
* value as `var(--castle-unit)`.
|
|
70
|
+
*/
|
|
71
|
+
export function cardUnit(width, height) {
|
|
72
|
+
return Math.min(Math.max(0, width), Math.max(0, height) * CARD_ASPECT) / 100;
|
|
73
|
+
}
|
|
74
|
+
/**
|
|
75
|
+
* A vertical field of view (degrees) for a camera tuned at 5:7. On a stage
|
|
76
|
+
* narrower than 5:7 (a tall phone) it widens so the card's horizontal extent
|
|
77
|
+
* stays in view; at 5:7 and wider it returns `fovDeg` unchanged, so a wide
|
|
78
|
+
* stage just shows more to the sides. Capped at 120 degrees.
|
|
79
|
+
*/
|
|
80
|
+
export function fovForAspect(fovDeg, aspect) {
|
|
81
|
+
if (!(aspect > 0) || aspect >= CARD_ASPECT)
|
|
82
|
+
return fovDeg;
|
|
83
|
+
const half = Math.atan((Math.tan((fovDeg * Math.PI) / 360) * CARD_ASPECT) / aspect);
|
|
84
|
+
return Math.min(120, (half * 360) / Math.PI);
|
|
85
|
+
}
|
|
86
|
+
/** The largest box inside `width` x `height` whose aspect is in the display's range. */
|
|
87
|
+
export function fitStage(width, height, display) {
|
|
88
|
+
const aw = Math.max(1, width);
|
|
89
|
+
const ah = Math.max(1, height);
|
|
90
|
+
const aspect = aw / ah;
|
|
91
|
+
if (aspect > display.maxAspect)
|
|
92
|
+
return { w: ah * display.maxAspect, h: ah };
|
|
93
|
+
if (aspect < display.minAspect)
|
|
94
|
+
return { w: aw, h: aw / display.minAspect };
|
|
95
|
+
return { w: aw, h: ah };
|
|
96
|
+
}
|
package/dist/runtime.js
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { CASTLE_SDK_PROTOCOL, } from "./commands";
|
|
2
2
|
import { getCastleEmbed, isEdit } from "./context";
|
|
3
|
+
import { cardUnit, fitStage, getDisplay } from "./display";
|
|
3
4
|
import { appliedNotice, applyTunedParams, } from "./paramsRegistry";
|
|
4
5
|
export const CARD_RATIO = 5 / 7;
|
|
5
6
|
// Host -> deck capture request, and the deck's answer. Kept in sync with the
|
|
@@ -31,6 +32,7 @@ export function setup() {
|
|
|
31
32
|
initHostCapture();
|
|
32
33
|
initPlaySelection();
|
|
33
34
|
initEditSelection();
|
|
35
|
+
initCardUnitFallback();
|
|
34
36
|
initPlayCard();
|
|
35
37
|
logPanelLoad();
|
|
36
38
|
}
|
|
@@ -120,7 +122,7 @@ export function initCard() {
|
|
|
120
122
|
body { display: flex; align-items: center; justify-content: center; }
|
|
121
123
|
#castle-card {
|
|
122
124
|
position: relative;
|
|
123
|
-
border-radius:
|
|
125
|
+
border-radius: ${cardRadius()};
|
|
124
126
|
overflow: hidden;
|
|
125
127
|
background: #121213;
|
|
126
128
|
}
|
|
@@ -129,10 +131,12 @@ export function initCard() {
|
|
|
129
131
|
const card = document.createElement("div");
|
|
130
132
|
card.id = "castle-card";
|
|
131
133
|
document.body.appendChild(card);
|
|
134
|
+
initCardUnitFallback();
|
|
132
135
|
function resize() {
|
|
133
136
|
const { w, h } = computeCardSize();
|
|
134
137
|
card.style.width = w + "px";
|
|
135
138
|
card.style.height = h + "px";
|
|
139
|
+
setCardUnit(w, h);
|
|
136
140
|
}
|
|
137
141
|
resize();
|
|
138
142
|
window.addEventListener("resize", resize);
|
|
@@ -231,8 +235,10 @@ function initEditSelection() {
|
|
|
231
235
|
selection.removeAllRanges();
|
|
232
236
|
});
|
|
233
237
|
}
|
|
234
|
-
// Constrains whatever the deck renders into #root to
|
|
235
|
-
//
|
|
238
|
+
// Constrains whatever the deck renders into #root to the deck's stage in play
|
|
239
|
+
// mode: a 5:7 card, or for a `fill` deck (see display.ts) the host's whole frame
|
|
240
|
+
// within the deck's aspect range. Hosts own max size and padding; the SDK only
|
|
241
|
+
// preserves the aspect.
|
|
236
242
|
function initPlayCard() {
|
|
237
243
|
if (isEdit())
|
|
238
244
|
return;
|
|
@@ -247,7 +253,7 @@ function initPlayCard() {
|
|
|
247
253
|
transform: translate(-50%, -50%) !important;
|
|
248
254
|
width: var(--castle-card-w, 100vw) !important;
|
|
249
255
|
height: var(--castle-card-h, 100vh) !important;
|
|
250
|
-
border-radius:
|
|
256
|
+
border-radius: ${cardRadius()} !important;
|
|
251
257
|
overflow: hidden !important;
|
|
252
258
|
}
|
|
253
259
|
`;
|
|
@@ -276,25 +282,37 @@ function initPlayCard() {
|
|
|
276
282
|
const { w, h } = computeCardSize();
|
|
277
283
|
document.documentElement.style.setProperty("--castle-card-w", w + "px");
|
|
278
284
|
document.documentElement.style.setProperty("--castle-card-h", h + "px");
|
|
285
|
+
setCardUnit(w, h);
|
|
279
286
|
}
|
|
280
287
|
resize();
|
|
281
288
|
window.addEventListener("resize", resize);
|
|
282
289
|
}
|
|
290
|
+
// `--castle-unit`: 1% of the width of the largest 5:7 card inside the stage
|
|
291
|
+
// (display.ts cardUnit). A deck sizes HUD and text in it so they keep card
|
|
292
|
+
// proportions on a wide fill stage. Play mode sets the exact px value on every
|
|
293
|
+
// resize; the stylesheet fallback covers edit mode and the moment before the
|
|
294
|
+
// first resize, and follows the window -- which is not the stage where a kit's
|
|
295
|
+
// scene editor draws the deck smaller inside the editor.
|
|
296
|
+
function setCardUnit(w, h) {
|
|
297
|
+
document.documentElement.style.setProperty("--castle-unit", cardUnit(w, h) + "px");
|
|
298
|
+
}
|
|
299
|
+
let cardUnitFallbackInstalled = false;
|
|
300
|
+
function initCardUnitFallback() {
|
|
301
|
+
if (cardUnitFallbackInstalled)
|
|
302
|
+
return;
|
|
303
|
+
cardUnitFallbackInstalled = true;
|
|
304
|
+
const style = document.createElement("style");
|
|
305
|
+
style.textContent = `:root { --castle-unit: min(1vw, 1vh * 5 / 7); }`;
|
|
306
|
+
document.head.appendChild(style);
|
|
307
|
+
}
|
|
283
308
|
function computeCardSize() {
|
|
284
309
|
const pad = shouldUseStandaloneChrome() ? 20 : 0;
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
h = w / CARD_RATIO;
|
|
292
|
-
}
|
|
293
|
-
else {
|
|
294
|
-
h = ah;
|
|
295
|
-
w = h * CARD_RATIO;
|
|
296
|
-
}
|
|
297
|
-
return { w, h };
|
|
310
|
+
return fitStage(window.innerWidth - pad * 2, window.innerHeight - pad * 2, getDisplay());
|
|
311
|
+
}
|
|
312
|
+
// A card keeps its rounded corners; a fill deck runs edge to edge and the host
|
|
313
|
+
// decides how its frame looks.
|
|
314
|
+
function cardRadius() {
|
|
315
|
+
return getDisplay().layout === "fill" ? "0" : "4% / calc(4% * (5 / 7))";
|
|
298
316
|
}
|
|
299
317
|
function shouldUseStandaloneChrome() {
|
|
300
318
|
if (window.parent !== window)
|
package/dist/store.d.ts
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import type { StoreAllowance } from "./commands";
|
|
1
2
|
import type { Json } from "./types";
|
|
2
3
|
export interface StoreListOptions {
|
|
3
4
|
/** Only keys beginning with this. Omit for every key in the scope. */
|
|
@@ -52,6 +53,16 @@ export interface StoreBoardApi {
|
|
|
52
53
|
/** One player's score, defaulting to the current player. Null if unset. */
|
|
53
54
|
get(userId?: string): Promise<StoreBoardScore | null>;
|
|
54
55
|
}
|
|
56
|
+
export type { StoreAllowance };
|
|
57
|
+
export interface StoreUsage {
|
|
58
|
+
/**
|
|
59
|
+
* The current player's own data in this deck: their `Store.me` and
|
|
60
|
+
* `Store.private` keys, their board scores and their `Characters` playthrough.
|
|
61
|
+
*/
|
|
62
|
+
player: StoreAllowance;
|
|
63
|
+
/** `Store.deck`: one allowance shared by every player of the deck. */
|
|
64
|
+
shared: StoreAllowance;
|
|
65
|
+
}
|
|
55
66
|
export interface CastleStoreApi {
|
|
56
67
|
/** Shared by everyone playing the deck. */
|
|
57
68
|
readonly deck: StoreScopeApi;
|
|
@@ -63,5 +74,11 @@ export interface CastleStoreApi {
|
|
|
63
74
|
readonly private: StoreScopeApi;
|
|
64
75
|
/** A leaderboard. */
|
|
65
76
|
board(name: string): StoreBoardApi;
|
|
77
|
+
/**
|
|
78
|
+
* How much of the current player's allowance, and of the deck's shared one, is
|
|
79
|
+
* used. Includes the `set` and `remove` calls made before it, even ones still
|
|
80
|
+
* being batched; an `increment` or board `submit` is included once awaited.
|
|
81
|
+
*/
|
|
82
|
+
usage(): Promise<StoreUsage>;
|
|
66
83
|
}
|
|
67
84
|
export declare const Store: CastleStoreApi;
|
package/dist/store.js
CHANGED
|
@@ -300,6 +300,15 @@ function createBoard(name) {
|
|
|
300
300
|
},
|
|
301
301
|
};
|
|
302
302
|
}
|
|
303
|
+
async function usage() {
|
|
304
|
+
// Writes still waiting out the coalescing window would otherwise be missing from
|
|
305
|
+
// the answer, so a deck that saves and then asks would see its old usage. Send
|
|
306
|
+
// them now; flushBucket never rejects, and a write that fails reports to its own
|
|
307
|
+
// caller.
|
|
308
|
+
await Promise.all(Array.from(buckets.values(), (bucket) => flushBucket(bucket, false)));
|
|
309
|
+
const { usage: allowances } = await hostRequest("cauldronStorage.usage", {});
|
|
310
|
+
return { player: allowances.player, shared: allowances.shared };
|
|
311
|
+
}
|
|
303
312
|
export const Store = {
|
|
304
313
|
deck: createScope("deck", null),
|
|
305
314
|
me: createScope("user", null),
|
|
@@ -310,4 +319,5 @@ export const Store = {
|
|
|
310
319
|
board(name) {
|
|
311
320
|
return createBoard(name);
|
|
312
321
|
},
|
|
322
|
+
usage,
|
|
313
323
|
};
|