From e89d7fa06728f3339a4af872e65fa0bfc6e81ee8 Mon Sep 17 00:00:00 2001 From: robtfm <50659922+robtfm@users.noreply.github.com> Date: Tue, 8 Sep 2026 15:40:38 +0100 Subject: [PATCH 1/2] =?UTF-8?q?feat:=20TeleportToRequest.realm=20=E2=80=94?= =?UTF-8?q?=20a=20teleport=20can=20name=20the=20realm=20its=20parcel=20is?= =?UTF-8?q?=20in=20(#477)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * feat: TeleportToRequest.realm — a teleport can name the realm its parcel is in Parcel coordinates only address one realm's grid. A client feature that offers "a place" (a Places listing, a map pin) has a realm and a parcel, and until now had to fire changeRealm and teleportTo separately and guess at their interleaving — the teleport lands on the realm being left. This lets the request carry the realm: the client changes realm (a full reconnect, as for ChangeRealm) and then lands on the parcel. Omitted, nothing changes. Co-Authored-By: Claude Fable 5.1 (cherry picked from commit 4a844e9c452f6731356346464fbc29968e4df90a) * feat: TeleportToRequest.world_coordinates optional; deprecate ChangeRealm A teleport that names a realm and no parcel lands on that realm's default spawn — exactly what ChangeRealm does — so a realm change is now just a TeleportTo. ChangeRealm stays for existing scenes. Co-Authored-By: Claude Fable 5.1 (cherry picked from commit 882d4eeece064ee71b16f49457c610d48f1c3ffb) --------- Co-authored-by: Claude Fable 5.1 --- proto/decentraland/kernel/apis/restricted_actions.proto | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/proto/decentraland/kernel/apis/restricted_actions.proto b/proto/decentraland/kernel/apis/restricted_actions.proto index 2a8c839f..2aeb03f3 100644 --- a/proto/decentraland/kernel/apis/restricted_actions.proto +++ b/proto/decentraland/kernel/apis/restricted_actions.proto @@ -13,7 +13,12 @@ message MovePlayerToRequest { } message TeleportToRequest { - decentraland.common.Vector2 world_coordinates = 1; + // The parcel to land on. Omitted: the realm's default spawn (only meaningful with `realm`). + optional decentraland.common.Vector2 world_coordinates = 1; + // The realm the parcel belongs to: a world name (`foo.dcl.eth`) or a realm url. When set, the + // client changes realm (a full reconnect, even to the realm the player is in) and lands on the + // parcel there. Omitted: the parcel is in the player's current realm. + optional string realm = 2; } message TriggerEmoteRequest { @@ -102,6 +107,7 @@ service RestrictedActionsService { // TriggerEmote will trigger an emote in this current user rpc TriggerEmote(TriggerEmoteRequest) returns (TriggerEmoteResponse) {} + // @deprecated, use TeleportTo with `realm` (and no `world_coordinates` for the realm's default spawn) // ChangeRealm prompts the user to change to a specific realm rpc ChangeRealm(ChangeRealmRequest) returns (SuccessResponse) {} From 6143bfb1cfaa4bf0105e5482926bef43fe6fd447 Mon Sep 17 00:00:00 2001 From: Pravus Date: Tue, 8 Sep 2026 18:55:22 +0200 Subject: [PATCH 2/2] fix: sync-main-to-experimental action correct base branch (#479) --- .../workflows/merge-sync-to-experimental.yml | 94 ++++++++++++++++ .../workflows/sync-main-to-experimental.yml | 106 +++++++++++++++++- 2 files changed, 194 insertions(+), 6 deletions(-) create mode 100644 .github/workflows/merge-sync-to-experimental.yml diff --git a/.github/workflows/merge-sync-to-experimental.yml b/.github/workflows/merge-sync-to-experimental.yml new file mode 100644 index 00000000..f5161234 --- /dev/null +++ b/.github/workflows/merge-sync-to-experimental.yml @@ -0,0 +1,94 @@ +name: Merge sync PR into experimental + +# The repo allows only squash merges, and squashing the sync PR would drop main's +# commits and freeze the merge base. This lands it as a real merge commit instead; +# GitHub closes the sync PR as merged once its commits are reachable from experimental. +on: + workflow_dispatch: + +# Shared with sync-main-to-experimental.yml: see the note there. +concurrency: + group: chore-sync-branch + cancel-in-progress: false + +permissions: + contents: write + pull-requests: write + +jobs: + merge: + runs-on: ubuntu-latest + + steps: + - name: Check out the code + uses: actions/checkout@v4 + with: + fetch-depth: 0 + + - name: Configure git identity + run: | + git config user.name "github-actions[bot]" + git config user.email "41898282+github-actions[bot]@users.noreply.github.com" + + - name: Merge chore/sync into experimental + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + run: | + set -euo pipefail + + git fetch --no-tags --force --prune origin + + if ! git rev-parse --verify --quiet refs/remotes/origin/chore/sync >/dev/null; then + echo "::error::chore/sync does not exist on the remote; there is no sync to land." + exit 1 + fi + + if git merge-base --is-ancestor origin/chore/sync origin/experimental; then + echo "experimental already contains chore/sync; nothing to merge." + # It is fully merged, so a leftover branch is only a failed cleanup. + git push origin --delete chore/sync || true + exit 0 + fi + + PR_NUMBER="$(gh pr list --base experimental --head chore/sync --state open --json number --jq '.[0].number // empty')" + + # The sync PR is opened as a draft so it cannot be squashed. Take it out of + # draft before the merge lands, so GitHub applies its normal + # merged-by-reachability handling to it. + if [ -n "$PR_NUMBER" ]; then + gh pr ready "$PR_NUMBER" \ + || echo "::warning::Could not mark PR #$PR_NUMBER ready; it may close as Closed rather than Merged." + fi + + git checkout -B experimental origin/experimental + # --no-ff so the sync always shows up as one labelled merge commit, the way the + # PR button would have recorded it. + if ! git merge --no-ff -m "chore: sync main to experimental${PR_NUMBER:+ (#$PR_NUMBER)}" origin/chore/sync; then + git merge --abort || true + echo "::error::Merging chore/sync into experimental failed. Resolve it on chore/sync, push, and re-run this workflow." + exit 1 + fi + + git push origin experimental + + # A PR closed by a push is not covered by delete_branch_on_merge, so remove the + # now fully-merged branch here: the sync job treats an existing chore/sync as a + # sync still in flight. Deleting a head branch also closes its PR, so wait for + # GitHub to register the merge first — otherwise the PR reads Closed, not Merged. + STATE="" + if [ -n "$PR_NUMBER" ]; then + for _ in $(seq 1 10); do + STATE="$(gh pr view "$PR_NUMBER" --json state --jq .state)" + [ "$STATE" = "MERGED" ] && break + sleep 3 + done + if [ "$STATE" != "MERGED" ]; then + echo "::warning::PR #$PR_NUMBER still reads $STATE, so chore/sync is left in place rather than closing the PR unmerged. Re-run this workflow to clean it up." + exit 0 + fi + fi + + # Non-fatal: experimental is already pushed, so a failed cleanup is not a + # failed sync. The no-op path above finishes the job on any later run. + git push origin --delete chore/sync \ + || echo "::warning::Could not delete chore/sync; re-run this workflow to clean it up." diff --git a/.github/workflows/sync-main-to-experimental.yml b/.github/workflows/sync-main-to-experimental.yml index 705c862d..11af7a42 100644 --- a/.github/workflows/sync-main-to-experimental.yml +++ b/.github/workflows/sync-main-to-experimental.yml @@ -5,6 +5,17 @@ on: branches: [ main ] workflow_dispatch: +# Shared with merge-sync-to-experimental.yml: both workflows mutate chore/sync, and +# interleaving them either rejects this push or recreates a branch that one just +# deleted. One group serializes every writer. +concurrency: + group: chore-sync-branch + cancel-in-progress: false + +permissions: + contents: write + pull-requests: write + jobs: create-pr: runs-on: ubuntu-latest @@ -12,28 +23,111 @@ jobs: steps: - name: Check out the code uses: actions/checkout@v4 + with: + # full history, otherwise the merges below have no common ancestor + fetch-depth: 0 + + - name: Configure git identity + run: | + git config user.name "github-actions[bot]" + git config user.email "41898282+github-actions[bot]@users.noreply.github.com" - name: Create or update sync branch run: | - git fetch origin experimental - git checkout -B chore/sync - git push --force --set-upstream origin chore/sync + set -euo pipefail + + git fetch --no-tags --force --prune origin + + # An existing chore/sync is a sync still in flight: keep it, so commits + # arriving on main accumulate on it and a manual conflict resolution pushed + # to it survives. The branch is deleted when its PR merges, so the next sync + # starts from experimental again. + BASE=origin/experimental + if git rev-parse --verify --quiet refs/remotes/origin/chore/sync >/dev/null; then + BASE=origin/chore/sync + fi + git checkout -B chore/sync "$BASE" + + # chore/sync must be experimental + main: anything that only exists on + # experimental has to survive the sync, or the package built from the branch + # ships main's protos alone. + for REF in origin/experimental origin/main; do + if ! git merge -m "chore: merge $REF into chore/sync" "$REF"; then + git merge --abort || true + echo "::error::Merging $REF into chore/sync failed. To recover: branch chore/sync off experimental (or check out the existing one), merge main into it, resolve, push it, and open a PR against experimental. Later runs merge on top of that resolution." + exit 1 + fi + done + + # Push whenever the recomputed branch differs from the remote, so a stale + # chore/sync can never stay the head of an open PR. Skip it when there is + # nothing to propose either, so a no-op run does not leave a branch behind + # that later runs would read as a sync in flight. + REMOTE_HEAD="$(git rev-parse --verify --quiet refs/remotes/origin/chore/sync || echo absent)" + if [ "$(git rev-parse HEAD)" = "$REMOTE_HEAD" ] \ + || git merge-base --is-ancestor HEAD origin/experimental; then + echo "Nothing to push: chore/sync is current, or experimental already contains it." + else + git push --force-with-lease origin chore/sync + fi - name: Create or update PR env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + # Backticks here are data, not shell source: they reach gh through the + # variable and are never re-evaluated. + PR_BODY: | + :crown: *An automated PR to keep experimental in sync with main* + + **This PR is a draft on purpose: it is landed by a workflow, not by a button.** + + ### How to land it + + 1. Test the `@dcl/protocol` tarball from the **build-deploy** comment below in + the Explorer, and review the diff as usual. + 2. Go to **Actions -> Merge sync PR into experimental -> Run workflow**, from + `main`. + 3. That workflow marks this PR ready, merges `chore/sync` into `experimental` + as a real merge commit, and this PR closes as **Merged**. + + Further commits on `main` are merged onto this same branch while the PR is + open, so it stays current on its own. + + > [!IMPORTANT] + > Do not take this PR out of draft in order to squash it. Squash is the only + > merge this repo allows, and squashing replaces `main`'s commits with a single + > new one: `main` stops being an ancestor of `experimental`, the merge base + > freezes, and every later sync re-applies changes `experimental` already has, + > conflicting within a couple of runs. + > + > If it does get squashed, restore the ancestry with + > `git merge -s ours origin/main` on `experimental`. run: | - PR_NUMBER=$(gh pr list --base experimental --head chore/sync --state open --json number --jq '.[0].number') + set -euo pipefail + + # gh pr create fails with "No commits between ..." when nothing is ahead. + # The tree check also covers a net-zero round of main changes, where commits + # exist but there is nothing left to propose. + if git merge-base --is-ancestor HEAD origin/experimental \ + || git diff --quiet origin/experimental HEAD; then + echo "experimental already has everything chore/sync would propose." + exit 0 + fi + + PR_NUMBER="$(gh pr list --base experimental --head chore/sync --state open --json number --jq '.[0].number // empty')" if [ -n "$PR_NUMBER" ]; then echo "PR #$PR_NUMBER already exists, adding a comment..." - gh pr comment $PR_NUMBER --body "🔄 Updated with latest changes from **main** on $(date -u '+%Y-%m-%d %H:%M UTC')" + gh pr comment "$PR_NUMBER" --body "🔄 Updated with latest changes from **main** on $(date -u '+%Y-%m-%d %H:%M UTC')" else echo "Creating new PR..." + # --draft: squash is the only merge the repo allows, and squashing this PR + # breaks the ancestry. A draft has no merge button at all. gh pr create \ + --draft \ --base experimental \ --head chore/sync \ --title "chore: sync main to experimental" \ - --body ":crown: *An automated PR to keep experimental in sync with main*" \ + --body "$PR_BODY" \ --label "auto-pr" fi