@underpostnet/cyberia 3.2.80 → 3.2.90

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.
Files changed (96) hide show
  1. package/.env.example +34 -15
  2. package/.github/workflows/cyberia-client.cd.yml +13 -1
  3. package/.github/workflows/cyberia-server.cd.yml +13 -1
  4. package/.github/workflows/docker-image.cyberia-client.ci.yml +4 -4
  5. package/.github/workflows/docker-image.cyberia-client.dev.ci.yml +4 -4
  6. package/.github/workflows/docker-image.cyberia-server.ci.yml +4 -4
  7. package/.github/workflows/docker-image.cyberia-server.dev.ci.yml +4 -4
  8. package/.github/workflows/docker-image.engine-cyberia.ci.yml +3 -3
  9. package/.github/workflows/docker-image.engine-cyberia.dev.ci.yml +3 -3
  10. package/.github/workflows/engine-cyberia.cd.yml +14 -3
  11. package/CHANGELOG.md +182 -1
  12. package/CLI-HELP.md +37 -16
  13. package/Dockerfile +1 -1
  14. package/Dockerfile.dev +1 -1
  15. package/Dockerfile.test +1 -1
  16. package/bin/cyberia.js +203 -49
  17. package/bin/deploy.js +18 -16
  18. package/bin/index.js +203 -49
  19. package/compose.env +34 -15
  20. package/deployment.yaml +1 -210
  21. package/docker-compose.yml +73 -62
  22. package/hardhat/package-lock.json +134 -126
  23. package/hardhat/package.json +2 -2
  24. package/manifests/cronjobs/dd-cron/dd-cron-backup.yaml +1 -1
  25. package/manifests/cronjobs/dd-cron/dd-cron-dns.yaml +1 -1
  26. package/manifests/deployment/dd-cyberia-development/deployment.yaml +1 -210
  27. package/manifests/deployment/dd-cyberia-development/gateway.yaml +80 -0
  28. package/manifests/deployment/dd-cyberia-development/httproute.yaml +504 -0
  29. package/manifests/deployment/dd-cyberia-development/proxy.yaml +12 -12
  30. package/manifests/deployment/dd-cyberia-development/pv-pvc.yaml +0 -82
  31. package/manifests/deployment/dd-cyberia-development/traffic-service.yaml +121 -0
  32. package/manifests/deployment/dd-default-development/deployment.yaml +2 -2
  33. package/manifests/deployment/playwright/deployment.yaml +1 -1
  34. package/manifests/mongodb/kustomization.yaml +4 -1
  35. package/manifests/mongodb/statefulset.yaml +4 -0
  36. package/manifests/mongodb/storage-class.yaml +9 -2
  37. package/nginx.conf +86 -16
  38. package/package.json +2 -2
  39. package/proxy.yaml +12 -12
  40. package/pv-pvc.yaml +0 -82
  41. package/scripts/nat-iptables.sh +10 -4
  42. package/scripts/test-monitor.sh +4 -3
  43. package/src/api/cyberia-action/cyberia-action.model.js +1 -0
  44. package/src/api/cyberia-instance/cyberia-fallback-world.js +41 -14
  45. package/src/api/cyberia-instance/cyberia-instance-map.service.js +8 -12
  46. package/src/api/cyberia-instance/cyberia-portal-connector.js +7 -5
  47. package/src/api/cyberia-instance/cyberia-random-source.js +80 -0
  48. package/src/api/cyberia-instance/cyberia-world-generator.js +4 -3
  49. package/src/api/cyberia-server-defaults/cyberia-server-defaults.js +73 -0
  50. package/src/cli/cluster.js +740 -55
  51. package/src/cli/db.js +2 -2
  52. package/src/cli/deploy.js +1679 -174
  53. package/src/cli/docker-compose.js +19 -178
  54. package/src/cli/image.js +15 -6
  55. package/src/cli/index.js +124 -35
  56. package/src/cli/ipfs.js +82 -11
  57. package/src/cli/monitor.js +1 -1
  58. package/src/cli/repository.js +1 -1
  59. package/src/cli/run.js +2161 -420
  60. package/src/cli/secrets.js +969 -0
  61. package/src/cli/ssh.js +8 -28
  62. package/src/client/components/cyberia/InstanceSelectionView.js +11 -8
  63. package/src/client/components/cyberia/SharedDefaultsCyberia.js +5 -0
  64. package/src/client/public/cyberia-docs/ACTION-SYSTEM.md +106 -39
  65. package/src/client/public/cyberia-docs/ARCHITECTURE.md +18 -0
  66. package/src/client/public/cyberia-docs/CYBERIA-CLI.md +44 -7
  67. package/src/client/public/cyberia-docs/ROADMAP.md +1 -1
  68. package/src/client/public/cyberia-docs/WHITE-PAPER.md +1 -1
  69. package/src/client-builder/client-build.js +94 -11
  70. package/src/client-builder/ssr.js +27 -73
  71. package/src/db/mongo/MongoBootstrap.js +295 -54
  72. package/src/db/mongo/MongooseDB.js +47 -32
  73. package/src/index.js +1 -1
  74. package/src/projects/cyberia/besu-genesis-generator.js +3 -2
  75. package/src/projects/cyberia/hot-reload-trigger.js +3 -3
  76. package/src/projects/cyberia/instance-data.js +42 -1
  77. package/src/runtime/cyberia-client/Dockerfile +1 -1
  78. package/src/runtime/cyberia-client/Dockerfile.dev +1 -1
  79. package/src/runtime/cyberia-server/Dockerfile +1 -1
  80. package/src/runtime/cyberia-server/Dockerfile.dev +1 -1
  81. package/src/runtime/engine-cyberia/Dockerfile +1 -1
  82. package/src/runtime/engine-cyberia/Dockerfile.dev +1 -1
  83. package/src/runtime/engine-cyberia/Dockerfile.test +1 -1
  84. package/src/runtime/engine-cyberia/compose.env +34 -15
  85. package/src/runtime/engine-cyberia/docker-compose.yml +73 -62
  86. package/src/runtime/engine-cyberia/nginx.conf +86 -16
  87. package/src/server/conf.js +1208 -70
  88. package/src/server/cri.js +70 -0
  89. package/src/server/underpost-gateway.js +1073 -0
  90. package/src/server/underpost-ingress.js +364 -0
  91. package/test/cluster-instances.test.js +435 -0
  92. package/test/deploy-node-placement.test.js +45 -0
  93. package/test/instance-traffic-plan.test.js +710 -0
  94. package/test/sops-secret-store.test.js +612 -0
  95. package/test/underpost-gateway.test.js +469 -0
  96. package/test/underpost-ingress.test.js +253 -0
