Skip to content

Releases: luohoa97/cordial

Cordial 0.9.0 — you can actually download it

Choose a tag to compare

@luohoa97 luohoa97 released this 27 Aug 13:06

Cordial 0.9.0 — you can actually download it

Four package formats build and attach: AppImage, .deb, .rpm and an Arch
package
. Every release before this one carried either nothing or a single
Flatpak bundle, so this is the first that meets people in their own package
manager.

Read What is still broken before installing. The startup freeze is not fixed
and the AppImage's sign-in window has a known limitation.

Getting it

AppImage installs nothing and runs on any distribution — download, chmod +x, run. .deb needs Ubuntu 25.10 or newer; 24.04 LTS ships GTK 4.14 and
Cordial needs 4.20, which is the dependency floor rather than an oversight, so
LTS users want the AppImage or the Flatpak. .rpm targets Fedora 44.
Arch builds from the PKGBUILD.

Flatpak remains the recommendation if you have no reason to prefer another: it
is sandboxed and it updates itself.

Input follows the device you last used

Plug in a keyboard and mouse on a phone and Roblox switches to desktop controls;
touch the screen and it switches straight back, instantly. Cordial now does the
same, because the engine's own mechanism turned out to be simpler than any flag:
it exports a different entry point per device family, so the device identity is
the function called. On Android the app's Java layer routes; Cordial replaces
that layer, so the routing is Cordial's to do.

A single session-wide "is this touch or mouse" decision used to seed every
event. It is gone. A pointer event reaches the mouse path, a touch reaches the
touch path, a key reaches the key path, and none of them consults a setting.

Touchscreen support is wired end to end for the first time, including
multi-touch contacts — untested against real hardware, because the machine
this was built on has no touchscreen.

Text boxes draw the right font

The editor drew one hardcoded family. It now resolves the font the engine names
against the font tables shipped inside the Roblox archive you supplied, and
registers every shipped face rather than one.

This matters more than it sounds: while a text box is focused, the engine stops
drawing its own text, so what you see while typing is Cordial's rendering. The
wrong family meant characters changing shape under your cursor.

Fonts from Roblox's marketplace cannot work, and that is settled rather than
unimplemented.
The field the engine passes is a 32-bit integer against a
48-entry table of built-in fonts; marketplace font ids are ten digits and
overflow it by an order of magnitude, and the structure carries no string field
at all. An unrecognised font falls back to the default and says so in the log
rather than silently drawing the wrong one.

Gamepads: present, off, and honest about why

The plumbing is complete and disabled. Roblox exports six gamepad entry points
and this build has no way to announce a pad without saying what kind it is —
and nothing available says which number means Xbox and which means PlayStation.
Shipping a guess would show PlayStation owners Xbox button glyphs, which is a
real bug in other clients rather than a hypothetical.

CORDIAL_GAMEPAD=1 turns it on for anyone who wants to help establish the
mapping. No rumble: it is absent rather than stubbed.

What is still broken

The startup freeze, when signed in. The client reaches the home screen,
draws one frame and stops. Not fixed. It is now precisely characterised — the
engine skips a render-view teardown in 13 microseconds that a healthy run spends
7 milliseconds on, with no overlap across 136 runs — which is a much smaller
target than it was, and a fix candidate rather than a fix. Reopening usually
works, and it happens less on mains power than on battery.

Four theories were tested and discarded getting there, two of them announced as
fixes before a control refuted them. Both retractions are in the history.

The AppImage's embedded browser window will be blank on a host without
WebKitGTK 6.0 installed.
WebKitGTK spawns its network process by absolute path
and ignores the override meant to redirect it, so that one piece does not travel
inside the bundle.

Ordinary sign-in does not use that window. It appears for two-factor
prompts, email recovery and similar flows, so most people will never meet it;
if you use 2FA on your Roblox account, prefer the Flatpak, the .deb or the
.rpm, none of which are affected. Everything else in the AppImage works.
Please report it rather than assuming Cordial is broken.

A black canvas inside an experience. Joining works; what you see once there
often does not.

Voice chat does not work. The microphone stays shut and there is no
downlink.

Nothing is signed — not the Flatpak remote, not any of the four new formats.
The README explains what that does and does not protect you from, and does not
hide it.

