Port the fullscreen policy omitted when adapting PR #12. Track requested fullscreen state across frame and style changes, exclude unmanaged windows, and use EWMH REMOVE when leaving fullscreen.
Extend the native geometry probe and document passive tracing for the reported vertical click offset, whose cause remains unconfirmed.
CGDisplayScreenSize returned 0x0 for any display with EDID (upstream left
that branch unimplemented). League divides by it for its camera aspect
ratio, so the view-projection x row became NaN: every 3D vertex was
discarded, the scene showed only its blue clear colour, and world clicks
were ignored. Read the size from the EDID detailed timing (mm) or basic
parameters (cm), fall back to 96 dpi, and use the main display for an
unknown id.
CGDisplayCopyAllDisplayModes returned NULL for display 0 (a window off
every screen), which League passes straight to CFArrayGetCount. Fall back
to the main display, then to an empty array.
Add riot-display-modes-check covering both for the main display,
display 0 and an unknown id.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Adapt the geometry fix from PR #12 to the current relative-mouse implementation. Keep display IDs independent of primary-first screen ordering, translate window and pointer coordinates consistently, and invalidate geometry on both RandR event types.
Handle failed RandR snapshots and free EDID data. Add isolated native geometry regression coverage and use the configured SDK when linking the X11 probe.
Validated all 30 component patches, the X11 and CoreGraphics builds, six native geometry groups, and the existing relative-mouse checks. The user confirmed the installed fix works on 2026-10-16.
Co-authored-by: Maka <[email protected]>
CGAssociateMouseAndMouseCursorPosition(false) used to warp the pointer to the
window center, freeze the reported position there and treat the game's own
CGWarpMouseCursorPosition calls as motion. On Xwayland, which ignores warps
while the cursor is visible, this left League's cursor stuck in the middle.
The backend now keeps a frozen virtual cursor: physical motion only produces
deltas (deltaY positive downwards), application warps move the virtual cursor
and their echoes are dropped, mouseLocation returns it, and the pointer is only
warped back where the server honours warps. The grab no longer uses the
invalid FocusChangeMask and is released and retaken across focus changes.
setCursor: no longer re-sends XDefineCursor/XFlush when the window already has
that cursor; League set it about 190 times per second.
Also carries the opt-in input trace (/tmp/rclol-input-trace in the guest).
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Traverse the layer tree before compositing so nested Metal layers consume queued drawables and recycle their pool. Add a nested presentation probe covering pool reuse and Vulkan-to-OpenGL pixel readback.
NVIDIA's GL rejects GL_LINEAR_TILING_EXT with GL_INVALID_VALUE, so the
linear internal image could not be imported and the window stayed black.
Pick the tiling by GL_VENDOR; other drivers keep linear.
Co-Authored-By: Claude Sonnet 5.5 <[email protected]>
NSApplication's attention timer calls -[CGWindow flashWindow], which X11Window
did not override; the base class raised an uncaught abstract-invocation
exception and LeagueClientUx aborted (and restarted every ~50s). Mark the
window as demanding attention through _NET_WM_STATE instead.
Co-Authored-By: Claude Sonnet 5.5 <[email protected]>
League sets framebufferOnly=NO on its CAMetalLayer, and QuartzCore read
that as needing host-visible coherent memory, so the game's final
render target and the linear image handed to OpenGL both lived in
system RAM (type 2 on the RX 6600) and crossed PCIe every frame.
framebufferOnly=NO only lets Metal sample or blit the texture: nothing
maps either image (replaceRegion aborts, getBytes is a stub, Indium
maps only Shared/Managed buffers).
Drawables now take the first allowed DEVICE_LOCAL type whatever
framebufferOnly says; the guest file /tmp/metal-no-drawable-vram
restores the old placement. metal-device.log shows drawable-vram=on and
storage=vram.
A native EGL/RADV test exported the drawable's linear image from VRAM
and imported it into GL with no errors and identical pixels. In a
practice-tool baron fight on the RX 6600 with the same champion it ran
about 20 FPS faster and the same when idle; not yet measured in an ARAM.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
find_package(LLVM CONFIG) also searches <prefix>/llvm/lib/cmake/llvm for
every PATH entry, before the system prefixes. With /opt/rocm/bin on PATH
it found ROCm's LLVM 22.0.0git, whose llvm-config reports no shared
library, and 4e88b79's fallback to the llvm-config on PATH then rejected
the system LLVM 23 because its major differs from the ROCm headers, so a
fresh configure failed (issue #1).
Before find_package, keep LLVM_DIR only if the llvm-config next to it
reports a shared library; otherwise, or when LLVM_DIR is unset, use the
llvm-config on PATH that does and take its --cmakedir. A working pinned
LLVM_DIR is left alone.
Checked with a fake ROCm prefix on PATH: plain find_package picks it;
the new code picks /usr/lib/cmake/llvm (LLVM 23) on a fresh configure,
replaces a cached ROCm LLVM_DIR, and keeps a working cached
/usr/lib64/cmake/llvm. A standalone configure of Indium with the fake
ROCm on PATH completes with the system LLVM.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Indium and QuartzCore took the first memory type a resource allowed. The
Vulkan spec orders a type with no flags before every DEVICE_LOCAL type,
and NVIDIA exposes such system-RAM types, so on NVIDIA private buffers,
textures and framebufferOnly drawables could land in system RAM. They
now take the first allowed DEVICE_LOCAL type and fall back to the old
choice; shared and managed resources are unchanged.
The drawable's exported image is a dedicated Vulkan allocation, but the
OpenGL import never set GL_DEDICATED_MEMORY_OBJECT_EXT, which
EXT_external_objects requires. Mesa ignores it; it is the suspected
cause of the black screen on NVIDIA in issue #3. The import's GL errors
were never checked (reportGLErrors is compiled out), so a failure was
silent.
/tmp/metal-device.log now lists the selected device's memory heaps and
types, the memory type chosen per resource kind (with the old choice and
changed=yes/no), and the result of the first drawable and semaphore
imports into OpenGL (later ones only on failure).
Practice match on the RX 6600: all choices changed=no, import
dedicated=yes with no GL errors, semaphore ok, match=yes. Not run on
NVIDIA.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
A leftover LLVM_DIR can point at a removed LLVM whose llvm-config exists
but whose library is gone, which made configure fail on LLVM 23 hosts.
Try the tools-dir llvm-config, then the one on PATH, and accept only one
that matches the header major and reports a shared library.
Co-Authored-By: Claude Sonnet 5.5 <[email protected]>
On a hybrid laptop (issue #3) Indium took whichever Vulkan device the
loader listed first, usually the integrated GPU, and still created a
VkDevice on every other supported GPU, waking a discrete GPU it never
used. Drawables reach the presenting OpenGL context as opaque FDs, which
only work on one physical device, and nothing checked that.
Indium now appends the device list, the selected device and the reason
to guest /tmp/metal-device.log, and creates a VkDevice for the selected
device only (/tmp/metal-all-devices restores the old behaviour).
/tmp/metal-gpu or METAL_INDIUM_GPU selects a device by index or name
for diagnosis. QuartzCore logs the presenting GL renderer and compares
its device UUID with the Vulkan device's (match=yes / MISMATCH); it
queries Vulkan itself, so it needs no new libindium export.
On the RX 6600 desktop the game selected RADV, created 1 VkDevice of 2
supported (lavapipe no longer gets one) and logged match=yes in a match.
Running on NVIDIA through prime-run is documented but not validated.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
LLVM 23 represents vector splats such as `<3 x float> zeroinitializer`
as vector-typed ConstantFP/ConstantInt values instead of
ConstantAggregateZero/ConstantDataVector. Iridium only handled scalar
ConstantFP/ConstantInt, so on hosts with LLVM 23 nearly every League
shader failed to translate: the r3dlog fills with "Failed to compile
shader", the loading screen hits "Material Missing Pipeline" and the
game crashes. Our matches had only run the overlay pinned to LLVM 22.
Declare the scalar element and splat it into a constant composite.
Offline, on the 74,112 metallibs in ShaderCache.metal.wad.client,
LLVM 23 went from ~2% to 74,108 translated, the same set as LLVM 22
(the 4 remaining failures are pre-existing on both); a sample of the
LLVM 23 SPIR-V passes spirv-val for Vulkan 1.3. Confirmed in a match
with the Iridium overlay built against the system LLVM 23.
RIOT_IRIDIUM_SYSTEM_LLVM=1 makes build-riot-indium-runtime.py ignore
the LLVM 22 pin, to build what a fresh checkout gets.
Refs #1
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Darling's libvDSP was almost entirely void stubs. Chromium's Blink
WebAudio on macOS uses vDSP_vsmul for any GainNode gain other than 0 or
1, so with the League client's master volume below 100% the master gain
output buffer was never written: silence when starting below 100%, and
the last block repeating as a buzz when lowering the volume. vDSP_vadd
was also a stub, so simultaneous sounds were not summed and a new sound
replaced the previous one.
Implement every vDSP function the Chromium Embedded Framework imports:
vadd, vsub, vmul, vsmul, vsadd, vsma, svesq, vclip, conv, ctoz, ztoc,
zvmul, deq22, deq22D, create_fftsetup, destroy_fftsetup and fft_zrip,
with Apple's argument order and scaling (forward real FFT is 2x the
DFT, inverse unscaled). The FFT is in place and keeps only read-only
twiddles in the setup, which callers share across threads. Build vDSP
at -O2 because it runs on the realtime render thread, and drop the
uncompiled basic.c.
The runtime installer now builds libvDSP and installs it at its
absolute path in the stopped prefix, keeping a .before-vdsp backup.
Verified against naive references for sizes up to 2^15 on the host;
confirmed manually that client sound is fixed at any volume.
Fixes#4
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Fix X11 presentation handoff after resizing, preserve NSView size callbacks, and correct content and backing-layer geometry. Add CoreText font enumeration for the language dropdown and rebuild/install the matching runtime overlays.
Correct CarbonCore Gestalt version queries and roll back proc_listpids for Vanguard testing. Document that VAN -101 can still recur. Include accumulated CoreAudio stream and AudioUnit corrections.
Add standalone font, version, resize and publisher-handoff regressions. Record manual confirmation of the language dropdown and window resizing; all 29 pinned patches and the source/script audit validate.
proc_info callnum 1 (proc_listpids) previously fell through to the unsupported-callnum path and returned ENOTSUP, so process enumeration (e.g. the Riot client's Vanguard kext probe) logged 'sys_proc_info(): Unsupported callnum: 1'.
Walk /proc like _sysctl_proc and _proc_pidinfo_listthreads already do and mirror XNU's filter semantics for PROC_ALL_PIDS, PROC_PGRP_ONLY, PROC_PPID_ONLY, PROC_TTY_ONLY, PROC_UID_ONLY and PROC_RUID_ONLY, with XNU's size-query, ENOMEM and truncation contracts.
setHiddenUntilMouseMoves: only hid or showed the cursor, so the flag set
by setHiddenUntilMouseMoves:YES was never cleared when the user moved the
pointer. Track and clear it in NSDisplay instead of NSCursor: add
setCursorHiddenUntilMouseMoves: and pointerMoved to the display, let the
X11 backend report genuine motion (after its synthetic warp filter), and
have NSCursor forward to the display. The idempotency guard keeps the
hide/show state balanced and preserves an explicit [NSCursor hide].
Electron 22.3.27's NativeWindowMac::IsVisible() compares the window's
occlusion state for exact equality with NSWindowOcclusionStateVisible:
bool occluded = [window occlusionState] == NSWindowOcclusionStateVisible;
Apple's contract is a bitmask: an on-screen window's state *includes* that bit
(tested with &). The exact == test is inverted, so an Apple-correct value made
every ordered-in window look occluded and the client's visible/not-minimized
checks failed. The previous workaround byte-patched Riot's shipped Electron
Framework to flip that comparison.
Fix it in Darling's AppKit instead: -[NSWindow occlusionState] now also reports
the reserved 1 << 2 bit for an on-screen window. &-based readers (Electron's own
windowDidChangeOcclusionState:, Chromium) still see NSWindowOcclusionStateVisible
set, while the exact == test is false, so unpatched Electron resolves
IsVisible() correctly. No Riot binary is modified.
Delete tools/patch-riot-electron-visibility.py and its call in
install-riot-gpu-runtime.sh; sync README and docs/STATUS, which already
described the binary patch as excluded.
iokitd passed an Xlib Display to eglGetDisplay(), so Mesa chose the
platform from EGL_PLATFORM. Wayland sessions often set it to "wayland",
and the first darling command passes its environment to every guest
service; Mesa then treated the Display as a wl_display, iokitd crashed
in pthread_mutex_lock and launchd restarted it every 10 seconds. Use
eglGetPlatformDisplay(EGL_PLATFORM_X11_KHR, ...) as OpenGL already does.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
LaunchServices now hands http, https and mailto URLs to the host desktop
itself instead of NSWorkspace writing them to a spool that nothing in
the normal workflow drains. It loads the host libsystemd through the ELF
loader and calls xdg-desktop-portal OpenURI on the session bus, falling
back to xdg-open in a transient systemd user unit, so the browser starts
outside Darling's namespaces. LSGetApplicationForURL reports the host
opener for these schemes because Electron checks for a handler before
opening a link. NSWorkspace goes through LSOpenCFURLRef.
Tested with a host-side D-Bus regression (79 checks) and in a fresh
guest prefix through open(1) and NSWorkspace, both reaching the portal
with the exact URL.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Two matches with the Indium hand-off round (narrow present wait, deferred
blits, worker-side command-buffer lease, skipping CPU Vulkan devices) ran
slower than before, with more main-thread futex waiting. Each change is now
behind a guest flag file and off by default, so the default libindium matches
the round-3 build (vertex-descriptor dirty masks, cheaper resource setters).
The wait counters stay opt-in, write a "waits enabled" line when they are on
and keep their state per image. The docs explain that flag files created from
the host while Darling runs may stay invisible through the overlay mount.
Also adds a passive cursor watcher. The stuck in-game cursor is Hyprland's
direct scanout, not Darling. The first match with direct scanout off averaged
135 FPS. STATUS, TROUBLESHOOTING and README are updated with the new matches.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Game process (ARAM Hexgate: 84 -> 127 FPS average, slowest stretches ~110):
- Local pthread mutexes, condition variables and Mach semaphores are on by
default in the game process only, with /tmp/rclol-no-local-* opt-outs.
The main thread's darlingserver waits fell from 1.36 to 0.10 ms per frame.
- Local semaphores: the count and the slot generation change in one
compare-exchange, a destroy leaves a negative marker, the table is reset
after fork, and names the table did not create go to the server.
- Metal: drop identical re-binds (keyed on the Indium object, depth-stencil
retained), pass blit resources by reference, and stop resetting vertex
descriptors twice.
- Core isolation of the main thread is opt-in (/tmp/rclol-core-isolation);
it lowered FPS in its only match.
- Optimized libc/libc++/Foundation overlays, AVX2 string primitives, real CPU
topology and their installers and offline checks.
Tools and docs:
- tools/riot-match-capture: passive /proc sampler, user-space IP profiler and
reports, plus a comparison against the game's netstats.
- tools/riot-lsem-model and tools/riot-metal-bind-model: host models that
extract the shipped code and check it.
- tools/install-riot-metal-pair.sh installs Metal and Indium together.
- Status, setup, testing and troubleshooting docs updated with the measured
matches and the switch table; manifest file counts match the patches.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Preserve floating-point arguments in GL resolvers, retain presentation across differently sized windows, export AppKit click and URL fixes, and guard nullable LaunchServices errors. Include compiler/build and installer corrections for the new PC.
Reuse server receive buffers with descriptor-safe exception cleanup and avoid disabled debug-string allocation. Translate Darwin free/reuse memory hints, add atomic server/library installers with separate user-owned memory backups, and preserve active processes until a manual namespace restart.
Remove automatic inspectors, client helpers and forced unlimited UI frames from normal launches. Retain historical hooks with offline mocks and document their exclusion, unresolved installer handoff, activation requirements and unmeasured FPS/RAM benefits.
Validation: all 29 pinned patches apply; all 461 exported source files match; host receive and memory checks, launcher and JavaScript mocks, syntax and documentation-link checks pass. No live client/game was controlled or restarted.