cyberia 3.2.12 → 3.2.70

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 (194) hide show
  1. package/.env.example +127 -68
  2. package/.github/workflows/cyberia-client.cd.yml +40 -0
  3. package/.github/workflows/cyberia-server.cd.yml +40 -0
  4. package/.github/workflows/docker-image.cyberia-client.ci.yml +49 -0
  5. package/.github/workflows/docker-image.cyberia-client.dev.ci.yml +48 -0
  6. package/.github/workflows/docker-image.cyberia-server.ci.yml +69 -0
  7. package/.github/workflows/docker-image.cyberia-server.dev.ci.yml +69 -0
  8. package/.github/workflows/docker-image.engine-cyberia.ci.yml +52 -0
  9. package/.github/workflows/docker-image.engine-cyberia.dev.ci.yml +52 -0
  10. package/.github/workflows/engine-cyberia.cd.yml +34 -24
  11. package/.github/workflows/engine-cyberia.ci.yml +27 -5
  12. package/.github/workflows/ghpkg.ci.yml +89 -1
  13. package/.github/workflows/gitlab.ci.yml +1 -1
  14. package/.github/workflows/hardhat.ci.yml +1 -1
  15. package/.github/workflows/npmpkg.ci.yml +19 -12
  16. package/.github/workflows/publish.ci.yml +2 -2
  17. package/.github/workflows/publish.cyberia.ci.yml +5 -16
  18. package/.github/workflows/pwa-microservices-template-page.cd.yml +1 -8
  19. package/.github/workflows/pwa-microservices-template-test.ci.yml +1 -1
  20. package/CHANGELOG.md +451 -1
  21. package/CLI-HELP.md +1040 -1130
  22. package/Dockerfile +141 -43
  23. package/Dockerfile.dev +143 -0
  24. package/Dockerfile.test +165 -0
  25. package/README.md +1 -1
  26. package/baremetal/commission-workflows.json +1 -0
  27. package/bin/build.js +128 -136
  28. package/bin/build.template.js +25 -179
  29. package/bin/cyberia.js +1089 -188
  30. package/bin/deploy.js +6 -3
  31. package/bin/index.js +1089 -188
  32. package/bump.config.js +1 -0
  33. package/compose.env +131 -0
  34. package/conf.js +19 -1
  35. package/deployment.yaml +74 -2
  36. package/docker-compose.yml +316 -0
  37. package/hardhat/hardhat.config.js +2 -2
  38. package/hardhat/package-lock.json +620 -2041
  39. package/hardhat/package.json +7 -5
  40. package/hardhat/scripts/deployObjectLayerToken.js +18 -18
  41. package/hardhat/test/ObjectLayerToken.js +378 -274
  42. package/ipfs/configure-ipfs.sh +13 -0
  43. package/manifests/cronjobs/dd-cron/dd-cron-backup.yaml +2 -2
  44. package/manifests/cronjobs/dd-cron/dd-cron-dns.yaml +1 -1
  45. package/manifests/deployment/dd-cyberia-development/deployment.yaml +74 -2
  46. package/manifests/deployment/dd-cyberia-development/grpc-service.yaml +17 -0
  47. package/manifests/deployment/dd-cyberia-development/pv-pvc.yaml +32 -0
  48. package/manifests/deployment/dd-default-development/deployment.yaml +2 -2
  49. package/mongodb/entrypoint.sh +76 -0
  50. package/nginx.conf +87 -0
  51. package/package.json +34 -22
  52. package/pv-pvc.yaml +32 -0
  53. package/scripts/disk-clean.sh +85 -60
  54. package/scripts/kubeadm-node-setup.sh +317 -0
  55. package/scripts/link-local-underpost-cli.sh +6 -0
  56. package/scripts/rocky-kickstart.sh +877 -185
  57. package/scripts/rpmfusion-ffmpeg-setup.sh +26 -50
  58. package/scripts/test-monitor.sh +248 -0
  59. package/src/api/atlas-sprite-sheet/atlas-sprite-sheet.router.js +43 -7
  60. package/src/api/atlas-sprite-sheet/atlas-sprite-sheet.service.js +17 -25
  61. package/src/api/cyberia-action/cyberia-action.controller.js +19 -0
  62. package/src/api/cyberia-action/cyberia-action.model.js +21 -29
  63. package/src/api/cyberia-action/cyberia-action.router.js +42 -7
  64. package/src/api/cyberia-action/cyberia-action.service.js +20 -4
  65. package/src/api/cyberia-client-hints/cyberia-client-hints.model.js +58 -57
  66. package/src/api/cyberia-dialogue/cyberia-dialogue.router.js +37 -6
  67. package/src/api/cyberia-dialogue/cyberia-dialogue.service.js +8 -2
  68. package/src/api/cyberia-entity/cyberia-entity.router.js +39 -7
  69. package/src/api/cyberia-entity-type-default/cyberia-entity-type-default.controller.js +74 -0
  70. package/src/api/cyberia-entity-type-default/cyberia-entity-type-default.model.js +67 -0
  71. package/src/api/cyberia-entity-type-default/cyberia-entity-type-default.router.js +63 -0
  72. package/src/api/cyberia-entity-type-default/cyberia-entity-type-default.service.js +46 -0
  73. package/src/api/cyberia-instance/cyberia-fallback-world.js +62 -10
  74. package/src/api/cyberia-instance/cyberia-instance.model.js +32 -2
  75. package/src/api/cyberia-instance/cyberia-instance.router.js +6 -6
  76. package/src/api/cyberia-instance/cyberia-world-generator.js +116 -3
  77. package/src/api/cyberia-instance-conf/cyberia-instance-conf.model.js +3 -0
  78. package/src/api/cyberia-instance-conf/cyberia-instance-conf.router.js +39 -7
  79. package/src/api/cyberia-map/cyberia-map.router.js +21 -6
  80. package/src/api/cyberia-quest/cyberia-quest.controller.js +38 -0
  81. package/src/api/cyberia-quest/cyberia-quest.router.js +47 -7
  82. package/src/api/cyberia-quest/cyberia-quest.service.js +59 -4
  83. package/src/api/cyberia-saga/cyberia-saga.controller.js +74 -0
  84. package/src/api/cyberia-saga/cyberia-saga.model.js +59 -0
  85. package/src/api/cyberia-saga/cyberia-saga.router.js +63 -0
  86. package/src/api/cyberia-saga/cyberia-saga.service.js +42 -0
  87. package/src/api/cyberia-server-defaults/cyberia-server-defaults.js +551 -122
  88. package/src/api/cyberia-skill/cyberia-skill.controller.js +74 -0
  89. package/src/api/cyberia-skill/cyberia-skill.model.js +50 -0
  90. package/src/api/cyberia-skill/cyberia-skill.router.js +63 -0
  91. package/src/api/cyberia-skill/cyberia-skill.service.js +42 -0
  92. package/src/api/ipfs/ipfs.service.js +28 -15
  93. package/src/api/object-layer/object-layer.router.js +25 -15
  94. package/src/api/object-layer/object-layer.service.js +15 -20
  95. package/src/api/object-layer-render-frames/object-layer-render-frames.router.js +39 -7
  96. package/src/cli/baremetal.js +717 -16
  97. package/src/cli/cluster.js +80 -5
  98. package/src/cli/deploy.js +326 -292
  99. package/src/cli/docker-compose.js +648 -0
  100. package/src/cli/env.js +12 -6
  101. package/src/cli/fs.js +75 -30
  102. package/src/cli/image.js +187 -55
  103. package/src/cli/index.js +157 -6
  104. package/src/cli/kickstart.js +142 -20
  105. package/src/cli/monitor.js +387 -6
  106. package/src/cli/release.js +72 -15
  107. package/src/cli/repository.js +339 -40
  108. package/src/cli/run.js +678 -186
  109. package/src/cli/secrets.js +82 -48
  110. package/src/cli/ssh.js +234 -0
  111. package/src/cli/static.js +2 -2
  112. package/src/client/components/core/PanelForm.js +44 -44
  113. package/src/client/components/cyberia/ActionEngineCyberia.js +1867 -0
  114. package/src/client/components/cyberia/EntityEngineCyberia.js +585 -0
  115. package/src/client/components/cyberia/InstanceEngineCyberia.js +219 -13
  116. package/src/client/components/cyberia/MapEngineCyberia.js +154 -71
  117. package/src/client/components/cyberia/ObjectLayerEngineModal.js +40 -63
  118. package/src/client/components/cyberia/ObjectLayerEngineViewer.js +34 -20
  119. package/src/client/components/cyberia/SharedDefaultsCyberia.js +195 -4
  120. package/src/client/components/cyberia-portal/AppShellCyberiaPortal.js +66 -0
  121. package/src/client/components/cyberia-portal/RouterCyberiaPortal.js +8 -0
  122. package/src/client/components/cyberia-portal/TranslateCyberiaPortal.js +8 -0
  123. package/src/client/public/cyberia-docs/ACTION-SYSTEM.md +92 -20
  124. package/src/client/public/cyberia-docs/ARCHITECTURE.md +272 -50
  125. package/src/client/public/cyberia-docs/CYBERIA-CLI.md +3 -3
  126. package/src/client/public/cyberia-docs/CYBERIA-CLIENT.md +39 -22
  127. package/src/client/public/cyberia-docs/CYBERIA-LORE.md +88 -0
  128. package/src/client/public/cyberia-docs/CYBERIA-SAGA.md +394 -0
  129. package/src/client/public/cyberia-docs/CYBERIA-SERVER.md +28 -12
  130. package/src/client/public/cyberia-docs/CYBERIA.md +1 -1
  131. package/src/client/public/cyberia-docs/QUEST-SYSTEM.md +27 -5
  132. package/src/client/public/cyberia-docs/ROADMAP.md +1 -1
  133. package/src/client/public/cyberia-docs/WHITE-PAPER.md +1 -1
  134. package/src/client/services/cyberia-entity-type-default/cyberia-entity-type-default.service.js +99 -0
  135. package/src/client/services/cyberia-instance/cyberia-instance.management.js +2 -2
  136. package/src/client/services/cyberia-map/cyberia-map.management.js +2 -2
  137. package/src/client/services/cyberia-saga/cyberia-saga.service.js +99 -0
  138. package/src/client/services/cyberia-skill/cyberia-skill.service.js +99 -0
  139. package/src/client/services/object-layer/object-layer.management.js +6 -6
  140. package/src/{server → client-builder}/client-build-docs.js +15 -5
  141. package/src/{server → client-builder}/client-build-live.js +3 -3
  142. package/src/{server → client-builder}/client-build.js +25 -22
  143. package/src/{server → client-builder}/client-dev-server.js +3 -3
  144. package/src/{server → client-builder}/client-icons.js +2 -2
  145. package/src/{server → client-builder}/ssr.js +5 -5
  146. package/src/client.build.js +1 -3
  147. package/src/client.dev.js +1 -1
  148. package/src/db/mongo/MongoBootstrap.js +12 -12
  149. package/src/db/mongo/MongooseDB.js +2 -1
  150. package/src/grpc/cyberia/grpc-server.js +255 -70
  151. package/src/index.js +12 -1
  152. package/src/mailer/EmailRender.js +1 -1
  153. package/src/{server → projects/cyberia}/atlas-sprite-sheet-generator.js +2 -2
  154. package/src/{server → projects/cyberia}/besu-genesis-generator.js +3 -3
  155. package/src/projects/cyberia/catalog-cyberia.js +81 -0
  156. package/src/projects/cyberia/gemini-client.js +175 -0
  157. package/src/projects/cyberia/generate-saga.js +2107 -0
  158. package/src/{server → projects/cyberia}/ipfs-client.js +6 -4
  159. package/src/{server → projects/cyberia}/object-layer.js +12 -108
  160. package/src/{server → projects/cyberia}/semantic-layer-generator-floor.js +1 -1
  161. package/src/{server → projects/cyberia}/semantic-layer-generator-resource.js +1 -1
  162. package/src/{server → projects/cyberia}/semantic-layer-generator-skin.js +1 -1
  163. package/src/{server → projects/cyberia}/semantic-layer-generator.js +2 -2
  164. package/src/{server → projects/cyberia}/shape-generator.js +2 -2
  165. package/src/projects/underpost/catalog-underpost.js +60 -0
  166. package/src/runtime/cyberia-client/Dockerfile +31 -83
  167. package/src/runtime/cyberia-client/Dockerfile.dev +23 -30
  168. package/src/runtime/cyberia-server/Dockerfile +23 -42
  169. package/src/runtime/cyberia-server/Dockerfile.dev +20 -35
  170. package/src/runtime/engine-cyberia/Dockerfile +143 -0
  171. package/src/runtime/engine-cyberia/Dockerfile.dev +143 -0
  172. package/src/runtime/engine-cyberia/Dockerfile.test +165 -0
  173. package/src/runtime/engine-cyberia/compose.env +131 -0
  174. package/src/runtime/engine-cyberia/docker-compose.yml +316 -0
  175. package/src/runtime/engine-cyberia/ipfs/configure-ipfs.sh +13 -0
  176. package/src/runtime/engine-cyberia/mongodb/entrypoint.sh +76 -0
  177. package/src/runtime/engine-cyberia/nginx.conf +87 -0
  178. package/src/runtime/express/Express.js +2 -2
  179. package/src/runtime/nginx/Nginx.js +250 -0
  180. package/src/runtime/wp/Dockerfile +3 -3
  181. package/src/server/catalog.js +72 -0
  182. package/src/server/conf.js +415 -60
  183. package/src/server/runtime-status.js +252 -0
  184. package/src/server/start.js +42 -11
  185. package/src/server.js +6 -2
  186. package/test/cyberia-instance-conf-defaults.test.js +140 -0
  187. package/test/deploy-monitor.test.js +267 -0
  188. package/test/shape-generator.test.js +7 -1
  189. package/typedoc.dd-cyberia.json +3 -1
  190. package/typedoc.json +3 -1
  191. package/manifests/deployment/dd-test-development/deployment.yaml +0 -256
  192. package/manifests/deployment/dd-test-development/proxy.yaml +0 -102
  193. /package/src/client/ssr/{Render.js → RootDocument.js} +0 -0
  194. /package/src/{server → client-builder}/client-formatted.js +0 -0
