@flare-engine/render 0.2.0 → 0.3.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/dist/index.d.ts CHANGED
@@ -1,11 +1,97 @@
1
- import { Rect, Vec2 } from '@flare-engine/math';
2
- import { SkImageLike, SpriteFrame } from '@flare-engine/assets';
3
- import { ComponentDef, World, Query } from '@flare-engine/ecs';
4
-
1
+ import { Rect, Vec2 } from "@flare-engine/math";
2
+ import { SkImageLike, SpriteFrame } from "@flare-engine/assets";
3
+ import { ComponentDef, Query, World } from "@flare-engine/ecs";
4
+ //#region src/camera2d.d.ts
5
+ /** Camera follow smoothing options. */
6
+ interface FollowOptions {
7
+ /** Smoothing factor (0 = instant, lower = faster, default 0.1). */
8
+ smoothing?: number;
9
+ /** Offset from target (e.g. look-ahead). */
10
+ offset?: Vec2;
11
+ /** Dead zone rect — camera won't move while target is inside. */
12
+ deadZone?: Rect;
13
+ }
14
+ /**
15
+ * Camera2D — 2D camera with position, zoom, rotation, follow, and shake.
16
+ * Pure data + methods — no Skia dependency. The renderer reads camera state
17
+ * to build the view transform.
18
+ */
19
+ declare class Camera2D {
20
+ /** Camera position (world-space center). */
21
+ x: number;
22
+ y: number;
23
+ /** Zoom level (1 = 100%). */
24
+ zoom: number;
25
+ /** Rotation in radians. */
26
+ rotation: number;
27
+ /** Viewport width in pixels. */
28
+ viewportWidth: number;
29
+ /** Viewport height in pixels. */
30
+ viewportHeight: number;
31
+ /** Optional world bounds to clamp camera position. */
32
+ bounds: Rect | null;
33
+ private _follow;
34
+ private _shake;
35
+ private _shakePerlin;
36
+ private _shakeSeed;
37
+ private _shakePerlinSeed;
38
+ private _motionScale;
39
+ /** Set viewport dimensions. */
40
+ setViewport(width: number, height: number): void;
41
+ /** Start following a target position. */
42
+ follow(target: Vec2, options?: FollowOptions): void;
43
+ /** Stop following. */
44
+ unfollow(): void;
45
+ /**
46
+ * Global motion multiplier applied to subsequent `shake()` calls. Scales
47
+ * both amplitude and duration. Default 1 (no-op). 0 fully suppresses
48
+ * shake. Intended to be driven by `@flare-engine/a11y` reduced-motion so
49
+ * callers don't have to guard every shake site.
50
+ *
51
+ * Only affects future `shake()` calls — does not retroactively modify an
52
+ * in-flight shake.
53
+ *
54
+ * Clamped to `>= 0`.
55
+ */
56
+ setMotionScale(scale: number): void;
57
+ /** Current motion scale (default 1). */
58
+ get motionScale(): number;
59
+ /** Start camera shake. */
60
+ shake(intensity: number, duration: number): void;
61
+ /**
62
+ * Start a Perlin-noise-based camera shake. Overwrites any in-flight
63
+ * shakePerlin (same semantics as shake()). Respects motionScale.
64
+ */
65
+ shakePerlin(intensity: number, duration: number): void;
66
+ /**
67
+ * Update camera. Call once per frame.
68
+ * @param dt Delta time in seconds.
69
+ */
70
+ update(dt: number): void;
71
+ /** Get the effective camera X (including shake + shakePerlin offsets). */
72
+ get effectiveX(): number;
73
+ /** Get the effective camera Y (including shake + shakePerlin offsets). */
74
+ get effectiveY(): number;
75
+ /** Convert world coordinates to screen coordinates. */
76
+ worldToScreen(worldX: number, worldY: number): Vec2;
77
+ /** Zero-alloc variant — writes screen coordinates into `out`. */
78
+ worldToScreenInto(worldX: number, worldY: number, out: Vec2): Vec2;
79
+ /** Convert screen coordinates to world coordinates. */
80
+ screenToWorld(screenX: number, screenY: number): Vec2;
81
+ /** Zero-alloc variant — writes world coordinates into `out`. */
82
+ screenToWorldInto(screenX: number, screenY: number, out: Vec2): Vec2;
83
+ /** Get the visible world-space bounding rect (allocates a fresh Rect). */
84
+ getBounds(): Rect;
85
+ /** Zero-alloc variant — writes the visible bounds into `out`. */
86
+ getBoundsInto(out: Rect): Rect;
87
+ private _clampToBounds;
88
+ }
89
+ //#endregion
90
+ //#region src/draw-order.d.ts
5
91
  /** A drawable item with a z-order for sorting. */
6
92
  interface Drawable {
7
- /** Z-index for draw ordering. Higher values draw on top. */
8
- z: number;
93
+ /** Z-index for draw ordering. Higher values draw on top. */
94
+ z: number;
9
95
  }
10
96
  /**
11
97
  * Sort an array of drawables by z-index (ascending = back to front).
@@ -24,175 +110,111 @@ declare function sortByDrawOrder<T extends Drawable>(items: T[], length?: number
24
110
  * Stable relative order for equal z values (returns 0).
25
111
  */
26
112
  declare function drawOrderCompare(a: Drawable, b: Drawable): number;
27
-
28
- /** Camera follow smoothing options. */
29
- interface FollowOptions {
30
- /** Smoothing factor (0 = instant, lower = faster, default 0.1). */
31
- smoothing?: number;
32
- /** Offset from target (e.g. look-ahead). */
33
- offset?: Vec2;
34
- /** Dead zone rect — camera won't move while target is inside. */
35
- deadZone?: Rect;
36
- }
37
- /**
38
- * Camera2D — 2D camera with position, zoom, rotation, follow, and shake.
39
- * Pure data + methods — no Skia dependency. The renderer reads camera state
40
- * to build the view transform.
41
- */
42
- declare class Camera2D {
43
- /** Camera position (world-space center). */
44
- x: number;
45
- y: number;
46
- /** Zoom level (1 = 100%). */
47
- zoom: number;
48
- /** Rotation in radians. */
49
- rotation: number;
50
- /** Viewport width in pixels. */
51
- viewportWidth: number;
52
- /** Viewport height in pixels. */
53
- viewportHeight: number;
54
- /** Optional world bounds to clamp camera position. */
55
- bounds: Rect | null;
56
- private _follow;
57
- private _shake;
58
- private _shakePerlin;
59
- private _shakeSeed;
60
- private _shakePerlinSeed;
61
- private _motionScale;
62
- /** Set viewport dimensions. */
63
- setViewport(width: number, height: number): void;
64
- /** Start following a target position. */
65
- follow(target: Vec2, options?: FollowOptions): void;
66
- /** Stop following. */
67
- unfollow(): void;
68
- /**
69
- * Global motion multiplier applied to subsequent `shake()` calls. Scales
70
- * both amplitude and duration. Default 1 (no-op). 0 fully suppresses
71
- * shake. Intended to be driven by `@flare-engine/a11y` reduced-motion so
72
- * callers don't have to guard every shake site.
73
- *
74
- * Only affects future `shake()` calls — does not retroactively modify an
75
- * in-flight shake.
76
- *
77
- * Clamped to `>= 0`.
78
- */
79
- setMotionScale(scale: number): void;
80
- /** Current motion scale (default 1). */
81
- get motionScale(): number;
82
- /** Start camera shake. */
83
- shake(intensity: number, duration: number): void;
84
- /**
85
- * Start a Perlin-noise-based camera shake. Overwrites any in-flight
86
- * shakePerlin (same semantics as shake()). Respects motionScale.
87
- */
88
- shakePerlin(intensity: number, duration: number): void;
89
- /**
90
- * Update camera. Call once per frame.
91
- * @param dt Delta time in seconds.
92
- */
93
- update(dt: number): void;
94
- /** Get the effective camera X (including shake + shakePerlin offsets). */
95
- get effectiveX(): number;
96
- /** Get the effective camera Y (including shake + shakePerlin offsets). */
97
- get effectiveY(): number;
98
- /** Convert world coordinates to screen coordinates. */
99
- worldToScreen(worldX: number, worldY: number): Vec2;
100
- /** Zero-alloc variant — writes screen coordinates into `out`. */
101
- worldToScreenInto(worldX: number, worldY: number, out: Vec2): Vec2;
102
- /** Convert screen coordinates to world coordinates. */
103
- screenToWorld(screenX: number, screenY: number): Vec2;
104
- /** Zero-alloc variant — writes world coordinates into `out`. */
105
- screenToWorldInto(screenX: number, screenY: number, out: Vec2): Vec2;
106
- /** Get the visible world-space bounding rect (allocates a fresh Rect). */
107
- getBounds(): Rect;
108
- /** Zero-alloc variant — writes the visible bounds into `out`. */
109
- getBoundsInto(out: Rect): Rect;
110
- private _clampToBounds;
111
- }
112
-
113
+ //#endregion
114
+ //#region src/renderer.d.ts
113
115
  /** Minimal Skia Canvas-like interface for the Renderer. */
114
116
  interface CanvasLike {
115
- save(): number;
116
- restore(): void;
117
- translate(x: number, y: number): void;
118
- scale(sx: number, sy: number): void;
119
- rotate(radians: number, cx: number, cy: number): void;
120
- clear(color: number): void;
117
+ save(): number;
118
+ restore(): void;
119
+ translate(x: number, y: number): void;
120
+ scale(sx: number, sy: number): void;
121
+ /** Rotate around (cx, cy). Angle in DEGREES — both Skia backends take degrees. */
122
+ rotate(degrees: number, cx: number, cy: number): void;
123
+ clear(color: number): void;
121
124
  }
122
125
  /** Stats tracked per frame. */
123
126
  interface RenderStats {
124
- drawCalls: number;
125
- batchCount: number;
127
+ /**
128
+ * Number of SpriteBatch flush runs emitted this frame — one per contiguous
129
+ * same-key (image, effect, alpha, stretch) run, NOT the number of raw canvas
130
+ * calls. An outline run fans into 9 canvas drawAtlas calls, a stretch run
131
+ * into n drawImageRect calls, a zone-bg run into n drawRect calls.
132
+ * Equal to batchCount whenever SpriteBatch.flush is the only writer;
133
+ * recordDrawCall()/recordBatch() are public and can diverge them if a
134
+ * custom batching system drives them independently.
135
+ */
136
+ drawCalls: number;
137
+ /**
138
+ * Number of SpriteBatch flush runs — incremented alongside drawCalls at
139
+ * the same two sites in SpriteBatch.flush. Equal to drawCalls when only
140
+ * SpriteBatch.flush writes both; recordDrawCall()/recordBatch() are
141
+ * independent public methods and can produce different values if called
142
+ * directly by a custom batching system.
143
+ */
144
+ batchCount: number;
126
145
  }
127
146
  /**
128
147
  * Renderer — manages the Skia Canvas lifecycle for a frame.
129
148
  * `begin()` to start, draw your content, `end()` to finish.
130
149
  */
