cyberia 3.1.3 → 3.2.5

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 (208) hide show
  1. package/.env.example +0 -2
  2. package/.github/workflows/engine-cyberia.cd.yml +10 -8
  3. package/.github/workflows/engine-cyberia.ci.yml +12 -29
  4. package/.github/workflows/ghpkg.ci.yml +4 -4
  5. package/.github/workflows/npmpkg.ci.yml +28 -11
  6. package/.github/workflows/publish.ci.yml +21 -2
  7. package/.github/workflows/pwa-microservices-template-page.cd.yml +4 -5
  8. package/.github/workflows/pwa-microservices-template-test.ci.yml +3 -3
  9. package/.github/workflows/release.cd.yml +13 -8
  10. package/CHANGELOG.md +433 -1
  11. package/CLI-HELP.md +57 -7
  12. package/Dockerfile +4 -2
  13. package/README.md +347 -22
  14. package/bin/build.js +5 -2
  15. package/bin/cyberia.js +1789 -112
  16. package/bin/deploy.js +177 -124
  17. package/bin/file.js +3 -0
  18. package/bin/index.js +1789 -112
  19. package/conf.js +64 -8
  20. package/deployment.yaml +92 -20
  21. package/hardhat/hardhat.config.js +13 -13
  22. package/hardhat/ignition/modules/ObjectLayerToken.js +1 -1
  23. package/hardhat/package-lock.json +2554 -5859
  24. package/hardhat/package.json +13 -22
  25. package/hardhat/scripts/deployObjectLayerToken.js +1 -1
  26. package/hardhat/test/ObjectLayerToken.js +4 -2
  27. package/hardhat/types/ethers-contracts/ObjectLayerToken.ts +690 -0
  28. package/hardhat/types/ethers-contracts/common.ts +92 -0
  29. package/hardhat/types/ethers-contracts/factories/ObjectLayerToken__factory.ts +1055 -0
  30. package/hardhat/types/ethers-contracts/factories/index.ts +4 -0
  31. package/hardhat/types/ethers-contracts/hardhat.d.ts +47 -0
  32. package/hardhat/types/ethers-contracts/index.ts +6 -0
  33. package/jsdoc.dd-cyberia.json +64 -55
  34. package/jsdoc.json +64 -55
  35. package/manifests/cronjobs/dd-cron/dd-cron-backup.yaml +5 -4
  36. package/manifests/cronjobs/dd-cron/dd-cron-dns.yaml +5 -4
  37. package/manifests/deployment/dd-cyberia-development/deployment.yaml +92 -20
  38. package/manifests/deployment/dd-cyberia-development/proxy.yaml +54 -18
  39. package/manifests/deployment/dd-default-development/deployment.yaml +2 -2
  40. package/manifests/deployment/dd-test-development/deployment.yaml +88 -74
  41. package/manifests/deployment/dd-test-development/proxy.yaml +13 -4
  42. package/manifests/deployment/playwright/deployment.yaml +1 -1
  43. package/nodemon.json +1 -1
  44. package/package.json +22 -16
  45. package/proxy.yaml +54 -18
  46. package/scripts/rhel-grpc-setup.sh +56 -0
  47. package/src/api/atlas-sprite-sheet/atlas-sprite-sheet.controller.js +44 -0
  48. package/src/api/atlas-sprite-sheet/atlas-sprite-sheet.model.js +16 -0
  49. package/src/api/atlas-sprite-sheet/atlas-sprite-sheet.router.js +5 -0
  50. package/src/api/atlas-sprite-sheet/atlas-sprite-sheet.service.js +80 -7
  51. package/src/api/cyberia-dialogue/cyberia-dialogue.controller.js +93 -0
  52. package/src/api/cyberia-dialogue/cyberia-dialogue.model.js +36 -0
  53. package/src/api/cyberia-dialogue/cyberia-dialogue.router.js +29 -0
  54. package/src/api/cyberia-dialogue/cyberia-dialogue.service.js +51 -0
  55. package/src/api/cyberia-entity/cyberia-entity.controller.js +74 -0
  56. package/src/api/cyberia-entity/cyberia-entity.model.js +24 -0
  57. package/src/api/cyberia-entity/cyberia-entity.router.js +27 -0
  58. package/src/api/cyberia-entity/cyberia-entity.service.js +42 -0
  59. package/src/api/cyberia-instance/cyberia-fallback-world.js +368 -0
  60. package/src/api/cyberia-instance/cyberia-instance.controller.js +92 -0
  61. package/src/api/cyberia-instance/cyberia-instance.model.js +84 -0
  62. package/src/api/cyberia-instance/cyberia-instance.router.js +63 -0
  63. package/src/api/cyberia-instance/cyberia-instance.service.js +191 -0
  64. package/src/api/cyberia-instance/cyberia-portal-connector.js +486 -0
  65. package/src/api/cyberia-instance-conf/cyberia-instance-conf.controller.js +74 -0
  66. package/src/api/cyberia-instance-conf/cyberia-instance-conf.defaults.js +413 -0
  67. package/src/api/cyberia-instance-conf/cyberia-instance-conf.model.js +228 -0
  68. package/src/api/cyberia-instance-conf/cyberia-instance-conf.router.js +27 -0
  69. package/src/api/cyberia-instance-conf/cyberia-instance-conf.service.js +42 -0
  70. package/src/api/cyberia-map/cyberia-map.controller.js +79 -0
  71. package/src/api/cyberia-map/cyberia-map.model.js +30 -0
  72. package/src/api/cyberia-map/cyberia-map.router.js +40 -0
  73. package/src/api/cyberia-map/cyberia-map.service.js +74 -0
  74. package/src/api/file/file.ref.json +18 -0
  75. package/src/api/ipfs/ipfs.controller.js +4 -25
  76. package/src/api/ipfs/ipfs.model.js +43 -34
  77. package/src/api/ipfs/ipfs.router.js +8 -13
  78. package/src/api/ipfs/ipfs.service.js +54 -102
  79. package/src/api/object-layer/README.md +347 -22
  80. package/src/api/object-layer/object-layer.router.js +30 -0
  81. package/src/api/object-layer/object-layer.service.js +114 -31
  82. package/src/api/user/user.service.js +8 -7
  83. package/src/cli/cluster.js +7 -7
  84. package/src/cli/db.js +710 -827
  85. package/src/cli/deploy.js +151 -93
  86. package/src/cli/env.js +29 -0
  87. package/src/cli/fs.js +5 -2
  88. package/src/cli/index.js +48 -2
  89. package/src/cli/kubectl.js +211 -0
  90. package/src/cli/release.js +284 -0
  91. package/src/cli/repository.js +438 -75
  92. package/src/cli/run.js +195 -35
  93. package/src/cli/secrets.js +73 -0
  94. package/src/cli/test.js +3 -3
  95. package/src/client/Cryptokoyn.index.js +3 -4
  96. package/src/client/CyberiaPortal.index.js +3 -4
  97. package/src/client/Default.index.js +3 -4
  98. package/src/client/Itemledger.index.js +3 -4
  99. package/src/client/Underpost.index.js +3 -4
  100. package/src/client/components/core/AppStore.js +69 -0
  101. package/src/client/components/core/CalendarCore.js +2 -2
  102. package/src/client/components/core/DropDown.js +137 -17
  103. package/src/client/components/core/Keyboard.js +2 -2
  104. package/src/client/components/core/LogIn.js +2 -2
  105. package/src/client/components/core/LogOut.js +2 -2
  106. package/src/client/components/core/Modal.js +0 -1
  107. package/src/client/components/core/Panel.js +0 -1
  108. package/src/client/components/core/PanelForm.js +19 -19
  109. package/src/client/components/core/SocketIo.js +82 -29
  110. package/src/client/components/core/SocketIoHandler.js +75 -0
  111. package/src/client/components/core/Stream.js +143 -95
  112. package/src/client/components/core/Webhook.js +40 -7
  113. package/src/client/components/cryptokoyn/AppStoreCryptokoyn.js +5 -0
  114. package/src/client/components/cryptokoyn/LogInCryptokoyn.js +3 -3
  115. package/src/client/components/cryptokoyn/LogOutCryptokoyn.js +2 -2
  116. package/src/client/components/cryptokoyn/MenuCryptokoyn.js +3 -3
  117. package/src/client/components/cryptokoyn/SocketIoCryptokoyn.js +3 -51
  118. package/src/client/components/cyberia/InstanceEngineCyberia.js +700 -0
  119. package/src/client/components/cyberia/MapEngineCyberia.js +1359 -2
  120. package/src/client/components/cyberia/ObjectLayerEngineModal.js +17 -6
  121. package/src/client/components/cyberia/ObjectLayerEngineViewer.js +92 -54
  122. package/src/client/components/cyberia-portal/AppStoreCyberiaPortal.js +5 -0
  123. package/src/client/components/cyberia-portal/CommonCyberiaPortal.js +216 -30
  124. package/src/client/components/cyberia-portal/LogInCyberiaPortal.js +3 -3
  125. package/src/client/components/cyberia-portal/LogOutCyberiaPortal.js +2 -2
  126. package/src/client/components/cyberia-portal/MenuCyberiaPortal.js +40 -7
  127. package/src/client/components/cyberia-portal/RoutesCyberiaPortal.js +4 -0
  128. package/src/client/components/cyberia-portal/SocketIoCyberiaPortal.js +3 -49
  129. package/src/client/components/cyberia-portal/TranslateCyberiaPortal.js +4 -0
  130. package/src/client/components/default/AppStoreDefault.js +5 -0
  131. package/src/client/components/default/LogInDefault.js +3 -3
  132. package/src/client/components/default/LogOutDefault.js +2 -2
  133. package/src/client/components/default/MenuDefault.js +5 -5
  134. package/src/client/components/default/SocketIoDefault.js +3 -51
  135. package/src/client/components/itemledger/AppStoreItemledger.js +5 -0
  136. package/src/client/components/itemledger/LogInItemledger.js +3 -3
  137. package/src/client/components/itemledger/LogOutItemledger.js +2 -2
  138. package/src/client/components/itemledger/MenuItemledger.js +3 -3
  139. package/src/client/components/itemledger/SocketIoItemledger.js +3 -51
  140. package/src/client/components/underpost/AppStoreUnderpost.js +5 -0
  141. package/src/client/components/underpost/LogInUnderpost.js +3 -3
  142. package/src/client/components/underpost/LogOutUnderpost.js +2 -2
  143. package/src/client/components/underpost/MenuUnderpost.js +5 -5
  144. package/src/client/components/underpost/SocketIoUnderpost.js +3 -51
  145. package/src/client/services/core/core.service.js +20 -8
  146. package/src/client/services/cyberia-dialogue/cyberia-dialogue.service.js +105 -0
  147. package/src/client/services/cyberia-entity/cyberia-entity.management.js +57 -0
  148. package/src/client/services/cyberia-entity/cyberia-entity.service.js +105 -0
  149. package/src/client/services/cyberia-instance/cyberia-instance.management.js +194 -0
  150. package/src/client/services/cyberia-instance/cyberia-instance.service.js +122 -0
  151. package/src/client/services/cyberia-instance-conf/cyberia-instance-conf.service.js +105 -0
  152. package/src/client/services/cyberia-map/cyberia-map.management.js +193 -0
  153. package/src/client/services/cyberia-map/cyberia-map.service.js +126 -0
  154. package/src/client/services/instance/instance.management.js +2 -2
  155. package/src/client/services/ipfs/ipfs.service.js +3 -23
  156. package/src/client/services/object-layer/object-layer.management.js +3 -3
  157. package/src/client/services/object-layer/object-layer.service.js +21 -0
  158. package/src/client/services/user/user.management.js +2 -2
  159. package/src/client/ssr/pages/CyberiaServerMetrics.js +1 -1
  160. package/src/grpc/cyberia/OFF_CHAIN_ECONOMY.md +305 -0
  161. package/src/grpc/cyberia/README.md +326 -0
  162. package/src/grpc/cyberia/grpc-server.js +530 -0
  163. package/src/index.js +24 -1
  164. package/src/runtime/express/Dockerfile +4 -0
  165. package/src/runtime/express/Express.js +18 -1
  166. package/src/runtime/lampp/Dockerfile +13 -2
  167. package/src/runtime/lampp/Lampp.js +27 -4
  168. package/src/runtime/wp/Dockerfile +68 -0
  169. package/src/runtime/wp/Wp.js +639 -0
  170. package/src/server/auth.js +24 -1
  171. package/src/server/backup.js +37 -9
  172. package/src/server/client-build-docs.js +9 -2
  173. package/src/server/client-build.js +31 -31
  174. package/src/server/client-formatted.js +109 -57
  175. package/src/server/conf.js +24 -9
  176. package/src/server/cron.js +25 -23
  177. package/src/server/dns.js +2 -1
  178. package/src/server/ipfs-client.js +24 -1
  179. package/src/server/object-layer.js +149 -108
  180. package/src/server/peer.js +8 -0
  181. package/src/server/runtime.js +25 -1
  182. package/src/server/semantic-layer-generator-floor.js +359 -0
  183. package/src/server/semantic-layer-generator-skin.js +1294 -0
  184. package/src/server/semantic-layer-generator.js +116 -555
  185. package/src/server/start.js +2 -2
  186. package/src/ws/IoInterface.js +1 -10
  187. package/src/ws/IoServer.js +14 -33
  188. package/src/ws/core/channels/core.ws.chat.js +65 -20
  189. package/src/ws/core/channels/core.ws.mailer.js +113 -32
  190. package/src/ws/core/channels/core.ws.stream.js +90 -31
  191. package/src/ws/core/core.ws.connection.js +12 -33
  192. package/src/ws/core/core.ws.emit.js +10 -26
  193. package/src/ws/core/core.ws.server.js +25 -58
  194. package/src/ws/default/channels/default.ws.main.js +53 -12
  195. package/src/ws/default/default.ws.connection.js +26 -13
  196. package/src/ws/default/default.ws.server.js +30 -12
  197. package/src/client/components/cryptokoyn/CommonCryptokoyn.js +0 -29
  198. package/src/client/components/cryptokoyn/ElementsCryptokoyn.js +0 -38
  199. package/src/client/components/cyberia-portal/ElementsCyberiaPortal.js +0 -38
  200. package/src/client/components/default/ElementsDefault.js +0 -38
  201. package/src/client/components/itemledger/CommonItemledger.js +0 -29
  202. package/src/client/components/itemledger/ElementsItemledger.js +0 -38
  203. package/src/client/components/underpost/CommonUnderpost.js +0 -29
  204. package/src/client/components/underpost/ElementsUnderpost.js +0 -38
  205. package/src/ws/core/management/core.ws.chat.js +0 -8
  206. package/src/ws/core/management/core.ws.mailer.js +0 -16
  207. package/src/ws/core/management/core.ws.stream.js +0 -8
  208. package/src/ws/default/management/default.ws.main.js +0 -8
