@underpostnet/cyberia 3.3.73 → 3.3.76

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 (228) hide show
  1. package/.env.example +17 -5
  2. package/.github/workflows/coverall.cyberia.ci.yml +5 -0
  3. package/.github/workflows/docker-image.cyberia-client.ci.yml +1 -1
  4. package/.github/workflows/docker-image.cyberia-client.dev.ci.yml +1 -1
  5. package/.github/workflows/docker-image.cyberia-server.ci.yml +1 -1
  6. package/.github/workflows/docker-image.cyberia-server.dev.ci.yml +1 -1
  7. package/.github/workflows/docker-image.engine-cyberia.ci.yml +1 -1
  8. package/.github/workflows/docker-image.engine-cyberia.dev.ci.yml +1 -1
  9. package/.github/workflows/publish.cyberia.ci.yml +44 -0
  10. package/AGENTS.md +61 -12
  11. package/CHANGELOG.md +126 -41
  12. package/CLI-HELP.md +19 -12
  13. package/Dockerfile +1 -1
  14. package/Dockerfile.dev +1 -1
  15. package/Dockerfile.test +2 -2
  16. package/README.md +28 -28
  17. package/bin/build.js +37 -17
  18. package/bin/cyberia.js +1247 -1373
  19. package/bin/index.js +1247 -1373
  20. package/compose.env +17 -5
  21. package/conf.js +7 -3
  22. package/deploy/cyberia-client/deploy.sh +10 -11
  23. package/deploy/cyberia-client/package.sh +0 -1
  24. package/deploy/cyberia-client/state.sh +3 -2
  25. package/deploy/cyberia-server/deploy.sh +10 -11
  26. package/deploy/cyberia-server/package.sh +0 -1
  27. package/deploy/cyberia-server/state.sh +3 -2
  28. package/deploy/dd-cyberia/deploy.sh +5 -5
  29. package/deploy/dd-cyberia/init.sh +15 -14
  30. package/deploy/dd-cyberia/package.sh +0 -1
  31. package/deploy/dd-cyberia/state.sh +2 -2
  32. package/deploy/dd-cyberia/sync-deploy.sh +139 -75
  33. package/deploy/lib/config.sh +29 -0
  34. package/deploy/lib/github-actions-logging.sh +92 -76
  35. package/deploy/lib/host.sh +27 -18
  36. package/deploy/lib/state.sh +4 -1
  37. package/deploy/pwa-microservices-template/deploy.sh +10 -8
  38. package/deploy/release/deploy.sh +0 -2
  39. package/deployment.yaml +1 -1
  40. package/docker-compose.yml +23 -17
  41. package/hardhat/package-lock.json +2 -2
  42. package/hardhat/package.json +1 -1
  43. package/manifests/cronjobs/dd-cron/dd-cron-backup.yaml +1 -1
  44. package/manifests/cronjobs/dd-cron/dd-cron-vultr.yaml +1 -1
  45. package/manifests/deployment/dd-cyberia-development/deployment.yaml +1 -1
  46. package/manifests/deployment/dd-default-development/deployment.yaml +2 -2
  47. package/manifests/deployment/mongo-express/deployment.yaml +46 -27
  48. package/manifests/deployment/mongo-express/kustomization.yaml +9 -0
  49. package/manifests/deployment/mongo-express/mongo-express-nodeport.yaml +18 -0
  50. package/manifests/deployment/mongo-express/service.yaml +16 -0
  51. package/manifests/deployment/mongo-express-no-auth/disable-mongodb-auth.yaml +16 -0
  52. package/manifests/deployment/mongo-express-no-auth/kustomization.yaml +16 -0
  53. package/package.json +19 -17
  54. package/scripts/test-monitor.sh +3 -3
  55. package/src/api/atlas-sprite-sheet/atlas-sprite-sheet.controller.js +14 -9
  56. package/src/api/atlas-sprite-sheet/atlas-sprite-sheet.model.js +36 -5
  57. package/src/api/atlas-sprite-sheet/atlas-sprite-sheet.router.js +5 -0
  58. package/src/api/atlas-sprite-sheet/atlas-sprite-sheet.service.js +88 -184
  59. package/src/api/cyberia-action/cyberia-action.router.js +2 -2
  60. package/src/api/cyberia-action/cyberia-action.service.js +3 -4
  61. package/src/api/cyberia-audio/cyberia-audio.controller.js +6 -0
  62. package/src/api/cyberia-audio/cyberia-audio.model.js +61 -0
  63. package/src/api/cyberia-audio/cyberia-audio.router.js +21 -0
  64. package/src/api/cyberia-audio/cyberia-audio.service.js +220 -0
  65. package/src/api/cyberia-client-hints/cyberia-client-hints.controller.js +1 -3
  66. package/src/api/cyberia-client-hints/cyberia-client-hints.model.js +10 -19
  67. package/src/api/cyberia-client-hints/cyberia-client-hints.router.js +12 -34
  68. package/src/api/cyberia-client-hints/cyberia-client-hints.service.js +15 -32
  69. package/src/api/cyberia-dialogue/cyberia-dialogue.model.js +1 -1
  70. package/src/api/cyberia-dialogue/cyberia-dialogue.router.js +1 -1
  71. package/src/api/cyberia-entity/cyberia-entity.model.js +2 -0
  72. package/src/api/cyberia-entity/cyberia-entity.service.js +1 -1
  73. package/src/api/cyberia-entity-type-default/cyberia-entity-type-default.controller.js +8 -2
  74. package/src/api/cyberia-entity-type-default/cyberia-entity-type-default.model.js +31 -20
  75. package/src/api/cyberia-entity-type-default/cyberia-entity-type-default.router.js +16 -1
  76. package/src/api/cyberia-entity-type-default/cyberia-entity-type-default.service.js +466 -3
  77. package/src/api/cyberia-instance/cyberia-fallback-capture.js +87 -51
  78. package/src/api/cyberia-instance/cyberia-fallback-world.js +21 -40
  79. package/src/api/cyberia-instance/cyberia-instance-boot.service.js +5 -5
  80. package/src/api/cyberia-instance/cyberia-instance-items.js +98 -0
  81. package/src/api/cyberia-instance/cyberia-instance-map.service.js +34 -141
  82. package/src/api/cyberia-instance/cyberia-instance.controller.js +1 -4
  83. package/src/api/cyberia-instance/cyberia-instance.model.js +0 -14
  84. package/src/api/cyberia-instance/cyberia-instance.router.js +3 -16
  85. package/src/api/cyberia-instance/cyberia-instance.service.js +3 -43
  86. package/src/api/cyberia-instance/cyberia-world-generator.js +8 -13
  87. package/src/api/cyberia-instance-conf/cyberia-instance-conf.model.js +49 -75
  88. package/src/api/cyberia-instance-conf/cyberia-instance-conf.service.js +74 -1
  89. package/src/api/cyberia-map/cyberia-map.service.js +6 -2
  90. package/src/api/cyberia-map-audio-conf/cyberia-map-audio-conf.controller.js +31 -0
  91. package/src/api/cyberia-map-audio-conf/cyberia-map-audio-conf.model.js +56 -0
  92. package/src/api/cyberia-map-audio-conf/cyberia-map-audio-conf.router.js +33 -0
  93. package/src/api/cyberia-map-audio-conf/cyberia-map-audio-conf.service.js +189 -0
  94. package/src/api/cyberia-quest/cyberia-quest.router.js +3 -3
  95. package/src/api/cyberia-quest/cyberia-quest.service.js +3 -3
  96. package/src/api/cyberia-quest-progress/cyberia-quest-progress.model.js +1 -1
  97. package/src/api/cyberia-server-defaults/cyberia-server-defaults.js +403 -188
  98. package/src/api/cyberia-skill/cyberia-skill.model.js +6 -6
  99. package/src/api/file/file.ref.js +153 -0
  100. package/src/api/file/file.ref.json +11 -2
  101. package/src/api/object-layer/object-layer.model.js +47 -12
  102. package/src/api/object-layer/object-layer.router.js +10 -3
  103. package/src/api/object-layer/object-layer.service.js +113 -120
  104. package/src/cli/app.js +12 -1
  105. package/src/cli/client.js +2 -2
  106. package/src/cli/cluster.js +7 -0
  107. package/src/cli/deploy.js +4 -4
  108. package/src/cli/docker-compose.js +1 -1
  109. package/src/cli/fs-selection.js +115 -0
  110. package/src/cli/fs.js +158 -342
  111. package/src/cli/index.js +22 -10
  112. package/src/cli/repository.js +28 -0
  113. package/src/cli/run.js +71 -197
  114. package/src/cli/test.js +4 -0
  115. package/src/cli/wireguard.js +32 -31
  116. package/src/client/components/core/Docs.js +77 -100
  117. package/src/client/components/core/Modal.js +56 -1
  118. package/src/client/components/cyberia/ActionEngineCyberia.js +12 -36
  119. package/src/client/components/cyberia/EntityEngineCyberia.js +492 -121
  120. package/src/client/components/cyberia/InstanceEngineCyberia.js +129 -160
  121. package/src/client/components/cyberia/InstanceSelectionView.js +7 -1
  122. package/src/client/components/cyberia/MapEngineCyberia.js +175 -69
  123. package/src/client/components/cyberia/ObjectLayerEngine.js +1011 -97
  124. package/src/client/components/cyberia/ObjectLayerEngineModal.js +81 -155
  125. package/src/client/components/cyberia/ObjectLayerEngineViewer.js +113 -98
  126. package/src/client/components/cyberia/SharedDefaultsCyberia.js +215 -56
  127. package/src/client/components/cyberia-portal/AppShellCyberiaPortal.js +0 -34
  128. package/src/client/components/cyberia-portal/CssCyberiaPortal.js +1 -1
  129. package/src/client/components/cyberia-portal/MainBodyCyberiaPortal.js +712 -76
  130. package/src/client/components/cyberia-portal/TranslateCyberiaPortal.js +0 -4
  131. package/src/client/public/cyberia-docs/ARCHITECTURE.md +15 -15
  132. package/src/client/public/cyberia-docs/CYBERIA-CLI.md +280 -24
  133. package/src/client/public/cyberia-docs/CYBERIA-CLIENT.md +51 -29
  134. package/src/client/public/cyberia-docs/CYBERIA-SAGA.md +52 -53
  135. package/src/client/public/cyberia-docs/CYBERIA-SERVER.md +16 -11
  136. package/src/client/public/cyberia-docs/CYBERIA.md +28 -28
  137. package/src/client/public/cyberia-docs/ENTITY-PROFILE.md +11 -5
  138. package/src/client/public/cyberia-docs/ROADMAP.md +1 -1
  139. package/src/client/public/cyberia-docs/STATS-PROGRESSION.md +217 -0
  140. package/src/client/public/cyberia-docs/WHITE-PAPER.md +20 -12
  141. package/src/client/services/cyberia-audio/cyberia-audio.service.js +99 -0
  142. package/src/client/services/cyberia-entity-type-default/cyberia-entity-type-default.service.js +38 -0
  143. package/src/client/services/cyberia-instance/cyberia-instance.management.js +5 -4
  144. package/src/client/services/cyberia-instance/cyberia-instance.service.js +0 -40
  145. package/src/client/services/cyberia-map/cyberia-map.management.js +5 -4
  146. package/src/client/services/cyberia-map-audio-conf/cyberia-map-audio-conf.service.js +138 -0
  147. package/src/client/services/object-layer/object-layer.management.js +6 -10
  148. package/src/client/services/object-layer/object-layer.service.js +3 -1
  149. package/src/client-builder/client-build-docs.js +52 -52
  150. package/src/client-builder/client-build.js +67 -44
  151. package/src/client-builder/client-bundle.js +347 -0
  152. package/src/db/mongo/MongoExpress.js +187 -0
  153. package/src/grpc/cyberia/grpc-server.js +5 -7
  154. package/src/index.js +1 -1
  155. package/src/projects/cyberia/atlas-sprite-sheet-generator.js +143 -146
  156. package/src/projects/cyberia/atlas-sprite-sheet-store.js +391 -0
  157. package/src/projects/cyberia/besu-genesis-generator.js +5 -14
  158. package/src/projects/cyberia/boot-contract-fixtures.js +32 -0
  159. package/src/projects/cyberia/catalog-cyberia.js +3 -2
  160. package/src/projects/cyberia/gemini-client.js +3 -3
  161. package/src/projects/cyberia/generate-saga.js +4 -11
  162. package/src/projects/cyberia/hot-reload-trigger.js +17 -28
  163. package/src/projects/cyberia/instance-backup.js +210 -0
  164. package/src/projects/cyberia/instance-data.js +177 -257
  165. package/src/projects/cyberia/map-preview-generator.js +32 -68
  166. package/src/projects/cyberia/object-layer.js +152 -319
  167. package/src/projects/cyberia/seed-audio.js +139 -0
  168. package/src/projects/cyberia/semantic-layer-generator-skin.js +2 -8
  169. package/src/projects/cyberia/semantic-layer-generator.js +2 -18
  170. package/src/projects/cyberia/stat-balance.js +81 -0
  171. package/src/projects/cyberia/stat-commands.js +23 -0
  172. package/src/projects/cyberia/stat-contract-generator.js +101 -0
  173. package/src/runtime/cyberia-client/Dockerfile +8 -4
  174. package/src/runtime/cyberia-client/Dockerfile.dev +5 -2
  175. package/src/runtime/cyberia-server/Dockerfile +9 -2
  176. package/src/runtime/cyberia-server/Dockerfile.dev +8 -1
  177. package/src/runtime/engine-cyberia/Dockerfile +1 -1
  178. package/src/runtime/engine-cyberia/Dockerfile.dev +1 -1
  179. package/src/runtime/engine-cyberia/Dockerfile.test +2 -2
  180. package/src/runtime/engine-cyberia/compose.env +17 -5
  181. package/src/runtime/engine-cyberia/docker-compose.yml +23 -17
  182. package/src/server/build/coverage.js +113 -42
  183. package/src/server/build/package.js +55 -12
  184. package/src/server/build/testing.js +59 -9
  185. package/src/server/network/middlewares.js +8 -2
  186. package/src/server/ops/logger.js +70 -35
  187. package/src/server/runtime/conf.js +4 -2
  188. package/src/server/storage/data-query.js +16 -0
  189. package/src/server/storage/downloader.js +62 -14
  190. package/src/server/storage/zip.js +153 -0
  191. package/test/integration/app/cyberia/atlas-sprite-sheet-store.test.js +402 -0
  192. package/test/integration/app/cyberia/atlas-sprite-sheet.test.js +86 -0
  193. package/test/integration/app/cyberia/cyberia-cli-plain-reads.test.js +34 -0
  194. package/test/integration/app/cyberia/cyberia-entity-type-default.test.js +842 -0
  195. package/test/integration/app/cyberia/cyberia-instance-conf-coerce.test.js +53 -0
  196. package/test/integration/app/cyberia/cyberia-instance-conf-defaults.test.js +16 -22
  197. package/test/integration/app/cyberia/cyberia-instance-items.test.js +90 -0
  198. package/test/integration/app/cyberia/cyberia-load.test.js +6 -7
  199. package/test/integration/app/cyberia/cyberia-map-audio-conf.test.js +256 -0
  200. package/test/integration/app/cyberia/cyberia-stats.test.js +150 -0
  201. package/test/integration/app/cyberia/fallback-world-capture.test.js +87 -2
  202. package/test/integration/app/cyberia/object-layer-item-selection.test.js +56 -0
  203. package/test/integration/app/cyberia/object-layer-natural-key.test.js +32 -0
  204. package/test/integration/app/cyberia/seed-audio.test.js +275 -0
  205. package/test/integration/infra/2-network/wireguard-cli.test.js +33 -9
  206. package/test/integration/infra/2-network/wireguard-edge.test.js +58 -18
  207. package/test/integration/infra/3-cluster/mongo-express-deploy.test.js +156 -0
  208. package/test/integration/infra/4-ingress/api-cross-origin.test.js +79 -0
  209. package/test/unit/client-build-docs.test.js +97 -70
  210. package/test/unit/client-bundle.test.js +313 -0
  211. package/test/unit/conf-resolution.test.js +17 -0
  212. package/test/unit/coverage-artifact.test.js +120 -29
  213. package/test/unit/cyberia/instance-backup.test.js +183 -0
  214. package/test/unit/cyberia/instance-object-layer-items.test.js +46 -0
  215. package/test/unit/cyberia/publish-workflow.test.js +22 -0
  216. package/test/unit/cyberia/stat-balance.test.js +69 -0
  217. package/test/unit/deploy-log-table.test.js +76 -5
  218. package/test/unit/downloader-integrity.test.js +98 -0
  219. package/test/unit/file-reference-registry.test.js +141 -0
  220. package/test/unit/fs-storage-paths.test.js +596 -0
  221. package/test/unit/logger-redaction.test.js +29 -0
  222. package/test/unit/package.test.js +70 -16
  223. package/test/unit/prepare-host.test.js +84 -1
  224. package/test/unit/test-tiers.test.js +27 -0
  225. package/test/unit/zip-archive.test.js +142 -0
  226. package/vitest.config.js +11 -3
  227. package/src/api/cyberia-instance/cyberia-fallback-default-items.js +0 -63
  228. package/src/client/components/cyberia/FallbackWorldEngineCyberia.js +0 -368
