@flighthq/particles-formats 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (99) hide show
  1. package/dist/detect.d.ts +18 -0
  2. package/dist/detect.d.ts.map +1 -0
  3. package/dist/detect.js +75 -0
  4. package/dist/detect.js.map +1 -0
  5. package/dist/formatRegistry.d.ts +60 -0
  6. package/dist/formatRegistry.d.ts.map +1 -0
  7. package/dist/formatRegistry.js +75 -0
  8. package/dist/formatRegistry.js.map +1 -0
  9. package/dist/index.d.ts +21 -0
  10. package/dist/index.d.ts.map +1 -0
  11. package/dist/index.js +21 -0
  12. package/dist/index.js.map +1 -0
  13. package/dist/libgdxParse.d.ts +27 -0
  14. package/dist/libgdxParse.d.ts.map +1 -0
  15. package/dist/libgdxParse.js +316 -0
  16. package/dist/libgdxParse.js.map +1 -0
  17. package/dist/libgdxSchema.d.ts +68 -0
  18. package/dist/libgdxSchema.d.ts.map +1 -0
  19. package/dist/libgdxSchema.js +12 -0
  20. package/dist/libgdxSchema.js.map +1 -0
  21. package/dist/libgdxSerialize.d.ts +17 -0
  22. package/dist/libgdxSerialize.d.ts.map +1 -0
  23. package/dist/libgdxSerialize.js +295 -0
  24. package/dist/libgdxSerialize.js.map +1 -0
  25. package/dist/parseParticleConfig.d.ts +34 -0
  26. package/dist/parseParticleConfig.d.ts.map +1 -0
  27. package/dist/parseParticleConfig.js +96 -0
  28. package/dist/parseParticleConfig.js.map +1 -0
  29. package/dist/particleDesignerParse.d.ts +23 -0
  30. package/dist/particleDesignerParse.d.ts.map +1 -0
  31. package/dist/particleDesignerParse.js +214 -0
  32. package/dist/particleDesignerParse.js.map +1 -0
  33. package/dist/particleDesignerSchema.d.ts +51 -0
  34. package/dist/particleDesignerSchema.d.ts.map +1 -0
  35. package/dist/particleDesignerSchema.js +5 -0
  36. package/dist/particleDesignerSchema.js.map +1 -0
  37. package/dist/particleDesignerSerialize.d.ts +22 -0
  38. package/dist/particleDesignerSerialize.d.ts.map +1 -0
  39. package/dist/particleDesignerSerialize.js +209 -0
  40. package/dist/particleDesignerSerialize.js.map +1 -0
  41. package/dist/pixiParse.d.ts +18 -0
  42. package/dist/pixiParse.d.ts.map +1 -0
  43. package/dist/pixiParse.js +173 -0
  44. package/dist/pixiParse.js.map +1 -0
  45. package/dist/serializeResult.d.ts +15 -0
  46. package/dist/serializeResult.d.ts.map +1 -0
  47. package/dist/serializeResult.js +2 -0
  48. package/dist/serializeResult.js.map +1 -0
  49. package/dist/spineParse.d.ts +18 -0
  50. package/dist/spineParse.d.ts.map +1 -0
  51. package/dist/spineParse.js +263 -0
  52. package/dist/spineParse.js.map +1 -0
  53. package/dist/spineSchema.d.ts +41 -0
  54. package/dist/spineSchema.d.ts.map +1 -0
  55. package/dist/spineSchema.js +8 -0
  56. package/dist/spineSchema.js.map +1 -0
  57. package/dist/spineSerialize.d.ts +16 -0
  58. package/dist/spineSerialize.d.ts.map +1 -0
  59. package/dist/spineSerialize.js +108 -0
  60. package/dist/spineSerialize.js.map +1 -0
  61. package/dist/starlingPexParse.d.ts +29 -0
  62. package/dist/starlingPexParse.d.ts.map +1 -0
  63. package/dist/starlingPexParse.js +254 -0
  64. package/dist/starlingPexParse.js.map +1 -0
  65. package/dist/starlingPexSchema.d.ts +49 -0
  66. package/dist/starlingPexSchema.d.ts.map +1 -0
  67. package/dist/starlingPexSchema.js +15 -0
  68. package/dist/starlingPexSchema.js.map +1 -0
  69. package/dist/starlingPexSerialize.d.ts +20 -0
  70. package/dist/starlingPexSerialize.d.ts.map +1 -0
  71. package/dist/starlingPexSerialize.js +184 -0
  72. package/dist/starlingPexSerialize.js.map +1 -0
  73. package/dist/unityParse.d.ts +23 -0
  74. package/dist/unityParse.d.ts.map +1 -0
  75. package/dist/unityParse.js +347 -0
  76. package/dist/unityParse.js.map +1 -0
  77. package/dist/unitySchema.d.ts +109 -0
  78. package/dist/unitySchema.d.ts.map +1 -0
  79. package/dist/unitySchema.js +9 -0
  80. package/dist/unitySchema.js.map +1 -0
  81. package/dist/unitySerialize.d.ts +20 -0
  82. package/dist/unitySerialize.d.ts.map +1 -0
  83. package/dist/unitySerialize.js +148 -0
  84. package/dist/unitySerialize.js.map +1 -0
  85. package/package.json +38 -0
  86. package/src/detect.test.ts +99 -0
  87. package/src/formatRegistry.test.ts +118 -0
  88. package/src/libgdxParse.test.ts +309 -0
  89. package/src/libgdxSerialize.test.ts +279 -0
  90. package/src/parseParticleConfig.test.ts +447 -0
  91. package/src/particleDesignerParse.test.ts +279 -0
  92. package/src/particleDesignerSerialize.test.ts +113 -0
  93. package/src/pixiParse.test.ts +131 -0
  94. package/src/spineParse.test.ts +289 -0
  95. package/src/spineSerialize.test.ts +81 -0
  96. package/src/starlingPexParse.test.ts +154 -0
  97. package/src/starlingPexSerialize.test.ts +115 -0
  98. package/src/unityParse.test.ts +328 -0
  99. package/src/unitySerialize.test.ts +77 -0