131
150
  declare class Renderer {
132
- /** Current frame stats. */
133
- readonly stats: RenderStats;
134
- private _canvas;
135
- private _inFrame;
136
- /** Set the canvas to render to. Called once when canvas is available. */
137
- setCanvas(canvas: CanvasLike): void;
138
- /** Get the current canvas. Throws if not in a frame. */
139
- get canvas(): CanvasLike;
140
- /** Whether we are currently between begin() and end(). */
141
- get inFrame(): boolean;
142
- /**
143
- * Begin a new frame. Clears the canvas and resets stats.
144
- * @param clearColor 32-bit ARGB color to clear with (default black = 0xFF000000).
145
- */
146
- begin(clearColor?: number): void;
147
- /**
148
- * End the current frame. Restores canvas state.
149
- */
150
- end(): void;
151
- /**
152
- * Apply camera transform to the canvas.
153
- * Call after begin() and before drawing.
154
- */
155
- applyCamera(cameraX: number, cameraY: number, zoom: number, rotation: number, viewportWidth: number, viewportHeight: number): void;
156
- /** Increment draw call counter. Called by batching systems. */
157
- recordDrawCall(): void;
158
- /** Increment batch counter. */
159
- recordBatch(): void;
160
- }
161
-
151
+ /** Current frame stats. */
152
+ readonly stats: RenderStats;
153
+ private _canvas;
154
+ private _inFrame;
155
+ /** Set the canvas to render to. Called once when canvas is available. */
156
+ setCanvas(canvas: CanvasLike): void;
157
+ /** Get the current canvas. Throws if not in a frame. */
158
+ get canvas(): CanvasLike;
159
+ /** Whether we are currently between begin() and end(). */
160
+ get inFrame(): boolean;
161
+ /**
162
+ * Begin a new frame. Clears the canvas and resets stats.
163
+ * @param clearColor 32-bit ARGB color to clear with (default black = 0xFF000000).
164
+ */
165
+ begin(clearColor?: number): void;
166
+ /**
167
+ * End the current frame. Restores canvas state.
168
+ */
169
+ end(): void;
170
+ /**
171
+ * Apply camera transform to the canvas.
172
+ * Call after begin() and before drawing.
173
+ */
174
+ applyCamera(cameraX: number, cameraY: number, zoom: number, rotation: number, viewportWidth: number, viewportHeight: number): void;
175
+ /** Increment draw call counter. Invoked in tandem with recordBatch, once per
176
+ * flush run in SpriteBatch.flush. */
177
+ recordDrawCall(): void;
178
+ /** Increment batch counter. Invoked in tandem with recordDrawCall, once per
179
+ * flush run in SpriteBatch.flush. */
180
+ recordBatch(): void;
181
+ }
182
+ //#endregion
183
+ //#region src/sprite-effects.d.ts
162
184
  /** Color as RGBA (0-1 per channel). */
163
185
  interface SpriteColor {
164
- readonly r: number;
165
- readonly g: number;
166
- readonly b: number;
167
- readonly a: number;
186
+ readonly r: number;
187
+ readonly g: number;
188
+ readonly b: number;
189
+ readonly a: number;
168
190
  }
169
191
  /** A sprite effect that can be applied during rendering. */
170
192
  interface SpriteEffect {
171
- readonly type: string;
193
+ readonly type: string;
172
194
  }
173
195
  /** Tint effect — blend a color onto the sprite. */
174
196
  interface TintEffect extends SpriteEffect {
175
- readonly type: 'tint';
176
- readonly color: SpriteColor;
177
- readonly amount: number;
197
+ readonly type: 'tint';
198
+ readonly color: SpriteColor;
199
+ readonly amount: number;
178
200
  }
179
201
  /** Flash effect — overlay a solid color that decays. */
180
202
  interface FlashEffect extends SpriteEffect {
181
- readonly type: 'flash';
182
- readonly color: SpriteColor;
183
- readonly duration: number;
184
- readonly elapsed: number;
203
+ readonly type: 'flash';
204
+ readonly color: SpriteColor;
205
+ readonly duration: number;
206
+ readonly elapsed: number;
185
207
  }
186
208
  /** Grayscale effect — desaturate the sprite. */
187
209
  interface GrayscaleEffect extends SpriteEffect {
188
- readonly type: 'grayscale';
189
- readonly amount: number;
210
+ readonly type: 'grayscale';
211
+ readonly amount: number;
190
212
  }
191
213
  /** Outline effect — draw an outline around the sprite. */
192
214
  interface OutlineEffect extends SpriteEffect {
193
- readonly type: 'outline';
194
- readonly color: SpriteColor;
195
- readonly width: number;
215
+ readonly type: 'outline';
216
+ readonly color: SpriteColor;
217
+ readonly width: number;
196
218
  }
197
219
  /**
198
220
  * Opaque pre-compiled shader program. Typically a Skia `RuntimeEffect` built
@@ -205,33 +227,33 @@ type CompiledShader = object;
205
227
  type ShaderUniforms = Record<string, number | readonly number[]>;
206
228
  /** Custom shader effect — apply a pre-compiled runtime shader with uniforms. */
207
229
  interface CustomShaderEffect extends SpriteEffect {
208
- readonly type: 'custom';
209
- readonly shader: CompiledShader;
210
- readonly uniforms: ShaderUniforms;
230
+ readonly type: 'custom';
231
+ readonly shader: CompiledShader;
232
+ readonly uniforms: ShaderUniforms;
211
233
  }
212
234
  /** Water surface effect — animated UV-displaced sprite with foam crests. */
213
235
  interface WaterEffect extends SpriteEffect {
214
- readonly type: 'water';
215
- readonly color: SpriteColor;
216
- readonly foamColor: SpriteColor;
217
- readonly waveSpeed: number;
218
- readonly waveAmplitude: number;
236
+ readonly type: 'water';
237
+ readonly color: SpriteColor;
238
+ readonly foamColor: SpriteColor;
239
+ readonly waveSpeed: number;
240
+ readonly waveAmplitude: number;
219
241
  }
220
242
  /** Fog / volumetric haze effect — animated FBM noise layered over a sprite. */
221
243
  interface FogEffect extends SpriteEffect {
222
- readonly type: 'fog';
223
- readonly color: SpriteColor;
224
- readonly density: number;
225
- readonly speed: number;
226
- readonly scale: number;
244
+ readonly type: 'fog';
245
+ readonly color: SpriteColor;
246
+ readonly density: number;
247
+ readonly speed: number;
248
+ readonly scale: number;
227
249
  }
228
250
  /** Fire / flame effect — value-noise FBM flame rising upward from a sprite. */
229
251
  interface FireEffect extends SpriteEffect {
230
- readonly type: 'fire';
231
- readonly color: SpriteColor;
232
- readonly tipColor: SpriteColor;
233
- readonly speed: number;
234
- readonly intensity: number;
252
+ readonly type: 'fire';
253
+ readonly color: SpriteColor;
254
+ readonly tipColor: SpriteColor;
255
+ readonly speed: number;
256
+ readonly intensity: number;
235
257
  }
236
258
  /**
237
259
  * Drop-shadow effect — a single offset, blurred, tinted copy of the sprite drawn
@@ -240,77 +262,77 @@ interface FireEffect extends SpriteEffect {
240
262
  * the offset, blur, and tint all live in the filter, so no extra draw pass.
241
263
  */
242
264
  interface DropShadowEffect extends SpriteEffect {
243
- readonly type: 'drop-shadow';
244
- /** Horizontal shadow offset in design px. */
245
- readonly offsetX: number;
246
- /** Vertical shadow offset in design px; positive = downward (grounding). */
247
- readonly offsetY: number;
248
- /** Gaussian blur sigma applied to the shadow. 0 = hard edge. */
249
- readonly blur: number;
250
- /** Shadow color; `a` is the shadow opacity. */
251
- readonly color: SpriteColor;
265
+ readonly type: 'drop-shadow';
266
+ /** Horizontal shadow offset in design px. */
267
+ readonly offsetX: number;
268
+ /** Vertical shadow offset in design px; positive = downward (grounding). */
269
+ readonly offsetY: number;
270
+ /** Gaussian blur sigma applied to the shadow. 0 = hard edge. */
271
+ readonly blur: number;
272
+ /** Shadow color; `a` is the shadow opacity. */
273
+ readonly color: SpriteColor;
252
274
  }
253
275
  /** Rooftop city skyline — AABB silhouettes via SDF hash fields. */
254
276
  interface RoofsCityEffect extends SpriteEffect {
255
- readonly type: 'roofs-city';
256
- readonly skyColor: SpriteColor;
257
- readonly roofColor: SpriteColor;
258
- readonly scrollOffsetY: number;
259
- readonly density: number;
260
- /** Destination render width in logical pixels — used as shader resolution.x. */
261
- readonly resolutionW: number;
262
- /** Destination render height in logical pixels — used as shader resolution.y. */
263
- readonly resolutionH: number;
277
+ readonly type: 'roofs-city';
278
+ readonly skyColor: SpriteColor;
279
+ readonly roofColor: SpriteColor;
280
+ readonly scrollOffsetY: number;
281
+ readonly density: number;
282
+ /** Destination render width in logical pixels — used as shader resolution.x. */
283
+ readonly resolutionW: number;
284
+ /** Destination render height in logical pixels — used as shader resolution.y. */
285
+ readonly resolutionH: number;
264
286
  }
265
287
  /** Forest canopy — value-noise tree line. */
266
288
  interface ForestCanopyEffect extends SpriteEffect {
267
- readonly type: 'forest-canopy';
268
- readonly skyColor: SpriteColor;
269
- readonly leafColor: SpriteColor;
270
- readonly scrollOffsetY: number;
271
- readonly density: number;
272
- readonly resolutionW: number;
273
- readonly resolutionH: number;
289
+ readonly type: 'forest-canopy';
290
+ readonly skyColor: SpriteColor;
291
+ readonly leafColor: SpriteColor;
292
+ readonly scrollOffsetY: number;
293
+ readonly density: number;
294
+ readonly resolutionW: number;
295
+ readonly resolutionH: number;
274
296
  }
275
297
  /** River surface — 1D height-field wave with foam crests. */
276
298
  interface RiverWaveEffect extends SpriteEffect {
277
- readonly type: 'river-wave';
278
- readonly waterColor: SpriteColor;
279
- readonly foamColor: SpriteColor;
280
- readonly scrollOffsetY: number;
281
- readonly waveScale: number;
282
- readonly resolutionW: number;
283
- readonly resolutionH: number;
299
+ readonly type: 'river-wave';
300
+ readonly waterColor: SpriteColor;
301
+ readonly foamColor: SpriteColor;
302
+ readonly scrollOffsetY: number;
303
+ readonly waveScale: number;
304
+ readonly resolutionW: number;
305
+ readonly resolutionH: number;
284
306
  }
285
307
  /** Moon surface — polar-SDF craters on a flat disc. */
