Skip to content

Release Candidate 1.12.0 - #2572

Merged
kuruk-mm merged 67 commits into
releasefrom
release-1.12.0
Aug 6, 2026
Merged

Release Candidate 1.12.0#2572
kuruk-mm merged 67 commits into
releasefrom
release-1.12.0

Conversation

@kuruk-mm

@kuruk-mm kuruk-mm commented Jul 23, 2026

Copy link
Copy Markdown
Member

Release Candidate 1.12.0

This is the release candidate for version 1.12.0. It promotes main as of aa56a2be plus two cherry-picked loading-screen fixes (65 commits ahead of release) into release once 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.


🚀 What's included

Features

Fixes

Loading & scenes

Camera & rendering

Multiplayer & comms

Avatars, backpack & emotes

UI & mobile

Analytics


🔧 Technical / Other

Internal changes with no user-facing behaviour — no QA needed on these.


🧪 Test Plan

Build under test: v1.12.0.<build>-d957b7a-prod · prod flavor
Devices: at least one low-spec phone + one Android. Grab the APK from the build-report comment, or the TestFlight build (build label).

Deep links are universal links — open them on the phone, or push via adb:
adb shell am start -a android.intent.action.VIEW -d "<link>"

0. Version check

  • Launch the app. The version on the login/lobby screen and under Settings → About reads v1.12.0.<build>-d957b7a-prod — version 1.12.0, commit d957b7a, prod env.

1. Native email sign-in #2452

  • Open the app → on the sign-in screen tap Email → enter an email you have never used → enter the 6-digit code from your inbox → the app signs in and enters the world.
  • Repeat with an email already linked to an existing account → the same OTP flow completes and signs in as that account's wallet.
  • Enter a wrong 6-digit code → an inline error appears in the code modal and you can retry.
  • Tap Resend email in the code modal → a new code arrives and the cooldown timer resets.
  • Tap X in the code modal → you return to the email input screen (not the home screen).
  • Type a malformed email (e.g. foo@) → the NEXT button stays disabled and the error only appears after the field loses focus.

2. Play as Guest on iOS #2545

  • On an iPhone, open the app → the account chooser screen appears with a Play as Guest option → tap it → you land in-world as a guest with no wallet.

3. Username picker with minted names #2547

  • Sign in with an account that owns no minted names → open Profile → Edit → only a free-text name input is shown, with the #XXXX tag visible.
  • Sign in with an account that owns one or more minted names → open Profile → EditUnique / Non-unique tabs appear; the Unique tab shows a dropdown listing your names with a verified icon on the selected one.
  • Select a minted name → tap Save → reopen the profile → the name persists and shows the verified icon (claimed name).
  • Switch to the Non-unique tab, type a free-text name → tap Save → the name persists with the #XXXX tag (not claimed).
  • Open and close the profile editor rapidly several times → the dropdown never shows duplicated names.
  • On mobile, open and close the gender, sexual orientation and relationship status dropdowns by tapping → each opens and closes correctly on tap.

4. On-chain wearables equip / unequip #2480 #2453

  • Sign in with a wallet that owns on-chain (minted) wearables already equipped in its profile → open the Backpack → those wearables show as equipped (highlighted) in the grid.
  • Tap an equipped on-chain wearable to unequip it → it visibly unequips on the avatar (e.g. hair or lower body reappears if it was hidden).
  • Equip a different wearable of the same category → the previous on-chain one is replaced, not duplicated.

5. Emote wheel rarity colour #2457 #1743

  • Open the emote wheel in-world → each slot's background shows its emote's rarity colour, and non-common emotes show a glow; common ones do not.
  • Open Backpack → Emotes → rarity backgrounds still render correctly for every rarity.

6. Animation loop on mobile #2560 #2497

  • On a phone, enter a scene with a looping SDK animation (e.g. an ambient/idle-animated model) → the animation loops continuously instead of playing once and freezing.
  • In a scene using the same GLTF more than once, all copies animate correctly.

7. Featured card tap #2456 #2105

  • Open the app → on Discover, tap the body of a featured card → the detail panel opens with the place info (it does not teleport you).
  • Tap the JUMP IN button on the featured card → you teleport directly into the place.
  • While the featured carousel is auto-scrolling, tap a card → the card snaps into position correctly and the detail panel opens.

