@umicat/phaser-sdk 1.0.0

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.
Files changed (62) hide show
  1. package/SDK-GUIDE.md +1726 -0
  2. package/dist/core/Transport.d.ts +28 -0
  3. package/dist/core/Transport.js +7 -0
  4. package/dist/core/Umicat.d.ts +45 -0
  5. package/dist/core/Umicat.js +60 -0
  6. package/dist/core/UmicatGame.d.ts +43 -0
  7. package/dist/core/UmicatGame.js +64 -0
  8. package/dist/core/UmicatScene.d.ts +19 -0
  9. package/dist/core/UmicatScene.js +38 -0
  10. package/dist/core/transports/LocalStorageTransport.d.ts +22 -0
  11. package/dist/core/transports/LocalStorageTransport.js +78 -0
  12. package/dist/core/transports/PostMessageTransport.d.ts +28 -0
  13. package/dist/core/transports/PostMessageTransport.js +105 -0
  14. package/dist/editor/EditorBridge.d.ts +114 -0
  15. package/dist/editor/EditorBridge.js +2608 -0
  16. package/dist/editor/EditorOverlayScene.d.ts +333 -0
  17. package/dist/editor/EditorOverlayScene.js +1896 -0
  18. package/dist/editor/EditorState.d.ts +251 -0
  19. package/dist/editor/EditorState.js +197 -0
  20. package/dist/gamedata/GameDataModule.d.ts +45 -0
  21. package/dist/gamedata/GameDataModule.js +59 -0
  22. package/dist/index.d.ts +43 -0
  23. package/dist/index.js +43 -0
  24. package/dist/orientation.d.ts +5 -0
  25. package/dist/orientation.js +4 -0
  26. package/dist/protocol.d.ts +807 -0
  27. package/dist/protocol.js +3 -0
  28. package/dist/realtime/RealtimeModule.d.ts +93 -0
  29. package/dist/realtime/RealtimeModule.js +115 -0
  30. package/dist/realtime/UmicatRoom.d.ts +197 -0
  31. package/dist/realtime/UmicatRoom.js +353 -0
  32. package/dist/recording/RecordingManager.d.ts +11 -0
  33. package/dist/recording/RecordingManager.js +59 -0
  34. package/dist/saves/SavesModule.d.ts +23 -0
  35. package/dist/saves/SavesModule.js +37 -0
  36. package/dist/scene/EditorMode.d.ts +17 -0
  37. package/dist/scene/EditorMode.js +22 -0
  38. package/dist/scene/EntityRegistry.d.ts +39 -0
  39. package/dist/scene/EntityRegistry.js +103 -0
  40. package/dist/scene/GameConfig.d.ts +60 -0
  41. package/dist/scene/GameConfig.js +50 -0
  42. package/dist/scene/HudRuntime.d.ts +131 -0
  43. package/dist/scene/HudRuntime.js +1224 -0
  44. package/dist/scene/Prefabs.d.ts +92 -0
  45. package/dist/scene/Prefabs.js +175 -0
  46. package/dist/scene/Rules.d.ts +73 -0
  47. package/dist/scene/Rules.js +164 -0
  48. package/dist/scene/SceneLoader.d.ts +118 -0
  49. package/dist/scene/SceneLoader.js +615 -0
  50. package/dist/scene/Waves.d.ts +85 -0
  51. package/dist/scene/Waves.js +365 -0
  52. package/dist/scene/autotile.d.ts +103 -0
  53. package/dist/scene/autotile.js +321 -0
  54. package/dist/scene/renderScripts.d.ts +53 -0
  55. package/dist/scene/renderScripts.js +67 -0
  56. package/dist/scene/spawnEntity.d.ts +201 -0
  57. package/dist/scene/spawnEntity.js +1326 -0
  58. package/dist/scene/types.d.ts +1166 -0
  59. package/dist/scene/types.js +34 -0
  60. package/dist/screenshot/ScreenshotManager.d.ts +14 -0
  61. package/dist/screenshot/ScreenshotManager.js +33 -0
  62. package/package.json +35 -0