286
308
  interface MoonCraterEffect extends SpriteEffect {
287
- readonly type: 'moon-crater';
288
- readonly moonColor: SpriteColor;
289
- readonly craterColor: SpriteColor;
290
- readonly scrollOffsetY: number;
291
- readonly craterCount: number;
292
- readonly resolutionW: number;
293
- readonly resolutionH: number;
309
+ readonly type: 'moon-crater';
310
+ readonly moonColor: SpriteColor;
311
+ readonly craterColor: SpriteColor;
312
+ readonly scrollOffsetY: number;
313
+ readonly craterCount: number;
314
+ readonly resolutionW: number;
315
+ readonly resolutionH: number;
294
316
  }
295
317
  /** Cave interior — stalactites hanging from the top edge. */
296
318
  interface CaveInteriorEffect extends SpriteEffect {
297
- readonly type: 'cave-interior';
298
- readonly caveColor: SpriteColor;
299
- readonly glowColor: SpriteColor;
300
- readonly scrollOffsetY: number;
301
- readonly stalactiteCount: number;
302
- readonly resolutionW: number;
303
- readonly resolutionH: number;
319
+ readonly type: 'cave-interior';
320
+ readonly caveColor: SpriteColor;
321
+ readonly glowColor: SpriteColor;
322
+ readonly scrollOffsetY: number;
323
+ readonly stalactiteCount: number;
324
+ readonly resolutionW: number;
325
+ readonly resolutionH: number;
304
326
  }
305
327
  /** Mountain / hill ridge — FBM height-field silhouette. */
306
328
  interface MountainHillsEffect extends SpriteEffect {
307
- readonly type: 'mountain-hills';
308
- readonly skyColor: SpriteColor;
309
- readonly hillColor: SpriteColor;
310
- readonly scrollOffsetY: number;
311
- readonly ridgeScale: number;
312
- readonly resolutionW: number;
313
- readonly resolutionH: number;
329
+ readonly type: 'mountain-hills';
330
+ readonly skyColor: SpriteColor;
331
+ readonly hillColor: SpriteColor;
332
+ readonly scrollOffsetY: number;
333
+ readonly ridgeScale: number;
334
+ readonly resolutionW: number;
335
+ readonly resolutionH: number;
314
336
  }
315
337
  /** Any concrete sprite effect. */
316
338
  type AnySpriteEffect = TintEffect | FlashEffect | GrayscaleEffect | OutlineEffect | CustomShaderEffect | WaterEffect | FogEffect | FireEffect | DropShadowEffect | RoofsCityEffect | ForestCanopyEffect | RiverWaveEffect | MoonCraterEffect | CaveInteriorEffect | MountainHillsEffect;