@@ -9,142 +9,19 @@ import { DropDown } from '../core/DropDown.js';
9
9
  import { CyberiaInstanceManagement } from '../../services/cyberia-instance/cyberia-instance.management.js';
10
10
  import { CyberiaInstanceService } from '../../services/cyberia-instance/cyberia-instance.service.js';
11
11
  import { CyberiaInstanceConfService } from '../../services/cyberia-instance-conf/cyberia-instance-conf.service.js';
12
+ import { CyberiaEntityTypeDefaultService } from '../../services/cyberia-entity-type-default/cyberia-entity-type-default.service.js';
12
13
  import { CyberiaMapService } from '../../services/cyberia-map/cyberia-map.service.js';
13
14
  import { FileService } from '../../services/file/file.service.js';
14
15
  import { DefaultManagement } from '../../services/default/default.management.js';
15
16
  import { getApiBaseUrl } from '../../services/core/core.service.js';
16
- import { ObjectLayerService } from '../../services/object-layer/object-layer.service.js';
17
-
18
- const dropdownValueKey = (value = '') => String(value).trim().replaceAll(' ', '-');
19
- const createDropdownOption = (value, onClick = () => {}, display = value, data = value) => ({
20
- value,
21
- display,
22
- data,
23
- onClick,
24
- });
17
+ import { getQueryParams, listenQueryParamsChange, setQueryParams } from '../core/Router.js';
25
18
 
