pelican-engine 0.0.0-stage → 0.3.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/AGENTS.md +147 -0
- package/LICENSE +22 -0
- package/README.md +129 -2
- package/distribution.json +197 -0
- package/docs/AI-WORKFLOW.md +517 -0
- package/docs/ARCHITECTURE.md +268 -0
- package/docs/CAPABILITIES.md +264 -0
- package/docs/DECISIONS.md +107 -0
- package/docs/M3-USAGE.md +192 -0
- package/docs/PELICAN-MIGRATION.md +38 -0
- package/docs/RENDER-BACKENDS.md +50 -0
- package/docs/ROADMAP.md +239 -0
- package/docs/THIRD-PARTY-NOTICES.md +151 -0
- package/docs/api/REFERENCE.md +1435 -0
- package/docs/api/index.json +1139 -0
- package/docs/npm/DISTRIBUTION.md +130 -0
- package/docs/npm/GAME-AUTHOR.md +147 -0
- package/engine.manifest.json +15 -0
- package/lib/assets/audio.js +67 -0
- package/lib/assets/errors.js +36 -0
- package/lib/assets/glb.js +277 -0
- package/lib/assets/index.js +53 -0
- package/lib/assets/jpeg.js +199 -0
- package/lib/assets/model.js +558 -0
- package/lib/assets/optimize.js +95 -0
- package/lib/assets/texture.js +187 -0
- package/lib/compiler/distribution.js +17 -0
- package/lib/compiler/extract.js +425 -0
- package/lib/compiler/graph.js +366 -0
- package/lib/compiler/index.js +574 -0
- package/lib/compiler/normalize.js +1194 -0
- package/lib/compiler/source-map.js +25 -0
- package/lib/compiler/types.js +2 -0
- package/lib/compiler/version.js +93 -0
- package/lib/experiments/simulation/index.js +2 -0
- package/lib/experiments/simulation/math.js +41 -0
- package/lib/experiments/simulation/probe.js +215 -0
- package/lib/experiments/simulation/scenes.js +192 -0
- package/lib/experiments/simulation/solver.js +346 -0
- package/lib/experiments/simulation/types.js +1 -0
- package/lib/experiments/world/adapters.js +30 -0
- package/lib/experiments/world/geometry.js +180 -0
- package/lib/experiments/world/index.js +2 -0
- package/lib/experiments/world/probe.js +288 -0
- package/lib/experiments/world/types.js +1 -0
- package/lib/experiments/world/world.js +599 -0
- package/lib/navigation/index.js +157 -0
- package/lib/physics/index.js +909 -0
- package/lib/protocol/index.js +92 -0
- package/lib/renderer/acceleration.js +609 -0
- package/lib/renderer/browser.js +2015 -0
- package/lib/renderer/bsdf.js +260 -0
- package/lib/renderer/checkpoint.js +88 -0
- package/lib/renderer/compute-executor.js +1034 -0
- package/lib/renderer/compute-texture-assets.js +49 -0
- package/lib/renderer/compute-view-contract.js +1 -0
- package/lib/renderer/denoiser.js +361 -0
- package/lib/renderer/dielectric.js +360 -0
- package/lib/renderer/embedded-image-pixels.js +368 -0
- package/lib/renderer/emissive-lights.js +67 -0
- package/lib/renderer/environment-importance.js +55 -0
- package/lib/renderer/environment.js +346 -0
- package/lib/renderer/gameplay.js +464 -0
- package/lib/renderer/geometry.js +103 -0
- package/lib/renderer/graph-expressions.js +485 -0
- package/lib/renderer/graph-tsl.js +141 -0
- package/lib/renderer/graph.js +214 -0
- package/lib/renderer/managed-texture-sampling.js +29 -0
- package/lib/renderer/material-graphs.js +297 -0
- package/lib/renderer/medium.js +348 -0
- package/lib/renderer/models.js +1053 -0
- package/lib/renderer/motion-shader.js +40 -0
- package/lib/renderer/path-shader.js +196 -0
- package/lib/renderer/pathtrace.js +324 -0
- package/lib/renderer/postprocess-contract.js +1 -0
- package/lib/renderer/postprocess.js +494 -0
- package/lib/renderer/render-buffers.js +132 -0
- package/lib/renderer/render-textures.js +155 -0
- package/lib/renderer/startup.js +50 -0
- package/lib/renderer/surface-capture.js +199 -0
- package/lib/renderer/surface-gpu.js +270 -0
- package/lib/renderer/surface-motion.js +84 -0
- package/lib/renderer/surface-ray-shader.js +91 -0
- package/lib/renderer/surface-shader.js +88 -0
- package/lib/renderer/surface-tracer.js +1720 -0
- package/lib/renderer/surface.js +1425 -0
- package/lib/renderer/temporal-filter.js +512 -0
- package/lib/renderer/texture-pixels.js +207 -0
- package/lib/renderer/traversal-nodes.js +54 -0
- package/lib/renderer/ui.js +446 -0
- package/lib/renderer/vertex-bounds.js +127 -0
- package/lib/renderer/vertex-contract.js +1 -0
- package/lib/renderer/vertex-graphs.js +197 -0
- package/lib/renderer/volume-shader.js +50 -0
- package/lib/runtime/compute-contract.js +1 -0
- package/lib/runtime/compute-service.js +674 -0
- package/lib/runtime/contracts.js +1 -0
- package/lib/runtime/error.js +37 -0
- package/lib/runtime/events.js +189 -0
- package/lib/runtime/game-services.js +772 -0
- package/lib/runtime/host.js +839 -0
- package/lib/runtime/material-parameters.js +145 -0
- package/lib/runtime/plugins.js +1 -0
- package/lib/runtime/postprocess-contract.js +1 -0
- package/lib/runtime/postprocess.js +189 -0
- package/lib/runtime/prefabs.js +13 -0
- package/lib/runtime/random.js +88 -0
- package/lib/runtime/render-buffer-contract.js +1 -0
- package/lib/runtime/render-buffers.js +243 -0
- package/lib/runtime/render-capabilities.js +17 -0
- package/lib/runtime/render-settings.js +249 -0
- package/lib/runtime/render-texture-contract.js +1 -0
- package/lib/runtime/render-textures.js +271 -0
- package/lib/runtime/resources.js +648 -0
- package/lib/runtime/scheduler.js +765 -0
- package/lib/runtime/storage.js +169 -0
- package/lib/runtime/world.js +594 -0
- package/lib/schema/index.js +24 -0
- package/lib/sdk/graph-values.js +16 -0
- package/lib/sdk/index.d.ts +800 -0
- package/lib/sdk/index.js +82 -0
- package/lib/tools/api-index.js +113 -0
- package/lib/tools/browser-session.js +89 -0
- package/lib/tools/cli.js +513 -0
- package/lib/tools/compress-dist.js +59 -0
- package/lib/tools/control-client.js +153 -0
- package/lib/tools/control-server.js +633 -0
- package/lib/tools/diagnostic-code.js +3 -0
- package/lib/tools/doctor.js +496 -0
- package/lib/tools/npm/browser-cli.js +6 -0
- package/lib/tools/npm/entry.js +40 -0
- package/lib/tools/npm/layout.js +195 -0
- package/lib/tools/setup.js +153 -0
- package/licenses/@dimforge/rapier2d-compat/LICENSE +201 -0
- package/licenses/@dimforge/rapier3d-compat/LICENSE +201 -0
- package/licenses/@gltf-transform/core/LICENSE.md +21 -0
- package/licenses/@gltf-transform/extensions/LICENSE.md +21 -0
- package/licenses/@gltf-transform/functions/LICENSE.md +21 -0
- package/licenses/@img/colour/LICENSE.md +82 -0
- package/licenses/@img/sharp-win32-x64/LICENSE +191 -0
- package/licenses/@oxc-project/types/LICENSE +22 -0
- package/licenses/@rolldown/pluginutils/LICENSE +21 -0
- package/licenses/@tweenjs/tween.js/LICENSE +23 -0
- package/licenses/@types/ndarray/LICENSE +21 -0
- package/licenses/@types/node/LICENSE +21 -0
- package/licenses/@types/stats.js/LICENSE +21 -0
- package/licenses/@types/three/@dimforge/rapier3d-compat/LICENSE +201 -0
- package/licenses/@types/three/LICENSE +21 -0
- package/licenses/@types/webxr/LICENSE +21 -0
- package/licenses/@webgpu/types/LICENSE +26 -0
- package/licenses/THIRD-PARTY-NOTICES.md +139 -0
- package/licenses/cwise-compiler/LICENSE +22 -0
- package/licenses/detect-libc/LICENSE +201 -0
- package/licenses/esbuild/LICENSE.md +21 -0
- package/licenses/fast-png/LICENSE +21 -0
- package/licenses/fdir/LICENSE +7 -0
- package/licenses/fflate/LICENSE +21 -0
- package/licenses/iobuffer/LICENSE +22 -0
- package/licenses/iota-array/LICENSE +22 -0
- package/licenses/is-buffer/LICENSE +21 -0
- package/licenses/ktx-parse/LICENSE +21 -0
- package/licenses/lightningcss/LICENSE +373 -0
- package/licenses/lightningcss-win32-x64-msvc/LICENSE +373 -0
- package/licenses/meshoptimizer/LICENSE.md +21 -0
- package/licenses/nanoid/LICENSE +20 -0
- package/licenses/ndarray/LICENSE +22 -0
- package/licenses/ndarray-lanczos/LICENSE +21 -0
- package/licenses/ndarray-ops/LICENSE +22 -0
- package/licenses/ndarray-pixels/LICENSE +21 -0
- package/licenses/ngraph.path/LICENSE +21 -0
- package/licenses/picocolors/LICENSE +15 -0
- package/licenses/picomatch/LICENSE +21 -0
- package/licenses/playwright/LICENSE +202 -0
- package/licenses/playwright/NOTICE +5 -0
- package/licenses/playwright-core/LICENSE +202 -0
- package/licenses/playwright-core/NOTICE +5 -0
- package/licenses/postcss/LICENSE +20 -0
- package/licenses/prettier/LICENSE +7 -0
- package/licenses/property-graph/LICENSE +21 -0
- package/licenses/rolldown/LICENSE +25 -0
- package/licenses/semver/LICENSE +15 -0
- package/licenses/sharp/LICENSE +191 -0
- package/licenses/source-map-js/LICENSE +28 -0
- package/licenses/three/LICENSE +21 -0
- package/licenses/tinyglobby/LICENSE +21 -0
- package/licenses/tsx/LICENSE +21 -0
- package/licenses/typescript/LICENSE.txt +55 -0
- package/licenses/undici-types/LICENSE +21 -0
- package/licenses/uniq/LICENSE +22 -0
- package/licenses/vite/LICENSE.md +2084 -0
- package/package.json +49 -4
- package/web/index.html +34 -0
|
@@ -0,0 +1,187 @@
|
|
|
1
|
+
import path from "node:path";
|
|
2
|
+
import { inflateSync } from "node:zlib";
|
|
3
|
+
import { inspectJPEG } from "./jpeg.js";
|
|
4
|
+
const signature = Buffer.from([137, 80, 78, 71, 13, 10, 26, 10]);
|
|
5
|
+
const crcTable = Array.from({ length: 256 }, (_, n) => {
|
|
6
|
+
for (let i = 0; i < 8; i++)
|
|
7
|
+
n = n & 1 ? 0xedb88320 ^ (n >>> 1) : n >>> 1;
|
|
8
|
+
return n >>> 0;
|
|
9
|
+
});
|
|
10
|
+
export function crc32(data) {
|
|
11
|
+
let n = 0xffffffff;
|
|
12
|
+
for (const b of data)
|
|
13
|
+
n = crcTable[(n ^ b) & 255] ^ (n >>> 8);
|
|
14
|
+
return (n ^ 0xffffffff) >>> 0;
|
|
15
|
+
}
|
|
16
|
+
// A bounded, fully checked decode of the lossless pixel stream. No browser or native service.
|
|
17
|
+
function inspectPNG(data, ctx) {
|
|
18
|
+
let offset = 8, width = 0, height = 0, bitDepth = 0, colorType = 0, interlace = 0;
|
|
19
|
+
let paletteEntries = 0, ended = false, idatClosed = false;
|
|
20
|
+
const compressed = [];
|
|
21
|
+
const seen = new Set();
|
|
22
|
+
while (offset < data.length) {
|
|
23
|
+
ctx.check(offset + 12 <= data.length, "PNG/chunk", "Truncated chunk header.");
|
|
24
|
+
const size = data.readUInt32BE(offset);
|
|
25
|
+
const type = data.toString("latin1", offset + 4, offset + 8);
|
|
26
|
+
const end = offset + 12 + size;
|
|
27
|
+
ctx.check(end <= data.length &&
|
|
28
|
+
/^[A-Za-z]{4}$/.test(type) &&
|
|
29
|
+
type[2] === type[2].toUpperCase(), `PNG/${type}`, "Invalid chunk type or chunk length exceeds file.");
|
|
30
|
+
ctx.check(data.readUInt32BE(end - 4) === crc32(data.subarray(offset + 4, end - 4)), `PNG/${type}`, "Chunk CRC mismatch.");
|
|
31
|
+
const chunk = data.subarray(offset + 8, end - 4);
|
|
32
|
+
ctx.check(seen.has("IHDR") || type === "IHDR", "PNG/IHDR", "IHDR must be first.");
|
|
33
|
+
if (type === "IHDR") {
|
|
34
|
+
ctx.check(!seen.has(type) && size === 13, "PNG/IHDR", "Invalid or duplicate IHDR.");
|
|
35
|
+
width = ctx.integer(chunk.readUInt32BE(0), "PNG/width", 1, 0x7fffffff);
|
|
36
|
+
height = ctx.integer(chunk.readUInt32BE(4), "PNG/height", 1, 0x7fffffff);
|
|
37
|
+
bitDepth = chunk[8];
|
|
38
|
+
colorType = chunk[9];
|
|
39
|
+
const depths = {
|
|
40
|
+
0: [1, 2, 4, 8, 16],
|
|
41
|
+
2: [8, 16],
|
|
42
|
+
3: [1, 2, 4, 8],
|
|
43
|
+
4: [8, 16],
|
|
44
|
+
6: [8, 16],
|
|
45
|
+
};
|
|
46
|
+
ctx.check(depths[colorType]?.includes(bitDepth), "PNG/IHDR", "Invalid bit depth/color type.");
|
|
47
|
+
ctx.check(chunk[10] === 0 && chunk[11] === 0 && chunk[12] <= 1, "PNG/IHDR", "Invalid compression, filter or interlace method.");
|
|
48
|
+
interlace = chunk[12];
|
|
49
|
+
}
|
|
50
|
+
else if (type === "PLTE") {
|
|
51
|
+
ctx.check(!seen.has(type) &&
|
|
52
|
+
!compressed.length &&
|
|
53
|
+
size > 0 &&
|
|
54
|
+
size % 3 === 0 &&
|
|
55
|
+
size <= 768 &&
|
|
56
|
+
![0, 4].includes(colorType), "PNG/PLTE", "Invalid, duplicate or misplaced palette.");
|
|
57
|
+
paletteEntries = size / 3;
|
|
58
|
+
ctx.check(colorType !== 3 || paletteEntries <= 2 ** bitDepth, "PNG/PLTE", "Palette exceeds bit depth.");
|
|
59
|
+
}
|
|
60
|
+
else if (type === "IDAT") {
|
|
61
|
+
ctx.check(!idatClosed && (colorType !== 3 || paletteEntries > 0), "PNG/IDAT", "Non-contiguous IDAT or missing palette.");
|
|
62
|
+
compressed.push(chunk);
|
|
63
|
+
}
|
|
64
|
+
else if (type === "IEND") {
|
|
65
|
+
ctx.check(size === 0 && compressed.length > 0 && end === data.length, "PNG/IEND", "Invalid end chunk, missing pixels or trailing bytes.");
|
|
66
|
+
ended = true;
|
|
67
|
+
}
|
|
68
|
+
else {
|
|
69
|
+
if (compressed.length)
|
|
70
|
+
idatClosed = true;
|
|
71
|
+
if (type === "tRNS") {
|
|
72
|
+
ctx.check(!seen.has(type) &&
|
|
73
|
+
!compressed.length &&
|
|
74
|
+
((colorType === 0 && size === 2) ||
|
|
75
|
+
(colorType === 2 && size === 6) ||
|
|
76
|
+
(colorType === 3 && size > 0 && size <= paletteEntries)), "PNG/tRNS", "Invalid transparency chunk.");
|
|
77
|
+
}
|
|
78
|
+
else if (type === "acTL" || type === "fcTL" || type === "fdAT") {
|
|
79
|
+
ctx.unsupported(`PNG/${type}`, "Animated PNG is not implemented.");
|
|
80
|
+
}
|
|
81
|
+
else if (type[0] === type[0].toUpperCase()) {
|
|
82
|
+
ctx.unsupported(`PNG/${type}`, "Unknown critical PNG chunk.");
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
seen.add(type);
|
|
86
|
+
offset = end;
|
|
87
|
+
}
|
|
88
|
+
ctx.check(ended, "PNG/IEND", "Missing IEND chunk.");
|
|
89
|
+
const channels = { 0: 1, 2: 3, 3: 1, 4: 2, 6: 4 }[colorType];
|
|
90
|
+
const patterns = interlace
|
|
91
|
+
? [
|
|
92
|
+
[0, 0, 8, 8],
|
|
93
|
+
[4, 0, 8, 8],
|
|
94
|
+
[0, 4, 4, 8],
|
|
95
|
+
[2, 0, 4, 4],
|
|
96
|
+
[0, 2, 2, 4],
|
|
97
|
+
[1, 0, 2, 2],
|
|
98
|
+
[0, 1, 1, 2],
|
|
99
|
+
]
|
|
100
|
+
: [[0, 0, 1, 1]];
|
|
101
|
+
const passes = patterns.map(([x, y, dx, dy]) => {
|
|
102
|
+
const passWidth = Math.max(0, Math.ceil((width - x) / dx));
|
|
103
|
+
const passHeight = Math.max(0, Math.ceil((height - y) / dy));
|
|
104
|
+
const rowBytes = Math.ceil((passWidth * channels * bitDepth) / 8);
|
|
105
|
+
return { width: passWidth, height: passHeight, rowBytes };
|
|
106
|
+
});
|
|
107
|
+
const decodedSize = passes.reduce((sum, pass) => sum + (pass.width && pass.height ? (pass.rowBytes + 1) * pass.height : 0), 0);
|
|
108
|
+
if (decodedSize > 256 * 1024 * 1024)
|
|
109
|
+
ctx.unsupported("PNG/pixels", "Decoded image exceeds the 256 MiB inspection limit.");
|
|
110
|
+
let pixels;
|
|
111
|
+
const encoded = Buffer.concat(compressed);
|
|
112
|
+
try {
|
|
113
|
+
const inflated = inflateSync(encoded, {
|
|
114
|
+
maxOutputLength: decodedSize + 1,
|
|
115
|
+
info: true,
|
|
116
|
+
});
|
|
117
|
+
ctx.check(inflated.engine.bytesWritten === encoded.length, "PNG/IDAT", "Trailing compressed stream data.");
|
|
118
|
+
pixels = inflated.buffer;
|
|
119
|
+
}
|
|
120
|
+
catch (error) {
|
|
121
|
+
if (error instanceof Error && error.name === "AssetInspectionError")
|
|
122
|
+
throw error;
|
|
123
|
+
ctx.invalid("PNG/IDAT", "Invalid or truncated zlib pixel stream.");
|
|
124
|
+
}
|
|
125
|
+
ctx.check(pixels.length === decodedSize, "PNG/IDAT", "Pixel stream length does not match dimensions.");
|
|
126
|
+
const bpp = Math.max(1, Math.ceil((channels * bitDepth) / 8));
|
|
127
|
+
let start = 0;
|
|
128
|
+
for (const [passIndex, pass] of passes.entries()) {
|
|
129
|
+
if (!pass.width || !pass.height)
|
|
130
|
+
continue;
|
|
131
|
+
const { rowBytes } = pass;
|
|
132
|
+
let previous = Buffer.alloc(rowBytes);
|
|
133
|
+
for (let y = 0; y < pass.height; y++) {
|
|
134
|
+
const resource = interlace
|
|
135
|
+
? `PNG/pass/${passIndex + 1}/row/${y}`
|
|
136
|
+
: `PNG/row/${y}`;
|
|
137
|
+
const filter = pixels[start];
|
|
138
|
+
ctx.check(filter <= 4, resource, "Invalid PNG row filter.");
|
|
139
|
+
const row = pixels.subarray(start + 1, start + 1 + rowBytes);
|
|
140
|
+
for (let x = 0; x < rowBytes; x++) {
|
|
141
|
+
const a = x >= bpp ? row[x - bpp] : 0, b = previous[x], c = x >= bpp ? previous[x - bpp] : 0;
|
|
142
|
+
let predictor = 0;
|
|
143
|
+
if (filter === 1)
|
|
144
|
+
predictor = a;
|
|
145
|
+
if (filter === 2)
|
|
146
|
+
predictor = b;
|
|
147
|
+
if (filter === 3)
|
|
148
|
+
predictor = Math.floor((a + b) / 2);
|
|
149
|
+
if (filter === 4) {
|
|
150
|
+
const p = a + b - c, pa = Math.abs(p - a), pb = Math.abs(p - b), pc = Math.abs(p - c);
|
|
151
|
+
predictor = pa <= pb && pa <= pc ? a : pb <= pc ? b : c;
|
|
152
|
+
}
|
|
153
|
+
row[x] = (row[x] + predictor) & 255;
|
|
154
|
+
}
|
|
155
|
+
if (colorType === 3)
|
|
156
|
+
for (let x = 0; x < pass.width; x++) {
|
|
157
|
+
const index = (row[Math.floor((x * bitDepth) / 8)] >>>
|
|
158
|
+
(8 - bitDepth - ((x * bitDepth) % 8))) &
|
|
159
|
+
((1 << bitDepth) - 1);
|
|
160
|
+
ctx.check(index < paletteEntries, resource, "Pixel references a missing palette entry.");
|
|
161
|
+
}
|
|
162
|
+
previous = row;
|
|
163
|
+
start += rowBytes + 1;
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
return {
|
|
167
|
+
format: "png",
|
|
168
|
+
width,
|
|
169
|
+
height,
|
|
170
|
+
bitDepth,
|
|
171
|
+
colorType,
|
|
172
|
+
channels,
|
|
173
|
+
hasAlpha: [4, 6].includes(colorType) || seen.has("tRNS"),
|
|
174
|
+
};
|
|
175
|
+
}
|
|
176
|
+
export function inspectTexture(data, ctx) {
|
|
177
|
+
if (data.subarray(0, 8).equals(signature))
|
|
178
|
+
return inspectPNG(data, ctx);
|
|
179
|
+
if (data[0] === 0xff && data[1] === 0xd8)
|
|
180
|
+
return inspectJPEG(data, ctx);
|
|
181
|
+
// Recognizable damaged signatures must not produce metadata.
|
|
182
|
+
if (data.toString("latin1", 1, 4) === "PNG")
|
|
183
|
+
ctx.invalid("PNG/signature", "Invalid PNG signature.");
|
|
184
|
+
if ([".png", ".jpg", ".jpeg"].includes(path.extname(ctx.file).toLowerCase()))
|
|
185
|
+
ctx.invalid("texture/signature", "Missing or damaged PNG/JPEG signature.");
|
|
186
|
+
return ctx.unsupported("texture/format", "Expected PNG or baseline JPEG image bytes.");
|
|
187
|
+
}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import { existsSync } from "node:fs";
|
|
2
|
+
import path from "node:path";
|
|
3
|
+
import { fileURLToPath } from "node:url";
|
|
4
|
+
export function compilerLayout(url, selectedRoot) {
|
|
5
|
+
const compiled = url.endsWith("/lib/compiler/index.js");
|
|
6
|
+
const root = selectedRoot
|
|
7
|
+
? path.resolve(selectedRoot)
|
|
8
|
+
: fileURLToPath(new URL(compiled ? "../../" : "../../../", url));
|
|
9
|
+
const installed = existsSync(path.join(root, "distribution.json"));
|
|
10
|
+
const sdk = path.join(root, installed ? "lib/sdk/index.js" : "packages/sdk/src/index.ts");
|
|
11
|
+
return {
|
|
12
|
+
root,
|
|
13
|
+
installed,
|
|
14
|
+
sdk,
|
|
15
|
+
types: installed ? sdk.replace(/\.js$/, ".d.ts") : sdk,
|
|
16
|
+
};
|
|
17
|
+
}
|
|
@@ -0,0 +1,425 @@
|
|
|
1
|
+
import ts from "typescript";
|
|
2
|
+
import path from "node:path";
|
|
3
|
+
const declarations = {
|
|
4
|
+
defineComponent: "component",
|
|
5
|
+
defineMaterial: "material",
|
|
6
|
+
defineCompute: "compute",
|
|
7
|
+
definePostProcess: "postprocess",
|
|
8
|
+
defineAsset: "asset",
|
|
9
|
+
definePrefab: "prefab",
|
|
10
|
+
defineScene: "scene",
|
|
11
|
+
defineSystem: "system",
|
|
12
|
+
defineGame: "game",
|
|
13
|
+
};
|
|
14
|
+
export class ExtractionFailure extends Error {
|
|
15
|
+
}
|
|
16
|
+
export class Extractor {
|
|
17
|
+
program;
|
|
18
|
+
projectRoot;
|
|
19
|
+
sdkPath;
|
|
20
|
+
diagnostics = [];
|
|
21
|
+
definitions = [];
|
|
22
|
+
origins = new WeakMap();
|
|
23
|
+
propertyOrigins = new WeakMap();
|
|
24
|
+
modules = new Map();
|
|
25
|
+
memo = new Map();
|
|
26
|
+
active = new Set();
|
|
27
|
+
constructor(program, projectRoot, sdkPath) {
|
|
28
|
+
this.program = program;
|
|
29
|
+
this.projectRoot = projectRoot;
|
|
30
|
+
this.sdkPath = sdkPath;
|
|
31
|
+
}
|
|
32
|
+
location(node) {
|
|
33
|
+
const source = node.getSourceFile();
|
|
34
|
+
const start = node.getStart(source);
|
|
35
|
+
const pos = source.getLineAndCharacterOfPosition(start);
|
|
36
|
+
return {
|
|
37
|
+
file: path
|
|
38
|
+
.relative(this.projectRoot, source.fileName)
|
|
39
|
+
.replaceAll("\\", "/"),
|
|
40
|
+
line: pos.line + 1,
|
|
41
|
+
column: pos.character + 1,
|
|
42
|
+
length: Math.max(1, node.end - start),
|
|
43
|
+
};
|
|
44
|
+
}
|
|
45
|
+
source(value, key) {
|
|
46
|
+
const node = (key !== undefined
|
|
47
|
+
? this.propertyOrigins.get(value)?.get(key)
|
|
48
|
+
: undefined) ?? this.origins.get(value);
|
|
49
|
+
return node ? this.location(node) : undefined;
|
|
50
|
+
}
|
|
51
|
+
fail(code, message, node, suggestion = "Use literal data and supported pelican-engine declaration calls.") {
|
|
52
|
+
this.diagnostics.push({
|
|
53
|
+
code,
|
|
54
|
+
severity: "error",
|
|
55
|
+
message,
|
|
56
|
+
suggestion,
|
|
57
|
+
source: this.location(node),
|
|
58
|
+
});
|
|
59
|
+
throw new ExtractionFailure(message);
|
|
60
|
+
}
|
|
61
|
+
key(file) {
|
|
62
|
+
return path.resolve(file).toLowerCase();
|
|
63
|
+
}
|
|
64
|
+
module(file) {
|
|
65
|
+
const key = this.key(file);
|
|
66
|
+
const cached = this.modules.get(key);
|
|
67
|
+
if (cached)
|
|
68
|
+
return cached;
|
|
69
|
+
const source = this.program.getSourceFile(file);
|
|
70
|
+
if (!source)
|
|
71
|
+
throw new Error("Compiler could not load source: " + file);
|
|
72
|
+
const mod = {
|
|
73
|
+
source,
|
|
74
|
+
bindings: new Map(),
|
|
75
|
+
imports: new Map(),
|
|
76
|
+
exports: new Map(),
|
|
77
|
+
};
|
|
78
|
+
this.modules.set(key, mod);
|
|
79
|
+
const inspectImports = (node) => {
|
|
80
|
+
const argument = ts.isImportTypeNode(node) && ts.isLiteralTypeNode(node.argument)
|
|
81
|
+
? node.argument.literal
|
|
82
|
+
: ts.isCallExpression(node) &&
|
|
83
|
+
node.expression.kind === ts.SyntaxKind.ImportKeyword
|
|
84
|
+
? node.arguments[0]
|
|
85
|
+
: ts.isExportDeclaration(node)
|
|
86
|
+
? node.moduleSpecifier
|
|
87
|
+
: undefined;
|
|
88
|
+
if (argument) {
|
|
89
|
+
if (!ts.isStringLiteral(argument))
|
|
90
|
+
this.fail("TC_PUBLIC_API", "Module references must be static strings.", argument);
|
|
91
|
+
const specifier = argument.text;
|
|
92
|
+
if (specifier !== "pelican-engine") {
|
|
93
|
+
if (!specifier.startsWith("."))
|
|
94
|
+
this.fail("TC_PUBLIC_API", "Only the public SDK and project-local modules may be referenced.", argument);
|
|
95
|
+
const resolved = ts.resolveModuleName(specifier, file, this.program.getCompilerOptions(), ts.sys).resolvedModule?.resolvedFileName;
|
|
96
|
+
if (resolved) {
|
|
97
|
+
const relative = path.relative(this.projectRoot, resolved);
|
|
98
|
+
if (relative.startsWith("..") ||
|
|
99
|
+
path.isAbsolute(relative) ||
|
|
100
|
+
relative.split(path.sep).includes("node_modules"))
|
|
101
|
+
this.fail("TC_PUBLIC_API", "Module reference leaves the game project.", argument);
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
ts.forEachChild(node, inspectImports);
|
|
106
|
+
};
|
|
107
|
+
inspectImports(source);
|
|
108
|
+
const addExport = (name, node) => {
|
|
109
|
+
if (ts.canHaveModifiers(node) &&
|
|
110
|
+
ts
|
|
111
|
+
.getModifiers(node)
|
|
112
|
+
?.some((m) => m.kind === ts.SyntaxKind.ExportKeyword))
|
|
113
|
+
mod.exports.set(name, name);
|
|
114
|
+
};
|
|
115
|
+
for (const statement of source.statements) {
|
|
116
|
+
if (ts.isImportDeclaration(statement)) {
|
|
117
|
+
const specifier = statement.moduleSpecifier.text;
|
|
118
|
+
if (!statement.importClause)
|
|
119
|
+
this.fail("TC_STATIC_SIDE_EFFECT", "Side-effect imports are not allowed in declaration modules.", statement, "Use named imports of declarations or gameplay functions.");
|
|
120
|
+
const clause = statement.importClause;
|
|
121
|
+
let resolved;
|
|
122
|
+
if (specifier !== "pelican-engine") {
|
|
123
|
+
if (!specifier.startsWith("."))
|
|
124
|
+
this.fail("TC_PUBLIC_API", "Game code may import pelican-engine and project-local modules only: " +
|
|
125
|
+
specifier, statement, "Access engine capabilities through pelican-engine.");
|
|
126
|
+
resolved = ts.resolveModuleName(specifier, file, this.program.getCompilerOptions(), ts.sys).resolvedModule?.resolvedFileName;
|
|
127
|
+
if (!resolved)
|
|
128
|
+
this.fail("TC_MODULE_MISSING", "Cannot resolve local module: " + specifier, statement, "Check the relative import path and file name.");
|
|
129
|
+
const relative = path.relative(this.projectRoot, resolved);
|
|
130
|
+
if (relative.startsWith("..") ||
|
|
131
|
+
path.isAbsolute(relative) ||
|
|
132
|
+
relative.split(path.sep).includes("node_modules"))
|
|
133
|
+
this.fail("TC_PUBLIC_API", "Local imports must remain inside the game project.", statement, "Reference engine functionality through pelican-engine.");
|
|
134
|
+
}
|
|
135
|
+
// Type-only imports obey the same public API boundary as value imports.
|
|
136
|
+
if (clause.isTypeOnly)
|
|
137
|
+
continue;
|
|
138
|
+
if (clause.name)
|
|
139
|
+
mod.imports.set(clause.name.text, {
|
|
140
|
+
file: resolved,
|
|
141
|
+
name: "default",
|
|
142
|
+
});
|
|
143
|
+
if (clause.namedBindings) {
|
|
144
|
+
if (!ts.isNamedImports(clause.namedBindings))
|
|
145
|
+
this.fail("TC_STATIC_SYNTAX", "Namespace imports are not supported by the M0 declaration extractor.", clause.namedBindings, "Use named imports.");
|
|
146
|
+
for (const item of clause.namedBindings.elements)
|
|
147
|
+
if (!item.isTypeOnly)
|
|
148
|
+
mod.imports.set(item.name.text, {
|
|
149
|
+
file: resolved,
|
|
150
|
+
name: item.propertyName?.text ?? item.name.text,
|
|
151
|
+
});
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
else if (ts.isVariableStatement(statement)) {
|
|
155
|
+
if (!(statement.declarationList.flags & ts.NodeFlags.Const))
|
|
156
|
+
this.fail("TC_STATIC_SIDE_EFFECT", "Top-level declaration bindings must be const.", statement, "Keep mutable state in components or inside system functions.");
|
|
157
|
+
for (const decl of statement.declarationList.declarations) {
|
|
158
|
+
if (!ts.isIdentifier(decl.name) || !decl.initializer)
|
|
159
|
+
this.fail("TC_STATIC_SYNTAX", "Use initialized const identifiers for definitions.", decl);
|
|
160
|
+
mod.bindings.set(decl.name.text, decl.initializer);
|
|
161
|
+
addExport(decl.name.text, statement);
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
else if (ts.isFunctionDeclaration(statement)) {
|
|
165
|
+
if (statement.name) {
|
|
166
|
+
mod.bindings.set(statement.name.text, statement);
|
|
167
|
+
addExport(statement.name.text, statement);
|
|
168
|
+
}
|
|
169
|
+
if (statement.modifiers?.some((m) => m.kind === ts.SyntaxKind.DefaultKeyword))
|
|
170
|
+
mod.exports.set("default", statement.name?.text ?? statement);
|
|
171
|
+
}
|
|
172
|
+
else if (ts.isExportAssignment(statement)) {
|
|
173
|
+
if (statement.isExportEquals)
|
|
174
|
+
this.fail("TC_STATIC_SYNTAX", "CommonJS export assignments are not supported.", statement);
|
|
175
|
+
mod.exports.set("default", statement.expression);
|
|
176
|
+
}
|
|
177
|
+
else if (ts.isExportDeclaration(statement)) {
|
|
178
|
+
if (statement.isTypeOnly)
|
|
179
|
+
continue;
|
|
180
|
+
if (statement.moduleSpecifier)
|
|
181
|
+
this.fail("TC_STATIC_SYNTAX", "M0 does not statically extract re-exported declarations.", statement, "Import a local definition and export the imported identifier.");
|
|
182
|
+
if (statement.exportClause && ts.isNamedExports(statement.exportClause))
|
|
183
|
+
for (const e of statement.exportClause.elements)
|
|
184
|
+
mod.exports.set(e.name.text, e.propertyName?.text ?? e.name.text);
|
|
185
|
+
}
|
|
186
|
+
else if (ts.isInterfaceDeclaration(statement) ||
|
|
187
|
+
ts.isTypeAliasDeclaration(statement) ||
|
|
188
|
+
ts.isEmptyStatement(statement)) {
|
|
189
|
+
// Type declarations are erased; no executable source is evaluated.
|
|
190
|
+
}
|
|
191
|
+
else
|
|
192
|
+
this.fail("TC_STATIC_SIDE_EFFECT", "Unsupported executable top-level statement; no source module is executed during compilation.", statement, "Move gameplay behavior into a system run function.");
|
|
193
|
+
}
|
|
194
|
+
return mod;
|
|
195
|
+
}
|
|
196
|
+
binding(mod, name) {
|
|
197
|
+
const key = this.key(mod.source.fileName) + ":" + name;
|
|
198
|
+
if (this.memo.has(key))
|
|
199
|
+
return this.memo.get(key);
|
|
200
|
+
if (this.active.has(key))
|
|
201
|
+
this.fail("TC_DECLARATION_CYCLE", "Cyclic static declaration: " + name, mod.bindings.get(name) ?? mod.source, "Remove the circular const or prefab reference.");
|
|
202
|
+
this.active.add(key);
|
|
203
|
+
try {
|
|
204
|
+
const imported = mod.imports.get(name);
|
|
205
|
+
let value;
|
|
206
|
+
if (imported) {
|
|
207
|
+
if (!imported.file)
|
|
208
|
+
this.fail("TC_STATIC_REFERENCE", "SDK functions are only permitted as supported declaration calls.", mod.source);
|
|
209
|
+
const target = this.module(imported.file);
|
|
210
|
+
const exported = target.exports.get(imported.name);
|
|
211
|
+
if (exported === undefined)
|
|
212
|
+
this.fail("TC_STATIC_REFERENCE", "Local module does not export: " + imported.name, mod.source);
|
|
213
|
+
value =
|
|
214
|
+
typeof exported === "string"
|
|
215
|
+
? this.binding(target, exported)
|
|
216
|
+
: this.evaluate(exported, target);
|
|
217
|
+
}
|
|
218
|
+
else {
|
|
219
|
+
const expression = mod.bindings.get(name);
|
|
220
|
+
if (!expression)
|
|
221
|
+
this.fail("TC_STATIC_REFERENCE", "Unknown static identifier: " + name, mod.source);
|
|
222
|
+
value = this.evaluate(expression, mod);
|
|
223
|
+
}
|
|
224
|
+
this.memo.set(key, value);
|
|
225
|
+
return value;
|
|
226
|
+
}
|
|
227
|
+
finally {
|
|
228
|
+
this.active.delete(key);
|
|
229
|
+
}
|
|
230
|
+
}
|
|
231
|
+
evaluate(node, mod) {
|
|
232
|
+
if (ts.isParenthesizedExpression(node) ||
|
|
233
|
+
ts.isAsExpression(node) ||
|
|
234
|
+
ts.isSatisfiesExpression(node) ||
|
|
235
|
+
ts.isTypeAssertionExpression(node))
|
|
236
|
+
return this.evaluate(node.expression, mod);
|
|
237
|
+
if (ts.isStringLiteral(node) || ts.isNoSubstitutionTemplateLiteral(node))
|
|
238
|
+
return node.text;
|
|
239
|
+
if (ts.isNumericLiteral(node))
|
|
240
|
+
return Number(node.text);
|
|
241
|
+
if (node.kind === ts.SyntaxKind.TrueKeyword)
|
|
242
|
+
return true;
|
|
243
|
+
if (node.kind === ts.SyntaxKind.FalseKeyword)
|
|
244
|
+
return false;
|
|
245
|
+
if (node.kind === ts.SyntaxKind.NullKeyword)
|
|
246
|
+
return null;
|
|
247
|
+
if (ts.isIdentifier(node))
|
|
248
|
+
return this.binding(mod, node.text);
|
|
249
|
+
if (ts.isArrowFunction(node) ||
|
|
250
|
+
ts.isFunctionExpression(node) ||
|
|
251
|
+
ts.isFunctionDeclaration(node) ||
|
|
252
|
+
ts.isMethodDeclaration(node)) {
|
|
253
|
+
const result = { __runtime: true };
|
|
254
|
+
this.origins.set(result, node);
|
|
255
|
+
return result;
|
|
256
|
+
}
|
|
257
|
+
if (ts.isArrayLiteralExpression(node)) {
|
|
258
|
+
const result = [];
|
|
259
|
+
const origins = new Map();
|
|
260
|
+
for (const element of node.elements) {
|
|
261
|
+
if (ts.isSpreadElement(element)) {
|
|
262
|
+
const value = this.evaluate(element.expression, mod);
|
|
263
|
+
if (!Array.isArray(value))
|
|
264
|
+
this.fail("TC_STATIC_TYPE", "Array spread requires a static array.", element);
|
|
265
|
+
for (const item of value) {
|
|
266
|
+
origins.set(String(result.length), element);
|
|
267
|
+
result.push(item);
|
|
268
|
+
}
|
|
269
|
+
}
|
|
270
|
+
else {
|
|
271
|
+
origins.set(String(result.length), element);
|
|
272
|
+
result.push(this.evaluate(element, mod));
|
|
273
|
+
}
|
|
274
|
+
}
|
|
275
|
+
this.origins.set(result, node);
|
|
276
|
+
this.propertyOrigins.set(result, origins);
|
|
277
|
+
return result;
|
|
278
|
+
}
|
|
279
|
+
if (ts.isObjectLiteralExpression(node)) {
|
|
280
|
+
const result = Object.create(null);
|
|
281
|
+
const origins = new Map();
|
|
282
|
+
for (const prop of node.properties) {
|
|
283
|
+
if (ts.isSpreadAssignment(prop)) {
|
|
284
|
+
const value = this.evaluate(prop.expression, mod);
|
|
285
|
+
if (!value || Array.isArray(value) || typeof value !== "object")
|
|
286
|
+
this.fail("TC_STATIC_TYPE", "Object spread requires static object data.", prop);
|
|
287
|
+
Object.assign(result, value);
|
|
288
|
+
for (const k of Object.keys(value))
|
|
289
|
+
origins.set(k, this.propertyOrigins.get(value)?.get(k) ?? prop);
|
|
290
|
+
continue;
|
|
291
|
+
}
|
|
292
|
+
if (!prop.name || ts.isComputedPropertyName(prop.name))
|
|
293
|
+
this.fail("TC_STATIC_SYNTAX", "Computed property names and accessors are not supported.", prop);
|
|
294
|
+
const name = prop.name.getText().replace(/^['"]|['"]$/g, "");
|
|
295
|
+
if (name === "__proto__" ||
|
|
296
|
+
name === "constructor" ||
|
|
297
|
+
name === "prototype")
|
|
298
|
+
this.fail("TC_STATIC_SYNTAX", "Reserved declaration property: " + name, prop);
|
|
299
|
+
if (ts.isPropertyAssignment(prop)) {
|
|
300
|
+
result[name] = this.evaluate(prop.initializer, mod);
|
|
301
|
+
origins.set(name, prop.initializer);
|
|
302
|
+
}
|
|
303
|
+
else if (ts.isShorthandPropertyAssignment(prop)) {
|
|
304
|
+
result[name] = this.binding(mod, prop.name.text);
|
|
305
|
+
origins.set(name, prop);
|
|
306
|
+
}
|
|
307
|
+
else if (ts.isMethodDeclaration(prop) && name === "run") {
|
|
308
|
+
result[name] = this.evaluate(prop, mod);
|
|
309
|
+
origins.set(name, prop);
|
|
310
|
+
}
|
|
311
|
+
else
|
|
312
|
+
this.fail("TC_STATIC_SYNTAX", "Only data properties and system run functions are supported.", prop);
|
|
313
|
+
}
|
|
314
|
+
this.origins.set(result, node);
|
|
315
|
+
this.propertyOrigins.set(result, origins);
|
|
316
|
+
return result;
|
|
317
|
+
}
|
|
318
|
+
if (ts.isCallExpression(node)) {
|
|
319
|
+
const name = ts.isIdentifier(node.expression)
|
|
320
|
+
? mod.imports.get(node.expression.text)
|
|
321
|
+
: undefined;
|
|
322
|
+
const kind = name && !name.file ? declarations[name.name] : undefined;
|
|
323
|
+
if (!kind || node.arguments.length !== 1)
|
|
324
|
+
this.fail("TC_STATIC_SIDE_EFFECT", "Arbitrary function calls are not evaluated in definitions.", node, "Use a supported define* call from pelican-engine; put executable behavior inside systems.");
|
|
325
|
+
const input = this.evaluate(node.arguments[0], mod);
|
|
326
|
+
if (!input || Array.isArray(input) || typeof input !== "object")
|
|
327
|
+
this.fail("TC_STATIC_TYPE", "A declaration requires an object.", node);
|
|
328
|
+
if (kind === "system" && input.run && typeof input.run === "object") {
|
|
329
|
+
const run = this.origins.get(input.run);
|
|
330
|
+
if (!run ||
|
|
331
|
+
!(ts.isArrowFunction(run) ||
|
|
332
|
+
ts.isFunctionExpression(run) ||
|
|
333
|
+
ts.isFunctionDeclaration(run) ||
|
|
334
|
+
ts.isMethodDeclaration(run)))
|
|
335
|
+
this.fail("TC_SYSTEM_RUN", "System run must be an actual function declaration.", run ?? node);
|
|
336
|
+
if (run &&
|
|
337
|
+
ts.canHaveModifiers(run) &&
|
|
338
|
+
ts
|
|
339
|
+
.getModifiers(run)
|
|
340
|
+
?.some((m) => m.kind === ts.SyntaxKind.AsyncKeyword))
|
|
341
|
+
this.fail("TC_SYSTEM_ASYNC", "Game systems must be synchronous in every phase.", run, "Move asynchronous preparation outside the update loop; apply ready data synchronously.");
|
|
342
|
+
if (run && "asteriskToken" in run && run.asteriskToken)
|
|
343
|
+
this.fail("TC_SYSTEM_GENERATOR", "M0 update systems cannot be generator functions.", run);
|
|
344
|
+
const checker = this.program.getTypeChecker();
|
|
345
|
+
const signature = checker.getSignatureFromDeclaration(run);
|
|
346
|
+
if (signature) {
|
|
347
|
+
const result = checker.getReturnTypeOfSignature(signature);
|
|
348
|
+
const results = result.isUnion() ? result.types : [result];
|
|
349
|
+
if (results.some((type) => type.getProperty("then")))
|
|
350
|
+
this.fail("TC_SYSTEM_ASYNC", "System run cannot return a Promise/thenable.", run, "Prepare asynchronous data outside systems and apply it synchronously at a safe boundary.");
|
|
351
|
+
}
|
|
352
|
+
}
|
|
353
|
+
const result = { ...input, kind };
|
|
354
|
+
this.origins.set(result, node);
|
|
355
|
+
this.propertyOrigins.set(result, this.propertyOrigins.get(input) ?? new Map());
|
|
356
|
+
this.definitions.push(result);
|
|
357
|
+
return result;
|
|
358
|
+
}
|
|
359
|
+
if (ts.isPrefixUnaryExpression(node)) {
|
|
360
|
+
const value = this.evaluate(node.operand, mod);
|
|
361
|
+
if (typeof value === "number" &&
|
|
362
|
+
[ts.SyntaxKind.PlusToken, ts.SyntaxKind.MinusToken].includes(node.operator))
|
|
363
|
+
return node.operator === ts.SyntaxKind.MinusToken ? -value : value;
|
|
364
|
+
}
|
|
365
|
+
if (ts.isBinaryExpression(node)) {
|
|
366
|
+
const a = this.evaluate(node.left, mod);
|
|
367
|
+
const b = this.evaluate(node.right, mod);
|
|
368
|
+
let value;
|
|
369
|
+
if (typeof a === "number" && typeof b === "number") {
|
|
370
|
+
switch (node.operatorToken.kind) {
|
|
371
|
+
case ts.SyntaxKind.PlusToken:
|
|
372
|
+
value = a + b;
|
|
373
|
+
break;
|
|
374
|
+
case ts.SyntaxKind.MinusToken:
|
|
375
|
+
value = a - b;
|
|
376
|
+
break;
|
|
377
|
+
case ts.SyntaxKind.AsteriskToken:
|
|
378
|
+
value = a * b;
|
|
379
|
+
break;
|
|
380
|
+
case ts.SyntaxKind.SlashToken:
|
|
381
|
+
value = a / b;
|
|
382
|
+
break;
|
|
383
|
+
}
|
|
384
|
+
}
|
|
385
|
+
if (value !== undefined && Number.isFinite(value))
|
|
386
|
+
return value;
|
|
387
|
+
this.fail("TC_STATIC_TYPE", "Static arithmetic must produce a finite number.", node);
|
|
388
|
+
}
|
|
389
|
+
this.fail("TC_STATIC_SYNTAX", "Unsupported static expression: " + ts.SyntaxKind[node.kind], node);
|
|
390
|
+
}
|
|
391
|
+
extract(entry) {
|
|
392
|
+
try {
|
|
393
|
+
const mod = this.module(entry);
|
|
394
|
+
// Every project source is inspected, including imports unused by the bundler.
|
|
395
|
+
for (const sf of this.program.getSourceFiles()) {
|
|
396
|
+
if (sf.isDeclarationFile ||
|
|
397
|
+
path.resolve(sf.fileName) === path.resolve(this.sdkPath))
|
|
398
|
+
continue;
|
|
399
|
+
const rel = path.relative(this.projectRoot, sf.fileName);
|
|
400
|
+
if (!rel.startsWith("..") && !path.isAbsolute(rel))
|
|
401
|
+
this.module(sf.fileName);
|
|
402
|
+
}
|
|
403
|
+
for (const item of this.modules.values())
|
|
404
|
+
for (const name of item.bindings.keys())
|
|
405
|
+
this.binding(item, name);
|
|
406
|
+
const exported = mod.exports.get("default");
|
|
407
|
+
if (!exported)
|
|
408
|
+
this.fail("TC_GAME_ENTRY", "Entry must default-export defineGame(...).", mod.source);
|
|
409
|
+
const result = typeof exported === "string"
|
|
410
|
+
? this.binding(mod, exported)
|
|
411
|
+
: this.evaluate(exported, mod);
|
|
412
|
+
if (!result ||
|
|
413
|
+
Array.isArray(result) ||
|
|
414
|
+
typeof result !== "object" ||
|
|
415
|
+
result.kind !== "game")
|
|
416
|
+
this.fail("TC_GAME_ENTRY", "Default export must be a defineGame declaration.", mod.source);
|
|
417
|
+
return result;
|
|
418
|
+
}
|
|
419
|
+
catch (error) {
|
|
420
|
+
if (!(error instanceof ExtractionFailure))
|
|
421
|
+
throw error;
|
|
422
|
+
return undefined;
|
|
423
|
+
}
|
|
424
|
+
}
|
|
425
|
+
}
|