@@ -335,72 +357,72 @@ declare function fog(color: SpriteColor, density?: number, speed?: number, scale
335
357
  /** Create a fire / flame effect. Time is passed to the renderer each
336
358
  * frame via `beginFrame(w, h, time)` — the effect itself is time-invariant. */
337
359
  declare function fire(opts: {
338
- color: SpriteColor;
339
- tipColor: SpriteColor;
340
- speed?: number;
341
- intensity?: number;
360
+ color: SpriteColor;
361
+ tipColor: SpriteColor;
362
+ speed?: number;
363
+ intensity?: number;
342
364
  }): FireEffect;
343
365
  /** Create a drop-shadow effect. Defaults to a soft shadow offset downward for
344
366
  * grounding; `color.a` is the shadow opacity. */
345
367
  declare function dropShadow(opts: {
346
- color: SpriteColor;
347
- offsetX?: number;
348
- offsetY?: number;
349
- blur?: number;
368
+ color: SpriteColor;
369
+ offsetX?: number;
370
+ offsetY?: number;
371
+ blur?: number;
350
372
  }): DropShadowEffect;
351
373
  /** Create a rooftop city skyline effect. */
352
374
  declare function roofsCity(opts: {
353
- skyColor: SpriteColor;
354
- roofColor: SpriteColor;
355
- scrollOffsetY?: number;
356
- density?: number;
357
- resolutionW?: number;
358
- resolutionH?: number;
375
+ skyColor: SpriteColor;
376
+ roofColor: SpriteColor;
377
+ scrollOffsetY?: number;
378
+ density?: number;
379
+ resolutionW?: number;
380
+ resolutionH?: number;
359
381
  }): RoofsCityEffect;
360
382
  /** Create a forest canopy effect. */
361
383
  declare function forestCanopy(opts: {
362
- skyColor: SpriteColor;
363
- leafColor: SpriteColor;
364
- scrollOffsetY?: number;
365
- density?: number;
366
- resolutionW?: number;
367
- resolutionH?: number;
384
+ skyColor: SpriteColor;
385
+ leafColor: SpriteColor;
386
+ scrollOffsetY?: number;
387
+ density?: number;
388
+ resolutionW?: number;
389
+ resolutionH?: number;
368
390
  }): ForestCanopyEffect;
369
391
  /** Create a river wave surface effect. */
370
392
  declare function riverWave(opts: {
371
- waterColor: SpriteColor;
372
- foamColor: SpriteColor;
373
- scrollOffsetY?: number;
374
- waveScale?: number;
375
- resolutionW?: number;
376
- resolutionH?: number;
393
+ waterColor: SpriteColor;
394
+ foamColor: SpriteColor;
395
+ scrollOffsetY?: number;
396
+ waveScale?: number;
397
+ resolutionW?: number;
398
+ resolutionH?: number;
377
399
  }): RiverWaveEffect;
378
400
  /** Create a moon crater surface effect. */
379
401
  declare function moonCrater(opts: {
380
- moonColor: SpriteColor;
381
- craterColor: SpriteColor;
382
- scrollOffsetY?: number;
383
- craterCount?: number;
384
- resolutionW?: number;
385
- resolutionH?: number;
402
+ moonColor: SpriteColor;
403
+ craterColor: SpriteColor;
404
+ scrollOffsetY?: number;
405
+ craterCount?: number;
406
+ resolutionW?: number;
407
+ resolutionH?: number;
386
408
  }): MoonCraterEffect;
387
409
  /** Create a cave interior effect. */
388
410
  declare function caveInterior(opts: {
389
- caveColor: SpriteColor;
390
- glowColor: SpriteColor;
391
- scrollOffsetY?: number;
392
- stalactiteCount?: number;
393
- resolutionW?: number;
394
- resolutionH?: number;
411
+ caveColor: SpriteColor;
412
+ glowColor: SpriteColor;
413
+ scrollOffsetY?: number;
414
+ stalactiteCount?: number;
415
+ resolutionW?: number;
416
+ resolutionH?: number;
395
417
  }): CaveInteriorEffect;
396
418
  /** Create a mountain / hill ridge effect. */
397
419
  declare function mountainHills(opts: {
398
- skyColor: SpriteColor;
399
- hillColor: SpriteColor;
400
- scrollOffsetY?: number;
401
- ridgeScale?: number;
402
- resolutionW?: number;
403
- resolutionH?: number;
420
+ skyColor: SpriteColor;
421
+ hillColor: SpriteColor;
422
+ scrollOffsetY?: number;
423
+ ridgeScale?: number;
424
+ resolutionW?: number;
425
+ resolutionH?: number;
404
426
  }): MountainHillsEffect;
405
427
  /**
406
428
  * Advance a time-based effect by `dt` seconds.
@@ -417,50 +439,51 @@ declare function updateEffect(effect: AnySpriteEffect, dt: number): AnySpriteEff
417
439
  * Get the current intensity of a flash effect (1 at start, 0 at end).
418
440
  */
419
441
  declare function flashIntensity(effect: FlashEffect): number;
420
-
442
+ //#endregion
443
+ //#region src/sprite-batch.d.ts
421
444
  /** A single sprite draw request in the batch. */
422
445
  interface SpriteDrawCommand {
423
- /** Texture (Skia SkImage). */
424
- image: SkImageLike;
425
- /** Source rectangle in the texture. */
426
- srcX: number;
427
- srcY: number;
428
- srcW: number;
429
- srcH: number;
430
- /** Destination position and size. */
431
- dstX: number;
432
- dstY: number;
433
- dstW: number;
434
- dstH: number;
435
- /** Optional per-sprite effect. Shared by reference across commands in a
436
- * batch — the render adapter applies it once per batch. */
437
- effect: AnySpriteEffect | undefined;
438
- /** Per-instance alpha multiplier. Range [0, 1]. Default 1 (fully opaque).
439
- * Participates in the batch key — sprites with different alphas are
440
- * placed in separate batches so the renderer can apply each alpha once
441
- * per draw call without per-sprite overhead. */
442
- alpha: number;
443
- /** Rotation in radians, clockwise (Skia Y-down convention). Default 0.
444
- * Rotation is applied about the anchor point. */
445
- rotation: number;
446
- /** Anchor X fraction stored for the bridge rotation path. Range [0, 1]. */
447
- anchorX: number;
448
- /** Anchor Y fraction stored for the bridge rotation path. Range [0, 1]. */
449
- anchorY: number;
450
- /**
451
- * When true, the sprite is drawn via `drawImageRect` so `dstW` and `dstH`
452
- * are honored independently (non-uniform scale). A 256×256 source image
453
- * will fill an arbitrary W×H destination rect.
454
- *
455
- * Default (false/absent): drawn via the batched `drawAtlas` path (uniform
456
- * scale — dstW/srcW drives both axes). Stretched sprites are NOT batched
457
- * with regular sprites; each is an independent `drawImageRect` call.
458
- *
459
- * Rotation is unsupported in stretch mode — the RSXform similarity-transform
460
- * limit does not apply, but the bridge draws a plain axis-aligned rect.
461
- * Combine with a zero rotation only.
462
- */
463
- stretch: boolean;
446
+ /** Texture (Skia SkImage). */
447
+ image: SkImageLike;
448
+ /** Source rectangle in the texture. */
449
+ srcX: number;
450
+ srcY: number;
451
+ srcW: number;
452
+ srcH: number;
453
+ /** Destination position and size. */
454
+ dstX: number;
455
+ dstY: number;
456
+ dstW: number;
457
+ dstH: number;
458
+ /** Optional per-sprite effect. Shared by reference across commands in a
459
+ * batch — the render adapter applies it once per batch. */
460
+ effect: AnySpriteEffect | undefined;
461
+ /** Per-instance alpha multiplier. Range [0, 1]. Default 1 (fully opaque).
462
+ * Participates in the batch key — sprites with different alphas are
463
+ * placed in separate batches so the renderer can apply each alpha once
464
+ * per draw call without per-sprite overhead. */
465
+ alpha: number;
466
+ /** Rotation in radians, clockwise (Skia Y-down convention). Default 0.
467
+ * Rotation is applied about the anchor point. */
468
+ rotation: number;
469
+ /** Anchor X fraction stored for the bridge rotation path. Range [0, 1]. */
470
+ anchorX: number;
471
+ /** Anchor Y fraction stored for the bridge rotation path. Range [0, 1]. */
472
+ anchorY: number;
473
+ /**
474
+ * When true, the sprite is drawn via `drawImageRect` so `dstW` and `dstH`
475
+ * are honored independently (non-uniform scale). A 256×256 source image
476
+ * will fill an arbitrary W×H destination rect.
477
+ *
478
+ * Default (false/absent): drawn via the batched `drawAtlas` path (uniform
479
+ * scale — dstW/srcW drives both axes). Stretched sprites are NOT batched
480
+ * with regular sprites; each is an independent `drawImageRect` call.
481
+ *
482
+ * Rotation is unsupported in stretch mode — the RSXform similarity-transform
483
+ * limit does not apply, but the bridge draws a plain axis-aligned rect.
484
+ * Combine with a zero rotation only.
485
+ */
486
+ stretch: boolean;
464
487
  }
465
488
  /**
466
489
  * SpriteBatch — collects sprite draw commands and groups them by texture
@@ -469,57 +492,57 @@ interface SpriteDrawCommand {
469
492
  * Usage:
470
493
  * 1. Call `draw()` for each sprite, optionally passing an effect
471
494
  * 2. Call `flush()` with an atlas draw callback to execute all batched draws
472
- * 3. Batching groups by texture identity, then by effect identity — so
473
- * sharing an effect instance across sprites maximizes batch size
495
+ * 3. Batching groups consecutive commands by `(image, effect, alpha, stretch)`
496
+ * identity, preserving global insertion (z-sorted) order. Sharing an effect
497
+ * reference and the same alpha across consecutive entities maximizes batch
498
+ * size while keeping sprites in correct draw order.
474
499
  *
475
500
  * The actual Skia Atlas draw call is delegated to a callback so this module
476
501
  * remains testable without Skia.
477
502
  */
478
503
  declare class SpriteBatch {
479
- private _commands;
480
- private _pool;
481
- private _batches;
482
- /** Number of queued draw commands. */
483
- get count(): number;
484
- /** Queue a sprite draw from a frame.
485
- *
486
- * `anchorX` / `anchorY` define the pivot point of the sprite as a
487
- * fraction of its destination size — `(0, 0)` = top-left (default),
488
- * `(0.5, 0.5)` = center, `(1, 1)` = bottom-right. `dstX` / `dstY`
489
- * then refer to the anchor point instead of the top-left corner.
490
- *
491
- * `alpha` is a per-instance opacity multiplier in [0, 1]. Default 1. */
492
- draw(image: SkImageLike, frame: SpriteFrame, dstX: number, dstY: number, dstW?: number, dstH?: number, effect?: AnySpriteEffect, anchorX?: number, anchorY?: number, alpha?: number, rotation?: number, stretch?: boolean): void;
493
- /** Queue a raw sprite draw with explicit src/dst rects. `dstX` / `dstY`
494
- * refer to the anchor point — offset by `anchorX * dstW` / `anchorY * dstH`.
495
- *
496
- * `alpha` is a per-instance opacity multiplier in [0, 1]. Default 1. */
497
- drawRaw(image: SkImageLike, srcX: number, srcY: number, srcW: number, srcH: number, dstX: number, dstY: number, dstW: number, dstH: number, effect?: AnySpriteEffect, anchorX?: number, anchorY?: number, alpha?: number, rotation?: number, stretch?: boolean): void;
498
- /**
499
- * Flush all queued commands, grouped by (texture, effect, alpha) identity.
500
- *
501
- * @param drawAtlas Callback invoked once per batch. Receives
502
- * `(image, commands)` — all commands in a batch share the same texture,
503
- * the same effect (readable from `commands[0].effect`), and the same
504
- * alpha (readable from `commands[0].alpha`).
505
- * @param renderer Optional renderer to record stats.
506
- */
507
- flush(drawAtlas: (image: SkImageLike, commands: SpriteDrawCommand[]) => void, renderer?: Renderer): void;
508
- /** Clear all queued commands without drawing. */
509
- clear(): void;
510
- private _acquire;
511
- }
512
-
513
- declare const FIRE_SKSL = "\nuniform shader src;\nuniform float2 resolution;\nuniform float3 color;\nuniform float3 tipColor;\nuniform float speed;\nuniform float intensity;\nuniform float time;\n\nfloat hash(float2 p) { return fract(sin(dot(p, float2(127.1, 311.7))) * 43758.5453); }\n\nfloat vnoise(float2 p) {\n float2 i = floor(p);\n float2 f = fract(p);\n float2 u = f * f * (3.0 - 2.0 * f);\n float a = hash(i);\n float b = hash(i + float2(1.0, 0.0));\n float c = hash(i + float2(0.0, 1.0));\n float d = hash(i + float2(1.0, 1.0));\n return mix(mix(a, b, u.x), mix(c, d, u.x), u.y);\n}\n\nfloat fbm(float2 p) {\n return 0.5 * vnoise(p) + 0.25 * vnoise(p * 2.0);\n}\n\nhalf4 main(float2 coord) {\n half4 srcColor = src.eval(coord);\n float2 uv = coord / max(resolution, float2(1.0));\n float2 np = float2(uv.x * 4.0, uv.y * 6.0 - time * speed * 2.0);\n float n = fbm(np);\n float flameMask = smoothstep(0.0, intensity, n * (1.0 - uv.y));\n float3 fireRgb = mix(color, tipColor, uv.y);\n half4 fireColor = half4(fireRgb.r, fireRgb.g, fireRgb.b, flameMask);\n return half4(fireColor.rgb, fireColor.a * srcColor.a);\n}\n";
514
- declare const FOG_SKSL = "\nuniform shader src;\nuniform float2 resolution;\nuniform float3 color;\nuniform float density;\nuniform float speed;\nuniform float scale;\nuniform float time;\n\nfloat hash(float2 p) {\n return fract(sin(dot(p, float2(127.1, 311.7))) * 43758.5453);\n}\n\nfloat valueNoise(float2 p) {\n float2 i = floor(p);\n float2 f = fract(p);\n float2 u = f * f * (3.0 - 2.0 * f);\n float a = hash(i);\n float b = hash(i + float2(1.0, 0.0));\n float c = hash(i + float2(0.0, 1.0));\n float d = hash(i + float2(1.0, 1.0));\n return mix(mix(a, b, u.x), mix(c, d, u.x), u.y);\n}\n\nfloat fbm(float2 p) {\n float value = 0.0;\n float amplitude = 0.5;\n for (int i = 0; i < 4; i++) {\n value += amplitude * valueNoise(p);\n p *= 2.0;\n amplitude *= 0.5;\n }\n return value;\n}\n\nhalf4 main(float2 coord) {\n float2 uv = coord / max(resolution, float2(1.0));\n float2 drift = float2(time * speed, 0.3 * time * speed);\n float n = clamp(fbm(uv * scale + drift), 0.0, 1.0);\n half4 srcColor = src.eval(coord);\n half3 blended = mix(srcColor.rgb, half3(color.r, color.g, color.b), half(density * n));\n return half4(blended, srcColor.a);\n}\n";
515
- declare const WATER_SKSL = "\nuniform shader src;\nuniform float2 resolution;\nuniform float3 color;\nuniform float3 foamColor;\nuniform float waveSpeed;\nuniform float waveAmplitude;\nuniform float time;\n\nhalf4 main(float2 coord) {\n float2 uv = coord / resolution;\n float wave1 = sin(uv.x * 6.2832 * 3.0 + time * waveSpeed * 2.0) * waveAmplitude;\n float wave2 = sin(uv.x * 6.2832 * 5.0 - time * waveSpeed * 1.3 + 1.5) * waveAmplitude * 0.5;\n float totalWave = wave1 + wave2;\n float2 displaced = float2(uv.x, uv.y + totalWave);\n displaced = clamp(displaced, float2(0.0), float2(1.0));\n half4 c = src.eval(displaced * resolution);\n float waveDeriv = cos(uv.x * 6.2832 * 3.0 + time * waveSpeed * 2.0) * waveAmplitude * 2.0\n + cos(uv.x * 6.2832 * 5.0 - time * waveSpeed * 1.3 + 1.5) * waveAmplitude;\n float foam = smoothstep(waveAmplitude * 0.6, waveAmplitude * 1.5, waveDeriv);\n half3 tinted = mix(c.rgb, half3(color.r, color.g, color.b), half(0.35));\n half3 final = mix(tinted, half3(foamColor.r, foamColor.g, foamColor.b), half(foam));\n return half4(final, c.a);\n}\n";
516
- declare const ROOFS_CITY_SKSL = "\nuniform float2 resolution;\nuniform float3 skyColor;\nuniform float3 roofColor;\nuniform float scrollOffsetY;\nuniform float density;\n\nfloat hash(float x) { return fract(sin(x * 127.1) * 43758.5453); }\n\nfloat buildingTop(float2 uv, float density) {\n float col = floor(uv.x * density);\n float h = 0.35 + 0.35 * hash(col);\n float w = 0.6 + 0.3 * hash(col + 7.3);\n float edge = step(abs(fract(uv.x * density) - 0.5), w * 0.5);\n return edge * step(uv.y, h);\n}\n\nhalf4 main(float2 coord) {\n float2 uv = coord / max(resolution, float2(1.0));\n uv.y = uv.y + scrollOffsetY / max(resolution.y, 1.0);\n uv.y = fract(uv.y);\n float buildings = buildingTop(uv, density);\n half3 col = mix(half3(skyColor.r, skyColor.g, skyColor.b),\n half3(roofColor.r, roofColor.g, roofColor.b),\n half(buildings));\n return half4(col, 1.0);\n}\n";
517
- declare const FOREST_CANOPY_SKSL = "\nuniform float2 resolution;\nuniform float3 skyColor;\nuniform float3 leafColor;\nuniform float scrollOffsetY;\nuniform float density;\n\nfloat hash(float2 p) { return fract(sin(dot(p, float2(127.1, 311.7))) * 43758.5453); }\n\nfloat valueNoise(float2 p) {\n float2 i = floor(p);\n float2 f = fract(p);\n float2 u = f * f * (3.0 - 2.0 * f);\n return mix(mix(hash(i), hash(i + float2(1.0, 0.0)), u.x),\n mix(hash(i + float2(0.0, 1.0)), hash(i + float2(1.0, 1.0)), u.x), u.y);\n}\n\nhalf4 main(float2 coord) {\n float2 uv = coord / max(resolution, float2(1.0));\n uv.y = uv.y + scrollOffsetY / max(resolution.y, 1.0);\n float n = valueNoise(float2(uv.x * density, uv.y * 3.0));\n float treeLine = 0.55 + 0.25 * valueNoise(float2(uv.x * density * 0.5, 0.0));\n float canopy = step(uv.y, treeLine) * smoothstep(0.3, 0.7, n);\n half3 col = mix(half3(skyColor.r, skyColor.g, skyColor.b),\n half3(leafColor.r, leafColor.g, leafColor.b),\n half(canopy));\n return half4(col, 1.0);\n}\n";
518
- declare const RIVER_WAVE_SKSL = "\nuniform float2 resolution;\nuniform float3 waterColor;\nuniform float3 foamColor;\nuniform float scrollOffsetY;\nuniform float waveScale;\n\nhalf4 main(float2 coord) {\n float2 uv = coord / max(resolution, float2(1.0));\n float scroll = scrollOffsetY / max(resolution.y, 1.0);\n float2 p = float2(uv.x, uv.y + scroll);\n float w1 = sin(p.x * 6.2832 * waveScale + p.y * 4.0) * 0.04;\n float w2 = sin(p.x * 6.2832 * waveScale * 1.7 - p.y * 2.5 + 1.2) * 0.02;\n float surface = 0.5 + w1 + w2;\n float inWater = step(uv.y, surface);\n float crest = smoothstep(surface - 0.015, surface, uv.y) * (1.0 - inWater) +\n smoothstep(surface + 0.015, surface, uv.y) * inWater;\n half3 base = mix(half3(foamColor.r, foamColor.g, foamColor.b),\n half3(waterColor.r, waterColor.g, waterColor.b),\n half(inWater));\n half3 col = mix(base, half3(foamColor.r, foamColor.g, foamColor.b), half(crest * 0.6));\n return half4(col, 1.0);\n}\n";
519
- declare const MOON_CRATER_SKSL = "\nuniform float2 resolution;\nuniform float3 moonColor;\nuniform float3 craterColor;\nuniform float scrollOffsetY;\nuniform float craterCount;\n\nfloat hash(float2 p) { return fract(sin(dot(p, float2(127.1, 311.7))) * 43758.5453); }\n\nfloat crater(float2 uv, float2 center, float r) {\n float d = length(uv - center);\n float rim = smoothstep(r, r * 0.85, d) - smoothstep(r * 0.85, r * 0.65, d);\n float floor_ = smoothstep(r * 0.65, r * 0.4, d);\n return rim * 0.6 + floor_ * 0.25;\n}\n\nhalf4 main(float2 coord) {\n float2 uv = coord / max(resolution, float2(1.0));\n uv.y = uv.y + scrollOffsetY / max(resolution.y, 1.0);\n float c = 0.0;\n for (int i = 0; i < 6; i++) {\n float fi = float(i);\n float2 center = float2(hash(float2(fi, 0.0)), hash(float2(fi, 1.0)));\n float r = 0.04 + 0.07 * hash(float2(fi, 2.0));\n c += crater(fract(uv), center, r) * step(fi, craterCount - 1.0);\n }\n c = clamp(c, 0.0, 1.0);\n half3 col = mix(half3(moonColor.r, moonColor.g, moonColor.b),\n half3(craterColor.r, craterColor.g, craterColor.b),\n half(c));\n return half4(col, 1.0);\n}\n";
520
- declare const CAVE_INTERIOR_SKSL = "\nuniform float2 resolution;\nuniform float3 caveColor;\nuniform float3 glowColor;\nuniform float scrollOffsetY;\nuniform float stalactiteCount;\n\nfloat hash(float x) { return fract(sin(x * 127.1) * 43758.5453); }\n\nfloat stalactite(float2 uv, float count) {\n float col = floor(uv.x * count);\n float tip = 0.1 + 0.2 * hash(col);\n float w = 0.3 + 0.3 * hash(col + 5.0);\n float dx = abs(fract(uv.x * count) - 0.5) / (w * 0.5);\n float shape = step(dx, 1.0) * step(uv.y, tip * (1.0 - clamp(dx, 0.0, 1.0)));\n return shape;\n}\n\nhalf4 main(float2 coord) {\n float2 uv = coord / max(resolution, float2(1.0));\n uv.y = 1.0 - uv.y;\n uv.y = uv.y + scrollOffsetY / max(resolution.y, 1.0);\n float stalac = stalactite(uv, stalactiteCount);\n float glow = smoothstep(0.8, 1.0, uv.y) * (1.0 - stalac);\n half3 col = mix(half3(caveColor.r, caveColor.g, caveColor.b),\n half3(glowColor.r, glowColor.g, glowColor.b),\n half(glow));\n col = mix(col, half3(caveColor.r, caveColor.g, caveColor.b) * half(0.3), half(stalac));\n return half4(col, 1.0);\n}\n";
521
- declare const MOUNTAIN_HILLS_SKSL = "\nuniform float2 resolution;\nuniform float3 skyColor;\nuniform float3 hillColor;\nuniform float scrollOffsetY;\nuniform float ridgeScale;\n\nfloat hash(float x) { return fract(sin(x * 127.1) * 43758.5453); }\n\nfloat heightField(float x, float scale) {\n float h = 0.0;\n float amp = 0.5;\n float freq = scale;\n for (int i = 0; i < 4; i++) {\n float ix = floor(x * freq);\n float fx = fract(x * freq);\n fx = fx * fx * (3.0 - 2.0 * fx);\n h += amp * mix(hash(ix), hash(ix + 1.0), fx);\n freq *= 2.0;\n amp *= 0.5;\n }\n return h;\n}\n\nhalf4 main(float2 coord) {\n float2 uv = coord / max(resolution, float2(1.0));\n float scroll = scrollOffsetY / max(resolution.y, 1.0);\n float horizon = 0.4 + 0.3 * heightField(uv.x + scroll * 0.3, ridgeScale);\n float mountains = step(uv.y, horizon);\n half3 col = mix(half3(skyColor.r, skyColor.g, skyColor.b),\n half3(hillColor.r, hillColor.g, hillColor.b),\n half(mountains));\n return half4(col, 1.0);\n}\n";
522
-
504
+ private _commands;
505
+ private _pool;
506
+ private _batches;
507
+ private _allocCount;
508
+ /** Number of queued draw commands. */
509
+ get count(): number;
510
+ /** Cumulative number of SpriteDrawCommand objects allocated because the internal
511
+ * command pool was empty. Steady state should be 0 after warm-up. */
512
+ get allocCount(): number;
513
+ /** Queue a sprite draw from a frame.
514
+ *
515
+ * `anchorX` / `anchorY` define the pivot point of the sprite as a
516
+ * fraction of its destination size — `(0, 0)` = top-left (default),
517
+ * `(0.5, 0.5)` = center, `(1, 1)` = bottom-right. `dstX` / `dstY`
518
+ * then refer to the anchor point instead of the top-left corner.
519
+ *
520
+ * `alpha` is a per-instance opacity multiplier in [0, 1]. Default 1. */
521
+ draw(image: SkImageLike, frame: SpriteFrame, dstX: number, dstY: number, dstW?: number, dstH?: number, effect?: AnySpriteEffect, anchorX?: number, anchorY?: number, alpha?: number, rotation?: number, stretch?: boolean): void;
522
+ /** Queue a raw sprite draw with explicit src/dst rects. `dstX` / `dstY`
523
+ * refer to the anchor point — offset by `anchorX * dstW` / `anchorY * dstH`.
524
+ *
525
+ * `alpha` is a per-instance opacity multiplier in [0, 1]. Default 1. */
526
+ drawRaw(image: SkImageLike, srcX: number, srcY: number, srcW: number, srcH: number, dstX: number, dstY: number, dstW: number, dstH: number, effect?: AnySpriteEffect, anchorX?: number, anchorY?: number, alpha?: number, rotation?: number, stretch?: boolean): void;
527
+ /**
528
+ * Flush all queued commands in insertion order, grouping maximal consecutive
529
+ * runs that share the same `(image, effect, alpha, stretch)` key into a
530
+ * single `drawAtlas` call. This preserves global z-order (the caller is
531
+ * responsible for queuing in z-sorted order) while still batching runs of
532
+ * same-key sprites for efficiency.
533
+ *
534
+ * @param drawAtlas Callback invoked once per run. Receives
535
+ * `(image, commands)` — all commands in a run share the same key and are
536
+ * contiguous in the original insertion order.
537
+ * @param renderer Optional renderer to record stats.
538
+ */
539
+ flush(drawAtlas: (image: SkImageLike, commands: SpriteDrawCommand[]) => void, renderer?: Renderer): void;
540
+ /** Clear all queued commands without drawing. */
541
+ clear(): void;
542
+ private _acquire;
543
+ }
544
+ //#endregion
545
+ //#region src/frame-renderer.d.ts
523
546
  /** A 4-float RGBA color buffer (r, g, b, a in [0, 1]). */
