cajonzero 0.1.2 → 0.1.3
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/README.md +42 -3
- package/dist/bin/cj0.js +14243 -47
- package/dist/{config-BOzAyP7m.d.ts → config-B32NDXlr.d.ts} +1 -1
- package/dist/core.d.ts +15 -7
- package/dist/core.js +319 -157
- package/dist/index.d.ts +5 -5
- package/dist/index.js +184 -22
- package/dist/{providers-BX7e4pNW.d.ts → providers-BwJKzGkd.d.ts} +17 -0
- package/dist/scaffold.d.ts +67 -3
- package/dist/scaffold.js +293 -32
- package/dist/serve.d.ts +1 -1
- package/dist/serve.js +325 -163
- package/dist/ui/assets/index-CNHWg0_t.js +4404 -0
- package/dist/ui/assets/{main-BeABKxzY.js → main-DTcEV4JE.js} +16 -16
- package/dist/ui/index.html +1 -1
- package/package.json +4 -4
- package/dist/ui/assets/index-CaAHje8X.js +0 -4404
package/dist/serve.js
CHANGED
|
@@ -5534,147 +5534,6 @@ async function persistSession(sessions, input, now = Date.now()) {
|
|
|
5534
5534
|
return record;
|
|
5535
5535
|
}
|
|
5536
5536
|
|
|
5537
|
-
// src/core/maps/catalog.ts
|
|
5538
|
-
var MAP_CATALOG_VERSION = 1;
|
|
5539
|
-
var SLUG_RE = /^[a-z0-9][a-z0-9-_]*$/;
|
|
5540
|
-
var MAX_SLUG_LENGTH = 63;
|
|
5541
|
-
var WORLD_ID_RE = /^[A-Za-z0-9][A-Za-z0-9._-]{0,127}$/;
|
|
5542
|
-
var EMPTY_MAP_CATALOG = { version: MAP_CATALOG_VERSION, maps: [] };
|
|
5543
|
-
var MapCatalogError = class extends Error {
|
|
5544
|
-
code;
|
|
5545
|
-
constructor(code, message) {
|
|
5546
|
-
super(message);
|
|
5547
|
-
this.name = "MapCatalogError";
|
|
5548
|
-
this.code = code;
|
|
5549
|
-
}
|
|
5550
|
-
};
|
|
5551
|
-
function fail(code, message) {
|
|
5552
|
-
throw new MapCatalogError(code, message);
|
|
5553
|
-
}
|
|
5554
|
-
function isMapSlug(value) {
|
|
5555
|
-
return value.length > 0 && value.length <= MAX_SLUG_LENGTH && SLUG_RE.test(value);
|
|
5556
|
-
}
|
|
5557
|
-
function assertMapSlug(slug) {
|
|
5558
|
-
if (!isMapSlug(slug)) fail("INVALID_SLUG", `invalid map slug "${slug}"`);
|
|
5559
|
-
return slug;
|
|
5560
|
-
}
|
|
5561
|
-
function assertWorldId(worldId) {
|
|
5562
|
-
if (!WORLD_ID_RE.test(worldId) || worldId === "." || worldId === "..") {
|
|
5563
|
-
fail("INVALID_PATH", `invalid world id "${worldId}"`);
|
|
5564
|
-
}
|
|
5565
|
-
return worldId;
|
|
5566
|
-
}
|
|
5567
|
-
function normalizeRootUrl(raw) {
|
|
5568
|
-
if (typeof raw !== "string" || raw.trim().length === 0) {
|
|
5569
|
-
fail("INVALID_ROOT", "map root must be a non-empty URL");
|
|
5570
|
-
}
|
|
5571
|
-
let url;
|
|
5572
|
-
try {
|
|
5573
|
-
url = new URL(raw.trim());
|
|
5574
|
-
} catch {
|
|
5575
|
-
fail("INVALID_ROOT", `map root is not a valid URL: ${raw}`);
|
|
5576
|
-
}
|
|
5577
|
-
if (url.protocol !== "http:" && url.protocol !== "https:") {
|
|
5578
|
-
fail("INVALID_ROOT", `map root must be http(s): ${raw}`);
|
|
5579
|
-
}
|
|
5580
|
-
url.hash = "";
|
|
5581
|
-
if (!url.pathname.endsWith("/")) {
|
|
5582
|
-
const lastSegment = url.pathname.split("/").pop() ?? "";
|
|
5583
|
-
if (/\.[A-Za-z0-9]+$/.test(lastSegment)) {
|
|
5584
|
-
fail(
|
|
5585
|
-
"INVALID_ROOT",
|
|
5586
|
-
`map root must be the unpacked map directory, not a file ("${lastSegment}"); use its parent directory ending in "/"`
|
|
5587
|
-
);
|
|
5588
|
-
}
|
|
5589
|
-
url.pathname += "/";
|
|
5590
|
-
}
|
|
5591
|
-
return url.toString();
|
|
5592
|
-
}
|
|
5593
|
-
function assertRelativeMapPath(relPath) {
|
|
5594
|
-
if (relPath.length === 0 || relPath.includes("\0") || relPath.includes("\\")) {
|
|
5595
|
-
fail("INVALID_PATH", `invalid map path "${relPath}"`);
|
|
5596
|
-
}
|
|
5597
|
-
if (relPath.startsWith("/")) fail("INVALID_PATH", `absolute map path "${relPath}"`);
|
|
5598
|
-
const parts2 = [];
|
|
5599
|
-
for (const part of relPath.split("/")) {
|
|
5600
|
-
if (part === "" || part === ".") continue;
|
|
5601
|
-
if (part === "..") fail("INVALID_PATH", `map path escapes the root: "${relPath}"`);
|
|
5602
|
-
parts2.push(part);
|
|
5603
|
-
}
|
|
5604
|
-
if (parts2.length === 0) fail("INVALID_PATH", "empty map path");
|
|
5605
|
-
return parts2.join("/");
|
|
5606
|
-
}
|
|
5607
|
-
function mapFilePath(root, relPath) {
|
|
5608
|
-
const base = new URL(normalizeRootUrl(root));
|
|
5609
|
-
const relative = assertRelativeMapPath(relPath);
|
|
5610
|
-
const url = new URL(relative, base);
|
|
5611
|
-
if (url.origin !== base.origin || !url.pathname.startsWith(base.pathname)) {
|
|
5612
|
-
fail("INVALID_PATH", `map path escapes the root: "${relPath}"`);
|
|
5613
|
-
}
|
|
5614
|
-
if (base.search.length > 0) url.search = base.search;
|
|
5615
|
-
url.hash = "";
|
|
5616
|
-
return url.toString();
|
|
5617
|
-
}
|
|
5618
|
-
function optionalString(value, label, code = "INVALID_MAP") {
|
|
5619
|
-
if (value === void 0) return void 0;
|
|
5620
|
-
if (typeof value !== "string" || value.trim().length === 0) {
|
|
5621
|
-
fail(code, `${label} must be a non-empty string`);
|
|
5622
|
-
}
|
|
5623
|
-
return value.trim();
|
|
5624
|
-
}
|
|
5625
|
-
function parseEntry(value, index) {
|
|
5626
|
-
if (typeof value !== "object" || value === null || Array.isArray(value)) {
|
|
5627
|
-
fail("INVALID_MAP", `maps[${index}] must be an object`);
|
|
5628
|
-
}
|
|
5629
|
-
const record = value;
|
|
5630
|
-
const slug = optionalString(record.slug, `maps[${index}].slug`, "INVALID_SLUG");
|
|
5631
|
-
if (slug === void 0) fail("INVALID_SLUG", `maps[${index}].slug is required`);
|
|
5632
|
-
assertMapSlug(slug);
|
|
5633
|
-
const rootRaw = optionalString(record.root, `maps[${index}].root`, "INVALID_ROOT");
|
|
5634
|
-
if (rootRaw === void 0) fail("INVALID_ROOT", `maps[${slug}].root is required`);
|
|
5635
|
-
const root = normalizeRootUrl(rootRaw);
|
|
5636
|
-
const worldId = optionalString(record.worldId, `maps[${index}].worldId`);
|
|
5637
|
-
const assetRoot = optionalString(record.assetRoot, `maps[${index}].assetRoot`, "INVALID_ROOT");
|
|
5638
|
-
const name = optionalString(record.name, `maps[${index}].name`);
|
|
5639
|
-
const author = optionalString(record.author, `maps[${index}].author`);
|
|
5640
|
-
const version = optionalString(record.version, `maps[${index}].version`);
|
|
5641
|
-
const cover = optionalString(record.cover, `maps[${index}].cover`);
|
|
5642
|
-
return {
|
|
5643
|
-
slug,
|
|
5644
|
-
root,
|
|
5645
|
-
...worldId !== void 0 ? { worldId: assertWorldId(worldId) } : {},
|
|
5646
|
-
...name !== void 0 ? { name } : {},
|
|
5647
|
-
...author !== void 0 ? { author } : {},
|
|
5648
|
-
...version !== void 0 ? { version } : {},
|
|
5649
|
-
...cover !== void 0 ? { cover } : {},
|
|
5650
|
-
...assetRoot !== void 0 ? { assetRoot: normalizeRootUrl(assetRoot) } : {}
|
|
5651
|
-
};
|
|
5652
|
-
}
|
|
5653
|
-
function parseMapCatalog(input) {
|
|
5654
|
-
if (input === void 0 || input === null) return EMPTY_MAP_CATALOG;
|
|
5655
|
-
if (typeof input !== "object" || Array.isArray(input)) {
|
|
5656
|
-
fail("INVALID_CATALOG", "maps.json must be an object");
|
|
5657
|
-
}
|
|
5658
|
-
const record = input;
|
|
5659
|
-
const version = record.version ?? MAP_CATALOG_VERSION;
|
|
5660
|
-
if (version !== MAP_CATALOG_VERSION) {
|
|
5661
|
-
fail("INVALID_CATALOG", `unsupported maps.json version ${String(version)}`);
|
|
5662
|
-
}
|
|
5663
|
-
if (!Array.isArray(record.maps)) {
|
|
5664
|
-
fail("INVALID_CATALOG", "maps.json `maps` must be an array");
|
|
5665
|
-
}
|
|
5666
|
-
const maps = record.maps.map((entry, index) => parseEntry(entry, index));
|
|
5667
|
-
const seen = /* @__PURE__ */ new Set();
|
|
5668
|
-
for (const map of maps) {
|
|
5669
|
-
if (seen.has(map.slug)) fail("DUPLICATE_SLUG", `duplicate map slug "${map.slug}"`);
|
|
5670
|
-
seen.add(map.slug);
|
|
5671
|
-
}
|
|
5672
|
-
return { version: MAP_CATALOG_VERSION, maps };
|
|
5673
|
-
}
|
|
5674
|
-
function findMap(catalog, slug) {
|
|
5675
|
-
return catalog.maps.find((entry) => entry.slug === slug);
|
|
5676
|
-
}
|
|
5677
|
-
|
|
5678
5537
|
// src/vendor/mudb/box-schema/backend/database-type.ts
|
|
5679
5538
|
var DB;
|
|
5680
5539
|
((DB2) => {
|
|
@@ -14279,16 +14138,268 @@ function decodeBlockInfo(bytes) {
|
|
|
14279
14138
|
return null;
|
|
14280
14139
|
}
|
|
14281
14140
|
}
|
|
14282
|
-
async function
|
|
14141
|
+
async function readBlockInfoBytes(reader, worldId, hash = BLOCK_INFO_HASH) {
|
|
14283
14142
|
try {
|
|
14284
14143
|
const bytes = await reader.read(`worlds/${worldId}/data/blobs/${hash}`);
|
|
14285
14144
|
if (bytes === null || bytes.length === 0) return null;
|
|
14286
|
-
return
|
|
14145
|
+
return bytes;
|
|
14287
14146
|
} catch {
|
|
14288
14147
|
return null;
|
|
14289
14148
|
}
|
|
14290
14149
|
}
|
|
14291
14150
|
|
|
14151
|
+
// src/core/maps/seed-pack.ts
|
|
14152
|
+
var SEED_PACK_VERSION = 1;
|
|
14153
|
+
var SEED_PACK_FORMAT = "cajonzero-seed";
|
|
14154
|
+
var SEED_PACK_FILES = ["seed.json.gz", "seed.json"];
|
|
14155
|
+
function isGzip(bytes) {
|
|
14156
|
+
return bytes.length >= 2 && bytes[0] === 31 && bytes[1] === 139;
|
|
14157
|
+
}
|
|
14158
|
+
async function gunzipBytes(bytes) {
|
|
14159
|
+
if (typeof DecompressionStream === "undefined") {
|
|
14160
|
+
throw new Error("this runtime does not support gzip seed packs");
|
|
14161
|
+
}
|
|
14162
|
+
const buffer = bytes.buffer.slice(
|
|
14163
|
+
bytes.byteOffset,
|
|
14164
|
+
bytes.byteOffset + bytes.byteLength
|
|
14165
|
+
);
|
|
14166
|
+
const stream = new Blob([buffer]).stream().pipeThrough(new DecompressionStream("gzip"));
|
|
14167
|
+
return new Uint8Array(await new Response(stream).arrayBuffer());
|
|
14168
|
+
}
|
|
14169
|
+
async function decodeSeedPackAuto(bytes) {
|
|
14170
|
+
return decodeSeedPack(isGzip(bytes) ? await gunzipBytes(bytes) : bytes);
|
|
14171
|
+
}
|
|
14172
|
+
async function loadSeedPackFromAssets(assets, slug, worldId) {
|
|
14173
|
+
if (assets === void 0) return null;
|
|
14174
|
+
for (const file of SEED_PACK_FILES) {
|
|
14175
|
+
const suffix = file.slice("seed".length);
|
|
14176
|
+
const path = `/seed/${encodeURIComponent(slug)}${suffix}`;
|
|
14177
|
+
try {
|
|
14178
|
+
const response = await assets.fetch(new Request(`https://cajonzero.internal${path}`));
|
|
14179
|
+
if (!response.ok) continue;
|
|
14180
|
+
const bytes = new Uint8Array(await response.arrayBuffer());
|
|
14181
|
+
const seed = await decodeSeedPackAuto(bytes);
|
|
14182
|
+
if (worldId !== void 0 && worldId.length > 0 && seed.mapId !== worldId) continue;
|
|
14183
|
+
return seed;
|
|
14184
|
+
} catch {
|
|
14185
|
+
}
|
|
14186
|
+
}
|
|
14187
|
+
return null;
|
|
14188
|
+
}
|
|
14189
|
+
function base64ToBytes(text) {
|
|
14190
|
+
const binary = atob(text);
|
|
14191
|
+
const out = new Uint8Array(binary.length);
|
|
14192
|
+
for (let i = 0; i < binary.length; i += 1) out[i] = binary.charCodeAt(i);
|
|
14193
|
+
return out;
|
|
14194
|
+
}
|
|
14195
|
+
function isRecord2(value) {
|
|
14196
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
14197
|
+
}
|
|
14198
|
+
function decodeSeedPack(bytes) {
|
|
14199
|
+
let parsed;
|
|
14200
|
+
try {
|
|
14201
|
+
parsed = JSON.parse(new TextDecoder().decode(bytes));
|
|
14202
|
+
} catch {
|
|
14203
|
+
throw new Error("seed pack is not valid JSON");
|
|
14204
|
+
}
|
|
14205
|
+
if (!isRecord2(parsed) || parsed["format"] !== SEED_PACK_FORMAT) {
|
|
14206
|
+
throw new Error("not a cajonzero seed pack");
|
|
14207
|
+
}
|
|
14208
|
+
if (parsed["version"] !== SEED_PACK_VERSION) {
|
|
14209
|
+
throw new Error(
|
|
14210
|
+
`unsupported seed pack version ${String(parsed["version"])} (expected ${SEED_PACK_VERSION})`
|
|
14211
|
+
);
|
|
14212
|
+
}
|
|
14213
|
+
if (typeof parsed["mapId"] !== "string" || parsed["mapId"].length === 0) {
|
|
14214
|
+
throw new Error("seed pack is missing mapId");
|
|
14215
|
+
}
|
|
14216
|
+
if (!isRecord2(parsed["seed"])) {
|
|
14217
|
+
throw new Error("seed pack is missing the seed payload");
|
|
14218
|
+
}
|
|
14219
|
+
const rawSeed = parsed["seed"];
|
|
14220
|
+
if (rawSeed["version"] !== WORLD_SEED_VERSION) {
|
|
14221
|
+
throw new Error(
|
|
14222
|
+
`seed pack was built for world-seed v${String(rawSeed["version"])} (this runtime expects v${WORLD_SEED_VERSION}); re-run cj0 maps pack`
|
|
14223
|
+
);
|
|
14224
|
+
}
|
|
14225
|
+
const rawVoxels = rawSeed["voxels"];
|
|
14226
|
+
let voxels = null;
|
|
14227
|
+
if (rawVoxels !== null) {
|
|
14228
|
+
if (!isRecord2(rawVoxels)) throw new Error("seed pack voxels are malformed");
|
|
14229
|
+
const blobs = rawVoxels["chunkBlobs"];
|
|
14230
|
+
if (!Array.isArray(blobs)) throw new Error("seed pack voxels are malformed");
|
|
14231
|
+
const rawShape = rawVoxels["shape"];
|
|
14232
|
+
const shape = isRecord2(rawShape) ? {
|
|
14233
|
+
x: Number(rawShape["x"]) || 0,
|
|
14234
|
+
y: Number(rawShape["y"]) || 0,
|
|
14235
|
+
z: Number(rawShape["z"]) || 0
|
|
14236
|
+
} : { x: 0, y: 0, z: 0 };
|
|
14237
|
+
const hashes = rawVoxels["chunkHashes"];
|
|
14238
|
+
voxels = {
|
|
14239
|
+
shape,
|
|
14240
|
+
chunkCount: Number(rawVoxels["chunkCount"]) || blobs.length,
|
|
14241
|
+
chunkBlobs: blobs.map(
|
|
14242
|
+
(blob) => typeof blob === "string" ? base64ToBytes(blob) : new Uint8Array(0)
|
|
14243
|
+
),
|
|
14244
|
+
chunkHashes: Array.isArray(hashes) ? hashes.map((hash) => typeof hash === "string" ? hash : "") : []
|
|
14245
|
+
};
|
|
14246
|
+
}
|
|
14247
|
+
const { voxels: _ignored, ...rest } = rawSeed;
|
|
14248
|
+
const seed = {
|
|
14249
|
+
...rest,
|
|
14250
|
+
mapId: parsed["mapId"],
|
|
14251
|
+
voxels
|
|
14252
|
+
};
|
|
14253
|
+
if (typeof parsed["blockInfo"] === "string" && parsed["blockInfo"].length > 0) {
|
|
14254
|
+
const blockInfo = decodeBlockInfo(base64ToBytes(parsed["blockInfo"]));
|
|
14255
|
+
if (blockInfo !== null) seed.blockInfo = blockInfo;
|
|
14256
|
+
}
|
|
14257
|
+
return seed;
|
|
14258
|
+
}
|
|
14259
|
+
|
|
14260
|
+
// src/core/maps/catalog.ts
|
|
14261
|
+
var MAP_CATALOG_VERSION = 1;
|
|
14262
|
+
var SLUG_RE = /^[a-z0-9][a-z0-9-_]*$/;
|
|
14263
|
+
var MAX_SLUG_LENGTH = 63;
|
|
14264
|
+
var WORLD_ID_RE = /^[A-Za-z0-9][A-Za-z0-9._-]{0,127}$/;
|
|
14265
|
+
var EMPTY_MAP_CATALOG = { version: MAP_CATALOG_VERSION, maps: [] };
|
|
14266
|
+
var MapCatalogError = class extends Error {
|
|
14267
|
+
code;
|
|
14268
|
+
constructor(code, message) {
|
|
14269
|
+
super(message);
|
|
14270
|
+
this.name = "MapCatalogError";
|
|
14271
|
+
this.code = code;
|
|
14272
|
+
}
|
|
14273
|
+
};
|
|
14274
|
+
function fail(code, message) {
|
|
14275
|
+
throw new MapCatalogError(code, message);
|
|
14276
|
+
}
|
|
14277
|
+
function isMapSlug(value) {
|
|
14278
|
+
return value.length > 0 && value.length <= MAX_SLUG_LENGTH && SLUG_RE.test(value);
|
|
14279
|
+
}
|
|
14280
|
+
function assertMapSlug(slug) {
|
|
14281
|
+
if (!isMapSlug(slug)) fail("INVALID_SLUG", `invalid map slug "${slug}"`);
|
|
14282
|
+
return slug;
|
|
14283
|
+
}
|
|
14284
|
+
function assertWorldId(worldId) {
|
|
14285
|
+
if (!WORLD_ID_RE.test(worldId) || worldId === "." || worldId === "..") {
|
|
14286
|
+
fail("INVALID_PATH", `invalid world id "${worldId}"`);
|
|
14287
|
+
}
|
|
14288
|
+
return worldId;
|
|
14289
|
+
}
|
|
14290
|
+
function normalizeRootUrl(raw) {
|
|
14291
|
+
if (typeof raw !== "string" || raw.trim().length === 0) {
|
|
14292
|
+
fail("INVALID_ROOT", "map root must be a non-empty URL");
|
|
14293
|
+
}
|
|
14294
|
+
let url;
|
|
14295
|
+
try {
|
|
14296
|
+
url = new URL(raw.trim());
|
|
14297
|
+
} catch {
|
|
14298
|
+
fail("INVALID_ROOT", `map root is not a valid URL: ${raw}`);
|
|
14299
|
+
}
|
|
14300
|
+
if (url.protocol !== "http:" && url.protocol !== "https:") {
|
|
14301
|
+
fail("INVALID_ROOT", `map root must be http(s): ${raw}`);
|
|
14302
|
+
}
|
|
14303
|
+
url.hash = "";
|
|
14304
|
+
if (!url.pathname.endsWith("/")) {
|
|
14305
|
+
const lastSegment = url.pathname.split("/").pop() ?? "";
|
|
14306
|
+
if (/\.[A-Za-z0-9]+$/.test(lastSegment)) {
|
|
14307
|
+
fail(
|
|
14308
|
+
"INVALID_ROOT",
|
|
14309
|
+
`map root must be the unpacked map directory, not a file ("${lastSegment}"); use its parent directory ending in "/"`
|
|
14310
|
+
);
|
|
14311
|
+
}
|
|
14312
|
+
url.pathname += "/";
|
|
14313
|
+
}
|
|
14314
|
+
return url.toString();
|
|
14315
|
+
}
|
|
14316
|
+
function assertRelativeMapPath(relPath) {
|
|
14317
|
+
if (relPath.length === 0 || relPath.includes("\0") || relPath.includes("\\")) {
|
|
14318
|
+
fail("INVALID_PATH", `invalid map path "${relPath}"`);
|
|
14319
|
+
}
|
|
14320
|
+
if (relPath.startsWith("/")) fail("INVALID_PATH", `absolute map path "${relPath}"`);
|
|
14321
|
+
const parts2 = [];
|
|
14322
|
+
for (const part of relPath.split("/")) {
|
|
14323
|
+
if (part === "" || part === ".") continue;
|
|
14324
|
+
if (part === "..") fail("INVALID_PATH", `map path escapes the root: "${relPath}"`);
|
|
14325
|
+
parts2.push(part);
|
|
14326
|
+
}
|
|
14327
|
+
if (parts2.length === 0) fail("INVALID_PATH", "empty map path");
|
|
14328
|
+
return parts2.join("/");
|
|
14329
|
+
}
|
|
14330
|
+
function mapFilePath(root, relPath) {
|
|
14331
|
+
const base = new URL(normalizeRootUrl(root));
|
|
14332
|
+
const relative = assertRelativeMapPath(relPath);
|
|
14333
|
+
const url = new URL(relative, base);
|
|
14334
|
+
if (url.origin !== base.origin || !url.pathname.startsWith(base.pathname)) {
|
|
14335
|
+
fail("INVALID_PATH", `map path escapes the root: "${relPath}"`);
|
|
14336
|
+
}
|
|
14337
|
+
if (base.search.length > 0) url.search = base.search;
|
|
14338
|
+
url.hash = "";
|
|
14339
|
+
return url.toString();
|
|
14340
|
+
}
|
|
14341
|
+
function optionalString(value, label, code = "INVALID_MAP") {
|
|
14342
|
+
if (value === void 0) return void 0;
|
|
14343
|
+
if (typeof value !== "string" || value.trim().length === 0) {
|
|
14344
|
+
fail(code, `${label} must be a non-empty string`);
|
|
14345
|
+
}
|
|
14346
|
+
return value.trim();
|
|
14347
|
+
}
|
|
14348
|
+
function parseEntry(value, index) {
|
|
14349
|
+
if (typeof value !== "object" || value === null || Array.isArray(value)) {
|
|
14350
|
+
fail("INVALID_MAP", `maps[${index}] must be an object`);
|
|
14351
|
+
}
|
|
14352
|
+
const record = value;
|
|
14353
|
+
const slug = optionalString(record.slug, `maps[${index}].slug`, "INVALID_SLUG");
|
|
14354
|
+
if (slug === void 0) fail("INVALID_SLUG", `maps[${index}].slug is required`);
|
|
14355
|
+
assertMapSlug(slug);
|
|
14356
|
+
const rootRaw = optionalString(record.root, `maps[${index}].root`, "INVALID_ROOT");
|
|
14357
|
+
if (rootRaw === void 0) fail("INVALID_ROOT", `maps[${slug}].root is required`);
|
|
14358
|
+
const root = normalizeRootUrl(rootRaw);
|
|
14359
|
+
const worldId = optionalString(record.worldId, `maps[${index}].worldId`);
|
|
14360
|
+
const assetRoot = optionalString(record.assetRoot, `maps[${index}].assetRoot`, "INVALID_ROOT");
|
|
14361
|
+
const name = optionalString(record.name, `maps[${index}].name`);
|
|
14362
|
+
const author = optionalString(record.author, `maps[${index}].author`);
|
|
14363
|
+
const version = optionalString(record.version, `maps[${index}].version`);
|
|
14364
|
+
const etag = optionalString(record.etag, `maps[${index}].etag`);
|
|
14365
|
+
const cover = optionalString(record.cover, `maps[${index}].cover`);
|
|
14366
|
+
return {
|
|
14367
|
+
slug,
|
|
14368
|
+
root,
|
|
14369
|
+
...worldId !== void 0 ? { worldId: assertWorldId(worldId) } : {},
|
|
14370
|
+
...name !== void 0 ? { name } : {},
|
|
14371
|
+
...author !== void 0 ? { author } : {},
|
|
14372
|
+
...version !== void 0 ? { version } : {},
|
|
14373
|
+
...etag !== void 0 ? { etag } : {},
|
|
14374
|
+
...cover !== void 0 ? { cover } : {},
|
|
14375
|
+
...assetRoot !== void 0 ? { assetRoot: normalizeRootUrl(assetRoot) } : {}
|
|
14376
|
+
};
|
|
14377
|
+
}
|
|
14378
|
+
function parseMapCatalog(input) {
|
|
14379
|
+
if (input === void 0 || input === null) return EMPTY_MAP_CATALOG;
|
|
14380
|
+
if (typeof input !== "object" || Array.isArray(input)) {
|
|
14381
|
+
fail("INVALID_CATALOG", "maps.json must be an object");
|
|
14382
|
+
}
|
|
14383
|
+
const record = input;
|
|
14384
|
+
const version = record.version ?? MAP_CATALOG_VERSION;
|
|
14385
|
+
if (version !== MAP_CATALOG_VERSION) {
|
|
14386
|
+
fail("INVALID_CATALOG", `unsupported maps.json version ${String(version)}`);
|
|
14387
|
+
}
|
|
14388
|
+
if (!Array.isArray(record.maps)) {
|
|
14389
|
+
fail("INVALID_CATALOG", "maps.json `maps` must be an array");
|
|
14390
|
+
}
|
|
14391
|
+
const maps = record.maps.map((entry, index) => parseEntry(entry, index));
|
|
14392
|
+
const seen = /* @__PURE__ */ new Set();
|
|
14393
|
+
for (const map of maps) {
|
|
14394
|
+
if (seen.has(map.slug)) fail("DUPLICATE_SLUG", `duplicate map slug "${map.slug}"`);
|
|
14395
|
+
seen.add(map.slug);
|
|
14396
|
+
}
|
|
14397
|
+
return { version: MAP_CATALOG_VERSION, maps };
|
|
14398
|
+
}
|
|
14399
|
+
function findMap(catalog, slug) {
|
|
14400
|
+
return catalog.maps.find((entry) => entry.slug === slug);
|
|
14401
|
+
}
|
|
14402
|
+
|
|
14292
14403
|
// src/core/maps/http.ts
|
|
14293
14404
|
var MapSourceError = class extends Error {
|
|
14294
14405
|
code;
|
|
@@ -14391,14 +14502,14 @@ var HttpMapSource = class {
|
|
|
14391
14502
|
return new Uint8Array(buffer);
|
|
14392
14503
|
}
|
|
14393
14504
|
};
|
|
14394
|
-
function
|
|
14505
|
+
function isRecord3(value) {
|
|
14395
14506
|
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
14396
14507
|
}
|
|
14397
14508
|
async function resolveWorldId(source) {
|
|
14398
14509
|
const bundle = await source.readJson("bundle.json");
|
|
14399
14510
|
if (bundle === null) fail2("MAP_NO_WORLD", "bundle.json is missing or not JSON");
|
|
14400
14511
|
const members = Array.isArray(bundle.members) ? bundle.members : [];
|
|
14401
|
-
const records = members.filter(
|
|
14512
|
+
const records = members.filter(isRecord3);
|
|
14402
14513
|
const main = records.find((member) => member.mapRole === "main") ?? records[0];
|
|
14403
14514
|
const candidate = (main !== void 0 && typeof main.id === "string" ? main.id : void 0) ?? (typeof bundle.mainProjectId === "string" ? bundle.mainProjectId : void 0);
|
|
14404
14515
|
if (candidate === void 0) {
|
|
@@ -14407,6 +14518,13 @@ async function resolveWorldId(source) {
|
|
|
14407
14518
|
return assertWorldId(candidate);
|
|
14408
14519
|
}
|
|
14409
14520
|
async function loadWorldSeedFromHttp(entry, options = {}) {
|
|
14521
|
+
if (options.preferSeedPack !== false) {
|
|
14522
|
+
const packed = await loadSeedPackFromHttp(entry, options);
|
|
14523
|
+
if (packed !== null) return packed;
|
|
14524
|
+
}
|
|
14525
|
+
return (await loadWorldSeedGraphFromHttp(entry, options)).seed;
|
|
14526
|
+
}
|
|
14527
|
+
async function loadWorldSeedGraphFromHttp(entry, options = {}) {
|
|
14410
14528
|
const source = new HttpMapSource(entry.root, options);
|
|
14411
14529
|
const worldId = entry.worldId ?? await resolveWorldId(source);
|
|
14412
14530
|
const manifest = await source.readJson(
|
|
@@ -14415,13 +14533,35 @@ async function loadWorldSeedFromHttp(entry, options = {}) {
|
|
|
14415
14533
|
if (manifest === null) {
|
|
14416
14534
|
fail2("MAP_NO_WORLD", `worlds/${worldId}/manifest.json is missing or not JSON`);
|
|
14417
14535
|
}
|
|
14418
|
-
|
|
14536
|
+
const seed = await resolveWorldSeed(
|
|
14419
14537
|
{ worldId, manifest, reader: source },
|
|
14420
14538
|
{ concurrency: options.concurrency ?? DEFAULT_CONCURRENCY }
|
|
14421
|
-
)
|
|
14422
|
-
|
|
14423
|
-
|
|
14424
|
-
|
|
14539
|
+
);
|
|
14540
|
+
const blockInfoBytes = await readBlockInfoBytes(source, worldId) ?? void 0;
|
|
14541
|
+
const blockInfo = blockInfoBytes === void 0 ? null : decodeBlockInfo(blockInfoBytes);
|
|
14542
|
+
return {
|
|
14543
|
+
seed: blockInfo === null ? seed : { ...seed, blockInfo },
|
|
14544
|
+
...blockInfoBytes !== void 0 ? { blockInfoBytes } : {}
|
|
14545
|
+
};
|
|
14546
|
+
}
|
|
14547
|
+
async function loadSeedPackFromHttp(entry, options) {
|
|
14548
|
+
const source = new HttpMapSource(entry.root, options);
|
|
14549
|
+
for (const file of SEED_PACK_FILES) {
|
|
14550
|
+
let bytes;
|
|
14551
|
+
try {
|
|
14552
|
+
bytes = await source.read(file);
|
|
14553
|
+
} catch {
|
|
14554
|
+
continue;
|
|
14555
|
+
}
|
|
14556
|
+
if (bytes === null) continue;
|
|
14557
|
+
try {
|
|
14558
|
+
const seed = await decodeSeedPackAuto(bytes);
|
|
14559
|
+
if (entry.worldId !== void 0 && seed.mapId !== entry.worldId) continue;
|
|
14560
|
+
return seed;
|
|
14561
|
+
} catch {
|
|
14562
|
+
}
|
|
14563
|
+
}
|
|
14564
|
+
return null;
|
|
14425
14565
|
}
|
|
14426
14566
|
var MEBIBYTE = 1024 * 1024;
|
|
14427
14567
|
var ZERO_PLAYER_TIMEOUT_DEFAULT_MS = 48e4;
|
|
@@ -23576,6 +23716,8 @@ var Room = class {
|
|
|
23576
23716
|
createPair;
|
|
23577
23717
|
logger;
|
|
23578
23718
|
secret;
|
|
23719
|
+
/** Worker static-assets binding, used to read seed packs bundled at deploy. */
|
|
23720
|
+
assets;
|
|
23579
23721
|
engine;
|
|
23580
23722
|
script;
|
|
23581
23723
|
explicitScriptHost;
|
|
@@ -23638,6 +23780,10 @@ var Room = class {
|
|
|
23638
23780
|
pendingEngineMap;
|
|
23639
23781
|
/** Identity of the map the current engine was seeded from. */
|
|
23640
23782
|
seededMap;
|
|
23783
|
+
/** Non-fatal seed warnings (missing blobs, skipped entities, …). */
|
|
23784
|
+
seedWarnings = [];
|
|
23785
|
+
/** Last map-load failure; cleared on a successful seed. */
|
|
23786
|
+
mapLoadError;
|
|
23641
23787
|
heartbeatBeats = 0;
|
|
23642
23788
|
ticksRun = 0;
|
|
23643
23789
|
lastTickError;
|
|
@@ -23661,6 +23807,7 @@ var Room = class {
|
|
|
23661
23807
|
this.createPair = options.createPair ?? defaultCreatePair;
|
|
23662
23808
|
this.logger = options.logger ?? defaultLogger;
|
|
23663
23809
|
this.secret = options.secret ?? resolveSessionSecret(env);
|
|
23810
|
+
this.assets = env.ASSETS;
|
|
23664
23811
|
this.engine = options.engine ?? getRoomEngineFactory()?.() ?? createNullEngine();
|
|
23665
23812
|
this.explicitScriptHost = options.scriptHost !== void 0;
|
|
23666
23813
|
this.script = options.scriptHost ?? this.engine;
|
|
@@ -23755,7 +23902,9 @@ var Room = class {
|
|
|
23755
23902
|
ok: true,
|
|
23756
23903
|
slug: this.mapBootstrap?.slug ?? null,
|
|
23757
23904
|
seeded: this.worldReady,
|
|
23758
|
-
entities: this.engine.getReplica().entities.length
|
|
23905
|
+
entities: this.engine.getReplica().entities.length,
|
|
23906
|
+
mapLoadError: this.mapLoadError ?? null,
|
|
23907
|
+
warnings: this.seedWarnings
|
|
23759
23908
|
}),
|
|
23760
23909
|
{ headers: { "content-type": "application/json", "cache-control": "no-store" } }
|
|
23761
23910
|
);
|
|
@@ -23786,6 +23935,8 @@ var Room = class {
|
|
|
23786
23935
|
secretPackets: this.secretPackets,
|
|
23787
23936
|
clockPings: this.clockPings,
|
|
23788
23937
|
inboundIds: Object.fromEntries(this.inboundIds),
|
|
23938
|
+
mapLoadError: this.mapLoadError ?? null,
|
|
23939
|
+
mapWarnings: this.seedWarnings,
|
|
23789
23940
|
voxel: this.engine.voxelGeometry?.() ?? null,
|
|
23790
23941
|
sampleEntity: (() => {
|
|
23791
23942
|
const entity = this.engine.getReplica().entities[0];
|
|
@@ -23883,21 +24034,32 @@ var Room = class {
|
|
|
23883
24034
|
this.engine = engine;
|
|
23884
24035
|
if (!this.explicitScriptHost) this.script = engine;
|
|
23885
24036
|
this.seededMap = { slug: bootstrap.slug, root: bootstrap.root, worldId: seed.mapId };
|
|
24037
|
+
this.seedWarnings = seed.warnings;
|
|
24038
|
+
this.mapLoadError = void 0;
|
|
24039
|
+
for (const warning of seed.warnings) {
|
|
24040
|
+
this.logger.log(`cajonzero: map "${bootstrap.slug}" warning: ${warning}`);
|
|
24041
|
+
}
|
|
23886
24042
|
this.applyPendingEngineSnapshot();
|
|
23887
24043
|
this.worldReady = true;
|
|
23888
24044
|
this.logger.log(`cajonzero: seeded room "${bootstrap.slug}" from "${seed.mapId}"`);
|
|
23889
24045
|
} catch (error) {
|
|
24046
|
+
this.mapLoadError = describeError2(error);
|
|
23890
24047
|
this.logger.error(
|
|
23891
|
-
`cajonzero: map load failed for "${bootstrap.slug}": ${
|
|
24048
|
+
`cajonzero: map load failed for "${bootstrap.slug}": ${this.mapLoadError}`
|
|
23892
24049
|
);
|
|
23893
24050
|
}
|
|
23894
24051
|
}
|
|
23895
24052
|
loadSeed(bootstrap) {
|
|
23896
|
-
return
|
|
23897
|
-
|
|
23898
|
-
|
|
23899
|
-
|
|
23900
|
-
|
|
24053
|
+
return loadSeedPackFromAssets(this.assets, bootstrap.slug, bootstrap.worldId).then(
|
|
24054
|
+
(packed) => {
|
|
24055
|
+
if (packed !== null) return packed;
|
|
24056
|
+
return loadWorldSeedFromHttp(bootstrap, {
|
|
24057
|
+
...bootstrap.maxDownloadBytes !== void 0 ? { maxDownloadBytes: bootstrap.maxDownloadBytes } : {},
|
|
24058
|
+
...bootstrap.concurrency !== void 0 ? { concurrency: bootstrap.concurrency } : {},
|
|
24059
|
+
...bootstrap.requestTimeoutMs !== void 0 ? { requestTimeoutMs: bootstrap.requestTimeoutMs } : {}
|
|
24060
|
+
});
|
|
24061
|
+
}
|
|
24062
|
+
);
|
|
23901
24063
|
}
|
|
23902
24064
|
/**
|
|
23903
24065
|
* Run the full connect path and return a structured outcome (no `Response`,
|
|
@@ -25713,7 +25875,7 @@ function createPlayEngine(options = {}) {
|
|
|
25713
25875
|
const spawnOnClientInput = options.spawnOnClientInput ?? true;
|
|
25714
25876
|
const scriptHost = options.scriptHost;
|
|
25715
25877
|
const physicsOverride = options.physics ? buildPhysicsOverride(options.physics) : null;
|
|
25716
|
-
const playerConfig =
|
|
25878
|
+
const playerConfig = isRecord4(options.worldSeed?.player) ? options.worldSeed.player : null;
|
|
25717
25879
|
let world = options.world ? NetStateSchema.clone(options.world) : options.worldSeed ? buildInitialWorld(options.worldSeed) : null;
|
|
25718
25880
|
let currentTick = 0;
|
|
25719
25881
|
let suspended = false;
|
|
@@ -26032,7 +26194,7 @@ function buildPhysicsOverride(override) {
|
|
|
26032
26194
|
}
|
|
26033
26195
|
return params;
|
|
26034
26196
|
}
|
|
26035
|
-
function
|
|
26197
|
+
function isRecord4(value) {
|
|
26036
26198
|
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
26037
26199
|
}
|
|
26038
26200
|
function finite(value) {
|
|
@@ -26090,7 +26252,7 @@ function spawnPlayerBody(id, config) {
|
|
|
26090
26252
|
body.friction = PLAYER_FRICTION;
|
|
26091
26253
|
body.restitution = PLAYER_RESTITUTION;
|
|
26092
26254
|
body.mass = PLAYER_MASS;
|
|
26093
|
-
const spawn =
|
|
26255
|
+
const spawn = isRecord4(config?.initialPosition) ? config.initialPosition : null;
|
|
26094
26256
|
body.px = finite(spawn?.x) ?? 0;
|
|
26095
26257
|
body.py = finite(spawn?.y) ?? 0;
|
|
26096
26258
|
body.pz = finite(spawn?.z) ?? 0;
|
|
@@ -26109,12 +26271,12 @@ function projectEnvironment(raw) {
|
|
|
26109
26271
|
return environment;
|
|
26110
26272
|
}
|
|
26111
26273
|
function copyEnvironmentFields(target, source) {
|
|
26112
|
-
if (!
|
|
26274
|
+
if (!isRecord4(target)) return;
|
|
26113
26275
|
for (const key of Object.keys(target)) {
|
|
26114
26276
|
const value = source[key];
|
|
26115
26277
|
if (value === void 0) continue;
|
|
26116
26278
|
const current = target[key];
|
|
26117
|
-
if (
|
|
26279
|
+
if (isRecord4(current) && isRecord4(value)) {
|
|
26118
26280
|
copyEnvironmentFields(current, value);
|
|
26119
26281
|
continue;
|
|
26120
26282
|
}
|
|
@@ -26843,7 +27005,7 @@ function mountMapRoutes(app, catalog) {
|
|
|
26843
27005
|
}
|
|
26844
27006
|
|
|
26845
27007
|
// src/version.ts
|
|
26846
|
-
var VERSION = "0.1.
|
|
27008
|
+
var VERSION = "0.1.3";
|
|
26847
27009
|
|
|
26848
27010
|
// src/core/server/room-gateway.ts
|
|
26849
27011
|
var CONTAINER_PATTERN3 = /^[A-Za-z0-9][A-Za-z0-9_-]{0,127}$/;
|