@@ -19,6 +19,7 @@
19
19
  */
20
20
 
21
21
  import { connectPortals } from './cyberia-portal-connector.js';
22
+ import { withSeededRandom } from './cyberia-random-source.js';
22
23
 
23
24
  import {
24
25
  generateObstacles,
@@ -38,6 +39,7 @@ import {
38
39
  DEFAULT_PLAYER_SPAWN,
39
40
  ENTITY_TYPE_DEFAULTS,
40
41
  RESOURCE_ENTITY_TYPE_DEFAULTS,
42
+ DefaultCyberiaActions,
41
43
  DefaultSkillConfig,
42
44
  } from '../cyberia-server-defaults/cyberia-server-defaults.js';
43
45
  import {
@@ -92,6 +94,19 @@ function collectReferencedItemIds() {
92
94
  }
93
95
  }
94
96
  }
97
+ // Provider catalogs: the interact modal draws an icon for every item a
98
+ // vendor sells and every ingredient/output an assembler recipe names, so all
99
+ // of them must resolve to an ObjectLayer.
100
+ for (const action of DefaultCyberiaActions) {
101
+ for (const shopItem of action.shopItems || []) {
102
+ push(shopItem.itemId);
103
+ push(shopItem.priceItemId);
104
+ }
105
+ for (const recipe of action.craftRecipes || []) {
106
+ (recipe.ingredients || []).forEach((i) => push(i.itemId));
107
+ (recipe.outputItems || []).forEach((o) => push(o.itemId));
108
+ }
109
+ }
95
110
  return ids;