524
547
  type ColorLike = Float32Array;
525
548
  /**
@@ -530,11 +553,11 @@ type ColorLike = Float32Array;
530
553
  * interface in contra-variant parameter positions.
531
554
  */
532
555
  interface RectLike {
533
- x: number;
534
- y: number;
535
- width: number;
536
- height: number;
537
- setXYWH(x: number, y: number, w: number, h: number): void;
556
+ x: number;
557
+ y: number;
558
+ width: number;
559
+ height: number;
560
+ setXYWH(x: number, y: number, w: number, h: number): void;
538
561
  }
539
562
  /**
540
563
  * A mutable similarity transform.
@@ -544,57 +567,57 @@ interface RectLike {
544
567
  * RN-Skia's `SkRSXform` exposes all four fields.
545
568
  */
546
569
  interface RSXformLike {
547
- scos: number;
548
- ssin: number;
549
- tx: number;
550
- ty: number;
551
- set(scos: number, ssin: number, tx: number, ty: number): void;
570
+ scos: number;
571
+ ssin: number;
572
+ tx: number;
573
+ ty: number;
574
+ set(scos: number, ssin: number, tx: number, ty: number): void;
552
575
  }
553
576
  /** A compiled SkSL runtime effect. */
554
577
  interface RuntimeEffectLike {
555
- makeShader(uniforms: number[]): unknown;
556
- getUniformCount(): number;
557
- getUniformFloatCount(): number;
558
- getUniformName(index: number): string;
559
- getUniform(index: number): {
560
- columns: number;
561
- rows: number;
562
- slot: number;
563
- };
578
+ makeShader(uniforms: number[]): unknown;
579
+ getUniformCount(): number;
580
+ getUniformFloatCount(): number;
581
+ getUniformName(index: number): string;
582
+ getUniform(index: number): {
583
+ columns: number;
584
+ rows: number;
585
+ slot: number;
586
+ };
564
587
  }
565
588
  /** Color filter factory — subset used by the frame-renderer. */
566
589
  interface ColorFilterFactoryLike {
567
- MakeBlend(color: Float32Array, mode: number): unknown;
568
- MakeMatrix(matrix: number[]): unknown;
590
+ MakeBlend(color: Float32Array, mode: number): unknown;
591
+ MakeMatrix(matrix: number[]): unknown;
569
592
  }
570
593
  /** Image filter factory — optional; absent on CPU/test surfaces. */
571
594
  interface ImageFilterFactoryLike {
572
- MakeDropShadow(dx: number, dy: number, sigmaX: number, sigmaY: number, color: Float32Array, input: null): unknown;
595
+ MakeDropShadow(dx: number, dy: number, sigmaX: number, sigmaY: number, color: Float32Array, input: null): unknown;
573
596
  }
574
597
  /** Runtime-effect factory — optional; absent when SkSL is unsupported. */
575
598
  interface RuntimeEffectFactoryLike {
576
- Make(sksl: string): RuntimeEffectLike | null;
599
+ Make(sksl: string): RuntimeEffectLike | null;
577
600
  }