@@ -6,7 +6,7 @@ import { EventsUI } from '../core/EventsUI.js';
6
6
  import { Translate } from '../core/Translate.js';
7
7
  import { s, append, hexToRgbA } from '../core/VanillaJs.js';
8
8
  import { getProxyPath, getQueryParams, setPath, setQueryParams, RouterEvents } from '../core/Router.js';
9
- import { s4 } from '../core/CommonJs.js';
9
+ import { s4, commonModeratorGuard } from '../core/CommonJs.js';
10
10
  import { Input } from '../core/Input.js';
11
11
  import { ToggleSwitch } from '../core/ToggleSwitch.js';
12
12
  import { ObjectLayerService } from '../../services/object-layer/object-layer.service.js';
@@ -16,6 +16,13 @@ import { Modal } from '../core/Modal.js';
16
16
  import { LoadingAnimation } from '../core/LoadingAnimation.js';
17
17
  import { DefaultManagement } from '../../services/default/default.management.js';
18
18
  import * as _ from '../cyberia/ObjectLayerEngine.js';
19
+ import {
20
+ ITEM_TYPES,
21
+ OBJECT_LAYER_DIRECTION_CODES,
22
+ OBJECT_LAYER_DIRECTION_LABELS,
23
+ getKeyframeDirectionsByCode,
24
+ STAT_TYPES,
25
+ } from './SharedDefaultsCyberia.js';
19
26
  import '../core/ColorPaletteElement.js';
