Avalonia / Desktop gotchas
Each one cost debugging time once. The code has comments at each site; this file is the index.
- The GL view isn't hit-testable by default.
OpenGlControlBasedraws through GL, not Avalonia's scene. Pointer events andCursorgo to the panel beneath unless the control implementsICustomHitTest(GameView.HitTest). Display-only overlays needIsHitTestVisible = falseso the pointer reaches the game. - Deactivation ≠ lost focus. Clicking another app deactivates the window
but keeps logical focus (
IsFocusedstays true), soOnLostFocusnever fires. Release held keys onWindow.Deactivatedtoo, becauseOnKeyUpnever arrives for a key released elsewhere. - The sim only advances while frames render.
GameLoop.Frameruns insideOnOpenGlRender. StoppingRequestNextFrameRendering(paused) stops the sim, and "keep simulating while minimized" would need a timer. - The render callback runs on the UI thread (verified on this Linux/X11
setup). The overlay and
FrameTimeStatsshare state without locks for that reason. Re-check if a platform moves rendering off-thread. - No pointer lock API in Avalonia 12.1.2. Free look reads deltas between
pointer positions and stalls at the window edge. Recentering uses a
platform call (
CursorWarp, X11XWarpPointer; off where it fails). - Escape vs focused controls. A clicked
CheckBoxtakes keyboard focus, soEscfor the options menu is a window-level tunnel handler, notGameView.OnKeyDown. - Tests: float vs double.
FixedStepClock.TickSecondsisfloat. Compare it against double arithmetic with a tolerance (~1e-6), not 9 places. - A clicked in-game
Buttonsteals the game's keys. It takes focus, so WASD stops reachingGameViewand Space presses the button. HUD buttons areFocusable = false(Ui.Action). - Raising
Button.ClickEventdoesn't toggle aCheckBox. Toggle logic lives inToggleButton.OnClick; scripted/pad presses useUi.Press. KeyBindingsinside a control resolves to Avalonia'sInputElement.KeyBindings, not ours — qualify the game's ownKeyBindingstype fully.- Invisible controls aren't templated.
IsVisible = falseskips measure/template, so the first real open pays for it (first NPC dialog ~170 ms).Ui.WarmUp()shows a zero-opacity card briefly instead. - Routed key events only reach controls on the route. A
KeyDownraised on the focusedGameViewtunnels window →GameScreen→GameView; a sibling panel (options) never sees it. Screen-wide key handling lives onGameScreen(e.g. key-rebind capture). - Scripted input vs X injection. On KDE Wayland
xdotoolkeys don't reach a focused XWayland window (and may type into the user's terminal); drive smoke runs withsmoke_run.sh --input FILE(routed events in-process). - The HUD is redrawn every frame over the GL view. The GL control changes every frame
and covers the window, so the compositor re-draws every overlay each frame — text is
the expensive part.
CacheMode = new BitmapCache()on HUD cards (Ui.Card) makes them re-render only on change; setRenderAtScaleto any scale transform above them or they blur. Merging text blocks into fewer visuals doesn't help. - Don't judge perf by CPU % on this machine (±50 % with other load). Use the
instr:smoke step, interleaved runs,PauseWhenInactiveoff; see game-loop spec §7.