- Remove the MOCKTAIL_VR_TEST_SESSION_LOSS_AFTER fault injection; nothing
used it.
- The scripted head pose and the eye evidence dumper only work in builds
configured with -DMOCKTAIL_VR_DIAGNOSTICS=ON (default OFF); release
builds ignore their environment variables.
- Remove FindFmodInputCaptureProfile and HasExpectedFmodInputCaptureVtable
(only their own tests called them) and the never-defined
PrepareCaptureLocked declaration; the audio bridge files match upstream
again.
- docs/VR.md lists every MOCKTAIL_VR_* variable with its default, linked
from the README.
- Once the guest eye format is known, the eye swapchains are recreated
once in the format that keeps its bytes: the eye format, else its
UNORM/SRGB sibling (raw copy), else the other channel order with the same
sRGB-ness (a blit that only swizzles). Mixed order-and-gamma cases no
longer go through a gamma-converting blit; if only that remains, it is
logged.
- The desktop mirror segment is created with O_EXCL after removing our own
stale segment (a crashed run's leftover), and must be private to this
user before eye images are written to it.
- A new frame_mutex_ is held for the whole XR frame cycle and by every path
that can destroy the session (taken before mutex_). That lets the cycle
release mutex_ while it blocks in xrWaitFrame, so guest render-thread
hooks no longer wait out the runtime's pacing. Swapchain-image and fence
waits stay under mutex_; they return promptly unless the GPU or
compositor stalls, and they run while guest eye bindings are copied.
- Every frame error, including the swapchain-image and copy-fence
timeouts, now tears the session down recoverably instead of ending XR
output for the rest of the run.
- Session recovery backs off (1, 2, 4, 8 s) and recreates the XrInstance
after an instance-loss event, an instance/runtime failure, or three
failed attempts, so VR returns after SteamVR or WiVRn restarts instead
of retrying forever.
- While no XR session runs (headset idle, session not READY, recovering)
presents are paced at 60 Hz outside the backend lock instead of running
unthrottled.
- A PrepareHostPresent failure that consumed nothing (copy resources
unavailable, failed fence reset or submit) presents normally instead of
failing the guest's vkQueuePresentKHR; only device loss is propagated.
- A runtime/guest GPU mismatch falls back to the plain host device and
keeps the game running without headset output, naming both GPUs.
- A failed EGL attach on the GLES path no longer makes window init fail;
the game runs without headset output and the bind is not retried.
An explicit --vr on a Roblox build without VR support now exits with the
build's version and Build ID, the builds VR supports, and how to select one
(--roblox-lib). When VR is only enabled in the config, the launch continues
without VR and logs the same details. The bridge's own Install error names
both as well. The verified build contracts carry their version labels and
move to namespace scope so they can be listed.
When Roblox asks for a keyboard (showKeyboard), a world-locked panel
opens below eye level as an OpenXR quad layer. Controllers point at keys
and the trigger types; text and key presses go through the same platform
events as the desktop keyboard, so the focused TextBox edits identically.
The layout is US QWERTY without the function row or Ctrl/Alt, with one-shot
Shift, Caps Lock, Tab, Backspace auto-repeat, Enter and Close. A trigger
aimed at the panel is withheld from Roblox so a key press is not also a
click. A text field above the keys mirrors the focused TextBox, whose
desktop overlay never reaches the eye images. MOCKTAIL_VR_KEYBOARD=0 turns
it off.
vr.frame_rate_limit / MOCKTAIL_VR_FRAME_RATE_LIMIT replaces
graphics.frame_rate_limit while VR is active and takes the same values.
It defaults to unlimited so xrWaitFrame paces Roblox at the headset
refresh rate instead of Roblox's own 60 FPS cap.
MOCKTAIL_VR_RENDER_SCALE scales the runtime's recommended eye extent.
SteamVR's recommendation for a Quest 2 at 90 Hz (2244x2352 per eye) missed
the 11.1 ms budget and halved the rate to 45 FPS.
MOCKTAIL_VR_UNPACED=1 stops guest frames from blocking in xrWaitFrame:
the open XR frame spans several guest frames and is submitted once per
display period, for measuring maximum render rate.
SteamVR (vrclient.so) binds its Vulkan renderer to the lifetime of the
XrInstance. When Roblox destroys the LuaApp Vulkan device and creates a
second VkInstance/VkDevice for UGC experiences, reusing the original
XrInstance causes SteamVR to dereference freed device handles in xrEndFrame.
Tear down and recreate the XrInstance whenever a new guest Vulkan instance
is initialized, resetting session state, spaces, and controller actions.
A driver can reject a present mode the device did not enable. Retry once with the mode the game asked for, except after errors another mode cannot fix.
* vr: capture the microphone for voice chat on Build 2998
On the vr branch Roblox 2.736.1408 joins voice chat and plays other
players, but never opens a microphone: its FmodAudioDevice capture
methods have no working host path, and main's native capture (537828a
"Fix vc") is pinned to 2.738 RVAs.
Port main's NativeInputCapture and interpose the FmodAudioDevice capture
slots for Build 2998. 2.738 inserted two device-list slots (7 and 11),
so the capture methods sit at {4, 11..15} here instead of {4, 13..17}.
The input-device slots {8, 9, 10, 16}, checked against main's input RVAs
for this image, anchor that layout before anything is patched, and each
capture slot must be a distinct method next to the input-device code.
The voice sink is called through its own vtable after checking that the
vtable and its PCM entry point lie in libroblox, and the retained
shared_ptr is released through the libc++ control-block ABI, so no
further Build 2998 RVAs are needed. If any check fails, capture stays
off and the output routes still work.
MOCKTAIL_NATIVE_INPUT_CAPTURE=0 turns the capture interposition off, and
audio.input_device: disabled reports no microphone to Roblox.
* Requested fix at PR 181
Brings in aarch64 host support (#152, #162, #167), natural scrolling (#163)
and the arm64 CI build job. Packaging, Pages and README keep the Nightcap
x86_64 side; upstream's aarch64 bundles are not published.
Each ETC2 upload created and destroyed its own staging buffer on the render
thread. Uploads now take ranges of 16 MiB blocks, reused once every range
is released; four empty blocks are kept. Profile traces gain per-frame CPU
time counters and slices for staging and driver memory calls.
The router inferred relative mode from SDL reporting zero absolute
coordinates. X11 does; Wayland reports a position that accumulates the
deltas and leaves the window, so every camera-drag event took the
absolute branch and walked Roblox's cursor to the viewport edge.
Motion, button and wheel events now carry the window's relative mouse
mode, and the RMB camera-drag fallback only captures inside an
experience so the app surface keeps a free cursor.
Uploads decode on a background dispatcher and the submit carries a
timeline semaphore wait, so the GPU waits for the texture rather than
the render thread. The gather of the application's compressed bytes
moves there too when they are already mapped; unmapping or freeing that
memory drains the decodes still reading it.
Staged copies gain an explicit host-write barrier. A queue submission
only makes host writes from before it visible to the device, and these
writes land after the submit.
Devices without timelineSemaphore, and submits whose pNext already sizes
arrays by their semaphore counts, decode inline as before.
Blade Ball, median of three: worst vkQueueSubmit 140ms -> 1ms, p99
4.9ms -> 0.05ms, total 2534ms -> 309ms. Frame interval unchanged; its
tail is the engine's own main-thread step.
EmitPass reuses the decoder's persistent workers through RunOnDecodeWorkers
instead of creating and joining threads per batch. A failed trace write stops
the writer thread rather than leaving it ticking every 250 ms.
Records presents, GPU waits, submits, ETC2 decode, and pipeline and shader creation for ui.perfetto.dev. Adds a benchmark guide and a trace summary script.
Rename the desktop entry, metainfo, icons, Flatpak manifest, and SDL app
identity so Nightcap installs side by side with upstream. Build a signed
Flatpak on v* tags, publish the OSTree repo and landing page to GitHub
Pages, and attach the bundle to the release. Release creation tolerates
the AppImage and Flatpak workflows racing on the same tag.
Unset or non-numeric MOCKTAIL_SMALL_TEXTURE_UPSCALE now means 4, so the
upscale applies on every launch path. Drop the variable from the desktop
entry Exec line.
Show Nightcap as the app name in the desktop entry, window title,
dialogs, and metainfo. Binary, config paths, and desktop ID keep the
mocktail name. Publish Nightcap-x86_64.AppImage to GitHub Releases on
v* tags. README points at the fork's releases instead of upstream
packages.
Resolve src/update/host_abi_deriver.cc: keep upstream's FMOD vtable layout
versions and input-method derivation inside the optional output-device
bridge, so a mismatch omits the bridge instead of failing derivation.
Level-0 uploads are hashed against the override store; MOCKTAIL_SMALL_TEXTURE_UPSCALE
creates colour images up to 64px N times larger, snaps block-rounded regions to the
host mip, fills every host mip from level 0 and turns image copies touching scaled
images into blits. MOCKTAIL_ADVERTISE_ETC2=0 hides the feature bit.
Compressed bytes are copied to heap scratch, decoded heap to heap across
up to 8 threads in block-row bands, and streamed into the staging buffer.
Decoding straight from uncached Vulkan memory caused 200-460 ms hitches.
The FPS trace now reports the longest frame gap per window.
Hosts without native ETC2 now report ETC2/EAC support. Uploads into emulated images are decoded to RGBA8/R16 staging buffers at submit. MOCKTAIL_DISABLE_ETC2_EMULATION=1 turns it off.
* refactor(crypto): deprecate custom sha256 in favor of OpenSSL libcrypto
- Remove standalone src/foundation/sha256.cc, include/mocktail/sha256.h, and tests/sha256_test.cc
- Remove mocktail_crypto_foundation library and sha256_test targets from CMake
- Export clean FileDigest and HashText helpers via include/update/payload_integrity.h
- Deduplicate byte-to-hex formatting across host ABI loader and updater modules
- Support direct generation hash sizing to avoid repetitive string slicing
- Validate all EVP return codes across streaming downloads and asset tree hashing
* fix(compat): abort candidate canary registration when profile hashing fails
- Return Failure before profile validation and registration if ComputeSha256Hex fails
- Propagate canary attestation hashing failures in ValidateCanaryAttestation
- Add SetHostAbiProfileHashingFaultForTesting hook and regression test in host_abi_profile_test
---------
Co-authored-by: glook9001 <310372091+glook9001@users.noreply.github.com>