@@ -185,11 +185,11 @@ const ObjectLayerEngineModal = {
185
185
  return null;
186
186
  }
187
187
  },
188
- Render: async (options = { idModal: '', Elements: {} }) => {
188
+ Render: async (options = { idModal: '', appStore: {} }) => {
189
189
  // Clear all cached data at the start of each render to prevent contamination
190
190
  ObjectLayerEngineModal.clearData();
191
191
 
192
- const { Elements } = options;
192
+ const { appStore } = options;
193
193
 
194
194
  const directionCodes = ['08', '18', '02', '12', '04', '14', '06', '16'];
195
195
  const directionCodeLabels = {
@@ -352,8 +352,19 @@ const ObjectLayerEngineModal = {
352
352
  }
353
353
  }
354
354
 
355
- const cells = 26;
356
- const pixelSize = parseInt(320 / cells);
355
+ let cellsW = 26;
356
+ let cellsH = 26;
357
+ if (loadedData && loadedData.objectLayerRenderFramesId && loadedData.objectLayerRenderFramesId.frames) {
358
+ const frames = loadedData.objectLayerRenderFramesId.frames;
359
+ for (const direction of Object.keys(frames)) {
360
+ if (frames[direction] && frames[direction].length > 0 && frames[direction][0].length > 0) {
361
+ cellsH = frames[direction][0].length;
362
+ cellsW = frames[direction][0][0].length;
363
+ break;
364
+ }
365
+ }
366
+ }
367
+ const pixelSize = parseInt(320 / Math.max(cellsW, cellsH));
357
368
  const idSectionA = 'template-section-a';
358
369
  const idSectionB = 'template-section-b';
359
370
 
@@ -838,7 +849,7 @@ const ObjectLayerEngineModal = {
838
849
  ObjectLayerEngineModal.existingObjectLayerId ? '(UPDATE MODE)' : '(CREATE MODE)',
839
850
  );
840
851
 
841
- if (Elements.Data.user.main.model.user.role === 'guest') {
852
+ if (appStore.Data.user.main.model.user.role === 'guest') {
842
853
  NotificationManager.Push({
843
854
  html: 'Guests cannot save object layers. Please log in.',
844
855
  status: 'warning',
@@ -1098,7 +1109,7 @@ const ObjectLayerEngineModal = {
1098
1109
  <div class="in section-mp section-mp-border frame-editor-container">
1099
1110
  <div class="in sub-title-modal"><i class="fa-solid fa-table-cells-large"></i> Frame editor</div>
1100
1111
 
1101
- <object-layer-engine id="ole" width="${cells}" height="${cells}" pixel-size="${pixelSize}">
1112
+ <object-layer-engine id="ole" width="${cellsW}" height="${cellsH}" pixel-size="${pixelSize}">
1102
1113
  </object-layer-engine>
1103
1114
  <object-layer-png-loader id="loader" editor-selector="#ole"></object-layer-png-loader>
1104
1115
  </div>
@@ -54,14 +54,14 @@ const ObjectLayerEngineViewer = {
54
54
  return directionCodeMap[key] || null;
55
55
  },
56
56
 
57
- Render: async function ({ Elements }) {
57
+ Render: async function ({ appStore }) {
58
58
  const id = 'object-layer-engine-viewer';
59
59
 
60
60
  // Reset currentObjectId when modal is rendered to ensure Reload triggers properly
61
61
  this.Data.currentObjectId = undefined;
62
62
 
63
63
  Modal.Data[`modal-${id}`].onReloadModalListener[id] = async () => {
64
- ObjectLayerEngineViewer.Reload({ Elements });
64
+ ObjectLayerEngineViewer.Reload({ appStore });
65
65
  };
66
66
 
67
67
  // Listen for query parameter changes for smooth navigation
@@ -77,7 +77,7 @@ const ObjectLayerEngineViewer = {
77
77
 
78
78
  // Only reload if object id actually changed (normalize undefined to null for comparison)
79
79
  if (objectId !== this.Data.currentObjectId) {
80
- await this.Reload({ Elements });
80
+ await this.Reload({ appStore });
81
81
  }
82
82
  },
83
83
  });
@@ -93,7 +93,7 @@ const ObjectLayerEngineViewer = {
93
93
  `;
94
94
  },
95
95
 
96
- renderEmpty: async function ({ Elements }) {
96
+ renderEmpty: async function ({ appStore }) {
97
97
  const id = 'object-layer-engine-viewer';
98
98
  const idModal = 'modal-object-layer-engine-viewer';
99
99
 
@@ -128,13 +128,13 @@ const ObjectLayerEngineViewer = {
128
128
  htmls(
129
129
  `#${id}`,
130
130
  await ObjectLayerManagement.RenderTable({
131
- Elements,
131
+ appStore,
132
132
  idModal,
133
133
  }),
134
134
  );
135
135
  },
136
136
 
137
- loadObjectLayer: async function (objectLayerId, Elements, options = {}) {
137
+ loadObjectLayer: async function (objectLayerId, appStore, options = {}) {
138
138
  const { skipWebp = false } = options;
139
139
  const id = 'object-layer-engine-viewer';
140
140
 
@@ -181,7 +181,7 @@ const ObjectLayerEngineViewer = {
181
181
  this.Data.currentMode = 'idle';
182
182
 
183
183
  // Render the viewer UI
184
- await this.renderViewer({ Elements });
184
+ await this.renderViewer({ appStore });
185
185
 
186
186
  // Generate WebP
187
187
  if (!skipWebp) {
@@ -208,7 +208,7 @@ const ObjectLayerEngineViewer = {
208
208
  }
209
209
  },
210
210
 
211
- renderViewer: async function ({ Elements }) {
211
+ renderViewer: async function ({ appStore }) {
212
212
  const id = 'object-layer-engine-viewer';
213
213
  const { objectLayer, frameCounts } = this.Data;
214
214
 
@@ -994,7 +994,7 @@ const ObjectLayerEngineViewer = {
994
994
  );
995
995
  ThemeEvents[id]();
996
996
  // Attach event listeners
997
- this.attachEventListeners({ Elements });
997
+ this.attachEventListeners({ appStore });
998
998
 
999
999
  // If we already have a webp loaded, display it without re-generating
1000
1000
  if (this.Data.webp) {
@@ -1012,19 +1012,33 @@ const ObjectLayerEngineViewer = {
1012
1012
  const { frameCount, frameDuration, currentDirection, currentMode, numericCode } = webpMetadata;
1013
1013
 
1014
1014
  const container = s('#webp-canvas-container');
1015
- if (container) {
1016
- // Clear container
1017
- container.innerHTML = '';
1015
+ if (!container) return;
1018
1016
 
1019
- // Create and append image
1020
- const img = document.createElement('img');
1021
- img.src = webp;
1022
- img.alt = 'WebP Animation';
1023
- container.appendChild(img);
1017
+ // Remove one-time placeholder without destroying the rest of the container
1018
+ // (clearing innerHTML would also destroy #webp-loading-overlay, breaking showLoading)
1019
+ const placeholder = container.querySelector('.webp-placeholder');
1020
+ if (placeholder) placeholder.remove();
1024
1021
 
1025
- // Create and append info badge
1026
- const infoBadge = document.createElement('div');
1027
- infoBadge.className = 'webp-info-badge';
1022
+ // Reuse the existing <img> element or create one — never nuke the container
1023
+ let img = container.querySelector('img');
1024
+ if (!img) {
1025
+ img = document.createElement('img');
1026
+ img.alt = 'WebP Animation';
1027
+ // Insert before the loading overlay so the overlay stays on top
1028
+ const overlay = container.querySelector('#webp-loading-overlay');
1029
+ container.insertBefore(img, overlay || null);
1030
+ }
1031
+ img.src = webp;
1032
+
1033
+ // Update info badge in-place or create it once
1034
+ const displayArea = s('.webp-display-area');
1035
+ if (displayArea) {
1036
+ let infoBadge = displayArea.querySelector('.webp-info-badge');
1037
+ if (!infoBadge) {
1038
+ infoBadge = document.createElement('div');
1039
+ infoBadge.className = 'webp-info-badge';
1040
+ displayArea.appendChild(infoBadge);
1041
+ }
1028
1042
  infoBadge.innerHTML = html`
1029
1043
  <span class="info-label" style="margin-left: 8px;">Frames:</span>
1030
1044
  <span>${frameCount}</span><br />
@@ -1037,13 +1051,6 @@ const ObjectLayerEngineViewer = {
1037
1051
  <span class="info-label" style="margin-left: 8px;">Code:</span>
1038
1052
  <span>${numericCode}</span>
1039
1053
  `;
1040
- const displayArea = s('.webp-display-area');
1041
- if (displayArea) {
1042
- // Remove old badge if exists
1043
- const oldBadge = s('.webp-info-badge');
1044
- if (oldBadge) oldBadge.remove();
1045
- displayArea.appendChild(infoBadge);
1046
- }
1047
1054
  }
1048
1055
  },
1049
1056
 
@@ -1114,7 +1121,7 @@ const ObjectLayerEngineViewer = {
1114
1121
  }
1115
1122
  },
1116
1123
 
1117
- deleteObjectLayer: async function ({ Elements } = {}) {
1124
+ deleteObjectLayer: async function ({ appStore } = {}) {
1118
1125
  const objectLayerId = this.Data.objectLayer?._id;
1119
1126
  if (!objectLayerId) return;
1120
1127
 
@@ -1169,7 +1176,7 @@ const ObjectLayerEngineViewer = {
1169
1176
  }
1170
1177
  },
1171
1178
 
1172
- attachEventListeners: function ({ Elements }) {
1179
+ attachEventListeners: function ({ appStore }) {
1173
1180
  // Direction buttons
1174
1181
  const directionButtons = document.querySelectorAll('[data-direction]');
1175
1182
  directionButtons.forEach((btn) => {
@@ -1178,8 +1185,8 @@ const ObjectLayerEngineViewer = {
1178
1185
  const direction = e.currentTarget.getAttribute('data-direction');
1179
1186
  if (direction !== this.Data.currentDirection) {
1180
1187
  this.Data.currentDirection = direction;
1181
- await this.renderViewer({ Elements });
1182
- // attachEventListeners is already called inside renderViewer
1188
+ // Update button active states without re-rendering the full viewer (prevents flicker)
1189
+ this._updateControlsState();
1183
1190
  await this.generateWebp();
1184
1191
  }
1185
1192
  });
@@ -1193,8 +1200,8 @@ const ObjectLayerEngineViewer = {
1193
1200
  const mode = e.currentTarget.getAttribute('data-mode');
1194
1201
  if (mode !== this.Data.currentMode) {
1195
1202
  this.Data.currentMode = mode;
1196
- await this.renderViewer({ Elements });
1197
- // attachEventListeners is already called inside renderViewer
1203
+ // Update button active states without re-rendering the full viewer (prevents flicker)
1204
+ this._updateControlsState();
1198
1205
  await this.generateWebp();
1199
1206
  }
1200
1207
  });
@@ -1230,7 +1237,7 @@ const ObjectLayerEngineViewer = {
1230
1237
  // listener → Reload → renderEmpty chain which can silently
1231
1238
  // fail when the URL was already clean or currentObjectId
1232
1239
  // was already null
1233
- await this.renderEmpty({ Elements });
1240
+ await this.renderEmpty({ appStore });
1234
1241
  });
1235
1242
  }
1236
1243
 
@@ -1246,21 +1253,21 @@ const ObjectLayerEngineViewer = {
1246
1253
  const deleteBtn = s('#delete-object-layer-btn');
1247
1254
  if (deleteBtn) {
1248
1255
  deleteBtn.addEventListener('click', async () => {
1249
- await this.deleteObjectLayer({ Elements });
1256
+ await this.deleteObjectLayer({ appStore });
1250
1257
  });
1251
1258
  }
1252
1259
 
1253
1260
  // Atlas buttons
1254
1261
  if (s('#generate-atlas-btn')) {
1255
1262
  EventsUI.onClick('#generate-atlas-btn', async () => {
1256
- await this.generateAtlas({ Elements });
1263
+ await this.generateAtlas({ appStore });
1257
1264
  });
1258
1265
  }
1259
1266
 
1260
1267
  const removeAtlasBtn = s('#remove-atlas-btn');
1261
1268
  if (removeAtlasBtn) {
1262
1269
  removeAtlasBtn.addEventListener('click', async () => {
1263
- await this.removeAtlas({ Elements });
1270
+ await this.removeAtlas({ appStore });
1264
1271
  });
1265
1272
  }
1266
1273
 
@@ -1306,10 +1313,10 @@ const ObjectLayerEngineViewer = {
1306
1313
  }
1307
1314
  },
1308
1315
 
1309
- generateAtlas: async function ({ Elements } = {}) {
1316
+ generateAtlas: async function ({ appStore } = {}) {
1310
1317
  const objectLayerId = this.Data.objectLayer._id;
1311
1318
  this.Data.isGeneratingAtlas = true;
1312
- await this.renderViewer({ Elements });
1319
+ await this.renderViewer({ appStore });
1313
1320
 
1314
1321
  try {
1315
1322
  const { status, data, message } = await AtlasSpriteSheetService.generateAtlas({ id: objectLayerId });
@@ -1321,7 +1328,7 @@ const ObjectLayerEngineViewer = {
1321
1328
  });
1322
1329
  // Reset generating flag before reload so renderViewer shows updated content
1323
1330
  this.Data.isGeneratingAtlas = false;
1324
- await this.Reload({ Elements, force: true, skipWebp: true });
1331
+ await this.Reload({ appStore, force: true, skipWebp: true });
1325
1332
  return;
1326
1333
  } else {
1327
1334
  throw new Error(message || 'Failed to generate atlas');
@@ -1335,12 +1342,12 @@ const ObjectLayerEngineViewer = {
1335
1342
  } finally {
1336
1343
  if (this.Data.isGeneratingAtlas) {
1337
1344
  this.Data.isGeneratingAtlas = false;
1338
- await this.renderViewer({ Elements });
1345
+ await this.renderViewer({ appStore });
1339
1346
  }
1340
1347
  }
1341
1348
  },
1342
1349
 
1343
- removeAtlas: async function ({ Elements } = {}) {
1350
+ removeAtlas: async function ({ appStore } = {}) {
1344
1351
  const confirmResult = await Modal.RenderConfirm({
1345
1352
  id: 'remove-atlas-confirm',
1346
1353
  html: async () => html`
@@ -1356,7 +1363,7 @@ const ObjectLayerEngineViewer = {
1356
1363
 
1357
1364
  const objectLayerId = this.Data.objectLayer._id;
1358
1365
  this.Data.isGeneratingAtlas = true;
1359
- await this.renderViewer({ Elements });
1366
+ await this.renderViewer({ appStore });
1360
1367
 
1361
1368
  try {
1362
1369
  const { status, message } = await AtlasSpriteSheetService.deleteByObjectLayerId({ id: objectLayerId });
@@ -1368,7 +1375,7 @@ const ObjectLayerEngineViewer = {
1368
1375
  });
1369
1376
  // Reset generating flag before reload so renderViewer shows updated content
1370
1377
  this.Data.isGeneratingAtlas = false;
1371
- await this.Reload({ Elements, force: true, skipWebp: true });
1378
+ await this.Reload({ appStore, force: true, skipWebp: true });
1372
1379
  return;
1373
1380
  } else {
1374
1381
  throw new Error(message || 'Failed to remove atlas');
@@ -1382,7 +1389,7 @@ const ObjectLayerEngineViewer = {
1382
1389
  } finally {
1383
1390
  if (this.Data.isGeneratingAtlas) {
1384
1391
  this.Data.isGeneratingAtlas = false;
1385
- await this.renderViewer({ Elements });
1392
+ await this.renderViewer({ appStore });
1386
1393
  }
1387
1394
  }
1388
1395
  },
@@ -1462,6 +1469,41 @@ const ObjectLayerEngineViewer = {
1462
1469
  }
1463
1470
  },
1464
1471
 
1472
+ /**
1473
+ * Updates direction/mode button active states and disabled flags in-place,
1474
+ * without re-rendering the viewer. Prevents layout flicker when switching
1475
+ * direction or mode while the WebP canvas and surrounding structure stay intact.
1476
+ */
1477
+ _updateControlsState: function () {
1478
+ const { currentDirection, currentMode, frameCounts } = this.Data;
1479
+ const hasFrames = (direction, mode) => {
1480
+ const code = this.getDirectionCode(direction, mode);
1481
+ return !!(code && frameCounts && frameCounts[code] && frameCounts[code] > 0);
1482
+ };
1483
+ const getFrameCount = (direction, mode) => {
1484
+ const code = this.getDirectionCode(direction, mode);
1485
+ return code ? (frameCounts && frameCounts[code]) || 0 : 0;
1486
+ };
1487
+
1488
+ document.querySelectorAll('[data-direction]').forEach((btn) => {
1489
+ const d = btn.getAttribute('data-direction');
1490
+ btn.classList.toggle('active', d === currentDirection);
1491
+ const hasFr = hasFrames(d, currentMode);
1492
+ btn.disabled = !hasFr;
1493
+ const countEl = btn.querySelector('.frame-count');
1494
+ if (countEl) countEl.textContent = hasFr ? `(${getFrameCount(d, currentMode)})` : '';
1495
+ });
1496
+
1497
+ document.querySelectorAll('[data-mode]').forEach((btn) => {
1498
+ const m = btn.getAttribute('data-mode');
1499
+ btn.classList.toggle('active', m === currentMode);
1500
+ const hasFr = hasFrames(currentDirection, m);
1501
+ btn.disabled = !hasFr;
1502
+ const countEl = btn.querySelector('.frame-count');
1503
+ if (countEl) countEl.textContent = hasFr ? `(${getFrameCount(currentDirection, m)})` : '';
1504
+ });
1505
+ },
1506
+
1465
1507
  showLoading: function (show, message = 'Generating WebP...') {
1466
1508
  const overlay = s('#webp-loading-overlay');
1467
1509
  if (overlay) {
@@ -1477,11 +1519,7 @@ const ObjectLayerEngineViewer = {
1477
1519
  downloadBtn.disabled = show;
1478
1520
  }
1479
1521
 
1480
- // Remove old info badge if exists
1481
- const oldBadge = s('.webp-info-badge');
1482
- if (oldBadge && show) {
1483
- oldBadge.remove();
1484
- }
1522
+ // Keep existing info badge visible during loading (removes the layout-shift flicker)
1485
1523
  },
1486
1524
 
1487
1525
  downloadWebp: function () {
@@ -1528,7 +1566,7 @@ const ObjectLayerEngineViewer = {
1528
1566
  },
1529
1567
 
1530
1568
  Reload: async function (options = {}) {
1531
- const { Elements, force = false, skipWebp = false } = options;
1569
+ const { appStore, force = false, skipWebp = false } = options;
1532
1570
  const queryParams = getQueryParams();
1533
1571
  const objectId = queryParams.id || null;
1534
1572
 
@@ -1541,9 +1579,9 @@ const ObjectLayerEngineViewer = {
1541
1579
  this.Data.currentObjectId = objectId;
1542
1580
 
1543
1581
  if (objectId) {
1544
- await this.loadObjectLayer(objectId, Elements, { skipWebp });
1582
+ await this.loadObjectLayer(objectId, appStore, { skipWebp });
1545
1583
  } else {
1546
- await this.renderEmpty({ Elements });
1584
+ await this.renderEmpty({ appStore });
1547
1585
  }
1548
1586
  } else if (!objectId && (this.Data.currentObjectId === null || force)) {
1549
1587
  // Special case: if we're already in empty state but DOM might have been reset
@@ -1555,7 +1593,7 @@ const ObjectLayerEngineViewer = {
1555
1593
 
1556
1594
  if (!gridDomExists) {
1557
1595
  // DOM was reset (e.g., modal HTML reloaded), re-render the table
1558
- await this.renderEmpty({ Elements });
1596
+ await this.renderEmpty({ appStore });
1559
1597
  }
1560
1598
  }
1561
1599
  },
@@ -0,0 +1,5 @@
1
+ import { AppStore } from '../core/AppStore.js';
2
+
3
+ const AppStoreCyberiaPortal = AppStore.create();
4
+
5
+ export { AppStoreCyberiaPortal };
@@ -1,37 +1,223 @@
1
- const ModelElement = {
2
- user: () => {
3
- return {
4
- user: {
5
- _id: '',
6
- },
7
- };
8
- },
9
- };
10
-
11
- const BaseElement = () => {
12
- return {
13
- user: {
14
- main: {
15
- model: {
16
- ...ModelElement.user(),
17
- },
18
- },
19
- },
20
- chat: {},
21
- mailer: {},
22
- };
23
- };
24
-
25
- const CyberiaPortalParams = {
26
- EVENT_CALLBACK_TIME: 45,
27
- };
28
-
29
1
  const CyberiaDependencies = {
30
2
  'maxrects-packer': '^2.7.3',
31
3
  pngjs: '^7.0.0',
32
4
  jimp: '^1.6.0',
33
- sharp: '^0.32.5',
5
+ sharp: '^0.34.5',
34
6
  ethers: '~6.16.0',
35
7
  };
36
8
 
37
- export { BaseElement, ModelElement, CyberiaPortalParams, CyberiaDependencies };
9
+ const DefaultCyberiaItems = [
10
+ { item: { id: 'coin', type: 'coin' } },
11
+ // { item: { id: 'red-power', type: 'skill' } },
12
+ // { item: { id: 'heal', type: 'skill' } },
13
+ // { item: { id: 'hatchet-skill', type: 'skill' } },
14
+ // { item: { id: 'green-power', type: 'skill' } },
15
+ // { item: { id: 'blood', type: 'skill' } },
16
+ { item: { id: 'atlas_pistol_mk2', type: 'weapon' } },
17
+ { item: { id: 'atlas_pistol_mk2_bullet', type: 'skill' } },
18
+ // { item: { id: 'tim-knife', type: 'weapon' } },
19
+ { item: { id: 'hatchet', type: 'weapon' } },
20
+ { item: { id: 'wason', type: 'skin' } },
21
+ { item: { id: 'scp-2040', type: 'skin' } },
22
+ { item: { id: 'purple', type: 'skin' } },
23
+ { item: { id: 'punk', type: 'skin' } },
24
+ // { item: { id: 'marciano', type: 'skin' } },
25
+ { item: { id: 'lain', type: 'skin' } },
26
+ { item: { id: 'kaneki', type: 'skin' } },
27
+ { item: { id: 'junko', type: 'skin' } },
28
+ { item: { id: 'ghost', type: 'skin' } },
29
+ { item: { id: 'eiri', type: 'skin' } },
30
+ { item: { id: 'anon', type: 'skin' } },
31
+ { item: { id: 'alex', type: 'skin' } },
32
+ { item: { id: 'agent', type: 'skin' } },
33
+ { item: { id: 'grass', type: 'floor' } },
34
+ ];
35
+
36
+ const DefaultSkillConfig = [
37
+ // { triggerItemId: 'anon', logicEventIds: ['doppelganger'] },
38
+ {
39
+ triggerItemId: 'atlas_pistol_mk2',
40
+ logicEventIds: ['atlas_pistol_mk2_logic'],
41
+ },
42
+ { triggerItemId: 'coin', logicEventIds: ['coin_drop_or_transaction'] },
43
+ // { triggerItemId: 'purple', logicEventIds: ['doppelganger'] },
44
+ // { triggerItemId: 'atlas_pistol_mk2_bullet', logicEventIds: ['doppelganger'] },
45
+ ];
46
+
47
+ /**
48
+ * Default dialogue seeds for every non-commented entry in DefaultCyberiaItems.
49
+ * Each item maps to an array of dialogue records following the CyberiaDialogue
50
+ * model schema: { itemId, order, speaker, text, mood }.
51
+ *
52
+ * Used by the seed/migration script to populate the database on first boot.
53
+ * The C client fetches these at runtime via GET /api/cyberia-dialogue?...
54
+ */
55
+ const DefaultCyberiaDialogues = [
56
+ {
57
+ itemId: 'coin',
58
+ order: 0,
59
+ speaker: 'Coin',
60
+ text: 'A standard unit of exchange in the cyberia network.',
61
+ mood: 'neutral',
62
+ },
63
+ {
64
+ itemId: 'atlas_pistol_mk2',
65
+ order: 0,
66
+ speaker: 'Atlas Pistol MK2',
67
+ text: 'Military-grade sidearm. Fires energy projectiles.',
68
+ mood: 'neutral',
69
+ },
70
+ {
71
+ itemId: 'atlas_pistol_mk2_bullet',
72
+ order: 0,
73
+ speaker: 'MK2 Bullet',
74
+ text: 'High-velocity energy round. Dissipates on impact.',
75
+ mood: 'neutral',
76
+ },
77
+ {
78
+ itemId: 'hatchet',
79
+ order: 0,
80
+ speaker: 'Hatchet',
81
+ text: 'A crude but reliable melee tool. Good for close quarters.',
82
+ mood: 'neutral',
83
+ },
84
+ {
85
+ itemId: 'wason',
86
+ order: 0,
87
+ speaker: 'Wason',
88
+ text: 'They say I am just a wandering merchant... but I have seen things.',
89
+ mood: 'neutral',
90
+ },
91
+ {
92
+ itemId: 'wason',
93
+ order: 1,
94
+ speaker: 'Wason',
95
+ text: 'The network was not always like this. There was a time before the portals.',
96
+ mood: 'sad',
97
+ },
98
+ {
99
+ itemId: 'scp-2040',
100
+ order: 0,
101
+ speaker: 'SCP-2040',
102
+ text: 'CONTAINMENT PROTOCOL ACTIVE. Do not make direct eye contact.',
103
+ mood: 'angry',
104
+ },
105
+ {
106
+ itemId: 'scp-2040',
107
+ order: 1,
108
+ speaker: 'SCP-2040',
109
+ text: 'I remember everything. Every iteration. Every reset.',
110
+ mood: 'sad',
111
+ },
112
+ {
113
+ itemId: 'purple',
114
+ order: 0,
115
+ speaker: 'Purple',
116
+ text: 'The void between nodes is not empty — it is alive.',
117
+ mood: 'neutral',
118
+ },
119
+ {
120
+ itemId: 'punk',
121
+ order: 0,
122
+ speaker: 'Punk',
123
+ text: 'Rules are just code someone else wrote. I write my own.',
124
+ mood: 'happy',
125
+ },
126
+ {
127
+ itemId: 'lain',
128
+ order: 0,
129
+ speaker: 'Lain',
130
+ text: 'No matter where you go, everyone is connected.',
131
+ mood: 'neutral',
132
+ },
133
+ {
134
+ itemId: 'lain',
135
+ order: 1,
136
+ speaker: 'Lain',
137
+ text: 'If you are not remembered, then you never existed.',
138
+ mood: 'sad',
139
+ },
140
+ {
141
+ itemId: 'lain',
142
+ order: 2,
143
+ speaker: 'Lain',
144
+ text: 'The wired is not a separate world. It is layered over this one.',
145
+ mood: 'neutral',
146
+ },
147
+ {
148
+ itemId: 'kaneki',
149
+ order: 0,
150
+ speaker: 'Kaneki',
151
+ text: 'I am not the protagonist of a novel. I am just... me.',
152
+ mood: 'sad',
153
+ },
154
+ { itemId: 'kaneki', order: 1, speaker: 'Kaneki', text: 'What is 1000 minus 7?', mood: 'angry' },
155
+ { itemId: 'junko', order: 0, speaker: 'Junko', text: 'Despair is the seed from which hope blooms!', mood: 'happy' },
156
+ {
157
+ itemId: 'junko',
158
+ order: 1,
159
+ speaker: 'Junko',
160
+ text: 'How boring... nothing ever surprises me anymore.',
161
+ mood: 'sad',
162
+ },
163
+ { itemId: 'ghost', order: 0, speaker: 'Ghost', text: '...', mood: 'neutral' },
164
+ {
165
+ itemId: 'eiri',
166
+ order: 0,
167
+ speaker: 'Eiri',
168
+ text: 'I am the god of the wired. I designed the protocol.',
169
+ mood: 'neutral',
170
+ },
171
+ {
172
+ itemId: 'eiri',
173
+ order: 1,
174
+ speaker: 'Eiri',
175
+ text: 'Flesh is just hardware. Consciousness is the only software that matters.',
176
+ mood: 'neutral',
177
+ },
178
+ { itemId: 'anon', order: 0, speaker: '???', text: 'You should not be here. Turn back.', mood: 'angry' },
179
+ {
180
+ itemId: 'anon',
181
+ order: 1,
182
+ speaker: '???',
183
+ text: 'Or stay. It does not matter. Nothing leaves this place.',
184
+ mood: 'neutral',
185
+ },
186
+ {
187
+ itemId: 'alex',
188
+ order: 0,
189
+ speaker: 'Alex',
190
+ text: 'I have been mapping the portal network. Something does not add up.',
191
+ mood: 'neutral',
192
+ },
193
+ {
194
+ itemId: 'alex',
195
+ order: 1,
196
+ speaker: 'Alex',
197
+ text: 'There are nodes that exist in the registry but have no physical anchor.',
198
+ mood: 'neutral',
199
+ },
200
+ {
201
+ itemId: 'agent',
202
+ order: 0,
203
+ speaker: 'Agent',
204
+ text: 'Civilian, this area is restricted. State your business.',
205
+ mood: 'neutral',
206
+ },
207
+ {
208
+ itemId: 'agent',
209
+ order: 1,
210
+ speaker: 'Agent',
211
+ text: 'Hmm. Proceed, but know that you are being watched.',
212
+ mood: 'neutral',
213
+ },
214
+ {
215
+ itemId: 'grass',
216
+ order: 0,
217
+ speaker: 'Grass',
218
+ text: 'A patch of synthetic grass. It sways gently despite no wind.',
219
+ mood: 'neutral',
220
+ },
221
+ ];
222
+
223
+ export { CyberiaDependencies, DefaultCyberiaItems, DefaultSkillConfig, DefaultCyberiaDialogues };
@@ -1,19 +1,19 @@
1
1
  import { Auth } from '../core/Auth.js';
2
2
  import { LogIn } from '../core/LogIn.js';
3
3
  import { s } from '../core/VanillaJs.js';
4
- import { ElementsCyberiaPortal } from './ElementsCyberiaPortal.js';
4
+ import { AppStoreCyberiaPortal } from './AppStoreCyberiaPortal.js';
5
5
  import { ObjectLayerManagement } from '../../services/object-layer/object-layer.management.js';
6
6
 
7
7
  const LogInCyberiaPortal = async function () {
8
8
  LogIn.Event['LogInCyberiaPortal'] = async (options) => {
9
9
  const { token, user } = options;
10
10
 
11
- ElementsCyberiaPortal.Data.user.main.model.user = user;
11
+ AppStoreCyberiaPortal.Data.user.main.model.user = user;
12
12
 
13
13
  await ObjectLayerManagement.Reload('viewer');
14
14
  };
15
15
  const { user } = await Auth.sessionIn();
16
- ElementsCyberiaPortal.Data.user.main.model.user = user;
16
+ AppStoreCyberiaPortal.Data.user.main.model.user = user;
17
17
  };
18
18
 
19
19
  export { LogInCyberiaPortal };