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,574 @@
|
|
|
1
|
+
import { mapSourcePath } from "./source-map.js";
|
|
2
|
+
import { compilerLayout } from "./distribution.js";
|
|
3
|
+
import fs from "node:fs/promises";
|
|
4
|
+
import path from "node:path";
|
|
5
|
+
import { fileURLToPath } from "node:url";
|
|
6
|
+
import { performance } from "node:perf_hooks";
|
|
7
|
+
import ts from "typescript";
|
|
8
|
+
import { build, version as viteVersion } from "vite";
|
|
9
|
+
import { SDK_VERSION } from "../sdk/index.js";
|
|
10
|
+
import { Extractor } from "./extract.js";
|
|
11
|
+
import { hash, normalize } from "./normalize.js";
|
|
12
|
+
import { readSDKVersion, resolveEnginePath, validateManifest, } from "./version.js";
|
|
13
|
+
export { IR_VERSION, SYSTEM_PHASES } from "./types.js";
|
|
14
|
+
export { readSDKVersion, resolveEnginePath } from "./version.js";
|
|
15
|
+
const slash = (p) => p.replaceAll("\\", "/");
|
|
16
|
+
async function readJSON(file) {
|
|
17
|
+
try {
|
|
18
|
+
return JSON.parse(await fs.readFile(file, "utf8"));
|
|
19
|
+
}
|
|
20
|
+
catch {
|
|
21
|
+
return undefined;
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
function usableCache(value) {
|
|
25
|
+
return (!!value &&
|
|
26
|
+
value.format === 1 &&
|
|
27
|
+
Array.isArray(value.inputs) &&
|
|
28
|
+
value.inputs.every((i) => typeof i.path === "string" &&
|
|
29
|
+
["project", "engine"].includes(i.root) &&
|
|
30
|
+
typeof i.hash === "string") &&
|
|
31
|
+
!!value.outputHashes &&
|
|
32
|
+
typeof value.outputHashes === "object" &&
|
|
33
|
+
!Array.isArray(value.outputHashes) &&
|
|
34
|
+
Object.keys(value.outputHashes).includes("game.js") &&
|
|
35
|
+
!!value.ir &&
|
|
36
|
+
value.ir.format === "pelican.scene" &&
|
|
37
|
+
typeof value.schemaHash === "string" &&
|
|
38
|
+
typeof value.defaultsHash === "string");
|
|
39
|
+
}
|
|
40
|
+
async function filesUnder(dir) {
|
|
41
|
+
let entries;
|
|
42
|
+
try {
|
|
43
|
+
entries = await fs.readdir(dir, { withFileTypes: true });
|
|
44
|
+
}
|
|
45
|
+
catch (error) {
|
|
46
|
+
if (error.code === "ENOENT")
|
|
47
|
+
return [];
|
|
48
|
+
throw error;
|
|
49
|
+
}
|
|
50
|
+
const results = await Promise.all(entries.map((e) => e.isDirectory()
|
|
51
|
+
? filesUnder(path.join(dir, e.name))
|
|
52
|
+
: [path.join(dir, e.name)]));
|
|
53
|
+
return results.flat();
|
|
54
|
+
}
|
|
55
|
+
function projectLocation(projectRoot, sf, start, length = 1) {
|
|
56
|
+
const pos = sf.getLineAndCharacterOfPosition(start);
|
|
57
|
+
return {
|
|
58
|
+
file: slash(path.relative(projectRoot, sf.fileName)),
|
|
59
|
+
line: pos.line + 1,
|
|
60
|
+
column: pos.character + 1,
|
|
61
|
+
length,
|
|
62
|
+
};
|
|
63
|
+
}
|
|
64
|
+
// Vite sets NODE_ENV globally. Serialize build environment transactions and restore
|
|
65
|
+
// the caller's exact value on both success and failure (including an absent key).
|
|
66
|
+
let viteBuildQueue = Promise.resolve();
|
|
67
|
+
async function productionBuild(config) {
|
|
68
|
+
const previousBuild = viteBuildQueue;
|
|
69
|
+
let release;
|
|
70
|
+
viteBuildQueue = new Promise((resolve) => {
|
|
71
|
+
release = resolve;
|
|
72
|
+
});
|
|
73
|
+
await previousBuild;
|
|
74
|
+
const previousNodeEnv = process.env.NODE_ENV;
|
|
75
|
+
try {
|
|
76
|
+
process.env.NODE_ENV = "production";
|
|
77
|
+
return await build(config);
|
|
78
|
+
}
|
|
79
|
+
finally {
|
|
80
|
+
if (previousNodeEnv === undefined)
|
|
81
|
+
delete process.env.NODE_ENV;
|
|
82
|
+
else
|
|
83
|
+
process.env.NODE_ENV = previousNodeEnv;
|
|
84
|
+
release();
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
async function bundle(entry, sdkPath, projectRoot) {
|
|
88
|
+
const virtual = slash(path.join(projectRoot, "__pelican_m0_game_entry__.js"));
|
|
89
|
+
const virtualId = "\0pelican:m0-game-entry";
|
|
90
|
+
const result = await productionBuild({
|
|
91
|
+
configFile: false,
|
|
92
|
+
envFile: false,
|
|
93
|
+
mode: "production",
|
|
94
|
+
root: projectRoot,
|
|
95
|
+
publicDir: false,
|
|
96
|
+
logLevel: "silent",
|
|
97
|
+
resolve: { alias: { "pelican-engine": slash(sdkPath) } },
|
|
98
|
+
plugins: [
|
|
99
|
+
{
|
|
100
|
+
name: "pelican-compiled-game",
|
|
101
|
+
enforce: "pre",
|
|
102
|
+
resolveId(id) {
|
|
103
|
+
if (id === "pelican-engine")
|
|
104
|
+
return slash(sdkPath);
|
|
105
|
+
if (slash(id) === virtual)
|
|
106
|
+
return virtualId;
|
|
107
|
+
},
|
|
108
|
+
load(id) {
|
|
109
|
+
if (id === virtualId)
|
|
110
|
+
return ("import game from " +
|
|
111
|
+
JSON.stringify(slash(entry)) +
|
|
112
|
+
"; import { instantiateGame } from " +
|
|
113
|
+
JSON.stringify(slash(sdkPath)) +
|
|
114
|
+
"; export function createGame(engine) { return instantiateGame(game, engine); }");
|
|
115
|
+
},
|
|
116
|
+
},
|
|
117
|
+
],
|
|
118
|
+
build: {
|
|
119
|
+
write: false,
|
|
120
|
+
emptyOutDir: false,
|
|
121
|
+
target: "es2022",
|
|
122
|
+
sourcemap: true,
|
|
123
|
+
minify: false,
|
|
124
|
+
lib: { entry: virtual, formats: ["es"], fileName: () => "game.js" },
|
|
125
|
+
rolldownOptions: { output: { codeSplitting: false } },
|
|
126
|
+
},
|
|
127
|
+
});
|
|
128
|
+
const outputs = (Array.isArray(result) ? result : [result]);
|
|
129
|
+
const items = outputs.flatMap((o) => o.output ?? []);
|
|
130
|
+
const chunk = items.find((i) => i.type === "chunk" && i.fileName === "game.js");
|
|
131
|
+
if (!chunk?.code)
|
|
132
|
+
throw new Error("Vite did not generate game.js.");
|
|
133
|
+
const mapAsset = items.find((i) => i.fileName === "game.js.map");
|
|
134
|
+
const raw = chunk.map?.toString() ??
|
|
135
|
+
(mapAsset?.source ? String(mapAsset.source) : undefined);
|
|
136
|
+
if (!raw)
|
|
137
|
+
throw new Error("Vite did not generate a source map.");
|
|
138
|
+
const map = JSON.parse(raw);
|
|
139
|
+
map.file = "game.js";
|
|
140
|
+
const sourceRoot = map.sourceRoot ?? "";
|
|
141
|
+
map.sourceRoot = "";
|
|
142
|
+
// Rollup/Vite map paths are relative to its virtual output at projectRoot/dist.
|
|
143
|
+
map.sources = map.sources.map((source) => mapSourcePath(source, projectRoot, sdkPath, path, sourceRoot));
|
|
144
|
+
return { code: chunk.code, map: JSON.stringify(map) };
|
|
145
|
+
}
|
|
146
|
+
/** Parse, typecheck and statically extract declarations; NEVER import a game source module. */
|
|
147
|
+
export async function compileProject(options) {
|
|
148
|
+
const start = performance.now();
|
|
149
|
+
const projectRoot = path.resolve(options.projectRoot);
|
|
150
|
+
const outDir = path.resolve(projectRoot, options.outDir);
|
|
151
|
+
const layout = compilerLayout(import.meta.url, options.engineRoot);
|
|
152
|
+
const engineRoot = layout.root;
|
|
153
|
+
const sdkPath = layout.sdk;
|
|
154
|
+
const diagnostics = [];
|
|
155
|
+
const report = {
|
|
156
|
+
format: "pelican.build-report",
|
|
157
|
+
version: 1,
|
|
158
|
+
success: false,
|
|
159
|
+
engineVersion: SDK_VERSION,
|
|
160
|
+
compilerVersion: ts.version,
|
|
161
|
+
project: { id: "", entry: "game.ts" },
|
|
162
|
+
diagnostics,
|
|
163
|
+
inputs: [],
|
|
164
|
+
outputs: [],
|
|
165
|
+
incremental: {
|
|
166
|
+
cacheHit: false,
|
|
167
|
+
changedInputs: [],
|
|
168
|
+
reused: [],
|
|
169
|
+
schemaHash: "",
|
|
170
|
+
defaultsHash: "",
|
|
171
|
+
},
|
|
172
|
+
hotReload: {
|
|
173
|
+
strategy: "cold-start",
|
|
174
|
+
preserveState: false,
|
|
175
|
+
defaults: "new-entities-only",
|
|
176
|
+
reason: "No previous successful build.",
|
|
177
|
+
},
|
|
178
|
+
modules: [],
|
|
179
|
+
limitations: [
|
|
180
|
+
"M0 static declaration subset; not arbitrary TypeScript compile-time execution.",
|
|
181
|
+
"Graph IR validation is implemented; GPU lowering and dispatch belong to the renderer.",
|
|
182
|
+
"Hot-reload policy is reported; live state migration is not implemented by this compiler.",
|
|
183
|
+
"Declaration imports are conservatively included; full module tree-shaking analysis is not an M0 claim.",
|
|
184
|
+
],
|
|
185
|
+
durationMs: 0,
|
|
186
|
+
};
|
|
187
|
+
const fail = async () => {
|
|
188
|
+
report.durationMs = performance.now() - start;
|
|
189
|
+
try {
|
|
190
|
+
await fs.mkdir(outDir, { recursive: true });
|
|
191
|
+
await fs.writeFile(path.join(outDir, "build-report.json"), JSON.stringify({ ...report, artifactState: "previous-success-not-current" }, null, 2));
|
|
192
|
+
}
|
|
193
|
+
catch {
|
|
194
|
+
diagnostics.push({
|
|
195
|
+
code: "TC_OUTPUT_WRITE",
|
|
196
|
+
severity: "error",
|
|
197
|
+
message: "Cannot write the build report to outDir.",
|
|
198
|
+
suggestion: "Choose an output directory that is writable and is not an existing file.",
|
|
199
|
+
});
|
|
200
|
+
}
|
|
201
|
+
return { success: false, diagnostics, report };
|
|
202
|
+
};
|
|
203
|
+
try {
|
|
204
|
+
const manifestPath = path.join(projectRoot, "pelican.project.json");
|
|
205
|
+
let rawManifest = {};
|
|
206
|
+
try {
|
|
207
|
+
rawManifest = JSON.parse(await fs.readFile(manifestPath, "utf8"));
|
|
208
|
+
}
|
|
209
|
+
catch (e) {
|
|
210
|
+
if (e.code !== "ENOENT") {
|
|
211
|
+
diagnostics.push({
|
|
212
|
+
code: "TC_PROJECT_CONFIG",
|
|
213
|
+
severity: "error",
|
|
214
|
+
message: "Cannot read pelican.project.json as JSON.",
|
|
215
|
+
suggestion: "Use a JSON object with an optional project-relative entry string.",
|
|
216
|
+
});
|
|
217
|
+
return await fail();
|
|
218
|
+
}
|
|
219
|
+
}
|
|
220
|
+
const validated = validateManifest(rawManifest, SDK_VERSION);
|
|
221
|
+
diagnostics.push(...validated.diagnostics);
|
|
222
|
+
if (!validated.manifest || diagnostics.some((d) => d.severity === "error"))
|
|
223
|
+
return await fail();
|
|
224
|
+
const manifest = validated.manifest;
|
|
225
|
+
const entry = path.resolve(projectRoot, manifest.entry ?? "game.ts");
|
|
226
|
+
report.project.entry = slash(path.relative(projectRoot, entry));
|
|
227
|
+
if (report.project.entry.startsWith("..") ||
|
|
228
|
+
path.isAbsolute(report.project.entry)) {
|
|
229
|
+
diagnostics.push({
|
|
230
|
+
code: "TC_PROJECT_ENTRY",
|
|
231
|
+
severity: "error",
|
|
232
|
+
message: "Entry must be inside projectRoot.",
|
|
233
|
+
suggestion: "Use a project-relative entry path.",
|
|
234
|
+
});
|
|
235
|
+
return await fail();
|
|
236
|
+
}
|
|
237
|
+
try {
|
|
238
|
+
await fs.access(entry);
|
|
239
|
+
await fs.access(sdkPath);
|
|
240
|
+
}
|
|
241
|
+
catch {
|
|
242
|
+
diagnostics.push({
|
|
243
|
+
code: "TC_PROJECT_MISSING",
|
|
244
|
+
severity: "error",
|
|
245
|
+
message: "Missing game entry or public SDK source.",
|
|
246
|
+
suggestion: "Supply game.ts (or a manifest entry) and a valid engineRoot.",
|
|
247
|
+
});
|
|
248
|
+
return await fail();
|
|
249
|
+
}
|
|
250
|
+
const selectedSDKVersion = readSDKVersion(await fs.readFile(layout.types, "utf8"));
|
|
251
|
+
if (selectedSDKVersion !== SDK_VERSION) {
|
|
252
|
+
diagnostics.push({
|
|
253
|
+
code: "TC_ENGINE_SDK_VERSION",
|
|
254
|
+
severity: "error",
|
|
255
|
+
message: "Selected engine SDK version " +
|
|
256
|
+
String(selectedSDKVersion) +
|
|
257
|
+
" differs from compiler SDK " +
|
|
258
|
+
SDK_VERSION +
|
|
259
|
+
".",
|
|
260
|
+
suggestion: "Run the compiler shipped with the selected engine; use matching engine and public SDK versions.",
|
|
261
|
+
});
|
|
262
|
+
return await fail();
|
|
263
|
+
}
|
|
264
|
+
if (manifest.engine?.path !== undefined) {
|
|
265
|
+
try {
|
|
266
|
+
const [locked, selected] = await Promise.all([
|
|
267
|
+
fs.realpath(resolveEnginePath(projectRoot, manifest.engine.path)),
|
|
268
|
+
fs.realpath(engineRoot),
|
|
269
|
+
]);
|
|
270
|
+
const equal = process.platform === "win32"
|
|
271
|
+
? locked.toLowerCase() === selected.toLowerCase()
|
|
272
|
+
: locked === selected;
|
|
273
|
+
if (!equal)
|
|
274
|
+
throw new Error("Engine path mismatch");
|
|
275
|
+
}
|
|
276
|
+
catch {
|
|
277
|
+
diagnostics.push({
|
|
278
|
+
code: "TC_ENGINE_PATH_MISMATCH",
|
|
279
|
+
severity: "error",
|
|
280
|
+
message: "engine.path does not resolve to the selected engineRoot.",
|
|
281
|
+
suggestion: "Use the correct project-relative or absolute engine directory, including its drive when on another Windows drive.",
|
|
282
|
+
});
|
|
283
|
+
return await fail();
|
|
284
|
+
}
|
|
285
|
+
}
|
|
286
|
+
const cachePath = path.join(outDir, ".pelican-cache.json");
|
|
287
|
+
const cacheCandidate = await readJSON(cachePath);
|
|
288
|
+
const previous = usableCache(cacheCandidate) ? cacheCandidate : undefined;
|
|
289
|
+
const toolFiles = (await filesUnder(fileURLToPath(new URL(".", import.meta.url)))).filter((p) => /\.(?:ts|js)$/.test(p));
|
|
290
|
+
const sdkFiles = (await filesUnder(path.dirname(sdkPath))).filter((p) => /\.(?:ts|js)$/.test(p));
|
|
291
|
+
const packageInputs = [];
|
|
292
|
+
let assetPackageAvailable = true;
|
|
293
|
+
for (const name of ["runtime", "schema", "assets"]) {
|
|
294
|
+
const directory = path.join(engineRoot, layout.installed ? "lib" : "packages", name);
|
|
295
|
+
const files = (await filesUnder(directory)).filter((p) => /\.(?:ts|js|json)$/.test(p) &&
|
|
296
|
+
!path
|
|
297
|
+
.relative(engineRoot, p)
|
|
298
|
+
.split(path.sep)
|
|
299
|
+
.includes("node_modules"));
|
|
300
|
+
packageInputs.push(...files);
|
|
301
|
+
if (name === "assets" && !files.length)
|
|
302
|
+
assetPackageAvailable = false;
|
|
303
|
+
if (!files.length)
|
|
304
|
+
diagnostics.push({
|
|
305
|
+
code: "TC_ENGINE_INPUT_MISSING",
|
|
306
|
+
severity: "warning",
|
|
307
|
+
message: "No hashable source files in packages/" +
|
|
308
|
+
name +
|
|
309
|
+
"; package may not yet be generated.",
|
|
310
|
+
suggestion: "Generate the package before its runtime/schema functionality is used; a later file addition invalidates this cache.",
|
|
311
|
+
});
|
|
312
|
+
}
|
|
313
|
+
const engineInputs = [
|
|
314
|
+
...new Set([...toolFiles, ...sdkFiles, ...packageInputs]),
|
|
315
|
+
];
|
|
316
|
+
if (layout.installed)
|
|
317
|
+
engineInputs.push(path.join(engineRoot, "distribution.json"), path.join(engineRoot, "package.json"));
|
|
318
|
+
const makeInput = async (file, root) => ({
|
|
319
|
+
root,
|
|
320
|
+
path: slash(path.relative(root === "project" ? projectRoot : engineRoot, file)),
|
|
321
|
+
hash: hash(await fs.readFile(file)),
|
|
322
|
+
});
|
|
323
|
+
const initialInputs = await Promise.all([
|
|
324
|
+
...engineInputs.map((p) => makeInput(p, "engine")),
|
|
325
|
+
makeInput(entry, "project"),
|
|
326
|
+
]);
|
|
327
|
+
try {
|
|
328
|
+
initialInputs.push(await makeInput(path.join(engineRoot, "packages/compiler/package.json"), "engine"));
|
|
329
|
+
}
|
|
330
|
+
catch {
|
|
331
|
+
// External engine copies may predate package-level metadata.
|
|
332
|
+
}
|
|
333
|
+
try {
|
|
334
|
+
await fs.access(manifestPath);
|
|
335
|
+
initialInputs.push(await makeInput(manifestPath, "project"));
|
|
336
|
+
}
|
|
337
|
+
catch {
|
|
338
|
+
/* optional manifest */
|
|
339
|
+
}
|
|
340
|
+
if (previous?.format === 1) {
|
|
341
|
+
const sameTools = initialInputs.every((i) => previous.inputs.some((p) => p.root === i.root && p.path === i.path && p.hash === i.hash));
|
|
342
|
+
const changed = [];
|
|
343
|
+
for (const input of previous.inputs) {
|
|
344
|
+
try {
|
|
345
|
+
const current = await makeInput(path.resolve(input.root === "project" ? projectRoot : engineRoot, input.path), input.root);
|
|
346
|
+
if (current.hash !== input.hash)
|
|
347
|
+
changed.push(input.path);
|
|
348
|
+
}
|
|
349
|
+
catch {
|
|
350
|
+
changed.push(input.path);
|
|
351
|
+
}
|
|
352
|
+
}
|
|
353
|
+
report.incremental.changedInputs = [
|
|
354
|
+
...new Set([
|
|
355
|
+
...changed,
|
|
356
|
+
...initialInputs
|
|
357
|
+
.filter((i) => !previous.inputs.some((p) => p.path === i.path && p.root === i.root && p.hash === i.hash))
|
|
358
|
+
.map((i) => i.path),
|
|
359
|
+
]),
|
|
360
|
+
];
|
|
361
|
+
let outputsValid = true;
|
|
362
|
+
for (const [file, fingerprint] of Object.entries(previous.outputHashes)) {
|
|
363
|
+
try {
|
|
364
|
+
if (hash(await fs.readFile(path.join(outDir, file))) !== fingerprint)
|
|
365
|
+
outputsValid = false;
|
|
366
|
+
}
|
|
367
|
+
catch {
|
|
368
|
+
outputsValid = false;
|
|
369
|
+
}
|
|
370
|
+
}
|
|
371
|
+
if (previous.compilerVersion !== ts.version ||
|
|
372
|
+
previous.bundlerVersion !== viteVersion)
|
|
373
|
+
report.incremental.changedInputs.push("<tool-version>");
|
|
374
|
+
if (sameTools &&
|
|
375
|
+
(assetPackageAvailable ||
|
|
376
|
+
!previous.ir.assets.some((asset) => asset.type === "model" || asset.type === "audio")) &&
|
|
377
|
+
previous.compilerVersion === ts.version &&
|
|
378
|
+
previous.bundlerVersion === viteVersion &&
|
|
379
|
+
!changed.length &&
|
|
380
|
+
outputsValid &&
|
|
381
|
+
previous.ir.engineVersion === SDK_VERSION &&
|
|
382
|
+
previous.ir.version === 1) {
|
|
383
|
+
report.success = true;
|
|
384
|
+
report.project.id = previous.ir.gameId;
|
|
385
|
+
report.inputs = previous.inputs;
|
|
386
|
+
report.outputs = [
|
|
387
|
+
...Object.keys(previous.outputHashes),
|
|
388
|
+
"build-report.json",
|
|
389
|
+
];
|
|
390
|
+
report.incremental = {
|
|
391
|
+
cacheHit: true,
|
|
392
|
+
changedInputs: [],
|
|
393
|
+
reused: [
|
|
394
|
+
"typecheck",
|
|
395
|
+
"static-extraction",
|
|
396
|
+
"graph-validation",
|
|
397
|
+
"game-bundle",
|
|
398
|
+
"assets",
|
|
399
|
+
],
|
|
400
|
+
schemaHash: previous.schemaHash,
|
|
401
|
+
defaultsHash: previous.defaultsHash,
|
|
402
|
+
};
|
|
403
|
+
report.hotReload = {
|
|
404
|
+
strategy: "reuse",
|
|
405
|
+
preserveState: true,
|
|
406
|
+
defaults: "new-entities-only",
|
|
407
|
+
reason: "All recorded source, SDK, compiler, asset and output hashes match.",
|
|
408
|
+
};
|
|
409
|
+
report.durationMs = performance.now() - start;
|
|
410
|
+
report.modules = modulesFor(previous.ir);
|
|
411
|
+
await fs.writeFile(path.join(outDir, "build-report.json"), JSON.stringify(report, null, 2));
|
|
412
|
+
return { success: true, diagnostics, report, ir: previous.ir };
|
|
413
|
+
}
|
|
414
|
+
if (!outputsValid)
|
|
415
|
+
report.incremental.changedInputs.push("<generated-output>");
|
|
416
|
+
}
|
|
417
|
+
else
|
|
418
|
+
report.incremental.changedInputs = initialInputs.map((i) => i.path);
|
|
419
|
+
const compilerOptions = {
|
|
420
|
+
target: ts.ScriptTarget.ES2023,
|
|
421
|
+
module: ts.ModuleKind.ESNext,
|
|
422
|
+
moduleResolution: ts.ModuleResolutionKind.Bundler,
|
|
423
|
+
strict: true,
|
|
424
|
+
noEmit: true,
|
|
425
|
+
skipLibCheck: true,
|
|
426
|
+
allowImportingTsExtensions: true,
|
|
427
|
+
types: [],
|
|
428
|
+
lib: ["lib.es2023.d.ts"],
|
|
429
|
+
paths: { "pelican-engine": [slash(layout.types)] },
|
|
430
|
+
};
|
|
431
|
+
const program = ts.createProgram([entry], compilerOptions);
|
|
432
|
+
program.getTypeChecker(); // Bind source parents before source-mapped static extraction.
|
|
433
|
+
const extractor = new Extractor(program, projectRoot, layout.types);
|
|
434
|
+
const game = extractor.extract(entry);
|
|
435
|
+
diagnostics.push(...extractor.diagnostics);
|
|
436
|
+
for (const d of ts.getPreEmitDiagnostics(program)) {
|
|
437
|
+
diagnostics.push({
|
|
438
|
+
code: "TC_TS_" + d.code,
|
|
439
|
+
severity: d.category === ts.DiagnosticCategory.Error ? "error" : "warning",
|
|
440
|
+
message: ts.flattenDiagnosticMessageText(d.messageText, "\n"),
|
|
441
|
+
suggestion: "Correct the TypeScript types or use a supported public SDK interface.",
|
|
442
|
+
source: d.file && d.start !== undefined
|
|
443
|
+
? projectLocation(projectRoot, d.file, d.start, d.length ?? 1)
|
|
444
|
+
: undefined,
|
|
445
|
+
});
|
|
446
|
+
}
|
|
447
|
+
const sourceFiles = program
|
|
448
|
+
.getSourceFiles()
|
|
449
|
+
.filter((sf) => path.resolve(sf.fileName) !== path.resolve(sdkPath));
|
|
450
|
+
const inputMap = new Map(initialInputs.map((i) => [i.root + ":" + i.path, i]));
|
|
451
|
+
for (const sf of sourceFiles) {
|
|
452
|
+
const relative = path.relative(projectRoot, sf.fileName);
|
|
453
|
+
if (!relative.startsWith("..") && !path.isAbsolute(relative)) {
|
|
454
|
+
const input = await makeInput(sf.fileName, "project");
|
|
455
|
+
inputMap.set(input.root + ":" + input.path, input);
|
|
456
|
+
}
|
|
457
|
+
}
|
|
458
|
+
report.inputs = [...inputMap.values()].sort((a, b) => (a.root + a.path).localeCompare(b.root + b.path));
|
|
459
|
+
if (!game || diagnostics.some((d) => d.severity === "error"))
|
|
460
|
+
return await fail();
|
|
461
|
+
const normalized = await normalize(game, extractor, projectRoot);
|
|
462
|
+
diagnostics.push(...normalized.diagnostics);
|
|
463
|
+
if (!normalized.ir || diagnostics.some((d) => d.severity === "error"))
|
|
464
|
+
return await fail();
|
|
465
|
+
const ir = normalized.ir;
|
|
466
|
+
report.project.id = ir.gameId;
|
|
467
|
+
for (const asset of ir.assets)
|
|
468
|
+
report.inputs.push({
|
|
469
|
+
root: "project",
|
|
470
|
+
path: asset.sourcePath,
|
|
471
|
+
hash: asset.hash,
|
|
472
|
+
});
|
|
473
|
+
report.incremental.schemaHash = hash(JSON.stringify(ir.components.map((c) => ({
|
|
474
|
+
id: c.id,
|
|
475
|
+
fields: Object.fromEntries(Object.entries(c.schema).map(([k, f]) => [k, f.type])),
|
|
476
|
+
}))));
|
|
477
|
+
report.incremental.defaultsHash = hash(JSON.stringify(ir.components.map((c) => ({ id: c.id, schema: c.schema }))));
|
|
478
|
+
if (previous?.format === 1) {
|
|
479
|
+
const sameSchema = previous.schemaHash === report.incremental.schemaHash;
|
|
480
|
+
const sameDefaults = previous.defaultsHash === report.incremental.defaultsHash;
|
|
481
|
+
report.hotReload = {
|
|
482
|
+
strategy: sameSchema ? "patch" : "restart",
|
|
483
|
+
preserveState: sameSchema,
|
|
484
|
+
defaults: "new-entities-only",
|
|
485
|
+
reason: !sameSchema
|
|
486
|
+
? "Component field layout/type changed; restart required."
|
|
487
|
+
: !sameDefaults
|
|
488
|
+
? "Only compatible changes; retain existing component values, apply new defaults to newly created entities."
|
|
489
|
+
: "Compatible schema; runtime may patch logic/content at a safe boundary.",
|
|
490
|
+
};
|
|
491
|
+
}
|
|
492
|
+
const bundled = await bundle(entry, sdkPath, projectRoot);
|
|
493
|
+
const artifactData = {
|
|
494
|
+
"scene.ir.json": JSON.stringify(ir, null, 2),
|
|
495
|
+
"asset-notices.json": JSON.stringify({
|
|
496
|
+
format: "pelican.asset-notices",
|
|
497
|
+
version: 1,
|
|
498
|
+
assets: ir.assetNotices ?? [],
|
|
499
|
+
}, null, 2),
|
|
500
|
+
"game.js": bundled.code,
|
|
501
|
+
"game.js.map": bundled.map,
|
|
502
|
+
"version.json": JSON.stringify({
|
|
503
|
+
format: "pelican.version",
|
|
504
|
+
version: 1,
|
|
505
|
+
engineVersion: SDK_VERSION,
|
|
506
|
+
sdkVersion: SDK_VERSION,
|
|
507
|
+
compilerVersion: ts.version,
|
|
508
|
+
irVersion: 1,
|
|
509
|
+
systemMetadataVersion: 1,
|
|
510
|
+
schemaHash: report.incremental.schemaHash,
|
|
511
|
+
}, null, 2),
|
|
512
|
+
};
|
|
513
|
+
for (const asset of normalized.assetCopies) {
|
|
514
|
+
const bytes = await fs.readFile(asset.source);
|
|
515
|
+
if (hash(bytes) !== asset.expectedHash) {
|
|
516
|
+
diagnostics.push({
|
|
517
|
+
code: "TC_ASSET_CHANGED",
|
|
518
|
+
severity: "error",
|
|
519
|
+
message: "Asset changed before output: " +
|
|
520
|
+
slash(path.relative(projectRoot, asset.source)),
|
|
521
|
+
suggestion: "Finish writing assets and rebuild.",
|
|
522
|
+
});
|
|
523
|
+
return await fail();
|
|
524
|
+
}
|
|
525
|
+
artifactData[asset.output] = bytes;
|
|
526
|
+
}
|
|
527
|
+
await fs.mkdir(outDir, { recursive: true });
|
|
528
|
+
for (const [file, data] of Object.entries(artifactData)) {
|
|
529
|
+
const target = path.join(outDir, file);
|
|
530
|
+
await fs.mkdir(path.dirname(target), { recursive: true });
|
|
531
|
+
await fs.writeFile(target, data);
|
|
532
|
+
}
|
|
533
|
+
report.success = true;
|
|
534
|
+
report.outputs = [...Object.keys(artifactData), "build-report.json"];
|
|
535
|
+
report.modules = modulesFor(ir);
|
|
536
|
+
report.durationMs = performance.now() - start;
|
|
537
|
+
await fs.writeFile(path.join(outDir, "build-report.json"), JSON.stringify(report, null, 2));
|
|
538
|
+
const cache = {
|
|
539
|
+
format: 1,
|
|
540
|
+
compilerVersion: ts.version,
|
|
541
|
+
bundlerVersion: viteVersion,
|
|
542
|
+
inputs: report.inputs,
|
|
543
|
+
outputHashes: Object.fromEntries(Object.entries(artifactData).map(([file, data]) => [file, hash(data)])),
|
|
544
|
+
schemaHash: report.incremental.schemaHash,
|
|
545
|
+
defaultsHash: report.incremental.defaultsHash,
|
|
546
|
+
ir,
|
|
547
|
+
};
|
|
548
|
+
await fs.writeFile(cachePath, JSON.stringify(cache));
|
|
549
|
+
return { success: true, diagnostics, report, ir };
|
|
550
|
+
}
|
|
551
|
+
catch (error) {
|
|
552
|
+
diagnostics.push({
|
|
553
|
+
code: "TC_COMPILER_FAILURE",
|
|
554
|
+
severity: "error",
|
|
555
|
+
message: error instanceof Error ? error.message : String(error),
|
|
556
|
+
suggestion: "Read the build diagnostics; verify source paths, supported syntax and installed build dependencies.",
|
|
557
|
+
});
|
|
558
|
+
return await fail();
|
|
559
|
+
}
|
|
560
|
+
}
|
|
561
|
+
function modulesFor(ir) {
|
|
562
|
+
return [
|
|
563
|
+
"scene",
|
|
564
|
+
...(ir.components.length ? ["components"] : []),
|
|
565
|
+
...(ir.systems.length ? ["systems"] : []),
|
|
566
|
+
...(ir.materials.length ? ["materials"] : []),
|
|
567
|
+
...(ir.computes.length ? ["compute-graph-ir"] : []),
|
|
568
|
+
...(ir.computeTextures?.length || ir.computes.some(kernel => kernel.textures?.length) ? ["compute-textures"] : []),
|
|
569
|
+
...(ir.postProcesses?.length ? ["postprocess-graph-ir"] : []),
|
|
570
|
+
...(ir.materials.some(material => material.vertex) ? ["vertex-graph-ir"] : []),
|
|
571
|
+
...(ir.materials.some(material => material.buffers?.length) ? ["render-buffer-bindings"] : []),
|
|
572
|
+
...(ir.assets.length ? ["assets"] : []),
|
|
573
|
+
];
|
|
574
|
+
}
|