Thanks

Sober, again. Its issue tracker is why several bugs here were recognised rather
than investigated from scratch, and it went first while it was much harder. No
Sober code was taken and none could be.

Cordial 0.8.0 — it fetches Roblox itself, and you can finally download it

Choose a tag to compare

@luohoa97 luohoa97 released this 27 Aug 03:16

Cordial 0.8.0 — it fetches Roblox itself, and you can finally download it

Two things changed that a user would notice. Cordial now obtains the Roblox
build on its own, verified, from a button; and this is the first release that
carries a file you can download.

The startup freeze is not fixed. It is worse understood than it was, which
is not the same thing, and §What is still broken says exactly where it stands
including two fixes that were announced and retracted in a single day. Read it
before installing if you are signed in.

Cordial downloads Roblox

Press Download Roblox on first run and it fetches the Android build, checks
it, and starts. No second program, no manual APK, nothing to find.

What it checks, because "download" should not mean "trust whatever arrives":

  • The archive must be signed by Roblox's own signing certificate. A mirror that
    alters a byte is refused, not used.
  • An archive whose v3 signature has been stripped to force the weaker v2 path
    is refused too. That attack works by deleting the newer signature block and
    relying on the verifier to fall back quietly; the scheme-3 stripping
    attestation (0xbeeff00d) is now read and a claimed-but-missing scheme is a
    refusal.
  • Downloads never land anywhere that belongs to another application. A build
    found in Sober's directory is read where it lies and never written to.

If you already have Sober, nothing changes and nothing is pressed — Cordial
finds that build and uses it in place.

The mirror is APKPure and it is a third party: it can be down, and it sees who
asked. That is why it is a button rather than something that happens to you.

You can download Cordial

AppImage, .deb, .rpm and an Arch package, built in CI on every push and
attached to this release.

Until now every release carried nothing at all. The Flatpak workflow did know
how to attach a bundle on a tag, but every run it ever had was a push to
main, so that path had never once fired.

The AppImage is the one to take if your distribution is not in that list, or if
you would rather not add a third-party Flatpak remote. It installs nothing.
It is also the least proven artifact here — see below.

Flatpak remains the recommendation for anyone without a reason to prefer
otherwise: it is sandboxed and it updates itself.

Updates

  • The updater reports the newest build compatible with your machine, not
    the newest build the mirror happens to hold. An announcement you cannot run
    is not an update.
  • The update badge clears once you have installed the update, rather than
    staying lit at something you already did.
  • Cancel a download and Cordial remembers where you stopped.
  • Cordial notices when the installed build changed underneath it, so a build
    that arrived from elsewhere is no longer invisible to the updater.

Audio

An OSS backend joins PipeWire, PulseAudio and ALSA, selected with
CORDIAL_AUDIO_HOST. It was asked for and the asking was the condition.
PipeWire remains the default and is the one measured against a control.

Plugins

A core event bus: Cordial publishes what it observes — launches, joins,
updates — and a plugin may listen. It may not veto, delay, or alter anything,
by construction rather than by policy. Building it found two bugs in the
existing plumbing, which is the usual argument for building a thing properly.

Smaller things you might hit

  • Cordial says when the disk is nearly full. A game that dies because it
    could not write used to say nothing, anywhere, and the engine will not
    mention it either.
  • Ctrl+V no longer pastes twice.
  • A password box now tells input methods that it is one.
  • The settings and update windows moved off a deprecated libadwaita window
    type; the update dialog follows the HIG for progress rather than swapping the
    button out from under you.
  • NOTICE now ships in every package. third_party/mocktail-webview/ is
    Apache-2.0 material and §4(d) requires that file to travel with binary
    distributions; no package built before this one included it. That was a
    licence compliance gap in everything shipped to date.

What is still broken

The startup freeze, on a signed-in profile. The client opens, reaches the
signed-in home screen, presents one frame and then spins. It is not fixed.

What is now known: it is render-side and happens after the app is ready — the
engine reports reaching its root screen in frozen runs too. It correlates
strongly with machine load. Across forty-five interleaved runs, eighteen
consecutive launches were healthy at a load average of 0.66 and twenty-one of
the next twenty-seven froze between 1.9 and 3.8 (p = 7.9e-08), across every arm
at once.

