cajonzero 0.1.0 → 0.1.2

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/dist/serve.js CHANGED
@@ -5343,7 +5343,6 @@ var KEY_PREFIX = {
5343
5343
  account: "account",
5344
5344
  session: "session",
5345
5345
  ban: "ban",
5346
- ratelimit: "ratelimit",
5347
5346
  settings: "settings",
5348
5347
  mapStorage: "mapstorage"
5349
5348
  };
@@ -5375,9 +5374,6 @@ function banAccountKey(subjectId) {
5375
5374
  function banIdentityKey(kind, hash) {
5376
5375
  return `ban:identity:${kind}:${seg2(hash)}`;
5377
5376
  }
5378
- function ratelimitKey(scope, hash) {
5379
- return `ratelimit:${seg2(scope)}:${seg2(hash)}`;
5380
- }
5381
5377
  function settingsKey(key) {
5382
5378
  return `settings:${seg2(key)}`;
5383
5379
  }
@@ -5420,12 +5416,6 @@ function parseKey(key) {
5420
5416
  }
5421
5417
  return void 0;
5422
5418
  }
5423
- case KEY_PREFIX.ratelimit: {
5424
- if (parts2.length !== 3) return void 0;
5425
- const scope = decode(parts2[1]);
5426
- const hash = decode(parts2[2]);
5427
- return scope === void 0 || hash === void 0 ? void 0 : { kind: "ratelimit", scope, hash };
5428
- }
5429
5419
  case KEY_PREFIX.settings: {
5430
5420
  if (parts2.length !== 2) return void 0;
5431
5421
  const settingKey = decode(parts2[1]);
@@ -5649,7 +5639,6 @@ function parseEntry(value, index) {
5649
5639
  const author = optionalString(record.author, `maps[${index}].author`);
5650
5640
  const version = optionalString(record.version, `maps[${index}].version`);
5651
5641
  const cover = optionalString(record.cover, `maps[${index}].cover`);
5652
- const etag = optionalString(record.etag, `maps[${index}].etag`);
5653
5642
  return {
5654
5643
  slug,
5655
5644
  root,
@@ -5658,7 +5647,6 @@ function parseEntry(value, index) {
5658
5647
  ...author !== void 0 ? { author } : {},
5659
5648
  ...version !== void 0 ? { version } : {},
5660
5649
  ...cover !== void 0 ? { cover } : {},
5661
- ...etag !== void 0 ? { etag } : {},
5662
5650
  ...assetRoot !== void 0 ? { assetRoot: normalizeRootUrl(assetRoot) } : {}
5663
5651
  };
5664
5652
  }
@@ -14282,79 +14270,24 @@ function buildInitialWorld(seed) {
14282
14270
  return world;
14283
14271
  }
14284
14272
 
14285
- // src/core/maps/seed-cache.ts
14286
- var SEED_CACHE_PREFIX = "map:seed";
14287
- function seedCacheKey(slug, revision) {
14288
- const safeRevision = encodeURIComponent(revision).slice(0, 160);
14289
- return `${SEED_CACHE_PREFIX}:${encodeURIComponent(slug)}:${safeRevision}`;
14290
- }
14291
- function toBase64(bytes) {
14292
- let binary = "";
14293
- const block = 32768;
14294
- for (let offset = 0; offset < bytes.length; offset += block) {
14295
- binary += String.fromCharCode(...bytes.subarray(offset, offset + block));
14296
- }
14297
- return btoa(binary);
14298
- }
14299
- function fromBase64(value) {
14300
- const binary = atob(value);
14301
- const bytes = new Uint8Array(binary.length);
14302
- for (let i = 0; i < binary.length; i += 1) {
14303
- bytes[i] = binary.charCodeAt(i);
14304
- }
14305
- return bytes;
14306
- }
14307
- function encode(seed) {
14308
- const wire = {
14309
- version: WORLD_SEED_VERSION,
14310
- seed: {
14311
- ...seed,
14312
- voxels: seed.voxels === null ? null : {
14313
- shape: seed.voxels.shape,
14314
- chunkCount: seed.voxels.chunkCount,
14315
- chunkBlobs: seed.voxels.chunkBlobs.map(toBase64),
14316
- chunkHashes: seed.voxels.chunkHashes.slice()
14317
- }
14318
- }
14319
- };
14320
- return JSON.stringify(wire);
14321
- }
14322
- function decode2(raw) {
14273
+ // src/core/maps/block-info.ts
14274
+ var BLOCK_INFO_HASH = "QmW2E9E8ipYUifsCfkMj5SULYMYbFQvP6BQxo52atXw7Zj";
14275
+ function decodeBlockInfo(bytes) {
14323
14276
  try {
14324
- const wire = JSON.parse(raw);
14325
- if (wire.version !== WORLD_SEED_VERSION || typeof wire.seed !== "object" || wire.seed === null) {
14326
- return null;
14327
- }
14328
- const { voxels, ...rest } = wire.seed;
14329
- return {
14330
- ...rest,
14331
- voxels: voxels === null ? null : {
14332
- shape: voxels.shape,
14333
- chunkCount: voxels.chunkCount,
14334
- chunkBlobs: voxels.chunkBlobs.map(fromBase64),
14335
- chunkHashes: Array.isArray(voxels.chunkHashes) ? voxels.chunkHashes : []
14336
- }
14337
- };
14277
+ return BlockInfoSchema.patch(BlockInfoSchema.identity, new MuReadStream(bytes));
14338
14278
  } catch {
14339
14279
  return null;
14340
14280
  }
14341
14281
  }
14342
- async function readCachedSeed(kv, key) {
14282
+ async function loadBlockInfo(reader, worldId, hash = BLOCK_INFO_HASH) {
14343
14283
  try {
14344
- const raw = await kv.get(key);
14345
- return raw === null ? null : decode2(raw);
14284
+ const bytes = await reader.read(`worlds/${worldId}/data/blobs/${hash}`);
14285
+ if (bytes === null || bytes.length === 0) return null;
14286
+ return decodeBlockInfo(bytes);
14346
14287
  } catch {
14347
14288
  return null;
14348
14289
  }
14349
14290
  }
14350
- async function writeCachedSeed(kv, key, seed, ttlSeconds) {
14351
- if (ttlSeconds <= 0) return;
14352
- try {
14353
- const options = ttlSeconds >= 60 ? { expirationTtl: ttlSeconds } : void 0;
14354
- await kv.put(key, encode(seed), options);
14355
- } catch {
14356
- }
14357
- }
14358
14291
 
14359
14292
  // src/core/maps/http.ts
14360
14293
  var MapSourceError = class extends Error {
@@ -14474,12 +14407,6 @@ async function resolveWorldId(source) {
14474
14407
  return assertWorldId(candidate);
14475
14408
  }
14476
14409
  async function loadWorldSeedFromHttp(entry, options = {}) {
14477
- const revision = entry.etag ?? entry.version;
14478
- const cacheKey = options.kv !== void 0 && revision !== void 0 ? seedCacheKey(entry.slug, revision) : void 0;
14479
- if (cacheKey !== void 0 && options.kv !== void 0) {
14480
- const cached = await readCachedSeed(options.kv, cacheKey);
14481
- if (cached !== null) return cached;
14482
- }
14483
14410
  const source = new HttpMapSource(entry.root, options);
14484
14411
  const worldId = entry.worldId ?? await resolveWorldId(source);
14485
14412
  const manifest = await source.readJson(
@@ -14488,14 +14415,13 @@ async function loadWorldSeedFromHttp(entry, options = {}) {
14488
14415
  if (manifest === null) {
14489
14416
  fail2("MAP_NO_WORLD", `worlds/${worldId}/manifest.json is missing or not JSON`);
14490
14417
  }
14491
- const seed = await resolveWorldSeed(
14418
+ return resolveWorldSeed(
14492
14419
  { worldId, manifest, reader: source },
14493
14420
  { concurrency: options.concurrency ?? DEFAULT_CONCURRENCY }
14494
- );
14495
- if (cacheKey !== void 0 && options.kv !== void 0) {
14496
- await writeCachedSeed(options.kv, cacheKey, seed, options.cacheTtlSeconds ?? 0);
14497
- }
14498
- return seed;
14421
+ ).then(async (seed) => {
14422
+ const blockInfo = await loadBlockInfo(source, worldId);
14423
+ return blockInfo === null ? seed : { ...seed, blockInfo };
14424
+ });
14499
14425
  }
14500
14426
  var MEBIBYTE = 1024 * 1024;
14501
14427
  var ZERO_PLAYER_TIMEOUT_DEFAULT_MS = 48e4;
@@ -15637,34 +15563,6 @@ function banDetails(input, at) {
15637
15563
  const expiresAt = input.expiresAt ?? (input.durationSeconds === void 0 ? void 0 : at + input.durationSeconds * 1e3);
15638
15564
  return { reason: input.reason, expiresAt, createdBy: input.createdBy };
15639
15565
  }
15640
- function createRateLimitsRepo(store, now) {
15641
- const repo = {
15642
- async increment(scope, hash, options = {}) {
15643
- const windowSeconds = Math.max(1, options.windowSeconds ?? 60);
15644
- const amount = options.amount ?? 1;
15645
- const at = now();
15646
- const current = await repo.get(scope, hash);
15647
- const windowStart = current !== void 0 && current.expiresAt > at ? current.windowStart : at;
15648
- const expiresAt = windowStart + windowSeconds * 1e3;
15649
- const next = {
15650
- count: (current !== void 0 && current.expiresAt > at ? current.count : 0) + amount,
15651
- windowStart,
15652
- expiresAt
15653
- };
15654
- const ttl = Math.max(1, Math.ceil((expiresAt - at) / 1e3));
15655
- await store.setItem(ratelimitKey(scope, hash), next, { ttl });
15656
- return next;
15657
- },
15658
- async get(scope, hash) {
15659
- const record = await store.getItem(ratelimitKey(scope, hash));
15660
- return record ?? void 0;
15661
- },
15662
- async reset(scope, hash) {
15663
- await store.removeItem(ratelimitKey(scope, hash));
15664
- }
15665
- };
15666
- return repo;
15667
- }
15668
15566
  function createSettingsRepo(store) {
15669
15567
  return {
15670
15568
  async get(key) {
@@ -15929,7 +15827,6 @@ function createRepos(store, options = {}) {
15929
15827
  accounts: createAccountsRepo(store, now),
15930
15828
  sessions: createSessionsRepo(store, now),
15931
15829
  bans: createBansRepo(store, now),
15932
- ratelimits: createRateLimitsRepo(store, now),
15933
15830
  settings: createSettingsRepo(store),
15934
15831
  mapStorage: (scope, namespace) => createMapStorageRepo(store, {
15935
15832
  scope,
@@ -16013,9 +15910,6 @@ function resolveAdminToken(env) {
16013
15910
  function resolveEnvironment(env) {
16014
15911
  return providers.environment?.(env);
16015
15912
  }
16016
- function resolveMapSeedKv(env) {
16017
- return providers.mapSeedKv?.(env);
16018
- }
16019
15913
  function resolveRooms(env) {
16020
15914
  return providers.rooms?.(env);
16021
15915
  }
@@ -16041,7 +15935,8 @@ function createSnapshot(input) {
16041
15935
  frameSkip: input.frameSkip,
16042
15936
  seed: input.seed ?? 0,
16043
15937
  engine: input.engine,
16044
- clients: input.clients.map((client) => ({ ...client }))
15938
+ clients: input.clients.map((client) => ({ ...client })),
15939
+ ...input.map !== void 0 ? { map: { ...input.map } } : {}
16045
15940
  };
16046
15941
  }
16047
15942
  function serializeSnapshot(snapshot2) {
@@ -16060,9 +15955,19 @@ function deserializeSnapshot(serialized) {
16060
15955
  frameSkip: parsed.frameSkip,
16061
15956
  seed: parsed.seed,
16062
15957
  engine: parsed.engine,
16063
- clients: parsed.clients.map((client) => ({ ...client }))
15958
+ clients: parsed.clients.map((client) => ({ ...client })),
15959
+ ...parsed.map !== void 0 && parsed.map !== null ? {
15960
+ map: {
15961
+ slug: parsed.map.slug,
15962
+ root: parsed.map.root,
15963
+ ...parsed.map.worldId !== void 0 ? { worldId: parsed.map.worldId } : {}
15964
+ }
15965
+ } : {}
16064
15966
  };
16065
15967
  }
15968
+ function snapshotMatchesMap(snapshot2, map) {
15969
+ return snapshot2 !== void 0 && snapshot2.slug === map.slug && snapshot2.root === map.root && (snapshot2.worldId ?? null) === (map.worldId ?? null);
15970
+ }
16066
15971
 
16067
15972
  // src/core/room/skin-sync.ts
16068
15973
  var ROOM_SKIN_UPDATE_PARAM = "cajonzeroSkinUpdate";
@@ -23620,9 +23525,7 @@ function parseMapBootstrap(url) {
23620
23525
  return void 0;
23621
23526
  }
23622
23527
  }
23623
- const etag = textParam(url, "cajonzeroEtag");
23624
23528
  const version = textParam(url, "cajonzeroVersion");
23625
- const cacheTtlSeconds = numberParam(url, "cajonzeroCacheTtl", 0, 31536e3);
23626
23529
  const maxDownloadBytes = numberParam(url, "cajonzeroMaxBytes", 1024, 1024 * 1024 * 1024);
23627
23530
  const concurrency = numberParam(url, "cajonzeroConcurrency", 1, 64);
23628
23531
  const requestTimeoutMs = numberParam(url, "cajonzeroTimeout", 100, 12e4);
@@ -23630,9 +23533,7 @@ function parseMapBootstrap(url) {
23630
23533
  slug,
23631
23534
  root,
23632
23535
  ...worldId !== void 0 ? { worldId } : {},
23633
- ...etag !== void 0 ? { etag } : {},
23634
23536
  ...version !== void 0 ? { version } : {},
23635
- ...cacheTtlSeconds !== void 0 ? { cacheTtlSeconds } : {},
23636
23537
  ...maxDownloadBytes !== void 0 ? { maxDownloadBytes } : {},
23637
23538
  ...concurrency !== void 0 ? { concurrency } : {},
23638
23539
  ...requestTimeoutMs !== void 0 ? { requestTimeoutMs } : {}
@@ -23679,7 +23580,6 @@ var Room = class {
23679
23580
  script;
23680
23581
  explicitScriptHost;
23681
23582
  mapLoader;
23682
- seedCacheKv;
23683
23583
  sockets;
23684
23584
  /** mudb wire codec: protocol envelope + netcode frames. */
23685
23585
  codec;
@@ -23721,6 +23621,8 @@ var Room = class {
23721
23621
  mapLoading;
23722
23622
  worldReady = false;
23723
23623
  snapshotRestored = false;
23624
+ /** One-shot guard for the "proof without sessionSecret" diagnostic. */
23625
+ guestWarningLogged = false;
23724
23626
  persistence;
23725
23627
  socketsStarted = false;
23726
23628
  perf = { minPing: 0, maxPing: 0, avgPing: 0, pingCount: 0 };
@@ -23732,6 +23634,10 @@ var Room = class {
23732
23634
  inboundIds = /* @__PURE__ */ new Map();
23733
23635
  /** Snapshot parked by `init()` until the seeded engine exists. */
23734
23636
  pendingEngineSnapshot = null;
23637
+ /** Map identity stored with the parked snapshot (absent on legacy snapshots). */
23638
+ pendingEngineMap;
23639
+ /** Identity of the map the current engine was seeded from. */
23640
+ seededMap;
23735
23641
  heartbeatBeats = 0;
23736
23642
  ticksRun = 0;
23737
23643
  lastTickError;
@@ -23759,7 +23665,6 @@ var Room = class {
23759
23665
  this.explicitScriptHost = options.scriptHost !== void 0;
23760
23666
  this.script = options.scriptHost ?? this.engine;
23761
23667
  this.mapBootstrap = options.map;
23762
- this.seedCacheKv = resolveMapSeedKv(env);
23763
23668
  this.mapLoader = options.loadMapSeed ?? ((bootstrap) => this.loadSeed(bootstrap));
23764
23669
  this.registryTtlSeconds = options.registryTtlSeconds ?? DEFAULT_SESSION_SLOT_TTL_SECONDS;
23765
23670
  const banCacheTtlMs = options.banCacheTtlMs ?? DEFAULT_BAN_CACHE_TTL_MS2;
@@ -23813,6 +23718,7 @@ var Room = class {
23813
23718
  this.tickValue = snapshot2.tick;
23814
23719
  this.frameSkip = snapshot2.frameSkip;
23815
23720
  this.pendingEngineSnapshot = snapshot2.engine;
23721
+ this.pendingEngineMap = snapshot2.map;
23816
23722
  this.snapshotRestored = true;
23817
23723
  }
23818
23724
  try {
@@ -23937,11 +23843,30 @@ var Room = class {
23937
23843
  }
23938
23844
  await this.mapLoading;
23939
23845
  }
23940
- /** Apply a snapshot parked by `init()` exactly once (seeded or not). */
23846
+ /**
23847
+ * Apply a snapshot parked by `init()` exactly once.
23848
+ *
23849
+ * A snapshot describes the world of the map it was captured from. Restoring
23850
+ * one that came from another map (or from a room that never had a map) would
23851
+ * replace the freshly seeded entities with nothing, leaving terrain but no
23852
+ * models — so it is dropped instead. Dropping it also resets the tick
23853
+ * counters, which `init()` had adopted optimistically.
23854
+ */
23941
23855
  applyPendingEngineSnapshot() {
23942
23856
  if (this.pendingEngineSnapshot === null) return;
23943
- this.engine.restore(this.pendingEngineSnapshot);
23857
+ const pending = this.pendingEngineSnapshot;
23858
+ const pendingMap = this.pendingEngineMap;
23944
23859
  this.pendingEngineSnapshot = null;
23860
+ this.pendingEngineMap = void 0;
23861
+ if (this.seededMap !== void 0) {
23862
+ if (!snapshotMatchesMap(pendingMap, this.seededMap)) {
23863
+ this.tickValue = 0;
23864
+ this.frameSkip = 0;
23865
+ clearServerNetState(this.net, 0);
23866
+ return;
23867
+ }
23868
+ }
23869
+ this.engine.restore(pending);
23945
23870
  }
23946
23871
  async loadWorld(bootstrap) {
23947
23872
  try {
@@ -23957,6 +23882,7 @@ var Room = class {
23957
23882
  const engine = factory({ worldSeed: seed });
23958
23883
  this.engine = engine;
23959
23884
  if (!this.explicitScriptHost) this.script = engine;
23885
+ this.seededMap = { slug: bootstrap.slug, root: bootstrap.root, worldId: seed.mapId };
23960
23886
  this.applyPendingEngineSnapshot();
23961
23887
  this.worldReady = true;
23962
23888
  this.logger.log(`cajonzero: seeded room "${bootstrap.slug}" from "${seed.mapId}"`);
@@ -23968,8 +23894,6 @@ var Room = class {
23968
23894
  }
23969
23895
  loadSeed(bootstrap) {
23970
23896
  return loadWorldSeedFromHttp(bootstrap, {
23971
- ...this.seedCacheKv !== void 0 ? { kv: this.seedCacheKv } : {},
23972
- cacheTtlSeconds: bootstrap.cacheTtlSeconds ?? 0,
23973
23897
  ...bootstrap.maxDownloadBytes !== void 0 ? { maxDownloadBytes: bootstrap.maxDownloadBytes } : {},
23974
23898
  ...bootstrap.concurrency !== void 0 ? { concurrency: bootstrap.concurrency } : {},
23975
23899
  ...bootstrap.requestTimeoutMs !== void 0 ? { requestTimeoutMs: bootstrap.requestTimeoutMs } : {}
@@ -24044,6 +23968,13 @@ var Room = class {
24044
23968
  this.pendingIdentities.set(sessionId, identity2);
24045
23969
  }
24046
23970
  } else {
23971
+ const proof = extractSessionProof(request.headers.get("sec-websocket-protocol"));
23972
+ if (proof !== void 0 && !this.guestWarningLogged) {
23973
+ this.guestWarningLogged = true;
23974
+ this.logger.error(
23975
+ "cajonzero: a session proof was supplied but no sessionSecret is configured; players join as guest. Pass serve({ sessionSecret: (env) => env.<SECRET> })."
23976
+ );
23977
+ }
24047
23978
  identity2 = guestIdentity(sessionId);
24048
23979
  }
24049
23980
  const ip = resolveClientIp(request);
@@ -24705,7 +24636,8 @@ var Room = class {
24705
24636
  tick: this.tickValue,
24706
24637
  frameSkip: this.frameSkip,
24707
24638
  engine: this.engine.capture(),
24708
- clients: [...this.connections.values()].map(toSnapshotClient)
24639
+ clients: [...this.connections.values()].map(toSnapshotClient),
24640
+ ...this.seededMap !== void 0 ? { map: { ...this.seededMap } } : {}
24709
24641
  });
24710
24642
  }
24711
24643
  /**
@@ -25078,6 +25010,65 @@ var PhysicsZoneIndex = class {
25078
25010
  }
25079
25011
  };
25080
25012
 
25013
+ // src/vendor/mudb/box-schema/voxel/blocks.ts
25014
+ function fillAndZero(x, count) {
25015
+ x.length = 0;
25016
+ for (let i = 0; i < count; ++i) {
25017
+ x.push(0);
25018
+ }
25019
+ }
25020
+ function unpackBlockIndex(blockInfo, outIndex) {
25021
+ let maxId = 0;
25022
+ for (let i = 0; i < blockInfo.ids.length; ++i) {
25023
+ maxId = Math.max(blockInfo.ids[i], maxId);
25024
+ }
25025
+ fillAndZero(outIndex.fluid, maxId);
25026
+ fillAndZero(outIndex.mass, maxId);
25027
+ fillAndZero(outIndex.friction, maxId);
25028
+ fillAndZero(outIndex.restitution, maxId);
25029
+ fillAndZero(outIndex.texture, 6 * maxId);
25030
+ fillAndZero(outIndex.emissive, maxId);
25031
+ fillAndZero(outIndex.velocity, 3 * maxId);
25032
+ outIndex.name.length = 0;
25033
+ outIndex.valid.length = 0;
25034
+ for (let i = 0; i < maxId; ++i) {
25035
+ outIndex.name.push("");
25036
+ outIndex.valid.push(false);
25037
+ }
25038
+ outIndex.fluidInfo.length = 0;
25039
+ outIndex.id = {};
25040
+ for (let i = 0; i < blockInfo.ids.length; ++i) {
25041
+ const id = blockInfo.ids[i];
25042
+ const name = blockInfo.names[i];
25043
+ outIndex.id[name] = id;
25044
+ outIndex.name[id] = name;
25045
+ outIndex.valid[id] = true;
25046
+ outIndex.emissive[id] = blockInfo.emissive[i];
25047
+ outIndex.restitution[id] = blockInfo.restitution[i];
25048
+ outIndex.friction[id] = blockInfo.friction[i];
25049
+ for (let j = 0; j < 6; ++j) {
25050
+ outIndex.texture[6 * id + j] = blockInfo.texture[6 * i + j] | blockInfo.animLength[6 * i + j] << 24;
25051
+ }
25052
+ for (let j = 0; j < 3; ++j) {
25053
+ outIndex.velocity[3 * id + j] = blockInfo.velocity[3 * i + j];
25054
+ }
25055
+ }
25056
+ if (!outIndex.isFluid) {
25057
+ outIndex.isFluid = new Uint8Array(4095);
25058
+ }
25059
+ for (let i = 0; i < 4095; ++i) {
25060
+ outIndex.isFluid[i] = 0;
25061
+ }
25062
+ outIndex.fluidInfo.push(0);
25063
+ for (let i = 0; i < blockInfo.fluids.length; ++i) {
25064
+ const { id, info, mass } = blockInfo.fluids[i];
25065
+ outIndex.fluid[id] = i + 1;
25066
+ outIndex.fluidInfo.push(info);
25067
+ outIndex.mass[id] = mass;
25068
+ outIndex.isFluid[id] = 1;
25069
+ }
25070
+ }
25071
+
25081
25072
  // src/vendor/mudb/box-schema/voxel/box-decode.ts
25082
25073
  function signDecode(x) {
25083
25074
  if (x & 1) {
@@ -25558,14 +25549,19 @@ function createEmptyVoxelHandles() {
25558
25549
  )
25559
25550
  };
25560
25551
  }
25561
- function buildVoxelHandles(voxels) {
25562
- const blockIndex = createSolidBlockIndex();
25552
+ function buildVoxelHandles(voxels, blockInfo) {
25553
+ const blockIndex = blockInfo === void 0 ? createSolidBlockIndex() : createBlockIndexFromInfo(blockInfo);
25563
25554
  const collision = VoxelCollisionSchema.clone(
25564
25555
  VoxelCollisionSchema.identity
25565
25556
  );
25566
25557
  intializeCollision(blockIndex, collision, voxels);
25567
25558
  return { blockIndex, collision };
25568
25559
  }
25560
+ function createBlockIndexFromInfo(blockInfo) {
25561
+ const blockIndex = BlockIndexSchema.clone(BlockIndexSchema.identity);
25562
+ unpackBlockIndex(blockInfo, blockIndex);
25563
+ return blockIndex;
25564
+ }
25569
25565
  function countSolidVoxels(voxels) {
25570
25566
  const data = voxels.data;
25571
25567
  let count = 0;
@@ -25781,7 +25777,7 @@ function createPlayEngine(options = {}) {
25781
25777
  options.worldSeed.voxels.shape,
25782
25778
  options.worldSeed.voxels.chunkBlobs
25783
25779
  ) : null;
25784
- const { blockIndex, collision } = options.voxelHandles ?? (decodedVoxels ? buildVoxelHandles(decodedVoxels) : createEmptyVoxelHandles());
25780
+ const { blockIndex, collision } = options.voxelHandles ?? (decodedVoxels ? buildVoxelHandles(decodedVoxels, options.worldSeed?.blockInfo) : createEmptyVoxelHandles());
25785
25781
  const zoneIndex = new PhysicsZoneIndex();
25786
25782
  let lastContact = ContactIndexSchema.alloc();
25787
25783
  const historyRing = /* @__PURE__ */ new Map();
@@ -26174,7 +26170,6 @@ var DEFAULT_CONFIG = {
26174
26170
  admin: { path: "/admin" },
26175
26171
  room: { tickRate: 64, emptyGraceMs: 48e4 },
26176
26172
  maps: {
26177
- cacheTtlSeconds: 86400,
26178
26173
  maxDownloadBytes: 32 * 1024 * 1024,
26179
26174
  concurrency: 8,
26180
26175
  requestTimeoutMs: 15e3
@@ -26233,13 +26228,6 @@ function resolveConfig(input) {
26233
26228
  )
26234
26229
  },
26235
26230
  maps: {
26236
- cacheTtlSeconds: integer(
26237
- maps.cacheTtlSeconds,
26238
- DEFAULT_CONFIG.maps.cacheTtlSeconds,
26239
- "maps.cacheTtlSeconds",
26240
- 0,
26241
- 31536e3
26242
- ),
26243
26231
  maxDownloadBytes: integer(
26244
26232
  maps.maxDownloadBytes,
26245
26233
  DEFAULT_CONFIG.maps.maxDownloadBytes,
@@ -26295,8 +26283,6 @@ function validationHook(code, status = 400) {
26295
26283
 
26296
26284
  // src/core/identity/account-routes.ts
26297
26285
  var SESSION_COOKIE_NAME = "cajonzero_session";
26298
- var RATE_LIMIT_MAX_ATTEMPTS = 5;
26299
- var RATE_LIMIT_WINDOW_SECONDS = 300;
26300
26286
  var USERNAME_PATTERN = /^[a-zA-Z][a-zA-Z0-9_]{2,31}$/;
26301
26287
  var MIN_PASSWORD_LENGTH = 10;
26302
26288
  var MAX_PASSWORD_LENGTH = 128;
@@ -26365,22 +26351,6 @@ function clientIp(request) {
26365
26351
  }
26366
26352
  return "unknown";
26367
26353
  }
26368
- async function enforceAttempts(c, deps, attempts) {
26369
- let retryAfter = null;
26370
- for (const attempt of attempts) {
26371
- const state = await deps.ratelimits.increment(attempt.scope, attempt.key, {
26372
- windowSeconds: RATE_LIMIT_WINDOW_SECONDS
26373
- });
26374
- if (state.count > RATE_LIMIT_MAX_ATTEMPTS) {
26375
- const seconds = Math.max(1, Math.ceil((state.expiresAt - deps.now()) / 1e3));
26376
- retryAfter = retryAfter === null ? seconds : Math.max(retryAfter, seconds);
26377
- }
26378
- }
26379
- if (retryAfter !== null) {
26380
- c.header("Retry-After", String(retryAfter));
26381
- throw new HttpError(429, "rate_limited");
26382
- }
26383
- }
26384
26354
  async function issueSession(c, deps, account, ip) {
26385
26355
  const minted = await mintSessionToken(
26386
26356
  {
@@ -26413,10 +26383,6 @@ function accountRoutes(deps) {
26413
26383
  const username = body.username;
26414
26384
  const password = body.password;
26415
26385
  const ip = clientIp(c.req.raw);
26416
- await enforceAttempts(c, deps, [
26417
- { scope: "register-ip", key: ip },
26418
- { scope: "register-account", key: username.toLowerCase() }
26419
- ]);
26420
26386
  const saltBytes = randomSalt();
26421
26387
  const passwordHash = await derivePassword(password, saltBytes);
26422
26388
  let account;
@@ -26441,11 +26407,6 @@ function accountRoutes(deps) {
26441
26407
  const username = body.username;
26442
26408
  const password = body.password;
26443
26409
  const ip = clientIp(c.req.raw);
26444
- const attempts = [{ scope: "login-ip", key: ip }];
26445
- if (username.length > 0) {
26446
- attempts.push({ scope: "login-account", key: username.toLowerCase() });
26447
- }
26448
- await enforceAttempts(c, deps, attempts);
26449
26410
  const account = username.length > 0 ? await deps.accounts.getByUsername(username) : void 0;
26450
26411
  if (account === void 0 || account.disabled) {
26451
26412
  await dummyVerify(password);
@@ -26612,12 +26573,12 @@ var STATE_CHANGING_METHODS = /* @__PURE__ */ new Set([
26612
26573
  "DELETE"
26613
26574
  ]);
26614
26575
  var ENCODER = new TextEncoder();
26615
- function encode2(text) {
26576
+ function encode(text) {
26616
26577
  return ENCODER.encode(text);
26617
26578
  }
26618
26579
  async function adminTokenMatches(provided, expected) {
26619
26580
  const [a, b] = await Promise.all([hashToken(provided), hashToken(expected)]);
26620
- return timingSafeEqual(encode2(a), encode2(b));
26581
+ return timingSafeEqual(encode(a), encode(b));
26621
26582
  }
26622
26583
  function readBearer(header) {
26623
26584
  if (header === null || header === void 0) return "";
@@ -26696,7 +26657,7 @@ function assertCsrf(c, allowedOrigins) {
26696
26657
  if (cookie === void 0 || header === void 0 || header === "") {
26697
26658
  throw new HttpError(403, "csrf");
26698
26659
  }
26699
- if (!timingSafeEqual(encode2(cookie), encode2(header))) {
26660
+ if (!timingSafeEqual(encode(cookie), encode(header))) {
26700
26661
  throw new HttpError(403, "csrf");
26701
26662
  }
26702
26663
  }
@@ -26863,7 +26824,7 @@ function toPublicMap(entry) {
26863
26824
  ...entry.worldId !== void 0 ? { worldId: entry.worldId } : {}
26864
26825
  };
26865
26826
  }
26866
- function mountMapRoutes(app, catalog, options = {}) {
26827
+ function mountMapRoutes(app, catalog) {
26867
26828
  app.get("/api/maps", (c) => {
26868
26829
  c.header("Cache-Control", "public, max-age=60");
26869
26830
  return c.json({
@@ -26879,30 +26840,10 @@ function mountMapRoutes(app, catalog, options = {}) {
26879
26840
  c.header("Cache-Control", "public, max-age=60");
26880
26841
  return c.json({ map: toPublicMap(entry) });
26881
26842
  });
26882
- app.get("/api/maps/:slug/seed", async (c) => {
26883
- const entry = findMap(catalog, c.req.param("slug"));
26884
- if (entry === void 0) {
26885
- return c.json({ error: "map_not_found" }, 404);
26886
- }
26887
- try {
26888
- const seed = await loadWorldSeedFromHttp(entry, {
26889
- kv: options.kv,
26890
- cacheTtlSeconds: options.cacheTtlSeconds,
26891
- concurrency: options.concurrency,
26892
- requestTimeoutMs: options.requestTimeoutMs,
26893
- fetch: options.fetch
26894
- });
26895
- c.header("Cache-Control", "public, max-age=300");
26896
- return c.json({ seed });
26897
- } catch (error) {
26898
- const message = error instanceof Error ? error.message : String(error);
26899
- return c.json({ error: "map_seed_failed", message }, 502);
26900
- }
26901
- });
26902
26843
  }
26903
26844
 
26904
26845
  // src/version.ts
26905
- var VERSION = "0.1.0";
26846
+ var VERSION = "0.1.1";
26906
26847
 
26907
26848
  // src/core/server/room-gateway.ts
26908
26849
  var CONTAINER_PATTERN3 = /^[A-Za-z0-9][A-Za-z0-9_-]{0,127}$/;
@@ -26975,10 +26916,8 @@ function writeMapBootstrap(url, entry, deps) {
26975
26916
  url.searchParams.set("cajonzeroMap", entry.slug);
26976
26917
  url.searchParams.set("cajonzeroRoot", entry.root);
26977
26918
  if (entry.worldId !== void 0) url.searchParams.set("cajonzeroWorld", entry.worldId);
26978
- if (entry.etag !== void 0) url.searchParams.set("cajonzeroEtag", entry.etag);
26979
26919
  if (entry.version !== void 0) url.searchParams.set("cajonzeroVersion", entry.version);
26980
26920
  const { maps, room } = deps.config;
26981
- url.searchParams.set("cajonzeroCacheTtl", String(maps.cacheTtlSeconds));
26982
26921
  url.searchParams.set("cajonzeroMaxBytes", String(maps.maxDownloadBytes));
26983
26922
  url.searchParams.set("cajonzeroConcurrency", String(maps.concurrency));
26984
26923
  url.searchParams.set("cajonzeroTimeout", String(maps.requestTimeoutMs));
@@ -27005,12 +26944,7 @@ function registerRoutes(app, deps) {
27005
26944
  })
27006
26945
  );
27007
26946
  mountRoomGateway(app, deps);
27008
- mountMapRoutes(app, deps.maps, {
27009
- kv: deps.mapSeedKv,
27010
- cacheTtlSeconds: deps.config.maps.cacheTtlSeconds,
27011
- concurrency: deps.config.maps.concurrency,
27012
- requestTimeoutMs: deps.config.maps.requestTimeoutMs
27013
- });
26947
+ mountMapRoutes(app, deps.maps);
27014
26948
  const repos = deps.repos ?? (deps.store !== void 0 ? createRepos(deps.store, { now: deps.now }) : void 0);
27015
26949
  const getSession = deps.getSession ?? (repos !== void 0 ? createCookieSessionReader({
27016
26950
  accounts: repos.accounts,
@@ -27033,7 +26967,6 @@ function registerRoutes(app, deps) {
27033
26967
  now: deps.now,
27034
26968
  accounts: repos.accounts,
27035
26969
  sessions: repos.sessions,
27036
- ratelimits: repos.ratelimits,
27037
26970
  getSession,
27038
26971
  sessionSecret: deps.sessionSecret,
27039
26972
  environment: deps.environment,
@@ -27094,7 +27027,6 @@ function createApp(options = {}) {
27094
27027
  sessionSecret: options.sessionSecret,
27095
27028
  adminToken: options.adminToken,
27096
27029
  environment: options.environment,
27097
- mapSeedKv: options.mapSeedKv,
27098
27030
  rooms: options.rooms,
27099
27031
  settingDefaults: options.settingDefaults
27100
27032
  };
@@ -27189,7 +27121,6 @@ function buildApp(env, options) {
27189
27121
  const sessionSecret = resolveSessionSecret(env);
27190
27122
  const adminToken = resolveAdminToken(env);
27191
27123
  const environment = resolveEnvironment(env);
27192
- const mapSeedKv = resolveMapSeedKv(env);
27193
27124
  const rooms = resolveRooms(env);
27194
27125
  const settingDefaults = resolveSettingDefaults(env);
27195
27126
  return createApp({
@@ -27198,7 +27129,6 @@ function buildApp(env, options) {
27198
27129
  ...sessionSecret !== void 0 ? { sessionSecret } : {},
27199
27130
  ...adminToken !== void 0 ? { adminToken } : {},
27200
27131
  ...environment !== void 0 ? { environment } : {},
27201
- ...mapSeedKv !== void 0 ? { mapSeedKv } : {},
27202
27132
  ...rooms !== void 0 ? { rooms } : {},
27203
27133
  ...settingDefaults !== void 0 ? { settingDefaults } : {}
27204
27134
  });
@@ -27216,7 +27146,6 @@ function serve(options = {}) {
27216
27146
  sessionSecret: options.sessionSecret,
27217
27147
  adminToken: options.adminToken,
27218
27148
  environment: options.environment,
27219
- mapSeedKv: options.mapSeedKv,
27220
27149
  rooms: options.rooms,
27221
27150
  settings: options.settings
27222
27151
  });