578
601
  /** Paint — the writable subset the frame-renderer mutates. */
579
602
  interface PaintLike {
580
- setAlphaf(alpha: number): void;
581
- setColor?(color: Float32Array): void;
582
- setColorFilter(filter: unknown): void;
583
- setShader(shader: unknown): void;
584
- setImageFilter(filter: unknown): void;
603
+ setAlphaf(alpha: number): void;
604
+ setColor?(color: Float32Array): void;
605
+ setColorFilter(filter: unknown): void;
606
+ setShader(shader: unknown): void;
607
+ setImageFilter(filter: unknown): void;
585
608
  }
586
609
  /** Minimal draw surface the frame-renderer calls into. */
587
610
  interface DrawSurfaceLike {
588
- save(): number;
589
- restore(): void;
590
- translate(x: number, y: number): void;
591
- scale(sx: number, sy: number): void;
592
- /** rotate(radians, cx, cy) — always in radians. Web shims must convert. */
593
- rotate(radians: number, cx: number, cy: number): void;
594
- clear(color: Float32Array): void;
595
- drawAtlas(image: unknown, srcRects: RectLike[], dstXforms: RSXformLike[], paint: PaintLike): void;
596
- drawImageRect(image: unknown, src: RectLike, dst: RectLike, paint: PaintLike): void;
597
- drawRect(rect: RectLike, paint: PaintLike): void;
611
+ save(): number;
612
+ restore(): void;
613
+ translate(x: number, y: number): void;
614
+ scale(sx: number, sy: number): void;
615
+ /** rotate(degrees, cx, cy) — DEGREES, forwarded verbatim (both Skia backends take degrees). */
616
+ rotate(degrees: number, cx: number, cy: number): void;
617
+ clear(color: Float32Array): void;
618
+ drawAtlas(image: unknown, srcRects: RectLike[], dstXforms: RSXformLike[], paint: PaintLike): void;
619
+ drawImageRect(image: unknown, src: RectLike, dst: RectLike, paint: PaintLike): void;
620
+ drawRect(rect: RectLike, paint: PaintLike): void;
598
621
  }
599
622
  /**
600
623
  * Opaque picture (recorded draw commands).
@@ -605,8 +628,8 @@ interface DrawSurfaceLike {
605
628
  type PictureLike = object;
606
629
  /** Picture recorder. */
607
630
  interface PictureRecorderLike {
608
- beginRecording(bounds: RectLike): DrawSurfaceLike;
609
- finishRecordingAsPicture(): PictureLike;
631
+ beginRecording(bounds: RectLike): DrawSurfaceLike;
632
+ finishRecordingAsPicture(): PictureLike;
610
633
  }
611
634
  /**
612
635
  * Minimal structural view of the Skia module the frame-renderer needs.
@@ -617,47 +640,47 @@ interface PictureRecorderLike {
617
640
  * on CPU surfaces and in test mocks.
618
641
  */
619
642
  interface FrameRendererModule {
620
- XYWHRect(x: number, y: number, w: number, h: number): RectLike;
621
- RSXform(scos: number, ssin: number, tx: number, ty: number): RSXformLike;
622
- Paint(): PaintLike;
623
- PictureRecorder(): PictureRecorderLike;
624
- ColorFilter: ColorFilterFactoryLike;
625
- RuntimeEffect?: RuntimeEffectFactoryLike;
626
- ImageFilter?: ImageFilterFactoryLike;
643
+ XYWHRect(x: number, y: number, w: number, h: number): RectLike;
644
+ RSXform(scos: number, ssin: number, tx: number, ty: number): RSXformLike;
645
+ Paint(): PaintLike;
646
+ PictureRecorder(): PictureRecorderLike;
647
+ ColorFilter: ColorFilterFactoryLike;
648
+ RuntimeEffect?: RuntimeEffectFactoryLike;
649
+ ImageFilter?: ImageFilterFactoryLike;
627
650
  }
628
651
  /**
629
652
  * A single compiled postfx pass descriptor produced by the runtime.
630
653
  */