8. Profile screen after backgrounding (iOS) #2478 #2378

  • On an iPhone, open the Profile screen, drag the bottom sheet to the half position, then use the swipe-up-to-home gesture → reopen the app → the card is still at half (not full).
  • With the card at full, background the app normally and return → the card is still full.
  • After returning from background, the card's width matches the screen width — no part of it extends past the display edge.

9. Loading reliability #2461 #2229 #2528 #2541 #2640 #2641

  • Enter Genesis Plaza, then teleport to another place while it is still loading, several times in a row → each teleport completes and the previous scene is gone (no old scene rendered on top of the new one).
  • With a throttled / poor network connection, enter a heavy scene → loading either completes or shows a recoverable error; it never sits forever at 25–30%.
  • Open decentraland://open?realm=pixelarcade.dcl.eth on the phone → the world loads (previously it silently timed out).
  • On Android, return to Discover and jump back into a place within 1–3 seconds, 5 times in a row → no crash (previously ~3/4 repro on low-RAM devices). Also sign out and back in mid-session → no crash.
  • Enter several Worlds and Genesis places back to back → every loading screen dismisses on its own within a normal time; it never sits for minutes over a scene that is already visible/playable behind it, and you never need START ANYWAY to get in.

10. Loading screen polish #2539 #2491 #2561

  • Cold-start the app (fully closed, then launch) → you see only the isotype logo during startup — no full logo, no logo swaps or flashes.
  • Open chat (or a search field) so the on-screen keyboard is up, then trigger a teleport → the keyboard disappears and chat collapses the moment the loading screen appears; nothing overlaps the loading UI.
  • Watch the loading screen for a place with a short 1-line name (e.g. CozyFarm) and one with a long name → the details text and % are spaced correctly and there is no large empty gap below the title.

11. uiBackground texture wrapMode #2544 #2506

  • Enter a scene that uses a tiled uiBackground texture → the texture tiles/repeats correctly instead of smearing the last column of pixels across the area.

12. Event detail name overflow #2537 #2485

  • On Discover → Events, open the detail for an event with a very long place name (e.g. "HODL National Park Climb and 'Vertical Voyager' Badge Unlock!") → the place name is truncated and stays inside the card; image, title, buttons and pills all render correctly.

13. Settings — skybox #2538 #2326

  • Open Settings in-world → in the graphics/skybox section there is no broken preview box (previously showed garbage pixels) and no leftover empty gap.
  • The Dynamic Skybox toggle and Custom Skybox dropdown still work and change the sky.

14. Low-memory modal copy #2526 #2527

  • On a low-spec phone, enter a heavy scene until the low-memory warning appears → the title reads "Low memory", the body reads "This place may not run smoothly on your device and could close unexpectedly.", and the buttons read CONTINUE and BACK TO DISCOVER.

15. Guest upgrade notice #2483

  • Sign in with an account that has already completed the guest → web3 upgrade, land on Discover → the guest upgrade notice never appears, not even as a brief flash, on this and every subsequent session.
  • Sign in as a non-upgraded guest → the notice still shows on Discover, and clearing a search re-shows it.

16. "Updating profile…" overlay #2487

  • Open the Backpack, change a wearable and save → while the "Updating profile…" overlay is up, the bottom of the screen shows the blurred, dimmed navbar — not a flat gray band.

17. Touch feedback off by default #2565 #2562

  • Launch the app on a phone and tap/drag around → no translucent circles appear under your fingers.
  • Open decentraland://open?touch-feedback=true on the phone (non-production build) → tap/drag → a circle now follows each finger, one per finger with multi-touch.
  • Open decentraland://open?touch-feedback=false → circles stop appearing. Normal touch input (buttons, camera drag, joystick) works throughout.

18. SDK: custom mobile controls #2330

  • On a phone, teleport to -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.
  • Pick the Changing Icons scheme (or Controls Builder → cycle an icon) → the scene's custom icon replaces the default button icon.
  • While a scheme with replaced icons is active, walk up to an interactable → the interaction hint shows the scene's replaced icon, not the default one.
  • Return to a normal scene → the native joystick and buttons are back to their default layout and icons.

