castle-web-cli 0.4.171 → 0.4.173
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/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/devSessionServer.js +9 -5
- package/dist/imports.js +2 -2
- package/dist/init.js +4 -4
- package/dist/serve.js +2 -0
- package/dist/serveSecurity.d.ts +2 -0
- package/dist/serveSecurity.js +17 -3
- package/kits/multiplayer-2d/CLAUDE.md +4 -0
- package/kits/multiplayer-2d/behaviors/Box.jsx +32 -9
- package/kits/multiplayer-2d/castle.json +3 -3
- package/kits/multiplayer-2d/code/client/avatars.js +6 -1
- 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 +14 -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 -0
- package/kits/multiplayer-3d/castle.json +3 -3
- package/kits/multiplayer-3d/code/client/avatars.js +42 -9
- package/kits/multiplayer-3d/code/server/sceneBodies.js +1 -1
- package/kits/multiplayer-3d/code/systems/multiplayer.js +17 -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 +185 -0
- 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 +17 -2
- package/kits/real-time/code/server/session.js +12 -6
- package/kits/real-time/package-lock.json +1 -1
- package/kits/turn-based/CLAUDE.md +4 -0
- package/kits/turn-based/castle.json +2 -2
- package/kits/turn-based/code/behaviors/Board.jsx +15 -5
- package/kits/turn-based/code/server/index.js +1 -0
- package/kits/turn-based/fonts.generated.js +1 -1
- package/kits/turn-based/package-lock.json +1 -1
- package/kits/turn-based/room.js +2 -0
- package/package.json +3 -1
- /package/kits/physics-2d/editors/{styleTheme.js → theme.js} +0 -0
|
@@ -12,6 +12,7 @@ const SEND_MS = 1000 / Messages.MOVE_HZ;
|
|
|
12
12
|
// connection the SDK connection
|
|
13
13
|
// inbox arrived messages, each with `localMs` added
|
|
14
14
|
// roster playerId -> username, as the platform last reported it
|
|
15
|
+
// userIds playerId -> userId, from the same roster
|
|
15
16
|
// you the SDK's entry for the local player, null until a roster has it
|
|
16
17
|
// sentAt `performance.now()` of the last move, so sends stay at MOVE_HZ
|
|
17
18
|
// claims ids asked for since the last send, held until it goes
|
|
@@ -21,6 +22,7 @@ function makeNet(connection) {
|
|
|
21
22
|
connection,
|
|
22
23
|
inbox: [],
|
|
23
24
|
roster: new Map(),
|
|
25
|
+
userIds: new Map(),
|
|
24
26
|
you: null,
|
|
25
27
|
sentAt: 0,
|
|
26
28
|
claims: new Set(),
|
|
@@ -48,6 +50,7 @@ export async function connectSession({ onLog = () => {} } = {}) {
|
|
|
48
50
|
|
|
49
51
|
// Replace the map from the complete SDK roster so departures disappear.
|
|
50
52
|
net.roster = new Map(players.map((player) => [player.playerId, player.username]));
|
|
53
|
+
net.userIds = new Map(players.map((player) => [player.playerId, player.userId]));
|
|
51
54
|
net.you = you;
|
|
52
55
|
});
|
|
53
56
|
|
|
@@ -61,6 +64,9 @@ export async function connectSession({ onLog = () => {} } = {}) {
|
|
|
61
64
|
// A stable fallback until the SDK roster includes this player.
|
|
62
65
|
username: (playerId) => net.roster.get(playerId) ?? 'player',
|
|
63
66
|
|
|
67
|
+
// The account behind a player, for `User.get`; null until the roster has it.
|
|
68
|
+
userId: (playerId) => net.userIds.get(playerId) ?? null,
|
|
69
|
+
|
|
64
70
|
// Every other player's roster entry. The local player is simulated by this client,
|
|
65
71
|
// so the deck draws only these from network samples.
|
|
66
72
|
others: () => new Map([...net.roster].filter(([id]) => id !== connection.playerId)),
|
|
@@ -85,7 +91,16 @@ export async function connectSession({ onLog = () => {} } = {}) {
|
|
|
85
91
|
sendMove(net, pose, owned, claims, releases, state),
|
|
86
92
|
|
|
87
93
|
// Send deck-specific data. The server routes these to `code/server/game.js`.
|
|
88
|
-
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',
|
|
89
104
|
|
|
90
105
|
// Connection and presence data for a deck status line.
|
|
91
106
|
status: () => ({
|
|
@@ -121,7 +136,7 @@ function sendMove(net, pose, owned, claims, releases, state) {
|
|
|
121
136
|
net.releases.add(id);
|
|
122
137
|
}
|
|
123
138
|
const now = performance.now();
|
|
124
|
-
if (now - net.sentAt < SEND_MS) {
|
|
139
|
+
if (now - net.sentAt < SEND_MS || net.connection.state === 'closed') {
|
|
125
140
|
return false;
|
|
126
141
|
}
|
|
127
142
|
net.sentAt = now;
|
|
@@ -80,12 +80,18 @@ function newServer(parts) {
|
|
|
80
80
|
export function makeSession(parts) {
|
|
81
81
|
const server = newServer(parts);
|
|
82
82
|
|
|
83
|
-
// Castle's handlers remain available while `createSimulation` is pending.
|
|
83
|
+
// Castle's handlers remain available while `createSimulation` is pending. A
|
|
84
|
+
// build that fails is logged here; `buildWorld` sees the same rejection when
|
|
85
|
+
// the first tick awaits it.
|
|
84
86
|
server.building = server.world.createSimulation();
|
|
87
|
+
server.building.catch((err) => {
|
|
88
|
+
console.error(`[${server.label}] simulation failed to build: ${err?.stack ?? err}`);
|
|
89
|
+
});
|
|
85
90
|
return {
|
|
86
|
-
onStart
|
|
87
|
-
|
|
88
|
-
|
|
91
|
+
// The world is built from the first tick or join, not here: `onStart` runs
|
|
92
|
+
// while the process is still booting, and a storage read made then can go
|
|
93
|
+
// unanswered.
|
|
94
|
+
onStart() {},
|
|
89
95
|
|
|
90
96
|
// Joins are logged here; the simulation's players are reconciled from the
|
|
91
97
|
// full session roster on the next tick.
|
|
@@ -124,8 +130,8 @@ export function makeSession(parts) {
|
|
|
124
130
|
};
|
|
125
131
|
}
|
|
126
132
|
|
|
127
|
-
// Keep the first session a
|
|
128
|
-
// room the session persists under is not known before this.
|
|
133
|
+
// Keep the first session a tick or join carries and build the world with it.
|
|
134
|
+
// The room the session persists under is not known before this.
|
|
129
135
|
function attach(server, session) {
|
|
130
136
|
if (server.session) {
|
|
131
137
|
return;
|
|
@@ -41,6 +41,10 @@ This kit adds no engine and no editors. What it holds:
|
|
|
41
41
|
`{ table }`. One table per `session.sessionId`, made on demand. `joinAsync` is
|
|
42
42
|
the join to use: it reads the saved table first, which the synchronous
|
|
43
43
|
`room.join` cannot.
|
|
44
|
+
- **A claimed square shows its holder's avatar.** The state carries `users`,
|
|
45
|
+
seat -> userId, and `Board.jsx` swaps the square's `Sprite.file` for the
|
|
46
|
+
avatar from `castle.physics-2d`'s `engine/avatarArt.js`; a holder with no
|
|
47
|
+
photo keeps the seat tint.
|
|
44
48
|
- **A room comes back.** `table.game`, the seat each account held, and the time
|
|
45
49
|
of the last accepted move are saved to deck storage on every accepted move, on
|
|
46
50
|
every leave, and from `room.save(session)` in `onShutdown`. Storage belongs to
|
|
@@ -17,7 +17,7 @@
|
|
|
17
17
|
"imports": {
|
|
18
18
|
"castle.physics-2d": {
|
|
19
19
|
"deckId": "ckRZGFW4iPrx",
|
|
20
|
-
"version": "2026-09-
|
|
20
|
+
"version": "2026-09-10T20:21:12.665Z"
|
|
21
21
|
},
|
|
22
22
|
"castle.base": {
|
|
23
23
|
"deckId": "yRcmH4_aYllE",
|
|
@@ -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,
|
|
@@ -3,8 +3,10 @@
|
|
|
3
3
|
// multiplayer session, send square claims, tint actors and format status text.
|
|
4
4
|
|
|
5
5
|
import { Multiplayer } from 'castle-web-sdk';
|
|
6
|
+
import { avatarSpritePath } from '@imports/castle.physics-2d/engine/avatarArt';
|
|
6
7
|
|
|
7
|
-
//
|
|
8
|
+
// A claimed square shows its holder's avatar. Until that has loaded, or when the
|
|
9
|
+
// holder has none, the seat's tint multiplies the square's own artwork instead.
|
|
8
10
|
const SEAT_TINT = { A: '#ea323cff', B: '#0cf1ffff' };
|
|
9
11
|
const UNCLAIMED_TINT = '#ffffffff';
|
|
10
12
|
|
|
@@ -130,10 +132,18 @@ function paintSquares(scene, state) {
|
|
|
130
132
|
for (const squareActor of scene.actorsWith('Square')) {
|
|
131
133
|
// Square.index selects the same slot in the server's sixteen-element array.
|
|
132
134
|
const seat = state.squares[squareActor.components.Square.index];
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
//
|
|
136
|
-
|
|
135
|
+
const sprite = squareActor.components.Sprite;
|
|
136
|
+
|
|
137
|
+
// The square's own art is remembered the first time, since `file` is
|
|
138
|
+
// swapped for an avatar below and back again when the square is cleared.
|
|
139
|
+
squareActor.runtime.squareFile ??= sprite.file;
|
|
140
|
+
const avatar = seat ? avatarSpritePath(scene, state.users?.[seat]) : null;
|
|
141
|
+
sprite.file = avatar ?? squareActor.runtime.squareFile;
|
|
142
|
+
|
|
143
|
+
// Sprite tint multiplies the pixels. White preserves their colors; a seat
|
|
144
|
+
// tint displays the owner of a square without an avatar. A game with
|
|
145
|
+
// separate pieces can spawn piece actors over these square actors.
|
|
146
|
+
sprite.tint = seat && !avatar ? SEAT_TINT[seat] : UNCLAIMED_TINT;
|
|
137
147
|
}
|
|
138
148
|
}
|
|
139
149
|
|
|
@@ -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/kits/turn-based/room.js
CHANGED
|
@@ -178,6 +178,8 @@ function createTable(settings, session) {
|
|
|
178
178
|
playerAt: (seat) => table.seats[seat] ?? null,
|
|
179
179
|
seatNames: () =>
|
|
180
180
|
Object.fromEntries(settings.seats.map((seat) => [seat, table.seats[seat]?.name ?? null])),
|
|
181
|
+
seatUsers: () =>
|
|
182
|
+
Object.fromEntries(settings.seats.map((seat) => [seat, table.seats[seat]?.userId ?? null])),
|
|
181
183
|
spectatorCount: (session) => spectatorCount(table, session),
|
|
182
184
|
|
|
183
185
|
// Call this past every rejection, never before one. A refused message is not
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "castle-web-cli",
|
|
3
|
-
"version": "0.4.
|
|
3
|
+
"version": "0.4.173",
|
|
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
|