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/core.d.ts CHANGED
@@ -1,31 +1,10 @@
1
- import { M as MapEntry, W as WorldSeed } from './providers-CJgLgWh4.js';
2
- export { c as EMPTY_MAP_CATALOG, E as EnvProvider, d as MapCatalog, e as Repos, R as Room, f as RoomOptions, g as RuntimeProviders, h as SETTINGS, i as SETTING_KEYS, a as SettingDefaults, j as SettingDefinition, k as Settings, l as SettingsError, m as SettingsErrorCode, n as SettingsService, o as StorageFactory, S as StorageSource, p as Store, q as WorldSeedReader, r as configureDefaultKV, s as createMemoryStore, t as createRepos, u as createSettingsService, v as createStore, w as findMap, x as getRuntimeProviders, y as isSettingKey, z as memoryDriver, A as parseMapCatalog, B as resolveAdminToken, C as resolveEnvironment, F as resolveMapSeedKv, G as resolveRooms, H as resolveSessionSecret, I as resolveSettingDefaults, J as resolveStore, L as setRuntimeProviders, N as setStorageFactory } from './providers-CJgLgWh4.js';
3
- export { C as CajonzeroConfig, D as DEFAULT_CONFIG, G as GAME_NET_INBOUND_BASE, a as GAME_NET_OUTBOUND_BASE, I as INBOUND_LAYOUT, M as MAX_INBOUND_FRAME_BYTES, b as MapsConfig, N as NetCodec, c as NetCodecError, d as NetCodecErrorCode, e as NetCodecHandlers, f as NetCodecOptions, O as OFFICIAL_PREFIX, g as OUTBOUND_LAYOUT, P as ProtocolDirection, h as ProtocolLayout, i as ProtocolSchemaLike, R as RoomConfig, j as chainLayouts, l as layoutMessages, k as layoutProtocol, m as matchesLayout, n as messageIds, o as messageNameAt, r as readNetFrameHeader, p as readNetMessageType, q as resolveConfig } from './config-Che_6xEE.js';
1
+ import { M as MapEntry, W as WorldSeed } from './providers-BX7e4pNW.js';
2
+ export { c as EMPTY_MAP_CATALOG, E as EnvProvider, d as MapCatalog, e as Repos, R as Room, f as RoomOptions, g as RuntimeProviders, h as SETTINGS, i as SETTING_KEYS, a as SettingDefaults, j as SettingDefinition, k as Settings, l as SettingsError, m as SettingsErrorCode, n as SettingsService, o as StorageFactory, S as StorageSource, p as Store, q as WorldSeedReader, r as configureDefaultKV, s as createMemoryStore, t as createRepos, u as createSettingsService, v as createStore, w as findMap, x as getRuntimeProviders, y as isSettingKey, z as memoryDriver, A as parseMapCatalog, B as resolveAdminToken, C as resolveEnvironment, F as resolveRooms, G as resolveSessionSecret, H as resolveSettingDefaults, I as resolveStore, J as setRuntimeProviders, K as setStorageFactory } from './providers-BX7e4pNW.js';
3
+ export { C as CajonzeroConfig, D as DEFAULT_CONFIG, G as GAME_NET_INBOUND_BASE, a as GAME_NET_OUTBOUND_BASE, I as INBOUND_LAYOUT, M as MAX_INBOUND_FRAME_BYTES, b as MapsConfig, N as NetCodec, c as NetCodecError, d as NetCodecErrorCode, e as NetCodecHandlers, f as NetCodecOptions, O as OFFICIAL_PREFIX, g as OUTBOUND_LAYOUT, P as ProtocolDirection, h as ProtocolLayout, i as ProtocolSchemaLike, R as RoomConfig, j as chainLayouts, l as layoutMessages, k as layoutProtocol, m as matchesLayout, n as messageIds, o as messageNameAt, r as readNetFrameHeader, p as readNetMessageType, q as resolveConfig } from './config-BOzAyP7m.js';
4
4
  import 'unstorage';
5
5
  import 'gl-matrix';
6
6
  import 'zod';
7
7
 