Two fixes were announced and withdrawn on 2026-08-27. One "cured" it by not
rendering at all — photographed, a blank grey window, which the survey scored
healthy because its verdict counts presented frames and an empty surface
presents faster than a real one. The other measured p = 0.0005 against a
control taken an hour earlier and p = 1.00 against one measured beside it.
Both are recorded rather than quietly deleted.

If you are hit by it, closing and reopening usually works, and a machine that
is not otherwise busy freezes far less.

A black canvas inside an experience. Joining works; what you see once you
are there frequently does not.

Voice chat does not work. The uplink is hooked and the microphone stays
shut; there is no downlink.

No gamepad or controller support. None, rather than partial. The engine
exports the natives and nothing here calls them yet.

The AppImage's web view is unconfirmed. It bundles WebKitGTK's helper
executables by hand, because they are spawned rather than linked and the
standard tooling does not follow them. If the sign-in window comes up blank,
that is why — the Flatpak is unaffected, and it is worth reporting rather than
assuming Cordial is broken.

Nothing is signed. Not the Flatpak remote, not any of the four new package
formats. What that does and does not protect you from is in the README, and it
is not hidden.

Thanks

Sober, again and specifically. Its issue tracker is why several bugs here were
recognised instead of investigated, its APK path is why Cordial finds a build
you already have, and it went first while it was much harder. No Sober code was
taken and none could be.

Cordial 0.7.0 — typing works

Choose a tag to compare

@luohoa97 luohoa97 released this 26 Aug 03:16

Six days, 193 commits. The headline is that text entry works — you can type
in a Roblox text box and see the characters — and that Cordial now has three
host audio backends, a verified in-experience web view, and a great deal more
instrumentation than it had.

Typing works, and it is tested rather than hoped

A focused Roblox TextBox now gets a real gtk::Text placed on it, in the box's
own font and colour, with a caret where a caret belongs. The engine sends the
geometry every time; Cordial could not receive it because the
NativeTextBoxInfo constructor hook was one argument short of the dex's
fifteen, so showKeyboard was handed a null and the whole path looked as though
Roblox volunteered nothing. It volunteers everything.

tools/text-input-e2e.py asserts thirty-six things about it and fails rather
than warns — typing, backspace, Home, End, insertion at the caret, shift-arrow
selection replaced by the next key, select-all overtyped, double-click word
selection, Delete, cut, a clipboard round trip, click-to-position at both ends
of the field through a real compositor click, Escape, refocus, and no text key
reaching the game while a box has focus. Four consecutive clean runs.

Two instruments had to be built before any of that could be believed. There was
no readback of typed text at all — the editor is a GTK widget and
cordial_screenshot photographs the engine's Vulkan swapchain, which cannot see
one — so cordial_textbox now reports what the field actually holds. And the
old harness ran under cage, whose headless seat never gains a keyboard, so
Cordial never bound its own wl_keyboard and the guard against double insertion
never ran. Under sway with a keyboard held open across the whole run, five keys
produce five characters. Measured, not inferred.

Audio: three backends, each verified against a real device

CORDIAL_AUDIO_HOST selects between pipewire (the default), pulse and
alsa. Each is dlopen'd and never linked, so none adds a runtime dependency,
and each reports what the device actually granted rather than what was asked
for — the engine opens a stream and reads back what it got, and FMOD multiplies
the burst by nine to size itself, so a guessed figure is a buffer nine times the
wrong size.

ALSA owns its own writer thread, because unlike the other two it is handed no
callback: snd_pcm_writei blocking is the pacing. docs/adr/ADR-023 records
the design, including what does not survive the port — an empty audio-sink
setting on PipeWire and PulseAudio means "follow the default sink and keep
following it", and ALSA resolves default once at open.

Two probes come with them, because waiting for the engine to open audio is not a
test: across six launches that reached a signed-in Home page, it opened a stream
on exactly one.

The in-experience web view, proved end to end

