castle-web-cli 0.4.172 → 0.4.174
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/dist/agent-prompts.d.ts +8 -2
- package/dist/agent-prompts.js +19 -11
- package/dist/agent.js +37 -13
- package/dist/assetCompression.d.ts +3 -0
- package/dist/assetCompression.js +30 -0
- package/dist/castleJson.d.ts +1 -1
- package/dist/castleJson.js +2 -2
- package/dist/imports.js +2 -2
- package/dist/init.js +4 -4
- package/dist/native/loop.js +9 -0
- package/dist/native/openrouter.d.ts +2 -0
- package/dist/native/openrouter.js +10 -1
- package/dist/native/types.d.ts +1 -0
- package/dist/serve.js +2 -0
- package/dist/serveSecurity.d.ts +2 -0
- package/dist/serveSecurity.js +17 -3
- package/dist/shell/assets/{index-Ws0WrCbi.js → index-DF1yMXPS.js} +4 -4
- package/dist/shell/index.html +1 -1
- package/kits/base/castle.json +1 -1
- package/kits/multiplayer-2d/castle.json +2 -2
- package/kits/multiplayer-2d/code/server/sceneBodies.js +1 -1
- package/kits/multiplayer-2d/code/server/world.js +2 -2
- package/kits/multiplayer-2d/code/systems/multiplayer.js +12 -0
- package/kits/multiplayer-2d/fonts.generated.js +1 -1
- package/kits/multiplayer-2d/package-lock.json +1 -1
- package/kits/multiplayer-3d/CLAUDE.md +5 -4
- package/kits/multiplayer-3d/castle.json +3 -3
- package/kits/multiplayer-3d/code/client/avatars.js +47 -18
- package/kits/multiplayer-3d/code/client/nameTags.js +16 -35
- package/kits/multiplayer-3d/code/server/sceneBodies.js +1 -1
- package/kits/multiplayer-3d/code/systems/multiplayer.js +14 -1
- package/kits/multiplayer-3d/fonts.generated.js +1 -1
- package/kits/multiplayer-3d/package-lock.json +1 -1
- package/kits/physics-2d/CLAUDE.md +7 -6
- package/kits/physics-2d/behaviors/Collider.jsx +7 -1
- package/kits/physics-2d/behaviors/RigidBody.jsx +6 -3
- package/kits/physics-2d/castle.json +2 -2
- package/kits/physics-2d/editors/SceneEditor.jsx +1 -0
- package/kits/physics-2d/editors/SingleEditor.jsx +4 -4
- package/kits/physics-2d/editors/{StyleEditor.jsx → ThemeEditor.jsx} +8 -8
- package/kits/physics-2d/editors/deckFont.js +2 -2
- package/kits/physics-2d/editors/{styleEditor.module.css → themeEditor.module.css} +2 -2
- package/kits/physics-2d/engine/avatarArt.js +108 -14
- package/kits/physics-2d/engine/files.js +2 -2
- package/kits/physics-2d/engine/physics/matterBridge.js +2 -2
- package/kits/physics-2d/engine/scene.js +1 -1
- package/kits/physics-2d/fonts.generated.js +1 -1
- package/kits/physics-2d/package-lock.json +1 -1
- package/kits/physics-3d/behaviors/Body.jsx +10 -1
- package/kits/physics-3d/behaviors/Door.jsx +5 -0
- package/kits/physics-3d/behaviors/Lookable.jsx +3 -0
- package/kits/physics-3d/behaviors/Model.jsx +4 -0
- package/kits/physics-3d/behaviors/Pickup.jsx +4 -0
- package/kits/physics-3d/behaviors/Player.jsx +7 -0
- package/kits/physics-3d/behaviors/Shape.jsx +13 -0
- package/kits/physics-3d/castle.json +1 -1
- package/kits/physics-3d/engine3d/editor3dInspector.jsx +1 -0
- package/kits/physics-3d/fonts.generated.js +1 -1
- package/kits/physics-3d/package-lock.json +1 -1
- package/kits/real-time/castle.json +1 -1
- package/kits/real-time/code/client/connection.js +11 -2
- package/kits/real-time/package-lock.json +1 -1
- package/kits/turn-based/castle.json +1 -1
- package/kits/turn-based/fonts.generated.js +1 -1
- package/kits/turn-based/package-lock.json +1 -1
- package/package.json +3 -1
- /package/kits/physics-2d/editors/{styleTheme.js → theme.js} +0 -0
|
@@ -1,11 +1,12 @@
|
|
|
1
|
-
// A player's
|
|
2
|
-
//
|
|
3
|
-
//
|
|
4
|
-
//
|
|
1
|
+
// A player's avatar for drawing in a scene, read through `User.get` once per
|
|
2
|
+
// user id and kept for the page. Two pictures: the round one, the photo clipped
|
|
3
|
+
// to a circle with their frame over it, and the face, the photo alone, square,
|
|
4
|
+
// for a surface it should cover edge to edge. A player with no photo has no
|
|
5
|
+
// avatar, and a caller keeps its default look.
|
|
5
6
|
//
|
|
6
|
-
// `avatarCanvas` is for code that draws with a canvas context, like a
|
|
7
|
-
// `avatarSpritePath`
|
|
8
|
-
// so a `Sprite`
|
|
7
|
+
// `avatarCanvas` is for code that draws with a canvas context, like a Box.
|
|
8
|
+
// `avatarSpritePath` and `avatarFacePath` register a picture as image art in
|
|
9
|
+
// `scene.sprites`, so a `Sprite` or a model face can show it by file path.
|
|
9
10
|
|
|
10
11
|
import { User } from 'castle-web-sdk';
|
|
11
12
|
import { imageArt } from './art';
|
|
@@ -16,32 +17,48 @@ const SIZE = 128;
|
|
|
16
17
|
// own profile views use.
|
|
17
18
|
const FRAMED_INSET = SIZE / 6;
|
|
18
19
|
|
|
19
|
-
// userId -> { canvas }, present from the first ask;
|
|
20
|
+
// userId -> { canvas, face }, present from the first ask; both fill in when the
|
|
20
21
|
// read completes.
|
|
21
22
|
const avatars = new Map();
|
|
22
23
|
|
|
23
24
|
// The avatar for one user id, or null until it is loaded or when there is none.
|
|
24
25
|
export function avatarCanvas(userId) {
|
|
26
|
+
return entryFor(userId)?.canvas ?? null;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
// The user's photo alone, square, or null as for `avatarCanvas`.
|
|
30
|
+
export function avatarFace(userId) {
|
|
31
|
+
return entryFor(userId)?.face ?? null;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
function entryFor(userId) {
|
|
25
35
|
if (!userId) {
|
|
26
36
|
return null;
|
|
27
37
|
}
|
|
28
38
|
let entry = avatars.get(userId);
|
|
29
39
|
if (!entry) {
|
|
30
|
-
entry = { canvas: null };
|
|
40
|
+
entry = { canvas: null, face: null };
|
|
31
41
|
avatars.set(userId, entry);
|
|
32
42
|
void load(userId, entry);
|
|
33
43
|
}
|
|
34
|
-
return entry
|
|
44
|
+
return entry;
|
|
35
45
|
}
|
|
36
46
|
|
|
37
|
-
// The `scene.sprites` path holding this user's avatar, or null while there
|
|
38
|
-
// none. A `Sprite` with this `file` draws it.
|
|
47
|
+
// The `scene.sprites` path holding this user's round avatar, or null while there
|
|
48
|
+
// is none. A `Sprite` with this `file` draws it.
|
|
39
49
|
export function avatarSpritePath(scene, userId) {
|
|
40
|
-
|
|
50
|
+
return registered(scene, `avatars/${userId}.png`, avatarCanvas(userId));
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
// The same for the square face.
|
|
54
|
+
export function avatarFacePath(scene, userId) {
|
|
55
|
+
return registered(scene, `avatars/${userId}-face.png`, avatarFace(userId));
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
function registered(scene, path, canvas) {
|
|
41
59
|
if (!canvas) {
|
|
42
60
|
return null;
|
|
43
61
|
}
|
|
44
|
-
const path = `avatars/${userId}.png`;
|
|
45
62
|
if (!scene.sprites[path]) {
|
|
46
63
|
scene.sprites[path] = imageArt(path, canvas.toDataURL());
|
|
47
64
|
}
|
|
@@ -54,6 +71,7 @@ async function load(userId, entry) {
|
|
|
54
71
|
const [photo, frame] = await Promise.all([loadImage(user?.photoUrl), loadImage(user?.frameUrl)]);
|
|
55
72
|
if (photo) {
|
|
56
73
|
entry.canvas = compose(photo, frame);
|
|
74
|
+
entry.face = square(photo);
|
|
57
75
|
}
|
|
58
76
|
} catch {
|
|
59
77
|
// A failed read leaves the entry without a canvas; the next page asks again.
|
|
@@ -72,6 +90,82 @@ function loadImage(url) {
|
|
|
72
90
|
});
|
|
73
91
|
}
|
|
74
92
|
|
|
93
|
+
// How much of the opaque box is cut from each side, so a round photo's edge and
|
|
94
|
+
// its anti-aliased rim stay off the face.
|
|
95
|
+
const FACE_INSET = 0.12;
|
|
96
|
+
|
|
97
|
+
// The opaque box is never taken smaller than this share of the photo on either
|
|
98
|
+
// axis. Transparency reaching far inward would otherwise blow a few pixels up
|
|
99
|
+
// to the whole face.
|
|
100
|
+
const FACE_MIN = 0.55;
|
|
101
|
+
|
|
102
|
+
// The photo cropped to its opaque pixels and then inset, filling the square.
|
|
103
|
+
// Profile photos are circles on a transparent ground, and a face texture wants
|
|
104
|
+
// no corners and no rim.
|
|
105
|
+
function square(photo) {
|
|
106
|
+
const canvas = document.createElement('canvas');
|
|
107
|
+
canvas.width = SIZE;
|
|
108
|
+
canvas.height = SIZE;
|
|
109
|
+
const ctx = canvas.getContext('2d');
|
|
110
|
+
ctx.drawImage(photo, 0, 0, SIZE, SIZE);
|
|
111
|
+
const box = atLeast(opaqueBox(ctx.getImageData(0, 0, SIZE, SIZE)), SIZE * FACE_MIN, SIZE);
|
|
112
|
+
const inset = Math.round(Math.min(box.width, box.height) * FACE_INSET);
|
|
113
|
+
const face = {
|
|
114
|
+
x: box.x + inset,
|
|
115
|
+
y: box.y + inset,
|
|
116
|
+
width: box.width - 2 * inset,
|
|
117
|
+
height: box.height - 2 * inset,
|
|
118
|
+
};
|
|
119
|
+
if (face.width <= 0 || face.height <= 0) {
|
|
120
|
+
return canvas;
|
|
121
|
+
}
|
|
122
|
+
const source = document.createElement('canvas');
|
|
123
|
+
source.width = SIZE;
|
|
124
|
+
source.height = SIZE;
|
|
125
|
+
source.getContext('2d').drawImage(canvas, 0, 0);
|
|
126
|
+
ctx.clearRect(0, 0, SIZE, SIZE);
|
|
127
|
+
ctx.drawImage(source, face.x, face.y, face.width, face.height, 0, 0, SIZE, SIZE);
|
|
128
|
+
return canvas;
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
// The box grown around its centre to at least `min` on each axis, kept inside
|
|
132
|
+
// `size`.
|
|
133
|
+
function atLeast(box, min, size) {
|
|
134
|
+
const out = { ...box };
|
|
135
|
+
for (const [at, extent] of [
|
|
136
|
+
['x', 'width'],
|
|
137
|
+
['y', 'height'],
|
|
138
|
+
]) {
|
|
139
|
+
if (out[extent] < min) {
|
|
140
|
+
out[at] = Math.round(Math.min(Math.max(0, out[at] + out[extent] / 2 - min / 2), size - min));
|
|
141
|
+
out[extent] = Math.round(min);
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
return out;
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
// The smallest box holding every pixel with alpha over a small threshold.
|
|
148
|
+
function opaqueBox({ data, width, height }) {
|
|
149
|
+
let left = width;
|
|
150
|
+
let top = height;
|
|
151
|
+
let right = -1;
|
|
152
|
+
let bottom = -1;
|
|
153
|
+
for (let y = 0; y < height; y += 1) {
|
|
154
|
+
for (let x = 0; x < width; x += 1) {
|
|
155
|
+
if (data[(y * width + x) * 4 + 3] > 16) {
|
|
156
|
+
left = Math.min(left, x);
|
|
157
|
+
top = Math.min(top, y);
|
|
158
|
+
right = Math.max(right, x);
|
|
159
|
+
bottom = Math.max(bottom, y);
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
if (right < 0) {
|
|
164
|
+
return { x: 0, y: 0, width, height };
|
|
165
|
+
}
|
|
166
|
+
return { x: left, y: top, width: right - left + 1, height: bottom - top + 1 };
|
|
167
|
+
}
|
|
168
|
+
|
|
75
169
|
function compose(photo, frame) {
|
|
76
170
|
const canvas = document.createElement('canvas');
|
|
77
171
|
canvas.width = SIZE;
|
|
@@ -119,13 +119,13 @@ export function mediaFilesOfKind(kind) {
|
|
|
119
119
|
}
|
|
120
120
|
// Which editor a file opens in when no `editorModule` was resolved. Media
|
|
121
121
|
// viewing (image / audio / video) lives on castle.base now; this only covers
|
|
122
|
-
// types this kit still routes itself (scene / sprite /
|
|
122
|
+
// types this kit still routes itself (scene / sprite / theme). Each extension
|
|
123
123
|
// gets its own line: legacy tooling matched one `endsWith` per `return`.
|
|
124
124
|
export function getFileKind(path) {
|
|
125
125
|
if (path.endsWith('.scene')) return 'scene';
|
|
126
126
|
if (path.endsWith('.sprite')) return 'pxart';
|
|
127
127
|
if (path.endsWith('.pxart')) return 'pxart';
|
|
128
|
-
if (path.endsWith('.style')) return '
|
|
128
|
+
if (path.endsWith('.style')) return 'theme';
|
|
129
129
|
if (path.endsWith('.js') || path.endsWith('.jsx')) return 'code';
|
|
130
130
|
return 'text';
|
|
131
131
|
}
|
|
@@ -147,7 +147,7 @@ export function patchBody(body, actor) {
|
|
|
147
147
|
// used the retired `kind: 'pickup'` label are still honored as sensors.
|
|
148
148
|
body.isSensor = Boolean(collider.isTrigger || collider.kind === 'pickup');
|
|
149
149
|
body.frictionAir = rb?.drag ?? 0.01;
|
|
150
|
-
body.plugin.gravityScale = rb?.gravityScale ??
|
|
150
|
+
body.plugin.gravityScale = rb?.gravityScale ?? 0;
|
|
151
151
|
body.plugin.angularDrag = rb?.angularDrag ?? 0;
|
|
152
152
|
// Density sets mass (= density x area) -- DYNAMIC bodies only. On a static or
|
|
153
153
|
// kinematic body, setDensity would replace the infinite mass that setStatic
|
|
@@ -172,7 +172,7 @@ export function syncBodyToPoint(body, point, angleDeg) {
|
|
|
172
172
|
export function applyWorldGravity(bodies, gravity) {
|
|
173
173
|
for (const body of bodies) {
|
|
174
174
|
if (body.isStatic || body.isSleeping) continue;
|
|
175
|
-
const scale = (body.plugin.gravityScale ??
|
|
175
|
+
const scale = (body.plugin.gravityScale ?? 0) * gravity.scale;
|
|
176
176
|
body.force.x += body.mass * gravity.x * scale;
|
|
177
177
|
body.force.y += body.mass * gravity.y * scale;
|
|
178
178
|
}
|
|
@@ -66,7 +66,7 @@ export class SceneRuntime {
|
|
|
66
66
|
this.camera = undefined;
|
|
67
67
|
this.status = undefined;
|
|
68
68
|
// The deck's font as a CSS font-family value, always usable: the face
|
|
69
|
-
// picked in the
|
|
69
|
+
// picked in the Theme editor, or a plain sans stack when there is none.
|
|
70
70
|
// For `draw` / `drawUi` hooks -- `ctx.font = `20px ${scene.font}`` -- since
|
|
71
71
|
// canvas takes a family name, not a class. Prefer `drawUi` for HUD text;
|
|
72
72
|
// the deprecated React `ui` overlay still inherits this face via CSS.
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
// Generated by the
|
|
1
|
+
// Generated by the Theme editor. Do not edit -- picking a font rewrites it.
|
|
2
2
|
//
|
|
3
3
|
// `theme.style` records that this deck's font is DMSans; this module is what
|
|
4
4
|
// puts DMSans's bytes in the published bundle. It has to name the face in a
|
|
@@ -6,6 +6,7 @@
|
|
|
6
6
|
import React from 'react';
|
|
7
7
|
import { SelectField } from '@imports/castle.physics-2d/engine/ui';
|
|
8
8
|
import { AutoFields, overrideProps } from '@imports/castle.physics-2d/engine/autoInspector';
|
|
9
|
+
import { POSITIVE } from '@imports/castle.physics-2d/engine/propertyRanges';
|
|
9
10
|
|
|
10
11
|
const TYPE_OPTIONS = [
|
|
11
12
|
{ value: 'fixed', label: 'Fixed' },
|
|
@@ -25,6 +26,7 @@ function BodyInspector({ component, setComponent, override }) {
|
|
|
25
26
|
/>
|
|
26
27
|
<AutoFields
|
|
27
28
|
defaultProps={Body.defaultProps}
|
|
29
|
+
meta={Body.propertyMeta}
|
|
28
30
|
component={component}
|
|
29
31
|
setComponent={setComponent}
|
|
30
32
|
exclude={['type']}
|
|
@@ -41,10 +43,17 @@ export class Body {
|
|
|
41
43
|
type: 'fixed', // 'fixed' | 'dynamic' | 'kinematic'
|
|
42
44
|
friction: 0.6,
|
|
43
45
|
restitution: 0,
|
|
44
|
-
|
|
46
|
+
// Match physics-2d RigidBody: newly added dynamics float until opted in.
|
|
47
|
+
gravityScale: 0,
|
|
45
48
|
lockRotations: false,
|
|
46
49
|
};
|
|
47
50
|
|
|
51
|
+
static propertyMeta = {
|
|
52
|
+
friction: POSITIVE,
|
|
53
|
+
restitution: POSITIVE,
|
|
54
|
+
gravityScale: { step: 0.1 },
|
|
55
|
+
};
|
|
56
|
+
|
|
48
57
|
constructor(props) {
|
|
49
58
|
this.props = props;
|
|
50
59
|
}
|
|
@@ -4,6 +4,11 @@
|
|
|
4
4
|
export class Door {
|
|
5
5
|
static behaviorName = 'Door';
|
|
6
6
|
static defaultProps = { range: 3, slide: 2.1, speed: 2.5 };
|
|
7
|
+
static propertyMeta = {
|
|
8
|
+
range: { min: 0, step: 0.1 },
|
|
9
|
+
slide: { min: 0, step: 0.1 },
|
|
10
|
+
speed: { min: 0, step: 0.1 },
|
|
11
|
+
};
|
|
7
12
|
|
|
8
13
|
constructor(props) {
|
|
9
14
|
this.props = props;
|
|
@@ -7,6 +7,10 @@
|
|
|
7
7
|
export class Model {
|
|
8
8
|
static behaviorName = 'Model';
|
|
9
9
|
static defaultProps = { file: '', scale: 1, yOffset: 0 };
|
|
10
|
+
static propertyMeta = {
|
|
11
|
+
scale: { min: 0, step: 0.05 },
|
|
12
|
+
yOffset: { step: 0.05 },
|
|
13
|
+
};
|
|
10
14
|
|
|
11
15
|
constructor(props) {
|
|
12
16
|
this.props = props;
|
|
@@ -14,6 +14,10 @@ function pickupKey(scene, actor) {
|
|
|
14
14
|
export class Pickup {
|
|
15
15
|
static behaviorName = 'Pickup';
|
|
16
16
|
static defaultProps = { bob: 0.12, spin: 120 };
|
|
17
|
+
static propertyMeta = {
|
|
18
|
+
bob: { min: 0, step: 0.01 },
|
|
19
|
+
spin: { min: 0, step: 5 },
|
|
20
|
+
};
|
|
17
21
|
|
|
18
22
|
constructor(props) {
|
|
19
23
|
this.props = props;
|
|
@@ -112,6 +112,13 @@ function findLookText(scene, actor, t, facing) {
|
|
|
112
112
|
export class Player {
|
|
113
113
|
static behaviorName = 'Player';
|
|
114
114
|
static defaultProps = { speed: 6, turnSpeed: 2.4, jumpSpeed: 8, camDistance: 9, camHeight: 7 };
|
|
115
|
+
static propertyMeta = {
|
|
116
|
+
speed: { min: 0, step: 0.5 },
|
|
117
|
+
turnSpeed: { min: 0, step: 0.1 },
|
|
118
|
+
jumpSpeed: { min: 0, step: 0.5 },
|
|
119
|
+
camDistance: { min: 0, step: 0.5 },
|
|
120
|
+
camHeight: { min: 0, step: 0.5 },
|
|
121
|
+
};
|
|
115
122
|
|
|
116
123
|
constructor(props) {
|
|
117
124
|
this.props = props;
|
|
@@ -8,6 +8,7 @@
|
|
|
8
8
|
import React from 'react';
|
|
9
9
|
import { SelectField, TextField } from '@imports/castle.physics-2d/engine/ui';
|
|
10
10
|
import { AutoFields, overrideProps } from '@imports/castle.physics-2d/engine/autoInspector';
|
|
11
|
+
import { POSITIVE, UNIT } from '@imports/castle.physics-2d/engine/propertyRanges';
|
|
11
12
|
|
|
12
13
|
const KIND_OPTIONS = [
|
|
13
14
|
{ value: 'box', label: 'Box' },
|
|
@@ -39,6 +40,7 @@ function ShapeInspector({ component, setComponent, override }) {
|
|
|
39
40
|
)}
|
|
40
41
|
<AutoFields
|
|
41
42
|
defaultProps={Shape.defaultProps}
|
|
43
|
+
meta={Shape.propertyMeta}
|
|
42
44
|
component={component}
|
|
43
45
|
setComponent={setComponent}
|
|
44
46
|
exclude={['kind', 'art', 'artRepeat']}
|
|
@@ -69,6 +71,17 @@ export class Shape {
|
|
|
69
71
|
solid: false,
|
|
70
72
|
};
|
|
71
73
|
|
|
74
|
+
static propertyMeta = {
|
|
75
|
+
width: POSITIVE,
|
|
76
|
+
height: POSITIVE,
|
|
77
|
+
depth: POSITIVE,
|
|
78
|
+
radius: POSITIVE,
|
|
79
|
+
roughness: UNIT,
|
|
80
|
+
metalness: UNIT,
|
|
81
|
+
clearcoat: UNIT,
|
|
82
|
+
emissiveIntensity: POSITIVE,
|
|
83
|
+
};
|
|
84
|
+
|
|
72
85
|
constructor(props) {
|
|
73
86
|
this.props = props;
|
|
74
87
|
}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
// Generated by the
|
|
1
|
+
// Generated by the Theme editor. Do not edit -- picking a font rewrites it.
|
|
2
2
|
//
|
|
3
3
|
// `theme.style` records that this deck's font is DMSans; this module is what
|
|
4
4
|
// puts DMSans's bytes in the published bundle. It has to name the face in a
|
|
@@ -91,7 +91,16 @@ export async function connectSession({ onLog = () => {} } = {}) {
|
|
|
91
91
|
sendMove(net, pose, owned, claims, releases, state),
|
|
92
92
|
|
|
93
93
|
// Send deck-specific data. The server routes these to `code/server/game.js`.
|
|
94
|
-
send: (data) =>
|
|
94
|
+
send: (data) => {
|
|
95
|
+
if (connection.state !== 'closed') {
|
|
96
|
+
net.connection.send(data);
|
|
97
|
+
}
|
|
98
|
+
},
|
|
99
|
+
|
|
100
|
+
// Whether the session has ended under this connection: the server was
|
|
101
|
+
// replaced, the platform closed it, or `close` was called. Nothing is sent
|
|
102
|
+
// on it again; the client system joins afresh.
|
|
103
|
+
closed: () => connection.state === 'closed',
|
|
95
104
|
|
|
96
105
|
// Connection and presence data for a deck status line.
|
|
97
106
|
status: () => ({
|
|
@@ -127,7 +136,7 @@ function sendMove(net, pose, owned, claims, releases, state) {
|
|
|
127
136
|
net.releases.add(id);
|
|
128
137
|
}
|
|
129
138
|
const now = performance.now();
|
|
130
|
-
if (now - net.sentAt < SEND_MS) {
|
|
139
|
+
if (now - net.sentAt < SEND_MS || net.connection.state === 'closed') {
|
|
131
140
|
return false;
|
|
132
141
|
}
|
|
133
142
|
net.sentAt = now;
|
|
@@ -28,7 +28,7 @@
|
|
|
28
28
|
"autoUpdateWhenImported": true,
|
|
29
29
|
"deckId": "K6-Xs7dCoMVi",
|
|
30
30
|
"cardId": "Kh_08IYATpC3",
|
|
31
|
-
"publishedVersion": "2026-09-
|
|
31
|
+
"publishedVersion": "2026-09-10T22:05:30.856Z",
|
|
32
32
|
"server": {
|
|
33
33
|
"main": "code/server/index.js",
|
|
34
34
|
"maxPlayers": 8,
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
// Generated by the
|
|
1
|
+
// Generated by the Theme editor. Do not edit -- picking a font rewrites it.
|
|
2
2
|
//
|
|
3
3
|
// `theme.style` records that this deck's font is DMSans; this module is what
|
|
4
4
|
// puts DMSans's bytes in the published bundle. It has to name the face in a
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "castle-web-cli",
|
|
3
|
-
"version": "0.4.
|
|
3
|
+
"version": "0.4.174",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"bin": {
|
|
6
6
|
"castle-web": "./dist/index.js"
|
|
@@ -41,6 +41,7 @@
|
|
|
41
41
|
"@xterm/headless": "^6.0.0",
|
|
42
42
|
"@xterm/xterm": "^6.0.0",
|
|
43
43
|
"codemirror": "^6.0.2",
|
|
44
|
+
"compression": "^1.8.1",
|
|
44
45
|
"html2canvas": "^1.4.1",
|
|
45
46
|
"marked": "^18.0.5",
|
|
46
47
|
"nanoid": "^5.1.7",
|
|
@@ -54,6 +55,7 @@
|
|
|
54
55
|
"ws": "^8.20.0"
|
|
55
56
|
},
|
|
56
57
|
"devDependencies": {
|
|
58
|
+
"@types/compression": "^1.8.1",
|
|
57
59
|
"@types/node": "^20.0.0",
|
|
58
60
|
"@types/picomatch": "^4.0.2",
|
|
59
61
|
"@types/react": "^18.3.31",
|
|
File without changes
|