> ## Documentation Index
> Fetch the complete documentation index at: https://cordial.mintlify.site/llms.txt
> Use this file to discover all available pages before exploring further.

# Cordial 0.13.0 — the plugins were never being started

Fifty-nine commits. The one that matters most is a single line: `start_all`
looked in one plugin directory and there are two, so **every first-party plugin
Cordial has ever shipped was listed in Settings, granted, switched on, and never
run.**

## Built-in plugins now start

`plugin_host::start_all` discovered `manifest::plugin_root()` — the *user*
directory, `~/.local/share/cordial/plugins`. The system directory, where every
plugin that ships with Cordial is installed, was read by the settings window to
list them and by `flags::collect` to read their flag files, and by nothing at
all to run them.

So Discord Rich Presence had never worked, and could not have. The plugin was
present, consented to and granted everything it asked for; its process was never
spawned.

It was found by reading a profile rather than the code. `plugin.log` had lines
from `fps-flex` and from nothing else, ever — and `fps-flex` turned out to be
the one plugin with a copy in the *user* directory, because it had been
installed by hand during testing. Everything else existed only under
`/usr/share/cordial/plugins`.

This is also why built-in plugins could not be tested during development.
Packaging did not help; they were never started from anywhere.

## Discord Rich Presence

Working, and with rather more than it had.

**It recovers.** The plugin set a presence twice — once at launch, once at ready
— and then nothing. Cordial's broker reconnects to Discord's socket on *every*
`presence.set`, and says so in a comment explaining why it has no retry loop of
its own: the caller is meant to be the retry. Nothing ever was. If Discord was
not running at those two instants, presence never appeared and waiting did not
help. It now re-sends every twenty seconds, which is inside Discord's rate limit
and picks Discord up within half a minute of it being opened.

**A game's own presence reaches Discord.** `bloxstrap_rpc` had parsed
BloxstrapRPC since presence landed and nothing had ever delivered it — the
module had no callers at all. Cordial now watches the engine's log, folds the
partial updates a game sends, and publishes them to the plugin.

**Two buttons**, of the two Discord allows: "Join server" pointing at a
`roblox://` deep link naming the actual server instance, or "See game page" when
the instance is not known, and always Cordial's own link. The deep-link shape
was read from Bloxstrap's source (MIT) rather than guessed — it had been guessed
at twice before in this project and got wrong both times.

**And it no longer says "Starting up" forever.** That state only advanced on a
`client.ready` event that does not always arrive. Discord already renders
"Playing Cordial" from the application itself, so both fields were saying, less
reliably, what the header said anyway.

## Controllers

On by default, and with a switch in Settings for turning them off.

