@underpostnet/cyberia 3.2.80 → 3.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.env.example +50 -15
- package/.github/workflows/coverall.cyberia.ci.yml +87 -0
- package/.github/workflows/cyberia-client.cd.yml +2 -7
- package/.github/workflows/cyberia-server.cd.yml +2 -7
- package/.github/workflows/docker-image.cyberia-client.ci.yml +4 -4
- package/.github/workflows/docker-image.cyberia-client.dev.ci.yml +4 -4
- package/.github/workflows/docker-image.cyberia-server.ci.yml +4 -4
- package/.github/workflows/docker-image.cyberia-server.dev.ci.yml +4 -4
- package/.github/workflows/docker-image.engine-cyberia.ci.yml +3 -3
- package/.github/workflows/docker-image.engine-cyberia.dev.ci.yml +3 -3
- package/.github/workflows/engine-cyberia.cd.yml +3 -67
- package/.github/workflows/ghpkg.ci.yml +7 -1
- package/.github/workflows/pwa-microservices-template-page.cd.yml +1 -16
- package/.github/workflows/pwa-microservices-template-test.ci.yml +1 -1
- package/.github/workflows/release.cd.yml +1 -9
- package/CHANGELOG.md +291 -1
- package/CLI-HELP.md +174 -23
- package/Dockerfile +1 -1
- package/Dockerfile.dev +1 -1
- package/Dockerfile.test +1 -1
- package/bin/build.js +7 -5
- package/bin/cyberia.js +272 -98
- package/bin/deploy.js +19 -17
- package/bin/index.js +272 -98
- package/compose.env +50 -15
- package/conf.js +2 -0
- package/deploy/dd-cyberia/deploy.sh +42 -0
- package/deploy/dd-cyberia/init.sh +63 -0
- package/deploy/dd-cyberia/sync-deploy.sh +194 -0
- package/deploy/lib/logging.sh +96 -0
- package/deploy/pwa-microservices-template/deploy.sh +72 -0
- package/deploy/release/deploy.sh +62 -0
- package/deployment.yaml +1 -210
- package/docker-compose.yml +90 -85
- package/hardhat/package-lock.json +139 -131
- package/hardhat/package.json +4 -4
- package/manifests/cronjobs/dd-cron/dd-cron-backup.yaml +5 -1
- package/manifests/cronjobs/dd-cron/dd-cron-dns.yaml +1 -1
- package/manifests/cronjobs/dd-cron/dd-cron-vultr.yaml +52 -0
- package/manifests/deployment/dd-cyberia-development/deployment.yaml +1 -210
- package/manifests/deployment/dd-cyberia-development/gateway.yaml +80 -0
- package/manifests/deployment/dd-cyberia-development/httproute.yaml +504 -0
- package/manifests/deployment/dd-cyberia-development/proxy.yaml +12 -12
- package/manifests/deployment/dd-cyberia-development/pv-pvc.yaml +0 -82
- package/manifests/deployment/dd-cyberia-development/traffic-service.yaml +121 -0
- package/manifests/deployment/dd-default-development/deployment.yaml +2 -2
- package/manifests/deployment/playwright/deployment.yaml +1 -1
- package/manifests/mongodb/kustomization.yaml +4 -1
- package/manifests/mongodb/statefulset.yaml +4 -0
- package/manifests/mongodb/storage-class.yaml +9 -2
- package/nginx.conf +86 -16
- package/package.json +2 -2
- package/proxy.yaml +12 -12
- package/pv-pvc.yaml +0 -82
- package/scripts/audit-selinux.sh +64 -0
- package/scripts/coverall-test-cyberia.sh +24 -0
- package/scripts/coverall-test.sh +24 -0
- package/scripts/gpu-diag.sh +0 -0
- package/scripts/ip-info.sh +0 -0
- package/scripts/k3s-node-setup.sh +18 -15
- package/scripts/kubeadm-node-setup.sh +12 -23
- package/scripts/link-local-underpost-cli.sh +0 -0
- package/scripts/lxd-vm-setup.sh +0 -0
- package/scripts/maas-nat-firewalld.sh +0 -0
- package/scripts/nat-iptables.sh +12 -4
- package/scripts/rhel-grpc-setup.sh +0 -0
- package/scripts/rocky-kickstart.sh +25 -9
- package/scripts/test-monitor.sh +4 -3
- package/src/api/cyberia-action/cyberia-action.model.js +1 -0
- package/src/api/cyberia-instance/cyberia-fallback-default-items.js +63 -0
- package/src/api/cyberia-instance/cyberia-fallback-world.js +49 -15
- package/src/api/cyberia-instance/cyberia-instance-map.service.js +8 -12
- package/src/api/cyberia-instance/cyberia-instance.controller.js +3 -0
- package/src/api/cyberia-instance/cyberia-instance.router.js +13 -0
- package/src/api/cyberia-instance/cyberia-instance.service.js +43 -0
- package/src/api/cyberia-instance/cyberia-portal-connector.js +7 -5
- package/src/api/cyberia-instance/cyberia-random-source.js +80 -0
- package/src/api/cyberia-instance/cyberia-world-generator.js +4 -3
- package/src/api/cyberia-instance-conf/cyberia-instance-conf.model.js +1 -0
- package/src/api/cyberia-server-defaults/cyberia-server-defaults.js +87 -1
- package/src/api/object-layer/object-layer.model.js +204 -3
- package/src/api/object-layer/object-layer.service.js +1 -14
- package/src/cli/baremetal.js +1 -2
- package/src/cli/cloud-init.js +1 -1
- package/src/cli/cluster.js +786 -96
- package/src/cli/db.js +11 -4
- package/src/cli/deploy.js +1698 -177
- package/src/cli/docker-compose.js +19 -178
- package/src/cli/env.js +1 -1
- package/src/cli/image.js +15 -7
- package/src/cli/index.js +245 -44
- package/src/cli/ipfs.js +82 -11
- package/src/cli/lxd.js +1 -1
- package/src/cli/monitor.js +2 -2
- package/src/cli/release.js +57 -22
- package/src/cli/repository.js +12 -10
- package/src/cli/run.js +2195 -427
- package/src/cli/secrets.js +969 -0
- package/src/cli/ssh.js +206 -105
- package/src/cli/system.js +26 -13
- package/src/cli/test.js +1 -1
- package/src/cli/vultr.js +583 -0
- package/src/cli/wireguard.js +2125 -0
- package/src/client/components/cyberia/FallbackWorldEngineCyberia.js +368 -0
- package/src/client/components/cyberia/InstanceSelectionView.js +11 -8
- package/src/client/components/cyberia/SharedDefaultsCyberia.js +5 -0
- package/src/client/components/cyberia-portal/AppShellCyberiaPortal.js +33 -0
- package/src/client/components/cyberia-portal/RouterCyberiaPortal.js +4 -0
- package/src/client/components/cyberia-portal/TranslateCyberiaPortal.js +4 -0
- package/src/client/public/cyberia-docs/ACTION-SYSTEM.md +106 -39
- package/src/client/public/cyberia-docs/ARCHITECTURE.md +35 -3
- package/src/client/public/cyberia-docs/CYBERIA-CLI.md +44 -7
- package/src/client/public/cyberia-docs/CYBERIA-SERVER.md +1 -1
- package/src/client/public/cyberia-docs/ROADMAP.md +1 -1
- package/src/client/public/cyberia-docs/WHITE-PAPER.md +1 -1
- package/src/client/services/cyberia-instance/cyberia-instance.service.js +40 -0
- package/src/client/services/object-layer/object-layer.management.js +4 -4
- package/src/client-builder/client-build.js +102 -13
- package/src/client-builder/ssr.js +27 -73
- package/src/db/mongo/MongoBootstrap.js +295 -54
- package/src/db/mongo/MongooseDB.js +51 -32
- package/src/index.js +25 -1
- package/src/projects/cyberia/besu-genesis-generator.js +3 -2
- package/src/projects/cyberia/catalog-cyberia.js +5 -0
- package/src/projects/cyberia/generate-saga.js +14 -23
- package/src/projects/cyberia/hot-reload-trigger.js +3 -3
- package/src/projects/cyberia/instance-data.js +63 -3
- package/src/projects/cyberia/object-layer.js +11 -21
- package/src/projects/underpost/catalog-underpost.js +4 -1
- package/src/runtime/cyberia-client/Dockerfile +1 -1
- package/src/runtime/cyberia-client/Dockerfile.dev +1 -1
- package/src/runtime/cyberia-server/Dockerfile +1 -1
- package/src/runtime/cyberia-server/Dockerfile.dev +1 -1
- package/src/runtime/engine-cyberia/Dockerfile +1 -1
- package/src/runtime/engine-cyberia/Dockerfile.dev +1 -1
- package/src/runtime/engine-cyberia/Dockerfile.test +1 -1
- package/src/runtime/engine-cyberia/compose.env +50 -15
- package/src/runtime/engine-cyberia/docker-compose.yml +90 -85
- package/src/runtime/engine-cyberia/nginx.conf +86 -16
- package/src/server/backup.js +1 -1
- package/src/server/conf.js +1216 -168
- package/src/server/cri.js +70 -0
- package/src/server/cron.js +249 -51
- package/src/server/dns.js +100 -6
- package/src/server/environment.js +98 -0
- package/src/server/forward-proxy.js +549 -0
- package/src/server/middlewares.js +56 -1
- package/src/server/process.js +0 -1
- package/src/server/selinux.js +185 -0
- package/src/server/systemd.js +205 -0
- package/src/server/underpost-compression.js +186 -0
- package/src/server/underpost-gateway.js +1083 -0
- package/src/server/underpost-ingress.js +380 -0
- package/test/cluster-instances.test.js +435 -0
- package/test/cyberia-instance-conf-defaults.test.js +1 -0
- package/test/deploy-node-placement.test.js +45 -0
- package/test/instance-traffic-plan.test.js +710 -0
- package/test/selinux.test.js +71 -0
- package/test/sops-secret-store.test.js +612 -0
- package/test/underpost-gateway.test.js +510 -0
- package/test/underpost-ingress.test.js +305 -0
- package/test/wireguard-edge.test.js +1177 -0
|
@@ -49,22 +49,25 @@ if [ -z "$(ls -A "$ENGINE_ROOT")" ]; then
|
|
|
49
49
|
fi
|
|
50
50
|
|
|
51
51
|
# ---------------------------------------------------------------------------
|
|
52
|
-
#
|
|
52
|
+
# System-wide Node.js is required by service units under SELinux.
|
|
53
53
|
# ---------------------------------------------------------------------------
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
export NVM_DIR="$([ -z "${XDG_CONFIG_HOME-}" ] && printf %s "${HOME}/.nvm" || printf %s "${XDG_CONFIG_HOME}/nvm")"
|
|
59
|
-
# shellcheck disable=SC1090
|
|
60
|
-
[ -s "$NVM_DIR/nvm.sh" ] && \. "$NVM_DIR/nvm.sh"
|
|
54
|
+
if command -v dnf >/dev/null 2>&1; then
|
|
55
|
+
sudo dnf install -y policycoreutils policycoreutils-python-utils selinux-policy-targeted audit
|
|
56
|
+
fi
|
|
61
57
|
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
58
|
+
if ! command -v node >/dev/null 2>&1 || ! node --version 2>/dev/null | grep -q '^v24'; then
|
|
59
|
+
echo "Installing system-wide Node.js 24..."
|
|
60
|
+
if command -v dnf >/dev/null 2>&1; then
|
|
61
|
+
curl -fsSL https://rpm.nodesource.com/setup_24.x | sudo bash -
|
|
62
|
+
sudo dnf install -y nodejs
|
|
63
|
+
elif command -v apt-get >/dev/null 2>&1; then
|
|
64
|
+
curl -fsSL https://deb.nodesource.com/setup_24.x | sudo bash -
|
|
65
|
+
sudo apt-get install -y nodejs
|
|
66
|
+
else
|
|
67
|
+
echo "ERROR: a supported system package manager is required for Node.js 24" >&2
|
|
68
|
+
exit 1
|
|
69
|
+
fi
|
|
70
|
+
fi
|
|
68
71
|
|
|
69
72
|
echo "
|
|
70
73
|
██╗░░░██╗███╗░░██╗██████╗░███████╗██████╗░██████╗░░█████╗░░██████╗████████╗
|
|
@@ -116,7 +119,7 @@ if [ "$ROLE" = "control" ]; then
|
|
|
116
119
|
curl -sfL https://get.k3s.io | \
|
|
117
120
|
K3S_URL="https://${CONTROL_IP}:6443" \
|
|
118
121
|
K3S_TOKEN="${K3S_TOKEN}" \
|
|
119
|
-
sh -s - agent
|
|
122
|
+
sh -s - agent $(if command -v selinuxenabled >/dev/null 2>&1 && selinuxenabled; then printf '%s' '--selinux'; fi)
|
|
120
123
|
|
|
121
124
|
echo ""
|
|
122
125
|
echo "K3s worker joined https://${CONTROL_IP}:6443 successfully."
|
|
@@ -5,8 +5,8 @@ set -euo pipefail
|
|
|
5
5
|
# Underpost Kubeadm Node Setup (bare-metal physical node)
|
|
6
6
|
#
|
|
7
7
|
# Mirrors scripts/k3s-node-setup.sh but for kubeadm on real hardware: it runs on
|
|
8
|
-
# the freshly deployed OS (disk-installed Rocky) after first boot, installs
|
|
9
|
-
# Node.js, ensures the engine source is present, and drives the cluster bring-up
|
|
8
|
+
# the freshly deployed OS (disk-installed Rocky) after first boot, installs
|
|
9
|
+
# system-wide Node.js, ensures the engine source is present, and drives the cluster bring-up
|
|
10
10
|
# through the local engine entrypoint `node bin cluster` (src/cli/cluster.js) —
|
|
11
11
|
# reusing its kubeadm + Contour + host-init logic instead of reimplementing it.
|
|
12
12
|
#
|
|
@@ -146,33 +146,22 @@ if [ "$JOIN_ONLY" = "1" ]; then
|
|
|
146
146
|
fi
|
|
147
147
|
|
|
148
148
|
# ---------------------------------------------------------------------------
|
|
149
|
-
# 0. Base prerequisites
|
|
150
|
-
# NVM needs to extract Node.js and the engine clone needs. Install them first.
|
|
149
|
+
# 0. Base prerequisites.
|
|
151
150
|
# ---------------------------------------------------------------------------
|
|
152
|
-
log "Installing base prerequisites
|
|
153
|
-
sudo dnf install -y tar xz gzip bzip2 git curl ca-certificates which findutils 2>/dev/null \
|
|
154
|
-
|| dnf install -y tar xz gzip bzip2 git curl ca-certificates which findutils
|
|
151
|
+
log "Installing base prerequisites..."
|
|
152
|
+
sudo dnf install -y tar xz gzip bzip2 git curl ca-certificates which findutils policycoreutils policycoreutils-python-utils selinux-policy-targeted audit 2>/dev/null \
|
|
153
|
+
|| dnf install -y tar xz gzip bzip2 git curl ca-certificates which findutils policycoreutils policycoreutils-python-utils selinux-policy-targeted audit
|
|
155
154
|
|
|
156
155
|
# ---------------------------------------------------------------------------
|
|
157
|
-
# 1.
|
|
158
|
-
#
|
|
156
|
+
# 1. System-wide Node.js. Home-directory runtimes cannot be used by hardened
|
|
157
|
+
# systemd services under SELinux.
|
|
159
158
|
# ---------------------------------------------------------------------------
|
|
160
159
|
if command -v node >/dev/null 2>&1 && node --version 2>/dev/null | grep -q '^v24'; then
|
|
161
|
-
log "Node.js $(node --version) already installed
|
|
160
|
+
log "Node.js $(node --version) already installed"
|
|
162
161
|
else
|
|
163
|
-
log "Installing
|
|
164
|
-
curl -
|
|
165
|
-
|
|
166
|
-
export NVM_DIR="$([ -z "${XDG_CONFIG_HOME-}" ] && printf %s "${HOME}/.nvm" || printf %s "${XDG_CONFIG_HOME}/nvm")"
|
|
167
|
-
# shellcheck disable=SC1090
|
|
168
|
-
[ -s "$NVM_DIR/nvm.sh" ] && \. "$NVM_DIR/nvm.sh"
|
|
169
|
-
|
|
170
|
-
nvm install 24.15.0
|
|
171
|
-
nvm use 24.15.0
|
|
172
|
-
nvm alias default 24.15.0
|
|
173
|
-
ln -sf "$(command -v node)" /usr/local/bin/node
|
|
174
|
-
ln -sf "$(command -v npm)" /usr/local/bin/npm
|
|
175
|
-
ln -sf "$(command -v npx)" /usr/local/bin/npx
|
|
162
|
+
log "Installing system-wide Node.js 24..."
|
|
163
|
+
curl -fsSL https://rpm.nodesource.com/setup_24.x | sudo bash -
|
|
164
|
+
sudo dnf install -y nodejs
|
|
176
165
|
fi
|
|
177
166
|
|
|
178
167
|
echo "
|
|
File without changes
|
package/scripts/lxd-vm-setup.sh
CHANGED
|
File without changes
|
|
File without changes
|
package/scripts/nat-iptables.sh
CHANGED
|
@@ -32,16 +32,24 @@ sudo firewall-cmd --permanent --zone=public --add-service=ssh
|
|
|
32
32
|
sudo firewall-cmd --permanent --zone=public --add-service=http
|
|
33
33
|
sudo firewall-cmd --permanent --zone=public --add-service=https
|
|
34
34
|
|
|
35
|
+
# QUIC / HTTP3. The `https` service above covers TCP 443 only, so without this
|
|
36
|
+
# an HTTP/3 gateway advertises `Alt-Svc: h3=":443"` and every client that acts
|
|
37
|
+
# on it silently fails over UDP while HTTP/2 keeps working.
|
|
38
|
+
sudo firewall-cmd --permanent --zone=public --add-port=443/udp
|
|
39
|
+
sudo firewall-cmd --permanent --zone=public --add-port=80/udp
|
|
40
|
+
|
|
35
41
|
echo "[INFO] Opening Kubernetes control plane ports..."
|
|
36
42
|
|
|
37
43
|
# Kubernetes API Server
|
|
38
44
|
sudo firewall-cmd --permanent --zone=public --add-port=6443/tcp
|
|
45
|
+
sudo firewall-cmd --permanent --zone=public --add-port=6443/udp
|
|
39
46
|
|
|
40
47
|
# etcd
|
|
41
48
|
sudo firewall-cmd --permanent --zone=public --add-port=2379-2380/tcp
|
|
42
49
|
|
|
43
50
|
# kubelet API
|
|
44
51
|
sudo firewall-cmd --permanent --zone=public --add-port=10250/tcp
|
|
52
|
+
sudo firewall-cmd --permanent --zone=public --add-port=10250/udp
|
|
45
53
|
|
|
46
54
|
# kube-scheduler
|
|
47
55
|
sudo firewall-cmd --permanent --zone=public --add-port=10259/tcp
|
|
@@ -98,10 +106,10 @@ sudo firewall-cmd --reload
|
|
|
98
106
|
echo
|
|
99
107
|
echo "[INFO] Sysctl status:"
|
|
100
108
|
sudo sysctl \
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
109
|
+
net.ipv4.ip_forward \
|
|
110
|
+
net.ipv6.conf.all.forwarding \
|
|
111
|
+
net.bridge.bridge-nf-call-iptables \
|
|
112
|
+
net.bridge.bridge-nf-call-ip6tables
|
|
105
113
|
|
|
106
114
|
echo
|
|
107
115
|
echo "[INFO] Firewall status:"
|
|
File without changes
|
|
@@ -168,7 +168,7 @@ PermitEmptyPasswords no
|
|
|
168
168
|
PasswordAuthentication ${SSHD_PASSWORD_AUTH}
|
|
169
169
|
ChallengeResponseAuthentication ${SSHD_KBD_INTERACTIVE}
|
|
170
170
|
KbdInteractiveAuthentication ${SSHD_KBD_INTERACTIVE}
|
|
171
|
-
UsePAM
|
|
171
|
+
UsePAM yes
|
|
172
172
|
X11Forwarding no
|
|
173
173
|
PrintMotd no
|
|
174
174
|
TCPKeepAlive yes
|
|
@@ -585,7 +585,7 @@ fi
|
|
|
585
585
|
post_status "installing" "\"detail\":\"installing packages\",\"disk\":\"\$REAL_DISK\""
|
|
586
586
|
|
|
587
587
|
ilog "Installing base packages with dnf --installroot ..."
|
|
588
|
-
PKGS="@core kernel grub2-tools openssh-server NetworkManager chrony dracut-config-generic rocky-release"
|
|
588
|
+
PKGS="@core kernel grub2-tools openssh-server NetworkManager chrony dracut-config-generic rocky-release policycoreutils policycoreutils-python-utils selinux-policy-targeted audit sudo"
|
|
589
589
|
if [ "\$FW" = "uefi" ]; then
|
|
590
590
|
PKGS="\$PKGS grub2-efi-x64 shim-x64 efibootmgr"
|
|
591
591
|
else
|
|
@@ -622,15 +622,10 @@ ilog "Configuring installed system (hostname, network, ssh, users) ..."
|
|
|
622
622
|
echo "\${TARGET_HOSTNAME:-rocky9}" > "\$TARGET_MNT/etc/hostname"
|
|
623
623
|
|
|
624
624
|
chroot "\$TARGET_MNT" /bin/bash -s << CHROOTEOF >> "\$KS_LOG" 2>&1
|
|
625
|
-
set
|
|
625
|
+
set -e
|
|
626
626
|
systemctl enable sshd NetworkManager chronyd 2>/dev/null
|
|
627
627
|
|
|
628
|
-
|
|
629
|
-
# first boot, files written from this installer chroot (authorized_keys, home
|
|
630
|
-
# dirs) carry the wrong security context, so sshd drops sessions ("Connection
|
|
631
|
-
# reset by peer") and PAM can block console logins. Permissive ignores contexts
|
|
632
|
-
# (no relabel reboot needed) and matches what kubeadm/cluster.js expect.
|
|
633
|
-
sed -i 's/^SELINUX=.*/SELINUX=permissive/' /etc/selinux/config 2>/dev/null || true
|
|
628
|
+
sed -i 's/^SELINUX=.*/SELINUX=enforcing/' /etc/selinux/config
|
|
634
629
|
|
|
635
630
|
# Ensure sshd host keys exist so sshd actually starts on first boot.
|
|
636
631
|
ssh-keygen -A 2>/dev/null || true
|
|
@@ -708,11 +703,19 @@ fi
|
|
|
708
703
|
# Key-only SSH on the installed system.
|
|
709
704
|
mkdir -p /etc/ssh/sshd_config.d
|
|
710
705
|
cat > /etc/ssh/sshd_config.d/00-underpost.conf <<SSHDEOF
|
|
706
|
+
Port ${SSH_PORT}
|
|
711
707
|
PermitRootLogin prohibit-password
|
|
712
708
|
PubkeyAuthentication yes
|
|
713
709
|
PasswordAuthentication no
|
|
710
|
+
KbdInteractiveAuthentication no
|
|
711
|
+
UsePAM yes
|
|
714
712
|
SSHDEOF
|
|
715
713
|
|
|
714
|
+
if [ "${SSH_PORT}" != "22" ]; then
|
|
715
|
+
semanage port -a -t ssh_port_t -p tcp "${SSH_PORT}" 2>/dev/null || \
|
|
716
|
+
semanage port -m -t ssh_port_t -p tcp "${SSH_PORT}"
|
|
717
|
+
fi
|
|
718
|
+
|
|
716
719
|
# Create the admin login accounts. create_user <name> writes the authorized key,
|
|
717
720
|
# wheel membership and passwordless sudo. Passwords are applied after the chroot.
|
|
718
721
|
create_user() {
|
|
@@ -738,11 +741,24 @@ chmod 600 /root/.ssh/authorized_keys
|
|
|
738
741
|
create_user "\${ADMIN_USER}"
|
|
739
742
|
create_user "\${DEPLOY_USER}"
|
|
740
743
|
|
|
744
|
+
restorecon -RF /etc/ssh /etc/sudoers.d /root/.ssh /home 2>/dev/null
|
|
745
|
+
touch /.autorelabel
|
|
746
|
+
|
|
741
747
|
# Build initramfs for the installed kernel.
|
|
742
748
|
KVER=\\\$(ls /lib/modules | head -1)
|
|
743
749
|
[ -n "\\\$KVER" ] && dracut -f /boot/initramfs-\\\$KVER.img "\\\$KVER"
|
|
744
750
|
CHROOTEOF
|
|
745
751
|
|
|
752
|
+
CHROOT_RC=\$?
|
|
753
|
+
if [ "\$CHROOT_RC" -ne 0 ]; then
|
|
754
|
+
ilog "FATAL: installed-system configuration failed (rc=\$CHROOT_RC)"
|
|
755
|
+
post_status "failed" "\"detail\":\"installed-system configuration rc=\$CHROOT_RC\""
|
|
756
|
+
for d in dev proc sys run; do umount -l "\$TARGET_MNT/\$d" 2>/dev/null; done
|
|
757
|
+
umount -R "\$TARGET_MNT" 2>/dev/null || true
|
|
758
|
+
rm -f "\$INSTALL_LOCK"
|
|
759
|
+
exit 1
|
|
760
|
+
fi
|
|
761
|
+
|
|
746
762
|
# Set console passwords AFTER the chroot heredoc, from decoded installer vars.
|
|
747
763
|
# Primary path: pipe 'user:password' into 'chroot chpasswd' (keeps password
|
|
748
764
|
# characters out of any heredoc). Fallback: hash with openssl and apply via
|
package/scripts/test-monitor.sh
CHANGED
|
@@ -24,7 +24,7 @@ MODE=runtime # runtime | instance
|
|
|
24
24
|
ENV=development # development | production
|
|
25
25
|
DEPLOY_ID=dd-test # deploy id (instance mode: parent of conf.instances.json)
|
|
26
26
|
INSTANCE_IDS= # instance mode: csv of ids (default: all in conf.instances.json)
|
|
27
|
-
IMAGE=underpost/wp:v3.
|
|
27
|
+
IMAGE=underpost/wp:v3.3.0 # runtime mode image (instance mode reads image from conf)
|
|
28
28
|
VERSIONS=green # csv of blue/green versions
|
|
29
29
|
REPLICAS=1 # replicas per deployment
|
|
30
30
|
NAMESPACE=default # k8s namespace
|
|
@@ -202,7 +202,8 @@ run_runtime_mode() {
|
|
|
202
202
|
setup_tls_secrets $(hosts_from "$SERVER_CONF")
|
|
203
203
|
expose_flags="--tls"
|
|
204
204
|
fi
|
|
205
|
-
[ "$DO_EXPOSE" = true ] && node bin
|
|
205
|
+
[ "$DO_EXPOSE" = true ] && node bin run expose "${DEPLOY_ID}-${ENV}-traffic-service" --namespace "$NAMESPACE" \
|
|
206
|
+
--local-proxy $DEV_FLAG $CLUSTER_FLAG $expose_flags
|
|
206
207
|
}
|
|
207
208
|
|
|
208
209
|
# ─────────────────────────── instance mode ───────────────────────────
|
|
@@ -234,7 +235,7 @@ run_instance_mode() {
|
|
|
234
235
|
# Plain HTTP: port-forward each instance service port directly.
|
|
235
236
|
for id in "${ids[@]}"; do
|
|
236
237
|
[ -n "$id" ] || continue
|
|
237
|
-
node bin
|
|
238
|
+
node bin run expose "${DEPLOY_ID}-${id}-${ENV}-traffic-service" --namespace "$NAMESPACE" $CLUSTER_FLAG || true
|
|
238
239
|
done
|
|
239
240
|
fi
|
|
240
241
|
}
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Fallback-world default items — process-local override store.
|
|
3
|
+
*
|
|
4
|
+
* The procedural fallback world is never persisted, so its default item set
|
|
5
|
+
* has no collection to live in. This module holds the set the FallbackWorldEngine
|
|
6
|
+
* view composed, in memory, for the lifetime of the engine process:
|
|
7
|
+
*
|
|
8
|
+
* - written by POST /api/cyberia-instance/fallback-world/hot-reload
|
|
9
|
+
* - read by fetchFullInstance()'s fallback branch (instance-data.js) and
|
|
10
|
+
* GET /api/cyberia-instance/fallback-world[/default-items]
|
|
11
|
+
*
|
|
12
|
+
* Deliberately NOT persisted: an engine restart drops back to the code defaults,
|
|
13
|
+
* so a stale editor session can never become the permanent shape of the world.
|
|
14
|
+
* This keeps the fallback path's "code defaults are authoritative" invariant —
|
|
15
|
+
* the only thing an operator can layer on top is this explicit, volatile list.
|
|
16
|
+
*
|
|
17
|
+
* @module src/api/cyberia-instance/cyberia-fallback-default-items.js
|
|
18
|
+
*/
|
|
19
|
+
|
|
20
|
+
/** @typedef {{ id: string, defaultPlayerInventory: boolean }} FallbackDefaultItem */
|
|
21
|
+
|
|
22
|
+
/** @type {FallbackDefaultItem[]} */
|
|
23
|
+
let defaultItems = [];
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* Coerce a caller-supplied list into the canonical `{ id, defaultPlayerInventory }`
|
|
27
|
+
* shape. Accepts the legacy `string[]` form and drops blank/duplicate ids so the
|
|
28
|
+
* world generator never sees a malformed entry.
|
|
29
|
+
*
|
|
30
|
+
* @param {Array<string|FallbackDefaultItem>} entries
|
|
31
|
+
* @returns {FallbackDefaultItem[]}
|
|
32
|
+
*/
|
|
33
|
+
function normalizeFallbackDefaultItems(entries) {
|
|
34
|
+
if (!Array.isArray(entries)) return [];
|
|
35
|
+
const seen = new Set();
|
|
36
|
+
const normalized = [];
|
|
37
|
+
for (const entry of entries) {
|
|
38
|
+
const raw = typeof entry === 'string' ? { id: entry } : entry;
|
|
39
|
+
const id = typeof raw?.id === 'string' ? raw.id.trim() : '';
|
|
40
|
+
if (!id || seen.has(id)) continue;
|
|
41
|
+
seen.add(id);
|
|
42
|
+
normalized.push({ id, defaultPlayerInventory: !!raw.defaultPlayerInventory });
|
|
43
|
+
}
|
|
44
|
+
return normalized;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
/**
|
|
48
|
+
* Current override set. Returns a copy so callers cannot mutate the store.
|
|
49
|
+
* @returns {FallbackDefaultItem[]}
|
|
50
|
+
*/
|
|
51
|
+
const getFallbackDefaultItems = () => defaultItems.map((entry) => ({ ...entry }));
|
|
52
|
+
|
|
53
|
+
/**
|
|
54
|
+
* Replace the override set. An empty list clears it, restoring pure code defaults.
|
|
55
|
+
* @param {Array<string|FallbackDefaultItem>} entries
|
|
56
|
+
* @returns {FallbackDefaultItem[]} The stored set.
|
|
57
|
+
*/
|
|
58
|
+
function setFallbackDefaultItems(entries) {
|
|
59
|
+
defaultItems = normalizeFallbackDefaultItems(entries);
|
|
60
|
+
return getFallbackDefaultItems();
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
export { getFallbackDefaultItems, setFallbackDefaultItems, normalizeFallbackDefaultItems };
|
|
@@ -19,6 +19,7 @@
|
|
|
19
19
|
*/
|
|
20
20
|
|
|
21
21
|
import { connectPortals } from './cyberia-portal-connector.js';
|
|
22
|
+
import { withSeededRandom } from './cyberia-random-source.js';
|
|
22
23
|
|
|
23
24
|
import {
|
|
24
25
|
generateObstacles,
|
|
@@ -38,6 +39,7 @@ import {
|
|
|
38
39
|
DEFAULT_PLAYER_SPAWN,
|
|
39
40
|
ENTITY_TYPE_DEFAULTS,
|
|
40
41
|
RESOURCE_ENTITY_TYPE_DEFAULTS,
|
|
42
|
+
DefaultCyberiaActions,
|
|
41
43
|
DefaultSkillConfig,
|
|
42
44
|
} from '../cyberia-server-defaults/cyberia-server-defaults.js';
|
|
43
45
|
import {
|
|
@@ -92,6 +94,19 @@ function collectReferencedItemIds() {
|
|
|
92
94
|
}
|
|
93
95
|
}
|
|
94
96
|
}
|
|
97
|
+
// Provider catalogs: the interact modal draws an icon for every item a
|
|
98
|
+
// vendor sells and every ingredient/output an assembler recipe names, so all
|
|
99
|
+
// of them must resolve to an ObjectLayer.
|
|
100
|
+
for (const action of DefaultCyberiaActions) {
|
|
101
|
+
for (const shopItem of action.shopItems || []) {
|
|
102
|
+
push(shopItem.itemId);
|
|
103
|
+
push(shopItem.priceItemId);
|
|
104
|
+
}
|
|
105
|
+
for (const recipe of action.craftRecipes || []) {
|
|
106
|
+
(recipe.ingredients || []).forEach((i) => push(i.itemId));
|
|
107
|
+
(recipe.outputItems || []).forEach((o) => push(o.itemId));
|
|
108
|
+
}
|
|
109
|
+
}
|
|
95
110
|
return ids;
|
|
96
111
|
}
|
|
97
112
|
|
|
@@ -164,7 +179,7 @@ function generateFallbackMap(mapCode, colors, opts = {}) {
|
|
|
164
179
|
const portalEntities = generatePortalEntities(mapDims, colors, { grid });
|
|
165
180
|
|
|
166
181
|
// 6. Bots — placed on walkable cells (avoids obstacles, portals, NPCs)
|
|
167
|
-
const bots = generateBots(mapDims, colors, { count: opts.botCount, grid });
|
|
182
|
+
const bots = mapCode === 'fallback-map-0' ? [] : generateBots(mapDims, colors, { count: opts.botCount, grid });
|
|
168
183
|
|
|
169
184
|
// 7. Resources — static exploitable entities placed on walkable cells
|
|
170
185
|
const resources = generateResources(mapDims, colors, { count: opts.resourceCount, grid });
|
|
@@ -217,6 +232,11 @@ function generateFallbackMap(mapCode, colors, opts = {}) {
|
|
|
217
232
|
* @param {number} [opts.resourceCount] Resources per map (random if omitted).
|
|
218
233
|
* @param {number} [opts.staticCount] Static decorators per map (random if omitted).
|
|
219
234
|
* @param {Array} [opts.colors] Override palette.
|
|
235
|
+
* @param {Array<{id: string, defaultPlayerInventory: boolean}>} [opts.itemIds=[]]
|
|
236
|
+
* Instance-level default items, mirroring `CyberiaInstance.itemIds`. Entries
|
|
237
|
+
* flagged `defaultPlayerInventory` are merged into the player entity default
|
|
238
|
+
* by `applyInstanceDefaultPlayerInventory`; every id is also resolved to an
|
|
239
|
+
* atlas at boot. Layout is unaffected, so this stays outside the world seed.
|
|
220
240
|
* @returns {{
|
|
221
241
|
* instance: object,
|
|
222
242
|
* maps: object[],
|
|
@@ -241,6 +261,7 @@ function generateFallbackWorld(opts = {}) {
|
|
|
241
261
|
// fallback before atlases load. The C client resolves real colours
|
|
242
262
|
// through domain/presentation_runtime — it does not read this value.
|
|
243
263
|
colors = PALETTE,
|
|
264
|
+
itemIds = [],
|
|
244
265
|
} = opts;
|
|
245
266
|
|
|
246
267
|
// Surface item-id drift loudly on the very first build, so a missing
|
|
@@ -262,20 +283,32 @@ function generateFallbackWorld(opts = {}) {
|
|
|
262
283
|
mapCodes.push(`fallback-map-${i}`);
|
|
263
284
|
}
|
|
264
285
|
|
|
265
|
-
//
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
)
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
286
|
+
// Deterministic layout: seed the shared random source so every call
|
|
287
|
+
// reproduces the SAME world. The instance-map `/static` POIs and the
|
|
288
|
+
// `/preview` node image are built by independent requests (and survive
|
|
289
|
+
// restarts) — they must agree on where every entity sits, so a resource POI
|
|
290
|
+
// lands exactly where the preview draws that resource. The seed folds in the
|
|
291
|
+
// layout-affecting options so a custom count set stays internally consistent.
|
|
292
|
+
const seed = `cyberia-fallback-world-v1:${mapCount}:${gridSize ?? ''}:${obstacleCount ?? ''}:${
|
|
293
|
+
foregroundCount ?? ''
|
|
294
|
+
}:${botCount ?? ''}:${resourceCount ?? ''}:${staticCount ?? ''}`;
|
|
295
|
+
|
|
296
|
+
const { maps, portals, topology } = withSeededRandom(seed, () => {
|
|
297
|
+
// Generate each map (deterministic under the seeded source).
|
|
298
|
+
const genMaps = mapCodes.map((code) =>
|
|
299
|
+
generateFallbackMap(code, colors, {
|
|
300
|
+
gridSize,
|
|
301
|
+
obstacleCount,
|
|
302
|
+
foregroundCount,
|
|
303
|
+
botCount,
|
|
304
|
+
resourceCount,
|
|
305
|
+
staticCount,
|
|
306
|
+
}),
|
|
307
|
+
);
|
|
308
|
+
// Connect maps with portal topology using the portal connector module.
|
|
309
|
+
const { portals: p, topology: t } = connectPortals(mapCodes, genMaps);
|
|
310
|
+
return { maps: genMaps, portals: p, topology: t };
|
|
311
|
+
});
|
|
279
312
|
|
|
280
313
|
// Build the instance shell (never persisted — no mongoId).
|
|
281
314
|
const instance = {
|
|
@@ -287,6 +320,7 @@ function generateFallbackWorld(opts = {}) {
|
|
|
287
320
|
portals,
|
|
288
321
|
topologyMode: 'procedural',
|
|
289
322
|
playerSpawn: { ...DEFAULT_PLAYER_SPAWN },
|
|
323
|
+
itemIds: itemIds.map((entry) => ({ ...entry })),
|
|
290
324
|
};
|
|
291
325
|
|
|
292
326
|
return {
|
|
@@ -31,8 +31,8 @@ import {
|
|
|
31
31
|
DefaultCyberiaActions,
|
|
32
32
|
DefaultCyberiaQuests,
|
|
33
33
|
ENTITY_TYPE_DEFAULTS,
|
|
34
|
-
RESOURCE_ENTITY_TYPE_DEFAULTS,
|
|
35
34
|
} from '../cyberia-server-defaults/cyberia-server-defaults.js';
|
|
35
|
+
import { DefaultCyberiaItems } from '../../client/components/cyberia/SharedDefaultsCyberia.js';
|
|
36
36
|
|
|
37
37
|
const logger = loggerFactory(import.meta);
|
|
38
38
|
|
|
@@ -430,22 +430,18 @@ const resolveInstanceWorld = async (instanceCode, options) => {
|
|
|
430
430
|
const instance = await CyberiaInstance.findOne({ code: instanceCode }).lean();
|
|
431
431
|
|
|
432
432
|
if (!instance) {
|
|
433
|
-
// Fallback world: topology is regenerated (portal cells may drift from
|
|
434
|
-
// the live simulation's world), but map codes and provider POIs come
|
|
435
|
-
// from the deterministic canonical defaults.
|
|
436
433
|
const world = generateFallbackWorld();
|
|
437
434
|
const mapCodes = new Set(world.instance.cyberiaMapCodes);
|
|
438
435
|
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
// resource defaults (wood-1, wood-2) are not persisted to MongoDB in the
|
|
442
|
-
// fallback path, so we compute a reasonable stats sum from the item type's
|
|
443
|
-
// default stat block (each stat 0-10, 6 stats → ~30 average).
|
|
444
|
-
const resourceItemIds = [
|
|
445
|
-
...new Set(RESOURCE_ENTITY_TYPE_DEFAULTS.flatMap((r) => [...(r.liveItemIds || []), ...(r.dropItemIds || [])])),
|
|
436
|
+
const fallbackItemIds = [
|
|
437
|
+
...new Set(world.maps.flatMap((m) => (m.entities || []).flatMap((e) => e.objectLayerItemIds || []))),
|
|
446
438
|
];
|
|
439
|
+
const itemTypeById = Object.fromEntries(DefaultCyberiaItems.map((entry) => [entry.item.id, entry.item.type]));
|
|
440
|
+
const resolved = await resolveObjectLayerMetadata(fallbackItemIds, options);
|
|
447
441
|
const objectLayerMetadata = Object.fromEntries(
|
|
448
|
-
|
|
442
|
+
Object.entries(resolved).map(([itemId, meta]) => {
|
|
443
|
+
return [itemId, { statsSum: meta.statsSum, type: meta.type }];
|
|
444
|
+
}),
|
|
449
445
|
);
|
|
450
446
|
|
|
451
447
|
return {
|
|
@@ -30,6 +30,9 @@ const CyberiaInstanceController = buildCrudController(CyberiaInstanceService, {
|
|
|
30
30
|
portalConnect: serviceHandler(CyberiaInstanceService.portalConnect),
|
|
31
31
|
// Moderator/admin-triggered world reload on a running cyberia-server.
|
|
32
32
|
hotReload: serviceHandler(CyberiaInstanceService.hotReload, { errorStatus: 502 }),
|
|
33
|
+
// Fallback-world default items (volatile) + its own reload trigger.
|
|
34
|
+
fallbackDefaultItems: serviceHandler(CyberiaInstanceService.fallbackDefaultItems),
|
|
35
|
+
fallbackHotReload: serviceHandler(CyberiaInstanceService.fallbackHotReload, { errorStatus: 502 }),
|
|
33
36
|
// Boot transport (REST fallback of the gRPC CyberiaDataService).
|
|
34
37
|
bootPing: serviceHandler(CyberiaInstanceBootService.ping),
|
|
35
38
|
bootObjectLayerBatch: serviceHandler(CyberiaInstanceBootService.objectLayerBatch, { errorStatus: 500 }),
|
|
@@ -38,6 +38,19 @@ class CyberiaInstanceRouter {
|
|
|
38
38
|
async (req, res) => await CyberiaInstanceController.bootFullInstance(req, res, options),
|
|
39
39
|
);
|
|
40
40
|
router.get(`/fallback-world`, async (req, res) => await CyberiaInstanceController.fallbackWorld(req, res, options));
|
|
41
|
+
// Fallback-world default items — volatile, process-local (never persisted).
|
|
42
|
+
// The read is open (same as /fallback-world); staging them and triggering a
|
|
43
|
+
// rebuild is elevated privilege, like every other hot-reload path.
|
|
44
|
+
router.get(
|
|
45
|
+
`/fallback-world/default-items`,
|
|
46
|
+
async (req, res) => await CyberiaInstanceController.fallbackDefaultItems(req, res, options),
|
|
47
|
+
);
|
|
48
|
+
router.post(
|
|
49
|
+
`/fallback-world/hot-reload`,
|
|
50
|
+
options.authMiddleware,
|
|
51
|
+
moderatorGuard,
|
|
52
|
+
async (req, res) => await CyberiaInstanceController.fallbackHotReload(req, res, options),
|
|
53
|
+
);
|
|
41
54
|
// Instance Map — static topology/presence plus dynamic player capability activity.
|
|
42
55
|
router.get(
|
|
43
56
|
`/instance-map/:instanceCode/static`,
|
|
@@ -7,6 +7,8 @@ import {
|
|
|
7
7
|
fallbackListInstance,
|
|
8
8
|
DEFAULT_FALLBACK_INSTANCE_CODE,
|
|
9
9
|
} from './cyberia-fallback-world.js';
|
|
10
|
+
import { getFallbackDefaultItems, setFallbackDefaultItems } from './cyberia-fallback-default-items.js';
|
|
11
|
+
import { CYBERIA_INSTANCE_CONF_DEFAULTS } from '../cyberia-server-defaults/cyberia-server-defaults.js';
|
|
10
12
|
import { triggerHotReload } from '../../projects/cyberia/hot-reload-trigger.js';
|
|
11
13
|
|
|
12
14
|
const logger = loggerFactory(import.meta);
|
|
@@ -183,8 +185,49 @@ class CyberiaInstanceService {
|
|
|
183
185
|
botCount: q.botCount ? parseInt(q.botCount, 10) : undefined,
|
|
184
186
|
obstacleCount: q.obstacleCount ? parseInt(q.obstacleCount, 10) : undefined,
|
|
185
187
|
foregroundCount: q.foregroundCount ? parseInt(q.foregroundCount, 10) : undefined,
|
|
188
|
+
itemIds: getFallbackDefaultItems(),
|
|
186
189
|
});
|
|
187
190
|
};
|
|
191
|
+
|
|
192
|
+
/**
|
|
193
|
+
* Current fallback-world default items — the volatile set the
|
|
194
|
+
* FallbackWorldEngine view last pushed, or `[]` when the engine is running on
|
|
195
|
+
* pure code defaults. Lets the view reopen showing what this process serves.
|
|
196
|
+
*
|
|
197
|
+
* `entityDefaultItemIds` are the ids the code defaults already ship in an
|
|
198
|
+
* entity's `defaultObjectLayers`. Selecting one of them and leaving
|
|
199
|
+
* `defaultPlayerInventory` unchecked REMOVES it (see
|
|
200
|
+
* applyInstanceDefaultPlayerInventory), so the editor needs to be able to warn
|
|
201
|
+
* before an operator silently strips the player's starting skin or weapon.
|
|
202
|
+
*/
|
|
203
|
+
static fallbackDefaultItems = async () => {
|
|
204
|
+
const entityDefaultItemIds = new Set();
|
|
205
|
+
for (const entityDefault of CYBERIA_INSTANCE_CONF_DEFAULTS.entityDefaults || []) {
|
|
206
|
+
for (const layer of entityDefault.defaultObjectLayers || []) {
|
|
207
|
+
if (layer.itemId) entityDefaultItemIds.add(layer.itemId);
|
|
208
|
+
}
|
|
209
|
+
}
|
|
210
|
+
return { itemIds: getFallbackDefaultItems(), entityDefaultItemIds: [...entityDefaultItemIds].sort() };
|
|
211
|
+
};
|
|
212
|
+
|
|
213
|
+
/**
|
|
214
|
+
* Stage the fallback world's default items, then ask a running cyberia-server
|
|
215
|
+
* to rebuild. The items are not persisted (see cyberia-fallback-default-items):
|
|
216
|
+
* they are staged here first so the rebuild's `getFullInstance` re-fetch picks
|
|
217
|
+
* them up, which is what makes them reach the server at all.
|
|
218
|
+
*
|
|
219
|
+
* `instanceCode` is intentionally left empty — the target world is addressed by
|
|
220
|
+
* `serverUrl` (origin, or origin + variant sub-path), and naming the synthetic
|
|
221
|
+
* `fallback` code would be rejected by any server serving a real instance code.
|
|
222
|
+
*/
|
|
223
|
+
static fallbackHotReload = async (req) => {
|
|
224
|
+
const { serverUrl, mode, itemIds } = req.body || {};
|
|
225
|
+
if (!serverUrl) throw new Error('serverUrl is required');
|
|
226
|
+
|
|
227
|
+
const staged = setFallbackDefaultItems(itemIds || []);
|
|
228
|
+
const { transport, result, grpcError } = await triggerHotReload({ serverUrl, instanceCode: '', mode });
|
|
229
|
+
return { transport, itemIds: staged, grpcError, ...result };
|
|
230
|
+
};
|
|
188
231
|
}
|
|
189
232
|
|
|
190
233
|
export { CyberiaInstanceService };
|
|
@@ -20,6 +20,8 @@
|
|
|
20
20
|
* @module src/api/cyberia-instance/cyberia-portal-connector
|
|
21
21
|
*/
|
|
22
22
|
|
|
23
|
+
import { nextRandom } from './cyberia-random-source.js';
|
|
24
|
+
|
|
23
25
|
// ── Portal mode constants ────────────────────────────────────────────────────
|
|
24
26
|
|
|
25
27
|
/**
|
|
@@ -117,7 +119,7 @@ function buildTopologyFromSubtypes(orderedCodes, portalIndex) {
|
|
|
117
119
|
|
|
118
120
|
// Target: pick any portal on the target map for landing coordinates
|
|
119
121
|
const tgtAll = portalIndex[tgtCode] || [];
|
|
120
|
-
const tgtEnt = tgtAll.length > 0 ? tgtAll[Math.floor(
|
|
122
|
+
const tgtEnt = tgtAll.length > 0 ? tgtAll[Math.floor(nextRandom() * tgtAll.length)] : null;
|
|
121
123
|
|
|
122
124
|
if (srcEnt) {
|
|
123
125
|
usedInRing.add(srcEnt);
|
|
@@ -139,7 +141,7 @@ function buildTopologyFromSubtypes(orderedCodes, portalIndex) {
|
|
|
139
141
|
if (n < 2) return srcCode;
|
|
140
142
|
let code;
|
|
141
143
|
do {
|
|
142
|
-
code = orderedCodes[Math.floor(
|
|
144
|
+
code = orderedCodes[Math.floor(nextRandom() * n)];
|
|
143
145
|
} while (code === srcCode && n > 1);
|
|
144
146
|
return code;
|
|
145
147
|
};
|
|
@@ -151,13 +153,13 @@ function buildTopologyFromSubtypes(orderedCodes, portalIndex) {
|
|
|
151
153
|
for (const srcEnt of remaining) {
|
|
152
154
|
// If the entity already has an explicit subtype, honour it;
|
|
153
155
|
// otherwise assign a random mode (matching fallback-world behaviour).
|
|
154
|
-
const sub = srcEnt.portalSubtype || PORTAL_MODE_LIST[Math.floor(
|
|
156
|
+
const sub = srcEnt.portalSubtype || PORTAL_MODE_LIST[Math.floor(nextRandom() * PORTAL_MODE_LIST.length)];
|
|
155
157
|
|
|
156
158
|
switch (sub) {
|
|
157
159
|
case PORTAL_MODES.INTER_PORTAL: {
|
|
158
160
|
const tgtCode = otherMap(srcCode);
|
|
159
161
|
const candidates = portalIndex[tgtCode] || [];
|
|
160
|
-
const tgtEnt = candidates.length > 0 ? candidates[Math.floor(
|
|
162
|
+
const tgtEnt = candidates.length > 0 ? candidates[Math.floor(nextRandom() * candidates.length)] : null;
|
|
161
163
|
portals.push({
|
|
162
164
|
sourceMapCode: srcCode,
|
|
163
165
|
sourceCellX: srcEnt.initCellX ?? 0,
|
|
@@ -198,7 +200,7 @@ function buildTopologyFromSubtypes(orderedCodes, portalIndex) {
|
|
|
198
200
|
const candidates = allOnMap.filter(
|
|
199
201
|
(e) => e !== srcEnt && (e.initCellX !== srcEnt.initCellX || e.initCellY !== srcEnt.initCellY),
|
|
200
202
|
);
|
|
201
|
-
const tgtEnt = candidates.length > 0 ? candidates[Math.floor(
|
|
203
|
+
const tgtEnt = candidates.length > 0 ? candidates[Math.floor(nextRandom() * candidates.length)] : null;
|
|
202
204
|
portals.push({
|
|
203
205
|
sourceMapCode: srcCode,
|
|
204
206
|
sourceCellX: srcEnt.initCellX ?? 0,
|