26
19
  class InstanceEngineCyberia {
27
20
  static currentInstanceId = null;
21
+ static currentInstanceCode = null;
28
22
  static currentThumbnailId = null;
29
23
  static thumbnailDirty = false;
30
24
  static portals = [];
31
- static itemIdsDropdownId = 'instance-engine-item-ids-dropdown';
32
- static itemInventoryListId = 'instance-engine-item-inventory-list';
33
- // itemId → defaultPlayerInventory flag for the currently selected item ids.
34
- static itemInventoryFlags = {};
35
-
36
- // Current selection, read from the dropdown's `oncheckvalues` (always live —
37
- // it is updated before `_renderSelectedBadges` runs, unlike `.value` which
38
- // lags by one in the option-click path).
39
- static getSelectedItemIds() {
40
- const token = DropDown.Tokens[InstanceEngineCyberia.itemIdsDropdownId];
41
- if (!token) return [];
42
- const fromChecks = Object.values(token.oncheckvalues || {})
43
- .map((v) => v.data)
44
- .filter(Boolean);
45
- if (fromChecks.length > 0) return fromChecks;
46
- return Array.isArray(token.value) ? token.value.filter(Boolean) : [];
47
- }
48
-
49
- // Render the per-item "Default Player Inventory" toggles for every selected
50
- // item id. Kept in sync with the dropdown via a MutationObserver on its badge
51
- // container (see render()).
52
- static renderItemInventoryList(containerId = InstanceEngineCyberia.itemInventoryListId) {
53
- const container = s(`.${containerId}`);
54
- if (!container) return;
55
-
56
- const selected = InstanceEngineCyberia.getSelectedItemIds();
57
-
58
- // Drop flags for items no longer selected so the payload never carries them.
59
- for (const key of Object.keys(InstanceEngineCyberia.itemInventoryFlags)) {
60
- if (!selected.includes(key)) delete InstanceEngineCyberia.itemInventoryFlags[key];
61
- }
62
-
63
- if (selected.length === 0) {
64
- htmls(`.${containerId}`, html`<div style="color:#888;font-size:12px;">No item IDs selected.</div>`);
65
- return;
66
- }
67
-
68
- let listHtml = '';
69
- for (const itemId of selected) {
70
- const checked = InstanceEngineCyberia.itemInventoryFlags[itemId] ? 'checked' : '';
71
- listHtml += html`<div class="fl" style="border-bottom:1px solid #444;padding:4px 0;align-items:center;">
72
- <div class="in fll" style="flex:1;font-size:12px;font-family:monospace;">${itemId}</div>
73
- <div class="in fll" style="display:flex;align-items:center;justify-content:flex-end;">
74
- <label style="font-size:11px;cursor:pointer;display:flex;align-items:center;gap:5px;">
75
- <input
76
- type="checkbox"
77
- class="instance-engine-item-inv-checkbox"
78
- data-item-id="${itemId}"
79
- ${checked}
80
- style="cursor:pointer;"
81
- />
82
- Default Player Inventory
83
- </label>
84
- </div>
85
- </div>`;
86
- }
87
- htmls(`.${containerId}`, listHtml);
88
-
89
- container.querySelectorAll('.instance-engine-item-inv-checkbox').forEach((cb) => {
90
- cb.onchange = () => {
91
- InstanceEngineCyberia.itemInventoryFlags[cb.dataset.itemId] = cb.checked;
92
- };
93
- });
94
- }
95
-
96
- static syncItemIdsDropdownSelection(itemIds = []) {
97
- const dropdownId = InstanceEngineCyberia.itemIdsDropdownId;
98
- if (!DropDown.Tokens[dropdownId]) return;
99
-
100
- // Accept both the new [{ id, defaultPlayerInventory }] shape and the legacy
101
- // string[] shape so older instance docs keep loading.
102
- const normalized = (itemIds || [])
103
- .map((entry) => (typeof entry === 'string' ? { id: entry, defaultPlayerInventory: false } : entry))
104
- .filter((entry) => entry && entry.id);
105
-
106
- DropDown.Tokens[dropdownId].value = [];
107
- if (s(`.${dropdownId}`)) s(`.${dropdownId}`).value = [];
108
- DropDown.Tokens[dropdownId].oncheckvalues = {};
109
- htmls(`.dropdown-current-${dropdownId}`, '');
110
- htmls(`.${dropdownId}-render-container`, '');
111
-
112
- InstanceEngineCyberia.itemInventoryFlags = {};
113
- const ids = [];
114
- for (const entry of normalized) {
115
- const key = dropdownValueKey(entry.id);
116
- DropDown.Tokens[dropdownId].oncheckvalues[key] = {
117
- data: entry.id,
118
- display: entry.id,
119
- value: entry.id,
120
- };
121
- InstanceEngineCyberia.itemInventoryFlags[entry.id] = !!entry.defaultPlayerInventory;
122
- ids.push(entry.id);
123
- }
124
- DropDown.Tokens[dropdownId].value = [...ids];
125
- if (s(`.${dropdownId}`)) s(`.${dropdownId}`).value = [...ids];
126
- DropDown.Tokens[dropdownId]._renderSelectedBadges?.();
127
- InstanceEngineCyberia.renderItemInventoryList();
128
- }
129
-
130
- static async buildItemIdsDropdown() {
131
- return await DropDown.instance({
132
- id: InstanceEngineCyberia.itemIdsDropdownId,
133
- label: html`Object Layer Item IDs`,
134
- data: [],
135
- type: 'checkbox',
136
- containerClass: 'inl',
137
- excludeSelected: true,
138
- serviceProvider: async (q) => {
139
- const result = await ObjectLayerService.searchItemIds({ q });
140
- if (result.status === 'success' && result.data?.itemIds) {
141
- return result.data.itemIds.map((itemId) => createDropdownOption(itemId));
142
- }
143
- return [];
144
- },
145
- });
146
- }
147
-
148
25
  static renderPortalList(containerId) {
149
26
  const container = s(`.${containerId}`);
150
27
  if (!container) return;
@@ -237,7 +114,6 @@ class InstanceEngineCyberia {
237
114
  const idHotReloadUrl = 'instance-engine-input-hot-reload-url';
238
115
  const idThumbnail = 'instance-engine-input-thumbnail';
239
116
  const idMapCodesDropdown = 'instance-engine-map-codes-dropdown';
240
- const idItemIdsDropdown = InstanceEngineCyberia.itemIdsDropdownId;
241
117
  const managementId = 'modal-cyberia-instance-engine';
242
118
  const portalListId = 'instance-engine-portal-list';
243
119
  const idSourceMapCode = 'instance-engine-source-map-code';
@@ -255,6 +131,7 @@ class InstanceEngineCyberia {
255
131
  const idAoiRadius = 'instance-engine-aoi-radius';
256
132
 
257
133
  InstanceEngineCyberia.currentInstanceId = null;
134
+ InstanceEngineCyberia.currentInstanceCode = null;
258
135
  InstanceEngineCyberia.currentThumbnailId = null;
259
136
  InstanceEngineCyberia.thumbnailDirty = false;
260
137
  InstanceEngineCyberia.portals = [];
@@ -268,10 +145,6 @@ class InstanceEngineCyberia {
268
145
  const cyberiaMapCodes = DropDown.Tokens[idMapCodesDropdown]?.value
269
146
  ? [...DropDown.Tokens[idMapCodesDropdown].value]
270
147
  : [];
271
- const itemIds = InstanceEngineCyberia.getSelectedItemIds().map((id) => ({
272
- id,
273
- defaultPlayerInventory: !!InstanceEngineCyberia.itemInventoryFlags[id],
274
- }));
275
148
  const payload = {
276
149
  code: s(`.${idCode}`)?.value || '',
277
150
  name: s(`.${idName}`)?.value || '',
@@ -279,7 +152,6 @@ class InstanceEngineCyberia {
279
152
  tags,
280
153
  status: DropDown.Tokens[idStatus]?.value || 'unlisted',
281
154
  cyberiaMapCodes,
282
- itemIds,
283
155
  portals: InstanceEngineCyberia.portals,
284
156
  playerSpawn: {
285
157
  sourceMapCode: s(`.${idSpawnMapCode}`)?.value?.trim() || '',
@@ -358,6 +230,9 @@ class InstanceEngineCyberia {
358
230
  }
359
231
  if (result.status === 'success') {
360
232
  if (result.data?._id) InstanceEngineCyberia.currentInstanceId = result.data._id;
233
+ // A newly saved instance becomes the one the URL points at.
234
+ InstanceEngineCyberia.currentInstanceCode = body.code || null;
235
+ setQueryParams({ instanceCode: body.code || null }, { replace: true });
361
236
  // The conf is auto-upserted on instance save; persist the AOI radius onto
362
237
  // it now that we know the instance code resolves to a conf document.
363
238
  await persistAoiRadius(body.code);
@@ -372,6 +247,8 @@ class InstanceEngineCyberia {
372
247
 
373
248
  const loadInstance = async (instanceData) => {
374
249
  InstanceEngineCyberia.currentInstanceId = instanceData._id || null;
250
+ InstanceEngineCyberia.currentInstanceCode = instanceData.code || null;
251
+ setQueryParams({ instanceCode: instanceData.code || null }, { replace: true });
375
252
  if (s(`.${idCode}`)) s(`.${idCode}`).value = instanceData.code || '';
376
253
  if (s(`.${idName}`)) s(`.${idName}`).value = instanceData.name || '';
377
254
  if (s(`.${idDescription}`)) s(`.${idDescription}`).value = instanceData.description || '';
@@ -450,7 +327,6 @@ class InstanceEngineCyberia {
450
327
  }
451
328
  }
452
329
 
453
- InstanceEngineCyberia.syncItemIdsDropdownSelection(instanceData.itemIds || instanceData.itemsId || []);
454
330
 
455
331
  // Load portals
456
332
  InstanceEngineCyberia.portals = (instanceData.portals || []).map((p) => ({
@@ -478,6 +354,8 @@ class InstanceEngineCyberia {
478
354
 
479
355
  const resetForm = () => {
480
356
  InstanceEngineCyberia.currentInstanceId = null;
357
+ InstanceEngineCyberia.currentInstanceCode = null;
358
+ setQueryParams({ instanceCode: null }, { replace: true });
481
359
  InstanceEngineCyberia.currentThumbnailId = null;
482
360
  InstanceEngineCyberia.thumbnailDirty = false;
483
361
  if (s(`.${idCode}`)) s(`.${idCode}`).value = '';
@@ -502,14 +380,6 @@ class InstanceEngineCyberia {
502
380
  htmls(`.dropdown-current-${idMapCodesDropdown}`, '');
503
381
  htmls(`.${idMapCodesDropdown}-render-container`, '');
504
382
  }
505
- if (DropDown.Tokens[idItemIdsDropdown]) {
506
- DropDown.Tokens[idItemIdsDropdown].oncheckvalues = {};
507
- DropDown.Tokens[idItemIdsDropdown].value = [];
508
- htmls(`.dropdown-current-${idItemIdsDropdown}`, '');
509
- htmls(`.${idItemIdsDropdown}-render-container`, '');
510
- }
511
- InstanceEngineCyberia.itemInventoryFlags = {};
512
- InstanceEngineCyberia.renderItemInventoryList();
513
383
  InstanceEngineCyberia.portals = [];
514
384
  InstanceEngineCyberia.renderPortalList(portalListId);
515
385
  if (s(`.${idSpawnMapCode}`)) s(`.${idSpawnMapCode}`).value = '';
@@ -519,9 +389,32 @@ class InstanceEngineCyberia {
519
389
  if (s(`.${idAoiRadius}`)) s(`.${idAoiRadius}`).value = '';
520
390
  };
521
391
 
522
- setTimeout(() => {
392
+ // ?instanceCode=<code> loads that instance into the form on arrival.
393
+ const loadInstanceByCode = async (code) => {
394
+ if (!code || code === InstanceEngineCyberia.currentInstanceCode) return;
395
+ const { status, data } = await CyberiaInstanceService.get({ id: code });
396
+ if (status !== 'success' || !data?._id) {
397
+ NotificationManager.Push({ html: `No instance answers to the code "${code}"`, status: 'error' });
398
+ return;
399
+ }
400
+ await loadInstance(data);
401
+ NotificationManager.Push({ html: `Instance "${data.name || data.code}" loaded`, status: 'success' });
402
+ };
403
+
404
+ listenQueryParamsChange({
405
+ id: 'cyberia-instance-engine-query-listener',
406
+ event: async (queryParams) => {
407
+ if (!s(`.${idCode}`)) return;
408
+ const code = queryParams.instanceCode || null;
409
+ if (code) await loadInstanceByCode(code);
410
+ else if (InstanceEngineCyberia.currentInstanceCode) resetForm();
411
+ },
412
+ });
413
+
414
+ setTimeout(async () => {
523
415
  if (s(`.btn-instance-engine-save`)) s(`.btn-instance-engine-save`).onclick = () => saveInstance();
524
416
  if (s(`.btn-instance-engine-new`)) s(`.btn-instance-engine-new`).onclick = () => resetForm();
417
+ await loadInstanceByCode(getQueryParams().instanceCode);
525
418
 
526
419
  if (s(`.btn-instance-engine-toggle-thumbnail`))
527
420
  s(`.btn-instance-engine-toggle-thumbnail`).onclick = () => {
@@ -595,6 +488,87 @@ class InstanceEngineCyberia {
595
488
  }
596
489
  };
597
490
 
491
+ // Syncs against what is on screen: the instance is persisted first so the conf and the map
492
+ // selection agree, then the server matches defaults to the entities those maps place.
493
+ if (s(`.btn-instance-engine-sync-entities`))
494
+ s(`.btn-instance-engine-sync-entities`).onclick = async () => {
495
+ const btn = s(`.btn-instance-engine-sync-entities`);
496
+ const status = s(`.instance-engine-sync-entities-status`);
497
+ const code = s(`.${idCode}`)?.value?.trim();
498
+ if (!code) {
499
+ NotificationManager.Push({ html: 'Instance code is required to sync.', status: 'error' });
500
+ return;
501
+ }
502
+ if (btn) btn.disabled = true;
503
+ if (status) htmls(`.instance-engine-sync-entities-status`, 'Syncing…');
504
+ try {
505
+ const persisted = await persistInstance({ notify: false });
506
+ if (persisted?.status !== 'success') {
507
+ NotificationManager.Push({ html: persisted?.message || 'Could not save the instance.', status: 'error' });
508
+ if (status) htmls(`.instance-engine-sync-entities-status`, '');
509
+ return;
510
+ }
511
+ const mapCodes = DropDown.Tokens[idMapCodesDropdown]?.value
512
+ ? [...DropDown.Tokens[idMapCodesDropdown].value]
513
+ : [];
514
+ const result = await CyberiaEntityTypeDefaultService.syncInstance({
515
+ instanceCode: code,
516
+ body: { mapCodes },
517
+ });
518
+ if (result.status === 'error') {
519
+ NotificationManager.Push({ html: result.message, status: 'error' });
520
+ if (status) htmls(`.instance-engine-sync-entities-status`, '');
521
+ return;
522
+ }
523
+ const {
524
+ linked = [],
525
+ skipped = [],
526
+ dropped = [],
527
+ entityDefaults = [],
528
+ skills = [],
529
+ entitiesUpdated = [],
530
+ conflicts = [],
531
+ duplicates = [],
532
+ } = result.data || {};
533
+ const placedRewritten = entitiesUpdated.reduce((sum, { entities }) => sum + entities, 0);
534
+ if (status) {
535
+ htmls(
536
+ `.instance-engine-sync-entities-status`,
537
+ html`${entityDefaults.length} reference${1 === entityDefaults.length ? '' : 's'}${linked.length
538
+ ? ` · linked ${linked.length}`
539
+ : ''}${dropped.length ? ` · dropped ${dropped.length}` : ''}${skills.length
540
+ ? ` · ${skills.length} skill${1 === skills.length ? '' : 's'}`
541
+ : ''}${placedRewritten
542
+ ? ` · placed ${placedRewritten}`
543
+ : ''}${skipped.length
544
+ ? html`<div style="margin-top:3px;color:#c90;">
545
+ ${skipped.length} match${1 === skipped.length ? '' : 'es'} already referenced by another
546
+ instance — link deliberately in the Entity engine, or clone it there.
547
+ </div>`
548
+ : ''}${conflicts.length
549
+ ? html`<div style="margin-top:3px;color:#c90;">
550
+ ${conflicts.length} match${1 === conflicts.length ? '' : 'es'} not linked: this instance already
551
+ references a default for the same entity type and live items. Link it in the Entity engine only
552
+ if you mean to replace the one it has.
553
+ </div>`
554
+ : ''}${duplicates.length
555
+ ? html`<div style="margin-top:3px;color:#c90;">
556
+ ${duplicates.length} reference${1 === duplicates.length ? '' : 's'} answer for a build another
557
+ reference already covers — which one applies depends on list order. Unlink or delete the
558
+ redundant record in the Entity engine.
559
+ </div>`
560
+ : ''}`,
561
+ );
562
+ }
563
+ NotificationManager.Push({
564
+ html: `Entity type defaults synced — ${entityDefaults.length} referenced`,
565
+ status: 'success',
566
+ });
567
+ } finally {
568
+ if (btn) btn.disabled = false;
569
+ }
570
+ };
571
+
598
572
  if (s(`.btn-instance-engine-hot-reload`))
599
573
  s(`.btn-instance-engine-hot-reload`).onclick = () => triggerHotReload('full');
600
574
  if (s(`.btn-instance-engine-hot-reload-incremental`))
@@ -695,19 +669,6 @@ class InstanceEngineCyberia {
695
669
  InstanceEngineCyberia.renderPortalList(portalListId);
696
670
  };
697
671
 
698
- // Keep the per-item "Default Player Inventory" toggles in sync with the
699
- // item-ids dropdown. Every add/remove/clear re-renders the dropdown badge
700
- // container, so observing it is a reliable change hook (the dropdown
701
- // exposes no onChange callback).
702
- const itemBadgeContainer = s(`.dropdown-current-${idItemIdsDropdown}`);
703
- if (itemBadgeContainer) {
704
- const itemInventoryObserver = new MutationObserver(() => {
705
- InstanceEngineCyberia.renderItemInventoryList();
706
- });
707
- itemInventoryObserver.observe(itemBadgeContainer, { childList: true, subtree: true });
708
- }
709
- InstanceEngineCyberia.renderItemInventoryList();
710
-
711
672
  ThemeEvents['instance-engine-theme'] = () => {
712
673
  InstanceEngineCyberia.renderPortalList(portalListId);
713
674
  };
@@ -858,11 +819,6 @@ class InstanceEngineCyberia {
858
819
  },
859
820
  })}
860
821
  </div>
861
- <div class="in section-mp" style="margin-top: 10px;">${await InstanceEngineCyberia.buildItemIdsDropdown()}</div>
862
- <div class="in section-mp" style="margin-top: 5px;">
863
- <div class="in input-label" style="font-size:13px;margin-bottom:5px;">Default Player Inventory</div>
864
- <div class="in ${InstanceEngineCyberia.itemInventoryListId}" style="max-height:200px;overflow-y:auto;"></div>
865
- </div>
866
822
  <div class="in section-mp" style="margin-top: 10px;">
867
823
  <div class="in input-label" style="font-size:14px;margin-bottom:5px;">Portals</div>
868
824
  ${dynamicCol({ containerSelector: 'instance-engine-container', id: dcPortalSource, type: 'search-inputs' })}
@@ -1029,6 +985,19 @@ class InstanceEngineCyberia {
1029
985
  </div>
1030
986
  ${canMutate
1031
987
  ? html`<div class="in section-mp" style="margin-top: 10px;">
988
+ <div class="in" style="color:#888;font-size:12px;margin-bottom:6px;">
989
+ Points this instance's conf at every entity-type default the maps selected above actually place —
990
+ matched the way the runtime matches them, by live item ids — and at every skill their items trigger.
991
+ References whose document is gone, and skills nothing carries, are dropped. Saves the instance first,
992
+ so the maps it syncs against are the ones on screen.
993
+ </div>
994
+ ${await BtnIcon.instance({
995
+ class: 'wfa btn-instance-engine-sync-entities',
996
+ label: html`<i class="fa-solid fa-diagram-project"></i> Sync Entity Type Defaults`,
997
+ })}
998
+ <div class="in instance-engine-sync-entities-status" style="margin-top:5px;font-size:12px;"></div>
999
+ </div>
1000
+ <div class="in section-mp" style="margin-top: 10px;">
1032
1001
  ${await BtnIcon.instance({
1033
1002
  class: 'wfa btn-instance-engine-toggle-hot-reload',
1034
1003
  label: html`<i class="fa-solid fa-caret-right instance-engine-hot-reload-caret"></i> Hot Reload`,
@@ -162,19 +162,23 @@ const enrichWithLiveStatus = async (instances, opts) => {
162
162
  // the instance catalogue, then (when `liveStatus`) overlays real Online/Busy/
163
163
  // Full/Maintenance/Offline from each world's metrics API. Swap the whole thing
164
164
  // via `render({ fetchInstances })` to source instances from anywhere.
165
+ // `limit` keeps only the first N catalogue entries *before* the live overlay so
166
+ // lightweight consumers (the portal landing) don't probe every world's metrics.
165
167
  const defaultInstanceProvider = async ({
166
168
  clientBaseUrl = DEFAULT_CLIENT_BASE_URL,
167
169
  serverBaseUrl = DEFAULT_SERVER_BASE_URL,
168
170
  defaultInstanceCode = DEFAULT_INSTANCE_CODE,
169
171
  liveStatus = true,
172
+ limit,
170
173
  } = {}) => {
171
174
  const res = await CyberiaInstanceService.get({ sort: 'createdAt', order: 'asc', limit: 60, fallback: true });
172
175
  if (!res || res.status !== 'success') throw new Error(res?.message || 'Could not load instances');
173
176
  const raw = res.data;
174
177
  const list = Array.isArray(raw) ? raw : Array.isArray(raw?.data) ? raw.data : [];
175
- const instances = list
178
+ let instances = list
176
179
  .filter((doc) => doc && doc.status !== 'archived')
177
180
  .map((doc) => normalizeInstance(doc, { clientBaseUrl }));
181
+ if (Number.isInteger(limit) && limit > 0) instances = instances.slice(0, limit);
178
182
  if (liveStatus && instances.length > 0) await enrichWithLiveStatus(instances, { serverBaseUrl, defaultInstanceCode });
179
183
  return instances;
180
184
  };
@@ -895,6 +899,8 @@ class InstanceSelectionView {
895
899
  export {
896
900
  InstanceSelectionView,
897
901
  defaultInstanceProvider,
902
+ placeholderThumbnail,
903
+ DEFAULT_CLIENT_BASE_URL,
898
904
  normalizeInstance,
899
905
  resolveStatus,
900
906
  statusFromMetrics,