Account settings, buying Robux, anything the client keeps inside itself: the
dialog opens over the engine's canvas, a real page renders in it, and the
JavaScript bridge round-trips into signalJavascriptCallback. That last part
was invisible until now, because it only fires when a Roblox page decides to use
it; CORDIAL_WEBVIEW_BRIDGE_TEST=1 makes the page call it directly, through the
same handler, the same origin and size policy, and the same sink.

One real limitation found in the process and reported loudly rather than
silently: this build of WebKitGTK has no WebAuthn at all, so a passkey sign-in
cannot complete in that window.

X11 is a supported backend again

ADR-011 said X11 was not developed further and scheduled its deletion. ADR-024
reverses that, because the argument it was decided on never contemplated a user
who cannot run Wayland — and one turned up. Wayland stays primary. X11 does not
have the editor yet, so typing there is still invisible; the ADR says so and
says what it will cost.

A whole core back

The engine busy-polls ALooper_pollOnce(0) with no blocking point, so it
free-runs at whatever rate the machine sustains. After sixty-four consecutive
empty polls Cordial now sleeps 250µs before looking again, and any real event,
any wake and any new descriptor resets it. Against a control in one session:
9,670,516 polls a second at 99.7% of a core, down to 3,261 at 1.2%, with the
median frame rate unchanged at 240 against 237.

It is gated on the engine having drawn 120 frames, so it cannot touch startup.

Also

  • The window no longer leaves a trail, and the canvas no longer goes black when
    it is restacked — five commits of opaque-region and subsurface-stacking work.
  • Linking.openURL is answered, so external links open in your browser.
  • Held mouse buttons are released when the pointer leaves the canvas, and a
    pointer lock the compositor switched off is noticed and recovered.
  • The Android ABI is a property of the build now rather than a string hardcoded
    in two crates, which is the groundwork an ARM64 port needs.
  • cargo test --workspace runs in CI. It ran nowhere before.

Known and unfixed

A startup freeze on roughly a third of launches, measured over eighty runs
rather than guessed at: the client reaches the home page, presents a frame or
two, and stops. Relaunching is the only remedy today. docs/NEXT.md §0 has
everything established about it, including four theories killed by measurement
and one of mine retracted two commits after I published it.

The canvas goes black when a TextBox focuses inside an experience, which is
the top open bug.

Full history in docs/NEXT.md, which is candid about what is broken and keeps
its retractions rather than editing them away.

Cordial 0.5.2 — the Flatpak works

Choose a tag to compare

@luohoa97 luohoa97 released this 05 Aug 11:19

The Flatpak works. It never had, on any machine, and six separate faults
stood between the packaged build and a running client.

Every one was invisible from cargo run. That is the reason they survived: the
packaged build is a different machine — different libc, different filesystem
view, none of the session services — and nothing here had ever been launched by
a person from an installed package until today.

Install

flatpak remote-add --if-not-exists cordial \
    https://luohoa97.github.io/cordial/cordial.flatpakrepo
flatpak install cordial io.github.luohoa97.Cordial

You supply the Roblox build yourself; Cordial ships none. The setup dialog will
point you at the easiest way to get one.

The blocker: the linker was built with PATH_MAX=256

third_party/mcpelauncher-linker/CMakeLists.txt defines it, shrinking seven
char buf[PATH_MAX] buffers across four linker files — while the realpath and
readlink they are handed to are the host's, entitled to write 4096. The
client aborted at linker init:

*** buffer overflow detected ***: terminated

and the call site says the rest — mov $0x100,%edx into __realpath_chk.

The host build is the less trustworthy of the two results here. It makes the
same calls through plain realpath@plt with zero __realpath_chk in the
binary — unfortified, never checked. It has carried the same undersized buffers
all along, and a resolved path over 255 characters smashes that frame today with
nothing to report it. The Flatpak's runtime fortifies these calls, so it was the
first thing to notice a latent overflow rather than the thing that caused one.