8
- /**
9
- * cajonzero — resolved-seed cache.
10
- *
11
- * Reading a map straight from object storage costs one request per graph node
12
- * plus one per voxel chunk (dozens of subrequests). The resolved
13
- * {@link WorldSeed} is tiny by comparison (a few hundred KB), so it is cached in
14
- * the deployment KV the first time a map revision is loaded and read back for
15
- * every later cold start.
16
- *
17
- * The cache is keyed by **slug + revision (etag)**. Without a revision we do not
18
- * cache, because a re-uploaded map would otherwise be served stale forever.
19
- *
20
- * Worker path: `btoa`/`atob` only (no Node `Buffer`).
21
- */
22
-
23
- /** The subset of a KV namespace the cache needs (structural). */
24
- interface SeedCacheKv {
25
- get(key: string): Promise<string | null>;
26
- put(key: string, value: string, options?: unknown): Promise<void>;
27
- }
28
-
29
8
  /**
30
9
  * cajonzero — HTTP map source.
31
10
  *
@@ -35,8 +14,8 @@ interface SeedCacheKv {
35
14
  * voxel chunk blobs. Meshes/audio are never downloaded by the server — the
36
15
  * browser loads those straight from the same root.
37
16
  *
38
- * The resolved seed is cached in KV (see `seed-cache.ts`) so the dozens of
39
- * subrequests this costs happen once per map revision, not once per cold start.
17
+ * The seed is resolved on demand from object storage: map bytes stay on the
18
+ * CDN, never in the deployment KV.
40
19
  *
41
20
  * Worker path: `fetch`, `AbortController`, `TextDecoder` only.
42
21
  */
@@ -62,10 +41,6 @@ interface HttpMapOptions {
62
41
  maxDownloadBytes?: number;
63
42
  /** Parallel voxel-chunk reads. Default 8. */
64
43
  concurrency?: number;
65
- /** KV handle used for the resolved-seed cache. */
66
- kv?: SeedCacheKv;
67
- /** Seed cache TTL (seconds); `0` disables it. */
68
- cacheTtlSeconds?: number;
69
44
  }
70
45
  /**
71
46
  * A {@link WorldSeedReader} over one unpacked map root.
@@ -91,8 +66,9 @@ declare class HttpMapSource {
91
66
  /**
92
67
  * Load one map entry into a resolved {@link WorldSeed}.
93
68
  *
94
- * Order: KV cache (when the entry has a revision) → `bundle.json`/manifest →
95
- * graph + voxel blob fetches → write the seed back to KV.
69
+ * Reads `bundle.json`/manifest and the project graph straight from object
70
+ * storage. Nothing is cached server-side: map bytes belong on the CDN, not in
71
+ * the deployment KV.
96
72
  */
97
73
  declare function loadWorldSeedFromHttp(entry: MapEntry, options?: HttpMapOptions): Promise<WorldSeed>;
98
74
 
package/dist/core.js CHANGED
@@ -4989,7 +4989,6 @@ var KEY_PREFIX = {
4989
4989
  account: "account",
4990
4990
  session: "session",
4991
4991
  ban: "ban",
4992
- ratelimit: "ratelimit",
4993
4992
  settings: "settings",
4994
4993
  mapStorage: "mapstorage"
4995
4994
  };
@@ -5021,9 +5020,6 @@ function banAccountKey(subjectId) {
5021
5020
  function banIdentityKey(kind, hash) {
5022
5021
  return `ban:identity:${kind}:${seg2(hash)}`;
5023
5022
  }
5024
- function ratelimitKey(scope, hash) {
5025
- return `ratelimit:${seg2(scope)}:${seg2(hash)}`;
5026
- }
5027
5023
  function settingsKey(key) {
5028
5024
  return `settings:${seg2(key)}`;
5029
5025
  }
@@ -5066,12 +5062,6 @@ function parseKey(key) {
5066
5062
  }
5067
5063
  return void 0;
5068
5064
  }