19. SDK: asset preload #2567

  • Clear the app cache, then teleport to mannakia.dcl.eth → the AssetLoad Preload demo loads and its banner announces either PRELOAD ALL or NO PRELOAD for this run.
  • Tap Instantiate → note how long the models take to appear. Clear the cache and repeat until you have seen both banners → the PRELOAD ALL run instantiates noticeably faster, with no download stall or pop-in.

20. Scenes: landscapeTerrain: false #2552 #2512

  • Open decentraland://open?realm=huevo.dcl.eth on 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.
  • Enter any other World (e.g. pixelarcade.dcl.eth) → terrain (grass, islands, trees) renders as usual.
  • Teleport to Genesis Plaza → terrain renders as usual.

21. Camera vs walls #2546 #1814

  • In third person, walk into a tight interior (e.g. the Genesis Plaza theatre or any small room), press your back against each wall and orbit the camera 360° → the camera never shows the far side of a wall; it pulls in close to the avatar instead.
  • Hug a thin wall or door frame and orbit → the camera stays on your side (previously it could cross to the far side of the door).
  • Back into a corner until the camera is forced all the way in → the avatar fades out smoothly instead of showing the inside of its head; step away → it fades back in.
  • Walk the camera through other players/NPCs → they only fade when the camera is almost touching them, and reappear immediately after.

22. SDK: ParticleSystem #2588 #1538

  • Teleport to sdk7testscenes.dcl.eth at 0,7 (or kuruk.dcl.eth) → particle effects render: emitters play with visible shapes, bursts, colors and textures; framerate stays playable and nothing crashes.
  • Teleport away and come back → particles are cleared while away and restart cleanly on re-entry.

23. Private worlds #2569 #1725

  • With a wallet not on the allow-list of a private World, try to enter it (Discover jump-in or /goto <world> in chat) → a blocking "world is private" modal appears with BACK TO DISCOVER; the world never starts loading behind it.
  • Cold-start the app from a deeplink to that private World — on Android and iOS → the modal shows over Discover; dismissing it leaves you on Discover (no empty scene, no random Genesis parcel), and a later teleport does not re-show the modal.
  • Enter a World you are allowed into, and any public World → both load normally.

24. Day/night lighting #2574 #2516

  • Open Settings → Developer → Fast Day/Night Cycle (10s) and watch a full cycle in-world → at night the world is not pitch black: a moon is visible in the sky, the ground stays readable, and objects still cast shadows.
  • During the day, shadows point away from the visible sun (and at night away from the moon), including at morning and afternoon.
  • Morning/afternoon light looks neutral — no strong salmon/pink tint.