The other five

  • The sandbox could not see the Roblox build its own dialog pointed at. "No
    Roblox build found" named the exact path it looked in, and that path held a
    97 MB base.apk the sandbox had no grant for. The instructions were right,
    the user followed them, and the program said they had not.
  • The session was being written to disk. With no org.freedesktop.secrets
    grant, the runtime found no service and fell back to a 0600 file — announcing
    it plainly. The install route the README recommends was the one build that
    kept the session off the keyring.
  • Every connection read as metered, because there was no system bus at all.
    Deliberately not fixed with the portal: NetworkMonitor needs no grant
    and was the obvious answer, but it reports metered as a boolean where
    NMMetered has five values, and only an explicit NO may enable a background
    download here. Going through it would have quietly rewritten that rule while
    looking tidier.
  • AT-SPI needed two grants, and one alone would have been a lie. The name
    makes GetAddress answer — with a socket path that is not in the sandbox. So
    granting only the name moves the failure from a lookup to a connect and looks
    like a fix.
  • CI could hang the queue indefinitely. No timeout-minutes, so GitHub's
    six-hour default applied while the concurrency group allows one run at a time.
    A run stuck in apt-get held everything behind it.

GameMode is not fixed and is not claimed to be. The name resolves now, so
the grant took; gamemoded declines to register the sandboxed process with
rc -1. Different problem, named as one.

Measured

Clean install from the published remote, local override reset so only the
package's own permissions applied:

[secrets] the session is kept in the desktop secret service
[accessibility] connected to the AT-SPI bus as :1.559
[android] display backend: Wayland
LOADED in 25ms
[android] vulkan: swapchain present mode FIFO -> MAILBOX
[roblox] datamodel notification: APP_READY Landing

cargo test --workspace: 460 passed, 0 failed.

Correcting 0.5.1

That release said the installed shell opening a window had not been observed,
and offered a name-collision hypothesis for why it exited immediately. The
hypothesis was right and it is now observed
— the launcher runs, draws its
window, finds a Roblox build and starts the client. The earlier exits were a
GApplication with a fixed id handing off to a development build that already
held the name: the single-instance mechanism working, not a fault.

Still broken

Text fields do not paint while focused. The pointer is not captured in first
person. X11 fullscreen segfaults. Web views are unimplemented. Audio initialises
and then fails with FMOD_ERR_OUTPUT_INIT.

There is no script execution, hooking or memory access — those are absent from
the API rather than disabled.

Cordial 0.5.1 — a Flatpak that actually builds, and an ID we own

Choose a tag to compare

@luohoa97 luohoa97 released this 05 Aug 10:01

The Flatpak builds for the first time on any machine, the remote exists, and
the application ID no longer claims a domain this project does not own.

The application ID is now io.github.luohoa97.Cordial

It was org.cordial.Cordial — a claim on cordial.org, a domain registered in
1999
and in active use by somebody else. Flathub requires an application ID
over a domain or forge account you demonstrably control, so the old one was
unsubmittable rather than merely untidy. The new one needs no domain, matches the
homepage, and is what <developer id="io.github.luohoa97"> in the metainfo had
been saying all along.

Done on the same day the remote went live, on purpose: a rename before a package
has users costs a git mv, and after it costs everyone their data.

Important

A Flatpak's data lives at ~/.var/app/<app-id>/. If you installed
org.cordial.Cordial during the ~40 minutes it existed, that install will
never update and its profiles, sign-in and extracted Roblox build stay behind
the old directory. flatpak uninstall --delete-data org.cordial.Cordial is the
whole of the fix.

The Flatpak had never built

The manifest asked for org.freedesktop.Platform, and the shell is GTK4 and
libadwaita end to end — that runtime carries neither. Every CI run had failed on
Package 'gtk4' not found: five in a row, unlooked at, under a commit titled
"A Flatpak that builds". Now org.gnome.Platform/org.gnome.Sdk 50.

That exposed a second thing immediately: the GNOME runtime is numbered 50 and the
freedesktop SDK extensions it inherits are numbered by the base runtime, 25.08.
Deriving all four refs from runtime-version asked for a rust-stable//50 that
does not exist.

The published remote definition could not be added

Both the workflow and cordial.flatpakrepo's own comment recorded that GPGKey=
with an empty value is how a .flatpakrepo states "unsigned", and that omitting
the line says the same thing only by accident. Backwards. Measured with
flatpak 1.18.0:

form result
GPGKey= error: Invalid gpg keyremote-add fails
omitted accepted; gpg-verify=false

Only a green run could expose this, because until one happened the generated file
had never existed to be added.