@@ -0,0 +1,309 @@
1
+ import { parseLibgdxParticle, parseLibgdxParticleDocument } from './libgdxParse';
2
+
3
+ const SPARK_P = `Particle Effect
4
+ - Spark -
5
+ minParticleCount: 4
6
+ maxParticleCount: 100
7
+ additive: true
8
+ imagePath: spark.png
9
+ imageCount: 1
10
+
11
+ Delay
12
+ active: false
13
+ lowMin: 0
14
+ lowMax: 0
15
+ highMin: 0
16
+ highMax: 0
17
+ relative: false
18
+ scalingCount: 1
19
+ scaling0: 1
20
+ timelineCount: 1
21
+ timeline0: 0
22
+
23
+ Duration
24
+ lowMin: 3000
25
+ lowMax: 3000
26
+ highMin: 3000
27
+ highMax: 3000
28
+ relative: false
29
+ scalingCount: 1
30
+ scaling0: 1
31
+ timelineCount: 1
32
+ timeline0: 0
33
+
34
+ Emission
35
+ lowMin: 0
36
+ lowMax: 0
37
+ highMin: 80
38
+ highMax: 120
39
+ relative: false
40
+ scalingCount: 1
41
+ scaling0: 1
42
+ timelineCount: 1
43
+ timeline0: 0
44
+
45
+ Life
46
+ lowMin: 400
47
+ lowMax: 400
48
+ highMin: 800
49
+ highMax: 800
50
+ relative: false
51
+ scalingCount: 1
52
+ scaling0: 1
53
+ timelineCount: 1
54
+ timeline0: 0
55
+
56
+ Life Offset
57
+ active: false
58
+ lowMin: 0
59
+ lowMax: 0
60
+ highMin: 0
61
+ highMax: 0
62
+ relative: false
63
+ scalingCount: 1
64
+ scaling0: 1
65
+ timelineCount: 1
66
+ timeline0: 0
67
+
68
+ X Offset
69
+ active: false
70
+ lowMin: 0
71
+ lowMax: 0
72
+ highMin: 0
73
+ highMax: 0
74
+ relative: false
75
+ scalingCount: 1
76
+ scaling0: 1
77
+ timelineCount: 1
78
+ timeline0: 0
79
+
80
+ Y Offset
81
+ active: false
82
+ lowMin: 0
83
+ lowMax: 0
84
+ highMin: 0
85
+ highMax: 0
86
+ relative: false
87
+ scalingCount: 1
88
+ scaling0: 1
89
+ timelineCount: 1
90
+ timeline0: 0
91
+
92
+ Spawn Shape
93
+ shape: point
94
+
95
+ Spawn Width
96
+ lowMin: 0
97
+ lowMax: 0
98
+ highMin: 0
99
+ highMax: 0
100
+ relative: false
101
+ scalingCount: 1
102
+ scaling0: 1
103
+ timelineCount: 1
104
+ timeline0: 0
105
+
106
+ Spawn Height
107
+ lowMin: 0
108
+ lowMax: 0
109
+ highMin: 0
110
+ highMax: 0
111
+ relative: false
112
+ scalingCount: 1
113
+ scaling0: 1
114
+ timelineCount: 1
115
+ timeline0: 0
116
+
117
+ Scale
118
+ lowMin: 16
119
+ lowMax: 16
120
+ highMin: 32
121
+ highMax: 32
122
+ relative: false
123
+ scalingCount: 2
124
+ scaling0: 1
125
+ scaling1: 0.25
126
+ timelineCount: 2
127
+ timeline0: 0
128
+ timeline1: 1
129
+
130
+ Velocity
131
+ active: true
132
+ lowMin: 50
133
+ lowMax: 50
134
+ highMin: 200
135
+ highMax: 200
136
+ relative: false
137
+ scalingCount: 1
138
+ scaling0: 1
139
+ timelineCount: 1
140
+ timeline0: 0
141
+
142
+ Angle
143
+ active: true
144
+ lowMin: 60
145
+ lowMax: 60
146
+ highMin: 120
147
+ highMax: 120
148
+ relative: false
149
+ scalingCount: 1
150
+ scaling0: 1
151
+ timelineCount: 1
152
+ timeline0: 0
153
+
154
+ Rotation
155
+ active: false
156
+ lowMin: 0
157
+ lowMax: 0
158
+ highMin: 0
159
+ highMax: 0
160
+ relative: false
161
+ scalingCount: 1
162
+ scaling0: 1
163
+ timelineCount: 1
164
+ timeline0: 0
165
+
166
+ Wind
167
+ active: false
168
+ lowMin: 0
169
+ lowMax: 0
170
+ highMin: 0
171
+ highMax: 0
172
+ relative: false
173
+ scalingCount: 1
174
+ scaling0: 1
175
+ timelineCount: 1
176
+ timeline0: 0
177
+
178
+ Gravity
179
+ active: false
180
+ lowMin: 0
181
+ lowMax: 0
182
+ highMin: 0
183
+ highMax: 0
184
+ relative: false
185
+ scalingCount: 1
186
+ scaling0: 1
187
+ timelineCount: 1
188
+ timeline0: 0
189
+
190
+ Tint
191
+ colors: ffaa00,ff0000
192
+ timelineCount: 2
193
+ timeline0: 0
194
+ timeline1: 1
195
+
196
+ Transparency
197
+ lowMin: 0
198
+ lowMax: 0
199
+ highMin: 1
200
+ highMax: 1
201
+ relative: false
202
+ scalingCount: 2
203
+ scaling0: 1
204
+ scaling1: 0
205
+ timelineCount: 2
206
+ timeline0: 0
207
+ timeline1: 1
208
+
209
+ `;
210
+
211
+ describe('parseLibgdxParticle', () => {
212
+ it('parses maxParticleCount to maxParticles', () => {
213
+ expect(parseLibgdxParticle(SPARK_P).maxParticles).toBe(100);
214
+ });
215
+ it('maps additive=true to blendMode="add"', () => {
216
+ expect(parseLibgdxParticle(SPARK_P).blendMode).toBe('add');
217
+ });
218
+ it('converts life range from ms to seconds', () => {
219
+ const c = parseLibgdxParticle(SPARK_P);
220
+ expect(c.lifetimeMin).toBeCloseTo(0.4);
221
+ expect(c.lifetimeMax).toBeCloseTo(0.8);
222
+ });
223
+ it('maps velocity to speedMin/Max', () => {
224
+ const c = parseLibgdxParticle(SPARK_P);
225
+ expect(c.speedMin).toBeCloseTo(50);
226
+ expect(c.speedMax).toBeCloseTo(200);
227
+ });
228
+ it('derives direction from angle midpoint and spread from range', () => {
229
+ const c = parseLibgdxParticle(SPARK_P);
230
+ // angle range 60..120 -> mid=90 degrees -> directionX=0, directionY=-1
231
+ expect(c.directionX).toBeCloseTo(0, 2);
232
+ expect(c.directionY).toBeCloseTo(-1, 2);
233
+ // spread = half of 60 degrees
234
+ expect(c.spread).toBeCloseTo((30 * Math.PI) / 180, 3);
235
+ });
236
+ it('maps tint colors to colorStart/End', () => {
237
+ const c = parseLibgdxParticle(SPARK_P);
238
+ expect(c.colorStartR).toBeCloseTo(1);
239
+ expect(c.colorStartG).toBeCloseTo(0xaa / 255, 2);
240
+ expect(c.colorEndR).toBeCloseTo(1);
241
+ expect(c.colorEndG).toBeCloseTo(0);
242
+ });
243
+ it('maps transparency scaling to alphaStart/End', () => {
244
+ const c = parseLibgdxParticle(SPARK_P);
245
+ expect(c.alphaStart).toBeCloseTo(1);
246
+ expect(c.alphaEnd).toBeCloseTo(0);
247
+ });
248
+ it('uses point emitter shape when spawn shape is point', () => {
249
+ expect(parseLibgdxParticle(SPARK_P).emitterShape).toBe('point');
250
+ });
251
+ it('normalises scale by textureSize', () => {
252
+ const c = parseLibgdxParticle(SPARK_P, { textureSize: 32 });
253
+ expect(c.scaleMin).toBeCloseTo(0.5);
254
+ expect(c.scaleMax).toBeCloseTo(1.0);
255
+ });
256
+ it('reads last scale scaling value as scaleEnd', () => {
257
+ // scaling[1] = 0.25 -> scaleEnd
258
+ expect(parseLibgdxParticle(SPARK_P).scaleEnd).toBeCloseTo(0.25);
259
+ });
260
+ it('maps duration > 0 to a finite non-looping emitter', () => {
261
+ const c = parseLibgdxParticle(SPARK_P);
262
+ expect(c.loop).toBe(false);
263
+ expect(c.duration).toBeCloseTo(3);
264
+ });
265
+ it('maps additive=false to blendMode="normal"', () => {
266
+ const text = SPARK_P.replace('additive: true', 'additive: false');
267
+ expect(parseLibgdxParticle(text).blendMode).toBe('normal');
268
+ });
269
+ it('throws a clear error for empty input', () => {
270
+ expect(() => parseLibgdxParticle('')).toThrow(/Invalid libGDX particle/);
271
+ expect(() => parseLibgdxParticle(' ')).toThrow(/Invalid libGDX particle/);
272
+ });
273
+ it('returns a ParticleEmitterConfig (not a ParseResult object)', () => {
274
+ const result = parseLibgdxParticle(SPARK_P) as unknown as Record<string, unknown>;
275
+ expect(typeof result.maxParticles).toBe('number');
276
+ expect(result.document).toBeUndefined();
277
+ });
278
+ });
279
+
280
+ describe('parseLibgdxParticleDocument', () => {
281
+ it('returns the same config values as parseLibgdxParticle', () => {
282
+ const config = parseLibgdxParticle(SPARK_P);
283
+ const { config: loaded } = parseLibgdxParticleDocument(SPARK_P);
284
+ expect(loaded.maxParticles).toBe(config.maxParticles);
285
+ expect(loaded.speedMax).toBeCloseTo(config.speedMax, 3);
286
+ });
287
+ it('preserves emitter name in document', () => {
288
+ const { document } = parseLibgdxParticleDocument(SPARK_P);
289
+ expect(document.name).toBe('Spark');
290
+ });
291
+ it('preserves imagePath in document', () => {
292
+ const { document } = parseLibgdxParticleDocument(SPARK_P);
293
+ expect(document.imagePath).toBe('spark.png');
294
+ });
295
+ it('has no warnings for a standard point emitter', () => {
296
+ const { warnings } = parseLibgdxParticleDocument(SPARK_P);
297
+ expect(warnings).toEqual([]);
298
+ });
299
+ it('warns when delay is active', () => {
300
+ const text = SPARK_P.replace('Delay\nactive: false', 'Delay\nactive: true');
301
+ const { warnings } = parseLibgdxParticleDocument(text);
302
+ expect(warnings.some((w) => w.includes('delay'))).toBe(true);
303
+ });
304
+ it('warns when spawn shape is line (no mapping)', () => {
305
+ const text = SPARK_P.replace('shape: point', 'shape: line');
306
+ const { warnings } = parseLibgdxParticleDocument(text);
307
+ expect(warnings.some((w) => w.includes('line'))).toBe(true);
308
+ });
309
+ });
@@ -0,0 +1,279 @@
1
+ import { createParticleEmitterConfig } from '@flighthq/particles';
2
+
3
+ import { parseLibgdxParticle, parseLibgdxParticleDocument } from './libgdxParse';
4
+ import { serializeLibgdxParticle, serializeLibgdxParticleDocument } from './libgdxSerialize';
5
+
6
+ const SPARK_P = `Particle Effect
7
+ - Spark -
8
+ minParticleCount: 4
9
+ maxParticleCount: 100
10
+ additive: true
11
+ imagePath: spark.png
12
+ imageCount: 1
13
+
14
+ Delay
15
+ active: false
16
+ lowMin: 0
17
+ lowMax: 0
18
+ highMin: 0
19
+ highMax: 0
20
+ relative: false
21
+ scalingCount: 1
22
+ scaling0: 1
23
+ timelineCount: 1
24
+ timeline0: 0
25
+
26
+ Duration
27
+ lowMin: 3000
28
+ lowMax: 3000
29
+ highMin: 3000
30
+ highMax: 3000
31
+ relative: false
32
+ scalingCount: 1
33
+ scaling0: 1
34
+ timelineCount: 1
35
+ timeline0: 0
36
+
37
+ Emission
38
+ lowMin: 0
39
+ lowMax: 0
40
+ highMin: 80
41
+ highMax: 120
42
+ relative: false
43
+ scalingCount: 1
44
+ scaling0: 1
45
+ timelineCount: 1
46
+ timeline0: 0
47
+
48
+ Life
49
+ lowMin: 400
50
+ lowMax: 400
51
+ highMin: 800
52
+ highMax: 800
53
+ relative: false
54
+ scalingCount: 1
55
+ scaling0: 1
56
+ timelineCount: 1
57
+ timeline0: 0
58
+
59
+ Life Offset
60
+ active: false
61
+ lowMin: 0
62
+ lowMax: 0
63
+ highMin: 0
64
+ highMax: 0
65
+ relative: false
66
+ scalingCount: 1
67
+ scaling0: 1
68
+ timelineCount: 1
69
+ timeline0: 0
70
+
71
+ X Offset
72
+ active: false
73
+ lowMin: 0
74
+ lowMax: 0
75
+ highMin: 0
76
+ highMax: 0
77
+ relative: false
78
+ scalingCount: 1
79
+ scaling0: 1
80
+ timelineCount: 1
81
+ timeline0: 0
82
+
83
+ Y Offset
84
+ active: false
85
+ lowMin: 0
86
+ lowMax: 0
87
+ highMin: 0
88
+ highMax: 0
89
+ relative: false
90
+ scalingCount: 1
91
+ scaling0: 1
92
+ timelineCount: 1
93
+ timeline0: 0
94
+
95
+ Spawn Shape
96
+ shape: point
97
+
98
+ Spawn Width
99
+ lowMin: 0
100
+ lowMax: 0
101
+ highMin: 0
102
+ highMax: 0
103
+ relative: false
104
+ scalingCount: 1
105
+ scaling0: 1
106
+ timelineCount: 1
107
+ timeline0: 0
108
+
109
+ Spawn Height
110
+ lowMin: 0
111
+ lowMax: 0
112
+ highMin: 0
113
+ highMax: 0
114
+ relative: false
115
+ scalingCount: 1
116
+ scaling0: 1
117
+ timelineCount: 1
118
+ timeline0: 0
119
+
120
+ Scale
121
+ lowMin: 16
122
+ lowMax: 16
123
+ highMin: 32
124
+ highMax: 32
125
+ relative: false
126
+ scalingCount: 2
127
+ scaling0: 1
128
+ scaling1: 0.25
129
+ timelineCount: 2
130
+ timeline0: 0
131
+ timeline1: 1
132
+
133
+ Velocity
134
+ active: true
135
+ lowMin: 50
136
+ lowMax: 50
137
+ highMin: 200
138
+ highMax: 200
139
+ relative: false
140
+ scalingCount: 1
141
+ scaling0: 1
142
+ timelineCount: 1
143
+ timeline0: 0
144
+
145
+ Angle
146
+ active: true
147
+ lowMin: 60
148
+ lowMax: 60
149
+ highMin: 120
150
+ highMax: 120
151
+ relative: false
152
+ scalingCount: 1
153
+ scaling0: 1
154
+ timelineCount: 1
155
+ timeline0: 0
156
+
157
+ Rotation
158
+ active: false
159
+ lowMin: 0
160
+ lowMax: 0
161
+ highMin: 0
162
+ highMax: 0
163
+ relative: false
164
+ scalingCount: 1
165
+ scaling0: 1
166
+ timelineCount: 1
167
+ timeline0: 0
168
+
169
+ Wind
170
+ active: false
171
+ lowMin: 0
172
+ lowMax: 0
173
+ highMin: 0
174
+ highMax: 0
175
+ relative: false
176
+ scalingCount: 1
177
+ scaling0: 1
178
+ timelineCount: 1
179
+ timeline0: 0
180
+
181
+ Gravity
182
+ active: false
183
+ lowMin: 0
184
+ lowMax: 0
185
+ highMin: 0
186
+ highMax: 0
187
+ relative: false
188
+ scalingCount: 1
189
+ scaling0: 1
190
+ timelineCount: 1
191
+ timeline0: 0
192
+
193
+ Tint
194
+ colors: ffaa00,ff0000
195
+ timelineCount: 2
196
+ timeline0: 0
197
+ timeline1: 1
198
+
199
+ Transparency
200
+ lowMin: 0
201
+ lowMax: 0
202
+ highMin: 1
203
+ highMax: 1
204
+ relative: false
205
+ scalingCount: 2
206
+ scaling0: 1
207
+ scaling1: 0
208
+ timelineCount: 2
209
+ timeline0: 0
210
+ timeline1: 1
211
+
212
+ `;
213
+
214
+ describe('serializeLibgdxParticle', () => {
215
+ it('round-trips key config fields', () => {
216
+ const config = parseLibgdxParticle(SPARK_P);
217
+ const { document } = parseLibgdxParticleDocument(SPARK_P);
218
+ const text = serializeLibgdxParticle(config, document);
219
+ const config2 = parseLibgdxParticle(text);
220
+ expect(config2.maxParticles).toBe(config.maxParticles);
221
+ expect(config2.speedMin).toBeCloseTo(config.speedMin, 1);
222
+ expect(config2.speedMax).toBeCloseTo(config.speedMax, 1);
223
+ expect(config2.lifetimeMin).toBeCloseTo(config.lifetimeMin, 2);
224
+ expect(config2.lifetimeMax).toBeCloseTo(config.lifetimeMax, 2);
225
+ });
226
+ it('preserves imagePath in round-trip', () => {
227
+ const config = parseLibgdxParticle(SPARK_P);
228
+ const { document } = parseLibgdxParticleDocument(SPARK_P);
229
+ const text = serializeLibgdxParticle(config, document);
230
+ expect(parseLibgdxParticleDocument(text).document.imagePath).toBe('spark.png');
231
+ });
232
+ it('produces text readable by parseLibgdxParticle for a fresh config', () => {
233
+ const config = createParticleEmitterConfig({ maxParticles: 50, blendMode: 'add' });
234
+ const text = serializeLibgdxParticle(config);
235
+ const c2 = parseLibgdxParticle(text);
236
+ expect(c2.maxParticles).toBe(50);
237
+ expect(c2.blendMode).toBe('add');
238
+ });
239
+ it('maps blendMode="add" to additive=true', () => {
240
+ const config = createParticleEmitterConfig({ blendMode: 'add' });
241
+ const text = serializeLibgdxParticle(config);
242
+ expect(text).toContain('additive: true');
243
+ });
244
+ it('maps blendMode="normal" to additive=false', () => {
245
+ const config = createParticleEmitterConfig({ blendMode: 'normal' });
246
+ const text = serializeLibgdxParticle(config);
247
+ expect(text).toContain('additive: false');
248
+ });
249
+ });
250
+
251
+ describe('serializeLibgdxParticleDocument', () => {
252
+ it('returns text and empty warnings for a default config', () => {
253
+ const config = createParticleEmitterConfig();
254
+ const result = serializeLibgdxParticleDocument(config);
255
+ expect(typeof result.text).toBe('string');
256
+ expect(result.text).toContain('Particle Effect');
257
+ expect(result.warnings).toEqual([]);
258
+ });
259
+ it('warns when multiply/screen blend mode cannot be represented', () => {
260
+ const config = createParticleEmitterConfig({ blendMode: 'multiply' });
261
+ const result = serializeLibgdxParticleDocument(config);
262
+ expect(result.warnings.some((w) => w.toLowerCase().includes('multiply'))).toBe(true);
263
+ });
264
+ it('warns when config has emission bursts', () => {
265
+ const config = createParticleEmitterConfig({ burstCount: 5 });
266
+ const result = serializeLibgdxParticleDocument(config);
267
+ expect(result.warnings.some((w) => w.toLowerCase().includes('burst'))).toBe(true);
268
+ });
269
+ it('warns when config has color start variance', () => {
270
+ const config = createParticleEmitterConfig({ colorStartVarianceR: 0.1 });
271
+ const result = serializeLibgdxParticleDocument(config);
272
+ expect(result.warnings.some((w) => w.includes('colorStartVariance'))).toBe(true);
273
+ });
274
+ it('has no warnings for add blend mode (supported)', () => {
275
+ const config = createParticleEmitterConfig({ blendMode: 'add' });
276
+ const result = serializeLibgdxParticleDocument(config);
277
+ expect(result.warnings.filter((w) => w.toLowerCase().includes('blend'))).toEqual([]);
278
+ });
279
+ });