@uzuhq/code-sdk 0.8.3 → 0.8.5

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 CHANGED
@@ -25,24 +25,6 @@ SDK チャネルのメッセージは `playSound()`, `setMicEnabled()` などの
25
25
 
26
26
  ---
27
27
 
28
- ## 3 つのパラダイム
29
-
30
- SDK は用途に応じて 3 つの API パターンを提供する。
31
-
32
- | パラダイム | API | state 管理 | 向いているゲーム |
33
- | ---------- | --------------------- | ---------------------------------- | ---------------------------- |
34
- | **Relay** | `init()` + `onRoom()` | ゲーム側の責務 | 独自プロトコルが必要なゲーム |
35
- | **sync()** | `sync()` | SDK + サーバーが JSON Patch で同期 | ターン制・ボードゲーム |
36
- | **run()** | `run()` | SDK + reducer がサーバーで逐次実行 | リアルタイム・アクション全般 |
37
-
38
- ### 選択基準
39
-
40
- - 同時操作で同じリソースを奪い合う → **`run()`**(reducer が最新 state に対して逐次実行、条件保証あり)
41
- - 各プレイヤーが自分の領域だけ変更 → **`sync()`** で十分
42
- - 独自のメッセージプロトコルが必要 → **Relay**
43
-
44
- ---
45
-
46
28
  ## 初期化
47
29
 
48
30
  ### `init(): void`
@@ -54,132 +36,19 @@ import { init } from '@uzuhq/code-sdk';
54
36
  init();
55
37
  ```
56
38
 
57
- - `run()` / `sync()` を使う場合は内部で `init()` が呼ばれるため不要
58
- - Relay パターン (`onRoom()`) のみ、最初に呼び出す
59
-
60
- ---
61
-
62
- ## パターン 1: Relay
63
-
64
- ### `onRoom(callback: (room: RoomLike) => void): void`
65
-
66
- Room に接続されたときのコールバックを登録する。
67
-
68
- ```ts
69
- import { init, onRoom } from '@uzuhq/code-sdk';
70
-
71
- init();
72
-
73
- onRoom((room) => {
74
- console.log('My ID:', room.myId);
75
-
76
- room.on('attack', (msg) => {
77
- console.log(`${msg.__from} sent ${msg.lines} lines`);
78
- });
79
-
80
- room.broadcast({ type: 'attack', lines: 2 });
81
- room.send(targetId, { type: 'whisper', text: 'hello' });
82
- });
83
- ```
84
-
85
- ### Room API
86
-
87
- | メソッド/プロパティ | 型 | 説明 |
88
- | ------------------------ | ------------------------------------------------------ | -------------------------------------------------------- |
89
- | `room.myId` | `string` (readonly) | 自分の playerId |
90
- | `room.broadcast(msg)` | `(msg: Record<string, unknown>) => void` | 自分以外の全員に送信 |
91
- | `room.send(id, msg)` | `(id: string, msg: Record<string, unknown>) => void` | 特定プレイヤーに送信 |
92
- | `room.on(type, handler)` | `(type: string, handler: (data: any) => void) => void` | メッセージハンドラ登録。受信データに `__from` が含まれる |
39
+ - `run()` が内部で呼ぶので、通常は直接書かなくてよい
40
+ - マルチプレイの state 同期を使わない 1 人用シナリオ (サウンド・ボイス・safe area だけ使う)
41
+ では、これだけを最初に呼ぶ
93
42
 
94
43
  ---
95
44
 
96
- ## パターン 2: sync()
97
-
98
- ### `sync<S>(config: SyncConfig<S>): void`
99
-
100
- JSON Patch ベースの状態同期を開始する。
101
-
102
- ```ts
103
- import { sync, SERVER_TIME } from '@uzuhq/code-sdk';
104
- import type { Seat } from '@uzuhq/code-sdk';
105
-
106
- sync<GameState>({
107
- playerCount: 2,
108
-
109
- initialState(players: Seat[]) {
110
- return { board: createBoard(8), currentPlayer: players[0].id };
111
- },
112
-
113
- onState(state, myPlayerId, serverTime) {
114
- currentState = state;
115
- render();
116
- },
117
-
118
- inputs(patch, set) {
119
- canvas.addEventListener('click', (e) => {
120
- const { row, col } = getCellFromClick(e);
121
- // 複数操作をまとめて送信
122
- patch([
123
- { op: 'replace', path: `/board/${row}/${col}`, value: myPlayerId },
124
- { op: 'replace', path: '/lastMoveAt', value: SERVER_TIME },
125
- ]);
126
- // 単一値のショートカット
127
- set('/currentPlayer', getNextPlayer());
128
- });
129
- },
130
-
131
- connection: {
132
- onConnectionStateChange(state) {
133
- console.log('Connection:', state);
134
- },
135
- onPatchFailed(error) {
136
- console.error('Patch failed:', error);
137
- },
138
- },
139
- });
140
- ```
141
-
142
- ### SyncConfig
143
-
144
- | キー | 型 | 必須 | 説明 |
145
- | -------------- | ------------------------------------------------------------ | ---- | ---------------------------- |
146
- | `playerCount` | `number` | Yes | プレイヤー数 |
147
- | `initialState` | `(players: Seat[]) => S` | Yes | 初期 state を生成 |
148
- | `onState` | `(state: S, myPlayerId: string, serverTime: number) => void` | Yes | state 更新時のコールバック |
149
- | `inputs` | `(patch: PatchFn, set: SetFn) => void` | Yes | 入力ハンドラ登録 |
150
- | `events` | `Record<string, (data: Record<string, unknown>) => void>` | No | ゲームイベントハンドラ |
151
- | `connection` | `ConnectionCallbacks` | No | 接続状態・エラーコールバック |
152
-
153
- ### PatchFn / SetFn
154
-
155
- ```ts
156
- type PatchFn = (ops: Operation[]) => void; // 複数操作をまとめて送信
157
- type SetFn = (path: string, value: unknown) => void; // 単一パスの replace ショートカット
158
- ```
159
-
160
- ### Operation (JSON Patch)
161
-
162
- ```ts
163
- interface Operation {
164
- op: 'replace' | 'add' | 'remove';
165
- path: string; // JSON Pointer パス (例: '/players/alice/score')
166
- value?: unknown; // 値。SERVER_TIME sentinel 使用可
167
- }
168
- ```
169
-
170
- ### 楽観的更新
171
-
172
- `sync()` は送信した patch をローカルに即座に適用する。サーバーからの権威的な state を受信すると上書きする。
173
-
174
- **注意**: `sync()` は patch の条件を検証しない。同時に同じパスを変更すると後勝ちになる。条件付きの状態遷移が必要なら `run()` を使う。
175
-
176
- ---
177
-
178
- ## パターン 3: run()
45
+ ## ゲームループ
179
46
 
180
47
  ### `run<S>(config: GameConfig<S>): void`
181
48
 
182
- Host-authoritative ゲームループを実行する。サーバー (GameRoom DO) reducer を逐次実行するため、条件保証あり。
49
+ ゲームループを開始する。state はサーバー (GameRoom DO) が持ち、送られた action
50
+ reducer で 1 件ずつ検証・実行して結果を全員へ配る。reducer は常にその時点の最新 state に
51
+ 対して走るので、同時操作で条件が壊れることがない。
183
52
 
184
53
  ```ts