25. TextShape #2602 #2371

  • Enter a scene with SDK 3D text (e.g. sdk7testscenes.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

  • In a scene with its own full-screen UI (e.g. -31,19 Eibriel 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.
  • Tap the round profile bubble in the navbar → the profile still opens; tap just outside the bubble → the tap reaches the scene underneath.

27. Own profile picture on a new account #2619 #2359

  • Create a brand-new account (email sign-in with a never-used email) → right after entering the world, your own face picture shows in the navbar bubble and on the Profile screen — no placeholder silhouette, no restart needed.

28. Guest upgrade & reward modals #2553

Setup: the upgrade modal's cadence in prod is 18h after the first guest session (then +5d, +5d) — plan the nudge check accordingly.

  • As a guest, the upgrade card copy reads "Add email to play across mobile and desktop, and claim an exclusive Wearable."
  • Complete the guest → email upgrade → the reward modal appears; tap CLAIM REWARD → a spinner shows while the claim runs, then the reward art reveals with the rays animation, the button flips to CLAIMED and "Tap to continue" appears; a tap dismisses the modal.
  • The reward image is not squashed (square art stays square, centered).
  • Force a claim failure (e.g. airplane mode right before tapping CLAIM) → a friendly error message shows, never a raw error code.

29. Controls after closing a player profile #2579

  • In-world, tap another player's avatar to open their profile, then close it → the joystick and camera respond immediately (no dead controls, no extra tap needed). On desktop, mouse capture returns to the world.

30. Android 16 back gesture #2601

  • On an Android 16 phone, use the system back gesture/button in-world and inside menus (Backpack, Settings, Map) → back closes the current panel or shows the normal exit behaviour; the app never insta-quits.

31. Comms after backgrounding + Multiplayer Debug #2541 #2571

  • In a crowded place with other avatars visible, background the app for ~1 minute, then resume → within a few seconds the other avatars, chat and voice recover on their own; no "session ended" modal appears from reclaiming your own stale session.
  • Open decentraland://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.
  • The Pulse line shows the transport disabled unless the server pulse feature flag is on — avatar sync stays on LiveKit and other players move normally throughout.

32. Nametag above tall wearables #2620 #2580

  • Equip a tall head wearable (helmet, top hat or similar) → your nametag floats above the wearable, not through it; other players' tags behave the same.
  • Play an emote that moves the head (e.g. a dance or crouch) → the tag follows the avatar's height during the emote and settles back afterwards.

33. Scene preview mode (creators) #2628 #2629 #2598

Setup: run a scene locally with the SDK (npm run start in 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).

  • While the preview loads, the loading screen shows the previewed scene's own title and thumbnail (from its scene.json) — not Genesis Plaza's; a scene without a thumbnail shows a neutral loading state.
  • On Android, preview the scene from the deep folder → all assets load (models, textures visible in-world); previously most assets failed and the scene came up empty.

34. Regression — core flows

  • Sign in with a wallet (existing flow) → enters the world normally.
  • Sign in as a guest on Android → enters the world normally.
  • Walk, jump, use the joystick and camera drag in-world → all movement and camera controls respond.
  • Open Backpack, Profile, Settings, Map, Emote wheel and Chat, and close each → no crash, no stuck overlay, no missing UI.
  • Teleport between at least three places (including Genesis Plaza and a World) → each loads and the previous one unloads.

sdilauro and others added 30 commits July 8, 2026 11:08
* 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
…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.
@kuruk-mm kuruk-mm added the build trigger ci build label Aug 4, 2026
@kuruk-mm
kuruk-mm marked this pull request as ready for review August 4, 2026 21:42
@kuruk-mm
kuruk-mm requested a review from a team as a code owner August 4, 2026 21:42
@github-actions github-actions Bot removed the build trigger ci build label Aug 4, 2026
@github-actions

github-actions Bot commented Aug 4, 2026

Copy link
Copy Markdown
Contributor

📱 Mobile Distribution Triggered

🍏 iOS → TestFlight   |   🤖 Android → APK ready (R2) + Slack notification

🔗 Workflow: View run

📍 Branch: release-1.12.0

🔄 Triggered: 2026-08-06 15:01:07 UTC

@github-actions

github-actions Bot commented Aug 4, 2026

Copy link
Copy Markdown
Contributor

🤖 Android Build Ready

Status: APK available for download

📱 Download APK: direct link

🔗 Workflow: View run

📍 Branch: release-1.12.0   |   Commit: 6dd39b7b3aff5726168125f09b0d4959dd47dc57

🔄 Completed: 2026-08-06 15:23:46 UTC

manuelmaceira and others added 3 commits August 5, 2026 12:29
* 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
Ludmilafantaniella self-requested a review August 5, 2026 13:03
kuruk-mm and others added 3 commits August 5, 2026 10:58
…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`.
@kuruk-mm kuruk-mm added the build trigger ci build label Aug 6, 2026
@github-actions github-actions Bot removed the build trigger ci build label Aug 6, 2026
…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.
@kuruk-mm kuruk-mm added the build trigger ci build label Aug 6, 2026
@github-actions github-actions Bot removed the build trigger ci build label Aug 6, 2026
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.
@kuruk-mm kuruk-mm added build trigger ci build and removed build trigger ci build labels Aug 6, 2026
@kuruk-mm
kuruk-mm merged commit de4ac26 into release Aug 6, 2026
@kuruk-mm
kuruk-mm deleted the release-1.12.0 branch August 6, 2026 21:21
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

build trigger ci build

Projects

Status: Done

Development

Successfully merging this pull request may close these issues.

7 participants