@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.
- package/SDK-GUIDE.md +1726 -0
- package/dist/core/Transport.d.ts +28 -0
- package/dist/core/Transport.js +7 -0
- package/dist/core/Umicat.d.ts +45 -0
- package/dist/core/Umicat.js +60 -0
- package/dist/core/UmicatGame.d.ts +43 -0
- package/dist/core/UmicatGame.js +64 -0
- package/dist/core/UmicatScene.d.ts +19 -0
- package/dist/core/UmicatScene.js +38 -0
- package/dist/core/transports/LocalStorageTransport.d.ts +22 -0
- package/dist/core/transports/LocalStorageTransport.js +78 -0
- package/dist/core/transports/PostMessageTransport.d.ts +28 -0
- package/dist/core/transports/PostMessageTransport.js +105 -0
- package/dist/editor/EditorBridge.d.ts +114 -0
- package/dist/editor/EditorBridge.js +2608 -0
- package/dist/editor/EditorOverlayScene.d.ts +333 -0
- package/dist/editor/EditorOverlayScene.js +1896 -0
- package/dist/editor/EditorState.d.ts +251 -0
- package/dist/editor/EditorState.js +197 -0
- package/dist/gamedata/GameDataModule.d.ts +45 -0
- package/dist/gamedata/GameDataModule.js +59 -0
- package/dist/index.d.ts +43 -0
- package/dist/index.js +43 -0
- package/dist/orientation.d.ts +5 -0
- package/dist/orientation.js +4 -0
- package/dist/protocol.d.ts +807 -0
- package/dist/protocol.js +3 -0
- package/dist/realtime/RealtimeModule.d.ts +93 -0
- package/dist/realtime/RealtimeModule.js +115 -0
- package/dist/realtime/UmicatRoom.d.ts +197 -0
- package/dist/realtime/UmicatRoom.js +353 -0
- package/dist/recording/RecordingManager.d.ts +11 -0
- package/dist/recording/RecordingManager.js +59 -0
- package/dist/saves/SavesModule.d.ts +23 -0
- package/dist/saves/SavesModule.js +37 -0
- package/dist/scene/EditorMode.d.ts +17 -0
- package/dist/scene/EditorMode.js +22 -0
- package/dist/scene/EntityRegistry.d.ts +39 -0
- package/dist/scene/EntityRegistry.js +103 -0
- package/dist/scene/GameConfig.d.ts +60 -0
- package/dist/scene/GameConfig.js +50 -0
- package/dist/scene/HudRuntime.d.ts +131 -0
- package/dist/scene/HudRuntime.js +1224 -0
- package/dist/scene/Prefabs.d.ts +92 -0
- package/dist/scene/Prefabs.js +175 -0
- package/dist/scene/Rules.d.ts +73 -0
- package/dist/scene/Rules.js +164 -0
- package/dist/scene/SceneLoader.d.ts +118 -0
- package/dist/scene/SceneLoader.js +615 -0
- package/dist/scene/Waves.d.ts +85 -0
- package/dist/scene/Waves.js +365 -0
- package/dist/scene/autotile.d.ts +103 -0
- package/dist/scene/autotile.js +321 -0
- package/dist/scene/renderScripts.d.ts +53 -0
- package/dist/scene/renderScripts.js +67 -0
- package/dist/scene/spawnEntity.d.ts +201 -0
- package/dist/scene/spawnEntity.js +1326 -0
- package/dist/scene/types.d.ts +1166 -0
- package/dist/scene/types.js +34 -0
- package/dist/screenshot/ScreenshotManager.d.ts +14 -0
- package/dist/screenshot/ScreenshotManager.js +33 -0
- package/package.json +35 -0
|
@@ -0,0 +1,1166 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Scene-as-data types — visual editor foundation (slice 1).
|
|
3
|
+
*
|
|
4
|
+
* Spec: umicat-design/features/visual-editor/01-scene-data-foundation.md
|
|
5
|
+
*
|
|
6
|
+
* v1 schema (`schemaVersion: 1`) supports the entity kinds needed for the
|
|
7
|
+
* read-only scene viewer. HUD entity kinds, tilemap, trigger, and
|
|
8
|
+
* code-rendered visuals will land in later slices.
|
|
9
|
+
*/
|
|
10
|
+
export declare const SCHEMA_VERSION = 1;
|
|
11
|
+
export type SceneType = 'world' | 'hud';
|
|
12
|
+
export interface SceneRef {
|
|
13
|
+
id: string;
|
|
14
|
+
type: SceneType;
|
|
15
|
+
/** Path under `public/scenes/`, e.g. `"world/level-1-1.json"`. */
|
|
16
|
+
file: string;
|
|
17
|
+
/** Optional HUD scene id to overlay (world scenes only). Null = no HUD. */
|
|
18
|
+
hud?: string | null;
|
|
19
|
+
}
|
|
20
|
+
export interface HudRef {
|
|
21
|
+
id: string;
|
|
22
|
+
/** Path under `public/scenes/`, e.g. `"hud/game-hud.json"`. */
|
|
23
|
+
file: string;
|
|
24
|
+
}
|
|
25
|
+
/**
|
|
26
|
+
* Per-game asset table. Source of truth for asset id → Phaser textureKey
|
|
27
|
+
* resolution. The editor and runtime both read from this.
|
|
28
|
+
*/
|
|
29
|
+
export type AssetKind = 'image' | 'spritesheet' | 'atlas' | 'audio' | 'json';
|
|
30
|
+
export interface AssetRecord {
|
|
31
|
+
/** Stable asset id used in scene files (`sprite.assetId` / `image.assetId`). */
|
|
32
|
+
id: string;
|
|
33
|
+
/** Phaser texture / cache key. May equal `id` for simple cases. */
|
|
34
|
+
textureKey: string;
|
|
35
|
+
/** Path relative to `public/`. */
|
|
36
|
+
path: string;
|
|
37
|
+
kind: AssetKind;
|
|
38
|
+
/** For `spritesheet`: Phaser SpriteSheetConfig. */
|
|
39
|
+
spriteSheetConfig?: {
|
|
40
|
+
frameWidth: number;
|
|
41
|
+
frameHeight: number;
|
|
42
|
+
margin?: number;
|
|
43
|
+
spacing?: number;
|
|
44
|
+
};
|
|
45
|
+
/**
|
|
46
|
+
* Top-level convenience fields. When the agent writes a spritesheet asset
|
|
47
|
+
* entry, it commonly puts these at the top level instead of nested in
|
|
48
|
+
* `spriteSheetConfig` — both shapes are now accepted by the loader.
|
|
49
|
+
*/
|
|
50
|
+
frameWidth?: number;
|
|
51
|
+
frameHeight?: number;
|
|
52
|
+
margin?: number;
|
|
53
|
+
spacing?: number;
|
|
54
|
+
/** For `atlas`: companion atlas file path (xml or json). */
|
|
55
|
+
atlasPath?: string;
|
|
56
|
+
/** `'xml'` (Starling) or `'json'` (TexturePacker). */
|
|
57
|
+
atlasFormat?: 'xml' | 'json';
|
|
58
|
+
/**
|
|
59
|
+
* For `spritesheet`: named animation ranges. SDK auto-registers these as
|
|
60
|
+
* Phaser anims at preload, so behavior code can call
|
|
61
|
+
* `sprite.play('walk-down')` without manual `anims.create()` setup.
|
|
62
|
+
*
|
|
63
|
+
* <p>Pack import populates this from vision detection (4-direction RPG /
|
|
64
|
+
* sidescroller / multi-action layouts) or from Aseprite `frameTags`. The
|
|
65
|
+
* agent can also add/edit entries by hand when wiring a sheet asset.
|
|
66
|
+
*
|
|
67
|
+
* <p>Animation keys are scene-global in Phaser; if two sheets register
|
|
68
|
+
* the same name, the second registration is skipped with a warning.
|
|
69
|
+
* Disambiguate by namespacing the name (e.g. `cow-walk-down`) when
|
|
70
|
+
* collisions matter.
|
|
71
|
+
*/
|
|
72
|
+
animations?: SheetAnimation[];
|
|
73
|
+
/** Default playback rate for `animations[]`. Defaults to 8. */
|
|
74
|
+
fps?: number;
|
|
75
|
+
/**
|
|
76
|
+
* 9-slice cut-line metadata. Two shapes accepted:
|
|
77
|
+
*
|
|
78
|
+
* - **Single image** (slice 7.5) — `NinePatchConfig` directly. Used on
|
|
79
|
+
* `kind: 'image'` assets like a single button/panel background. All four
|
|
80
|
+
* corner widths apply to the image as a whole.
|
|
81
|
+
* - **Per-frame on a uniform grid** (slice 7.6) — `{ perFrame: NinePatchConfig }`.
|
|
82
|
+
* Used on `kind: 'spritesheet'` assets where every cell is its own
|
|
83
|
+
* stretchable button/panel sharing the same corner radius (typical
|
|
84
|
+
* itch.io UI button packs, e.g. `Square Buttons 26x26.png`). The HUD
|
|
85
|
+
* widget references which cell to render via `backgroundFrame`.
|
|
86
|
+
*
|
|
87
|
+
* Per-cell keyed config (`{ perFrame: Record<number, NinePatchConfig> }`)
|
|
88
|
+
* is deferred to v3 per design 07 §7.9.5 — promote only when a real pack
|
|
89
|
+
* needs different corner radii on different cells.
|
|
90
|
+
*/
|
|
91
|
+
ninePatch?: NinePatchConfig | NinePatchPerFrame;
|
|
92
|
+
/**
|
|
93
|
+
* Vision-detected pixel-art flag. When true, SDK applies
|
|
94
|
+
* `Phaser.Textures.FilterMode.NEAREST` to the texture after load so it
|
|
95
|
+
* renders crisp instead of bilinear-blurred. Population is per-source:
|
|
96
|
+
* pack-import sets it via vision; AI generation / manual upload / library
|
|
97
|
+
* backfill close their own gaps separately (see design doc 07 §8.4).
|
|
98
|
+
*/
|
|
99
|
+
pixelArt?: boolean;
|
|
100
|
+
/**
|
|
101
|
+
* Per-asset collision body metadata (slice 8 — see design doc 09).
|
|
102
|
+
*
|
|
103
|
+
* Two shapes:
|
|
104
|
+
* - `HitboxRect` — single rect shared across every frame (top-down
|
|
105
|
+
* characters with constant foot footprint, trees, buildings, walls).
|
|
106
|
+
* - `HitboxPerFrame` — `default` rect plus per-frame overrides indexed by
|
|
107
|
+
* frame number. Used for melee combat where an attack-swing frame's
|
|
108
|
+
* hitbox extends to where the tool reaches (Sprout Lands chop-the-tree
|
|
109
|
+
* case). The SDK installs an `ANIMATION_UPDATE` listener that swaps
|
|
110
|
+
* `body.setSize/setOffset` per frame during animation playback.
|
|
111
|
+
*
|
|
112
|
+
* v1 ships `kind: 'rect'` only. v2 adds `'circle'` as a pure forward-compat
|
|
113
|
+
* extension — see design doc 09 §9.2.1.
|
|
114
|
+
*
|
|
115
|
+
* Consumed by `applyAssetHitbox(sprite, asset)` at spawn time AND callable
|
|
116
|
+
* by behavior code after attaching a body. No-op if the sprite has no body.
|
|
117
|
+
*/
|
|
118
|
+
hitbox?: HitboxRect | HitboxPerFrame;
|
|
119
|
+
/**
|
|
120
|
+
* Tileset metadata (slice 6 — see design doc 06).
|
|
121
|
+
*
|
|
122
|
+
* Present on assets used as tilemap tilesets. When set, the SDK knows
|
|
123
|
+
* the source-pixel cell size so `Phaser.Tilemap.addTilesetImage` can
|
|
124
|
+
* slice the image correctly. Phase A ships `cellSize` only; Phase C adds
|
|
125
|
+
* per-tile metadata (collision / damage / terrainTag); Phase D adds
|
|
126
|
+
* `autotile`; Phase F adds `animations`. All forward-compat — new
|
|
127
|
+
* optional sub-fields, never breaking the existing shape.
|
|
128
|
+
*
|
|
129
|
+
* Population paths:
|
|
130
|
+
* - Pack import wizard: vision-detected from uniform-grid sheets +
|
|
131
|
+
* "looks like a tileset" heuristic. Cell size auto-filled.
|
|
132
|
+
* - Configure Tileset modal: user picks a single-image asset as a
|
|
133
|
+
* tileset; modal prompts for cell size + persists onto this field.
|
|
134
|
+
*
|
|
135
|
+
* Note: a tileset asset typically has `kind: 'image'` (single PNG) — the
|
|
136
|
+
* `tileset` field is what marks it as "available as a tileset" in the
|
|
137
|
+
* editor's tilemap picker. Sprite-sheet assets (`kind: 'spritesheet'`)
|
|
138
|
+
* already carry frame dims via `spriteSheetConfig`; the SDK falls back
|
|
139
|
+
* to those when a layer's tileset is a spritesheet without a separate
|
|
140
|
+
* `tileset.cellSize`. See `resolveTilesetCellSize` in spawnEntity.ts.
|
|
141
|
+
*/
|
|
142
|
+
tileset?: TilesetMetadata;
|
|
143
|
+
/**
|
|
144
|
+
* Asset-wide footprint anchor (slice 8 — see design doc 09 §2).
|
|
145
|
+
*
|
|
146
|
+
* Pixel within the asset's frame (top-left = 0,0) that defines where the
|
|
147
|
+
* sprite "touches the ground." SDK applies as
|
|
148
|
+
* `sprite.setOrigin(x / frameW, y / frameH)` at spawn, so `sprite.y` in
|
|
149
|
+
* world space equals the world y of the anchor pixel — perfect for ySort
|
|
150
|
+
* (`sprite.depth = sprite.y`).
|
|
151
|
+
*
|
|
152
|
+
* Typical values: character feet (e.g. `(16, 28)` on a 32×32 cell), tree
|
|
153
|
+
* trunk base, building foundation midpoint. Decorations / centered objects
|
|
154
|
+
* leave unset → Phaser default origin (0.5, 0.5) → ySort by geometric
|
|
155
|
+
* center (acceptable fallback; the scene panel surfaces a warning when
|
|
156
|
+
* ySort is on and some sprite assets lack anchor).
|
|
157
|
+
*/
|
|
158
|
+
depthAnchor?: DepthAnchor;
|
|
159
|
+
}
|
|
160
|
+
/** Single-image 9-slice config. Pixels from each edge to the slice line. */
|
|
161
|
+
export interface NinePatchConfig {
|
|
162
|
+
leftWidth: number;
|
|
163
|
+
rightWidth: number;
|
|
164
|
+
topHeight: number;
|
|
165
|
+
bottomHeight: number;
|
|
166
|
+
}
|
|
167
|
+
/**
|
|
168
|
+
* Per-frame 9-slice config (slice 7.6). One shared `NinePatchConfig` applied
|
|
169
|
+
* to every frame of a uniform-grid spritesheet. Covers ~95% of itch.io UI
|
|
170
|
+
* button packs, where each cell is a different button color/state but all
|
|
171
|
+
* share the same corner radius.
|
|
172
|
+
*/
|
|
173
|
+
export interface NinePatchPerFrame {
|
|
174
|
+
perFrame: NinePatchConfig;
|
|
175
|
+
}
|
|
176
|
+
/**
|
|
177
|
+
* Type guard — true when `ninePatch` is the per-frame variant. Lets call
|
|
178
|
+
* sites that only need to handle one shape stay narrow.
|
|
179
|
+
*/
|
|
180
|
+
export declare function isPerFrameNinePatch(np: NinePatchConfig | NinePatchPerFrame | undefined): np is NinePatchPerFrame;
|
|
181
|
+
/**
|
|
182
|
+
* Rectangular hitbox in source-pixel coordinates (top-left = 0,0 within the
|
|
183
|
+
* asset's frame). Consumed by the SDK at sprite spawn time as
|
|
184
|
+
* `body.setSize(w, h)` + `body.setOffset(x, y)` when a physics body exists.
|
|
185
|
+
*
|
|
186
|
+
* v1 is the only supported `kind`. v2 adds `'circle'` (see design doc 09
|
|
187
|
+
* §9.2.1 for the full forward-compat migration story); polygon is v3+.
|
|
188
|
+
*/
|
|
189
|
+
export interface HitboxRect {
|
|
190
|
+
kind: 'rect';
|
|
191
|
+
/** Top-left x of the hitbox in source pixels. */
|
|
192
|
+
x: number;
|
|
193
|
+
/** Top-left y of the hitbox in source pixels. */
|
|
194
|
+
y: number;
|
|
195
|
+
w: number;
|
|
196
|
+
h: number;
|
|
197
|
+
}
|
|
198
|
+
/**
|
|
199
|
+
* v2 — placeholder doc only. NOT in v1 schema. When v2 adds circle, this
|
|
200
|
+
* interface lands and the `AssetRecord.hitbox` + `HitboxPerFrame.default` /
|
|
201
|
+
* `.frames` unions widen to include it. v1 backend validation whitelists
|
|
202
|
+
* `kind in ['rect']`; v2 widens the whitelist. All v1 data stays valid.
|
|
203
|
+
*
|
|
204
|
+
* interface HitboxCircle {
|
|
205
|
+
* kind: 'circle';
|
|
206
|
+
* x: number; // center x in source-pixel coords of frame 0
|
|
207
|
+
* y: number; // center y in source-pixel coords of frame 0
|
|
208
|
+
* radius: number;
|
|
209
|
+
* }
|
|
210
|
+
*/
|
|
211
|
+
/**
|
|
212
|
+
* Per-frame hitbox configuration. The `default` shape applies to every frame
|
|
213
|
+
* not present in `frames`; entries in `frames` override per frame index.
|
|
214
|
+
*
|
|
215
|
+
* This default+overrides model matches how combat sheets decompose — most
|
|
216
|
+
* frames share one hitbox (idle / walk / hurt at the feet), only attack-swing
|
|
217
|
+
* frames need a wider shape that reaches to where the tool / weapon visually
|
|
218
|
+
* extends. The motivating Sprout Lands case: 16-frame character sheet where
|
|
219
|
+
* frames 8–11 are an axe swing reaching ~16 pixels sideways past the foot.
|
|
220
|
+
*
|
|
221
|
+
* Discriminator: `'default' in hitbox` (HitboxRect has `kind` instead).
|
|
222
|
+
*/
|
|
223
|
+
export interface HitboxPerFrame {
|
|
224
|
+
default: HitboxRect;
|
|
225
|
+
/** Frame index → override shape. Frames absent fall back to `default`. */
|
|
226
|
+
frames: Record<number, HitboxRect>;
|
|
227
|
+
}
|
|
228
|
+
/**
|
|
229
|
+
* Type guard — true when `hitbox` is the per-frame variant. Used by SDK
|
|
230
|
+
* runtime to decide whether to install an `ANIMATION_UPDATE` listener that
|
|
231
|
+
* swaps body shape per frame.
|
|
232
|
+
*
|
|
233
|
+
* <p>Checks `default != null` (handles both `null` and `undefined`) rather
|
|
234
|
+
* than `'default' in h`. The backend stores hitboxes after a mode switch
|
|
235
|
+
* with EXPLICIT NULLS on the alternate shape's fields — `{ kind: 'rect',
|
|
236
|
+
* x, y, w, h, default: null, frames: null }` for Single mode — because
|
|
237
|
+
* OpenSearch's _update API deep-merges nested objects and would otherwise
|
|
238
|
+
* leave stale fields. `'default' in h` would return true on that shape and
|
|
239
|
+
* trip the per-frame path with a null default. The null check is the
|
|
240
|
+
* correct discriminator.
|
|
241
|
+
*/
|
|
242
|
+
export declare function isPerFrameHitbox(h: HitboxRect | HitboxPerFrame | undefined): h is HitboxPerFrame;
|
|
243
|
+
/**
|
|
244
|
+
* Depth anchor — pixel within the asset's frame (top-left = 0,0) that defines
|
|
245
|
+
* the sprite's footprint position. The SDK applies it at spawn as
|
|
246
|
+
* `sprite.setOrigin(x / frameW, y / frameH)` so the sprite's world `y` aligns
|
|
247
|
+
* with the world position of this pixel. ySort then compares foot positions
|
|
248
|
+
* directly via `sprite.depth = sprite.y` without per-asset offset math.
|
|
249
|
+
*
|
|
250
|
+
* Asset-wide — shared across all frames of a spritesheet. Per-frame variation
|
|
251
|
+
* is not in scope (footprint position doesn't change across animations even
|
|
252
|
+
* when hitbox shape does).
|
|
253
|
+
*/
|
|
254
|
+
export interface DepthAnchor {
|
|
255
|
+
x: number;
|
|
256
|
+
y: number;
|
|
257
|
+
}
|
|
258
|
+
/**
|
|
259
|
+
* Tileset metadata for tilemap rendering (slice 6 — design doc 06).
|
|
260
|
+
*
|
|
261
|
+
* Carried on `AssetRecord.tileset`. Phase A ships the cell-size + layout
|
|
262
|
+
* fields needed to render a Phaser tilemap; later phases extend this
|
|
263
|
+
* shape additively (per-tile metadata, Wang autotile rules, animations).
|
|
264
|
+
*/
|
|
265
|
+
export interface TilesetMetadata {
|
|
266
|
+
/**
|
|
267
|
+
* Source-image cell size in pixels. Each cell becomes one tile in the
|
|
268
|
+
* Phaser tileset (`map.addTilesetImage(id, key, cellSize.w, cellSize.h)`).
|
|
269
|
+
* Required — without this the SDK can't slice the source image.
|
|
270
|
+
*/
|
|
271
|
+
cellSize: {
|
|
272
|
+
width: number;
|
|
273
|
+
height: number;
|
|
274
|
+
};
|
|
275
|
+
/**
|
|
276
|
+
* Optional column count of the cell grid. Used by the editor's tile
|
|
277
|
+
* palette and as a sanity check during slice; derivable from
|
|
278
|
+
* `texture.width / cellSize.width` when omitted.
|
|
279
|
+
*/
|
|
280
|
+
cols?: number;
|
|
281
|
+
/** Optional row count of the cell grid. See `cols`. */
|
|
282
|
+
rows?: number;
|
|
283
|
+
/**
|
|
284
|
+
* Optional margin (pixels) around the entire tileset image — pixels
|
|
285
|
+
* from the image edge before the first cell starts. Defaults to 0.
|
|
286
|
+
*/
|
|
287
|
+
margin?: number;
|
|
288
|
+
/**
|
|
289
|
+
* Optional spacing (pixels) between adjacent cells. Defaults to 0.
|
|
290
|
+
*/
|
|
291
|
+
spacing?: number;
|
|
292
|
+
/**
|
|
293
|
+
* Per-tile metadata keyed by tile index (0-based, row-major in the tileset
|
|
294
|
+
* image). Sparse — only tiles with non-default metadata are stored.
|
|
295
|
+
* Authored via the Tile Metadata Editor modal (right-click a cell in the
|
|
296
|
+
* tile palette). Slice 6 Phase C — design doc 06 §5.
|
|
297
|
+
*
|
|
298
|
+
* Runtime wiring:
|
|
299
|
+
* - `solid: true` → SDK auto-flags via
|
|
300
|
+
* `layer.setCollisionByProperty({ solid: true })` at scene load.
|
|
301
|
+
* - other fields readable-but-not-auto-applied — game code reads via
|
|
302
|
+
* the SDK's `getTilemapAt(scene, entityId, x, y)` helper and decides.
|
|
303
|
+
*/
|
|
304
|
+
tiles?: {
|
|
305
|
+
[tileIndex: number]: TileMetadata;
|
|
306
|
+
};
|
|
307
|
+
/**
|
|
308
|
+
* Wang 4-bit autotile configuration (slice 6 Phase D — design doc 06 §7).
|
|
309
|
+
*
|
|
310
|
+
* Present when this tileset supports terrain-mode painting. Multi-terrain
|
|
311
|
+
* baseline: one tileset can carry N terrains (grass + dirt-cliff + stone),
|
|
312
|
+
* each with its own rule map (§7.5). Authored via the manual rule-map
|
|
313
|
+
* editor (Phase D.3) or auto-populated by vision on pack import (Phase
|
|
314
|
+
* D.4); SDK runtime (Phase D.2) reads `terrains[i].ruleMap` at paint time
|
|
315
|
+
* to pick the right tile index from the 4-corner bitmask.
|
|
316
|
+
*/
|
|
317
|
+
autotile?: TilesetAutotile;
|
|
318
|
+
/**
|
|
319
|
+
* Animated tiles (slice 6 Phase F — design doc 06 §8). Each entry
|
|
320
|
+
* defines an animation cycle keyed on a "root" tile index. At runtime
|
|
321
|
+
* the SDK installs a per-frame update hook that swaps every painted
|
|
322
|
+
* cell whose source index === `rootTileIndex` through the animation's
|
|
323
|
+
* frame sequence, all cells synchronously (water-flow / lava-bubble /
|
|
324
|
+
* torch-flicker pattern).
|
|
325
|
+
*
|
|
326
|
+
* Painted data stores the root tile index; the SDK overlays the
|
|
327
|
+
* current-frame index at render time. On enterEdit, animated cells are
|
|
328
|
+
* reset back to root so the editor always shows the static "data"
|
|
329
|
+
* frame, not whatever was currently visible at the moment of toggle.
|
|
330
|
+
* Save paths read from the host-side draft (not the iframe's mutated
|
|
331
|
+
* tile state), so saving mid-animation is safe.
|
|
332
|
+
*
|
|
333
|
+
* Phaser's TilemapLayer does NOT auto-render Tiled `tile.animation`
|
|
334
|
+
* data — it only parses it. The SDK drives frame swaps via a scene
|
|
335
|
+
* UPDATE listener; design 06 §8.8's claim of "Phaser handles timing
|
|
336
|
+
* and rendering" was wrong (the parser stores the data on
|
|
337
|
+
* `tileset.tileData[id].animation` but no rendering code consumes it).
|
|
338
|
+
* 50-line custom impl avoids depending on the bit-rotted
|
|
339
|
+
* `phaser-animated-tiles` community plugin.
|
|
340
|
+
*/
|
|
341
|
+
animations?: TilesetAnimation[];
|
|
342
|
+
}
|
|
343
|
+
/**
|
|
344
|
+
* One animation cycle on a tileset (slice 6 Phase F).
|
|
345
|
+
*
|
|
346
|
+
* Painted tiles whose source index equals `rootTileIndex` cycle through
|
|
347
|
+
* `frames[*].tileIndex` per their `duration` (ms). All cells animate
|
|
348
|
+
* synchronously — Sprout Lands water lake-cells all flow together.
|
|
349
|
+
* Per-cell phase offset (organic-water look) is v2.
|
|
350
|
+
*
|
|
351
|
+
* Example — Sprout Lands 4-frame water at 4fps:
|
|
352
|
+
* ```json
|
|
353
|
+
* {
|
|
354
|
+
* "id": "water-flow",
|
|
355
|
+
* "rootTileIndex": 12,
|
|
356
|
+
* "frames": [
|
|
357
|
+
* { "tileIndex": 12, "duration": 250 },
|
|
358
|
+
* { "tileIndex": 13, "duration": 250 },
|
|
359
|
+
* { "tileIndex": 14, "duration": 250 },
|
|
360
|
+
* { "tileIndex": 15, "duration": 250 }
|
|
361
|
+
* ]
|
|
362
|
+
* }
|
|
363
|
+
* ```
|
|
364
|
+
*
|
|
365
|
+
* The first frame conventionally references `rootTileIndex` so the
|
|
366
|
+
* static editor view (where animations don't run) matches frame 0 of
|
|
367
|
+
* the animation. Not enforced — the cycle starts on whichever frame the
|
|
368
|
+
* elapsed-time math lands on; users who set `frames[0].tileIndex` !==
|
|
369
|
+
* `rootTileIndex` get a visible jump when the animation starts.
|
|
370
|
+
*/
|
|
371
|
+
export interface TilesetAnimation {
|
|
372
|
+
/** Stable id within the tileset's `animations[]`. Used by editor UI for selection. */
|
|
373
|
+
id: string;
|
|
374
|
+
/**
|
|
375
|
+
* Tile index (0-based, row-major in the tileset image) that the user
|
|
376
|
+
* paints with. All cells painted with this index get animated.
|
|
377
|
+
*/
|
|
378
|
+
rootTileIndex: number;
|
|
379
|
+
/**
|
|
380
|
+
* Frames cycled through in order, then looping. Empty frames array →
|
|
381
|
+
* SDK skips this animation (no error, just no-op).
|
|
382
|
+
*/
|
|
383
|
+
frames: Array<{
|
|
384
|
+
/** Tile index to display during this frame. */
|
|
385
|
+
tileIndex: number;
|
|
386
|
+
/** Milliseconds to hold this frame before advancing. Minimum 16ms (~60fps). */
|
|
387
|
+
duration: number;
|
|
388
|
+
}>;
|
|
389
|
+
}
|
|
390
|
+
/**
|
|
391
|
+
* Wang autotile configuration on a tileset (slice 6 Phase D — design
|
|
392
|
+
* doc 06 §7). Multi-terrain: one tileset can drive multiple independent
|
|
393
|
+
* autotile palettes.
|
|
394
|
+
*
|
|
395
|
+
* Two modes:
|
|
396
|
+
*
|
|
397
|
+
* - **`'wang-4bit'`** (Phase D.1–D.2 baseline, design §7.1–§7.4) — 4 corner
|
|
398
|
+
* bits per cell, max 16 ruleMap entries. Covers 9 unique edge/corner
|
|
399
|
+
* patterns + interior; missing 6 concave/diagonal cases per §7.7. Each
|
|
400
|
+
* terrain typically needs 9-15 tiles.
|
|
401
|
+
*
|
|
402
|
+
* - **`'wang-8bit'`** (Phase D.2.5.2, design §7.7 "blob" / Cr31
|
|
403
|
+
* "2-edge + 2-corner") — 4 corner bits + 4 side bits per cell, max 256
|
|
404
|
+
* ruleMap entries (~47 geometrically valid). Distinguishes patterns
|
|
405
|
+
* the 4-bit model collapses to bm 15 (isolated 1×1, 1-wide strips,
|
|
406
|
+
* T-intersections). Each terrain typically needs 47 tiles for full
|
|
407
|
+
* coverage; commercial tilesets like Sprout Lands ship these.
|
|
408
|
+
*/
|
|
409
|
+
export interface TilesetAutotile {
|
|
410
|
+
kind: 'wang-4bit' | 'wang-8bit';
|
|
411
|
+
/**
|
|
412
|
+
* One entry per terrain this tileset supports. Order is editor-display
|
|
413
|
+
* order (also serves as the default selection in the palette).
|
|
414
|
+
*/
|
|
415
|
+
terrains: WangTerrain[];
|
|
416
|
+
}
|
|
417
|
+
/**
|
|
418
|
+
* One Wang terrain palette inside a tileset (slice 6 Phase D).
|
|
419
|
+
*
|
|
420
|
+
* Each terrain owns a `ruleMap` from a bitmask to a tile index. The bit
|
|
421
|
+
* encoding depends on the parent `TilesetAutotile.kind`:
|
|
422
|
+
*
|
|
423
|
+
* - **`'wang-4bit'`**: bitmask is 4 bits (0..15). Bit 0 = BR corner, 1 = BL,
|
|
424
|
+
* 2 = TR, 3 = TL. So `0b1111 = 15` = interior, `0b0000 = 0` = empty.
|
|
425
|
+
*
|
|
426
|
+
* - **`'wang-8bit'`**: bitmask is 8 bits (0..255). Low 4 bits = corners
|
|
427
|
+
* (same as 4-bit). High 4 bits = sides: bit 4 = E neighbor painted,
|
|
428
|
+
* 5 = W, 6 = S, 7 = N. So `0b00001111 = 15` = isolated 1×1 island
|
|
429
|
+
* (all corners but no painted neighbors), `0b11111111 = 255` = full
|
|
430
|
+
* interior. Geometrically: if a side bit is set, the 2 corners adjacent
|
|
431
|
+
* to that side must also be set (the painted neighbor touches them).
|
|
432
|
+
*
|
|
433
|
+
* Sparse — keys without an entry mean "no tile for this configuration",
|
|
434
|
+
* and the cell stays empty when the painter resolves that bitmask. Bitmask
|
|
435
|
+
* 0 is typically absent (it's the "empty / unpainted" cell).
|
|
436
|
+
*/
|
|
437
|
+
export interface WangTerrain {
|
|
438
|
+
/**
|
|
439
|
+
* Stable id unique within this tileset's `autotile.terrains[]`. Layers
|
|
440
|
+
* reference this via `TilemapLayer.autotile.terrainId`. Typically the
|
|
441
|
+
* same string as `terrainTag` (e.g. `"grass"`) but kept separate so
|
|
442
|
+
* users can rename the display tag without rewriting every layer.
|
|
443
|
+
*/
|
|
444
|
+
id: string;
|
|
445
|
+
/**
|
|
446
|
+
* Free-form terrain label mirroring `TileMetadata.terrainTag`. Allows
|
|
447
|
+
* behavior code to cross-reference "what terrain did the user paint
|
|
448
|
+
* here?" with per-tile gameplay metadata (movement, damage, etc.).
|
|
449
|
+
*/
|
|
450
|
+
terrainTag: string;
|
|
451
|
+
/**
|
|
452
|
+
* Map from bitmask to tile index in the tileset image (0-based,
|
|
453
|
+
* row-major). Key range depends on parent kind: 0..15 for `wang-4bit`,
|
|
454
|
+
* 0..255 for `wang-8bit`. JSON wire format uses string keys (Jackson
|
|
455
|
+
* default for `Map<Integer, V>`); the SDK accepts both string and
|
|
456
|
+
* numeric keys at runtime.
|
|
457
|
+
*/
|
|
458
|
+
ruleMap: {
|
|
459
|
+
[bitmask: number]: number;
|
|
460
|
+
};
|
|
461
|
+
}
|
|
462
|
+
/**
|
|
463
|
+
* Per-tile metadata for tilemap collision / gameplay (slice 6 Phase C —
|
|
464
|
+
* design doc 06 §5.1). All fields optional and default at runtime when
|
|
465
|
+
* undefined. Mirrors backend `TilesetMetadata.TileMetadata`.
|
|
466
|
+
*/
|
|
467
|
+
export interface TileMetadata {
|
|
468
|
+
/** Blocks player via Phaser's `setCollisionByProperty({ solid: true })`. */
|
|
469
|
+
solid?: boolean;
|
|
470
|
+
/** Only collides from above (jump-through platforms). v1 stores flag only. */
|
|
471
|
+
oneWay?: boolean;
|
|
472
|
+
/** `"up-left"` / `"up-right"` / undefined — slope direction. */
|
|
473
|
+
slope?: string;
|
|
474
|
+
/** HP/sec dealt when player stands on this tile. Game code reads + applies. */
|
|
475
|
+
damage?: number;
|
|
476
|
+
/** Free-form terrain label (`"grass"` / `"water"` / …) for Phase D + behavior. */
|
|
477
|
+
terrainTag?: string;
|
|
478
|
+
/** `"walk"` / `"swim"` / `"fly"` / `"block"` — movement modifier. */
|
|
479
|
+
movement?: string;
|
|
480
|
+
/** Free-form ground-type label for footstep-sound selection. */
|
|
481
|
+
groundType?: string;
|
|
482
|
+
/** Free-form user tag for game-specific logic (e.g. `"checkpoint"`). */
|
|
483
|
+
customTag?: string;
|
|
484
|
+
/**
|
|
485
|
+
* Sub-tile collision rectangles (slice 6 Phase C follow-up). N axis-
|
|
486
|
+
* aligned rects in source-pixel coords, relative to tile top-left.
|
|
487
|
+
* Multi-rect supports L / U / frame-shaped collision (wall corners,
|
|
488
|
+
* borders). When non-empty, the SDK creates one invisible Arcade static
|
|
489
|
+
* body per rect at each painted instance and disables Phaser's native
|
|
490
|
+
* cell-rect collision for those cells. When empty but `solid: true`,
|
|
491
|
+
* default Phaser cell-rect collision applies (full tile collides).
|
|
492
|
+
*/
|
|
493
|
+
collisionRects?: Array<{
|
|
494
|
+
x: number;
|
|
495
|
+
y: number;
|
|
496
|
+
w: number;
|
|
497
|
+
h: number;
|
|
498
|
+
}>;
|
|
499
|
+
}
|
|
500
|
+
export interface SheetAnimation {
|
|
501
|
+
/** Phaser animation key — used as `sprite.play('<name>')`. */
|
|
502
|
+
name: string;
|
|
503
|
+
/** First frame index, inclusive. */
|
|
504
|
+
from: number;
|
|
505
|
+
/** Last frame index, inclusive. */
|
|
506
|
+
to: number;
|
|
507
|
+
/** When true, Phaser registers with `repeat: -1`. Defaults to true. */
|
|
508
|
+
loop?: boolean;
|
|
509
|
+
/** Override the asset-level fps for this specific animation. */
|
|
510
|
+
fps?: number;
|
|
511
|
+
}
|
|
512
|
+
export interface Manifest {
|
|
513
|
+
schemaVersion: number;
|
|
514
|
+
id: string;
|
|
515
|
+
title: string;
|
|
516
|
+
version: string;
|
|
517
|
+
initialScene: string;
|
|
518
|
+
scenes: SceneRef[];
|
|
519
|
+
huds: HudRef[];
|
|
520
|
+
assets: AssetRecord[];
|
|
521
|
+
/**
|
|
522
|
+
* Entity TYPE definitions for runtime spawning — slice 11, Phase B.1.
|
|
523
|
+
* Each prefab is a self-contained template: visual + physics + properties.
|
|
524
|
+
* Code spawns instances via the SDK's `spawnPrefab(scene, prefabId, x, y)`.
|
|
525
|
+
* Optional — games that have only authored static entities (no runtime
|
|
526
|
+
* spawning) can omit this field.
|
|
527
|
+
*/
|
|
528
|
+
prefabs?: PrefabRecord[];
|
|
529
|
+
globals?: {
|
|
530
|
+
physics?: {
|
|
531
|
+
gravity?: {
|
|
532
|
+
x?: number;
|
|
533
|
+
y?: number;
|
|
534
|
+
};
|
|
535
|
+
};
|
|
536
|
+
world?: {
|
|
537
|
+
pixelArt?: boolean;
|
|
538
|
+
};
|
|
539
|
+
};
|
|
540
|
+
}
|
|
541
|
+
/**
|
|
542
|
+
* Prefab kinds — same renderable kinds as world entities. SDK 0.3.0 flattened
|
|
543
|
+
* `primitive` → `rect | circle`. (Prefabs never targeted `tilemap` / `trigger`
|
|
544
|
+
* / `group` — those are scene-authored, not runtime-spawned.)
|
|
545
|
+
*/
|
|
546
|
+
export type PrefabKind = 'sprite' | 'rect' | 'circle' | 'code-rendered';
|
|
547
|
+
/**
|
|
548
|
+
* Arcade physics body configuration. Used by prefabs (`PrefabRecord.physics`,
|
|
549
|
+
* applied inside `spawnPrefab`) AND by authored scene entities
|
|
550
|
+
* (`WorldEntityBase.physics`, applied inside `spawnEntity`). In both cases the
|
|
551
|
+
* SDK calls `physics.add.existing` and applies these knobs to the body, so
|
|
552
|
+
* behavior code doesn't have to call `body.setSize` / `setOffset` manually.
|
|
553
|
+
*
|
|
554
|
+
* Conventions:
|
|
555
|
+
* - `bodyW` / `bodyH` default to the visual's drawn extent (sprite
|
|
556
|
+
* texture size, primitive width/height, or code-rendered visual.width
|
|
557
|
+
* / visual.height — 64 if unset).
|
|
558
|
+
* - `offsetX` / `offsetY` default to centering the body inside the
|
|
559
|
+
* GameObject's local bounds (i.e. `(visualW - bodyW) / 2`).
|
|
560
|
+
* - All fields are optional; pass an empty `physics: {}` to get the
|
|
561
|
+
* default body sized to the visual.
|
|
562
|
+
*/
|
|
563
|
+
export interface PrefabPhysics {
|
|
564
|
+
bodyW?: number;
|
|
565
|
+
bodyH?: number;
|
|
566
|
+
offsetX?: number;
|
|
567
|
+
offsetY?: number;
|
|
568
|
+
immovable?: boolean;
|
|
569
|
+
velocityX?: number;
|
|
570
|
+
velocityY?: number;
|
|
571
|
+
collideWorldBounds?: boolean;
|
|
572
|
+
bounceX?: number;
|
|
573
|
+
bounceY?: number;
|
|
574
|
+
}
|
|
575
|
+
/**
|
|
576
|
+
* Entity TYPE — used as a template by `spawnPrefab` to create runtime
|
|
577
|
+
* instances. Spawn-time coordinates and per-instance overrides come from
|
|
578
|
+
* the caller; everything else is shared across all instances.
|
|
579
|
+
*
|
|
580
|
+
* Prefabs do NOT have a `transform` field — that's set per spawn.
|
|
581
|
+
* Editor live-edits to a prefab record propagate to existing instances
|
|
582
|
+
* via `umicat:editor:editPrefab` (see slice 11 §6.4).
|
|
583
|
+
*
|
|
584
|
+
* **Flat schema** (SDK 0.3.0). Prefab fields live at the top level —
|
|
585
|
+
* exactly like world entities. The shape depends on `kind`:
|
|
586
|
+
*
|
|
587
|
+
* - `kind: 'sprite'` + `{ assetId, frame?, tint?, alpha?, flipX?, flipY? }`
|
|
588
|
+
* - `kind: 'rect'` + `{ width, height, fillColor?, strokeColor?, strokeWidth?, alpha? }`
|
|
589
|
+
* - `kind: 'circle'` + `{ radius, fillColor?, strokeColor?, strokeWidth?, alpha? }`
|
|
590
|
+
* - `kind: 'code-rendered'` + `{ script, params?, width?, height? }`
|
|
591
|
+
*/
|
|
592
|
+
export type PrefabRecord = SpritePrefab | RectPrefab | CirclePrefab | CodeRenderedPrefab;
|
|
593
|
+
export interface PrefabBase {
|
|
594
|
+
/** Stable id used by `spawnPrefab(scene, '<id>', ...)`. */
|
|
595
|
+
id: string;
|
|
596
|
+
/** Semantic tag — `registry.byRole('enemy')` lists every spawned instance. */
|
|
597
|
+
role?: string;
|
|
598
|
+
/** Physics body config applied at spawn. Omit when the prefab has no body. */
|
|
599
|
+
physics?: PrefabPhysics;
|
|
600
|
+
/** Free-form per-type data behavior code reads via `go.getData('entityProperties').<key>`. */
|
|
601
|
+
properties?: Record<string, unknown>;
|
|
602
|
+
}
|
|
603
|
+
export interface SpritePrefab extends PrefabBase {
|
|
604
|
+
kind: 'sprite';
|
|
605
|
+
assetId: string;
|
|
606
|
+
frame?: string | number;
|
|
607
|
+
tint?: string;
|
|
608
|
+
alpha?: number;
|
|
609
|
+
flipX?: boolean;
|
|
610
|
+
flipY?: boolean;
|
|
611
|
+
}
|
|
612
|
+
export interface RectPrefab extends PrefabBase {
|
|
613
|
+
kind: 'rect';
|
|
614
|
+
width: number;
|
|
615
|
+
height: number;
|
|
616
|
+
fillColor?: string;
|
|
617
|
+
strokeColor?: string | null;
|
|
618
|
+
strokeWidth?: number;
|
|
619
|
+
alpha?: number;
|
|
620
|
+
}
|
|
621
|
+
export interface CirclePrefab extends PrefabBase {
|
|
622
|
+
kind: 'circle';
|
|
623
|
+
radius: number;
|
|
624
|
+
fillColor?: string;
|
|
625
|
+
strokeColor?: string | null;
|
|
626
|
+
strokeWidth?: number;
|
|
627
|
+
alpha?: number;
|
|
628
|
+
}
|
|
629
|
+
export interface CodeRenderedPrefab extends PrefabBase {
|
|
630
|
+
kind: 'code-rendered';
|
|
631
|
+
script: string;
|
|
632
|
+
params?: Record<string, unknown>;
|
|
633
|
+
width?: number;
|
|
634
|
+
height?: number;
|
|
635
|
+
}
|
|
636
|
+
/**
|
|
637
|
+
* Wave schedule — a time-ordered sequence of spawn instructions that
|
|
638
|
+
* reference prefab ids. Models everything from a Galaga formation to a
|
|
639
|
+
* survivor game's difficulty curve.
|
|
640
|
+
*
|
|
641
|
+
* Each schedule lives in its own file under `public/waves/<id>.json`.
|
|
642
|
+
* Behavior code consumes them via `runWaveSchedule(scene, id, callbacks)`
|
|
643
|
+
* which returns a `WaveController` for game-flow operations
|
|
644
|
+
* (pause / stop / skip).
|
|
645
|
+
*
|
|
646
|
+
* Design: umicat-design/features/visual-editor/11-game-data-foundation.md §7
|
|
647
|
+
*/
|
|
648
|
+
export interface WaveScheduleRecord {
|
|
649
|
+
schemaVersion: number;
|
|
650
|
+
id: string;
|
|
651
|
+
name?: string;
|
|
652
|
+
waves: WaveRecord[];
|
|
653
|
+
/** Restart from wave 0 after the last wave's endCondition fires. */
|
|
654
|
+
loop?: boolean;
|
|
655
|
+
}
|
|
656
|
+
export interface WaveRecord {
|
|
657
|
+
id: string;
|
|
658
|
+
/** Wait this long after the previous wave's endCondition fires before starting. */
|
|
659
|
+
delayMs?: number;
|
|
660
|
+
spawns: SpawnInstruction[];
|
|
661
|
+
/** When this wave is considered "complete" — default `'allDead'`. */
|
|
662
|
+
endCondition?: WaveEndCondition;
|
|
663
|
+
}
|
|
664
|
+
/**
|
|
665
|
+
* One spawn instruction within a wave. v1 ships `point` and `formation`
|
|
666
|
+
* (with `shape: 'grid' | 'line'`) — `arc`, `v-shape`, and `random` are
|
|
667
|
+
* forward-compat'd in the wire shape but the runtime treats unknown
|
|
668
|
+
* shapes as a single `point` at the formation origin and warns.
|
|
669
|
+
*/
|
|
670
|
+
export type SpawnInstruction = SpawnPointInstruction | SpawnFormationInstruction;
|
|
671
|
+
export interface SpawnPointInstruction {
|
|
672
|
+
kind: 'point';
|
|
673
|
+
prefabId: string;
|
|
674
|
+
x: number;
|
|
675
|
+
y: number;
|
|
676
|
+
/** Time within the wave (relative to wave start) at which to spawn. */
|
|
677
|
+
atMs: number;
|
|
678
|
+
/** Optional per-spawn overrides — same shape as `SpawnPrefabOverrides`. */
|
|
679
|
+
overrides?: Record<string, unknown>;
|
|
680
|
+
}
|
|
681
|
+
export interface SpawnFormationInstruction {
|
|
682
|
+
kind: 'formation';
|
|
683
|
+
prefabId: string;
|
|
684
|
+
formation: FormationRecord;
|
|
685
|
+
/** Time within the wave at which the formation starts spawning. */
|
|
686
|
+
startMs: number;
|
|
687
|
+
/** Delay between successive entity spawns inside the formation. */
|
|
688
|
+
intervalMs: number;
|
|
689
|
+
/** Optional per-spawn overrides shared by every formation member. */
|
|
690
|
+
overrides?: Record<string, unknown>;
|
|
691
|
+
}
|
|
692
|
+
export type FormationShape = 'grid' | 'line' | 'arc' | 'v-shape';
|
|
693
|
+
export interface FormationRecord {
|
|
694
|
+
shape: FormationShape;
|
|
695
|
+
/** Grid + v-shape: number of rows. Line + arc: usually 1. */
|
|
696
|
+
rows?: number;
|
|
697
|
+
/** Grid + line + arc + v-shape: number of columns / members per row. */
|
|
698
|
+
cols?: number;
|
|
699
|
+
spacing?: {
|
|
700
|
+
x?: number;
|
|
701
|
+
y?: number;
|
|
702
|
+
};
|
|
703
|
+
/** World-coordinate top-left (grid) or center (arc) of the formation. */
|
|
704
|
+
origin?: {
|
|
705
|
+
x: number;
|
|
706
|
+
y: number;
|
|
707
|
+
};
|
|
708
|
+
/** Arc-only: radius from `origin`. */
|
|
709
|
+
radius?: number;
|
|
710
|
+
/** Arc-only: span in radians (default π). */
|
|
711
|
+
arcRadians?: number;
|
|
712
|
+
}
|
|
713
|
+
export type WaveEndCondition = 'allDead' | {
|
|
714
|
+
type: 'time';
|
|
715
|
+
ms: number;
|
|
716
|
+
} | {
|
|
717
|
+
type: 'count';
|
|
718
|
+
killed: number;
|
|
719
|
+
};
|
|
720
|
+
export interface Transform {
|
|
721
|
+
x: number;
|
|
722
|
+
y: number;
|
|
723
|
+
/** Radians. */
|
|
724
|
+
rotation?: number;
|
|
725
|
+
scaleX?: number;
|
|
726
|
+
scaleY?: number;
|
|
727
|
+
depth?: number;
|
|
728
|
+
}
|
|
729
|
+
export type AnchorSide = 'top-left' | 'top' | 'top-right' | 'left' | 'center' | 'right' | 'bottom-left' | 'bottom' | 'bottom-right';
|
|
730
|
+
export interface Anchor {
|
|
731
|
+
side: AnchorSide;
|
|
732
|
+
offsetX?: number;
|
|
733
|
+
offsetY?: number;
|
|
734
|
+
}
|
|
735
|
+
export type WorldEntityKind = 'sprite' | 'rect' | 'circle' | 'code-rendered' | 'group' | 'tilemap' | 'trigger';
|
|
736
|
+
export interface WorldEntityBase {
|
|
737
|
+
id: string;
|
|
738
|
+
/** Optional semantic tag. Behavior code keys off this. */
|
|
739
|
+
role?: string;
|
|
740
|
+
/** Free-form per-entity data the agent's behavior code reads. */
|
|
741
|
+
properties?: Record<string, unknown>;
|
|
742
|
+
transform: Transform;
|
|
743
|
+
/**
|
|
744
|
+
* Optional Arcade physics body. When set, `spawnEntity` calls
|
|
745
|
+
* `physics.add.existing` and applies the body's size / offset / immovable /
|
|
746
|
+
* velocity / bounce — the same body-level path and `PrefabPhysics` shape
|
|
747
|
+
* `spawnPrefab` uses, so authored entities and runtime instances get
|
|
748
|
+
* identical bodies. Applied for renderable entities (`sprite` / `rect` /
|
|
749
|
+
* `circle` / `code-rendered`); ignored on `group` / `tilemap` / `trigger`.
|
|
750
|
+
* Omit for entities with no body, or that wire physics in behavior code.
|
|
751
|
+
*/
|
|
752
|
+
physics?: PrefabPhysics;
|
|
753
|
+
}
|
|
754
|
+
export interface SpriteEntity extends WorldEntityBase {
|
|
755
|
+
kind: 'sprite';
|
|
756
|
+
assetId: string;
|
|
757
|
+
/** Atlas frame name OR sprite-sheet frame index. */
|
|
758
|
+
frame?: string | number;
|
|
759
|
+
tint?: string;
|
|
760
|
+
alpha?: number;
|
|
761
|
+
flipX?: boolean;
|
|
762
|
+
flipY?: boolean;
|
|
763
|
+
}
|
|
764
|
+
export interface RectEntity extends WorldEntityBase {
|
|
765
|
+
kind: 'rect';
|
|
766
|
+
width: number;
|
|
767
|
+
height: number;
|
|
768
|
+
fillColor?: string;
|
|
769
|
+
strokeColor?: string | null;
|
|
770
|
+
strokeWidth?: number;
|
|
771
|
+
alpha?: number;
|
|
772
|
+
}
|
|
773
|
+
export interface CircleEntity extends WorldEntityBase {
|
|
774
|
+
kind: 'circle';
|
|
775
|
+
radius: number;
|
|
776
|
+
fillColor?: string;
|
|
777
|
+
strokeColor?: string | null;
|
|
778
|
+
strokeWidth?: number;
|
|
779
|
+
alpha?: number;
|
|
780
|
+
}
|
|
781
|
+
export interface CodeRenderedEntity extends WorldEntityBase {
|
|
782
|
+
kind: 'code-rendered';
|
|
783
|
+
/** Path to render script, e.g. `"src/visuals/boss-renderer.ts"`. */
|
|
784
|
+
script: string;
|
|
785
|
+
params?: Record<string, unknown>;
|
|
786
|
+
/**
|
|
787
|
+
* Optional bounds used for editor hit-testing (click + drag) and the
|
|
788
|
+
* selection rectangle. Phaser Graphics has no intrinsic size, so we set
|
|
789
|
+
* it explicitly. Defaults to 64×64 centered on the entity. The agent can
|
|
790
|
+
* override per-entity when the script draws something larger or smaller.
|
|
791
|
+
*/
|
|
792
|
+
width?: number;
|
|
793
|
+
height?: number;
|
|
794
|
+
}
|
|
795
|
+
export interface GroupEntity extends WorldEntityBase {
|
|
796
|
+
kind: 'group';
|
|
797
|
+
/**
|
|
798
|
+
* One level of nesting in v1: a child cannot itself be a `group`.
|
|
799
|
+
* If you need deeper hierarchy, the agent should flatten via the
|
|
800
|
+
* existing transform and add a role tag for grouping queries.
|
|
801
|
+
*/
|
|
802
|
+
children: NonGroupWorldEntity[];
|
|
803
|
+
}
|
|
804
|
+
/**
|
|
805
|
+
* Tilemap entity — slice 6.
|
|
806
|
+
*
|
|
807
|
+
* Phase A (render-only foundation, 2026-05-18): SDK reads tilemap entities
|
|
808
|
+
* and renders them as Phaser Tilemaps. One tileset per layer; per-cell
|
|
809
|
+
* `data` is a 2D array of tile indices (numeric, matching Phaser's native
|
|
810
|
+
* tile-id model). Empty layers (no `data`) fall back to the slice-3 grid
|
|
811
|
+
* sketch so unpainted tilemaps still show their bounds in the editor.
|
|
812
|
+
*
|
|
813
|
+
* Phase B adds the painter UI; Phase C adds per-tile metadata + Y-sort;
|
|
814
|
+
* Phase D adds Wang autotile resolution; Phase F adds animated tiles.
|
|
815
|
+
*
|
|
816
|
+
* Multi-tileset per layer (`tilesetIds: string[]`) is reserved in the
|
|
817
|
+
* shape for later phases but Phase A only consumes the first id.
|
|
818
|
+
*/
|
|
819
|
+
export interface TilemapEntity extends WorldEntityBase {
|
|
820
|
+
kind: 'tilemap';
|
|
821
|
+
/** Render-time cell size in pixels. Layer data indexes cells at this size. */
|
|
822
|
+
tileSize: {
|
|
823
|
+
width: number;
|
|
824
|
+
height: number;
|
|
825
|
+
};
|
|
826
|
+
/** Map size in cells (not pixels). */
|
|
827
|
+
size: {
|
|
828
|
+
width: number;
|
|
829
|
+
height: number;
|
|
830
|
+
};
|
|
831
|
+
layers: TilemapLayer[];
|
|
832
|
+
}
|
|
833
|
+
export interface TilemapLayer {
|
|
834
|
+
id: string;
|
|
835
|
+
name?: string;
|
|
836
|
+
/**
|
|
837
|
+
* Tilesets this layer can paint with. Phase A consumes the FIRST id only
|
|
838
|
+
* (single-tileset rendering); the array shape is kept so later phases
|
|
839
|
+
* can paint blended layers (grass + flowers) without a schema break.
|
|
840
|
+
*/
|
|
841
|
+
tilesetIds?: string[];
|
|
842
|
+
z?: number;
|
|
843
|
+
/**
|
|
844
|
+
* 2D array of tile indices (row-major: `data[y][x]`). `null` cells are
|
|
845
|
+
* empty (not painted). Integer indices match Phaser's native tile-id
|
|
846
|
+
* scheme — the index within the tileset image, 0-based, row-major. Phase
|
|
847
|
+
* D will introduce a parallel autotile bitmask channel for Wang layers.
|
|
848
|
+
*/
|
|
849
|
+
data?: Array<Array<number | null>>;
|
|
850
|
+
visible?: boolean;
|
|
851
|
+
/** Editor-only: prevents accidental painting. No runtime effect. */
|
|
852
|
+
locked?: boolean;
|
|
853
|
+
/**
|
|
854
|
+
* Wang 4-bit autotile binding (slice 6 Phase D — design doc 06 §7).
|
|
855
|
+
*
|
|
856
|
+
* When set, the painter operates in "terrain" mode on this layer: each
|
|
857
|
+
* brush stroke marks corner-vertices for `terrainId` and the SDK
|
|
858
|
+
* resolves the tile index from the tileset's `autotile.terrains[]`
|
|
859
|
+
* ruleMap on every affected cell. `terrainId` references
|
|
860
|
+
* `TilesetAutotile.terrains[].id` on the layer's first tileset.
|
|
861
|
+
*
|
|
862
|
+
* Absent (the common case) → layer is a plain stamp painter; cells
|
|
863
|
+
* carry author-chosen tile indices verbatim.
|
|
864
|
+
*/
|
|
865
|
+
autotile?: {
|
|
866
|
+
terrainId: string;
|
|
867
|
+
};
|
|
868
|
+
ySort?: boolean;
|
|
869
|
+
}
|
|
870
|
+
export type TriggerShape = {
|
|
871
|
+
kind: 'rect';
|
|
872
|
+
width: number;
|
|
873
|
+
height: number;
|
|
874
|
+
} | {
|
|
875
|
+
kind: 'circle';
|
|
876
|
+
radius: number;
|
|
877
|
+
};
|
|
878
|
+
/**
|
|
879
|
+
* Trigger entity — slice 3 ships a stub renderer (semi-transparent rect/circle
|
|
880
|
+
* with the design's cyan tint). Full behavior wiring (description / targets /
|
|
881
|
+
* behavior script) lands in slice 5 (03-world-editor.md §8).
|
|
882
|
+
*/
|
|
883
|
+
export interface TriggerEntity extends WorldEntityBase {
|
|
884
|
+
kind: 'trigger';
|
|
885
|
+
shape: TriggerShape;
|
|
886
|
+
/** User-facing label shown in Hierarchy + on canvas. */
|
|
887
|
+
description?: string;
|
|
888
|
+
/** Entity ids this trigger affects. Slice 5 wires the relationship UI. */
|
|
889
|
+
targets?: string[];
|
|
890
|
+
/** Optional path to behavior script. Slice 5 fills this in. */
|
|
891
|
+
behaviorScript?: string;
|
|
892
|
+
/** Optional preset name (open-door / damage-zone / scene-transition / spawner / heal). */
|
|
893
|
+
preset?: string;
|
|
894
|
+
}
|
|
895
|
+
export type NonGroupWorldEntity = SpriteEntity | RectEntity | CircleEntity | CodeRenderedEntity | TilemapEntity | TriggerEntity;
|
|
896
|
+
export type WorldEntity = NonGroupWorldEntity | GroupEntity;
|
|
897
|
+
/**
|
|
898
|
+
* Renderable entity kinds — those that produce a single GameObject with
|
|
899
|
+
* physics + transform. Excludes `group` (container), `tilemap` (multi-layer
|
|
900
|
+
* data), `trigger` (invisible volume).
|
|
901
|
+
*/
|
|
902
|
+
export type RenderableEntity = SpriteEntity | RectEntity | CircleEntity | CodeRenderedEntity;
|
|
903
|
+
export interface CameraConfig {
|
|
904
|
+
/** Entity id to follow. Null = static camera. */
|
|
905
|
+
follow?: string | null;
|
|
906
|
+
smoothing?: number;
|
|
907
|
+
deadzone?: {
|
|
908
|
+
x: number;
|
|
909
|
+
y: number;
|
|
910
|
+
};
|
|
911
|
+
lookahead?: number;
|
|
912
|
+
bounds?: {
|
|
913
|
+
x: number;
|
|
914
|
+
y: number;
|
|
915
|
+
width: number;
|
|
916
|
+
height: number;
|
|
917
|
+
};
|
|
918
|
+
zoom?: number;
|
|
919
|
+
pixelPerfect?: boolean;
|
|
920
|
+
shakeDecay?: number;
|
|
921
|
+
}
|
|
922
|
+
export interface WorldSceneConfig {
|
|
923
|
+
width: number;
|
|
924
|
+
height: number;
|
|
925
|
+
background?: {
|
|
926
|
+
color?: string;
|
|
927
|
+
};
|
|
928
|
+
physics?: {
|
|
929
|
+
gravity?: {
|
|
930
|
+
x?: number;
|
|
931
|
+
y?: number;
|
|
932
|
+
};
|
|
933
|
+
};
|
|
934
|
+
}
|
|
935
|
+
export interface WorldScene {
|
|
936
|
+
schemaVersion: number;
|
|
937
|
+
id: string;
|
|
938
|
+
name: string;
|
|
939
|
+
type: 'world';
|
|
940
|
+
world: WorldSceneConfig;
|
|
941
|
+
camera?: CameraConfig;
|
|
942
|
+
entities: WorldEntity[];
|
|
943
|
+
/**
|
|
944
|
+
* Scene-level Y-sort opt-in (slice 8 — see design doc 09 §2.2).
|
|
945
|
+
*
|
|
946
|
+
* When `true`, the SDK installs a per-frame update hook in `loadWorldScene`
|
|
947
|
+
* that walks the entity registry and sets `sprite.depth = sprite.y` so
|
|
948
|
+
* closer-to-camera sprites overlap farther ones. Default `false` —
|
|
949
|
+
* side-scrollers, single-screen arcade, and chat-only Workflow A games
|
|
950
|
+
* don't pay the per-frame cost.
|
|
951
|
+
*
|
|
952
|
+
* Two opt-out mechanisms (orthogonal, cover different intents):
|
|
953
|
+
* - `entity.transform.depth` set to a number → that constant wins, ySort
|
|
954
|
+
* skips the entity. For "cloud always on top," "tilemap at -1000," etc.
|
|
955
|
+
* - `entity.properties.skipYSort: true` → ySort doesn't touch the entity's
|
|
956
|
+
* depth at all. For behavior code that manages depth dynamically.
|
|
957
|
+
*
|
|
958
|
+
* Top-down RPG / farming / iso / city-builder / MOBA / 2.5D platformer
|
|
959
|
+
* are the target genres.
|
|
960
|
+
*/
|
|
961
|
+
ySort?: boolean;
|
|
962
|
+
metadata?: {
|
|
963
|
+
tags?: string[];
|
|
964
|
+
author?: string;
|
|
965
|
+
createdAt?: string;
|
|
966
|
+
updatedAt?: string;
|
|
967
|
+
};
|
|
968
|
+
}
|
|
969
|
+
export type HudLayer = 'base' | 'overlay' | 'modal';
|
|
970
|
+
/**
|
|
971
|
+
* Text content source — `static` is a literal string the user typed;
|
|
972
|
+
* `dynamic` reads a value from `umicat.gameData` at runtime, with optional
|
|
973
|
+
* prefix/suffix formatting. The Inspector only edits the static path; the
|
|
974
|
+
* agent flips a widget to `dynamic` via the inline AI popover when the user
|
|
975
|
+
* asks for live values ("show current score"). The variable registry +
|
|
976
|
+
* picker UX from design 04 §4.7 is intentionally NOT implemented — Umicat's
|
|
977
|
+
* thesis is users don't see "variables", agent handles bindings.
|
|
978
|
+
*/
|
|
979
|
+
export type HudTextSource = {
|
|
980
|
+
mode: 'static';
|
|
981
|
+
text: string;
|
|
982
|
+
} | {
|
|
983
|
+
mode: 'dynamic';
|
|
984
|
+
/** Key in `umicat.gameData` whose value drives the text. */
|
|
985
|
+
binding: string;
|
|
986
|
+
prefix?: string;
|
|
987
|
+
suffix?: string;
|
|
988
|
+
/** Fallback value rendered when the binding is unset. */
|
|
989
|
+
fallback?: string;
|
|
990
|
+
};
|
|
991
|
+
/**
|
|
992
|
+
* A progress bar's numeric input. Static = literal number; dynamic = read
|
|
993
|
+
* from `umicat.gameData` / Phaser's game registry by key. Mirrors
|
|
994
|
+
* `HudTextSource` but for numbers (no prefix/suffix — bar is visual).
|
|
995
|
+
*/
|
|
996
|
+
export type HudNumberSource = {
|
|
997
|
+
mode: 'static';
|
|
998
|
+
value: number;
|
|
999
|
+
} | {
|
|
1000
|
+
mode: 'dynamic';
|
|
1001
|
+
binding: string;
|
|
1002
|
+
/** Used when the binding is unset at runtime. Defaults to 0. */
|
|
1003
|
+
fallback?: number;
|
|
1004
|
+
};
|
|
1005
|
+
export type HudEntityKind = 'text' | 'image' | 'icon-button' | 'progress-bar' | 'panel';
|
|
1006
|
+
export interface HudEntityBase {
|
|
1007
|
+
id: string;
|
|
1008
|
+
/** Optional semantic tag. Behavior code keys off this. */
|
|
1009
|
+
role?: string;
|
|
1010
|
+
/** Free-form per-entity data the agent's behavior code reads. */
|
|
1011
|
+
properties?: Record<string, unknown>;
|
|
1012
|
+
anchor: Anchor;
|
|
1013
|
+
/** Render layer (base < overlay < modal). Defaults to `base`. */
|
|
1014
|
+
layer?: HudLayer;
|
|
1015
|
+
/**
|
|
1016
|
+
* Z-order within the same layer. Higher = on top. Optional; defaults to
|
|
1017
|
+
* insertion order if absent.
|
|
1018
|
+
*/
|
|
1019
|
+
z?: number;
|
|
1020
|
+
/** When false, the widget is hidden at runtime. Useful for modal panels
|
|
1021
|
+
* that the agent toggles via gameData / scene events. */
|
|
1022
|
+
visible?: boolean;
|
|
1023
|
+
}
|
|
1024
|
+
/**
|
|
1025
|
+
* HUD text widget — renders a string at an anchor position. Source is either
|
|
1026
|
+
* static (a literal) or dynamic (a key into `umicat.gameData` / Phaser's
|
|
1027
|
+
* game registry). All fields flat per SDK 0.3.0.
|
|
1028
|
+
*/
|
|
1029
|
+
export interface HudTextEntity extends HudEntityBase {
|
|
1030
|
+
kind: 'text';
|
|
1031
|
+
source: HudTextSource;
|
|
1032
|
+
fontFamily?: string;
|
|
1033
|
+
fontSize?: number;
|
|
1034
|
+
color?: string;
|
|
1035
|
+
align?: 'left' | 'center' | 'right';
|
|
1036
|
+
}
|
|
1037
|
+
/**
|
|
1038
|
+
* HUD image widget — renders an asset by id at an anchor position. Supports
|
|
1039
|
+
* single images, atlas frames, and sprite-sheet frame indexes. Optional 9-slice
|
|
1040
|
+
* stretching when the referenced asset has `ninePatch` metadata.
|
|
1041
|
+
*/
|
|
1042
|
+
export interface HudImageEntity extends HudEntityBase {
|
|
1043
|
+
kind: 'image';
|
|
1044
|
+
assetId: string;
|
|
1045
|
+
frame?: string | number;
|
|
1046
|
+
/** CSS pixel size on the HUD scene's logical canvas. */
|
|
1047
|
+
width?: number;
|
|
1048
|
+
height?: number;
|
|
1049
|
+
tint?: string;
|
|
1050
|
+
alpha?: number;
|
|
1051
|
+
}
|
|
1052
|
+
/**
|
|
1053
|
+
* Icon button. v1 has no built-in onPress wiring — clicks emit a Phaser
|
|
1054
|
+
* scene event `hud:press` with the entity id, and the agent's behavior code
|
|
1055
|
+
* subscribes (`scene.events.on('hud:press', id => ...)`). The slice-4
|
|
1056
|
+
* popover is the user's path to wire one up: select the button, ✨, "pause
|
|
1057
|
+
* the game when this is pressed". design 04 §6 / §7's bind-to-action +
|
|
1058
|
+
* built-in-presets system is reserved for slice 5.5.
|
|
1059
|
+
*/
|
|
1060
|
+
export interface HudIconButtonEntity extends HudEntityBase {
|
|
1061
|
+
kind: 'icon-button';
|
|
1062
|
+
/** Optional displayed label. */
|
|
1063
|
+
label?: string;
|
|
1064
|
+
/** Optional icon asset shown inside the button. */
|
|
1065
|
+
iconAssetId?: string;
|
|
1066
|
+
/**
|
|
1067
|
+
* Optional image asset rendered as the button background. When the
|
|
1068
|
+
* referenced asset has `ninePatch` metadata, the SDK renders it via
|
|
1069
|
+
* `phaser3-rex-plugins` NinePatch so corners stay fixed at any width/height;
|
|
1070
|
+
* otherwise the image is stretched to the widget's size. When set, the
|
|
1071
|
+
* colored-rect bg below (fillColor / strokeColor / shape) is ignored, and
|
|
1072
|
+
* `pressedFillColor` no-ops (no fill swap on a textured bg in v1).
|
|
1073
|
+
*
|
|
1074
|
+
* For uniform-grid spritesheet assets with `ninePatch.perFrame` set
|
|
1075
|
+
* (slice 7.6), pair this with `backgroundFrame` to pick which cell to
|
|
1076
|
+
* render — common case is a button-pack sheet where every cell is a
|
|
1077
|
+
* different button color/state.
|
|
1078
|
+
*/
|
|
1079
|
+
backgroundAssetId?: string;
|
|
1080
|
+
/**
|
|
1081
|
+
* For uniform-grid spritesheet backgrounds (slice 7.6): index of the cell
|
|
1082
|
+
* to render. Defaults to 0 when omitted. Ignored when the background asset
|
|
1083
|
+
* is a single image. Mutually exclusive with `backgroundRegion` — when both
|
|
1084
|
+
* are set, the SDK picks `backgroundRegion` (atlas-name lookup wins).
|
|
1085
|
+
*/
|
|
1086
|
+
backgroundFrame?: number;
|
|
1087
|
+
/**
|
|
1088
|
+
* For region-atlas backgrounds (slice 10): name of the atlas frame to
|
|
1089
|
+
* render. Used when the referenced asset is `kind: 'atlas'` + `atlasFormat:
|
|
1090
|
+
* 'json'` — the region atlas can carry per-frame `ninePatch` metadata, in
|
|
1091
|
+
* which case the SDK 9-slices each region independently. Mutually exclusive
|
|
1092
|
+
* with `backgroundFrame`.
|
|
1093
|
+
*/
|
|
1094
|
+
backgroundRegion?: string;
|
|
1095
|
+
/** Button shape. Default `rounded-rect`. Ignored when backgroundAssetId is set. */
|
|
1096
|
+
shape?: 'rounded-rect' | 'circle';
|
|
1097
|
+
width?: number;
|
|
1098
|
+
height?: number;
|
|
1099
|
+
fillColor?: string;
|
|
1100
|
+
strokeColor?: string | null;
|
|
1101
|
+
strokeWidth?: number;
|
|
1102
|
+
pressedFillColor?: string;
|
|
1103
|
+
textColor?: string;
|
|
1104
|
+
fontSize?: number;
|
|
1105
|
+
}
|
|
1106
|
+
export interface HudProgressBarEntity extends HudEntityBase {
|
|
1107
|
+
kind: 'progress-bar';
|
|
1108
|
+
/** Current value source (e.g. player's HP). */
|
|
1109
|
+
value: HudNumberSource;
|
|
1110
|
+
/** Max value source (e.g. player's max HP). */
|
|
1111
|
+
max: HudNumberSource;
|
|
1112
|
+
width?: number;
|
|
1113
|
+
height?: number;
|
|
1114
|
+
fillColor?: string;
|
|
1115
|
+
backgroundColor?: string;
|
|
1116
|
+
borderColor?: string | null;
|
|
1117
|
+
borderWidth?: number;
|
|
1118
|
+
/** Border radius for rounded rectangles. Defaults to 0 (sharp corners). */
|
|
1119
|
+
borderRadius?: number;
|
|
1120
|
+
}
|
|
1121
|
+
export interface HudPanelEntity extends HudEntityBase {
|
|
1122
|
+
kind: 'panel';
|
|
1123
|
+
width?: number;
|
|
1124
|
+
height?: number;
|
|
1125
|
+
/**
|
|
1126
|
+
* Optional image asset rendered as the panel background. When the
|
|
1127
|
+
* referenced asset has `ninePatch` metadata, the SDK renders it via
|
|
1128
|
+
* `phaser3-rex-plugins` NinePatch so corners stay fixed at any width/height;
|
|
1129
|
+
* otherwise the image is stretched to the widget's size. When set,
|
|
1130
|
+
* `backgroundColor` / `backgroundAlpha` / `borderColor` / `borderWidth` are
|
|
1131
|
+
* ignored — the asset image carries its own pixels.
|
|
1132
|
+
*/
|
|
1133
|
+
backgroundAssetId?: string;
|
|
1134
|
+
backgroundFrame?: number;
|
|
1135
|
+
backgroundRegion?: string;
|
|
1136
|
+
/** Solid fill colour. Falls back to a transparent panel if omitted. */
|
|
1137
|
+
backgroundColor?: string;
|
|
1138
|
+
/** Background alpha (independent of widget-level alpha). */
|
|
1139
|
+
backgroundAlpha?: number;
|
|
1140
|
+
borderColor?: string | null;
|
|
1141
|
+
borderWidth?: number;
|
|
1142
|
+
/** Border radius for the rect. Defaults to 0. */
|
|
1143
|
+
borderRadius?: number;
|
|
1144
|
+
}
|
|
1145
|
+
export type HudEntity = HudTextEntity | HudImageEntity | HudIconButtonEntity | HudProgressBarEntity | HudPanelEntity;
|
|
1146
|
+
export interface HudScene {
|
|
1147
|
+
schemaVersion: number;
|
|
1148
|
+
id: string;
|
|
1149
|
+
name: string;
|
|
1150
|
+
type: 'hud';
|
|
1151
|
+
design?: {
|
|
1152
|
+
/** e.g. `"16:9"`, `"9:16"`. Free-form for now; the editor uses it for the
|
|
1153
|
+
* preview-frame visualization (design 04 §2.1). v1 doesn't enforce it
|
|
1154
|
+
* at runtime — anchors resolve against the actual canvas size. */
|
|
1155
|
+
designAspectRatio?: string;
|
|
1156
|
+
safeArea?: {
|
|
1157
|
+
top: number;
|
|
1158
|
+
right: number;
|
|
1159
|
+
bottom: number;
|
|
1160
|
+
left: number;
|
|
1161
|
+
};
|
|
1162
|
+
};
|
|
1163
|
+
entities: HudEntity[];
|
|
1164
|
+
metadata?: WorldScene['metadata'];
|
|
1165
|
+
}
|
|
1166
|
+
export type SceneFile = WorldScene | HudScene;
|