96
111
  }
97
112
 
@@ -262,20 +277,32 @@ function generateFallbackWorld(opts = {}) {
262
277
  mapCodes.push(`fallback-map-${i}`);
263
278
  }
264
279
 
265
- // Generate each map (all randomized independently).
266
- const maps = mapCodes.map((code) =>
267
- generateFallbackMap(code, colors, {
268
- gridSize,
269
- obstacleCount,
270
- foregroundCount,
271
- botCount,
272
- resourceCount,
273
- staticCount,
274
- }),
275
- );
276
-
277
- // Connect maps with portal topology using the portal connector module.
278
- const { portals, topology } = connectPortals(mapCodes, maps);
280
+ // Deterministic layout: seed the shared random source so every call
281
+ // reproduces the SAME world. The instance-map `/static` POIs and the
282
+ // `/preview` node image are built by independent requests (and survive
283
+ // restarts) — they must agree on where every entity sits, so a resource POI
284
+ // lands exactly where the preview draws that resource. The seed folds in the
285
+ // layout-affecting options so a custom count set stays internally consistent.
286
+ const seed = `cyberia-fallback-world-v1:${mapCount}:${gridSize ?? ''}:${obstacleCount ?? ''}:${
287
+ foregroundCount ?? ''
288
+ }:${botCount ?? ''}:${resourceCount ?? ''}:${staticCount ?? ''}`;
289
+
290
+ const { maps, portals, topology } = withSeededRandom(seed, () => {
291
+ // Generate each map (deterministic under the seeded source).
292
+ const genMaps = mapCodes.map((code) =>
293
+ generateFallbackMap(code, colors, {
294
+ gridSize,
295
+ obstacleCount,
296
+ foregroundCount,
297
+ botCount,
298
+ resourceCount,
299
+ staticCount,
300
+ }),
301
+ );
302
+ // Connect maps with portal topology using the portal connector module.
303
+ const { portals: p, topology: t } = connectPortals(mapCodes, genMaps);
304
+ return { maps: genMaps, portals: p, topology: t };
305
+ });
279
306
 
280
307
  // Build the instance shell (never persisted — no mongoId).
