Enzo Markarian

The cursor moved. Nothing clicked.

A Windows game, a Mac, and six separate bugs stacked on top of each other — only one of which was the one everybody was talking about. A case study in directing a debugging effort, and in how often an error message points away from its own cause.

The problem

PEAK is a Unity 6 game that only runs on Windows. I wanted to play it on my Mac with friends. The usual answer is Wine — free software that translates Windows programs so they run on other systems — wrapped in an app called Whisky that makes it manageable.

The game launched. It rendered. The cursor moved and menus highlighted under it. But no click ever did anything. Keyboard fine. Mouse movement fine. Clicks: nothing.

Even the paid commercial alternative didn't fully solve it. Getting there meant finding six distinct problems, spread across every layer between the game and the screen.

Where each bug lived

Every box is a translation step. A Windows game speaks Windows; a Mac doesn't. Each layer converts one thing into another, and each one had somewhere to hide a bug.

The layer stack from game to GPU, with six bug locations marked PEAK — a Unity 6 game asks Windows for mouse and graphics 1 Windows input API (win32u) the part Wine has to re-implement 3 Wine — the translation layer decides which library actually gets used 4 5 DirectX → Metal (DXMT) turns Windows graphics into Apple graphics 2 6 Metal — Apple's graphics system native, fast, nothing wrong here The GPU Apple M2 Six numbered bugs. Only bug 1 was the one people write about.
Each layer translates for the one below it. A failure anywhere looks identical from the top: the game just doesn't work.

The six bugs

  1. The clicks that went nowhere. Unity 6 asks Windows for a newer kind of mouse message, then listens only for that kind. Wine replies "not implemented" and keeps sending the old kind. The clicks genuinely happened — nothing was listening. Movement uses a different path, which is why the cursor still moved and the failure looked so bizarre.
  2. Half of one graphics system, half of another. Whisky switches graphics translators by copying files. Its two packages contain different numbers of files, and nothing deletes the old ones — so switching leaves a mismatched set that can't start a game. This is a real bug in Whisky, and it re-breaks itself every time you launch from the app.
  3. A screen size that doesn't exist. On a Retina Mac, macOS reports the screen as smaller than it physically is. Unity saved that fake size; Wine checked it against real display modes; nothing matched. The game refused to start — permanently, because the bad value was saved.
  4. A setting that was never written down. Whisky tells Wine which graphics library to use only when Whisky itself launches the game. Start it any other way and Wine quietly falls back to a version that cannot work on a Mac.
  5. 1,325 debuggers. Wine automatically launches a debugger whenever a program crashes. In a crash loop that compounds — 1,325 debugger processes consumed 15 GB of memory and made the machine unusable. Killing them freed 13 GB instantly. Nothing warns you this can happen.
  6. A setting that doesn't exist anywhere. A widely-copied guide tells people to set a particular option to enable the graphics translator. That option exists in no version of Wine. Everyone following that advice silently gets nothing — and every "I tried it and it didn't help" conclusion built on it is meaningless.

The measurement that changed everything

A previous attempt had asked: is this rebuilt library the same as the one that ships? It compared the library's exported functions — the list of things it offers to other programs.

exports: 504 vs 504  →  "identical, theory eliminated"

That comparison could not possibly have found the difference. Exports are a library's public menu. They're declared in a list, and they don't change based on what you leave out when building. Comparing them proves almost nothing.

The right question was the opposite one: what does this library need from others? Its imports.

imports: 164 vs 118  →  46 missing, including the entire font system

An entire subsystem had been silently left out of the build. The check that said "identical" and the check that revealed the truth were both one command long. The difference was knowing which question to ask.

Then it got worse — usefully. After fixing that, the rebuilt library passed every check: imports matched, exports matched, all the right pieces were linked, every function it needed existed. It still didn't work. The cause was a missing search path — a single line telling the library where to look for its own dependencies. No amount of inspection would have caught it. Only running it and reading what it said.

Five errors that pointed the wrong way

Building the fix produced its own set of problems. Not one of them said what it meant.

What it saidWhat it actually was
C compiler cannot create executablesA space in a folder name split one argument into two. The compiler was fine.
Your bison version is too oldThe modern version was installed — deliberately hidden from the default path, so a 2006 copy won.
Build died generating fontsmacOS security silently strips certain settings when one program launches another.
(nothing — build succeeded)A library name was mis-read into an unusable string. Failed only later, at runtime.
(nothing — all checks passed)The missing search path above.

The pattern is worth keeping: when an error describes something that should obviously work, stop debugging the thing it names. Check paths, versions and environment instead.

How I actually worked

I didn't write the fix. The mouse implementation is CodeWeavers' work, published as open source because its licence requires it. What I did was direct the effort — and the parts that mattered were mostly about process.

I set the rules before the work started

Research before trial and error. Free software only. Prefer whichever fix survives future updates. Explain it to me well enough that I understand it. Those constraints shaped every decision that followed.

I challenged an answer I couldn't verify

I was told the commercial software had already fixed right-click. I knew it hadn't, because I'd played the game and it didn't work. I had no way to evaluate the technical argument — it came with disassembled machine code attached. I pushed back anyway, on the strength of what I'd seen.

That forced a re-verification, and the earlier analysis turned out to have been incomplete. Deferring to the confident technical answer would have been the wrong call.

I noticed a missing abstraction

Backup scripts were multiplying — restore this, restore that. I suggested version control instead. The scripts weren't the problem; they were a symptom of not having the right tool.

I tried to break it deliberately

Once it worked, I went back in and changed every setting I could find that might break it — windowed, fullscreen, resolution, launching from different places. That's how bug 2 was caught: it only appears when you launch the game a particular way.

Result

What I'd claim, and what I wouldn't. I did not implement a Windows API, build a compiler toolchain, or read assembly. I directed a multi-day debugging effort, set its constraints, caught an error in its analysis, and made sure the result was verified rather than assumed. That's the honest version — and it's the one I can defend in a conversation.

Full technical write-up, with the evidence for every claim: the project repository.