20
27
 
21
28
  const CANVAS_BEHAVIOR_ICON = 'fa-solid fa-shapes';
@@ -614,8 +621,7 @@ class ObjectLayerEngineModal {
614
621
  ole.clear();
615
622
  }
616
623
 
617
- const directionCodes = ['08', '18', '02', '12', '04', '14', '06', '16'];
618
- for (const directionCode of directionCodes) {
624
+ for (const directionCode of OBJECT_LAYER_DIRECTION_CODES) {
619
625
  const framesContainer = s(`.frames-${directionCode}`);
620
626
  if (framesContainer) {
621
627
  framesContainer.innerHTML = '';
@@ -637,8 +643,7 @@ class ObjectLayerEngineModal {
637
643
  if (activableCheckbox) activableCheckbox.checked = false;
638
644
 
639
645
  // Clear stat inputs with correct IDs
640
- const statTypes = Object.keys(ObjectLayerEngineModal.statDescriptions);
641
- for (const stat of statTypes) {
646
+ for (const stat of STAT_TYPES) {
642
647
  const statInput = getRenderedInputNode(`ol-input-item-stats-${stat}`);
643
648
  if (statInput) statInput.value = '0';
644
649
  }
@@ -713,20 +718,16 @@ class ObjectLayerEngineModal {
713
718
  ObjectLayerEngineModal.clearData();
714
719
 
715
720
  const { appStore } = options;
721
+ const canMutate = commonModeratorGuard(appStore?.Data?.user?.main?.model?.user?.role || 'guest');
722
+
723
+ const directionCodes = OBJECT_LAYER_DIRECTION_CODES;
724
+ const directionCodeLabels = OBJECT_LAYER_DIRECTION_LABELS;
725
+ const statTypes = STAT_TYPES;
726
+
727
+ // Canonical authorable item types (includes `static`). A per-render copy so a
728
+ // loaded layer's custom type can be appended without mutating the shared enum.
729
+ const itemTypes = Object.values(ITEM_TYPES);
716
730
 
717
- const directionCodes = ['08', '18', '02', '12', '04', '14', '06', '16'];
718
- const directionCodeLabels = {
719
- '08': 'Down Idle',
720
- 18: 'Down Walk',
721
- '02': 'Up Idle',
722
- 12: 'Up Walk',
723
- '04': 'Left Idle',
724
- 14: 'Left Walk',
725
- '06': 'Right Idle',
726
- 16: 'Right Walk',
727
- };
728
- const itemTypes = ['skin', 'weapon', 'armor', 'artifact', 'floor', 'resource', 'obstacle', 'foreground', 'portal'];
729
- const statTypes = ['effect', 'resistance', 'agility', 'range', 'intelligence', 'utility'];
730
731
  const distortionDropdownId = 'ol-dropdown-distortion-type';
731
732
  const distortionApplyBtnClass = 'ol-btn-apply-distortion';
732
733
  const distortionStatusClass = 'ol-distortion-status';
@@ -1004,8 +1005,8 @@ class ObjectLayerEngineModal {
1004
1005
  }
1005
1006
  }
1006
1007
 
1007
- let cellsW = 26;
1008
- let cellsH = 26;
1008
+ let cellsW = 16;
1009
+ let cellsH = 16;
1009
1010
  if (loadedData && loadedData.objectLayerRenderFramesId && loadedData.objectLayerRenderFramesId.frames) {
1010
1011
  const frames = loadedData.objectLayerRenderFramesId.frames;
1011
1012
  for (const direction of Object.keys(frames)) {
@@ -1016,7 +1017,8 @@ class ObjectLayerEngineModal {
1016
1017
  }
1017
1018
  }
1018
1019
  }
1019
- const pixelSize = parseInt(320 / Math.max(cellsW, cellsH));
1020
+ // const pixelSize = parseInt(320 / Math.max(cellsW, cellsH));
1021
+ const pixelSize = 30;
1020
1022
  const idSectionA = 'template-section-a';
1021
1023
  const idSectionB = 'template-section-b';
1022
1024
  const colorPaletteClass = 'ol-color-palette';
@@ -1427,10 +1429,12 @@ class ObjectLayerEngineModal {
1427
1429
 
1428
1430
  console.log(`Loading frames for direction code: ${currentDirectionCode}, directions:`, directions);
1429
1431
 
1430
- // Check if frames exist for any direction mapped to this direction code
1431
- const { frames } = loadedData.objectLayerRenderFramesId;
1432
+ // Check if frames exist for any direction mapped to this direction code.
1433
+ // Object layers with an empty render (no render-frames doc yet) load
1434
+ // with zero frames so the user can author them from scratch.
1435
+ const frames = loadedData.objectLayerRenderFramesId?.frames;
1432
1436
  for (const direction of directions) {
1433
- if (frames[direction] && frames[direction].length > 0) {
1437
+ if (frames && frames[direction] && frames[direction].length > 0) {
1434
1438
  // Track this direction code as having original data
1435
1439
  if (!ObjectLayerEngineModal.originalDirectionCodes.includes(currentDirectionCode)) {
1436
1440
  ObjectLayerEngineModal.originalDirectionCodes.push(currentDirectionCode);
@@ -1814,9 +1818,9 @@ class ObjectLayerEngineModal {
1814
1818
  clone ? '(CLONE MODE)' : isUpdateMode ? '(UPDATE MODE)' : '(CREATE MODE)',
1815
1819
  );
1816
1820
 
1817
- if (appStore.Data.user.main.model.user.role === 'guest') {
1821
+ if (!commonModeratorGuard(appStore.Data.user.main.model.user.role)) {
1818
1822
  NotificationManager.Push({
1819
- html: 'Guests cannot save object layers. Please log in.',
1823
+ html: 'Only moderators and admins can create or edit object layers.',
1820
1824
  status: 'warning',
1821
1825
  });
1822
1826
  return;
@@ -2202,7 +2206,7 @@ class ObjectLayerEngineModal {
2202
2206
  </div>
2203
2207
 
2204
2208
  <div class="in section-mp section-mp-border">
2205
- <div class="in sub-title-modal"><i class="fa-solid fa-database"></i> instance data</div>
2209
+ <div class="in sub-title-modal"><i class="fa-solid fa-database"></i> render data</div>
2206
2210
  ${dynamicCol({ containerSelector: options.idModal, id: idSectionA })}
2207
2211
 
2208
2212
  <div class="fl">
@@ -2342,12 +2346,14 @@ class ObjectLayerEngineModal {
2342
2346
  </div>
2343
2347
 
2344
2348
  <div class="fl section-mp">
2345
- ${await BtnIcon.instance({
2346
- label: html`<i class="submit-btn-icon fa-solid fa-folder-open"></i>
2347
- ${ObjectLayerEngineModal.existingObjectLayerId ? 'Update' : Translate.instance('save')}`,
2348
- class: `in flr ol-btn-save`,
2349
- })}
2350
- ${ObjectLayerEngineModal.existingObjectLayerId
2349
+ ${canMutate
2350
+ ? await BtnIcon.instance({
2351
+ label: html`<i class="submit-btn-icon fa-solid fa-folder-open"></i>
2352
+ ${ObjectLayerEngineModal.existingObjectLayerId ? 'Update' : Translate.instance('save')}`,
2353
+ class: `in flr ol-btn-save`,
2354
+ })
2355
+ : ''}
2356
+ ${canMutate && ObjectLayerEngineModal.existingObjectLayerId
2351
2357
  ? await BtnIcon.instance({
2352
2358
  label: html`<i class="submit-btn-icon fa-solid fa-clone"></i> Clone`,
2353
2359
  class: `in flr ol-btn-clone`,
@@ -2363,36 +2369,7 @@ class ObjectLayerEngineModal {
2363
2369
  };
2364
2370
 
2365
2371
  static getDirectionsFromDirectionCode(directionCode = '08') {
2366
- let objectLayerFrameDirections = [];
2367
-
2368
- switch (directionCode) {
2369
- case '08':
2370
- objectLayerFrameDirections = ['down_idle', 'none_idle', 'default_idle'];
2371
- break;
2372
- case '18':
2373
- objectLayerFrameDirections = ['down_walking'];
2374
- break;
2375
- case '02':
2376
- objectLayerFrameDirections = ['up_idle'];
2377
- break;
2378
- case '12':
2379
- objectLayerFrameDirections = ['up_walking'];
2380
- break;
2381
- case '04':
2382
- objectLayerFrameDirections = ['left_idle', 'up_left_idle', 'down_left_idle'];
2383
- break;
2384
- case '14':
2385
- objectLayerFrameDirections = ['left_walking', 'up_left_walking', 'down_left_walking'];
2386
- break;
2387
- case '06':
2388
- objectLayerFrameDirections = ['right_idle', 'up_right_idle', 'down_right_idle'];
2389
- break;
2390
- case '16':
2391
- objectLayerFrameDirections = ['right_walking', 'up_right_walking', 'down_right_walking'];
2392
- break;
2393
- }
2394
-
2395
- return objectLayerFrameDirections;
2372
+ return getKeyframeDirectionsByCode(directionCode);
2396
2373
  }
2397
2374
 
2398
2375
  static toManagement = async (id = null) => {
@@ -11,6 +11,7 @@ import { ObjectLayerService } from '../../services/object-layer/object-layer.ser
11
11
  import { AtlasSpriteSheetService } from '../../services/atlas-sprite-sheet/atlas-sprite-sheet.service.js';
12
12
  import { NotificationManager } from '../core/NotificationManager.js';
13
13
  import { append, htmls, s } from '../core/VanillaJs.js';
14
+ import { commonModeratorGuard } from '../core/CommonJs.js';
14
15
  import { darkTheme, ThemeEvents, subThemeManager, lightenHex, darkenHex } from '../core/Css.js';
15
16
  import { ObjectLayerManagement } from '../../services/object-layer/object-layer.management.js';
16
17
  import { ObjectLayerEngineModal } from './ObjectLayerEngineModal.js';
@@ -179,6 +180,7 @@ class ObjectLayerEngineViewer {
179
180
  }
180
181
  static async renderViewer({ appStore }) {
181
182
  const id = 'object-layer-engine-viewer';
183
+ const canMutate = commonModeratorGuard(appStore?.Data?.user?.main?.model?.user?.role || 'guest');
182
184
  const { objectLayer, frameCounts } = ObjectLayerEngineViewer.Data;
183
185
  if (!objectLayer || !frameCounts) return;
184
186
  // Check if DOM element exists
@@ -915,10 +917,12 @@ class ObjectLayerEngineViewer {
915
917
  </div>
916
918
  </div>
917
919
  <div class="atlas-actions-grid">
918
- <button class="default-viewer-btn" id="generate-atlas-btn">
919
- <i class="fa-solid fa-sync"></i>
920
- <span>Update</span>
921
- </button>
920
+ ${canMutate
921
+ ? html`<button class="default-viewer-btn" id="generate-atlas-btn">
922
+ <i class="fa-solid fa-sync"></i>
923
+ <span>Update</span>
924
+ </button>`
925
+ : ''}
922
926
  <button class="default-viewer-btn" id="download-atlas-png-btn">
923
927
  <i class="fa-solid fa-download"></i>
924
928
  <span>PNG</span>
@@ -927,18 +931,22 @@ class ObjectLayerEngineViewer {
927
931
  <i class="fa-solid fa-code"></i>
928
932
  <span>JSON</span>
929
933
  </button>
930
- <button class="default-viewer-btn" id="remove-atlas-btn" style="background: #dc3545;">
931
- <i class="fa-solid fa-trash"></i>
932
- <span>Remove</span>
933
- </button>
934
+ ${canMutate
935
+ ? html`<button class="default-viewer-btn" id="remove-atlas-btn" style="background: #dc3545;">
936
+ <i class="fa-solid fa-trash"></i>
937
+ <span>Remove</span>
938
+ </button>`
939
+ : ''}
934
940
  </div>
935
941
  `
936
942
  : html`
937
943
  <p>No atlas sprite sheet associated with this object layer.</p>
938
- <button class="default-viewer-btn" id="generate-atlas-btn">
939
- <i class="fa-solid fa-wand-magic-sparkles"></i>
940
- <span>Generate Atlas</span>
941
- </button>
944
+ ${canMutate
945
+ ? html`<button class="default-viewer-btn" id="generate-atlas-btn">
946
+ <i class="fa-solid fa-wand-magic-sparkles"></i>
947
+ <span>Generate Atlas</span>
948
+ </button>`
949
+ : ''}
942
950
  `}
943
951
  </div>
944
952
  </div>
@@ -948,14 +956,20 @@ class ObjectLayerEngineViewer {
948
956
  <i class="fa-solid fa-arrow-left"></i>
949
957
  <span>Return to List</span>
950
958
  </button>
951
- <button class="default-viewer-btn edit-btn" id="edit-object-layer-btn">
952
- <i class="fa-solid fa-edit"></i>
953
- <span>Edit</span>
954
- </button>
955
- <button class="default-viewer-btn" id="delete-object-layer-btn" style="background: #dc3545;">
956
- <i class="fa-solid fa-trash"></i>
957
- <span>Delete</span>
958
- </button>
959
+ ${canMutate
960
+ ? html`<button class="default-viewer-btn edit-btn" id="edit-object-layer-btn">
961
+ <i class="fa-solid fa-edit"></i>
962
+ <span>Edit</span>
963
+ </button>
964
+ <button
965
+ class="default-viewer-btn"
966
+ id="delete-object-layer-btn"
967
+ style="background: #dc3545;"
968
+ >
969
+ <i class="fa-solid fa-trash"></i>
970
+ <span>Delete</span>
971
+ </button>`
972
+ : ''}
959
973
  </div>
960
974
  `}
961
975
  </div>
@@ -18,7 +18,7 @@
18
18
  *
19
19
  * 2. **Shared content vocabulary** — `ITEM_TYPES`, `ENTITY_TYPES`,
20
20
  * `DefaultCyberiaItems` registry + lookups, type-to-item mapping,
21
- * quest step / action type enums. The data shape both the
21
+ * quest step objective enum. The data shape both the
22
22
  * browser-side editor UI and the engine REST controllers need to
23
23
  * understand; it is **not** simulation state.
24
24
  *
@@ -70,6 +70,7 @@ export const ITEM_TYPES = Object.freeze({
70
70
  portal: 'portal',
71
71
  foreground: 'foreground',
72
72
  resource: 'resource',
73
+ static: 'static',
73
74
  });
74
75
 
75
76
  /**
@@ -89,6 +90,7 @@ export const ENTITY_TYPES = Object.freeze({
89
90
  portal: 'portal',
90
91
  foreground: 'foreground',
91
92
  resource: 'resource',
93
+ static: 'static',
92
94
  });
93
95
 
94
96
  /** Per-entity-type allowlist of item types that may appear on the entity. */
@@ -103,13 +105,186 @@ export const ENTITY_TYPE_TO_ITEM_TYPES = Object.freeze({
103
105
  [ENTITY_TYPES.portal]: Object.freeze([ITEM_TYPES.portal]),
104
106
  [ENTITY_TYPES.foreground]: Object.freeze([ITEM_TYPES.foreground]),
105
107
  [ENTITY_TYPES.resource]: Object.freeze([ITEM_TYPES.resource]),
108
+ [ENTITY_TYPES.static]: Object.freeze([ITEM_TYPES.static]),
106
109
  });
107
110
 
108
111
  /** Quest step objective types accepted by the quest-progress engine. */
109
112
  export const QUEST_STEPS_TYPES = Object.freeze(['collect', 'talk', 'kill']);
110
113
 
111
- /** Action categories accepted by the cyberia-action engine. */
112
- export const CYBERIA_ACTION_TYPES = Object.freeze(['craft', 'shop', 'storage', 'quest-talk']);
114
+ /**
115
+ * Canonical skill LogicId registry the single source of truth for the
116
+ * `logicEventId` handler keys the simulation skill dispatcher knows how to run.
117
+ * MUST stay aligned with the handlers registered in cyberia-server
118
+ * `game/skill_dispatcher.go#InitSkills`. The skill editor (ActionEngineCyberia)
119
+ * offers ONLY these ids, and `DefaultSkillConfig` (cyberia-server-defaults.js)
120
+ * draws its `logicEventId`s from here.
121
+ *
122
+ * @type {ReadonlyArray<{id:string,name:string,description:string}>}
123
+ */
124
+ export const SKILL_LOGIC_IDS = Object.freeze([
125
+ Object.freeze({
126
+ id: 'projectile',
127
+ name: 'Projectile',
128
+ description: 'Fires a projectile toward the tap. Spawn chance and lifetime scale with Intelligence and Range.',
129
+ }),
130
+ Object.freeze({
131
+ id: 'coin_drop_or_transaction',
132
+ name: 'Coin Drop',
133
+ description: 'Drops coins on kill; transfer amount follows the kill-percent economy rules.',
134
+ }),
135
+ Object.freeze({
136
+ id: 'doppelganger',
137
+ name: 'Doppelganger',
138
+ description: 'Summons a passive clone that wanders nearby. Spawn chance scales with Intelligence.',
139
+ }),
140
+ ]);
141
+
142
+ /** Ordered list of canonical skill LogicId values. */
143
+ export const SKILL_LOGIC_ID_VALUES = Object.freeze(SKILL_LOGIC_IDS.map((l) => l.id));
144
+
145
+ /** True when `logicEventId` is a known canonical skill LogicId. */
146
+ export const isCanonicalSkillLogicId = (logicEventId) => SKILL_LOGIC_ID_VALUES.includes(logicEventId);
147
+
148
+ /**
149
+ * Canonical entity-behavior registry — the authoritative vocabulary for the
150
+ * `behavior` an entity-type default may bind to its matched entities. The Go
151
+ * simulation owns the runtime semantics; this registry is the shared label /
152
+ * documentation source consumed by the editor (EntityEngineCyberia) and by
153
+ * content-authority validation. MUST stay aligned with cyberia-server
154
+ * `game/behavior.go`.
155
+ *
156
+ * `selectable: false` marks behaviors the runtime assigns itself
157
+ * (projectiles, coin drops) — they are not author-assignable to a default.
158
+ *
159
+ * @type {ReadonlyArray<{id:string,label:string,description:string,selectable:boolean}>}
160
+ */
161
+ export const ENTITY_BEHAVIORS = Object.freeze([
162
+ Object.freeze({
163
+ id: 'passive',
164
+ label: 'Passive',
165
+ description: 'Wanders within its spawn radius; never aggroes. Default for unarmed entities.',
166
+ selectable: true,
167
+ }),
168
+ Object.freeze({
169
+ id: 'hostile',
170
+ label: 'Hostile',
171
+ description: 'Pursues and attacks players within aggro range. Default for armed entities.',
172
+ selectable: true,
173
+ }),
174
+ Object.freeze({
175
+ id: 'provider',
176
+ label: 'Provider',
177
+ description: 'Mission/action giver: barely moves from its spawn (sporadic short steps) and is immortal.',
178
+ selectable: true,
179
+ }),
180
+ Object.freeze({
181
+ id: 'provider-static',
182
+ label: 'Provider (Static)',
183
+ description: 'Like provider but completely immobile, and immortal.',
184
+ selectable: true,
185
+ }),
186
+ Object.freeze({
187
+ id: 'skill',
188
+ label: 'Skill',
189
+ description: 'Runtime projectile entity — assigned by the skill engine, not author-selectable.',
190
+ selectable: false,
191
+ }),
192
+ Object.freeze({
193
+ id: 'coin',
194
+ label: 'Coin',
195
+ description: 'Runtime coin entity — assigned by the economy engine, not author-selectable.',
196
+ selectable: false,
197
+ }),
198
+ ]);
199
+
200
+ /** Ordered list of canonical behavior id values. */
201
+ export const ENTITY_BEHAVIOR_VALUES = Object.freeze(ENTITY_BEHAVIORS.map((b) => b.id));
202
+
203
+ /** Author-assignable behaviors (the subset the entity-default editor offers). */
204
+ export const SELECTABLE_ENTITY_BEHAVIORS = Object.freeze(ENTITY_BEHAVIORS.filter((b) => b.selectable));
205
+
206
+ /** True when `behavior` is a known canonical entity behavior. */
207
+ export const isCanonicalEntityBehavior = (behavior) => ENTITY_BEHAVIOR_VALUES.includes(behavior);
208
+
209
+ /**
210
+ * Canonical object-layer animation directions. Each entry binds:
211
+ * code — numeric asset folder name on disk
212
+ * (`./assets/<type>/<id>/<code>/<frame>.png`)
213
+ * label — human-readable label for the editor UI
214
+ * keyframes — render keyframe direction names this folder code feeds
215
+ *
216
+ * Single source of truth for the browser editor (`ObjectLayerEngineModal`) and
217
+ * the Node asset pipeline (`src/projects/cyberia/object-layer.js`). Adding a
218
+ * direction here is the only place it needs to be declared.
219
+ *
220
+ * @type {ReadonlyArray<{code:string,label:string,keyframes:ReadonlyArray<string>}>}
221
+ */
222
+ export const OBJECT_LAYER_DIRECTIONS = Object.freeze([
223
+ Object.freeze({
224
+ code: '08',
225
+ label: 'Down Idle',
226
+ keyframes: Object.freeze(['down_idle', 'none_idle', 'default_idle']),
227
+ }),
228
+ Object.freeze({ code: '18', label: 'Down Walk', keyframes: Object.freeze(['down_walking']) }),
229
+ Object.freeze({ code: '02', label: 'Up Idle', keyframes: Object.freeze(['up_idle']) }),
230
+ Object.freeze({ code: '12', label: 'Up Walk', keyframes: Object.freeze(['up_walking']) }),
231
+ Object.freeze({
232
+ code: '04',
233
+ label: 'Left Idle',
234
+ keyframes: Object.freeze(['left_idle', 'up_left_idle', 'down_left_idle']),
235
+ }),
236
+ Object.freeze({
237
+ code: '14',
238
+ label: 'Left Walk',
239
+ keyframes: Object.freeze(['left_walking', 'up_left_walking', 'down_left_walking']),
240
+ }),
241
+ Object.freeze({
242
+ code: '06',
243
+ label: 'Right Idle',
244
+ keyframes: Object.freeze(['right_idle', 'up_right_idle', 'down_right_idle']),
245
+ }),
246
+ Object.freeze({
247
+ code: '16',
248
+ label: 'Right Walk',
249
+ keyframes: Object.freeze(['right_walking', 'up_right_walking', 'down_right_walking']),
250
+ }),
251
+ ]);
252
+
253
+ /** Ordered list of object-layer direction folder codes. */
254
+ export const OBJECT_LAYER_DIRECTION_CODES = Object.freeze(OBJECT_LAYER_DIRECTIONS.map((d) => d.code));
255
+
256
+ /** Map: direction folder code → editor label. */
257
+ export const OBJECT_LAYER_DIRECTION_LABELS = Object.freeze(
258
+ Object.fromEntries(OBJECT_LAYER_DIRECTIONS.map((d) => [d.code, d.label])),
259
+ );
260
+
261
+ /** Render keyframe direction names a numeric folder code feeds (empty if unknown). */
262
+ export const getKeyframeDirectionsByCode = (code) => {
263
+ const entry = OBJECT_LAYER_DIRECTIONS.find((d) => d.code === code);
264
+ return entry ? [...entry.keyframes] : [];
265
+ };
266
+
267
+ /** Inverse map: render keyframe direction name → numeric folder code. */
268
+ export const OBJECT_LAYER_DIRECTION_NAME_TO_CODE = Object.freeze(
269
+ OBJECT_LAYER_DIRECTIONS.reduce((acc, d) => {
270
+ for (const name of d.keyframes) acc[name] = d.code;
271
+ return acc;
272
+ }, {}),
273
+ );
274
+
275
+ /**
276
+ * Canonical entity/item stat types, in display order. Shared by the object-layer
277
+ * editor (stat inputs) and the asset pipeline (random stat generation).
278
+ */
279
+ export const STAT_TYPES = Object.freeze(['effect', 'resistance', 'agility', 'range', 'intelligence', 'utility']);
280
+
281
+ /**
282
+ * Build a random stat block keyed by STAT_TYPES, each value 0–10 inclusive.
283
+ * Non-deterministic (uses Math.random) — the asset pipeline uses it to seed
284
+ * default stats for newly authored object layers.
285
+ */
286
+ export const generateRandomStats = () =>
287
+ Object.fromEntries(STAT_TYPES.map((statType) => [statType, Math.floor(Math.random() * 11)]));
113
288
 
114
289
  /**
115
290
  * Canonical (itemId → itemType) registry shipped with the engine. Used
@@ -126,6 +301,7 @@ export const DefaultCyberiaItems = [
126
301
  { item: { id: 'tim-knife', type: ITEM_TYPES.weapon } },
127
302
  { item: { id: 'hatchet', type: ITEM_TYPES.weapon } },
128
303
  { item: { id: 'wason', type: ITEM_TYPES.skin } },
304
+ { item: { id: 'kishins', type: ITEM_TYPES.skin } },
129
305
  { item: { id: 'scp-2040', type: ITEM_TYPES.skin } },
130
306
  { item: { id: 'purple', type: ITEM_TYPES.skin } },
131
307
  { item: { id: 'punk', type: ITEM_TYPES.skin } },
@@ -198,6 +374,7 @@ export const PALETTE = Object.freeze([
198
374
  { key: 'COIN', r: 255, g: 215, b: 0, a: 255 },
199
375
  { key: 'SKILL', r: 255, g: 255, b: 50, a: 255 },
200
376
  { key: 'RESOURCE', r: 100, g: 180, b: 80, a: 255 },
377
+ { key: 'STATIC', r: 120, g: 140, b: 110, a: 255 },
201
378
  { key: 'WEAPON', r: 180, g: 50, b: 50, a: 255 },
202
379
  { key: 'SELF_BORDER', r: 220, g: 190, b: 60, a: 240 },
203
380
  ]);
@@ -218,6 +395,7 @@ export const ENTITY_COLOR_KEYS = Object.freeze([
218
395
  { entityType: 'portal', colorKey: 'PORTAL' },
219
396
  { entityType: 'foreground', colorKey: 'FOREGROUND' },
220
397
  { entityType: 'resource', colorKey: 'RESOURCE' },
398
+ { entityType: 'static', colorKey: 'STATIC' },
221
399
  ]);
222
400
 
223
401
  /**
@@ -236,6 +414,12 @@ export const ENTITY_COLOR_KEYS = Object.freeze([
236
414
  * defaultObjHeight — default entity dimensions used by the world editor
237
415
  * and the client when a doc omits dims. Presentation
238
416
  * because dims-in-cells is just a visual sizing.
417
+ * fontFamily — TTF file name under engine `assets/fonts/`. When set, the
418
+ * cyberia-client fetches `assets/fonts/<fontFamily>` and loads
419
+ * it as the main default font for all text. Empty = raylib's
420
+ * built-in font.
421
+ * fontFactorSize — uniform multiplier applied to every text size, so a
422
+ * deployment can scale all UI/HUD text without per-call edits.
239
423
  */
240
424
  export const RENDER_DEFAULTS = Object.freeze({
241
425
  cellSize: 45,
@@ -247,6 +431,8 @@ export const RENDER_DEFAULTS = Object.freeze({
247
431
  defaultHeightScreenFactor: 1,
248
432
  interpolationMs: 100,
249
433
  devUi: false,
434
+ fontFamily: 'PressStart2P-Regular.ttf',
435
+ fontFactorSize: 0.8,
250
436
  });
251
437
 
252
438
  /**
@@ -267,7 +453,10 @@ export const STATUS_ICONS_PRESENTATION = Object.freeze([
267
453
  { id: 5, iconId: 'skull', bounce: false, borderColor: { r: 160, g: 130, b: 200, a: 200 } },
268
454
  { id: 6, iconId: 'arrow-down-gray', bounce: false, borderColor: { r: 100, g: 180, b: 80, a: 220 } },
269
455
  { id: 7, iconId: 'clock', bounce: false, borderColor: { r: 160, g: 130, b: 200, a: 200 } },
270
- { id: 8, iconId: 'chat', bounce: true, borderColor: { r: 220, g: 190, b: 60, a: 240 } },
456
+ { id: 8, iconId: 'hand', bounce: true, borderColor: { r: 80, g: 160, b: 220, a: 240 } },
457
+ { id: 9, iconId: 'quest', bounce: true, borderColor: { r: 220, g: 190, b: 60, a: 240 } },
458
+ { id: 10, iconId: 'transport', bounce: false, borderColor: { r: 90, g: 170, b: 230, a: 240 } },
459
+ { id: 11, iconId: 'transport-random', bounce: false, borderColor: { r: 150, g: 130, b: 230, a: 240 } },
271
460
  ]);
272
461
 
273
462
  /**
@@ -320,6 +509,8 @@ export function buildClientHints(overrides = {}) {
320
509
  defaultHeightScreenFactor: ov.defaultHeightScreenFactor ?? RENDER_DEFAULTS.defaultHeightScreenFactor,
321
510
  interpolationMs: ov.interpolationMs ?? RENDER_DEFAULTS.interpolationMs,
322
511
  devUi: ov.devUi ?? RENDER_DEFAULTS.devUi,
512
+ fontFamily: ov.fontFamily ?? RENDER_DEFAULTS.fontFamily,
513
+ fontFactorSize: ov.fontFactorSize ?? RENDER_DEFAULTS.fontFactorSize,
323
514
  };
324
515
  }
325
516
 
@@ -31,6 +31,8 @@ import { ObjectLayerManagement } from '../../services/object-layer/object-layer.
31
31
  import { MainBodyCyberiaPortal } from './MainBodyCyberiaPortal.js';
32
32
  import { MapEngineCyberia } from '../cyberia/MapEngineCyberia.js';
33
33
  import { InstanceEngineCyberia } from '../cyberia/InstanceEngineCyberia.js';
34
+ import { ActionEngineCyberia } from '../cyberia/ActionEngineCyberia.js';
35
+ import { EntityEngineCyberia } from '../cyberia/EntityEngineCyberia.js';
34
36
 
35
37
  class AppShellCyberiaPortal {
36
38
  static Data = {};
@@ -249,6 +251,30 @@ class AppShellCyberiaPortal {
249
251
  handleContainerClass: 'handle-btn-container',
250
252
  tooltipHtml: await Badge.instance(buildBadgeToolTipMenuOption('cyberia-instance-engine')),
251
253
  })}
254
+ ${await BtnIcon.instance({
255
+ class: 'in wfa main-btn-menu main-btn-cyberia-action-engine',
256
+ useMenuBtn: true,
257
+ label: renderMenuLabel({
258
+ icon: html`<img class="inl cyberia-menu-icon" src="${getProxyPath()}assets/ui-icons/engine.png" />`,
259
+ text: html`<span class="menu-label-text">${Translate.instance('cyberia-action-engine')}</span>`,
260
+ }),
261
+ attrs: `data-id="cyberia-action-engine"`,
262
+ tabHref: `${getProxyPath()}cyberia-action-engine`,
263
+ handleContainerClass: 'handle-btn-container',
264
+ tooltipHtml: await Badge.instance(buildBadgeToolTipMenuOption('cyberia-action-engine')),
265
+ })}
266
+ ${await BtnIcon.instance({
267
+ class: 'in wfa main-btn-menu main-btn-cyberia-entity-engine',
268
+ useMenuBtn: true,
269
+ label: renderMenuLabel({
270
+ icon: html`<img class="inl cyberia-menu-icon" src="${getProxyPath()}assets/ui-icons/engine.png" />`,
271
+ text: html`<span class="menu-label-text">${Translate.instance('cyberia-entity-engine')}</span>`,
272
+ }),
273
+ attrs: `data-id="cyberia-entity-engine"`,
274
+ tabHref: `${getProxyPath()}cyberia-entity-engine`,
275
+ handleContainerClass: 'handle-btn-container',
276
+ tooltipHtml: await Badge.instance(buildBadgeToolTipMenuOption('cyberia-entity-engine')),
277
+ })}
252
278
  </div>
253
279
  `,
254
280
  // htmlMainBody: async () => {
@@ -664,6 +690,46 @@ class AppShellCyberiaPortal {
664
690
  });
665
691
  });
666
692
 
693
+ EventsUI.onClick(`.main-btn-cyberia-action-engine`, async () => {
694
+ const { barConfig } = await Themes[Css.currentTheme]();
695
+ await Modal.instance({
696
+ id: 'modal-cyberia-action-engine',
697
+ route: 'cyberia-action-engine',
698
+ barConfig,
699
+ title: renderViewTitle({
700
+ icon: html`<img class="inl cyberia-menu-icon-modal" src="${getProxyPath()}assets/ui-icons/engine.png" />`,
701
+ text: `<span class='inl cyberia-text-title-modal'>${Translate.instance('cyberia-action-engine')}</span>`,
702
+ }),
703
+ html: async () => await ActionEngineCyberia.render({ appStore: AppStoreCyberiaPortal }),
704
+ handleType: 'bar',
705
+ maximize: true,
706
+ mode: 'view',
707
+ slideMenu: 'modal-menu',
708
+ RouterInstance,
709
+ observer: true,
710
+ });
711
+ });
712
+
713
+ EventsUI.onClick(`.main-btn-cyberia-entity-engine`, async () => {
714
+ const { barConfig } = await Themes[Css.currentTheme]();
715
+ await Modal.instance({
716
+ id: 'modal-cyberia-entity-engine',
717
+ route: 'cyberia-entity-engine',
718
+ barConfig,
719
+ title: renderViewTitle({
720
+ icon: html`<img class="inl cyberia-menu-icon-modal" src="${getProxyPath()}assets/ui-icons/engine.png" />`,
721
+ text: `<span class='inl cyberia-text-title-modal'>${Translate.instance('cyberia-entity-engine')}</span>`,
722
+ }),
723
+ html: async () => await EntityEngineCyberia.render({ appStore: AppStoreCyberiaPortal }),
724
+ handleType: 'bar',
725
+ maximize: true,
726
+ mode: 'view',
727
+ slideMenu: 'modal-menu',
728
+ RouterInstance,
729
+ observer: true,
730
+ });
731
+ });
732
+
667
733
  s(`.main-btn-admin`).onclick = () => {
668
734
  const { protocol, hostname } = window.location;
669
735
  return (location.href = `${protocol}//${hostname}/admin${['', 0][random(0, 1)]}`);
@@ -48,6 +48,14 @@ class RouterCyberiaPortal {
48
48
  title: 'cyberia-instance-engine',
49
49
  render: () => s(`.main-btn-cyberia-instance-engine`).click(),
50
50
  },
51
+ '/cyberia-action-engine': {
52
+ title: 'cyberia-action-engine',
53
+ render: () => s(`.main-btn-cyberia-action-engine`).click(),
54
+ },
55
+ '/cyberia-entity-engine': {
56
+ title: 'cyberia-entity-engine',
57
+ render: () => s(`.main-btn-cyberia-entity-engine`).click(),
58
+ },
51
59
  '/docs': { title: 'docs', render: () => s(`.main-btn-docs`).click() },
52
60
  };
53
61
  }