631
654
  interface FrameRendererCompiledPass {
632
- readonly id: string;
633
- readonly shader: {
634
- makeShaderWithChildren(uniforms: number[], children: unknown[]): unknown | null;
635
- getUniformCount?(): number;
636
- getUniformName?(index: number): string;
637
- getUniform?(index: number): {
638
- columns: number;
639
- rows: number;
640
- slot: number;
641
- };
655
+ readonly id: string;
656
+ readonly shader: {
657
+ makeShaderWithChildren(uniforms: number[], children: unknown[]): unknown | null;
658
+ getUniformCount?(): number;
659
+ getUniformName?(index: number): string;
660
+ getUniform?(index: number): {
661
+ columns: number;
662
+ rows: number;
663
+ slot: number;
642
664
  };
643
- readonly uniforms: Float32Array;
644
- readonly children: readonly unknown[];
665
+ };
666
+ readonly uniforms: Float32Array;
667
+ readonly children: readonly unknown[];
645
668
  }
646
669
  /** Minimal structural view of a PostFxRuntime that the frame-renderer needs. */
647
670
  interface FrameRendererPostFxRuntime {
648
- readonly supported: boolean;
649
- buildPass?(chain: FrameRendererPostFxChain, width: number, height: number): readonly FrameRendererCompiledPass[];
671
+ readonly supported: boolean;
672
+ buildPass?(chain: FrameRendererPostFxChain, width: number, height: number): readonly FrameRendererCompiledPass[];
650
673
  }
651
674
  /** Minimal structural view of an EffectChain the frame-renderer needs. */
652
675
  interface FrameRendererPostFxChain {
653
- enabled(): Iterable<unknown>;
676
+ enabled(): Iterable<unknown>;
654
677
  }
655
678
  /** A resolved postfx pass descriptor with named uniforms (for React JSX). */
656
679
  interface FrameRendererPostFxDescriptor {
657
- readonly id: string;
658
- readonly source: unknown;
659
- readonly uniforms: Record<string, number | readonly number[]>;
660
- readonly children: readonly unknown[];
680
+ readonly id: string;
681
+ readonly source: unknown;
682
+ readonly uniforms: Record<string, number | readonly number[]>;
683
+ readonly children: readonly unknown[];
661
684
  }
662
685
  /**
663
686
  * Frame renderer that bridges a `SpriteBatch` to a picture-backed draw surface.
@@ -668,12 +691,18 @@ interface FrameRendererPostFxDescriptor {
668
691
  * 3. Call `endFrame()` — returns the recorded picture.
669
692
  */
670
693
  interface FrameRenderer {
671
- readonly canvas: CanvasLike;
672
- beginFrame(viewportWidth: number, viewportHeight: number, time?: number, pixelDensity?: number): void;
673
- drawAtlas(image: SkImageLike, commands: SpriteDrawCommand[]): void;
674
- buildPostFxPasses(runtime: FrameRendererPostFxRuntime | null, chain: FrameRendererPostFxChain | null, width: number, height: number): readonly FrameRendererPostFxDescriptor[];
675
- setPostFxBinding(runtime: FrameRendererPostFxRuntime | null, chain: FrameRendererPostFxChain | null): void;
676
- endFrame(): PictureLike | null;
694
+ readonly canvas: CanvasLike;
695
+ beginFrame(viewportWidth: number, viewportHeight: number, time?: number, pixelDensity?: number): void;
696
+ drawAtlas(image: SkImageLike, commands: SpriteDrawCommand[]): void;
697
+ buildPostFxPasses(runtime: FrameRendererPostFxRuntime | null, chain: FrameRendererPostFxChain | null, width: number, height: number): readonly FrameRendererPostFxDescriptor[];
698
+ setPostFxBinding(runtime: FrameRendererPostFxRuntime | null, chain: FrameRendererPostFxChain | null): void;
699
+ /**
700
+ * Whether `buildPostFxPasses` would attempt to build passes from the bound
701
+ * runtime and chain. Pure null/flag checks — never calls `runtime.buildPass`,
702
+ * so it is safe on platforms where building throws (React Native Web).
703
+ */
704
+ hasPostFxBinding(): boolean;
705
+ endFrame(): PictureLike | null;
677
706
  }
678
707
  /**
679
708
  * Create a `FrameRenderer` backed by the given structural Skia module.
@@ -682,98 +711,71 @@ interface FrameRenderer {
682
711
  * `@flare-engine/render-web` (passing a CanvasKit shim) use this factory.
683
712
  */
684
713
  declare function createFrameRenderer(Skia: FrameRendererModule): FrameRenderer;
685
-
686
- /** A single glyph in a bitmap font. */
687
- interface Glyph {
688
- /** Character code (Unicode). */
689
- id: number;
690
- /** Source rect in the font texture. */
691
- x: number;
692
- y: number;
693
- width: number;
694
- height: number;
695
- /** Draw offset. */
696
- xOffset: number;
697
- yOffset: number;
698
- /** Horizontal advance after this glyph. */
699
- xAdvance: number;
700
- }
701
- /** Kerning pair. */
702
- interface KerningPair {
703
- first: number;
704
- second: number;
705
- amount: number;
706
- }
707
- /** Parsed bitmap font data. */
708
- interface BitmapFont {
709
- /** Line height in pixels. */
710
- lineHeight: number;
711
- /** Baseline offset. */
712
- base: number;
713
- /** Glyph map by character code. */
714
- glyphs: Map<number, Glyph>;
715
- /** Kerning map. Key = `first << 16 | second`. */
716
- kernings: Map<number, number>;
717
- }
714
+ /** Zero both branch counters unconditionally (no __DEV__ guard). */
715
+ declare function resetXformCounters(): void;
718
716
  /**
719
- * Parse BMFont JSON format (common output from tools like Hiero, BMFont, msdf-bmfont).
720
- */
721
- declare function parseBMFontJSON(json: unknown): BitmapFont;
722
- /** Get kerning between two characters. */
723
- declare function getKerning(font: BitmapFont, first: number, second: number): number;
724
- /** Measure the width of a text string in pixels. */
725
- declare function measureText(font: BitmapFont, text: string): number;
726
- /**
727
- * Draw text using a SpriteBatch.
728
- * Characters are batched as individual sprites from the font texture.
717
+ * Cumulative `fillXforms` branch hit counts since the last reset. Both stay 0
718
+ * when `__DEV__` is falsy or absent.
719
+ *
720
+ * These count transform WRITES, not sprites: a run drawn with an outline effect
721
+ * passes through `fillXforms` 9 times (8 offset passes + 1 main pass), so it
722
+ * contributes 9 hits per command — the transform work the renderer actually
723
+ * performs. The counters are process-global: every `createFrameRenderer`
724
+ * instance increments the same pair, so readings sum across live renderers.
725
+ *
726
+ * Allocates one object per call — never call it from the render loop.
729
727
  */
730
- declare function drawText(batch: SpriteBatch, font: BitmapFont, image: SkImageLike, text: string, x: number, y: number, scale?: number): void;
731
-
728
+ declare function getXformCounters(): {
729
+ rotated: number;
730
+ unrotated: number;
731
+ };
732
+ //#endregion
733
+ //#region src/render-system.d.ts
732
734
  /** Position component data expected by the render system. */
733
735
  interface PositionData {
734
- x: number;
735
- y: number;
736
+ x: number;
737
+ y: number;
736
738
  }
737
739
  /** Sprite anchor as a fraction of the destination rect — `(0, 0)` is
738
740
  * top-left (default), `(0.5, 0.5)` is center, `(1, 1)` is bottom-right.
739
741
  * When present, `Position` refers to the anchor point rather than the
740
742
  * sprite's top-left corner. */
741
743
  interface SpriteAnchor {
742
- x: number;
743
- y: number;
744
+ x: number;
745
+ y: number;
744
746
  }
745
747
  /** Sprite component data expected by the render system. */
746
748
  interface SpriteData {
747
- image: SkImageLike;
748
- frame: SpriteFrame;
749
- z: number;
750
- width?: number;
751
- height?: number;
752
- /** Optional per-sprite effect (tint, flash, grayscale, outline, custom).
753
- * Shared instances are batched together — prefer reusing an effect
754
- * reference across entities that should render identically. */
755
- effect?: AnySpriteEffect;
756
- /** Optional pivot point for the sprite. Defaults to top-left `(0, 0)`. */
757
- anchor?: SpriteAnchor;
758
- /** Per-instance alpha multiplier. Range [0, 1]. Default 1 (fully opaque).
759
- * Mutate directly each frame — e.g. `sprite.alpha = lerp(1, 0, t)`.
760
- * Sprites with different alphas are placed in separate draw batches. */
761
- alpha?: number;
762
- /** Rotation in radians, clockwise (Skia Y-down convention). Default 0.
763
- * Applied about the sprite's anchor point. Non-uniform scale combined with
764
- * rotation is unsupported — scale is inferred uniformly from dstW/srcW. */
765
- rotation?: number;
766
- /**
767
- * When true, draw via `drawImageRect` so `width` and `height` are honored
768
- * independently (non-uniform scale). Use for background tiles or any sprite
769
- * that must fill a non-square destination rect exactly.
770
- *
771
- * Default (absent/false): drawn via the batched `drawAtlas` uniform-scale
772
- * path. Stretched sprites are NOT batched with regular sprites.
773
- *
774
- * Rotation is unsupported in stretch mode — combine only with `rotation = 0`.
775
- */
776
- stretch?: boolean;
749
+ image: SkImageLike;
750
+ frame: SpriteFrame;
751
+ z: number;
752
+ width?: number;
753
+ height?: number;
754
+ /** Optional per-sprite effect (tint, flash, grayscale, outline, custom).
755
+ * Shared instances are batched together — prefer reusing an effect
756
+ * reference across entities that should render identically. */
757
+ effect?: AnySpriteEffect;
758
+ /** Optional pivot point for the sprite. Defaults to top-left `(0, 0)`. */
759
+ anchor?: SpriteAnchor;
760
+ /** Per-instance alpha multiplier. Range [0, 1]. Default 1 (fully opaque).
761
+ * Mutate directly each frame — e.g. `sprite.alpha = lerp(1, 0, t)`.
762
+ * Sprites with different alphas are placed in separate draw batches. */
763
+ alpha?: number;
764
+ /** Rotation in radians, clockwise (Skia Y-down convention). Default 0.
765
+ * Applied about the sprite's anchor point. Non-uniform scale combined with
766
+ * rotation is unsupported — scale is inferred uniformly from dstW/srcW. */
767
+ rotation?: number;
768
+ /**
769
+ * When true, draw via `drawImageRect` so `width` and `height` are honored
770
+ * independently (non-uniform scale). Use for background tiles or any sprite
771
+ * that must fill a non-square destination rect exactly.
772
+ *
773
+ * Default (absent/false): drawn via the batched `drawAtlas` uniform-scale
774
+ * path. Stretched sprites are NOT batched with regular sprites.
775
+ *
776
+ * Rotation is unsupported in stretch mode — combine only with `rotation = 0`.
777
+ */
778
+ stretch?: boolean;
777
779
  }
778
780
  /**
779
781
  * Create a render system function.
@@ -796,5 +798,64 @@ interface SpriteData {
796
798
  declare function createRenderSystem(world: World, query: Query, positionDef: ComponentDef<PositionData>, spriteDef: ComponentDef<SpriteData>, batch: SpriteBatch): () => void;
797
799
  /** Canonical Sprite component — queried by the render system alongside `Position`. */
798
800
  declare const Sprite: ComponentDef<SpriteData>;
799
-
800
- export { type AnySpriteEffect, type BitmapFont, CAVE_INTERIOR_SKSL, Camera2D, type CanvasLike, type CaveInteriorEffect, type ColorFilterFactoryLike, type ColorLike, type CompiledShader, type CustomShaderEffect, type DrawSurfaceLike, type Drawable, type DropShadowEffect, FIRE_SKSL, FOG_SKSL, FOREST_CANOPY_SKSL, type FireEffect, type FlashEffect, type FogEffect, type FollowOptions, type ForestCanopyEffect, type FrameRenderer, type FrameRendererCompiledPass, type FrameRendererModule, type FrameRendererPostFxChain, type FrameRendererPostFxDescriptor, type FrameRendererPostFxRuntime, type Glyph, type GrayscaleEffect, type ImageFilterFactoryLike, type KerningPair, MOON_CRATER_SKSL, MOUNTAIN_HILLS_SKSL, type MoonCraterEffect, type MountainHillsEffect, type OutlineEffect, type PaintLike, type PictureLike, type PictureRecorderLike, type PositionData, RIVER_WAVE_SKSL, ROOFS_CITY_SKSL, type RSXformLike, type RectLike, type RenderStats, Renderer, type RiverWaveEffect, type RoofsCityEffect, type RuntimeEffectFactoryLike, type RuntimeEffectLike, type ShaderUniforms, Sprite, type SpriteAnchor, SpriteBatch, type SpriteColor, type SpriteData, type SpriteDrawCommand, type SpriteEffect, type TintEffect, WATER_SKSL, type WaterEffect, caveInterior, createFrameRenderer, createRenderSystem, custom, drawOrderCompare, drawText, dropShadow, fire, flash, flashIntensity, fog, forestCanopy, getKerning, grayscale, measureText, moonCrater, mountainHills, outline, parseBMFontJSON, riverWave, roofsCity, sortByDrawOrder, tint, updateEffect, water };
801
+ //#endregion
802
+ //#region src/shaders.d.ts
803
+ declare const FIRE_SKSL = "\nuniform float2 resolution;\nuniform float3 color;\nuniform float3 tipColor;\nuniform float speed;\nuniform float intensity;\nuniform float time;\n\nfloat hash(float2 p) { return fract(sin(dot(p, float2(127.1, 311.7))) * 43758.5453); }\n\nfloat vnoise(float2 p) {\n float2 i = floor(p);\n float2 f = fract(p);\n float2 u = f * f * (3.0 - 2.0 * f);\n float a = hash(i);\n float b = hash(i + float2(1.0, 0.0));\n float c = hash(i + float2(0.0, 1.0));\n float d = hash(i + float2(1.0, 1.0));\n return mix(mix(a, b, u.x), mix(c, d, u.x), u.y);\n}\n\nfloat fbm(float2 p) {\n return 0.5 * vnoise(p) + 0.25 * vnoise(p * 2.0);\n}\n\nhalf4 main(float2 coord) {\n float2 uv = coord / max(resolution, float2(1.0));\n float2 np = float2(uv.x * 4.0, uv.y * 6.0 - time * speed * 2.0);\n float n = fbm(np);\n float flameMask = smoothstep(0.0, intensity, n * (1.0 - uv.y));\n float3 fireRgb = mix(color, tipColor, uv.y);\n return half4(fireRgb.r, fireRgb.g, fireRgb.b, flameMask);\n}\n";
804
+ declare const FOG_SKSL = "\nuniform float2 resolution;\nuniform float3 color;\nuniform float density;\nuniform float speed;\nuniform float scale;\nuniform float time;\n\nfloat hash(float2 p) {\n return fract(sin(dot(p, float2(127.1, 311.7))) * 43758.5453);\n}\n\nfloat valueNoise(float2 p) {\n float2 i = floor(p);\n float2 f = fract(p);\n float2 u = f * f * (3.0 - 2.0 * f);\n float a = hash(i);\n float b = hash(i + float2(1.0, 0.0));\n float c = hash(i + float2(0.0, 1.0));\n float d = hash(i + float2(1.0, 1.0));\n return mix(mix(a, b, u.x), mix(c, d, u.x), u.y);\n}\n\nfloat fbm(float2 p) {\n float value = 0.0;\n float amplitude = 0.5;\n for (int i = 0; i < 4; i++) {\n value += amplitude * valueNoise(p);\n p *= 2.0;\n amplitude *= 0.5;\n }\n return value;\n}\n\nhalf4 main(float2 coord) {\n float2 uv = coord / max(resolution, float2(1.0));\n float2 drift = float2(time * speed, 0.3 * time * speed);\n float n = clamp(fbm(uv * scale + drift), 0.0, 1.0);\n return half4(color.r, color.g, color.b, clamp(density * n, 0.0, 1.0));\n}\n";
805
+ declare const WATER_SKSL = "\nuniform float2 resolution;\nuniform float3 color;\nuniform float3 foamColor;\nuniform float waveSpeed;\nuniform float waveAmplitude;\nuniform float time;\n\nhalf4 main(float2 coord) {\n float2 uv = coord / max(resolution, float2(1.0));\n float waveDeriv = cos(uv.x * 6.2832 * 3.0 + time * waveSpeed * 2.0) * waveAmplitude * 2.0\n + cos(uv.x * 6.2832 * 5.0 - time * waveSpeed * 1.3 + 1.5) * waveAmplitude;\n float foam = smoothstep(waveAmplitude * 0.6, waveAmplitude * 1.5, waveDeriv);\n half3 tinted = half3(color.r, color.g, color.b);\n half3 final = mix(tinted, half3(foamColor.r, foamColor.g, foamColor.b), half(foam));\n return half4(final, 1.0);\n}\n";
806
+ declare const ROOFS_CITY_SKSL = "\nuniform float2 resolution;\nuniform float3 skyColor;\nuniform float3 roofColor;\nuniform float scrollOffsetY;\nuniform float density;\n\nfloat hash(float x) { return fract(sin(x * 127.1) * 43758.5453); }\n\nfloat buildingTop(float2 uv, float density) {\n float col = floor(uv.x * density);\n float h = 0.35 + 0.35 * hash(col);\n float w = 0.6 + 0.3 * hash(col + 7.3);\n float edge = step(abs(fract(uv.x * density) - 0.5), w * 0.5);\n return edge * step(uv.y, h);\n}\n\nhalf4 main(float2 coord) {\n float2 uv = coord / max(resolution, float2(1.0));\n uv.y = uv.y + scrollOffsetY / max(resolution.y, 1.0);\n uv.y = fract(uv.y);\n float buildings = buildingTop(uv, density);\n half3 col = mix(half3(skyColor.r, skyColor.g, skyColor.b),\n half3(roofColor.r, roofColor.g, roofColor.b),\n half(buildings));\n return half4(col, 1.0);\n}\n";
807
+ declare const FOREST_CANOPY_SKSL = "\nuniform float2 resolution;\nuniform float3 skyColor;\nuniform float3 leafColor;\nuniform float scrollOffsetY;\nuniform float density;\n\nfloat hash(float2 p) { return fract(sin(dot(p, float2(127.1, 311.7))) * 43758.5453); }\n\nfloat valueNoise(float2 p) {\n float2 i = floor(p);\n float2 f = fract(p);\n float2 u = f * f * (3.0 - 2.0 * f);\n return mix(mix(hash(i), hash(i + float2(1.0, 0.0)), u.x),\n mix(hash(i + float2(0.0, 1.0)), hash(i + float2(1.0, 1.0)), u.x), u.y);\n}\n\nhalf4 main(float2 coord) {\n float2 uv = coord / max(resolution, float2(1.0));\n uv.y = uv.y + scrollOffsetY / max(resolution.y, 1.0);\n float n = valueNoise(float2(uv.x * density, uv.y * 3.0));\n float treeLine = 0.55 + 0.25 * valueNoise(float2(uv.x * density * 0.5, 0.0));\n float canopy = step(uv.y, treeLine) * smoothstep(0.3, 0.7, n);\n half3 col = mix(half3(skyColor.r, skyColor.g, skyColor.b),\n half3(leafColor.r, leafColor.g, leafColor.b),\n half(canopy));\n return half4(col, 1.0);\n}\n";
808
+ declare const RIVER_WAVE_SKSL = "\nuniform float2 resolution;\nuniform float3 waterColor;\nuniform float3 foamColor;\nuniform float scrollOffsetY;\nuniform float waveScale;\n\nhalf4 main(float2 coord) {\n float2 uv = coord / max(resolution, float2(1.0));\n float scroll = scrollOffsetY / max(resolution.y, 1.0);\n float2 p = float2(uv.x, uv.y + scroll);\n float w1 = sin(p.x * 6.2832 * waveScale + p.y * 4.0) * 0.04;\n float w2 = sin(p.x * 6.2832 * waveScale * 1.7 - p.y * 2.5 + 1.2) * 0.02;\n float surface = 0.5 + w1 + w2;\n float inWater = step(uv.y, surface);\n float crest = smoothstep(surface - 0.015, surface, uv.y) * (1.0 - inWater) +\n smoothstep(surface + 0.015, surface, uv.y) * inWater;\n half3 base = mix(half3(foamColor.r, foamColor.g, foamColor.b),\n half3(waterColor.r, waterColor.g, waterColor.b),\n half(inWater));\n half3 col = mix(base, half3(foamColor.r, foamColor.g, foamColor.b), half(crest * 0.6));\n return half4(col, 1.0);\n}\n";
809
+ declare const MOON_CRATER_SKSL = "\nuniform float2 resolution;\nuniform float3 moonColor;\nuniform float3 craterColor;\nuniform float scrollOffsetY;\nuniform float craterCount;\n\nfloat hash(float2 p) { return fract(sin(dot(p, float2(127.1, 311.7))) * 43758.5453); }\n\nfloat crater(float2 uv, float2 center, float r) {\n float d = length(uv - center);\n float rim = smoothstep(r, r * 0.85, d) - smoothstep(r * 0.85, r * 0.65, d);\n float floor_ = smoothstep(r * 0.65, r * 0.4, d);\n return rim * 0.6 + floor_ * 0.25;\n}\n\nhalf4 main(float2 coord) {\n float2 uv = coord / max(resolution, float2(1.0));\n uv.y = uv.y + scrollOffsetY / max(resolution.y, 1.0);\n float c = 0.0;\n for (int i = 0; i < 6; i++) {\n float fi = float(i);\n float2 center = float2(hash(float2(fi, 0.0)), hash(float2(fi, 1.0)));\n float r = 0.04 + 0.07 * hash(float2(fi, 2.0));\n c += crater(fract(uv), center, r) * step(fi, craterCount - 1.0);\n }\n c = clamp(c, 0.0, 1.0);\n half3 col = mix(half3(moonColor.r, moonColor.g, moonColor.b),\n half3(craterColor.r, craterColor.g, craterColor.b),\n half(c));\n return half4(col, 1.0);\n}\n";
810
+ declare const CAVE_INTERIOR_SKSL = "\nuniform float2 resolution;\nuniform float3 caveColor;\nuniform float3 glowColor;\nuniform float scrollOffsetY;\nuniform float stalactiteCount;\n\nfloat hash(float x) { return fract(sin(x * 127.1) * 43758.5453); }\n\nfloat stalactite(float2 uv, float count) {\n float col = floor(uv.x * count);\n float tip = 0.1 + 0.2 * hash(col);\n float w = 0.3 + 0.3 * hash(col + 5.0);\n float dx = abs(fract(uv.x * count) - 0.5) / (w * 0.5);\n float shape = step(dx, 1.0) * step(uv.y, tip * (1.0 - clamp(dx, 0.0, 1.0)));\n return shape;\n}\n\nhalf4 main(float2 coord) {\n float2 uv = coord / max(resolution, float2(1.0));\n uv.y = 1.0 - uv.y;\n uv.y = uv.y + scrollOffsetY / max(resolution.y, 1.0);\n float stalac = stalactite(uv, stalactiteCount);\n float glow = smoothstep(0.8, 1.0, uv.y) * (1.0 - stalac);\n half3 col = mix(half3(caveColor.r, caveColor.g, caveColor.b),\n half3(glowColor.r, glowColor.g, glowColor.b),\n half(glow));\n col = mix(col, half3(caveColor.r, caveColor.g, caveColor.b) * half(0.3), half(stalac));\n return half4(col, 1.0);\n}\n";
811
+ declare const MOUNTAIN_HILLS_SKSL = "\nuniform float2 resolution;\nuniform float3 skyColor;\nuniform float3 hillColor;\nuniform float scrollOffsetY;\nuniform float ridgeScale;\n\nfloat hash(float x) { return fract(sin(x * 127.1) * 43758.5453); }\n\nfloat heightField(float x, float scale) {\n float h = 0.0;\n float amp = 0.5;\n float freq = scale;\n for (int i = 0; i < 4; i++) {\n float ix = floor(x * freq);\n float fx = fract(x * freq);\n fx = fx * fx * (3.0 - 2.0 * fx);\n h += amp * mix(hash(ix), hash(ix + 1.0), fx);\n freq *= 2.0;\n amp *= 0.5;\n }\n return h;\n}\n\nhalf4 main(float2 coord) {\n float2 uv = coord / max(resolution, float2(1.0));\n float scroll = scrollOffsetY / max(resolution.y, 1.0);\n float horizon = 0.4 + 0.3 * heightField(uv.x + scroll * 0.3, ridgeScale);\n float mountains = step(uv.y, horizon);\n half3 col = mix(half3(skyColor.r, skyColor.g, skyColor.b),\n half3(hillColor.r, hillColor.g, hillColor.b),\n half(mountains));\n return half4(col, 1.0);\n}\n";
812
+ //#endregion
813
+ //#region src/sprite-font.d.ts
814
+ /** A single glyph in a bitmap font. */
815
+ interface Glyph {
816
+ /** Character code (Unicode). */
817
+ id: number;
818
+ /** Source rect in the font texture. */
819
+ x: number;
820
+ y: number;
821
+ width: number;
822
+ height: number;
823
+ /** Draw offset. */
824
+ xOffset: number;
825
+ yOffset: number;
826
+ /** Horizontal advance after this glyph. */
827
+ xAdvance: number;
828
+ }
829
+ /** Kerning pair. */
830
+ interface KerningPair {
831
+ first: number;
832
+ second: number;
833
+ amount: number;
834
+ }
835
+ /** Parsed bitmap font data. */
836
+ interface BitmapFont {
837
+ /** Line height in pixels. */
838
+ lineHeight: number;
839
+ /** Baseline offset. */
840
+ base: number;
841
+ /** Glyph map by character code. */
842
+ glyphs: Map<number, Glyph>;
843
+ /** Kerning map. Key = `first << 16 | second`. */
844
+ kernings: Map<number, number>;
845
+ }
846
+ /**
847
+ * Parse BMFont JSON format (common output from tools like Hiero, BMFont, msdf-bmfont).
848
+ */
849
+ declare function parseBMFontJSON(json: unknown): BitmapFont;
850
+ /** Get kerning between two characters. */
851
+ declare function getKerning(font: BitmapFont, first: number, second: number): number;
852
+ /** Measure the width of a text string in pixels. */
853
+ declare function measureText(font: BitmapFont, text: string): number;
854
+ /**
855
+ * Draw text using a SpriteBatch.
856
+ * Characters are batched as individual sprites from the font texture.
857
+ */
858
+ declare function drawText(batch: SpriteBatch, font: BitmapFont, image: SkImageLike, text: string, x: number, y: number, scale?: number): void;
859
+ //#endregion
860
+ export { type AnySpriteEffect, type BitmapFont, CAVE_INTERIOR_SKSL, Camera2D, type CanvasLike, type CaveInteriorEffect, type ColorFilterFactoryLike, type ColorLike, type CompiledShader, type CustomShaderEffect, type DrawSurfaceLike, type Drawable, type DropShadowEffect, FIRE_SKSL, FOG_SKSL, FOREST_CANOPY_SKSL, type FireEffect, type FlashEffect, type FogEffect, type FollowOptions, type ForestCanopyEffect, type FrameRenderer, type FrameRendererCompiledPass, type FrameRendererModule, type FrameRendererPostFxChain, type FrameRendererPostFxDescriptor, type FrameRendererPostFxRuntime, type Glyph, type GrayscaleEffect, type ImageFilterFactoryLike, type KerningPair, MOON_CRATER_SKSL, MOUNTAIN_HILLS_SKSL, type MoonCraterEffect, type MountainHillsEffect, type OutlineEffect, type PaintLike, type PictureLike, type PictureRecorderLike, type PositionData, RIVER_WAVE_SKSL, ROOFS_CITY_SKSL, type RSXformLike, type RectLike, type RenderStats, Renderer, type RiverWaveEffect, type RoofsCityEffect, type RuntimeEffectFactoryLike, type RuntimeEffectLike, type ShaderUniforms, Sprite, type SpriteAnchor, SpriteBatch, type SpriteColor, type SpriteData, type SpriteDrawCommand, type SpriteEffect, type TintEffect, WATER_SKSL, type WaterEffect, caveInterior, createFrameRenderer, createRenderSystem, custom, drawOrderCompare, drawText, dropShadow, fire, flash, flashIntensity, fog, forestCanopy, getKerning, getXformCounters, grayscale, measureText, moonCrater, mountainHills, outline, parseBMFontJSON, resetXformCounters, riverWave, roofsCity, sortByDrawOrder, tint, updateEffect, water };
861
+ //# sourceMappingURL=index.d.ts.map