@game_engine/runtime-browser-pixi 0.1.0-alpha.1
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/LICENSE +201 -0
- package/README.md +35 -0
- package/dist/index.d.ts +203 -0
- package/dist/index.js +1130 -0
- package/dist/index.js.map +1 -0
- package/package.json +45 -0
package/dist/index.js
ADDED
|
@@ -0,0 +1,1130 @@
|
|
|
1
|
+
// src/index.ts
|
|
2
|
+
var defaultMarkerSize = 4;
|
|
3
|
+
var defaultRectangleFill = 3900150;
|
|
4
|
+
var defaultMarkerFill = 16096779;
|
|
5
|
+
var defaultStroke = 1120295;
|
|
6
|
+
var defaultStrokeWidth = 1;
|
|
7
|
+
var defaultAlpha = 0.35;
|
|
8
|
+
var defaultLayer = "debug";
|
|
9
|
+
var supportedManifestAssetTypes = /* @__PURE__ */ new Set(["texture", "sprite", "atlas", "audio", "font"]);
|
|
10
|
+
function createDisplayList(snapshot, options = {}) {
|
|
11
|
+
const diagnostics = [];
|
|
12
|
+
const animationClips = collectAnimationClips(snapshot);
|
|
13
|
+
const assetsById = collectDisplayAssets(options.assets);
|
|
14
|
+
const items = [...snapshot.entities ?? []].sort((left, right) => left.id.localeCompare(right.id)).flatMap((entity) => {
|
|
15
|
+
const transform = readTransform(entity.components?.Transform);
|
|
16
|
+
if (!transform) {
|
|
17
|
+
if (isRenderMetadataOnly(entity.components)) {
|
|
18
|
+
return [];
|
|
19
|
+
}
|
|
20
|
+
diagnostics.push({
|
|
21
|
+
severity: "warning",
|
|
22
|
+
code: "PIXI_ENTITY_MISSING_TRANSFORM",
|
|
23
|
+
entityId: entity.id,
|
|
24
|
+
path: `$.entities[${JSON.stringify(entity.id)}].components.Transform`,
|
|
25
|
+
message: `Entity '${entity.id}' was skipped because it has no valid Transform component.`,
|
|
26
|
+
suggestion: "Add a Transform component before expecting the browser renderer to project this entity."
|
|
27
|
+
});
|
|
28
|
+
return [];
|
|
29
|
+
}
|
|
30
|
+
const collider = readCollider(entity.components?.Collider);
|
|
31
|
+
const animation = readAnimatorPlayback(
|
|
32
|
+
entity.id,
|
|
33
|
+
entity.components?.Animator,
|
|
34
|
+
animationClips,
|
|
35
|
+
assetsById,
|
|
36
|
+
diagnostics
|
|
37
|
+
);
|
|
38
|
+
if (collider) {
|
|
39
|
+
return [
|
|
40
|
+
createDisplayItem(entity.id, "rectangle", transform, collider.size[0], collider.size[1], {
|
|
41
|
+
fill: options.rectangleFill ?? defaultRectangleFill,
|
|
42
|
+
options,
|
|
43
|
+
animation
|
|
44
|
+
})
|
|
45
|
+
];
|
|
46
|
+
}
|
|
47
|
+
if (options.drawTransformOnlyMarkers ?? true) {
|
|
48
|
+
const markerSize = options.markerSize ?? defaultMarkerSize;
|
|
49
|
+
return [
|
|
50
|
+
createDisplayItem(entity.id, "marker", transform, markerSize, markerSize, {
|
|
51
|
+
fill: options.markerFill ?? defaultMarkerFill,
|
|
52
|
+
options,
|
|
53
|
+
animation
|
|
54
|
+
})
|
|
55
|
+
];
|
|
56
|
+
}
|
|
57
|
+
return [];
|
|
58
|
+
}).map((item, index) => ({
|
|
59
|
+
...item,
|
|
60
|
+
order: index
|
|
61
|
+
}));
|
|
62
|
+
return {
|
|
63
|
+
version: 1,
|
|
64
|
+
frame: snapshot.frame,
|
|
65
|
+
items,
|
|
66
|
+
diagnostics
|
|
67
|
+
};
|
|
68
|
+
}
|
|
69
|
+
function createPixiTextureResolverFromManifest(manifest, textures = {}) {
|
|
70
|
+
const diagnostics = [];
|
|
71
|
+
const document = readManifestDocument(manifest, diagnostics);
|
|
72
|
+
if (!document) {
|
|
73
|
+
return {
|
|
74
|
+
assets: [],
|
|
75
|
+
textureResolver: () => void 0,
|
|
76
|
+
diagnostics
|
|
77
|
+
};
|
|
78
|
+
}
|
|
79
|
+
const assets = readManifestAssets(document.assets, diagnostics);
|
|
80
|
+
const spriteAssetIds = new Set(assets.filter((asset) => asset.type === "sprite").map((asset) => asset.id));
|
|
81
|
+
return {
|
|
82
|
+
assets,
|
|
83
|
+
textureResolver: (request) => {
|
|
84
|
+
if (!spriteAssetIds.has(request.assetId)) {
|
|
85
|
+
return void 0;
|
|
86
|
+
}
|
|
87
|
+
return resolveTextureByAssetId(textures, request);
|
|
88
|
+
},
|
|
89
|
+
diagnostics
|
|
90
|
+
};
|
|
91
|
+
}
|
|
92
|
+
async function createPixiAssetLoaderFromManifest(manifestOrAssets, options = {}) {
|
|
93
|
+
const manifestDiagnostics = [];
|
|
94
|
+
const assets = readManifestOrAssetDefinitions(manifestOrAssets, manifestDiagnostics);
|
|
95
|
+
const diagnostics = [...manifestDiagnostics];
|
|
96
|
+
const assetsById = new Map(assets.map((asset) => [asset.id, asset]));
|
|
97
|
+
const textures = /* @__PURE__ */ new Map();
|
|
98
|
+
const loadTexture = options.loadTexture ?? loadPixiTextureFromUrl;
|
|
99
|
+
for (const asset of assets) {
|
|
100
|
+
if (asset.type !== "sprite") {
|
|
101
|
+
continue;
|
|
102
|
+
}
|
|
103
|
+
const atlasFrame = resolveDisplayAtlasFrameReference(asset, assetsById);
|
|
104
|
+
const url = resolveAssetLoadUrl(asset, atlasFrame, options, diagnostics);
|
|
105
|
+
if (!url) {
|
|
106
|
+
continue;
|
|
107
|
+
}
|
|
108
|
+
let texture;
|
|
109
|
+
try {
|
|
110
|
+
texture = await loadTexture({
|
|
111
|
+
asset,
|
|
112
|
+
...atlasFrame ? { atlasFrame } : {},
|
|
113
|
+
url
|
|
114
|
+
});
|
|
115
|
+
} catch (error) {
|
|
116
|
+
diagnostics.push({
|
|
117
|
+
severity: "warning",
|
|
118
|
+
code: "PIXI_ASSET_TEXTURE_LOAD_FAILED",
|
|
119
|
+
path: `$.assets[${JSON.stringify(asset.id)}].path`,
|
|
120
|
+
message: `Sprite asset '${asset.id}' failed to load texture from '${url}'.`,
|
|
121
|
+
suggestion: error instanceof Error ? error.message : "Check the browser asset URL, server routing, and caller-provided texture loader."
|
|
122
|
+
});
|
|
123
|
+
continue;
|
|
124
|
+
}
|
|
125
|
+
if (!texture) {
|
|
126
|
+
diagnostics.push({
|
|
127
|
+
severity: "warning",
|
|
128
|
+
code: "PIXI_ASSET_TEXTURE_LOAD_MISSING",
|
|
129
|
+
path: `$.assets[${JSON.stringify(asset.id)}].path`,
|
|
130
|
+
message: `Sprite asset '${asset.id}' did not produce a Pixi texture from '${url}'.`,
|
|
131
|
+
suggestion: "Return a Pixi texture from loadTexture or leave the renderer debug fallback in place."
|
|
132
|
+
});
|
|
133
|
+
continue;
|
|
134
|
+
}
|
|
135
|
+
textures.set(asset.id, texture);
|
|
136
|
+
}
|
|
137
|
+
const spriteAssetIds = new Set(assets.filter((asset) => asset.type === "sprite").map((asset) => asset.id));
|
|
138
|
+
return {
|
|
139
|
+
assets,
|
|
140
|
+
textureResolver: (request) => {
|
|
141
|
+
if (!spriteAssetIds.has(request.assetId)) {
|
|
142
|
+
return void 0;
|
|
143
|
+
}
|
|
144
|
+
return textures.get(request.assetId);
|
|
145
|
+
},
|
|
146
|
+
diagnostics,
|
|
147
|
+
loadedTextureIds: [...textures.keys()].sort((left, right) => left.localeCompare(right))
|
|
148
|
+
};
|
|
149
|
+
}
|
|
150
|
+
async function createPixiAtlasTextureResolverFromManifest(manifestOrAssets, options = {}) {
|
|
151
|
+
const manifestDiagnostics = [];
|
|
152
|
+
const assets = readManifestOrAssetDefinitions(manifestOrAssets, manifestDiagnostics);
|
|
153
|
+
const diagnostics = [...manifestDiagnostics];
|
|
154
|
+
const assetsById = new Map(assets.map((asset) => [asset.id, asset]));
|
|
155
|
+
const textures = /* @__PURE__ */ new Map();
|
|
156
|
+
const frameTextures = /* @__PURE__ */ new Map();
|
|
157
|
+
const sliceTexture = options.sliceTexture ?? slicePixiTextureByAtlasFrame;
|
|
158
|
+
const diagnosedAtlasTextureIds = /* @__PURE__ */ new Set();
|
|
159
|
+
for (const asset of assets) {
|
|
160
|
+
if (asset.type !== "sprite" || !asset.atlas || !asset.frame) {
|
|
161
|
+
continue;
|
|
162
|
+
}
|
|
163
|
+
const atlas = assetsById.get(asset.atlas);
|
|
164
|
+
const atlasFrame = resolveDisplayAtlasFrameReference(asset, assetsById);
|
|
165
|
+
if (atlas?.type !== "atlas" || !atlasFrame) {
|
|
166
|
+
diagnostics.push({
|
|
167
|
+
severity: "warning",
|
|
168
|
+
code: "PIXI_ATLAS_SLICE_FRAME_UNRESOLVED",
|
|
169
|
+
path: `$.assets[${JSON.stringify(asset.id)}].frame`,
|
|
170
|
+
message: `Sprite asset '${asset.id}' cannot resolve atlas frame '${asset.frame}' in atlas '${asset.atlas}'.`,
|
|
171
|
+
suggestion: "Run project validation and ensure SpriteAsset.atlas and SpriteAsset.frame still match atlas metadata."
|
|
172
|
+
});
|
|
173
|
+
continue;
|
|
174
|
+
}
|
|
175
|
+
const atlasTexture = resolveAtlasTexture(atlas, assetsById, options.atlasTextures);
|
|
176
|
+
if (!atlasTexture) {
|
|
177
|
+
const diagnosticKey = atlas.texture ?? atlas.id;
|
|
178
|
+
if (!diagnosedAtlasTextureIds.has(diagnosticKey)) {
|
|
179
|
+
diagnosedAtlasTextureIds.add(diagnosticKey);
|
|
180
|
+
diagnostics.push(createMissingAtlasTextureDiagnostic(atlas));
|
|
181
|
+
}
|
|
182
|
+
continue;
|
|
183
|
+
}
|
|
184
|
+
if (!isAtlasFrameInsideTexture(atlasFrame.rect, atlasTexture)) {
|
|
185
|
+
diagnostics.push({
|
|
186
|
+
severity: "warning",
|
|
187
|
+
code: "PIXI_ATLAS_SLICE_RECT_OUT_OF_BOUNDS",
|
|
188
|
+
path: `$.assets[${JSON.stringify(asset.id)}].frame`,
|
|
189
|
+
message: `Sprite asset '${asset.id}' frame '${atlasFrame.frame}' is outside atlas texture '${atlas.id}'.`,
|
|
190
|
+
suggestion: "Check atlas frame rects against the loaded source texture dimensions before slicing. Scene validation remains metadata-only."
|
|
191
|
+
});
|
|
192
|
+
continue;
|
|
193
|
+
}
|
|
194
|
+
let slicedTexture;
|
|
195
|
+
try {
|
|
196
|
+
slicedTexture = await sliceTexture({
|
|
197
|
+
sprite: asset,
|
|
198
|
+
atlas,
|
|
199
|
+
atlasFrame,
|
|
200
|
+
atlasTexture
|
|
201
|
+
});
|
|
202
|
+
} catch (error) {
|
|
203
|
+
diagnostics.push({
|
|
204
|
+
severity: "warning",
|
|
205
|
+
code: "PIXI_ATLAS_SLICE_FAILED",
|
|
206
|
+
path: `$.assets[${JSON.stringify(asset.id)}].frame`,
|
|
207
|
+
message: `Sprite asset '${asset.id}' failed to slice frame '${atlasFrame.frame}' from atlas '${atlas.id}'.`,
|
|
208
|
+
suggestion: error instanceof Error ? error.message : "Check the loaded atlas texture, frame rect, and caller-provided sliceTexture function."
|
|
209
|
+
});
|
|
210
|
+
continue;
|
|
211
|
+
}
|
|
212
|
+
if (!slicedTexture) {
|
|
213
|
+
diagnostics.push({
|
|
214
|
+
severity: "warning",
|
|
215
|
+
code: "PIXI_ATLAS_SLICE_MISSING",
|
|
216
|
+
path: `$.assets[${JSON.stringify(asset.id)}].frame`,
|
|
217
|
+
message: `Sprite asset '${asset.id}' did not produce a sliced texture for frame '${atlasFrame.frame}'.`,
|
|
218
|
+
suggestion: "Return a Pixi texture from sliceTexture or leave the renderer debug fallback in place."
|
|
219
|
+
});
|
|
220
|
+
continue;
|
|
221
|
+
}
|
|
222
|
+
textures.set(asset.id, slicedTexture);
|
|
223
|
+
if (!frameTextures.has(atlasFrame.frame)) {
|
|
224
|
+
frameTextures.set(atlasFrame.frame, slicedTexture);
|
|
225
|
+
}
|
|
226
|
+
}
|
|
227
|
+
return {
|
|
228
|
+
assets,
|
|
229
|
+
textureResolver: (request) => {
|
|
230
|
+
const slicedTexture = textures.get(request.assetId) ?? (request.atlasFrame ? frameTextures.get(request.atlasFrame.frame) : void 0);
|
|
231
|
+
if (slicedTexture) {
|
|
232
|
+
return slicedTexture;
|
|
233
|
+
}
|
|
234
|
+
return options.fallbackTextureResolver ? resolveTextureByAssetId(options.fallbackTextureResolver, request) : void 0;
|
|
235
|
+
},
|
|
236
|
+
diagnostics,
|
|
237
|
+
slicedTextureIds: [...textures.keys()].sort((left, right) => left.localeCompare(right)),
|
|
238
|
+
slicedFrameIds: [...frameTextures.keys()].sort((left, right) => left.localeCompare(right))
|
|
239
|
+
};
|
|
240
|
+
}
|
|
241
|
+
async function createPixiRenderer(options = {}) {
|
|
242
|
+
const pixi = await import("pixi.js");
|
|
243
|
+
const app = new pixi.Application();
|
|
244
|
+
await app.init({
|
|
245
|
+
width: options.width ?? 800,
|
|
246
|
+
height: options.height ?? 450,
|
|
247
|
+
backgroundColor: options.backgroundColor ?? 988970,
|
|
248
|
+
resolution: options.resolution ?? 1,
|
|
249
|
+
antialias: options.antialias ?? false,
|
|
250
|
+
preference: options.preference ?? "webgl",
|
|
251
|
+
preserveDrawingBuffer: options.preserveDrawingBuffer ?? false
|
|
252
|
+
});
|
|
253
|
+
if (options.container) {
|
|
254
|
+
options.container.appendChild(app.canvas);
|
|
255
|
+
}
|
|
256
|
+
return {
|
|
257
|
+
app,
|
|
258
|
+
stage: app.stage,
|
|
259
|
+
async renderSnapshot(snapshot, renderOptions) {
|
|
260
|
+
return renderWorldSnapshot(snapshot, {
|
|
261
|
+
...renderOptions,
|
|
262
|
+
app,
|
|
263
|
+
stage: app.stage
|
|
264
|
+
});
|
|
265
|
+
},
|
|
266
|
+
destroy() {
|
|
267
|
+
app.destroy(true);
|
|
268
|
+
}
|
|
269
|
+
};
|
|
270
|
+
}
|
|
271
|
+
async function renderWorldSnapshot(snapshot, options = {}) {
|
|
272
|
+
const renderer = options.renderer;
|
|
273
|
+
const app = options.app ?? renderer?.app;
|
|
274
|
+
const stage = options.stage ?? renderer?.stage ?? app?.stage;
|
|
275
|
+
if (!stage) {
|
|
276
|
+
throw new Error("renderWorldSnapshot requires a Pixi renderer, app, or stage.");
|
|
277
|
+
}
|
|
278
|
+
const pixi = await import("pixi.js");
|
|
279
|
+
const displayList = createDisplayList(snapshot, options);
|
|
280
|
+
if (options.clear ?? true) {
|
|
281
|
+
stage.removeChildren();
|
|
282
|
+
}
|
|
283
|
+
for (const item of displayList.items) {
|
|
284
|
+
const texture = resolveTextureForDisplayItem(item, options.textureResolver);
|
|
285
|
+
if (texture) {
|
|
286
|
+
const sprite = new pixi.Sprite(texture);
|
|
287
|
+
markAnimationTextureState(item, true);
|
|
288
|
+
drawTexturedDisplayItem(sprite, item);
|
|
289
|
+
stage.addChild(sprite);
|
|
290
|
+
} else {
|
|
291
|
+
const graphic = new pixi.Graphics();
|
|
292
|
+
markAnimationTextureState(item, false);
|
|
293
|
+
drawDisplayItem(graphic, item);
|
|
294
|
+
stage.addChild(graphic);
|
|
295
|
+
}
|
|
296
|
+
}
|
|
297
|
+
app?.renderer.render(stage);
|
|
298
|
+
return displayList;
|
|
299
|
+
}
|
|
300
|
+
async function renderWorld(world, options = {}) {
|
|
301
|
+
return renderWorldSnapshot(worldToSnapshot(world, options), options);
|
|
302
|
+
}
|
|
303
|
+
function createDisplayItem(id, kind, transform, width, height, config) {
|
|
304
|
+
return {
|
|
305
|
+
id,
|
|
306
|
+
kind,
|
|
307
|
+
x: transform.position[0],
|
|
308
|
+
y: transform.position[1],
|
|
309
|
+
width,
|
|
310
|
+
height,
|
|
311
|
+
rotation: transform.rotation,
|
|
312
|
+
scaleX: transform.scale[0],
|
|
313
|
+
scaleY: transform.scale[1],
|
|
314
|
+
fill: config.fill,
|
|
315
|
+
alpha: config.options.alpha ?? defaultAlpha,
|
|
316
|
+
stroke: config.options.stroke ?? defaultStroke,
|
|
317
|
+
strokeWidth: config.options.strokeWidth ?? defaultStrokeWidth,
|
|
318
|
+
layer: config.options.layer ?? defaultLayer,
|
|
319
|
+
order: 0,
|
|
320
|
+
...config.animation ? { animation: config.animation } : {}
|
|
321
|
+
};
|
|
322
|
+
}
|
|
323
|
+
function drawDisplayItem(graphic, item) {
|
|
324
|
+
graphic.position.set(item.x, item.y);
|
|
325
|
+
graphic.rotation = item.rotation;
|
|
326
|
+
graphic.scale.set(item.scaleX, item.scaleY);
|
|
327
|
+
if (item.kind === "rectangle") {
|
|
328
|
+
graphic.rect(item.width / -2, item.height / -2, item.width, item.height);
|
|
329
|
+
} else {
|
|
330
|
+
graphic.rect(item.width / -2, item.height / -2, item.width, item.height);
|
|
331
|
+
}
|
|
332
|
+
graphic.fill({ color: item.fill, alpha: item.alpha });
|
|
333
|
+
graphic.stroke({ color: item.stroke, width: item.strokeWidth });
|
|
334
|
+
}
|
|
335
|
+
function drawTexturedDisplayItem(sprite, item) {
|
|
336
|
+
sprite.anchor.set(0.5);
|
|
337
|
+
sprite.position.set(item.x, item.y);
|
|
338
|
+
sprite.rotation = item.rotation;
|
|
339
|
+
sprite.width = item.width * item.scaleX;
|
|
340
|
+
sprite.height = item.height * item.scaleY;
|
|
341
|
+
}
|
|
342
|
+
function resolveTextureForDisplayItem(item, resolver) {
|
|
343
|
+
const asset = item.animation?.asset;
|
|
344
|
+
if (!asset || !resolver) {
|
|
345
|
+
return void 0;
|
|
346
|
+
}
|
|
347
|
+
if (typeof resolver === "function") {
|
|
348
|
+
return resolver({
|
|
349
|
+
assetId: asset.id,
|
|
350
|
+
asset,
|
|
351
|
+
...asset.atlasFrame ? { atlasFrame: asset.atlasFrame } : {},
|
|
352
|
+
item
|
|
353
|
+
});
|
|
354
|
+
}
|
|
355
|
+
if (resolver instanceof Map) {
|
|
356
|
+
return resolver.get(asset.id);
|
|
357
|
+
}
|
|
358
|
+
return resolver[asset.id];
|
|
359
|
+
}
|
|
360
|
+
function resolveTextureByAssetId(resolver, request) {
|
|
361
|
+
if (typeof resolver === "function") {
|
|
362
|
+
return resolver(request);
|
|
363
|
+
}
|
|
364
|
+
if (resolver instanceof Map) {
|
|
365
|
+
return resolver.get(request.assetId);
|
|
366
|
+
}
|
|
367
|
+
return resolver[request.assetId];
|
|
368
|
+
}
|
|
369
|
+
async function loadPixiTextureFromUrl(request) {
|
|
370
|
+
const pixi = await import("pixi.js");
|
|
371
|
+
return await pixi.Assets.load(request.url);
|
|
372
|
+
}
|
|
373
|
+
async function slicePixiTextureByAtlasFrame(request) {
|
|
374
|
+
const pixi = await import("pixi.js");
|
|
375
|
+
const [x, y, width, height] = request.atlasFrame.rect;
|
|
376
|
+
return new pixi.Texture({
|
|
377
|
+
source: request.atlasTexture.source,
|
|
378
|
+
frame: new pixi.Rectangle(x, y, width, height)
|
|
379
|
+
});
|
|
380
|
+
}
|
|
381
|
+
function resolveAtlasTexture(atlas, assetsById, resolver) {
|
|
382
|
+
if (!resolver) {
|
|
383
|
+
return void 0;
|
|
384
|
+
}
|
|
385
|
+
const textureAsset = atlas.texture ? assetsById.get(atlas.texture) : void 0;
|
|
386
|
+
const request = {
|
|
387
|
+
atlas,
|
|
388
|
+
...textureAsset?.type === "texture" ? { textureAsset } : {},
|
|
389
|
+
...atlas.texture ? { textureId: atlas.texture } : {}
|
|
390
|
+
};
|
|
391
|
+
if (typeof resolver === "function") {
|
|
392
|
+
return resolver(request);
|
|
393
|
+
}
|
|
394
|
+
return resolveTextureByAtlasTextureId(resolver, atlas.texture) ?? resolveTextureByAtlasTextureId(resolver, atlas.id);
|
|
395
|
+
}
|
|
396
|
+
function resolveTextureByAtlasTextureId(resolver, textureId) {
|
|
397
|
+
if (!textureId) {
|
|
398
|
+
return void 0;
|
|
399
|
+
}
|
|
400
|
+
if (resolver instanceof Map) {
|
|
401
|
+
return resolver.get(textureId);
|
|
402
|
+
}
|
|
403
|
+
return resolver[textureId];
|
|
404
|
+
}
|
|
405
|
+
function createMissingAtlasTextureDiagnostic(atlas) {
|
|
406
|
+
if (!atlas.texture) {
|
|
407
|
+
return {
|
|
408
|
+
severity: "warning",
|
|
409
|
+
code: "PIXI_ATLAS_SLICE_TEXTURE_REFERENCE_MISSING",
|
|
410
|
+
path: `$.assets[${JSON.stringify(atlas.id)}].texture`,
|
|
411
|
+
message: `Atlas asset '${atlas.id}' has no source texture id for slicing.`,
|
|
412
|
+
suggestion: "Set atlas.texture to a texture asset id or pass an atlas texture keyed by the atlas id."
|
|
413
|
+
};
|
|
414
|
+
}
|
|
415
|
+
return {
|
|
416
|
+
severity: "warning",
|
|
417
|
+
code: "PIXI_ATLAS_SLICE_TEXTURE_MISSING",
|
|
418
|
+
path: `$.assets[${JSON.stringify(atlas.id)}].texture`,
|
|
419
|
+
message: `Atlas asset '${atlas.id}' source texture '${atlas.texture}' was not provided to the Pixi atlas slicing helper.`,
|
|
420
|
+
suggestion: "Pass atlasTextures keyed by atlas.texture or atlas id, or keep debug fallback behavior."
|
|
421
|
+
};
|
|
422
|
+
}
|
|
423
|
+
function isAtlasFrameInsideTexture(rect, texture) {
|
|
424
|
+
const [x, y, width, height] = rect;
|
|
425
|
+
return x >= 0 && y >= 0 && x + width <= texture.width && y + height <= texture.height;
|
|
426
|
+
}
|
|
427
|
+
function resolveAssetLoadUrl(asset, atlasFrame, options, diagnostics) {
|
|
428
|
+
if (options.resolveUrl) {
|
|
429
|
+
let resolved;
|
|
430
|
+
try {
|
|
431
|
+
resolved = options.resolveUrl({
|
|
432
|
+
asset,
|
|
433
|
+
...atlasFrame ? { atlasFrame } : {}
|
|
434
|
+
});
|
|
435
|
+
} catch (error) {
|
|
436
|
+
diagnostics.push({
|
|
437
|
+
severity: "warning",
|
|
438
|
+
code: "PIXI_ASSET_TEXTURE_URL_FAILED",
|
|
439
|
+
path: `$.assets[${JSON.stringify(asset.id)}].path`,
|
|
440
|
+
message: `Sprite asset '${asset.id}' texture URL resolution failed.`,
|
|
441
|
+
suggestion: error instanceof Error ? error.message : "Check the caller-provided resolveUrl function and keep asset URL policy explicit."
|
|
442
|
+
});
|
|
443
|
+
return void 0;
|
|
444
|
+
}
|
|
445
|
+
const url = stringifyAssetUrl(resolved);
|
|
446
|
+
if (url) {
|
|
447
|
+
return url;
|
|
448
|
+
}
|
|
449
|
+
diagnostics.push({
|
|
450
|
+
severity: "warning",
|
|
451
|
+
code: "PIXI_ASSET_TEXTURE_URL_UNRESOLVED",
|
|
452
|
+
path: `$.assets[${JSON.stringify(asset.id)}].path`,
|
|
453
|
+
message: `Sprite asset '${asset.id}' has no browser texture URL.`,
|
|
454
|
+
suggestion: "Return a URL from resolveUrl or omit the texture so the renderer uses the debug fallback."
|
|
455
|
+
});
|
|
456
|
+
return void 0;
|
|
457
|
+
}
|
|
458
|
+
if (options.baseUrl !== void 0) {
|
|
459
|
+
try {
|
|
460
|
+
return new URL(asset.path, options.baseUrl).toString();
|
|
461
|
+
} catch (error) {
|
|
462
|
+
diagnostics.push({
|
|
463
|
+
severity: "warning",
|
|
464
|
+
code: "PIXI_ASSET_TEXTURE_URL_FAILED",
|
|
465
|
+
path: `$.assets[${JSON.stringify(asset.id)}].path`,
|
|
466
|
+
message: `Sprite asset '${asset.id}' path '${asset.path}' could not be resolved against the loader baseUrl.`,
|
|
467
|
+
suggestion: error instanceof Error ? error.message : "Pass an absolute baseUrl or a resolveUrl function for browser asset paths."
|
|
468
|
+
});
|
|
469
|
+
return void 0;
|
|
470
|
+
}
|
|
471
|
+
}
|
|
472
|
+
return asset.path;
|
|
473
|
+
}
|
|
474
|
+
function stringifyAssetUrl(value) {
|
|
475
|
+
if (value instanceof URL) {
|
|
476
|
+
return value.toString();
|
|
477
|
+
}
|
|
478
|
+
return typeof value === "string" && value.length > 0 ? value : void 0;
|
|
479
|
+
}
|
|
480
|
+
function markAnimationTextureState(item, applied) {
|
|
481
|
+
const assetId = item.animation?.asset?.id;
|
|
482
|
+
if (!assetId || !item.animation) {
|
|
483
|
+
return;
|
|
484
|
+
}
|
|
485
|
+
item.animation.texture = {
|
|
486
|
+
assetId,
|
|
487
|
+
applied,
|
|
488
|
+
mode: applied ? "texture" : "debug"
|
|
489
|
+
};
|
|
490
|
+
}
|
|
491
|
+
function worldToSnapshot(world, options) {
|
|
492
|
+
const fixedDeltaSeconds = options.fixedDeltaSeconds ?? 1 / 60;
|
|
493
|
+
const frame = options.frame ?? 0;
|
|
494
|
+
return {
|
|
495
|
+
version: 1,
|
|
496
|
+
frame,
|
|
497
|
+
fixedDeltaSeconds,
|
|
498
|
+
elapsedSeconds: options.elapsedSeconds ?? roundDeterministic(frame * fixedDeltaSeconds),
|
|
499
|
+
entities: world.listEntities().map((entity) => ({
|
|
500
|
+
id: entity.id,
|
|
501
|
+
components: entity.components
|
|
502
|
+
})).sort((left, right) => left.id.localeCompare(right.id))
|
|
503
|
+
};
|
|
504
|
+
}
|
|
505
|
+
function collectAnimationClips(snapshot) {
|
|
506
|
+
const clips = /* @__PURE__ */ new Map();
|
|
507
|
+
for (const entity of snapshot.entities ?? []) {
|
|
508
|
+
const clip = readAnimationClip(entity.components?.AnimationClip);
|
|
509
|
+
if (clip) {
|
|
510
|
+
clips.set(clip.id, clip);
|
|
511
|
+
}
|
|
512
|
+
}
|
|
513
|
+
return clips;
|
|
514
|
+
}
|
|
515
|
+
function readAnimationClip(value) {
|
|
516
|
+
if (!isRecord(value) || typeof value.id !== "string" || !Array.isArray(value.frames)) {
|
|
517
|
+
return void 0;
|
|
518
|
+
}
|
|
519
|
+
const frames = value.frames.flatMap((frame) => {
|
|
520
|
+
if (!isRecord(frame) || typeof frame.id !== "string") {
|
|
521
|
+
return [];
|
|
522
|
+
}
|
|
523
|
+
const sprite = typeof frame.sprite === "string" ? frame.sprite : void 0;
|
|
524
|
+
const rect = readNumberTuple(frame.rect, 4);
|
|
525
|
+
return [
|
|
526
|
+
{
|
|
527
|
+
id: frame.id,
|
|
528
|
+
...sprite ? { sprite } : {},
|
|
529
|
+
...rect ? { rect } : {}
|
|
530
|
+
}
|
|
531
|
+
];
|
|
532
|
+
});
|
|
533
|
+
if (frames.length === 0) {
|
|
534
|
+
return void 0;
|
|
535
|
+
}
|
|
536
|
+
return {
|
|
537
|
+
id: value.id,
|
|
538
|
+
...Array.isArray(value.frameOrder) ? { frameOrder: value.frameOrder.filter((frameId) => typeof frameId === "string") } : {},
|
|
539
|
+
frames
|
|
540
|
+
};
|
|
541
|
+
}
|
|
542
|
+
function readAnimatorPlayback(entityId, value, clips, assetsById, diagnostics) {
|
|
543
|
+
if (!isRecord(value)) {
|
|
544
|
+
return void 0;
|
|
545
|
+
}
|
|
546
|
+
const activeClip = readString(value.activeClip) ?? readString(value.defaultClip) ?? readString(value.currentClip);
|
|
547
|
+
if (!activeClip) {
|
|
548
|
+
return void 0;
|
|
549
|
+
}
|
|
550
|
+
const clip = clips.get(activeClip);
|
|
551
|
+
if (!clip) {
|
|
552
|
+
diagnostics.push({
|
|
553
|
+
severity: "warning",
|
|
554
|
+
code: "PIXI_ANIMATION_CLIP_UNRESOLVED",
|
|
555
|
+
entityId,
|
|
556
|
+
path: `$.entities[${JSON.stringify(entityId)}].components.Animator.activeClip`,
|
|
557
|
+
message: `Animator activeClip '${activeClip}' cannot be resolved by the Pixi display list.`,
|
|
558
|
+
suggestion: "Run scene validation and ensure the active clip id exists in the rendered snapshot."
|
|
559
|
+
});
|
|
560
|
+
return void 0;
|
|
561
|
+
}
|
|
562
|
+
const orderedFrames = orderAnimationFrames(clip);
|
|
563
|
+
const explicitActiveFrameId = readString(value.activeFrameId);
|
|
564
|
+
const explicitFrameIndex = readNonNegativeInteger(value.currentFrameIndex);
|
|
565
|
+
const frameIndex = explicitActiveFrameId !== void 0 ? orderedFrames.findIndex((frame2) => frame2.id === explicitActiveFrameId) : explicitFrameIndex ?? 0;
|
|
566
|
+
const frame = orderedFrames[frameIndex];
|
|
567
|
+
if (!frame || frameIndex < 0) {
|
|
568
|
+
const frameId = explicitActiveFrameId ?? String(explicitFrameIndex ?? 0);
|
|
569
|
+
diagnostics.push({
|
|
570
|
+
severity: "warning",
|
|
571
|
+
code: "PIXI_ANIMATION_FRAME_UNRESOLVED",
|
|
572
|
+
entityId,
|
|
573
|
+
path: `$.entities[${JSON.stringify(entityId)}].components.Animator.activeFrameId`,
|
|
574
|
+
message: `Animator activeFrameId '${frameId}' cannot be resolved in clip '${activeClip}'.`,
|
|
575
|
+
suggestion: "Run scene validation and ensure activeFrameId references a SpriteFrame in the active clip."
|
|
576
|
+
});
|
|
577
|
+
return void 0;
|
|
578
|
+
}
|
|
579
|
+
const elapsedFrames = readNonNegativeInteger(value.elapsedFrames);
|
|
580
|
+
const elapsedSeconds = readFiniteNumber(value.elapsedSeconds);
|
|
581
|
+
const asset = frame.sprite ? resolveDisplayAssetReference(entityId, frame.sprite, assetsById, diagnostics) : void 0;
|
|
582
|
+
return {
|
|
583
|
+
activeClip,
|
|
584
|
+
activeFrameId: frame.id,
|
|
585
|
+
frameIndex,
|
|
586
|
+
...frame.sprite ? { sprite: frame.sprite } : {},
|
|
587
|
+
...frame.rect ? { rect: frame.rect } : {},
|
|
588
|
+
...asset ? { asset } : {},
|
|
589
|
+
...asset?.atlasFrame ? { atlasFrame: asset.atlasFrame } : {},
|
|
590
|
+
...elapsedFrames !== void 0 ? { elapsedFrames } : {},
|
|
591
|
+
...elapsedSeconds !== void 0 ? { elapsedSeconds } : {}
|
|
592
|
+
};
|
|
593
|
+
}
|
|
594
|
+
function orderAnimationFrames(clip) {
|
|
595
|
+
if (!clip.frameOrder || clip.frameOrder.length === 0) {
|
|
596
|
+
return clip.frames;
|
|
597
|
+
}
|
|
598
|
+
const framesById = new Map(clip.frames.map((frame) => [frame.id, frame]));
|
|
599
|
+
return clip.frameOrder.flatMap((frameId) => {
|
|
600
|
+
const frame = framesById.get(frameId);
|
|
601
|
+
return frame ? [frame] : [];
|
|
602
|
+
});
|
|
603
|
+
}
|
|
604
|
+
function collectDisplayAssets(assets) {
|
|
605
|
+
if (!assets) {
|
|
606
|
+
return void 0;
|
|
607
|
+
}
|
|
608
|
+
return new Map(assets.map((asset) => [asset.id, asset]));
|
|
609
|
+
}
|
|
610
|
+
function readManifestOrAssetDefinitions(value, diagnostics) {
|
|
611
|
+
if (Array.isArray(value)) {
|
|
612
|
+
return readManifestAssets(value, diagnostics);
|
|
613
|
+
}
|
|
614
|
+
const document = readManifestDocument(value, diagnostics);
|
|
615
|
+
return document ? readManifestAssets(document.assets, diagnostics) : [];
|
|
616
|
+
}
|
|
617
|
+
function readManifestDocument(value, diagnostics) {
|
|
618
|
+
const document = parseManifestValue(value, diagnostics);
|
|
619
|
+
if (!isRecord(document)) {
|
|
620
|
+
diagnostics.push({
|
|
621
|
+
severity: "error",
|
|
622
|
+
code: "PIXI_ASSET_MANIFEST_INVALID",
|
|
623
|
+
path: "$",
|
|
624
|
+
message: "Asset manifest must be an object.",
|
|
625
|
+
suggestion: "Pass parsed Asset Manifest v0 JSON with version and assets."
|
|
626
|
+
});
|
|
627
|
+
return void 0;
|
|
628
|
+
}
|
|
629
|
+
if (!Array.isArray(document.assets)) {
|
|
630
|
+
diagnostics.push({
|
|
631
|
+
severity: "error",
|
|
632
|
+
code: "PIXI_ASSET_MANIFEST_ASSETS_MISSING",
|
|
633
|
+
path: "$.assets",
|
|
634
|
+
message: "Asset manifest is missing an assets array.",
|
|
635
|
+
suggestion: "Add assets: [] entries with stable ids, types, and paths."
|
|
636
|
+
});
|
|
637
|
+
return void 0;
|
|
638
|
+
}
|
|
639
|
+
return {
|
|
640
|
+
assets: document.assets
|
|
641
|
+
};
|
|
642
|
+
}
|
|
643
|
+
function parseManifestValue(value, diagnostics) {
|
|
644
|
+
if (typeof value !== "string") {
|
|
645
|
+
return value;
|
|
646
|
+
}
|
|
647
|
+
try {
|
|
648
|
+
return JSON.parse(value);
|
|
649
|
+
} catch (error) {
|
|
650
|
+
diagnostics.push({
|
|
651
|
+
severity: "error",
|
|
652
|
+
code: "PIXI_ASSET_MANIFEST_PARSE_FAILED",
|
|
653
|
+
path: "$",
|
|
654
|
+
message: error instanceof Error ? error.message : String(error),
|
|
655
|
+
suggestion: "Pass parsed JSON or a valid JSON string."
|
|
656
|
+
});
|
|
657
|
+
return void 0;
|
|
658
|
+
}
|
|
659
|
+
}
|
|
660
|
+
function readManifestAssets(assets, diagnostics) {
|
|
661
|
+
const assetsById = /* @__PURE__ */ new Map();
|
|
662
|
+
for (const [index, value] of assets.entries()) {
|
|
663
|
+
const assetPath = `$.assets[${index}]`;
|
|
664
|
+
if (!isRecord(value)) {
|
|
665
|
+
diagnostics.push({
|
|
666
|
+
severity: "error",
|
|
667
|
+
code: "PIXI_ASSET_INVALID",
|
|
668
|
+
path: assetPath,
|
|
669
|
+
message: "Asset entry must be an object.",
|
|
670
|
+
suggestion: "Use asset entries with id, type, and path fields."
|
|
671
|
+
});
|
|
672
|
+
continue;
|
|
673
|
+
}
|
|
674
|
+
const id = readString(value.id);
|
|
675
|
+
if (!id) {
|
|
676
|
+
diagnostics.push({
|
|
677
|
+
severity: "error",
|
|
678
|
+
code: "PIXI_ASSET_ID_MISSING",
|
|
679
|
+
path: `${assetPath}.id`,
|
|
680
|
+
message: "Asset entry is missing a stable id.",
|
|
681
|
+
suggestion: "Add a non-empty asset id and reference that id from SpriteFrame.sprite."
|
|
682
|
+
});
|
|
683
|
+
continue;
|
|
684
|
+
}
|
|
685
|
+
if (assetsById.has(id)) {
|
|
686
|
+
diagnostics.push({
|
|
687
|
+
severity: "error",
|
|
688
|
+
code: "PIXI_ASSET_DUPLICATE_ID",
|
|
689
|
+
path: `${assetPath}.id`,
|
|
690
|
+
message: `Duplicate asset id '${id}'.`,
|
|
691
|
+
suggestion: "Use one stable asset id once in the manifest."
|
|
692
|
+
});
|
|
693
|
+
continue;
|
|
694
|
+
}
|
|
695
|
+
const type = readString(value.type);
|
|
696
|
+
if (!type || !supportedManifestAssetTypes.has(type)) {
|
|
697
|
+
diagnostics.push({
|
|
698
|
+
severity: "error",
|
|
699
|
+
code: "PIXI_ASSET_TYPE_UNSUPPORTED",
|
|
700
|
+
path: `${assetPath}.type`,
|
|
701
|
+
message: `Asset '${id}' has unsupported type '${String(value.type)}'.`,
|
|
702
|
+
suggestion: "Use one of: texture, sprite, atlas, audio, font."
|
|
703
|
+
});
|
|
704
|
+
continue;
|
|
705
|
+
}
|
|
706
|
+
const sourcePath = readString(value.path);
|
|
707
|
+
if (!sourcePath) {
|
|
708
|
+
diagnostics.push({
|
|
709
|
+
severity: "error",
|
|
710
|
+
code: "PIXI_ASSET_PATH_MISSING",
|
|
711
|
+
path: `${assetPath}.path`,
|
|
712
|
+
message: `Asset '${id}' is missing a source path.`,
|
|
713
|
+
suggestion: "Add a project-relative path. The renderer helper validates metadata only and does not load the file."
|
|
714
|
+
});
|
|
715
|
+
continue;
|
|
716
|
+
}
|
|
717
|
+
const asset = createAssetDefinition(id, type, sourcePath, value, assetPath, diagnostics);
|
|
718
|
+
assetsById.set(id, asset);
|
|
719
|
+
}
|
|
720
|
+
validateManifestAssetReferences(assetsById, diagnostics);
|
|
721
|
+
return [...assetsById.values()].sort((left, right) => left.id.localeCompare(right.id));
|
|
722
|
+
}
|
|
723
|
+
function createAssetDefinition(id, type, sourcePath, value, assetPath, diagnostics) {
|
|
724
|
+
if (type === "sprite") {
|
|
725
|
+
const texture = readString(value.texture);
|
|
726
|
+
const atlas = readString(value.atlas);
|
|
727
|
+
const frame = readString(value.frame);
|
|
728
|
+
const rect = readManifestSpriteRect(value.rect, assetPath, id, diagnostics);
|
|
729
|
+
return {
|
|
730
|
+
id,
|
|
731
|
+
type,
|
|
732
|
+
path: sourcePath,
|
|
733
|
+
...texture ? { texture } : {},
|
|
734
|
+
...atlas ? { atlas } : {},
|
|
735
|
+
...frame ? { frame } : {},
|
|
736
|
+
...rect ? { rect } : {}
|
|
737
|
+
};
|
|
738
|
+
}
|
|
739
|
+
if (type === "atlas") {
|
|
740
|
+
const texture = readString(value.texture);
|
|
741
|
+
return {
|
|
742
|
+
id,
|
|
743
|
+
type,
|
|
744
|
+
path: sourcePath,
|
|
745
|
+
...texture ? { texture } : {},
|
|
746
|
+
frames: readManifestAtlasFrames(value.frames, assetPath, id, diagnostics)
|
|
747
|
+
};
|
|
748
|
+
}
|
|
749
|
+
return {
|
|
750
|
+
id,
|
|
751
|
+
type,
|
|
752
|
+
path: sourcePath
|
|
753
|
+
};
|
|
754
|
+
}
|
|
755
|
+
function readManifestAtlasFrames(value, assetPath, atlasId, diagnostics) {
|
|
756
|
+
if (!Array.isArray(value)) {
|
|
757
|
+
diagnostics.push({
|
|
758
|
+
severity: "error",
|
|
759
|
+
code: "PIXI_ASSET_ATLAS_FRAMES_MISSING",
|
|
760
|
+
path: `${assetPath}.frames`,
|
|
761
|
+
message: `Atlas asset '${atlasId}' is missing a frames array.`,
|
|
762
|
+
suggestion: "Add frame entries with stable ids and [x, y, width, height] rect metadata."
|
|
763
|
+
});
|
|
764
|
+
return [];
|
|
765
|
+
}
|
|
766
|
+
const framesById = /* @__PURE__ */ new Map();
|
|
767
|
+
for (const [index, frame] of value.entries()) {
|
|
768
|
+
const framePath = `${assetPath}.frames[${index}]`;
|
|
769
|
+
if (!isRecord(frame)) {
|
|
770
|
+
diagnostics.push({
|
|
771
|
+
severity: "error",
|
|
772
|
+
code: "PIXI_ASSET_ATLAS_FRAME_INVALID",
|
|
773
|
+
path: framePath,
|
|
774
|
+
message: `Atlas frame entry in '${atlasId}' must be an object.`,
|
|
775
|
+
suggestion: "Use frame entries with id and rect fields."
|
|
776
|
+
});
|
|
777
|
+
continue;
|
|
778
|
+
}
|
|
779
|
+
const frameId = readString(frame.id);
|
|
780
|
+
if (!frameId) {
|
|
781
|
+
diagnostics.push({
|
|
782
|
+
severity: "error",
|
|
783
|
+
code: "PIXI_ASSET_ATLAS_FRAME_ID_MISSING",
|
|
784
|
+
path: `${framePath}.id`,
|
|
785
|
+
message: `Atlas asset '${atlasId}' has a frame without a stable id.`,
|
|
786
|
+
suggestion: "Add a non-empty frame id and reference it from SpriteAsset.frame."
|
|
787
|
+
});
|
|
788
|
+
continue;
|
|
789
|
+
}
|
|
790
|
+
if (framesById.has(frameId)) {
|
|
791
|
+
diagnostics.push({
|
|
792
|
+
severity: "error",
|
|
793
|
+
code: "PIXI_ASSET_ATLAS_DUPLICATE_FRAME_ID",
|
|
794
|
+
path: `${framePath}.id`,
|
|
795
|
+
message: `Atlas asset '${atlasId}' defines duplicate frame id '${frameId}'.`,
|
|
796
|
+
suggestion: "Use each frame id once per atlas."
|
|
797
|
+
});
|
|
798
|
+
continue;
|
|
799
|
+
}
|
|
800
|
+
const rect = readManifestAtlasFrameRect(frame.rect, framePath, atlasId, frameId, diagnostics);
|
|
801
|
+
if (!rect) {
|
|
802
|
+
continue;
|
|
803
|
+
}
|
|
804
|
+
framesById.set(frameId, {
|
|
805
|
+
id: frameId,
|
|
806
|
+
rect
|
|
807
|
+
});
|
|
808
|
+
}
|
|
809
|
+
return [...framesById.values()].sort((left, right) => left.id.localeCompare(right.id));
|
|
810
|
+
}
|
|
811
|
+
function readManifestAtlasFrameRect(value, framePath, atlasId, frameId, diagnostics) {
|
|
812
|
+
if (!Array.isArray(value) || value.length !== 4) {
|
|
813
|
+
diagnostics.push({
|
|
814
|
+
severity: "error",
|
|
815
|
+
code: "PIXI_ASSET_ATLAS_FRAME_RECT_INVALID",
|
|
816
|
+
path: `${framePath}.rect`,
|
|
817
|
+
message: `Atlas frame '${frameId}' in atlas '${atlasId}' rect must be [x, y, width, height].`,
|
|
818
|
+
suggestion: "Use four finite numbers. The Pixi helper records metadata only and does not slice atlases."
|
|
819
|
+
});
|
|
820
|
+
return void 0;
|
|
821
|
+
}
|
|
822
|
+
const invalidIndex = value.findIndex((item) => typeof item !== "number" || !Number.isFinite(item));
|
|
823
|
+
if (invalidIndex !== -1) {
|
|
824
|
+
diagnostics.push({
|
|
825
|
+
severity: "error",
|
|
826
|
+
code: "PIXI_ASSET_ATLAS_FRAME_RECT_INVALID",
|
|
827
|
+
path: `${framePath}.rect[${invalidIndex}]`,
|
|
828
|
+
message: `Atlas frame '${frameId}' in atlas '${atlasId}' rect value at index ${invalidIndex} must be a finite number.`,
|
|
829
|
+
suggestion: "Use numeric [x, y, width, height] atlas metadata."
|
|
830
|
+
});
|
|
831
|
+
return void 0;
|
|
832
|
+
}
|
|
833
|
+
const rect = [value[0], value[1], value[2], value[3]];
|
|
834
|
+
if (rect[2] <= 0 || rect[3] <= 0) {
|
|
835
|
+
diagnostics.push({
|
|
836
|
+
severity: "error",
|
|
837
|
+
code: "PIXI_ASSET_ATLAS_FRAME_RECT_INVALID",
|
|
838
|
+
path: `${framePath}.rect[${rect[2] <= 0 ? 2 : 3}]`,
|
|
839
|
+
message: `Atlas frame '${frameId}' in atlas '${atlasId}' rect width and height must be greater than zero.`,
|
|
840
|
+
suggestion: "Use positive width and height values. Atlas slicing remains caller-owned."
|
|
841
|
+
});
|
|
842
|
+
return void 0;
|
|
843
|
+
}
|
|
844
|
+
return rect;
|
|
845
|
+
}
|
|
846
|
+
function readManifestSpriteRect(value, assetPath, assetId, diagnostics) {
|
|
847
|
+
if (value === void 0) {
|
|
848
|
+
return void 0;
|
|
849
|
+
}
|
|
850
|
+
if (!Array.isArray(value) || value.length !== 4) {
|
|
851
|
+
diagnostics.push({
|
|
852
|
+
severity: "error",
|
|
853
|
+
code: "PIXI_ASSET_RECT_INVALID",
|
|
854
|
+
path: `${assetPath}.rect`,
|
|
855
|
+
message: `Sprite asset '${assetId}' rect must be [x, y, width, height].`,
|
|
856
|
+
suggestion: "Use four finite numbers. The Pixi helper records metadata only and does not slice atlases."
|
|
857
|
+
});
|
|
858
|
+
return void 0;
|
|
859
|
+
}
|
|
860
|
+
const invalidIndex = value.findIndex((item) => typeof item !== "number" || !Number.isFinite(item));
|
|
861
|
+
if (invalidIndex !== -1) {
|
|
862
|
+
diagnostics.push({
|
|
863
|
+
severity: "error",
|
|
864
|
+
code: "PIXI_ASSET_RECT_INVALID",
|
|
865
|
+
path: `${assetPath}.rect[${invalidIndex}]`,
|
|
866
|
+
message: `Sprite asset '${assetId}' rect value at index ${invalidIndex} must be a finite number.`,
|
|
867
|
+
suggestion: "Use numeric [x, y, width, height] atlas planning metadata."
|
|
868
|
+
});
|
|
869
|
+
return void 0;
|
|
870
|
+
}
|
|
871
|
+
const rect = [value[0], value[1], value[2], value[3]];
|
|
872
|
+
if (rect[2] <= 0 || rect[3] <= 0) {
|
|
873
|
+
diagnostics.push({
|
|
874
|
+
severity: "error",
|
|
875
|
+
code: "PIXI_ASSET_RECT_INVALID",
|
|
876
|
+
path: `${assetPath}.rect[${rect[2] <= 0 ? 2 : 3}]`,
|
|
877
|
+
message: `Sprite asset '${assetId}' rect width and height must be greater than zero.`,
|
|
878
|
+
suggestion: "Use positive width and height values. Atlas slicing remains caller-owned."
|
|
879
|
+
});
|
|
880
|
+
return void 0;
|
|
881
|
+
}
|
|
882
|
+
return rect;
|
|
883
|
+
}
|
|
884
|
+
function validateManifestAssetReferences(assetsById, diagnostics) {
|
|
885
|
+
for (const asset of assetsById.values()) {
|
|
886
|
+
if (asset.type === "sprite") {
|
|
887
|
+
if (asset.texture) {
|
|
888
|
+
validateManifestReferenceToType({
|
|
889
|
+
assetsById,
|
|
890
|
+
diagnostics,
|
|
891
|
+
path: `$.assets[${JSON.stringify(asset.id)}].texture`,
|
|
892
|
+
ownerType: "Sprite asset",
|
|
893
|
+
assetId: asset.id,
|
|
894
|
+
referencedId: asset.texture,
|
|
895
|
+
expectedType: "texture",
|
|
896
|
+
missingCode: "PIXI_ASSET_REFERENCE_UNRESOLVED",
|
|
897
|
+
mismatchCode: "PIXI_ASSET_REFERENCE_TYPE_MISMATCH",
|
|
898
|
+
suggestion: "Add the referenced texture asset or remove the texture link until atlas loading exists."
|
|
899
|
+
});
|
|
900
|
+
}
|
|
901
|
+
validateManifestSpriteAtlasFrameReference(asset, assetsById, diagnostics);
|
|
902
|
+
continue;
|
|
903
|
+
}
|
|
904
|
+
if (asset.type === "atlas" && asset.texture) {
|
|
905
|
+
validateManifestReferenceToType({
|
|
906
|
+
assetsById,
|
|
907
|
+
diagnostics,
|
|
908
|
+
path: `$.assets[${JSON.stringify(asset.id)}].texture`,
|
|
909
|
+
ownerType: "Atlas asset",
|
|
910
|
+
assetId: asset.id,
|
|
911
|
+
referencedId: asset.texture,
|
|
912
|
+
expectedType: "texture",
|
|
913
|
+
missingCode: "PIXI_ASSET_ATLAS_TEXTURE_REFERENCE_UNRESOLVED",
|
|
914
|
+
mismatchCode: "PIXI_ASSET_ATLAS_TEXTURE_TYPE_MISMATCH",
|
|
915
|
+
suggestion: "Point atlas.texture to a texture asset id or remove the texture reference."
|
|
916
|
+
});
|
|
917
|
+
}
|
|
918
|
+
}
|
|
919
|
+
}
|
|
920
|
+
function validateManifestReferenceToType(input) {
|
|
921
|
+
const referenced = input.assetsById.get(input.referencedId);
|
|
922
|
+
if (!referenced) {
|
|
923
|
+
input.diagnostics.push({
|
|
924
|
+
severity: "error",
|
|
925
|
+
code: input.missingCode,
|
|
926
|
+
path: input.path,
|
|
927
|
+
message: `${input.ownerType} '${input.assetId}' references missing ${input.expectedType} asset '${input.referencedId}'.`,
|
|
928
|
+
suggestion: input.suggestion
|
|
929
|
+
});
|
|
930
|
+
return;
|
|
931
|
+
}
|
|
932
|
+
if (referenced.type !== input.expectedType) {
|
|
933
|
+
input.diagnostics.push({
|
|
934
|
+
severity: "error",
|
|
935
|
+
code: input.mismatchCode,
|
|
936
|
+
path: input.path,
|
|
937
|
+
message: `${input.ownerType} '${input.assetId}' references '${input.referencedId}', but it is '${referenced.type}', not '${input.expectedType}'.`,
|
|
938
|
+
suggestion: input.suggestion
|
|
939
|
+
});
|
|
940
|
+
}
|
|
941
|
+
}
|
|
942
|
+
function validateManifestSpriteAtlasFrameReference(asset, assetsById, diagnostics) {
|
|
943
|
+
if (!asset.atlas && !asset.frame) {
|
|
944
|
+
return;
|
|
945
|
+
}
|
|
946
|
+
if (!asset.atlas || !asset.frame) {
|
|
947
|
+
diagnostics.push({
|
|
948
|
+
severity: "error",
|
|
949
|
+
code: "PIXI_ASSET_ATLAS_FRAME_REFERENCE_INCOMPLETE",
|
|
950
|
+
path: `$.assets[${JSON.stringify(asset.id)}].${asset.atlas ? "frame" : "atlas"}`,
|
|
951
|
+
message: `Sprite asset '${asset.id}' must set both atlas and frame when referencing atlas metadata.`,
|
|
952
|
+
suggestion: "Set atlas to an atlas asset id and frame to a frame id inside that atlas."
|
|
953
|
+
});
|
|
954
|
+
return;
|
|
955
|
+
}
|
|
956
|
+
const atlas = assetsById.get(asset.atlas);
|
|
957
|
+
if (!atlas) {
|
|
958
|
+
diagnostics.push({
|
|
959
|
+
severity: "error",
|
|
960
|
+
code: "PIXI_ASSET_ATLAS_REFERENCE_UNRESOLVED",
|
|
961
|
+
path: `$.assets[${JSON.stringify(asset.id)}].atlas`,
|
|
962
|
+
message: `Sprite asset '${asset.id}' references missing atlas asset '${asset.atlas}'.`,
|
|
963
|
+
suggestion: "Add the referenced atlas asset or update SpriteAsset.atlas."
|
|
964
|
+
});
|
|
965
|
+
return;
|
|
966
|
+
}
|
|
967
|
+
if (atlas.type !== "atlas") {
|
|
968
|
+
diagnostics.push({
|
|
969
|
+
severity: "error",
|
|
970
|
+
code: "PIXI_ASSET_ATLAS_REFERENCE_TYPE_MISMATCH",
|
|
971
|
+
path: `$.assets[${JSON.stringify(asset.id)}].atlas`,
|
|
972
|
+
message: `Sprite asset '${asset.id}' references '${asset.atlas}', but it is '${atlas.type}', not 'atlas'.`,
|
|
973
|
+
suggestion: "Point SpriteAsset.atlas to an atlas asset id."
|
|
974
|
+
});
|
|
975
|
+
return;
|
|
976
|
+
}
|
|
977
|
+
const frame = atlas.frames.find((candidate) => candidate.id === asset.frame);
|
|
978
|
+
if (!frame) {
|
|
979
|
+
diagnostics.push({
|
|
980
|
+
severity: "error",
|
|
981
|
+
code: "PIXI_ASSET_ATLAS_FRAME_REFERENCE_UNRESOLVED",
|
|
982
|
+
path: `$.assets[${JSON.stringify(asset.id)}].frame`,
|
|
983
|
+
message: `Sprite asset '${asset.id}' references missing frame '${asset.frame}' in atlas '${asset.atlas}'.`,
|
|
984
|
+
suggestion: "Use a frame id defined by the referenced atlas asset."
|
|
985
|
+
});
|
|
986
|
+
}
|
|
987
|
+
}
|
|
988
|
+
function resolveDisplayAssetReference(entityId, assetId, assetsById, diagnostics) {
|
|
989
|
+
if (!assetsById) {
|
|
990
|
+
return void 0;
|
|
991
|
+
}
|
|
992
|
+
const asset = assetsById.get(assetId);
|
|
993
|
+
if (!asset) {
|
|
994
|
+
diagnostics.push({
|
|
995
|
+
severity: "warning",
|
|
996
|
+
code: "PIXI_ANIMATION_ASSET_UNRESOLVED",
|
|
997
|
+
entityId,
|
|
998
|
+
path: `$.entities[${JSON.stringify(entityId)}].components.Animator.activeFrameId`,
|
|
999
|
+
message: `Animation sprite asset '${assetId}' cannot be resolved by the Pixi display list.`,
|
|
1000
|
+
suggestion: "Pass the project asset manifest assets to the renderer or update SpriteFrame.sprite to a known sprite asset id."
|
|
1001
|
+
});
|
|
1002
|
+
return void 0;
|
|
1003
|
+
}
|
|
1004
|
+
if (asset.type !== "sprite") {
|
|
1005
|
+
diagnostics.push({
|
|
1006
|
+
severity: "warning",
|
|
1007
|
+
code: "PIXI_ANIMATION_ASSET_TYPE_MISMATCH",
|
|
1008
|
+
entityId,
|
|
1009
|
+
path: `$.entities[${JSON.stringify(entityId)}].components.Animator.activeFrameId`,
|
|
1010
|
+
message: `Animation sprite asset '${assetId}' is '${asset.type}', not 'sprite'.`,
|
|
1011
|
+
suggestion: "Use a SpriteFrame.sprite id whose manifest asset type is 'sprite'."
|
|
1012
|
+
});
|
|
1013
|
+
return void 0;
|
|
1014
|
+
}
|
|
1015
|
+
const atlasFrame = resolveDisplayAtlasFrameReference(asset, assetsById);
|
|
1016
|
+
if ((asset.atlas || asset.frame) && !atlasFrame) {
|
|
1017
|
+
diagnostics.push({
|
|
1018
|
+
severity: "warning",
|
|
1019
|
+
code: "PIXI_ANIMATION_ATLAS_FRAME_UNRESOLVED",
|
|
1020
|
+
entityId,
|
|
1021
|
+
path: `$.entities[${JSON.stringify(entityId)}].components.Animator.activeFrameId`,
|
|
1022
|
+
message: `Animation sprite asset '${assetId}' has atlas/frame metadata that cannot be resolved by the Pixi display list.`,
|
|
1023
|
+
suggestion: "Run project validation and ensure SpriteAsset.atlas points to an atlas asset and SpriteAsset.frame points to an atlas frame id."
|
|
1024
|
+
});
|
|
1025
|
+
}
|
|
1026
|
+
return {
|
|
1027
|
+
id: asset.id,
|
|
1028
|
+
type: "sprite",
|
|
1029
|
+
path: asset.path,
|
|
1030
|
+
...typeof asset.texture === "string" ? { texture: asset.texture } : {},
|
|
1031
|
+
...asset.rect ? { rect: asset.rect } : {},
|
|
1032
|
+
...typeof asset.atlas === "string" ? { atlas: asset.atlas } : {},
|
|
1033
|
+
...typeof asset.frame === "string" ? { frame: asset.frame } : {},
|
|
1034
|
+
...atlasFrame ? { atlasFrame } : {}
|
|
1035
|
+
};
|
|
1036
|
+
}
|
|
1037
|
+
function resolveDisplayAtlasFrameReference(asset, assetsById) {
|
|
1038
|
+
if (!asset.atlas || !asset.frame) {
|
|
1039
|
+
return void 0;
|
|
1040
|
+
}
|
|
1041
|
+
const atlas = assetsById.get(asset.atlas);
|
|
1042
|
+
if (atlas?.type !== "atlas") {
|
|
1043
|
+
return void 0;
|
|
1044
|
+
}
|
|
1045
|
+
const frame = atlas.frames.find((candidate) => candidate.id === asset.frame);
|
|
1046
|
+
if (!frame) {
|
|
1047
|
+
return void 0;
|
|
1048
|
+
}
|
|
1049
|
+
return {
|
|
1050
|
+
atlas: atlas.id,
|
|
1051
|
+
frame: frame.id,
|
|
1052
|
+
rect: frame.rect,
|
|
1053
|
+
...typeof atlas.texture === "string" ? { texture: atlas.texture } : {}
|
|
1054
|
+
};
|
|
1055
|
+
}
|
|
1056
|
+
function isRenderMetadataOnly(components) {
|
|
1057
|
+
if (!components) {
|
|
1058
|
+
return false;
|
|
1059
|
+
}
|
|
1060
|
+
const componentNames = Object.keys(components);
|
|
1061
|
+
return componentNames.length > 0 && componentNames.every((name) => name === "AnimationClip");
|
|
1062
|
+
}
|
|
1063
|
+
function readTransform(value) {
|
|
1064
|
+
if (!isRecord(value)) {
|
|
1065
|
+
return void 0;
|
|
1066
|
+
}
|
|
1067
|
+
const position = readNumberPair(value.position);
|
|
1068
|
+
const scale = readNumberPair(value.scale);
|
|
1069
|
+
const rotation = typeof value.rotation === "number" && Number.isFinite(value.rotation) ? value.rotation : void 0;
|
|
1070
|
+
if (!position || !scale || rotation === void 0) {
|
|
1071
|
+
return void 0;
|
|
1072
|
+
}
|
|
1073
|
+
return {
|
|
1074
|
+
position,
|
|
1075
|
+
rotation,
|
|
1076
|
+
scale
|
|
1077
|
+
};
|
|
1078
|
+
}
|
|
1079
|
+
function readCollider(value) {
|
|
1080
|
+
if (!isRecord(value) || value.shape !== "box") {
|
|
1081
|
+
return void 0;
|
|
1082
|
+
}
|
|
1083
|
+
const size = readNumberPair(value.size);
|
|
1084
|
+
if (!size || size[0] <= 0 || size[1] <= 0) {
|
|
1085
|
+
return void 0;
|
|
1086
|
+
}
|
|
1087
|
+
return {
|
|
1088
|
+
size
|
|
1089
|
+
};
|
|
1090
|
+
}
|
|
1091
|
+
function readNumberPair(value) {
|
|
1092
|
+
if (!Array.isArray(value) || value.length !== 2 || typeof value[0] !== "number" || typeof value[1] !== "number" || !Number.isFinite(value[0]) || !Number.isFinite(value[1])) {
|
|
1093
|
+
return void 0;
|
|
1094
|
+
}
|
|
1095
|
+
return [value[0], value[1]];
|
|
1096
|
+
}
|
|
1097
|
+
function readNumberTuple(value, length) {
|
|
1098
|
+
if (!Array.isArray(value) || value.length !== length) {
|
|
1099
|
+
return void 0;
|
|
1100
|
+
}
|
|
1101
|
+
if (!value.every((item) => typeof item === "number" && Number.isFinite(item))) {
|
|
1102
|
+
return void 0;
|
|
1103
|
+
}
|
|
1104
|
+
return [value[0], value[1], value[2], value[3]];
|
|
1105
|
+
}
|
|
1106
|
+
function readString(value) {
|
|
1107
|
+
return typeof value === "string" && value.length > 0 ? value : void 0;
|
|
1108
|
+
}
|
|
1109
|
+
function readFiniteNumber(value) {
|
|
1110
|
+
return typeof value === "number" && Number.isFinite(value) ? value : void 0;
|
|
1111
|
+
}
|
|
1112
|
+
function readNonNegativeInteger(value) {
|
|
1113
|
+
return typeof value === "number" && Number.isInteger(value) && value >= 0 ? value : void 0;
|
|
1114
|
+
}
|
|
1115
|
+
function isRecord(value) {
|
|
1116
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
1117
|
+
}
|
|
1118
|
+
function roundDeterministic(value) {
|
|
1119
|
+
return Number(value.toFixed(12));
|
|
1120
|
+
}
|
|
1121
|
+
export {
|
|
1122
|
+
createDisplayList,
|
|
1123
|
+
createPixiAssetLoaderFromManifest,
|
|
1124
|
+
createPixiAtlasTextureResolverFromManifest,
|
|
1125
|
+
createPixiRenderer,
|
|
1126
|
+
createPixiTextureResolverFromManifest,
|
|
1127
|
+
renderWorld,
|
|
1128
|
+
renderWorldSnapshot
|
|
1129
|
+
};
|
|
1130
|
+
//# sourceMappingURL=index.js.map
|