185
54
  import { run } from '@uzuhq/code-sdk';
@@ -206,15 +75,14 @@ run({
206
75
 
207
76
  ### GameConfig
208
77
 
209
- | キー | 型 | 必須 | 説明 |
210
- | ------------------------- | -------------------------------------------------------------- | ---- | ----------------------------------- |
211
- | `logic` | `GameLogic<S>` | Yes | ゲームロジック定義 |
212
- | `onState` | `(state: S, myPlayerId: string) => void` | Yes | state 更新時のコールバック |
213
- | `inputs` | `(sendAction: (action: string, payload: any) => void) => void` | Yes | 入力ハンドラ登録 |
214
- | `events` | `Record<string, (data: any) => void>` | No | ゲームイベントハンドラ |
215
- | `playerCount` | `number` | Yes | プレイヤー数 |
216
- | `onConnectionStateChange` | `(state: ConnectionState) => void` | No | 接続状態変化コールバック |
217
- | `onPatchFailed` | `(reason: string) => void` | No | サーバー patch 適用失敗コールバック |
78
+ | キー | 型 | 必須 | 説明 |
79
+ | ------------------------- | -------------------------------------------------------------- | ---- | -------------------------- |
80
+ | `logic` | `GameLogic<S>` | Yes | ゲームロジック定義 |
81
+ | `onState` | `(state: S, myPlayerId: string) => void` | Yes | state 更新時のコールバック |
82
+ | `inputs` | `(sendAction: (action: string, payload: any) => void) => void` | Yes | 入力ハンドラ登録 |
83
+ | `events` | `Record<string, (data: any) => void>` | No | ゲームイベントハンドラ |
84
+ | `playerCount` | `number` | Yes | プレイヤー数 |
85
+ | `onConnectionStateChange` | `(state: ConnectionState) => void` | No | 接続状態変化コールバック |
218
86
 
219
87
  ### GameLogic
220
88
 
@@ -479,17 +347,6 @@ on('attack', (payload) => {
479
347
 
480
348
  ---
481
349
 
482
- ## SERVER_TIME
483
-
484
- サーバー時刻 sentinel 定数。Patch の `value` に指定すると、サーバー側で `Date.now()` に自動置換される。
485
-
486
- ```ts
487
- import { SERVER_TIME } from '@uzuhq/code-sdk';
488
- set('/meta/phaseStartedAt', SERVER_TIME);
489
- ```
490
-
491
- ---
492
-
493
350
  ## 型定義
494
351
 
495
352
  ### BridgeMessage
@@ -554,7 +411,6 @@ type ConnectionState = 'connecting' | 'connected' | 'reconnecting' | 'disconnect
554
411
  ```ts
555
412
  interface ConnectionCallbacks {
556
413
  onConnectionStateChange?: (state: ConnectionState) => void;
557
- onPatchFailed?: (error: string) => void;
558
414
  }
559
415
  ```
560
416
 
@@ -578,7 +434,7 @@ interface PlayerVoiceState {
578
434
 
579
435
  ## URL パラメータ
580
436
 
581
- `init()` / `run()` / `sync()` は以下の URL パラメータを読み取る。
437
+ `init()` / `run()` は以下の URL パラメータを読み取る。
582
438
 
583
439
  | パラメータ | 説明 |
584
440
  | ----------- | --------------------------------------------- |
@@ -604,7 +460,6 @@ interface PlayerVoiceState {
604
460
 
605
461
  - `run()` の devHarness → **親 frame だけ** (`page.mainFrame()`)
606
462
  - `runLocalServerAction` (ソロモード) 単独 frame → その frame
607
- - sync 系単独 frame → その frame
608
463
  - devHarness の **子 iframe には attach しない**
609
464
 
610
465
  ```ts
@@ -653,7 +508,7 @@ await window.__uzu_dev.waitForSnapshot((s) => s.self.isReady === true);
653
508
  - 部分更新は `mergeRawState`、array 要素単体の書換は `patchRawState` を使う
654
509
  - `mergeRawState` で array field に non-array object patch を当てると **throw** する (array が pure object に化けるのを構造的に防止)
655
510
  - online ServerAction では 3 API すべて `undefined`
656
- - `send` は run devHarness 親 frame のみ。非 parent モード (runLocal / emulator / sync) では scenario 側の `window.__uzu.sendAction` を使う
511
+ - `send` は run devHarness 親 frame のみ。非 parent モード (runLocal / emulator) では scenario 側の `window.__uzu.sendAction` を使う
657
512
  - `await d.send(...)` / `await d.*RawState(...)` は **(a) handler 完了 (b) parent state broadcast 投函** までを待つ。**子 iframe canvas の paint 完了は含まない** ので、screenshot / visual e2e test では `await new Promise(r => requestAnimationFrame(r))` を別途挟むこと
658
513
  - phase 遷移時の field reset や `markReady` 等の **state shape を仮定する helper は SDK には含めない**。scenario 側で `window.__<scene>_dev` を生やして上記 primitive を組み合わせる
659
514
 
@@ -1,4 +1,4 @@
1
- import { i as UzuDevHooks } from "./dev-hooks-DOtJHP55.js";
1
+ import { r as UzuDevHooks } from "./dev-hooks-DmOV2p0j.js";
2
2
  //#region src/dev-globals.d.ts
3
3
  declare global {
4
4
  interface Window {
@@ -6,7 +6,7 @@ declare global {
6
6
  * dev harness / Playwright / 単独 page で attach される dev hooks。
7
7
  * 本番 (Flutter native ホスト) では undefined。
8
8
  * - run() devHarness: 親 frame に attach (authoritative state)
9
- * - sync() / online: 子 frame で undefined
9
+ * - online: 子 frame で undefined
10
10
  */
11
11
  __uzu_dev?: UzuDevHooks<unknown>;
12
12
  }
@@ -352,25 +352,9 @@ interface GameLogic<S, A extends ActionMap<S> = ActionMap<S>, SA extends ServerA
352
352
  deadlines?: Record<string, Deadline<S>>;
353
353
  tickRate?: number;
354
354
  }
355
- /** Sentinel value — patch の value にセットすると、サーバーが Date.now() に置換する */
356
- declare const SERVER_TIME: "__SERVER_TIME__";
357
355
  /** デフォルトのプレイヤーアイコン URL 一覧(dev / local モード用) */
358
356
  declare const DEFAULT_ICON_URLS: readonly ["https://imagedelivery.net/htp-D7B2hJT5XtdWYN9e7Q/4d0da24d-bcf2-4f7b-d1a0-f1bb8c747300/original", "https://imagedelivery.net/htp-D7B2hJT5XtdWYN9e7Q/8c75fccb-41d6-429d-e943-06c728a72a00/original", "https://imagedelivery.net/htp-D7B2hJT5XtdWYN9e7Q/43f45d11-da38-4d6e-637d-3df78e583500/original"];
359
357
  //#endregion
360
- //#region ../engine-core/src/json-patch.d.ts
361
- /**
362
- * @docs
363
- * - SyncRoom仕様: docs/docs/uzu_code/sync-room.md
364
- *
365
- * state 差分同期用の JSON Patch。RFC 6902 の全 op は実装せず、
366
- * `compare` が生成する replace / add / remove だけを扱う。
367
- */
368
- interface Operation {
369
- op: 'replace' | 'add' | 'remove';
370
- path: string;
371
- value?: unknown;
372
- }
373
- //#endregion
374
358
  //#region src/types.d.ts
375
359
  /** @deprecated 旧フラット形式。新コードでは BridgeMessage を使用 */
376
360
  type PlayScreenMessage = {
@@ -474,29 +458,10 @@ interface GameConfig<S, A extends ActionMap<S> = ActionMap<S>, SA extends Server
474
458
  */
475
459
  devMinIframeShortEdge?: number;
476
460
  }
477
- type PatchFn = (ops: Operation[]) => void;
478
- type SetFn = (path: string, value: unknown) => void;
479
- interface SyncConfig<S = any> extends ConnectionCallbacks {
480
- initialState: (players: Seat[]) => S;
481
- onState: (state: S, myPlayerId: string, serverTime: number) => void;
482
- inputs: (patch: PatchFn, set: SetFn) => void;
483
- events?: Record<string, (data: Record<string, unknown>) => void>;
484
- playerCount: number;
485
- /** Dev harness のデフォルト向き。manifest.json の `orientation` を渡す。 */
486
- orientation?: 'portrait' | 'landscape';
487
- /**
488
- * Dev harness で各 iframe inner viewport 短辺の下限 (CSS px)。
489
- * default 360。狭い viewport では親 frame に `body { zoom: N }` を当てて
490
- * iframe 内部の `window.innerWidth/Height` を保証する。
491
- */
492
- devMinIframeShortEdge?: number;
493
- }
494
461
  type ConnectionState = 'connecting' | 'connected' | 'reconnecting' | 'disconnected';
495
462
  interface ConnectionCallbacks {
496
463
  /** WebSocket 接続状態が変化した時に呼ばれる */
497
464
  onConnectionStateChange?: (state: ConnectionState) => void;
498
- /** サーバーで patch 適用が失敗した時に呼ばれる (sync モード専用) */
499
- onPatchFailed?: (reason: string) => void;
500
465
  }
501
466
  //#endregion
502
467
  //#region src/host-globals.d.ts
@@ -671,23 +636,9 @@ interface RunHandle<S = unknown> {
671
636
  /** 既存 inputs と同等の action 送信 (dev hooks 用) */
672
637
  sendAction(type: string, payload?: Record<string, unknown>): void;
673
638
  }
674
- /**
675
- * sync/* モジュール (sync/dev / sync/local) が dev hooks に state を expose
676
- * するための handle。online sync では handle 自体を返さない。
677
- */
678
- interface SyncHandle<S = unknown> {
679
- /** 内部 state の getter (dev hooks 用) */
680
- getRawState?(): S | null;
681
- /** 生 state 全置換 */
682
- setRawState?(state: S): Promise<void>;
683
- /** RFC 7396 風 Merge Patch */
684
- mergeRawState?(patch: JsonMergePatch<S>): Promise<void>;
685
- /** RFC 6902 JSON Patch */
686
- patchRawState?(ops: JsonPatchOp[]): Promise<void>;
687
- }
688
639
  /**
689
640
  * dev-hooks.ts から見た「SDK 内部のコンテキスト」。
690
- * 各 run/* / sync/* モジュールがこの形の handle を組み立てて attachDevHooks に渡す。
641
+ * 各 run/* モジュールがこの形の handle を組み立てて attachDevHooks に渡す。
691
642
  */
692
643
  interface DevHooksCtx<S = unknown> {
693
644
  /** 最新 snapshot (per-player view)。未初期化なら null。 */
@@ -738,4 +689,4 @@ interface DevHooksCtx<S = unknown> {
738
689
  declare function createDevHooks<S>(ctx: DevHooksCtx<S>): UzuDevHooks<S>;
739
690
  declare function attachDevHooks<S>(ctx: DevHooksCtx<S>): void;
740
691
  //#endregion
741
- export { GameTime as $, DEFAULT_ICON_URLS as A, ServerActionContext as B, SetFn as C, ActionContext as D, ActionArgs as E, GameLogic as F, ServerOnlyActionContext as G, ServerActionMap as H, SERVER_TIME as I, SetupContext as J, ServerOnlyActionHandlerFn as K, Seat as L, DeadlineArgs as M, DeadlineContext as N, ActionHandler as O, Emit as P, GAME_START as Q, SeededRandom as R, SendAction as S, Operation as T, ServerEvent as U, ServerActionHandler as V, ServerOnlyAction as W, UpdateContext as X, UpdateArgs as Y, Duration as Z, PayloadMap as _, attachDevHooks as a, applyJsonMergePatch as at, PlayersChangedMessage as b, getPredictionWarnings as c, ConnectionCallbacks as d, minus as et, ConnectionState as f, PatchFn as g, GameConfig as h, UzuDevHooks as i, JsonPatchOp as it, Deadline as j, ActionMap as k, BridgeChannel as l, EventSubscription as m, RunHandle as n, sub as nt, createDevHooks as o, applyJsonPatch as ot, EventHandler as p, SetupArgs as q, SyncHandle as r, JsonMergePatch as rt, PredictionWarning as s, DevHooksCtx as t, plus as tt, BridgeMessage as u, PlayScreenMessage as v, SyncConfig as w, SeatKind as x, PlayerVoiceState as y, ServerActionArgs as z };
692
+ export { applyJsonMergePatch as $, GameLogic as A, ServerOnlyActionHandlerFn as B, ActionHandler as C, DeadlineArgs as D, Deadline as E, ServerActionHandler as F, Duration as G, SetupContext as H, ServerActionMap as I, minus as J, GAME_START as K, ServerEvent as L, SeededRandom as M, ServerActionArgs as N, DeadlineContext as O, ServerActionContext as P, JsonPatchOp as Q, ServerOnlyAction as R, ActionContext as S, DEFAULT_ICON_URLS as T, UpdateArgs as U, SetupArgs as V, UpdateContext as W, sub as X, plus as Y, JsonMergePatch as Z, PlayerVoiceState as _, createDevHooks as a, SendAction as b, BridgeChannel as c, ConnectionState as d, applyJsonPatch as et, EventHandler as f, PlayScreenMessage as g, PayloadMap as h, attachDevHooks as i, Seat as j, Emit as k, BridgeMessage as l, GameConfig as m, RunHandle as n, PredictionWarning as o, EventSubscription as p, GameTime as q, UzuDevHooks as r, getPredictionWarnings as s, DevHooksCtx as t, ConnectionCallbacks as u, PlayersChangedMessage as v, ActionMap as w, ActionArgs as x, SeatKind as y, ServerOnlyActionContext as z };
package/dist/index.d.ts CHANGED
@@ -1,4 +1,18 @@
1
- import { $ as GameTime, A as DEFAULT_ICON_URLS, B as ServerActionContext, C as SetFn, D as ActionContext, E as ActionArgs, F as GameLogic, G as ServerOnlyActionContext, H as ServerActionMap, I as SERVER_TIME, J as SetupContext, K as ServerOnlyActionHandlerFn, L as Seat, M as DeadlineArgs, N as DeadlineContext, O as ActionHandler, P as Emit, Q as GAME_START, R as SeededRandom, S as SendAction, T as Operation, U as ServerEvent, V as ServerActionHandler, W as ServerOnlyAction, X as UpdateContext, Y as UpdateArgs, Z as Duration, _ as PayloadMap, a as attachDevHooks, at as applyJsonMergePatch, b as PlayersChangedMessage, c as getPredictionWarnings, d as ConnectionCallbacks, et as minus, f as ConnectionState, g as PatchFn, h as GameConfig, i as UzuDevHooks, it as JsonPatchOp, j as Deadline, k as ActionMap, l as BridgeChannel, m as EventSubscription, n as RunHandle, nt as sub, o as createDevHooks, ot as applyJsonPatch, p as EventHandler, q as SetupArgs, r as SyncHandle, rt as JsonMergePatch, s as PredictionWarning, t as DevHooksCtx, tt as plus, u as BridgeMessage, v as PlayScreenMessage, w as SyncConfig, x as SeatKind, y as PlayerVoiceState, z as ServerActionArgs } from "./dev-hooks-DOtJHP55.js";
1
+ import { $ as applyJsonMergePatch, A as GameLogic, B as ServerOnlyActionHandlerFn, C as ActionHandler, D as DeadlineArgs, E as Deadline, F as ServerActionHandler, G as Duration, H as SetupContext, I as ServerActionMap, J as minus, K as GAME_START, L as ServerEvent, M as SeededRandom, N as ServerActionArgs, O as DeadlineContext, P as ServerActionContext, Q as JsonPatchOp, R as ServerOnlyAction, S as ActionContext, T as DEFAULT_ICON_URLS, U as UpdateArgs, V as SetupArgs, W as UpdateContext, X as sub, Y as plus, Z as JsonMergePatch, _ as PlayerVoiceState, a as createDevHooks, b as SendAction, c as BridgeChannel, d as ConnectionState, et as applyJsonPatch, f as EventHandler, g as PlayScreenMessage, h as PayloadMap, i as attachDevHooks, j as Seat, k as Emit, l as BridgeMessage, m as GameConfig, n as RunHandle, o as PredictionWarning, p as EventSubscription, q as GameTime, r as UzuDevHooks, s as getPredictionWarnings, t as DevHooksCtx, u as ConnectionCallbacks, v as PlayersChangedMessage, w as ActionMap, x as ActionArgs, y as SeatKind, z as ServerOnlyActionContext } from "./dev-hooks-DmOV2p0j.js";
2
+ //#region ../engine-core/src/json-patch.d.ts
3
+ /**
4
+ * @docs
5
+ * - play-server 仕様: docs/docs/uzu_code/play-server.md
6
+ *
7
+ * state 差分同期用の JSON Patch。RFC 6902 の全 op は実装せず、
8
+ * `compare` が生成する replace / add / remove だけを扱う。
9
+ */
10
+ interface Operation {
11
+ op: 'replace' | 'add' | 'remove';
12
+ path: string;
13
+ value?: unknown;
14
+ }
15
+ //#endregion
2
16
  //#region ../engine-core/src/random.d.ts
3
17
  declare class SeededRandomImpl implements SeededRandom {
4
18
  private _state;
@@ -45,30 +59,6 @@ declare function isPaused(): boolean;
45
59
  /** 停止状態の変化を購読する。 */
46
60
  declare function onPauseChange(cb: (paused: boolean) => void): void;
47
61
  //#endregion
48
- //#region src/room.d.ts
49
- type RoomMessageHandler = (data: Record<string, unknown>) => void;
50
- /** WebSocket 互換の send/addEventListener インターフェース */
51
- interface WebSocketLike {
52
- send(data: string): void;
53
- addEventListener(type: 'message', handler: (ev: MessageEvent) => void): void;
54
- }
55
- /** Room の公開インターフェース。WebSocket / BroadcastChannel どちらでも実装可能。 */
56
- interface RoomLike {
57
- readonly myId: string;
58
- broadcast(msg: Record<string, unknown>): void;
59
- send(id: string, msg: Record<string, unknown>): void;
60
- on(type: string, handler: RoomMessageHandler): void;
61
- }
62
- declare class Room implements RoomLike {
63
- readonly myId: string;
64
- private ws;
65
- private handlers;
66
- constructor(ws: WebSocketLike, myId: string);
67
- broadcast(msg: Record<string, unknown>): void;
68
- send(id: string, msg: Record<string, unknown>): void;
69
- on(type: string, handler: RoomMessageHandler): void;
70
- }
71
- //#endregion
72
62
  //#region src/reconnectable-ws.d.ts
73
63
  /**
74
64
  * 自動再接続 WebSocket ラッパー
@@ -135,11 +125,7 @@ declare class ReconnectableWebSocket {
135
125
  type GameMessageHandler = (payload: Record<string, unknown>) => void;
136
126
  type PlayersChangedHandler = (players: Record<string, PlayerVoiceState>) => void;
137
127
  declare const isHosted: boolean;
138
- declare function getRoom(): RoomLike | null;
139
- declare function onRoom(callback: (room: RoomLike) => void): void;
140
128
  declare function init(opts?: {
141
- wsEndpoint?: string;
142
- syncEndpoint?: string;
143
129
  playerCount?: number;
144
130
  orientation?: 'portrait' | 'landscape';
145
131
  /**
@@ -203,6 +189,5 @@ declare function onPlayersChanged(handler: PlayersChangedHandler): void;
203
189
  * `serverActions` を持たない logic でもキーが残るよう空 map を既定にする。
204
190
  */
205
191
  declare function run<S, A extends ActionMap<S> = ActionMap<S>, SA extends ServerActionMap<S> = Record<never, never>>(config: GameConfig<S, A, SA>): void;
206
- declare function sync<S>(config: SyncConfig<S>): void;
207
192
  //#endregion
208
- export { type ActionArgs, type ActionContext, type ActionHandler, type ActionMap, type BridgeChannel, type BridgeMessage, type ConnectionCallbacks, type ConnectionState, DEFAULT_ICON_URLS, type Deadline, type DeadlineArgs, type DeadlineContext, type DevHooksCtx, type Duration, type Emit, type EventHandler, type EventSubscription, GAME_START, type GameConfig, type GameLogic, type GameTime, type JsonMergePatch, type JsonPatchOp, type Operation, type PatchFn, type PayloadMap, type PlayScreenMessage, type PlayerVoiceState, type PlayersChangedMessage, type PredictionWarning, type ReconnectableWSOptions, ReconnectableWebSocket, Room, type RoomLike, type RunHandle, SERVER_TIME, type Seat, type SeatKind, type SeededRandom, SeededRandomImpl, type SendAction, type ServerActionArgs, type ServerActionContext, type ServerActionHandler, type ServerActionMap, type ServerEvent, type ServerOnlyAction, type ServerOnlyActionContext, type ServerOnlyActionHandlerFn, type SetFn, type SetupArgs, type SetupContext, type SyncConfig, type SyncHandle, type UpdateArgs, type UpdateContext, type UzuDevHooks, applyJsonMergePatch, applyJsonPatch, attachDevHooks, changeRoom, createDevHooks, firstFrameReady, gameReady, gameTime, getPredictionWarnings, getRoom, init, isHosted, isPaused, isServerOnlyAction, minus, on, onPauseChange, onPlayersChanged, onRoom, playBgm, playSound, plus, run, send, serverOnly, setMicEnabled, stopBgm, sub, sync };
193
+ export { type ActionArgs, type ActionContext, type ActionHandler, type ActionMap, type BridgeChannel, type BridgeMessage, type ConnectionCallbacks, type ConnectionState, DEFAULT_ICON_URLS, type Deadline, type DeadlineArgs, type DeadlineContext, type DevHooksCtx, type Duration, type Emit, type EventHandler, type EventSubscription, GAME_START, type GameConfig, type GameLogic, type GameTime, type JsonMergePatch, type JsonPatchOp, type Operation, type PayloadMap, type PlayScreenMessage, type PlayerVoiceState, type PlayersChangedMessage, type PredictionWarning, type ReconnectableWSOptions, ReconnectableWebSocket, type RunHandle, type Seat, type SeatKind, type SeededRandom, SeededRandomImpl, type SendAction, type ServerActionArgs, type ServerActionContext, type ServerActionHandler, type ServerActionMap, type ServerEvent, type ServerOnlyAction, type ServerOnlyActionContext, type ServerOnlyActionHandlerFn, type SetupArgs, type SetupContext, type UpdateArgs, type UpdateContext, type UzuDevHooks, applyJsonMergePatch, applyJsonPatch, attachDevHooks, changeRoom, createDevHooks, firstFrameReady, gameReady, gameTime, getPredictionWarnings, init, isHosted, isPaused, isServerOnlyAction, minus, on, onPauseChange, onPlayersChanged, playBgm, playSound, plus, run, send, serverOnly, setMicEnabled, stopBgm, sub };
package/dist/index.js CHANGED
@@ -1,6 +1,4 @@
1
1
  //#region ../engine-core/src/types.ts
2
- /** Sentinel value — patch の value にセットすると、サーバーが Date.now() に置換する */
3
- const SERVER_TIME = "__SERVER_TIME__";
4
2
  /** デフォルトのプレイヤーアイコン URL 一覧(dev / local モード用) */
5
3
  const DEFAULT_ICON_URLS = [
6
4
  "https://imagedelivery.net/htp-D7B2hJT5XtdWYN9e7Q/4d0da24d-bcf2-4f7b-d1a0-f1bb8c747300/original",
@@ -284,7 +282,7 @@ function isServerOnlyAction(handler) {
284
282
  //#region src/server-clock.ts
285
283
  /**
286
284
  * @docs
287
- * - ServerAction仕様: docs/docs/uzu_code/connection-method/arch3-authority.md
285
+ * - play-server 仕様: docs/docs/uzu_code/play-server.md
288
286
  * - 緊急停止とゲーム内時計: docs/docs/uzu_code/emergency-stop.md
289
287
  *
290
288
  * サーバーのゲーム内時計の推定値をクライアント全体へ配る。
@@ -400,46 +398,6 @@ function requestResume() {
400
398
  requester(false);
401
399
  }
402
400
  //#endregion
403
- //#region src/room.ts
404
- var Room = class {
405
- constructor(ws, myId) {
406
- this.handlers = /* @__PURE__ */ new Map();
407
- this.ws = ws;
408
- this.myId = myId;
409
- this.ws.addEventListener("message", (ev) => {
410
- let parsed;
411
- try {
412
- parsed = JSON.parse(ev.data);
413
- } catch {
414
- return;
415
- }
416
- const type = parsed.type;
417
- if (!type) return;
418
- const from = parsed.__from;
419
- console.log(`[SDK Room] ⬅ recv type=${type} from=${from}`, JSON.stringify(parsed));
420
- (this.handlers.get(type) || []).forEach((h) => h({
421
- ...parsed,
422
- __from: from
423
- }));
424
- });
425
- }
426
- broadcast(msg) {
427
- console.log(`[SDK Room] ➡ broadcast type=${msg.type}`, JSON.stringify(msg));
428
- this.ws.send(JSON.stringify(msg));
429
- }
430
- send(id, msg) {
431
- console.log(`[SDK Room] ➡ send to=${id} type=${msg.type}`, JSON.stringify(msg));
432
- this.ws.send(JSON.stringify({
433
- ...msg,
434
- __to: id
435
- }));
436
- }
437
- on(type, handler) {
438
- if (!this.handlers.has(type)) this.handlers.set(type, []);
439
- this.handlers.get(type).push(handler);
440
- }
441
- };
442
- //#endregion
443
401
  //#region src/reconnectable-ws.ts
444
402
  const DEFAULTS = {
445
403
  maxReconnectAttempts: 15,
@@ -1310,15 +1268,12 @@ function runOnlineServerAction(config, gameEndpoint, roomId, seatId, players, se
1310
1268
  id: p.id,
1311
1269
  name: p.nickname,
1312
1270
  iconUrl: p.iconUrl,
1313
- characterId: p.characterId,
1314
- kind: "player"
1271
+ characterId: p.characterId
1315
1272
  });
1316
- const roster = JSON.stringify(players.map(toWire));
1317
1273
  const wsUrl = `${gameEndpoint}/${roomId}?${new URLSearchParams({
1318
1274
  seatId,
1319
1275
  nickname: "Player",
1320
- players: roster,
1321
- seats: roster
1276
+ players: JSON.stringify(players.map(toWire))
1322
1277
  })}`;
1323
1278
  console.log(`[SDK ServerAction] 🔗 Connecting wsUrl=${wsUrl}`);
1324
1279
  const ws = new ReconnectableWebSocket(wsUrl, {
@@ -1567,7 +1522,6 @@ function runLocalServerAction(config) {
1567
1522
  const setupTime = gameTime();
1568
1523
  const setupArgs = {
1569
1524
  players,
1570
- seats: players,
1571
1525
  ctx: {
1572
1526
  random,
1573
1527
  ...timeCtx(setupTime)
@@ -1703,217 +1657,15 @@ function runLocalServerAction(config) {
1703
1657
  };
1704
1658
  }
1705
1659
  //#endregion
1706
- //#region src/sync/local.ts
1707
- function resolveLocalTime(ops) {
1708
- const now = Date.now();
1709
- return ops.map((op) => op.value === "__SERVER_TIME__" ? {
1710
- ...op,
1711
- value: now
1712
- } : op);
1713
- }
1714
- function syncLocal(config) {
1715
- const { initialState, onState, inputs, events: _events } = config;
1716
- const localCount = config.playerCount;
1717
- const players = [];
1718
- for (let i = 0; i < localCount; i++) players.push({
1719
- id: `local_${i}`,
1720
- nickname: `Player ${i + 1}`,
1721
- iconUrl: DEFAULT_ICON_URLS[i % DEFAULT_ICON_URLS.length]
1722
- });
1723
- let state = initialState(players);
1724
- const myId = players[0].id;
1725
- const patchFn = (ops) => {
1726
- applyPatch(state, resolveLocalTime(ops));
1727
- onState(state, myId, Date.now());
1728
- };
1729
- const setFn = (path, value) => {
1730
- patchFn([{
1731
- op: "replace",
1732
- path,
1733
- value
1734
- }]);
1735
- };
1736
- inputs(patchFn, setFn);
1737
- onState(state, myId, Date.now());
1738
- setInterval(() => {
1739
- onState(state, myId, Date.now());
1740
- }, 100);
1741
- return {
1742
- getRawState: () => state,
1743
- setRawState: async (next) => {
1744
- state = next;
1745
- onState(state, myId, Date.now());
1746
- },
1747
- mergeRawState: async (patch) => {
1748
- applyJsonMergePatch(state, patch);
1749
- onState(state, myId, Date.now());
1750
- },
1751
- patchRawState: async (ops) => {
1752
- applyJsonPatch(state, ops);
1753
- onState(state, myId, Date.now());
1754
- }
1755
- };
1756
- }
1757
- //#endregion
1758
- //#region src/sync/online.ts
1759
- function syncOnline(config, syncEndpoint, roomId, playerId, hostPlayers) {
1760
- const { initialState, onState, inputs, events: _events } = config;
1761
- const wsUrl = `${syncEndpoint}/${roomId}?playerId=${encodeURIComponent(playerId)}`;
1762
- console.log(`[SDK Sync] 🔗 Connecting wsUrl=${wsUrl}`);
1763
- const ws = new ReconnectableWebSocket(wsUrl, {
1764
- onConnectionStateChange: (state) => {
1765
- console.log(`[SDK Sync] 📡 Connection state: ${state}`);
1766
- config.onConnectionStateChange?.(state);
1767
- },
1768
- shouldBuffer: (data) => {
1769
- try {
1770
- const parsed = JSON.parse(data);
1771
- return parsed.type !== "__patch" && parsed.type !== "__patch_ack";
1772
- } catch {
1773
- return true;
1774
- }
1775
- }
1776
- });
1777
- const myId = playerId;
1778
- const players = hostPlayers;
1779
- let state = null;
1780
- let gameInitSent = false;
1781
- let serverTimeOffset = 0;
1782
- let localSeq = 0;
1783
- let requestStatePending = false;
1784
- const estimateServerTime = () => Date.now() + serverTimeOffset;
1785
- const resolveToServerTime = (ops) => {
1786
- const now = estimateServerTime();
1787
- return ops.map((op) => op.value === "__SERVER_TIME__" ? {
1788
- ...op,
1789
- value: now
1790
- } : op);
1791
- };
1792
- const sendPatch = (ops) => {
1793
- if (!ops || ops.length === 0) return;
1794
- if (state) {
1795
- applyPatch(state, resolveToServerTime(ops));
1796
- onState(state, myId, estimateServerTime());
1797
- }
1798
- console.log(`[SDK Sync] ➡ send __patch ops=${ops.length}`, JSON.stringify(ops));
1799
- ws.send(JSON.stringify({
1800
- type: "__patch",
1801
- ops
1802
- }));
1803
- };
1804
- const sendSet = (path, value) => {
1805
- sendPatch([{
1806
- op: "replace",
1807
- path,
1808
- value
1809
- }]);
1810
- };
1811
- inputs(sendPatch, sendSet);
1812
- setInterval(() => {
1813
- if (state) onState(state, myId, estimateServerTime());
1814
- }, 100);
1815
- const tryInitState = () => {
1816
- if (gameInitSent) return;
1817
- gameInitSent = true;
1818
- const initState = initialState(players);
1819
- console.log(`[SDK Sync] ➡ send __init_state players=${players.length}`);
1820
- ws.send(JSON.stringify({
1821
- type: "__init_state",
1822
- state: initState
1823
- }));
1824
- };
1825
- ws.addEventListener("message", (ev) => {
1826
- let parsed;
1827
- try {
1828
- parsed = JSON.parse(ev.data);
1829
- } catch {
1830
- return;
1831
- }
1832
- const msgType = parsed.type;
1833
- console.log(`[SDK Sync] ⬅ recv type=${msgType}`, JSON.stringify(parsed));
1834
- if (msgType === "__room_init") {
1835
- console.log(`[SDK Sync] ✅ Room init myId=${myId} players=${players.length}`);
1836
- tryInitState();
1837
- return;
1838
- }
1839
- if (msgType === "__reconnected") {
1840
- console.log(`[SDK Sync] 🔄 Reconnected myId=${myId}`);
1841
- if (!gameInitSent) tryInitState();
1842
- return;
1843
- }
1844
- if (msgType === "__state_cleared") {
1845
- console.log(`[SDK Sync] 🗑 State cleared, reinitializing`);
1846
- state = null;
1847
- localSeq = 0;
1848
- requestStatePending = false;
1849
- gameInitSent = false;
1850
- tryInitState();
1851
- return;
1852
- }
1853
- if (msgType === "__patch_failed") {
1854
- console.log(`[SDK Sync] ⚠️ Patch failed: ${parsed.reason}`);
1855
- config.onPatchFailed?.(parsed.reason);
1856
- return;
1857
- }
1858
- if (msgType === "__patch_ack") {
1859
- if (!state) return;
1860
- const ackSeq = parsed.seq ?? 0;
1861
- const ackSenderId = parsed.senderId;
1862
- const ackOps = parsed.ops;
1863
- const serverTime = parsed.serverTime;
1864
- if (ackSenderId === myId) {
1865
- localSeq = ackSeq;
1866
- serverTimeOffset = serverTime - Date.now();
1867
- return;
1868
- }
1869
- if (requestStatePending) return;
1870
- if (ackSeq === localSeq + 1) {
1871
- if (!applyPatch(state, ackOps)) {
1872
- console.log(`[SDK Sync] ⚠️ Local applyPatch failed, requesting full state`);
1873
- requestStatePending = true;
1874
- ws.send(JSON.stringify({ type: "__request_state" }));
1875
- return;
1876
- }
1877
- localSeq = ackSeq;
1878
- serverTimeOffset = serverTime - Date.now();
1879
- onState(state, myId, serverTime);
1880
- } else if (ackSeq > localSeq + 1) {
1881
- console.log(`[SDK Sync] ⚠️ Seq gap detected: expected=${localSeq + 1} received=${ackSeq}, requesting full state`);
1882
- requestStatePending = true;
1883
- ws.send(JSON.stringify({ type: "__request_state" }));
1884
- }
1885
- return;
1886
- }
1887
- if (msgType === "__state") {
1888
- state = parsed.state;
1889
- const serverTime = parsed.serverTime;
1890
- localSeq = parsed.seq ?? 0;
1891
- requestStatePending = false;
1892
- serverTimeOffset = serverTime - Date.now();
1893
- console.log(`[SDK Sync] ✅ State received serverTime=${serverTime} offset=${serverTimeOffset} seq=${localSeq}`);
1894
- ws.clearBuffer();
1895
- onState(state, myId, serverTime);
1896
- return;
1897
- }
1898
- });
1899
- return { ws };
1900
- }
1901
- //#endregion
1902
1660
  //#region src/index.ts
1903
1661
  const gameHandlers = /* @__PURE__ */ new Map();
1904
1662
  const _playersChangedHandlers = [];
1905
- let _room = null;
1906
1663
  let _lastStateSnap = null;
1907
- let _onRoomCallbacks = [];
1908
- let _wsEndpoint = null;
1909
- let _syncEndpoint = null;
1910
1664
  let _gameEndpoint = null;
1911
- let _syncWs = null;
1912
1665
  let _initialized = false;
1913
1666
  let _usesRun = false;
1914
1667
  let _playerId = null;
1915
1668
  let _runHandle = null;
1916
- let _syncHandle = null;
1917
1669
  const _snapshotListeners = /* @__PURE__ */ new Set();
1918
1670
  function notifyDevSnapshot(snap) {
1919
1671
  _snapshotListeners.forEach((cb) => {
@@ -1928,10 +1680,10 @@ function attachDevHooksIfNotHosted() {
1928
1680
  if (window.FlutterHost) return;
1929
1681
  attachDevHooks({
1930
1682
  getSnapshot: () => _lastStateSnap?.state ?? null,
1931
- getRawState: _runHandle?.getRawState ?? _syncHandle?.getRawState,
1932
- setRawState: _runHandle?.setRawState ?? _syncHandle?.setRawState,
1933
- mergeRawState: _runHandle?.mergeRawState ?? _syncHandle?.mergeRawState,
1934
- patchRawState: _runHandle?.patchRawState ?? _syncHandle?.patchRawState,
1683
+ getRawState: _runHandle?.getRawState,
1684
+ setRawState: _runHandle?.setRawState,
1685
+ mergeRawState: _runHandle?.mergeRawState,
1686
+ patchRawState: _runHandle?.patchRawState,
1935
1687
  playerId: () => _lastStateSnap?.myId ?? _playerId,
1936
1688
  subscribeSnapshot: (cb) => {
1937
1689
  _snapshotListeners.add(cb);
@@ -1942,13 +1694,6 @@ function attachDevHooksIfNotHosted() {
1942
1694
  });
1943
1695
  }
1944
1696
  const isHosted = typeof window !== "undefined" && (!!window.FlutterHost || window.parent !== window);
1945
- function getRoom() {
1946
- return _room;
1947
- }
1948
- function onRoom(callback) {
1949
- if (_room) callback(_room);
1950
- else _onRoomCallbacks.push(callback);
1951
- }
1952
1697
  function init(opts) {
1953
1698
  const params = new URLSearchParams(window.location.search);
1954
1699
  _playerId = params.get("seatId");
@@ -1956,14 +1701,10 @@ function init(opts) {
1956
1701
  const serverParam = params.get("server");
1957
1702
  if (serverParam && serverParam.length > 0) {
1958
1703
  const base = serverParam.replace(/\/$/, "");
1959
- _wsEndpoint = `${base}/ws/rooms`;
1960
- _syncEndpoint = `${base}/ws/sync`;
1961
1704
  const revisionId = params.get("revisionId");
1962
1705
  if (!revisionId) throw new Error("[UZU SDK] revisionId is required when server is specified. Pass ?revisionId=xxx in the URL.");
1963
1706
  _gameEndpoint = `${base}/ws/games/${revisionId}`;
1964
1707
  }
1965
- if (opts?.wsEndpoint) _wsEndpoint = opts.wsEndpoint;
1966
- if (opts?.syncEndpoint) _syncEndpoint = opts.syncEndpoint;
1967
1708
  if (!isHosted) return;
1968
1709
  if (!_initialized) {
1969
1710
  window.__ps = { onMessage: (msg) => {
@@ -1993,13 +1734,6 @@ function init(opts) {
1993
1734
  sendRaw("sdk", "ready", {});
1994
1735
  _initialized = true;
1995
1736
  }
1996
- if (!_usesRun) {
1997
- const roomIdParam = params.get("roomId");
1998
- if (roomIdParam) {
1999
- if (_wsEndpoint) connectRoom(roomIdParam);
2000
- else console.warn("[SDK] roomId is present but server is not configured — skipping Relay connection");
2001
- }
2002
- }
2003
1737
  if (!_usesRun) attachDevHooksIfNotHosted();
2004
1738
  }
2005
1739
  /** game channel でカスタムメッセージを送信する。 native コマンドには使用不可。 */
@@ -2104,33 +1838,6 @@ function run(config) {
2104
1838
  } else throw new Error("[UZU SDK] roomId is set but ?server= is missing. Multiplayer requires a WebSocket server URL — pass ?server=ws://host:port in the URL.");
2105
1839
  attachDevHooksIfNotHosted();
2106
1840
  }
2107
- function sync(config) {
2108
- _usesRun = true;
2109
- const params = new URLSearchParams(window.location.search);
2110
- if (!isHosted) return;
2111
- init();
2112
- const origOnState = config.onState;
2113
- const wrappedConfig = {
2114
- ...config,
2115
- onState(state, myPlayerId, serverTime) {
2116
- _lastStateSnap = {
2117
- state,
2118
- serverTime,
2119
- myId: myPlayerId
2120
- };
2121
- origOnState(state, myPlayerId, serverTime);
2122
- notifyDevSnapshot(state);
2123
- }
2124
- };
2125
- const roomId = params.get("roomId");
2126
- if (!roomId) _syncHandle = syncLocal(wrappedConfig);
2127
- else if (_syncEndpoint) {
2128
- const { seatId, players } = resolveSeatParams(params);
2129
- const { ws } = syncOnline(wrappedConfig, _syncEndpoint, roomId, seatId, players);
2130
- _syncWs = ws;
2131
- } else throw new Error("[UZU SDK] roomId is set but ?server= is missing. Multiplayer sync requires a WebSocket server URL — pass ?server=ws://host:port in the URL.");
2132
- attachDevHooksIfNotHosted();
2133
- }
2134
1841
  /**
2135
1842
  * 自分の席種別を URL から取り出す。
2136
1843
  *
@@ -2149,13 +1856,11 @@ function resolveSeatKind(params) {
2149
1856
  *
2150
1857
  * roster (`?players=`) に自席が居ないことは異常ではない。観測者はそもそも roster に
2151
1858
  * 載らないまま接続してくる。
2152
- *
2153
- * 移行期: 旧ホスト (デプロイ前の mobile / uzutokyo / emulator) は `?seats=` で送ってくる。
2154
1859
  */
2155
1860
  function resolveSeatParams(params) {
2156
1861
  const seatId = params.get("seatId");
2157
1862
  if (!seatId) throw new Error("[UZU SDK] seatId is required. Pass ?seatId=xxx in the URL.");
2158
- const json = params.get("players") ?? params.get("seats");
1863
+ const json = params.get("players");
2159
1864
  if (!json) throw new Error("[UZU SDK] players is required. Pass ?players=[...] in the URL.");
2160
1865
  return {
2161
1866
  seatId,
@@ -2167,33 +1872,6 @@ function resolveSeatParams(params) {
2167
1872
  }))
2168
1873
  };
2169
1874
  }
2170
- function requireEndpoint(endpoint, name) {
2171
- if (!endpoint) throw new Error(`[UZU SDK] ${name} is not configured. Pass ?server=ws://host:port or call init({ wsEndpoint, syncEndpoint }).`);
2172
- return endpoint;
2173
- }
2174
- function connectRoom(roomId) {
2175
- const { seatId } = resolveSeatParams(new URLSearchParams(window.location.search));
2176
- const wsParams = new URLSearchParams({ playerId: seatId });
2177
- const wsUrl = `${requireEndpoint(_wsEndpoint, "wsEndpoint")}/${roomId}?${wsParams}`;
2178
- console.log(`[SDK] 🔗 connectRoom wsUrl=${wsUrl}`);
2179
- const ws = new ReconnectableWebSocket(wsUrl);
2180
- ws.addEventListener("message", (ev) => {
2181
- let parsed;
2182
- try {
2183
- parsed = JSON.parse(ev.data);
2184
- } catch {
2185
- return;
2186
- }
2187
- console.log(`[SDK] ⬅ recv ws type=${parsed.type}`, JSON.stringify(parsed));
2188
- if (parsed.type === "__room_init") {
2189
- const myId = parsed.myId;
2190
- console.log(`[SDK] ✅ Room initialized myId=${myId}`);
2191
- _room = new Room(ws, myId);
2192
- _onRoomCallbacks.forEach((cb) => cb(_room));
2193
- _onRoomCallbacks = [];
2194
- } else if (parsed.type === "__reconnected") console.log(`[SDK] 🔄 Room reconnected`);
2195
- });
2196
- }
2197
1875
  /** 内部送信関数。 channel + type + payload のエンベロープで送信する。 */
2198
1876
  function sendRaw(channel, type, payload) {
2199
1877
  const msg = {
@@ -2214,10 +1892,6 @@ function handleMessage(msg) {
2214
1892
  console.log(`[SDK] 🔧 handleMessage sdk/getState → responding`);
2215
1893
  sendRaw("sdk", "stateResponse", { state: _lastStateSnap });
2216
1894
  return;
2217
- case "clearState":
2218
- console.log(`[SDK] 🗑 handleMessage sdk/clearState → forwarding to server`);
2219
- if (_syncWs && _syncWs.connectionState === "connected") _syncWs.send(JSON.stringify({ type: "__clear_state" }));
2220
- return;
2221
1895
  case "playersChanged": {
2222
1896
  const players = payload?.players ?? {};
2223
1897
  _playersChangedHandlers.forEach((fn) => fn(players));
@@ -2257,7 +1931,7 @@ const ACTION_ITEM_WIDTH = 38;
2257
1931
  function calcHudInsets(params) {
2258
1932
  const y = 52;
2259
1933
  let actionCount = 2;
2260
- const json = params.get("players") ?? params.get("seats");
1934
+ const json = params.get("players");
2261
1935
  if (json) try {
2262
1936
  actionCount = JSON.parse(json).length >= 2 ? 2 : 0;
2263
1937
  } catch {}
@@ -2269,4 +1943,4 @@ function calcHudInsets(params) {
2269
1943
  };
2270
1944
  }
2271
1945
  //#endregion
2272
- export { DEFAULT_ICON_URLS, GAME_START, ReconnectableWebSocket, Room, SERVER_TIME, SeededRandomImpl, applyJsonMergePatch, applyJsonPatch, attachDevHooks, changeRoom, createDevHooks, firstFrameReady, gameReady, gameTime, getPredictionWarnings, getRoom, init, isHosted, isPaused, isServerOnlyAction, minus, on, onPauseChange, onPlayersChanged, onRoom, playBgm, playSound, plus, run, send, serverOnly, setMicEnabled, stopBgm, sub, sync };
1946
+ export { DEFAULT_ICON_URLS, GAME_START, ReconnectableWebSocket, SeededRandomImpl, applyJsonMergePatch, applyJsonPatch, attachDevHooks, changeRoom, createDevHooks, firstFrameReady, gameReady, gameTime, getPredictionWarnings, init, isHosted, isPaused, isServerOnlyAction, minus, on, onPauseChange, onPlayersChanged, playBgm, playSound, plus, run, send, serverOnly, setMicEnabled, stopBgm, sub };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@uzuhq/code-sdk",
3
- "version": "0.8.3",
3
+ "version": "0.8.5",
4
4
  "description": "UZU PlayScreen SDK - Flutter ↔ JS ゲーム通信ライブラリ",
5
5
  "type": "module",
6
6
  "exports": {