Conversation
Contributor
🚦 CI StatusBuild skipped — no changes detected under No C# files changed — lint ratchet skipped. Waiting for tests to start… Bare-metal benchmarks run automatically after each successful build; results land in this section. Add the On demand — comment |
eordano
force-pushed
the
dev-linux
branch
2 times, most recently
from
September 13, 2026 19:54
cea98d6 to
dbe9fcd
Compare
Contributor
Avatar Preview Renderer — Vercel Preview is ready!
|
Enable the Linux standalone build target with Vulkan first and GLCore as the fallback graphics API, and align serialized project state with the 6000.5 editor: new player-settings fields, URP global settings, graphics/quality settings, TextMesh Pro font atlases and the localization string table as re-serialized by the editor. Graphics jobs are switched off for the Vulkan path.
Add BuildScript entry points that produce Linux, Windows and macOS players via -executeMethod, so player builds run unattended without an interactive editor session, plus small editor probes for diagnosing a player bring-up: shader-bundle contents, shader error dumps, PNG decoding and grass mesh inspection.
feat(linux): Linux native plugin binaries
Ship the linux-x86_64 natives the player loads at runtime — ClearScript
V8, KTX, the LiveKit FFI and its audio helper, native audio analysis,
the process helper, a window-resize constraint, Sentry, and the
Ethereum signer — with import settings scoped to Linux64. The Ethereum
signer ships as librust_eth.so: DllImport("rust_eth") resolves through
the platform's lib-prefix convention on Linux, and without the prefixed
name every signing call dies at runtime with DllNotFoundException.
Folds in:
- docs(plugins): record what each shipped native actually is
- chore(plugins): add the missing meta for the ENet provenance note
- docs(segment): record the analytics native as unreproducible
- the UUAV plugin's README and third-party notices (from feat(media): UUAV media backend)
build(provenance): Nix derivations that rebuild the shipped native plugins
The Linux player ships prebuilt native libraries whose origin was recorded
only in prose, so a reader had no way to check what was in them. Add a
derivation per binary that rebuilds it from hash-pinned sources, a tool that
compares a rebuild against the committed artifact by dynamic symbol table,
and a lock file recording the verdict for each.
Sources are pinned public releases wherever one exists; the UUAV workspace,
which has no upstream repository, builds from its sources in this repository.
Derivations that cannot yet be reproduced byte-for-byte carry notes
explaining the difference, and the README records the outstanding gaps.
Folds in:
- build(provenance): make a rebuild depend only on pinned inputs
Embed the LiveKit client SDK as a local package with the native FFI enabled on Linux. The upstream package gates Linux out at compile time (NativeMethods stubbed behind !UNITY_STANDALONE_LINUX), so FfiClient.InitializeSdk throws PlatformNotSupportedException before the first loading stage and no Linux player can boot with comms enabled. The vendored copy carries the Linux bindings and native library so voice and multiplayer work as on the other desktop platforms.
Replace the proprietary GPU-instancing dependency with an interface-compatible implementation: GPU-driven indirect rendering with compute frustum culling, Hi-Z occlusion culling, LOD selection with cross-fade dissolve, and shadow casters culled by the profile's shadow distance and frustum. Includes the tree/vegetation prototype assets bound to the instancer and an EditMode test assembly. Clean-room provenance: written against the consumer-facing surface only (the call sites, serialized fields and GUIDs this project already binds to), without reference to the replaced package's sources or binaries, so the repository carries no proprietary code.
Reimplement the render features the renderer asset binds: the ocean water surface (waves, reflection, refraction, foam, depth color), the skybox-to-cubemap environment probe, object highlight, and the avatar outline including the compute-skinned pose path. Shaders are bound through the renderer asset rather than ad-hoc references, and orphaned sub-assets plus a dead duplicate of the skybox probe are dropped. Clean-room provenance: written against the consumer-facing surface only (serialized renderer-feature fields, material schemas and GUIDs already referenced from this project), without reference to the replaced package's sources or binaries. Folds in: - fix(packages): orient full-screen passes by the platform's UV origin
Reimplement the stylized grass rendering the landscape uses: the grass colour map is baked from the live ground splat data, and the shader samples the authored wind and translucency maps, billboards blades, applies hue variation and normal remapping, and fades blades by view angle. Clean-room provenance: written against the consumer-facing surface only (component names, serialized fields and GUIDs already referenced from this project), without reference to the replaced package's sources or binaries.
Reimplement the recycling list/grid scroll views the UI binds to: LoopListView2 moves panels in anchored space and settles jumps on measured row sizes, LoopGridView implements snapping and treats mItemRecycleDistance as the grid's resident window, Reset no longer latches initialization on an unconfigured view, and an EditMode test assembly covers the behavior. Clean-room provenance: written against the consumer-facing surface only (types, fields and GUIDs already referenced from this project), without reference to the replaced package's sources or binaries. Folds in: - fix(packages): keep the item count when a list view is reset
Reimplement the file-dialog package behind the FileBrowser surface the creation and bug-reporting flows call. On Linux, dialogs go through the XDG desktop portal first and fall back to zenity/kdialog, and AllowSyncCalls gates the blocking variants so callers that cannot block the main thread fail fast instead of deadlocking. Clean-room provenance: written against the consumer-facing surface only (the FileBrowser entry points and GUIDs already referenced from this project), without reference to the replaced package's sources or binaries.
…plorer/PackagesLocal chore(packages): resolve the clean-room and vendored packages from Explorer/PackagesLocal Switch both package manifests and locks from the private git and registry references to the local packages introduced in the previous commits, so a fresh checkout builds without access to private registries or third-party git hosts and the dependency content is pinned by the repository itself. unity-shared-dependencies keeps resolving from the in-repo copy. Folds in: - chore(packages): resolve every local package from Explorer/PackagesLocal ci: drop the SSH agent steps now that no package needs a private remote Every Git dependency of the Explorer and of avatar-preview-renderer now resolves over HTTPS or from Explorer/PackagesLocal, so the SSH_PRIVATE_KEY agent, the GitHub known-hosts steps, the /tmp/ssh-unity staging and the sshAgent input to the Unity test runner have nothing left to serve.
feat(media): UUAV media backend Add the UUAV playback backend — an ffmpeg-based decoder with bundled LGPL ffmpeg shared libraries, a native core and an out-of-process helper — as the media playback path. Tests cover the audio ring, frame handoff, ffmpeg command-line construction and log parsing. refactor(media): collapse video playback onto the UUAV backend The media layer kept a runtime choice between video backends and carried the naming of a proprietary package this repository does not ship. Only the UUAV path remains: the selection layer is removed, consumers bind the UUAV-backed player directly, and the surrounding assemblies, namespaces and types take vendor-neutral names. feat(media): Linux sources for the UUAV native workspace The Rust workspace that produces the UUAV media plugins carried macOS and Windows implementations only; the Linux binaries it ships could not be rebuilt from this repository. Add the Linux side — VAAPI/dma-buf device setup, video decode and output, GL and Vulkan presentation, unix-socket and fd-passing IPC channels, the helper's device/watch/sandbox paths — so the platform builds from source alongside the other two. fix(uuav): pass Vulkan external textures as VkImage* cells Root cause: Texture2D.CreateExternalTexture is handed the decoder's plane handles by value. Unity's Vulkan backend dereferences nativeTex once as a VkImage*, so on Linux/Vulkan the VkImage handle itself was treated as a pointer and the Y/UV planes never wrapped a valid image; video surfaces stayed black and the render thread could fault on the bogus address. Metal, D3D11 and GL take the handle by value and were fine. Fix: on Vulkan, write each plane handle into an unmanaged cell and pass the cell's address. The render thread reads the cell when the queued create executes, up to RenderThreadLagFrames later, so cells are never rewritten: each create takes a fresh frame-stamped cell, cells are freed only once the current frame is past the lag, and cells still inside the lag when a player is destroyed move to a shared retired list swept by the next create or destroy on any player. The cell bookkeeping is a pure helper covered by VulkanImageCellsShould. Verified on Linux: Genesis Plaza's theatre screen plays through the golden ready-wait and two world-only frames 12 s apart show different video frames.
feat(ab): asset-bundle routing for Linux with an embedded generator fallback Linux players load the windows-suffix asset bundles — bundle content is platform-portable, and the shader bundles that are not ship embedded separately — and when the CDN has no bundle for a scene, an embedded asset-bundle generator (abgen) produces it locally, so scenes the bundle pipeline never built still load. Includes the Linux addressables content state and the manifest-version plumbing the fallback needs. fix(linux): embed the scene and LOD shader bundles for Linux Scene materials resolve through platform shader bundles; without Linux variants every scene mesh fell back to the error shader and LOD texture arrays had no shader at all. Embed the Linux scene shader bundle and the LOD texture-array bundle in StreamingAssets, teach the bundle-loading parameters to prefer the embedded copies, and cover the embedded-bundle path with an EditMode test. An editor utility compiles the scene shader bundle so the embedded copies can be regenerated.
…textures under Vulkan fix(webrequests): decode palette-indexed PNGs on the CPU Unity's LoadImage on Linux reports success for palette-indexed (color type 3) PNGs but never writes the decoded pixels, so the texture keeps whatever memory backed its allocation. Decode these explicitly and expand the palette to RGBA before the bytes reach the engine (non-interlaced, bit depths 1/2/4/8), and route NFT image loading — where such PNGs are common — through the decoder. fix(rendering): GPU-instanced landscape rendering under Vulkan Fix the GPU-instancing service for the Vulkan backend and re-serialize the landscape's instanced assets to match: the rock, tree and tree-impostor shaders and the instanced materials for trees, rocks, flowers, terrain and the debug asset set. fix(streamable): own the texture bytes across async disk-cache writes GetRawTextureData returns a view into the texture's native memory, but the serializer's iterator is consumed across an asynchronous disk write, during which the texture can be destroyed or re-uploaded — the write would then persist freed or repurposed memory. Snapshot the bytes into an owned copy that lives until the iterator is disposed, with a matching guard in the game-object pool. fix(landscape): match Rock_GPUI surface model to the authored materials Root cause: the Rock_GPUI shader modelled the rock surface with a hand-rolled Lambert loop and a normal-map-driven moss term, and ignored the _TerrainRender/_Tiling/_Offset and _Smoothness slots the Rock0*_GPUIPro materials bind. Rocks therefore lost the terrain-colour blend at their foot, grew moss per normal-map texel instead of per facet, rendered without specular and, lacking the _CLUSTER_LIGHT_LOOP, _LIGHT_LAYERS and soft-shadow quality keywords, lit differently from every other Forward+ surface around them. Fix: sample the terrain base colour by absolute world XZ and blend it in by absolute world height, drive moss from the world-space vertex normal via _MossMask, and shade through UniversalFragmentPBR (metallic 0, smoothness _Smoothness) with the pipeline's full keyword set, evaluating fog per fragment from positionWS. Verified on Linux against the upstream baseline frame. fix(roads): draw the hedge foliage through the road instancer branch of StylizedGrass Genesis City's planter hedges, flower rows and fountain hedges are drawn only through the roads GPU instancer. After the third-party instancer was removed, StylizedGrass carried no _GPU_INSTANCER_BATCHER branch to read the instancer's per-instance buffers, so every hedge draw produced nothing and only the plain pot and soil-bed meshes remained along the roads. Add the road-instancer branch to StylizedGrass and register the shader with the GPU instancing settings; StylizedGrassShaderShould pins the keyword and buffer declarations. Verified on Linux: foliage renders at every planter position the upstream baseline shows. Its palette does not yet match the baseline (dark, speckled foliage), which remains open.
The scene and toon shaders in the in-repo shared package fail to compile or read undefined bytes under Vulkan (glslang / DXC SPIR-V): - DOTS-instanced integer properties expand to an intBitsToFloat overload glslang cannot resolve; store them as float and cast at the read site, as URP itself does. - The legacy CGPROGRAM global unity_ObjectToWorld is undeclared under DXC; use GetObjectToWorldMatrix() at every site. - _MatCap_Sampler_ST is never uploaded on this pipeline; sample with the raw rotated UV. - The toon LitInput replacement claims URP 17.5's include guard so the real LitInput cannot shadow it.
Resolve the genesis world manifest from the realm's own base domain rather than assuming the canonical hosts, and treat an empty manifest as a valid deployment — like the worlds-terrain Disabled case — instead of a transient failure, so teleports into self-hosted realms do not throw. Comms against local untrusted realms go through the loopback ICE policy. URL sources, realm data and the manifest provider carry the plumbing, with EditMode coverage for both.
…lease tagging fix(linux): read launcher bridge files from the XDG data dir The deep-link sentinel and the token-file authenticator looked for the launcher's bridge files in a location the Linux launcher does not write to; resolve them under the XDG data directory so identity handoff from the launcher works on Linux. chore(sentry): 6000.5 option fields and a Linux preview release tag Re-serialize SentryOptions with the fields the 6000.5 SDK adds, and tag Linux preview builds with their own release name so their crash reports group separately from the established desktop releases. feat(linux): self-serve update channel and update flow Linux ships without the launcher the other desktop platforms rely on for updates, so the version guard gets a Linux path: it polls the release channel (latest-linux.json), compares versions by their numeric core so preview suffixes do not defeat the comparison, and fails open when the channel is unreachable — an unreachable update channel is not a reason to block a boot. The update popup works end to end: UPDATE NOW opens the download page (or defers to the owning launcher process when one exists and exits), USE CURRENT VERSION continues into the session, and the guard's decisions are logged in production builds so update problems are diagnosable from player logs.
feat(golden): deterministic pixel-capture harness Add a capture harness that freezes the sources of frame-to-frame nondeterminism — time, per-process epochs, spring-bone simulation, remote-avatar variability, scene-runtime scheduling and the release queue for JS module work — so two runs of the same build produce pixel-comparable frames for regression testing. The per-process epoch comes from ProcessEpoch, which guards Process.StartTime: IL2CPP on Linux does not support process-handle duplication, and a throw there would poison the static initialization of every type that touches it. fix(golden): keep shader time and the MSAA target pinned while frozen URP re-publishes six time globals per camera (_Time, _SinTime, _CosTime, unity_DeltaTime, _TimeParameters, _LastTimeParameters) after the harness pins them, so a PinnedShaderTimeFeature re-issues all six from a render-graph pass at BeforeRenderingShadows, AfterRenderingShadows and BeforeRenderingOpaques while the world is frozen; unity_DeltaTime carries the lock-step 1/60 rather than the frozen frame's zero. The Metal backbuffer keeps its MSAA target pinned so the frozen frame is not resolved into black.
Add an in-world performance benchmark plus scripted A/B gates for shader changes. A benchmark app flag puts the session into a controlled mode that hard-disables remote-avatar spawning at the comms layer — transport-agnostic, with local propagation intact — so measurements are not polluted by whoever happens to be standing nearby.
Add SetGameObjectActive and SetRendererVisible tools to the MCP server and register them with the plugin, so automation can toggle scene objects and renderer visibility — needed by capture-and-compare workflows that must hide dynamic content before taking a frame.
…t coverage golden: pin the scene-light budget so captures do not follow the avatar LightSourceCullingSystem keeps only the scene lights nearest the character, caps how many cast shadows and picks each light's LOD by that distance, so a capture that pins only the camera sees a boot-dependent lit set (the Genesis Plaza marquee rim lit on one boot, dull on the next). While the harness is armed every active scene light now renders at LOD 0 with no per-scene or shadow-count cap; the pin re-asserts each frame because the quality runtime rewrites those fields whenever a preset applies. fix(golden): make the frozen camera pose and scene state canonical Root cause: frozen golden frames still differed between boots and platforms in scattered edge pixels, and the pose-pinned avatar stops differed frame-wide. With dt=0 the FreeLook composer and collider kept converging on previous-frame history, and the output pose's last bits passed through libm and rig arithmetic that need not agree across boots, so the rasterizer's 1/256 px vertex snap flipped edges. The projection pin covered only fov 60, leaving the drone camera's cotangent to per-platform libm rounding. Animation-rigging constraints re-applied dt-gated IK targets on top of the rewound pose, legacy Animation crossfades were captured mid-blend at boot-dependent weights, the avatar's skinning matrices baked in boot-dependent inputs at the freeze instant, and quantized scene clocks could re-issue CRDT output after the freeze-time normalizations ran. Fix: while frozen, invalidate the active vcam's previous state before the brain update and snap the written pose through GoldenPoseGrid (1/32 m, 2^-11 quaternion grid, renormalized in double); pin every non-physical perspective camera's projection from a double cotangent rounded once; deactivate rig layers and disable their rigs; sample exactly one legacy Animation state chosen by name; pin the avatar pose through GoldenPosePinHost; raise golden.sceneHold on the last lockstep frame and at the freeze. Scene runtimes stop dispatching ticks once golden.sceneHold or golden.frozen is raised, under DCL_GOLDEN_SCENE_CLOCK every ticking scene runs at the current scene's rate, and the number of quantized clocks still below the ceiling is published for the freeze report. The AppDomain slot contract is documented on GoldenFreeze. Full-precision camera pose, rig state and matrices are dumped at frozen frames so any residual can be located on either side of the snap. Verified on Linux: cam-third, cam-first, cam-drone and chat-sent are byte-equal across three boots each; emote-wave still differs per boot. test(golden): cover the pose grid, the light-budget pin and the Forward+ light loop GoldenPoseGridShould: snapping is idempotent, lands on the 1/32 m and 2^-11 grids with a normalized quaternion, and neighbours within half a step collapse to one value. SceneLightBudgetPinShould: after the pin a LightSourceSettings instance has unbounded light and shadow counts and LOD 0 at infinite distance. ForwardShadersShould: every project shader with a UniversalForward pass that lights through URP declares the _CLUSTER_LIGHT_LOOP multi_compile, with the legitimate exceptions listed in the test. The EditMode test assembly gains the reference these need. 32 tests pass headless on Linux (Unity 6000.5.9f1, -nographics).
The committed x86_64 uuav.dll, uuav_core.dll and uuav-helper.exe were the upstream 0.3.0 builds (native/src digest 597277eb…) while this branch's native tree is at 0.4.0 (digest c1419a58…: the IPC protocol gained GraphicsApiWire, TextureImportWire and TexturePlaneWire plus Init.graphics, connection.rs reads UUAV_TIMEOUT_SCALE), so verify-binaries.py failed the windows-x86_64 target on every source, manifest and config pin. Rebuilt with the uuav-native.yml recipe on a Windows host: rustc 1.97.1, x86_64-pc-windows-gnu, MSYS2 gcc 16.1.0 Rev5 linker, FFmpeg n8.1.2-34-g9b6c8969e0 (the pinned BtbN lgpl-shared asset; its four runtime DLLs are unchanged). Gate A (scripts/uuav/repro-gate.sh windows-x86_64) produced the three cargo artifacts byte-identical from two source trees. Relocked with --update --only windows-x86_64 --toolchain, so the lock pins the gcc that produced these bytes (16.1.0 Rev5 instead of 16.2.0 Rev3). macos-universal still carries the 0.3.0 binaries and needs the same rebuild on an Apple Silicon host.
ffmpeg-sys-next generates its FFmpeg bindings at build time through bindgen, which probes the system include directories by running the clang executable. With only LIBCLANG_PATH set, libclang parses mingw's malloc.h without its own resource headers and the build stops at mm_malloc.h. The GitHub Windows runner image has LLVM's bin on PATH, which is why uuav-native.yml never had to say so.
The Explorer seeks as soon as the facade reports media (MediaOpened covers
Opening) and the core rejects a seek until it holds a playback unit, so every
video open logged "[UUAV] [player N] uuav seek failed: no media open:
UUAV_OPENING" a handful of times and dropped the requested position. The compat
backend now keeps the latest target while the player is Opening and issues it
from Tick once the media settles; a later Seek replaces it and a media that goes
away before opening drops it. Stop routes its rewind through the same path.
Tests: two PlayMode cases drive the compat backend over the suite's tap-paced
player (the compat assembly exposes its internals to UUAV.Tests). The fixture
server gained a "hold/{token}/" prefix that delays a url's first response so a
test can act inside the Opening window. 33/33 on Windows (D3D11).
examples/netshutdown.rs defined with_probe only for Linux (the headless Vulkan probe) while main() called it unconditionally, so `cargo check -p uuav-client --all-targets` failed on macOS with E0425 and an unused-import warning; that is the "Checks for the FFmpeg-free crates" job of uuav-verify.yml, which builds every example. A non-Linux with_probe now compiles the example everywhere and refuses to run outside Linux. examples/ is outside the lock's client_source tree, so no binary needs a rebuild.
Upstream #9930 formats the init failure, the helper spawn failure and present failures with anyhow's alternate form so the underlying OS error is part of the message. The Linux media commit was written before that change and its squash onto a newer base silently kept the older one-line form at those three sites; this restores them.
libuuav.so is rebuilt with scripts/native-provenance/drv/uuav/default.nix from the native tree at 5fa6356ff3, where uuav-client keeps the cause chain in its error messages. libuuav_core.so and uuav-helper come out byte-identical to the committed blobs (uuav-core and uuav-server are unchanged), and `nix-build --check` rebuilds the same bytes, so all three grade `reproduced` in PROVENANCE.lock and the drv verdict. The 2026-09-06 uuav lines in shipped.sha256 now name the committed blobs. The FFmpeg sonames beside them are untouched.
…tree and relock macos-universal still shipped upstream's 0.3.0 blobs (rust source digest 597277eeb…, 21 files) while the branch's native tree is at 0.4.0 (the IPC wire carries GraphicsApiWire, TextureImportWire and TexturePlaneWire, the client keeps the cause chain in its error messages), so verify-binaries.py failed the target on every source pin. windows-x86_64 was built from the 0.4.0 tree before the error-message change and no longer matched the uuav-client digest. Both targets are rebuilt with the uuav-native.yml recipe from this tree: - macOS on an Apple Silicon host: rustc 1.97.1 for both slices, Apple clang 21.0.0 / Xcode 26.6 / macOS 26.5 SDK, FFmpeg n8.1 built from the pinned commit 9047fa1b by build-ffmpeg-macos.sh (all ten blobs are new). - Windows: rustc 1.97.1, x86_64-pc-windows-gnu, MSYS2 gcc 16.1.0 Rev5, the pinned BtbN FFmpeg asset. Only uuav.dll changes; uuav_core.dll and uuav-helper.exe come out byte-identical to the committed ones, matching the Linux rebuild where only libuuav.so moved. Gate A (scripts/uuav/repro-gate.sh) produced the cargo artifacts byte-identical from two source trees on both hosts. Relocked with --update --only <target> --toolchain, so the lock records the compilers that produced these bytes; the full verifier passes for both targets.
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.
This PR fully ports Decentraland to Linux; still work in progress:
Verification so far: we have a byte-identical plaza + gauntlet captures across Linux and Windows-under-Wine, plus some user testing and voice chat seems to be working.