@@ -0,0 +1,807 @@
1
+ export declare const PROTOCOL_VERSION = 1;
2
+ export interface UmicatUser {
3
+ id: string;
4
+ name: string;
5
+ avatar?: string;
6
+ }
7
+ export interface HelloMessage {
8
+ type: 'umicat:hello';
9
+ protocolVersion: number;
10
+ sdkVersion: string;
11
+ }
12
+ export interface RpcRequestMessage {
13
+ type: 'umicat:rpc';
14
+ id: string;
15
+ method: string;
16
+ params?: unknown;
17
+ }
18
+ export type SdkToHostMessage = HelloMessage | RpcRequestMessage;
19
+ export interface InitMessage {
20
+ type: 'umicat:init';
21
+ protocolVersion: number;
22
+ gameId: string;
23
+ user: UmicatUser | null;
24
+ capabilities: string[];
25
+ /**
26
+ * WebSocket endpoint for umicat-realtime-service. Present only when the host
27
+ * has multiplayer configured (capabilities includes 'realtime'). SDK connects
28
+ * here after fetching a JWT via the 'realtime.getToken' RPC.
29
+ */
30
+ realtimeUrl?: string;
31
+ }
32
+ export interface RpcResultOk {
33
+ type: 'umicat:rpc.result';
34
+ id: string;
35
+ ok: true;
36
+ result: unknown;
37
+ }
38
+ export interface RpcErrorPayload {
39
+ code: string;
40
+ message: string;
41
+ }
42
+ export interface RpcResultError {
43
+ type: 'umicat:rpc.result';
44
+ id: string;
45
+ ok: false;
46
+ error: RpcErrorPayload;
47
+ }
48
+ export type HostToSdkMessage = InitMessage | RpcResultOk | RpcResultError;
49
+ /**
50
+ * Patch shape applied to a live entity. Only the fields present are updated;
51
+ * `null` is the explicit clear (drops the field).
52
+ *
53
+ * **Flat schema** (SDK 0.3.0). Visual / render fields live at the patch root,
54
+ * the same place they live on the entity. Sprite tint goes in `tint`, not
55
+ * `visual.tint`. Same rule for every renderable field across every entity kind.
56
+ *
57
+ * The bridge applies only the slot relevant to the entity's kind — passing
58
+ * `width` on a sprite is a no-op, same as `tint` on a panel.
59
+ */
60
+ export interface EditorEntityPatch {
61
+ transform?: Partial<{
62
+ x: number;
63
+ y: number;
64
+ rotation: number;
65
+ scaleX: number;
66
+ scaleY: number;
67
+ depth: number;
68
+ }>;
69
+ /**
70
+ * HUD-only (slice 5). Updates an entity's anchor side / offset. Only one
71
+ * of `transform` (world entities) or `anchor` (HUD entities) is set per
72
+ * patch; the SDK picks the right path based on the active mode.
73
+ */
74
+ anchor?: Partial<{
75
+ side: string;
76
+ offsetX: number;
77
+ offsetY: number;
78
+ }>;
79
+ /** HUD-only (slice 5). Render layer / z-order overrides. */
80
+ layer?: string;
81
+ z?: number;
82
+ /** Sprite / HUD-image tint + opacity controls. */
83
+ tint?: string | null;
84
+ alpha?: number;
85
+ flipX?: boolean;
86
+ flipY?: boolean;
87
+ /** Sprite / HUD-image frame index OR atlas frame name. */
88
+ frame?: string | number;
89
+ /** Primitive (rect / circle) + HUD widget dims. */
90
+ width?: number;
91
+ height?: number;
92
+ radius?: number;
93
+ fillColor?: string;
94
+ strokeColor?: string | null;
95
+ strokeWidth?: number;
96
+ /**
97
+ * For `code-rendered` entities — the entire params object replaces
98
+ * `params`. SDK re-calls `render(g, params)` on receipt.
99
+ */
100
+ params?: Record<string, unknown>;
101
+ /**
102
+ * HUD text widgets — replaces `source` wholesale. Static→dynamic switches
103
+ * and prefix/suffix edits all land here.
104
+ */
105
+ source?: Record<string, unknown>;
106
+ /** HUD text font / colour. */
107
+ fontFamily?: string;
108
+ fontSize?: number;
109
+ color?: string;
110
+ align?: 'left' | 'center' | 'right';
111
+ /** Sprite / HUD-image / HUD-icon-button asset references. */
112
+ assetId?: string;
113
+ iconAssetId?: string;
114
+ backgroundAssetId?: string | null;
115
+ backgroundFrame?: number;
116
+ backgroundRegion?: string;
117
+ /** HUD icon-button label + shape + colors. */
118
+ label?: string;
119
+ shape?: 'rounded-rect' | 'circle';
120
+ pressedFillColor?: string;
121
+ textColor?: string;
122
+ /** HUD progress-bar / panel — value + background. */
123
+ value?: Record<string, unknown>;
124
+ max?: Record<string, unknown>;
125
+ backgroundColor?: string;
126
+ backgroundAlpha?: number;
127
+ borderColor?: string | null;
128
+ borderWidth?: number;
129
+ borderRadius?: number;
130
+ role?: string | null;
131
+ properties?: Record<string, unknown>;
132
+ }
133
+ export interface EditorEnterMessage {
134
+ type: 'umicat:editor:enter';
135
+ }
136
+ export interface EditorExitMessage {
137
+ type: 'umicat:editor:exit';
138
+ }
139
+ export interface EditorGetSceneMessage {
140
+ /** Host requests a snapshot of what's currently loaded in the iframe. */
141
+ type: 'umicat:editor:getScene';
142
+ }
143
+ export interface EditorApplyEditMessage {
144
+ type: 'umicat:editor:applyEdit';
145
+ entityId: string;
146
+ patch: EditorEntityPatch;
147
+ /**
148
+ * Optional asset record to upsert into the iframe's manifest cache + lazy-
149
+ * load before the patch applies. Mirrors {@link EditorCreateEntityMessage}'s
150
+ * `manifestAsset` field. Used by the Inspector's Bg-image picker when the
151
+ * user picks an asset whose manifest entry needs flipping (e.g. region
152
+ * atlases — visual editor slice 10 — where the existing manifest entry
153
+ * was {@code kind: 'image'} but the asset is now atlas-format).
154
+ *
155
+ * <p>When present, the bridge:
156
+ * 1. Merges the asset into {@code scene.cache.json}'s manifest record
157
+ * 2. Lazy-loads the texture (and atlas-ninepatch sidecar JSON when
158
+ * {@code kind: 'atlas' + atlasFormat: 'json'})
159
+ * 3. Applies the entity patch
160
+ */
161
+ manifestAsset?: unknown;
162
+ }
163
+ export interface EditorSetSelectionMessage {
164
+ type: 'umicat:editor:setSelection';
165
+ /** Empty array deselects. v1: max length 1 (multi-select is slice 2.5). */
166
+ entityIds: string[];
167
+ }
168
+ export interface EditorPanZoomMessage {
169
+ /** Editor camera control — separate from in-game camera. */
170
+ type: 'umicat:editor:panZoom';
171
+ scrollX?: number;
172
+ scrollY?: number;
173
+ /** Absolute zoom (1 = 100%). */
174
+ zoom?: number;
175
+ /** If true, deltas are added to current scroll. */
176
+ relative?: boolean;
177
+ }
178
+ /**
179
+ * Create a new entity in the active scene (slice 3 — drag-to-place).
180
+ * The SDK picks an asset record from `manifestAsset` (if present) so the
181
+ * texture can be lazy-loaded before spawn. Position can be supplied as
182
+ * world coords (preferred — host computed via 1:1 mapping in slice 3) or
183
+ * skipped if the entity already carries a transform.
184
+ */
185
+ export interface EditorCreateEntityMessage {
186
+ type: 'umicat:editor:createEntity';
187
+ /** Full entity record. transform.x/y is authoritative. */
188
+ entity: unknown;
189
+ /** Asset record to add to runtime cache before spawn. Omit if assetId is already loaded. */
190
+ manifestAsset?: unknown;
191
+ }
192
+ export interface EditorDeleteEntityMessage {
193
+ type: 'umicat:editor:deleteEntity';
194
+ entityId: string;
195
+ }
196
+ /**
197
+ * Editor mode — slice 5. World mode edits the world scene; HUD mode edits
198
+ * the HUD scene attached to the active world. The host sends this when the
199
+ * user toggles the World/HUD switch in the editor canvas.
200
+ *
201
+ * The mode change does NOT clear selection at the SDK level; the host owns
202
+ * selection state per mode (separate selectedId for world / HUD drafts).
203
+ */
204
+ export interface EditorSetEditModeMessage {
205
+ type: 'umicat:editor:setEditMode';
206
+ mode: 'world' | 'hud';
207
+ }
208
+ /**
209
+ * Asset-level live update (slice 8 — hitbox / depthAnchor editing).
210
+ *
211
+ * When the user edits an Asset's hitbox or depthAnchor in the Hitbox Editor
212
+ * modal, the change is at the asset level — not tied to a specific entity
213
+ * patch. The host sends this message; the bridge:
214
+ * 1. Upserts the asset into the iframe's cached manifest
215
+ * 2. Walks the entity registry, finds every sprite entity using this asset
216
+ * (matched via `entityAssetId` data-manager stash), and re-applies
217
+ * `setOrigin(depthAnchor)` + `applyAssetHitbox` to each
218
+ *
219
+ * Unlike `applyEdit`, this is NOT scoped to a single entity — one asset edit
220
+ * can affect dozens of in-scene sprites simultaneously.
221
+ */
222
+ export interface EditorAssetUpdateMessage {
223
+ type: 'umicat:editor:assetUpdate';
224
+ /** Full AssetRecord — replaces the existing manifest entry. */
225
+ asset: unknown;
226
+ }
227
+ /**
228
+ * Patch applied to a prefab record (slice 11 Phase B.5 / editor P0.2).
229
+ *
230
+ * Mirrors the slice-2 entity patch shape but scoped to prefab fields. Only
231
+ * the fields present in the patch are updated; unset fields are unchanged.
232
+ *
233
+ * The bridge merges the patch into `manifest.prefabs[<id>]` in the cached
234
+ * manifest, then walks `EntityRegistry.byPrefabId(id)` and re-applies the
235
+ * visual / physics changes to every live instance. Subsequent `spawnPrefab`
236
+ * calls pick up the new values automatically.
237
+ *
238
+ * Property edits are stamped on `getData('entityProperties')` so behavior
239
+ * code reading `go.getData('entityProperties').hp` sees the new value
240
+ * immediately. Role edits update the entity-registry's role map.
241
+ */
242
+ /**
243
+ * Patch to a prefab record. Flat schema (SDK 0.3.0) — render fields
244
+ * (assetId / tint / fillColor / width / params / etc.) live at the patch
245
+ * root, exactly like world-entity patches.
246
+ *
247
+ * The bridge deep-merges the patch into `manifest.prefabs[<id>]` then
248
+ * re-applies the same fields to every live instance returned by
249
+ * `EntityRegistry.byPrefabId(id)`. Subsequent `spawnPrefab` calls pick up
250
+ * the new values automatically.
251
+ */
252
+ export interface EditorPrefabPatch {
253
+ /** Sprite. */
254
+ assetId?: string;
255
+ frame?: string | number;
256
+ tint?: string | null;
257
+ alpha?: number;
258
+ flipX?: boolean;
259
+ flipY?: boolean;
260
+ /** Primitive (rect / circle). */
261
+ width?: number;
262
+ height?: number;
263
+ radius?: number;
264
+ fillColor?: string;
265
+ strokeColor?: string | null;
266
+ strokeWidth?: number;
267
+ /** Code-rendered — entire params object replaces existing. */
268
+ params?: Record<string, unknown>;
269
+ /** Patched physics fields — body size / offset / immovable / velocity / bounce. */
270
+ physics?: {
271
+ bodyW?: number;
272
+ bodyH?: number;
273
+ offsetX?: number;
274
+ offsetY?: number;
275
+ immovable?: boolean;
276
+ velocityX?: number;
277
+ velocityY?: number;
278
+ collideWorldBounds?: boolean;
279
+ bounceX?: number;
280
+ bounceY?: number;
281
+ };
282
+ /** Patched per-type properties — shallow-merged onto `prefab.properties`. */
283
+ properties?: Record<string, unknown>;
284
+ /** Patched role — `null` clears it from the prefab record. */
285
+ role?: string | null;
286
+ }
287
+ /**
288
+ * Patch a single rule (slice 11 Phase B.5 / editor P0.3 — 2026-05-16).
289
+ *
290
+ * Sent by the host when the Rules form commits a field change. Bridge
291
+ * delegates to `patchRule(scene, path, value)` in `Rules.ts`, which:
292
+ * 1. Writes the path-value pair into the cached rules tree.
293
+ * 2. Fires every subscriber whose path is the patched path OR an
294
+ * ancestor of it (so a subscriber to `'balance'` sees an edit to
295
+ * `'balance.lives'`).
296
+ *
297
+ * `path` is dot-notation (`'balance.lives'`, `'physics.gravity.y'`).
298
+ * `value` can be any JSON-serializable shape — primitive, array, or
299
+ * nested object. The host's draft layer mirrors this patch into its
300
+ * pending rules tree so the eventual flush writes the merged tree to
301
+ * `public/rules.json`.
302
+ */
303
+ export interface EditorPatchRuleMessage {
304
+ type: 'umicat:editor:patchRule';
305
+ path: string;
306
+ value: unknown;
307
+ }
308
+ /**
309
+ * Snapshot of the cached rules tree posted by the SDK on enterEdit so
310
+ * the host's Rules panel can render the form without a separate fetch.
311
+ *
312
+ * Fires once per editor-enter, regardless of how many scenes are loaded
313
+ * (rules are game-level, not per-scene). Posts `{}` when the game has no
314
+ * `public/rules.json` or `preloadRules` was never called.
315
+ */
316
+ export interface EditorRulesLoadedMessage {
317
+ type: 'umicat:editor:rulesLoaded';
318
+ rules: Record<string, unknown>;
319
+ }
320
+ /**
321
+ * Edit a prefab record (slice 11 Phase B.5 / editor P0.2 — 2026-05-16).
322
+ *
323
+ * Sent by the host when the Prefab Inspector commits a change. The bridge:
324
+ * 1. Deep-merges the patch into the cached `manifest.prefabs[<id>]`.
325
+ * 2. Walks `EntityRegistry.byPrefabId(id)` and re-applies the patched
326
+ * visual fields (via the same paths `applyEdit` uses for world
327
+ * entities — color/tint/alpha/code-rendered params/etc.) + physics
328
+ * block (re-applies body size/offset/immovable/etc.) + property
329
+ * stamps (via `setData('entityProperties', merged)`).
330
+ *
331
+ * Next `spawnPrefab` call picks up the new values automatically — no
332
+ * special path needed; the merged record is the new source of truth.
333
+ */
334
+ export interface EditorEditPrefabMessage {
335
+ type: 'umicat:editor:editPrefab';
336
+ prefabId: string;
337
+ patch: EditorPrefabPatch;
338
+ }
339
+ /**
340
+ * Debug overlay toggle (slice 8 — "Show hitboxes" diagnostic).
341
+ *
342
+ * When the user toggles the scene-panel checkbox, the host sends this
343
+ * message. The overlay scene draws each sprite's hitbox rect (semi-
344
+ * transparent green) + depth-anchor crosshair (cyan ✚) for entities whose
345
+ * asset has those metadata fields set. Sprites without metadata get
346
+ * nothing drawn — chat-only Workflow A respect.
347
+ *
348
+ * Edit-mode only — complements Phaser's runtime `physics.world.createDebugGraphic`
349
+ * (which is the Play-mode tool), doesn't replace it.
350
+ */
351
+ export interface EditorSetDebugOverlayMessage {
352
+ type: 'umicat:editor:setDebugOverlay';
353
+ showHitboxes: boolean;
354
+ }
355
+ /**
356
+ * Tilemap painter — active tool/tile/layer state (slice 6 Phase B).
357
+ *
358
+ * Host pushes this when the user selects a tilemap entity, switches paint
359
+ * tool (B/E/R/G/I), picks a different tile in the palette, or picks a
360
+ * different layer in the Hierarchy. SDK caches the values + uses them to
361
+ * drive paint behavior on canvas pointer events. `tilemapEditingId: null`
362
+ * deactivates paint mode (clearing both the cached state + ending any
363
+ * in-flight stroke).
364
+ *
365
+ * Why split state instead of per-pointer round-trip: pointer events fire
366
+ * many times per second; round-tripping each one through postMessage would
367
+ * add visible latency. SDK paints locally + posts ONE `tilemapEdited` per
368
+ * completed stroke for undo recording — same pattern as drag-to-move.
369
+ */
370
+ export interface EditorSetTilemapToolMessage {
371
+ type: 'umicat:editor:setTilemapTool';
372
+ /**
373
+ * Tilemap entity being painted. Null clears paint mode (e.g. user
374
+ * selected a different entity kind, or explicitly exited tilemap mode).
375
+ */
376
+ tilemapEditingId: string | null;
377
+ /** Which layer (within the tilemap) the brush affects. Null when no layer chosen. */
378
+ activeLayerId: string | null;
379
+ /**
380
+ * Active paint tool. v1 surfaces brush + eraser (Phase B.3); rect/fill/picker
381
+ * (Phase B.4) land additively without protocol change.
382
+ */
383
+ tool: 'brush' | 'eraser' | 'rect' | 'fill' | 'picker';
384
+ /**
385
+ * Active tile index (0-based, row-major within the layer's tileset). Null
386
+ * means no tile picked yet — brush is no-op in that state. Eraser ignores
387
+ * this field.
388
+ */
389
+ activeTile: number | null;
390
+ /**
391
+ * Active Wang terrain id (slice 6 Phase D — design doc 06 §7). When
392
+ * non-null AND the active layer's tileset has matching autotile
393
+ * metadata, the brush/eraser paints with the terrain (cascade + ruleMap
394
+ * lookup) instead of stamping `activeTile` literally. Null = stamp-mode
395
+ * brush (existing Phase B behavior). Mutually meaningful with
396
+ * `activeTile`: stamp mode ignores terrainId; terrain mode ignores
397
+ * activeTile.
398
+ */
399
+ activeTerrainId?: string | null;
400
+ }
401
+ /**
402
+ * Explicit tilemap edit op pushed by the host (slice 6 Phase B).
403
+ *
404
+ * Used for: agent-authored writes ("paint a 5×5 grass patch"), undo/redo
405
+ * replay (host pushes the inverse op), and any future host-driven flow.
406
+ * Live painting via pointer events does NOT use this message — that path
407
+ * goes SDK→host (`tilemapEdited`) for undo recording.
408
+ */
409
+ export interface EditorEditTilemapMessage {
410
+ type: 'umicat:editor:editTilemap';
411
+ entityId: string;
412
+ ops: TilemapEditOp[];
413
+ /**
414
+ * Optional manifest asset to upsert + load BEFORE the ops apply.
415
+ * Required when any op in this batch references an asset id that
416
+ * isn't yet in the iframe's cached manifest (typical case: addLayer
417
+ * with a freshly-picked tileset via Add Layer modal). Without this,
418
+ * the SDK handler hits a cache miss + skips the addLayer entirely
419
+ * → painter finds no layer → click falls through to drag.
420
+ *
421
+ * Pattern mirrors `EditorCreateEntityMessage.manifestAsset` (slice 3
422
+ * drag-to-place). The handler is async; upserts cache + awaits
423
+ * `ensureAssetLoaded` before invoking applyTilemapStructureOp.
424
+ *
425
+ * Typed `unknown` to avoid importing the AssetRecord type into
426
+ * protocol.ts (which has no runtime imports otherwise) — the SDK
427
+ * handler casts to AssetRecord.
428
+ */
429
+ manifestAsset?: unknown;
430
+ }
431
+ /**
432
+ * A single tilemap mutation. Cell coords are tile-grid coords (not pixels).
433
+ * paint/erase/fillRect/bucketFill mutate cell data within a layer (Phase B.3
434
+ * + B.4); addLayer/removeLayer/setLayerVisibility mutate the layer list
435
+ * itself (Phase B.5).
436
+ */
437
+ export type TilemapEditOp = {
438
+ kind: 'paint';
439
+ layerId: string;
440
+ cells: Array<{
441
+ x: number;
442
+ y: number;
443
+ index: number;
444
+ }>;
445
+ } | {
446
+ kind: 'erase';
447
+ layerId: string;
448
+ cells: Array<{
449
+ x: number;
450
+ y: number;
451
+ }>;
452
+ } | {
453
+ kind: 'fillRect';
454
+ layerId: string;
455
+ x: number;
456
+ y: number;
457
+ w: number;
458
+ h: number;
459
+ /** When omitted, fillRect acts as an erase-rect. */
460
+ index?: number;
461
+ } | {
462
+ kind: 'bucketFill';
463
+ layerId: string;
464
+ x: number;
465
+ y: number;
466
+ index: number;
467
+ } | {
468
+ /**
469
+ * Wang 4-bit autotile paint (slice 6 Phase D — design doc 06 §7).
470
+ *
471
+ * Each entry in `cells` is a USER-CLICKED cell; the SDK runs the
472
+ * cascade across that cell's 3×3 neighborhood, sets the 4 vertices,
473
+ * recomputes each affected cell's bitmask, and writes the
474
+ * corresponding tile index from `terrain.ruleMap`.
475
+ *
476
+ * The accompanying `tilemapEdited` message's `previousCells` array
477
+ * carries every cell whose tile changed (the click cells AND their
478
+ * cascade neighbors) — that's how the host records the inverse
479
+ * stroke for undo. Replaying the inverse re-issues an
480
+ * `autotilePaint` with the same `cells` and `erase` toggled, so
481
+ * the vertex grid stays in sync after undo.
482
+ */
483
+ kind: 'autotilePaint';
484
+ layerId: string;
485
+ /** Wang terrain id, references `tileset.autotile.terrains[].id`. */
486
+ terrainId: string;
487
+ cells: Array<{
488
+ x: number;
489
+ y: number;
490
+ }>;
491
+ /**
492
+ * When true the cells are UNPAINTED (4 vertices cleared, cascade
493
+ * recomputes). Omitted / false = paint.
494
+ */
495
+ erase?: boolean;
496
+ /**
497
+ * Cascade-resolved cells with their final tile indices (D.2.5.2 fix).
498
+ *
499
+ * Populated by the SDK when posting `tilemapEdited` so the host can
500
+ * persist the autotile result without re-running cascade logic
501
+ * (which needs ruleMap + asset context the host doesn't have).
502
+ * One entry per cell mutated by the click + its cascade neighbors
503
+ * (3×3 around each click, deduped). `index = null` means the cell
504
+ * became empty (no tile in ruleMap for the resulting bitmask).
505
+ *
506
+ * Agent-written `editTilemap` ops can omit this — the SDK's
507
+ * `applyTilemapOp` recomputes via `applyAutotile`. Only required
508
+ * when the host materializes the op into `layer.data` (save flush).
509
+ */
510
+ resolvedCells?: Array<{
511
+ x: number;
512
+ y: number;
513
+ index: number | null;
514
+ }>;
515
+ } | {
516
+ /**
517
+ * Add a new layer to the tilemap (Phase B.5). The new layer is
518
+ * appended at the end of the layers array (renders on top of
519
+ * existing layers); explicit `z` in `layer` overrides this.
520
+ */
521
+ kind: 'addLayer';
522
+ layer: {
523
+ id: string;
524
+ name?: string;
525
+ tilesetIds?: string[];
526
+ z?: number;
527
+ visible?: boolean;
528
+ };
529
+ } | {
530
+ /** Remove a layer from the tilemap. The layer's data is captured in `previousCells` so undo can restore. */
531
+ kind: 'removeLayer';
532
+ layerId: string;
533
+ } | {
534
+ /** Toggle a layer's runtime visibility (and the editor-side checkbox). */
535
+ kind: 'setLayerVisibility';
536
+ layerId: string;
537
+ visible: boolean;
538
+ } | {
539
+ /**
540
+ * Rename a layer. Editor-only metadata — name has no runtime
541
+ * effect (SDK doesn't use it; layers are looked up by `id`).
542
+ * Persisted to scene JSON so Inspector + Hierarchy + future
543
+ * tooling show the user-chosen name across sessions.
544
+ */
545
+ kind: 'setLayerName';
546
+ layerId: string;
547
+ name: string;
548
+ } | {
549
+ /**
550
+ * Resize the tilemap (Phase B.6). Affects all layers atomically —
551
+ * each layer's data grid is clipped (shrink) or padded with -1
552
+ * (grow) to match the new dims. Tile cells at coords (x >= newW)
553
+ * or (y >= newH) are LOST on shrink — UI guards with a confirm
554
+ * when painted cells would be discarded.
555
+ *
556
+ * SDK runtime: destroys + recreates each TilemapLayer with the
557
+ * new dims (Phaser tilemap dims are immutable post-creation; the
558
+ * cleanest "resize" is a full layer rebuild). Background grid
559
+ * sketch is redrawn to match.
560
+ */
561
+ kind: 'resize';
562
+ width: number;
563
+ height: number;
564
+ /**
565
+ * Optional transform shift applied alongside resize (Phase B.6.1).
566
+ * Used by edge/corner handle drags to keep the opposite edge
567
+ * anchored. e.g. dragging the right edge to grow width: the LEFT
568
+ * edge must stay put → tilemap center.x shifts right by half the
569
+ * width delta → `transformDelta.x = delta/2`. Without this, the
570
+ * tilemap would grow symmetrically around center (Figma's symmetric
571
+ * resize via Alt-drag); we want one-edge-anchored as the default
572
+ * (matches Photoshop / Aseprite canvas resize).
573
+ *
574
+ * Number-input resize (B.6) sends no transformDelta → symmetric
575
+ * grow/shrink around center. Handle-drag resize (B.6.1) computes
576
+ * and sends transformDelta to anchor the opposite edge.
577
+ */
578
+ transformDelta?: {
579
+ x: number;
580
+ y: number;
581
+ };
582
+ };
583
+ export type EditorHostToSdkMessage = EditorEnterMessage | EditorExitMessage | EditorGetSceneMessage | EditorApplyEditMessage | EditorSetSelectionMessage | EditorPanZoomMessage | EditorCreateEntityMessage | EditorDeleteEntityMessage | EditorSetEditModeMessage | EditorAssetUpdateMessage | EditorSetDebugOverlayMessage | EditorEditPrefabMessage | EditorPatchRuleMessage | EditorSetTilemapToolMessage | EditorEditTilemapMessage;
584
+ export interface EditorSceneLoadedMessage {
585
+ type: 'umicat:editor:sceneLoaded';
586
+ sceneId: string;
587
+ /**
588
+ * Discriminator (slice 5+). 'world' (default for back-compat) is the
589
+ * world scene; 'hud' is the HUD overlay scene. SDK posts one message per
590
+ * scene type when entering edit mode, so the host can populate both
591
+ * Hierarchy / Inspector drafts and switch between them via the
592
+ * World/HUD toggle without a fresh SDK round-trip.
593
+ */
594
+ mode?: 'world' | 'hud';
595
+ /** Snapshot of the scene file's current entities. */
596
+ sceneFile: unknown;
597
+ /**
598
+ * Snapshot of the manifest (asset table + scene list). Slice 3+ — home-ui
599
+ * needs this to mutate the manifest when drag-to-place adds a new asset.
600
+ */
601
+ manifest?: unknown;
602
+ }
603
+ export interface EditorSelectionPickedMessage {
604
+ /** Pointer-down on an entity in the canvas — host should update selection. */
605
+ type: 'umicat:editor:pickEntity';
606
+ entityId: string | null;
607
+ /**
608
+ * When the picked entity is a runtime-spawned prefab instance (tagged
609
+ * `entityPrefabId` by `spawnPrefab`), the source prefab id — slice 11 B.5
610
+ * / editor P0.2. Host uses this to route the click to the Prefab
611
+ * Inspector instead of the Entity Inspector. Editing one instance is
612
+ * editing the prefab type (= all instances), so the visible Inspector
613
+ * scope matches the change scope.
614
+ *
615
+ * Absent on authored scene entities (those have a real record in
616
+ * `sceneFile.entities[]` and edit per-instance).
617
+ */
618
+ prefabId?: string;
619
+ /** Modifier keys at pointer-down so host can decide single/toggle/range. */
620
+ modifiers: {
621
+ shift: boolean;
622
+ cmdOrCtrl: boolean;
623
+ alt: boolean;
624
+ };
625
+ }
626
+ export interface EditorDragEndMessage {
627
+ /** Pointer-up after dragging an entity. delta is total movement in world coords. */
628
+ type: 'umicat:editor:dragEnd';
629
+ entityId: string;
630
+ /** Position before drag started. */
631
+ before: {
632
+ x: number;
633
+ y: number;
634
+ };
635
+ /** Position at release. */
636
+ after: {
637
+ x: number;
638
+ y: number;
639
+ };
640
+ }
641
+ /**
642
+ * Editor keyboard shortcut intercepted inside the iframe (user clicked the
643
+ * canvas, so focus is on the iframe and the host window doesn't see the
644
+ * native keydown). The SDK forwards a small allowlist back to the host.
645
+ */
646
+ export interface EditorShortcutMessage {
647
+ type: 'umicat:editor:shortcut';
648
+ action: 'undo' | 'redo' | 'save' | 'delete';
649
+ }
650
+ /**
651
+ * The selected entity's DOM screen rect (slice 4 — popover anchoring).
652
+ *
653
+ * Emitted on selection change, drag-end, and pan/zoom — NOT per frame. The
654
+ * host uses this to anchor the floating ✨ button + InlineAIPopover next
655
+ * to the entity. Coords are relative to the iframe element (not the
656
+ * entity's world coords); the host adds the iframe's own bounding rect to
657
+ * land in viewport space. Sent with `null` when nothing is selected, so
658
+ * the host can hide the ✨ button.
659
+ *
660
+ * Slice 4 only handles single-select. Multi-select (slice 4.5) will pass
661
+ * the union bounding box.
662
+ */
663
+ export interface EditorSelectionRectMessage {
664
+ type: 'umicat:editor:selectionRect';
665
+ entityId: string | null;
666
+ /** null when nothing is selected. */
667
+ rect: {
668
+ x: number;
669
+ y: number;
670
+ width: number;
671
+ height: number;
672
+ } | null;
673
+ }
674
+ /**
675
+ * Editor camera state (P1 infinite canvas — 2026-05-17).
676
+ *
677
+ * Posted by the SDK after every pan / zoom action — either host-driven
678
+ * (via `umicat:editor:panZoom`) or SDK-driven (mouse-wheel zoom in the
679
+ * canvas, middle-button drag, space+drag). The host uses it to render
680
+ * the zoom percentage indicator + "reset to 100%" button in the iframe
681
+ * toolbar, and to compute which entities are off-screen for the
682
+ * Hierarchy panel's dim-when-outside-viewport hint.
683
+ *
684
+ * `viewportWorld` is the world-coord rectangle currently visible inside
685
+ * the iframe — `scrollX / scrollY` plus the canvas dimensions divided
686
+ * by zoom. Convenient for the host so it doesn't have to know canvas
687
+ * size or zoom math.
688
+ *
689
+ * Fired only in world mode — HUD has an identity camera; pan/zoom is
690
+ * not meaningful there.
691
+ */
692
+ export interface EditorCameraStateMessage {
693
+ type: 'umicat:editor:cameraState';
694
+ zoom: number;
695
+ scrollX: number;
696
+ scrollY: number;
697
+ viewportWorld: {
698
+ x: number;
699
+ y: number;
700
+ width: number;
701
+ height: number;
702
+ };
703
+ }
704
+ /**
705
+ * Tilemap stroke completed (slice 6 Phase B — pointer-up after brush /
706
+ * eraser / rect / fill).
707
+ *
708
+ * Sent by the SDK after every painter stroke so the host can:
709
+ * 1. Push a command onto the undo stack (whole stroke = ONE undo step)
710
+ * 2. Mutate the cached scene draft so the next flush persists the change
711
+ *
712
+ * The SDK already applied the op locally before posting — so the iframe is
713
+ * already showing the painted cells. This message exists for undo +
714
+ * persistence, NOT live preview (mirrors how `dragEnd` works).
715
+ */
716
+ export interface EditorTilemapEditedMessage {
717
+ type: 'umicat:editor:tilemapEdited';
718
+ entityId: string;
719
+ /** The forward op (replay on redo, reverse-derive on undo). */
720
+ op: TilemapEditOp;
721
+ /**
722
+ * Per-cell prior values that the op overwrote — host stores these to
723
+ * build the inverse op for undo. For paint/erase, indices match `op.cells`;
724
+ * for fillRect/bucketFill, this is a flat list of every cell the op
725
+ * changed (in row-major order from top-left of the affected region).
726
+ */
727
+ previousCells: Array<{
728
+ x: number;
729
+ y: number;
730
+ index: number | null;
731
+ }>;
732
+ }
733
+ /**
734
+ * Tile picked from the canvas (slice 6 Phase B.4 — Picker / eyedropper tool).
735
+ *
736
+ * Fires on pointerdown when the active tool is `picker`. The SDK reads the
737
+ * cell's current tile index and posts this message; the host's painter UI
738
+ * sets it as the active tile + auto-switches the tool back to brush (matches
739
+ * Photoshop / Aseprite eyedropper convention — pick once, paint next).
740
+ *
741
+ * `tileIndex: null` means the picked cell is empty.
742
+ */
743
+ export interface EditorTilemapTilePickedMessage {
744
+ type: 'umicat:editor:tilemapTilePicked';
745
+ entityId: string;
746
+ layerId: string;
747
+ tileIndex: number | null;
748
+ }
749
+ export type EditorSdkToHostMessage = EditorSceneLoadedMessage | EditorSelectionPickedMessage | EditorDragEndMessage | EditorShortcutMessage | EditorSelectionRectMessage | EditorRulesLoadedMessage | EditorCameraStateMessage | EditorTilemapEditedMessage | EditorTilemapTilePickedMessage;
750
+ export type RpcMethod = 'saves.get' | 'saves.set' | 'saves.delete' | 'saves.list' | 'gameData.get' | 'gameData.set' | 'gameData.delete' | 'gameData.list' | 'realtime.getToken';
751
+ export interface SavesGetParams {
752
+ key: string;
753
+ }
754
+ export interface SavesGetResult {
755
+ value: unknown | null;
756
+ version: number | null;
757
+ }
758
+ export interface SavesSetParams {
759
+ key: string;
760
+ value: unknown;
761
+ ifVersion?: number;
762
+ }
763
+ export interface SavesSetResult {
764
+ version: number;
765
+ }
766
+ export interface SavesDeleteParams {
767
+ key: string;
768
+ }
769
+ export type SavesDeleteResult = {
770
+ deleted: boolean;
771
+ };
772
+ export interface SavesListResult {
773
+ keys: string[];
774
+ }
775
+ export interface GameDataGetParams {
776
+ key: string;
777
+ }
778
+ export interface GameDataGetResult {
779
+ value: unknown | null;
780
+ version: number | null;
781
+ }
782
+ export interface GameDataSetParams {
783
+ key: string;
784
+ value: unknown;
785
+ ifVersion?: number;
786
+ }
787
+ export interface GameDataSetResult {
788
+ version: number;
789
+ }
790
+ export interface GameDataDeleteParams {
791
+ key: string;
792
+ }
793
+ export type GameDataDeleteResult = {
794
+ deleted: boolean;
795
+ };
796
+ export interface GameDataListResult {
797
+ keys: string[];
798
+ }
799
+ export interface RealtimeGetTokenParams {
800
+ /** Opaque, forwarded to server-side auth (future use). */
801
+ purpose?: string;
802
+ }
803
+ export interface RealtimeGetTokenResult {
804
+ token: string;
805
+ /** Epoch millis when the token expires. */
806
+ expiresAt: number;
807
+ }