Everything else

  • GitHub Pages is enabled and the site is live at
    https://luohoa97.github.io/cordial/, sharing one deployment with the OSTree
    remote under /repo/.
  • Releases exist at all. v0.2.0 and v0.3.0 had been tagged and never
    published; v0.4.0 was a version bump that never got a tag.
    CHANGELOG.md is new.
  • The documentation table stopped at ADR-013 — ADR-014 through ADR-018 and
    HANDOVER.md were written and never listed.
  • unimplemented.rs's fallback log path skipped XDG_DATA_HOME, which inside a
    Flatpak names the real home the sandbox cannot write. It now matches every
    other path derivation in the tree.

Measured: remote-add accepted, remote-ls returning the application ref at
6.1 MB download and 16.1 MB installed, install placing both
cordial-shell and cordial-run in /app/bin, and cordial-run --help
answering from inside the sandbox. cargo test --workspace: 460 passed, 0
failed. appstreamcli validate: clean.

Not verified: that the installed shell opens a window. It exits 0 without one,
and the leading candidate is that GApplication with a fixed id is
single-instance by design and a development build already owned the name — but
the control has not been run, and this project does not report diagnoses it has
not tested.

Cordial ships no Roblox code, APK or assets — you supply the Android client
yourself.

Cordial 0.5.0 — the app bridge, and a register of what is missing

Choose a tag to compare

@luohoa97 luohoa97 released this 05 Aug 09:07

The app bridge, a register of what is missing, and a kernel sandbox under
plugins.

The app bridge was never given its surface or platform params

Sober makes 87 JNIAppBridge calls during a join; Cordial made 3. Both
UpdateSurfaceAppWithPlatformParams and UpdateSurfaceGameWithPlatformParams
are now driven after StartApp, with CORDIAL_SKIP_UPDATE_SURFACE=1 as the
control.

Whether this fixes the 304 disconnect is untested. It needs a join run on a
real account and has not had one. INFERRED.

Error 304 is not what it looked like

The join succeeds: connection at 4.0 s, replication for roughly 61 s, then
Disconnect reason received: 304 from the server. The websocket to
10.110.101.222:5052 that looked like the cause is a red herring — Sober opens
the identical connection and plays for 942 s. KeyRing is identical between the
two.

A register of everything unimplemented

Unresolved JNI symbols, called libc stubs, unregistered natives and placeholder
returns, printed as one report at exit. The JNI half only populates when libjnivm
is built with CORDIAL_JNI_TRACE=1, and the report says so when the section is
empty
— an empty list was very nearly published as "zero unresolved symbols",
which would have been a false negative produced by a switched-off instrument.

Graphics gets an explicit OpenGL ES option

And loses the FastFlag that never worked. FStringDebugGraphicsPreferredBackend
was measured to be inert: libroblox.so imports zero Vulkan symbols and 91
EGL/GL ones, and picks its backend by dlopen. Selection now works by
withholding the virtual libvulkan soname, which is the mechanism that actually
decides it.

A kernel sandbox under the plugin process

bwrap with --unshare-all, a private tmpfs and the entry module bound
read-only, sitting below Deno's zero permissions and the capability broker. Its
absence is a downgrade rather than a hole, and every spawn prints which layers
are in force. See ADR-018.

A Flatpak grant deliberately not taken. --talk-name=org.freedesktop.Flatpak
would have let Cordial create sub-sandboxes inside Flatpak — and, because
flatpak-spawn --sandbox and --host are the same D-Bus name, would equally
have handed every plugin arbitrary command execution on the host. A Flatpak
install keeps two layers instead of three; that is the correct trade.

