Skip to content

feat(linux): allow more users to experience Decentraland [DO NOT MERGE] - #10010

Draft
eordano wants to merge 27 commits into
devfrom
dev-linux
Draft

eordano wants to merge 27 commits into
devfrom
dev-linux

Conversation

@eordano

@eordano eordano commented Sep 6, 2026

Copy link
Copy Markdown
Member

This PR fully ports Decentraland to Linux; still work in progress:

  • batchmode build lane
  • Vulkan+GLCore player settings
  • native plugin binaries
  • minimum-spec profiles
  • platform guards
  • texture pipeline memory safety
  • GPU instancing fixes under Vulkan
  • windows-suffix bundles + embedded abgen CDN-miss fallback
  • UUAV media backend with runtime backend selection
  • MCP scene automation tools used to test pixel-perfect porting to Vulkan and a pixel-determinism capture harness
  • plaza performance harness
  • clean-room replacement of proprietary UPM git deps

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.

@github-actions

github-actions Bot commented Sep 6, 2026

Copy link
Copy Markdown
Contributor

🚦 CI Status

Build

Build skipped — no changes detected under Explorer/.

Lint

No C# files changed — lint ratchet skipped.

Tests

Waiting for tests to start…

Performance

Bare-metal benchmarks run automatically after each successful build; results land in this section. Add the perf_test label to run the in-repo Unity performance suite instead (skips normal CI and blocks merge while set).

Automation

On demand — comment /visual-tests on this PR to run the visual regression suite against its build.

@eordano eordano closed this Sep 7, 2026
@eordano
eordano deleted the dev-linux branch September 7, 2026 09:36
@eordano
eordano restored the dev-linux branch September 7, 2026 23:21
@eordano eordano reopened this Sep 7, 2026
@eordano
eordano force-pushed the dev-linux branch 2 times, most recently from cea98d6 to dbe9fcd Compare September 13, 2026 19:54
@github-actions

github-actions Bot commented Sep 13, 2026

Copy link
Copy Markdown
Contributor

badge

Avatar Preview Renderer — Vercel Preview is ready!

Field Value
Preview https://unity-explorer-9hw8lexpt-decentraland1.vercel.app
Commit 8785fed418ba17e8e8282fa085dea2e35cb34c1e
Logs https://github.com/decentraland/unity-explorer/actions/runs/34787804452

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.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant