/ opens chat; a consent prompt that
asked again every time Settings was opened; and a FastFlags page with colour and
live errors.
Plugins had no interpreter, anywhere
deno is in Arch’s extra, and in neither Fedora (dnf5 list deno on 44
returns nothing) nor Debian (its own source index answers "exact": null). It
went unnoticed here because this development machine has Deno from Homebrew.
The Flatpak decides the shape of the fix. Inside one, PATH is the
runtime’s, and Cordial deliberately takes no route to the host —
flatpak-spawn --host needs a D-Bus name
ADR-018 refuses, because it is the same
name that runs an arbitrary command on the host. A Flatpak user therefore cannot install an
interpreter by any means available to them. Cordial has to provide one or
plugins are permanently dead there.
So Settings, Plugins, grows a row only when there is no interpreter, with a
Download button. It fetches a pinned Deno — version and SHA-256 both constants
in the source, so nothing asks the network what the current release is — checks
the hash over the stream before the file gets the name anything looks for, and
extracts atomically, because the check that decides whether to download is “is
there a file at this path” and a torn extract would be mistaken for an
interpreter for ever.
This is not a new class of trust: Cordial already downloads a 118 MB
libroblox.so and loads it as native code in-process.
Arch never fetches. Its package now depends on deno properly, and PATH is
searched first. Both .SRCINFO files were stale and did not list it —
that is the file the AUR actually reads for dependencies, so an AUR install
would not have pulled Deno in even after the PKGBUILD was fixed. Corrected
here, along with the missing bubblewrap optional dependency.
The end-to-end download was run against Deno’s real release and the extracted
binary reported the pinned version. What is not verified is the Settings row
itself — this machine could not open a second Settings window while a client
held the single-instance lock.
The AppImage could not open a web view
A user’s AppImage answered a web view with:libwebkitgtk-6.0.so.4 reaches five
things through absolute paths fixed at its build time, and only Fedora and its
derivatives put the helpers in /usr/libexec — Debian uses
/usr/lib/x86_64-linux-gnu/webkitgtk-6.0, Arch /usr/lib/webkitgtk-6.0. So
installing the host package leaves the baked-in path just as empty.
WEBKIT_EXEC_PATH was already exported by AppRun and does nothing: it
appears zero times in the library, against thirty-odd other WEBKIT_* names
that do appear. A knob that looks like the fix and is inert is worse than no
knob.
The paths are therefore made to exist rather than redirected. AppRun
re-execs under bwrap with an overlay over /usr/libexec — an overlay rather
than a tmpfs so the host’s other contents survive — and binds the bundled
helpers in at the name the library asks for. It probes first and falls through
unwrapped, saying so, on a kernel or policy that refuses unprivileged
overlayfs; CORDIAL_APPIMAGE_NO_WRAP=1 turns it off entirely.
Verified with a control on a host standing in for one that never installed
WebKitGTK: with the wrap, both helper processes ran from inside the image and a
page reached load-changed FINISHED; same image, same host,
CORDIAL_APPIMAGE_NO_WRAP=1, the reported error verbatim. What is not
verified: the tested image’s binaries came from an installed rpm rather than a
fresh build, the probe was a bare WebKitWebView rather than Cordial’s own
sign-in view, and nothing but Fedora was tried.
The / flash, and the consent prompt that would not stay answered
Two user reports, one cause each.
The GTK background flashed when / opened chat, but only while a movement key
was held. The restacking code already had the right idea — make the background
transparent, repaint, then lower the subsurface — and relied on a repaint_now
that was queue_draw(); pump();. The pump runs at most 32 non-blocking
iterations, deliberately, because the engine’s pump must return. Idle, 32
iterations reach a frame easily. Holding a movement key fills the context with
key repeats, the 32 are spent on events, and the frame clock never ticks inside
them — so the restack went out under a parent that was still opaque. That is
exactly why it needed a key held to reproduce, which is the part of the report
that identified the mechanism. It now waits for after-paint on the frame
clock, bounded by 40 ms, because the thing being waited for is a frame and
frames are a duration.
The consent prompt returned every time Settings was opened, even after the
plugins were enabled. It was recorded in the response handler, which fires only
if that dialog is answered — and every built-in plugin row presents its own
prompt as the page renders, so several stack at once. Answer the top one, close
Settings, and the rest are torn down with their parent without a response;
nothing is written and all of them ask again. “Has this profile been asked” is
now true the moment the question is on screen.
ADR-003’s default deny is untouched:
grants are still only written by the allow branch, so a dismissed prompt grants
nothing and merely stops nagging.
Neither is verified by observation. The flash is GTK’s own buffer showing
through, which cordial_screenshot cannot see — it reads the engine’s swapchain
— so confirming it needs a compositor capture at frame rate. The mechanisms are
read from the code and the reports; the fixes follow from them. Worth a look
before either is called done.
FastFlags: colour, and errors as you type
A comparison with Pigment, another Linux Roblox launcher, made two gaps obvious. Its editor is syntax-coloured; ours was a wall of monospace where every flag name looked like every value. GtkSourceView would have added a runtime dependency to five packaging files to colour one text box, so the document is scanned here instead and painted with tags — two palettes, repainted when the system theme flips, because a text tag holds a literal colour and does not follow a theme the way a CSS class would. Applying was previously the only way to find out whether a paste parsed, so the feedback for a misplaced comma was a toast after a save that did not happen. The status line now names the error as you type and Apply goes insensitive while the document does not parse. Nothing was ever written in that state anyway; the only change is whether pressing the button is how you find out. The actions moved, for the third time, into a row beneath the editor with the file path. The first two arrangements were each half right, and the lesson from the second was the wrong one: the problem was that nothing was grouped, not where the group sat. The scanner is tested; the colours are not. No nested compositor was available to photograph the rendering.Discord presence
A game’s cover art, not Cordial’s icon. A game that setlargeImage
through BloxstrapRPC got Cordial’s logo, because the picture was thrown away on
the way to the payload by a comment that contradicted the one a screen above it.
And a presence for games that send no RPC at all, field for field the shape
Bloxstrap uses, read from its source (MIT) rather than guessed: the experience
name, by <creator>, the game’s icon as the large image, the Roblox badge as
the small one, and the join time as the start. Every default goes in only where
the game said nothing, so a game that speaks still wins.
One correction, and it is the important one. The previous attempt at this
shipped a URL that returns 404, and it was reported here as working because
Discord accepted it — Discord rewrites any URL to its proxy form without
fetching it, so acceptance measures nothing. The result on a real profile was a
broken-image placeholder where Cordial’s own icon used to be, which is worse
than the bug it replaced. The check that settles it is curl against the URL,
and it takes one command. The working endpoint is thumbnails.roblox.com, and
Cordial now resolves pictures through it, cached, off the log-watching thread.
A plugin never handles a URL. Cordial issues an opaque key per picture and
the payload echoes one back; an unresolved key renders as no image. That was the
alternative to host-checking a URL and it is the stronger one: checking hosts
still lets a URL cross the boundary and then argues about which ones are
acceptable, whereas a key cannot be a link at all.
The small image will not appear yet. Bloxstrap’s small image key is the
literal string roblox, a Discord application asset rather than a URL, which
renders only once an image of that name is uploaded to the application’s Rich
Presence assets. Only the application’s owner can do that. Until then Discord
draws no badge and nothing else changes.
Games are now told this launcher speaks BloxstrapRPC.
FFlagUserLaunchedWithBloxstrap is how an experience finds out whether the
launcher under it implements the protocol, and a game that gates on it says
nothing at all. Bloxstrap’s own RPC test place reports the answer on a board,
and against Cordial the board said NO. It is not an engine flag — the name
appears nowhere in libroblox.so; the launcher injects it and the game asks
about it. A user’s own FastFlags still overrule it.
And the log tells the truth now. The broker wrote the frame, read Discord’s
reply and threw it away. Discord refuses an activity it will not store with an
equally well-formed frame carrying evt: "ERROR", so a refused presence was
returned to the plugin as ok. Every “presence.set came back: ok” in a log
from 0.13.0 or earlier means “Discord answered”, not “Discord accepted”, and
anything concluded from those lines needs re-reading with that in mind.
Launch stopped scanning 118 MB of engine
Profiled withperf before porting anything from another project, and the
largest named cost in Cordial’s own code was ours alone: reading the engine’s
version, so the client can tell the server which build it is, scanned the whole
of libroblox.so byte by byte at every launch. It is now cached against the
file’s length and mtime, so a launch reads a stamp instead.
The TaskScheduler crash: three more reports, and a diagnosis
Two more users hitCan't initialize the TaskScheduler before flags have been loaded — Linux Mint and Arch, on top of CachyOS twice. That is three
distributions across both the AppImage and the Flatpak, so the distribution is
not the variable and neither is the packaging format.
The Mint log is the first with the whole startup in it, and it names something
two sessions of inference could not: on that machine the engine build does not
export nativeInitClientSettings under the name Cordial looks for, so the
flags were never delivered at all and the engine crashed on exactly what it
said. The two-physical-core theory that looked so promising was tested with
taskset on the same binary and profile and did not reproduce.
Cordial now says so when a native is missing instead of skipping it in
silence, and the report names its own answer. That is a diagnostic, not a fix:
the engine version there is 2.734.0.917 against 2.736.0.1408 here, and a JNI
method renamed or moved between builds cannot be tested from this machine. If
you hit this, the log now carries the evidence.
Measured and not changed
Three things were investigated at length and produced no code, which is worth saying so nobody reopens them from a title. The startup freeze holds nothing. Three frozen clients were caught under gdb on a signed-in profile. The engine thread is parked, not spinning — its poll count moves 199 in ten seconds at 1.6% of a core, which is the ceiling the idle backoff produces — and not one of 73 threads is on a mutex. It is waiting on an in-process pipe nobody writes to. That refutes two claims in the investigation notes, which have been corrected. No performance work was worth taking from mocktail. The lock traffic that a lock-free JNI scheme would have targeted is 4.7% of startup, and LBR call stacks put 177 of 179 caller frames insidelibroblox.so — the engine locking against
itself, with nothing on our side to make lock-free. Every other candidate died
the same way, on measurement.
All three clients were re-measured side by side, same method, same host, one
at a time, three passes each — because the standing comparison was a month old
on our side and older on everyone else’s:
What is still broken
Everything 0.13.0 listed, unchanged: the startup freeze when signed in — better characterised now, not fixed — the black canvas inside an experience, text in a box disappearing when you click off it, the editor lagging a text box that Roblox is animating, and the web-view dialog’s clicks and cursor. The TaskScheduler crash above is not fixed, only diagnosed and instrumented.gamepadType’s ordinals are still unestablished. Settling it needs a controller
and a game.
Three of this release’s fixes have not been seen working, each for a stated
reason: the / flash and the consent prompt (no compositor capture, no second
Settings window), the FastFlags colouring (no nested compositor), and the
plugin-download row in Settings. The mechanisms are established and the
components underneath them are tested. That is not the same as having watched
them work, and this note will be corrected rather than left wrong if any of them
turns out not to.