Everything else

  • Ctrl+V pastes into any Roblox text box or chat.
  • The queued-link banner was showing part of an auth ticket. summarise()
    truncated a deep link to 64 characters, which was enough to include it. Fixed,
    with a test that a synthetic ticket cannot appear in the banner.
  • Updates: the build window is a changelog with one button under it, sourced
    from Roblox's Creator Hub release-notes table and rendered from markdown.
  • Download on Wi-Fi was the metered switch wearing the wrong name. One switch
    now: download on metered connections, off by default.
  • Per-profile VPN gate (#8): a profile marked vpn-required refuses to launch
    without pvpn rather than leaking the connection.
  • Deep links reach the engine; roblox-player:// does not, and the README says
    which does.

Known broken

Text fields do not paint while focused. The pointer is not captured in first
person. X11 fullscreen segfaults. Web views are unimplemented. Audio initialises
and then fails with FMOD_ERR_OUTPUT_INIT (51) at about t=3.3 s on a signed-in
session.


cargo test --workspace: 460 passed, 0 failed.

Cordial ships no Roblox code, APK or assets — you supply the Android client
yourself. There is no script execution, no hooking and no memory access; those
are absent from the API rather than disabled
(ADR-001).

Cordial 0.4.0 — sound comes out

Choose a tag to compare

@luohoa97 luohoa97 released this 05 Aug 09:07

Sound comes out, the pointer locks, and two claims are withdrawn.

  • Sound comes out, and the microphone is only open while Roblox is recording.
  • The pointer locks, and the run ends when the window closes.
  • The launcher stops imposing a session length. --run 30 was a debugging aid
    that had quietly become a default.
  • The busy-profile message names the process actually holding the lock instead
    of guessing.
  • The MangoHUD hint named two packages and sent people to the wrong one.

Retracted here: the 1 fps report. It was the desktop, not the engine.

Corrected here: ADR-015. Roblox publishes no Android build that Cordial may
ship, so Cordial may fetch a build and may never ship one.

Tagged retroactively, at the commit that set the workspace version to 0.4.0.

Cordial 0.3.0 — keys work in an experience

Choose a tag to compare

@luohoa97 luohoa97 released this 05 Aug 09:07

Keys work in an experience, and this project's frame-rate metric is retracted.

  • Keys work in an experience. nativePassKeyEvent wants evdev codes, not
    Android keycodes — which is why every keystroke had been arriving as the wrong
    key, or as nothing.
  • Present mode MAILBOX instead of FIFO: a flat 60 where FIFO gave a variable
    35–50.
  • pthread_once and thread-specific data are implemented in the shim. The
    stub that returned success for pthread_once could not be survived — a stub
    that lies is worse than one that fails.
  • The extracted engine cache never invalidated, so a new APK ran the old
    engine. Warm start now invalidates when the build changes.
  • The build is stamped with git describe, so a binary says which tree it
    came from, including -dirty.

Retracted here, and it matters: vkQueuePresentKHR counts were this
project's fps metric and they measure the wrong thing. Presents run at about 60 a
second for thirteen seconds and then drop to exactly 1.0 a second, identically on
X11 and Wayland — an idle throttle, not a frame rate. Synthetic pointer motion
holds 50–60 for a whole 240-second run and toggling it flips the rate both ways.
Every count recorded before 2026-08-02 is that curve integrated, and several were
quoted as evidence.

Also corrected: pthread_cond_t is 48 bytes in bionic, not 32. A recorded
finding was wrong.

Cordial 0.2.0 — you can sign in

Choose a tag to compare

@luohoa97 luohoa97 released this 05 Aug 09:07

The first release anyone could use. Everything before this was the bionic
linker port, the JNI layer and the framework work — 178 commits, none released.

  • Sign-in works via Quick Sign-in, and the session persists across restarts
    in the desktop keyring
    rather than in a plaintext file on disk.
  • Profiles: storage per account, owner-only directories, and an flock so one
    instance holds one profile. Two accounts run side by side.
  • A libadwaita shell that finds a Roblox build, explains how to get one when
    there isn't one, and launches the client beside itself.
  • A native Wayland backend — xdg_shell, EGL, input, and the
    zwp_text_input_v3 IME bridge — replacing the blank window.
  • Real plugin brokers: presence.set, notify.send, url.open and
    events.* do something, as payloads and effects rather than channels.
  • An AT-SPI accessibility bridge over Roblox's Android accessibility surface.
  • The scroll wheel works; every mouse button and a real delta are passed, so the
    camera can turn.

Retracted here: the advice to inject input with window-targeted XSendEvent.
Wayland has no such thing.

Known broken: text is invisible while typing, because Android draws it with a
widget. The cause is recorded; the fix is not in this release.

Cordial ships no Roblox code, APK or assets — you supply the Android client
yourself. Build from source: see CONTRIBUTING.md.