281
308
  const instance = {
@@ -31,8 +31,8 @@ import {
31
31
  DefaultCyberiaActions,
32
32
  DefaultCyberiaQuests,
33
33
  ENTITY_TYPE_DEFAULTS,
34
- RESOURCE_ENTITY_TYPE_DEFAULTS,
35
34
  } from '../cyberia-server-defaults/cyberia-server-defaults.js';
35
+ import { DefaultCyberiaItems } from '../../client/components/cyberia/SharedDefaultsCyberia.js';
36
36
 
37
37
  const logger = loggerFactory(import.meta);
38
38
 
@@ -430,22 +430,18 @@ const resolveInstanceWorld = async (instanceCode, options) => {
430
430
  const instance = await CyberiaInstance.findOne({ code: instanceCode }).lean();
431
431
 
432
432
  if (!instance) {
433
- // Fallback world: topology is regenerated (portal cells may drift from
434
- // the live simulation's world), but map codes and provider POIs come
435
- // from the deterministic canonical defaults.
436
433
  const world = generateFallbackWorld();
437
434
  const mapCodes = new Set(world.instance.cyberiaMapCodes);
438
435
 
439
- // Build synthetic object-layer metadata for the fallback world's resource
440
- // items so resource entities carry a stats-sum readout. The canonical
441
- // resource defaults (wood-1, wood-2) are not persisted to MongoDB in the
442
- // fallback path, so we compute a reasonable stats sum from the item type's
443
- // default stat block (each stat 0-10, 6 stats → ~30 average).
444
- const resourceItemIds = [
445
- ...new Set(RESOURCE_ENTITY_TYPE_DEFAULTS.flatMap((r) => [...(r.liveItemIds || []), ...(r.dropItemIds || [])])),
436
+ const fallbackItemIds = [
437
+ ...new Set(world.maps.flatMap((m) => (m.entities || []).flatMap((e) => e.objectLayerItemIds || []))),
446
438
  ];
439
+ const itemTypeById = Object.fromEntries(DefaultCyberiaItems.map((entry) => [entry.item.id, entry.item.type]));
440
+ const resolved = await resolveObjectLayerMetadata(fallbackItemIds, options);
447
441
  const objectLayerMetadata = Object.fromEntries(
448
- resourceItemIds.map((itemId) => [itemId, { statsSum: 30, type: 'resource' }]),
442
+ Object.entries(resolved).map(([itemId, meta]) => {
443
+ return [itemId, { statsSum: meta.statsSum, type: meta.type }];
444
+ }),
449
445
  );
450
446
 
451
447
  return {
@@ -20,6 +20,8 @@
20
20
  * @module src/api/cyberia-instance/cyberia-portal-connector
21
21
  */
22
22
 
23
+ import { nextRandom } from './cyberia-random-source.js';
24
+
23
25
  // ── Portal mode constants ────────────────────────────────────────────────────
24
26
 
25
27
  /**
@@ -117,7 +119,7 @@ function buildTopologyFromSubtypes(orderedCodes, portalIndex) {
117
119
 
118
120
  // Target: pick any portal on the target map for landing coordinates
119
121
  const tgtAll = portalIndex[tgtCode] || [];
120
- const tgtEnt = tgtAll.length > 0 ? tgtAll[Math.floor(Math.random() * tgtAll.length)] : null;
122
+ const tgtEnt = tgtAll.length > 0 ? tgtAll[Math.floor(nextRandom() * tgtAll.length)] : null;
121
123
 
122
124
  if (srcEnt) {
123
125
  usedInRing.add(srcEnt);
@@ -139,7 +141,7 @@ function buildTopologyFromSubtypes(orderedCodes, portalIndex) {
139
141
  if (n < 2) return srcCode;
140
142
  let code;
141
143
  do {
142
- code = orderedCodes[Math.floor(Math.random() * n)];
144
+ code = orderedCodes[Math.floor(nextRandom() * n)];
143
145
  } while (code === srcCode && n > 1);
144
146
  return code;
145
147
  };
@@ -151,13 +153,13 @@ function buildTopologyFromSubtypes(orderedCodes, portalIndex) {
151
153
  for (const srcEnt of remaining) {
152
154
  // If the entity already has an explicit subtype, honour it;
153
155
  // otherwise assign a random mode (matching fallback-world behaviour).
154
- const sub = srcEnt.portalSubtype || PORTAL_MODE_LIST[Math.floor(Math.random() * PORTAL_MODE_LIST.length)];
156
+ const sub = srcEnt.portalSubtype || PORTAL_MODE_LIST[Math.floor(nextRandom() * PORTAL_MODE_LIST.length)];
155
157
 
156
158
  switch (sub) {
157
159
  case PORTAL_MODES.INTER_PORTAL: {
158
160
  const tgtCode = otherMap(srcCode);
159
161
  const candidates = portalIndex[tgtCode] || [];
160
- const tgtEnt = candidates.length > 0 ? candidates[Math.floor(Math.random() * candidates.length)] : null;
162
+ const tgtEnt = candidates.length > 0 ? candidates[Math.floor(nextRandom() * candidates.length)] : null;
161
163
  portals.push({
162
164
  sourceMapCode: srcCode,
163
165
  sourceCellX: srcEnt.initCellX ?? 0,
@@ -198,7 +200,7 @@ function buildTopologyFromSubtypes(orderedCodes, portalIndex) {
198
200
  const candidates = allOnMap.filter(
199
201
  (e) => e !== srcEnt && (e.initCellX !== srcEnt.initCellX || e.initCellY !== srcEnt.initCellY),
200
202
  );
201
- const tgtEnt = candidates.length > 0 ? candidates[Math.floor(Math.random() * candidates.length)] : null;
203
+ const tgtEnt = candidates.length > 0 ? candidates[Math.floor(nextRandom() * candidates.length)] : null;
202
204
  portals.push({
203
205
  sourceMapCode: srcCode,
204
206
  sourceCellX: srcEnt.initCellX ?? 0,
@@ -0,0 +1,80 @@
1
+ /**
2
+ * Swappable random source for procedural world generation.
3
+ *
4
+ * The world generator and portal connector draw all randomness through
5
+ * `nextRandom()` instead of `Math.random()` directly. By default it IS
6
+ * `Math.random`, so live/editor generation stays random. The fallback world
7
+ * temporarily installs a seeded PRNG so the SAME layout is produced on every
8
+ * call — the instance-map `/static` POIs and the `/preview` image are built by
9
+ * independent HTTP requests (and survive server restarts), so they must agree
10
+ * on where every entity sits.
11
+ *
12
+ * @module src/api/cyberia-instance/cyberia-random-source.js
13
+ */
14
+
15
+ let source = Math.random;
16
+
17
+ /** Draw the next float in [0, 1). Routes through the installed source. */
18
+ const nextRandom = () => source();
19
+
20
+ /**
21
+ * mulberry32 — a small, fast, well-distributed seeded PRNG. Deterministic:
22
+ * the same seed always yields the same sequence.
23
+ * @param {number} seed 32-bit unsigned integer
24
+ * @returns {() => number}
25
+ */
26
+ function mulberry32(seed) {
27
+ let a = seed >>> 0;
28
+ return function () {
29
+ a |= 0;
30
+ a = (a + 0x6d2b79f5) | 0;
31
+ let t = Math.imul(a ^ (a >>> 15), 1 | a);
32
+ t = (t + Math.imul(t ^ (t >>> 7), 61 | t)) ^ t;
33
+ return ((t ^ (t >>> 14)) >>> 0) / 4294967296;
34
+ };
35
+ }
36
+
37
+ /** Derive a stable 32-bit seed from a string (FNV-1a). */
38
+ function hashSeed(str) {
39
+ let h = 0x811c9dc5;
40
+ for (let i = 0; i < String(str).length; i++) {
41
+ h ^= String(str).charCodeAt(i);
42
+ h = Math.imul(h, 0x01000193);
43
+ }
44
+ return h >>> 0;
45
+ }
46
+
47
+ /**
48
+ * Install a deterministic seeded source. Accepts a number or a string
49
+ * (hashed to a 32-bit seed). Call `resetRandomSource()` to restore Math.random.
50
+ * @param {number|string} seed
51
+ */
52
+ function seedRandomSource(seed) {
53
+ const s = typeof seed === 'number' ? seed >>> 0 : hashSeed(seed);
54
+ source = mulberry32(s);
55
+ }
56
+
57
+ /** Restore the default `Math.random` source. */
58
+ function resetRandomSource() {
59
+ source = Math.random;
60
+ }
61
+
62
+ /**
63
+ * Run `fn` with a deterministic seeded source, restoring the previous source
64
+ * afterward even if `fn` throws. Returns `fn`'s result.
65
+ * @template T
66
+ * @param {number|string} seed
67
+ * @param {() => T} fn
68
+ * @returns {T}
69
+ */
70
+ function withSeededRandom(seed, fn) {
71
+ const prev = source;
72
+ seedRandomSource(seed);
73
+ try {
74
+ return fn();
75
+ } finally {
76
+ source = prev;
77
+ }
78
+ }
79
+
80
+ export { nextRandom, seedRandomSource, resetRandomSource, withSeededRandom, hashSeed };
@@ -23,6 +23,7 @@ import {
23
23
  } from '../cyberia-server-defaults/cyberia-server-defaults.js';
24
24
 
25
25
  import { DefaultCyberiaItems, ITEM_TYPES } from '../../client/components/cyberia/SharedDefaultsCyberia.js';
26
+ import { nextRandom } from './cyberia-random-source.js';
26
27
 
27
28
  import { PORTAL_MODES, PORTAL_MODE_COLOR_KEY, EXTRA_PORTAL_MODES } from './cyberia-portal-connector.js';
28
29
 
@@ -51,7 +52,7 @@ const findColor = (colors, key) => colors.find((c) => c.key === key);
51
52
  * @param {number} max
52
53
  * @returns {number}
53
54
  */
54
- const randInt = (min, max) => min + Math.floor(Math.random() * (max - min + 1));
55
+ const randInt = (min, max) => min + Math.floor(nextRandom() * (max - min + 1));
55
56
 
56
57
  // ── Entity count ranges ──────────────────────────────────────────────────────
57
58
  // [min, max] — actual count is random within range on each generation call.
@@ -438,7 +439,7 @@ function generatePortalEntities(mapDims, colors, opts = {}) {
438
439
  // First portal is always inter-portal (reserved for the ring topology);
439
440
  // extra portals get a random non-ring subtype.
440
441
  const subtype =
441
- i === 0 ? PORTAL_MODES.INTER_PORTAL : EXTRA_PORTAL_MODES[Math.floor(Math.random() * EXTRA_PORTAL_MODES.length)];
442
+ i === 0 ? PORTAL_MODES.INTER_PORTAL : EXTRA_PORTAL_MODES[Math.floor(nextRandom() * EXTRA_PORTAL_MODES.length)];
442
443
  const portal = generatePortalEntity(mapDims, colors, opts.grid, subtype);
443
444
  if (portal) entities.push(portal);
444
445
  }
@@ -484,7 +485,7 @@ function generateBots(mapDims, colors, opts = {}) {
484
485
 
485
486
  // const skin = BOT_SKIN_POOL[Math.floor(Math.random() * BOT_SKIN_POOL.length)];
486
487
  const skin = 'kishins';
487
- const hasWeapon = Math.random() < BOT_WEAPON_CHANCE;
488
+ const hasWeapon = nextRandom() < BOT_WEAPON_CHANCE;
488
489
  const itemIds = hasWeapon ? [skin, 'atlas_pistol_mk2'] : [skin];
489
490
 
490
491
  entities.push({
@@ -591,6 +591,76 @@ export const DefaultCyberiaActions = [
591
591
  dialogCode: 'default-agent',
592
592
  questDialogueCodes: [{ questCode: 'bounty-quest-alpha', dialogCode: 'quest-talk-agent' }],
593
593
  },
594
+ {
595
+ // Shop capability: an action carrying shopItems is a vendor. The client
596
+ // surfaces a Shop tab for it and the simulation validates every purchase
597
+ // against this catalog (price item + quantity).
598
+ code: 'loc-fallback-map-0-18-16',
599
+ label: 'Punk',
600
+ sourceMapCode: 'fallback-map-0',
601
+ sourceCellX: 18,
602
+ sourceCellY: 16,
603
+ dialogCode: 'default-punk',
604
+ shopItems: [{ itemId: 'tim-knife', priceItemId: 'coin', priceQty: 10 }],
605
+ },
606
+ {
607
+ // Assembler capability: an action carrying craftRecipes is a fabrication
608
+ // terminal. The client surfaces an Assembly tab for it and the simulation
609
+ // validates every synthesis against these recipes (ingredients → outputs).
610
+ // Every id here is obtainable inside the fallback world — wood from the
611
+ // resource nodes, coin from field drops — so the loop is playable on a
612
+ // fresh spawn with no seeding.
613
+ code: 'loc-fallback-map-0-15-16',
614
+ label: 'Eiri',
615
+ sourceMapCode: 'fallback-map-0',
616
+ sourceCellX: 15,
617
+ sourceCellY: 16,
618
+ dialogCode: 'default-eiri',
619
+ craftRecipes: [
620
+ {
621
+ outputItems: [{ itemId: 'hatchet', qty: 1 }],
622
+ ingredients: [
623
+ { itemId: 'wood-drop-1', qty: 2 },
624
+ { itemId: 'coin', qty: 5 },
625
+ ],
626
+ craftTimeMs: 3000,
627
+ },
628
+ {
629
+ outputItems: [{ itemId: 'tim-knife', qty: 1 }],
630
+ ingredients: [
631
+ { itemId: 'wood-drop-2', qty: 1 },
632
+ { itemId: 'wood-drop-1', qty: 1 },
633
+ { itemId: 'coin', qty: 10 },
634
+ ],
635
+ craftTimeMs: 5000,
636
+ },
637
+ {
638
+ // Multi-output: one blade breaks down into three component stacks, so
639
+ // the assembly UI and the arrival flights are exercised with more than
640
+ // a single result slot. Closes the loop with Punk's shop — buy a knife,
641
+ // salvage it back into parts.
642
+ outputItems: [
643
+ { itemId: 'wood-drop-1', qty: 2 },
644
+ { itemId: 'wood-drop-2', qty: 1 },
645
+ { itemId: 'coin', qty: 5 },
646
+ ],
647
+ ingredients: [{ itemId: 'tim-knife', qty: 1 }],
648
+ craftTimeMs: 2000,
649
+ },
650
+ ],
651
+ },
652
+ {
653
+ // Storage capability: an action carrying storageSlots is a personal vault.
654
+ // The client surfaces a Storage tab whose square grid is sized from the
655
+ // capacity (25 → 5x5) and the simulation owns the contents.
656
+ code: 'loc-fallback-map-0-12-22',
657
+ label: 'Kaneki',
658
+ sourceMapCode: 'fallback-map-0',
659
+ sourceCellX: 12,
660
+ sourceCellY: 22,
661
+ dialogCode: 'default-kaneki',
662
+ storageSlots: 25,
663
+ },
594
664
  {
595
665
  code: 'loc-fallback-map-0-15-22',
596
666
  label: 'Lain',
@@ -868,6 +938,9 @@ export const ENTITY_TYPE_DEFAULTS = Object.freeze([
868
938
  { entityType: ENTITY_TYPES.bot, liveItemIds: ['wason'], deadItemIds: [DEFAULT_DEAD_ITEM_ID], behavior: 'provider' },
869
939
  { entityType: ENTITY_TYPES.bot, liveItemIds: ['alex'], deadItemIds: [DEFAULT_DEAD_ITEM_ID], behavior: 'provider' },
870
940
  { entityType: ENTITY_TYPES.bot, liveItemIds: ['agent'], deadItemIds: [DEFAULT_DEAD_ITEM_ID], behavior: 'provider' },
941
+ { entityType: ENTITY_TYPES.bot, liveItemIds: ['punk'], deadItemIds: [DEFAULT_DEAD_ITEM_ID], behavior: 'provider' },
942
+ { entityType: ENTITY_TYPES.bot, liveItemIds: ['eiri'], deadItemIds: [DEFAULT_DEAD_ITEM_ID], behavior: 'provider-static' },
943
+ { entityType: ENTITY_TYPES.bot, liveItemIds: ['kaneki'], deadItemIds: [DEFAULT_DEAD_ITEM_ID], behavior: 'provider-static' },
871
944
  {
872
945
  entityType: ENTITY_TYPES.bot,
873
946
  liveItemIds: ['lain'],