5069
- case KEY_PREFIX.ratelimit: {
5070
- if (parts2.length !== 3) return void 0;
5071
- const scope = decode(parts2[1]);
5072
- const hash = decode(parts2[2]);
5073
- return scope === void 0 || hash === void 0 ? void 0 : { kind: "ratelimit", scope, hash };
5074
- }
5075
5065
  case KEY_PREFIX.settings: {
5076
5066
  if (parts2.length !== 2) return void 0;
5077
5067
  const settingKey = decode(parts2[1]);
@@ -5247,7 +5237,6 @@ function parseEntry(value, index) {
5247
5237
  const author = optionalString(record.author, `maps[${index}].author`);
5248
5238
  const version = optionalString(record.version, `maps[${index}].version`);
5249
5239
  const cover = optionalString(record.cover, `maps[${index}].cover`);
5250
- const etag = optionalString(record.etag, `maps[${index}].etag`);
5251
5240
  return {
5252
5241
  slug,
5253
5242
  root,
@@ -5256,7 +5245,6 @@ function parseEntry(value, index) {
5256
5245
  ...author !== void 0 ? { author } : {},
5257
5246
  ...version !== void 0 ? { version } : {},
5258
5247
  ...cover !== void 0 ? { cover } : {},
5259
- ...etag !== void 0 ? { etag } : {},
5260
5248
  ...assetRoot !== void 0 ? { assetRoot: normalizeRootUrl(assetRoot) } : {}
5261
5249
  };
5262
5250
  }
@@ -13853,79 +13841,24 @@ async function resolveWorldSeed(source, options = {}) {
13853
13841
  };
13854
13842
  }
13855
13843
 
13856
- // src/core/maps/seed-cache.ts
13857
- var SEED_CACHE_PREFIX = "map:seed";
13858
- function seedCacheKey(slug, revision) {
13859
- const safeRevision = encodeURIComponent(revision).slice(0, 160);
13860
- return `${SEED_CACHE_PREFIX}:${encodeURIComponent(slug)}:${safeRevision}`;
13861
- }
13862
- function toBase64(bytes) {
13863
- let binary = "";
13864
- const block = 32768;
13865
- for (let offset = 0; offset < bytes.length; offset += block) {
13866
- binary += String.fromCharCode(...bytes.subarray(offset, offset + block));
13867
- }
13868
- return btoa(binary);
13869
- }
13870
- function fromBase64(value) {
13871
- const binary = atob(value);
13872
- const bytes = new Uint8Array(binary.length);
13873
- for (let i = 0; i < binary.length; i += 1) {
13874
- bytes[i] = binary.charCodeAt(i);
13875
- }
13876
- return bytes;
13877
- }
13878
- function encode(seed) {
13879
- const wire = {
13880
- version: WORLD_SEED_VERSION,
13881
- seed: {
13882
- ...seed,
13883
- voxels: seed.voxels === null ? null : {
13884
- shape: seed.voxels.shape,
13885
- chunkCount: seed.voxels.chunkCount,
13886
- chunkBlobs: seed.voxels.chunkBlobs.map(toBase64),
13887
- chunkHashes: seed.voxels.chunkHashes.slice()
13888
- }
13889
- }
13890
- };
13891
- return JSON.stringify(wire);
13892
- }
13893
- function decode2(raw) {
13844
+ // src/core/maps/block-info.ts
13845
+ var BLOCK_INFO_HASH = "QmW2E9E8ipYUifsCfkMj5SULYMYbFQvP6BQxo52atXw7Zj";
13846
+ function decodeBlockInfo(bytes) {
13894
13847
  try {
13895
- const wire = JSON.parse(raw);
13896
- if (wire.version !== WORLD_SEED_VERSION || typeof wire.seed !== "object" || wire.seed === null) {
13897
- return null;
13898
- }
13899
- const { voxels, ...rest } = wire.seed;
13900
- return {
13901
- ...rest,
13902
- voxels: voxels === null ? null : {
13903
- shape: voxels.shape,
13904
- chunkCount: voxels.chunkCount,
13905
- chunkBlobs: voxels.chunkBlobs.map(fromBase64),
13906
- chunkHashes: Array.isArray(voxels.chunkHashes) ? voxels.chunkHashes : []
13907
- }
13908
- };
13848
+ return BlockInfoSchema.patch(BlockInfoSchema.identity, new MuReadStream(bytes));
13909
13849
  } catch {
13910
13850
  return null;
13911
13851
  }
13912
13852
  }
13913
- async function readCachedSeed(kv, key) {
13853
+ async function loadBlockInfo(reader, worldId, hash = BLOCK_INFO_HASH) {
13914
13854
  try {
13915
- const raw = await kv.get(key);
13916
- return raw === null ? null : decode2(raw);
13855
+ const bytes = await reader.read(`worlds/${worldId}/data/blobs/${hash}`);
13856
+ if (bytes === null || bytes.length === 0) return null;
13857
+ return decodeBlockInfo(bytes);
13917
13858
  } catch {
13918
13859
  return null;
13919
13860
  }
13920
13861
  }
13921
- async function writeCachedSeed(kv, key, seed, ttlSeconds) {
13922
- if (ttlSeconds <= 0) return;
13923
- try {
13924
- const options = ttlSeconds >= 60 ? { expirationTtl: ttlSeconds } : void 0;
13925
- await kv.put(key, encode(seed), options);
13926
- } catch {
13927
- }
13928
- }
13929
13862
 
13930
13863
  // src/core/maps/http.ts
13931
13864
  var MapSourceError = class extends Error {
@@ -14045,12 +13978,6 @@ async function resolveWorldId(source) {
14045
13978
  return assertWorldId(candidate);
14046
13979
  }
14047
13980
  async function loadWorldSeedFromHttp(entry, options = {}) {
14048
- const revision = entry.etag ?? entry.version;
14049
- const cacheKey = options.kv !== void 0 && revision !== void 0 ? seedCacheKey(entry.slug, revision) : void 0;
14050
- if (cacheKey !== void 0 && options.kv !== void 0) {
14051
- const cached = await readCachedSeed(options.kv, cacheKey);
14052
- if (cached !== null) return cached;
14053
- }
14054
13981
  const source = new HttpMapSource(entry.root, options);
14055
13982
  const worldId = entry.worldId ?? await resolveWorldId(source);
14056
13983
  const manifest = await source.readJson(
@@ -14059,14 +13986,13 @@ async function loadWorldSeedFromHttp(entry, options = {}) {
14059
13986
  if (manifest === null) {
14060
13987
  fail2("MAP_NO_WORLD", `worlds/${worldId}/manifest.json is missing or not JSON`);
14061
13988
  }
14062
- const seed = await resolveWorldSeed(
13989
+ return resolveWorldSeed(
14063
13990
  { worldId, manifest, reader: source },
14064
13991
  { concurrency: options.concurrency ?? DEFAULT_CONCURRENCY }
14065
- );
14066
- if (cacheKey !== void 0 && options.kv !== void 0) {
14067
- await writeCachedSeed(options.kv, cacheKey, seed, options.cacheTtlSeconds ?? 0);
14068
- }
14069
- return seed;
13992
+ ).then(async (seed) => {
13993
+ const blockInfo = await loadBlockInfo(source, worldId);
13994
+ return blockInfo === null ? seed : { ...seed, blockInfo };
13995
+ });
14070
13996
  }
14071
13997
  var MEBIBYTE = 1024 * 1024;
14072
13998
  var ZERO_PLAYER_TIMEOUT_DEFAULT_MS = 48e4;
@@ -15278,34 +15204,6 @@ function banDetails(input, at) {
15278
15204
  const expiresAt = input.expiresAt ?? (input.durationSeconds === void 0 ? void 0 : at + input.durationSeconds * 1e3);
15279
15205
  return { reason: input.reason, expiresAt, createdBy: input.createdBy };
15280
15206
  }
15281
- function createRateLimitsRepo(store, now) {
15282
- const repo = {
15283
- async increment(scope, hash, options = {}) {
15284
- const windowSeconds = Math.max(1, options.windowSeconds ?? 60);
15285
- const amount = options.amount ?? 1;
15286
- const at = now();
15287
- const current = await repo.get(scope, hash);
15288
- const windowStart = current !== void 0 && current.expiresAt > at ? current.windowStart : at;
15289
- const expiresAt = windowStart + windowSeconds * 1e3;
15290
- const next = {
15291
- count: (current !== void 0 && current.expiresAt > at ? current.count : 0) + amount,
15292
- windowStart,
15293
- expiresAt
15294
- };
15295
- const ttl = Math.max(1, Math.ceil((expiresAt - at) / 1e3));
15296
- await store.setItem(ratelimitKey(scope, hash), next, { ttl });
15297
- return next;
15298
- },
15299
- async get(scope, hash) {
15300
- const record = await store.getItem(ratelimitKey(scope, hash));
15301
- return record ?? void 0;
15302
- },
15303
- async reset(scope, hash) {
15304
- await store.removeItem(ratelimitKey(scope, hash));
15305
- }
15306
- };
15307
- return repo;
15308
- }
15309
15207
  function createSettingsRepo(store) {
15310
15208
  return {
15311
15209
  async get(key) {
@@ -15570,7 +15468,6 @@ function createRepos(store, options = {}) {
15570
15468
  accounts: createAccountsRepo(store, now),
15571
15469
  sessions: createSessionsRepo(store, now),
15572
15470
  bans: createBansRepo(store, now),
15573
- ratelimits: createRateLimitsRepo(store, now),
15574
15471
  settings: createSettingsRepo(store),
15575
15472
  mapStorage: (scope, namespace) => createMapStorageRepo(store, {
15576
15473
  scope,
@@ -15670,9 +15567,6 @@ function resolveAdminToken(env) {
15670
15567
  function resolveEnvironment(env) {
15671
15568
  return providers.environment?.(env);
15672
15569
  }
15673
- function resolveMapSeedKv(env) {
15674
- return providers.mapSeedKv?.(env);
15675
- }
15676
15570
  function resolveRooms(env) {
15677
15571
  return providers.rooms?.(env);
15678
15572
  }
@@ -15698,7 +15592,8 @@ function createSnapshot(input) {
15698
15592
  frameSkip: input.frameSkip,
15699
15593
  seed: input.seed ?? 0,
15700
15594
  engine: input.engine,
15701
- clients: input.clients.map((client) => ({ ...client }))
15595
+ clients: input.clients.map((client) => ({ ...client })),
15596
+ ...input.map !== void 0 ? { map: { ...input.map } } : {}
15702
15597
  };
15703
15598
  }
15704
15599
  function serializeSnapshot(snapshot) {
@@ -15717,9 +15612,19 @@ function deserializeSnapshot(serialized) {
15717
15612
  frameSkip: parsed.frameSkip,
15718
15613
  seed: parsed.seed,
15719
15614
  engine: parsed.engine,
15720
- clients: parsed.clients.map((client) => ({ ...client }))
15615
+ clients: parsed.clients.map((client) => ({ ...client })),
15616
+ ...parsed.map !== void 0 && parsed.map !== null ? {
15617
+ map: {
15618
+ slug: parsed.map.slug,
15619
+ root: parsed.map.root,
15620
+ ...parsed.map.worldId !== void 0 ? { worldId: parsed.map.worldId } : {}
15621
+ }
15622
+ } : {}
15721
15623
  };
15722
15624
  }
15625
+ function snapshotMatchesMap(snapshot, map) {
15626
+ return snapshot !== void 0 && snapshot.slug === map.slug && snapshot.root === map.root && (snapshot.worldId ?? null) === (map.worldId ?? null);
15627
+ }
15723
15628
 
15724
15629
  // src/core/room/skin-sync.ts
15725
15630
  var ROOM_SKIN_UPDATE_PARAM = "cajonzeroSkinUpdate";
@@ -16620,9 +16525,7 @@ function parseMapBootstrap(url) {
16620
16525
  return void 0;
16621
16526
  }
16622
16527
  }
16623
- const etag = textParam(url, "cajonzeroEtag");
16624
16528
  const version = textParam(url, "cajonzeroVersion");
16625
- const cacheTtlSeconds = numberParam(url, "cajonzeroCacheTtl", 0, 31536e3);
16626
16529
  const maxDownloadBytes = numberParam(url, "cajonzeroMaxBytes", 1024, 1024 * 1024 * 1024);
16627
16530
  const concurrency = numberParam(url, "cajonzeroConcurrency", 1, 64);
16628
16531
  const requestTimeoutMs = numberParam(url, "cajonzeroTimeout", 100, 12e4);
@@ -16630,9 +16533,7 @@ function parseMapBootstrap(url) {
16630
16533
  slug,
16631
16534
  root,
16632
16535
  ...worldId !== void 0 ? { worldId } : {},
16633
- ...etag !== void 0 ? { etag } : {},
16634
16536
  ...version !== void 0 ? { version } : {},
16635
- ...cacheTtlSeconds !== void 0 ? { cacheTtlSeconds } : {},
16636
16537
  ...maxDownloadBytes !== void 0 ? { maxDownloadBytes } : {},
16637
16538
  ...concurrency !== void 0 ? { concurrency } : {},
16638
16539
  ...requestTimeoutMs !== void 0 ? { requestTimeoutMs } : {}
@@ -16679,7 +16580,6 @@ var Room = class {
16679
16580
  script;
16680
16581
  explicitScriptHost;
16681
16582
  mapLoader;
16682
- seedCacheKv;
16683
16583
  sockets;
16684
16584
  /** mudb wire codec: protocol envelope + netcode frames. */
16685
16585
  codec;
@@ -16721,6 +16621,8 @@ var Room = class {
16721
16621
  mapLoading;
16722
16622
  worldReady = false;
16723
16623
  snapshotRestored = false;
16624
+ /** One-shot guard for the "proof without sessionSecret" diagnostic. */
16625
+ guestWarningLogged = false;
16724
16626
  persistence;
16725
16627
  socketsStarted = false;
16726
16628
  perf = { minPing: 0, maxPing: 0, avgPing: 0, pingCount: 0 };
@@ -16732,6 +16634,10 @@ var Room = class {
16732
16634
  inboundIds = /* @__PURE__ */ new Map();
16733
16635
  /** Snapshot parked by `init()` until the seeded engine exists. */
16734
16636
  pendingEngineSnapshot = null;
16637
+ /** Map identity stored with the parked snapshot (absent on legacy snapshots). */
16638
+ pendingEngineMap;
16639
+ /** Identity of the map the current engine was seeded from. */
16640
+ seededMap;
16735
16641
  heartbeatBeats = 0;
16736
16642
  ticksRun = 0;
16737
16643
  lastTickError;
@@ -16759,7 +16665,6 @@ var Room = class {
16759
16665
  this.explicitScriptHost = options.scriptHost !== void 0;
16760
16666
  this.script = options.scriptHost ?? this.engine;
16761
16667
  this.mapBootstrap = options.map;
16762
- this.seedCacheKv = resolveMapSeedKv(env);
16763
16668
  this.mapLoader = options.loadMapSeed ?? ((bootstrap) => this.loadSeed(bootstrap));
16764
16669
  this.registryTtlSeconds = options.registryTtlSeconds ?? DEFAULT_SESSION_SLOT_TTL_SECONDS;
16765
16670
  const banCacheTtlMs = options.banCacheTtlMs ?? DEFAULT_BAN_CACHE_TTL_MS2;
@@ -16813,6 +16718,7 @@ var Room = class {
16813
16718
  this.tickValue = snapshot.tick;
16814
16719
  this.frameSkip = snapshot.frameSkip;
16815
16720
  this.pendingEngineSnapshot = snapshot.engine;
16721
+ this.pendingEngineMap = snapshot.map;
16816
16722
  this.snapshotRestored = true;
16817
16723
  }
16818
16724
  try {
@@ -16937,11 +16843,30 @@ var Room = class {
16937
16843
  }
16938
16844
  await this.mapLoading;
16939
16845
  }
16940
- /** Apply a snapshot parked by `init()` exactly once (seeded or not). */
16846
+ /**
16847
+ * Apply a snapshot parked by `init()` exactly once.
16848
+ *
16849
+ * A snapshot describes the world of the map it was captured from. Restoring
16850
+ * one that came from another map (or from a room that never had a map) would
16851
+ * replace the freshly seeded entities with nothing, leaving terrain but no
16852
+ * models — so it is dropped instead. Dropping it also resets the tick
16853
+ * counters, which `init()` had adopted optimistically.
16854
+ */
16941
16855
  applyPendingEngineSnapshot() {
16942
16856
  if (this.pendingEngineSnapshot === null) return;
16943
- this.engine.restore(this.pendingEngineSnapshot);
16857
+ const pending = this.pendingEngineSnapshot;
16858
+ const pendingMap = this.pendingEngineMap;
16944
16859
  this.pendingEngineSnapshot = null;
16860
+ this.pendingEngineMap = void 0;
16861
+ if (this.seededMap !== void 0) {
16862
+ if (!snapshotMatchesMap(pendingMap, this.seededMap)) {
16863
+ this.tickValue = 0;
16864
+ this.frameSkip = 0;
16865
+ clearServerNetState(this.net, 0);
16866
+ return;
16867
+ }
16868
+ }
16869
+ this.engine.restore(pending);
16945
16870
  }
16946
16871
  async loadWorld(bootstrap) {
16947
16872
  try {
@@ -16957,6 +16882,7 @@ var Room = class {
16957
16882
  const engine = factory({ worldSeed: seed });
16958
16883
  this.engine = engine;
16959
16884
  if (!this.explicitScriptHost) this.script = engine;
16885
+ this.seededMap = { slug: bootstrap.slug, root: bootstrap.root, worldId: seed.mapId };
16960
16886
  this.applyPendingEngineSnapshot();
16961
16887
  this.worldReady = true;
16962
16888
  this.logger.log(`cajonzero: seeded room "${bootstrap.slug}" from "${seed.mapId}"`);
@@ -16968,8 +16894,6 @@ var Room = class {
16968
16894
  }
16969
16895
  loadSeed(bootstrap) {
16970
16896
  return loadWorldSeedFromHttp(bootstrap, {
16971
- ...this.seedCacheKv !== void 0 ? { kv: this.seedCacheKv } : {},
16972
- cacheTtlSeconds: bootstrap.cacheTtlSeconds ?? 0,
16973
16897
  ...bootstrap.maxDownloadBytes !== void 0 ? { maxDownloadBytes: bootstrap.maxDownloadBytes } : {},
16974
16898
  ...bootstrap.concurrency !== void 0 ? { concurrency: bootstrap.concurrency } : {},
16975
16899
  ...bootstrap.requestTimeoutMs !== void 0 ? { requestTimeoutMs: bootstrap.requestTimeoutMs } : {}
@@ -17044,6 +16968,13 @@ var Room = class {
17044
16968
  this.pendingIdentities.set(sessionId, identity2);
17045
16969
  }
17046
16970
  } else {
16971
+ const proof = extractSessionProof(request.headers.get("sec-websocket-protocol"));
16972
+ if (proof !== void 0 && !this.guestWarningLogged) {
16973
+ this.guestWarningLogged = true;
16974
+ this.logger.error(
16975
+ "cajonzero: a session proof was supplied but no sessionSecret is configured; players join as guest. Pass serve({ sessionSecret: (env) => env.<SECRET> })."
16976
+ );
16977
+ }
17047
16978
  identity2 = guestIdentity(sessionId);
17048
16979
  }
17049
16980
  const ip = resolveClientIp(request);
@@ -17705,7 +17636,8 @@ var Room = class {
17705
17636
  tick: this.tickValue,
17706
17637
  frameSkip: this.frameSkip,
17707
17638
  engine: this.engine.capture(),
17708
- clients: [...this.connections.values()].map(toSnapshotClient)
17639
+ clients: [...this.connections.values()].map(toSnapshotClient),
17640
+ ...this.seededMap !== void 0 ? { map: { ...this.seededMap } } : {}
17709
17641
  });
17710
17642
  }
17711
17643
  /**
@@ -17818,7 +17750,6 @@ var DEFAULT_CONFIG = {
17818
17750
  admin: { path: "/admin" },
17819
17751
  room: { tickRate: 64, emptyGraceMs: 48e4 },
17820
17752
  maps: {
17821
- cacheTtlSeconds: 86400,
17822
17753
  maxDownloadBytes: 32 * 1024 * 1024,
17823
17754
  concurrency: 8,
17824
17755
  requestTimeoutMs: 15e3
@@ -17877,13 +17808,6 @@ function resolveConfig(input) {
17877
17808
  )
17878
17809
  },
17879
17810
  maps: {
17880
- cacheTtlSeconds: integer(
17881
- maps.cacheTtlSeconds,
17882
- DEFAULT_CONFIG.maps.cacheTtlSeconds,
17883
- "maps.cacheTtlSeconds",
17884
- 0,
17885
- 31536e3
17886
- ),
17887
17811
  maxDownloadBytes: integer(
17888
17812
  maps.maxDownloadBytes,
17889
17813
  DEFAULT_CONFIG.maps.maxDownloadBytes,
@@ -17912,6 +17836,6 @@ function resolveConfig(input) {
17912
17836
  // src/core.ts
17913
17837
  var CORE_ENTRYPOINT = "cajonzero/core";
17914
17838
 
17915
- export { CORE_ENTRYPOINT, DEFAULT_CONFIG, EMPTY_MAP_CATALOG, GAME_NET_INBOUND_BASE, GAME_NET_OUTBOUND_BASE, HttpMapSource, INBOUND_LAYOUT, MAX_INBOUND_FRAME_BYTES, MapSourceError, NetCodec, NetCodecError, OFFICIAL_PREFIX, OUTBOUND_LAYOUT, Room, SETTINGS, SETTING_KEYS, SettingsError, chainLayouts, configureDefaultKV, createMemoryStore, createRepos, createSettingsService, createStore, findMap, getRuntimeProviders, isSettingKey, layoutMessages, layoutProtocol, loadWorldSeedFromHttp, matchesLayout, memoryDriver, messageIds, messageNameAt, parseMapCatalog, readNetFrameHeader, readNetMessageType, resolveAdminToken, resolveConfig, resolveEnvironment, resolveMapSeedKv, resolveRooms, resolveSessionSecret, resolveSettingDefaults, resolveStore, setRuntimeProviders, setStorageFactory };
17839
+ export { CORE_ENTRYPOINT, DEFAULT_CONFIG, EMPTY_MAP_CATALOG, GAME_NET_INBOUND_BASE, GAME_NET_OUTBOUND_BASE, HttpMapSource, INBOUND_LAYOUT, MAX_INBOUND_FRAME_BYTES, MapSourceError, NetCodec, NetCodecError, OFFICIAL_PREFIX, OUTBOUND_LAYOUT, Room, SETTINGS, SETTING_KEYS, SettingsError, chainLayouts, configureDefaultKV, createMemoryStore, createRepos, createSettingsService, createStore, findMap, getRuntimeProviders, isSettingKey, layoutMessages, layoutProtocol, loadWorldSeedFromHttp, matchesLayout, memoryDriver, messageIds, messageNameAt, parseMapCatalog, readNetFrameHeader, readNetMessageType, resolveAdminToken, resolveConfig, resolveEnvironment, resolveRooms, resolveSessionSecret, resolveSettingDefaults, resolveStore, setRuntimeProviders, setStorageFactory };
17916
17840
  //# sourceMappingURL=core.js.map
17917
17841
  //# sourceMappingURL=core.js.map
package/dist/index.d.ts CHANGED
@@ -1,10 +1,10 @@
1
1
  import { Context, Hono } from 'hono';
2
2
  import { ContentfulStatusCode } from 'hono/utils/http-status';
3
- import { b as Env, d as MapCatalog, e as Repos, O as SessionsRepo, K as KVNamespace, D as DurableObjectNamespace } from './providers-CJgLgWh4.js';
3
+ import { b as Env, d as MapCatalog, e as Repos, L as SessionsRepo, D as DurableObjectNamespace } from './providers-BX7e4pNW.js';
4
4
  import { Store, SettingDefaults } from './core.js';
5
- export { c as EMPTY_MAP_CATALOG, M as MapEntry, R as Room, w as findMap, A as parseMapCatalog } from './providers-CJgLgWh4.js';
6
- import { C as CajonzeroConfig } from './config-Che_6xEE.js';
7
- export { D as DEFAULT_CONFIG, G as GAME_NET_INBOUND_BASE, a as GAME_NET_OUTBOUND_BASE, b as MapsConfig, N as NetCodec, e as NetCodecHandlers, R as RoomConfig, q as resolveConfig } from './config-Che_6xEE.js';
5
+ export { c as EMPTY_MAP_CATALOG, M as MapEntry, R as Room, w as findMap, A as parseMapCatalog } from './providers-BX7e4pNW.js';
6
+ import { C as CajonzeroConfig } from './config-BOzAyP7m.js';
7
+ export { D as DEFAULT_CONFIG, G as GAME_NET_INBOUND_BASE, a as GAME_NET_OUTBOUND_BASE, b as MapsConfig, N as NetCodec, e as NetCodecHandlers, R as RoomConfig, q as resolveConfig } from './config-BOzAyP7m.js';
8
8
  export { DO_CLASSES, ServeOptions, WorkersEntry, WorkersModule, serve } from './serve.js';
9
9
  import 'unstorage';
10
10
  import 'gl-matrix';
@@ -18,7 +18,7 @@ import 'zod';
18
18
  * can read the version without creating a circular import.
19
19
  */
20
20
  declare const NAME = "cajonzero";
21
- declare const VERSION = "0.1.0";
21
+ declare const VERSION = "0.1.1";
22
22
 
23
23
  /** The authenticated principal carried by a login session (T11 supplies it). */
24
24
  interface SessionIdentity {
@@ -90,7 +90,6 @@ interface AppDeps {
90
90
  readonly sessionSecret?: string;
91
91
  readonly adminToken?: string;
92
92
  readonly environment?: string;
93
- readonly mapSeedKv?: KVNamespace;
94
93
  readonly rooms?: DurableObjectNamespace;
95
94
  readonly settingDefaults?: SettingDefaults;
96
95
  }
@@ -116,7 +115,6 @@ interface CreateAppOptions {
116
115
  readonly sessionSecret?: string;
117
116
  readonly adminToken?: string;
118
117
  readonly environment?: string;
119
- readonly mapSeedKv?: KVNamespace;
120
118
  readonly rooms?: DurableObjectNamespace;
121
119
  readonly settingDefaults?: SettingDefaults;
122
120
  }