GHAMRA STUDIOS

· v0.2.0

Say it out loud

Mara the Blacksmith answering the player in the dialogue panel, with the text field open below the exchange.

You talk to people in this game by typing. That’s the whole interface — a text field, and whatever you actually want to say into it.

Now you can hold Alt+M and say it instead.

Dictation, not push-to-send

The obvious version of this feature sends what it heard. We didn’t build that one, and the reason is a name.

Speech recognition will mangle Mor’Dhul every single time. It will mangle every proper noun this game invents, because the game invents them at runtime and no recogniser has ever seen them. A design that sent what it heard would spend a real language-model call on a sentence nobody said, in a world where that sentence then becomes part of the record.

So the transcript is appended to the field. The caret parks at the end. The noun-completion list opens on the half-heard name so you can fix it in one keystroke. Then you press Enter, or you don’t.

The microphone is an input method. It is not a commit.

The measurement came first

The ticket named the real risk before any code was written: speech recognition needs the processor, and it has to keep up while the machine is busy with everything else. If dictation stutters under load, it’s useless.

So the spike happened first, and the script that produced it is checked in:

 5s of speech   0.04s idle   0.05s under heavy load
11s of speech   0.08s idle   0.08s under heavy load

M2 Max, ggml-base.en, Metal — roughly 110× realtime, with no measurable degradation under load.

One detail in that script matters: it prints how much work the background load actually did during the timing run. Two identical numbers are exactly what you’d also get from a load that had quietly finished before the test started, and a benchmark that can’t tell success from a no-op isn’t a benchmark.

That figure will not travel to a machine without a GPU backend. The doc says to re-measure rather than assume, and says what to change when it’s worse.

Never a silent no-op

The branch that matters most is the one where there is no microphone.

The rules live in a pure file so that branch is unit-tested rather than hoped for. Six refusal sentences — one per way this can be unavailable — and a test asserting that no unavailable state is ever silent. The button is built either way: present and greyed with the reason in its tooltip, never hidden. The key raises the same sentence as a toast whether the button is disabled or not.

macOS makes this harder in a way worth knowing about: a refused microphone permission isn’t detectable up front. The device is still listed. Capture still starts. Every frame is just zero. So the only honest moment to say “you haven’t granted permission” is after the player has held the key and got nothing — and that’s what it does.

macOS only, and that’s a decision

Windows is deferred deliberately, not overlooked. There is no Windows machine in the loop that produced this, and we have a standing reminder in the tracker of what happens when we make a runtime claim about Windows anyway.

Everything preparable from here is prepared: the Windows library is in the same pinned release, the export preset already carries the model, and the plan doc lists the exact six steps that machine still owes.

The addon is fetched, not committed

addons/ is otherwise tracked on purpose — a fresh clone should just run. The speech addon is the single exception, and only for size: about 200MB of binaries across seven platforms, plus a 141MB model.

So there’s a fetch script that pins the tag and the model, verifies both against a recorded SHA-256, and pipes nothing into a shell.

Which makes absent a supported state — a fresh clone, and CI. Nothing in the source names the capture class at compile time; it’s loaded by path. The game runs fine without it and tells you why the button is grey.

← All entries