← Devlog
Brass Ledger

A disabled button reads as a broken game

A tester told me a card in Brass Ledger was broken. It wasn't. It was a card they couldn't afford, and the button carried HTML's disabled attribute.

That attribute swallows the click whole. Not a shake, not a sound, not a line of text — nothing happens at all. From the outside that is indistinguishable from a bug, and it gets reported as one. I counted thirteen places in the game where a click died in silence.

So every refusal now explains itself in words, with the actual numbers in it:

Not enough energy: Ram costs 2, you have 0.

Not enough plunder: costs 50, you have 0.

Already struck one card out at this market.

The fix sounds like a text change and wasn't. Three things turned out to be structural:

The reason has to live next to the rule. whyYouCannotPlay sits in the combat engine beside the rule itself, and canPlay now calls it. Keep them apart and the first balance change makes "you can't" and "here's why" disagree.

The card has to stay clickable. aria-disabled instead of disabled, so the press registers, the card shakes, and the sentence appears. The cost of that: any test selector written as :not([disabled]) now thinks every card is playable. One full-run test had been stalling for two weeks because of exactly that — a bot clicking two unaffordable cards forever and never reaching End Turn. It failed on a timeout and looked merely slow.

The mouse was talking and the keyboard wasn't. CombatKeyboard had its own if (!playable) return — the same silent refusal through a different door, with its own copy of the rule that knew nothing about unplayable cards.

What I would take from this if I were starting again: a refusal is a message, not an absence. Any place in an interface where nothing happens is a place where the player assumes you are broken.

One thing I am still unsure about. In the dock, the reason is printed as a line under the button rather than a toast, because the toast tray mounts inside the run shell and the dock opens before it — the message would have gone nowhere. It works, but now one screen explains refusals differently from the other eight. Is a consistent-but-worse placement better than a correct-but-odd one? I genuinely don't know. Write to me if you have an opinion.

— Mark

Disagree, or have the measurement I missed? Tell me. Replies get read by the person who wrote this, because there is only one person.