The caveat is unchanged and worth repeating: **the button glyphs Roblox draws
may name the wrong brand.** Which integer selects which brand is still
unestablished. The buttons work; Sober has the same fault (#584, #1810) and
nobody there reports a button that stopped working. `CORDIAL_GAMEPAD_TYPE=<n>`
overrides it, and if you find the value that draws your pad's own glyphs, please
say so — it settles this for everybody.

**A phantom controller was fixed on the way.** joydev binds to anything
advertising ABS\_X/ABS\_Y, so a keyboard remapper's virtual mouse became
`/dev/input/js0` and Roblox was told a pad had arrived with nothing plugged in.
Cordial now applies udev's own test — a button in `BTN_JOYSTICK..BTN_THUMBR` —
rather than a list of device names to reject.

## Settings

* **Frame pacing.** Mailbox, FIFO, Immediate, Automatic. Mailbox is the default:
  FIFO shipped as the default for about an hour and was reported floaty within
  it, which is a queue against the display clock and is what FIFO is. FIFO saves
  power and is one row away, with the cost stated on the row.
* **FastFlags**, edited as text in the same format Bloxstrap uses, so a list
  pastes straight in. Applying writes the canonical document back into the box,
  so tabs and stray indentation normalise themselves.
* **Controllers**, on or off.
* **Session**: quit when you leave a game, and whether to pass the browser's
  sign-in ticket (below).
* **Developing a plugin**: load one from the folder you are writing it in.

## Plugins

* **Load unpacked.** Point at the folder holding a plugin's `plugin.json` — not
  a folder plugins are kept in — and it loads from where you are editing it.
* **It reloads as you edit**, using Deno's own `--watch`. Only unpacked plugins:
  an installed one does not change under a running client.
* **A plugin that failed to start says so on its row**, instead of failing to a
  `println!` that a packaged launch has no terminal for.
* **A session state API.** `state.get` returns the place, universe, server
  instance, server address, user id and join time. Cordial keeps it so that
  plugins do not each fold the event stream into a private copy and disagree.
  It needs a new `state.read` capability — `lifecycle.read`'s consent text ends
  "Not what you play", and this is the one that is.
* **`hide-gui`**, a new built-in. Roblox gates its own interface-hiding
  shortcuts behind an account being in a particular group;
  this names one. Bloxstrap's group is the default because that is the one the
  shortcuts are documented against. **The shortcuts only work if your account is
  in the group, and Cordial cannot check** — if nothing happens, that is the
  first thing to look at.

## Signing in from a browser launch, and why it is off

A play link from roblox.com carries a one-time authentication ticket. That is
what makes clicking play sign you in on Windows. Cordial has always dropped it.

There is now a switch. It is **off**, and stays off until somebody tests it,
because two things are true at once: it hands a live credential to the engine,
and **it is not established that this engine accepts one.** The old code said
the Android client "has no use for" the ticket, which was asserted without a
citation and does not survive contact with the binary — `AuthTicket`,
`redeem`, `/v1/authentication-ticket/redeem` and
`WebLoginProtocolHandleAuthTicket` are all in it. That proves the strings exist
and nothing more. Sober runs the same client and does not do this either.

If you turn it on and clicking play on the website signs you in, that is a
finding worth reporting.

## Everything else

Escape has always released a captured pointer, and keeps it released for as long
as the game goes on asking — a game that grabs the cursor and will not let go is
one key away. That worked for weeks and was documented nowhere.

apt, dnf and pacman repositories are published and installing. The system
cursor no longer appears when a text box takes focus. A web-view dialog now
takes the clicks aimed at it. [ADR-029](/adr/ADR-029-overlays-are-three-decisions)
records what an overlay actually is here, after five separate bugs each blamed
on the wrong one of its three parts.

## What is still broken

Everything 0.12.0 and 0.12.1 listed, unchanged: **the startup freeze when signed
in**, the black canvas inside an experience, and the second half of the CachyOS
crash.

**Text in a box disappears when you click off it.** This is a regression and it
is ours. The engine draws a text box's contents once it loses focus — measured
here on 2026-08-03, with a table — and something between then and now stopped
it. The editor overlay did not exist when that was measured, so the blur path is
new since. Not diagnosed.

**The editor lags a text box that Roblox is animating.** Its geometry is polled
at 10 Hz against a 60 fps animation, so while a box slides the editor trails it
— and clicking off is blocked until it settles, because the region GTK accepts
clicks in is at the editor's last known position.

**Three fixes in this release had not been seen working when these notes were
written. Two of them have since been.** See the addendum at the end.

The one that stands: the web-view dialog's clicks and cursor. Those cannot be
exercised by the development control surface at all, for the reason 0.12.1's
notes gave — its click verbs call the engine's input entry points directly, and
the checks that matter live in the Wayland pointer listener, which only fires
for a real seat.

**`gamepadType`'s ordinals are still unestablished.** The probe was run and the
screens reachable before signing in draw no per-type glyphs at all, so that
instrument reads out nothing. Settling it needs a controller and a game.

## Addendum, 2026-09-01: the Discord presence was tested in a real game

Written after the release, because the notes above say two things that are no
longer true and a release note that stays wrong is worse than one that admits
a correction.

Cordial was run against a signed-in profile and joined a real experience. The
measurement is Discord's own reply to each `SET_ACTIVITY`, which echoes what it
stored — quoted here verbatim, with `evt: null` meaning accepted:

```
{"cmd":"SET_ACTIVITY","data":{"application_id":"1543200871767212062",
 "buttons":["Join server","Cordial on GitHub"],
 "metadata":{"button_urls":[
   "roblox://experiences/start?placeId=17625359962&gameInstanceId=a7727e9e-2c06-4159-b8aa-46892f188ce7",
   "https://github.com/luohoa97/cordial"]},
 "name":"Cordial","platform":"desktop","timestamps":{"start":1788249818000},
 "type":0},"evt":null,"nonce":"cordial-2"}
```

The `gameInstanceId` is the server the client actually joined, byte for byte
the same UUID the engine log named three lines earlier. That is the whole
chain: engine log, log watcher, plugin, broker, Discord.

Thirteen activities went across in one session and every one was accepted: the
launch presence with one button, the game presence with two, the twenty-second
heartbeats — which kept the correct server rather than drifting — and the clear
on shutdown, which Discord confirmed with `data: null`.

**A game's own BloxstrapRPC reaches Discord too**, with `details`, `state` and
the game's `timeStart` replacing Cordial's while both buttons survive. **This
one is a qualified result and the qualification matters**: the experience used
for the join emits no `[BloxstrapRPC]` lines at all, so the line was appended
to the engine log file the live watcher was really tailing. That exercises
Cordial's parse, fold, publish and broker path against a real log and a running
client. It does not show that any particular game emits such a line, which is
the game's business and not Cordial's.

**One bug was found by trying to measure this.** The broker read Discord's
reply and threw it away, so an activity Discord *refused* — which it refuses
with an equally well-formed frame carrying `evt: "ERROR"` — was reported to the
plugin as `ok` and written to the plugin log as `ok`. Every "came back: ok" in
a log from 0.13.0 or earlier means "Discord answered", not "Discord accepted".
Fixed after the release; the log tells the truth from the next version.


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.