Release Candidate 1.12.0 - #2572
Merged
Merged
Conversation
* fix(emote-wheel): restore rarity color on wheel items via Inner modulate * remove unnecessary nodes * fix(emote-wheel): refresh emote slots on open to reflect backpack changes * Revert "fix(emote-wheel): refresh emote slots on open to reflect backpack changes" This reverts commit b2c9b1e.
…nk (#2482) * feat: allow deleting an upgraded (email-linked) guest via double unlink The existing on-device guest deletion (#2335) only handles a NON-upgraded guest: it unlinks the sole `guest` profile and refuses if any email/social profile is linked. This adds an opt-in path to fully delete an UPGRADED guest. When enabled, an upgraded thirdweb guest takes the same automatic on-device deletion path (instead of the manual `/deletion` request) and does a DOUBLE unlink — it unlinks BOTH the `guest` and the linked `email` profile, so the whole thirdweb user is deleted rather than merely stripped back to a bare guest. That frees the deterministic sessionId for a brand-new wallet. Gate (both required): NON-production build (`not is_production()`) AND a `decentraland://open?enable-upgraded-deletion=true` deeplink. Hard-disabled in prod and off by default, so a recoverable account is never force-deleted on a release cut. - thirdweb_guest.rs: share the unlink POST + 500-as-success as `post_unlink`; add `unlink_upgraded_account` (unlinks every profile, guest last so the last-profile 500 lands there). Email profiles unlink by `details.email`, everything else by `details.id`. - dcl_player_identity.rs: `async_delete_upgraded_account` (best-effort). - global.gd: `_enable_upgraded_deletion` flag + `is_upgraded_deletion_enabled()`. - deep_link_router.gd: set the flag from the deeplink param. - account_deletion_popup.gd: route an upgraded guest to the double-unlink path only when the gate is on. * fix: set allowAccountDeletion on thirdweb unlink so upgraded deletion works The double unlink (guest + email) was rejected by thirdweb with `400 "user must have at least one account associated with their in app wallet. Please link an account first."` — unlinking the LAST auth method requires the `allowAccountDeletion` flag, which we weren't sending. Set `allowAccountDeletion: true` on the unlink request so removing the final profile deletes the account instead of erroring. It is a no-op when the profile being unlinked is not the last one. Verified on-device: an upgraded guest (guest + email) now deletes fully — email unlink → 200, guest (last) unlink → 500 (user deleted, treated as success) — and the same device anchor mints a brand-new wallet on the next login (is_new_user=true). * fix: set new PbBillboard.target_entity field in billboard test The @dcl/protocol @next bump added `optional uint32 target_entity` to PBBillboard (billboard-faces-target-entity). The billboard test constructed PbBillboard without it, so the build broke with E0063 once CI fetched the new protocol. Initialize it to None. Unrelated to the account-deletion feature; unblocks the Linux/iOS build.
…ns (#2480) The backpack keys wearable_data by the item urn and compares the equipped list against it, but a profile can store the token-instance urn (…:<itemId>:<tokenId>) for on-chain wearables. That mismatch made on-chain wearables already equipped in the profile read as un-equipped in the grid, impossible to unequip (find() missed the token form), and never replaced by a new same-category item (duplicates). Add to_item_urn() — collapse any urn to its item form, matching the Rust ContentProvider.get_wearable normalization (truncate at the 6th ':') — and use it for the equipped highlight, the unequip removal, the same-category replacement on equip, and the marketplace preview.
…sers The guest upgrade notice briefly flashed (~0.5-1s) on every session for users who had already upgraded their account. Two issues combined: - guest_upgrade_card.gd set `_upgrade_checked = true` *before* awaiting the thirdweb check. Discover emits `orientation_changed` repeatedly during a menu transition (set_orientation_portrait always emits), so a concurrent re-entry took the cached fast-path and read the Rust flag while it still held its default `false` (not-yet-populated) -> `visible = true`. Now the card stays hidden while a check is in flight and only flips `_upgrade_checked` once an authoritative result is in. - discover.gd force-showed the card on search-clear via `.show()`, bypassing the upgrade check. It now calls `refresh_visibility()` so the card re-evaluates its own state instead of being forced visible. Default is now hide; the notice only shows once a non-upgraded guest is authoritatively confirmed. Fixes #2483
When navigation triggers the loading screen while a search or chat input is active, the on-screen keyboard stayed up and overlapped the loading UI on mobile. Both loading-screen show paths funnel through loading_show_requested, so dismiss the virtual keyboard and emit close_chat there — covering every case the loading screen becomes visible. Fixes #2491
…oad on teleport A scene killed on teleport/realm-change delivers its kill via the capacity-1 RendererResponse channel. If the scene's JS is wedged and isn't draining that channel (op_crdt_recv_wait never called), try_send(Kill) returns Full every tick and the scene stays in ToKill forever: it never reaches KillSignal, so the 10s V8 force-terminate never runs, the scene never becomes Dead, and its Godot root node is never freed - leaving the old scene rendered on top of the new one after a teleport (issue #2229). This is the "waited 10s+ and the old scene is still loaded" case; the KillSignal path already resolves within 10s. Give ToKill a kill-request timestamp and, when the kill can't be delivered (channel Full) past the timeout, force-terminate the V8 isolate directly (it doesn't need the channel) and mark the scene Dead so it gets reaped. Extract a shared force_terminate_scene_v8 helper used by both the ToKill and KillSignal timeout paths. Refs #2229
Godot performs a single automatic screen capture per frame, shared by every hint_screen_texture material. In the backpack, AvatarVFX (addition_shader) draws before the bottom navigation bar and claims that capture, so the deploy overlay's BackgrundBlur re-used a snapshot taken before the bottom bar and the overlay dim were drawn. The uncovered strip contained only the viewport clear color (3D rendering is disabled while the menu is open), which showed up as the gray footer band. Add a BackBufferCopy (viewport mode) inside Control_DeployingProfile so a fresh capture is taken right before the blur draws. Also replace the menu background's leftover light-gray GradientTexture2D with the gradient-background.png asset. Fixes #2487
CI resolves @dcl/protocol from the npm `next` dist-tag on every build, and protocol 1.0.0-28974105118.commit-a598406 added `target_entity` to PBBillboard, so the exhaustive struct literal in the billboard itest no longer compiled (E0063). Fill remaining fields with `..Default::default()` so new optional protocol fields don't break the test again.
Tracking the npm `next` dist-tag re-resolves the protocol on every CI run, so upstream publishes can break or change RC builds with no repo change (PBBillboard.target_entity did exactly that mid-RC). Pin the tarball that current code compiles against; reset to `None` to resume tracking @next after the release is cut.
The dcl-ios self-hosted VM keeps target/, lib/target/ and .bin/ between runs (clean: false) and its single disk serves every branch's iOS build, so the caches grow until cargo dies mid-link with "No space left on device" (as happened on PR #2503). Before building, check free space and prune the Rust caches when below 40 GB — the next build runs cold but self-heals instead of failing. .bin/ is kept: re-downloading templates is the expensive part.
…s-to-main hotfix: port release-1.11.0 fixes to main
…ad notify leak, malformed /about) (#2461) * fix(loading): count deleted-while-loading GLTFs as finished (RC-10) Deleting an entity mid-GLTF-load removed it from `gltf_loading` without bumping `gltf_loading_finished_count` (unlike the component-removal and normal-completion paths). started > finished forever kept loaded_assets < expected_assets, so the 60% Assets phase never reached 100% and the loading screen hung with nothing stuck. Mirrors the existing finished-count pattern. Refs #1640. * fix(loading): bound asset downloads with connect/response/idle timeouts (RC-1) ResourceProvider used Client::new() with no timeout, so a stalled/half-open connection hung the download future forever and left the entity stuck in gltf_loading (infinite loading). Adds bounds that kill dead connections without killing slow-but-alive ones: a legitimate asset was observed taking ~198s of wire time on a 1 Mbps link, so there is deliberately no total-request timeout — only connect_timeout, a response/header timeout, and a per-chunk idle timeout. A stall now surfaces as Err. Refs #1640. * fix(loading): release pending-download slot on every path (RC-2) On download failure the pending_downloads Arc<Notify> was never removed/fired (cleanup lived only on the success tail), so duplicate waiters hung forever and the content hash was poisoned permanently — even retries could never succeed. Cleanup now runs on every exit path via finish_pending_download across all three fetch variants. Adds a regression test (test_failed_download_releases_pending_slot): a failed download must release its slot and a retry must then succeed; before the fix the retry hangs forever. Refs #1640. * fix(loading): reject malformed /about content before committing realm state (RC-6) A /about with content:null or a missing publicUrl threw at content_base_url AFTER realm state was partially committed and WITHOUT emitting realm_change_failed, leaving a silent stuck loading screen. Now validated before the commit so the failure is surfaced and recoverable. Refs #1640.
Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
Merge release-1.11.0 hotfixes into main
…stead of desktop keyboard shortcut Fixes #2509
…l-text-release hotfix: update emote wheel loading tip for mobile
* fix(ui): update low memory modal copy to be less technical * fix(ui): trim low memory modal body copy, options already on buttons --------- Co-authored-by: Sebatián Di Lauro <jsdilauro@gmail.com>
…comment (#2530) The Android job hard-fails on every fork PR, unrelated to the PR's contents. Fork PRs don't receive repository secrets, so APK_ANDROID_KEYSTORE_BASE64 is empty and `echo "" | base64 -d` writes a 0-byte keystore. apksigner then dies with "Failed to load signer #1: Tag number over 30 is not supported" (exit 2), and the build-report comment step fails too because the fork's GITHUB_TOKEN is read-only ("Resource not accessible by integration"). - Sign Android APK: skip the production re-sign when the keystore secret is empty (fork PR), keeping the APK already signed by the Godot export step (export_presets.cfg has package/signed=true, using an auto-generated keystore). Password is now read from env instead of being inlined. Same-repo branches (main/release and internal PRs) re-sign with the production key exactly as before. - Comment PR with Build Report: continue-on-error so a read-only token on fork PRs can't fail the build; the comment simply won't appear there. Mirrors the graceful fork-PR degradation already used by the R2-upload and build-number steps.
…und (#2478) * fix(mobile): prevent profile screen state changes when returning from background * fix(mobile): restore bottom sheet position after returning from background * fix(mobile): restore card state after iOS swipe-up-to-home gesture
* docs(review): add QA-executable test-steps guide to REVIEW.md Adds a "Writing test steps QA can execute" subsection under Section 4 explaining how to write `## Test plan` cases the QA team can run by hand: setup/preconditions, numbered steps, an observable expected result, and a platform matrix. Includes one worked example (built on the #2398 multitouch case) and a list of anti-patterns. Cross-references it from Tier 3 item 17 (Test plan quality). * docs(review): match QA test-steps to real workflow (auto-distribution, real phones, start at app open)
Sync release → main: emote wheel loading tip (#2520)
…ll load fix(realm): make SpawnPoint.default optional so worlds without it still load
docs: add protocol development workflow guide
* fix(ui): truncate scene name in event detail to prevent overflow * remove useless separator
Dead code since #1433, which deleted the drag handlers that positioned PreviewCamera3D and left the SubViewport at render_target_update_mode DISABLED with clear_mode NEVER. It never redraws and never clears, so it shows leftover framebuffer garbage -- that is the screenshot on the issue. Fixing it makes no sense: Settings now lives as a popup over the game, so the preview would render the same thing as the main viewport already visible behind it. Removing it is the right call, per the decision on #2326. Removes the three nodes plus HSeparator3 (the preview's own spacer; keeping it would leave a 60px gap) and the six references in settings.gd. All skybox settings are untouched: the Dynamic Skybox toggle, the Custom Skybox dropdown and the SDK-driven warning all keep working. Also drops settings.tscn29753053905.tmp, an accidentally committed Godot editor temp file holding a stale copy of the whole scene and sharing settings.tscn's UID.
feat: native OTP email sign-in
* improve loading layout according to the review * fix minor issues
`wrapMode` was parsed into `DclTexture.wrap_mode` but never read. In Godot 4 texture repeat is a property of the CanvasItem that samples the texture, not of the texture, so the `uv_child` TextureRect stayed at clamp-to-edge and custom UVs beyond 1.0 smeared the last column of pixels instead of tiling. Map wrapMode -> CanvasItem::set_texture_repeat on the uv_child (REPEAT -> ENABLED, MIRROR -> MIRROR, CLAMP -> DISABLED; default CLAMP, unchanged for textures that don't declare wrapMode). The issue's "correct on desktop" screenshot is from unity-explorer, so this reproduced on Godot on every platform, not just mobile. Tests: - Unit tests for the wrapMode -> TextureRepeat mapping. - Visual client-test (uibackground_repeat_stretch) that drives the real change_value path and diffs a rendered snapshot; goes red (75%) without the fix, green (100%) with it. Also verified on-device (A54, GLES) with the real ETC2/NPOT pipeline; that variant was kept out of the committed suite to avoid cross-GPU baseline flakiness in CI coverage runs.
* adding touch visualization for debugging * implementing controls customization * formatting fix * removing protocol errors, adding stubs * bumping proto to main, implementing control customization * joypad design fixes * improve joypad icons * chore: fix formatting after merge (gdformat + rustfmt) Collapse a multi-line add_theme_constant_override call in joypad.gd and re-sort the touch_screen_controls module declarations that the main merge left out of alphabetical order. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> * fix: allow needless_update in billboard itest under older protocol pin Main's PBBillboard itest uses ..Default::default() so new optional protocol fields don't break the exhaustive struct literal, but this branch pins the controls-customization protocol build where all fields are explicit, making the update needless. Allow the lint so the test compiles under either pin. * refactor: extract SDK touch-controls applier from explorer.gd The merge from main pushed explorer.gd to 1931 lines, over gdlint's max-file-lines (1900) — both sides were individually under it. Move this branch's PBTouchScreenControls hide-joystick/crosshair logic into components/utils/SdkTouchControlsApplier (pure-logic helper per the UI componentization guide), driven from Explorer._process as before. No behavior change. * improving control button ordering * using TextureUnion instead of String for control icons * fix(ci): pin @dcl/protocol to a build with the TextureUnion icon field The controls-icon Rust code reads `icon` as a TextureUnion, but the pinned protocol build (commit-9b4f100) still defined it as a plain string, so CI regenerated the proto with `icon: string` and the lib failed to compile (E0609: no field `tex` on String). Bump the pin to commit-6d59503, which ships the TextureUnion `icon` field (and the merged PR #426 review changes). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> * adding mask parameter to AvatarScene and SceneManager * pointing protocol to main * moving _custom_icon from code to scene * fix stuck input actions, improved action mapping --------- Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com> Co-authored-by: Mateo "Kuruk" Miccino <mateomiccino@gmail.com>
…2616) * fix: HUD overlays no longer swallow touch input meant for scene UI (#2578) In the explorer's single Control tree, everything after SceneUIContainer sits above the SDK scene's UI, and only MOUSE_FILTER_IGNORE is click-through — the Container default PASS still claims the hit against lower siblings. Three HUD widgets relied on that default and acted as invisible input walls: - debug_panel.tscn: the right-aligned VBoxContainer keeps a 500x280 body (Control_Panel's minimum size) even while the console is collapsed, blocking a large top-right region in preview mode. The VBox and its top-buttons row are now IGNORE; the buttons themselves still receive input. This also un-blocks the settings tabs that sat underneath the invisible rect. - navbar.tscn: the profile corner Control (120x136) defaulted to STOP and its toggle Button was full-rect, stealing ~2.5x the area of the visible 80x80 bubble. The container is now IGNORE and the Button hitbox matches the bubble. - explorer.tscn: the bottom-center version/FPS HBoxContainer defaulted to PASS; now IGNORE (its Labels already ignore input). A cargo-test tscn lint (lib/src/hud_input_lint.rs) pins these invariants so the PASS-default trap doesn't regress. * fix: render debug console and scene stats below friends/notifications/settings (#2578) SafeMarginContainerDebug was declared after LeftRightSafeContainer, so the debug tools drew on top of the right-side panels. Moving it between SceneUIContainer and LeftRightSafeContainer keeps the debug tools above scene UI for input while the friends/notifications/settings panels and navbar now cover them, and the ordering is pinned by a new lint test.
Contributor
📱 Mobile Distribution Triggered🍏 iOS → TestFlight | 🤖 Android → APK ready (R2) + Slack notification 🔗 Workflow: View run 📍 Branch: 🔄 Triggered: 2026-08-06 15:01:07 UTC |
Contributor
🤖 Android Build Ready✅ Status: APK available for download 📱 Download APK: direct link 🔗 Workflow: View run 📍 Branch: 🔄 Completed: 2026-08-06 15:23:46 UTC |
* fix(avatar): place nametag above avatar+wearables bounds (#2580) The nametag anchor was a fixed bone offset (Y=75 on Avatar_Head), so tall head wearables overlapped the tag. Now NameplateLayer computes the anchor Y from Avatar.get_bounds_top_y(): posed skeleton bone tops (follow emotes, include merged wearable bones) plus a bind-pose clearance (bind mesh AABB top minus bind bone top) that accounts for skull/hat mesh volume. Out of scope: XR path and the SDK AAPT_NAME_TAG anchor keep the legacy bone-based behavior. * perf(avatar): cache nametag bounds computation get_bounds_top_y() was scanning ~70 bones (posed + rest) and merging mesh AABBs on every call (per frame per avatar). Now: - posed top recomputes on skeleton_updated (already LOD-throttled) and is cached as max dot(skeleton basis Y row, bone pose origin) - clearance (bind mesh AABB top minus bind bone top) recomputes only on wearable load and _ready - get_bounds_top_y() is O(1): skeleton origin.y + both cached values
Sibling fix to decentraland/godot-asc-deploy#4, which was prompted by a real failure: the iOS deploy run there shipped build 1414 to TestFlight and then went red because the dSYM upload exited 1 on a server-side Sentry error ("Reported checksum mismatch"). `sentry-cli upload-dif --wait` blocks on Sentry's server-side processing and propagates its result, so a Sentry-side hiccup fails a job whose artifacts already shipped. Both symbol-upload steps in this repo have the same shape and the same exposure: - ios_r2_artifact.yml — runs after the unsigned export is already in R2. Its comment even claimed "Best-effort", but a failure took the whole job down (and with it the hand-off to the signing pipeline). - android_builds.yml — runs after the APK/AAB are built and uploaded. Make both `continue-on-error` and notify instead: a warning annotation on the run (plus a threaded Slack reply on the iOS leg, which has the thread context). Losing symbolication is worth a warning, not a red build.
…on Android) (#2629) * fix: preview assets fail to load when cache file names exceed 255 bytes The SDK preview server hashes every file as `b64-<base64(absolutePath + "-" + os.hostname())>` (`b64HashingFunction` in @dcl/sdk-commands), and we used that hash verbatim as the file name inside the content cache folder. The name therefore grows with the developer's project path: a scene in a deep folder produces 250-300+ char hashes, past the 255-byte per-component filename limit of ext4/f2fs (Android), APFS and NTFS. Every `File::create` then failed with ENAMETOOLONG and the asset never loaded: GLTF load error for assets/asset-packs/lava/lava.glb: File creation error: Os { code: 36, kind: InvalidFilename, message: "File name too long" } Reproduced on a Samsung A54 with a scene whose root path is 128 chars: 120 of its 145 files were unreachable, while the short-path ones (bin/index.js, main.crdt) loaded — so the scene ran but came up empty. Fold names that don't fit — or that contain a path separator, which base64 can emit — into a short deterministic digest of the hash. Short catalyst CIDs are returned untouched, so existing caches and the `-mobile.zip` / `.scn` naming built on top of them are unaffected. Applied at every hash-to-path site, and exposed to GDScript as `content_provider.get_cache_file_path()` for the places that concatenated the raw hash themselves (main.js, main.crdt, video). Verified on device: the cache now holds the folded names and the scene loads with no GLTF errors. * fix: hard-cap cache file names at 128 bytes Deriving the cap from the 255-byte filesystem limit left it at 239, which is larger than it needs to be: everything we legitimately hash is far below it (catalyst CIDs are 59 bytes, url-texture `hashed_{hex}_q{N}` names ~74), and a name that long still pushes the *whole* path close to limits that bound the full string rather than one component (Windows MAX_PATH is 260). A flat 128 keeps every cache file name short with room to spare for the `wearable_`/`emote_` prefixes and the `.scn` / `.tmp` suffixes, and it now also folds the mid-length preview hashes (main.js, main.crdt) that happened to fit before. Re-verified on device: those two land under their folded names and the scene runs with no GLTF errors. * fix: keep MAX_FILE_NAME_BYTES used outside tests (clippy -D warnings) It only appeared in test asserts after the cap became a flat 128, so CI's `cargo clippy -- -D warnings` failed on dead_code. Tie it to the cap with a compile-time assertion, which also guards the relationship the comment claims.
Ludmilafantaniella
self-requested a review
August 5, 2026 13:03
…title (#2628) In preview mode the loading screen fetched place data from the Places API by parcel position, which returns whatever Genesis City scene owns those coordinates (e.g. Genesis Plaza at 0,0) — never the previewed scene. The scene's own scene.json display.navmapThumbnail was also unreadable client-side: the Rust metadata parser dropped the field. - lib: parse display.navmapThumbnail in SceneDisplay (+ serde tests) and expose get_navmap_thumbnail() on DclSceneEntityDefinition - scene_fetcher: add get_scene_definition_at(parcel) coordinator lookup - loading_screen: in preview mode, populate title/background from the local scene entity definition (thumbnail resolved via the content mapping) instead of querying the Places API; scenes without a thumbnail get a neutral loading state instead of Genesis data Fixes #2598
…2641) Floating-islands generation could never report completion when the camera saw none of its candidate parcels, leaving the loading screen up indefinitely over a scene that was already fully loaded and rendered. `tick_culling` only emitted `generation_complete` under `in_view_candidates > 0`. A candidate counts as in view when it is either within the `dist <= 1` ring or passes the camera-visibility test, so when every nearby candidate fails that test and none sits in the inner ring, the counter stays at zero and the condition is unsatisfiable. No parcel is enqueued in that state either, so `generation_progress` never fires and `generated_so_far` stays at 0 — generation is stuck with no way out. That matters beyond terrain, because `generation_complete` is what GDScript uses to clear `waiting_for_floating_islands` on the loading session, and `LoadingPhase::Assets` returns early while that flag is set. The phase is therefore held regardless of asset state: sessions were observed sitting at `all_loaded=true`, `stable=true`, grace period passed and the scene marked ready, for over 8 minutes, until the user dismissed the "taking longer than expected" modal via START ANYWAY. Treat "nothing in view" as vacuously complete, debounced by EMPTY_VIEW_COMPLETE_MS so a startup frame where the camera is not yet positioned cannot cut generation short. Terrain building is unaffected: enqueueing is not gated on `generating`, so parcels are still built as they come into view later. Verified on a Samsung A54 against the World that reproduced the stall: generation now completes ~1s in and the loading screen dismisses on its own, where the same build without the fix held for minutes.
…#2640) A GltfContainer that leaves and re-enters the scene tree (a normal SDK re-parent) never resumed its load, holding the loading screen until the 120s per-container timeout even though the scene was fully playable behind it. `_exit_tree` detached the container from its shared LoadGroup but left `dcl_gltf_loading_state` at LOADING with `_requested_hash` still set, and `_ready` — the only caller of `async_load_gltf` — runs once per node. The container ended up in LOADING with no group behind it, so Rust kept counting it in `scene.gltf_loading` and the session's `expected_assets` never balanced. `_exit_tree` now clears `_requested_hash` to mark the container detached, and a new `_enter_tree` re-requests the load when it finds itself mid-load and detached. The loading state deliberately stays LOADING across the detach so a re-parent resumes instead of reporting a false FINISHED; a finished or errored container is left alone. The Assets phase amplified this: its `all_loaded` gate had no escape hatch. `check_loading_timeouts` force-marks a stalled scene ready after SCENE_TIMEOUT_SECS, but that only fed `ready_scenes`, which the gate never consulted — so a handful of unresolved assets out of hundreds held the whole screen. Scenes in `ready_scenes` no longer gate the phase. Covered by `test_stalled_asset_does_not_pin_assets_phase`.
…2650) Avatar body snapshots (fetch_avatar_body_texture / fetch_default_avatar_body_texture) are consumed exclusively by the impostor capturer, which reads TextureEntry.image (raw pixels) and uploads them into its own TextureArray. The entry's texture is never rendered, yet the mobile pipeline ETC2-compressed the shared image in-place to build it — leaving image in a format that rejects resize/convert/mipmaps in set_impostor_texture (the Sentry error spam: 'Cannot resize in compressed image formats', 'Cannot generate mipmaps from compressed image formats', 'Image format must match texture's image format'). - load_image_texture gains a compress flag; body fetches pass false - compress=false keeps image raw and ships a 2x2 placeholder texture - capturer compresses the entry image to ETC2 after consuming it, so session-cached entries reclaim the same RAM as before - set_impostor_texture decompresses defensively (recaptures re-read the compressed entry image) Net: no compress→decompress cycle on first capture, same steady-state RAM, no error spam. Supersedes the decompress-only approach for this path.
The build label on a release-* PR produced staging-baked binaries labeled prod: on pull_request events github.ref is refs/pull/N/merge, so set-prod-flag never saw the release* branch, and android_builds ignored the ref input mobile_distribute passes on rebuilds. The AAB and Sentry symbol gates were also exact-matched to main/release, silently skipping versioned release branches. - android_builds: pass `inputs.ref || head_ref` to set-prod-flag; gate AAB export/sign/upload + Sentry symbols on the resolved branch with a release* prefix match - ios_self_hosted: pass `head_ref` to set-prod-flag so the reused R2 export is prod-baked - set-prod-flag: BRANCH_NAME prefers the explicit ref input - mobile_distribute: AAB wait gate matches release*; dispatch ref input is a free string so release-* branches can be dispatched manually Push triggers are unchanged: only main/release build on push; release-* branches distribute via the build label or manual dispatch.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Release Candidate 1.12.0
This is the release candidate for version 1.12.0. It promotes
mainas ofaa56a2beplus two cherry-picked loading-screen fixes (65 commits ahead ofrelease) intoreleaseonce QA sign-off is complete. Headline items: native email (OTP) sign-in, guest play enabled on iOS, a username picker with minted NFT names, the new Pulse avatar-sync transport (fail-closed behind a server flag) with a Multiplayer Debug panel, third-person camera collision (no more seeing through walls), the SDK ParticleSystem component, private-world access gating, mobile day/night lighting parity, SDK-driven custom mobile controls and asset preloading for scene creators, the guest upgrade & reward modals, plus a large batch of loading-reliability and comms-resilience fixes (infinite loading hangs, teleport crashes, wedged scenes, app-resume recovery) and full loading-funnel instrumentation so loading problems are measurable from field data instead of needing a repro.releaserelease-1.12.0(=main@aa56a2be+ cherry-picks of fix: loading screen pinned forever when no island parcel is in view #2641 and fix: re-parented GLTF container pins the loading screen for 2 minutes #2640, originals3f9f7d06/b88ce99e→edc428bc/d957b7a1here — new hashes because their parent onmainis the not-included98b3d217)1.12.0(lib/Cargo.toml,export_presets.cfg; bumped in bump marketing version to v1.12.0 #2499, forwarded after the 1.11.1 reconcile)releaseis fully contained in this branch. Twomaincommits are deliberately not included:98b3d217(PointerEvents HOVER, fix: PointerEvents HOVER on 3D entities populates hit and respects range (#2017) #2052) and8ef0e59c(in-game upgrade notice & badge, feat: in-game account upgrade notice & badge (landscape) #2622) — the cherry-picks resolve automatically whenreleaseis next reconciled intomain.🚀 What's included
Features
SignInWithEmailflow with resend, inline error handling and cancel-back-to-email (feat: native OTP email sign-in #2452, Closes AUTH: OTP Sign In Native Flow - Implementation #2373)#XXXXtag when you don't; saving setshas_claimed_namecorrectly. Also fixes tap-to-close behaviour on all profile dropdowns on mobile (feat: username picker with minted names support #2547)pulsefeature flag is explicitly true; local overrides viapulse=true/pulse-server=<host:port>/livekit=falsedeeplinks and--pulse/--no-pulseCLI, and a 5-strike session fallback parks it when the server never answers (blocked-UDP environments degrade silently to LiveKit-only). The LiveKit debug panel becomes a full Multiplayer Debug panel (multiplayer_debug=truedeeplink or the renamed Settings toggle): realm, adapter, main room, archipelago/island, scene room, Pulse state and per-peer room sources, collapsible on touch (feat: Pulse transport (ENet avatar-state relay) + Multiplayer Debug panel #2541)enable-upgraded-deletiondeeplink (feat: allow deleting an upgraded (email-linked) guest via double unlink #2482)PBMobileInputControls(hide the native joystick / gamepad) andPBUiInputBinding(bind a UI entity to InputActions, so scenes can build their own on-screen buttons), plus custom button icons and button-ordering/cascade forPBTouchScreenControls(feat: add MobileInputControls and UiInputBinding components #2330)PBAssetLoad/PBAssetLoadLoadingStatecomponents let a scene pre-download, process and GPU-upload assets ahead of time, so the matchingGltfContainerappears instantly instead of popping in. Preloads are refcounted per content hash and released on component removal / entity delete / scene unload (feat: implement sdk component asset preload #2567)PBParticleSystemmapped onto Godot'sGPUParticles3D, following the Unity Explorer reference: emission shapes (point/sphere/box/cone), bursts, size/rotation/color over lifetime, velocity/gravity/forces, billboard & blend modes, sprite-sheet animation, async texture loading, with per-emitter (5k) and per-scene (50k) particle budgets. Particles stop when the player leaves the scene and restart on re-entry. Verified side-by-side with the Unity client (feat(sdk): implement ParticleSystem component (#1538) #2588, Refs SDK Particle System Support #1538)GET /world/<name>/permissionsbefore every realm change (deeplink, /goto, Discover, SDK changeRealm, resume, cold start on both platforms) and shows a blocking "world is private" modal instead. Only plain wallet allow-lists deny; everything ambiguous (nft/shared-secret/network errors) fails open since comms remains the real enforcement (feat: block entry to private worlds and show the "is private" modal #2569, Refs Modals: Prevent Scene/World Load for Private Worlds - Implementation #1725)landscapeTerrain: falseinscene.json— a World or local-dev scene can now opt out of the auto-generated landscape terrain (the floating islands around it), matching Unity Explorer. Genesis City ignores the flag, and multi-scene setups keep their terrain (feat: support landscapeTerrain: false in scene.json (#2512) #2552, Refs [Feature Parity] Support scene.json 'landscapeTerrain: false' to disable landscape terrain in worlds #2512)Fixes
Loading & scenes
/aboutleft the loading screen stuck silently with no recoverable error (fix: infinite loading hangs (delete accounting, HTTP timeouts, download notify leak, malformed /about) #2461, Closes Count a deleted while loading GLTF as finished so the screen does not hang #2475 Add HTTP timeouts to the asset download path #2462 Release the pending_downloads slot on every path to avoid poisoning a hash #2463 Guard malformed /about content before committing realm state #2472, Refs Scene Lifecycle Issues: Investigation and Root Cause Analysis #1640 Scene Loading Process + Flow Review #1602 [Bug] Loading Stuck at 25% Endlessly #2450)spawnPointsomit thedefaultfield no longer breaks scene parsing; the field is now optional (fix(realm): make SpawnPoint.default optional so worlds without it still load #2528)GltfContainerthat leaves and re-enters the scene tree (a normal SDK re-parent) never resumed its load, holding the loading screen until the 120s per-container timeout over an already-playable scene; the load is now re-requested on re-entry, and scenes already force-marked ready no longer gate the Assets phase (cherry-pick of fix: re-parented GLTF container pins the loading screen for 2 minutes #2640)scene.jsontitle andnavmapThumbnail(fix: preview-mode loading screen shows the scene's own thumbnail and title #2628, Fixes [Bug] Loading Screen - Preview Mode: Shows Genesis Plaza load instead of correct scene thumbnail and data #2598)Camera & rendering
Multiplayer & comms
lambdasEndpointwithout a trailing slash, so profile fetches built a broken URL and burned ~5 minutes of retries while the avatar stayed a loading ghost; URL building fixed, broken peer endpoints fall back to the realm catalyst on the first hard error, and emotes arriving before the avatar finishes loading are latched and replayed (part of feat: Pulse transport (ENet avatar-state relay) + Multiplayer Debug panel #2541)Avatars, backpack & emotes
loopnot respected on mobile — the SDK Animator's loop parameter is now applied explicitly, and animations on duplicated GLTFs no longer break (fix: Animator loop parameter not respected on mobile (#2497) #2560, Fixes [Bug] Animator component: Loop parameter not respected on mobile #2497)UI & mobile
a1f58aef)uiBackgroundtexture wrapMode —REPEAT/MIRRORare now actually applied, so custom UVs beyond 1.0 tile instead of smearing the last column of pixels (fix(ui): apply uiBackground texture wrapMode (repeat/mirror) (#2506) #2544, Fixes [Bug] uiBackground wrapMode 'repeat' renders with incorrect stretch on mobile #2506)touch-feedbackdeeplink and never on production (feat: disable touch feedback by default, enable via deeplink #2565, Closes Disable touch feedback by default and add a deeplink for enabling it #2562)multiplayer_debug=true, no realm/position) popped a data-less "Scene Title" card over Discover; it now lands on Discover itself (part of feat: Pulse transport (ENet avatar-state relay) + Multiplayer Debug panel #2541)Analytics
Loading Event, discriminated bytype(started/progress/completed/asset_failure/realm_change_failed) and correlated across a whole load byloading_id, carrying phase breakdown, milestone timings, network throughput/stall and dismissal reason. Loads that never complete (user quits mid-load) now emit a progress pulse every 10s, so abandoned loads are still visible. No PII — no coords, URLs or addresses (feat(loading): scene-loading funnel in Rust → single Segment "Loading Event" + GLTF loading coordinator #2540, item P of Scene loading & lifecycle — infinite loads, stalls & root causes #2536, Refs Send loading telemetry to Segment so the loading decisions are data driven #2477)Guest Wallet Creationevent per guest-login attempt with outcome,is_new_user, classified failure reason, HTTP status and duration, for the guest-wallet stability dashboard (feat(analytics): track guest wallet creation to Segment #2554, Closes [Data] Guest User ThirdWeb Stability/Crash Alerts #2415)🔧 Technical / Other
Internal changes with no user-facing behaviour — no QA needed on these.
pkg@{version}.{build}-{hash}-{env}, which Sentry's semver parser rejects, sorelease.buildwas never populated andrelease.build:>=N/release.version:>=Xmatched nothing (adoption, regression detection and "resolved in next release" were all disabled). A dedicatedGODOT_EXPLORER_SENTRY_RELEASE={major.minor.patch}+{build}is now used foroptions.release; commit hash and environment are unchanged, already carried bydist/environment(fix(sentry): report release as clean semver so build/version filtering works #2566)https://mobile-bff.decentraland.org/feature-flags(fail-open, 5s timeout race) intoGlobal.feature_flags, with an archipelago kill-switch applied at runtime and the fail-closedpulseactivation flag;livekit/dual-channelflags are fetched but deliberately not applied yet (part of feat: Pulse transport (ENet avatar-state relay) + Multiplayer Debug panel #2541)onBackPressed()for targetSdk-36 apps, so the plugin manifest opts out viaenableOnBackInvokedCallback="false"— temporary, stops working at targetSdk 37 (chore(android): bump targetSdk to 36 (Android 16) #2601, Refs Bump AppTarget to Android 16 (API level 36) or higher #2587)idand timing out every command; the parser now splits balanced objects, repairs the corruption signature and stops spamming the error console. Also wires the in-game Pause Scene toggle, which was inert once inside the explorer (fix: scene-inspector command timeout on corrupted WebSocket frames + wire in-game Pause Scene toggle #2564, Closes Bug: Scene inspector timeouts on some commands #2563)apksignerand the read-only token killed the build-report comment; both now degrade gracefully (ci(android): don't fail fork-PR builds on APK signing / build-report comment #2530)target/between runs on a single shared disk, so caches grew until cargo died mid-link with "No space left on device"; the runner now prunes Rust caches below a 40 GB thresholdsentry-cli upload-dif --waitpropagates Sentry's server-side result, so a Sentry hiccup went red on a job whose artifacts had already shipped; both symbol-upload steps (iOS dSYM, Android) are nowcontinue-on-errorwith a warning annotation instead (ci: never fail a mobile build on a Sentry symbol upload error #2627)@dcl/protocolpinned — tracking thenextdist-tag re-resolved the protocol on every run, so an upstream publish broke all platform builds mid-RC with no repo change (commits94d964d3,39a8d2b4). The pin has since been repointed at the npm build of merged protocolmain(commit-0ff6038, after protocol PR Update README.md - typo fix #449 landed the Pulse protos) and stays as an explicit pin so upstream publishes can't change builds without a repo change (part of feat: Pulse transport (ENet avatar-state relay) + Multiplayer Debug panel #2541)/commandsextracted fromexplorer.gdintoChatCommands, avatar bone-merging + toon materials intoAvatarMeshAssembler; pure moves, no behavior change (part of feat: Pulse transport (ENet avatar-state relay) + Multiplayer Debug panel #2541)REVIEW.md§4 (docs(review): add QA-executable test-steps guide to REVIEW.md #2448)release(1.11.1) reconciled intomainand version forwarded (chore: reconcile release (1.11.1) into main + forward version to 1.12.0 #2559)🧪 Test Plan
Build under test:
v1.12.0.<build>-d957b7a-prod· prod flavorDevices: at least one low-spec phone + one Android. Grab the APK from the build-report comment, or the TestFlight build (
buildlabel).0. Version check
v1.12.0.<build>-d957b7a-prod— version1.12.0, commitd957b7a,prodenv.1. Native email sign-in
#2452foo@) → the NEXT button stays disabled and the error only appears after the field loses focus.2. Play as Guest on iOS
#25453. Username picker with minted names
#2547#XXXXtag visible.#XXXXtag (not claimed).4. On-chain wearables equip / unequip
#2480#24535. Emote wheel rarity colour
#2457#17436. Animation loop on mobile
#2560#24977. Featured card tap
#2456#21058. Profile screen after backgrounding (iOS)
#2478#23789. Loading reliability
#2461#2229#2528#2541#2640#2641decentraland://open?realm=pixelarcade.dcl.ethon the phone → the world loads (previously it silently timed out).10. Loading screen polish
#2539#2491#256111.
uiBackgroundtexture wrapMode#2544#2506uiBackgroundtexture → the texture tiles/repeats correctly instead of smearing the last column of pixels across the area.12. Event detail name overflow
#2537#248513. Settings — skybox
#2538#232614. Low-memory modal copy
#2526#252715. Guest upgrade notice
#248316. "Updating profile…" overlay
#248717. Touch feedback off by default
#2565#2562decentraland://open?touch-feedback=trueon the phone (non-production build) → tap/drag → a circle now follows each finger, one per finger with multi-touch.decentraland://open?touch-feedback=false→ circles stop appearing. Normal touch input (buttons, camera drag, joystick) works throughout.18. SDK: custom mobile controls
#2330-31,19(Eibriel Tests) → from the on-screen list pick Controls → select a scheme that hides a button → the hidden button disappears and the remaining buttons cascade up to fill the gap; nothing is left stuck in the pressed state.19. SDK: asset preload
#2567mannakia.dcl.eth→ the AssetLoad Preload demo loads and its banner announces either PRELOAD ALL or NO PRELOAD for this run.20. Scenes:
landscapeTerrain: false#2552#2512decentraland://open?realm=huevo.dcl.ethon the phone → the world loads to 100% (no hang) → look around: there is no grass, no trees and no floating islands around the scene, just sky. The scene's NPC avatars render normally.pixelarcade.dcl.eth) → terrain (grass, islands, trees) renders as usual.21. Camera vs walls
#2546#181422. SDK: ParticleSystem
#2588#1538sdk7testscenes.dcl.ethat0,7(orkuruk.dcl.eth) → particle effects render: emitters play with visible shapes, bursts, colors and textures; framerate stays playable and nothing crashes.23. Private worlds
#2569#1725/goto <world>in chat) → a blocking "world is private" modal appears with BACK TO DISCOVER; the world never starts loading behind it.24. Day/night lighting
#2574#251625. TextShape
#2602#2371sdk7testscenes.dcl.eth) → text renders at the intended size with correct line spacing and alignment — no overlapping lines, no clipped or floating text.26. HUD no longer blocks scene UI taps
#2616#2578-31,19Eibriel Tests), tap scene UI elements in the top-right area and at the bottom-center (near the version/FPS text) → they respond; no invisible HUD area swallows the tap.27. Own profile picture on a new account
#2619#235928. Guest upgrade & reward modals
#2553Setup: the upgrade modal's cadence in prod is 18h after the first guest session (then +5d, +5d) — plan the nudge check accordingly.
29. Controls after closing a player profile
#257930. Android 16 back gesture
#260131. Comms after backgrounding + Multiplayer Debug
#2541#2571decentraland://open?multiplayer_debug=true→ the Multiplayer Debug panel overlays the world showing realm, adapter, main room, archipelago/island, scene room and a Pulse line; the header collapse button shrinks it to just the header and expands it back. The panel is still there after jumping into a scene from Discover.pulsefeature flag is on — avatar sync stays on LiveKit and other players move normally throughout.32. Nametag above tall wearables
#2620#258033. Scene preview mode (creators)
#2628#2629#2598Setup: run a scene locally with the SDK (
npm run startin a scene project) and open its preview deeplink on the phone. For the second check the scene project must live in a deeply nested folder (long absolute path).scene.json) — not Genesis Plaza's; a scene without a thumbnail shows a neutral loading state.34. Regression — core flows